/*
 * IEEE 1394 for Linux
 *
 * Copyright (C) 2006 Johannes Berg <johannes@sipsolutions.net>
 *
 * This code is licensed under the GPL v2. See the file COPYING in the root
 * directory of the kernel sources for details.
 *
 * This module provides a character device for each node attached
 * to the bus and allows direct memory access on them.
 *
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/cdev.h>

#include "ieee1394.h"
#include "ieee1394_core.h"
#include "ieee1394_transactions.h"
#include "mem1394.h"
#include "nodemgr.h"
#include "highlevel.h"

static int mem1394_mmap(struct file * file, struct vm_area_struct * vma)
{
	return -ENOSYS;
}

static int mem1394_open(struct inode *inode, struct file *file)
{
	struct mem1394_file_info *fi;

	fi = kzalloc(sizeof(*fi), GFP_KERNEL);
	if (!fi)
		return -ENOMEM;
	file->private_data = fi;
	fi->memdev = container_of(inode->i_cdev, struct mem1394_dev, cdev);
	return 0;
}

/* This function essentially clones hpsb_read. Might be better
 * to create a new hpsb_read_user function instead... */
static int mem1394_read(struct file *file, char __user * buffer,
			size_t count, loff_t *offset)
{
	struct mem1394_file_info *fi = (struct mem1394_file_info*)file->private_data;
	/* lower levels only support count as a multiple of 4 */
	size_t submitcount = (count + (4-1)) & ~(4-1);
	int retval = 0;
	struct hpsb_packet *packet;

	/* this is a bit icky. I think I'll want to create a
	 * "struct hpsb_node_class_interface" that you register
	 * with nodemgr.c instead of registering the "struct class_interface"
	 * directly. It would wrap around the "struct class_interface"
	 * and handle things like this.
	 *
	 * This means it would call the node_class_interface's
	 *  - "add" method whenever the device is fully there, and an
	 *  - "update" method when it survived a bus reset, and the
	 *  - "remove" method when it went away, also taking care of
	 * debouncing, which the mem1394 interface currently doesn't handle.
	 *
	 * But I need advice on this. It'll probably works this way
	 * but most likely not once this interface stuff gets more
	 * use; I can imagine using it for scanners instead of raw1394
	 * so that the kernel can validate that a user can only
	 * access a certain scanner and not all 1394 devices on the bus.
	 * In other words some 'raw1394intf' instead of 'raw1394' which
	 * creates one character device per ieee1394 node for finer
	 * grained access control.
	 * That would definitely want to have debouncing etc.
	 *
	 * However, I don't fully understand the states node_entries go
	 * through yet, so I'm not sure this should even be here!
	 * Maybe it should be in open? But then the device could go
	 * into limbo when it is already opened...
	 *
	 * Similarly, what happens if a node is suspended?
	 */
	if (fi->memdev->ne->in_limbo)
		return -ENODEV;

	if (count == 0)
		return 0;

	/* I wonder if it is possible to do DMA directly to the userspace buffer... */
	packet = hpsb_make_readpacket(fi->memdev->ne->host, fi->memdev->ne->nodeid, *offset, submitcount);
	if (!packet)
		return -ENOENT;
	
	packet->generation = fi->memdev->ne->generation;
	retval = hpsb_send_packet_and_wait(packet);
	if (retval < 0)
		goto out_free;

	retval = hpsb_packet_success(packet);
	
	if (retval == 0) {
		if (submitcount == 4) {
			if (copy_to_user(buffer, &packet->header[3], count))
				retval = -EFAULT;
		} else {
			if (copy_to_user(buffer, packet->data, count))
				retval = -EFAULT;
		}
	}

	if (retval == 0) {
		retval = count;
		*offset += count;
	}

 out_free:
	hpsb_free_tlabel(packet);
	hpsb_free_packet(packet);

	return retval;
}

static int mem1394_release(struct inode *inode, struct file *file)
{
	struct mem1394_file_info *fi = (struct mem1394_file_info*)file->private_data;
	
	kfree(fi);

	return 0;
}

static struct class *mem1394_sysfs_class;

