[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] Re: [Xen-devel] [PATCH 04/17 v5] xen/arm: vpl011: Add SBSA UART emulation in Xen
On Thu, 22 Jun 2017, Bhupinder Thakur wrote: > Add emulation code to emulate read/write access to pl011 registers > and pl011 interrupts: > > - Emulate DR read/write by reading and writing from/to the IN > and OUT ring buffers and raising an event to the backend when > there is data in the OUT ring buffer and injecting an interrupt > to the guest when there is data in the IN ring buffer > > - Other registers are related to interrupt management and > essentially control when interrupts are delivered to the guest > > This patch implements the SBSA Generic UART which is a subset of ARM > PL011 UART. > > The SBSA Generic UART is covered in Appendix B of > https://static.docs.arm.com/den0029/a/Server_Base_System_Architecture_v3_1_ARM_DEN_0029A.pdf > > Signed-off-by: Bhupinder Thakur <bhupinder.thakur@xxxxxxxxxx> > --- > CC: Stefano Stabellini <sstabellini@xxxxxxxxxx> > CC: Julien Grall <julien.grall@xxxxxxx> > CC: Andre Przywara <andre.przywara@xxxxxxx> > > Changes since v4: > - Renamed vpl011_update() to vpl011_update_interrupt_status() and added logic > to avoid > raising spurious interrupts. > - Used barrier instructions correctly while reading/writing data to the ring > buffer. > - Proper lock taken before reading ring buffer indices. > > Changes since v3: > - Moved the call to DEFINE_XEN_FLEX_RING from vpl011.h to public/console.h. > This macro defines > standard functions to operate on the ring buffer. > - Lock taken while updating the interrupt mask and clear registers in > mmio_write. > - Use gfn_t instead of xen_pfn_t. > - vgic_free_virq called if there is any error in vpl011 initialization. > - mmio handlers freed if there is any error in vpl011 initialization. > - Removed vpl011->initialized flag usage as the same check could be done > using vpl011->ring-ref. > - Used return instead of break in the switch handling of emulation of > different pl011 registers. > - Renamed vpl011_update_spi() to vpl011_update(). > > Changes since v2: > - Use generic vreg_reg* for read/write of registers emulating pl011. > - Use generic ring buffer functions defined using DEFINE_XEN_FLEX_RING. > - Renamed the SPI injection function to vpl011_update_spi() to reflect level > triggered nature of pl011 interrupts. > - The pl011 register access address should always be the base address of the > corresponding register as per section B of the SBSA document. For this > reason, > the register range address access is not allowed. > > Changes since v1: > - Removed the optimiztion related to sendiing events to xenconsole > - Use local variables as ring buffer indices while using the ring buffer > > xen/arch/arm/Kconfig | 7 + > xen/arch/arm/Makefile | 1 + > xen/arch/arm/vpl011.c | 449 > +++++++++++++++++++++++++++++++++++++++ > xen/include/asm-arm/domain.h | 6 + > xen/include/asm-arm/pl011-uart.h | 2 + > xen/include/asm-arm/vpl011.h | 73 +++++++ > xen/include/public/arch-arm.h | 6 + > 7 files changed, 544 insertions(+) > create mode 100644 xen/arch/arm/vpl011.c > create mode 100644 xen/include/asm-arm/vpl011.h > > diff --git a/xen/arch/arm/Kconfig b/xen/arch/arm/Kconfig > index d46b98c..f58019d 100644 > --- a/xen/arch/arm/Kconfig > +++ b/xen/arch/arm/Kconfig > @@ -50,6 +50,13 @@ config HAS_ITS > prompt "GICv3 ITS MSI controller support" if EXPERT = "y" > depends on HAS_GICV3 > > +config SBSA_VUART_CONSOLE > + bool "Emulated SBSA UART console support" > + default y > + ---help--- > + Allows a guest to use SBSA Generic UART as a console. The > + SBSA Generic UART implements a subset of ARM PL011 UART. > + > endmenu > > menu "ARM errata workaround via the alternative framework" > diff --git a/xen/arch/arm/Makefile b/xen/arch/arm/Makefile > index 49e1fb2..d9c6ebf 100644 > --- a/xen/arch/arm/Makefile > +++ b/xen/arch/arm/Makefile > @@ -50,6 +50,7 @@ obj-$(CONFIG_HAS_GICV3) += vgic-v3.o > obj-$(CONFIG_HAS_ITS) += vgic-v3-its.o > obj-y += vm_event.o > obj-y += vtimer.o > +obj-$(CONFIG_SBSA_VUART_CONSOLE) += vpl011.o > obj-y += vpsci.o > obj-y += vuart.o > > diff --git a/xen/arch/arm/vpl011.c b/xen/arch/arm/vpl011.c > new file mode 100644 > index 0000000..db8651c > --- /dev/null > +++ b/xen/arch/arm/vpl011.c > @@ -0,0 +1,449 @@ > +/* > + * arch/arm/vpl011.c > + * > + * Virtual PL011 UART > + * > + * 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, 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 <xen/errno.h> > +#include <xen/event.h> > +#include <xen/guest_access.h> > +#include <xen/init.h> > +#include <xen/lib.h> > +#include <xen/mm.h> > +#include <xen/sched.h> > +#include <public/domctl.h> > +#include <public/io/console.h> > +#include <asm-arm/pl011-uart.h> > +#include <asm-arm/vgic-emul.h> > +#include <asm/vpl011.h> > + > +/* > + * Since pl011 registers are 32-bit registers, all registers > + * are handled similarly allowing 8-bit, 16-bit and 32-bit > + * accesses except 64-bit access. > + */ > +static bool vpl011_reg32_check_access(struct hsr_dabt dabt) > +{ > + return (dabt.size != DABT_DOUBLE_WORD); > +} > + > +static void vpl011_update_interrupt_status(struct domain *d) > +{ > + struct vpl011 *vpl011 = &d->arch.vpl011; > + uint32_t interrupt_status = vpl011->uartris & vpl011->uartimsc; > + > + /* > + * TODO: PL011 interrupts are level triggered which means > + * that interrupt needs to be set/clear instead of being > + * injected. However, currently vGIC does not handle level > + * triggered interrupts properly. This function needs to be > + * revisited once vGIC starts handling level triggered > + * interrupts. > + */ > + > + /* > + * Raise an interrupt only if any additional interrupt > + * status bit has been set since the last time. > + */ > + if ( interrupt_status & ~vpl011->shadow_uartris ) > + vgic_vcpu_inject_spi(d, GUEST_VPL011_SPI); > + > + vpl011->shadow_uartris = interrupt_status; > +} > + > +static uint8_t vpl011_read_data(struct domain *d) > +{ > + unsigned long flags; > + uint8_t data = 0; > + struct vpl011 *vpl011 = &d->arch.vpl011; > + struct xencons_interface *intf = vpl011->ring_buf; > + XENCONS_RING_IDX in_cons, in_prod; > + > + VPL011_LOCK(d, flags); > + > + in_cons = intf->in_cons; > + in_prod = intf->in_prod; > + > + smp_rmb(); > + > + /* > + * It is expected that there will be data in the ring buffer when this > + * function is called since the guest is expected to read the data > register > + * only if the TXFE flag is not set. > + * If the guest still does read when TXFE bit is set then 0 will be > returned. > + */ > + if ( xencons_queued(in_prod, in_cons, sizeof(intf->in)) > 0 ) > + { > + data = intf->in[xencons_mask(in_cons, sizeof(intf->in))]; > + in_cons += 1; > + smp_mb(); > + intf->in_cons = in_cons; > + } > + else > + gprintk(XENLOG_ERR, "vpl011: Unexpected IN ring buffer empty\n"); > + > + if ( xencons_queued(in_prod, in_cons, sizeof(intf->in)) == 0 ) > + { > + vpl011->uartfr |= RXFE; > + vpl011->uartris &= ~RXI; > + } > + > + vpl011->uartfr &= ~RXFF; > + > + vpl011_update_interrupt_status(d); > + > + VPL011_UNLOCK(d, flags); > + > + /* > + * Send an event to console backend to indicate that data has been > + * read from the IN ring buffer. > + */ > + notify_via_xen_event_channel(d, vpl011->evtchn); > + > + return data; > +} > + > +static void vpl011_write_data(struct domain *d, uint8_t data) > +{ > + unsigned long flags; > + struct vpl011 *vpl011 = &d->arch.vpl011; > + struct xencons_interface *intf = vpl011->ring_buf; > + XENCONS_RING_IDX out_cons, out_prod; > + > + VPL011_LOCK(d, flags); > + > + out_cons = intf->out_cons; > + out_prod = intf->out_prod; > + > + smp_rmb(); This should be smp_mb(); Aside from this, the patch looks good to me. > + /* > + * It is expected that the ring is not full when this function is called > + * as the guest is expected to write to the data register only when the > + * TXFF flag is not set. > + * In case the guest does write even when the TXFF flag is set then the > + * data will be silently dropped. > + */ > + if ( xencons_queued(out_prod, out_cons, sizeof(intf->out)) != > + sizeof (intf->out) ) > + { > + intf->out[xencons_mask(out_prod, sizeof(intf->out))] = data; > + out_prod += 1; > + smp_wmb(); > + intf->out_prod = out_prod; > + } > + else > + gprintk(XENLOG_ERR, "vpl011: Unexpected OUT ring buffer full\n"); > + > + if ( xencons_queued(out_prod, out_cons, sizeof(intf->out)) == > + sizeof (intf->out) ) > + { > + vpl011->uartfr |= TXFF; > + vpl011->uartris &= ~TXI; > + } > + > + vpl011->uartfr |= BUSY; > + > + vpl011->uartfr &= ~TXFE; > + > + vpl011_update_interrupt_status(d); > + > + VPL011_UNLOCK(d, flags); > + > + /* > + * Send an event to console backend to indicate that there is > + * data in the OUT ring buffer. > + */ > + notify_via_xen_event_channel(d, vpl011->evtchn); > +} > + > +static int vpl011_mmio_read(struct vcpu *v, > + mmio_info_t *info, > + register_t *r, > + void *priv) > +{ > + struct hsr_dabt dabt = info->dabt; > + uint32_t vpl011_reg = (uint32_t)(info->gpa - GUEST_PL011_BASE); > + struct vpl011 *vpl011 = &v->domain->arch.vpl011; > + struct domain *d = v->domain; > + unsigned long flags; > + > + switch ( vpl011_reg ) > + { > + case DR: > + if ( !vpl011_reg32_check_access(dabt) ) goto bad_width; > + > + *r = vreg_reg32_extract(vpl011_read_data(d), info); > + return 1; > + > + case RSR: > + if ( !vpl011_reg32_check_access(dabt) ) goto bad_width; > + > + /* It always returns 0 as there are no physical errors. */ > + *r = 0; > + return 1; > + > + case FR: > + if ( !vpl011_reg32_check_access(dabt) ) goto bad_width; > + > + VPL011_LOCK(d, flags); > + *r = vreg_reg32_extract(vpl011->uartfr, info); > + VPL011_UNLOCK(d, flags); > + return 1; > + > + case RIS: > + if ( !vpl011_reg32_check_access(dabt) ) goto bad_width; > + > + VPL011_LOCK(d, flags); > + *r = vreg_reg32_extract(vpl011->uartris, info); > + VPL011_UNLOCK(d, flags); > + return 1; > + > + case MIS: > + if ( !vpl011_reg32_check_access(dabt) ) goto bad_width; > + > + VPL011_LOCK(d, flags); > + *r = vreg_reg32_extract(vpl011->uartris & vpl011->uartimsc, > + info); > + VPL011_UNLOCK(d, flags); > + return 1; > + > + case IMSC: > + if ( !vpl011_reg32_check_access(dabt) ) goto bad_width; > + > + VPL011_LOCK(d, flags); > + *r = vreg_reg32_extract(vpl011->uartimsc, info); > + VPL011_UNLOCK(d, flags); > + return 1; > + > + case ICR: > + if ( !vpl011_reg32_check_access(dabt) ) goto bad_width; > + > + /* Only write is valid. */ > + return 0; > + > + default: > + gprintk(XENLOG_ERR, "vpl011: unhandled read r%d offset %#08x\n", > + dabt.reg, vpl011_reg); > + return 0; > + } > + > + return 1; > + > +bad_width: > + gprintk(XENLOG_ERR, "vpl011: bad read width %d r%d offset %#08x\n", > + dabt.size, dabt.reg, vpl011_reg); > + domain_crash_synchronous(); > + return 0; > + > +} > + > +static int vpl011_mmio_write(struct vcpu *v, > + mmio_info_t *info, > + register_t r, > + void *priv) > +{ > + struct hsr_dabt dabt = info->dabt; > + uint32_t vpl011_reg = (uint32_t)(info->gpa - GUEST_PL011_BASE); > + struct vpl011 *vpl011 = &v->domain->arch.vpl011; > + struct domain *d = v->domain; > + unsigned long flags; > + > + switch ( vpl011_reg ) > + { > + case DR: > + { > + uint32_t data = 0; > + > + if ( !vpl011_reg32_check_access(dabt) ) goto bad_width; > + > + vreg_reg32_update(&data, r, info); > + vpl011_write_data(v->domain, data); > + return 1; > + } > + case RSR: /* Nothing to clear. */ > + if ( !vpl011_reg32_check_access(dabt) ) goto bad_width; > + > + return 1; > + > + case FR: > + case RIS: > + case MIS: > + goto write_ignore; > + > + case IMSC: > + if ( !vpl011_reg32_check_access(dabt) ) goto bad_width; > + > + VPL011_LOCK(d, flags); > + vreg_reg32_update(&vpl011->uartimsc, r, info); > + vpl011_update_interrupt_status(v->domain); > + VPL011_UNLOCK(d, flags); > + return 1; > + > + case ICR: > + if ( !vpl011_reg32_check_access(dabt) ) goto bad_width; > + > + VPL011_LOCK(d, flags); > + vreg_reg32_clearbits(&vpl011->uartris, r, info); > + vpl011_update_interrupt_status(d); > + VPL011_UNLOCK(d, flags); > + return 1; > + > + default: > + gprintk(XENLOG_ERR, "vpl011: unhandled write r%d offset %#08x\n", > + dabt.reg, vpl011_reg); > + return 0; > + } > + > +write_ignore: > + return 1; > + > +bad_width: > + gprintk(XENLOG_ERR, "vpl011: bad write width %d r%d offset %#08x\n", > + dabt.size, dabt.reg, vpl011_reg); > + domain_crash_synchronous(); > + return 0; > + > +} > + > +static const struct mmio_handler_ops vpl011_mmio_handler = { > + .read = vpl011_mmio_read, > + .write = vpl011_mmio_write, > +}; > + > +static void vpl011_data_avail(struct domain *d) > +{ > + unsigned long flags; > + struct vpl011 *vpl011 = &d->arch.vpl011; > + struct xencons_interface *intf = vpl011->ring_buf; > + XENCONS_RING_IDX in_cons, in_prod, out_cons, out_prod; > + XENCONS_RING_IDX in_ring_qsize, out_ring_qsize; > + > + VPL011_LOCK(d, flags); > + > + in_cons = intf->in_cons; > + in_prod = intf->in_prod; > + out_cons = intf->out_cons; > + out_prod = intf->out_prod; > + > + smp_rmb(); > + > + in_ring_qsize = xencons_queued(in_prod, > + in_cons, > + sizeof(intf->in)); > + > + out_ring_qsize = xencons_queued(out_prod, > + out_cons, > + sizeof(intf->out)); > + > + /* Update the uart rx state if the buffer is not empty. */ > + if ( in_ring_qsize != 0 ) > + { > + vpl011->uartfr &= ~RXFE; > + if ( in_ring_qsize == sizeof(intf->in) ) > + vpl011->uartfr |= RXFF; > + vpl011->uartris |= RXI; > + } > + > + /* Update the uart tx state if the buffer is not full. */ > + if ( out_ring_qsize != sizeof(intf->out) ) > + { > + vpl011->uartfr &= ~TXFF; > + vpl011->uartris |= TXI; > + if ( out_ring_qsize == 0 ) > + { > + vpl011->uartfr &= ~BUSY; > + vpl011->uartfr |= TXFE; > + } > + } > + > + vpl011_update_interrupt_status(d); > + > + VPL011_UNLOCK(d, flags); > +} > + > + > +static void vpl011_notification(struct vcpu *v, unsigned int port) > +{ > + vpl011_data_avail(v->domain); > +} > + > +int domain_vpl011_init(struct domain *d, struct vpl011_init_info *info) > +{ > + int rc; > + struct vpl011 *vpl011 = &d->arch.vpl011; > + > + if ( vpl011->ring_buf ) > + return -EINVAL; > + > + /* Map the guest PFN to Xen address space. */ > + rc = prepare_ring_for_helper(d, > + gfn_x(info->gfn), > + &vpl011->ring_page, > + &vpl011->ring_buf); > + if ( rc < 0 ) > + goto out; > + > + rc = vgic_reserve_virq(d, GUEST_VPL011_SPI); > + if ( !rc ) > + { > + rc = -EINVAL; > + goto out1; > + } > + > + rc = alloc_unbound_xen_event_channel(d, 0, info->console_domid, > + vpl011_notification); > + if ( rc < 0 ) > + goto out2; > + > + vpl011->evtchn = info->evtchn = rc; > + > + spin_lock_init(&vpl011->lock); > + > + register_mmio_handler(d, &vpl011_mmio_handler, > + GUEST_PL011_BASE, GUEST_PL011_SIZE, NULL); > + > + return 0; > + > +out2: > + vgic_free_virq(d, GUEST_VPL011_SPI); > + > +out1: > + destroy_ring_for_helper(&vpl011->ring_buf, vpl011->ring_page); > + > +out: > + return rc; > +} > + > +void domain_vpl011_deinit(struct domain *d) > +{ > + struct vpl011 *vpl011 = &d->arch.vpl011; > + > + if ( !vpl011->ring_buf ) > + return; > + > + free_xen_event_channel(d, vpl011->evtchn); > + destroy_ring_for_helper(&vpl011->ring_buf, vpl011->ring_page); > + xfree(d->arch.vmmio.handlers); > +} > + > +/* > + * Local variables: > + * mode: C > + * c-file-style: "BSD" > + * c-basic-offset: 4 > + * indent-tabs-mode: nil > + * End: > + */ > diff --git a/xen/include/asm-arm/domain.h b/xen/include/asm-arm/domain.h > index 8dfc1d1..1a1145d 100644 > --- a/xen/include/asm-arm/domain.h > +++ b/xen/include/asm-arm/domain.h > @@ -11,6 +11,7 @@ > #include <public/hvm/params.h> > #include <xen/serial.h> > #include <xen/rbtree.h> > +#include <asm-arm/vpl011.h> > > struct hvm_domain > { > @@ -145,6 +146,11 @@ struct arch_domain > struct { > uint8_t privileged_call_enabled : 1; > } monitor; > + > +#ifdef CONFIG_SBSA_VUART_CONSOLE > + struct vpl011 vpl011; > +#endif > + > } __cacheline_aligned; > > struct arch_vcpu > diff --git a/xen/include/asm-arm/pl011-uart.h > b/xen/include/asm-arm/pl011-uart.h > index 123f477..57e9ec7 100644 > --- a/xen/include/asm-arm/pl011-uart.h > +++ b/xen/include/asm-arm/pl011-uart.h > @@ -49,6 +49,8 @@ > /* FR bits */ > #define TXFE (1<<7) /* TX FIFO empty */ > #define RXFE (1<<4) /* RX FIFO empty */ > +#define TXFF (1<<5) /* TX FIFO full */ > +#define RXFF (1<<6) /* RX FIFO full */ > #define BUSY (1<<3) /* Transmit is not complete */ > > /* LCR_H bits */ > diff --git a/xen/include/asm-arm/vpl011.h b/xen/include/asm-arm/vpl011.h > new file mode 100644 > index 0000000..f1c6716 > --- /dev/null > +++ b/xen/include/asm-arm/vpl011.h > @@ -0,0 +1,73 @@ > +/* > + * include/xen/vpl011.h > + * > + * Virtual PL011 UART > + * > + * 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, 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/>. > + */ > + > +#ifndef _VPL011_H_ > +#define _VPL011_H_ > + > +#include <public/domctl.h> > +#include <public/io/ring.h> > +#include <asm-arm/vreg.h> > +#include <xen/mm.h> > + > +/* helper macros */ > +#define VPL011_LOCK(d,flags) spin_lock_irqsave(&(d)->arch.vpl011.lock, flags) > +#define VPL011_UNLOCK(d,flags) > spin_unlock_irqrestore(&(d)->arch.vpl011.lock, flags) > + > +struct vpl011 { > + void *ring_buf; > + struct page_info *ring_page; > + uint32_t uartfr; /* Flag register */ > + uint32_t uartcr; /* Control register */ > + uint32_t uartimsc; /* Interrupt mask register*/ > + uint32_t uarticr; /* Interrupt clear register */ > + uint32_t uartris; /* Raw interrupt status register */ > + uint32_t shadow_uartris; /* shadowed Raw interrupt status register */ > + uint32_t uartmis; /* Masked interrupt register */ > + spinlock_t lock; > + evtchn_port_t evtchn; > +}; > + > +struct vpl011_init_info { > + uint32_t console_domid; > + gfn_t gfn; > + evtchn_port_t evtchn; > +}; > + > +#ifdef CONFIG_SBSA_VUART_CONSOLE > +int domain_vpl011_init(struct domain *d, > + struct vpl011_init_info *info); > +void domain_vpl011_deinit(struct domain *d); > +#else > +static inline int domain_vpl011_init(struct domain *d, > + struct vpl011_init_info *info) > +{ > + return -ENOSYS; > +} > + > +static inline void domain_vpl011_deinit(struct domain *d) { } > +#endif > +#endif > + > +/* > + * Local variables: > + * mode: C > + * c-file-style: "BSD" > + * c-basic-offset: 4 > + * indent-tabs-mode: nil > + * End: > + */ > diff --git a/xen/include/public/arch-arm.h b/xen/include/public/arch-arm.h > index bd974fb..85ab665 100644 > --- a/xen/include/public/arch-arm.h > +++ b/xen/include/public/arch-arm.h > @@ -410,6 +410,10 @@ typedef uint64_t xen_callback_t; > #define GUEST_ACPI_BASE 0x20000000ULL > #define GUEST_ACPI_SIZE 0x02000000ULL > > +/* PL011 mappings */ > +#define GUEST_PL011_BASE 0x22000000ULL > +#define GUEST_PL011_SIZE 0x00001000ULL > + > /* > * 16MB == 4096 pages reserved for guest to use as a region to map its > * grant table in. > @@ -444,6 +448,8 @@ typedef uint64_t xen_callback_t; > #define GUEST_TIMER_PHYS_NS_PPI 30 > #define GUEST_EVTCHN_PPI 31 > > +#define GUEST_VPL011_SPI 32 > + > /* PSCI functions */ > #define PSCI_cpu_suspend 0 > #define PSCI_cpu_off 1 > -- > 2.7.4 > _______________________________________________ Xen-devel mailing list Xen-devel@xxxxxxxxxxxxx https://lists.xen.org/xen-devel
|
Lists.xenproject.org is hosted with RackSpace, monitoring our |