VirtualBox

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

Last change on this file since 56992 was 56429, checked in by vboxsync, 10 years ago

vboxscsiReset: Reset cbBufLeft too.

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