VirtualBox

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

Last change on this file since 81773 was 81773, checked in by vboxsync, 5 years ago

DevLsiLogicSCSI: Converted MMIO and I/O port handlers. bugref:9218

  • 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 81773 2019-11-11 18:05:17Z vboxsync $ */
2/** @file
3 * VBox storage devices - Simple SCSI interface for BIOS access.
4 */
5
6/*
7 * Copyright (C) 2006-2019 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 * @retval VINF_SUCCESS
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 * @retval VINF_SUCCESS on success.
148 * @retval VERR_MORE_DATA if a command is ready to be sent to the SCSI driver.
149 *
150 * @param pVBoxSCSI Pointer to the SCSI state.
151 * @param iRegister Index of the register to write to.
152 * @param uVal Value to write.
153 */
154int vboxscsiWriteRegister(PVBOXSCSI pVBoxSCSI, uint8_t iRegister, uint8_t uVal)
155{
156 int rc = VINF_SUCCESS;
157
158 switch (iRegister)
159 {
160 case 0:
161 {
162 if (pVBoxSCSI->enmState == VBOXSCSISTATE_NO_COMMAND)
163 {
164 pVBoxSCSI->enmState = VBOXSCSISTATE_READ_TXDIR;
165 pVBoxSCSI->uTargetDevice = uVal;
166 }
167 else if (pVBoxSCSI->enmState == VBOXSCSISTATE_READ_TXDIR)
168 {
169 if (uVal != VBOXSCSI_TXDIR_FROM_DEVICE && uVal != VBOXSCSI_TXDIR_TO_DEVICE)
170 vboxscsiReset(pVBoxSCSI, true /*fEverything*/);
171 else
172 {
173 pVBoxSCSI->enmState = VBOXSCSISTATE_READ_CDB_SIZE_BUFHI;
174 pVBoxSCSI->uTxDir = uVal;
175 }
176 }
177 else if (pVBoxSCSI->enmState == VBOXSCSISTATE_READ_CDB_SIZE_BUFHI)
178 {
179 uint8_t cbCDB = uVal & 0x0F;
180
181 if (cbCDB == 0)
182 cbCDB = 16;
183 if (cbCDB > VBOXSCSI_CDB_SIZE_MAX)
184 vboxscsiReset(pVBoxSCSI, true /*fEverything*/);
185 else
186 {
187 pVBoxSCSI->enmState = VBOXSCSISTATE_READ_BUFFER_SIZE_LSB;
188 pVBoxSCSI->cbCDB = cbCDB;
189 pVBoxSCSI->cbBuf = (uVal & 0xF0) << 12; /* Bits 16-19 of buffer size. */
190 }
191 }
192 else if (pVBoxSCSI->enmState == VBOXSCSISTATE_READ_BUFFER_SIZE_LSB)
193 {
194 pVBoxSCSI->enmState = VBOXSCSISTATE_READ_BUFFER_SIZE_MID;
195 pVBoxSCSI->cbBuf |= uVal; /* Bits 0-7 of buffer size. */
196 }
197 else if (pVBoxSCSI->enmState == VBOXSCSISTATE_READ_BUFFER_SIZE_MID)
198 {
199 pVBoxSCSI->enmState = VBOXSCSISTATE_READ_COMMAND;
200 pVBoxSCSI->cbBuf |= (((uint16_t)uVal) << 8); /* Bits 8-15 of buffer size. */
201 }
202 else if (pVBoxSCSI->enmState == VBOXSCSISTATE_READ_COMMAND)
203 {
204 pVBoxSCSI->abCDB[pVBoxSCSI->iCDB] = uVal;
205 pVBoxSCSI->iCDB++;
206
207 /* Check if we have all necessary command data. */
208 if (pVBoxSCSI->iCDB == pVBoxSCSI->cbCDB)
209 {
210 Log(("%s: Command ready for processing\n", __FUNCTION__));
211 pVBoxSCSI->enmState = VBOXSCSISTATE_COMMAND_READY;
212 pVBoxSCSI->cbBufLeft = pVBoxSCSI->cbBuf;
213 if (pVBoxSCSI->uTxDir == VBOXSCSI_TXDIR_TO_DEVICE)
214 {
215 /* This is a write allocate buffer. */
216 pVBoxSCSI->pbBuf = (uint8_t *)RTMemAllocZ(pVBoxSCSI->cbBuf);
217 if (!pVBoxSCSI->pbBuf)
218 return VERR_NO_MEMORY;
219 }
220 else
221 {
222 /* This is a read from the device. */
223 ASMAtomicXchgBool(&pVBoxSCSI->fBusy, true);
224 rc = VERR_MORE_DATA; /** @todo Better return value to indicate ready command? */
225 }
226 }
227 }
228 else
229 AssertMsgFailed(("Invalid state %d\n", pVBoxSCSI->enmState));
230 break;
231 }
232
233 case 1:
234 {
235 if ( pVBoxSCSI->enmState != VBOXSCSISTATE_COMMAND_READY
236 || pVBoxSCSI->uTxDir != VBOXSCSI_TXDIR_TO_DEVICE)
237 {
238 /* Reset the state */
239 vboxscsiReset(pVBoxSCSI, true /*fEverything*/);
240 }
241 else if (pVBoxSCSI->cbBufLeft > 0)
242 {
243 pVBoxSCSI->pbBuf[pVBoxSCSI->iBuf++] = uVal;
244 pVBoxSCSI->cbBufLeft--;
245 if (pVBoxSCSI->cbBufLeft == 0)
246 {
247 rc = VERR_MORE_DATA;
248 ASMAtomicXchgBool(&pVBoxSCSI->fBusy, true);
249 }
250 }
251 /* else: Ignore extra data, request pending or something. */
252 break;
253 }
254
255 case 2:
256 {
257 pVBoxSCSI->regIdentify = uVal;
258 break;
259 }
260
261 case 3:
262 {
263 /* Reset */
264 vboxscsiReset(pVBoxSCSI, true /*fEverything*/);
265 break;
266 }
267
268 default:
269 AssertMsgFailed(("Invalid register to write to %u\n", iRegister));
270 }
271
272 return rc;
273}
274
275/**
276 * Sets up a SCSI request which the owning SCSI device can process.
277 *
278 * @returns VBox status code.
279 * @param pVBoxSCSI Pointer to the SCSI state.
280 * @param puLun Where to store the LUN on success.
281 * @param ppbCdb Where to store the pointer to the CDB on success.
282 * @param pcbCdb Where to store the size of the CDB on success.
283 * @param pcbBuf Where to store th size of the data buffer on success.
284 * @param puTargetDevice Where to store the target device ID.
285 */
286int vboxscsiSetupRequest(PVBOXSCSI pVBoxSCSI, uint32_t *puLun, uint8_t **ppbCdb,
287 size_t *pcbCdb, size_t *pcbBuf, uint32_t *puTargetDevice)
288{
289 int rc = VINF_SUCCESS;
290
291 LogFlowFunc(("pVBoxSCSI=%#p puTargetDevice=%#p\n", pVBoxSCSI, puTargetDevice));
292
293 AssertMsg(pVBoxSCSI->enmState == VBOXSCSISTATE_COMMAND_READY, ("Invalid state %u\n", pVBoxSCSI->enmState));
294
295 /* Clear any errors from a previous request. */
296 pVBoxSCSI->rcCompletion = 0;
297
298 if (pVBoxSCSI->uTxDir == VBOXSCSI_TXDIR_FROM_DEVICE)
299 {
300 if (pVBoxSCSI->pbBuf)
301 RTMemFree(pVBoxSCSI->pbBuf);
302
303 pVBoxSCSI->pbBuf = (uint8_t *)RTMemAllocZ(pVBoxSCSI->cbBuf);
304 if (!pVBoxSCSI->pbBuf)
305 return VERR_NO_MEMORY;
306 }
307
308 *puLun = 0;
309 *ppbCdb = &pVBoxSCSI->abCDB[0];
310 *pcbCdb = pVBoxSCSI->cbCDB;
311 *pcbBuf = pVBoxSCSI->cbBuf;
312 *puTargetDevice = pVBoxSCSI->uTargetDevice;
313
314 return rc;
315}
316
317/**
318 * Notifies the device that a request finished and the incoming data
319 * is ready at the incoming data port.
320 */
321int vboxscsiRequestFinished(PVBOXSCSI pVBoxSCSI, int rcCompletion)
322{
323 LogFlowFunc(("pVBoxSCSI=%#p\n", pVBoxSCSI));
324
325 if (pVBoxSCSI->uTxDir == VBOXSCSI_TXDIR_TO_DEVICE)
326 vboxscsiReset(pVBoxSCSI, false /*fEverything*/);
327
328 pVBoxSCSI->rcCompletion = rcCompletion;
329
330 ASMAtomicXchgBool(&pVBoxSCSI->fBusy, false);
331
332 return VINF_SUCCESS;
333}
334
335size_t vboxscsiCopyToBuf(PVBOXSCSI pVBoxSCSI, PRTSGBUF pSgBuf, size_t cbSkip, size_t cbCopy)
336{
337 AssertPtrReturn(pVBoxSCSI->pbBuf, 0);
338 AssertReturn(cbSkip + cbCopy <= pVBoxSCSI->cbBuf, 0);
339
340 void *pvBuf = pVBoxSCSI->pbBuf + cbSkip;
341 return RTSgBufCopyToBuf(pSgBuf, pvBuf, cbCopy);
342}
343
344size_t vboxscsiCopyFromBuf(PVBOXSCSI pVBoxSCSI, PRTSGBUF pSgBuf, size_t cbSkip, size_t cbCopy)
345{
346 AssertPtrReturn(pVBoxSCSI->pbBuf, 0);
347 AssertReturn(cbSkip + cbCopy <= pVBoxSCSI->cbBuf, 0);
348
349 void *pvBuf = pVBoxSCSI->pbBuf + cbSkip;
350 return RTSgBufCopyFromBuf(pSgBuf, pvBuf, cbCopy);
351}
352
353/**
354 * @retval VINF_SUCCESS
355 */
356int vboxscsiReadString(PPDMDEVINS pDevIns, PVBOXSCSI pVBoxSCSI, uint8_t iRegister,
357 uint8_t *pbDst, uint32_t *pcTransfers, unsigned cb)
358{
359 RT_NOREF(pDevIns);
360 LogFlowFunc(("pDevIns=%#p pVBoxSCSI=%#p iRegister=%d cTransfers=%u cb=%u\n",
361 pDevIns, pVBoxSCSI, iRegister, *pcTransfers, cb));
362
363 /*
364 * Check preconditions, fall back to non-string I/O handler.
365 */
366 Assert(*pcTransfers > 0);
367
368 /* Read string only valid for data in register. */
369 AssertMsgReturn(iRegister == 1, ("Hey! Only register 1 can be read from with string!\n"), VINF_SUCCESS);
370
371 /* Accesses without a valid buffer will be ignored. */
372 AssertReturn(pVBoxSCSI->pbBuf, VINF_SUCCESS);
373
374 /* Check state. */
375 AssertReturn(pVBoxSCSI->enmState == VBOXSCSISTATE_COMMAND_READY, VINF_SUCCESS);
376 Assert(!pVBoxSCSI->fBusy);
377
378 /*
379 * Also ignore attempts to read more data than is available.
380 */
381 uint32_t cbTransfer = *pcTransfers * cb;
382 if (pVBoxSCSI->cbBufLeft > 0)
383 {
384 Assert(cbTransfer <= pVBoxSCSI->cbBuf);
385 if (cbTransfer > pVBoxSCSI->cbBuf)
386 {
387 memset(pbDst + pVBoxSCSI->cbBuf, 0xff, cbTransfer - pVBoxSCSI->cbBuf);
388 cbTransfer = pVBoxSCSI->cbBuf; /* Ignore excess data (not supposed to happen). */
389 }
390
391 /* Copy the data and adance the buffer position. */
392 memcpy(pbDst, pVBoxSCSI->pbBuf + pVBoxSCSI->iBuf, cbTransfer);
393
394 /* Advance current buffer position. */
395 pVBoxSCSI->iBuf += cbTransfer;
396 pVBoxSCSI->cbBufLeft -= cbTransfer;
397
398 /* When the guest reads the last byte from the data in buffer, clear
399 everything and reset command buffer. */
400 if (pVBoxSCSI->cbBufLeft == 0)
401 vboxscsiReset(pVBoxSCSI, false /*fEverything*/);
402 }
403 else
404 {
405 AssertFailed();
406 memset(pbDst, 0, cbTransfer);
407 }
408 *pcTransfers = 0;
409
410 return VINF_SUCCESS;
411}
412
413/**
414 * @retval VINF_SUCCESS
415 * @retval VERR_MORE_DATA
416 */
417int vboxscsiWriteString(PPDMDEVINS pDevIns, PVBOXSCSI pVBoxSCSI, uint8_t iRegister,
418 uint8_t const *pbSrc, uint32_t *pcTransfers, unsigned cb)
419{
420 RT_NOREF(pDevIns);
421
422 /*
423 * Check preconditions, fall back to non-string I/O handler.
424 */
425 Assert(*pcTransfers > 0);
426 /* Write string only valid for data in/out register. */
427 AssertMsgReturn(iRegister == 1, ("Hey! Only register 1 can be written to with string!\n"), VINF_SUCCESS);
428
429 /* Accesses without a valid buffer will be ignored. */
430 AssertReturn(pVBoxSCSI->pbBuf, VINF_SUCCESS);
431
432 /* State machine assumptions. */
433 AssertReturn(pVBoxSCSI->enmState == VBOXSCSISTATE_COMMAND_READY, VINF_SUCCESS);
434 AssertReturn(pVBoxSCSI->uTxDir == VBOXSCSI_TXDIR_TO_DEVICE, VINF_SUCCESS);
435
436 /*
437 * Ignore excess data (not supposed to happen).
438 */
439 int rc = VINF_SUCCESS;
440 if (pVBoxSCSI->cbBufLeft > 0)
441 {
442 uint32_t cbTransfer = RT_MIN(*pcTransfers * cb, pVBoxSCSI->cbBufLeft);
443
444 /* Copy the data and adance the buffer position. */
445 memcpy(pVBoxSCSI->pbBuf + pVBoxSCSI->iBuf, pbSrc, cbTransfer);
446 pVBoxSCSI->iBuf += cbTransfer;
447 pVBoxSCSI->cbBufLeft -= cbTransfer;
448
449 /* If we've reached the end, tell the caller to submit the command. */
450 if (pVBoxSCSI->cbBufLeft == 0)
451 {
452 ASMAtomicXchgBool(&pVBoxSCSI->fBusy, true);
453 rc = VERR_MORE_DATA;
454 }
455 }
456 else
457 AssertFailed();
458 *pcTransfers = 0;
459
460 return rc;
461}
462
463void vboxscsiSetRequestRedo(PVBOXSCSI pVBoxSCSI)
464{
465 AssertMsg(pVBoxSCSI->fBusy, ("No request to redo\n"));
466
467 if (pVBoxSCSI->uTxDir == VBOXSCSI_TXDIR_FROM_DEVICE)
468 {
469 AssertPtr(pVBoxSCSI->pbBuf);
470 }
471}
472
473DECLHIDDEN(int) vboxscsiR3LoadExec(PCPDMDEVHLPR3 pHlp, PVBOXSCSI pVBoxSCSI, PSSMHANDLE pSSM)
474{
475 SSMR3GetU8 (pSSM, &pVBoxSCSI->regIdentify);
476 SSMR3GetU8 (pSSM, &pVBoxSCSI->uTargetDevice);
477 SSMR3GetU8 (pSSM, &pVBoxSCSI->uTxDir);
478 SSMR3GetU8 (pSSM, &pVBoxSCSI->cbCDB);
479
480 /*
481 * The CDB buffer was increased with r104155 in trunk (backported to 5.0
482 * in r104311) without bumping the SSM state versions which leaves us
483 * with broken saved state restoring for older VirtualBox releases
484 * (up to 5.0.10).
485 */
486 if ( ( SSMR3HandleRevision(pSSM) < 104311
487 && SSMR3HandleVersion(pSSM) < VBOX_FULL_VERSION_MAKE(5, 0, 12))
488 || ( SSMR3HandleRevision(pSSM) < 104155
489 && SSMR3HandleVersion(pSSM) >= VBOX_FULL_VERSION_MAKE(5, 0, 51)))
490 {
491 memset(&pVBoxSCSI->abCDB[0], 0, sizeof(pVBoxSCSI->abCDB));
492 SSMR3GetMem (pSSM, &pVBoxSCSI->abCDB[0], 12);
493 }
494 else
495 SSMR3GetMem (pSSM, &pVBoxSCSI->abCDB[0], sizeof(pVBoxSCSI->abCDB));
496
497 SSMR3GetU8 (pSSM, &pVBoxSCSI->iCDB);
498 SSMR3GetU32 (pSSM, &pVBoxSCSI->cbBufLeft);
499 SSMR3GetU32 (pSSM, &pVBoxSCSI->iBuf);
500 SSMR3GetBoolV(pSSM, &pVBoxSCSI->fBusy);
501 PDMDEVHLP_SSM_GET_ENUM8_RET(pHlp, pSSM, pVBoxSCSI->enmState, VBOXSCSISTATE);
502
503 /*
504 * Old saved states only save the size of the buffer left to read/write.
505 * To avoid changing the saved state version we can just calculate the original
506 * buffer size from the offset and remaining size.
507 */
508 pVBoxSCSI->cbBuf = pVBoxSCSI->cbBufLeft + pVBoxSCSI->iBuf;
509
510 if (pVBoxSCSI->cbBuf)
511 {
512 pVBoxSCSI->pbBuf = (uint8_t *)RTMemAllocZ(pVBoxSCSI->cbBuf);
513 if (!pVBoxSCSI->pbBuf)
514 return VERR_NO_MEMORY;
515
516 SSMR3GetMem(pSSM, pVBoxSCSI->pbBuf, pVBoxSCSI->cbBuf);
517 }
518
519 return VINF_SUCCESS;
520}
521
522DECLHIDDEN(int) vboxscsiR3SaveExec(PCPDMDEVHLPR3 pHlp, PVBOXSCSI pVBoxSCSI, PSSMHANDLE pSSM)
523{
524 RT_NOREF(pHlp);
525 SSMR3PutU8 (pSSM, pVBoxSCSI->regIdentify);
526 SSMR3PutU8 (pSSM, pVBoxSCSI->uTargetDevice);
527 SSMR3PutU8 (pSSM, pVBoxSCSI->uTxDir);
528 SSMR3PutU8 (pSSM, pVBoxSCSI->cbCDB);
529 SSMR3PutMem (pSSM, pVBoxSCSI->abCDB, sizeof(pVBoxSCSI->abCDB));
530 SSMR3PutU8 (pSSM, pVBoxSCSI->iCDB);
531 SSMR3PutU32 (pSSM, pVBoxSCSI->cbBufLeft);
532 SSMR3PutU32 (pSSM, pVBoxSCSI->iBuf);
533 SSMR3PutBool (pSSM, pVBoxSCSI->fBusy);
534 SSMR3PutU8 (pSSM, pVBoxSCSI->enmState);
535
536 if (pVBoxSCSI->cbBuf)
537 SSMR3PutMem(pSSM, pVBoxSCSI->pbBuf, pVBoxSCSI->cbBuf);
538
539 return VINF_SUCCESS;
540}
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