/*
 * IEEE 1394 for Linux
 *
 * Raw interface to the bus
 *
 * Copyright (C) 1999, 2000 Andreas E. Bombe
 *               2001, 2002 Manfred Weihs <weihs@ict.tuwien.ac.at>
 *                     2002 Christian Toegel <christian.toegel@gmx.at>
 *
 * This code is licensed under the GPL.  See the file COPYING in the root
 * directory of the kernel sources for details.
 *
 *
 * Contributions:
 *
 * Manfred Weihs <weihs@ict.tuwien.ac.at>
 *        configuration ROM manipulation
 *        address range mapping
 *        adaptation for new (transparent) loopback mechanism
 *        sending of arbitrary async packets
 * Christian Toegel <christian.toegel@gmx.at>
 *        address range mapping
 *        lock64 request
 *        transmit physical packet
 *        busreset notification control (switch on/off)
 *        busreset with selection of type (short/long)
 *        request_reply
 */

#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/poll.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/version.h>
#include <linux/smp_lock.h>
#include <linux/vmalloc.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
#include <linux/devfs_fs_kernel.h>

#include "ieee1394.h"
#include "ieee1394_types.h"
#include "ieee1394_core.h"
#include "nodemgr.h"
#include "hosts.h"
#include "highlevel.h"
#include "iso.h"
#include "ieee1394_transactions.h"
#include "raw1394.h"
#include "raw1394-private.h"

#if BITS_PER_LONG == 64
#define int2ptr(x) ((void *)x)
#define ptr2int(x) ((u64)x)
#else
#define int2ptr(x) ((void *)(u32)x)
#define ptr2int(x) ((u64)(u32)x)
#endif

#ifdef CONFIG_IEEE1394_VERBOSEDEBUG
#define RAW1394_DEBUG
#endif

