⛏️ index : haiku.git

/*
 * Copyright 2022, Haiku, Inc.
 * Distributed under the terms of the MIT License.
 */


#include "DWPCIController.h"
#include <bus/FDT.h>

#include <AutoDeleterDrivers.h>
#include <util/AutoLock.h>

#include <string.h>
#include <new>


static uint32
ReadReg8(addr_t adr)
{
	uint32 ofs = adr % 4;
	adr = adr / 4 * 4;
	union {
		uint32 in;
		uint8 out[4];
	} val{.in = *(vuint32*)adr};
	return val.out[ofs];
}


static uint32
ReadReg16(addr_t adr)
{
	uint32 ofs = adr / 2 % 2;
	adr = adr / 4 * 4;
	union {
		uint32 in;
		uint16 out[2];
	} val{.in = *(vuint32*)adr};
	return val.out[ofs];
}


static void
WriteReg8(addr_t adr, uint32 value)
{
	uint32 ofs = adr % 4;
	adr = adr / 4 * 4;
	union {
		uint32 in;
		uint8 out[4];
	} val{.in = *(vuint32*)adr};
	val.out[ofs] = (uint8)value;
	*(vuint32*)adr = val.in;
}


static void
WriteReg16(addr_t adr, uint32 value)
{
	uint32 ofs = adr / 2 % 2;
	adr = adr / 4 * 4;
	union {
		uint32 in;
		uint16 out[2];
	} val{.in = *(vuint32*)adr};
	val.out[ofs] = (uint16)value;
	*(vuint32*)adr = val.in;
}


//#pragma mark - driver


float
DWPCIController::SupportsDevice(device_node* parent)
{
	const char* bus;
	status_t status = gDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false);
	if (status < B_OK)
		return -1.0f;

	if (strcmp(bus, "fdt") != 0)
		return 0.0f;

	const char* compatible;
	status = gDeviceManager->get_attr_string(parent, "fdt/compatible", &compatible, false);
	if (status < B_OK)
		return -1.0f;

	// Support only a variant used in HiFive Unmatched board.
	// TODO: Support more Synapsis Designware IP core based PCIe host controllers.
	if (strcmp(compatible, "sifive,fu740-pcie") != 0)
		return 0.0f;

	return 1.0f;
}


status_t
DWPCIController::RegisterDevice(device_node* parent)
{
	device_attr attrs[] = {
		{ B_DEVICE_PRETTY_NAME, B_STRING_TYPE, {.string = "Designware PCI Host Controller"} },
		{ B_DEVICE_FIXED_CHILD, B_STRING_TYPE, {.string = "bus_managers/pci/root/driver_v1"} },
		{}
	};

	return gDeviceManager->register_node(parent, DESIGNWARE_PCI_DRIVER_MODULE_NAME, attrs, NULL,
		NULL);
}


status_t
DWPCIController::InitDriver(device_node* node, DWPCIController*& outDriver)
{
	ObjectDeleter<DWPCIController> driver(new(std::nothrow) DWPCIController());
	if (!driver.IsSet())
		return B_NO_MEMORY;

	CHECK_RET(driver->InitDriverInt(node));
	outDriver = driver.Detach();
	return B_OK;
}


