root/src/add-ons/kernel/drivers/network/ether/virtio/virtio_net.cpp
/*
 * Copyright 2013, 2018, Jérôme Duval, jerome.duval@gmail.com.
 * Copyright 2017, Philippe Houdoin, philippe.houdoin@gmail.com.
 * Distributed under the terms of the MIT License.
 */


#include <net/if_media.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <sys/sockio.h>

#include <ethernet.h>
#include <kernel.h>
#include <lock.h>
#include <net_buffer.h>
#include <util/AutoLock.h>
#include <util/DoublyLinkedList.h>
#include <virtio.h>

#include "ether_driver.h"
#define ETHER_ADDR_LEN  ETHER_ADDRESS_LENGTH
#include "virtio_net.h"


#define VIRTIO_NET_DRIVER_MODULE_NAME "drivers/network/virtio_net/driver_v1"
#define VIRTIO_NET_DEVICE_MODULE_NAME "drivers/network/virtio_net/device_v1"
#define VIRTIO_NET_DEVICE_ID_GENERATOR  "virtio_net/device_id"

#define BUFFER_SIZE     2048
#define MAX_FRAME_SIZE 1536


struct virtio_net_rx_hdr {
        struct virtio_net_hdr   hdr;
        uint8                                   pad[4];
} _PACKED;


struct virtio_net_tx_hdr {
        union {
                struct virtio_net_hdr                   hdr;
                struct virtio_net_hdr_mrg_rxbuf mhdr;
        };
} _PACKED;


struct BufInfo : DoublyLinkedListLinkImpl<BufInfo> {
        char*                                   buffer;
        struct virtio_net_hdr*  hdr;
        physical_entry                  entry;
        physical_entry                  hdrEntry;
        uint32                                  rxUsedLength;
};


typedef DoublyLinkedList<BufInfo> BufInfoList;


typedef struct {
        device_node*                    node;
        ::virtio_device                 virtio_device;
        virtio_device_interface*        virtio;

        uint64                                  features;

        uint32                                  pairsCount;

        ::virtio_queue*                 rxQueues;
        uint16*                                 rxSizes;

        BufInfo**                               rxBufInfos;
        sem_id                                  rxDone;
        area_id                                 rxArea;
        BufInfoList                             rxFullList;
        mutex                                   rxLock;

        ::virtio_queue*                 txQueues;
        uint16*                                 txSizes;

        BufInfo**                               txBufInfos;
        sem_id                                  txDone;
        area_id                                 txArea;
        BufInfoList                             txFreeList;
        mutex                                   txLock;

        ::virtio_queue                  ctrlQueue;

        bool                                    nonblocking;
        bool                                    promiscuous;
        uint32                                  maxframesize;
        ether_address_t                 macaddr;

#define MAX_MULTI 128
        uint32                                  multiCount;
        ether_address_t                 multi[MAX_MULTI];

} virtio_net_driver_info;


typedef struct {
        virtio_net_driver_info*         info;
} virtio_net_handle;


#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <fs/devfs.h>


//#define TRACE_VIRTIO_NET
#ifdef TRACE_VIRTIO_NET
#       define TRACE(x...) dprintf("virtio_net: " x)
#else
#       define TRACE(x...) ;
#endif
#define ERROR(x...)                     dprintf("\33[33mvirtio_net:\33[0m " x)
#define CALLED()                        TRACE("CALLED %s\n", __PRETTY_FUNCTION__)


static device_manager_info* sDeviceManager;
static net_buffer_module_info* sBufferModule;


static void virtio_net_rxDone(void* driverCookie, void* cookie);
static void virtio_net_txDone(void* driverCookie, void* cookie);


