/*
 * 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 <new>
 
#include <ethernet.h>
#include <lock.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;
 
	uint32 					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;
	uint32					maxframesize;
	uint8					macaddr[6];
 
} 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 void virtio_net_rxDone(void* driverCookie, void* cookie);
static void virtio_net_txDone(void* driverCookie, void* cookie);
 
 
const char*
get_feature_name(uint32 feature)
{
	switch (feature) {
		case VIRTIO_NET_F_CSUM:
			return "host checksum";
		case VIRTIO_NET_F_GUEST_CSUM:
			return "guest checksum";
		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)
{
	while (true) {
		BufInfo* buf = (BufInfo*)info->virtio->queue_dequeue(
			info->txQueues[0], NULL);
		if (buf == NULL)
			break;
		info->txFreeList.Add(buf);
	}
 
	while (true) {
		BufInfo* buf = (BufInfo*)info->virtio->queue_dequeue(
			info->rxQueues[0], NULL);
		if (buf == NULL)
			break;
	}
 
	while (true) {
		BufInfo* buf = info->rxFullList.RemoveHead();
		if (buf == NULL)
			break;
	}
 
	return B_OK;
}
 
 
static status_t
virtio_net_rx_enqueue_buf(virtio_net_driver_info* info, BufInfo* buf)
{
	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;
}
 
 
#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_CTRL_VQ | 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);
	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(info->rxBufInfos));
	memset(info->txBufInfos, 0, sizeof(info->txBufInfos));
 
	// 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));
	}
 
	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_read(void* cookie, off_t pos, void* buffer, size_t* _length)
{
	CALLED();
	virtio_net_handle* handle = (virtio_net_handle*)cookie;
	virtio_net_driver_info* info = handle->info;
 
	mutex_lock(&info->rxLock);
	while (info->rxFullList.Head() == NULL) {
		mutex_unlock(&info->rxLock);
 
		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);
 
		mutex_lock(&info->rxLock);
		while (info->rxDone != -1) {
			uint32 usedLength = 0;
			BufInfo* buf = (BufInfo*)info->virtio->queue_dequeue(
				info->rxQueues[0], &usedLength);
			if (buf == NULL)
				break;
 
			buf->rxUsedLength = usedLength;
			info->rxFullList.Add(buf);
		}
		TRACE("virtio_net_read: finished waiting\n");
	}
 
	BufInfo* buf = info->rxFullList.RemoveHead();
	*_length = MIN(buf->rxUsedLength, *_length);
	memcpy(buffer, buf->buffer, *_length);
	virtio_net_rx_enqueue_buf(info, buf);
	mutex_unlock(&info->rxLock);
	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_write(void* cookie, off_t pos, const void* buffer,
	size_t* _length)
{
	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 = (BufInfo*)info->virtio->queue_dequeue(
				info->txQueues[0], NULL);
			if (buf == NULL)
				break;
			info->txFreeList.Add(buf);
		}
	}
	BufInfo* buf = info->txFreeList.RemoveHead();
 
	TRACE("virtio_net_write: copying %lu\n", MIN(MAX_FRAME_SIZE, *_length));
	memcpy(buf->buffer, buffer, MIN(MAX_FRAME_SIZE, *_length));
	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 = MIN(MAX_FRAME_SIZE, *_length);
 
	// 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;
	}
 
	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");
			memcpy(buffer, &info->macaddr, sizeof(info->macaddr));
			return B_OK;
 
		case ETHER_INIT:
			TRACE("ioctl: init\n");
			return B_OK;
 
		case ETHER_GETFRAMESIZE:
			TRACE("ioctl: get frame size\n");
			*(uint32*)buffer = info->maxframesize;
			return B_OK;
 
		case ETHER_SETPROMISC:
			TRACE("ioctl: set promisc\n");
			break;
 
		case ETHER_NONBLOCK:
			info->nonblocking = *(int32*)buffer == 0;
			TRACE("ioctl: non blocking ? %s\n", info->nonblocking ? "yes" : "no");
			return B_OK;
 
		case ETHER_ADDMULTI:
			TRACE("ioctl: add multicast\n");
			break;
 
		case ETHER_REMMULTI:
			TRACE("ioctl: remove multicast\n");
			break;
 
		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));
		}
 
		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();
 
	// ready to register
	device_attr attrs[] = {
		{ 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(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},
	{}
};
 
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,
	virtio_net_read,
	virtio_net_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
};

V630 The 'malloc' function is used to allocate memory for an array of objects which are classes containing constructors and destructors.

V579 The memset function receives the pointer and its size as arguments. It is possibly a mistake. Inspect the third argument.

V579 The memset function receives the pointer and its size as arguments. It is possibly a mistake. Inspect the third argument.