/* * 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 * * 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, INSTALL_OPEN, 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(uint32_t ba) { return arch->host_pci_base + (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.hi size -- virt ) */ static void ob_pci_map_in(int *idx) { phys_addr_t phys; uint32_t ba; ucell size, virt; PCI_DPRINTF("ob_pci_bar_map_in idx=%p\n", idx); size = POP(); POP(); ba = POP(); phys = pci_bus_addr_to_host_addr(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) = { { NULL, ob_pci_initialize }, { "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; /* first encode PCI configuration space */ { 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); } 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; } 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]; int ncells, i; ncells = 0; for (i = 0; i < 6; i++) { if (!config->assigned[i] || !config->sizes[i]) continue; props[ncells++] = config->assigned[i] & ~0x0000000F; } 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 */ PUSH(phandle); fword("get-package-path"); char *new_path = pop_fstr_copy(); 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) { unsigned long rom; uint32_t rom_size, size; phandle_t ph; if (config->assigned[0] != 0x00000000) { setup_video(); rom = pci_bus_addr_to_host_addr(config->assigned[1] & ~0x0000000F); rom_size = config->sizes[1]; 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); } } /* 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; 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)), 0); props[ncells++] = config->sizes[i]; } 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"); 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; } 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); 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; uint16_t cmd; int reg; 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); } 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; case PCI_CLASS_DISPLAY: REGISTER_NAMED_NODE_PHANDLE(ob_pci_empty_node, config.path, phandle); break; default: REGISTER_NAMED_NODE_PHANDLE(ob_pci_simple_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); } /* 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; u32 props[128]; int i; #if defined(CONFIG_PPC) phandle_t target_node; /* Oldworld macs do interrupt maps differently */ if (!is_newworld()) return; dnode = dt_iterate_type(0, "open-pic"); if (dnode) { /* patch in openpic interrupt-parent properties */ target_node = find_dev("/pci/mac-io"); set_int_property(target_node, "interrupt-parent", dnode); target_node = find_dev("/pci/mac-io/escc/ch-a"); set_int_property(target_node, "interrupt-parent", dnode); target_node = find_dev("/pci/mac-io/escc/ch-b"); 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 ata-3 and ata-4 which seems to work for both * at least for clients we care about */ target_node = find_dev("/pci/mac-io/ata-3"); set_int_property(target_node, "interrupt-parent", dnode); target_node = find_dev("/pci/mac-io/ata-4"); set_int_property(target_node, "interrupt-parent", dnode); target_node = find_dev("/pci/mac-io/via-cuda"); set_int_property(target_node, "interrupt-parent", dnode); target_node = find_dev("/pci"); set_int_property(target_node, "interrupt-parent", dnode); /* openpic interrupt mapping */ for (i = 0; i < (7*8); i += 7) { props[i + PCI_INT_MAP_PCI0] = 0; props[i + PCI_INT_MAP_PCI1] = 0; props[i + PCI_INT_MAP_PCI2] = 0; props[i + PCI_INT_MAP_PCI_INT] = (i / 7) + 1; // starts at PINA=1 props[i + PCI_INT_MAP_PIC_HANDLE] = dnode; props[i + PCI_INT_MAP_PIC_INT] = arch->irqs[i / 7]; props[i + PCI_INT_MAP_PIC_POL] = 3; } set_property(host, "interrupt-map", (char *)props, 7 * 8 * sizeof(props[0])); props[PCI_INT_MAP_PCI0] = 0; props[PCI_INT_MAP_PCI1] = 0; props[PCI_INT_MAP_PCI2] = 0; props[PCI_INT_MAP_PCI_INT] = 0x7; set_property(host, "interrupt-map-mask", (char *)props, 4 * sizeof(props[0])); } #elif defined(CONFIG_SPARC64) int ncells, len; u32 *val, addr; char *reg; /* Set interrupt-map for PCI devices with an interrupt pin present */ ncells = 0; PUSH(host); fword("child"); dnode = POP(); while (dnode) { if (get_int_property(dnode, "interrupts", &len)) { reg = get_property(dnode, "reg", &len); if (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 & 0x03000000)) { ncells += pci_encode_phys_addr(props + ncells, 0, 0, addr, 0, 0); props[ncells++] = 1; /* always interrupt pin 1 for QEMU */ props[ncells++] = host; props[ncells++] = SUN4U_INTERRUPT(addr, 1); } } } } PUSH(dnode); fword("peer"); dnode = POP(); } set_property(host, "interrupt-map", (char *)props, ncells * sizeof(props[0])); props[0] = 0x0000f800; props[1] = 0x0; props[2] = 0x0; props[3] = 7; set_property(host, "interrupt-map-mask", (char *)props, 4 * sizeof(props[0])); #endif } 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; }