VirtualBox

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

Last change on this file since 58466 was 57358, checked in by vboxsync, 9 years ago

*: scm cleanup run.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 16.7 KB
Line 
1/* $Id: VBoxSCSI.cpp 57358 2015-08-14 15:16:38Z 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/*********************************************************************************************************************************
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 if (cbCDB > VBOXSCSI_CDB_SIZE_MAX)
179 vboxscsiReset(pVBoxSCSI, true /*fEverything*/);
180 else
181 {
182 pVBoxSCSI->enmState = VBOXSCSISTATE_READ_BUFFER_SIZE_LSB;
183 pVBoxSCSI->cbCDB = cbCDB;
184 pVBoxSCSI->cbBuf = (uVal & 0xF0) << 12; /* Bits 16-19 of buffer size. */
185 }
186 }
187 else if (pVBoxSCSI->enmState == VBOXSCSISTATE_READ_BUFFER_SIZE_LSB)
188 {
189 pVBoxSCSI->enmState = VBOXSCSISTATE_READ_BUFFER_SIZE_MID;
190 pVBoxSCSI->cbBuf |= uVal; /* Bits 0-7 of buffer size. */
191 }
192 else if (pVBoxSCSI->enmState == VBOXSCSISTATE_READ_BUFFER_SIZE_MID)
193 {
194 pVBoxSCSI->enmState = VBOXSCSISTATE_READ_COMMAND;
195 pVBoxSCSI->cbBuf |= (((uint16_t)uVal) << 8); /* Bits 8-15 of buffer size. */
196 }
197 else if (pVBoxSCSI->enmState == VBOXSCSISTATE_READ_COMMAND)
198 {
199 pVBoxSCSI->abCDB[pVBoxSCSI->iCDB] = uVal;
200 pVBoxSCSI->iCDB++;
201
202 /* Check if we have all necessary command data. */
203 if (pVBoxSCSI->iCDB == pVBoxSCSI->cbCDB)
204 {
205 Log(("%s: Command ready for processing\n", __FUNCTION__));
206 pVBoxSCSI->enmState = VBOXSCSISTATE_COMMAND_READY;
207 pVBoxSCSI->cbBufLeft = pVBoxSCSI->cbBuf;
208 if (pVBoxSCSI->uTxDir == VBOXSCSI_TXDIR_TO_DEVICE)
209 {
210 /* This is a write allocate buffer. */
211 pVBoxSCSI->pbBuf = (uint8_t *)RTMemAllocZ(pVBoxSCSI->cbBuf);
212 if (!pVBoxSCSI->pbBuf)
213 return VERR_NO_MEMORY;
214 }
215 else
216 {
217 /* This is a read from the device. */
218 ASMAtomicXchgBool(&pVBoxSCSI->fBusy, true);
219 rc = VERR_MORE_DATA; /** @todo Better return value to indicate ready command? */
220 }
221 }
222 }
223 else
224 AssertMsgFailed(("Invalid state %d\n", pVBoxSCSI->enmState));
225 break;
226 }
227
228 case 1:
229 {
230 if ( pVBoxSCSI->enmState != VBOXSCSISTATE_COMMAND_READY
231 || pVBoxSCSI->uTxDir != VBOXSCSI_TXDIR_TO_DEVICE)
232 {
233 /* Reset the state */
234 vboxscsiReset(pVBoxSCSI, true /*fEverything*/);
235 }
236 else if (pVBoxSCSI->cbBufLeft > 0)
237 {
238 pVBoxSCSI->pbBuf[pVBoxSCSI->iBuf++] = uVal;
239 pVBoxSCSI->cbBufLeft--;
240 if (pVBoxSCSI->cbBufLeft == 0)
241 {
242 rc = VERR_MORE_DATA;
243 ASMAtomicXchgBool(&pVBoxSCSI->fBusy, true);
244 }
245 }
246 /* else: Ignore extra data, request pending or something. */
247 break;
248 }
249
250 case 2:
251 {
252 pVBoxSCSI->regIdentify = uVal;
253 break;
254 }
255
256 case 3:
257 {
258 /* Reset */
259 vboxscsiReset(pVBoxSCSI, true /*fEverything*/);
260 break;
261 }
262
263 default:
264 AssertMsgFailed(("Invalid register to write to %u\n", iRegister));
265 }
266
267 return rc;
268}
269
270/**
271 * Sets up a SCSI request which the owning SCSI device can process.
272 *
273 * @returns VBox status code.
274 * @param pVBoxSCSI Pointer to the SCSI state.
275 * @param pScsiRequest Pointer to a scsi request to setup.
276 * @param puTargetDevice Where to store the target device ID.
277 */
278int vboxscsiSetupRequest(PVBOXSCSI pVBoxSCSI, PPDMSCSIREQUEST pScsiRequest, uint32_t *puTargetDevice)
279{
280 int rc = VINF_SUCCESS;
281
282 LogFlowFunc(("pVBoxSCSI=%#p pScsiRequest=%#p puTargetDevice=%#p\n", pVBoxSCSI, pScsiRequest, puTargetDevice));
283
284 AssertMsg(pVBoxSCSI->enmState == VBOXSCSISTATE_COMMAND_READY, ("Invalid state %u\n", pVBoxSCSI->enmState));
285
286 /* Clear any errors from a previous request. */
287 pVBoxSCSI->rcCompletion = 0;
288
289 if (pVBoxSCSI->uTxDir == VBOXSCSI_TXDIR_FROM_DEVICE)
290 {
291 if (pVBoxSCSI->pbBuf)
292 RTMemFree(pVBoxSCSI->pbBuf);
293
294 pVBoxSCSI->pbBuf = (uint8_t *)RTMemAllocZ(pVBoxSCSI->cbBuf);
295 if (!pVBoxSCSI->pbBuf)
296 return VERR_NO_MEMORY;
297 }
298
299 /* Allocate scatter gather element. */
300 pScsiRequest->paScatterGatherHead = (PRTSGSEG)RTMemAllocZ(sizeof(RTSGSEG) * 1); /* Only one element. */
301 if (!pScsiRequest->paScatterGatherHead)
302 {
303 RTMemFree(pVBoxSCSI->pbBuf);
304 pVBoxSCSI->pbBuf = NULL;
305 return VERR_NO_MEMORY;
306 }
307
308 /* Allocate sense buffer. */
309 pScsiRequest->cbSenseBuffer = 18;
310 pScsiRequest->pbSenseBuffer = (uint8_t *)RTMemAllocZ(pScsiRequest->cbSenseBuffer);
311
312 pScsiRequest->cbCDB = pVBoxSCSI->cbCDB;
313 pScsiRequest->pbCDB = pVBoxSCSI->abCDB;
314 pScsiRequest->uLogicalUnit = 0;
315 pScsiRequest->cbScatterGather = pVBoxSCSI->cbBuf;
316 pScsiRequest->cScatterGatherEntries = 1;
317
318 pScsiRequest->paScatterGatherHead[0].cbSeg = pVBoxSCSI->cbBuf;
319 pScsiRequest->paScatterGatherHead[0].pvSeg = pVBoxSCSI->pbBuf;
320
321 *puTargetDevice = pVBoxSCSI->uTargetDevice;
322
323 return rc;
324}
325
326/**
327 * Notifies the device that a request finished and the incoming data
328 * is ready at the incoming data port.
329 */
330int vboxscsiRequestFinished(PVBOXSCSI pVBoxSCSI, PPDMSCSIREQUEST pScsiRequest, int rcCompletion)
331{
332 LogFlowFunc(("pVBoxSCSI=%#p pScsiRequest=%#p\n", pVBoxSCSI, pScsiRequest));
333 RTMemFree(pScsiRequest->paScatterGatherHead);
334 RTMemFree(pScsiRequest->pbSenseBuffer);
335
336 if (pVBoxSCSI->uTxDir == VBOXSCSI_TXDIR_TO_DEVICE)
337 vboxscsiReset(pVBoxSCSI, false /*fEverything*/);
338
339 pVBoxSCSI->rcCompletion = rcCompletion;
340
341 ASMAtomicXchgBool(&pVBoxSCSI->fBusy, false);
342
343 return VINF_SUCCESS;
344}
345
346int vboxscsiReadString(PPDMDEVINS pDevIns, PVBOXSCSI pVBoxSCSI, uint8_t iRegister,
347 uint8_t *pbDst, uint32_t *pcTransfers, unsigned cb)
348{
349 LogFlowFunc(("pDevIns=%#p pVBoxSCSI=%#p iRegister=%d cTransfers=%u cb=%u\n",
350 pDevIns, pVBoxSCSI, iRegister, *pcTransfers, cb));
351
352 /*
353 * Check preconditions, fall back to non-string I/O handler.
354 */
355 Assert(*pcTransfers > 0);
356
357 /* Read string only valid for data in register. */
358 AssertMsgReturn(iRegister == 1, ("Hey! Only register 1 can be read from with string!\n"), VINF_SUCCESS);
359
360 /* Accesses without a valid buffer will be ignored. */
361 AssertReturn(pVBoxSCSI->pbBuf, VINF_SUCCESS);
362
363 /* Check state. */
364 AssertReturn(pVBoxSCSI->enmState == VBOXSCSISTATE_COMMAND_READY, VINF_SUCCESS);
365 Assert(!pVBoxSCSI->fBusy);
366
367 /*
368 * Also ignore attempts to read more data than is available.
369 */
370 int rc = VINF_SUCCESS;
371 uint32_t cbTransfer = *pcTransfers * cb;
372 if (pVBoxSCSI->cbBufLeft > 0)
373 {
374 Assert(cbTransfer <= pVBoxSCSI->cbBuf);
375 if (cbTransfer > pVBoxSCSI->cbBuf)
376 {
377 memset(pbDst + pVBoxSCSI->cbBuf, 0xff, cbTransfer - pVBoxSCSI->cbBuf);
378 cbTransfer = pVBoxSCSI->cbBuf; /* Ignore excess data (not supposed to happen). */
379 }
380
381 /* Copy the data and adance the buffer position. */
382 memcpy(pbDst, pVBoxSCSI->pbBuf + pVBoxSCSI->iBuf, cbTransfer);
383
384 /* Advance current buffer position. */
385 pVBoxSCSI->iBuf += cbTransfer;
386 pVBoxSCSI->cbBufLeft -= cbTransfer;
387
388 /* When the guest reads the last byte from the data in buffer, clear
389 everything and reset command buffer. */
390 if (pVBoxSCSI->cbBufLeft == 0)
391 vboxscsiReset(pVBoxSCSI, false /*fEverything*/);
392 }
393 else
394 {
395 AssertFailed();
396 memset(pbDst, 0, cbTransfer);
397 }
398 *pcTransfers = 0;
399
400 return rc;
401}
402
403int vboxscsiWriteString(PPDMDEVINS pDevIns, PVBOXSCSI pVBoxSCSI, uint8_t iRegister,
404 uint8_t const *pbSrc, uint32_t *pcTransfers, unsigned cb)
405{
406 /*
407 * Check preconditions, fall back to non-string I/O handler.
408 */
409 Assert(*pcTransfers > 0);
410 /* Write string only valid for data in/out register. */
411 AssertMsgReturn(iRegister == 1, ("Hey! Only register 1 can be written to with string!\n"), VINF_SUCCESS);
412
413 /* Accesses without a valid buffer will be ignored. */
414 AssertReturn(pVBoxSCSI->pbBuf, VINF_SUCCESS);
415
416 /* State machine assumptions. */
417 AssertReturn(pVBoxSCSI->enmState == VBOXSCSISTATE_COMMAND_READY, VINF_SUCCESS);
418 AssertReturn(pVBoxSCSI->uTxDir == VBOXSCSI_TXDIR_TO_DEVICE, VINF_SUCCESS);
419
420 /*
421 * Ignore excess data (not supposed to happen).
422 */
423 int rc = VINF_SUCCESS;
424 if (pVBoxSCSI->cbBufLeft > 0)
425 {
426 uint32_t cbTransfer = RT_MIN(*pcTransfers * cb, pVBoxSCSI->cbBufLeft);
427
428 /* Copy the data and adance the buffer position. */
429 memcpy(pVBoxSCSI->pbBuf + pVBoxSCSI->iBuf, pbSrc, cbTransfer);
430 pVBoxSCSI->iBuf += cbTransfer;
431 pVBoxSCSI->cbBufLeft -= cbTransfer;
432
433 /* If we've reached the end, tell the caller to submit the command. */
434 if (pVBoxSCSI->cbBufLeft == 0)
435 {
436 ASMAtomicXchgBool(&pVBoxSCSI->fBusy, true);
437 rc = VERR_MORE_DATA;
438 }
439 }
440 else
441 AssertFailed();
442 *pcTransfers = 0;
443
444 return rc;
445}
446
447void vboxscsiSetRequestRedo(PVBOXSCSI pVBoxSCSI, PPDMSCSIREQUEST pScsiRequest)
448{
449 AssertMsg(pVBoxSCSI->fBusy, ("No request to redo\n"));
450
451 RTMemFree(pScsiRequest->paScatterGatherHead);
452 RTMemFree(pScsiRequest->pbSenseBuffer);
453
454 if (pVBoxSCSI->uTxDir == VBOXSCSI_TXDIR_FROM_DEVICE)
455 {
456 AssertPtr(pVBoxSCSI->pbBuf);
457 }
458}
459
460DECLHIDDEN(int) vboxscsiR3LoadExec(PVBOXSCSI pVBoxSCSI, PSSMHANDLE pSSM)
461{
462 SSMR3GetU8 (pSSM, &pVBoxSCSI->regIdentify);
463 SSMR3GetU8 (pSSM, &pVBoxSCSI->uTargetDevice);
464 SSMR3GetU8 (pSSM, &pVBoxSCSI->uTxDir);
465 SSMR3GetU8 (pSSM, &pVBoxSCSI->cbCDB);
466 SSMR3GetMem (pSSM, &pVBoxSCSI->abCDB[0], sizeof(pVBoxSCSI->abCDB));
467 SSMR3GetU8 (pSSM, &pVBoxSCSI->iCDB);
468 SSMR3GetU32 (pSSM, &pVBoxSCSI->cbBufLeft);
469 SSMR3GetU32 (pSSM, &pVBoxSCSI->iBuf);
470 SSMR3GetBool(pSSM, (bool *)&pVBoxSCSI->fBusy);
471 SSMR3GetU8 (pSSM, (uint8_t *)&pVBoxSCSI->enmState);
472
473 /*
474 * Old saved states only save the size of the buffer left to read/write.
475 * To avoid changing the saved state version we can just calculate the original
476 * buffer size from the offset and remaining size.
477 */
478 pVBoxSCSI->cbBuf = pVBoxSCSI->cbBufLeft + pVBoxSCSI->iBuf;
479
480 if (pVBoxSCSI->cbBuf)
481 {
482 pVBoxSCSI->pbBuf = (uint8_t *)RTMemAllocZ(pVBoxSCSI->cbBuf);
483 if (!pVBoxSCSI->pbBuf)
484 return VERR_NO_MEMORY;
485
486 SSMR3GetMem(pSSM, pVBoxSCSI->pbBuf, pVBoxSCSI->cbBuf);
487 }
488
489 return VINF_SUCCESS;
490}
491
492DECLHIDDEN(int) vboxscsiR3SaveExec(PVBOXSCSI pVBoxSCSI, PSSMHANDLE pSSM)
493{
494 SSMR3PutU8 (pSSM, pVBoxSCSI->regIdentify);
495 SSMR3PutU8 (pSSM, pVBoxSCSI->uTargetDevice);
496 SSMR3PutU8 (pSSM, pVBoxSCSI->uTxDir);
497 SSMR3PutU8 (pSSM, pVBoxSCSI->cbCDB);
498 SSMR3PutMem (pSSM, pVBoxSCSI->abCDB, sizeof(pVBoxSCSI->abCDB));
499 SSMR3PutU8 (pSSM, pVBoxSCSI->iCDB);
500 SSMR3PutU32 (pSSM, pVBoxSCSI->cbBufLeft);
501 SSMR3PutU32 (pSSM, pVBoxSCSI->iBuf);
502 SSMR3PutBool (pSSM, pVBoxSCSI->fBusy);
503 SSMR3PutU8 (pSSM, pVBoxSCSI->enmState);
504
505 if (pVBoxSCSI->cbBuf)
506 SSMR3PutMem(pSSM, pVBoxSCSI->pbBuf, pVBoxSCSI->cbBuf);
507
508 return VINF_SUCCESS;
509}
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