xref: /haiku/src/add-ons/kernel/busses/random/virtio/VirtioRNGDevice.cpp (revision fc7456e9b1ec38c941134ed6d01c438cf289381e)
1 /*
2  * Copyright 2013, Jérôme Duval, korli@users.berlios.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include "VirtioRNGPrivate.h"
8 
9 #include <new>
10 #include <stdlib.h>
11 #include <string.h>
12 
13 #include <util/AutoLock.h>
14 
15 
16 const char *
17 get_feature_name(uint64 feature)
18 {
19 	switch (feature) {
20 	}
21 	return NULL;
22 }
23 
24 
25 VirtioRNGDevice::VirtioRNGDevice(device_node *node)
26 	:
27 	fVirtio(NULL),
28 	fVirtioDevice(NULL),
29 	fStatus(B_NO_INIT),
30 	fExpectsInterrupt(false),
31 	fDPCHandle(NULL)
32 {
33 	CALLED();
34 
35 	B_INITIALIZE_SPINLOCK(&fInterruptLock);
36 	fInterruptCondition.Init(this, "virtio rng transfer");
37 
38 	// get the Virtio device from our parent's parent
39 	device_node *virtioParent = gDeviceManager->get_parent_node(node);
40 
41 	gDeviceManager->get_driver(virtioParent, (driver_module_info **)&fVirtio,
42 		(void **)&fVirtioDevice);
43 	gDeviceManager->put_node(virtioParent);
44 
45 	fVirtio->negotiate_features(fVirtioDevice,
46 		0, &fFeatures, &get_feature_name);
47 
48 	fStatus = fVirtio->alloc_queues(fVirtioDevice, 1, &fVirtioQueue, NULL);
49 	if (fStatus != B_OK) {
50 		ERROR("queue allocation failed (%s)\n", strerror(fStatus));
51 		return;
52 	}
53 
54 	fStatus = fVirtio->setup_interrupt(fVirtioDevice, NULL, this);
55 	if (fStatus != B_OK) {
56 		ERROR("interrupt setup failed (%s)\n", strerror(fStatus));
57 		return;
58 	}
59 
60 	fStatus = fVirtio->queue_setup_interrupt(fVirtioQueue, _RequestCallback,
61 		this);
62 	if (fStatus != B_OK) {
63 		ERROR("queue interrupt setup failed (%s)\n", strerror(fStatus));
64 		return;
65 	}
66 
67 	fStatus = gDPC->new_dpc_queue(&fDPCHandle, "virtio_rng timer",
68 		B_LOW_PRIORITY);
69 	if (fStatus != B_OK) {
70 		ERROR("dpc setup failed (%s)\n", strerror(fStatus));
71 		return;
72 	}
73 
74 	if (fStatus == B_OK) {
75 		fTimer.user_data = this;
76 		fStatus = add_timer(&fTimer, &HandleTimerHook, 300 * 1000 * 1000, B_PERIODIC_TIMER);
77 		if (fStatus != B_OK)
78 			ERROR("timer setup failed (%s)\n", strerror(fStatus));
79 	}
80 
81 	// trigger also now
82 	gDPC->queue_dpc(fDPCHandle, HandleDPC, this);
83 }
84 
85 
86 VirtioRNGDevice::~VirtioRNGDevice()
87 {
88 	cancel_timer(&fTimer);
89 	gDPC->delete_dpc_queue(fDPCHandle);
90 
91 }
92 
93 
94 status_t
95 VirtioRNGDevice::InitCheck()
96 {
97 	return fStatus;
98 }
99 
100 
101 status_t
102 VirtioRNGDevice::_Enqueue()
103 {
104 	CALLED();
105 
106 	uint64 value = 0;
107 	physical_entry entry;
108 	get_memory_map(&value, sizeof(value), &entry, 1);
109 
110 	ConditionVariableEntry conditionVariableEntry;
111 	{
112 		InterruptsSpinLocker locker(fInterruptLock);
113 		fExpectsInterrupt = true;
114 		fInterruptCondition.Add(&conditionVariableEntry);
115 	}
116 
117 	status_t result = fVirtio->queue_request(fVirtioQueue, NULL, &entry, NULL);
118 	if (result != B_OK) {
119 		ERROR("queueing failed (%s)\n", strerror(result));
120 		return result;
121 	}
122 
123 	result = conditionVariableEntry.Wait(B_CAN_INTERRUPT);
124 
125 	{
126 		InterruptsSpinLocker locker(fInterruptLock);
127 		fExpectsInterrupt = false;
128 	}
129 
130 	if (result == B_OK) {
131 		if (value != 0) {
132 			gRandom->queue_randomness(value);
133 			TRACE("enqueue %" B_PRIx64 "\n", value);
134 		}
135 	} else if (result != B_OK && result != B_INTERRUPTED) {
136 		ERROR("request failed (%s)\n", strerror(result));
137 	}
138 
139 	return result;
140 }
141 
142 
143 void
144 VirtioRNGDevice::_RequestCallback(void* driverCookie, void* cookie)
145 {
146 	VirtioRNGDevice* device = (VirtioRNGDevice*)driverCookie;
147 
148 	while (device->fVirtio->queue_dequeue(device->fVirtioQueue, NULL, NULL))
149 		;
150 
151 	device->_RequestInterrupt();
152 }
153 
154 
155 void
156 VirtioRNGDevice::_RequestInterrupt()
157 {
158 	SpinLocker locker(fInterruptLock);
159 	fInterruptCondition.NotifyAll();
160 }
161 
162 
163 /*static*/ int32
164 VirtioRNGDevice::HandleTimerHook(struct timer* timer)
165 {
166 	VirtioRNGDevice* device = reinterpret_cast<VirtioRNGDevice*>(timer->user_data);
167 
168 	gDPC->queue_dpc(device->fDPCHandle, HandleDPC, device);
169 	return B_HANDLED_INTERRUPT;
170 }
171 
172 
173 /*static*/ void
174 VirtioRNGDevice::HandleDPC(void *arg)
175 {
176 	VirtioRNGDevice* device = reinterpret_cast<VirtioRNGDevice*>(arg);
177 	device->_Enqueue();
178 }
179 
180