/*
 * Copyright (C) 2013 Cloudius Systems, Ltd.
 *
 * This work is open source software, licensed under the terms of the
 * BSD license as described in the LICENSE file in the top-level directory.
 */

#include <string.h>
#include <sstream>

#include "drivers/xenfront.hh"
#include "drivers/xenfront-xenbus.hh"
#include <osv/debug.h>
#include <bsd/porting/bus.h>
#include <xen/xenstore/xenstorevar.h>
#include <xen/xenbus/xenbusb.h>

extern driver_t netfront_driver;
extern driver_t blkfront_driver;

namespace xenfront {

void xenfront_driver::otherend_changed(XenbusState state)
{
    if (_backend_changed)
        _backend_changed(&_bsd_dev, state);
}

void xenfront_driver::probe()
{
    if (_probe)
        _probe(&_bsd_dev);
}

int xenfront_driver::attach()
{
    if (_attach)
        return _attach(&_bsd_dev);
    return 0;
}

// No need to have a counter for disks, because unit number for disks are not an
// increasing counter, but rather it position in the bus. For the network, we could
// hash the mac address if we would really care, to make sure that those are stable
// This will do for now.
static std::atomic<int> net_unit;

void xenfront_driver::set_ivars(struct xenbus_device_ivars *ivars)
{
    driver_t *table;
    std::stringstream ss;

    _otherend_path = std::string(ivars->xd_otherend_path);
    _otherend_id = ivars->xd_otherend_id;
    _node_path = std::string(ivars->xd_node);
    _type = std::string(ivars->xd_type);

    if (!strcmp(ivars->xd_type, "vif")) {
        table = &netfront_driver;
        _irq_type = INTR_TYPE_NET,
        ss << "xenfront-net";
        // Very unfrequent, so don't care about how expensive and full of barriers this is
        _bsd_dev.unit = net_unit++;

    } else if (!strcmp(ivars->xd_type, "vbd")) {
        table = &blkfront_driver;
        _irq_type = INTR_TYPE_BIO;
        ss << "vblk";
    } else
        return;

    _driver_name = ss.str();
    device_method_t *dm = table->methods;
    for (auto i = 0; dm[i].id; i++) {
        if (dm[i].id == bus_device_probe)
            _probe = reinterpret_cast<xenfront::probe>(dm[i].func);
        if (dm[i].id == bus_device_attach)
            _attach = reinterpret_cast<xenfront::attach>(dm[i].func);
        if (dm[i].id == bus_xenbus_otherend_changed)
            _backend_changed = reinterpret_cast<xenfront::backend_changed>(dm[i].func);
    }
    _bsd_dev.softc = malloc(table->size);
    // Simpler and we don't expect driver loading to fail anyway
    assert(_bsd_dev.softc);
    memset(_bsd_dev.softc, 0, table->size);
}

void xenfront_driver::finished()
{
    if (_irq_type == INTR_TYPE_BIO)
        read_partition_table(&this->_bsd_dev);
    _bus->remove_pending(this);
}

xenfront_driver::xenfront_driver(xenbus *bus)
    : _bus(bus)
{
}
}