const char*
get_feature_name(uint64 feature)
{
        switch (feature) {
                case VIRTIO_NET_F_CSUM:
                        return "host checksum";
                case VIRTIO_NET_F_GUEST_CSUM:
                        return "guest checksum";
                case VIRTIO_NET_F_MTU:
                        return "mtu";
                case VIRTIO_NET_F_MAC:
                        return "macaddress";
                case VIRTIO_NET_F_GSO:
                        return "host allgso";
                case VIRTIO_NET_F_GUEST_TSO4:
                        return "guest tso4";
                case VIRTIO_NET_F_GUEST_TSO6:
                        return "guest tso6";
                case VIRTIO_NET_F_GUEST_ECN:
                        return "guest tso6+ecn";
                case VIRTIO_NET_F_GUEST_UFO:
                        return "guest ufo";
                case VIRTIO_NET_F_HOST_TSO4:
                        return "host tso4";
                case VIRTIO_NET_F_HOST_TSO6:
                        return "host tso6";
                case VIRTIO_NET_F_HOST_ECN:
                        return "host tso6+ecn";
                case VIRTIO_NET_F_HOST_UFO:
                        return "host UFO";
                case VIRTIO_NET_F_MRG_RXBUF:
                        return "host mergerxbuffers";
                case VIRTIO_NET_F_STATUS:
                        return "status";
                case VIRTIO_NET_F_CTRL_VQ:
                        return "control vq";
                case VIRTIO_NET_F_CTRL_RX:
                        return "rx mode";
                case VIRTIO_NET_F_CTRL_VLAN:
                        return "vlan filter";
                case VIRTIO_NET_F_CTRL_RX_EXTRA:
                        return "rx mode extra";
                case VIRTIO_NET_F_GUEST_ANNOUNCE:
                        return "guest announce";
                case VIRTIO_NET_F_MQ:
                        return "multiqueue";
                case VIRTIO_NET_F_CTRL_MAC_ADDR:
                        return "set macaddress";
        }
        return NULL;
}


static status_t
virtio_net_drain_queues(virtio_net_driver_info* info)
{
        BufInfo* buf = NULL;
        while (info->virtio->queue_dequeue(info->txQueues[0], (void**)&buf, NULL))
                info->txFreeList.Add(buf);

        while (info->virtio->queue_dequeue(info->rxQueues[0], NULL, NULL))
                ;

        while (info->rxFullList.RemoveHead() != NULL)
                ;

        return B_OK;
}


static status_t
virtio_net_rx_enqueue_buf(virtio_net_driver_info* info, BufInfo* buf)
{
        CALLED();
        physical_entry entries[2];
        entries[0] = buf->hdrEntry;
        entries[1] = buf->entry;

        memset(buf->hdr, 0, sizeof(struct virtio_net_hdr));

        // queue the rx buffer
        status_t status = info->virtio->queue_request_v(info->rxQueues[0],
                entries, 0, 2, buf);
        if (status != B_OK) {
                ERROR("rx queueing on queue %d failed (%s)\n", 0, strerror(status));
                return status;
        }

        return B_OK;
}


static status_t
virtio_net_ctrl_exec_cmd(virtio_net_driver_info* info, int cmd, bool value)
{
        struct {
                struct virtio_net_ctrl_hdr hdr;
                uint8 pad1;
                uint8 onoff;
                uint8 pad2;
                uint8 ack;
        } s __attribute__((aligned(2)));

        s.hdr.net_class = VIRTIO_NET_CTRL_RX;
        s.hdr.cmd = cmd;
        s.onoff = value;
        s.ack = VIRTIO_NET_ERR;

        physical_entry entries[3];
        status_t status = get_memory_map(&s.hdr, sizeof(s.hdr), &entries[0], 1);
        if (status != B_OK)
                return status;
        status = get_memory_map(&s.onoff, sizeof(s.onoff), &entries[1], 1);
        if (status != B_OK)
                return status;
        status = get_memory_map(&s.ack, sizeof(s.ack), &entries[2], 1);
        if (status != B_OK)
                return status;

        if (!info->virtio->queue_is_empty(info->ctrlQueue))
                return B_ERROR;

        status = info->virtio->queue_request_v(info->ctrlQueue, entries, 2, 1,
                NULL);
        if (status != B_OK)
                return status;

        while (!info->virtio->queue_dequeue(info->ctrlQueue, NULL, NULL))
                spin(10);

        return s.ack == VIRTIO_NET_OK ? B_OK : B_IO_ERROR;
}


static status_t
virtio_net_set_promisc(virtio_net_driver_info* info, bool on)
{
        return virtio_net_ctrl_exec_cmd(info, VIRTIO_NET_CTRL_RX_PROMISC, on);
}


static int
vtnet_set_allmulti(virtio_net_driver_info* info, bool on)
{
        return virtio_net_ctrl_exec_cmd(info, VIRTIO_NET_CTRL_RX_ALLMULTI, on);
}


#define ROUND_TO_PAGE_SIZE(x) (((x) + (B_PAGE_SIZE) - 1) & ~((B_PAGE_SIZE) - 1))


//      #pragma mark - device module API