#ifdef RAW1394_DEBUG
#define DBGMSG(fmt, args...) \
printk(KERN_INFO "raw1394:" fmt "\n" , ## args)
#else
#define DBGMSG(fmt, args...)
#endif

static devfs_handle_t devfs_handle;

static LIST_HEAD(host_info_list);
static int host_count;
static spinlock_t host_info_lock = SPIN_LOCK_UNLOCKED;
static atomic_t internal_generation = ATOMIC_INIT(0);

static atomic_t iso_buffer_size;
static const int iso_buffer_max = 4 * 1024 * 1024; /* 4 MB */

static struct hpsb_highlevel raw1394_highlevel;

static int arm_read (struct hpsb_host *host, int nodeid, quadlet_t *buffer,
		     u64 addr, size_t length, u16 flags);
static int arm_write (struct hpsb_host *host, int nodeid, int destid,
		      quadlet_t *data, u64 addr, size_t length, u16 flags);
static int arm_lock (struct hpsb_host *host, int nodeid, quadlet_t *store,
             u64 addr, quadlet_t data, quadlet_t arg, int ext_tcode, u16 flags);
static int arm_lock64 (struct hpsb_host *host, int nodeid, octlet_t *store,
               u64 addr, octlet_t data, octlet_t arg, int ext_tcode, u16 flags);
static struct hpsb_address_ops arm_ops = {
	.read	= arm_read,
	.write	= arm_write,
	.lock	= arm_lock,
	.lock64	= arm_lock64,
};

static void queue_complete_cb(struct pending_request *req);

static struct pending_request *__alloc_pending_request(int flags)
{
        struct pending_request *req;

        req = (struct pending_request *)kmalloc(sizeof(struct pending_request),
                                                flags);
        if (req != NULL) {
                memset(req, 0, sizeof(struct pending_request));
                INIT_LIST_HEAD(&req->list);
        }

        return req;
}

static inline struct pending_request *alloc_pending_request(void)
{
        return __alloc_pending_request(SLAB_KERNEL);
}

static void free_pending_request(struct pending_request *req)
{
        if (req->ibs) {
                if (atomic_dec_and_test(&req->ibs->refcount)) {
                        atomic_sub(req->ibs->data_size, &iso_buffer_size);
                        kfree(req->ibs);
                }
        } else if (req->free_data) {
                kfree(req->data);
        }
        free_hpsb_packet(req->packet);
        kfree(req);
}

/* fi->reqlists_lock must be taken */
static void __queue_complete_req(struct pending_request *req)
{
	struct file_info *fi = req->file_info;
	list_del(&req->list);
        list_add_tail(&req->list, &fi->req_complete);

	up(&fi->complete_sem);
        wake_up_interruptible(&fi->poll_wait_complete);
}

static void queue_complete_req(struct pending_request *req)
{
        unsigned long flags;
        struct file_info *fi = req->file_info;

        spin_lock_irqsave(&fi->reqlists_lock, flags);
	__queue_complete_req(req);
        spin_unlock_irqrestore(&fi->reqlists_lock, flags);
}

static void queue_complete_cb(struct pending_request *req)
{
        struct hpsb_packet *packet = req->packet;
        int rcode = (packet->header[1] >> 12) & 0xf;

        switch (packet->ack_code) {
        case ACKX_NONE:
        case ACKX_SEND_ERROR:
                req->req.error = RAW1394_ERROR_SEND_ERROR;
                break;
        case ACKX_ABORTED:
                req->req.error = RAW1394_ERROR_ABORTED;
                break;
        case ACKX_TIMEOUT:
                req->req.error = RAW1394_ERROR_TIMEOUT;
                break;
        default:
                req->req.error = (packet->ack_code << 16) | rcode;
                break;
        }

        if (!((packet->ack_code == ACK_PENDING) && (rcode == RCODE_COMPLETE))) {
                req->req.length = 0;
        }

        if ((req->req.type == RAW1394_REQ_ASYNC_READ) ||
	    (req->req.type == RAW1394_REQ_ASYNC_WRITE) ||
	    (req->req.type == RAW1394_REQ_ASYNC_STREAM) ||
	    (req->req.type == RAW1394_REQ_LOCK) ||
	    (req->req.type == RAW1394_REQ_LOCK64))
                hpsb_free_tlabel(packet);

        queue_complete_req(req);
}


static void add_host(struct hpsb_host *host)
{
        struct host_info *hi;
        unsigned long flags;

        hi = (struct host_info *)kmalloc(sizeof(struct host_info), GFP_KERNEL);

        if (hi != NULL) {
                INIT_LIST_HEAD(&hi->list);
                hi->host = host;
                INIT_LIST_HEAD(&hi->file_info_list);

                spin_lock_irqsave(&host_info_lock, flags);
                list_add_tail(&hi->list, &host_info_list);
                host_count++;
                spin_unlock_irqrestore(&host_info_lock, flags);
        }

        atomic_inc(&internal_generation);
}


static struct host_info *find_host_info(struct hpsb_host *host)
{
        struct list_head *lh;
        struct host_info *hi;

        list_for_each(lh, &host_info_list) {
                hi = list_entry(lh, struct host_info, list);
                if (hi->host == host) {
                        return hi;
                }
        }

        return NULL;
}

static void remove_host(struct hpsb_host *host)
{
        struct host_info *hi;
        unsigned long flags;

        spin_lock_irqsave(&host_info_lock, flags);
        hi = find_host_info(host);

        if (hi != NULL) {
                list_del(&hi->list);
                host_count--;
                /* 
                   FIXME: address ranges should be removed 
                   and fileinfo states should be initialized
                   (including setting generation to 
                   internal-generation ...)
                */
        }
        spin_unlock_irqrestore(&host_info_lock, flags);

        if (hi == NULL) {
                printk(KERN_ERR "raw1394: attempt to remove unknown host "
                       "0x%p\n", host);
                return;
        }

        kfree(hi);

        atomic_inc(&internal_generation);
}

static void host_reset(struct hpsb_host *host)
{
        unsigned long flags;
        struct list_head *lh;
        struct host_info *hi;
        struct file_info *fi;
        struct pending_request *req;

        spin_lock_irqsave(&host_info_lock, flags);
        hi = find_host_info(host);

        if (hi != NULL) {
                list_for_each(lh, &hi->file_info_list) {
                        fi = list_entry(lh, struct file_info, list);
                        if (fi->notification == RAW1394_NOTIFY_ON) {
                                req = __alloc_pending_request(SLAB_ATOMIC);

                                if (req != NULL) {
                                        req->file_info = fi;
                                        req->req.type = RAW1394_REQ_BUS_RESET;
                                        req->req.generation = get_hpsb_generation(host);
                                        req->req.misc = (host->node_id << 16)
                                                | host->node_count;
                                        if (fi->protocol_version > 3) {
                                                req->req.misc |= (NODEID_TO_NODE(host->irm_id)
                                                                  << 8);
                                        }

                                        queue_complete_req(req);
                                }
                        }
                }
        }
        spin_unlock_irqrestore(&host_info_lock, flags);
}

static void iso_receive(struct hpsb_host *host, int channel, quadlet_t *data,
                        size_t length)
{
        unsigned long flags;
        struct list_head *lh;
        struct host_info *hi;
        struct file_info *fi;
        struct pending_request *req;
        struct iso_block_store *ibs = NULL;
        LIST_HEAD(reqs);

        if ((atomic_read(&iso_buffer_size) + length) > iso_buffer_max) {
                HPSB_INFO("dropped iso packet");
                return;
        }

        spin_lock_irqsave(&host_info_lock, flags);
        hi = find_host_info(host);

        if (hi != NULL) {
		list_for_each(lh, &hi->file_info_list) {
                        fi = list_entry(lh, struct file_info, list);

                        if (!(fi->listen_channels & (1ULL << channel))) {
                                continue;
                        }

                        req = __alloc_pending_request(SLAB_ATOMIC);
                        if (!req) break;

                        if (!ibs) {
                                ibs = kmalloc(sizeof(struct iso_block_store)
                                              + length, SLAB_ATOMIC);
                                if (!ibs) {
                                        kfree(req);
                                        break;
                                }

                                atomic_add(length, &iso_buffer_size);
                                atomic_set(&ibs->refcount, 0);
                                ibs->data_size = length;
                                memcpy(ibs->data, data, length);
                        }

                        atomic_inc(&ibs->refcount);

                        req->file_info = fi;
                        req->ibs = ibs;
                        req->data = ibs->data;
                        req->req.type = RAW1394_REQ_ISO_RECEIVE;
                        req->req.generation = get_hpsb_generation(host);
                        req->req.misc = 0;
                        req->req.recvb = ptr2int(fi->iso_buffer);
                        req->req.length = min(length, fi->iso_buffer_length);
                        
                        list_add_tail(&req->list, &reqs);
                }
        }
        spin_unlock_irqrestore(&host_info_lock, flags);

        lh = reqs.next;
        while (lh != &reqs) {
                req = list_entry(lh, struct pending_request, list);
                lh = lh->next;

                queue_complete_req(req);
        }
}

static void fcp_request(struct hpsb_host *host, int nodeid, int direction,
			int cts, u8 *data, size_t length)
{
        unsigned long flags;
        struct list_head *lh;
        struct host_info *hi;
        struct file_info *fi;
        struct pending_request *req;
        struct iso_block_store *ibs = NULL;
        LIST_HEAD(reqs);

        if ((atomic_read(&iso_buffer_size) + length) > iso_buffer_max) {
                HPSB_INFO("dropped fcp request");
                return;
        }

        spin_lock_irqsave(&host_info_lock, flags);
        hi = find_host_info(host);

        if (hi != NULL) {
		list_for_each(lh, &hi->file_info_list) {
                        fi = list_entry(lh, struct file_info, list);

                        if (!fi->fcp_buffer) {
                                continue;
                        }

                        req = __alloc_pending_request(SLAB_ATOMIC);
                        if (!req) break;

                        if (!ibs) {
                                ibs = kmalloc(sizeof(struct iso_block_store)
                                              + length, SLAB_ATOMIC);
                                if (!ibs) {
                                        kfree(req);
                                        break;
                                }

                                atomic_add(length, &iso_buffer_size);
                                atomic_set(&ibs->refcount, 0);
                                ibs->data_size = length;
                                memcpy(ibs->data, data, length);
                        }

                        atomic_inc(&ibs->refcount);

                        req->file_info = fi;
                        req->ibs = ibs;
                        req->data = ibs->data;
                        req->req.type = RAW1394_REQ_FCP_REQUEST;
                        req->req.generation = get_hpsb_generation(host);
                        req->req.misc = nodeid | (direction << 16);
                        req->req.recvb = ptr2int(fi->fcp_buffer);
                        req->req.length = length;
                        
                        list_add_tail(&req->list, &reqs);
                }
        }
        spin_unlock_irqrestore(&host_info_lock, flags);

        lh = reqs.next;
        while (lh != &reqs) {
                req = list_entry(lh, struct pending_request, list);
                lh = lh->next;

                queue_complete_req(req);
        }
}


static ssize_t raw1394_read(struct file *file, char *buffer, size_t count,
                    loff_t *offset_is_ignored)
{
        struct file_info *fi = (struct file_info *)file->private_data;
        struct list_head *lh;
        struct pending_request *req;

        if (count != sizeof(struct raw1394_request)) {
                return -EINVAL;
        }

        if (!access_ok(VERIFY_WRITE, buffer, count)) {
                return -EFAULT;
        }

        if (file->f_flags & O_NONBLOCK) {
                if (down_trylock(&fi->complete_sem)) {
                        return -EAGAIN;
                }
        } else {
                if (down_interruptible(&fi->complete_sem)) {
                        return -ERESTARTSYS;
                }
        }

        spin_lock_irq(&fi->reqlists_lock);
        lh = fi->req_complete.next;
        list_del(lh);
        spin_unlock_irq(&fi->reqlists_lock);

        req = list_entry(lh, struct pending_request, list);

        if (req->req.length) {
                if (copy_to_user(int2ptr(req->req.recvb), req->data,
                                 req->req.length)) {
                        req->req.error = RAW1394_ERROR_MEMFAULT;
                }
        }
        __copy_to_user(buffer, &req->req, sizeof(req->req));

        free_pending_request(req);
        return sizeof(struct raw1394_request);
}


static int state_opened(struct file_info *fi, struct pending_request *req)
{
        if (req->req.type == RAW1394_REQ_INITIALIZE) {
                switch (req->req.misc) {
                case RAW1394_KERNELAPI_VERSION:
                case 3:
                        fi->state = initialized;
                        fi->protocol_version = req->req.misc;
                        req->req.error = RAW1394_ERROR_NONE;
                        req->req.generation = atomic_read(&internal_generation);
                        break;

                default:
                        req->req.error = RAW1394_ERROR_COMPAT;
                        req->req.misc = RAW1394_KERNELAPI_VERSION;
                }
        } else {
                req->req.error = RAW1394_ERROR_STATE_ORDER;
        }

        req->req.length = 0;
        queue_complete_req(req);
        return sizeof(struct raw1394_request);
}

static int state_initialized(struct file_info *fi, struct pending_request *req)
{
        struct list_head *lh;
        struct host_info *hi;
        struct raw1394_khost_list *khl;

        if (req->req.generation != atomic_read(&internal_generation)) {
                req->req.error = RAW1394_ERROR_GENERATION;
                req->req.generation = atomic_read(&internal_generation);
                req->req.length = 0;
                queue_complete_req(req);
                return sizeof(struct raw1394_request);
        }

        switch (req->req.type) {
        case RAW1394_REQ_LIST_CARDS:
                spin_lock_irq(&host_info_lock);
                khl = kmalloc(sizeof(struct raw1394_khost_list) * host_count,
                              SLAB_ATOMIC);

                if (khl != NULL) {
                        req->req.misc = host_count;
                        req->data = (quadlet_t *)khl;
                        
                        list_for_each(lh, &host_info_list) {
                                hi = list_entry(lh, struct host_info, list);

                                khl->nodes = hi->host->node_count;
                                strcpy(khl->name, hi->host->driver->name);

                                khl++;
                        }
                }
                spin_unlock_irq(&host_info_lock);

                if (khl != NULL) {
                        req->req.error = RAW1394_ERROR_NONE;
                        req->req.length = min(req->req.length,
                                              (u32)(sizeof(struct raw1394_khost_list)
                                              * req->req.misc));
                        req->free_data = 1;
                } else {
                        return -ENOMEM;
                }
                break;

        case RAW1394_REQ_SET_CARD:
                lh = NULL;

                spin_lock_irq(&host_info_lock);
                if (req->req.misc < host_count) {
                        lh = host_info_list.next;
                        while (req->req.misc--) {
                                lh = lh->next;
                        }
                        hi = list_entry(lh, struct host_info, list);
                        hpsb_ref_host(hi->host); // XXX Need to handle failure case
                        list_add_tail(&fi->list, &hi->file_info_list);
                        fi->host = hi->host;
                        fi->state = connected;
                }
                spin_unlock_irq(&host_info_lock);

                if (lh != NULL) {
                        req->req.error = RAW1394_ERROR_NONE;
                        req->req.generation = get_hpsb_generation(fi->host);
                        req->req.misc = (fi->host->node_id << 16) 
                                | fi->host->node_count;
                        if (fi->protocol_version > 3) {
                                req->req.misc |= NODEID_TO_NODE(fi->host->irm_id) << 8;
                        }
                } else {
                        req->req.error = RAW1394_ERROR_INVALID_ARG;
                }

                req->req.length = 0;
                break;

        default:
                req->req.error = RAW1394_ERROR_STATE_ORDER;
                req->req.length = 0;
                break;
        }

        queue_complete_req(req);
        return sizeof(struct raw1394_request);
}

static void handle_iso_listen(struct file_info *fi, struct pending_request *req)
{
        int channel = req->req.misc;

        spin_lock_irq(&host_info_lock);
        if ((channel > 63) || (channel < -64)) {
                req->req.error = RAW1394_ERROR_INVALID_ARG;
        } else if (channel >= 0) {
                /* allocate channel req.misc */
                if (fi->listen_channels & (1ULL << channel)) {
                        req->req.error = RAW1394_ERROR_ALREADY;
                } else {
                        if (hpsb_listen_channel(&raw1394_highlevel, fi->host, channel)) {
				req->req.error = RAW1394_ERROR_ALREADY;
			} else {
				fi->listen_channels |= 1ULL << channel;
				fi->iso_buffer = int2ptr(req->req.recvb);
				fi->iso_buffer_length = req->req.length;
			}
                }
        } else {
                /* deallocate channel (one's complement neg) req.misc */
                channel = ~channel;

                if (fi->listen_channels & (1ULL << channel)) {
                        hpsb_unlisten_channel(&raw1394_highlevel, fi->host, channel);
                        fi->listen_channels &= ~(1ULL << channel);
                } else {
                        req->req.error = RAW1394_ERROR_INVALID_ARG;
                }
        }

        req->req.length = 0;
        queue_complete_req(req);
        spin_unlock_irq(&host_info_lock);
}

static void handle_fcp_listen(struct file_info *fi, struct pending_request *req)
{
        if (req->req.misc) {
                if (fi->fcp_buffer) {
                        req->req.error = RAW1394_ERROR_ALREADY;
                } else {
                        fi->fcp_buffer = (u8 *)int2ptr(req->req.recvb);
                }
        } else {
                if (!fi->fcp_buffer) {
                        req->req.error = RAW1394_ERROR_ALREADY;
                } else {
                        fi->fcp_buffer = NULL;
                }
        }

        req->req.length = 0;
        queue_complete_req(req);
}


static int handle_async_request(struct file_info *fi,
                                struct pending_request *req, int node)
{
        struct hpsb_packet *packet = NULL;
        u64 addr = req->req.address & 0xffffffffffffULL;

        switch (req->req.type) {
        case RAW1394_REQ_ASYNC_READ:
		DBGMSG("read_request called");
		packet = hpsb_make_readpacket(fi->host, node, addr, req->req.length);

		if (!packet)
			return -ENOMEM;

		if (req->req.length == 4)
			req->data = &packet->header[3];
		else
			req->data = packet->data;
  
                break;

	case RAW1394_REQ_ASYNC_WRITE:
		DBGMSG("write_request called");

		packet = hpsb_make_writepacket(fi->host, node, addr, NULL,
					       req->req.length);
		if (!packet)
			return -ENOMEM;

		if (req->req.length == 4) {
			if (copy_from_user(&packet->header[3], int2ptr(req->req.sendb),
					req->req.length))
				req->req.error = RAW1394_ERROR_MEMFAULT;
		} else {
			if (copy_from_user(packet->data, int2ptr(req->req.sendb),
					req->req.length))
				req->req.error = RAW1394_ERROR_MEMFAULT;
		}
			
		req->req.length = 0;
	    break;

	case RAW1394_REQ_ASYNC_STREAM:
		DBGMSG("stream_request called");

		packet = hpsb_make_streampacket(fi->host, NULL, req->req.length, node & 0x3f/*channel*/,
                                        (req->req.misc >> 16) & 0x3, req->req.misc & 0xf);
		if (!packet)
			return -ENOMEM;

		if (copy_from_user(packet->data, int2ptr(req->req.sendb),
		                   req->req.length))
			req->req.error = RAW1394_ERROR_MEMFAULT;
			
		req->req.length = 0;
		break;

        case RAW1394_REQ_LOCK:
                DBGMSG("lock_request called");
                if ((req->req.misc == EXTCODE_FETCH_ADD)
                    || (req->req.misc == EXTCODE_LITTLE_ADD)) {
                        if (req->req.length != 4) {
                                req->req.error = RAW1394_ERROR_INVALID_ARG;
                                break;
                        }
                } else {
                        if (req->req.length != 8) {
                                req->req.error = RAW1394_ERROR_INVALID_ARG;
                                break;
                        }
                }

                packet = hpsb_make_lockpacket(fi->host, node, addr,
                                              req->req.misc, NULL, 0);
                if (!packet) return -ENOMEM;

                if (copy_from_user(packet->data, int2ptr(req->req.sendb),
                                   req->req.length)) {
                        req->req.error = RAW1394_ERROR_MEMFAULT;
                        break;
                }

                req->data = packet->data;
                req->req.length = 4;
                break;

        case RAW1394_REQ_LOCK64:
                DBGMSG("lock64_request called");
                if ((req->req.misc == EXTCODE_FETCH_ADD)
                    || (req->req.misc == EXTCODE_LITTLE_ADD)) {
                        if (req->req.length != 8) {
                                req->req.error = RAW1394_ERROR_INVALID_ARG;
                                break;
                        }
                } else {
                        if (req->req.length != 16) {
                                req->req.error = RAW1394_ERROR_INVALID_ARG;
                                break;
                        }
                }
                packet = hpsb_make_lock64packet(fi->host, node, addr,
                                                req->req.misc, NULL, 0);
                if (!packet) return -ENOMEM;

                if (copy_from_user(packet->data, int2ptr(req->req.sendb),
                                   req->req.length)) {
                        req->req.error = RAW1394_ERROR_MEMFAULT;
                        break;
                }

                req->data = packet->data;
                req->req.length = 8;
                break;

        default:
                req->req.error = RAW1394_ERROR_STATE_ORDER;
        }

        req->packet = packet;

        if (req->req.error) {
                req->req.length = 0;
                queue_complete_req(req);
                return sizeof(struct raw1394_request);
        }

	hpsb_set_packet_complete_task(packet, (void(*)(void*))queue_complete_cb, req);

        spin_lock_irq(&fi->reqlists_lock);
        list_add_tail(&req->list, &fi->req_pending);
        spin_unlock_irq(&fi->reqlists_lock);

	packet->generation = req->req.generation;

        if (!hpsb_send_packet(packet)) {
                req->req.error = RAW1394_ERROR_SEND_ERROR;
                req->req.length = 0;
                hpsb_free_tlabel(packet);
                queue_complete_req(req);
        }
        return sizeof(struct raw1394_request);
}

static int handle_iso_send(struct file_info *fi, struct pending_request *req,
                           int channel)
{
        struct hpsb_packet *packet;

	packet = hpsb_make_isopacket(fi->host, req->req.length, channel & 0x3f,
				     (req->req.misc >> 16) & 0x3, req->req.misc & 0xf);
	if (!packet)
		return -ENOMEM;

        packet->speed_code = req->req.address & 0x3;

	req->packet = packet;

        if (copy_from_user(packet->data, int2ptr(req->req.sendb),
                           req->req.length)) {
                req->req.error = RAW1394_ERROR_MEMFAULT;
                req->req.length = 0;
                queue_complete_req(req);
                return sizeof(struct raw1394_request);
        }

        req->req.length = 0;
	hpsb_set_packet_complete_task(packet, (void (*)(void*))queue_complete_req, req);

        spin_lock_irq(&fi->reqlists_lock);
        list_add_tail(&req->list, &fi->req_pending);
        spin_unlock_irq(&fi->reqlists_lock);

	/* Update the generation of the packet just before sending. */
	packet->generation = req->req.generation;

        if (!hpsb_send_packet(packet)) {
                req->req.error = RAW1394_ERROR_SEND_ERROR;
                queue_complete_req(req);
        }

        return sizeof(struct raw1394_request);
}

static int handle_async_send(struct file_info *fi, struct pending_request *req)
{
        struct hpsb_packet *packet;
        int header_length = req->req.misc & 0xffff;
        int expect_response = req->req.misc >> 16;

        if ((header_length > req->req.length) ||
            (header_length  < 12))
        {
                req->req.error = RAW1394_ERROR_INVALID_ARG;
                req->req.length = 0;
                queue_complete_req(req);
                return sizeof(struct raw1394_request);
        } 

        packet = alloc_hpsb_packet(req->req.length-header_length);
        req->packet = packet;
        if (!packet) return -ENOMEM;

        if (copy_from_user(packet->header, int2ptr(req->req.sendb),
                           header_length)) {
                req->req.error = RAW1394_ERROR_MEMFAULT;
                req->req.length = 0;
                queue_complete_req(req);
                return sizeof(struct raw1394_request);
        }

        if (copy_from_user(packet->data, ((u8*) int2ptr(req->req.sendb)) + header_length,
                           packet->data_size)) {
                req->req.error = RAW1394_ERROR_MEMFAULT;
                req->req.length = 0;
                queue_complete_req(req);
                return sizeof(struct raw1394_request);
        }

        packet->type = hpsb_async;
        packet->node_id = packet->header[0] >> 16;
        packet->tcode = (packet->header[0] >> 4) & 0xf;
        packet->tlabel = (packet->header[0] >> 10) &0x3f;
        packet->host = fi->host;
        packet->expect_response = expect_response;
        packet->header_size=header_length;
        packet->data_size=req->req.length-header_length;

        req->req.length = 0;
        hpsb_set_packet_complete_task(packet, (void(*)(void*))queue_complete_cb, req);

        spin_lock_irq(&fi->reqlists_lock);
        list_add_tail(&req->list, &fi->req_pending);
        spin_unlock_irq(&fi->reqlists_lock);

        /* Update the generation of the packet just before sending. */
        packet->generation = req->req.generation;

        if (!hpsb_send_packet(packet)) {
                req->req.error = RAW1394_ERROR_SEND_ERROR;
                queue_complete_req(req);
        }

        return sizeof(struct raw1394_request);
}

static int arm_read (struct hpsb_host *host, int nodeid, quadlet_t *buffer,
		     u64 addr, size_t length, u16 flags)
{
        struct pending_request *req;
        struct list_head *lh;
        struct host_info *hi;
        struct file_info *fi = NULL;
        struct list_head *entry;
        struct arm_addr  *arm_addr = NULL;
        struct arm_request  *arm_req = NULL;
        struct arm_response *arm_resp = NULL;
        int found=0, size=0, rcode=-1;
        struct arm_request_response *arm_req_resp = NULL;

        DBGMSG("arm_read  called by node: %X"
              "addr: %4.4x %8.8x length: %Zu", nodeid,
              (u16) ((addr >>32) & 0xFFFF), (u32) (addr & 0xFFFFFFFF),
              length);
        spin_lock(&host_info_lock);
        hi = find_host_info(host); /* search address-entry */
        if (hi != NULL) {
                list_for_each(lh, &hi->file_info_list) {
                        fi = list_entry(lh, struct file_info, list);
                        entry = fi->addr_list.next;
                        while (entry != &(fi->addr_list)) {
                                arm_addr = list_entry(entry, struct arm_addr, addr_list);
                                if (((arm_addr->start) <= (addr)) && 
                                        ((arm_addr->end) >= (addr+length))) {
                                        found = 1;
                                        break;
                                }
                                entry = entry->next;
                        }
                        if (found) {
                                break;
                        }
                }
        }
        rcode = -1;
        if (!found) {
                printk(KERN_ERR "raw1394: arm_read FAILED addr_entry not found"
                " -> rcode_address_error\n");
                spin_unlock(&host_info_lock);
                return (RCODE_ADDRESS_ERROR);
        } else {
                DBGMSG("arm_read addr_entry FOUND");
        }
        if (arm_addr->rec_length < length) {
                DBGMSG("arm_read blocklength too big -> rcode_data_error");
                rcode = RCODE_DATA_ERROR; /* hardware error, data is unavailable */
        }
        if (rcode == -1) {
                if (arm_addr->access_rights & ARM_READ) {
                        if (!(arm_addr->client_transactions & ARM_READ)) {
                                memcpy(buffer,(arm_addr->addr_space_buffer)+(addr-(arm_addr->start)), 
                                       length);
                                DBGMSG("arm_read -> (rcode_complete)");
                                rcode = RCODE_COMPLETE;
                        }
                } else {
                        rcode = RCODE_TYPE_ERROR; /* function not allowed */
                        DBGMSG("arm_read -> rcode_type_error (access denied)");
                }
        }
        if (arm_addr->notification_options & ARM_READ) {
                DBGMSG("arm_read -> entering notification-section");
                req = __alloc_pending_request(SLAB_ATOMIC);
                if (!req) {
                        DBGMSG("arm_read -> rcode_conflict_error");
                        spin_unlock(&host_info_lock);
                        return(RCODE_CONFLICT_ERROR); /* A resource conflict was detected. 
                                                        The request may be retried */
                }
                if (rcode == RCODE_COMPLETE) {
                        size =  sizeof(struct arm_request)+sizeof(struct arm_response) +
                                length * sizeof(byte_t) +
                                sizeof (struct arm_request_response);
                } else {
                        size =  sizeof(struct arm_request)+sizeof(struct arm_response) +
                                sizeof (struct arm_request_response);
                }
                req->data = kmalloc(size, SLAB_ATOMIC);
                if (!(req->data)) {
                        free_pending_request(req);
                        DBGMSG("arm_read -> rcode_conflict_error");
                        spin_unlock(&host_info_lock);
                        return(RCODE_CONFLICT_ERROR); /* A resource conflict was detected. 
                                                        The request may be retried */
                }
                req->free_data=1;
                req->file_info = fi;
                req->req.type = RAW1394_REQ_ARM;
                req->req.generation = get_hpsb_generation(host);
                req->req.misc = ( ((length << 16) & (0xFFFF0000)) | (ARM_READ & 0xFF));
                req->req.tag  = arm_addr->arm_tag;
                req->req.recvb = arm_addr->recvb;
                req->req.length = size;
                arm_req_resp = (struct arm_request_response *) (req->data);
                arm_req  = (struct arm_request *) ((byte_t *)(req->data) + 
                        (sizeof (struct arm_request_response)));
                arm_resp = (struct arm_response *) ((byte_t *)(arm_req) + 
                        (sizeof(struct arm_request)));
                arm_req->buffer  = NULL;
                arm_resp->buffer = NULL;
                if (rcode == RCODE_COMPLETE) {
                        arm_resp->buffer = ((byte_t *)(arm_resp) + 
                                (sizeof(struct arm_response)));
                        memcpy (arm_resp->buffer,
                                (arm_addr->addr_space_buffer)+(addr-(arm_addr->start)), 
                                length);
                        arm_resp->buffer = int2ptr((arm_addr->recvb) + 
                                sizeof (struct arm_request_response) +
                                sizeof (struct arm_request) +
                                sizeof (struct arm_response));
                }
                arm_resp->buffer_length = (rcode == RCODE_COMPLETE) ? length : 0;
                arm_resp->response_code = rcode;
                arm_req->buffer_length = 0;
                arm_req->generation = req->req.generation;
                arm_req->extended_transaction_code = 0;
                arm_req->destination_offset = addr;
                arm_req->source_nodeid = nodeid;
                arm_req->destination_nodeid = host->node_id;
                arm_req->tlabel = (flags >> 10) & 0x3f;
                arm_req->tcode = (flags >> 4) & 0x0f;
                arm_req_resp->request  = int2ptr((arm_addr->recvb) + 
                        sizeof (struct arm_request_response));
                arm_req_resp->response = int2ptr((arm_addr->recvb) + 
                        sizeof (struct arm_request_response) +
                        sizeof (struct arm_request));
                queue_complete_req(req);
        }
        spin_unlock(&host_info_lock);
        return(rcode);
}

static int arm_write (struct hpsb_host *host, int nodeid, int destid,
		      quadlet_t *data, u64 addr, size_t length, u16 flags)
{
        struct pending_request *req;
        struct list_head *lh;
        struct host_info *hi;
        struct file_info *fi = NULL;
        struct list_head *entry;
        struct arm_addr  *arm_addr = NULL;
        struct arm_request  *arm_req = NULL;
        struct arm_response *arm_resp = NULL;        
        int found=0, size=0, rcode=-1, length_conflict=0;
        struct arm_request_response *arm_req_resp = NULL;

        DBGMSG("arm_write called by node: %X"
              "addr: %4.4x %8.8x length: %Zu", nodeid,
              (u16) ((addr >>32) & 0xFFFF), (u32) (addr & 0xFFFFFFFF),
              length);
        spin_lock(&host_info_lock);
        hi = find_host_info(host); /* search address-entry */
        if (hi != NULL) {
                list_for_each(lh, &hi->file_info_list) {
                        fi = list_entry(lh, struct file_info, list);
                        entry = fi->addr_list.next;
                        while (entry != &(fi->addr_list)) {
                                arm_addr = list_entry(entry, struct arm_addr, addr_list);
                                if (((arm_addr->start) <= (addr)) && 
                                        ((arm_addr->end) >= (addr+length))) {
                                        found = 1;
                                        break;
                                }
                                entry = entry->next;
                        }
                        if (found) {
                                break;
                        }
                }
        }
        rcode = -1;
        if (!found) {
                printk(KERN_ERR "raw1394: arm_write FAILED addr_entry not found"
                " -> rcode_address_error\n");
                spin_unlock(&host_info_lock);
                return (RCODE_ADDRESS_ERROR);
        } else {
                DBGMSG("arm_write addr_entry FOUND");
        }
        if (arm_addr->rec_length < length) {
                DBGMSG("arm_write blocklength too big -> rcode_data_error");
                length_conflict = 1;
                rcode = RCODE_DATA_ERROR; /* hardware error, data is unavailable */
        }
        if (rcode == -1) {
                if (arm_addr->access_rights & ARM_WRITE) {
                        if (!(arm_addr->client_transactions & ARM_WRITE)) {
                                memcpy((arm_addr->addr_space_buffer)+(addr-(arm_addr->start)),
                                        data, length);
                                DBGMSG("arm_write -> (rcode_complete)");
                                rcode = RCODE_COMPLETE;
                        }
                } else {
                        rcode = RCODE_TYPE_ERROR; /* function not allowed */
                        DBGMSG("arm_write -> rcode_type_error (access denied)");
                }
        }
        if (arm_addr->notification_options & ARM_WRITE) {
                DBGMSG("arm_write -> entering notification-section");
                req = __alloc_pending_request(SLAB_ATOMIC);
                if (!req) {
                        DBGMSG("arm_write -> rcode_conflict_error");
                        spin_unlock(&host_info_lock);
                        return(RCODE_CONFLICT_ERROR); /* A resource conflict was detected. 
                                                        The request my be retried */
                }
                size =  sizeof(struct arm_request)+sizeof(struct arm_response) +
                        (length) * sizeof(byte_t) +
                        sizeof (struct arm_request_response);
                req->data = kmalloc(size, SLAB_ATOMIC);
                if (!(req->data)) {
                        free_pending_request(req);
                        DBGMSG("arm_write -> rcode_conflict_error");
                        spin_unlock(&host_info_lock);
                        return(RCODE_CONFLICT_ERROR); /* A resource conflict was detected. 
                                                        The request may be retried */
                }
                req->free_data=1;
                req->file_info = fi;
                req->req.type = RAW1394_REQ_ARM;
                req->req.generation = get_hpsb_generation(host);
                req->req.misc = ( ((length << 16) & (0xFFFF0000)) | (ARM_WRITE & 0xFF));
                req->req.tag  = arm_addr->arm_tag;
                req->req.recvb = arm_addr->recvb;
                req->req.length = size;
                arm_req_resp = (struct arm_request_response *) (req->data);
                arm_req  = (struct arm_request *) ((byte_t *)(req->data) + 
                        (sizeof (struct arm_request_response)));
                arm_resp = (struct arm_response *) ((byte_t *)(arm_req) + 
                        (sizeof(struct arm_request)));
                arm_req->buffer = ((byte_t *)(arm_resp) + 
                        (sizeof(struct arm_response)));
                arm_resp->buffer = NULL;
                memcpy (arm_req->buffer, data, length);
                arm_req->buffer = int2ptr((arm_addr->recvb) + 
                        sizeof (struct arm_request_response) +
                        sizeof (struct arm_request) +
                        sizeof (struct arm_response));
                arm_req->buffer_length = length;
                arm_req->generation = req->req.generation;
                arm_req->extended_transaction_code = 0;
                arm_req->destination_offset = addr;
                arm_req->source_nodeid = nodeid;
                arm_req->destination_nodeid = destid;
                arm_req->tlabel = (flags >> 10) & 0x3f;
                arm_req->tcode = (flags >> 4) & 0x0f;
                arm_resp->buffer_length = 0;
                arm_resp->response_code = rcode;
                arm_req_resp->request  = int2ptr((arm_addr->recvb) + 
                        sizeof (struct arm_request_response));
                arm_req_resp->response = int2ptr((arm_addr->recvb) + 
                        sizeof (struct arm_request_response) +
                        sizeof (struct arm_request));
                queue_complete_req(req);
        }
        spin_unlock(&host_info_lock);
        return(rcode);
}

static int arm_lock (struct hpsb_host *host, int nodeid, quadlet_t *store,
             u64 addr, quadlet_t data, quadlet_t arg, int ext_tcode, u16 flags)
{
        struct pending_request *req;
        struct list_head *lh;
        struct host_info *hi;
        struct file_info *fi = NULL;
        struct list_head *entry;
        struct arm_addr  *arm_addr = NULL;
        struct arm_request  *arm_req = NULL;
        struct arm_response *arm_resp = NULL;        
        int found=0, size=0, rcode=-1;
        quadlet_t old, new;
        struct arm_request_response *arm_req_resp = NULL;

        if (((ext_tcode & 0xFF) == EXTCODE_FETCH_ADD) ||
                ((ext_tcode & 0xFF) == EXTCODE_LITTLE_ADD)) {
                DBGMSG("arm_lock  called by node: %X "
                      "addr: %4.4x %8.8x extcode: %2.2X data: %8.8X", 
                      nodeid, (u16) ((addr >>32) & 0xFFFF), (u32) (addr & 0xFFFFFFFF),
                      ext_tcode & 0xFF , be32_to_cpu(data));
        } else {
                DBGMSG("arm_lock  called by node: %X "
                      "addr: %4.4x %8.8x extcode: %2.2X data: %8.8X arg: %8.8X", 
                      nodeid, (u16) ((addr >>32) & 0xFFFF), (u32) (addr & 0xFFFFFFFF),
                      ext_tcode & 0xFF , be32_to_cpu(data), be32_to_cpu(arg));
        }
        spin_lock(&host_info_lock);
        hi = find_host_info(host); /* search address-entry */
        if (hi != NULL) {
                list_for_each(lh, &hi->file_info_list) {
                        fi = list_entry(lh, struct file_info, list);
                        entry = fi->addr_list.next;
                        while (entry != &(fi->addr_list)) {
                                arm_addr = list_entry(entry, struct arm_addr, addr_list);
                                if (((arm_addr->start) <= (addr)) && 
                                        ((arm_addr->end) >= (addr+sizeof(*store)))) {
                                        found = 1;
                                        break;
                                }
                                entry = entry->next;
                        }
                        if (found) {
                                break;
                        }
                }
        }
        rcode = -1;
        if (!found) {
                printk(KERN_ERR "raw1394: arm_lock FAILED addr_entry not found"
                " -> rcode_address_error\n");
                spin_unlock(&host_info_lock);
                return (RCODE_ADDRESS_ERROR);
        } else {
                DBGMSG("arm_lock addr_entry FOUND");
        }
        if (rcode == -1) {
                if (arm_addr->access_rights & ARM_LOCK) {
                        if (!(arm_addr->client_transactions & ARM_LOCK)) {
                                memcpy(&old,(arm_addr->addr_space_buffer)+(addr-(arm_addr->start)),
                                        sizeof(old));
                                switch (ext_tcode) {
                                        case (EXTCODE_MASK_SWAP):
                                                new = data | (old & ~arg);
                                                break;
                                        case (EXTCODE_COMPARE_SWAP):
                                                if (old == arg) {
                                                        new = data;
                                                } else {
                                                        new = old;
                                                }
                                                break;
                                        case (EXTCODE_FETCH_ADD):
                                                new = cpu_to_be32(be32_to_cpu(data) + be32_to_cpu(old));
                                                break;
                                        case (EXTCODE_LITTLE_ADD):
                                                new = cpu_to_le32(le32_to_cpu(data) + le32_to_cpu(old));
                                                break;
                                        case (EXTCODE_BOUNDED_ADD):
                                                if (old != arg) {
                                                        new = cpu_to_be32(be32_to_cpu(data) + 
                                                                be32_to_cpu(old));
                                                } else {
                                                        new = old;
                                                }
                                                break;
                                        case (EXTCODE_WRAP_ADD):
                                                if (old != arg) {
                                                        new = cpu_to_be32(be32_to_cpu(data) + 
                                                                be32_to_cpu(old));
                                                } else {
                                                        new = data;
                                                }
                                                break;
                                        default:
                                                rcode = RCODE_TYPE_ERROR; /* function not allowed */
                                                printk(KERN_ERR "raw1394: arm_lock FAILED "
                                                "ext_tcode not allowed -> rcode_type_error\n");
                                                break;
                                } /*switch*/
                                if (rcode == -1) {
                                        DBGMSG("arm_lock -> (rcode_complete)");
                                        rcode = RCODE_COMPLETE;
                                        memcpy (store, &old, sizeof(*store));
                                        memcpy ((arm_addr->addr_space_buffer)+
                                                (addr-(arm_addr->start)), 
                                                &new, sizeof(*store));
                                }
                        }
                } else {
                        rcode = RCODE_TYPE_ERROR; /* function not allowed */
                        DBGMSG("arm_lock -> rcode_type_error (access denied)");
                }
        }
        if (arm_addr->notification_options & ARM_LOCK) {
                DBGMSG("arm_lock -> entering notification-section");
                req = __alloc_pending_request(SLAB_ATOMIC);
                if (!req) {
                        DBGMSG("arm_lock -> rcode_conflict_error");
                        spin_unlock(&host_info_lock);
                        return(RCODE_CONFLICT_ERROR); /* A resource conflict was detected. 
                                                        The request may be retried */
                }
                size =  sizeof(struct arm_request)+sizeof(struct arm_response) +
                        3 * sizeof(*store) + 
                        sizeof (struct arm_request_response);  /* maximum */
                req->data = kmalloc(size, SLAB_ATOMIC);
                if (!(req->data)) {
                        free_pending_request(req);
                        DBGMSG("arm_lock -> rcode_conflict_error");
                        spin_unlock(&host_info_lock);
                        return(RCODE_CONFLICT_ERROR); /* A resource conflict was detected. 
                                                        The request may be retried */
                }
                req->free_data=1;
                arm_req_resp = (struct arm_request_response *) (req->data);
                arm_req  = (struct arm_request *) ((byte_t *)(req->data) + 
                        (sizeof (struct arm_request_response)));
                arm_resp = (struct arm_response *) ((byte_t *)(arm_req) + 
                        (sizeof(struct arm_request)));
                arm_req->buffer = ((byte_t *)(arm_resp) + 
                        (sizeof(struct arm_response)));
                arm_resp->buffer = ((byte_t *)(arm_req->buffer) + 
                        (2* sizeof(*store)));
                if ((ext_tcode == EXTCODE_FETCH_ADD) || 
                        (ext_tcode == EXTCODE_LITTLE_ADD)) {
                        arm_req->buffer_length = sizeof(*store);
                        memcpy (arm_req->buffer, &data, sizeof(*store));

                } else {
                        arm_req->buffer_length = 2 * sizeof(*store);
                        memcpy (arm_req->buffer, &arg,  sizeof(*store));
                        memcpy (((arm_req->buffer) + sizeof(*store)), 
                                &data, sizeof(*store));
                }
                if (rcode == RCODE_COMPLETE) {
                        arm_resp->buffer_length = sizeof(*store);
                        memcpy (arm_resp->buffer, &old, sizeof(*store));
                } else {
                        arm_resp->buffer = NULL;
                        arm_resp->buffer_length = 0;
                }
                req->file_info = fi;
                req->req.type = RAW1394_REQ_ARM;
                req->req.generation = get_hpsb_generation(host);
                req->req.misc = ( (((sizeof(*store)) << 16) & (0xFFFF0000)) | 
                        (ARM_LOCK & 0xFF));
                req->req.tag  = arm_addr->arm_tag;
                req->req.recvb = arm_addr->recvb;
                req->req.length = size;
                arm_req->generation = req->req.generation;
                arm_req->extended_transaction_code = ext_tcode;
                arm_req->destination_offset = addr;
                arm_req->source_nodeid = nodeid;
                arm_req->destination_nodeid = host->node_id;
                arm_req->tlabel = (flags >> 10) & 0x3f;
                arm_req->tcode = (flags >> 4) & 0x0f;
                arm_resp->response_code = rcode;
                arm_req_resp->request  = int2ptr((arm_addr->recvb) + 
                        sizeof (struct arm_request_response));
                arm_req_resp->response = int2ptr((arm_addr->recvb) + 
                        sizeof (struct arm_request_response) +
                        sizeof (struct arm_request));
                arm_req->buffer = int2ptr((arm_addr->recvb) + 
                        sizeof (struct arm_request_response) +
                        sizeof (struct arm_request) +
                        sizeof (struct arm_response));
                arm_resp->buffer = int2ptr((arm_addr->recvb) + 
                        sizeof (struct arm_request_response) +
                        sizeof (struct arm_request) +
                        sizeof (struct arm_response) +
                        2* sizeof (*store));
                queue_complete_req(req);
        }
        spin_unlock(&host_info_lock);
        return(rcode);
}

static int arm_lock64 (struct hpsb_host *host, int nodeid, octlet_t *store,
               u64 addr, octlet_t data, octlet_t arg, int ext_tcode, u16 flags)
{
        struct pending_request *req;
        struct list_head *lh;
        struct host_info *hi;
        struct file_info *fi = NULL;
        struct list_head *entry;
        struct arm_addr  *arm_addr = NULL;
        struct arm_request  *arm_req = NULL;
        struct arm_response *arm_resp = NULL;
        int found=0, size=0, rcode=-1;
        octlet_t old, new;
        struct arm_request_response *arm_req_resp = NULL;

        if (((ext_tcode & 0xFF) == EXTCODE_FETCH_ADD) ||
                ((ext_tcode & 0xFF) == EXTCODE_LITTLE_ADD)) {
                DBGMSG("arm_lock64 called by node: %X "
                      "addr: %4.4x %8.8x extcode: %2.2X data: %8.8X %8.8X ",
                      nodeid, (u16) ((addr >>32) & 0xFFFF),
                      (u32) (addr & 0xFFFFFFFF), 
                      ext_tcode & 0xFF , 
                      (u32) ((be64_to_cpu(data) >> 32) & 0xFFFFFFFF), 
                      (u32) (be64_to_cpu(data) & 0xFFFFFFFF));
        } else {
                DBGMSG("arm_lock64 called by node: %X "
                      "addr: %4.4x %8.8x extcode: %2.2X data: %8.8X %8.8X arg: "
                      "%8.8X %8.8X ",
                      nodeid, (u16) ((addr >>32) & 0xFFFF),
                      (u32) (addr & 0xFFFFFFFF), 
                      ext_tcode & 0xFF , 
                      (u32) ((be64_to_cpu(data) >> 32) & 0xFFFFFFFF), 
                      (u32) (be64_to_cpu(data) & 0xFFFFFFFF),
                      (u32) ((be64_to_cpu(arg)  >> 32) & 0xFFFFFFFF), 
                      (u32) (be64_to_cpu(arg)  & 0xFFFFFFFF));
        }
        spin_lock(&host_info_lock);
        hi = find_host_info(host); /* search addressentry in file_info's for host */
        if (hi != NULL) {
                list_for_each(lh, &hi->file_info_list) {
                        fi = list_entry(lh, struct file_info, list);
                        entry = fi->addr_list.next;
                        while (entry != &(fi->addr_list)) {
                                arm_addr = list_entry(entry, struct arm_addr, addr_list);
                                if (((arm_addr->start) <= (addr)) && 
                                        ((arm_addr->end) >= (addr+sizeof(*store)))) {
                                        found = 1;
                                        break;
                                }
                                entry = entry->next;
                        }
                        if (found) {
                                break;
                        }
                }
        }
        rcode = -1;
        if (!found) {
                printk(KERN_ERR "raw1394: arm_lock64 FAILED addr_entry not found"
                " -> rcode_address_error\n");
                spin_unlock(&host_info_lock);
                return (RCODE_ADDRESS_ERROR);
        } else {
                DBGMSG("arm_lock64 addr_entry FOUND");
        }
        if (rcode == -1) {
                if (arm_addr->access_rights & ARM_LOCK) {
                        if (!(arm_addr->client_transactions & ARM_LOCK)) {
                                memcpy(&old,(arm_addr->addr_space_buffer)+(addr-(arm_addr->start)),
                                        sizeof(old));
                                switch (ext_tcode) {
                                        case (EXTCODE_MASK_SWAP):
                                                new = data | (old & ~arg);
                                                break;
                                        case (EXTCODE_COMPARE_SWAP):
                                                if (old == arg) {
                                                        new = data;
                                                } else {
                                                        new = old;
                                                }
                                                break;
                                        case (EXTCODE_FETCH_ADD):
                                                new = cpu_to_be64(be64_to_cpu(data) + be64_to_cpu(old));
                                                break;
                                        case (EXTCODE_LITTLE_ADD):
                                                new = cpu_to_le64(le64_to_cpu(data) + le64_to_cpu(old));
                                                break;
                                        case (EXTCODE_BOUNDED_ADD):
                                                if (old != arg) {
                                                        new = cpu_to_be64(be64_to_cpu(data) + 
                                                                be64_to_cpu(old));
                                                } else {
                                                        new = old;
                                                }
                                                break;
                                        case (EXTCODE_WRAP_ADD):
                                                if (old != arg) {
                                                        new = cpu_to_be64(be64_to_cpu(data) + 
                                                                be64_to_cpu(old));
                                                } else {
                                                        new = data;
                                                }
                                                break;
                                        default:
                                                printk(KERN_ERR "raw1394: arm_lock64 FAILED "
                                                "ext_tcode not allowed -> rcode_type_error\n");
                                                rcode = RCODE_TYPE_ERROR; /* function not allowed */
                                                break;
                                } /*switch*/
                                if (rcode == -1) {
                                        DBGMSG("arm_lock64 -> (rcode_complete)");
                                        rcode = RCODE_COMPLETE;
                                        memcpy (store, &old, sizeof(*store));
                                        memcpy ((arm_addr->addr_space_buffer)+
                                                (addr-(arm_addr->start)), 
                                                &new, sizeof(*store));
                                } 
                        }
                } else {
                        rcode = RCODE_TYPE_ERROR; /* function not allowed */
                        DBGMSG("arm_lock64 -> rcode_type_error (access denied)");
                }
        }
        if (arm_addr->notification_options & ARM_LOCK) {
                DBGMSG("arm_lock64 -> entering notification-section");
                req = __alloc_pending_request(SLAB_ATOMIC);
                if (!req) {
                        spin_unlock(&host_info_lock);
                        DBGMSG("arm_lock64 -> rcode_conflict_error");
                        return(RCODE_CONFLICT_ERROR); /* A resource conflict was detected. 
                                                        The request may be retried */
                }
                size =  sizeof(struct arm_request)+sizeof(struct arm_response) +
                        3 * sizeof(*store) +
                        sizeof (struct arm_request_response); /* maximum */
                req->data = kmalloc(size, SLAB_ATOMIC);
                if (!(req->data)) {
                        free_pending_request(req);
                        spin_unlock(&host_info_lock);
                        DBGMSG("arm_lock64 -> rcode_conflict_error");
                        return(RCODE_CONFLICT_ERROR); /* A resource conflict was detected. 
                                                        The request may be retried */
                }
                req->free_data=1;
                arm_req_resp = (struct arm_request_response *) (req->data);
                arm_req  = (struct arm_request *) ((byte_t *)(req->data) + 
                        (sizeof (struct arm_request_response)));
                arm_resp = (struct arm_response *) ((byte_t *)(arm_req) + 
                        (sizeof(struct arm_request)));
                arm_req->buffer = ((byte_t *)(arm_resp) + 
                        (sizeof(struct arm_response)));
                arm_resp->buffer = ((byte_t *)(arm_req->buffer) + 
                        (2* sizeof(*store)));
                if ((ext_tcode == EXTCODE_FETCH_ADD) || 
                        (ext_tcode == EXTCODE_LITTLE_ADD)) {
                        arm_req->buffer_length = sizeof(*store);
                        memcpy (arm_req->buffer, &data, sizeof(*store));

                } else {
                        arm_req->buffer_length = 2 * sizeof(*store);
                        memcpy (arm_req->buffer, &arg,  sizeof(*store));
                        memcpy (((arm_req->buffer) + sizeof(*store)), 
                                &data, sizeof(*store));
                }
                if (rcode == RCODE_COMPLETE) {
                        arm_resp->buffer_length = sizeof(*store);
                        memcpy (arm_resp->buffer, &old, sizeof(*store));
                } else {
                        arm_resp->buffer = NULL;
                        arm_resp->buffer_length = 0;
                }
                req->file_info = fi;
                req->req.type = RAW1394_REQ_ARM;
                req->req.generation = get_hpsb_generation(host);
                req->req.misc = ( (((sizeof(*store)) << 16) & (0xFFFF0000)) | 
                        (ARM_LOCK & 0xFF));
                req->req.tag  = arm_addr->arm_tag;
                req->req.recvb = arm_addr->recvb;
                req->req.length = size;
                arm_req->generation = req->req.generation;
                arm_req->extended_transaction_code = ext_tcode;
                arm_req->destination_offset = addr;
                arm_req->source_nodeid = nodeid;
                arm_req->destination_nodeid = host->node_id;
                arm_req->tlabel = (flags >> 10) & 0x3f;
                arm_req->tcode = (flags >> 4) & 0x0f;
                arm_resp->response_code = rcode;
                arm_req_resp->request  = int2ptr((arm_addr->recvb) + 
                        sizeof (struct arm_request_response));
                arm_req_resp->response = int2ptr((arm_addr->recvb) + 
                        sizeof (struct arm_request_response) +
                        sizeof (struct arm_request));
                arm_req->buffer = int2ptr((arm_addr->recvb) + 
                        sizeof (struct arm_request_response) +
                        sizeof (struct arm_request) +
                        sizeof (struct arm_response));
                arm_resp->buffer = int2ptr((arm_addr->recvb) + 
                        sizeof (struct arm_request_response) +
                        sizeof (struct arm_request) +
                        sizeof (struct arm_response) +
                        2* sizeof (*store));
                queue_complete_req(req);
        }
        spin_unlock(&host_info_lock);
        return(rcode);
}

static int arm_register(struct file_info *fi, struct pending_request *req)
{
        int retval;
        struct arm_addr *addr;
        struct list_head *lh, *lh_1, *lh_2;
        struct host_info *hi;
        struct file_info *fi_hlp = NULL;
        struct list_head *entry;
        struct arm_addr  *arm_addr = NULL;
        int same_host, another_host;
        unsigned long flags;

        DBGMSG("arm_register called "
              "addr(Offset): %8.8x %8.8x length: %u "
              "rights: %2.2X notify: %2.2X "
              "max_blk_len: %4.4X",
              (u32) ((req->req.address >>32) & 0xFFFF),
              (u32) (req->req.address & 0xFFFFFFFF),
              req->req.length, ((req->req.misc >> 8) & 0xFF),
              (req->req.misc & 0xFF),((req->req.misc >> 16) & 0xFFFF));
        /* check addressrange */
        if ((((req->req.address) & ~(0xFFFFFFFFFFFFULL)) != 0) ||
                (((req->req.address + req->req.length) & ~(0xFFFFFFFFFFFFULL)) != 0)) {
                req->req.length = 0;
                return (-EINVAL);
        }
        /* addr-list-entry for fileinfo */
        addr = (struct arm_addr *)kmalloc(sizeof(struct arm_addr), SLAB_KERNEL); 
        if (!addr) {
                req->req.length = 0;
                return (-ENOMEM);
        } 
        /* allocation of addr_space_buffer */
        addr->addr_space_buffer = (u8 *)vmalloc(req->req.length);
        if (!(addr->addr_space_buffer)) {
                kfree(addr);
                req->req.length = 0;
                return (-ENOMEM);
        }
        /* initialization of addr_space_buffer */
        if ((req->req.sendb)== (unsigned long)NULL) {
                /* init: set 0 */
                memset(addr->addr_space_buffer, 0,req->req.length);
        } else {
                /* init: user -> kernel */
                if (copy_from_user(addr->addr_space_buffer,int2ptr(req->req.sendb),
                        req->req.length)) {
                        vfree(addr->addr_space_buffer);
                        kfree(addr);
                        return (-EFAULT);
                }
        }
        INIT_LIST_HEAD(&addr->addr_list);
        addr->arm_tag   = req->req.tag;
        addr->start     = req->req.address;
        addr->end       = req->req.address + req->req.length;
        addr->access_rights = (u8) (req->req.misc & 0x0F);
        addr->notification_options = (u8) ((req->req.misc >> 4) & 0x0F);
        addr->client_transactions = (u8) ((req->req.misc >> 8) & 0x0F);
        addr->access_rights |= addr->client_transactions;
        addr->notification_options |= addr->client_transactions;
        addr->recvb     = req->req.recvb;
        addr->rec_length = (u16) ((req->req.misc >> 16) & 0xFFFF);
        spin_lock_irqsave(&host_info_lock, flags);
        hi = find_host_info(fi->host);
        same_host = 0;
        another_host = 0;
        /* same host with address-entry containing same addressrange ? */
        list_for_each(lh, &hi->file_info_list) {
                fi_hlp = list_entry(lh, struct file_info, list);
                entry = fi_hlp->addr_list.next;
                while (entry != &(fi_hlp->addr_list)) {
                        arm_addr = list_entry(entry, struct arm_addr, addr_list);
                        if ( (arm_addr->start == addr->start) && 
                                (arm_addr->end == addr->end)) {
                                DBGMSG("same host ownes same "
                                        "addressrange -> EALREADY");
                                same_host = 1;
                                break;
                        }
                        entry = entry->next;
                }
                if (same_host) {
                        break;
                }
        }
        if (same_host) {
                /* addressrange occupied by same host */
                vfree(addr->addr_space_buffer);
                kfree(addr);
                spin_unlock_irqrestore(&host_info_lock, flags);
                return (-EALREADY);
        }
        /* another host with valid address-entry containing same addressrange */
        list_for_each(lh_1, &host_info_list) {
                hi = list_entry(lh_1, struct host_info, list);
                if (hi->host != fi->host) {
                        list_for_each(lh_2, &hi->file_info_list) {
                                fi_hlp = list_entry(lh_2, struct file_info, list);
                                entry = fi_hlp->addr_list.next;
                                while (entry != &(fi_hlp->addr_list)) {
                                        arm_addr = list_entry(entry, struct arm_addr, addr_list);
                                        if ( (arm_addr->start == addr->start) && 
                                                (arm_addr->end == addr->end)) {
                                                DBGMSG("another host ownes same "
                                                "addressrange");
                                                another_host = 1;
                                                break;
                                        }
                                        entry = entry->next;
                                }
                                if (another_host) {
                                        break;
                                }
                        }
                }
        }
        if (another_host) {
                DBGMSG("another hosts entry is valid -> SUCCESS");
                if (copy_to_user(int2ptr(req->req.recvb),
                        int2ptr(&addr->start),sizeof(u64))) {
                        printk(KERN_ERR "raw1394: arm_register failed "
                              " address-range-entry is invalid -> EFAULT !!!\n");
                        vfree(addr->addr_space_buffer);
                        kfree(addr);
                        spin_unlock_irqrestore(&host_info_lock, flags);
                        return (-EFAULT);
                }
                free_pending_request(req); /* immediate success or fail */
                /* INSERT ENTRY */
                list_add_tail(&addr->addr_list, &fi->addr_list);
                spin_unlock_irqrestore(&host_info_lock, flags);
                return sizeof(struct raw1394_request);
        }
        retval = hpsb_register_addrspace(&raw1394_highlevel, &arm_ops, req->req.address,
                req->req.address + req->req.length);
        if (retval) {
               /* INSERT ENTRY */
               list_add_tail(&addr->addr_list, &fi->addr_list);
        } else {
                DBGMSG("arm_register failed errno: %d \n",retval);
                vfree(addr->addr_space_buffer);
                kfree(addr);
                spin_unlock_irqrestore(&host_info_lock, flags);
                return (-EALREADY); 
        }
        spin_unlock_irqrestore(&host_info_lock, flags);
        free_pending_request(req); /* immediate success or fail */
        return sizeof(struct raw1394_request);
}

static int arm_unregister(struct file_info *fi, struct pending_request *req)
{
        int found  = 0;
        int retval = 0;
        struct list_head *entry;
        struct arm_addr  *addr = NULL;
        struct list_head *lh_1, *lh_2;
        struct host_info *hi;
        struct file_info *fi_hlp = NULL;
        struct arm_addr  *arm_addr = NULL;
        int another_host;
        unsigned long flags;

        DBGMSG("arm_Unregister called addr(Offset): "
              "%8.8x %8.8x",
              (u32) ((req->req.address >>32) & 0xFFFF),
              (u32) (req->req.address & 0xFFFFFFFF));
        spin_lock_irqsave(&host_info_lock, flags);
        /* get addr */
        entry = fi->addr_list.next;
        while (entry != &(fi->addr_list)) {
                addr = list_entry(entry, struct arm_addr, addr_list);
                if (addr->start == req->req.address) {
                        found = 1;
                        break;
                }
                entry = entry->next;
        }
        if (!found) {
                DBGMSG("arm_Unregister addr not found");
                spin_unlock_irqrestore(&host_info_lock, flags);
                return (-EINVAL);
        }
        DBGMSG("arm_Unregister addr found");
        another_host = 0;
        /* another host with valid address-entry containing 
           same addressrange */
        list_for_each(lh_1, &host_info_list) {
                hi = list_entry(lh_1, struct host_info, list);
                if (hi->host != fi->host) {
                        list_for_each(lh_2, &hi->file_info_list) {
                                fi_hlp = list_entry(lh_2, struct file_info, list);
                                entry = fi_hlp->addr_list.next;
                                while (entry != &(fi_hlp->addr_list)) {
                                        arm_addr = list_entry(entry, 
                                                struct arm_addr, addr_list);
                                        if (arm_addr->start == 
                                                addr->start) {
                                                DBGMSG("another host ownes "
                                                "same addressrange");
                                                another_host = 1;
                                                break;
                                        }
                                        entry = entry->next;
                                }
                                if (another_host) {
                                        break;
                                }
                        }
                }
        }
        if (another_host) {
                DBGMSG("delete entry from list -> success");
                list_del(&addr->addr_list);
                vfree(addr->addr_space_buffer);
                kfree(addr);
                free_pending_request(req); /* immediate success or fail */
                spin_unlock_irqrestore(&host_info_lock, flags);
                return sizeof(struct raw1394_request);
        } 
        retval = hpsb_unregister_addrspace(&raw1394_highlevel, addr->start);
        if (!retval) {
                printk(KERN_ERR "raw1394: arm_Unregister failed -> EINVAL\n");
                spin_unlock_irqrestore(&host_info_lock, flags);
                return (-EINVAL);
        }
        DBGMSG("delete entry from list -> success");
        list_del(&addr->addr_list);
        spin_unlock_irqrestore(&host_info_lock, flags);
        vfree(addr->addr_space_buffer);
        kfree(addr);
        free_pending_request(req); /* immediate success or fail */
        return sizeof(struct raw1394_request);
}

static int reset_notification(struct file_info *fi, struct pending_request *req)
{
        DBGMSG("reset_notification called - switch %s ",
                (req->req.misc == RAW1394_NOTIFY_OFF)?"OFF":"ON");
        if ((req->req.misc == RAW1394_NOTIFY_OFF) ||
                (req->req.misc == RAW1394_NOTIFY_ON)) {
                fi->notification=(u8)req->req.misc;
                free_pending_request(req); /* we have to free the request, because we queue no response, and therefore nobody will free it */
                return sizeof(struct raw1394_request);
        } 
        /* error EINVAL (22) invalid argument */
        return (-EINVAL);
}

static int write_phypacket(struct file_info *fi, struct pending_request *req)
{
        struct hpsb_packet *packet = NULL;
        int retval=0;
        quadlet_t data;

        data = be32_to_cpu((u32)req->req.sendb);
        DBGMSG("write_phypacket called - quadlet 0x%8.8x ",data);
        packet = hpsb_make_phypacket (fi->host, data);
        if (!packet) return -ENOMEM;
        req->req.length=0;
        req->packet=packet;
        hpsb_set_packet_complete_task(packet, (void(*)(void*))queue_complete_cb, req);
        spin_lock_irq(&fi->reqlists_lock);
        list_add_tail(&req->list, &fi->req_pending);
        spin_unlock_irq(&fi->reqlists_lock);
        packet->generation = req->req.generation;
        retval = hpsb_send_packet(packet);
        DBGMSG("write_phypacket send_packet called => retval: %d ",
                retval);
        if (! retval) {
                req->req.error = RAW1394_ERROR_SEND_ERROR;
                req->req.length = 0;
                queue_complete_req(req);
        }
        return sizeof(struct raw1394_request);
}

static int get_config_rom(struct file_info *fi, struct pending_request *req)
{
        size_t return_size;
        unsigned char rom_version;
        int ret=sizeof(struct raw1394_request);
        quadlet_t *data = kmalloc(req->req.length, SLAB_KERNEL);
        int status;
        if (!data) return -ENOMEM;
        status = hpsb_get_config_rom(fi->host, data, 
                req->req.length, &return_size, &rom_version);
        if (copy_to_user(int2ptr(req->req.recvb), data, 
                req->req.length))
                ret = -EFAULT;
        if (copy_to_user(int2ptr(req->req.tag), &return_size, 
                sizeof(return_size)))
                ret = -EFAULT;
        if (copy_to_user(int2ptr(req->req.address), &rom_version, 
                sizeof(rom_version)))
                ret = -EFAULT;
        if (copy_to_user(int2ptr(req->req.sendb), &status, 
                sizeof(status)))
                ret = -EFAULT;
        kfree(data);
        if (ret >= 0) {
                free_pending_request(req); /* we have to free the request, because we queue no response, and therefore nobody will free it */
        }
        return ret;
}

static int update_config_rom(struct file_info *fi, struct pending_request *req)
{
        int ret=sizeof(struct raw1394_request);
        quadlet_t *data = kmalloc(req->req.length, SLAB_KERNEL);
        if (!data) return -ENOMEM;
        if (copy_from_user(data,int2ptr(req->req.sendb), 
                req->req.length)) {
                ret= -EFAULT;
        } else {
                int status = hpsb_update_config_rom(fi->host, 
                        data, req->req.length, 
                        (unsigned char) req->req.misc);
                if (copy_to_user(int2ptr(req->req.recvb), 
                        &status, sizeof(status)))
                        ret = -ENOMEM;
        }
        kfree(data);
        if (ret >= 0) {
                free_pending_request(req); /* we have to free the request, because we queue no response, and therefore nobody will free it */
        }
        return ret;
}

static int state_connected(struct file_info *fi, struct pending_request *req)
{
        int node = req->req.address >> 48;

        req->req.error = RAW1394_ERROR_NONE;

        switch (req->req.type) {

        case RAW1394_REQ_ECHO:
                queue_complete_req(req);
                return sizeof(struct raw1394_request);

        case RAW1394_REQ_ISO_SEND:
                return handle_iso_send(fi, req, node);

        case RAW1394_REQ_ARM_REGISTER:
                return arm_register(fi, req);

        case RAW1394_REQ_ARM_UNREGISTER:
                return arm_unregister(fi, req);

        case RAW1394_REQ_RESET_NOTIFY:
                return reset_notification(fi, req);

        case RAW1394_REQ_ISO_LISTEN:
                handle_iso_listen(fi, req);
                return sizeof(struct raw1394_request);

        case RAW1394_REQ_FCP_LISTEN:
                handle_fcp_listen(fi, req);
                return sizeof(struct raw1394_request);

        case RAW1394_REQ_RESET_BUS:
                if (req->req.misc == RAW1394_LONG_RESET) {
                        DBGMSG("busreset called (type: LONG)");
                        hpsb_reset_bus(fi->host, LONG_RESET);
                        free_pending_request(req); /* we have to free the request, because we queue no response, and therefore nobody will free it */
                        return sizeof(struct raw1394_request);
                }
                if (req->req.misc == RAW1394_SHORT_RESET) {
                        DBGMSG("busreset called (type: SHORT)");
                        hpsb_reset_bus(fi->host, SHORT_RESET);
                        free_pending_request(req); /* we have to free the request, because we queue no response, and therefore nobody will free it */
                        return sizeof(struct raw1394_request);
                }
                /* error EINVAL (22) invalid argument */
                return (-EINVAL);
        case RAW1394_REQ_GET_ROM:
                return get_config_rom(fi, req);

        case RAW1394_REQ_UPDATE_ROM:
                return update_config_rom(fi, req);
        }

        if (req->req.generation != get_hpsb_generation(fi->host)) {
                req->req.error = RAW1394_ERROR_GENERATION;
                req->req.generation = get_hpsb_generation(fi->host);
                req->req.length = 0;
                queue_complete_req(req);
                return sizeof(struct raw1394_request);
        }

        switch (req->req.type) {
        case RAW1394_REQ_PHYPACKET:
                return write_phypacket(fi, req);
        case RAW1394_REQ_ASYNC_SEND:
                return handle_async_send(fi, req);
        }

        if (req->req.length == 0) {
                req->req.error = RAW1394_ERROR_INVALID_ARG;
                queue_complete_req(req);
                return sizeof(struct raw1394_request);
        }

        return handle_async_request(fi, req, node);
}


static ssize_t raw1394_write(struct file *file, const char *buffer, size_t count,
                     loff_t *offset_is_ignored)
{
        struct file_info *fi = (struct file_info *)file->private_data;
        struct pending_request *req;
        ssize_t retval = 0;

        if (count != sizeof(struct raw1394_request)) {
                return -EINVAL;
        }

        req = alloc_pending_request();
        if (req == NULL) {
                return -ENOMEM;
        }
        req->file_info = fi;

        if (copy_from_user(&req->req, buffer, sizeof(struct raw1394_request))) {
                free_pending_request(req);
                return -EFAULT;
        }

        switch (fi->state) {
        case opened:
                retval = state_opened(fi, req);
                break;

        case initialized:
                retval = state_initialized(fi, req);
                break;

        case connected:
                retval = state_connected(fi, req);
                break;
        }

        if (retval < 0) {
                free_pending_request(req);
        }

        return retval;
}

/* rawiso operations */

/* check if any RAW1394_REQ_RAWISO_ACTIVITY event is already in the
 * completion queue (reqlists_lock must be taken) */
static inline int __rawiso_event_in_queue(struct file_info *fi)
{
	struct list_head *lh;
	struct pending_request *req;

	list_for_each(lh, &fi->req_complete) {
		req = list_entry(lh, struct pending_request, list);
		if (req->req.type == RAW1394_REQ_RAWISO_ACTIVITY) {
			return 1;
		}
	}

	return 0;
}

/* put a RAWISO_ACTIVITY event in the queue, if one isn't there already */
static void queue_rawiso_event(struct file_info *fi)
{
	unsigned long flags;

	spin_lock_irqsave(&fi->reqlists_lock, flags);

	/* only one ISO activity event may be in the queue */
	if (!__rawiso_event_in_queue(fi)) {
		struct pending_request *req = __alloc_pending_request(SLAB_ATOMIC);

		if (req) {
			req->file_info = fi;
			req->req.type = RAW1394_REQ_RAWISO_ACTIVITY;
			req->req.generation = get_hpsb_generation(fi->host);
			__queue_complete_req(req);
		} else {
			/* on allocation failure, signal an overflow */
			if (fi->iso_handle) {
				atomic_inc(&fi->iso_handle->overflows);
			}
		}
	}
	spin_unlock_irqrestore(&fi->reqlists_lock, flags);
}

static void rawiso_activity_cb(struct hpsb_iso *iso)
{
	unsigned long flags;
        struct list_head *lh;
        struct host_info *hi;

        spin_lock_irqsave(&host_info_lock, flags);
        hi = find_host_info(iso->host);

	if (hi != NULL) {
		list_for_each(lh, &hi->file_info_list) {
			struct file_info *fi = list_entry(lh, struct file_info, list);
			if (fi->iso_handle == iso)
				queue_rawiso_event(fi);
		}
	}

	spin_unlock_irqrestore(&host_info_lock, flags);
}

/* helper function - gather all the kernel iso status bits for returning to user-space */
static void raw1394_iso_fill_status(struct hpsb_iso *iso, struct raw1394_iso_status *stat)
{
	stat->config.data_buf_size = iso->buf_size;
	stat->config.buf_packets = iso->buf_packets;
	stat->config.channel = iso->channel;
	stat->config.speed = iso->speed;
	stat->config.irq_interval = iso->irq_interval;
	stat->n_packets = hpsb_iso_n_ready(iso);
	stat->overflows = atomic_read(&iso->overflows);
	stat->xmit_cycle = iso->xmit_cycle;
}

static int raw1394_iso_xmit_init(struct file_info *fi, void *uaddr)
{
	struct raw1394_iso_status stat;

	if (!fi->host)
		return -EINVAL;

	if (copy_from_user(&stat, uaddr, sizeof(stat)))
		return -EFAULT;

	fi->iso_handle = hpsb_iso_xmit_init(fi->host,
					    stat.config.data_buf_size,
					    stat.config.buf_packets,
					    stat.config.channel,
					    stat.config.speed,
					    stat.config.irq_interval,
					    rawiso_activity_cb);
	if (!fi->iso_handle)
		return -ENOMEM;

	fi->iso_state = RAW1394_ISO_XMIT;

	raw1394_iso_fill_status(fi->iso_handle, &stat);
	if (copy_to_user(uaddr, &stat, sizeof(stat)))
		return -EFAULT;

	/* queue an event to get things started */
	rawiso_activity_cb(fi->iso_handle);

	return 0;
}

static int raw1394_iso_recv_init(struct file_info *fi, void *uaddr)
{
	struct raw1394_iso_status stat;

	if (!fi->host)
		return -EINVAL;

	if (copy_from_user(&stat, uaddr, sizeof(stat)))
		return -EFAULT;

	fi->iso_handle = hpsb_iso_recv_init(fi->host,
					    stat.config.data_buf_size,
					    stat.config.buf_packets,
					    stat.config.channel,
					    stat.config.irq_interval,
					    rawiso_activity_cb);
	if (!fi->iso_handle)
		return -ENOMEM;

	fi->iso_state = RAW1394_ISO_RECV;

	raw1394_iso_fill_status(fi->iso_handle, &stat);
	if (copy_to_user(uaddr, &stat, sizeof(stat)))
		return -EFAULT;
	return 0;
}

static int raw1394_iso_get_status(struct file_info *fi, void *uaddr)
{
	struct raw1394_iso_status stat;
	struct hpsb_iso *iso = fi->iso_handle;

	raw1394_iso_fill_status(fi->iso_handle, &stat);
	if (copy_to_user(uaddr, &stat, sizeof(stat)))
		return -EFAULT;

	/* reset overflow counter */
	atomic_set(&iso->overflows, 0);

	return 0;
}

/* copy N packet_infos out of the ringbuffer into user-supplied array */
static int raw1394_iso_recv_packets(struct file_info *fi, void *uaddr)
{
	struct raw1394_iso_packets upackets;
	unsigned int packet = fi->iso_handle->first_packet;
	int i;

	if (copy_from_user(&upackets, uaddr, sizeof(upackets)))
		return -EFAULT;

	if (upackets.n_packets > hpsb_iso_n_ready(fi->iso_handle))
		return -EINVAL;

	/* ensure user-supplied buffer is accessible and big enough */
	if (verify_area(VERIFY_WRITE, upackets.infos,
		       upackets.n_packets * sizeof(struct raw1394_iso_packet_info)))
		return -EFAULT;

	/* copy the packet_infos out */
	for (i = 0; i < upackets.n_packets; i++) {
		if (__copy_to_user(&upackets.infos[i],
				  &fi->iso_handle->infos[packet],
				  sizeof(struct raw1394_iso_packet_info)))
			return -EFAULT;
		
		packet = (packet + 1) % fi->iso_handle->buf_packets;
	}

	return 0;
}

/* copy N packet_infos from user to ringbuffer, and queue them for transmission */
static int raw1394_iso_send_packets(struct file_info *fi, void *uaddr)
{
	struct raw1394_iso_packets upackets;
	int i, rv;

	if (copy_from_user(&upackets, uaddr, sizeof(upackets)))
		return -EFAULT;

	if (upackets.n_packets > hpsb_iso_n_ready(fi->iso_handle))
		return -EINVAL;

	/* ensure user-supplied buffer is accessible and big enough */
	if (verify_area(VERIFY_READ, upackets.infos,
		       upackets.n_packets * sizeof(struct raw1394_iso_packet_info)))
		return -EFAULT;

	/* copy the infos structs in and queue the packets */
	for (i = 0; i < upackets.n_packets; i++) {
		struct raw1394_iso_packet_info info;

		if (__copy_from_user(&info, &upackets.infos[i],
				    sizeof(struct raw1394_iso_packet_info)))
			return -EFAULT;

		rv = hpsb_iso_xmit_queue_packet(fi->iso_handle, info.offset,
						info.len, info.tag, info.sy);
		if (rv)
			return rv;
	}

	return 0;
}

static void raw1394_iso_shutdown(struct file_info *fi)
{
	if (fi->iso_handle)
		hpsb_iso_shutdown(fi->iso_handle);

	fi->iso_handle = NULL;
	fi->iso_state = RAW1394_ISO_INACTIVE;
}

/* mmap the rawiso xmit/recv buffer */
static int raw1394_mmap(struct file *file, struct vm_area_struct *vma)
{
	struct file_info *fi = file->private_data;

	if (fi->iso_state == RAW1394_ISO_INACTIVE)
		return -EINVAL;

	return dma_region_mmap(&fi->iso_handle->data_buf, file, vma);
}

/* ioctl is only used for rawiso operations */
static int raw1394_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
	struct file_info *fi = file->private_data;

	switch(fi->iso_state) {
	case RAW1394_ISO_INACTIVE:
		switch(cmd) {
		case RAW1394_IOC_ISO_XMIT_INIT:
			return raw1394_iso_xmit_init(fi, (void*) arg);
		case RAW1394_IOC_ISO_RECV_INIT:
			return raw1394_iso_recv_init(fi, (void*) arg);
		default:
			break;
		}
		break;
	case RAW1394_ISO_RECV:
		switch(cmd) {
		case RAW1394_IOC_ISO_RECV_START: {
			/* copy args from user-space */
			int args[3];
			if (copy_from_user(&args[0], (void*) arg, sizeof(args)))
				return -EFAULT;
			return hpsb_iso_recv_start(fi->iso_handle, args[0], args[1], args[2]);
		}
		case RAW1394_IOC_ISO_XMIT_RECV_STOP:
			hpsb_iso_stop(fi->iso_handle);
			return 0;
		case RAW1394_IOC_ISO_RECV_LISTEN_CHANNEL:
			return hpsb_iso_recv_listen_channel(fi->iso_handle, arg);
		case RAW1394_IOC_ISO_RECV_UNLISTEN_CHANNEL:
			return hpsb_iso_recv_unlisten_channel(fi->iso_handle, arg);
		case RAW1394_IOC_ISO_RECV_SET_CHANNEL_MASK: {
			/* copy the u64 from user-space */
			u64 mask;
			if (copy_from_user(&mask, (void*) arg, sizeof(mask)))
				return -EFAULT;
			return hpsb_iso_recv_set_channel_mask(fi->iso_handle, mask);
		}
		case RAW1394_IOC_ISO_GET_STATUS:
			return raw1394_iso_get_status(fi, (void*) arg);
		case RAW1394_IOC_ISO_RECV_PACKETS:
			return raw1394_iso_recv_packets(fi, (void*) arg);
		case RAW1394_IOC_ISO_RECV_RELEASE_PACKETS:
			return hpsb_iso_recv_release_packets(fi->iso_handle, arg);
		case RAW1394_IOC_ISO_RECV_FLUSH:
			return hpsb_iso_recv_flush(fi->iso_handle);
		case RAW1394_IOC_ISO_SHUTDOWN:
			raw1394_iso_shutdown(fi);
			return 0;
		case RAW1394_IOC_ISO_QUEUE_ACTIVITY:
			queue_rawiso_event(fi);
			return 0;
		}
		break;
	case RAW1394_ISO_XMIT:
		switch(cmd) {
		case RAW1394_IOC_ISO_XMIT_START: {
			/* copy two ints from user-space */
			int args[2];
			if (copy_from_user(&args[0], (void*) arg, sizeof(args)))
				return -EFAULT;
			return hpsb_iso_xmit_start(fi->iso_handle, args[0], args[1]);
		}
		case RAW1394_IOC_ISO_XMIT_SYNC:
			return hpsb_iso_xmit_sync(fi->iso_handle);
		case RAW1394_IOC_ISO_XMIT_RECV_STOP:
			hpsb_iso_stop(fi->iso_handle);
			return 0;
		case RAW1394_IOC_ISO_GET_STATUS:
			return raw1394_iso_get_status(fi, (void*) arg);
		case RAW1394_IOC_ISO_XMIT_PACKETS:
			return raw1394_iso_send_packets(fi, (void*) arg);
		case RAW1394_IOC_ISO_SHUTDOWN:
			raw1394_iso_shutdown(fi);
			return 0;
		case RAW1394_IOC_ISO_QUEUE_ACTIVITY:
			queue_rawiso_event(fi);
			return 0;
		}
		break;
	default:
		break;
	}

	return -EINVAL;
}

static unsigned int raw1394_poll(struct file *file, poll_table *pt)
{
        struct file_info *fi = file->private_data;
        unsigned int mask = POLLOUT | POLLWRNORM;

        poll_wait(file, &fi->poll_wait_complete, pt);

        spin_lock_irq(&fi->reqlists_lock);
        if (!list_empty(&fi->req_complete)) {
                mask |= POLLIN | POLLRDNORM;
        }
        spin_unlock_irq(&fi->reqlists_lock);

        return mask;
}

static int raw1394_open(struct inode *inode, struct file *file)
{
        struct file_info *fi;

        if (ieee1394_file_to_instance(file) > 0) {
                return -ENXIO;
        }

        fi = kmalloc(sizeof(struct file_info), SLAB_KERNEL);
        if (fi == NULL)
                return -ENOMEM;
        
        memset(fi, 0, sizeof(struct file_info));
        fi->notification = (u8) RAW1394_NOTIFY_ON; /* busreset notification */

        INIT_LIST_HEAD(&fi->list);
        fi->state = opened;
        INIT_LIST_HEAD(&fi->req_pending);
        INIT_LIST_HEAD(&fi->req_complete);
        sema_init(&fi->complete_sem, 0);
        spin_lock_init(&fi->reqlists_lock);
        init_waitqueue_head(&fi->poll_wait_complete);
        INIT_LIST_HEAD(&fi->addr_list);

        file->private_data = fi;

        return 0;
}