status_t
DWPCIController::ReadResourceInfo()
{
	DeviceNodePutter<&gDeviceManager> fdtNode(gDeviceManager->get_parent_node(fNode));

	const char* bus;
	CHECK_RET(gDeviceManager->get_attr_string(fdtNode.Get(), B_DEVICE_BUS, &bus, false));
	if (strcmp(bus, "fdt") != 0)
		return B_ERROR;

	fdt_device_module_info *fdtModule;
	fdt_device* fdtDev;
	CHECK_RET(gDeviceManager->get_driver(fdtNode.Get(),
		(driver_module_info**)&fdtModule, (void**)&fdtDev));

	const void* prop;
	int propLen;

	prop = fdtModule->get_prop(fdtDev, "bus-range", &propLen);
	if (prop != NULL && propLen == 8) {
		uint32 busBeg = B_BENDIAN_TO_HOST_INT32(*((uint32*)prop + 0));
		uint32 busEnd = B_BENDIAN_TO_HOST_INT32(*((uint32*)prop + 1));
		dprintf("  bus-range: %" B_PRIu32 " - %" B_PRIu32 "\n", busBeg, busEnd);
	}

	prop = fdtModule->get_prop(fdtDev, "interrupt-map-mask", &propLen);
	if (prop == NULL || propLen != 4 * 4) {
		dprintf("  \"interrupt-map-mask\" property not found or invalid");
		return B_ERROR;
	}
	fInterruptMapMask.childAdr = B_BENDIAN_TO_HOST_INT32(*((uint32*)prop + 0));
	fInterruptMapMask.childIrq = B_BENDIAN_TO_HOST_INT32(*((uint32*)prop + 3));

	prop = fdtModule->get_prop(fdtDev, "interrupt-map", &propLen);
	fInterruptMapLen = (uint32)propLen / (6 * 4);
	fInterruptMap.SetTo(new(std::nothrow) InterruptMap[fInterruptMapLen]);
	if (!fInterruptMap.IsSet())
		return B_NO_MEMORY;

	for (uint32_t *it = (uint32_t*)prop; (uint8_t*)it - (uint8_t*)prop < propLen; it += 6) {
		size_t i = (it - (uint32_t*)prop) / 6;

		fInterruptMap[i].childAdr = B_BENDIAN_TO_HOST_INT32(*(it + 0));
		fInterruptMap[i].childIrq = B_BENDIAN_TO_HOST_INT32(*(it + 3));
		fInterruptMap[i].parentIrqCtrl = B_BENDIAN_TO_HOST_INT32(*(it + 4));
		fInterruptMap[i].parentIrq = B_BENDIAN_TO_HOST_INT32(*(it + 5));
	}

	dprintf("  interrupt-map:\n");
	for (size_t i = 0; i < fInterruptMapLen; i++) {
		dprintf("    ");
		// child unit address
		PciAddress pciAddress{.val = fInterruptMap[i].childAdr};
		dprintf("bus: %" B_PRIu32, pciAddress.bus);
		dprintf(", dev: %" B_PRIu32, pciAddress.device);
		dprintf(", fn: %" B_PRIu32, pciAddress.function);

		dprintf(", childIrq: %" B_PRIu32, fInterruptMap[i].childIrq);
		dprintf(", parentIrq: (%" B_PRIu32, fInterruptMap[i].parentIrqCtrl);
		dprintf(", %" B_PRIu32, fInterruptMap[i].parentIrq);
		dprintf(")\n");
		if (i % 4 == 3 && (i + 1 < fInterruptMapLen))
			dprintf("\n");
	}

	prop = fdtModule->get_prop(fdtDev, "ranges", &propLen);
	if (prop == NULL) {
		dprintf("  \"ranges\" property not found");
		return B_ERROR;
	}
	dprintf("  ranges:\n");
	for (uint32_t *it = (uint32_t*)prop; (uint8_t*)it - (uint8_t*)prop < propLen; it += 7) {
		dprintf("    ");
		uint32_t type      = B_BENDIAN_TO_HOST_INT32(*(it + 0));
		uint64_t childAdr  = B_BENDIAN_TO_HOST_INT64(*(uint64_t*)(it + 1));
		uint64_t parentAdr = B_BENDIAN_TO_HOST_INT64(*(uint64_t*)(it + 3));
		uint64_t len       = B_BENDIAN_TO_HOST_INT64(*(uint64_t*)(it + 5));

		pci_resource_range range = {};
		range.host_address = parentAdr;
		range.pci_address = childAdr;
		range.size = len;

		if ((type & fdtPciRangePrefechable) != 0)
			range.address_type |= PCI_address_prefetchable;

		switch (type & fdtPciRangeTypeMask) {
		case fdtPciRangeIoPort:
			range.type = B_IO_PORT;
			fResourceRanges.Add(range);
			break;
		case fdtPciRangeMmio32Bit:
			range.type = B_IO_MEMORY;
			range.address_type |= PCI_address_type_32;
			fResourceRanges.Add(range);
			break;
		case fdtPciRangeMmio64Bit:
			range.type = B_IO_MEMORY;
			range.address_type |= PCI_address_type_64;
			fResourceRanges.Add(range);
			break;
		}

		switch (type & fdtPciRangeTypeMask) {
		case fdtPciRangeConfig:    dprintf("CONFIG"); break;
		case fdtPciRangeIoPort:    dprintf("IOPORT"); break;
		case fdtPciRangeMmio32Bit: dprintf("MMIO32"); break;
		case fdtPciRangeMmio64Bit: dprintf("MMIO64"); break;
		}

		dprintf(" (0x%08" B_PRIx32 "): ", type);
		dprintf("child: %08" B_PRIx64, childAdr);
		dprintf(", parent: %08" B_PRIx64, parentAdr);
		dprintf(", len: %" B_PRIx64 "\n", len);
	}
	return B_OK;
}


