mirror of
https://github.com/projectacrn/acrn-hypervisor.git
synced 2025-06-19 20:22:46 +00:00
When the virtio-XXX pci devices fall back to legacy PCI intx, the pci_irq_assert is called to inject the interrupt and then the pci_irq_deassert is used to mark the completion of PCI interrupt. Currently the HV vIOAPIC uses the pin_state for the interrupt injection of legacy PCI intx. In such case it will fail to inject the PCI legacy intx and the guest system fails to be booted when adding the boot option of "pci=nomsi". PCI legacy INTx usually use active low level trigger mode as it is Open-Drain state and allows multitple interrupt signals to share a single line. https://wiki.osdev.org/PCI_Local_Bus_Signals In such case DM needs to set the correct state for the PCI device so that the HV vIOAPIC can help to inject the PCI legacy intx. BTW: When the MSI/MSIX is used for PCI device, it uses another mechanism to inject the interrupt. It is harmless to configure the initial state. Tracked-On: #7124 Signed-off-by: Zhao Yakui <yakui.zhao@intel.com> Acked-by: Wang, Yu1 <yu1.wang@intel.com>
327 lines
8.2 KiB
C
327 lines
8.2 KiB
C
/*-
|
|
* Copyright (c) 2014 Hudson River Trading LLC
|
|
* Written by: John H. Baldwin <jhb@FreeBSD.org>
|
|
* 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 AND CONTRIBUTORS ``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.
|
|
*/
|
|
|
|
|
|
#include <pthread.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "types.h"
|
|
#include "acpi.h"
|
|
#include "vmmapi.h"
|
|
#include "pci_core.h"
|
|
#include "lpc.h"
|
|
|
|
/*
|
|
* Implement an 8 pin PCI interrupt router compatible with the router
|
|
* present on Intel's ICH10 chip.
|
|
*/
|
|
|
|
/* Fields in each PIRQ register. */
|
|
#define PIRQ_DIS 0x80
|
|
#define PIRQ_IRQ 0x0f
|
|
|
|
/* Only IRQs 3-7, 9-12, and 14-15 are permitted. */
|
|
#define PERMITTED_IRQS 0xdef8
|
|
#define IRQ_PERMITTED(irq) (((1U << (irq)) & PERMITTED_IRQS) != 0)
|
|
|
|
/* IRQ count to disable an IRQ. */
|
|
#define IRQ_DISABLED 0xff
|
|
|
|
static struct pirq {
|
|
uint8_t reg;
|
|
int use_count;
|
|
int active_count;
|
|
pthread_mutex_t lock;
|
|
} pirqs[8];
|
|
|
|
static u_char irq_counts[16];
|
|
static int pirq_cold = 1;
|
|
|
|
/*
|
|
* Returns true if this pin is enabled with a valid IRQ. Setting the
|
|
* register to a reserved IRQ causes interrupts to not be asserted as
|
|
* if the pin was disabled.
|
|
*/
|
|
static bool
|
|
pirq_valid_irq(int reg)
|
|
{
|
|
if (reg & PIRQ_DIS)
|
|
return false;
|
|
return IRQ_PERMITTED(reg & PIRQ_IRQ);
|
|
}
|
|
|
|
uint8_t
|
|
pirq_read(int pin)
|
|
{
|
|
if (pin <= 0 || pin > nitems(pirqs))
|
|
return PIRQ_DIS;
|
|
|
|
return pirqs[pin - 1].reg;
|
|
}
|
|
|
|
void
|
|
pirq_write(struct vmctx *ctx, int pin, uint8_t val)
|
|
{
|
|
struct pirq *pirq;
|
|
|
|
if (pin <= 0 || pin > nitems(pirqs))
|
|
return;
|
|
|
|
pirq = &pirqs[pin - 1];
|
|
pthread_mutex_lock(&pirq->lock);
|
|
if (pirq->reg != (val & (PIRQ_DIS | PIRQ_IRQ))) {
|
|
if (pirq->active_count != 0 && pirq_valid_irq(pirq->reg))
|
|
vm_set_gsi_irq(ctx, pirq->reg & PIRQ_IRQ, GSI_SET_LOW);
|
|
pirq->reg = val & (PIRQ_DIS | PIRQ_IRQ);
|
|
if (pirq->active_count != 0 && pirq_valid_irq(pirq->reg))
|
|
vm_set_gsi_irq(ctx, pirq->reg & PIRQ_IRQ, GSI_SET_HIGH);
|
|
}
|
|
pthread_mutex_unlock(&pirq->lock);
|
|
}
|
|
|
|
void
|
|
pci_irq_reserve(int irq) {
|
|
if ((irq >= 0 && irq < nitems(irq_counts)) && pirq_cold
|
|
&& (irq_counts[irq] == 0 || irq_counts[irq] == IRQ_DISABLED))
|
|
irq_counts[irq] = IRQ_DISABLED;
|
|
}
|
|
|
|
void
|
|
pci_irq_use(int irq)
|
|
{
|
|
if ((irq >= 0 && irq < nitems(irq_counts)) && pirq_cold
|
|
&& (irq_counts[irq] != IRQ_DISABLED))
|
|
irq_counts[irq]++;
|
|
}
|
|
|
|
void
|
|
pci_irq_init(struct vmctx *ctx)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < nitems(pirqs); i++) {
|
|
pirqs[i].reg = PIRQ_DIS;
|
|
pirqs[i].use_count = 0;
|
|
pirqs[i].active_count = 0;
|
|
pthread_mutex_init(&pirqs[i].lock, NULL);
|
|
}
|
|
for (i = 0; i < nitems(irq_counts); i++) {
|
|
if (IRQ_PERMITTED(i))
|
|
irq_counts[i] = 0;
|
|
else
|
|
irq_counts[i] = IRQ_DISABLED;
|
|
}
|
|
}
|
|
|
|
void pci_irq_deinit(struct vmctx *ctx)
|
|
{
|
|
pirq_cold = 1;
|
|
}
|
|
|
|
void
|
|
pci_irq_assert(struct pci_vdev *dev)
|
|
{
|
|
vm_set_gsi_irq(dev->vmctx, dev->lintr.ioapic_irq, GSI_SET_LOW);
|
|
}
|
|
|
|
void
|
|
pci_irq_deassert(struct pci_vdev *dev)
|
|
{
|
|
vm_set_gsi_irq(dev->vmctx, dev->lintr.ioapic_irq, GSI_SET_HIGH);
|
|
}
|
|
|
|
int
|
|
pirq_alloc_pin(struct pci_vdev *dev)
|
|
{
|
|
int best_count, best_irq, best_pin, irq, pin;
|
|
|
|
pirq_cold = 0;
|
|
|
|
/* Find the least-used PIRQ pin. */
|
|
best_pin = 0;
|
|
best_count = pirqs[0].use_count;
|
|
for (pin = 1; pin < nitems(pirqs); pin++) {
|
|
if (pirqs[pin].use_count < best_count) {
|
|
best_pin = pin;
|
|
best_count = pirqs[pin].use_count;
|
|
}
|
|
}
|
|
pirqs[best_pin].use_count++;
|
|
|
|
/* Second, route this pin to an IRQ. */
|
|
if (pirqs[best_pin].reg == PIRQ_DIS) {
|
|
best_irq = -1;
|
|
best_count = 0;
|
|
for (irq = 0; irq < nitems(irq_counts); irq++) {
|
|
if (irq_counts[irq] == IRQ_DISABLED)
|
|
continue;
|
|
if (best_irq == -1 || irq_counts[irq] < best_count) {
|
|
best_irq = irq;
|
|
best_count = irq_counts[irq];
|
|
}
|
|
}
|
|
if (best_irq < 0)
|
|
return -1;
|
|
|
|
irq_counts[best_irq]++;
|
|
pirqs[best_pin].reg = best_irq;
|
|
}
|
|
|
|
return (best_pin + 1);
|
|
}
|
|
|
|
int
|
|
pirq_irq(int pin)
|
|
{
|
|
if (pin <= 0 || pin > nitems(pirqs))
|
|
return 0xFF;
|
|
|
|
return (pirqs[pin - 1].reg & PIRQ_IRQ);
|
|
}
|
|
|
|
/* XXX: Generate $PIR table. */
|
|
|
|
static void
|
|
pirq_dsdt(void)
|
|
{
|
|
char *irq_prs, *old;
|
|
int irq, pin;
|
|
|
|
irq_prs = NULL;
|
|
for (irq = 0; irq < nitems(irq_counts); irq++) {
|
|
if (!IRQ_PERMITTED(irq))
|
|
continue;
|
|
if (irq_prs == NULL) {
|
|
if (asprintf(&irq_prs, "%d", irq) < 0) {
|
|
/*error*/
|
|
if (irq_prs != NULL)
|
|
free(irq_prs);
|
|
|
|
return;
|
|
}
|
|
} else {
|
|
old = irq_prs;
|
|
if (asprintf(&irq_prs, "%s,%d", old, irq) < 0) {
|
|
/*error*/
|
|
if (irq_prs != NULL)
|
|
free(irq_prs);
|
|
|
|
free(old);
|
|
return;
|
|
}
|
|
free(old);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* A helper method to validate a link register's value. This
|
|
* duplicates pirq_valid_irq().
|
|
*/
|
|
dsdt_line("");
|
|
dsdt_line("Method (PIRV, 1, NotSerialized)");
|
|
dsdt_line("{");
|
|
dsdt_line(" If (And (Arg0, 0x%02X))", PIRQ_DIS);
|
|
dsdt_line(" {");
|
|
dsdt_line(" Return (0x00)");
|
|
dsdt_line(" }");
|
|
dsdt_line(" And (Arg0, 0x%02X, Local0)", PIRQ_IRQ);
|
|
dsdt_line(" If (LLess (Local0, 0x03))");
|
|
dsdt_line(" {");
|
|
dsdt_line(" Return (0x00)");
|
|
dsdt_line(" }");
|
|
dsdt_line(" If (LEqual (Local0, 0x08))");
|
|
dsdt_line(" {");
|
|
dsdt_line(" Return (0x00)");
|
|
dsdt_line(" }");
|
|
dsdt_line(" If (LEqual (Local0, 0x0D))");
|
|
dsdt_line(" {");
|
|
dsdt_line(" Return (0x00)");
|
|
dsdt_line(" }");
|
|
dsdt_line(" Return (0x01)");
|
|
dsdt_line("}");
|
|
|
|
for (pin = 0; pin < nitems(pirqs); pin++) {
|
|
dsdt_line("");
|
|
dsdt_line("Device (LNK%c)", 'A' + pin);
|
|
dsdt_line("{");
|
|
dsdt_line(" Name (_HID, EisaId (\"PNP0C0F\"))");
|
|
dsdt_line(" Name (_UID, 0x%02X)", pin + 1);
|
|
dsdt_line(" Method (_STA, 0, NotSerialized)");
|
|
dsdt_line(" {");
|
|
dsdt_line(" If (PIRV (PIR%c))", 'A' + pin);
|
|
dsdt_line(" {");
|
|
dsdt_line(" Return (0x0B)");
|
|
dsdt_line(" }");
|
|
dsdt_line(" Else");
|
|
dsdt_line(" {");
|
|
dsdt_line(" Return (0x09)");
|
|
dsdt_line(" }");
|
|
dsdt_line(" }");
|
|
dsdt_line(" Name (_PRS, ResourceTemplate ()");
|
|
dsdt_line(" {");
|
|
dsdt_line(" IRQ (Level, ActiveLow, Shared, )");
|
|
dsdt_line(" {%s}", irq_prs);
|
|
dsdt_line(" })");
|
|
dsdt_line(" Name (CB%02X, ResourceTemplate ()", pin + 1);
|
|
dsdt_line(" {");
|
|
dsdt_line(" IRQ (Level, ActiveLow, Shared, )");
|
|
dsdt_line(" {}");
|
|
dsdt_line(" })");
|
|
dsdt_line(" CreateWordField (CB%02X, 0x01, CIR%c)",
|
|
pin + 1, 'A' + pin);
|
|
dsdt_line(" Method (_CRS, 0, NotSerialized)");
|
|
dsdt_line(" {");
|
|
dsdt_line(" And (PIR%c, 0x%02X, Local0)", 'A' + pin,
|
|
PIRQ_DIS | PIRQ_IRQ);
|
|
dsdt_line(" If (PIRV (Local0))");
|
|
dsdt_line(" {");
|
|
dsdt_line(" ShiftLeft (0x01, Local0, CIR%c)", 'A' + pin);
|
|
dsdt_line(" }");
|
|
dsdt_line(" Else");
|
|
dsdt_line(" {");
|
|
dsdt_line(" Store (0x00, CIR%c)", 'A' + pin);
|
|
dsdt_line(" }");
|
|
dsdt_line(" Return (CB%02X)", pin + 1);
|
|
dsdt_line(" }");
|
|
dsdt_line(" Method (_DIS, 0, NotSerialized)");
|
|
dsdt_line(" {");
|
|
dsdt_line(" Store (0x80, PIR%c)", 'A' + pin);
|
|
dsdt_line(" }");
|
|
dsdt_line(" Method (_SRS, 1, NotSerialized)");
|
|
dsdt_line(" {");
|
|
dsdt_line(" CreateWordField (Arg0, 0x01, SIR%c)", 'A' + pin);
|
|
dsdt_line(" FindSetRightBit (SIR%c, Local0)", 'A' + pin);
|
|
dsdt_line(" Store (Decrement (Local0), PIR%c)", 'A' + pin);
|
|
dsdt_line(" }");
|
|
dsdt_line("}");
|
|
}
|
|
free(irq_prs);
|
|
}
|
|
LPC_DSDT(pirq_dsdt);
|