/*
 * IEEE 1394 for Linux
 *
 * Transaction support.
 *
 * Copyright (C) 1999 Andreas E. Bombe
 *
 * This code is licensed under the GPL.  See the file COPYING in the root
 * directory of the kernel sources for details.
 */

#include <linux/sched.h>
#include <linux/bitops.h>
#include <asm/errno.h>
#include <linux/interrupt.h>

#include "ieee1394.h"
#include "ieee1394_types.h"
#include "hosts.h"
#include "ieee1394_core.h"
#include "highlevel.h"
#include "nodemgr.h"


#define PREP_ASYNC_HEAD_ADDRESS(tc) \
        packet->tcode = tc; \
        packet->header[0] = (packet->node_id << 16) | (packet->tlabel << 10) \
                | (1 << 8) | (tc << 4); \
        packet->header[1] = (packet->host->node_id << 16) | (addr >> 32); \
        packet->header[2] = addr & 0xffffffff


static void fill_async_readquad(struct hpsb_packet *packet, u64 addr)
{
        PREP_ASYNC_HEAD_ADDRESS(TCODE_READQ);
        packet->header_size = 12;
        packet->data_size = 0;
        packet->expect_response = 1;
}

static void fill_async_readblock(struct hpsb_packet *packet, u64 addr, int length)
{
        PREP_ASYNC_HEAD_ADDRESS(TCODE_READB);
        packet->header[3] = length << 16;
        packet->header_size = 16;
        packet->data_size = 0;
        packet->expect_response = 1;
}

static void fill_async_writequad(struct hpsb_packet *packet, u64 addr, quadlet_t data)
{
        PREP_ASYNC_HEAD_ADDRESS(TCODE_WRITEQ);
        packet->header[3] = data;
        packet->header_size = 16;
        packet->data_size = 0;
        packet->expect_response = 1;
}

static void fill_async_writeblock(struct hpsb_packet *packet, u64 addr, int length)
{
        PREP_ASYNC_HEAD_ADDRESS(TCODE_WRITEB);
        packet->header[3] = length << 16;
        packet->header_size = 16;
        packet->expect_response = 1;
        packet->data_size = length + (length % 4 ? 4 - (length % 4) : 0);
}

static void fill_async_lock(struct hpsb_packet *packet, u64 addr, int extcode, 
                     int length)
{
        PREP_ASYNC_HEAD_ADDRESS(TCODE_LOCK_REQUEST);
        packet->header[3] = (length << 16) | extcode;
        packet->header_size = 16;
        packet->data_size = length;
        packet->expect_response = 1;
}

static void fill_iso_packet(struct hpsb_packet *packet, int length, int channel,
                     int tag, int sync)
{
        packet->header[0] = (length << 16) | (tag << 14) | (channel << 8)
                | (TCODE_ISO_DATA << 4) | sync;

        packet->header_size = 4;
        packet->data_size = length;
        packet->type = hpsb_iso;
        packet->tcode = TCODE_ISO_DATA;
}

static void fill_phy_packet(struct hpsb_packet *packet, quadlet_t data) 
{ 
        packet->header[0] = data;
        packet->header[1] = ~data; 
        packet->header_size = 8;
        packet->data_size = 0;
        packet->expect_response = 0;
        packet->type = hpsb_raw;             /* No CRC added */
        packet->speed_code = IEEE1394_SPEED_100; /* Force speed to be 100Mbps */
}

static void fill_async_stream_packet(struct hpsb_packet *packet, int length,
				     int channel, int tag, int sync)
{
	packet->header[0] = (length << 16) | (tag << 14) | (channel << 8)
	                  | (TCODE_STREAM_DATA << 4) | sync;

	packet->header_size = 4;
	packet->data_size = length;
	packet->type = hpsb_async;
	packet->tcode = TCODE_ISO_DATA;
}

/**
 * hpsb_get_tlabel - allocate a transaction label
 * @packet: the packet who's tlabel/tpool we set
 *
 * Every asynchronous transaction on the 1394 bus needs a transaction
 * label to match the response to the request.  This label has to be
 * different from any other transaction label in an outstanding request to
 * the same node to make matching possible without ambiguity.
 *
 * There are 64 different tlabels, so an allocated tlabel has to be freed
 * with hpsb_free_tlabel() after the transaction is complete (unless it's
 * reused again for the same target node).
 *
 * Return value: Zero on success, otherwise non-zero. A non-zero return
 * generally means there are no available tlabels. If this is called out
 * of interrupt or atomic context, then it will sleep until can return a
 * tlabel.
 */
