linuxPci.c   [plain text]


/*
 * Copyright 1998 by Concurrent Computer Corporation
 *
 * Permission to use, copy, modify, distribute, and sell this software
 * and its documentation for any purpose is hereby granted without fee,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation, and that the name of Concurrent Computer
 * Corporation not be used in advertising or publicity pertaining to
 * distribution of the software without specific, written prior
 * permission.  Concurrent Computer Corporation makes no representations
 * about the suitability of this software for any purpose.  It is
 * provided "as is" without express or implied warranty.
 *
 * CONCURRENT COMPUTER CORPORATION DISCLAIMS ALL WARRANTIES WITH REGARD
 * TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS, IN NO EVENT SHALL CONCURRENT COMPUTER CORPORATION BE
 * LIABLE FOR ANY SPECIAL, 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.
 *
 * Copyright 1998 by Metro Link Incorporated
 *
 * Permission to use, copy, modify, distribute, and sell this software
 * and its documentation for any purpose is hereby granted without fee,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation, and that the name of Metro Link
 * Incorporated not be used in advertising or publicity pertaining to
 * distribution of the software without specific, written prior
 * permission.  Metro Link Incorporated makes no representations
 * about the suitability of this software for any purpose.  It is
 * provided "as is" without express or implied warranty.
 *
 * METRO LINK INCORPORATED DISCLAIMS ALL WARRANTIES WITH REGARD
 * TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS, IN NO EVENT SHALL METRO LINK INCORPORATED BE
 * LIABLE FOR ANY SPECIAL, 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.
 */

#ifdef HAVE_XORG_CONFIG_H
#include <xorg-config.h>
#endif

#include <stdio.h>
#include "compiler.h"
#include "xf86.h"
#include "xf86Priv.h"
#include "xf86_OSlib.h"
#include "Pci.h"
#include <dirent.h>

/*
 * linux platform specific PCI access functions -- using /proc/bus/pci
 * needs kernel version 2.2.x
 */
static CARD32 linuxPciCfgRead(PCITAG tag, int off);
static void linuxPciCfgWrite(PCITAG, int off, CARD32 val);
static void linuxPciCfgSetBits(PCITAG tag, int off, CARD32 mask, CARD32 bits);
static ADDRESS linuxTransAddrBusToHost(PCITAG tag, PciAddrType type, ADDRESS addr);
#if defined(__powerpc__)
static ADDRESS linuxPpcBusAddrToHostAddr(PCITAG, PciAddrType, ADDRESS);
static ADDRESS linuxPpcHostAddrToBusAddr(PCITAG, PciAddrType, ADDRESS);
#endif

static CARD8 linuxPciCfgReadByte(PCITAG tag, int off);
static void linuxPciCfgWriteByte(PCITAG tag, int off, CARD8 val);
static CARD16 linuxPciCfgReadWord(PCITAG tag, int off);
static void linuxPciCfgWriteWord(PCITAG tag, int off, CARD16 val);
static int linuxPciHandleBIOS(PCITAG Tag, int basereg, unsigned char *buf, int len);
static Bool linuxDomainSupport(void);

static pciBusFuncs_t linuxFuncs0 = {
/* pciReadLong      */	linuxPciCfgRead,
/* pciWriteLong     */	linuxPciCfgWrite,
/* pciSetBitsLong   */	linuxPciCfgSetBits,
#if defined(__powerpc__)
/* pciAddrHostToBus */	linuxPpcHostAddrToBusAddr,
/* pciAddrBusToHost */	linuxPpcBusAddrToHostAddr,
#else
/* pciAddrHostToBus */	pciAddrNOOP,
/* linuxTransAddrBusToHost is busted on sparc64 but the PCI rework tree
 * makes it all moot, so we kludge it for now */
#if defined(__sparc__)
/* pciAddrBusToHost */  pciAddrNOOP,
#else
/* pciAddrBusToHost */	linuxTransAddrBusToHost,
#endif /* __sparc64__ */
#endif

/* pciControlBridge */		NULL,
/* pciGetBridgeBuses */		NULL,
/* pciGetBridgeResources */	NULL,

/* pciReadByte */	linuxPciCfgReadByte,
/* pciWriteByte */	linuxPciCfgWriteByte,

/* pciReadWord */	linuxPciCfgReadWord,
/* pciWriteWord */	linuxPciCfgWriteWord,
};

static pciBusInfo_t linuxPci0 = {
/* configMech  */	PCI_CFG_MECH_OTHER,
/* numDevices  */	32,
/* secondary   */	FALSE,
/* primary_bus */	0,
/* funcs       */	&linuxFuncs0,
/* pciBusPriv  */	NULL,
/* bridge      */	NULL
};

/* from lnx_pci.c. */
extern int lnxPciInit(void);

static Bool	domain_support = FALSE;

