xref: /haiku/src/add-ons/kernel/busses/scsi/virtio/VirtioSCSIController.cpp (revision bd6068614473f87449dfa2eaa67fad1527c61e11)
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 "VirtioSCSIPrivate.h"
8 
9 #include <StackOrHeapArray.h>
10 
11 #include <new>
12 #include <stdlib.h>
13 #include <strings.h>
14 
15 #include <util/AutoLock.h>
16 
17 
18 const char *
19 get_feature_name(uint32 feature)
20 {
21 	switch (feature) {
22 		case VIRTIO_SCSI_F_INOUT:
23 			return "in out";
24 		case VIRTIO_SCSI_F_HOTPLUG:
25 			return "hotplug";
26 		case VIRTIO_SCSI_F_CHANGE:
27 			return "change";
28 	}
29 	return NULL;
30 }
31 
32 
33 VirtioSCSIController::VirtioSCSIController(device_node *node)
34 	:
35 	fNode(node),
36 	fVirtio(NULL),
37 	fVirtioDevice(NULL),
38 	fStatus(B_NO_INIT),
39 	fRequest(NULL),
40 	fEventDPC(NULL)
41 {
42 	CALLED();
43 
44 	B_INITIALIZE_SPINLOCK(&fInterruptLock);
45 	fInterruptCondition.Init(this, "virtio scsi transfer");
46 
47 	if (gSCSI->alloc_dpc(&fEventDPC) != B_OK)
48 		return;
49 
50 	// get the Virtio device from our parent's parent
51 	device_node *parent = gDeviceManager->get_parent_node(node);
52 	device_node *virtioParent = gDeviceManager->get_parent_node(parent);
53 	gDeviceManager->put_node(parent);
54 
55 	gDeviceManager->get_driver(virtioParent, (driver_module_info **)&fVirtio,
56 		(void **)&fVirtioDevice);
57 	gDeviceManager->put_node(virtioParent);
58 
59 	fVirtio->negotiate_features(fVirtioDevice,
60 		VIRTIO_SCSI_F_CHANGE /*VIRTIO_SCSI_F_HOTPLUG*/,
61 		&fFeatures, &get_feature_name);
62 
63 	fStatus = fVirtio->read_device_config(fVirtioDevice, 0, &fConfig,
64 		sizeof(struct virtio_scsi_config));
65 	if (fStatus != B_OK)
66 		return;
67 
68 	fConfig.sense_size = VIRTIO_SCSI_SENSE_SIZE;
69 	fConfig.cdb_size = VIRTIO_SCSI_CDB_SIZE;
70 
71 	fVirtio->write_device_config(fVirtioDevice,
72 		offsetof(struct virtio_scsi_config, sense_size), &fConfig.sense_size,
73 		sizeof(fConfig.sense_size));
74 	fVirtio->write_device_config(fVirtioDevice,
75 		offsetof(struct virtio_scsi_config, cdb_size), &fConfig.cdb_size,
76 		sizeof(fConfig.cdb_size));
77 
78 	fRequest = new(std::nothrow) VirtioSCSIRequest(true);
79 	if (fRequest == NULL) {
80 		fStatus = B_NO_MEMORY;
81 		return;
82 	}
83 
84 	::virtio_queue virtioQueues[3];
85 	fStatus = fVirtio->alloc_queues(fVirtioDevice, 3, virtioQueues);
86 	if (fStatus != B_OK) {
87 		ERROR("queue allocation failed (%s)\n", strerror(fStatus));
88 		return;
89 	}
90 
91 	fControlVirtioQueue = virtioQueues[0];
92 	fEventVirtioQueue = virtioQueues[1];
93 	fRequestVirtioQueue = virtioQueues[2];
94 
95 	for (uint32 i = 0; i < VIRTIO_SCSI_NUM_EVENTS; i++)
96 		_SubmitEvent(i);
97 
98 	fStatus = fVirtio->setup_interrupt(fVirtioDevice, NULL, this);
99 	if (fStatus != B_OK) {
100 		ERROR("interrupt setup failed (%s)\n", strerror(fStatus));
101 		return;
102 	}
103 
104 	fStatus = fVirtio->queue_setup_interrupt(fControlVirtioQueue,
105 		NULL, NULL);
106 	if (fStatus == B_OK) {
107 		fStatus = fVirtio->queue_setup_interrupt(fEventVirtioQueue,
108 			VirtioSCSIController::_EventCallback, this);
109 	}
110 	if (fStatus == B_OK) {
111 		fStatus = fVirtio->queue_setup_interrupt(fRequestVirtioQueue,
112 			VirtioSCSIController::_RequestCallback, this);
113 	}
114 
115 	if (fStatus != B_OK) {
116 		ERROR("queue interrupt setup failed (%s)\n", strerror(fStatus));
117 		return;
118 	}
119 }
120 
121 
122 VirtioSCSIController::~VirtioSCSIController()
123 {
124 	CALLED();
125 	delete fRequest;
126 
127 	gSCSI->free_dpc(fEventDPC);
128 }
129 
130 
131 status_t
132 VirtioSCSIController::InitCheck()
133 {
134 	return fStatus;
135 }
136 
137 
138 void
139 VirtioSCSIController::SetBus(scsi_bus bus)
140 {
141 	fBus = bus;
142 }
143 
144 
145 void
146 VirtioSCSIController::PathInquiry(scsi_path_inquiry *info)
147 {
148 	info->hba_inquiry = SCSI_PI_TAG_ABLE;
149 	info->hba_misc = 0;
150 	info->sim_priv = 0;
151 	info->initiator_id = VIRTIO_SCSI_INITIATOR_ID;
152 	info->hba_queue_size = fConfig.cmd_per_lun != 0 ? fConfig.cmd_per_lun : 1;
153 	memset(info->vuhba_flags, 0, sizeof(info->vuhba_flags));
154 
155 	strlcpy(info->sim_vid, "Haiku", SCSI_SIM_ID);
156 	strlcpy(info->hba_vid, "VirtIO", SCSI_HBA_ID);
157 
158 	strlcpy(info->sim_version, "1.0", SCSI_VERS);
159 	strlcpy(info->hba_version, "1.0", SCSI_VERS);
160 	strlcpy(info->controller_family, "Virtio", SCSI_FAM_ID);
161 	strlcpy(info->controller_type, "Virtio", SCSI_TYPE_ID);
162 }
163 
164 
165 void
166 VirtioSCSIController::GetRestrictions(uint8 targetID, bool *isATAPI,
167 	bool *noAutoSense, uint32 *maxBlocks)
168 {
169 	*isATAPI = false;
170 	*noAutoSense = true;
171 	*maxBlocks = fConfig.cmd_per_lun;
172 }
173 
174 
175 uchar
176 VirtioSCSIController::ResetDevice(uchar targetID, uchar targetLUN)
177 {
178 	return SCSI_REQ_CMP;
179 }
180 
181 
182 status_t
183 VirtioSCSIController::ExecuteRequest(scsi_ccb *ccb)
184 {
185 	status_t result = fRequest->Start(ccb);
186 	if (result != B_OK)
187 		return result;
188 
189 	if (ccb->cdb[0] == SCSI_OP_REQUEST_SENSE && fRequest->HasSense()) {
190 		TRACE("request sense\n");
191 		fRequest->RequestSense();
192 		fRequest->Finish(false);
193 		return B_OK;
194 	}
195 
196 	if (ccb->target_id > fConfig.max_target) {
197 		ERROR("invalid target device\n");
198 		fRequest->SetStatus(SCSI_TID_INVALID);
199 		fRequest->Finish(false);
200 		return B_BAD_INDEX;
201 	}
202 
203 	if (ccb->target_lun > fConfig.max_lun) {
204 		ERROR("invalid lun device\n");
205 		fRequest->SetStatus(SCSI_LUN_INVALID);
206 		fRequest->Finish(false);
207 		return B_BAD_INDEX;
208 	}
209 
210 	if (ccb->cdb_length > VIRTIO_SCSI_CDB_SIZE) {
211 		fRequest->SetStatus(SCSI_REQ_INVALID);
212 		fRequest->Finish(false);
213 		return B_BAD_VALUE;
214 	}
215 
216 	bool isOut = (ccb->flags & SCSI_DIR_MASK) == SCSI_DIR_OUT;
217 	bool isIn = (ccb->flags & SCSI_DIR_MASK) == SCSI_DIR_IN;
218 
219 	// TODO check feature inout if request is bidirectional
220 
221 	fRequest->SetTimeout(ccb->timeout > 0 ? ccb->timeout * 1000 * 1000
222 		: VIRTIO_SCSI_STANDARD_TIMEOUT);
223 
224 	uint32 inCount = (isIn ? ccb->sg_count : 0) + 1;
225 	uint32 outCount = (isOut ? ccb->sg_count : 0) + 1;
226 	BStackOrHeapArray<physical_entry, 16> entries(inCount + outCount);
227 	if (!entries.IsValid()) {
228 		fRequest->SetStatus(SCSI_REQ_INVALID);
229 		fRequest->Finish(false);
230 		return B_NO_MEMORY;
231 	}
232 	fRequest->FillRequest(inCount, outCount, entries);
233 
234 	{
235 		InterruptsSpinLocker locker(fInterruptLock);
236 		fExpectsInterrupt = true;
237 		fInterruptCondition.Add(&fInterruptConditionEntry);
238 	}
239 
240 	fVirtio->queue_request_v(fRequestVirtioQueue, entries,
241 		outCount, inCount, NULL);
242 
243 	result = fInterruptConditionEntry.Wait(B_RELATIVE_TIMEOUT,
244 		fRequest->Timeout());
245 
246 	{
247 		InterruptsSpinLocker locker(fInterruptLock);
248 		fExpectsInterrupt = false;
249 	}
250 
251 	if (result != B_OK)
252 		return result;
253 
254 	return fRequest->Finish(false);
255 }
256 
257 
258 uchar
259 VirtioSCSIController::AbortRequest(scsi_ccb *request)
260 {
261 	return SCSI_REQ_CMP;
262 }
263 
264 
265 uchar
266 VirtioSCSIController::TerminateRequest(scsi_ccb *request)
267 {
268 	return SCSI_REQ_CMP;
269 }
270 
271 
272 status_t
273 VirtioSCSIController::Control(uint8 targetID, uint32 op, void *buffer,
274 	size_t length)
275 {
276 	CALLED();
277 	return B_DEV_INVALID_IOCTL;
278 }
279 
280 
281 void
282 VirtioSCSIController::_RequestCallback(void* driverCookie, void* cookie)
283 {
284 	CALLED();
285 	VirtioSCSIController* controller = (VirtioSCSIController*)driverCookie;
286 
287 	while (controller->fVirtio->queue_dequeue(
288 			controller->fRequestVirtioQueue, NULL) != NULL) {
289 	}
290 
291 	controller->_RequestInterrupt();
292 }
293 
294 
295 void
296 VirtioSCSIController::_RequestInterrupt()
297 {
298 	SpinLocker locker(fInterruptLock);
299 	fInterruptCondition.NotifyAll();
300 }
301 
302 
303 
304 void
305 VirtioSCSIController::_EventCallback(void* driverCookie, void* cookie)
306 {
307 	CALLED();
308 	VirtioSCSIController* controller = (VirtioSCSIController*)driverCookie;
309 
310 	while (true) {
311 		virtio_scsi_event* event = (virtio_scsi_event*)
312 			controller->fVirtio->queue_dequeue(controller->fEventVirtioQueue,
313 				NULL);
314 		if (event == NULL)
315 			break;
316 		controller->_EventInterrupt(event);
317 	}
318 }
319 
320 
321 void
322 VirtioSCSIController::_EventInterrupt(struct virtio_scsi_event* event)
323 {
324 	CALLED();
325 	TRACE("events %#x\n", event->event);
326 	if ((event->event & VIRTIO_SCSI_T_EVENTS_MISSED) != 0) {
327 		ERROR("events missed\n");
328 	} else switch (event->event) {
329 		case VIRTIO_SCSI_T_TRANSPORT_RESET:
330 			ERROR("transport reset\n");
331 			break;
332 		case VIRTIO_SCSI_T_ASYNC_NOTIFY:
333 			ERROR("async notify\n");
334 			break;
335 		case VIRTIO_SCSI_T_PARAM_CHANGE:
336 		{
337 			uint16 sense = (event->reason >> 8)
338 				| ((event->reason & 0xff) << 8);
339 			if (sense == SCSIS_ASC_CAPACITY_DATA_HAS_CHANGED) {
340 				ERROR("capacity data has changed for %x:%x\n", event->lun[1],
341 					event->lun[2] << 8 | event->lun[3]);
342 				gSCSI->schedule_dpc(fBus, fEventDPC, _RescanChildBus, this);
343 			} else
344 				ERROR("param change, unknown reason\n");
345 			break;
346 		}
347 		default:
348 			ERROR("unknown event %#x\n", event->event);
349 			break;
350 	}
351 }
352 
353 
354 void
355 VirtioSCSIController::_SubmitEvent(uint32 eventNumber)
356 {
357 	CALLED();
358 	struct virtio_scsi_event* event = &fEventBuffers[eventNumber];
359 	bzero(event, sizeof(struct virtio_scsi_event));
360 
361 	physical_entry entry;
362 	get_memory_map(event, sizeof(struct virtio_scsi_event), &entry, 1);
363 
364 	fVirtio->queue_request_v(fEventVirtioQueue, &entry,
365 		0, 1, event);
366 }
367 
368 
369 void
370 VirtioSCSIController::_RescanChildBus(void *cookie)
371 {
372 	CALLED();
373 	VirtioSCSIController* controller = (VirtioSCSIController*)cookie;
374 	device_node *childNode = NULL;
375 	const device_attr attrs[] = { { NULL } };
376 	if (gDeviceManager->get_next_child_node(controller->fNode, attrs,
377 		&childNode) != B_OK) {
378 		ERROR("couldn't find the child node for %p\n", controller->fNode);
379 		return;
380 	}
381 
382 	gDeviceManager->rescan_node(childNode);
383 	TRACE("rescan done %p\n", childNode);
384 	gDeviceManager->put_node(childNode);
385 }
386