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

#include <string.h>
#include <errno.h>

#include <OpenIPMI/ipmiif.h>

#include <OpenIPMI/internal/ipmi_event.h>
#include <OpenIPMI/internal/ipmi_int.h>
#include <OpenIPMI/internal/ipmi_mc.h>
#include <OpenIPMI/internal/ipmi_domain.h>

struct ipmi_event_s
{
    ipmi_mcid_t   mcid; /* The MC this event is stored in. */

    ipmi_lock_t   *lock;
    unsigned int  refcount;
    unsigned int  record_id;
    unsigned int  type;
    ipmi_time_t   timestamp;
    unsigned int  data_len;
    unsigned char old;
    unsigned char data[0];
};

ipmi_event_t *
ipmi_event_alloc(ipmi_mcid_t   mcid,
		 unsigned int  record_id,
		 unsigned int  type,
		 ipmi_time_t   timestamp,
		 unsigned char *data,
		 unsigned int  data_len)
{
    ipmi_event_t *rv;

    rv = ipmi_mem_alloc(sizeof(ipmi_event_t) + data_len);
    if (!rv)
	return NULL;

    if (ipmi_create_global_lock(&rv->lock)) {
	ipmi_mem_free(rv);
	return NULL;
    }
    rv->mcid = mcid;
    rv->record_id = record_id;
    rv->type = type;
    rv->timestamp = timestamp;
    rv->data_len = data_len;
    rv->old = 0;
    if (data_len)
	memcpy(rv->data, data, data_len);

    rv->refcount = 1;
    return rv;
}

ipmi_event_t *
ipmi_event_dup(ipmi_event_t *event)
{
    if (!event)
	return NULL;
    ipmi_lock(event->lock);
    event->refcount++;
    ipmi_unlock(event->lock);
    return event;
}

void
ipmi_event_free(ipmi_event_t *event)
{
    if (!event)
	return;
    ipmi_lock(event->lock);
    event->refcount--;
    if (event->refcount == 0) {
	ipmi_unlock(event->lock);
	ipmi_destroy_lock(event->lock);
	ipmi_mem_free(event);
	return;
    }
    ipmi_unlock(event->lock);
}

ipmi_mcid_t
ipmi_event_get_mcid(const ipmi_event_t *event)
{
    return event->mcid;
}

void
ipmi_event_set_mcid(ipmi_event_t *event, ipmi_mcid_t mcid)
{
    event->mcid = mcid;
}

unsigned int
ipmi_event_get_record_id(const ipmi_event_t *event)
{
    return event->record_id;
}

unsigned int
ipmi_event_get_type(const ipmi_event_t *event)
{
    return event->type;
}

ipmi_time_t
ipmi_event_get_timestamp(const ipmi_event_t *event)
{
    return event->timestamp;
}

unsigned int
ipmi_event_get_data_len(const ipmi_event_t *event)
{
    return event->data_len;
}

unsigned int
ipmi_event_get_data(const ipmi_event_t *event, unsigned char *data,
		    unsigned int offset, unsigned int len)
{
    if (offset > event->data_len)
	return 0;

    if (offset+len > event->data_len)
	len = event->data_len - offset;

    memcpy(data, event->data+offset, len);

    return len;
}

const unsigned char *
ipmi_event_get_data_ptr(const ipmi_event_t *event)
{
    return event->data;
}

int
ipmi_event_is_old(const ipmi_event_t *event)
{
    return event->old;
}

void
ipmi_event_set_is_old(ipmi_event_t *event, int val)
{
    event->old = val;
}

typedef struct del_event_info_s
{
    ipmi_event_t   *event;
    ipmi_domain_cb done_handler;
    void           *cb_data;
    int            rv;
} del_event_info_t;

static void
mc_del_event_done(ipmi_mc_t *mc, int err, void *cb_data)
{
    del_event_info_t *info = cb_data;

    if (info->done_handler) {
	ipmi_domain_t *domain = NULL;
	if (mc)
	    domain = ipmi_mc_get_domain(mc);
	info->done_handler(domain, err, info->cb_data);
    }
    ipmi_mem_free(info);
}