int hpsb_get_tlabel(struct hpsb_packet *packet)
{
	unsigned long flags;
	struct hpsb_tlabel_pool *tp;

	tp = &packet->host->tpool[packet->node_id & NODE_MASK];

	if (in_interrupt()) {
		if (down_trylock(&tp->count))
			return 1;
	} else {
		down(&tp->count);
	}

	spin_lock_irqsave(&tp->lock, flags);
	
	packet->tlabel = find_next_zero_bit(tp->pool, 64, tp->next);
	if (packet->tlabel > 63)
		packet->tlabel = find_first_zero_bit(tp->pool, 64);
	tp->next = (packet->tlabel + 1) % 64;
	/* Should _never_ happen */
	BUG_ON(test_and_set_bit(packet->tlabel, tp->pool));
	tp->allocations++;
	spin_unlock_irqrestore(&tp->lock, flags);

	return 0;
}

/** 
 * hpsb_free_tlabel - free an allocated transaction label
 * @packet: packet whos tlabel/tpool needs to be cleared
 *
 * Frees the transaction label allocated with hpsb_get_tlabel().  The
 * tlabel has to be freed after the transaction is complete (i.e. response
 * was received for a split transaction or packet was sent for a unified
 * transaction).
 *
 * A tlabel must not be freed twice.
 */
void hpsb_free_tlabel(struct hpsb_packet *packet)
{
        unsigned long flags;
	struct hpsb_tlabel_pool *tp;
	
	tp = &packet->host->tpool[packet->node_id & NODE_MASK];

	BUG_ON(packet->tlabel > 63 || packet->tlabel < 0);

        spin_lock_irqsave(&tp->lock, flags);
	BUG_ON(!test_and_clear_bit(packet->tlabel, tp->pool));
        spin_unlock_irqrestore(&tp->lock, flags);

	up(&tp->count);
}



int hpsb_packet_success(struct hpsb_packet *packet)
{
        switch (packet->ack_code) {
        case ACK_PENDING:
                switch ((packet->header[1] >> 12) & 0xf) {
                case RCODE_COMPLETE:
                        return 0;
                case RCODE_CONFLICT_ERROR:
                        return -EAGAIN;
                case RCODE_DATA_ERROR:
                        return -EREMOTEIO;
                case RCODE_TYPE_ERROR:
                        return -EACCES;
                case RCODE_ADDRESS_ERROR:
                        return -EINVAL;
                default:
                        HPSB_ERR("received reserved rcode %d from node %d",
                                 (packet->header[1] >> 12) & 0xf, 
                                 packet->node_id);
                        return -EAGAIN;
                }
                HPSB_PANIC("reached unreachable code 1 in %s", __FUNCTION__);

        case ACK_BUSY_X:
        case ACK_BUSY_A:
        case ACK_BUSY_B:
                return -EBUSY;

        case ACK_TYPE_ERROR:
                return -EACCES;

        case ACK_COMPLETE:
                if (packet->tcode == TCODE_WRITEQ
                    || packet->tcode == TCODE_WRITEB) {
                        return 0;
                } else {
                        HPSB_ERR("impossible ack_complete from node %d "
                                 "(tcode %d)", packet->node_id, packet->tcode);
                        return -EAGAIN;
                }


        case ACK_DATA_ERROR:
                if (packet->tcode == TCODE_WRITEB
                    || packet->tcode == TCODE_LOCK_REQUEST) {
                        return -EAGAIN;
                } else {
                        HPSB_ERR("impossible ack_data_error from node %d "
                                 "(tcode %d)", packet->node_id, packet->tcode);
                        return -EAGAIN;
                }

        case ACKX_NONE:
        case ACKX_SEND_ERROR:
        case ACKX_ABORTED:
        case ACKX_TIMEOUT:
                /* error while sending */
                return -EAGAIN;

        default:
                HPSB_ERR("got invalid ack %d from node %d (tcode %d)",
                         packet->ack_code, packet->node_id, packet->tcode);
                return -EAGAIN;
        }

        HPSB_PANIC("reached unreachable code 2 in %s", __FUNCTION__);
}

struct hpsb_packet *hpsb_make_readpacket(struct hpsb_host *host, nodeid_t node,
					 u64 addr, size_t length)
{
        struct hpsb_packet *packet;

	if (length == 0)
		return NULL;

	packet = alloc_hpsb_packet(length + (length % 4 ? 4 - (length % 4) : 0));
	if (!packet)
		return NULL;

	packet->host = host;
	packet->node_id = node;

	if (hpsb_get_tlabel(packet)) {
		free_hpsb_packet(packet);
		return NULL;
	}

	if (length == 4)
		fill_async_readquad(packet, addr);
	else
		fill_async_readblock(packet, addr, length);

	return packet;
}

