⛏️ index : haiku.git

/*
 * Copyright 2013, JΓ©rΓ΄me Duval, korli@users.berlios.de.
 * Distributed under the terms of the MIT License.
 */


#include "VirtioRNGPrivate.h"

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

#include <util/AutoLock.h>


const char *
get_feature_name(uint64 feature)
{
	switch (feature) {
	}
	return NULL;
}


VirtioRNGDevice::VirtioRNGDevice(device_node *node)
	:
	fVirtio(NULL),
	fVirtioDevice(NULL),
	fStatus(B_NO_INIT),
	fExpectsInterrupt(false),
	fDPCHandle(NULL)
{
	CALLED();

	B_INITIALIZE_SPINLOCK(&fInterruptLock);
	fInterruptCondition.Init(this, "virtio rng transfer");

	// get the Virtio device from our parent's parent
	device_node *virtioParent = gDeviceManager->get_parent_node(node);

	gDeviceManager->get_driver(virtioParent, (driver_module_info **)&fVirtio,
		(void **)&fVirtioDevice);
	gDeviceManager->put_node(virtioParent);

	fVirtio->negotiate_features(fVirtioDevice,
		0, &fFeatures, &get_feature_name);

	fStatus = fVirtio->alloc_queues(fVirtioDevice, 1, &fVirtioQueue, NULL);
	if (fStatus != B_OK) {
		ERROR("queue allocation failed (%s)\n", strerror(fStatus));
		return;
	}

	fStatus = fVirtio->setup_interrupt(fVirtioDevice, NULL, this);
	if (fStatus != B_OK) {
		ERROR("interrupt setup failed (%s)\n", strerror(fStatus));
		return;
	}

	fStatus = fVirtio->queue_setup_interrupt(fVirtioQueue, _RequestCallback,
		this);
	if (fStatus != B_OK) {
		ERROR("queue interrupt setup failed (%s)\n", strerror(fStatus));
		return;
	}

	fStatus = gDPC->new_dpc_queue(&fDPCHandle, "virtio_rng timer",
		B_LOW_PRIORITY);
	if (fStatus != B_OK) {
		ERROR("dpc setup failed (%s)\n", strerror(fStatus));
		return;
	}

	if (fStatus == B_OK) {
		fTimer.user_data = this;
		fStatus = add_timer(&fTimer, &HandleTimerHook, 300 * 1000 * 1000, B_PERIODIC_TIMER);
		if (fStatus != B_OK)
			ERROR("timer setup failed (%s)\n", strerror(fStatus));
	}

	// trigger also now
	gDPC->queue_dpc(fDPCHandle, HandleDPC, this);
}


VirtioRNGDevice::~VirtioRNGDevice()
{
	cancel_timer(&fTimer);
	gDPC->delete_dpc_queue(fDPCHandle);

}


status_t
VirtioRNGDevice::InitCheck()
{
	return fStatus;
}


status_t
VirtioRNGDevice::_Enqueue()
{
	CALLED();

	uint64 value = 0;
	physical_entry entry;
	get_memory_map(&value, sizeof(value), &entry, 1);

	ConditionVariableEntry conditionVariableEntry;
	{
		InterruptsSpinLocker locker(fInterruptLock);
		fExpectsInterrupt = true;
		fInterruptCondition.Add(&conditionVariableEntry);
	}

	status_t result = fVirtio->queue_request(fVirtioQueue, NULL, &entry, NULL);
	if (result != B_OK) {
		ERROR("queueing failed (%s)\n", strerror(result));
		return result;
	}

	result = conditionVariableEntry.Wait(B_CAN_INTERRUPT);

	{
		InterruptsSpinLocker locker(fInterruptLock);
		fExpectsInterrupt = false;
	}

	if (result == B_OK) {
		if (value != 0) {
			gRandom->queue_randomness(value);
			TRACE("enqueue %" B_PRIx64 "\n", value);
		}
	} else if (result != B_OK && result != B_INTERRUPTED) {
		ERROR("request failed (%s)\n", strerror(result));
	}

	return result;
}


void
VirtioRNGDevice::_RequestCallback(void* driverCookie, void* cookie)
{
	VirtioRNGDevice* device = (VirtioRNGDevice*)driverCookie;

	while (device->fVirtio->queue_dequeue(device->fVirtioQueue, NULL, NULL))
		;

	device->_RequestInterrupt();
}


void
VirtioRNGDevice::_RequestInterrupt()
{
	SpinLocker locker(fInterruptLock);
	fInterruptCondition.NotifyAll();
}


/*static*/ int32
VirtioRNGDevice::HandleTimerHook(struct timer* timer)
{
	VirtioRNGDevice* device = reinterpret_cast<VirtioRNGDevice*>(timer->user_data);

	gDPC->queue_dpc(device->fDPCHandle, HandleDPC, device);
	return B_HANDLED_INTERRUPT;
}


/*static*/ void
VirtioRNGDevice::HandleDPC(void *arg)
{
	VirtioRNGDevice* device = reinterpret_cast<VirtioRNGDevice*>(arg);
	device->_Enqueue();
}