// -*- mode: c++; c-basic-offset: 4 -*-
/*
 * fromhost.{cc,hh} -- receives packets from Linux
 * Max Poletto, Eddie Kohler
 *
 * Copyright (c) 1999-2000 Massachusetts Institute of Technology
 * Copyright (c) 2000 Mazu Networks, Inc.
 * Copyright (c) 2001-2003 International Computer Science Institute
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, subject to the conditions
 * listed in the Click LICENSE file. These conditions include: you must
 * preserve this copyright notice, and you cannot mention the copyright
 * holders in advertising related to the Software without their permission.
 * The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
 * notice is a summary of the Click LICENSE file; the license in that file is
 * legally binding.
 */

#include <click/config.h>
#include <click/router.hh>
#include "fromhost.hh"
#include <click/confparse.hh>
#include <click/error.hh>
#include <click/standard/scheduleinfo.hh>

#include <click/cxxprotect.h>
CLICK_CXX_PROTECT
#include <asm/types.h>
#include <asm/uaccess.h>
#include <linux/ip.h>
#include <linux/inetdevice.h>
#include <net/route.h>
CLICK_CXX_UNPROTECT
#include <click/cxxunprotect.h>

#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 4, 0)
# define netif_start_queue(dev)	do { dev->start=1; dev->tbusy=0; } while (0)
# define netif_stop_queue(dev)	do { dev->tbusy=1; } while (0)
# define netif_wake_queue(dev)	do { dev->tbusy=0; } while (0)
#endif

static int fl_open(net_device *);
static int fl_close(net_device *);
static net_device_stats *fl_stats(net_device *);
static void fl_wakeup(Timer *, void *);

static int from_linux_count;
static AnyDeviceMap fromlinux_map;

void
FromHost::static_initialize()
{
    fromlinux_map.initialize();
}

FromHost::FromHost()
    : _macaddr((const unsigned char *)"\000\001\002\003\004\005"),
      _task(this), _wakeup_timer(fl_wakeup, this), _queue(0)
{
    memset(&_stats, 0, sizeof(_stats));
}

FromHost::~FromHost()
{
}

net_device *
FromHost::new_device(const char *name)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 0)
    read_lock(&dev_base_lock);
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
    net_device *dev = alloc_netdev(0, name, ether_setup);
#else
    int errcode;
    net_device *dev = dev_alloc(name, &errcode);
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 0)
    read_unlock(&dev_base_lock);
#endif
    if (!dev)
	return 0;
    
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 4, 0)
    // need to zero out the dev structure
    char *nameptr = dev->name;
    memset(dev, 0, sizeof(*dev));
    dev->name = nameptr;
#endif

#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 0)
    ether_setup(dev);
#endif
    dev->open = fl_open;
    dev->stop = fl_close;
    dev->hard_start_xmit = fl_tx;
    dev->get_stats = fl_stats;
    return dev;
}

int
FromHost::configure(Vector<String> &conf, ErrorHandler *errh)
{
    if (cp_va_parse(conf, this, errh,
		    cpString, "device name", &_devname,
		    cpIPPrefix, "destination IP prefix", &_destaddr, &_destmask,
		    cpKeywords,
		    "ETHER", cpEthernetAddress, "fake device Ethernet address", &_macaddr,
		    cpEnd) < 0)
	return -1;
    if (_devname.length() > IFNAMSIZ - 1)
	return errh->error("device name '%s' too long", _devname.c_str());

    // check for duplicate element
    void *&used = router()->force_attachment("FromHost_" + _devname);
    if (used)
	return errh->error("duplicate FromHost for device '%s'", _devname.c_str());
    used = this;
    
    // check for existing device
    _dev = dev_get_by_name(_devname.c_str());
    if (_dev) {
	if (_dev->open != fl_open) {
	    dev_put(_dev);
	    _dev = 0;
	    return errh->error("device '%s' already exists", _devname.c_str());
	} else {
	    fromlinux_map.insert(this);
	    return 0;
	}
    }

    // if not found, create new device
    int res;
    _dev = new_device(_devname.c_str());
    if (!_dev)
	return errh->error("out of memory!", res, _devname.c_str());
    else if ((res = register_netdev(_dev)) < 0) {
	kfree(_dev);
	_dev = 0;
	return errh->error("error %d registering device '%s'", res, _devname.c_str());
    }

    dev_hold(_dev);
    fromlinux_map.insert(this);
    return 0;
}

#if 0 /* Why was this code here? */
static void
dev_locks(int up)
{
# if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 4, 0)
    if (up > 0)
	rtnl_lock();
    else
	rtnl_unlock();
# endif
}
#endif