static int raw1394_release(struct inode *inode, struct file *file)
{
        struct file_info *fi = file->private_data;
        struct list_head *lh;
        struct pending_request *req;
        int done = 0, i, fail = 0;
        int retval = 0;
        struct list_head *entry;
        struct arm_addr  *addr = NULL;
        struct list_head *lh_1, *lh_2;
        struct host_info *hi;
        struct file_info *fi_hlp = NULL;
        struct arm_addr  *arm_addr = NULL;
        int another_host;

	if (fi->iso_state != RAW1394_ISO_INACTIVE)
		raw1394_iso_shutdown(fi);

        for (i = 0; i < 64; i++) {
                if (fi->listen_channels & (1ULL << i)) {
                        hpsb_unlisten_channel(&raw1394_highlevel, fi->host, i);
                }
        }

        spin_lock_irq(&host_info_lock);
        fi->listen_channels = 0;
        spin_unlock_irq(&host_info_lock);

        fail = 0;
        /* set address-entries invalid */
        spin_lock_irq(&host_info_lock);

        while (!list_empty(&fi->addr_list)) {
                another_host = 0;
                lh = fi->addr_list.next;
                addr = list_entry(lh, struct arm_addr, addr_list);
                /* another host with valid address-entry containing 
                   same addressrange? */
                list_for_each(lh_1, &host_info_list) {
                        hi = list_entry(lh_1, struct host_info, list);
                        if (hi->host != fi->host) {
                                list_for_each(lh_2, &hi->file_info_list) {
                                        fi_hlp = list_entry(lh_2, struct file_info, list);
                                        entry = fi_hlp->addr_list.next;
                                        while (entry != &(fi_hlp->addr_list)) {
                                                arm_addr = list_entry(entry, 
                                                        struct arm_addr, addr_list);
                                                if (arm_addr->start == 
                                                        addr->start) {
                                                        DBGMSG("raw1394_release: "
                                                        "another host ownes "
                                                        "same addressrange");
                                                        another_host = 1;
                                                        break;
                                                }
                                                entry = entry->next;
                                        }
                                        if (another_host) {
                                                break;
                                        }
                                }
                        }
                }
                if (!another_host) {
                        DBGMSG("raw1394_release: call hpsb_arm_unregister");
                        retval = hpsb_unregister_addrspace(&raw1394_highlevel, addr->start);
                        if (!retval) {
                                ++fail;
                                printk(KERN_ERR "raw1394_release arm_Unregister failed\n");
                        }
                }
                DBGMSG("raw1394_release: delete addr_entry from list");
                list_del(&addr->addr_list);
                vfree(addr->addr_space_buffer);
                kfree(addr);
        } /* while */
        spin_unlock_irq(&host_info_lock);
        if (fail > 0) {
                printk(KERN_ERR "raw1394: during addr_list-release "
                        "error(s) occurred \n");
        }

        while (!done) {
                spin_lock_irq(&fi->reqlists_lock);

                while (!list_empty(&fi->req_complete)) {
                        lh = fi->req_complete.next;
                        list_del(lh);

                        req = list_entry(lh, struct pending_request, list);

                        free_pending_request(req);
                }

                if (list_empty(&fi->req_pending)) done = 1;

                spin_unlock_irq(&fi->reqlists_lock);

                if (!done) down_interruptible(&fi->complete_sem);
        }

        if (fi->state == connected) {
                spin_lock_irq(&host_info_lock);
                list_del(&fi->list);
                spin_unlock_irq(&host_info_lock);

                hpsb_unref_host(fi->host);
        }

        kfree(fi);

        return 0;
}


