1/*
2 * Copyright 2013, J��r��me Duval, korli@users.berlios.de.
3 * Copyright 2009, Michael Lotz, mmlr@mlotz.ch.
4 * Distributed under the terms of the MIT License.
5 */
6
7
8#include "VirtioSCSIPrivate.h"
9
10#include <strings.h>
11
12
13VirtioSCSIRequest::VirtioSCSIRequest(bool hasLock)
14	:
15	fHasLock(hasLock),
16	fStatus(SCSI_REQ_CMP),
17	fTimeout(0),
18	fBytesLeft(0),
19	fIsWrite(false),
20	fCCB(NULL)
21{
22	if (hasLock)
23		mutex_init(&fLock, "virtio scsi request");
24
25	fBuffer = malloc(sizeof(struct virtio_scsi_cmd_req)
26		+ sizeof(struct virtio_scsi_cmd_resp));
27	bzero(fBuffer, sizeof(struct virtio_scsi_cmd_req)
28		+ sizeof(struct virtio_scsi_cmd_resp));
29
30	fRequest = (struct virtio_scsi_cmd_req *)fBuffer;
31	fResponse = (struct virtio_scsi_cmd_resp *)
32		((addr_t)fBuffer + sizeof(struct virtio_scsi_cmd_req));
33
34	fResponse->sense_len = 0;
35}
36
37
38VirtioSCSIRequest::~VirtioSCSIRequest()
39{
40	if (fHasLock)
41		mutex_destroy(&fLock);
42
43	free(fBuffer);
44}
45
46
47void
48VirtioSCSIRequest::SetStatus(uint8 status)
49{
50	fStatus = status;
51}
52
53
54void
55VirtioSCSIRequest::SetTimeout(bigtime_t timeout)
56{
57	fTimeout = timeout;
58}
59
60
61void
62VirtioSCSIRequest::SetIsWrite(bool isWrite)
63{
64	fIsWrite = isWrite;
65}
66
67
68void
69VirtioSCSIRequest::SetBytesLeft(uint32 bytesLeft)
70{
71	fBytesLeft = bytesLeft;
72}
73
74
75status_t
76VirtioSCSIRequest::Start(scsi_ccb *ccb)
77{
78	CALLED();
79	if (mutex_trylock(&fLock) != B_OK)
80		return B_BUSY;
81
82	fCCB = ccb;
83	fStatus = SCSI_REQ_CMP;
84	fCCB->device_status = SCSI_STATUS_GOOD;
85	fIsWrite = false;
86	bzero(fResponse, sizeof(struct virtio_scsi_cmd_resp));
87
88	TRACE("VirtioSCSIRequest::Start() opcode %x tid %x lun %x\n", ccb->cdb[0],
89		ccb->target_id, ccb->target_lun);
90
91	return B_OK;
92}
93
94
95status_t
96VirtioSCSIRequest::Finish(bool resubmit)
97{
98	CALLED();
99	fStatus = _ResponseStatus();
100	fCCB->data_resid = fResponse->resid;
101	fCCB->subsys_status = fStatus;
102
103	TRACE("VirtioSCSIRequest::Finish() status 0x%x response 0x%x resid:0x%x"
104		" sense_len:%x\n", fResponse->status, fResponse->response,
105		fResponse->resid, fResponse->sense_len);
106
107	if (fCCB->cdb[0] == SCSI_OP_INQUIRY) {
108		// when the request is an inquiry, don't do anything
109	} else if (fStatus == SCSI_REQ_CMP && fResponse->status != 0
110		&& HasSense()) {
111		// when the request completed and has set sense
112		// data, report this to the scsi stack by setting
113		// CHECK CONDITION status
114		TRACE("setting check condition\n");
115
116		fCCB->subsys_status = SCSI_REQ_CMP_ERR;
117		fCCB->device_status = SCSI_STATUS_CHECK_CONDITION;
118
119		// copy sense data if caller requested it
120		if ((fCCB->flags & SCSI_DIS_AUTOSENSE) == 0) {
121			size_t senseLength = min_c(sizeof(fCCB->sense),
122				fResponse->sense_len);
123			memcpy(fCCB->sense, fResponse->sense, senseLength);
124			fCCB->sense_resid = sizeof(fCCB->sense) - senseLength;
125			fCCB->subsys_status |= SCSI_AUTOSNS_VALID;
126		}
127	}
128
129	scsi_ccb *ccb = fCCB;
130	mutex_unlock(&fLock);
131
132	if (resubmit)
133		gSCSI->resubmit(ccb);
134	else
135		gSCSI->finished(ccb, 1);
136
137	TRACE("VirtioSCSIRequest::Finish() done\n");
138
139	return B_OK;
140}
141
142
143void
144VirtioSCSIRequest::Abort()
145{
146	scsi_ccb *ccb = fCCB;
147	mutex_unlock(&fLock);
148
149	ccb->subsys_status = SCSI_REQ_ABORTED;
150	gSCSI->finished(ccb, 1);
151}
152
153
154void
155VirtioSCSIRequest::RequestSense()
156{
157	CALLED();
158	// Copy sense data from last request into data buffer of current request.
159	// The sense data of last request is still present in the current request,
160	// as it isn't cleared on SCSI_OP_REQUEST_SENSE.
161	scsi_cmd_request_sense *command = (scsi_cmd_request_sense *)fCCB->cdb;
162	copy_sg_data(fCCB, 0, command->allocation_length, fResponse->sense,
163		fResponse->sense_len, false);
164
165	fCCB->data_resid = fCCB->data_length - min_c(min_c(fResponse->sense_len,
166		command->allocation_length), fCCB->data_length);
167	fResponse->sense_len = 0;
168}
169
170
171void
172VirtioSCSIRequest::FillRequest(uint32 inCount, uint32 outCount,
173	physical_entry *entries)
174{
175	CALLED();
176	fRequest->task_attr = VIRTIO_SCSI_S_SIMPLE;
177	fRequest->tag = (addr_t)fCCB;
178	fRequest->lun[0] = 1;
179	fRequest->lun[1] = fCCB->target_id;
180	// we don't support lun >= 256
181	fRequest->lun[2] = 0x40;
182	fRequest->lun[3] = fCCB->target_lun & 0xff;
183
184	memcpy(fRequest->cdb, fCCB->cdb, min_c(fCCB->cdb_length,
185		min_c(sizeof(fRequest->cdb), sizeof(fCCB->cdb))));
186
187	get_memory_map(fBuffer, sizeof(struct virtio_scsi_cmd_req)
188		+ sizeof(struct virtio_scsi_cmd_resp), &entries[0], 1);
189	entries[0].size = sizeof(struct virtio_scsi_cmd_req);
190	if (outCount > 1) {
191		memcpy(entries + 1, fCCB->sg_list, fCCB->sg_count
192			* sizeof(physical_entry));
193	}
194
195	entries[outCount].address = entries[0].address
196		+ sizeof(struct virtio_scsi_cmd_req);
197	entries[outCount].size = sizeof(struct virtio_scsi_cmd_resp);
198
199	if (inCount > 1) {
200		memcpy(entries + outCount + 1, fCCB->sg_list, fCCB->sg_count
201			* sizeof(physical_entry));
202	}
203}
204
205
206uchar
207VirtioSCSIRequest::_ResponseStatus()
208{
209	uchar status;
210
211	switch (fResponse->response) {
212		case VIRTIO_SCSI_S_OK:
213			status = SCSI_REQ_CMP;
214			break;
215		case VIRTIO_SCSI_S_OVERRUN:
216			status = SCSI_DATA_RUN_ERR;
217			break;
218		case VIRTIO_SCSI_S_ABORTED:
219			status = SCSI_REQ_ABORTED;
220			break;
221		case VIRTIO_SCSI_S_BAD_TARGET:
222			status = SCSI_TID_INVALID;
223			break;
224		case VIRTIO_SCSI_S_RESET:
225			status = SCSI_SCSI_BUS_RESET;
226			break;
227		case VIRTIO_SCSI_S_BUSY:
228			status = SCSI_SCSI_BUSY;
229			break;
230		case VIRTIO_SCSI_S_TRANSPORT_FAILURE:
231		case VIRTIO_SCSI_S_TARGET_FAILURE:
232		case VIRTIO_SCSI_S_NEXUS_FAILURE:
233			status = SCSI_NO_NEXUS;
234			break;
235		default: /* VIRTIO_SCSI_S_FAILURE */
236			status = SCSI_REQ_CMP_ERR;
237			break;
238	}
239
240	return status;
241}
242