status_t
DWPCIController::InitDriverInt(device_node* node)
{
	fNode = node;
	dprintf("+DWPCIController::InitDriver()\n");

	CHECK_RET(ReadResourceInfo());

	DeviceNodePutter<&gDeviceManager> fdtNode(gDeviceManager->get_parent_node(node));

	fdt_device_module_info *fdtModule;
	fdt_device* fdtDev;
	CHECK_RET(gDeviceManager->get_driver(fdtNode.Get(),
		(driver_module_info**)&fdtModule, (void**)&fdtDev));

	if (!fdtModule->get_reg(fdtDev, 0, &fDbiPhysBase, &fDbiSize))
		return B_ERROR;
	dprintf("  DBI: %08" B_PRIx64 ", %08" B_PRIx64 "\n", fDbiPhysBase, fDbiSize);

	if (!fdtModule->get_reg(fdtDev, 1, &fConfigPhysBase, &fConfigSize))
		return B_ERROR;
	dprintf("  config: %08" B_PRIx64 ", %08" B_PRIx64 "\n", fConfigPhysBase, fConfigSize);

	uint64 msiIrq;
	if (!fdtModule->get_interrupt(fdtDev, 0, NULL, &msiIrq))
		return B_ERROR;

	fDbiArea.SetTo(map_physical_memory("PCI DBI MMIO", fDbiPhysBase, fDbiSize, B_ANY_KERNEL_ADDRESS,
		B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA, (void**)&fDbiBase));
	CHECK_RET(fDbiArea.Get());

	fConfigArea.SetTo(map_physical_memory("PCI Config MMIO", fConfigPhysBase, fConfigSize,
		B_ANY_KERNEL_ADDRESS, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA, (void**)&fConfigBase));
	CHECK_RET(fConfigArea.Get());

	CHECK_RET(fIrqCtrl.Init(GetDbuRegs(), msiIrq));

	AtuDump();

	dprintf("-DWPCIController::InitDriver()\n");
	return B_OK;
}


void
DWPCIController::UninitDriver()
{
	delete this;
}


addr_t
DWPCIController::ConfigAddress(uint8 bus, uint8 device, uint8 function, uint16 offset)
{
	uint32 atuType;
	if (bus == 0) {
		if (device != 0 || function != 0)
			return 0;
		return fDbiBase + offset;
	} else if (bus == 1)
		atuType = kPciAtuTypeCfg0;
	else
		atuType = kPciAtuTypeCfg1;

	uint64 address = (uint64)(PciAddress {
		.function = function,
		.device = device,
		.bus = bus
	}.val) << 8;

	status_t res = AtuMap(1, kPciAtuOutbound, atuType, fConfigPhysBase, address, fConfigSize);
	if (res < B_OK)
		return 0;

	return fConfigBase + offset;
}


//#pragma mark - PCI controller


status_t
DWPCIController::ReadConfig(uint8 bus, uint8 device, uint8 function,
	uint16 offset, uint8 size, uint32& value)
{
	InterruptsSpinLocker lock(fLock);

	addr_t address = ConfigAddress(bus, device, function, offset);
	if (address == 0)
		return ERANGE;

	switch (size) {
		case 1: value = ReadReg8(address); break;
		case 2: value = ReadReg16(address); break;
		case 4: value = *(vuint32*)address; break;
		default:
			return B_BAD_VALUE;
	}

	return B_OK;
}