struct hpsb_packet *hpsb_make_writepacket (struct hpsb_host *host, nodeid_t node,
					   u64 addr, quadlet_t *buffer, size_t length)
{
	struct hpsb_packet *packet;

	if (length == 0)
		return NULL;

	packet = alloc_hpsb_packet(length + (length % 4 ? 4 - (length % 4) : 0));
	if (!packet)
		return NULL;

	if (length % 4) { /* zero padding bytes */
		packet->data[length >> 2] = 0;
	}
	packet->host = host;
	packet->node_id = node;

	if (hpsb_get_tlabel(packet)) {
		free_hpsb_packet(packet);
		return NULL;
	}

	if (length == 4) {
		fill_async_writequad(packet, addr, buffer ? *buffer : 0);
	} else {
		fill_async_writeblock(packet, addr, length);
		if (buffer)
			memcpy(packet->data, buffer, length);
	}

	return packet;
}

struct hpsb_packet *hpsb_make_streampacket(struct hpsb_host *host, u8 *buffer, int length,
                                           int channel, int tag, int sync)
{
	struct hpsb_packet *packet;

	if (length == 0)
		return NULL;

	packet = alloc_hpsb_packet(length + (length % 4 ? 4 - (length % 4) : 0));
	if (!packet)
		return NULL;

	if (length % 4) { /* zero padding bytes */
		packet->data[length >> 2] = 0;
	}
	packet->host = host;
    
	if (hpsb_get_tlabel(packet)) {
		free_hpsb_packet(packet);
		return NULL;
	}

	fill_async_stream_packet(packet, length, channel, tag, sync);
	if (buffer)
		memcpy(packet->data, buffer, length);

	return packet;
}

struct hpsb_packet *hpsb_make_lockpacket(struct hpsb_host *host, nodeid_t node,
                                         u64 addr, int extcode, quadlet_t *data,
					 quadlet_t arg)
{
	struct hpsb_packet *p;
	u32 length;

	p = alloc_hpsb_packet(8);
	if (!p) return NULL;

	p->host = host;
	p->node_id = node;
	if (hpsb_get_tlabel(p)) {
		free_hpsb_packet(p);
		return NULL;
	}

	switch (extcode) {
	case EXTCODE_FETCH_ADD:
	case EXTCODE_LITTLE_ADD:
		length = 4;
		if (data)
			p->data[0] = *data;
		break;
	default:
		length = 8;
		if (data) {
			p->data[0] = arg;
			p->data[1] = *data;
		}
		break;
	}
	fill_async_lock(p, addr, extcode, length);

	return p;
}

struct hpsb_packet *hpsb_make_lock64packet(struct hpsb_host *host, nodeid_t node,
                                           u64 addr, int extcode, octlet_t *data,
					   octlet_t arg)
{
	struct hpsb_packet *p;
	u32 length;

	p = alloc_hpsb_packet(16);
	if (!p) return NULL;

	p->host = host;
	p->node_id = node;
	if (hpsb_get_tlabel(p)) {
		free_hpsb_packet(p);
		return NULL;
	}

	switch (extcode) {
	case EXTCODE_FETCH_ADD:
	case EXTCODE_LITTLE_ADD:
		length = 8;
		if (data) {
			p->data[0] = *data >> 32;
			p->data[1] = *data & 0xffffffff;
		}
		break;
	default:
		length = 16;
		if (data) {
			p->data[0] = arg >> 32;
			p->data[1] = arg & 0xffffffff;
			p->data[2] = *data >> 32;
			p->data[3] = *data & 0xffffffff;
		}
		break;
	}
	fill_async_lock(p, addr, extcode, length);

	return p;
}

struct hpsb_packet *hpsb_make_phypacket(struct hpsb_host *host,
                                        quadlet_t data) 
{
        struct hpsb_packet *p; 

        p = alloc_hpsb_packet(0); 
        if (!p) return NULL; 

        p->host = host; 
        fill_phy_packet(p, data); 

        return p; 
}

struct hpsb_packet *hpsb_make_isopacket(struct hpsb_host *host,
					int length, int channel,
					int tag, int sync)
{
	struct hpsb_packet *p;

	p = alloc_hpsb_packet(length);
	if (!p) return NULL;

	p->host = host;
	fill_iso_packet(p, length, channel, tag, sync);

	p->generation = get_hpsb_generation(host);

	return p;
}

/*
 * FIXME - these functions should probably read from / write to user space to
 * avoid in kernel buffers for user space callers
 */