int
FromHost::set_device_addresses(ErrorHandler *errh)
{
    int res;
    struct ifreq ifr;
    strncpy(ifr.ifr_name, _dev->name, IFNAMSIZ);
    struct sockaddr_in *sin = (struct sockaddr_in *)&ifr.ifr_addr;

    mm_segment_t oldfs = get_fs();
    set_fs(get_ds());

    ifr.ifr_hwaddr.sa_family = _dev->type;
    memcpy(ifr.ifr_hwaddr.sa_data, _macaddr.data(), 6);
    if ((res = dev_ioctl(SIOCSIFHWADDR, &ifr)) < 0)
	errh->error("error %d setting hardware address for device '%s'", res, _devname.c_str());

    sin->sin_family = AF_INET;
    sin->sin_addr = _destaddr;
    if (res >= 0 && (res = devinet_ioctl(SIOCSIFADDR, &ifr)) < 0)
	errh->error("error %d setting address for device '%s'", res, _devname.c_str());

    sin->sin_addr = _destmask;
    if (res >= 0 && (res = devinet_ioctl(SIOCSIFNETMASK, &ifr)) < 0)
	errh->error("error %d setting netmask for device '%s'", res, _devname.c_str());

    set_fs(oldfs);
    return res;
}

static int
dev_updown(net_device *dev, int up, ErrorHandler *errh)
{
    struct ifreq ifr;
    strncpy(ifr.ifr_name, dev->name, IFNAMSIZ);
    uint32_t flags = IFF_UP | IFF_RUNNING;
    int res;

    mm_segment_t oldfs = get_fs();
    set_fs(get_ds());

    (void) dev_ioctl(SIOCGIFFLAGS, &ifr);
    ifr.ifr_flags = (up > 0 ? ifr.ifr_flags | flags : ifr.ifr_flags & ~flags);
    if ((res = dev_ioctl(SIOCSIFFLAGS, &ifr)) < 0 && errh)
	errh->error("error %d bringing %s device '%s'", res, (up > 0 ? "up" : "down"), dev->name);

    set_fs(oldfs);
    return res;
}

int
FromHost::initialize(ErrorHandler *errh)
{
    ScheduleInfo::initialize_task(this, &_task, _dev != 0, errh);
    _nonfull_signal = Notifier::downstream_full_signal(this, 0, &_task);
    if (_dev->flags & IFF_UP) {
	_wakeup_timer.initialize(this);
	_wakeup_timer.schedule_now();
	return 0;
    } else if (set_device_addresses(errh) < 0)
	return -1;
    else
	return dev_updown(_dev, 1, errh);
}

void
FromHost::cleanup(CleanupStage)
{
    fromlinux_map.remove(this);

    if (_queue) {
	_queue->kill();
	_queue = 0;
    }
    
    if (_dev) {
	dev_put(_dev);
	if (fromlinux_map.lookup(_dev, 0))
	    _dev = 0;		// do not free device; still in use
	else {
	    if (_dev->flags & IFF_UP)
		dev_updown(_dev, -1, 0);
	    unregister_netdev(_dev);
	    kfree(_dev);
	    _dev = 0;
	}
    }
}

static void
fl_wakeup(Timer *, void *thunk)
{
    FromHost *fl = (FromHost *)thunk;
    PrefixErrorHandler errh(ErrorHandler::default_handler(), fl->declaration() + ": ");
    net_device *dev = fl->device();

    if (dev->flags & IFF_UP)
	dev_updown(dev, -1, &errh);
    
    fl->set_device_addresses(&errh);
    
    dev_updown(dev, 1, &errh);
}

/*
 * Device callbacks
 */

static int
fl_open(net_device *dev)
{
    netif_start_queue(dev);
    return 0;
}

static int
fl_close(net_device *dev)
{
    netif_stop_queue(dev);
    return 0;
}

int
FromHost::fl_tx(struct sk_buff *skb, net_device *dev)
{
    /* 8.May.2003 - Doug and company had crashes with FromHost configurations.
         We eventually figured out this was because fl_tx was called at
         interrupt time -- at bottom-half time, to be exact -- and then pushed
         a packet through the configuration. Whoops: if Click was interrupted,
         and during the bottom-half FromHost emitted a packet into Click,
         DISASTER -- we assume that, when running single-threaded, at most one
         Click thread is active at a time; so there were race conditions,
         particularly with the task list. The solution is a single-packet-long
         queue in FromHost. fl_tx puts a packet onto the queue, a regular
         Click Task takes the packet off the queue. We could have implemented
         a larger queue, but why bother? Linux already maintains a queue for
         the device. */
    if (FromHost *fl = (FromHost *)fromlinux_map.lookup(dev, 0))
	if (!fl->_queue) {
	    fl->_queue = Packet::make(skb);
	    netif_stop_queue(dev);
	    fl->_stats.tx_packets++;
	    fl->_stats.tx_bytes += fl->_queue->length();
	    fl->_task.reschedule();
    	    return 0;
	}
    return -1;
}

static net_device_stats *
fl_stats(net_device *dev)
{
    if (FromHost *fl = (FromHost *)fromlinux_map.lookup(dev, 0))
	return fl->stats();
    return 0;
}

bool
FromHost::run_task()
{
    if (!_nonfull_signal)
	return false;
    else if (Packet *p = _queue) {
	_queue = 0;
	netif_wake_queue(_dev);
	output(0).push(p);
	return true;
    } else
	return false;
}

ELEMENT_REQUIRES(AnyDevice linuxmodule)
EXPORT_ELEMENT(FromHost)


syntax highlighted by Code2HTML, v. 0.9.1