static status_t
virtio_net_init_device(void* _info, void** _cookie)
{
        CALLED();
        virtio_net_driver_info* info = (virtio_net_driver_info*)_info;

        device_node* parent = sDeviceManager->get_parent_node(info->node);
        sDeviceManager->get_driver(parent, (driver_module_info**)&info->virtio,
                (void**)&info->virtio_device);
        sDeviceManager->put_node(parent);

        info->virtio->negotiate_features(info->virtio_device,
                VIRTIO_NET_F_STATUS | VIRTIO_NET_F_MAC | VIRTIO_NET_F_MTU
                        | VIRTIO_NET_F_CTRL_VQ | VIRTIO_NET_F_CTRL_RX | VIRTIO_NET_F_GUEST_CSUM
                        /* | VIRTIO_NET_F_MQ */,
                &info->features, &get_feature_name);

        if ((info->features & VIRTIO_NET_F_MQ) != 0
                        && (info->features & VIRTIO_NET_F_CTRL_VQ) != 0
                        && info->virtio->read_device_config(info->virtio_device,
                                offsetof(struct virtio_net_config, max_virtqueue_pairs),
                                &info->pairsCount, sizeof(info->pairsCount)) == B_OK) {
                system_info sysinfo;
                if (get_system_info(&sysinfo) == B_OK
                        && info->pairsCount > sysinfo.cpu_count) {
                        info->pairsCount = sysinfo.cpu_count;
                }
        } else
                info->pairsCount = 1;

        // TODO read config

        // Setup queues
        uint32 queueCount = info->pairsCount * 2;
        if ((info->features & VIRTIO_NET_F_CTRL_VQ) != 0)
                queueCount++;
        ::virtio_queue virtioQueues[queueCount];
        status_t status = info->virtio->alloc_queues(info->virtio_device, queueCount,
                virtioQueues, NULL);
        if (status != B_OK) {
                ERROR("queue allocation failed (%s)\n", strerror(status));
                return status;
        }

        char* rxBuffer;
        char* txBuffer;

        info->rxQueues = new(std::nothrow) virtio_queue[info->pairsCount];
        info->txQueues = new(std::nothrow) virtio_queue[info->pairsCount];
        info->rxSizes = new(std::nothrow) uint16[info->pairsCount];
        info->txSizes = new(std::nothrow) uint16[info->pairsCount];
        if (info->rxQueues == NULL || info->txQueues == NULL
                || info->rxSizes == NULL || info->txSizes == NULL) {
                status = B_NO_MEMORY;
                goto err1;
        }
        for (uint32 i = 0; i < info->pairsCount; i++) {
                info->rxQueues[i] = virtioQueues[i * 2];
                info->txQueues[i] = virtioQueues[i * 2 + 1];
                info->rxSizes[i] = info->virtio->queue_size(info->rxQueues[i]) / 2;
                info->txSizes[i] = info->virtio->queue_size(info->txQueues[i]) / 2;
        }
        if ((info->features & VIRTIO_NET_F_CTRL_VQ) != 0)
                info->ctrlQueue = virtioQueues[info->pairsCount * 2];

        info->rxBufInfos = new(std::nothrow) BufInfo*[info->rxSizes[0]];
        info->txBufInfos = new(std::nothrow) BufInfo*[info->txSizes[0]];
        if (info->rxBufInfos == NULL || info->txBufInfos == NULL) {
                status = B_NO_MEMORY;
                goto err2;
        }
        memset(info->rxBufInfos, 0, sizeof(BufInfo*) * info->rxSizes[0]);
        memset(info->txBufInfos, 0, sizeof(BufInfo*) * info->txSizes[0]);

        // create receive buffer area
        info->rxArea = create_area("virtionet rx buffer", (void**)&rxBuffer,
                B_ANY_KERNEL_BLOCK_ADDRESS, ROUND_TO_PAGE_SIZE(
                        BUFFER_SIZE * info->rxSizes[0]),
                B_FULL_LOCK, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA);
        if (info->rxArea < B_OK) {
                status = info->rxArea;
                goto err3;
        }

        // initialize receive buffer descriptors
        for (int i = 0; i < info->rxSizes[0]; i++) {
                BufInfo* buf = new(std::nothrow) BufInfo;
                if (buf == NULL) {
                        status = B_NO_MEMORY;
                        goto err4;
                }

                info->rxBufInfos[i] = buf;
                buf->hdr = (struct virtio_net_hdr*)((addr_t)rxBuffer
                        + i * BUFFER_SIZE);
                buf->buffer = (char*)((addr_t)buf->hdr + sizeof(virtio_net_rx_hdr));

                status = get_memory_map(buf->buffer,
                        BUFFER_SIZE - sizeof(virtio_net_rx_hdr), &buf->entry, 1);
                if (status != B_OK)
                        goto err4;

                status = get_memory_map(buf->hdr, sizeof(struct virtio_net_hdr),
                        &buf->hdrEntry, 1);
                if (status != B_OK)
                        goto err4;
        }

        // create transmit buffer area
        info->txArea = create_area("virtionet tx buffer", (void**)&txBuffer,
                B_ANY_KERNEL_BLOCK_ADDRESS, ROUND_TO_PAGE_SIZE(
                        BUFFER_SIZE * info->txSizes[0]),
                B_FULL_LOCK, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA);
        if (info->txArea < B_OK) {
                status = info->txArea;
                goto err5;
        }

        // initialize transmit buffer descriptors
        for (int i = 0; i < info->txSizes[0]; i++) {
                BufInfo* buf = new(std::nothrow) BufInfo;
                if (buf == NULL) {
                        status = B_NO_MEMORY;
                        goto err6;
                }

                info->txBufInfos[i] = buf;
                buf->hdr = (struct virtio_net_hdr*)((addr_t)txBuffer
                        + i * BUFFER_SIZE);
                buf->buffer = (char*)((addr_t)buf->hdr + sizeof(virtio_net_tx_hdr));

                status = get_memory_map(buf->buffer,
                        BUFFER_SIZE - sizeof(virtio_net_tx_hdr), &buf->entry, 1);
                if (status != B_OK)
                        goto err6;

                status = get_memory_map(buf->hdr, sizeof(struct virtio_net_hdr),
                        &buf->hdrEntry, 1);
                if (status != B_OK)
                        goto err6;

                info->txFreeList.Add(buf);
        }

        mutex_init(&info->rxLock, "virtionet rx lock");
        mutex_init(&info->txLock, "virtionet tx lock");

        // Setup interrupt
        status = info->virtio->setup_interrupt(info->virtio_device, NULL, info);
        if (status != B_OK) {
                ERROR("interrupt setup failed (%s)\n", strerror(status));
                goto err6;
        }

        status = info->virtio->queue_setup_interrupt(info->rxQueues[0],
                virtio_net_rxDone, info);
        if (status != B_OK) {
                ERROR("queue interrupt setup failed (%s)\n", strerror(status));
                goto err6;
        }

        status = info->virtio->queue_setup_interrupt(info->txQueues[0],
                virtio_net_txDone, info);
        if (status != B_OK) {
                ERROR("queue interrupt setup failed (%s)\n", strerror(status));
                goto err6;
        }

        if ((info->features & VIRTIO_NET_F_CTRL_VQ) != 0) {
                status = info->virtio->queue_setup_interrupt(info->ctrlQueue,
                        NULL, info);
                if (status != B_OK) {
                        ERROR("queue interrupt setup failed (%s)\n", strerror(status));
                        goto err6;
                }
        }

        *_cookie = info;
        return B_OK;

err6:
        for (int i = 0; i < info->txSizes[0]; i++)
                delete info->txBufInfos[i];
err5:
        delete_area(info->txArea);
err4:
        for (int i = 0; i < info->rxSizes[0]; i++)
                delete info->rxBufInfos[i];
err3:
        delete_area(info->rxArea);
err2:
        delete[] info->rxBufInfos;
        delete[] info->txBufInfos;
err1:
        delete[] info->rxQueues;
        delete[] info->txQueues;
        delete[] info->rxSizes;
        delete[] info->txSizes;
        return status;
}


