Blob Blame History Raw
/*
 * ipmi_sol.c
 *
 * IPMI Serial-over-LAN Client Code
 *
 * Author: Cyclades Australia Pty. Ltd.
 *         Darius Davis <darius.davis@cyclades.com>
 *
 * Copyright 2005 Cyclades Australia Pty. Ltd.
 *
 *  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.
 */

/*
 * TODO:
 *	- We only support UDP port 623 for now.  Add support for other ports.
 *
 * CAVEATS:
 *	- Multiple connections at once: should work, but UNTESTED.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/stat.h>
#include <sys/poll.h>
#include <sys/time.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netdb.h>

#include <OpenIPMI/ipmi_conn.h>
#include <OpenIPMI/ipmi_msgbits.h>
#include <OpenIPMI/ipmi_err.h>
#include <OpenIPMI/ipmi_lan.h>
#include <OpenIPMI/internal/ipmi_malloc.h>
#include <OpenIPMI/ipmi_auth.h>
#include <OpenIPMI/internal/locked_list.h>
#include <OpenIPMI/internal/ipmi_int.h>
#include <OpenIPMI/ipmi_sol.h>

/* FIXME - after processing waiting packets, a transmitter prod may be
   necessary in some cases.  Figure out where. */


/*
 * Locking notes:
 *
 * You may claim the queue_lock or the oob_lock if you already hold
 * packet_lock.  The reverse is not allowed.  You may not claim the
 * oob_lock and the queue lock at the same time.
 *
 * You may not hold the conn_lock with any other lock; use the
 * refcounts instead.
 *
 * The packet_lock is the "general" lock for the connection; it
 * protects the non-atomic data updates that would affect the
 * operation of the connection.
 *
 * No locks may be held in user callbacks.  All the do_xxx_callbacks()
 * functions must be called with the packet lock held.  They will
 * release the packet lock and reclaim it.  These functions are
 * responsible for serializing
 */

/*
 * Bit masks for status conditions sent BMC -> console, see Table 15-2
 * [1], page 208, 3rd column.
 */
#define IPMI_SOL_STATUS_NACK_PACKET 0x40
#define IPMI_SOL_STATUS_CHARACTER_TRANSFER_UNAVAIL 0x20
#define IPMI_SOL_STATUS_DEACTIVATED 0x10
#define IPMI_SOL_STATUS_BMC_TX_OVERRUN 0x08
#define IPMI_SOL_STATUS_BREAK_DETECTED 0x04


/*
 * Bit masks for operations sent console -> BMC, see Table 15-2 [1],
 * page 208, 4th column.
 */
#define IPMI_SOL_OPERATION_NACK_PACKET 0x40
#define IPMI_SOL_OPERATION_RING_REQUEST 0x20
#define IPMI_SOL_OPERATION_GENERATE_BREAK 0x10
#define IPMI_SOL_OPERATION_CTS_PAUSE 0x08
#define IPMI_SOL_OPERATION_DROP_DCD_DSR 0x04
#define IPMI_SOL_OPERATION_FLUSH_CONSOLE_TO_BMC 0x02
#define IPMI_SOL_OPERATION_FLUSH_BMC_TO_CONSOLE 0x01


#define IPMI_SOL_AUX_USE_ENCRYPTION 0x80
#define IPMI_SOL_AUX_USE_AUTHENTICATION 0x40
#define IPMI_SOL_AUX_SHARED_SERIAL_BEHAVIOR_SHIFT 2
#define IPMI_SOL_AUX_SHARED_SERIAL_BEHAVIOR_MASK 0x03
#define IPMI_SOL_AUX_DEASSERT_HANDSHAKE 0x02


#define IPMI_SOL_MAX_DATA_SIZE 103

#if 0
#define IPMI_SOL_DEBUG_TRANSMIT
#define IPMI_SOL_VERBOSE
#define IPMI_SOL_DEBUG_RECEIVE
#endif


typedef struct ipmi_sol_transmitter_context_s ipmi_sol_transmitter_context_t;


/**
 * Stores a list of generic callback functions.  Values are to be typecast as
 * they are extracted.
 */
typedef struct callback_list_s callback_list_t;
struct callback_list_s {
    void            *cb;
    void            *cb_data;
    int             error;
    callback_list_t *next;
};

/**
 * Stores a write-request from the client software, along with its transmit
 * complete callback and in-band operation mask (currently used only for serial
 * breaks).
 */
typedef struct ipmi_sol_outgoing_queue_item_s ipmi_sol_outgoing_queue_item_t;
struct ipmi_sol_outgoing_queue_item_s {
    /* The bytes to transmit.  This will be NULL if the queue item
       represents a BREAK. */
    char *data;

    /* The number of bytes in this packet.  Will be zero if the queue
       item represents a BREAK. */
    unsigned char data_len;

    /* The in-band (sequential) operation.  Should only contain
       IPMI_SOL_GENERATE_BREAK for now. */
    unsigned char ib_op;
	
    /* The callback to call when the data (or BREAK) have been ACKed
       or sent. */
    ipmi_sol_transmit_complete_cb transmit_complete_callback;
    void *cb_data;

    ipmi_sol_outgoing_queue_item_t *next;
};



/**
 * Outgoing write-requests are queued for transmission as the SoL channel
 * is available.  This structure stores the queue.
 */
typedef struct ipmi_sol_outgoing_queue_s {
    ipmi_sol_outgoing_queue_item_t *head;
    ipmi_sol_outgoing_queue_item_t *tail;
} ipmi_sol_outgoing_queue_t;


/**
 * Offsets of fields within the SoL packet
 */
#define PACKET_SEQNR 0
#define PACKET_ACK_NACK_SEQNR 1
#define PACKET_ACCEPTED_CHARACTER_COUNT 2
#define PACKET_OP 3
#define PACKET_STATUS 3
#define PACKET_DATA 4

typedef struct ipmi_sol_outgoing_packet_record_s {
    /* OS handler for freeing the timer. */
    os_handler_t *os_hnd;

    /* The connection that owns this packet. */
    ipmi_sol_conn_t *conn;

    /* The outgoing SoL payload data.  Min 4 bytes long. */
    unsigned char *packet;

    /* The length of the outgoing SoL payload data. */
    int packet_size;

    /* The timer to manage retransmits.  Needs a lock for concurrency
       handling. */
    int deleted;
    int timer_running;
    ipmi_lock_t *timer_lock;
    os_hnd_timer_id_t *ack_timer;

    /* Nonzero iff we're expecting an ACK for this packet. */
    int expecting_ACK;

    /* Countdown of number of transmission attempts left before we
       declare the packet "lost". */
    int transmit_attempts_remaining;

    /* callbacks for operations in this packet. */
    callback_list_t *op_callback_list;
} ipmi_sol_outgoing_packet_record_t;


/* Used to keep a list of incoming packets to process. */
typedef struct sol_in_packet_info_s
{
    unsigned int  data_len;

    struct sol_in_packet_info_s *next;

    /* Data is tacked on to the end of this. */
} sol_in_packet_info_t;

/* Used to keep a list of pending state transitions. */
typedef struct sol_state_cb_info_s
{
    ipmi_sol_state state;
    int            error;

    struct sol_state_cb_info_s *next;
} sol_state_cb_info_t;

struct ipmi_sol_transmitter_context_s {
    /* A queue of un-acked transmit requests. */
    ipmi_sol_outgoing_queue_t outgoing_queue;

    /* A reference back to the SoL connection to which this
       transmitter belongs. */
    ipmi_sol_conn_t *sol_conn;

    /* The latest entire packet transmitted is stored here. */
    ipmi_sol_outgoing_packet_record_t *transmitted_packet;

    /* A buffer into which the data is coalesced for transmission. */
    unsigned char *scratch_area;

    /* The size of the scratch_area buffer. */
    int scratch_area_size;

    /* Keep track of the sequence number that we've most recently sent. */
    int latest_outgoing_seqnr;

    /* Next outgoing packet will ACK/NACK this packet. */
    int packet_to_acknowledge;

    /* We accepted this many chars from the above packet. */
    int accepted_character_count;

    /* Nack returns pending that we need release_nack calls for. */
    int nack_count;

    /* Are we currently in a receive callback? */
    int in_recv_cb;

    /* We have already acked this many chars from the request at the
       head of the tx queue. */
    int bytes_acked_at_head;

    /* Lock for the op callback list and the op data. */
    ipmi_lock_t   *oob_op_lock;

    /* a combination of IPMI_SOL_OPERATION_RING_REQUEST,
       IPMI_SOL_OPERATION_CTS_PAUSE,
       IPMI_SOL_OPERATION_DROP_DCD_DSR */
    unsigned char oob_persistent_op;

    /* a combination of IPMI_SOL_OPERATION_FLUSH_CONSOLE_TO_BMC,
       IPMI_SOL_OPERATION_FLUSH_BMC_TO_CONSOLE */
    unsigned char oob_transient_op;

    /* callbacks for operations currently in oob_persistent_op and
       oob_transient_op but not yet transmitted */
    callback_list_t *op_callback_list;

    /* Locking for queue operations and packet operations */
    ipmi_lock_t *queue_lock, *packet_lock;
};

struct ipmi_sol_conn_s {
    /* The IPMI connection for commands. */
    ipmi_con_t *ipmi;

    /* The IPMI connection for SOL data. */
    ipmi_con_t *ipmid;

    /* Used to know how many users are using this right now. */
    unsigned int refcount;

    /* The system interface address is cached here for sending RMCP+
       commands. */
    ipmi_system_interface_addr_t addr;

    /* The RMCP+ destination address is cached here for sending SoL
       packets. */
    ipmi_rmcpp_addr_t sol_payload_addr;

    unsigned char initial_bit_rate;
    unsigned char privilege_level;

    /* Nonzero allows ipmi_sol_open to alter the nonvolatile
       configuration to force SoL to come up if at all possible.  Only
       for debugging, please! */
    int force_connection_configure;

    /* Connects more quickly, but will give a lot less diagnostic info
       if it fails. */
    int try_fast_connect;

    /* The current state of the SoL connection.  Note that state
       changes are protected by the packet lock. */
    ipmi_sol_state state;

    /* Max payload size outbound from here->BMC */
    unsigned int max_outbound_payload_size;

    /* Max payload size inbound from BMC->here */
    unsigned int max_inbound_payload_size;

    unsigned int payload_port_number;

    /* We choose a payload instance number when activating the SoL payload */
    unsigned int payload_instance;

    /* This is the transmitter for this connection */
    ipmi_sol_transmitter_context_t transmitter;

    /* The last sequence number we received from the BMC. */
    unsigned char prev_received_seqnr;

    /* The number of characters we ACKed in the last packet received
       from the BMC. */
    unsigned char prev_character_count;

    /* Configuration data used at Payload Activation */
    unsigned char auxiliary_payload_data;
    int ACK_timeout_usec;
    int ACK_retries;

    /* A list of callbacks that are called when data received from the BMC. */
    locked_list_t *data_received_callback_list;

    /* A list of callbacks that are called when a break is reported by
       the BMC. */
    locked_list_t *break_detected_callback_list;

    /* A list of callbacks that are called when a transmit overrun is
       reported by the BMC. */
    locked_list_t *bmc_transmit_overrun_callback_list;

    /* A list of callbacks that are called when the SoL connection
       changes state. */
    locked_list_t *connection_state_callback_list;

    /* We single-thread the processing of packets for a connection.
       New packets get queued to be process later if processing is
       already going on. The following handle this. */
    unsigned int         processing_packet;
    sol_in_packet_info_t *waiting_packets;
    callback_list_t      *waiting_callbacks;
    sol_state_cb_info_t  *waiting_states;

    /* Used to make a linked-list of these */
    ipmi_sol_conn_t *next;
};


static int transmitter_startup(ipmi_sol_transmitter_context_t *transmitter);
static void transmitter_shutdown(ipmi_sol_transmitter_context_t *transmitter,
				 int error);
static void transmitter_prod_nolock
    (ipmi_sol_transmitter_context_t *transmitter);
static void process_packet(ipmi_sol_conn_t *conn,
			   unsigned char   *packet,
			   unsigned int    data_len);

static void
dump_hex(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]);
    }
}


/****************************************************************************
 * SoL Connection List
 *
 * Used to match up an incoming packet with the SoL connection that should
 * be interested in that packet.
 */

/* FIXME - a list is ineffecient for large numbers of connections.  It
   probably doesn't matter for now, but a hash table might be a good
   idea in the future. */

static ipmi_sol_conn_t *conn_list = NULL;
static ipmi_lock_t *conn_lock = NULL;

/**
 * Adds the given (ipmi, sol) pairing to the list of connections we're
 * managing.
 */
static int
add_connection(ipmi_sol_conn_t *nconn)
{
    ipmi_sol_conn_t *conn;

    ipmi_lock(conn_lock);

    /* Make sure the connection doesn't already exist */
    conn = conn_list;
    while (conn) {
	if (conn->ipmi == nconn->ipmi) {
	    ipmi_unlock(conn_lock);
	    return EAGAIN;
	}
	conn = conn->next;
    }

    nconn->next = conn_list;
    conn_list = nconn;
    ipmi_unlock(conn_lock);
    return 0;
}


/**
 * Removes the given connection from the list of connections we're managing.
 */
static void delete_connection(ipmi_sol_conn_t *sol)
{
    ipmi_sol_conn_t *curr;
    ipmi_sol_conn_t *prev = NULL;

    ipmi_lock(conn_lock);
    curr = conn_list;
    while (curr) {
	if (curr == sol) {
	    /*
	     * Delete me!
	     */
	    if (!prev)
		/* Deleting from head of list... */
		conn_list = conn_list->next;
	    else
		/* Deleting from within list */
		prev->next = curr->next;
	    break;
	}

	prev = curr;
	curr = curr->next;
    }
    ipmi_unlock(conn_lock);
}


/**
 * Finds the sol connection for a given ipmi connection.
 */
static ipmi_sol_conn_t *
find_sol_connection_for_ipmi(ipmi_con_t *ipmi)
{
    ipmi_sol_conn_t *conn;

    ipmi_lock(conn_lock);
    conn = conn_list;
    while (conn) {
	if (conn->ipmi == ipmi) {
	    conn->refcount++;
	    ipmi_unlock(conn_lock);
	    return conn;
	}
	conn = conn->next;
    }
    ipmi_unlock(conn_lock);

    return NULL;
}

static ipmi_sol_conn_t *
find_sol_connection(ipmi_sol_conn_t *sol)
{
    ipmi_sol_conn_t *conn;

    ipmi_lock(conn_lock);
    conn = conn_list;
    while (conn) {
	if (conn == sol) {
	    conn->refcount++;
	    ipmi_unlock(conn_lock);
	    return conn;
	}
	conn = conn->next;
    }
    ipmi_unlock(conn_lock);

    return NULL;
}

static void ipmid_changed(ipmi_con_t   *ipmid,
			  int          err,
			  unsigned int port_num,
			  int          any_port_up,
			  void         *cb_data);

static void
sol_connection_closed(ipmi_con_t *ipmi, void *cb_data)
{
    ipmi_sol_conn_t *conn = cb_data;

    if (conn->transmitter.packet_lock)
	ipmi_destroy_lock(conn->transmitter.packet_lock);
    if (conn->transmitter.queue_lock)
	ipmi_destroy_lock(conn->transmitter.queue_lock);
    if (conn->transmitter.oob_op_lock)
	ipmi_destroy_lock(conn->transmitter.oob_op_lock);
    if (conn->data_received_callback_list)
	locked_list_destroy(conn->data_received_callback_list);
    if (conn->break_detected_callback_list)
	locked_list_destroy(conn->break_detected_callback_list);
    if (conn->bmc_transmit_overrun_callback_list)
	locked_list_destroy(conn->bmc_transmit_overrun_callback_list);
    if (conn->connection_state_callback_list)
	locked_list_destroy(conn->connection_state_callback_list);
    ipmi_mem_free(conn);
}

static void
sol_cleanup(ipmi_sol_conn_t *conn)
{
    if (conn->ipmid)
	conn->ipmid->remove_con_change_handler(conn->ipmid, ipmid_changed,
					       conn);

    if (conn->state != ipmi_sol_state_closed)
	ipmi_sol_force_close(conn);

    delete_connection(conn);

    while (conn->waiting_packets) {
	sol_in_packet_info_t *to_free = conn->waiting_packets;

	conn->waiting_packets = to_free->next;
	ipmi_mem_free(to_free);
    }

    if (conn->ipmi->close_connection_done(conn->ipmid, sol_connection_closed,
					  conn))
	sol_connection_closed(NULL, conn);
}

/**
 * Tell the system that a user is done with the connection.
 */
static void
sol_put_connection(ipmi_sol_conn_t *conn)
{
    ipmi_lock(conn_lock);
    conn->refcount--;
    if (conn->refcount == 0) {
	/* No more users, destroy the connection. */
	ipmi_unlock(conn_lock);
	sol_cleanup(conn);
    } else
	ipmi_unlock(conn_lock);
}


/***************************************************************************
 ** Shorthand IPMI messaging; used to set up or close an ipmi_sol_conn_t.
 ** This is NOT used for handling the SoL data... for that, see the payload
 ** functions towards the end of this file.
 **
 ** Note that the packet lock will be held in the callback.
 **/

typedef void (*sol_command_callback)(ipmi_sol_conn_t *conn, ipmi_msg_t *msg);

static int handle_response(ipmi_con_t *ipmi, ipmi_msgi_t *rspi)
{
    ipmi_sol_conn_t      *conn = find_sol_connection(rspi->data1);
    sol_command_callback cb = rspi->data2;

    if (! conn)
	/* Connection went away while in progress... */
	goto out;

    if (cb) {
	ipmi_lock(conn->transmitter.packet_lock);
	cb(conn, &rspi->msg);
	ipmi_unlock(conn->transmitter.packet_lock);
    }

    sol_put_connection(conn);
 out:
    ipmi_free_msg_item(rspi);
    return IPMI_MSG_ITEM_USED;
}


static int
send_message(ipmi_sol_conn_t      *conn,
	     ipmi_msg_t           *msg_out,
	     sol_command_callback cb)
{
    int rv = 0;
    ipmi_msgi_t *rspi = ipmi_alloc_msg_item();

    if (!rspi)
	return ENOMEM;

    rspi->data1 = conn;
    rspi->data2 = cb;
    rspi->data3 = NULL;
    rspi->data4 = NULL;
    rv = conn->ipmi->send_command(conn->ipmi,
				  (ipmi_addr_t *)&conn->addr,
				  sizeof(conn->addr),
				  msg_out,
				  handle_response,
				  rspi);

    if (rv)
	ipmi_free_msg_item(rspi);

    return rv;
}

static int
send_close(ipmi_sol_conn_t *conn, sol_command_callback cb)
{
    ipmi_msg_t    msg_out;
    unsigned char data[6];

    /*
     * Send a Deactivate Payload
     */
    msg_out.data_len = 6;
    msg_out.data = data;

    msg_out.data[0] = IPMI_RMCPP_PAYLOAD_TYPE_SOL & 0x3f; /* payload type */
    msg_out.data[1] = conn->payload_instance; /* payload instance number */
    msg_out.data[2] = 0x00; /* payload aux data */
    msg_out.data[3] = 0x00;
    msg_out.data[4] = 0x00;
    msg_out.data[5] = 0x00;

    msg_out.netfn = IPMI_APP_NETFN;
    msg_out.cmd = IPMI_DEACTIVATE_PAYLOAD_CMD;

    return send_message(conn, &msg_out, cb);
}


/****************************************************************************
 ** Async callback handling - list management, registration, deregistration
 **/

typedef struct do_data_received_callback_s
{
    ipmi_sol_conn_t *conn;
    const void      *buf;
    size_t          count;
    int             nack;
} do_data_received_callback_t;

static int
do_data_received_callback(void *cb_data, void *item1, void *item2)
{
    do_data_received_callback_t *info = cb_data;
    ipmi_sol_data_received_cb   cb = item1;
    
    if (cb(info->conn, info->buf, info->count, item2))
	info->nack++;
    return LOCKED_LIST_ITER_CONTINUE;
}

static int
do_data_received_callbacks(ipmi_sol_conn_t *conn,
			   const void      *buf,
			   size_t          count)
{
    do_data_received_callback_t    info;

    info.conn = conn;
    info.buf = buf;
    info.count = count;
    info.nack = 0;
    locked_list_iterate(conn->data_received_callback_list,
			do_data_received_callback,
			&info);

    /* Only called from the packet handling routine, no need for any
       special handling. for waiting */
    return info.nack;
}

static int
do_break_detected_callback(void *cb_data, void *item1, void *item2)
{
    ipmi_sol_conn_t            *conn = cb_data; 
    ipmi_sol_break_detected_cb cb = item1;
    
    cb(conn, item2);
    return LOCKED_LIST_ITER_CONTINUE;
}

static void
do_break_detected_callbacks(ipmi_sol_conn_t *conn)
{
    locked_list_iterate(conn->break_detected_callback_list,
			do_break_detected_callback,
			conn);

    /* Only called from the packet handling routine, no need for any
       special handling. for waiting */
}

static int
do_transmit_overrun_callback(void *cb_data, void *item1, void *item2)
{
    ipmi_sol_conn_t                  *conn = cb_data; 
    ipmi_sol_bmc_transmit_overrun_cb cb = item1;
    
    cb(conn, item2);
    return LOCKED_LIST_ITER_CONTINUE;
}

static void
do_transmit_overrun_callbacks(ipmi_sol_conn_t *conn)
{
    locked_list_iterate(conn->bmc_transmit_overrun_callback_list,
			do_transmit_overrun_callback,
			conn);

    /* Only called from the packet handling routine, no need for any
       special handling. for waiting */
}

typedef struct do_connection_state_callback_s
{
    ipmi_sol_conn_t *conn;
    ipmi_sol_state  state;
    int             error;
} do_connection_state_callback_t;

static int
do_connection_state_callback(void *cb_data, void *item1, void *item2)
{
    do_connection_state_callback_t *info = cb_data;
    ipmi_sol_connection_state_cb   cb = item1;
    
    cb(info->conn, info->state, info->error, item2);
    return LOCKED_LIST_ITER_CONTINUE;
}

void
do_connection_state_callbacks(ipmi_sol_conn_t *conn,
			      ipmi_sol_state  new_state,
			      int             error)
{
    do_connection_state_callback_t info;

    info.conn = conn;
    info.state = new_state;
    info.error = error;
    locked_list_iterate(conn->connection_state_callback_list,
			do_connection_state_callback,
			&info);
}

int
ipmi_sol_register_data_received_callback(ipmi_sol_conn_t           *conn,
					 ipmi_sol_data_received_cb cb,
					 void                      *cb_data)
{
    if (locked_list_add(conn->data_received_callback_list, cb, cb_data))
	return 0;
    else
	return ENOMEM;
}

int
ipmi_sol_deregister_data_received_callback(ipmi_sol_conn_t           *conn,
					   ipmi_sol_data_received_cb cb,
					   void                      *cb_data)
{
    if (locked_list_remove(conn->data_received_callback_list, cb, cb_data))
	return 0;
    else
	return EINVAL;
}


int
ipmi_sol_register_break_detected_callback(ipmi_sol_conn_t            *conn,
					  ipmi_sol_break_detected_cb cb,
					  void                       *cb_data)
{
    if (locked_list_add(conn->break_detected_callback_list, cb, cb_data))
	return 0;
    else
	return ENOMEM;
}

int
ipmi_sol_deregister_break_detected_callback(ipmi_sol_conn_t            *conn,
					    ipmi_sol_break_detected_cb cb,
					    void                      *cb_data)
{
    if (locked_list_remove(conn->break_detected_callback_list, cb, cb_data))
	return 0;
    else
	return EINVAL;
}


int
ipmi_sol_register_bmc_transmit_overrun_callback(ipmi_sol_conn_t *conn,
						ipmi_sol_bmc_transmit_overrun_cb cb,
						void *cb_data)
{
    if (locked_list_add(conn->bmc_transmit_overrun_callback_list, cb, cb_data))
	return 0;
    else
	return ENOMEM;
}

int
ipmi_sol_deregister_bmc_transmit_overrun_callback(ipmi_sol_conn_t *conn,
						  ipmi_sol_bmc_transmit_overrun_cb cb,
						  void *cb_data)
{
    if (locked_list_remove(conn->bmc_transmit_overrun_callback_list, cb,
			   cb_data))
	return 0;
    else
	return EINVAL;
}


int
ipmi_sol_register_connection_state_callback(ipmi_sol_conn_t              *conn,
					    ipmi_sol_connection_state_cb cb,
					    void                       *cb_data)
{
    if (locked_list_add(conn->connection_state_callback_list, cb, cb_data))
	return 0;
    else
	return ENOMEM;
}

int
ipmi_sol_deregister_connection_state_callback(ipmi_sol_conn_t         *conn,
					      ipmi_sol_connection_state_cb cb,
					      void                    *cb_data)
{
    if (locked_list_remove(conn->connection_state_callback_list, cb, cb_data))
	return 0;
    else
	return EINVAL;
}


void
ipmi_sol_set_ACK_timeout(ipmi_sol_conn_t *conn, int timeout_usec)
{
    conn->ACK_timeout_usec = timeout_usec;
}

int
ipmi_sol_get_ACK_timeout(ipmi_sol_conn_t *conn)
{
    return conn->ACK_timeout_usec;
}

void
ipmi_sol_set_ACK_retries(ipmi_sol_conn_t *conn, int retries)
{
    conn->ACK_retries = retries;
}

int
ipmi_sol_get_ACK_retries(ipmi_sol_conn_t *conn)
{
    return conn->ACK_retries;
}


/******************************************************************************
 * SoL auxiliary payload data configuration
 *
 * These parameters will be set when the payload is activated:
 *	- authentication (enabled, disabled)
 *	- encryption (enabled, disabled)
 *	- shared serial alert behaviour (fail, defer, succeed)
 *	- Deassert DSR/DCD/CTS on connect (enabled, disabled)
 */

int
ipmi_sol_set_use_authentication(ipmi_sol_conn_t *conn,
				int             use_authentication)
{
    if (!conn)
	return EINVAL;

    ipmi_lock(conn->transmitter.packet_lock);
    if (conn->state != ipmi_sol_state_closed) {
	ipmi_unlock(conn->transmitter.packet_lock);
	return EINVAL;
    }

    if (use_authentication)
	conn->auxiliary_payload_data |= IPMI_SOL_AUX_USE_AUTHENTICATION;
    else
	conn->auxiliary_payload_data &= ~IPMI_SOL_AUX_USE_AUTHENTICATION;
    ipmi_unlock(conn->transmitter.packet_lock);
    
    return 0;
}


int
ipmi_sol_get_use_authentication(ipmi_sol_conn_t *conn)
{
    return ((conn->auxiliary_payload_data & IPMI_SOL_AUX_USE_AUTHENTICATION)
	    != 0);
}

int
ipmi_sol_set_use_encryption(ipmi_sol_conn_t *conn, int use_encryption)
{
    if (!conn)
	return EINVAL;

    ipmi_lock(conn->transmitter.packet_lock);
    if (conn->state != ipmi_sol_state_closed) {
	ipmi_unlock(conn->transmitter.packet_lock);
	return EINVAL;
    }

    if (use_encryption)
	conn->auxiliary_payload_data |= IPMI_SOL_AUX_USE_ENCRYPTION;
    else
	conn->auxiliary_payload_data &= ~IPMI_SOL_AUX_USE_ENCRYPTION;
    ipmi_unlock(conn->transmitter.packet_lock);

    return 0;
}

int
ipmi_sol_get_use_encryption(ipmi_sol_conn_t *conn)
{
    return ((conn->auxiliary_payload_data & IPMI_SOL_AUX_USE_ENCRYPTION)
	    != 0);
}


int
ipmi_sol_set_shared_serial_alert_behavior(ipmi_sol_conn_t *conn,
	ipmi_sol_serial_alert_behavior behavior)
{
    if (!conn)
	return EINVAL;

    ipmi_lock(conn->transmitter.packet_lock);
    if (conn->state != ipmi_sol_state_closed) {
	ipmi_unlock(conn->transmitter.packet_lock);
	return EINVAL;
    }

    conn->auxiliary_payload_data
	&= ~(IPMI_SOL_AUX_SHARED_SERIAL_BEHAVIOR_MASK
	     << IPMI_SOL_AUX_SHARED_SERIAL_BEHAVIOR_SHIFT);
    conn->auxiliary_payload_data
	|= behavior << IPMI_SOL_AUX_SHARED_SERIAL_BEHAVIOR_SHIFT;
    ipmi_unlock(conn->transmitter.packet_lock);

    return 0;
}


ipmi_sol_serial_alert_behavior
ipmi_sol_get_shared_serial_alert_behavior(ipmi_sol_conn_t *conn)
{
    return (ipmi_sol_serial_alert_behavior)
	((conn->auxiliary_payload_data
	  >> IPMI_SOL_AUX_SHARED_SERIAL_BEHAVIOR_SHIFT)
	 & IPMI_SOL_AUX_SHARED_SERIAL_BEHAVIOR_MASK);
}

