/*
 * domain.c
 *
 * MontaVista IPMI code for handling IPMI domains
 *
 * Author: MontaVista Software, Inc.
 *         Corey Minyard <minyard@mvista.com>
 *         source@mvista.com
 *
 * Copyright 2002,2003 MontaVista Software Inc.
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public License
 *  as published by the Free Software Foundation; either version 2 of
 *  the License, or (at your option) any later version.
 *
 *
 *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 *  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 *  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 *  TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 *  USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this program; if not, write to the Free
 *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#include <OpenIPMI/ipmi_conn.h>
#include <OpenIPMI/ipmiif.h>
#include <OpenIPMI/ipmi_sdr.h>
#include <OpenIPMI/ipmi_msgbits.h>
#include <OpenIPMI/ipmi_err.h>
#include <OpenIPMI/ipmi_auth.h>

#include <OpenIPMI/internal/locked_list.h>
#include <OpenIPMI/internal/ilist.h>
#include <OpenIPMI/internal/ipmi_event.h>
#include <OpenIPMI/internal/ipmi_int.h>
#include <OpenIPMI/internal/ipmi_oem.h>
#include <OpenIPMI/internal/ipmi_utils.h>
#include <OpenIPMI/internal/ipmi_domain.h>
#include <OpenIPMI/internal/ipmi_entity.h>
#include <OpenIPMI/internal/ipmi_mc.h>

#ifdef DEBUG_EVENTS
static void
dump_hex(const unsigned char *data, int len)
{
    int i;
    for (i=0; i<len; i++) {
	if ((i != 0) && ((i % 16) == 0)) {
	    ipmi_log(IPMI_LOG_DEBUG_CONT, "\n  ");
	}
	ipmi_log(IPMI_LOG_DEBUG_CONT, " %2.2x", data[i]);
    }
}
#endif

/* Rescan the bus for MCs every 10 minutes by default. */
#define IPMI_AUDIT_DOMAIN_INTERVAL 600

/* Re-query the SEL every 10 seconds by default. */
#define IPMI_SEL_QUERY_INTERVAL 10

/* Timer structure for rescanning the bus. */
typedef struct audit_domain_info_s
{
    int           cancelled;
    os_handler_t  *os_hnd;
    ipmi_lock_t   *lock;
    ipmi_domain_t *domain;
} audit_domain_info_t;

/* Used to keep a record of a bus scan. */
typedef struct mc_ipmb_scan_info_s mc_ipmb_scan_info_t;
struct mc_ipmb_scan_info_s
{
    ipmi_addr_t         addr;
    unsigned int        addr_len;
    ipmi_domain_t       *domain;
    ipmi_msg_t          msg;
    unsigned int        end_addr;
    ipmi_domain_cb      done_handler;
    void                *cb_data;
    mc_ipmb_scan_info_t *next;
    unsigned int        missed_responses;
    int                 cancelled;
    int                 timer_running;
    os_handler_t        *os_hnd;
    os_hnd_timer_id_t   *timer;
    ipmi_lock_t         *lock;
};

/* This structure tracks messages sent to the domain, it is primarily
   here so messages can be rerouted to other connections when a
   connection fails. */
typedef struct ll_msg_s
{
    ipmi_domain_t                *domain;
    int                          con;

    ipmi_msg_t                   msg;
    unsigned char                msg_data[IPMI_MAX_MSG_LENGTH];

    ipmi_addr_response_handler_t rsp_handler;
    ipmi_msgi_t                  *rsp_item;

    long                         seq;

    int                          side_effects;

    ilist_item_t link;
} ll_msg_t;

typedef struct activate_timer_info_s
{
    int           cancelled;
    ipmi_domain_t *domain;
    os_handler_t  *os_hnd;
    ipmi_lock_t   *lock;
    volatile int  running;
} activate_timer_info_t;

typedef struct domain_check_oem_s domain_check_oem_t;

typedef struct mc_table_s
{
    unsigned short size;
    unsigned short curr;
    ipmi_mc_t      **mcs;
} mc_table_t;

struct ipmi_domain_s
{
    /* Used for error reporting. We add an extra space at the end, thus
       the +1. */
    char name[IPMI_DOMAIN_NAME_LEN+1];

    /* Used to handle shutdown race conditions. */
    int             valid;
    int             in_shutdown;

    /* Is anyone using this domain? */
    unsigned int    usecount;

    /* Used to handle startup race conditions. */
    int             in_startup;

    /* OS handler to use for domain operations. */
    os_handler_t *os_hnd;

    /* A lock for handling miscellaneous data changes. */
    ipmi_lock_t *domain_lock;

    /* The main set of SDRs on a BMC. */
    ipmi_sdr_info_t *main_sdrs;

    /* The sensors that came from the main SDR. */
    ipmi_sensor_t **sensors_in_main_sdr;
    unsigned int  sensors_in_main_sdr_count;

    /* The entities that came from the device SDR on this MC are
       somehow stored in this data structure. */
    void *entities_in_main_sdr;

    /* OEM data for OEM code. */
    void                            *oem_data;
    ipmi_domain_destroy_oem_data_cb oem_data_destroyer;

    /* The type of domain, defaults to unknown */
    enum ipmi_domain_type domain_type;

    /* Major and minor versions of the connection. */
    unsigned int major_version : 4;
    unsigned int minor_version : 4;
    unsigned int SDR_repository_support : 1;

    /* A special MC used to represent the system interface. */
    ipmi_mc_t *si_mc;

    /* Used for generating unique numbers for a domain. */
    unsigned int uniq_num;

#define IPMB_HASH 32
    mc_table_t ipmb_mcs[IPMB_HASH];
#define MAX_CONS 2
    ipmi_mc_t *sys_intf_mcs[MAX_CONS];
    ipmi_lock_t *mc_lock;

    /* A list of outstanding messages.  We use this so we can reroute
       messages to another connection in case a connection fails. */
    ilist_t     *cmds;
    ipmi_lock_t *cmds_lock;
    long        cmds_seq; /* Sequence number for messages to avoid
			     reuse problems. */
    long        conn_seq[MAX_CONS]; /* Sequence number for connection
				       switchovers to avoid handling
				       old messages. */

    locked_list_t            *event_handlers;
    locked_list_t            *event_handlers_cl;
    ipmi_oem_event_handler_cb oem_event_handler;
    void                      *oem_event_cb_data;

    locked_list_t            *new_sensor_handlers; /* callbacks for
                                             OEM-specific sensors*/

    ipmi_domain_shutdown_cb shutdown_handler;

    /* Are we in the middle of an MC bus scan? */
    int scanning_bus_count;

    ipmi_entity_info_t    *entities;
    ipmi_lock_t           *entities_lock;

    ipmi_lock_t   *con_lock;
    int           working_conn;
    ipmi_con_t    *conn[MAX_CONS];
    int           con_active[MAX_CONS];
    unsigned char con_ipmb_addr[MAX_CONS][MAX_IPMI_USED_CHANNELS];

    int           con_up[MAX_CONS];

    /* A list of connection fail handler, separate from the main one. */
    locked_list_t *con_change_handlers;
    locked_list_t *con_change_cl_handlers;

    /* Are any low-level connections up? */
    int connection_up;

    /* If we got some type of invalid return from the BMC, we mark
       this and retry at audit intervals. */
    int got_invalid_dev_id;

    /* Are we in the process of connecting? */
    int connecting;

#define MAX_PORTS_PER_CON 16
    /* -1 if not valid, 0 if not up, 1 if up. */
    int           port_up[MAX_PORTS_PER_CON][MAX_CONS];

    /* Should I do a full bus scan for devices on the bus? */
    int           do_bus_scan;

    /* Timer for rescanning the bus periodically. */
    unsigned int        audit_domain_interval; /* seconds between checks */
    os_hnd_timer_id_t   *audit_domain_timer;
    audit_domain_info_t *audit_domain_timer_info;

    /* This is a list of all the bus scans currently happening, so
       they can be properly freed. */
    mc_ipmb_scan_info_t *bus_scans_running;

    ipmi_chan_info_t chan[MAX_IPMI_USED_CHANNELS];
    char             chan_set[MAX_IPMI_USED_CHANNELS];
    unsigned char    msg_int_type;
    unsigned char    event_msg_int_type;

    /* A list of handlers to call when an MC is added to the domain. */
    locked_list_t *mc_upd_handlers;
    locked_list_t *mc_upd_cl_handlers;

    /* A list of IPMB addresses to not scan. */
    ilist_t     *ipmb_ignores;
    ipmi_lock_t *ipmb_ignores_lock;

    /* This is a timer that waits a little while before activating a
       connection if all connections are not active.  It avoids race
       conditions with activiation. */
    os_hnd_timer_id_t     *activate_timer;
    activate_timer_info_t *activate_timer_info;

    unsigned int default_sel_rescan_time;

    /* Used to inform the user that the main SDR has been read. */
    ipmi_domain_cb SDRs_read_handler;
    void           *SDRs_read_handler_cb_data;

    /* Used to inform OEM code that the user has been informed that
       the connection is up. */
    ipmi_domain_ptr_cb con_up_handler;
    void               *con_up_handler_cb_data;

    /* Fixups for SDRs. */
    ipmi_domain_oem_fixup_sdrs_cb fixup_sdrs_handler;
    void                          *fixup_sdrs_cb_data;

    unsigned int       fully_up_count;
    ipmi_domain_ptr_cb domain_fully_up;
    void               *domain_fully_up_cb_data;

    /* Used to inform the user that the bus scanning has been done */
    ipmi_domain_cb bus_scan_handler;
    void           *bus_scan_handler_cb_data;

    /* If we are running a domain OEM check, then this will be the
       check that is running.  Otherwise it is NULL. */
    domain_check_oem_t *check;

    int                       close_count;
    ipmi_domain_close_done_cb close_done;
    void                      *close_done_cb_data;

    i_ipmi_domain_fru_setup_cb fru_setup_cb;
    void                      *fru_setup_cb_data;

    /* Anonymous attributes for the domain. */
    locked_list_t *attr;

    /* Statistics for the domain. */
    locked_list_t *stats;

    /* Keep a linked-list of these. */
    ipmi_domain_t *next, *prev;

    /* Cruft... */
    struct ipmi_domain_mc_upd_s     *mc_upd_cruft;
    struct ipmi_event_handler_id_s  *event_cruft;
    struct ipmi_domain_con_change_s *con_change_cruft;

    ipmi_domain_entity_cb cruft_entity_update_handler;
    void                  *cruft_entity_update_cb_data;

    ipmi_ll_stat_info_t *con_stat_info;

    /* Option processing */
    unsigned int option_all : 1;
    unsigned int option_SDRs : 1;
    unsigned int option_SEL : 1;
    unsigned int option_FRUs : 1;
    unsigned int option_IPMB_scan : 1;
    unsigned int option_OEM_init : 1;
    unsigned int option_set_event_rcvr : 1;
    unsigned int option_set_sel_time : 1;
    unsigned int option_activate_if_possible : 1;
    unsigned int option_local_only : 1;
    unsigned int option_local_only_set : 1;
    unsigned int option_use_cache : 1;
};

/* A list of all domains in the system. */
static locked_list_t *domains_list;

static void domain_audit(void *cb_data, os_hnd_timer_id_t *id);

static int domain_send_mc_id(ipmi_domain_t *domain);

static void cancel_domain_oem_check(ipmi_domain_t *domain);

static void real_close_connection(ipmi_domain_t *domain);

static void free_domain_cruft(ipmi_domain_t *domain);

static void ll_con_changed(ipmi_con_t   *ipmi,
			   int          err,
			   unsigned int port_num,
			   int          still_connected,
			   void         *cb_data);

static void ll_addr_changed(ipmi_con_t    *ipmi,
			    int           err,
			    const unsigned char ipmb_addr[],
			    unsigned int  num_ipmb_addr,
			    int           active,
			    unsigned int  hacks,
			    void          *cb_data);

/***********************************************************************
 *
 * Some general utilities
 *
 **********************************************************************/

static int
first_working_con(ipmi_domain_t *domain)
{
    int i;

    for (i=0; i<MAX_CONS; i++)
	if (domain->con_up[i])
	    return i;
    return -1;
}

static int
first_active_con(ipmi_domain_t *domain)
{
    int i;

    for (i=0; i<MAX_CONS; i++)
	if (domain->con_up[i] && domain->con_active[i])
	    return i;
    return -1;
}

static int
get_con_num(ipmi_domain_t *domain, ipmi_con_t *ipmi)
{
    int u;

    for (u=0; u<MAX_CONS; u++) {
	if (ipmi == domain->conn[u])
	    break;
    }

    if (u == MAX_CONS) {
	ipmi_log(IPMI_LOG_SEVERE,
		 "%sdomain.c(get_con_num): "
		 "Got a connection change from an invalid domain",
		 DOMAIN_NAME(domain));
	return -1;
    }

    return u;
}

static void
deliver_rsp(ipmi_domain_t                *domain, 
	    ipmi_addr_response_handler_t rsp_handler,
	    ipmi_msgi_t                  *rspi)
{
    int used = IPMI_MSG_ITEM_NOT_USED;

    if (rsp_handler)
	used = rsp_handler(domain, rspi);

    if (!used)
	ipmi_free_msg_item(rspi);
}

/***********************************************************************
 *
 * Used for handling detecting when the domain is fully up.
 *
 **********************************************************************/
void
i_ipmi_get_domain_fully_up(ipmi_domain_t *domain, const char *name)
{
    ipmi_lock(domain->domain_lock);
    domain->fully_up_count++;
    ipmi_unlock(domain->domain_lock);
}

void
i_ipmi_put_domain_fully_up(ipmi_domain_t *domain, const char *name)
{
    ipmi_lock(domain->domain_lock);
    domain->fully_up_count--;
    if (domain->fully_up_count == 0) {
	ipmi_domain_ptr_cb domain_fully_up;
	void               *domain_fully_up_cb_data;

	domain_fully_up = domain->domain_fully_up;
	domain_fully_up_cb_data = domain->domain_fully_up_cb_data;
	domain->domain_fully_up = NULL;
	ipmi_unlock(domain->domain_lock);
	i_ipmi_entities_report_mcs_scanned(domain->entities);
	if (domain_fully_up)
	    domain_fully_up(domain, domain_fully_up_cb_data);
	return;
    }
    ipmi_unlock(domain->domain_lock);
}

int
ipmi_domain_is_fully_up(ipmi_domain_t *domain)
{
    return domain->fully_up_count == 0;
}

/***********************************************************************
 *
 * Domain data structure creation and destruction
 *
 **********************************************************************/

static locked_list_t *domain_change_handlers;

int
ipmi_domain_add_domain_change_handler(ipmi_domain_change_cb handler,
				      void                  *cb_data)
{
    if (locked_list_add(domain_change_handlers, handler, cb_data))
	return 0;
    else
	return ENOMEM;
}

int
ipmi_domain_remove_domain_change_handler(ipmi_domain_change_cb handler,
					 void                  *cb_data)
{
    if (locked_list_remove(domain_change_handlers, handler, cb_data))
	return 0;
    else
	return EINVAL;
}

typedef struct domain_change_info_s
{
    enum ipmi_update_e op;
    ipmi_domain_t      *domain;
} domain_change_info_t;

static int
iterate_domain_changes(void *cb_data, void *item1, void *item2)
{
    domain_change_info_t  *info = cb_data;
    ipmi_domain_change_cb handler = item1;

    handler(info->domain, info->op, item2);
    return LOCKED_LIST_ITER_CONTINUE;
}

static void
call_domain_change(ipmi_domain_t      *domain,
		   enum ipmi_update_e op)
{
    domain_change_info_t info = { op, domain };
    locked_list_iterate(domain_change_handlers, iterate_domain_changes, &info);
}

int
i_ipmi_domain_in_shutdown(ipmi_domain_t *domain)
{
    return domain->in_shutdown;
}

static void
iterate_cleanup_mc(ipmi_domain_t *domain, ipmi_mc_t *mc, void *cb_data)
{
    i_ipmi_cleanup_mc(mc);
}

void
ipmi_domain_set_oem_shutdown_handler(ipmi_domain_t           *domain,
				     ipmi_domain_shutdown_cb handler)
{
    domain->shutdown_handler = handler;
}

static int destroy_attr(void *cb_data, void *item1, void *item2);
static int destroy_stat(void *cb_data, void *item1, void *item2);
static void call_mc_upd_cl_handlers(ipmi_domain_t         *domain,
				    ipmi_domain_mc_upd_cb handler,
				    void                  *handler_data);
static void call_con_change_cl_handlers(ipmi_domain_t      *domain,
					ipmi_domain_con_cb handler,
					void               *handler_data);
static void call_event_handler_cl_handlers(ipmi_domain_t         *domain,
					   ipmi_event_handler_cb handler,
					   void                 *handler_data);

static int
mc_upds_cleanup(void *cb_data, void *item1, void *item2)
{
    call_mc_upd_cl_handlers(cb_data, item1, item2);
    return LOCKED_LIST_ITER_CONTINUE;
}

static int
con_change_cleanup(void *cb_data, void *item1, void *item2)
{
    call_con_change_cl_handlers(cb_data, item1, item2);
    return LOCKED_LIST_ITER_CONTINUE;
}

static int
event_handler_cleanup(void *cb_data, void *item1, void *item2)
{
    call_event_handler_cl_handlers(cb_data, item1, item2);
    return LOCKED_LIST_ITER_CONTINUE;
}

static void
cleanup_domain(ipmi_domain_t *domain)
{
    unsigned int i;
    int          rv;

    /* This must be first, so that nuking the oustanding messages will
       cause the right thing to happen. */
    cancel_domain_oem_check(domain);

    if (domain->attr) {
	locked_list_iterate(domain->attr, destroy_attr, domain);
	locked_list_destroy(domain->attr);
	domain->attr = NULL;
    }

    if (domain->stats) {
	locked_list_iterate(domain->stats, destroy_stat, domain);
	locked_list_destroy(domain->stats);
	domain->stats = NULL;
    }

    /* Nuke all outstanding messages. */
    if ((domain->cmds_lock) && (domain->cmds)) {
	ll_msg_t     *nmsg;
	int          ok;
	ilist_iter_t iter;

	ipmi_lock(domain->cmds_lock);

	ilist_init_iter(&iter, domain->cmds);
	ok = ilist_first(&iter);
	while (ok) {
	    ipmi_msgi_t *rspi;

	    nmsg = ilist_get(&iter);
	    rspi = nmsg->rsp_item;

	    rspi->msg.netfn = nmsg->msg.netfn | 1;
	    rspi->msg.cmd = nmsg->msg.cmd;
	    rspi->msg.data = rspi->data;
	    rspi->msg.data_len = 1;
	    rspi->msg.data[0] = IPMI_UNKNOWN_ERR_CC;
	    deliver_rsp(domain, nmsg->rsp_handler, rspi);
	    
	    ilist_delete(&iter);
	    ipmi_mem_free(nmsg);
	    ok = ilist_first(&iter);
	}
	ipmi_unlock(domain->cmds_lock);
    }
    if (domain->cmds_lock)
	ipmi_destroy_lock(domain->cmds_lock);
    if (domain->cmds)
	free_ilist(domain->cmds);

    /* Shutdown code called here. */
    if (domain->shutdown_handler)
	domain->shutdown_handler(domain);

    /* Delete the sensors from the main SDR repository. */
    if (domain->sensors_in_main_sdr) {
	for (i=0; i<domain->sensors_in_main_sdr_count; i++) {
	    i_ipmi_domain_entity_lock(domain);
	    if (domain->sensors_in_main_sdr[i]) {
		ipmi_sensor_t *sensor = domain->sensors_in_main_sdr[i];
		ipmi_entity_t *entity = ipmi_sensor_get_entity(sensor);
		ipmi_mc_t     *mc = ipmi_sensor_get_mc(sensor);
		i_ipmi_entity_get(entity);
		i_ipmi_sensor_get(sensor);
		i_ipmi_domain_entity_unlock(domain);
		i_ipmi_domain_mc_lock(domain);
		i_ipmi_mc_get(mc);
		i_ipmi_domain_mc_unlock(domain);
		ipmi_sensor_destroy(domain->sensors_in_main_sdr[i]);
		i_ipmi_sensor_put(sensor);
		i_ipmi_mc_put(mc);
		i_ipmi_entity_put(entity);
	    } else
		i_ipmi_domain_entity_unlock(domain);
	}
	ipmi_mem_free(domain->sensors_in_main_sdr);
    }

    if (domain->entities_in_main_sdr) {
	ipmi_sdr_entity_destroy(domain->entities_in_main_sdr);
	domain->entities_in_main_sdr = NULL;
    }

    if (domain->activate_timer_info) {
	if (domain->activate_timer_info->lock) {
	    ipmi_lock(domain->activate_timer_info->lock);
	    if (domain->activate_timer) {
		int arv = 0;
		if (domain->activate_timer_info->running)
		    arv = domain->os_hnd->stop_timer(domain->os_hnd,
						     domain->activate_timer);

		if (!arv) {
		    /* If we can stop the timer, free it and it's info.
		       If we can't stop the timer, that means that the
		       code is currently in the timer handler, so we let
		       the "cancelled" value do this for us. */
		    domain->os_hnd->free_timer(domain->os_hnd,
					       domain->activate_timer);
		    ipmi_unlock(domain->activate_timer_info->lock);
		    ipmi_destroy_lock(domain->activate_timer_info->lock);
		    ipmi_mem_free(domain->activate_timer_info);
		} else {
		    domain->activate_timer_info->cancelled = 1;
		    ipmi_unlock(domain->activate_timer_info->lock);
		}
	    } else {
		ipmi_unlock(domain->activate_timer_info->lock);
		ipmi_destroy_lock(domain->activate_timer_info->lock);
	    }
	} else {
	    ipmi_mem_free(domain->activate_timer_info);
	}
    }

    /* We cleanup the MCs twice.  Some MCs may not be destroyed (but
       only left inactive) in the first pass due to references form
       other MCs SDR repositories.  The second pass will get them
       all. */
    ipmi_domain_iterate_mcs(domain, iterate_cleanup_mc, NULL);
    ipmi_domain_iterate_mcs(domain, iterate_cleanup_mc, NULL);

    if (domain->si_mc) {
	i_ipmi_mc_get(domain->si_mc);
	i_ipmi_mc_release(domain->si_mc);
	i_ipmi_cleanup_mc(domain->si_mc);
	i_ipmi_mc_put(domain->si_mc);
    }

    /* Destroy the main SDR repository, if it exists. */
    if (domain->main_sdrs)
	ipmi_sdr_info_destroy(domain->main_sdrs, NULL, NULL);

    if (domain->audit_domain_timer_info) {
	domain->audit_domain_timer_info->cancelled = 1;
	ipmi_lock(domain->audit_domain_timer_info->lock);
	rv = domain->os_hnd->stop_timer(domain->os_hnd,
					domain->audit_domain_timer);
	ipmi_unlock(domain->audit_domain_timer_info->lock);
	if (!rv) {
	    /* If we can stop the timer or it wasn't running, free it
	       and it's info.  If we can't stop the timer, that means
	       that the code is currently in the timer handler, so we
	       let the "cancelled" value do this for us. */
	    if (domain->audit_domain_timer)
		domain->os_hnd->free_timer(domain->os_hnd,
					   domain->audit_domain_timer);
	    if (domain->audit_domain_timer_info->lock)
		ipmi_destroy_lock(domain->audit_domain_timer_info->lock);
	    ipmi_mem_free(domain->audit_domain_timer_info);
	}
    }

    if (domain->event_handlers) {
	locked_list_iterate(domain->event_handlers, event_handler_cleanup,
			    domain);
	locked_list_destroy(domain->event_handlers);
    }
    if (domain->event_handlers_cl)
	locked_list_destroy(domain->event_handlers_cl);

    if (domain->con_change_handlers) {
	locked_list_iterate(domain->con_change_handlers, con_change_cleanup,
			    domain);
	locked_list_destroy(domain->con_change_handlers);
    }
    if (domain->con_change_cl_handlers)
	locked_list_destroy(domain->con_change_cl_handlers);

    if (domain->new_sensor_handlers)
        locked_list_destroy(domain->new_sensor_handlers);

    if (domain->ipmb_ignores) {
	ilist_iter_t iter;
	ilist_init_iter(&iter, domain->ipmb_ignores);
	while (ilist_first(&iter)) {
	    ilist_delete(&iter);
	}
	free_ilist(domain->ipmb_ignores);
    }
    if (domain->bus_scans_running) {
	mc_ipmb_scan_info_t *item;
	while (domain->bus_scans_running) {
	    item = domain->bus_scans_running;
	    domain->bus_scans_running = item->next;
	    ipmi_lock(item->lock);
	    if (item->timer_running) {
		if (item->os_hnd->stop_timer(item->os_hnd, item->timer)) {
		    item->cancelled = 1;
		    ipmi_unlock(item->lock);
		    item = NULL;
		}
	    }
	    if (item) {
		ipmi_unlock(item->lock);
		item->os_hnd->free_timer(item->os_hnd, item->timer);
		ipmi_destroy_lock(item->lock);
		ipmi_mem_free(item);
	    }
	}
    }

    /* Destroy the entities last, since sensors and controls may
       refer to them. */
    if (domain->entities)
	ipmi_entity_info_destroy(domain->entities);
    if (domain->entities_lock)
	ipmi_destroy_lock(domain->entities_lock);

    call_domain_change(domain, IPMI_DELETED);

    /* The MC list should no longer have anything in it. */
    if (domain->mc_upd_handlers) {
	locked_list_iterate(domain->mc_upd_handlers, mc_upds_cleanup, domain);
	locked_list_destroy(domain->mc_upd_handlers);
    }
    if (domain->mc_upd_cl_handlers)
	locked_list_destroy(domain->mc_upd_cl_handlers);

    for (i=0; i<IPMB_HASH; i++) {
	if (domain->ipmb_mcs[i].mcs)
	    ipmi_mem_free(domain->ipmb_mcs[i].mcs);
    }

    /* We wait until here to call the OEM data destroyer, the process
       of destroying information that has previously gone on can call
       OEM callbacks, we want the OEM data to hang around until we
       don't need it for sure. */
    if (domain->oem_data && domain->oem_data_destroyer)
	domain->oem_data_destroyer(domain, domain->oem_data);

    if (domain->con_stat_info)
	ipmi_ll_con_free_stat_info(domain->con_stat_info);

    /* Locks must be last, because they can be used by many things. */
    if (domain->ipmb_ignores_lock)
	ipmi_destroy_lock(domain->ipmb_ignores_lock);
    if (domain->mc_lock)
	ipmi_destroy_lock(domain->mc_lock);
    if (domain->con_lock)
	ipmi_destroy_lock(domain->con_lock);
    if (domain->domain_lock)
	ipmi_destroy_lock(domain->domain_lock);

    /* Cruft */
    free_domain_cruft(domain);

    ipmi_mem_free(domain);
}

static int con_register_stat(ipmi_ll_stat_info_t *info,
			     const char          *name,
			     const char          *instance,
			     void                **stat)
{
    ipmi_domain_stat_t *rstat = NULL;
    int                rv;
    ipmi_domain_t      *domain = ipmi_ll_con_stat_get_user_data(info);

    rv = ipmi_domain_stat_register(domain, name, instance, &rstat);
    if (!rv)
	*stat = rstat;
    return rv;
}

static void con_add_stat(ipmi_ll_stat_info_t *info,
			 void                *stat,
			 int                 value)
{
    ipmi_domain_stat_add(stat, value);
}

static void con_unregister_stat(ipmi_ll_stat_info_t *info,
				void                *stat)
{
    ipmi_domain_stat_put(stat);
}

static int
process_options(ipmi_domain_t      *domain, 
		ipmi_open_option_t *options,
		unsigned int       num_options)
{
    unsigned int i;

    /* Option processing. */
    for (i=0; i<num_options; i++) {
	switch (options[i].option) {
	case IPMI_OPEN_OPTION_ALL:
	    domain->option_all = options[i].ival != 0;
	    break;
	case IPMI_OPEN_OPTION_SDRS:
	    domain->option_SDRs = options[i].ival != 0;
	    break;
	case IPMI_OPEN_OPTION_FRUS:
	    domain->option_FRUs = options[i].ival != 0;
	    break;
	case IPMI_OPEN_OPTION_SEL:
	    domain->option_SEL = options[i].ival != 0;
	    break;
	case IPMI_OPEN_OPTION_IPMB_SCAN:
	    domain->option_IPMB_scan = options[i].ival != 0;
	    break;
	case IPMI_OPEN_OPTION_OEM_INIT:
	    domain->option_OEM_init = options[i].ival != 0;
	    break;
	case IPMI_OPEN_OPTION_SET_EVENT_RCVR:
	    domain->option_set_event_rcvr = options[i].ival != 0;
	    break;
	case IPMI_OPEN_OPTION_SET_SEL_TIME:
	    domain->option_set_sel_time = options[i].ival != 0;
	    break;
	case IPMI_OPEN_OPTION_USE_CACHE:
	    domain->option_use_cache = options[i].ival != 0;
	    break;
	case IPMI_OPEN_OPTION_ACTIVATE_IF_POSSIBLE:
	    domain->option_activate_if_possible = options[i].ival != 0;
	    break;
	case IPMI_OPEN_OPTION_LOCAL_ONLY:
	    domain->option_local_only = options[i].ival != 0;
	    domain->option_local_only_set = 1;
	    break;
	default:
	    return EINVAL;
	}
    }

    return 0;
}

static int
setup_domain(const char         *name,
	     ipmi_con_t         *ipmi[],
	     int                num_con,
	     ipmi_open_option_t *options,
	     unsigned int       num_options,
	     ipmi_domain_t      **new_domain)
{
    struct timeval               timeout;
    ipmi_domain_t                *domain;
    int                          rv;
    ipmi_system_interface_addr_t si;
    int                          i, j;
    unsigned int                 priv;

    /* Don't allow '(' in the domain name, as that messes up the
       naming.  That is the only restriction. */
    if (strchr(name, '('))
	return EINVAL;

    domain = ipmi_mem_alloc(sizeof(*domain));
    if (!domain)
	return ENOMEM;
    memset(domain, 0, sizeof(*domain));

    domain->in_startup = 1;
    domain->option_all = 1;
    domain->option_set_event_rcvr = 1;
    domain->option_set_sel_time = 1;
    domain->option_activate_if_possible = 1;
    domain->option_local_only = 0;
    domain->option_local_only_set = 0;
    domain->option_use_cache = 1;

    priv = IPMI_PRIVILEGE_ADMIN;
    for (i=0; i<num_con; i++) {
	/* Find the least-common demominator privilege for the
	   connections. */
	if ((ipmi[i]->priv_level != 0) && (ipmi[i]->priv_level < priv))
	    priv = ipmi[i]->priv_level;
    }

    /* Enable setting the event receiver (by default) if the privilege
       is admin or greater. */
    domain->option_set_event_rcvr = (priv >= IPMI_PRIVILEGE_ADMIN);
    domain->option_set_sel_time = (priv >= IPMI_PRIVILEGE_ADMIN);

    if (options)
	process_options(domain, options, num_options);

    strncpy(domain->name, name, sizeof(domain->name)-2);
    i = strlen(domain->name);
    if (i > 0) {
	domain->name[i] = ' ';
	domain->name[i+1] = '\0';
    }

    domain->os_hnd = ipmi[0]->os_hnd;

    domain->valid = 1;
    domain->in_shutdown = 0;
    domain->usecount = 1;

    domain->stats = locked_list_alloc(domain->os_hnd);
    if (!domain->stats) {
	ipmi_mem_free(domain);
	return ENOMEM;
    }

    domain->con_stat_info = ipmi_ll_con_alloc_stat_info();
    if (!domain->con_stat_info) {
	locked_list_destroy(domain->stats);
	ipmi_mem_free(domain);
	return ENOMEM;
    }
    ipmi_ll_con_stat_info_set_register(domain->con_stat_info,
				       con_register_stat);
    ipmi_ll_con_stat_info_set_adder(domain->con_stat_info, con_add_stat);
    ipmi_ll_con_stat_info_set_unregister(domain->con_stat_info,
					 con_unregister_stat);
    ipmi_ll_con_stat_set_user_data(domain->con_stat_info, domain);

    for (i=0; i<num_con; i++) {
	int len1 = strlen(domain->name);
	domain->conn[i] = ipmi[i];
	for (j=0; j<MAX_IPMI_USED_CHANNELS; j++)
	    domain->con_ipmb_addr[i][j] = 0x20;
	domain->con_active[i] = 1;
	domain->con_up[i] = 0;
	ipmi[i]->name = ipmi_mem_alloc(len1 + 11);
	if (ipmi[i]->name)
	    snprintf(ipmi[i]->name, len1 + 11, "%s%d ", domain->name, i);
	ipmi[i]->user_data = domain;

	for (j=0; j<MAX_PORTS_PER_CON; j++)
	    domain->port_up[j][i] = -1;

	if (ipmi[i]->register_stat_handler)
	    ipmi[i]->register_stat_handler(ipmi[i], domain->con_stat_info);
    }

    domain->connection_up = 0;

    /* Create the locks before anything else. */
    domain->default_sel_rescan_time = IPMI_SEL_QUERY_INTERVAL;

    /* Set the default timer intervals. */
    domain->audit_domain_interval = IPMI_AUDIT_DOMAIN_INTERVAL;

    rv = ipmi_create_lock(domain, &domain->mc_lock);
    if (rv)
	goto out_err;

    rv = ipmi_create_lock(domain, &domain->con_lock);
    if (rv)
	goto out_err;

    rv = ipmi_create_lock(domain, &domain->domain_lock);
    if (rv)
	goto out_err;

    rv = ipmi_create_lock(domain, &domain->entities_lock);
    if (rv)
	goto out_err;

    domain->activate_timer_info = ipmi_mem_alloc(sizeof(activate_timer_info_t));
    if (!domain->activate_timer_info) {
	rv = ENOMEM;
	goto out_err;
    }

    domain->activate_timer_info->lock = NULL;
    domain->activate_timer_info->domain = domain;
    domain->activate_timer_info->cancelled = 0;
    domain->activate_timer_info->os_hnd = domain->os_hnd;
    domain->activate_timer_info->running = 0;

    rv = ipmi_create_lock(domain, &domain->activate_timer_info->lock);
    if (rv)
	goto out_err;

    rv = domain->os_hnd->alloc_timer(domain->os_hnd,
				     &(domain->activate_timer));
    if (rv)
	goto out_err;

    domain->event_handlers_cl = locked_list_alloc(domain->os_hnd);
    if (!domain->event_handlers_cl) {
	rv = ENOMEM;
	goto out_err;
    }

    domain->event_handlers = locked_list_alloc(domain->os_hnd);
    if (!domain->event_handlers) {
	rv = ENOMEM;
	goto out_err;
    }

    domain->attr = locked_list_alloc(domain->os_hnd);
    if (!domain->attr) {
	rv = ENOMEM;
	goto out_err;
    }

    domain->do_bus_scan = 1;

    si.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
    si.channel = IPMI_BMC_CHANNEL;
    si.lun = 0;
    rv = i_ipmi_create_mc(domain,
			  (ipmi_addr_t *) &si, sizeof(si),
			  &domain->si_mc);
    if (rv)
	goto out_err;
    i_ipmi_mc_use(domain->si_mc);

    /* Force this one to always be active, so anything that uses it is
       always ready to go.  Since it represents the connection, it
       really can't ever go inactive. */
    i_ipmi_mc_force_active(domain->si_mc, 1);

    rv = ipmi_sdr_info_alloc(domain, domain->si_mc, 0, 0, &domain->main_sdrs);
    if (rv)
	goto out_err;

    rv = ipmi_create_lock(domain, &domain->cmds_lock);
    if (rv)
	goto out_err;

    domain->cmds = alloc_ilist();
    if (! domain->cmds) {
	rv = ENOMEM;
	goto out_err;
    }

    domain->con_change_cl_handlers = locked_list_alloc(domain->os_hnd);
    if (! domain->con_change_cl_handlers) {
	rv = ENOMEM;
	goto out_err;
    }

    domain->con_change_handlers = locked_list_alloc(domain->os_hnd);
    if (! domain->con_change_handlers) {
	rv = ENOMEM;
	goto out_err;
    }

    domain->mc_upd_cl_handlers = locked_list_alloc(domain->os_hnd);
    if (! domain->mc_upd_cl_handlers) {
	rv = ENOMEM;
	goto out_err;
    }

    domain->mc_upd_handlers = locked_list_alloc(domain->os_hnd);
    if (! domain->mc_upd_handlers) {
	rv = ENOMEM;
	goto out_err;
    }

    domain->new_sensor_handlers = locked_list_alloc(domain->os_hnd);
    if (! domain->new_sensor_handlers) {
        rv = ENOMEM;
        goto out_err;
    }

    rv = ipmi_create_lock(domain, &domain->ipmb_ignores_lock);
    if (rv)
	goto out_err;

    domain->ipmb_ignores = alloc_ilist();
    if (! domain->ipmb_ignores) {
	rv = ENOMEM;
	goto out_err;
    }

    domain->bus_scans_running = NULL;

    domain->audit_domain_timer_info
	= ipmi_mem_alloc(sizeof(audit_domain_info_t));
    if (!domain->audit_domain_timer_info) {
	rv = ENOMEM;
	goto out_err;
    }
    memset(domain->audit_domain_timer_info, 0, sizeof(audit_domain_info_t));
	
    domain->audit_domain_timer_info->domain = domain;
    domain->audit_domain_timer_info->os_hnd = domain->os_hnd;
    domain->audit_domain_timer_info->cancelled = 0;
    rv = ipmi_create_lock(domain, &domain->audit_domain_timer_info->lock);
    if (rv)
	goto out_err;
    rv = domain->os_hnd->alloc_timer(domain->os_hnd,
				     &(domain->audit_domain_timer));
    if (rv)
	goto out_err;

    timeout.tv_sec = domain->audit_domain_interval;
    timeout.tv_usec = 0;
    domain->os_hnd->start_timer(domain->os_hnd,
				domain->audit_domain_timer,
				&timeout,
				domain_audit,
				domain->audit_domain_timer_info);

    rv = ipmi_entity_info_alloc(domain, &(domain->entities));
    if (rv)
	goto out_err;

    memset(domain->chan, 0, sizeof(domain->chan));

 out_err:
    if (domain->si_mc)
	i_ipmi_mc_put(domain->si_mc);

    if (rv) {
	for (i=0; i<num_con; i++) {
	    if (ipmi[i]->register_stat_handler)
		ipmi[i]->unregister_stat_handler(ipmi[i],
						 domain->con_stat_info);
	}
	cleanup_domain(domain);
    } else
	*new_domain = domain;

    return rv;
}

/***********************************************************************
 *
 * Locking handling
 *
 **********************************************************************/

#ifdef IPMI_CHECK_LOCKS
void
i__ipmi_check_domain_lock(const ipmi_domain_t *domain)
{
    if (!domain)
	return;

    if (!DEBUG_LOCKS)
	return;

    if (domain->usecount == 0)
	ipmi_report_lock_error(domain->os_hnd,
			       "domain not locked when it should have been");
}
#endif

void
i_ipmi_domain_entity_lock(ipmi_domain_t *domain)
{

    CHECK_DOMAIN_LOCK(domain);
    ipmi_lock(domain->entities_lock);
}

void
i_ipmi_domain_entity_unlock(ipmi_domain_t *domain)
{
    CHECK_DOMAIN_LOCK(domain);
    ipmi_unlock(domain->entities_lock);
}

void
i_ipmi_domain_mc_lock(ipmi_domain_t *domain)
{

    CHECK_DOMAIN_LOCK(domain);
    ipmi_lock(domain->mc_lock);
}

void
i_ipmi_domain_mc_unlock(ipmi_domain_t *domain)
{
    CHECK_DOMAIN_LOCK(domain);
    ipmi_unlock(domain->mc_lock);
}

/***********************************************************************
 *
 * Domain validation
 *
 **********************************************************************/

/* A open hash table of all the registered domains. */
#define DOMAIN_HASH_SIZE 128
static ipmi_domain_t *domains[DOMAIN_HASH_SIZE];
static ipmi_lock_t *domains_lock;
static int domains_initialized = 0;

static void
add_known_domain(ipmi_domain_t *domain)
{
    unsigned int hash = ipmi_hash_pointer(domain) % DOMAIN_HASH_SIZE;

    ipmi_lock(domains_lock);

    domain->prev = NULL;
    domain->next = domains[hash];
    if (domains[hash])
	domains[hash]->prev = domain;
    domains[hash] = domain;

    ipmi_unlock(domains_lock);
}

static void
remove_known_domain(ipmi_domain_t *domain)
{
    ipmi_lock(domains_lock);

    if (domain->next)
	domain->next->prev = domain->prev;
    if (domain->prev)
	domain->prev->next = domain->next;
    else {
	unsigned int hash = ipmi_hash_pointer(domain) % DOMAIN_HASH_SIZE;
	domains[hash] = domain->next;
    }

    ipmi_unlock(domains_lock);
}

/* Validate that the domain and it's underlying connection is valid
   and increment its use count. */
int
i_ipmi_domain_get(ipmi_domain_t *domain)
{
    unsigned int  hash = ipmi_hash_pointer(domain) % DOMAIN_HASH_SIZE;
    ipmi_domain_t *c;
    int           rv = 0;

    if (!domains_initialized)
	    return ECANCELED;

    ipmi_lock(domains_lock);

    c = domains[hash];
    while (c != NULL) {
	if (c == domain)
	    break;
	c = c->next;
    }
    if (c == NULL) {
	rv = EINVAL;
	goto out;
    }

    /* We do this check after we find the domain in the list, because
       want to make sure the pointer is good before we do this. */
    if (!domain->valid) {
	rv = EINVAL;
	goto out;
    }

    domain->usecount++;

 out:
    ipmi_unlock(domains_lock);

    return rv;
}

void
i_ipmi_domain_put(ipmi_domain_t *domain)
{
    ipmi_lock(domains_lock);

    if ((domain->usecount == 1) && (domain->in_shutdown)) {
	ipmi_unlock(domains_lock);
	/* The domain has been destroyed, finish the process. */
	real_close_connection(domain);
	return;
    }

    domain->usecount--;

    ipmi_unlock(domains_lock);
}

/***********************************************************************
 *
 * Handle global OEM callbacks new domains.
 *
 **********************************************************************/
typedef struct oem_handlers_s {
    ipmi_domain_oem_check check;
    void                  *cb_data;
} oem_handlers_t;

/* FIXME - do we need a lock?  Probably, add it. */
static ilist_t *oem_handlers;

int
ipmi_register_domain_oem_check(ipmi_domain_oem_check check,
			       void                  *cb_data)
{
    oem_handlers_t *new_item;

    new_item = ipmi_mem_alloc(sizeof(*new_item));
    if (!new_item)
	return ENOMEM;

    new_item->check = check;
    new_item->cb_data = cb_data;

    if (! ilist_add_tail(oem_handlers, new_item, NULL)) {
	ipmi_mem_free(new_item);
	return ENOMEM;
    }

    return 0;
}

static int
oem_handler_cmp(void *item, void *cb_data)
{
    oem_handlers_t *hndlr = item;
    oem_handlers_t *cmp = cb_data;

    return ((hndlr->check == cmp->check)
	    && (hndlr->cb_data == cmp->cb_data));
}

int
ipmi_deregister_domain_oem_check(ipmi_domain_oem_check check,
				 void                  *cb_data)
{
    oem_handlers_t *hndlr;
    oem_handlers_t tmp;
    ilist_iter_t   iter;

    tmp.check = check;
    tmp.cb_data = cb_data;
    ilist_init_iter(&iter, oem_handlers);
    ilist_unpositioned(&iter);
    hndlr = ilist_search_iter(&iter, oem_handler_cmp, &tmp);
    if (hndlr) {
	ilist_delete(&iter);
	ipmi_mem_free(hndlr);
	return 0;
    }
    return ENOENT;
}

struct domain_check_oem_s
{
    int                        cancelled;
    ipmi_domain_oem_check_done done;
    void                       *cb_data;
    oem_handlers_t             *curr_handler;
};

static void domain_oem_check_done(ipmi_domain_t *domain,
				  int           err,
				  void          *cb_data);

static void
start_oem_domain_check(ipmi_domain_t      *domain, 
		       domain_check_oem_t *check)
{
    ilist_iter_t     iter;

    ilist_init_iter(&iter, oem_handlers);
    if (!ilist_first(&iter)) {
	/* Empty list, just go on */
	check->done(domain, 0, check->cb_data);
	ipmi_mem_free(check);
	goto out;
    } else {
	oem_handlers_t *h = ilist_get(&iter);
	int            rv = ENOSYS;

	while (rv) {
	    check->curr_handler = h;
	    rv = h->check(domain, domain_oem_check_done, check);
	    if (!rv)
		break;
	    if (rv != ENOSYS)
		break;
	    if (!ilist_next(&iter)) {
		/* End of list, just go on */
		check->done(domain, 0, check->cb_data);
		ipmi_mem_free(check);
		goto out;
	    }
	    h = ilist_get(&iter);
	}
	if (rv) {
	    if (rv == ENOSYS)
		/* This just means that we didn't match anything. */
		rv = 0;

	    /* We didn't get a check to start, just give up. */
	    check->done(domain, rv, check->cb_data);
	    ipmi_mem_free(check);
	}
    }
 out:
    return;
}

static int
oem_handler_cmp2(void *item, void *cb_data)
{
    oem_handlers_t *hndlr = item;
    oem_handlers_t *cmp = cb_data;

    return (hndlr == cmp);
}

static void
next_oem_domain_check(ipmi_domain_t      *domain, 
		      domain_check_oem_t *check)
{
    oem_handlers_t *h;
    ilist_iter_t   iter;

    /* We can't keep an interater in the check, because the list may
       change during execution. */
    ilist_init_iter(&iter, oem_handlers);
    ilist_unpositioned(&iter);
    h = ilist_search_iter(&iter, oem_handler_cmp2, check->curr_handler);
    if (!h) {
	/* The current handler we were working on went away, start over. */
	start_oem_domain_check(domain, check);
    } else {
	int rv = 1;

	while (rv) {
	    if (!ilist_next(&iter)) {
		/* End of list, just go on */
		check->done(domain, 0, check->cb_data);
		ipmi_mem_free(check);
		goto out;
	    }
	    h = ilist_get(&iter);
	    check->curr_handler = h;
	    rv = h->check(domain, domain_oem_check_done, check);
	}
	if (rv) {
	    /* We didn't get a check to start, just give up. */
	    check->done(domain, 0, check->cb_data);
	    ipmi_mem_free(check);
	}
    }
 out:
    return;
}

static void
domain_oem_check_done(ipmi_domain_t *domain,
		      int           err,
		      void          *cb_data)
{
    domain_check_oem_t *check = cb_data;

    if (check->cancelled) {
	check->done(NULL, ECANCELED, check->cb_data);
	ipmi_mem_free(check);
	return;
    }

    if (err != ENOSYS) {
	/* Either we got a success or some error trying to install the
	   OEM handlers. */
	check->done(domain, err, check->cb_data);
	ipmi_mem_free(check);
	return;
    }

    next_oem_domain_check(domain, check);
}

static int
check_oem_handlers(ipmi_domain_t              *domain,
		   ipmi_domain_oem_check_done done,
		   void                       *cb_data)
{
    domain_check_oem_t *check;

    check = ipmi_mem_alloc(sizeof(*check));
    if (!check)
	return ENOMEM;

    check->done = done;
    check->cb_data = cb_data;
    check->cancelled = 0;

    start_oem_domain_check(domain, check);

    return 0;
}

static void
cancel_domain_oem_check(ipmi_domain_t *domain)
{
    if (domain->check)
	domain->check->cancelled = 1;
}

/***********************************************************************
 *
 * FRU data handling
 *
 **********************************************************************/
int i_ipmi_domain_fru_set_special_setup(ipmi_domain_t             *domain,
					i_ipmi_domain_fru_setup_cb setup,
					void                      *cb_data)
{
    domain->fru_setup_cb = setup;
    domain->fru_setup_cb_data = cb_data;
    return 0;
}

int i_ipmi_domain_fru_call_special_setup(ipmi_domain_t *domain,
					 unsigned char is_logical,
					 unsigned char device_address,
					 unsigned char device_id,
					 unsigned char lun,
					 unsigned char private_bus,
					 unsigned char channel,
					 ipmi_fru_t    *fru)
{
    if (!domain->fru_setup_cb)
	return 0;
    return domain->fru_setup_cb(domain, is_logical, device_address,
				device_id, lun, private_bus, channel,
				fru, domain->fru_setup_cb_data);
}

/***********************************************************************
 *
 * MC handling
 *
 **********************************************************************/

#define HASH_SLAVE_ADDR(x) (((x) >> 1) & (IPMB_HASH-1))

ipmi_mc_t *
i_ipmi_find_mc_by_addr(ipmi_domain_t     *domain,
		       const ipmi_addr_t *addr,
		       unsigned int      addr_len)
{
    ipmi_mc_t     *mc = NULL;

    if (addr_len > sizeof(ipmi_addr_t))
	return NULL;

    ipmi_lock(domain->mc_lock);
    if (addr->addr_type == IPMI_SYSTEM_INTERFACE_ADDR_TYPE) {
	if (addr->channel == IPMI_BMC_CHANNEL)
	    mc = domain->si_mc;
	else if (addr->channel < MAX_CONS)
	    mc = domain->sys_intf_mcs[addr->channel];
    } else if (addr->addr_type == IPMI_IPMB_ADDR_TYPE) {
	const ipmi_ipmb_addr_t *ipmb = (ipmi_ipmb_addr_t *) addr;
	int                    idx;
	const mc_table_t       *tab;
	ipmi_addr_t            addr2;
	unsigned int           addr2_len;
	int                    i;

	if (addr_len >= sizeof(*ipmb)) {
	    idx = HASH_SLAVE_ADDR(ipmb->slave_addr);
	    tab = &(domain->ipmb_mcs[idx]);
	    for (i=0; i<tab->size; i++) {
		if (tab->mcs[i]) {
		    ipmi_mc_get_ipmi_address(tab->mcs[i], &addr2, &addr2_len);

		    if (ipmi_addr_equal_nolun(addr, addr_len,
					      &addr2, addr2_len))
		    {
			mc = tab->mcs[i];
			break;
		    }
		}
	    }
	}
    }

    /* If we cannot get the MC, it has been destroyed. */
    if (mc) {
	if (i_ipmi_mc_get(mc))
	    mc = NULL;
    }
    ipmi_unlock(domain->mc_lock);

    return mc;
}

static int
in_ipmb_ignores(ipmi_domain_t *domain,
		unsigned char channel,
		unsigned char ipmb_addr)
{
    unsigned long addr;
    unsigned char first, last, ichan;
    ilist_iter_t iter;
    int          rv = 0;

    ipmi_lock(domain->ipmb_ignores_lock);
    ilist_init_iter(&iter, domain->ipmb_ignores);
    ilist_unpositioned(&iter);
    while (ilist_next(&iter)) {
	addr = (unsigned long) ilist_get(&iter);
	first = addr & 0xff;
	last = (addr >> 8) & 0xff;
	ichan = (addr >> 16) & 0xff;
	if ((ichan == channel) && (ipmb_addr >= first) && (ipmb_addr <= last))
	    rv = 1;
    }
    ipmi_unlock(domain->ipmb_ignores_lock);

    return rv;
}

int
ipmi_domain_add_ipmb_ignore(ipmi_domain_t *domain,
			    unsigned char channel,
			    unsigned char ipmb_addr)
{
    unsigned long addr = ipmb_addr | (ipmb_addr << 8) | (channel << 16);
    int           rv = 0;

    ipmi_lock(domain->ipmb_ignores_lock);
    if (! ilist_add_tail(domain->ipmb_ignores, (void *) addr, NULL))
	rv = ENOMEM;
    ipmi_unlock(domain->ipmb_ignores_lock);

    return rv;
}

int
ipmi_domain_add_ipmb_ignore_range(ipmi_domain_t *domain,
				  unsigned char channel,
				  unsigned char first_ipmb_addr,
				  unsigned char last_ipmb_addr)
{
    unsigned long addr = (first_ipmb_addr | (last_ipmb_addr << 8)
			  | (channel << 16));
    int           rv = 0;

    ipmi_lock(domain->ipmb_ignores_lock);
    if (! ilist_add_tail(domain->ipmb_ignores, (void *) addr, NULL))
	return ENOMEM;
    ipmi_unlock(domain->ipmb_ignores_lock);

    return rv;
}

typedef struct mc_upd_info_s
{
    enum ipmi_update_e op;
    ipmi_domain_t      *domain;
    ipmi_mc_t          *mc;
} mc_upd_info_t;

static int
iterate_mc_upds(void *cb_data, void *item1, void *item2)
{
    mc_upd_info_t         *info = cb_data;
    ipmi_domain_mc_upd_cb handler = item1;

    handler(info->op, info->domain, info->mc, item2);
    return LOCKED_LIST_ITER_CONTINUE;
}

static int
add_mc_to_domain(ipmi_domain_t *domain, ipmi_mc_t *mc)
{
    char         addr_data[sizeof(ipmi_addr_t)];
    ipmi_addr_t  *addr = (ipmi_addr_t *) addr_data;
    unsigned int addr_len;
    int          rv = 0;

    CHECK_DOMAIN_LOCK(domain);
    CHECK_MC_LOCK(mc);

    ipmi_mc_get_ipmi_address(mc, addr, &addr_len);
    
    ipmi_lock(domain->mc_lock);

    if (addr->addr_type == IPMI_SYSTEM_INTERFACE_ADDR_TYPE) {
	if (addr->channel >= MAX_CONS)
	    rv = EINVAL;
	else
	    domain->sys_intf_mcs[addr->channel] = mc;
    } else if (addr->addr_type == IPMI_IPMB_ADDR_TYPE) {
	ipmi_ipmb_addr_t *ipmb = (ipmi_ipmb_addr_t *) addr;
	int              idx;
	mc_table_t       *tab;
	int              i;

	idx = HASH_SLAVE_ADDR(ipmb->slave_addr);
	tab = &(domain->ipmb_mcs[idx]);
	if (tab->size == tab->curr) {
	    ipmi_mc_t **nmcs;

	    nmcs = ipmi_mem_alloc(sizeof(ipmi_mc_t *) * (tab->size+5));
	    if (!nmcs) {
		rv = ENOMEM;
		goto out_unlock;
	    }
	    if (tab->mcs) {
		memcpy(nmcs, tab->mcs, sizeof(ipmi_mc_t *) * tab->size);
		ipmi_mem_free(tab->mcs);
	    }
	    memset(nmcs+tab->size, 0, sizeof(ipmi_mc_t *) * 5);
	    tab->size += 5;
	    tab->mcs = nmcs;
	}
	for (i=0; i<tab->size; i++) {
	    if (!tab->mcs[i]) {
		tab->mcs[i] = mc;
		tab->curr++;
		break;
	    }
	}
    }

out_unlock:
    ipmi_unlock(domain->mc_lock);

    return rv;
}

static void
call_mc_upd_handlers(ipmi_domain_t      *domain,
		     ipmi_mc_t          *mc,
		     enum ipmi_update_e op)
{
    mc_upd_info_t info;

    CHECK_DOMAIN_LOCK(domain);
    CHECK_MC_LOCK(mc);

    info.domain = domain;
    info.op = op;
    info.mc = mc;
    locked_list_iterate(domain->mc_upd_handlers, iterate_mc_upds, &info);
}

int
ipmi_domain_add_mc_updated_handler(ipmi_domain_t         *domain,
				   ipmi_domain_mc_upd_cb handler,
				   void                  *cb_data)
{
    if (locked_list_add(domain->mc_upd_handlers, handler, cb_data))
	return 0;
    else
	return ENOMEM;
}

typedef struct mc_upd_cl_info_s
{
    ipmi_domain_mc_upd_cb handler;
    void                  *handler_data;
} mc_upd_cl_info_t;


static int
iterate_mc_upds_cl(void *cb_data, void *item1, void *item2)
{
    mc_upd_cl_info_t         *info = cb_data;
    ipmi_domain_mc_upd_cl_cb handler = item1;

    handler(info->handler, info->handler_data, item2);
    return LOCKED_LIST_ITER_CONTINUE;
}

static void
call_mc_upd_cl_handlers(ipmi_domain_t         *domain,
			ipmi_domain_mc_upd_cb handler,
			void                  *handler_data)
{
    mc_upd_cl_info_t info;

    info.handler = handler;
    info.handler_data = handler_data;
    locked_list_iterate(domain->mc_upd_cl_handlers, iterate_mc_upds_cl, &info);
}

int
ipmi_domain_remove_mc_updated_handler(ipmi_domain_t        *domain,
				      ipmi_domain_mc_upd_cb handler,
				      void                  *cb_data)
{
    if (locked_list_remove(domain->mc_upd_handlers, handler, cb_data))
	return 0;
    else
	return EINVAL;
}

int
ipmi_domain_add_mc_updated_handler_cl(ipmi_domain_t            *domain,
				      ipmi_domain_mc_upd_cl_cb handler,
				      void                     *cb_data)
{
    if (locked_list_add(domain->mc_upd_cl_handlers, handler, cb_data))
	return 0;
    else
	return ENOMEM;
}

int
ipmi_domain_remove_mc_updated_handler_cl(ipmi_domain_t            *domain,
					 ipmi_domain_mc_upd_cl_cb handler,
					 void                     *cb_data)
{
    if (locked_list_remove(domain->mc_upd_cl_handlers, handler, cb_data))
	return 0;
    else
	return EINVAL;
}

/* Must be called with the domain MC lock held.  It will be
   released. */
int
i_ipmi_remove_mc_from_domain(ipmi_domain_t *domain, ipmi_mc_t *mc)
{
    char         addr_data[sizeof(ipmi_addr_t)];
    ipmi_addr_t  *addr = (ipmi_addr_t *) addr_data;
    unsigned int addr_len;
    int          found = 0;

    ipmi_mc_get_ipmi_address(mc, addr, &addr_len);
    
    if (addr->addr_type == IPMI_SYSTEM_INTERFACE_ADDR_TYPE) {
	if ((addr->channel < MAX_CONS)
	    && (mc == domain->sys_intf_mcs[addr->channel]))
	{
	    domain->sys_intf_mcs[addr->channel] = NULL;
	    found = 1;
	}
    } else if (addr->addr_type == IPMI_IPMB_ADDR_TYPE) {
	ipmi_ipmb_addr_t *ipmb = (ipmi_ipmb_addr_t *) addr;
	int              idx;
	mc_table_t       *tab;
	int              i;

	idx = HASH_SLAVE_ADDR(ipmb->slave_addr);
	tab = &(domain->ipmb_mcs[idx]);
	for (i=0; i<tab->size; i++) {
	    if (tab->mcs[i] == mc) {
		tab->curr--;
		tab->mcs[i] = NULL;
		found = 1;
	    }
	}
    }

    ipmi_unlock(domain->mc_lock);

    if (found) {
	call_mc_upd_handlers(domain, mc, IPMI_DELETED);
	return 0;
    } else
	return ENOENT;
}

int
i_ipmi_find_or_create_mc_by_slave_addr(ipmi_domain_t *domain,
				       unsigned int  channel,
				       unsigned int  slave_addr,
				       ipmi_mc_t     **new_mc)
{
    ipmi_mc_t   *mc;
    char        addr_data[sizeof(ipmi_addr_t)];
    ipmi_addr_t *addr = (ipmi_addr_t *) addr_data;
    int         addr_size;
    int         rv;

    if (channel == IPMI_BMC_CHANNEL) {
	ipmi_system_interface_addr_t *saddr = (void *) addr;
	saddr->addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
	saddr->channel = slave_addr;
	saddr->lun = 0;
	addr_size = sizeof(*saddr);
    } else {
	ipmi_ipmb_addr_t *iaddr = (void *) addr;
	iaddr->addr_type = IPMI_IPMB_ADDR_TYPE;
	iaddr->channel = channel;
	iaddr->lun = 0;
	iaddr->slave_addr = slave_addr;
	addr_size = sizeof(*iaddr);
    }

    mc = i_ipmi_find_mc_by_addr(domain, addr, addr_size);
    if (mc) {
	if (new_mc)
	    *new_mc = mc;
	return 0;
    }

    rv = i_ipmi_create_mc(domain, addr, addr_size, &mc);
    if (rv)
	return rv;

    /* If we find an MC in the SDRs that we don't know about yet,
       attempt to scan it. */
    if (ipmi_option_IPMB_scan(domain))
	ipmi_start_ipmb_mc_scan(domain, channel, slave_addr, slave_addr,
				NULL, NULL);

    rv = add_mc_to_domain(domain, mc);
    if (rv) {
	i_ipmi_cleanup_mc(mc);
	i_ipmi_mc_put(mc);
	return rv;
    }
    call_mc_upd_handlers(domain, mc, IPMI_ADDED);

    if (new_mc)
	*new_mc = mc;
    return 0;
}

/***********************************************************************
 *
 * Command/response handling
 *
 **********************************************************************/

static int cmp_nmsg(void *item, void *cb_data)
{
    ll_msg_t *nmsg1 = item;
    ll_msg_t *nmsg2 = cb_data;

    return ((nmsg1 == nmsg2)
	    && (nmsg1->domain == nmsg2->domain));
}

/* Must be called with the cmds_lock held. */
static int
find_and_remove_msg(ipmi_domain_t *domain, ll_msg_t *nmsg, long seq)
{
    ilist_iter_t iter;
    int          rv = 0;

    ilist_init_iter(&iter, domain->cmds);
    ilist_unpositioned(&iter);
    if ((ilist_search_iter(&iter, cmp_nmsg, nmsg) != NULL)
	&& (nmsg->seq == seq))
    {
	ilist_delete(&iter);
	rv = 1;
    }
    return rv;
}

static int
ll_rsp_handler(ipmi_con_t   *ipmi,
	       ipmi_msgi_t  *orspi)
{
    ipmi_msgi_t   *rspi;
    ipmi_domain_t *domain = orspi->data1;
    ll_msg_t      *nmsg = orspi->data2;
    long          seq = (long) orspi->data3;
    long          conn_seq = (long) orspi->data4;
    int           rv;

    rv = i_ipmi_domain_get(domain);
    if (rv)
	/* No need to report these to the upper layer, they have
	   already been delivered in the cleanup code. */
	return IPMI_MSG_ITEM_NOT_USED;

    ipmi_lock(domain->cmds_lock);
    if (conn_seq != domain->conn_seq[nmsg->con]) {
	/* The message has been rerouted, just ignore this response. */
	ipmi_unlock(domain->cmds_lock);
	goto out_unlock;
    }

    if (!find_and_remove_msg(domain, nmsg, seq)) {
	ipmi_unlock(domain->cmds_lock);
	goto out_unlock;
    }
    ipmi_unlock(domain->cmds_lock);

    rspi = nmsg->rsp_item;
    if (nmsg->rsp_handler) {
	ipmi_move_msg_item(rspi, orspi);
	memcpy(&rspi->addr, &orspi->addr, orspi->addr_len);
	rspi->addr_len = orspi->addr_len;
	deliver_rsp(domain, nmsg->rsp_handler, rspi);
    } else
	ipmi_free_msg_item(rspi);
    ipmi_mem_free(nmsg);
 out_unlock:
    i_ipmi_domain_put(domain);
    return IPMI_MSG_ITEM_NOT_USED;
}

static int
ll_si_rsp_handler(ipmi_con_t *ipmi, ipmi_msgi_t *orspi)
{
    ipmi_msgi_t                  *rspi;
    ipmi_domain_t                *domain = orspi->data1;
    ll_msg_t                     *nmsg = orspi->data2;
    int                          rv;

    rspi = nmsg->rsp_item;

    rv = i_ipmi_domain_get(domain);
    if (rv) {
	/* Note that since we don't track SI messages, we must report
	   them to the upper layer through this interface when the
	   domain goes away. */
	deliver_rsp(NULL, nmsg->rsp_handler, rspi);
	return IPMI_MSG_ITEM_NOT_USED;
    }

    if (nmsg->rsp_handler) {
	ipmi_move_msg_item(rspi, orspi);
	/* Set the LUN from the response message. */
	ipmi_addr_set_lun(&rspi->addr, ipmi_addr_get_lun(&rspi->addr));
	deliver_rsp(domain, nmsg->rsp_handler, rspi);
    } else
	ipmi_free_msg_item(rspi);
    ipmi_mem_free(nmsg);

    i_ipmi_domain_put(domain);
    return IPMI_MSG_ITEM_NOT_USED;
}

static int
matching_domain_sysaddr(ipmi_domain_t *domain, const ipmi_addr_t *addr,
			ipmi_system_interface_addr_t *si)
{
    if (addr->addr_type == IPMI_IPMB_ADDR_TYPE) {
	ipmi_ipmb_addr_t *ipmb = (ipmi_ipmb_addr_t *) addr;
	int              i;

	if (ipmb->channel >= MAX_IPMI_USED_CHANNELS)
	    return 0;

	if (domain->chan[ipmb->channel].medium != IPMI_CHANNEL_MEDIUM_IPMB)
	    return 0;

	for (i=0; i<MAX_CONS; i++) {
	    if (domain->con_active[i]
		&& domain->con_up[i]
		&& (domain->con_ipmb_addr[i][ipmb->channel]==ipmb->slave_addr)
		&& domain->sys_intf_mcs[i])
	    {
		si->addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
		si->channel = i;
		si->lun = ipmb->lun;
		return 1;
	    }
	}
    }

    return 0;
}

static int
send_command_option(ipmi_domain_t           *domain,
		    int                     conn,
		    const ipmi_addr_t       *addr,
		    unsigned int            addr_len,
		    const ipmi_msg_t        *msg,
		    const ipmi_con_option_t *options,
		    ipmi_ll_rsp_handler_t   handler,
		    void		    *handler_data)
{
    if (domain->conn[conn]->send_command_option)
	return domain->conn[conn]->send_command_option(domain->conn[conn],
						       addr, addr_len,
						       msg,
						       options,
						       handler,
						       handler_data);
    else
	return domain->conn[conn]->send_command(domain->conn[conn],
						addr, addr_len,
						msg,
						handler,
						handler_data);
}

static int
send_command_addr(ipmi_domain_t                *domain,
		  const ipmi_addr_t            *addr,
		  unsigned int                 addr_len,
		  const ipmi_msg_t             *msg,
		  ipmi_addr_response_handler_t rsp_handler,
		  void                         *rsp_data1,
		  void                         *rsp_data2,
		  int			       side_effects)
{
    int                          rv;
    int                          u;
    ll_msg_t                     *nmsg;
    ipmi_system_interface_addr_t si;
    ipmi_ll_rsp_handler_t        handler;
    void                         *data4 = NULL;
    int                          is_ipmb = 0;
    ipmi_msgi_t                  *rspi;
    ipmi_con_option_t            opt_data[2];
    ipmi_con_option_t		 *options = NULL;

    if (addr_len > sizeof(ipmi_addr_t))
	return EINVAL;

    if (msg->data_len > IPMI_MAX_MSG_LENGTH)
	return EINVAL;

    if (domain->in_shutdown)
	return EINVAL;

    if (side_effects) {
	options = opt_data;
	options[0].option = IPMI_CON_MSG_OPTION_SIDE_EFFECTS;
	options[0].ival = 1;
	options[1].option = IPMI_CON_OPTION_LIST_END;
    }

    CHECK_DOMAIN_LOCK(domain);

    nmsg = ipmi_mem_alloc(sizeof(*nmsg));
    if (!nmsg)
	return ENOMEM;
    nmsg->rsp_item = ipmi_alloc_msg_item();
    if (!nmsg->rsp_item) {
	ipmi_mem_free(nmsg);
	return ENOMEM;
    }

    /* Copy the address here because where we send it may change.  But
       we want the response address to match what we sent. */
    memcpy(&nmsg->rsp_item->addr, addr, addr_len);
    nmsg->rsp_item->addr_len = addr_len;

    if (matching_domain_sysaddr(domain, addr, &si)) {
	/* We have a direct connection to this BMC and it is up and
	   operational, so talk directly to it. */
	u = si.channel;
	si.channel = IPMI_BMC_CHANNEL;
	addr = (ipmi_addr_t *) &si;
	addr_len = sizeof(si);
	handler = ll_si_rsp_handler;
    } else if ((addr->addr_type == IPMI_SYSTEM_INTERFACE_ADDR_TYPE)
	&& (addr->channel != IPMI_BMC_CHANNEL))
    {
	u = addr->channel;

	/* Messages to system interface addresses use the channel to
           choose which system address to message. */
	if ((u < 0) || (u >= MAX_CONS)) {
	    rv = EINVAL;
	    goto out;
	}
	if (!domain->conn[u]) {
	    rv = EINVAL;
	    goto out;
	}

	si.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
	si.channel = IPMI_BMC_CHANNEL;
	si.lun = ((ipmi_system_interface_addr_t *) addr)->lun;
	addr = (ipmi_addr_t *) &si;
	addr_len = sizeof(si);
	handler = ll_si_rsp_handler;
    } else {
	u = domain->working_conn;

	/* If we don't have any working connection, just use connection
	   zero. */
	if (u == -1)
	    u = 0;
	handler = ll_rsp_handler;
	is_ipmb = 1;
    }

    nmsg->domain = domain;
    nmsg->con = u;

    memcpy(&nmsg->msg, msg, sizeof(nmsg->msg));
    nmsg->msg.data = nmsg->msg_data;
    nmsg->msg.data_len = msg->data_len;
    memcpy(nmsg->msg.data, msg->data, msg->data_len);

    nmsg->rsp_handler = rsp_handler;
    nmsg->rsp_item->data1 = rsp_data1;
    nmsg->rsp_item->data2 = rsp_data2;

    nmsg->side_effects = side_effects;

    ipmi_lock(domain->cmds_lock);
    nmsg->seq = domain->cmds_seq;
    domain->cmds_seq++;

    /* Have to delay this to here so we are holding the lock. */
    if (is_ipmb)
	data4 = (void *) (long) domain->conn_seq[u];

    rspi = ipmi_alloc_msg_item();
    if (!rspi) {
	rv = ENOMEM;
	goto out_unlock;
    }

    rspi->data1 = domain;
    rspi->data2 = nmsg;
    rspi->data3 = (void *) nmsg->seq;
    rspi->data4 = data4;
    rv = send_command_option(domain, u, addr, addr_len,
			     msg, options, handler, rspi);

    if (rv) {
	ipmi_free_msg_item(rspi);
	goto out_unlock;
    } else if (is_ipmb) {
	/* If it's a system interface we don't add it to the list of
	   commands running, because it will never need to be
	   rerouted. */
	ilist_add_tail(domain->cmds, nmsg, &nmsg->link);
    }
 out_unlock:
    ipmi_unlock(domain->cmds_lock);

 out:
    if (rv) {
	ipmi_free_msg_item(nmsg->rsp_item);
	ipmi_mem_free(nmsg);
    }
    return rv;
}

int
ipmi_send_command_addr(ipmi_domain_t                *domain,
		       const ipmi_addr_t	    *addr,
		       unsigned int                 addr_len,
		       const ipmi_msg_t             *msg,
		       ipmi_addr_response_handler_t rsp_handler,
		       void                         *rsp_data1,
		       void                         *rsp_data2)
{
    return send_command_addr(domain, addr, addr_len, msg, rsp_handler,
			     rsp_data1, rsp_data2, 0);
}

int
ipmi_send_command_addr_sideeff(ipmi_domain_t                *domain,
			       const ipmi_addr_t	    *addr,
			       unsigned int                 addr_len,
			       const ipmi_msg_t             *msg,
			       ipmi_addr_response_handler_t rsp_handler,
			       void                         *rsp_data1,
			       void                         *rsp_data2)
{
    return send_command_addr(domain, addr, addr_len, msg, rsp_handler,
			     rsp_data1, rsp_data2, 1);
}

/* Take all the commands for any inactive or down connection and
   resend them on another connection.  */
static void
reroute_cmds(ipmi_domain_t *domain, int old_con, int new_con)
{
    ilist_iter_t iter;
    int          rv;
    ll_msg_t     *nmsg;

    ipmi_lock(domain->cmds_lock);
    ilist_init_iter(&iter, domain->cmds);
    rv = ilist_first(&iter);
    (domain->conn_seq[old_con])++;
    while (rv) {
	nmsg = ilist_get(&iter);
	if (nmsg->con == old_con) {
	    ipmi_msgi_t       *rspi;
	    ipmi_con_option_t opt_data[2];
	    ipmi_con_option_t *options = NULL;

	    nmsg->seq = domain->cmds_seq;
	    domain->cmds_seq++; /* Make the message unique so a
                                   response from the other connection
                                   will not match. */
	    nmsg->con = new_con;

	    rspi = ipmi_alloc_msg_item();
	    if (!rspi)
		goto send_err;

	    if (nmsg->side_effects) {
		options = opt_data;
		options[0].option = IPMI_CON_MSG_OPTION_SIDE_EFFECTS;
		options[0].ival = 1;
		options[1].option = IPMI_CON_OPTION_LIST_END;
	    }

	    rspi->data1 = domain;
	    rspi->data2 = nmsg;
	    rspi->data3 = (void *) nmsg->seq;
	    rspi->data4 = (void *) domain->conn_seq[new_con];
	    rv = send_command_option(domain, new_con,
				     &nmsg->rsp_item->addr,
				     nmsg->rsp_item->addr_len,
				     &nmsg->msg,
				     options,
				     ll_rsp_handler,
				     rspi);
	    if (rv) {
		ipmi_free_msg_item(rspi);
	    send_err:
		/* Couldn't send the message, just fail it. */
		if (nmsg->rsp_handler) {
		    rspi = nmsg->rsp_item;
		    rspi->msg.netfn = nmsg->msg.netfn | 1;
		    rspi->msg.cmd = nmsg->msg.cmd;
		    rspi->msg.data = rspi->data;
		    rspi->msg.data_len = 1;
		    rspi->data[0] = IPMI_UNKNOWN_ERR_CC;
		    deliver_rsp(domain, nmsg->rsp_handler, rspi);
		}
		rv = ilist_delete(&iter);
		ipmi_mem_free(nmsg);
		continue;
	    }
	}
	rv = ilist_next(&iter);
    }
    ipmi_unlock(domain->cmds_lock);
}

