6ab3fc32ea
The qemu_chr_fe_write method will return -1 on EAGAIN if the chardev backend write would block. Almost no callers of the qemu_chr_fe_write() method check the return value, instead blindly assuming data was successfully sent. In most cases this will lead to silent data loss on interactive consoles, but in some cases (eg RNG EGD) it'll just cause corruption of the protocol being spoken. We unfortunately can't fix the virtio-console code, due to a bug in the Linux guest drivers, which would cause the entire Linux kernel to hang if we delay processing of the incoming data in any way. Fixing this requires first fixing the guest driver to not hold spinlocks while writing to the hvc device backend. Fixes bug: https://bugs.launchpad.net/qemu/+bug/1586756 Signed-off-by: Daniel P. Berrange <berrange@redhat.com> Message-Id: <1473170165-540-4-git-send-email-berrange@redhat.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
204 lines
5.0 KiB
C
204 lines
5.0 KiB
C
/*
|
|
* QEMU model of the Canon DIGIC UART block.
|
|
*
|
|
* Copyright (C) 2013 Antony Pavlov <antonynpavlov@gmail.com>
|
|
*
|
|
* 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/hw.h"
|
|
#include "hw/sysbus.h"
|
|
#include "sysemu/char.h"
|
|
#include "qemu/log.h"
|
|
|
|
#include "hw/char/digic-uart.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"
|
|
TARGET_FMT_plx, 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:
|
|
if (s->chr) {
|
|
/* 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"
|
|
TARGET_FMT_plx, 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, int 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);
|
|
|
|
if (s->chr) {
|
|
qemu_chr_add_handlers(s->chr, uart_can_rx, uart_rx, uart_event, s);
|
|
}
|
|
}
|
|
|
|
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 = (VMStateField[]) {
|
|
VMSTATE_UINT32(reg_rx, DigicUartState),
|
|
VMSTATE_UINT32(reg_st, DigicUartState),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static Property digic_uart_properties[] = {
|
|
DEFINE_PROP_CHR("chardev", DigicUartState, chr),
|
|
DEFINE_PROP_END_OF_LIST(),
|
|
};
|
|
|
|
static void digic_uart_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
|
|
dc->realize = digic_uart_realize;
|
|
dc->reset = digic_uart_reset;
|
|
dc->vmsd = &vmstate_digic_uart;
|
|
dc->props = 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)
|