void
linuxPciInit()
{
	struct stat st;
	if ((xf86Info.pciFlags == PCIForceNone) ||
	    (-1 == stat("/proc/bus/pci", &st))) {
		/* when using this as default for all linux architectures,
		   we'll need a fallback for 2.0 kernels here */
		return;
	}
#ifndef INCLUDE_XF86_NO_DOMAIN
	domain_support = linuxDomainSupport();
#endif
	pciNumBuses    = 1;
	pciBusInfo[0]  = &linuxPci0;
	pciFindFirstFP = pciGenFindFirst;
	pciFindNextFP  = pciGenFindNext;
	pciSetOSBIOSPtr(linuxPciHandleBIOS);
        xf86MaxPciDevs = lnxPciInit();
}

static int
linuxPciOpenFile(PCITAG tag, Bool write)
{
	static int	ldomain, lbus,ldev,lfunc,fd = -1,is_write = 0;
	int		domain, bus, dev, func;
	char		file[64];
	struct stat	ignored;
	static int is26 = -1;

	domain = PCI_DOM_FROM_TAG(tag);
	bus  = PCI_BUS_NO_DOMAIN(PCI_BUS_FROM_TAG(tag));
	dev  = PCI_DEV_FROM_TAG(tag);
	func = PCI_FUNC_FROM_TAG(tag);
	if (is26 == -1) {
		if (stat("/sys/bus/pci",&ignored) < 0)
			is26 = 0;
		else
			is26 = 1;
	}
	
	if (!domain_support && domain > 0)
	    return -1;

	if (fd == -1 || (write && (!is_write)) || domain != ldomain
	    || bus != lbus || dev != ldev || func != lfunc) {
		if (fd != -1) {
			close(fd);
			fd = -1;
		}
		if (is26)
			sprintf(file,"/sys/bus/pci/devices/%04x:%02x:%02x.%01x/config",
				domain, bus, dev, func);
		else {
			if (bus < 256) {
				sprintf(file, "/proc/bus/pci/%04x:%02x", domain, bus);
				if (stat(file, &ignored) < 0) {
					if (domain == 0) 
						sprintf(file, "/proc/bus/pci/%02x/%02x.%1x",
							bus, dev, func);
					else
						goto bail;
				} else
					sprintf(file, "/proc/bus/pci/%04x:%02x/%02x.%1x",
						domain, bus, dev, func);
			} else {
				sprintf(file, "/proc/bus/pci/%04x:%04x", domain, bus);
				if (stat(file, &ignored) < 0) {
					if (domain == 0)
						sprintf(file, "/proc/bus/pci/%04x/%02x.%1x",
							bus, dev, func);
					else
						goto bail;
				} else
					sprintf(file, "/proc/bus/pci/%04x:%04x/%02x.%1x",
						domain, bus, dev, func);
			}
		}
		if (write) {
		    fd = open(file,O_RDWR);
		    if (fd != -1) is_write = TRUE;
		} else switch (is_write) {
			case TRUE:
			    fd = open(file,O_RDWR);
			    if (fd > -1)
				break;
			default:
			    fd = open(file,O_RDONLY);
			    is_write = FALSE;
		}
	bail:
		ldomain = domain;
		lbus  = bus;
		ldev  = dev;
		lfunc = func;
	}
	return fd;
}

static CARD32
linuxPciCfgRead(PCITAG tag, int off)
{
	int	fd;
	CARD32	val = 0xffffffff;

	if (-1 != (fd = linuxPciOpenFile(tag,FALSE))) {
		lseek(fd,off,SEEK_SET);
		read(fd,&val,4);
	}
	return PCI_CPU(val);
}

static void
linuxPciCfgWrite(PCITAG tag, int off, CARD32 val)
{
	int	fd;

	if (-1 != (fd = linuxPciOpenFile(tag,TRUE))) {
		lseek(fd,off,SEEK_SET);
		val = PCI_CPU(val);
		write(fd,&val,4);
	}
}

static void
linuxPciCfgSetBits(PCITAG tag, int off, CARD32 mask, CARD32 bits)
{
	int	fd;
	CARD32	val = 0xffffffff;

	if (-1 != (fd = linuxPciOpenFile(tag,TRUE))) {
		lseek(fd,off,SEEK_SET);
		read(fd,&val,4);
		val = PCI_CPU(val);
		val = (val & ~mask) | (bits & mask);
		val = PCI_CPU(val);
		lseek(fd,off,SEEK_SET);
		write(fd,&val,4);
	}
}

/*
 * This function will convert a BAR address into a host address
 * suitable for passing into the mmap function of a /proc/bus
 * device.
 */
ADDRESS linuxTransAddrBusToHost(PCITAG tag, PciAddrType type, ADDRESS addr)
{
    ADDRESS ret = xf86GetOSOffsetFromPCI(tag, PCI_MEM|PCI_IO, addr);

    if (ret)
	return ret;

    /*
     * if it is not a BAR address, it must be legacy, (or wrong)
     * return it as is..
     */
    return addr;
}


#if defined(__powerpc__)

#ifndef __NR_pciconfig_iobase
#define __NR_pciconfig_iobase   200
#endif

