92e81ca475
using the new pci_map_int_right() variant of pci_map_int(). Fast interrupts work for PCI devices if and only if they are exclusive. (The PCI interrupt mux doesn't support fast interrupts and can't support a mixture of fast and slow interrupts even in principle.) Don't assume that intrmask_t == unsigned in pci_map_int().
482 lines
11 KiB
C
482 lines
11 KiB
C
/*
|
|
* Copyright (c) 1997, Stefan Esser <se@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 unmodified, 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 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.
|
|
*
|
|
* $Id: pci_compat.c,v 1.17 1998/12/14 05:47:28 dillon Exp $
|
|
*
|
|
*/
|
|
|
|
#include "pci.h"
|
|
#if NPCI > 0
|
|
|
|
/* for compatibility to FreeBSD-2.2 version of PCI code */
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/kernel.h> /* for DATA_SET support */
|
|
|
|
#include <vm/vm.h>
|
|
#include <vm/pmap.h>
|
|
#include <sys/interrupt.h>
|
|
|
|
#include <pci/pcireg.h>
|
|
#include <pci/pcivar.h>
|
|
|
|
#ifdef RESOURCE_CHECK
|
|
#include <sys/drvresource.h>
|
|
#endif
|
|
|
|
#ifdef APIC_IO
|
|
#include <machine/smp.h>
|
|
#endif
|
|
|
|
#ifdef PCI_COMPAT
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
static int
|
|
pci_mapno(pcicfgregs *cfg, int reg)
|
|
{
|
|
int map = -1;
|
|
if ((reg & 0x03) == 0) {
|
|
map = (reg -0x10) / 4;
|
|
if (map < 0 || map >= cfg->nummaps)
|
|
map = -1;
|
|
}
|
|
return (map);
|
|
}
|
|
|
|
static int
|
|
pci_porten(pcicfgregs *cfg)
|
|
{
|
|
return ((cfg->cmdreg & PCIM_CMD_PORTEN) != 0);
|
|
}
|
|
|
|
static int
|
|
pci_isportmap(pcicfgregs *cfg, int map)
|
|
|
|
{
|
|
return ((unsigned)map < cfg->nummaps
|
|
&& (cfg->map[map].type & PCI_MAPPORT) != 0);
|
|
}
|
|
|
|
static int
|
|
pci_memen(pcicfgregs *cfg)
|
|
{
|
|
return ((cfg->cmdreg & PCIM_CMD_MEMEN) != 0);
|
|
}
|
|
|
|
static int
|
|
pci_ismemmap(pcicfgregs *cfg, int map)
|
|
{
|
|
return ((unsigned)map < cfg->nummaps
|
|
&& (cfg->map[map].type & PCI_MAPMEM) != 0);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
u_long
|
|
pci_conf_read(pcici_t tag, u_long reg)
|
|
{
|
|
return (pci_cfgread(tag, reg, 4));
|
|
}
|
|
|
|
void
|
|
pci_conf_write(pcici_t tag, u_long reg, u_long data)
|
|
{
|
|
pci_cfgwrite(tag, reg, data, 4);
|
|
}
|
|
|
|
int pci_map_port(pcici_t cfg, u_long reg, pci_port_t* pa)
|
|
{
|
|
int map;
|
|
|
|
map = pci_mapno(cfg, reg);
|
|
if (pci_porten(cfg) && pci_isportmap(cfg, map)) {
|
|
u_int32_t iobase;
|
|
u_int32_t iosize;
|
|
|
|
iobase = cfg->map[map].base;
|
|
iosize = 1 << cfg->map[map].ln2size;
|
|
#ifdef RESOURCE_CHECK
|
|
if (resource_claim(cfg, REST_PORT, RESF_NONE,
|
|
iobase, iobase + iosize -1) == 0)
|
|
#endif /* RESOURCE_CHECK */
|
|
{
|
|
*pa = iobase;
|
|
return (1);
|
|
}
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
int pci_map_mem(pcici_t cfg, u_long reg, vm_offset_t* va, vm_offset_t* pa)
|
|
{
|
|
int map;
|
|
|
|
map = pci_mapno(cfg, reg);
|
|
if (pci_memen(cfg) && pci_ismemmap(cfg, map)) {
|
|
u_int32_t paddr;
|
|
u_int32_t psize;
|
|
|
|
paddr = cfg->map[map].base;
|
|
psize = 1 << cfg->map[map].ln2size;
|
|
#ifdef RESOURCE_CHECK
|
|
if (resource_claim(cfg, REST_MEM, RESF_NONE,
|
|
paddr, paddr + psize -1) == 0)
|
|
#endif /* RESOURCE_CHECK */
|
|
{
|
|
u_int32_t poffs;
|
|
vm_offset_t vaddr;
|
|
|
|
poffs = paddr - trunc_page(paddr);
|
|
#ifdef __i386__
|
|
vaddr = (vm_offset_t)pmap_mapdev(paddr-poffs, psize+poffs);
|
|
#endif
|
|
#ifdef __alpha__
|
|
vaddr = paddr-poffs;
|
|
#endif
|
|
if (vaddr != NULL) {
|
|
vaddr += poffs;
|
|
*va = vaddr;
|
|
*pa = paddr;
|
|
return (1);
|
|
}
|
|
}
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
pci_map_dense(pcici_t cfg, u_long reg, vm_offset_t* va, vm_offset_t* pa)
|
|
{
|
|
if(pci_map_mem(cfg, reg, va, pa)){
|
|
#ifdef __alpha__
|
|
vm_offset_t dense;
|
|
|
|
if(dense = pci_cvt_to_dense(*pa)){
|
|
*pa = dense;
|
|
*va = ALPHA_PHYS_TO_K0SEG(*pa);
|
|
return (1);
|
|
}
|
|
#endif
|
|
#ifdef __i386__
|
|
return(1);
|
|
#endif
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
pci_map_bwx(pcici_t cfg, u_long reg, vm_offset_t* va, vm_offset_t* pa)
|
|
{
|
|
if(pci_map_mem(cfg, reg, va, pa)){
|
|
#ifdef __alpha__
|
|
vm_offset_t bwx;
|
|
|
|
if(bwx = pci_cvt_to_bwx(*pa)){
|
|
*pa = bwx;
|
|
*va = ALPHA_PHYS_TO_K0SEG(*pa);
|
|
return (1);
|
|
}
|
|
#endif
|
|
#ifdef __i386__
|
|
return(1);
|
|
#endif
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
pci_map_int(pcici_t cfg, pci_inthand_t *handler, void *arg, intrmask_t *maskptr)
|
|
{
|
|
return (pci_map_int_right(cfg, handler, arg, maskptr, 0));
|
|
}
|
|
|
|
int
|
|
pci_map_int_right(pcici_t cfg, pci_inthand_t *handler, void *arg,
|
|
intrmask_t *maskptr, u_int flags)
|
|
{
|
|
int error;
|
|
#ifdef APIC_IO
|
|
int nextpin, muxcnt;
|
|
#endif
|
|
if (cfg->intpin != 0) {
|
|
int irq = cfg->intline;
|
|
void *dev_instance = (void *)-1; /* XXX use cfg->devdata */
|
|
void *idesc;
|
|
|
|
idesc = intr_create(dev_instance, irq, handler, arg, maskptr,
|
|
flags);
|
|
error = intr_connect(idesc);
|
|
if (error != 0)
|
|
return 0;
|
|
#ifdef APIC_IO
|
|
nextpin = next_apic_irq(irq);
|
|
|
|
if (nextpin < 0)
|
|
return 1;
|
|
|
|
/*
|
|
* Attempt handling of some broken mp tables.
|
|
*
|
|
* It's OK to yell (since the mp tables are broken).
|
|
*
|
|
* Hanging in the boot is not OK
|
|
*/
|
|
|
|
muxcnt = 2;
|
|
nextpin = next_apic_irq(nextpin);
|
|
while (muxcnt < 5 && nextpin >= 0) {
|
|
muxcnt++;
|
|
nextpin = next_apic_irq(nextpin);
|
|
}
|
|
if (muxcnt >= 5) {
|
|
printf("bogus MP table, more than 4 IO APIC pins connected to the same PCI device or ISA/EISA interrupt\n");
|
|
return 0;
|
|
}
|
|
|
|
printf("bogus MP table, %d IO APIC pins connected to the same PCI device or ISA/EISA interrupt\n", muxcnt);
|
|
|
|
nextpin = next_apic_irq(irq);
|
|
while (nextpin >= 0) {
|
|
idesc = intr_create(dev_instance, nextpin, handler,
|
|
arg, maskptr, flags);
|
|
error = intr_connect(idesc);
|
|
if (error != 0)
|
|
return 0;
|
|
printf("Registered extra interrupt handler for int %d (in addition to int %d)\n", nextpin, irq);
|
|
nextpin = next_apic_irq(nextpin);
|
|
}
|
|
#endif
|
|
}
|
|
return (1);
|
|
}
|
|
|
|
int
|
|
pci_unmap_int(pcici_t cfg)
|
|
{
|
|
return (0); /* not supported, yet, since cfg doesn't know about idesc */
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
/*
|
|
* Preliminary support for "wired" PCI devices.
|
|
* This code supports currently only devices on PCI bus 0, since the
|
|
* mapping from PCI BIOS bus numbers to configuration file bus numbers
|
|
* is not yet maintained, whenever a PCI to PCI bridge is found.
|
|
* The "bus" field of "pciwirecfg" correlates an PCI bus with the bridge
|
|
* it is attached to. The "biosbus" field is to be updated for each bus,
|
|
* whose bridge is probed. An entry with bus != 0 and biosbus == 0 is
|
|
* invalid and will be skipped in the search for a wired unit, but not
|
|
* in the test for a free unit number.
|
|
*/
|
|
|
|
typedef struct {
|
|
char *name;
|
|
int unit;
|
|
u_int8_t bus;
|
|
u_int8_t slot;
|
|
u_int8_t func;
|
|
u_int8_t biosbus;
|
|
} pciwirecfg;
|
|
|
|
static pciwirecfg pci_wireddevs[] = {
|
|
/* driver, unit, bus, slot, func, BIOS bus */
|
|
#ifdef PCI_DEBUG
|
|
{ "ncr", 2, 1, 4, 0, 0 },
|
|
{ "ed", 2, 1, 5, 0, 0 },
|
|
#endif /* PCI_DEBUG */
|
|
/* do not delete the end marker that follows this comment !!! */
|
|
{ NULL }
|
|
};
|
|
|
|
/* return unit number of wired device, or -1 if no match */
|
|
|
|
static int
|
|
pci_wiredunit(pcicfgregs *cfg, char *name)
|
|
{
|
|
pciwirecfg *p;
|
|
|
|
p = pci_wireddevs;
|
|
while (p->name != NULL) {
|
|
if (p->bus == cfg->bus
|
|
&& p->slot == cfg->slot
|
|
&& p->func == cfg->func
|
|
&& strcmp(p->name, name) == 0)
|
|
return (p->unit);
|
|
p++;
|
|
}
|
|
return (-1);
|
|
}
|
|
|
|
/* return free unit number equal or greater to the one supplied as parameter */
|
|
|
|
static int
|
|
pci_freeunit(pcicfgregs *cfg, char *name, int unit)
|
|
{
|
|
pciwirecfg *p;
|
|
|
|
p = pci_wireddevs;
|
|
while (p->name != NULL) {
|
|
if (p->unit == unit && strcmp(p->name, name) == 0) {
|
|
p = pci_wireddevs;
|
|
unit++;
|
|
} else {
|
|
p++;
|
|
}
|
|
}
|
|
return (unit);
|
|
}
|
|
|
|
static const char *drvname;
|
|
|
|
static const char*
|
|
pci_probedrv(pcicfgregs *cfg, struct pci_device *dvp)
|
|
{
|
|
if (dvp && dvp->pd_probe) {
|
|
pcidi_t type = (cfg->device << 16) + cfg->vendor;
|
|
return (dvp->pd_probe(cfg, type));
|
|
}
|
|
return (NULL);
|
|
}
|
|
|
|
static struct pci_lkm *pci_lkm_head;
|
|
|
|
static struct pci_device*
|
|
pci_finddrv(pcicfgregs *cfg)
|
|
{
|
|
struct pci_device **dvpp;
|
|
struct pci_device *dvp = NULL;
|
|
struct pci_lkm *lkm;
|
|
|
|
drvname = NULL;
|
|
lkm = pci_lkm_head;
|
|
while (drvname == NULL && lkm != NULL) {
|
|
dvp = lkm->dvp;
|
|
drvname = pci_probedrv(cfg, dvp);
|
|
lkm = lkm->next;
|
|
}
|
|
|
|
dvpp = (struct pci_device **)pcidevice_set.ls_items;
|
|
while (drvname == NULL && (dvp = *dvpp++) != NULL)
|
|
drvname = pci_probedrv(cfg, dvp);
|
|
return (dvp);
|
|
}
|
|
|
|
static void
|
|
pci_drvmessage(pcicfgregs *cfg, char *name, int unit)
|
|
{
|
|
if (drvname == NULL || *drvname == '\0')
|
|
return;
|
|
printf("%s%d: <%s> rev 0x%02x", name, unit, drvname, cfg->revid);
|
|
if (cfg->intpin != 0)
|
|
printf(" int %c irq %d", cfg->intpin + 'a' -1, cfg->intline);
|
|
printf(" on pci%d.%d.%d\n", cfg->bus, cfg->slot, cfg->func);
|
|
}
|
|
|
|
|
|
void
|
|
pci_drvattach(struct pci_devinfo *dinfo)
|
|
{
|
|
struct pci_device *dvp;
|
|
pcicfgregs *cfg;
|
|
|
|
cfg = &dinfo->cfg;
|
|
dvp = pci_finddrv(cfg);
|
|
if (dvp != NULL) {
|
|
int unit;
|
|
|
|
unit = pci_wiredunit(cfg, dvp->pd_name);
|
|
if (unit < 0) {
|
|
unit = pci_freeunit(cfg, dvp->pd_name, *dvp->pd_count);
|
|
*dvp->pd_count = unit +1;
|
|
}
|
|
pci_drvmessage(cfg, dvp->pd_name, unit);
|
|
if (dvp->pd_attach)
|
|
dvp->pd_attach(cfg, unit);
|
|
|
|
dinfo->device = dvp;
|
|
|
|
/*
|
|
* XXX KDM for some devices, dvp->pd_name winds up NULL.
|
|
* I haven't investigated enough to figure out why this
|
|
* would happen.
|
|
*/
|
|
if (dvp->pd_name != NULL)
|
|
strncpy(dinfo->conf.pd_name, dvp->pd_name,
|
|
sizeof(dinfo->conf.pd_name));
|
|
else
|
|
strncpy(dinfo->conf.pd_name, "????",
|
|
sizeof(dinfo->conf.pd_name));
|
|
dinfo->conf.pd_name[sizeof(dinfo->conf.pd_name) - 1] = 0;
|
|
|
|
dinfo->conf.pd_unit = unit;
|
|
|
|
}
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
static void
|
|
pci_rescan(void)
|
|
{
|
|
/* XXX do nothing, currently, soon to come ... */
|
|
}
|
|
|
|
int pci_register_lkm (struct pci_device *dvp, int if_revision)
|
|
{
|
|
struct pci_lkm *lkm;
|
|
|
|
if (if_revision != 0) {
|
|
return (-1);
|
|
}
|
|
if (dvp == NULL || dvp->pd_probe == NULL || dvp->pd_attach == NULL) {
|
|
return (-1);
|
|
}
|
|
lkm = malloc (sizeof (*lkm), M_DEVBUF, M_WAITOK);
|
|
if (lkm == NULL) {
|
|
return (-1);
|
|
}
|
|
|
|
lkm->dvp = dvp;
|
|
lkm->next = pci_lkm_head;
|
|
pci_lkm_head = lkm;
|
|
pci_rescan();
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
pci_configure(void)
|
|
{
|
|
pci_probe(NULL);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
#endif /* PCI_COMPAT */
|
|
#endif /* NPCI > 0 */
|