Message ID | 1500296815-10243-3-git-send-email-bhupinder.thakur@linaro.org (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Hi Bhupinder, On 17/07/17 14:06, 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@linaro.org> Acked-by: Julien Grall <julien.grall@arm.com> Cheers,
On Mon, 17 Jul 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@linaro.org> Reviewed-by: Stefano Stabellini <sstabellini@kernel.org> > --- > CC: Stefano Stabellini <sstabellini@kernel.org> > CC: Julien Grall <julien.grall@arm.com> > CC: Andre Przywara <andre.przywara@arm.com> > > Changes since v5: > - use <asm/> instead of <asm-arm/> for including arm specific header files. > - renamed shadow_uartris to shadow_uartmis to indicate that it is masked interrupt status. > - use smp_mb() instead of smp_rmb() in vpl011_write_data(). > > 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 | 455 +++++++++++++++++++++++++++++++++++++++ > xen/include/asm-arm/domain.h | 6 + > xen/include/asm-arm/pl011-uart.h | 2 + > xen/include/asm-arm/vpl011.h | 72 +++++++ > xen/include/public/arch-arm.h | 6 + > 7 files changed, 549 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..dc98490 > --- /dev/null > +++ b/xen/arch/arm/vpl011.c > @@ -0,0 +1,455 @@ > +/* > + * 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/pl011-uart.h> > +#include <asm/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 uartmis = vpl011->uartris & vpl011->uartimsc; > + > + /* > + * This function is expected to be called with the lock taken. > + */ > + ASSERT(spin_is_locked(&vpl011->lock)); > + > + /* > + * 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 ( uartmis & ~vpl011->shadow_uartmis ) > + vgic_vcpu_inject_spi(d, GUEST_VPL011_SPI); > + > + vpl011->shadow_uartmis = uartmis; > +} > + > +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_mb(); > + > + /* > + * 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); > + data &= 0xFF; > + 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..0439e64 > --- /dev/null > +++ b/xen/include/asm-arm/vpl011.h > @@ -0,0 +1,72 @@ > +/* > + * 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/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_uartmis; /* shadow 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 /* _VPL011_H_ */ > + > +/* > + * 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 >
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..dc98490 --- /dev/null +++ b/xen/arch/arm/vpl011.c @@ -0,0 +1,455 @@ +/* + * 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/pl011-uart.h> +#include <asm/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 uartmis = vpl011->uartris & vpl011->uartimsc; + + /* + * This function is expected to be called with the lock taken. + */ + ASSERT(spin_is_locked(&vpl011->lock)); + + /* + * 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 ( uartmis & ~vpl011->shadow_uartmis ) + vgic_vcpu_inject_spi(d, GUEST_VPL011_SPI); + + vpl011->shadow_uartmis = uartmis; +} + +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_mb(); + + /* + * 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); + data &= 0xFF; + 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..0439e64 --- /dev/null +++ b/xen/include/asm-arm/vpl011.h @@ -0,0 +1,72 @@ +/* + * 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/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_uartmis; /* shadow 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 /* _VPL011_H_ */ + +/* + * 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
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@linaro.org> --- CC: Stefano Stabellini <sstabellini@kernel.org> CC: Julien Grall <julien.grall@arm.com> CC: Andre Przywara <andre.przywara@arm.com> Changes since v5: - use <asm/> instead of <asm-arm/> for including arm specific header files. - renamed shadow_uartris to shadow_uartmis to indicate that it is masked interrupt status. - use smp_mb() instead of smp_rmb() in vpl011_write_data(). 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 | 455 +++++++++++++++++++++++++++++++++++++++ xen/include/asm-arm/domain.h | 6 + xen/include/asm-arm/pl011-uart.h | 2 + xen/include/asm-arm/vpl011.h | 72 +++++++ xen/include/public/arch-arm.h | 6 + 7 files changed, 549 insertions(+) create mode 100644 xen/arch/arm/vpl011.c create mode 100644 xen/include/asm-arm/vpl011.h