int
ipmi_sol_set_deassert_CTS_DCD_DSR_on_connect(ipmi_sol_conn_t *conn,
					     int             deassert)
{
    if (!conn)
	return EINVAL;

    ipmi_lock(conn->transmitter.packet_lock);
    if (conn->state != ipmi_sol_state_closed) {
	ipmi_unlock(conn->transmitter.packet_lock);
	return EINVAL;
    }

    if (deassert)
	conn->auxiliary_payload_data |= IPMI_SOL_AUX_DEASSERT_HANDSHAKE;
    else
	conn->auxiliary_payload_data &= ~IPMI_SOL_AUX_DEASSERT_HANDSHAKE;
    ipmi_unlock(conn->transmitter.packet_lock);

    return 0;
}


int
ipmi_sol_get_deassert_CTS_DCD_DSR_on_connect(ipmi_sol_conn_t *conn)
{
    return ((conn->auxiliary_payload_data & IPMI_SOL_AUX_DEASSERT_HANDSHAKE)
	    != 0);
}


int ipmi_sol_set_bit_rate(ipmi_sol_conn_t *conn, unsigned char rate)
{
    if (!conn)
	return EINVAL;

    ipmi_lock(conn->transmitter.packet_lock);
    if (conn->state != ipmi_sol_state_closed) {
	ipmi_unlock(conn->transmitter.packet_lock);
	return EINVAL;
    }

    conn->initial_bit_rate = rate;
    ipmi_unlock(conn->transmitter.packet_lock);

    return 0;
}

unsigned char
ipmi_sol_get_bit_rate(ipmi_sol_conn_t *conn)
{
    return conn->initial_bit_rate;
}


static void
do_and_destroy_transmit_complete_callbacks(callback_list_t *list,
					   ipmi_sol_conn_t *conn)
{
    callback_list_t *temp;

    while (NULL != list) {
	((ipmi_sol_transmit_complete_cb)list->cb)(conn, list->error,
						  list->cb_data);
	temp = list;
	list = list->next;
	ipmi_mem_free(temp);
    }
}


/*
 * Handle and packets that are waiting to be processed.
 */
static void
process_waiting_packets(ipmi_sol_conn_t *conn)
{
    while ((conn->waiting_packets) || conn->waiting_callbacks
	   || (conn->waiting_states))
    {
	while (conn->waiting_callbacks) {
	    callback_list_t *callbacks = conn->waiting_callbacks;
	    conn->waiting_callbacks = NULL;
	    ipmi_unlock(conn->transmitter.packet_lock);
	    do_and_destroy_transmit_complete_callbacks(callbacks, conn);
	    ipmi_lock(conn->transmitter.packet_lock);
	}

	if (conn->waiting_states) {
	    sol_state_cb_info_t *state = conn->waiting_states;
	    conn->waiting_states = state->next;
	    ipmi_unlock(conn->transmitter.packet_lock);
	    do_connection_state_callbacks(conn, state->state, state->error);
	    ipmi_mem_free(state);
	    ipmi_lock(conn->transmitter.packet_lock);
	    continue;
	}

	if (conn->waiting_packets) {
	    sol_in_packet_info_t *packet = conn->waiting_packets;
	    unsigned char        *pdata
		= ((unsigned char *) packet) + sizeof(*packet);

	    /* Connection may have closed during reporting information,
	       make sure to check this. */
	    if (conn->state == ipmi_sol_state_closed) {
		ipmi_log(IPMI_LOG_WARNING,
			 "ipmi_sol.c(sol_handle_recv_async): "
			 "Dropped incoming SoL packet: connection closed.");
		while (packet) {
		    sol_in_packet_info_t *npacket = packet->next;
		    ipmi_mem_free(packet);
		    packet = npacket;
		}
		conn->waiting_packets = NULL;
		return;
	    }

	    process_packet(conn, pdata, packet->data_len);
	    ipmi_mem_free(packet);
	    continue;
	}
    }
}

/**
 * Changes the currently recorded "state" for the SoL connection.
 *
 * Does nothing if the currently recorded state is the same as the new state.
 *
 * @param [in] conn	The SoL connection
 * @param [in] state	The new connection state
 * @param [in] error	The error value to pass to callbacks that are listening
 *			for connection state changes.
 */
static void
ipmi_sol_set_connection_state(ipmi_sol_conn_t *conn,
			      ipmi_sol_state  new_state,
			      int             error)
{
    if (conn->state == new_state)
	return;

    if (new_state == ipmi_sol_state_closed) {
	transmitter_shutdown(&conn->transmitter, error);
    } else if (((new_state == ipmi_sol_state_connected)
		|| (new_state == ipmi_sol_state_connected_ctu))
	       && (conn->state == ipmi_sol_state_connecting))
    {
	int rv = transmitter_startup(&conn->transmitter);
	if (rv) {
	    new_state = ipmi_sol_state_closed;
	    error = rv;
	}
    }

    conn->state = new_state;

    if (conn->processing_packet) {
	sol_state_cb_info_t *sp = ipmi_mem_alloc(sizeof(*sp));
	if (!sp) {
	    /* Yikes, no memory to store this.  Just log and give up. */
	    ipmi_log(IPMI_LOG_SEVERE,
		     "ipmi_sol.c(ipmi_sol_set_connection_state): "
		     "Could not allocate memory to queue state change.");
	    
	}
	sp->state = new_state;
	sp->error = error;
	sp->next = NULL;
	if (conn->waiting_states) {
	    sol_state_cb_info_t *end = conn->waiting_states;
	    while (end->next)
		end = end->next;
	    end->next = sp;
	} else {
	    conn->waiting_states = sp;
	}
	return;
    }

    conn->processing_packet = 1;
    ipmi_unlock(conn->transmitter.packet_lock);
    do_connection_state_callbacks(conn, new_state, error);
    ipmi_lock(conn->transmitter.packet_lock);

    /* See if some other thread stuck some packets in for me to
       process.  Do that now. */
    process_waiting_packets(conn);

    conn->processing_packet = 0;
}


/*****************************************************************************
 ** IPMI SoL write operations
 **/
 
#ifdef IPMI_SOL_DEBUG_TRANSMIT
/**
 * Dump the transmitter state.
 */
static void
dump_transmitter_queue_state(ipmi_sol_transmitter_context_t *transmitter)
{
    /* DEBUG: Just dump the queue! */
    ipmi_lock(transmitter->queue_lock);
    printf("Outgoing queue: 0x%p\n", &transmitter->outgoing_queue);
    if (!transmitter->outgoing_queue)
	return;

    printf("   head: 0x%p\n"
	   "   tail: 0x%p\n", transmitter->outgoing_queue.head,
	   transmitter->outgoing_queue.tail
	   );
	
    printf("vvvvv Outgoing queue:\n");
    if (transmitter->outgoing_queue.head) {
	ipmi_sol_outgoing_queue_item_t *i = transmitter->outgoing_queue.head;
	while (i) {
	    printf("%p -> %d chars at %p -> [", i, i->data_len, i->data);
	    fflush(stdout);
	    int j;
	    for (j = 0; j < i->data_len; ++j)
		printf("%c", i->data[j]);
	    printf("]\n"); fflush(stdout);
	    i = i->next;
	}
    }
    else
	printf("is empty.");

    ipmi_unlock(transmitter->queue_lock);

    printf("^^^^^ Outgoing queue\n\n"); fflush(stdout);
}
#endif

static ipmi_sol_outgoing_packet_record_t *
transmitter_gather(ipmi_sol_transmitter_context_t *transmitter,
		   int                            control_only)
{
    int data_len = 0;
    unsigned char *ptr = &transmitter->scratch_area[0];
    ipmi_sol_outgoing_packet_record_t *new_packet_record = NULL;
    ipmi_sol_outgoing_queue_item_t *qi = transmitter->outgoing_queue.head;
    unsigned char ib_op = 0;
    unsigned int already_acked = transmitter->bytes_acked_at_head;

    if ((control_only || !qi)
	&& !transmitter->packet_to_acknowledge
	&& !transmitter->op_callback_list)
	/*
	 * Absolutely nothing to transmit.
	 */
	return NULL;

    if (!control_only) {
	/*
	 * Do the data gather into the transmitter scratch area
	 */
	while (qi && (data_len < transmitter->scratch_area_size)) {
	    /*
	     * Is this queue item a break?
	     */
	    if (0 == qi->data_len) {
		/*
		 * It's a break... can we add it to this packet?
		 */
		if (0 == data_len)
		    ib_op |= IPMI_SOL_OPERATION_GENERATE_BREAK;
		else
		    /*
		     * The data packet endeth here, if we want the break
		     * to occur at the right time (and we do...).
		     */
		    break;
	    } else {
		/*
		 * Data in this qi: Figure out how many chars we are going to
		 * copy.  Skip any bytes that the BMC has already ACKed,
		 * then limit our copy to fit within the buffer space.
		 */
		int copychars = qi->data_len - already_acked;
		if (copychars > transmitter->scratch_area_size - data_len)
		    copychars = transmitter->scratch_area_size - data_len;

		memcpy(ptr, &qi->data[already_acked], copychars);
		ptr += copychars;
		data_len += copychars;
		already_acked = 0;
	    }
	    qi = qi->next;
	}
    }
	
    /*
     * There's something there to send. Allocate the structure that
     * holds the packet info
     */
    new_packet_record = ipmi_mem_alloc(sizeof(*new_packet_record));
    if (!new_packet_record)
	return NULL;
    memset(new_packet_record, 0, sizeof(*new_packet_record));

    new_packet_record->packet_size = 4 + data_len;

    new_packet_record->packet = ipmi_mem_alloc(new_packet_record->packet_size);
    if (!new_packet_record->packet) {
	ipmi_mem_free(new_packet_record);
	return NULL;
    }

    /*
     * Put the control and ack/nack information into the packet
     */
    new_packet_record->packet[PACKET_ACK_NACK_SEQNR]
	= transmitter->packet_to_acknowledge;
    if (! (transmitter->oob_transient_op & IPMI_SOL_OPERATION_NACK_PACKET))
	/* We have to ack the packet if we nack it, leave it around
	   for the release if we are nack-ing. */
	transmitter->packet_to_acknowledge = 0;

    new_packet_record->packet[PACKET_ACCEPTED_CHARACTER_COUNT]
	= transmitter->accepted_character_count;
    transmitter->accepted_character_count = 0;

    ipmi_lock(transmitter->oob_op_lock);
    new_packet_record->packet[PACKET_OP]
	= (transmitter->oob_transient_op | transmitter->oob_persistent_op
	   | ib_op);

    /* Transmitted NACK has to be cleared by the user */
    transmitter->oob_transient_op &= IPMI_SOL_OPERATION_NACK_PACKET;

    new_packet_record->op_callback_list = transmitter->op_callback_list;
    transmitter->op_callback_list = NULL;
    ipmi_unlock(transmitter->oob_op_lock);

    /*
     * Note here that control_only implies data_len==0.
     */
    new_packet_record->expecting_ACK = (data_len > 0);

    if (new_packet_record->expecting_ACK) {
	/* Data-bearing packet; will be ACKed (hopefully), needs a
	   sequence number and a retransmit count */
	new_packet_record->packet[PACKET_SEQNR]
	    = transmitter->latest_outgoing_seqnr++;
	if (transmitter->latest_outgoing_seqnr > 15)
	    transmitter->latest_outgoing_seqnr = 1;

	new_packet_record->transmit_attempts_remaining
	    = transmitter->sol_conn->ACK_retries;

	/* Give it the data */
	memcpy(&new_packet_record->packet[PACKET_DATA],
	       transmitter->scratch_area,
	       data_len);
    } else {
	/* Zero sequence number for control-only packet */
	new_packet_record->packet[PACKET_SEQNR] = 0;
    }

    new_packet_record->os_hnd = transmitter->sol_conn->ipmi->os_hnd;

    return new_packet_record;
}


static void
do_outstanding_op_callbacks(ipmi_sol_transmitter_context_t *transmitter,
			    int                            error)
{
    callback_list_t *callbacks;
    callback_list_t *end;
    ipmi_sol_conn_t *conn = transmitter->sol_conn;

    callbacks = transmitter->transmitted_packet->op_callback_list;
    if (!callbacks)
	return;
    transmitter->transmitted_packet->op_callback_list = NULL;

    end = callbacks;
    while (!end) {
	end->error = error;
	end = end->next;
    }

    if (conn->processing_packet) {
	if (conn->waiting_callbacks) {
	    end = conn->waiting_callbacks;
	    while (end->next)
		end = end->next;
	    end->next = callbacks;
	} else {
	    conn->waiting_callbacks = callbacks;
	}
	return;
    }

    conn->processing_packet = 1;
    ipmi_unlock(transmitter->packet_lock);
    do_and_destroy_transmit_complete_callbacks(callbacks,
					       transmitter->sol_conn);
    ipmi_lock(transmitter->packet_lock);

    /* See if some other thread stuck some packets in for me to
       process.  Do that now. */
    process_waiting_packets(conn);

    conn->processing_packet = 0;
}


/**
 * Get rid of any packet that we've recently transmitted and still
 * have in storage.
 *
 * MUST be called with the packet_lock held.
 *
 * @param [in] transmitter	The SoL transmitter
 * @param [in] error		The error code to return to any callbacks
 *				waiting on this packet or its data.
 */
static void
dispose_of_outstanding_packet(ipmi_sol_transmitter_context_t *transmitter,
			      int                            error)
{
    os_handler_t *os_hnd;
    int          rv = 0;
    ipmi_sol_outgoing_packet_record_t *packet
	= transmitter->transmitted_packet;

    if (!packet)
	return;
    
    if (packet->ack_timer) {
	os_hnd = transmitter->sol_conn->ipmi->os_hnd;

	ipmi_lock(packet->timer_lock);
	if (packet->timer_running)
	    rv = os_hnd->stop_timer(os_hnd, packet->ack_timer);
	if (! rv) {
	    ipmi_unlock(packet->timer_lock);
	    ipmi_destroy_lock(packet->timer_lock);
	    os_hnd->free_timer(os_hnd, packet->ack_timer);
	} else {
	    /* Tell the timer handler to throw the packet away, since
	       it's about to run. */
	    packet->deleted = 1;
	    ipmi_unlock(packet->timer_lock);
	    packet = NULL;
	}
    }

    do_outstanding_op_callbacks(transmitter, error);

    if (packet) {
	if (packet->packet)
	    ipmi_mem_free(packet->packet);

	ipmi_mem_free(packet);
    }
    transmitter->transmitted_packet = NULL;
}

/*
 * Must be called with the packet lock held.
 */
static int
transmit_outstanding_packet(ipmi_sol_transmitter_context_t *transmitter)
{
    int rv;
    /*
     * Pack the packet into a pseudo-IPMI message.
     */
    ipmi_msg_t msg;
    ipmi_con_option_t options[3];
    int                curr_opt = 0;
    ipmi_sol_conn_t   *conn = transmitter->sol_conn;

    options[curr_opt].option = IPMI_CON_MSG_OPTION_CONF;
    options[curr_opt].ival = ipmi_sol_get_use_encryption(conn);
    curr_opt++;
    options[curr_opt].option = IPMI_CON_MSG_OPTION_AUTH;
    options[curr_opt].ival = ipmi_sol_get_use_authentication(conn);
    curr_opt++;
    options[curr_opt].option = IPMI_CON_OPTION_LIST_END;

    msg.netfn = 1;
    msg.cmd = 0;
    msg.data = (unsigned char *)transmitter->transmitted_packet->packet;
    msg.data_len = transmitter->transmitted_packet->packet_size;

#ifdef IPMI_SOL_DEBUG_TRANSMIT
    printf("Sending a packet! %d bytes: ",
	   transmitter->transmitted_packet->packet_size);
    dump_hex(msg.data, msg.data_len);
    printf("That's it!\n");
    fflush(stdout);
#endif

    /*
     * And fire it off!
     */
    rv = conn->ipmid->send_command_option
	(conn->ipmi,
	 (ipmi_addr_t *)&transmitter->sol_conn->sol_payload_addr,
	 sizeof(transmitter->sol_conn->sol_payload_addr),
	 &msg,
	 options,
	 NULL, NULL);

    if (rv) {
	char buf[50];
	ipmi_log(IPMI_LOG_WARNING, "ipmi_sol.c(transmit_outstanding_packet): "
		 "ipmi_send_command_addr: [%s]",
		 ipmi_get_error_string(rv, buf, 50));
	dispose_of_outstanding_packet(transmitter, rv);
    }
    return rv;
}


/**
 * Handle expiration of the timer for a packet ACK.
 */
static void sol_ACK_timer_expired(void *cb_data, os_hnd_timer_id_t *id);

static int
setup_ACK_timer(ipmi_sol_transmitter_context_t *transmitter)
{
    struct timeval timeout;
    os_handler_t   *os_hnd;
    int            rv;
    ipmi_sol_outgoing_packet_record_t *packet
	= transmitter->transmitted_packet;

    os_hnd = transmitter->sol_conn->ipmi->os_hnd;

    ipmi_lock(packet->timer_lock);
    if (packet->timer_running) {
	ipmi_unlock(packet->timer_lock);
	ipmi_log(IPMI_LOG_WARNING, "ipmi_sol.c(setup_ACK_timer): "
		 "Timer start when timer was already running");
	return 0;
    }
    timeout.tv_sec = transmitter->sol_conn->ACK_timeout_usec / 1000000;
    timeout.tv_usec = transmitter->sol_conn->ACK_timeout_usec % 1000000;

    rv = os_hnd->start_timer(os_hnd,
			     packet->ack_timer,
			     &timeout,
			     sol_ACK_timer_expired,
			     packet);
    if (!rv)
	packet->timer_running = 1;
    ipmi_unlock(packet->timer_lock);
    return rv;
}

static void
sol_ACK_timer_expired(void *cb_data, os_hnd_timer_id_t *id)
{
    ipmi_sol_transmitter_context_t    *transmitter;
    ipmi_sol_conn_t                   *conn;
    ipmi_sol_outgoing_packet_record_t *packet = cb_data;

#ifdef SOL_DEBUG_TRANSMIT
    printf("sol_ACK_timer_expired!\n");
#endif

    ipmi_lock(packet->timer_lock);
    if (packet->deleted) {
	/* Packet was deleted while the timer was going off, just
	   delete and return here. */
	ipmi_unlock(packet->timer_lock);
	if (packet->packet)
	    ipmi_mem_free(packet->packet);
	ipmi_destroy_lock(packet->timer_lock);
	packet->os_hnd->free_timer(packet->os_hnd, packet->ack_timer);
	ipmi_mem_free(packet);
	return;
    }
    packet->timer_running = 0;
    ipmi_unlock(packet->timer_lock);

    /* Get a refcount to the connection. */
    conn = find_sol_connection(packet->conn);
    if (!conn)
	return;

    transmitter = &conn->transmitter;

    ipmi_lock(transmitter->packet_lock);

    if (transmitter->transmitted_packet != packet)
	/* OK, the packet was ACKed, it seems... */
	goto out_unlock;

    packet->transmit_attempts_remaining--;
    if (packet->transmit_attempts_remaining == 0) {
	/*
	 * Didn't get a response even after retries... connection is lost.
	 */
	ipmi_sol_set_connection_state(transmitter->sol_conn,
				      ipmi_sol_state_closed,
				      IPMI_SOL_ERR_VAL(IPMI_SOL_DISCONNECTED));
    } else {
	int rv;

	transmit_outstanding_packet(transmitter);
	rv = setup_ACK_timer(transmitter);
	if (rv) {
	    char buf[50];
	    ipmi_log(IPMI_LOG_WARNING, "ipmi_sol.c(sol_ACK_timer_expired): "
		     "Unable to setup_ACK_timer: %s",
		     ipmi_get_error_string(rv, buf, 50));
	    dispose_of_outstanding_packet(transmitter, rv);
	    /* FIXME: What is the right error value? */
	}
    }
 out_unlock:
    ipmi_unlock(transmitter->packet_lock);
    sol_put_connection(conn);
}


static void transmitter_handle_acknowledge(ipmi_sol_conn_t *conn,
					   int             error,
					   int         cknowledged_char_count);

/**
 * Causes the transmitter to examine its transmit queue and to prepare a packet
 * for transmission if needed.
 *
 * Anything that gives the transmitter something to transmit (data or control)
 * should call this.
 *
 * Must be called with the packet lock held.
 */
static void
transmitter_prod_nolock(ipmi_sol_transmitter_context_t *transmitter)
{
    ipmi_sol_outgoing_packet_record_t *packet;

    /*
     * TODO: If we are awaiting an ACK and we get a data packet from
     * the remote system that doesn't ACK our outstanding packet,
     * perhaps we should be able to send back an ACK (and control)
     * packet immediately???  If so, we should keep going here, and
     * later call transmitter_gather(transmitter, 1) to collect a
     * strictly control-only packet (no BREAKs, either!).
     */
    if (transmitter->transmitted_packet) {
#ifdef IPMI_SOL_DEBUG_TRANSMIT
	ipmi_log(IPMI_LOG_INFO, "ipmi_sol.c(transmitter_prod_nolock): "
		 "exiting early:"
		 " already waiting for an ACK.");
#endif
	return;
    }

#ifdef IPMI_SOL_DEBUG_TRANSMIT
    dump_transmitter_queue_state(transmitter);
#endif

    /* TODO: PREFERABLY this should happen next time we enter the event 
     * loop,  not right away! This will allow for control and character
     * accumulation in a reasonable way. Think Nagling (!TCP_NODELAY). */

    if (! transmitter->transmitted_packet)
	transmitter->transmitted_packet = transmitter_gather(transmitter, 0);
    packet = transmitter->transmitted_packet;

    if (packet) {
	int rv = 0;

	if (packet->expecting_ACK) {
	    os_handler_t *os_hnd = transmitter->sol_conn->ipmi->os_hnd;
    
	    packet->conn = transmitter->sol_conn;
	    rv = ipmi_create_lock_os_hnd(os_hnd, &packet->timer_lock);
	    if (rv)
		goto handle_err;
	    rv = os_hnd->alloc_timer(os_hnd, &packet->ack_timer);
	    if (rv) {
		ipmi_destroy_lock(packet->timer_lock);
		goto handle_err;
	    }
	    packet->timer_running = 0;
	}

	if (!rv)
	    rv = transmit_outstanding_packet(transmitter);

    handle_err:
	if (rv) {
	    dispose_of_outstanding_packet(transmitter, rv);
	    return;
	}

	if (transmitter->transmitted_packet->expecting_ACK) {
	    rv = setup_ACK_timer(transmitter);
	    if (rv) {
		char buf[50];
		ipmi_log(IPMI_LOG_WARNING,
			 "ipmi_sol.c(transmitter_prod_nolock): "
			 "Unable to setup_ACK_timer: %s",
			 ipmi_get_error_string(rv, buf, 50));
		dispose_of_outstanding_packet(transmitter, rv);
		/* FIXME: What is the right error value? */
	    }
	} else {
	    /*
	     * This packet won't get an acknowledgement!  Dispose of
	     * it NOW, and do its callbacks.
	     */
	    int errval = IPMI_SOL_ERR_VAL(IPMI_SOL_UNCONFIRMABLE_OPERATION);
	    transmitter_handle_acknowledge(transmitter->sol_conn, errval, 0);
		 
	    dispose_of_outstanding_packet(transmitter, errval);
	}
    }
}

/**
 * Lock and call transmitter_prod_nolock()
 */
static void
transmitter_prod(ipmi_sol_transmitter_context_t *transmitter)
{
    ipmi_lock(transmitter->packet_lock);
    transmitter_prod_nolock(transmitter);
    ipmi_unlock(transmitter->packet_lock);
}

/**
 * Remove a packet from the head of the queue.  This should be called
 * only after an ACK has been received or it has been determined that
 * the packet will never be ACKed.
 */
static void
dequeue_head(ipmi_sol_transmitter_context_t *transmitter, int error)
{
    ipmi_sol_outgoing_queue_item_t *qitem;

    transmitter->bytes_acked_at_head = 0;
    qitem = transmitter->outgoing_queue.head;

    if (qitem) {
	if (qitem->transmit_complete_callback) {
	    ipmi_unlock(transmitter->queue_lock);
	    ipmi_unlock(transmitter->packet_lock);
	    (qitem->transmit_complete_callback)(transmitter->sol_conn,
						error, qitem->cb_data);
	    ipmi_lock(transmitter->packet_lock);
	    ipmi_lock(transmitter->queue_lock);
	}
	if (qitem->data)
	    ipmi_mem_free(qitem->data);
	
	transmitter->outgoing_queue.head = qitem->next;
	ipmi_mem_free(qitem);
	
	/* Deleting the last packet in the list? */
	if (NULL == transmitter->outgoing_queue.head)
	    transmitter->outgoing_queue.tail = NULL;
    }
}


/**
 * Remove all outgoing packets queued on the given transmitter.
 *
 * @param [in] transmitter	The transmitter to be flushed.
 */
static void
transmitter_flush_outbound(ipmi_sol_transmitter_context_t *transmitter,
			   int                            error)
{
    dispose_of_outstanding_packet(transmitter, error);

    ipmi_lock(transmitter->queue_lock);
    while (transmitter->outgoing_queue.head)
	dequeue_head(transmitter, error);
    ipmi_unlock(transmitter->queue_lock);
}


/**
 * Handle the acknowledgement of the given number of characters.
 * Note that acknowledged_character_count might be zero, if and only
 * if we have just sent out a packet with a BREAK and no data (in which
 * case, incidentally, "error" will be IPMI_SOL_UNCONFIRMABLE_OPERATION), and
 * we must dequeue the break only.
 */
static void
transmitter_handle_acknowledge(ipmi_sol_conn_t *conn,
			       int             error,
			       int             acknowledged_char_count)
{
    ipmi_sol_outgoing_queue_item_t *qitem;

    /*
     * Handle the in-band data by iterating through packets, and:
     *	1) Counting off how many bytes of this packet have been ACKed,
     *	2) When a packet is done, calling the IB callbacks, then disposing
     *     of it.
     */
#ifdef IPMI_SOL_DEBUG_RECEIVE
    ipmi_log(IPMI_LOG_INFO, "Received ACK for %d chars",
	     acknowledged_char_count);
#endif

    do {
	int avail_this_pkt;
	int this_ack;

	qitem = conn->transmitter.outgoing_queue.head;
	if (!qitem) {
	    if (acknowledged_char_count) {
		/*
		 * The BMC has acknowledged more than we've sent!
		 */
		ipmi_log(IPMI_LOG_WARNING,
			 "ipmi_sol.c(transmitter_handle_acknowledge): "
			 "The BMC has acknowledged more data than we sent."
			 " Ignoring excess ACK.");
	    }
	    return;
	}

	/*
	 * This will also work for BREAKs, stored as a zero-length transmit
	 * request.  They will be dequeued if they are at the start of the
	 * queue for this ACK.  They will also be dequeued if they are mid-
	 * queue for this ACK, but that SHOULD never occur.  They will
	 * NOT be dequeued if they are directly after the last completely
	 * ACKed request.  This also means that should two BREAKs be queued
	 * consecutively, only the first one will be dequeued if
	 * acknowledged_char_count is zero.
	 */

	avail_this_pkt = (qitem->data_len
			  - conn->transmitter.bytes_acked_at_head);
	if (avail_this_pkt < acknowledged_char_count)
	    this_ack = avail_this_pkt;
	else
	    this_ack = acknowledged_char_count;

	conn->transmitter.bytes_acked_at_head += this_ack;
	if (conn->transmitter.bytes_acked_at_head == qitem->data_len) {
	    /*
	     * This packet is DONE.
	     */
	    ipmi_lock(conn->transmitter.queue_lock);
	    dequeue_head(&conn->transmitter, error);
	    ipmi_unlock(conn->transmitter.queue_lock);
	}

	acknowledged_char_count -= this_ack;
    } while (acknowledged_char_count > 0);

    /*
     * Thus endeth the ACKing game.
     */
}


/**
 * Creates a new transmitter tail packet and adds it to the transmit queue.
 *
 * If count > 0, the given data bytes are added to the queue.  If count == 0,
 * this means a serial "break" to the transmitter.
 */
static int
add_to_transmit_queue(ipmi_sol_transmitter_context_t *tx,
		      const void                     *buf,
		      int                            count,
		      unsigned char                  ib_op,
		      ipmi_sol_transmit_complete_cb  cb,
		      void                           *cb_data)
{
    ipmi_sol_outgoing_queue_item_t *new_tail;

#ifdef IPMI_SOL_DEBUG_TRANSMIT
    dump_transmitter_queue_state(tx);
#endif

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

    if (count) {
	new_tail->data = ipmi_mem_alloc(count);
	if (!new_tail->data) {
	    ipmi_mem_free(new_tail);
	    return ENOMEM;
	}
	
	memcpy(new_tail->data, buf, count);
    } else
	new_tail->data = NULL;

    new_tail->data_len = count;
    new_tail->ib_op = ib_op;
    new_tail->transmit_complete_callback = cb;
    new_tail->cb_data = cb_data;
    new_tail->next = NULL;

    ipmi_lock(tx->queue_lock);

    if (tx->outgoing_queue.tail)
	tx->outgoing_queue.tail->next = new_tail;

    tx->outgoing_queue.tail = new_tail;

    /* Adding to a previously empty list? */
    if (!tx->outgoing_queue.head)
	tx->outgoing_queue.head = new_tail;

    ipmi_unlock(tx->queue_lock);

    transmitter_prod_nolock(tx);

    return 0;
}

