| /****************************************************************************** |
| * Copyright (c) 2004, 2008 IBM Corporation |
| * Copyright (c) 2008, 2009 Pattrick Hueper <phueper@hueper.net> |
| * All rights reserved. |
| * This program and the accompanying materials |
| * are made available under the terms of the BSD License |
| * which accompanies this distribution, and is available at |
| * http://www.opensource.org/licenses/bsd-license.php |
| * |
| * Contributors: |
| * IBM Corporation - initial implementation |
| *****************************************************************************/ |
| |
| |
| #include "device.h" |
| #include "compat/rtas.h" |
| #include <string.h> |
| #include "debug.h" |
| |
| #include <device/device.h> |
| #include <device/pci.h> |
| #include <device/pci_ops.h> |
| #include <device/resource.h> |
| |
| /* the device we are working with... */ |
| biosemu_device_t bios_device; |
| //max. 6 BARs and 1 Exp.ROM plus CfgSpace and 3 legacy ranges |
| translate_address_t translate_address_array[11]; |
| u8 taa_last_entry; |
| |
| typedef struct { |
| u8 info; |
| u8 bus; |
| u8 devfn; |
| u8 cfg_space_offset; |
| u64 address; |
| u64 size; |
| } __attribute__ ((__packed__)) assigned_address_t; |
| |
| #ifdef CONFIG_PCI_OPTION_ROM_RUN_YABEL |
| /* coreboot version */ |
| |
| static void |
| biosemu_dev_get_addr_info(void) |
| { |
| int taa_index = 0; |
| int i = 0; |
| struct resource *r; |
| u8 bus = bios_device.dev->bus->link; |
| u16 devfn = bios_device.dev->path.pci.devfn; |
| |
| bios_device.bus = bus; |
| bios_device.devfn = devfn; |
| |
| DEBUG_PRINTF("bus: %x, devfn: %x\n", bus, devfn); |
| for (r = bios_device.dev->resource_list; r; r = r->next) { |
| translate_address_array[taa_index].info = r->flags; |
| translate_address_array[taa_index].bus = bus; |
| translate_address_array[taa_index].devfn = devfn; |
| translate_address_array[taa_index].cfg_space_offset = |
| r->index; |
| translate_address_array[taa_index].address = r->base; |
| translate_address_array[taa_index].size = r->size; |
| /* dont translate addresses... all addresses are 1:1 */ |
| translate_address_array[taa_index].address_offset = 0; |
| taa_index++; |
| } |
| /* Expansion ROM */ |
| translate_address_array[taa_index].info = IORESOURCE_MEM | IORESOURCE_READONLY; |
| translate_address_array[taa_index].bus = bus; |
| translate_address_array[taa_index].devfn = devfn; |
| translate_address_array[taa_index].cfg_space_offset = 0x30; |
| translate_address_array[taa_index].address = bios_device.img_addr; |
| translate_address_array[taa_index].size = 0; /* TODO: do we need the size? */ |
| /* dont translate addresses... all addresses are 1:1 */ |
| translate_address_array[taa_index].address_offset = 0; |
| taa_index++; |
| /* legacy ranges if its a VGA card... */ |
| if ((bios_device.dev->class & 0xFF0000) == 0x030000) { |
| DEBUG_PRINTF("%s: VGA device found, adding legacy resources... \n", __func__); |
| /* I/O 0x3B0-0x3BB */ |
| translate_address_array[taa_index].info = IORESOURCE_FIXED | IORESOURCE_IO; |
| translate_address_array[taa_index].bus = bus; |
| translate_address_array[taa_index].devfn = devfn; |
| translate_address_array[taa_index].cfg_space_offset = 0; |
| translate_address_array[taa_index].address = 0x3b0; |
| translate_address_array[taa_index].size = 0xc; |
| /* dont translate addresses... all addresses are 1:1 */ |
| translate_address_array[taa_index].address_offset = 0; |
| taa_index++; |
| /* I/O 0x3C0-0x3DF */ |
| translate_address_array[taa_index].info = IORESOURCE_FIXED | IORESOURCE_IO; |
| translate_address_array[taa_index].bus = bus; |
| translate_address_array[taa_index].devfn = devfn; |
| translate_address_array[taa_index].cfg_space_offset = 0; |
| translate_address_array[taa_index].address = 0x3c0; |
| translate_address_array[taa_index].size = 0x20; |
| /* dont translate addresses... all addresses are 1:1 */ |
| translate_address_array[taa_index].address_offset = 0; |
| taa_index++; |
| /* Mem 0xA0000-0xBFFFF */ |
| translate_address_array[taa_index].info = IORESOURCE_FIXED | IORESOURCE_MEM; |
| translate_address_array[taa_index].bus = bus; |
| translate_address_array[taa_index].devfn = devfn; |
| translate_address_array[taa_index].cfg_space_offset = 0; |
| translate_address_array[taa_index].address = 0xa0000; |
| translate_address_array[taa_index].size = 0x20000; |
| /* dont translate addresses... all addresses are 1:1 */ |
| translate_address_array[taa_index].address_offset = 0; |
| taa_index++; |
| } |
| // store last entry index of translate_address_array |
| taa_last_entry = taa_index - 1; |
| #if defined(CONFIG_X86EMU_DEBUG) && CONFIG_X86EMU_DEBUG |
| //dump translate_address_array |
| printf("translate_address_array: \n"); |
| translate_address_t ta; |
| for (i = 0; i <= taa_last_entry; i++) { |
| ta = translate_address_array[i]; |
| printf |
| ("%d: info: %08lx bus: %02x devfn: %02x cfg_space_offset: %02x\n\taddr: %016llx\n\toffs: %016llx\n\tsize: %016llx\n", |
| i, ta.info, ta.bus, ta.devfn, ta.cfg_space_offset, |
| ta.address, ta.address_offset, ta.size); |
| } |
| #endif |
| } |
| #else |
| // use translate_address_dev and get_puid from net-snk's net_support.c |
| void translate_address_dev(u64 *, phandle_t); |
| u64 get_puid(phandle_t node); |
| |
| |
| // scan all adresses assigned to the device ("assigned-addresses" and "reg") |
| // store in translate_address_array for faster translation using dev_translate_address |
| void |
| biosemu_dev_get_addr_info(void) |
| { |
| // get bus/dev/fn from assigned-addresses |
| int32_t len; |
| //max. 6 BARs and 1 Exp.ROM plus CfgSpace and 3 legacy ranges |
| assigned_address_t buf[11]; |
| len = |
| of_getprop(bios_device.phandle, "assigned-addresses", buf, |
| sizeof(buf)); |
| bios_device.bus = buf[0].bus; |
| bios_device.devfn = buf[0].devfn; |
| DEBUG_PRINTF("bus: %x, devfn: %x\n", bios_device.bus, |
| bios_device.devfn); |
| //store address translations for all assigned-addresses and regs in |
| //translate_address_array for faster translation later on... |
| int i = 0; |
| // index to insert data into translate_address_array |
| int taa_index = 0; |
| u64 address_offset; |
| for (i = 0; i < (len / sizeof(assigned_address_t)); i++, taa_index++) { |
| //copy all info stored in assigned-addresses |
| translate_address_array[taa_index].info = buf[i].info; |
| translate_address_array[taa_index].bus = buf[i].bus; |
| translate_address_array[taa_index].devfn = buf[i].devfn; |
| translate_address_array[taa_index].cfg_space_offset = |
| buf[i].cfg_space_offset; |
| translate_address_array[taa_index].address = buf[i].address; |
| translate_address_array[taa_index].size = buf[i].size; |
| // translate first address and store it as address_offset |
| address_offset = buf[i].address; |
| translate_address_dev(&address_offset, bios_device.phandle); |
| translate_address_array[taa_index].address_offset = |
| address_offset - buf[i].address; |
| } |
| //get "reg" property |
| len = of_getprop(bios_device.phandle, "reg", buf, sizeof(buf)); |
| for (i = 0; i < (len / sizeof(assigned_address_t)); i++) { |
| if ((buf[i].size == 0) || (buf[i].cfg_space_offset != 0)) { |
| // we dont care for ranges with size 0 and |
| // BARs and Expansion ROM must be in assigned-addresses... so in reg |
| // we only look for those without config space offset set... |
| // i.e. the legacy ranges |
| continue; |
| } |
| //copy all info stored in assigned-addresses |
| translate_address_array[taa_index].info = buf[i].info; |
| translate_address_array[taa_index].bus = buf[i].bus; |
| translate_address_array[taa_index].devfn = buf[i].devfn; |
| translate_address_array[taa_index].cfg_space_offset = |
| buf[i].cfg_space_offset; |
| translate_address_array[taa_index].address = buf[i].address; |
| translate_address_array[taa_index].size = buf[i].size; |
| // translate first address and store it as address_offset |
| address_offset = buf[i].address; |
| translate_address_dev(&address_offset, bios_device.phandle); |
| translate_address_array[taa_index].address_offset = |
| address_offset - buf[i].address; |
| taa_index++; |
| } |
| // store last entry index of translate_address_array |
| taa_last_entry = taa_index - 1; |
| #if defined(CONFIG_X86EMU_DEBUG) && CONFIG_X86EMU_DEBUG |
| //dump translate_address_array |
| printf("translate_address_array: \n"); |
| translate_address_t ta; |
| for (i = 0; i <= taa_last_entry; i++) { |
| ta = translate_address_array[i]; |
| printf |
| ("%d: %02x%02x%02x%02x\n\taddr: %016llx\n\toffs: %016llx\n\tsize: %016llx\n", |
| i, ta.info, ta.bus, ta.devfn, ta.cfg_space_offset, |
| ta.address, ta.address_offset, ta.size); |
| } |
| #endif |
| } |
| #endif |
| |
| #ifndef CONFIG_PCI_OPTION_ROM_RUN_YABEL |
| // to simulate accesses to legacy VGA Memory (0xA0000-0xBFFFF) |
| // we look for the first prefetchable memory BAR, if no prefetchable BAR found, |
| // we use the first memory BAR |
| // dev_translate_addr will translate accesses to the legacy VGA Memory into the found vmem BAR |
| static void |
| biosemu_dev_find_vmem_addr(void) |
| { |
| int i = 0; |
| translate_address_t ta; |
| s8 tai_np = -1, tai_p = -1; // translate_address_array index for non-prefetchable and prefetchable memory |
| //search backwards to find first entry |
| for (i = taa_last_entry; i >= 0; i--) { |
| ta = translate_address_array[i]; |
| if ((ta.cfg_space_offset >= 0x10) |
| && (ta.cfg_space_offset <= 0x24)) { |
| //only BARs |
| if ((ta.info & 0x03) >= 0x02) { |
| //32/64bit memory |
| tai_np = i; |
| if ((ta.info & 0x40) != 0) { |
| // prefetchable |
| tai_p = i; |
| } |
| } |
| } |
| } |
| if (tai_p != -1) { |
| ta = translate_address_array[tai_p]; |
| bios_device.vmem_addr = ta.address; |
| bios_device.vmem_size = ta.size; |
| DEBUG_PRINTF |
| ("%s: Found prefetchable Virtual Legacy Memory BAR: %llx, size: %llx\n", |
| __func__, bios_device.vmem_addr, |
| bios_device.vmem_size); |
| } else if (tai_np != -1) { |
| ta = translate_address_array[tai_np]; |
| bios_device.vmem_addr = ta.address; |
| bios_device.vmem_size = ta.size; |
| DEBUG_PRINTF |
| ("%s: Found non-prefetchable Virtual Legacy Memory BAR: %llx, size: %llx", |
| __func__, bios_device.vmem_addr, |
| bios_device.vmem_size); |
| } |
| // disable vmem |
| //bios_device.vmem_size = 0; |
| } |
| |
| void |
| biosemu_dev_get_puid(void) |
| { |
| // get puid |
| bios_device.puid = get_puid(bios_device.phandle); |
| DEBUG_PRINTF("puid: 0x%llx\n", bios_device.puid); |
| } |
| #endif |
| |
| static void |
| biosemu_dev_get_device_vendor_id(void) |
| { |
| |
| u32 pci_config_0; |
| #ifdef CONFIG_PCI_OPTION_ROM_RUN_YABEL |
| pci_config_0 = pci_read_config32(bios_device.dev, 0x0); |
| #else |
| pci_config_0 = |
| rtas_pci_config_read(bios_device.puid, 4, bios_device.bus, |
| bios_device.devfn, 0x0); |
| #endif |
| bios_device.pci_device_id = |
| (u16) ((pci_config_0 & 0xFFFF0000) >> 16); |
| bios_device.pci_vendor_id = (u16) (pci_config_0 & 0x0000FFFF); |
| DEBUG_PRINTF("PCI Device ID: %04x, PCI Vendor ID: %x\n", |
| bios_device.pci_device_id, bios_device.pci_vendor_id); |
| } |
| |
| /* Check whether the device has a valid Expansion ROM and search the PCI Data |
| * Structure and any Expansion ROM Header (using dev_scan_exp_header()) for |
| * needed information. If the rom_addr parameter is != 0, it is the address of |
| * the Expansion ROM image and will be used, if it is == 0, the Expansion ROM |
| * BAR address will be used. |
| */ |
| u8 |
| biosemu_dev_check_exprom(unsigned long rom_base_addr) |
| { |
| int i = 0; |
| translate_address_t ta; |
| u16 pci_ds_offset; |
| pci_data_struct_t pci_ds; |
| if (rom_base_addr == 0) { |
| // check for ExpROM Address (Offset 30) in taa |
| for (i = 0; i <= taa_last_entry; i++) { |
| ta = translate_address_array[i]; |
| if (ta.cfg_space_offset == 0x30) { |
| //translated address |
| rom_base_addr = ta.address + ta.address_offset; |
| break; |
| } |
| } |
| } |
| /* In the ROM there could be multiple Expansion ROM Images... start |
| * searching them for an x86 image. |
| */ |
| do { |
| if (rom_base_addr == 0) { |
| printf("Error: no Expansion ROM address found!\n"); |
| return -1; |
| } |
| set_ci(); |
| u16 rom_signature = in16le((void *) rom_base_addr); |
| clr_ci(); |
| if (rom_signature != 0xaa55) { |
| printf |
| ("Error: invalid Expansion ROM signature: %02x!\n", |
| *((u16 *) rom_base_addr)); |
| return -1; |
| } |
| set_ci(); |
| // at offset 0x18 is the (16bit little-endian) pointer to the PCI Data Structure |
| pci_ds_offset = in16le((void *) (rom_base_addr + 0x18)); |
| //copy the PCI Data Structure |
| memcpy(&pci_ds, (void *) (rom_base_addr + pci_ds_offset), |
| sizeof(pci_ds)); |
| clr_ci(); |
| #if defined(CONFIG_X86EMU_DEBUG) && CONFIG_X86EMU_DEBUG |
| DEBUG_PRINTF("PCI Data Structure @%lx:\n", |
| rom_base_addr + pci_ds_offset); |
| dump((void *) &pci_ds, sizeof(pci_ds)); |
| #endif |
| if (strncmp((const char *) pci_ds.signature, "PCIR", 4) != 0) { |
| printf("Invalid PCI Data Structure found!\n"); |
| break; |
| } |
| //little-endian conversion |
| pci_ds.vendor_id = in16le(&pci_ds.vendor_id); |
| pci_ds.device_id = in16le(&pci_ds.device_id); |
| pci_ds.img_length = in16le(&pci_ds.img_length); |
| pci_ds.pci_ds_length = in16le(&pci_ds.pci_ds_length); |
| if (pci_ds.vendor_id != bios_device.pci_vendor_id) { |
| printf |
| ("Image has invalid Vendor ID: %04x, expected: %04x\n", |
| pci_ds.vendor_id, bios_device.pci_vendor_id); |
| break; |
| } |
| if (pci_ds.device_id != bios_device.pci_device_id) { |
| printf |
| ("Image has invalid Device ID: %04x, expected: %04x\n", |
| pci_ds.device_id, bios_device.pci_device_id); |
| break; |
| } |
| DEBUG_PRINTF("Image Length: %d\n", pci_ds.img_length * 512); |
| DEBUG_PRINTF("Image Code Type: %d\n", pci_ds.code_type); |
| if (pci_ds.code_type == 0) { |
| //x86 image |
| //store image address and image length in bios_device struct |
| bios_device.img_addr = rom_base_addr; |
| bios_device.img_size = pci_ds.img_length * 512; |
| // we found the image, exit the loop |
| break; |
| } else { |
| // no x86 image, check next image (if any) |
| rom_base_addr += pci_ds.img_length * 512; |
| } |
| if ((pci_ds.indicator & 0x80) == 0x80) { |
| //last image found, exit the loop |
| DEBUG_PRINTF("Last PCI Expansion ROM Image found.\n"); |
| break; |
| } |
| } |
| while (bios_device.img_addr == 0); |
| // in case we did not find a valid x86 Expansion ROM Image |
| if (bios_device.img_addr == 0) { |
| printf("Error: no valid x86 Expansion ROM Image found!\n"); |
| return -1; |
| } |
| return 0; |
| } |
| |
| u8 |
| biosemu_dev_init(struct device * device) |
| { |
| u8 rval = 0; |
| //init bios_device struct |
| DEBUG_PRINTF("%s\n", __func__); |
| memset(&bios_device, 0, sizeof(bios_device)); |
| |
| #ifndef CONFIG_PCI_OPTION_ROM_RUN_YABEL |
| bios_device.ihandle = of_open(device_name); |
| if (bios_device.ihandle == 0) { |
| DEBUG_PRINTF("%s is no valid device!\n", device_name); |
| return -1; |
| } |
| bios_device.phandle = of_finddevice(device_name); |
| #else |
| bios_device.dev = device; |
| #endif |
| biosemu_dev_get_addr_info(); |
| #ifndef CONFIG_PCI_OPTION_ROM_RUN_YABEL |
| biosemu_dev_find_vmem_addr(); |
| biosemu_dev_get_puid(); |
| #endif |
| biosemu_dev_get_device_vendor_id(); |
| return rval; |
| } |
| |
| // translate address function using translate_address_array assembled |
| // by dev_get_addr_info... MUCH faster than calling translate_address_dev |
| // and accessing client interface for every translation... |
| // returns: 0 if addr not found in translate_address_array, 1 if found. |
| u8 |
| biosemu_dev_translate_address(unsigned long * addr) |
| { |
| int i = 0; |
| translate_address_t ta; |
| #ifndef CONFIG_PCI_OPTION_ROM_RUN_YABEL |
| /* we dont need this hack for coreboot... we can access legacy areas */ |
| //check if it is an access to legacy VGA Mem... if it is, map the address |
| //to the vmem BAR and then translate it... |
| // (translation info provided by Ben Herrenschmidt) |
| // NOTE: the translation seems to only work for NVIDIA cards... but it is needed |
| // to make some NVIDIA cards work at all... |
| if ((bios_device.vmem_size > 0) |
| && ((*addr >= 0xA0000) && (*addr < 0xB8000))) { |
| *addr = (*addr - 0xA0000) * 4 + 2 + bios_device.vmem_addr; |
| } |
| if ((bios_device.vmem_size > 0) |
| && ((*addr >= 0xB8000) && (*addr < 0xC0000))) { |
| u8 shift = *addr & 1; |
| *addr &= 0xfffffffe; |
| *addr = (*addr - 0xB8000) * 4 + shift + bios_device.vmem_addr; |
| } |
| #endif |
| for (i = 0; i <= taa_last_entry; i++) { |
| ta = translate_address_array[i]; |
| if ((*addr >= ta.address) && (*addr <= (ta.address + ta.size))) { |
| *addr += ta.address_offset; |
| return 1; |
| } |
| } |
| return 0; |
| } |