/*** HOTPLUG STUFF **********************************************************/
/*
 * Export information about protocols/devices supported by this driver.
 */
static struct ieee1394_device_id raw1394_id_table[] = {
	{
		.match_flags	= IEEE1394_MATCH_SPECIFIER_ID | IEEE1394_MATCH_VERSION,
		.specifier_id	= AVC_UNIT_SPEC_ID_ENTRY & 0xffffff,
		.version	= AVC_SW_VERSION_ENTRY & 0xffffff
	},
	{
		.match_flags	= IEEE1394_MATCH_SPECIFIER_ID | IEEE1394_MATCH_VERSION,
		.specifier_id	= CAMERA_UNIT_SPEC_ID_ENTRY & 0xffffff,
		.version	= CAMERA_SW_VERSION_ENTRY & 0xffffff
	},
	{ }
};

MODULE_DEVICE_TABLE(ieee1394, raw1394_id_table);

static struct hpsb_protocol_driver raw1394_driver = {
	.name =		"raw1394 Driver",
	.id_table = 	raw1394_id_table,
};


/******************************************************************************/


static struct hpsb_highlevel raw1394_highlevel = {
	.name =		RAW1394_DEVICE_NAME,
        .add_host =    add_host,
        .remove_host = remove_host,
        .host_reset =  host_reset,
        .iso_receive = iso_receive,
        .fcp_request = fcp_request,
};

