dingusppc/devices/common/pci/pcibase.h
joevt 6d23e18c11 pci: Add PCI CardBus bridge.
PCCard is used by PowerBook G3 Wallstreet in Open Firmware 2.0.1.
CardBus is probed in New World Macs starting from at least Open Firmware 4.1.9f1 sometime after Open Firmware 3.1.1.

- Create PCIBase from common stuff in PCIDevice.
- Add PCIBridgeBase. These have a primary bus number, secondary bus number, and subordinate bus number which are used to determine if PCI type 1 config cycle should be passed.
- Change PCIBridge to use PCIBridgeBase instead of PCIDevice.
- Add PCICardBusBridge which uses PCIBridgeBase.
2024-03-03 16:00:55 -07:00

246 lines
8.8 KiB
C++

/*
DingusPPC - The Experimental PowerPC Macintosh emulator
Copyright (C) 2018-23 divingkatae and maximum
(theweirdo) spatium
(Contact divingkatae#1017 or powermax#2286 on Discord for more info)
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 3 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
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef PCI_BASE_H
#define PCI_BASE_H
#include <devices/common/mmiodevice.h>
#include <devices/common/pci/pcihost.h>
#include <cinttypes>
#include <functional>
#include <memory>
#include <string>
#include <vector>
/** PCI configuration space header types */
enum PCIHeaderType : uint8_t {
PCI_HEADER_TYPE_0 = 0, // PCI Device
PCI_HEADER_TYPE_1 = 1, // PCI-PCI Bridge
PCI_HEADER_TYPE_2 = 2, // PCI-To-Cardbus Bridge
};
/** PCI configuration space registers offsets */
enum {
PCI_CFG_DEV_ID = 0x00, // device and vendor IDs
PCI_CFG_STAT_CMD = 0x04, // command/status register
PCI_CFG_CLASS_REV = 0x08, // class code and revision ID
PCI_CFG_DWORD_3 = 0x0C, // BIST, HeaderType, Lat_Timer and Cache_Line_Size
PCI_CFG_BAR0 = 0x10, // base address register 0 (type 0, 1, and 2)
PCI_CFG_BAR1 = 0x14, // base address register 1 (type 0 and 1)
PCI_CFG_DWORD_13 = 0x34, // capabilities pointer (type 0 and 1)
PCI_CFG_DWORD_15 = 0x3C, // Max_Lat, Min_Gnt (Type 0); Bridge Control (Type 1 and 2); Int_Pin and Int_Line registers (type 0, 1, and 2)
};
/** PCI Vendor IDs for devices used in Power Macintosh computers. */
enum {
PCI_VENDOR_ATI = 0x1002,
PCI_VENDOR_DEC = 0x1011,
PCI_VENDOR_MOTOROLA = 0x1057,
PCI_VENDOR_APPLE = 0x106B,
PCI_VENDOR_NVIDIA = 0x10DE,
};
/** PCI BAR types */
enum PCIBarType : uint32_t {
Unused = 0,
Io_16_Bit,
Io_32_Bit,
Mem_20_Bit, // legacy type for < 1MB memory
Mem_32_Bit,
Mem_64_Bit_Lo,
Mem_64_Bit_Hi
};
typedef struct {
uint32_t bar_num;
uint32_t bar_cfg;
} BarConfig;
class PCIBase : public MMIODevice {
public:
PCIBase(std::string name, PCIHeaderType hdr_type, int num_bars);
virtual ~PCIBase() = default;
virtual bool supports_io_space() {
return has_io_space;
};
/* I/O space access methods */
virtual bool pci_io_read(uint32_t offset, uint32_t size, uint32_t* res) {
return false;
};
virtual bool pci_io_write(uint32_t offset, uint32_t value, uint32_t size) {
return false;
};
// configuration space access methods
virtual uint32_t pci_cfg_read(uint32_t reg_offs, AccessDetails &details);
virtual void pci_cfg_write(uint32_t reg_offs, uint32_t value, AccessDetails &details);
// plugin interface for using in the derived classes
std::function<uint16_t()> pci_rd_stat;
std::function<void(uint16_t)> pci_wr_stat;
std::function<uint16_t()> pci_rd_cmd;
std::function<void(uint16_t)> pci_wr_cmd;
std::function<uint8_t()> pci_rd_bist;
std::function<void(uint8_t)> pci_wr_bist;
std::function<uint8_t()> pci_rd_lat_timer;
std::function<void(uint8_t)> pci_wr_lat_timer;
std::function<uint8_t()> pci_rd_cache_lnsz;
std::function<void(uint8_t)> pci_wr_cache_lnsz;
std::function<void(int)> pci_notify_bar_change;
int attach_exp_rom_image(const std::string img_path);
virtual void set_host(PCIHost* host_instance) {
this->host_instance = host_instance;
};
virtual void set_multi_function(bool is_multi_function) {
this->hdr_type = is_multi_function ? (this->hdr_type | 0x80) : (this->hdr_type & 0x7f);
}
virtual void set_irq_pin(uint8_t irq_pin) {
this->irq_pin = irq_pin;
}
// MMIODevice methods
virtual uint32_t read(uint32_t rgn_start, uint32_t offset, int size) { return 0; }
virtual void write(uint32_t rgn_start, uint32_t offset, uint32_t value, int size) { }
protected:
void set_bar_value(int bar_num, uint32_t value);
void setup_bars(std::vector<BarConfig> cfg_data);
void finish_config_bars();
void pci_wr_exp_rom_bar(uint32_t data);
void map_exp_rom_mem();
void unmap_exp_rom_mem();
std::string pci_name; // human-readable device name
PCIHost* host_instance; // host bridge instance to call back
// PCI configuration space state (type 0, 1, and 2)
uint16_t vendor_id;
uint16_t device_id;
uint16_t command = 0;
uint16_t status = 0;
uint32_t class_rev; // class code and revision id
uint8_t cache_ln_sz = 0; // cache line size
uint8_t lat_timer = 0; // latency timer
uint8_t hdr_type; // header type, single function
uint8_t cap_ptr = 0; // capabilities ptr
uint8_t irq_pin = 0;
uint8_t irq_line = 0;
bool has_io_space = false;
int num_bars; // number of BARs. Type 0:6, type 1:2, type 2:1 (4K)
uint32_t bars[6] = { 0 }; // BARs (base address registers)
uint32_t bars_cfg[6] = { 0 }; // configuration values for BARs
PCIBarType bars_typ[6] = { PCIBarType::Unused }; // types for BARs
// PCI configuration space state (type 0 and 1)
uint32_t exp_bar_cfg = 0; // expansion ROM configuration
uint32_t exp_rom_bar = 0; // expansion ROM base address register
uint32_t exp_rom_addr = 0; // expansion ROM base address
uint32_t exp_rom_size = 0; // expansion ROM size in bytes
std::unique_ptr<uint8_t[]> exp_rom_data;
};
inline uint32_t pci_cfg_log(uint32_t value, AccessDetails &details) {
switch (details.size << 2 | details.offset) {
case 0x04: return (uint8_t) value;
case 0x05: return (uint8_t)(value >> 8);
case 0x06: return (uint8_t)(value >> 16);
case 0x07: return (uint8_t)(value >> 24);
case 0x08: return (uint16_t) value;
case 0x09: return (uint16_t) (value >> 8);
case 0x0a: return (uint16_t) (value >> 16);
case 0x0b: return (uint16_t)((value >> 24) | (value << 8));
case 0x10: return value;
case 0x11: return (value >> 8) | (value << 24);
case 0x12: return (value >> 16) | (value << 16);
case 0x13: return (value >> 24) | (value << 8);
default: return 0xffffffff;
}
}
#define SIZE_ARGS details.size == 4 ? 'l' : details.size == 2 ? 'w' : \
details.size == 1 ? 'b' : '0' + details.size
#define LOG_READ_UNIMPLEMENTED_CONFIG_REGISTER() \
do { if ((details.flags & PCI_CONFIG_DIRECTION) == PCI_CONFIG_READ) { \
VLOG_F( \
(~-details.size & details.offset) ? loguru::Verbosity_ERROR : loguru::Verbosity_WARNING, \
"%s: read unimplemented config register @%02x.%c", \
this->name.c_str(), reg_offs + details.offset, \
SIZE_ARGS \
); \
} } while(0)
#define LOG_NAMED_CONFIG_REGISTER(reg_verb, reg_name) \
VLOG_F( \
(~-details.size & details.offset) ? loguru::Verbosity_ERROR : loguru::Verbosity_WARNING, \
"%s: %s %s register @%02x.%c = %0*x", \
this->name.c_str(), reg_verb, reg_name, reg_offs + details.offset, \
SIZE_ARGS, \
details.size * 2, pci_cfg_log(value, details) \
)
#define LOG_READ_NAMED_CONFIG_REGISTER(reg_name) \
do { if ((details.flags & PCI_CONFIG_DIRECTION) == PCI_CONFIG_READ) { \
LOG_NAMED_CONFIG_REGISTER("read ", reg_name); \
} } while(0)
#define LOG_WRITE_NAMED_CONFIG_REGISTER(reg_name) \
LOG_NAMED_CONFIG_REGISTER("write", reg_name)
#define LOG_READ_UNIMPLEMENTED_CONFIG_REGISTER_WITH_VALUE() \
LOG_READ_NAMED_CONFIG_REGISTER("unimplemented config")
#define LOG_WRITE_UNIMPLEMENTED_CONFIG_REGISTER() \
LOG_WRITE_NAMED_CONFIG_REGISTER("unimplemented config")
#define LOG_READ_NON_EXISTENT_PCI_DEVICE() \
LOG_F( \
ERROR, \
"%s err: read attempt from non-existent PCI device %02x:%02x.%x @%02x.%c", \
this->name.c_str(), bus_num, dev_num, fun_num, reg_offs + details.offset, \
SIZE_ARGS \
)
#define LOG_WRITE_NON_EXISTENT_PCI_DEVICE() \
LOG_F( \
ERROR, \
"%s err: write attempt to non-existent PCI device %02x:%02x.%x @%02x.%c = %0*x", \
this->name.c_str(), bus_num, dev_num, fun_num, reg_offs + details.offset, \
SIZE_ARGS, \
details.size * 2, BYTESWAP_SIZED(value, details.size) \
)
#endif /* PCI_BASE_H */