Import QEMU upstream snapshot d2e570c

Upstream: https://gitlab.com/qemu-project/qemu.git

Upstream-Commit: d2e570cc0f97b936902a5b1b86b73c0f5998b475
This commit is contained in:
2026-08-31 02:15:30 +02:00
commit cf256aa081
11315 changed files with 3598369 additions and 0 deletions
+97
View File
@@ -0,0 +1,97 @@
config ESCC
bool
config HTIF
bool
config PARALLEL
bool
default y
depends on ISA_BUS
config PL011
bool
# The PL011 has both a Rust and a C implementation
select PL011_C if !HAVE_RUST
select X_PL011_RUST if HAVE_RUST
config PL011_C
bool
config SERIAL
bool
config SERIAL_ISA
bool
default y
depends on ISA_BUS
select SERIAL
config SERIAL_MM
bool
select SERIAL
config SERIAL_PCI
bool
default y if PCI_DEVICES
depends on PCI
select SERIAL
config SERIAL_PCI_MULTI
bool
default y if PCI_DEVICES
depends on PCI
select SERIAL
config VIRTIO_SERIAL
bool
default y
depends on VIRTIO
config MAX78000_UART
bool
config STM32F2XX_USART
bool
config STM32L4X5_USART
bool
config CMSDK_APB_UART
bool
config SCLPCONSOLE
bool
config TERMINAL3270
bool
config SH_SCI
bool
config RENESAS_SCI
bool
config AVR_USART
bool
config DIVA_GSP
bool
config MCHP_PFSOC_MMUART
bool
select SERIAL
config SIFIVE_UART
bool
config GOLDFISH_TTY
bool
config SHAKTI_UART
bool
config IP_OCTAL_232
bool
default y
depends on IPACK
+320
View File
@@ -0,0 +1,320 @@
/*
* AVR USART
*
* Copyright (c) 2018 University of Kent
* Author: Sarah Harris
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see
* <http://www.gnu.org/licenses/lgpl-2.1.html>
*/
#include "qemu/osdep.h"
#include "hw/char/avr_usart.h"
#include "qemu/log.h"
#include "hw/core/irq.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-properties-system.h"
static int avr_usart_can_receive(void *opaque)
{
AVRUsartState *usart = opaque;
if (usart->data_valid || !(usart->csrb & USART_CSRB_RXEN)) {
return 0;
}
return 1;
}
static void avr_usart_receive(void *opaque, const uint8_t *buffer, int size)
{
AVRUsartState *usart = opaque;
assert(size == 1);
assert(!usart->data_valid);
usart->data = buffer[0];
usart->data_valid = true;
usart->csra |= USART_CSRA_RXC;
if (usart->csrb & USART_CSRB_RXCIE) {
qemu_set_irq(usart->rxc_irq, 1);
}
}
static void update_char_mask(AVRUsartState *usart)
{
uint8_t mode = ((usart->csrc & USART_CSRC_CSZ0) ? 1 : 0) |
((usart->csrc & USART_CSRC_CSZ1) ? 2 : 0) |
((usart->csrb & USART_CSRB_CSZ2) ? 4 : 0);
switch (mode) {
case 0:
usart->char_mask = 0b11111;
break;
case 1:
usart->char_mask = 0b111111;
break;
case 2:
usart->char_mask = 0b1111111;
break;
case 3:
usart->char_mask = 0b11111111;
break;
case 4:
/* Fallthrough. */
case 5:
/* Fallthrough. */
case 6:
qemu_log_mask(
LOG_GUEST_ERROR,
"%s: Reserved character size 0x%x\n",
__func__,
mode);
break;
case 7:
qemu_log_mask(
LOG_GUEST_ERROR,
"%s: Nine bit character size not supported (forcing eight)\n",
__func__);
usart->char_mask = 0b11111111;
break;
default:
g_assert_not_reached();
}
}
static void avr_usart_reset(DeviceState *dev)
{
AVRUsartState *usart = AVR_USART(dev);
usart->data_valid = false;
usart->csra = 0b00100000;
usart->csrb = 0b00000000;
usart->csrc = 0b00000110;
usart->brrl = 0;
usart->brrh = 0;
update_char_mask(usart);
qemu_set_irq(usart->rxc_irq, 0);
qemu_set_irq(usart->txc_irq, 0);
qemu_set_irq(usart->dre_irq, 0);
}
static uint64_t avr_usart_read(void *opaque, hwaddr addr, unsigned int size)
{
AVRUsartState *usart = opaque;
uint8_t data;
assert(size == 1);
if (!usart->enabled) {
return 0;
}
switch (addr) {
case USART_DR:
if (!(usart->csrb & USART_CSRB_RXEN)) {
/* Receiver disabled, ignore. */
return 0;
}
if (usart->data_valid) {
data = usart->data & usart->char_mask;
usart->data_valid = false;
} else {
data = 0;
}
usart->csra &= 0xff ^ USART_CSRA_RXC;
qemu_set_irq(usart->rxc_irq, 0);
qemu_chr_fe_accept_input(&usart->chr);
return data;
case USART_CSRA:
return usart->csra;
case USART_CSRB:
return usart->csrb;
case USART_CSRC:
return usart->csrc;
case USART_BRRL:
return usart->brrl;
case USART_BRRH:
return usart->brrh;
default:
qemu_log_mask(
LOG_GUEST_ERROR,
"%s: Bad offset 0x%"HWADDR_PRIx"\n",
__func__,
addr);
}
return 0;
}
static void avr_usart_write(void *opaque, hwaddr addr, uint64_t value,
unsigned int size)
{
AVRUsartState *usart = opaque;
uint8_t mask;
uint8_t data;
assert((value & 0xff) == value);
assert(size == 1);
if (!usart->enabled) {
return;
}
switch (addr) {
case USART_DR:
if (!(usart->csrb & USART_CSRB_TXEN)) {
/* Transmitter disabled, ignore. */
return;
}
usart->csra |= USART_CSRA_TXC;
usart->csra |= USART_CSRA_DRE;
if (usart->csrb & USART_CSRB_TXCIE) {
qemu_set_irq(usart->txc_irq, 1);
usart->csra &= 0xff ^ USART_CSRA_TXC;
}
if (usart->csrb & USART_CSRB_DREIE) {
qemu_set_irq(usart->dre_irq, 1);
}
data = value;
qemu_chr_fe_write_all(&usart->chr, &data, 1);
break;
case USART_CSRA:
mask = 0b01000011;
/* Mask read-only bits. */
value = (value & mask) | (usart->csra & (0xff ^ mask));
usart->csra = value;
if (value & USART_CSRA_TXC) {
usart->csra ^= USART_CSRA_TXC;
qemu_set_irq(usart->txc_irq, 0);
}
if (value & USART_CSRA_MPCM) {
qemu_log_mask(
LOG_GUEST_ERROR,
"%s: MPCM not supported by USART\n",
__func__);
}
break;
case USART_CSRB:
mask = 0b11111101;
/* Mask read-only bits. */
value = (value & mask) | (usart->csrb & (0xff ^ mask));
usart->csrb = value;
if (!(value & USART_CSRB_RXEN)) {
/* Receiver disabled, flush input buffer. */
usart->data_valid = false;
}
qemu_set_irq(usart->rxc_irq,
((value & USART_CSRB_RXCIE) &&
(usart->csra & USART_CSRA_RXC)) ? 1 : 0);
qemu_set_irq(usart->txc_irq,
((value & USART_CSRB_TXCIE) &&
(usart->csra & USART_CSRA_TXC)) ? 1 : 0);
qemu_set_irq(usart->dre_irq,
((value & USART_CSRB_DREIE) &&
(usart->csra & USART_CSRA_DRE)) ? 1 : 0);
update_char_mask(usart);
break;
case USART_CSRC:
usart->csrc = value;
if ((value & USART_CSRC_MSEL1) && (value & USART_CSRC_MSEL0)) {
qemu_log_mask(
LOG_GUEST_ERROR,
"%s: SPI mode not supported by USART\n",
__func__);
}
if ((value & USART_CSRC_MSEL1) && !(value & USART_CSRC_MSEL0)) {
qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad USART mode\n", __func__);
}
if (!(value & USART_CSRC_PM1) && (value & USART_CSRC_PM0)) {
qemu_log_mask(
LOG_GUEST_ERROR,
"%s: Bad USART parity mode\n",
__func__);
}
update_char_mask(usart);
break;
case USART_BRRL:
usart->brrl = value;
break;
case USART_BRRH:
usart->brrh = value & 0b00001111;
break;
default:
qemu_log_mask(
LOG_GUEST_ERROR,
"%s: Bad offset 0x%"HWADDR_PRIx"\n",
__func__,
addr);
}
}
static const MemoryRegionOps avr_usart_ops = {
.read = avr_usart_read,
.write = avr_usart_write,
.endianness = DEVICE_NATIVE_ENDIAN,
.impl = {.min_access_size = 1, .max_access_size = 1}
};
static const Property avr_usart_properties[] = {
DEFINE_PROP_CHR("chardev", AVRUsartState, chr),
};
static void avr_usart_pr(void *opaque, int irq, int level)
{
AVRUsartState *s = AVR_USART(opaque);
s->enabled = !level;
if (!s->enabled) {
avr_usart_reset(DEVICE(s));
}
}
static void avr_usart_init(Object *obj)
{
AVRUsartState *s = AVR_USART(obj);
sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->rxc_irq);
sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->dre_irq);
sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->txc_irq);
memory_region_init_io(&s->mmio, obj, &avr_usart_ops, s, TYPE_AVR_USART, 7);
sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
qdev_init_gpio_in(DEVICE(s), avr_usart_pr, 1);
s->enabled = true;
}
static void avr_usart_realize(DeviceState *dev, Error **errp)
{
AVRUsartState *s = AVR_USART(dev);
qemu_chr_fe_set_handlers(&s->chr, avr_usart_can_receive,
avr_usart_receive, NULL, NULL,
s, NULL, true);
avr_usart_reset(dev);
}
static void avr_usart_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
device_class_set_legacy_reset(dc, avr_usart_reset);
device_class_set_props(dc, avr_usart_properties);
dc->realize = avr_usart_realize;
}
static const TypeInfo avr_usart_info = {
.name = TYPE_AVR_USART,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(AVRUsartState),
.instance_init = avr_usart_init,
.class_init = avr_usart_class_init,
};
static void avr_usart_register_types(void)
{
type_register_static(&avr_usart_info);
}
type_init(avr_usart_register_types)
+322
View File
@@ -0,0 +1,322 @@
/*
* BCM2835 (Raspberry Pi / Pi 2) Aux block (mini UART and SPI).
* Copyright (c) 2015, Microsoft
* Written by Andrew Baumann
* Based on pl011.c, copyright terms below:
*
* Arm PrimeCell PL011 UART
*
* Copyright (c) 2006 CodeSourcery.
* Written by Paul Brook
*
* This code is licensed under the GPL.
*
* At present only the core UART functions (data path for tx/rx) are
* implemented. The following features/registers are unimplemented:
* - Line/modem control
* - Scratch register
* - Extra control
* - Baudrate
* - SPI interfaces
*/
#include "qemu/osdep.h"
#include "hw/char/bcm2835_aux.h"
#include "hw/core/irq.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-properties-system.h"
#include "migration/vmstate.h"
#include "qemu/log.h"
#include "qemu/module.h"
#define AUX_IRQ 0x0
#define AUX_ENABLES 0x4
#define AUX_MU_IO_REG 0x40
#define AUX_MU_IER_REG 0x44
#define AUX_MU_IIR_REG 0x48
#define AUX_MU_LCR_REG 0x4c
#define AUX_MU_MCR_REG 0x50
#define AUX_MU_LSR_REG 0x54
#define AUX_MU_MSR_REG 0x58
#define AUX_MU_SCRATCH 0x5c
#define AUX_MU_CNTL_REG 0x60
#define AUX_MU_STAT_REG 0x64
#define AUX_MU_BAUD_REG 0x68
/* bits in IER/IIR registers */
#define RX_INT 0x1
#define TX_INT 0x2
static void bcm2835_aux_update(BCM2835AuxState *s)
{
/* signal an interrupt if either:
* 1. rx interrupt is enabled and we have a non-empty rx fifo, or
* 2. the tx interrupt is enabled (since we instantly drain the tx fifo)
*/
s->iir = 0;
if ((s->ier & RX_INT) && s->read_count != 0) {
s->iir |= RX_INT;
}
if (s->ier & TX_INT) {
s->iir |= TX_INT;
}
qemu_set_irq(s->irq, s->iir != 0);
}
static uint64_t bcm2835_aux_read(void *opaque, hwaddr offset, unsigned size)
{
BCM2835AuxState *s = opaque;
uint32_t c, res;
switch (offset) {
case AUX_IRQ:
return s->iir != 0;
case AUX_ENABLES:
return 1; /* mini UART permanently enabled */
case AUX_MU_IO_REG:
/* "DLAB bit set means access baudrate register" is NYI */
c = s->read_fifo[s->read_pos];
if (s->read_count > 0) {
s->read_count--;
if (++s->read_pos == BCM2835_AUX_RX_FIFO_LEN) {
s->read_pos = 0;
}
}
qemu_chr_fe_accept_input(&s->chr);
bcm2835_aux_update(s);
return c;
case AUX_MU_IER_REG:
/* "DLAB bit set means access baudrate register" is NYI */
return 0xc0 | s->ier; /* FIFO enables always read 1 */
case AUX_MU_IIR_REG:
res = 0xc0; /* FIFO enables */
/* The spec is unclear on what happens when both tx and rx
* interrupts are active, besides that this cannot occur. At
* present, we choose to prioritise the rx interrupt, since
* the tx fifo is always empty. */
if ((s->iir & RX_INT) && s->read_count != 0) {
res |= 0x4;
} else {
res |= 0x2;
}
if (s->iir == 0) {
res |= 0x1;
}
return res;
case AUX_MU_LCR_REG:
qemu_log_mask(LOG_UNIMP, "%s: AUX_MU_LCR_REG unsupported\n", __func__);
return 0;
case AUX_MU_MCR_REG:
qemu_log_mask(LOG_UNIMP, "%s: AUX_MU_MCR_REG unsupported\n", __func__);
return 0;
case AUX_MU_LSR_REG:
res = 0x60; /* tx idle, empty */
if (s->read_count != 0) {
res |= 0x1;
}
return res;
case AUX_MU_MSR_REG:
qemu_log_mask(LOG_UNIMP, "%s: AUX_MU_MSR_REG unsupported\n", __func__);
return 0;
case AUX_MU_SCRATCH:
qemu_log_mask(LOG_UNIMP, "%s: AUX_MU_SCRATCH unsupported\n", __func__);
return 0;
case AUX_MU_CNTL_REG:
return 0x3; /* tx, rx enabled */
case AUX_MU_STAT_REG:
res = 0x30e; /* space in the output buffer, empty tx fifo, idle tx/rx */
if (s->read_count > 0) {
res |= 0x1; /* data in input buffer */
assert(s->read_count <= BCM2835_AUX_RX_FIFO_LEN);
res |= ((uint32_t)s->read_count) << 16; /* rx fifo fill level */
}
return res;
case AUX_MU_BAUD_REG:
qemu_log_mask(LOG_UNIMP, "%s: AUX_MU_BAUD_REG unsupported\n", __func__);
return 0;
default:
qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %"HWADDR_PRIx"\n",
__func__, offset);
return 0;
}
}
static void bcm2835_aux_write(void *opaque, hwaddr offset, uint64_t value,
unsigned size)
{
BCM2835AuxState *s = opaque;
unsigned char ch;
switch (offset) {
case AUX_ENABLES:
if (value != 1) {
qemu_log_mask(LOG_UNIMP, "%s: unsupported attempt to enable SPI"
" or disable UART: 0x%"PRIx64"\n",
__func__, value);
}
break;
case AUX_MU_IO_REG:
/* "DLAB bit set means access baudrate register" is NYI */
ch = value;
/* XXX this blocks entire thread. Rewrite to use
* qemu_chr_fe_write and background I/O callbacks */
qemu_chr_fe_write_all(&s->chr, &ch, 1);
break;
case AUX_MU_IER_REG:
/* "DLAB bit set means access baudrate register" is NYI */
s->ier = value & (TX_INT | RX_INT);
bcm2835_aux_update(s);
break;
case AUX_MU_IIR_REG:
if (value & 0x2) {
s->read_count = 0;
}
break;
case AUX_MU_LCR_REG:
qemu_log_mask(LOG_UNIMP, "%s: AUX_MU_LCR_REG unsupported\n", __func__);
break;
case AUX_MU_MCR_REG:
qemu_log_mask(LOG_UNIMP, "%s: AUX_MU_MCR_REG unsupported\n", __func__);
break;
case AUX_MU_SCRATCH:
qemu_log_mask(LOG_UNIMP, "%s: AUX_MU_SCRATCH unsupported\n", __func__);
break;
case AUX_MU_CNTL_REG:
qemu_log_mask(LOG_UNIMP, "%s: AUX_MU_CNTL_REG unsupported\n", __func__);
break;
case AUX_MU_BAUD_REG:
qemu_log_mask(LOG_UNIMP, "%s: AUX_MU_BAUD_REG unsupported\n", __func__);
break;
default:
qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %"HWADDR_PRIx"\n",
__func__, offset);
}
bcm2835_aux_update(s);
}
static int bcm2835_aux_can_receive(void *opaque)
{
BCM2835AuxState *s = opaque;
return BCM2835_AUX_RX_FIFO_LEN - s->read_count;
}
static void bcm2835_aux_put_fifo(void *opaque, uint8_t value)
{
BCM2835AuxState *s = opaque;
int slot;
slot = s->read_pos + s->read_count;
if (slot >= BCM2835_AUX_RX_FIFO_LEN) {
slot -= BCM2835_AUX_RX_FIFO_LEN;
}
s->read_fifo[slot] = value;
s->read_count++;
if (s->read_count == BCM2835_AUX_RX_FIFO_LEN) {
/* buffer full */
}
bcm2835_aux_update(s);
}
static void bcm2835_aux_receive(void *opaque, const uint8_t *buf, int size)
{
for (int i = 0; i < size; i++) {
bcm2835_aux_put_fifo(opaque, buf[i]);
}
}
static const MemoryRegionOps bcm2835_aux_ops = {
.read = bcm2835_aux_read,
.write = bcm2835_aux_write,
.endianness = DEVICE_NATIVE_ENDIAN,
.impl.min_access_size = 4,
.impl.max_access_size = 4,
.valid.min_access_size = 1,
.valid.max_access_size = 4,
};
static const VMStateDescription vmstate_bcm2835_aux = {
.name = TYPE_BCM2835_AUX,
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_UINT8_ARRAY(read_fifo, BCM2835AuxState,
BCM2835_AUX_RX_FIFO_LEN),
VMSTATE_UINT8(read_pos, BCM2835AuxState),
VMSTATE_UINT8(read_count, BCM2835AuxState),
VMSTATE_UINT8(ier, BCM2835AuxState),
VMSTATE_UINT8(iir, BCM2835AuxState),
VMSTATE_END_OF_LIST()
}
};
static void bcm2835_aux_init(Object *obj)
{
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
BCM2835AuxState *s = BCM2835_AUX(obj);
memory_region_init_io(&s->iomem, OBJECT(s), &bcm2835_aux_ops, s,
TYPE_BCM2835_AUX, 0x100);
sysbus_init_mmio(sbd, &s->iomem);
sysbus_init_irq(sbd, &s->irq);
}
static void bcm2835_aux_realize(DeviceState *dev, Error **errp)
{
BCM2835AuxState *s = BCM2835_AUX(dev);
qemu_chr_fe_set_handlers(&s->chr, bcm2835_aux_can_receive,
bcm2835_aux_receive, NULL, NULL, s, NULL, true);
}
static const Property bcm2835_aux_props[] = {
DEFINE_PROP_CHR("chardev", BCM2835AuxState, chr),
};
static void bcm2835_aux_class_init(ObjectClass *oc, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(oc);
dc->realize = bcm2835_aux_realize;
dc->vmsd = &vmstate_bcm2835_aux;
set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
device_class_set_props(dc, bcm2835_aux_props);
}
static const TypeInfo bcm2835_aux_info = {
.name = TYPE_BCM2835_AUX,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(BCM2835AuxState),
.instance_init = bcm2835_aux_init,
.class_init = bcm2835_aux_class_init,
};
static void bcm2835_aux_register_types(void)
{
type_register_static(&bcm2835_aux_info);
}
type_init(bcm2835_aux_register_types)
+649
View File
@@ -0,0 +1,649 @@
/*
* Device model for Cadence UART
*
* Reference: Xilinx Zynq 7000 reference manual
* - http://www.xilinx.com/support/documentation/user_guides/ug585-Zynq-7000-TRM.pdf
* - Chapter 19 UART Controller
* - Appendix B for Register details
*
* Copyright (c) 2010 Xilinx Inc.
* Copyright (c) 2012 Peter A.G. Crosthwaite ([email protected])
* Copyright (c) 2012 PetaLogix Pty Ltd.
* Written by Haibing Ma
* M.Habib
*
* 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; either version
* 2 of the License, or (at your option) any later version.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "hw/core/sysbus.h"
#include "migration/vmstate.h"
#include "chardev/char-fe.h"
#include "chardev/char-serial.h"
#include "qemu/timer.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "hw/char/cadence_uart.h"
#include "hw/core/irq.h"
#include "hw/core/qdev-clock.h"
#include "hw/core/qdev-properties-system.h"
#include "trace.h"
#ifdef CADENCE_UART_ERR_DEBUG
#define DB_PRINT(...) do { \
fprintf(stderr, ": %s: ", __func__); \
fprintf(stderr, ## __VA_ARGS__); \
} while (0)
#else
#define DB_PRINT(...)
#endif
#define UART_SR_INTR_RTRIG 0x00000001
#define UART_SR_INTR_REMPTY 0x00000002
#define UART_SR_INTR_RFUL 0x00000004
#define UART_SR_INTR_TEMPTY 0x00000008
#define UART_SR_INTR_TFUL 0x00000010
/* somewhat awkwardly, TTRIG is misaligned between SR and ISR */
#define UART_SR_TTRIG 0x00002000
#define UART_INTR_TTRIG 0x00000400
/* bits fields in CSR that correlate to CISR. If any of these bits are set in
* SR, then the same bit in CISR is set high too */
#define UART_SR_TO_CISR_MASK 0x0000001F
#define UART_INTR_ROVR 0x00000020
#define UART_INTR_FRAME 0x00000040
#define UART_INTR_PARE 0x00000080
#define UART_INTR_TIMEOUT 0x00000100
#define UART_INTR_DMSI 0x00000200
#define UART_INTR_TOVR 0x00001000
#define UART_SR_RACTIVE 0x00000400
#define UART_SR_TACTIVE 0x00000800
#define UART_SR_FDELT 0x00001000
#define UART_CR_RXRST 0x00000001
#define UART_CR_TXRST 0x00000002
#define UART_CR_RX_EN 0x00000004
#define UART_CR_RX_DIS 0x00000008
#define UART_CR_TX_EN 0x00000010
#define UART_CR_TX_DIS 0x00000020
#define UART_CR_RST_TO 0x00000040
#define UART_CR_STARTBRK 0x00000080
#define UART_CR_STOPBRK 0x00000100
#define UART_MR_CLKS 0x00000001
#define UART_MR_CHRL 0x00000006
#define UART_MR_CHRL_SH 1
#define UART_MR_PAR 0x00000038
#define UART_MR_PAR_SH 3
#define UART_MR_NBSTOP 0x000000C0
#define UART_MR_NBSTOP_SH 6
#define UART_MR_CHMODE 0x00000300
#define UART_MR_CHMODE_SH 8
#define UART_MR_UCLKEN 0x00000400
#define UART_MR_IRMODE 0x00000800
#define UART_DATA_BITS_6 (0x3 << UART_MR_CHRL_SH)
#define UART_DATA_BITS_7 (0x2 << UART_MR_CHRL_SH)
#define UART_PARITY_ODD (0x1 << UART_MR_PAR_SH)
#define UART_PARITY_EVEN (0x0 << UART_MR_PAR_SH)
#define UART_STOP_BITS_1 (0x3 << UART_MR_NBSTOP_SH)
#define UART_STOP_BITS_2 (0x2 << UART_MR_NBSTOP_SH)
#define NORMAL_MODE (0x0 << UART_MR_CHMODE_SH)
#define ECHO_MODE (0x1 << UART_MR_CHMODE_SH)
#define LOCAL_LOOPBACK (0x2 << UART_MR_CHMODE_SH)
#define REMOTE_LOOPBACK (0x3 << UART_MR_CHMODE_SH)
#define UART_DEFAULT_REF_CLK (50 * 1000 * 1000)
#define R_CR (0x00/4)
#define R_MR (0x04/4)
#define R_IER (0x08/4)
#define R_IDR (0x0C/4)
#define R_IMR (0x10/4)
#define R_CISR (0x14/4)
#define R_BRGR (0x18/4)
#define R_RTOR (0x1C/4)
#define R_RTRIG (0x20/4)
#define R_MCR (0x24/4)
#define R_MSR (0x28/4)
#define R_SR (0x2C/4)
#define R_TX_RX (0x30/4)
#define R_BDIV (0x34/4)
#define R_FDEL (0x38/4)
#define R_PMIN (0x3C/4)
#define R_PWID (0x40/4)
#define R_TTRIG (0x44/4)
static void uart_update_status(CadenceUARTState *s)
{
s->r[R_SR] = 0;
s->r[R_SR] |= s->rx_count == CADENCE_UART_RX_FIFO_SIZE ? UART_SR_INTR_RFUL
: 0;
s->r[R_SR] |= !s->rx_count ? UART_SR_INTR_REMPTY : 0;
s->r[R_SR] |= s->rx_count >= s->r[R_RTRIG] ? UART_SR_INTR_RTRIG : 0;
s->r[R_SR] |= s->tx_count == CADENCE_UART_TX_FIFO_SIZE ? UART_SR_INTR_TFUL
: 0;
s->r[R_SR] |= !s->tx_count ? UART_SR_INTR_TEMPTY : 0;
s->r[R_SR] |= s->tx_count >= s->r[R_TTRIG] ? UART_SR_TTRIG : 0;
s->r[R_CISR] |= s->r[R_SR] & UART_SR_TO_CISR_MASK;
s->r[R_CISR] |= s->r[R_SR] & UART_SR_TTRIG ? UART_INTR_TTRIG : 0;
qemu_set_irq(s->irq, !!(s->r[R_IMR] & s->r[R_CISR]));
}
static void fifo_trigger_update(void *opaque)
{
CadenceUARTState *s = opaque;
if (s->r[R_RTOR]) {
s->r[R_CISR] |= UART_INTR_TIMEOUT;
uart_update_status(s);
}
}
static void uart_rx_reset(CadenceUARTState *s)
{
s->rx_wpos = 0;
s->rx_count = 0;
qemu_chr_fe_accept_input(&s->chr);
}
static void uart_tx_reset(CadenceUARTState *s)
{
s->tx_count = 0;
}
static void uart_send_breaks(CadenceUARTState *s)
{
int break_enabled = 1;
qemu_chr_fe_ioctl(&s->chr, CHR_IOCTL_SERIAL_SET_BREAK,
&break_enabled);
}
static void uart_parameters_setup(CadenceUARTState *s)
{
QEMUSerialSetParams ssp;
unsigned int baud_rate, packet_size, input_clk;
input_clk = clock_get_hz(s->refclk);
baud_rate = (s->r[R_MR] & UART_MR_CLKS) ? input_clk / 8 : input_clk;
baud_rate /= (s->r[R_BRGR] * (s->r[R_BDIV] + 1));
trace_cadence_uart_baudrate(baud_rate);
ssp.speed = baud_rate;
packet_size = 1;
switch (s->r[R_MR] & UART_MR_PAR) {
case UART_PARITY_EVEN:
ssp.parity = 'E';
packet_size++;
break;
case UART_PARITY_ODD:
ssp.parity = 'O';
packet_size++;
break;
default:
ssp.parity = 'N';
break;
}
switch (s->r[R_MR] & UART_MR_CHRL) {
case UART_DATA_BITS_6:
ssp.data_bits = 6;
break;
case UART_DATA_BITS_7:
ssp.data_bits = 7;
break;
default:
ssp.data_bits = 8;
break;
}
switch (s->r[R_MR] & UART_MR_NBSTOP) {
case UART_STOP_BITS_1:
ssp.stop_bits = 1;
break;
default:
ssp.stop_bits = 2;
break;
}
packet_size += ssp.data_bits + ssp.stop_bits;
if (ssp.speed == 0) {
/*
* Avoid division-by-zero below.
* TODO: find something better
*/
ssp.speed = 1;
}
s->char_tx_time = (NANOSECONDS_PER_SECOND / ssp.speed) * packet_size;
qemu_chr_fe_ioctl(&s->chr, CHR_IOCTL_SERIAL_SET_PARAMS, &ssp);
}
static int uart_can_receive(void *opaque)
{
CadenceUARTState *s = opaque;
int ret;
uint32_t ch_mode;
/* ignore characters when unclocked or in reset */
if (!clock_is_enabled(s->refclk) || device_is_in_reset(DEVICE(s))) {
qemu_log_mask(LOG_GUEST_ERROR, "%s: uart is unclocked or in reset\n",
__func__);
return 0;
}
ret = MAX(CADENCE_UART_RX_FIFO_SIZE, CADENCE_UART_TX_FIFO_SIZE);
ch_mode = s->r[R_MR] & UART_MR_CHMODE;
if (ch_mode == NORMAL_MODE || ch_mode == ECHO_MODE) {
ret = MIN(ret, CADENCE_UART_RX_FIFO_SIZE - s->rx_count);
}
if (ch_mode == REMOTE_LOOPBACK || ch_mode == ECHO_MODE) {
ret = MIN(ret, CADENCE_UART_TX_FIFO_SIZE - s->tx_count);
}
return ret;
}
static void uart_ctrl_update(CadenceUARTState *s)
{
if (s->r[R_CR] & UART_CR_TXRST) {
uart_tx_reset(s);
}
if (s->r[R_CR] & UART_CR_RXRST) {
uart_rx_reset(s);
}
s->r[R_CR] &= ~(UART_CR_TXRST | UART_CR_RXRST);
if (s->r[R_CR] & UART_CR_STARTBRK && !(s->r[R_CR] & UART_CR_STOPBRK)) {
uart_send_breaks(s);
}
}
static void uart_write_rx_fifo(void *opaque, const uint8_t *buf, int size)
{
CadenceUARTState *s = opaque;
uint64_t new_rx_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
int i;
if ((s->r[R_CR] & UART_CR_RX_DIS) || !(s->r[R_CR] & UART_CR_RX_EN)) {
return;
}
if (s->rx_count == CADENCE_UART_RX_FIFO_SIZE) {
s->r[R_CISR] |= UART_INTR_ROVR;
} else {
for (i = 0; i < size; i++) {
s->rx_fifo[s->rx_wpos] = buf[i];
s->rx_wpos = (s->rx_wpos + 1) % CADENCE_UART_RX_FIFO_SIZE;
s->rx_count++;
}
timer_mod(s->fifo_trigger_handle, new_rx_time +
(s->char_tx_time * 4));
}
uart_update_status(s);
}
static gboolean cadence_uart_xmit(void *do_not_use, GIOCondition cond,
void *opaque)
{
CadenceUARTState *s = opaque;
int ret;
/* instant drain the fifo when there's no back-end */
if (!qemu_chr_fe_backend_connected(&s->chr)) {
s->tx_count = 0;
return G_SOURCE_REMOVE;
}
if (!s->tx_count) {
return G_SOURCE_REMOVE;
}
ret = qemu_chr_fe_write(&s->chr, s->tx_fifo, s->tx_count);
if (ret >= 0) {
s->tx_count -= ret;
memmove(s->tx_fifo, s->tx_fifo + ret, s->tx_count);
}
if (s->tx_count) {
guint r = qemu_chr_fe_add_watch(&s->chr, G_IO_OUT | G_IO_HUP,
cadence_uart_xmit, s);
if (!r) {
s->tx_count = 0;
return G_SOURCE_REMOVE;
}
}
uart_update_status(s);
return G_SOURCE_REMOVE;
}
static void uart_write_tx_fifo(CadenceUARTState *s, const uint8_t *buf,
int size)
{
if ((s->r[R_CR] & UART_CR_TX_DIS) || !(s->r[R_CR] & UART_CR_TX_EN)) {
return;
}
if (size > CADENCE_UART_TX_FIFO_SIZE - s->tx_count) {
size = CADENCE_UART_TX_FIFO_SIZE - s->tx_count;
/*
* This can only be a guest error via a bad tx fifo register push,
* as can_receive() should stop remote loop and echo modes ever getting
* us to here.
*/
qemu_log_mask(LOG_GUEST_ERROR, "cadence_uart: TxFIFO overflow");
s->r[R_CISR] |= UART_INTR_ROVR;
}
memcpy(s->tx_fifo + s->tx_count, buf, size);
s->tx_count += size;
cadence_uart_xmit(NULL, G_IO_OUT, s);
}
static void uart_receive(void *opaque, const uint8_t *buf, int size)
{
CadenceUARTState *s = opaque;
uint32_t ch_mode = s->r[R_MR] & UART_MR_CHMODE;
if (ch_mode == NORMAL_MODE || ch_mode == ECHO_MODE) {
uart_write_rx_fifo(opaque, buf, size);
}
if (ch_mode == REMOTE_LOOPBACK || ch_mode == ECHO_MODE) {
uart_write_tx_fifo(s, buf, size);
}
}
static void uart_event(void *opaque, QEMUChrEvent event)
{
CadenceUARTState *s = opaque;
uint8_t buf = '\0';
/* ignore characters when unclocked or in reset */
if (!clock_is_enabled(s->refclk) || device_is_in_reset(DEVICE(s))) {
qemu_log_mask(LOG_GUEST_ERROR, "%s: uart is unclocked or in reset\n",
__func__);
return;
}
if (event == CHR_EVENT_BREAK) {
uart_write_rx_fifo(opaque, &buf, 1);
}
uart_update_status(s);
}
static void uart_read_rx_fifo(CadenceUARTState *s, uint32_t *c)
{
if ((s->r[R_CR] & UART_CR_RX_DIS) || !(s->r[R_CR] & UART_CR_RX_EN)) {
return;
}
if (s->rx_count) {
uint32_t rx_rpos = (CADENCE_UART_RX_FIFO_SIZE + s->rx_wpos -
s->rx_count) % CADENCE_UART_RX_FIFO_SIZE;
*c = s->rx_fifo[rx_rpos];
s->rx_count--;
qemu_chr_fe_accept_input(&s->chr);
} else {
*c = 0;
}
uart_update_status(s);
}
static MemTxResult uart_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size, MemTxAttrs attrs)
{
CadenceUARTState *s = opaque;
/* ignore access when unclocked or in reset */
if (!clock_is_enabled(s->refclk) || device_is_in_reset(DEVICE(s))) {
qemu_log_mask(LOG_GUEST_ERROR, "%s: uart is unclocked or in reset\n",
__func__);
return MEMTX_ERROR;
}
DB_PRINT(" offset:%x data:%08x\n", (unsigned)offset, (unsigned)value);
offset >>= 2;
if (offset >= CADENCE_UART_R_MAX) {
return MEMTX_DECODE_ERROR;
}
switch (offset) {
case R_IER: /* ier (wts imr) */
s->r[R_IMR] |= value;
break;
case R_IDR: /* idr (wtc imr) */
s->r[R_IMR] &= ~value;
break;
case R_IMR: /* imr (read only) */
break;
case R_CISR: /* cisr (wtc) */
s->r[R_CISR] &= ~value;
break;
case R_TX_RX: /* UARTDR */
switch (s->r[R_MR] & UART_MR_CHMODE) {
case NORMAL_MODE:
uart_write_tx_fifo(s, (uint8_t *) &value, 1);
break;
case LOCAL_LOOPBACK:
uart_write_rx_fifo(opaque, (uint8_t *) &value, 1);
break;
}
break;
case R_BRGR: /* Baud rate generator */
value &= 0xffff;
if (value >= 0x01) {
s->r[offset] = value;
}
break;
case R_BDIV: /* Baud rate divider */
value &= 0xff;
if (value >= 0x04) {
s->r[offset] = value;
}
break;
default:
s->r[offset] = value;
}
switch (offset) {
case R_CR:
uart_ctrl_update(s);
break;
case R_MR:
uart_parameters_setup(s);
break;
}
uart_update_status(s);
return MEMTX_OK;
}
static MemTxResult uart_read(void *opaque, hwaddr offset,
uint64_t *value, unsigned size, MemTxAttrs attrs)
{
CadenceUARTState *s = opaque;
uint32_t c = 0;
/* ignore access when unclocked or in reset */
if (!clock_is_enabled(s->refclk) || device_is_in_reset(DEVICE(s))) {
qemu_log_mask(LOG_GUEST_ERROR, "%s: uart is unclocked or in reset\n",
__func__);
return MEMTX_ERROR;
}
offset >>= 2;
if (offset >= CADENCE_UART_R_MAX) {
return MEMTX_DECODE_ERROR;
}
if (offset == R_TX_RX) {
uart_read_rx_fifo(s, &c);
} else {
c = s->r[offset];
}
DB_PRINT(" offset:%x data:%08x\n", (unsigned)(offset << 2), (unsigned)c);
*value = c;
return MEMTX_OK;
}
static const MemoryRegionOps uart_ops = {
.read_with_attrs = uart_read,
.write_with_attrs = uart_write,
.endianness = DEVICE_NATIVE_ENDIAN,
};
static void cadence_uart_reset_init(Object *obj, ResetType type)
{
CadenceUARTState *s = CADENCE_UART(obj);
s->r[R_CR] = 0x00000128;
s->r[R_IMR] = 0;
s->r[R_CISR] = 0;
s->r[R_RTRIG] = 0x00000020;
s->r[R_BRGR] = 0x0000028B;
s->r[R_BDIV] = 0x0000000F;
s->r[R_TTRIG] = 0x00000020;
}
static void cadence_uart_reset_hold(Object *obj, ResetType type)
{
CadenceUARTState *s = CADENCE_UART(obj);
uart_rx_reset(s);
uart_tx_reset(s);
uart_update_status(s);
}
static void cadence_uart_realize(DeviceState *dev, Error **errp)
{
CadenceUARTState *s = CADENCE_UART(dev);
s->fifo_trigger_handle = timer_new_ns(QEMU_CLOCK_VIRTUAL,
fifo_trigger_update, s);
qemu_chr_fe_set_handlers(&s->chr, uart_can_receive, uart_receive,
uart_event, NULL, s, NULL, true);
}
static void cadence_uart_refclk_update(void *opaque, ClockEvent event)
{
CadenceUARTState *s = opaque;
/* recompute uart's speed on clock change */
uart_parameters_setup(s);
}
static void cadence_uart_init(Object *obj)
{
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
CadenceUARTState *s = CADENCE_UART(obj);
memory_region_init_io(&s->iomem, obj, &uart_ops, s, "uart", 0x1000);
sysbus_init_mmio(sbd, &s->iomem);
sysbus_init_irq(sbd, &s->irq);
s->refclk = qdev_init_clock_in(DEVICE(obj), "refclk",
cadence_uart_refclk_update, s, ClockUpdate);
/* initialize the frequency in case the clock remains unconnected */
clock_set_hz(s->refclk, UART_DEFAULT_REF_CLK);
s->char_tx_time = (NANOSECONDS_PER_SECOND / 9600) * 10;
}
static int cadence_uart_pre_load(void *opaque)
{
CadenceUARTState *s = opaque;
/* the frequency will be overridden if the refclk field is present */
clock_set_hz(s->refclk, UART_DEFAULT_REF_CLK);
return 0;
}
static int cadence_uart_post_load(void *opaque, int version_id)
{
CadenceUARTState *s = opaque;
/* Ensure these two aren't invalid numbers */
if (s->r[R_BRGR] < 1 || s->r[R_BRGR] & ~0xFFFF ||
s->r[R_BDIV] <= 3 || s->r[R_BDIV] & ~0xFF) {
/* Value is invalid, abort */
return 1;
}
uart_parameters_setup(s);
uart_update_status(s);
return 0;
}
static const VMStateDescription vmstate_cadence_uart = {
.name = "cadence_uart",
.version_id = 3,
.minimum_version_id = 2,
.pre_load = cadence_uart_pre_load,
.post_load = cadence_uart_post_load,
.fields = (const VMStateField[]) {
VMSTATE_UINT32_ARRAY(r, CadenceUARTState, CADENCE_UART_R_MAX),
VMSTATE_UINT8_ARRAY(rx_fifo, CadenceUARTState,
CADENCE_UART_RX_FIFO_SIZE),
VMSTATE_UINT8_ARRAY(tx_fifo, CadenceUARTState,
CADENCE_UART_TX_FIFO_SIZE),
VMSTATE_UINT32(rx_count, CadenceUARTState),
VMSTATE_UINT32(tx_count, CadenceUARTState),
VMSTATE_UINT32(rx_wpos, CadenceUARTState),
VMSTATE_TIMER_PTR(fifo_trigger_handle, CadenceUARTState),
VMSTATE_CLOCK_V(refclk, CadenceUARTState, 3),
VMSTATE_END_OF_LIST()
},
};
static const Property cadence_uart_properties[] = {
DEFINE_PROP_CHR("chardev", CadenceUARTState, chr),
};
static void cadence_uart_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
ResettableClass *rc = RESETTABLE_CLASS(klass);
dc->realize = cadence_uart_realize;
dc->vmsd = &vmstate_cadence_uart;
rc->phases.enter = cadence_uart_reset_init;
rc->phases.hold = cadence_uart_reset_hold;
device_class_set_props(dc, cadence_uart_properties);
}
static const TypeInfo cadence_uart_info = {
.name = TYPE_CADENCE_UART,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(CadenceUARTState),
.instance_init = cadence_uart_init,
.class_init = cadence_uart_class_init,
};
static void cadence_uart_register_types(void)
{
type_register_static(&cadence_uart_info);
}
type_init(cadence_uart_register_types)
+413
View File
@@ -0,0 +1,413 @@
/*
* ARM CMSDK APB UART emulation
*
* Copyright (c) 2017 Linaro Limited
* Written by Peter Maydell
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 or
* (at your option) any later version.
*/
/* This is a model of the "APB UART" which is part of the Cortex-M
* System Design Kit (CMSDK) and documented in the Cortex-M System
* Design Kit Technical Reference Manual (ARM DDI0479C):
* https://developer.arm.com/products/system-design/system-design-kits/cortex-m-system-design-kit
*/
#include "qemu/osdep.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "qapi/error.h"
#include "trace.h"
#include "hw/core/sysbus.h"
#include "migration/vmstate.h"
#include "hw/core/registerfields.h"
#include "chardev/char-fe.h"
#include "chardev/char-serial.h"
#include "hw/char/cmsdk-apb-uart.h"
#include "hw/core/irq.h"
#include "hw/core/qdev-properties-system.h"
REG32(DATA, 0)
REG32(STATE, 4)
FIELD(STATE, TXFULL, 0, 1)
FIELD(STATE, RXFULL, 1, 1)
FIELD(STATE, TXOVERRUN, 2, 1)
FIELD(STATE, RXOVERRUN, 3, 1)
REG32(CTRL, 8)
FIELD(CTRL, TX_EN, 0, 1)
FIELD(CTRL, RX_EN, 1, 1)
FIELD(CTRL, TX_INTEN, 2, 1)
FIELD(CTRL, RX_INTEN, 3, 1)
FIELD(CTRL, TXO_INTEN, 4, 1)
FIELD(CTRL, RXO_INTEN, 5, 1)
FIELD(CTRL, HSTEST, 6, 1)
REG32(INTSTATUS, 0xc)
FIELD(INTSTATUS, TX, 0, 1)
FIELD(INTSTATUS, RX, 1, 1)
FIELD(INTSTATUS, TXO, 2, 1)
FIELD(INTSTATUS, RXO, 3, 1)
REG32(BAUDDIV, 0x10)
REG32(PID4, 0xFD0)
REG32(PID5, 0xFD4)
REG32(PID6, 0xFD8)
REG32(PID7, 0xFDC)
REG32(PID0, 0xFE0)
REG32(PID1, 0xFE4)
REG32(PID2, 0xFE8)
REG32(PID3, 0xFEC)
REG32(CID0, 0xFF0)
REG32(CID1, 0xFF4)
REG32(CID2, 0xFF8)
REG32(CID3, 0xFFC)
/* PID/CID values */
static const int uart_id[] = {
0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */
0x21, 0xb8, 0x1b, 0x00, /* PID0..PID3 */
0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */
};
static bool uart_baudrate_ok(CMSDKAPBUART *s)
{
/* The minimum permitted bauddiv setting is 16, so we just ignore
* settings below that (usually this means the device has just
* been reset and not yet programmed).
*/
return s->bauddiv >= 16 && s->bauddiv <= s->pclk_frq;
}
static void uart_update_parameters(CMSDKAPBUART *s)
{
QEMUSerialSetParams ssp;
/* This UART is always 8N1 but the baud rate is programmable. */
if (!uart_baudrate_ok(s)) {
return;
}
ssp.data_bits = 8;
ssp.parity = 'N';
ssp.stop_bits = 1;
ssp.speed = s->pclk_frq / s->bauddiv;
qemu_chr_fe_ioctl(&s->chr, CHR_IOCTL_SERIAL_SET_PARAMS, &ssp);
trace_cmsdk_apb_uart_set_params(ssp.speed);
}
static void cmsdk_apb_uart_update(CMSDKAPBUART *s)
{
/* update outbound irqs, including handling the way the rxo and txo
* interrupt status bits are just logical AND of the overrun bit in
* STATE and the overrun interrupt enable bit in CTRL.
*/
uint32_t omask = (R_INTSTATUS_RXO_MASK | R_INTSTATUS_TXO_MASK);
s->intstatus &= ~omask;
s->intstatus |= (s->state & (s->ctrl >> 2) & omask);
qemu_set_irq(s->txint, !!(s->intstatus & R_INTSTATUS_TX_MASK));
qemu_set_irq(s->rxint, !!(s->intstatus & R_INTSTATUS_RX_MASK));
qemu_set_irq(s->txovrint, !!(s->intstatus & R_INTSTATUS_TXO_MASK));
qemu_set_irq(s->rxovrint, !!(s->intstatus & R_INTSTATUS_RXO_MASK));
qemu_set_irq(s->uartint, !!(s->intstatus));
}
static int uart_can_receive(void *opaque)
{
CMSDKAPBUART *s = CMSDK_APB_UART(opaque);
/* We can take a char if RX is enabled and the buffer is empty */
if (s->ctrl & R_CTRL_RX_EN_MASK && !(s->state & R_STATE_RXFULL_MASK)) {
return 1;
}
return 0;
}
static void uart_receive(void *opaque, const uint8_t *buf, int size)
{
CMSDKAPBUART *s = CMSDK_APB_UART(opaque);
trace_cmsdk_apb_uart_receive(*buf);
/* In fact uart_can_receive() ensures that we can't be
* called unless RX is enabled and the buffer is empty,
* but we include this logic as documentation of what the
* hardware does if a character arrives in these circumstances.
*/
if (!(s->ctrl & R_CTRL_RX_EN_MASK)) {
/* Just drop the character on the floor */
return;
}
if (s->state & R_STATE_RXFULL_MASK) {
s->state |= R_STATE_RXOVERRUN_MASK;
}
s->rxbuf = *buf;
s->state |= R_STATE_RXFULL_MASK;
if (s->ctrl & R_CTRL_RX_INTEN_MASK) {
s->intstatus |= R_INTSTATUS_RX_MASK;
}
cmsdk_apb_uart_update(s);
}
static uint64_t uart_read(void *opaque, hwaddr offset, unsigned size)
{
CMSDKAPBUART *s = CMSDK_APB_UART(opaque);
uint64_t r;
switch (offset) {
case A_DATA:
r = s->rxbuf;
if (!(s->ctrl & R_CTRL_RX_EN_MASK)) {
qemu_log_mask(LOG_GUEST_ERROR,
"CMSDK APB UART: receive data read with Rx disabled\n");
}
s->state &= ~R_STATE_RXFULL_MASK;
cmsdk_apb_uart_update(s);
qemu_chr_fe_accept_input(&s->chr);
break;
case A_STATE:
r = s->state;
break;
case A_CTRL:
r = s->ctrl;
break;
case A_INTSTATUS:
r = s->intstatus;
break;
case A_BAUDDIV:
r = s->bauddiv;
break;
case A_PID4 ... A_CID3:
r = uart_id[(offset - A_PID4) / 4];
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"CMSDK APB UART read: bad offset %x\n", (int) offset);
r = 0;
break;
}
trace_cmsdk_apb_uart_read(offset, r, size);
return r;
}
/* Try to send tx data, and arrange to be called back later if
* we can't (ie the char backend is busy/blocking).
*/
static gboolean uart_transmit(void *do_not_use, GIOCondition cond, void *opaque)
{
CMSDKAPBUART *s = CMSDK_APB_UART(opaque);
int ret;
s->watch_tag = 0;
if (!(s->ctrl & R_CTRL_TX_EN_MASK) || !(s->state & R_STATE_TXFULL_MASK)) {
return G_SOURCE_REMOVE;
}
ret = qemu_chr_fe_write(&s->chr, &s->txbuf, 1);
if (ret <= 0) {
s->watch_tag = qemu_chr_fe_add_watch(&s->chr, G_IO_OUT | G_IO_HUP,
uart_transmit, s);
if (!s->watch_tag) {
/* Most common reason to be here is "no chardev backend":
* just insta-drain the buffer, so the serial output
* goes into a void, rather than blocking the guest.
*/
goto buffer_drained;
}
/* Transmit pending */
trace_cmsdk_apb_uart_tx_pending();
return G_SOURCE_REMOVE;
}
buffer_drained:
/* Character successfully sent */
trace_cmsdk_apb_uart_tx(s->txbuf);
s->state &= ~R_STATE_TXFULL_MASK;
/* Going from TXFULL set to clear triggers the tx interrupt */
if (s->ctrl & R_CTRL_TX_INTEN_MASK) {
s->intstatus |= R_INTSTATUS_TX_MASK;
}
cmsdk_apb_uart_update(s);
return G_SOURCE_REMOVE;
}
static void uart_cancel_transmit(CMSDKAPBUART *s)
{
g_clear_handle_id(&s->watch_tag, g_source_remove);
}
static void uart_write(void *opaque, hwaddr offset, uint64_t value,
unsigned size)
{
CMSDKAPBUART *s = CMSDK_APB_UART(opaque);
trace_cmsdk_apb_uart_write(offset, value, size);
switch (offset) {
case A_DATA:
s->txbuf = value;
if (!(s->ctrl & R_CTRL_TX_EN_MASK)) {
qemu_log_mask(LOG_GUEST_ERROR,
"CMSDK APB UART: transmit data write with Tx disabled\n");
}
if (s->state & R_STATE_TXFULL_MASK) {
/* Buffer already full -- note the overrun and let the
* existing pending transmit callback handle the new char.
*/
s->state |= R_STATE_TXOVERRUN_MASK;
cmsdk_apb_uart_update(s);
} else {
s->state |= R_STATE_TXFULL_MASK;
uart_transmit(NULL, G_IO_OUT, s);
}
break;
case A_STATE:
/* Bits 0 and 1 are read only; bits 2 and 3 are W1C */
s->state &= ~(value &
(R_STATE_TXOVERRUN_MASK | R_STATE_RXOVERRUN_MASK));
cmsdk_apb_uart_update(s);
break;
case A_CTRL:
s->ctrl = value & 0x7f;
if ((s->ctrl & R_CTRL_TX_EN_MASK) && !uart_baudrate_ok(s)) {
qemu_log_mask(LOG_GUEST_ERROR,
"CMSDK APB UART: Tx enabled with invalid baudrate\n");
}
cmsdk_apb_uart_update(s);
break;
case A_INTSTATUS:
/* All bits are W1C. Clearing the overrun interrupt bits really
* clears the overrun status bits in the STATE register (which
* is then reflected into the intstatus value by the update function).
*/
s->state &= ~(value & (R_INTSTATUS_TXO_MASK | R_INTSTATUS_RXO_MASK));
s->intstatus &= ~value;
cmsdk_apb_uart_update(s);
break;
case A_BAUDDIV:
s->bauddiv = value & 0xFFFFF;
uart_update_parameters(s);
break;
case A_PID4 ... A_CID3:
qemu_log_mask(LOG_GUEST_ERROR,
"CMSDK APB UART write: write to RO offset 0x%x\n",
(int)offset);
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"CMSDK APB UART write: bad offset 0x%x\n", (int) offset);
break;
}
}
static const MemoryRegionOps uart_ops = {
.read = uart_read,
.write = uart_write,
.endianness = DEVICE_LITTLE_ENDIAN,
};
static void cmsdk_apb_uart_reset(DeviceState *dev)
{
CMSDKAPBUART *s = CMSDK_APB_UART(dev);
trace_cmsdk_apb_uart_reset();
uart_cancel_transmit(s);
s->state = 0;
s->ctrl = 0;
s->intstatus = 0;
s->bauddiv = 0;
s->txbuf = 0;
s->rxbuf = 0;
}
static void cmsdk_apb_uart_init(Object *obj)
{
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
CMSDKAPBUART *s = CMSDK_APB_UART(obj);
memory_region_init_io(&s->iomem, obj, &uart_ops, s, "uart", 0x1000);
sysbus_init_mmio(sbd, &s->iomem);
sysbus_init_irq(sbd, &s->txint);
sysbus_init_irq(sbd, &s->rxint);
sysbus_init_irq(sbd, &s->txovrint);
sysbus_init_irq(sbd, &s->rxovrint);
sysbus_init_irq(sbd, &s->uartint);
}
static void cmsdk_apb_uart_realize(DeviceState *dev, Error **errp)
{
CMSDKAPBUART *s = CMSDK_APB_UART(dev);
if (s->pclk_frq == 0) {
error_setg(errp, "CMSDK APB UART: pclk-frq property must be set");
return;
}
/* This UART has no flow control, so we do not need to register
* an event handler to deal with CHR_EVENT_BREAK.
*/
qemu_chr_fe_set_handlers(&s->chr, uart_can_receive, uart_receive,
NULL, NULL, s, NULL, true);
}
static int cmsdk_apb_uart_post_load(void *opaque, int version_id)
{
CMSDKAPBUART *s = CMSDK_APB_UART(opaque);
/* If we have a pending character, arrange to resend it. */
if (s->state & R_STATE_TXFULL_MASK) {
s->watch_tag = qemu_chr_fe_add_watch(&s->chr, G_IO_OUT | G_IO_HUP,
uart_transmit, s);
}
uart_update_parameters(s);
return 0;
}
static const VMStateDescription cmsdk_apb_uart_vmstate = {
.name = "cmsdk-apb-uart",
.version_id = 1,
.minimum_version_id = 1,
.post_load = cmsdk_apb_uart_post_load,
.fields = (const VMStateField[]) {
VMSTATE_UINT32(state, CMSDKAPBUART),
VMSTATE_UINT32(ctrl, CMSDKAPBUART),
VMSTATE_UINT32(intstatus, CMSDKAPBUART),
VMSTATE_UINT32(bauddiv, CMSDKAPBUART),
VMSTATE_UINT8(txbuf, CMSDKAPBUART),
VMSTATE_UINT8(rxbuf, CMSDKAPBUART),
VMSTATE_END_OF_LIST()
}
};
static const Property cmsdk_apb_uart_properties[] = {
DEFINE_PROP_CHR("chardev", CMSDKAPBUART, chr),
DEFINE_PROP_UINT32("pclk-frq", CMSDKAPBUART, pclk_frq, 0),
};
static void cmsdk_apb_uart_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = cmsdk_apb_uart_realize;
dc->vmsd = &cmsdk_apb_uart_vmstate;
device_class_set_legacy_reset(dc, cmsdk_apb_uart_reset);
device_class_set_props(dc, cmsdk_apb_uart_properties);
}
static const TypeInfo cmsdk_apb_uart_info = {
.name = TYPE_CMSDK_APB_UART,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(CMSDKAPBUART),
.instance_init = cmsdk_apb_uart_init,
.class_init = cmsdk_apb_uart_class_init,
};
static void cmsdk_apb_uart_register_types(void)
{
type_register_static(&cmsdk_apb_uart_info);
}
type_init(cmsdk_apb_uart_register_types);
+144
View File
@@ -0,0 +1,144 @@
/*
* QEMU Bochs-style debug console ("port E9") emulation
*
* Copyright (c) 2003-2004 Fabrice Bellard
* Copyright (c) 2008 Citrix Systems, Inc.
* Copyright (c) Intel Corporation; author: H. Peter Anvin
*
* 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
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* 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.
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qemu/module.h"
#include "chardev/char-fe.h"
#include "hw/isa/isa.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-properties-system.h"
#include "qom/object.h"
#define TYPE_ISA_DEBUGCON_DEVICE "isa-debugcon"
OBJECT_DECLARE_SIMPLE_TYPE(ISADebugconState, ISA_DEBUGCON_DEVICE)
//#define DEBUG_DEBUGCON
typedef struct DebugconState {
MemoryRegion io;
CharFrontend chr;
uint32_t readback;
} DebugconState;
struct ISADebugconState {
ISADevice parent_obj;
uint32_t iobase;
DebugconState state;
};
static void debugcon_ioport_write(void *opaque, hwaddr addr, uint64_t val,
unsigned width)
{
DebugconState *s = opaque;
unsigned char ch = val;
#ifdef DEBUG_DEBUGCON
printf(" [debugcon: write addr=0x%04" HWADDR_PRIx " val=0x%02" PRIx64 "]\n", addr, val);
#endif
/* XXX this blocks entire thread. Rewrite to use
* qemu_chr_fe_write and background I/O callbacks */
qemu_chr_fe_write_all(&s->chr, &ch, 1);
}
static uint64_t debugcon_ioport_read(void *opaque, hwaddr addr, unsigned width)
{
DebugconState *s = opaque;
#ifdef DEBUG_DEBUGCON
printf("debugcon: read addr=0x%04" HWADDR_PRIx "\n", addr);
#endif
return s->readback;
}
static const MemoryRegionOps debugcon_ops = {
.read = debugcon_ioport_read,
.write = debugcon_ioport_write,
.valid.min_access_size = 1,
.valid.max_access_size = 1,
.endianness = DEVICE_LITTLE_ENDIAN,
};
static void debugcon_realize_core(DebugconState *s, Error **errp)
{
if (!qemu_chr_fe_backend_connected(&s->chr)) {
error_setg(errp, "Can't create debugcon device, empty char device");
return;
}
qemu_chr_fe_set_handlers(&s->chr, NULL, NULL, NULL, NULL, s, NULL, true);
}
static void debugcon_isa_realizefn(DeviceState *dev, Error **errp)
{
ISADevice *d = ISA_DEVICE(dev);
ISADebugconState *isa = ISA_DEBUGCON_DEVICE(dev);
DebugconState *s = &isa->state;
Error *err = NULL;
debugcon_realize_core(s, &err);
if (err != NULL) {
error_propagate(errp, err);
return;
}
memory_region_init_io(&s->io, OBJECT(dev), &debugcon_ops, s,
TYPE_ISA_DEBUGCON_DEVICE, 1);
memory_region_add_subregion(isa_address_space_io(d),
isa->iobase, &s->io);
}
static const Property debugcon_isa_properties[] = {
DEFINE_PROP_UINT32("iobase", ISADebugconState, iobase, 0xe9),
DEFINE_PROP_CHR("chardev", ISADebugconState, state.chr),
DEFINE_PROP_UINT32("readback", ISADebugconState, state.readback, 0xe9),
};
static void debugcon_isa_class_initfn(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = debugcon_isa_realizefn;
device_class_set_props(dc, debugcon_isa_properties);
set_bit(DEVICE_CATEGORY_MISC, dc->categories);
}
static const TypeInfo debugcon_isa_info = {
.name = TYPE_ISA_DEBUGCON_DEVICE,
.parent = TYPE_ISA_DEVICE,
.instance_size = sizeof(ISADebugconState),
.class_init = debugcon_isa_class_initfn,
};
static void debugcon_register_types(void)
{
type_register_static(&debugcon_isa_info);
}
type_init(debugcon_register_types)
+202
View File
@@ -0,0 +1,202 @@
/*
* QEMU model of the Canon DIGIC UART block.
*
* Copyright (C) 2013 Antony Pavlov <[email protected]>
*
* This model is based on reverse engineering efforts
* made by CHDK (http://chdk.wikia.com) and
* Magic Lantern (http://www.magiclantern.fm) projects
* contributors.
*
* See "Serial terminal" docs here:
* http://magiclantern.wikia.com/wiki/Register_Map#Misc_Registers
*
* The QEMU model of the Milkymist UART block by Michael Walle
* is used as a template.
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
*/
#include "qemu/osdep.h"
#include "hw/core/sysbus.h"
#include "migration/vmstate.h"
#include "chardev/char-fe.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "hw/char/digic-uart.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-properties-system.h"
enum {
ST_RX_RDY = (1 << 0),
ST_TX_RDY = (1 << 1),
};
static uint64_t digic_uart_read(void *opaque, hwaddr addr,
unsigned size)
{
DigicUartState *s = opaque;
uint64_t ret = 0;
addr >>= 2;
switch (addr) {
case R_RX:
s->reg_st &= ~(ST_RX_RDY);
ret = s->reg_rx;
break;
case R_ST:
ret = s->reg_st;
break;
default:
qemu_log_mask(LOG_UNIMP,
"digic-uart: read access to unknown register 0x"
HWADDR_FMT_plx "\n", addr << 2);
}
return ret;
}
static void digic_uart_write(void *opaque, hwaddr addr, uint64_t value,
unsigned size)
{
DigicUartState *s = opaque;
unsigned char ch = value;
addr >>= 2;
switch (addr) {
case R_TX:
/* XXX this blocks entire thread. Rewrite to use
* qemu_chr_fe_write and background I/O callbacks */
qemu_chr_fe_write_all(&s->chr, &ch, 1);
break;
case R_ST:
/*
* Ignore write to R_ST.
*
* The point is that this register is actively used
* during receiving and transmitting symbols,
* but we don't know the function of most of bits.
*
* Ignoring writes to R_ST is only a simplification
* of the model. It has no perceptible side effects
* for existing guests.
*/
break;
default:
qemu_log_mask(LOG_UNIMP,
"digic-uart: write access to unknown register 0x"
HWADDR_FMT_plx "\n", addr << 2);
}
}
static const MemoryRegionOps uart_mmio_ops = {
.read = digic_uart_read,
.write = digic_uart_write,
.valid = {
.min_access_size = 4,
.max_access_size = 4,
},
.endianness = DEVICE_NATIVE_ENDIAN,
};
static int uart_can_rx(void *opaque)
{
DigicUartState *s = opaque;
return !(s->reg_st & ST_RX_RDY);
}
static void uart_rx(void *opaque, const uint8_t *buf, int size)
{
DigicUartState *s = opaque;
assert(uart_can_rx(opaque));
s->reg_st |= ST_RX_RDY;
s->reg_rx = *buf;
}
static void uart_event(void *opaque, QEMUChrEvent event)
{
}
static void digic_uart_reset(DeviceState *d)
{
DigicUartState *s = DIGIC_UART(d);
s->reg_rx = 0;
s->reg_st = ST_TX_RDY;
}
static void digic_uart_realize(DeviceState *dev, Error **errp)
{
DigicUartState *s = DIGIC_UART(dev);
qemu_chr_fe_set_handlers(&s->chr, uart_can_rx, uart_rx,
uart_event, NULL, s, NULL, true);
}
static void digic_uart_init(Object *obj)
{
DigicUartState *s = DIGIC_UART(obj);
memory_region_init_io(&s->regs_region, OBJECT(s), &uart_mmio_ops, s,
TYPE_DIGIC_UART, 0x18);
sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->regs_region);
}
static const VMStateDescription vmstate_digic_uart = {
.name = "digic-uart",
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_UINT32(reg_rx, DigicUartState),
VMSTATE_UINT32(reg_st, DigicUartState),
VMSTATE_END_OF_LIST()
}
};
static const Property digic_uart_properties[] = {
DEFINE_PROP_CHR("chardev", DigicUartState, chr),
};
static void digic_uart_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = digic_uart_realize;
device_class_set_legacy_reset(dc, digic_uart_reset);
dc->vmsd = &vmstate_digic_uart;
device_class_set_props(dc, digic_uart_properties);
}
static const TypeInfo digic_uart_info = {
.name = TYPE_DIGIC_UART,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(DigicUartState),
.instance_init = digic_uart_init,
.class_init = digic_uart_class_init,
};
static void digic_uart_register_types(void)
{
type_register_static(&digic_uart_info);
}
type_init(digic_uart_register_types)
+292
View File
@@ -0,0 +1,292 @@
/*
* HP Diva GSP controller
*
* The Diva PCI boards are Remote Management cards for PA-RISC machines.
* They come with built-in 16550A multi UARTs for serial consoles
* and a mailbox-like memory area for hardware auto-reboot functionality.
* GSP stands for "Guardian Service Processor". Later products were marketed
* "Management Processor" (MP).
*
* Diva cards are multifunctional cards. The first part, the aux port,
* is on physical machines not useable but we still try to mimic it here.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Copyright (c) 2025 Helge Deller <[email protected]>
*/
#include "qemu/osdep.h"
#include "qemu/units.h"
#include "hw/char/serial.h"
#include "hw/core/irq.h"
#include "hw/pci/pci_device.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-properties-system.h"
#include "migration/vmstate.h"
#define PCI_DEVICE_ID_HP_DIVA 0x1048
/* various DIVA GSP cards: */
#define PCI_DEVICE_ID_HP_DIVA_TOSCA1 0x1049
#define PCI_DEVICE_ID_HP_DIVA_TOSCA2 0x104A
#define PCI_DEVICE_ID_HP_DIVA_MAESTRO 0x104B
#define PCI_DEVICE_ID_HP_REO_IOC 0x10f1
#define PCI_DEVICE_ID_HP_DIVA_HALFDOME 0x1223
#define PCI_DEVICE_ID_HP_DIVA_KEYSTONE 0x1226
#define PCI_DEVICE_ID_HP_DIVA_POWERBAR 0x1227
#define PCI_DEVICE_ID_HP_DIVA_EVEREST 0x1282
#define PCI_DEVICE_ID_HP_DIVA_AUX 0x1290
#define PCI_DEVICE_ID_HP_DIVA_RMP3 0x1301
#define PCI_DEVICE_ID_HP_DIVA_HURRICANE 0x132a
#define PCI_SERIAL_MAX_PORTS 4
typedef struct PCIDivaSerialState {
PCIDevice dev;
MemoryRegion membar; /* for serial ports */
MemoryRegion mailboxbar; /* for hardware mailbox */
uint32_t subvendor;
uint32_t ports;
char *name[PCI_SERIAL_MAX_PORTS];
SerialState state[PCI_SERIAL_MAX_PORTS];
uint32_t level[PCI_SERIAL_MAX_PORTS];
qemu_irq *irqs;
} PCIDivaSerialState;
static void diva_pci_exit(PCIDevice *dev)
{
PCIDivaSerialState *pci = DO_UPCAST(PCIDivaSerialState, dev, dev);
SerialState *s;
int i;
for (i = 0; i < pci->ports; i++) {
s = pci->state + i;
memory_region_del_subregion(&pci->membar, &s->io);
qdev_unrealize(DEVICE(s));
g_free(pci->name[i]);
}
qemu_free_irqs(pci->irqs, pci->ports);
}
static void multi_serial_irq_mux(void *opaque, int n, int level)
{
PCIDivaSerialState *pci = opaque;
int i, pending = 0;
pci->level[n] = level;
for (i = 0; i < pci->ports; i++) {
if (pci->level[i]) {
pending = 1;
}
}
pci_set_irq(&pci->dev, pending);
}
struct diva_info {
unsigned int nports:4; /* number of serial ports */
unsigned int omask:12; /* offset mask: BIT(1) -> offset 8 */
};
static struct diva_info diva_get_diva_info(PCIDeviceClass *pc)
{
switch (pc->subsystem_id) {
case PCI_DEVICE_ID_HP_DIVA_POWERBAR:
case PCI_DEVICE_ID_HP_DIVA_HURRICANE:
return (struct diva_info) { .nports = 1,
.omask = BIT(0) };
case PCI_DEVICE_ID_HP_DIVA_TOSCA2:
return (struct diva_info) { .nports = 2,
.omask = BIT(0) | BIT(1) };
case PCI_DEVICE_ID_HP_DIVA_TOSCA1:
case PCI_DEVICE_ID_HP_DIVA_HALFDOME:
case PCI_DEVICE_ID_HP_DIVA_KEYSTONE:
return (struct diva_info) { .nports = 3,
.omask = BIT(0) | BIT(1) | BIT(2) };
case PCI_DEVICE_ID_HP_DIVA_EVEREST: /* e.g. in rp3410 */
return (struct diva_info) { .nports = 3,
.omask = BIT(0) | BIT(2) | BIT(7) };
case PCI_DEVICE_ID_HP_DIVA_MAESTRO:
return (struct diva_info) { .nports = 4,
.omask = BIT(0) | BIT(1) | BIT(2) | BIT(7) };
}
g_assert_not_reached();
}
static void diva_pci_realize(PCIDevice *dev, Error **errp)
{
PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(dev);
PCIDivaSerialState *pci = DO_UPCAST(PCIDivaSerialState, dev, dev);
SerialState *s;
struct diva_info di = diva_get_diva_info(pc);
size_t i, offset = 0;
size_t portmask = di.omask;
pci->dev.config[PCI_CLASS_PROG] = 2; /* 16550 compatible */
pci->dev.config[PCI_INTERRUPT_PIN] = 1;
memory_region_init(&pci->membar, OBJECT(pci), "serial_ports", 4096);
pci_register_bar(&pci->dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &pci->membar);
pci->irqs = qemu_allocate_irqs(multi_serial_irq_mux, pci, di.nports);
for (i = 0; i < di.nports; i++) {
s = pci->state + i;
if (!qdev_realize(DEVICE(s), NULL, errp)) {
diva_pci_exit(dev);
return;
}
s->irq = pci->irqs[i];
pci->name[i] = g_strdup_printf("uart #%zu", i + 1);
memory_region_init_io(&s->io, OBJECT(pci), &serial_io_ops, s,
pci->name[i], 8);
/* calculate offset of given port based on bitmask */
while ((portmask & BIT(0)) == 0) {
offset += 8;
portmask >>= 1;
}
memory_region_add_subregion(&pci->membar, offset, &s->io);
offset += 8;
portmask >>= 1;
pci->ports++;
}
/* mailbox bar */
memory_region_init(&pci->mailboxbar, OBJECT(pci), "mailbox", 128 * KiB);
pci_register_bar(&pci->dev, 1, PCI_BASE_ADDRESS_SPACE_MEMORY |
PCI_BASE_ADDRESS_MEM_PREFETCH, &pci->mailboxbar);
}
static const VMStateDescription vmstate_pci_diva = {
.name = "pci-diva-serial",
.version_id = 2,
.minimum_version_id = 2,
.fields = (const VMStateField[]) {
VMSTATE_PCI_DEVICE(dev, PCIDivaSerialState),
VMSTATE_STRUCT_ARRAY(state, PCIDivaSerialState, PCI_SERIAL_MAX_PORTS,
0, vmstate_serial, SerialState),
VMSTATE_UINT32_ARRAY(level, PCIDivaSerialState, PCI_SERIAL_MAX_PORTS),
VMSTATE_END_OF_LIST()
}
};
static const Property diva_serial_properties[] = {
DEFINE_PROP_CHR("chardev1", PCIDivaSerialState, state[0].chr),
DEFINE_PROP_CHR("chardev2", PCIDivaSerialState, state[1].chr),
DEFINE_PROP_CHR("chardev3", PCIDivaSerialState, state[2].chr),
DEFINE_PROP_CHR("chardev4", PCIDivaSerialState, state[3].chr),
DEFINE_PROP_UINT32("subvendor", PCIDivaSerialState, subvendor,
PCI_DEVICE_ID_HP_DIVA_TOSCA1),
};
static void diva_serial_class_initfn(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
PCIDeviceClass *pc = PCI_DEVICE_CLASS(klass);
pc->realize = diva_pci_realize;
pc->exit = diva_pci_exit;
pc->vendor_id = PCI_VENDOR_ID_HP;
pc->device_id = PCI_DEVICE_ID_HP_DIVA;
pc->subsystem_vendor_id = PCI_VENDOR_ID_HP;
pc->subsystem_id = PCI_DEVICE_ID_HP_DIVA_TOSCA1;
pc->revision = 3;
pc->class_id = PCI_CLASS_COMMUNICATION_SERIAL;
dc->vmsd = &vmstate_pci_diva;
device_class_set_props(dc, diva_serial_properties);
set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
}
static void diva_serial_init(Object *o)
{
PCIDevice *dev = PCI_DEVICE(o);
PCIDivaSerialState *pms = DO_UPCAST(PCIDivaSerialState, dev, dev);
struct diva_info di = diva_get_diva_info(PCI_DEVICE_GET_CLASS(dev));
size_t i;
for (i = 0; i < di.nports; i++) {
object_initialize_child(o, "serial[*]", &pms->state[i], TYPE_SERIAL);
}
}
/* Diva-aux is the driver for portion 0 of the multifunction PCI device */
struct DivaAuxState {
PCIDevice dev;
MemoryRegion mem;
qemu_irq irq;
};
#define TYPE_DIVA_AUX "diva-aux"
OBJECT_DECLARE_SIMPLE_TYPE(DivaAuxState, DIVA_AUX)
static void diva_aux_realize(PCIDevice *dev, Error **errp)
{
DivaAuxState *pci = DO_UPCAST(DivaAuxState, dev, dev);
pci->dev.config[PCI_CLASS_PROG] = 0x02;
pci->dev.config[PCI_INTERRUPT_PIN] = 0x01;
pci->irq = pci_allocate_irq(&pci->dev);
memory_region_init(&pci->mem, OBJECT(pci), "mem", 16);
pci_register_bar(&pci->dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &pci->mem);
}
static void diva_aux_exit(PCIDevice *dev)
{
DivaAuxState *pci = DO_UPCAST(DivaAuxState, dev, dev);
qemu_free_irq(pci->irq);
}
static void diva_aux_class_initfn(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
PCIDeviceClass *pc = PCI_DEVICE_CLASS(klass);
pc->realize = diva_aux_realize;
pc->exit = diva_aux_exit;
pc->vendor_id = PCI_VENDOR_ID_HP;
pc->device_id = PCI_DEVICE_ID_HP_DIVA_AUX;
pc->subsystem_vendor_id = PCI_VENDOR_ID_HP;
pc->subsystem_id = 0x1291;
pc->revision = 1;
pc->class_id = PCI_CLASS_COMMUNICATION_MULTISERIAL;
set_bit(DEVICE_CATEGORY_MISC, dc->categories);
dc->user_creatable = false;
}
static void diva_aux_init(Object *o)
{
}
static const TypeInfo diva_aux_info = {
.name = TYPE_DIVA_AUX,
.parent = TYPE_PCI_DEVICE,
.instance_size = sizeof(DivaAuxState),
.instance_init = diva_aux_init,
.class_init = diva_aux_class_initfn,
.interfaces = (const InterfaceInfo[]) {
{ INTERFACE_CONVENTIONAL_PCI_DEVICE },
{ },
},
};
static const TypeInfo diva_serial_pci_info = {
.name = "diva-gsp",
.parent = TYPE_PCI_DEVICE,
.instance_size = sizeof(PCIDivaSerialState),
.instance_init = diva_serial_init,
.class_init = diva_serial_class_initfn,
.interfaces = (const InterfaceInfo[]) {
{ INTERFACE_CONVENTIONAL_PCI_DEVICE },
{ },
},
};
static void diva_pci_register_type(void)
{
type_register_static(&diva_serial_pci_info);
type_register_static(&diva_aux_info);
}
type_init(diva_pci_register_type)
+1123
View File
File diff suppressed because it is too large Load Diff
+737
View File
@@ -0,0 +1,737 @@
/*
* Exynos4210 UART Emulation
*
* Copyright (C) 2011 Samsung Electronics Co Ltd.
* Maksim Kozlov, <[email protected]>
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
#include "qemu/osdep.h"
#include "hw/core/sysbus.h"
#include "migration/vmstate.h"
#include "qapi/error.h"
#include "qemu/error-report.h"
#include "qemu/module.h"
#include "qemu/timer.h"
#include "chardev/char-fe.h"
#include "chardev/char-serial.h"
#include "hw/arm/exynos4210.h"
#include "hw/core/irq.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-properties-system.h"
#include "trace.h"
#include "qom/object.h"
/*
* Offsets for UART registers relative to SFR base address
* for UARTn
*
*/
#define ULCON 0x0000 /* Line Control */
#define UCON 0x0004 /* Control */
#define UFCON 0x0008 /* FIFO Control */
#define UMCON 0x000C /* Modem Control */
#define UTRSTAT 0x0010 /* Tx/Rx Status */
#define UERSTAT 0x0014 /* UART Error Status */
#define UFSTAT 0x0018 /* FIFO Status */
#define UMSTAT 0x001C /* Modem Status */
#define UTXH 0x0020 /* Transmit Buffer */
#define URXH 0x0024 /* Receive Buffer */
#define UBRDIV 0x0028 /* Baud Rate Divisor */
#define UFRACVAL 0x002C /* Divisor Fractional Value */
#define UINTP 0x0030 /* Interrupt Pending */
#define UINTSP 0x0034 /* Interrupt Source Pending */
#define UINTM 0x0038 /* Interrupt Mask */
/*
* for indexing register in the uint32_t array
*
* 'reg' - register offset (see offsets definitions above)
*
*/
#define I_(reg) (reg / sizeof(uint32_t))
typedef struct Exynos4210UartReg {
const char *name; /* the only reason is the debug output */
hwaddr offset;
uint32_t reset_value;
} Exynos4210UartReg;
static const Exynos4210UartReg exynos4210_uart_regs[] = {
{"ULCON", ULCON, 0x00000000},
{"UCON", UCON, 0x00003000},
{"UFCON", UFCON, 0x00000000},
{"UMCON", UMCON, 0x00000000},
{"UTRSTAT", UTRSTAT, 0x00000006}, /* RO */
{"UERSTAT", UERSTAT, 0x00000000}, /* RO */
{"UFSTAT", UFSTAT, 0x00000000}, /* RO */
{"UMSTAT", UMSTAT, 0x00000000}, /* RO */
{"UTXH", UTXH, 0x5c5c5c5c}, /* WO, undefined reset value*/
{"URXH", URXH, 0x00000000}, /* RO */
{"UBRDIV", UBRDIV, 0x00000000},
{"UFRACVAL", UFRACVAL, 0x00000000},
{"UINTP", UINTP, 0x00000000},
{"UINTSP", UINTSP, 0x00000000},
{"UINTM", UINTM, 0x00000000},
};
#define EXYNOS4210_UART_REGS_MEM_SIZE 0x3C
/* UART FIFO Control */
#define UFCON_FIFO_ENABLE 0x1
#define UFCON_Rx_FIFO_RESET 0x2
#define UFCON_Tx_FIFO_RESET 0x4
#define UFCON_Tx_FIFO_TRIGGER_LEVEL_SHIFT 8
#define UFCON_Tx_FIFO_TRIGGER_LEVEL (7 << UFCON_Tx_FIFO_TRIGGER_LEVEL_SHIFT)
#define UFCON_Rx_FIFO_TRIGGER_LEVEL_SHIFT 4
#define UFCON_Rx_FIFO_TRIGGER_LEVEL (7 << UFCON_Rx_FIFO_TRIGGER_LEVEL_SHIFT)
/* Uart FIFO Status */
#define UFSTAT_Rx_FIFO_COUNT 0xff
#define UFSTAT_Rx_FIFO_FULL 0x100
#define UFSTAT_Rx_FIFO_ERROR 0x200
#define UFSTAT_Tx_FIFO_COUNT_SHIFT 16
#define UFSTAT_Tx_FIFO_COUNT (0xff << UFSTAT_Tx_FIFO_COUNT_SHIFT)
#define UFSTAT_Tx_FIFO_FULL_SHIFT 24
#define UFSTAT_Tx_FIFO_FULL (1 << UFSTAT_Tx_FIFO_FULL_SHIFT)
/* UART Interrupt Source Pending */
#define UINTSP_RXD 0x1 /* Receive interrupt */
#define UINTSP_ERROR 0x2 /* Error interrupt */
#define UINTSP_TXD 0x4 /* Transmit interrupt */
#define UINTSP_MODEM 0x8 /* Modem interrupt */
/* UART Line Control */
#define ULCON_IR_MODE_SHIFT 6
#define ULCON_PARITY_SHIFT 3
#define ULCON_STOP_BIT_SHIFT 1
/* UART Tx/Rx Status */
#define UTRSTAT_Rx_TIMEOUT 0x8
#define UTRSTAT_TRANSMITTER_EMPTY 0x4
#define UTRSTAT_Tx_BUFFER_EMPTY 0x2
#define UTRSTAT_Rx_BUFFER_DATA_READY 0x1
/* UART Error Status */
#define UERSTAT_OVERRUN 0x1
#define UERSTAT_PARITY 0x2
#define UERSTAT_FRAME 0x4
#define UERSTAT_BREAK 0x8
typedef struct {
uint8_t *data;
uint32_t sp, rp; /* store and retrieve pointers */
uint32_t size;
} Exynos4210UartFIFO;
#define TYPE_EXYNOS4210_UART "exynos4210.uart"
OBJECT_DECLARE_SIMPLE_TYPE(Exynos4210UartState, EXYNOS4210_UART)
struct Exynos4210UartState {
SysBusDevice parent_obj;
MemoryRegion iomem;
uint32_t reg[EXYNOS4210_UART_REGS_MEM_SIZE / sizeof(uint32_t)];
Exynos4210UartFIFO rx;
Exynos4210UartFIFO tx;
QEMUTimer *fifo_timeout_timer;
uint64_t wordtime; /* word time in ns */
CharFrontend chr;
qemu_irq irq;
qemu_irq dmairq;
uint32_t channel;
};
/* Used only for tracing */
static const char *exynos4210_uart_regname(hwaddr offset)
{
int i;
for (i = 0; i < ARRAY_SIZE(exynos4210_uart_regs); i++) {
if (offset == exynos4210_uart_regs[i].offset) {
return exynos4210_uart_regs[i].name;
}
}
return NULL;
}
static void fifo_store(Exynos4210UartFIFO *q, uint8_t ch)
{
q->data[q->sp] = ch;
q->sp = (q->sp + 1) % q->size;
}
static uint8_t fifo_retrieve(Exynos4210UartFIFO *q)
{
uint8_t ret = q->data[q->rp];
q->rp = (q->rp + 1) % q->size;
return ret;
}
static int fifo_elements_number(const Exynos4210UartFIFO *q)
{
if (q->sp < q->rp) {
return q->size - q->rp + q->sp;
}
return q->sp - q->rp;
}
static int fifo_empty_elements_number(const Exynos4210UartFIFO *q)
{
return q->size - fifo_elements_number(q);
}
static void fifo_reset(Exynos4210UartFIFO *q)
{
g_free(q->data);
q->data = NULL;
q->data = g_malloc0(q->size);
q->sp = 0;
q->rp = 0;
}
static uint32_t exynos4210_uart_FIFO_trigger_level(uint32_t channel,
uint32_t reg)
{
uint32_t level;
switch (channel) {
case 0:
level = reg * 32;
break;
case 1:
case 4:
level = reg * 8;
break;
case 2:
case 3:
level = reg * 2;
break;
default:
level = 0;
trace_exynos_uart_channel_error(channel);
break;
}
return level;
}
static uint32_t
exynos4210_uart_Tx_FIFO_trigger_level(const Exynos4210UartState *s)
{
uint32_t reg;
reg = (s->reg[I_(UFCON)] & UFCON_Tx_FIFO_TRIGGER_LEVEL) >>
UFCON_Tx_FIFO_TRIGGER_LEVEL_SHIFT;
return exynos4210_uart_FIFO_trigger_level(s->channel, reg);
}
static uint32_t
exynos4210_uart_Rx_FIFO_trigger_level(const Exynos4210UartState *s)
{
uint32_t reg;
reg = ((s->reg[I_(UFCON)] & UFCON_Rx_FIFO_TRIGGER_LEVEL) >>
UFCON_Rx_FIFO_TRIGGER_LEVEL_SHIFT) + 1;
return exynos4210_uart_FIFO_trigger_level(s->channel, reg);
}
/*
* Update Rx DMA busy signal if Rx DMA is enabled. For simplicity,
* mark DMA as busy if DMA is enabled and the receive buffer is empty.
*/
static void exynos4210_uart_update_dmabusy(Exynos4210UartState *s)
{
bool rx_dma_enabled = (s->reg[I_(UCON)] & 0x03) == 0x02;
uint32_t count = fifo_elements_number(&s->rx);
if (rx_dma_enabled && !count) {
qemu_irq_raise(s->dmairq);
trace_exynos_uart_dmabusy(s->channel);
} else {
qemu_irq_lower(s->dmairq);
trace_exynos_uart_dmaready(s->channel);
}
}
static void exynos4210_uart_update_irq(Exynos4210UartState *s)
{
/*
* The Tx interrupt is always requested if the number of data in the
* transmit FIFO is smaller than the trigger level.
*/
if (s->reg[I_(UFCON)] & UFCON_FIFO_ENABLE) {
uint32_t count = (s->reg[I_(UFSTAT)] & UFSTAT_Tx_FIFO_COUNT) >>
UFSTAT_Tx_FIFO_COUNT_SHIFT;
if (count <= exynos4210_uart_Tx_FIFO_trigger_level(s)) {
s->reg[I_(UINTSP)] |= UINTSP_TXD;
}
/*
* Rx interrupt if trigger level is reached or if rx timeout
* interrupt is disabled and there is data in the receive buffer
*/
count = fifo_elements_number(&s->rx);
if ((count && !(s->reg[I_(UCON)] & 0x80)) ||
count >= exynos4210_uart_Rx_FIFO_trigger_level(s)) {
exynos4210_uart_update_dmabusy(s);
s->reg[I_(UINTSP)] |= UINTSP_RXD;
timer_del(s->fifo_timeout_timer);
}
} else if (s->reg[I_(UTRSTAT)] & UTRSTAT_Rx_BUFFER_DATA_READY) {
exynos4210_uart_update_dmabusy(s);
s->reg[I_(UINTSP)] |= UINTSP_RXD;
}
s->reg[I_(UINTP)] = s->reg[I_(UINTSP)] & ~s->reg[I_(UINTM)];
if (s->reg[I_(UINTP)]) {
qemu_irq_raise(s->irq);
trace_exynos_uart_irq_raised(s->channel, s->reg[I_(UINTP)]);
} else {
qemu_irq_lower(s->irq);
trace_exynos_uart_irq_lowered(s->channel);
}
}
static void exynos4210_uart_timeout_int(void *opaque)
{
Exynos4210UartState *s = opaque;
trace_exynos_uart_rx_timeout(s->channel, s->reg[I_(UTRSTAT)],
s->reg[I_(UINTSP)]);
if ((s->reg[I_(UTRSTAT)] & UTRSTAT_Rx_BUFFER_DATA_READY) ||
(s->reg[I_(UCON)] & (1 << 11))) {
s->reg[I_(UINTSP)] |= UINTSP_RXD;
s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_TIMEOUT;
exynos4210_uart_update_dmabusy(s);
exynos4210_uart_update_irq(s);
}
}
static void exynos4210_uart_update_parameters(Exynos4210UartState *s)
{
int speed, parity, data_bits, stop_bits;
QEMUSerialSetParams ssp;
uint64_t uclk_rate;
if (s->reg[I_(UBRDIV)] == 0) {
return;
}
if (s->reg[I_(ULCON)] & 0x20) {
if (s->reg[I_(ULCON)] & 0x28) {
parity = 'E';
} else {
parity = 'O';
}
} else {
parity = 'N';
}
if (s->reg[I_(ULCON)] & 0x4) {
stop_bits = 2;
} else {
stop_bits = 1;
}
data_bits = (s->reg[I_(ULCON)] & 0x3) + 5;
uclk_rate = 24000000;
speed = uclk_rate / ((16 * (s->reg[I_(UBRDIV)]) & 0xffff) +
(s->reg[I_(UFRACVAL)] & 0x7) + 16);
ssp.speed = speed;
ssp.parity = parity;
ssp.data_bits = data_bits;
ssp.stop_bits = stop_bits;
s->wordtime = NANOSECONDS_PER_SECOND * (data_bits + stop_bits + 1) / speed;
qemu_chr_fe_ioctl(&s->chr, CHR_IOCTL_SERIAL_SET_PARAMS, &ssp);
trace_exynos_uart_update_params(
s->channel, speed, parity, data_bits, stop_bits, s->wordtime);
}
static void exynos4210_uart_rx_timeout_set(Exynos4210UartState *s)
{
if (s->reg[I_(UCON)] & 0x80) {
uint32_t timeout = ((s->reg[I_(UCON)] >> 12) & 0x0f) * s->wordtime;
timer_mod(s->fifo_timeout_timer,
qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + timeout);
} else {
timer_del(s->fifo_timeout_timer);
}
}
static void exynos4210_uart_write(void *opaque, hwaddr offset,
uint64_t val, unsigned size)
{
Exynos4210UartState *s = (Exynos4210UartState *)opaque;
uint8_t ch;
trace_exynos_uart_write(s->channel, offset,
exynos4210_uart_regname(offset), val);
switch (offset) {
case ULCON:
case UBRDIV:
case UFRACVAL:
s->reg[I_(offset)] = val;
exynos4210_uart_update_parameters(s);
break;
case UFCON:
s->reg[I_(UFCON)] = val;
if (val & UFCON_Rx_FIFO_RESET) {
fifo_reset(&s->rx);
s->reg[I_(UFCON)] &= ~UFCON_Rx_FIFO_RESET;
trace_exynos_uart_rx_fifo_reset(s->channel);
}
if (val & UFCON_Tx_FIFO_RESET) {
fifo_reset(&s->tx);
s->reg[I_(UFCON)] &= ~UFCON_Tx_FIFO_RESET;
trace_exynos_uart_tx_fifo_reset(s->channel);
}
break;
case UTXH:
if (qemu_chr_fe_backend_connected(&s->chr)) {
s->reg[I_(UTRSTAT)] &= ~(UTRSTAT_TRANSMITTER_EMPTY |
UTRSTAT_Tx_BUFFER_EMPTY);
ch = (uint8_t)val;
/* XXX this blocks entire thread. Rewrite to use
* qemu_chr_fe_write and background I/O callbacks */
qemu_chr_fe_write_all(&s->chr, &ch, 1);
trace_exynos_uart_tx(s->channel, ch);
s->reg[I_(UTRSTAT)] |= UTRSTAT_TRANSMITTER_EMPTY |
UTRSTAT_Tx_BUFFER_EMPTY;
s->reg[I_(UINTSP)] |= UINTSP_TXD;
exynos4210_uart_update_irq(s);
}
break;
case UINTP:
s->reg[I_(UINTP)] &= ~val;
s->reg[I_(UINTSP)] &= ~val;
trace_exynos_uart_intclr(s->channel, s->reg[I_(UINTP)]);
exynos4210_uart_update_irq(s);
break;
case UTRSTAT:
if (val & UTRSTAT_Rx_TIMEOUT) {
s->reg[I_(UTRSTAT)] &= ~UTRSTAT_Rx_TIMEOUT;
}
break;
case UERSTAT:
case UFSTAT:
case UMSTAT:
case URXH:
trace_exynos_uart_ro_write(
s->channel, exynos4210_uart_regname(offset), offset);
break;
case UINTSP:
s->reg[I_(UINTSP)] &= ~val;
break;
case UINTM:
s->reg[I_(UINTM)] = val;
exynos4210_uart_update_irq(s);
break;
case UCON:
case UMCON:
default:
s->reg[I_(offset)] = val;
break;
}
}
static uint64_t exynos4210_uart_read(void *opaque, hwaddr offset,
unsigned size)
{
Exynos4210UartState *s = (Exynos4210UartState *)opaque;
uint32_t res;
switch (offset) {
case UERSTAT: /* Read Only */
res = s->reg[I_(UERSTAT)];
s->reg[I_(UERSTAT)] = 0;
trace_exynos_uart_read(s->channel, offset,
exynos4210_uart_regname(offset), res);
return res;
case UFSTAT: /* Read Only */
s->reg[I_(UFSTAT)] = fifo_elements_number(&s->rx) & 0xff;
if (fifo_empty_elements_number(&s->rx) == 0) {
s->reg[I_(UFSTAT)] |= UFSTAT_Rx_FIFO_FULL;
s->reg[I_(UFSTAT)] &= ~0xff;
}
trace_exynos_uart_read(s->channel, offset,
exynos4210_uart_regname(offset),
s->reg[I_(UFSTAT)]);
return s->reg[I_(UFSTAT)];
case URXH:
if (s->reg[I_(UFCON)] & UFCON_FIFO_ENABLE) {
if (fifo_elements_number(&s->rx)) {
res = fifo_retrieve(&s->rx);
trace_exynos_uart_rx(s->channel, res);
if (!fifo_elements_number(&s->rx)) {
s->reg[I_(UTRSTAT)] &= ~UTRSTAT_Rx_BUFFER_DATA_READY;
} else {
s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY;
}
} else {
trace_exynos_uart_rx_error(s->channel);
s->reg[I_(UINTSP)] |= UINTSP_ERROR;
exynos4210_uart_update_irq(s);
res = 0;
}
} else {
s->reg[I_(UTRSTAT)] &= ~UTRSTAT_Rx_BUFFER_DATA_READY;
res = s->reg[I_(URXH)];
}
qemu_chr_fe_accept_input(&s->chr);
exynos4210_uart_update_dmabusy(s);
trace_exynos_uart_read(s->channel, offset,
exynos4210_uart_regname(offset), res);
return res;
case UTXH:
trace_exynos_uart_wo_read(s->channel, exynos4210_uart_regname(offset),
offset);
break;
default:
trace_exynos_uart_read(s->channel, offset,
exynos4210_uart_regname(offset),
s->reg[I_(offset)]);
return s->reg[I_(offset)];
}
trace_exynos_uart_read(s->channel, offset, exynos4210_uart_regname(offset),
0);
return 0;
}
static const MemoryRegionOps exynos4210_uart_ops = {
.read = exynos4210_uart_read,
.write = exynos4210_uart_write,
.endianness = DEVICE_NATIVE_ENDIAN,
.valid = {
.max_access_size = 4,
.unaligned = false
},
};
static int exynos4210_uart_can_receive(void *opaque)
{
Exynos4210UartState *s = (Exynos4210UartState *)opaque;
if (s->reg[I_(UFCON)] & UFCON_FIFO_ENABLE) {
return fifo_empty_elements_number(&s->rx);
} else {
return !(s->reg[I_(UTRSTAT)] & UTRSTAT_Rx_BUFFER_DATA_READY);
}
}
static void exynos4210_uart_receive(void *opaque, const uint8_t *buf, int size)
{
Exynos4210UartState *s = (Exynos4210UartState *)opaque;
int i;
if (s->reg[I_(UFCON)] & UFCON_FIFO_ENABLE) {
if (fifo_empty_elements_number(&s->rx) < size) {
size = fifo_empty_elements_number(&s->rx);
s->reg[I_(UINTSP)] |= UINTSP_ERROR;
}
for (i = 0; i < size; i++) {
fifo_store(&s->rx, buf[i]);
}
exynos4210_uart_rx_timeout_set(s);
} else {
s->reg[I_(URXH)] = buf[0];
}
s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY;
exynos4210_uart_update_irq(s);
}
static void exynos4210_uart_event(void *opaque, QEMUChrEvent event)
{
Exynos4210UartState *s = (Exynos4210UartState *)opaque;
if (event == CHR_EVENT_BREAK) {
/* When the RxDn is held in logic 0, then a null byte is pushed into the
* fifo */
fifo_store(&s->rx, '\0');
s->reg[I_(UERSTAT)] |= UERSTAT_BREAK;
exynos4210_uart_update_irq(s);
}
}
static void exynos4210_uart_reset(DeviceState *dev)
{
Exynos4210UartState *s = EXYNOS4210_UART(dev);
int i;
for (i = 0; i < ARRAY_SIZE(exynos4210_uart_regs); i++) {
s->reg[I_(exynos4210_uart_regs[i].offset)] =
exynos4210_uart_regs[i].reset_value;
}
fifo_reset(&s->rx);
fifo_reset(&s->tx);
trace_exynos_uart_rxsize(s->channel, s->rx.size);
}
static int exynos4210_uart_post_load(void *opaque, int version_id)
{
Exynos4210UartState *s = (Exynos4210UartState *)opaque;
exynos4210_uart_update_parameters(s);
exynos4210_uart_rx_timeout_set(s);
return 0;
}
static const VMStateDescription vmstate_exynos4210_uart_fifo = {
.name = "exynos4210.uart.fifo",
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_UINT32(sp, Exynos4210UartFIFO),
VMSTATE_UINT32(rp, Exynos4210UartFIFO),
VMSTATE_VBUFFER_UINT32(data, Exynos4210UartFIFO, 1, NULL, size),
VMSTATE_END_OF_LIST()
}
};
static const VMStateDescription vmstate_exynos4210_uart = {
.name = "exynos4210.uart",
.version_id = 1,
.minimum_version_id = 1,
.post_load = exynos4210_uart_post_load,
.fields = (const VMStateField[]) {
VMSTATE_STRUCT(rx, Exynos4210UartState, 1,
vmstate_exynos4210_uart_fifo, Exynos4210UartFIFO),
VMSTATE_UINT32_ARRAY(reg, Exynos4210UartState,
EXYNOS4210_UART_REGS_MEM_SIZE / sizeof(uint32_t)),
VMSTATE_END_OF_LIST()
}
};
DeviceState *exynos4210_uart_create(hwaddr addr,
int fifo_size,
int channel,
Chardev *chr,
qemu_irq irq)
{
DeviceState *dev;
SysBusDevice *bus;
dev = qdev_new(TYPE_EXYNOS4210_UART);
qdev_prop_set_chr(dev, "chardev", chr);
qdev_prop_set_uint32(dev, "channel", channel);
qdev_prop_set_uint32(dev, "rx-size", fifo_size);
qdev_prop_set_uint32(dev, "tx-size", fifo_size);
bus = SYS_BUS_DEVICE(dev);
sysbus_realize_and_unref(bus, &error_fatal);
if (addr != (hwaddr)-1) {
sysbus_mmio_map(bus, 0, addr);
}
sysbus_connect_irq(bus, 0, irq);
return dev;
}
static void exynos4210_uart_init(Object *obj)
{
SysBusDevice *dev = SYS_BUS_DEVICE(obj);
Exynos4210UartState *s = EXYNOS4210_UART(dev);
s->wordtime = NANOSECONDS_PER_SECOND * 10 / 9600;
/* memory mapping */
memory_region_init_io(&s->iomem, obj, &exynos4210_uart_ops, s,
"exynos4210.uart", EXYNOS4210_UART_REGS_MEM_SIZE);
sysbus_init_mmio(dev, &s->iomem);
sysbus_init_irq(dev, &s->irq);
sysbus_init_irq(dev, &s->dmairq);
}
static void exynos4210_uart_realize(DeviceState *dev, Error **errp)
{
Exynos4210UartState *s = EXYNOS4210_UART(dev);
s->fifo_timeout_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
exynos4210_uart_timeout_int, s);
qemu_chr_fe_set_handlers(&s->chr, exynos4210_uart_can_receive,
exynos4210_uart_receive, exynos4210_uart_event,
NULL, s, NULL, true);
}
static const Property exynos4210_uart_properties[] = {
DEFINE_PROP_CHR("chardev", Exynos4210UartState, chr),
DEFINE_PROP_UINT32("channel", Exynos4210UartState, channel, 0),
DEFINE_PROP_UINT32("rx-size", Exynos4210UartState, rx.size, 16),
DEFINE_PROP_UINT32("tx-size", Exynos4210UartState, tx.size, 16),
};
static void exynos4210_uart_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = exynos4210_uart_realize;
device_class_set_legacy_reset(dc, exynos4210_uart_reset);
device_class_set_props(dc, exynos4210_uart_properties);
dc->vmsd = &vmstate_exynos4210_uart;
}
static const TypeInfo exynos4210_uart_info = {
.name = TYPE_EXYNOS4210_UART,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(Exynos4210UartState),
.instance_init = exynos4210_uart_init,
.class_init = exynos4210_uart_class_init,
};
static void exynos4210_uart_register(void)
{
type_register_static(&exynos4210_uart_info);
}
type_init(exynos4210_uart_register)
+284
View File
@@ -0,0 +1,284 @@
/*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* Goldfish TTY
*
* (c) 2020 Laurent Vivier <[email protected]>
*
*/
#include "qemu/osdep.h"
#include "hw/core/irq.h"
#include "hw/core/qdev-properties-system.h"
#include "hw/core/sysbus.h"
#include "migration/vmstate.h"
#include "chardev/char-fe.h"
#include "qemu/log.h"
#include "trace.h"
#include "system/address-spaces.h"
#include "system/dma.h"
#include "hw/char/goldfish_tty.h"
#define GOLDFISH_TTY_VERSION 1
/* registers */
enum {
REG_PUT_CHAR = 0x00,
REG_BYTES_READY = 0x04,
REG_CMD = 0x08,
REG_DATA_PTR = 0x10,
REG_DATA_LEN = 0x14,
REG_DATA_PTR_HIGH = 0x18,
REG_VERSION = 0x20,
};
/* commands */
enum {
CMD_INT_DISABLE = 0x00,
CMD_INT_ENABLE = 0x01,
CMD_WRITE_BUFFER = 0x02,
CMD_READ_BUFFER = 0x03,
};
static uint64_t goldfish_tty_read(void *opaque, hwaddr addr,
unsigned size)
{
GoldfishTTYState *s = opaque;
uint64_t value = 0;
switch (addr) {
case REG_BYTES_READY:
value = fifo8_num_used(&s->rx_fifo);
break;
case REG_VERSION:
value = GOLDFISH_TTY_VERSION;
break;
default:
qemu_log_mask(LOG_UNIMP,
"%s: unimplemented register read 0x%02"HWADDR_PRIx"\n",
__func__, addr);
break;
}
trace_goldfish_tty_read(s, addr, size, value);
return value;
}
static void goldfish_tty_cmd(GoldfishTTYState *s, uint32_t cmd)
{
uint32_t to_copy;
uint8_t data_out[GOLFISH_TTY_BUFFER_SIZE];
int len;
uint64_t ptr;
switch (cmd) {
case CMD_INT_DISABLE:
if (s->int_enabled) {
if (!fifo8_is_empty(&s->rx_fifo)) {
qemu_set_irq(s->irq, 0);
}
s->int_enabled = false;
}
break;
case CMD_INT_ENABLE:
if (!s->int_enabled) {
if (!fifo8_is_empty(&s->rx_fifo)) {
qemu_set_irq(s->irq, 1);
}
s->int_enabled = true;
}
break;
case CMD_WRITE_BUFFER:
len = s->data_len;
ptr = s->data_ptr;
while (len) {
to_copy = MIN(GOLFISH_TTY_BUFFER_SIZE, len);
dma_memory_read_relaxed(&address_space_memory, ptr,
data_out, to_copy);
qemu_chr_fe_write_all(&s->chr, data_out, to_copy);
len -= to_copy;
ptr += to_copy;
}
break;
case CMD_READ_BUFFER:
len = s->data_len;
ptr = s->data_ptr;
while (len && !fifo8_is_empty(&s->rx_fifo)) {
const uint8_t *buf = fifo8_pop_bufptr(&s->rx_fifo, len, &to_copy);
dma_memory_write_relaxed(&address_space_memory, ptr, buf, to_copy);
len -= to_copy;
ptr += to_copy;
}
if (s->int_enabled && fifo8_is_empty(&s->rx_fifo)) {
qemu_set_irq(s->irq, 0);
}
break;
}
}
static void goldfish_tty_write(void *opaque, hwaddr addr,
uint64_t value, unsigned size)
{
GoldfishTTYState *s = opaque;
unsigned char c;
trace_goldfish_tty_write(s, addr, size, value);
switch (addr) {
case REG_PUT_CHAR:
c = value;
qemu_chr_fe_write_all(&s->chr, &c, sizeof(c));
break;
case REG_CMD:
goldfish_tty_cmd(s, value);
break;
case REG_DATA_PTR:
s->data_ptr = value;
break;
case REG_DATA_PTR_HIGH:
s->data_ptr = deposit64(s->data_ptr, 32, 32, value);
break;
case REG_DATA_LEN:
s->data_len = value;
break;
default:
qemu_log_mask(LOG_UNIMP,
"%s: unimplemented register write 0x%02"HWADDR_PRIx"\n",
__func__, addr);
break;
}
}
static const MemoryRegionOps goldfish_tty_ops = {
.read = goldfish_tty_read,
.write = goldfish_tty_write,
.endianness = DEVICE_NATIVE_ENDIAN,
.valid.max_access_size = 4,
.impl.max_access_size = 4,
.impl.min_access_size = 4,
};
static int goldfish_tty_can_receive(void *opaque)
{
GoldfishTTYState *s = opaque;
int available = fifo8_num_free(&s->rx_fifo);
trace_goldfish_tty_can_receive(s, available);
return available;
}
static void goldfish_tty_receive(void *opaque, const uint8_t *buffer, int size)
{
GoldfishTTYState *s = opaque;
trace_goldfish_tty_receive(s, size);
g_assert(size <= fifo8_num_free(&s->rx_fifo));
fifo8_push_all(&s->rx_fifo, buffer, size);
if (s->int_enabled && !fifo8_is_empty(&s->rx_fifo)) {
qemu_set_irq(s->irq, 1);
}
}
static void goldfish_tty_reset(DeviceState *dev)
{
GoldfishTTYState *s = GOLDFISH_TTY(dev);
trace_goldfish_tty_reset(s);
fifo8_reset(&s->rx_fifo);
s->int_enabled = false;
s->data_ptr = 0;
s->data_len = 0;
}
static void goldfish_tty_realize(DeviceState *dev, Error **errp)
{
GoldfishTTYState *s = GOLDFISH_TTY(dev);
trace_goldfish_tty_realize(s);
fifo8_create(&s->rx_fifo, GOLFISH_TTY_BUFFER_SIZE);
memory_region_init_io(&s->iomem, OBJECT(s), &goldfish_tty_ops, s,
"goldfish_tty", 0x24);
if (qemu_chr_fe_backend_connected(&s->chr)) {
qemu_chr_fe_set_handlers(&s->chr, goldfish_tty_can_receive,
goldfish_tty_receive, NULL, NULL,
s, NULL, true);
}
}
static void goldfish_tty_unrealize(DeviceState *dev)
{
GoldfishTTYState *s = GOLDFISH_TTY(dev);
trace_goldfish_tty_unrealize(s);
fifo8_destroy(&s->rx_fifo);
}
static const VMStateDescription vmstate_goldfish_tty = {
.name = "goldfish_tty",
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_UINT32(data_len, GoldfishTTYState),
VMSTATE_UINT64(data_ptr, GoldfishTTYState),
VMSTATE_BOOL(int_enabled, GoldfishTTYState),
VMSTATE_FIFO8(rx_fifo, GoldfishTTYState),
VMSTATE_END_OF_LIST()
}
};
static const Property goldfish_tty_properties[] = {
DEFINE_PROP_CHR("chardev", GoldfishTTYState, chr),
};
static void goldfish_tty_instance_init(Object *obj)
{
SysBusDevice *dev = SYS_BUS_DEVICE(obj);
GoldfishTTYState *s = GOLDFISH_TTY(obj);
trace_goldfish_tty_instance_init(s);
sysbus_init_mmio(dev, &s->iomem);
sysbus_init_irq(dev, &s->irq);
}
static void goldfish_tty_class_init(ObjectClass *oc, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(oc);
device_class_set_props(dc, goldfish_tty_properties);
device_class_set_legacy_reset(dc, goldfish_tty_reset);
dc->realize = goldfish_tty_realize;
dc->unrealize = goldfish_tty_unrealize;
dc->vmsd = &vmstate_goldfish_tty;
set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
}
static const TypeInfo goldfish_tty_info = {
.name = TYPE_GOLDFISH_TTY,
.parent = TYPE_SYS_BUS_DEVICE,
.class_init = goldfish_tty_class_init,
.instance_init = goldfish_tty_instance_init,
.instance_size = sizeof(GoldfishTTYState),
};
static void goldfish_tty_register_types(void)
{
type_register_static(&goldfish_tty_info);
}
type_init(goldfish_tty_register_types)
+305
View File
@@ -0,0 +1,305 @@
/*
* QEMU GRLIB APB UART Emulator
*
* SPDX-License-Identifier: MIT
*
* Copyright (c) 2010-2024 AdaCore
*
* 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
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* 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.
*/
#include "qemu/osdep.h"
#include "hw/core/irq.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-properties-system.h"
#include "hw/char/grlib_uart.h"
#include "hw/core/sysbus.h"
#include "qemu/module.h"
#include "chardev/char-fe.h"
#include "trace.h"
#include "qom/object.h"
#define UART_REG_SIZE 20 /* Size of memory mapped registers */
/* UART status register fields */
#define UART_DATA_READY (1 << 0)
#define UART_TRANSMIT_SHIFT_EMPTY (1 << 1)
#define UART_TRANSMIT_FIFO_EMPTY (1 << 2)
#define UART_BREAK_RECEIVED (1 << 3)
#define UART_OVERRUN (1 << 4)
#define UART_PARITY_ERROR (1 << 5)
#define UART_FRAMING_ERROR (1 << 6)
#define UART_TRANSMIT_FIFO_HALF (1 << 7)
#define UART_RECEIVE_FIFO_HALF (1 << 8)
#define UART_TRANSMIT_FIFO_FULL (1 << 9)
#define UART_RECEIVE_FIFO_FULL (1 << 10)
/* UART control register fields */
#define UART_RECEIVE_ENABLE (1 << 0)
#define UART_TRANSMIT_ENABLE (1 << 1)
#define UART_RECEIVE_INTERRUPT (1 << 2)
#define UART_TRANSMIT_INTERRUPT (1 << 3)
#define UART_PARITY_SELECT (1 << 4)
#define UART_PARITY_ENABLE (1 << 5)
#define UART_FLOW_CONTROL (1 << 6)
#define UART_LOOPBACK (1 << 7)
#define UART_EXTERNAL_CLOCK (1 << 8)
#define UART_RECEIVE_FIFO_INTERRUPT (1 << 9)
#define UART_TRANSMIT_FIFO_INTERRUPT (1 << 10)
#define UART_FIFO_DEBUG_MODE (1 << 11)
#define UART_OUTPUT_ENABLE (1 << 12)
#define UART_FIFO_AVAILABLE (1 << 31)
/* Memory mapped register offsets */
#define DATA_OFFSET 0x00
#define STATUS_OFFSET 0x04
#define CONTROL_OFFSET 0x08
#define SCALER_OFFSET 0x0C /* not supported */
#define FIFO_DEBUG_OFFSET 0x10 /* not supported */
#define FIFO_LENGTH 1024
OBJECT_DECLARE_SIMPLE_TYPE(UART, GRLIB_APB_UART)
struct UART {
SysBusDevice parent_obj;
MemoryRegion iomem;
qemu_irq irq;
CharFrontend chr;
/* registers */
uint32_t status;
uint32_t control;
/* FIFO */
char buffer[FIFO_LENGTH];
int len;
int current;
};
static int uart_data_to_read(UART *uart)
{
return uart->current < uart->len;
}
static char uart_pop(UART *uart)
{
char ret;
if (uart->len == 0) {
uart->status &= ~UART_DATA_READY;
return 0;
}
ret = uart->buffer[uart->current++];
if (uart->current >= uart->len) {
/* Flush */
uart->len = 0;
uart->current = 0;
}
if (!uart_data_to_read(uart)) {
uart->status &= ~UART_DATA_READY;
}
return ret;
}
static void uart_add_to_fifo(UART *uart,
const uint8_t *buffer,
int length)
{
if (uart->len + length > FIFO_LENGTH) {
abort();
}
memcpy(uart->buffer + uart->len, buffer, length);
uart->len += length;
}
static int grlib_apbuart_can_receive(void *opaque)
{
UART *uart = opaque;
return FIFO_LENGTH - uart->len;
}
static void grlib_apbuart_receive(void *opaque, const uint8_t *buf, int size)
{
UART *uart = opaque;
if (uart->control & UART_RECEIVE_ENABLE) {
uart_add_to_fifo(uart, buf, size);
uart->status |= UART_DATA_READY;
if (uart->control & UART_RECEIVE_INTERRUPT) {
qemu_irq_pulse(uart->irq);
}
}
}
static void grlib_apbuart_event(void *opaque, QEMUChrEvent event)
{
trace_grlib_apbuart_event(event);
}
static uint64_t grlib_apbuart_read(void *opaque, hwaddr addr,
unsigned size)
{
UART *uart = opaque;
addr &= 0xff;
/* Unit registers */
switch (addr) {
case DATA_OFFSET:
case DATA_OFFSET + 3: /* when only one byte read */
return uart_pop(uart);
case STATUS_OFFSET:
/* Read Only */
return uart->status;
case CONTROL_OFFSET:
return uart->control;
case SCALER_OFFSET:
/* Not supported */
return 0;
default:
trace_grlib_apbuart_readl_unknown(addr);
return 0;
}
}
static void grlib_apbuart_write(void *opaque, hwaddr addr,
uint64_t value, unsigned size)
{
UART *uart = opaque;
unsigned char c = 0;
addr &= 0xff;
/* Unit registers */
switch (addr) {
case DATA_OFFSET:
case DATA_OFFSET + 3: /* When only one byte write */
/* Transmit when character device available and transmitter enabled */
if (qemu_chr_fe_backend_connected(&uart->chr) &&
(uart->control & UART_TRANSMIT_ENABLE)) {
c = value & 0xFF;
/* XXX this blocks entire thread. Rewrite to use
* qemu_chr_fe_write and background I/O callbacks */
qemu_chr_fe_write_all(&uart->chr, &c, 1);
/* Generate interrupt */
if (uart->control & UART_TRANSMIT_INTERRUPT) {
qemu_irq_pulse(uart->irq);
}
}
return;
case STATUS_OFFSET:
/* Read Only */
return;
case CONTROL_OFFSET:
uart->control = value;
return;
case SCALER_OFFSET:
/* Not supported */
return;
default:
break;
}
trace_grlib_apbuart_writel_unknown(addr, value);
}
static const MemoryRegionOps grlib_apbuart_ops = {
.write = grlib_apbuart_write,
.read = grlib_apbuart_read,
.endianness = DEVICE_BIG_ENDIAN,
};
static void grlib_apbuart_realize(DeviceState *dev, Error **errp)
{
UART *uart = GRLIB_APB_UART(dev);
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
qemu_chr_fe_set_handlers(&uart->chr,
grlib_apbuart_can_receive,
grlib_apbuart_receive,
grlib_apbuart_event,
NULL, uart, NULL, true);
sysbus_init_irq(sbd, &uart->irq);
memory_region_init_io(&uart->iomem, OBJECT(uart), &grlib_apbuart_ops, uart,
"uart", UART_REG_SIZE);
sysbus_init_mmio(sbd, &uart->iomem);
}
static void grlib_apbuart_reset(DeviceState *d)
{
UART *uart = GRLIB_APB_UART(d);
/* Transmitter FIFO and shift registers are always empty in QEMU */
uart->status = UART_TRANSMIT_FIFO_EMPTY | UART_TRANSMIT_SHIFT_EMPTY;
/* Everything is off */
uart->control = 0;
/* Flush receive FIFO */
uart->len = 0;
uart->current = 0;
}
static const Property grlib_apbuart_properties[] = {
DEFINE_PROP_CHR("chrdev", UART, chr),
};
static void grlib_apbuart_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = grlib_apbuart_realize;
device_class_set_legacy_reset(dc, grlib_apbuart_reset);
device_class_set_props(dc, grlib_apbuart_properties);
}
static const TypeInfo grlib_apbuart_info = {
.name = TYPE_GRLIB_APB_UART,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(UART),
.class_init = grlib_apbuart_class_init,
};
static void grlib_apbuart_register_types(void)
{
type_register_static(&grlib_apbuart_info);
}
type_init(grlib_apbuart_register_types)
+569
View File
@@ -0,0 +1,569 @@
/*
* QEMU lowRISC Ibex UART device
*
* Copyright (c) 2020 Western Digital
*
* For details check the documentation here:
* https://docs.opentitan.org/hw/ip/uart/doc/
*
* 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
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* 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.
*/
#include "qemu/osdep.h"
#include "hw/char/ibex_uart.h"
#include "hw/core/irq.h"
#include "hw/core/qdev-clock.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-properties-system.h"
#include "hw/core/registerfields.h"
#include "migration/vmstate.h"
#include "qemu/log.h"
#include "qemu/module.h"
REG32(INTR_STATE, 0x00)
FIELD(INTR_STATE, TX_WATERMARK, 0, 1)
FIELD(INTR_STATE, RX_WATERMARK, 1, 1)
FIELD(INTR_STATE, TX_EMPTY, 2, 1)
FIELD(INTR_STATE, RX_OVERFLOW, 3, 1)
REG32(INTR_ENABLE, 0x04)
REG32(INTR_TEST, 0x08)
REG32(ALERT_TEST, 0x0C)
REG32(CTRL, 0x10)
FIELD(CTRL, TX_ENABLE, 0, 1)
FIELD(CTRL, RX_ENABLE, 1, 1)
FIELD(CTRL, NF, 2, 1)
FIELD(CTRL, SLPBK, 4, 1)
FIELD(CTRL, LLPBK, 5, 1)
FIELD(CTRL, PARITY_EN, 6, 1)
FIELD(CTRL, PARITY_ODD, 7, 1)
FIELD(CTRL, RXBLVL, 8, 2)
FIELD(CTRL, NCO, 16, 16)
REG32(STATUS, 0x14)
FIELD(STATUS, TXFULL, 0, 1)
FIELD(STATUS, RXFULL, 1, 1)
FIELD(STATUS, TXEMPTY, 2, 1)
FIELD(STATUS, RXIDLE, 4, 1)
FIELD(STATUS, RXEMPTY, 5, 1)
REG32(RDATA, 0x18)
REG32(WDATA, 0x1C)
REG32(FIFO_CTRL, 0x20)
FIELD(FIFO_CTRL, RXRST, 0, 1)
FIELD(FIFO_CTRL, TXRST, 1, 1)
FIELD(FIFO_CTRL, RXILVL, 2, 3)
FIELD(FIFO_CTRL, TXILVL, 5, 2)
REG32(FIFO_STATUS, 0x24)
FIELD(FIFO_STATUS, TXLVL, 0, 5)
FIELD(FIFO_STATUS, RXLVL, 16, 5)
REG32(OVRD, 0x28)
REG32(VAL, 0x2C)
REG32(TIMEOUT_CTRL, 0x30)
static void ibex_uart_update_irqs(IbexUartState *s)
{
if (s->uart_intr_state & s->uart_intr_enable & R_INTR_STATE_TX_WATERMARK_MASK) {
qemu_set_irq(s->tx_watermark, 1);
} else {
qemu_set_irq(s->tx_watermark, 0);
}
if (s->uart_intr_state & s->uart_intr_enable & R_INTR_STATE_RX_WATERMARK_MASK) {
qemu_set_irq(s->rx_watermark, 1);
} else {
qemu_set_irq(s->rx_watermark, 0);
}
if (s->uart_intr_state & s->uart_intr_enable & R_INTR_STATE_TX_EMPTY_MASK) {
qemu_set_irq(s->tx_empty, 1);
} else {
qemu_set_irq(s->tx_empty, 0);
}
if (s->uart_intr_state & s->uart_intr_enable & R_INTR_STATE_RX_OVERFLOW_MASK) {
qemu_set_irq(s->rx_overflow, 1);
} else {
qemu_set_irq(s->rx_overflow, 0);
}
}
static int ibex_uart_can_receive(void *opaque)
{
IbexUartState *s = opaque;
if ((s->uart_ctrl & R_CTRL_RX_ENABLE_MASK)
&& !(s->uart_status & R_STATUS_RXFULL_MASK)) {
return 1;
}
return 0;
}
static void ibex_uart_receive(void *opaque, const uint8_t *buf, int size)
{
IbexUartState *s = opaque;
uint8_t rx_fifo_level = (s->uart_fifo_ctrl & R_FIFO_CTRL_RXILVL_MASK)
>> R_FIFO_CTRL_RXILVL_SHIFT;
s->uart_rdata = *buf;
s->uart_status &= ~R_STATUS_RXIDLE_MASK;
s->uart_status &= ~R_STATUS_RXEMPTY_MASK;
/* The RXFULL is set after receiving a single byte
* as the FIFO buffers are not yet implemented.
*/
s->uart_status |= R_STATUS_RXFULL_MASK;
s->rx_level += 1;
if (size > rx_fifo_level) {
s->uart_intr_state |= R_INTR_STATE_RX_WATERMARK_MASK;
}
ibex_uart_update_irqs(s);
}
static gboolean ibex_uart_xmit(void *do_not_use, GIOCondition cond,
void *opaque)
{
IbexUartState *s = opaque;
uint8_t tx_fifo_level = (s->uart_fifo_ctrl & R_FIFO_CTRL_TXILVL_MASK)
>> R_FIFO_CTRL_TXILVL_SHIFT;
int ret;
/* instant drain the fifo when there's no back-end */
if (!qemu_chr_fe_backend_connected(&s->chr)) {
s->tx_level = 0;
return G_SOURCE_REMOVE;
}
if (!s->tx_level) {
s->uart_status &= ~R_STATUS_TXFULL_MASK;
s->uart_status |= R_STATUS_TXEMPTY_MASK;
s->uart_intr_state |= R_INTR_STATE_TX_EMPTY_MASK;
s->uart_intr_state &= ~R_INTR_STATE_TX_WATERMARK_MASK;
ibex_uart_update_irqs(s);
return G_SOURCE_REMOVE;
}
ret = qemu_chr_fe_write(&s->chr, s->tx_fifo, s->tx_level);
if (ret >= 0) {
s->tx_level -= ret;
memmove(s->tx_fifo, s->tx_fifo + ret, s->tx_level);
}
if (s->tx_level) {
guint r = qemu_chr_fe_add_watch(&s->chr, G_IO_OUT | G_IO_HUP,
ibex_uart_xmit, s);
if (!r) {
s->tx_level = 0;
return G_SOURCE_REMOVE;
}
}
/* Clear the TX Full bit */
if (s->tx_level != IBEX_UART_TX_FIFO_SIZE) {
s->uart_status &= ~R_STATUS_TXFULL_MASK;
}
/* Disable the TX_WATERMARK IRQ */
if (s->tx_level < tx_fifo_level) {
s->uart_intr_state &= ~R_INTR_STATE_TX_WATERMARK_MASK;
}
/* Set TX empty */
if (s->tx_level == 0) {
s->uart_status |= R_STATUS_TXEMPTY_MASK;
s->uart_intr_state |= R_INTR_STATE_TX_EMPTY_MASK;
}
ibex_uart_update_irqs(s);
return G_SOURCE_REMOVE;
}
static void uart_write_tx_fifo(IbexUartState *s, const uint8_t *buf,
int size)
{
uint64_t current_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
uint8_t tx_fifo_level = (s->uart_fifo_ctrl & R_FIFO_CTRL_TXILVL_MASK)
>> R_FIFO_CTRL_TXILVL_SHIFT;
if (size > IBEX_UART_TX_FIFO_SIZE - s->tx_level) {
size = IBEX_UART_TX_FIFO_SIZE - s->tx_level;
qemu_log_mask(LOG_GUEST_ERROR, "ibex_uart: TX FIFO overflow");
}
memcpy(s->tx_fifo + s->tx_level, buf, size);
s->tx_level += size;
if (s->tx_level > 0) {
s->uart_status &= ~R_STATUS_TXEMPTY_MASK;
}
if (s->tx_level >= tx_fifo_level) {
s->uart_intr_state |= R_INTR_STATE_TX_WATERMARK_MASK;
ibex_uart_update_irqs(s);
}
if (s->tx_level == IBEX_UART_TX_FIFO_SIZE) {
s->uart_status |= R_STATUS_TXFULL_MASK;
}
timer_mod(s->fifo_trigger_handle, current_time +
(s->char_tx_time * 4));
}
static void ibex_uart_reset(DeviceState *dev)
{
IbexUartState *s = IBEX_UART(dev);
s->uart_intr_state = 0x00000000;
s->uart_intr_state = 0x00000000;
s->uart_intr_enable = 0x00000000;
s->uart_ctrl = 0x00000000;
s->uart_status = 0x0000003c;
s->uart_rdata = 0x00000000;
s->uart_fifo_ctrl = 0x00000000;
s->uart_fifo_status = 0x00000000;
s->uart_ovrd = 0x00000000;
s->uart_val = 0x00000000;
s->uart_timeout_ctrl = 0x00000000;
s->tx_level = 0;
s->rx_level = 0;
s->char_tx_time = (NANOSECONDS_PER_SECOND / 230400) * 10;
ibex_uart_update_irqs(s);
}
static uint64_t ibex_uart_get_baud(IbexUartState *s)
{
uint64_t baud;
baud = ((s->uart_ctrl & R_CTRL_NCO_MASK) >> 16);
baud *= clock_get_hz(s->f_clk);
baud >>= 20;
return baud;
}
static uint64_t ibex_uart_read(void *opaque, hwaddr addr,
unsigned int size)
{
IbexUartState *s = opaque;
uint64_t retvalue = 0;
switch (addr >> 2) {
case R_INTR_STATE:
retvalue = s->uart_intr_state;
break;
case R_INTR_ENABLE:
retvalue = s->uart_intr_enable;
break;
case R_INTR_TEST:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: wdata is write only\n", __func__);
break;
case R_CTRL:
retvalue = s->uart_ctrl;
break;
case R_STATUS:
retvalue = s->uart_status;
break;
case R_RDATA:
retvalue = s->uart_rdata;
if ((s->uart_ctrl & R_CTRL_RX_ENABLE_MASK) && (s->rx_level > 0)) {
qemu_chr_fe_accept_input(&s->chr);
s->rx_level -= 1;
s->uart_status &= ~R_STATUS_RXFULL_MASK;
if (s->rx_level == 0) {
s->uart_status |= R_STATUS_RXIDLE_MASK;
s->uart_status |= R_STATUS_RXEMPTY_MASK;
}
}
break;
case R_WDATA:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: wdata is write only\n", __func__);
break;
case R_FIFO_CTRL:
retvalue = s->uart_fifo_ctrl;
break;
case R_FIFO_STATUS:
retvalue = s->uart_fifo_status;
retvalue |= (s->rx_level & 0x1F) << R_FIFO_STATUS_RXLVL_SHIFT;
retvalue |= (s->tx_level & 0x1F) << R_FIFO_STATUS_TXLVL_SHIFT;
qemu_log_mask(LOG_UNIMP,
"%s: RX fifos are not supported\n", __func__);
break;
case R_OVRD:
retvalue = s->uart_ovrd;
qemu_log_mask(LOG_UNIMP,
"%s: ovrd is not supported\n", __func__);
break;
case R_VAL:
retvalue = s->uart_val;
qemu_log_mask(LOG_UNIMP,
"%s: val is not supported\n", __func__);
break;
case R_TIMEOUT_CTRL:
retvalue = s->uart_timeout_ctrl;
qemu_log_mask(LOG_UNIMP,
"%s: timeout_ctrl is not supported\n", __func__);
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr);
return 0;
}
return retvalue;
}
static void ibex_uart_write(void *opaque, hwaddr addr,
uint64_t val64, unsigned int size)
{
IbexUartState *s = opaque;
uint32_t value = val64;
switch (addr >> 2) {
case R_INTR_STATE:
/* Write 1 clear */
s->uart_intr_state &= ~value;
ibex_uart_update_irqs(s);
break;
case R_INTR_ENABLE:
s->uart_intr_enable = value;
ibex_uart_update_irqs(s);
break;
case R_INTR_TEST:
s->uart_intr_state |= value;
ibex_uart_update_irqs(s);
break;
case R_CTRL:
s->uart_ctrl = value;
if (value & R_CTRL_NF_MASK) {
qemu_log_mask(LOG_UNIMP,
"%s: UART_CTRL_NF is not supported\n", __func__);
}
if (value & R_CTRL_SLPBK_MASK) {
qemu_log_mask(LOG_UNIMP,
"%s: UART_CTRL_SLPBK is not supported\n", __func__);
}
if (value & R_CTRL_LLPBK_MASK) {
qemu_log_mask(LOG_UNIMP,
"%s: UART_CTRL_LLPBK is not supported\n", __func__);
}
if (value & R_CTRL_PARITY_EN_MASK) {
qemu_log_mask(LOG_UNIMP,
"%s: UART_CTRL_PARITY_EN is not supported\n",
__func__);
}
if (value & R_CTRL_PARITY_ODD_MASK) {
qemu_log_mask(LOG_UNIMP,
"%s: UART_CTRL_PARITY_ODD is not supported\n",
__func__);
}
if (value & R_CTRL_RXBLVL_MASK) {
qemu_log_mask(LOG_UNIMP,
"%s: UART_CTRL_RXBLVL is not supported\n", __func__);
}
if (value & R_CTRL_NCO_MASK) {
uint64_t baud = ibex_uart_get_baud(s);
s->char_tx_time = (NANOSECONDS_PER_SECOND / baud) * 10;
}
break;
case R_STATUS:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: status is read only\n", __func__);
break;
case R_RDATA:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: rdata is read only\n", __func__);
break;
case R_WDATA:
uart_write_tx_fifo(s, (uint8_t *) &value, 1);
break;
case R_FIFO_CTRL:
s->uart_fifo_ctrl = value;
if (value & R_FIFO_CTRL_RXRST_MASK) {
s->rx_level = 0;
qemu_log_mask(LOG_UNIMP,
"%s: RX fifos are not supported\n", __func__);
}
if (value & R_FIFO_CTRL_TXRST_MASK) {
s->tx_level = 0;
}
break;
case R_FIFO_STATUS:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: fifo_status is read only\n", __func__);
break;
case R_OVRD:
s->uart_ovrd = value;
qemu_log_mask(LOG_UNIMP,
"%s: ovrd is not supported\n", __func__);
break;
case R_VAL:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: val is read only\n", __func__);
break;
case R_TIMEOUT_CTRL:
s->uart_timeout_ctrl = value;
qemu_log_mask(LOG_UNIMP,
"%s: timeout_ctrl is not supported\n", __func__);
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr);
}
}
static void ibex_uart_clk_update(void *opaque, ClockEvent event)
{
IbexUartState *s = opaque;
/* recompute uart's speed on clock change */
uint64_t baud = ibex_uart_get_baud(s);
s->char_tx_time = (NANOSECONDS_PER_SECOND / baud) * 10;
}
static void fifo_trigger_update(void *opaque)
{
IbexUartState *s = opaque;
if (s->uart_ctrl & R_CTRL_TX_ENABLE_MASK) {
ibex_uart_xmit(NULL, G_IO_OUT, s);
}
}
static const MemoryRegionOps ibex_uart_ops = {
.read = ibex_uart_read,
.write = ibex_uart_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.impl.min_access_size = 4,
.impl.max_access_size = 4,
};
static int ibex_uart_post_load(void *opaque, int version_id)
{
IbexUartState *s = opaque;
ibex_uart_update_irqs(s);
return 0;
}
static const VMStateDescription vmstate_ibex_uart = {
.name = TYPE_IBEX_UART,
.version_id = 1,
.minimum_version_id = 1,
.post_load = ibex_uart_post_load,
.fields = (const VMStateField[]) {
VMSTATE_UINT8_ARRAY(tx_fifo, IbexUartState,
IBEX_UART_TX_FIFO_SIZE),
VMSTATE_UINT32(tx_level, IbexUartState),
VMSTATE_UINT64(char_tx_time, IbexUartState),
VMSTATE_TIMER_PTR(fifo_trigger_handle, IbexUartState),
VMSTATE_UINT32(uart_intr_state, IbexUartState),
VMSTATE_UINT32(uart_intr_enable, IbexUartState),
VMSTATE_UINT32(uart_ctrl, IbexUartState),
VMSTATE_UINT32(uart_status, IbexUartState),
VMSTATE_UINT32(uart_rdata, IbexUartState),
VMSTATE_UINT32(uart_fifo_ctrl, IbexUartState),
VMSTATE_UINT32(uart_fifo_status, IbexUartState),
VMSTATE_UINT32(uart_ovrd, IbexUartState),
VMSTATE_UINT32(uart_val, IbexUartState),
VMSTATE_UINT32(uart_timeout_ctrl, IbexUartState),
VMSTATE_END_OF_LIST()
}
};
static const Property ibex_uart_properties[] = {
DEFINE_PROP_CHR("chardev", IbexUartState, chr),
};
static void ibex_uart_init(Object *obj)
{
IbexUartState *s = IBEX_UART(obj);
s->f_clk = qdev_init_clock_in(DEVICE(obj), "f_clock",
ibex_uart_clk_update, s, ClockUpdate);
clock_set_hz(s->f_clk, IBEX_UART_CLOCK);
sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->tx_watermark);
sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->rx_watermark);
sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->tx_empty);
sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->rx_overflow);
memory_region_init_io(&s->mmio, obj, &ibex_uart_ops, s,
TYPE_IBEX_UART, 0x400);
sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
}
static void ibex_uart_realize(DeviceState *dev, Error **errp)
{
IbexUartState *s = IBEX_UART(dev);
s->fifo_trigger_handle = timer_new_ns(QEMU_CLOCK_VIRTUAL,
fifo_trigger_update, s);
qemu_chr_fe_set_handlers(&s->chr, ibex_uart_can_receive,
ibex_uart_receive, NULL, NULL,
s, NULL, true);
}
static void ibex_uart_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
device_class_set_legacy_reset(dc, ibex_uart_reset);
dc->realize = ibex_uart_realize;
dc->vmsd = &vmstate_ibex_uart;
device_class_set_props(dc, ibex_uart_properties);
set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
}
static const TypeInfo ibex_uart_info = {
.name = TYPE_IBEX_UART,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(IbexUartState),
.instance_init = ibex_uart_init,
.class_init = ibex_uart_class_init,
};
static void ibex_uart_register_types(void)
{
type_register_static(&ibex_uart_info);
}
type_init(ibex_uart_register_types)
+496
View File
@@ -0,0 +1,496 @@
/*
* IMX31 UARTS
*
* Copyright (c) 2008 OKL
* Originally Written by Hans Jiang
* Copyright (c) 2011 NICTA Pty Ltd.
* Updated by Jean-Christophe Dubois <[email protected]>
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*
* This is a `bare-bones' implementation of the IMX series serial ports.
* TODO:
* -- implement FIFOs. The real hardware has 32 word transmit
* and receive FIFOs; we currently use a 1-char buffer
* -- implement DMA
* -- implement BAUD-rate and modem lines, for when the backend
* is a real serial device.
*/
#include "qemu/osdep.h"
#include "hw/char/imx_serial.h"
#include "hw/core/irq.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-properties-system.h"
#include "migration/vmstate.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "qemu/fifo32.h"
#include "trace.h"
#ifndef DEBUG_IMX_UART
#define DEBUG_IMX_UART 0
#endif
#define DPRINTF(fmt, args...) \
do { \
if (DEBUG_IMX_UART) { \
fprintf(stderr, "[%s]%s: " fmt , TYPE_IMX_SERIAL, \
__func__, ##args); \
} \
} while (0)
static const VMStateDescription vmstate_imx_serial = {
.name = TYPE_IMX_SERIAL,
.version_id = 4,
.minimum_version_id = 4,
.fields = (const VMStateField[]) {
VMSTATE_FIFO32(rx_fifo, IMXSerialState),
VMSTATE_TIMER(ageing_timer, IMXSerialState),
VMSTATE_UINT32(usr1, IMXSerialState),
VMSTATE_UINT32(usr2, IMXSerialState),
VMSTATE_UINT32(ucr1, IMXSerialState),
VMSTATE_UINT32(ucr2, IMXSerialState),
VMSTATE_UINT32(uts1, IMXSerialState),
VMSTATE_UINT32(onems, IMXSerialState),
VMSTATE_UINT32(ufcr, IMXSerialState),
VMSTATE_UINT32(ubmr, IMXSerialState),
VMSTATE_UINT32(ubrc, IMXSerialState),
VMSTATE_UINT32(ucr3, IMXSerialState),
VMSTATE_UINT32(ucr4, IMXSerialState),
VMSTATE_END_OF_LIST()
},
};
static void imx_update(IMXSerialState *s)
{
uint32_t usr1;
uint32_t usr2;
uint32_t mask;
/*
* Lucky for us TRDY and RRDY has the same offset in both USR1 and
* UCR1, so we can get away with something as simple as the
* following:
*/
usr1 = s->usr1 & s->ucr1 & (USR1_TRDY | USR1_RRDY);
/*
* Interrupt if AGTIM is set (ageing timer interrupt in RxFIFO)
*/
usr1 |= (s->ucr2 & UCR2_ATEN) ? (s->usr1 & USR1_AGTIM) : 0;
/*
* Bits that we want in USR2 are not as conveniently laid out,
* unfortunately.
*/
mask = (s->ucr1 & UCR1_TXMPTYEN) ? USR2_TXFE : 0;
/*
* TCEN and TXDC are both bit 3
* ORE and OREN are both bit 1
* RDR and DREN are both bit 0
*/
mask |= s->ucr4 & (UCR4_WKEN | UCR4_TCEN | UCR4_DREN | UCR4_OREN);
usr2 = s->usr2 & mask;
qemu_set_irq(s->irq, usr1 || usr2);
}
static void imx_serial_rx_fifo_push(IMXSerialState *s, uint32_t value)
{
uint32_t pushed_value = value;
if (fifo32_is_full(&s->rx_fifo)) {
/* Set ORE if FIFO is already full */
s->usr2 |= USR2_ORE;
} else {
if (fifo32_num_used(&s->rx_fifo) == FIFO_SIZE - 1) {
/* Set OVRRUN on 32nd character in FIFO */
pushed_value |= URXD_ERR | URXD_OVRRUN;
}
fifo32_push(&s->rx_fifo, pushed_value);
}
}
static uint32_t imx_serial_rx_fifo_pop(IMXSerialState *s)
{
if (fifo32_is_empty(&s->rx_fifo)) {
return 0;
}
return fifo32_pop(&s->rx_fifo);
}
static void imx_serial_rx_fifo_ageing_timer_int(void *opaque)
{
IMXSerialState *s = (IMXSerialState *) opaque;
s->usr1 |= USR1_AGTIM;
imx_update(s);
}
static void imx_serial_rx_fifo_ageing_timer_restart(void *opaque)
{
/*
* Ageing timer starts ticking when
* RX FIFO is non empty and below trigger level.
* Timer is reset if new character is received or
* a FIFO read occurs.
* Timer triggers an interrupt when duration of
* 8 characters has passed (assuming 115200 baudrate).
*/
IMXSerialState *s = (IMXSerialState *) opaque;
if (!(s->usr1 & USR1_RRDY) && !(s->uts1 & UTS1_RXEMPTY)) {
timer_mod_ns(&s->ageing_timer,
qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + AGE_DURATION_NS);
} else {
timer_del(&s->ageing_timer);
}
}
static void imx_serial_reset(IMXSerialState *s)
{
s->usr1 = USR1_TRDY | USR1_RXDS;
/*
* Fake attachment of a terminal: assert RTS.
*/
s->usr1 |= USR1_RTSS;
s->usr2 = USR2_TXFE | USR2_TXDC | USR2_DCDIN;
s->uts1 = UTS1_RXEMPTY | UTS1_TXEMPTY;
s->ucr1 = 0;
s->ucr2 = UCR2_SRST;
s->ucr3 = 0x700;
s->ubmr = 0;
s->ubrc = 4;
s->ufcr = BIT(11) | BIT(0);
fifo32_reset(&s->rx_fifo);
timer_del(&s->ageing_timer);
}
static void imx_serial_reset_at_boot(DeviceState *dev)
{
IMXSerialState *s = IMX_SERIAL(dev);
imx_serial_reset(s);
/*
* enable the uart on boot, so messages from the linux decompressor
* are visible. On real hardware this is done by the boot rom
* before anything else is loaded.
*/
s->ucr1 = UCR1_UARTEN;
s->ucr2 = UCR2_TXEN;
}
static uint64_t imx_serial_read(void *opaque, hwaddr offset,
unsigned size)
{
IMXSerialState *s = (IMXSerialState *)opaque;
Chardev *chr = qemu_chr_fe_get_driver(&s->chr);
uint32_t c, rx_used;
uint8_t rxtl = s->ufcr & TL_MASK;
uint64_t value;
switch (offset >> 2) {
case 0x0: /* URXD */
c = imx_serial_rx_fifo_pop(s);
if (!(s->uts1 & UTS1_RXEMPTY)) {
/* Character is valid */
c |= URXD_CHARRDY;
rx_used = fifo32_num_used(&s->rx_fifo);
/* Clear RRDY if below threshold */
if (rx_used < rxtl) {
s->usr1 &= ~USR1_RRDY;
}
if (rx_used == 0) {
s->usr2 &= ~USR2_RDR;
s->uts1 |= UTS1_RXEMPTY;
}
imx_update(s);
imx_serial_rx_fifo_ageing_timer_restart(s);
qemu_chr_fe_accept_input(&s->chr);
}
value = c;
break;
case 0x20: /* UCR1 */
value = s->ucr1;
break;
case 0x21: /* UCR2 */
value = s->ucr2;
break;
case 0x25: /* USR1 */
value = s->usr1;
break;
case 0x26: /* USR2 */
value = s->usr2;
break;
case 0x2A: /* BRM Modulator */
value = s->ubmr;
break;
case 0x2B: /* Baud Rate Count */
value = s->ubrc;
break;
case 0x2d: /* Test register */
value = s->uts1;
break;
case 0x24: /* UFCR */
value = s->ufcr;
break;
case 0x2c:
value = s->onems;
break;
case 0x22: /* UCR3 */
value = s->ucr3;
break;
case 0x23: /* UCR4 */
value = s->ucr4;
break;
case 0x29: /* BRM Incremental */
value = 0x0; /* TODO */
break;
default:
qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%"
HWADDR_PRIx "\n", TYPE_IMX_SERIAL, __func__, offset);
value = 0;
break;
}
trace_imx_serial_read(chr ? chr->label : "NODEV", offset, value);
return value;
}
static void imx_serial_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size)
{
IMXSerialState *s = (IMXSerialState *)opaque;
g_autofree char *label = qemu_chr_fe_backend_name(&s->chr);
unsigned char ch;
trace_imx_serial_write(label ? label : "NODEV", offset, value);
switch (offset >> 2) {
case 0x10: /* UTXD */
ch = value;
if (s->ucr2 & UCR2_TXEN) {
/* XXX this blocks entire thread. Rewrite to use
* qemu_chr_fe_write and background I/O callbacks */
qemu_chr_fe_write_all(&s->chr, &ch, 1);
s->usr1 &= ~USR1_TRDY;
s->usr2 &= ~USR2_TXDC;
imx_update(s);
s->usr1 |= USR1_TRDY;
s->usr2 |= USR2_TXDC;
imx_update(s);
}
break;
case 0x20: /* UCR1 */
s->ucr1 = value & 0xffff;
DPRINTF("write(ucr1=%x)\n", (unsigned int)value);
imx_update(s);
break;
case 0x21: /* UCR2 */
/*
* Only a few bits in control register 2 are implemented as yet.
* If it's intended to use a real serial device as a back-end, this
* register will have to be implemented more fully.
*/
if (!(value & UCR2_SRST)) {
imx_serial_reset(s);
imx_update(s);
value |= UCR2_SRST;
}
if (value & UCR2_RXEN) {
if (!(s->ucr2 & UCR2_RXEN)) {
qemu_chr_fe_accept_input(&s->chr);
}
}
s->ucr2 = value & 0xffff;
break;
case 0x25: /* USR1 */
value &= USR1_AWAKE | USR1_AIRINT | USR1_DTRD | USR1_AGTIM |
USR1_FRAMERR | USR1_ESCF | USR1_RTSD | USR1_PARTYER;
s->usr1 &= ~value;
break;
case 0x26: /* USR2 */
/*
* Writing 1 to some bits clears them; all other
* values are ignored
*/
value &= USR2_ADET | USR2_DTRF | USR2_IDLE | USR2_ACST |
USR2_RIDELT | USR2_IRINT | USR2_WAKE |
USR2_DCDDELT | USR2_RTSF | USR2_BRCD | USR2_ORE;
s->usr2 &= ~value;
break;
/*
* Linux expects to see what it writes to these registers
* We don't currently alter the baud rate
*/
case 0x29: /* UBIR */
s->ubrc = value & 0xffff;
break;
case 0x2a: /* UBMR */
s->ubmr = value & 0xffff;
break;
case 0x2c: /* One ms reg */
s->onems = value & 0xffff;
break;
case 0x24: /* FIFO control register */
s->ufcr = value & 0xffff;
break;
case 0x22: /* UCR3 */
s->ucr3 = value & 0xffff;
break;
case 0x23: /* UCR4 */
s->ucr4 = value & 0xffff;
imx_update(s);
break;
case 0x2d: /* UTS1 */
qemu_log_mask(LOG_UNIMP, "[%s]%s: Unimplemented reg 0x%"
HWADDR_PRIx "\n", TYPE_IMX_SERIAL, __func__, offset);
/* TODO */
break;
default:
qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%"
HWADDR_PRIx "\n", TYPE_IMX_SERIAL, __func__, offset);
}
}
static int imx_can_receive(void *opaque)
{
IMXSerialState *s = (IMXSerialState *)opaque;
return s->ucr2 & UCR2_RXEN ? fifo32_num_free(&s->rx_fifo) : 0;
}
static void imx_put_data(void *opaque, uint32_t value)
{
IMXSerialState *s = (IMXSerialState *)opaque;
Chardev *chr = qemu_chr_fe_get_driver(&s->chr);
uint8_t rxtl = s->ufcr & TL_MASK;
trace_imx_serial_put_data(chr ? chr->label : "NODEV", value);
imx_serial_rx_fifo_push(s, value);
if (fifo32_num_used(&s->rx_fifo) >= rxtl) {
s->usr1 |= USR1_RRDY;
}
s->usr2 |= USR2_RDR;
s->uts1 &= ~UTS1_RXEMPTY;
if (value & URXD_BRK) {
s->usr2 |= USR2_BRCD;
}
imx_serial_rx_fifo_ageing_timer_restart(s);
imx_update(s);
}
static void imx_receive(void *opaque, const uint8_t *buf, int size)
{
IMXSerialState *s = (IMXSerialState *)opaque;
s->usr2 |= USR2_WAKE;
for (int i = 0; i < size; i++) {
imx_put_data(opaque, buf[i]);
}
}
static void imx_event(void *opaque, QEMUChrEvent event)
{
if (event == CHR_EVENT_BREAK) {
imx_put_data(opaque, URXD_BRK | URXD_FRMERR | URXD_ERR);
}
}
static const struct MemoryRegionOps imx_serial_ops = {
.read = imx_serial_read,
.write = imx_serial_write,
.endianness = DEVICE_NATIVE_ENDIAN,
};
static void imx_serial_realize(DeviceState *dev, Error **errp)
{
IMXSerialState *s = IMX_SERIAL(dev);
fifo32_create(&s->rx_fifo, FIFO_SIZE);
timer_init_ns(&s->ageing_timer, QEMU_CLOCK_VIRTUAL,
imx_serial_rx_fifo_ageing_timer_int, s);
DPRINTF("char dev for uart: %p\n", qemu_chr_fe_get_driver(&s->chr));
qemu_chr_fe_set_handlers(&s->chr, imx_can_receive, imx_receive,
imx_event, NULL, s, NULL, true);
}
static void imx_serial_init(Object *obj)
{
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
IMXSerialState *s = IMX_SERIAL(obj);
memory_region_init_io(&s->iomem, obj, &imx_serial_ops, s,
TYPE_IMX_SERIAL, 0x1000);
sysbus_init_mmio(sbd, &s->iomem);
sysbus_init_irq(sbd, &s->irq);
}
static const Property imx_serial_properties[] = {
DEFINE_PROP_CHR("chardev", IMXSerialState, chr),
};
static void imx_serial_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = imx_serial_realize;
dc->vmsd = &vmstate_imx_serial;
device_class_set_legacy_reset(dc, imx_serial_reset_at_boot);
set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
dc->desc = "i.MX series UART";
device_class_set_props(dc, imx_serial_properties);
}
static const TypeInfo imx_serial_info = {
.name = TYPE_IMX_SERIAL,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(IMXSerialState),
.instance_init = imx_serial_init,
.class_init = imx_serial_class_init,
};
static void imx_serial_register_types(void)
{
type_register_static(&imx_serial_info);
}
type_init(imx_serial_register_types)
+606
View File
@@ -0,0 +1,606 @@
/*
* QEMU GE IP-Octal 232 IndustryPack emulation
*
* Copyright (C) 2012 Igalia, S.L.
* Author: Alberto Garcia <[email protected]>
*
* This code is licensed under the GNU GPL v2 or (at your option) any
* later version.
*/
#include "qemu/osdep.h"
#include "hw/ipack/ipack.h"
#include "hw/core/irq.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-properties-system.h"
#include "migration/vmstate.h"
#include "qemu/bitops.h"
#include "qemu/module.h"
#include "chardev/char-fe.h"
#include "qom/object.h"
/* #define DEBUG_IPOCTAL */
#ifdef DEBUG_IPOCTAL
#define DPRINTF2(fmt, ...) \
do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
#else
#define DPRINTF2(fmt, ...) do { } while (0)
#endif
#define DPRINTF(fmt, ...) DPRINTF2("IP-Octal: " fmt, ## __VA_ARGS__)
#define RX_FIFO_SIZE 3
/* The IP-Octal has 8 channels (a-h)
divided into 4 blocks (A-D) */
#define N_CHANNELS 8
#define N_BLOCKS 4
#define REG_MRa 0x01
#define REG_MRb 0x11
#define REG_SRa 0x03
#define REG_SRb 0x13
#define REG_CSRa 0x03
#define REG_CSRb 0x13
#define REG_CRa 0x05
#define REG_CRb 0x15
#define REG_RHRa 0x07
#define REG_RHRb 0x17
#define REG_THRa 0x07
#define REG_THRb 0x17
#define REG_ACR 0x09
#define REG_ISR 0x0B
#define REG_IMR 0x0B
#define REG_OPCR 0x1B
#define CR_ENABLE_RX BIT(0)
#define CR_DISABLE_RX BIT(1)
#define CR_ENABLE_TX BIT(2)
#define CR_DISABLE_TX BIT(3)
#define CR_CMD(cr) ((cr) >> 4)
#define CR_NO_OP 0
#define CR_RESET_MR 1
#define CR_RESET_RX 2
#define CR_RESET_TX 3
#define CR_RESET_ERR 4
#define CR_RESET_BRKINT 5
#define CR_START_BRK 6
#define CR_STOP_BRK 7
#define CR_ASSERT_RTSN 8
#define CR_NEGATE_RTSN 9
#define CR_TIMEOUT_ON 10
#define CR_TIMEOUT_OFF 12
#define SR_RXRDY BIT(0)
#define SR_FFULL BIT(1)
#define SR_TXRDY BIT(2)
#define SR_TXEMT BIT(3)
#define SR_OVERRUN BIT(4)
#define SR_PARITY BIT(5)
#define SR_FRAMING BIT(6)
#define SR_BREAK BIT(7)
#define ISR_TXRDYA BIT(0)
#define ISR_RXRDYA BIT(1)
#define ISR_BREAKA BIT(2)
#define ISR_CNTRDY BIT(3)
#define ISR_TXRDYB BIT(4)
#define ISR_RXRDYB BIT(5)
#define ISR_BREAKB BIT(6)
#define ISR_MPICHG BIT(7)
#define ISR_TXRDY(CH) (((CH) & 1) ? BIT(4) : BIT(0))
#define ISR_RXRDY(CH) (((CH) & 1) ? BIT(5) : BIT(1))
#define ISR_BREAK(CH) (((CH) & 1) ? BIT(6) : BIT(2))
#define TYPE_IPOCTAL "ipoctal232"
OBJECT_DECLARE_SIMPLE_TYPE(IPOctalState, IPOCTAL)
typedef struct SCC2698Channel SCC2698Channel;
typedef struct SCC2698Block SCC2698Block;
struct SCC2698Channel {
IPOctalState *ipoctal;
CharFrontend dev;
bool rx_enabled;
uint8_t mr[2];
uint8_t mr_idx;
uint8_t sr;
uint8_t rhr[RX_FIFO_SIZE];
uint8_t rhr_idx;
uint8_t rx_pending;
};
struct SCC2698Block {
uint8_t imr;
uint8_t isr;
};
struct IPOctalState {
IPackDevice parent_obj;
SCC2698Channel ch[N_CHANNELS];
SCC2698Block blk[N_BLOCKS];
uint8_t irq_vector;
};
static const VMStateDescription vmstate_scc2698_channel = {
.name = "scc2698_channel",
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_BOOL(rx_enabled, SCC2698Channel),
VMSTATE_UINT8_ARRAY(mr, SCC2698Channel, 2),
VMSTATE_UINT8(mr_idx, SCC2698Channel),
VMSTATE_UINT8(sr, SCC2698Channel),
VMSTATE_UINT8_ARRAY(rhr, SCC2698Channel, RX_FIFO_SIZE),
VMSTATE_UINT8(rhr_idx, SCC2698Channel),
VMSTATE_UINT8(rx_pending, SCC2698Channel),
VMSTATE_END_OF_LIST()
}
};
static const VMStateDescription vmstate_scc2698_block = {
.name = "scc2698_block",
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_UINT8(imr, SCC2698Block),
VMSTATE_UINT8(isr, SCC2698Block),
VMSTATE_END_OF_LIST()
}
};
static const VMStateDescription vmstate_ipoctal = {
.name = "ipoctal232",
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_IPACK_DEVICE(parent_obj, IPOctalState),
VMSTATE_STRUCT_ARRAY(ch, IPOctalState, N_CHANNELS, 1,
vmstate_scc2698_channel, SCC2698Channel),
VMSTATE_STRUCT_ARRAY(blk, IPOctalState, N_BLOCKS, 1,
vmstate_scc2698_block, SCC2698Block),
VMSTATE_UINT8(irq_vector, IPOctalState),
VMSTATE_END_OF_LIST()
}
};
/* data[10] is 0x0C, not 0x0B as the doc says */
static const uint8_t id_prom_data[] = {
0x49, 0x50, 0x41, 0x43, 0xF0, 0x22,
0xA1, 0x00, 0x00, 0x00, 0x0C, 0xCC
};
static void update_irq(IPOctalState *dev, unsigned block)
{
IPackDevice *idev = IPACK_DEVICE(dev);
/* Blocks A and B interrupt on INT0#, C and D on INT1#.
Thus, to get the status we have to check two blocks. */
SCC2698Block *blk0 = &dev->blk[block];
SCC2698Block *blk1 = &dev->blk[block^1];
unsigned intno = block / 2;
if ((blk0->isr & blk0->imr) || (blk1->isr & blk1->imr)) {
qemu_irq_raise(&idev->irq[intno]);
} else {
qemu_irq_lower(&idev->irq[intno]);
}
}
static void write_cr(IPOctalState *dev, unsigned channel, uint8_t val)
{
SCC2698Channel *ch = &dev->ch[channel];
SCC2698Block *blk = &dev->blk[channel / 2];
DPRINTF("Write CR%c %u: ", channel + 'a', val);
/* The lower 4 bits are used to enable and disable Tx and Rx */
if (val & CR_ENABLE_RX) {
DPRINTF2("Rx on, ");
ch->rx_enabled = true;
}
if (val & CR_DISABLE_RX) {
DPRINTF2("Rx off, ");
ch->rx_enabled = false;
}
if (val & CR_ENABLE_TX) {
DPRINTF2("Tx on, ");
ch->sr |= SR_TXRDY | SR_TXEMT;
blk->isr |= ISR_TXRDY(channel);
}
if (val & CR_DISABLE_TX) {
DPRINTF2("Tx off, ");
ch->sr &= ~(SR_TXRDY | SR_TXEMT);
blk->isr &= ~ISR_TXRDY(channel);
}
DPRINTF2("cmd: ");
/* The rest of the bits implement different commands */
switch (CR_CMD(val)) {
case CR_NO_OP:
DPRINTF2("none");
break;
case CR_RESET_MR:
DPRINTF2("reset MR");
ch->mr_idx = 0;
break;
case CR_RESET_RX:
DPRINTF2("reset Rx");
ch->rx_enabled = false;
ch->rx_pending = 0;
ch->sr &= ~SR_RXRDY;
blk->isr &= ~ISR_RXRDY(channel);
break;
case CR_RESET_TX:
DPRINTF2("reset Tx");
ch->sr &= ~(SR_TXRDY | SR_TXEMT);
blk->isr &= ~ISR_TXRDY(channel);
break;
case CR_RESET_ERR:
DPRINTF2("reset err");
ch->sr &= ~(SR_OVERRUN | SR_PARITY | SR_FRAMING | SR_BREAK);
break;
case CR_RESET_BRKINT:
DPRINTF2("reset brk ch int");
blk->isr &= ~(ISR_BREAKA | ISR_BREAKB);
break;
default:
DPRINTF2("unsupported 0x%x", CR_CMD(val));
}
DPRINTF2("\n");
}
static uint16_t io_read(IPackDevice *ip, uint8_t addr)
{
IPOctalState *dev = IPOCTAL(ip);
uint16_t ret = 0;
/* addr[7:6]: block (A-D)
addr[7:5]: channel (a-h)
addr[5:0]: register */
unsigned block = addr >> 5;
unsigned channel = addr >> 4;
/* Big endian, accessed using 8-bit bytes at odd locations */
unsigned offset = (addr & 0x1F) ^ 1;
SCC2698Channel *ch = &dev->ch[channel];
SCC2698Block *blk = &dev->blk[block];
uint8_t old_isr = blk->isr;
switch (offset) {
case REG_MRa:
case REG_MRb:
ret = ch->mr[ch->mr_idx];
DPRINTF("Read MR%u%c: 0x%x\n", ch->mr_idx + 1, channel + 'a', ret);
ch->mr_idx = 1;
break;
case REG_SRa:
case REG_SRb:
ret = ch->sr;
DPRINTF("Read SR%c: 0x%x\n", channel + 'a', ret);
break;
case REG_RHRa:
case REG_RHRb:
ret = ch->rhr[ch->rhr_idx];
if (ch->rx_pending > 0) {
ch->rx_pending--;
if (ch->rx_pending == 0) {
ch->sr &= ~SR_RXRDY;
blk->isr &= ~ISR_RXRDY(channel);
qemu_chr_fe_accept_input(&ch->dev);
} else {
ch->rhr_idx = (ch->rhr_idx + 1) % RX_FIFO_SIZE;
}
if (ch->sr & SR_BREAK) {
ch->sr &= ~SR_BREAK;
blk->isr |= ISR_BREAK(channel);
}
}
DPRINTF("Read RHR%c (0x%x)\n", channel + 'a', ret);
break;
case REG_ISR:
ret = blk->isr;
DPRINTF("Read ISR%c: 0x%x\n", block + 'A', ret);
break;
default:
DPRINTF("Read unknown/unsupported register 0x%02x\n", offset);
}
if (old_isr != blk->isr) {
update_irq(dev, block);
}
return ret;
}
static void io_write(IPackDevice *ip, uint8_t addr, uint16_t val)
{
IPOctalState *dev = IPOCTAL(ip);
unsigned reg = val & 0xFF;
/* addr[7:6]: block (A-D)
addr[7:5]: channel (a-h)
addr[5:0]: register */
unsigned block = addr >> 5;
unsigned channel = addr >> 4;
/* Big endian, accessed using 8-bit bytes at odd locations */
unsigned offset = (addr & 0x1F) ^ 1;
SCC2698Channel *ch = &dev->ch[channel];
SCC2698Block *blk = &dev->blk[block];
uint8_t old_isr = blk->isr;
uint8_t old_imr = blk->imr;
switch (offset) {
case REG_MRa:
case REG_MRb:
ch->mr[ch->mr_idx] = reg;
DPRINTF("Write MR%u%c 0x%x\n", ch->mr_idx + 1, channel + 'a', reg);
ch->mr_idx = 1;
break;
/* Not implemented */
case REG_CSRa:
case REG_CSRb:
DPRINTF("Write CSR%c: 0x%x\n", channel + 'a', reg);
break;
case REG_CRa:
case REG_CRb:
write_cr(dev, channel, reg);
break;
case REG_THRa:
case REG_THRb:
if (ch->sr & SR_TXRDY) {
uint8_t thr = reg;
DPRINTF("Write THR%c (0x%x)\n", channel + 'a', reg);
/* XXX this blocks entire thread. Rewrite to use
* qemu_chr_fe_write and background I/O callbacks */
qemu_chr_fe_write_all(&ch->dev, &thr, 1);
} else {
DPRINTF("Write THR%c (0x%x), Tx disabled\n", channel + 'a', reg);
}
break;
/* Not implemented */
case REG_ACR:
DPRINTF("Write ACR%c 0x%x\n", block + 'A', val);
break;
case REG_IMR:
DPRINTF("Write IMR%c 0x%x\n", block + 'A', val);
blk->imr = reg;
break;
/* Not implemented */
case REG_OPCR:
DPRINTF("Write OPCR%c 0x%x\n", block + 'A', val);
break;
default:
DPRINTF("Write unknown/unsupported register 0x%02x %u\n", offset, val);
}
if (old_isr != blk->isr || old_imr != blk->imr) {
update_irq(dev, block);
}
}
static uint16_t id_read(IPackDevice *ip, uint8_t addr)
{
uint16_t ret = 0;
unsigned pos = addr / 2; /* The ID PROM data is stored every other byte */
if (pos < ARRAY_SIZE(id_prom_data)) {
ret = id_prom_data[pos];
} else {
DPRINTF("Attempt to read unavailable PROM data at 0x%x\n", addr);
}
return ret;
}
static void id_write(IPackDevice *ip, uint8_t addr, uint16_t val)
{
IPOctalState *dev = IPOCTAL(ip);
if (addr == 1) {
DPRINTF("Write IRQ vector: %u\n", (unsigned) val);
dev->irq_vector = val; /* Undocumented, but the hw works like that */
} else {
DPRINTF("Attempt to write 0x%x to 0x%x\n", val, addr);
}
}
static uint16_t int_read(IPackDevice *ip, uint8_t addr)
{
IPOctalState *dev = IPOCTAL(ip);
/* Read address 0 to ACK INT0# and address 2 to ACK INT1# */
if (addr != 0 && addr != 2) {
DPRINTF("Attempt to read from 0x%x\n", addr);
return 0;
} else {
/* Update interrupts if necessary */
update_irq(dev, addr);
return dev->irq_vector;
}
}
static void int_write(IPackDevice *ip, uint8_t addr, uint16_t val)
{
DPRINTF("Attempt to write 0x%x to 0x%x\n", val, addr);
}
static uint16_t mem_read16(IPackDevice *ip, uint32_t addr)
{
DPRINTF("Attempt to read from 0x%x\n", addr);
return 0;
}
static void mem_write16(IPackDevice *ip, uint32_t addr, uint16_t val)
{
DPRINTF("Attempt to write 0x%x to 0x%x\n", val, addr);
}
static uint8_t mem_read8(IPackDevice *ip, uint32_t addr)
{
DPRINTF("Attempt to read from 0x%x\n", addr);
return 0;
}
static void mem_write8(IPackDevice *ip, uint32_t addr, uint8_t val)
{
IPOctalState *dev = IPOCTAL(ip);
if (addr == 1) {
DPRINTF("Write IRQ vector: %u\n", (unsigned) val);
dev->irq_vector = val;
} else {
DPRINTF("Attempt to write 0x%x to 0x%x\n", val, addr);
}
}
static int hostdev_can_receive(void *opaque)
{
SCC2698Channel *ch = opaque;
int available_bytes = RX_FIFO_SIZE - ch->rx_pending;
return ch->rx_enabled ? available_bytes : 0;
}
static void hostdev_receive(void *opaque, const uint8_t *buf, int size)
{
SCC2698Channel *ch = opaque;
IPOctalState *dev = ch->ipoctal;
unsigned pos = ch->rhr_idx + ch->rx_pending;
int i;
assert(size + ch->rx_pending <= RX_FIFO_SIZE);
/* Copy data to the RxFIFO */
for (i = 0; i < size; i++) {
pos %= RX_FIFO_SIZE;
ch->rhr[pos++] = buf[i];
}
ch->rx_pending += size;
/* If the RxFIFO was empty raise an interrupt */
if (!(ch->sr & SR_RXRDY)) {
unsigned block, channel = 0;
/* Find channel number to update the ISR register */
while (&dev->ch[channel] != ch) {
channel++;
}
block = channel / 2;
dev->blk[block].isr |= ISR_RXRDY(channel);
ch->sr |= SR_RXRDY;
update_irq(dev, block);
}
}
static void hostdev_event(void *opaque, QEMUChrEvent event)
{
SCC2698Channel *ch = opaque;
switch (event) {
case CHR_EVENT_OPENED:
DPRINTF("Device %s opened\n", ch->dev->label);
break;
case CHR_EVENT_BREAK: {
uint8_t zero = 0;
DPRINTF("Device %s received break\n", ch->dev->label);
if (!(ch->sr & SR_BREAK)) {
IPOctalState *dev = ch->ipoctal;
unsigned block, channel = 0;
while (&dev->ch[channel] != ch) {
channel++;
}
block = channel / 2;
ch->sr |= SR_BREAK;
dev->blk[block].isr |= ISR_BREAK(channel);
}
/* Put a zero character in the buffer */
hostdev_receive(ch, &zero, 1);
}
break;
default:
DPRINTF("Device %s received event %d\n", ch->dev->label, event);
}
}
static void ipoctal_realize(DeviceState *dev, Error **errp)
{
IPOctalState *s = IPOCTAL(dev);
unsigned i;
for (i = 0; i < N_CHANNELS; i++) {
SCC2698Channel *ch = &s->ch[i];
ch->ipoctal = s;
/* Redirect IP-Octal channels to host character devices */
if (qemu_chr_fe_backend_connected(&ch->dev)) {
qemu_chr_fe_set_handlers(&ch->dev, hostdev_can_receive,
hostdev_receive, hostdev_event,
NULL, ch, NULL, true);
DPRINTF("Redirecting channel %u to %s\n", i, ch->dev->label);
} else {
DPRINTF("Could not redirect channel %u, no chardev set\n", i);
}
}
}
static const Property ipoctal_properties[] = {
DEFINE_PROP_CHR("chardev0", IPOctalState, ch[0].dev),
DEFINE_PROP_CHR("chardev1", IPOctalState, ch[1].dev),
DEFINE_PROP_CHR("chardev2", IPOctalState, ch[2].dev),
DEFINE_PROP_CHR("chardev3", IPOctalState, ch[3].dev),
DEFINE_PROP_CHR("chardev4", IPOctalState, ch[4].dev),
DEFINE_PROP_CHR("chardev5", IPOctalState, ch[5].dev),
DEFINE_PROP_CHR("chardev6", IPOctalState, ch[6].dev),
DEFINE_PROP_CHR("chardev7", IPOctalState, ch[7].dev),
};
static void ipoctal_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
IPackDeviceClass *ic = IPACK_DEVICE_CLASS(klass);
ic->realize = ipoctal_realize;
ic->io_read = io_read;
ic->io_write = io_write;
ic->id_read = id_read;
ic->id_write = id_write;
ic->int_read = int_read;
ic->int_write = int_write;
ic->mem_read16 = mem_read16;
ic->mem_write16 = mem_write16;
ic->mem_read8 = mem_read8;
ic->mem_write8 = mem_write8;
set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
dc->desc = "GE IP-Octal 232 8-channel RS-232 IndustryPack";
device_class_set_props(dc, ipoctal_properties);
dc->vmsd = &vmstate_ipoctal;
}
static const TypeInfo ipoctal_info = {
.name = TYPE_IPOCTAL,
.parent = TYPE_IPACK_DEVICE,
.instance_size = sizeof(IPOctalState),
.class_init = ipoctal_class_init,
};
static void ipoctal_register_types(void)
{
type_register_static(&ipoctal_info);
}
type_init(ipoctal_register_types)
+292
View File
@@ -0,0 +1,292 @@
/*
* MAX78000 UART
*
* Copyright (c) 2025 Jackson Donaldson <[email protected]>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
#include "hw/char/max78000_uart.h"
#include "hw/core/irq.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-properties-system.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "migration/vmstate.h"
#include "trace.h"
static int max78000_uart_can_receive(void *opaque)
{
Max78000UartState *s = opaque;
if (!(s->ctrl & UART_BCLKEN)) {
return 0;
}
return fifo8_num_free(&s->rx_fifo);
}
static void max78000_update_irq(Max78000UartState *s)
{
int interrupt_level;
interrupt_level = s->int_fl & s->int_en;
qemu_set_irq(s->irq, interrupt_level);
}
static void max78000_uart_receive(void *opaque, const uint8_t *buf, int size)
{
Max78000UartState *s = opaque;
assert(size <= fifo8_num_free(&s->rx_fifo));
fifo8_push_all(&s->rx_fifo, buf, size);
uint32_t rx_threshold = s->ctrl & 0xf;
if (fifo8_num_used(&s->rx_fifo) >= rx_threshold) {
s->int_fl |= UART_RX_THD;
}
max78000_update_irq(s);
}
static void max78000_uart_reset_hold(Object *obj, ResetType type)
{
Max78000UartState *s = MAX78000_UART(obj);
s->ctrl = 0;
s->status = UART_TX_EM | UART_RX_EM;
s->int_en = 0;
s->int_fl = 0;
s->osr = 0;
s->txpeek = 0;
s->pnr = UART_RTS;
s->fifo = 0;
s->dma = 0;
s->wken = 0;
s->wkfl = 0;
fifo8_reset(&s->rx_fifo);
}
static uint64_t max78000_uart_read(void *opaque, hwaddr addr,
unsigned int size)
{
Max78000UartState *s = opaque;
uint64_t retvalue = 0;
switch (addr) {
case UART_CTRL:
retvalue = s->ctrl;
break;
case UART_STATUS:
retvalue = (fifo8_num_used(&s->rx_fifo) << UART_RX_LVL) |
UART_TX_EM |
(fifo8_is_empty(&s->rx_fifo) ? UART_RX_EM : 0);
break;
case UART_INT_EN:
retvalue = s->int_en;
break;
case UART_INT_FL:
retvalue = s->int_fl;
break;
case UART_CLKDIV:
retvalue = s->clkdiv;
break;
case UART_OSR:
retvalue = s->osr;
break;
case UART_TXPEEK:
if (!fifo8_is_empty(&s->rx_fifo)) {
retvalue = fifo8_peek(&s->rx_fifo);
}
break;
case UART_PNR:
retvalue = s->pnr;
break;
case UART_FIFO:
if (!fifo8_is_empty(&s->rx_fifo)) {
retvalue = fifo8_pop(&s->rx_fifo);
max78000_update_irq(s);
}
break;
case UART_DMA:
/* DMA not implemented */
retvalue = s->dma;
break;
case UART_WKEN:
retvalue = s->wken;
break;
case UART_WKFL:
retvalue = s->wkfl;
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr);
break;
}
return retvalue;
}
static void max78000_uart_write(void *opaque, hwaddr addr,
uint64_t val64, unsigned int size)
{
Max78000UartState *s = opaque;
uint32_t value = val64;
uint8_t data;
switch (addr) {
case UART_CTRL:
if (value & UART_FLUSH_RX) {
fifo8_reset(&s->rx_fifo);
}
if (value & UART_BCLKEN) {
value = value | UART_BCLKRDY;
}
s->ctrl = value & ~(UART_FLUSH_RX | UART_FLUSH_TX);
/*
* Software can manage UART flow control manually by setting hfc_en
* in UART_CTRL. This would require emulating uart at a lower level,
* and is currently unimplemented.
*/
return;
case UART_STATUS:
/* UART_STATUS is read only */
return;
case UART_INT_EN:
s->int_en = value;
return;
case UART_INT_FL:
s->int_fl = s->int_fl & ~(value);
max78000_update_irq(s);
return;
case UART_CLKDIV:
s->clkdiv = value;
return;
case UART_OSR:
s->osr = value;
return;
case UART_PNR:
s->pnr = value;
return;
case UART_FIFO:
data = value & 0xff;
/*
* XXX this blocks entire thread. Rewrite to use
* qemu_chr_fe_write and background I/O callbacks
*/
qemu_chr_fe_write_all(&s->chr, &data, 1);
/* TX is always empty */
s->int_fl |= UART_TX_HE;
max78000_update_irq(s);
return;
case UART_DMA:
/* DMA not implemented */
s->dma = value;
return;
case UART_WKEN:
s->wken = value;
return;
case UART_WKFL:
s->wkfl = value;
return;
default:
qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%"
HWADDR_PRIx "\n", __func__, addr);
}
}
static const MemoryRegionOps max78000_uart_ops = {
.read = max78000_uart_read,
.write = max78000_uart_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.valid.min_access_size = 4,
.valid.max_access_size = 4,
};
static const Property max78000_uart_properties[] = {
DEFINE_PROP_CHR("chardev", Max78000UartState, chr),
};
static const VMStateDescription max78000_uart_vmstate = {
.name = TYPE_MAX78000_UART,
.version_id = 1,
.minimum_version_id = 1,
.fields = (VMStateField[]) {
VMSTATE_UINT32(ctrl, Max78000UartState),
VMSTATE_UINT32(status, Max78000UartState),
VMSTATE_UINT32(int_en, Max78000UartState),
VMSTATE_UINT32(int_fl, Max78000UartState),
VMSTATE_UINT32(clkdiv, Max78000UartState),
VMSTATE_UINT32(osr, Max78000UartState),
VMSTATE_UINT32(txpeek, Max78000UartState),
VMSTATE_UINT32(pnr, Max78000UartState),
VMSTATE_UINT32(fifo, Max78000UartState),
VMSTATE_UINT32(dma, Max78000UartState),
VMSTATE_UINT32(wken, Max78000UartState),
VMSTATE_UINT32(wkfl, Max78000UartState),
VMSTATE_FIFO8(rx_fifo, Max78000UartState),
VMSTATE_END_OF_LIST()
}
};
static void max78000_uart_init(Object *obj)
{
Max78000UartState *s = MAX78000_UART(obj);
fifo8_create(&s->rx_fifo, 8);
sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
memory_region_init_io(&s->mmio, obj, &max78000_uart_ops, s,
TYPE_MAX78000_UART, 0x400);
sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
}
static void max78000_uart_finalize(Object *obj)
{
Max78000UartState *s = MAX78000_UART(obj);
fifo8_destroy(&s->rx_fifo);
}
static void max78000_uart_realize(DeviceState *dev, Error **errp)
{
Max78000UartState *s = MAX78000_UART(dev);
qemu_chr_fe_set_handlers(&s->chr, max78000_uart_can_receive,
max78000_uart_receive, NULL, NULL,
s, NULL, true);
}
static void max78000_uart_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
ResettableClass *rc = RESETTABLE_CLASS(klass);
rc->phases.hold = max78000_uart_reset_hold;
device_class_set_props(dc, max78000_uart_properties);
dc->realize = max78000_uart_realize;
dc->vmsd = &max78000_uart_vmstate;
}
static const TypeInfo max78000_uart_info = {
.name = TYPE_MAX78000_UART,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(Max78000UartState),
.instance_init = max78000_uart_init,
.instance_finalize = max78000_uart_finalize,
.class_init = max78000_uart_class_init,
};
static void max78000_uart_register_types(void)
{
type_register_static(&max78000_uart_info);
}
type_init(max78000_uart_register_types)
+372
View File
@@ -0,0 +1,372 @@
/*
* ColdFire UART emulation.
*
* Copyright (c) 2007 CodeSourcery.
*
* This code is licensed under the GPL
*/
#include "qemu/osdep.h"
#include "hw/core/irq.h"
#include "hw/core/sysbus.h"
#include "qemu/module.h"
#include "qapi/error.h"
#include "hw/m68k/mcf.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-properties-system.h"
#include "chardev/char-fe.h"
#include "qom/object.h"
#define FIFO_DEPTH 4
struct mcf_uart_state {
SysBusDevice parent_obj;
MemoryRegion iomem;
uint8_t mr[2];
uint8_t sr;
uint8_t isr;
uint8_t imr;
uint8_t bg1;
uint8_t bg2;
uint8_t fifo[FIFO_DEPTH];
uint8_t tb;
int current_mr;
int fifo_len;
int tx_enabled;
int rx_enabled;
qemu_irq irq;
CharFrontend chr;
};
#define TYPE_MCF_UART "mcf-uart"
OBJECT_DECLARE_SIMPLE_TYPE(mcf_uart_state, MCF_UART)
/* UART Status Register bits. */
#define MCF_UART_RxRDY 0x01
#define MCF_UART_FFULL 0x02
#define MCF_UART_TxRDY 0x04
#define MCF_UART_TxEMP 0x08
#define MCF_UART_OE 0x10
#define MCF_UART_PE 0x20
#define MCF_UART_FE 0x40
#define MCF_UART_RB 0x80
/* Interrupt flags. */
#define MCF_UART_TxINT 0x01
#define MCF_UART_RxINT 0x02
#define MCF_UART_DBINT 0x04
#define MCF_UART_COSINT 0x80
/* UMR1 flags. */
#define MCF_UART_BC0 0x01
#define MCF_UART_BC1 0x02
#define MCF_UART_PT 0x04
#define MCF_UART_PM0 0x08
#define MCF_UART_PM1 0x10
#define MCF_UART_ERR 0x20
#define MCF_UART_RxIRQ 0x40
#define MCF_UART_RxRTS 0x80
static void mcf_uart_update(mcf_uart_state *s)
{
s->isr &= ~(MCF_UART_TxINT | MCF_UART_RxINT);
if (s->sr & MCF_UART_TxRDY)
s->isr |= MCF_UART_TxINT;
if ((s->sr & ((s->mr[0] & MCF_UART_RxIRQ)
? MCF_UART_FFULL : MCF_UART_RxRDY)) != 0)
s->isr |= MCF_UART_RxINT;
qemu_set_irq(s->irq, (s->isr & s->imr) != 0);
}
uint64_t mcf_uart_read(void *opaque, hwaddr addr,
unsigned size)
{
mcf_uart_state *s = (mcf_uart_state *)opaque;
switch (addr & 0x3f) {
case 0x00:
return s->mr[s->current_mr];
case 0x04:
return s->sr;
case 0x0c:
{
uint8_t val;
int i;
if (s->fifo_len == 0)
return 0;
val = s->fifo[0];
s->fifo_len--;
for (i = 0; i < s->fifo_len; i++)
s->fifo[i] = s->fifo[i + 1];
s->sr &= ~MCF_UART_FFULL;
if (s->fifo_len == 0)
s->sr &= ~MCF_UART_RxRDY;
mcf_uart_update(s);
qemu_chr_fe_accept_input(&s->chr);
return val;
}
case 0x10:
/* TODO: Implement IPCR. */
return 0;
case 0x14:
return s->isr;
case 0x18:
return s->bg1;
case 0x1c:
return s->bg2;
default:
return 0;
}
}
/* Update TxRDY flag and set data if present and enabled. */
static void mcf_uart_do_tx(mcf_uart_state *s)
{
if (s->tx_enabled && (s->sr & MCF_UART_TxEMP) == 0) {
/* XXX this blocks entire thread. Rewrite to use
* qemu_chr_fe_write and background I/O callbacks */
qemu_chr_fe_write_all(&s->chr, (unsigned char *)&s->tb, 1);
s->sr |= MCF_UART_TxEMP;
}
if (s->tx_enabled) {
s->sr |= MCF_UART_TxRDY;
} else {
s->sr &= ~MCF_UART_TxRDY;
}
}
static void mcf_do_command(mcf_uart_state *s, uint8_t cmd)
{
/* Misc command. */
switch ((cmd >> 4) & 7) {
case 0: /* No-op. */
break;
case 1: /* Reset mode register pointer. */
s->current_mr = 0;
break;
case 2: /* Reset receiver. */
s->rx_enabled = 0;
s->fifo_len = 0;
s->sr &= ~(MCF_UART_RxRDY | MCF_UART_FFULL);
break;
case 3: /* Reset transmitter. */
s->tx_enabled = 0;
s->sr |= MCF_UART_TxEMP;
s->sr &= ~MCF_UART_TxRDY;
break;
case 4: /* Reset error status. */
break;
case 5: /* Reset break-change interrupt. */
s->isr &= ~MCF_UART_DBINT;
break;
case 6: /* Start break. */
case 7: /* Stop break. */
break;
}
/* Transmitter command. */
switch ((cmd >> 2) & 3) {
case 0: /* No-op. */
break;
case 1: /* Enable. */
s->tx_enabled = 1;
mcf_uart_do_tx(s);
break;
case 2: /* Disable. */
s->tx_enabled = 0;
mcf_uart_do_tx(s);
break;
case 3: /* Reserved. */
fprintf(stderr, "mcf_uart: Bad TX command\n");
break;
}
/* Receiver command. */
switch (cmd & 3) {
case 0: /* No-op. */
break;
case 1: /* Enable. */
s->rx_enabled = 1;
break;
case 2:
s->rx_enabled = 0;
break;
case 3: /* Reserved. */
fprintf(stderr, "mcf_uart: Bad RX command\n");
break;
}
}
void mcf_uart_write(void *opaque, hwaddr addr,
uint64_t val, unsigned size)
{
mcf_uart_state *s = (mcf_uart_state *)opaque;
switch (addr & 0x3f) {
case 0x00:
s->mr[s->current_mr] = val;
s->current_mr = 1;
break;
case 0x04:
/* CSR is ignored. */
break;
case 0x08: /* Command Register. */
mcf_do_command(s, val);
break;
case 0x0c: /* Transmit Buffer. */
s->sr &= ~MCF_UART_TxEMP;
s->tb = val;
mcf_uart_do_tx(s);
break;
case 0x10:
/* ACR is ignored. */
break;
case 0x14:
s->imr = val;
break;
default:
break;
}
mcf_uart_update(s);
}
static void mcf_uart_reset(DeviceState *dev)
{
mcf_uart_state *s = MCF_UART(dev);
s->fifo_len = 0;
s->mr[0] = 0;
s->mr[1] = 0;
s->sr = MCF_UART_TxEMP;
s->tx_enabled = 0;
s->rx_enabled = 0;
s->isr = 0;
s->imr = 0;
}
static void mcf_uart_push_byte(mcf_uart_state *s, uint8_t data)
{
/* Break events overwrite the last byte if the fifo is full. */
if (s->fifo_len == FIFO_DEPTH) {
s->fifo_len--;
}
s->fifo[s->fifo_len] = data;
s->fifo_len++;
s->sr |= MCF_UART_RxRDY;
if (s->fifo_len == FIFO_DEPTH) {
s->sr |= MCF_UART_FFULL;
}
mcf_uart_update(s);
}
static void mcf_uart_event(void *opaque, QEMUChrEvent event)
{
mcf_uart_state *s = (mcf_uart_state *)opaque;
switch (event) {
case CHR_EVENT_BREAK:
s->isr |= MCF_UART_DBINT;
mcf_uart_push_byte(s, 0);
break;
default:
break;
}
}
static int mcf_uart_can_receive(void *opaque)
{
mcf_uart_state *s = (mcf_uart_state *)opaque;
return s->rx_enabled ? FIFO_DEPTH - s->fifo_len : 0;
}
static void mcf_uart_receive(void *opaque, const uint8_t *buf, int size)
{
mcf_uart_state *s = (mcf_uart_state *)opaque;
for (int i = 0; i < size; i++) {
mcf_uart_push_byte(s, buf[i]);
}
}
static const MemoryRegionOps mcf_uart_ops = {
.read = mcf_uart_read,
.write = mcf_uart_write,
.endianness = DEVICE_NATIVE_ENDIAN,
};
static void mcf_uart_instance_init(Object *obj)
{
SysBusDevice *dev = SYS_BUS_DEVICE(obj);
mcf_uart_state *s = MCF_UART(dev);
memory_region_init_io(&s->iomem, obj, &mcf_uart_ops, s, "uart", 0x40);
sysbus_init_mmio(dev, &s->iomem);
sysbus_init_irq(dev, &s->irq);
}
static void mcf_uart_realize(DeviceState *dev, Error **errp)
{
mcf_uart_state *s = MCF_UART(dev);
qemu_chr_fe_set_handlers(&s->chr, mcf_uart_can_receive, mcf_uart_receive,
mcf_uart_event, NULL, s, NULL, true);
}
static const Property mcf_uart_properties[] = {
DEFINE_PROP_CHR("chardev", mcf_uart_state, chr),
};
static void mcf_uart_class_init(ObjectClass *oc, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(oc);
dc->realize = mcf_uart_realize;
device_class_set_legacy_reset(dc, mcf_uart_reset);
device_class_set_props(dc, mcf_uart_properties);
set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
}
static const TypeInfo mcf_uart_info = {
.name = TYPE_MCF_UART,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(mcf_uart_state),
.instance_init = mcf_uart_instance_init,
.class_init = mcf_uart_class_init,
};
static void mcf_uart_register(void)
{
type_register_static(&mcf_uart_info);
}
type_init(mcf_uart_register)
DeviceState *mcf_uart_create(qemu_irq irq, Chardev *chrdrv)
{
DeviceState *dev;
dev = qdev_new(TYPE_MCF_UART);
if (chrdrv) {
qdev_prop_set_chr(dev, "chardev", chrdrv);
}
sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, irq);
return dev;
}
DeviceState *mcf_uart_create_mmap(hwaddr base, qemu_irq irq, Chardev *chrdrv)
{
DeviceState *dev;
dev = mcf_uart_create(irq, chrdrv);
sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, base);
return dev;
}
+163
View File
@@ -0,0 +1,163 @@
/*
* Microchip PolarFire SoC MMUART emulation
*
* Copyright (c) 2020 Wind River Systems, Inc.
*
* Author:
* Bin Meng <[email protected]>
*
* 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; either version 2 or
* (at your option) 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 <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "qemu/log.h"
#include "qapi/error.h"
#include "migration/vmstate.h"
#include "hw/char/mchp_pfsoc_mmuart.h"
#include "hw/core/qdev-properties.h"
#define REGS_OFFSET 0x20
static uint64_t mchp_pfsoc_mmuart_read(void *opaque, hwaddr addr, unsigned size)
{
MchpPfSoCMMUartState *s = opaque;
addr >>= 2;
if (addr >= MCHP_PFSOC_MMUART_REG_COUNT) {
qemu_log_mask(LOG_GUEST_ERROR, "%s: read: addr=0x%" HWADDR_PRIx "\n",
__func__, addr << 2);
return 0;
}
return s->reg[addr];
}
static void mchp_pfsoc_mmuart_write(void *opaque, hwaddr addr,
uint64_t value, unsigned size)
{
MchpPfSoCMMUartState *s = opaque;
uint32_t val32 = (uint32_t)value;
addr >>= 2;
if (addr >= MCHP_PFSOC_MMUART_REG_COUNT) {
qemu_log_mask(LOG_GUEST_ERROR, "%s: bad write: addr=0x%" HWADDR_PRIx
" v=0x%x\n", __func__, addr << 2, val32);
return;
}
s->reg[addr] = val32;
}
static const MemoryRegionOps mchp_pfsoc_mmuart_ops = {
.read = mchp_pfsoc_mmuart_read,
.write = mchp_pfsoc_mmuart_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.impl = {
.min_access_size = 4,
.max_access_size = 4,
},
};
static void mchp_pfsoc_mmuart_reset(DeviceState *dev)
{
MchpPfSoCMMUartState *s = MCHP_PFSOC_UART(dev);
memset(s->reg, 0, sizeof(s->reg));
device_cold_reset(DEVICE(&s->serial_mm));
}
static void mchp_pfsoc_mmuart_init(Object *obj)
{
MchpPfSoCMMUartState *s = MCHP_PFSOC_UART(obj);
object_initialize_child(obj, "serial-mm", &s->serial_mm, TYPE_SERIAL_MM);
object_property_add_alias(obj, "chardev", OBJECT(&s->serial_mm), "chardev");
}
static void mchp_pfsoc_mmuart_realize(DeviceState *dev, Error **errp)
{
MchpPfSoCMMUartState *s = MCHP_PFSOC_UART(dev);
qdev_prop_set_uint8(DEVICE(&s->serial_mm), "regshift", 2);
qdev_prop_set_uint32(DEVICE(&s->serial_mm), "baudbase", 399193);
qdev_prop_set_uint8(DEVICE(&s->serial_mm), "endianness",
DEVICE_LITTLE_ENDIAN);
if (!sysbus_realize(SYS_BUS_DEVICE(&s->serial_mm), errp)) {
return;
}
sysbus_pass_irq(SYS_BUS_DEVICE(dev), SYS_BUS_DEVICE(&s->serial_mm));
memory_region_init(&s->container, OBJECT(s), "mchp.pfsoc.mmuart", 0x1000);
sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->container);
memory_region_add_subregion(&s->container, 0,
sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->serial_mm), 0));
memory_region_init_io(&s->iomem, OBJECT(s), &mchp_pfsoc_mmuart_ops, s,
"mchp.pfsoc.mmuart.regs", 0x1000 - REGS_OFFSET);
memory_region_add_subregion(&s->container, REGS_OFFSET, &s->iomem);
}
static const VMStateDescription mchp_pfsoc_mmuart_vmstate = {
.name = "mchp.pfsoc.uart",
.version_id = 0,
.minimum_version_id = 0,
.fields = (const VMStateField[]) {
VMSTATE_UINT32_ARRAY(reg, MchpPfSoCMMUartState,
MCHP_PFSOC_MMUART_REG_COUNT),
VMSTATE_END_OF_LIST()
}
};
static void mchp_pfsoc_mmuart_class_init(ObjectClass *oc, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(oc);
dc->realize = mchp_pfsoc_mmuart_realize;
device_class_set_legacy_reset(dc, mchp_pfsoc_mmuart_reset);
dc->vmsd = &mchp_pfsoc_mmuart_vmstate;
set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
}
static const TypeInfo mchp_pfsoc_mmuart_info = {
.name = TYPE_MCHP_PFSOC_UART,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(MchpPfSoCMMUartState),
.instance_init = mchp_pfsoc_mmuart_init,
.class_init = mchp_pfsoc_mmuart_class_init,
};
static void mchp_pfsoc_mmuart_register_types(void)
{
type_register_static(&mchp_pfsoc_mmuart_info);
}
type_init(mchp_pfsoc_mmuart_register_types)
MchpPfSoCMMUartState *mchp_pfsoc_mmuart_create(MemoryRegion *sysmem,
hwaddr base,
qemu_irq irq, Chardev *chr)
{
DeviceState *dev = qdev_new(TYPE_MCHP_PFSOC_UART);
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
qdev_prop_set_chr(dev, "chardev", chr);
sysbus_realize(sbd, &error_fatal);
memory_region_add_subregion(sysmem, base, sysbus_mmio_get_region(sbd, 0));
sysbus_connect_irq(sbd, 0, irq);
return MCHP_PFSOC_UART(dev);
}
+43
View File
@@ -0,0 +1,43 @@
system_ss.add(when: 'CONFIG_CADENCE', if_true: files('cadence_uart.c'))
system_ss.add(when: 'CONFIG_CMSDK_APB_UART', if_true: files('cmsdk-apb-uart.c'))
system_ss.add(when: 'CONFIG_ESCC', if_true: files('escc.c'))
system_ss.add(when: 'CONFIG_GRLIB', if_true: files('grlib_apbuart.c'))
system_ss.add(when: 'CONFIG_IBEX', if_true: files('ibex_uart.c'))
system_ss.add(when: 'CONFIG_IMX', if_true: files('imx_serial.c'))
system_ss.add(when: 'CONFIG_IP_OCTAL_232', if_true: files('ipoctal232.c'))
system_ss.add(when: 'CONFIG_ISA_BUS', if_true: files('parallel-isa.c'))
system_ss.add(when: 'CONFIG_ISA_DEBUG', if_true: files('debugcon.c'))
system_ss.add(when: 'CONFIG_NRF51_SOC', if_true: files('nrf51_uart.c'))
system_ss.add(when: 'CONFIG_PARALLEL', if_true: files('parallel.c'))
system_ss.add(when: 'CONFIG_PL011_C', if_true: files('pl011.c'))
system_ss.add(when: 'CONFIG_SCLPCONSOLE', if_true: files('sclpconsole.c', 'sclpconsole-lm.c'))
system_ss.add(when: 'CONFIG_SERIAL', if_true: files('serial.c'))
system_ss.add(when: 'CONFIG_SERIAL_ISA', if_true: files('serial-isa.c'))
system_ss.add(when: 'CONFIG_SERIAL_MM', if_true: files('serial-mm.c'))
system_ss.add(when: 'CONFIG_SERIAL_PCI', if_true: files('serial-pci.c'))
system_ss.add(when: 'CONFIG_SERIAL_PCI_MULTI', if_true: files('serial-pci-multi.c'))
system_ss.add(when: 'CONFIG_SHAKTI_UART', if_true: files('shakti_uart.c'))
system_ss.add(when: 'CONFIG_VIRTIO', if_true: files('virtio-serial-bus.c'))
system_ss.add(when: 'CONFIG_VIRTIO_SERIAL', if_true: files('virtio-console.c'))
system_ss.add(when: 'CONFIG_XEN_BUS', if_true: files('xen_console.c'))
system_ss.add(when: 'CONFIG_XILINX', if_true: files('xilinx_uartlite.c'))
system_ss.add(when: 'CONFIG_DIVA_GSP', if_true: files('diva-gsp.c'))
system_ss.add(when: 'CONFIG_AVR_USART', if_true: files('avr_usart.c'))
system_ss.add(when: 'CONFIG_COLDFIRE', if_true: files('mcf_uart.c'))
system_ss.add(when: 'CONFIG_DIGIC', if_true: files('digic-uart.c'))
system_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_uart.c'))
system_ss.add(when: 'CONFIG_MAX78000_UART', if_true: files('max78000_uart.c'))
system_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_uart.c'))
system_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2835_aux.c'))
system_ss.add(when: 'CONFIG_RENESAS_SCI', if_true: files('renesas_sci.c'))
system_ss.add(when: 'CONFIG_SIFIVE_UART', if_true: files('sifive_uart.c'))
system_ss.add(when: 'CONFIG_SH_SCI', if_true: files('sh_serial.c'))
system_ss.add(when: 'CONFIG_STM32F2XX_USART', if_true: files('stm32f2xx_usart.c'))
system_ss.add(when: 'CONFIG_STM32L4X5_USART', if_true: files('stm32l4x5_usart.c'))
system_ss.add(when: 'CONFIG_MCHP_PFSOC_MMUART', if_true: files('mchp_pfsoc_mmuart.c'))
system_ss.add(when: 'CONFIG_HTIF', if_true: files('riscv_htif.c'))
system_ss.add(when: 'CONFIG_GOLDFISH_TTY', if_true: files('goldfish_tty.c'))
specific_ss.add(when: 'CONFIG_TERMINAL3270', if_true: files('terminal3270.c'))
specific_ss.add(when: 'CONFIG_PSERIES', if_true: files('spapr_vty.c'))
+331
View File
@@ -0,0 +1,331 @@
/*
* nRF51 SoC UART emulation
*
* See nRF51 Series Reference Manual, "29 Universal Asynchronous
* Receiver/Transmitter" for hardware specifications:
* http://infocenter.nordicsemi.com/pdf/nRF51_RM_v3.0.pdf
*
* Copyright (c) 2018 Julia Suvorova <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 or
* (at your option) any later version.
*/
#include "qemu/osdep.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "hw/char/nrf51_uart.h"
#include "hw/core/irq.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-properties-system.h"
#include "migration/vmstate.h"
#include "trace.h"
static void nrf51_uart_update_irq(NRF51UARTState *s)
{
bool irq = false;
irq |= (s->reg[R_UART_RXDRDY] &&
(s->reg[R_UART_INTEN] & R_UART_INTEN_RXDRDY_MASK));
irq |= (s->reg[R_UART_TXDRDY] &&
(s->reg[R_UART_INTEN] & R_UART_INTEN_TXDRDY_MASK));
irq |= (s->reg[R_UART_ERROR] &&
(s->reg[R_UART_INTEN] & R_UART_INTEN_ERROR_MASK));
irq |= (s->reg[R_UART_RXTO] &&
(s->reg[R_UART_INTEN] & R_UART_INTEN_RXTO_MASK));
qemu_set_irq(s->irq, irq);
}
static uint64_t uart_read(void *opaque, hwaddr addr, unsigned int size)
{
NRF51UARTState *s = NRF51_UART(opaque);
uint64_t r;
if (!s->enabled) {
return 0;
}
switch (addr) {
case A_UART_RXD:
r = s->rx_fifo[s->rx_fifo_pos];
if (s->rx_started && s->rx_fifo_len) {
s->rx_fifo_pos = (s->rx_fifo_pos + 1) % UART_FIFO_LENGTH;
s->rx_fifo_len--;
if (s->rx_fifo_len) {
s->reg[R_UART_RXDRDY] = 1;
nrf51_uart_update_irq(s);
}
qemu_chr_fe_accept_input(&s->chr);
}
break;
case A_UART_INTENSET:
case A_UART_INTENCLR:
case A_UART_INTEN:
r = s->reg[R_UART_INTEN];
break;
default:
r = s->reg[addr / 4];
break;
}
trace_nrf51_uart_read(addr, r, size);
return r;
}
static gboolean uart_transmit(void *do_not_use, GIOCondition cond, void *opaque)
{
NRF51UARTState *s = NRF51_UART(opaque);
int r;
uint8_t c = s->reg[R_UART_TXD];
s->watch_tag = 0;
r = qemu_chr_fe_write(&s->chr, &c, 1);
if (r <= 0) {
s->watch_tag = qemu_chr_fe_add_watch(&s->chr, G_IO_OUT | G_IO_HUP,
uart_transmit, s);
if (!s->watch_tag) {
/* The hardware has no transmit error reporting,
* so silently drop the byte
*/
goto buffer_drained;
}
return G_SOURCE_REMOVE;
}
buffer_drained:
s->reg[R_UART_TXDRDY] = 1;
s->pending_tx_byte = false;
return G_SOURCE_REMOVE;
}
static void uart_cancel_transmit(NRF51UARTState *s)
{
g_clear_handle_id(&s->watch_tag, g_source_remove);
}
static void uart_write(void *opaque, hwaddr addr,
uint64_t value, unsigned int size)
{
NRF51UARTState *s = NRF51_UART(opaque);
trace_nrf51_uart_write(addr, value, size);
if (!s->enabled && (addr != A_UART_ENABLE)) {
return;
}
switch (addr) {
case A_UART_TXD:
if (!s->pending_tx_byte && s->tx_started) {
s->reg[R_UART_TXD] = value;
s->pending_tx_byte = true;
uart_transmit(NULL, G_IO_OUT, s);
}
break;
case A_UART_INTEN:
s->reg[R_UART_INTEN] = value;
break;
case A_UART_INTENSET:
s->reg[R_UART_INTEN] |= value;
break;
case A_UART_INTENCLR:
s->reg[R_UART_INTEN] &= ~value;
break;
case A_UART_TXDRDY ... A_UART_RXTO:
s->reg[addr / 4] = value;
break;
case A_UART_ERRORSRC:
s->reg[addr / 4] &= ~value;
break;
case A_UART_RXD:
break;
case A_UART_RXDRDY:
if (value == 0) {
s->reg[R_UART_RXDRDY] = 0;
}
break;
case A_UART_STARTTX:
if (value == 1) {
s->tx_started = true;
}
break;
case A_UART_STARTRX:
if (value == 1) {
s->rx_started = true;
}
break;
case A_UART_ENABLE:
if (value) {
if (value == 4) {
s->enabled = true;
}
break;
}
s->enabled = false;
value = 1;
/* fall through */
case A_UART_SUSPEND:
case A_UART_STOPTX:
if (value == 1) {
s->tx_started = false;
}
/* fall through */
case A_UART_STOPRX:
if (addr != A_UART_STOPTX && value == 1) {
s->rx_started = false;
s->reg[R_UART_RXTO] = 1;
}
break;
default:
s->reg[addr / 4] = value;
break;
}
nrf51_uart_update_irq(s);
}
static const MemoryRegionOps uart_ops = {
.read = uart_read,
.write = uart_write,
.endianness = DEVICE_LITTLE_ENDIAN,
};
static void nrf51_uart_reset(DeviceState *dev)
{
NRF51UARTState *s = NRF51_UART(dev);
s->pending_tx_byte = 0;
uart_cancel_transmit(s);
memset(s->reg, 0, sizeof(s->reg));
s->reg[R_UART_PSELRTS] = 0xFFFFFFFF;
s->reg[R_UART_PSELTXD] = 0xFFFFFFFF;
s->reg[R_UART_PSELCTS] = 0xFFFFFFFF;
s->reg[R_UART_PSELRXD] = 0xFFFFFFFF;
s->reg[R_UART_BAUDRATE] = 0x4000000;
s->rx_fifo_len = 0;
s->rx_fifo_pos = 0;
s->rx_started = false;
s->tx_started = false;
s->enabled = false;
}
static void uart_receive(void *opaque, const uint8_t *buf, int size)
{
NRF51UARTState *s = NRF51_UART(opaque);
int i;
if (size == 0 || s->rx_fifo_len >= UART_FIFO_LENGTH) {
return;
}
for (i = 0; i < size; i++) {
uint32_t pos = (s->rx_fifo_pos + s->rx_fifo_len) % UART_FIFO_LENGTH;
s->rx_fifo[pos] = buf[i];
s->rx_fifo_len++;
}
s->reg[R_UART_RXDRDY] = 1;
nrf51_uart_update_irq(s);
}
static int uart_can_receive(void *opaque)
{
NRF51UARTState *s = NRF51_UART(opaque);
return s->rx_started ? (UART_FIFO_LENGTH - s->rx_fifo_len) : 0;
}
static void uart_event(void *opaque, QEMUChrEvent event)
{
NRF51UARTState *s = NRF51_UART(opaque);
if (event == CHR_EVENT_BREAK) {
s->reg[R_UART_ERRORSRC] |= 3;
s->reg[R_UART_ERROR] = 1;
nrf51_uart_update_irq(s);
}
}
static void nrf51_uart_realize(DeviceState *dev, Error **errp)
{
NRF51UARTState *s = NRF51_UART(dev);
qemu_chr_fe_set_handlers(&s->chr, uart_can_receive, uart_receive,
uart_event, NULL, s, NULL, true);
}
static void nrf51_uart_init(Object *obj)
{
NRF51UARTState *s = NRF51_UART(obj);
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
memory_region_init_io(&s->iomem, obj, &uart_ops, s,
"nrf51_soc.uart", UART_SIZE);
sysbus_init_mmio(sbd, &s->iomem);
sysbus_init_irq(sbd, &s->irq);
}
static int nrf51_uart_post_load(void *opaque, int version_id)
{
NRF51UARTState *s = NRF51_UART(opaque);
if (s->pending_tx_byte) {
s->watch_tag = qemu_chr_fe_add_watch(&s->chr, G_IO_OUT | G_IO_HUP,
uart_transmit, s);
}
return 0;
}
static const VMStateDescription nrf51_uart_vmstate = {
.name = "nrf51_soc.uart",
.post_load = nrf51_uart_post_load,
.fields = (const VMStateField[]) {
VMSTATE_UINT32_ARRAY(reg, NRF51UARTState, 0x56C),
VMSTATE_UINT8_ARRAY(rx_fifo, NRF51UARTState, UART_FIFO_LENGTH),
VMSTATE_UINT32(rx_fifo_pos, NRF51UARTState),
VMSTATE_UINT32(rx_fifo_len, NRF51UARTState),
VMSTATE_BOOL(rx_started, NRF51UARTState),
VMSTATE_BOOL(tx_started, NRF51UARTState),
VMSTATE_BOOL(pending_tx_byte, NRF51UARTState),
VMSTATE_BOOL(enabled, NRF51UARTState),
VMSTATE_END_OF_LIST()
}
};
static const Property nrf51_uart_properties[] = {
DEFINE_PROP_CHR("chardev", NRF51UARTState, chr),
};
static void nrf51_uart_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
device_class_set_legacy_reset(dc, nrf51_uart_reset);
dc->realize = nrf51_uart_realize;
device_class_set_props(dc, nrf51_uart_properties);
dc->vmsd = &nrf51_uart_vmstate;
}
static const TypeInfo nrf51_uart_info = {
.name = TYPE_NRF51_UART,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(NRF51UARTState),
.instance_init = nrf51_uart_init,
.class_init = nrf51_uart_class_init
};
static void nrf51_uart_register_types(void)
{
type_register_static(&nrf51_uart_info);
}
type_init(nrf51_uart_register_types)
+65
View File
@@ -0,0 +1,65 @@
/*
* TI OMAP processors UART emulation.
*
* Copyright (C) 2006-2008 Andrzej Zaborowski <[email protected]>
* Copyright (C) 2007-2009 Nokia Corporation
*
* 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; either version 2 or
* (at your option) 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 <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "chardev/char.h"
#include "hw/arm/omap.h"
#include "hw/char/serial-mm.h"
#include "system/address-spaces.h"
/* UARTs */
struct omap_uart_s {
MemoryRegion iomem;
hwaddr base;
SerialMM *serial; /* TODO */
omap_clk fclk;
qemu_irq irq;
uint8_t eblr;
uint8_t syscontrol;
uint8_t wkup;
uint8_t cfps;
uint8_t clksel;
};
void omap_uart_reset(struct omap_uart_s *s)
{
s->eblr = 0x00;
s->syscontrol = 0;
s->wkup = 0x3f;
s->cfps = 0x69;
s->clksel = 0;
}
struct omap_uart_s *omap_uart_init(hwaddr base,
qemu_irq irq, omap_clk fclk, omap_clk iclk,
qemu_irq txdma, qemu_irq rxdma,
const char *label, Chardev *chr)
{
struct omap_uart_s *s = g_new0(struct omap_uart_s, 1);
s->base = base;
s->fclk = fclk;
s->irq = irq;
s->serial = serial_mm_init(get_system_memory(), base, 2, irq,
omap_clk_getrate(fclk) / 16,
chr ?: qemu_chr_new(label, "null", NULL),
DEVICE_NATIVE_ENDIAN);
return s;
}
+57
View File
@@ -0,0 +1,57 @@
/*
* QEMU Parallel PORT (ISA bus helpers)
*
* These functions reside in a separate file since they also might be
* required for linking when compiling QEMU without CONFIG_PARALLEL.
*
* Copyright (c) 2003 Fabrice Bellard
*
* SPDX-License-Identifier: MIT
*/
#include "qemu/osdep.h"
#include "system/system.h"
#include "hw/isa/isa.h"
#include "hw/core/qdev-properties.h"
#include "hw/char/parallel-isa.h"
#include "hw/char/parallel.h"
#include "qapi/error.h"
static void parallel_init(ISABus *bus, int index, Chardev *chr)
{
DeviceState *dev;
ISADevice *isadev;
isadev = isa_new(TYPE_ISA_PARALLEL);
dev = DEVICE(isadev);
qdev_prop_set_uint32(dev, "index", index);
qdev_prop_set_chr(dev, "chardev", chr);
isa_realize_and_unref(isadev, bus, &error_fatal);
}
void parallel_hds_isa_init(ISABus *bus, int n)
{
int i;
assert(n <= MAX_PARALLEL_PORTS);
for (i = 0; i < n; i++) {
if (parallel_hds[i]) {
parallel_init(bus, i, parallel_hds[i]);
}
}
}
void isa_parallel_set_iobase(ISADevice *parallel, hwaddr iobase)
{
ISAParallelState *s = ISA_PARALLEL(parallel);
parallel->ioport_id = iobase;
s->iobase = iobase;
portio_list_set_address(&s->portio_list, s->iobase);
}
void isa_parallel_set_enabled(ISADevice *parallel, bool enabled)
{
portio_list_set_enabled(&ISA_PARALLEL(parallel)->portio_list, enabled);
}
+641
View File
@@ -0,0 +1,641 @@
/*
* QEMU Parallel PORT emulation
*
* Copyright (c) 2003-2005 Fabrice Bellard
* Copyright (c) 2007 Marko Kohtala
*
* 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
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* 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.
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qemu/module.h"
#include "chardev/char-parallel.h"
#include "hw/acpi/acpi_aml_interface.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-properties-system.h"
#include "migration/vmstate.h"
#include "hw/char/parallel-isa.h"
#include "hw/char/parallel.h"
#include "system/reset.h"
#include "system/system.h"
#include "trace.h"
#include "qom/object.h"
//#define DEBUG_PARALLEL
#ifdef DEBUG_PARALLEL
#define pdebug(fmt, ...) printf("pp: " fmt, ## __VA_ARGS__)
#else
#define pdebug(fmt, ...) ((void)0)
#endif
#define PARA_REG_DATA 0
#define PARA_REG_STS 1
#define PARA_REG_CTR 2
#define PARA_REG_EPP_ADDR 3
#define PARA_REG_EPP_DATA 4
/*
* These are the definitions for the Printer Status Register
*/
#define PARA_STS_BUSY 0x80 /* Busy complement */
#define PARA_STS_ACK 0x40 /* Acknowledge */
#define PARA_STS_PAPER 0x20 /* Out of paper */
#define PARA_STS_ONLINE 0x10 /* Online */
#define PARA_STS_ERROR 0x08 /* Error complement */
#define PARA_STS_TMOUT 0x01 /* EPP timeout */
/*
* These are the definitions for the Printer Control Register
*/
#define PARA_CTR_DIR 0x20 /* Direction (1=read, 0=write) */
#define PARA_CTR_INTEN 0x10 /* IRQ Enable */
#define PARA_CTR_SELECT 0x08 /* Select In complement */
#define PARA_CTR_INIT 0x04 /* Initialize Printer complement */
#define PARA_CTR_AUTOLF 0x02 /* Auto linefeed complement */
#define PARA_CTR_STROBE 0x01 /* Strobe complement */
#define PARA_CTR_SIGNAL (PARA_CTR_SELECT|PARA_CTR_INIT|PARA_CTR_AUTOLF|PARA_CTR_STROBE)
static void parallel_update_irq(ParallelState *s)
{
if (s->irq_pending)
qemu_irq_raise(s->irq);
else
qemu_irq_lower(s->irq);
}
static void
parallel_ioport_write_sw(void *opaque, uint32_t addr, uint32_t val)
{
ParallelState *s = opaque;
addr &= 7;
trace_parallel_ioport_write("SW", addr, val);
switch(addr) {
case PARA_REG_DATA:
s->dataw = val;
parallel_update_irq(s);
break;
case PARA_REG_CTR:
val |= 0xc0;
if ((val & PARA_CTR_INIT) == 0 ) {
s->status = PARA_STS_BUSY;
s->status |= PARA_STS_ACK;
s->status |= PARA_STS_ONLINE;
s->status |= PARA_STS_ERROR;
}
else if (val & PARA_CTR_SELECT) {
if (val & PARA_CTR_STROBE) {
s->status &= ~PARA_STS_BUSY;
if ((s->control & PARA_CTR_STROBE) == 0)
/* XXX this blocks entire thread. Rewrite to use
* qemu_chr_fe_write and background I/O callbacks */
qemu_chr_fe_write_all(&s->chr, &s->dataw, 1);
} else {
if (s->control & PARA_CTR_INTEN) {
s->irq_pending = 1;
}
}
}
parallel_update_irq(s);
s->control = val;
break;
}
}
static void parallel_ioport_write_hw(void *opaque, uint32_t addr, uint32_t val)
{
ParallelState *s = opaque;
uint8_t parm = val;
int dir;
/* Sometimes programs do several writes for timing purposes on old
HW. Take care not to waste time on writes that do nothing. */
s->last_read_offset = ~0U;
addr &= 7;
trace_parallel_ioport_write("HW", addr, val);
switch(addr) {
case PARA_REG_DATA:
if (s->dataw == val)
return;
pdebug("wd%02x\n", val);
qemu_chr_fe_ioctl(&s->chr, CHR_IOCTL_PP_WRITE_DATA, &parm);
s->dataw = val;
break;
case PARA_REG_STS:
pdebug("ws%02x\n", val);
if (val & PARA_STS_TMOUT)
s->epp_timeout = 0;
break;
case PARA_REG_CTR:
val |= 0xc0;
if (s->control == val)
return;
pdebug("wc%02x\n", val);
if ((val & PARA_CTR_DIR) != (s->control & PARA_CTR_DIR)) {
if (val & PARA_CTR_DIR) {
dir = 1;
} else {
dir = 0;
}
qemu_chr_fe_ioctl(&s->chr, CHR_IOCTL_PP_DATA_DIR, &dir);
parm &= ~PARA_CTR_DIR;
}
qemu_chr_fe_ioctl(&s->chr, CHR_IOCTL_PP_WRITE_CONTROL, &parm);
s->control = val;
break;
case PARA_REG_EPP_ADDR:
if ((s->control & (PARA_CTR_DIR|PARA_CTR_SIGNAL)) != PARA_CTR_INIT)
/* Controls not correct for EPP address cycle, so do nothing */
pdebug("wa%02x s\n", val);
else {
struct ParallelIOArg ioarg = { .buffer = &parm, .count = 1 };
if (qemu_chr_fe_ioctl(&s->chr,
CHR_IOCTL_PP_EPP_WRITE_ADDR, &ioarg)) {
s->epp_timeout = 1;
pdebug("wa%02x t\n", val);
}
else
pdebug("wa%02x\n", val);
}
break;
case PARA_REG_EPP_DATA:
if ((s->control & (PARA_CTR_DIR|PARA_CTR_SIGNAL)) != PARA_CTR_INIT)
/* Controls not correct for EPP data cycle, so do nothing */
pdebug("we%02x s\n", val);
else {
struct ParallelIOArg ioarg = { .buffer = &parm, .count = 1 };
if (qemu_chr_fe_ioctl(&s->chr, CHR_IOCTL_PP_EPP_WRITE, &ioarg)) {
s->epp_timeout = 1;
pdebug("we%02x t\n", val);
}
else
pdebug("we%02x\n", val);
}
break;
}
}
static void
parallel_ioport_eppdata_write_hw2(void *opaque, uint32_t addr, uint32_t val)
{
ParallelState *s = opaque;
uint16_t eppdata = cpu_to_le16(val);
int err;
struct ParallelIOArg ioarg = {
.buffer = &eppdata, .count = sizeof(eppdata)
};
trace_parallel_ioport_write("EPP", addr, val);
if ((s->control & (PARA_CTR_DIR|PARA_CTR_SIGNAL)) != PARA_CTR_INIT) {
/* Controls not correct for EPP data cycle, so do nothing */
pdebug("we%04x s\n", val);
return;
}
err = qemu_chr_fe_ioctl(&s->chr, CHR_IOCTL_PP_EPP_WRITE, &ioarg);
if (err) {
s->epp_timeout = 1;
pdebug("we%04x t\n", val);
}
else
pdebug("we%04x\n", val);
}
static void
parallel_ioport_eppdata_write_hw4(void *opaque, uint32_t addr, uint32_t val)
{
ParallelState *s = opaque;
uint32_t eppdata = cpu_to_le32(val);
int err;
struct ParallelIOArg ioarg = {
.buffer = &eppdata, .count = sizeof(eppdata)
};
trace_parallel_ioport_write("EPP", addr, val);
if ((s->control & (PARA_CTR_DIR|PARA_CTR_SIGNAL)) != PARA_CTR_INIT) {
/* Controls not correct for EPP data cycle, so do nothing */
pdebug("we%08x s\n", val);
return;
}
err = qemu_chr_fe_ioctl(&s->chr, CHR_IOCTL_PP_EPP_WRITE, &ioarg);
if (err) {
s->epp_timeout = 1;
pdebug("we%08x t\n", val);
}
else
pdebug("we%08x\n", val);
}
static uint32_t parallel_ioport_read_sw(void *opaque, uint32_t addr)
{
ParallelState *s = opaque;
uint32_t ret = 0xff;
addr &= 7;
switch(addr) {
case PARA_REG_DATA:
if (s->control & PARA_CTR_DIR)
ret = s->datar;
else
ret = s->dataw;
break;
case PARA_REG_STS:
ret = s->status;
s->irq_pending = 0;
if ((s->status & PARA_STS_BUSY) == 0 && (s->control & PARA_CTR_STROBE) == 0) {
/* XXX Fixme: wait 5 microseconds */
if (s->status & PARA_STS_ACK)
s->status &= ~PARA_STS_ACK;
else {
/* XXX Fixme: wait 5 microseconds */
s->status |= PARA_STS_ACK;
s->status |= PARA_STS_BUSY;
}
}
parallel_update_irq(s);
break;
case PARA_REG_CTR:
ret = s->control;
break;
}
trace_parallel_ioport_read("SW", addr, ret);
return ret;
}
static uint32_t parallel_ioport_read_hw(void *opaque, uint32_t addr)
{
ParallelState *s = opaque;
uint8_t ret = 0xff;
addr &= 7;
switch(addr) {
case PARA_REG_DATA:
qemu_chr_fe_ioctl(&s->chr, CHR_IOCTL_PP_READ_DATA, &ret);
if (s->last_read_offset != addr || s->datar != ret)
pdebug("rd%02x\n", ret);
s->datar = ret;
break;
case PARA_REG_STS:
qemu_chr_fe_ioctl(&s->chr, CHR_IOCTL_PP_READ_STATUS, &ret);
ret &= ~PARA_STS_TMOUT;
if (s->epp_timeout)
ret |= PARA_STS_TMOUT;
if (s->last_read_offset != addr || s->status != ret)
pdebug("rs%02x\n", ret);
s->status = ret;
break;
case PARA_REG_CTR:
/* s->control has some bits fixed to 1. It is zero only when
it has not been yet written to. */
if (s->control == 0) {
qemu_chr_fe_ioctl(&s->chr, CHR_IOCTL_PP_READ_CONTROL, &ret);
if (s->last_read_offset != addr)
pdebug("rc%02x\n", ret);
s->control = ret;
}
else {
ret = s->control;
if (s->last_read_offset != addr)
pdebug("rc%02x\n", ret);
}
break;
case PARA_REG_EPP_ADDR:
if ((s->control & (PARA_CTR_DIR | PARA_CTR_SIGNAL)) !=
(PARA_CTR_DIR | PARA_CTR_INIT))
/* Controls not correct for EPP addr cycle, so do nothing */
pdebug("ra%02x s\n", ret);
else {
struct ParallelIOArg ioarg = { .buffer = &ret, .count = 1 };
if (qemu_chr_fe_ioctl(&s->chr,
CHR_IOCTL_PP_EPP_READ_ADDR, &ioarg)) {
s->epp_timeout = 1;
pdebug("ra%02x t\n", ret);
}
else
pdebug("ra%02x\n", ret);
}
break;
case PARA_REG_EPP_DATA:
if ((s->control & (PARA_CTR_DIR | PARA_CTR_SIGNAL)) !=
(PARA_CTR_DIR | PARA_CTR_INIT))
/* Controls not correct for EPP data cycle, so do nothing */
pdebug("re%02x s\n", ret);
else {
struct ParallelIOArg ioarg = { .buffer = &ret, .count = 1 };
if (qemu_chr_fe_ioctl(&s->chr, CHR_IOCTL_PP_EPP_READ, &ioarg)) {
s->epp_timeout = 1;
pdebug("re%02x t\n", ret);
}
else
pdebug("re%02x\n", ret);
}
break;
}
trace_parallel_ioport_read("HW", addr, ret);
s->last_read_offset = addr;
return ret;
}
static uint32_t
parallel_ioport_eppdata_read_hw2(void *opaque, uint32_t addr)
{
ParallelState *s = opaque;
uint32_t ret;
uint16_t eppdata = ~0;
int err;
struct ParallelIOArg ioarg = {
.buffer = &eppdata, .count = sizeof(eppdata)
};
if ((s->control & (PARA_CTR_DIR|PARA_CTR_SIGNAL)) != (PARA_CTR_DIR|PARA_CTR_INIT)) {
/* Controls not correct for EPP data cycle, so do nothing */
pdebug("re%04x s\n", eppdata);
return eppdata;
}
err = qemu_chr_fe_ioctl(&s->chr, CHR_IOCTL_PP_EPP_READ, &ioarg);
ret = le16_to_cpu(eppdata);
if (err) {
s->epp_timeout = 1;
pdebug("re%04x t\n", ret);
}
else
pdebug("re%04x\n", ret);
trace_parallel_ioport_read("EPP", addr, ret);
return ret;
}
static uint32_t
parallel_ioport_eppdata_read_hw4(void *opaque, uint32_t addr)
{
ParallelState *s = opaque;
uint32_t ret;
uint32_t eppdata = ~0U;
int err;
struct ParallelIOArg ioarg = {
.buffer = &eppdata, .count = sizeof(eppdata)
};
if ((s->control & (PARA_CTR_DIR|PARA_CTR_SIGNAL)) != (PARA_CTR_DIR|PARA_CTR_INIT)) {
/* Controls not correct for EPP data cycle, so do nothing */
pdebug("re%08x s\n", eppdata);
return eppdata;
}
err = qemu_chr_fe_ioctl(&s->chr, CHR_IOCTL_PP_EPP_READ, &ioarg);
ret = le32_to_cpu(eppdata);
if (err) {
s->epp_timeout = 1;
pdebug("re%08x t\n", ret);
}
else
pdebug("re%08x\n", ret);
trace_parallel_ioport_read("EPP", addr, ret);
return ret;
}
static void parallel_ioport_ecp_write(void *opaque, uint32_t addr, uint32_t val)
{
trace_parallel_ioport_write("ECP", addr & 7, val);
pdebug("wecp%d=%02x\n", addr & 7, val);
}
static uint32_t parallel_ioport_ecp_read(void *opaque, uint32_t addr)
{
uint8_t ret = 0xff;
trace_parallel_ioport_read("ECP", addr & 7, ret);
pdebug("recp%d:%02x\n", addr & 7, ret);
return ret;
}
static void parallel_reset(void *opaque)
{
ParallelState *s = opaque;
s->datar = ~0;
s->dataw = ~0;
s->status = PARA_STS_BUSY;
s->status |= PARA_STS_ACK;
s->status |= PARA_STS_ONLINE;
s->status |= PARA_STS_ERROR;
s->status |= PARA_STS_TMOUT;
s->control = PARA_CTR_SELECT;
s->control |= PARA_CTR_INIT;
s->control |= 0xc0;
s->irq_pending = 0;
s->hw_driver = 0;
s->epp_timeout = 0;
s->last_read_offset = ~0U;
}
static const int isa_parallel_io[MAX_PARALLEL_PORTS] = { 0x378, 0x278, 0x3bc };
static const MemoryRegionPortio isa_parallel_portio_hw_list[] = {
{ 0, 8, 1,
.read = parallel_ioport_read_hw,
.write = parallel_ioport_write_hw },
{ 4, 1, 2,
.read = parallel_ioport_eppdata_read_hw2,
.write = parallel_ioport_eppdata_write_hw2 },
{ 4, 1, 4,
.read = parallel_ioport_eppdata_read_hw4,
.write = parallel_ioport_eppdata_write_hw4 },
{ 0x400, 8, 1,
.read = parallel_ioport_ecp_read,
.write = parallel_ioport_ecp_write },
PORTIO_END_OF_LIST(),
};
static const MemoryRegionPortio isa_parallel_portio_sw_list[] = {
{ 0, 8, 1,
.read = parallel_ioport_read_sw,
.write = parallel_ioport_write_sw },
PORTIO_END_OF_LIST(),
};
static const VMStateDescription vmstate_parallel_isa = {
.name = "parallel_isa",
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_UINT8(state.dataw, ISAParallelState),
VMSTATE_UINT8(state.datar, ISAParallelState),
VMSTATE_UINT8(state.status, ISAParallelState),
VMSTATE_UINT8(state.control, ISAParallelState),
VMSTATE_INT32(state.irq_pending, ISAParallelState),
VMSTATE_INT32(state.epp_timeout, ISAParallelState),
VMSTATE_END_OF_LIST()
}
};
static int parallel_can_receive(void *opaque)
{
return 1;
}
static void parallel_isa_realizefn(DeviceState *dev, Error **errp)
{
static int index;
ISADevice *isadev = ISA_DEVICE(dev);
ISAParallelState *isa = ISA_PARALLEL(dev);
ParallelState *s = &isa->state;
int base;
uint8_t dummy;
if (!qemu_chr_fe_backend_connected(&s->chr)) {
error_setg(errp, "Can't create parallel device, empty char device");
return;
}
if (isa->index == -1) {
isa->index = index;
}
if (isa->index >= MAX_PARALLEL_PORTS) {
error_setg(errp, "Max. supported number of parallel ports is %d.",
MAX_PARALLEL_PORTS);
return;
}
if (isa->iobase == -1) {
isa->iobase = isa_parallel_io[isa->index];
}
index++;
base = isa->iobase;
s->irq = isa_get_irq(isadev, isa->isairq);
qemu_register_reset(parallel_reset, s);
qemu_chr_fe_set_handlers(&s->chr, parallel_can_receive, NULL,
NULL, NULL, s, NULL, true);
if (qemu_chr_fe_ioctl(&s->chr, CHR_IOCTL_PP_READ_STATUS, &dummy) == 0) {
s->hw_driver = 1;
s->status = dummy;
}
isa_register_portio_list(isadev, &isa->portio_list, base,
(s->hw_driver
? &isa_parallel_portio_hw_list[0]
: &isa_parallel_portio_sw_list[0]),
s, "parallel");
}
static void parallel_isa_build_aml(AcpiDevAmlIf *adev, Aml *scope)
{
ISAParallelState *isa = ISA_PARALLEL(adev);
Aml *dev;
Aml *crs;
crs = aml_resource_template();
aml_append(crs, aml_io(AML_DECODE16, isa->iobase, isa->iobase, 0x08, 0x08));
aml_append(crs, aml_irq_no_flags(isa->isairq));
dev = aml_device("LPT%d", isa->index + 1);
aml_append(dev, aml_name_decl("_HID", aml_eisaid("PNP0400")));
aml_append(dev, aml_name_decl("_UID", aml_int(isa->index + 1)));
aml_append(dev, aml_name_decl("_STA", aml_int(0xf)));
aml_append(dev, aml_name_decl("_CRS", crs));
aml_append(scope, dev);
}
/* Memory mapped interface */
static uint64_t parallel_mm_readfn(void *opaque, hwaddr addr, unsigned size)
{
ParallelState *s = opaque;
return parallel_ioport_read_sw(s, addr >> s->it_shift) &
MAKE_64BIT_MASK(0, size * 8);
}
static void parallel_mm_writefn(void *opaque, hwaddr addr,
uint64_t value, unsigned size)
{
ParallelState *s = opaque;
parallel_ioport_write_sw(s, addr >> s->it_shift,
value & MAKE_64BIT_MASK(0, size * 8));
}
static const MemoryRegionOps parallel_mm_ops = {
.read = parallel_mm_readfn,
.write = parallel_mm_writefn,
.valid.min_access_size = 1,
.valid.max_access_size = 4,
.endianness = DEVICE_NATIVE_ENDIAN,
};
/* If fd is zero, it means that the parallel device uses the console */
bool parallel_mm_init(MemoryRegion *address_space,
hwaddr base, int it_shift, qemu_irq irq,
Chardev *chr)
{
ParallelState *s;
s = g_new0(ParallelState, 1);
s->irq = irq;
qemu_chr_fe_init(&s->chr, chr, &error_abort);
s->it_shift = it_shift;
qemu_register_reset(parallel_reset, s);
memory_region_init_io(&s->iomem, NULL, &parallel_mm_ops, s,
"parallel", 8 << it_shift);
memory_region_add_subregion(address_space, base, &s->iomem);
return true;
}
static const Property parallel_isa_properties[] = {
DEFINE_PROP_UINT32("index", ISAParallelState, index, -1),
DEFINE_PROP_UINT32("iobase", ISAParallelState, iobase, -1),
DEFINE_PROP_UINT32("irq", ISAParallelState, isairq, 7),
DEFINE_PROP_CHR("chardev", ISAParallelState, state.chr),
};
static void parallel_isa_class_initfn(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
AcpiDevAmlIfClass *adevc = ACPI_DEV_AML_IF_CLASS(klass);
dc->realize = parallel_isa_realizefn;
dc->vmsd = &vmstate_parallel_isa;
adevc->build_dev_aml = parallel_isa_build_aml;
device_class_set_props(dc, parallel_isa_properties);
set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
}
static const TypeInfo parallel_isa_info = {
.name = TYPE_ISA_PARALLEL,
.parent = TYPE_ISA_DEVICE,
.instance_size = sizeof(ISAParallelState),
.class_init = parallel_isa_class_initfn,
.interfaces = (const InterfaceInfo[]) {
{ TYPE_ACPI_DEV_AML_IF },
{ },
},
};
static void parallel_register_types(void)
{
type_register_static(&parallel_isa_info);
}
type_init(parallel_register_types)
+729
View File
@@ -0,0 +1,729 @@
/*
* Arm PrimeCell PL011 UART
*
* Copyright (c) 2006 CodeSourcery.
* Written by Paul Brook
*
* This code is licensed under the GPL.
*/
/*
* QEMU interface:
* + sysbus MMIO region 0: device registers
* + sysbus IRQ 0: UARTINTR (combined interrupt line)
* + sysbus IRQ 1: UARTRXINTR (receive FIFO interrupt line)
* + sysbus IRQ 2: UARTTXINTR (transmit FIFO interrupt line)
* + sysbus IRQ 3: UARTRTINTR (receive timeout interrupt line)
* + sysbus IRQ 4: UARTMSINTR (momem status interrupt line)
* + sysbus IRQ 5: UARTEINTR (error interrupt line)
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "hw/char/pl011.h"
#include "hw/core/irq.h"
#include "hw/core/sysbus.h"
#include "hw/core/qdev-clock.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-properties-system.h"
#include "migration/vmstate.h"
#include "chardev/char-fe.h"
#include "chardev/char-serial.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "trace.h"
DeviceState *pl011_create(hwaddr addr, qemu_irq irq, Chardev *chr)
{
DeviceState *dev;
SysBusDevice *s;
dev = qdev_new("pl011");
s = SYS_BUS_DEVICE(dev);
qdev_prop_set_chr(dev, "chardev", chr);
sysbus_realize_and_unref(s, &error_fatal);
sysbus_mmio_map(s, 0, addr);
sysbus_connect_irq(s, 0, irq);
return dev;
}
/* Flag Register, UARTFR */
#define PL011_FLAG_RI 0x100
#define PL011_FLAG_TXFE 0x80
#define PL011_FLAG_RXFF 0x40
#define PL011_FLAG_TXFF 0x20
#define PL011_FLAG_RXFE 0x10
#define PL011_FLAG_DCD 0x04
#define PL011_FLAG_DSR 0x02
#define PL011_FLAG_CTS 0x01
/* Data Register, UARTDR */
#define DR_BE (1 << 10)
/* Interrupt status bits in UARTRIS, UARTMIS, UARTIMSC */
#define INT_OE (1 << 10)
#define INT_BE (1 << 9)
#define INT_PE (1 << 8)
#define INT_FE (1 << 7)
#define INT_RT (1 << 6)
#define INT_TX (1 << 5)
#define INT_RX (1 << 4)
#define INT_DSR (1 << 3)
#define INT_DCD (1 << 2)
#define INT_CTS (1 << 1)
#define INT_RI (1 << 0)
#define INT_E (INT_OE | INT_BE | INT_PE | INT_FE)
#define INT_MS (INT_RI | INT_DSR | INT_DCD | INT_CTS)
/* Line Control Register, UARTLCR_H */
#define LCR_FEN (1 << 4)
#define LCR_BRK (1 << 0)
/* Control Register, UARTCR */
#define CR_OUT2 (1 << 13)
#define CR_OUT1 (1 << 12)
#define CR_RTS (1 << 11)
#define CR_DTR (1 << 10)
#define CR_RXE (1 << 9)
#define CR_TXE (1 << 8)
#define CR_LBE (1 << 7)
#define CR_UARTEN (1 << 0)
/* Integer Baud Rate Divider, UARTIBRD */
#define IBRD_MASK 0xffff
/* Fractional Baud Rate Divider, UARTFBRD */
#define FBRD_MASK 0x3f
static const unsigned char pl011_id_arm[8] =
{ 0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1 };
static const unsigned char pl011_id_luminary[8] =
{ 0x11, 0x00, 0x18, 0x01, 0x0d, 0xf0, 0x05, 0xb1 };
static const char *pl011_regname(hwaddr offset)
{
static const char *const rname[] = {
[0] = "DR", [1] = "RSR", [6] = "FR", [8] = "ILPR", [9] = "IBRD",
[10] = "FBRD", [11] = "LCRH", [12] = "CR", [13] = "IFLS", [14] = "IMSC",
[15] = "RIS", [16] = "MIS", [17] = "ICR", [18] = "DMACR",
};
unsigned idx = offset >> 2;
if (idx < ARRAY_SIZE(rname) && rname[idx]) {
return rname[idx];
}
if (idx >= 0x3f8 && idx <= 0x400) {
return "ID";
}
return "UNKN";
}
/* Which bits in the interrupt status matter for each outbound IRQ line ? */
static const uint32_t irqmask[] = {
INT_E | INT_MS | INT_RT | INT_TX | INT_RX, /* combined IRQ */
INT_RX,
INT_TX,
INT_RT,
INT_MS,
INT_E,
};
static void pl011_update(PL011State *s)
{
uint32_t flags;
int i;
flags = s->int_level & s->int_enabled;
trace_pl011_irq_state(flags != 0);
for (i = 0; i < ARRAY_SIZE(s->irq); i++) {
qemu_set_irq(s->irq[i], (flags & irqmask[i]) != 0);
}
}
static bool pl011_loopback_enabled(PL011State *s)
{
return !!(s->cr & CR_LBE);
}
static bool pl011_is_fifo_enabled(PL011State *s)
{
return (s->lcr & LCR_FEN) != 0;
}
static inline unsigned pl011_get_fifo_depth(PL011State *s)
{
/* Note: FIFO depth is expected to be power-of-2 */
return pl011_is_fifo_enabled(s) ? PL011_FIFO_DEPTH : 1;
}
static inline void pl011_reset_rx_fifo(PL011State *s)
{
s->read_count = 0;
s->read_pos = 0;
/* Reset FIFO flags */
s->flags &= ~PL011_FLAG_RXFF;
s->flags |= PL011_FLAG_RXFE;
}
static inline void pl011_reset_tx_fifo(PL011State *s)
{
/* Reset FIFO flags */
s->flags &= ~PL011_FLAG_TXFF;
s->flags |= PL011_FLAG_TXFE;
}
static void pl011_fifo_rx_put(void *opaque, uint32_t value)
{
PL011State *s = (PL011State *)opaque;
int slot;
unsigned pipe_depth;
pipe_depth = pl011_get_fifo_depth(s);
slot = (s->read_pos + s->read_count) & (pipe_depth - 1);
s->read_fifo[slot] = value;
s->read_count++;
s->flags &= ~PL011_FLAG_RXFE;
trace_pl011_fifo_rx_put(value, s->read_count, pipe_depth);
if (s->read_count == pipe_depth) {
trace_pl011_fifo_rx_full();
s->flags |= PL011_FLAG_RXFF;
}
if (s->read_count == s->read_trigger) {
s->int_level |= INT_RX;
pl011_update(s);
}
}
static void pl011_loopback_tx(PL011State *s, uint32_t value)
{
if (!pl011_loopback_enabled(s)) {
return;
}
/*
* Caveat:
*
* In real hardware, TX loopback happens at the serial-bit level
* and then reassembled by the RX logics back into bytes and placed
* into the RX fifo. That is, loopback happens after TX fifo.
*
* Because the real hardware TX fifo is time-drained at the frame
* rate governed by the configured serial format, some loopback
* bytes in TX fifo may still be able to get into the RX fifo
* that could be full at times while being drained at software
* pace.
*
* In such scenario, the RX draining pace is the major factor
* deciding which loopback bytes get into the RX fifo, unless
* hardware flow-control is enabled.
*
* For simplicity, the above described is not emulated.
*/
pl011_fifo_rx_put(s, value);
}
static void pl011_write_txdata(PL011State *s, uint8_t data)
{
if (!(s->cr & CR_UARTEN)) {
/*
* Only log this message once, not every time the guest outputs:
* otherwise we would flood the logs with this message, making
* harder to debug guests. (Some very popular guests like Linux
* don't actively enable the UART.)
*/
if (!s->logged_disabled_uart) {
qemu_log_mask(LOG_GUEST_ERROR,
"PL011 data written to disabled UART\n");
s->logged_disabled_uart = true;
}
}
if (!(s->cr & CR_TXE)) {
/*
* We don't bother with the only-log-once machinery for this check
* because TXE is enabled by default from PL011 reset, so there
* isn't likely to be existing in-the-wild guest code that trips
* over this one.
*/
qemu_log_mask(LOG_GUEST_ERROR,
"PL011 data written to disabled TX UART\n");
}
/*
* XXX this blocks entire thread. Rewrite to use
* qemu_chr_fe_write and background I/O callbacks
*/
qemu_chr_fe_write_all(&s->chr, &data, 1);
pl011_loopback_tx(s, data);
s->int_level |= INT_TX;
pl011_update(s);
}
static uint32_t pl011_read_rxdata(PL011State *s)
{
uint32_t c;
unsigned fifo_depth = pl011_get_fifo_depth(s);
s->flags &= ~PL011_FLAG_RXFF;
c = s->read_fifo[s->read_pos];
if (s->read_count > 0) {
s->read_count--;
s->read_pos = (s->read_pos + 1) & (fifo_depth - 1);
}
if (s->read_count == 0) {
s->flags |= PL011_FLAG_RXFE;
}
if (s->read_count == s->read_trigger - 1) {
s->int_level &= ~INT_RX;
}
trace_pl011_read_fifo(s->read_count, fifo_depth);
s->rsr = c >> 8;
pl011_update(s);
qemu_chr_fe_accept_input(&s->chr);
return c;
}
static uint64_t pl011_read(void *opaque, hwaddr offset,
unsigned size)
{
PL011State *s = (PL011State *)opaque;
uint64_t r;
switch (offset >> 2) {
case 0: /* UARTDR */
r = pl011_read_rxdata(s);
break;
case 1: /* UARTRSR */
r = s->rsr;
break;
case 6: /* UARTFR */
r = s->flags;
break;
case 8: /* UARTILPR */
r = s->ilpr;
break;
case 9: /* UARTIBRD */
r = s->ibrd;
break;
case 10: /* UARTFBRD */
r = s->fbrd;
break;
case 11: /* UARTLCR_H */
r = s->lcr;
break;
case 12: /* UARTCR */
r = s->cr;
break;
case 13: /* UARTIFLS */
r = s->ifl;
break;
case 14: /* UARTIMSC */
r = s->int_enabled;
break;
case 15: /* UARTRIS */
r = s->int_level;
break;
case 16: /* UARTMIS */
r = s->int_level & s->int_enabled;
break;
case 18: /* UARTDMACR */
r = s->dmacr;
break;
case 0x3f8 ... 0x400:
r = s->id[(offset - 0xfe0) >> 2];
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"pl011_read: Bad offset 0x%x\n", (int)offset);
r = 0;
break;
}
trace_pl011_read(offset, r, pl011_regname(offset));
return r;
}
static void pl011_set_read_trigger(PL011State *s)
{
#if 0
/* The docs say the RX interrupt is triggered when the FIFO exceeds
the threshold. However linux only reads the FIFO in response to an
interrupt. Triggering the interrupt when the FIFO is non-empty seems
to make things work. */
if (s->lcr & LCR_FEN)
s->read_trigger = (s->ifl >> 1) & 0x1c;
else
#endif
s->read_trigger = 1;
}
static unsigned int pl011_get_baudrate(const PL011State *s)
{
uint64_t clk;
if (s->ibrd == 0) {
return 0;
}
clk = clock_get_hz(s->clk);
return (clk / ((s->ibrd << 6) + s->fbrd)) << 2;
}
static void pl011_trace_baudrate_change(const PL011State *s)
{
trace_pl011_baudrate_change(pl011_get_baudrate(s),
clock_get_hz(s->clk),
s->ibrd, s->fbrd);
}
static void pl011_loopback_mdmctrl(PL011State *s)
{
uint32_t cr, fr, il;
if (!pl011_loopback_enabled(s)) {
return;
}
/*
* Loopback software-driven modem control outputs to modem status inputs:
* FR.RI <= CR.Out2
* FR.DCD <= CR.Out1
* FR.CTS <= CR.RTS
* FR.DSR <= CR.DTR
*
* The loopback happens immediately even if this call is triggered
* by setting only CR.LBE.
*
* CTS/RTS updates due to enabled hardware flow controls are not
* dealt with here.
*/
cr = s->cr;
fr = s->flags & ~(PL011_FLAG_RI | PL011_FLAG_DCD |
PL011_FLAG_DSR | PL011_FLAG_CTS);
fr |= (cr & CR_OUT2) ? PL011_FLAG_RI : 0;
fr |= (cr & CR_OUT1) ? PL011_FLAG_DCD : 0;
fr |= (cr & CR_RTS) ? PL011_FLAG_CTS : 0;
fr |= (cr & CR_DTR) ? PL011_FLAG_DSR : 0;
/* Change interrupts based on updated FR */
il = s->int_level & ~(INT_DSR | INT_DCD | INT_CTS | INT_RI);
il |= (fr & PL011_FLAG_DSR) ? INT_DSR : 0;
il |= (fr & PL011_FLAG_DCD) ? INT_DCD : 0;
il |= (fr & PL011_FLAG_CTS) ? INT_CTS : 0;
il |= (fr & PL011_FLAG_RI) ? INT_RI : 0;
s->flags = fr;
s->int_level = il;
pl011_update(s);
}
static void pl011_loopback_break(PL011State *s, int brk_enable)
{
if (brk_enable) {
pl011_loopback_tx(s, DR_BE);
}
}
static void pl011_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size)
{
PL011State *s = (PL011State *)opaque;
unsigned char ch;
trace_pl011_write(offset, value, pl011_regname(offset));
switch (offset >> 2) {
case 0: /* UARTDR */
ch = value;
pl011_write_txdata(s, ch);
break;
case 1: /* UARTRSR/UARTECR */
s->rsr = 0;
break;
case 6: /* UARTFR */
/* Writes to Flag register are ignored. */
break;
case 8: /* UARTILPR */
s->ilpr = value;
break;
case 9: /* UARTIBRD */
s->ibrd = value & IBRD_MASK;
pl011_trace_baudrate_change(s);
break;
case 10: /* UARTFBRD */
s->fbrd = value & FBRD_MASK;
pl011_trace_baudrate_change(s);
break;
case 11: /* UARTLCR_H */
/* Reset the FIFO state on FIFO enable or disable */
if ((s->lcr ^ value) & LCR_FEN) {
pl011_reset_rx_fifo(s);
pl011_reset_tx_fifo(s);
}
if ((s->lcr ^ value) & LCR_BRK) {
int break_enable = value & LCR_BRK;
qemu_chr_fe_ioctl(&s->chr, CHR_IOCTL_SERIAL_SET_BREAK,
&break_enable);
pl011_loopback_break(s, break_enable);
}
s->lcr = value;
pl011_set_read_trigger(s);
break;
case 12: /* UARTCR */
/* ??? Need to implement the enable bit. */
if ((s->cr ^ value) & CR_UARTEN) {
/* Re-arm the log warning when the guest toggles UARTEN */
s->logged_disabled_uart = false;
}
s->cr = value;
pl011_loopback_mdmctrl(s);
break;
case 13: /* UARTIFS */
s->ifl = value;
pl011_set_read_trigger(s);
break;
case 14: /* UARTIMSC */
s->int_enabled = value;
pl011_update(s);
break;
case 17: /* UARTICR */
s->int_level &= ~value;
pl011_update(s);
break;
case 18: /* UARTDMACR */
s->dmacr = value;
if (value & 3) {
qemu_log_mask(LOG_UNIMP, "pl011: DMA not implemented\n");
}
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"pl011_write: Bad offset 0x%x\n", (int)offset);
}
}
static int pl011_can_receive(void *opaque)
{
PL011State *s = (PL011State *)opaque;
unsigned fifo_depth = pl011_get_fifo_depth(s);
unsigned fifo_available = fifo_depth - s->read_count;
/*
* In theory we should check the UART and RX enable bits here and
* return 0 if they are not set (so the guest can't receive data
* until you have enabled the UART). In practice we suspect there
* is at least some guest code out there which has been tested only
* on QEMU and which never bothers to enable the UART because we
* historically never enforced that. So we effectively keep the
* UART continuously enabled regardless of the enable bits.
*/
trace_pl011_can_receive(s->lcr, s->read_count, fifo_depth, fifo_available);
return fifo_available;
}
static void pl011_receive(void *opaque, const uint8_t *buf, int size)
{
trace_pl011_receive(size);
/*
* In loopback mode, the RX input signal is internally disconnected
* from the entire receiving logics; thus, all inputs are ignored,
* and BREAK detection on RX input signal is also not performed.
*/
if (pl011_loopback_enabled(opaque)) {
return;
}
for (int i = 0; i < size; i++) {
pl011_fifo_rx_put(opaque, buf[i]);
}
}
static void pl011_event(void *opaque, QEMUChrEvent event)
{
if (event == CHR_EVENT_BREAK && !pl011_loopback_enabled(opaque)) {
pl011_fifo_rx_put(opaque, DR_BE);
}
}
static void pl011_clock_update(void *opaque, ClockEvent event)
{
PL011State *s = PL011(opaque);
pl011_trace_baudrate_change(s);
}
static const MemoryRegionOps pl011_ops = {
.read = pl011_read,
.write = pl011_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.impl.min_access_size = 4,
.impl.max_access_size = 4,
};
static bool pl011_clock_needed(void *opaque)
{
PL011State *s = PL011(opaque);
return s->migrate_clk;
}
static const VMStateDescription vmstate_pl011_clock = {
.name = "pl011/clock",
.version_id = 1,
.minimum_version_id = 1,
.needed = pl011_clock_needed,
.fields = (const VMStateField[]) {
VMSTATE_CLOCK(clk, PL011State),
VMSTATE_END_OF_LIST()
}
};
static int pl011_post_load(void *opaque, int version_id)
{
PL011State* s = opaque;
/* Sanity-check input state */
if (s->read_pos >= ARRAY_SIZE(s->read_fifo) ||
s->read_count > ARRAY_SIZE(s->read_fifo)) {
return -1;
}
if (!pl011_is_fifo_enabled(s) && s->read_count > 0 && s->read_pos > 0) {
/*
* Older versions of PL011 didn't ensure that the single
* character in the FIFO in FIFO-disabled mode is in
* element 0 of the array; convert to follow the current
* code's assumptions.
*/
s->read_fifo[0] = s->read_fifo[s->read_pos];
s->read_pos = 0;
}
s->ibrd &= IBRD_MASK;
s->fbrd &= FBRD_MASK;
return 0;
}
static const VMStateDescription vmstate_pl011 = {
.name = "pl011",
.version_id = 2,
.minimum_version_id = 2,
.post_load = pl011_post_load,
.fields = (const VMStateField[]) {
VMSTATE_UNUSED(sizeof(uint32_t)),
VMSTATE_UINT32(flags, PL011State),
VMSTATE_UINT32(lcr, PL011State),
VMSTATE_UINT32(rsr, PL011State),
VMSTATE_UINT32(cr, PL011State),
VMSTATE_UINT32(dmacr, PL011State),
VMSTATE_UINT32(int_enabled, PL011State),
VMSTATE_UINT32(int_level, PL011State),
VMSTATE_UINT32_ARRAY(read_fifo, PL011State, PL011_FIFO_DEPTH),
VMSTATE_UINT32(ilpr, PL011State),
VMSTATE_UINT32(ibrd, PL011State),
VMSTATE_UINT32(fbrd, PL011State),
VMSTATE_UINT32(ifl, PL011State),
VMSTATE_INT32(read_pos, PL011State),
VMSTATE_INT32(read_count, PL011State),
VMSTATE_INT32(read_trigger, PL011State),
VMSTATE_END_OF_LIST()
},
.subsections = (const VMStateDescription * const []) {
&vmstate_pl011_clock,
NULL
}
};
static const Property pl011_properties[] = {
DEFINE_PROP_CHR("chardev", PL011State, chr),
DEFINE_PROP_BOOL("migrate-clk", PL011State, migrate_clk, true),
};
static void pl011_init(Object *obj)
{
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
PL011State *s = PL011(obj);
int i;
memory_region_init_io(&s->iomem, OBJECT(s), &pl011_ops, s, "pl011", 0x1000);
sysbus_init_mmio(sbd, &s->iomem);
for (i = 0; i < ARRAY_SIZE(s->irq); i++) {
sysbus_init_irq(sbd, &s->irq[i]);
}
s->clk = qdev_init_clock_in(DEVICE(obj), "clk", pl011_clock_update, s,
ClockUpdate);
s->id = pl011_id_arm;
}
static void pl011_realize(DeviceState *dev, Error **errp)
{
PL011State *s = PL011(dev);
qemu_chr_fe_set_handlers(&s->chr, pl011_can_receive, pl011_receive,
pl011_event, NULL, s, NULL, true);
}
static void pl011_reset(DeviceState *dev)
{
PL011State *s = PL011(dev);
s->lcr = 0;
s->rsr = 0;
s->dmacr = 0;
s->int_enabled = 0;
s->int_level = 0;
s->ilpr = 0;
s->ibrd = 0;
s->fbrd = 0;
s->read_trigger = 1;
s->ifl = 0x12;
s->cr = 0x300;
s->flags = 0;
s->logged_disabled_uart = false;
pl011_reset_rx_fifo(s);
pl011_reset_tx_fifo(s);
}
static void pl011_class_init(ObjectClass *oc, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(oc);
dc->realize = pl011_realize;
device_class_set_legacy_reset(dc, pl011_reset);
dc->vmsd = &vmstate_pl011;
device_class_set_props(dc, pl011_properties);
}
static const TypeInfo pl011_arm_info = {
.name = TYPE_PL011,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(PL011State),
.instance_init = pl011_init,
.class_init = pl011_class_init,
};
static void pl011_luminary_init(Object *obj)
{
PL011State *s = PL011(obj);
s->id = pl011_id_luminary;
}
static const TypeInfo pl011_luminary_info = {
.name = TYPE_PL011_LUMINARY,
.parent = TYPE_PL011,
.instance_init = pl011_luminary_init,
};
static void pl011_register_types(void)
{
type_register_static(&pl011_arm_info);
type_register_static(&pl011_luminary_info);
}
type_init(pl011_register_types)
+350
View File
@@ -0,0 +1,350 @@
/*
* Renesas Serial Communication Interface
*
* Datasheet: RX62N Group, RX621 Group User's Manual: Hardware
* (Rev.1.40 R01UH0033EJ0140)
*
* Copyright (c) 2019 Yoshinori Sato
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2 or later, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "qemu/log.h"
#include "hw/core/irq.h"
#include "hw/core/registerfields.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-properties-system.h"
#include "hw/char/renesas_sci.h"
#include "migration/vmstate.h"
/* SCI register map */
REG8(SMR, 0)
FIELD(SMR, CKS, 0, 2)
FIELD(SMR, MP, 2, 1)
FIELD(SMR, STOP, 3, 1)
FIELD(SMR, PM, 4, 1)
FIELD(SMR, PE, 5, 1)
FIELD(SMR, CHR, 6, 1)
FIELD(SMR, CM, 7, 1)
REG8(BRR, 1)
REG8(SCR, 2)
FIELD(SCR, CKE, 0, 2)
FIELD(SCR, TEIE, 2, 1)
FIELD(SCR, MPIE, 3, 1)
FIELD(SCR, RE, 4, 1)
FIELD(SCR, TE, 5, 1)
FIELD(SCR, RIE, 6, 1)
FIELD(SCR, TIE, 7, 1)
REG8(TDR, 3)
REG8(SSR, 4)
FIELD(SSR, MPBT, 0, 1)
FIELD(SSR, MPB, 1, 1)
FIELD(SSR, TEND, 2, 1)
FIELD(SSR, ERR, 3, 3)
FIELD(SSR, PER, 3, 1)
FIELD(SSR, FER, 4, 1)
FIELD(SSR, ORER, 5, 1)
FIELD(SSR, RDRF, 6, 1)
FIELD(SSR, TDRE, 7, 1)
REG8(RDR, 5)
REG8(SCMR, 6)
FIELD(SCMR, SMIF, 0, 1)
FIELD(SCMR, SINV, 2, 1)
FIELD(SCMR, SDIR, 3, 1)
FIELD(SCMR, BCP2, 7, 1)
REG8(SEMR, 7)
FIELD(SEMR, ACS0, 0, 1)
FIELD(SEMR, ABCS, 4, 1)
static int can_receive(void *opaque)
{
RSCIState *sci = RSCI(opaque);
if (sci->rx_next > qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)) {
return 0;
} else {
return FIELD_EX8(sci->scr, SCR, RE);
}
}
static void receive(void *opaque, const uint8_t *buf, int size)
{
RSCIState *sci = RSCI(opaque);
sci->rx_next = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + sci->trtime;
if (FIELD_EX8(sci->ssr, SSR, RDRF) || size > 1) {
sci->ssr = FIELD_DP8(sci->ssr, SSR, ORER, 1);
if (FIELD_EX8(sci->scr, SCR, RIE)) {
qemu_set_irq(sci->irq[ERI], 1);
}
} else {
sci->rdr = buf[0];
sci->ssr = FIELD_DP8(sci->ssr, SSR, RDRF, 1);
if (FIELD_EX8(sci->scr, SCR, RIE)) {
qemu_irq_pulse(sci->irq[RXI]);
}
}
}
static void send_byte(RSCIState *sci)
{
if (qemu_chr_fe_backend_connected(&sci->chr)) {
qemu_chr_fe_write_all(&sci->chr, &sci->tdr, 1);
}
timer_mod(&sci->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + sci->trtime);
sci->ssr = FIELD_DP8(sci->ssr, SSR, TEND, 0);
sci->ssr = FIELD_DP8(sci->ssr, SSR, TDRE, 1);
qemu_set_irq(sci->irq[TEI], 0);
if (FIELD_EX8(sci->scr, SCR, TIE)) {
qemu_irq_pulse(sci->irq[TXI]);
}
}
static void txend(void *opaque)
{
RSCIState *sci = RSCI(opaque);
if (!FIELD_EX8(sci->ssr, SSR, TDRE)) {
send_byte(sci);
} else {
sci->ssr = FIELD_DP8(sci->ssr, SSR, TEND, 1);
if (FIELD_EX8(sci->scr, SCR, TEIE)) {
qemu_set_irq(sci->irq[TEI], 1);
}
}
}
static void update_trtime(RSCIState *sci)
{
/* char per bits */
sci->trtime = 8 - FIELD_EX8(sci->smr, SMR, CHR);
sci->trtime += FIELD_EX8(sci->smr, SMR, PE);
sci->trtime += FIELD_EX8(sci->smr, SMR, STOP) + 1;
/* x bit transmit time (32 * divrate * brr) / base freq */
sci->trtime *= 32 * sci->brr;
sci->trtime *= 1 << (2 * FIELD_EX8(sci->smr, SMR, CKS));
sci->trtime *= NANOSECONDS_PER_SECOND;
sci->trtime /= sci->input_freq;
}
static bool sci_is_tr_enabled(RSCIState *sci)
{
return FIELD_EX8(sci->scr, SCR, TE) || FIELD_EX8(sci->scr, SCR, RE);
}
static void sci_write(void *opaque, hwaddr offset, uint64_t val, unsigned size)
{
RSCIState *sci = RSCI(opaque);
switch (offset) {
case A_SMR:
if (!sci_is_tr_enabled(sci)) {
sci->smr = val;
update_trtime(sci);
}
break;
case A_BRR:
if (!sci_is_tr_enabled(sci)) {
sci->brr = val;
update_trtime(sci);
}
break;
case A_SCR:
sci->scr = val;
if (FIELD_EX8(sci->scr, SCR, TE)) {
sci->ssr = FIELD_DP8(sci->ssr, SSR, TDRE, 1);
sci->ssr = FIELD_DP8(sci->ssr, SSR, TEND, 1);
if (FIELD_EX8(sci->scr, SCR, TIE)) {
qemu_irq_pulse(sci->irq[TXI]);
}
}
if (!FIELD_EX8(sci->scr, SCR, TEIE)) {
qemu_set_irq(sci->irq[TEI], 0);
}
if (!FIELD_EX8(sci->scr, SCR, RIE)) {
qemu_set_irq(sci->irq[ERI], 0);
}
break;
case A_TDR:
sci->tdr = val;
if (FIELD_EX8(sci->ssr, SSR, TEND)) {
send_byte(sci);
} else {
sci->ssr = FIELD_DP8(sci->ssr, SSR, TDRE, 0);
}
break;
case A_SSR:
sci->ssr = FIELD_DP8(sci->ssr, SSR, MPBT,
FIELD_EX8(val, SSR, MPBT));
sci->ssr = FIELD_DP8(sci->ssr, SSR, ERR,
FIELD_EX8(val, SSR, ERR) & 0x07);
if (FIELD_EX8(sci->read_ssr, SSR, ERR) &&
FIELD_EX8(sci->ssr, SSR, ERR) == 0) {
qemu_set_irq(sci->irq[ERI], 0);
}
break;
case A_RDR:
qemu_log_mask(LOG_GUEST_ERROR, "reneas_sci: RDR is read only.\n");
break;
case A_SCMR:
sci->scmr = val; break;
case A_SEMR: /* SEMR */
sci->semr = val; break;
default:
qemu_log_mask(LOG_UNIMP, "renesas_sci: Register 0x%" HWADDR_PRIX " "
"not implemented\n",
offset);
}
}
static uint64_t sci_read(void *opaque, hwaddr offset, unsigned size)
{
RSCIState *sci = RSCI(opaque);
switch (offset) {
case A_SMR:
return sci->smr;
case A_BRR:
return sci->brr;
case A_SCR:
return sci->scr;
case A_TDR:
return sci->tdr;
case A_SSR:
sci->read_ssr = sci->ssr;
return sci->ssr;
case A_RDR:
sci->ssr = FIELD_DP8(sci->ssr, SSR, RDRF, 0);
return sci->rdr;
case A_SCMR:
return sci->scmr;
case A_SEMR:
return sci->semr;
default:
qemu_log_mask(LOG_UNIMP, "renesas_sci: Register 0x%" HWADDR_PRIX
" not implemented.\n", offset);
}
return UINT64_MAX;
}
static const MemoryRegionOps sci_ops = {
.write = sci_write,
.read = sci_read,
.endianness = DEVICE_NATIVE_ENDIAN,
.impl.max_access_size = 1,
.valid.max_access_size = 1,
};
static void rsci_reset(DeviceState *dev)
{
RSCIState *sci = RSCI(dev);
sci->smr = sci->scr = 0x00;
sci->brr = 0xff;
sci->tdr = 0xff;
sci->rdr = 0x00;
sci->ssr = 0x84;
sci->scmr = 0x00;
sci->semr = 0x00;
sci->rx_next = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
}
static void sci_event(void *opaque, QEMUChrEvent event)
{
RSCIState *sci = RSCI(opaque);
if (event == CHR_EVENT_BREAK) {
sci->ssr = FIELD_DP8(sci->ssr, SSR, FER, 1);
if (FIELD_EX8(sci->scr, SCR, RIE)) {
qemu_set_irq(sci->irq[ERI], 1);
}
}
}
static void rsci_realize(DeviceState *dev, Error **errp)
{
RSCIState *sci = RSCI(dev);
if (sci->input_freq == 0) {
qemu_log_mask(LOG_GUEST_ERROR,
"renesas_sci: input-freq property must be set.");
return;
}
qemu_chr_fe_set_handlers(&sci->chr, can_receive, receive,
sci_event, NULL, sci, NULL, true);
}
static void rsci_init(Object *obj)
{
SysBusDevice *d = SYS_BUS_DEVICE(obj);
RSCIState *sci = RSCI(obj);
int i;
memory_region_init_io(&sci->memory, OBJECT(sci), &sci_ops,
sci, "renesas-sci", 0x8);
sysbus_init_mmio(d, &sci->memory);
for (i = 0; i < SCI_NR_IRQ; i++) {
sysbus_init_irq(d, &sci->irq[i]);
}
timer_init_ns(&sci->timer, QEMU_CLOCK_VIRTUAL, txend, sci);
}
static const VMStateDescription vmstate_rsci = {
.name = "renesas-sci",
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_INT64(trtime, RSCIState),
VMSTATE_INT64(rx_next, RSCIState),
VMSTATE_UINT8(smr, RSCIState),
VMSTATE_UINT8(brr, RSCIState),
VMSTATE_UINT8(scr, RSCIState),
VMSTATE_UINT8(tdr, RSCIState),
VMSTATE_UINT8(ssr, RSCIState),
VMSTATE_UINT8(rdr, RSCIState),
VMSTATE_UINT8(scmr, RSCIState),
VMSTATE_UINT8(semr, RSCIState),
VMSTATE_UINT8(read_ssr, RSCIState),
VMSTATE_TIMER(timer, RSCIState),
VMSTATE_END_OF_LIST()
}
};
static const Property rsci_properties[] = {
DEFINE_PROP_UINT64("input-freq", RSCIState, input_freq, 0),
DEFINE_PROP_CHR("chardev", RSCIState, chr),
};
static void rsci_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = rsci_realize;
dc->vmsd = &vmstate_rsci;
device_class_set_legacy_reset(dc, rsci_reset);
device_class_set_props(dc, rsci_properties);
}
static const TypeInfo rsci_info = {
.name = TYPE_RENESAS_SCI,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(RSCIState),
.instance_init = rsci_init,
.class_init = rsci_class_init,
};
static void rsci_register_types(void)
{
type_register_static(&rsci_info);
}
type_init(rsci_register_types)
+368
View File
@@ -0,0 +1,368 @@
/*
* QEMU RISC-V Host Target Interface (HTIF) Emulation
*
* Copyright (c) 2016-2017 Sagar Karandikar, [email protected]
* Copyright (c) 2017-2018 SiFive, Inc.
*
* This provides HTIF device emulation for QEMU. At the moment this allows
* for identical copies of bbl/linux to run on both spike and QEMU.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2 or later, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qemu/log.h"
#include "hw/char/riscv_htif.h"
#include "chardev/char.h"
#include "chardev/char-fe.h"
#include "qemu/timer.h"
#include "qemu/error-report.h"
#include "system/address-spaces.h"
#include "system/dma.h"
#include "system/physmem.h"
#include "system/runstate.h"
#include "exec/cpu-common.h"
#include "trace.h"
#define HTIF_DEV_SHIFT 56
#define HTIF_CMD_SHIFT 48
#define HTIF_DEV_SYSTEM 0
#define HTIF_DEV_CONSOLE 1
#define HTIF_SYSTEM_CMD_SYSCALL 0
#define HTIF_CONSOLE_CMD_GETC 0
#define HTIF_CONSOLE_CMD_PUTC 1
/* PK system call number */
#define PK_SYS_WRITE 64
const char *sig_file;
uint8_t line_size = 16;
static uint64_t fromhost_addr, tohost_addr, begin_sig_addr, end_sig_addr;
void htif_symbol_callback(const char *st_name, int st_info, uint64_t st_value,
uint64_t st_size)
{
if (strcmp("fromhost", st_name) == 0) {
fromhost_addr = st_value;
if (st_size != 8) {
error_report("HTIF fromhost must be 8 bytes");
exit(1);
}
} else if (strcmp("tohost", st_name) == 0) {
tohost_addr = st_value;
if (st_size != 8) {
error_report("HTIF tohost must be 8 bytes");
exit(1);
}
} else if (strcmp("begin_signature", st_name) == 0) {
begin_sig_addr = st_value;
} else if (strcmp("end_signature", st_name) == 0) {
end_sig_addr = st_value;
}
}
/*
* Called by the char dev to see if HTIF is ready to accept input.
*/
static int htif_can_recv(void *opaque)
{
return 1;
}
/*
* Called by the char dev to supply input to HTIF console.
* We assume that we will receive one character at a time.
*/
static void htif_recv(void *opaque, const uint8_t *buf, int size)
{
HTIFState *s = opaque;
if (size != 1) {
return;
}
/*
* TODO - we need to check whether mfromhost is zero which indicates
* the device is ready to receive. The current implementation
* will drop characters
*/
uint64_t val_written = s->pending_read;
uint64_t resp = 0x100 | *buf;
s->fromhost = (val_written >> 48 << 48) | (resp << 16 >> 16);
}
/*
* Called by the char dev to supply special events to the HTIF console.
* Not used for HTIF.
*/
static void htif_event(void *opaque, QEMUChrEvent event)
{
}
static int htif_be_change(void *opaque)
{
HTIFState *s = opaque;
qemu_chr_fe_set_handlers(&s->chr, htif_can_recv, htif_recv, htif_event,
htif_be_change, s, NULL, true);
return 0;
}
/*
* See below the tohost register format.
*
* Bits 63:56 indicate the "device".
* Bits 55:48 indicate the "command".
*
* Device 0 is the syscall device, which is used to emulate Unixy syscalls.
* It only implements command 0, which has two subfunctions:
* - If bit 0 is clear, then bits 47:0 represent a pointer to a struct
* describing the syscall.
* - If bit 1 is set, then bits 47:1 represent an exit code, with a zero
* value indicating success and other values indicating failure.
*
* Device 1 is the blocking character device.
* - Command 0 reads a character
* - Command 1 writes a character from the 8 LSBs of tohost
*
* For RV32, the tohost register is zero-extended, so only device=0 and
* command=0 (i.e. HTIF syscalls/exit codes) are supported.
*/
static void htif_handle_tohost_write(HTIFState *s, uint64_t val_written)
{
uint8_t device = val_written >> HTIF_DEV_SHIFT;
uint8_t cmd = val_written >> HTIF_CMD_SHIFT;
uint64_t payload = val_written & 0xFFFFFFFFFFFFULL;
int resp = 0;
trace_htif_uart_write_to_host(device, cmd, payload);
/*
* Currently, there is a fixed mapping of devices:
* 0: riscv-tests Pass/Fail Reporting Only (no syscall proxy)
* 1: Console
*/
if (unlikely(device == HTIF_DEV_SYSTEM)) {
/* frontend syscall handler, shutdown and exit code support */
if (cmd == HTIF_SYSTEM_CMD_SYSCALL) {
if (payload & 0x1) {
/* exit code */
int exit_code = payload >> 1;
/*
* Dump signature data if sig_file is specified and
* begin/end_signature symbols exist.
*/
if (sig_file && begin_sig_addr && end_sig_addr) {
if (end_sig_addr <= begin_sig_addr) {
error_report("Invalid HTIF signature range:"
" begin=0x%" PRIx64 " end=0x%" PRIx64,
begin_sig_addr, end_sig_addr);
return;
}
uint64_t sig_len = end_sig_addr - begin_sig_addr;
char *sig_data = g_malloc(sig_len);
dma_memory_read(&address_space_memory, begin_sig_addr,
sig_data, sig_len, MEMTXATTRS_UNSPECIFIED);
FILE *signature = fopen(sig_file, "w");
if (signature == NULL) {
error_report("Unable to open %s with error %s",
sig_file, strerror(errno));
exit(1);
}
for (int i = 0; i < sig_len; i += line_size) {
for (int j = line_size; j > 0; j--) {
if (i + j <= sig_len) {
fprintf(signature, "%02x",
sig_data[i + j - 1] & 0xff);
} else {
fprintf(signature, "%02x", 0);
}
}
fprintf(signature, "\n");
}
fclose(signature);
g_free(sig_data);
}
qemu_system_shutdown_request_with_code(
SHUTDOWN_CAUSE_GUEST_SHUTDOWN, exit_code);
return;
} else {
uint64_t syscall[8];
physical_memory_read(payload, syscall, sizeof(syscall));
if (le64_to_cpu(syscall[0]) == PK_SYS_WRITE &&
le64_to_cpu(syscall[1]) == HTIF_DEV_CONSOLE &&
le64_to_cpu(syscall[3]) == HTIF_CONSOLE_CMD_PUTC) {
uint8_t ch;
physical_memory_read(le64_to_cpu(syscall[2]), &ch, 1);
/*
* XXX this blocks entire thread. Rewrite to use
* qemu_chr_fe_write and background I/O callbacks
*/
qemu_chr_fe_write_all(&s->chr, &ch, 1);
resp = 0x100 | (uint8_t)payload;
} else {
qemu_log_mask(LOG_UNIMP,
"pk syscall proxy not supported\n");
}
}
} else {
qemu_log("HTIF device %d: unknown command\n", device);
}
} else if (likely(device == HTIF_DEV_CONSOLE)) {
/* HTIF Console */
if (cmd == HTIF_CONSOLE_CMD_GETC) {
/* this should be a queue, but not yet implemented as such */
s->pending_read = val_written;
s->tohost = 0; /* clear to indicate we read */
return;
} else if (cmd == HTIF_CONSOLE_CMD_PUTC) {
uint8_t ch = (uint8_t)payload;
/*
* XXX this blocks entire thread. Rewrite to use
* qemu_chr_fe_write and background I/O callbacks
*/
qemu_chr_fe_write_all(&s->chr, &ch, 1);
resp = 0x100 | (uint8_t)payload;
} else {
qemu_log("HTIF device %d: unknown command\n", device);
}
} else {
qemu_log("HTIF unknown device or command\n");
trace_htif_uart_unknown_device_command(device, cmd, payload);
}
/*
* Latest bbl does not set fromhost to 0 if there is a value in tohost.
* With this code enabled, qemu hangs waiting for fromhost to go to 0.
* With this code disabled, qemu works with bbl priv v1.9.1 and v1.10.
* HTIF needs protocol documentation and a more complete state machine.
*
* while (!s->fromhost_inprogress &&
* s->fromhost != 0x0) {
* }
*/
s->fromhost = (val_written >> 48 << 48) | (resp << 16 >> 16);
s->tohost = 0; /* clear to indicate we read */
}
#define TOHOST_OFFSET1 (s->tohost_offset)
#define TOHOST_OFFSET2 (s->tohost_offset + 4)
#define FROMHOST_OFFSET1 (s->fromhost_offset)
#define FROMHOST_OFFSET2 (s->fromhost_offset + 4)
/* CPU wants to read an HTIF register */
static uint64_t htif_mm_read(void *opaque, hwaddr addr, unsigned size)
{
HTIFState *s = opaque;
if (addr == TOHOST_OFFSET1) {
return s->tohost & 0xFFFFFFFF;
} else if (addr == TOHOST_OFFSET2) {
return (s->tohost >> 32) & 0xFFFFFFFF;
} else if (addr == FROMHOST_OFFSET1) {
return s->fromhost & 0xFFFFFFFF;
} else if (addr == FROMHOST_OFFSET2) {
return (s->fromhost >> 32) & 0xFFFFFFFF;
} else {
qemu_log("Invalid htif read: address %016" PRIx64 "\n",
(uint64_t)addr);
return 0;
}
}
/* CPU wrote to an HTIF register */
static void htif_mm_write(void *opaque, hwaddr addr,
uint64_t value, unsigned size)
{
HTIFState *s = opaque;
if (addr == TOHOST_OFFSET1) {
if (s->tohost == 0x0) {
s->allow_tohost = 1;
s->tohost = value & 0xFFFFFFFF;
} else {
s->allow_tohost = 0;
}
} else if (addr == TOHOST_OFFSET2) {
if (s->allow_tohost) {
s->tohost |= value << 32;
htif_handle_tohost_write(s, s->tohost);
}
} else if (addr == FROMHOST_OFFSET1) {
s->fromhost_inprogress = 1;
s->fromhost = value & 0xFFFFFFFF;
} else if (addr == FROMHOST_OFFSET2) {
s->fromhost |= value << 32;
s->fromhost_inprogress = 0;
} else {
qemu_log("Invalid htif write: address %016" PRIx64 "\n",
(uint64_t)addr);
}
}
static const MemoryRegionOps htif_mm_ops = {
.read = htif_mm_read,
.write = htif_mm_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.impl = {
.min_access_size = 4,
.max_access_size = 4,
},
};
HTIFState *htif_mm_init(MemoryRegion *address_space, Chardev *chr,
uint64_t nonelf_base, bool custom_base)
{
uint64_t base, size, tohost_offset, fromhost_offset;
if (custom_base) {
fromhost_addr = nonelf_base;
tohost_addr = nonelf_base + 8;
} else {
if (!fromhost_addr || !tohost_addr) {
error_report("Invalid HTIF fromhost or tohost address");
exit(1);
}
}
base = MIN(tohost_addr, fromhost_addr);
size = MAX(tohost_addr + 8, fromhost_addr + 8) - base;
tohost_offset = tohost_addr - base;
fromhost_offset = fromhost_addr - base;
HTIFState *s = g_new0(HTIFState, 1);
s->tohost_offset = tohost_offset;
s->fromhost_offset = fromhost_offset;
s->pending_read = 0;
s->allow_tohost = 0;
s->fromhost_inprogress = 0;
qemu_chr_fe_init(&s->chr, chr, &error_abort);
qemu_chr_fe_set_handlers(&s->chr, htif_can_recv, htif_recv, htif_event,
htif_be_change, s, NULL, true);
memory_region_init_io(&s->mmio, NULL, &htif_mm_ops, s,
TYPE_HTIF_UART, size);
memory_region_add_subregion_overlap(address_space, base,
&s->mmio, 1);
return s;
}
+373
View File
@@ -0,0 +1,373 @@
/*
* SCLP event types
* Operations Command - Line Mode input
* Message - Line Mode output
*
* Copyright IBM, Corp. 2013
*
* Authors:
* Heinz Graalfs <[email protected]>
*
* This work is licensed under the terms of the GNU GPL, version 2 or (at your
* option) any later version. See the COPYING file in the top-level directory.
*
*/
#include "qemu/osdep.h"
#include "qemu/thread.h"
#include "qemu/error-report.h"
#include "qemu/module.h"
#include "chardev/char-fe.h"
#include "hw/s390x/sclp.h"
#include "migration/vmstate.h"
#include "hw/s390x/event-facility.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-properties-system.h"
#include "hw/s390x/ebcdic.h"
#include "qom/object.h"
#define SIZE_BUFFER 4096
#define NEWLINE "\n"
typedef struct OprtnsCommand {
EventBufferHeader header;
MDMSU message_unit;
char data[];
} QEMU_PACKED OprtnsCommand;
/* max size for line-mode data in 4K SCCB page */
#define SIZE_CONSOLE_BUFFER (SCCB_DATA_LEN - sizeof(OprtnsCommand))
struct SCLPConsoleLM {
SCLPEvent event;
CharFrontend chr;
bool echo; /* immediate echo of input if true */
uint32_t write_errors; /* errors writing to char layer */
uint32_t length; /* length of byte stream in buffer */
uint8_t buf[SIZE_CONSOLE_BUFFER];
};
typedef struct SCLPConsoleLM SCLPConsoleLM;
#define TYPE_SCLPLM_CONSOLE "sclplmconsole"
DECLARE_INSTANCE_CHECKER(SCLPConsoleLM, SCLPLM_CONSOLE,
TYPE_SCLPLM_CONSOLE)
/*
* Character layer call-back functions
*
* Allow 1 character at a time
*
* Accumulate bytes from character layer in console buffer,
* event_pending is set when a newline character is encountered
*
* The maximum command line length is limited by the maximum
* space available in an SCCB. Line mode console input is sent
* truncated to the guest in case it doesn't fit into the SCCB.
*/
static int chr_can_read(void *opaque)
{
SCLPConsoleLM *scon = opaque;
if (scon->event.event_pending) {
return 0;
}
return 1;
}
static void chr_read(void *opaque, const uint8_t *buf, int size)
{
SCLPConsoleLM *scon = opaque;
assert(size == 1);
if (*buf == '\r' || *buf == '\n') {
scon->event.event_pending = true;
sclp_service_interrupt(0);
return;
}
if (scon->length == SIZE_CONSOLE_BUFFER) {
/* Eat the character, but still process CR and LF. */
return;
}
scon->buf[scon->length] = *buf;
scon->length += 1;
if (scon->echo) {
/* XXX this blocks entire thread. Rewrite to use
* qemu_chr_fe_write and background I/O callbacks */
qemu_chr_fe_write_all(&scon->chr, buf, size);
}
}
/* functions to be called by event facility */
static bool can_handle_event(uint8_t type)
{
return type == SCLP_EVENT_MESSAGE || type == SCLP_EVENT_PMSGCMD;
}
static sccb_mask_t send_mask(void)
{
return SCLP_EVENT_MASK_OP_CMD | SCLP_EVENT_MASK_PMSGCMD;
}
static sccb_mask_t receive_mask(void)
{
return SCLP_EVENT_MASK_MSG | SCLP_EVENT_MASK_PMSGCMD;
}
/*
* Triggered by SCLP's read_event_data
* - convert ASCII byte stream to EBCDIC and
* - copy converted data into provided (SCLP) buffer
*/
static int get_console_data(SCLPEvent *event, uint8_t *buf, size_t *size,
int avail)
{
int len;
SCLPConsoleLM *cons = SCLPLM_CONSOLE(event);
len = cons->length;
/* data need to fit into provided SCLP buffer */
if (len > avail) {
return 1;
}
ebcdic_put(buf, (char *)&cons->buf, len);
*size = len;
cons->length = 0;
/* data provided and no more data pending */
event->event_pending = false;
qemu_notify_event();
return 0;
}
static int read_event_data(SCLPEvent *event, EventBufferHeader *evt_buf_hdr,
int *slen)
{
int avail, rc;
size_t src_len;
uint8_t *to;
OprtnsCommand *oc = (OprtnsCommand *) evt_buf_hdr;
if (!event->event_pending) {
/* no data pending */
return 0;
}
to = (uint8_t *)&oc->data;
avail = *slen - sizeof(OprtnsCommand);
rc = get_console_data(event, to, &src_len, avail);
if (rc) {
/* data didn't fit, try next SCCB */
return 1;
}
oc->message_unit.mdmsu.gds_id = GDS_ID_MDSMU;
oc->message_unit.mdmsu.length = cpu_to_be16(sizeof(struct MDMSU));
oc->message_unit.cpmsu.gds_id = GDS_ID_CPMSU;
oc->message_unit.cpmsu.length =
cpu_to_be16(sizeof(struct MDMSU) - sizeof(GdsVector));
oc->message_unit.text_command.gds_id = GDS_ID_TEXTCMD;
oc->message_unit.text_command.length =
cpu_to_be16(sizeof(struct MDMSU) - (2 * sizeof(GdsVector)));
oc->message_unit.self_def_text_message.key = GDS_KEY_SELFDEFTEXTMSG;
oc->message_unit.self_def_text_message.length =
cpu_to_be16(sizeof(struct MDMSU) - (3 * sizeof(GdsVector)));
oc->message_unit.text_message.key = GDS_KEY_TEXTMSG;
oc->message_unit.text_message.length =
cpu_to_be16(sizeof(GdsSubvector) + src_len);
oc->header.length = cpu_to_be16(sizeof(OprtnsCommand) + src_len);
oc->header.type = SCLP_EVENT_OPRTNS_COMMAND;
*slen = avail - src_len;
return 1;
}
/*
* Triggered by SCLP's write_event_data
* - write console data to character layer
* returns < 0 if an error occurred
*/
static int write_console_data(SCLPEvent *event, const uint8_t *buf, int len)
{
SCLPConsoleLM *scon = SCLPLM_CONSOLE(event);
if (!qemu_chr_fe_backend_connected(&scon->chr)) {
/* If there's no backend, we can just say we consumed all data. */
return len;
}
/* XXX this blocks entire thread. Rewrite to use
* qemu_chr_fe_write and background I/O callbacks */
return qemu_chr_fe_write_all(&scon->chr, buf, len);
}
static int process_mdb(SCLPEvent *event, MDBO *mdbo)
{
int rc;
int len;
QEMU_UNINITIALIZED uint8_t buffer[SIZE_BUFFER];
len = be16_to_cpu(mdbo->length);
len -= sizeof(mdbo->length) + sizeof(mdbo->type)
+ sizeof(mdbo->mto.line_type_flags)
+ sizeof(mdbo->mto.alarm_control)
+ sizeof(mdbo->mto._reserved);
assert(len <= SIZE_BUFFER);
/* convert EBCDIC SCLP contents to ASCII console message */
ascii_put(buffer, mdbo->mto.message, len);
rc = write_console_data(event, (uint8_t *)NEWLINE, 1);
if (rc < 0) {
return rc;
}
return write_console_data(event, buffer, len);
}
static int write_event_data(SCLPEvent *event, EventBufferHeader *ebh)
{
int len;
int written;
int errors = 0;
MDBO *mdbo;
SclpMsg *data = (SclpMsg *) ebh;
SCLPConsoleLM *scon = SCLPLM_CONSOLE(event);
len = be16_to_cpu(data->mdb.header.length);
if (len < sizeof(data->mdb.header) ||
len > be16_to_cpu(data->header.length) - sizeof(EventBufferHeader)) {
return SCLP_RC_INCONSISTENT_LENGTHS;
}
len -= sizeof(data->mdb.header);
/* first check message buffers */
mdbo = data->mdb.mdbo;
while (len > 0) {
if (be16_to_cpu(mdbo->length) > len
|| be16_to_cpu(mdbo->length) == 0) {
return SCLP_RC_INCONSISTENT_LENGTHS;
}
len -= be16_to_cpu(mdbo->length);
mdbo = (void *) mdbo + be16_to_cpu(mdbo->length);
}
/* then execute */
len = be16_to_cpu(data->mdb.header.length) - sizeof(data->mdb.header);
mdbo = data->mdb.mdbo;
while (len > 0) {
switch (be16_to_cpu(mdbo->type)) {
case MESSAGE_TEXT:
/* message text object */
written = process_mdb(event, mdbo);
if (written < 0) {
/* character layer error */
errors++;
}
break;
default: /* ignore */
break;
}
len -= be16_to_cpu(mdbo->length);
mdbo = (void *) mdbo + be16_to_cpu(mdbo->length);
}
if (errors) {
scon->write_errors += errors;
}
data->header.flags = SCLP_EVENT_BUFFER_ACCEPTED;
return SCLP_RC_NORMAL_COMPLETION;
}
/* functions for live migration */
static const VMStateDescription vmstate_sclplmconsole = {
.name = "sclplmconsole",
.version_id = 0,
.minimum_version_id = 0,
.fields = (const VMStateField[]) {
VMSTATE_BOOL(event.event_pending, SCLPConsoleLM),
VMSTATE_UINT32(write_errors, SCLPConsoleLM),
VMSTATE_UINT32(length, SCLPConsoleLM),
VMSTATE_UINT8_ARRAY(buf, SCLPConsoleLM, SIZE_CONSOLE_BUFFER),
VMSTATE_END_OF_LIST()
}
};
/* qemu object creation and initialization functions */
/* tell character layer our call-back functions */
static int console_init(SCLPEvent *event)
{
static bool console_available;
SCLPConsoleLM *scon = SCLPLM_CONSOLE(event);
if (console_available) {
error_report("Multiple line-mode operator consoles are not supported");
return -1;
}
console_available = true;
qemu_chr_fe_set_handlers(&scon->chr, chr_can_read,
chr_read, NULL, NULL, scon, NULL, true);
return 0;
}
static void console_reset(DeviceState *dev)
{
SCLPEvent *event = SCLP_EVENT(dev);
SCLPConsoleLM *scon = SCLPLM_CONSOLE(event);
event->event_pending = false;
scon->length = 0;
scon->write_errors = 0;
}
static const Property console_properties[] = {
DEFINE_PROP_CHR("chardev", SCLPConsoleLM, chr),
DEFINE_PROP_UINT32("write_errors", SCLPConsoleLM, write_errors, 0),
DEFINE_PROP_BOOL("echo", SCLPConsoleLM, echo, true),
};
static void console_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
SCLPEventClass *ec = SCLP_EVENT_CLASS(klass);
device_class_set_props(dc, console_properties);
device_class_set_legacy_reset(dc, console_reset);
dc->vmsd = &vmstate_sclplmconsole;
ec->init = console_init;
ec->get_send_mask = send_mask;
ec->get_receive_mask = receive_mask;
ec->can_handle_event = can_handle_event;
ec->read_event_data = read_event_data;
ec->write_event_data = write_event_data;
set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
}
static const TypeInfo sclp_console_info = {
.name = TYPE_SCLPLM_CONSOLE,
.parent = TYPE_SCLP_EVENT,
.instance_size = sizeof(SCLPConsoleLM),
.class_init = console_class_init,
.class_size = sizeof(SCLPEventClass),
};
static void register_types(void)
{
type_register_static(&sclp_console_info);
}
type_init(register_types)
+288
View File
@@ -0,0 +1,288 @@
/*
* SCLP event type
* Ascii Console Data (VT220 Console)
*
* Copyright IBM, Corp. 2012
*
* Authors:
* Heinz Graalfs <[email protected]>
*
* This work is licensed under the terms of the GNU GPL, version 2 or (at your
* option) any later version. See the COPYING file in the top-level directory.
*
*/
#include "qemu/osdep.h"
#include "qemu/thread.h"
#include "qemu/error-report.h"
#include "qemu/module.h"
#include "hw/s390x/sclp.h"
#include "migration/vmstate.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-properties-system.h"
#include "hw/s390x/event-facility.h"
#include "chardev/char-fe.h"
#include "qom/object.h"
typedef struct ASCIIConsoleData {
EventBufferHeader ebh;
char data[];
} QEMU_PACKED ASCIIConsoleData;
/* max size for ASCII data in 4K SCCB page */
#define SIZE_BUFFER_VT220 4080
struct SCLPConsole {
SCLPEvent event;
CharFrontend chr;
uint8_t iov[SIZE_BUFFER_VT220];
uint32_t iov_sclp; /* offset in buf for SCLP read operation */
uint32_t iov_bs; /* offset in buf for char layer read operation */
uint32_t iov_data_len; /* length of byte stream in buffer */
uint32_t iov_sclp_rest; /* length of byte stream not read via SCLP */
bool notify; /* qemu_notify_event() req'd if true */
};
typedef struct SCLPConsole SCLPConsole;
#define TYPE_SCLP_CONSOLE "sclpconsole"
DECLARE_INSTANCE_CHECKER(SCLPConsole, SCLP_CONSOLE,
TYPE_SCLP_CONSOLE)
/* character layer call-back functions */
/* Return number of bytes that fit into iov buffer */
static int chr_can_read(void *opaque)
{
SCLPConsole *scon = opaque;
int avail = SIZE_BUFFER_VT220 - scon->iov_data_len;
if (avail == 0) {
scon->notify = true;
}
return avail;
}
/* Send data from a char device over to the guest */
static void chr_read(void *opaque, const uint8_t *buf, int size)
{
SCLPConsole *scon = opaque;
assert(scon);
/* read data must fit into current buffer */
assert(size <= SIZE_BUFFER_VT220 - scon->iov_data_len);
/* put byte-stream from character layer into buffer */
memcpy(&scon->iov[scon->iov_bs], buf, size);
scon->iov_data_len += size;
scon->iov_sclp_rest += size;
scon->iov_bs += size;
scon->event.event_pending = true;
sclp_service_interrupt(0);
}
/* functions to be called by event facility */
static bool can_handle_event(uint8_t type)
{
return type == SCLP_EVENT_ASCII_CONSOLE_DATA;
}
static sccb_mask_t send_mask(void)
{
return SCLP_EVENT_MASK_MSG_ASCII;
}
static sccb_mask_t receive_mask(void)
{
return SCLP_EVENT_MASK_MSG_ASCII;
}
/* triggered by SCLP's read_event_data -
* copy console data byte-stream into provided (SCLP) buffer
*/
static void get_console_data(SCLPEvent *event, uint8_t *buf, size_t *size,
int avail)
{
SCLPConsole *cons = SCLP_CONSOLE(event);
/* first byte is hex 0 saying an ascii string follows */
*buf++ = '\0';
avail--;
/* if all data fit into provided SCLP buffer */
if (avail >= cons->iov_sclp_rest) {
/* copy character byte-stream to SCLP buffer */
memcpy(buf, &cons->iov[cons->iov_sclp], cons->iov_sclp_rest);
*size = cons->iov_sclp_rest + 1;
cons->iov_sclp = 0;
cons->iov_bs = 0;
cons->iov_data_len = 0;
cons->iov_sclp_rest = 0;
event->event_pending = false;
/* data provided and no more data pending */
} else {
/* if provided buffer is too small, just copy part */
memcpy(buf, &cons->iov[cons->iov_sclp], avail);
*size = avail + 1;
cons->iov_sclp_rest -= avail;
cons->iov_sclp += avail;
/* more data pending */
}
if (cons->notify) {
cons->notify = false;
qemu_notify_event();
}
}
static int read_event_data(SCLPEvent *event, EventBufferHeader *evt_buf_hdr,
int *slen)
{
int avail;
size_t src_len;
uint8_t *to;
ASCIIConsoleData *acd = (ASCIIConsoleData *) evt_buf_hdr;
if (!event->event_pending) {
/* no data pending */
return 0;
}
to = (uint8_t *)&acd->data;
avail = *slen - sizeof(ASCIIConsoleData);
get_console_data(event, to, &src_len, avail);
acd->ebh.length = cpu_to_be16(sizeof(ASCIIConsoleData) + src_len);
acd->ebh.type = SCLP_EVENT_ASCII_CONSOLE_DATA;
acd->ebh.flags |= SCLP_EVENT_BUFFER_ACCEPTED;
*slen = avail - src_len;
return 1;
}
/* triggered by SCLP's write_event_data
* - write console data to character layer
* returns < 0 if an error occurred
*/
static ssize_t write_console_data(SCLPEvent *event, const uint8_t *buf,
size_t len)
{
SCLPConsole *scon = SCLP_CONSOLE(event);
if (!qemu_chr_fe_backend_connected(&scon->chr)) {
/* If there's no backend, we can just say we consumed all data. */
return len;
}
/* XXX this blocks entire thread. Rewrite to use
* qemu_chr_fe_write and background I/O callbacks */
return qemu_chr_fe_write_all(&scon->chr, buf, len);
}
static int write_event_data(SCLPEvent *event, EventBufferHeader *evt_buf_hdr)
{
int rc;
int length;
ssize_t written;
ASCIIConsoleData *acd = (ASCIIConsoleData *) evt_buf_hdr;
length = be16_to_cpu(evt_buf_hdr->length) - sizeof(EventBufferHeader);
written = write_console_data(event, (uint8_t *)acd->data, length);
rc = SCLP_RC_NORMAL_COMPLETION;
/* set event buffer accepted flag */
evt_buf_hdr->flags |= SCLP_EVENT_BUFFER_ACCEPTED;
/* written will be zero if a pty is not connected - don't treat as error */
if (written < 0) {
/* event buffer not accepted due to error in character layer */
evt_buf_hdr->flags &= ~(SCLP_EVENT_BUFFER_ACCEPTED);
rc = SCLP_RC_CONTAINED_EQUIPMENT_CHECK;
}
return rc;
}
static const VMStateDescription vmstate_sclpconsole = {
.name = "sclpconsole",
.version_id = 0,
.minimum_version_id = 0,
.fields = (const VMStateField[]) {
VMSTATE_BOOL(event.event_pending, SCLPConsole),
VMSTATE_UINT8_ARRAY(iov, SCLPConsole, SIZE_BUFFER_VT220),
VMSTATE_UINT32(iov_sclp, SCLPConsole),
VMSTATE_UINT32(iov_bs, SCLPConsole),
VMSTATE_UINT32(iov_data_len, SCLPConsole),
VMSTATE_UINT32(iov_sclp_rest, SCLPConsole),
VMSTATE_END_OF_LIST()
}
};
/* qemu object creation and initialization functions */
/* tell character layer our call-back functions */
static int console_init(SCLPEvent *event)
{
static bool console_available;
SCLPConsole *scon = SCLP_CONSOLE(event);
if (console_available) {
error_report("Multiple VT220 operator consoles are not supported");
return -1;
}
console_available = true;
qemu_chr_fe_set_handlers(&scon->chr, chr_can_read,
chr_read, NULL, NULL, scon, NULL, true);
return 0;
}
static void console_reset(DeviceState *dev)
{
SCLPEvent *event = SCLP_EVENT(dev);
SCLPConsole *scon = SCLP_CONSOLE(event);
event->event_pending = false;
scon->iov_sclp = 0;
scon->iov_bs = 0;
scon->iov_data_len = 0;
scon->iov_sclp_rest = 0;
scon->notify = false;
}
static const Property console_properties[] = {
DEFINE_PROP_CHR("chardev", SCLPConsole, chr),
};
static void console_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
SCLPEventClass *ec = SCLP_EVENT_CLASS(klass);
device_class_set_props(dc, console_properties);
device_class_set_legacy_reset(dc, console_reset);
dc->vmsd = &vmstate_sclpconsole;
ec->init = console_init;
ec->get_send_mask = send_mask;
ec->get_receive_mask = receive_mask;
ec->can_handle_event = can_handle_event;
ec->read_event_data = read_event_data;
ec->write_event_data = write_event_data;
set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
}
static const TypeInfo sclp_console_info = {
.name = TYPE_SCLP_CONSOLE,
.parent = TYPE_SCLP_EVENT,
.instance_size = sizeof(SCLPConsole),
.class_init = console_class_init,
.class_size = sizeof(SCLPEventClass),
};
static void register_types(void)
{
type_register_static(&sclp_console_info);
}
type_init(register_types)
+201
View File
@@ -0,0 +1,201 @@
/*
* 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
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* 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.
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qemu/module.h"
#include "system/system.h"
#include "hw/acpi/acpi_aml_interface.h"
#include "hw/char/serial.h"
#include "hw/char/serial-isa.h"
#include "hw/isa/isa.h"
#include "hw/core/qdev-properties.h"
#include "migration/vmstate.h"
#include "qom/object.h"
OBJECT_DECLARE_SIMPLE_TYPE(ISASerialState, ISA_SERIAL)
struct ISASerialState {
ISADevice parent_obj;
uint32_t index;
uint32_t iobase;
uint32_t isairq;
SerialState state;
};
static const int isa_serial_io[MAX_ISA_SERIAL_PORTS] = {
0x3f8, 0x2f8, 0x3e8, 0x2e8
};
static const int isa_serial_irq[MAX_ISA_SERIAL_PORTS] = {
4, 3, 4, 3
};
static void serial_isa_realizefn(DeviceState *dev, Error **errp)
{
static int index;
ISADevice *isadev = ISA_DEVICE(dev);
ISASerialState *isa = ISA_SERIAL(dev);
SerialState *s = &isa->state;
if (isa->index == -1) {
isa->index = index;
}
if (isa->index >= MAX_ISA_SERIAL_PORTS) {
error_setg(errp, "Max. supported number of ISA serial ports is %d.",
MAX_ISA_SERIAL_PORTS);
return;
}
if (isa->iobase == -1) {
isa->iobase = isa_serial_io[isa->index];
}
if (isa->isairq == -1) {
isa->isairq = isa_serial_irq[isa->index];
}
index++;
s->irq = isa_get_irq(isadev, isa->isairq);
qdev_realize(DEVICE(s), NULL, errp);
qdev_set_legacy_instance_id(dev, isa->iobase, 3);
memory_region_init_io(&s->io, OBJECT(isa), &serial_io_ops, s, "serial", 8);
isa_register_ioport(isadev, &s->io, isa->iobase);
}
static void serial_isa_build_aml(AcpiDevAmlIf *adev, Aml *scope)
{
ISASerialState *isa = ISA_SERIAL(adev);
Aml *dev;
Aml *crs;
crs = aml_resource_template();
aml_append(crs, aml_io(AML_DECODE16, isa->iobase, isa->iobase, 0x00, 0x08));
aml_append(crs, aml_irq(isa->isairq, AML_LEVEL, AML_ACTIVE_LOW,
AML_SHARED));
dev = aml_device("COM%d", isa->index + 1);
aml_append(dev, aml_name_decl("_HID", aml_eisaid("PNP0501")));
aml_append(dev, aml_name_decl("_UID", aml_int(isa->index + 1)));
aml_append(dev, aml_name_decl("_STA", aml_int(0xf)));
aml_append(dev, aml_name_decl("_CRS", crs));
aml_append(scope, dev);
}
static const VMStateDescription vmstate_isa_serial = {
.name = "serial",
.version_id = 3,
.minimum_version_id = 2,
.fields = (const VMStateField[]) {
VMSTATE_STRUCT(state, ISASerialState, 0, vmstate_serial, SerialState),
VMSTATE_END_OF_LIST()
}
};
static const Property serial_isa_properties[] = {
DEFINE_PROP_UINT32("index", ISASerialState, index, -1),
DEFINE_PROP_UINT32("iobase", ISASerialState, iobase, -1),
DEFINE_PROP_UINT32("irq", ISASerialState, isairq, -1),
};
static void serial_isa_class_initfn(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
AcpiDevAmlIfClass *adevc = ACPI_DEV_AML_IF_CLASS(klass);
dc->realize = serial_isa_realizefn;
dc->vmsd = &vmstate_isa_serial;
adevc->build_dev_aml = serial_isa_build_aml;
device_class_set_props(dc, serial_isa_properties);
set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
}
static void serial_isa_initfn(Object *o)
{
ISASerialState *self = ISA_SERIAL(o);
object_initialize_child(o, "serial", &self->state, TYPE_SERIAL);
qdev_alias_all_properties(DEVICE(&self->state), o);
}
static const TypeInfo serial_isa_info = {
.name = TYPE_ISA_SERIAL,
.parent = TYPE_ISA_DEVICE,
.instance_size = sizeof(ISASerialState),
.instance_init = serial_isa_initfn,
.class_init = serial_isa_class_initfn,
.interfaces = (const InterfaceInfo[]) {
{ TYPE_ACPI_DEV_AML_IF },
{ },
},
};
static void serial_register_types(void)
{
type_register_static(&serial_isa_info);
}
type_init(serial_register_types)
static void serial_isa_init(ISABus *bus, int index, Chardev *chr)
{
DeviceState *dev;
ISADevice *isadev;
isadev = isa_new(TYPE_ISA_SERIAL);
dev = DEVICE(isadev);
qdev_prop_set_uint32(dev, "index", index);
qdev_prop_set_chr(dev, "chardev", chr);
isa_realize_and_unref(isadev, bus, &error_fatal);
}
void serial_hds_isa_init(ISABus *bus, int from, int to)
{
int i;
assert(from >= 0);
assert(to <= MAX_ISA_SERIAL_PORTS);
for (i = from; i < to; ++i) {
if (serial_hd(i)) {
serial_isa_init(bus, i, serial_hd(i));
}
}
}
void isa_serial_set_iobase(ISADevice *serial, hwaddr iobase)
{
ISASerialState *s = ISA_SERIAL(serial);
serial->ioport_id = iobase;
s->iobase = iobase;
memory_region_set_address(&s->state.io, s->iobase);
}
void isa_serial_set_enabled(ISADevice *serial, bool enabled)
{
memory_region_set_enabled(&ISA_SERIAL(serial)->state.io, enabled);
}
+156
View File
@@ -0,0 +1,156 @@
/*
* 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
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* 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.
*/
#include "qemu/osdep.h"
#include "hw/char/serial-mm.h"
#include "exec/cpu-common.h"
#include "migration/vmstate.h"
#include "qapi/error.h"
#include "hw/core/qdev-properties.h"
static uint64_t serial_mm_read(void *opaque, hwaddr addr, unsigned size)
{
SerialMM *s = SERIAL_MM(opaque);
return serial_io_ops.read(&s->serial, addr >> s->regshift, 1);
}
static void serial_mm_write(void *opaque, hwaddr addr,
uint64_t value, unsigned size)
{
SerialMM *s = SERIAL_MM(opaque);
value &= 255;
serial_io_ops.write(&s->serial, addr >> s->regshift, value, 1);
}
static const MemoryRegionOps serial_mm_ops[] = {
[DEVICE_NATIVE_ENDIAN] = {
.read = serial_mm_read,
.write = serial_mm_write,
.endianness = DEVICE_NATIVE_ENDIAN,
.valid.max_access_size = 8,
.impl.max_access_size = 8,
},
[DEVICE_LITTLE_ENDIAN] = {
.read = serial_mm_read,
.write = serial_mm_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.valid.max_access_size = 8,
.impl.max_access_size = 8,
},
[DEVICE_BIG_ENDIAN] = {
.read = serial_mm_read,
.write = serial_mm_write,
.endianness = DEVICE_BIG_ENDIAN,
.valid.max_access_size = 8,
.impl.max_access_size = 8,
},
};
static void serial_mm_realize(DeviceState *dev, Error **errp)
{
SerialMM *smm = SERIAL_MM(dev);
SerialState *s = &smm->serial;
if (!qdev_realize(DEVICE(s), NULL, errp)) {
return;
}
memory_region_init_io(&s->io, OBJECT(dev),
&serial_mm_ops[smm->endianness], smm, "serial",
8 << smm->regshift);
sysbus_init_mmio(SYS_BUS_DEVICE(smm), &s->io);
sysbus_init_irq(SYS_BUS_DEVICE(smm), &smm->serial.irq);
}
static const VMStateDescription vmstate_serial_mm = {
.name = "serial",
.version_id = 3,
.minimum_version_id = 2,
.fields = (const VMStateField[]) {
VMSTATE_STRUCT(serial, SerialMM, 0, vmstate_serial, SerialState),
VMSTATE_END_OF_LIST()
}
};
SerialMM *serial_mm_init(MemoryRegion *address_space,
hwaddr base, int regshift,
qemu_irq irq, int baudbase,
Chardev *chr, enum device_endian end)
{
SerialMM *smm = SERIAL_MM(qdev_new(TYPE_SERIAL_MM));
MemoryRegion *mr;
qdev_prop_set_uint8(DEVICE(smm), "regshift", regshift);
qdev_prop_set_uint32(DEVICE(smm), "baudbase", baudbase);
qdev_prop_set_chr(DEVICE(smm), "chardev", chr);
qdev_set_legacy_instance_id(DEVICE(smm), base, 2);
qdev_prop_set_uint8(DEVICE(smm), "endianness", end);
sysbus_realize_and_unref(SYS_BUS_DEVICE(smm), &error_fatal);
sysbus_connect_irq(SYS_BUS_DEVICE(smm), 0, irq);
mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(smm), 0);
memory_region_add_subregion(address_space, base, mr);
return smm;
}
static void serial_mm_instance_init(Object *o)
{
SerialMM *smm = SERIAL_MM(o);
object_initialize_child(o, "serial", &smm->serial, TYPE_SERIAL);
qdev_alias_all_properties(DEVICE(&smm->serial), o);
}
static const Property serial_mm_properties[] = {
/*
* Set the spacing between adjacent memory-mapped UART registers.
* Each register will be at (1 << regshift) bytes after the previous one.
*/
DEFINE_PROP_UINT8("regshift", SerialMM, regshift, 0),
DEFINE_PROP_UINT8("endianness", SerialMM, endianness, DEVICE_NATIVE_ENDIAN),
};
static void serial_mm_class_init(ObjectClass *oc, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(oc);
device_class_set_props(dc, serial_mm_properties);
dc->realize = serial_mm_realize;
dc->vmsd = &vmstate_serial_mm;
}
static const TypeInfo types[] = {
{
.name = TYPE_SERIAL_MM,
.parent = TYPE_SYS_BUS_DEVICE,
.class_init = serial_mm_class_init,
.instance_init = serial_mm_instance_init,
.instance_size = sizeof(SerialMM),
},
};
DEFINE_TYPES(types)
+219
View File
@@ -0,0 +1,219 @@
/*
* QEMU 16550A multi UART emulation
*
* SPDX-License-Identifier: MIT
*
* 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
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* 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.
*/
/* see docs/specs/pci-serial.rst */
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "hw/char/serial.h"
#include "hw/core/irq.h"
#include "hw/pci/pci_device.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-properties-system.h"
#include "migration/vmstate.h"
#define PCI_SERIAL_MAX_PORTS 4
typedef struct PCIMultiSerialState {
PCIDevice dev;
MemoryRegion iobar;
uint32_t ports;
char *name[PCI_SERIAL_MAX_PORTS];
SerialState state[PCI_SERIAL_MAX_PORTS];
uint32_t level[PCI_SERIAL_MAX_PORTS];
IRQState irqs[PCI_SERIAL_MAX_PORTS];
} PCIMultiSerialState;
static void multi_serial_pci_exit(PCIDevice *dev)
{
PCIMultiSerialState *pci = DO_UPCAST(PCIMultiSerialState, dev, dev);
SerialState *s;
int i;
for (i = 0; i < pci->ports; i++) {
s = pci->state + i;
memory_region_del_subregion(&pci->iobar, &s->io);
qdev_unrealize(DEVICE(s));
g_free(pci->name[i]);
}
}
static void multi_serial_irq_mux(void *opaque, int n, int level)
{
PCIMultiSerialState *pci = opaque;
int i, pending = 0;
pci->level[n] = level;
for (i = 0; i < pci->ports; i++) {
if (pci->level[i]) {
pending = 1;
}
}
pci_set_irq(&pci->dev, pending);
}
static size_t multi_serial_get_port_count(PCIDeviceClass *pc)
{
switch (pc->device_id) {
case 0x0003:
return 2;
case 0x0004:
return 4;
}
g_assert_not_reached();
}
static void multi_serial_pci_realize(PCIDevice *dev, Error **errp)
{
PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(dev);
PCIMultiSerialState *pci = DO_UPCAST(PCIMultiSerialState, dev, dev);
SerialState *s;
size_t i, nports = multi_serial_get_port_count(pc);
pci->dev.config[PCI_CLASS_PROG] = 2; /* 16550 compatible */
pci->dev.config[PCI_INTERRUPT_PIN] = 1;
memory_region_init(&pci->iobar, OBJECT(pci), "multiserial", 8 * nports);
pci_register_bar(&pci->dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &pci->iobar);
for (i = 0; i < nports; i++) {
s = pci->state + i;
if (!qdev_realize(DEVICE(s), NULL, errp)) {
multi_serial_pci_exit(dev);
return;
}
s->irq = &pci->irqs[i];
pci->name[i] = g_strdup_printf("uart #%zu", i + 1);
memory_region_init_io(&s->io, OBJECT(pci), &serial_io_ops, s,
pci->name[i], 8);
memory_region_add_subregion(&pci->iobar, 8 * i, &s->io);
pci->ports++;
}
}
static const VMStateDescription vmstate_pci_multi_serial = {
.name = "pci-serial-multi",
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_PCI_DEVICE(dev, PCIMultiSerialState),
VMSTATE_STRUCT_ARRAY(state, PCIMultiSerialState, PCI_SERIAL_MAX_PORTS,
0, vmstate_serial, SerialState),
VMSTATE_UINT32_ARRAY(level, PCIMultiSerialState, PCI_SERIAL_MAX_PORTS),
VMSTATE_END_OF_LIST()
}
};
static const Property multi_2x_serial_pci_properties[] = {
DEFINE_PROP_CHR("chardev1", PCIMultiSerialState, state[0].chr),
DEFINE_PROP_CHR("chardev2", PCIMultiSerialState, state[1].chr),
};
static const Property multi_4x_serial_pci_properties[] = {
DEFINE_PROP_CHR("chardev1", PCIMultiSerialState, state[0].chr),
DEFINE_PROP_CHR("chardev2", PCIMultiSerialState, state[1].chr),
DEFINE_PROP_CHR("chardev3", PCIMultiSerialState, state[2].chr),
DEFINE_PROP_CHR("chardev4", PCIMultiSerialState, state[3].chr),
};
static void multi_2x_serial_pci_class_initfn(ObjectClass *klass,
const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
PCIDeviceClass *pc = PCI_DEVICE_CLASS(klass);
pc->realize = multi_serial_pci_realize;
pc->exit = multi_serial_pci_exit;
pc->vendor_id = PCI_VENDOR_ID_REDHAT;
pc->device_id = PCI_DEVICE_ID_REDHAT_SERIAL2;
pc->revision = 1;
pc->class_id = PCI_CLASS_COMMUNICATION_SERIAL;
dc->vmsd = &vmstate_pci_multi_serial;
device_class_set_props(dc, multi_2x_serial_pci_properties);
set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
}
static void multi_4x_serial_pci_class_initfn(ObjectClass *klass,
const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
PCIDeviceClass *pc = PCI_DEVICE_CLASS(klass);
pc->realize = multi_serial_pci_realize;
pc->exit = multi_serial_pci_exit;
pc->vendor_id = PCI_VENDOR_ID_REDHAT;
pc->device_id = PCI_DEVICE_ID_REDHAT_SERIAL4;
pc->revision = 1;
pc->class_id = PCI_CLASS_COMMUNICATION_SERIAL;
dc->vmsd = &vmstate_pci_multi_serial;
device_class_set_props(dc, multi_4x_serial_pci_properties);
set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
}
static void multi_serial_init(Object *o)
{
PCIDevice *dev = PCI_DEVICE(o);
PCIMultiSerialState *pms = DO_UPCAST(PCIMultiSerialState, dev, dev);
size_t i, nports = multi_serial_get_port_count(PCI_DEVICE_GET_CLASS(dev));
for (i = 0; i < nports; i++) {
qemu_init_irq_child(o, "irq[*]", &pms->irqs[i],
multi_serial_irq_mux, pms, i);
object_initialize_child(o, "serial[*]", &pms->state[i], TYPE_SERIAL);
}
}
static const TypeInfo multi_2x_serial_pci_info = {
.name = "pci-serial-2x",
.parent = TYPE_PCI_DEVICE,
.instance_size = sizeof(PCIMultiSerialState),
.instance_init = multi_serial_init,
.class_init = multi_2x_serial_pci_class_initfn,
.interfaces = (const InterfaceInfo[]) {
{ INTERFACE_CONVENTIONAL_PCI_DEVICE },
{ },
},
};
static const TypeInfo multi_4x_serial_pci_info = {
.name = "pci-serial-4x",
.parent = TYPE_PCI_DEVICE,
.instance_size = sizeof(PCIMultiSerialState),
.instance_init = multi_serial_init,
.class_init = multi_4x_serial_pci_class_initfn,
.interfaces = (const InterfaceInfo[]) {
{ INTERFACE_CONVENTIONAL_PCI_DEVICE },
{ },
},
};
static void multi_serial_pci_register_types(void)
{
type_register_static(&multi_2x_serial_pci_info);
type_register_static(&multi_4x_serial_pci_info);
}
type_init(multi_serial_pci_register_types)
+123
View File
@@ -0,0 +1,123 @@
/*
* 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
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* 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.
*/
/* see docs/specs/pci-serial.rst */
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qemu/module.h"
#include "hw/char/serial.h"
#include "hw/core/irq.h"
#include "hw/pci/pci_device.h"
#include "hw/core/qdev-properties.h"
#include "migration/vmstate.h"
#include "qom/object.h"
struct PCISerialState {
PCIDevice dev;
SerialState state;
};
#define TYPE_PCI_SERIAL "pci-serial"
OBJECT_DECLARE_SIMPLE_TYPE(PCISerialState, PCI_SERIAL)
static void serial_pci_realize(PCIDevice *dev, Error **errp)
{
PCISerialState *pci = DO_UPCAST(PCISerialState, dev, dev);
SerialState *s = &pci->state;
if (!qdev_realize(DEVICE(s), NULL, errp)) {
return;
}
pci->dev.config[PCI_CLASS_PROG] = 2; /* 16550 compatible */
pci->dev.config[PCI_INTERRUPT_PIN] = 1;
s->irq = pci_allocate_irq(&pci->dev);
memory_region_init_io(&s->io, OBJECT(pci), &serial_io_ops, s, "serial", 8);
pci_register_bar(&pci->dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &s->io);
}
static void serial_pci_exit(PCIDevice *dev)
{
PCISerialState *pci = DO_UPCAST(PCISerialState, dev, dev);
SerialState *s = &pci->state;
qdev_unrealize(DEVICE(s));
qemu_free_irq(s->irq);
}
static const VMStateDescription vmstate_pci_serial = {
.name = "pci-serial",
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_PCI_DEVICE(dev, PCISerialState),
VMSTATE_STRUCT(state, PCISerialState, 0, vmstate_serial, SerialState),
VMSTATE_END_OF_LIST()
}
};
static void serial_pci_class_initfn(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
PCIDeviceClass *pc = PCI_DEVICE_CLASS(klass);
pc->realize = serial_pci_realize;
pc->exit = serial_pci_exit;
pc->vendor_id = PCI_VENDOR_ID_REDHAT;
pc->device_id = PCI_DEVICE_ID_REDHAT_SERIAL;
pc->revision = 1;
pc->class_id = PCI_CLASS_COMMUNICATION_SERIAL;
dc->vmsd = &vmstate_pci_serial;
set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
}
static void serial_pci_init(Object *o)
{
PCISerialState *ps = PCI_SERIAL(o);
object_initialize_child(o, "serial", &ps->state, TYPE_SERIAL);
qdev_alias_all_properties(DEVICE(&ps->state), o);
}
static const TypeInfo serial_pci_info = {
.name = TYPE_PCI_SERIAL,
.parent = TYPE_PCI_DEVICE,
.instance_size = sizeof(PCISerialState),
.instance_init = serial_pci_init,
.class_init = serial_pci_class_initfn,
.interfaces = (const InterfaceInfo[]) {
{ INTERFACE_CONVENTIONAL_PCI_DEVICE },
{ },
},
};
static void serial_pci_register_types(void)
{
type_register_static(&serial_pci_info);
}
type_init(serial_pci_register_types)
+994
View File
@@ -0,0 +1,994 @@
/*
* 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
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* 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.
*/
#include "qemu/osdep.h"
#include "qemu/bitops.h"
#include "hw/char/serial.h"
#include "hw/core/irq.h"
#include "migration/vmstate.h"
#include "chardev/char-serial.h"
#include "qapi/error.h"
#include "qemu/timer.h"
#include "system/reset.h"
#include "system/runstate.h"
#include "qemu/error-report.h"
#include "trace.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-properties-system.h"
#define UART_LCR_DLAB 0x80 /* Divisor latch access bit */
#define UART_LCR_SB 0x40 /* Set break */
#define UART_LCR_EPS 0x10 /* Even parity select */
#define UART_LCR_PEN 0x08 /* Parity enable */
#define UART_LCR_NSTB 0x04 /* Number of stop bits */
#define UART_LCR_WLS 0x03 /* Word length select */
#define UART_IER_MSI 0x08 /* Enable Modem status interrupt */
#define UART_IER_RLSI 0x04 /* Enable receiver line status interrupt */
#define UART_IER_THRI 0x02 /* Enable Transmitter holding register int. */
#define UART_IER_RDI 0x01 /* Enable receiver data interrupt */
#define UART_IIR_NO_INT 0x01 /* No interrupts pending */
#define UART_IIR_ID 0x06 /* Mask for the interrupt ID */
#define UART_IIR_MSI 0x00 /* Modem status interrupt */
#define UART_IIR_THRI 0x02 /* Transmitter holding register empty */
#define UART_IIR_RDI 0x04 /* Receiver data interrupt */
#define UART_IIR_RLSI 0x06 /* Receiver line status interrupt */
#define UART_IIR_CTI 0x0C /* Character Timeout Indication */
#define UART_IIR_FENF 0x80 /* Fifo enabled, but not functioning */
#define UART_IIR_FE 0xC0 /* Fifo enabled */
/*
* These are the definitions for the Modem Control Register
*/
#define UART_MCR_LOOP 0x10 /* Enable loopback test mode */
#define UART_MCR_OUT2 0x08 /* Out2 complement */
#define UART_MCR_OUT1 0x04 /* Out1 complement */
#define UART_MCR_RTS 0x02 /* RTS complement */
#define UART_MCR_DTR 0x01 /* DTR complement */
/*
* These are the definitions for the Modem Status Register
*/
#define UART_MSR_DCD 0x80 /* Data Carrier Detect */
#define UART_MSR_RI 0x40 /* Ring Indicator */
#define UART_MSR_DSR 0x20 /* Data Set Ready */
#define UART_MSR_CTS 0x10 /* Clear to Send */
#define UART_MSR_DDCD 0x08 /* Delta DCD */
#define UART_MSR_TERI 0x04 /* Trailing edge ring indicator */
#define UART_MSR_DDSR 0x02 /* Delta DSR */
#define UART_MSR_DCTS 0x01 /* Delta CTS */
#define UART_MSR_ANY_DELTA 0x0F /* Any of the delta bits! */
#define UART_LSR_TEMT 0x40 /* Transmitter empty */
#define UART_LSR_THRE 0x20 /* Transmit-hold-register empty */
#define UART_LSR_BI 0x10 /* Break interrupt indicator */
#define UART_LSR_FE 0x08 /* Frame error indicator */
#define UART_LSR_PE 0x04 /* Parity error indicator */
#define UART_LSR_OE 0x02 /* Overrun error indicator */
#define UART_LSR_DR 0x01 /* Receiver data ready */
#define UART_LSR_INT_ANY 0x1E /* Any of the lsr-interrupt-triggering status bits */
/* Interrupt trigger levels. The byte-counts are for 16550A - in newer UARTs the byte-count for each ITL is higher. */
#define UART_FCR_ITL_1 0x00 /* 1 byte ITL */
#define UART_FCR_ITL_2 0x40 /* 4 bytes ITL */
#define UART_FCR_ITL_3 0x80 /* 8 bytes ITL */
#define UART_FCR_ITL_4 0xC0 /* 14 bytes ITL */
#define UART_FCR_DMS 0x08 /* DMA Mode Select */
#define UART_FCR_XFR 0x04 /* XMIT Fifo Reset */
#define UART_FCR_RFR 0x02 /* RCVR Fifo Reset */
#define UART_FCR_FE 0x01 /* FIFO Enable */
#define MAX_XMIT_RETRY 4
static void serial_receive1(void *opaque, const uint8_t *buf, int size);
static void serial_xmit(SerialState *s);
static inline void recv_fifo_put(SerialState *s, uint8_t chr)
{
/* Receive overruns do not overwrite FIFO contents. */
if (!fifo8_is_full(&s->recv_fifo)) {
fifo8_push(&s->recv_fifo, chr);
} else {
s->lsr |= UART_LSR_OE;
}
}
static void serial_update_irq(SerialState *s)
{
uint8_t tmp_iir = UART_IIR_NO_INT;
if ((s->ier & UART_IER_RLSI) && (s->lsr & UART_LSR_INT_ANY)) {
tmp_iir = UART_IIR_RLSI;
} else if ((s->ier & UART_IER_RDI) && s->timeout_ipending) {
/* Note that(s->ier & UART_IER_RDI) can mask this interrupt,
* this is not in the specification but is observed on existing
* hardware. */
tmp_iir = UART_IIR_CTI;
} else if ((s->ier & UART_IER_RDI) && (s->lsr & UART_LSR_DR) &&
(!(s->fcr & UART_FCR_FE) ||
fifo8_num_used(&s->recv_fifo) >= s->recv_fifo_itl)) {
tmp_iir = UART_IIR_RDI;
} else if ((s->ier & UART_IER_THRI) && s->thr_ipending) {
tmp_iir = UART_IIR_THRI;
} else if ((s->ier & UART_IER_MSI) && (s->msr & UART_MSR_ANY_DELTA)) {
tmp_iir = UART_IIR_MSI;
}
s->iir = tmp_iir | (s->iir & 0xF0);
if (tmp_iir != UART_IIR_NO_INT) {
qemu_irq_raise(s->irq);
} else {
qemu_irq_lower(s->irq);
}
}
static void serial_update_parameters(SerialState *s)
{
float speed;
int parity, data_bits, stop_bits, frame_size;
QEMUSerialSetParams ssp;
/* Start bit. */
frame_size = 1;
if (s->lcr & UART_LCR_PEN) {
/* Parity bit. */
frame_size++;
if (s->lcr & UART_LCR_EPS)
parity = 'E';
else
parity = 'O';
} else {
parity = 'N';
}
if (s->lcr & UART_LCR_NSTB) {
stop_bits = 2;
} else {
stop_bits = 1;
}
data_bits = (s->lcr & UART_LCR_WLS) + 5;
frame_size += data_bits + stop_bits;
/* Zero divisor should give about 3500 baud */
speed = (s->divider == 0) ? 3500 : (float) s->baudbase / s->divider;
ssp.speed = speed;
ssp.parity = parity;
ssp.data_bits = data_bits;
ssp.stop_bits = stop_bits;
s->char_transmit_time = (NANOSECONDS_PER_SECOND / speed) * frame_size;
qemu_chr_fe_ioctl(&s->chr, CHR_IOCTL_SERIAL_SET_PARAMS, &ssp);
trace_serial_update_parameters(speed, parity, data_bits, stop_bits);
}
static void serial_update_msl(SerialState *s)
{
uint8_t omsr;
int flags;
timer_del(s->modem_status_poll);
if (qemu_chr_fe_ioctl(&s->chr, CHR_IOCTL_SERIAL_GET_TIOCM,
&flags) == -ENOTSUP) {
s->poll_msl = -1;
return;
}
omsr = s->msr;
s->msr = (flags & CHR_TIOCM_CTS) ? s->msr | UART_MSR_CTS : s->msr & ~UART_MSR_CTS;
s->msr = (flags & CHR_TIOCM_DSR) ? s->msr | UART_MSR_DSR : s->msr & ~UART_MSR_DSR;
s->msr = (flags & CHR_TIOCM_CAR) ? s->msr | UART_MSR_DCD : s->msr & ~UART_MSR_DCD;
s->msr = (flags & CHR_TIOCM_RI) ? s->msr | UART_MSR_RI : s->msr & ~UART_MSR_RI;
if (s->msr != omsr) {
/* Set delta bits */
s->msr = s->msr | ((s->msr >> 4) ^ (omsr >> 4));
/* UART_MSR_TERI only if change was from 1 -> 0 */
if ((s->msr & UART_MSR_TERI) && !(omsr & UART_MSR_RI))
s->msr &= ~UART_MSR_TERI;
serial_update_irq(s);
}
/* The real 16550A apparently has a 250ns response latency to line status changes.
We'll be lazy and poll only every 10ms, and only poll it at all if MSI interrupts are turned on */
if (s->poll_msl) {
timer_mod(s->modem_status_poll, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
NANOSECONDS_PER_SECOND / 100);
}
}
static gboolean serial_watch_cb(void *do_not_use, GIOCondition cond,
void *opaque)
{
SerialState *s = opaque;
s->watch_tag = 0;
serial_xmit(s);
return G_SOURCE_REMOVE;
}
static void serial_xmit(SerialState *s)
{
do {
assert(!(s->lsr & UART_LSR_TEMT));
if (s->tsr_retry == 0) {
assert(!(s->lsr & UART_LSR_THRE));
if (s->fcr & UART_FCR_FE) {
assert(!fifo8_is_empty(&s->xmit_fifo));
s->tsr = fifo8_pop(&s->xmit_fifo);
if (fifo8_is_empty(&s->xmit_fifo)) {
s->lsr |= UART_LSR_THRE;
}
} else {
s->tsr = s->thr;
s->lsr |= UART_LSR_THRE;
}
if ((s->lsr & UART_LSR_THRE) && !s->thr_ipending) {
s->thr_ipending = 1;
serial_update_irq(s);
}
}
if (s->mcr & UART_MCR_LOOP) {
/* in loopback mode, say that we just received a char */
serial_receive1(s, &s->tsr, 1);
} else {
int rc = qemu_chr_fe_write(&s->chr, &s->tsr, 1);
if ((rc == 0 ||
(rc == -1 && errno == EAGAIN)) &&
s->tsr_retry < MAX_XMIT_RETRY) {
assert(s->watch_tag == 0);
s->watch_tag =
qemu_chr_fe_add_watch(&s->chr, G_IO_OUT | G_IO_HUP,
serial_watch_cb, s);
if (s->watch_tag > 0) {
s->tsr_retry++;
return;
}
}
}
s->tsr_retry = 0;
/* Transmit another byte if it is already available. It is only
possible when FIFO is enabled and not empty. */
} while (!(s->lsr & UART_LSR_THRE));
s->last_xmit_ts = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
s->lsr |= UART_LSR_TEMT;
}
static void serial_write_fcr(SerialState *s, uint8_t val)
{
/* Set fcr - val only has the bits that are supposed to "stick" */
s->fcr = val;
if (val & UART_FCR_FE) {
s->iir |= UART_IIR_FE;
/* Set recv_fifo trigger Level */
switch (val & 0xC0) {
case UART_FCR_ITL_1:
s->recv_fifo_itl = 1;
break;
case UART_FCR_ITL_2:
s->recv_fifo_itl = 4;
break;
case UART_FCR_ITL_3:
s->recv_fifo_itl = 8;
break;
case UART_FCR_ITL_4:
s->recv_fifo_itl = 14;
break;
}
} else {
s->iir &= ~UART_IIR_FE;
}
}
static void serial_update_tiocm(SerialState *s)
{
int flags;
qemu_chr_fe_ioctl(&s->chr, CHR_IOCTL_SERIAL_GET_TIOCM, &flags);
flags &= ~(CHR_TIOCM_RTS | CHR_TIOCM_DTR);
if (s->mcr & UART_MCR_RTS) {
flags |= CHR_TIOCM_RTS;
}
if (s->mcr & UART_MCR_DTR) {
flags |= CHR_TIOCM_DTR;
}
qemu_chr_fe_ioctl(&s->chr, CHR_IOCTL_SERIAL_SET_TIOCM, &flags);
}
static void serial_ioport_write(void *opaque, hwaddr addr, uint64_t val,
unsigned size)
{
SerialState *s = opaque;
assert(size == 1 && addr < 8);
trace_serial_write(addr, val);
switch(addr) {
default:
case 0:
if (s->lcr & UART_LCR_DLAB) {
s->divider = deposit32(s->divider, 8 * addr, 8, val);
serial_update_parameters(s);
} else {
s->thr = (uint8_t) val;
if(s->fcr & UART_FCR_FE) {
/* xmit overruns overwrite data, so make space if needed */
if (fifo8_is_full(&s->xmit_fifo)) {
fifo8_pop(&s->xmit_fifo);
}
fifo8_push(&s->xmit_fifo, s->thr);
}
s->thr_ipending = 0;
s->lsr &= ~UART_LSR_THRE;
s->lsr &= ~UART_LSR_TEMT;
serial_update_irq(s);
if (s->tsr_retry == 0) {
serial_xmit(s);
}
}
break;
case 1:
if (s->lcr & UART_LCR_DLAB) {
s->divider = deposit32(s->divider, 8 * addr, 8, val);
serial_update_parameters(s);
} else {
uint8_t changed = (s->ier ^ val) & 0x0f;
s->ier = val & 0x0f;
/* If the backend device is a real serial port, turn polling of the modem
* status lines on physical port on or off depending on UART_IER_MSI state.
*/
if ((changed & UART_IER_MSI) && s->poll_msl >= 0) {
if (s->ier & UART_IER_MSI) {
s->poll_msl = 1;
serial_update_msl(s);
} else {
timer_del(s->modem_status_poll);
s->poll_msl = 0;
}
}
/* Turning on the THRE interrupt on IER can trigger the interrupt
* if LSR.THRE=1, even if it had been masked before by reading IIR.
* This is not in the datasheet, but Windows relies on it. It is
* unclear if THRE has to be resampled every time THRI becomes
* 1, or only on the rising edge. Bochs does the latter, and Windows
* always toggles IER to all zeroes and back to all ones, so do the
* same.
*
* If IER.THRI is zero, thr_ipending is not used. Set it to zero
* so that the thr_ipending subsection is not migrated.
*/
if (changed & UART_IER_THRI) {
if ((s->ier & UART_IER_THRI) && (s->lsr & UART_LSR_THRE)) {
s->thr_ipending = 1;
} else {
s->thr_ipending = 0;
}
}
if (changed) {
serial_update_irq(s);
}
}
break;
case 2:
/* Did the enable/disable flag change? If so, make sure FIFOs get flushed */
if ((val ^ s->fcr) & UART_FCR_FE) {
val |= UART_FCR_XFR | UART_FCR_RFR;
}
/* FIFO clear */
if (val & UART_FCR_RFR) {
s->lsr &= ~(UART_LSR_DR | UART_LSR_BI);
timer_del(s->fifo_timeout_timer);
s->timeout_ipending = 0;
fifo8_reset(&s->recv_fifo);
}
if (val & UART_FCR_XFR) {
s->lsr |= UART_LSR_THRE;
s->thr_ipending = 1;
fifo8_reset(&s->xmit_fifo);
}
serial_write_fcr(s, val & 0xC9);
serial_update_irq(s);
break;
case 3:
{
int break_enable;
s->lcr = val;
serial_update_parameters(s);
break_enable = !!(val & UART_LCR_SB);
if (break_enable != s->last_break_enable) {
s->last_break_enable = break_enable;
qemu_chr_fe_ioctl(&s->chr, CHR_IOCTL_SERIAL_SET_BREAK,
&break_enable);
}
}
break;
case 4:
{
int old_mcr = s->mcr;
s->mcr = val & 0x1f;
if (val & UART_MCR_LOOP)
break;
if (s->poll_msl >= 0 && old_mcr != s->mcr) {
serial_update_tiocm(s);
/* Update the modem status after a one-character-send wait-time, since there may be a response
from the device/computer at the other end of the serial line */
timer_mod(s->modem_status_poll, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->char_transmit_time);
}
}
break;
case 5:
break;
case 6:
break;
case 7:
s->scr = val;
break;
}
}
static uint64_t serial_ioport_read(void *opaque, hwaddr addr, unsigned size)
{
SerialState *s = opaque;
uint32_t ret;
assert(size == 1 && addr < 8);
switch(addr) {
default:
case 0:
if (s->lcr & UART_LCR_DLAB) {
ret = extract16(s->divider, 8 * addr, 8);
} else {
if(s->fcr & UART_FCR_FE) {
ret = fifo8_is_empty(&s->recv_fifo) ?
0 : fifo8_pop(&s->recv_fifo);
if (fifo8_is_empty(&s->recv_fifo)) {
s->lsr &= ~(UART_LSR_DR | UART_LSR_BI);
} else {
timer_mod(s->fifo_timeout_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->char_transmit_time * 4);
}
s->timeout_ipending = 0;
} else {
ret = s->rbr;
s->lsr &= ~(UART_LSR_DR | UART_LSR_BI);
}
serial_update_irq(s);
if (!(s->mcr & UART_MCR_LOOP)) {
/* in loopback mode, don't receive any data */
qemu_chr_fe_accept_input(&s->chr);
}
}
break;
case 1:
if (s->lcr & UART_LCR_DLAB) {
ret = extract16(s->divider, 8 * addr, 8);
} else {
ret = s->ier;
}
break;
case 2:
ret = s->iir;
if ((ret & UART_IIR_ID) == UART_IIR_THRI) {
s->thr_ipending = 0;
serial_update_irq(s);
}
break;
case 3:
ret = s->lcr;
break;
case 4:
ret = s->mcr;
break;
case 5:
ret = s->lsr;
/* Clear break and overrun interrupts */
if (s->lsr & (UART_LSR_BI|UART_LSR_OE)) {
s->lsr &= ~(UART_LSR_BI|UART_LSR_OE);
serial_update_irq(s);
}
break;
case 6:
if (s->mcr & UART_MCR_LOOP) {
/* in loopback, the modem output pins are connected to the
inputs */
ret = (s->mcr & 0x0c) << 4;
ret |= (s->mcr & 0x02) << 3;
ret |= (s->mcr & 0x01) << 5;
} else {
if (s->poll_msl >= 0)
serial_update_msl(s);
ret = s->msr;
/* Clear delta bits & msr int after read, if they were set */
if (s->msr & UART_MSR_ANY_DELTA) {
s->msr &= 0xF0;
serial_update_irq(s);
}
}
break;
case 7:
ret = s->scr;
break;
}
trace_serial_read(addr, ret);
return ret;
}
static int serial_can_receive(SerialState *s)
{
if(s->fcr & UART_FCR_FE) {
if (!fifo8_is_full(&s->recv_fifo)) {
/*
* Advertise (fifo.itl - fifo.count) bytes when count < ITL, and 1
* if above. If UART_FIFO_LENGTH - fifo.count is advertised the
* effect will be to almost always fill the fifo completely before
* the guest has a chance to respond, effectively overriding the ITL
* that the guest has set.
*/
return (fifo8_num_used(&s->recv_fifo) <= s->recv_fifo_itl) ?
s->recv_fifo_itl - fifo8_num_used(&s->recv_fifo) : 1;
} else {
return 0;
}
} else {
return !(s->lsr & UART_LSR_DR);
}
}
static void serial_receive_break(SerialState *s)
{
s->rbr = 0;
/* When the LSR_DR is set a null byte is pushed into the fifo */
recv_fifo_put(s, '\0');
s->lsr |= UART_LSR_BI | UART_LSR_DR;
serial_update_irq(s);
}
/* There's data in recv_fifo and s->rbr has not been read for 4 char transmit times */
static void fifo_timeout_int (void *opaque) {
SerialState *s = opaque;
if (!fifo8_is_empty(&s->recv_fifo)) {
s->timeout_ipending = 1;
serial_update_irq(s);
}
}
static int serial_can_receive1(void *opaque)
{
SerialState *s = opaque;
return serial_can_receive(s);
}
static void serial_receive1(void *opaque, const uint8_t *buf, int size)
{
SerialState *s = opaque;
if (s->wakeup) {
qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER, NULL);
}
if(s->fcr & UART_FCR_FE) {
int i;
for (i = 0; i < size; i++) {
recv_fifo_put(s, buf[i]);
}
s->lsr |= UART_LSR_DR;
/* call the timeout receive callback in 4 char transmit time */
timer_mod(s->fifo_timeout_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->char_transmit_time * 4);
} else {
if (s->lsr & UART_LSR_DR)
s->lsr |= UART_LSR_OE;
s->rbr = buf[0];
s->lsr |= UART_LSR_DR;
}
serial_update_irq(s);
}
static void serial_event(void *opaque, QEMUChrEvent event)
{
SerialState *s = opaque;
if (event == CHR_EVENT_BREAK)
serial_receive_break(s);
}
static int serial_pre_save(void *opaque)
{
SerialState *s = opaque;
s->fcr_vmstate = s->fcr;
return 0;
}
static int serial_pre_load(void *opaque)
{
SerialState *s = opaque;
s->thr_ipending = -1;
s->poll_msl = -1;
return 0;
}
static int serial_post_load(void *opaque, int version_id)
{
SerialState *s = opaque;
if (version_id < 3) {
s->fcr_vmstate = 0;
}
if (s->thr_ipending == -1) {
s->thr_ipending = ((s->iir & UART_IIR_ID) == UART_IIR_THRI);
}
if (s->tsr_retry > 0) {
/* tsr_retry > 0 implies LSR.TEMT = 0 (transmitter not empty). */
if (s->lsr & UART_LSR_TEMT) {
error_report("inconsistent state in serial device "
"(tsr empty, tsr_retry=%d", s->tsr_retry);
return -1;
}
if (s->tsr_retry > MAX_XMIT_RETRY) {
s->tsr_retry = MAX_XMIT_RETRY;
}
assert(s->watch_tag == 0);
s->watch_tag = qemu_chr_fe_add_watch(&s->chr, G_IO_OUT | G_IO_HUP,
serial_watch_cb, s);
} else {
/* tsr_retry == 0 implies LSR.TEMT = 1 (transmitter empty). */
if (!(s->lsr & UART_LSR_TEMT)) {
error_report("inconsistent state in serial device "
"(tsr not empty, tsr_retry=0");
return -1;
}
}
s->last_break_enable = !!(s->lcr & UART_LCR_SB);
/* Initialize fcr via setter to perform essential side-effects */
serial_write_fcr(s, s->fcr_vmstate);
serial_update_parameters(s);
return 0;
}
static bool serial_thr_ipending_needed(void *opaque)
{
SerialState *s = opaque;
if (s->ier & UART_IER_THRI) {
bool expected_value = ((s->iir & UART_IIR_ID) == UART_IIR_THRI);
return s->thr_ipending != expected_value;
} else {
/* LSR.THRE will be sampled again when the interrupt is
* enabled. thr_ipending is not used in this case, do
* not migrate it.
*/
return false;
}
}
static const VMStateDescription vmstate_serial_thr_ipending = {
.name = "serial/thr_ipending",
.version_id = 1,
.minimum_version_id = 1,
.needed = serial_thr_ipending_needed,
.fields = (const VMStateField[]) {
VMSTATE_INT32(thr_ipending, SerialState),
VMSTATE_END_OF_LIST()
}
};
static bool serial_tsr_needed(void *opaque)
{
SerialState *s = opaque;
return s->tsr_retry != 0;
}
static const VMStateDescription vmstate_serial_tsr = {
.name = "serial/tsr",
.version_id = 1,
.minimum_version_id = 1,
.needed = serial_tsr_needed,
.fields = (const VMStateField[]) {
VMSTATE_UINT32(tsr_retry, SerialState),
VMSTATE_UINT8(thr, SerialState),
VMSTATE_UINT8(tsr, SerialState),
VMSTATE_END_OF_LIST()
}
};
static bool serial_recv_fifo_needed(void *opaque)
{
SerialState *s = opaque;
return !fifo8_is_empty(&s->recv_fifo);
}
static const VMStateDescription vmstate_serial_recv_fifo = {
.name = "serial/recv_fifo",
.version_id = 1,
.minimum_version_id = 1,
.needed = serial_recv_fifo_needed,
.fields = (const VMStateField[]) {
VMSTATE_STRUCT(recv_fifo, SerialState, 1, vmstate_fifo8, Fifo8),
VMSTATE_END_OF_LIST()
}
};
static bool serial_xmit_fifo_needed(void *opaque)
{
SerialState *s = opaque;
return !fifo8_is_empty(&s->xmit_fifo);
}
static const VMStateDescription vmstate_serial_xmit_fifo = {
.name = "serial/xmit_fifo",
.version_id = 1,
.minimum_version_id = 1,
.needed = serial_xmit_fifo_needed,
.fields = (const VMStateField[]) {
VMSTATE_STRUCT(xmit_fifo, SerialState, 1, vmstate_fifo8, Fifo8),
VMSTATE_END_OF_LIST()
}
};
static bool serial_fifo_timeout_timer_needed(void *opaque)
{
SerialState *s = opaque;
return timer_pending(s->fifo_timeout_timer);
}
static const VMStateDescription vmstate_serial_fifo_timeout_timer = {
.name = "serial/fifo_timeout_timer",
.version_id = 1,
.minimum_version_id = 1,
.needed = serial_fifo_timeout_timer_needed,
.fields = (const VMStateField[]) {
VMSTATE_TIMER_PTR(fifo_timeout_timer, SerialState),
VMSTATE_END_OF_LIST()
}
};
static bool serial_timeout_ipending_needed(void *opaque)
{
SerialState *s = opaque;
return s->timeout_ipending != 0;
}
static const VMStateDescription vmstate_serial_timeout_ipending = {
.name = "serial/timeout_ipending",
.version_id = 1,
.minimum_version_id = 1,
.needed = serial_timeout_ipending_needed,
.fields = (const VMStateField[]) {
VMSTATE_INT32(timeout_ipending, SerialState),
VMSTATE_END_OF_LIST()
}
};
static bool serial_poll_needed(void *opaque)
{
SerialState *s = opaque;
return s->poll_msl >= 0;
}
static const VMStateDescription vmstate_serial_poll = {
.name = "serial/poll",
.version_id = 1,
.needed = serial_poll_needed,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_INT32(poll_msl, SerialState),
VMSTATE_TIMER_PTR(modem_status_poll, SerialState),
VMSTATE_END_OF_LIST()
}
};
const VMStateDescription vmstate_serial = {
.name = "serial",
.version_id = 3,
.minimum_version_id = 2,
.pre_save = serial_pre_save,
.pre_load = serial_pre_load,
.post_load = serial_post_load,
.fields = (const VMStateField[]) {
VMSTATE_UINT16_V(divider, SerialState, 2),
VMSTATE_UINT8(rbr, SerialState),
VMSTATE_UINT8(ier, SerialState),
VMSTATE_UINT8(iir, SerialState),
VMSTATE_UINT8(lcr, SerialState),
VMSTATE_UINT8(mcr, SerialState),
VMSTATE_UINT8(lsr, SerialState),
VMSTATE_UINT8(msr, SerialState),
VMSTATE_UINT8(scr, SerialState),
VMSTATE_UINT8_V(fcr_vmstate, SerialState, 3),
VMSTATE_END_OF_LIST()
},
.subsections = (const VMStateDescription * const []) {
&vmstate_serial_thr_ipending,
&vmstate_serial_tsr,
&vmstate_serial_recv_fifo,
&vmstate_serial_xmit_fifo,
&vmstate_serial_fifo_timeout_timer,
&vmstate_serial_timeout_ipending,
&vmstate_serial_poll,
NULL
}
};
static void serial_reset(void *opaque)
{
SerialState *s = opaque;
g_clear_handle_id(&s->watch_tag, g_source_remove);
s->rbr = 0;
s->ier = 0;
s->iir = UART_IIR_NO_INT;
s->lcr = 0;
s->lsr = UART_LSR_TEMT | UART_LSR_THRE;
s->msr = UART_MSR_DCD | UART_MSR_DSR | UART_MSR_CTS;
/* Default to 9600 baud, 1 start bit, 8 data bits, 1 stop bit, no parity. */
s->divider = 0x0C;
s->mcr = UART_MCR_OUT2;
s->scr = 0;
s->tsr_retry = 0;
s->char_transmit_time = (NANOSECONDS_PER_SECOND / 9600) * 10;
s->poll_msl = 0;
s->timeout_ipending = 0;
timer_del(s->fifo_timeout_timer);
timer_del(s->modem_status_poll);
fifo8_reset(&s->recv_fifo);
fifo8_reset(&s->xmit_fifo);
s->last_xmit_ts = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
s->thr_ipending = 0;
s->last_break_enable = 0;
qemu_irq_lower(s->irq);
serial_update_msl(s);
s->msr &= ~UART_MSR_ANY_DELTA;
}
static int serial_be_change(void *opaque)
{
SerialState *s = opaque;
qemu_chr_fe_set_handlers(&s->chr, serial_can_receive1, serial_receive1,
serial_event, serial_be_change, s, NULL, true);
serial_update_parameters(s);
qemu_chr_fe_ioctl(&s->chr, CHR_IOCTL_SERIAL_SET_BREAK,
&s->last_break_enable);
s->poll_msl = (s->ier & UART_IER_MSI) ? 1 : 0;
serial_update_msl(s);
if (s->poll_msl >= 0 && !(s->mcr & UART_MCR_LOOP)) {
serial_update_tiocm(s);
}
if (s->watch_tag > 0) {
g_source_remove(s->watch_tag);
s->watch_tag = qemu_chr_fe_add_watch(&s->chr, G_IO_OUT | G_IO_HUP,
serial_watch_cb, s);
}
return 0;
}
static void serial_realize(DeviceState *dev, Error **errp)
{
SerialState *s = SERIAL(dev);
s->modem_status_poll = timer_new_ns(QEMU_CLOCK_VIRTUAL, (QEMUTimerCB *) serial_update_msl, s);
s->fifo_timeout_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, (QEMUTimerCB *) fifo_timeout_int, s);
qemu_register_reset(serial_reset, s);
qemu_chr_fe_set_handlers(&s->chr, serial_can_receive1, serial_receive1,
serial_event, serial_be_change, s, NULL, true);
fifo8_create(&s->recv_fifo, UART_FIFO_LENGTH);
fifo8_create(&s->xmit_fifo, UART_FIFO_LENGTH);
}
static void serial_unrealize(DeviceState *dev)
{
SerialState *s = SERIAL(dev);
g_clear_handle_id(&s->watch_tag, g_source_remove);
qemu_chr_fe_deinit(&s->chr, false);
timer_free(s->modem_status_poll);
timer_free(s->fifo_timeout_timer);
fifo8_destroy(&s->recv_fifo);
fifo8_destroy(&s->xmit_fifo);
qemu_unregister_reset(serial_reset, s);
}
const MemoryRegionOps serial_io_ops = {
.read = serial_ioport_read,
.write = serial_ioport_write,
.valid = {
.unaligned = 1,
},
.impl = {
.min_access_size = 1,
.max_access_size = 1,
},
.endianness = DEVICE_LITTLE_ENDIAN,
};
static const Property serial_properties[] = {
DEFINE_PROP_CHR("chardev", SerialState, chr),
DEFINE_PROP_UINT32("baudbase", SerialState, baudbase, 115200),
DEFINE_PROP_BOOL("wakeup", SerialState, wakeup, false),
};
static void serial_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
/* internal device for serialio/serialmm, not user-creatable */
dc->user_creatable = false;
dc->realize = serial_realize;
dc->unrealize = serial_unrealize;
device_class_set_props(dc, serial_properties);
}
static const TypeInfo serial_info = {
.name = TYPE_SERIAL,
.parent = TYPE_DEVICE,
.instance_size = sizeof(SerialState),
.class_init = serial_class_init,
};
static void serial_register_types(void)
{
type_register_static(&serial_info);
}
type_init(serial_register_types)
+466
View File
@@ -0,0 +1,466 @@
/*
* QEMU SCI/SCIF serial port emulation
*
* Copyright (c) 2007 Magnus Damm
*
* Based on serial.c - QEMU 16450 UART emulation
* Copyright (c) 2003-2004 Fabrice Bellard
*
* 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
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* 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.
*/
#include "qemu/osdep.h"
#include "hw/core/sysbus.h"
#include "hw/core/irq.h"
#include "hw/core/qdev.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-properties-system.h"
#include "hw/sh4/sh.h"
#include "chardev/char-fe.h"
#include "qapi/error.h"
#include "qemu/timer.h"
#include "qemu/log.h"
#include "trace.h"
#define SH_SERIAL_FLAG_TEND (1 << 0)
#define SH_SERIAL_FLAG_TDE (1 << 1)
#define SH_SERIAL_FLAG_RDF (1 << 2)
#define SH_SERIAL_FLAG_BRK (1 << 3)
#define SH_SERIAL_FLAG_DR (1 << 4)
#define SH_RX_FIFO_LENGTH (16)
OBJECT_DECLARE_SIMPLE_TYPE(SHSerialState, SH_SERIAL)
struct SHSerialState {
SysBusDevice parent;
uint8_t smr;
uint8_t brr;
uint8_t scr;
uint8_t dr; /* ftdr / tdr */
uint8_t sr; /* fsr / ssr */
uint16_t fcr;
uint8_t sptr;
uint8_t rx_fifo[SH_RX_FIFO_LENGTH]; /* frdr / rdr */
uint8_t rx_cnt;
uint8_t rx_tail;
uint8_t rx_head;
uint8_t feat;
int flags;
int rtrg;
CharFrontend chr;
QEMUTimer fifo_timeout_timer;
uint64_t etu; /* Elementary Time Unit (ns) */
qemu_irq eri;
qemu_irq rxi;
qemu_irq txi;
qemu_irq tei;
qemu_irq bri;
};
static void sh_serial_clear_fifo(SHSerialState *s)
{
memset(s->rx_fifo, 0, SH_RX_FIFO_LENGTH);
s->rx_cnt = 0;
s->rx_head = 0;
s->rx_tail = 0;
}
static void sh_serial_write(void *opaque, hwaddr offs,
uint64_t val, unsigned size)
{
SHSerialState *s = opaque;
DeviceState *d = DEVICE(s);
unsigned char ch;
trace_sh_serial_write(d->id, size, offs, val);
switch (offs) {
case 0x00: /* SMR */
s->smr = val & ((s->feat & SH_SERIAL_FEAT_SCIF) ? 0x7b : 0xff);
return;
case 0x04: /* BRR */
s->brr = val;
return;
case 0x08: /* SCR */
/* TODO : For SH7751, SCIF mask should be 0xfb. */
s->scr = val & ((s->feat & SH_SERIAL_FEAT_SCIF) ? 0xfa : 0xff);
if (!(val & (1 << 5))) {
s->flags |= SH_SERIAL_FLAG_TEND;
}
if ((s->feat & SH_SERIAL_FEAT_SCIF) && s->txi) {
qemu_set_irq(s->txi, val & (1 << 7));
}
if (!(val & (1 << 6))) {
qemu_set_irq(s->rxi, 0);
}
return;
case 0x0c: /* FTDR / TDR */
if (qemu_chr_fe_backend_connected(&s->chr)) {
ch = val;
/*
* XXX this blocks entire thread. Rewrite to use
* qemu_chr_fe_write and background I/O callbacks
*/
qemu_chr_fe_write_all(&s->chr, &ch, 1);
}
s->dr = val;
s->flags &= ~SH_SERIAL_FLAG_TDE;
return;
#if 0
case 0x14: /* FRDR / RDR */
ret = 0;
break;
#endif
}
if (s->feat & SH_SERIAL_FEAT_SCIF) {
switch (offs) {
case 0x10: /* FSR */
if (!(val & (1 << 6))) {
s->flags &= ~SH_SERIAL_FLAG_TEND;
}
if (!(val & (1 << 5))) {
s->flags &= ~SH_SERIAL_FLAG_TDE;
}
if (!(val & (1 << 4))) {
s->flags &= ~SH_SERIAL_FLAG_BRK;
}
if (!(val & (1 << 1))) {
s->flags &= ~SH_SERIAL_FLAG_RDF;
}
if (!(val & (1 << 0))) {
s->flags &= ~SH_SERIAL_FLAG_DR;
}
if (!(val & (1 << 1)) || !(val & (1 << 0))) {
if (s->rxi) {
qemu_set_irq(s->rxi, 0);
}
}
return;
case 0x18: /* FCR */
s->fcr = val;
switch ((val >> 6) & 3) {
case 0:
s->rtrg = 1;
break;
case 1:
s->rtrg = 4;
break;
case 2:
s->rtrg = 8;
break;
case 3:
s->rtrg = 14;
break;
}
if (val & (1 << 1)) {
sh_serial_clear_fifo(s);
s->sr &= ~(1 << 1);
}
return;
case 0x20: /* SPTR */
s->sptr = val & 0xf3;
return;
case 0x24: /* LSR */
return;
}
} else {
switch (offs) {
#if 0
case 0x0c:
ret = s->dr;
break;
case 0x10:
ret = 0;
break;
#endif
case 0x1c:
s->sptr = val & 0x8f;
return;
}
}
qemu_log_mask(LOG_GUEST_ERROR,
"%s: unsupported write to 0x%02" HWADDR_PRIx "\n",
__func__, offs);
}
static uint64_t sh_serial_read(void *opaque, hwaddr offs,
unsigned size)
{
SHSerialState *s = opaque;
DeviceState *d = DEVICE(s);
uint32_t ret = UINT32_MAX;
#if 0
switch (offs) {
case 0x00:
ret = s->smr;
break;
case 0x04:
ret = s->brr;
break;
case 0x08:
ret = s->scr;
break;
case 0x14:
ret = 0;
break;
}
#endif
if (s->feat & SH_SERIAL_FEAT_SCIF) {
switch (offs) {
case 0x00: /* SMR */
ret = s->smr;
break;
case 0x08: /* SCR */
ret = s->scr;
break;
case 0x10: /* FSR */
ret = 0;
if (s->flags & SH_SERIAL_FLAG_TEND) {
ret |= (1 << 6);
}
if (s->flags & SH_SERIAL_FLAG_TDE) {
ret |= (1 << 5);
}
if (s->flags & SH_SERIAL_FLAG_BRK) {
ret |= (1 << 4);
}
if (s->flags & SH_SERIAL_FLAG_RDF) {
ret |= (1 << 1);
}
if (s->flags & SH_SERIAL_FLAG_DR) {
ret |= (1 << 0);
}
if (s->scr & (1 << 5)) {
s->flags |= SH_SERIAL_FLAG_TDE | SH_SERIAL_FLAG_TEND;
}
break;
case 0x14:
if (s->rx_cnt > 0) {
ret = s->rx_fifo[s->rx_tail++];
s->rx_cnt--;
if (s->rx_tail == SH_RX_FIFO_LENGTH) {
s->rx_tail = 0;
}
if (s->rx_cnt < s->rtrg) {
s->flags &= ~SH_SERIAL_FLAG_RDF;
}
}
break;
case 0x18:
ret = s->fcr;
break;
case 0x1c:
ret = s->rx_cnt;
break;
case 0x20:
ret = s->sptr;
break;
case 0x24:
ret = 0;
break;
}
} else {
switch (offs) {
#if 0
case 0x0c:
ret = s->dr;
break;
case 0x10:
ret = 0;
break;
case 0x14:
ret = s->rx_fifo[0];
break;
#endif
case 0x1c:
ret = s->sptr;
break;
}
}
trace_sh_serial_read(d->id, size, offs, ret);
if (ret > UINT16_MAX) {
qemu_log_mask(LOG_GUEST_ERROR,
"%s: unsupported read from 0x%02" HWADDR_PRIx "\n",
__func__, offs);
ret = 0;
}
return ret;
}
static int sh_serial_can_receive(SHSerialState *s)
{
return s->scr & (1 << 4) ? SH_RX_FIFO_LENGTH - s->rx_head : 0;
}
static void sh_serial_receive_break(SHSerialState *s)
{
if (s->feat & SH_SERIAL_FEAT_SCIF) {
s->sr |= (1 << 4);
}
}
static int sh_serial_can_receive1(void *opaque)
{
SHSerialState *s = opaque;
return sh_serial_can_receive(s);
}
static void sh_serial_timeout_int(void *opaque)
{
SHSerialState *s = opaque;
s->flags |= SH_SERIAL_FLAG_RDF;
if (s->scr & (1 << 6) && s->rxi) {
qemu_set_irq(s->rxi, 1);
}
}
static void sh_serial_receive1(void *opaque, const uint8_t *buf, int size)
{
SHSerialState *s = opaque;
if (s->feat & SH_SERIAL_FEAT_SCIF) {
int i;
for (i = 0; i < size; i++) {
s->rx_fifo[s->rx_head++] = buf[i];
if (s->rx_head == SH_RX_FIFO_LENGTH) {
s->rx_head = 0;
}
s->rx_cnt++;
if (s->rx_cnt >= s->rtrg) {
s->flags |= SH_SERIAL_FLAG_RDF;
if (s->scr & (1 << 6) && s->rxi) {
timer_del(&s->fifo_timeout_timer);
qemu_set_irq(s->rxi, 1);
}
} else {
timer_mod(&s->fifo_timeout_timer,
qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 15 * s->etu);
}
}
} else {
s->rx_fifo[0] = buf[0];
}
}
static void sh_serial_event(void *opaque, QEMUChrEvent event)
{
SHSerialState *s = opaque;
if (event == CHR_EVENT_BREAK) {
sh_serial_receive_break(s);
}
}
static const MemoryRegionOps sh_serial_ops = {
.read = sh_serial_read,
.write = sh_serial_write,
.endianness = DEVICE_NATIVE_ENDIAN,
};
static void sh_serial_reset(DeviceState *dev)
{
SHSerialState *s = SH_SERIAL(dev);
s->flags = SH_SERIAL_FLAG_TEND | SH_SERIAL_FLAG_TDE;
s->rtrg = 1;
s->smr = 0;
s->brr = 0xff;
s->scr = 1 << 5; /* pretend that TX is enabled so early printk works */
s->sptr = 0;
if (s->feat & SH_SERIAL_FEAT_SCIF) {
s->fcr = 0;
} else {
s->dr = 0xff;
}
sh_serial_clear_fifo(s);
}
static void sh_serial_realize(DeviceState *d, Error **errp)
{
SHSerialState *s = SH_SERIAL(d);
MemoryRegion *iomem = g_malloc(sizeof(*iomem));
assert(d->id);
memory_region_init_io(iomem, OBJECT(d), &sh_serial_ops, s, d->id, 0x28);
sysbus_init_mmio(SYS_BUS_DEVICE(d), iomem);
qdev_init_gpio_out_named(d, &s->eri, "eri", 1);
qdev_init_gpio_out_named(d, &s->rxi, "rxi", 1);
qdev_init_gpio_out_named(d, &s->txi, "txi", 1);
qdev_init_gpio_out_named(d, &s->tei, "tei", 1);
qdev_init_gpio_out_named(d, &s->bri, "bri", 1);
if (qemu_chr_fe_backend_connected(&s->chr)) {
qemu_chr_fe_set_handlers(&s->chr, sh_serial_can_receive1,
sh_serial_receive1,
sh_serial_event, NULL, s, NULL, true);
}
timer_init_ns(&s->fifo_timeout_timer, QEMU_CLOCK_VIRTUAL,
sh_serial_timeout_int, s);
s->etu = NANOSECONDS_PER_SECOND / 9600;
}
static void sh_serial_unrealize(DeviceState *dev)
{
SHSerialState *s = SH_SERIAL(dev);
timer_del(&s->fifo_timeout_timer);
}
static const Property sh_serial_properties[] = {
DEFINE_PROP_CHR("chardev", SHSerialState, chr),
DEFINE_PROP_UINT8("features", SHSerialState, feat, 0),
};
static void sh_serial_class_init(ObjectClass *oc, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(oc);
device_class_set_props(dc, sh_serial_properties);
dc->realize = sh_serial_realize;
dc->unrealize = sh_serial_unrealize;
device_class_set_legacy_reset(dc, sh_serial_reset);
/* Reason: part of SuperH CPU/SoC, needs to be wired up */
dc->user_creatable = false;
}
static const TypeInfo sh_serial_types[] = {
{
.name = TYPE_SH_SERIAL,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(SHSerialState),
.class_init = sh_serial_class_init,
},
};
DEFINE_TYPES(sh_serial_types)
+185
View File
@@ -0,0 +1,185 @@
/*
* SHAKTI UART
*
* Copyright (c) 2021 Vijai Kumar K <[email protected]>
*
* 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
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* 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.
*/
#include "qemu/osdep.h"
#include "hw/char/shakti_uart.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-properties-system.h"
#include "qemu/log.h"
static uint64_t shakti_uart_read(void *opaque, hwaddr addr, unsigned size)
{
ShaktiUartState *s = opaque;
switch (addr) {
case SHAKTI_UART_BAUD:
return s->uart_baud;
case SHAKTI_UART_RX:
qemu_chr_fe_accept_input(&s->chr);
s->uart_status &= ~SHAKTI_UART_STATUS_RX_NOT_EMPTY;
return s->uart_rx;
case SHAKTI_UART_STATUS:
return s->uart_status;
case SHAKTI_UART_DELAY:
return s->uart_delay;
case SHAKTI_UART_CONTROL:
return s->uart_control;
case SHAKTI_UART_INT_EN:
return s->uart_interrupt;
case SHAKTI_UART_IQ_CYCLES:
return s->uart_iq_cycles;
case SHAKTI_UART_RX_THRES:
return s->uart_rx_threshold;
default:
/* Also handles TX REG which is write only */
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr);
}
return 0;
}
static void shakti_uart_write(void *opaque, hwaddr addr,
uint64_t data, unsigned size)
{
ShaktiUartState *s = opaque;
uint32_t value = data;
uint8_t ch;
switch (addr) {
case SHAKTI_UART_BAUD:
s->uart_baud = value;
break;
case SHAKTI_UART_TX:
ch = value;
qemu_chr_fe_write_all(&s->chr, &ch, 1);
s->uart_status &= ~SHAKTI_UART_STATUS_TX_FULL;
break;
case SHAKTI_UART_STATUS:
s->uart_status = value;
break;
case SHAKTI_UART_DELAY:
s->uart_delay = value;
break;
case SHAKTI_UART_CONTROL:
s->uart_control = value;
break;
case SHAKTI_UART_INT_EN:
s->uart_interrupt = value;
break;
case SHAKTI_UART_IQ_CYCLES:
s->uart_iq_cycles = value;
break;
case SHAKTI_UART_RX_THRES:
s->uart_rx_threshold = value;
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr);
}
}
static const MemoryRegionOps shakti_uart_ops = {
.read = shakti_uart_read,
.write = shakti_uart_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.impl = {.min_access_size = 1, .max_access_size = 4},
.valid = {.min_access_size = 1, .max_access_size = 4},
};
static void shakti_uart_reset(DeviceState *dev)
{
ShaktiUartState *s = SHAKTI_UART(dev);
s->uart_baud = SHAKTI_UART_BAUD_DEFAULT;
s->uart_tx = 0x0;
s->uart_rx = 0x0;
s->uart_status = 0x0000;
s->uart_delay = 0x0000;
s->uart_control = SHAKTI_UART_CONTROL_DEFAULT;
s->uart_interrupt = 0x0000;
s->uart_iq_cycles = 0x00;
s->uart_rx_threshold = 0x00;
}
static int shakti_uart_can_receive(void *opaque)
{
ShaktiUartState *s = opaque;
return !(s->uart_status & SHAKTI_UART_STATUS_RX_NOT_EMPTY);
}
static void shakti_uart_receive(void *opaque, const uint8_t *buf, int size)
{
ShaktiUartState *s = opaque;
s->uart_rx = *buf;
s->uart_status |= SHAKTI_UART_STATUS_RX_NOT_EMPTY;
}
static void shakti_uart_realize(DeviceState *dev, Error **errp)
{
ShaktiUartState *sus = SHAKTI_UART(dev);
qemu_chr_fe_set_handlers(&sus->chr, shakti_uart_can_receive,
shakti_uart_receive, NULL, NULL, sus, NULL, true);
}
static void shakti_uart_instance_init(Object *obj)
{
ShaktiUartState *sus = SHAKTI_UART(obj);
memory_region_init_io(&sus->mmio,
obj,
&shakti_uart_ops,
sus,
TYPE_SHAKTI_UART,
0x1000);
sysbus_init_mmio(SYS_BUS_DEVICE(obj), &sus->mmio);
}
static const Property shakti_uart_properties[] = {
DEFINE_PROP_CHR("chardev", ShaktiUartState, chr),
};
static void shakti_uart_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
device_class_set_legacy_reset(dc, shakti_uart_reset);
dc->realize = shakti_uart_realize;
device_class_set_props(dc, shakti_uart_properties);
set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
}
static const TypeInfo shakti_uart_info = {
.name = TYPE_SHAKTI_UART,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(ShaktiUartState),
.class_init = shakti_uart_class_init,
.instance_init = shakti_uart_instance_init,
};
static void shakti_uart_register_types(void)
{
type_register_static(&shakti_uart_info);
}
type_init(shakti_uart_register_types)
+411
View File
@@ -0,0 +1,411 @@
/*
* QEMU model of the UART on the SiFive E300 and U500 series SOCs.
*
* Copyright (c) 2016 Stefan O'Rear
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2 or later, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qemu/log.h"
#include "migration/vmstate.h"
#include "chardev/char.h"
#include "chardev/char-fe.h"
#include "hw/core/irq.h"
#include "hw/char/sifive_uart.h"
#include "hw/core/qdev-properties-system.h"
#define TX_INTERRUPT_TRIGGER_DELAY_NS 100
/* Returns the state of the IP (interrupt pending) register */
static uint32_t sifive_uart_ip(SiFiveUARTState *s)
{
uint32_t ret = 0;
uint32_t txcnt = SIFIVE_UART_GET_TXCNT(s->txctrl);
uint32_t rxcnt = SIFIVE_UART_GET_RXCNT(s->rxctrl);
if (fifo8_num_used(&s->tx_fifo) < txcnt) {
ret |= SIFIVE_UART_IP_TXWM;
}
if (s->rx_fifo_len > rxcnt) {
ret |= SIFIVE_UART_IP_RXWM;
}
return ret;
}
static void sifive_uart_update_irq(SiFiveUARTState *s)
{
int cond = 0;
uint32_t ip = sifive_uart_ip(s);
if (((ip & SIFIVE_UART_IP_TXWM) && (s->ie & SIFIVE_UART_IE_TXWM)) ||
((ip & SIFIVE_UART_IP_RXWM) && (s->ie & SIFIVE_UART_IE_RXWM))) {
cond = 1;
}
qemu_set_irq(s->irq, cond);
}
static gboolean sifive_uart_xmit(void *do_not_use, GIOCondition cond,
void *opaque)
{
SiFiveUARTState *s = opaque;
int ret;
const uint8_t *characters;
uint32_t numptr = 0;
/* instant drain the fifo when there's no back-end */
if (!qemu_chr_fe_backend_connected(&s->chr)) {
fifo8_reset(&s->tx_fifo);
return G_SOURCE_REMOVE;
}
if (fifo8_is_empty(&s->tx_fifo)) {
return G_SOURCE_REMOVE;
}
/* Don't pop the FIFO if transmit is disabled. */
if (!SIFIVE_UART_TXEN(s->txctrl)) {
return G_SOURCE_REMOVE;
}
/* Don't pop the FIFO in case the write fails */
characters = fifo8_peek_bufptr(&s->tx_fifo,
fifo8_num_used(&s->tx_fifo), &numptr);
ret = qemu_chr_fe_write(&s->chr, characters, numptr);
if (ret >= 0) {
/* We wrote the data, actually pop the fifo */
fifo8_pop_bufptr(&s->tx_fifo, ret, NULL);
}
if (!fifo8_is_empty(&s->tx_fifo)) {
guint r = qemu_chr_fe_add_watch(&s->chr, G_IO_OUT | G_IO_HUP,
sifive_uart_xmit, s);
if (!r) {
fifo8_reset(&s->tx_fifo);
return G_SOURCE_REMOVE;
}
}
/* Clear the TX Full bit */
if (!fifo8_is_full(&s->tx_fifo)) {
s->txfifo &= ~SIFIVE_UART_TXFIFO_FULL;
}
sifive_uart_update_irq(s);
return G_SOURCE_REMOVE;
}
static void sifive_uart_trigger_tx_fifo(SiFiveUARTState *s)
{
uint64_t current_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
if (!timer_pending(s->fifo_trigger_handle)) {
timer_mod(s->fifo_trigger_handle, current_time +
TX_INTERRUPT_TRIGGER_DELAY_NS);
}
}
static void sifive_uart_write_tx_fifo(SiFiveUARTState *s, const uint8_t *buf,
int size)
{
uint32_t txcnt = SIFIVE_UART_GET_TXCNT(s->txctrl);
bool update_irq = false;
if (size > fifo8_num_free(&s->tx_fifo)) {
size = fifo8_num_free(&s->tx_fifo);
qemu_log_mask(LOG_GUEST_ERROR, "sifive_uart: TX FIFO overflow.\n");
}
if (size > 0) {
if (fifo8_num_used(&s->tx_fifo) < txcnt &&
(fifo8_num_used(&s->tx_fifo) + size) >= txcnt) {
update_irq = true;
}
fifo8_push_all(&s->tx_fifo, buf, size);
}
if (fifo8_is_full(&s->tx_fifo)) {
s->txfifo |= SIFIVE_UART_TXFIFO_FULL;
}
/*
* Update txwm interrupt pending status when the number of entries
* in the transmit FIFO crosses or reaches the watermark.
*/
if (update_irq) {
sifive_uart_update_irq(s);
}
sifive_uart_trigger_tx_fifo(s);
}
static uint64_t
sifive_uart_read(void *opaque, hwaddr addr, unsigned int size)
{
SiFiveUARTState *s = opaque;
unsigned char r;
switch (addr) {
case SIFIVE_UART_RXFIFO:
if (s->rx_fifo_len) {
r = s->rx_fifo[0];
memmove(s->rx_fifo, s->rx_fifo + 1, s->rx_fifo_len - 1);
s->rx_fifo_len--;
qemu_chr_fe_accept_input(&s->chr);
sifive_uart_update_irq(s);
return r;
}
return 0x80000000;
case SIFIVE_UART_TXFIFO:
return s->txfifo;
case SIFIVE_UART_IE:
return s->ie;
case SIFIVE_UART_IP:
return sifive_uart_ip(s);
case SIFIVE_UART_TXCTRL:
return s->txctrl;
case SIFIVE_UART_RXCTRL:
return s->rxctrl;
case SIFIVE_UART_DIV:
return s->div;
}
qemu_log_mask(LOG_GUEST_ERROR, "%s: bad read: addr=0x%x\n",
__func__, (int)addr);
return 0;
}
static void
sifive_uart_write(void *opaque, hwaddr addr,
uint64_t val64, unsigned int size)
{
SiFiveUARTState *s = opaque;
uint32_t value = val64;
uint8_t ch = value;
switch (addr) {
case SIFIVE_UART_TXFIFO:
sifive_uart_write_tx_fifo(s, &ch, 1);
return;
case SIFIVE_UART_IE:
s->ie = val64;
sifive_uart_update_irq(s);
return;
case SIFIVE_UART_TXCTRL:
s->txctrl = val64;
if (SIFIVE_UART_TXEN(s->txctrl) && !fifo8_is_empty(&s->tx_fifo)) {
sifive_uart_trigger_tx_fifo(s);
}
sifive_uart_update_irq(s);
return;
case SIFIVE_UART_RXCTRL:
s->rxctrl = val64;
sifive_uart_update_irq(s);
return;
case SIFIVE_UART_DIV:
s->div = val64;
return;
}
qemu_log_mask(LOG_GUEST_ERROR, "%s: bad write: addr=0x%x v=0x%x\n",
__func__, (int)addr, (int)value);
}
static void fifo_trigger_update(void *opaque)
{
SiFiveUARTState *s = opaque;
sifive_uart_xmit(NULL, G_IO_OUT, s);
}
static const MemoryRegionOps sifive_uart_ops = {
.read = sifive_uart_read,
.write = sifive_uart_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.valid = {
.min_access_size = 4,
.max_access_size = 4
}
};
static void sifive_uart_rx(void *opaque, const uint8_t *buf, int size)
{
SiFiveUARTState *s = opaque;
/* Got a byte. */
if (s->rx_fifo_len >= sizeof(s->rx_fifo)) {
printf("WARNING: UART dropped char.\n");
return;
}
s->rx_fifo[s->rx_fifo_len++] = *buf;
sifive_uart_update_irq(s);
}
static int sifive_uart_can_rx(void *opaque)
{
SiFiveUARTState *s = opaque;
return SIFIVE_UART_RXEN(s->rxctrl) && (s->rx_fifo_len < sizeof(s->rx_fifo));
}
static void sifive_uart_event(void *opaque, QEMUChrEvent event)
{
}
static int sifive_uart_be_change(void *opaque)
{
SiFiveUARTState *s = opaque;
qemu_chr_fe_set_handlers(&s->chr, sifive_uart_can_rx, sifive_uart_rx,
sifive_uart_event, sifive_uart_be_change, s,
NULL, true);
return 0;
}
static void sifive_uart_reset_enter(Object *obj, ResetType type)
{
SiFiveUARTState *s = SIFIVE_UART(obj);
s->txfifo = 0;
s->ie = 0;
s->txctrl = 0;
s->rxctrl = 0;
s->div = 0;
s->rx_fifo_len = 0;
memset(s->rx_fifo, 0, SIFIVE_UART_RX_FIFO_SIZE);
fifo8_reset(&s->tx_fifo);
}
static const Property sifive_uart_properties[] = {
DEFINE_PROP_CHR("chardev", SiFiveUARTState, chr),
};
static void sifive_uart_init(Object *obj)
{
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
SiFiveUARTState *s = SIFIVE_UART(obj);
memory_region_init_io(&s->mmio, OBJECT(s), &sifive_uart_ops, s,
TYPE_SIFIVE_UART, SIFIVE_UART_MAX);
sysbus_init_mmio(sbd, &s->mmio);
sysbus_init_irq(sbd, &s->irq);
}
static void sifive_uart_realize(DeviceState *dev, Error **errp)
{
SiFiveUARTState *s = SIFIVE_UART(dev);
fifo8_create(&s->tx_fifo, SIFIVE_UART_TX_FIFO_SIZE);
s->fifo_trigger_handle = timer_new_ns(QEMU_CLOCK_VIRTUAL,
fifo_trigger_update, s);
if (qemu_chr_fe_backend_connected(&s->chr)) {
qemu_chr_fe_set_handlers(&s->chr, sifive_uart_can_rx, sifive_uart_rx,
sifive_uart_event, sifive_uart_be_change, s,
NULL, true);
}
}
static void sifive_uart_unrealize(DeviceState *dev)
{
SiFiveUARTState *s = SIFIVE_UART(dev);
fifo8_destroy(&s->tx_fifo);
}
static void sifive_uart_reset_hold(Object *obj, ResetType type)
{
SiFiveUARTState *s = SIFIVE_UART(obj);
qemu_irq_lower(s->irq);
}
static const VMStateDescription vmstate_sifive_uart = {
.name = TYPE_SIFIVE_UART,
.version_id = 3,
.minimum_version_id = 3,
.fields = (const VMStateField[]) {
VMSTATE_UINT8_ARRAY(rx_fifo, SiFiveUARTState,
SIFIVE_UART_RX_FIFO_SIZE),
VMSTATE_UINT8(rx_fifo_len, SiFiveUARTState),
VMSTATE_UINT32(ie, SiFiveUARTState),
VMSTATE_UINT32(txctrl, SiFiveUARTState),
VMSTATE_UINT32(rxctrl, SiFiveUARTState),
VMSTATE_UINT32(div, SiFiveUARTState),
VMSTATE_UINT32(txfifo, SiFiveUARTState),
VMSTATE_FIFO8(tx_fifo, SiFiveUARTState),
VMSTATE_TIMER_PTR(fifo_trigger_handle, SiFiveUARTState),
VMSTATE_END_OF_LIST()
},
};
static void sifive_uart_class_init(ObjectClass *oc, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(oc);
ResettableClass *rc = RESETTABLE_CLASS(oc);
dc->realize = sifive_uart_realize;
dc->unrealize = sifive_uart_unrealize;
dc->vmsd = &vmstate_sifive_uart;
rc->phases.enter = sifive_uart_reset_enter;
rc->phases.hold = sifive_uart_reset_hold;
device_class_set_props(dc, sifive_uart_properties);
set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
}
static const TypeInfo sifive_uart_info = {
.name = TYPE_SIFIVE_UART,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(SiFiveUARTState),
.instance_init = sifive_uart_init,
.class_init = sifive_uart_class_init,
};
static void sifive_uart_register_types(void)
{
type_register_static(&sifive_uart_info);
}
type_init(sifive_uart_register_types)
/*
* Create UART device.
*/
SiFiveUARTState *sifive_uart_create(MemoryRegion *address_space, hwaddr base,
Chardev *chr, qemu_irq irq)
{
DeviceState *dev;
SysBusDevice *s;
dev = qdev_new("riscv.sifive.uart");
s = SYS_BUS_DEVICE(dev);
qdev_prop_set_chr(dev, "chardev", chr);
sysbus_realize_and_unref(s, &error_fatal);
memory_region_add_subregion(address_space, base,
sysbus_mmio_get_region(s, 0));
sysbus_connect_irq(s, 0, irq);
return SIFIVE_UART(dev);
}
+271
View File
@@ -0,0 +1,271 @@
#include "qemu/osdep.h"
#include "qemu/error-report.h"
#include "qemu/module.h"
#include "qapi/error.h"
#include "migration/vmstate.h"
#include "chardev/char-fe.h"
#include "hw/ppc/spapr.h"
#include "hw/ppc/spapr_vio.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-properties-system.h"
#include "qom/object.h"
#define VTERM_BUFSIZE 16
struct SpaprVioVty {
SpaprVioDevice sdev;
CharFrontend chardev;
uint32_t in, out;
uint8_t buf[VTERM_BUFSIZE];
};
#define TYPE_VIO_SPAPR_VTY_DEVICE "spapr-vty"
OBJECT_DECLARE_SIMPLE_TYPE(SpaprVioVty, VIO_SPAPR_VTY_DEVICE)
static int vty_can_receive(void *opaque)
{
SpaprVioVty *dev = VIO_SPAPR_VTY_DEVICE(opaque);
return VTERM_BUFSIZE - (dev->in - dev->out);
}
static void vty_receive(void *opaque, const uint8_t *buf, int size)
{
SpaprVioVty *dev = VIO_SPAPR_VTY_DEVICE(opaque);
int i;
if ((dev->in == dev->out) && size) {
/* toggle line to simulate edge interrupt */
spapr_vio_irq_pulse(&dev->sdev);
}
for (i = 0; i < size; i++) {
if (dev->in - dev->out >= VTERM_BUFSIZE) {
static bool reported;
if (!reported) {
error_report("VTY input buffer exhausted - characters dropped."
" (input size = %i)", size);
reported = true;
}
break;
}
dev->buf[dev->in++ % VTERM_BUFSIZE] = buf[i];
}
}
static int vty_getchars(SpaprVioDevice *sdev, uint8_t *buf, int max)
{
SpaprVioVty *dev = VIO_SPAPR_VTY_DEVICE(sdev);
int n = 0;
while ((n < max) && (dev->out != dev->in)) {
/*
* Long ago, PowerVM's vty implementation had a bug where it
* inserted a \0 after every \r going to the guest. Existing
* guests have a workaround for this which removes every \0
* immediately following a \r. To avoid triggering this
* workaround, we stop before inserting a \0 if the preceding
* character in the output buffer is a \r.
*/
if (n > 0 && (buf[n - 1] == '\r') &&
(dev->buf[dev->out % VTERM_BUFSIZE] == '\0')) {
break;
}
buf[n++] = dev->buf[dev->out++ % VTERM_BUFSIZE];
}
qemu_chr_fe_accept_input(&dev->chardev);
return n;
}
void vty_putchars(SpaprVioDevice *sdev, uint8_t *buf, int len)
{
SpaprVioVty *dev = VIO_SPAPR_VTY_DEVICE(sdev);
/* XXX this blocks entire thread. Rewrite to use
* qemu_chr_fe_write and background I/O callbacks */
qemu_chr_fe_write_all(&dev->chardev, buf, len);
}
static void spapr_vty_realize(SpaprVioDevice *sdev, Error **errp)
{
SpaprVioVty *dev = VIO_SPAPR_VTY_DEVICE(sdev);
if (!qemu_chr_fe_backend_connected(&dev->chardev)) {
error_setg(errp, "chardev property not set");
return;
}
qemu_chr_fe_set_handlers(&dev->chardev, vty_can_receive,
vty_receive, NULL, NULL, dev, NULL, true);
}
/* Forward declaration */
static target_ulong h_put_term_char(PowerPCCPU *cpu, SpaprMachineState *spapr,
target_ulong opcode, target_ulong *args)
{
target_ulong reg = args[0];
target_ulong len = args[1];
target_ulong char0_7 = args[2];
target_ulong char8_15 = args[3];
SpaprVioDevice *sdev;
uint8_t buf[16];
sdev = vty_lookup(spapr, reg);
if (!sdev) {
return H_PARAMETER;
}
if (len > 16) {
return H_PARAMETER;
}
*((uint64_t *)buf) = cpu_to_be64(char0_7);
*((uint64_t *)buf + 1) = cpu_to_be64(char8_15);
vty_putchars(sdev, buf, len);
return H_SUCCESS;
}
static target_ulong h_get_term_char(PowerPCCPU *cpu, SpaprMachineState *spapr,
target_ulong opcode, target_ulong *args)
{
target_ulong reg = args[0];
target_ulong *len = args + 0;
target_ulong *char0_7 = args + 1;
target_ulong *char8_15 = args + 2;
SpaprVioDevice *sdev;
uint8_t buf[16];
sdev = vty_lookup(spapr, reg);
if (!sdev) {
return H_PARAMETER;
}
*len = vty_getchars(sdev, buf, sizeof(buf));
if (*len < 16) {
memset(buf + *len, 0, 16 - *len);
}
*char0_7 = be64_to_cpu(*((uint64_t *)buf));
*char8_15 = be64_to_cpu(*((uint64_t *)buf + 1));
return H_SUCCESS;
}
void spapr_vty_create(SpaprVioBus *bus, Chardev *chardev)
{
DeviceState *dev;
dev = qdev_new("spapr-vty");
qdev_prop_set_chr(dev, "chardev", chardev);
qdev_realize_and_unref(dev, &bus->bus, &error_fatal);
}
static const Property spapr_vty_properties[] = {
DEFINE_SPAPR_PROPERTIES(SpaprVioVty, sdev),
DEFINE_PROP_CHR("chardev", SpaprVioVty, chardev),
};
static const VMStateDescription vmstate_spapr_vty = {
.name = "spapr_vty",
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_SPAPR_VIO(sdev, SpaprVioVty),
VMSTATE_UINT32(in, SpaprVioVty),
VMSTATE_UINT32(out, SpaprVioVty),
VMSTATE_BUFFER(buf, SpaprVioVty),
VMSTATE_END_OF_LIST()
},
};
static void spapr_vty_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
SpaprVioDeviceClass *k = VIO_SPAPR_DEVICE_CLASS(klass);
k->realize = spapr_vty_realize;
k->dt_name = "vty";
k->dt_type = "serial";
k->dt_compatible = "hvterm1";
set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
device_class_set_props(dc, spapr_vty_properties);
dc->vmsd = &vmstate_spapr_vty;
}
static const TypeInfo spapr_vty_info = {
.name = TYPE_VIO_SPAPR_VTY_DEVICE,
.parent = TYPE_VIO_SPAPR_DEVICE,
.instance_size = sizeof(SpaprVioVty),
.class_init = spapr_vty_class_init,
};
SpaprVioDevice *spapr_vty_get_default(SpaprVioBus *bus)
{
SpaprVioDevice *sdev, *selected;
BusChild *kid;
/*
* To avoid the console bouncing around we want one VTY to be
* the "default". We haven't really got anything to go on, so
* arbitrarily choose the one with the lowest reg value.
*/
selected = NULL;
QTAILQ_FOREACH(kid, &bus->bus.children, sibling) {
DeviceState *iter = kid->child;
/* Only look at VTY devices */
if (!object_dynamic_cast(OBJECT(iter), TYPE_VIO_SPAPR_VTY_DEVICE)) {
continue;
}
sdev = VIO_SPAPR_DEVICE(iter);
/* First VTY we've found, so it is selected for now */
if (!selected) {
selected = sdev;
continue;
}
/* Choose VTY with lowest reg value */
if (sdev->reg < selected->reg) {
selected = sdev;
}
}
return selected;
}
SpaprVioDevice *vty_lookup(SpaprMachineState *spapr, target_ulong reg)
{
SpaprVioDevice *sdev;
sdev = spapr_vio_find_by_reg(spapr->vio_bus, reg);
if (!sdev && reg == 0) {
/* Hack for kernel early debug, which always specifies reg==0.
* We search all VIO devices, and grab the vty with the lowest
* reg. This attempts to mimic existing PowerVM behaviour
* (early debug does work there, despite having no vty with
* reg==0. */
return spapr_vty_get_default(spapr->vio_bus);
}
if (!object_dynamic_cast(OBJECT(sdev), TYPE_VIO_SPAPR_VTY_DEVICE)) {
return NULL;
}
return sdev;
}
static void spapr_vty_register_types(void)
{
spapr_register_hypercall(H_PUT_TERM_CHAR, h_put_term_char);
spapr_register_hypercall(H_GET_TERM_CHAR, h_get_term_char);
type_register_static(&spapr_vty_info);
}
type_init(spapr_vty_register_types)
+245
View File
@@ -0,0 +1,245 @@
/*
* STM32F2XX USART
*
* Copyright (c) 2014 Alistair Francis <[email protected]>
*
* 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
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* 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.
*/
#include "qemu/osdep.h"
#include "hw/char/stm32f2xx_usart.h"
#include "hw/core/irq.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-properties-system.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "trace.h"
static int stm32f2xx_usart_can_receive(void *opaque)
{
STM32F2XXUsartState *s = opaque;
if (!(s->usart_sr & USART_SR_RXNE)) {
return 1;
}
return 0;
}
static void stm32f2xx_update_irq(STM32F2XXUsartState *s)
{
uint32_t mask = s->usart_sr & s->usart_cr1;
if (mask & (USART_SR_TXE | USART_SR_TC | USART_SR_RXNE)) {
qemu_set_irq(s->irq, 1);
} else {
qemu_set_irq(s->irq, 0);
}
}
static void stm32f2xx_usart_receive(void *opaque, const uint8_t *buf, int size)
{
STM32F2XXUsartState *s = opaque;
DeviceState *d = DEVICE(s);
if (!(s->usart_cr1 & USART_CR1_UE && s->usart_cr1 & USART_CR1_RE)) {
/* USART not enabled - drop the chars */
trace_stm32f2xx_usart_drop(d->id);
return;
}
s->usart_dr = *buf;
s->usart_sr |= USART_SR_RXNE;
stm32f2xx_update_irq(s);
trace_stm32f2xx_usart_receive(d->id, *buf);
}
static void stm32f2xx_usart_reset(DeviceState *dev)
{
STM32F2XXUsartState *s = STM32F2XX_USART(dev);
s->usart_sr = USART_SR_RESET;
s->usart_dr = 0x00000000;
s->usart_brr = 0x00000000;
s->usart_cr1 = 0x00000000;
s->usart_cr2 = 0x00000000;
s->usart_cr3 = 0x00000000;
s->usart_gtpr = 0x00000000;
stm32f2xx_update_irq(s);
}
static uint64_t stm32f2xx_usart_read(void *opaque, hwaddr addr,
unsigned int size)
{
STM32F2XXUsartState *s = opaque;
DeviceState *d = DEVICE(s);
uint64_t retvalue = 0;
switch (addr) {
case USART_SR:
retvalue = s->usart_sr;
qemu_chr_fe_accept_input(&s->chr);
break;
case USART_DR:
retvalue = s->usart_dr & 0x3FF;
s->usart_sr &= ~USART_SR_RXNE;
qemu_chr_fe_accept_input(&s->chr);
stm32f2xx_update_irq(s);
break;
case USART_BRR:
retvalue = s->usart_brr;
break;
case USART_CR1:
retvalue = s->usart_cr1;
break;
case USART_CR2:
retvalue = s->usart_cr2;
break;
case USART_CR3:
retvalue = s->usart_cr3;
break;
case USART_GTPR:
retvalue = s->usart_gtpr;
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr);
return 0;
}
trace_stm32f2xx_usart_read(d->id, size, addr, retvalue);
return retvalue;
}
static void stm32f2xx_usart_write(void *opaque, hwaddr addr,
uint64_t val64, unsigned int size)
{
STM32F2XXUsartState *s = opaque;
DeviceState *d = DEVICE(s);
uint32_t value = val64;
unsigned char ch;
trace_stm32f2xx_usart_write(d->id, size, addr, val64);
switch (addr) {
case USART_SR:
if (value <= 0x3FF) {
/* I/O being synchronous, TXE is always set. In addition, it may
only be set by hardware, so keep it set here. */
s->usart_sr = value | USART_SR_TXE;
} else {
s->usart_sr &= value;
}
stm32f2xx_update_irq(s);
return;
case USART_DR:
if (value < 0xF000) {
ch = value;
/* XXX this blocks entire thread. Rewrite to use
* qemu_chr_fe_write and background I/O callbacks */
qemu_chr_fe_write_all(&s->chr, &ch, 1);
/* XXX I/O are currently synchronous, making it impossible for
software to observe transient states where TXE or TC aren't
set. Unlike TXE however, which is read-only, software may
clear TC by writing 0 to the SR register, so set it again
on each write. */
s->usart_sr |= USART_SR_TC;
stm32f2xx_update_irq(s);
}
return;
case USART_BRR:
s->usart_brr = value;
return;
case USART_CR1:
s->usart_cr1 = value;
stm32f2xx_update_irq(s);
return;
case USART_CR2:
s->usart_cr2 = value;
return;
case USART_CR3:
s->usart_cr3 = value;
return;
case USART_GTPR:
s->usart_gtpr = value;
return;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr);
}
}
static const MemoryRegionOps stm32f2xx_usart_ops = {
.read = stm32f2xx_usart_read,
.write = stm32f2xx_usart_write,
.endianness = DEVICE_NATIVE_ENDIAN,
};
static const Property stm32f2xx_usart_properties[] = {
DEFINE_PROP_CHR("chardev", STM32F2XXUsartState, chr),
};
static void stm32f2xx_usart_init(Object *obj)
{
STM32F2XXUsartState *s = STM32F2XX_USART(obj);
sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
memory_region_init_io(&s->mmio, obj, &stm32f2xx_usart_ops, s,
TYPE_STM32F2XX_USART, 0x400);
sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
}
static void stm32f2xx_usart_realize(DeviceState *dev, Error **errp)
{
STM32F2XXUsartState *s = STM32F2XX_USART(dev);
qemu_chr_fe_set_handlers(&s->chr, stm32f2xx_usart_can_receive,
stm32f2xx_usart_receive, NULL, NULL,
s, NULL, true);
}
static void stm32f2xx_usart_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
device_class_set_legacy_reset(dc, stm32f2xx_usart_reset);
device_class_set_props(dc, stm32f2xx_usart_properties);
dc->realize = stm32f2xx_usart_realize;
}
static const TypeInfo stm32f2xx_usart_info = {
.name = TYPE_STM32F2XX_USART,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(STM32F2XXUsartState),
.instance_init = stm32f2xx_usart_init,
.class_init = stm32f2xx_usart_class_init,
};
static void stm32f2xx_usart_register_types(void)
{
type_register_static(&stm32f2xx_usart_info);
}
type_init(stm32f2xx_usart_register_types)
+651
View File
@@ -0,0 +1,651 @@
/*
* STM32L4X5 USART (Universal Synchronous Asynchronous Receiver Transmitter)
*
* Copyright (c) 2023 Arnaud Minier <[email protected]>
* Copyright (c) 2023 Inès Varhol <[email protected]>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*
* The STM32L4X5 USART is heavily inspired by the stm32f2xx_usart
* by Alistair Francis.
* The reference used is the STMicroElectronics RM0351 Reference manual
* for STM32L4x5 and STM32L4x6 advanced Arm ® -based 32-bit MCUs.
*/
#include "qemu/osdep.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "qapi/error.h"
#include "chardev/char-fe.h"
#include "chardev/char-serial.h"
#include "migration/vmstate.h"
#include "hw/char/stm32l4x5_usart.h"
#include "hw/core/clock.h"
#include "hw/core/irq.h"
#include "hw/core/qdev-clock.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-properties-system.h"
#include "hw/core/registerfields.h"
#include "trace.h"
REG32(CR1, 0x00)
FIELD(CR1, M1, 28, 1) /* Word length (part 2, see M0) */
FIELD(CR1, EOBIE, 27, 1) /* End of Block interrupt enable */
FIELD(CR1, RTOIE, 26, 1) /* Receiver timeout interrupt enable */
FIELD(CR1, DEAT, 21, 5) /* Driver Enable assertion time */
FIELD(CR1, DEDT, 16, 5) /* Driver Enable de-assertion time */
FIELD(CR1, OVER8, 15, 1) /* Oversampling mode */
FIELD(CR1, CMIE, 14, 1) /* Character match interrupt enable */
FIELD(CR1, MME, 13, 1) /* Mute mode enable */
FIELD(CR1, M0, 12, 1) /* Word length (part 1, see M1) */
FIELD(CR1, WAKE, 11, 1) /* Receiver wakeup method */
FIELD(CR1, PCE, 10, 1) /* Parity control enable */
FIELD(CR1, PS, 9, 1) /* Parity selection */
FIELD(CR1, PEIE, 8, 1) /* PE interrupt enable */
FIELD(CR1, TXEIE, 7, 1) /* TXE interrupt enable */
FIELD(CR1, TCIE, 6, 1) /* Transmission complete interrupt enable */
FIELD(CR1, RXNEIE, 5, 1) /* RXNE interrupt enable */
FIELD(CR1, IDLEIE, 4, 1) /* IDLE interrupt enable */
FIELD(CR1, TE, 3, 1) /* Transmitter enable */
FIELD(CR1, RE, 2, 1) /* Receiver enable */
FIELD(CR1, UESM, 1, 1) /* USART enable in Stop mode */
FIELD(CR1, UE, 0, 1) /* USART enable */
REG32(CR2, 0x04)
FIELD(CR2, ADD_1, 28, 4) /* ADD[7:4] */
FIELD(CR2, ADD_0, 24, 4) /* ADD[3:0] */
FIELD(CR2, RTOEN, 23, 1) /* Receiver timeout enable */
FIELD(CR2, ABRMOD, 21, 2) /* Auto baud rate mode */
FIELD(CR2, ABREN, 20, 1) /* Auto baud rate enable */
FIELD(CR2, MSBFIRST, 19, 1) /* Most significant bit first */
FIELD(CR2, DATAINV, 18, 1) /* Binary data inversion */
FIELD(CR2, TXINV, 17, 1) /* TX pin active level inversion */
FIELD(CR2, RXINV, 16, 1) /* RX pin active level inversion */
FIELD(CR2, SWAP, 15, 1) /* Swap RX/TX pins */
FIELD(CR2, LINEN, 14, 1) /* LIN mode enable */
FIELD(CR2, STOP, 12, 2) /* STOP bits */
FIELD(CR2, CLKEN, 11, 1) /* Clock enable */
FIELD(CR2, CPOL, 10, 1) /* Clock polarity */
FIELD(CR2, CPHA, 9, 1) /* Clock phase */
FIELD(CR2, LBCL, 8, 1) /* Last bit clock pulse */
FIELD(CR2, LBDIE, 6, 1) /* LIN break detection interrupt enable */
FIELD(CR2, LBDL, 5, 1) /* LIN break detection length */
FIELD(CR2, ADDM7, 4, 1) /* 7-bit / 4-bit Address Detection */
REG32(CR3, 0x08)
/* TCBGTIE only on STM32L496xx/4A6xx devices */
FIELD(CR3, UCESM, 23, 1) /* USART Clock Enable in Stop Mode */
FIELD(CR3, WUFIE, 22, 1) /* Wakeup from Stop mode interrupt enable */
FIELD(CR3, WUS, 20, 2) /* Wakeup from Stop mode interrupt flag selection */
FIELD(CR3, SCARCNT, 17, 3) /* Smartcard auto-retry count */
FIELD(CR3, DEP, 15, 1) /* Driver enable polarity selection */
FIELD(CR3, DEM, 14, 1) /* Driver enable mode */
FIELD(CR3, DDRE, 13, 1) /* DMA Disable on Reception Error */
FIELD(CR3, OVRDIS, 12, 1) /* Overrun Disable */
FIELD(CR3, ONEBIT, 11, 1) /* One sample bit method enable */
FIELD(CR3, CTSIE, 10, 1) /* CTS interrupt enable */
FIELD(CR3, CTSE, 9, 1) /* CTS enable */
FIELD(CR3, RTSE, 8, 1) /* RTS enable */
FIELD(CR3, DMAT, 7, 1) /* DMA enable transmitter */
FIELD(CR3, DMAR, 6, 1) /* DMA enable receiver */
FIELD(CR3, SCEN, 5, 1) /* Smartcard mode enable */
FIELD(CR3, NACK, 4, 1) /* Smartcard NACK enable */
FIELD(CR3, HDSEL, 3, 1) /* Half-duplex selection */
FIELD(CR3, IRLP, 2, 1) /* IrDA low-power */
FIELD(CR3, IREN, 1, 1) /* IrDA mode enable */
FIELD(CR3, EIE, 0, 1) /* Error interrupt enable */
REG32(BRR, 0x0C)
FIELD(BRR, BRR, 0, 16)
REG32(GTPR, 0x10)
FIELD(GTPR, GT, 8, 8) /* Guard time value */
FIELD(GTPR, PSC, 0, 8) /* Prescaler value */
REG32(RTOR, 0x14)
FIELD(RTOR, BLEN, 24, 8) /* Block Length */
FIELD(RTOR, RTO, 0, 24) /* Receiver timeout value */
REG32(RQR, 0x18)
FIELD(RQR, TXFRQ, 4, 1) /* Transmit data flush request */
FIELD(RQR, RXFRQ, 3, 1) /* Receive data flush request */
FIELD(RQR, MMRQ, 2, 1) /* Mute mode request */
FIELD(RQR, SBKRQ, 1, 1) /* Send break request */
FIELD(RQR, ABBRRQ, 0, 1) /* Auto baud rate request */
REG32(ISR, 0x1C)
/* TCBGT only for STM32L475xx/476xx/486xx devices */
FIELD(ISR, REACK, 22, 1) /* Receive enable acknowledge flag */
FIELD(ISR, TEACK, 21, 1) /* Transmit enable acknowledge flag */
FIELD(ISR, WUF, 20, 1) /* Wakeup from Stop mode flag */
FIELD(ISR, RWU, 19, 1) /* Receiver wakeup from Mute mode */
FIELD(ISR, SBKF, 18, 1) /* Send break flag */
FIELD(ISR, CMF, 17, 1) /* Character match flag */
FIELD(ISR, BUSY, 16, 1) /* Busy flag */
FIELD(ISR, ABRF, 15, 1) /* Auto Baud rate flag */
FIELD(ISR, ABRE, 14, 1) /* Auto Baud rate error */
FIELD(ISR, EOBF, 12, 1) /* End of block flag */
FIELD(ISR, RTOF, 11, 1) /* Receiver timeout */
FIELD(ISR, CTS, 10, 1) /* CTS flag */
FIELD(ISR, CTSIF, 9, 1) /* CTS interrupt flag */
FIELD(ISR, LBDF, 8, 1) /* LIN break detection flag */
FIELD(ISR, TXE, 7, 1) /* Transmit data register empty */
FIELD(ISR, TC, 6, 1) /* Transmission complete */
FIELD(ISR, RXNE, 5, 1) /* Read data register not empty */
FIELD(ISR, IDLE, 4, 1) /* Idle line detected */
FIELD(ISR, ORE, 3, 1) /* Overrun error */
FIELD(ISR, NF, 2, 1) /* START bit Noise detection flag */
FIELD(ISR, FE, 1, 1) /* Framing Error */
FIELD(ISR, PE, 0, 1) /* Parity Error */
REG32(ICR, 0x20)
FIELD(ICR, WUCF, 20, 1) /* Wakeup from Stop mode clear flag */
FIELD(ICR, CMCF, 17, 1) /* Character match clear flag */
FIELD(ICR, EOBCF, 12, 1) /* End of block clear flag */
FIELD(ICR, RTOCF, 11, 1) /* Receiver timeout clear flag */
FIELD(ICR, CTSCF, 9, 1) /* CTS clear flag */
FIELD(ICR, LBDCF, 8, 1) /* LIN break detection clear flag */
/* TCBGTCF only on STM32L496xx/4A6xx devices */
FIELD(ICR, TCCF, 6, 1) /* Transmission complete clear flag */
FIELD(ICR, IDLECF, 4, 1) /* Idle line detected clear flag */
FIELD(ICR, ORECF, 3, 1) /* Overrun error clear flag */
FIELD(ICR, NCF, 2, 1) /* Noise detected clear flag */
FIELD(ICR, FECF, 1, 1) /* Framing error clear flag */
FIELD(ICR, PECF, 0, 1) /* Parity error clear flag */
REG32(RDR, 0x24)
FIELD(RDR, RDR, 0, 9)
REG32(TDR, 0x28)
FIELD(TDR, TDR, 0, 9)
static void stm32l4x5_update_isr(Stm32l4x5UsartBaseState *s)
{
if (s->cr1 & R_CR1_TE_MASK) {
s->isr |= R_ISR_TEACK_MASK;
} else {
s->isr &= ~R_ISR_TEACK_MASK;
}
if (s->cr1 & R_CR1_RE_MASK) {
s->isr |= R_ISR_REACK_MASK;
} else {
s->isr &= ~R_ISR_REACK_MASK;
}
}
static void stm32l4x5_update_irq(Stm32l4x5UsartBaseState *s)
{
if (((s->isr & R_ISR_WUF_MASK) && (s->cr3 & R_CR3_WUFIE_MASK)) ||
((s->isr & R_ISR_CMF_MASK) && (s->cr1 & R_CR1_CMIE_MASK)) ||
((s->isr & R_ISR_ABRF_MASK) && (s->cr1 & R_CR1_RXNEIE_MASK)) ||
((s->isr & R_ISR_EOBF_MASK) && (s->cr1 & R_CR1_EOBIE_MASK)) ||
((s->isr & R_ISR_RTOF_MASK) && (s->cr1 & R_CR1_RTOIE_MASK)) ||
((s->isr & R_ISR_CTSIF_MASK) && (s->cr3 & R_CR3_CTSIE_MASK)) ||
((s->isr & R_ISR_LBDF_MASK) && (s->cr2 & R_CR2_LBDIE_MASK)) ||
((s->isr & R_ISR_TXE_MASK) && (s->cr1 & R_CR1_TXEIE_MASK)) ||
((s->isr & R_ISR_TC_MASK) && (s->cr1 & R_CR1_TCIE_MASK)) ||
((s->isr & R_ISR_RXNE_MASK) && (s->cr1 & R_CR1_RXNEIE_MASK)) ||
((s->isr & R_ISR_IDLE_MASK) && (s->cr1 & R_CR1_IDLEIE_MASK)) ||
((s->isr & R_ISR_ORE_MASK) &&
((s->cr1 & R_CR1_RXNEIE_MASK) || (s->cr3 & R_CR3_EIE_MASK))) ||
/* TODO: Handle NF ? */
((s->isr & R_ISR_FE_MASK) && (s->cr3 & R_CR3_EIE_MASK)) ||
((s->isr & R_ISR_PE_MASK) && (s->cr1 & R_CR1_PEIE_MASK))) {
qemu_irq_raise(s->irq);
trace_stm32l4x5_usart_irq_raised(s->isr);
} else {
qemu_irq_lower(s->irq);
trace_stm32l4x5_usart_irq_lowered();
}
}
static int stm32l4x5_usart_base_can_receive(void *opaque)
{
Stm32l4x5UsartBaseState *s = opaque;
if (!(s->isr & R_ISR_RXNE_MASK)) {
return 1;
}
return 0;
}
static void stm32l4x5_usart_base_receive(void *opaque, const uint8_t *buf,
int size)
{
Stm32l4x5UsartBaseState *s = opaque;
if (!((s->cr1 & R_CR1_UE_MASK) && (s->cr1 & R_CR1_RE_MASK))) {
trace_stm32l4x5_usart_receiver_not_enabled(
FIELD_EX32(s->cr1, CR1, UE), FIELD_EX32(s->cr1, CR1, RE));
return;
}
/* Check if overrun detection is enabled and if there is an overrun */
if (!(s->cr3 & R_CR3_OVRDIS_MASK) && (s->isr & R_ISR_RXNE_MASK)) {
/*
* A character has been received while
* the previous has not been read = Overrun.
*/
s->isr |= R_ISR_ORE_MASK;
trace_stm32l4x5_usart_overrun_detected(s->rdr, *buf);
} else {
/* No overrun */
s->rdr = *buf;
s->isr |= R_ISR_RXNE_MASK;
trace_stm32l4x5_usart_rx(s->rdr);
}
stm32l4x5_update_irq(s);
}
/*
* Try to send tx data, and arrange to be called back later if
* we can't (ie the char backend is busy/blocking).
*/
static gboolean usart_transmit(void *do_not_use, GIOCondition cond,
void *opaque)
{
Stm32l4x5UsartBaseState *s = STM32L4X5_USART_BASE(opaque);
int ret;
/* TODO: Handle 9 bits transmission */
uint8_t ch = s->tdr;
s->watch_tag = 0;
if (!(s->cr1 & R_CR1_TE_MASK) || (s->isr & R_ISR_TXE_MASK)) {
return G_SOURCE_REMOVE;
}
ret = qemu_chr_fe_write(&s->chr, &ch, 1);
if (ret <= 0) {
s->watch_tag = qemu_chr_fe_add_watch(&s->chr, G_IO_OUT | G_IO_HUP,
usart_transmit, s);
if (!s->watch_tag) {
/*
* Most common reason to be here is "no chardev backend":
* just insta-drain the buffer, so the serial output
* goes into a void, rather than blocking the guest.
*/
goto buffer_drained;
}
/* Transmit pending */
trace_stm32l4x5_usart_tx_pending();
return G_SOURCE_REMOVE;
}
buffer_drained:
/* Character successfully sent */
trace_stm32l4x5_usart_tx(ch);
s->isr |= R_ISR_TC_MASK | R_ISR_TXE_MASK;
stm32l4x5_update_irq(s);
return G_SOURCE_REMOVE;
}
static void usart_cancel_transmit(Stm32l4x5UsartBaseState *s)
{
g_clear_handle_id(&s->watch_tag, g_source_remove);
}
static void stm32l4x5_update_params(Stm32l4x5UsartBaseState *s)
{
int speed, parity, data_bits, stop_bits;
uint32_t value, usart_div;
QEMUSerialSetParams ssp;
/* Select the parity type */
if (s->cr1 & R_CR1_PCE_MASK) {
if (s->cr1 & R_CR1_PS_MASK) {
parity = 'O';
} else {
parity = 'E';
}
} else {
parity = 'N';
}
/* Select the number of stop bits */
switch (FIELD_EX32(s->cr2, CR2, STOP)) {
case 0:
stop_bits = 1;
break;
case 2:
stop_bits = 2;
break;
default:
qemu_log_mask(LOG_UNIMP,
"UNIMPLEMENTED: fractionnal stop bits; CR2[13:12] = %u",
FIELD_EX32(s->cr2, CR2, STOP));
return;
}
/* Select the length of the word */
switch ((FIELD_EX32(s->cr1, CR1, M1) << 1) | FIELD_EX32(s->cr1, CR1, M0)) {
case 0:
data_bits = 8;
break;
case 1:
data_bits = 9;
break;
case 2:
data_bits = 7;
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"UNDEFINED: invalid word length, CR1.M = 0b11");
return;
}
/* Select the baud rate */
value = FIELD_EX32(s->brr, BRR, BRR);
if (value < 16) {
qemu_log_mask(LOG_GUEST_ERROR,
"UNDEFINED: BRR less than 16: %u", value);
return;
}
if (FIELD_EX32(s->cr1, CR1, OVER8) == 0) {
/*
* Oversampling by 16
* BRR = USARTDIV
*/
usart_div = value;
} else {
/*
* Oversampling by 8
* - BRR[2:0] = USARTDIV[3:0] shifted 1 bit to the right.
* - BRR[3] must be kept cleared.
* - BRR[15:4] = USARTDIV[15:4]
* - The frequency is multiplied by 2
*/
usart_div = ((value & 0xFFF0) | ((value & 0x0007) << 1)) / 2;
}
speed = clock_get_hz(s->clk) / usart_div;
ssp.speed = speed;
ssp.parity = parity;
ssp.data_bits = data_bits;
ssp.stop_bits = stop_bits;
qemu_chr_fe_ioctl(&s->chr, CHR_IOCTL_SERIAL_SET_PARAMS, &ssp);
trace_stm32l4x5_usart_update_params(speed, parity, data_bits, stop_bits);
}
static void stm32l4x5_usart_base_reset_hold(Object *obj, ResetType type)
{
Stm32l4x5UsartBaseState *s = STM32L4X5_USART_BASE(obj);
s->cr1 = 0x00000000;
s->cr2 = 0x00000000;
s->cr3 = 0x00000000;
s->brr = 0x00000000;
s->gtpr = 0x00000000;
s->rtor = 0x00000000;
s->isr = 0x020000C0;
s->rdr = 0x00000000;
s->tdr = 0x00000000;
usart_cancel_transmit(s);
stm32l4x5_update_irq(s);
}
static void usart_update_rqr(Stm32l4x5UsartBaseState *s, uint32_t value)
{
/* TXFRQ */
/* Reset RXNE flag */
if (value & R_RQR_RXFRQ_MASK) {
s->isr &= ~R_ISR_RXNE_MASK;
}
/* MMRQ */
/* SBKRQ */
/* ABRRQ */
stm32l4x5_update_irq(s);
}
static uint64_t stm32l4x5_usart_base_read(void *opaque, hwaddr addr,
unsigned int size)
{
Stm32l4x5UsartBaseState *s = opaque;
uint64_t retvalue = 0;
switch (addr) {
case A_CR1:
retvalue = s->cr1;
break;
case A_CR2:
retvalue = s->cr2;
break;
case A_CR3:
retvalue = s->cr3;
break;
case A_BRR:
retvalue = FIELD_EX32(s->brr, BRR, BRR);
break;
case A_GTPR:
retvalue = s->gtpr;
break;
case A_RTOR:
retvalue = s->rtor;
break;
case A_RQR:
/* RQR is a write only register */
retvalue = 0x00000000;
break;
case A_ISR:
retvalue = s->isr;
break;
case A_ICR:
/* ICR is a clear register */
retvalue = 0x00000000;
break;
case A_RDR:
retvalue = FIELD_EX32(s->rdr, RDR, RDR);
/* Reset RXNE flag */
s->isr &= ~R_ISR_RXNE_MASK;
stm32l4x5_update_irq(s);
break;
case A_TDR:
retvalue = FIELD_EX32(s->tdr, TDR, TDR);
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr);
break;
}
trace_stm32l4x5_usart_read(addr, retvalue);
return retvalue;
}
static void stm32l4x5_usart_base_write(void *opaque, hwaddr addr,
uint64_t val64, unsigned int size)
{
Stm32l4x5UsartBaseState *s = opaque;
const uint32_t value = val64;
trace_stm32l4x5_usart_write(addr, value);
switch (addr) {
case A_CR1:
s->cr1 = value;
stm32l4x5_update_params(s);
stm32l4x5_update_isr(s);
stm32l4x5_update_irq(s);
return;
case A_CR2:
s->cr2 = value;
stm32l4x5_update_params(s);
return;
case A_CR3:
s->cr3 = value;
return;
case A_BRR:
s->brr = value;
stm32l4x5_update_params(s);
return;
case A_GTPR:
s->gtpr = value;
return;
case A_RTOR:
s->rtor = value;
return;
case A_RQR:
usart_update_rqr(s, value);
return;
case A_ISR:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: ISR is read only !\n", __func__);
return;
case A_ICR:
/* Clear the status flags */
s->isr &= ~value;
stm32l4x5_update_irq(s);
return;
case A_RDR:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: RDR is read only !\n", __func__);
return;
case A_TDR:
s->tdr = value;
s->isr &= ~R_ISR_TXE_MASK;
usart_transmit(NULL, G_IO_OUT, s);
return;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr);
}
}
static const MemoryRegionOps stm32l4x5_usart_base_ops = {
.read = stm32l4x5_usart_base_read,
.write = stm32l4x5_usart_base_write,
.endianness = DEVICE_NATIVE_ENDIAN,
.valid = {
.max_access_size = 4,
.min_access_size = 4,
.unaligned = false
},
.impl = {
.max_access_size = 4,
.min_access_size = 4,
.unaligned = false
},
};
static const Property stm32l4x5_usart_base_properties[] = {
DEFINE_PROP_CHR("chardev", Stm32l4x5UsartBaseState, chr),
};
static void stm32l4x5_usart_base_init(Object *obj)
{
Stm32l4x5UsartBaseState *s = STM32L4X5_USART_BASE(obj);
sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
memory_region_init_io(&s->mmio, obj, &stm32l4x5_usart_base_ops, s,
TYPE_STM32L4X5_USART_BASE, 0x400);
sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
s->clk = qdev_init_clock_in(DEVICE(s), "clk", NULL, s, 0);
}
static int stm32l4x5_usart_base_post_load(void *opaque, int version_id)
{
Stm32l4x5UsartBaseState *s = (Stm32l4x5UsartBaseState *)opaque;
stm32l4x5_update_params(s);
return 0;
}
static const VMStateDescription vmstate_stm32l4x5_usart_base = {
.name = TYPE_STM32L4X5_USART_BASE,
.version_id = 1,
.minimum_version_id = 1,
.post_load = stm32l4x5_usart_base_post_load,
.fields = (VMStateField[]) {
VMSTATE_UINT32(cr1, Stm32l4x5UsartBaseState),
VMSTATE_UINT32(cr2, Stm32l4x5UsartBaseState),
VMSTATE_UINT32(cr3, Stm32l4x5UsartBaseState),
VMSTATE_UINT32(brr, Stm32l4x5UsartBaseState),
VMSTATE_UINT32(gtpr, Stm32l4x5UsartBaseState),
VMSTATE_UINT32(rtor, Stm32l4x5UsartBaseState),
VMSTATE_UINT32(isr, Stm32l4x5UsartBaseState),
VMSTATE_UINT32(rdr, Stm32l4x5UsartBaseState),
VMSTATE_UINT32(tdr, Stm32l4x5UsartBaseState),
VMSTATE_CLOCK(clk, Stm32l4x5UsartBaseState),
VMSTATE_END_OF_LIST()
}
};
static void stm32l4x5_usart_base_realize(DeviceState *dev, Error **errp)
{
ERRP_GUARD();
Stm32l4x5UsartBaseState *s = STM32L4X5_USART_BASE(dev);
if (!clock_has_source(s->clk)) {
error_setg(errp, "USART clock must be wired up by SoC code");
return;
}
qemu_chr_fe_set_handlers(&s->chr, stm32l4x5_usart_base_can_receive,
stm32l4x5_usart_base_receive, NULL, NULL,
s, NULL, true);
}
static void stm32l4x5_usart_base_class_init(ObjectClass *klass,
const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
ResettableClass *rc = RESETTABLE_CLASS(klass);
rc->phases.hold = stm32l4x5_usart_base_reset_hold;
device_class_set_props(dc, stm32l4x5_usart_base_properties);
dc->realize = stm32l4x5_usart_base_realize;
dc->vmsd = &vmstate_stm32l4x5_usart_base;
}
static void stm32l4x5_usart_class_init(ObjectClass *oc, const void *data)
{
Stm32l4x5UsartBaseClass *subc = STM32L4X5_USART_BASE_CLASS(oc);
subc->type = STM32L4x5_USART;
}
static void stm32l4x5_uart_class_init(ObjectClass *oc, const void *data)
{
Stm32l4x5UsartBaseClass *subc = STM32L4X5_USART_BASE_CLASS(oc);
subc->type = STM32L4x5_UART;
}
static void stm32l4x5_lpuart_class_init(ObjectClass *oc, const void *data)
{
Stm32l4x5UsartBaseClass *subc = STM32L4X5_USART_BASE_CLASS(oc);
subc->type = STM32L4x5_LPUART;
}
static const TypeInfo stm32l4x5_usart_types[] = {
{
.name = TYPE_STM32L4X5_USART_BASE,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(Stm32l4x5UsartBaseState),
.instance_init = stm32l4x5_usart_base_init,
.class_size = sizeof(Stm32l4x5UsartBaseClass),
.class_init = stm32l4x5_usart_base_class_init,
.abstract = true,
}, {
.name = TYPE_STM32L4X5_USART,
.parent = TYPE_STM32L4X5_USART_BASE,
.class_init = stm32l4x5_usart_class_init,
}, {
.name = TYPE_STM32L4X5_UART,
.parent = TYPE_STM32L4X5_USART_BASE,
.class_init = stm32l4x5_uart_class_init,
}, {
.name = TYPE_STM32L4X5_LPUART,
.parent = TYPE_STM32L4X5_USART_BASE,
.class_init = stm32l4x5_lpuart_class_init,
}
};
DEFINE_TYPES(stm32l4x5_usart_types)
+317
View File
@@ -0,0 +1,317 @@
/*
* Terminal 3270 implementation
*
* Copyright 2017 IBM Corp.
*
* Authors: Yang Chen <[email protected]>
* Jing Liu <[email protected]>
*
* This work is licensed under the terms of the GNU GPL, version 2 or (at
* your option) any later version. See the COPYING file in the top-level
* directory.
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qemu/module.h"
#include "chardev/char-fe.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-properties-system.h"
#include "hw/s390x/3270-ccw.h"
#include "qom/object.h"
/* Enough spaces for different window sizes. */
#define INPUT_BUFFER_SIZE 1000
/*
* 1 for header, 1024*2 for datastream, 2 for tail
* Reserve enough spaces for telnet IAC escape.
*/
#define OUTPUT_BUFFER_SIZE 2051
struct Terminal3270 {
EmulatedCcw3270Device cdev;
CharFrontend chr;
uint8_t inv[INPUT_BUFFER_SIZE];
uint8_t outv[OUTPUT_BUFFER_SIZE];
int in_len;
bool handshake_done;
guint timer_tag;
};
typedef struct Terminal3270 Terminal3270;
#define TYPE_TERMINAL_3270 "x-terminal3270"
DECLARE_INSTANCE_CHECKER(Terminal3270, TERMINAL_3270,
TYPE_TERMINAL_3270)
static int terminal_can_read(void *opaque)
{
Terminal3270 *t = opaque;
return INPUT_BUFFER_SIZE - t->in_len;
}
static void terminal_timer_cancel(Terminal3270 *t)
{
g_clear_handle_id(&t->timer_tag, g_source_remove);
}
/*
* Protocol handshake done,
* signal guest by an unsolicited DE irq.
*/
static void TN3270_handshake_done(Terminal3270 *t)
{
CcwDevice *ccw_dev = CCW_DEVICE(t);
SubchDev *sch = ccw_dev->sch;
t->handshake_done = true;
sch->curr_status.scsw.dstat = SCSW_DSTAT_DEVICE_END;
css_conditional_io_interrupt(sch);
}
/*
* Called when the interval is timeout to detect
* if the client is still alive by Timing Mark.
*/
static gboolean send_timing_mark_cb(gpointer opaque)
{
Terminal3270 *t = opaque;
const uint8_t timing[] = {0xff, 0xfd, 0x06};
qemu_chr_fe_write_all(&t->chr, timing, sizeof(timing));
return true;
}
/*
* Receive inbound data from socket.
* For data given to guest, drop the data boundary IAC, IAC_EOR.
* TODO:
* Using "Reset" key on x3270 may result multiple commands in one packet.
* This usually happens when the user meets a poor traffic of the network.
* As of now, for such case, we simply terminate the connection,
* and we should come back here later with a better solution.
*/
static void terminal_read(void *opaque, const uint8_t *buf, int size)
{
Terminal3270 *t = opaque;
CcwDevice *ccw_dev = CCW_DEVICE(t);
SubchDev *sch = ccw_dev->sch;
int end;
assert(size <= (INPUT_BUFFER_SIZE - t->in_len));
terminal_timer_cancel(t);
t->timer_tag = g_timeout_add_seconds(600, send_timing_mark_cb, t);
memcpy(&t->inv[t->in_len], buf, size);
t->in_len += size;
if (t->in_len < 2) {
return;
}
if (!t->handshake_done) {
/*
* Receiving Terminal Type is the last step of handshake.
* The data format: IAC SB Terminal-Type IS <terminal type> IAC SE
* The code for Terminal-Type is 0x18, for IS is 0.
* Simply check the data format and mark handshake_done.
*/
if (t->in_len > 6 && t->inv[2] == 0x18 && t->inv[3] == 0x0 &&
t->inv[t->in_len - 2] == IAC && t->inv[t->in_len - 1] == IAC_SE) {
TN3270_handshake_done(t);
t->in_len = 0;
}
return;
}
for (end = 0; end < t->in_len - 1; end++) {
if (t->inv[end] == IAC && t->inv[end + 1] == IAC_EOR) {
break;
}
}
if (end == t->in_len - 2) {
/* Data is valid for consuming. */
t->in_len -= 2;
sch->curr_status.scsw.dstat = SCSW_DSTAT_ATTENTION;
css_conditional_io_interrupt(sch);
} else if (end < t->in_len - 2) {
/* "Reset" key is used. */
qemu_chr_fe_disconnect(&t->chr);
} else {
/* Gathering data. */
return;
}
}
static void chr_event(void *opaque, QEMUChrEvent event)
{
Terminal3270 *t = opaque;
CcwDevice *ccw_dev = CCW_DEVICE(t);
SubchDev *sch = ccw_dev->sch;
/* Ensure the initial status correct, always reset them. */
t->in_len = 0;
t->handshake_done = false;
terminal_timer_cancel(t);
switch (event) {
case CHR_EVENT_OPENED:
/*
* 3270 does handshake firstly by the negotiate options in
* char-socket.c. Once qemu receives the terminal-type of the
* client, mark handshake done and trigger everything rolling again.
*/
t->timer_tag = g_timeout_add_seconds(600, send_timing_mark_cb, t);
break;
case CHR_EVENT_CLOSED:
sch->curr_status.scsw.dstat = SCSW_DSTAT_DEVICE_END;
css_conditional_io_interrupt(sch);
break;
case CHR_EVENT_BREAK:
case CHR_EVENT_MUX_IN:
case CHR_EVENT_MUX_OUT:
/* Ignore */
break;
}
}
static void terminal_init(EmulatedCcw3270Device *dev, Error **errp)
{
Terminal3270 *t = TERMINAL_3270(dev);
static bool terminal_available;
if (terminal_available) {
error_setg(errp, "Multiple 3270 terminals are not supported.");
return;
}
terminal_available = true;
qemu_chr_fe_set_handlers(&t->chr, terminal_can_read,
terminal_read, chr_event, NULL, t, NULL, true);
}
static inline CcwDataStream *get_cds(Terminal3270 *t)
{
return &(CCW_DEVICE(&t->cdev)->sch->cds);
}
static int read_payload_3270(EmulatedCcw3270Device *dev)
{
Terminal3270 *t = TERMINAL_3270(dev);
int len;
int ret;
len = MIN(ccw_dstream_avail(get_cds(t)), t->in_len);
ret = ccw_dstream_write_buf(get_cds(t), t->inv, len);
if (ret < 0) {
return ret;
}
t->in_len -= len;
return len;
}
/* TN3270 uses binary transmission, which needs escape IAC to IAC IAC */
static int insert_IAC_escape_char(uint8_t *outv, int out_len)
{
int IAC_num = 0, new_out_len, i, j;
for (i = 0; i < out_len; i++) {
if (outv[i] == IAC) {
IAC_num++;
}
}
if (IAC_num == 0) {
return out_len;
}
new_out_len = out_len + IAC_num;
for (i = out_len - 1, j = new_out_len - 1; j > i && i >= 0; i--, j--) {
outv[j] = outv[i];
if (outv[i] == IAC) {
outv[--j] = IAC;
}
}
return new_out_len;
}
/*
* Write 3270 outbound to socket.
* Return the count of 3270 data field if succeeded, zero if failed.
*/
static int write_payload_3270(EmulatedCcw3270Device *dev, uint8_t cmd)
{
Terminal3270 *t = TERMINAL_3270(dev);
int retval = 0;
int count = ccw_dstream_avail(get_cds(t));
int bound = (OUTPUT_BUFFER_SIZE - 3) / 2;
int len = MIN(count, bound);
int out_len = 0;
if (!t->handshake_done) {
if (!(t->outv[0] == IAC && t->outv[1] != IAC)) {
/*
* Before having finished 3270 negotiation,
* sending outbound data except protocol options is prohibited.
*/
return 0;
}
}
if (!qemu_chr_fe_backend_connected(&t->chr)) {
/* We just say we consumed all data if there's no backend. */
return count;
}
t->outv[out_len++] = cmd;
do {
retval = ccw_dstream_read_buf(get_cds(t), &t->outv[out_len], len);
if (retval < 0) {
return retval;
}
count = ccw_dstream_avail(get_cds(t));
out_len += len;
out_len = insert_IAC_escape_char(t->outv, out_len);
if (!count) {
t->outv[out_len++] = IAC;
t->outv[out_len++] = IAC_EOR;
}
retval = qemu_chr_fe_write_all(&t->chr, t->outv, out_len);
len = MIN(count, bound);
out_len = 0;
} while (len && retval >= 0);
return (retval <= 0) ? 0 : get_cds(t)->count;
}
static const Property terminal_properties[] = {
DEFINE_PROP_CHR("chardev", Terminal3270, chr),
};
static const VMStateDescription terminal3270_vmstate = {
.name = TYPE_TERMINAL_3270,
.unmigratable = 1,
};
static void terminal_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
EmulatedCcw3270Class *ck = EMULATED_CCW_3270_CLASS(klass);
device_class_set_props(dc, terminal_properties);
dc->vmsd = &terminal3270_vmstate;
ck->init = terminal_init;
ck->read_payload_3270 = read_payload_3270;
ck->write_payload_3270 = write_payload_3270;
}
static const TypeInfo ccw_terminal_info = {
.name = TYPE_TERMINAL_3270,
.parent = TYPE_EMULATED_CCW_3270,
.instance_size = sizeof(Terminal3270),
.class_init = terminal_class_init,
.class_size = sizeof(EmulatedCcw3270Class),
};
static void register_types(void)
{
type_register_static(&ccw_terminal_info);
}
type_init(register_types)
+143
View File
@@ -0,0 +1,143 @@
# See docs/devel/tracing.rst for syntax documentation.
# parallel.c
parallel_ioport_read(const char *desc, uint16_t addr, uint8_t value) "read [%s] addr 0x%02x val 0x%02x"
parallel_ioport_write(const char *desc, uint16_t addr, uint8_t value) "write [%s] addr 0x%02x val 0x%02x"
# serial.c
serial_read(uint64_t addr, uint8_t value) "[0x%02" PRIx64 "] -> 0x%02" PRIx8
serial_write(uint64_t addr, uint64_t value) "[0x%02" PRIx64 "] <- 0x%02" PRIx64
serial_update_parameters(uint64_t baudrate, char parity, int data_bits, int stop_bits) "baudrate=%"PRIu64" parity='%c' data=%d stop=%d"
# virtio-serial-bus.c
virtio_serial_send_control_event(unsigned int port, uint16_t event, uint16_t value) "port %u, event %u, value %u"
virtio_serial_throttle_port(unsigned int port, bool throttle) "port %u, throttle %d"
virtio_serial_handle_control_message(uint16_t event, uint16_t value) "event %u, value %u"
virtio_serial_handle_control_message_port(unsigned int port) "port %u"
# virtio-console.c
virtio_console_flush_buf(unsigned int port, size_t len, ssize_t ret) "port %u, in_len %zu, out_len %zd"
virtio_console_chr_read(unsigned int port, int size) "port %u, size %d"
virtio_console_chr_event(unsigned int port, int event) "port %u, event %d"
# goldfish_tty.c
goldfish_tty_read(void *dev, unsigned int addr, unsigned int size, uint64_t value) "tty: %p reg: 0x%02x size: %d value: 0x%"PRIx64
goldfish_tty_write(void *dev, unsigned int addr, unsigned int size, uint64_t value) "tty: %p reg: 0x%02x size: %d value: 0x%"PRIx64
goldfish_tty_can_receive(void *dev, unsigned int available) "tty: %p available: %u"
goldfish_tty_receive(void *dev, unsigned int size) "tty: %p size: %u"
goldfish_tty_reset(void *dev) "tty: %p"
goldfish_tty_realize(void *dev) "tty: %p"
goldfish_tty_unrealize(void *dev) "tty: %p"
goldfish_tty_instance_init(void *dev) "tty: %p"
# grlib_apbuart.c
grlib_apbuart_event(int event) "event:%d"
grlib_apbuart_writel_unknown(uint64_t addr, uint32_t value) "addr 0x%"PRIx64" value 0x%x"
grlib_apbuart_readl_unknown(uint64_t addr) "addr 0x%"PRIx64
# escc.c
escc_hard_reset(void) "hard reset"
escc_soft_reset_chn(char channel) "soft reset channel %c"
escc_put_queue(char channel, int b) "channel %c put: 0x%02x"
escc_get_queue(char channel, int val) "channel %c get 0x%02x"
escc_update_irq(int irq) "IRQ = %d"
escc_update_parameters(char channel, int speed, int parity, int data_bits, int stop_bits) "channel %c: speed=%d parity=%c data=%d stop=%d"
escc_mem_writeb_ctrl(char channel, uint32_t reg, uint32_t val) "Write channel %c, reg[%d] = 0x%2.2x"
escc_mem_writeb_data(char channel, uint32_t val) "Write channel %c, ch %d"
escc_mem_readb_ctrl(char channel, uint32_t reg, uint8_t val) "Read channel %c, reg[%d] = 0x%2.2x"
escc_mem_readb_data(char channel, uint32_t ret) "Read channel %c, ch %d"
escc_serial_receive_byte(char channel, int ch) "channel %c put ch %d"
escc_sunkbd_event_in(int ch, const char *name, int down) "QKeyCode 0x%2.2x [%s], down %d"
escc_sunkbd_event_out(int ch) "Translated keycode 0x%2.2x"
escc_kbd_command(int val) "Command %d"
escc_sunmouse_event(int dx, int dy, int buttons_state) "dx=%d dy=%d buttons=0x%01x"
# imx_serial.c
imx_serial_read(const char *chrname, uint64_t addr, uint64_t value) "%s:[0x%03" PRIu64 "] -> 0x%08" PRIx64
imx_serial_write(const char *chrname, uint64_t addr, uint64_t value) "%s:[0x%03" PRIu64 "] <- 0x%08" PRIx64
imx_serial_put_data(const char *chrname, uint32_t value) "%s: 0x%" PRIx32
# pl011.c
pl011_irq_state(bool level) "irq state %d"
pl011_read(uint64_t addr, uint32_t value, const char *regname) "addr 0x%03" PRIx64 " value 0x%08x reg %s"
pl011_read_fifo(unsigned rx_fifo_used, unsigned rx_fifo_depth) "RX FIFO read, used %u/%u"
pl011_write(uint64_t addr, uint32_t value, const char *regname) "addr 0x%03" PRIx64 " value 0x%08x reg %s"
pl011_can_receive(uint32_t lcr, unsigned rx_fifo_used, unsigned rx_fifo_depth, unsigned rx_fifo_available) "LCR 0x%02x, RX FIFO used %u/%u, can_receive %u chars"
pl011_fifo_rx_put(uint32_t c, unsigned read_count, unsigned rx_fifo_depth) "RX FIFO push char [0x%02x] %d/%u depth used"
pl011_fifo_rx_full(void) "RX FIFO now full, RXFF set"
pl011_baudrate_change(unsigned int baudrate, uint64_t clock, uint32_t ibrd, uint32_t fbrd) "new baudrate %u (clk: %" PRIu64 "hz, ibrd: %" PRIu32 ", fbrd: %" PRIu32 ")"
pl011_receive(size_t size) "recv %zd chars"
# cmsdk-apb-uart.c
cmsdk_apb_uart_read(uint64_t offset, uint64_t data, unsigned size) "CMSDK APB UART read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
cmsdk_apb_uart_write(uint64_t offset, uint64_t data, unsigned size) "CMSDK APB UART write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
cmsdk_apb_uart_reset(void) "CMSDK APB UART: reset"
cmsdk_apb_uart_receive(uint8_t c) "CMSDK APB UART: got character 0x%x from backend"
cmsdk_apb_uart_tx_pending(void) "CMSDK APB UART: character send to backend pending"
cmsdk_apb_uart_tx(uint8_t c) "CMSDK APB UART: character 0x%x sent to backend"
cmsdk_apb_uart_set_params(int speed) "CMSDK APB UART: params set to %d 8N1"
# nrf51_uart.c
nrf51_uart_read(uint64_t addr, uint64_t r, unsigned int size) "addr 0x%" PRIx64 " value 0x%" PRIx64 " size %u"
nrf51_uart_write(uint64_t addr, uint64_t value, unsigned int size) "addr 0x%" PRIx64 " value 0x%" PRIx64 " size %u"
# shakti_uart.c
shakti_uart_read(uint64_t addr, uint16_t r, unsigned int size) "addr 0x%" PRIx64 " value 0x%" PRIx16 " size %u"
shakti_uart_write(uint64_t addr, uint64_t value, unsigned int size) "addr 0x%" PRIx64 " value 0x%" PRIx64 " size %u"
# exynos4210_uart.c
exynos_uart_dmabusy(uint32_t channel) "UART%d: DMA busy (Rx buffer empty)"
exynos_uart_dmaready(uint32_t channel) "UART%d: DMA ready"
exynos_uart_irq_raised(uint32_t channel, uint32_t reg) "UART%d: IRQ raised: 0x%08"PRIx32
exynos_uart_irq_lowered(uint32_t channel) "UART%d: IRQ lowered"
exynos_uart_update_params(uint32_t channel, int speed, uint8_t parity, int data, int stop, uint64_t wordtime) "UART%d: speed: %d, parity: %c, data bits: %d, stop bits: %d wordtime: %"PRId64"ns"
exynos_uart_write(uint32_t channel, uint32_t offset, const char *name, uint64_t val) "UART%d: <0x%04x> %s <- 0x%" PRIx64
exynos_uart_read(uint32_t channel, uint32_t offset, const char *name, uint64_t val) "UART%d: <0x%04x> %s -> 0x%" PRIx64
exynos_uart_rx_fifo_reset(uint32_t channel) "UART%d: Rx FIFO Reset"
exynos_uart_tx_fifo_reset(uint32_t channel) "UART%d: Tx FIFO Reset"
exynos_uart_tx(uint32_t channel, uint8_t ch) "UART%d: Tx 0x%02"PRIx32
exynos_uart_intclr(uint32_t channel, uint32_t reg) "UART%d: interrupts cleared: 0x%08"PRIx32
exynos_uart_ro_write(uint32_t channel, const char *name, uint32_t reg) "UART%d: Trying to write into RO register: %s [0x%04"PRIx32"]"
exynos_uart_rx(uint32_t channel, uint8_t ch) "UART%d: Rx 0x%02"PRIx32
exynos_uart_rx_error(uint32_t channel) "UART%d: Rx error"
exynos_uart_wo_read(uint32_t channel, const char *name, uint32_t reg) "UART%d: Trying to read from WO register: %s [0x%04"PRIx32"]"
exynos_uart_rxsize(uint32_t channel, uint32_t size) "UART%d: Rx FIFO size: %d"
exynos_uart_channel_error(uint32_t channel) "Wrong UART channel number: %d"
exynos_uart_rx_timeout(uint32_t channel, uint32_t stat, uint32_t intsp) "UART%d: Rx timeout stat=0x%x intsp=0x%x"
# cadence_uart.c
cadence_uart_baudrate(unsigned baudrate) "baudrate %u"
# sh_serial.c
sh_serial_read(char *id, unsigned size, uint64_t offs, uint64_t val) " %s size %d offs 0x%02" PRIx64 " -> 0x%02" PRIx64
sh_serial_write(char *id, unsigned size, uint64_t offs, uint64_t val) "%s size %d offs 0x%02" PRIx64 " <- 0x%02" PRIx64
# stm32l4x5_usart.c
stm32l4x5_usart_read(uint64_t addr, uint32_t data) "USART: Read <0x%" PRIx64 "> -> 0x%" PRIx32 ""
stm32l4x5_usart_write(uint64_t addr, uint32_t data) "USART: Write <0x%" PRIx64 "> <- 0x%" PRIx32 ""
stm32l4x5_usart_rx(uint8_t c) "USART: got character 0x%x from backend"
stm32l4x5_usart_tx(uint8_t c) "USART: character 0x%x sent to backend"
stm32l4x5_usart_tx_pending(void) "USART: character send to backend pending"
stm32l4x5_usart_irq_raised(uint32_t reg) "USART: IRQ raised: 0x%08"PRIx32
stm32l4x5_usart_irq_lowered(void) "USART: IRQ lowered"
stm32l4x5_usart_overrun_detected(uint8_t current, uint8_t received) "USART: Overrun detected, RDR='0x%x', received 0x%x"
stm32l4x5_usart_receiver_not_enabled(uint8_t ue_bit, uint8_t re_bit) "USART: Receiver not enabled, UE=0x%x, RE=0x%x"
stm32l4x5_usart_update_params(int speed, uint8_t parity, int data, int stop) "USART: speed: %d, parity: %c, data bits: %d, stop bits: %d"
# xen_console.c
xen_console_connect(unsigned int idx, unsigned int ring_ref, unsigned int port, unsigned int limit) "idx %u ring_ref %u port %u limit %u"
xen_console_disconnect(unsigned int idx) "idx %u"
xen_console_unrealize(unsigned int idx) "idx %u"
xen_console_realize(unsigned int idx, const char *chrdev) "idx %u chrdev %s"
xen_console_device_create(unsigned int idx) "idx %u"
xen_console_device_destroy(unsigned int idx) "idx %u"
# stm32f2xx_usart.c
stm32f2xx_usart_read(char *id, unsigned size, uint64_t ofs, uint64_t val) " %s size %d ofs 0x%02" PRIx64 " -> 0x%02" PRIx64
stm32f2xx_usart_write(char *id, unsigned size, uint64_t ofs, uint64_t val) "%s size %d ofs 0x%02" PRIx64 " <- 0x%02" PRIx64
stm32f2xx_usart_drop(char *id) " %s dropping the chars"
stm32f2xx_usart_receive(char *id, uint8_t chr) " %s receiving '%c'"
# riscv_htif.c
htif_uart_write_to_host(uint8_t device, uint8_t cmd, uint64_t payload) "device: %u cmd: %02u payload: %016" PRIx64
htif_uart_unknown_device_command(uint8_t device, uint8_t cmd, uint64_t payload) "device: %u cmd: %02u payload: %016" PRIx64
+1
View File
@@ -0,0 +1 @@
#include "trace/trace-hw_char.h"
+302
View File
@@ -0,0 +1,302 @@
/*
* Virtio Console and Generic Serial Port Devices
*
* Copyright Red Hat, Inc. 2009, 2010
*
* Authors:
* Amit Shah <[email protected]>
*
* This work is licensed under the terms of the GNU GPL, version 2. See
* the COPYING file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "chardev/char-fe.h"
#include "qemu/error-report.h"
#include "qemu/module.h"
#include "trace.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-properties-system.h"
#include "hw/virtio/virtio-serial.h"
#include "qapi/error.h"
#include "qapi/qapi-events-char.h"
#include "qom/object.h"
#define TYPE_VIRTIO_CONSOLE_SERIAL_PORT "virtserialport"
typedef struct VirtConsole VirtConsole;
DECLARE_INSTANCE_CHECKER(VirtConsole, VIRTIO_CONSOLE,
TYPE_VIRTIO_CONSOLE_SERIAL_PORT)
struct VirtConsole {
VirtIOSerialPort parent_obj;
CharFrontend chr;
guint watch;
};
/*
* Callback function that's called from chardevs when backend becomes
* writable.
*/
static gboolean chr_write_unblocked(void *do_not_use, GIOCondition cond,
void *opaque)
{
VirtConsole *vcon = opaque;
vcon->watch = 0;
virtio_serial_throttle_port(VIRTIO_SERIAL_PORT(vcon), false);
return G_SOURCE_REMOVE;
}
/* Callback function that's called when the guest sends us data */
static ssize_t flush_buf(VirtIOSerialPort *port,
const uint8_t *buf, ssize_t len)
{
VirtConsole *vcon = VIRTIO_CONSOLE(port);
ssize_t ret;
if (!qemu_chr_fe_backend_connected(&vcon->chr)) {
/* If there's no backend, we can just say we consumed all data. */
return len;
}
ret = qemu_chr_fe_write(&vcon->chr, buf, len);
trace_virtio_console_flush_buf(port->id, len, ret);
if (ret < len) {
VirtIOSerialPortClass *k = VIRTIO_SERIAL_PORT_GET_CLASS(port);
/*
* Ideally we'd get a better error code than just -1, but
* that's what the chardev interface gives us right now. If
* we had a finer-grained message, like -EPIPE, we could close
* this connection.
*/
if (ret < 0)
ret = 0;
/* XXX we should be queuing data to send later for the
* console devices too rather than silently dropping
* console data on EAGAIN. The Linux virtio-console
* hvc driver though does sends with spinlocks held,
* so if we enable throttling that'll stall the entire
* guest kernel, not merely the process writing to the
* console.
*
* While we could queue data for later write without
* enabling throttling, this would result in the guest
* being able to trigger arbitrary memory usage in QEMU
* buffering data for later writes.
*
* So fixing this problem likely requires fixing the
* Linux virtio-console hvc driver to not hold spinlocks
* while writing, and instead merely block the process
* that's writing. QEMU would then need some way to detect
* if the guest had the fixed driver too, before we can
* use throttling on host side.
*/
if (!k->is_console) {
virtio_serial_throttle_port(port, true);
if (!vcon->watch) {
vcon->watch = qemu_chr_fe_add_watch(&vcon->chr,
G_IO_OUT|G_IO_HUP,
chr_write_unblocked, vcon);
}
}
}
return ret;
}
/* Callback function that's called when the guest opens/closes the port */
static void set_guest_connected(VirtIOSerialPort *port, int guest_connected)
{
VirtConsole *vcon = VIRTIO_CONSOLE(port);
DeviceState *dev = DEVICE(port);
VirtIOSerialPortClass *k = VIRTIO_SERIAL_PORT_GET_CLASS(port);
if (!k->is_console) {
qemu_chr_fe_set_open(&vcon->chr, guest_connected);
}
if (dev->id) {
qapi_event_send_vserport_change(dev->id, guest_connected);
}
}
static void guest_writable(VirtIOSerialPort *port)
{
VirtConsole *vcon = VIRTIO_CONSOLE(port);
qemu_chr_fe_accept_input(&vcon->chr);
}
/* Readiness of the guest to accept data on a port */
static int chr_can_read(void *opaque)
{
VirtConsole *vcon = opaque;
return virtio_serial_guest_ready(VIRTIO_SERIAL_PORT(vcon));
}
/* Send data from a char device over to the guest */
static void chr_read(void *opaque, const uint8_t *buf, int size)
{
VirtConsole *vcon = opaque;
VirtIOSerialPort *port = VIRTIO_SERIAL_PORT(vcon);
trace_virtio_console_chr_read(port->id, size);
virtio_serial_write(port, buf, size);
}
static void chr_event(void *opaque, QEMUChrEvent event)
{
VirtConsole *vcon = opaque;
VirtIOSerialPort *port = VIRTIO_SERIAL_PORT(vcon);
trace_virtio_console_chr_event(port->id, event);
switch (event) {
case CHR_EVENT_OPENED:
virtio_serial_open(port);
break;
case CHR_EVENT_CLOSED:
g_clear_handle_id(&vcon->watch, g_source_remove);
virtio_serial_close(port);
break;
case CHR_EVENT_BREAK:
case CHR_EVENT_MUX_IN:
case CHR_EVENT_MUX_OUT:
/* Ignore */
break;
}
}
static int chr_be_change(void *opaque)
{
VirtConsole *vcon = opaque;
VirtIOSerialPort *port = VIRTIO_SERIAL_PORT(vcon);
VirtIOSerialPortClass *k = VIRTIO_SERIAL_PORT_GET_CLASS(port);
if (k->is_console) {
qemu_chr_fe_set_handlers(&vcon->chr, chr_can_read, chr_read,
NULL, chr_be_change, vcon, NULL, true);
} else {
qemu_chr_fe_set_handlers(&vcon->chr, chr_can_read, chr_read,
chr_event, chr_be_change, vcon, NULL, false);
}
if (vcon->watch) {
g_source_remove(vcon->watch);
vcon->watch = qemu_chr_fe_add_watch(&vcon->chr,
G_IO_OUT | G_IO_HUP,
chr_write_unblocked, vcon);
}
return 0;
}
static void virtconsole_enable_backend(VirtIOSerialPort *port, bool enable)
{
VirtConsole *vcon = VIRTIO_CONSOLE(port);
if (!qemu_chr_fe_backend_connected(&vcon->chr)) {
return;
}
if (enable) {
VirtIOSerialPortClass *k = VIRTIO_SERIAL_PORT_GET_CLASS(port);
qemu_chr_fe_set_handlers(&vcon->chr, chr_can_read, chr_read,
k->is_console ? NULL : chr_event,
chr_be_change, vcon, NULL, false);
} else {
qemu_chr_fe_set_handlers(&vcon->chr, NULL, NULL, NULL,
NULL, NULL, NULL, false);
}
}
static void virtconsole_realize(DeviceState *dev, Error **errp)
{
VirtIOSerialPort *port = VIRTIO_SERIAL_PORT(dev);
VirtConsole *vcon = VIRTIO_CONSOLE(dev);
VirtIOSerialPortClass *k = VIRTIO_SERIAL_PORT_GET_CLASS(dev);
if (port->id == 0 && !k->is_console) {
error_setg(errp, "Port number 0 on virtio-serial devices reserved "
"for virtconsole devices for backward compatibility.");
return;
}
if (qemu_chr_fe_backend_connected(&vcon->chr)) {
/*
* For consoles we don't block guest data transfer just
* because nothing is connected - we'll just let it go
* whetherever the chardev wants - /dev/null probably.
*
* For serial ports we need 100% reliable data transfer
* so we use the opened/closed signals from chardev to
* trigger open/close of the device
*/
if (k->is_console) {
qemu_chr_fe_set_handlers(&vcon->chr, chr_can_read, chr_read,
NULL, chr_be_change,
vcon, NULL, true);
virtio_serial_open(port);
} else {
qemu_chr_fe_set_handlers(&vcon->chr, chr_can_read, chr_read,
chr_event, chr_be_change,
vcon, NULL, false);
}
}
}
static void virtconsole_unrealize(DeviceState *dev)
{
VirtConsole *vcon = VIRTIO_CONSOLE(dev);
g_clear_handle_id(&vcon->watch, g_source_remove);
}
static void virtconsole_class_init(ObjectClass *klass, const void *data)
{
VirtIOSerialPortClass *k = VIRTIO_SERIAL_PORT_CLASS(klass);
k->is_console = true;
}
static const TypeInfo virtconsole_info = {
.name = "virtconsole",
.parent = TYPE_VIRTIO_CONSOLE_SERIAL_PORT,
.class_init = virtconsole_class_init,
};
static const Property virtserialport_properties[] = {
DEFINE_PROP_CHR("chardev", VirtConsole, chr),
};
static void virtserialport_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
VirtIOSerialPortClass *k = VIRTIO_SERIAL_PORT_CLASS(klass);
k->realize = virtconsole_realize;
k->unrealize = virtconsole_unrealize;
k->have_data = flush_buf;
k->set_guest_connected = set_guest_connected;
k->enable_backend = virtconsole_enable_backend;
k->guest_writable = guest_writable;
device_class_set_props(dc, virtserialport_properties);
}
static const TypeInfo virtserialport_info = {
.name = TYPE_VIRTIO_CONSOLE_SERIAL_PORT,
.parent = TYPE_VIRTIO_SERIAL_PORT,
.instance_size = sizeof(VirtConsole),
.class_init = virtserialport_class_init,
};
static void virtconsole_register_types(void)
{
type_register_static(&virtserialport_info);
type_register_static(&virtconsole_info);
}
type_init(virtconsole_register_types)
File diff suppressed because it is too large Load Diff
+654
View File
@@ -0,0 +1,654 @@
/*
* Copyright (C) International Business Machines Corp., 2005
* Author(s): Anthony Liguori <[email protected]>
*
* Copyright (C) Red Hat 2007
*
* Xen Console
*
* 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; under version 2 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 <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "qemu/cutils.h"
#include <sys/select.h>
#include <termios.h>
#include "qapi/error.h"
#include "system/system.h"
#include "chardev/char-fe.h"
#include "hw/xen/xen-backend.h"
#include "hw/xen/xen-bus-helper.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-properties-system.h"
#include "hw/xen/interface/io/console.h"
#include "hw/xen/interface/io/xs_wire.h"
#include "hw/xen/interface/grant_table.h"
#include "hw/i386/kvm/xen_primary_console.h"
#include "trace.h"
struct buffer {
uint8_t *data;
size_t consumed;
size_t size;
size_t capacity;
size_t max_capacity;
};
struct XenConsole {
struct XenDevice xendev; /* must be first */
XenEventChannel *event_channel;
int dev;
struct buffer buffer;
char *fe_path;
unsigned int ring_ref;
void *sring;
CharFrontend chr;
int backlog;
};
#define TYPE_XEN_CONSOLE_DEVICE "xen-console"
OBJECT_DECLARE_SIMPLE_TYPE(XenConsole, XEN_CONSOLE_DEVICE)
static bool buffer_append(XenConsole *con)
{
struct buffer *buffer = &con->buffer;
XENCONS_RING_IDX cons, prod, size;
struct xencons_interface *intf = con->sring;
cons = intf->out_cons;
prod = intf->out_prod;
xen_mb();
size = prod - cons;
if ((size == 0) || (size > sizeof(intf->out)))
return false;
if ((buffer->capacity - buffer->size) < size) {
buffer->capacity += (size + 1024);
buffer->data = g_realloc(buffer->data, buffer->capacity);
}
while (cons != prod)
buffer->data[buffer->size++] = intf->out[
MASK_XENCONS_IDX(cons++, intf->out)];
xen_mb();
intf->out_cons = cons;
xen_device_notify_event_channel(XEN_DEVICE(con), con->event_channel, NULL);
if (buffer->max_capacity &&
buffer->size > buffer->max_capacity) {
/* Discard the middle of the data. */
size_t over = buffer->size - buffer->max_capacity;
uint8_t *maxpos = buffer->data + buffer->max_capacity;
memmove(maxpos - over, maxpos, over);
buffer->data = g_realloc(buffer->data, buffer->max_capacity);
buffer->size = buffer->capacity = buffer->max_capacity;
if (buffer->consumed > buffer->max_capacity - over)
buffer->consumed = buffer->max_capacity - over;
}
return true;
}
static void buffer_advance(struct buffer *buffer, size_t len)
{
buffer->consumed += len;
if (buffer->consumed == buffer->size) {
buffer->consumed = 0;
buffer->size = 0;
}
}
static int ring_free_bytes(XenConsole *con)
{
struct xencons_interface *intf = con->sring;
XENCONS_RING_IDX cons, prod, space;
cons = intf->in_cons;
prod = intf->in_prod;
xen_mb();
space = prod - cons;
if (space > sizeof(intf->in))
return 0; /* ring is screwed: ignore it */
return (sizeof(intf->in) - space);
}
static int xencons_can_receive(void *opaque)
{
XenConsole *con = opaque;
return ring_free_bytes(con);
}
static void xencons_receive(void *opaque, const uint8_t *buf, int len)
{
XenConsole *con = opaque;
struct xencons_interface *intf = con->sring;
XENCONS_RING_IDX prod;
int i, max;
max = ring_free_bytes(con);
/* The can_receive() func limits this, but check again anyway */
if (max < len)
len = max;
prod = intf->in_prod;
for (i = 0; i < len; i++) {
intf->in[MASK_XENCONS_IDX(prod++, intf->in)] =
buf[i];
}
xen_wmb();
intf->in_prod = prod;
xen_device_notify_event_channel(XEN_DEVICE(con), con->event_channel, NULL);
}
static bool xencons_send(XenConsole *con)
{
ssize_t len, size;
size = con->buffer.size - con->buffer.consumed;
if (qemu_chr_fe_backend_connected(&con->chr)) {
len = qemu_chr_fe_write(&con->chr,
con->buffer.data + con->buffer.consumed,
size);
} else {
len = size;
}
if (len < 1) {
if (!con->backlog) {
con->backlog = 1;
}
} else {
buffer_advance(&con->buffer, len);
if (con->backlog && len == size) {
con->backlog = 0;
}
}
return len > 0;
}
/* -------------------------------------------------------------------- */
static bool con_event(void *_xendev)
{
XenConsole *con = XEN_CONSOLE_DEVICE(_xendev);
bool done_something;
if (xen_device_backend_get_state(&con->xendev) != XenbusStateConnected) {
return false;
}
done_something = buffer_append(con);
if (con->buffer.size - con->buffer.consumed) {
done_something |= xencons_send(con);
}
return done_something;
}
/* -------------------------------------------------------------------- */
static bool xen_console_connect(XenDevice *xendev, Error **errp)
{
ERRP_GUARD();
XenConsole *con = XEN_CONSOLE_DEVICE(xendev);
unsigned int port, limit;
if (xen_device_frontend_scanf(xendev, "ring-ref", "%u",
&con->ring_ref) != 1) {
error_setg(errp, "failed to read ring-ref");
return false;
}
if (xen_device_frontend_scanf(xendev, "port", "%u", &port) != 1) {
error_setg(errp, "failed to read remote port");
return false;
}
if (xen_device_frontend_scanf(xendev, "limit", "%u", &limit) == 1) {
con->buffer.max_capacity = limit;
}
con->event_channel = xen_device_bind_event_channel(xendev, port,
con_event,
con,
errp);
if (!con->event_channel) {
return false;
}
switch (con->dev) {
case 0:
/*
* The primary console is special. For real Xen the ring-ref is
* actually a GFN which needs to be mapped as foreignmem.
*/
if (xen_mode != XEN_EMULATE) {
xen_pfn_t mfn = (xen_pfn_t)con->ring_ref;
con->sring = qemu_xen_foreignmem_map(xendev->frontend_id, NULL,
PROT_READ | PROT_WRITE,
1, &mfn, NULL);
if (!con->sring) {
error_setg(errp, "failed to map console page");
return false;
}
break;
}
/*
* For Xen emulation, we still follow the convention of ring-ref
* holding the GFN, but we map the fixed GNTTAB_RESERVED_CONSOLE
* grant ref because there is no implementation of foreignmem
* operations for emulated mode. The emulation code which handles
* the guest-side page and event channel also needs to be informed
* of the backend event channel port, in order to reconnect to it
* after a soft reset.
*/
xen_primary_console_set_be_port(
xen_event_channel_get_local_port(con->event_channel));
con->ring_ref = GNTTAB_RESERVED_CONSOLE;
/* fallthrough */
default:
con->sring = xen_device_map_grant_refs(xendev,
&con->ring_ref, 1,
PROT_READ | PROT_WRITE,
errp);
if (!con->sring) {
error_prepend(errp, "failed to map console grant ref: ");
return false;
}
break;
}
trace_xen_console_connect(con->dev, con->ring_ref, port,
con->buffer.max_capacity);
qemu_chr_fe_set_handlers(&con->chr, xencons_can_receive,
xencons_receive, NULL, NULL, con, NULL,
true);
return true;
}
static void xen_console_disconnect(XenDevice *xendev, Error **errp)
{
XenConsole *con = XEN_CONSOLE_DEVICE(xendev);
trace_xen_console_disconnect(con->dev);
qemu_chr_fe_set_handlers(&con->chr, NULL, NULL, NULL, NULL,
con, NULL, true);
if (con->event_channel) {
xen_device_unbind_event_channel(xendev, con->event_channel,
errp);
con->event_channel = NULL;
if (xen_mode == XEN_EMULATE && !con->dev) {
xen_primary_console_set_be_port(0);
}
}
if (con->sring) {
if (!con->dev && xen_mode != XEN_EMULATE) {
qemu_xen_foreignmem_unmap(con->sring, 1);
} else {
xen_device_unmap_grant_refs(xendev, con->sring,
&con->ring_ref, 1, errp);
}
con->sring = NULL;
}
}
static void xen_console_frontend_changed(XenDevice *xendev,
enum xenbus_state frontend_state,
Error **errp)
{
ERRP_GUARD();
enum xenbus_state backend_state = xen_device_backend_get_state(xendev);
switch (frontend_state) {
case XenbusStateInitialised:
case XenbusStateConnected:
if (backend_state == XenbusStateConnected) {
break;
}
xen_console_disconnect(xendev, errp);
if (*errp) {
break;
}
if (!xen_console_connect(xendev, errp)) {
xen_device_backend_set_state(xendev, XenbusStateClosing);
break;
}
xen_device_backend_set_state(xendev, XenbusStateConnected);
break;
case XenbusStateClosing:
xen_device_backend_set_state(xendev, XenbusStateClosing);
break;
case XenbusStateClosed:
case XenbusStateUnknown:
xen_console_disconnect(xendev, errp);
if (*errp) {
break;
}
xen_device_backend_set_state(xendev, XenbusStateClosed);
break;
default:
break;
}
}
static char *xen_console_get_name(XenDevice *xendev, Error **errp)
{
XenConsole *con = XEN_CONSOLE_DEVICE(xendev);
if (con->dev == -1) {
XenBus *xenbus = XEN_BUS(qdev_get_parent_bus(DEVICE(xendev)));
int idx = (xen_mode == XEN_EMULATE) ? 0 : 1;
Error *local_err = NULL;
char *value;
/* Theoretically we could go up to INT_MAX here but that's overkill */
while (idx < 100) {
if (!idx) {
value = xs_node_read(xenbus->xsh, XBT_NULL, NULL, &local_err,
"/local/domain/%u/console",
xendev->frontend_id);
} else {
value = xs_node_read(xenbus->xsh, XBT_NULL, NULL, &local_err,
"/local/domain/%u/device/console/%u",
xendev->frontend_id, idx);
}
if (!value) {
if (errno == ENOENT) {
con->dev = idx;
error_free(local_err);
goto found;
}
error_propagate(errp, local_err);
return NULL;
}
free(value);
idx++;
}
error_setg(errp, "cannot find device index for console device");
return NULL;
}
found:
return g_strdup_printf("%u", con->dev);
}
static void xen_console_unrealize(XenDevice *xendev)
{
XenConsole *con = XEN_CONSOLE_DEVICE(xendev);
trace_xen_console_unrealize(con->dev);
/* Disconnect from the frontend in case this has not already happened */
xen_console_disconnect(xendev, NULL);
qemu_chr_fe_deinit(&con->chr, false);
}
static void xen_console_realize(XenDevice *xendev, Error **errp)
{
ERRP_GUARD();
XenConsole *con = XEN_CONSOLE_DEVICE(xendev);
Chardev *cs = qemu_chr_fe_get_driver(&con->chr);
unsigned int u;
g_autofree char *pty_name = NULL;
if (!cs) {
error_setg(errp, "no backing character device");
return;
}
if (con->dev == -1) {
error_setg(errp, "no device index provided");
return;
}
/*
* The Xen primary console is special. The ring-ref is actually a GFN to
* be mapped directly as foreignmem (not a grant ref), and the guest port
* was allocated *for* the guest by the toolstack. The guest gets these
* through HVMOP_get_param and can use the console long before it's got
* XenStore up and running. We cannot create those for a true Xen guest,
* but we can for Xen emulation.
*/
if (!con->dev) {
if (xen_mode == XEN_EMULATE) {
xen_primary_console_create();
} else if (xen_device_frontend_scanf(xendev, "ring-ref", "%u", &u)
!= 1 ||
xen_device_frontend_scanf(xendev, "port", "%u", &u) != 1) {
error_setg(errp, "cannot create primary Xen console");
return;
}
}
trace_xen_console_realize(con->dev, object_get_typename(OBJECT(cs)));
pty_name = qemu_chr_get_pty_name(cs);
if (pty_name) {
xen_device_frontend_printf(xendev, "tty", "%s", pty_name);
}
/* No normal PV driver initialization for the primary console under Xen */
if (!con->dev && xen_mode != XEN_EMULATE) {
xen_console_connect(xendev, errp);
}
}
static char *console_frontend_path(struct qemu_xs_handle *xenstore,
unsigned int dom_id, unsigned int dev)
{
if (!dev) {
return g_strdup_printf("/local/domain/%u/console", dom_id);
} else {
return g_strdup_printf("/local/domain/%u/device/console/%u", dom_id,
dev);
}
}
static char *xen_console_get_frontend_path(XenDevice *xendev, Error **errp)
{
XenConsole *con = XEN_CONSOLE_DEVICE(xendev);
XenBus *xenbus = XEN_BUS(qdev_get_parent_bus(DEVICE(xendev)));
char *ret = console_frontend_path(xenbus->xsh, xendev->frontend_id,
con->dev);
if (!ret) {
error_setg(errp, "failed to create frontend path");
}
return ret;
}
static const Property xen_console_properties[] = {
DEFINE_PROP_CHR("chardev", XenConsole, chr),
DEFINE_PROP_INT32("idx", XenConsole, dev, -1),
};
static void xen_console_class_init(ObjectClass *class, const void *data)
{
DeviceClass *dev_class = DEVICE_CLASS(class);
XenDeviceClass *xendev_class = XEN_DEVICE_CLASS(class);
xendev_class->backend = "console";
xendev_class->device = "console";
xendev_class->get_name = xen_console_get_name;
xendev_class->realize = xen_console_realize;
xendev_class->frontend_changed = xen_console_frontend_changed;
xendev_class->unrealize = xen_console_unrealize;
xendev_class->get_frontend_path = xen_console_get_frontend_path;
device_class_set_props(dev_class, xen_console_properties);
}
static const TypeInfo xen_console_type_info = {
.name = TYPE_XEN_CONSOLE_DEVICE,
.parent = TYPE_XEN_DEVICE,
.instance_size = sizeof(XenConsole),
.class_init = xen_console_class_init,
};
static void xen_console_register_types(void)
{
type_register_static(&xen_console_type_info);
}
type_init(xen_console_register_types)
/* Called to instantiate a XenConsole when the backend is detected. */
static void xen_console_device_create(XenBackendInstance *backend,
QDict *opts, Error **errp)
{
ERRP_GUARD();
XenBus *xenbus = xen_backend_get_bus(backend);
const char *name = xen_backend_get_name(backend);
unsigned long number;
char *fe = NULL, *type = NULL, *output = NULL;
char label[32];
XenDevice *xendev = NULL;
XenConsole *con;
Chardev *cd = NULL;
struct qemu_xs_handle *xsh = xenbus->xsh;
if (qemu_strtoul(name, NULL, 10, &number) || number > INT_MAX) {
error_setg(errp, "failed to parse name '%s'", name);
goto fail;
}
trace_xen_console_device_create(number);
fe = console_frontend_path(xsh, xen_domid, number);
if (fe == NULL) {
error_setg(errp, "failed to generate frontend path");
goto fail;
}
type = xs_node_read(xsh, XBT_NULL, NULL, errp, "%s/%s", fe, "type");
if (!type) {
error_prepend(errp, "failed to read console device type: ");
goto fail;
}
if (strcmp(type, "ioemu")) {
error_setg(errp, "declining to handle console type '%s'",
type);
goto fail;
}
xendev = XEN_DEVICE(qdev_new(TYPE_XEN_CONSOLE_DEVICE));
con = XEN_CONSOLE_DEVICE(xendev);
con->dev = number;
snprintf(label, sizeof(label), "xencons%ld", number);
output = xs_node_read(xsh, XBT_NULL, NULL, errp, "%s/%s", fe, "output");
if (output) {
/*
* FIXME: sure we want to support implicit
* muxed monitors here?
*/
cd = qemu_chr_new_mux_mon(label, output, NULL);
if (!cd) {
error_setg(errp, "console: No valid chardev found at '%s': ",
output);
goto fail;
}
} else if (errno != ENOENT) {
error_prepend(errp, "console: No valid chardev found: ");
goto fail;
} else {
error_free(*errp);
*errp = NULL;
if (number) {
cd = serial_hd(number);
if (!cd) {
error_setg(errp, "console: No serial device #%ld found",
number);
goto fail;
}
} else {
/* No 'output' node on primary console: use null. */
cd = qemu_chr_new(label, "null", NULL);
if (!cd) {
error_setg(errp, "console: failed to create null device");
goto fail;
}
}
}
if (!qemu_chr_fe_init(&con->chr, cd, errp)) {
error_prepend(errp, "console: failed to initialize backing chardev: ");
goto fail;
}
if (qdev_realize_and_unref(DEVICE(xendev), BUS(xenbus), errp)) {
xen_backend_set_device(backend, xendev);
goto done;
}
error_prepend(errp, "realization of console device %lu failed: ",
number);
fail:
if (xendev) {
object_unparent(OBJECT(xendev));
}
done:
g_free(fe);
free(type);
free(output);
}
static void xen_console_device_destroy(XenBackendInstance *backend,
Error **errp)
{
ERRP_GUARD();
XenDevice *xendev = xen_backend_get_device(backend);
XenConsole *con = XEN_CONSOLE_DEVICE(xendev);
trace_xen_console_device_destroy(con->dev);
object_unparent(OBJECT(xendev));
}
static const XenBackendInfo xen_console_backend_info = {
.type = "console",
.create = xen_console_device_create,
.destroy = xen_console_device_destroy,
};
static void xen_console_register_backend(void)
{
xen_backend_register(&xen_console_backend_info);
}
xen_backend_init(xen_console_register_backend);
+266
View File
@@ -0,0 +1,266 @@
/*
* QEMU model of Xilinx uartlite.
*
* Copyright (c) 2009 Edgar E. Iglesias.
*
* 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
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* 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.
*/
#include "qemu/osdep.h"
#include "qemu/log.h"
#include "qapi/error.h"
#include "hw/char/xilinx_uartlite.h"
#include "hw/core/irq.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/qdev-properties-system.h"
#include "hw/core/sysbus.h"
#include "qemu/module.h"
#include "chardev/char-fe.h"
#include "qom/object.h"
#define DUART(x)
#define R_RX 0
#define R_TX 1
#define R_STATUS 2
#define R_CTRL 3
#define R_MAX 4
#define STATUS_RXVALID 0x01
#define STATUS_RXFULL 0x02
#define STATUS_TXEMPTY 0x04
#define STATUS_TXFULL 0x08
#define STATUS_IE 0x10
#define STATUS_OVERRUN 0x20
#define STATUS_FRAME 0x40
#define STATUS_PARITY 0x80
#define CONTROL_RST_TX 0x01
#define CONTROL_RST_RX 0x02
#define CONTROL_IE 0x10
struct XilinxUARTLite {
SysBusDevice parent_obj;
EndianMode model_endianness;
MemoryRegion mmio;
CharFrontend chr;
qemu_irq irq;
uint8_t rx_fifo[8];
unsigned int rx_fifo_pos;
unsigned int rx_fifo_len;
uint32_t regs[R_MAX];
};
static void uart_update_irq(XilinxUARTLite *s)
{
unsigned int irq;
if (s->rx_fifo_len)
s->regs[R_STATUS] |= STATUS_IE;
irq = (s->regs[R_STATUS] & STATUS_IE) && (s->regs[R_CTRL] & CONTROL_IE);
qemu_set_irq(s->irq, irq);
}
static void uart_update_status(XilinxUARTLite *s)
{
uint32_t r;
r = s->regs[R_STATUS];
r &= ~7;
r |= 1 << 2; /* Tx fifo is always empty. We are fast :) */
r |= (s->rx_fifo_len == sizeof (s->rx_fifo)) << 1;
r |= (!!s->rx_fifo_len);
s->regs[R_STATUS] = r;
}
static void xilinx_uartlite_reset(DeviceState *dev)
{
uart_update_status(XILINX_UARTLITE(dev));
}
static uint64_t
uart_read(void *opaque, hwaddr addr, unsigned int size)
{
XilinxUARTLite *s = opaque;
uint32_t r = 0;
addr >>= 2;
switch (addr)
{
case R_RX:
r = s->rx_fifo[(s->rx_fifo_pos - s->rx_fifo_len) & 7];
if (s->rx_fifo_len)
s->rx_fifo_len--;
uart_update_status(s);
uart_update_irq(s);
qemu_chr_fe_accept_input(&s->chr);
break;
default:
if (addr < ARRAY_SIZE(s->regs))
r = s->regs[addr];
DUART(qemu_log("%s addr=%x v=%x\n", __func__, addr, r));
break;
}
return r;
}
static void
uart_write(void *opaque, hwaddr addr,
uint64_t val64, unsigned int size)
{
XilinxUARTLite *s = opaque;
uint32_t value = val64;
unsigned char ch = value;
addr >>= 2;
switch (addr)
{
case R_STATUS:
qemu_log_mask(LOG_GUEST_ERROR, "%s: write to UART STATUS\n",
__func__);
break;
case R_CTRL:
if (value & CONTROL_RST_RX) {
s->rx_fifo_pos = 0;
s->rx_fifo_len = 0;
}
s->regs[addr] = value;
break;
case R_TX:
/* XXX this blocks entire thread. Rewrite to use
* qemu_chr_fe_write and background I/O callbacks */
qemu_chr_fe_write_all(&s->chr, &ch, 1);
s->regs[addr] = value;
/* hax. */
s->regs[R_STATUS] |= STATUS_IE;
break;
default:
DUART(printf("%s addr=%x v=%x\n", __func__, addr, value));
if (addr < ARRAY_SIZE(s->regs))
s->regs[addr] = value;
break;
}
uart_update_status(s);
uart_update_irq(s);
}
static const MemoryRegionOps uart_ops[2] = {
[0 ... 1] = {
.read = uart_read,
.write = uart_write,
.valid = {
.min_access_size = 1,
.max_access_size = 4,
},
},
[0].endianness = DEVICE_LITTLE_ENDIAN,
[1].endianness = DEVICE_BIG_ENDIAN,
};
static const Property xilinx_uartlite_properties[] = {
DEFINE_PROP_ENDIAN_NODEFAULT("endianness", XilinxUARTLite, model_endianness),
DEFINE_PROP_CHR("chardev", XilinxUARTLite, chr),
};
static void uart_rx(void *opaque, const uint8_t *buf, int size)
{
XilinxUARTLite *s = opaque;
/* Got a byte. */
if (s->rx_fifo_len >= 8) {
printf("WARNING: UART dropped char.\n");
return;
}
s->rx_fifo[s->rx_fifo_pos] = *buf;
s->rx_fifo_pos++;
s->rx_fifo_pos &= 0x7;
s->rx_fifo_len++;
uart_update_status(s);
uart_update_irq(s);
}
static int uart_can_rx(void *opaque)
{
XilinxUARTLite *s = opaque;
return s->rx_fifo_len < sizeof(s->rx_fifo);
}
static void uart_event(void *opaque, QEMUChrEvent event)
{
}
static void xilinx_uartlite_realize(DeviceState *dev, Error **errp)
{
XilinxUARTLite *s = XILINX_UARTLITE(dev);
if (s->model_endianness == ENDIAN_MODE_UNSPECIFIED) {
error_setg(errp, TYPE_XILINX_UARTLITE " property 'endianness'"
" must be set to 'big' or 'little'");
return;
}
memory_region_init_io(&s->mmio, OBJECT(dev),
&uart_ops[s->model_endianness == ENDIAN_MODE_BIG],
s, "xlnx.xps-uartlite", R_MAX * 4);
qemu_chr_fe_set_handlers(&s->chr, uart_can_rx, uart_rx,
uart_event, NULL, s, NULL, true);
}
static void xilinx_uartlite_init(Object *obj)
{
XilinxUARTLite *s = XILINX_UARTLITE(obj);
sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
}
static void xilinx_uartlite_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
device_class_set_legacy_reset(dc, xilinx_uartlite_reset);
dc->realize = xilinx_uartlite_realize;
device_class_set_props(dc, xilinx_uartlite_properties);
}
static const TypeInfo xilinx_uartlite_info = {
.name = TYPE_XILINX_UARTLITE,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(XilinxUARTLite),
.instance_init = xilinx_uartlite_init,
.class_init = xilinx_uartlite_class_init,
};
static void xilinx_uart_register_types(void)
{
type_register_static(&xilinx_uartlite_info);
}
type_init(xilinx_uart_register_types)