static void
del_event_handler(ipmi_mc_t *mc, void *cb_data)
{
    del_event_info_t *info = cb_data;
    del_event_info_t *ninfo;

    ninfo = ipmi_mem_alloc(sizeof(*ninfo));
    if (!ninfo) {
	info->rv = ENOMEM;
	return;
    }
    *ninfo = *info;

    info->rv = ipmi_mc_del_event(mc, info->event, mc_del_event_done, ninfo);
    if (info->rv)
	ipmi_mem_free(ninfo);
}

int
ipmi_event_delete(ipmi_event_t   *event,
		  ipmi_domain_cb done_handler,
		  void           *cb_data)
{
    int              rv;
    del_event_info_t info;
    ipmi_mcid_t      mcid = ipmi_event_get_mcid(event);

    info.event = event;
    info.done_handler = done_handler;
    info.cb_data = cb_data;
    info.rv = 0;
    rv = ipmi_mc_pointer_cb(mcid, del_event_handler, &info);
    if (!rv)
	rv = info.rv;

    return rv;
}

ipmi_mc_t *
i_ipmi_event_get_generating_mc(ipmi_domain_t      *domain,
			       ipmi_mc_t          *sel_mc,
			       const ipmi_event_t *event)
{
    ipmi_ipmb_addr_t    addr;
    const unsigned char *data;
    unsigned int        type = ipmi_event_get_type(event);

    if (type != 0x02)
	/* It's not a standard IPMI event. */
	return NULL;

    data = ipmi_event_get_data_ptr(event);
    addr.addr_type = IPMI_IPMB_ADDR_TYPE;
    /* See if the MC has an OEM handler for this. */
    if (data[6] == 0x03) {
	addr.channel = 0;
    } else {
	addr.channel = data[5] >> 4;
    }
    if ((data[4] & 0x01) == 0) {
	addr.slave_addr = data[4];
    } else if (sel_mc) {
	/* A software ID, assume it comes from the MC where we go it. */
	ipmi_addr_t iaddr;

	ipmi_mc_get_ipmi_address(sel_mc, &iaddr, NULL);
	addr.slave_addr = ipmi_addr_get_slave_addr(&iaddr);
	if (addr.slave_addr == 0)
	    /* A system interface, just assume it's the BMC. */
	    addr.slave_addr = 0x20;
    } else {
	return NULL;
    }
    addr.lun = 0;

    return i_ipmi_find_mc_by_addr(domain, (ipmi_addr_t *) &addr, sizeof(addr));
}

ipmi_sensor_id_t
ipmi_event_get_generating_sensor_id(ipmi_domain_t       *domain,
			            ipmi_mc_t           *sel_mc,
			            const ipmi_event_t  *event)
{
    ipmi_sensor_id_t    id;
    ipmi_mc_t           *mc;
    const unsigned char *data;
    unsigned int        type = ipmi_event_get_type(event);


    if (type != 0x02)
	/* It's not a standard IPMI event. */
	goto out_invalid;

    mc = i_ipmi_event_get_generating_mc(domain, sel_mc, event);
    if (!mc)
	goto out_invalid;

    data = ipmi_event_get_data_ptr(event);
    id.mcid = ipmi_mc_convert_to_id(mc);
    id.lun = data[5] & 0x3;
    id.sensor_num = data[8];

    i_ipmi_mc_put(mc);

    return id;

 out_invalid:
    ipmi_sensor_id_set_invalid(&id);
    return id;
}

struct ipmi_event_handlers_s
{
    ipmi_sensor_threshold_event_cb threshold;
    ipmi_sensor_discrete_event_cb  discrete;
};

ipmi_event_handlers_t *
ipmi_event_handlers_alloc(void)
{
    ipmi_event_handlers_t *rv;
    rv = ipmi_mem_alloc(sizeof(*rv));
    if (!rv)
	return NULL;
    memset(rv, 0, sizeof(*rv));
    return rv;
}

void
ipmi_event_handlers_free(ipmi_event_handlers_t *handlers)
{
    ipmi_mem_free(handlers);
}

void
ipmi_event_handlers_set_threshold(ipmi_event_handlers_t         *handlers,
				  ipmi_sensor_threshold_event_cb handler)
{
    handlers->threshold = handler;
}