/*
 * Must be called with oob_op_lock held.
 */
static int
add_op_control_callback(ipmi_sol_transmitter_context_t *tx, 
			ipmi_sol_transmit_complete_cb  cb,
			void                           *cb_data)
{
    callback_list_t *new_entry;
    callback_list_t *iter = tx->op_callback_list;

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

    new_entry->cb = cb;
    new_entry->cb_data = cb_data;
    new_entry->next = NULL;

    if (!iter) {
	tx->op_callback_list = new_entry;
    } else {
	while (NULL != iter->next)
	    iter = iter->next;

	/*
	 * iter points to the end of the list.
	 */
	iter->next = new_entry;
    }
    return 0;
}

static int
transmitter_startup(ipmi_sol_transmitter_context_t *transmitter)
{
    transmitter->scratch_area = ipmi_mem_alloc(transmitter->scratch_area_size);
    if (!transmitter->scratch_area) {
	/* Alloc failed! */
	ipmi_log(IPMI_LOG_SEVERE, "ipmi_sol.c(transmitter_startup): "
		 "Insufficient memory for transmitter scratch area.");
		
	return ENOMEM;
    }

    return 0;
}

static void
transmitter_shutdown(ipmi_sol_transmitter_context_t *transmitter, int error)
{
    transmitter_flush_outbound(transmitter, error);

    /* Free the memory being used by sundry parts */
    if (transmitter->scratch_area) {
	ipmi_mem_free(transmitter->scratch_area);
	transmitter->scratch_area = NULL;
    }
}


/*
 * ipmi_sol_write -
 *
 * Send a sequence of bytes to the remote.
 *	buf - the bytes to send.
 *	count - the number of bytes to send from the buffer.
 * This function (like all the others!) will either return an ERROR
 * and never call the callback, or will return no error and then WILL
 * call the callback, indicating an error later if necessary.  The
 * callback is an indication that the BMC has ACKed *all* of the bytes
 * in this request.  There is no guarantee that the packet will not be
 * fragmented or coalesced in transmission.
 */
int
ipmi_sol_write(ipmi_sol_conn_t               *conn,
	       const void                    *buf,
	       int                           count,
	       ipmi_sol_transmit_complete_cb cb,
	       void                          *cb_data)
{
    int rv;

    if (count <= 0)
	return EINVAL;

    ipmi_lock(conn->transmitter.packet_lock);
    if ((conn->state == ipmi_sol_state_connected)
	|| (conn->state == ipmi_sol_state_connected_ctu))
    {
	rv = add_to_transmit_queue(&conn->transmitter, buf, count, 0,
				   cb, cb_data);
    } else {
	rv = EINVAL;
    }
    ipmi_unlock(conn->transmitter.packet_lock);
    return rv;
}


/* 
 * ipmi_sol_release_nack -
 *
 * Remove any pending nacks.
 */
int
ipmi_sol_release_nack(ipmi_sol_conn_t *conn)
{
    int rv = 0;

    ipmi_lock(conn->transmitter.packet_lock);
    if (conn->transmitter.in_recv_cb) {
	/* Raced with the receive callback, just mark it for the
	   receive callback to handle. */
	conn->transmitter.nack_count--;
	goto out;
    }
    if (! conn->transmitter.nack_count) {
	/* Nothing to NACK. */
	rv = EINVAL;
	goto out;
    }
    conn->transmitter.nack_count--;
    if (! conn->transmitter.nack_count) {
	/* Time to kick things off again. */
	conn->transmitter.oob_transient_op &= ~IPMI_SOL_OPERATION_NACK_PACKET;

	/* This is here just in case we decide that the accepted
	   character count in a NACK packet is the number of bytes
	   nack-ed. */
	conn->transmitter.accepted_character_count = 0;
	transmitter_prod_nolock(&conn->transmitter);
    }
 out:
    ipmi_unlock(conn->transmitter.packet_lock);
    return rv;
}

/* 
 * ipmi_sol_send_break -
 *
 * See ipmi_sol_write, except we're not sending any bytes, just a
 * serial "break".  Callback contract is the same as for
 * ipmi_sol_write.
 */
int
ipmi_sol_send_break(ipmi_sol_conn_t               *conn,
		    ipmi_sol_transmit_complete_cb cb,
		    void                          *cb_data)
{
    int rv;

    ipmi_lock(conn->transmitter.packet_lock);
    if ((conn->state == ipmi_sol_state_connected)
	|| (conn->state == ipmi_sol_state_connected_ctu))
    {
	rv = add_to_transmit_queue(&conn->transmitter, NULL, 0,
				   IPMI_SOL_OPERATION_GENERATE_BREAK,
				   cb, cb_data);
    } else
	rv = EINVAL;
    ipmi_unlock(conn->transmitter.packet_lock);
    return rv;
}


/* 
 * ipmi_sol_set_CTS_assertable -
 *
 * Asserts CTS at the BMC, to request that the system attached to the
 * BMC ceases transmitting characters.  No guarantee is given that the
 * BMC will honour this request.  Further buffered characters might
 * still be received after CTS is asserted.  See ipmi_sol_write,
 * except we're not sending any bytes, just changing control lines.
 * Callback contract is the same as for ipmi_sol_write.
 */
int
ipmi_sol_set_CTS_assertable(ipmi_sol_conn_t               *conn,
			    int                           assertable,
			    ipmi_sol_transmit_complete_cb cb,
			    void                          *cb_data)
{
    int rv;

    ipmi_lock(conn->transmitter.packet_lock);
    if ((conn->state == ipmi_sol_state_connected)
	|| (conn->state == ipmi_sol_state_connected_ctu))
    {
	ipmi_lock(conn->transmitter.oob_op_lock);
	if (assertable)
	    conn->transmitter.oob_persistent_op
		&= ~IPMI_SOL_OPERATION_CTS_PAUSE;
	else
	    conn->transmitter.oob_persistent_op
		|= IPMI_SOL_OPERATION_CTS_PAUSE;

	rv = add_op_control_callback(&conn->transmitter, cb, cb_data);
	ipmi_unlock(conn->transmitter.oob_op_lock);

	if (!rv)
	    transmitter_prod(&conn->transmitter);
    } else
	rv = EINVAL;
    ipmi_unlock(conn->transmitter.packet_lock);

    return rv;
}


/* 
 * ipmi_sol_set_DCD_DSR_asserted -
 *
 * Asserts DCD and DSR, as if we've answered the phone line.
 * 
 * See ipmi_sol_write, except we're not sending any bytes, just
 * changing control lines.  Callback contract is the same as for
 * ipmi_sol_write.
 */
int
ipmi_sol_set_DCD_DSR_asserted(ipmi_sol_conn_t               *conn,
			      int                           asserted,
			      ipmi_sol_transmit_complete_cb cb,
			      void                          *cb_data)
{
    int rv;

    ipmi_lock(conn->transmitter.packet_lock);
    if ((conn->state == ipmi_sol_state_connected)
	|| (conn->state == ipmi_sol_state_connected_ctu))
    {
	ipmi_lock(conn->transmitter.oob_op_lock);
	if (asserted)
	    conn->transmitter.oob_persistent_op
		&= ~IPMI_SOL_OPERATION_DROP_DCD_DSR;
	else
	    conn->transmitter.oob_persistent_op
		|= IPMI_SOL_OPERATION_DROP_DCD_DSR;

	rv = add_op_control_callback(&conn->transmitter, cb, cb_data);
	ipmi_unlock(conn->transmitter.oob_op_lock);

	if (!rv)
	    transmitter_prod(&conn->transmitter);
    } else
	rv = EINVAL;
    ipmi_unlock(conn->transmitter.packet_lock);

    return rv;
}


/* 
 * ipmi_sol_set_RI_asserted -
 *
 * Asserts RI, as if the phone line is ringing.
 * 
 * See ipmi_sol_write, except we're not sending any bytes, just
 * changing control lines.  Callback contract is the same as for
 * ipmi_sol_write.
 */
int
ipmi_sol_set_RI_asserted(ipmi_sol_conn_t               *conn,
			 int                           asserted,
			 ipmi_sol_transmit_complete_cb cb,
			 void                          *cb_data)
{
    int rv;

    ipmi_lock(conn->transmitter.packet_lock);
    if ((conn->state == ipmi_sol_state_connected)
	|| (conn->state == ipmi_sol_state_connected_ctu))
    {
	ipmi_lock(conn->transmitter.oob_op_lock);
	if (asserted)
	    conn->transmitter.oob_persistent_op
		|= IPMI_SOL_OPERATION_RING_REQUEST;
	else
	    conn->transmitter.oob_persistent_op
		&= ~IPMI_SOL_OPERATION_RING_REQUEST;

	rv = add_op_control_callback(&conn->transmitter, cb, cb_data);
	ipmi_unlock(conn->transmitter.oob_op_lock);

	if (!rv)
	    transmitter_prod(&conn->transmitter);
    } else
	rv = EINVAL;
    ipmi_unlock(conn->transmitter.packet_lock);

    return rv;
}


/**
 * Requests a flush of the transmit queue(s) identified by
 * queue_selector, which is a bitwise-OR of the following:
 *
 *	IPMI_SOL_BMC_TRANSMIT_QUEUE
 *	IPMI_SOL_BMC_RECEIVE_QUEUE
 *	IPMI_SOL_MANAGEMENT_CONSOLE_TRANSMIT_QUEUE
 *	IPMI_SOL_MANAGEMENT_CONSOLE_RECEIVE_QUEUE
 *
 * This operation will never use the callback if it returns an error.
 * 
 * If no error is returned, the callback will be called in a
 * synchronous fashion if it does not involve the BMC, asynchronous
 * otherwise.
 */

typedef struct ipmi_sol_flush_data_s {
    ipmi_sol_conn_t *conn;
    int selectors_flushed;
    int selectors_pending;
    ipmi_sol_flush_complete_cb cb;
    void *cb_data;
} ipmi_sol_flush_data_t;


static void
flush_finalize(ipmi_sol_conn_t *conn, int error, void *cb_data)
{
    ipmi_sol_flush_data_t *my = (ipmi_sol_flush_data_t *)cb_data;

    /*
     * Did the remote flush go OK?
     */
    if (!error) {
	/*
	 * Yep, the BMC has confirmed that the data has been flushed (or
	 * at least no error has occurred).
	 */
	my->selectors_flushed |= my->selectors_pending;
    }

    if (my->cb)
	my->cb(conn, error, my->selectors_flushed, my->cb_data);

    ipmi_mem_free(cb_data);
}


int
ipmi_sol_flush(ipmi_sol_conn_t            *conn,
	       int                        queue_selectors,
	       ipmi_sol_flush_complete_cb cb,
	       void                       *cb_data)
{
    int rv = 0;
    int need_callback = 0;

    ipmi_lock(conn->transmitter.packet_lock);
    if ((conn->state != ipmi_sol_state_connected)
	&& (conn->state != ipmi_sol_state_connected_ctu))
    {
	ipmi_unlock(conn->transmitter.packet_lock);
	return EINVAL;
    }

    /*
     * Do we flush the local transmit queue?
     */
    if (!rv
	&& (! (queue_selectors & IPMI_SOL_MANAGEMENT_CONSOLE_TRANSMIT_QUEUE)))
    {
	transmitter_flush_outbound(&conn->transmitter,
				   IPMI_SOL_ERR_VAL(IPMI_SOL_FLUSHED));
    }

    /*
     * Do we flush the local receive queue?
     */
    if (!rv
	&& (! (queue_selectors & IPMI_SOL_MANAGEMENT_CONSOLE_RECEIVE_QUEUE)))
    {
	/* We don't HAVE a local RX queue... */
	/*VOID*/
    }

    ipmi_lock(conn->transmitter.oob_op_lock);
    /*
     * Do we flush the remote transmit queue?
     */
    if (!rv && (! (queue_selectors & IPMI_SOL_BMC_TRANSMIT_QUEUE))) {
	conn->transmitter.oob_transient_op
	    |= IPMI_SOL_OPERATION_FLUSH_BMC_TO_CONSOLE;
	need_callback = 1;
    }

    /*
     * Do we flush the remote receive queue?
     */
    if (!rv && (! (queue_selectors & IPMI_SOL_BMC_RECEIVE_QUEUE))) {
	conn->transmitter.oob_transient_op
	    |= IPMI_SOL_OPERATION_FLUSH_CONSOLE_TO_BMC;
	need_callback = 1;
    }

    if (need_callback) {
	ipmi_sol_flush_data_t *flush_data;

	flush_data = ipmi_mem_alloc(sizeof(*flush_data));

	flush_data->cb = cb;
	flush_data->cb_data = cb_data;

	/* FIXME - the below two had &&, not &.  I assumed that was wrong. */
	flush_data->selectors_flushed
	    = queue_selectors & IPMI_SOL_MANAGEMENT_CONSOLE_QUEUES;
	flush_data->selectors_pending = queue_selectors & IPMI_SOL_BMC_QUEUES;

	rv = add_op_control_callback(&conn->transmitter, flush_finalize,
				     flush_data);
	ipmi_unlock(conn->transmitter.oob_op_lock);

	transmitter_prod(&conn->transmitter);
    } else {
	ipmi_unlock(conn->transmitter.oob_op_lock);
    }

    ipmi_unlock(conn->transmitter.packet_lock);
    return rv;
}


/********************************************************
 ** IPMI SoL API
 *******************************************************/


/**
 * Constructs a handle for managing an SoL session.
 *
 * This function does NOT communicate with the BMC or activate the SoL payload.
 *
 * @param [in] ipmi	the existing IPMI over LAN session.
 * @param [out] sol_conn	the address into which to store the allocated
 *				IPMI SoL connection structure.
 * @return	zero on success, or ENOMEM if memory allocation fails.
 */
