VirtualBox

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

Last change on this file since 64141 was 63657, checked in by vboxsync, 8 years ago

VBoxSCSI: fix loading a saved state from before r104155 (or r104311 for 5.0.x)

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