static void
virtio_net_uninit_device(void* _cookie)
{
        CALLED();
        virtio_net_driver_info* info = (virtio_net_driver_info*)_cookie;

        info->virtio->free_interrupts(info->virtio_device);

        mutex_destroy(&info->rxLock);
        mutex_destroy(&info->txLock);

        while (true) {
                BufInfo* buf = info->txFreeList.RemoveHead();
                if (buf == NULL)
                        break;
        }

        for (int i = 0; i < info->rxSizes[0]; i++) {
                delete info->rxBufInfos[i];
        }
        for (int i = 0; i < info->txSizes[0]; i++) {
                delete info->txBufInfos[i];
        }
        delete_area(info->rxArea);
        delete_area(info->txArea);
        delete[] info->rxBufInfos;
        delete[] info->txBufInfos;
        delete[] info->rxSizes;
        delete[] info->txSizes;
        delete[] info->rxQueues;
        delete[] info->txQueues;

        info->virtio->free_queues(info->virtio_device);
}


static status_t
virtio_net_open(void* _info, const char* path, int openMode, void** _cookie)
{
        CALLED();
        virtio_net_driver_info* info = (virtio_net_driver_info*)_info;

        virtio_net_handle* handle = (virtio_net_handle*)malloc(
                sizeof(virtio_net_handle));
        if (handle == NULL)
                return B_NO_MEMORY;

        info->nonblocking = (openMode & O_NONBLOCK) != 0;
        info->maxframesize = MAX_FRAME_SIZE;
        info->rxDone = create_sem(0, "virtio_net_rx");
        info->txDone = create_sem(1, "virtio_net_tx");
        if (info->rxDone < B_OK || info->txDone < B_OK)
                goto error;
        handle->info = info;

        if ((info->features & VIRTIO_NET_F_MAC) != 0) {
                info->virtio->read_device_config(info->virtio_device,
                        offsetof(struct virtio_net_config, mac),
                        &info->macaddr, sizeof(info->macaddr));
        }

        if ((info->features & VIRTIO_NET_F_MTU) != 0) {
                dprintf("virtio_net: mtu feature\n");
                uint16 mtu;
                info->virtio->read_device_config(info->virtio_device,
                        offsetof(struct virtio_net_config, mtu),
                        &mtu, sizeof(mtu));
                // check against minimum MTU
                if (mtu > 68)
                        info->maxframesize = mtu;
                else
                        info->virtio->clear_feature(info->virtio_device, VIRTIO_NET_F_MTU);
        } else {
                dprintf("virtio_net: no mtu feature\n");
        }

        for (int i = 0; i < info->rxSizes[0]; i++)
                virtio_net_rx_enqueue_buf(info, info->rxBufInfos[i]);

        *_cookie = handle;
        return B_OK;

error:
        delete_sem(info->rxDone);
        delete_sem(info->txDone);
        info->rxDone = info->txDone = -1;
        free(handle);
        return B_ERROR;
}