int hpsb_read(struct hpsb_host *host, nodeid_t node, unsigned int generation,
	      u64 addr, quadlet_t *buffer, size_t length)
{
        struct hpsb_packet *packet;
        int retval = 0;
        
        if (length == 0)
                return -EINVAL;

	BUG_ON(in_interrupt()); // We can't be called in an interrupt, yet

	packet = hpsb_make_readpacket(host, node, addr, length);

        if (!packet) {
                return -ENOMEM;
        }

	packet->generation = generation;
        if (!hpsb_send_packet(packet)) {
		retval = -EINVAL;
		goto hpsb_read_fail;
	}

        down(&packet->state_change);
        down(&packet->state_change);
        retval = hpsb_packet_success(packet);

        if (retval == 0) {
                if (length == 4) {
                        *buffer = packet->header[3];
                } else {
                        memcpy(buffer, packet->data, length);
                }
        }

hpsb_read_fail:
        hpsb_free_tlabel(packet);
        free_hpsb_packet(packet);

        return retval;
}


int hpsb_write(struct hpsb_host *host, nodeid_t node, unsigned int generation,
	       u64 addr, quadlet_t *buffer, size_t length)
{
	struct hpsb_packet *packet;
	int retval;

	if (length == 0)
		return -EINVAL;

	BUG_ON(in_interrupt()); // We can't be called in an interrupt, yet

	packet = hpsb_make_writepacket (host, node, addr, buffer, length);

	if (!packet)
		return -ENOMEM;

	packet->generation = generation;
        if (!hpsb_send_packet(packet)) {
		retval = -EINVAL;
		goto hpsb_write_fail;
	}

        down(&packet->state_change);
        down(&packet->state_change);
        retval = hpsb_packet_success(packet);

hpsb_write_fail:
        hpsb_free_tlabel(packet);
        free_hpsb_packet(packet);

        return retval;
}


int hpsb_lock(struct hpsb_host *host, nodeid_t node, unsigned int generation,
	      u64 addr, int extcode, quadlet_t *data, quadlet_t arg)
{
        struct hpsb_packet *packet;
        int retval = 0;

	BUG_ON(in_interrupt()); // We can't be called in an interrupt, yet

	packet = hpsb_make_lockpacket(host, node, addr, extcode, data, arg);        
        if (!packet)
                return -ENOMEM;

	packet->generation = generation;
        if (!hpsb_send_packet(packet)) {
		retval = -EINVAL;
		goto hpsb_lock_fail;
	}
        down(&packet->state_change);
        down(&packet->state_change);
        retval = hpsb_packet_success(packet);

        if (retval == 0) {
                *data = packet->data[0];
        }

hpsb_lock_fail:
        hpsb_free_tlabel(packet);
        free_hpsb_packet(packet);

        return retval;
}

int hpsb_lock64(struct hpsb_host *host, nodeid_t node, unsigned int generation,
		u64 addr, int extcode, octlet_t *data, octlet_t arg)
{
	struct hpsb_packet *packet;
	int retval = 0;

	BUG_ON(in_interrupt()); // We can't be called in an interrupt, yet

	packet = hpsb_make_lock64packet(host, node, addr, extcode, data, arg);
	if (!packet)
		return -ENOMEM;

	packet->generation = generation;
	if (!hpsb_send_packet(packet)) {
		retval = -EINVAL;
		goto hpsb_lock64_fail;
	}
	down(&packet->state_change);
	down(&packet->state_change);
	retval = hpsb_packet_success(packet);

	if (retval == 0)
		*data = (u64)packet->data[1] << 32 | packet->data[0];

hpsb_lock64_fail:
	hpsb_free_tlabel(packet);
	free_hpsb_packet(packet);

        return retval;
}

int hpsb_send_gasp(struct hpsb_host *host, int channel, unsigned int generation,
		   quadlet_t *buffer, size_t length, u32 specifier_id,
		   unsigned int version)
{
	struct hpsb_packet *packet;
	int retval = 0;
	u16 specifier_id_hi = (specifier_id & 0x00ffff00) >> 8;
	u8 specifier_id_lo = specifier_id & 0xff;

	HPSB_VERBOSE("Send GASP: channel = %d, length = %Zd", channel, length);

	length += 8;
    
	packet = hpsb_make_streampacket(host, NULL, length, channel, 3, 0);
	if (!packet)
		return -ENOMEM;

	packet->data[0] = cpu_to_be32((host->node_id << 16) | specifier_id_hi);
	packet->data[1] = cpu_to_be32((specifier_id_lo << 24) | (version & 0x00ffffff));

	memcpy(&(packet->data[2]), buffer, length - 8);

	packet->generation = generation;

	packet->no_waiter = 1;

	if (!hpsb_send_packet(packet)) {
		free_hpsb_packet(packet);
		retval = -EINVAL;
	}

	return retval;
}
