diff options
Diffstat (limited to '')
-rw-r--r-- | src/VBox/Main/src-server/SerialPortImpl.cpp | 787 |
1 files changed, 787 insertions, 0 deletions
diff --git a/src/VBox/Main/src-server/SerialPortImpl.cpp b/src/VBox/Main/src-server/SerialPortImpl.cpp new file mode 100644 index 00000000..b77940d6 --- /dev/null +++ b/src/VBox/Main/src-server/SerialPortImpl.cpp @@ -0,0 +1,787 @@ +/* $Id: SerialPortImpl.cpp $ */ +/** @file + * VirtualBox COM class implementation + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#define LOG_GROUP LOG_GROUP_MAIN_SERIALPORT +#include "SerialPortImpl.h" +#include "MachineImpl.h" +#include "VirtualBoxImpl.h" +#include "GuestOSTypeImpl.h" + +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/cpp/utils.h> + +#include <VBox/settings.h> + +#include "AutoStateDep.h" +#include "AutoCaller.h" +#include "LoggingNew.h" + +////////////////////////////////////////////////////////////////////////////////// +// +// SerialPort private data definition +// +////////////////////////////////////////////////////////////////////////////////// + +struct SerialPort::Data +{ + Data() + : fModified(false), + pMachine(NULL) + { } + + bool fModified; + Machine * const pMachine; + const ComObjPtr<SerialPort> pPeer; + Backupable<settings::SerialPort> bd; +}; + +// constructor / destructor +///////////////////////////////////////////////////////////////////////////// + +DEFINE_EMPTY_CTOR_DTOR(SerialPort) + +HRESULT SerialPort::FinalConstruct() +{ + return BaseFinalConstruct(); +} + +void SerialPort::FinalRelease() +{ + uninit(); + BaseFinalRelease(); +} + +// public initializer/uninitializer for internal purposes only +///////////////////////////////////////////////////////////////////////////// + +/** + * Initializes the Serial Port object. + * + * @param aParent Handle of the parent object. + * @param aSlot Slot number the serial port is plugged into. + */ +HRESULT SerialPort::init(Machine *aParent, ULONG aSlot) +{ + LogFlowThisFunc(("aParent=%p, aSlot=%d\n", aParent, aSlot)); + + ComAssertRet(aParent, E_INVALIDARG); + + /* Enclose the state transition NotReady->InInit->Ready */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + m = new Data(); + + unconst(m->pMachine) = aParent; + /* m->pPeer is left null */ + + m->bd.allocate(); + + /* initialize data */ + m->bd->ulSlot = aSlot; + + /* Confirm a successful initialization */ + autoInitSpan.setSucceeded(); + + return S_OK; +} + +/** + * Initializes the Serial Port object given another serial port object + * (a kind of copy constructor). This object shares data with + * the object passed as an argument. + * + * @note This object must be destroyed before the original object + * it shares data with is destroyed. + * + * @note Locks @a aThat object for reading. + */ +HRESULT SerialPort::init(Machine *aParent, SerialPort *aThat) +{ + LogFlowThisFunc(("aParent=%p, aThat=%p\n", aParent, aThat)); + + ComAssertRet(aParent && aThat, E_INVALIDARG); + + /* Enclose the state transition NotReady->InInit->Ready */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + m = new Data(); + + unconst(m->pMachine) = aParent; + unconst(m->pPeer) = aThat; + + AutoCaller thatCaller(aThat); + AssertComRCReturnRC(thatCaller.rc()); + + AutoReadLock thatLock(aThat COMMA_LOCKVAL_SRC_POS); + m->bd.share(aThat->m->bd); + + /* Confirm a successful initialization */ + autoInitSpan.setSucceeded(); + + return S_OK; +} + +/** + * Initializes the guest object given another guest object + * (a kind of copy constructor). This object makes a private copy of data + * of the original object passed as an argument. + * + * @note Locks @a aThat object for reading. + */ +HRESULT SerialPort::initCopy(Machine *aParent, SerialPort *aThat) +{ + LogFlowThisFunc(("aParent=%p, aThat=%p\n", aParent, aThat)); + + ComAssertRet(aParent && aThat, E_INVALIDARG); + + /* Enclose the state transition NotReady->InInit->Ready */ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + m = new Data(); + + unconst(m->pMachine) = aParent; + /* pPeer is left null */ + + AutoCaller thatCaller(aThat); + AssertComRCReturnRC(thatCaller.rc()); + + AutoReadLock thatLock(aThat COMMA_LOCKVAL_SRC_POS); + m->bd.attachCopy(aThat->m->bd); + + /* Confirm a successful initialization */ + autoInitSpan.setSucceeded(); + + return S_OK; +} + +/** + * Uninitializes the instance and sets the ready flag to FALSE. + * Called either from FinalRelease() or by the parent when it gets destroyed. + */ +void SerialPort::uninit() +{ + LogFlowThisFunc(("\n")); + + /* Enclose the state transition Ready->InUninit->NotReady */ + AutoUninitSpan autoUninitSpan(this); + if (autoUninitSpan.uninitDone()) + return; + + m->bd.free(); + + unconst(m->pPeer) = NULL; + unconst(m->pMachine) = NULL; + + delete m; + m = NULL; +} + +// ISerialPort properties +///////////////////////////////////////////////////////////////////////////// + +HRESULT SerialPort::getEnabled(BOOL *aEnabled) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aEnabled = m->bd->fEnabled; + + return S_OK; +} + + +HRESULT SerialPort::setEnabled(BOOL aEnabled) +{ + LogFlowThisFunc(("aEnabled=%RTbool\n", aEnabled)); + + /* the machine needs to be mutable */ + AutoMutableStateDependency adep(m->pMachine); + if (FAILED(adep.rc())) return adep.rc(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (m->bd->fEnabled != RT_BOOL(aEnabled)) + { + m->bd.backup(); + m->bd->fEnabled = RT_BOOL(aEnabled); + + m->fModified = true; + // leave the lock before informing callbacks + alock.release(); + + AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS); + m->pMachine->i_setModified(Machine::IsModified_SerialPorts); + mlock.release(); + + m->pMachine->i_onSerialPortChange(this); + } + + return S_OK; +} + + +HRESULT SerialPort::getHostMode(PortMode_T *aHostMode) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aHostMode = m->bd->portMode; + + return S_OK; +} + +HRESULT SerialPort::setHostMode(PortMode_T aHostMode) +{ + /* the machine needs to be mutable */ + AutoMutableOrSavedOrRunningStateDependency adep(m->pMachine); + if (FAILED(adep.rc())) return adep.rc(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (m->bd->portMode != aHostMode) + { + switch (aHostMode) + { + case PortMode_RawFile: + if (m->bd->strPath.isEmpty()) + return setError(E_INVALIDARG, + tr("Cannot set the raw file mode of the serial port %d " + "because the file path is empty or null"), + m->bd->ulSlot); + break; + case PortMode_HostPipe: + if (m->bd->strPath.isEmpty()) + return setError(E_INVALIDARG, + tr("Cannot set the host pipe mode of the serial port %d " + "because the pipe path is empty or null"), + m->bd->ulSlot); + break; + case PortMode_HostDevice: + if (m->bd->strPath.isEmpty()) + return setError(E_INVALIDARG, + tr("Cannot set the host device mode of the serial port %d " + "because the device path is empty or null"), + m->bd->ulSlot); + break; + case PortMode_TCP: + if (m->bd->strPath.isEmpty()) + return setError(E_INVALIDARG, + tr("Cannot set the host device mode of the serial port %d " + "because the server address or TCP port is invalid"), + m->bd->ulSlot); + break; + case PortMode_Disconnected: + break; +#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK + case PortMode_32BitHack: /* (compiler warnings) */ + AssertFailedBreak(); +#endif + } + + m->bd.backup(); + m->bd->portMode = aHostMode; + + m->fModified = true; + // leave the lock before informing callbacks + alock.release(); + + AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS); + m->pMachine->i_setModified(Machine::IsModified_SerialPorts); + mlock.release(); + + m->pMachine->i_onSerialPortChange(this); + } + + return S_OK; +} + +HRESULT SerialPort::getSlot(ULONG *aSlot) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aSlot = m->bd->ulSlot; + + return S_OK; +} + + +HRESULT SerialPort::getIRQ(ULONG *aIRQ) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aIRQ = m->bd->ulIRQ; + + return S_OK; +} + + +HRESULT SerialPort::setIRQ(ULONG aIRQ) +{ + /* check IRQ limits + * (when changing this, make sure it corresponds to XML schema */ + if (aIRQ > 255) + return setError(E_INVALIDARG, + tr("Invalid IRQ number of the serial port %d: %lu (must be in range [0, %lu])"), + m->bd->ulSlot, aIRQ, 255); + + /* the machine needs to be mutable */ + AutoMutableStateDependency adep(m->pMachine); + if (FAILED(adep.rc())) return adep.rc(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (m->bd->ulIRQ != aIRQ) + { + m->bd.backup(); + m->bd->ulIRQ = aIRQ; + + m->fModified = true; + // leave the lock before informing callbacks + alock.release(); + + AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS); + m->pMachine->i_setModified(Machine::IsModified_SerialPorts); + mlock.release(); + + m->pMachine->i_onSerialPortChange(this); + } + + return S_OK; +} + + +HRESULT SerialPort::getIOBase(ULONG *aIOBase) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aIOBase = m->bd->ulIOBase; + + return S_OK; +} + +HRESULT SerialPort::setIOBase(ULONG aIOBase) +{ + /* check IOBase limits + * (when changing this, make sure it corresponds to XML schema */ + if (aIOBase > 0xFFFF) + return setError(E_INVALIDARG, + tr("Invalid I/O port base address of the serial port %d: %lu (must be in range [0, 0x%X])"), + m->bd->ulSlot, aIOBase, 0, 0xFFFF); + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) return autoCaller.rc(); + + /* the machine needs to be mutable */ + AutoMutableStateDependency adep(m->pMachine); + if (FAILED(adep.rc())) return adep.rc(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + HRESULT rc = S_OK; + + if (m->bd->ulIOBase != aIOBase) + { + m->bd.backup(); + m->bd->ulIOBase = aIOBase; + + m->fModified = true; + // leave the lock before informing callbacks + alock.release(); + + AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS); + m->pMachine->i_setModified(Machine::IsModified_SerialPorts); + mlock.release(); + + m->pMachine->i_onSerialPortChange(this); + } + + return rc; +} + +HRESULT SerialPort::getPath(com::Utf8Str &aPath) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + aPath = m->bd->strPath; + + return S_OK; +} + + +HRESULT SerialPort::setPath(const com::Utf8Str &aPath) +{ + /* the machine needs to be mutable */ + AutoMutableOrSavedOrRunningStateDependency adep(m->pMachine); + if (FAILED(adep.rc())) return adep.rc(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (aPath != m->bd->strPath) + { + HRESULT rc = i_checkSetPath(aPath); + if (FAILED(rc)) return rc; + + m->bd.backup(); + m->bd->strPath = aPath; + + m->fModified = true; + // leave the lock before informing callbacks + alock.release(); + + AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS); + m->pMachine->i_setModified(Machine::IsModified_SerialPorts); + mlock.release(); + + m->pMachine->i_onSerialPortChange(this); + } + + return S_OK; +} + +HRESULT SerialPort::getServer(BOOL *aServer) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aServer = m->bd->fServer; + + return S_OK; +} + +HRESULT SerialPort::setServer(BOOL aServer) +{ + /* the machine needs to be mutable */ + AutoMutableOrSavedOrRunningStateDependency adep(m->pMachine); + if (FAILED(adep.rc())) return adep.rc(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (m->bd->fServer != RT_BOOL(aServer)) + { + m->bd.backup(); + m->bd->fServer = RT_BOOL(aServer); + + m->fModified = true; + // leave the lock before informing callbacks + alock.release(); + + AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS); + m->pMachine->i_setModified(Machine::IsModified_SerialPorts); + mlock.release(); + + m->pMachine->i_onSerialPortChange(this); + } + + return S_OK; +} + +HRESULT SerialPort::getUartType(UartType_T *aUartType) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + *aUartType = m->bd->uartType; + + return S_OK; +} + +HRESULT SerialPort::setUartType(UartType_T aUartType) +{ + /* the machine needs to be mutable */ + AutoMutableOrSavedOrRunningStateDependency adep(m->pMachine); + if (FAILED(adep.rc())) return adep.rc(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (m->bd->uartType != aUartType) + { + m->bd.backup(); + m->bd->uartType = aUartType; + + m->fModified = true; + // leave the lock before informing callbacks + alock.release(); + + AutoWriteLock mlock(m->pMachine COMMA_LOCKVAL_SRC_POS); + m->pMachine->i_setModified(Machine::IsModified_SerialPorts); + mlock.release(); + + m->pMachine->i_onSerialPortChange(this); + } + + return S_OK; +} + +// public methods only for internal purposes +//////////////////////////////////////////////////////////////////////////////// + +/** + * Loads settings from the given port node. + * May be called once right after this object creation. + * + * @param data Configuration settings. + * + * @note Locks this object for writing. + */ +HRESULT SerialPort::i_loadSettings(const settings::SerialPort &data) +{ + + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + // simply copy + *m->bd.data() = data; + + return S_OK; +} + +/** + * Saves the port settings to the given port node. + * + * Note that the given Port node is completely empty on input. + * + * @param data Configuration settings. + * + * @note Locks this object for reading. + */ +HRESULT SerialPort::i_saveSettings(settings::SerialPort &data) +{ + AutoCaller autoCaller(this); + AssertComRCReturnRC(autoCaller.rc()); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + // simply copy + data = *m->bd.data(); + + return S_OK; +} + +/** + * Returns true if any setter method has modified settings of this instance. + * @return + */ +bool SerialPort::i_isModified() +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + return m->fModified; +} + +/** + * @note Locks this object for writing. + */ +void SerialPort::i_rollback() +{ + /* sanity */ + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + m->bd.rollback(); +} + +/** + * @note Locks this object for writing, together with the peer object (also + * for writing) if there is one. + */ +void SerialPort::i_commit() +{ + /* sanity */ + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); + + /* sanity too */ + AutoCaller peerCaller(m->pPeer); + AssertComRCReturnVoid(peerCaller.rc()); + + /* lock both for writing since we modify both (pPeer is "master" so locked + * first) */ + AutoMultiWriteLock2 alock(m->pPeer, this COMMA_LOCKVAL_SRC_POS); + + if (m->bd.isBackedUp()) + { + m->bd.commit(); + if (m->pPeer) + { + /* attach new data to the peer and reshare it */ + m->pPeer->m->bd.attach(m->bd); + } + } +} + +/** + * @note Locks this object for writing, together with the peer object + * represented by @a aThat (locked for reading). + */ +void SerialPort::i_copyFrom(SerialPort *aThat) +{ + AssertReturnVoid(aThat != NULL); + + /* sanity */ + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); + + /* sanity too */ + AutoCaller thatCaller(aThat); + AssertComRCReturnVoid(thatCaller.rc()); + + /* peer is not modified, lock it for reading (aThat is "master" so locked + * first) */ + AutoReadLock rl(aThat COMMA_LOCKVAL_SRC_POS); + AutoWriteLock wl(this COMMA_LOCKVAL_SRC_POS); + + /* this will back up current data */ + m->bd.assignCopy(aThat->m->bd); +} + +/** + * Applies the defaults for this serial port. + * + * @note This method currently assumes that the object is in the state after + * calling init(), it does not set defaults from an arbitrary state. + */ +void SerialPort::i_applyDefaults(GuestOSType *aOsType) +{ + /* sanity */ + AutoCaller autoCaller(this); + AssertComRCReturnVoid(autoCaller.rc()); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + /* Set some more defaults. */ + switch (m->bd->ulSlot) + { + case 0: + { + m->bd->ulIOBase = 0x3f8; + m->bd->ulIRQ = 4; + break; + } + case 1: + { + m->bd->ulIOBase = 0x2f8; + m->bd->ulIRQ = 3; + break; + } + case 2: + { + m->bd->ulIOBase = 0x3e8; + m->bd->ulIRQ = 4; + break; + } + case 3: + { + m->bd->ulIOBase = 0x2e8; + m->bd->ulIRQ = 3; + break; + } + default: + AssertMsgFailed(("Serial port slot %u exceeds limit\n", m->bd->ulSlot)); + break; + } + + uint32_t numSerialEnabled = 0; + if (aOsType) + numSerialEnabled = aOsType->i_numSerialEnabled(); + + /* Enable port if requested */ + if (m->bd->ulSlot < numSerialEnabled) + { + m->bd->fEnabled = true; + } +} + +bool SerialPort::i_hasDefaults() +{ + /* sanity */ + AutoCaller autoCaller(this); + AssertComRCReturn(autoCaller.rc(), true); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + if ( !m->bd->fEnabled + && m->bd->portMode == PortMode_Disconnected + && !m->bd->fServer) + { + /* Could be default, check the IO base and IRQ. */ + switch (m->bd->ulSlot) + { + case 0: + if (m->bd->ulIOBase == 0x3f8 && m->bd->ulIRQ == 4) + return true; + break; + case 1: + if (m->bd->ulIOBase == 0x2f8 && m->bd->ulIRQ == 3) + return true; + break; + case 2: + if (m->bd->ulIOBase == 0x3e8 && m->bd->ulIRQ == 4) + return true; + break; + case 3: + if (m->bd->ulIOBase == 0x2e8 && m->bd->ulIRQ == 3) + return true; + break; + default: + AssertMsgFailed(("Serial port slot %u exceeds limit\n", m->bd->ulSlot)); + break; + } + + /* Detect old-style defaults (0x3f8, irq 4) in any slot, they are still + * in place for many VMs created by old VirtualBox versions. */ + if (m->bd->ulIOBase == 0x3f8 && m->bd->ulIRQ == 4) + return true; + } + + return false; +} + +/** + * Validates COMSETTER(Path) arguments. + */ +HRESULT SerialPort::i_checkSetPath(const Utf8Str &str) +{ + AssertReturn(isWriteLockOnCurrentThread(), E_FAIL); + + if ( ( m->bd->portMode == PortMode_HostDevice + || m->bd->portMode == PortMode_HostPipe + || m->bd->portMode == PortMode_TCP + || m->bd->portMode == PortMode_RawFile + ) && str.isEmpty() + ) + return setError(E_INVALIDARG, + tr("Path of the serial port %d may not be empty or null in " + "host pipe, host device or TCP mode"), + m->bd->ulSlot); + + return S_OK; +} + +/* vi: set tabstop=4 shiftwidth=4 expandtab: */ |