static ADDRESS
linuxPpcBusAddrToHostAddr(PCITAG tag, PciAddrType type, ADDRESS addr)
{
    if (type == PCI_MEM)
    {
	ADDRESS membase = syscall(__NR_pciconfig_iobase, 1,
		    PCI_BUS_FROM_TAG(tag), PCI_DFN_FROM_TAG(tag));
	return (addr + membase);
    }
    else if (type == PCI_IO)
    {
	ADDRESS iobase = syscall(__NR_pciconfig_iobase, 2,
		    PCI_BUS_FROM_TAG(tag), PCI_DFN_FROM_TAG(tag));
	return (addr + iobase);
    }
    else return addr;
}

static ADDRESS
linuxPpcHostAddrToBusAddr(PCITAG tag, PciAddrType type, ADDRESS addr)
{
    if (type == PCI_MEM)
    {
	ADDRESS membase = syscall(__NR_pciconfig_iobase, 1,
		    PCI_BUS_FROM_TAG(tag), PCI_DFN_FROM_TAG(tag));
	return (addr - membase);
    }
    else if (type == PCI_IO)
    {
	ADDRESS iobase = syscall(__NR_pciconfig_iobase, 2,
		    PCI_BUS_FROM_TAG(tag), PCI_DFN_FROM_TAG(tag));
	return (addr - iobase);
    }
    else return addr;
}

#endif /* __powerpc__ */

static CARD8
linuxPciCfgReadByte(PCITAG tag, int off)
{
	int	fd;
	CARD8	val = 0xff;

	if (-1 != (fd = linuxPciOpenFile(tag,FALSE))) {
		lseek(fd,off,SEEK_SET);
		read(fd,&val,1);
	}

	return val;
}

static void
linuxPciCfgWriteByte(PCITAG tag, int off, CARD8 val)
{
	int	fd;

	if (-1 != (fd = linuxPciOpenFile(tag,TRUE))) {
		lseek(fd,off,SEEK_SET);
		write(fd, &val, 1);
	}
}

static CARD16
linuxPciCfgReadWord(PCITAG tag, int off)
{
	int	fd;
	CARD16	val = 0xff;

	if (-1 != (fd = linuxPciOpenFile(tag,FALSE))) {
		lseek(fd, off, SEEK_SET);
		read(fd, &val, 2);
	}

	return PCI_CPU16(val);
}

static void
linuxPciCfgWriteWord(PCITAG tag, int off, CARD16 val)
{
	int	fd;

	if (-1 != (fd = linuxPciOpenFile(tag,TRUE))) {
		lseek(fd, off, SEEK_SET);
		val = PCI_CPU16(val);
		write(fd, &val, 2);
	}
}

#ifndef INCLUDE_XF86_NO_DOMAIN

/*
 * Compiling the following simply requires the presence of <linux/pci.c>.
 * Actually running this is another matter altogether...
 *
 * This scheme requires that the kernel allow mmap()'ing of a host bridge's I/O
 * and memory spaces through its /proc/bus/pci/BUS/DFN entry.  Which one is
 * determined by a prior ioctl().
 *
 * For the sparc64 port, this means 2.4.12 or later.  For ppc, this
 * functionality is almost, but not quite there yet.  Alpha and other kernel
 * ports to multi-domain architectures still need to implement this.
 *
 * This scheme is also predicated on the use of an IOADDRESS compatible type to
 * designate I/O addresses.  Although IOADDRESS is defined as an unsigned
 * integral type, it is actually the virtual address of, i.e. a pointer to, the
 * I/O port to access.  And so, the inX/outX macros in "compiler.h" need to be
 * #define'd appropriately (as is done on SPARC's).
 *
 * Another requirement to port this scheme to another multi-domain architecture
 * is to add the appropriate entries in the pciControllerSizes array below.
 *
 * TO DO:  Address the deleterious reaction some host bridges have to master
 *         aborts.  This is already done for secondary PCI buses, but not yet
 *         for accesses to primary buses (except for the SPARC port, where
 *         master aborts are avoided during PCI scans).
 */

#include <linux/pci.h>

#ifndef PCIIOC_BASE		/* Ioctls for /proc/bus/pci/X/Y nodes. */
#define PCIIOC_BASE		('P' << 24 | 'C' << 16 | 'I' << 8)

/* Get controller for PCI device. */
#define PCIIOC_CONTROLLER	(PCIIOC_BASE | 0x00)
/* Set mmap state to I/O space. */
#define PCIIOC_MMAP_IS_IO	(PCIIOC_BASE | 0x01)
/* Set mmap state to MEM space. */
#define PCIIOC_MMAP_IS_MEM	(PCIIOC_BASE | 0x02)
/* Enable/disable write-combining. */
#define PCIIOC_WRITE_COMBINE	(PCIIOC_BASE | 0x03)

#endif

/* This probably shouldn't be Linux-specific */
static pciConfigPtr
xf86GetPciHostConfigFromTag(PCITAG Tag)
{
    int bus = PCI_BUS_FROM_TAG(Tag);
    pciBusInfo_t *pBusInfo;

    while ((bus < pciNumBuses) && (pBusInfo = pciBusInfo[bus])) {
	if (bus == pBusInfo->primary_bus)
	    return pBusInfo->bridge;
	bus = pBusInfo->primary_bus;
    }

    return NULL;	/* Bad data */
}