/***********************************************************************
 *
 * Bus scanning
 *
 **********************************************************************/

/* This is the number of device ID queries that an MC must not respond
   to in a row to be considered dead. */
#define MAX_MC_MISSED_RESPONSES 10

void
ipmi_domain_set_ipmb_rescan_time(ipmi_domain_t *domain, unsigned int seconds)
{
    int            rv;
    struct timeval timeout;

    CHECK_DOMAIN_LOCK(domain);

    ipmi_lock(domain->audit_domain_timer_info->lock);
    domain->audit_domain_interval = seconds;
    rv = domain->os_hnd->stop_timer(domain->os_hnd,
				    domain->audit_domain_timer);
    if (rv) {
	/* If we can't stop the timer, that's ok, the timer is in the
	   wakeup and will handle the restart for us. */
	ipmi_unlock(domain->audit_domain_timer_info->lock);
	return;
    }
    timeout.tv_sec = domain->audit_domain_interval;
    timeout.tv_usec = 0;
    domain->os_hnd->start_timer(domain->os_hnd,
				domain->audit_domain_timer,
				&timeout,
				domain_audit,
				domain->audit_domain_timer_info);
    ipmi_unlock(domain->audit_domain_timer_info->lock);
}

unsigned int
ipmi_domain_get_ipmb_rescan_time(ipmi_domain_t *domain)
{
    CHECK_DOMAIN_LOCK(domain);

    return domain->audit_domain_interval;
}

int
ipmi_domain_set_full_bus_scan(ipmi_domain_t *domain, int val)
{
    CHECK_DOMAIN_LOCK(domain);

    domain->do_bus_scan = val;
    return 0;
}

static void
add_bus_scans_running(ipmi_domain_t *domain, mc_ipmb_scan_info_t *info)
{
    info->next = domain->bus_scans_running;
    domain->bus_scans_running = info;
}

static void
remove_bus_scans_running(ipmi_domain_t *domain, mc_ipmb_scan_info_t *info)
{
    mc_ipmb_scan_info_t *i2;

    i2 = domain->bus_scans_running;
    if (i2 == info)
	domain->bus_scans_running = info->next;
    else
	while (i2->next != NULL) {
	    if (i2->next == info) {
		i2->next = info->next;
		break;
	    }
	    i2 = i2->next;
	}
}

static int devid_bc_rsp_handler(ipmi_domain_t *domain, ipmi_msgi_t *rspi);

static void
rescan_timeout_handler(void *cb_data, os_hnd_timer_id_t *id)
{
    mc_ipmb_scan_info_t *info = cb_data;
    int                 rv;
    ipmi_ipmb_addr_t    *ipmb;
    ipmi_domain_t       *domain;

    ipmi_lock(info->lock);
    if (info->cancelled) {
	ipmi_unlock(info->lock);
	info->os_hnd->free_timer(info->os_hnd, info->timer);
	ipmi_destroy_lock(info->lock);
	ipmi_mem_free(info);
	return;
    }
    info->timer_running = 0;
    ipmi_unlock(info->lock);

    domain = info->domain;
    rv = i_ipmi_domain_get(domain);
    if (rv) {
	ipmi_log(IPMI_LOG_INFO,
		 "%sdomain.c(rescan_timeout_handler): "
		 "BMC went away while scanning for MCs",
		 DOMAIN_NAME(domain));
	return;
    }

    goto retry_addr;

 next_addr_nolock:
    ipmb = (ipmi_ipmb_addr_t *) &info->addr;
    ipmb->slave_addr += 2;
    if ((info->addr.addr_type == IPMI_SYSTEM_INTERFACE_ADDR_TYPE)
	|| (ipmb->slave_addr > info->end_addr)) {
	/* We've hit the end, we can quit now. */
	if (info->done_handler)
	    info->done_handler(domain, 0, info->cb_data);
	remove_bus_scans_running(domain, info);
	info->os_hnd->free_timer(info->os_hnd, info->timer);
	ipmi_destroy_lock(info->lock);
	ipmi_mem_free(info);
	goto out;
    }
    info->missed_responses = 0;
    if (in_ipmb_ignores(domain, ipmb->channel, ipmb->slave_addr))
	goto next_addr_nolock;

 retry_addr:
    rv = ipmi_send_command_addr(domain,
				&(info->addr),
				info->addr_len,
				&(info->msg),
				devid_bc_rsp_handler,
				info, NULL);
    if (rv)
	goto next_addr_nolock;

 out:
    i_ipmi_domain_put(domain);
}

static int
devid_bc_rsp_handler(ipmi_domain_t *domain, ipmi_msgi_t *rspi)
{
    ipmi_msg_t          *msg = &rspi->msg;
    ipmi_addr_t         *addr = &rspi->addr;
    unsigned int        addr_len = rspi->addr_len;
    mc_ipmb_scan_info_t *info = rspi->data1;
    int                 rv;
    ipmi_mc_t           *mc = NULL;
    ipmi_ipmb_addr_t    *ipmb;
    int                 mc_added = 0;
    int                 mc_changed = 0;


    rv = i_ipmi_domain_get(domain);
    if (rv) {
	ipmi_log(IPMI_LOG_INFO,
		 "%sdomain.c(devid_bc_rsp_handler): "
		 "BMC went away while scanning for MCs",
		 DOMAIN_NAME(domain));
	return IPMI_MSG_ITEM_NOT_USED;
    }

    mc = i_ipmi_find_mc_by_addr(domain, addr, addr_len);
    if (msg->data[0] == 0) {
	if (mc && ipmi_mc_is_active(mc)
	    && !i_ipmi_mc_device_data_compares(mc, msg))
	{
	    /* The MC was replaced with a new one, so clear the old
               one and add a new one. */
	    i_ipmi_cleanup_mc(mc);
	    i_ipmi_mc_put(mc);
	    mc = i_ipmi_find_mc_by_addr(domain, addr, addr_len);
	}
	if (!mc || !ipmi_mc_is_active(mc)) {
	    /* It doesn't already exist, or it's inactive, so add
               it. */
	    if (!mc) {
		/* If it's not there, then add it.  If it's just not
                   active, reuse the same data. */
		rv = i_ipmi_create_mc(domain, addr, addr_len, &mc);
		if (rv) {
		    /* Out of memory, just give up for now. */
		    if (info->done_handler)
			info->done_handler(domain, 0, info->cb_data);
		    remove_bus_scans_running(domain, info);
		    info->os_hnd->free_timer(info->os_hnd, info->timer);
		    ipmi_destroy_lock(info->lock);
		    ipmi_mem_free(info);
		    goto out;
		}

		rv = add_mc_to_domain(domain, mc);
		if (rv) {
		    i_ipmi_cleanup_mc(mc);
		    goto next_addr;
		}

		rv = i_ipmi_mc_get_device_id_data_from_rsp(mc, msg);
		if (rv) {
		    /* If we couldn't handle the device data, just clean
		       it up */
		    i_ipmi_cleanup_mc(mc);
		    goto out;
		}

		/* In this case, the use count is defined to be 1, so
		   it will always be set up properly and the previous
		   function will not return EAGAIN, no need to
		   check. */

		mc_added = 1;
		i_ipmi_mc_handle_new(mc);
	    } else {
		/* It was inactive, activate it. */
		rv = i_ipmi_mc_get_device_id_data_from_rsp(mc, msg);
		if (rv == EAGAIN) {
		    /* The MC is in use, so we cannot handle it right
		       now.  We wait until the MC is released to do
		       that and cue off the pending new MC field in
		       the MC. */
		} else if (rv) {
		    /* If we couldn't handle the device data, just clean
		       it up. */
		    i_ipmi_cleanup_mc(mc);
		} else {
		    mc_changed = 1;
		    i_ipmi_mc_handle_new(mc);
		}
	    }
	} else {
	    /* Periodically check the MCs. */
	    i_ipmi_mc_check_mc(mc);
	}
    } else if (mc && ipmi_mc_is_active(mc)) {
	/* Didn't get a response.  Maybe the MC has gone away? */
	info->missed_responses++;

	/* We fail system interface addresses immediately, since they
           shouldn't be a timeout problem. */
	if ((info->addr.addr_type == IPMI_SYSTEM_INTERFACE_ADDR_TYPE)
	    || (info->missed_responses >= MAX_MC_MISSED_RESPONSES))
	{
	    i_ipmi_cleanup_mc(mc);
	    goto next_addr;
	} else {
	    /* Try again after a second. */
	    struct timeval timeout;

	    if (msg->data[0] == IPMI_TIMEOUT_CC)
		/* If we timed out, then no need to time, since a
		   second has gone by already. */
		goto retry_addr;

	    ipmi_lock(info->lock);
	    timeout.tv_sec = 1;
	    timeout.tv_usec = 0;
	    info->timer_running = 1;
	    info->os_hnd->start_timer(info->os_hnd,
				      info->timer,
				      &timeout,
				      rescan_timeout_handler,
				      info);
	    ipmi_unlock(info->lock);
	    goto out;
	}
    }

 next_addr:
    if (mc_added)
	call_mc_upd_handlers(domain, mc, IPMI_ADDED);
    else if (mc_changed)
	call_mc_upd_handlers(domain, mc, IPMI_CHANGED);

 next_addr_nolock:
    ipmb = (ipmi_ipmb_addr_t *) &info->addr;
    ipmb->slave_addr += 2;
    if ((info->addr.addr_type == IPMI_SYSTEM_INTERFACE_ADDR_TYPE)
	|| (ipmb->slave_addr > info->end_addr)) {
	/* We've hit the end, we can quit now. */
	if (info->done_handler)
	    info->done_handler(domain, 0, info->cb_data);
	remove_bus_scans_running(domain, info);
	info->os_hnd->free_timer(info->os_hnd, info->timer);
	ipmi_destroy_lock(info->lock);
	ipmi_mem_free(info);
	goto out;
    }
    info->missed_responses = 0;
    if (in_ipmb_ignores(domain, ipmb->channel, ipmb->slave_addr))
	goto next_addr_nolock;

 retry_addr:
    rv = ipmi_send_command_addr(domain,
				&(info->addr),
				info->addr_len,
				&(info->msg),
				devid_bc_rsp_handler,
				info, NULL);
    if (rv)
	goto next_addr_nolock;

 out:
    if (mc)
	i_ipmi_mc_put(mc);
    i_ipmi_domain_put(domain);
    return IPMI_MSG_ITEM_NOT_USED;
}

int
ipmi_start_ipmb_mc_scan(ipmi_domain_t  *domain,
	       		int            channel,
	       		unsigned int   start_addr,
			unsigned int   end_addr,
			ipmi_domain_cb done_handler,
			void           *cb_data)
{
    mc_ipmb_scan_info_t *info;
    int                 rv;
    ipmi_ipmb_addr_t    *ipmb;

    CHECK_DOMAIN_LOCK(domain);

    if (channel >= MAX_IPMI_USED_CHANNELS)
	return EINVAL;

    if ((domain->chan[channel].medium != 1)
	&& !(start_addr == 0x20 || end_addr == 0x20))
	/* Make sure it is IPMB, or the BMC address. */
	return ENOSYS;

    info = ipmi_mem_alloc(sizeof(*info));
    if (!info)
	return ENOMEM;
    memset(info, 0, sizeof(*info));

    info->domain = domain;
    ipmb = (ipmi_ipmb_addr_t *) &info->addr;
    ipmb->addr_type = IPMI_IPMB_BROADCAST_ADDR_TYPE;
    ipmb->channel = channel;
    ipmb->slave_addr = start_addr;
    ipmb->lun = 0;
    info->addr_len = sizeof(*ipmb);
    info->msg.netfn = IPMI_APP_NETFN;
    info->msg.cmd = IPMI_GET_DEVICE_ID_CMD;
    info->msg.data = NULL;
    info->msg.data_len = 0;
    info->end_addr = end_addr;
    info->done_handler = done_handler;
    info->cb_data = cb_data;
    info->missed_responses = 0;
    info->os_hnd = domain->os_hnd;
    rv = info->os_hnd->alloc_timer(info->os_hnd, &info->timer);
    if (rv)
	goto out_err;

    rv = ipmi_create_lock(domain, &info->lock);
    if (rv)
	goto out_err;

    rv = ENOSYS; /* Return err if no scans done */

    /* Skip addresses we must ignore. */
    while (in_ipmb_ignores(domain, ipmb->channel, ipmb->slave_addr)) {
	/* ipmb->slave_addr is 8 bits, so we can't do a <= comparison as it
	   will overflow after 254. */
	if (ipmb->slave_addr == end_addr)
	    goto out_err;
	ipmb->slave_addr += 2;
    }
    while (rv) {
	rv = ipmi_send_command_addr(domain,
				    &info->addr,
				    info->addr_len,
				    &(info->msg),
				    devid_bc_rsp_handler,
				    info, NULL);
	if (rv) {
	    if (ipmb->slave_addr == end_addr)
		goto out_err;
	    ipmb->slave_addr += 2;
	}
    }

    if (rv)
	goto out_err;
    else
	add_bus_scans_running(domain, info);
    return 0;

 out_err:
    if (info->timer)
	info->os_hnd->free_timer(info->os_hnd, info->timer);
    if (info->lock)
	ipmi_destroy_lock(info->lock);
    ipmi_mem_free(info);
    return 0; /* Since the done handler is always called, always
		 return true.  Bus scans always succeed. */
}

int
ipmi_start_si_scan(ipmi_domain_t  *domain,
		   int            si_num,
		   ipmi_domain_cb done_handler,
		   void           *cb_data)
{
    mc_ipmb_scan_info_t          *info;
    ipmi_system_interface_addr_t *si;
    int                          rv;

    info = ipmi_mem_alloc(sizeof(mc_ipmb_scan_info_t));
    if (!info) 
	return ENOMEM;
    memset(info, 0, sizeof(*info));

    info->domain = domain;
    si = (void *) &info->addr;
    si->addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
    si->channel = si_num;
    si->lun = 0;
    info->addr_len = sizeof(*si);
    info->msg.netfn = IPMI_APP_NETFN;
    info->msg.cmd = IPMI_GET_DEVICE_ID_CMD;
    info->msg.data = NULL;
    info->msg.data_len = 0;
    info->done_handler = done_handler;
    info->cb_data = cb_data;
    info->missed_responses = 0;
    info->os_hnd = domain->os_hnd;
    rv = info->os_hnd->alloc_timer(info->os_hnd, &info->timer);
    if (rv)
	goto out_err;

    rv = ipmi_create_lock(domain, &info->lock);
    if (rv)
	goto out_err;

    rv = ipmi_send_command_addr(domain,
				&info->addr,
				info->addr_len,
				&(info->msg),
				devid_bc_rsp_handler,
				info, NULL);
    if (rv)
	goto out_err;
    else
	add_bus_scans_running(domain, info);
    return 0;

 out_err:
    if (info->timer)
	info->os_hnd->free_timer(info->os_hnd, info->timer);
    if (info->lock)
	ipmi_destroy_lock(info->lock);
    ipmi_mem_free(info);
    return rv;
}

static void
mc_scan_done(ipmi_domain_t *domain, int err, void *cb_data)
{
    ipmi_domain_cb bus_scan_handler;
    void           *bus_scan_handler_cb_data;

    ipmi_lock(domain->mc_lock);
    domain->scanning_bus_count--;
    if (domain->scanning_bus_count) {
	i_ipmi_put_domain_fully_up(domain, "mc_scan_done");
	ipmi_unlock(domain->mc_lock);
	return;
    }

    bus_scan_handler = domain->bus_scan_handler;
    bus_scan_handler_cb_data = domain->bus_scan_handler_cb_data;
    ipmi_unlock(domain->mc_lock);
    if (bus_scan_handler)
	bus_scan_handler(domain, 0,
			 bus_scan_handler_cb_data);
    i_ipmi_put_domain_fully_up(domain, "mc_scan_done");
}

void
i_ipmi_start_mc_scan_one(ipmi_domain_t *domain, int chan, int first, int last)
{
    int rv;

    i_ipmi_get_domain_fully_up(domain, "i_ipmi_start_mc_scan_one");
    domain->scanning_bus_count++;
    rv = ipmi_start_ipmb_mc_scan(domain, chan, first, last,
				 mc_scan_done, NULL);
    if (rv) {
	domain->scanning_bus_count--;
	i_ipmi_put_domain_fully_up(domain, "i_ipmi_start_mc_scan_one");
    }
}

static int
cmp_int(const void *v1, const void *v2)
{
    const int *i1 = v1;
    const int *i2 = v2;
    if (*i1 < *i2)
	return -1;
    else if (*i1 > *i2)
	return 1;
    else
	return 0;
}

void
ipmi_domain_start_full_ipmb_scan(ipmi_domain_t *domain)
{
    int i, j;
    int rv;
    int got_bmc = 0;

    if (domain->in_shutdown)
	return;

    ipmi_lock(domain->mc_lock);
    if (!domain->do_bus_scan || (!ipmi_option_IPMB_scan(domain))) {
	/* Always scan the local BMC(s). */
	for (i=0; i<MAX_CONS; i++) {
	    if (!domain->conn[i])
		continue;
	    for (j=0; j<MAX_IPMI_USED_CHANNELS; j++) {
		if (domain->chan[j].medium != IPMI_CHANNEL_MEDIUM_IPMB)
		    continue;
		i_ipmi_start_mc_scan_one(domain, j,
					 domain->con_ipmb_addr[i][j],
					 domain->con_ipmb_addr[i][j]);
		break;
	    }
	    if (j == MAX_IPMI_USED_CHANNELS) {
		/* Didn't find a valid channel, just scan 0 to get one. */
		i_ipmi_start_mc_scan_one(domain, 0,
					 domain->con_ipmb_addr[i][0],
					 domain->con_ipmb_addr[i][0]);
	    }
	}
	ipmi_unlock(domain->mc_lock);
	return;
    }

    if (domain->scanning_bus_count) {
	ipmi_unlock(domain->mc_lock);
	return;
    }

    /* If a connections supports sysaddress scanning, then scan the
       system address for that connection. */
    for (i=0; i<MAX_CONS; i++) {
	if ((domain->con_up[i]) && domain->conn[i]->scan_sysaddr) {
	    i_ipmi_get_domain_fully_up(domain,
				       "ipmi_domain_start_full_ipmb_scan");
	    domain->scanning_bus_count++;
	    rv = ipmi_start_si_scan(domain, i, mc_scan_done, NULL);
	    if (rv) {
		domain->scanning_bus_count--;
		i_ipmi_put_domain_fully_up(domain,
					   "ipmi_domain_start_full_ipmb_scan");
	    }
	}
    }

    /* Now start the IPMB scans. */
    for (i=0; i<MAX_IPMI_USED_CHANNELS; i++) {
	if (domain->chan[i].medium == IPMI_CHANNEL_MEDIUM_IPMB) {
	    if (!got_bmc) {
		got_bmc = 1;
		/* Always scan the normal BMC first. */
		i_ipmi_start_mc_scan_one(domain, i, 0x20, 0x20);
		i_ipmi_start_mc_scan_one(domain, i, 0x10, 0xf0);
	    } else {
		/* This is unfortunately complicated.  We only want
		   the BMC to show up in one place, so we only scan
		   the BMC's address on the first one.  If we have a
		   system with two connections (two BMCs), we want to
		   make sure they don't show up on each others lists.
		   So except for the first IPMB, we ignore all BMC
		   IPMB addresses. */
		int ignore_addr[MAX_CONS];
		int num_ignore = 0;
		int cstart = 0x10;
		for (j=0; j<MAX_CONS; j++) {
		    if (! domain->conn[j])
			continue;
		    ignore_addr[num_ignore] = domain->con_ipmb_addr[j][i];
		    num_ignore++;
		}
		qsort(ignore_addr, num_ignore, sizeof(int), cmp_int);
		for (j=0; j<num_ignore; j++) {
		    i_ipmi_start_mc_scan_one(domain, i,
					     cstart, ignore_addr[j]-1);
		    cstart = ignore_addr[j]+1;
		}
		if (cstart <= 0xf0)
		    i_ipmi_start_mc_scan_one(domain, i, cstart, 0xf0);
	    }
	}
    }
    ipmi_unlock(domain->mc_lock);
}

static void
refetch_sdr_handler(ipmi_sdr_info_t *sdrs,
		    int             err,
		    int             changed,
		    unsigned int    count,
		    void            *cb_data)
{
    ipmi_domain_t *domain = cb_data;

    if (changed) {
	ipmi_entity_scan_sdrs(domain, NULL,
			      domain->entities, domain->main_sdrs);
	ipmi_sensor_handle_sdrs(domain, NULL, domain->main_sdrs);
	ipmi_detect_ents_presence_changes(domain->entities, 1);
	i_ipmi_entities_report_sdrs_read(domain->entities);
    }
}

static void
check_main_sdrs(ipmi_domain_t *domain)
{
    if (ipmi_option_SDRs(domain))
	ipmi_sdr_fetch(domain->main_sdrs, refetch_sdr_handler, domain);
}

static void
domain_audit(void *cb_data, os_hnd_timer_id_t *id)
{
    struct timeval      timeout;
    audit_domain_info_t *info = cb_data;
    ipmi_domain_t       *domain = info->domain;
    int                 rv;

    ipmi_lock(info->lock);
    if (info->cancelled) {
	ipmi_unlock(info->lock);
	info->os_hnd->free_timer(info->os_hnd, id);
	ipmi_destroy_lock(info->lock);
	ipmi_mem_free(info);
	return;
    }

    rv = i_ipmi_domain_get(domain);
    if (rv)
	goto out;

    if (domain->got_invalid_dev_id) {
	/* Failure getting device id, just try again. */
	domain_send_mc_id(domain);
	goto out_start_timer;
    }

    /* Only operate if we know a connection is up. */
    if (! domain->connection_up)
	goto out_start_timer;

    /* Rescan all the presence sensors to make sure they are valid. */
    ipmi_detect_domain_presence_changes(domain, 1);
    
    ipmi_domain_start_full_ipmb_scan(domain);

    /* Also check to see if the SDRs have changed. */
    check_main_sdrs(domain);

 out_start_timer:
    timeout.tv_sec = domain->audit_domain_interval;
    timeout.tv_usec = 0;
    domain->os_hnd->start_timer(domain->os_hnd,
				id,
				&timeout,
				domain_audit,
				info);
    i_ipmi_domain_put(domain);
 out:
    ipmi_unlock(info->lock);
}

/***********************************************************************
 *
 * Incoming event handling.
 *
 **********************************************************************/

typedef struct event_sensor_info_s
{
    int          err;
    ipmi_event_t *event;
} event_sensor_info_t;

void
event_sensor_cb(ipmi_sensor_t *sensor, void *cb_data)
{
    event_sensor_info_t *info = cb_data;

    /* It's an event for a specific sensor, and the sensor exists. */
    info->err = ipmi_sensor_event(sensor, info->event);
}

void
i_ipmi_domain_system_event_handler(ipmi_domain_t *domain,
				   ipmi_mc_t     *ev_mc,
				   ipmi_event_t  *event)
{
    int          rv = 1;
    ipmi_time_t  timestamp = ipmi_event_get_timestamp(event);
    unsigned int type = ipmi_event_get_type(event);

    /* We do not need any locking to assure that events are delivered
       in order (from the same SEL).  Indeed, locking here wouldn't
       help.  But the event-fetching mechanisms are guaranteed to be
       single-threaded, so ordering is always preserved there. */

    if (DEBUG_EVENTS) {
	ipmi_mcid_t         mcid = ipmi_event_get_mcid(event);
	unsigned int        record_id = ipmi_event_get_record_id(event);
	unsigned int        data_len = ipmi_event_get_data_len(event);
	const unsigned char *data;

	ipmi_log(IPMI_LOG_DEBUG_START,
		 "Event recid mc (0x%x):%4.4x type:%2.2x timestamp %lld:",
		 mcid.mc_num, record_id, type, (long long) timestamp);
	if (data_len) {
	    ipmi_log(IPMI_LOG_DEBUG_CONT, "\n  ");
	    data = ipmi_event_get_data_ptr(event);
	    dump_hex(data, data_len);
	}
	ipmi_log(IPMI_LOG_DEBUG_END, " ");
    }

    /* Let the OEM handler for the MC that the message came from have
       a go at it first.  Note that OEM handlers must look at the time
       themselves. */
    if (i_ipmi_mc_check_sel_oem_event_handler(ev_mc, event))
	return;

    /* It's a system event record from an MC, and the timestamp is
       later than our startup timestamp. */
    if ((type == 0x02) && !ipmi_event_is_old(event)) {
	/* It's a standard IPMI event. */
	ipmi_mc_t           *mc;
	ipmi_sensor_id_t    id;
	event_sensor_info_t info;
	const unsigned char *data;

	mc = i_ipmi_event_get_generating_mc(domain, ev_mc, event);
	if (!mc)
	    goto out;

	/* Let the OEM handler for the MC that sent the event try
	   next. */
	if (i_ipmi_mc_check_oem_event_handler(mc, event)) {
	    i_ipmi_mc_put(mc);
	    return;
	}

	/* The OEM code didn't handle it. */
	data = ipmi_event_get_data_ptr(event);
	id.mcid = ipmi_mc_convert_to_id(mc);
	id.lun = data[5] & 0x3;
	id.sensor_num = data[8];

	info.event = event;

	rv = ipmi_sensor_pointer_cb(id, event_sensor_cb, &info);
	if (!rv)
	    rv = info.err;

	i_ipmi_mc_put(mc);
    }

 out:
    /* It's an event from system software, or the info couldn't be found. */
    if (rv)
	ipmi_handle_unhandled_event(domain, event);
}

