Edit

IABSD.fr/src/sys/dev/acpi/acpiprt.c

Branch :

  • Show log

    Commit

  • Author : kettenis
    Date : 2020-04-11 11:01:18
    Hash : 248de57f
    Message : Use acpi_getsta(); avoids printing "no _STA method" messages and assumes the device is present instead. ok deraadt@

  • sys/dev/acpi/acpiprt.c
  • /* $OpenBSD: acpiprt.c,v 1.49 2020/04/11 11:01:18 kettenis Exp $ */
    /*
     * Copyright (c) 2006 Mark Kettenis <kettenis@openbsd.org>
     *
     * Permission to use, copy, modify, and distribute this software for any
     * purpose with or without fee is hereby granted, provided that the above
     * copyright notice and this permission notice appear in all copies.
     *
     * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     */
    
    #include <sys/param.h>
    #include <sys/signalvar.h>
    #include <sys/systm.h>
    #include <sys/device.h>
    #include <sys/malloc.h>
    
    #include <machine/bus.h>
    
    #include <dev/acpi/acpireg.h>
    #include <dev/acpi/acpivar.h>
    #include <dev/acpi/acpidev.h>
    #include <dev/acpi/amltypes.h>
    #include <dev/acpi/dsdt.h>
    
    #include <dev/pci/pcivar.h>
    #include <dev/pci/ppbreg.h>
    
    #include <machine/i82093reg.h>
    #include <machine/i82093var.h>
    
    #include <machine/mpbiosvar.h>
    
    #include "ioapic.h"
    
    struct acpiprt_irq {
    	int _int;
    	int _shr;
    	int _ll;
    	int _he;
    };
    
    struct acpiprt_map {
    	int bus, dev;
    	int pin;
    	int irq;
    	struct acpiprt_softc *sc;
    	struct aml_node *node;
    	SIMPLEQ_ENTRY(acpiprt_map) list;
    };
    
    SIMPLEQ_HEAD(, acpiprt_map) acpiprt_map_list =
        SIMPLEQ_HEAD_INITIALIZER(acpiprt_map_list);
    
    int	acpiprt_match(struct device *, void *, void *);
    void	acpiprt_attach(struct device *, struct device *, void *);
    int	acpiprt_getirq(int, union acpi_resource *, void *);
    int	acpiprt_chooseirq(int, union acpi_resource *, void *);
    
    struct acpiprt_softc {
    	struct device		sc_dev;
    
    	struct acpi_softc	*sc_acpi;
    	struct aml_node		*sc_devnode;
    
    	int			sc_bus;
    };
    
    struct cfattach acpiprt_ca = {
    	sizeof(struct acpiprt_softc), acpiprt_match, acpiprt_attach
    };
    
    struct cfdriver acpiprt_cd = {
    	NULL, "acpiprt", DV_DULL
    };
    
    void	acpiprt_prt_add(struct acpiprt_softc *, struct aml_value *);
    int	acpiprt_getpcibus(struct acpiprt_softc *, struct aml_node *);
    void	acpiprt_route_interrupt(int bus, int dev, int pin);
    
    int
    acpiprt_match(struct device *parent, void *match, void *aux)
    {
    	struct acpi_attach_args	*aa = aux;
    	struct cfdata  *cf = match;
    
    	/* sanity */
    	if (aa->aaa_name == NULL ||
    	    strcmp(aa->aaa_name, cf->cf_driver->cd_name) != 0 ||
    	    aa->aaa_table != NULL)
    		return (0);
    
    	return (1);
    }
    
    void
    acpiprt_attach(struct device *parent, struct device *self, void *aux)
    {
    	struct acpiprt_softc *sc = (struct acpiprt_softc *)self;
    	struct acpi_attach_args *aa = aux;
    	struct aml_value res;
    	int i;
    
    	sc->sc_acpi = (struct acpi_softc *)parent;
    	sc->sc_devnode = aa->aaa_node;
    	sc->sc_bus = acpiprt_getpcibus(sc, sc->sc_devnode);
    	printf(": bus %d (%s)", sc->sc_bus, sc->sc_devnode->parent->name);
    
    	if (sc->sc_bus == -1) {
    		printf("\n");
    		return;
    	}
    
    	if (aml_evalnode(sc->sc_acpi, sc->sc_devnode, 0, NULL, &res)) {
    		printf(": no PCI interrupt routing table\n");
    		return;
    	}
    
    	if (res.type != AML_OBJTYPE_PACKAGE) {
    		printf(": _PRT is not a package\n");
    		aml_freevalue(&res);
    		return;
    	}
    
    	printf("\n");
    
    	for (i = 0; i < res.length; i++)
    		acpiprt_prt_add(sc, res.v_package[i]);
    
    	aml_freevalue(&res);
    }
    
    int
    acpiprt_getirq(int crsidx, union acpi_resource *crs, void *arg)
    {
    	struct acpiprt_irq *irq = arg;
    	int typ, len;
    
    	irq->_shr = 0;
    	irq->_ll = 0;
    	irq->_he = 1;
    
    	typ = AML_CRSTYPE(crs);
    	len = AML_CRSLEN(crs);
    	switch (typ) {
    	case SR_IRQ:
    		irq->_int= ffs(letoh16(crs->sr_irq.irq_mask)) - 1;
    		if (len > 2) {
    			irq->_shr = (crs->sr_irq.irq_flags & SR_IRQ_SHR);
    			irq->_ll = (crs->sr_irq.irq_flags & SR_IRQ_POLARITY);
    			irq->_he = (crs->sr_irq.irq_flags & SR_IRQ_MODE);
    		}
    		break;
    	case LR_EXTIRQ:
    		irq->_int = letoh32(crs->lr_extirq.irq[0]);
    		irq->_shr = (crs->lr_extirq.flags & LR_EXTIRQ_SHR);
    		irq->_ll = (crs->lr_extirq.flags & LR_EXTIRQ_POLARITY);
    		irq->_he = (crs->lr_extirq.flags & LR_EXTIRQ_MODE);
    		break;
    	default:
    		printf("unknown interrupt: %x\n", typ);
    	}
    	return (0);
    }
    
    int
    acpiprt_pri[16] = {
    	0,			/* 8254 Counter 0 */
    	1,			/* Keyboard */
    	0,			/* 8259 Slave */
    	2,			/* Serial Port A */
    	2,			/* Serial Port B */
    	5,			/* Parallel Port / Generic */
    	2,			/* Floppy Disk */
    	4,			/* Parallel Port / Generic */
    	1,			/* RTC */
    	6,			/* Generic */
    	7,			/* Generic */
    	7,			/* Generic */
    	1,			/* Mouse */
    	0,			/* FPU */
    	2,			/* Primary IDE */
    	3			/* Secondary IDE */
    };
    
    int
    acpiprt_chooseirq(int crsidx, union acpi_resource *crs, void *arg)
    {
    	struct acpiprt_irq *irq = arg;
    	int typ, len, i, pri = -1;
    
    	irq->_shr = 0;
    	irq->_ll = 0;
    	irq->_he = 1;
    
    	typ = AML_CRSTYPE(crs);
    	len = AML_CRSLEN(crs);
    	switch (typ) {
    	case SR_IRQ:
    		for (i = 0; i < sizeof(crs->sr_irq.irq_mask) * 8; i++) {
    			if (crs->sr_irq.irq_mask & (1 << i) &&
    			    acpiprt_pri[i] > pri) {
    				irq->_int = i;
    				pri = acpiprt_pri[irq->_int];
    			}
    		}
    		if (len > 2) {
    			irq->_shr = (crs->sr_irq.irq_flags & SR_IRQ_SHR);
    			irq->_ll = (crs->sr_irq.irq_flags & SR_IRQ_POLARITY);
    			irq->_he = (crs->sr_irq.irq_flags & SR_IRQ_MODE);
    		}
    		break;
    	case LR_EXTIRQ:
    		/* First try non-8259 interrupts. */
    		for (i = 0; i < crs->lr_extirq.irq_count; i++) {
    			if (crs->lr_extirq.irq[i] > 15) {
    				irq->_int = crs->lr_extirq.irq[i];
    				return (0);
    			}
    		}
    
    		for (i = 0; i < crs->lr_extirq.irq_count; i++) {
    			if (acpiprt_pri[crs->lr_extirq.irq[i]] > pri) {
    				irq->_int = crs->lr_extirq.irq[i];
    				pri = acpiprt_pri[irq->_int];
    			}
    		}
    		irq->_shr = (crs->lr_extirq.flags & LR_EXTIRQ_SHR);
    		irq->_ll = (crs->lr_extirq.flags & LR_EXTIRQ_POLARITY);
    		irq->_he = (crs->lr_extirq.flags & LR_EXTIRQ_MODE);
    		break;
    	default:
    		printf("unknown interrupt: %x\n", typ);
    	}
    	return (0);
    }
    
    void
    acpiprt_prt_add(struct acpiprt_softc *sc, struct aml_value *v)
    {
    	struct aml_node	*node;
    	struct aml_value res, *pp;
    	struct acpiprt_irq irq;
    	u_int64_t addr;
    	int pin;
    	int64_t sta;
    #if NIOAPIC > 0
    	struct mp_intr_map *map;
    	struct ioapic_softc *apic;
    #endif
    	pci_chipset_tag_t pc = NULL;
    	pcitag_t tag;
    	pcireg_t reg;
    	int bus, dev, func, nfuncs;
    	struct acpiprt_map *p;
    
    	if (v->type != AML_OBJTYPE_PACKAGE || v->length != 4) {
    		printf("invalid mapping object\n");
    		return;
    	}
    
    	addr = aml_val2int(v->v_package[0]);
    	pin = aml_val2int(v->v_package[1]);
    	if (pin > 3) {
    		return;
    	}
    
    	pp = v->v_package[2];
    	if (pp->type == AML_OBJTYPE_STRING) {
    		node = aml_searchrel(sc->sc_devnode, pp->v_string);
    		if (node == NULL) {
    			printf("Invalid device\n");
    			return;
    		}
    		pp = node->value;
    	}
    	if (pp->type == AML_OBJTYPE_NAMEREF) {
    		node = aml_searchrel(sc->sc_devnode, pp->v_nameref);
    		if (node == NULL) {
    			printf("Invalid device\n");
    			return;
    		}
    		pp = node->value;
    	}
    	if (pp->type == AML_OBJTYPE_OBJREF) {
    		pp = pp->v_objref.ref;
    	}
    	if (pp->type == AML_OBJTYPE_DEVICE) {
    		node = pp->node;
    
    		sta = acpi_getsta(sc->sc_acpi, node);
    		if ((sta & STA_PRESENT) == 0)
    			return;
    
    		if (aml_evalname(sc->sc_acpi, node, "_CRS", 0, NULL, &res)) {
    			printf("no _CRS method\n");
    			return;
    		}
    
    		if (res.type != AML_OBJTYPE_BUFFER || res.length < 5) {
    			printf("invalid _CRS object\n");
    			aml_freevalue(&res);
    			return;
    		}
    		aml_parse_resource(&res, acpiprt_getirq, &irq);
    		aml_freevalue(&res);
    
    		/* Pick a new IRQ if necessary. */
    		if ((irq._int == 0 || irq._int == 2 || irq._int == 13) &&
    		    !aml_evalname(sc->sc_acpi, node, "_PRS", 0, NULL, &res)){
    			aml_parse_resource(&res, acpiprt_chooseirq, &irq);
    			aml_freevalue(&res);
    		}
    
    		if ((p = malloc(sizeof(*p), M_ACPI, M_NOWAIT)) == NULL)
    			return;
    		p->bus = sc->sc_bus;
    		p->dev = ACPI_PCI_DEV(addr << 16);
    		p->pin = pin;
    		p->irq = irq._int;
    		p->sc = sc;
    		p->node = node;
    		SIMPLEQ_INSERT_TAIL(&acpiprt_map_list, p, list);
    	} else {
    		irq._int = aml_val2int(v->v_package[3]);
    		irq._shr = 1;
    		irq._ll = 1;
    		irq._he = 0;
    	}
    
    #ifdef ACPI_DEBUG
    	printf("%s: %s addr 0x%llx pin %d irq %d\n",
    	    DEVNAME(sc), aml_nodename(pp->node), addr, pin, irq._int);
    #endif
    
    #if NIOAPIC > 0
    	if (nioapics > 0) {
    		apic = ioapic_find_bybase(irq._int);
    		if (apic == NULL) {
    			printf("%s: no apic found for irq %d\n",
    			    DEVNAME(sc), irq._int);
    			return;
    		}
    
    		map = malloc(sizeof(*map), M_DEVBUF, M_NOWAIT | M_ZERO);
    		if (map == NULL)
    			return;
    
    		map->ioapic = apic;
    		map->ioapic_pin = irq._int - apic->sc_apic_vecbase;
    		map->bus_pin = ((addr >> 14) & 0x7c) | (pin & 0x3);
    		if (irq._ll)
    			map->flags |= (MPS_INTPO_ACTLO << MPS_INTPO_SHIFT);
    		else
    			map->flags |= (MPS_INTPO_ACTHI << MPS_INTPO_SHIFT);
    		if (irq._he)
    			map->flags |= (MPS_INTTR_EDGE << MPS_INTTR_SHIFT);
    		else
    			map->flags |= (MPS_INTTR_LEVEL << MPS_INTTR_SHIFT);
    
    		map->redir = (IOAPIC_REDLO_DEL_LOPRI << IOAPIC_REDLO_DEL_SHIFT);
    		switch ((map->flags >> MPS_INTPO_SHIFT) & MPS_INTPO_MASK) {
    		case MPS_INTPO_DEF:
    		case MPS_INTPO_ACTLO:
    			map->redir |= IOAPIC_REDLO_ACTLO;
    			break;
    		}
    		switch ((map->flags >> MPS_INTTR_SHIFT) & MPS_INTTR_MASK) {
    		case MPS_INTTR_DEF:
    		case MPS_INTTR_LEVEL:
    			map->redir |= IOAPIC_REDLO_LEVEL;
    			break;
    		}
    
    		map->ioapic_ih = APIC_INT_VIA_APIC |
    		    ((apic->sc_apicid << APIC_INT_APIC_SHIFT) |
    		    (map->ioapic_pin << APIC_INT_PIN_SHIFT));
    
    		apic->sc_pins[map->ioapic_pin].ip_map = map;
    
    		map->next = mp_busses[sc->sc_bus].mb_intrs;
    		mp_busses[sc->sc_bus].mb_intrs = map;
    
    		return;
    	}
    #endif
    
    	bus = sc->sc_bus;
    	dev = ACPI_PCI_DEV(addr << 16);
    	tag = pci_make_tag(pc, bus, dev, 0);
    
    	reg = pci_conf_read(pc, tag, PCI_BHLC_REG);
    	if (PCI_HDRTYPE_MULTIFN(reg))
    		nfuncs = 8;
    	else
    		nfuncs = 1;
    
    	for (func = 0; func < nfuncs; func++) {
    		tag = pci_make_tag(pc, bus, dev, func);
    		reg = pci_conf_read(pc, tag, PCI_INTERRUPT_REG);
    		if (PCI_INTERRUPT_PIN(reg) == pin + 1) {
    			reg &= ~(PCI_INTERRUPT_LINE_MASK << PCI_INTERRUPT_LINE_SHIFT);
    			reg |= irq._int << PCI_INTERRUPT_LINE_SHIFT;
    			pci_conf_write(pc, tag, PCI_INTERRUPT_REG, reg);
    		}
    	}
    }
    
    int
    acpiprt_getpcibus(struct acpiprt_softc *sc, struct aml_node *node)
    {
    	/* Check if parent device has PCI mapping */
    	return (node->parent && node->parent->pci) ?
    		node->parent->pci->sub : -1;
    }
    
    void
    acpiprt_route_interrupt(int bus, int dev, int pin)
    {
    	struct acpiprt_softc *sc;
    	struct acpiprt_map *p;
    	struct acpiprt_irq irq;
    	struct aml_node *node = NULL;
    	struct aml_value res, res2;
    	union acpi_resource *crs;
    	int newirq;
    	int64_t sta;
    
    	SIMPLEQ_FOREACH(p, &acpiprt_map_list, list) {
    		if (p->bus == bus && p->dev == dev && p->pin == (pin - 1)) {
    			newirq = p->irq;
    			sc = p->sc;
    			node = p->node;
    			break;
    		}
    	}
    	if (node == NULL)
    		return;
    
    	sta = acpi_getsta(sc->sc_acpi, node);
    	KASSERT(sta & STA_PRESENT);
    
    	if (aml_evalname(sc->sc_acpi, node, "_CRS", 0, NULL, &res)) {
    		printf("no _CRS method\n");
    		return;
    	}
    	if (res.type != AML_OBJTYPE_BUFFER || res.length < 5) {
    		printf("invalid _CRS object\n");
    		aml_freevalue(&res);
    		return;
    	}
    	aml_parse_resource(&res, acpiprt_getirq, &irq);
    
    	/* Only re-route interrupts when necessary. */
    	if ((sta & STA_ENABLED) && irq._int == newirq) {
    		aml_freevalue(&res);
    		return;
    	}
    
    	crs = (union acpi_resource *)res.v_buffer;
    	switch (AML_CRSTYPE(crs)) {
    	case SR_IRQ:
    		crs->sr_irq.irq_mask = htole16(1 << newirq);
    		break;
    	case LR_EXTIRQ:
    		crs->lr_extirq.irq[0] = htole32(newirq);
    		break;
    	}
    
    	if (aml_evalname(sc->sc_acpi, node, "_SRS", 1, &res, &res2)) {
    		printf("no _SRS method\n");
    		aml_freevalue(&res);
    		return;
    	}
    	aml_freevalue(&res);
    	aml_freevalue(&res2);
    }