Merge pull request #41 from joevt/fix-pci-unaligned

Fix PCI config r/w of byte and word and unaligned.
This commit is contained in:
Maxim Poliakovski 2023-01-23 13:26:06 +01:00 committed by GitHub
commit 699b62373a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 414 additions and 437 deletions

View File

@ -41,9 +41,11 @@ const int MultiplyDeBruijnBitPosition2[] =
#define WHAT_BIT_SET(val) (MultiplyDeBruijnBitPosition2[(uint32_t)(val * 0x077CB531U) >> 27])
Bandit::Bandit(int bridge_num, std::string name, int dev_id, int rev)
: PCIHost(), PCIDevice(name)
: BanditHost()
{
supports_types(HWCompType::PCI_HOST | HWCompType::PCI_DEV);
this->name = name;
supports_types(HWCompType::PCI_HOST);
this->base_addr = 0xF0000000 + ((bridge_num & 3) << 25);
@ -58,6 +60,15 @@ Bandit::Bandit(int bridge_num, std::string name, int dev_id, int rev)
// base_addr + 0x1000000 --> pass-through memory space (not included below)
mem_ctrl->add_mmio_region(base_addr, 0x01000000, this);
std::string banditpcitname = "Bandit1PCI";
attach_pci_device(banditpcitname, 1);
}
BanditPCI::BanditPCI(int bridge_num, std::string name)
: PCIDevice(name)
{
supports_types(HWCompType::PCI_DEV);
// prepare the PCI config header
this->vendor_id = PCI_VENDOR_APPLE;
this->device_id = 0x0001;
@ -80,195 +91,149 @@ Bandit::Bandit(int bridge_num, std::string name, int dev_id, int rev)
this->rd_hold_off_cnt = 8;
}
uint32_t Bandit::pci_cfg_read(uint32_t reg_offs, uint32_t size)
uint32_t BanditPCI::pci_cfg_read(uint32_t reg_offs, AccessDetails &details)
{
if (reg_offs < 64) {
return PCIDevice::pci_cfg_read(reg_offs, size);
return PCIDevice::pci_cfg_read(reg_offs, details);
}
switch (reg_offs) {
case BANDIT_ADDR_MASK:
return BYTESWAP_32(this->addr_mask);
return this->addr_mask;
case BANDIT_MODE_SELECT:
return BYTESWAP_32(this->mode_ctrl);
return this->mode_ctrl;
case BANDIT_ARBUS_RD_HOLD_OFF:
return BYTESWAP_32(this->rd_hold_off_cnt);
default:
LOG_F(WARNING, "%s: reading from unimplemented config register at 0x%X",
this->pci_name.c_str(), reg_offs);
return this->rd_hold_off_cnt;
}
LOG_READ_UNIMPLEMENTED_CONFIG_REGISTER();
return 0;
}
void Bandit::pci_cfg_write(uint32_t reg_offs, uint32_t value, uint32_t size)
void BanditPCI::pci_cfg_write(uint32_t reg_offs, uint32_t value, AccessDetails &details)
{
if (reg_offs < 64) {
PCIDevice::pci_cfg_write(reg_offs, value, size);
PCIDevice::pci_cfg_write(reg_offs, value, details);
return;
}
if (size == 4) {
value = BYTESWAP_32(value);
} else {
LOG_F(WARNING, "%s: non-DWORD writes to the control registers not supported",
this->pci_name.c_str());
}
switch (reg_offs) {
case BANDIT_ADDR_MASK:
this->addr_mask = value;
this->verbose_address_space();
break;
return;
case BANDIT_MODE_SELECT:
this->mode_ctrl = value;
break;
return;
case BANDIT_ARBUS_RD_HOLD_OFF:
this->rd_hold_off_cnt = value & 0x1F;
break;
default:
LOG_F(WARNING, "%s: writing to unimplemented config register at 0x%X",
this->pci_name.c_str(), reg_offs);
this->rd_hold_off_cnt = value & 0x1F;
return;
}
LOG_WRITE_UNIMPLEMENTED_CONFIG_REGISTER();
}
void BanditHost::cfg_setup(uint32_t offset, int size, int &bus_num, int &dev_num, int &fun_num, uint8_t &reg_offs, AccessDetails &details, PCIDevice *&device)
{
device = NULL;
details.size = size;
details.offset = offset & 3;
fun_num = (this->config_addr >> 8) & 7;
reg_offs = this->config_addr & 0xFCU;
if (this->config_addr & BANDIT_CAR_TYPE) { // type 1 configuration command
details.flags = PCI_CONFIG_TYPE_1;
bus_num = (this->config_addr >> 16) & 255;
dev_num = (this->config_addr >> 11) & 31;
device = pci_find_device(bus_num, dev_num, fun_num);
return;
}
details.flags = PCI_CONFIG_TYPE_0;
bus_num = 0; // bus number is meaningless for type 0 configuration command; a type 1 configuration command cannot reach devices attached directly to the host
uint32_t idsel = this->config_addr & 0xFFFFF800U;
if (!SINGLE_BIT_SET(idsel)) {
for (dev_num = -1, idsel = this->config_addr; idsel; idsel >>= 1, dev_num++) {}
LOG_F(ERROR, "%s: config_addr 0x%08x does not contain valid IDSEL", this->name.c_str(), (uint32_t)this->config_addr);
return;
}
dev_num = WHAT_BIT_SET(idsel);
if (this->dev_map.count(idsel >> 11)) {
device = this->dev_map[idsel >> 11];
}
}
uint32_t Bandit::read(uint32_t rgn_start, uint32_t offset, int size)
uint32_t BanditHost::read(uint32_t rgn_start, uint32_t offset, int size)
{
int bus_num, dev_num, fun_num;
uint8_t reg_offs;
uint32_t result, idsel;
switch (offset >> 22) {
if (offset & BANDIT_CONFIG_SPACE) {
if (offset & 0x00400000) {
fun_num = (this->config_addr >> 8) & 7;
reg_offs = this->config_addr & 0xFCU;
// access to the CONFIG_DATA pseudo-register causes a Config Cycle
if (this->config_addr & BANDIT_CAR_TYPE) {
bus_num = (this->config_addr >> 16) & 255;
dev_num = (this->config_addr >> 11) & 31;
LOG_F(
WARNING, "%s: read config cycle type 1 not supported yet %02x:%02x.%x @%02x.%c",
this->name.c_str(), bus_num, dev_num, fun_num, reg_offs + (offset & 3),
size == 4 ? 'l' : size == 2 ? 'w' : size == 1 ? 'b' : '0' + size
);
return 0xFFFFFFFFUL; // PCI spec §6.1
case 3: // 0xC00000 // CONFIG_DATA
int bus_num, dev_num, fun_num;
uint8_t reg_offs;
AccessDetails details;
PCIDevice *device;
cfg_setup(offset, size, bus_num, dev_num, fun_num, reg_offs, details, device);
details.flags |= PCI_CONFIG_READ;
if (device) {
return pci_cfg_rev_read(device->pci_cfg_read(reg_offs, details), details);
}
LOG_READ_NON_EXISTENT_PCI_DEVICE();
return 0xFFFFFFFFUL; // PCI spec §6.1
idsel = (this->config_addr >> 11) & 0x1FFFFFU;
case 2: // 0x800000 // CONFIG_ADDR
return this->config_addr;
if (!SINGLE_BIT_SET(idsel)) {
LOG_F(
ERROR, "%s: read invalid IDSEL=0x%X config:0x%X ??:??.%x? @%02x?.%c",
this->name.c_str(), idsel, this->config_addr,
fun_num, reg_offs + (offset & 3),
size == 4 ? 'l' : size == 2 ? 'w' : size == 1 ? 'b' : '0' + size
);
return 0xFFFFFFFFUL; // PCI spec §6.1
}
if (idsel == BANDIT_ID_SEL) { // access to myself
result = this->pci_cfg_read(reg_offs, size);
} else {
if (this->dev_map.count(idsel)) {
result = this->dev_map[idsel]->pci_cfg_read(reg_offs, size);
} else {
dev_num = WHAT_BIT_SET(idsel) + 11;
LOG_F(
ERROR, "%s err: read attempt from non-existing PCI device ??:%02x.%x @%02x.%c",
this->name.c_str(), dev_num, fun_num, reg_offs + (offset & 3),
size == 4 ? 'l' : size == 2 ? 'w' : size == 1 ? 'b' : '0' + size
);
return 0xFFFFFFFFUL; // PCI spec §6.1
default: // 0x000000 // I/O space
{
uint32_t result;
// broadcast I/O request to devices that support I/O space
// until a device returns true that means "request accepted"
for (auto& dev : this->io_space_devs) {
if (dev->pci_io_read(offset, size, &result)) {
return result;
}
}
} else {
result = this->config_addr;
LOG_F(ERROR, "%s: attempt to read from unmapped PCI I/O space, offset=0x%X",
this->name.c_str(), offset);
return 0;
}
} else { // I/O space access
// broadcast I/O request to devices that support I/O space
// until a device returns true that means "request accepted"
for (auto& dev : this->io_space_devs) {
if (dev->pci_io_read(offset, size, &result)) {
return result;
}
}
LOG_F(ERROR, "%s: attempt to read from unmapped PCI I/O space, offset=0x%X",
this->name.c_str(), offset);
}
return result;
}
void Bandit::write(uint32_t rgn_start, uint32_t offset, uint32_t value, int size)
void BanditHost::write(uint32_t rgn_start, uint32_t offset, uint32_t value, int size)
{
int bus_num, dev_num, fun_num;
uint8_t reg_offs;
uint32_t idsel;
switch (offset >> 22) {
if (offset & BANDIT_CONFIG_SPACE) {
if (offset & 0x00400000) {
fun_num = (this->config_addr >> 8) & 7;
reg_offs = this->config_addr & 0xFCU;
// access to the CONFIG_DATA pseudo-register causes a Config Cycle
if (this->config_addr & BANDIT_CAR_TYPE) {
bus_num = (this->config_addr >> 16) & 255;
dev_num = (this->config_addr >> 11) & 31;
LOG_F(
WARNING, "%s: write config cycle type 1 not supported yet %02x:%02x.%x @%02x.%c = %0*x",
this->name.c_str(), bus_num, dev_num, fun_num, reg_offs + (offset & 3),
size == 4 ? 'l' : size == 2 ? 'w' : size == 1 ? 'b' : '0' + size,
size * 2, BYTESWAP_SIZED(value, size)
);
case 3: // 0xC00000 // CONFIG_DATA
int bus_num, dev_num, fun_num;
uint8_t reg_offs;
AccessDetails details;
PCIDevice *device;
cfg_setup(offset, size, bus_num, dev_num, fun_num, reg_offs, details, device);
details.flags |= PCI_CONFIG_WRITE;
if (device) {
uint32_t oldvalue = details.size == 4 ? 0 : device->pci_cfg_read(reg_offs, details);
value = pci_cfg_rev_write(oldvalue, details, value);
device->pci_cfg_write(reg_offs, value, details);
return;
}
LOG_WRITE_NON_EXISTENT_PCI_DEVICE();
break;
idsel = (this->config_addr >> 11) & 0x1FFFFFU;
if (!SINGLE_BIT_SET(idsel)) {
LOG_F(
ERROR, "%s: write invalid IDSEL=0x%X config:0x%X ??:??.%x? @%02x?.%c = %0*x",
this->name.c_str(), idsel, this->config_addr,
fun_num, reg_offs + (offset & 3),
size == 4 ? 'l' : size == 2 ? 'w' : size == 1 ? 'b' : '0' + size,
size * 2, BYTESWAP_SIZED(value, size)
);
return;
}
if (idsel == BANDIT_ID_SEL) { // access to myself
this->pci_cfg_write(reg_offs, value, size);
return;
}
if (this->dev_map.count(idsel)) {
this->dev_map[idsel]->pci_cfg_write(reg_offs, value, size);
} else {
dev_num = WHAT_BIT_SET(idsel) + 11;
LOG_F(
ERROR, "%s err: write attempt to non-existing PCI device ??:%02x.%x @%02x.%c = %0*x",
this->name.c_str(), dev_num, fun_num, reg_offs + (offset & 3),
size == 4 ? 'l' : size == 2 ? 'w' : size == 1 ? 'b' : '0' + size,
size * 2, BYTESWAP_SIZED(value, size)
);
}
} else {
case 2: // 0x800000 // CONFIG_ADDR
this->config_addr = BYTESWAP_32(value);
}
} else { // I/O space access
// broadcast I/O request to devices that support I/O space
// until a device returns true that means "request accepted"
for (auto& dev : this->io_space_devs) {
if (dev->pci_io_write(offset, value, size)) {
return;
break;
default: // 0x000000 // I/O space
// broadcast I/O request to devices that support I/O space
// until a device returns true that means "request accepted"
for (auto& dev : this->io_space_devs) {
if (dev->pci_io_write(offset, value, size)) {
return;
}
}
}
LOG_F(ERROR, "%s: attempt to write to unmapped PCI I/O space, offset=0x%X",
this->name.c_str(), offset);
LOG_F(ERROR, "%s: attempt to write to unmapped PCI I/O space, offset=0x%X",
this->name.c_str(), offset);
}
}
void Bandit::verbose_address_space()
void BanditPCI::verbose_address_space()
{
uint32_t mask;
int bit_pos;
@ -296,7 +261,7 @@ void Bandit::verbose_address_space()
}
}
Chaos::Chaos(std::string name) : PCIHost()
Chaos::Chaos(std::string name) : BanditHost()
{
this->name = name;
@ -312,118 +277,6 @@ Chaos::Chaos(std::string name) : PCIHost()
mem_ctrl->add_mmio_region(0xF0000000UL, 0x01000000, this);
}
uint32_t Chaos::read(uint32_t rgn_start, uint32_t offset, int size)
{
int bus_num, dev_num, fun_num;
uint8_t reg_offs;
uint32_t result, idsel;
if (offset & BANDIT_CONFIG_SPACE) {
if (offset & 0x00400000) {
fun_num = (this->config_addr >> 8) & 7;
reg_offs = this->config_addr & 0xFCU;
// access to the CONFIG_DATA pseudo-register causes a Config Cycle
if (this->config_addr & BANDIT_CAR_TYPE) {
bus_num = (this->config_addr >> 16) & 255;
dev_num = (this->config_addr >> 11) & 31;
LOG_F(
WARNING, "%s: read config cycle type 1 not supported yet %02x:%02x.%x @%02x.%c",
this->name.c_str(), bus_num, dev_num, fun_num, reg_offs + (offset & 3),
size == 4 ? 'l' : size == 2 ? 'w' : size == 1 ? 'b' : '0' + size
);
return 0xFFFFFFFFUL; // PCI spec §6.1
}
idsel = (this->config_addr >> 11) & 0x1FFFFFU;
if (!SINGLE_BIT_SET(idsel)) {
LOG_F(
ERROR, "%s: read invalid IDSEL=0x%X config:0x%X ??:??.%x? @%02x?.%c",
this->name.c_str(), idsel, this->config_addr,
fun_num, reg_offs + (offset & 3),
size == 4 ? 'l' : size == 2 ? 'w' : size == 1 ? 'b' : '0' + size
);
return 0xFFFFFFFFUL; // PCI spec §6.1
}
if (this->dev_map.count(idsel)) {
result = this->dev_map[idsel]->pci_cfg_read(reg_offs, size);
} else {
dev_num = WHAT_BIT_SET(idsel) + 11;
LOG_F(
ERROR, "%s err: read attempt from non-existing VCI device ??:%02x.%x @%02x.%c",
this->name.c_str(), dev_num, fun_num, reg_offs + (offset & 3),
size == 4 ? 'l' : size == 2 ? 'w' : size == 1 ? 'b' : '0' + size
);
return 0xFFFFFFFFUL; // PCI spec §6.1
}
} else {
result = this->config_addr;
}
} else { // I/O space access
LOG_F(ERROR, "%s: I/O space not supported", this->name.c_str());
return 0;
}
return result;
}
void Chaos::write(uint32_t rgn_start, uint32_t offset, uint32_t value, int size)
{
int bus_num, dev_num, fun_num;
uint8_t reg_offs;
uint32_t idsel;
if (offset & BANDIT_CONFIG_SPACE) {
if (offset & 0x00400000) {
fun_num = (this->config_addr >> 8) & 7;
reg_offs = this->config_addr & 0xFCU;
// access to the CONFIG_DATA pseudo-register causes a Config Cycle
if (this->config_addr & BANDIT_CAR_TYPE) {
bus_num = (this->config_addr >> 16) & 255;
dev_num = (this->config_addr >> 11) & 31;
LOG_F(
WARNING, "%s: write config cycle type 1 not supported yet %02x:%02x.%x @%02x.%c = %0*x",
this->name.c_str(), bus_num, dev_num, fun_num, reg_offs + (offset & 3),
size == 4 ? 'l' : size == 2 ? 'w' : size == 1 ? 'b' : '0' + size,
size * 2, BYTESWAP_SIZED(value, size)
);
return;
}
idsel = (this->config_addr >> 11) & 0x1FFFFFU;
if (!SINGLE_BIT_SET(idsel)) {
LOG_F(
ERROR, "%s: write invalid IDSEL=0x%X config:0x%X ??:??.%x? @%02x?.%c = %0*x",
this->name.c_str(), idsel, this->config_addr,
fun_num, reg_offs + (offset & 3),
size == 4 ? 'l' : size == 2 ? 'w' : size == 1 ? 'b' : '0' + size,
size * 2, BYTESWAP_SIZED(value, size)
);
return;
}
if (this->dev_map.count(idsel)) {
this->dev_map[idsel]->pci_cfg_write(reg_offs, value, size);
} else {
dev_num = WHAT_BIT_SET(idsel) + 11;
LOG_F(
ERROR, "%s err: write attempt to non-existing VCI device ??:%02x.%x @%02x.%c = %0*x",
this->name.c_str(), dev_num, fun_num, reg_offs + (offset & 3),
size == 4 ? 'l' : size == 2 ? 'w' : size == 1 ? 'b' : '0' + size,
size * 2, BYTESWAP_SIZED(value, size)
);
}
} else {
this->config_addr = BYTESWAP_32(value);
}
} else { // I/O space access
LOG_F(ERROR, "%s: I/O space not supported", this->name.c_str());
}
}
static const DeviceDescription Bandit1_Descriptor = {
Bandit::create_first, {}, {}
};
@ -436,6 +289,11 @@ static const DeviceDescription Chaos_Descriptor = {
Chaos::create, {}, {}
};
REGISTER_DEVICE(Bandit1, Bandit1_Descriptor);
REGISTER_DEVICE(PsxPci1, PsxPci1_Descriptor);
REGISTER_DEVICE(Chaos, Chaos_Descriptor);
static const DeviceDescription Bandit1PCI_Descriptor = {
BanditPCI::create_first, {}, {}
};
REGISTER_DEVICE(Bandit1 , Bandit1_Descriptor);
REGISTER_DEVICE(Bandit1PCI, Bandit1PCI_Descriptor);
REGISTER_DEVICE(PsxPci1 , PsxPci1_Descriptor);
REGISTER_DEVICE(Chaos , Chaos_Descriptor);

View File

@ -38,7 +38,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <string>
#include <unordered_map>
#define BANDIT_ID_SEL (1 << 0) // Bandit's own IDSEL
#define BANDIT_DEV (11) // Bandit's own device number
#define BANDIT_CAR_TYPE (1 << 0) // Bandit config address type bit
#define BANDIT_CONFIG_SPACE 0x00800000 // Bandit Config Space bit
@ -52,7 +52,19 @@ enum {
/** checks if one bit is set at time, return 0 if not */
#define SINGLE_BIT_SET(val) ((val) && !((val) & ((val)-1)))
class Bandit : public PCIHost, public PCIDevice {
class BanditHost : public PCIHost, public MMIODevice {
public:
void cfg_setup(uint32_t offset, int size, int &bus_num, int &dev_num, int &fun_num, uint8_t &reg_offs, AccessDetails &details, PCIDevice *&device);
// MMIODevice methods
uint32_t read(uint32_t rgn_start, uint32_t offset, int size);
void write(uint32_t rgn_start, uint32_t offset, uint32_t value, int size);
protected:
uint32_t config_addr;
};
class Bandit : public BanditHost {
public:
Bandit(int bridge_num, std::string name, int dev_id=1, int rev=3);
~Bandit() = default;
@ -65,19 +77,27 @@ public:
return std::unique_ptr<Bandit>(new Bandit(1, "PSX-PCI1", 8, 0));
};
uint32_t pci_cfg_read(uint32_t reg_offs, uint32_t size);
void pci_cfg_write(uint32_t reg_offs, uint32_t value, uint32_t size);
private:
uint32_t base_addr;
};
// MMIODevice methods
uint32_t read(uint32_t rgn_start, uint32_t offset, int size);
void write(uint32_t rgn_start, uint32_t offset, uint32_t value, int size);
class BanditPCI : public PCIDevice {
public:
BanditPCI(int bridge_num, std::string name);
~BanditPCI() = default;
static std::unique_ptr<HWComponent> create_first() {
return std::unique_ptr<BanditPCI>(new BanditPCI(1, "BanditPCI"));
};
// PCIDevice methods
uint32_t pci_cfg_read(uint32_t reg_offs, AccessDetails &details);
void pci_cfg_write(uint32_t reg_offs, uint32_t value, AccessDetails &details);
protected:
void verbose_address_space();
private:
uint32_t base_addr;
uint32_t config_addr;
uint32_t addr_mask;
uint32_t mode_ctrl; // controls various chip modes/features
uint32_t rd_hold_off_cnt;
@ -88,7 +108,7 @@ private:
frequency as the CPU bus (40-50 MHz) and provides an interface
between video input/output devices and the CPU bus.
*/
class Chaos : public PCIHost, public MMIODevice {
class Chaos : public BanditHost {
public:
Chaos(std::string name);
~Chaos() = default;
@ -96,13 +116,6 @@ public:
static std::unique_ptr<HWComponent> create() {
return std::unique_ptr<Chaos>(new Chaos("VCI0"));
};
// MMIODevice methods
uint32_t read(uint32_t rgn_start, uint32_t offset, int size);
void write(uint32_t rgn_start, uint32_t offset, uint32_t value, int size);
private:
uint32_t config_addr;
};
#endif // BANDIT_PCI_H

View File

@ -50,81 +50,49 @@ PCIDevice::PCIDevice(std::string name)
this->pci_notify_bar_change = [](int bar_num) {};
};
uint32_t PCIDevice::pci_cfg_read(uint32_t reg_offs, uint32_t size)
uint32_t PCIDevice::pci_cfg_read(uint32_t reg_offs, AccessDetails &details)
{
uint32_t result;
switch (reg_offs) {
case PCI_CFG_DEV_ID:
result = (this->device_id << 16) | (this->vendor_id);
break;
return (this->device_id << 16) | (this->vendor_id);
case PCI_CFG_STAT_CMD:
result = (this->pci_rd_stat() << 16) | (this->pci_rd_cmd());
break;
return (this->pci_rd_stat() << 16) | (this->pci_rd_cmd());
case PCI_CFG_CLASS_REV:
result = this->class_rev;
break;
return this->class_rev;
case PCI_CFG_DWORD_3:
result = (pci_rd_bist() << 24) | (this->hdr_type << 16) |
(pci_rd_lat_timer() << 8) | pci_rd_cache_lnsz();
break;
return (pci_rd_bist() << 24) | (this->hdr_type << 16) |
(pci_rd_lat_timer() << 8) | pci_rd_cache_lnsz();
case PCI_CFG_BAR0:
case PCI_CFG_BAR1:
case PCI_CFG_BAR2:
case PCI_CFG_BAR3:
case PCI_CFG_BAR4:
case PCI_CFG_BAR5:
result = this->bars[(reg_offs - 0x10) >> 2];
break;
return this->bars[(reg_offs - 0x10) >> 2];
case PCI_CFG_SUBSYS_ID:
result = (this->subsys_id << 16) | (this->subsys_vndr);
break;
return (this->subsys_id << 16) | (this->subsys_vndr);
case PCI_CFG_ROM_BAR:
result = this->exp_rom_bar;
break;
return this->exp_rom_bar;
case PCI_CFG_DWORD_15:
result = (max_lat << 24) | (min_gnt << 16) | (irq_pin << 8) | irq_line;
break;
return (max_lat << 24) | (min_gnt << 16) | (irq_pin << 8) | irq_line;
case PCI_CFG_CAP_PTR:
result = cap_ptr;
break;
default:
LOG_F(
WARNING, "%s: attempt to read from reserved/unimplemented register @%02x.%c",
this->pci_name.c_str(), reg_offs,
size == 4 ? 'l' : size == 2 ? 'w' : size == 1 ? 'b' : '0' + size
);
return 0;
}
if (size == 4) {
return BYTESWAP_32(result);
} else {
return read_mem_rev(((uint8_t *)&result) + (reg_offs & 3), size);
return cap_ptr;
}
LOG_READ_UNIMPLEMENTED_CONFIG_REGISTER();
return 0;
}
void PCIDevice::pci_cfg_write(uint32_t reg_offs, uint32_t value, uint32_t size)
void PCIDevice::pci_cfg_write(uint32_t reg_offs, uint32_t value, AccessDetails &details)
{
uint32_t data;
if (size == 4) {
data = BYTESWAP_32(value);
} else {
// get current register content as DWORD and update it partially
data = BYTESWAP_32(this->pci_cfg_read(reg_offs, 4));
write_mem_rev(((uint8_t *)&data) + (reg_offs & 3), value, size);
}
switch (reg_offs) {
case PCI_CFG_STAT_CMD:
this->pci_wr_stat(data >> 16);
this->pci_wr_cmd(data & 0xFFFFU);
this->pci_wr_stat(value >> 16);
this->pci_wr_cmd(value & 0xFFFFU);
break;
case PCI_CFG_DWORD_3:
this->pci_wr_bist(data >> 24);
this->pci_wr_lat_timer((data >> 8) & 0xFF);
this->pci_wr_cache_lnsz(data & 0xFF);
this->pci_wr_bist(value >> 24);
this->pci_wr_lat_timer((value >> 8) & 0xFF);
this->pci_wr_cache_lnsz(value & 0xFF);
break;
case PCI_CFG_BAR0:
case PCI_CFG_BAR1:
@ -132,17 +100,17 @@ void PCIDevice::pci_cfg_write(uint32_t reg_offs, uint32_t value, uint32_t size)
case PCI_CFG_BAR3:
case PCI_CFG_BAR4:
case PCI_CFG_BAR5:
if (data == 0xFFFFFFFFUL) {
if (value == 0xFFFFFFFFUL) {
this->do_bar_sizing((reg_offs - 0x10) >> 2);
} else {
this->set_bar_value((reg_offs - 0x10) >> 2, data);
this->set_bar_value((reg_offs - 0x10) >> 2, value);
}
break;
case PCI_CFG_ROM_BAR:
if ((data & this->exp_bar_cfg) == this->exp_bar_cfg) {
this->exp_rom_bar = (data & (this->exp_bar_cfg | 1));
if ((value & this->exp_bar_cfg) == this->exp_bar_cfg) {
this->exp_rom_bar = (value & (this->exp_bar_cfg | 1));
} else {
this->exp_rom_bar = (data & (this->exp_bar_cfg | 1));
this->exp_rom_bar = (value & (this->exp_bar_cfg | 1));
if (this->exp_rom_bar & 1) {
this->map_exp_rom_mem();
} else {
@ -152,15 +120,10 @@ void PCIDevice::pci_cfg_write(uint32_t reg_offs, uint32_t value, uint32_t size)
}
break;
case PCI_CFG_DWORD_15:
this->irq_line = data >> 24;
this->irq_line = value >> 24;
break;
default:
LOG_F(
WARNING, "%s: attempt to write to reserved/unimplemented register @%02x.%c = %0*x",
this->pci_name.c_str(), reg_offs,
size == 4 ? 'l' : size == 2 ? 'w' : size == 1 ? 'b' : '0' + size,
size * 2, BYTESWAP_SIZED(value, size)
);
LOG_WRITE_UNIMPLEMENTED_CONFIG_REGISTER();
}
}

View File

@ -78,8 +78,8 @@ public:
};
// configuration space access methods
virtual uint32_t pci_cfg_read(uint32_t reg_offs, uint32_t size);
virtual void pci_cfg_write(uint32_t reg_offs, uint32_t value, uint32_t size);
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;
@ -101,6 +101,10 @@ public:
this->host_instance = host_instance;
};
// 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 do_bar_sizing(int bar_num);
void set_bar_value(int bar_num, uint32_t value);
@ -137,4 +141,124 @@ protected:
std::unique_ptr<uint8_t[]> exp_rom_data;
};
/* value is dword from PCI config. MSB..LSB of value is stored in PCI config as 0:LSB..3:MSB.
result is part of value at byte offset from LSB with size bytes (with wrap around) and flipped as required for pci_cfg_read result. */
inline uint32_t pci_cfg_rev_read(uint32_t value, AccessDetails &details) {
switch (details.size << 2 | details.offset) {
case 0x04: return value & 0xff; // 0
case 0x05: return (value >> 8) & 0xff; // 1
case 0x06: return (value >> 16) & 0xff; // 2
case 0x07: return (value >> 24) & 0xff; // 3
case 0x08: return ((value & 0xff) << 8) | ((value >> 8) & 0xff); // 0 1
case 0x09: return ( value & 0xff00) | ((value >> 16) & 0xff); // 1 2
case 0x0a: return ((value >> 8) & 0xff00) | ((value >> 24) & 0xff); // 2 3
case 0x0b: return ((value >> 16) & 0xff00) | ( value & 0xff); // 3 0
case 0x10: return ((value & 0xff) << 24) | ((value & 0xff00) << 8) | ((value >> 8) & 0xff00) | ((value >> 24) & 0xff); // 0 1 2 3
case 0x11: return ((value & 0xff00) << 16) | ( value & 0xff0000) | ((value >> 16) & 0xff00) | ( value & 0xff); // 1 2 3 0
case 0x12: return ((value & 0xff0000) << 8) | ((value >> 8) & 0xff0000) | ((value & 0xff) << 8) | ((value >> 8) & 0xff); // 2 3 0 1
case 0x13: return ( value & 0xff000000) | ((value & 0xff) << 16) | ( value & 0xff00) | ((value >> 16) & 0xff); // 3 0 1 2
default: return 0xffffffff;
}
}
/* value is dword from PCI config. MSB..LSB of value (3.2.1.0) is stored in PCI config as 0:LSB..3:MSB.
newvalue is flipped bytes (d0.d1.d2.d3, as passed to pci_cfg_write) to be merged into value.
result is part of value at byte offset from LSB with size bytes (with wrap around) modified by newvalue. */
inline uint32_t pci_cfg_rev_write(uint32_t value, AccessDetails &details, uint32_t newvalue) {
switch (details.size << 2 | details.offset) {
case 0x04: return (value & 0xffffff00) | (newvalue & 0xff); // 3 2 1 d0
case 0x05: return (value & 0xffff00ff) | ((newvalue & 0xff) << 8); // 3 2 d0 0
case 0x06: return (value & 0xff00ffff) | ((newvalue & 0xff) << 16); // 3 d0 1 0
case 0x07: return (value & 0x00ffffff) | ((newvalue & 0xff) << 24); // d0 2 1 0
case 0x08: return (value & 0xffff0000) | ((newvalue >> 8) & 0xff) | ((newvalue & 0xff) << 8); // 3 2 d1 d0
case 0x09: return (value & 0xff0000ff) | (newvalue & 0xff00) | ((newvalue & 0xff) << 16); // 3 d1 d0 0
case 0x0a: return (value & 0x0000ffff) | ((newvalue & 0xff00) << 8) | ((newvalue & 0xff) << 24); // d1 d0 1 0
case 0x0b: return (value & 0x00ffff00) | ((newvalue & 0xff00) << 16) | (newvalue & 0xff); // d0 2 1 d1
case 0x10: return ((newvalue & 0xff) << 24) | ((newvalue & 0xff00) << 8) | ((newvalue >> 8) & 0xff00) | ((newvalue >> 24) & 0xff); // d3 d2 d1 d0
case 0x11: return ((newvalue & 0xff00) << 16) | ( newvalue & 0xff0000) | ((newvalue >> 16) & 0xff00) | ( newvalue & 0xff); // d2 d1 d0 d3
case 0x12: return ((newvalue & 0xff0000) << 8) | ((newvalue >> 8) & 0xff0000) | ((newvalue & 0xff) << 8) | ((newvalue >> 8) & 0xff); // d1 d0 d3 d2
case 0x13: return ( newvalue & 0xff000000) | ((newvalue & 0xff) << 16) | ( newvalue & 0xff00) | ((newvalue >> 16) & 0xff); // d0 d3 d2 d1
default: return 0xffffffff;
}
}
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_DEVICE_H */

View File

@ -83,3 +83,8 @@ void PCIHost::attach_pci_device(std::string& dev_name, int slot_id)
this->pci_register_device(
slot_id, dynamic_cast<PCIDevice*>(gMachineObj->get_comp_by_name(dev_name)));
}
PCIDevice *PCIHost::pci_find_device(uint8_t bus_num, uint8_t dev_num, uint8_t fun_num)
{
return NULL;
}

View File

@ -29,6 +29,23 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <unordered_map>
#include <vector>
enum {
PCI_CONFIG_DIRECTION = 1,
PCI_CONFIG_READ = 0,
PCI_CONFIG_WRITE = 1,
PCI_CONFIG_TYPE = 4,
PCI_CONFIG_TYPE_0 = 0,
PCI_CONFIG_TYPE_1 = 4,
};
/** PCI config space access details */
typedef struct AccessDetails {
uint8_t size;
uint8_t offset;
uint8_t flags;
} AccessDetails;
class PCIDevice; // forward declaration to prevent errors
class PCIHost {
@ -46,6 +63,8 @@ public:
virtual void attach_pci_device(std::string& dev_name, int slot_id);
virtual PCIDevice *pci_find_device(uint8_t bus_num, uint8_t dev_num, uint8_t fun_num);
protected:
std::unordered_map<int, PCIDevice*> dev_map;
std::vector<PCIDevice*> io_space_devs;

View File

@ -72,6 +72,32 @@ int MPC106::device_postinit()
return 0;
}
void MPC106::cfg_setup(uint32_t offset, int size, int &bus_num, int &dev_num, int &fun_num, uint8_t &reg_offs, AccessDetails &details, PCIDevice *&device)
{
device = NULL;
details.size = size;
details.offset = offset & 3;
bus_num = (this->config_addr >> 8) & 0xFF;
dev_num = (this->config_addr >> 19) & 0x1F;
fun_num = (this->config_addr >> 16) & 0x07;
reg_offs = (this->config_addr >> 24) & 0xFC;
if (bus_num) {
details.flags = PCI_CONFIG_TYPE_1;
device = pci_find_device(bus_num, dev_num, fun_num);
}
else {
details.flags = PCI_CONFIG_TYPE_0;
if (dev_num == 0 && fun_num == 0) {
device = this; // dev_num 0 is assigned to myself
}
else if (this->dev_map.count(dev_num)) {
device = this->dev_map[dev_num];
}
}
}
uint32_t MPC106::read(uint32_t rgn_start, uint32_t offset, int size) {
uint32_t result;
@ -87,7 +113,7 @@ uint32_t MPC106::read(uint32_t rgn_start, uint32_t offset, int size) {
} else {
if (offset >= 0x200000) {
if (this->config_addr & 0x80) // process only if bit E (enable) is set
return pci_read(size);
return pci_read(offset, size);
}
}
@ -111,116 +137,83 @@ void MPC106::write(uint32_t rgn_start, uint32_t offset, uint32_t value, int size
this->config_addr = value;
} else {
if (this->config_addr & 0x80) // process only if bit E (enable) is set
return pci_write(value, size);
return pci_write(offset, value, size);
}
}
}
uint32_t MPC106::pci_read(uint32_t size) {
int bus_num, dev_num, fun_num, reg_offs;
bus_num = (this->config_addr >> 8) & 0xFF;
dev_num = (this->config_addr >> 19) & 0x1F;
fun_num = (this->config_addr >> 16) & 0x07;
reg_offs = (this->config_addr >> 24) & 0xFC;
if (bus_num) {
LOG_F(
ERROR,
"%s err: read attempt from non-local PCI bus, config_addr = %x %02x:%02x.%x @%02x.%c",
this->name.c_str(), this->config_addr, bus_num, dev_num, fun_num, reg_offs,
size == 4 ? 'l' : size == 2 ? 'w' : size == 1 ? 'b' : '0' + size
);
return 0xFFFFFFFFUL; // PCI spec §6.1
uint32_t MPC106::pci_read(uint32_t offset, uint32_t size) {
int bus_num, dev_num, fun_num;
uint8_t reg_offs;
AccessDetails details;
PCIDevice *device;
cfg_setup(offset, size, bus_num, dev_num, fun_num, reg_offs, details, device);
details.flags |= PCI_CONFIG_READ;
if (device) {
return pci_cfg_rev_read(device->pci_cfg_read(reg_offs, details), details);
}
if (dev_num == 0 && fun_num == 0) { // dev_num 0 is assigned to myself
return this->pci_cfg_read(reg_offs, size);
} else {
if (this->dev_map.count(dev_num)) {
return this->dev_map[dev_num]->pci_cfg_read(reg_offs, size);
} else {
LOG_F(
ERROR,
"%s err: read attempt from non-existing PCI device %02x:%02x.%x @%02x.%c",
this->name.c_str(), bus_num, dev_num, fun_num, reg_offs,
size == 4 ? 'l' : size == 2 ? 'w' : size == 1 ? 'b' : '0' + size
);
return 0xFFFFFFFFUL; // PCI spec §6.1
}
}
return 0;
LOG_READ_NON_EXISTENT_PCI_DEVICE();
return 0xFFFFFFFFUL; // PCI spec §6.1
}
void MPC106::pci_write(uint32_t value, uint32_t size) {
int bus_num, dev_num, fun_num, reg_offs;
bus_num = (this->config_addr >> 8) & 0xFF;
dev_num = (this->config_addr >> 19) & 0x1F;
fun_num = (this->config_addr >> 16) & 0x07;
reg_offs = (this->config_addr >> 24) & 0xFC;
if (bus_num) {
LOG_F(
ERROR,
"%s err: write attempt to non-local PCI bus, config_addr = %x %02x:%02x.%x @%02x.%c = %0*x",
this->name.c_str(), this->config_addr, bus_num, dev_num, fun_num, reg_offs,
size == 4 ? 'l' : size == 2 ? 'w' : size == 1 ? 'b' : '0' + size,
size * 2, BYTESWAP_SIZED(value, size)
);
void MPC106::pci_write(uint32_t offset, uint32_t value, uint32_t size) {
int bus_num, dev_num, fun_num;
uint8_t reg_offs;
AccessDetails details;
PCIDevice *device;
cfg_setup(offset, size, bus_num, dev_num, fun_num, reg_offs, details, device);
details.flags |= PCI_CONFIG_WRITE;
if (device) {
uint32_t oldvalue = details.size == 4 ? 0 : device->pci_cfg_read(reg_offs, details);
value = pci_cfg_rev_write(oldvalue, details, value);
device->pci_cfg_write(reg_offs, value, details);
return;
}
if (dev_num == 0 && fun_num == 0) { // dev_num 0 is assigned to myself
this->pci_cfg_write(reg_offs, value, size);
} else {
if (this->dev_map.count(dev_num)) {
this->dev_map[dev_num]->pci_cfg_write(reg_offs, value, size);
} else {
LOG_F(
ERROR,
"%s err: write attempt to non-existing PCI device %02x:%02x.%x @%02x.%c = %0*x",
this->name.c_str(), bus_num, dev_num, fun_num, reg_offs,
size == 4 ? 'l' : size == 2 ? 'w' : size == 1 ? 'b' : '0' + size,
size * 2, BYTESWAP_SIZED(value, size)
);
}
}
LOG_WRITE_NON_EXISTENT_PCI_DEVICE();
}
uint32_t MPC106::pci_cfg_read(uint32_t reg_offs, uint32_t size) {
uint32_t MPC106::pci_cfg_read(uint32_t reg_offs, AccessDetails &details) {
#ifdef MPC106_DEBUG
LOG_F(9, "read from Grackle register %08X", reg_offs);
#endif
if (reg_offs < 64) {
return PCIDevice::pci_cfg_read(reg_offs, size);
return PCIDevice::pci_cfg_read(reg_offs, details);
}
return read_mem(&this->my_pci_cfg_hdr[reg_offs], size);
uint32_t value = READ_DWORD_LE_A(&this->my_pci_cfg_hdr[reg_offs]);
if ((reg_offs >= 0x80 && reg_offs <= 0xA0) || reg_offs == 0xF0) {
return value;
}
LOG_READ_UNIMPLEMENTED_CONFIG_REGISTER_WITH_VALUE();
return value;
}
void MPC106::pci_cfg_write(uint32_t reg_offs, uint32_t value, uint32_t size) {
void MPC106::pci_cfg_write(uint32_t reg_offs, uint32_t value, AccessDetails &details) {
#ifdef MPC106_DEBUG
LOG_F(9, "write %08X to Grackle register %08X", value, reg_offs);
#endif
if (reg_offs < 64) {
PCIDevice::pci_cfg_write(reg_offs, value, size);
PCIDevice::pci_cfg_write(reg_offs, value, details);
return;
}
// FIXME: implement write-protection for read-only registers
write_mem(&this->my_pci_cfg_hdr[reg_offs], value, size);
uint32_t *addr = (uint32_t *)&this->my_pci_cfg_hdr[reg_offs];
WRITE_DWORD_LE_A(addr, value);
if (this->my_pci_cfg_hdr[0xF2] & 8) {
if ((reg_offs >= 0x80 && reg_offs <= 0xA0) || reg_offs == 0xF0) {
if (this->my_pci_cfg_hdr[0xF2] & 8) {
#ifdef MPC106_DEBUG
LOG_F(9, "MPC106: MCCR1[MEMGO] was set!");
LOG_F(9, "MPC106: MCCR1[MEMGO] was set!");
#endif
setup_ram();
setup_ram();
}
return;
}
LOG_WRITE_UNIMPLEMENTED_CONFIG_REGISTER();
}
void MPC106::setup_ram() {

View File

@ -59,12 +59,13 @@ public:
protected:
/* PCI access */
uint32_t pci_read(uint32_t size);
void pci_write(uint32_t value, uint32_t size);
uint32_t pci_read(uint32_t offset, uint32_t size);
void pci_write(uint32_t offset, uint32_t value, uint32_t size);
void cfg_setup(uint32_t offset, int size, int &bus_num, int &dev_num, int &fun_num, uint8_t &reg_offs, AccessDetails &details, PCIDevice *&device);
/* my own PCI configuration registers access */
uint32_t pci_cfg_read(uint32_t reg_offs, uint32_t size);
void pci_cfg_write(uint32_t reg_offs, uint32_t value, uint32_t size);
uint32_t pci_cfg_read(uint32_t reg_offs, AccessDetails &details);
void pci_cfg_write(uint32_t reg_offs, uint32_t value, AccessDetails &details);
bool supports_io_space(void) {
return true;

View File

@ -161,34 +161,35 @@ void ATIRage::notify_bar_change(int bar_num)
}
}
uint32_t ATIRage::pci_cfg_read(uint32_t reg_offs, uint32_t size)
uint32_t ATIRage::pci_cfg_read(uint32_t reg_offs, AccessDetails &details)
{
if (reg_offs < 64) {
return PCIDevice::pci_cfg_read(reg_offs, size);
return PCIDevice::pci_cfg_read(reg_offs, details);
}
switch (reg_offs) {
case 0x40:
return this->user_cfg;
default:
LOG_F(WARNING, "ATIRage: reading from unimplemented config register at 0x%X", reg_offs);
LOG_READ_UNIMPLEMENTED_CONFIG_REGISTER();
}
return 0;
}
void ATIRage::pci_cfg_write(uint32_t reg_offs, uint32_t value, uint32_t size)
void ATIRage::pci_cfg_write(uint32_t reg_offs, uint32_t value, AccessDetails &details)
{
if (reg_offs < 64) {
PCIDevice::pci_cfg_write(reg_offs, value, size);
} else {
switch (reg_offs) {
case 0x40:
this->user_cfg = value;
break;
default:
LOG_F(WARNING, "ATIRage: writing to unimplemented config register at 0x%X", reg_offs);
}
PCIDevice::pci_cfg_write(reg_offs, value, details);
return;
}
switch (reg_offs) {
case 0x40:
this->user_cfg = value;
break;
default:
LOG_WRITE_UNIMPLEMENTED_CONFIG_REGISTER();
}
}

View File

@ -64,8 +64,8 @@ public:
return true;
};
uint32_t pci_cfg_read(uint32_t reg_offs, uint32_t size);
void pci_cfg_write(uint32_t reg_offs, uint32_t value, uint32_t size);
uint32_t pci_cfg_read(uint32_t reg_offs, AccessDetails &details);
void pci_cfg_write(uint32_t reg_offs, uint32_t value, AccessDetails &details);
/* I/O space access methods */
bool pci_io_read(uint32_t offset, uint32_t size, uint32_t* res);