void
ipmi_event_handlers_set_discrete(ipmi_event_handlers_t         *handlers,
				 ipmi_sensor_discrete_event_cb handler)
{
    handlers->discrete = handler;
}

typedef struct event_call_handlers_s
{
    ipmi_domain_t         *domain;
    ipmi_event_handlers_t *handlers;
    ipmi_event_t          *event;
    int                   rv;
    void                  *cb_data;
} event_call_handlers_t;

static void
sensor_event_call(ipmi_sensor_t *sensor, void *cb_data)
{
    event_call_handlers_t *info = cb_data;
    int                   rv;

    if (ipmi_sensor_get_event_reading_type(sensor)
	== IPMI_EVENT_READING_TYPE_THRESHOLD)
    {
	enum ipmi_event_dir_e       dir;
	enum ipmi_thresh_e          threshold;
	enum ipmi_event_value_dir_e high_low;
	enum ipmi_value_present_e   value_present;
	unsigned int                raw_value;
	double                      value;
	const unsigned char         *data;

	data = ipmi_event_get_data_ptr(info->event);
	dir = data[9] >> 7;
	threshold = (data[10] >> 1) & 0x07;
	high_low = data[10] & 1;
	raw_value = data[11];
	value = 0.0;

	if ((data[10] >> 6) == 1) {
	    rv = ipmi_sensor_convert_from_raw(sensor, raw_value, &value);
	    if (rv)
		value_present = IPMI_RAW_VALUE_PRESENT;
	    else
		value_present = IPMI_BOTH_VALUES_PRESENT;
	} else {
	    value_present = IPMI_NO_VALUES_PRESENT;
	}
	if (info->handlers->threshold)
	    info->handlers->threshold(sensor, dir,
				      threshold,
				      high_low,
				      value_present,
				      raw_value, value,
				      info->cb_data,
				      info->event);

	else
	    info->rv = EAGAIN;
    } else {
	enum ipmi_event_dir_e dir;
	int                   offset;
	int                   severity = 0;
	int                   prev_severity = 0;
	const unsigned char   *data;

	data = ipmi_event_get_data_ptr(info->event);
	dir = data[9] >> 7;
	offset = data[10] & 0x0f;
	if ((data[10] >> 6) == 2) {
	    severity = data[11] >> 4;
	    prev_severity = data[11] & 0xf;
	    if (severity == 0xf)
		severity = -1;
	    if (prev_severity == 0xf)
		prev_severity = -1;
	}

	if (info->handlers->discrete)
	    info->handlers->discrete(sensor, dir, offset,
				     severity,
				     prev_severity,
				     info->cb_data,
				     info->event);
	else
	    info->rv = EAGAIN;
    }
}

static void
sel_mc_handler(ipmi_mc_t *mc, void *cb_data)
{
    ipmi_sensor_id_t      sensor_id;
    event_call_handlers_t *info = cb_data;
    int                   rv;

    sensor_id = ipmi_event_get_generating_sensor_id(info->domain, mc,
						    info->event);
    rv = ipmi_sensor_pointer_cb(sensor_id, sensor_event_call, info);
    if (rv)
	info->rv = rv;
}

int
ipmi_event_call_handler(ipmi_domain_t         *domain,
			ipmi_event_handlers_t *handlers,
			ipmi_event_t          *event,
			void                  *cb_data)
{


    ipmi_sensor_id_t      sensor_id;
    event_call_handlers_t info;
    int                   rv = 0;
    ipmi_mcid_t           mc_id;

    info.domain = domain;
    info.handlers = handlers;
    info.event = event;
    info.rv = 0;
    info.cb_data = cb_data;

    /* We try first to get the MC the event is stored in.  If that
       doesn't work, then just attempt to do the sensor without an MC. */
    mc_id = ipmi_event_get_mcid(event);
    if (ipmi_mc_pointer_cb(mc_id, sel_mc_handler, &info) != 0) {
	sensor_id = ipmi_event_get_generating_sensor_id(domain, NULL, event);
	rv = ipmi_sensor_pointer_cb(sensor_id, sensor_event_call, &info);
    }
    if (!rv)
	rv = info.rv;
    return rv;
}