static status_t
virtio_net_close(void* cookie)
{
        virtio_net_handle* handle = (virtio_net_handle*)cookie;
        CALLED();

        virtio_net_driver_info* info = handle->info;
        delete_sem(info->rxDone);
        delete_sem(info->txDone);
        info->rxDone = info->txDone = -1;

        return B_OK;
}


static status_t
virtio_net_free(void* cookie)
{
        CALLED();
        virtio_net_handle* handle = (virtio_net_handle*)cookie;

        virtio_net_driver_info* info = handle->info;
        virtio_net_drain_queues(info);
        free(handle);
        return B_OK;
}


static void
virtio_net_rxDone(void* driverCookie, void* cookie)
{
        CALLED();
        virtio_net_driver_info* info = (virtio_net_driver_info*)cookie;

        release_sem_etc(info->rxDone, 1, B_DO_NOT_RESCHEDULE);
}


static status_t
virtio_net_receive(void* cookie, net_buffer** _buffer)
{
        CALLED();
        virtio_net_handle* handle = (virtio_net_handle*)cookie;
        virtio_net_driver_info* info = handle->info;

        MutexLocker rxLocker(info->rxLock);
        while (info->rxFullList.Head() == NULL) {
                rxLocker.Unlock();

                if (info->nonblocking)
                        return B_WOULD_BLOCK;
                TRACE("virtio_net_read: waiting\n");
                status_t status = acquire_sem(info->rxDone);
                if (status != B_OK) {
                        ERROR("acquire_sem(rxDone) failed (%s)\n", strerror(status));
                        return status;
                }
                int32 semCount = 0;
                get_sem_count(info->rxDone, &semCount);
                if (semCount > 0)
                        acquire_sem_etc(info->rxDone, semCount, B_RELATIVE_TIMEOUT, 0);

                rxLocker.Lock();
                while (info->rxDone != -1) {
                        uint32 usedLength = 0;
                        BufInfo* buf = NULL;
                        if (!info->virtio->queue_dequeue(info->rxQueues[0], (void**)&buf,
                                        &usedLength) || buf == NULL) {
                                break;
                        }

                        if (usedLength > sizeof(virtio_net_hdr))
                                buf->rxUsedLength = usedLength - sizeof(virtio_net_hdr);
                        else
                                buf->rxUsedLength = 0;
                        info->rxFullList.Add(buf);
                }
                TRACE("virtio_net_read: finished waiting\n");
        }

        net_buffer* buffer = sBufferModule->create(0);
        if (buffer == NULL)
                return B_NO_MEMORY;

        BufInfo* buf = info->rxFullList.RemoveHead();
        rxLocker.Unlock();

        if (sBufferModule->append(buffer, buf->buffer, buf->rxUsedLength) != B_OK) {
                sBufferModule->free(buffer);
                buffer = NULL;
        }
        const uint8_t flags = buf->hdr->flags;
        rxLocker.Lock();
        virtio_net_rx_enqueue_buf(info, buf);
        rxLocker.Unlock();

        if (buffer == NULL)
                return B_NO_MEMORY;

        if ((flags & (VIRTIO_NET_HDR_F_DATA_VALID | VIRTIO_NET_HDR_F_NEEDS_CSUM)) != 0) {
                buffer->buffer_flags |= NET_BUFFER_L3_CHECKSUM_VALID;

                // virtio also checks the L4 checksum for common protocols.
                uint16 etherType;
                if (sBufferModule->read(buffer, offsetof(ether_header, type),
                                &etherType, sizeof(etherType)) == B_OK) {
                        uint8 protocol = 0;
                        etherType = ntohs(etherType);
                        if (etherType == ETHER_TYPE_IP) {
                                sBufferModule->read(buffer,
                                        ETHER_HEADER_LENGTH + offsetof(struct ip, ip_p),
                                        &protocol, sizeof(protocol));
                        } else if (etherType == ETHER_TYPE_IPV6) {
                                sBufferModule->read(buffer,
                                        ETHER_HEADER_LENGTH + offsetof(struct ip6_hdr, ip6_nxt),
                                        &protocol, sizeof(protocol));
                        }
                        if (protocol == IPPROTO_TCP || protocol == IPPROTO_UDP)
                                buffer->buffer_flags |= NET_BUFFER_L4_CHECKSUM_VALID;
                }

                if ((flags & VIRTIO_NET_HDR_F_NEEDS_CSUM) != 0) {
                        // The data is known to be valid but the checksum in the packet is incomplete.
                        // Ignore this flag for now; the stack accepts packets with CHECKSUM_VALID set.
                }
        }

        *_buffer = buffer;
        return B_OK;
}


