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
18const char *
19get_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
33VirtioSCSIController::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	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
122VirtioSCSIController::~VirtioSCSIController()
123{
124	CALLED();
125	delete fRequest;
126
127	gSCSI->free_dpc(fEventDPC);
128}
129
130
131status_t
132VirtioSCSIController::InitCheck()
133{
134	return fStatus;
135}
136
137
138void
139VirtioSCSIController::SetBus(scsi_bus bus)
140{
141	fBus = bus;
142}
143
144
145void
146VirtioSCSIController::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
165void
166VirtioSCSIController::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
175uchar
176VirtioSCSIController::ResetDevice(uchar targetID, uchar targetLUN)
177{
178	return SCSI_REQ_CMP;
179}
180
181
182status_t
183VirtioSCSIController::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	atomic_add(&fCurrentRequest, 1);
235	fInterruptCondition.Add(&fInterruptConditionEntry);
236
237	fVirtio->queue_request_v(fRequestVirtioQueue, entries,
238		outCount, inCount, (void *)(addr_t)fCurrentRequest);
239
240	result = fInterruptConditionEntry.Wait(B_RELATIVE_TIMEOUT,
241		fRequest->Timeout());
242
243	if (result != B_OK) {
244		ERROR("wait failed with status: %#" B_PRIx32 "\n", result);
245		fRequest->Abort();
246		return result;
247	}
248
249	return fRequest->Finish(false);
250}
251
252
253uchar
254VirtioSCSIController::AbortRequest(scsi_ccb *request)
255{
256	return SCSI_REQ_CMP;
257}
258
259
260uchar
261VirtioSCSIController::TerminateRequest(scsi_ccb *request)
262{
263	return SCSI_REQ_CMP;
264}
265
266
267status_t
268VirtioSCSIController::Control(uint8 targetID, uint32 op, void *buffer,
269	size_t length)
270{
271	CALLED();
272	return B_DEV_INVALID_IOCTL;
273}
274
275
276void
277VirtioSCSIController::_RequestCallback(void* driverCookie, void* cookie)
278{
279	CALLED();
280	VirtioSCSIController* controller = (VirtioSCSIController*)driverCookie;
281	controller->_RequestInterrupt();
282}
283
284
285void
286VirtioSCSIController::_RequestInterrupt()
287{
288	void* cookie = NULL;
289	while (fVirtio->queue_dequeue(fRequestVirtioQueue, &cookie, NULL)) {
290		if ((int32)(addr_t)cookie == atomic_get(&fCurrentRequest))
291			fInterruptCondition.NotifyAll();
292	}
293}
294
295
296
297void
298VirtioSCSIController::_EventCallback(void* driverCookie, void* cookie)
299{
300	CALLED();
301	VirtioSCSIController* controller = (VirtioSCSIController*)driverCookie;
302
303	virtio_scsi_event* event = NULL;
304	while (controller->fVirtio->queue_dequeue(controller->fEventVirtioQueue,
305			(void**)&event, NULL)) {
306		controller->_EventInterrupt(event);
307	}
308}
309
310
311void
312VirtioSCSIController::_EventInterrupt(struct virtio_scsi_event* event)
313{
314	CALLED();
315	TRACE("events %#x\n", event->event);
316	if ((event->event & VIRTIO_SCSI_T_EVENTS_MISSED) != 0) {
317		ERROR("events missed\n");
318	} else switch (event->event) {
319		case VIRTIO_SCSI_T_TRANSPORT_RESET:
320			ERROR("transport reset\n");
321			break;
322		case VIRTIO_SCSI_T_ASYNC_NOTIFY:
323			ERROR("async notify\n");
324			break;
325		case VIRTIO_SCSI_T_PARAM_CHANGE:
326		{
327			uint16 sense = (event->reason >> 8)
328				| ((event->reason & 0xff) << 8);
329			if (sense == SCSIS_ASC_CAPACITY_DATA_HAS_CHANGED) {
330				ERROR("capacity data has changed for %x:%x\n", event->lun[1],
331					event->lun[2] << 8 | event->lun[3]);
332				gSCSI->schedule_dpc(fBus, fEventDPC, _RescanChildBus, this);
333			} else
334				ERROR("param change, unknown reason\n");
335			break;
336		}
337		default:
338			ERROR("unknown event %#x\n", event->event);
339			break;
340	}
341}
342
343
344void
345VirtioSCSIController::_SubmitEvent(uint32 eventNumber)
346{
347	CALLED();
348	struct virtio_scsi_event* event = &fEventBuffers[eventNumber];
349	bzero(event, sizeof(struct virtio_scsi_event));
350
351	physical_entry entry;
352	get_memory_map(event, sizeof(struct virtio_scsi_event), &entry, 1);
353
354	fVirtio->queue_request_v(fEventVirtioQueue, &entry,
355		0, 1, event);
356}
357
358
359void
360VirtioSCSIController::_RescanChildBus(void *cookie)
361{
362	CALLED();
363	VirtioSCSIController* controller = (VirtioSCSIController*)cookie;
364	device_node *childNode = NULL;
365	const device_attr attrs[] = { { NULL } };
366	if (gDeviceManager->get_next_child_node(controller->fNode, attrs,
367		&childNode) != B_OK) {
368		ERROR("couldn't find the child node for %p\n", controller->fNode);
369		return;
370	}
371
372	gDeviceManager->rescan_node(childNode);
373	TRACE("rescan done %p\n", childNode);
374	gDeviceManager->put_node(childNode);
375}
376