/*- * Copyright (c) 2014 Tycho Nightingale * Copyright (c) 2017 Intel Corporation * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #define pr_prefix "vpic: " #include #include #include #include #include #include #define DBG_LEVEL_PIC 6U static void vpic_set_pinstate(struct acrn_vpic *vpic, uint32_t pin, uint8_t level); static inline struct acrn_vm *vpic2vm(const struct acrn_vpic *vpic) { return container_of(container_of(vpic, struct vm_arch, vpic), struct acrn_vm, arch_vm); } struct acrn_vpic *vm_pic(const struct acrn_vm *vm) { return (struct acrn_vpic *)&(vm->arch_vm.vpic); } static inline bool primary_pic(const struct acrn_vpic *vpic, const struct i8259_reg_state *i8259) { bool ret; if (i8259 == &vpic->i8259[0]) { ret = true; } else { ret = false; } return ret; } static inline uint32_t vpic_get_highest_isrpin(const struct i8259_reg_state *i8259) { uint32_t bit, pin, i; uint32_t found_pin = INVALID_INTERRUPT_PIN; pin = (i8259->lowprio + 1U) & 0x7U; for (i = 0U; i < NR_VPIC_PINS_PER_CHIP; i++) { bit = (1U << pin); if ((i8259->service & bit) != 0U) { /* * An IS bit that is masked by an IMR bit will not be * cleared by a non-specific EOI in Special Mask Mode. */ if ((i8259->smm != 0U) && ((i8259->mask & bit) != 0U)) { pin = (pin + 1U) & 0x7U; continue; } else { found_pin = pin; break; } } pin = (pin + 1U) & 0x7U; } return found_pin; } static inline uint32_t vpic_get_highest_irrpin(const struct i8259_reg_state *i8259) { uint8_t serviced; uint32_t bit, pin, tmp; uint32_t found_pin = INVALID_INTERRUPT_PIN; /* * In 'Special Fully-Nested Mode' when an interrupt request from * a secondary PIC is in service, the secondary PIC is not locked out from the * primary PIC's priority logic. */ serviced = i8259->service; if (i8259->sfn) { serviced &= ~(uint8_t)(1U << 2U); } /* * In 'Special Mask Mode', when a mask bit is set in OCW1 it inhibits * further interrupts at that level and enables interrupts from all * other levels that are not masked. In other words the ISR has no * bearing on the levels that can generate interrupts. */ if (i8259->smm != 0U) { serviced = 0U; } pin = (i8259->lowprio + 1U) & 0x7U; for (tmp = 0U; tmp < NR_VPIC_PINS_PER_CHIP; tmp++) { bit = (1U << pin); /* * If there is already an interrupt in service at the same * or higher priority then bail. */ if ((serviced & bit) != 0U) { break; } /* * If an interrupt is asserted and not masked then return * the corresponding 'pin' to the caller. */ if (((i8259->request & bit) != 0U) && ((i8259->mask & bit) == 0U)) { found_pin = pin; break; } pin = (pin + 1U) & 0x7U; } return found_pin; } static void vpic_notify_intr(struct acrn_vpic *vpic) { struct i8259_reg_state *i8259; uint32_t pin; /* * First check the secondary vPIC. */ i8259 = &vpic->i8259[1]; pin = vpic_get_highest_irrpin(i8259); if (!i8259->intr_raised && (pin < NR_VPIC_PINS_PER_CHIP)) { dev_dbg(DBG_LEVEL_PIC, "Secondary vPIC notify pin = %hhu (imr 0x%x irr 0x%x isr 0x%x)\n", pin, i8259->mask, i8259->request, i8259->service); /* * Cascade the request from the secondary to the primary vPIC. */ i8259->intr_raised = true; vpic_set_pinstate(vpic, 2U, 1U); vpic_set_pinstate(vpic, 2U, 0U); } else { dev_dbg(DBG_LEVEL_PIC, "Secondary vPIC no eligible interrupt (imr 0x%x irr 0x%x isr 0x%x)", i8259->mask, i8259->request, i8259->service); } /* * Then check the primary vPIC. */ i8259 = &vpic->i8259[0]; pin = vpic_get_highest_irrpin(i8259); if (!i8259->intr_raised && (pin < NR_VPIC_PINS_PER_CHIP)) { struct acrn_vm *vm = vpic2vm(vpic); dev_dbg(DBG_LEVEL_PIC, "Primary PIC notify pin = %hhu (imr 0x%x irr 0x%x isr 0x%x)\n", pin, i8259->mask, i8259->request, i8259->service); /* * From Section 3.6.2, "Interrupt Modes", in the * MPtable Specification, Version 1.4 * * PIC interrupts are routed to both the Local APIC * and the I/O APIC to support operation in 1 of 3 * modes. * * 1. Legacy PIC Mode: the PIC effectively bypasses * all APIC components. In this mode the local APIC is * disabled and LINT0 is reconfigured as INTR to * deliver the PIC interrupt directly to the CPU. * * 2. Virtual Wire Mode: the APIC is treated as a * virtual wire which delivers interrupts from the PIC * to the CPU. In this mode LINT0 is programmed as * ExtINT to indicate that the PIC is the source of * the interrupt. * * 3. Virtual Wire Mode via I/O APIC: PIC interrupts are * fielded by the I/O APIC and delivered to the appropriate * CPU. In this mode the I/O APIC input 0 is programmed * as ExtINT to indicate that the PIC is the source of the * interrupt. */ i8259->intr_raised = true; if (vm->wire_mode == VPIC_WIRE_INTR) { struct acrn_vcpu *bsp = vcpu_from_vid(vm, BSP_CPU_ID); vcpu_inject_extint(bsp); } else { /* * The input parameters here guarantee the return value of vlapic_set_local_intr is 0, means * success. */ (void)vlapic_set_local_intr(vm, BROADCAST_CPU_ID, APIC_LVT_LINT0); /* notify vioapic pin0 if existing * For vPIC + vIOAPIC mode, primary vPIC irq connected * to vioapic pin0 (irq2) * From MPSpec session 5.1 */ vioapic_set_irqline_lock(vm, 0U, GSI_RAISING_PULSE); } } else { dev_dbg(DBG_LEVEL_PIC, "Primary vPIC has no eligible interrupt (imr 0x%x irr 0x%x isr 0x%x)", i8259->mask, i8259->request, i8259->service); } } static int32_t vpic_icw1(const struct acrn_vpic *vpic, struct i8259_reg_state *i8259, uint8_t val) { int32_t ret; dev_dbg(DBG_LEVEL_PIC, "vm 0x%x: i8259 icw1 0x%x\n", vpic2vm(vpic), val); i8259->ready = false; i8259->icw_num = 1U; i8259->request = 0U; i8259->mask = 0U; i8259->lowprio = 7U; i8259->rd_cmd_reg = 0U; i8259->poll = false; i8259->smm = 0U; if ((val & ICW1_SNGL) != 0U) { dev_dbg(DBG_LEVEL_PIC, "vpic cascade mode required\n"); ret = -1; } else if ((val & ICW1_IC4) == 0U) { dev_dbg(DBG_LEVEL_PIC, "vpic icw4 required\n"); ret = -1; } else { i8259->icw_num++; ret = 0; } return ret; } static int32_t vpic_icw2(const struct acrn_vpic *vpic, struct i8259_reg_state *i8259, uint8_t val) { dev_dbg(DBG_LEVEL_PIC, "vm 0x%x: i8259 icw2 0x%x\n", vpic2vm(vpic), val); i8259->irq_base = val & 0xf8U; i8259->icw_num++; return 0; } static int32_t vpic_icw3(const struct acrn_vpic *vpic, struct i8259_reg_state *i8259, uint8_t val) { dev_dbg(DBG_LEVEL_PIC, "vm 0x%x: i8259 icw3 0x%x\n", vpic2vm(vpic), val); i8259->icw_num++; return 0; } static int32_t vpic_icw4(const struct acrn_vpic *vpic, struct i8259_reg_state *i8259, uint8_t val) { int32_t ret; dev_dbg(DBG_LEVEL_PIC, "vm 0x%x: i8259 icw4 0x%x\n", vpic2vm(vpic), val); if ((val & ICW4_8086) == 0U) { dev_dbg(DBG_LEVEL_PIC, "vpic microprocessor mode required\n"); ret = -1; } else { if ((val & ICW4_AEOI) != 0U) { i8259->aeoi = true; } if ((val & ICW4_SFNM) != 0U) { if (primary_pic(vpic, i8259)) { i8259->sfn = true; } else { dev_dbg(DBG_LEVEL_PIC, "Ignoring special fully nested mode on secondary pic: %#x", val); } } i8259->icw_num = 0U; i8259->ready = true; ret = 0; } return ret; } static uint32_t vpin_to_vgsi(const struct acrn_vm *vm, uint32_t vpin) { uint32_t vgsi = vpin; /* * Remap depending on the type of VM */ if (is_sos_vm(vm)) { /* * For SOS VM vPIC pin to GSI is same as the one * that is used for platform */ vgsi = get_pic_pin_from_ioapic_pin(vpin); } else if (is_postlaunched_vm(vm)) { /* * Devicemodel provides Interrupt Source Override Structure * via ACPI to Post-Launched VM. * * 1) Interrupt source connected to vPIC pin 0 is connected to vIOAPIC pin 2 * 2) Devicemodel, as of today, does not request to hold ptirq entry with vPIC as * interrupt controller, for a Post-Launched VM. */ if (vpin == 0U) { vgsi = 2U; } } else { /* * For Pre-launched VMs, Interrupt Source Override Structure * and IO-APIC Structure are not provided in the VM's ACPI info. * No remapping needed. */ } return vgsi; } static uint32_t vgsi_to_vpin(const struct acrn_vm *vm, uint32_t vgsi) { uint32_t vpin = vgsi; /* * Remap depending on the type of VM */ if (is_sos_vm(vm)) { /* * For SOS VM vPIC pin to GSI is same as the one * that is used for platform */ vpin = get_pic_pin_from_ioapic_pin(vgsi); } else if (is_postlaunched_vm(vm)) { /* * Devicemodel provides Interrupt Source Override Structure * via ACPI to Post-Launched VM. * * 1) Interrupt source connected to vPIC pin 0 is connected to vIOAPIC pin 2 * 2) Devicemodel, as of today, does not request to hold ptirq entry with vPIC as * interrupt controller, for a Post-Launched VM. */ if (vgsi == 2U) { vpin = 0U; } } else { /* * For Pre-launched VMs, Interrupt Source Override Structure * and IO-APIC Structure are not provided in the VM's ACPI info. * No remapping needed. */ } return vpin; } static int32_t vpic_ocw1(const struct acrn_vpic *vpic, struct i8259_reg_state *i8259, uint8_t val) { uint32_t pin, i, bit; uint8_t old = i8259->mask; struct acrn_vm *vm = vpic2vm(vpic); dev_dbg(DBG_LEVEL_PIC, "vm 0x%x: i8259 ocw1 0x%x\n", vm, val); i8259->mask = val & 0xffU; pin = (i8259->lowprio + 1U) & 0x7U; /* query and setup if pin/irq is for passthrough device */ for (i = 0U; i < NR_VPIC_PINS_PER_CHIP; i++) { bit = (1U << pin); /* remap for active: interrupt mask -> unmask * remap for deactive: when vIOAPIC take it over */ if (((i8259->mask & bit) == 0U) && ((old & bit) != 0U)) { uint32_t virt_pin; uint32_t vgsi; /* Primary i8259 pin2 connect with secondary i8259, * not device, so not need pt remap */ if ((pin == 2U) && primary_pic(vpic, i8259)) { pin = (pin + 1U) & 0x7U; continue; } virt_pin = (primary_pic(vpic, i8259)) ? pin : (pin + 8U); vgsi = vpin_to_vgsi(vm, virt_pin); (void)ptirq_intx_pin_remap(vm, vgsi, INTX_CTLR_PIC); } pin = (pin + 1U) & 0x7U; } return 0; } static int32_t vpic_ocw2(const struct acrn_vpic *vpic, struct i8259_reg_state *i8259, uint8_t val) { struct acrn_vm *vm = vpic2vm(vpic); dev_dbg(DBG_LEVEL_PIC, "vm 0x%x: i8259 ocw2 0x%x\n", vm, val); i8259->rotate = ((val & OCW2_R) != 0U); if ((val & OCW2_EOI) != 0U) { uint32_t isr_bit; uint32_t vgsi; if ((val & OCW2_SL) != 0U) { /* specific EOI */ isr_bit = val & 0x7U; } else { /* non-specific EOI */ isr_bit = vpic_get_highest_isrpin(i8259); } if (isr_bit < NR_VPIC_PINS_PER_CHIP) { i8259->service &= ~(uint8_t)(1U << isr_bit); if (i8259->rotate) { i8259->lowprio = isr_bit; } } /* if level ack PTDEV */ if ((i8259->elc & (1U << (isr_bit & 0x7U))) != 0U) { vgsi = vpin_to_vgsi(vm, (primary_pic(vpic, i8259) ? isr_bit : isr_bit + 8U)); ptirq_intx_ack(vm, vgsi, INTX_CTLR_PIC); } } else if (((val & OCW2_SL) != 0U) && i8259->rotate) { /* specific priority */ i8259->lowprio = val & 0x7U; } else { /* TODO: Any action required in this case? */ } return 0; } static int32_t vpic_ocw3(const struct acrn_vpic *vpic, struct i8259_reg_state *i8259, uint8_t val) { dev_dbg(DBG_LEVEL_PIC, "vm 0x%x: i8259 ocw3 0x%x\n", vpic2vm(vpic), val); if ((val & OCW3_ESMM) != 0U) { i8259->smm = ((val & OCW3_SMM) != 0U) ? 1U : 0U; dev_dbg(DBG_LEVEL_PIC, "%s i8259 special mask mode %s\n", primary_pic(vpic, i8259) ? "primary vPIC" : "secondary vPIC", (i8259->smm != 0U) ? "enabled" : "disabled"); } if ((val & OCW3_RR) != 0U) { /* read register command */ i8259->rd_cmd_reg = val & OCW3_RIS; /* Polling mode */ i8259->poll = ((val & OCW3_P) != 0U); } return 0; } /** * @pre pin < NR_VPIC_PINS_TOTAL */ static void vpic_set_pinstate(struct acrn_vpic *vpic, uint32_t pin, uint8_t level) { struct i8259_reg_state *i8259; uint8_t old_lvl; bool lvl_trigger; if (pin < NR_VPIC_PINS_TOTAL) { i8259 = &vpic->i8259[pin >> 3U]; old_lvl = i8259->pin_state[pin & 0x7U]; if (level != 0U) { i8259->pin_state[pin & 0x7U] = 1U; } else { i8259->pin_state[pin & 0x7U] = 0U; } lvl_trigger = ((vpic->i8259[pin >> 3U].elc & (1U << (pin & 0x7U))) != 0U); if (((old_lvl == 0U) && (level == 1U)) || ((level == 1U) && lvl_trigger)) { /* raising edge or level */ dev_dbg(DBG_LEVEL_PIC, "pic pin%hhu: asserted\n", pin); i8259->request |= (uint8_t)(1U << (pin & 0x7U)); } else if ((old_lvl == 1U) && (level == 0U)) { /* falling edge */ dev_dbg(DBG_LEVEL_PIC, "pic pin%hhu: deasserted\n", pin); if (lvl_trigger) { i8259->request &= ~(uint8_t)(1U << (pin & 0x7U)); } } else { dev_dbg(DBG_LEVEL_PIC, "pic pin%hhu: %s, ignored\n", pin, (level != 0U) ? "asserted" : "deasserted"); } } } /** * @brief Set vPIC IRQ line status. * * @param[in] vpic Pointer to virtual pic structure * @param[in] irqline Target IRQ number * @param[in] operation action options:GSI_SET_HIGH/GSI_SET_LOW/ * GSI_RAISING_PULSE/GSI_FALLING_PULSE * * @return None */ void vpic_set_irqline(struct acrn_vpic *vpic, uint32_t vgsi, uint32_t operation) { struct i8259_reg_state *i8259; uint32_t pin; uint64_t rflags; if (vgsi < NR_VPIC_PINS_TOTAL) { i8259 = &vpic->i8259[vgsi >> 3U]; if (i8259->ready) { pin = vgsi_to_vpin(vpic2vm(vpic), vgsi); spinlock_irqsave_obtain(&(vpic->lock), &rflags); switch (operation) { case GSI_SET_HIGH: vpic_set_pinstate(vpic, pin, 1U); break; case GSI_SET_LOW: vpic_set_pinstate(vpic, pin, 0U); break; case GSI_RAISING_PULSE: vpic_set_pinstate(vpic, pin, 1U); vpic_set_pinstate(vpic, pin, 0U); break; case GSI_FALLING_PULSE: vpic_set_pinstate(vpic, pin, 0U); vpic_set_pinstate(vpic, pin, 1U); break; default: /* * The function caller could guarantee the pre condition. */ break; } vpic_notify_intr(vpic); spinlock_irqrestore_release(&(vpic->lock), rflags); } } } uint32_t vpic_pincount(void) { return NR_VPIC_PINS_TOTAL; } /** * @pre vm->vpic != NULL * @pre irqline < NR_VPIC_PINS_TOTAL * @pre this function should be called after vpic_init() */ void vpic_get_irqline_trigger_mode(const struct acrn_vpic *vpic, uint32_t vgsi, enum vpic_trigger *trigger) { uint32_t irqline = vgsi_to_vpin(vpic2vm(vpic), vgsi); if ((vpic->i8259[irqline >> 3U].elc & (1U << (irqline & 0x7U))) != 0U) { *trigger = LEVEL_TRIGGER; } else { *trigger = EDGE_TRIGGER; } } /** * @brief Get pending virtual interrupts for vPIC. * * @param[in] vpic Pointer to target VM's vpic table * @param[inout] vecptr Pointer to vector buffer and will be filled * with eligible vector if any. * * @pre this function should be called after vpic_init() * @return None */ void vpic_pending_intr(struct acrn_vpic *vpic, uint32_t *vecptr) { struct i8259_reg_state *i8259; uint32_t pin; uint64_t rflags; i8259 = &vpic->i8259[0]; spinlock_irqsave_obtain(&(vpic->lock), &rflags); pin = vpic_get_highest_irrpin(i8259); if (pin == 2U) { i8259 = &vpic->i8259[1]; pin = vpic_get_highest_irrpin(i8259); } /* * If there are no pins active at this moment then return the spurious * interrupt vector instead. */ if (pin >= NR_VPIC_PINS_PER_CHIP) { *vecptr = VECTOR_INVALID; } else { *vecptr = i8259->irq_base + pin; dev_dbg(DBG_LEVEL_PIC, "Got pending vector 0x%x\n", *vecptr); } spinlock_irqrestore_release(&(vpic->lock), rflags); } static void vpic_pin_accepted(struct i8259_reg_state *i8259, uint32_t pin) { i8259->intr_raised = false; if ((i8259->elc & (1U << pin)) == 0U) { /*only used edge trigger mode*/ i8259->request &= ~(uint8_t)(1U << pin); } if (i8259->aeoi) { if (i8259->rotate) { i8259->lowprio = pin; } } else { i8259->service |= (uint8_t)(1U << pin); } } /** * @brief Accept virtual interrupt for vPIC. * * @param[in] vm Pointer to target VM * @param[in] vector Target virtual interrupt vector * * @return None * * @pre vm != NULL * @pre this function should be called after vpic_init() */ void vpic_intr_accepted(struct acrn_vpic *vpic, uint32_t vector) { uint32_t pin; uint64_t rflags; spinlock_irqsave_obtain(&(vpic->lock), &rflags); pin = (vector & 0x7U); if ((vector & ~0x7U) == vpic->i8259[1].irq_base) { vpic_pin_accepted(&vpic->i8259[1], pin); /* * If this vector originated from the secondary vPIC, * accept the cascaded interrupt too. */ vpic_pin_accepted(&vpic->i8259[0], 2U); } else { vpic_pin_accepted(&vpic->i8259[0], pin); } vpic_notify_intr(vpic); spinlock_irqrestore_release(&(vpic->lock), rflags); } static int32_t vpic_read(struct acrn_vpic *vpic, struct i8259_reg_state *i8259, uint16_t port, uint32_t *eax) { uint32_t pin; uint64_t rflags; spinlock_irqsave_obtain(&(vpic->lock), &rflags); if (i8259->poll) { i8259->poll = false; pin = vpic_get_highest_irrpin(i8259); if (pin < NR_VPIC_PINS_PER_CHIP) { vpic_pin_accepted(i8259, pin); *eax = 0x80U | pin; } else { *eax = 0U; } } else { if ((port & ICU_IMR_OFFSET) != 0U) { /* read interrupt mask register */ *eax = i8259->mask; } else { if (i8259->rd_cmd_reg == OCW3_RIS) { /* read interrupt service register */ *eax = i8259->service; } else { /* read interrupt request register */ *eax = i8259->request; } } } spinlock_irqrestore_release(&(vpic->lock), rflags); return 0; } static int32_t vpic_write(struct acrn_vpic *vpic, struct i8259_reg_state *i8259, uint16_t port, uint32_t *eax) { int32_t error; uint8_t val; uint64_t rflags; error = 0; val = (uint8_t)*eax; spinlock_irqsave_obtain(&(vpic->lock), &rflags); if ((port & ICU_IMR_OFFSET) != 0U) { switch (i8259->icw_num) { case 2U: error = vpic_icw2(vpic, i8259, val); break; case 3U: error = vpic_icw3(vpic, i8259, val); break; case 4U: error = vpic_icw4(vpic, i8259, val); break; default: error = vpic_ocw1(vpic, i8259, val); break; } } else { if ((val & (1U << 4U)) != 0U) { error = vpic_icw1(vpic, i8259, val); } if (i8259->ready) { if ((val & (1U << 3U)) != 0U) { error = vpic_ocw3(vpic, i8259, val); } else { error = vpic_ocw2(vpic, i8259, val); } } } if (i8259->ready) { vpic_notify_intr(vpic); } spinlock_irqrestore_release(&(vpic->lock), rflags); return error; } static int32_t vpic_primary_handler(struct acrn_vpic *vpic, bool in, uint16_t port, size_t bytes, uint32_t *eax) { struct i8259_reg_state *i8259; int32_t ret; i8259 = &vpic->i8259[0]; if (bytes != 1U) { ret = -1; } else if (in) { ret = vpic_read(vpic, i8259, port, eax); } else { ret = vpic_write(vpic, i8259, port, eax); } return ret; } /** * @pre vcpu != NULL * @pre vcpu->vm != NULL */ static bool vpic_primary_io_read(struct acrn_vcpu *vcpu, uint16_t addr, size_t width) { struct pio_request *pio_req = &vcpu->req.reqs.pio; if (vpic_primary_handler(vm_pic(vcpu->vm), true, addr, width, &pio_req->value) < 0) { pr_err("Primary vPIC read port 0x%x width=%d failed\n", addr, width); } return true; } /** * @pre vcpu != NULL * @pre vcpu->vm != NULL */ static bool vpic_primary_io_write(struct acrn_vcpu *vcpu, uint16_t addr, size_t width, uint32_t v) { uint32_t val = v; if (vpic_primary_handler(vm_pic(vcpu->vm), false, addr, width, &val) < 0) { pr_err("%s: write port 0x%x width=%d value 0x%x failed\n", __func__, addr, width, val); } return true; } static int32_t vpic_secondary_handler(struct acrn_vpic *vpic, bool in, uint16_t port, size_t bytes, uint32_t *eax) { struct i8259_reg_state *i8259; int32_t ret; i8259 = &vpic->i8259[1]; if (bytes != 1U) { ret = -1; } else if (in) { ret = vpic_read(vpic, i8259, port, eax); } else { ret = vpic_write(vpic, i8259, port, eax); } return ret; } /** * @pre vcpu != NULL * @pre vcpu->vm != NULL */ static bool vpic_secondary_io_read(struct acrn_vcpu *vcpu, uint16_t addr, size_t width) { struct pio_request *pio_req = &vcpu->req.reqs.pio; if (vpic_secondary_handler(vm_pic(vcpu->vm), true, addr, width, &pio_req->value) < 0) { pr_err("Secondary vPIC read port 0x%x width=%d failed\n", addr, width); } return true; } /** * @pre vcpu != NULL * @pre vcpu->vm != NULL */ static bool vpic_secondary_io_write(struct acrn_vcpu *vcpu, uint16_t addr, size_t width, uint32_t v) { uint32_t val = v; if (vpic_secondary_handler(vm_pic(vcpu->vm), false, addr, width, &val) < 0) { pr_err("%s: write port 0x%x width=%d value 0x%x failed\n", __func__, addr, width, val); } return true; } static int32_t vpic_elc_handler(struct acrn_vpic *vpic, bool in, uint16_t port, size_t bytes, uint32_t *eax) { bool is_primary_vpic; int32_t ret; is_primary_vpic = (port == IO_ELCR1); if (bytes == 1U) { uint64_t rflags; spinlock_irqsave_obtain(&(vpic->lock), &rflags); if (in) { if (is_primary_vpic) { *eax = vpic->i8259[0].elc; } else { *eax = vpic->i8259[1].elc; } } else { /* * For the primary vPIC the cascade channel (IRQ2), the * heart beat timer (IRQ0), and the keyboard * controller (IRQ1) cannot be programmed for level * mode. * * For the secondary vPIC the real time clock (IRQ8) and * the floating point error interrupt (IRQ13) cannot * be programmed for level mode. */ if (is_primary_vpic) { vpic->i8259[0].elc = (uint8_t)(*eax & 0xf8U); } else { vpic->i8259[1].elc = (uint8_t)(*eax & 0xdeU); } } spinlock_irqrestore_release(&(vpic->lock), rflags); ret = 0; } else { ret = -1; } return ret; } /** * @pre vcpu != NULL * @pre vcpu->vm != NULL */ static bool vpic_elc_io_read(struct acrn_vcpu *vcpu, uint16_t addr, size_t width) { struct pio_request *pio_req = &vcpu->req.reqs.pio; if (vpic_elc_handler(vm_pic(vcpu->vm), true, addr, width, &pio_req->value) < 0) { pr_err("pic elc read port 0x%x width=%d failed", addr, width); } return true; } /** * @pre vcpu != NULL * @pre vcpu->vm != NULL */ static bool vpic_elc_io_write(struct acrn_vcpu *vcpu, uint16_t addr, size_t width, uint32_t v) { uint32_t val = v; if (vpic_elc_handler(vm_pic(vcpu->vm), false, addr, width, &val) < 0) { pr_err("%s: write port 0x%x width=%d value 0x%x failed\n", __func__, addr, width, val); } return true; } static void vpic_register_io_handler(struct acrn_vm *vm) { struct vm_io_range primary_vPIC_range = { .base = 0x20U, .len = 2U }; struct vm_io_range secondary_vPIC_range = { .base = 0xa0U, .len = 2U }; struct vm_io_range elcr_range = { .base = 0x4d0U, .len = 2U }; register_pio_emulation_handler(vm, PIC_PRIMARY_PIO_IDX, &primary_vPIC_range, vpic_primary_io_read, vpic_primary_io_write); register_pio_emulation_handler(vm, PIC_SECONDARY_PIO_IDX, &secondary_vPIC_range, vpic_secondary_io_read, vpic_secondary_io_write); register_pio_emulation_handler(vm, PIC_ELC_PIO_IDX, &elcr_range, vpic_elc_io_read, vpic_elc_io_write); } void vpic_init(struct acrn_vm *vm) { struct acrn_vpic *vpic = vm_pic(vm); vpic_register_io_handler(vm); vpic->i8259[0].mask = 0xffU; vpic->i8259[1].mask = 0xffU; spinlock_init(&(vpic->lock)); }