int
ipmi_sol_create(ipmi_con_t      *ipmi,
		ipmi_sol_conn_t **sol_conn)
{
    ipmi_sol_conn_t                *new_conn;
    os_handler_t                   *os_hnd = ipmi->os_hnd;
    ipmi_sol_transmitter_context_t *xmitter;
    int                            rv;

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

    memset(new_conn, 0, sizeof(*new_conn));

    new_conn->refcount = 1;

    xmitter = &new_conn->transmitter;

    /* Enable authentication and encryption by default. */
    new_conn->auxiliary_payload_data = (IPMI_SOL_AUX_USE_ENCRYPTION
					| IPMI_SOL_AUX_USE_AUTHENTICATION);

    rv = ipmi_create_lock_os_hnd(os_hnd, &xmitter->packet_lock);
    if (rv)
	goto out_err;

    rv = ipmi_create_lock_os_hnd(os_hnd, &xmitter->queue_lock);
    if (rv)
	goto out_err;

    rv = ipmi_create_lock_os_hnd(os_hnd, &xmitter->oob_op_lock);
    if (rv)
	goto out_err;

    new_conn->ipmi = ipmi;
    new_conn->data_received_callback_list = locked_list_alloc(os_hnd);
    if (! new_conn->data_received_callback_list) {
	rv = ENOMEM;
	goto out_err;
    }
    new_conn->break_detected_callback_list = locked_list_alloc(os_hnd);
    if (! new_conn->break_detected_callback_list) {
	rv = ENOMEM;
	goto out_err;
    }
    new_conn->bmc_transmit_overrun_callback_list = locked_list_alloc(os_hnd);
    if (! new_conn->bmc_transmit_overrun_callback_list) {
	rv = ENOMEM;
	goto out_err;
    }
    new_conn->connection_state_callback_list = locked_list_alloc(os_hnd);
    if (! new_conn->connection_state_callback_list) {
	rv = ENOMEM;
	goto out_err;
    }

    new_conn->prev_received_seqnr = 0;
    new_conn->prev_character_count = 0;

    new_conn->state = ipmi_sol_state_closed;
    new_conn->try_fast_connect = 1;

    xmitter->sol_conn = new_conn;
    xmitter->transmitted_packet = NULL;
    xmitter->latest_outgoing_seqnr = 1;

    new_conn->ACK_retries = 10;
    new_conn->ACK_timeout_usec = 1000000;

    rv = add_connection(new_conn);
    if (rv)
	goto out_err;

    *sol_conn = new_conn;

    return 0;

 out_err:
    if (xmitter->packet_lock)
	ipmi_destroy_lock(xmitter->packet_lock);
    if (xmitter->queue_lock)
	ipmi_destroy_lock(xmitter->queue_lock);
    if (xmitter->oob_op_lock)
	ipmi_destroy_lock(xmitter->oob_op_lock);
    if (new_conn->data_received_callback_list)
	locked_list_destroy(new_conn->data_received_callback_list);
    if (new_conn->break_detected_callback_list)
	locked_list_destroy(new_conn->break_detected_callback_list);
    if (new_conn->bmc_transmit_overrun_callback_list)
	locked_list_destroy(new_conn->bmc_transmit_overrun_callback_list);
    if (new_conn->connection_state_callback_list)
	locked_list_destroy(new_conn->connection_state_callback_list);
    ipmi_mem_free(new_conn);
    return rv;
}


/**
 * Figure out the "correct" maximum payload size.  This should *never*
 * be larger than 259 (0x0103) due to the constraint of the 8-bit
 * Accepted Character Count field plus the 4-byte payload header.
 * Some manufacturers (who shall remain nameless) have wrong-endianed
 * the maximum payload size fields, so we have to figure out which way
 * around they should be.  b1 and b2 are in the order they are in the
 * packet. They should be little-endian, so we try that first.
 */
static int
get_sane_payload_size(int b1, int b2)
{
    int result = (b2 << 8) + b1;
    if ((result > 0x0103) || (result < 5)) {
	result = (b1 << 8) + b2;
	if ((result > 0x0103) || (result < 5)) {
	    ipmi_log(IPMI_LOG_WARNING, "ipmi_sol.c(get_sane_payload_size): "
		     "BMC did not supply a sensible buffer size"
		     " (0x%02x, 0x%02x). Defaulting to 16.",
		     b1, b2);
	    result = 0x10; /* 16 bytes should be a safe buffer size. */
	} else
	    ipmi_log(IPMI_LOG_INFO, "ipmi_sol.c(get_sane_payload_size): "
		     "BMC sent a byte-swapped buffer size (%d bytes)."
		     " Using %d bytes.", (b2 << 8) + b1, result);
    }
    return result;
}

static void
finish_activate_payload(ipmi_sol_conn_t *conn)
{
    if (conn->max_outbound_payload_size > IPMI_SOL_MAX_DATA_SIZE)
	conn->transmitter.scratch_area_size = IPMI_SOL_MAX_DATA_SIZE;
    else
	conn->transmitter.scratch_area_size = conn->max_outbound_payload_size;

    ipmi_log(IPMI_LOG_INFO,
	     "ipmi_sol.c(handle_active_payload_response): "
	     "Connected to BMC SoL through port %d.",
	     /*		conn->hostname,*/
	     conn->payload_port_number);

#ifdef IPMI_SOL_VERBOSE
    ipmi_log(IPMI_LOG_INFO,
	     "ipmi_sol.c(handle_active_payload_response): "
	     "BMC requested transmit limit %d bytes, receive limit %d bytes.",
	     conn->max_outbound_payload_size,
	     conn->max_inbound_payload_size);

    if (conn->max_outbound_payload_size > conn->transmitter.scratch_area_size)
	ipmi_log(IPMI_LOG_WARNING,
		 "ipmi_sol.c(handle_active_payload_response): "
		 "Limiting transmit to %d bytes.",
		 conn->transmitter.scratch_area_size);
#endif

    /*
     * Set the hardware handshaking bits to match the "holdoff" option...
     */
    ipmi_lock(conn->transmitter.oob_op_lock);
    if (conn->auxiliary_payload_data & IPMI_SOL_AUX_DEASSERT_HANDSHAKE)
	conn->transmitter.oob_persistent_op
	    |= (IPMI_SOL_OPERATION_CTS_PAUSE
		| IPMI_SOL_OPERATION_DROP_DCD_DSR);
    else
	conn->transmitter.oob_persistent_op
	    &= ~(IPMI_SOL_OPERATION_CTS_PAUSE
		 | IPMI_SOL_OPERATION_DROP_DCD_DSR);
    ipmi_unlock(conn->transmitter.oob_op_lock);

    /*
     * And officially bring the connection "up"!
     */
    ipmi_sol_set_connection_state(conn, ipmi_sol_state_connected, 0);
}

static void ipmid_changed(ipmi_con_t   *ipmid,
			  int          err,
			  unsigned int port_num,
			  int          any_port_up,
			  void         *cb_data)
{
    ipmi_sol_conn_t *conn = cb_data;

    ipmi_lock(conn->transmitter.packet_lock);
    if (err) {
	ipmi_log(IPMI_LOG_SEVERE,
		 "ipmi_sol.c(handle_active_payload_response): "
		 "Error setting up new port: %d", err);
	goto out_err;
    }

    finish_activate_payload(conn);
    ipmi_unlock(conn->transmitter.packet_lock);
    return;

 out_err:
    send_close(conn, NULL); 
    ipmi_sol_set_connection_state(conn, ipmi_sol_state_closed, err);
    ipmi_unlock(conn->transmitter.packet_lock);
}

/*
 * Create a new IPMI connection to the BMC on the port specified in
 * the payload port number.
 */
static int
setup_new_ipmi(ipmi_sol_conn_t *conn)
{
    ipmi_args_t *args;
    int         rv;
    char        pname[20];

    ipmi_log(IPMI_LOG_INFO,
	     "ipmi_sol.c(setup_new_ipmi): "
	     "Setting up new IPMI connection to port %d.",
	     conn->payload_port_number);

    if (!conn->ipmi->get_startup_args) {
	ipmi_log(IPMI_LOG_SEVERE,
		 "ipmi_sol.c(handle_active_payload_response): "
		 "Required a new port, but connection doesn't support "
		 "fetching arguments.");
	return ENOSYS;
    }

    args = conn->ipmi->get_startup_args(conn->ipmi);
    if (!args) {
	ipmi_log(IPMI_LOG_SEVERE,
		 "ipmi_sol.c(handle_active_payload_response): "
		 "Unable to get arguments from the IPMI connection.");
	return ENOMEM;
    }

    snprintf(pname, sizeof(pname), "%d", conn->payload_port_number);
    rv = ipmi_args_set_val(args, -1, "Port", pname);
    if (rv) {
	ipmi_log(IPMI_LOG_SEVERE,
		 "ipmi_sol.c(handle_active_payload_response): "
		 "Error setting port argument: %d.", rv);
	return rv;
    }

    rv = ipmi_args_setup_con(args, conn->ipmi->os_hnd, NULL, &conn->ipmid);
    if (rv) {
	ipmi_log(IPMI_LOG_SEVERE,
		 "ipmi_sol.c(handle_active_payload_response): "
		 "Error setting up new connection: %d.", rv);
	return rv;
    }
    ipmi_free_args(args);

    rv = conn->ipmid->add_con_change_handler(conn->ipmid, ipmid_changed, conn);
    if (rv) {
	ipmi_log(IPMI_LOG_SEVERE,
		 "ipmi_sol.c(handle_active_payload_response): "
		 "Error adding connection change handler: %d.", rv);
	return rv;
    }

    rv = conn->ipmid->start_con(conn->ipmid);
    if (rv) {
	ipmi_log(IPMI_LOG_SEVERE,
		 "ipmi_sol.c(handle_active_payload_response): "
		 "Error starting secondary connection: %d.", rv);
	return rv;
    }

    return 0;
}

static void
handle_activate_payload_response(ipmi_sol_conn_t *conn,
				 ipmi_msg_t      *msg_in)
{
    /*
     * Did it work?
     */
    if (msg_in->data_len != 13) {
	if (msg_in->data_len != 1) {
	    ipmi_log(IPMI_LOG_WARNING,
		     "ipmi_sol.c(handle_active_payload_response): "
		     "Received %d bytes... was expecting 13 bytes.\n",
		     msg_in->data_len);
	    dump_hex(msg_in->data, msg_in->data_len);
	}

	if (msg_in->data_len > 0)
	    ipmi_sol_set_connection_state(conn, ipmi_sol_state_closed,
					  IPMI_IPMI_ERR_VAL(msg_in->data[0]));
	else
	    ipmi_sol_set_connection_state
		(conn, ipmi_sol_state_closed,
		 IPMI_SOL_ERR_VAL(IPMI_SOL_NOT_AVAILABLE));
	return;
    }

    if (msg_in->data[0] != 0x00) {
	ipmi_log(IPMI_LOG_SEVERE,
		 "ipmi_sol.c(handle_active_payload_response): "
		 "Activate payload failed.");
	ipmi_sol_set_connection_state(conn, ipmi_sol_state_closed,
				      IPMI_IPMI_ERR_VAL(msg_in->data[0]));
	return;
    }

    /* Recover payload sizes that might be wrong-endianed... */

    /* outbound from here->BMC */
    conn->max_outbound_payload_size
	= get_sane_payload_size(msg_in->data[5], msg_in->data[6]);

    /* inbound from BMC->here */
    conn->max_inbound_payload_size
	= get_sane_payload_size(msg_in->data[7], msg_in->data[8]);

    conn->payload_port_number = (msg_in->data[10] << 8) + msg_in->data[9];
    if (conn->payload_port_number == 28418) {
	/* Bad byte-swapping */
	ipmi_log(IPMI_LOG_WARNING,
		 "ipmi_sol.c(handle_active_payload_response): "
		 "Got a badly byte-swapped UDP port, most likely.  Setting"
		 " it to the proper value.");
	conn->payload_port_number = IPMI_LAN_STD_PORT;
    }

    if (conn->payload_port_number != IPMI_LAN_STD_PORT) {
	int rv = setup_new_ipmi(conn);
	if (rv) {
	    send_close(conn, NULL); 
	    ipmi_sol_set_connection_state(conn, ipmi_sol_state_closed, rv);
	}
    } else {
	conn->ipmid = conn->ipmi;
	finish_activate_payload(conn);
    }
}

static int
send_activate_payload(ipmi_sol_conn_t *conn)
{
    ipmi_msg_t    msg_out;
    unsigned char data[6];
	
    /*
     * Send an Activate Payload command
     */
    msg_out.data_len = 6;
    msg_out.data = data;

    msg_out.data[0] = IPMI_RMCPP_PAYLOAD_TYPE_SOL & 0x3f; /* payload type */
    msg_out.data[1] = conn->payload_instance; /* payload instance number */
    /* NOTE: Can't connect to an Intel AXXIMMADV with the
       "Serial/Modem alerts fail" option, it seems. */

    /* enc, auth, Serial alerts behavior, deassert CTS and DCD/DSR */
    msg_out.data[2] = conn->auxiliary_payload_data;
    msg_out.data[3] = 0x00;
    msg_out.data[4] = 0x00;
    msg_out.data[5] = 0x00;

    msg_out.netfn = IPMI_APP_NETFN;
    msg_out.cmd = IPMI_ACTIVATE_PAYLOAD_CMD;
    return send_message(conn, &msg_out,
			handle_activate_payload_response);
}


static void
handle_set_volatile_bitrate_response(ipmi_sol_conn_t *conn,
				     ipmi_msg_t      *msg_in)
{
    if (msg_in->data_len != 1) {
	ipmi_log(IPMI_LOG_WARNING,
		 "ipmi_sol.c(handle_set_volatile_bitrate_response): "
		 "Received %d bytes... was expecting 1 byte.\n",
		 msg_in->data_len);
	dump_hex(msg_in->data, msg_in->data_len);

	if (msg_in->data_len > 0)
	    ipmi_sol_set_connection_state(conn, ipmi_sol_state_closed,
					  IPMI_IPMI_ERR_VAL(msg_in->data[0]));
	else
	    ipmi_sol_set_connection_state
		(conn, ipmi_sol_state_closed,
		 IPMI_SOL_ERR_VAL(IPMI_SOL_NOT_AVAILABLE));
	return;
    }

    if (msg_in->data[0] != 0x00) {
	ipmi_log(IPMI_LOG_SEVERE,
		 "ipmi_sol.c(handle_set_volatile_bitrate_response): "
		 "Set SoL configuration[Volatile bit rate] failed.");
	ipmi_sol_set_connection_state(conn, ipmi_sol_state_closed,
				      IPMI_IPMI_ERR_VAL(msg_in->data[0]));
	return;
    }

#ifdef IPMI_SOL_VERBOSE
    ipmi_log(IPMI_LOG_INFO,
	     "ipmi_sol.c(handle_set_volatile_bitrate_response): "
	     "Volatile bit rate set.");
#endif
    send_activate_payload(conn);
}