static void
ll_event_handler(ipmi_con_t        *ipmi,
		 const ipmi_addr_t *addr,
		 unsigned int      addr_len,
		 ipmi_event_t      *event,
		 void              *cb_data)
{
    ipmi_domain_t                *domain = cb_data;
    ipmi_mc_t                    *mc;
    int                          rv;
    ipmi_system_interface_addr_t si;

    rv = i_ipmi_domain_get(domain);
    if (rv)
	return;

    /* Convert the address to the proper one if it comes from a
       specific connection. */
    if (addr->addr_type == IPMI_SYSTEM_INTERFACE_ADDR_TYPE) {
	int i;

	for (i=0; i<MAX_CONS; i++) {
	    if (domain->conn[i] == ipmi)
		break;
	}
	if (i == MAX_CONS)
	    goto out;
	addr = (ipmi_addr_t *) &si;
	si.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
	si.channel = i;
	si.lun = 0;
	addr_len = sizeof(si);
    }

    /* It came from an MC, so find the MC. */
    mc = i_ipmi_find_mc_by_addr(domain, addr, addr_len);
    if (!mc)
	goto out;
    ipmi_event_set_mcid(event, ipmi_mc_convert_to_id(mc));

    if (event == NULL) {
	/* The incoming event didn't carry the full event information.
	   Just scan for events in the MC's SEL. */
	ipmi_mc_reread_sel(mc, NULL, NULL);
    } else {
	/* Add it to the mc's event log. */
	rv = i_ipmi_mc_sel_event_add(mc, event);

	if (rv != EEXIST)
	    /* Call the handler on it if it wasn't already in there. */
	    i_ipmi_domain_system_event_handler(domain, mc, event);
    }
    i_ipmi_mc_put(mc);

 out:
    i_ipmi_domain_put(domain);
}

typedef struct call_event_handler_s
{
    ipmi_domain_t *domain;
    ipmi_event_t  *event;
} call_event_handler_t;

static int
call_event_handler(void *cb_data, void *item1, void *item2)
{
    call_event_handler_t  *info = cb_data;
    ipmi_event_handler_cb handler = item1;

    handler(info->domain, info->event, item2);
    return LOCKED_LIST_ITER_CONTINUE;
}

void
ipmi_handle_unhandled_event(ipmi_domain_t *domain, ipmi_event_t *event)
{
    call_event_handler_t info;

    info.domain = domain;
    info.event = event;
    locked_list_iterate(domain->event_handlers, call_event_handler, &info);
}

int
ipmi_domain_add_event_handler(ipmi_domain_t           *domain,
			      ipmi_event_handler_cb   handler,
			      void                    *cb_data)
{
    CHECK_DOMAIN_LOCK(domain);

    if (locked_list_add(domain->event_handlers, handler, cb_data))
	return 0;
    else
	return ENOMEM;
}

int
ipmi_domain_remove_event_handler(ipmi_domain_t           *domain,
				 ipmi_event_handler_cb   handler,
				 void                    *cb_data)
{
    CHECK_DOMAIN_LOCK(domain);

    if (locked_list_remove(domain->event_handlers, handler, cb_data))
	return 0;
    else
	return EINVAL;
}

typedef struct event_handler_cl_info_s
{
    ipmi_event_handler_cb handler;
    void                  *handler_data;
} event_handler_cl_info_t;


static int
iterate_event_handler_cl(void *cb_data, void *item1, void *item2)
{
    event_handler_cl_info_t  *info = cb_data;
    ipmi_event_handler_cl_cb handler = item1;

    handler(info->handler, info->handler_data, item2);
    return LOCKED_LIST_ITER_CONTINUE;
}

static void
call_event_handler_cl_handlers(ipmi_domain_t         *domain,
			       ipmi_event_handler_cb handler,
			       void                  *handler_data)
{
    event_handler_cl_info_t info;

    info.handler = handler;
    info.handler_data = handler_data;
    locked_list_iterate(domain->event_handlers_cl, iterate_event_handler_cl,
			&info);
}

int
ipmi_domain_add_event_handler_cl(ipmi_domain_t            *domain,
				 ipmi_event_handler_cl_cb handler,
				 void                     *cb_data)
{
    CHECK_DOMAIN_LOCK(domain);

    if (locked_list_add(domain->event_handlers_cl, handler, cb_data))
	return 0;
    else
	return ENOMEM;
}

int
ipmi_domain_remove_event_handler_cl(ipmi_domain_t            *domain,
				    ipmi_event_handler_cl_cb handler,
				    void                     *cb_data)
{
    CHECK_DOMAIN_LOCK(domain);

    if (locked_list_remove(domain->event_handlers_cl, handler, cb_data))
	return 0;
    else
	return EINVAL;
}

int
ipmi_domain_disable_events(ipmi_domain_t *domain)
{
    int rv;
    int return_rv = 0;
    int i;

    CHECK_DOMAIN_LOCK(domain);

    for (i=0; i<MAX_CONS; i++) {
	rv = domain->conn[i]->remove_event_handler(domain->conn[i],
						   ll_event_handler,
						   domain);
	if (!return_rv)
	    return_rv = rv;
    }
    return return_rv;
}

int
ipmi_domain_enable_events(ipmi_domain_t *domain)
{
    int return_rv = 0;
    int rv;
    int i;

    CHECK_DOMAIN_LOCK(domain);

    for (i=0; i<MAX_CONS; i++) {
	if (! domain->conn[i])
	    continue;

	rv = domain->conn[i]->add_event_handler(domain->conn[i],
						ll_event_handler,
						domain);
	if (!return_rv)
	    return_rv = rv;
    }
    return return_rv;
}

/***********************************************************************
 *
 * SEL handling
 *
 **********************************************************************/

int
ipmi_domain_del_event(ipmi_domain_t  *domain,
		      ipmi_event_t   *event,
		      ipmi_domain_cb done_handler,
		      void           *cb_data)
{
    return ipmi_event_delete(event, done_handler, cb_data);
}

typedef struct next_event_handler_info_s
{
    ipmi_event_t       *rv;
    const ipmi_event_t *event;
    ipmi_mcid_t        event_mcid;
    int                found_curr_mc;
    int                do_prev; /* If going backwards, this will be 1. */
} next_event_handler_info_t;

static void
next_event_handler(ipmi_domain_t *domain, ipmi_mc_t *mc, void *cb_data)
{
    next_event_handler_info_t *info = cb_data;
    ipmi_mcid_t               mcid = ipmi_mc_convert_to_id(mc);

    if (info->rv)
	/* We've found an event already, just return. */
	return;

    if (info->do_prev) {
	if (info->found_curr_mc)
	    /* We've found the MC that had the event, but it didn't have
	       any more events.  Look for last events now. */
	    info->rv = ipmi_mc_last_event(mc);
	else if (ipmi_cmp_mc_id(info->event_mcid, mcid) == 0) {
	    info->found_curr_mc = 1;
	    info->rv = ipmi_mc_prev_event(mc, info->event);
	}
    } else {
	if (info->found_curr_mc)
	    /* We've found the MC that had the event, but it didn't have
	       any more events.  Look for first events now. */
	    info->rv = ipmi_mc_first_event(mc);
	else if (ipmi_cmp_mc_id(info->event_mcid, mcid) == 0) {
	    info->found_curr_mc = 1;
	    info->rv = ipmi_mc_next_event(mc, info->event);
	}
    }
}

ipmi_event_t *
ipmi_domain_first_event(ipmi_domain_t *domain)
{
    next_event_handler_info_t info;

    CHECK_DOMAIN_LOCK(domain);

    info.rv = NULL;
    info.event = NULL;
    info.found_curr_mc = 1;
    info.do_prev = 0;
    ipmi_domain_iterate_mcs(domain, next_event_handler, &info);

    return info.rv;
}

ipmi_event_t *
ipmi_domain_last_event(ipmi_domain_t *domain)
{
    next_event_handler_info_t info;

    CHECK_DOMAIN_LOCK(domain);

    info.rv = NULL;
    info.event = NULL;
    info.found_curr_mc = 1;
    info.do_prev = 1;
    ipmi_domain_iterate_mcs_rev(domain, next_event_handler, &info);

    return info.rv;
}

ipmi_event_t *
ipmi_domain_next_event(ipmi_domain_t *domain, const ipmi_event_t *event)
{
    next_event_handler_info_t info;

    CHECK_DOMAIN_LOCK(domain);

    info.rv = NULL;
    info.event = event;
    info.found_curr_mc = 0;
    info.do_prev = 0;
    info.event_mcid = ipmi_event_get_mcid(event);
    ipmi_domain_iterate_mcs(domain, next_event_handler, &info);

    return info.rv;
}

ipmi_event_t *
ipmi_domain_prev_event(ipmi_domain_t *domain, const ipmi_event_t *event)
{
    next_event_handler_info_t info;

    CHECK_DOMAIN_LOCK(domain);

    info.rv = NULL;
    info.event = event;
    info.found_curr_mc = 0;
    info.do_prev = 1;
    info.event_mcid = ipmi_event_get_mcid(event);
    ipmi_domain_iterate_mcs_rev(domain, next_event_handler, &info);

    return info.rv;
}

static void
sel_count_handler(ipmi_domain_t *domain, ipmi_mc_t *mc, void *cb_data)
{
    int *count = cb_data;

    *count += ipmi_mc_sel_count(mc);
}

int
ipmi_domain_sel_count(ipmi_domain_t *domain,
		      unsigned int  *count)
{
    CHECK_DOMAIN_LOCK(domain);

    *count = 0;
    ipmi_domain_iterate_mcs(domain, sel_count_handler, count);
    return 0;
}

static void
sel_entries_used_handler(ipmi_domain_t *domain, ipmi_mc_t *mc, void *cb_data)
{
    int *count = cb_data;

    *count += ipmi_mc_sel_entries_used(mc);
}

int ipmi_domain_sel_entries_used(ipmi_domain_t *domain,
				 unsigned int  *count)
{
    CHECK_DOMAIN_LOCK(domain);

    *count = 0;
    ipmi_domain_iterate_mcs(domain, sel_entries_used_handler, count);
    return 0;
}

static void
set_sel_rescan_time(ipmi_domain_t *domain, ipmi_mc_t *mc, void *cb_data)
{
    ipmi_mc_set_sel_rescan_time(mc, domain->default_sel_rescan_time);
}

void
ipmi_domain_set_sel_rescan_time(ipmi_domain_t *domain,
				unsigned int  seconds)
{
    CHECK_DOMAIN_LOCK(domain);

    domain->default_sel_rescan_time = seconds;
    ipmi_domain_iterate_mcs(domain, set_sel_rescan_time, NULL);
}

unsigned int
ipmi_domain_get_sel_rescan_time(ipmi_domain_t *domain)
{
    CHECK_DOMAIN_LOCK(domain);

    return domain->default_sel_rescan_time;
}

/* Code to explicitly reread all the SELs in the domain. */
typedef struct sels_reread_s
{
    /* The number of pending requests. */
    int         count;

    /* The actual number of MCs we tried to request from .*/
    int         tried;

    /* This is the last error that occurred. */
    int         err;

    ipmi_domain_cb handler;
    void           *cb_data;

    /* We may have multiple threads going at the data from multiple
       SEL reads, so we need to protect the data. */
    ipmi_lock_t *lock;

    ipmi_domain_t *domain;
} sels_reread_t;

static void
reread_sel_handler(ipmi_mc_t *mc, int err, void *cb_data)
{
    sels_reread_t *info = cb_data;
    int           count;
    int           rv;

    ipmi_lock(info->lock);
    info->count--;
    count = info->count;
    if (err)
	info->err = err;
    ipmi_unlock(info->lock);
    if (count == 0) {
	/* We were the last one, call the main handler. */

	/* First validate the domain. */
	rv = i_ipmi_domain_get(info->domain);
	if (rv)
	    info->domain = NULL;

	if (info->handler)
	    info->handler(info->domain, info->err, info->cb_data);
	ipmi_destroy_lock(info->lock);
	if (info->domain)
	    i_ipmi_domain_put(info->domain);
	ipmi_mem_free(info);
    }
}

static void
reread_sels_handler(ipmi_domain_t *domain,
		    ipmi_mc_t     *mc,
		    void          *cb_data)
{
    sels_reread_t *info = cb_data;
    int           rv;

    if (ipmi_mc_sel_device_support(mc)) {
	info->tried++;
	rv = ipmi_mc_reread_sel(mc, reread_sel_handler, info);
	if (rv)
	    info->err = rv;
	else
	    info->count++;
    }
}

int
ipmi_domain_reread_sels(ipmi_domain_t  *domain,
			ipmi_domain_cb handler,
			void           *cb_data)
{
    sels_reread_t *info;
    int           rv;

    info = ipmi_mem_alloc(sizeof(*info));
    if (!info)
	return ENOMEM;

    rv = ipmi_create_lock(domain, &info->lock);
    if (rv) {
	ipmi_mem_free(info);
	return rv;
    }
    info->count = 0;
    info->tried = 0;
    info->err = 0;
    info->domain = domain;
    info->handler = handler;
    info->cb_data = cb_data;
    ipmi_lock(info->lock);
    rv = ipmi_domain_iterate_mcs(domain, reread_sels_handler, info);
    if (rv) {
	ipmi_unlock(info->lock);
	ipmi_destroy_lock(info->lock);
	ipmi_mem_free(info);
	return rv;
    }
    if ((info->tried > 0) && (info->count == 0)) {
	/* We tried to do an SEL fetch, but failed to actually
	   accomplish any.  Return an error. */
	rv = info->err;
	ipmi_unlock(info->lock);
	ipmi_destroy_lock(info->lock);
	ipmi_mem_free(info);
	return rv;
    }

    if (info->count == 0) {
	/* No requests, so return an error. */
	ipmi_unlock(info->lock);
	ipmi_destroy_lock(info->lock);
	ipmi_mem_free(info);
	return ENOSYS;
    }
    ipmi_unlock(info->lock);

    return 0;
}

/***********************************************************************
 *
 * Generic handling of entities and MCs.
 *
 **********************************************************************/

int
ipmi_detect_domain_presence_changes(ipmi_domain_t *domain, int force)
{
    int rv;

    CHECK_DOMAIN_LOCK(domain);
    
    rv = ipmi_detect_ents_presence_changes(domain->entities, force);
    return rv;
}

os_handler_t *
ipmi_domain_get_os_hnd(ipmi_domain_t *domain)
{
    CHECK_DOMAIN_LOCK(domain);
    return domain->os_hnd;
}

ipmi_entity_info_t *
ipmi_domain_get_entities(ipmi_domain_t *domain)
{
    CHECK_DOMAIN_LOCK(domain);
    return domain->entities;
}

void
i_ipmi_get_sdr_sensors(ipmi_domain_t *domain,
		       ipmi_mc_t     *mc,
		       ipmi_sensor_t ***sensors,
		       unsigned int  *count)
{
    if (mc) {
	i_ipmi_mc_get_sdr_sensors(mc, sensors, count);
    } else {
	CHECK_DOMAIN_LOCK(domain);
	*sensors = domain->sensors_in_main_sdr;
	*count = domain->sensors_in_main_sdr_count;
    }
}

void
i_ipmi_set_sdr_sensors(ipmi_domain_t *domain,
		       ipmi_mc_t     *mc,
		       ipmi_sensor_t **sensors,
		       unsigned int  count)
{
    if (mc) {
	i_ipmi_mc_set_sdr_sensors(mc, sensors, count);
    } else {
	CHECK_DOMAIN_LOCK(domain);
	domain->sensors_in_main_sdr = sensors;
	domain->sensors_in_main_sdr_count = count;
    }
}

void *
i_ipmi_get_sdr_entities(ipmi_domain_t *domain,
			ipmi_mc_t     *mc)
{
    if (mc) {
	return i_ipmi_mc_get_sdr_entities(mc);
    } else {
	CHECK_DOMAIN_LOCK(domain);
	return domain->entities_in_main_sdr;
    }
}

void
i_ipmi_set_sdr_entities(ipmi_domain_t *domain,
			ipmi_mc_t     *mc,
			void          *entities)
{
    if (mc) {
	i_ipmi_mc_set_sdr_entities(mc, entities);
    } else {
	CHECK_DOMAIN_LOCK(domain);
	domain->entities_in_main_sdr = entities;
    }
}

int
ipmi_domain_add_entity_update_handler(ipmi_domain_t         *domain,
				      ipmi_domain_entity_cb handler,
				      void                  *cb_data)
{
    CHECK_DOMAIN_LOCK(domain);

    return ipmi_entity_info_add_update_handler(domain->entities,
					       handler,
					       cb_data);
}

int
ipmi_domain_remove_entity_update_handler(ipmi_domain_t         *domain,
					 ipmi_domain_entity_cb handler,
					 void                  *cb_data)
{
    CHECK_DOMAIN_LOCK(domain);

    return ipmi_entity_info_remove_update_handler(domain->entities,
						  handler,
						  cb_data);
}

int
ipmi_domain_add_entity_update_handler_cl(ipmi_domain_t            *domain,
					 ipmi_domain_entity_cl_cb handler,
					 void                     *cb_data)
{
    CHECK_DOMAIN_LOCK(domain);

    return ipmi_entity_info_add_update_handler_cl(domain->entities,
						  handler,
						  cb_data);
}

int
ipmi_domain_remove_entity_update_handler_cl(ipmi_domain_t            *domain,
					    ipmi_domain_entity_cl_cb handler,
					    void                     *cb_data)
{
    CHECK_DOMAIN_LOCK(domain);

    return ipmi_entity_info_remove_update_handler_cl(domain->entities,
						     handler,
						     cb_data);
}

int
ipmi_domain_iterate_entities(ipmi_domain_t      *domain,
			     ipmi_entity_ptr_cb handler,
			     void               *cb_data)
{
    CHECK_DOMAIN_LOCK(domain);

    ipmi_entities_iterate_entities(domain->entities, handler, cb_data);
    return 0;
}

int
ipmi_domain_iterate_mcs(ipmi_domain_t              *domain,
			ipmi_domain_iterate_mcs_cb handler,
			void                       *cb_data)
{
    int i, j;

    CHECK_DOMAIN_LOCK(domain);

    ipmi_lock(domain->mc_lock);
    for (i=0; i<MAX_CONS; i++) {
	ipmi_mc_t *mc = domain->sys_intf_mcs[i];
	if (mc && !i_ipmi_mc_get(mc)) {
	    ipmi_unlock(domain->mc_lock);
	    handler(domain, mc, cb_data);
	    i_ipmi_mc_put(mc);
	    ipmi_lock(domain->mc_lock);
	}
    }
    for (i=0; i<IPMB_HASH; i++) {
	mc_table_t *tab = &(domain->ipmb_mcs[i]);

	for (j=0; j<tab->size; j++) {
	    ipmi_mc_t *mc = tab->mcs[j];
	    if (mc && !i_ipmi_mc_get(mc)) {
		ipmi_unlock(domain->mc_lock);
		handler(domain, mc, cb_data);
		i_ipmi_mc_put(mc);
		ipmi_lock(domain->mc_lock);
	    }
	}
    }
    ipmi_unlock(domain->mc_lock);
    return 0;
}

int
ipmi_domain_iterate_mcs_rev(ipmi_domain_t              *domain,
			    ipmi_domain_iterate_mcs_cb handler,
			    void                       *cb_data)
{
    int i, j;

    CHECK_DOMAIN_LOCK(domain);

    ipmi_lock(domain->mc_lock);
    for (i=IPMB_HASH-1; i>=0; i--) {
	mc_table_t *tab = &(domain->ipmb_mcs[i]);

	for (j=tab->size-1; j>=0; j--) {
	    ipmi_mc_t *mc = tab->mcs[j];
	    if (mc && !i_ipmi_mc_get(mc)) {
		ipmi_unlock(domain->mc_lock);
		handler(domain, mc, cb_data);
		i_ipmi_mc_put(mc);
		ipmi_lock(domain->mc_lock);
	    }
	}
    }
    for (i=MAX_CONS-1; i>=0; i--) {
	ipmi_mc_t *mc = domain->sys_intf_mcs[i];
	if (mc && !i_ipmi_mc_get(mc)) {
	    ipmi_unlock(domain->mc_lock);
	    handler(domain, mc, cb_data);
	    i_ipmi_mc_put(mc);
	    ipmi_lock(domain->mc_lock);
	}
    }
    ipmi_unlock(domain->mc_lock);
    return 0;
}

#if SAVE_SDR_CODE_ENABLE
typedef struct sdrs_saved_info_s
{
    ipmi_domain_t  *domain;
    ipmi_domain_cb done;
    void           *cb_data;
} sdrs_saved_info_t;

static void
sdrs_saved(ipmi_sdr_info_t *sdrs, int err, void *cb_data)
{
    sdrs_saved_info_t *info = cb_data;

    info->done(info->domain, err, info->cb_data);
    ipmi_mem_free(info);
}

int
ipmi_domain_store_entities(ipmi_domain_t  *domain,
			   ipmi_domain_cb done,
			   void           *cb_data)
{
    int               rv;
    ipmi_sdr_info_t   *stored_sdrs;
    sdrs_saved_info_t *info;

    /* FIXME - this is certainly broken. */

    CHECK_DOMAIN_LOCK(domain);

    info = ipmi_mem_alloc(sizeof(*info));
    if (!info)
	return ENOMEM;

    /* Create an SDR repository to store. */
    rv = ipmi_sdr_info_alloc(domain, NULL, 0, 0, &stored_sdrs);
    if (rv) {
	ipmi_mem_free(info);
	return rv;
    }

    /* Now store a channel SDR in case we are less than 1.5. */
    {
	ipmi_sdr_t sdr;
	int        i;
	
	sdr.major_version = 1;
	sdr.minor_version = 0;
	sdr.type = 0x14; /*  */
	sdr.length = 11;
	for (i=0; i<8; i++) {
	    /* FIXME - what about the LUN and transmit support? */
	    if (domain->chan[i].protocol) {
		sdr.data[i] = (domain->chan[i].protocol
			       | (domain->chan[i].xmit_support << 7)
			       | (domain->chan[i].recv_lun << 4));
	    } else {
		sdr.data[i] = 0;
	    }
	}
	sdr.data[8] = domain->msg_int_type;
	sdr.data[9] = domain->event_msg_int_type;
	sdr.data[10] = 0;

	rv = ipmi_sdr_add(stored_sdrs, &sdr);
	if (rv)
	    goto out_err;
    }

    rv = ipmi_entity_append_to_sdrs(domain->entities, stored_sdrs);
    if (rv)
	goto out_err;

    info->domain = domain;
    info->done = done;
    info->cb_data = cb_data;
    rv = ipmi_sdr_save(stored_sdrs, sdrs_saved, info);

 out_err:
    if (rv)
	ipmi_mem_free(info);
    ipmi_sdr_info_destroy(stored_sdrs, NULL, NULL);
    return rv;
}
#endif

/***********************************************************************
 *
 * ID handling
 *
 **********************************************************************/

ipmi_domain_id_t
ipmi_domain_convert_to_id(ipmi_domain_t *domain)
{
    ipmi_domain_id_t val;

    CHECK_DOMAIN_LOCK(domain);

    val.domain = domain;
    return val;
}

int
ipmi_domain_pointer_cb(ipmi_domain_id_t   id,
		       ipmi_domain_ptr_cb handler,
		       void               *cb_data)
{
    int           rv;
    ipmi_domain_t *domain;

    domain = id.domain;

    rv = i_ipmi_domain_get(domain);
    if (!rv) {
	handler(domain, cb_data);
	i_ipmi_domain_put(domain);
    }

    return rv;
}

int
ipmi_cmp_domain_id(ipmi_domain_id_t id1, ipmi_domain_id_t id2)
{
    if (id1.domain > id2.domain)
	return 1;
    if (id1.domain < id2.domain)
	return -1;
    return 0;
}

void
ipmi_domain_id_set_invalid(ipmi_domain_id_t *id)
{
    id->domain = NULL;
}

int
ipmi_domain_id_is_invalid(const ipmi_domain_id_t *id)
{
    return (id->domain == NULL);
}

/***********************************************************************
 *
 * Handle global OEM callbacks for new domain MCs.
 *
 **********************************************************************/

typedef struct mc_oem_handlers_s {
    unsigned int                 manufacturer_id;
    unsigned int                 first_product_id;
    unsigned int                 last_product_id;
    ipmi_oem_domain_match_handler_cb handler;
    ipmi_oem_domain_shutdown_handler_cb shutdown;
    void                         *cb_data;
} mc_oem_handlers_t;

static locked_list_t *mc_oem_handlers;

int
ipmi_domain_register_oem_handler(unsigned int                 manufacturer_id,
				 unsigned int                 product_id,
				 ipmi_oem_domain_match_handler_cb handler,
				 ipmi_oem_domain_shutdown_handler_cb shutdown,
				 void                         *cb_data)
{
    mc_oem_handlers_t *new_item;
    int               rv;

    /* This might be called before initialization, so be 100% sure. */
    rv = i_ipmi_domain_init();
    if (rv)
	return rv;

    new_item = ipmi_mem_alloc(sizeof(*new_item));
    if (!new_item)
	return ENOMEM;

    new_item->manufacturer_id = manufacturer_id;
    new_item->first_product_id = product_id;
    new_item->last_product_id = product_id;
    new_item->handler = handler;
    new_item->shutdown = shutdown;
    new_item->cb_data = cb_data;

    if (! locked_list_add(mc_oem_handlers, new_item, NULL)) {
	ipmi_mem_free(new_item);
	return ENOMEM;
    }

    return 0;
}

int
ipmi_domain_register_oem_handler_range(unsigned int           manufacturer_id,
				       unsigned int           first_product_id,
				       unsigned int           last_product_id,
				       ipmi_oem_domain_match_handler_cb handler,
				       ipmi_oem_domain_shutdown_handler_cb shutdown,
				       void                         *cb_data)
{
    mc_oem_handlers_t *new_item;
    int               rv;

    /* This might be called before initialization, so be 100% sure. */
    rv = i_ipmi_mc_init();
    if (rv)
	return rv;

    new_item = ipmi_mem_alloc(sizeof(*new_item));
    if (!new_item)
	return ENOMEM;

    new_item->manufacturer_id = manufacturer_id;
    new_item->first_product_id = first_product_id;
    new_item->last_product_id = last_product_id;
    new_item->handler = handler;
    new_item->shutdown = shutdown;
    new_item->cb_data = cb_data;

    if (! locked_list_add(mc_oem_handlers, new_item, NULL)) {
	ipmi_mem_free(new_item);
	return ENOMEM;
    }

    return 0;
}

typedef struct handler_cmp_s
{
    int           rv;
    unsigned int  manufacturer_id;
    unsigned int  first_product_id;
    unsigned int  last_product_id;
    ipmi_domain_t *domain;
} handler_cmp_t;

static int
mc_oem_handler_cmp_dereg(void *cb_data, void *item1, void *item2)
{
    mc_oem_handlers_t *hndlr = item1;
    handler_cmp_t     *cmp = cb_data;

    if ((hndlr->manufacturer_id == cmp->manufacturer_id)
	&& (hndlr->first_product_id <= cmp->first_product_id)
	&& (hndlr->last_product_id >= cmp->last_product_id))
    {
	cmp->rv = 0;
	locked_list_remove(mc_oem_handlers, item1, item2);
	ipmi_mem_free(hndlr);
	return LOCKED_LIST_ITER_STOP;
    }
    return LOCKED_LIST_ITER_CONTINUE;
}

int
ipmi_domain_deregister_oem_handler(unsigned int manufacturer_id,
				   unsigned int product_id)
{
    handler_cmp_t  tmp;

    tmp.rv = ENOENT;
    tmp.manufacturer_id = manufacturer_id;
    tmp.first_product_id = product_id;
    tmp.last_product_id = product_id;
    locked_list_iterate(mc_oem_handlers, mc_oem_handler_cmp_dereg, &tmp);
    return tmp.rv;
}

int
ipmi_domain_deregister_oem_handler_range(unsigned int manufacturer_id,
					 unsigned int first_product_id,
					 unsigned int last_product_id)
{
    handler_cmp_t  tmp;

    tmp.rv = ENOENT;
    tmp.manufacturer_id = manufacturer_id;
    tmp.first_product_id = first_product_id;
    tmp.last_product_id = last_product_id;
    locked_list_iterate(mc_oem_handlers, mc_oem_handler_cmp_dereg, &tmp);
    return tmp.rv;
}

static int
mc_oem_handler_call(void *cb_data, void *item1, void *item2)
{
    mc_oem_handlers_t *hndlr = item1;
    handler_cmp_t     *cmp = cb_data;

    if ((hndlr->manufacturer_id == cmp->manufacturer_id)
	&& (hndlr->first_product_id <= cmp->first_product_id)
	&& (hndlr->last_product_id >= cmp->last_product_id))
    {
	cmp->rv = hndlr->handler(cmp->domain, hndlr->cb_data);
	return LOCKED_LIST_ITER_STOP;
    }
    return LOCKED_LIST_ITER_CONTINUE;
}

static int
check_mc_oem_handlers(ipmi_domain_t *domain)
{
    handler_cmp_t  tmp;

    tmp.rv = 0;
    tmp.manufacturer_id = ipmi_mc_manufacturer_id(domain->si_mc);
    tmp.first_product_id = ipmi_mc_product_id(domain->si_mc);
    tmp.last_product_id = tmp.first_product_id;
    tmp.domain = domain;
    locked_list_iterate(mc_oem_handlers, mc_oem_handler_call, &tmp);
    return tmp.rv;
}

int
ipmi_domain_set_sdrs_fixup_handler(ipmi_domain_t                 *domain,
				   ipmi_domain_oem_fixup_sdrs_cb handler,
				   void                          *cb_data)
{
    CHECK_DOMAIN_LOCK(domain);
    domain->fixup_sdrs_handler = handler;
    domain->fixup_sdrs_cb_data = cb_data;
    return 0;
}


/***********************************************************************
 *
 * Connection setup and handling
 *
 **********************************************************************/

int
ipmi_domain_set_main_SDRs_read_handler(ipmi_domain_t  *domain,
				       ipmi_domain_cb handler,
				       void           *cb_data)
{
    CHECK_DOMAIN_LOCK(domain);

    ipmi_lock(domain->domain_lock);
    domain->SDRs_read_handler = handler;
    domain->SDRs_read_handler_cb_data = cb_data;
    ipmi_unlock(domain->domain_lock);
    return 0;
}

int
ipmi_domain_set_con_up_handler(ipmi_domain_t      *domain,
			       ipmi_domain_ptr_cb handler,
			       void               *cb_data)
{
    CHECK_DOMAIN_LOCK(domain);

    ipmi_lock(domain->domain_lock);
    domain->con_up_handler = handler;
    domain->con_up_handler_cb_data = cb_data;
    ipmi_unlock(domain->domain_lock);
    return 0;
}

int
ipmi_domain_set_bus_scan_handler(ipmi_domain_t  *domain,
				 ipmi_domain_cb handler,
				 void           *cb_data)
{
    CHECK_DOMAIN_LOCK(domain);

    ipmi_lock(domain->mc_lock);
    domain->bus_scan_handler = handler;
    domain->bus_scan_handler_cb_data = cb_data;
    ipmi_unlock(domain->mc_lock);
    return 0;
}

static void
conn_close(ipmi_con_t *ipmi, void *cb_data)
{
    ipmi_domain_close_done_cb close_done;
    void                      *my_cb_data;
    ipmi_domain_t             *domain = cb_data;
    int                       done = 0;

    ipmi_lock(domain->domain_lock);
    domain->close_count--;
    done = domain->close_count <= 0;
    ipmi_unlock(domain->domain_lock);

    if (!done)
	return;

    remove_known_domain(domain);

    close_done = domain->close_done;
    my_cb_data = domain->close_done_cb_data;

    cleanup_domain(domain);

    if (close_done)
	close_done(my_cb_data);
}

static void
real_close_connection(ipmi_domain_t *domain)
{
    ipmi_con_t                *ipmi[MAX_CONS];
    int                       i;

    for (i=0; i<MAX_CONS; i++) {
	ipmi[i] = domain->conn[i];

	if (!ipmi[i])
	    continue;

	/* Remove all the handlers. */
	domain->conn[i]->remove_event_handler(domain->conn[i],
					      ll_event_handler,
					      domain);
	domain->conn[i]->remove_con_change_handler(domain->conn[i],
						   ll_con_changed,
						   domain);
	domain->conn[i]->remove_ipmb_addr_handler(domain->conn[i],
						  ll_addr_changed,
						  domain);
	domain->conn[i] = NULL;
    }

    /* No lock needed here, this is single threaded until we start
       actually closing the connections. */
    domain->close_count = 0;
    for (i=0; i<MAX_CONS; i++) {
	if (ipmi[i])
	    domain->close_count++;
    }
    for (i=0; i<MAX_CONS; i++) {
	if (ipmi[i]) {
	    if (ipmi[i]->register_stat_handler)
		ipmi[i]->unregister_stat_handler(ipmi[i],
						 domain->con_stat_info);
	    ipmi[i]->close_connection_done(ipmi[i], conn_close, domain);
	}
    }
}

int
ipmi_domain_close(ipmi_domain_t             *domain,
		  ipmi_domain_close_done_cb close_done,
		  void                      *cb_data)
{
    CHECK_DOMAIN_LOCK(domain);

    if (domain->in_shutdown)
	return EINVAL;

    domain->in_shutdown = 1;
    domain->close_done = close_done;
    domain->close_done_cb_data = cb_data;

    locked_list_remove(domains_list, domain, NULL);

    /* We don't actually do the destroy here, since the domain should
       be in use.  We wait until the usecount goes to zero. */

    return 0;
}

int
ipmi_domain_add_connect_change_handler(ipmi_domain_t      *domain,
				       ipmi_domain_con_cb handler,
				       void               *cb_data)
{
    if (locked_list_add(domain->con_change_handlers, handler, cb_data))
	return 0;
    else
	return ENOMEM;
}

int
ipmi_domain_remove_connect_change_handler(ipmi_domain_t      *domain,
					  ipmi_domain_con_cb handler,
					  void               *cb_data)
{
    if (locked_list_remove(domain->con_change_handlers, handler, cb_data))
	return 0;
    else
	return EINVAL;
}

typedef struct con_change_cl_info_s
{
    ipmi_domain_con_cb handler;
    void               *handler_data;
} con_change_cl_info_t;

static int
iterate_con_change_cl(void *cb_data, void *item1, void *item2)
{
    con_change_cl_info_t  *info = cb_data;
    ipmi_domain_con_cl_cb handler = item1;

    handler(info->handler, info->handler_data, item2);
    return LOCKED_LIST_ITER_CONTINUE;
}

static void
call_con_change_cl_handlers(ipmi_domain_t      *domain,
			    ipmi_domain_con_cb handler,
			    void               *handler_data)
{
    con_change_cl_info_t info;

    info.handler = handler;
    info.handler_data = handler_data;
    locked_list_iterate(domain->con_change_cl_handlers, iterate_con_change_cl,
			&info);
}

int
ipmi_domain_add_connect_change_handler_cl(ipmi_domain_t         *domain,
					  ipmi_domain_con_cl_cb handler,
					  void                  *cb_data)
{
    if (locked_list_add(domain->con_change_cl_handlers, handler, cb_data))
	return 0;
    else
	return ENOMEM;
}

int
ipmi_domain_remove_connect_change_handler_cl(ipmi_domain_t         *domain,
					     ipmi_domain_con_cl_cb handler,
					     void                  *cb_data)
{
    if (locked_list_remove(domain->con_change_cl_handlers, handler, cb_data))
	return 0;
    else
	return EINVAL;
}

typedef struct con_change_info_s
{
    ipmi_domain_t *domain;
    int           err;
    unsigned int  conn_num;
    unsigned int  port_num;
    int           still_connected;
} con_change_info_t;

static int
iterate_con_changes(void *cb_data, void *item1, void *item2)
{
    con_change_info_t  *info = cb_data;
    ipmi_domain_con_cb handler = item1;

    handler(info->domain, info->err, info->conn_num, info->port_num,
	    info->still_connected, item2);
    return LOCKED_LIST_ITER_CONTINUE;
}

static void
call_con_change(ipmi_domain_t *domain,
		int           err,
		unsigned int  conn_num,
		unsigned int  port_num,
		int           still_connected)
{
    con_change_info_t info = {domain, err, conn_num, port_num,
			      still_connected};
    locked_list_iterate(domain->con_change_handlers, iterate_con_changes,
			&info);
}

static void
call_con_fails(ipmi_domain_t *domain,
	       int           err,
	       unsigned int  conn_num,
	       unsigned int  port_num,
	       int           still_connected)
{
    ipmi_lock(domain->con_lock);
    domain->connecting = 0;
    if (err) {
	/* Nothing really to do, can't start anything up, just report it. */
	ipmi_unlock(domain->con_lock);
    } else if (domain->in_startup) {
	domain->in_startup = 0;
	ipmi_unlock(domain->con_lock);
    } else
	ipmi_unlock(domain->con_lock);

    call_con_change(domain, err, conn_num, port_num, still_connected);
}

static void	
con_up_complete(ipmi_domain_t *domain)
{
    int                i, j;
    ipmi_domain_ptr_cb con_up_handler;
    void               *con_up_handler_cb_data;
    ipmi_domain_cb     SDRs_read_handler;
    void               *SDRs_read_handler_cb_data;

    if (domain->in_shutdown)
	return;

    /* This is an unusual looking piece of code, but is required for
       systems that do not implement the get channel command.  For
       those, it is required that channel 0 be an IPMB channel.
       Basically, if all of the channel info commands failed, set
       channel 0 to IPMB. */
    for (i=0; i<MAX_IPMI_USED_CHANNELS; i++) {
	if (domain->chan_set[i])
	    break;
    }
    if (i == MAX_IPMI_USED_CHANNELS) {
	domain->chan[0].medium = IPMI_CHANNEL_MEDIUM_IPMB;
	domain->chan[0].xmit_support = 1;
	domain->chan[0].recv_lun = 0;
	domain->chan[0].protocol = IPMI_CHANNEL_PROTOCOL_IPMB;
	domain->chan[0].session_support = IPMI_CHANNEL_SESSION_LESS;
	domain->chan[0].vendor_id = IPMI_ENTERPRISE_NUMBER;
	domain->chan[0].aux_info = 0;
    }

    domain->connection_up = 1;

    if (domain->working_conn != -1)
	domain->con_up[domain->working_conn] = 1;

    for (i=0; i<MAX_CONS; i++) {
	for (j=0; j<MAX_PORTS_PER_CON; j++) {
	    if (domain->port_up[j][i] == 1)
		call_con_fails(domain, 0, i, j, 1);
	}
    }

    ipmi_lock(domain->domain_lock);
    con_up_handler = domain->con_up_handler;
    con_up_handler_cb_data = domain->con_up_handler_cb_data;
    ipmi_unlock(domain->domain_lock);
    if (con_up_handler)
	con_up_handler(domain, con_up_handler_cb_data);

    ipmi_domain_start_full_ipmb_scan(domain);

    ipmi_detect_ents_presence_changes(domain->entities, 1);

    ipmi_entity_scan_sdrs(domain, NULL, domain->entities, domain->main_sdrs);
    ipmi_sensor_handle_sdrs(domain, NULL, domain->main_sdrs);
    ipmi_lock(domain->domain_lock);
    SDRs_read_handler = domain->SDRs_read_handler;
    SDRs_read_handler_cb_data = domain->SDRs_read_handler_cb_data;
    ipmi_unlock(domain->domain_lock);
    if (SDRs_read_handler)
	SDRs_read_handler(domain, 0, SDRs_read_handler_cb_data);
    i_ipmi_entities_report_sdrs_read(domain->entities);
    i_ipmi_put_domain_fully_up(domain, "con_up_complete");
}

static void
chan_info_rsp_handler(ipmi_mc_t  *mc,
		      ipmi_msg_t *rsp,
		      void       *rsp_data)
{
    int           rv = 0;
    long          curr = (long) rsp_data;
    ipmi_domain_t *domain;

    if (!mc)
	return;

    domain = ipmi_mc_get_domain(mc);

    if (rsp->data[0] != 0) {
	rv = IPMI_IPMI_ERR_VAL(rsp->data[0]);
    } else if (rsp->data_len < 8) {
	rv = EINVAL;
    }

    if (rv) {
	/* Got an error, invalidate the channel.  IPMI requires that
	   1.5 systems implement this command if they have
	   channels. */
	memset(&domain->chan[curr], 0, sizeof(domain->chan[curr]));
	/* Keep going, there may be more channels. */
    } else {
	domain->chan_set[curr] = 1;

        /* Get the info from the channel info response. */
        domain->chan[curr].medium = rsp->data[2] & 0x7f;
	domain->chan[curr].xmit_support = rsp->data[2] >> 7;
	domain->chan[curr].recv_lun = (rsp->data[2] >> 4) & 0x7;
	domain->chan[curr].protocol = rsp->data[3] & 0x1f;
	domain->chan[curr].session_support = rsp->data[4] >> 6;
	domain->chan[curr].vendor_id = (rsp->data[5]
					| (rsp->data[6] << 8)
					| (rsp->data[7] << 16));
	domain->chan[curr].aux_info = rsp->data[8] | (rsp->data[9] << 8);
    }

    curr++;
    if (curr < MAX_IPMI_USED_CHANNELS) {
	ipmi_msg_t    cmd_msg;
	unsigned char cmd_data[1];

	cmd_msg.netfn = IPMI_APP_NETFN;
	cmd_msg.cmd = IPMI_GET_CHANNEL_INFO_CMD;
	cmd_msg.data = cmd_data;
	cmd_msg.data_len = 1;
	cmd_data[0] = curr;

	rv = ipmi_mc_send_command(mc, 0, &cmd_msg, chan_info_rsp_handler,
				  (void *) curr);
    } else {
	goto chan_info_done;
    }

    if (rv) {
	call_con_fails(domain, rv, 0, 0, 0);
	return;
    }

    return;

 chan_info_done:
    domain->msg_int_type = 0xff;
    domain->event_msg_int_type = 0xff;
    con_up_complete(domain);
}

static int 
get_channels(ipmi_domain_t *domain)
{
    int rv;

    if (domain->in_shutdown)
	return ECANCELED;

    if ((domain->major_version > 1)
	|| ((domain->major_version == 1) && (domain->minor_version >= 5)))
    {
	ipmi_msg_t    cmd_msg;
	unsigned char cmd_data[1];

	/* IPMI 1.5 or later, use a get channel command. */
	cmd_msg.netfn = IPMI_APP_NETFN;
	cmd_msg.cmd = IPMI_GET_CHANNEL_INFO_CMD;
	cmd_msg.data = cmd_data;
	cmd_msg.data_len = 1;
	cmd_data[0] = 0;

	i_ipmi_mc_get(domain->si_mc);
	rv = ipmi_mc_send_command(domain->si_mc, 0, &cmd_msg,
				  chan_info_rsp_handler, (void *) 0);
	i_ipmi_mc_put(domain->si_mc);
    } else {
	ipmi_sdr_t sdr;

	/* Get the channel info record. */
	rv = ipmi_get_sdr_by_type(domain->main_sdrs, 0x14, &sdr);
	if (rv) {
	    domain->chan_set[0] = 1;

	    /* Add a dummy channel zero and finish. */
	    domain->chan[0].medium = IPMI_CHANNEL_MEDIUM_IPMB;
	    domain->chan[0].xmit_support = 1;
	    domain->chan[0].recv_lun = 0;
	    domain->chan[0].protocol = IPMI_CHANNEL_PROTOCOL_IPMB;
	    domain->chan[0].session_support = IPMI_CHANNEL_SESSION_LESS;
	    domain->chan[0].vendor_id = IPMI_ENTERPRISE_NUMBER;
	    domain->chan[0].aux_info = 0;
	    domain->msg_int_type = 0xff;
	    domain->event_msg_int_type = 0xff;
	    domain->msg_int_type = 0xff;
	    domain->event_msg_int_type = 0xff;
	    rv = 0;
	} else {
	    int i;

	    for (i=0; i<MAX_IPMI_USED_CHANNELS; i++) {
		int protocol = sdr.data[i] & 0xf;
		
		if (protocol != 0) {
		    domain->chan_set[i] = 1;

		    domain->chan[i].medium = IPMI_CHANNEL_MEDIUM_IPMB;
		    domain->chan[i].xmit_support = 1;
		    domain->chan[i].recv_lun = 0;
		    domain->chan[i].protocol = protocol;
		    domain->chan[i].session_support= IPMI_CHANNEL_SESSION_LESS;
		    domain->chan[i].vendor_id = IPMI_ENTERPRISE_NUMBER;
		    domain->chan[i].aux_info = 0;
		}
	    }
	    domain->msg_int_type = sdr.data[8];
	    domain->event_msg_int_type = sdr.data[9];
	}

	con_up_complete(domain);
    }

    return rv;
}

static void
sdr_handler(ipmi_sdr_info_t *sdrs,
	    int             err,
	    int             changed,
	    unsigned int    count,
	    void            *cb_data)
{
    ipmi_domain_t *domain = cb_data;
    int           rv;

    if (err) {
	/* Just report an error, it shouldn't be a big deal if this
           fails. */
	ipmi_log(IPMI_LOG_WARNING,
		 "%sdomain.c(sdr_handler): "
		 "Could not get main SDRs, error 0x%x",
		 DOMAIN_NAME(domain), err);
    }

    if (domain->fixup_sdrs_handler)
	domain->fixup_sdrs_handler(domain, domain->main_sdrs,
				   domain->fixup_sdrs_cb_data);

    rv = get_channels(domain);
    if (rv)
	call_con_fails(domain, rv, 0, 0, 0);
}

static void
got_guid(ipmi_mc_t  *mc,
	 ipmi_msg_t *rsp,
	 void       *rsp_data)
{
    ipmi_domain_t *domain = rsp_data;
    int           rv;

    if (!mc)
	return; /* domain went away while processing. */

    if ((rsp->data[0] == 0) && (rsp->data_len >= 17)) {
	/* We have a GUID, save it */
	ipmi_mc_set_guid(mc, rsp->data+1);
    }

    if (domain->SDR_repository_support && ipmi_option_SDRs(domain)) {
	rv = ipmi_sdr_fetch(domain->main_sdrs, sdr_handler, domain);
    } else {
	rv = get_channels(domain);
    }
    if (rv)
	call_con_fails(domain, rv, 0, 0, 0);
}

static void
domain_oem_handlers_checked(ipmi_domain_t *domain, int err, void *cb_data)
{
    ipmi_msg_t msg;
    int        rv;

    /* FIXME - handle errors setting up OEM comain information. */

    msg.netfn = IPMI_APP_NETFN;
    msg.cmd = IPMI_GET_SYSTEM_GUID_CMD;
    msg.data_len = 0;
    msg.data = NULL;

    i_ipmi_mc_get(domain->si_mc);
    rv = ipmi_mc_send_command(domain->si_mc, 0, &msg, got_guid, domain);
    i_ipmi_mc_put(domain->si_mc);
    if (rv)
	call_con_fails(domain, rv, 0, 0, 0);
}

static void
got_dev_id(ipmi_mc_t  *mc,
	   ipmi_msg_t *rsp,
	   void       *rsp_data)
{
    ipmi_domain_t *domain = rsp_data;
    int           rv;

    if (!mc)
	return; /* domain went away while processing. */

    rv = i_ipmi_mc_get_device_id_data_from_rsp(mc, rsp);
    if (rv) {
	/* At least the get device id has to work. */
	if ((rsp->data[0] == 0) && (rsp->data_len >= 6)) {
	    int major_version = rsp->data[5] & 0xf;
	    int minor_version = (rsp->data[5] >> 4) & 0xf;

	    if (major_version < 1) {
		ipmi_log(IPMI_LOG_ERR_INFO,
			 "%sdomain.c(got_dev_id): "
			 "IPMI version of the BMC is %d.%d, which is older"
			 " than OpenIPMI supports",
			 DOMAIN_NAME(domain), major_version, minor_version);
		domain->got_invalid_dev_id = 1;
		call_con_fails(domain, ENOSYS, 0, 0, 0);
		return;
	    }
	}
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "%sdomain.c(got_dev_id): "
		 "Invalid return from IPMI Get Device ID, something is"
		 " seriously wrong with the BMC",
		 DOMAIN_NAME(domain));
	domain->got_invalid_dev_id = 1;
	call_con_fails(domain, rv, 0, 0, 0);
	return;
    }

    domain->got_invalid_dev_id = 0;

    /* Get the information from the MC, not the message, since it may have
       been fixed up. */
    domain->major_version = ipmi_mc_major_version(mc);
    domain->minor_version = ipmi_mc_minor_version(mc);
    domain->SDR_repository_support = ipmi_mc_sdr_repository_support(mc);

    if (((domain->major_version < 1) || (domain->major_version > 2))
	|| ((domain->major_version == 1)
	    && (domain->minor_version != 5)
	    && (domain->minor_version != 0))
	|| ((domain->major_version == 2)
	    && (domain->minor_version != 0)))
    {
	ipmi_log(IPMI_LOG_WARNING,
		 "%sdomain.c(got_dev_id): "
		 "IPMI version of the BMC is %d.%d, which is not directly"
		 " supported by OpenIPMI.  It may work, but there may be"
		 " issues.",
		 DOMAIN_NAME(domain),
		 domain->major_version, domain->minor_version);
    }

    if (domain->major_version < 1) {
	/* We only support 1.0 and greater. */
	domain->got_invalid_dev_id = 0;
	call_con_fails(domain, EINVAL, 0, 0, 0);
	return;
    }

    if (ipmi_option_OEM_init(domain)) {
	rv = check_oem_handlers(domain, domain_oem_handlers_checked, NULL);
	if (rv)
	    call_con_fails(domain, rv, 0, 0, 0);
	rv = check_mc_oem_handlers(domain);
	if (rv)
	    call_con_fails(domain, rv, 0, 0, 0);
    } else {
	domain_oem_handlers_checked(domain, 0, NULL);
    }
}