static void
virtio_net_txDone(void* driverCookie, void* cookie)
{
        CALLED();
        virtio_net_driver_info* info = (virtio_net_driver_info*)cookie;

        release_sem_etc(info->txDone, 1, B_DO_NOT_RESCHEDULE);
}


static status_t
virtio_net_send(void* cookie, net_buffer* buffer)
{
        CALLED();
        virtio_net_handle* handle = (virtio_net_handle*)cookie;
        virtio_net_driver_info* info = handle->info;

        mutex_lock(&info->txLock);
        while (info->txFreeList.Head() == NULL) {
                mutex_unlock(&info->txLock);
                if (info->nonblocking)
                        return B_WOULD_BLOCK;

                status_t status = acquire_sem(info->txDone);
                if (status != B_OK) {
                        ERROR("acquire_sem(txDone) failed (%s)\n", strerror(status));
                        return status;
                }

                int32 semCount = 0;
                get_sem_count(info->txDone, &semCount);
                if (semCount > 0)
                        acquire_sem_etc(info->txDone, semCount, B_RELATIVE_TIMEOUT, 0);

                mutex_lock(&info->txLock);
                while (info->txDone != -1) {
                        BufInfo* buf = NULL;
                        if (!info->virtio->queue_dequeue(info->txQueues[0], (void**)&buf,
                                        NULL) || buf == NULL) {
                                break;
                        }

                        info->txFreeList.Add(buf);
                }
        }
        BufInfo* buf = info->txFreeList.RemoveHead();

        const size_t size = MIN(MAX_FRAME_SIZE, buffer->size);
        TRACE("virtio_net_write: copying %lu\n", size);
        if (sBufferModule->read(buffer, 0, buf->buffer, size) != B_OK) {
                info->txFreeList.Add(buf);
                mutex_unlock(&info->txLock);
                return B_BAD_DATA;
        }
        memset(buf->hdr, 0, sizeof(virtio_net_hdr));

        physical_entry entries[2];
        entries[0] = buf->hdrEntry;
        entries[0].size = sizeof(virtio_net_hdr);
        entries[1] = buf->entry;
        entries[1].size = size;

        // queue the virtio_net_hdr + buffer data
        status_t status = info->virtio->queue_request_v(info->txQueues[0],
                entries, 2, 0, buf);
        mutex_unlock(&info->txLock);

        if (status != B_OK) {
                ERROR("tx queueing on queue %d failed (%s)\n", 0, strerror(status));
                return status;
        }

        sBufferModule->free(buffer);
        return B_OK;
}