static int
send_set_volatile_bitrate(ipmi_sol_conn_t *conn)
{
    ipmi_msg_t    msg_out;
    unsigned char data[3];
    /*
     * Send a Set SoL Configuration command
     */
    msg_out.data_len = 3;
    msg_out.data = data;
    msg_out.data[0] = IPMI_SELF_CHANNEL; /* own channel, set param */
    msg_out.data[1] = 6; /* parameter selector = SOL volatile bit rate */
    msg_out.data[2] = conn->initial_bit_rate;
	
    msg_out.netfn = IPMI_TRANSPORT_NETFN;
    msg_out.cmd = IPMI_SET_SOL_CONFIGURATION_PARAMETERS;

    return send_message(conn, &msg_out,
			handle_set_volatile_bitrate_response);
}

static void
handle_get_payload_activation_status_response(ipmi_sol_conn_t *conn,
					      ipmi_msg_t      *msg_in)
{
    int count = 0, found, max, byte, index;

    if (msg_in->data_len != 4) {
	ipmi_log(IPMI_LOG_SEVERE,
		 "ipmi_sol.c(handle_get_payload_activation_status_response): "
		 "Get Payload Activation Status command failed.");
	if (msg_in->data_len > 0)
	    ipmi_sol_set_connection_state(conn, ipmi_sol_state_closed,
					  IPMI_IPMI_ERR_VAL(msg_in->data[0]));
	else
	    ipmi_sol_set_connection_state
		(conn, ipmi_sol_state_closed,
		 IPMI_SOL_ERR_VAL(IPMI_SOL_NOT_AVAILABLE));
	return;
    }

    found = 0;
    for (byte = 0; byte <= 1; byte++) {
	for (index = 0; index < 7; index++) {
	    if (msg_in->data[2 + byte] & (1 << index)) {
		/* This payload instance slot is in use */
		count++;
	    } else if (!found) {
		found = 1;
		conn->payload_instance = 8 * byte + index + 1;
	    }
	}
    }

    max = msg_in->data[1] & 0x0f;

#ifdef IPMI_SOL_VERBOSE
    ipmi_log(IPMI_LOG_INFO,
	     "ipmi_sol.c(handle_get_payload_activation_status_response): "
	     "BMC currently using %d SoL payload instances; limit is %d.",
	     count, max);
#endif

    if (!found || (count >= max)) {
	ipmi_log(IPMI_LOG_SEVERE,
		 "ipmi_sol.c(handle_get_payload_activation_status_response): "
		 "BMC can't accept any more SoL sessions.");
	ipmi_sol_set_connection_state
	    (conn, ipmi_sol_state_closed,
	     IPMI_RMCPP_ERR_VAL(IPMI_RMCPP_INVALID_PAYLOAD_TYPE));
	return;
    }
#ifdef IPMI_SOL_VERBOSE
    ipmi_log(IPMI_LOG_INFO,
	     "ipmi_sol.c(handle_get_payload_activation_status_response): "
	     "SoL sessions are available; Using instance slot %d.",
	     conn->payload_instance);
#endif

    if (conn->initial_bit_rate)
	send_set_volatile_bitrate(conn);
    else
	send_activate_payload(conn);
}

static int
send_get_payload_activation_status_command(ipmi_sol_conn_t *conn)
{
    ipmi_msg_t    msg_out;
    unsigned char data[1];

    /*
     * Send a Get Payload Activation Status command
     */
    msg_out.data_len = 1;
    msg_out.data = data;

    msg_out.data[0] = IPMI_RMCPP_PAYLOAD_TYPE_SOL; /* Payload type */

    msg_out.netfn = IPMI_APP_NETFN;
    msg_out.cmd = IPMI_GET_PAYLOAD_ACTIVATION_STATUS_CMD;

    return send_message(conn, &msg_out,
			handle_get_payload_activation_status_response);
}


static void
handle_session_info_response(ipmi_sol_conn_t *conn,
			     ipmi_msg_t      *msg_in)
{
#ifdef IPMI_SOL_VERBOSE
    char *privilege_level[16] = {
	"Unknown", "Callback", "User", "Operator", "Administrator",
	"OEM Proprietary", "Unknown", "Unknown", "Unknown", "Unknown",
	"Unknown", "Unknown", "Unknown", "Unknown", "Unknown", "Unknown"};
#endif

    if (msg_in->data_len < 7) {
	ipmi_log(IPMI_LOG_SEVERE,
		 "ipmi_sol.c(handle_session_info_response): "
		 "Get Session Info command failed.");
	if (msg_in->data_len > 0)
	    ipmi_sol_set_connection_state(conn, ipmi_sol_state_closed,
					  IPMI_IPMI_ERR_VAL(msg_in->data[0]));
	else
	    ipmi_sol_set_connection_state
		(conn, ipmi_sol_state_closed,
		 IPMI_SOL_ERR_VAL(IPMI_SOL_NOT_AVAILABLE));

	return;
    }

#ifdef IPMI_SOL_VERBOSE
    ipmi_log(IPMI_LOG_INFO,
	     "ipmi_sol.c(handle_session_info_response): "
	     "This session handle: 0x%02x"
	     "  BMC currently using %d of %d sessions",
	     msg_in->data[1], msg_in->data[3], msg_in->data[2]);
    ipmi_log(IPMI_LOG_INFO,
	     "ipmi_sol.c(handle_session_info_response): "
	     "Current UserID: 0x%02x (%s)  Channel number: 0x%02x",
	     msg_in->data[4] & 0x3f, privilege_level[msg_in->data[5] & 0x0f],
	     msg_in->data[6] & 0x0f);
#endif
    send_get_payload_activation_status_command(conn);
}

static int
send_get_session_info(ipmi_sol_conn_t *conn)
{
    /*
     * Send a Get Session Info command (gives us our User ID, among
     * other things)
     */
    ipmi_msg_t    msg_out;
    unsigned char data[1];

    msg_out.data_len = 1;
    msg_out.data = data;

    msg_out.data[0] = 0x00; /* current session */

    msg_out.netfn = IPMI_APP_NETFN;
    msg_out.cmd = IPMI_GET_SESSION_INFO_CMD;

    return send_message(conn, &msg_out, handle_session_info_response);
}

static void
handle_commit_write_response(ipmi_sol_conn_t *conn,
			     ipmi_msg_t      *msg_in)
{
    send_get_session_info(conn);
}

static int
send_commit_write(ipmi_sol_conn_t *conn)
{
    ipmi_msg_t    msg_out;
    unsigned char data[3];

    msg_out.data_len = 3;
    msg_out.data = data;

    /* own channel, get param (not just version) */
    msg_out.data[0] = IPMI_SELF_CHANNEL;
    msg_out.data[1] = 0; /* parameter selector = Set In Progress */
    msg_out.data[2] = 0; /* Commit write */
	
    msg_out.netfn = IPMI_TRANSPORT_NETFN;
    msg_out.cmd = IPMI_SET_SOL_CONFIGURATION_PARAMETERS;

    return send_message(conn, &msg_out, handle_commit_write_response);
}

static void
handle_set_sol_enabled_response(ipmi_sol_conn_t *conn,
				ipmi_msg_t      *msg_in)
{
#if 0
    if ((msg_in->data_len != 1) || (msg_in->data[0])) {
	if (msg_in->data_len > 0)
	    ipmi_sol_set_connection_state(conn, ipmi_sol_state_closed,
					  IPMI_IPMI_ERR_VAL(msg_in->data[0]));
	else
	    ipmi_sol_set_connection_state
		(conn, ipmi_sol_state_closed,
		 IPMI_SOL_ERR_VAL(IPMI_SOL_NOT_AVAILABLE));

	return 0;
    }
#endif

    send_commit_write(conn);
}

static int
send_enable_sol_command(ipmi_sol_conn_t *conn)
{
    ipmi_msg_t msg_out;
    unsigned char data[3];

    /*
     * Send a Set SoL Configuration command
     */
    ipmi_log(IPMI_LOG_INFO,
	     "ipmi_sol.c(send_enable_sol_command): "
	     "Attempting to enable SoL on BMC.");

    msg_out.data_len = 3;
    msg_out.data = data;

    /* own channel, get param (not just version) */
    msg_out.data[0] = IPMI_SELF_CHANNEL;
    msg_out.data[1] = 2; /* parameter selector = SOL Auth */
    msg_out.data[2] = 0x02; /* Enable SoL! */
	
    msg_out.netfn = IPMI_TRANSPORT_NETFN;
    msg_out.cmd = IPMI_SET_SOL_CONFIGURATION_PARAMETERS;

    return send_message(conn, &msg_out,
			handle_set_sol_enabled_response);
}

static void
handle_get_sol_enabled_response(ipmi_sol_conn_t *conn,
				ipmi_msg_t      *msg_in)
{
    if (msg_in->data_len != 3) {
	ipmi_log(IPMI_LOG_SEVERE,
		 "ipmi_sol.c(handle_get_sol_enabled_response): "
		 "Get SoL Configuration[SoL Enabled] failed.");
	if (msg_in->data_len > 0)
	    ipmi_sol_set_connection_state(conn, ipmi_sol_state_closed,
					  IPMI_IPMI_ERR_VAL(msg_in->data[0]));
	else
	    ipmi_sol_set_connection_state
		(conn, ipmi_sol_state_closed,
		 IPMI_SOL_ERR_VAL(IPMI_SOL_NOT_AVAILABLE));

	return;
    }

    if ((msg_in->data[2] && 1)) {
#ifdef IPMI_SOL_VERBOSE
	ipmi_log(IPMI_LOG_INFO,
		 "ipmi_sol.c(handle_get_sol_enabled_response): "
		 "BMC says SoL is enabled.");
#endif
	send_get_session_info(conn);
	return;
    }
    ipmi_log(IPMI_LOG_SEVERE,
	     "ipmi_sol.c(handle_get_sol_enabled_response): "
	     "BMC says SoL is disabled.");
	
    if (conn->force_connection_configure)
	send_enable_sol_command(conn);
    else
	ipmi_sol_set_connection_state
	    (conn, ipmi_sol_state_closed,
	     IPMI_SOL_ERR_VAL(IPMI_SOL_NOT_AVAILABLE));
}

static void
send_get_sol_configuration_command(ipmi_sol_conn_t *conn)
{
    ipmi_msg_t    msg_out;
    unsigned char data[4];

    /*
     * Send a Get SoL Configuration command
     */
    msg_out.data_len = 4;
    msg_out.data = data;

    /* own channel, get param (not just version) */
    msg_out.data[0] = IPMI_SELF_CHANNEL;
    msg_out.data[1] = 1; /* parameter selector, 1 = SOL Enabled */
    msg_out.data[2] = 0; /* set selector */
    msg_out.data[3] = 0; /* block selector */

    msg_out.netfn = IPMI_TRANSPORT_NETFN;
    msg_out.cmd = IPMI_GET_SOL_CONFIGURATION_PARAMETERS;

    send_message(conn, &msg_out, handle_get_sol_enabled_response);
}


static void
handle_get_channel_payload_support_response(ipmi_sol_conn_t *conn,
					    ipmi_msg_t      *msg_in)
{
    if (msg_in->data_len != 9) {
	ipmi_log(IPMI_LOG_SEVERE,
		 "ipmi_sol.c(handle_get_channel_payload_support_response): "
		 "Get Channel Payload Support command failed.");
	if (msg_in->data_len > 0)
	    ipmi_sol_set_connection_state(conn, ipmi_sol_state_closed,
					  IPMI_IPMI_ERR_VAL(msg_in->data[0]));
	else
	    ipmi_sol_set_connection_state
		(conn, ipmi_sol_state_closed,
		 IPMI_SOL_ERR_VAL(IPMI_SOL_NOT_AVAILABLE));

	return;
    }

    if (!(msg_in->data[1] & (1 << IPMI_RMCPP_PAYLOAD_TYPE_SOL))) {
	/* SoL is not supported! */
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "ipmi_sol.c(handle_get_channel_payload_support_response): "
		 "BMC says SoL is not supported.");
	ipmi_sol_set_connection_state
	    (conn, ipmi_sol_state_closed,
	     IPMI_RMCPP_ERR_VAL(IPMI_RMCPP_INVALID_PAYLOAD_TYPE));
	return;
    }
#ifdef IPMI_SOL_VERBOSE
    ipmi_log(IPMI_LOG_INFO,
	     "ipmi_sol.c(handle_get_channel_payload_support_response): "
	     "BMC says SoL is supported.");
#endif
    send_get_sol_configuration_command(conn);
}

static int
send_get_channel_payload_support_command(ipmi_sol_conn_t *conn)
{
    ipmi_msg_t    msg_out;
    unsigned char data[1];

    /*
     * Send a Get Payload Support command
     */
    msg_out.data_len = 1;
    msg_out.data = data;

    msg_out.data[0] = IPMI_SELF_CHANNEL; /* current channel */

    msg_out.netfn = IPMI_APP_NETFN;
    msg_out.cmd = IPMI_GET_CHANNEL_PAYLOAD_SUPPORT_CMD;

    return send_message(conn, &msg_out,
			handle_get_channel_payload_support_response);
}


int
ipmi_sol_open(ipmi_sol_conn_t *conn)
{
    int rv;

    ipmi_lock(conn->transmitter.packet_lock);
    if (conn->state != ipmi_sol_state_closed) {
	/* It's an error to try to connect when not in closed state. */
	ipmi_unlock(conn->transmitter.packet_lock);
	ipmi_log(IPMI_LOG_ERR_INFO,
		 "ipmi_sol.c(ipmi_sol_open): "
		 "An attempt was made to open an SoL connection"
		 " that's already open.");
	return EINVAL;
    }

    conn->addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
    conn->addr.channel = IPMI_BMC_CHANNEL;
    conn->addr.lun = 0;
    
    /*
     * Note: For SoL over IPMI 1.5, the ipmi_lan code will translate this
     * RMCP+ address into the right packet format over RMCP (instead of
     * RMCP+).
     */
    conn->sol_payload_addr.addr_type = IPMI_RMCPP_ADDR_SOL;

    if (conn->try_fast_connect)
	rv = send_get_payload_activation_status_command(conn);
    else
	rv = send_get_channel_payload_support_command(conn);

    if (!rv)
	ipmi_sol_set_connection_state(conn, ipmi_sol_state_connecting, 0);

    conn->transmitter.nack_count = 0;
    conn->transmitter.packet_to_acknowledge = 0;
    conn->transmitter.accepted_character_count = 0;
    conn->transmitter.bytes_acked_at_head = 0;

    ipmi_unlock(conn->transmitter.packet_lock);
    return rv;
}