status_t
DWPCIController::WriteConfig(uint8 bus, uint8 device, uint8 function,
	uint16 offset, uint8 size, uint32 value)
{
	InterruptsSpinLocker lock(fLock);

	addr_t address = ConfigAddress(bus, device, function, offset);
	if (address == 0)
		return ERANGE;

	switch (size) {
		case 1: WriteReg8(address, value); break;
		case 2: WriteReg16(address, value); break;
		case 4: *(vuint32*)address = value; break;
		default:
			return B_BAD_VALUE;
	}

	return B_OK;
}


status_t
DWPCIController::GetMaxBusDevices(int32& count)
{
	count = 32;
	return B_OK;
}


status_t
DWPCIController::ReadIrq(uint8 bus, uint8 device, uint8 function,
	uint8 pin, uint8& irq)
{
	return B_UNSUPPORTED;
}


status_t
DWPCIController::WriteIrq(uint8 bus, uint8 device, uint8 function,
	uint8 pin, uint8 irq)
{
	return B_UNSUPPORTED;
}


status_t
DWPCIController::GetRange(uint32 index, pci_resource_range* range)
{
	if (index >= (uint32)fResourceRanges.Count())
		return B_BAD_INDEX;

	*range = fResourceRanges[index];
	return B_OK;
}


//#pragma mark - DWPCIController


status_t
DWPCIController::AtuMap(uint32 index, uint32 direction, uint32 type, uint64 parentAdr,
	uint64 childAdr, uint32 size)
{
	/*
	dprintf("AtuMap(%" B_PRIu32 ", %" B_PRIu32 ", %#" B_PRIx64 ", %#" B_PRIx64 ", "
		"%#" B_PRIx32 ")\n", index, type, parentAdr, childAdr, size);
	*/
	volatile PciAtuRegs* atu = (PciAtuRegs*)(fDbiBase + kPciAtuOffset
		+ (2 * index + direction) * sizeof(PciAtuRegs));

	atu->baseLo = (uint32)parentAdr;
	atu->baseHi = (uint32)(parentAdr >> 32);
	atu->limit = (uint32)(parentAdr + size - 1);
	atu->targetLo = (uint32)childAdr;
	atu->targetHi = (uint32)(childAdr >> 32);
	atu->ctrl1 = type;
	atu->ctrl2 = kPciAtuEnable;

	for (;;) {
		if ((atu->ctrl2 & kPciAtuEnable) != 0)
			break;
	}

	return B_OK;
}


void
DWPCIController::AtuDump()
{
	dprintf("ATU:\n");
	for (uint32 direction = 0; direction < 2; direction++) {
		switch (direction) {
			case kPciAtuOutbound:
				dprintf("  outbound:\n");
				break;
			case kPciAtuInbound:
				dprintf("  inbound:\n");
				break;
		}

		for (uint32 index = 0; index < 8; index++) {
			volatile PciAtuRegs* atu = (PciAtuRegs*)(fDbiBase
				+ kPciAtuOffset + (2 * index + direction) * sizeof(PciAtuRegs));

			dprintf("    %" B_PRIu32 ": ", index);
			dprintf("base: %#08" B_PRIx64, atu->baseLo + ((uint64)atu->baseHi << 32));
			dprintf(", limit: %#08" B_PRIx32, atu->limit);
			dprintf(", target: %#08" B_PRIx64, atu->targetLo
				+ ((uint64)atu->targetHi << 32));
			dprintf(", ctrl1: ");
			uint32 ctrl1 = atu->ctrl1;
			switch (ctrl1) {
				case kPciAtuTypeMem:
					dprintf("mem");
					break;
				case kPciAtuTypeIo:
					dprintf("io");
					break;
				case kPciAtuTypeCfg0:
					dprintf("cfg0");
					break;
				case kPciAtuTypeCfg1:
					dprintf("cfg1");
					break;
				default:
					dprintf("? (%#" B_PRIx32 ")", ctrl1);
			}
			dprintf(", ctrl2: {");
			uint32 ctrl2 = atu->ctrl2;
			bool first = true;
			for (uint32 i = 0; i < 32; i++) {
				if (((1 << i) & ctrl2) != 0) {
					if (first)
						first = false;
					else
						dprintf(", ");
					switch (i) {
						case 30:
							dprintf("barModeEnable");
							break;
						case 31:
							dprintf("enable");
							break;
						default:
							dprintf("? (%" B_PRIu32 ")", i);
							break;
					}
				}
			}
			dprintf("}\n");
		}
	}
}