ATAController.cpp revision ad8fb8c920c36650d5ead020ef8e05b681dd4375
ee4d840f54fd2dcea8a73b1b86d5ec0db370b05dvboxsync/* $Id$ */
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync/** @file
cbaf00194b28ee57e4aeee473f66f91f1be4e022vboxsync * DevATA, DevAHCI - Shared ATA/ATAPI controller code (disk and cdrom).
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync *
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync * @todo Actually share this code?
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync */
8137be2315957032783c582a2e5c2523ea96f9bcvboxsync
8137be2315957032783c582a2e5c2523ea96f9bcvboxsync/*
8137be2315957032783c582a2e5c2523ea96f9bcvboxsync * Copyright (C) 2006-2009 Sun Microsystems, Inc.
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync *
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync * This file is part of VirtualBox Open Source Edition (OSE), as
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync * available from http://www.virtualbox.org. This file is free software;
e64031e20c39650a7bc902a3e1aba613b9415deevboxsync * you can redistribute it and/or modify it under the terms of the GNU
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync * General Public License (GPL) as published by the Free Software
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync * Foundation, in version 2 as it comes in the "COPYING" file of the
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
a16eb14ad7a4b5ef91ddc22d3e8e92d930f736fcvboxsync *
a16eb14ad7a4b5ef91ddc22d3e8e92d930f736fcvboxsync * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
a16eb14ad7a4b5ef91ddc22d3e8e92d930f736fcvboxsync * Clara, CA 95054 USA or visit http://www.sun.com if you need
a16eb14ad7a4b5ef91ddc22d3e8e92d930f736fcvboxsync * additional information or have any questions.
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync */
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync/*******************************************************************************
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync* Header Files *
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync*******************************************************************************/
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync#define LOG_GROUP LOG_GROUP_DEV_IDE
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync#include <VBox/pdmdev.h>
43747b1f0bc8302a238fb35e55857a5e9aa1933dvboxsync#include <iprt/assert.h>
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsync#include <iprt/string.h>
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync#ifdef IN_RING3
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync# include <iprt/uuid.h>
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsync# include <iprt/semaphore.h>
ee4d840f54fd2dcea8a73b1b86d5ec0db370b05dvboxsync# include <iprt/thread.h>
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync# include <iprt/time.h>
f5e53763b0a581b0299e98028c6c52192eb06785vboxsync# include <iprt/alloc.h>
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync#endif /* IN_RING3 */
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync#include <iprt/critsect.h>
ee4d840f54fd2dcea8a73b1b86d5ec0db370b05dvboxsync#include <iprt/asm.h>
ee4d840f54fd2dcea8a73b1b86d5ec0db370b05dvboxsync#include <VBox/stam.h>
ee4d840f54fd2dcea8a73b1b86d5ec0db370b05dvboxsync#include <VBox/mm.h>
0f1e77149ab5ab40fa2bd74a5330e087416b3c7bvboxsync#include <VBox/pgm.h>
0f1e77149ab5ab40fa2bd74a5330e087416b3c7bvboxsync
ee4d840f54fd2dcea8a73b1b86d5ec0db370b05dvboxsync#include <VBox/scsi.h>
ee4d840f54fd2dcea8a73b1b86d5ec0db370b05dvboxsync
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync#include "ATAController.h"
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync#include "PIIX3ATABmDma.h"
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync#include "ide.h"
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync/**
ee4d840f54fd2dcea8a73b1b86d5ec0db370b05dvboxsync * The SSM saved state version.
0f1e77149ab5ab40fa2bd74a5330e087416b3c7bvboxsync */
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync#define ATA_CTL_SAVED_STATE_VERSION 3
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync#define ATA_CTL_SAVED_STATE_VERSION_WITHOUT_FULL_SENSE 1
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync#define ATA_CTL_SAVED_STATE_VERSION_WITHOUT_EVENT_STATUS 2
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync#ifndef VBOX_DEVICE_STRUCT_TESTCASE
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsyncDECLINLINE(void) ataSetStatusValue(AHCIATADevState *s, uint8_t stat)
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync{
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync PAHCIATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync
0f1e77149ab5ab40fa2bd74a5330e087416b3c7bvboxsync /* Freeze status register contents while processing RESET. */
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync if (!pCtl->fReset)
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync {
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync s->uATARegStatus = stat;
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync Log2(("%s: LUN#%d status %#04x\n", __FUNCTION__, s->iLUN, s->uATARegStatus));
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsync }
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsync}
ad27e1d5e48ca41245120c331cc88b50464813cevboxsync
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsync
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsyncDECLINLINE(void) ataSetStatus(AHCIATADevState *s, uint8_t stat)
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsync{
8137be2315957032783c582a2e5c2523ea96f9bcvboxsync PAHCIATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
8137be2315957032783c582a2e5c2523ea96f9bcvboxsync
8137be2315957032783c582a2e5c2523ea96f9bcvboxsync /* Freeze status register contents while processing RESET. */
fe96bc0e43d9c137304462ef8c2d79cbff22446fvboxsync if (!pCtl->fReset)
fe96bc0e43d9c137304462ef8c2d79cbff22446fvboxsync {
fe96bc0e43d9c137304462ef8c2d79cbff22446fvboxsync s->uATARegStatus |= stat;
fe96bc0e43d9c137304462ef8c2d79cbff22446fvboxsync Log2(("%s: LUN#%d status %#04x\n", __FUNCTION__, s->iLUN, s->uATARegStatus));
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync }
fa033b734cf3b131680f290326ccbbd23c42946bvboxsync}
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsyncDECLINLINE(void) ataUnsetStatus(AHCIATADevState *s, uint8_t stat)
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync{
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync PAHCIATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync /* Freeze status register contents while processing RESET. */
ee4d840f54fd2dcea8a73b1b86d5ec0db370b05dvboxsync if (!pCtl->fReset)
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync {
ee4d840f54fd2dcea8a73b1b86d5ec0db370b05dvboxsync s->uATARegStatus &= ~stat;
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync Log2(("%s: LUN#%d status %#04x\n", __FUNCTION__, s->iLUN, s->uATARegStatus));
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync }
548ca31b6b47c36bacce49bed3339cb8075b9681vboxsync}
ee4d840f54fd2dcea8a73b1b86d5ec0db370b05dvboxsync
a39ea3668b7019c23a68936259545f9b71bce1aavboxsync#ifdef IN_RING3
a39ea3668b7019c23a68936259545f9b71bce1aavboxsync
ee4d840f54fd2dcea8a73b1b86d5ec0db370b05dvboxsynctypedef void (*PBeginTransferFunc)(AHCIATADevState *);
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsynctypedef bool (*PSourceSinkFunc)(AHCIATADevState *);
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsyncstatic void ataReadWriteSectorsBT(AHCIATADevState *);
0f1e77149ab5ab40fa2bd74a5330e087416b3c7bvboxsyncstatic void ataPacketBT(AHCIATADevState *);
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsyncstatic void atapiCmdBT(AHCIATADevState *);
0f1e77149ab5ab40fa2bd74a5330e087416b3c7bvboxsyncstatic void atapiPassthroughCmdBT(AHCIATADevState *);
0f1e77149ab5ab40fa2bd74a5330e087416b3c7bvboxsync
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsyncstatic bool ataIdentifySS(AHCIATADevState *);
548ca31b6b47c36bacce49bed3339cb8075b9681vboxsyncstatic bool ataFlushSS(AHCIATADevState *);
f379f813372b948dc6603b556f0ade7f838a5a65vboxsyncstatic bool ataReadSectorsSS(AHCIATADevState *);
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsyncstatic bool ataWriteSectorsSS(AHCIATADevState *);
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsyncstatic bool ataExecuteDeviceDiagnosticSS(AHCIATADevState *);
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsyncstatic bool ataPacketSS(AHCIATADevState *);
f379f813372b948dc6603b556f0ade7f838a5a65vboxsyncstatic bool atapiGetConfigurationSS(AHCIATADevState *);
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsyncstatic bool atapiGetEventStatusNotificationSS(AHCIATADevState *);
8137be2315957032783c582a2e5c2523ea96f9bcvboxsyncstatic bool atapiIdentifySS(AHCIATADevState *);
8137be2315957032783c582a2e5c2523ea96f9bcvboxsyncstatic bool atapiInquirySS(AHCIATADevState *);
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsyncstatic bool atapiMechanismStatusSS(AHCIATADevState *);
8137be2315957032783c582a2e5c2523ea96f9bcvboxsyncstatic bool atapiModeSenseErrorRecoverySS(AHCIATADevState *);
8137be2315957032783c582a2e5c2523ea96f9bcvboxsyncstatic bool atapiModeSenseCDStatusSS(AHCIATADevState *);
548ca31b6b47c36bacce49bed3339cb8075b9681vboxsyncstatic bool atapiReadSS(AHCIATADevState *);
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsyncstatic bool atapiReadCapacitySS(AHCIATADevState *);
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsyncstatic bool atapiReadDiscInformationSS(AHCIATADevState *);
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsyncstatic bool atapiReadTOCNormalSS(AHCIATADevState *);
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsyncstatic bool atapiReadTOCMultiSS(AHCIATADevState *);
0f1e77149ab5ab40fa2bd74a5330e087416b3c7bvboxsyncstatic bool atapiReadTOCRawSS(AHCIATADevState *);
0f1e77149ab5ab40fa2bd74a5330e087416b3c7bvboxsyncstatic bool atapiReadTrackInformationSS(AHCIATADevState *);
aa32d4906f2f685992091893d5abdf27a2352a85vboxsyncstatic bool atapiRequestSenseSS(AHCIATADevState *);
548ca31b6b47c36bacce49bed3339cb8075b9681vboxsyncstatic bool atapiPassthroughSS(AHCIATADevState *);
aa32d4906f2f685992091893d5abdf27a2352a85vboxsync
e3f5c51715cbf77ae2d2e9d05bafd00d69b1bec9vboxsync/**
aa32d4906f2f685992091893d5abdf27a2352a85vboxsync * Begin of transfer function indexes for g_apfnBeginTransFuncs.
aa32d4906f2f685992091893d5abdf27a2352a85vboxsync */
aa32d4906f2f685992091893d5abdf27a2352a85vboxsynctypedef enum ATAFNBT
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsync{
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsync ATAFN_BT_NULL = 0,
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync ATAFN_BT_READ_WRITE_SECTORS,
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsync ATAFN_BT_PACKET,
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync ATAFN_BT_ATAPI_CMD,
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsync ATAFN_BT_ATAPI_PASSTHROUGH_CMD,
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsync ATAFN_BT_MAX
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsync} ATAFNBT;
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsync
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsync/**
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsync * Array of end transfer functions, the index is ATAFNET.
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsync * Make sure ATAFNET and this array match!
548ca31b6b47c36bacce49bed3339cb8075b9681vboxsync */
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsyncstatic const PBeginTransferFunc g_apfnBeginTransFuncs[ATAFN_BT_MAX] =
8137be2315957032783c582a2e5c2523ea96f9bcvboxsync{
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync NULL,
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsync ataReadWriteSectorsBT,
8137be2315957032783c582a2e5c2523ea96f9bcvboxsync ataPacketBT,
8137be2315957032783c582a2e5c2523ea96f9bcvboxsync atapiCmdBT,
8137be2315957032783c582a2e5c2523ea96f9bcvboxsync atapiPassthroughCmdBT,
8137be2315957032783c582a2e5c2523ea96f9bcvboxsync};
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsync
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsync/**
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsync * Source/sink function indexes for g_apfnSourceSinkFuncs.
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsync */
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsynctypedef enum ATAFNSS
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync{
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync ATAFN_SS_NULL = 0,
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync ATAFN_SS_IDENTIFY,
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync ATAFN_SS_FLUSH,
8137be2315957032783c582a2e5c2523ea96f9bcvboxsync ATAFN_SS_READ_SECTORS,
8137be2315957032783c582a2e5c2523ea96f9bcvboxsync ATAFN_SS_WRITE_SECTORS,
8137be2315957032783c582a2e5c2523ea96f9bcvboxsync ATAFN_SS_EXECUTE_DEVICE_DIAGNOSTIC,
8137be2315957032783c582a2e5c2523ea96f9bcvboxsync ATAFN_SS_PACKET,
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync ATAFN_SS_ATAPI_GET_CONFIGURATION,
8137be2315957032783c582a2e5c2523ea96f9bcvboxsync ATAFN_SS_ATAPI_GET_EVENT_STATUS_NOTIFICATION,
8137be2315957032783c582a2e5c2523ea96f9bcvboxsync ATAFN_SS_ATAPI_IDENTIFY,
8137be2315957032783c582a2e5c2523ea96f9bcvboxsync ATAFN_SS_ATAPI_INQUIRY,
8137be2315957032783c582a2e5c2523ea96f9bcvboxsync ATAFN_SS_ATAPI_MECHANISM_STATUS,
8137be2315957032783c582a2e5c2523ea96f9bcvboxsync ATAFN_SS_ATAPI_MODE_SENSE_ERROR_RECOVERY,
8137be2315957032783c582a2e5c2523ea96f9bcvboxsync ATAFN_SS_ATAPI_MODE_SENSE_CD_STATUS,
8137be2315957032783c582a2e5c2523ea96f9bcvboxsync ATAFN_SS_ATAPI_READ,
06696af9c885503e8dc3b1fe5836033c833e51a9vboxsync ATAFN_SS_ATAPI_READ_CAPACITY,
06696af9c885503e8dc3b1fe5836033c833e51a9vboxsync ATAFN_SS_ATAPI_READ_DISC_INFORMATION,
06696af9c885503e8dc3b1fe5836033c833e51a9vboxsync ATAFN_SS_ATAPI_READ_TOC_NORMAL,
06696af9c885503e8dc3b1fe5836033c833e51a9vboxsync ATAFN_SS_ATAPI_READ_TOC_MULTI,
8137be2315957032783c582a2e5c2523ea96f9bcvboxsync ATAFN_SS_ATAPI_READ_TOC_RAW,
8137be2315957032783c582a2e5c2523ea96f9bcvboxsync ATAFN_SS_ATAPI_READ_TRACK_INFORMATION,
8137be2315957032783c582a2e5c2523ea96f9bcvboxsync ATAFN_SS_ATAPI_REQUEST_SENSE,
8137be2315957032783c582a2e5c2523ea96f9bcvboxsync ATAFN_SS_ATAPI_PASSTHROUGH,
8137be2315957032783c582a2e5c2523ea96f9bcvboxsync ATAFN_SS_MAX
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync} ATAFNSS;
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync/**
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsync * Array of source/sink functions, the index is ATAFNSS.
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync * Make sure ATAFNSS and this array match!
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync */
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsyncstatic const PSourceSinkFunc g_apfnSourceSinkFuncs[ATAFN_SS_MAX] =
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync{
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync NULL,
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync ataIdentifySS,
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync ataFlushSS,
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync ataReadSectorsSS,
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync ataWriteSectorsSS,
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync ataExecuteDeviceDiagnosticSS,
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync ataPacketSS,
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync atapiGetConfigurationSS,
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync atapiGetEventStatusNotificationSS,
f379f813372b948dc6603b556f0ade7f838a5a65vboxsync atapiIdentifySS,
f379f813372b948dc6603b556f0ade7f838a5a65vboxsync atapiInquirySS,
f379f813372b948dc6603b556f0ade7f838a5a65vboxsync atapiMechanismStatusSS,
f379f813372b948dc6603b556f0ade7f838a5a65vboxsync atapiModeSenseErrorRecoverySS,
f379f813372b948dc6603b556f0ade7f838a5a65vboxsync atapiModeSenseCDStatusSS,
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync atapiReadSS,
548ca31b6b47c36bacce49bed3339cb8075b9681vboxsync atapiReadCapacitySS,
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync atapiReadDiscInformationSS,
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync atapiReadTOCNormalSS,
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync atapiReadTOCMultiSS,
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync atapiReadTOCRawSS,
548ca31b6b47c36bacce49bed3339cb8075b9681vboxsync atapiReadTrackInformationSS,
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync atapiRequestSenseSS,
f379f813372b948dc6603b556f0ade7f838a5a65vboxsync atapiPassthroughSS
f379f813372b948dc6603b556f0ade7f838a5a65vboxsync};
f379f813372b948dc6603b556f0ade7f838a5a65vboxsync
b7a5b3f9f9ecce32ddacf8404c625ce0451bbdc1vboxsync
c6958b923ed12aadcf58ebbdbc80aadebbd9493evboxsyncstatic const AHCIATARequest ataDMARequest = { AHCIATA_AIO_DMA, };
fe813b3594039ba864493438e78ee0e7132bc445vboxsyncstatic const AHCIATARequest ataPIORequest = { AHCIATA_AIO_PIO, };
c6958b923ed12aadcf58ebbdbc80aadebbd9493evboxsyncstatic const AHCIATARequest ataResetARequest = { AHCIATA_AIO_RESET_ASSERTED, };
c6958b923ed12aadcf58ebbdbc80aadebbd9493evboxsyncstatic const AHCIATARequest ataResetCRequest = { AHCIATA_AIO_RESET_CLEARED, };
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsyncstatic void ataAsyncIOClearRequests(PAHCIATACONTROLLER pCtl)
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync{
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync int rc;
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync rc = RTSemMutexRequest(pCtl->AsyncIORequestMutex, RT_INDEFINITE_WAIT);
f379f813372b948dc6603b556f0ade7f838a5a65vboxsync AssertRC(rc);
f379f813372b948dc6603b556f0ade7f838a5a65vboxsync pCtl->AsyncIOReqHead = 0;
b7a5b3f9f9ecce32ddacf8404c625ce0451bbdc1vboxsync pCtl->AsyncIOReqTail = 0;
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync rc = RTSemMutexRelease(pCtl->AsyncIORequestMutex);
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync AssertRC(rc);
f379f813372b948dc6603b556f0ade7f838a5a65vboxsync}
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync
548ca31b6b47c36bacce49bed3339cb8075b9681vboxsync
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsyncstatic void ataAsyncIOPutRequest(PAHCIATACONTROLLER pCtl, const AHCIATARequest *pReq)
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync{
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync int rc;
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync rc = RTSemMutexRequest(pCtl->AsyncIORequestMutex, RT_INDEFINITE_WAIT);
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync AssertRC(rc);
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync Assert((pCtl->AsyncIOReqHead + 1) % RT_ELEMENTS(pCtl->aAsyncIORequests) != pCtl->AsyncIOReqTail);
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync memcpy(&pCtl->aAsyncIORequests[pCtl->AsyncIOReqHead], pReq, sizeof(*pReq));
b1cc88518a7578ee20491f3d97b9792c24c6428dvboxsync pCtl->AsyncIOReqHead++;
fe813b3594039ba864493438e78ee0e7132bc445vboxsync pCtl->AsyncIOReqHead %= RT_ELEMENTS(pCtl->aAsyncIORequests);
b1cc88518a7578ee20491f3d97b9792c24c6428dvboxsync rc = RTSemMutexRelease(pCtl->AsyncIORequestMutex);
b1cc88518a7578ee20491f3d97b9792c24c6428dvboxsync AssertRC(rc);
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync rc = PDMR3CritSectScheduleExitEvent(&pCtl->lock, pCtl->AsyncIOSem);
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync if (RT_FAILURE(rc))
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync {
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync rc = RTSemEventSignal(pCtl->AsyncIOSem);
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync AssertRC(rc);
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync }
362838d79d234a41380be42aae9118850cc3c929vboxsync}
362838d79d234a41380be42aae9118850cc3c929vboxsync
362838d79d234a41380be42aae9118850cc3c929vboxsync
362838d79d234a41380be42aae9118850cc3c929vboxsyncstatic const AHCIATARequest *ataAsyncIOGetCurrentRequest(PAHCIATACONTROLLER pCtl)
362838d79d234a41380be42aae9118850cc3c929vboxsync{
bc36547e8dd3d35e5f756643a267bbe01e2c1d4cvboxsync int rc;
bc36547e8dd3d35e5f756643a267bbe01e2c1d4cvboxsync const AHCIATARequest *pReq;
362838d79d234a41380be42aae9118850cc3c929vboxsync
0f1e77149ab5ab40fa2bd74a5330e087416b3c7bvboxsync rc = RTSemMutexRequest(pCtl->AsyncIORequestMutex, RT_INDEFINITE_WAIT);
362838d79d234a41380be42aae9118850cc3c929vboxsync AssertRC(rc);
362838d79d234a41380be42aae9118850cc3c929vboxsync if (pCtl->AsyncIOReqHead != pCtl->AsyncIOReqTail)
362838d79d234a41380be42aae9118850cc3c929vboxsync pReq = &pCtl->aAsyncIORequests[pCtl->AsyncIOReqTail];
362838d79d234a41380be42aae9118850cc3c929vboxsync else
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync pReq = NULL;
22bdb1ce26b2d5a41d1b071c16f1078e5348bb0dvboxsync rc = RTSemMutexRelease(pCtl->AsyncIORequestMutex);
22bdb1ce26b2d5a41d1b071c16f1078e5348bb0dvboxsync AssertRC(rc);
22bdb1ce26b2d5a41d1b071c16f1078e5348bb0dvboxsync return pReq;
22bdb1ce26b2d5a41d1b071c16f1078e5348bb0dvboxsync}
22bdb1ce26b2d5a41d1b071c16f1078e5348bb0dvboxsync
22bdb1ce26b2d5a41d1b071c16f1078e5348bb0dvboxsync
22bdb1ce26b2d5a41d1b071c16f1078e5348bb0dvboxsync/**
22bdb1ce26b2d5a41d1b071c16f1078e5348bb0dvboxsync * Remove the request with the given type, as it's finished. The request
0f1e77149ab5ab40fa2bd74a5330e087416b3c7bvboxsync * is not removed blindly, as this could mean a RESET request that is not
22bdb1ce26b2d5a41d1b071c16f1078e5348bb0dvboxsync * yet processed (but has cleared the request queue) is lost.
22bdb1ce26b2d5a41d1b071c16f1078e5348bb0dvboxsync *
22bdb1ce26b2d5a41d1b071c16f1078e5348bb0dvboxsync * @param pCtl Controller for which to remove the request.
22bdb1ce26b2d5a41d1b071c16f1078e5348bb0dvboxsync * @param ReqType Type of the request to remove.
22bdb1ce26b2d5a41d1b071c16f1078e5348bb0dvboxsync */
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsyncstatic void ataAsyncIORemoveCurrentRequest(PAHCIATACONTROLLER pCtl, AHCIATAAIO ReqType)
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync{
e74eef731a813e4e06680c587a6759b9974b29c9vboxsync int rc;
e74eef731a813e4e06680c587a6759b9974b29c9vboxsync
e74eef731a813e4e06680c587a6759b9974b29c9vboxsync rc = RTSemMutexRequest(pCtl->AsyncIORequestMutex, RT_INDEFINITE_WAIT);
e74eef731a813e4e06680c587a6759b9974b29c9vboxsync AssertRC(rc);
e74eef731a813e4e06680c587a6759b9974b29c9vboxsync if (pCtl->AsyncIOReqHead != pCtl->AsyncIOReqTail && pCtl->aAsyncIORequests[pCtl->AsyncIOReqTail].ReqType == ReqType)
e74eef731a813e4e06680c587a6759b9974b29c9vboxsync {
e74eef731a813e4e06680c587a6759b9974b29c9vboxsync pCtl->AsyncIOReqTail++;
e74eef731a813e4e06680c587a6759b9974b29c9vboxsync pCtl->AsyncIOReqTail %= RT_ELEMENTS(pCtl->aAsyncIORequests);
e74eef731a813e4e06680c587a6759b9974b29c9vboxsync }
e74eef731a813e4e06680c587a6759b9974b29c9vboxsync rc = RTSemMutexRelease(pCtl->AsyncIORequestMutex);
e74eef731a813e4e06680c587a6759b9974b29c9vboxsync AssertRC(rc);
e74eef731a813e4e06680c587a6759b9974b29c9vboxsync}
e74eef731a813e4e06680c587a6759b9974b29c9vboxsync
e74eef731a813e4e06680c587a6759b9974b29c9vboxsync
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync/**
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync * Dump the request queue for a particular controller. First dump the queue
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync * contents, then the already processed entries, as long as they haven't been
e74eef731a813e4e06680c587a6759b9974b29c9vboxsync * overwritten.
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync *
e74eef731a813e4e06680c587a6759b9974b29c9vboxsync * @param pCtl Controller for which to dump the queue.
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync */
e74eef731a813e4e06680c587a6759b9974b29c9vboxsyncstatic void ataAsyncIODumpRequests(PAHCIATACONTROLLER pCtl)
e74eef731a813e4e06680c587a6759b9974b29c9vboxsync{
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync int rc;
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync uint8_t curr;
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync rc = RTSemMutexRequest(pCtl->AsyncIORequestMutex, RT_INDEFINITE_WAIT);
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync AssertRC(rc);
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync LogRel(("ATA: Ctl: request queue dump (topmost is current):\n"));
cbaf00194b28ee57e4aeee473f66f91f1be4e022vboxsync curr = pCtl->AsyncIOReqTail;
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync do
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync {
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync if (curr == pCtl->AsyncIOReqHead)
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync LogRel(("ATA: Ctl: processed requests (topmost is oldest):\n"));
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync switch (pCtl->aAsyncIORequests[curr].ReqType)
e74eef731a813e4e06680c587a6759b9974b29c9vboxsync {
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync case AHCIATA_AIO_NEW:
e74eef731a813e4e06680c587a6759b9974b29c9vboxsync LogRel(("new transfer request, iIf=%d iBeginTransfer=%d iSourceSink=%d cbTotalTransfer=%d uTxDir=%d\n", pCtl->aAsyncIORequests[curr].u.t.iIf, pCtl->aAsyncIORequests[curr].u.t.iBeginTransfer, pCtl->aAsyncIORequests[curr].u.t.iSourceSink, pCtl->aAsyncIORequests[curr].u.t.cbTotalTransfer, pCtl->aAsyncIORequests[curr].u.t.uTxDir));
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync break;
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync case AHCIATA_AIO_DMA:
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync LogRel(("dma transfer finished\n"));
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync break;
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync case AHCIATA_AIO_PIO:
e74eef731a813e4e06680c587a6759b9974b29c9vboxsync LogRel(("pio transfer finished\n"));
ddab1f70f4034896cebf5e6af5a492ac46218346vboxsync break;
ddab1f70f4034896cebf5e6af5a492ac46218346vboxsync case AHCIATA_AIO_RESET_ASSERTED:
ddab1f70f4034896cebf5e6af5a492ac46218346vboxsync LogRel(("reset asserted request\n"));
ddab1f70f4034896cebf5e6af5a492ac46218346vboxsync break;
ddab1f70f4034896cebf5e6af5a492ac46218346vboxsync case AHCIATA_AIO_RESET_CLEARED:
ddab1f70f4034896cebf5e6af5a492ac46218346vboxsync LogRel(("reset cleared request\n"));
e74eef731a813e4e06680c587a6759b9974b29c9vboxsync break;
e74eef731a813e4e06680c587a6759b9974b29c9vboxsync case AHCIATA_AIO_ABORT:
e74eef731a813e4e06680c587a6759b9974b29c9vboxsync LogRel(("abort request, iIf=%d fResetDrive=%d\n", pCtl->aAsyncIORequests[curr].u.a.iIf, pCtl->aAsyncIORequests[curr].u.a.fResetDrive));
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync break;
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync default:
ad77e3ec3cde24263bc7537575f5cae442bee3b1vboxsync LogRel(("unknown request %d\n", pCtl->aAsyncIORequests[curr].ReqType));
cba6719bd64ec749967bbe931230452664109857vboxsync }
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync curr = (curr + 1) % RT_ELEMENTS(pCtl->aAsyncIORequests);
c28fa006ba669ad8f26ae31d00a338379c04ea1bvboxsync } while (curr != pCtl->AsyncIOReqTail);
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync rc = RTSemMutexRelease(pCtl->AsyncIORequestMutex);
548ca31b6b47c36bacce49bed3339cb8075b9681vboxsync AssertRC(rc);
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync}
e74eef731a813e4e06680c587a6759b9974b29c9vboxsync
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync/**
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync * Checks whether the request queue for a particular controller is empty
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync * or whether a particular controller is idle.
548ca31b6b47c36bacce49bed3339cb8075b9681vboxsync *
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync * @param pCtl Controller for which to check the queue.
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync * @param fStrict If set then the controller is checked to be idle.
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync */
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsyncstatic bool ataAsyncIOIsIdle(PAHCIATACONTROLLER pCtl, bool fStrict)
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync{
0f1e77149ab5ab40fa2bd74a5330e087416b3c7bvboxsync int rc;
0f1e77149ab5ab40fa2bd74a5330e087416b3c7bvboxsync bool fIdle;
0f1e77149ab5ab40fa2bd74a5330e087416b3c7bvboxsync
0f1e77149ab5ab40fa2bd74a5330e087416b3c7bvboxsync rc = RTSemMutexRequest(pCtl->AsyncIORequestMutex, RT_INDEFINITE_WAIT);
0f1e77149ab5ab40fa2bd74a5330e087416b3c7bvboxsync AssertRC(rc);
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync fIdle = pCtl->fRedoIdle;
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync if (!fIdle)
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync fIdle = (pCtl->AsyncIOReqHead == pCtl->AsyncIOReqTail);
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync if (fStrict)
0f1e77149ab5ab40fa2bd74a5330e087416b3c7bvboxsync fIdle &= (pCtl->uAsyncIOState == AHCIATA_AIO_NEW);
548ca31b6b47c36bacce49bed3339cb8075b9681vboxsync rc = RTSemMutexRelease(pCtl->AsyncIORequestMutex);
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync AssertRC(rc);
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync return fIdle;
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync}
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync/**
cba6719bd64ec749967bbe931230452664109857vboxsync * Send a transfer request to the async I/O thread.
b7a5b3f9f9ecce32ddacf8404c625ce0451bbdc1vboxsync *
30adc6dd25ed9fef4d800a6d9f1ab7e765b4c340vboxsync * @param s Pointer to the ATA device state data.
da3503c04ce76e653401396fe2795a9bc2427a1dvboxsync * @param cbTotalTransfer Data transfer size.
548ca31b6b47c36bacce49bed3339cb8075b9681vboxsync * @param uTxDir Data transfer direction.
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync * @param iBeginTransfer Index of BeginTransfer callback.
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync * @param iSourceSink Index of SourceSink callback.
ad77e3ec3cde24263bc7537575f5cae442bee3b1vboxsync * @param fChainedTransfer Whether this is a transfer that is part of the previous command/transfer.
801238b286a80a5dd67ba56a1f26c0bc98a2a1eavboxsync */
801238b286a80a5dd67ba56a1f26c0bc98a2a1eavboxsyncstatic void ataStartTransfer(AHCIATADevState *s, uint32_t cbTotalTransfer, uint8_t uTxDir, ATAFNBT iBeginTransfer, ATAFNSS iSourceSink, bool fChainedTransfer)
801238b286a80a5dd67ba56a1f26c0bc98a2a1eavboxsync{
801238b286a80a5dd67ba56a1f26c0bc98a2a1eavboxsync PAHCIATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync AHCIATARequest Req;
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync
801238b286a80a5dd67ba56a1f26c0bc98a2a1eavboxsync Assert(PDMCritSectIsOwner(&pCtl->lock));
801238b286a80a5dd67ba56a1f26c0bc98a2a1eavboxsync
801238b286a80a5dd67ba56a1f26c0bc98a2a1eavboxsync /* Do not issue new requests while the RESET line is asserted. */
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync if (pCtl->fReset)
548ca31b6b47c36bacce49bed3339cb8075b9681vboxsync {
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync Log2(("%s: Ctl: suppressed new request as RESET is active\n", __FUNCTION__));
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsync return;
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync }
dd97657cc7e8460edff31ebcff4c9d19bf8ad694vboxsync
b7a5b3f9f9ecce32ddacf8404c625ce0451bbdc1vboxsync /* If the controller is already doing something else right now, ignore
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsync * the command that is being submitted. Some broken guests issue commands
914d33aebb63d8c288dfd1b7e74f8e2acf3eaa66vboxsync * twice (e.g. the Linux kernel that comes with Acronis True Image 8). */
fe96bc0e43d9c137304462ef8c2d79cbff22446fvboxsync if (!fChainedTransfer && !ataAsyncIOIsIdle(pCtl, true))
548ca31b6b47c36bacce49bed3339cb8075b9681vboxsync {
548ca31b6b47c36bacce49bed3339cb8075b9681vboxsync Log(("%s: Ctl: ignored command %#04x, controller state %d\n", __FUNCTION__, s->uATARegCommand, pCtl->uAsyncIOState));
e3f5c51715cbf77ae2d2e9d05bafd00d69b1bec9vboxsync LogRel(("IDE: guest issued command %#04x while controller busy\n", s->uATARegCommand));
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync return;
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync }
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync Req.ReqType = AHCIATA_AIO_NEW;
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync if (fChainedTransfer)
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync Req.u.t.iIf = pCtl->iAIOIf;
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync else
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync Req.u.t.iIf = pCtl->iSelectedIf;
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync Req.u.t.cbTotalTransfer = cbTotalTransfer;
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync Req.u.t.uTxDir = uTxDir;
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync Req.u.t.iBeginTransfer = iBeginTransfer;
5b465a7c1237993faf8bb50120d247f3f0319adavboxsync Req.u.t.iSourceSink = iSourceSink;
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync ataSetStatusValue(s, ATA_STAT_BUSY);
ad77e3ec3cde24263bc7537575f5cae442bee3b1vboxsync pCtl->fChainedTransfer = fChainedTransfer;
ad77e3ec3cde24263bc7537575f5cae442bee3b1vboxsync
ad77e3ec3cde24263bc7537575f5cae442bee3b1vboxsync /*
ad77e3ec3cde24263bc7537575f5cae442bee3b1vboxsync * Kick the worker thread into action.
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync */
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync Log2(("%s: Ctl: message to async I/O thread, new request\n", __FUNCTION__));
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync ataAsyncIOPutRequest(pCtl, &Req);
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync}
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync/**
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync * Send an abort command request to the async I/O thread.
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync *
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync * @param s Pointer to the ATA device state data.
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync * @param fResetDrive Whether to reset the drive or just abort a command.
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync */
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsyncstatic void ataAbortCurrentCommand(AHCIATADevState *s, bool fResetDrive)
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync{
ad77e3ec3cde24263bc7537575f5cae442bee3b1vboxsync PAHCIATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
ad77e3ec3cde24263bc7537575f5cae442bee3b1vboxsync AHCIATARequest Req;
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync Assert(PDMCritSectIsOwner(&pCtl->lock));
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync /* Do not issue new requests while the RESET line is asserted. */
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync if (pCtl->fReset)
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync {
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync Log2(("%s: Ctl: suppressed aborting command as RESET is active\n", __FUNCTION__));
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync return;
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync }
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync
cba6719bd64ec749967bbe931230452664109857vboxsync Req.ReqType = AHCIATA_AIO_ABORT;
cba6719bd64ec749967bbe931230452664109857vboxsync Req.u.a.iIf = pCtl->iSelectedIf;
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync Req.u.a.fResetDrive = fResetDrive;
ad77e3ec3cde24263bc7537575f5cae442bee3b1vboxsync ataSetStatus(s, ATA_STAT_BUSY);
cba6719bd64ec749967bbe931230452664109857vboxsync Log2(("%s: Ctl: message to async I/O thread, abort command on LUN#%d\n", __FUNCTION__, s->iLUN));
ad77e3ec3cde24263bc7537575f5cae442bee3b1vboxsync ataAsyncIOPutRequest(pCtl, &Req);
cba6719bd64ec749967bbe931230452664109857vboxsync}
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync
cba6719bd64ec749967bbe931230452664109857vboxsync
cba6719bd64ec749967bbe931230452664109857vboxsyncstatic void ataSetIRQ(AHCIATADevState *s)
5b1d6bab9f4cf5dacf1883e7c4a40c84349f597fvboxsync{
PAHCIATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
PPDMDEVINS pDevIns = ATADEVSTATE_2_DEVINS(s);
if (!(s->uATARegDevCtl & ATA_DEVCTL_DISABLE_IRQ))
{
Log2(("%s: LUN#%d asserting IRQ\n", __FUNCTION__, s->iLUN));
/* The BMDMA unit unconditionally sets BM_STATUS_INT if the interrupt
* line is asserted. It monitors the line for a rising edge. */
if (!s->fIrqPending)
pCtl->BmDma.u8Status |= BM_STATUS_INT;
/* Only actually set the IRQ line if updating the currently selected drive. */
if (s == &pCtl->aIfs[pCtl->iSelectedIf])
PDMDevHlpISASetIrqNoWait(pDevIns, pCtl->irq, 1);
}
s->fIrqPending = true;
}
#endif /* IN_RING3 */
static void ataUnsetIRQ(AHCIATADevState *s)
{
PAHCIATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
PPDMDEVINS pDevIns = ATADEVSTATE_2_DEVINS(s);
if (!(s->uATARegDevCtl & ATA_DEVCTL_DISABLE_IRQ))
{
Log2(("%s: LUN#%d deasserting IRQ\n", __FUNCTION__, s->iLUN));
/* Only actually unset the IRQ line if updating the currently selected drive. */
if (s == &pCtl->aIfs[pCtl->iSelectedIf])
PDMDevHlpISASetIrqNoWait(pDevIns, pCtl->irq, 0);
}
s->fIrqPending = false;
}
#ifdef IN_RING3
static void ataPIOTransferStart(AHCIATADevState *s, uint32_t start, uint32_t size)
{
Log2(("%s: LUN#%d start %d size %d\n", __FUNCTION__, s->iLUN, start, size));
s->iIOBufferPIODataStart = start;
s->iIOBufferPIODataEnd = start + size;
ataSetStatus(s, ATA_STAT_DRQ);
}
static void ataPIOTransferStop(AHCIATADevState *s)
{
Log2(("%s: LUN#%d\n", __FUNCTION__, s->iLUN));
if (s->fATAPITransfer)
{
s->uATARegNSector = (s->uATARegNSector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
Log2(("%s: interrupt reason %#04x\n", __FUNCTION__, s->uATARegNSector));
ataSetIRQ(s);
s->fATAPITransfer = false;
}
s->cbTotalTransfer = 0;
s->cbElementaryTransfer = 0;
s->iIOBufferPIODataStart = 0;
s->iIOBufferPIODataEnd = 0;
s->iBeginTransfer = ATAFN_BT_NULL;
s->iSourceSink = ATAFN_SS_NULL;
}
static void ataPIOTransferLimitATAPI(AHCIATADevState *s)
{
uint32_t cbLimit, cbTransfer;
cbLimit = s->uATARegLCyl | (s->uATARegHCyl << 8);
/* Use maximum transfer size if the guest requested 0. Avoids a hang. */
if (cbLimit == 0)
cbLimit = 0xfffe;
Log2(("%s: byte count limit=%d\n", __FUNCTION__, cbLimit));
if (cbLimit == 0xffff)
cbLimit--;
cbTransfer = RT_MIN(s->cbTotalTransfer, s->iIOBufferEnd - s->iIOBufferCur);
if (cbTransfer > cbLimit)
{
/* Byte count limit for clipping must be even in this case */
if (cbLimit & 1)
cbLimit--;
cbTransfer = cbLimit;
}
s->uATARegLCyl = cbTransfer;
s->uATARegHCyl = cbTransfer >> 8;
s->cbElementaryTransfer = cbTransfer;
}
static uint32_t ataGetNSectors(AHCIATADevState *s)
{
/* 0 means either 256 (LBA28) or 65536 (LBA48) sectors. */
if (s->fLBA48)
{
if (!s->uATARegNSector && !s->uATARegNSectorHOB)
return 65536;
else
return s->uATARegNSectorHOB << 8 | s->uATARegNSector;
}
else
{
if (!s->uATARegNSector)
return 256;
else
return s->uATARegNSector;
}
}
static void ataPadString(uint8_t *pbDst, const char *pbSrc, uint32_t cbSize)
{
for (uint32_t i = 0; i < cbSize; i++)
{
if (*pbSrc)
pbDst[i ^ 1] = *pbSrc++;
else
pbDst[i ^ 1] = ' ';
}
}
static void ataSCSIPadStr(uint8_t *pbDst, const char *pbSrc, uint32_t cbSize)
{
for (uint32_t i = 0; i < cbSize; i++)
{
if (*pbSrc)
pbDst[i] = *pbSrc++;
else
pbDst[i] = ' ';
}
}
DECLINLINE(void) ataH2BE_U16(uint8_t *pbBuf, uint16_t val)
{
pbBuf[0] = val >> 8;
pbBuf[1] = val;
}
DECLINLINE(void) ataH2BE_U24(uint8_t *pbBuf, uint32_t val)
{
pbBuf[0] = val >> 16;
pbBuf[1] = val >> 8;
pbBuf[2] = val;
}
DECLINLINE(void) ataH2BE_U32(uint8_t *pbBuf, uint32_t val)
{
pbBuf[0] = val >> 24;
pbBuf[1] = val >> 16;
pbBuf[2] = val >> 8;
pbBuf[3] = val;
}
DECLINLINE(uint16_t) ataBE2H_U16(const uint8_t *pbBuf)
{
return (pbBuf[0] << 8) | pbBuf[1];
}
DECLINLINE(uint32_t) ataBE2H_U24(const uint8_t *pbBuf)
{
return (pbBuf[0] << 16) | (pbBuf[1] << 8) | pbBuf[2];
}
DECLINLINE(uint32_t) ataBE2H_U32(const uint8_t *pbBuf)
{
return (pbBuf[0] << 24) | (pbBuf[1] << 16) | (pbBuf[2] << 8) | pbBuf[3];
}
DECLINLINE(void) ataLBA2MSF(uint8_t *pbBuf, uint32_t iATAPILBA)
{
iATAPILBA += 150;
pbBuf[0] = (iATAPILBA / 75) / 60;
pbBuf[1] = (iATAPILBA / 75) % 60;
pbBuf[2] = iATAPILBA % 75;
}
DECLINLINE(uint32_t) ataMSF2LBA(const uint8_t *pbBuf)
{
return (pbBuf[0] * 60 + pbBuf[1]) * 75 + pbBuf[2];
}
static void ataCmdOK(AHCIATADevState *s, uint8_t status)
{
s->uATARegError = 0; /* Not needed by ATA spec, but cannot hurt. */
ataSetStatusValue(s, ATA_STAT_READY | status);
}
static void ataCmdError(AHCIATADevState *s, uint8_t uErrorCode)
{
Log(("%s: code=%#x\n", __FUNCTION__, uErrorCode));
s->uATARegError = uErrorCode;
ataSetStatusValue(s, ATA_STAT_READY | ATA_STAT_ERR);
s->cbTotalTransfer = 0;
s->cbElementaryTransfer = 0;
s->iIOBufferCur = 0;
s->iIOBufferEnd = 0;
s->uTxDir = PDMBLOCKTXDIR_NONE;
s->iBeginTransfer = ATAFN_BT_NULL;
s->iSourceSink = ATAFN_SS_NULL;
}
static bool ataIdentifySS(AHCIATADevState *s)
{
uint16_t *p;
char aSerial[20];
int rc;
RTUUID Uuid;
Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
Assert(s->cbElementaryTransfer == 512);
rc = s->pDrvBlock ? s->pDrvBlock->pfnGetUuid(s->pDrvBlock, &Uuid) : RTUuidClear(&Uuid);
if (RT_FAILURE(rc) || RTUuidIsNull(&Uuid))
{
PAHCIATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
/* Generate a predictable serial for drives which don't have a UUID. */
RTStrPrintf(aSerial, sizeof(aSerial), "VB%x-%04x%04x",
s->iLUN + ATADEVSTATE_2_DEVINS(s)->iInstance * 32,
pCtl->IOPortBase1, pCtl->IOPortBase2);
}
else
RTStrPrintf(aSerial, sizeof(aSerial), "VB%08x-%08x", Uuid.au32[0], Uuid.au32[3]);
p = (uint16_t *)s->CTXALLSUFF(pbIOBuffer);
memset(p, 0, 512);
p[0] = RT_H2LE_U16(0x0040);
p[1] = RT_H2LE_U16(RT_MIN(s->PCHSGeometry.cCylinders, 16383));
p[3] = RT_H2LE_U16(s->PCHSGeometry.cHeads);
/* Block size; obsolete, but required for the BIOS. */
p[5] = RT_H2LE_U16(512);
p[6] = RT_H2LE_U16(s->PCHSGeometry.cSectors);
ataPadString((uint8_t *)(p + 10), aSerial, 20); /* serial number */
p[20] = RT_H2LE_U16(3); /* XXX: retired, cache type */
p[21] = RT_H2LE_U16(512); /* XXX: retired, cache size in sectors */
p[22] = RT_H2LE_U16(0); /* ECC bytes per sector */
ataPadString((uint8_t *)(p + 23), "1.0", 8); /* firmware version */
ataPadString((uint8_t *)(p + 27), "VBOX HARDDISK", 40); /* model */
#if ATA_MAX_MULT_SECTORS > 1
p[47] = RT_H2LE_U16(0x8000 | ATA_MAX_MULT_SECTORS);
#endif
p[48] = RT_H2LE_U16(1); /* dword I/O, used by the BIOS */
p[49] = RT_H2LE_U16(1 << 11 | 1 << 9 | 1 << 8); /* DMA and LBA supported */
p[50] = RT_H2LE_U16(1 << 14); /* No drive specific standby timer minimum */
p[51] = RT_H2LE_U16(240); /* PIO transfer cycle */
p[52] = RT_H2LE_U16(240); /* DMA transfer cycle */
p[53] = RT_H2LE_U16(1 | 1 << 1 | 1 << 2); /* words 54-58,64-70,88 valid */
p[54] = RT_H2LE_U16(RT_MIN(s->PCHSGeometry.cCylinders, 16383));
p[55] = RT_H2LE_U16(s->PCHSGeometry.cHeads);
p[56] = RT_H2LE_U16(s->PCHSGeometry.cSectors);
p[57] = RT_H2LE_U16( RT_MIN(s->PCHSGeometry.cCylinders, 16383)
* s->PCHSGeometry.cHeads
* s->PCHSGeometry.cSectors);
p[58] = RT_H2LE_U16( RT_MIN(s->PCHSGeometry.cCylinders, 16383)
* s->PCHSGeometry.cHeads
* s->PCHSGeometry.cSectors >> 16);
if (s->cMultSectors)
p[59] = RT_H2LE_U16(0x100 | s->cMultSectors);
if (s->cTotalSectors <= (1 << 28) - 1)
{
p[60] = RT_H2LE_U16(s->cTotalSectors);
p[61] = RT_H2LE_U16(s->cTotalSectors >> 16);
}
else
{
/* Report maximum number of sectors possible with LBA28 */
p[60] = RT_H2LE_U16(((1 << 28) - 1) & 0xffff);
p[61] = RT_H2LE_U16(((1 << 28) - 1) >> 16);
}
p[63] = RT_H2LE_U16(ATA_TRANSFER_ID(ATA_MODE_MDMA, ATA_MDMA_MODE_MAX, s->uATATransferMode)); /* MDMA modes supported / mode enabled */
p[64] = RT_H2LE_U16(ATA_PIO_MODE_MAX > 2 ? (1 << (ATA_PIO_MODE_MAX - 2)) - 1 : 0); /* PIO modes beyond PIO2 supported */
p[65] = RT_H2LE_U16(120); /* minimum DMA multiword tx cycle time */
p[66] = RT_H2LE_U16(120); /* recommended DMA multiword tx cycle time */
p[67] = RT_H2LE_U16(120); /* minimum PIO cycle time without flow control */
p[68] = RT_H2LE_U16(120); /* minimum PIO cycle time with IORDY flow control */
p[80] = RT_H2LE_U16(0x7e); /* support everything up to ATA/ATAPI-6 */
p[81] = RT_H2LE_U16(0x22); /* conforms to ATA/ATAPI-6 */
p[82] = RT_H2LE_U16(1 << 3 | 1 << 5 | 1 << 6); /* supports power management, write cache and look-ahead */
if (s->cTotalSectors <= (1 << 28) - 1)
p[83] = RT_H2LE_U16(1 << 14 | 1 << 12); /* supports FLUSH CACHE */
else
p[83] = RT_H2LE_U16(1 << 14 | 1 << 10 | 1 << 12 | 1 << 13); /* supports LBA48, FLUSH CACHE and FLUSH CACHE EXT */
p[84] = RT_H2LE_U16(1 << 14);
p[85] = RT_H2LE_U16(1 << 3 | 1 << 5 | 1 << 6); /* enabled power management, write cache and look-ahead */
if (s->cTotalSectors <= (1 << 28) - 1)
p[86] = RT_H2LE_U16(1 << 12); /* enabled FLUSH CACHE */
else
p[86] = RT_H2LE_U16(1 << 10 | 1 << 12 | 1 << 13); /* enabled LBA48, FLUSH CACHE and FLUSH CACHE EXT */
p[87] = RT_H2LE_U16(1 << 14);
p[88] = RT_H2LE_U16(ATA_TRANSFER_ID(ATA_MODE_UDMA, ATA_UDMA_MODE_MAX, s->uATATransferMode)); /* UDMA modes supported / mode enabled */
p[93] = RT_H2LE_U16((1 | 1 << 1) << ((s->iLUN & 1) == 0 ? 0 : 8) | 1 << 13 | 1 << 14);
if (s->cTotalSectors > (1 << 28) - 1)
{
p[100] = RT_H2LE_U16(s->cTotalSectors);
p[101] = RT_H2LE_U16(s->cTotalSectors >> 16);
p[102] = RT_H2LE_U16(s->cTotalSectors >> 32);
p[103] = RT_H2LE_U16(s->cTotalSectors >> 48);
}
s->iSourceSink = ATAFN_SS_NULL;
ataCmdOK(s, ATA_STAT_SEEK);
return false;
}
static bool ataFlushSS(AHCIATADevState *s)
{
PAHCIATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
int rc;
Assert(s->uTxDir == PDMBLOCKTXDIR_NONE);
Assert(!s->cbElementaryTransfer);
PDMCritSectLeave(&pCtl->lock);
STAM_PROFILE_START(&s->StatFlushes, f);
rc = s->pDrvBlock->pfnFlush(s->pDrvBlock);
AssertRC(rc);
STAM_PROFILE_STOP(&s->StatFlushes, f);
STAM_PROFILE_START(&pCtl->StatLockWait, a);
PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
ataCmdOK(s, 0);
return false;
}
static bool atapiIdentifySS(AHCIATADevState *s)
{
uint16_t *p;
char aSerial[20];
RTUUID Uuid;
int rc;
Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
Assert(s->cbElementaryTransfer == 512);
rc = s->pDrvBlock ? s->pDrvBlock->pfnGetUuid(s->pDrvBlock, &Uuid) : RTUuidClear(&Uuid);
if (RT_FAILURE(rc) || RTUuidIsNull(&Uuid))
{
PAHCIATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
/* Generate a predictable serial for drives which don't have a UUID. */
RTStrPrintf(aSerial, sizeof(aSerial), "VB%x-%04x%04x",
s->iLUN + ATADEVSTATE_2_DEVINS(s)->iInstance * 32,
pCtl->IOPortBase1, pCtl->IOPortBase2);
}
else
RTStrPrintf(aSerial, sizeof(aSerial), "VB%08x-%08x", Uuid.au32[0], Uuid.au32[3]);
p = (uint16_t *)s->CTXALLSUFF(pbIOBuffer);
memset(p, 0, 512);
/* Removable CDROM, 50us response, 12 byte packets */
p[0] = RT_H2LE_U16(2 << 14 | 5 << 8 | 1 << 7 | 2 << 5 | 0 << 0);
ataPadString((uint8_t *)(p + 10), aSerial, 20); /* serial number */
p[20] = RT_H2LE_U16(3); /* XXX: retired, cache type */
p[21] = RT_H2LE_U16(512); /* XXX: retired, cache size in sectors */
ataPadString((uint8_t *)(p + 23), "1.0", 8); /* firmware version */
ataPadString((uint8_t *)(p + 27), "VBOX CD-ROM", 40); /* model */
p[49] = RT_H2LE_U16(1 << 11 | 1 << 9 | 1 << 8); /* DMA and LBA supported */
p[50] = RT_H2LE_U16(1 << 14); /* No drive specific standby timer minimum */
p[51] = RT_H2LE_U16(240); /* PIO transfer cycle */
p[52] = RT_H2LE_U16(240); /* DMA transfer cycle */
p[53] = RT_H2LE_U16(1 << 1 | 1 << 2); /* words 64-70,88 are valid */
p[63] = RT_H2LE_U16(ATA_TRANSFER_ID(ATA_MODE_MDMA, ATA_MDMA_MODE_MAX, s->uATATransferMode)); /* MDMA modes supported / mode enabled */
p[64] = RT_H2LE_U16(ATA_PIO_MODE_MAX > 2 ? (1 << (ATA_PIO_MODE_MAX - 2)) - 1 : 0); /* PIO modes beyond PIO2 supported */
p[65] = RT_H2LE_U16(120); /* minimum DMA multiword tx cycle time */
p[66] = RT_H2LE_U16(120); /* recommended DMA multiword tx cycle time */
p[67] = RT_H2LE_U16(120); /* minimum PIO cycle time without flow control */
p[68] = RT_H2LE_U16(120); /* minimum PIO cycle time with IORDY flow control */
p[73] = RT_H2LE_U16(0x003e); /* ATAPI CDROM major */
p[74] = RT_H2LE_U16(9); /* ATAPI CDROM minor */
p[75] = RT_H2LE_U16(1); /* queue depth 1 */
p[80] = RT_H2LE_U16(0x7e); /* support everything up to ATA/ATAPI-6 */
p[81] = RT_H2LE_U16(0x22); /* conforms to ATA/ATAPI-6 */
p[82] = RT_H2LE_U16(1 << 4 | 1 << 9); /* supports packet command set and DEVICE RESET */
p[83] = RT_H2LE_U16(1 << 14);
p[84] = RT_H2LE_U16(1 << 14);
p[85] = RT_H2LE_U16(1 << 4 | 1 << 9); /* enabled packet command set and DEVICE RESET */
p[86] = RT_H2LE_U16(0);
p[87] = RT_H2LE_U16(1 << 14);
p[88] = RT_H2LE_U16(ATA_TRANSFER_ID(ATA_MODE_UDMA, ATA_UDMA_MODE_MAX, s->uATATransferMode)); /* UDMA modes supported / mode enabled */
p[93] = RT_H2LE_U16((1 | 1 << 1) << ((s->iLUN & 1) == 0 ? 0 : 8) | 1 << 13 | 1 << 14);
s->iSourceSink = ATAFN_SS_NULL;
ataCmdOK(s, ATA_STAT_SEEK);
return false;
}
static void ataSetSignature(AHCIATADevState *s)
{
s->uATARegSelect &= 0xf0; /* clear head */
/* put signature */
s->uATARegNSector = 1;
s->uATARegSector = 1;
if (s->fATAPI)
{
s->uATARegLCyl = 0x14;
s->uATARegHCyl = 0xeb;
}
else if (s->pDrvBlock)
{
s->uATARegLCyl = 0;
s->uATARegHCyl = 0;
}
else
{
s->uATARegLCyl = 0xff;
s->uATARegHCyl = 0xff;
}
}
static uint64_t ataGetSector(AHCIATADevState *s)
{
uint64_t iLBA;
if (s->uATARegSelect & 0x40)
{
/* any LBA variant */
if (s->fLBA48)
{
/* LBA48 */
iLBA = ((uint64_t)s->uATARegHCylHOB << 40) |
((uint64_t)s->uATARegLCylHOB << 32) |
((uint64_t)s->uATARegSectorHOB << 24) |
((uint64_t)s->uATARegHCyl << 16) |
((uint64_t)s->uATARegLCyl << 8) |
s->uATARegSector;
}
else
{
/* LBA */
iLBA = ((s->uATARegSelect & 0x0f) << 24) | (s->uATARegHCyl << 16) |
(s->uATARegLCyl << 8) | s->uATARegSector;
}
}
else
{
/* CHS */
iLBA = ((s->uATARegHCyl << 8) | s->uATARegLCyl) * s->PCHSGeometry.cHeads * s->PCHSGeometry.cSectors +
(s->uATARegSelect & 0x0f) * s->PCHSGeometry.cSectors +
(s->uATARegSector - 1);
}
return iLBA;
}
static void ataSetSector(AHCIATADevState *s, uint64_t iLBA)
{
uint32_t cyl, r;
if (s->uATARegSelect & 0x40)
{
/* any LBA variant */
if (s->fLBA48)
{
/* LBA48 */
s->uATARegHCylHOB = iLBA >> 40;
s->uATARegLCylHOB = iLBA >> 32;
s->uATARegSectorHOB = iLBA >> 24;
s->uATARegHCyl = iLBA >> 16;
s->uATARegLCyl = iLBA >> 8;
s->uATARegSector = iLBA;
}
else
{
/* LBA */
s->uATARegSelect = (s->uATARegSelect & 0xf0) | (iLBA >> 24);
s->uATARegHCyl = (iLBA >> 16);
s->uATARegLCyl = (iLBA >> 8);
s->uATARegSector = (iLBA);
}
}
else
{
/* CHS */
cyl = iLBA / (s->PCHSGeometry.cHeads * s->PCHSGeometry.cSectors);
r = iLBA % (s->PCHSGeometry.cHeads * s->PCHSGeometry.cSectors);
s->uATARegHCyl = cyl >> 8;
s->uATARegLCyl = cyl;
s->uATARegSelect = (s->uATARegSelect & 0xf0) | ((r / s->PCHSGeometry.cSectors) & 0x0f);
s->uATARegSector = (r % s->PCHSGeometry.cSectors) + 1;
}
}
static int ataReadSectors(AHCIATADevState *s, uint64_t u64Sector, void *pvBuf, uint32_t cSectors)
{
PAHCIATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
int rc;
PDMCritSectLeave(&pCtl->lock);
STAM_PROFILE_ADV_START(&s->StatReads, r);
s->pLed->Asserted.s.fReading = s->pLed->Actual.s.fReading = 1;
rc = s->pDrvBlock->pfnRead(s->pDrvBlock, u64Sector * 512, pvBuf, cSectors * 512);
s->pLed->Actual.s.fReading = 0;
STAM_PROFILE_ADV_STOP(&s->StatReads, r);
STAM_REL_COUNTER_ADD(s->pStatBytesRead, cSectors * 512);
STAM_PROFILE_START(&pCtl->StatLockWait, a);
PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
return rc;
}
static int ataWriteSectors(AHCIATADevState *s, uint64_t u64Sector, const void *pvBuf, uint32_t cSectors)
{
PAHCIATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
int rc;
PDMCritSectLeave(&pCtl->lock);
STAM_PROFILE_ADV_START(&s->StatWrites, w);
s->pLed->Asserted.s.fWriting = s->pLed->Actual.s.fWriting = 1;
rc = s->pDrvBlock->pfnWrite(s->pDrvBlock, u64Sector * 512, pvBuf, cSectors * 512);
s->pLed->Actual.s.fWriting = 0;
STAM_PROFILE_ADV_STOP(&s->StatWrites, w);
STAM_REL_COUNTER_ADD(s->pStatBytesWritten, cSectors * 512);
STAM_PROFILE_START(&pCtl->StatLockWait, a);
PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
return rc;
}
static void ataReadWriteSectorsBT(AHCIATADevState *s)
{
uint32_t cSectors;
cSectors = s->cbTotalTransfer / 512;
if (cSectors > s->cSectorsPerIRQ)
s->cbElementaryTransfer = s->cSectorsPerIRQ * 512;
else
s->cbElementaryTransfer = cSectors * 512;
if (s->uTxDir == PDMBLOCKTXDIR_TO_DEVICE)
ataCmdOK(s, 0);
}
static void ataWarningDiskFull(PPDMDEVINS pDevIns)
{
int rc;
LogRel(("ATA: Host disk full\n"));
rc = PDMDevHlpVMSetRuntimeError(pDevIns, 0 /*fFlags*/, "DevATA_DISKFULL",
N_("Host system reported disk full. VM execution is suspended. You can resume after freeing some space"));
AssertRC(rc);
}
static void ataWarningFileTooBig(PPDMDEVINS pDevIns)
{
int rc;
LogRel(("ATA: File too big\n"));
rc = PDMDevHlpVMSetRuntimeError(pDevIns, 0 /*fFlags*/, "DevATA_FILETOOBIG",
N_("Host system reported that the file size limit of the host file system has been exceeded. VM execution is suspended. You need to move your virtual hard disk to a filesystem which allows bigger files"));
AssertRC(rc);
}
static void ataWarningISCSI(PPDMDEVINS pDevIns)
{
int rc;
LogRel(("ATA: iSCSI target unavailable\n"));
rc = PDMDevHlpVMSetRuntimeError(pDevIns, 0 /*fFlags*/, "DevATA_ISCSIDOWN",
N_("The iSCSI target has stopped responding. VM execution is suspended. You can resume when it is available again"));
AssertRC(rc);
}
static bool ataReadSectorsSS(AHCIATADevState *s)
{
int rc;
uint32_t cSectors;
uint64_t iLBA;
cSectors = s->cbElementaryTransfer / 512;
Assert(cSectors);
iLBA = ataGetSector(s);
Log(("%s: %d sectors at LBA %d\n", __FUNCTION__, cSectors, iLBA));
rc = ataReadSectors(s, iLBA, s->CTXALLSUFF(pbIOBuffer), cSectors);
if (RT_SUCCESS(rc))
{
ataSetSector(s, iLBA + cSectors);
if (s->cbElementaryTransfer == s->cbTotalTransfer)
s->iSourceSink = ATAFN_SS_NULL;
ataCmdOK(s, ATA_STAT_SEEK);
}
else
{
if (rc == VERR_DISK_FULL)
{
ataWarningDiskFull(ATADEVSTATE_2_DEVINS(s));
return true;
}
if (rc == VERR_FILE_TOO_BIG)
{
ataWarningFileTooBig(ATADEVSTATE_2_DEVINS(s));
return true;
}
if (rc == VERR_BROKEN_PIPE || rc == VERR_NET_CONNECTION_REFUSED)
{
/* iSCSI connection abort (first error) or failure to reestablish
* connection (second error). Pause VM. On resume we'll retry. */
ataWarningISCSI(ATADEVSTATE_2_DEVINS(s));
return true;
}
if (s->cErrors++ < MAX_LOG_REL_ERRORS)
LogRel(("AHCI ATA: LUN#%d: disk read error (rc=%Rrc iSector=%#RX64 cSectors=%#RX32)\n",
s->iLUN, rc, iLBA, cSectors));
ataCmdError(s, ID_ERR);
}
/** @todo implement redo for iSCSI */
return false;
}
static bool ataWriteSectorsSS(AHCIATADevState *s)
{
int rc;
uint32_t cSectors;
uint64_t iLBA;
cSectors = s->cbElementaryTransfer / 512;
Assert(cSectors);
iLBA = ataGetSector(s);
Log(("%s: %d sectors at LBA %d\n", __FUNCTION__, cSectors, iLBA));
rc = ataWriteSectors(s, iLBA, s->CTXALLSUFF(pbIOBuffer), cSectors);
if (RT_SUCCESS(rc))
{
ataSetSector(s, iLBA + cSectors);
if (!s->cbTotalTransfer)
s->iSourceSink = ATAFN_SS_NULL;
ataCmdOK(s, ATA_STAT_SEEK);
}
else
{
if (rc == VERR_DISK_FULL)
{
ataWarningDiskFull(ATADEVSTATE_2_DEVINS(s));
return true;
}
if (rc == VERR_FILE_TOO_BIG)
{
ataWarningFileTooBig(ATADEVSTATE_2_DEVINS(s));
return true;
}
if (rc == VERR_BROKEN_PIPE || rc == VERR_NET_CONNECTION_REFUSED)
{
/* iSCSI connection abort (first error) or failure to reestablish
* connection (second error). Pause VM. On resume we'll retry. */
ataWarningISCSI(ATADEVSTATE_2_DEVINS(s));
return true;
}
if (s->cErrors++ < MAX_LOG_REL_ERRORS)
LogRel(("AHCI ATA: LUN#%d: disk write error (rc=%Rrc iSector=%#RX64 cSectors=%#RX32)\n",
s->iLUN, rc, iLBA, cSectors));
ataCmdError(s, ID_ERR);
}
/** @todo implement redo for iSCSI */
return false;
}
static void atapiCmdOK(AHCIATADevState *s)
{
s->uATARegError = 0;
ataSetStatusValue(s, ATA_STAT_READY);
s->uATARegNSector = (s->uATARegNSector & ~7)
| ((s->uTxDir != PDMBLOCKTXDIR_TO_DEVICE) ? ATAPI_INT_REASON_IO : 0)
| (!s->cbTotalTransfer ? ATAPI_INT_REASON_CD : 0);
Log2(("%s: interrupt reason %#04x\n", __FUNCTION__, s->uATARegNSector));
memset(s->abATAPISense, '\0', sizeof(s->abATAPISense));
s->abATAPISense[0] = 0x70 | (1 << 7);
s->abATAPISense[7] = 10;
}
static void atapiCmdError(AHCIATADevState *s, const uint8_t *pabATAPISense, size_t cbATAPISense)
{
Log(("%s: sense=%#x (%s) asc=%#x ascq=%#x (%s)\n", __FUNCTION__, pabATAPISense[2] & 0x0f, SCSISenseText(pabATAPISense[2] & 0x0f),
pabATAPISense[12], pabATAPISense[13], SCSISenseExtText(pabATAPISense[12], pabATAPISense[13])));
s->uATARegError = pabATAPISense[2] << 4;
ataSetStatusValue(s, ATA_STAT_READY | ATA_STAT_ERR);
s->uATARegNSector = (s->uATARegNSector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
Log2(("%s: interrupt reason %#04x\n", __FUNCTION__, s->uATARegNSector));
memset(s->abATAPISense, '\0', sizeof(s->abATAPISense));
memcpy(s->abATAPISense, pabATAPISense, RT_MIN(cbATAPISense, sizeof(s->abATAPISense)));
s->cbTotalTransfer = 0;
s->cbElementaryTransfer = 0;
s->iIOBufferCur = 0;
s->iIOBufferEnd = 0;
s->uTxDir = PDMBLOCKTXDIR_NONE;
s->iBeginTransfer = ATAFN_BT_NULL;
s->iSourceSink = ATAFN_SS_NULL;
}
/** @todo deprecated function - doesn't provide enough info. Replace by direct
* calls to atapiCmdError() with full data. */
static void atapiCmdErrorSimple(AHCIATADevState *s, uint8_t uATAPISenseKey, uint8_t uATAPIASC)
{
uint8_t abATAPISense[ATAPI_SENSE_SIZE];
memset(abATAPISense, '\0', sizeof(abATAPISense));
abATAPISense[0] = 0x70 | (1 << 7);
abATAPISense[2] = uATAPISenseKey & 0x0f;
abATAPISense[7] = 10;
abATAPISense[12] = uATAPIASC;
atapiCmdError(s, abATAPISense, sizeof(abATAPISense));
}
static void atapiCmdBT(AHCIATADevState *s)
{
s->fATAPITransfer = true;
s->cbElementaryTransfer = s->cbTotalTransfer;
if (s->uTxDir == PDMBLOCKTXDIR_TO_DEVICE)
atapiCmdOK(s);
}
static void atapiPassthroughCmdBT(AHCIATADevState *s)
{
/* @todo implement an algorithm for correctly determining the read and
* write sector size without sending additional commands to the drive.
* This should be doable by saving processing the configuration requests
* and replies. */
#if 0
if (s->uTxDir == PDMBLOCKTXDIR_TO_DEVICE)
{
uint8_t cmd = s->aATAPICmd[0];
if (cmd == SCSI_WRITE_10 || cmd == SCSI_WRITE_12 || cmd == SCSI_WRITE_AND_VERIFY_10)
{
uint8_t aModeSenseCmd[10];
uint8_t aModeSenseResult[16];
uint8_t uDummySense;
uint32_t cbTransfer;
int rc;
cbTransfer = sizeof(aModeSenseResult);
aModeSenseCmd[0] = SCSI_MODE_SENSE_10;
aModeSenseCmd[1] = 0x08; /* disable block descriptor = 1 */
aModeSenseCmd[2] = (SCSI_PAGECONTROL_CURRENT << 6) | SCSI_MODEPAGE_WRITE_PARAMETER;
aModeSenseCmd[3] = 0; /* subpage code */
aModeSenseCmd[4] = 0; /* reserved */
aModeSenseCmd[5] = 0; /* reserved */
aModeSenseCmd[6] = 0; /* reserved */
aModeSenseCmd[7] = cbTransfer >> 8;
aModeSenseCmd[8] = cbTransfer & 0xff;
aModeSenseCmd[9] = 0; /* control */
rc = s->pDrvBlock->pfnSendCmd(s->pDrvBlock, aModeSenseCmd, PDMBLOCKTXDIR_FROM_DEVICE, aModeSenseResult, &cbTransfer, &uDummySense, 500);
if (RT_FAILURE(rc))
{
atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_NONE);
return;
}
/* Select sector size based on the current data block type. */
switch (aModeSenseResult[12] & 0x0f)
{
case 0:
s->cbATAPISector = 2352;
break;
case 1:
s->cbATAPISector = 2368;
break;
case 2:
case 3:
s->cbATAPISector = 2448;
break;
case 8:
case 10:
s->cbATAPISector = 2048;
break;
case 9:
s->cbATAPISector = 2336;
break;
case 11:
s->cbATAPISector = 2056;
break;
case 12:
s->cbATAPISector = 2324;
break;
case 13:
s->cbATAPISector = 2332;
break;
default:
s->cbATAPISector = 0;
}
Log2(("%s: sector size %d\n", __FUNCTION__, s->cbATAPISector));
s->cbTotalTransfer *= s->cbATAPISector;
if (s->cbTotalTransfer == 0)
s->uTxDir = PDMBLOCKTXDIR_NONE;
}
}
#endif
atapiCmdBT(s);
}
static bool atapiReadSS(AHCIATADevState *s)
{
PAHCIATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
int rc = VINF_SUCCESS;
uint32_t cbTransfer, cSectors;
Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
cbTransfer = RT_MIN(s->cbTotalTransfer, s->cbIOBuffer);
cSectors = cbTransfer / s->cbATAPISector;
Assert(cSectors * s->cbATAPISector <= cbTransfer);
Log(("%s: %d sectors at LBA %d\n", __FUNCTION__, cSectors, s->iATAPILBA));
PDMCritSectLeave(&pCtl->lock);
STAM_PROFILE_ADV_START(&s->StatReads, r);
s->pLed->Asserted.s.fReading = s->pLed->Actual.s.fReading = 1;
switch (s->cbATAPISector)
{
case 2048:
rc = s->pDrvBlock->pfnRead(s->pDrvBlock, (uint64_t)s->iATAPILBA * s->cbATAPISector, s->CTXALLSUFF(pbIOBuffer), s->cbATAPISector * cSectors);
break;
case 2352:
{
uint8_t *pbBuf = s->CTXALLSUFF(pbIOBuffer);
for (uint32_t i = s->iATAPILBA; i < s->iATAPILBA + cSectors; i++)
{
/* sync bytes */
*pbBuf++ = 0x00;
memset(pbBuf, 0xff, 11);
pbBuf += 11;
/* MSF */
ataLBA2MSF(pbBuf, i);
pbBuf += 3;
*pbBuf++ = 0x01; /* mode 1 data */
/* data */
rc = s->pDrvBlock->pfnRead(s->pDrvBlock, (uint64_t)i * 2048, pbBuf, 2048);
if (RT_FAILURE(rc))
break;
pbBuf += 2048;
/* ECC */
memset(pbBuf, 0, 288);
pbBuf += 288;
}
}
break;
default:
break;
}
STAM_PROFILE_ADV_STOP(&s->StatReads, r);
STAM_PROFILE_START(&pCtl->StatLockWait, a);
PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
if (RT_SUCCESS(rc))
{
s->pLed->Actual.s.fReading = 0;
STAM_REL_COUNTER_ADD(s->pStatBytesRead, s->cbATAPISector * cSectors);
/* The initial buffer end value has been set up based on the total
* transfer size. But the I/O buffer size limits what can actually be
* done in one transfer, so set the actual value of the buffer end. */
s->cbElementaryTransfer = cbTransfer;
if (cbTransfer >= s->cbTotalTransfer)
s->iSourceSink = ATAFN_SS_NULL;
atapiCmdOK(s);
s->iATAPILBA += cSectors;
}
else
{
if (s->cErrors++ < MAX_LOG_REL_ERRORS)
LogRel(("AHCI ATA: LUN#%d: CD-ROM read error, %d sectors at LBA %d\n", s->iLUN, cSectors, s->iATAPILBA));
atapiCmdErrorSimple(s, SCSI_SENSE_MEDIUM_ERROR, SCSI_ASC_READ_ERROR);
}
return false;
}
static bool atapiPassthroughSS(AHCIATADevState *s)
{
PAHCIATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
int rc = VINF_SUCCESS;
uint8_t abATAPISense[ATAPI_SENSE_SIZE];
uint32_t cbTransfer;
PSTAMPROFILEADV pProf = NULL;
cbTransfer = s->cbElementaryTransfer;
if (s->uTxDir == PDMBLOCKTXDIR_TO_DEVICE)
Log3(("ATAPI PT data write (%d): %.*Rhxs\n", cbTransfer, cbTransfer, s->CTXALLSUFF(pbIOBuffer)));
/* Simple heuristics: if there is at least one sector of data
* to transfer, it's worth updating the LEDs. */
if (cbTransfer >= 2048)
{
if (s->uTxDir != PDMBLOCKTXDIR_TO_DEVICE)
{
s->pLed->Asserted.s.fReading = s->pLed->Actual.s.fReading = 1;
pProf = &s->StatReads;
}
else
{
s->pLed->Asserted.s.fWriting = s->pLed->Actual.s.fWriting = 1;
pProf = &s->StatWrites;
}
}
PDMCritSectLeave(&pCtl->lock);
memset(abATAPISense, '\0', sizeof(abATAPISense));
if (pProf) { STAM_PROFILE_ADV_START(pProf, b); }
if (cbTransfer > 100 * _1K)
{
/* Linux accepts commands with up to 100KB of data, but expects
* us to handle commands with up to 128KB of data. The usual
* imbalance of powers. */
uint8_t aATAPICmd[ATAPI_PACKET_SIZE];
uint32_t iATAPILBA, cSectors, cReqSectors, cbCurrTX;
uint8_t *pbBuf = s->CTXALLSUFF(pbIOBuffer);
switch (s->aATAPICmd[0])
{
case SCSI_READ_10:
case SCSI_WRITE_10:
case SCSI_WRITE_AND_VERIFY_10:
iATAPILBA = ataBE2H_U32(s->aATAPICmd + 2);
cSectors = ataBE2H_U16(s->aATAPICmd + 7);
break;
case SCSI_READ_12:
case SCSI_WRITE_12:
iATAPILBA = ataBE2H_U32(s->aATAPICmd + 2);
cSectors = ataBE2H_U32(s->aATAPICmd + 6);
break;
case SCSI_READ_CD:
iATAPILBA = ataBE2H_U32(s->aATAPICmd + 2);
cSectors = ataBE2H_U24(s->aATAPICmd + 6) / s->cbATAPISector;
break;
case SCSI_READ_CD_MSF:
iATAPILBA = ataMSF2LBA(s->aATAPICmd + 3);
cSectors = ataMSF2LBA(s->aATAPICmd + 6) - iATAPILBA;
break;
default:
AssertMsgFailed(("Don't know how to split command %#04x\n", s->aATAPICmd[0]));
if (s->cErrors++ < MAX_LOG_REL_ERRORS)
LogRel(("AHCI ATA: LUN#%d: CD-ROM passthrough split error\n", s->iLUN));
atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
{
STAM_PROFILE_START(&pCtl->StatLockWait, a);
PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
}
return false;
}
memcpy(aATAPICmd, s->aATAPICmd, ATAPI_PACKET_SIZE);
cReqSectors = 0;
for (uint32_t i = cSectors; i > 0; i -= cReqSectors)
{
if (i * s->cbATAPISector > 100 * _1K)
cReqSectors = (100 * _1K) / s->cbATAPISector;
else
cReqSectors = i;
cbCurrTX = s->cbATAPISector * cReqSectors;
switch (s->aATAPICmd[0])
{
case SCSI_READ_10:
case SCSI_WRITE_10:
case SCSI_WRITE_AND_VERIFY_10:
ataH2BE_U32(aATAPICmd + 2, iATAPILBA);
ataH2BE_U16(aATAPICmd + 7, cReqSectors);
break;
case SCSI_READ_12:
case SCSI_WRITE_12:
ataH2BE_U32(aATAPICmd + 2, iATAPILBA);
ataH2BE_U32(aATAPICmd + 6, cReqSectors);
break;
case SCSI_READ_CD:
ataH2BE_U32(s->aATAPICmd + 2, iATAPILBA);
ataH2BE_U24(s->aATAPICmd + 6, cbCurrTX);
break;
case SCSI_READ_CD_MSF:
ataLBA2MSF(aATAPICmd + 3, iATAPILBA);
ataLBA2MSF(aATAPICmd + 6, iATAPILBA + cReqSectors);
break;
}
rc = s->pDrvBlock->pfnSendCmd(s->pDrvBlock, aATAPICmd, (PDMBLOCKTXDIR)s->uTxDir, pbBuf, &cbCurrTX, abATAPISense, sizeof(abATAPISense), 30000 /**< @todo timeout */);
if (rc != VINF_SUCCESS)
break;
iATAPILBA += cReqSectors;
pbBuf += s->cbATAPISector * cReqSectors;
}
}
else
rc = s->pDrvBlock->pfnSendCmd(s->pDrvBlock, s->aATAPICmd, (PDMBLOCKTXDIR)s->uTxDir, s->CTXALLSUFF(pbIOBuffer), &cbTransfer, abATAPISense, sizeof(abATAPISense), 30000 /**< @todo timeout */);
if (pProf) { STAM_PROFILE_ADV_STOP(pProf, b); }
STAM_PROFILE_START(&pCtl->StatLockWait, a);
PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
/* Update the LEDs and the read/write statistics. */
if (cbTransfer >= 2048)
{
if (s->uTxDir != PDMBLOCKTXDIR_TO_DEVICE)
{
s->pLed->Actual.s.fReading = 0;
STAM_REL_COUNTER_ADD(s->pStatBytesRead, cbTransfer);
}
else
{
s->pLed->Actual.s.fWriting = 0;
STAM_REL_COUNTER_ADD(s->pStatBytesWritten, cbTransfer);
}
}
if (RT_SUCCESS(rc))
{
if (s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE)
{
Assert(cbTransfer <= s->cbTotalTransfer);
/* Reply with the same amount of data as the real drive. */
s->cbTotalTransfer = cbTransfer;
/* The initial buffer end value has been set up based on the total
* transfer size. But the I/O buffer size limits what can actually be
* done in one transfer, so set the actual value of the buffer end. */
s->cbElementaryTransfer = cbTransfer;
if (s->aATAPICmd[0] == SCSI_INQUIRY)
{
/* Make sure that the real drive cannot be identified.
* Motivation: changing the VM configuration should be as
* invisible as possible to the guest. */
Log3(("ATAPI PT inquiry data before (%d): %.*Rhxs\n", cbTransfer, cbTransfer, s->CTXALLSUFF(pbIOBuffer)));
ataSCSIPadStr(s->CTXALLSUFF(pbIOBuffer) + 8, "VBOX", 8);
ataSCSIPadStr(s->CTXALLSUFF(pbIOBuffer) + 16, "CD-ROM", 16);
ataSCSIPadStr(s->CTXALLSUFF(pbIOBuffer) + 32, "1.0", 4);
}
if (cbTransfer)
Log3(("ATAPI PT data read (%d): %.*Rhxs\n", cbTransfer, cbTransfer, s->CTXALLSUFF(pbIOBuffer)));
}
s->iSourceSink = ATAFN_SS_NULL;
atapiCmdOK(s);
}
else
{
if (s->cErrors++ < MAX_LOG_REL_ERRORS)
{
uint8_t u8Cmd = s->aATAPICmd[0];
do
{
/* don't log superflous errors */
if ( rc == VERR_DEV_IO_ERROR
&& ( u8Cmd == SCSI_TEST_UNIT_READY
|| u8Cmd == SCSI_READ_CAPACITY
|| u8Cmd == SCSI_READ_TOC_PMA_ATIP))
break;
LogRel(("AHCI ATA: LUN#%d: CD-ROM passthrough command (%#04x) error %d %Rrc\n", s->iLUN, u8Cmd, abATAPISense[0] & 0x0f, rc));
} while (0);
}
atapiCmdError(s, abATAPISense, sizeof(abATAPISense));
}
return false;
}
static bool atapiReadSectors(AHCIATADevState *s, uint32_t iATAPILBA, uint32_t cSectors, uint32_t cbSector)
{
Assert(cSectors > 0);
s->iATAPILBA = iATAPILBA;
s->cbATAPISector = cbSector;
ataStartTransfer(s, cSectors * cbSector, PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ, true);
return false;
}
static bool atapiReadCapacitySS(AHCIATADevState *s)
{
uint8_t *pbBuf = s->CTXALLSUFF(pbIOBuffer);
Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
Assert(s->cbElementaryTransfer <= 8);
ataH2BE_U32(pbBuf, s->cTotalSectors - 1);
ataH2BE_U32(pbBuf + 4, 2048);
s->iSourceSink = ATAFN_SS_NULL;
atapiCmdOK(s);
return false;
}
static bool atapiReadDiscInformationSS(AHCIATADevState *s)
{
uint8_t *pbBuf = s->CTXALLSUFF(pbIOBuffer);
Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
Assert(s->cbElementaryTransfer <= 34);
memset(pbBuf, '\0', 34);
ataH2BE_U16(pbBuf, 32);
pbBuf[2] = (0 << 4) | (3 << 2) | (2 << 0); /* not erasable, complete session, complete disc */
pbBuf[3] = 1; /* number of first track */
pbBuf[4] = 1; /* number of sessions (LSB) */
pbBuf[5] = 1; /* first track number in last session (LSB) */
pbBuf[6] = 1; /* last track number in last session (LSB) */
pbBuf[7] = (0 << 7) | (0 << 6) | (1 << 5) | (0 << 2) | (0 << 0); /* disc id not valid, disc bar code not valid, unrestricted use, not dirty, not RW medium */
pbBuf[8] = 0; /* disc type = CD-ROM */
pbBuf[9] = 0; /* number of sessions (MSB) */
pbBuf[10] = 0; /* number of sessions (MSB) */
pbBuf[11] = 0; /* number of sessions (MSB) */
ataH2BE_U32(pbBuf + 16, 0x00ffffff); /* last session lead-in start time is not available */
ataH2BE_U32(pbBuf + 20, 0x00ffffff); /* last possible start time for lead-out is not available */
s->iSourceSink = ATAFN_SS_NULL;
atapiCmdOK(s);
return false;
}
static bool atapiReadTrackInformationSS(AHCIATADevState *s)
{
uint8_t *pbBuf = s->CTXALLSUFF(pbIOBuffer);
Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
Assert(s->cbElementaryTransfer <= 36);
/* Accept address/number type of 1 only, and only track 1 exists. */
if ((s->aATAPICmd[1] & 0x03) != 1 || ataBE2H_U32(&s->aATAPICmd[2]) != 1)
{
atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
return false;
}
memset(pbBuf, '\0', 36);
ataH2BE_U16(pbBuf, 34);
pbBuf[2] = 1; /* track number (LSB) */
pbBuf[3] = 1; /* session number (LSB) */
pbBuf[5] = (0 << 5) | (0 << 4) | (4 << 0); /* not damaged, primary copy, data track */
pbBuf[6] = (0 << 7) | (0 << 6) | (0 << 5) | (0 << 6) | (1 << 0); /* not reserved track, not blank, not packet writing, not fixed packet, data mode 1 */
pbBuf[7] = (0 << 1) | (0 << 0); /* last recorded address not valid, next recordable address not valid */
ataH2BE_U32(pbBuf + 8, 0); /* track start address is 0 */
ataH2BE_U32(pbBuf + 24, s->cTotalSectors); /* track size */
pbBuf[32] = 0; /* track number (MSB) */
pbBuf[33] = 0; /* session number (MSB) */
s->iSourceSink = ATAFN_SS_NULL;
atapiCmdOK(s);
return false;
}
static bool atapiGetConfigurationSS(AHCIATADevState *s)
{
uint8_t *pbBuf = s->CTXALLSUFF(pbIOBuffer);
uint16_t u16Sfn = ataBE2H_U16(&s->aATAPICmd[2]);
Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
Assert(s->cbElementaryTransfer <= 32);
/* Accept valid request types only, and only starting feature 0. */
if ((s->aATAPICmd[1] & 0x03) == 3 || u16Sfn != 0)
{
atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
return false;
}
memset(pbBuf, '\0', 32);
ataH2BE_U32(pbBuf, 16);
/** @todo implement switching between CD-ROM and DVD-ROM profile (the only
* way to differentiate them right now is based on the image size). Also
* implement signalling "no current profile" if no medium is loaded. */
ataH2BE_U16(pbBuf + 6, 0x08); /* current profile: read-only CD */
ataH2BE_U16(pbBuf + 8, 0); /* feature 0: list of profiles supported */
pbBuf[10] = (0 << 2) | (1 << 1) | (1 || 0); /* version 0, persistent, current */
pbBuf[11] = 8; /* additional bytes for profiles */
/* The MMC-3 spec says that DVD-ROM read capability should be reported
* before CD-ROM read capability. */
ataH2BE_U16(pbBuf + 12, 0x10); /* profile: read-only DVD */
pbBuf[14] = (0 << 0); /* NOT current profile */
ataH2BE_U16(pbBuf + 16, 0x08); /* profile: read only CD */
pbBuf[18] = (1 << 0); /* current profile */
/* Other profiles we might want to add in the future: 0x40 (BD-ROM) and 0x50 (HDDVD-ROM) */
s->iSourceSink = ATAFN_SS_NULL;
atapiCmdOK(s);
return false;
}
static bool atapiGetEventStatusNotificationSS(AHCIATADevState *s)
{
uint8_t *pbBuf = s->CTX_SUFF(pbIOBuffer);
Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
Assert(s->cbElementaryTransfer <= 8);
if (!(s->aATAPICmd[1] & 1))
{
/* no asynchronous operation supported */
atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
return false;
}
uint32_t OldStatus, NewStatus;
do
{
OldStatus = ASMAtomicReadU32(&s->MediaEventStatus);
NewStatus = ATA_EVENT_STATUS_UNCHANGED;
switch (OldStatus)
{
case ATA_EVENT_STATUS_MEDIA_NEW:
/* mount */
ataH2BE_U16(pbBuf + 0, 6);
pbBuf[2] = 0x04;
pbBuf[3] = 0x5e;
pbBuf[4] = 0x02;
pbBuf[5] = 0x02;
pbBuf[6] = 0x00;
pbBuf[7] = 0x00;
break;
case ATA_EVENT_STATUS_MEDIA_CHANGED:
case ATA_EVENT_STATUS_MEDIA_REMOVED:
/* umount */
ataH2BE_U16(pbBuf + 0, 6);
pbBuf[2] = 0x04;
pbBuf[3] = 0x5e;
pbBuf[4] = 0x03;
pbBuf[5] = 0x00;
pbBuf[6] = 0x00;
pbBuf[7] = 0x00;
if (OldStatus == ATA_EVENT_STATUS_MEDIA_CHANGED)
NewStatus = ATA_EVENT_STATUS_MEDIA_NEW;
break;
case ATA_EVENT_STATUS_UNCHANGED:
default:
ataH2BE_U16(pbBuf + 0, 6);
pbBuf[2] = 0x01;
pbBuf[3] = 0x5e;
pbBuf[4] = 0x00;
pbBuf[5] = 0x00;
pbBuf[6] = 0x00;
pbBuf[7] = 0x00;
break;
}
} while (!ASMAtomicCmpXchgU32(&s->MediaEventStatus, NewStatus, OldStatus));
s->iSourceSink = ATAFN_SS_NULL;
atapiCmdOK(s);
return false;
}
static bool atapiInquirySS(AHCIATADevState *s)
{
uint8_t *pbBuf = s->CTXALLSUFF(pbIOBuffer);
Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
Assert(s->cbElementaryTransfer <= 36);
pbBuf[0] = 0x05; /* CD-ROM */
pbBuf[1] = 0x80; /* removable */
#if 1/*ndef VBOX*/ /** @todo implement MESN + AENC. (async notification on removal and stuff.) */
pbBuf[2] = 0x00; /* ISO */
pbBuf[3] = 0x21; /* ATAPI-2 (XXX: put ATAPI-4 ?) */
#else
pbBuf[2] = 0x00; /* ISO */
pbBuf[3] = 0x91; /* format 1, MESN=1, AENC=9 ??? */
#endif
pbBuf[4] = 31; /* additional length */
pbBuf[5] = 0; /* reserved */
pbBuf[6] = 0; /* reserved */
pbBuf[7] = 0; /* reserved */
ataSCSIPadStr(pbBuf + 8, "VBOX", 8);
ataSCSIPadStr(pbBuf + 16, "CD-ROM", 16);
ataSCSIPadStr(pbBuf + 32, "1.0", 4);
s->iSourceSink = ATAFN_SS_NULL;
atapiCmdOK(s);
return false;
}
static bool atapiModeSenseErrorRecoverySS(AHCIATADevState *s)
{
uint8_t *pbBuf = s->CTXALLSUFF(pbIOBuffer);
Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
Assert(s->cbElementaryTransfer <= 16);
ataH2BE_U16(&pbBuf[0], 16 + 6);
pbBuf[2] = 0x70;
pbBuf[3] = 0;
pbBuf[4] = 0;
pbBuf[5] = 0;
pbBuf[6] = 0;
pbBuf[7] = 0;
pbBuf[8] = 0x01;
pbBuf[9] = 0x06;
pbBuf[10] = 0x00;
pbBuf[11] = 0x05;
pbBuf[12] = 0x00;
pbBuf[13] = 0x00;
pbBuf[14] = 0x00;
pbBuf[15] = 0x00;
s->iSourceSink = ATAFN_SS_NULL;
atapiCmdOK(s);
return false;
}
static bool atapiModeSenseCDStatusSS(AHCIATADevState *s)
{
uint8_t *pbBuf = s->CTXALLSUFF(pbIOBuffer);
Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
Assert(s->cbElementaryTransfer <= 40);
ataH2BE_U16(&pbBuf[0], 38);
pbBuf[2] = 0x70;
pbBuf[3] = 0;
pbBuf[4] = 0;
pbBuf[5] = 0;
pbBuf[6] = 0;
pbBuf[7] = 0;
pbBuf[8] = 0x2a;
pbBuf[9] = 30; /* page length */
pbBuf[10] = 0x08; /* DVD-ROM read support */
pbBuf[11] = 0x00; /* no write support */
/* The following claims we support audio play. This is obviously false,
* but the Linux generic CDROM support makes many features depend on this
* capability. If it's not set, this causes many things to be disabled. */
pbBuf[12] = 0x71; /* multisession support, mode 2 form 1/2 support, audio play */
pbBuf[13] = 0x00; /* no subchannel reads supported */
pbBuf[14] = (1 << 0) | (1 << 3) | (1 << 5); /* lock supported, eject supported, tray type loading mechanism */
if (s->pDrvMount->pfnIsLocked(s->pDrvMount))
pbBuf[14] |= 1 << 1; /* report lock state */
pbBuf[15] = 0; /* no subchannel reads supported, no separate audio volume control, no changer etc. */
ataH2BE_U16(&pbBuf[16], 5632); /* (obsolete) claim 32x speed support */
ataH2BE_U16(&pbBuf[18], 2); /* number of audio volume levels */
ataH2BE_U16(&pbBuf[20], s->cbIOBuffer / _1K); /* buffer size supported in Kbyte */
ataH2BE_U16(&pbBuf[22], 5632); /* (obsolete) current read speed 32x */
pbBuf[24] = 0; /* reserved */
pbBuf[25] = 0; /* reserved for digital audio (see idx 15) */
ataH2BE_U16(&pbBuf[26], 0); /* (obsolete) maximum write speed */
ataH2BE_U16(&pbBuf[28], 0); /* (obsolete) current write speed */
ataH2BE_U16(&pbBuf[30], 0); /* copy management revision supported 0=no CSS */
pbBuf[32] = 0; /* reserved */
pbBuf[33] = 0; /* reserved */
pbBuf[34] = 0; /* reserved */
pbBuf[35] = 1; /* rotation control CAV */
ataH2BE_U16(&pbBuf[36], 0); /* current write speed */
ataH2BE_U16(&pbBuf[38], 0); /* number of write speed performance descriptors */
s->iSourceSink = ATAFN_SS_NULL;
atapiCmdOK(s);
return false;
}
static bool atapiRequestSenseSS(AHCIATADevState *s)
{
uint8_t *pbBuf = s->CTXALLSUFF(pbIOBuffer);
Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
memset(pbBuf, '\0', s->cbElementaryTransfer);
memcpy(pbBuf, s->abATAPISense, RT_MIN(s->cbElementaryTransfer, sizeof(s->abATAPISense)));
s->iSourceSink = ATAFN_SS_NULL;
atapiCmdOK(s);
return false;
}
static bool atapiMechanismStatusSS(AHCIATADevState *s)
{
uint8_t *pbBuf = s->CTXALLSUFF(pbIOBuffer);
Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
Assert(s->cbElementaryTransfer <= 8);
ataH2BE_U16(pbBuf, 0);
/* no current LBA */
pbBuf[2] = 0;
pbBuf[3] = 0;
pbBuf[4] = 0;
pbBuf[5] = 1;
ataH2BE_U16(pbBuf + 6, 0);
s->iSourceSink = ATAFN_SS_NULL;
atapiCmdOK(s);
return false;
}
static bool atapiReadTOCNormalSS(AHCIATADevState *s)
{
uint8_t *pbBuf = s->CTXALLSUFF(pbIOBuffer), *q, iStartTrack;
bool fMSF;
uint32_t cbSize;
Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
fMSF = (s->aATAPICmd[1] >> 1) & 1;
iStartTrack = s->aATAPICmd[6];
if (iStartTrack > 1 && iStartTrack != 0xaa)
{
atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
return false;
}
q = pbBuf + 2;
*q++ = 1; /* first session */
*q++ = 1; /* last session */
if (iStartTrack <= 1)
{
*q++ = 0; /* reserved */
*q++ = 0x14; /* ADR, control */
*q++ = 1; /* track number */
*q++ = 0; /* reserved */
if (fMSF)
{
*q++ = 0; /* reserved */
ataLBA2MSF(q, 0);
q += 3;
}
else
{
/* sector 0 */
ataH2BE_U32(q, 0);
q += 4;
}
}
/* lead out track */
*q++ = 0; /* reserved */
*q++ = 0x14; /* ADR, control */
*q++ = 0xaa; /* track number */
*q++ = 0; /* reserved */
if (fMSF)
{
*q++ = 0; /* reserved */
ataLBA2MSF(q, s->cTotalSectors);
q += 3;
}
else
{
ataH2BE_U32(q, s->cTotalSectors);
q += 4;
}
cbSize = q - pbBuf;
ataH2BE_U16(pbBuf, cbSize - 2);
if (cbSize < s->cbTotalTransfer)
s->cbTotalTransfer = cbSize;
s->iSourceSink = ATAFN_SS_NULL;
atapiCmdOK(s);
return false;
}
static bool atapiReadTOCMultiSS(AHCIATADevState *s)
{
uint8_t *pbBuf = s->CTXALLSUFF(pbIOBuffer);
bool fMSF;
Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
Assert(s->cbElementaryTransfer <= 12);
fMSF = (s->aATAPICmd[1] >> 1) & 1;
/* multi session: only a single session defined */
/** @todo double-check this stuff against what a real drive says for a CD-ROM (not a CD-R) with only a single data session. Maybe solve the problem with "cdrdao read-toc" not being able to figure out whether numbers are in BCD or hex. */
memset(pbBuf, 0, 12);
pbBuf[1] = 0x0a;
pbBuf[2] = 0x01;
pbBuf[3] = 0x01;
pbBuf[5] = 0x14; /* ADR, control */
pbBuf[6] = 1; /* first track in last complete session */
if (fMSF)
{
pbBuf[8] = 0; /* reserved */
ataLBA2MSF(&pbBuf[9], 0);
}
else
{
/* sector 0 */
ataH2BE_U32(pbBuf + 8, 0);
}
s->iSourceSink = ATAFN_SS_NULL;
atapiCmdOK(s);
return false;
}
static bool atapiReadTOCRawSS(AHCIATADevState *s)
{
uint8_t *pbBuf = s->CTXALLSUFF(pbIOBuffer), *q, iStartTrack;
bool fMSF;
uint32_t cbSize;
Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
fMSF = (s->aATAPICmd[1] >> 1) & 1;
iStartTrack = s->aATAPICmd[6];
q = pbBuf + 2;
*q++ = 1; /* first session */
*q++ = 1; /* last session */
*q++ = 1; /* session number */
*q++ = 0x14; /* data track */
*q++ = 0; /* track number */
*q++ = 0xa0; /* first track in program area */
*q++ = 0; /* min */
*q++ = 0; /* sec */
*q++ = 0; /* frame */
*q++ = 0;
*q++ = 1; /* first track */
*q++ = 0x00; /* disk type CD-DA or CD data */
*q++ = 0;
*q++ = 1; /* session number */
*q++ = 0x14; /* data track */
*q++ = 0; /* track number */
*q++ = 0xa1; /* last track in program area */
*q++ = 0; /* min */
*q++ = 0; /* sec */
*q++ = 0; /* frame */
*q++ = 0;
*q++ = 1; /* last track */
*q++ = 0;
*q++ = 0;
*q++ = 1; /* session number */
*q++ = 0x14; /* data track */
*q++ = 0; /* track number */
*q++ = 0xa2; /* lead-out */
*q++ = 0; /* min */
*q++ = 0; /* sec */
*q++ = 0; /* frame */
if (fMSF)
{
*q++ = 0; /* reserved */
ataLBA2MSF(q, s->cTotalSectors);
q += 3;
}
else
{
ataH2BE_U32(q, s->cTotalSectors);
q += 4;
}
*q++ = 1; /* session number */
*q++ = 0x14; /* ADR, control */
*q++ = 0; /* track number */
*q++ = 1; /* point */
*q++ = 0; /* min */
*q++ = 0; /* sec */
*q++ = 0; /* frame */
if (fMSF)
{
*q++ = 0; /* reserved */
ataLBA2MSF(q, 0);
q += 3;
}
else
{
/* sector 0 */
ataH2BE_U32(q, 0);
q += 4;
}
cbSize = q - pbBuf;
ataH2BE_U16(pbBuf, cbSize - 2);
if (cbSize < s->cbTotalTransfer)
s->cbTotalTransfer = cbSize;
s->iSourceSink = ATAFN_SS_NULL;
atapiCmdOK(s);
return false;
}
static void atapiParseCmdVirtualATAPI(AHCIATADevState *s)
{
const uint8_t *pbPacket;
uint8_t *pbBuf;
uint32_t cbMax;
pbPacket = s->aATAPICmd;
pbBuf = s->CTXALLSUFF(pbIOBuffer);
switch (pbPacket[0])
{
case SCSI_TEST_UNIT_READY:
if (s->cNotifiedMediaChange > 0)
{
if (s->cNotifiedMediaChange-- > 2)
atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
else
atapiCmdErrorSimple(s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
}
else if (s->pDrvMount->pfnIsMounted(s->pDrvMount))
atapiCmdOK(s);
else
atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
break;
case SCSI_GET_EVENT_STATUS_NOTIFICATION:
cbMax = ataBE2H_U16(pbPacket + 7);
ataStartTransfer(s, RT_MIN(cbMax, 8), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_GET_EVENT_STATUS_NOTIFICATION, true);
break;
case SCSI_MODE_SENSE_10:
{
uint8_t uPageControl, uPageCode;
cbMax = ataBE2H_U16(pbPacket + 7);
uPageControl = pbPacket[2] >> 6;
uPageCode = pbPacket[2] & 0x3f;
switch (uPageControl)
{
case SCSI_PAGECONTROL_CURRENT:
switch (uPageCode)
{
case SCSI_MODEPAGE_ERROR_RECOVERY:
ataStartTransfer(s, RT_MIN(cbMax, 16), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_MODE_SENSE_ERROR_RECOVERY, true);
break;
case SCSI_MODEPAGE_CD_STATUS:
ataStartTransfer(s, RT_MIN(cbMax, 40), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_MODE_SENSE_CD_STATUS, true);
break;
default:
goto error_cmd;
}
break;
case SCSI_PAGECONTROL_CHANGEABLE:
goto error_cmd;
case SCSI_PAGECONTROL_DEFAULT:
goto error_cmd;
default:
case SCSI_PAGECONTROL_SAVED:
atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_SAVING_PARAMETERS_NOT_SUPPORTED);
break;
}
}
break;
case SCSI_REQUEST_SENSE:
cbMax = pbPacket[4];
ataStartTransfer(s, RT_MIN(cbMax, 18), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_REQUEST_SENSE, true);
break;
case SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL:
if (s->pDrvMount->pfnIsMounted(s->pDrvMount))
{
if (pbPacket[4] & 1)
s->pDrvMount->pfnLock(s->pDrvMount);
else
s->pDrvMount->pfnUnlock(s->pDrvMount);
atapiCmdOK(s);
}
else
atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
break;
case SCSI_READ_10:
case SCSI_READ_12:
{
uint32_t cSectors, iATAPILBA;
if (s->cNotifiedMediaChange > 0)
{
s->cNotifiedMediaChange-- ;
atapiCmdErrorSimple(s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
break;
}
else if (!s->pDrvMount->pfnIsMounted(s->pDrvMount))
{
atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
break;
}
if (pbPacket[0] == SCSI_READ_10)
cSectors = ataBE2H_U16(pbPacket + 7);
else
cSectors = ataBE2H_U32(pbPacket + 6);
iATAPILBA = ataBE2H_U32(pbPacket + 2);
if (cSectors == 0)
{
atapiCmdOK(s);
break;
}
if ((uint64_t)iATAPILBA + cSectors > s->cTotalSectors)
{
/* Rate limited logging, one log line per second. For
* guests that insist on reading from places outside the
* valid area this often generates too many release log
* entries otherwise. */
static uint64_t uLastLogTS = 0;
if (RTTimeMilliTS() >= uLastLogTS + 1000)
{
LogRel(("AHCI ATA: LUN#%d: CD-ROM block number %Ld invalid (READ)\n", s->iLUN, (uint64_t)iATAPILBA + cSectors));
uLastLogTS = RTTimeMilliTS();
}
atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_LOGICAL_BLOCK_OOR);
break;
}
atapiReadSectors(s, iATAPILBA, cSectors, 2048);
}
break;
case SCSI_READ_CD:
{
uint32_t cSectors, iATAPILBA;
if (s->cNotifiedMediaChange > 0)
{
s->cNotifiedMediaChange-- ;
atapiCmdErrorSimple(s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
break;
}
else if (!s->pDrvMount->pfnIsMounted(s->pDrvMount))
{
atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
break;
}
cSectors = (pbPacket[6] << 16) | (pbPacket[7] << 8) | pbPacket[8];
iATAPILBA = ataBE2H_U32(pbPacket + 2);
if (cSectors == 0)
{
atapiCmdOK(s);
break;
}
if ((uint64_t)iATAPILBA + cSectors > s->cTotalSectors)
{
/* Rate limited logging, one log line per second. For
* guests that insist on reading from places outside the
* valid area this often generates too many release log
* entries otherwise. */
static uint64_t uLastLogTS = 0;
if (RTTimeMilliTS() >= uLastLogTS + 1000)
{
LogRel(("AHCI ATA: LUN#%d: CD-ROM block number %Ld invalid (READ CD)\n", s->iLUN, (uint64_t)iATAPILBA + cSectors));
uLastLogTS = RTTimeMilliTS();
}
atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_LOGICAL_BLOCK_OOR);
break;
}
switch (pbPacket[9] & 0xf8)
{
case 0x00:
/* nothing */
atapiCmdOK(s);
break;
case 0x10:
/* normal read */
atapiReadSectors(s, iATAPILBA, cSectors, 2048);
break;
case 0xf8:
/* read all data */
atapiReadSectors(s, iATAPILBA, cSectors, 2352);
break;
default:
LogRel(("AHCI ATA: LUN#%d: CD-ROM sector format not supported\n", s->iLUN));
atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
break;
}
}
break;
case SCSI_SEEK_10:
{
uint32_t iATAPILBA;
if (s->cNotifiedMediaChange > 0)
{
s->cNotifiedMediaChange-- ;
atapiCmdErrorSimple(s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
break;
}
else if (!s->pDrvMount->pfnIsMounted(s->pDrvMount))
{
atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
break;
}
iATAPILBA = ataBE2H_U32(pbPacket + 2);
if (iATAPILBA > s->cTotalSectors)
{
/* Rate limited logging, one log line per second. For
* guests that insist on seeking to places outside the
* valid area this often generates too many release log
* entries otherwise. */
static uint64_t uLastLogTS = 0;
if (RTTimeMilliTS() >= uLastLogTS + 1000)
{
LogRel(("AHCI ATA: LUN#%d: CD-ROM block number %Ld invalid (SEEK)\n", s->iLUN, (uint64_t)iATAPILBA));
uLastLogTS = RTTimeMilliTS();
}
atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_LOGICAL_BLOCK_OOR);
break;
}
atapiCmdOK(s);
ataSetStatus(s, ATA_STAT_SEEK); /* Linux expects this. */
}
break;
case SCSI_START_STOP_UNIT:
{
int rc = VINF_SUCCESS;
switch (pbPacket[4] & 3)
{
case 0: /* 00 - Stop motor */
case 1: /* 01 - Start motor */
break;
case 2: /* 10 - Eject media */
/* This must be done from EMT. */
{
PAHCIATACONTROLLER pCtl = ATADEVSTATE_2_CONTROLLER(s);
PPDMDEVINS pDevIns = ATADEVSTATE_2_DEVINS(s);
PDMCritSectLeave(&pCtl->lock);
rc = VMR3ReqCallWait(PDMDevHlpGetVM(pDevIns), VMCPUID_ANY,
(PFNRT)s->pDrvMount->pfnUnmount, 2, s->pDrvMount, false);
AssertReleaseRC(rc);
{
STAM_PROFILE_START(&pCtl->StatLockWait, a);
PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
}
}
break;
case 3: /* 11 - Load media */
/** @todo rc = s->pDrvMount->pfnLoadMedia(s->pDrvMount) */
break;
}
if (RT_SUCCESS(rc))
atapiCmdOK(s);
else
atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIA_LOAD_OR_EJECT_FAILED);
}
break;
case SCSI_MECHANISM_STATUS:
{
cbMax = ataBE2H_U16(pbPacket + 8);
ataStartTransfer(s, RT_MIN(cbMax, 8), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_MECHANISM_STATUS, true);
}
break;
case SCSI_READ_TOC_PMA_ATIP:
{
uint8_t format;
if (s->cNotifiedMediaChange > 0)
{
s->cNotifiedMediaChange-- ;
atapiCmdErrorSimple(s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
break;
}
else if (!s->pDrvMount->pfnIsMounted(s->pDrvMount))
{
atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
break;
}
cbMax = ataBE2H_U16(pbPacket + 7);
/* SCSI MMC-3 spec says format is at offset 2 (lower 4 bits),
* but Linux kernel uses offset 9 (topmost 2 bits). Hope that
* the other field is clear... */
format = (pbPacket[2] & 0xf) | (pbPacket[9] >> 6);
switch (format)
{
case 0:
ataStartTransfer(s, cbMax, PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_TOC_NORMAL, true);
break;
case 1:
ataStartTransfer(s, RT_MIN(cbMax, 12), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_TOC_MULTI, true);
break;
case 2:
ataStartTransfer(s, cbMax, PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_TOC_RAW, true);
break;
default:
error_cmd:
atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
break;
}
}
break;
case SCSI_READ_CAPACITY:
if (s->cNotifiedMediaChange > 0)
{
s->cNotifiedMediaChange-- ;
atapiCmdErrorSimple(s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
break;
}
else if (!s->pDrvMount->pfnIsMounted(s->pDrvMount))
{
atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
break;
}
ataStartTransfer(s, 8, PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_CAPACITY, true);
break;
case SCSI_READ_DISC_INFORMATION:
if (s->cNotifiedMediaChange > 0)
{
s->cNotifiedMediaChange-- ;
atapiCmdErrorSimple(s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
break;
}
else if (!s->pDrvMount->pfnIsMounted(s->pDrvMount))
{
atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
break;
}
cbMax = ataBE2H_U16(pbPacket + 7);
ataStartTransfer(s, RT_MIN(cbMax, 34), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_DISC_INFORMATION, true);
break;
case SCSI_READ_TRACK_INFORMATION:
if (s->cNotifiedMediaChange > 0)
{
s->cNotifiedMediaChange-- ;
atapiCmdErrorSimple(s, SCSI_SENSE_UNIT_ATTENTION, SCSI_ASC_MEDIUM_MAY_HAVE_CHANGED); /* media changed */
break;
}
else if (!s->pDrvMount->pfnIsMounted(s->pDrvMount))
{
atapiCmdErrorSimple(s, SCSI_SENSE_NOT_READY, SCSI_ASC_MEDIUM_NOT_PRESENT);
break;
}
cbMax = ataBE2H_U16(pbPacket + 7);
ataStartTransfer(s, RT_MIN(cbMax, 36), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_READ_TRACK_INFORMATION, true);
break;
case SCSI_GET_CONFIGURATION:
/* No media change stuff here, it can confuse Linux guests. */
cbMax = ataBE2H_U16(pbPacket + 7);
ataStartTransfer(s, RT_MIN(cbMax, 32), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_GET_CONFIGURATION, true);
break;
case SCSI_INQUIRY:
cbMax = ataBE2H_U16(pbPacket + 3);
ataStartTransfer(s, RT_MIN(cbMax, 36), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_INQUIRY, true);
break;
default:
atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
break;
}
}
/*
* Parse ATAPI commands, passing them directly to the CD/DVD drive.
*/
static void atapiParseCmdPassthrough(AHCIATADevState *s)
{
const uint8_t *pbPacket;
uint8_t *pbBuf;
uint32_t cSectors, iATAPILBA;
uint32_t cbTransfer = 0;
PDMBLOCKTXDIR uTxDir = PDMBLOCKTXDIR_NONE;
pbPacket = s->aATAPICmd;
pbBuf = s->CTXALLSUFF(pbIOBuffer);
switch (pbPacket[0])
{
case SCSI_BLANK:
goto sendcmd;
case SCSI_CLOSE_TRACK_SESSION:
goto sendcmd;
case SCSI_ERASE_10:
iATAPILBA = ataBE2H_U32(pbPacket + 2);
cbTransfer = ataBE2H_U16(pbPacket + 7);
Log2(("ATAPI PT: lba %d\n", iATAPILBA));
uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
goto sendcmd;
case SCSI_FORMAT_UNIT:
cbTransfer = s->uATARegLCyl | (s->uATARegHCyl << 8); /* use ATAPI transfer length */
uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
goto sendcmd;
case SCSI_GET_CONFIGURATION:
cbTransfer = ataBE2H_U16(pbPacket + 7);
uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
goto sendcmd;
case SCSI_GET_EVENT_STATUS_NOTIFICATION:
cbTransfer = ataBE2H_U16(pbPacket + 7);
if (ASMAtomicReadU32(&s->MediaEventStatus) != ATA_EVENT_STATUS_UNCHANGED)
{
ataStartTransfer(s, RT_MIN(cbTransfer, 8), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_GET_EVENT_STATUS_NOTIFICATION, true);
break;
}
uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
goto sendcmd;
case SCSI_GET_PERFORMANCE:
cbTransfer = s->uATARegLCyl | (s->uATARegHCyl << 8); /* use ATAPI transfer length */
uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
goto sendcmd;
case SCSI_INQUIRY:
cbTransfer = ataBE2H_U16(pbPacket + 3);
uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
goto sendcmd;
case SCSI_LOAD_UNLOAD_MEDIUM:
goto sendcmd;
case SCSI_MECHANISM_STATUS:
cbTransfer = ataBE2H_U16(pbPacket + 8);
uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
goto sendcmd;
case SCSI_MODE_SELECT_10:
cbTransfer = ataBE2H_U16(pbPacket + 7);
uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
goto sendcmd;
case SCSI_MODE_SENSE_10:
cbTransfer = ataBE2H_U16(pbPacket + 7);
uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
goto sendcmd;
case SCSI_PAUSE_RESUME:
goto sendcmd;
case SCSI_PLAY_AUDIO_10:
goto sendcmd;
case SCSI_PLAY_AUDIO_12:
goto sendcmd;
case SCSI_PLAY_AUDIO_MSF:
goto sendcmd;
case SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL:
/** @todo do not forget to unlock when a VM is shut down */
goto sendcmd;
case SCSI_READ_10:
iATAPILBA = ataBE2H_U32(pbPacket + 2);
cSectors = ataBE2H_U16(pbPacket + 7);
Log2(("ATAPI PT: lba %d sectors %d\n", iATAPILBA, cSectors));
s->cbATAPISector = 2048; /**< @todo this size is not always correct */
cbTransfer = cSectors * s->cbATAPISector;
uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
goto sendcmd;
case SCSI_READ_12:
iATAPILBA = ataBE2H_U32(pbPacket + 2);
cSectors = ataBE2H_U32(pbPacket + 6);
Log2(("ATAPI PT: lba %d sectors %d\n", iATAPILBA, cSectors));
s->cbATAPISector = 2048; /**< @todo this size is not always correct */
cbTransfer = cSectors * s->cbATAPISector;
uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
goto sendcmd;
case SCSI_READ_BUFFER:
cbTransfer = ataBE2H_U24(pbPacket + 6);
uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
goto sendcmd;
case SCSI_READ_BUFFER_CAPACITY:
cbTransfer = ataBE2H_U16(pbPacket + 7);
uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
goto sendcmd;
case SCSI_READ_CAPACITY:
cbTransfer = 8;
uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
goto sendcmd;
case SCSI_READ_CD:
s->cbATAPISector = 2048; /**< @todo this size is not always correct */
cbTransfer = ataBE2H_U24(pbPacket + 6) / s->cbATAPISector * s->cbATAPISector;
uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
goto sendcmd;
case SCSI_READ_CD_MSF:
cSectors = ataMSF2LBA(pbPacket + 6) - ataMSF2LBA(pbPacket + 3);
if (cSectors > 32)
cSectors = 32; /* Limit transfer size to 64~74K. Safety first. In any case this can only harm software doing CDDA extraction. */
s->cbATAPISector = 2048; /**< @todo this size is not always correct */
cbTransfer = cSectors * s->cbATAPISector;
uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
goto sendcmd;
case SCSI_READ_DISC_INFORMATION:
cbTransfer = ataBE2H_U16(pbPacket + 7);
uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
goto sendcmd;
case SCSI_READ_DVD_STRUCTURE:
cbTransfer = ataBE2H_U16(pbPacket + 8);
uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
goto sendcmd;
case SCSI_READ_FORMAT_CAPACITIES:
cbTransfer = ataBE2H_U16(pbPacket + 7);
uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
goto sendcmd;
case SCSI_READ_SUBCHANNEL:
cbTransfer = ataBE2H_U16(pbPacket + 7);
uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
goto sendcmd;
case SCSI_READ_TOC_PMA_ATIP:
cbTransfer = ataBE2H_U16(pbPacket + 7);
uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
goto sendcmd;
case SCSI_READ_TRACK_INFORMATION:
cbTransfer = ataBE2H_U16(pbPacket + 7);
uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
goto sendcmd;
case SCSI_REPAIR_TRACK:
goto sendcmd;
case SCSI_REPORT_KEY:
cbTransfer = ataBE2H_U16(pbPacket + 8);
uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
goto sendcmd;
case SCSI_REQUEST_SENSE:
cbTransfer = pbPacket[4];
if ((s->abATAPISense[2] & 0x0f) != SCSI_SENSE_NONE)
{
ataStartTransfer(s, RT_MIN(cbTransfer, 18), PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_ATAPI_CMD, ATAFN_SS_ATAPI_REQUEST_SENSE, true);
break;
}
uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
goto sendcmd;
case SCSI_RESERVE_TRACK:
goto sendcmd;
case SCSI_SCAN:
goto sendcmd;
case SCSI_SEEK_10:
goto sendcmd;
case SCSI_SEND_CUE_SHEET:
cbTransfer = ataBE2H_U24(pbPacket + 6);
uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
goto sendcmd;
case SCSI_SEND_DVD_STRUCTURE:
cbTransfer = ataBE2H_U16(pbPacket + 8);
uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
goto sendcmd;
case SCSI_SEND_EVENT:
cbTransfer = ataBE2H_U16(pbPacket + 8);
uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
goto sendcmd;
case SCSI_SEND_KEY:
cbTransfer = ataBE2H_U16(pbPacket + 8);
uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
goto sendcmd;
case SCSI_SEND_OPC_INFORMATION:
cbTransfer = ataBE2H_U16(pbPacket + 7);
uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
goto sendcmd;
case SCSI_SET_CD_SPEED:
goto sendcmd;
case SCSI_SET_READ_AHEAD:
goto sendcmd;
case SCSI_SET_STREAMING:
cbTransfer = ataBE2H_U16(pbPacket + 9);
uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
goto sendcmd;
case SCSI_START_STOP_UNIT:
goto sendcmd;
case SCSI_STOP_PLAY_SCAN:
goto sendcmd;
case SCSI_SYNCHRONIZE_CACHE:
goto sendcmd;
case SCSI_TEST_UNIT_READY:
goto sendcmd;
case SCSI_VERIFY_10:
goto sendcmd;
case SCSI_WRITE_10:
iATAPILBA = ataBE2H_U32(pbPacket + 2);
cSectors = ataBE2H_U16(pbPacket + 7);
Log2(("ATAPI PT: lba %d sectors %d\n", iATAPILBA, cSectors));
#if 0
/* The sector size is determined by the async I/O thread. */
s->cbATAPISector = 0;
/* Preliminary, will be corrected once the sector size is known. */
cbTransfer = cSectors;
#else
s->cbATAPISector = 2048; /**< @todo this size is not always correct */
cbTransfer = cSectors * s->cbATAPISector;
#endif
uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
goto sendcmd;
case SCSI_WRITE_12:
iATAPILBA = ataBE2H_U32(pbPacket + 2);
cSectors = ataBE2H_U32(pbPacket + 6);
Log2(("ATAPI PT: lba %d sectors %d\n", iATAPILBA, cSectors));
#if 0
/* The sector size is determined by the async I/O thread. */
s->cbATAPISector = 0;
/* Preliminary, will be corrected once the sector size is known. */
cbTransfer = cSectors;
#else
s->cbATAPISector = 2048; /**< @todo this size is not always correct */
cbTransfer = cSectors * s->cbATAPISector;
#endif
uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
goto sendcmd;
case SCSI_WRITE_AND_VERIFY_10:
iATAPILBA = ataBE2H_U32(pbPacket + 2);
cSectors = ataBE2H_U16(pbPacket + 7);
Log2(("ATAPI PT: lba %d sectors %d\n", iATAPILBA, cSectors));
/* The sector size is determined by the async I/O thread. */
s->cbATAPISector = 0;
/* Preliminary, will be corrected once the sector size is known. */
cbTransfer = cSectors;
uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
goto sendcmd;
case SCSI_WRITE_BUFFER:
switch (pbPacket[1] & 0x1f)
{
case 0x04: /* download microcode */
case 0x05: /* download microcode and save */
case 0x06: /* download microcode with offsets */
case 0x07: /* download microcode with offsets and save */
case 0x0e: /* download microcode with offsets and defer activation */
case 0x0f: /* activate deferred microcode */
LogRel(("AHCI ATA: LUN#%d: CD-ROM passthrough command attempted to update firmware, blocked\n", s->iLUN));
atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
break;
default:
cbTransfer = ataBE2H_U16(pbPacket + 6);
uTxDir = PDMBLOCKTXDIR_TO_DEVICE;
goto sendcmd;
}
break;
case SCSI_REPORT_LUNS: /* Not part of MMC-3, but used by Windows. */
cbTransfer = ataBE2H_U32(pbPacket + 6);
uTxDir = PDMBLOCKTXDIR_FROM_DEVICE;
goto sendcmd;
case SCSI_REZERO_UNIT:
/* Obsolete command used by cdrecord. What else would one expect?
* This command is not sent to the drive, it is handled internally,
* as the Linux kernel doesn't like it (message "scsi: unknown
* opcode 0x01" in syslog) and replies with a sense code of 0,
* which sends cdrecord to an endless loop. */
atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
break;
default:
LogRel(("AHCI ATA: LUN#%d: passthrough unimplemented for command %#x\n", s->iLUN, pbPacket[0]));
atapiCmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
break;
sendcmd:
/* Send a command to the drive, passing data in/out as required. */
Log2(("ATAPI PT: max size %d\n", cbTransfer));
Assert(cbTransfer <= s->cbIOBuffer);
if (cbTransfer == 0)
uTxDir = PDMBLOCKTXDIR_NONE;
ataStartTransfer(s, cbTransfer, uTxDir, ATAFN_BT_ATAPI_PASSTHROUGH_CMD, ATAFN_SS_ATAPI_PASSTHROUGH, true);
}
}
static void atapiParseCmd(AHCIATADevState *s)
{
const uint8_t *pbPacket;
pbPacket = s->aATAPICmd;
#ifdef DEBUG
Log(("%s: LUN#%d DMA=%d CMD=%#04x \"%s\"\n", __FUNCTION__, s->iLUN, s->fDMA, pbPacket[0], SCSICmdText(pbPacket[0])));
#else /* !DEBUG */
Log(("%s: LUN#%d DMA=%d CMD=%#04x\n", __FUNCTION__, s->iLUN, s->fDMA, pbPacket[0]));
#endif /* !DEBUG */
Log2(("%s: limit=%#x packet: %.*Rhxs\n", __FUNCTION__, s->uATARegLCyl | (s->uATARegHCyl << 8), ATAPI_PACKET_SIZE, pbPacket));
if (s->fATAPIPassthrough)
atapiParseCmdPassthrough(s);
else
atapiParseCmdVirtualATAPI(s);
}
static bool ataPacketSS(AHCIATADevState *s)
{
s->fDMA = !!(s->uATARegFeature & 1);
memcpy(s->aATAPICmd, s->CTXALLSUFF(pbIOBuffer), ATAPI_PACKET_SIZE);
s->uTxDir = PDMBLOCKTXDIR_NONE;
s->cbTotalTransfer = 0;
s->cbElementaryTransfer = 0;
atapiParseCmd(s);
return false;
}
#if 0
/**
* SCSI_GET_EVENT_STATUS_NOTIFICATION should return "medium removed" event
* from now on, regardless if there was a medium inserted or not.
*/
static void ataMediumRemoved(AHCIATADevState *s)
{
ASMAtomicWriteU32(&s->MediaEventStatus, ATA_EVENT_STATUS_MEDIA_REMOVED);
}
/**
* SCSI_GET_EVENT_STATUS_NOTIFICATION should return "medium inserted". If
* there was already a medium inserted, don't forget to send the "medium
* removed" event first.
*/
static void ataMediumInserted(AHCIATADevState *s)
{
uint32_t OldStatus, NewStatus;
do
{
OldStatus = ASMAtomicReadU32(&s->MediaEventStatus);
switch (OldStatus)
{
case ATA_EVENT_STATUS_MEDIA_CHANGED:
case ATA_EVENT_STATUS_MEDIA_REMOVED:
/* no change, we will send "medium removed" + "medium inserted" */
NewStatus = ATA_EVENT_STATUS_MEDIA_CHANGED;
break;
default:
NewStatus = ATA_EVENT_STATUS_MEDIA_NEW;
break;
}
} while (!ASMAtomicCmpXchgU32(&s->MediaEventStatus, NewStatus, OldStatus));
}
/**
* Called when a media is mounted.
*
* @param pInterface Pointer to the interface structure containing the called function pointer.
*/
static DECLCALLBACK(void) ataMountNotify(PPDMIMOUNTNOTIFY pInterface)
{
AHCIATADevState *pIf = PDMIMOUNTNOTIFY_2_ATASTATE(pInterface);
Log(("%s: changing LUN#%d\n", __FUNCTION__, pIf->iLUN));
/* Ignore the call if we're called while being attached. */
if (!pIf->pDrvBlock)
return;
LogRel(("ATA: LUN#%d: CD/DVD, total number of sectors %Ld, passthrough unchanged\n", pIf->iLUN, pIf->cTotalSectors));
if (pIf->fATAPI)
pIf->cTotalSectors = pIf->pDrvBlock->pfnGetSize(pIf->pDrvBlock) / 2048;
else
pIf->cTotalSectors = pIf->pDrvBlock->pfnGetSize(pIf->pDrvBlock) / 512;
/* Report media changed in TEST UNIT and other (probably incorrect) places. */
if (pIf->cNotifiedMediaChange < 2)
pIf->cNotifiedMediaChange = 2;
ataMediumInserted(pIf);
}
/**
* Called when a media is unmounted
* @param pInterface Pointer to the interface structure containing the called function pointer.
*/
static DECLCALLBACK(void) ataUnmountNotify(PPDMIMOUNTNOTIFY pInterface)
{
AHCIATADevState *pIf = PDMIMOUNTNOTIFY_2_ATASTATE(pInterface);
Log(("%s:\n", __FUNCTION__));
pIf->cTotalSectors = 0;
/*
* Whatever I do, XP will not use the GET MEDIA STATUS nor the EVENT stuff.
* However, it will respond to TEST UNIT with a 0x6 0x28 (media changed) sense code.
* So, we'll give it 4 TEST UNIT command to catch up, two which the media is not
* present and 2 in which it is changed.
*/
pIf->cNotifiedMediaChange = 4;
ataMediumRemoved(pIf);
}
#endif
static void ataPacketBT(AHCIATADevState *s)
{
s->cbElementaryTransfer = s->cbTotalTransfer;
s->uATARegNSector = (s->uATARegNSector & ~7) | ATAPI_INT_REASON_CD;
Log2(("%s: interrupt reason %#04x\n", __FUNCTION__, s->uATARegNSector));
ataSetStatusValue(s, ATA_STAT_READY);
}
static void ataResetDevice(AHCIATADevState *s)
{
s->cMultSectors = ATA_MAX_MULT_SECTORS;
s->cNotifiedMediaChange = 0;
ASMAtomicWriteU32(&s->MediaEventStatus, ATA_EVENT_STATUS_UNCHANGED);
ataUnsetIRQ(s);
s->uATARegSelect = 0x20;
ataSetStatusValue(s, ATA_STAT_READY);
ataSetSignature(s);
s->cbTotalTransfer = 0;
s->cbElementaryTransfer = 0;
s->iIOBufferPIODataStart = 0;
s->iIOBufferPIODataEnd = 0;
s->iBeginTransfer = ATAFN_BT_NULL;
s->iSourceSink = ATAFN_SS_NULL;
s->fATAPITransfer = false;
s->uATATransferMode = ATA_MODE_UDMA | 2; /* AHCI supports only up to UDMA2 */
s->uATARegFeature = 0;
}
static bool ataExecuteDeviceDiagnosticSS(AHCIATADevState *s)
{
ataSetSignature(s);
if (s->fATAPI)
ataSetStatusValue(s, 0); /* NOTE: READY is _not_ set */
else
ataSetStatusValue(s, ATA_STAT_READY);
s->uATARegError = 0x01;
return false;
}
static void ataParseCmd(AHCIATADevState *s, uint8_t cmd)
{
#ifdef DEBUG
Log(("%s: LUN#%d CMD=%#04x \"%s\"\n", __FUNCTION__, s->iLUN, cmd, ATACmdText(cmd)));
#else /* !DEBUG */
Log(("%s: LUN#%d CMD=%#04x\n", __FUNCTION__, s->iLUN, cmd));
#endif /* !DEBUG */
s->fLBA48 = false;
s->fDMA = false;
if (cmd == ATA_IDLE_IMMEDIATE)
{
/* Detect Linux timeout recovery, first tries IDLE IMMEDIATE (which
* would overwrite the failing command unfortunately), then RESET. */
int32_t uCmdWait = -1;
uint64_t uNow = RTTimeNanoTS();
if (s->u64CmdTS)
uCmdWait = (uNow - s->u64CmdTS) / 1000;
LogRel(("AHCI ATA: LUN#%d: IDLE IMMEDIATE, CmdIf=%#04x (%d usec ago)\n",
s->iLUN, s->uATARegCommand, uCmdWait));
}
s->uATARegCommand = cmd;
switch (cmd)
{
case ATA_IDENTIFY_DEVICE:
if (s->pDrvBlock && !s->fATAPI)
ataStartTransfer(s, 512, PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_NULL, ATAFN_SS_IDENTIFY, false);
else
{
if (s->fATAPI)
ataSetSignature(s);
ataCmdError(s, ABRT_ERR);
ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
}
break;
case ATA_INITIALIZE_DEVICE_PARAMETERS:
case ATA_RECALIBRATE:
ataCmdOK(s, ATA_STAT_SEEK);
ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
break;
case ATA_SET_MULTIPLE_MODE:
if ( s->uATARegNSector != 0
&& ( s->uATARegNSector > ATA_MAX_MULT_SECTORS
|| (s->uATARegNSector & (s->uATARegNSector - 1)) != 0))
{
ataCmdError(s, ABRT_ERR);
}
else
{
Log2(("%s: set multi sector count to %d\n", __FUNCTION__, s->uATARegNSector));
s->cMultSectors = s->uATARegNSector;
ataCmdOK(s, 0);
}
ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
break;
case ATA_READ_VERIFY_SECTORS_EXT:
s->fLBA48 = true;
case ATA_READ_VERIFY_SECTORS:
case ATA_READ_VERIFY_SECTORS_WITHOUT_RETRIES:
/* do sector number check ? */
ataCmdOK(s, 0);
ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
break;
case ATA_READ_SECTORS_EXT:
s->fLBA48 = true;
case ATA_READ_SECTORS:
case ATA_READ_SECTORS_WITHOUT_RETRIES:
if (!s->pDrvBlock)
goto abort_cmd;
s->cSectorsPerIRQ = 1;
ataStartTransfer(s, ataGetNSectors(s) * 512, PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_READ_WRITE_SECTORS, ATAFN_SS_READ_SECTORS, false);
break;
case ATA_WRITE_SECTORS_EXT:
s->fLBA48 = true;
case ATA_WRITE_SECTORS:
case ATA_WRITE_SECTORS_WITHOUT_RETRIES:
s->cSectorsPerIRQ = 1;
ataStartTransfer(s, ataGetNSectors(s) * 512, PDMBLOCKTXDIR_TO_DEVICE, ATAFN_BT_READ_WRITE_SECTORS, ATAFN_SS_WRITE_SECTORS, false);
break;
case ATA_READ_MULTIPLE_EXT:
s->fLBA48 = true;
case ATA_READ_MULTIPLE:
if (!s->cMultSectors)
goto abort_cmd;
s->cSectorsPerIRQ = s->cMultSectors;
ataStartTransfer(s, ataGetNSectors(s) * 512, PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_READ_WRITE_SECTORS, ATAFN_SS_READ_SECTORS, false);
break;
case ATA_WRITE_MULTIPLE_EXT:
s->fLBA48 = true;
case ATA_WRITE_MULTIPLE:
if (!s->cMultSectors)
goto abort_cmd;
s->cSectorsPerIRQ = s->cMultSectors;
ataStartTransfer(s, ataGetNSectors(s) * 512, PDMBLOCKTXDIR_TO_DEVICE, ATAFN_BT_READ_WRITE_SECTORS, ATAFN_SS_WRITE_SECTORS, false);
break;
case ATA_READ_DMA_EXT:
s->fLBA48 = true;
case ATA_READ_DMA:
case ATA_READ_DMA_WITHOUT_RETRIES:
if (!s->pDrvBlock)
goto abort_cmd;
s->cSectorsPerIRQ = ATA_MAX_MULT_SECTORS;
s->fDMA = true;
ataStartTransfer(s, ataGetNSectors(s) * 512, PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_READ_WRITE_SECTORS, ATAFN_SS_READ_SECTORS, false);
break;
case ATA_WRITE_DMA_EXT:
s->fLBA48 = true;
case ATA_WRITE_DMA:
case ATA_WRITE_DMA_WITHOUT_RETRIES:
if (!s->pDrvBlock)
goto abort_cmd;
s->cSectorsPerIRQ = ATA_MAX_MULT_SECTORS;
s->fDMA = true;
ataStartTransfer(s, ataGetNSectors(s) * 512, PDMBLOCKTXDIR_TO_DEVICE, ATAFN_BT_READ_WRITE_SECTORS, ATAFN_SS_WRITE_SECTORS, false);
break;
case ATA_READ_NATIVE_MAX_ADDRESS_EXT:
s->fLBA48 = true;
ataSetSector(s, s->cTotalSectors - 1);
ataCmdOK(s, 0);
ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
break;
case ATA_READ_NATIVE_MAX_ADDRESS:
ataSetSector(s, RT_MIN(s->cTotalSectors, 1 << 28) - 1);
ataCmdOK(s, 0);
ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
break;
case ATA_CHECK_POWER_MODE:
s->uATARegNSector = 0xff; /* drive active or idle */
ataCmdOK(s, 0);
ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
break;
case ATA_SET_FEATURES:
Log2(("%s: feature=%#x\n", __FUNCTION__, s->uATARegFeature));
if (!s->pDrvBlock)
goto abort_cmd;
switch (s->uATARegFeature)
{
case 0x02: /* write cache enable */
Log2(("%s: write cache enable\n", __FUNCTION__));
ataCmdOK(s, ATA_STAT_SEEK);
ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
break;
case 0xaa: /* read look-ahead enable */
Log2(("%s: read look-ahead enable\n", __FUNCTION__));
ataCmdOK(s, ATA_STAT_SEEK);
ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
break;
case 0x55: /* read look-ahead disable */
Log2(("%s: read look-ahead disable\n", __FUNCTION__));
ataCmdOK(s, ATA_STAT_SEEK);
ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
break;
case 0xcc: /* reverting to power-on defaults enable */
Log2(("%s: revert to power-on defaults enable\n", __FUNCTION__));
ataCmdOK(s, ATA_STAT_SEEK);
ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
break;
case 0x66: /* reverting to power-on defaults disable */
Log2(("%s: revert to power-on defaults disable\n", __FUNCTION__));
ataCmdOK(s, ATA_STAT_SEEK);
ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
break;
case 0x82: /* write cache disable */
Log2(("%s: write cache disable\n", __FUNCTION__));
/* As per the ATA/ATAPI-6 specs, a write cache disable
* command MUST flush the write buffers to disc. */
ataStartTransfer(s, 0, PDMBLOCKTXDIR_NONE, ATAFN_BT_NULL, ATAFN_SS_FLUSH, false);
break;
case 0x03: { /* set transfer mode */
Log2(("%s: transfer mode %#04x\n", __FUNCTION__, s->uATARegNSector));
switch (s->uATARegNSector & 0xf8)
{
case 0x00: /* PIO default */
case 0x08: /* PIO mode */
break;
case ATA_MODE_MDMA: /* MDMA mode */
s->uATATransferMode = (s->uATARegNSector & 0xf8) | RT_MIN(s->uATARegNSector & 0x07, ATA_MDMA_MODE_MAX);
break;
case ATA_MODE_UDMA: /* UDMA mode */
s->uATATransferMode = (s->uATARegNSector & 0xf8) | RT_MIN(s->uATARegNSector & 0x07, ATA_UDMA_MODE_MAX);
break;
default:
goto abort_cmd;
}
ataCmdOK(s, ATA_STAT_SEEK);
ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
break;
}
default:
goto abort_cmd;
}
/*
* OS/2 workarond:
* The OS/2 IDE driver from MCP2 appears to rely on the feature register being
* reset here. According to the specification, this is a driver bug as the register
* contents are undefined after the call. This means we can just as well reset it.
*/
s->uATARegFeature = 0;
break;
case ATA_FLUSH_CACHE_EXT:
case ATA_FLUSH_CACHE:
if (!s->pDrvBlock || s->fATAPI)
goto abort_cmd;
ataStartTransfer(s, 0, PDMBLOCKTXDIR_NONE, ATAFN_BT_NULL, ATAFN_SS_FLUSH, false);
break;
case ATA_STANDBY_IMMEDIATE:
ataCmdOK(s, 0);
ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
break;
case ATA_IDLE_IMMEDIATE:
LogRel(("AHCI ATA: LUN#%d: aborting current command\n", s->iLUN));
ataAbortCurrentCommand(s, false);
break;
/* ATAPI commands */
case ATA_IDENTIFY_PACKET_DEVICE:
if (s->fATAPI)
ataStartTransfer(s, 512, PDMBLOCKTXDIR_FROM_DEVICE, ATAFN_BT_NULL, ATAFN_SS_ATAPI_IDENTIFY, false);
else
{
ataCmdError(s, ABRT_ERR);
ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
}
break;
case ATA_EXECUTE_DEVICE_DIAGNOSTIC:
ataStartTransfer(s, 0, PDMBLOCKTXDIR_NONE, ATAFN_BT_NULL, ATAFN_SS_EXECUTE_DEVICE_DIAGNOSTIC, false);
break;
case ATA_DEVICE_RESET:
if (!s->fATAPI)
goto abort_cmd;
LogRel(("AHCI ATA: LUN#%d: performing device RESET\n", s->iLUN));
ataAbortCurrentCommand(s, true);
break;
case ATA_PACKET:
if (!s->fATAPI)
goto abort_cmd;
/* overlapping commands not supported */
if (s->uATARegFeature & 0x02)
goto abort_cmd;
ataStartTransfer(s, ATAPI_PACKET_SIZE, PDMBLOCKTXDIR_TO_DEVICE, ATAFN_BT_PACKET, ATAFN_SS_PACKET, false);
break;
default:
abort_cmd:
ataCmdError(s, ABRT_ERR);
ataSetIRQ(s); /* Shortcut, do not use AIO thread. */
break;
}
}
/**
* Waits for a particular async I/O thread to complete whatever it
* is doing at the moment.
*
* @returns true on success.
* @returns false when the thread is still processing.
* @param pThis Pointer to the controller data.
* @param cMillies How long to wait (total).
*/
static bool ataWaitForAsyncIOIsIdle(PAHCIATACONTROLLER pCtl, unsigned cMillies)
{
uint64_t u64Start;
bool fRc;
/* Hope for the simple way out... */
if (ataAsyncIOIsIdle(pCtl, false /*fStrict*/))
return true;
/*
* Have to wait. Do the setup while owning the mutex to avoid races.
*/
RTSemMutexRequest(pCtl->AsyncIORequestMutex, RT_INDEFINITE_WAIT);
RTThreadUserReset(pCtl->AsyncIOThread);
ASMAtomicWriteBool(&pCtl->fSignalIdle, true);
RTSemMutexRelease(pCtl->AsyncIORequestMutex);
u64Start = RTTimeMilliTS();
for (;;)
{
fRc = ataAsyncIOIsIdle(pCtl, false /*fStrict*/);
if (fRc)
break;
if (RTTimeMilliTS() - u64Start >= cMillies)
break;
int rc = RTThreadUserWait(pCtl->AsyncIOThread, 100 /*ms*/);
AssertMsg( ( RT_SUCCESS(rc)
&& ataAsyncIOIsIdle(pCtl, false /*fStrict*/))
|| rc == VERR_TIMEOUT,
("rc=%Rrc irq=%u\n", rc, pCtl->irq));
}
ASMAtomicWriteBool(&pCtl->fSignalIdle, false);
return fRc;
}
#endif /* IN_RING3 */
static int ataIOPortWriteU8(PAHCIATACONTROLLER pCtl, uint32_t addr, uint32_t val)
{
Log2(("%s: write addr=%#x val=%#04x\n", __FUNCTION__, addr, val));
addr &= 7;
switch (addr)
{
case 0:
break;
case 1: /* feature register */
/* NOTE: data is written to the two drives */
pCtl->aIfs[0].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
pCtl->aIfs[1].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
pCtl->aIfs[0].uATARegFeatureHOB = pCtl->aIfs[0].uATARegFeature;
pCtl->aIfs[1].uATARegFeatureHOB = pCtl->aIfs[1].uATARegFeature;
pCtl->aIfs[0].uATARegFeature = val;
pCtl->aIfs[1].uATARegFeature = val;
break;
case 2: /* sector count */
pCtl->aIfs[0].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
pCtl->aIfs[1].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
pCtl->aIfs[0].uATARegNSectorHOB = pCtl->aIfs[0].uATARegNSector;
pCtl->aIfs[1].uATARegNSectorHOB = pCtl->aIfs[1].uATARegNSector;
pCtl->aIfs[0].uATARegNSector = val;
pCtl->aIfs[1].uATARegNSector = val;
break;
case 3: /* sector number */
pCtl->aIfs[0].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
pCtl->aIfs[1].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
pCtl->aIfs[0].uATARegSectorHOB = pCtl->aIfs[0].uATARegSector;
pCtl->aIfs[1].uATARegSectorHOB = pCtl->aIfs[1].uATARegSector;
pCtl->aIfs[0].uATARegSector = val;
pCtl->aIfs[1].uATARegSector = val;
break;
case 4: /* cylinder low */
pCtl->aIfs[0].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
pCtl->aIfs[1].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
pCtl->aIfs[0].uATARegLCylHOB = pCtl->aIfs[0].uATARegLCyl;
pCtl->aIfs[1].uATARegLCylHOB = pCtl->aIfs[1].uATARegLCyl;
pCtl->aIfs[0].uATARegLCyl = val;
pCtl->aIfs[1].uATARegLCyl = val;
break;
case 5: /* cylinder high */
pCtl->aIfs[0].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
pCtl->aIfs[1].uATARegDevCtl &= ~ATA_DEVCTL_HOB;
pCtl->aIfs[0].uATARegHCylHOB = pCtl->aIfs[0].uATARegHCyl;
pCtl->aIfs[1].uATARegHCylHOB = pCtl->aIfs[1].uATARegHCyl;
pCtl->aIfs[0].uATARegHCyl = val;
pCtl->aIfs[1].uATARegHCyl = val;
break;
case 6: /* drive/head */
pCtl->aIfs[0].uATARegSelect = (val & ~0x10) | 0xa0;
pCtl->aIfs[1].uATARegSelect = (val | 0x10) | 0xa0;
if (((val >> 4) & 1) != pCtl->iSelectedIf)
{
PPDMDEVINS pDevIns = CONTROLLER_2_DEVINS(pCtl);
/* select another drive */
pCtl->iSelectedIf = (val >> 4) & 1;
/* The IRQ line is multiplexed between the two drives, so
* update the state when switching to another drive. Only need
* to update interrupt line if it is enabled and there is a
* state change. */
if ( !(pCtl->aIfs[pCtl->iSelectedIf].uATARegDevCtl & ATA_DEVCTL_DISABLE_IRQ)
&& ( pCtl->aIfs[pCtl->iSelectedIf].fIrqPending
!= pCtl->aIfs[pCtl->iSelectedIf ^ 1].fIrqPending))
{
if (pCtl->aIfs[pCtl->iSelectedIf].fIrqPending)
{
Log2(("%s: LUN#%d asserting IRQ (drive select change)\n", __FUNCTION__, pCtl->aIfs[pCtl->iSelectedIf].iLUN));
/* The BMDMA unit unconditionally sets BM_STATUS_INT if
* the interrupt line is asserted. It monitors the line
* for a rising edge. */
pCtl->BmDma.u8Status |= BM_STATUS_INT;
if (pCtl->irq == 16)
PDMDevHlpPCISetIrqNoWait(pDevIns, 0, 1);
else
PDMDevHlpISASetIrqNoWait(pDevIns, pCtl->irq, 1);
}
else
{
Log2(("%s: LUN#%d deasserting IRQ (drive select change)\n", __FUNCTION__, pCtl->aIfs[pCtl->iSelectedIf].iLUN));
if (pCtl->irq == 16)
PDMDevHlpPCISetIrqNoWait(pDevIns, 0, 0);
else
PDMDevHlpISASetIrqNoWait(pDevIns, pCtl->irq, 0);
}
}
}
break;
default:
case 7: /* command */
/* ignore commands to non existant slave */
if (pCtl->iSelectedIf && !pCtl->aIfs[pCtl->iSelectedIf].pDrvBlock)
break;
#ifndef IN_RING3
/* Don't do anything complicated in GC */
return VINF_IOM_HC_IOPORT_WRITE;
#else /* IN_RING3 */
ataParseCmd(&pCtl->aIfs[pCtl->iSelectedIf], val);
#endif /* !IN_RING3 */
}
return VINF_SUCCESS;
}
static int ataIOPortReadU8(PAHCIATACONTROLLER pCtl, uint32_t addr, uint32_t *pu32)
{
AHCIATADevState *s = &pCtl->aIfs[pCtl->iSelectedIf];
uint32_t val;
bool fHOB;
fHOB = !!(s->uATARegDevCtl & (1 << 7));
switch (addr & 7)
{
case 0: /* data register */
val = 0xff;
break;
case 1: /* error register */
/* The ATA specification is very terse when it comes to specifying
* the precise effects of reading back the error/feature register.
* The error register (read-only) shares the register number with
* the feature register (write-only), so it seems that it's not
* necessary to support the usual HOB readback here. */
if (!s->pDrvBlock)
val = 0;
else
val = s->uATARegError;
break;
case 2: /* sector count */
if (!s->pDrvBlock)
val = 0;
else if (fHOB)
val = s->uATARegNSectorHOB;
else
val = s->uATARegNSector;
break;
case 3: /* sector number */
if (!s->pDrvBlock)
val = 0;
else if (fHOB)
val = s->uATARegSectorHOB;
else
val = s->uATARegSector;
break;
case 4: /* cylinder low */
if (!s->pDrvBlock)
val = 0;
else if (fHOB)
val = s->uATARegLCylHOB;
else
val = s->uATARegLCyl;
break;
case 5: /* cylinder high */
if (!s->pDrvBlock)
val = 0;
else if (fHOB)
val = s->uATARegHCylHOB;
else
val = s->uATARegHCyl;
break;
case 6: /* drive/head */
/* This register must always work as long as there is at least
* one drive attached to the controller. It is common between
* both drives anyway (completely identical content). */
if (!pCtl->aIfs[0].pDrvBlock && !pCtl->aIfs[1].pDrvBlock)
val = 0;
else
val = s->uATARegSelect;
break;
default:
case 7: /* primary status */
{
/* Counter for number of busy status seen in GC in a row. */
static unsigned cBusy = 0;
if (!s->pDrvBlock)
val = 0;
else
val = s->uATARegStatus;
/* Give the async I/O thread an opportunity to make progress,
* don't let it starve by guests polling frequently. EMT has a
* lower priority than the async I/O thread, but sometimes the
* host OS doesn't care. With some guests we are only allowed to
* be busy for about 5 milliseconds in some situations. Note that
* this is no guarantee for any other VBox thread getting
* scheduled, so this just lowers the CPU load a bit when drives
* are busy. It cannot help with timing problems. */
if (val & ATA_STAT_BUSY)
{
#ifdef IN_RING3
cBusy = 0;
PDMCritSectLeave(&pCtl->lock);
RTThreadYield();
{
STAM_PROFILE_START(&pCtl->StatLockWait, a);
PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
}
val = s->uATARegStatus;
#else /* !IN_RING3 */
/* Cannot yield CPU in guest context. And switching to host
* context for each and every busy status is too costly,
* especially on SMP systems where we don't gain much by
* yielding the CPU to someone else. */
if (++cBusy >= 20)
{
cBusy = 0;
return VINF_IOM_HC_IOPORT_READ;
}
#endif /* !IN_RING3 */
}
else
cBusy = 0;
ataUnsetIRQ(s);
break;
}
}
Log2(("%s: addr=%#x val=%#04x\n", __FUNCTION__, addr, val));
*pu32 = val;
return VINF_SUCCESS;
}
static uint32_t ataStatusRead(PAHCIATACONTROLLER pCtl, uint32_t addr)
{
AHCIATADevState *s = &pCtl->aIfs[pCtl->iSelectedIf];
uint32_t val;
if ((!pCtl->aIfs[0].pDrvBlock && !pCtl->aIfs[1].pDrvBlock) ||
(pCtl->iSelectedIf == 1 && !s->pDrvBlock))
val = 0;
else
val = s->uATARegStatus;
Log2(("%s: addr=%#x val=%#04x\n", __FUNCTION__, addr, val));
return val;
}
static int ataControlWrite(PAHCIATACONTROLLER pCtl, uint32_t addr, uint32_t val)
{
#ifndef IN_RING3
if ((val ^ pCtl->aIfs[0].uATARegDevCtl) & ATA_DEVCTL_RESET)
return VINF_IOM_HC_IOPORT_WRITE; /* The RESET stuff is too complicated for GC. */
#endif /* !IN_RING3 */
Log2(("%s: addr=%#x val=%#04x\n", __FUNCTION__, addr, val));
/* RESET is common for both drives attached to a controller. */
if (!(pCtl->aIfs[0].uATARegDevCtl & ATA_DEVCTL_RESET) &&
(val & ATA_DEVCTL_RESET))
{
#ifdef IN_RING3
/* Software RESET low to high */
int32_t uCmdWait0 = -1, uCmdWait1 = -1;
uint64_t uNow = RTTimeNanoTS();
if (pCtl->aIfs[0].u64CmdTS)
uCmdWait0 = (uNow - pCtl->aIfs[0].u64CmdTS) / 1000;
if (pCtl->aIfs[1].u64CmdTS)
uCmdWait1 = (uNow - pCtl->aIfs[1].u64CmdTS) / 1000;
LogRel(("ATA: Ctl: RESET, DevSel=%d AIOIf=%d CmdIf0=%#04x (%d usec ago) CmdIf1=%#04x (%d usec ago)\n",
pCtl->iSelectedIf, pCtl->iAIOIf,
pCtl->aIfs[0].uATARegCommand, uCmdWait0,
pCtl->aIfs[1].uATARegCommand, uCmdWait1));
pCtl->fReset = true;
/* Everything must be done after the reset flag is set, otherwise
* there are unavoidable races with the currently executing request
* (which might just finish in the mean time). */
pCtl->fChainedTransfer = false;
for (uint32_t i = 0; i < RT_ELEMENTS(pCtl->aIfs); i++)
{
ataResetDevice(&pCtl->aIfs[i]);
/* The following cannot be done using ataSetStatusValue() since the
* reset flag is already set, which suppresses all status changes. */
pCtl->aIfs[i].uATARegStatus = ATA_STAT_BUSY | ATA_STAT_SEEK;
Log2(("%s: LUN#%d status %#04x\n", __FUNCTION__, pCtl->aIfs[i].iLUN, pCtl->aIfs[i].uATARegStatus));
pCtl->aIfs[i].uATARegError = 0x01;
}
ataAsyncIOClearRequests(pCtl);
Log2(("%s: Ctl: message to async I/O thread, resetA\n", __FUNCTION__));
if (val & ATA_DEVCTL_HOB)
{
val &= ~ATA_DEVCTL_HOB;
Log2(("%s: ignored setting HOB\n", __FUNCTION__));
}
ataAsyncIOPutRequest(pCtl, &ataResetARequest);
#else /* !IN_RING3 */
AssertMsgFailed(("RESET handling is too complicated for GC\n"));
#endif /* IN_RING3 */
}
else if ((pCtl->aIfs[0].uATARegDevCtl & ATA_DEVCTL_RESET) &&
!(val & ATA_DEVCTL_RESET))
{
#ifdef IN_RING3
/* Software RESET high to low */
Log(("%s: deasserting RESET\n", __FUNCTION__));
Log2(("%s: Ctl: message to async I/O thread, resetC\n", __FUNCTION__));
if (val & ATA_DEVCTL_HOB)
{
val &= ~ATA_DEVCTL_HOB;
Log2(("%s: ignored setting HOB\n", __FUNCTION__));
}
ataAsyncIOPutRequest(pCtl, &ataResetCRequest);
#else /* !IN_RING3 */
AssertMsgFailed(("RESET handling is too complicated for GC\n"));
#endif /* IN_RING3 */
}
/* Change of interrupt disable flag. Update interrupt line if interrupt
* is pending on the current interface. */
if ((val ^ pCtl->aIfs[0].uATARegDevCtl) & ATA_DEVCTL_DISABLE_IRQ
&& pCtl->aIfs[pCtl->iSelectedIf].fIrqPending)
{
if (!(val & ATA_DEVCTL_DISABLE_IRQ))
{
Log2(("%s: LUN#%d asserting IRQ (interrupt disable change)\n", __FUNCTION__, pCtl->aIfs[pCtl->iSelectedIf].iLUN));
/* The BMDMA unit unconditionally sets BM_STATUS_INT if the
* interrupt line is asserted. It monitors the line for a rising
* edge. */
pCtl->BmDma.u8Status |= BM_STATUS_INT;
if (pCtl->irq == 16)
PDMDevHlpPCISetIrqNoWait(CONTROLLER_2_DEVINS(pCtl), 0, 1);
else
PDMDevHlpISASetIrqNoWait(CONTROLLER_2_DEVINS(pCtl), pCtl->irq, 1);
}
else
{
Log2(("%s: LUN#%d deasserting IRQ (interrupt disable change)\n", __FUNCTION__, pCtl->aIfs[pCtl->iSelectedIf].iLUN));
if (pCtl->irq == 16)
PDMDevHlpPCISetIrqNoWait(CONTROLLER_2_DEVINS(pCtl), 0, 0);
else
PDMDevHlpISASetIrqNoWait(CONTROLLER_2_DEVINS(pCtl), pCtl->irq, 0);
}
}
if (val & ATA_DEVCTL_HOB)
Log2(("%s: set HOB\n", __FUNCTION__));
pCtl->aIfs[0].uATARegDevCtl = val;
pCtl->aIfs[1].uATARegDevCtl = val;
return VINF_SUCCESS;
}
#ifdef IN_RING3
static void ataPIOTransfer(PAHCIATACONTROLLER pCtl)
{
AHCIATADevState *s;
s = &pCtl->aIfs[pCtl->iAIOIf];
Log3(("%s: if=%p\n", __FUNCTION__, s));
if (s->cbTotalTransfer && s->iIOBufferCur > s->iIOBufferEnd)
{
LogRel(("AHCI ATA: LUN#%d: %s data in the middle of a PIO transfer - VERY SLOW\n", s->iLUN, s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE ? "loading" : "storing"));
/* Any guest OS that triggers this case has a pathetic ATA driver.
* In a real system it would block the CPU via IORDY, here we do it
* very similarly by not continuing with the current instruction
* until the transfer to/from the storage medium is completed. */
if (s->iSourceSink != ATAFN_SS_NULL)
{
bool fRedo;
uint8_t status = s->uATARegStatus;
ataSetStatusValue(s, ATA_STAT_BUSY);
Log2(("%s: calling source/sink function\n", __FUNCTION__));
fRedo = g_apfnSourceSinkFuncs[s->iSourceSink](s);
pCtl->fRedo = fRedo;
if (RT_UNLIKELY(fRedo))
return;
ataSetStatusValue(s, status);
s->iIOBufferCur = 0;
s->iIOBufferEnd = s->cbElementaryTransfer;
}
}
if (s->cbTotalTransfer)
{
if (s->fATAPITransfer)
ataPIOTransferLimitATAPI(s);
if (s->uTxDir == PDMBLOCKTXDIR_TO_DEVICE && s->cbElementaryTransfer > s->cbTotalTransfer)
s->cbElementaryTransfer = s->cbTotalTransfer;
Log2(("%s: %s tx_size=%d elem_tx_size=%d index=%d end=%d\n",
__FUNCTION__, s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE ? "T2I" : "I2T",
s->cbTotalTransfer, s->cbElementaryTransfer,
s->iIOBufferCur, s->iIOBufferEnd));
ataPIOTransferStart(s, s->iIOBufferCur, s->cbElementaryTransfer);
s->cbTotalTransfer -= s->cbElementaryTransfer;
s->iIOBufferCur += s->cbElementaryTransfer;
if (s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE && s->cbElementaryTransfer > s->cbTotalTransfer)
s->cbElementaryTransfer = s->cbTotalTransfer;
}
else
ataPIOTransferStop(s);
}
DECLINLINE(void) ataPIOTransferFinish(PAHCIATACONTROLLER pCtl, AHCIATADevState *s)
{
/* Do not interfere with RESET processing if the PIO transfer finishes
* while the RESET line is asserted. */
if (pCtl->fReset)
{
Log2(("%s: Ctl: suppressed continuing PIO transfer as RESET is active\n", __FUNCTION__));
return;
}
if ( s->uTxDir == PDMBLOCKTXDIR_TO_DEVICE
|| ( s->iSourceSink != ATAFN_SS_NULL
&& s->iIOBufferCur >= s->iIOBufferEnd))
{
/* Need to continue the transfer in the async I/O thread. This is
* the case for write operations or generally for not yet finished
* transfers (some data might need to be read). */
ataUnsetStatus(s, ATA_STAT_READY | ATA_STAT_DRQ);
ataSetStatus(s, ATA_STAT_BUSY);
Log2(("%s: Ctl: message to async I/O thread, continuing PIO transfer\n", __FUNCTION__));
ataAsyncIOPutRequest(pCtl, &ataPIORequest);
}
else
{
/* Either everything finished (though some data might still be pending)
* or some data is pending before the next read is due. */
/* Continue a previously started transfer. */
ataUnsetStatus(s, ATA_STAT_DRQ);
ataSetStatus(s, ATA_STAT_READY);
if (s->cbTotalTransfer)
{
/* There is more to transfer, happens usually for large ATAPI
* reads - the protocol limits the chunk size to 65534 bytes. */
ataPIOTransfer(pCtl);
ataSetIRQ(s);
}
else
{
Log2(("%s: Ctl: skipping message to async I/O thread, ending PIO transfer\n", __FUNCTION__));
/* Finish PIO transfer. */
ataPIOTransfer(pCtl);
Assert(!pCtl->fRedo);
}
}
}
#endif /* IN_RING3 */
static int ataDataWrite(PAHCIATACONTROLLER pCtl, uint32_t addr, uint32_t cbSize, const uint8_t *pbBuf)
{
AHCIATADevState *s = &pCtl->aIfs[pCtl->iSelectedIf];
uint8_t *p;
if (s->iIOBufferPIODataStart < s->iIOBufferPIODataEnd)
{
Assert(s->uTxDir == PDMBLOCKTXDIR_TO_DEVICE);
p = s->CTX_SUFF(pbIOBuffer) + s->iIOBufferPIODataStart;
#ifndef IN_RING3
/* All but the last transfer unit is simple enough for GC, but
* sending a request to the async IO thread is too complicated. */
if (s->iIOBufferPIODataStart + cbSize < s->iIOBufferPIODataEnd)
{
memcpy(p, pbBuf, cbSize);
s->iIOBufferPIODataStart += cbSize;
}
else
return VINF_IOM_HC_IOPORT_WRITE;
#else /* IN_RING3 */
memcpy(p, pbBuf, cbSize);
s->iIOBufferPIODataStart += cbSize;
if (s->iIOBufferPIODataStart >= s->iIOBufferPIODataEnd)
ataPIOTransferFinish(pCtl, s);
#endif /* !IN_RING3 */
}
else
Log2(("%s: DUMMY data\n", __FUNCTION__));
Log3(("%s: addr=%#x val=%.*Rhxs\n", __FUNCTION__, addr, cbSize, pbBuf));
return VINF_SUCCESS;
}
static int ataDataRead(PAHCIATACONTROLLER pCtl, uint32_t addr, uint32_t cbSize, uint8_t *pbBuf)
{
AHCIATADevState *s = &pCtl->aIfs[pCtl->iSelectedIf];
uint8_t *p;
if (s->iIOBufferPIODataStart < s->iIOBufferPIODataEnd)
{
Assert(s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE);
p = s->CTX_SUFF(pbIOBuffer) + s->iIOBufferPIODataStart;
#ifndef IN_RING3
/* All but the last transfer unit is simple enough for GC, but
* sending a request to the async IO thread is too complicated. */
if (s->iIOBufferPIODataStart + cbSize < s->iIOBufferPIODataEnd)
{
memcpy(pbBuf, p, cbSize);
s->iIOBufferPIODataStart += cbSize;
}
else
return VINF_IOM_HC_IOPORT_READ;
#else /* IN_RING3 */
memcpy(pbBuf, p, cbSize);
s->iIOBufferPIODataStart += cbSize;
if (s->iIOBufferPIODataStart >= s->iIOBufferPIODataEnd)
ataPIOTransferFinish(pCtl, s);
#endif /* !IN_RING3 */
}
else
{
Log2(("%s: DUMMY data\n", __FUNCTION__));
memset(pbBuf, '\xff', cbSize);
}
Log3(("%s: addr=%#x val=%.*Rhxs\n", __FUNCTION__, addr, cbSize, pbBuf));
return VINF_SUCCESS;
}
#ifdef IN_RING3
static void ataDMATransferStop(AHCIATADevState *s)
{
s->cbTotalTransfer = 0;
s->cbElementaryTransfer = 0;
s->iBeginTransfer = ATAFN_BT_NULL;
s->iSourceSink = ATAFN_SS_NULL;
}
/**
* Perform the entire DMA transfer in one go (unless a source/sink operation
* has to be redone or a RESET comes in between). Unlike the PIO counterpart
* this function cannot handle empty transfers.
*
* @param pCtl Controller for which to perform the transfer.
*/
static void ataDMATransfer(PAHCIATACONTROLLER pCtl)
{
PPDMDEVINS pDevIns = CONTROLLER_2_DEVINS(pCtl);
AHCIATADevState *s = &pCtl->aIfs[pCtl->iAIOIf];
bool fRedo;
RTGCPHYS32 pDesc;
uint32_t cbTotalTransfer, cbElementaryTransfer;
uint32_t iIOBufferCur, iIOBufferEnd;
uint32_t dmalen;
PDMBLOCKTXDIR uTxDir;
bool fLastDesc = false;
Assert(sizeof(BMDMADesc) == 8);
fRedo = pCtl->fRedo;
if (RT_LIKELY(!fRedo))
Assert(s->cbTotalTransfer);
uTxDir = (PDMBLOCKTXDIR)s->uTxDir;
cbTotalTransfer = s->cbTotalTransfer;
cbElementaryTransfer = s->cbElementaryTransfer;
iIOBufferCur = s->iIOBufferCur;
iIOBufferEnd = s->iIOBufferEnd;
/* The DMA loop is designed to hold the lock only when absolutely
* necessary. This avoids long freezes should the guest access the
* ATA registers etc. for some reason. */
PDMCritSectLeave(&pCtl->lock);
Log2(("%s: %s tx_size=%d elem_tx_size=%d index=%d end=%d\n",
__FUNCTION__, uTxDir == PDMBLOCKTXDIR_FROM_DEVICE ? "T2I" : "I2T",
cbTotalTransfer, cbElementaryTransfer,
iIOBufferCur, iIOBufferEnd));
for (pDesc = pCtl->pFirstDMADesc; pDesc <= pCtl->pLastDMADesc; pDesc += sizeof(BMDMADesc))
{
BMDMADesc DMADesc;
RTGCPHYS32 pBuffer;
uint32_t cbBuffer;
if (RT_UNLIKELY(fRedo))
{
pBuffer = pCtl->pRedoDMABuffer;
cbBuffer = pCtl->cbRedoDMABuffer;
fLastDesc = pCtl->fRedoDMALastDesc;
}
else
{
PDMDevHlpPhysRead(pDevIns, pDesc, &DMADesc, sizeof(BMDMADesc));
pBuffer = RT_LE2H_U32(DMADesc.pBuffer);
cbBuffer = RT_LE2H_U32(DMADesc.cbBuffer);
fLastDesc = !!(cbBuffer & 0x80000000);
cbBuffer &= 0xfffe;
if (cbBuffer == 0)
cbBuffer = 0x10000;
if (cbBuffer > cbTotalTransfer)
cbBuffer = cbTotalTransfer;
}
while (RT_UNLIKELY(fRedo) || (cbBuffer && cbTotalTransfer))
{
if (RT_LIKELY(!fRedo))
{
dmalen = RT_MIN(cbBuffer, iIOBufferEnd - iIOBufferCur);
Log2(("%s: DMA desc %#010x: addr=%#010x size=%#010x\n", __FUNCTION__,
pDesc, pBuffer, cbBuffer));
if (uTxDir == PDMBLOCKTXDIR_FROM_DEVICE)
PDMDevHlpPhysWrite(pDevIns, pBuffer, s->CTX_SUFF(pbIOBuffer) + iIOBufferCur, dmalen);
else
PDMDevHlpPhysRead(pDevIns, pBuffer, s->CTX_SUFF(pbIOBuffer) + iIOBufferCur, dmalen);
iIOBufferCur += dmalen;
cbTotalTransfer -= dmalen;
cbBuffer -= dmalen;
pBuffer += dmalen;
}
if ( iIOBufferCur == iIOBufferEnd
&& (uTxDir == PDMBLOCKTXDIR_TO_DEVICE || cbTotalTransfer))
{
if (uTxDir == PDMBLOCKTXDIR_FROM_DEVICE && cbElementaryTransfer > cbTotalTransfer)
cbElementaryTransfer = cbTotalTransfer;
{
STAM_PROFILE_START(&pCtl->StatLockWait, a);
PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
}
/* The RESET handler could have cleared the DMA transfer
* state (since we didn't hold the lock until just now
* the guest can continue in parallel). If so, the state
* is already set up so the loop is exited immediately. */
if (s->iSourceSink != ATAFN_SS_NULL)
{
s->iIOBufferCur = iIOBufferCur;
s->iIOBufferEnd = iIOBufferEnd;
s->cbElementaryTransfer = cbElementaryTransfer;
s->cbTotalTransfer = cbTotalTransfer;
Log2(("%s: calling source/sink function\n", __FUNCTION__));
fRedo = g_apfnSourceSinkFuncs[s->iSourceSink](s);
if (RT_UNLIKELY(fRedo))
{
pCtl->pFirstDMADesc = pDesc;
pCtl->pRedoDMABuffer = pBuffer;
pCtl->cbRedoDMABuffer = cbBuffer;
pCtl->fRedoDMALastDesc = fLastDesc;
}
else
{
cbTotalTransfer = s->cbTotalTransfer;
cbElementaryTransfer = s->cbElementaryTransfer;
if (uTxDir == PDMBLOCKTXDIR_TO_DEVICE && cbElementaryTransfer > cbTotalTransfer)
cbElementaryTransfer = cbTotalTransfer;
iIOBufferCur = 0;
iIOBufferEnd = cbElementaryTransfer;
}
pCtl->fRedo = fRedo;
}
else
{
/* This forces the loop to exit immediately. */
pDesc = pCtl->pLastDMADesc + 1;
}
PDMCritSectLeave(&pCtl->lock);
if (RT_UNLIKELY(fRedo))
break;
}
}
if (RT_UNLIKELY(fRedo))
break;
/* end of transfer */
if (!cbTotalTransfer || fLastDesc)
break;
{
STAM_PROFILE_START(&pCtl->StatLockWait, a);
PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
}
if (!(pCtl->BmDma.u8Cmd & BM_CMD_START) || pCtl->fReset)
{
LogRel(("ATA: Ctl: ABORT DMA%s\n", pCtl->fReset ? " due to RESET" : ""));
if (!pCtl->fReset)
ataDMATransferStop(s);
/* This forces the loop to exit immediately. */
pDesc = pCtl->pLastDMADesc + 1;
}
PDMCritSectLeave(&pCtl->lock);
}
{
STAM_PROFILE_START(&pCtl->StatLockWait, a);
PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
}
if (RT_UNLIKELY(fRedo))
return;
if (fLastDesc)
pCtl->BmDma.u8Status &= ~BM_STATUS_DMAING;
s->cbTotalTransfer = cbTotalTransfer;
s->cbElementaryTransfer = cbElementaryTransfer;
s->iIOBufferCur = iIOBufferCur;
s->iIOBufferEnd = iIOBufferEnd;
}
/**
* Suspend I/O operations on a controller. Also suspends EMT, because it's
* waiting for I/O to make progress. The next attempt to perform an I/O
* operation will be made when EMT is resumed up again (as the resume
* callback below restarts I/O).
*
* @param pCtl Controller for which to suspend I/O.
*/
static void ataSuspendRedo(PAHCIATACONTROLLER pCtl)
{
PPDMDEVINS pDevIns = CONTROLLER_2_DEVINS(pCtl);
int rc;
pCtl->fRedoIdle = true;
rc = VMR3ReqCallWait(PDMDevHlpGetVM(pDevIns), VMCPUID_ANY,
(PFNRT)PDMDevHlpVMSuspend, 1, pDevIns);
AssertReleaseRC(rc);
}
/**
* Signal ataWaitForAsyncIOIsIdle that we're idle (if we actually are).
*
* @param pCtl The controller.
*/
static void ataAsyncSignalIdle(PAHCIATACONTROLLER pCtl)
{
/*
* Take the mutex here and recheck the idle indicator as there might be
* interesting races, like in the ataReset code.
*/
int rc = RTSemMutexRequest(pCtl->AsyncIORequestMutex, RT_INDEFINITE_WAIT); AssertRC(rc);
if ( pCtl->fSignalIdle
&& ataAsyncIOIsIdle(pCtl, false /*fStrict*/))
RTThreadUserSignal(pCtl->AsyncIOThread);
rc = RTSemMutexRelease(pCtl->AsyncIORequestMutex); AssertRC(rc);
}
/** Asynch I/O thread for an interface. Once upon a time this was readable
* code with several loops and a different semaphore for each purpose. But
* then came the "how can one save the state in the middle of a PIO transfer"
* question. The solution was to use an ASM, which is what's there now. */
static DECLCALLBACK(int) ataAsyncIOLoop(RTTHREAD ThreadSelf, void *pvUser)
{
const AHCIATARequest *pReq;
uint64_t u64TS = 0; /* shut up gcc */
uint64_t uWait;
int rc = VINF_SUCCESS;
PAHCIATACONTROLLER pCtl = (PAHCIATACONTROLLER)pvUser;
AHCIATADevState *s;
pReq = NULL;
pCtl->fChainedTransfer = false;
while (!pCtl->fShutdown)
{
/* Keep this thread from doing anything as long as EMT is suspended. */
while (pCtl->fRedoIdle)
{
if (pCtl->fSignalIdle)
ataAsyncSignalIdle(pCtl);
rc = RTSemEventWait(pCtl->SuspendIOSem, RT_INDEFINITE_WAIT);
if (RT_FAILURE(rc) || pCtl->fShutdown)
break;
pCtl->fRedoIdle = false;
}
/* Wait for work. */
if (pReq == NULL)
{
if (pCtl->fSignalIdle)
ataAsyncSignalIdle(pCtl);
rc = RTSemEventWait(pCtl->AsyncIOSem, RT_INDEFINITE_WAIT);
if (RT_FAILURE(rc) || pCtl->fShutdown)
break;
pReq = ataAsyncIOGetCurrentRequest(pCtl);
}
if (pReq == NULL)
continue;
AHCIATAAIO ReqType = pReq->ReqType;
Log2(("%s: Ctl: state=%d, req=%d\n", __FUNCTION__, pCtl->uAsyncIOState, ReqType));
if (pCtl->uAsyncIOState != ReqType)
{
/* The new state is not the state that was expected by the normal
* state changes. This is either a RESET/ABORT or there's something
* really strange going on. */
if ( (pCtl->uAsyncIOState == AHCIATA_AIO_PIO || pCtl->uAsyncIOState == AHCIATA_AIO_DMA)
&& (ReqType == AHCIATA_AIO_PIO || ReqType == AHCIATA_AIO_DMA))
{
/* Incorrect sequence of PIO/DMA states. Dump request queue. */
ataAsyncIODumpRequests(pCtl);
}
AssertReleaseMsg(ReqType == AHCIATA_AIO_RESET_ASSERTED || ReqType == AHCIATA_AIO_RESET_CLEARED || ReqType == AHCIATA_AIO_ABORT || pCtl->uAsyncIOState == ReqType, ("I/O state inconsistent: state=%d request=%d\n", pCtl->uAsyncIOState, ReqType));
}
/* Do our work. */
{
STAM_PROFILE_START(&pCtl->StatLockWait, a);
PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
STAM_PROFILE_STOP(&pCtl->StatLockWait, a);
}
if (pCtl->uAsyncIOState == AHCIATA_AIO_NEW && !pCtl->fChainedTransfer)
{
u64TS = RTTimeNanoTS();
#if defined(DEBUG) || defined(VBOX_WITH_STATISTICS)
STAM_PROFILE_ADV_START(&pCtl->StatAsyncTime, a);
#endif /* DEBUG || VBOX_WITH_STATISTICS */
}
switch (ReqType)
{
case AHCIATA_AIO_NEW:
pCtl->iAIOIf = pReq->u.t.iIf;
s = &pCtl->aIfs[pCtl->iAIOIf];
s->cbTotalTransfer = pReq->u.t.cbTotalTransfer;
s->uTxDir = pReq->u.t.uTxDir;
s->iBeginTransfer = pReq->u.t.iBeginTransfer;
s->iSourceSink = pReq->u.t.iSourceSink;
s->iIOBufferEnd = 0;
s->u64CmdTS = u64TS;
if (s->fATAPI)
{
if (pCtl->fChainedTransfer)
{
/* Only count the actual transfers, not the PIO
* transfer of the ATAPI command bytes. */
if (s->fDMA)
STAM_REL_COUNTER_INC(&s->StatATAPIDMA);
else
STAM_REL_COUNTER_INC(&s->StatATAPIPIO);
}
}
else
{
if (s->fDMA)
STAM_REL_COUNTER_INC(&s->StatATADMA);
else
STAM_REL_COUNTER_INC(&s->StatATAPIO);
}
pCtl->fChainedTransfer = false;
if (s->iBeginTransfer != ATAFN_BT_NULL)
{
Log2(("%s: Ctl: calling begin transfer function\n", __FUNCTION__));
g_apfnBeginTransFuncs[s->iBeginTransfer](s);
s->iBeginTransfer = ATAFN_BT_NULL;
if (s->uTxDir != PDMBLOCKTXDIR_FROM_DEVICE)
s->iIOBufferEnd = s->cbElementaryTransfer;
}
else
{
s->cbElementaryTransfer = s->cbTotalTransfer;
s->iIOBufferEnd = s->cbTotalTransfer;
}
s->iIOBufferCur = 0;
if (s->uTxDir != PDMBLOCKTXDIR_TO_DEVICE)
{
if (s->iSourceSink != ATAFN_SS_NULL)
{
bool fRedo;
Log2(("%s: Ctl: calling source/sink function\n", __FUNCTION__));
fRedo = g_apfnSourceSinkFuncs[s->iSourceSink](s);
pCtl->fRedo = fRedo;
if (RT_UNLIKELY(fRedo))
{
/* Operation failed at the initial transfer, restart
* everything from scratch by resending the current
* request. Occurs very rarely, not worth optimizing. */
LogRel(("%s: Ctl: redo entire operation\n", __FUNCTION__));
ataAsyncIOPutRequest(pCtl, pReq);
ataSuspendRedo(pCtl);
break;
}
}
else
ataCmdOK(s, 0);
s->iIOBufferEnd = s->cbElementaryTransfer;
}
/* Do not go into the transfer phase if RESET is asserted.
* The CritSect is released while waiting for the host OS
* to finish the I/O, thus RESET is possible here. Most
* important: do not change uAsyncIOState. */
if (pCtl->fReset)
break;
if (s->fDMA)
{
if (s->cbTotalTransfer)
{
ataSetStatus(s, ATA_STAT_DRQ);
pCtl->uAsyncIOState = AHCIATA_AIO_DMA;
/* If BMDMA is already started, do the transfer now. */
if (pCtl->BmDma.u8Cmd & BM_CMD_START)
{
Log2(("%s: Ctl: message to async I/O thread, continuing DMA transfer immediately\n", __FUNCTION__));
ataAsyncIOPutRequest(pCtl, &ataDMARequest);
}
}
else
{
Assert(s->uTxDir == PDMBLOCKTXDIR_NONE); /* Any transfer which has an initial transfer size of 0 must be marked as such. */
/* Finish DMA transfer. */
ataDMATransferStop(s);
ataSetIRQ(s);
pCtl->uAsyncIOState = AHCIATA_AIO_NEW;
}
}
else
{
if (s->cbTotalTransfer)
{
ataPIOTransfer(pCtl);
Assert(!pCtl->fRedo);
if (s->fATAPITransfer || s->uTxDir != PDMBLOCKTXDIR_TO_DEVICE)
ataSetIRQ(s);
if (s->uTxDir == PDMBLOCKTXDIR_TO_DEVICE || s->iSourceSink != ATAFN_SS_NULL)
{
/* Write operations and not yet finished transfers
* must be completed in the async I/O thread. */
pCtl->uAsyncIOState = AHCIATA_AIO_PIO;
}
else
{
/* Finished read operation can be handled inline
* in the end of PIO transfer handling code. Linux
* depends on this, as it waits only briefly for
* devices to become ready after incoming data
* transfer. Cannot find anything in the ATA spec
* that backs this assumption, but as all kernels
* are affected (though most of the time it does
* not cause any harm) this must work. */
pCtl->uAsyncIOState = AHCIATA_AIO_NEW;
}
}
else
{
Assert(s->uTxDir == PDMBLOCKTXDIR_NONE); /* Any transfer which has an initial transfer size of 0 must be marked as such. */
/* Finish PIO transfer. */
ataPIOTransfer(pCtl);
Assert(!pCtl->fRedo);
if (!s->fATAPITransfer)
ataSetIRQ(s);
pCtl->uAsyncIOState = AHCIATA_AIO_NEW;
}
}
break;
case AHCIATA_AIO_DMA:
{
BMDMAState *bm = &pCtl->BmDma;
s = &pCtl->aIfs[pCtl->iAIOIf]; /* Do not remove or there's an instant crash after loading the saved state */
ATAFNSS iOriginalSourceSink = (ATAFNSS)s->iSourceSink; /* Used by the hack below, but gets reset by then. */
if (s->uTxDir == PDMBLOCKTXDIR_FROM_DEVICE)
AssertRelease(bm->u8Cmd & BM_CMD_WRITE);
else
AssertRelease(!(bm->u8Cmd & BM_CMD_WRITE));
if (RT_LIKELY(!pCtl->fRedo))
{
/* The specs say that the descriptor table must not cross a
* 4K boundary. */
pCtl->pFirstDMADesc = bm->pvAddr;
pCtl->pLastDMADesc = RT_ALIGN_32(bm->pvAddr + 1, _4K) - sizeof(BMDMADesc);
}
ataDMATransfer(pCtl);
if (RT_UNLIKELY(pCtl->fRedo))
{
LogRel(("ATA: Ctl: redo DMA operation\n"));
ataAsyncIOPutRequest(pCtl, &ataDMARequest);
ataSuspendRedo(pCtl);
break;
}
/* The infamous delay IRQ hack. */
if ( iOriginalSourceSink == ATAFN_SS_WRITE_SECTORS
&& s->cbTotalTransfer == 0
&& pCtl->DelayIRQMillies)
{
/* Delay IRQ for writing. Required to get the Win2K
* installation work reliably (otherwise it crashes,
* usually during component install). So far no better
* solution has been found. */
Log(("%s: delay IRQ hack\n", __FUNCTION__));
PDMCritSectLeave(&pCtl->lock);
RTThreadSleep(pCtl->DelayIRQMillies);
PDMCritSectEnter(&pCtl->lock, VINF_SUCCESS);
}
ataUnsetStatus(s, ATA_STAT_DRQ);
Assert(!pCtl->fChainedTransfer);
Assert(s->iSourceSink == ATAFN_SS_NULL);
if (s->fATAPITransfer)
{
s->uATARegNSector = (s->uATARegNSector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD;
Log2(("%s: Ctl: interrupt reason %#04x\n", __FUNCTION__, s->uATARegNSector));
s->fATAPITransfer = false;
}
ataSetIRQ(s);
pCtl->uAsyncIOState = AHCIATA_AIO_NEW;
break;
}
case AHCIATA_AIO_PIO:
s = &pCtl->aIfs[pCtl->iAIOIf]; /* Do not remove or there's an instant crash after loading the saved state */
if (s->iSourceSink != ATAFN_SS_NULL)
{
bool fRedo;
Log2(("%s: Ctl: calling source/sink function\n", __FUNCTION__));
fRedo = g_apfnSourceSinkFuncs[s->iSourceSink](s);
pCtl->fRedo = fRedo;
if (RT_UNLIKELY(fRedo))
{
LogRel(("ATA: Ctl#%d: redo PIO operation\n"));
ataAsyncIOPutRequest(pCtl, &ataPIORequest);
ataSuspendRedo(pCtl);
break;
}
s->iIOBufferCur = 0;
s->iIOBufferEnd = s->cbElementaryTransfer;
}
else
{
/* Continue a previously started transfer. */
ataUnsetStatus(s, ATA_STAT_BUSY);
ataSetStatus(s, ATA_STAT_READY);
}
/* It is possible that the drives on this controller get RESET
* during the above call to the source/sink function. If that's
* the case, don't restart the transfer and don't finish it the
* usual way. RESET handling took care of all that already.
* Most important: do not change uAsyncIOState. */
if (pCtl->fReset)
break;
if (s->cbTotalTransfer)
{
ataPIOTransfer(pCtl);
ataSetIRQ(s);
if (s->uTxDir == PDMBLOCKTXDIR_TO_DEVICE || s->iSourceSink != ATAFN_SS_NULL)
{
/* Write operations and not yet finished transfers
* must be completed in the async I/O thread. */
pCtl->uAsyncIOState = AHCIATA_AIO_PIO;
}
else
{
/* Finished read operation can be handled inline
* in the end of PIO transfer handling code. Linux
* depends on this, as it waits only briefly for
* devices to become ready after incoming data
* transfer. Cannot find anything in the ATA spec
* that backs this assumption, but as all kernels
* are affected (though most of the time it does
* not cause any harm) this must work. */
pCtl->uAsyncIOState = AHCIATA_AIO_NEW;
}
}
else
{
/* Finish PIO transfer. */
ataPIOTransfer(pCtl);
if ( !pCtl->fChainedTransfer
&& !s->fATAPITransfer
&& s->uTxDir != PDMBLOCKTXDIR_FROM_DEVICE)
{
ataSetIRQ(s);
}
pCtl->uAsyncIOState = AHCIATA_AIO_NEW;
}
break;
case AHCIATA_AIO_RESET_ASSERTED:
pCtl->uAsyncIOState = AHCIATA_AIO_RESET_CLEARED;
ataPIOTransferStop(&pCtl->aIfs[0]);
ataPIOTransferStop(&pCtl->aIfs[1]);
/* Do not change the DMA registers, they are not affected by the
* ATA controller reset logic. It should be sufficient to issue a
* new command, which is now possible as the state is cleared. */
break;
case AHCIATA_AIO_RESET_CLEARED:
pCtl->uAsyncIOState = AHCIATA_AIO_NEW;
pCtl->fReset = false;
LogRel(("ATA: Ctl: finished processing RESET\n"));
for (uint32_t i = 0; i < RT_ELEMENTS(pCtl->aIfs); i++)
{
if (pCtl->aIfs[i].fATAPI)
ataSetStatusValue(&pCtl->aIfs[i], 0); /* NOTE: READY is _not_ set */
else
ataSetStatusValue(&pCtl->aIfs[i], ATA_STAT_READY | ATA_STAT_SEEK);
ataSetSignature(&pCtl->aIfs[i]);
}
break;
case AHCIATA_AIO_ABORT:
/* Abort the current command only if it operates on the same interface. */
if (pCtl->iAIOIf == pReq->u.a.iIf)
{
s = &pCtl->aIfs[pCtl->iAIOIf];
pCtl->uAsyncIOState = AHCIATA_AIO_NEW;
/* Do not change the DMA registers, they are not affected by the
* ATA controller reset logic. It should be sufficient to issue a
* new command, which is now possible as the state is cleared. */
if (pReq->u.a.fResetDrive)
{
ataResetDevice(s);
ataExecuteDeviceDiagnosticSS(s);
}
else
{
ataPIOTransferStop(s);
ataUnsetStatus(s, ATA_STAT_BUSY | ATA_STAT_DRQ | ATA_STAT_SEEK | ATA_STAT_ERR);
ataSetStatus(s, ATA_STAT_READY);
ataSetIRQ(s);
}
}
break;
default:
AssertMsgFailed(("Undefined async I/O state %d\n", pCtl->uAsyncIOState));
}
ataAsyncIORemoveCurrentRequest(pCtl, ReqType);
pReq = ataAsyncIOGetCurrentRequest(pCtl);
if (pCtl->uAsyncIOState == AHCIATA_AIO_NEW && !pCtl->fChainedTransfer)
{
#if defined(DEBUG) || defined(VBOX_WITH_STATISTICS)
STAM_PROFILE_ADV_STOP(&pCtl->StatAsyncTime, a);
#endif /* DEBUG || VBOX_WITH_STATISTICS */
u64TS = RTTimeNanoTS() - u64TS;
uWait = u64TS / 1000;
Log(("%s: Ctl: LUN#%d finished I/O transaction in %d microseconds\n", __FUNCTION__, pCtl->aIfs[pCtl->iAIOIf].iLUN, (uint32_t)(uWait)));
/* Mark command as finished. */
pCtl->aIfs[pCtl->iAIOIf].u64CmdTS = 0;
/*
* Release logging of command execution times depends on the
* command type. ATAPI commands often take longer (due to CD/DVD
* spin up time etc.) so the threshold is different.
*/
if (pCtl->aIfs[pCtl->iAIOIf].uATARegCommand != ATA_PACKET)
{
if (uWait > 8 * 1000 * 1000)
{
/*
* Command took longer than 8 seconds. This is close
* enough or over the guest's command timeout, so place
* an entry in the release log to allow tracking such
* timing errors (which are often caused by the host).
*/
LogRel(("AHCI ATA: execution time for ATA command %#04x was %d seconds\n", pCtl->aIfs[pCtl->iAIOIf].uATARegCommand, uWait / (1000 * 1000)));
}
}
else
{
if (uWait > 20 * 1000 * 1000)
{
/*
* Command took longer than 20 seconds. This is close
* enough or over the guest's command timeout, so place
* an entry in the release log to allow tracking such
* timing errors (which are often caused by the host).
*/
LogRel(("AHCI ATA: execution time for ATAPI command %#04x was %d seconds\n", pCtl->aIfs[pCtl->iAIOIf].aATAPICmd[0], uWait / (1000 * 1000)));
}
}
#if defined(DEBUG) || defined(VBOX_WITH_STATISTICS)
if (uWait < pCtl->StatAsyncMinWait || !pCtl->StatAsyncMinWait)
pCtl->StatAsyncMinWait = uWait;
if (uWait > pCtl->StatAsyncMaxWait)
pCtl->StatAsyncMaxWait = uWait;
STAM_COUNTER_ADD(&pCtl->StatAsyncTimeUS, uWait);
STAM_COUNTER_INC(&pCtl->StatAsyncOps);
#endif /* DEBUG || VBOX_WITH_STATISTICS */
}
PDMCritSectLeave(&pCtl->lock);
}
/* Signal the ultimate idleness. */
RTThreadUserSignal(ThreadSelf);
/* Do not destroy request mutex yet, still needed for proper shutdown. */
pCtl->fShutdown = false;
Log2(("%s: Ctl: return %Rrc\n", __FUNCTION__, rc));
return rc;
}
#endif /* IN_RING3 */
static uint32_t ataBMDMACmdReadB(PAHCIATACONTROLLER pCtl, uint32_t addr)
{
uint32_t val = pCtl->BmDma.u8Cmd;
Log2(("%s: addr=%#06x val=%#04x\n", __FUNCTION__, addr, val));
return val;
}
static void ataBMDMACmdWriteB(PAHCIATACONTROLLER pCtl, uint32_t addr, uint32_t val)
{
Log2(("%s: addr=%#06x val=%#04x\n", __FUNCTION__, addr, val));
if (!(val & BM_CMD_START))
{
pCtl->BmDma.u8Status &= ~BM_STATUS_DMAING;
pCtl->BmDma.u8Cmd = val & (BM_CMD_START | BM_CMD_WRITE);
}
else
{
#ifdef IN_RING3
/* Check whether the guest OS wants to change DMA direction in
* mid-flight. Not allowed, according to the AHCI specs. */
Assert(!(pCtl->BmDma.u8Status & BM_STATUS_DMAING) || !((val ^ pCtl->BmDma.u8Cmd) & 0x04));
pCtl->BmDma.u8Status |= BM_STATUS_DMAING;
pCtl->BmDma.u8Cmd = val & (BM_CMD_START | BM_CMD_WRITE);
/* Do not continue DMA transfers while the RESET line is asserted. */
if (pCtl->fReset)
{
Log2(("%s: Ctl: suppressed continuing DMA transfer as RESET is active\n", __FUNCTION__));
return;
}
/* Do not start DMA transfers if there's a PIO transfer going on. */
if (!pCtl->aIfs[pCtl->iSelectedIf].fDMA)
return;
if (pCtl->aIfs[pCtl->iAIOIf].uATARegStatus & ATA_STAT_DRQ)
{
Log2(("%s: Ctl: message to async I/O thread, continuing DMA transfer\n", __FUNCTION__));
ataAsyncIOPutRequest(pCtl, &ataDMARequest);
}
#else /* !IN_RING3 */
AssertMsgFailed(("DMA START handling is too complicated for GC\n"));
#endif /* IN_RING3 */
}
}
static uint32_t ataBMDMAStatusReadB(PAHCIATACONTROLLER pCtl, uint32_t addr)
{
uint32_t val = pCtl->BmDma.u8Status;
Log2(("%s: addr=%#06x val=%#04x\n", __FUNCTION__, addr, val));
return val;
}
static void ataBMDMAStatusWriteB(PAHCIATACONTROLLER pCtl, uint32_t addr, uint32_t val)
{
Log2(("%s: addr=%#06x val=%#04x\n", __FUNCTION__, addr, val));
pCtl->BmDma.u8Status = (val & (BM_STATUS_D0DMA | BM_STATUS_D1DMA))
| (pCtl->BmDma.u8Status & BM_STATUS_DMAING)
| (pCtl->BmDma.u8Status & ~val & (BM_STATUS_ERROR | BM_STATUS_INT));
}
static uint32_t ataBMDMAAddrReadL(PAHCIATACONTROLLER pCtl, uint32_t addr)
{
uint32_t val = (uint32_t)pCtl->BmDma.pvAddr;
Log2(("%s: addr=%#06x val=%#010x\n", __FUNCTION__, addr, val));
return val;
}
static void ataBMDMAAddrWriteL(PAHCIATACONTROLLER pCtl, uint32_t addr, uint32_t val)
{
Log2(("%s: addr=%#06x val=%#010x\n", __FUNCTION__, addr, val));
pCtl->BmDma.pvAddr = val & ~3;
}
static void ataBMDMAAddrWriteLowWord(PAHCIATACONTROLLER pCtl, uint32_t addr, uint32_t val)
{
Log2(("%s: addr=%#06x val=%#010x\n", __FUNCTION__, addr, val));
pCtl->BmDma.pvAddr = (pCtl->BmDma.pvAddr & 0xFFFF0000) | RT_LOWORD(val & ~3);
}
static void ataBMDMAAddrWriteHighWord(PAHCIATACONTROLLER pCtl, uint32_t addr, uint32_t val)
{
Log2(("%s: addr=%#06x val=%#010x\n", __FUNCTION__, addr, val));
pCtl->BmDma.pvAddr = (RT_LOWORD(val) << 16) | RT_LOWORD(pCtl->BmDma.pvAddr);
}
#define VAL(port, size) ( ((port) & 7) | ((size) << 3) )
/**
* Port I/O Handler for bus master DMA IN operations.
* @see FNIOMIOPORTIN for details.
*/
int ataControllerBMDMAIOPortRead(PAHCIATACONTROLLER pCtl, RTIOPORT Port, uint32_t *pu32, unsigned cb)
{
int rc;
rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_HC_IOPORT_READ);
if (rc != VINF_SUCCESS)
return rc;
switch (VAL(Port, cb))
{
case VAL(0, 1): *pu32 = ataBMDMACmdReadB(pCtl, Port); break;
case VAL(0, 2): *pu32 = ataBMDMACmdReadB(pCtl, Port); break;
case VAL(2, 1): *pu32 = ataBMDMAStatusReadB(pCtl, Port); break;
case VAL(2, 2): *pu32 = ataBMDMAStatusReadB(pCtl, Port); break;
case VAL(4, 4): *pu32 = ataBMDMAAddrReadL(pCtl, Port); break;
default:
AssertMsgFailed(("%s: Unsupported read from port %x size=%d\n", __FUNCTION__, Port, cb));
PDMCritSectLeave(&pCtl->lock);
return VERR_IOM_IOPORT_UNUSED;
}
PDMCritSectLeave(&pCtl->lock);
return rc;
}
/**
* Port I/O Handler for bus master DMA OUT operations.
* @see FNIOMIOPORTOUT for details.
*/
int ataControllerBMDMAIOPortWrite(PAHCIATACONTROLLER pCtl, RTIOPORT Port, uint32_t u32, unsigned cb)
{
int rc;
rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_HC_IOPORT_WRITE);
if (rc != VINF_SUCCESS)
return rc;
switch (VAL(Port, cb))
{
case VAL(0, 1):
#ifndef IN_RING3
if (u32 & BM_CMD_START)
{
rc = VINF_IOM_HC_IOPORT_WRITE;
break;
}
#endif /* !IN_RING3 */
ataBMDMACmdWriteB(pCtl, Port, u32);
break;
case VAL(2, 1): ataBMDMAStatusWriteB(pCtl, Port, u32); break;
case VAL(4, 4): ataBMDMAAddrWriteL(pCtl, Port, u32); break;
case VAL(4, 2): ataBMDMAAddrWriteLowWord(pCtl, Port, u32); break;
case VAL(6, 2): ataBMDMAAddrWriteHighWord(pCtl, Port, u32); break;
default: AssertMsgFailed(("%s: Unsupported write to port %x size=%d val=%x\n", __FUNCTION__, Port, cb, u32)); break;
}
PDMCritSectLeave(&pCtl->lock);
return rc;
}
#undef VAL
#ifdef IN_RING3
#if 0
/**
* Callback function for mapping an PCI I/O region.
*
* @return VBox status code.
* @param pPciDev Pointer to PCI device. Use pPciDev->pDevIns to get the device instance.
* @param iRegion The region number.
* @param GCPhysAddress Physical address of the region. If iType is PCI_ADDRESS_SPACE_IO, this is an
* I/O port, else it's a physical address.
* This address is *NOT* relative to pci_mem_base like earlier!
* @param enmType One of the PCI_ADDRESS_SPACE_* values.
*/
static DECLCALLBACK(int) ataBMDMAIORangeMap(PPCIDEVICE pPciDev, /*unsigned*/ int iRegion, RTGCPHYS GCPhysAddress, uint32_t cb, PCIADDRESSSPACE enmType)
{
PCIATAState *pThis = PCIDEV_2_PCIATASTATE(pPciDev);
int rc = VINF_SUCCESS;
Assert(enmType == PCI_ADDRESS_SPACE_IO);
Assert(iRegion == 4);
AssertMsg(RT_ALIGN(GCPhysAddress, 8) == GCPhysAddress, ("Expected 8 byte alignment. GCPhysAddress=%#x\n", GCPhysAddress));
/* Register the port range. */
for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aCts); i++)
{
int rc2 = PDMDevHlpIOPortRegister(pPciDev->pDevIns, (RTIOPORT)GCPhysAddress + i * 8, 8,
(RTHCPTR)i, ataBMDMAIOPortWrite, ataBMDMAIOPortRead, NULL, NULL, "ATA Bus Master DMA");
AssertRC(rc2);
if (rc2 < rc)
rc = rc2;
if (pThis->fGCEnabled)
{
rc2 = PDMDevHlpIOPortRegisterGC(pPciDev->pDevIns, (RTIOPORT)GCPhysAddress + i * 8, 8,
(RTGCPTR)i, "ataBMDMAIOPortWrite", "ataBMDMAIOPortRead", NULL, NULL, "ATA Bus Master DMA");
AssertRC(rc2);
if (rc2 < rc)
rc = rc2;
}
if (pThis->fR0Enabled)
{
rc2 = PDMDevHlpIOPortRegisterR0(pPciDev->pDevIns, (RTIOPORT)GCPhysAddress + i * 8, 8,
(RTR0PTR)i, "ataBMDMAIOPortWrite", "ataBMDMAIOPortRead", NULL, NULL, "ATA Bus Master DMA");
AssertRC(rc2);
if (rc2 < rc)
rc = rc2;
}
}
return rc;
}
#endif
/**
* Reset the controller to an intial state.
*
* @returns VBox status.
* @param pDevIns The device instance data.
*/
void ataControllerReset(PAHCIATACONTROLLER pCtl)
{
pCtl->iSelectedIf = 0;
pCtl->iAIOIf = 0;
pCtl->BmDma.u8Cmd = 0;
/* Report that both drives present on the bus are in DMA mode. This
* pretends that there is a BIOS that has set it up. Normal reset
* default is 0x00. */
pCtl->BmDma.u8Status = (pCtl->aIfs[0].pDrvBase != NULL ? BM_STATUS_D0DMA : 0)
| (pCtl->aIfs[1].pDrvBase != NULL ? BM_STATUS_D1DMA : 0);
pCtl->BmDma.pvAddr = 0;
pCtl->fReset = true;
pCtl->fRedo = false;
pCtl->fRedoIdle = false;
ataAsyncIOClearRequests(pCtl);
Log2(("%s: Ctl: message to async I/O thread, reset controller\n", __FUNCTION__));
ataAsyncIOPutRequest(pCtl, &ataResetARequest);
ataAsyncIOPutRequest(pCtl, &ataResetCRequest);
if (!ataWaitForAsyncIOIsIdle(pCtl, 30000))
AssertReleaseMsgFailed(("Async I/O thread busy after reset\n"));
for (uint32_t i = 0; i < RT_ELEMENTS(pCtl->aIfs); i++)
ataResetDevice(&pCtl->aIfs[i]);
}
#if 0
/* -=-=-=-=-=- AHCIATADevState::IBase -=-=-=-=-=- */
/**
* Queries an interface to the driver.
*
* @returns Pointer to interface.
* @returns NULL if the interface was not supported by the device.
* @param pInterface Pointer to AHCIATADevState::IBase.
* @param enmInterface The requested interface identification.
*/
static DECLCALLBACK(void *) ataQueryInterface(PPDMIBASE pInterface, PDMINTERFACE enmInterface)
{
AHCIATADevState *pIf = PDMIBASE_2_ATASTATE(pInterface);
switch (enmInterface)
{
case PDMINTERFACE_BASE:
return &pIf->IBase;
case PDMINTERFACE_BLOCK_PORT:
return &pIf->IPort;
case PDMINTERFACE_MOUNT_NOTIFY:
return &pIf->IMountNotify;
default:
return NULL;
}
}
#endif
#endif /* IN_RING3 */
/* -=-=-=-=-=- Wrappers -=-=-=-=-=- */
/**
* Port I/O Handler for primary port range OUT operations.
* @see FNIOMIOPORTOUT for details.
*/
int ataControllerIOPortWrite1(PAHCIATACONTROLLER pCtl, RTIOPORT Port, uint32_t u32, unsigned cb)
{
int rc = VINF_SUCCESS;
rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_HC_IOPORT_WRITE);
if (rc != VINF_SUCCESS)
return rc;
if (cb == 1)
rc = ataIOPortWriteU8(pCtl, Port, u32);
else if (Port == pCtl->IOPortBase1)
{
Assert(cb == 2 || cb == 4);
rc = ataDataWrite(pCtl, Port, cb, (const uint8_t *)&u32);
}
else
AssertMsgFailed(("ataIOPortWrite1: unsupported write to port %x val=%x size=%d\n", Port, u32, cb));
PDMCritSectLeave(&pCtl->lock);
return rc;
}
/**
* Port I/O Handler for primary port range IN operations.
* @see FNIOMIOPORTIN for details.
*/
int ataControllerIOPortRead1(PAHCIATACONTROLLER pCtl, RTIOPORT Port, uint32_t *pu32, unsigned cb)
{
int rc = VINF_SUCCESS;
rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_HC_IOPORT_READ);
if (rc != VINF_SUCCESS)
return rc;
if (cb == 1)
{
rc = ataIOPortReadU8(pCtl, Port, pu32);
}
else if (Port == pCtl->IOPortBase1)
{
Assert(cb == 2 || cb == 4);
rc = ataDataRead(pCtl, Port, cb, (uint8_t *)pu32);
if (cb == 2)
*pu32 &= 0xffff;
}
else
{
AssertMsgFailed(("ataIOPortRead1: unsupported read from port %x size=%d\n", Port, cb));
rc = VERR_IOM_IOPORT_UNUSED;
}
PDMCritSectLeave(&pCtl->lock);
return rc;
}
#ifndef IN_RING0 /** @todo do this in ring-0 as well. */
/**
* Port I/O Handler for primary port range IN string operations.
* @see FNIOMIOPORTINSTRING for details.
*/
int ataControllerIOPortReadStr1(PAHCIATACONTROLLER pCtl, RTIOPORT Port, RTGCPTR *pGCPtrDst, PRTGCUINTREG pcTransfer, unsigned cb)
{
int rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_HC_IOPORT_READ);
if (rc != VINF_SUCCESS)
return rc;
if (Port == pCtl->IOPortBase1)
{
uint32_t cTransAvailable, cTransfer = *pcTransfer, cbTransfer;
RTGCPTR GCDst = *pGCPtrDst;
AHCIATADevState *s = &pCtl->aIfs[pCtl->iSelectedIf];
Assert(cb == 2 || cb == 4);
cTransAvailable = (s->iIOBufferPIODataEnd - s->iIOBufferPIODataStart) / cb;
#ifndef IN_RING3
/* The last transfer unit cannot be handled in GC, as it involves thread communication. */
cTransAvailable--;
#endif /* !IN_RING3 */
/* Do not handle the dummy transfer stuff here, leave it to the single-word transfers.
* They are not performance-critical and generally shouldn't occur at all. */
if (cTransAvailable > cTransfer)
cTransAvailable = cTransfer;
cbTransfer = cTransAvailable * cb;
PPDMDEVINS pDevIns = pCtl->CTX_SUFF(pDevIns);
rc = PGMPhysSimpleDirtyWriteGCPtr(PDMDevHlpGetVMCPU(pDevIns), GCDst, s->CTX_SUFF(pbIOBuffer) + s->iIOBufferPIODataStart, cbTransfer);
Assert(rc == VINF_SUCCESS);
if (cbTransfer)
Log3(("%s: addr=%#x val=%.*Rhxs\n", __FUNCTION__, Port, cbTransfer, s->CTX_SUFF(pbIOBuffer) + s->iIOBufferPIODataStart));
s->iIOBufferPIODataStart += cbTransfer;
*pGCPtrDst = (RTGCPTR)((RTGCUINTPTR)GCDst + cbTransfer);
*pcTransfer = cTransfer - cTransAvailable;
#ifdef IN_RING3
if (s->iIOBufferPIODataStart >= s->iIOBufferPIODataEnd)
ataPIOTransferFinish(pCtl, s);
#endif /* IN_RING3 */
}
PDMCritSectLeave(&pCtl->lock);
return rc;
}
/**
* Port I/O Handler for primary port range OUT string operations.
* @see FNIOMIOPORTOUTSTRING for details.
*/
int ataControllerIOPortWriteStr1(PAHCIATACONTROLLER pCtl, RTIOPORT Port, RTGCPTR *pGCPtrSrc, PRTGCUINTREG pcTransfer, unsigned cb)
{
int rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_HC_IOPORT_WRITE);
if (rc != VINF_SUCCESS)
return rc;
if (Port == pCtl->IOPortBase1)
{
uint32_t cTransAvailable, cTransfer = *pcTransfer, cbTransfer;
RTGCPTR GCSrc = *pGCPtrSrc;
AHCIATADevState *s = &pCtl->aIfs[pCtl->iSelectedIf];
Assert(cb == 2 || cb == 4);
cTransAvailable = (s->iIOBufferPIODataEnd - s->iIOBufferPIODataStart) / cb;
#ifndef IN_RING3
/* The last transfer unit cannot be handled in GC, as it involves thread communication. */
cTransAvailable--;
#endif /* !IN_RING3 */
/* Do not handle the dummy transfer stuff here, leave it to the single-word transfers.
* They are not performance-critical and generally shouldn't occur at all. */
if (cTransAvailable > cTransfer)
cTransAvailable = cTransfer;
cbTransfer = cTransAvailable * cb;
PPDMDEVINS pDevIns = pCtl->CTX_SUFF(pDevIns);
rc = PGMPhysSimpleReadGCPtr(PDMDevHlpGetVMCPU(pDevIns), s->CTX_SUFF(pbIOBuffer) + s->iIOBufferPIODataStart, GCSrc, cbTransfer);
Assert(rc == VINF_SUCCESS);
if (cbTransfer)
Log3(("%s: addr=%#x val=%.*Rhxs\n", __FUNCTION__, Port, cbTransfer, s->CTX_SUFF(pbIOBuffer) + s->iIOBufferPIODataStart));
s->iIOBufferPIODataStart += cbTransfer;
*pGCPtrSrc = (RTGCPTR)((RTGCUINTPTR)GCSrc + cbTransfer);
*pcTransfer = cTransfer - cTransAvailable;
#ifdef IN_RING3
if (s->iIOBufferPIODataStart >= s->iIOBufferPIODataEnd)
ataPIOTransferFinish(pCtl, s);
#endif /* IN_RING3 */
}
PDMCritSectLeave(&pCtl->lock);
return rc;
}
#endif /* !IN_RING0 */
/**
* Port I/O Handler for secondary port range OUT operations.
* @see FNIOMIOPORTOUT for details.
*/
int ataControllerIOPortWrite2(PAHCIATACONTROLLER pCtl, RTIOPORT Port, uint32_t u32, unsigned cb)
{
int rc;
if (cb != 1)
return VINF_SUCCESS;
rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_HC_IOPORT_WRITE);
if (rc != VINF_SUCCESS)
return rc;
rc = ataControlWrite(pCtl, Port, u32);
PDMCritSectLeave(&pCtl->lock);
return rc;
}
/**
* Port I/O Handler for secondary port range IN operations.
* @see FNIOMIOPORTIN for details.
*/
int ataControllerIOPortRead2(PAHCIATACONTROLLER pCtl, RTIOPORT Port, uint32_t *pu32, unsigned cb)
{
int rc;
if (cb != 1)
return VERR_IOM_IOPORT_UNUSED;
rc = PDMCritSectEnter(&pCtl->lock, VINF_IOM_HC_IOPORT_READ);
if (rc != VINF_SUCCESS)
return rc;
*pu32 = ataStatusRead(pCtl, Port);
PDMCritSectLeave(&pCtl->lock);
return VINF_SUCCESS;
}
#ifdef IN_RING3
/**
* Waits for all async I/O threads to complete whatever they
* are doing at the moment.
*
* @returns true on success.
* @returns false when one or more threads is still processing.
* @param pThis Pointer to the instance data.
* @param cMillies How long to wait (total).
*/
static bool ataWaitForAllAsyncIOIsIdle(PAHCIATACONTROLLER pCtl, unsigned cMillies)
{
uint64_t u64Start;
PPDMDEVINS pDevIns = pCtl->CTXALLSUFF(pDevIns);
bool fAllIdle = false;
/*
* Wait for any pending async operation to finish
*/
u64Start = RTTimeMilliTS();
for (;;)
{
/* Check all async I/O threads. */
fAllIdle = true;
fAllIdle &= ataAsyncIOIsIdle(pCtl, false);
if (!fAllIdle)
break;
if ( fAllIdle
|| RTTimeMilliTS() - u64Start >= cMillies)
break;
/* Sleep for a bit. */
RTThreadSleep(100);
}
if (!fAllIdle)
LogRel(("ATA: Ctl is still executing, DevSel=%d AIOIf=%d CmdIf0=%#04x CmdIf1=%#04x\n",
pCtl->iSelectedIf, pCtl->iAIOIf,
pCtl->aIfs[0].uATARegCommand, pCtl->aIfs[1].uATARegCommand));
return fAllIdle;
}
DECLINLINE(void) ataRelocBuffer(PPDMDEVINS pDevIns, AHCIATADevState *s)
{
if (s->pbIOBufferR3)
s->pbIOBufferRC = MMHyperR3ToRC(PDMDevHlpGetVM(pDevIns), s->pbIOBufferR3);
}
/**
* @copydoc FNPDMDEVRELOCATE
*/
void ataControllerRelocate(PAHCIATACONTROLLER pCtl, RTGCINTPTR offDelta)
{
PPDMDEVINS pDevIns = pCtl->CTXALLSUFF(pDevIns);
pCtl->pDevInsRC += offDelta;
pCtl->aIfs[0].pDevInsRC += offDelta;
pCtl->aIfs[0].pControllerRC += offDelta;
ataRelocBuffer(pDevIns, &pCtl->aIfs[0]);
pCtl->aIfs[1].pDevInsRC += offDelta;
pCtl->aIfs[1].pControllerRC += offDelta;
ataRelocBuffer(pDevIns, &pCtl->aIfs[1]);
}
/**
* Destroy a controller instance.
*
* Most VM resources are freed by the VM. This callback is provided so that any non-VM
* resources can be freed correctly.
*
* @param pCtl The controller instance.
*/
int ataControllerDestroy(PAHCIATACONTROLLER pCtl)
{
int rc;
Log(("%s:\n", __FUNCTION__));
/*
* Terminate the async helper thread and wait for it to finish up.
*/
if (pCtl->AsyncIOThread != NIL_RTTHREAD)
{
ASMAtomicWriteU32(&pCtl->fShutdown, true);
rc = RTSemEventSignal(pCtl->AsyncIOSem);
AssertRC(rc);
rc = RTThreadWait(pCtl->AsyncIOThread, 30000 /* 30 s*/, NULL);
if (RT_SUCCESS(rc))
pCtl->AsyncIOThread = NIL_RTTHREAD;
else
LogRel(("PIIX3 ATA Dtor: Ctl/irq=%u is still executing, DevSel=%d AIOIf=%d CmdIf0=%#04x CmdIf1=%#04x rc=%Rrc\n",
pCtl->irq, pCtl->iSelectedIf, pCtl->iAIOIf,
pCtl->aIfs[0].uATARegCommand, pCtl->aIfs[1].uATARegCommand, rc));
}
/*
* Now the request mutexes are no longer needed. Free resources.
*/
if (pCtl->AsyncIORequestMutex != NIL_RTSEMMUTEX)
{
RTSemMutexDestroy(pCtl->AsyncIORequestMutex);
pCtl->AsyncIORequestMutex = NIL_RTSEMMUTEX;
}
if (pCtl->AsyncIOSem != NIL_RTSEMEVENT)
{
RTSemEventDestroy(pCtl->AsyncIOSem);
pCtl->AsyncIOSem = NIL_RTSEMEVENT;
}
if (pCtl->SuspendIOSem != NIL_RTSEMEVENT)
{
RTSemEventDestroy(pCtl->SuspendIOSem);
pCtl->SuspendIOSem = NIL_RTSEMEVENT;
}
/* try one final time */
if (pCtl->AsyncIOThread != NIL_RTTHREAD)
{
rc = RTThreadWait(pCtl->AsyncIOThread, 1 /*ms*/, NULL);
if (RT_SUCCESS(rc))
{
pCtl->AsyncIOThread = NIL_RTTHREAD;
LogRel(("AHCI ATA Dtor: Ctl/irq=%u actually completed.\n", pCtl->irq));
}
}
return VINF_SUCCESS;
}
#if 0
/**
* Detach notification.
*
* The DVD drive has been unplugged.
*
* @param pDevIns The device instance.
* @param iLUN The logical unit which is being detached.
*/
static DECLCALLBACK(void) ataDetach(PPDMDEVINS pDevIns, unsigned iLUN)
{
PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
PAHCIATACONTROLLER pCtl;
AHCIATADevState *pIf;
unsigned iController;
unsigned iInterface;
/*
* Locate the controller and stuff.
*/
iController = iLUN / RT_ELEMENTS(pThis->aCts[0].aIfs);
AssertReleaseMsg(iController < RT_ELEMENTS(pThis->aCts), ("iController=%d iLUN=%d\n", iController, iLUN));
pCtl = &pThis->aCts[iController];
iInterface = iLUN % RT_ELEMENTS(pThis->aCts[0].aIfs);
pIf = &pCtl->aIfs[iInterface];
/*
* Zero some important members.
*/
pIf->pDrvBase = NULL;
pIf->pDrvBlock = NULL;
pIf->pDrvBlockBios = NULL;
pIf->pDrvMount = NULL;
/*
* Just in case there was a medium inserted. Only required when attached to a physical drive
* in passthrough mode as in virtual ATAPI mode we've got an unmount notification.
*/
if (pIf->fATAPIPassthrough)
ataMediumRemoved(pIf);
}
#endif
/**
* Configure a LUN.
*
* @returns VBox status code.
* @param pDevIns The device instance.
* @param pIf The ATA unit state.
*/
static int ataConfigLun(PPDMDEVINS pDevIns, AHCIATADevState *pIf)
{
int rc;
PDMBLOCKTYPE enmType;
/*
* Query Block, Bios and Mount interfaces.
*/
pIf->pDrvBlock = (PDMIBLOCK *)pIf->pDrvBase->pfnQueryInterface(pIf->pDrvBase, PDMINTERFACE_BLOCK);
if (!pIf->pDrvBlock)
{
AssertMsgFailed(("Configuration error: LUN#%d hasn't a block interface!\n", pIf->iLUN));
return VERR_PDM_MISSING_INTERFACE;
}
/** @todo implement the BIOS invisible code path. */
pIf->pDrvBlockBios = (PDMIBLOCKBIOS *)pIf->pDrvBase->pfnQueryInterface(pIf->pDrvBase, PDMINTERFACE_BLOCK_BIOS);
if (!pIf->pDrvBlockBios)
{
AssertMsgFailed(("Configuration error: LUN#%d hasn't a block BIOS interface!\n", pIf->iLUN));
return VERR_PDM_MISSING_INTERFACE;
}
pIf->pDrvMount = (PDMIMOUNT *)pIf->pDrvBase->pfnQueryInterface(pIf->pDrvBase, PDMINTERFACE_MOUNT);
/*
* Validate type.
*/
enmType = pIf->pDrvBlock->pfnGetType(pIf->pDrvBlock);
if ( enmType != PDMBLOCKTYPE_CDROM
&& enmType != PDMBLOCKTYPE_DVD
&& enmType != PDMBLOCKTYPE_HARD_DISK)
{
AssertMsgFailed(("Configuration error: LUN#%d isn't a disk or cd/dvd-rom. enmType=%d\n", pIf->iLUN, enmType));
return VERR_PDM_UNSUPPORTED_BLOCK_TYPE;
}
if ( ( enmType == PDMBLOCKTYPE_DVD
|| enmType == PDMBLOCKTYPE_CDROM)
&& !pIf->pDrvMount)
{
AssertMsgFailed(("Internal error: cdrom without a mountable interface, WTF???!\n"));
return VERR_INTERNAL_ERROR;
}
pIf->fATAPI = enmType == PDMBLOCKTYPE_DVD || enmType == PDMBLOCKTYPE_CDROM;
pIf->fATAPIPassthrough = pIf->fATAPI ? (pIf->pDrvBlock->pfnSendCmd != NULL) : false;
/*
* Allocate I/O buffer.
*/
PVM pVM = PDMDevHlpGetVM(pDevIns);
if (pIf->cbIOBuffer)
{
/* Buffer is (probably) already allocated. Validate the fields,
* because memory corruption can also overwrite pIf->cbIOBuffer. */
if (pIf->fATAPI)
AssertRelease(pIf->cbIOBuffer == _128K);
else
AssertRelease(pIf->cbIOBuffer == ATA_MAX_MULT_SECTORS * 512);
Assert(pIf->pbIOBufferR3);
Assert(pIf->pbIOBufferR0 == MMHyperR3ToR0(pVM, pIf->pbIOBufferR3));
Assert(pIf->pbIOBufferRC == MMHyperR3ToRC(pVM, pIf->pbIOBufferR3));
}
else
{
if (pIf->fATAPI)
pIf->cbIOBuffer = _128K;
else
pIf->cbIOBuffer = ATA_MAX_MULT_SECTORS * 512;
Assert(!pIf->pbIOBufferR3);
rc = MMR3HyperAllocOnceNoRel(pVM, pIf->cbIOBuffer, 0, MM_TAG_PDM_DEVICE_USER, (void **)&pIf->pbIOBufferR3);
if (RT_FAILURE(rc))
return VERR_NO_MEMORY;
pIf->pbIOBufferR0 = MMHyperR3ToR0(pVM, pIf->pbIOBufferR3);
pIf->pbIOBufferRC = MMHyperR3ToRC(pVM, pIf->pbIOBufferR3);
}
/*
* Init geometry (only for non-CD/DVD media).
*/
if (pIf->fATAPI)
{
pIf->cTotalSectors = pIf->pDrvBlock->pfnGetSize(pIf->pDrvBlock) / 2048;
pIf->PCHSGeometry.cCylinders = 0; /* dummy */
pIf->PCHSGeometry.cHeads = 0; /* dummy */
pIf->PCHSGeometry.cSectors = 0; /* dummy */
LogRel(("ATA: LUN#%d: CD/DVD, total number of sectors %Ld, passthrough %s\n", pIf->iLUN, pIf->cTotalSectors, (pIf->fATAPIPassthrough ? "enabled" : "disabled")));
}
else
{
pIf->cTotalSectors = pIf->pDrvBlock->pfnGetSize(pIf->pDrvBlock) / 512;
rc = pIf->pDrvBlockBios->pfnGetPCHSGeometry(pIf->pDrvBlockBios,
&pIf->PCHSGeometry);
if (rc == VERR_PDM_MEDIA_NOT_MOUNTED)
{
pIf->PCHSGeometry.cCylinders = 0;
pIf->PCHSGeometry.cHeads = 16; /*??*/
pIf->PCHSGeometry.cSectors = 63; /*??*/
}
else if (rc == VERR_PDM_GEOMETRY_NOT_SET)
{
pIf->PCHSGeometry.cCylinders = 0; /* autodetect marker */
rc = VINF_SUCCESS;
}
AssertRC(rc);
if ( pIf->PCHSGeometry.cCylinders == 0
|| pIf->PCHSGeometry.cHeads == 0
|| pIf->PCHSGeometry.cSectors == 0
)
{
uint64_t cCylinders = pIf->cTotalSectors / (16 * 63);
pIf->PCHSGeometry.cCylinders = RT_MAX(RT_MIN(cCylinders, 16383), 1);
pIf->PCHSGeometry.cHeads = 16;
pIf->PCHSGeometry.cSectors = 63;
/* Set the disk geometry information. */
rc = pIf->pDrvBlockBios->pfnSetPCHSGeometry(pIf->pDrvBlockBios,
&pIf->PCHSGeometry);
}
LogRel(("AHCI ATA: LUN#%d: disk, PCHS=%u/%u/%u, total number of sectors %Ld\n", pIf->iLUN, pIf->PCHSGeometry.cCylinders, pIf->PCHSGeometry.cHeads, pIf->PCHSGeometry.cSectors, pIf->cTotalSectors));
}
return VINF_SUCCESS;
}
#if 0
/**
* Attach command.
*
* This is called when we change block driver for the DVD drive.
*
* @returns VBox status code.
* @param pDevIns The device instance.
* @param iLUN The logical unit which is being detached.
*/
static DECLCALLBACK(int) ataAttach(PPDMDEVINS pDevIns, unsigned iLUN)
{
PCIATAState *pThis = PDMINS_2_DATA(pDevIns, PCIATAState *);
PAHCIATACONTROLLER pCtl;
AHCIATADevState *pIf;
int rc;
unsigned iController;
unsigned iInterface;
/*
* Locate the controller and stuff.
*/
iController = iLUN / RT_ELEMENTS(pThis->aCts[0].aIfs);
AssertReleaseMsg(iController < RT_ELEMENTS(pThis->aCts), ("iController=%d iLUN=%d\n", iController, iLUN));
pCtl = &pThis->aCts[iController];
iInterface = iLUN % RT_ELEMENTS(pThis->aCts[0].aIfs);
pIf = &pCtl->aIfs[iInterface];
/* the usual paranoia */
AssertRelease(!pIf->pDrvBase);
AssertRelease(!pIf->pDrvBlock);
Assert(ATADEVSTATE_2_CONTROLLER(pIf) == pCtl);
Assert(pIf->iLUN == iLUN);
/*
* Try attach the block device and get the interfaces,
* required as well as optional.
*/
rc = PDMDevHlpDriverAttach(pDevIns, pIf->iLUN, &pIf->IBase, &pIf->pDrvBase, NULL);
if (RT_SUCCESS(rc))
{
rc = ataConfigLun(pDevIns, pIf);
/*
* In case there is a new medium inserted. In virtual ATAPI mode we get an mount
* notification.
*/
if (pIf->fATAPIPassthrough)
ataMediumInserted(pIf);
}
else
AssertMsgFailed(("Failed to attach LUN#%d. rc=%Rrc\n", pIf->iLUN, rc));
if (RT_FAILURE(rc))
{
pIf->pDrvBase = NULL;
pIf->pDrvBlock = NULL;
}
return rc;
}
#endif
/**
* Suspend notification.
*
* @returns VBox status.
* @param pDevIns The device instance data.
*/
void ataControllerSuspend(PAHCIATACONTROLLER pCtl)
{
Log(("%s:\n", __FUNCTION__));
if (!ataWaitForAllAsyncIOIsIdle(pCtl, 20000))
AssertMsgFailed(("Async I/O didn't stop in 20 seconds!\n"));
return;
}
/**
* Resume notification.
*
* @returns VBox status.
* @param pDevIns The device instance data.
*/
void ataControllerResume(PAHCIATACONTROLLER pCtl)
{
int rc;
Log(("%s:\n", __FUNCTION__));
if (pCtl->fRedo && pCtl->fRedoIdle)
{
rc = RTSemEventSignal(pCtl->SuspendIOSem);
AssertRC(rc);
}
return;
}
/**
* Power Off notification.
*
* @returns VBox status.
* @param pCtl The controller instance.
*/
void ataControllerPowerOff(PAHCIATACONTROLLER pCtl)
{
Log(("%s:\n", __FUNCTION__));
if (!ataWaitForAllAsyncIOIsIdle(pCtl, 20000))
AssertMsgFailed(("Async I/O didn't stop in 20 seconds!\n"));
return;
}
/**
* Prepare state save and load operation.
*
* @returns VBox status code.
* @param pDevIns Device instance of the device which registered the data unit.
* @param pSSM SSM operation handle.
*/
static int ataSaveLoadPrep(PAHCIATACONTROLLER pCtl)
{
/* sanity - the suspend notification will wait on the async stuff. */
Assert(ataAsyncIOIsIdle(pCtl, false));
if (!ataAsyncIOIsIdle(pCtl, false))
return VERR_SSM_IDE_ASYNC_TIMEOUT;
return VINF_SUCCESS;
}
/**
* Prepare state save operation.
*
* @returns VBox status code.
* @param pCtl The controller instance.
* @param pSSM SSM operation handle.
*/
DECLCALLBACK(int) ataControllerSavePrep(PAHCIATACONTROLLER pCtl, PSSMHANDLE pSSM)
{
return ataSaveLoadPrep(pCtl);
}
/**
* Prepare state load operation.
*
* @returns VBox status code.
* @param pCtl The controller instance.
* @param pSSM SSM operation handle.
*/
DECLCALLBACK(int) ataControllerLoadPrep(PAHCIATACONTROLLER pCtl, PSSMHANDLE pSSM)
{
return ataSaveLoadPrep(pCtl);
}
/**
* Saves a state of the ATA device.
*
* @returns VBox status code.
* @param pCtl Controller instance.
* @param pSSMHandle The handle to save the state to.
*/
int ataControllerSaveExec(PAHCIATACONTROLLER pCtl, PSSMHANDLE pSSMHandle)
{
SSMR3PutU32(pSSMHandle, ATA_CTL_SAVED_STATE_VERSION);
SSMR3PutU8(pSSMHandle, pCtl->iSelectedIf);
SSMR3PutU8(pSSMHandle, pCtl->iAIOIf);
SSMR3PutU8(pSSMHandle, pCtl->uAsyncIOState);
SSMR3PutBool(pSSMHandle, pCtl->fChainedTransfer);
SSMR3PutBool(pSSMHandle, pCtl->fReset);
SSMR3PutBool(pSSMHandle, pCtl->fRedo);
SSMR3PutBool(pSSMHandle, pCtl->fRedoIdle);
SSMR3PutBool(pSSMHandle, pCtl->fRedoDMALastDesc);
SSMR3PutMem(pSSMHandle, &pCtl->BmDma, sizeof(pCtl->BmDma));
SSMR3PutGCPhys32(pSSMHandle, pCtl->pFirstDMADesc);
SSMR3PutGCPhys32(pSSMHandle, pCtl->pLastDMADesc);
SSMR3PutGCPhys32(pSSMHandle, pCtl->pRedoDMABuffer);
SSMR3PutU32(pSSMHandle, pCtl->cbRedoDMABuffer);
for (uint32_t j = 0; j < RT_ELEMENTS(pCtl->aIfs); j++)
{
SSMR3PutBool(pSSMHandle, pCtl->aIfs[j].fLBA48);
SSMR3PutBool(pSSMHandle, pCtl->aIfs[j].fATAPI);
SSMR3PutBool(pSSMHandle, pCtl->aIfs[j].fIrqPending);
SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].cMultSectors);
SSMR3PutU32(pSSMHandle, pCtl->aIfs[j].PCHSGeometry.cCylinders);
SSMR3PutU32(pSSMHandle, pCtl->aIfs[j].PCHSGeometry.cHeads);
SSMR3PutU32(pSSMHandle, pCtl->aIfs[j].PCHSGeometry.cSectors);
SSMR3PutU32(pSSMHandle, pCtl->aIfs[j].cSectorsPerIRQ);
SSMR3PutU64(pSSMHandle, pCtl->aIfs[j].cTotalSectors);
SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uATARegFeature);
SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uATARegFeatureHOB);
SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uATARegError);
SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uATARegNSector);
SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uATARegNSectorHOB);
SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uATARegSector);
SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uATARegSectorHOB);
SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uATARegLCyl);
SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uATARegLCylHOB);
SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uATARegHCyl);
SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uATARegHCylHOB);
SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uATARegSelect);
SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uATARegStatus);
SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uATARegCommand);
SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uATARegDevCtl);
SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uATATransferMode);
SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].uTxDir);
SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].iBeginTransfer);
SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].iSourceSink);
SSMR3PutBool(pSSMHandle, pCtl->aIfs[j].fDMA);
SSMR3PutBool(pSSMHandle, pCtl->aIfs[j].fATAPITransfer);
SSMR3PutU32(pSSMHandle, pCtl->aIfs[j].cbTotalTransfer);
SSMR3PutU32(pSSMHandle, pCtl->aIfs[j].cbElementaryTransfer);
SSMR3PutU32(pSSMHandle, pCtl->aIfs[j].iIOBufferCur);
SSMR3PutU32(pSSMHandle, pCtl->aIfs[j].iIOBufferEnd);
SSMR3PutU32(pSSMHandle, pCtl->aIfs[j].iIOBufferPIODataStart);
SSMR3PutU32(pSSMHandle, pCtl->aIfs[j].iIOBufferPIODataEnd);
SSMR3PutU32(pSSMHandle, pCtl->aIfs[j].iATAPILBA);
SSMR3PutU32(pSSMHandle, pCtl->aIfs[j].cbATAPISector);
SSMR3PutMem(pSSMHandle, &pCtl->aIfs[j].aATAPICmd, sizeof(pCtl->aIfs[j].aATAPICmd));
SSMR3PutMem(pSSMHandle, &pCtl->aIfs[j].abATAPISense, sizeof(pCtl->aIfs[j].abATAPISense));
SSMR3PutU8(pSSMHandle, pCtl->aIfs[j].cNotifiedMediaChange);
SSMR3PutU32(pSSMHandle, pCtl->aIfs[j].MediaEventStatus);
SSMR3PutMem(pSSMHandle, pCtl->aIfs[j].pLed, sizeof(PDMLED));
SSMR3PutU32(pSSMHandle, pCtl->aIfs[j].cbIOBuffer);
if (pCtl->aIfs[j].cbIOBuffer)
SSMR3PutMem(pSSMHandle, pCtl->aIfs[j].CTX_SUFF(pbIOBuffer), pCtl->aIfs[j].cbIOBuffer);
else
Assert(pCtl->aIfs[j].CTX_SUFF(pbIOBuffer) == NULL);
}
return SSMR3PutU32(pSSMHandle, ~0); /* sanity/terminator */
}
/**
* Loads a saved ATA device state.
*
* @returns VBox status code.
* @param pDevIns The device instance.
* @param pSSMHandle The handle to the saved state.
*/
int ataControllerLoadExec(PAHCIATACONTROLLER pCtl, PSSMHANDLE pSSMHandle)
{
int rc;
uint32_t u32Version;
uint32_t u32;
/* Test for correct version. */
rc = SSMR3GetU32(pSSMHandle, &u32Version);
AssertRCReturn(rc, rc);
if ( u32Version != ATA_CTL_SAVED_STATE_VERSION
&& u32Version != ATA_CTL_SAVED_STATE_VERSION_WITHOUT_FULL_SENSE
&& u32Version != ATA_CTL_SAVED_STATE_VERSION_WITHOUT_EVENT_STATUS)
{
AssertMsgFailed(("u32Version=%d\n", u32Version));
return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
}
/* integrity check */
if (!ataAsyncIOIsIdle(pCtl, false))
{
AssertMsgFailed(("Async I/O for controller is active\n"));
return VERR_SSM_DATA_UNIT_FORMAT_CHANGED;
}
SSMR3GetU8(pSSMHandle, &pCtl->iSelectedIf);
SSMR3GetU8(pSSMHandle, &pCtl->iAIOIf);
SSMR3GetU8(pSSMHandle, &pCtl->uAsyncIOState);
SSMR3GetBool(pSSMHandle, &pCtl->fChainedTransfer);
SSMR3GetBool(pSSMHandle, (bool *)&pCtl->fReset);
SSMR3GetBool(pSSMHandle, (bool *)&pCtl->fRedo);
SSMR3GetBool(pSSMHandle, (bool *)&pCtl->fRedoIdle);
SSMR3GetBool(pSSMHandle, (bool *)&pCtl->fRedoDMALastDesc);
SSMR3GetMem(pSSMHandle, &pCtl->BmDma, sizeof(pCtl->BmDma));
SSMR3GetGCPhys32(pSSMHandle, &pCtl->pFirstDMADesc);
SSMR3GetGCPhys32(pSSMHandle, &pCtl->pLastDMADesc);
SSMR3GetGCPhys32(pSSMHandle, &pCtl->pRedoDMABuffer);
SSMR3GetU32(pSSMHandle, &pCtl->cbRedoDMABuffer);
for (uint32_t j = 0; j < RT_ELEMENTS(pCtl->aIfs); j++)
{
SSMR3GetBool(pSSMHandle, &pCtl->aIfs[j].fLBA48);
SSMR3GetBool(pSSMHandle, &pCtl->aIfs[j].fATAPI);
SSMR3GetBool(pSSMHandle, &pCtl->aIfs[j].fIrqPending);
SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].cMultSectors);
SSMR3GetU32(pSSMHandle, &pCtl->aIfs[j].PCHSGeometry.cCylinders);
SSMR3GetU32(pSSMHandle, &pCtl->aIfs[j].PCHSGeometry.cHeads);
SSMR3GetU32(pSSMHandle, &pCtl->aIfs[j].PCHSGeometry.cSectors);
SSMR3GetU32(pSSMHandle, &pCtl->aIfs[j].cSectorsPerIRQ);
SSMR3GetU64(pSSMHandle, &pCtl->aIfs[j].cTotalSectors);
SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uATARegFeature);
SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uATARegFeatureHOB);
SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uATARegError);
SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uATARegNSector);
SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uATARegNSectorHOB);
SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uATARegSector);
SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uATARegSectorHOB);
SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uATARegLCyl);
SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uATARegLCylHOB);
SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uATARegHCyl);
SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uATARegHCylHOB);
SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uATARegSelect);
SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uATARegStatus);
SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uATARegCommand);
SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uATARegDevCtl);
SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uATATransferMode);
SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].uTxDir);
SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].iBeginTransfer);
SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].iSourceSink);
SSMR3GetBool(pSSMHandle, &pCtl->aIfs[j].fDMA);
SSMR3GetBool(pSSMHandle, &pCtl->aIfs[j].fATAPITransfer);
SSMR3GetU32(pSSMHandle, &pCtl->aIfs[j].cbTotalTransfer);
SSMR3GetU32(pSSMHandle, &pCtl->aIfs[j].cbElementaryTransfer);
SSMR3GetU32(pSSMHandle, &pCtl->aIfs[j].iIOBufferCur);
SSMR3GetU32(pSSMHandle, &pCtl->aIfs[j].iIOBufferEnd);
SSMR3GetU32(pSSMHandle, &pCtl->aIfs[j].iIOBufferPIODataStart);
SSMR3GetU32(pSSMHandle, &pCtl->aIfs[j].iIOBufferPIODataEnd);
SSMR3GetU32(pSSMHandle, &pCtl->aIfs[j].iATAPILBA);
SSMR3GetU32(pSSMHandle, &pCtl->aIfs[j].cbATAPISector);
SSMR3GetMem(pSSMHandle, &pCtl->aIfs[j].aATAPICmd, sizeof(pCtl->aIfs[j].aATAPICmd));
if (u32Version > ATA_CTL_SAVED_STATE_VERSION_WITHOUT_FULL_SENSE)
{
SSMR3GetMem(pSSMHandle, &pCtl->aIfs[j].abATAPISense, sizeof(pCtl->aIfs[j].abATAPISense));
}
else
{
uint8_t uATAPISenseKey, uATAPIASC;
memset(pCtl->aIfs[j].abATAPISense, '\0', sizeof(pCtl->aIfs[j].abATAPISense));
pCtl->aIfs[j].abATAPISense[0] = 0x70 | (1 << 7);
pCtl->aIfs[j].abATAPISense[7] = 10;
SSMR3GetU8(pSSMHandle, &uATAPISenseKey);
SSMR3GetU8(pSSMHandle, &uATAPIASC);
pCtl->aIfs[j].abATAPISense[2] = uATAPISenseKey & 0x0f;
pCtl->aIfs[j].abATAPISense[12] = uATAPIASC;
}
/** @todo triple-check this hack after passthrough is working */
SSMR3GetU8(pSSMHandle, &pCtl->aIfs[j].cNotifiedMediaChange);
if (u32Version > ATA_CTL_SAVED_STATE_VERSION_WITHOUT_EVENT_STATUS)
SSMR3GetU32(pSSMHandle, (uint32_t*)&pCtl->aIfs[j].MediaEventStatus);
else
pCtl->aIfs[j].MediaEventStatus = ATA_EVENT_STATUS_UNCHANGED;
PDMLED Led;
SSMR3GetMem(pSSMHandle, &Led, sizeof(PDMLED));
SSMR3GetU32(pSSMHandle, &pCtl->aIfs[j].cbIOBuffer);
if (pCtl->aIfs[j].cbIOBuffer)
{
if (pCtl->aIfs[j].CTX_SUFF(pbIOBuffer))
SSMR3GetMem(pSSMHandle, pCtl->aIfs[j].CTX_SUFF(pbIOBuffer), pCtl->aIfs[j].cbIOBuffer);
else
{
LogRel(("ATA: No buffer for %d\n", j));
if (SSMR3HandleGetAfter(pSSMHandle) != SSMAFTER_DEBUG_IT)
return VERR_SSM_LOAD_CONFIG_MISMATCH;
/* skip the buffer if we're loading for the debugger / animator. */
uint8_t u8Ignored;
size_t cbLeft = pCtl->aIfs[j].cbIOBuffer;
while (cbLeft-- > 0)
SSMR3GetU8(pSSMHandle, &u8Ignored);
}
}
else
Assert(pCtl->aIfs[j].CTX_SUFF(pbIOBuffer) == NULL);
}
rc = SSMR3GetU32(pSSMHandle, &u32);
if (RT_FAILURE(rc))
return rc;
if (u32 != ~0U)
{
AssertMsgFailed(("u32=%#x expected ~0\n", u32));
rc = VERR_SSM_DATA_UNIT_FORMAT_CHANGED;
return rc;
}
return VINF_SUCCESS;
}
DECLCALLBACK(int) ataControllerInit(PPDMDEVINS pDevIns, PAHCIATACONTROLLER pCtl, PPDMIBASE pDrvBaseMaster, PPDMIBASE pDrvBaseSlave,
uint32_t *pcbSSMState, const char *szName, PPDMLED pLed,
PSTAMCOUNTER pStatBytesRead, PSTAMCOUNTER pStatBytesWritten)
{
int rc;
AssertMsg(pcbSSMState, ("pcbSSMState is invalid\n"));
pCtl->pDevInsR3 = pDevIns;
pCtl->pDevInsR0 = PDMDEVINS_2_R0PTR(pDevIns);
pCtl->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns);
pCtl->AsyncIOSem = NIL_RTSEMEVENT;
pCtl->SuspendIOSem = NIL_RTSEMEVENT;
pCtl->AsyncIORequestMutex = NIL_RTSEMMUTEX;
pCtl->AsyncIOThread = NIL_RTTHREAD;
for (uint32_t j = 0; j < RT_ELEMENTS(pCtl->aIfs); j++)
{
pCtl->aIfs[j].iLUN = j;
pCtl->aIfs[j].pDevInsR3 = pDevIns;
pCtl->aIfs[j].pDevInsR0 = PDMDEVINS_2_R0PTR(pDevIns);
pCtl->aIfs[j].pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns);
pCtl->aIfs[j].pControllerR3 = pCtl;
pCtl->aIfs[j].pControllerR0 = MMHyperR3ToR0(PDMDevHlpGetVM(pDevIns), pCtl);
pCtl->aIfs[j].pControllerRC = MMHyperR3ToRC(PDMDevHlpGetVM(pDevIns), pCtl);
pCtl->aIfs[j].pLed = pLed;
pCtl->aIfs[j].pStatBytesRead = pStatBytesRead;
pCtl->aIfs[j].pStatBytesWritten = pStatBytesWritten;
}
/* Initialize per-controller critical section */
rc = PDMDevHlpCritSectInit(pDevIns, &pCtl->lock, szName);
if (RT_FAILURE(rc))
return PDMDEV_SET_ERROR(pDevIns, rc, N_("AHCI ATA: cannot initialize critical section"));
/*
* Attach the units.
*/
uint32_t cbTotalBuffer = 0;
/*
* Start the worker thread.
*/
pCtl->uAsyncIOState = AHCIATA_AIO_NEW;
rc = RTSemEventCreate(&pCtl->AsyncIOSem);
AssertRCReturn(rc, rc);
rc = RTSemEventCreate(&pCtl->SuspendIOSem);
AssertRCReturn(rc, rc);
rc = RTSemMutexCreate(&pCtl->AsyncIORequestMutex);
AssertRCReturn(rc, rc);
ataAsyncIOClearRequests(pCtl);
rc = RTThreadCreateF(&pCtl->AsyncIOThread, ataAsyncIOLoop, (void *)pCtl, 128*1024,
RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "AHCI-ATA-%u", pCtl->irq);
AssertRCReturn(rc, rc);
Assert(pCtl->AsyncIOThread != NIL_RTTHREAD && pCtl->AsyncIOSem != NIL_RTSEMEVENT && pCtl->SuspendIOSem != NIL_RTSEMEVENT && pCtl->AsyncIORequestMutex != NIL_RTSEMMUTEX);
Log(("%s: controller AIO thread id %#x; sem %p susp_sem %p mutex %p\n", __FUNCTION__, pCtl->AsyncIOThread, pCtl->AsyncIOSem, pCtl->SuspendIOSem, pCtl->AsyncIORequestMutex));
for (uint32_t j = 0; j < RT_ELEMENTS(pCtl->aIfs); j++)
{
/*
* Try attach the block device and get the interfaces,
* required as well as optional.
*/
AHCIATADevState *pIf = &pCtl->aIfs[j];
pIf->pDrvBase = (j == 0) ? pDrvBaseMaster : pDrvBaseSlave;
#if 0
rc = PDMDevHlpDriverAttach(pDevIns, pIf->iLUN, &pIf->IBase, &pIf->pDrvBase, s_apszDescs[j]);
if (RT_SUCCESS(rc))
#endif
if (pIf->pDrvBase)
rc = ataConfigLun(pDevIns, pIf);
else
{
pIf->pDrvBase = NULL;
pIf->pDrvBlock = NULL;
pIf->cbIOBuffer = 0;
pIf->pbIOBufferR3 = NULL;
pIf->pbIOBufferR0 = NIL_RTR0PTR;
pIf->pbIOBufferRC = NIL_RTRCPTR;
LogRel(("AHCI ATA: LUN#%d: no unit\n", pIf->iLUN));
}
cbTotalBuffer += pIf->cbIOBuffer;
}
*pcbSSMState = cbTotalBuffer;
/*
* Initialize the device state.
*/
ataControllerReset(pCtl);
return VINF_SUCCESS;
}
#endif /* IN_RING3 */
#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */