/* $Id$ */
/** @file
* DevSerial - 16550A UART emulation.
*/
/*
* Copyright (C) 2006-2013 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
/*
* This code is based on:
*
* QEMU 16550A UART emulation
*
* Copyright (c) 2003-2004 Fabrice Bellard
* Copyright (c) 2008 Citrix Systems, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#include <iprt/semaphore.h>
#include <iprt/critsect.h>
#include "VBoxDD.h"
#undef VBOX_SERIAL_PCI /* The PCI variant has lots of problems: wrong IRQ line and wrong IO base assigned. */
#ifdef VBOX_SERIAL_PCI
#endif /* VBOX_SERIAL_PCI */
/*******************************************************************************
* Defined Constants And Macros *
*******************************************************************************/
/*
* These are the definitions for the Modem Control Register
*/
/*
* These are the definitions for the Modem Status Register
*/
/*
* Interrupt trigger levels.
* The byte-counts are for 16550A - in newer UARTs the byte-count for each ITL is higher.
*/
#define XMIT_FIFO 0
#define MAX_XMIT_RETRY_TIME 1 /* max time (in seconds) for retrying the character xmit before dropping it */
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
struct SerialFifo
{
};
/**
* Serial device.
*
* @implements PDMIBASE
* @implements PDMICHARPORT
*/
typedef struct SerialState
{
/** Access critical section. */
/** Pointer to the device instance - R3 Ptr. */
/** Pointer to the device instance - R0 Ptr. */
/** Pointer to the device instance - RC Ptr. */
/** Alignment. */
/** LUN\#0: The base interface. */
/** LUN\#0: The character port interface. */
/** Pointer to the attached base driver. */
/** Pointer to the attached character driver. */
/* NOTE: this hidden state is necessary for tx irq generation as
it can be reset while reading iir */
int thr_ipending;
int timeout_ipending;
int irq;
int last_break_enable;
/** Counter for retrying xmit */
int tsr_retry;
int tsr_retry_bound_max; /**< maximum possible tsr_retry_bound value that can be set while dynamic bound adjustment */
int tsr_retry_bound_min; /**< minimum possible tsr_retry_bound value that can be set while dynamic bound adjustment */
bool msr_changed;
bool fGCEnabled;
bool fR0Enabled;
bool fYieldOnLSRRead;
bool volatile fRecvWaiting;
bool f16550AEnabled;
/** Time it takes to transmit a character */
#ifdef VBOX_SERIAL_PCI
#endif /* VBOX_SERIAL_PCI */
} DEVSERIAL;
/** Pointer to the serial device state. */
#ifndef VBOX_DEVICE_STRUCT_TESTCASE
#ifdef IN_RING3
{
f->count = 0;
f->head = 0;
f->tail = 0;
}
{
/* Receive overruns do not overwrite FIFO contents. */
{
if (f->head == UART_FIFO_LENGTH)
f->head = 0;
}
if (f->count < UART_FIFO_LENGTH)
f->count++;
++f->tail;
return 1;
}
{
uint8_t c;
if (f->count == 0)
return 0;
if (f->tail == UART_FIFO_LENGTH)
f->tail = 0;
f->count--;
return c;
}
{
/* Note that(pThis->ier & UART_IER_RDI) can mask this interrupt,
* this is not in the specification but is observed on existing
* hardware. */
&& pThis->thr_ipending) {
}
/** XXX only call the SetIrq function if the state really changes! */
if (tmp_iir != UART_IIR_NO_INT) {
# ifdef VBOX_SERIAL_PCI
# else /* !VBOX_SERIAL_PCI */
# endif /* !VBOX_SERIAL_PCI */
} else {
# ifdef VBOX_SERIAL_PCI
# else /* !VBOX_SERIAL_PCI */
# endif /* !VBOX_SERIAL_PCI */
}
}
{
pThis->tsr_retry_bound_max = RT_MAX((tf * MAX_XMIT_RETRY_TIME) / pThis->char_transmit_time, MIN_XMIT_RETRY);
pThis->tsr_retry_bound_min = RT_MAX(pThis->tsr_retry_bound_max / (1000 * MAX_XMIT_RETRY_TIME), MIN_XMIT_RETRY);
/* for simplicity just reset to max retry count */
}
{
/* this is most likely means we have some backend connection issues */
/* decrement the retry bound */
pThis->tsr_retry_bound = RT_MAX(pThis->tsr_retry_bound / (10 * MAX_XMIT_RETRY_TIME), pThis->tsr_retry_bound_min);
}
{
/* success means we have a backend connection working OK,
* set retry bound to its maximum value */
}
{
return;
frame_size = 1;
frame_size++;
parity = 'E';
else
parity = 'O';
} else {
parity = 'N';
}
stop_bits = 2;
else
stop_bits = 1;
}
{
} else {
}
}
/* in loopback mode, say that we just received a char */
else if (bRetryXmit) /* do not increase the retry count if the retry is actually caused by next char write */
TMTimerSet(CTX_SUFF(pThis->transmit_timer), TMTimerGet(CTX_SUFF(pThis->transmit_timer)) + pThis->char_transmit_time * 4);
return;
} else {
/* drop this character. */
}
}
else {
}
}
}
#endif /* IN_RING3 */
{
addr &= 7;
#ifndef IN_RING3
return VINF_IOM_R3_IOPORT_WRITE;
#else
switch(addr) {
default:
case 0:
} else {
pThis->thr_ipending = 0;
} else {
pThis->thr_ipending = 0;
}
serial_xmit(pThis, false);
}
break;
case 1:
} else {
}
}
break;
case 2:
if (!pThis->f16550AEnabled)
break;
break;
/* FIFO clear */
if (val & UART_FCR_RFR) {
pThis->timeout_ipending = 0;
}
if (val & UART_FCR_XFR) {
}
if (val & UART_FCR_FE) {
/* Set RECV_FIFO trigger Level */
switch (val & 0xC0) {
case UART_FCR_ITL_1:
break;
case UART_FCR_ITL_2:
break;
case UART_FCR_ITL_3:
break;
case UART_FCR_ITL_4:
break;
}
} else
/* Set fcr - or at least the bits in it that are supposed to "stick" */
break;
case 3:
{
int break_enable;
{
}
}
}
break;
case 4:
{
}
break;
case 5:
break;
case 6:
break;
case 7:
break;
}
return VINF_SUCCESS;
#endif
}
{
*pRC = VINF_SUCCESS;
addr &= 7;
switch(addr) {
default:
case 0:
/* DLAB == 1: divisor latch (LS) */
} else {
#ifndef IN_RING3
#else
else
pThis->timeout_ipending = 0;
} else {
}
if (pThis->fRecvWaiting)
{
pThis->fRecvWaiting = false;
}
#endif
}
break;
case 1:
/* DLAB == 1: divisor latch (MS) */
} else {
}
break;
case 2:
#ifndef IN_RING3
#else
pThis->thr_ipending = 0;
}
/* reset msr changed bit */
pThis->msr_changed = false;
#endif
break;
case 3:
break;
case 4:
break;
case 5:
{
/* No data available and yielding is enabled, so yield in ring3. */
#ifndef IN_RING3
break;
#else
RTThreadYield ();
#endif
}
/* Clear break and overrun interrupts */
#ifndef IN_RING3
#else
#endif
}
break;
case 6:
/* in loopback, the modem output pins are connected to the
inputs */
} else {
/* Clear delta bits & msr int after read, if they were set */
#ifndef IN_RING3
#else
#endif
}
}
break;
case 7:
break;
}
return ret;
}
#ifdef IN_RING3
{
else
return 0;
} else {
}
}
{
int i;
for (i = 0; i < size; i++) {
}
/* call the timeout receive callback in 4 char transmit time */
TMTimerSet(pThis->fifo_timeout_timer, TMTimerGet(pThis->fifo_timeout_timer) + pThis->char_transmit_time * 4);
} else {
}
}
/**
* @interface_method_impl{PDMICHARPORT,pfnNotifyRead}
*/
static DECLCALLBACK(int) serialNotifyRead(PPDMICHARPORT pInterface, const void *pvBuf, size_t *pcbRead)
{
{
if (!serial_can_receive(pThis))
{
/* If we cannot receive then wait for not more than 250ms. If we still
* cannot receive then the new character will either overwrite rbr
* or it will be dropped at fifo_put(). */
pThis->fRecvWaiting = true;
}
}
return VINF_SUCCESS;
}
/**
* @@interface_method_impl{PDMICHARPORT,pfnNotifyStatusLinesChanged}
*/
static DECLCALLBACK(int) serialNotifyStatusLinesChanged(PPDMICHARPORT pInterface, uint32_t newStatusLines)
{
/* Set new states. */
newMsr |= UART_MSR_DCD;
newMsr |= UART_MSR_RI;
newMsr |= UART_MSR_DSR;
newMsr |= UART_MSR_CTS;
/* Compare the old and the new states and set the delta bits accordingly. */
newMsr |= UART_MSR_DDCD;
newMsr |= UART_MSR_TERI;
newMsr |= UART_MSR_DDSR;
newMsr |= UART_MSR_DCTS;
pThis->msr_changed = true;
return VINF_SUCCESS;
}
/**
* @interface_method_impl{PDMICHARPORT,pfnNotifyBufferFull}
*/
{
return VINF_SUCCESS;
}
/**
* @interface_method_impl{PDMICHARPORT,pfnNotifyBreak}
*/
{
return VINF_SUCCESS;
}
/* -=-=-=-=-=-=-=-=- Timer callbacks -=-=-=-=-=-=-=-=- */
/**
* @callback_method_tmpl{FNTMTIMERDEV, Fifo timer function.}
*/
{
{
}
}
/**
* @callback_method_tmpl{FNTMTIMERDEV, Transmit timer function.}
*
* Just retry to transmit a character.
*/
{
serial_xmit(pThis, true);
}
#endif /* IN_RING3 */
/* -=-=-=-=-=-=-=-=- I/O Port Access Handlers -=-=-=-=-=-=-=-=- */
/**
* @callback_method_impl{FNIOMIOPORTOUT}
*/
PDMBOTHCBDECL(int) serialIOPortWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb)
{
int rc;
if (cb == 1)
{
}
else
{
rc = VINF_SUCCESS;
}
return rc;
}
/**
* @callback_method_impl{FNIOMIOPORTIN}
*/
PDMBOTHCBDECL(int) serialIOPortRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb)
{
int rc;
if (cb == 1)
{
}
else
return rc;
}
#ifdef IN_RING3
/* -=-=-=-=-=-=-=-=- Saved State -=-=-=-=-=-=-=-=- */
/**
* @callback_method_tmpl{FNSSMDEVLIVEEXEC}
*/
{
return VINF_SSM_DONT_CALL_AGAIN;
}
/**
* @callback_method_tmpl{FNSSMDEVSAVEEXEC}
*/
{
/* Version 5, safe everything that might be of importance. Much better than
missing relevant bits! */
/* Don't store:
* - the content of the FIFO
* - tsr_retry
*/
}
/**
* @callback_method_tmpl{FNSSMDEVLOADEXEC}
*/
static DECLCALLBACK(int) serialLoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
{
AssertMsgReturn(uVersion >= SERIAL_SAVED_STATE_VERSION_16450, ("%d\n", uVersion), VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION);
if (uPass != SSM_PASS_FINAL)
{
}
else
{
{
pThis->f16550AEnabled = false;
}
{
}
/* the marker. */
if (RT_FAILURE(rc))
return rc;
|| pThis->fRecvWaiting)
{
pThis->fRecvWaiting = false;
}
/* this isn't strictly necessary but cannot hurt... */
}
/*
* Check the config.
*/
N_("Config mismatch - saved irq=%#x iobase=%#x; configured irq=%#x iobase=%#x"),
return VINF_SUCCESS;
}
#ifdef VBOX_SERIAL_PCI
/* -=-=-=-=-=-=-=-=- PCI Device Callback(s) -=-=-=-=-=-=-=-=- */
/**
* @callback_method_impl{FNPCIIOREGIONMAP}
*/
static DECLCALLBACK(int) serialIOPortRegionMap(PPCIDEVICE pPciDev, int iRegion, RTGCPHYS GCPhysAddress,
{
AssertMsg(RT_ALIGN(GCPhysAddress, 8) == GCPhysAddress, ("Expected 8 byte alignment. GCPhysAddress=%#x\n", GCPhysAddress));
/*
* Register our port IO handlers.
*/
return rc;
}
#endif /* VBOX_SERIAL_PCI */
/* -=-=-=-=-=-=-=-=- PDMIBASE on LUN#1 -=-=-=-=-=-=-=-=- */
/**
* @interface_method_impl{PDMIBASE, pfnQueryInterface}
*/
{
return NULL;
}
/* -=-=-=-=-=-=-=-=- PDMDEVREG -=-=-=-=-=-=-=-=- */
/**
* @interface_method_impl{PDMDEVREG, pfnRelocate}
*/
{
}
/**
* @interface_method_impl{PDMDEVREG, pfnReset}
*/
{
/* Default to 9600 baud, 1 start bit, 8 data bits, 1 stop bit, no parity. */
pThis->thr_ipending = 0;
pThis->last_break_enable = 0;
# ifdef VBOX_SERIAL_PCI
# else /* !VBOX_SERIAL_PCI */
# endif /* !VBOX_SERIAL_PCI */
}
/**
* @interface_method_impl{PDMDEVREG, pfnDestruct}
*/
{
return VINF_SUCCESS;
}
/**
* @interface_method_impl{PDMDEVREG, pfnConstruct}
*/
{
int rc;
/*
* Initialize the instance data.
* (Do this early or the destructor might choke on something!)
*/
/* IBase */
/* ICharPort */
#ifdef VBOX_SERIAL_PCI
/* the PCI device */
#endif /* VBOX_SERIAL_PCI */
/*
* Validate and read the configuration.
*/
"IOBase\0"
"GCEnabled\0"
"R0Enabled\0"
"YieldOnLSRRead\0"
"Enable16550A\0"
))
{
AssertMsgFailed(("serialConstruct Invalid configuration values\n"));
}
if (RT_FAILURE(rc))
N_("Configuration error: Failed to get the \"GCEnabled\" value"));
if (RT_FAILURE(rc))
N_("Configuration error: Failed to get the \"R0Enabled\" value"));
if (RT_FAILURE(rc))
N_("Configuration error: Failed to get the \"YieldOnLSRRead\" value"));
if (rc == VERR_CFGM_VALUE_NOT_FOUND)
{
/* Provide sensible defaults. */
if (iInstance == 0)
irq_lvl = 4;
else if (iInstance == 1)
irq_lvl = 3;
else
AssertReleaseFailed(); /* irq_lvl is undefined. */
}
else if (RT_FAILURE(rc))
N_("Configuration error: Failed to get the \"IRQ\" value"));
if (rc == VERR_CFGM_VALUE_NOT_FOUND)
{
if (iInstance == 0)
io_base = 0x3f8;
else if (iInstance == 1)
io_base = 0x2f8;
else
AssertReleaseFailed(); /* io_base is undefined */
}
else if (RT_FAILURE(rc))
N_("Configuration error: Failed to get the \"IOBase\" value"));
if (RT_FAILURE(rc))
N_("Configuration error: Failed to get the \"Enable16550A\" value"));
#ifdef VBOX_SERIAL_PCI
#else
#endif
LogRel(("Serial#%d: emulating %s\n", pDevIns->iInstance, pThis->f16550AEnabled ? "16550A" : "16450"));
/*
* Initialize critical section and the semaphore. Change the default
* critical section to ours so that TM and IOM will enter it before
* calling us.
*
* Note! This must of be done BEFORE creating timers, registering I/O ports
* and other things which might pick up the default CS or end up
* calling back into the device.
*/
/*
* Create the timers.
*/
TMTIMER_FLAGS_DEFAULT_CRIT_SECT, "Serial Fifo Timer",
TMTIMER_FLAGS_DEFAULT_CRIT_SECT, "Serial Transmit Timer",
#ifdef VBOX_SERIAL_PCI
/*
* Register the PCI Device and region.
*/
if (RT_FAILURE(rc))
return rc;
if (RT_FAILURE(rc))
return rc;
#else /* !VBOX_SERIAL_PCI */
/*
* Register the I/O ports.
*/
if (RT_FAILURE(rc))
return rc;
if (pThis->fGCEnabled)
{
if (RT_FAILURE(rc))
return rc;
}
if (pThis->fR0Enabled)
{
if (RT_FAILURE(rc))
return rc;
}
#endif /* !VBOX_SERIAL_PCI */
/*
* Saved state.
*/
if (RT_FAILURE(rc))
return rc;
/*
* Attach the char driver and get the interfaces.
* For now no run-time changes are supported.
*/
if (RT_SUCCESS(rc))
{
{
return VERR_PDM_MISSING_INTERFACE;
}
/** @todo provide read notification interface!!!! */
}
else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
{
}
else
{
/* Don't call VMSetError here as we assume that the driver already set an appropriate error */
return rc;
}
return VINF_SUCCESS;
}
/**
* The device registration structure.
*/
const PDMDEVREG g_DeviceSerialPort =
{
/* u32Version */
/* szName */
"serial",
/* szRCMod */
"VBoxDDGC.gc",
/* szR0Mod */
"VBoxDDR0.r0",
/* pszDescription */
"Serial Communication Port",
/* fFlags */
/* fClass */
/* cMaxInstances */
/* cbInstance */
sizeof(DEVSERIAL),
/* pfnConstruct */
/* pfnDestruct */
/* pfnRelocate */
/* pfnMemSetup */
NULL,
/* pfnPowerOn */
NULL,
/* pfnReset */
/* pfnSuspend */
NULL,
/* pfnResume */
NULL,
/* pfnAttach */
NULL,
/* pfnDetach */
NULL,
/* pfnQueryInterface. */
NULL,
/* pfnInitComplete */
NULL,
/* pfnPowerOff */
NULL,
/* pfnSoftReset */
NULL,
/* u32VersionEnd */
};
#endif /* IN_RING3 */
#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */