Import QEMU upstream snapshot d2e570c
Upstream: https://gitlab.com/qemu-project/qemu.git Upstream-Commit: d2e570cc0f97b936902a5b1b86b73c0f5998b475
This commit is contained in:
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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);
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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'))
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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, ¶llel_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(¶llel_isa_info);
|
||||
}
|
||||
|
||||
type_init(parallel_register_types)
|
||||
+729
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
#include "trace/trace-hw_char.h"
|
||||
@@ -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
@@ -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);
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user