static int
domain_send_mc_id(ipmi_domain_t *domain)
{
    ipmi_msg_t msg;
    int        rv;

    msg.netfn = IPMI_APP_NETFN;
    msg.cmd = IPMI_GET_DEVICE_ID_CMD;
    msg.data_len = 0;
    msg.data = NULL;

    i_ipmi_mc_get(domain->si_mc);
    rv = ipmi_mc_send_command(domain->si_mc, 0, &msg, got_dev_id, domain);
    i_ipmi_mc_put(domain->si_mc);

    return rv;
}

static int
start_con_up(ipmi_domain_t *domain)
{
    ipmi_lock(domain->con_lock);
    if (domain->connecting || domain->connection_up) {
	ipmi_unlock(domain->con_lock);
	return 0;
    }
    domain->connecting = 1;
    ipmi_unlock(domain->con_lock);

    return domain_send_mc_id(domain);
}

static void start_activate_timer(ipmi_domain_t *domain);

static void
initial_ipmb_addr_cb(ipmi_con_t    *ipmi,
		     int           err,
		     const unsigned char ipmb_addr[],
		     unsigned int  num_ipmb_addr,
		     int           active,
		     unsigned int  hacks,
		     void          *cb_data)
{
    ipmi_domain_t *domain = cb_data;
    int           u;
    int           rv;

    rv = i_ipmi_domain_get(domain);
    if (rv)
	/* So the connection failed.  So what, there's nothing to talk to. */
	return;

    u = get_con_num(domain, ipmi);
    if (u == -1)
	goto out_unlock;

    if (err) {
	call_con_fails(domain, err, u, 0, domain->connection_up);
	goto out_unlock;
    }

    /* If we are not activating connections, just use whatever we get
       and don't worry if it is active or not. */
    if (! domain->option_activate_if_possible)
	active = 1;

    if (active) {
        domain->working_conn = u;
	rv = start_con_up(domain);
	if (rv)
	    call_con_fails(domain, rv, u, 0, domain->connection_up);
    } else {
	/* Start the timer to activate the connection, if necessary. */
	start_activate_timer(domain);
    }

 out_unlock:
    i_ipmi_domain_put(domain);
}

static void
activate_timer_cb(void *cb_data, os_hnd_timer_id_t *id)
{
    activate_timer_info_t *info = cb_data;
    ipmi_domain_t         *domain = info->domain;
    int                   to_activate;
    int                   u;
    int                   rv;

    ipmi_lock(info->lock);
    if (info->cancelled) {
	info->os_hnd->free_timer(info->os_hnd, id);
	ipmi_unlock(info->lock);
	ipmi_destroy_lock(info->lock);
	ipmi_mem_free(info);
	return;
    }
    info->running = 0;

    rv = i_ipmi_domain_get(domain);
    if (rv)
	/* Domain is gone, just give up. */
	goto out_unlock;

    /* If no one is active, activate one. */
    to_activate = -1;
    for (u=0; u<MAX_CONS; u++) {
	if (!domain->conn[u]
	    || !domain->con_up[u])
	{
	    continue;
	}
	if (domain->con_active[u]) {
	    to_activate = u;
	    break;
	}
	to_activate = u;
    }
    u = to_activate;
    if ((u != -1)
	&& domain->option_activate_if_possible
	&& ! domain->con_active[u]
	&& domain->conn[u]->set_active_state)
    {
	/* If we didn't find an active connection, but we found a
	   working one, activate it.  Note that we may re-activate
	   the connection that just went inactive if it is the
	   only working connection. */
	domain->conn[u]->set_active_state(
	    domain->conn[u],
	    1,
	    ll_addr_changed,
	    domain);
    }

    i_ipmi_domain_put(domain);

 out_unlock:
    ipmi_unlock(info->lock);
}

int
ipmi_domain_activate_connection(ipmi_domain_t *domain, unsigned int connection)
{
    CHECK_DOMAIN_LOCK(domain);

    if ((connection >= MAX_CONS) || !domain->conn[connection])
	return EINVAL;

    if (!domain->conn[connection]->set_active_state
	|| !domain->option_activate_if_possible)
	return ENOSYS;

    domain->conn[connection]->set_active_state(domain->conn[connection], 1, 
					       ll_addr_changed, domain);

    /* The other connections will be deactivated when this one
       activates, if that is required. */
    return 0;
}

int
ipmi_domain_is_connection_active(ipmi_domain_t *domain,
				 unsigned int  connection,
				 unsigned int  *active)
{
    CHECK_DOMAIN_LOCK(domain);

    if ((connection >= MAX_CONS) || !domain->conn[connection])
	return EINVAL;

    *active = domain->con_active[connection];
    return 0;
}

int
ipmi_domain_is_connection_up(ipmi_domain_t *domain,
			     unsigned int  connection,
			     unsigned int  *up)
{
    int          port;
    unsigned int val;

    CHECK_DOMAIN_LOCK(domain);

    if ((connection >= MAX_CONS) || !domain->conn[connection])
	return EINVAL;

    val = 0;
    for (port=0; port<MAX_PORTS_PER_CON; port++) {
	if (domain->port_up[port][connection] == 1)
	    val = 1;
    }

    *up = val;
    return 0;
}

int
ipmi_domain_num_connection_ports(ipmi_domain_t *domain,
				 unsigned int  connection,
				 unsigned int  *ports)
{
    int          port;
    unsigned int val = 0;

    CHECK_DOMAIN_LOCK(domain);

    if ((connection >= MAX_CONS) || !domain->conn[connection])
	return EINVAL;

    for (port=0; port<MAX_PORTS_PER_CON; port++) {
	if (domain->port_up[port][connection] != -1)
	    val = port+1;
    }

    *ports = val;
    return 0;
}

int
ipmi_domain_is_connection_port_up(ipmi_domain_t *domain,
				  unsigned int  connection,
				  unsigned int  port,
				  unsigned int  *up)
{
    CHECK_DOMAIN_LOCK(domain);

    if ((connection >= MAX_CONS) || !domain->conn[connection])
	return EINVAL;

    if (port >= MAX_PORTS_PER_CON)
	return EINVAL;

    if (domain->port_up[port][connection] == -1)
	return ENOSYS;

    *up = domain->port_up[port][connection];

    return 0;
}

int
ipmi_domain_get_port_info(ipmi_domain_t *domain,
			  unsigned int  connection,
			  unsigned int  port,
			  char          *info,
			  int           *info_len)
{
    CHECK_DOMAIN_LOCK(domain);

    if ((connection >= MAX_CONS) || !domain->conn[connection])
	return EINVAL;

    if (port >= MAX_PORTS_PER_CON)
	return EINVAL;

    if (!domain->conn[connection]->get_port_info)
	return ENOSYS;

    return domain->conn[connection]->get_port_info(domain->conn[connection],
						   port, info, info_len);
}

int
i_ipmi_domain_get_connection(ipmi_domain_t *domain,
			     int           con_num,
			     ipmi_con_t    **con)
{
    if (con_num >= MAX_CONS)
	return EINVAL;
    *con = domain->conn[con_num];
    return 0;
}

void
ipmi_domain_iterate_connections(ipmi_domain_t          *domain,
				ipmi_connection_ptr_cb handler,
				void                   *cb_data)
{
    int i;

    CHECK_DOMAIN_LOCK(domain);

    for (i=0; i<MAX_CONS; i++) {
	if (domain->conn[i])
	    handler(domain, i, cb_data);
    }
}

ipmi_args_t *
ipmi_domain_get_connection_args(ipmi_domain_t *domain,
				unsigned int  con)
{
    CHECK_DOMAIN_LOCK(domain);

    if (con >= MAX_CONS)
	return NULL;

    if (!domain->conn[con])
	return NULL;

    if (! domain->conn[con]->get_startup_args)
	return NULL;

    return domain->conn[con]->get_startup_args(domain->conn[con]);
}

char *
ipmi_domain_get_connection_type(ipmi_domain_t *domain,
				unsigned int  connection)
{
    CHECK_DOMAIN_LOCK(domain);

    if (connection >= MAX_CONS)
	return NULL;

    if (!domain->conn[connection])
	return NULL;

    return domain->conn[connection]->con_type;
}

ipmi_con_t *
ipmi_domain_get_connection(ipmi_domain_t *domain,
			   unsigned int  connection)
{
    CHECK_DOMAIN_LOCK(domain);

    if (connection >= MAX_CONS)
	return NULL;

    if (!domain->conn[connection])
	return NULL;

    if (! domain->conn[connection]->use_connection)
	return NULL;

    domain->conn[connection]->use_connection(domain->conn[connection]);
    return domain->conn[connection];
}

/* If the activate timer is not running, then start it.  This
   allows some time for other connections to become active before
   we go off and start activating things.  We wait a random amount
   of time so that if we get into a war with another program about
   who is active, someone will eventually win. */
static void
start_activate_timer(ipmi_domain_t *domain)
{
    ipmi_lock(domain->activate_timer_info->lock);
    if (!domain->activate_timer_info->running) {
	struct timeval tv;
	domain->os_hnd->get_random(domain->os_hnd,
				   &tv.tv_sec,
				   sizeof(tv.tv_sec));
	/* Wait a random value between 5 and 15 seconds */
	tv.tv_sec = (tv.tv_sec % 10) + 5;
	tv.tv_usec = 0;
	domain->os_hnd->start_timer(domain->os_hnd,
				    domain->activate_timer,
				    &tv,
				    activate_timer_cb,
				    domain->activate_timer_info);
	domain->activate_timer_info->running = 1;
    }
    ipmi_unlock(domain->activate_timer_info->lock);
}

static void
ll_addr_changed(ipmi_con_t    *ipmi,
		int           err,
		const unsigned char ipmb_addr[],
		unsigned int  num_ipmb_addr,
		int           active,
		unsigned int  hacks,
		void          *cb_data)
{
    ipmi_domain_t *domain = cb_data;
    int           rv;
    int           u;
    int           start_connection;
    unsigned char old_addr[MAX_IPMI_USED_CHANNELS];
    unsigned int  i;

    rv = i_ipmi_domain_get(domain);
    if (rv)
	/* So the connection failed.  So what, there's nothing to talk to. */
	return;

    if (err)
	goto out_unlock;

    u = get_con_num(domain, ipmi);
    if (u == -1)
	goto out_unlock;

    memcpy(old_addr, domain->con_ipmb_addr[u], sizeof(old_addr));

    for (i=0; i<num_ipmb_addr && i<MAX_IPMI_USED_CHANNELS; i++) {
	if (! ipmb_addr[i])
	    continue;
	domain->con_ipmb_addr[u][i] = ipmb_addr[i];
    }

    if (!domain->in_startup) {
	/* Only scan the IPMBs if we are not in startup.  Otherwise things
	   get reported before we are ready. */
	for (i=0; i<num_ipmb_addr && i<MAX_IPMI_USED_CHANNELS; i++) {
	    if (! ipmb_addr[i])
		continue;
	    if (ipmb_addr[i] != old_addr[i]) {
		/* First scan the old address to remove it. */
		if (domain->con_ipmb_addr[u] != 0)
		    ipmi_start_ipmb_mc_scan(domain, i,
					    old_addr[i], old_addr[i],
					    NULL, NULL);
	    }

	    /* Scan the new address.  Even though the address may not have
	       changed, it may have changed modes and need to be rescanned. */
	    ipmi_start_ipmb_mc_scan(domain, i, ipmb_addr[i], ipmb_addr[i],
				    NULL, NULL);
	}
    }

    /* If we are not activating connections, just use whatever we get
       and don't worry if it is active or not. */
    if (! domain->option_activate_if_possible)
	active = 1;

    start_connection = (active && (first_active_con(domain) == -1));

    if (domain->con_active[u] != active) {
	domain->con_active[u] = active;
	if (active) {
	    /* Deactivate all the other connections, if they support
	       it. */
	    for (u=0; u<MAX_CONS; u++) {
		if (u == domain->working_conn
		    || !domain->conn[u]
		    || !domain->con_up[u])
		{
		    continue;
		}

		if (domain->conn[u]->set_active_state
		    && domain->option_activate_if_possible)
		{
		    domain->conn[u]->set_active_state(
			domain->conn[u],
			0,
			ll_addr_changed,
			domain);
		}
	    }
	} else {
	    /* The connection went inactive, route message from it to
	       the current working connection. */
	    reroute_cmds(domain, u, domain->working_conn);
	}
    } else if (active) {
        /* Always pick the last working active connection to use. */
	domain->working_conn = u;
    } else if (domain->conn[u]->set_active_state
	       && domain->option_activate_if_possible)
    {
        /* Start the timer to activate the connection, if necessary. */
	start_activate_timer(domain);
    }

    if (start_connection) {
	/* We now have an active connection and we didn't before,
           attempt to start up the connection. */
	rv = start_con_up(domain);
	if (rv)
	    call_con_fails(domain, rv, u, 0, domain->connection_up);
    }

 out_unlock:
    i_ipmi_domain_put(domain);
}

static void
ll_con_changed(ipmi_con_t   *ipmi,
	       int          err,
	       unsigned int port_num,
	       int          still_connected,
	       void         *cb_data)
{
    ipmi_domain_t   *domain = cb_data;
    int             rv;
    int             u;

    if (port_num >= MAX_PORTS_PER_CON) {
	ipmi_log(IPMI_LOG_SEVERE,
		 "%sdomain.c(ll_con_changed): Got port number %d,"
		 " but %d is the max number of ports",
		 DOMAIN_NAME(domain), port_num, MAX_PORTS_PER_CON);
	return;
    }

    rv = i_ipmi_domain_get(domain);
    if (rv)
	/* So the connection failed.  So what, there's nothing to talk to. */
	return;

    u = get_con_num(domain, ipmi);
    if (u == -1)
	goto out_unlock;

    if (err == ENOENT)
	domain->port_up[port_num][u] = -1;
    else if (err)
	domain->port_up[port_num][u] = 0;
    else
	domain->port_up[port_num][u] = 1;

    /* If we are not starting up, if we gain or lose a connection
       then scan the address. */
    if ((!domain->in_startup) && (ipmi->scan_sysaddr))
    	ipmi_start_si_scan(domain, u, NULL, NULL);

    if (still_connected) {
	domain->con_up[u] = 1;
	if (domain->connecting) {
	    /* If we are connecting, don't report it, it will be
	       reported when the connection is finished. */
	} else if (domain->connection_up) {
	    /* We already have a connection, just report this. */
	    call_con_change(domain, err, u, port_num, domain->connection_up);
	} else {
	    /* We don't have a working connection, so start up the
               process. */
	    domain->working_conn = u;

	    if (domain->conn[u]->get_ipmb_addr)
		/* If we can fetch the IPMB address, see if this is an
                   active connection first. */
		rv = domain->conn[u]->get_ipmb_addr(domain->conn[u],
						    initial_ipmb_addr_cb,
						    domain);
	    else
		/* When a connection comes back up, start the process of
		   getting SDRs, scanning the bus, and the like. */
		rv = start_con_up(domain);

	    if (rv)
		call_con_fails(domain, rv, u, port_num, domain->connection_up);
	}
    } else {
	/* A connection failed, try to find a working connection and
           activate it, if necessary. */
	domain->con_up[u] = 0;
	domain->working_conn = first_working_con(domain);
	if (domain->working_conn == -1)
	    domain->connection_up = 0;
	else if ((!domain->con_active[domain->working_conn])
		 && (domain->conn[domain->working_conn]->set_active_state)
		 && domain->option_activate_if_possible)
	{
	    domain->conn[domain->working_conn]->set_active_state(
		domain->conn[domain->working_conn],
		1,
		ll_addr_changed,
		domain);
	} else {
	    reroute_cmds(domain, u, domain->working_conn);
	}
	call_con_fails(domain, err, u, port_num, domain->connection_up);
    }

 out_unlock:
    i_ipmi_domain_put(domain);
}

int
ipmi_option_SDRs(ipmi_domain_t *domain)
{
    return domain->option_all || domain->option_SDRs;
}

int
ipmi_option_SEL(ipmi_domain_t *domain)
{
    return domain->option_all || domain->option_SEL;
}

int
ipmi_option_FRUs(ipmi_domain_t *domain)
{
    return domain->option_all || domain->option_FRUs;
}

int
ipmi_option_IPMB_scan(ipmi_domain_t *domain)
{
    if (domain->option_local_only)
	return 0;
    return domain->option_all || domain->option_IPMB_scan;
}

int
ipmi_option_OEM_init(ipmi_domain_t *domain)
{
    return domain->option_all || domain->option_OEM_init;
}

int
ipmi_option_set_event_rcvr(ipmi_domain_t *domain)
{
    if (domain->option_local_only)
	return 0;
    return domain->option_set_event_rcvr;
}

int
ipmi_option_set_sel_time(ipmi_domain_t *domain)
{
    return domain->option_set_sel_time;
}

int
ipmi_option_use_cache(ipmi_domain_t *domain)
{
    return domain->option_use_cache;
}

int
ipmi_option_activate_if_possible(ipmi_domain_t *domain)
{
    return domain->option_activate_if_possible;
}

int
ipmi_option_local_only(ipmi_domain_t *domain)
{
    return domain->option_local_only;
}

void
i_ipmi_option_set_local_only_if_not_specified(ipmi_domain_t *domain, int val)
{
    if (domain->option_local_only_set)
	return;
    domain->option_local_only = val != 0;
}


int
ipmi_open_domain(const char         *name,
		 ipmi_con_t         *con[],
		 unsigned int       num_con,
		 ipmi_domain_con_cb con_change_handler,
		 void               *con_change_cb_data,
		 ipmi_domain_ptr_cb domain_fully_up,
		 void               *domain_fully_up_cb_data,
		 ipmi_open_option_t *options,
		 unsigned int       num_options,
		 ipmi_domain_id_t   *new_domain)
{
    int           rv;
    ipmi_domain_t *domain = NULL;
    unsigned int  i;

    if ((num_con < 1) || (num_con > MAX_CONS))
	return EINVAL;

    rv = setup_domain(name, con, num_con, options, num_options, &domain);
    if (rv)
	return rv;

    domain->domain_fully_up = domain_fully_up;
    domain->domain_fully_up_cb_data = domain_fully_up_cb_data;
    domain->fully_up_count = 1;

    for (i=0; i<num_con; i++) {
	rv = con[i]->add_con_change_handler(con[i], ll_con_changed, domain);
	if (rv)
	    goto out_err;
	rv = con[i]->add_ipmb_addr_handler(con[i], ll_addr_changed, domain);
	if (rv)
	    goto out_err;
    }

    add_known_domain(domain);

    if (con_change_handler) {
	rv = ipmi_domain_add_connect_change_handler(domain,
						    con_change_handler,
						    con_change_cb_data);
	if (rv)
	    goto out_err;
    }

    for (i=0; i<num_con; i++) {
	/* Set the ports that we will have valid and unconnected. */
	if (con[i]->get_num_ports) {
	    int m = con[i]->get_num_ports(con[i]);
	    int j;
	    for (j=0; j<m; j++)
		domain->port_up[j][i] = 0;
	} else
	    /* Only one port 0 */
	    domain->port_up[0][i] = 0;
	rv = con[i]->start_con(con[i]);
	if (rv)
	    break;
    }
    if (rv)
	goto out_err;

    if (new_domain)
	*new_domain = ipmi_domain_convert_to_id(domain);
    
    if (! locked_list_add(domains_list, domain, NULL)) {
	ipmi_log(IPMI_LOG_SEVERE,
		 "%sdomain.c(sdr_handler): "
		 "Out of memory, could not add domain to the domains list",
		 DOMAIN_NAME(domain));
    }

    call_domain_change(domain, IPMI_ADDED);

    i_ipmi_domain_put(domain);
    return rv;

 out_err:
    for (i=0; i<num_con; i++) {
	con[i]->remove_con_change_handler(con[i], ll_con_changed, domain);
	con[i]->remove_ipmb_addr_handler(con[i], ll_addr_changed, domain);
	if (con[i]->register_stat_handler)
	    con[i]->unregister_stat_handler(con[i],
					    domain->con_stat_info);
    }
    remove_known_domain(domain);
    cleanup_domain(domain);
    return rv;
}

/***********************************************************************
 *
 * Handle misc data about domains.
 *
 **********************************************************************/

typedef struct domains_iter_s
{
    ipmi_domain_ptr_cb handler;
    void               *cb_data;
} domains_iter_t;

static int
iterate_domains(void *cb_data, void *item1, void *item2)
{
    domains_iter_t *info = cb_data;
    ipmi_domain_t  *domain = item1;
    int            rv;
    
    rv = i_ipmi_domain_get(domain);
    if (!rv) {
	info->handler(item1, info->cb_data);
	i_ipmi_domain_put(domain);
    }
    return LOCKED_LIST_ITER_CONTINUE;
}

void
ipmi_domain_iterate_domains(ipmi_domain_ptr_cb handler,
			    void               *cb_data)
{
    domains_iter_t info;

    if (!handler)
	return;
    if (!domains_list)
	return;

    info.handler = handler;
    info.cb_data = cb_data;
    locked_list_iterate(domains_list, iterate_domains, &info);
}

ipmi_sdr_info_t *
ipmi_domain_get_main_sdrs(ipmi_domain_t *domain)
{
    return domain->main_sdrs;
}

int
ipmi_domain_get_num_channels(ipmi_domain_t *domain, int *val)
{
    CHECK_DOMAIN_LOCK(domain);

    *val = MAX_IPMI_USED_CHANNELS;
    return 0;
}

int
ipmi_domain_get_channel(ipmi_domain_t    *domain,
			int              index,
			ipmi_chan_info_t *chan)
{
    CHECK_DOMAIN_LOCK(domain);

    if (index >= MAX_IPMI_USED_CHANNELS)
	return EINVAL;

    *chan = domain->chan[index];
    return 0;
}

int
ipmi_domain_get_guid(ipmi_domain_t *domain, unsigned char *guid)
{
    int rv;
    i_ipmi_mc_get(domain->si_mc);
    rv = ipmi_mc_get_guid(domain->si_mc, guid);
    i_ipmi_mc_put(domain->si_mc);
    return rv;
}

int
ipmi_domain_con_up(ipmi_domain_t *domain)
{
    CHECK_DOMAIN_LOCK(domain);
    return domain->connection_up;
}

static void
check_event_rcvr(ipmi_domain_t *domain, ipmi_mc_t *mc, void *cb_data)
{
    unsigned int *addr = cb_data;
    if (*addr)
	return;
    if (!ipmi_mc_ipmb_event_receiver_support(mc))
	return;
    if (ipmi_mc_get_channel(mc) == IPMI_BMC_CHANNEL)
	return;
    *addr = ipmi_mc_get_address(mc);
}

int
ipmi_domain_get_event_rcvr(ipmi_domain_t *domain)
{
    unsigned int addr = 0;

    ipmi_domain_iterate_mcs(domain, check_event_rcvr, &addr);
    return addr;
}

const char *
i_ipmi_domain_name(const ipmi_domain_t *domain)
{
    return domain->name;
}

int
ipmi_domain_get_name(ipmi_domain_t *domain, char *name, int length)
{
    int  slen;

    if (length <= 0)
	return 0;

    /* Never changes, no lock needed. */
    slen = strlen(domain->name);
    if (slen == 0) {
	if (name)
	    *name = '\0';
	goto out;
    }

    slen -= 1; /* Remove the trailing ' ' */
    if (slen >= length) {
	slen = length - 1;
    }

    if (name) {
	memcpy(name, domain->name, slen);
	name[slen] = '\0';
    }
 out:
    return slen;
}

void
ipmi_domain_set_oem_data(ipmi_domain_t                   *domain,
			 void                            *oem_data,
			 ipmi_domain_destroy_oem_data_cb destroyer)
{
    domain->oem_data = oem_data;
    domain->oem_data_destroyer = destroyer;
}

