| /****************************************************************************** |
| * Copyright (c) 2004, 2008 IBM Corporation |
| * Copyright (c) 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 <types.h> |
| #include "compat/rtas.h" |
| #include "compat/time.h" |
| #include "device.h" |
| #include "debug.h" |
| #include <x86emu/x86emu.h> |
| #include <device/oprom/include/io.h> |
| #include "io.h" |
| |
| #include <device/pci.h> |
| #include <device/pci_ops.h> |
| #include <device/resource.h> |
| |
| #include <arch/io.h> |
| |
| // these are not used, only needed for linking, must be overridden using X86emu_setupPioFuncs |
| // with the functions and struct below |
| void |
| outb(u8 val, u16 port) |
| { |
| printf("WARNING: outb not implemented!\n"); |
| HALT_SYS(); |
| } |
| |
| void |
| outw(u16 val, u16 port) |
| { |
| printf("WARNING: outw not implemented!\n"); |
| HALT_SYS(); |
| } |
| |
| void |
| outl(u32 val, u16 port) |
| { |
| printf("WARNING: outl not implemented!\n"); |
| HALT_SYS(); |
| } |
| |
| u8 |
| inb(u16 port) |
| { |
| printf("WARNING: inb not implemented!\n"); |
| HALT_SYS(); |
| return 0; |
| } |
| |
| u16 |
| inw(u16 port) |
| { |
| printf("WARNING: inw not implemented!\n"); |
| HALT_SYS(); |
| return 0; |
| } |
| |
| u32 |
| inl(u16 port) |
| { |
| printf("WARNING: inl not implemented!\n"); |
| HALT_SYS(); |
| return 0; |
| } |
| |
| #if CONFIG_YABEL_DIRECTHW |
| u8 my_inb(X86EMU_pioAddr addr) |
| { |
| u8 val; |
| |
| val = inb(addr); |
| DEBUG_PRINTF_IO("inb(0x%04x) = 0x%02x\n", addr, val); |
| |
| return val; |
| } |
| |
| u16 my_inw(X86EMU_pioAddr addr) |
| { |
| u16 val; |
| |
| val = inw(addr); |
| DEBUG_PRINTF_IO("inw(0x%04x) = 0x%04x\n", addr, val); |
| |
| return val; |
| } |
| |
| u32 my_inl(X86EMU_pioAddr addr) |
| { |
| u32 val; |
| |
| val = inl(addr); |
| DEBUG_PRINTF_IO("inl(0x%04x) = 0x%08x\n", addr, val); |
| |
| return val; |
| } |
| |
| void my_outb(X86EMU_pioAddr addr, u8 val) |
| { |
| DEBUG_PRINTF_IO("outb(0x%02x, 0x%04x)\n", val, addr); |
| outb(val, addr); |
| } |
| |
| void my_outw(X86EMU_pioAddr addr, u16 val) |
| { |
| DEBUG_PRINTF_IO("outw(0x%04x, 0x%04x)\n", val, addr); |
| outw(val, addr); |
| } |
| |
| void my_outl(X86EMU_pioAddr addr, u32 val) |
| { |
| DEBUG_PRINTF_IO("outl(0x%08x, 0x%04x)\n", val, addr); |
| outl(val, addr); |
| } |
| |
| #else |
| |
| static unsigned int |
| read_io(void *addr, size_t sz) |
| { |
| unsigned int ret; |
| /* since we are using inb instructions, we need the port number as 16bit value */ |
| u16 port = (u16)(u32) addr; |
| |
| switch (sz) { |
| case 1: |
| ret = inb(port); |
| break; |
| case 2: |
| ret = inw(port); |
| break; |
| case 4: |
| ret = inl(port); |
| break; |
| default: |
| ret = 0; |
| } |
| |
| return ret; |
| } |
| |
| static int |
| write_io(void *addr, unsigned int value, size_t sz) |
| { |
| u16 port = (u16)(u32) addr; |
| switch (sz) { |
| /* since we are using inb instructions, we need the port number as 16bit value */ |
| case 1: |
| outb(value, port); |
| break; |
| case 2: |
| outw(value, port); |
| break; |
| case 4: |
| outl(value, port); |
| break; |
| default: |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| u32 pci_cfg_read(X86EMU_pioAddr addr, u8 size); |
| void pci_cfg_write(X86EMU_pioAddr addr, u32 val, u8 size); |
| u8 handle_port_61h(void); |
| |
| u8 |
| my_inb(X86EMU_pioAddr addr) |
| { |
| u8 rval = 0xFF; |
| unsigned long translated_addr = addr; |
| u8 translated = biosemu_dev_translate_address(IORESOURCE_IO, &translated_addr); |
| if (translated != 0) { |
| //translation successful, access Device I/O (BAR or Legacy...) |
| DEBUG_PRINTF_IO("%s(%x): access to Device I/O\n", __func__, |
| addr); |
| //DEBUG_PRINTF_IO("%s(%04x): translated_addr: %llx\n", __func__, addr, translated_addr); |
| rval = read_io((void *)translated_addr, 1); |
| DEBUG_PRINTF_IO("%s(%04x) Device I/O --> %02x\n", __func__, |
| addr, rval); |
| return rval; |
| } else { |
| switch (addr) { |
| case 0x61: |
| //8254 KB Controller / Timer Port |
| // rval = handle_port_61h(); |
| rval = inb(0x61); |
| //DEBUG_PRINTF_IO("%s(%04x) KB / Timer Port B --> %02x\n", __func__, addr, rval); |
| return rval; |
| break; |
| case 0xCFC: |
| case 0xCFD: |
| case 0xCFE: |
| case 0xCFF: |
| // PCI Config Mechanism 1 Ports |
| return (u8) pci_cfg_read(addr, 1); |
| break; |
| case 0x0a: |
| CHECK_DBG(DEBUG_INTR) { |
| X86EMU_trace_on(); |
| } |
| M.x86.debug &= ~DEBUG_DECODE_NOPRINT_F; |
| //HALT_SYS(); |
| // no break, intentional fall-through to default!! |
| default: |
| DEBUG_PRINTF_IO |
| ("%s(%04x) reading from bios_device.io_buffer\n", |
| __func__, addr); |
| rval = *((u8 *) (bios_device.io_buffer + addr)); |
| DEBUG_PRINTF_IO("%s(%04x) I/O Buffer --> %02x\n", |
| __func__, addr, rval); |
| return rval; |
| break; |
| } |
| } |
| } |
| |
| u16 |
| my_inw(X86EMU_pioAddr addr) |
| { |
| unsigned long translated_addr = addr; |
| u8 translated = biosemu_dev_translate_address(IORESOURCE_IO, &translated_addr); |
| if (translated != 0) { |
| //translation successful, access Device I/O (BAR or Legacy...) |
| DEBUG_PRINTF_IO("%s(%x): access to Device I/O\n", __func__, |
| addr); |
| //DEBUG_PRINTF_IO("%s(%04x): translated_addr: %llx\n", __func__, addr, translated_addr); |
| u16 rval; |
| if ((translated_addr & (u64) 0x1) == 0) { |
| // 16 bit aligned access... |
| u16 tempval = read_io((void *)translated_addr, 2); |
| //little endian conversion |
| rval = in16le((void *) &tempval); |
| } else { |
| // unaligned access, read single bytes, little-endian |
| rval = (read_io((void *)translated_addr, 1) << 8) |
| | (read_io((void *)(translated_addr + 1), 1)); |
| } |
| DEBUG_PRINTF_IO("%s(%04x) Device I/O --> %04x\n", __func__, |
| addr, rval); |
| return rval; |
| } else { |
| switch (addr) { |
| case 0xCFC: |
| case 0xCFE: |
| //PCI Config Mechanism 1 |
| return (u16) pci_cfg_read(addr, 2); |
| break; |
| default: |
| DEBUG_PRINTF_IO |
| ("%s(%04x) reading from bios_device.io_buffer\n", |
| __func__, addr); |
| u16 rval = |
| in16le((void *) bios_device.io_buffer + addr); |
| DEBUG_PRINTF_IO("%s(%04x) I/O Buffer --> %04x\n", |
| __func__, addr, rval); |
| return rval; |
| break; |
| } |
| } |
| } |
| |
| u32 |
| my_inl(X86EMU_pioAddr addr) |
| { |
| unsigned long translated_addr = addr; |
| u8 translated = biosemu_dev_translate_address(IORESOURCE_IO, &translated_addr); |
| if (translated != 0) { |
| //translation successful, access Device I/O (BAR or Legacy...) |
| DEBUG_PRINTF_IO("%s(%x): access to Device I/O\n", __func__, |
| addr); |
| //DEBUG_PRINTF_IO("%s(%04x): translated_addr: %llx\n", __func__, addr, translated_addr); |
| u32 rval; |
| if ((translated_addr & (u64) 0x3) == 0) { |
| // 32 bit aligned access... |
| u32 tempval = read_io((void *) translated_addr, 4); |
| //little endian conversion |
| rval = in32le((void *) &tempval); |
| } else { |
| // unaligned access, read single bytes, little-endian |
| rval = (read_io((void *)(translated_addr), 1) << 24) |
| | (read_io((void *)(translated_addr + 1), 1) << 16) |
| | (read_io((void *)(translated_addr + 2), 1) << 8) |
| | (read_io((void *)(translated_addr + 3), 1)); |
| } |
| DEBUG_PRINTF_IO("%s(%04x) Device I/O --> %08x\n", __func__, |
| addr, rval); |
| return rval; |
| } else { |
| switch (addr) { |
| case 0xCFC: |
| //PCI Config Mechanism 1 |
| return pci_cfg_read(addr, 4); |
| break; |
| default: |
| DEBUG_PRINTF_IO |
| ("%s(%04x) reading from bios_device.io_buffer\n", |
| __func__, addr); |
| u32 rval = |
| in32le((void *) bios_device.io_buffer + addr); |
| DEBUG_PRINTF_IO("%s(%04x) I/O Buffer --> %08x\n", |
| __func__, addr, rval); |
| return rval; |
| break; |
| } |
| } |
| } |
| |
| void |
| my_outb(X86EMU_pioAddr addr, u8 val) |
| { |
| unsigned long translated_addr = addr; |
| u8 translated = biosemu_dev_translate_address(IORESOURCE_IO, &translated_addr); |
| if (translated != 0) { |
| //translation successful, access Device I/O (BAR or Legacy...) |
| DEBUG_PRINTF_IO("%s(%x, %x): access to Device I/O\n", |
| __func__, addr, val); |
| //DEBUG_PRINTF_IO("%s(%04x): translated_addr: %llx\n", __func__, addr, translated_addr); |
| write_io((void *) translated_addr, val, 1); |
| DEBUG_PRINTF_IO("%s(%04x) Device I/O <-- %02x\n", __func__, |
| addr, val); |
| } else { |
| switch (addr) { |
| case 0xCFC: |
| case 0xCFD: |
| case 0xCFE: |
| case 0xCFF: |
| // PCI Config Mechanism 1 Ports |
| pci_cfg_write(addr, val, 1); |
| break; |
| default: |
| DEBUG_PRINTF_IO |
| ("%s(%04x,%02x) writing to bios_device.io_buffer\n", |
| __func__, addr, val); |
| *((u8 *) (bios_device.io_buffer + addr)) = val; |
| break; |
| } |
| } |
| } |
| |
| void |
| my_outw(X86EMU_pioAddr addr, u16 val) |
| { |
| unsigned long translated_addr = addr; |
| u8 translated = biosemu_dev_translate_address(IORESOURCE_IO, &translated_addr); |
| if (translated != 0) { |
| //translation successful, access Device I/O (BAR or Legacy...) |
| DEBUG_PRINTF_IO("%s(%x, %x): access to Device I/O\n", |
| __func__, addr, val); |
| //DEBUG_PRINTF_IO("%s(%04x): translated_addr: %llx\n", __func__, addr, translated_addr); |
| if ((translated_addr & (u64) 0x1) == 0) { |
| // little-endian conversion |
| u16 tempval = in16le((void *) &val); |
| // 16 bit aligned access... |
| write_io((void *) translated_addr, tempval, 2); |
| } else { |
| // unaligned access, write single bytes, little-endian |
| write_io(((void *) (translated_addr + 1)), |
| (u8) ((val & 0xFF00) >> 8), 1); |
| write_io(((void *) translated_addr), |
| (u8) (val & 0x00FF), 1); |
| } |
| DEBUG_PRINTF_IO("%s(%04x) Device I/O <-- %04x\n", __func__, |
| addr, val); |
| } else { |
| switch (addr) { |
| case 0xCFC: |
| case 0xCFE: |
| // PCI Config Mechanism 1 Ports |
| pci_cfg_write(addr, val, 2); |
| break; |
| default: |
| DEBUG_PRINTF_IO |
| ("%s(%04x,%04x) writing to bios_device.io_buffer\n", |
| __func__, addr, val); |
| out16le((void *) bios_device.io_buffer + addr, val); |
| break; |
| } |
| } |
| } |
| |
| void |
| my_outl(X86EMU_pioAddr addr, u32 val) |
| { |
| unsigned long translated_addr = addr; |
| u8 translated = biosemu_dev_translate_address(IORESOURCE_IO, &translated_addr); |
| if (translated != 0) { |
| //translation successful, access Device I/O (BAR or Legacy...) |
| DEBUG_PRINTF_IO("%s(%x, %x): access to Device I/O\n", |
| __func__, addr, val); |
| //DEBUG_PRINTF_IO("%s(%04x): translated_addr: %llx\n", __func__, addr, translated_addr); |
| if ((translated_addr & (u64) 0x3) == 0) { |
| // little-endian conversion |
| u32 tempval = in32le((void *) &val); |
| // 32 bit aligned access... |
| write_io((void *) translated_addr, tempval, 4); |
| } else { |
| // unaligned access, write single bytes, little-endian |
| write_io(((void *) translated_addr + 3), |
| (u8) ((val & 0xFF000000) >> 24), 1); |
| write_io(((void *) translated_addr + 2), |
| (u8) ((val & 0x00FF0000) >> 16), 1); |
| write_io(((void *) translated_addr + 1), |
| (u8) ((val & 0x0000FF00) >> 8), 1); |
| write_io(((void *) translated_addr), |
| (u8) (val & 0x000000FF), 1); |
| } |
| DEBUG_PRINTF_IO("%s(%04x) Device I/O <-- %08x\n", __func__, |
| addr, val); |
| } else { |
| switch (addr) { |
| case 0xCFC: |
| // PCI Config Mechanism 1 Ports |
| pci_cfg_write(addr, val, 4); |
| break; |
| default: |
| DEBUG_PRINTF_IO |
| ("%s(%04x,%08x) writing to bios_device.io_buffer\n", |
| __func__, addr, val); |
| out32le((void *) bios_device.io_buffer + addr, val); |
| break; |
| } |
| } |
| } |
| |
| u32 |
| pci_cfg_read(X86EMU_pioAddr addr, u8 size) |
| { |
| u32 rval = 0xFFFFFFFF; |
| struct device * dev; |
| if ((addr >= 0xCFC) && ((addr + size) <= 0xD00)) { |
| // PCI Configuration Mechanism 1 step 1 |
| // write to 0xCF8, sets bus, device, function and Config Space offset |
| // later read from 0xCFC-0xCFF returns the value... |
| u8 bus, devfn, offs; |
| u32 port_cf8_val = my_inl(0xCF8); |
| if ((port_cf8_val & 0x80000000) != 0) { |
| //highest bit enables config space mapping |
| bus = (port_cf8_val & 0x00FF0000) >> 16; |
| devfn = (port_cf8_val & 0x0000FF00) >> 8; |
| offs = (port_cf8_val & 0x000000FF); |
| offs += (addr - 0xCFC); // if addr is not 0xcfc, the offset is moved accordingly |
| DEBUG_PRINTF_INTR("%s(): PCI Config Read from device: bus: %02x, devfn: %02x, offset: %02x\n", |
| __func__, bus, devfn, offs); |
| #if CONFIG_YABEL_PCI_ACCESS_OTHER_DEVICES |
| dev = dev_find_slot(bus, devfn); |
| DEBUG_PRINTF_INTR("%s(): dev_find_slot() returned: %s\n", |
| __func__, dev_path(dev)); |
| if (dev == 0) { |
| // fail accesses to non-existent devices... |
| #else |
| dev = bios_device.dev; |
| if ((bus != bios_device.bus) |
| || (devfn != bios_device.devfn)) { |
| // fail accesses to any device but ours... |
| #endif |
| printf |
| ("%s(): Config read access invalid device! bus: %02x (%02x), devfn: %02x (%02x), offs: %02x\n", |
| __func__, bus, bios_device.bus, devfn, |
| bios_device.devfn, offs); |
| SET_FLAG(F_CF); |
| HALT_SYS(); |
| return 0; |
| } else { |
| #if CONFIG_PCI_OPTION_ROM_RUN_YABEL |
| switch (size) { |
| case 1: |
| rval = pci_read_config8(dev, offs); |
| break; |
| case 2: |
| rval = pci_read_config16(dev, offs); |
| break; |
| case 4: |
| rval = pci_read_config32(dev, offs); |
| break; |
| } |
| #else |
| rval = |
| (u32) rtas_pci_config_read(bios_device. |
| puid, size, |
| bus, devfn, |
| offs); |
| #endif |
| DEBUG_PRINTF_IO |
| ("%s(%04x) PCI Config Read @%02x, size: %d --> 0x%08x\n", |
| __func__, addr, offs, size, rval); |
| } |
| } |
| } |
| return rval; |
| } |
| |
| void |
| pci_cfg_write(X86EMU_pioAddr addr, u32 val, u8 size) |
| { |
| if ((addr >= 0xCFC) && ((addr + size) <= 0xD00)) { |
| // PCI Configuration Mechanism 1 step 1 |
| // write to 0xCF8, sets bus, device, function and Config Space offset |
| // later write to 0xCFC-0xCFF sets the value... |
| u8 bus, devfn, offs; |
| u32 port_cf8_val = my_inl(0xCF8); |
| if ((port_cf8_val & 0x80000000) != 0) { |
| //highest bit enables config space mapping |
| bus = (port_cf8_val & 0x00FF0000) >> 16; |
| devfn = (port_cf8_val & 0x0000FF00) >> 8; |
| offs = (port_cf8_val & 0x000000FF); |
| offs += (addr - 0xCFC); // if addr is not 0xcfc, the offset is moved accordingly |
| if ((bus != bios_device.bus) |
| || (devfn != bios_device.devfn)) { |
| // fail accesses to any device but ours... |
| printf |
| ("Config write access invalid! PCI device %x:%x.%x, offs: %x\n", |
| bus, devfn >> 3, devfn & 7, offs); |
| #if !CONFIG_YABEL_PCI_FAKE_WRITING_OTHER_DEVICES_CONFIG |
| HALT_SYS(); |
| #endif |
| } else { |
| #if CONFIG_PCI_OPTION_ROM_RUN_YABEL |
| switch (size) { |
| case 1: |
| pci_write_config8(bios_device.dev, offs, val); |
| break; |
| case 2: |
| pci_write_config16(bios_device.dev, offs, val); |
| break; |
| case 4: |
| pci_write_config32(bios_device.dev, offs, val); |
| break; |
| } |
| #else |
| rtas_pci_config_write(bios_device.puid, |
| size, bus, devfn, offs, |
| val); |
| #endif |
| DEBUG_PRINTF_IO |
| ("%s(%04x) PCI Config Write @%02x, size: %d <-- 0x%08x\n", |
| __func__, addr, offs, size, val); |
| } |
| } |
| } |
| } |
| |
| u8 |
| handle_port_61h(void) |
| { |
| static u64 last_time = 0; |
| u64 curr_time = get_time(); |
| u64 time_diff; // time since last call |
| u32 period_ticks; // length of a period in ticks |
| u32 nr_periods; //number of periods passed since last call |
| // bit 4 should toggle with every (DRAM) refresh cycle... (66kHz??) |
| time_diff = curr_time - last_time; |
| // at 66kHz a period is ~ 15 ns long, converted to ticks: (tb_freq is ticks/second) |
| // TODO: as long as the frequency does not change, we should not calculate this every time |
| period_ticks = (15 * tb_freq) / 1000000; |
| nr_periods = time_diff / period_ticks; |
| // if the number if ticks passed since last call is odd, we toggle bit 4 |
| if ((nr_periods % 2) != 0) { |
| *((u8 *) (bios_device.io_buffer + 0x61)) ^= 0x10; |
| } |
| //finally read the value from the io_buffer |
| return *((u8 *) (bios_device.io_buffer + 0x61)); |
| } |
| #endif |