freebsd-dev/sys/pci/pci.c
David Greenman a54907a1a7 Restore my changes to initialize the kdc_shutdown routine pointer. Stefan
clobbered it in his previous commit and not having it causes the machine
to panic during reboot (as well as not doing the important shutdown callout).
1995-03-23 06:14:44 +00:00

1536 lines
34 KiB
C

/**************************************************************************
**
** $Id: pci.c,v 1.20 1995/03/21 23:01:01 se Exp $
**
** General subroutines for the PCI bus.
** pci_configure ()
**
** FreeBSD
**
**-------------------------------------------------------------------------
**
** Copyright (c) 1994 Wolfgang Stanglmeier. 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.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** 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.
**
***************************************************************************
*/
#define __PCI_C__ "pl13 95/03/21"
#include <pci.h>
#if NPCI > 0
/*========================================================
**
** #includes and declarations
**
**========================================================
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/errno.h>
#include <sys/kernel.h>
#include <sys/proc.h> /* declaration of wakeup(), used by vm.h */
#include <sys/devconf.h>
#include <machine/cpu.h> /* bootverbose */
#include <vm/vm.h>
#include <vm/vm_param.h>
#include <machine/pmap.h>
#include <sys/devconf.h>
#include <pci/pcivar.h>
#include <pci/pcireg.h>
#include <pci/pcibus.h>
#define PCI_MAX_IRQ (16)
/*========================================================
**
** Structs and Functions
**
**========================================================
*/
struct pci_devconf {
struct kern_devconf pdc_kdc;
struct pci_info pdc_pi;
};
struct pcicb {
struct pcicb *pcicb_next;
struct pcicb *pcicb_up;
struct pcicb *pcicb_down;
pcici_t pcicb_bridge;
u_long pcicb_seen;
u_char pcicb_bus;
u_char pcicb_subordinate;
u_char pcicb_flags;
#define PCICB_ISAMEM 0x01
u_int pcicb_mfrom;
u_int pcicb_mupto;
u_int pcicb_mamount;
u_short pcicb_pfrom;
u_short pcicb_pupto;
u_short pcicb_pamount;
u_char pcicb_bfrom;
u_char pcicb_bupto;
u_long pcicb_iobase;
u_long pcicb_iolimit;
u_long pcicb_membase;
u_long pcicb_memlimit;
u_long pcicb_p_membase;
u_long pcicb_p_memlimit;
};
static int
pci_externalize (struct proc *, struct kern_devconf *, void *, size_t);
static int
pci_internalize (struct proc *, struct kern_devconf *, void *, size_t);
static void
not_supported (pcici_t tag, u_long type);
static void
pci_bus_config (void);
static void
pci_bridge_config (void);
/*========================================================
**
** Variables
**
**========================================================
*/
/*
** log2 of safe burst len (in words)
*/
unsigned pci_max_burst_len = 2;
unsigned pci_mechanism = 0;
unsigned pci_maxdevice = 0;
struct pcibus* pcibus;
/*--------------------------------------------------------
**
** Local variables.
**
**--------------------------------------------------------
*/
static int pci_conf_count;
static int pci_info_done;
static struct pcicb pcibus0 = {
NULL, NULL, NULL,
{ 0 },
0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, /* real allocation */
0, 0xFFFF, /* iobase/limit */
0x4000000, 0xFFFFFFFFu, /* nonprefetch membase/limit */
0x4000000, 0xFFFFFFFFu /* prefetch membase/limit */
};
static struct pcicb *pcicb;
/*========================================================
**
** pci_configure ()
**
** Autoconfiguration of pci devices.
**
** May be called more than once.
** Any device is attached only once.
**
** Has to take care of mirrored devices, which are
** entailed by incomplete decoding of pci address lines.
**
**========================================================
*/
void pci_configure()
{
int i;
/*
** check pci bus present
*/
for (i=0; i<pcibus_set.ls_length; i++) {
if (pci_maxdevice) break;
pcibus = (struct pcibus*) pcibus_set.ls_items[i];
pcibus->pb_setup ();
}
if (!pci_maxdevice) return;
/*
** hello world ..
*/
for (pcicb = &pcibus0; pcicb != NULL;) {
pci_bus_config ();
if (pcicb->pcicb_down) {
pcicb = pcicb->pcicb_down;
continue;
};
while (pcicb && !pcicb->pcicb_next)
pcicb = pcicb->pcicb_up;
if (pcicb)
pcicb = pcicb->pcicb_next;
}
pci_conf_count++;
}
/*========================================================
**
** Subroutines for configuration.
**
**========================================================
*/
static void
pci_register_io (struct pcicb * cb, u_int base, u_int limit)
{
#ifdef PCI_BRIDGE_DEBUG
printf ("register_io: bus=%d base=%x limit=%x\n",
cb->pcicb_bus, base, limit);
#endif
if (!cb->pcicb_pfrom || base < cb->pcicb_pfrom)
cb->pcicb_pfrom = base;
if (limit > cb->pcicb_pupto)
cb->pcicb_pupto = limit;
/*
** XXX should set bridge io mapping here
** but it can be mapped in 4k blocks only,
** leading to conflicts with isa/eisa ..
*/
}
static void
pci_register_memory (struct pcicb * cb, u_int base, u_int limit)
{
#ifdef PCI_BRIDGE_DEBUG
printf ("register_mem: bus=%d base=%x limit=%x\n",
cb->pcicb_bus, base, limit);
#endif
if (!cb->pcicb_mfrom || base < cb->pcicb_mfrom)
cb->pcicb_mfrom = base;
if (limit > cb->pcicb_mupto)
cb->pcicb_mupto = limit;
/*
** set the bridges mapping
**
** XXX should handle the 1Mb granularity.
*/
if (cb->pcicb_bridge.tag) {
pci_conf_write(cb->pcicb_bridge,
PCI_PCI_BRIDGE_MEM_REG,
(cb->pcicb_memlimit & 0xffff0000) |
(cb->pcicb_membase >> 16));
printf ("\t[pci%d uses memory from %x to %x]\n",
cb->pcicb_bus,
(unsigned) cb->pcicb_membase,
(unsigned) cb->pcicb_memlimit);
}
}
/*
** XXX This function is neither complete nor tested.
** It's only used if the bios hasn't done it's job
** of mapping the pci devices in the physical memory.
*/
static u_int
pci_memalloc (struct pcicb * cb, u_int addr, u_int size)
{
u_int result = 0, limit=0, newbase=0;
#ifdef PCI_BRIDGE_DEBUG
printf ("memalloc: bus=%d addr=%x size=%x ..\n",
cb->pcicb_bus, addr, size);
#endif
if (!cb) goto done;
if (!cb->pcicb_membase) {
printf ("memalloc: bus%d: membase not set.\n",
cb->pcicb_bus);
goto done;
}
/*
** get upper allocation limit
*/
limit = cb->pcicb_memlimit;
if (cb->pcicb_mfrom && cb->pcicb_mfrom <= limit)
limit = cb->pcicb_mfrom-1;
/*
** address fixed, and impossible to allocate ?
*/
if (addr && addr+size-1 > limit)
goto done;
/*
** get possible address
*/
result = addr;
if (!result) result = ((limit + 1) / size - 1) * size;
/*
** if not local available, request from parent.
*/
if (result < cb->pcicb_membase) {
newbase = pci_memalloc (cb->pcicb_up, result, size);
if (newbase) cb->pcicb_membase = result;
else result=0;
}
done:
if (result)
pci_register_memory (cb, result, result+size-1);
#ifdef PCI_BRIDGE_DEBUG
printf ("memalloc: bus=%d addr=%x size=%x --> %x (limit=%x).\n",
cb->pcicb_bus, addr, size, result, limit);
#endif
return (result);
}
/*========================================================
**
** pci_bus_config()
**
** Autoconfiguration of one pci bus.
**
**========================================================
*/
static void
pci_bus_config (void)
{
u_char device;
u_char reg;
pcici_t tag, mtag;
pcidi_t type;
u_long data;
int unit;
int pciint;
int irq;
char* name=0;
int dvi;
struct pci_device *dvp=0;
struct pci_devconf *pdcp;
/*
** first initialize the bridge (bus controller chip)
*/
pci_bridge_config ();
#ifndef PCI_QUIET
printf ("Probing for devices on the %s%d bus:\n",
pcibus->pb_name, pcicb->pcicb_bus);
if (!pci_info_done) {
pci_info_done=1;
printf ("\tconfiguration mode %d allows %d devices.\n",
pci_mechanism, pci_maxdevice);
};
#endif
for (device=0; device<pci_maxdevice; device ++) {
if ((pcicb->pcicb_seen >> device) & 1)
continue;
tag = pcibus->pb_tag (pcicb->pcicb_bus, device, 0);
type = pcibus->pb_read (tag, PCI_ID_REG);
if ((!type) || (type==0xfffffffful)) continue;
/*
** lookup device in ioconfiguration:
*/
for (dvi=0; dvi<pcidevice_set.ls_length; dvi++) {
dvp = (struct pci_device*) pcidevice_set.ls_items[dvi];
if ((name=(*dvp->pd_probe)(tag, type)))
break;
dvp = NULL;
};
/*
** check for mirrored devices.
*/
if (device & 0x10) {
mtag=pcibus->pb_tag (pcicb->pcicb_bus,
(u_char)(device & ~0x10), 0);
} else if (device & 0x08) {
mtag=pcibus->pb_tag (pcicb->pcicb_bus,
(u_char)(device & ~0x08), 0);
} else goto real_device;
if (type!=pcibus->pb_read (mtag, PCI_ID_REG))
goto real_device;
for (reg=PCI_MAP_REG_START;reg<PCI_MAP_REG_END;reg+=4)
if (pcibus->pb_read(tag,reg)!=pcibus->pb_read(mtag,reg))
goto real_device;
#ifndef PCI_QUIET
if (dvp==NULL) continue;
printf ("%s? <%s> mirrored on pci%d:%d\n",
dvp->pd_name, name, pcicb->pcicb_bus, device);
#endif
continue;
real_device:
if (dvp==NULL) {
#ifndef PCI_QUIET
if (pci_conf_count)
continue;
printf("%s%d:%d: ", pcibus->pb_name,
pcicb->pcicb_bus, device);
not_supported (tag, type);
#endif
continue;
};
pcicb->pcicb_seen |= (1ul << device);
/*
** Get and increment the unit.
*/
unit = (*dvp->pd_count)++;
/*
** ignore device ?
*/
if (!*name) continue;
/*
** Announce this device
*/
printf ("%s%d <%s> rev %d", dvp->pd_name, unit, name,
(unsigned) pci_conf_read (tag, PCI_CLASS_REG) & 0xff);
/*
** Get the int pin number (pci interrupt number a-d)
** from the pci configuration space.
*/
data = pcibus->pb_read (tag, PCI_INTERRUPT_REG);
pciint = PCI_INTERRUPT_PIN_EXTRACT(data);
if (pciint) {
printf (" int %c irq ", 0x60+pciint);
irq = PCI_INTERRUPT_LINE_EXTRACT(data);
/*
** If it's zero, the isa irq number is unknown,
** and we cannot bind the pci interrupt.
*/
if (irq)
printf ("%d", irq);
else
printf ("??");
};
printf (" on pci%d:%d\n", pcicb->pcicb_bus, device);
/*
** Read the current mapping,
** and update the pcicb fields.
*/
for (reg=PCI_MAP_REG_START;reg<PCI_MAP_REG_END;reg+=4) {
u_int map, addr, size;
data = pci_conf_read(tag, PCI_CLASS_REG);
switch (data & (PCI_CLASS_MASK|PCI_SUBCLASS_MASK)) {
case PCI_CLASS_BRIDGE|PCI_SUBCLASS_BRIDGE_PCI:
continue;
};
map = pcibus->pb_read (tag, reg);
if (!(map & PCI_MAP_MEMORY_ADDRESS_MASK))
continue;
pcibus->pb_write (tag, reg, 0xffffffff);
data = pcibus->pb_read (tag, reg);
pcibus->pb_write (tag, reg, map);
switch (data & 7) {
default:
continue;
case 1:
case 5:
size = -(data & PCI_MAP_IO_ADDRESS_MASK);
addr = map & PCI_MAP_IO_ADDRESS_MASK;
pci_register_io (pcicb, addr, addr+size-1);
pcicb->pcicb_pamount += size;
break;
case 0:
case 2:
case 4:
size = -(data & PCI_MAP_MEMORY_ADDRESS_MASK);
addr = map & PCI_MAP_MEMORY_ADDRESS_MASK;
if (addr >= 0x100000) {
pci_register_memory
(pcicb, addr, addr+size-1);
pcicb->pcicb_mamount += size;
} else {
pcicb->pcicb_flags |= PCICB_ISAMEM;
};
break;
};
if (!bootverbose)
continue;
printf ("\tmapreg[%02x] type=%d addr=%08x size=%04x.\n",
reg, map&7, addr, size);
};
/*
** Allocate a devconf structure
** We should, and eventually will, set the
** parent pointer to a pci bus devconf structure,
** and arrange to set the state field dynamically.
*/
pdcp = (struct pci_devconf *)
malloc (sizeof (struct pci_devconf),M_DEVBUF,M_WAITOK);
pdcp -> pdc_pi.pi_bus = pcicb->pcicb_bus;
pdcp -> pdc_pi.pi_device = device;
pdcp -> pdc_kdc.kdc_name = dvp->pd_name;
pdcp -> pdc_kdc.kdc_unit = unit;
pdcp -> pdc_kdc.kdc_md.mddc_devtype = MDDT_PCI;
pdcp -> pdc_kdc.kdc_externalize = pci_externalize;
pdcp -> pdc_kdc.kdc_internalize = pci_internalize;
pdcp -> pdc_kdc.kdc_datalen = PCI_EXTERNAL_LEN;
pdcp -> pdc_kdc.kdc_parentdata = &pdcp->pdc_pi;
pdcp -> pdc_kdc.kdc_state = DC_UNKNOWN;
pdcp -> pdc_kdc.kdc_description = name;
pdcp -> pdc_kdc.kdc_shutdown = dvp->pd_shutdown;
/*
** And register this device
*/
dev_attach (&pdcp->pdc_kdc);
/*
** attach device
** may produce additional log messages,
** i.e. when installing subdevices.
*/
(*dvp->pd_attach) (tag, unit);
/*
** Special processing of certain classes
*/
data = pci_conf_read(tag, PCI_CLASS_REG);
switch (data & (PCI_CLASS_MASK|PCI_SUBCLASS_MASK)) {
struct pcicb *this, **link;
unsigned char primary, secondary, subordinate;
u_int command;
case PCI_CLASS_BRIDGE|PCI_SUBCLASS_BRIDGE_PCI:
/*
** get current configuration of the bridge.
*/
data = pci_conf_read (tag, PCI_PCI_BRIDGE_BUS_REG);
primary = PCI_PRIMARY_BUS_EXTRACT (data);
secondary = PCI_SECONDARY_BUS_EXTRACT(data);
subordinate = PCI_SUBORDINATE_BUS_EXTRACT(data);
#ifndef PCI_QUIET
printf ("\tbridge from pci%d to pci%d through %d.\n",
primary, secondary, subordinate);
#endif
/*
** check for uninitialized bridge.
*/
if (secondary == 0 || secondary < primary ||
pcicb->pcicb_bus != primary)
{
printf ("\tINCORRECTLY or NEVER CONFIGURED.\n");
/*
** disable this bridge
*/
pcibus->pb_write (tag, PCI_COMMAND_STATUS_REG,
0xffff0000);
secondary = 0;
subordinate = 0;
};
/*
** allocate bus descriptor for bus behind the bridge
*/
link = &pcicb->pcicb_down;
while (*link) link = &(*link)->pcicb_next;
this = malloc (sizeof (*this), M_DEVBUF, M_WAITOK);
/*
** Initialize this descriptor so far.
** (the initialization is completed just before
** scanning the bus behind the bridge.
*/
bzero (this, sizeof(*this));
this->pcicb_up = pcicb;
this->pcicb_bridge = tag;
this->pcicb_bus = secondary;
this->pcicb_subordinate = subordinate;
command = pci_conf_read(tag,PCI_COMMAND_STATUS_REG);
if (command & PCI_COMMAND_IO_ENABLE){
/*
** Bridge was configured by the bios.
** Read out the mapped io region.
*/
u_int reg, data, mask;
reg = pci_conf_read (tag,
PCI_PCI_BRIDGE_IO_REG);
pci_conf_write(tag,
PCI_PCI_BRIDGE_IO_REG, 0xFFFF);
data = pci_conf_read (tag,
PCI_PCI_BRIDGE_IO_REG);
pci_conf_write(tag,
PCI_PCI_BRIDGE_IO_REG, reg & 0xffff);
mask = (0xFF00 ^ (data & 0xFF00)) | 0xFF;
this->pcicb_iobase =
PCI_PPB_IOBASE_EXTRACT (reg);
this->pcicb_iolimit =
PCI_PPB_IOLIMIT_EXTRACT(reg) | mask;
/*
** Note the used io space.
*/
pci_register_io (pcicb, this->pcicb_iobase,
this->pcicb_iolimit);
};
if (command & PCI_COMMAND_MEM_ENABLE) {
/*
** Bridge was configured by the bios.
** Read out the mapped memory regions.
*/
u_int reg, data, mask;
/*
** non prefetchable memory
*/
reg = pci_conf_read (tag,
PCI_PCI_BRIDGE_MEM_REG);
pci_conf_write(tag,
PCI_PCI_BRIDGE_MEM_REG, 0xFFFFFFFF);
data = pci_conf_read (tag,
PCI_PCI_BRIDGE_MEM_REG);
pci_conf_write(tag,
PCI_PCI_BRIDGE_MEM_REG, reg);
mask = (0xFFFF0000 ^ (data & 0xFFFF0000))
| 0xFFFF;
this->pcicb_membase =
PCI_PPB_MEMBASE_EXTRACT (reg);
this->pcicb_memlimit =
PCI_PPB_MEMLIMIT_EXTRACT(reg) | mask;
/*
** Register used memory space.
*/
pci_register_memory (pcicb,
this->pcicb_membase,
this->pcicb_memlimit);
/*
** prefetchable memory
*/
reg = pci_conf_read (tag,
PCI_PCI_BRIDGE_PMEM_REG);
pci_conf_write(tag,
PCI_PCI_BRIDGE_PMEM_REG, 0xFFFFFFFF);
data = pci_conf_read (tag,
PCI_PCI_BRIDGE_PMEM_REG);
pci_conf_write(tag,
PCI_PCI_BRIDGE_PMEM_REG, reg);
mask = (0xFFFF0000 ^ (data & 0xFFFF0000))
| 0xFFFF;
this->pcicb_p_membase=
PCI_PPB_MEMBASE_EXTRACT (reg);
this->pcicb_p_memlimit=
PCI_PPB_MEMLIMIT_EXTRACT(reg) | mask;
/*
** Register used memory space.
*/
pci_register_memory (pcicb,
this->pcicb_p_membase,
this->pcicb_p_memlimit);
}
/*
** Link it in chain.
*/
*link=this;
/*
** Update mapping info of parent bus.
*/
if (!pcicb->pcicb_bfrom||secondary< pcicb->pcicb_bfrom)
pcicb->pcicb_bfrom = secondary;
if (subordinate > pcicb->pcicb_bupto)
pcicb->pcicb_bupto = subordinate;
break;
}
}
#ifndef PCI_QUIET
if (pcicb->pcicb_mamount)
printf ("%s%d: uses %d bytes of memory from %x upto %x.\n",
pcibus->pb_name, pcicb->pcicb_bus,
pcicb->pcicb_mamount,
pcicb->pcicb_mfrom, pcicb->pcicb_mupto);
if (pcicb->pcicb_pamount)
printf ("%s%d: uses %d bytes of I/O space from %x upto %x.\n",
pcibus->pb_name, pcicb->pcicb_bus,
pcicb->pcicb_pamount,
pcicb->pcicb_pfrom, pcicb->pcicb_pupto);
if (pcicb->pcicb_bfrom)
printf ("%s%d: subordinate busses from %x upto %x.\n",
pcibus->pb_name, pcicb->pcicb_bus,
pcicb->pcicb_bfrom, pcicb->pcicb_bupto);
#endif
}
/*========================================================
**
** pci_bridge_config()
**
** Configuration of a pci bridge.
**
**========================================================
*/
static void
pci_bridge_config (void)
{
pcici_t tag;
struct pcicb* parent;
tag = pcicb->pcicb_bridge;
if (!tag.tag) return;
if (!pcicb->pcicb_bus) {
u_int data;
/*
** Get the lowest available bus number.
*/
pcicb->pcicb_bus = ++pcibus0.pcicb_subordinate;
/*
** and configure the bridge
*/
data = pci_conf_read (tag, PCI_PCI_BRIDGE_BUS_REG);
data = PCI_PRIMARY_BUS_INSERT(data, pcicb->pcicb_up->pcicb_bus);
data = PCI_SECONDARY_BUS_INSERT(data, pcicb->pcicb_bus);
data = PCI_SUBORDINATE_BUS_INSERT(data, pcicb->pcicb_bus);
pci_conf_write (tag, PCI_PCI_BRIDGE_BUS_REG, data);
/*
** Propagate the new upper bus number limit.
*/
for (parent = pcicb->pcicb_up; parent != NULL;
parent = parent->pcicb_up)
{
if (parent->pcicb_subordinate >= pcicb->pcicb_bus)
continue;
parent->pcicb_subordinate = pcicb->pcicb_bus;
if (!parent->pcicb_bridge.tag)
continue;
data = pci_conf_read
(parent->pcicb_bridge, PCI_PCI_BRIDGE_BUS_REG);
data = PCI_SUBORDINATE_BUS_INSERT
(data, pcicb->pcicb_bus);
pci_conf_write (parent->pcicb_bridge,
PCI_PCI_BRIDGE_BUS_REG, data);
}
}
if (!pcicb->pcicb_membase) {
u_int size = 0x100000;
pcicb->pcicb_membase = pci_memalloc (pcicb->pcicb_up, 0, size);
if (pcicb->pcicb_membase)
pcicb->pcicb_memlimit = pcicb->pcicb_membase+size-1;
}
}
/*-----------------------------------------------------------------
**
** The following functions are provided for the device driver
** to read/write the configuration space.
**
** pci_conf_read():
** Read a long word from the pci configuration space.
** Requires a tag (from pcitag) and the register
** number (should be a long word alligned one).
**
** pci_conf_write():
** Writes a long word to the pci configuration space.
** Requires a tag (from pcitag), the register number
** (should be a long word alligned one), and a value.
**
**-----------------------------------------------------------------
*/
u_long
pci_conf_read (pcici_t tag, u_long reg)
{
return (pcibus->pb_read (tag, reg));
}
void
pci_conf_write (pcici_t tag, u_long reg, u_long data)
{
pcibus->pb_write (tag, reg, data);
}
/*-----------------------------------------------------------------------
**
** Map device into port space.
**
** Actually the device should have been mapped by the bios.
** This function only reads and verifies the value.
**
** PCI-Specification: 6.2.5.1: address maps
**
**-----------------------------------------------------------------------
*/
int pci_map_port (pcici_t tag, u_long reg, u_short* pa)
{
unsigned data, ioaddr, iosize;
struct pcicb *link = pcicb;
/*
** sanity check
*/
if (reg < PCI_MAP_REG_START || reg >= PCI_MAP_REG_END || (reg & 3)) {
printf ("pci_map_port failed: bad register=0x%x\n",
(unsigned)reg);
return (0);
};
/*if (pcicb->pcicb_flags & PCICB_NOIOSET) {
printf ("pci_map_port failed: pci%d has not been configured for I/O access\n",
pcicb->pcicb_bus);
return (0);
}*/
/*
** get size and type of port
**
** type is in the lowest two bits.
** If device requires 2^n bytes, the next
** n-2 bits are hardwired as 0.
*/
ioaddr = pcibus->pb_read (tag, reg) & PCI_MAP_IO_ADDRESS_MASK;
if (!ioaddr || ioaddr > 0xfffful) {
printf ("pci_map_port failed: not configured by bios.\n");
return (0);
};
pcibus->pb_write (tag, reg, 0xfffffffful);
data = pcibus->pb_read (tag, reg);
pcibus->pb_write (tag, reg, ioaddr);
if ((data & 0x03) != PCI_MAP_IO) {
printf ("pci_map_port failed: bad port type=0x%x\n",
(unsigned) data);
return (0);
};
iosize = -(data & PCI_MAP_IO_ADDRESS_MASK);
if (ioaddr < pcicb->pcicb_iobase
|| ioaddr + iosize > pcicb->pcicb_iolimit) {
printf ("pci_map_port failed: device's iorange 0x%x-0x%x "
"is incompatible with its bridge's range 0x%x-0x%x\n",
(unsigned) ioaddr, (unsigned) ioaddr + iosize - 1,
(unsigned) pcicb->pcicb_iobase,
(unsigned) pcicb->pcicb_iolimit);
return (0);
}
#ifndef PCI_QUIET
printf ("\treg%d: ioaddr=0x%x size=0x%x\n",
(unsigned) reg, (unsigned) ioaddr, (unsigned) iosize);
#endif
/*
** set the configuration register of and
** return the address to the driver.
** Make sure to enable each upstream bridge
** so I/O and DMA can go all the way.
*/
for (;;) {
data = pcibus->pb_read (tag, PCI_COMMAND_STATUS_REG) & 0xffff;
data |= PCI_COMMAND_IO_ENABLE | PCI_COMMAND_MASTER_ENABLE;
(void) pcibus->pb_write(tag, PCI_COMMAND_STATUS_REG, data);
if ((link = link->pcicb_up) == NULL)
break;
tag = link->pcicb_bridge;
}
*pa = ioaddr;
return (1);
}
/*-----------------------------------------------------------------------
**
** Map device into virtual and physical space
**
** Actually the device should have been mapped by the bios.
** This function only reads and verifies the value.
**
** PCI-Specification: 6.2.5.1: address maps
**
**-----------------------------------------------------------------------
*/
int pci_map_mem (pcici_t tag, u_long reg, vm_offset_t* va, vm_offset_t* pa)
{
struct pcicb *link = pcicb;
unsigned data ,paddr;
vm_size_t psize, poffs;
vm_offset_t vaddr;
/*
** sanity check
*/
if (reg < PCI_MAP_REG_START || reg >= PCI_MAP_REG_END || (reg & 3)) {
printf ("pci_map_mem failed: bad register=0x%x\n",
(unsigned)reg);
return (0);
};
/*
** save old mapping, get size and type of memory
**
** type is in the lowest four bits.
** If device requires 2^n bytes, the next
** n-4 bits are read as 0.
*/
paddr = pcibus->pb_read (tag, reg) & PCI_MAP_MEMORY_ADDRESS_MASK;
pcibus->pb_write (tag, reg, 0xfffffffful);
data = pcibus->pb_read (tag, reg);
pcibus->pb_write (tag, reg, paddr);
/*
** check the type
*/
if ((data & PCI_MAP_MEMORY_TYPE_MASK) != PCI_MAP_MEMORY_TYPE_32BIT) {
printf ("pci_map_mem failed: bad memory type=0x%x\n",
(unsigned) data);
return (0);
};
/*
** get the size.
*/
psize = -(data & PCI_MAP_MEMORY_ADDRESS_MASK);
if (!paddr || paddr == PCI_MAP_MEMORY_ADDRESS_MASK) {
paddr = pci_memalloc (pcicb, 0, psize);
if (!paddr) {
printf ("pci_map_mem: not configured by bios.\n");
return (0);
};
pci_register_memory (pcicb, paddr, paddr+psize-1);
};
if (paddr < pcicb->pcicb_membase ||
paddr + psize - 1 > pcicb->pcicb_memlimit) {
printf ("pci_map_mem failed: device's memrange 0x%x-0x%x is "
"incompatible with its bridge's memrange 0x%x-0x%x\n",
(unsigned) paddr,
(unsigned) (paddr + psize - 1),
(unsigned) pcicb->pcicb_membase,
(unsigned) pcicb->pcicb_memlimit);
return (0);
}
pcibus->pb_write (tag, reg, paddr);
/*
** Truncate paddr to page boundary.
** (Or does pmap_mapdev the job?)
*/
poffs = paddr - trunc_page (paddr);
vaddr = (vm_offset_t) pmap_mapdev (paddr-poffs, psize+poffs);
if (!vaddr) return (0);
vaddr += poffs;
#ifndef PCI_QUIET
/*
** display values.
*/
printf ("\treg%d: virtual=0x%lx physical=0x%lx size=0x%lx\n",
(unsigned) reg, (u_long)vaddr, (u_long)paddr, (u_long)psize);
#endif
/*
** set the configuration register and
** return the address to the driver
** Make sure to enable each upstream bridge
** so memory and DMA can go all the way.
*/
for (;;) {
data = pcibus->pb_read (tag, PCI_COMMAND_STATUS_REG) & 0xffff;
data |= PCI_COMMAND_MEM_ENABLE | PCI_COMMAND_MASTER_ENABLE;
(void) pcibus->pb_write(tag, PCI_COMMAND_STATUS_REG, data);
if ((link = link->pcicb_up) == NULL)
break;
tag = link->pcicb_bridge;
}
*va = vaddr;
*pa = paddr;
return (1);
}
/*------------------------------------------------------------
**
** Interface functions for the devconf module.
**
**------------------------------------------------------------
*/
static int
pci_externalize (struct proc *p, struct kern_devconf *kdcp, void *u, size_t l)
{
struct pci_externalize_buffer buffer;
struct pci_info * pip = kdcp->kdc_parentdata;
pcici_t tag;
int i;
if (l < sizeof buffer) {
return ENOMEM;
};
tag = pcibus->pb_tag (pip->pi_bus, pip->pi_device, 0);
buffer.peb_pci_info = *pip;
for (i=0; i<PCI_EXT_CONF_LEN; i++) {
buffer.peb_config[i] = pcibus->pb_read (tag, i*4);
};
return copyout(&buffer, u, sizeof buffer);
}
static int
pci_internalize (struct proc *p, struct kern_devconf *kdcp, void *u, size_t s)
{
return EOPNOTSUPP;
}
/*-----------------------------------------------------------------------
**
** Pci meta interrupt handler
**
** This handler assumes level triggered interrupts.
** It's possible to build a kernel which handles shared
** edge triggered interrupts by the options "PCI_EDGE_INT".
** But there is a performance penalty.
**
** (Of course you can delete the #ifdef PCI_EDGE_INT bracketed
** code at all :-) :-) :-)
**
**-----------------------------------------------------------------------
*/
struct pci_int_desc*
pci_int_desc [PCI_MAX_IRQ];
#ifndef NO_SHARED_IRQ
static inline unsigned
splq (unsigned mask)
{
unsigned temp=cpl;
cpl |= mask;
return temp;
}
static void
pci_int (int irq)
{
struct pci_int_desc * p;
int c, s;
#ifdef PCI_EDGE_INT
int i, n;
#endif
if (irq<0 || irq >= PCI_MAX_IRQ) {
printf ("pci_int(%d)\n", irq);
return;
};
#ifdef PCI_EDGE_INT
for (i=0; i<1000; i++) {
n = 0;
#endif
for (p = pci_int_desc[irq]; p!=NULL; p=p->pcid_next) {
s = splq (*p->pcid_maskptr);
c= (*p->pcid_handler) (p->pcid_argument);
p-> pcid_tally += c;
splx (s);
#ifdef PCI_EDGE_INT
n += c;
#endif
#if 0
if (c && p->pcid_tally<20)
printf ("PCI_INT: irq=%d h=%p cpl o=%x n=%x val=%d\n",
irq, p->pcid_handler, s, cpl, c);
#endif
};
#ifdef PCI_EDGE_INT
if (!n) return;
};
printf ("pci_int(%d): permanent interrupt request.\n", irq);
#endif
}
#endif
/*-----------------------------------------------------------------------
**
** Auxiliary function for interrupt (un)mapping.
**
**-----------------------------------------------------------------------
*/
static u_int
getirq (pcici_t tag)
{
u_int irq;
irq = PCI_INTERRUPT_LINE_EXTRACT(
pcibus->pb_read (tag, PCI_INTERRUPT_REG));
if (irq <= 0) {
printf ("\tint line register not set by bios\n");
return (0);
}
if (irq >= pcibus->pb_maxirq || irq >= PCI_MAX_IRQ) {
printf ("\tirq %d invalid.\n", irq);
return (0);
}
return (irq);
}
static struct pci_int_desc **
getintdescbytag (u_int irq, pcici_t tag)
{
struct pci_int_desc *p, **pp;
pp=&pci_int_desc[irq];
while (((p=*pp)) && !sametag(p->pcid_tag,tag))
pp=&p->pcid_next;
if (!p) return (NULL);
return (pp);
}
static struct pci_int_desc *
getintdescbymptr (u_int irq, unsigned * mptr)
{
struct pci_int_desc *p;
for (p=pci_int_desc[irq];p;p=p->pcid_next)
if (p->pcid_maskptr == mptr) break;
return (p);
}
/*-----------------------------------------------------------------------
**
** Map pci interrupt.
**
**-----------------------------------------------------------------------
*/
static unsigned pci_mask0 = 0;
int pci_map_int (pcici_t tag, int(*func)(), void* arg, unsigned* maskptr)
{
u_int irq;
int result, oldspl;
unsigned mask;
struct pci_int_desc *tail, *mdp=NULL, *new=NULL;
/*
** Get irq line from configuration space,
** and check for consistency.
*/
irq = getirq (tag);
if (irq >= PCI_MAX_IRQ) {
printf ("\tillegal irq %d.\n", irq);
return (0);
};
mask= 1ul << irq;
/*
** disable this interrupt.
*/
oldspl = splq (mask);
/*
** If handler for this tag already installed,
** remove it first.
*/
if (getintdescbytag (irq, tag) != NULL)
pci_unmap_int (tag);
/*
** If this irq not yet included in the mask, include it.
*/
mdp = getintdescbymptr (irq, maskptr);
if (!mdp) {
result = pcibus->pb_imaskinc (irq, maskptr);
if (result)
goto conflict;
};
/*
** Allocate descriptor and initialize it.
*/
tail = pci_int_desc[irq];
new = malloc (sizeof (*new), M_DEVBUF, M_WAITOK);
bzero (new, sizeof (*new));
new->pcid_next = tail;
new->pcid_tag = tag;
new->pcid_handler = func;
new->pcid_argument = arg;
new->pcid_maskptr = maskptr;
new->pcid_tally = 0;
new->pcid_mask = mask;
/*
** If first handler: install it.
** If second handler: install shared-int-handler.
*/
if (!tail) {
/*
** first handler for this irq.
*/
result = pcibus->pb_iattach
(irq, (void(*)()) func, (int) arg, maskptr);
if (result) goto conflict;
#ifdef NO_SHARED_IRQ
} else goto conflict;
#else
} else if (!tail->pcid_next) {
/*
** Second handler for this irq.
*/
printf ("\tusing shared irq %d.\n", irq);
/*
** replace old handler by shared-int-handler.
*/
result = pcibus->pb_idetach (irq,(void(*)())tail->pcid_handler);
if (result)
printf ("\tCANNOT DETACH INT HANDLER.\n");
result = pcibus->pb_iattach (irq, pci_int, irq, &pci_mask0);
if (result) {
printf ("\tCANNOT ATTACH SHARED INT HANDLER.\n");
goto fail;
};
}
#endif
/*
** Link new descriptor, reenable ints and done.
*/
pci_int_desc[irq] = new;
splx (oldspl);
return (1);
/*
** Handle some problems.
*/
conflict:
printf ("\tirq %d already in use.\n", irq);
fail:
/*
** If descriptor allocated, free it.
** If included in mask, remove it.
*/
if (free) free (new, M_DEVBUF);
if (!mdp) (void) pcibus->pb_imaskexc (irq, maskptr);
splx (oldspl);
return (0);
}
/*-----------------------------------------------------------------------
**
** Unmap pci interrupt.
**
**-----------------------------------------------------------------------
*/
int pci_unmap_int (pcici_t tag)
{
int result, oldspl;
struct pci_int_desc *this, **hook, *tail;
unsigned irq;
/*
** Get irq line from configuration space,
** and check for consistency.
*/
irq = getirq (tag);
if (irq >= PCI_MAX_IRQ) {
printf ("\tillegal irq %d.\n", irq);
return (0);
};
/*
** Search and unlink interrupt descriptor.
*/
hook = getintdescbytag (irq, tag);
if (hook == NULL) {
printf ("\tno irq %d handler for pci %x\n",
irq, tag.tag);
return (0);
};
this = *hook;
*hook= this->pcid_next;
/*
** Message
*/
printf ("\tirq %d handler %p(%p) unmapped for pci %x after %d ints.\n",
irq, this->pcid_handler, this->pcid_argument,
this->pcid_tag.tag, this->pcid_tally);
/*
** If this irq no longer included in the mask, remove it.
*/
if (!getintdescbymptr (irq, this->pcid_maskptr))
(void) pcibus->pb_imaskexc (irq, this->pcid_maskptr);
tail = pci_int_desc[irq];
if (tail == NULL) {
/*
** Remove the old handler.
*/
result = pcibus->pb_idetach (irq,(void(*)())this->pcid_handler);
if (result)
printf ("\tirq %d: cannot remove handler.\n", irq);
} else if (tail->pcid_next == NULL) {
/*
** Remove the shared int handler.
** Install the last remaining handler.
*/
oldspl = splq (1ul << irq);
result = pcibus->pb_idetach (irq, pci_int);
if (result)
printf ("\tirq %d: cannot remove handler.\n", irq);
result = pcibus->pb_iattach (irq,
(void(*)()) tail->pcid_handler,
(int) tail->pcid_argument,
tail->pcid_maskptr);
if (result)
printf ("\tirq %d: cannot install handler.\n", irq);
splx (oldspl);
};
free (this, M_DEVBUF);
return (1);
}
/*-----------------------------------------------------------
**
** Display of unknown devices.
**
**-----------------------------------------------------------
*/
struct vt {
u_short ident;
char* name;
};
static struct vt VendorTable[] = {
{0x1002, "ATI TECHNOLOGIES INC"},
{0x1011, "DIGITAL EQUIPMENT CORPORATION"},
{0x101A, "NCR"},
{0x102B, "MATROX"},
{0x1045, "OPTI"},
{0x5333, "S3 INC."},
{0x8086, "INTEL CORPORATION"},
{0,0}
};
static const char *const majclasses[] = {
"old", "storage", "network", "display",
"multimedia", "memory", "bridge"
};
void not_supported (pcici_t tag, u_long type)
{
u_char reg;
u_long data;
struct vt * vp;
/*
** lookup the names.
*/
for (vp=VendorTable; vp->ident; vp++)
if (vp->ident == (type & 0xffff))
break;
/*
** and display them.
*/
if (vp->ident) printf (vp->name);
else printf ("vendor=0x%lx", type & 0xffff);
printf (", device=0x%lx", type >> 16);
data = (pcibus->pb_read(tag, PCI_CLASS_REG) >> 24) & 0xff;
if (data < sizeof(majclasses) / sizeof(majclasses[0]))
printf(", class=%s", majclasses[data]);
printf (" [not supported]\n");
for (reg=PCI_MAP_REG_START; reg<PCI_MAP_REG_END; reg+=4) {
data = pcibus->pb_read (tag, reg);
if ((data&~7)==0) continue;
switch (data&7) {
case 1:
case 5:
printf (" map(%x): io(%lx)\n",
reg, data & ~3);
break;
case 0:
printf (" map(%x): mem32(%lx)\n",
reg, data & ~7);
break;
case 2:
printf (" map(%x): mem20(%lx)\n",
reg, data & ~7);
break;
case 4:
printf (" map(%x): mem64(%lx)\n",
reg, data & ~7);
break;
}
}
}
#endif /* NPCI */