/*
 * This is ugly, but until I can extract this information from the kernel,
 * it'll have to do.  The default I/O space size is 64K, and 4G for memory.
 * Anything else needs to go in this table.  (PowerPC folk take note.)
 *
 * Note that Linux/SPARC userland is 32-bit, so 4G overflows to zero here.
 *
 * Please keep this table in ascending vendor/device order.
 */
static const struct pciSizes {
    unsigned short vendor, device;
    unsigned long io_size, mem_size;
} pciControllerSizes[] = {
    {
	PCI_VENDOR_SUN, PCI_CHIP_PSYCHO,
	1U << 16, 1U << 31
    },
    {
	PCI_VENDOR_SUN, PCI_CHIP_SCHIZO,
	1U << 24, 1U << 31	/* ??? */
    },
    {
	PCI_VENDOR_SUN, PCI_CHIP_SABRE,
	1U << 24, (unsigned long)(1ULL << 32)
    },
    {
	PCI_VENDOR_SUN, PCI_CHIP_HUMMINGBIRD,
	1U << 24, (unsigned long)(1ULL << 32)
    }
};
#define NUM_SIZES (sizeof(pciControllerSizes) / sizeof(pciControllerSizes[0]))

static const struct pciSizes *
linuxGetSizesStruct(PCITAG Tag)
{
    static const struct pciSizes default_size = {
	0, 0, 1U << 16, (unsigned long)(1ULL << 32)
    };
    pciConfigPtr pPCI;
    int          i;

    /* Find host bridge */
    if ((pPCI = xf86GetPciHostConfigFromTag(Tag))) {
	/* Look up vendor/device */
	for (i = 0;  i < NUM_SIZES;  i++) {
	    if ((pPCI->pci_vendor == pciControllerSizes[i].vendor)
		&& (pPCI->pci_device == pciControllerSizes[i].device)) {
		return & pciControllerSizes[i];
	    }
	}
    }

    /* Default to 64KB I/O and 4GB memory. */
    return & default_size;
}

static __inline__ unsigned long
linuxGetIOSize(PCITAG Tag)
{
    const struct pciSizes * const sizes = linuxGetSizesStruct(Tag);
    return sizes->io_size;
}

static __inline__ void
linuxGetSizes(PCITAG Tag, unsigned long *io_size, unsigned long *mem_size)
{
    const struct pciSizes * const sizes = linuxGetSizesStruct(Tag);

    *io_size  = sizes->io_size;
    *mem_size = sizes->mem_size;
}

static Bool
linuxDomainSupport(void)
{
    DIR *dir;
    struct dirent *dirent;
    char *end;

    if (!(dir = opendir("/proc/bus/pci")))
       return FALSE;
    while (1) {
	if (!(dirent = readdir(dir)))
	    return FALSE;
	strtol(dirent->d_name,&end,16);
	/* entry of the form xx or xxxx : x=[0..f] no domain */
	if (*end == '\0')
	    return FALSE;
	else if (*end == ':') {
	    /* ':' found immediately after: verify for xxxx:xx or xxxx:xxxx */
	    strtol(end + 1,&end,16);
	    if (*end == '\0')
		return TRUE;
	}
    }
    return FALSE;
} 

_X_EXPORT int
xf86GetPciDomain(PCITAG Tag)
{
    pciConfigPtr pPCI;
    int fd, result;

    pPCI = xf86GetPciHostConfigFromTag(Tag);

    if (pPCI && (result = PCI_DOM_FROM_BUS(pPCI->busnum)))
	return result + 1;

    if (!pPCI || pPCI->fakeDevice)
	return 1;		/* Domain 0 is reserved */

    if ((fd = linuxPciOpenFile(pPCI ? pPCI->tag : 0,FALSE)) < 0)
	return 0;

    if ((result = ioctl(fd, PCIIOC_CONTROLLER, 0)) < 0)
	return 0;

    return result + 1;		/* Domain 0 is reserved */
}

