#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>
#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));
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))
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
,
&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;
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]);
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;
}
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;
}
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;
}
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");
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));
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;
uint16 etherType;
if (sBufferModule->read(buffer, offsetof(ether_header, type),
ðerType, 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) {
}
}
*_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;
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)
{
virtio_net_handle* handle = (virtio_net_handle*)cookie;
virtio_net_driver_info* info = handle->info;
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;
}
static float
virtio_net_supports_device(device_node* parent)
{
CALLED();
const char* bus;
uint16 deviceType;
if (sDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false))
return -1;
if (strcmp(bus, "virtio"))
return 0.0;
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;
}
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,
virtio_net_open,
virtio_net_close,
virtio_net_free,
NULL,
NULL,
NULL,
virtio_net_ioctl,
NULL,
NULL,
};
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,
NULL,
};
module_info* modules[] = {
(module_info*)&sVirtioNetDriver,
(module_info*)&sVirtioNetDevice,
NULL
};