xref: /haiku/src/add-ons/kernel/busses/scsi/virtio/VirtioSCSIController.cpp (revision 344ded80d400028c8f561b4b876257b94c12db4a)
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(uint64 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 	fCurrentRequest(0),
41 	fEventDPC(NULL)
42 {
43 	CALLED();
44 
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 	uint16 requestedSizes[3] = { 0, 0, 0 };
86 	requestedSizes[2] = fConfig.seg_max + 2;
87 		// two entries are taken up by the header and result
88 
89 	fStatus = fVirtio->alloc_queues(fVirtioDevice, 3, virtioQueues,
90 		requestedSizes);
91 	if (fStatus != B_OK) {
92 		ERROR("queue allocation failed (%s)\n", strerror(fStatus));
93 		return;
94 	}
95 
96 	fControlVirtioQueue = virtioQueues[0];
97 	fEventVirtioQueue = virtioQueues[1];
98 	fRequestVirtioQueue = virtioQueues[2];
99 
100 	for (uint32 i = 0; i < VIRTIO_SCSI_NUM_EVENTS; i++)
101 		_SubmitEvent(i);
102 
103 	fStatus = fVirtio->setup_interrupt(fVirtioDevice, NULL, this);
104 	if (fStatus != B_OK) {
105 		ERROR("interrupt setup failed (%s)\n", strerror(fStatus));
106 		return;
107 	}
108 
109 	fStatus = fVirtio->queue_setup_interrupt(fControlVirtioQueue,
110 		NULL, NULL);
111 	if (fStatus == B_OK) {
112 		fStatus = fVirtio->queue_setup_interrupt(fEventVirtioQueue,
113 			VirtioSCSIController::_EventCallback, this);
114 	}
115 	if (fStatus == B_OK) {
116 		fStatus = fVirtio->queue_setup_interrupt(fRequestVirtioQueue,
117 			VirtioSCSIController::_RequestCallback, this);
118 	}
119 
120 	if (fStatus != B_OK) {
121 		ERROR("queue interrupt setup failed (%s)\n", strerror(fStatus));
122 		return;
123 	}
124 }
125 
126 
127 VirtioSCSIController::~VirtioSCSIController()
128 {
129 	CALLED();
130 	delete fRequest;
131 
132 	gSCSI->free_dpc(fEventDPC);
133 }
134 
135 
136 status_t
137 VirtioSCSIController::InitCheck()
138 {
139 	return fStatus;
140 }
141 
142 
143 void
144 VirtioSCSIController::SetBus(scsi_bus bus)
145 {
146 	fBus = bus;
147 }
148 
149 
150 void
151 VirtioSCSIController::PathInquiry(scsi_path_inquiry *info)
152 {
153 	info->hba_inquiry = SCSI_PI_TAG_ABLE;
154 	info->hba_misc = 0;
155 	info->initiator_id = VIRTIO_SCSI_INITIATOR_ID;
156 	info->hba_queue_size = fConfig.cmd_per_lun != 0 ? fConfig.cmd_per_lun : 1;
157 	memset(info->vuhba_flags, 0, sizeof(info->vuhba_flags));
158 
159 	strlcpy(info->sim_vid, "Haiku", SCSI_SIM_ID);
160 	strlcpy(info->hba_vid, "VirtIO", SCSI_HBA_ID);
161 
162 	strlcpy(info->sim_version, "1.0", SCSI_VERS);
163 	strlcpy(info->hba_version, "1.0", SCSI_VERS);
164 	strlcpy(info->controller_family, "Virtio", SCSI_FAM_ID);
165 	strlcpy(info->controller_type, "Virtio", SCSI_TYPE_ID);
166 }
167 
168 
169 void
170 VirtioSCSIController::GetRestrictions(uint8 targetID, bool *isATAPI,
171 	bool *noAutoSense, uint32 *maxBlocks)
172 {
173 	*isATAPI = false;
174 	*noAutoSense = true;
175 	*maxBlocks = fConfig.cmd_per_lun;
176 }
177 
178 
179 uchar
180 VirtioSCSIController::ResetDevice(uchar targetID, uchar targetLUN)
181 {
182 	return SCSI_REQ_CMP;
183 }
184 
185 
186 status_t
187 VirtioSCSIController::ExecuteRequest(scsi_ccb *ccb)
188 {
189 	status_t result = fRequest->Start(ccb);
190 	if (result != B_OK)
191 		return result;
192 
193 	if (ccb->cdb[0] == SCSI_OP_REQUEST_SENSE && fRequest->HasSense()) {
194 		TRACE("request sense\n");
195 		fRequest->RequestSense();
196 		fRequest->Finish(false);
197 		return B_OK;
198 	}
199 
200 	if (ccb->target_id > fConfig.max_target) {
201 		ERROR("invalid target device\n");
202 		fRequest->SetStatus(SCSI_TID_INVALID);
203 		fRequest->Finish(false);
204 		return B_BAD_INDEX;
205 	}
206 
207 	if (ccb->target_lun > fConfig.max_lun) {
208 		ERROR("invalid lun device\n");
209 		fRequest->SetStatus(SCSI_LUN_INVALID);
210 		fRequest->Finish(false);
211 		return B_BAD_INDEX;
212 	}
213 
214 	if (ccb->cdb_length > VIRTIO_SCSI_CDB_SIZE) {
215 		fRequest->SetStatus(SCSI_REQ_INVALID);
216 		fRequest->Finish(false);
217 		return B_BAD_VALUE;
218 	}
219 
220 	bool isOut = (ccb->flags & SCSI_DIR_MASK) == SCSI_DIR_OUT;
221 	bool isIn = (ccb->flags & SCSI_DIR_MASK) == SCSI_DIR_IN;
222 
223 	// TODO check feature inout if request is bidirectional
224 
225 	fRequest->SetTimeout(ccb->timeout > 0 ? ccb->timeout * 1000 * 1000
226 		: VIRTIO_SCSI_STANDARD_TIMEOUT);
227 
228 	uint32 inCount = (isIn ? ccb->sg_count : 0) + 1;
229 	uint32 outCount = (isOut ? ccb->sg_count : 0) + 1;
230 	BStackOrHeapArray<physical_entry, 16> entries(inCount + outCount);
231 	if (!entries.IsValid()) {
232 		fRequest->SetStatus(SCSI_REQ_INVALID);
233 		fRequest->Finish(false);
234 		return B_NO_MEMORY;
235 	}
236 	fRequest->FillRequest(inCount, outCount, entries);
237 
238 	atomic_add(&fCurrentRequest, 1);
239 	ConditionVariableEntry entry;
240 	fInterruptCondition.Add(&entry);
241 
242 	result = fVirtio->queue_request_v(fRequestVirtioQueue, entries,
243 		outCount, inCount, (void *)(addr_t)fCurrentRequest);
244 
245 	if (result != B_OK)
246 		ERROR("queueing failed with status: %#" B_PRIx32 "\n", result);
247 	else {
248 		result = entry.Wait(B_RELATIVE_TIMEOUT, fRequest->Timeout());
249 		if (result != B_OK)
250 			ERROR("wait failed with status: %#" B_PRIx32 "\n", result);
251 	}
252 
253 	if (result != B_OK) {
254 		fRequest->Abort();
255 		return result;
256 	}
257 
258 	return fRequest->Finish(false);
259 }
260 
261 
262 uchar
263 VirtioSCSIController::AbortRequest(scsi_ccb *request)
264 {
265 	return SCSI_REQ_CMP;
266 }
267 
268 
269 uchar
270 VirtioSCSIController::TerminateRequest(scsi_ccb *request)
271 {
272 	return SCSI_REQ_CMP;
273 }
274 
275 
276 status_t
277 VirtioSCSIController::Control(uint8 targetID, uint32 op, void *buffer,
278 	size_t length)
279 {
280 	CALLED();
281 	return B_DEV_INVALID_IOCTL;
282 }
283 
284 
285 void
286 VirtioSCSIController::_RequestCallback(void* driverCookie, void* cookie)
287 {
288 	CALLED();
289 	VirtioSCSIController* controller = (VirtioSCSIController*)driverCookie;
290 	controller->_RequestInterrupt();
291 }
292 
293 
294 void
295 VirtioSCSIController::_RequestInterrupt()
296 {
297 	void* cookie = NULL;
298 	while (fVirtio->queue_dequeue(fRequestVirtioQueue, &cookie, NULL)) {
299 		if ((int32)(addr_t)cookie == atomic_get(&fCurrentRequest))
300 			fInterruptCondition.NotifyAll();
301 	}
302 }
303 
304 
305 
306 void
307 VirtioSCSIController::_EventCallback(void* driverCookie, void* cookie)
308 {
309 	CALLED();
310 	VirtioSCSIController* controller = (VirtioSCSIController*)driverCookie;
311 
312 	virtio_scsi_event* event = NULL;
313 	while (controller->fVirtio->queue_dequeue(controller->fEventVirtioQueue,
314 			(void**)&event, NULL)) {
315 		controller->_EventInterrupt(event);
316 	}
317 }
318 
319 
320 void
321 VirtioSCSIController::_EventInterrupt(struct virtio_scsi_event* event)
322 {
323 	CALLED();
324 	TRACE("events %#x\n", event->event);
325 	if ((event->event & VIRTIO_SCSI_T_EVENTS_MISSED) != 0) {
326 		ERROR("events missed\n");
327 	} else switch (event->event) {
328 		case VIRTIO_SCSI_T_TRANSPORT_RESET:
329 			ERROR("transport reset\n");
330 			break;
331 		case VIRTIO_SCSI_T_ASYNC_NOTIFY:
332 			ERROR("async notify\n");
333 			break;
334 		case VIRTIO_SCSI_T_PARAM_CHANGE:
335 		{
336 			uint16 sense = (event->reason >> 8)
337 				| ((event->reason & 0xff) << 8);
338 			if (sense == SCSIS_ASC_CAPACITY_DATA_HAS_CHANGED) {
339 				ERROR("capacity data has changed for %x:%x\n", event->lun[1],
340 					event->lun[2] << 8 | event->lun[3]);
341 				gSCSI->schedule_dpc(fBus, fEventDPC, _RescanChildBus, this);
342 			} else
343 				ERROR("param change, unknown reason\n");
344 			break;
345 		}
346 		default:
347 			ERROR("unknown event %#x\n", event->event);
348 			break;
349 	}
350 }
351 
352 
353 void
354 VirtioSCSIController::_SubmitEvent(uint32 eventNumber)
355 {
356 	CALLED();
357 	struct virtio_scsi_event* event = &fEventBuffers[eventNumber];
358 	bzero(event, sizeof(struct virtio_scsi_event));
359 
360 	physical_entry entry;
361 	get_memory_map(event, sizeof(struct virtio_scsi_event), &entry, 1);
362 
363 	fVirtio->queue_request_v(fEventVirtioQueue, &entry,
364 		0, 1, event);
365 }
366 
367 
368 void
369 VirtioSCSIController::_RescanChildBus(void *cookie)
370 {
371 	CALLED();
372 	VirtioSCSIController* controller = (VirtioSCSIController*)cookie;
373 	device_node *childNode = NULL;
374 	const device_attr attrs[] = { { NULL } };
375 	if (gDeviceManager->get_next_child_node(controller->fNode, attrs,
376 		&childNode) != B_OK) {
377 		ERROR("couldn't find the child node for %p\n", controller->fNode);
378 		return;
379 	}
380 
381 	gDeviceManager->rescan_node(childNode);
382 	TRACE("rescan done %p\n", childNode);
383 	gDeviceManager->put_node(childNode);
384 }
385