| /* Simplistic PCI support. |
| |
| Copyright (C) 2011 Richard Henderson |
| |
| This file is part of QEMU PALcode. |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2 of the License or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the text |
| of the GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program; see the file COPYING. If not see |
| <http://www.gnu.org/licenses/>. */ |
| |
| /* We don't bother supporting PCI bridges, because the device model we're |
| currently using for QEMU doesn't build any. |
| |
| We don't bother to build real datastructures in memory, because it's |
| fairly quick under emulation simply to access configuration space again. |
| This helps when running kernels under the emulator that might have |
| re-organized the BARs out from under us. */ |
| |
| #include "protos.h" |
| #include "pci.h" |
| #include "pci_regs.h" |
| #include SYSTEM_H |
| |
| |
| #define PCI_SLOT_MAX 32 |
| #define PCI_FUNC_MAX 8 |
| #define PCI_REGION_ROM 6 |
| #define PCI_REGIONS_MAX 7 |
| |
| |
| void |
| pci_config_maskw(int bdf, int addr, uint16_t off, uint16_t on) |
| { |
| uint16_t val = pci_config_readw(bdf, addr); |
| val = (val & ~off) | on; |
| pci_config_writew(bdf, addr, val); |
| } |
| |
| int |
| pci_next(int bdf, int *pmax) |
| { |
| int max; |
| |
| if (PCI_FUNC(bdf) == 1) |
| { |
| /* If the last device was not a multi-function device, skip to next. */ |
| if ((pci_config_readb(bdf-1, PCI_HEADER_TYPE) & 0x80) == 0) |
| bdf += 7; |
| } |
| |
| max = *pmax; |
| while (1) |
| { |
| uint16_t vendor; |
| |
| /* ??? Support multiple PCI busses here at some point. */ |
| if (bdf >= max) |
| return -1; |
| |
| /* Check if there is a device present at the location. */ |
| vendor = pci_config_readw(bdf, PCI_VENDOR_ID); |
| if (vendor != 0x0000 && vendor != 0xffff) |
| return bdf; |
| |
| bdf += (PCI_FUNC(bdf) == 0 ? 8 : 1); |
| } |
| } |
| |
| static void |
| pci_setup_device(int bdf, uint32_t *p_io_base, uint32_t *p_mem_base) |
| { |
| int vendor_id, device_id, class_id, region; |
| |
| vendor_id = pci_config_readw(bdf, PCI_VENDOR_ID); |
| device_id = pci_config_readw(bdf, PCI_DEVICE_ID); |
| class_id = pci_config_readw(bdf, PCI_CLASS_DEVICE); |
| |
| printf("PCI: %02x:%02x:%x class %04x id %04x:%04x\r\n", |
| PCI_BUS(bdf), PCI_SLOT(bdf), PCI_FUNC(bdf), |
| class_id, vendor_id, device_id); |
| |
| for (region = 0; region < PCI_REGION_ROM; region++) |
| { |
| int ofs = PCI_BASE_ADDRESS_0 + region * 4; |
| uint32_t old, mask, val, size, align; |
| uint32_t *p_base; |
| |
| old = pci_config_readl(bdf, ofs); |
| if (old & PCI_BASE_ADDRESS_SPACE_IO) |
| { |
| mask = PCI_BASE_ADDRESS_IO_MASK; |
| p_base = p_io_base; |
| } |
| else |
| { |
| mask = PCI_BASE_ADDRESS_MEM_MASK; |
| p_base = p_mem_base; |
| } |
| |
| pci_config_writel(bdf, ofs, -1); |
| val = pci_config_readl(bdf, ofs); |
| pci_config_writel(bdf, ofs, old); |
| |
| align = size = ~(val & mask) + 1; |
| if (val != 0) |
| { |
| uint32_t addr = *p_base; |
| addr = (addr + align - 1) & ~(align - 1); |
| *p_base = addr + size; |
| pci_config_writel(bdf, ofs, addr); |
| |
| printf("PCI: region %d: %08x\r\n", region, addr); |
| |
| if ((old & (PCI_BASE_ADDRESS_SPACE | PCI_BASE_ADDRESS_MEM_TYPE_MASK)) |
| == (PCI_BASE_ADDRESS_SPACE_MEMORY | PCI_BASE_ADDRESS_MEM_TYPE_64)) |
| { |
| pci_config_writel(bdf, ofs + 4, 0); |
| region++; |
| } |
| } |
| } |
| |
| pci_config_maskw(bdf, PCI_COMMAND, 0, PCI_COMMAND_IO | PCI_COMMAND_MEMORY); |
| |
| /* Map the interrupt and program the IRQ into the line register. |
| Some operating systems rely on the Console providing this information |
| in order to avoid having mapping tables for every possible system |
| variation. */ |
| |
| const uint8_t pin = pci_config_readb(bdf, PCI_INTERRUPT_PIN); |
| const uint8_t slot = PCI_SLOT(bdf); |
| const uint8_t irq = MAP_PCI_INTERRUPT(slot, pin, class_id); |
| |
| pci_config_writeb(bdf, PCI_INTERRUPT_LINE, irq); |
| } |
| |
| void |
| pci_setup(void) |
| { |
| uint32_t io_base = 0xc000; |
| uint32_t mem_base = 256 * 1024 * 1024; |
| int bdf, max; |
| |
| foreachpci (bdf, max) |
| pci_setup_device(bdf, &io_base, &mem_base); |
| } |