static struct file_operations mem1394_fops = {
	.owner = THIS_MODULE,
	.mmap = mem1394_mmap,
	.open = mem1394_open,
	.read = mem1394_read,
	.release = mem1394_release,
};

static atomic_t mem1394_dev_ctr;

static struct mem1394_dev * alloc_mem1394_dev(struct device *dev)
{
	struct mem1394_dev *result;
	struct node_entry *ne = container_of(dev, struct node_entry, device);
	int ret;
	struct class_device * mem1394_class_member;

	result = kzalloc(sizeof(*result), GFP_KERNEL);
	if (!result)
		return NULL;

	cdev_init(&result->cdev, &mem1394_fops);
	result->cdev.owner = THIS_MODULE;
	result->ne = ne;

	ret = hpsb_cdev_add(&result->cdev);
	if (ret) {
		printk(KERN_ERR "mem1394: failed to register character device!\n");
		goto out_free;
	}

	atomic_inc(&mem1394_dev_ctr);
	mem1394_class_member = class_device_create(mem1394_sysfs_class, NULL, result->cdev.dev,
						dev, "fwmem-%d", atomic_read(&mem1394_dev_ctr));
	if (IS_ERR(mem1394_class_member)) {
		printk(KERN_WARNING "mem1394: class_device_create failed\n");
	} else {
		class_set_devdata(mem1394_class_member, result);
	}
	dev->driver_data = result;

	return result;
 out_free:
	kfree(result);
	return NULL;
}

static DEFINE_SPINLOCK(dev_list_lock);
static LIST_HEAD(dev_list);

static int mem1394_add(struct class_device *cl_dev, struct class_interface *cl_intf)
{
	struct mem1394_dev *memdev;

	if (!cl_dev) {
		printk("cl_dev not assigned\n");
		return -ENOENT;
	}
	if (!cl_dev->dev) {
		printk("cl_dev->dev not assigned\n");
		return -ENONET;
	}

	memdev = alloc_mem1394_dev(cl_dev->dev);
	if (!memdev)
		return -ENOMEM;

	spin_lock(&dev_list_lock);
	list_add_tail(&memdev->list, &dev_list);
	spin_unlock(&dev_list_lock);

	/* need we do anything else? */
	return 0;
}

static void mem1394_del(struct mem1394_dev *memdev)
{
	class_device_destroy(mem1394_sysfs_class, memdev->cdev.dev);

	/* kill off everything that might be in progress */
	/* TODO */

	/* remove character device */
	hpsb_cdev_del(&memdev->cdev);
	kfree(memdev);
}

static void mem1394_remove(struct class_device *cl_dev, struct class_interface *cl_intf)
{
	struct mem1394_dev *memdev, *tmp, *found = NULL;
	
	/* find our memdev corresponding to the class device */
	spin_lock(&dev_list_lock);
	list_for_each_entry_safe(memdev, tmp, &dev_list, list) {
		if (cl_dev->dev == memdev->dev) {
			list_del(&memdev->list);
			found = memdev;
			break;
		}
	}
	spin_unlock(&dev_list_lock);
	if (!found)
		return;

	mem1394_del(found);
}

static struct class_interface mem1394_interface = {
	.add		= mem1394_add,
	.remove		= mem1394_remove,
};
                
static int __init init_mem1394(void)
{
	int ret;
	
	spin_lock_init(&dev_list_lock);
	
	mem1394_sysfs_class = class_create(THIS_MODULE, "mem1394");
	if (IS_ERR(mem1394_sysfs_class)) {
		return PTR_ERR(mem1394_sysfs_class);
	}

	ret = hpsb_register_node_interface(&mem1394_interface);
	return ret;
}

static void __exit cleanup_mem1394(void)
{
	struct mem1394_dev *memdev, *tmp;

	hpsb_unregister_node_interface(&mem1394_interface);
	list_for_each_entry_safe(memdev, tmp, &dev_list, list) {
		list_del(&memdev->list);
		mem1394_del(memdev);
	}
	class_destroy(mem1394_sysfs_class);
}

module_init(init_mem1394);
module_exit(cleanup_mem1394);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