static void
handle_deactivate_payload_response(ipmi_sol_conn_t *conn,
				   ipmi_msg_t      *msg_in)
{
    /*
     * We assume that conn hasn't gone away already, since we got the message
     * through the connection table.
     */
    if (conn->state == ipmi_sol_state_closed)
	return;

    /*
     * Did it work?  (Do we care?)
     */
    if (msg_in->data_len != 1)
	ipmi_sol_set_connection_state(conn, ipmi_sol_state_closed,
				      IPMI_SOL_ERR_VAL(IPMI_SOL_DISCONNECTED));
    else
	if (msg_in->data[0] != 0x00)
	    ipmi_sol_set_connection_state(conn, ipmi_sol_state_closed,
					  IPMI_IPMI_ERR_VAL(msg_in->data[0]));
	else
	     /* Success! */
	    ipmi_sol_set_connection_state(conn, ipmi_sol_state_closed, 0);

    transmitter_shutdown(&conn->transmitter, 0);
}

int
ipmi_sol_close(ipmi_sol_conn_t *conn)
{
    ipmi_lock(conn->transmitter.packet_lock);
    if ((conn->state == ipmi_sol_state_closing)
	|| (conn->state == ipmi_sol_state_closed))
    {
	ipmi_unlock(conn->transmitter.packet_lock);
	return EINVAL;
    }
	
    send_close(conn, handle_deactivate_payload_response);
    ipmi_unlock(conn->transmitter.packet_lock);
    return 0;
}


int
ipmi_sol_force_close_wsend(ipmi_sol_conn_t *conn, int rem_close)
{
    ipmi_lock(conn->transmitter.packet_lock);
    if (conn->state == ipmi_sol_state_closed) {
	ipmi_unlock(conn->transmitter.packet_lock);
	return EINVAL;
    }

    if (rem_close && conn->state != ipmi_sol_state_closing)
	/*
	 * Try to be polite to the BMC. Don't ask for a callback,
	 * cos we'll be gone!
	 */
	send_close(conn, NULL); 

    transmitter_shutdown(&conn->transmitter,
			 IPMI_SOL_ERR_VAL(IPMI_SOL_DISCONNECTED));

    ipmi_sol_set_connection_state(conn, ipmi_sol_state_closed,
				  IPMI_SOL_ERR_VAL(IPMI_SOL_DISCONNECTED));
    ipmi_unlock(conn->transmitter.packet_lock);

    return 0;
}

int
ipmi_sol_force_close(ipmi_sol_conn_t *conn)
{
    return ipmi_sol_force_close_wsend(conn, 1);
}

int
ipmi_sol_free(ipmi_sol_conn_t *conn)
{
    sol_put_connection(conn);
    return 0;
}


/********************************************************************
 ** IPMI SoL Payload handling           *****************************
 ********************************************************************/

/* Format a message for transmit on this payload.  The address and
   message is the one specified by the user.  The out_data is a
   pointer to where to store the output, out_data_len will point
   to the length of the buffer to store the output and should be
   updatated to be the actual length.  The seq is a 6-bit value
   that should be store somewhere so the that response to this
   message can be identified.  If the netfn is odd, the sequence
   number is not used.  The out_of_session variable is set to zero
   by default; if the message is meant to be sent out of session,
   then the formatter should set this value to 1. */

static int
sol_format_msg(ipmi_con_t        *conn,
	       const ipmi_addr_t *addr,
	       unsigned int      addr_len,
	       const ipmi_msg_t  *msg,
	       unsigned char     *out_data,
	       unsigned int      *out_data_len,
	       int               *out_of_session,
	       unsigned char     seq)
{
    if (*out_data_len < msg->data_len)
	return E2BIG;

    memcpy(out_data, msg->data, msg->data_len);

    *out_data_len = msg->data_len;

    out_of_session = 0;
    return 0;
}


/* Get the recv sequence number from the message.  Return ENOSYS
   if the sequence number is not valid for the message (it is
   asynchronous), zero otherwise */
static int sol_get_recv_seq(ipmi_con_t    *conn,
			unsigned char *data,
			unsigned int  data_len,
			unsigned char *seq)
{
    /*
     * We force the packets to go through to sol_handle_recv_async for
     * our processing.  This is because we can't use the OpenIPMI payload
     * sequence number interface.
     */
    return ENOSYS;
}


/* Fill in the rspi data structure from the given data, responses
   only.  This does *not* deliver the message, that is done by the
   LAN code. */
static int
sol_handle_recv(ipmi_con_t    *conn,
		ipmi_msgi_t   *rspi,
		ipmi_addr_t   *orig_addr,
		unsigned int  orig_addr_len,
		ipmi_msg_t    *orig_msg,
		unsigned char *data,
		unsigned int  data_len)
{
    /*
     * This should NEVER be called.
     */
    return ENOSYS;
}

static void
process_packet(ipmi_sol_conn_t *conn,
	       unsigned char   *packet,
	       unsigned int    data_len)
{
    ipmi_sol_transmitter_context_t *xmitter;
    int                            nack = 0;

    xmitter = &conn->transmitter;

    nack = (packet[PACKET_STATUS] & IPMI_SOL_STATUS_NACK_PACKET) != 0;

    /* If NACK && CTU != prev CTU, do a conn state change */

    if (nack) {
	/* Check CTU */
	int new_state;

	if (packet[PACKET_STATUS]
	    & IPMI_SOL_STATUS_CHARACTER_TRANSFER_UNAVAIL)
	    new_state = ipmi_sol_state_connected_ctu;
	else
	    new_state = ipmi_sol_state_connected;

	ipmi_sol_set_connection_state(conn, new_state, 0);
    }

    if (data_len > 4) {
	data_len -= 4; /* Skip over header */

	if (0 == packet[PACKET_SEQNR]) {
	    /* Can't have data in a packet with zero seqnr: error */
	    ipmi_log(IPMI_LOG_WARNING,
		     "ipmi_sol.c(sol_handle_recv_async): "
		     "Broken BMC: Received a packet with non-empty data"
		     " and a sequence number of zero.");
	} else {
	    int character_count;
	    int do_nack;

	    /* FIXME - validate that the sequence numbers are
	       sequentially increasing. */
	    if (conn->prev_received_seqnr == packet[PACKET_SEQNR]) {
		/* overlapping packets... yummy */
		character_count = data_len - conn->prev_character_count;
	    } else {
		/* This whole packet goes to the client(s) */
		character_count = data_len;
		conn->prev_received_seqnr = packet[PACKET_SEQNR];
	    }
	    if (xmitter->nack_count) {
		/* The user already sent a NACK, no reason to send any
		   more til they release it. */
	    } else {
		xmitter->in_recv_cb = 1;
		ipmi_unlock(xmitter->packet_lock);
		do_nack = do_data_received_callbacks
		    (conn, &packet[PACKET_DATA + data_len - character_count],
		     character_count);
		ipmi_lock(xmitter->packet_lock);
		xmitter->in_recv_cb = 0;

		xmitter->nack_count += do_nack;
		if (xmitter->nack_count < 0) {
		    ipmi_log(IPMI_LOG_WARNING,
			     "ipmi_sol.c(process_packet): "
			     "Too many NACK releases.");
		    xmitter->nack_count = 0;
		}

		if (conn->state == ipmi_sol_state_closed)
		    return;
	    }

	    conn->prev_received_seqnr = packet[PACKET_SEQNR];
	    xmitter->packet_to_acknowledge = packet[PACKET_SEQNR];

	    if (xmitter->nack_count) {
		conn->prev_character_count = 0;
		/* FIXME: It is unclear from the spec whether the
		   accepted character count on a NACK should be 0 or
		   the number of bytes not accepted.  Zero seems more
		   reasonable, but neither works with my machine, it
		   just keeps retransmitting then gives up when it
		   gets a NACK. - Corey */
		xmitter->accepted_character_count = 0;
		ipmi_lock(xmitter->oob_op_lock);
		xmitter->oob_transient_op |= IPMI_SOL_OPERATION_NACK_PACKET;
		ipmi_unlock(xmitter->oob_op_lock);
	    } else {
		conn->prev_character_count = data_len;
		xmitter->accepted_character_count = data_len;
	    }
	}
    }


    if (packet[PACKET_ACK_NACK_SEQNR] &&
	xmitter->transmitted_packet &&
	(packet[PACKET_ACK_NACK_SEQNR]
	 == xmitter->transmitted_packet->packet[PACKET_SEQNR]))
    {
	/*
	 * The op callbacks are always successful if we got an ACK.
	 */
	do_outstanding_op_callbacks(xmitter, 0);

	/* NACK and Char Trans Unavail? */
	if ((packet[PACKET_STATUS] & IPMI_SOL_STATUS_NACK_PACKET)
	    && (packet[PACKET_STATUS] & IPMI_SOL_STATUS_CHARACTER_TRANSFER_UNAVAIL))
	{
	    /*
	     * This will never send CTU error code to a control
	     * callback, cos they have been done-and-destroyed just
	     * above!
	     */
	    transmitter_flush_outbound
		(xmitter,
		 IPMI_SOL_ERR_VAL(IPMI_SOL_CHARACTER_TRANSFER_UNAVAILABLE));
	} else if (packet[PACKET_ACCEPTED_CHARACTER_COUNT] > 0) {
	    /* Accepted chars? */
	    transmitter_handle_acknowledge
		(conn, 0, packet[PACKET_ACCEPTED_CHARACTER_COUNT]);
	} else if (!(packet[PACKET_STATUS] & IPMI_SOL_STATUS_NACK_PACKET)) {
	    /* FIXME: Intel hack */
	    /*
	     * If the packet wasn't NACKed, and the accepted char
	     * count was zero, assume they meant to ACK the whole
	     * packet.
	     */
	    transmitter_handle_acknowledge
		(conn, 0, xmitter->transmitted_packet->packet_size - 4);
	}

	/*
	 * Destroy the packet, reporting success on anything else we've missed.
	 */
	dispose_of_outstanding_packet(xmitter, 0);
    }

    if (packet[PACKET_STATUS] & IPMI_SOL_STATUS_BREAK_DETECTED) {
	ipmi_unlock(xmitter->packet_lock);
	do_break_detected_callbacks(conn);
	ipmi_lock(xmitter->packet_lock);
	if (conn->state == ipmi_sol_state_closed)
	    return;
    }

    if (packet[PACKET_STATUS] & IPMI_SOL_STATUS_BMC_TX_OVERRUN) {
	ipmi_unlock(xmitter->packet_lock);
	do_transmit_overrun_callbacks(conn);
	ipmi_lock(xmitter->packet_lock);
	if (conn->state == ipmi_sol_state_closed)
	    return;
    }

    if (nack && (packet[PACKET_STATUS] & IPMI_SOL_STATUS_DEACTIVATED)) {
	transmitter_shutdown(xmitter, IPMI_SOL_ERR_VAL(IPMI_SOL_DEACTIVATED));
	 /* Success! */
	ipmi_sol_set_connection_state(conn,
				      ipmi_sol_state_closed,
				      IPMI_SOL_ERR_VAL(IPMI_SOL_DEACTIVATED));
    } else {
	transmitter_prod_nolock(xmitter);
    }
}


/* Handle an asynchronous message.  This *should* deliver the
   message, if possible. */
static void
sol_handle_recv_async(ipmi_con_t    *ipmi_conn,
		      unsigned char *packet,
		      unsigned int  data_len)
{
    ipmi_sol_transmitter_context_t *xmitter;
    ipmi_sol_conn_t                *conn;

    conn = find_sol_connection_for_ipmi(ipmi_conn);
    if (!conn) {
	ipmi_log(IPMI_LOG_WARNING,
		 "ipmi_sol.c(sol_handle_recv_async): "
		 "Dropped incoming SoL packet: Unrecognized connection.");
	return;
    }

    xmitter = &conn->transmitter;

    ipmi_lock(xmitter->packet_lock);

    if (data_len < 4) {
	ipmi_log(IPMI_LOG_WARNING,
		 "ipmi_sol.c(sol_handle_recv_async): "
		 "Dropped incoming SoL packet: Too short, at %d bytes.",
		 data_len);
	goto out_unlock;
    }

#ifdef IPMI_SOL_DEBUG_RECEIVE
    ipmi_log(IPMI_LOG_INFO,
	     "ipmi_sol.c(sol_handle_recv_async): "
	     "Received SoL packet, %d bytes", data_len);
    dump_hex(packet, data_len);
#endif

    if ((conn->state != ipmi_sol_state_connected)
	&& (conn->state != ipmi_sol_state_connected_ctu)) {
	ipmi_log(IPMI_LOG_WARNING,
		 "ipmi_sol.c(sol_handle_recv_async): "
		 "Dropped incoming SoL packet: connection closed.");
	goto out_unlock;
    }

    if (conn->processing_packet) {
	/* Some other thread is already processing packets.  Tack this
	   packet onto the end of waiting packets for the other thread
	   to handle. */
	sol_in_packet_info_t *packet, *epacket;
	unsigned char        *pdata;

	packet = ipmi_mem_alloc(sizeof(*packet) + data_len);
	if (!packet)
	    goto out_unlock;
	packet->data_len = data_len;
	packet->next = NULL;
	pdata = ((unsigned char *) packet) + sizeof(*packet);
	memcpy(pdata, packet, data_len);

	if (conn->waiting_packets) {
	    conn->waiting_packets = packet;
	} else {
	    epacket = conn->waiting_packets;
	    while (epacket->next)
		epacket = epacket->next;
	    epacket->next = packet;
	}
	goto out_unlock;
    }

    conn->processing_packet = 1;

    /* At this point we are single-threaded.  No other process can be
       running this code but me, even if I release the packet_lock. */

    process_packet(conn, packet, data_len);

    /* See if some other thread stuck some packets in for me to
       process.  Do that now. */
    process_waiting_packets(conn);

    conn->processing_packet = 0;

 out_unlock:
    ipmi_unlock(xmitter->packet_lock);
    sol_put_connection(conn);
}

static ipmi_payload_t ipmi_sol_payload =
{ sol_format_msg, sol_get_recv_seq, sol_handle_recv,
  sol_handle_recv_async, NULL /*sol_get_msg_tag*/ };

int
i_ipmi_sol_init()
{
    int rv;

    rv = ipmi_rmcpp_register_payload(IPMI_RMCPP_PAYLOAD_TYPE_SOL,
				     &ipmi_sol_payload);
    if (rv)
	goto out;

    rv = ipmi_create_global_lock(&conn_lock);
    if (rv) {
	ipmi_rmcpp_register_payload(IPMI_RMCPP_PAYLOAD_TYPE_SOL, NULL);
	goto out;
    }

 out:
    return rv;
}

void
i_ipmi_sol_shutdown(void)
{
    if (conn_lock) {
	ipmi_destroy_lock(conn_lock);
	conn_lock = NULL;
    }
    ipmi_rmcpp_register_payload(IPMI_RMCPP_PAYLOAD_TYPE_SOL, NULL);
}