/* * Copyright (C) 2018 Intel Corporation. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ #include #define IOAPIC_MAX_PIN 240U #define IOAPIC_INVALID_PIN 0xffU struct gsi_table { uint8_t ioapic_id; uint8_t pin; void *addr; }; static struct gsi_table gsi_table[NR_MAX_GSI]; static uint32_t nr_gsi; static spinlock_t ioapic_lock; static union ioapic_rte saved_rte[CONFIG_NR_IOAPICS][IOAPIC_MAX_PIN]; /* * the irq to ioapic pin mapping should extract from ACPI MADT table * hardcoded here */ uint8_t legacy_irq_to_pin[NR_LEGACY_IRQ] = { 2U, /* IRQ0*/ 1U, /* IRQ1*/ 0U, /* IRQ2 connected to Pin0 (ExtInt source of PIC) if existing */ 3U, /* IRQ3*/ 4U, /* IRQ4*/ 5U, /* IRQ5*/ 6U, /* IRQ6*/ 7U, /* IRQ7*/ 8U, /* IRQ8*/ 9U, /* IRQ9*/ 10U, /* IRQ10*/ 11U, /* IRQ11*/ 12U, /* IRQ12*/ 13U, /* IRQ13*/ 14U, /* IRQ14*/ 15U, /* IRQ15*/ }; uint64_t legacy_irq_trigger_mode[NR_LEGACY_IRQ] = { IOAPIC_RTE_TRGREDG, /* IRQ0*/ IOAPIC_RTE_TRGREDG, /* IRQ1*/ IOAPIC_RTE_TRGREDG, /* IRQ2*/ IOAPIC_RTE_TRGREDG, /* IRQ3*/ IOAPIC_RTE_TRGREDG, /* IRQ4*/ IOAPIC_RTE_TRGREDG, /* IRQ5*/ IOAPIC_RTE_TRGREDG, /* IRQ6*/ IOAPIC_RTE_TRGREDG, /* IRQ7*/ IOAPIC_RTE_TRGREDG, /* IRQ8*/ IOAPIC_RTE_TRGRLVL, /* IRQ9*/ IOAPIC_RTE_TRGREDG, /* IRQ10*/ IOAPIC_RTE_TRGREDG, /* IRQ11*/ IOAPIC_RTE_TRGREDG, /* IRQ12*/ IOAPIC_RTE_TRGREDG, /* IRQ13*/ IOAPIC_RTE_TRGREDG, /* IRQ14*/ IOAPIC_RTE_TRGREDG, /* IRQ15*/ }; uint8_t pic_ioapic_pin_map[NR_LEGACY_PIN] = { 2U, /* pin0*/ 1U, /* pin1*/ 0U, /* pin2*/ 3U, /* pin3*/ 4U, /* pin4*/ 5U, /* pin5*/ 6U, /* pin6*/ 7U, /* pin7*/ 8U, /* pin8*/ 9U, /* pin9*/ 10U, /* pin10*/ 11U, /* pin11*/ 12U, /* pin12*/ 13U, /* pin13*/ 14U, /* pin14*/ 15U, /* pin15*/ }; static void *map_ioapic(uint64_t ioapic_paddr) { /* At some point we may need to translate this paddr to a vaddr. * 1:1 mapping for now. */ return HPA2HVA(ioapic_paddr); } static inline uint32_t ioapic_read_reg32(const void *ioapic_base, const uint32_t offset) { uint32_t v; spinlock_rflags; spinlock_irqsave_obtain(&ioapic_lock); /* Write IOREGSEL */ mmio_write_long(offset, (void *)ioapic_base + IOAPIC_REGSEL); /* Read IOWIN */ v = mmio_read_long((void *)ioapic_base + IOAPIC_WINDOW); spinlock_irqrestore_release(&ioapic_lock); return v; } static inline void ioapic_write_reg32(const void *ioapic_base, const uint32_t offset, const uint32_t value) { spinlock_rflags; spinlock_irqsave_obtain(&ioapic_lock); /* Write IOREGSEL */ mmio_write_long(offset, (void *)ioapic_base + IOAPIC_REGSEL); /* Write IOWIN */ mmio_write_long(value, (void *)ioapic_base + IOAPIC_WINDOW); spinlock_irqrestore_release(&ioapic_lock); } static inline uint64_t get_ioapic_base(uint8_t apic_id) { uint64_t addr = 0xffffffffffffffffUL; /* should extract next ioapic from ACPI MADT table */ if (apic_id == 0U) { addr = DEFAULT_IO_APIC_BASE; } else if (apic_id == 1U) { addr = 0xfec3f000UL; } else if (apic_id == 2U) { addr = 0xfec7f000UL; } else { ASSERT(apic_id <= 2U, "ACPI MADT table missing"); } return addr; } static inline void ioapic_get_rte_entry(void *ioapic_addr, uint8_t pin, union ioapic_rte *rte) { uint32_t rte_addr = ((uint32_t)pin * 2U) + 0x10U; rte->u.lo_32 = ioapic_read_reg32(ioapic_addr, rte_addr); rte->u.hi_32 = ioapic_read_reg32(ioapic_addr, rte_addr + 1U); } static inline void ioapic_set_rte_entry(void *ioapic_addr, uint8_t pin, union ioapic_rte rte) { uint32_t rte_addr = ((uint32_t)pin * 2U) + 0x10U; ioapic_write_reg32(ioapic_addr, rte_addr, rte.u.lo_32); ioapic_write_reg32(ioapic_addr, rte_addr + 1U, rte.u.hi_32); } static inline union ioapic_rte create_rte_for_legacy_irq(uint32_t irq, uint32_t vr) { union ioapic_rte rte; /* Legacy IRQ 0-15 setup, default masked * are actually defined in either MPTable or ACPI MADT table * before we have ACPI table parsing in HV we use common hardcode */ rte.full = IOAPIC_RTE_INTMSET; rte.full |= legacy_irq_trigger_mode[irq]; rte.full |= DEFAULT_DEST_MODE; rte.full |= DEFAULT_DELIVERY_MODE; rte.full |= (IOAPIC_RTE_INTVEC & (uint64_t)vr); /* FIXME: Fixed to active Low? */ rte.full |= IOAPIC_RTE_INTALO; /* Dest field: legacy irq fixed to CPU0 */ rte.full |= (1UL << IOAPIC_RTE_DEST_SHIFT); return rte; } static inline union ioapic_rte create_rte_for_gsi_irq(uint32_t irq, uint32_t vr) { union ioapic_rte rte; if (irq < NR_LEGACY_IRQ) { return create_rte_for_legacy_irq(irq, vr); } /* irq default masked, level trig */ rte.full = IOAPIC_RTE_INTMSET; rte.full |= IOAPIC_RTE_TRGRLVL; rte.full |= DEFAULT_DEST_MODE; rte.full |= DEFAULT_DELIVERY_MODE; rte.full |= (IOAPIC_RTE_INTVEC & (uint64_t)vr); /* FIXME: Fixed to active Low? */ rte.full |= IOAPIC_RTE_INTALO; /* Dest field */ rte.full |= ((uint64_t)ALL_CPUS_MASK << IOAPIC_RTE_DEST_SHIFT); return rte; } static void ioapic_set_routing(uint32_t gsi, uint32_t vr) { void *addr; union ioapic_rte rte; addr = gsi_table[gsi].addr; rte = create_rte_for_gsi_irq(gsi, vr); ioapic_set_rte_entry(addr, gsi_table[gsi].pin, rte); if ((rte.full & IOAPIC_RTE_TRGRMOD) != 0UL) { update_irq_handler(gsi, handle_level_interrupt_common); } else { update_irq_handler(gsi, common_handler_edge); } dev_dbg(ACRN_DBG_IRQ, "GSI: irq:%d pin:%hhu rte:%lx", gsi, gsi_table[gsi].pin, rte.full); } /** * @pre rte != NULL */ void ioapic_get_rte(uint32_t irq, union ioapic_rte *rte) { void *addr; if (!irq_is_gsi(irq)) { return; } addr = gsi_table[irq].addr; ioapic_get_rte_entry(addr, gsi_table[irq].pin, rte); } void ioapic_set_rte(uint32_t irq, union ioapic_rte rte) { void *addr; if (!irq_is_gsi(irq)) { return; } addr = gsi_table[irq].addr; ioapic_set_rte_entry(addr, gsi_table[irq].pin, rte); dev_dbg(ACRN_DBG_IRQ, "GSI: irq:%d pin:%hhu rte:%lx", irq, gsi_table[irq].pin, rte.full); } uint32_t irq_gsi_num(void) { return nr_gsi; } bool irq_is_gsi(uint32_t irq) { return irq < nr_gsi; } uint8_t irq_to_pin(uint32_t irq) { if (irq_is_gsi(irq)) { return gsi_table[irq].pin; } else { return IOAPIC_INVALID_PIN; } } uint32_t pin_to_irq(uint8_t pin) { uint32_t i; for (i = 0U; i < nr_gsi; i++) { if (gsi_table[i].pin == pin) { return i; } } return IRQ_INVALID; } void irq_gsi_mask_unmask(uint32_t irq, bool mask) { void *addr = gsi_table[irq].addr; uint8_t pin = gsi_table[irq].pin; union ioapic_rte rte; if (!irq_is_gsi(irq)) { return; } ioapic_get_rte_entry(addr, pin, &rte); if (mask) { rte.full |= IOAPIC_RTE_INTMSET; } else { rte.full &= ~IOAPIC_RTE_INTMASK; } ioapic_set_rte_entry(addr, pin, rte); dev_dbg(ACRN_DBG_PTIRQ, "update: irq:%d pin:%hhu rte:%lx", irq, pin, rte.full); } static uint8_t ioapic_nr_pins(void *ioapic_base) { uint32_t version; uint8_t nr_pins; version = ioapic_read_reg32(ioapic_base, IOAPIC_VER); dev_dbg(ACRN_DBG_IRQ, "IOAPIC version: %x", version); /* The 23:16 bits in the version register is the highest entry in the * I/O redirection table, which is 1 smaller than the number of * interrupt input pins. */ nr_pins = (uint8_t) (((version & IOAPIC_MAX_RTE_MASK) >> MAX_RTE_SHIFT) + 1U); ASSERT(nr_pins > NR_LEGACY_IRQ, "Legacy IRQ num > total GSI"); ASSERT(nr_pins <= IOAPIC_MAX_PIN, "IOAPIC pins exceeding 240"); return nr_pins; } void setup_ioapic_irq(void) { uint8_t ioapic_id; uint32_t gsi = 0U; uint32_t vr; spinlock_init(&ioapic_lock); for (ioapic_id = 0U; ioapic_id < CONFIG_NR_IOAPICS; ioapic_id++) { void *addr; uint8_t pin, nr_pins; addr = map_ioapic(get_ioapic_base(ioapic_id)); nr_pins = ioapic_nr_pins(addr); for (pin = 0U; pin < nr_pins; pin++) { gsi_table[gsi].ioapic_id = ioapic_id; gsi_table[gsi].addr = addr; if (gsi < NR_LEGACY_IRQ) { gsi_table[gsi].pin = legacy_irq_to_pin[gsi] & 0xffU; } else { gsi_table[gsi].pin = pin; } /* pinned irq before use it */ if (irq_mark_used(gsi) >= NR_IRQS) { pr_err("failed to alloc IRQ[%d]", gsi); gsi++; continue; } /* assign vector for this GSI * for legacy irq, reserved vector and never free */ if (gsi < NR_LEGACY_IRQ) { vr = irq_desc_alloc_vector(gsi, false); if (vr > NR_MAX_VECTOR) { pr_err("failed to alloc VR"); gsi++; continue; } } else { vr = 0U; /* not to allocate VR right now */ } ioapic_set_routing(gsi, vr); gsi++; } } /* system max gsi numbers */ nr_gsi = gsi; ASSERT(nr_gsi <= NR_MAX_GSI, "GSI table overflow"); } static void dump_ioapic(void) { uint32_t irq; for (irq = 0U; irq < nr_gsi; irq++) { void *addr = gsi_table[irq].addr; uint8_t pin = gsi_table[irq].pin; union ioapic_rte rte; ioapic_get_rte_entry(addr, pin, &rte); dev_dbg(ACRN_DBG_IRQ, "DUMP: irq:%d pin:%hhu rte:%lx", irq, pin, rte.full); } } void suspend_ioapic(void) { uint8_t ioapic_id, ioapic_pin; for (ioapic_id = 0U; ioapic_id < CONFIG_NR_IOAPICS; ioapic_id++) { void *addr; uint8_t nr_pins; addr = map_ioapic(get_ioapic_base(ioapic_id)); nr_pins = ioapic_nr_pins(addr); for (ioapic_pin = 0U; ioapic_pin < nr_pins; ioapic_pin++) { ioapic_get_rte_entry(addr, ioapic_pin, &saved_rte[ioapic_id][ioapic_pin]); } } } void resume_ioapic(void) { uint8_t ioapic_id, ioapic_pin; for (ioapic_id = 0U; ioapic_id < CONFIG_NR_IOAPICS; ioapic_id++) { void *addr; uint8_t nr_pins; addr = map_ioapic(get_ioapic_base(ioapic_id)); nr_pins = ioapic_nr_pins(addr); for (ioapic_pin = 0U; ioapic_pin < nr_pins; ioapic_pin++) { ioapic_set_rte_entry(addr, ioapic_pin, saved_rte[ioapic_id][ioapic_pin]); } } } #ifdef HV_DEBUG static void get_rte_info(union ioapic_rte rte, bool *mask, bool *irr, bool *phys, uint32_t *delmode, bool *level, uint32_t *vector, uint32_t *dest) { *mask = ((rte.full & IOAPIC_RTE_INTMASK) == IOAPIC_RTE_INTMSET); *irr = ((rte.full & IOAPIC_RTE_REM_IRR) == IOAPIC_RTE_REM_IRR); *phys = ((rte.full & IOAPIC_RTE_DESTMOD) == IOAPIC_RTE_DESTPHY); *delmode = (uint32_t)(rte.full & IOAPIC_RTE_DELMOD); *level = ((rte.full & IOAPIC_RTE_TRGRLVL) != 0UL); *vector = (uint32_t)(rte.full & IOAPIC_RTE_INTVEC); *dest = (uint32_t)(rte.full >> APIC_ID_SHIFT); } int get_ioapic_info(char *str, int str_max_len) { uint32_t irq; int len, size = str_max_len; len = snprintf(str, size, "\r\nIRQ\tPIN\tRTE.HI32\tRTE.LO32\tVEC\tDST\tDM\tTM\tDELM\tIRR\tMASK"); size -= len; str += len; for (irq = 0U; irq < nr_gsi; irq++) { void *addr = gsi_table[irq].addr; uint8_t pin = gsi_table[irq].pin; union ioapic_rte rte; bool irr, phys, level, mask; uint32_t delmode, vector, dest; ioapic_get_rte_entry(addr, pin, &rte); get_rte_info(rte, &mask, &irr, &phys, &delmode, &level, &vector, &dest); len = snprintf(str, size, "\r\n%03d\t%03hhu\t0x%08X\t0x%08X\t", irq, pin, rte.u.hi_32, rte.u.lo_32); size -= len; str += len; len = snprintf(str, size, "0x%02X\t0x%02X\t%s\t%s\t%u\t%d\t%d", vector, dest, phys ? "phys" : "logic", level ? "level" : "edge", delmode >> 8, irr, mask); size -= len; str += len; if (size < 2) { pr_err("\r\nsmall buffer for ioapic dump"); return -1; } } snprintf(str, size, "\r\n"); return 0; } #endif /* HV_DEBUG */