Files
openbios/drivers/pci.c
Stefan Reinauer 81cfd7c87b Remove obsolete email addresses
Signed-off-by: Stefan Reinauer <stefan.reinauer@coreboot.org>
2017-06-08 19:28:38 +02:00

1666 lines
45 KiB
C

/*
* OpenBIOS pci driver
*
* This driver is compliant to the
* PCI bus binding to IEEE 1275-1994 Rev 2.1
*
* (C) 2004 Stefan Reinauer
* (C) 2005 Ed Schouten <ed@fxq.nl>
*
* Some parts from OpenHackWare-0.4, Copyright (c) 2004-2005 Jocelyn Mayer
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2
*
*/
#include "config.h"
#include "libopenbios/bindings.h"
#include "libopenbios/ofmem.h"
#include "kernel/kernel.h"
#include "drivers/pci.h"
#include "libc/byteorder.h"
#include "libc/vsprintf.h"
#include "drivers/drivers.h"
#include "drivers/vga.h"
#include "packages/video.h"
#include "libopenbios/video.h"
#include "timer.h"
#include "pci.h"
#include "pci_database.h"
#ifdef CONFIG_DRIVER_MACIO
#include "cuda.h"
#include "macio.h"
#endif
#ifdef CONFIG_DRIVER_USB
#include "drivers/usb.h"
#endif
#if defined (CONFIG_DEBUG_PCI)
# define PCI_DPRINTF(format, ...) printk(format, ## __VA_ARGS__)
#else
# define PCI_DPRINTF(format, ...) do { } while (0)
#endif
#define set_bool_property(ph, name) set_property(ph, name, NULL, 0);
/* DECLARE data structures for the nodes. */
DECLARE_UNNAMED_NODE( ob_pci_bus_node, INSTALL_OPEN, 2*sizeof(int) );
DECLARE_UNNAMED_NODE( ob_pci_simple_node, 0, 2*sizeof(int) );
DECLARE_UNNAMED_NODE( ob_pci_empty_node, 0, 2*sizeof(int) );
const pci_arch_t *arch;
#define IS_NOT_RELOCATABLE 0x80000000
#define IS_PREFETCHABLE 0x40000000
#define IS_ALIASED 0x20000000
enum {
CONFIGURATION_SPACE = 0,
IO_SPACE = 1,
MEMORY_SPACE_32 = 2,
MEMORY_SPACE_64 = 3,
};
static int encode_int32_cells(int num_cells, u32 *prop, ucell val)
{
int i = 0;
/* hi ... lo */
for (i=0; i < num_cells; ++i) {
prop[num_cells - i - 1] = val;
val >>= 16;
val >>= 16;
}
return num_cells;
}
static inline int pci_encode_phys_addr(u32 *phys, int flags, int space_code,
pci_addr dev, uint8_t reg, uint64_t addr)
{
/* phys.hi */
phys[0] = flags | (space_code << 24) | dev | reg;
/* phys.mid */
phys[1] = addr >> 32;
/* phys.lo */
phys[2] = addr;
return 3;
}
static inline int pci_encode_size(u32 *prop, uint64_t size)
{
return encode_int32_cells(2, prop, size);
}
static int host_address_cells(void)
{
return get_int_property(find_dev("/"), "#address-cells", NULL);
}
static int host_encode_phys_addr(u32 *prop, ucell addr)
{
return encode_int32_cells(host_address_cells(), prop, addr);
}
static int host_size_cells(void)
{
return get_int_property(find_dev("/"), "#size-cells", NULL);
}
/*
static int parent_address_cells(void)
{
phandle_t parent_ph = ih_to_phandle(my_parent());
return get_int_property(parent_ph, "#address-cells", NULL);
}
static int parent_size_cells(void)
{
phandle_t parent_ph = ih_to_phandle(my_parent());
return get_int_property(parent_ph, "#size-cells", NULL);
}
*/
#if defined(CONFIG_DEBUG_PCI)
static void dump_reg_property(const char* description, int nreg, u32 *reg)
{
int i;
printk("%s reg", description);
for (i=0; i < nreg; ++i) {
printk(" %08X", reg[i]);
}
printk("\n");
}
#endif
static unsigned long pci_bus_addr_to_host_addr(int space, uint32_t ba)
{
if (space == IO_SPACE) {
return arch->io_base + (unsigned long)ba;
} else if (space == MEMORY_SPACE_32) {
return arch->host_pci_base + (unsigned long)ba;
} else {
/* Return unaltered to aid debugging property values */
return (unsigned long)ba;
}
}
static void
ob_pci_open(int *idx)
{
int ret=1;
RET ( -ret );
}
static void
ob_pci_close(int *idx)
{
}
static void
ob_pci_initialize(int *idx)
{
}
/* ( str len -- phys.lo phys.mid phys.hi ) */
static void
ob_pci_decode_unit(int *idx)
{
ucell hi, mid, lo;
const char *arg = pop_fstr_copy();
int dev, fn, reg, ss, n, p, t;
int bus = 0; /* no information */
char *ptr;
PCI_DPRINTF("ob_pci_decode_unit idx=%p\n", idx);
fn = 0;
reg = 0;
n = 0;
p = 0;
t = 0;
ptr = (char*)arg;
if (*ptr == 'n') {
n = IS_NOT_RELOCATABLE;
ptr++;
}
if (*ptr == 'i') {
ss = IO_SPACE;
ptr++;
if (*ptr == 't') {
t = IS_ALIASED;
ptr++;
}
/* DD,F,RR,NNNNNNNN */
dev = strtol(ptr, &ptr, 16);
ptr++;
fn = strtol(ptr, &ptr, 16);
ptr++;
reg = strtol(ptr, &ptr, 16);
ptr++;
lo = strtol(ptr, &ptr, 16);
mid = 0;
} else if (*ptr == 'm') {
ss = MEMORY_SPACE_32;
ptr++;
if (*ptr == 't') {
t = IS_ALIASED;
ptr++;
}
if (*ptr == 'p') {
p = IS_PREFETCHABLE;
ptr++;
}
/* DD,F,RR,NNNNNNNN */
dev = strtol(ptr, &ptr, 16);
ptr++;
fn = strtol(ptr, &ptr, 16);
ptr++;
reg = strtol(ptr, &ptr, 16);
ptr++;
lo = strtol(ptr, &ptr, 16);
mid = 0;
} else if (*ptr == 'x') {
unsigned long long addr64;
ss = MEMORY_SPACE_64;
ptr++;
if (*ptr == 'p') {
p = IS_PREFETCHABLE;
ptr++;
}
/* DD,F,RR,NNNNNNNNNNNNNNNN */
dev = strtol(ptr, &ptr, 16);
ptr++;
fn = strtol(ptr, &ptr, 16);
ptr++;
reg = strtol(ptr, &ptr, 16);
ptr++;
addr64 = strtoll(ptr, &ptr, 16);
lo = (ucell)addr64;
mid = addr64 >> 32;
} else {
ss = CONFIGURATION_SPACE;
/* "DD" or "DD,FF" */
dev = strtol(ptr, &ptr, 16);
if (*ptr == ',') {
ptr++;
fn = strtol(ptr, NULL, 16);
}
lo = 0;
mid = 0;
}
free((char*)arg);
hi = n | p | t | (ss << 24) | (bus << 16) | (dev << 11) | (fn << 8) | reg;
PUSH(lo);
PUSH(mid);
PUSH(hi);
PCI_DPRINTF("ob_pci_decode_unit idx=%p addr="
FMT_ucellx " " FMT_ucellx " " FMT_ucellx "\n",
idx, lo, mid, hi);
}
/* ( phys.lo phy.mid phys.hi -- str len ) */
static void
ob_pci_encode_unit(int *idx)
{
char buf[28];
cell hi = POP();
cell mid = POP();
cell lo = POP();
int n, p, t, ss, dev, fn, reg;
n = hi & IS_NOT_RELOCATABLE;
p = hi & IS_PREFETCHABLE;
t = hi & IS_ALIASED;
ss = (hi >> 24) & 0x03;
dev = (hi >> 11) & 0x1F;
fn = (hi >> 8) & 0x07;
reg = hi & 0xFF;
switch(ss) {
case CONFIGURATION_SPACE:
if (fn == 0) /* DD */
snprintf(buf, sizeof(buf), "%x", dev);
else /* DD,F */
snprintf(buf, sizeof(buf), "%x,%x", dev, fn);
break;
case IO_SPACE:
/* [n]i[t]DD,F,RR,NNNNNNNN */
snprintf(buf, sizeof(buf), "%si%s%x,%x,%x," FMT_ucellx,
n ? "n" : "", /* relocatable */
t ? "t" : "", /* aliased */
dev, fn, reg, t ? lo & 0x03FF : lo);
break;
case MEMORY_SPACE_32:
/* [n]m[t][p]DD,F,RR,NNNNNNNN */
snprintf(buf, sizeof(buf), "%sm%s%s%x,%x,%x," FMT_ucellx,
n ? "n" : "", /* relocatable */
t ? "t" : "", /* aliased */
p ? "p" : "", /* prefetchable */
dev, fn, reg, lo );
break;
case MEMORY_SPACE_64:
/* [n]x[p]DD,F,RR,NNNNNNNNNNNNNNNN */
snprintf(buf, sizeof(buf), "%sx%s%x,%x,%x,%llx",
n ? "n" : "", /* relocatable */
p ? "p" : "", /* prefetchable */
dev, fn, reg, ((long long)mid << 32) | (long long)lo);
break;
}
push_str(buf);
PCI_DPRINTF("ob_pci_encode_unit space=%d dev=%d fn=%d buf=%s\n",
ss, dev, fn, buf);
}
/* ( pci-addr.lo pci-addr.mid pci-addr.hi size -- virt ) */
static void
ob_pci_map_in(int *idx)
{
phys_addr_t phys;
uint32_t ba;
ucell size, virt, tmp;
int space;
PCI_DPRINTF("ob_pci_bar_map_in idx=%p\n", idx);
size = POP();
tmp = POP();
POP();
ba = POP();
/* Get the space from the pci-addr.hi */
space = ((tmp & PCI_RANGE_TYPE_MASK) >> 24);
phys = pci_bus_addr_to_host_addr(space, ba);
#if defined(CONFIG_OFMEM)
ofmem_claim_phys(phys, size, 0);
#if defined(CONFIG_PPC)
/* For some reason PPC gets upset when virt != phys for map-in... */
virt = ofmem_claim_virt(phys, size, 0);
#else
virt = ofmem_claim_virt(-1, size, size);
#endif
ofmem_map(phys, virt, size, ofmem_arch_io_translation_mode(phys));
#else
virt = size; /* Keep compiler quiet */
virt = phys;
#endif
PUSH(virt);
}
NODE_METHODS(ob_pci_bus_node) = {
{ NULL, ob_pci_initialize },
{ "open", ob_pci_open },
{ "close", ob_pci_close },
{ "decode-unit", ob_pci_decode_unit },
{ "encode-unit", ob_pci_encode_unit },
{ "pci-map-in", ob_pci_map_in },
};
NODE_METHODS(ob_pci_simple_node) = {
{ "open", ob_pci_open },
{ "close", ob_pci_close },
};
NODE_METHODS(ob_pci_empty_node) = {
{ NULL, ob_pci_initialize }
};
static void pci_set_bus_range(const pci_config_t *config)
{
phandle_t dev = find_dev(config->path);
u32 props[2];
props[0] = config->secondary_bus;
props[1] = config->subordinate_bus;
PCI_DPRINTF("setting bus range for %s PCI device, "
"package handle " FMT_ucellx " "
"bus primary=%d secondary=%d subordinate=%d\n",
config->path,
dev,
config->primary_bus,
config->secondary_bus,
config->subordinate_bus);
set_property(dev, "bus-range", (char *)props, 2 * sizeof(props[0]));
}
static void pci_host_set_reg(phandle_t phandle)
{
phandle_t dev = phandle;
/* at most 2 integers for address and size */
u32 props[4];
int ncells = 0;
ncells += encode_int32_cells(host_address_cells(), props + ncells,
arch->cfg_base);
ncells += encode_int32_cells(host_size_cells(), props + ncells,
arch->cfg_len);
set_property(dev, "reg", (char *)props, ncells * sizeof(props[0]));
#if defined(CONFIG_DEBUG_PCI)
dump_reg_property("pci_host_set_reg", 4, props);
#endif
}
/* child-phys : parent-phys : size */
/* 3 cells for PCI : 2 cells for 64bit parent : 2 cells for PCI */
static void pci_host_set_ranges(const pci_config_t *config)
{
phandle_t dev = get_cur_dev();
u32 props[32];
int ncells;
ncells = 0;
#ifdef CONFIG_SPARC64
/* While configuration space isn't mentioned in the IEEE-1275 PCI
bindings, it appears in the PCI host bridge ranges property in
real device trees. Hence we disable this range for all host
bridges except for SPARC, particularly as it causes Darwin/OS X
to incorrectly calculated PCI memory space ranges on PPC. */
ncells += pci_encode_phys_addr(props + ncells, 0, CONFIGURATION_SPACE,
config->dev, 0, 0);
ncells += host_encode_phys_addr(props + ncells, arch->cfg_addr);
ncells += pci_encode_size(props + ncells, arch->cfg_len);
#endif
if (arch->io_base) {
ncells += pci_encode_phys_addr(props + ncells, 0, IO_SPACE,
config->dev, 0, 0);
ncells += host_encode_phys_addr(props + ncells, arch->io_base);
ncells += pci_encode_size(props + ncells, arch->io_len);
}
if (arch->rbase) {
ncells += pci_encode_phys_addr(props + ncells, 0, MEMORY_SPACE_32,
config->dev, 0, 0);
ncells += host_encode_phys_addr(props + ncells, arch->rbase);
ncells += pci_encode_size(props + ncells, arch->rlen);
}
if (arch->pci_mem_base) {
ncells += pci_encode_phys_addr(props + ncells, 0, MEMORY_SPACE_32,
config->dev, 0, arch->pci_mem_base);
ncells += host_encode_phys_addr(props + ncells, arch->host_pci_base +
arch->pci_mem_base);
ncells += pci_encode_size(props + ncells, arch->mem_len);
}
set_property(dev, "ranges", (char *)props, ncells * sizeof(props[0]));
}
int host_config_cb(const pci_config_t *config)
{
//XXX this overrides "reg" property
pci_host_set_reg(get_cur_dev());
pci_host_set_ranges(config);
return 0;
}
static int sabre_configure(phandle_t dev)
{
uint32_t props[28];
props[0] = 0xc0000000;
props[1] = 0x20000000;
set_property(dev, "virtual-dma", (char *)props, 2 * sizeof(props[0]));
props[0] = 1;
set_property(dev, "#virtual-dma-size-cells", (char *)props,
sizeof(props[0]));
set_property(dev, "#virtual-dma-addr-cells", (char *)props,
sizeof(props[0]));
set_property(dev, "no-streaming-cache", (char *)props, 0);
props[0] = 0x000007f0;
props[1] = 0x000007ee;
props[2] = 0x000007ef;
props[3] = 0x000007e5;
set_property(dev, "interrupts", (char *)props, 4 * sizeof(props[0]));
props[0] = 0x0000001f;
set_property(dev, "upa-portid", (char *)props, 1 * sizeof(props[0]));
return 0;
}
int sabre_config_cb(const pci_config_t *config)
{
host_config_cb(config);
return sabre_configure(get_cur_dev());
}
int bridge_config_cb(const pci_config_t *config)
{
phandle_t aliases;
aliases = find_dev("/aliases");
set_property(aliases, "bridge", config->path, strlen(config->path) + 1);
return 0;
}
int ide_config_cb2 (const pci_config_t *config)
{
ob_ide_init(config->path,
config->assigned[0] & ~0x0000000F,
(config->assigned[1] & ~0x0000000F) + 2,
config->assigned[2] & ~0x0000000F,
(config->assigned[3] & ~0x0000000F) + 2);
return 0;
}
int eth_config_cb (const pci_config_t *config)
{
phandle_t ph = get_cur_dev();
set_property(ph, "network-type", "ethernet", 9);
set_property(ph, "removable", "network", 8);
set_property(ph, "category", "net", 4);
return 0;
}
int rtl8139_config_cb(const pci_config_t *config)
{
#ifdef CONFIG_PPC
/* Apple's OF seemingly enables bus mastering on some cards by
* default, which means that some buggy drivers forget to
* explicitly set it (OS X, MorphOS). Mimic this behaviour so
* that these buggy drivers work under emulation. */
if (is_apple()) {
ob_pci_enable_bus_master(config);
}
#endif
return eth_config_cb(config);
}
static inline void pci_decode_pci_addr(pci_addr addr, int *flags,
int *space_code, uint32_t *mask)
{
*flags = 0;
if (addr & 0x01) {
*space_code = IO_SPACE;
*mask = 0x00000001;
} else {
if (addr & 0x04) {
*space_code = MEMORY_SPACE_64;
*flags |= IS_NOT_RELOCATABLE; /* XXX: why not relocatable? */
} else {
*space_code = MEMORY_SPACE_32;
}
if (addr & 0x08) {
*flags |= IS_PREFETCHABLE;
}
*mask = 0x0000000F;
}
}
/*
* "Designing PCI Cards and Drivers for Power Macintosh Computers", p. 454
*
* "AAPL,address" provides an array of 32-bit logical addresses
* Nth entry corresponding to Nth "assigned-address" base address entry.
*/
static void pci_set_AAPL_address(const pci_config_t *config)
{
phandle_t dev = get_cur_dev();
cell props[7];
uint32_t mask;
int ncells, i, flags, space_code;
ncells = 0;
for (i = 0; i < 6; i++) {
if (!config->assigned[i] || !config->sizes[i])
continue;
pci_decode_pci_addr(config->assigned[i],
&flags, &space_code, &mask);
props[ncells++] = pci_bus_addr_to_host_addr(space_code,
config->assigned[i] & ~mask);
}
if (ncells)
set_property(dev, "AAPL,address", (char *)props,
ncells * sizeof(cell));
}
static void pci_set_assigned_addresses(phandle_t phandle,
const pci_config_t *config, int num_bars)
{
phandle_t dev = phandle;
u32 props[32];
int ncells;
int i;
uint32_t mask;
int flags, space_code;
ncells = 0;
for (i = 0; i < num_bars; i++) {
/* consider only bars with non-zero region size */
if (!config->sizes[i])
continue;
pci_decode_pci_addr(config->assigned[i],
&flags, &space_code, &mask);
ncells += pci_encode_phys_addr(props + ncells,
flags, space_code, config->dev,
PCI_BASE_ADDR_0 + (i * sizeof(uint32_t)),
config->assigned[i] & ~mask);
props[ncells++] = 0x00000000;
props[ncells++] = config->sizes[i];
}
if (ncells)
set_property(dev, "assigned-addresses", (char *)props,
ncells * sizeof(props[0]));
}
/* call after writing "reg" property to update config->path */
static void ob_pci_reload_device_path(phandle_t phandle, pci_config_t *config)
{
/* since "name" and "reg" are now assigned
we need to reload current node name */
char *new_path = get_path_from_ph(phandle);
if (new_path) {
if (0 != strcmp(config->path, new_path)) {
PCI_DPRINTF("\n=== CHANGED === package path old=%s new=%s\n",
config->path, new_path);
strncpy(config->path, new_path, sizeof(config->path));
config->path[sizeof(config->path)-1] = '\0';
}
free(new_path);
} else {
PCI_DPRINTF("\n=== package path old=%s new=NULL\n", config->path);
}
}
static void pci_set_reg(phandle_t phandle,
pci_config_t *config, int num_bars)
{
phandle_t dev = phandle;
u32 props[38];
int ncells;
int i;
uint32_t mask;
int space_code, flags;
ncells = 0;
/* first (addr, size) pair is the beginning of configuration address space */
ncells += pci_encode_phys_addr(props + ncells, 0, CONFIGURATION_SPACE,
config->dev, 0, 0);
ncells += pci_encode_size(props + ncells, 0);
for (i = 0; i < num_bars; i++) {
/* consider only bars with non-zero region size */
if (!config->sizes[i])
continue;
pci_decode_pci_addr(config->regions[i],
&flags, &space_code, &mask);
ncells += pci_encode_phys_addr(props + ncells,
flags, space_code, config->dev,
PCI_BASE_ADDR_0 + (i * sizeof(uint32_t)),
config->regions[i] & ~mask);
/* set size */
ncells += pci_encode_size(props + ncells, config->sizes[i]);
}
set_property(dev, "reg", (char *)props, ncells * sizeof(props[0]));
ob_pci_reload_device_path(dev, config);
#if defined(CONFIG_DEBUG_PCI)
dump_reg_property("pci_set_reg", ncells, props);
#endif
}
static void pci_set_ranges(const pci_config_t *config)
{
phandle_t dev = get_cur_dev();
u32 props[32];
int ncells;
int i;
uint32_t mask;
int flags;
int space_code;
ncells = 0;
for (i = 0; i < 6; i++) {
if (!config->assigned[i] || !config->sizes[i])
continue;
/* child address */
props[ncells++] = 0x00000000;
/* parent address */
pci_decode_pci_addr(config->assigned[i],
&flags, &space_code, &mask);
ncells += pci_encode_phys_addr(props + ncells, flags, space_code,
config->dev, 0x10 + i * 4,
config->assigned[i] & ~mask);
/* size */
props[ncells++] = config->sizes[i];
}
set_property(dev, "ranges", (char *)props, ncells * sizeof(props[0]));
}
int macio_heathrow_config_cb (const pci_config_t *config)
{
pci_set_ranges(config);
#ifdef CONFIG_DRIVER_MACIO
ob_macio_heathrow_init(config->path, config->assigned[0] & ~0x0000000F);
#endif
return 0;
}
int macio_keylargo_config_cb (const pci_config_t *config)
{
pci_set_ranges(config);
#ifdef CONFIG_DRIVER_MACIO
ob_macio_keylargo_init(config->path, config->assigned[0] & ~0x0000000F);
#endif
return 0;
}
int vga_config_cb (const pci_config_t *config)
{
#ifdef CONFIG_PPC
unsigned long rom;
uint32_t rom_size, size, bar;
phandle_t ph;
#endif
if (config->assigned[0] != 0x00000000) {
setup_video();
#ifdef CONFIG_PPC
if (config->assigned[6]) {
rom = pci_bus_addr_to_host_addr(MEMORY_SPACE_32,
config->assigned[6] & ~0x0000000F);
rom_size = config->sizes[6];
bar = pci_config_read32(config->dev, PCI_ROM_ADDRESS);
bar |= PCI_ROM_ADDRESS_ENABLE;
pci_config_write32(config->dev, PCI_COMMAND, bar);
ph = get_cur_dev();
if (rom_size >= 8) {
const char *p;
p = (const char *)rom;
if (p[0] == 'N' && p[1] == 'D' && p[2] == 'R' && p[3] == 'V') {
size = *(uint32_t*)(p + 4);
set_property(ph, "driver,AAPL,MacOS,PowerPC",
p + 8, size);
} else if (p[0] == 'J' && p[1] == 'o' &&
p[2] == 'y' && p[3] == '!') {
set_property(ph, "driver,AAPL,MacOS,PowerPC",
p, rom_size);
}
}
}
#endif
/* Currently we don't read FCode from the hardware but execute
* it directly */
feval("['] vga-driver-fcode 2 cells + 1 byte-load");
#ifdef CONFIG_MOL
/* Install special words for Mac On Linux */
molvideo_init();
#endif
}
return 0;
}
int ebus_config_cb(const pci_config_t *config)
{
#ifdef CONFIG_DRIVER_EBUS
phandle_t dev = get_cur_dev();
uint32_t props[12];
int ncells;
int i;
uint32_t mask;
int flags, space_code;
ucell virt;
phys_addr_t io_phys_base = 0;
props[0] = 0x14;
props[1] = 0x3f8;
props[2] = 1;
props[3] = find_dev("/");
props[4] = 0x2b;
set_property(dev, "interrupt-map", (char *)props, 5 * sizeof(props[0]));
props[0] = 0x000001ff;
props[1] = 0xffffffff;
props[2] = 3;
set_property(dev, "interrupt-map-mask", (char *)props, 3 * sizeof(props[0]));
/* Build ranges property from the BARs */
ncells = 0;
for (i = 0; i < 6; i++) {
/* consider only bars with non-zero region size */
if (!config->sizes[i])
continue;
pci_decode_pci_addr(config->assigned[i],
&flags, &space_code, &mask);
props[ncells++] = PCI_BASE_ADDR_0 + (i * sizeof(uint32_t));
props[ncells++] = 0x0;
ncells += pci_encode_phys_addr(props + ncells,
flags, space_code, config->dev,
PCI_BASE_ADDR_0 + (i * sizeof(uint32_t)),
config->assigned[i] & ~mask);
props[ncells++] = config->sizes[i];
/* Store base of IO space for NVRAM */
if (io_phys_base == 0x0 && space_code == IO_SPACE) {
io_phys_base = pci_bus_addr_to_host_addr(space_code, config->assigned[i] & ~mask);
}
}
set_property(dev, "ranges", (char *)props, ncells * sizeof(props[0]));
/* Build eeprom node */
fword("new-device");
PUSH(0x14);
fword("encode-int");
PUSH(0x2000);
fword("encode-int");
fword("encode+");
PUSH(0x2000);
fword("encode-int");
fword("encode+");
push_str("reg");
fword("property");
push_str("mk48t59");
fword("model");
/* OpenSolaris (e.g. Milax) requires the RTC to be pre-mapped by the PROM */
virt = ofmem_map_io(io_phys_base + 0x2000, 0x2000);
PUSH(virt);
fword("encode-int");
push_str("address");
fword("property");
push_str("eeprom");
fword("device-name");
fword("finish-device");
#ifdef CONFIG_DRIVER_FLOPPY
ob_floppy_init(config->path, "fdthree", 0x3f0ULL, 0);
#endif
#ifdef CONFIG_DRIVER_PC_SERIAL
ob_pc_serial_init(config->path, "su", (PCI_BASE_ADDR_1 | 0ULL) << 32, 0x3f8ULL, 0);
#endif
#ifdef CONFIG_DRIVER_PC_KBD
ob_pc_kbd_init(config->path, "kb_ps2", (PCI_BASE_ADDR_1 | 0ULL) << 32, 0x60ULL, 0);
#endif
#endif
return 0;
}
int i82378_config_cb(const pci_config_t *config)
{
#ifdef CONFIG_DRIVER_PC_SERIAL
ob_pc_serial_init(config->path, "serial", arch->io_base, 0x3f8ULL, 0);
#endif
#ifdef CONFIG_DRIVER_PC_KBD
ob_pc_kbd_init(config->path, "8042", arch->io_base, 0x60ULL, 0);
#endif
#ifdef CONFIG_DRIVER_IDE
ob_ide_init(config->path, 0x1f0, 0x3f6, 0x170, 0x376);
#endif
return 0;
}
int usb_ohci_config_cb(const pci_config_t *config)
{
#ifdef CONFIG_DRIVER_USB
ob_usb_ohci_init(config->path, 0x80000000 | config->dev);
#endif
return 0;
}
void ob_pci_enable_bus_master(const pci_config_t *config)
{
/* Enable bus mastering for the PCI device */
uint16_t cmd;
pci_addr addr = PCI_ADDR(
PCI_BUS(config->dev), PCI_DEV(config->dev), PCI_FN(config->dev));
cmd = pci_config_read16(addr, PCI_COMMAND);
cmd |= PCI_COMMAND_BUS_MASTER;
pci_config_write16(addr, PCI_COMMAND, cmd);
}
static void ob_pci_add_properties(phandle_t phandle,
pci_addr addr, const pci_dev_t *pci_dev,
const pci_config_t *config, int num_bars)
{
/* cannot use get_cur_dev() path resolution since "name" and "reg"
properties are being changed */
phandle_t dev=phandle;
int status,id;
uint16_t vendor_id, device_id;
uint8_t rev;
uint8_t class_prog;
uint32_t class_code;
vendor_id = pci_config_read16(addr, PCI_VENDOR_ID);
device_id = pci_config_read16(addr, PCI_DEVICE_ID);
rev = pci_config_read8(addr, PCI_REVISION_ID);
class_prog = pci_config_read8(addr, PCI_CLASS_PROG);
class_code = pci_config_read16(addr, PCI_CLASS_DEVICE);
if (pci_dev) {
/**/
if (pci_dev->name) {
push_str(pci_dev->name);
fword("encode-string");
push_str("name");
fword("property");
} else {
char path[256];
snprintf(path, sizeof(path),
"pci%x,%x", vendor_id, device_id);
push_str(path);
fword("encode-string");
push_str("name");
fword("property");
}
} else {
PCI_DPRINTF("*** missing pci_dev\n");
}
/* create properties as described in 2.5 */
set_int_property(dev, "vendor-id", vendor_id);
set_int_property(dev, "device-id", device_id);
set_int_property(dev, "revision-id", rev);
set_int_property(dev, "class-code", class_code << 8 | class_prog);
if (config->irq_pin) {
OLDWORLD(set_int_property(dev, "AAPL,interrupts",
config->irq_line));
#if defined(CONFIG_SPARC64)
set_int_property(dev, "interrupts", config->irq_pin);
#else
NEWWORLD(set_int_property(dev, "interrupts", config->irq_pin));
#endif
}
set_int_property(dev, "min-grant", pci_config_read8(addr, PCI_MIN_GNT));
set_int_property(dev, "max-latency", pci_config_read8(addr, PCI_MAX_LAT));
status=pci_config_read16(addr, PCI_STATUS);
set_int_property(dev, "devsel-speed",
(status&PCI_STATUS_DEVSEL_MASK)>>10);
if(status&PCI_STATUS_FAST_BACK)
set_bool_property(dev, "fast-back-to-back");
if(status&PCI_STATUS_66MHZ)
set_bool_property(dev, "66mhz-capable");
if(status&PCI_STATUS_UDF)
set_bool_property(dev, "udf-supported");
id=pci_config_read16(addr, PCI_SUBSYSTEM_VENDOR_ID);
if(id)
set_int_property(dev, "subsystem-vendor-id", id);
id=pci_config_read16(addr, PCI_SUBSYSTEM_ID);
if(id)
set_int_property(dev, "subsystem-id", id);
set_int_property(dev, "cache-line-size",
pci_config_read16(addr, PCI_CACHE_LINE_SIZE));
if (pci_dev) {
if (pci_dev->type) {
push_str(pci_dev->type);
fword("encode-string");
push_str("device_type");
fword("property");
}
if (pci_dev->model) {
push_str(pci_dev->model);
fword("encode-string");
push_str("model");
fword("property");
}
if (pci_dev->compat)
set_property(dev, "compatible",
pci_dev->compat, pci_compat_len(pci_dev));
if (pci_dev->acells)
set_int_property(dev, "#address-cells",
pci_dev->acells);
if (pci_dev->scells)
set_int_property(dev, "#size-cells",
pci_dev->scells);
if (pci_dev->icells)
set_int_property(dev, "#interrupt-cells",
pci_dev->icells);
}
pci_set_assigned_addresses(phandle, config, num_bars);
if (is_apple() && is_oldworld())
pci_set_AAPL_address(config);
PCI_DPRINTF("\n");
}
#ifdef CONFIG_XBOX
static char pci_xbox_blacklisted (int bus, int devnum, int fn)
{
/*
* The Xbox MCPX chipset is a derivative of the nForce 1
* chipset. It almost has the same bus layout; some devices
* cannot be used, because they have been removed.
*/
/*
* Devices 00:00.1 and 00:00.2 used to be memory controllers on
* the nForce chipset, but on the Xbox, using them will lockup
* the chipset.
*/
if ((bus == 0) && (devnum == 0) && ((fn == 1) || (fn == 2)))
return 1;
/*
* Bus 1 only contains a VGA controller at 01:00.0. When you try
* to probe beyond that device, you only get garbage, which
* could cause lockups.
*/
if ((bus == 1) && ((devnum != 0) || (fn != 0)))
return 1;
/*
* Bus 2 used to contain the AGP controller, but the Xbox MCPX
* doesn't have one. Probing it can cause lockups.
*/
if (bus >= 2)
return 1;
/*
* The device is not blacklisted.
*/
return 0;
}
#endif
static void ob_pci_configure_bar(pci_addr addr, pci_config_t *config,
int reg, int config_addr,
uint32_t *p_omask,
unsigned long *mem_base,
unsigned long *io_base)
{
uint32_t smask, amask, size, reloc, min_align;
unsigned long base;
config->assigned[reg] = 0x00000000;
config->sizes[reg] = 0x00000000;
if ((*p_omask & 0x0000000f) == 0x4) {
/* 64 bits memory mapping */
PCI_DPRINTF("Skipping 64 bit BARs for %s\n", config->path);
return;
}
config->regions[reg] = pci_config_read32(addr, config_addr);
/* get region size */
pci_config_write32(addr, config_addr, 0xffffffff);
smask = pci_config_read32(addr, config_addr);
if (smask == 0x00000000 || smask == 0xffffffff)
return;
if (smask & 0x00000001 && reg != 6) {
/* I/O space */
base = *io_base;
min_align = 1 << 7;
amask = 0x00000001;
} else {
/* Memory Space */
base = *mem_base;
min_align = 1 << 16;
amask = 0x0000000F;
if (reg == 6) {
smask |= 1; /* ROM */
}
}
*p_omask = smask & amask;
smask &= ~amask;
size = (~smask) + 1;
config->sizes[reg] = size;
reloc = base;
if (size < min_align)
size = min_align;
reloc = (reloc + size -1) & ~(size - 1);
if (*io_base == base) {
PCI_DPRINTF("changing io_base from 0x%lx to 0x%x\n",
*io_base, reloc + size);
*io_base = reloc + size;
} else {
PCI_DPRINTF("changing mem_base from 0x%lx to 0x%x\n",
*mem_base, reloc + size);
*mem_base = reloc + size;
}
PCI_DPRINTF("Configuring BARs for %s: reloc 0x%x omask 0x%x "
"io_base 0x%lx mem_base 0x%lx size 0x%x\n",
config->path, reloc, *p_omask, *io_base, *mem_base, size);
pci_config_write32(addr, config_addr, reloc | *p_omask);
config->assigned[reg] = reloc | *p_omask;
}
static void ob_pci_configure_irq(pci_addr addr, pci_config_t *config)
{
uint8_t irq_pin, irq_line;
irq_pin = pci_config_read8(addr, PCI_INTERRUPT_PIN);
if (irq_pin) {
config->irq_pin = irq_pin;
irq_pin = (((config->dev >> 11) & 0x1F) + irq_pin - 1) & 3;
irq_line = arch->irqs[irq_pin];
pci_config_write8(addr, PCI_INTERRUPT_LINE, irq_line);
config->irq_line = irq_line;
} else
config->irq_line = -1;
}
static void
ob_pci_configure(pci_addr addr, pci_config_t *config, int num_regs, int rom_bar,
unsigned long *mem_base, unsigned long *io_base)
{
uint32_t omask, mask;
uint16_t cmd;
int reg, flags, space_code;
pci_addr config_addr;
ob_pci_configure_irq(addr, config);
omask = 0x00000000;
for (reg = 0; reg < num_regs; ++reg) {
config_addr = PCI_BASE_ADDR_0 + reg * 4;
ob_pci_configure_bar(addr, config, reg, config_addr,
&omask, mem_base,
io_base);
/* Ignore 64-bit BAR MSBs (always map in 32-bit space) */
pci_decode_pci_addr(config->assigned[reg],
&flags, &space_code, &mask);
if (space_code == MEMORY_SPACE_64) {
reg++;
}
}
if (rom_bar) {
config_addr = rom_bar;
ob_pci_configure_bar(addr, config, reg, config_addr,
&omask, mem_base, io_base);
}
cmd = pci_config_read16(addr, PCI_COMMAND);
cmd |= PCI_COMMAND_IO | PCI_COMMAND_MEMORY;
pci_config_write16(addr, PCI_COMMAND, cmd);
}
static void ob_configure_pci_device(const char* parent_path,
int *bus_num, unsigned long *mem_base, unsigned long *io_base,
int bus, int devnum, int fn, int *p_is_multi);
static void ob_scan_pci_bus(int *bus_num, unsigned long *mem_base,
unsigned long *io_base, const char *path,
int bus)
{
int devnum, fn, is_multi;
PCI_DPRINTF("\nScanning bus %d at %s...\n", bus, path);
for (devnum = 0; devnum < 32; devnum++) {
is_multi = 0;
for (fn = 0; fn==0 || (is_multi && fn<8); fn++) {
ob_configure_pci_device(path, bus_num, mem_base, io_base,
bus, devnum, fn, &is_multi);
}
}
}
static void ob_configure_pci_bridge(pci_addr addr,
int *bus_num, unsigned long *mem_base,
unsigned long *io_base,
int primary_bus, pci_config_t *config)
{
config->primary_bus = primary_bus;
pci_config_write8(addr, PCI_PRIMARY_BUS, config->primary_bus);
config->secondary_bus = *bus_num;
pci_config_write8(addr, PCI_SECONDARY_BUS, config->secondary_bus);
config->subordinate_bus = 0xff;
pci_config_write8(addr, PCI_SUBORDINATE_BUS, config->subordinate_bus);
PCI_DPRINTF("scanning new pci bus %u under bridge %s\n",
config->secondary_bus, config->path);
/* make pci bridge parent device, prepare for recursion */
ob_scan_pci_bus(bus_num, mem_base, io_base,
config->path, config->secondary_bus);
/* bus scan updates *bus_num to last revealed pci bus number */
config->subordinate_bus = *bus_num;
pci_config_write8(addr, PCI_SUBORDINATE_BUS, config->subordinate_bus);
PCI_DPRINTF("bridge %s PCI bus primary=%d secondary=%d subordinate=%d\n",
config->path, config->primary_bus, config->secondary_bus,
config->subordinate_bus);
pci_set_bus_range(config);
}
static int ob_pci_read_identification(int bus, int devnum, int fn,
int *p_vid, int *p_did,
uint8_t *p_class, uint8_t *p_subclass)
{
int vid, did;
uint32_t ccode;
pci_addr addr;
#ifdef CONFIG_XBOX
if (pci_xbox_blacklisted (bus, devnum, fn))
return;
#endif
addr = PCI_ADDR(bus, devnum, fn);
vid = pci_config_read16(addr, PCI_VENDOR_ID);
did = pci_config_read16(addr, PCI_DEVICE_ID);
if (vid==0xffff || vid==0) {
return 0;
}
if (p_vid) {
*p_vid = vid;
}
if (p_did) {
*p_did = did;
}
ccode = pci_config_read16(addr, PCI_CLASS_DEVICE);
if (p_class) {
*p_class = ccode >> 8;
}
if (p_subclass) {
*p_subclass = ccode;
}
return 1;
}
static void ob_configure_pci_device(const char* parent_path,
int *bus_num, unsigned long *mem_base, unsigned long *io_base,
int bus, int devnum, int fn, int *p_is_multi)
{
int vid, did;
unsigned int htype;
pci_addr addr;
pci_config_t config = {};
const pci_dev_t *pci_dev;
uint8_t class, subclass, iface;
int num_bars, rom_bar;
phandle_t phandle = 0;
int is_host_bridge = 0;
if (!ob_pci_read_identification(bus, devnum, fn, &vid, &did, &class, &subclass)) {
return;
}
addr = PCI_ADDR(bus, devnum, fn);
iface = pci_config_read8(addr, PCI_CLASS_PROG);
pci_dev = pci_find_device(class, subclass, iface,
vid, did);
PCI_DPRINTF("%x:%x.%x - %x:%x - ", bus, devnum, fn,
vid, did);
htype = pci_config_read8(addr, PCI_HEADER_TYPE);
if (fn == 0) {
if (p_is_multi) {
*p_is_multi = htype & 0x80;
}
}
/* stop adding host bridge accessible from it's primary bus
PCI host bridge is to be added by host code
*/
if (class == PCI_BASE_CLASS_BRIDGE &&
subclass == PCI_SUBCLASS_BRIDGE_HOST) {
is_host_bridge = 1;
}
if (is_host_bridge) {
/* reuse device tree node */
PCI_DPRINTF("host bridge found - ");
snprintf(config.path, sizeof(config.path),
"%s", parent_path);
} else if (pci_dev == NULL || pci_dev->name == NULL) {
snprintf(config.path, sizeof(config.path),
"%s/pci%x,%x", parent_path, vid, did);
}
else {
snprintf(config.path, sizeof(config.path),
"%s/%s", parent_path, pci_dev->name);
}
PCI_DPRINTF("%s - ", config.path);
config.dev = addr & 0x00FFFFFF;
switch (class) {
case PCI_BASE_CLASS_BRIDGE:
if (subclass != PCI_SUBCLASS_BRIDGE_HOST) {
REGISTER_NAMED_NODE_PHANDLE(ob_pci_bus_node, config.path, phandle);
}
break;
default:
REGISTER_NAMED_NODE_PHANDLE(ob_pci_empty_node, config.path, phandle);
break;
}
if (is_host_bridge) {
phandle = find_dev(config.path);
if (get_property(phandle, "vendor-id", NULL)) {
PCI_DPRINTF("host bridge already configured\n");
return;
}
}
activate_dev(phandle);
if (htype & PCI_HEADER_TYPE_BRIDGE) {
num_bars = 2;
rom_bar = PCI_ROM_ADDRESS1;
} else {
num_bars = 6;
rom_bar = PCI_ROM_ADDRESS;
}
ob_pci_configure(addr, &config, num_bars, rom_bar,
mem_base, io_base);
ob_pci_add_properties(phandle, addr, pci_dev, &config, num_bars);
if (!is_host_bridge) {
pci_set_reg(phandle, &config, num_bars);
}
/* call device-specific configuration callback */
if (pci_dev && pci_dev->config_cb) {
//activate_device(config.path);
pci_dev->config_cb(&config);
}
/* if devices haven't supplied open/close words then supply them with simple defaults */
if (!find_package_method("open", phandle) && !find_package_method("close", phandle)) {
REGISTER_NODE_METHODS(ob_pci_simple_node, config.path);
}
/* device is configured so we may move it out of scope */
device_end();
/* scan bus behind bridge device */
//if (htype & PCI_HEADER_TYPE_BRIDGE && class == PCI_BASE_CLASS_BRIDGE) {
if ( class == PCI_BASE_CLASS_BRIDGE &&
( subclass == PCI_SUBCLASS_BRIDGE_PCI ||
subclass == PCI_SUBCLASS_BRIDGE_HOST ) ) {
if (subclass == PCI_SUBCLASS_BRIDGE_PCI) {
/* reserve next pci bus number for this PCI bridge */
++(*bus_num);
}
ob_configure_pci_bridge(addr, bus_num, mem_base, io_base, bus, &config);
}
}
static void ob_pci_set_available(phandle_t host, unsigned long mem_base, unsigned long io_base)
{
/* Create an available property for both memory and IO space */
uint32_t props[10];
int ncells;
ncells = 0;
ncells += pci_encode_phys_addr(props + ncells, 0, MEMORY_SPACE_32, 0, 0, mem_base);
ncells += pci_encode_size(props + ncells, arch->mem_len - mem_base);
ncells += pci_encode_phys_addr(props + ncells, 0, IO_SPACE, 0, 0, io_base);
ncells += pci_encode_size(props + ncells, arch->io_len - io_base);
set_property(host, "available", (char *)props, ncells * sizeof(props[0]));
}
/* Convert device/irq pin to interrupt property */
#define SUN4U_INTERRUPT(dev, irq_pin) \
((((dev >> 11) << 2) + irq_pin - 1) & 0x1f)
static void ob_pci_host_set_interrupt_map(phandle_t host)
{
phandle_t dnode = 0, pci_childnode = 0;
u32 props[128], intno;
int i, ncells, len;
u32 *val, addr;
char *reg;
#if defined(CONFIG_PPC)
phandle_t target_node;
char *path, buf[256];
/* Oldworld macs do interrupt maps differently */
if (!is_newworld())
return;
PCI_DPRINTF("setting up interrupt map for host %x\n", host);
dnode = dt_iterate_type(0, "open-pic");
path = get_path_from_ph(host);
if (dnode && path) {
/* patch in openpic interrupt-parent properties */
snprintf(buf, sizeof(buf), "%s/mac-io", path);
target_node = find_dev(buf);
set_int_property(target_node, "interrupt-parent", dnode);
snprintf(buf, sizeof(buf), "%s/mac-io/escc/ch-a", path);
target_node = find_dev(buf);
set_int_property(target_node, "interrupt-parent", dnode);
snprintf(buf, sizeof(buf), "%s/mac-io/escc/ch-b", path);
target_node = find_dev(buf);
set_int_property(target_node, "interrupt-parent", dnode);
snprintf(buf, sizeof(buf), "%s/mac-io/escc-legacy/ch-a", path);
target_node = find_dev(buf);
set_int_property(target_node, "interrupt-parent", dnode);
snprintf(buf, sizeof(buf), "%s/mac-io/escc-legacy/ch-b", path);
target_node = find_dev(buf);
set_int_property(target_node, "interrupt-parent", dnode);
/* QEMU only emulates 2 of the 3 ata buses currently */
/* On a new world Mac these are not numbered but named by the
* ATA version they support. Thus we have: ata-3, ata-3, ata-4
* On g3beige they all called just ide.
* We take 2 x ata-3 buses which seems to work for
* at least the clients we care about */
snprintf(buf, sizeof(buf), "%s/mac-io/ata-3@20000", path);
target_node = find_dev(buf);
set_int_property(target_node, "interrupt-parent", dnode);
snprintf(buf, sizeof(buf), "%s/mac-io/ata-3@21000", path);
target_node = find_dev(buf);
set_int_property(target_node, "interrupt-parent", dnode);
snprintf(buf, sizeof(buf), "%s/mac-io/via-cuda", path);
target_node = find_dev(buf);
set_int_property(target_node, "interrupt-parent", dnode);
target_node = find_dev(path);
set_int_property(target_node, "interrupt-parent", dnode);
}
#else
/* PCI host bridge is the default interrupt controller */
dnode = host;
#endif
/* Set interrupt-map for PCI devices with an interrupt pin present */
ncells = 0;
PUSH(host);
fword("child");
pci_childnode = POP();
while (pci_childnode) {
intno = get_int_property(pci_childnode, "interrupts", &len);
if (len && intno) {
reg = get_property(pci_childnode, "reg", &len);
if (len && reg) {
val = (u32 *)reg;
for (i = 0; i < (len / sizeof(u32)); i += 5) {
addr = val[i];
/* Device address is in 1st 32-bit word of encoded PCI address for config space */
if ((addr & PCI_RANGE_TYPE_MASK) == PCI_RANGE_CONFIG) {
#if defined(CONFIG_SPARC64)
ncells += pci_encode_phys_addr(props + ncells, 0, 0, addr, 0, 0);
props[ncells++] = intno;
props[ncells++] = dnode;
props[ncells++] = SUN4U_INTERRUPT(addr, intno);
#elif defined(CONFIG_PPC)
ncells += pci_encode_phys_addr(props + ncells, 0, 0, addr, 0, 0);
props[ncells++] = intno;
props[ncells++] = dnode;
props[ncells++] = arch->irqs[((intno - 1) + (addr >> 11)) & 3];
props[ncells++] = 1;
#else
/* Keep compiler quiet */
dnode = dnode;
#endif
}
}
}
}
PUSH(pci_childnode);
fword("peer");
pci_childnode = POP();
}
set_property(host, "interrupt-map", (char *)props, ncells * sizeof(props[0]));
props[0] = 0x0000f800;
props[1] = 0x0;
props[2] = 0x0;
props[3] = 0x7;
set_property(host, "interrupt-map-mask", (char *)props, 4 * sizeof(props[0]));
}
int ob_pci_init(void)
{
int bus, devnum, fn;
uint8_t class, subclass;
unsigned long mem_base, io_base;
pci_config_t config = {}; /* host bridge */
phandle_t phandle_host = 0;
PCI_DPRINTF("Initializing PCI host bridge...\n");
activate_device("/");
/* Find all PCI bridges */
mem_base = arch->pci_mem_base;
/* I/O ports under 0x400 are used by devices mapped at fixed
location. */
io_base = 0x400;
bus = 0;
for (devnum = 0; devnum < 32; devnum++) {
/* scan only fn 0 */
fn = 0;
if (!ob_pci_read_identification(bus, devnum, fn,
0, 0, &class, &subclass)) {
continue;
}
if (class != PCI_BASE_CLASS_BRIDGE || subclass != PCI_SUBCLASS_BRIDGE_HOST) {
continue;
}
/* create root node for host PCI bridge */
/* configure */
snprintf(config.path, sizeof(config.path), "/pci");
REGISTER_NAMED_NODE_PHANDLE(ob_pci_bus_node, config.path, phandle_host);
pci_host_set_reg(phandle_host);
/* update device path after changing "reg" property */
ob_pci_reload_device_path(phandle_host, &config);
ob_configure_pci_device(config.path, &bus, &mem_base, &io_base,
bus, devnum, fn, 0);
/* we expect single host PCI bridge
but this may be machine-specific */
break;
}
/* create available attributes for the PCI bridge */
ob_pci_set_available(phandle_host, mem_base, io_base);
/* configure the host bridge interrupt map */
ob_pci_host_set_interrupt_map(phandle_host);
device_end();
return 0;
}