xref: /haiku/src/add-ons/kernel/busses/random/virtio/VirtioRNGDevice.cpp (revision dd2a1e350b303b855a50fd64e6cb55618be1ae6a)
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);
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 	{
111 		InterruptsSpinLocker locker(fInterruptLock);
112 		fExpectsInterrupt = true;
113 		fInterruptCondition.Add(&fInterruptConditionEntry);
114 	}
115 	status_t result = fVirtio->queue_request(fVirtioQueue, NULL, &entry, NULL);
116 	if (result != B_OK) {
117 		ERROR("queueing failed (%s)\n", strerror(result));
118 		return result;
119 	}
120 
121 	result = fInterruptConditionEntry.Wait(B_CAN_INTERRUPT);
122 
123 	{
124 		InterruptsSpinLocker locker(fInterruptLock);
125 		fExpectsInterrupt = false;
126 	}
127 
128 	if (result == B_OK) {
129 		if (value != 0) {
130 			gRandom->queue_randomness(value);
131 			TRACE("enqueue %" B_PRIx64 "\n", value);
132 		}
133 	} else if (result != B_OK && result != B_INTERRUPTED) {
134 		ERROR("request failed (%s)\n", strerror(result));
135 	}
136 
137 	return result;
138 }
139 
140 
141 void
142 VirtioRNGDevice::_RequestCallback(void* driverCookie, void* cookie)
143 {
144 	VirtioRNGDevice* device = (VirtioRNGDevice*)driverCookie;
145 
146 	while (device->fVirtio->queue_dequeue(device->fVirtioQueue, NULL, NULL))
147 		;
148 
149 	device->_RequestInterrupt();
150 }
151 
152 
153 void
154 VirtioRNGDevice::_RequestInterrupt()
155 {
156 	SpinLocker locker(fInterruptLock);
157 	fInterruptCondition.NotifyAll();
158 }
159 
160 
161 /*static*/ int32
162 VirtioRNGDevice::HandleTimerHook(struct timer* timer)
163 {
164 	VirtioRNGDevice* device = reinterpret_cast<VirtioRNGDevice*>(timer->user_data);
165 
166 	gDPC->queue_dpc(device->fDPCHandle, HandleDPC, device);
167 	return B_HANDLED_INTERRUPT;
168 }
169 
170 
171 /*static*/ void
172 VirtioRNGDevice::HandleDPC(void *arg)
173 {
174 	VirtioRNGDevice* device = reinterpret_cast<VirtioRNGDevice*>(arg);
175 	device->_Enqueue();
176 }
177 
178