static struct file_operations file_ops = {
	.owner =	THIS_MODULE,
        .read =		raw1394_read, 
        .write =	raw1394_write,
	.mmap =         raw1394_mmap,
	.ioctl =        raw1394_ioctl,
        .poll =		raw1394_poll, 
        .open =		raw1394_open, 
        .release =	raw1394_release, 
};

static int __init init_raw1394(void)
{
	hpsb_register_highlevel(&raw1394_highlevel);

        devfs_handle = devfs_register(NULL,
                                      RAW1394_DEVICE_NAME, DEVFS_FL_NONE,
                                      IEEE1394_MAJOR,
                                      IEEE1394_MINOR_BLOCK_RAW1394 * 16,
                                      S_IFCHR | S_IRUSR | S_IWUSR, &file_ops,
                                      NULL);

        if (ieee1394_register_chardev(IEEE1394_MINOR_BLOCK_RAW1394,
                                      THIS_MODULE, &file_ops)) {
                HPSB_ERR("raw1394 failed to register minor device block");
                devfs_unregister(devfs_handle);
                hpsb_unregister_highlevel(&raw1394_highlevel);
                return -EBUSY;
        }

        printk(KERN_INFO "raw1394: /dev/%s device initialized\n", RAW1394_DEVICE_NAME);

	hpsb_register_protocol(&raw1394_driver);

        return 0;
}

static void __exit cleanup_raw1394(void)
{
	hpsb_unregister_protocol(&raw1394_driver);
        ieee1394_unregister_chardev(IEEE1394_MINOR_BLOCK_RAW1394);
        devfs_unregister(devfs_handle);
        hpsb_unregister_highlevel(&raw1394_highlevel);
}

module_init(init_raw1394);
module_exit(cleanup_raw1394);
MODULE_LICENSE("GPL");