static pointer
linuxMapPci(int ScreenNum, int Flags, PCITAG Tag,
	    ADDRESS Base, unsigned long Size, int mmap_ioctl)
{
    do {
	pciConfigPtr pPCI;
	unsigned char *result;
	ADDRESS realBase, Offset;
	int fd, mmapflags, prot;

	xf86InitVidMem();

       prot = ((Flags & VIDMEM_READONLY) == 0);
       if (((fd = linuxPciOpenFile(Tag, prot)) < 0) ||
	    (ioctl(fd, mmap_ioctl, 0) < 0))
	    break;

/* Note:  IA-64 doesn't compile this and doesn't need to */
#ifdef __ia64__

# ifndef  MAP_WRITECOMBINED
#  define MAP_WRITECOMBINED 0x00010000
# endif
# ifndef  MAP_NONCACHED
#  define MAP_NONCACHED     0x00020000
# endif

	if (Flags & VIDMEM_FRAMEBUFFER)
	    mmapflags = MAP_SHARED | MAP_WRITECOMBINED;
	else
	    mmapflags = MAP_SHARED | MAP_NONCACHED;

#else /* !__ia64__ */

	mmapflags = (Flags & VIDMEM_FRAMEBUFFER) / VIDMEM_FRAMEBUFFER;

	if (ioctl(fd, PCIIOC_WRITE_COMBINE, mmapflags) < 0)
	    break;

	mmapflags = MAP_SHARED;

#endif /* ?__ia64__ */

	/* Align to page boundary */
	realBase = Base & ~(getpagesize() - 1);
	Offset = Base - realBase;

	if (Flags & VIDMEM_READONLY)
	    prot = PROT_READ;
	else
	    prot = PROT_READ | PROT_WRITE;

	result = mmap(NULL, Size + Offset, prot, mmapflags, fd, realBase);

	if (!result || ((pointer)result == MAP_FAILED))
	    return NULL;

	xf86MakeNewMapping(ScreenNum, Flags, realBase, Size + Offset, result);

	return result + Offset;
    } while (0);

    if (mmap_ioctl == PCIIOC_MMAP_IS_MEM)
	return xf86MapVidMem(ScreenNum, Flags, Base, Size);

    return NULL;
}

#define MAX_DOMAINS 257
static pointer DomainMmappedIO[MAX_DOMAINS];

static int
linuxOpenLegacy(PCITAG Tag, char *name)
{
#define PREFIX "/sys/class/pci_bus/%04x:%02x/%s"
    char *path;
    int domain, bus;
    pciBusInfo_t *pBusInfo;
    pciConfigPtr bridge = NULL;
    int fd;

    path = xalloc(strlen(PREFIX) + strlen(name));
    if (!path)
	return -1;

    for (;;) {
	domain = xf86GetPciDomain(Tag);
	bus = PCI_BUS_NO_DOMAIN(PCI_BUS_FROM_TAG(Tag));

	/* Domain 0 is reserved -- see xf86GetPciDomain() */
	if ((domain <= 0) || (domain >= MAX_DOMAINS))
	    FatalError("linuxOpenLegacy():  domain out of range\n");

	sprintf(path, PREFIX, domain - 1, bus, name);
	fd = open(path, O_RDWR);
	if (fd >= 0) {
	    xfree(path);
	    return fd;
	}

	pBusInfo = pciBusInfo[PCI_BUS_FROM_TAG(Tag)];
	if (!pBusInfo || (bridge == pBusInfo->bridge) ||
		!(bridge = pBusInfo->bridge)) {
	    xfree(path);
	    return -1;
	}

	Tag = bridge->tag;
    }

    xfree(path);
    return fd;
}

/*
 * xf86MapDomainMemory - memory map PCI domain memory
 *
 * This routine maps the memory region in the domain specified by Tag and
 * returns a pointer to it.  The pointer is saved for future use if it's in
 * the legacy ISA memory space (memory in a domain between 0 and 1MB).
 */
_X_EXPORT pointer
xf86MapDomainMemory(int ScreenNum, int Flags, PCITAG Tag,
		    ADDRESS Base, unsigned long Size)
{
    int domain = xf86GetPciDomain(Tag);
    int fd = -1;
    pointer addr;

    /*
     * We use /proc/bus/pci on non-legacy addresses or if the Linux sysfs
     * legacy_mem interface is unavailable.
     */
    if (Base >= 1024*1024)
	addr = linuxMapPci(ScreenNum, Flags, Tag, Base, Size,
			   PCIIOC_MMAP_IS_MEM);
    else if ((fd = linuxOpenLegacy(Tag, "legacy_mem")) < 0)
	addr = linuxMapPci(ScreenNum, Flags, Tag, Base, Size,
			   PCIIOC_MMAP_IS_MEM);
    else
	addr = mmap(NULL, Size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, Base);

    if (fd >= 0)
	close(fd);
    if (addr == NULL || addr == MAP_FAILED) {
	perror("mmap failure");
	FatalError("xf86MapDomainMem():  mmap() failure\n");
    }
    return addr;
}

/*
 * xf86MapDomainIO - map I/O space in this domain
 *
 * Each domain has a legacy ISA I/O space.  This routine will try to
 * map it using the Linux sysfs legacy_io interface.  If that fails,
 * it'll fall back to using /proc/bus/pci.
 *
 * If the legacy_io interface *does* exist, the file descriptor (fd below)
 * will be saved in the DomainMmappedIO array in the upper bits of the
 * pointer.  Callers will do I/O with small port numbers (<64k values), so
 * the platform I/O code can extract the port number and the fd, lseek to
 * the port number in the legacy_io file, and issue the read or write.
 *
 * This has no means of returning failure, so all errors are fatal
 */