void *
ipmi_domain_get_oem_data(ipmi_domain_t *domain)
{
    return domain->oem_data;
}

enum ipmi_domain_type
ipmi_domain_get_type(ipmi_domain_t *domain)
{
    return domain->domain_type;
}

void
ipmi_domain_set_type(ipmi_domain_t *domain, enum ipmi_domain_type dtype)
{
    domain->domain_type = dtype;
}

unsigned int
ipmi_domain_get_unique_num(ipmi_domain_t *domain)
{
    unsigned int rv;

    ipmi_lock(domain->domain_lock);
    rv = domain->uniq_num;
    domain->uniq_num++;
    ipmi_unlock(domain->domain_lock);
    return rv;
}


/*OEM-specific sensors handling*/
int
ipmi_domain_add_new_sensor_handler(ipmi_domain_t         *domain,
                                   ipmi_domain_sensor_cb handler,
                                   void                  *cb_data)
{
    if (locked_list_add(domain->new_sensor_handlers, handler, cb_data))
        return 0;
    else
        return ENOMEM;
}

int
ipmi_domain_remove_new_sensor_handler(ipmi_domain_t        *domain,
                                      ipmi_domain_sensor_cb handler,
                                       void                *cb_data)
{
    if (locked_list_remove(domain->new_sensor_handlers, handler, cb_data))
        return 0;
    else
        return EINVAL;
}

typedef struct new_sensor_handler_info_s
{
    ipmi_domain_t *domain;
    ipmi_sensor_t *sensor;
} new_sensor_handler_info_t;

static int
call_new_sensor_handler(void *cb_data, void *item1, void *item2)
{
    new_sensor_handler_info_t *info = cb_data;
    ipmi_domain_sensor_cb     handler = item1;

    handler(info->domain, info->sensor, item2);
    return 0;
}

int
i_call_new_sensor_handlers(ipmi_domain_t *domain,
			   ipmi_sensor_t *sensor)
{
    new_sensor_handler_info_t info;

    info.domain = domain;
    info.sensor = sensor;

    locked_list_iterate(domain->new_sensor_handlers, call_new_sensor_handler,
		        &info);
    return 0;
}

/***********************************************************************
 *
 * Handling anonmymous attributes for domains
 *
 **********************************************************************/

struct ipmi_domain_attr_s
{
    char *name;
    void *data;

    ipmi_lock_t *lock;
    unsigned int refcount;

    ipmi_domain_attr_kill_cb destroy;
    void                     *cb_data;
};

static int
destroy_attr(void *cb_data, void *item1, void *item2)
{
    ipmi_domain_t      *domain = cb_data;
    ipmi_domain_attr_t *attr = item1;

    locked_list_remove(domain->attr, item1, item2);
    ipmi_domain_attr_put(attr);
    return LOCKED_LIST_ITER_CONTINUE;
}

typedef struct domain_attr_cmp_s
{
    char               *name;
    ipmi_domain_attr_t *attr;
} domain_attr_cmp_t;

static int
domain_attr_cmp(void *cb_data, void *item1, void *item2)
{
    domain_attr_cmp_t  *info = cb_data;
    ipmi_domain_attr_t *attr = item1;

    if (strcmp(info->name, attr->name) == 0) {
	info->attr = attr;
	return LOCKED_LIST_ITER_STOP;
    }

    return LOCKED_LIST_ITER_CONTINUE;
}

int
ipmi_domain_register_attribute(ipmi_domain_t            *domain,
			       char                     *name,
			       ipmi_domain_attr_init_cb init,
			       ipmi_domain_attr_kill_cb destroy,
			       void                     *cb_data,
			       ipmi_domain_attr_t       **attr)
{
    ipmi_domain_attr_t  *val = NULL;
    domain_attr_cmp_t   info;
    int                 rv = 0;
    locked_list_entry_t *entry;

    info.name = name;
    info.attr = NULL;
    locked_list_lock(domain->attr);
    locked_list_iterate_nolock(domain->attr, domain_attr_cmp, &info);
    if (info.attr) {
	ipmi_lock(info.attr->lock);
	info.attr->refcount++;
	ipmi_unlock(info.attr->lock);
	*attr = info.attr;
	goto out_unlock;
    }

    val = ipmi_mem_alloc(sizeof(*val));
    if (!val) {
	rv = ENOMEM;
	goto out_unlock;
    }

    val->name = ipmi_strdup(name);
    if (!val->name) {
	ipmi_mem_free(val);
	rv = ENOMEM;
	goto out_unlock;
    }

    entry = locked_list_alloc_entry();
    if (!entry) {
	ipmi_mem_free(val->name);
	ipmi_mem_free(val);
	rv = ENOMEM;
	goto out_unlock;
    }

    rv = ipmi_create_lock(domain, &val->lock);
    if (rv) {
	locked_list_free_entry(entry);
	ipmi_mem_free(val->name);
	ipmi_mem_free(val);
	goto out_unlock;
    }

    val->refcount = 2;
    val->destroy = destroy;
    val->cb_data = cb_data;
    val->data = NULL;

    if (init) {
	rv = init(domain, cb_data, &val->data);
	if (rv) {
	    ipmi_destroy_lock(val->lock);
	    locked_list_free_entry(entry);
	    ipmi_mem_free(val->name);
	    ipmi_mem_free(val);
	    rv = ENOMEM;
	    goto out_unlock;
	}
    }

    locked_list_add_entry_nolock(domain->attr, val, NULL, entry);

    *attr = val;

 out_unlock:
    locked_list_unlock(domain->attr);
    return rv;
}
			       
int
ipmi_domain_find_attribute(ipmi_domain_t      *domain,
			   char               *name,
			   ipmi_domain_attr_t **attr)
{
    domain_attr_cmp_t info;

    if (!domain->attr)
	return EINVAL;

    /* Attributes are immutable, no lock is required. */
    info.name = name;
    info.attr = NULL;
    locked_list_iterate(domain->attr, domain_attr_cmp, &info);
    if (info.attr) {
	ipmi_lock(info.attr->lock);
	info.attr->refcount++;
	ipmi_unlock(info.attr->lock);
	*attr = info.attr;
	return 0;
    }
    return EINVAL;
}

void *
ipmi_domain_attr_get_data(ipmi_domain_attr_t *attr)
{
    return attr->data;
}

void
ipmi_domain_attr_put(ipmi_domain_attr_t *attr)
{
    ipmi_lock(attr->lock);
    attr->refcount--;
    if (attr->refcount > 0) {
	ipmi_unlock(attr->lock);
	return;
    }
    ipmi_unlock(attr->lock);
    if (attr->destroy)
	attr->destroy(attr->cb_data, attr->data);
    ipmi_destroy_lock(attr->lock);
    ipmi_mem_free(attr->name);
    ipmi_mem_free(attr);
}
			       
typedef struct find_attr_s
{
    char               *name;
    ipmi_domain_attr_t **attr;
    int                rv;
} find_attr_t;

static void
find_attr_2(ipmi_domain_t *domain, void *cb_data)
{
    find_attr_t *info = cb_data;

    info->rv = ipmi_domain_find_attribute(domain, info->name, info->attr);
}

int
ipmi_domain_id_find_attribute(ipmi_domain_id_t   domain_id,
			      char               *name,
			      ipmi_domain_attr_t **attr)
{
    find_attr_t info = { name, attr, 0 };
    int  rv;

    rv = ipmi_domain_pointer_cb(domain_id, find_attr_2, &info);
    if (!rv)
	rv = info.rv;
    return rv;
}

/***********************************************************************
 *
 * Statistics
 *
 **********************************************************************/

struct ipmi_domain_stat_s
{
    char               *name;
    char               *instance;
    ipmi_lock_t        *lock;
    unsigned int       count;
    ipmi_domain_stat_t *stat;
    unsigned int       refcount;
};

static int
destroy_stat(void *cb_data, void *item1, void *item2)
{
    ipmi_domain_t      *domain = cb_data;
    ipmi_domain_stat_t *stat = item1;

    locked_list_remove(domain->stats, item1, item2);
    ipmi_domain_stat_put(stat);
    return LOCKED_LIST_ITER_CONTINUE;
}

typedef struct domain_stat_cmp_s
{
    const char         *name;
    const char         *instance;
    ipmi_domain_stat_t *stat;
} domain_stat_cmp_t;

static int
domain_stat_cmp(void *cb_data, void *item1, void *item2)
{
    domain_stat_cmp_t  *info = cb_data;
    ipmi_domain_stat_t *stat = item1;

    if ((strcmp(info->name, stat->name) == 0)
	&& (strcmp(info->instance, stat->instance) == 0))
    {
	info->stat = stat;
	return LOCKED_LIST_ITER_STOP;
    }

    return LOCKED_LIST_ITER_CONTINUE;
}

int
ipmi_domain_stat_register(ipmi_domain_t      *domain,
			  const char         *name,
			  const char         *instance,
			  ipmi_domain_stat_t **stat)
{
    ipmi_domain_stat_t  *val = NULL;
    domain_stat_cmp_t   info;
    int                 rv = 0;
    locked_list_entry_t *entry;

    info.name = name;
    info.instance = instance;
    info.stat = NULL;
    locked_list_lock(domain->stats);
    locked_list_iterate_nolock(domain->stats, domain_stat_cmp, &info);
    if (info.stat) {
	ipmi_lock(info.stat->lock);
	info.stat->refcount++;
	ipmi_unlock(info.stat->lock);
	*stat = info.stat;
	goto out_unlock;
    }

    val = ipmi_mem_alloc(sizeof(*val));
    if (!val) {
	rv = ENOMEM;
	goto out_unlock;
    }

    val->name = ipmi_strdup(name);
    if (!val->name) {
	ipmi_mem_free(val);
	rv = ENOMEM;
	goto out_unlock;
    }

    val->instance = ipmi_strdup(instance);
    if (!val->instance) {
	ipmi_mem_free(val->name);
	ipmi_mem_free(val);
	rv = ENOMEM;
	goto out_unlock;
    }

    entry = locked_list_alloc_entry();
    if (!entry) {
	ipmi_mem_free(val->instance);
	ipmi_mem_free(val->name);
	ipmi_mem_free(val);
	rv = ENOMEM;
	goto out_unlock;
    }

    rv = ipmi_create_lock(domain, &val->lock);
    if (rv) {
	locked_list_free_entry(entry);
	ipmi_mem_free(val->instance);
	ipmi_mem_free(val->name);
	ipmi_mem_free(val);
	goto out_unlock;
    }

    val->refcount = 2;
    val->count = 0;

    locked_list_add_entry_nolock(domain->stats, val, NULL, entry);

    *stat = val;

 out_unlock:    
    locked_list_unlock(domain->stats);
    return 0;
}

int
ipmi_domain_find_stat(ipmi_domain_t      *domain,
		      const char         *name,
		      const char         *instance,
		      ipmi_domain_stat_t **stat)
{
    domain_stat_cmp_t   info;
    int                 rv = ENOENT;

    info.name = name;
    info.instance = instance;
    info.stat = NULL;
    locked_list_lock(domain->stats);
    locked_list_iterate_nolock(domain->stats, domain_stat_cmp, &info);
    if (info.stat) {
	ipmi_lock(info.stat->lock);
	info.stat->refcount++;
	ipmi_unlock(info.stat->lock);
	*stat = info.stat;
	rv = 0;
    }
    locked_list_unlock(domain->stats);

    return rv;
}

void
ipmi_domain_stat_put(ipmi_domain_stat_t *stat)
{
    ipmi_lock(stat->lock);
    stat->refcount--;
    if (stat->refcount > 0) {
	ipmi_unlock(stat->lock);
	return;
    }
    ipmi_unlock(stat->lock);
    ipmi_destroy_lock(stat->lock);
    ipmi_mem_free(stat->name);
    ipmi_mem_free(stat->instance);
    ipmi_mem_free(stat);
}

void
ipmi_domain_stat_add(ipmi_domain_stat_t *stat, int amount)
{
    ipmi_lock(stat->lock);
    stat->count += amount;
    ipmi_unlock(stat->lock);
}

unsigned int
ipmi_domain_stat_get(ipmi_domain_stat_t *stat)
{
    unsigned int rv;
    ipmi_lock(stat->lock);
    rv = stat->count;
    ipmi_unlock(stat->lock);
    return rv;
}

unsigned int
ipmi_domain_stat_get_and_zero(ipmi_domain_stat_t *stat)
{
    unsigned int rv;
    ipmi_lock(stat->lock);
    rv = stat->count;
    stat->count = 0;
    ipmi_unlock(stat->lock);
    return rv;
}

const char *
ipmi_domain_stat_get_name(ipmi_domain_stat_t *stat)
{
    return stat->name;
}

const char *
ipmi_domain_stat_get_instance(ipmi_domain_stat_t *stat)
{
    return stat->instance;
}

typedef struct stat_iterate_s
{
    ipmi_domain_t *domain;
    const char    *name;
    const char    *instance;
    ipmi_stat_cb  handler;
    void          *cb_data;
} stat_iterate_t;

static int
domain_stat_iter_pre(void *cb_data, void *item1, void *item2)
{
    stat_iterate_t     *info = cb_data;
    ipmi_domain_stat_t *stat = item1;

    if (info->name && (strcmp(info->name, stat->name) != 0))
	return LOCKED_LIST_ITER_SKIP;
    if (info->instance && (strcmp(info->instance, stat->instance) != 0))
	return LOCKED_LIST_ITER_SKIP;

    ipmi_lock(stat->lock);
    stat->refcount++;
    ipmi_unlock(stat->lock);

    return LOCKED_LIST_ITER_CONTINUE;
}

static int
domain_stat_iter(void *cb_data, void *item1, void *item2)
{
    stat_iterate_t     *info = cb_data;
    ipmi_domain_stat_t *stat = item1;

    /* Prefunc already matched, this is a good one. */
    info->handler(info->domain, stat, info->cb_data);
    ipmi_domain_stat_put(stat);

    return LOCKED_LIST_ITER_CONTINUE;
}

void
ipmi_domain_stat_iterate(ipmi_domain_t *domain,
			 const char    *name,
			 const char    *instance,
			 ipmi_stat_cb  handler,
			 void          *cb_data)
{
    stat_iterate_t info;

    info.domain = domain;
    info.name = name;
    info.instance = instance;
    info.handler = handler;
    info.cb_data = cb_data;
    locked_list_iterate_prefunc(domain->stats, domain_stat_iter_pre,
				domain_stat_iter, &info);
}

/***********************************************************************
 *
 * Initialization and shutdown
 *
 **********************************************************************/

int
i_ipmi_domain_init(void)
{
    int rv;

    if (domains_initialized)
	return 0;

    mc_oem_handlers = locked_list_alloc(ipmi_get_global_os_handler());
    if (!mc_oem_handlers)
	return ENOMEM;

    domain_change_handlers = locked_list_alloc(ipmi_get_global_os_handler());
    if (!domain_change_handlers)
	return ENOMEM;

    domains_list = locked_list_alloc(ipmi_get_global_os_handler());
    if (!domains_list) {
	locked_list_destroy(domain_change_handlers);
	return ENOMEM;
    }

    oem_handlers = alloc_ilist();
    if (!oem_handlers) {
	locked_list_destroy(domain_change_handlers);
	locked_list_destroy(domains_list);
	domains_list = NULL;
	return ENOMEM;
    }

    rv = ipmi_create_global_lock(&domains_lock);
    if (rv) {
	locked_list_destroy(domain_change_handlers);
	locked_list_destroy(domains_list);
	domains_list = NULL;
	free_ilist(oem_handlers);
	oem_handlers = NULL;
	return rv;
    }

    domains_initialized = 1;

    return 0;
}

void
i_ipmi_domain_shutdown(void)
{
    domains_initialized = 0;

    locked_list_destroy(domain_change_handlers);
    locked_list_destroy(mc_oem_handlers);
    locked_list_destroy(domains_list);
    domains_list = NULL;
    free_ilist(oem_handlers);
    oem_handlers = NULL;
    ipmi_destroy_lock(domains_lock);
    domains_lock = NULL;
}


/***********************************************************************
 *
 * Cruft
 *
 **********************************************************************/

struct ipmi_domain_mc_upd_s
{
    ipmi_domain_mc_upd_cb handler;
    void                  *cb_data;
    struct ipmi_domain_mc_upd_s *next, *prev;
};

int
ipmi_domain_register_mc_update_handler(ipmi_domain_t         *domain,
				       ipmi_domain_mc_upd_cb handler,
				       void                  *cb_data,
				       struct ipmi_domain_mc_upd_s **id)
{
    struct ipmi_domain_mc_upd_s *info;
    int                         rv;

    info = ipmi_mem_alloc(sizeof(*info));
    if (!info)
	return ENOMEM;
    rv = ipmi_domain_add_mc_updated_handler(domain, handler, cb_data);
    if (rv) {
	ipmi_mem_free(info);
    } else {
	info->handler = handler;
	info->cb_data = cb_data;
	ipmi_lock(domain->domain_lock);
	info->next = domain->mc_upd_cruft;
	info->prev = NULL;
	domain->mc_upd_cruft = info;
	ipmi_unlock(domain->domain_lock);

	if (id)
	    *id = info;
    }

    return rv;
}

void
ipmi_domain_remove_mc_update_handler(ipmi_domain_t               *domain,
				     struct ipmi_domain_mc_upd_s *id)
{
    ipmi_domain_remove_mc_updated_handler(domain, id->handler, id->cb_data);
    ipmi_lock(domain->domain_lock);
    if (id->next)
	id->next->prev = id->prev;
    if (id->prev)
	id->prev->next = id->next;
    else
	domain->mc_upd_cruft = id->next;
    ipmi_unlock(domain->domain_lock);
    ipmi_mem_free(id);
}

struct ipmi_event_handler_id_s
{
    ipmi_event_handler_cb   handler;
    void                    *event_data;
    struct ipmi_event_handler_id_s *next, *prev;
};

int
ipmi_register_for_events(ipmi_domain_t                  *domain,
			 ipmi_event_handler_cb          handler,
			 void                           *event_data,
			 struct ipmi_event_handler_id_s **id)
{
    struct ipmi_event_handler_id_s *info;
    int                            rv;

    info = ipmi_mem_alloc(sizeof(*info));
    if (!info)
	return ENOMEM;
    rv = ipmi_domain_add_event_handler(domain, handler, event_data);
    if (rv) {
	ipmi_mem_free(info);
    } else {
	info->handler = handler;
	info->event_data = event_data;
	ipmi_lock(domain->domain_lock);
	info->next = domain->event_cruft;
	info->prev = NULL;
	domain->event_cruft = info;
	ipmi_unlock(domain->domain_lock);

	if (id)
	    *id = info;
    }

    return rv;
}

int
ipmi_deregister_for_events(ipmi_domain_t                  *domain,
			   struct ipmi_event_handler_id_s *id)
{
    int rv;
    rv = ipmi_domain_remove_event_handler(domain, id->handler, id->event_data);
    ipmi_lock(domain->domain_lock);
    if (id->next)
	id->next->prev = id->prev;
    if (id->prev)
	id->prev->next = id->next;
    else
	domain->event_cruft = id->next;
    ipmi_unlock(domain->domain_lock);
    ipmi_mem_free(id);
    return rv;
}

struct ipmi_domain_con_change_s
{
    ipmi_domain_con_cb              handler;
    void                            *cb_data;
    struct ipmi_domain_con_change_s *next, *prev;
};

static int
ipmi_domain_add_con_change_handler_nd(ipmi_domain_t                   *domain,
				      ipmi_domain_con_cb              handler,
				      void                            *cb_data,
				      struct ipmi_domain_con_change_s **id)
{
    struct ipmi_domain_con_change_s *info;
    int                             rv;

    info = ipmi_mem_alloc(sizeof(*info));
    if (!info)
	return ENOMEM;
    rv = ipmi_domain_add_connect_change_handler(domain, handler, cb_data);
    if (rv) {
	ipmi_mem_free(info);
    } else {
	info->handler = handler;
	info->cb_data = cb_data;
	ipmi_lock(domain->domain_lock);
	info->next = domain->con_change_cruft;
	info->prev = NULL;
	domain->con_change_cruft = info;
	ipmi_unlock(domain->domain_lock);

	if (id)
	    *id = info;
    }

    return rv;
}

int
ipmi_domain_add_con_change_handler(ipmi_domain_t                   *domain,
				   ipmi_domain_con_cb              handler,
				   void                            *cb_data,
				   struct ipmi_domain_con_change_s **id)
{
    return ipmi_domain_add_con_change_handler_nd(domain,
						 handler,
						 cb_data,
						 id);
}

void
ipmi_domain_remove_con_change_handler(ipmi_domain_t                   *domain,
				      struct ipmi_domain_con_change_s *id)
{
    ipmi_domain_remove_connect_change_handler(domain, id->handler,
					      id->cb_data);
    ipmi_lock(domain->domain_lock);
    if (id->next)
	id->next->prev = id->prev;
    if (id->prev)
	id->prev->next = id->next;
    else
	domain->con_change_cruft = id->next;
    ipmi_unlock(domain->domain_lock);
    ipmi_mem_free(id);
}

int
ipmi_init_domain(ipmi_con_t               *con[],
		 unsigned int             num_con,
		 ipmi_domain_con_cb       con_change_handler,
		 void                     *con_change_cb_data,
		 struct ipmi_domain_con_change_s **con_change_id,
		 ipmi_domain_id_t         *new_domain)
{
    int           rv;
    ipmi_domain_t *domain;
    unsigned int  i;

    if ((num_con < 1) || (num_con > MAX_CONS))
	return EINVAL;

    rv = setup_domain("", con, num_con, NULL, 0, &domain);
    if (rv)
	return rv;

    domain->in_startup = 1;
    for (i=0; i<num_con; i++) {
	rv = con[i]->add_con_change_handler(con[i], ll_con_changed, domain);
	if (rv)
	    return rv;
	rv = con[i]->add_ipmb_addr_handler(con[i], ll_addr_changed, domain);
	if (rv)
	    return rv;
    }

    add_known_domain(domain);

    if (con_change_handler) {
	rv = ipmi_domain_add_con_change_handler_nd(domain, con_change_handler,
						   con_change_cb_data,
						   con_change_id);
	if (rv)
	    goto out_err;
    }

    for (i=0; i<num_con; i++)
	rv = con[i]->start_con(con[i]);
    if (rv)
	goto out_err;

    if (new_domain)
	*new_domain = ipmi_domain_convert_to_id(domain);
    
    if (! locked_list_add(domains_list, domain, NULL)) {
	ipmi_log(IPMI_LOG_SEVERE,
		 "%sdomain.c(sdr_handler): "
		 "Out of memory, could not add domain to the domains list",
		 DOMAIN_NAME(domain));
    }

 out:
    i_ipmi_domain_put(domain);
    return rv;

 out_err:
    for (i=0; i<num_con; i++) {
	con[i]->remove_con_change_handler(con[i], ll_con_changed, domain);
	con[i]->remove_ipmb_addr_handler(con[i], ll_addr_changed, domain);
	if (con[i]->register_stat_handler)
	    con[i]->unregister_stat_handler(con[i],
					    domain->con_stat_info);
    }
    remove_known_domain(domain);
    cleanup_domain(domain);
    goto out;
}

int
ipmi_domain_set_entity_update_handler(ipmi_domain_t         *domain,
				      ipmi_domain_entity_cb handler,
				      void                  *cb_data)
{
    int rv = 0;

    CHECK_DOMAIN_LOCK(domain);

    ipmi_lock(domain->domain_lock);
    if (domain->cruft_entity_update_handler)
	ipmi_entity_info_remove_update_handler
	    (domain->entities,
	     domain->cruft_entity_update_handler,
	     domain->cruft_entity_update_cb_data);

    domain->cruft_entity_update_handler = handler;
    domain->cruft_entity_update_cb_data = cb_data;
    if (handler)
	rv = ipmi_entity_info_add_update_handler(domain->entities,
						 handler,
						 cb_data);
    ipmi_unlock(domain->domain_lock);
    return rv;
}

int
ipmi_close_connection(ipmi_domain_t             *domain,
		      ipmi_domain_close_done_cb close_done,
		      void                      *cb_data)
{
    return ipmi_domain_close(domain, close_done, cb_data);
}

static void
free_domain_cruft(ipmi_domain_t *domain)
{
    while (domain->mc_upd_cruft) {
	struct ipmi_domain_mc_upd_s *to_free;
	to_free = domain->mc_upd_cruft;
	domain->mc_upd_cruft = to_free->next;
	ipmi_mem_free(to_free);
    }

    while (domain->event_cruft) {
	struct ipmi_event_handler_id_s *to_free;
	to_free = domain->event_cruft;
	domain->event_cruft = to_free->next;
	ipmi_mem_free(to_free);
    }

    while (domain->con_change_cruft) {
	struct ipmi_domain_con_change_s *to_free;
	to_free = domain->con_change_cruft;
	domain->con_change_cruft = to_free->next;
	ipmi_mem_free(to_free);
    }
}
