VirtualBox

source: vbox/trunk/src/VBox/Devices/Storage/VBoxSCSI.cpp@ 62956

Last change on this file since 62956 was 62506, checked in by vboxsync, 8 years ago

(C) 2016

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 16.8 KB
Line 
1/* $Id: VBoxSCSI.cpp 62506 2016-07-22 19:09:44Z vboxsync $ */
2/** @file
3 * VBox storage devices - Simple SCSI interface for BIOS access.
4 */
5
6/*
7 * Copyright (C) 2006-2016 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22//#define DEBUG
23#define LOG_GROUP LOG_GROUP_DEV_BUSLOGIC /** @todo Create extra group. */
24
25#if defined(IN_R0) || defined(IN_RC)
26# error This device has no R0 or RC components
27#endif
28
29#include <VBox/vmm/pdmdev.h>
30#include <VBox/vmm/pgm.h>
31#include <iprt/asm.h>
32#include <iprt/mem.h>
33#include <iprt/thread.h>
34#include <iprt/string.h>
35
36#include "VBoxSCSI.h"
37
38
39/**
40 * Resets the state.
41 */
42static void vboxscsiReset(PVBOXSCSI pVBoxSCSI, bool fEverything)
43{
44 if (fEverything)
45 {
46 pVBoxSCSI->regIdentify = 0;
47 pVBoxSCSI->fBusy = false;
48 }
49 pVBoxSCSI->cbCDB = 0;
50 RT_ZERO(pVBoxSCSI->abCDB);
51 pVBoxSCSI->iCDB = 0;
52 pVBoxSCSI->rcCompletion = 0;
53 pVBoxSCSI->uTargetDevice = 0;
54 pVBoxSCSI->cbBuf = 0;
55 pVBoxSCSI->cbBufLeft = 0;
56 pVBoxSCSI->iBuf = 0;
57 if (pVBoxSCSI->pbBuf)
58 RTMemFree(pVBoxSCSI->pbBuf);
59 pVBoxSCSI->pbBuf = NULL;
60 pVBoxSCSI->enmState = VBOXSCSISTATE_NO_COMMAND;
61}
62
63/**
64 * Initializes the state for the SCSI interface.
65 *
66 * @returns VBox status code.
67 * @param pVBoxSCSI Pointer to the unitialized SCSI state.
68 */
69int vboxscsiInitialize(PVBOXSCSI pVBoxSCSI)
70{
71 pVBoxSCSI->pbBuf = NULL;
72 vboxscsiReset(pVBoxSCSI, true /*fEverything*/);
73
74 return VINF_SUCCESS;
75}
76
77/**
78 * Reads a register value.
79 *
80 * @returns VBox status code.
81 * @param pVBoxSCSI Pointer to the SCSI state.
82 * @param iRegister Index of the register to read.
83 * @param pu32Value Where to store the content of the register.
84 */
85int vboxscsiReadRegister(PVBOXSCSI pVBoxSCSI, uint8_t iRegister, uint32_t *pu32Value)
86{
87 uint8_t uVal = 0;
88
89 switch (iRegister)
90 {
91 case 0:
92 {
93 if (ASMAtomicReadBool(&pVBoxSCSI->fBusy) == true)
94 {
95 uVal |= VBOX_SCSI_BUSY;
96 /* There is an I/O operation in progress.
97 * Yield the execution thread to let the I/O thread make progress.
98 */
99 RTThreadYield();
100 }
101 if (pVBoxSCSI->rcCompletion)
102 uVal |= VBOX_SCSI_ERROR;
103 break;
104 }
105 case 1:
106 {
107 /* If we're not in the 'command ready' state, there may not even be a buffer yet. */
108 if ( pVBoxSCSI->enmState == VBOXSCSISTATE_COMMAND_READY
109 && pVBoxSCSI->cbBufLeft > 0)
110 {
111 AssertMsg(pVBoxSCSI->pbBuf, ("pBuf is NULL\n"));
112 Assert(!pVBoxSCSI->fBusy);
113 uVal = pVBoxSCSI->pbBuf[pVBoxSCSI->iBuf];
114 pVBoxSCSI->iBuf++;
115 pVBoxSCSI->cbBufLeft--;
116
117 /* When the guest reads the last byte from the data in buffer, clear
118 everything and reset command buffer. */
119 if (pVBoxSCSI->cbBufLeft == 0)
120 vboxscsiReset(pVBoxSCSI, false /*fEverything*/);
121 }
122 break;
123 }
124 case 2:
125 {
126 uVal = pVBoxSCSI->regIdentify;
127 break;
128 }
129 case 3:
130 {
131 uVal = pVBoxSCSI->rcCompletion;
132 break;
133 }
134 default:
135 AssertMsgFailed(("Invalid register to read from %u\n", iRegister));
136 }
137
138 *pu32Value = uVal;
139
140 return VINF_SUCCESS;
141}
142
143/**
144 * Writes to a register.
145 *
146 * @returns VBox status code.
147 * @retval VERR_MORE_DATA if a command is ready to be sent to the SCSI driver.
148 * @param pVBoxSCSI Pointer to the SCSI state.
149 * @param iRegister Index of the register to write to.
150 * @param uVal Value to write.
151 */
152int vboxscsiWriteRegister(PVBOXSCSI pVBoxSCSI, uint8_t iRegister, uint8_t uVal)
153{
154 int rc = VINF_SUCCESS;
155
156 switch (iRegister)
157 {
158 case 0:
159 {
160 if (pVBoxSCSI->enmState == VBOXSCSISTATE_NO_COMMAND)
161 {
162 pVBoxSCSI->enmState = VBOXSCSISTATE_READ_TXDIR;
163 pVBoxSCSI->uTargetDevice = uVal;
164 }
165 else if (pVBoxSCSI->enmState == VBOXSCSISTATE_READ_TXDIR)
166 {
167 if (uVal != VBOXSCSI_TXDIR_FROM_DEVICE && uVal != VBOXSCSI_TXDIR_TO_DEVICE)
168 vboxscsiReset(pVBoxSCSI, true /*fEverything*/);
169 else
170 {
171 pVBoxSCSI->enmState = VBOXSCSISTATE_READ_CDB_SIZE_BUFHI;
172 pVBoxSCSI->uTxDir = uVal;
173 }
174 }
175 else if (pVBoxSCSI->enmState == VBOXSCSISTATE_READ_CDB_SIZE_BUFHI)
176 {
177 uint8_t cbCDB = uVal & 0x0F;
178
179 if (cbCDB == 0)
180 cbCDB = 16;
181 if (cbCDB > VBOXSCSI_CDB_SIZE_MAX)
182 vboxscsiReset(pVBoxSCSI, true /*fEverything*/);
183 else
184 {
185 pVBoxSCSI->enmState = VBOXSCSISTATE_READ_BUFFER_SIZE_LSB;
186 pVBoxSCSI->cbCDB = cbCDB;
187 pVBoxSCSI->cbBuf = (uVal & 0xF0) << 12; /* Bits 16-19 of buffer size. */
188 }
189 }
190 else if (pVBoxSCSI->enmState == VBOXSCSISTATE_READ_BUFFER_SIZE_LSB)
191 {
192 pVBoxSCSI->enmState = VBOXSCSISTATE_READ_BUFFER_SIZE_MID;
193 pVBoxSCSI->cbBuf |= uVal; /* Bits 0-7 of buffer size. */
194 }
195 else if (pVBoxSCSI->enmState == VBOXSCSISTATE_READ_BUFFER_SIZE_MID)
196 {
197 pVBoxSCSI->enmState = VBOXSCSISTATE_READ_COMMAND;
198 pVBoxSCSI->cbBuf |= (((uint16_t)uVal) << 8); /* Bits 8-15 of buffer size. */
199 }
200 else if (pVBoxSCSI->enmState == VBOXSCSISTATE_READ_COMMAND)
201 {
202 pVBoxSCSI->abCDB[pVBoxSCSI->iCDB] = uVal;
203 pVBoxSCSI->iCDB++;
204
205 /* Check if we have all necessary command data. */
206 if (pVBoxSCSI->iCDB == pVBoxSCSI->cbCDB)
207 {
208 Log(("%s: Command ready for processing\n", __FUNCTION__));
209 pVBoxSCSI->enmState = VBOXSCSISTATE_COMMAND_READY;
210 pVBoxSCSI->cbBufLeft = pVBoxSCSI->cbBuf;
211 if (pVBoxSCSI->uTxDir == VBOXSCSI_TXDIR_TO_DEVICE)
212 {
213 /* This is a write allocate buffer. */
214 pVBoxSCSI->pbBuf = (uint8_t *)RTMemAllocZ(pVBoxSCSI->cbBuf);
215 if (!pVBoxSCSI->pbBuf)
216 return VERR_NO_MEMORY;
217 }
218 else
219 {
220 /* This is a read from the device. */
221 ASMAtomicXchgBool(&pVBoxSCSI->fBusy, true);
222 rc = VERR_MORE_DATA; /** @todo Better return value to indicate ready command? */
223 }
224 }
225 }
226 else
227 AssertMsgFailed(("Invalid state %d\n", pVBoxSCSI->enmState));
228 break;
229 }
230
231 case 1:
232 {
233 if ( pVBoxSCSI->enmState != VBOXSCSISTATE_COMMAND_READY
234 || pVBoxSCSI->uTxDir != VBOXSCSI_TXDIR_TO_DEVICE)
235 {
236 /* Reset the state */
237 vboxscsiReset(pVBoxSCSI, true /*fEverything*/);
238 }
239 else if (pVBoxSCSI->cbBufLeft > 0)
240 {
241 pVBoxSCSI->pbBuf[pVBoxSCSI->iBuf++] = uVal;
242 pVBoxSCSI->cbBufLeft--;
243 if (pVBoxSCSI->cbBufLeft == 0)
244 {
245 rc = VERR_MORE_DATA;
246 ASMAtomicXchgBool(&pVBoxSCSI->fBusy, true);
247 }
248 }
249 /* else: Ignore extra data, request pending or something. */
250 break;
251 }
252
253 case 2:
254 {
255 pVBoxSCSI->regIdentify = uVal;
256 break;
257 }
258
259 case 3:
260 {
261 /* Reset */
262 vboxscsiReset(pVBoxSCSI, true /*fEverything*/);
263 break;
264 }
265
266 default:
267 AssertMsgFailed(("Invalid register to write to %u\n", iRegister));
268 }
269
270 return rc;
271}
272
273/**
274 * Sets up a SCSI request which the owning SCSI device can process.
275 *
276 * @returns VBox status code.
277 * @param pVBoxSCSI Pointer to the SCSI state.
278 * @param pScsiRequest Pointer to a scsi request to setup.
279 * @param puTargetDevice Where to store the target device ID.
280 */
281int vboxscsiSetupRequest(PVBOXSCSI pVBoxSCSI, PPDMSCSIREQUEST pScsiRequest, uint32_t *puTargetDevice)
282{
283 int rc = VINF_SUCCESS;
284
285 LogFlowFunc(("pVBoxSCSI=%#p pScsiRequest=%#p puTargetDevice=%#p\n", pVBoxSCSI, pScsiRequest, puTargetDevice));
286
287 AssertMsg(pVBoxSCSI->enmState == VBOXSCSISTATE_COMMAND_READY, ("Invalid state %u\n", pVBoxSCSI->enmState));
288
289 /* Clear any errors from a previous request. */
290 pVBoxSCSI->rcCompletion = 0;
291
292 if (pVBoxSCSI->uTxDir == VBOXSCSI_TXDIR_FROM_DEVICE)
293 {
294 if (pVBoxSCSI->pbBuf)
295 RTMemFree(pVBoxSCSI->pbBuf);
296
297 pVBoxSCSI->pbBuf = (uint8_t *)RTMemAllocZ(pVBoxSCSI->cbBuf);
298 if (!pVBoxSCSI->pbBuf)
299 return VERR_NO_MEMORY;
300 }
301
302 /* Allocate scatter gather element. */
303 pScsiRequest->paScatterGatherHead = (PRTSGSEG)RTMemAllocZ(sizeof(RTSGSEG) * 1); /* Only one element. */
304 if (!pScsiRequest->paScatterGatherHead)
305 {
306 RTMemFree(pVBoxSCSI->pbBuf);
307 pVBoxSCSI->pbBuf = NULL;
308 return VERR_NO_MEMORY;
309 }
310
311 /* Allocate sense buffer. */
312 pScsiRequest->cbSenseBuffer = 18;
313 pScsiRequest->pbSenseBuffer = (uint8_t *)RTMemAllocZ(pScsiRequest->cbSenseBuffer);
314
315 pScsiRequest->cbCDB = pVBoxSCSI->cbCDB;
316 pScsiRequest->pbCDB = pVBoxSCSI->abCDB;
317 pScsiRequest->uLogicalUnit = 0;
318 pScsiRequest->cbScatterGather = pVBoxSCSI->cbBuf;
319 pScsiRequest->cScatterGatherEntries = 1;
320
321 pScsiRequest->paScatterGatherHead[0].cbSeg = pVBoxSCSI->cbBuf;
322 pScsiRequest->paScatterGatherHead[0].pvSeg = pVBoxSCSI->pbBuf;
323
324 *puTargetDevice = pVBoxSCSI->uTargetDevice;
325
326 return rc;
327}
328
329/**
330 * Notifies the device that a request finished and the incoming data
331 * is ready at the incoming data port.
332 */
333int vboxscsiRequestFinished(PVBOXSCSI pVBoxSCSI, PPDMSCSIREQUEST pScsiRequest, int rcCompletion)
334{
335 LogFlowFunc(("pVBoxSCSI=%#p pScsiRequest=%#p\n", pVBoxSCSI, pScsiRequest));
336 RTMemFree(pScsiRequest->paScatterGatherHead);
337 RTMemFree(pScsiRequest->pbSenseBuffer);
338
339 if (pVBoxSCSI->uTxDir == VBOXSCSI_TXDIR_TO_DEVICE)
340 vboxscsiReset(pVBoxSCSI, false /*fEverything*/);
341
342 pVBoxSCSI->rcCompletion = rcCompletion;
343
344 ASMAtomicXchgBool(&pVBoxSCSI->fBusy, false);
345
346 return VINF_SUCCESS;
347}
348
349int vboxscsiReadString(PPDMDEVINS pDevIns, PVBOXSCSI pVBoxSCSI, uint8_t iRegister,
350 uint8_t *pbDst, uint32_t *pcTransfers, unsigned cb)
351{
352 LogFlowFunc(("pDevIns=%#p pVBoxSCSI=%#p iRegister=%d cTransfers=%u cb=%u\n",
353 pDevIns, pVBoxSCSI, iRegister, *pcTransfers, cb));
354
355 /*
356 * Check preconditions, fall back to non-string I/O handler.
357 */
358 Assert(*pcTransfers > 0);
359
360 /* Read string only valid for data in register. */
361 AssertMsgReturn(iRegister == 1, ("Hey! Only register 1 can be read from with string!\n"), VINF_SUCCESS);
362
363 /* Accesses without a valid buffer will be ignored. */
364 AssertReturn(pVBoxSCSI->pbBuf, VINF_SUCCESS);
365
366 /* Check state. */
367 AssertReturn(pVBoxSCSI->enmState == VBOXSCSISTATE_COMMAND_READY, VINF_SUCCESS);
368 Assert(!pVBoxSCSI->fBusy);
369
370 /*
371 * Also ignore attempts to read more data than is available.
372 */
373 int rc = VINF_SUCCESS;
374 uint32_t cbTransfer = *pcTransfers * cb;
375 if (pVBoxSCSI->cbBufLeft > 0)
376 {
377 Assert(cbTransfer <= pVBoxSCSI->cbBuf);
378 if (cbTransfer > pVBoxSCSI->cbBuf)
379 {
380 memset(pbDst + pVBoxSCSI->cbBuf, 0xff, cbTransfer - pVBoxSCSI->cbBuf);
381 cbTransfer = pVBoxSCSI->cbBuf; /* Ignore excess data (not supposed to happen). */
382 }
383
384 /* Copy the data and adance the buffer position. */
385 memcpy(pbDst, pVBoxSCSI->pbBuf + pVBoxSCSI->iBuf, cbTransfer);
386
387 /* Advance current buffer position. */
388 pVBoxSCSI->iBuf += cbTransfer;
389 pVBoxSCSI->cbBufLeft -= cbTransfer;
390
391 /* When the guest reads the last byte from the data in buffer, clear
392 everything and reset command buffer. */
393 if (pVBoxSCSI->cbBufLeft == 0)
394 vboxscsiReset(pVBoxSCSI, false /*fEverything*/);
395 }
396 else
397 {
398 AssertFailed();
399 memset(pbDst, 0, cbTransfer);
400 }
401 *pcTransfers = 0;
402
403 return rc;
404}
405
406int vboxscsiWriteString(PPDMDEVINS pDevIns, PVBOXSCSI pVBoxSCSI, uint8_t iRegister,
407 uint8_t const *pbSrc, uint32_t *pcTransfers, unsigned cb)
408{
409 /*
410 * Check preconditions, fall back to non-string I/O handler.
411 */
412 Assert(*pcTransfers > 0);
413 /* Write string only valid for data in/out register. */
414 AssertMsgReturn(iRegister == 1, ("Hey! Only register 1 can be written to with string!\n"), VINF_SUCCESS);
415
416 /* Accesses without a valid buffer will be ignored. */
417 AssertReturn(pVBoxSCSI->pbBuf, VINF_SUCCESS);
418
419 /* State machine assumptions. */
420 AssertReturn(pVBoxSCSI->enmState == VBOXSCSISTATE_COMMAND_READY, VINF_SUCCESS);
421 AssertReturn(pVBoxSCSI->uTxDir == VBOXSCSI_TXDIR_TO_DEVICE, VINF_SUCCESS);
422
423 /*
424 * Ignore excess data (not supposed to happen).
425 */
426 int rc = VINF_SUCCESS;
427 if (pVBoxSCSI->cbBufLeft > 0)
428 {
429 uint32_t cbTransfer = RT_MIN(*pcTransfers * cb, pVBoxSCSI->cbBufLeft);
430
431 /* Copy the data and adance the buffer position. */
432 memcpy(pVBoxSCSI->pbBuf + pVBoxSCSI->iBuf, pbSrc, cbTransfer);
433 pVBoxSCSI->iBuf += cbTransfer;
434 pVBoxSCSI->cbBufLeft -= cbTransfer;
435
436 /* If we've reached the end, tell the caller to submit the command. */
437 if (pVBoxSCSI->cbBufLeft == 0)
438 {
439 ASMAtomicXchgBool(&pVBoxSCSI->fBusy, true);
440 rc = VERR_MORE_DATA;
441 }
442 }
443 else
444 AssertFailed();
445 *pcTransfers = 0;
446
447 return rc;
448}
449
450void vboxscsiSetRequestRedo(PVBOXSCSI pVBoxSCSI, PPDMSCSIREQUEST pScsiRequest)
451{
452 AssertMsg(pVBoxSCSI->fBusy, ("No request to redo\n"));
453
454 RTMemFree(pScsiRequest->paScatterGatherHead);
455 RTMemFree(pScsiRequest->pbSenseBuffer);
456
457 if (pVBoxSCSI->uTxDir == VBOXSCSI_TXDIR_FROM_DEVICE)
458 {
459 AssertPtr(pVBoxSCSI->pbBuf);
460 }
461}
462
463DECLHIDDEN(int) vboxscsiR3LoadExec(PVBOXSCSI pVBoxSCSI, PSSMHANDLE pSSM)
464{
465 SSMR3GetU8 (pSSM, &pVBoxSCSI->regIdentify);
466 SSMR3GetU8 (pSSM, &pVBoxSCSI->uTargetDevice);
467 SSMR3GetU8 (pSSM, &pVBoxSCSI->uTxDir);
468 SSMR3GetU8 (pSSM, &pVBoxSCSI->cbCDB);
469 SSMR3GetMem (pSSM, &pVBoxSCSI->abCDB[0], sizeof(pVBoxSCSI->abCDB));
470 SSMR3GetU8 (pSSM, &pVBoxSCSI->iCDB);
471 SSMR3GetU32 (pSSM, &pVBoxSCSI->cbBufLeft);
472 SSMR3GetU32 (pSSM, &pVBoxSCSI->iBuf);
473 SSMR3GetBool(pSSM, (bool *)&pVBoxSCSI->fBusy);
474 SSMR3GetU8 (pSSM, (uint8_t *)&pVBoxSCSI->enmState);
475
476 /*
477 * Old saved states only save the size of the buffer left to read/write.
478 * To avoid changing the saved state version we can just calculate the original
479 * buffer size from the offset and remaining size.
480 */
481 pVBoxSCSI->cbBuf = pVBoxSCSI->cbBufLeft + pVBoxSCSI->iBuf;
482
483 if (pVBoxSCSI->cbBuf)
484 {
485 pVBoxSCSI->pbBuf = (uint8_t *)RTMemAllocZ(pVBoxSCSI->cbBuf);
486 if (!pVBoxSCSI->pbBuf)
487 return VERR_NO_MEMORY;
488
489 SSMR3GetMem(pSSM, pVBoxSCSI->pbBuf, pVBoxSCSI->cbBuf);
490 }
491
492 return VINF_SUCCESS;
493}
494
495DECLHIDDEN(int) vboxscsiR3SaveExec(PVBOXSCSI pVBoxSCSI, PSSMHANDLE pSSM)
496{
497 SSMR3PutU8 (pSSM, pVBoxSCSI->regIdentify);
498 SSMR3PutU8 (pSSM, pVBoxSCSI->uTargetDevice);
499 SSMR3PutU8 (pSSM, pVBoxSCSI->uTxDir);
500 SSMR3PutU8 (pSSM, pVBoxSCSI->cbCDB);
501 SSMR3PutMem (pSSM, pVBoxSCSI->abCDB, sizeof(pVBoxSCSI->abCDB));
502 SSMR3PutU8 (pSSM, pVBoxSCSI->iCDB);
503 SSMR3PutU32 (pSSM, pVBoxSCSI->cbBufLeft);
504 SSMR3PutU32 (pSSM, pVBoxSCSI->iBuf);
505 SSMR3PutBool (pSSM, pVBoxSCSI->fBusy);
506 SSMR3PutU8 (pSSM, pVBoxSCSI->enmState);
507
508 if (pVBoxSCSI->cbBuf)
509 SSMR3PutMem(pSSM, pVBoxSCSI->pbBuf, pVBoxSCSI->cbBuf);
510
511 return VINF_SUCCESS;
512}
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette