diff --git a/xen/arch/arm/Rules.mk b/xen/arch/arm/Rules.mk index 569a0badc8..43b32d094d 100644 --- a/xen/arch/arm/Rules.mk +++ b/xen/arch/arm/Rules.mk @@ -44,6 +44,7 @@ EARLY_PRINTK_vexpress := pl011,0x1c090000 EARLY_PRINTK_xgene-mcdivitt := 8250,0x1c021000,2 EARLY_PRINTK_xgene-storm := 8250,0x1c020000,2 EARLY_PRINTK_zynqmp := cadence,0xff000000 +EARLY_PRINTK_tegra := 8250,0x70006000,2 ifneq ($(EARLY_PRINTK_$(CONFIG_EARLY_PRINTK)),) EARLY_PRINTK_CFG := $(subst $(comma), ,$(EARLY_PRINTK_$(CONFIG_EARLY_PRINTK))) diff --git a/xen/arch/arm/domain_build.c b/xen/arch/arm/domain_build.c index de59e5fd0e..92536dd4ad 100644 --- a/xen/arch/arm/domain_build.c +++ b/xen/arch/arm/domain_build.c @@ -778,8 +778,8 @@ static int make_gic_node(const struct domain *d, void *fdt, { const struct dt_device_node *gic = dt_interrupt_controller; int res = 0; - const void *addrcells, *sizecells; - u32 addrcells_len, sizecells_len; + const void *addrcells, *sizecells, *iparent; + u32 addrcells_len, sizecells_len, iparent_len; /* * Xen currently supports only a single GIC. Discard any secondary @@ -809,6 +809,19 @@ static int make_gic_node(const struct domain *d, void *fdt, return res; } + /* + * If available, explicitly inherit interrupt-parent property from host + * device tree. This will prevent the risk of incorrect identification + * of the parent on platforms with more than one interrupt controller. + */ + iparent = dt_get_property(gic, "interrupt-parent", &iparent_len); + if ( iparent ) + { + res = fdt_property(fdt, "interrupt-parent", iparent, iparent_len); + if ( res ) + return res; + } + addrcells = dt_get_property(gic, "#address-cells", &addrcells_len); if ( addrcells ) { @@ -1107,12 +1120,16 @@ static int handle_device(struct domain *d, struct dt_device_node *dev, /* * Don't map IRQ that have no physical meaning - * ie: IRQ whose controller is not the GIC + * ie: IRQ that does not wind up being controlled by the GIC + * (Note that we can't just check to see if an IRQ is owned by the GIC, + * as some platforms have a controller between the device irq and the GIC, + * such as the Tegra legacy interrupt controller.) */ - if ( rirq.controller != dt_interrupt_controller ) + if ( !platform_irq_is_routable(&rirq) ) { - dt_dprintk("irq %u not connected to primary controller. Connected to %s\n", - i, dt_node_full_name(rirq.controller)); + dt_dprintk("irq %u not (directly or indirectly) connected to primary" + "controller. Connected to %s\n", i, + dt_node_full_name(rirq.controller)); continue; } diff --git a/xen/arch/arm/irq.c b/xen/arch/arm/irq.c index f3f20a69c7..51bce58efc 100644 --- a/xen/arch/arm/irq.c +++ b/xen/arch/arm/irq.c @@ -26,6 +26,7 @@ #include #include +#include static unsigned int local_irqs_type[NR_LOCAL_IRQS]; static DEFINE_SPINLOCK(local_irqs_type_lock); @@ -142,6 +143,16 @@ static inline struct domain *irq_get_domain(struct irq_desc *desc) return irq_get_guest_info(desc)->d; } +domid_t irq_get_domain_id(struct irq_desc *desc) +{ + /* If this domain isn't routed to a guest, return DOMID_XEN. */ + if ( !test_bit(_IRQ_GUEST, &desc->status) ) + return DOMID_XEN; + + /* Otherise, get the guest domain's information. */ + return irq_get_domain(desc)->domain_id; +} + void irq_set_affinity(struct irq_desc *desc, const cpumask_t *cpu_mask) { if ( desc != NULL ) @@ -369,7 +380,7 @@ int setup_irq(unsigned int irq, unsigned int irqflags, struct irqaction *new) /* First time the IRQ is setup */ if ( disabled ) { - gic_route_irq_to_xen(desc, GIC_PRI_IRQ); + platform_route_irq_to_xen(desc, GIC_PRI_IRQ); /* It's fine to use smp_processor_id() because: * For PPI: irq_desc is banked * For SPI: we don't care for now which CPU will receive the @@ -506,7 +517,7 @@ int route_irq_to_guest(struct domain *d, unsigned int virq, if ( retval ) goto out; - retval = gic_route_irq_to_guest(d, virq, desc, GIC_PRI_IRQ); + retval = platform_route_irq_to_guest(d, virq, desc, GIC_PRI_IRQ); spin_unlock_irqrestore(&desc->lock, flags); diff --git a/xen/arch/arm/platform.c b/xen/arch/arm/platform.c index 0af6d57bfe..539ee3b2b9 100644 --- a/xen/arch/arm/platform.c +++ b/xen/arch/arm/platform.c @@ -147,6 +147,36 @@ bool_t platform_device_is_blacklisted(const struct dt_device_node *node) return (dt_match_node(blacklist, node) != NULL); } +int platform_route_irq_to_guest(struct domain *d, unsigned int virq, + struct irq_desc *desc, unsigned int priority) +{ + if ( platform && platform->route_irq_to_guest ) + return platform->route_irq_to_guest(d, virq, desc, priority); + else + return gic_route_irq_to_guest(d, virq, desc, priority); +} + +void platform_route_irq_to_xen(struct irq_desc *desc, unsigned int priority) +{ + if ( platform && platform->route_irq_to_xen ) + platform->route_irq_to_xen(desc, priority); + else + gic_route_irq_to_xen(desc, priority); +} + +bool platform_irq_is_routable(const struct dt_raw_irq * rirq) +{ + /* + * If we have a platform-specific method to determine if an IRQ is routable, + * check that; otherwise fall back to checking to see if an IRQ belongs to + * the GIC. + */ + if ( platform && platform->irq_is_routable ) + return platform->irq_is_routable(rirq); + else + return (rirq->controller == dt_interrupt_controller); +} + /* * Local variables: * mode: C diff --git a/xen/arch/arm/platforms/Makefile b/xen/arch/arm/platforms/Makefile index 49fa683780..5701e62135 100644 --- a/xen/arch/arm/platforms/Makefile +++ b/xen/arch/arm/platforms/Makefile @@ -6,5 +6,9 @@ obj-$(CONFIG_ARM_32) += omap5.o obj-$(CONFIG_ARM_32) += rcar2.o obj-$(CONFIG_ARM_64) += seattle.o obj-$(CONFIG_ARM_32) += sunxi.o +obj-$(CONFIG_ARM_32) += tegra.o +obj-$(CONFIG_ARM_32) += tegra-mlic.o +obj-$(CONFIG_ARM_64) += tegra.o +obj-$(CONFIG_ARM_64) += tegra-mlic.o obj-$(CONFIG_ARM_64) += xgene-storm.o obj-$(CONFIG_ARM_64) += xilinx-zynqmp.o diff --git a/xen/arch/arm/platforms/tegra-mlic.c b/xen/arch/arm/platforms/tegra-mlic.c new file mode 100644 index 0000000000..df26c489d3 --- /dev/null +++ b/xen/arch/arm/platforms/tegra-mlic.c @@ -0,0 +1,260 @@ +/* + * xen/arch/arm/tegra_mlic.c + * + * Mediator for Tegra Legacy Interrupt Controller + * + * This module allow the hardware domain to have access to the sections of + * the legacy interrupt controller that correspond to its devices, + * but disallow access to the sections controlled by other domains + * or by Xen. + * + * Kyle Temkin; Copyright (c) 2016 Assured Information Security, Inc. + * Chris Patterson; Copyright (c) 2016 Assured Information Security, Inc. + * + * 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +static int tegra_mlic_mmio_read(struct vcpu *v, mmio_info_t *info, + register_t *r, void *priv); +static int tegra_mlic_mmio_write(struct vcpu *v, mmio_info_t *info, + register_t r, void *priv); + +static const struct mmio_handler_ops tegra_mlic_mmio_handler = { + .read = tegra_mlic_mmio_read, + .write = tegra_mlic_mmio_write, +}; + +/* + * Parses a LIC MMIO read or write, and extracts the information needed to + * complete the request. + * + * info: Information describing the MMIO read/write being performed + * ictlr_index: The interrupt controller number in the ictlr (e.g. 0-5) + * register_offset: The register offset into the specified interrupt controller + * (e.g. TEGRA_ICTLR_CPU_IER_SET) + * irq_base: The number of the first IRQ represented by the given ictlr. + */ +static void tegra_mlic_parse_mmio_request(mmio_info_t *info, + uint32_t *ictlr_index, uint32_t *register_offset, uint32_t *irq_base) +{ + /* Determine the offset of the access into the ICTLR region. */ + uint32_t offset = info->gpa - TEGRA_ICTLR_BASE; + uint32_t ictlr = offset / TEGRA_ICTLR_SIZE; + uint32_t reg = offset % TEGRA_ICTLR_SIZE; + + if ( ictlr_index ) + *ictlr_index = ictlr; + + if ( register_offset ) + *register_offset = reg; + + if ( irq_base ) + *irq_base = (ictlr * TEGRA_IRQS_PER_ICTLR) + NR_LOCAL_IRQS; + + /* Ensure that we've only been handed a valid offset within our region. */ + BUG_ON(ictlr >= TEGRA_ICTLR_COUNT); + BUG_ON(offset >= (TEGRA_ICTLR_COUNT * TEGRA_ICTLR_SIZE)); + BUG_ON((ictlr * TEGRA_ICTLR_SIZE + reg) != offset); +} + +/* + * Returns true iff the given IRQ is currently routed to the given domain. + */ +static bool irq_owned_by_domain(int irq, struct domain *d) +{ + struct irq_desc *desc = irq_to_desc(irq); + domid_t domid; + unsigned long flags; + + BUG_ON(!desc); + + spin_lock_irqsave(&desc->lock, flags); + domid = irq_get_domain_id(desc); + spin_unlock_irqrestore(&desc->lock, flags); + + return (d->domain_id == domid); +} + +/* + * Mediates an MMIO-read to the Tegra legacy interrupt controller. + * Ensures that each domain only is passed interrupt state for its + * own interupts. + */ +static int tegra_mlic_mmio_read(struct vcpu *v, mmio_info_t *info, + register_t *target_register, void *priv) +{ + register_t raw_value; + unsigned int ictlr_index; + unsigned int register_offset; + unsigned int irq_base; + int i; + + perfc_incr(tegra_mlic_reads); + + tegra_mlic_parse_mmio_request(info, &ictlr_index, ®ister_offset, + &irq_base); + + /* Sanity check the read. */ + if ( register_offset & 0x3 ) + { + printk(XENLOG_G_ERR "d%u: Tegra LIC: Attempt to read unaligned ictlr addr" + "(%" PRIpaddr ")\n", current->domain->domain_id, info->gpa); + domain_crash_synchronous(); + } + + if ( info->dabt.size != DABT_WORD ) + { + printk(XENLOG_G_ERR "d%u: Tegra LIC: Non-word read from ictlr addr" + "%" PRIpaddr "\n", current->domain->domain_id, info->gpa); + domain_crash_synchronous(); + } + + + /* Perform the core ictlr read. */ + raw_value = tegra_lic_readl(ictlr_index, register_offset); + + /* + * We don't want to leak information about interrupts not controlled + * by the active domain. Thus, we'll zero out any ictlr slots for + * IRQs not owned by the given domain. + */ + for ( i = 0; i < TEGRA_IRQS_PER_ICTLR; ++i ) { + int irq = irq_base + i; + + if ( !irq_owned_by_domain(irq, current->domain) ) + raw_value &= ~( 1 << i ); + } + + /* Finally, set the target register to our read value */ + *target_register = raw_value; + return 1; +} + +/* + * Mediates an MMIO-read to the Tegra legacy interrupt controller. + * Ensures that each domain only can only control is own interrupts. + */ +static int tegra_mlic_mmio_write(struct vcpu *v, mmio_info_t *info, + register_t new_value, void *priv) +{ + register_t write_mask = 0; + register_t raw_value; + unsigned int ictlr_index; + unsigned int register_offset; + unsigned int irq_base; + int i; + + perfc_incr(tegra_mlic_writes); + + tegra_mlic_parse_mmio_request(info, &ictlr_index, ®ister_offset, + &irq_base); + + /* Sanity check the read. */ + if ( register_offset & 0x3 ) { + printk(XENLOG_G_ERR "d%u: Tegra LIC: Attempt to write unaligned ictlr addr" + "(%" PRIpaddr ")\n", current->domain->domain_id, info->gpa); + domain_crash_synchronous(); + return 0; + } + + if ( info->dabt.size != DABT_WORD ) { + printk(XENLOG_G_ERR "d%u: Tegra LIC: Non-word write to ictlr addr" + "%" PRIpaddr "\n", current->domain->domain_id, info->gpa); + domain_crash_synchronous(); + return 0; + } + + /* + * We only want to write to bits that correspond to interrupts that the + * current domain controls. Accordingly, we'll create a mask that has a + * single bit set for each writable bit. + */ + for ( i = 0; i < TEGRA_IRQS_PER_ICTLR; ++i ) { + int irq = irq_base + i; + + if ( irq_owned_by_domain(irq, current->domain) ) + write_mask |= ( 1 << i ); + } + + /* + * Read in the original value. We'll use this to ensure that we maintain + * the bit values for any bits not actively controlled by this domain. Note + * that we can perform this read without side effects, so this shouldn't + * change the actual operation being performed. + */ + raw_value = tegra_lic_readl(ictlr_index, register_offset); + + /* Remove bits that the guest is not allowed to write. */ + raw_value &= ~write_mask; + raw_value |= (write_mask & new_value); + + /* Finally perform the write. */ + tegra_lic_writel(ictlr_index, register_offset, raw_value); + return 1; +} + +/* + * Set up the hardware domain for the Tegra, giving it mediated access to the + * platform's legacy interrupt controller. + */ +int domain_tegra_mlic_init(struct domain *d) +{ + int rc; + unsigned long pfn_start, pfn_end; + + ASSERT( is_hardware_domain(d) ); + + pfn_start = paddr_to_pfn(TEGRA_ICTLR_BASE); + pfn_end = DIV_ROUND_UP(TEGRA_ICTLR_BASE + (TEGRA_ICTLR_SIZE * TEGRA_ICTLR_COUNT), PAGE_SIZE); + + /* Force all access to the ictlr to go through our mediator. */ + rc = iomem_deny_access(d, pfn_start, pfn_end); + + if ( rc ) + panic("Failed to deny access to the Tegra LIC iomem"); + + rc = unmap_mmio_regions(d, _gfn(pfn_start), + pfn_end - pfn_start + 1, + _mfn(pfn_start)); + + if ( rc ) + panic("Failed to deny access to the Tegra LIC"); + + register_mmio_handler(d, &tegra_mlic_mmio_handler, + TEGRA_ICTLR_BASE, + TEGRA_ICTLR_SIZE * TEGRA_ICTLR_COUNT, + NULL); + + return 0; +} + +/* + * Local variables: + * mode: C + * c-file-style: "BSD" + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/xen/arch/arm/platforms/tegra.c b/xen/arch/arm/platforms/tegra.c new file mode 100644 index 0000000000..8ca48fad39 --- /dev/null +++ b/xen/arch/arm/platforms/tegra.c @@ -0,0 +1,325 @@ +/* + * NVIDIA Tegra specific settings + * + * Ian Campbell; Copyright (c) 2014 Citrix Systems + * Kyle Temkin; Copyright (c) 2016 Assured Information Security, Inc. + * Chris Patterson; Copyright (c) 2016 Assured Information Security, Inc. + * + * 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* Permanent mapping to the Tegra legacy interrupt controller. */ +static void __iomem *tegra_ictlr_base; + +/* + * List of legacy interrupt controllers that can be used to route + * Tegra interrupts. + */ +static const char * const tegra_interrupt_compat[] __initconst = +{ + "nvidia,tegra124-ictlr", /* Tegra K1 controllers */ + "nvidia,tegra210-ictlr" /* Tegra X1 controllers */ +}; + +/* + * Returns true iff the given IRQ belongs to a supported tegra interrupt + * controller. + */ +static bool tegra_irq_belongs_to_ictlr(const struct dt_raw_irq * rirq) { + int i; + + for ( i = 0; i < ARRAY_SIZE(tegra_interrupt_compat); i++ ) { + if ( dt_device_is_compatible(rirq->controller, tegra_interrupt_compat[i]) ) + return true; + } + + return false; +} + +/* + * Returns true iff the given IRQ is routable -- that is, if it is descended + * from the platform's primary GIC. + */ +static bool tegra_irq_is_routable(const struct dt_raw_irq * rirq) +{ + /* If the IRQ connects directly to our GIC, it's trivially routable. */ + if ( rirq->controller == dt_interrupt_controller ) + return true; + + /* + * If the IRQ belongs to a legacy interrupt controller, then it's + * effectively owned by the GIC, and is routable. + */ + if ( tegra_irq_belongs_to_ictlr(rirq) ) + return true; + + return false; +} + +/* + * Platform-specific reset code for the Tegra devices. + * Should not return. + */ +static void tegra_reset(void) +{ + void __iomem *addr; + u32 val; + + addr = ioremap_nocache(TEGRA_RESET_BASE, TEGRA_RESET_SIZE); + if ( !addr ) + { + printk(XENLOG_ERR "Tegra: Unable to map tegra reset address. Reset failed!\n"); + return; + } + + /* Write into the reset device. */ + val = readl(addr) | TEGRA_RESET_MASK; + writel(val, addr); + + /* Should not get here */ + iounmap(addr); +} + +/* + * Convert irq line to index of servicing legacy interrupt controller. + */ +static unsigned int tegra_lic_irq_to_ictlr_index(unsigned int irq) +{ + return (irq - NR_LOCAL_IRQS) / TEGRA_IRQS_PER_ICTLR; +} + +/* + * Convert irq line to index of irq within servicing interrupt controller. + */ +static unsigned int tegra_lic_irq_to_ictlr_irq_index(unsigned int irq) +{ + return (irq - NR_LOCAL_IRQS) % TEGRA_IRQS_PER_ICTLR; +} + +/* + * Mark interrupt as normal rather than a fast IRQ. + */ +static void tegra_lic_set_interrupt_type_normal(unsigned int irq) +{ + uint32_t previous_iep_class; + unsigned int ictlr_index = tegra_lic_irq_to_ictlr_index(irq); + unsigned int ictlr_irq_index = tegra_lic_irq_to_ictlr_irq_index(irq); + uint32_t mask = BIT(ictlr_irq_index); + + /* Mark the interrupt as a normal interrupt-- not a fast IRQ. */ + previous_iep_class = tegra_lic_readl(ictlr_index, TEGRA_ICTLR_CPU_IEP_CLASS); + tegra_lic_writel(ictlr_index, TEGRA_ICTLR_CPU_IEP_CLASS, previous_iep_class & ~mask); +} + +/* + * Enable/disable interrupt line for specified irq. + */ +static void tegra_lic_set_interrupt_enable(unsigned int irq, bool enabled) +{ + unsigned int ictlr_index = tegra_lic_irq_to_ictlr_index(irq); + unsigned int ictlr_irq_index = tegra_lic_irq_to_ictlr_irq_index(irq); + uint32_t mask = BIT(ictlr_irq_index); + + if ( enabled ) + tegra_lic_writel(ictlr_index, TEGRA_ICTLR_CPU_IER_SET, mask); + else + tegra_lic_writel(ictlr_index, TEGRA_ICTLR_CPU_IER_CLR, mask); +} + +/* + * Routes an IRQ to a guest, applying sane values to the ictlr masks. + * Returns 0 on success, or an error code on failure. + */ +static int tegra_route_irq_to_guest(struct domain *d, unsigned int virq, + struct irq_desc *desc, unsigned int priority) +{ + /* Program the core GIC to deliver the interrupt to the guest. */ + int rc = gic_route_irq_to_guest(d, virq, desc, priority); + + /* If we couldn't route the IRQ via the GIC, bail out. */ + if ( rc ) + { + printk(XENLOG_ERR "Tegra LIC: Couldn't program GIC to route vIRQ %d (%d).\n", + desc->irq, rc); + return rc; + } + + /* + * If this is a local IRQ, it's not masked by the ictlr, so we + * don't need to perform any ictlr manipulation. + */ + if ( desc->irq < NR_LOCAL_IRQS ) + return rc; + + /* + * If this is the hardware domain, it will have real access to the ictlr, + * and will program the ictlr itself, so it should start with the ictlr + * disabled. If we're not the hwdom, the domain won't interact with the + * ictlr, and the interrupt shouldn't be masked. Either way, first + * set the interrupt type to normal (if previously set to fast IRQ). + */ + tegra_lic_set_interrupt_type_normal(desc->irq); + tegra_lic_set_interrupt_enable(desc->irq, !is_hardware_domain(d)); + return rc; +} + + +/* + * Routes an IRQ to Xen. This method both performs the core IRQ routing, and + * sets up any ictlr routing necessary. + */ +static void tegra_route_irq_to_xen(struct irq_desc *desc, unsigned int priority) +{ + unsigned int irq = desc->irq; + + /* Program the core GIC to deliver the interrupt to Xen. */ + gic_route_irq_to_xen(desc, priority); + + /* + * If this is a local IRQ, it's not masked by the ictlr, so we + * don't need to perform any ictlr manipulation. + */ + if ( irq < NR_LOCAL_IRQS ) + return; + + /* + * Enable the interrupt in the ictlr. Xen only uses the GIC to + * perform masking, so we'll enable the interrupt to prevent ictlr + * gating of the interrupt. + */ + tegra_lic_set_interrupt_type_normal(desc->irq); + tegra_lic_set_interrupt_enable(desc->irq, true); +} + +/* + * Use platform specific mapping for hook to initialize mediated legacy + * interrupt controller for hardware domain. + */ +static int tegra_specific_mapping(struct domain *d) +{ + return domain_tegra_mlic_init(d); +} + +/* + * Read register from specified legacy interrupt interrupt controller. + */ +uint32_t tegra_lic_readl(unsigned int ictlr_index, unsigned int register_offset) +{ + ASSERT(tegra_ictlr_base); + ASSERT(ictlr_index < TEGRA_ICTLR_COUNT); + ASSERT(register_offset < TEGRA_ICTLR_SIZE); + return readl(tegra_ictlr_base + ictlr_index * TEGRA_ICTLR_SIZE + + register_offset); +} + +/* + * Write register for specified legacy interrupt interrupt controller. + */ +void tegra_lic_writel(unsigned int ictlr_index, unsigned int register_offset, uint32_t value) +{ + ASSERT(tegra_ictlr_base); + ASSERT(ictlr_index < TEGRA_ICTLR_COUNT); + ASSERT(register_offset < TEGRA_ICTLR_SIZE); + writel(value, tegra_ictlr_base + ictlr_index * TEGRA_ICTLR_SIZE + + register_offset); +} + +/* + * Initialize the Tegra legacy interrupt controller, placing each interrupt + * into a default state. These defaults ensure that stray interrupts don't + * affect Xen. + */ +static int tegra_lic_init(void) +{ + int i; + + /* Map in the tegra ictlr. */ + tegra_ictlr_base = ioremap_nocache(TEGRA_ICTLR_BASE, + TEGRA_ICTLR_SIZE * TEGRA_ICTLR_COUNT); + + if ( !tegra_ictlr_base ) + panic("Failed to map in the Tegra legacy interrupt controller"); + + /* Initialize each of the legacy interrupt controllers. */ + for ( i = 0; i < TEGRA_ICTLR_COUNT; i++ ) { + + /* Clear the interrupt enables for every interrupt. */ + tegra_lic_writel(i, TEGRA_ICTLR_CPU_IER_CLR, ~0); + + /* + * Mark all of our interrupts as normal ARM interrupts (as opposed + * to Fast Interrupts.) + */ + tegra_lic_writel(i, TEGRA_ICTLR_CPU_IEP_CLASS, 0); + } + + return 0; +} + +/** + * Startup code for the Tegra. + */ +static int tegra_init(void) +{ + return tegra_lic_init(); +} + + +static const char * const tegra_dt_compat[] __initconst = +{ + "nvidia,tegra120", /* Tegra K1 */ + "nvidia,tegra210", /* Tegra X1 */ + NULL +}; + +static const struct dt_device_match tegra_blacklist_dev[] __initconst = +{ + /* + * The UARTs share a page which runs the risk of mapping the Xen console + * UART to dom0, so don't map any of them. + */ + DT_MATCH_COMPATIBLE("nvidia,tegra20-uart"), + { /* sentinel */ }, +}; + +PLATFORM_START(tegra, "Tegra") + .blacklist_dev = tegra_blacklist_dev, + .compatible = tegra_dt_compat, + .init = tegra_init, + .reset = tegra_reset, + .irq_is_routable = tegra_irq_is_routable, + .route_irq_to_xen = tegra_route_irq_to_xen, + .route_irq_to_guest = tegra_route_irq_to_guest, + .specific_mapping = tegra_specific_mapping, +PLATFORM_END + +/* + * Local variables: + * mode: C + * c-file-style: "BSD" + * c-basic-offset: 4 + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/xen/common/device_tree.c b/xen/common/device_tree.c index 7b009ead13..3fabbcdcdf 100644 --- a/xen/common/device_tree.c +++ b/xen/common/device_tree.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -1467,11 +1468,8 @@ int dt_irq_translate(const struct dt_raw_irq *raw, ASSERT(dt_irq_xlate != NULL); ASSERT(dt_interrupt_controller != NULL); - /* - * TODO: Retrieve the right irq_xlate. This is only works for the primary - * interrupt controller. - */ - if ( raw->controller != dt_interrupt_controller ) + /* Only proceed with translation if the irq is routable on the platform. */ + if ( !platform_irq_is_routable(raw) ) return -EINVAL; return dt_irq_xlate(raw->specifier, raw->size, diff --git a/xen/drivers/char/ns16550.c b/xen/drivers/char/ns16550.c index e4de3b476c..1b75e89ca9 100644 --- a/xen/drivers/char/ns16550.c +++ b/xen/drivers/char/ns16550.c @@ -62,7 +62,7 @@ static struct ns16550 { struct timer resume_timer; unsigned int timeout_ms; bool_t intr_works; - bool_t dw_usr_bsy; + uint8_t hw_quirks; #ifdef CONFIG_HAS_PCI /* PCI card parameters. */ bool_t pb_bdf_enable; /* if =1, pb-bdf effective, port behind bridge */ @@ -414,6 +414,10 @@ static const struct ns16550_config __initconst uart_config[] = }; #endif +/* Various hardware quirks/features that may be need be enabled per device */ +#define HW_QUIRKS_DW_USR_BSY (1<<0) +#define HW_QUIRKS_USE_RTOIE (1<<1) + static void ns16550_delayed_resume(void *data); static u8 ns_read_reg(struct ns16550 *uart, unsigned int reg) @@ -578,7 +582,7 @@ static void ns16550_setup_preirq(struct ns16550 *uart) /* No interrupts. */ ns_write_reg(uart, UART_IER, 0); - if ( uart->dw_usr_bsy && + if ( (uart->hw_quirks & HW_QUIRKS_DW_USR_BSY) && (ns_read_reg(uart, UART_IIR) & UART_IIR_BSY) == UART_IIR_BSY ) { /* DesignWare 8250 detects if LCR is written while the UART is @@ -651,12 +655,23 @@ static void ns16550_setup_postirq(struct ns16550 *uart) { if ( uart->irq > 0 ) { + u8 ier_value = 0; + /* Master interrupt enable; also keep DTR/RTS asserted. */ ns_write_reg(uart, UART_MCR, UART_MCR_OUT2 | UART_MCR_DTR | UART_MCR_RTS); /* Enable receive interrupts. */ - ns_write_reg(uart, UART_IER, UART_IER_ERDAI); + ier_value = UART_IER_ERDAI; + + /* + * If we're on a platform that needs Rx timeouts enabled, also + * set Rx TimeOut Interrupt Enable (RTOIE). + */ + if ( uart->hw_quirks & HW_QUIRKS_USE_RTOIE ) + ier_value |= UART_IER_RTOIE; + + ns_write_reg(uart, UART_IER, ier_value); } if ( uart->irq >= 0 ) @@ -1271,7 +1286,11 @@ static int __init ns16550_uart_dt_init(struct dt_device_node *dev, return -EINVAL; uart->irq = res; - uart->dw_usr_bsy = dt_device_is_compatible(dev, "snps,dw-apb-uart"); + if ( dt_device_is_compatible(dev, "snps,dw-apb-uart") ) + uart->hw_quirks |= HW_QUIRKS_DW_USR_BSY; + + if ( dt_device_is_compatible(dev, "nvidia,tegra20-uart") ) + uart->hw_quirks |= HW_QUIRKS_USE_RTOIE; uart->vuart.base_addr = uart->io_base; uart->vuart.size = uart->io_size; @@ -1292,6 +1311,7 @@ static const struct dt_device_match ns16550_dt_match[] __initconst = DT_MATCH_COMPATIBLE("ns16550"), DT_MATCH_COMPATIBLE("ns16550a"), DT_MATCH_COMPATIBLE("snps,dw-apb-uart"), + DT_MATCH_COMPATIBLE("nvidia,tegra20-uart"), { /* sentinel */ }, }; diff --git a/xen/include/asm-arm/irq.h b/xen/include/asm-arm/irq.h index 4849f16ea0..d0fd6db92c 100644 --- a/xen/include/asm-arm/irq.h +++ b/xen/include/asm-arm/irq.h @@ -44,6 +44,8 @@ int route_irq_to_guest(struct domain *d, unsigned int virq, unsigned int irq, const char *devname); int release_guest_irq(struct domain *d, unsigned int irq); +domid_t irq_get_domain_id(struct irq_desc *desc); + void arch_move_irqs(struct vcpu *v); #define arch_evtchn_bind_pirq(d, pirq) ((void)((d) + (pirq))) diff --git a/xen/include/asm-arm/platform.h b/xen/include/asm-arm/platform.h index 08010ba09b..119ad2ed81 100644 --- a/xen/include/asm-arm/platform.h +++ b/xen/include/asm-arm/platform.h @@ -26,6 +26,12 @@ struct platform_desc { void (*reset)(void); /* Platform power-off */ void (*poweroff)(void); + /* Platform-specific IRQ routing */ + int (*route_irq_to_guest)(struct domain *d, unsigned int virq, + struct irq_desc *desc, unsigned int priority); + void (*route_irq_to_xen)(struct irq_desc *desc, unsigned int priority); + bool (*irq_is_routable)(const struct dt_raw_irq * rirq); + /* * Platform quirks * Defined has a function because a platform can support multiple @@ -56,6 +62,12 @@ int platform_cpu_up(int cpu); void platform_reset(void); void platform_poweroff(void); bool_t platform_has_quirk(uint32_t quirk); + +int platform_route_irq_to_guest(struct domain *d, unsigned int virq, + struct irq_desc *desc, unsigned int priority); +void platform_route_irq_to_xen(struct irq_desc *desc, unsigned int priority); +bool platform_irq_is_routable(const struct dt_raw_irq *rirq); + bool_t platform_device_is_blacklisted(const struct dt_device_node *node); #define PLATFORM_START(_name, _namestr) \ diff --git a/xen/include/asm-arm/platforms/tegra-mlic.h b/xen/include/asm-arm/platforms/tegra-mlic.h new file mode 100644 index 0000000000..fee10c4ce0 --- /dev/null +++ b/xen/include/asm-arm/platforms/tegra-mlic.h @@ -0,0 +1,34 @@ +/* + * xen/arch/arm/vuart.h + * + * Mediated Tegra Legacy Interrupt Controller + * + * Kyle Temkin; Copyright (c) 2016 Assured Information Security, Inc. + * Chris Patterson; Copyright (c) 2016 Assured Information Security, Inc. + * + * 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. + */ + +#ifndef __ASM_ARM_PLATFORMS_TEGRA_MLIC_H +#define __ASM_ARM_PLATFORMS_TEGRA_MLIC_H + +int domain_tegra_mlic_init(struct domain *d); + +#endif /* __ASM_ARM_PLATFORMS_TEGRA_MLIC_H */ + +/* + * Local variables: + * mode: C + * c-file-style: "BSD" + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/xen/include/asm-arm/platforms/tegra.h b/xen/include/asm-arm/platforms/tegra.h new file mode 100644 index 0000000000..e9cd792a23 --- /dev/null +++ b/xen/include/asm-arm/platforms/tegra.h @@ -0,0 +1,54 @@ +/* + * NVIDIA Tegra platform definitions + * + * Kyle Temkin; Copyright (c) 2016 Assured Information Security, Inc. + * Chris Patterson; Copyright (c) 2016 Assured Information Security, Inc. + * + * 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. + */ + + +#ifndef __ASM_ARM_PLATFORMS_TEGRA_H +#define __ASM_ARM_PLATFORMS_TEGRA_H + +#define TEGRA_ICTLR_BASE 0x60004000 +#define TEGRA_ICTLR_SIZE 0x00000100 +#define TEGRA_ICTLR_COUNT 6 +#define TEGRA_IRQS_PER_ICTLR 32 + +#define TEGRA_ICTLR_CPU_IER 0x20 +#define TEGRA_ICTLR_CPU_IER_SET 0x24 +#define TEGRA_ICTLR_CPU_IER_CLR 0x28 +#define TEGRA_ICTLR_CPU_IEP_CLASS 0x2C + +#define TEGRA_ICTLR_COP_IER 0x30 +#define TEGRA_ICTLR_COP_IER_SET 0x34 +#define TEGRA_ICTLR_COP_IER_CLR 0x38 +#define TEGRA_ICTLR_COP_IEP_CLASS 0x3c + +#define TEGRA_RESET_BASE 0x7000e400 +#define TEGRA_RESET_SIZE 4 +#define TEGRA_RESET_MASK 0x10 + +uint32_t tegra_lic_readl(unsigned int ictlr_index, unsigned int register_offset); +void tegra_lic_writel(unsigned int ictlr_index, unsigned int register_offset, uint32_t value); + +#endif /* __ASM_ARM_PLATFORMS_TEGRA_H */ + +/* + * Local variables: + * mode: C + * c-file-style: "BSD" + * c-basic-offset: 4 + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/xen/include/xen/8250-uart.h b/xen/include/xen/8250-uart.h index c6b62c8cf4..2ad0ee68c3 100644 --- a/xen/include/xen/8250-uart.h +++ b/xen/include/xen/8250-uart.h @@ -41,6 +41,7 @@ #define UART_IER_ETHREI 0x02 /* tx reg. empty */ #define UART_IER_ELSI 0x04 /* rx line status */ #define UART_IER_EMSI 0x08 /* MODEM status */ +#define UART_IER_RTOIE 0x10 /* rx timeout */ /* Interrupt Identificatiegister */ #define UART_IIR_NOINT 0x01 /* no interrupt pending */