qemu-patch-raspberry4/hw/char/digic-uart.c
Daniel P. Berrange 6ab3fc32ea hw: replace most use of qemu_chr_fe_write with qemu_chr_fe_write_all
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>
2016-09-13 19:09:42 +02:00

204 lines
5 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)