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