/* $Id: DrvHostBase-freebsd.cpp $ */ /** @file * DrvHostBase - Host base drive access driver, FreeBSD specifics. */ /* * Copyright (C) 2006-2023 Oracle and/or its affiliates. * * This file is part of VirtualBox base platform packages, as * available from https://www.virtualbox.org. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, in version 3 of the * License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * SPDX-License-Identifier: GPL-3.0-only */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #define LOG_GROUP LOG_GROUP_DRV_HOST_BASE #include #include #include #include #include #include #include #include #include #include #include #include #include /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ /** * Host backend specific data. */ typedef struct DRVHOSTBASEOS { /** The filehandle of the device. */ RTFILE hFileDevice; /** The block size. Set when querying the media size. */ uint32_t cbBlock; /** SCSI bus number. */ path_id_t ScsiBus; /** target ID of the passthrough device. */ target_id_t ScsiTargetID; /** LUN of the passthrough device. */ lun_id_t ScsiLunID; } DRVHOSTBASEOS; /** Pointer to the host backend specific data. */ typedef DRVHOSTBASEOS *PDRVHOSBASEOS; AssertCompile(sizeof(DRVHOSTBASEOS) <= 64); #define DRVHOSTBASE_OS_INT_DECLARED #include "DrvHostBase.h" /********************************************************************************************************************************* * Defined Constants And Macros * *********************************************************************************************************************************/ /** Maximum buffer size supported by the CAM subsystem. */ #define FBSD_SCSI_MAX_BUFFER_SIZE (64 * _1K) DECLHIDDEN(int) drvHostBaseScsiCmdOs(PDRVHOSTBASE pThis, const uint8_t *pbCmd, size_t cbCmd, PDMMEDIATXDIR enmTxDir, void *pvBuf, uint32_t *pcbBuf, uint8_t *pbSense, size_t cbSense, uint32_t cTimeoutMillies) { /* * Minimal input validation. */ Assert(enmTxDir == PDMMEDIATXDIR_NONE || enmTxDir == PDMMEDIATXDIR_FROM_DEVICE || enmTxDir == PDMMEDIATXDIR_TO_DEVICE); Assert(!pvBuf || pcbBuf); Assert(pvBuf || enmTxDir == PDMMEDIATXDIR_NONE); Assert(pbSense || !cbSense); AssertPtr(pbCmd); Assert(cbCmd <= 16 && cbCmd >= 1); const uint32_t cbBuf = pcbBuf ? *pcbBuf : 0; if (pcbBuf) *pcbBuf = 0; int rc = VINF_SUCCESS; int rcBSD = 0; union ccb DeviceCCB; union ccb *pDeviceCCB = &DeviceCCB; u_int32_t fFlags; memset(pDeviceCCB, 0, sizeof(DeviceCCB)); pDeviceCCB->ccb_h.path_id = pThis->Os.ScsiBus; pDeviceCCB->ccb_h.target_id = pThis->Os.ScsiTargetID; pDeviceCCB->ccb_h.target_lun = pThis->Os.ScsiLunID; /* The SCSI INQUIRY command can't be passed through directly. */ if (pbCmd[0] == SCSI_INQUIRY) { pDeviceCCB->ccb_h.func_code = XPT_GDEV_TYPE; rcBSD = ioctl(RTFileToNative(pThis->Os.hFileDevice), CAMIOCOMMAND, pDeviceCCB); if (!rcBSD) { uint32_t cbCopy = cbBuf < sizeof(struct scsi_inquiry_data) ? cbBuf : sizeof(struct scsi_inquiry_data);; memcpy(pvBuf, &pDeviceCCB->cgd.inq_data, cbCopy); memset(pbSense, 0, cbSense); if (pcbBuf) *pcbBuf = cbCopy; } else rc = RTErrConvertFromErrno(errno); } else { /* Copy the CDB. */ memcpy(&pDeviceCCB->csio.cdb_io.cdb_bytes, pbCmd, cbCmd); /* Set direction. */ if (enmTxDir == PDMMEDIATXDIR_NONE) fFlags = CAM_DIR_NONE; else if (enmTxDir == PDMMEDIATXDIR_FROM_DEVICE) fFlags = CAM_DIR_IN; else fFlags = CAM_DIR_OUT; fFlags |= CAM_DEV_QFRZDIS; cam_fill_csio(&pDeviceCCB->csio, 1, NULL, fFlags, MSG_SIMPLE_Q_TAG, (u_int8_t *)pvBuf, cbBuf, cbSense, cbCmd, cTimeoutMillies ? cTimeoutMillies : 30000/* timeout */); /* Send command */ rcBSD = ioctl(RTFileToNative(pThis->Os.hFileDevice), CAMIOCOMMAND, pDeviceCCB); if (!rcBSD) { switch (pDeviceCCB->ccb_h.status & CAM_STATUS_MASK) { case CAM_REQ_CMP: rc = VINF_SUCCESS; break; case CAM_SEL_TIMEOUT: rc = VERR_DEV_IO_ERROR; break; case CAM_CMD_TIMEOUT: rc = VERR_TIMEOUT; break; default: rc = VERR_DEV_IO_ERROR; } if (pcbBuf) *pcbBuf = cbBuf - pDeviceCCB->csio.resid; if (pbSense) memcpy(pbSense, &pDeviceCCB->csio.sense_data, cbSense - pDeviceCCB->csio.sense_resid); } else rc = RTErrConvertFromErrno(errno); } return rc; } DECLHIDDEN(size_t) drvHostBaseScsiCmdGetBufLimitOs(PDRVHOSTBASE pThis) { RT_NOREF(pThis); return FBSD_SCSI_MAX_BUFFER_SIZE; } DECLHIDDEN(int) drvHostBaseGetMediaSizeOs(PDRVHOSTBASE pThis, uint64_t *pcb) { /* * Try a READ_CAPACITY command... */ struct { uint32_t cBlocks; uint32_t cbBlock; } Buf = {0, 0}; uint32_t cbBuf = sizeof(Buf); uint8_t abCmd[16] = { SCSI_READ_CAPACITY, 0, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0 }; int rc = drvHostBaseScsiCmdOs(pThis, abCmd, 6, PDMMEDIATXDIR_FROM_DEVICE, &Buf, &cbBuf, NULL, 0, 0); if (RT_SUCCESS(rc)) { Assert(cbBuf == sizeof(Buf)); Buf.cBlocks = RT_BE2H_U32(Buf.cBlocks); Buf.cbBlock = RT_BE2H_U32(Buf.cbBlock); //if (Buf.cbBlock > 2048) /* everyone else is doing this... check if it needed/right.*/ // Buf.cbBlock = 2048; pThis->Os.cbBlock = Buf.cbBlock; *pcb = (uint64_t)Buf.cBlocks * Buf.cbBlock; } return rc; } DECLHIDDEN(int) drvHostBaseReadOs(PDRVHOSTBASE pThis, uint64_t off, void *pvBuf, size_t cbRead) { int rc = VINF_SUCCESS; if (pThis->Os.cbBlock) { /* * Issue a READ(12) request. */ do { const uint32_t LBA = off / pThis->Os.cbBlock; AssertReturn(!(off % pThis->Os.cbBlock), VERR_INVALID_PARAMETER); uint32_t cbRead32 = cbRead > FBSD_SCSI_MAX_BUFFER_SIZE ? FBSD_SCSI_MAX_BUFFER_SIZE : (uint32_t)cbRead; const uint32_t cBlocks = cbRead32 / pThis->Os.cbBlock; AssertReturn(!(cbRead % pThis->Os.cbBlock), VERR_INVALID_PARAMETER); uint8_t abCmd[16] = { SCSI_READ_12, 0, RT_BYTE4(LBA), RT_BYTE3(LBA), RT_BYTE2(LBA), RT_BYTE1(LBA), RT_BYTE4(cBlocks), RT_BYTE3(cBlocks), RT_BYTE2(cBlocks), RT_BYTE1(cBlocks), 0, 0, 0, 0, 0 }; rc = drvHostBaseScsiCmdOs(pThis, abCmd, 12, PDMMEDIATXDIR_FROM_DEVICE, pvBuf, &cbRead32, NULL, 0, 0); off += cbRead32; cbRead -= cbRead32; pvBuf = (uint8_t *)pvBuf + cbRead32; } while ((cbRead > 0) && RT_SUCCESS(rc)); } else rc = VERR_MEDIA_NOT_PRESENT; return rc; } DECLHIDDEN(int) drvHostBaseWriteOs(PDRVHOSTBASE pThis, uint64_t off, const void *pvBuf, size_t cbWrite) { RT_NOREF4(pThis, off, pvBuf, cbWrite); return VERR_WRITE_PROTECT; } DECLHIDDEN(int) drvHostBaseFlushOs(PDRVHOSTBASE pThis) { RT_NOREF1(pThis); return VINF_SUCCESS; } DECLHIDDEN(int) drvHostBaseDoLockOs(PDRVHOSTBASE pThis, bool fLock) { uint8_t abCmd[16] = { SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL, 0, 0, 0, fLock, 0, 0,0,0,0,0,0,0,0,0,0 }; return drvHostBaseScsiCmdOs(pThis, abCmd, 6, PDMMEDIATXDIR_NONE, NULL, NULL, NULL, 0, 0); } DECLHIDDEN(int) drvHostBaseEjectOs(PDRVHOSTBASE pThis) { uint8_t abCmd[16] = { SCSI_START_STOP_UNIT, 0, 0, 0, 2 /*eject+stop*/, 0, 0,0,0,0,0,0,0,0,0,0 }; return drvHostBaseScsiCmdOs(pThis, abCmd, 6, PDMMEDIATXDIR_NONE, NULL, NULL, NULL, 0, 0); } DECLHIDDEN(int) drvHostBaseQueryMediaStatusOs(PDRVHOSTBASE pThis, bool *pfMediaChanged, bool *pfMediaPresent) { /* * Issue a TEST UNIT READY request. */ *pfMediaChanged = false; *pfMediaPresent = false; uint8_t abCmd[16] = { SCSI_TEST_UNIT_READY, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }; uint8_t abSense[32]; int rc = drvHostBaseScsiCmdOs(pThis, abCmd, 6, PDMMEDIATXDIR_NONE, NULL, NULL, abSense, sizeof(abSense), 0); if (RT_SUCCESS(rc)) *pfMediaPresent = true; else if ( rc == VERR_UNRESOLVED_ERROR && abSense[2] == 6 /* unit attention */ && ( (abSense[12] == 0x29 && abSense[13] < 5 /* reset */) || (abSense[12] == 0x2a && abSense[13] == 0 /* parameters changed */) //??? || (abSense[12] == 0x3f && abSense[13] == 0 /* target operating conditions have changed */) //??? || (abSense[12] == 0x3f && abSense[13] == 2 /* changed operating definition */) //??? || (abSense[12] == 0x3f && abSense[13] == 3 /* inquiry parameters changed */) || (abSense[12] == 0x3f && abSense[13] == 5 /* device identifier changed */) ) ) { *pfMediaPresent = false; *pfMediaChanged = true; rc = VINF_SUCCESS; /** @todo check this media change stuff on Darwin. */ } return rc; } DECLHIDDEN(void) drvHostBaseInitOs(PDRVHOSTBASE pThis) { pThis->Os.hFileDevice = NIL_RTFILE; } DECLHIDDEN(int) drvHostBaseOpenOs(PDRVHOSTBASE pThis, bool fReadOnly) { RT_NOREF(fReadOnly); RTFILE hFileDevice; int rc = RTFileOpen(&hFileDevice, pThis->pszDevice, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE); if (RT_FAILURE(rc)) return rc; /* * The current device handle can't passthrough SCSI commands. * We have to get he passthrough device path and open this. */ union ccb DeviceCCB; memset(&DeviceCCB, 0, sizeof(DeviceCCB)); DeviceCCB.ccb_h.func_code = XPT_GDEVLIST; int rcBSD = ioctl(RTFileToNative(hFileDevice), CAMGETPASSTHRU, &DeviceCCB); if (!rcBSD) { char *pszPassthroughDevice = NULL; rc = RTStrAPrintf(&pszPassthroughDevice, "/dev/%s%u", DeviceCCB.cgdl.periph_name, DeviceCCB.cgdl.unit_number); if (rc >= 0) { RTFILE hPassthroughDevice; rc = RTFileOpen(&hPassthroughDevice, pszPassthroughDevice, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE); RTStrFree(pszPassthroughDevice); if (RT_SUCCESS(rc)) { /* Get needed device parameters. */ /* * The device path, target id and lun id. Those are * needed for the SCSI passthrough ioctl. */ memset(&DeviceCCB, 0, sizeof(DeviceCCB)); DeviceCCB.ccb_h.func_code = XPT_GDEVLIST; rcBSD = ioctl(RTFileToNative(hPassthroughDevice), CAMGETPASSTHRU, &DeviceCCB); if (!rcBSD) { if (DeviceCCB.cgdl.status != CAM_GDEVLIST_ERROR) { pThis->Os.ScsiBus = DeviceCCB.ccb_h.path_id; pThis->Os.ScsiTargetID = DeviceCCB.ccb_h.target_id; pThis->Os.ScsiLunID = DeviceCCB.ccb_h.target_lun; pThis->Os.hFileDevice = hPassthroughDevice; } else { /* The passthrough device wasn't found. */ rc = VERR_NOT_FOUND; } } else rc = RTErrConvertFromErrno(errno); if (RT_FAILURE(rc)) RTFileClose(hPassthroughDevice); } } else rc = VERR_NO_STR_MEMORY; } else rc = RTErrConvertFromErrno(errno); RTFileClose(hFileDevice); return rc; } DECLHIDDEN(int) drvHostBaseMediaRefreshOs(PDRVHOSTBASE pThis) { RT_NOREF(pThis); return VINF_SUCCESS; } DECLHIDDEN(bool) drvHostBaseIsMediaPollingRequiredOs(PDRVHOSTBASE pThis) { if (pThis->enmType == PDMMEDIATYPE_CDROM || pThis->enmType == PDMMEDIATYPE_DVD) return true; AssertMsgFailed(("FreeBSD supports only CD/DVD host drive access\n")); return false; } DECLHIDDEN(void) drvHostBaseDestructOs(PDRVHOSTBASE pThis) { /* * Unlock the drive if we've locked it or we're in passthru mode. */ if ( pThis->fLocked && pThis->Os.hFileDevice != NIL_RTFILE && pThis->pfnDoLock) { int rc = pThis->pfnDoLock(pThis, false); if (RT_SUCCESS(rc)) pThis->fLocked = false; } if (pThis->Os.hFileDevice != NIL_RTFILE) { int rc = RTFileClose(pThis->Os.hFileDevice); AssertRC(rc); pThis->Os.hFileDevice = NIL_RTFILE; } }