static status_t
virtio_net_ioctl(void* cookie, uint32 op, void* buffer, size_t length)
{
        // CALLED();
        virtio_net_handle* handle = (virtio_net_handle*)cookie;
        virtio_net_driver_info* info = handle->info;

        // TRACE("ioctl(op = %lx)\n", op);

        switch (op) {
                case ETHER_GETADDR:
                        TRACE("ioctl: get macaddr\n");
                        return user_memcpy(buffer, &info->macaddr, sizeof(info->macaddr));

                case ETHER_INIT:
                        TRACE("ioctl: init\n");
                        return B_OK;

                case ETHER_GETFRAMESIZE:
                        TRACE("ioctl: get frame size\n");
                        if (length != sizeof(info->maxframesize))
                                return B_BAD_VALUE;

                        return user_memcpy(buffer, &info->maxframesize,
                                sizeof(info->maxframesize));

                case ETHER_SETPROMISC:
                {
                        TRACE("ioctl: set promisc\n");
                        int32 value;
                        if (length != sizeof(value))
                                return B_BAD_VALUE;
                        if (user_memcpy(&value, buffer, sizeof(value)) != B_OK)
                                return B_BAD_ADDRESS;
                        if (info->promiscuous == value)
                                return B_OK;
                        info->promiscuous = value;
                        return virtio_net_set_promisc(info, value != 0);
                }
                case ETHER_NONBLOCK:
                {
                        TRACE("ioctl: non blocking ? %s\n",
                                info->nonblocking ? "yes" : "no");
                        int32 value;
                        if (length != sizeof(value))
                                return B_BAD_VALUE;
                        if (user_memcpy(&value, buffer, sizeof(value)) != B_OK)
                                return B_BAD_ADDRESS;
                        info->nonblocking = value == 0;
                        return B_OK;
                }
                case ETHER_ADDMULTI:
                {
                        uint32 i, multiCount = info->multiCount;
                        TRACE("ioctl: add multicast\n");

                        if ((info->features & VIRTIO_NET_F_CTRL_RX) == 0)
                                return B_NOT_SUPPORTED;

                        if (multiCount == MAX_MULTI)
                                return B_ERROR;

                        for (i = 0; i < multiCount; i++) {
                                if (memcmp(&info->multi[i], buffer,
                                        sizeof(info->multi[0])) == 0) {
                                        break;
                                }
                        }

                        if (i == multiCount) {
                                memcpy(&info->multi[i], buffer, sizeof(info->multi[i]));
                                info->multiCount++;
                        }
                        if (info->multiCount == 1) {
                                TRACE("Enabling multicast\n");
                                vtnet_set_allmulti(info, true);
                        }

                        return B_OK;
                }
                case ETHER_REMMULTI:
                {
                        uint32 i, multiCount = info->multiCount;
                        TRACE("ioctl: remove multicast\n");

                        if ((info->features & VIRTIO_NET_F_CTRL_RX) == 0)
                                return B_NOT_SUPPORTED;

                        for (i = 0; i < multiCount; i++) {
                                if (memcmp(&info->multi[i], buffer,
                                        sizeof(info->multi[0])) == 0) {
                                        break;
                                }
                        }

                        if (i != multiCount) {
                                if (i < multiCount - 1) {
                                        memmove(&info->multi[i], &info->multi[i + 1],
                                                sizeof(info->multi[i]) * (multiCount - i - 1));
                                }
                                info->multiCount--;
                                if (info->multiCount == 0) {
                                        TRACE("Disabling multicast\n");
                                        vtnet_set_allmulti(info, false);
                                }
                                return B_OK;
                        }
                        return B_BAD_VALUE;
                }
                case ETHER_GET_LINK_STATE:
                {
                        TRACE("ioctl: get link state\n");
                        ether_link_state_t state;
                        uint16 status = VIRTIO_NET_S_LINK_UP;
                        if ((info->features & VIRTIO_NET_F_STATUS) != 0) {
                                info->virtio->read_device_config(info->virtio_device,
                                        offsetof(struct virtio_net_config, status),
                                        &status, sizeof(status));
                        }
                        state.media = ((status & VIRTIO_NET_S_LINK_UP) != 0 ? IFM_ACTIVE : 0)
                                | IFM_ETHER | IFM_FULL_DUPLEX | IFM_10G_T;
                        state.speed = 10000000000ULL;
                        state.quality = 1000;

                        return user_memcpy(buffer, &state, sizeof(ether_link_state_t));
                }

                case ETHER_SEND_NET_BUFFER:
                        if (buffer == NULL || length == 0)
                                return B_BAD_DATA;
                        if (!IS_KERNEL_ADDRESS(buffer))
                                return B_BAD_ADDRESS;
                        return virtio_net_send(cookie, (net_buffer*)buffer);

                case ETHER_RECEIVE_NET_BUFFER:
                        if (buffer == NULL || length == 0)
                                return B_BAD_DATA;
                        if (!IS_KERNEL_ADDRESS(buffer))
                                return B_BAD_ADDRESS;
                        return virtio_net_receive(cookie, (net_buffer**)buffer);

                case SIOCGIFSTATS:
                        break;

                default:
                        ERROR("ioctl: unknown message %" B_PRIx32 "\n", op);
                        break;
        }


        return B_DEV_INVALID_IOCTL;
}


//      #pragma mark - driver module API


static float
virtio_net_supports_device(device_node* parent)
{
        CALLED();
        const char* bus;
        uint16 deviceType;

        // make sure parent is really the Virtio bus manager
        if (sDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false))
                return -1;

        if (strcmp(bus, "virtio"))
                return 0.0;

        // check whether it's really a Direct Access Device
        if (sDeviceManager->get_attr_uint16(parent, VIRTIO_DEVICE_TYPE_ITEM,
                        &deviceType, true) != B_OK || deviceType != VIRTIO_DEVICE_ID_NETWORK)
                return 0.0;

        TRACE("Virtio network device found!\n");

        return 0.6;
}


static status_t
virtio_net_register_device(device_node* node)
{
        CALLED();

        device_attr attrs[] = {
                { B_DEVICE_PRETTY_NAME, B_STRING_TYPE, {.string = "Virtio Network"} },
                { NULL }
        };

        return sDeviceManager->register_node(node, VIRTIO_NET_DRIVER_MODULE_NAME,
                attrs, NULL, NULL);
}


static status_t
virtio_net_init_driver(device_node* node, void** cookie)
{
        CALLED();

        virtio_net_driver_info* info = (virtio_net_driver_info*)malloc(
                sizeof(virtio_net_driver_info));
        if (info == NULL)
                return B_NO_MEMORY;

        memset((void*)info, 0, sizeof(*info));

        info->node = node;

        *cookie = info;
        return B_OK;
}


static void
virtio_net_uninit_driver(void* _cookie)
{
        CALLED();
        virtio_net_driver_info* info = (virtio_net_driver_info*)_cookie;
        free(info);
}


static status_t
virtio_net_register_child_devices(void* _cookie)
{
        CALLED();
        virtio_net_driver_info* info = (virtio_net_driver_info*)_cookie;
        status_t status;

        int32 id = sDeviceManager->create_id(VIRTIO_NET_DEVICE_ID_GENERATOR);
        if (id < 0)
                return id;

        char name[64];
        snprintf(name, sizeof(name), "net/virtio/%" B_PRId32,
                id);

        status = sDeviceManager->publish_device(info->node, name,
                VIRTIO_NET_DEVICE_MODULE_NAME);

        return status;
}


//      #pragma mark -


module_dependency module_dependencies[] = {
        {B_DEVICE_MANAGER_MODULE_NAME, (module_info**)&sDeviceManager},
        {NET_BUFFER_MODULE_NAME, (module_info**)&sBufferModule},
        {}
};

struct device_module_info sVirtioNetDevice = {
        {
                VIRTIO_NET_DEVICE_MODULE_NAME,
                0,
                NULL
        },

        virtio_net_init_device,
        virtio_net_uninit_device,
        NULL, // remove,

        virtio_net_open,
        virtio_net_close,
        virtio_net_free,
        NULL,   // read
        NULL,   // write
        NULL,   // io
        virtio_net_ioctl,

        NULL,   // select
        NULL,   // deselect
};

struct driver_module_info sVirtioNetDriver = {
        {
                VIRTIO_NET_DRIVER_MODULE_NAME,
                0,
                NULL
        },

        virtio_net_supports_device,
        virtio_net_register_device,
        virtio_net_init_driver,
        virtio_net_uninit_driver,
        virtio_net_register_child_devices,
        NULL,   // rescan
        NULL,   // removed
};

module_info* modules[] = {
        (module_info*)&sVirtioNetDriver,
        (module_info*)&sVirtioNetDevice,
        NULL
};