_X_EXPORT IOADDRESS
xf86MapDomainIO(int ScreenNum, int Flags, PCITAG Tag,
		IOADDRESS Base, unsigned long Size)
{
    int domain = xf86GetPciDomain(Tag);
    int fd;

    if ((domain <= 0) || (domain >= MAX_DOMAINS))
	FatalError("xf86MapDomainIO():  domain out of range\n");

    if (DomainMmappedIO[domain])
	return (IOADDRESS)DomainMmappedIO[domain] + Base;

    /* Permanently map all of I/O space */
    if ((fd = linuxOpenLegacy(Tag, "legacy_io")) < 0) {
	    DomainMmappedIO[domain] = linuxMapPci(ScreenNum, Flags, Tag,
						  0, linuxGetIOSize(Tag),
						  PCIIOC_MMAP_IS_IO);
	    /* ia64 can't mmap legacy IO port space */
	    if (!DomainMmappedIO[domain])
		return Base;
    }
    else { /* legacy_io file exists, encode fd */
	DomainMmappedIO[domain] = (pointer)(fd << 24);
    }

    return (IOADDRESS)DomainMmappedIO[domain] + Base;
}

/*
 * xf86ReadDomainMemory - copy from domain memory into a caller supplied buffer
 */
_X_EXPORT int
xf86ReadDomainMemory(PCITAG Tag, ADDRESS Base, int Len, unsigned char *Buf)
{
    unsigned char *ptr, *src;
    ADDRESS offset;
    unsigned long size;
    int len, pagemask = getpagesize() - 1;

    unsigned int i, dom, bus, dev, func;
    unsigned int fd;
    char file[256];
    struct stat st;

    dom  = PCI_DOM_FROM_TAG(Tag);
    bus  = PCI_BUS_NO_DOMAIN(PCI_BUS_FROM_TAG(Tag));
    dev  = PCI_DEV_FROM_TAG(Tag);
    func = PCI_FUNC_FROM_TAG(Tag);
    sprintf(file, "/sys/bus/pci/devices/%04x:%02x:%02x.%1x/rom",
	    dom, bus, dev, func);

    /*
     * If the caller wants the ROM and the sysfs rom interface exists,
     * try to use it instead of reading it from /proc/bus/pci.
     */
    if (((Base & 0xfffff) == 0xC0000) && (stat(file, &st) == 0)) {
        if ((fd = open(file, O_RDWR)))
            Base = 0x0;

	/* enable the ROM first */
	write(fd, "1", 2);
	lseek(fd, 0, SEEK_SET);

    len = min(Len, st.st_size);

        /* copy the ROM until we hit Len, EOF or read error */
        for (; len && (size = read(fd, Buf, len)) > 0 ; Buf+=size, len-=size)
            ;

	write(fd, "0", 2);
	close(fd);

	return Len;
    }

    /* Ensure page boundaries */
    offset = Base & ~pagemask;
    size = ((Base + Len + pagemask) & ~pagemask) - offset;

    ptr = xf86MapDomainMemory(-1, VIDMEM_READONLY, Tag, offset, size);

    if (!ptr)
	return -1;

    /* Using memcpy() here can hang the system */
    src = ptr + (Base - offset);
    for (len = Len;  len-- > 0;)
	*Buf++ = *src++;

    xf86UnMapVidMem(-1, ptr, size);

    return Len;
}

resPtr
xf86BusAccWindowsFromOS(void)
{
    pciConfigPtr  *ppPCI, pPCI;
    resPtr        pRes = NULL;
    resRange      range;
    unsigned long io_size, mem_size;
    int           domain;

    if ((ppPCI = xf86scanpci(0))) {
	for (;  (pPCI = *ppPCI);  ppPCI++) {
	    if ((pPCI->pci_base_class != PCI_CLASS_BRIDGE) ||
		(pPCI->pci_sub_class  != PCI_SUBCLASS_BRIDGE_HOST))
		continue;

	    domain = xf86GetPciDomain(pPCI->tag);
	    linuxGetSizes(pPCI->tag, &io_size, &mem_size);

	    RANGE(range, 0, (ADDRESS)(mem_size - 1),
		  RANGE_TYPE(ResExcMemBlock, domain));
	    pRes = xf86AddResToList(pRes, &range, -1);

	    RANGE(range, 0, (IOADDRESS)(io_size - 1),
		  RANGE_TYPE(ResExcIoBlock, domain));
	    pRes = xf86AddResToList(pRes, &range, -1);

	    if (domain <= 0)
		break;
	}
    }

    return pRes;
}

