summaryrefslogtreecommitdiff
path: root/hw/xfree86/os-support/bus/linuxPci.c
blob: fcfdbcb6757788cf57c8dedd44d83cc2c4ad298f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
/*
 * 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 "xf86_OSlib.h"
#include "Pci.h"

static const struct pci_id_match match_host_bridge = {
    PCI_MATCH_ANY, PCI_MATCH_ANY, PCI_MATCH_ANY, PCI_MATCH_ANY,
    (PCI_CLASS_BRIDGE << 16) | (PCI_SUBCLASS_BRIDGE_HOST << 8),
    0x0000ffff00, 0
};

#define MAX_DOMAINS 257
static pointer DomainMmappedIO[MAX_DOMAINS];

void
linuxPciInit(void)
{
    memset(DomainMmappedIO, 0, sizeof(DomainMmappedIO));
}

/**
 * \bug
 * The generation of the procfs file name for the domain != 0 case may not be 
 * correct.
 */
static int
linuxPciOpenFile(struct pci_device *dev, Bool write)
{
    static struct pci_device *last_dev = NULL;
    static int	fd = -1,is_write = 0;
    char		file[64];
    struct stat	ignored;
    static int is26 = -1;

    if (dev == NULL) {
	return -1;
    }

    if (is26 == -1) {
	is26 = (stat("/sys/bus/pci", &ignored) < 0) ? 0 : 1;
    }
	
    if (fd == -1 || (write && (!is_write)) || (last_dev != dev)) {
	if (fd != -1) {
	    close(fd);
	    fd = -1;
	}

	if (is26) {
	    sprintf(file,"/sys/bus/pci/devices/%04u:%02x:%02x.%01x/config",
		    dev->domain, dev->bus, dev->dev, dev->func);
	} else {
	    if (dev->domain == 0) {
		sprintf(file,"/proc/bus/pci/%02x", dev->bus);
		if (stat(file, &ignored) < 0) {
		    sprintf(file, "/proc/bus/pci/0000:%02x/%02x.%1x",
			    dev->bus, dev->dev, dev->func);
		} else {
		    sprintf(file, "/proc/bus/pci/%02x/%02x.%1x",
			    dev->bus, dev->dev, dev->func);
		}
	    } else {
		sprintf(file,"/proc/bus/pci/%02x%02x", dev->domain, dev->bus);
		if (stat(file, &ignored) < 0) {
		    sprintf(file, "/proc/bus/pci/%04x:%04x/%02x.%1x",
			    dev->domain, dev->bus, dev->dev, dev->func);
		} else {
		    sprintf(file, "/proc/bus/pci/%02x%02x/%02x.%1x",
			    dev->domain, dev->bus, dev->dev, 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;
	    }
	}

	last_dev = dev;
    }

    return fd;
}

/*
 * 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 struct pci_device *
get_parent_bridge(struct pci_device *dev)
{
    struct pci_id_match bridge_match = {
	PCI_MATCH_ANY, PCI_MATCH_ANY, PCI_MATCH_ANY, PCI_MATCH_ANY,
	(PCI_CLASS_BRIDGE << 16) | (PCI_SUBCLASS_BRIDGE_PCI << 8),
	0
    };
    struct pci_device *bridge;
    struct pci_device_iterator *iter;

    if (dev == NULL) {
	return NULL;
    }

    iter = pci_id_match_iterator_create(& bridge_match);
    if (iter == NULL) {
	return NULL;
    }

    while ((bridge = pci_device_next(iter)) != NULL) {
	if (bridge->domain == dev->domain) {
	    const struct pci_bridge_info *info = 
		pci_device_get_bridge_info(bridge);

	    if (info != NULL) {
		if (info->secondary_bus == dev->bus) {
		    break;
		}
	    }
	}
    }

    pci_iterator_destroy(iter);

    return bridge;
}

/*
 * 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(const struct pci_device *dev)
{
    static const struct pciSizes default_size = {
	0, 0, 1U << 16, (unsigned long)(1ULL << 32)
    };
    int          i;

    /* Look up vendor/device */
    if (dev != NULL) {
	for (i = 0;  i < NUM_SIZES;  i++) {
	    if ((dev->vendor_id == pciControllerSizes[i].vendor)
		&& (dev->device_id == pciControllerSizes[i].device)) {
		return & pciControllerSizes[i];
	    }
	}
    }

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

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

static pointer
linuxMapPci(int ScreenNum, int Flags, struct pci_device *dev,
	    ADDRESS Base, unsigned long Size, int mmap_ioctl)
{
    /* Align to page boundary */
    const ADDRESS realBase = Base & ~(getpagesize() - 1);
    const ADDRESS Offset = Base - realBase;

    do {
	unsigned char *result;
	int fd, mmapflags, prot;

	xf86InitVidMem();

	/* If dev is NULL, linuxPciOpenFile will return -1, and this routine
	 * will fail gracefully.
	 */
        prot = ((Flags & VIDMEM_READONLY) == 0);
        if (((fd = linuxPciOpenFile(dev, 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__ */


	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;
}

static int
linuxOpenLegacy(struct pci_device *dev, char *name)
{
    static const char PREFIX[] = "/sys/class/pci_bus/%04x:%02x/%s";
    char path[sizeof(PREFIX) + 10];
    int fd = -1;

    while (dev != NULL) {
	snprintf(path, sizeof(path) - 1, PREFIX, dev->domain, dev->bus, name);
	fd = open(path, O_RDWR);
	if (fd >= 0) {
	    return fd;
	}

	dev = get_parent_bridge(dev);
    }

    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).
 */
pointer
xf86MapDomainMemory(int ScreenNum, int Flags, struct pci_device *dev,
		    ADDRESS Base, unsigned long Size)
{
    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) || ((fd = linuxOpenLegacy(dev, "legacy_mem")) < 0))
	return linuxMapPci(ScreenNum, Flags, dev, 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;
}

/**
 * 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 \b does exist, the file descriptor (\c fd below)
 * will be saved in the \c 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 \c fd, \c 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
 */
IOADDRESS
xf86MapLegacyIO(struct pci_device *dev)
{
    const int domain = dev->domain;
    struct pci_device *bridge = get_parent_bridge(dev);
    int fd;

    if (domain >= MAX_DOMAINS)
	FatalError("xf86MapLegacyIO():  domain out of range\n");

    if (DomainMmappedIO[domain] == NULL) {
	/* Permanently map all of I/O space */
	fd = linuxOpenLegacy(bridge, "legacy_io");
	if (fd < 0) {
	    DomainMmappedIO[domain] = linuxMapPci(-1, VIDMEM_MMIO, bridge,
						  0, linuxGetIOSize(bridge),
						  PCIIOC_MMAP_IS_IO);
	}
	else { /* legacy_io file exists, encode fd */
	    DomainMmappedIO[domain] = (pointer)(intptr_t)(fd << 24);
	}
    }

    return (IOADDRESS)DomainMmappedIO[domain];
}