Message ID | 20170331180525.30038-6-andre.przywara@arm.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
On Fri, 31 Mar 2017, Andre Przywara wrote: > To be able to easily send commands to the ITS, create the respective > wrapper functions, which take care of the ring buffer. > The first two commands we implement provide methods to map a collection > to a redistributor (aka host core) and to flush the command queue (SYNC). > Start using these commands for mapping one collection to each host CPU. > > Signed-off-by: Andre Przywara <andre.przywara@arm.com> Please address Julien's comments. In particular, cmd_lock needs to be initialized here. > --- > xen/arch/arm/gic-v3-its.c | 182 ++++++++++++++++++++++++++++++++++++++ > xen/arch/arm/gic-v3-lpi.c | 22 +++++ > xen/arch/arm/gic-v3.c | 25 +++++- > xen/include/asm-arm/gic_v3_defs.h | 2 + > xen/include/asm-arm/gic_v3_its.h | 38 ++++++++ > 5 files changed, 267 insertions(+), 2 deletions(-) > > diff --git a/xen/arch/arm/gic-v3-its.c b/xen/arch/arm/gic-v3-its.c > index 9a86769..1ac598f 100644 > --- a/xen/arch/arm/gic-v3-its.c > +++ b/xen/arch/arm/gic-v3-its.c > @@ -19,11 +19,14 @@ > */ > > #include <xen/lib.h> > +#include <xen/delay.h> > #include <xen/mm.h> > #include <xen/sizes.h> > +#include <asm/gic.h> > #include <asm/gic_v3_defs.h> > #include <asm/gic_v3_its.h> > #include <asm/io.h> > +#include <asm/page.h> > > #define ITS_CMD_QUEUE_SZ SZ_1M > > @@ -34,6 +37,147 @@ bool gicv3_its_host_has_its(void) > return !list_empty(&host_its_list); > } > > +#define BUFPTR_MASK GENMASK_ULL(19, 5) > +static int its_send_command(struct host_its *hw_its, const void *its_cmd) > +{ > + /* Some small grace period in case the command queue is congested. */ > + s_time_t deadline = NOW() + MILLISECS(1); > + uint64_t readp, writep; > + int ret = -EBUSY; > + > + /* No ITS commands from an interrupt handler (at the moment). */ > + ASSERT(!in_irq()); > + > + spin_lock(&hw_its->cmd_lock); > + > + do { > + readp = readq_relaxed(hw_its->its_base + GITS_CREADR) & BUFPTR_MASK; > + writep = readq_relaxed(hw_its->its_base + GITS_CWRITER) & BUFPTR_MASK; > + > + if ( ((writep + ITS_CMD_SIZE) % ITS_CMD_QUEUE_SZ) != readp ) > + { > + ret = 0; > + break; > + } > + > + /* > + * If the command queue is full, wait for a bit in the hope it drains > + * before giving up. > + */ > + spin_unlock(&hw_its->cmd_lock); > + cpu_relax(); > + udelay(1); > + spin_lock(&hw_its->cmd_lock); > + } while ( NOW() <= deadline ); > + > + if ( ret ) > + { > + spin_unlock(&hw_its->cmd_lock); > + printk(XENLOG_WARNING "ITS: command queue full.\n"); > + return ret; > + } > + > + memcpy(hw_its->cmd_buf + writep, its_cmd, ITS_CMD_SIZE); > + if ( hw_its->flags & HOST_ITS_FLUSH_CMD_QUEUE ) > + clean_and_invalidate_dcache_va_range(hw_its->cmd_buf + writep, > + ITS_CMD_SIZE); > + else > + dsb(ishst); > + > + writep = (writep + ITS_CMD_SIZE) % ITS_CMD_QUEUE_SZ; > + writeq_relaxed(writep & BUFPTR_MASK, hw_its->its_base + GITS_CWRITER); > + > + spin_unlock(&hw_its->cmd_lock); > + > + return 0; > +} > + > +/* Wait for an ITS to finish processing all commands. */ > +static int gicv3_its_wait_commands(struct host_its *hw_its) > +{ > + /* Define an upper limit for our wait time. */ > + s_time_t deadline = NOW() + MILLISECS(100); > + uint64_t readp, writep; > + > + do { > + spin_lock(&hw_its->cmd_lock); > + readp = readq_relaxed(hw_its->its_base + GITS_CREADR) & BUFPTR_MASK; > + writep = readq_relaxed(hw_its->its_base + GITS_CWRITER) & BUFPTR_MASK; > + spin_unlock(&hw_its->cmd_lock); > + > + if ( readp == writep ) > + return 0; > + > + cpu_relax(); > + udelay(1); > + } while ( NOW() <= deadline ); > + > + return -ETIMEDOUT; > +} > + > +static uint64_t encode_rdbase(struct host_its *hw_its, unsigned int cpu, > + uint64_t reg) > +{ > + reg &= ~GENMASK_ULL(51, 16); > + > + reg |= gicv3_get_redist_address(cpu, hw_its->flags & HOST_ITS_USES_PTA); > + > + return reg; > +} > + > +static int its_send_cmd_sync(struct host_its *its, unsigned int cpu) > +{ > + uint64_t cmd[4]; > + > + cmd[0] = GITS_CMD_SYNC; > + cmd[1] = 0x00; > + cmd[2] = encode_rdbase(its, cpu, 0x0); > + cmd[3] = 0x00; > + > + return its_send_command(its, cmd); > +} > + > +static int its_send_cmd_mapc(struct host_its *its, uint32_t collection_id, > + unsigned int cpu) > +{ > + uint64_t cmd[4]; > + > + cmd[0] = GITS_CMD_MAPC; > + cmd[1] = 0x00; > + cmd[2] = encode_rdbase(its, cpu, collection_id); > + cmd[2] |= GITS_VALID_BIT; > + cmd[3] = 0x00; > + > + return its_send_command(its, cmd); > +} > + > +/* Set up the (1:1) collection mapping for the given host CPU. */ > +int gicv3_its_setup_collection(unsigned int cpu) > +{ > + struct host_its *its; > + int ret; > + > + list_for_each_entry(its, &host_its_list, entry) > + { > + if ( !its->cmd_buf ) > + continue; > + > + ret = its_send_cmd_mapc(its, cpu, cpu); > + if ( ret ) > + return ret; > + > + ret = its_send_cmd_sync(its, cpu); > + if ( ret ) > + return ret; > + > + ret = gicv3_its_wait_commands(its); > + if ( ret ) > + return ret; > + } > + > + return 0; > +} > + > #define BASER_ATTR_MASK \ > ((0x3UL << GITS_BASER_SHAREABILITY_SHIFT) | \ > (0x7UL << GITS_BASER_OUTER_CACHEABILITY_SHIFT) | \ > @@ -178,6 +322,38 @@ retry: > return -EINVAL; > } > > +/* > + * Before an ITS gets initialized, it should be in a quiescent state, where > + * all outstanding commands and transactions have finished. > + * So if the ITS is already enabled, turn it off and wait for all outstanding > + * operations to get processed by polling the QUIESCENT bit. > + */ > +static int gicv3_disable_its(struct host_its *hw_its) > +{ > + uint32_t reg; > + /* A similar generous wait limit as we use for the command queue wait. */ > + s_time_t deadline = NOW() + MILLISECS(100); > + > + reg = readl_relaxed(hw_its->its_base + GITS_CTLR); > + if ( !(reg & GITS_CTLR_ENABLE) && (reg & GITS_CTLR_QUIESCENT) ) > + return 0; > + > + writel_relaxed(reg & ~GITS_CTLR_ENABLE, hw_its->its_base + GITS_CTLR); > + > + do { > + reg = readl_relaxed(hw_its->its_base + GITS_CTLR); > + if ( reg & GITS_CTLR_QUIESCENT ) > + return 0; > + > + cpu_relax(); > + udelay(1); > + } while ( NOW() <= deadline ); > + > + dprintk(XENLOG_ERR, "ITS not quiescent.\n"); > + > + return -ETIMEDOUT; > +} > + > /* Allow a user to limit the number of devices. */ > static unsigned int max_its_device_bits = 32; > integer_param("max_its_device_bits", max_its_device_bits); > @@ -191,9 +367,15 @@ static int gicv3_its_init_single_its(struct host_its *hw_its) > if ( !hw_its->its_base ) > return -ENOMEM; > > + ret = gicv3_disable_its(hw_its); > + if ( ret ) > + return ret; > + > reg = readq_relaxed(hw_its->its_base + GITS_TYPER); > hw_its->devid_bits = GITS_TYPER_DEVICE_ID_BITS(reg); > hw_its->devid_bits = min(hw_its->devid_bits, max_its_device_bits); > + if ( reg & GITS_TYPER_PTA ) > + hw_its->flags |= HOST_ITS_USES_PTA; > > for ( i = 0; i < GITS_BASER_NR_REGS; i++ ) > { > diff --git a/xen/arch/arm/gic-v3-lpi.c b/xen/arch/arm/gic-v3-lpi.c > index 77f6009..d85d63d 100644 > --- a/xen/arch/arm/gic-v3-lpi.c > +++ b/xen/arch/arm/gic-v3-lpi.c > @@ -43,6 +43,8 @@ static struct { > } lpi_data; > > struct lpi_redist_data { > + paddr_t redist_addr; > + unsigned int redist_id; > void *pending_table; > }; > > @@ -50,6 +52,26 @@ static DEFINE_PER_CPU(struct lpi_redist_data, lpi_redist); > > #define MAX_PHYS_LPIS (lpi_data.nr_host_lpis - LPI_OFFSET) > > +/* Stores this redistributor's physical address and ID in a per-CPU variable */ > +void gicv3_set_redist_address(paddr_t address, unsigned int redist_id) > +{ > + this_cpu(lpi_redist).redist_addr = address; > + this_cpu(lpi_redist).redist_id = redist_id; > +} > + > +/* > + * Returns a redistributor's ID (either as an address or as an ID). > + * This must be (and is) called only after it has been setup by the above > + * function. > + */ > +uint64_t gicv3_get_redist_address(unsigned int cpu, bool use_pta) > +{ > + if ( use_pta ) > + return per_cpu(lpi_redist, cpu).redist_addr & GENMASK_ULL(51, 16); > + else > + return per_cpu(lpi_redist, cpu).redist_id << 16; > +} > + > static int gicv3_lpi_allocate_pendtable(uint64_t *reg) > { > uint64_t val; > diff --git a/xen/arch/arm/gic-v3.c b/xen/arch/arm/gic-v3.c > index b84bc40..0e21cb2 100644 > --- a/xen/arch/arm/gic-v3.c > +++ b/xen/arch/arm/gic-v3.c > @@ -666,7 +666,21 @@ static int __init gicv3_populate_rdist(void) > > if ( typer & GICR_TYPER_PLPIS ) > { > - int ret; > + paddr_t rdist_addr; > + int procnum, ret; > + > + /* > + * The ITS refers to redistributors either by their physical > + * address or by their ID. Determine those two values and > + * let the ITS code store them in per host CPU variables to > + * later be able to address those redistributors. > + */ > + rdist_addr = gicv3.rdist_regions[i].base; > + rdist_addr += ptr - gicv3.rdist_regions[i].map_base; > + procnum = (typer & GICR_TYPER_PROC_NUM_MASK); > + procnum >>= GICR_TYPER_PROC_NUM_SHIFT; > + > + gicv3_set_redist_address(rdist_addr, procnum); > > ret = gicv3_lpi_init_rdist(ptr); > if ( ret && ret != -ENODEV ) > @@ -705,7 +719,7 @@ static int __init gicv3_populate_rdist(void) > > static int gicv3_cpu_init(void) > { > - int i; > + int i, ret; > uint32_t priority; > > /* Register ourselves with the rest of the world */ > @@ -715,6 +729,13 @@ static int gicv3_cpu_init(void) > if ( gicv3_enable_redist() ) > return -ENODEV; > > + if ( gicv3_its_host_has_its() ) > + { > + ret = gicv3_its_setup_collection(smp_processor_id()); > + if ( ret ) > + return ret; > + } > + > /* Set priority on PPI and SGI interrupts */ > priority = (GIC_PRI_IPI << 24 | GIC_PRI_IPI << 16 | GIC_PRI_IPI << 8 | > GIC_PRI_IPI); > diff --git a/xen/include/asm-arm/gic_v3_defs.h b/xen/include/asm-arm/gic_v3_defs.h > index 7cdebc5..b01b6ed 100644 > --- a/xen/include/asm-arm/gic_v3_defs.h > +++ b/xen/include/asm-arm/gic_v3_defs.h > @@ -103,6 +103,8 @@ > #define GICR_TYPER_PLPIS (1U << 0) > #define GICR_TYPER_VLPIS (1U << 1) > #define GICR_TYPER_LAST (1U << 4) > +#define GICR_TYPER_PROC_NUM_SHIFT 8 > +#define GICR_TYPER_PROC_NUM_MASK (0xffff << GICR_TYPER_PROC_NUM_SHIFT) > > /* For specifying the inner cacheability type only */ > #define GIC_BASER_CACHE_nCnB 0ULL > diff --git a/xen/include/asm-arm/gic_v3_its.h b/xen/include/asm-arm/gic_v3_its.h > index f21162a..4c2ae1c 100644 > --- a/xen/include/asm-arm/gic_v3_its.h > +++ b/xen/include/asm-arm/gic_v3_its.h > @@ -42,10 +42,12 @@ > #define GITS_CTLR_QUIESCENT BIT(31) > #define GITS_CTLR_ENABLE BIT(0) > > +#define GITS_TYPER_PTA BIT_ULL(19) > #define GITS_TYPER_DEVIDS_SHIFT 13 > #define GITS_TYPER_DEVIDS_MASK (0x1fUL << GITS_TYPER_DEVIDS_SHIFT) > #define GITS_TYPER_DEVICE_ID_BITS(r) (((r & GITS_TYPER_DEVIDS_MASK) >> \ > GITS_TYPER_DEVIDS_SHIFT) + 1) > +#define GITS_TYPER_IDBITS_SHIFT 8 > > #define GITS_IIDR_VALUE 0x34c > > @@ -76,9 +78,26 @@ > > #define GITS_CBASER_SIZE_MASK 0xff > > +/* ITS command definitions */ > +#define ITS_CMD_SIZE 32 > + > +#define GITS_CMD_MOVI 0x01 > +#define GITS_CMD_INT 0x03 > +#define GITS_CMD_CLEAR 0x04 > +#define GITS_CMD_SYNC 0x05 > +#define GITS_CMD_MAPD 0x08 > +#define GITS_CMD_MAPC 0x09 > +#define GITS_CMD_MAPTI 0x0a > +#define GITS_CMD_MAPI 0x0b > +#define GITS_CMD_INV 0x0c > +#define GITS_CMD_INVALL 0x0d > +#define GITS_CMD_MOVALL 0x0e > +#define GITS_CMD_DISCARD 0x0f > + > #include <xen/device_tree.h> > > #define HOST_ITS_FLUSH_CMD_QUEUE (1U << 0) > +#define HOST_ITS_USES_PTA (1U << 1) > > /* data structure for each hardware ITS */ > struct host_its { > @@ -88,6 +107,7 @@ struct host_its { > paddr_t size; > void __iomem *its_base; > unsigned int devid_bits; > + spinlock_t cmd_lock; > void *cmd_buf; > unsigned int flags; > }; > @@ -108,6 +128,13 @@ int gicv3_lpi_init_rdist(void __iomem * rdist_base); > int gicv3_lpi_init_host_lpis(unsigned int nr_lpis); > int gicv3_its_init(void); > > +/* Store the physical address and ID for each redistributor as read from DT. */ > +void gicv3_set_redist_address(paddr_t address, unsigned int redist_id); > +uint64_t gicv3_get_redist_address(unsigned int cpu, bool use_pta); > + > +/* Map a collection for this host CPU to each host ITS. */ > +int gicv3_its_setup_collection(unsigned int cpu); > + > #else > > static LIST_HEAD(host_its_list); > @@ -135,6 +162,17 @@ static inline int gicv3_its_init(void) > { > return 0; > } > + > +static inline void gicv3_set_redist_address(paddr_t address, > + unsigned int redist_id) > +{ > +} > + > +static inline int gicv3_its_setup_collection(unsigned int cpu) > +{ > + return 0; > +} > + > #endif /* CONFIG_HAS_ITS */ > > #endif > -- > 2.9.0 >
Hi Andre, I will be nice and repeating my comments. I am hoping they will be fixed next version. On 31/03/17 19:05, Andre Przywara wrote: [...] > #define ITS_CMD_QUEUE_SZ SZ_1M > > @@ -34,6 +37,147 @@ bool gicv3_its_host_has_its(void) > return !list_empty(&host_its_list); > } > > +#define BUFPTR_MASK GENMASK_ULL(19, 5) > +static int its_send_command(struct host_its *hw_its, const void *its_cmd) > +{ > + /* Some small grace period in case the command queue is congested. */ This comment is a nice improvement. But as mention in the previous version, should make it clear that it is a guess. People will likely ask why you choose 1ms whilst Linux is using 1s. > + s_time_t deadline = NOW() + MILLISECS(1); > + uint64_t readp, writep; > + int ret = -EBUSY; > + > + /* No ITS commands from an interrupt handler (at the moment). */ > + ASSERT(!in_irq()); > + > + spin_lock(&hw_its->cmd_lock); > + > + do { > + readp = readq_relaxed(hw_its->its_base + GITS_CREADR) & BUFPTR_MASK; > + writep = readq_relaxed(hw_its->its_base + GITS_CWRITER) & BUFPTR_MASK; > + > + if ( ((writep + ITS_CMD_SIZE) % ITS_CMD_QUEUE_SZ) != readp ) > + { > + ret = 0; > + break; > + } > + > + /* > + * If the command queue is full, wait for a bit in the hope it drains > + * before giving up. > + */ > + spin_unlock(&hw_its->cmd_lock); > + cpu_relax(); > + udelay(1); > + spin_lock(&hw_its->cmd_lock); > + } while ( NOW() <= deadline ); > + > + if ( ret ) > + { > + spin_unlock(&hw_its->cmd_lock); > + printk(XENLOG_WARNING "ITS: command queue full.\n"); This function could be called from a domain. So please ratelimit the message (see printk_ratelimit). You replied this morning, you will fixed in in v4. I am hoping this will be the case. > + return ret; > + } > + > + memcpy(hw_its->cmd_buf + writep, its_cmd, ITS_CMD_SIZE); > + if ( hw_its->flags & HOST_ITS_FLUSH_CMD_QUEUE ) > + clean_and_invalidate_dcache_va_range(hw_its->cmd_buf + writep, > + ITS_CMD_SIZE); > + else > + dsb(ishst); > + > + writep = (writep + ITS_CMD_SIZE) % ITS_CMD_QUEUE_SZ; > + writeq_relaxed(writep & BUFPTR_MASK, hw_its->its_base + GITS_CWRITER); > + > + spin_unlock(&hw_its->cmd_lock); > + > + return 0; > +} > + > +/* Wait for an ITS to finish processing all commands. */ > +static int gicv3_its_wait_commands(struct host_its *hw_its) > +{ > + /* Define an upper limit for our wait time. */ See my remark on the previous timeout comment. > + s_time_t deadline = NOW() + MILLISECS(100); > + uint64_t readp, writep; > + > + do { > + spin_lock(&hw_its->cmd_lock); > + readp = readq_relaxed(hw_its->its_base + GITS_CREADR) & BUFPTR_MASK; > + writep = readq_relaxed(hw_its->its_base + GITS_CWRITER) & BUFPTR_MASK; > + spin_unlock(&hw_its->cmd_lock); > + > + if ( readp == writep ) > + return 0; > + > + cpu_relax(); > + udelay(1); > + } while ( NOW() <= deadline ); > + > + return -ETIMEDOUT; > +} > + [...] > +/* Set up the (1:1) collection mapping for the given host CPU. */ > +int gicv3_its_setup_collection(unsigned int cpu) > +{ > + struct host_its *its; > + int ret; > + > + list_for_each_entry(its, &host_its_list, entry) > + { > + if ( !its->cmd_buf ) This check should be dropped. [...] > +/* > + * Before an ITS gets initialized, it should be in a quiescent state, where > + * all outstanding commands and transactions have finished. > + * So if the ITS is already enabled, turn it off and wait for all outstanding > + * operations to get processed by polling the QUIESCENT bit. > + */ > +static int gicv3_disable_its(struct host_its *hw_its) > +{ > + uint32_t reg; > + /* A similar generous wait limit as we use for the command queue wait. */ See my above comments about the timeout. > + s_time_t deadline = NOW() + MILLISECS(100); > + > + reg = readl_relaxed(hw_its->its_base + GITS_CTLR); > + if ( !(reg & GITS_CTLR_ENABLE) && (reg & GITS_CTLR_QUIESCENT) ) > + return 0; > + > + writel_relaxed(reg & ~GITS_CTLR_ENABLE, hw_its->its_base + GITS_CTLR); > + > + do { > + reg = readl_relaxed(hw_its->its_base + GITS_CTLR); > + if ( reg & GITS_CTLR_QUIESCENT ) > + return 0; > + > + cpu_relax(); > + udelay(1); > + } while ( NOW() <= deadline ); > + > + dprintk(XENLOG_ERR, "ITS not quiescent.\n"); dprintk will disappear on non-debug build. But this looks quite useful. So I would use printk. [...] > +uint64_t gicv3_get_redist_address(unsigned int cpu, bool use_pta) > +{ > + if ( use_pta ) > + return per_cpu(lpi_redist, cpu).redist_addr & GENMASK_ULL(51, 16); > + else > + return per_cpu(lpi_redist, cpu).redist_id << 16; > +} > + > static int gicv3_lpi_allocate_pendtable(uint64_t *reg) > { > uint64_t val; > diff --git a/xen/arch/arm/gic-v3.c b/xen/arch/arm/gic-v3.c > index b84bc40..0e21cb2 100644 > --- a/xen/arch/arm/gic-v3.c > +++ b/xen/arch/arm/gic-v3.c > @@ -666,7 +666,21 @@ static int __init gicv3_populate_rdist(void) > > if ( typer & GICR_TYPER_PLPIS ) > { > - int ret; > + paddr_t rdist_addr; > + int procnum, ret; procnum should be unsigned. > + > + /* > + * The ITS refers to redistributors either by their physical > + * address or by their ID. Determine those two values and > + * let the ITS code store them in per host CPU variables to > + * later be able to address those redistributors. > + */ I said it on v2 this morning and will repeat it for record. This comment is not useful in itself here because redist_address could be used by other code. It would be more useful on top of the call to initialize ITS as it would explain why it is done there and not before. [...] > diff --git a/xen/include/asm-arm/gic_v3_defs.h b/xen/include/asm-arm/gic_v3_defs.h > index 7cdebc5..b01b6ed 100644 > --- a/xen/include/asm-arm/gic_v3_defs.h > +++ b/xen/include/asm-arm/gic_v3_defs.h [...] > /* data structure for each hardware ITS */ > struct host_its { > @@ -88,6 +107,7 @@ struct host_its { > paddr_t size; > void __iomem *its_base; > unsigned int devid_bits; > + spinlock_t cmd_lock; Again, initialization, clean-up of a field should be done in the same that added the field. Otherwise, this is a call to miss a bit of the code and makes more difficult for the reviewer. So please initialize cmd_lock in this patch and patch #6. > void *cmd_buf; > unsigned int flags; > }; > @@ -108,6 +128,13 @@ int gicv3_lpi_init_rdist(void __iomem * rdist_base); > int gicv3_lpi_init_host_lpis(unsigned int nr_lpis); > int gicv3_its_init(void); > > +/* Store the physical address and ID for each redistributor as read from DT. */ > +void gicv3_set_redist_address(paddr_t address, unsigned int redist_id); > +uint64_t gicv3_get_redist_address(unsigned int cpu, bool use_pta); > + > +/* Map a collection for this host CPU to each host ITS. */ > +int gicv3_its_setup_collection(unsigned int cpu); > + > #else > > static LIST_HEAD(host_its_list); > @@ -135,6 +162,17 @@ static inline int gicv3_its_init(void) > { > return 0; > } > + > +static inline void gicv3_set_redist_address(paddr_t address, > + unsigned int redist_id) > +{ > +} > + > +static inline int gicv3_its_setup_collection(unsigned int cpu) > +{ This function should never be called as it is gated by the presence of ITS. I would add a BUG() with a comment to ensure this is the case. > + return 0; > +} > + > #endif /* CONFIG_HAS_ITS */ > > #endif >
diff --git a/xen/arch/arm/gic-v3-its.c b/xen/arch/arm/gic-v3-its.c index 9a86769..1ac598f 100644 --- a/xen/arch/arm/gic-v3-its.c +++ b/xen/arch/arm/gic-v3-its.c @@ -19,11 +19,14 @@ */ #include <xen/lib.h> +#include <xen/delay.h> #include <xen/mm.h> #include <xen/sizes.h> +#include <asm/gic.h> #include <asm/gic_v3_defs.h> #include <asm/gic_v3_its.h> #include <asm/io.h> +#include <asm/page.h> #define ITS_CMD_QUEUE_SZ SZ_1M @@ -34,6 +37,147 @@ bool gicv3_its_host_has_its(void) return !list_empty(&host_its_list); } +#define BUFPTR_MASK GENMASK_ULL(19, 5) +static int its_send_command(struct host_its *hw_its, const void *its_cmd) +{ + /* Some small grace period in case the command queue is congested. */ + s_time_t deadline = NOW() + MILLISECS(1); + uint64_t readp, writep; + int ret = -EBUSY; + + /* No ITS commands from an interrupt handler (at the moment). */ + ASSERT(!in_irq()); + + spin_lock(&hw_its->cmd_lock); + + do { + readp = readq_relaxed(hw_its->its_base + GITS_CREADR) & BUFPTR_MASK; + writep = readq_relaxed(hw_its->its_base + GITS_CWRITER) & BUFPTR_MASK; + + if ( ((writep + ITS_CMD_SIZE) % ITS_CMD_QUEUE_SZ) != readp ) + { + ret = 0; + break; + } + + /* + * If the command queue is full, wait for a bit in the hope it drains + * before giving up. + */ + spin_unlock(&hw_its->cmd_lock); + cpu_relax(); + udelay(1); + spin_lock(&hw_its->cmd_lock); + } while ( NOW() <= deadline ); + + if ( ret ) + { + spin_unlock(&hw_its->cmd_lock); + printk(XENLOG_WARNING "ITS: command queue full.\n"); + return ret; + } + + memcpy(hw_its->cmd_buf + writep, its_cmd, ITS_CMD_SIZE); + if ( hw_its->flags & HOST_ITS_FLUSH_CMD_QUEUE ) + clean_and_invalidate_dcache_va_range(hw_its->cmd_buf + writep, + ITS_CMD_SIZE); + else + dsb(ishst); + + writep = (writep + ITS_CMD_SIZE) % ITS_CMD_QUEUE_SZ; + writeq_relaxed(writep & BUFPTR_MASK, hw_its->its_base + GITS_CWRITER); + + spin_unlock(&hw_its->cmd_lock); + + return 0; +} + +/* Wait for an ITS to finish processing all commands. */ +static int gicv3_its_wait_commands(struct host_its *hw_its) +{ + /* Define an upper limit for our wait time. */ + s_time_t deadline = NOW() + MILLISECS(100); + uint64_t readp, writep; + + do { + spin_lock(&hw_its->cmd_lock); + readp = readq_relaxed(hw_its->its_base + GITS_CREADR) & BUFPTR_MASK; + writep = readq_relaxed(hw_its->its_base + GITS_CWRITER) & BUFPTR_MASK; + spin_unlock(&hw_its->cmd_lock); + + if ( readp == writep ) + return 0; + + cpu_relax(); + udelay(1); + } while ( NOW() <= deadline ); + + return -ETIMEDOUT; +} + +static uint64_t encode_rdbase(struct host_its *hw_its, unsigned int cpu, + uint64_t reg) +{ + reg &= ~GENMASK_ULL(51, 16); + + reg |= gicv3_get_redist_address(cpu, hw_its->flags & HOST_ITS_USES_PTA); + + return reg; +} + +static int its_send_cmd_sync(struct host_its *its, unsigned int cpu) +{ + uint64_t cmd[4]; + + cmd[0] = GITS_CMD_SYNC; + cmd[1] = 0x00; + cmd[2] = encode_rdbase(its, cpu, 0x0); + cmd[3] = 0x00; + + return its_send_command(its, cmd); +} + +static int its_send_cmd_mapc(struct host_its *its, uint32_t collection_id, + unsigned int cpu) +{ + uint64_t cmd[4]; + + cmd[0] = GITS_CMD_MAPC; + cmd[1] = 0x00; + cmd[2] = encode_rdbase(its, cpu, collection_id); + cmd[2] |= GITS_VALID_BIT; + cmd[3] = 0x00; + + return its_send_command(its, cmd); +} + +/* Set up the (1:1) collection mapping for the given host CPU. */ +int gicv3_its_setup_collection(unsigned int cpu) +{ + struct host_its *its; + int ret; + + list_for_each_entry(its, &host_its_list, entry) + { + if ( !its->cmd_buf ) + continue; + + ret = its_send_cmd_mapc(its, cpu, cpu); + if ( ret ) + return ret; + + ret = its_send_cmd_sync(its, cpu); + if ( ret ) + return ret; + + ret = gicv3_its_wait_commands(its); + if ( ret ) + return ret; + } + + return 0; +} + #define BASER_ATTR_MASK \ ((0x3UL << GITS_BASER_SHAREABILITY_SHIFT) | \ (0x7UL << GITS_BASER_OUTER_CACHEABILITY_SHIFT) | \ @@ -178,6 +322,38 @@ retry: return -EINVAL; } +/* + * Before an ITS gets initialized, it should be in a quiescent state, where + * all outstanding commands and transactions have finished. + * So if the ITS is already enabled, turn it off and wait for all outstanding + * operations to get processed by polling the QUIESCENT bit. + */ +static int gicv3_disable_its(struct host_its *hw_its) +{ + uint32_t reg; + /* A similar generous wait limit as we use for the command queue wait. */ + s_time_t deadline = NOW() + MILLISECS(100); + + reg = readl_relaxed(hw_its->its_base + GITS_CTLR); + if ( !(reg & GITS_CTLR_ENABLE) && (reg & GITS_CTLR_QUIESCENT) ) + return 0; + + writel_relaxed(reg & ~GITS_CTLR_ENABLE, hw_its->its_base + GITS_CTLR); + + do { + reg = readl_relaxed(hw_its->its_base + GITS_CTLR); + if ( reg & GITS_CTLR_QUIESCENT ) + return 0; + + cpu_relax(); + udelay(1); + } while ( NOW() <= deadline ); + + dprintk(XENLOG_ERR, "ITS not quiescent.\n"); + + return -ETIMEDOUT; +} + /* Allow a user to limit the number of devices. */ static unsigned int max_its_device_bits = 32; integer_param("max_its_device_bits", max_its_device_bits); @@ -191,9 +367,15 @@ static int gicv3_its_init_single_its(struct host_its *hw_its) if ( !hw_its->its_base ) return -ENOMEM; + ret = gicv3_disable_its(hw_its); + if ( ret ) + return ret; + reg = readq_relaxed(hw_its->its_base + GITS_TYPER); hw_its->devid_bits = GITS_TYPER_DEVICE_ID_BITS(reg); hw_its->devid_bits = min(hw_its->devid_bits, max_its_device_bits); + if ( reg & GITS_TYPER_PTA ) + hw_its->flags |= HOST_ITS_USES_PTA; for ( i = 0; i < GITS_BASER_NR_REGS; i++ ) { diff --git a/xen/arch/arm/gic-v3-lpi.c b/xen/arch/arm/gic-v3-lpi.c index 77f6009..d85d63d 100644 --- a/xen/arch/arm/gic-v3-lpi.c +++ b/xen/arch/arm/gic-v3-lpi.c @@ -43,6 +43,8 @@ static struct { } lpi_data; struct lpi_redist_data { + paddr_t redist_addr; + unsigned int redist_id; void *pending_table; }; @@ -50,6 +52,26 @@ static DEFINE_PER_CPU(struct lpi_redist_data, lpi_redist); #define MAX_PHYS_LPIS (lpi_data.nr_host_lpis - LPI_OFFSET) +/* Stores this redistributor's physical address and ID in a per-CPU variable */ +void gicv3_set_redist_address(paddr_t address, unsigned int redist_id) +{ + this_cpu(lpi_redist).redist_addr = address; + this_cpu(lpi_redist).redist_id = redist_id; +} + +/* + * Returns a redistributor's ID (either as an address or as an ID). + * This must be (and is) called only after it has been setup by the above + * function. + */ +uint64_t gicv3_get_redist_address(unsigned int cpu, bool use_pta) +{ + if ( use_pta ) + return per_cpu(lpi_redist, cpu).redist_addr & GENMASK_ULL(51, 16); + else + return per_cpu(lpi_redist, cpu).redist_id << 16; +} + static int gicv3_lpi_allocate_pendtable(uint64_t *reg) { uint64_t val; diff --git a/xen/arch/arm/gic-v3.c b/xen/arch/arm/gic-v3.c index b84bc40..0e21cb2 100644 --- a/xen/arch/arm/gic-v3.c +++ b/xen/arch/arm/gic-v3.c @@ -666,7 +666,21 @@ static int __init gicv3_populate_rdist(void) if ( typer & GICR_TYPER_PLPIS ) { - int ret; + paddr_t rdist_addr; + int procnum, ret; + + /* + * The ITS refers to redistributors either by their physical + * address or by their ID. Determine those two values and + * let the ITS code store them in per host CPU variables to + * later be able to address those redistributors. + */ + rdist_addr = gicv3.rdist_regions[i].base; + rdist_addr += ptr - gicv3.rdist_regions[i].map_base; + procnum = (typer & GICR_TYPER_PROC_NUM_MASK); + procnum >>= GICR_TYPER_PROC_NUM_SHIFT; + + gicv3_set_redist_address(rdist_addr, procnum); ret = gicv3_lpi_init_rdist(ptr); if ( ret && ret != -ENODEV ) @@ -705,7 +719,7 @@ static int __init gicv3_populate_rdist(void) static int gicv3_cpu_init(void) { - int i; + int i, ret; uint32_t priority; /* Register ourselves with the rest of the world */ @@ -715,6 +729,13 @@ static int gicv3_cpu_init(void) if ( gicv3_enable_redist() ) return -ENODEV; + if ( gicv3_its_host_has_its() ) + { + ret = gicv3_its_setup_collection(smp_processor_id()); + if ( ret ) + return ret; + } + /* Set priority on PPI and SGI interrupts */ priority = (GIC_PRI_IPI << 24 | GIC_PRI_IPI << 16 | GIC_PRI_IPI << 8 | GIC_PRI_IPI); diff --git a/xen/include/asm-arm/gic_v3_defs.h b/xen/include/asm-arm/gic_v3_defs.h index 7cdebc5..b01b6ed 100644 --- a/xen/include/asm-arm/gic_v3_defs.h +++ b/xen/include/asm-arm/gic_v3_defs.h @@ -103,6 +103,8 @@ #define GICR_TYPER_PLPIS (1U << 0) #define GICR_TYPER_VLPIS (1U << 1) #define GICR_TYPER_LAST (1U << 4) +#define GICR_TYPER_PROC_NUM_SHIFT 8 +#define GICR_TYPER_PROC_NUM_MASK (0xffff << GICR_TYPER_PROC_NUM_SHIFT) /* For specifying the inner cacheability type only */ #define GIC_BASER_CACHE_nCnB 0ULL diff --git a/xen/include/asm-arm/gic_v3_its.h b/xen/include/asm-arm/gic_v3_its.h index f21162a..4c2ae1c 100644 --- a/xen/include/asm-arm/gic_v3_its.h +++ b/xen/include/asm-arm/gic_v3_its.h @@ -42,10 +42,12 @@ #define GITS_CTLR_QUIESCENT BIT(31) #define GITS_CTLR_ENABLE BIT(0) +#define GITS_TYPER_PTA BIT_ULL(19) #define GITS_TYPER_DEVIDS_SHIFT 13 #define GITS_TYPER_DEVIDS_MASK (0x1fUL << GITS_TYPER_DEVIDS_SHIFT) #define GITS_TYPER_DEVICE_ID_BITS(r) (((r & GITS_TYPER_DEVIDS_MASK) >> \ GITS_TYPER_DEVIDS_SHIFT) + 1) +#define GITS_TYPER_IDBITS_SHIFT 8 #define GITS_IIDR_VALUE 0x34c @@ -76,9 +78,26 @@ #define GITS_CBASER_SIZE_MASK 0xff +/* ITS command definitions */ +#define ITS_CMD_SIZE 32 + +#define GITS_CMD_MOVI 0x01 +#define GITS_CMD_INT 0x03 +#define GITS_CMD_CLEAR 0x04 +#define GITS_CMD_SYNC 0x05 +#define GITS_CMD_MAPD 0x08 +#define GITS_CMD_MAPC 0x09 +#define GITS_CMD_MAPTI 0x0a +#define GITS_CMD_MAPI 0x0b +#define GITS_CMD_INV 0x0c +#define GITS_CMD_INVALL 0x0d +#define GITS_CMD_MOVALL 0x0e +#define GITS_CMD_DISCARD 0x0f + #include <xen/device_tree.h> #define HOST_ITS_FLUSH_CMD_QUEUE (1U << 0) +#define HOST_ITS_USES_PTA (1U << 1) /* data structure for each hardware ITS */ struct host_its { @@ -88,6 +107,7 @@ struct host_its { paddr_t size; void __iomem *its_base; unsigned int devid_bits; + spinlock_t cmd_lock; void *cmd_buf; unsigned int flags; }; @@ -108,6 +128,13 @@ int gicv3_lpi_init_rdist(void __iomem * rdist_base); int gicv3_lpi_init_host_lpis(unsigned int nr_lpis); int gicv3_its_init(void); +/* Store the physical address and ID for each redistributor as read from DT. */ +void gicv3_set_redist_address(paddr_t address, unsigned int redist_id); +uint64_t gicv3_get_redist_address(unsigned int cpu, bool use_pta); + +/* Map a collection for this host CPU to each host ITS. */ +int gicv3_its_setup_collection(unsigned int cpu); + #else static LIST_HEAD(host_its_list); @@ -135,6 +162,17 @@ static inline int gicv3_its_init(void) { return 0; } + +static inline void gicv3_set_redist_address(paddr_t address, + unsigned int redist_id) +{ +} + +static inline int gicv3_its_setup_collection(unsigned int cpu) +{ + return 0; +} + #endif /* CONFIG_HAS_ITS */ #endif
To be able to easily send commands to the ITS, create the respective wrapper functions, which take care of the ring buffer. The first two commands we implement provide methods to map a collection to a redistributor (aka host core) and to flush the command queue (SYNC). Start using these commands for mapping one collection to each host CPU. Signed-off-by: Andre Przywara <andre.przywara@arm.com> --- xen/arch/arm/gic-v3-its.c | 182 ++++++++++++++++++++++++++++++++++++++ xen/arch/arm/gic-v3-lpi.c | 22 +++++ xen/arch/arm/gic-v3.c | 25 +++++- xen/include/asm-arm/gic_v3_defs.h | 2 + xen/include/asm-arm/gic_v3_its.h | 38 ++++++++ 5 files changed, 267 insertions(+), 2 deletions(-)