resPtr
xf86PciBusAccWindowsFromOS(void)
{
    pciConfigPtr  *ppPCI, pPCI;
    resPtr        pRes = NULL;
    resRange      range;
    unsigned long io_size, mem_size;
    int           domain;

    if ((ppPCI = xf86scanpci(0))) {
	for (;  (pPCI = *ppPCI);  ppPCI++) {
	    if ((pPCI->pci_base_class != PCI_CLASS_BRIDGE) ||
		(pPCI->pci_sub_class  != PCI_SUBCLASS_BRIDGE_HOST))
		continue;

	    domain = xf86GetPciDomain(pPCI->tag);
	    linuxGetSizes(pPCI->tag, &io_size, &mem_size);

	    RANGE(range, 0, (ADDRESS)(mem_size - 1),
		  RANGE_TYPE(ResExcMemBlock, domain));
	    pRes = xf86AddResToList(pRes, &range, -1);

	    RANGE(range, 0, (IOADDRESS)(io_size - 1),
		  RANGE_TYPE(ResExcIoBlock, domain));
	    pRes = xf86AddResToList(pRes, &range, -1);

	    if (domain <= 0)
		break;
	}
    }

    return pRes;
}


resPtr
xf86AccResFromOS(resPtr pRes)
{
    pciConfigPtr  *ppPCI, pPCI;
    resRange      range;
    unsigned long io_size, mem_size;
    int           domain;

    if ((ppPCI = xf86scanpci(0))) {
	for (;  (pPCI = *ppPCI);  ppPCI++) {
	    if ((pPCI->pci_base_class != PCI_CLASS_BRIDGE) ||
		(pPCI->pci_sub_class  != PCI_SUBCLASS_BRIDGE_HOST))
		continue;

	    domain = xf86GetPciDomain(pPCI->tag);
	    linuxGetSizes(pPCI->tag, &io_size, &mem_size);

	    /*
	     * At minimum, the top and bottom resources must be claimed, so
	     * that resources that are (or appear to be) unallocated can be
	     * relocated.
	     */
	    RANGE(range, 0x00000000u, 0x0009ffffu,
		  RANGE_TYPE(ResExcMemBlock, domain));
	    pRes = xf86AddResToList(pRes, &range, -1);
	    RANGE(range, 0x000c0000u, 0x000effffu,
		  RANGE_TYPE(ResExcMemBlock, domain));
	    pRes = xf86AddResToList(pRes, &range, -1);
	    RANGE(range, 0x000f0000u, 0x000fffffu,
		  RANGE_TYPE(ResExcMemBlock, domain));
	    pRes = xf86AddResToList(pRes, &range, -1);

	    RANGE(range, (ADDRESS)(mem_size - 1), (ADDRESS)(mem_size - 1),
		  RANGE_TYPE(ResExcMemBlock, domain));
	    pRes = xf86AddResToList(pRes, &range, -1);

	    RANGE(range, 0x00000000u, 0x00000000u,
		  RANGE_TYPE(ResExcIoBlock, domain));
	    pRes = xf86AddResToList(pRes, &range, -1);
	    RANGE(range, (IOADDRESS)(io_size - 1), (IOADDRESS)(io_size - 1),
		  RANGE_TYPE(ResExcIoBlock, domain));
	    pRes = xf86AddResToList(pRes, &range, -1);

	    if (domain <= 0)
		break;
	}
    }

    return pRes;
}

#endif /* !INCLUDE_XF86_NO_DOMAIN */

int linuxPciHandleBIOS(PCITAG Tag, int basereg, unsigned char *buf, int len)
{
  unsigned int dom, bus, dev, func;
  unsigned int fd;
  char file[256];
  struct stat st;
  int ret;
  int sofar = 0;

  dom  = PCI_DOM_FROM_TAG(Tag);
  bus  = PCI_BUS_NO_DOMAIN(PCI_BUS_FROM_TAG(Tag));
  dev  = PCI_DEV_FROM_TAG(Tag);
  func = PCI_FUNC_FROM_TAG(Tag);
  sprintf(file, "/sys/bus/pci/devices/%04x:%02x:%02x.%1x/rom",
	  dom, bus, dev, func);

  if (stat(file, &st) == 0)
  {
    if ((fd = open(file, O_RDWR)))
      basereg = 0x0;
    
    /* enable the ROM first */
    write(fd, "1", 2);
    lseek(fd, 0, SEEK_SET);
    do {
        /* copy the ROM until we hit Len, EOF or read error */
    	ret = read(fd, buf+sofar, len-sofar);
    	if (ret <= 0)
		break;
	sofar += ret;
    } while (sofar < len);
    
    write(fd, "0", 2);
    close(fd);
    if (sofar < len)
    	xf86MsgVerb(X_INFO, 3, "Attempted to read BIOS %dKB from %s: got %dKB\n", len/1024, file, sofar/1024);
    return sofar;
  }
  return 0;
}

#ifdef __ia64__
static PCITAG ia64linuxPciFindFirst(void);
static PCITAG ia64linuxPciFindNext(void);

void   
ia64linuxPciInit()
{
    struct stat st;

    linuxPciInit();
	   
    if (!stat("/proc/sgi_sn/licenseID", &st) && pciNumBuses) {
       /* Be a little paranoid here and only use this code for Altix systems.
	* It is generic, so it should work on any system, but depends on
	* /proc/bus/pci entries for each domain/bus combination. Altix is
	* guaranteed a recent enough kernel to have them.
	*/
       pciFindFirstFP = ia64linuxPciFindFirst;
       pciFindNextFP  = ia64linuxPciFindNext;
    }
}

static DIR *busdomdir;
static DIR *devdir;
	       
static PCITAG
ia64linuxPciFindFirst(void)
{   
       busdomdir = opendir("/proc/bus/pci");
       devdir = NULL;

       return ia64linuxPciFindNext();
}   

static struct dirent *getnextbus(int *domain, int *bus)
{
    struct dirent *entry;
    int dombus;

    for (;;) {
	entry = readdir(busdomdir);
	if (entry == NULL) {
	    *domain = 0;
	    *bus = 0;
	    closedir(busdomdir);
	    return NULL;
	}
	if (sscanf(entry->d_name, "%04x:%02x", domain, bus) != 2)
	    continue;
	dombus = PCI_MAKE_BUS(*domain, *bus);

	if (pciNumBuses <= dombus)
	    pciNumBuses = dombus + 1;
	if (!pciBusInfo[dombus]) {
	    pciBusInfo[dombus] = xnfalloc(sizeof(pciBusInfo_t));
	    *pciBusInfo[dombus] = *pciBusInfo[0];
	}

	return entry;
    }
}

static PCITAG
ia64linuxPciFindNext(void)
{
    struct dirent *entry;
    char file[40];
    static int bus, dev, func, domain;
    PCITAG pciDeviceTag;
    CARD32 devid;

    for (;;) {
	if (devdir == NULL) {
	    entry = getnextbus(&domain, &bus);
	    if (!entry)
		return PCI_NOT_FOUND;
	    snprintf(file, 40, "/proc/bus/pci/%s", entry->d_name);
	    devdir = opendir(file);
	    if (!devdir)
		return PCI_NOT_FOUND;

	}

	entry = readdir(devdir);

	if (entry == NULL) {
	    closedir(devdir);
	    devdir = NULL;
	    continue;
	}

	if (sscanf(entry->d_name, "%02x . %01x", &dev, &func) == 2) {
	    CARD32 tmp;
	    int sec_bus, pri_bus;
	    unsigned char base_class, sub_class;

	    int pciBusNum = PCI_MAKE_BUS(domain, bus);
	    pciDeviceTag = PCI_MAKE_TAG(pciBusNum, dev, func);

	    /*
	     * Before checking for a specific devid, look for enabled
	     * PCI to PCI bridge devices.  If one is found, create and
	     * initialize a bus info record (if one does not already exist).
	     */
	    tmp = pciReadLong(pciDeviceTag, PCI_CLASS_REG);
	    base_class = PCI_CLASS_EXTRACT(tmp);
	    sub_class = PCI_SUBCLASS_EXTRACT(tmp);
	    if ((base_class == PCI_CLASS_BRIDGE) &&
		((sub_class == PCI_SUBCLASS_BRIDGE_PCI) ||
		 (sub_class == PCI_SUBCLASS_BRIDGE_CARDBUS))) {
		tmp = pciReadLong(pciDeviceTag, PCI_PCI_BRIDGE_BUS_REG);
		sec_bus = PCI_SECONDARY_BUS_EXTRACT(tmp, pciDeviceTag);
		pri_bus = PCI_PRIMARY_BUS_EXTRACT(tmp, pciDeviceTag);
#ifdef DEBUGPCI
		ErrorF("ia64linuxPciFindNext: pri_bus %d sec_bus %d\n",
		       pri_bus, sec_bus);
#endif
		if (pciBusNum != pri_bus) {
		    /* Some bridges do not implement the primary bus register */
		    if ((PCI_BUS_NO_DOMAIN(pri_bus) != 0) ||
			(sub_class != PCI_SUBCLASS_BRIDGE_CARDBUS))
			xf86Msg(X_WARNING,
				"ia64linuxPciFindNext:  primary bus mismatch on PCI"
				" bridge 0x%08lx (0x%02x, 0x%02x)\n",
				pciDeviceTag, pciBusNum, pri_bus);
		    pri_bus = pciBusNum;
	        }
		if ((pri_bus < sec_bus) && (sec_bus < pciMaxBusNum) &&
		    pciBusInfo[pri_bus]) {
		    /*
		     * Found a secondary PCI bus
		     */
		    if (!pciBusInfo[sec_bus]) {
			pciBusInfo[sec_bus] = xnfalloc(sizeof(pciBusInfo_t));

			/* Copy parents settings... */
			*pciBusInfo[sec_bus] = *pciBusInfo[pri_bus];
		    }

		    /* ...but not everything same as parent */
		    pciBusInfo[sec_bus]->primary_bus = pri_bus;
		    pciBusInfo[sec_bus]->secondary = TRUE;
		    pciBusInfo[sec_bus]->numDevices = 32;

		    if (pciNumBuses <= sec_bus)
			pciNumBuses = sec_bus + 1;
		}
	    }

	    devid = pciReadLong(pciDeviceTag, PCI_ID_REG);
	    if ((devid & pciDevidMask) == pciDevid)
		/* Yes - Return it.  Otherwise, next device */
		return pciDeviceTag;
	}
    }
}
#endif