xaizek / tos (License: GPLv3 only) (since 2018-12-07)
This is an alternative version of sources presented as part of Write Your Own OS video tutorial by Viktor Engelmann.
<root> / src / hwcomm / PCI.cpp (a91ef8fd695edbb08737ee3ffe23f8843f558b53) (5,230B) (mode 100644) [raw]
#include "hwcomm/PCI.hpp"

#include <cstdint>

#include "drv/amd_am79c973.hpp"
#include "drv/drivers.hpp"
#include "drv/rtl8139.hpp"
#include "print.hpp"

using namespace hwcomm;

PCIFunctionAddr::PCIFunctionAddr(std::uint8_t bus, std::uint8_t device,
                                 std::uint8_t function)
    : bus(bus), device(device), function(function)
{
}

PCIFunctionAddr::PCIFunctionAddr(PCIDeviceAddr deviceAddr,
                                 std::uint8_t function)
    : bus(deviceAddr.bus), device(deviceAddr.device), function(function)
{
}

std::uint32_t
PCIFunctionAddr::address(std::uint32_t offset) const
{
    return 0x1 << 31
        | ((bus & 0xff) << 16)
        | ((device & 0x1f) << 11)
        | ((function & 0x07) << 8)
        | (offset & 0xfc);
}

PCIController::PCIController() : cmdPort(0xcf8), dataPort(0xcfc)
{
}

std::uint32_t
PCIController::read(PCIFunctionAddr functionAddr, std::uint32_t offset)
{
    cmdPort.write(functionAddr.address(offset));
    std::uint32_t result = dataPort.read();
    return result >> (8*(offset % 4));
}

void
PCIController::write(PCIFunctionAddr functionAddr, std::uint32_t offset,
                     std::uint32_t value)
{
    cmdPort.write(functionAddr.address(offset));
    dataPort.write(value);
}

void
PCIController::selectDrivers(drv::DriverManager &drvManager,
                             InterruptManager &interrupts)
{
    for (std::uint8_t bus = 0; bus < 8; ++bus) {
        for (std::uint8_t dev = 0; dev < 32; ++dev) {
            const int numFunctions = (deviceHasFunctions({ bus, dev }) ? 8 : 1);
            for (std::uint8_t func = 0; func < numFunctions; ++func) {
                PCIFunctionAddr addr = { bus, dev, func };
                PCIDeviceDescr descr(*this, addr);

                if (descr.vendorId == 0x0000 || descr.vendorId == 0xffff) {
                    continue;
                }

                for (int barNum = 0; barNum < 6; ++barNum) {
                    BAR bar = readBAR(addr, barNum);
                    if (bar.type == BARType::InputOutput) {
                        descr.portBase = bar.portBase;
                    }
                }

                if (drv::Driver *driver = getDriver(descr, interrupts)) {
                    drvManager.addDriver(*driver);
                }

                kprint("PCI BUS ", bus,
                       ", DEVICE ", dev,
                       ", FUNCTION ", func,
                       " = VENDOR ", descr.vendorId,
                       ", DEVICE ", descr.deviceId, "\n");
            }
        }
    }
}

bool
PCIController::deviceHasFunctions(PCIDeviceAddr deviceAddr)
{
    return read({ deviceAddr, 0 }, 0x0e) & 0x80;
}

BAR
PCIController::readBAR(PCIFunctionAddr functionAddr, std::uint16_t barNum)
{
    const std::uint32_t hdrType = read(functionAddr, 0x0e) & 0x7f;
    const int maxBARs = 6 - (4*hdrType);
    if (barNum >= maxBARs) {
        return {};
    }

    BAR bar = {};
    const std::uint32_t barValue = read(functionAddr, 0x10 + 4*barNum);
    bar.type = (barValue & 0x1 ? BARType::InputOutput : BARType::MemoryMapping);

    if (bar.type == BARType::MemoryMapping) {
        switch ((barValue >> 1) & 0x3) {
            case 0: // 32-bit Mode
            case 1: // 20-bit Mode
            case 2: // 64-bit Mode
                break;
        }
        return {};
    } else {
        bar.portBase = (barValue & ~0x3);
        bar.prefetchable = false;
    }

    return bar;
}

drv::Driver *
PCIController::getDriver(PCIDeviceDescr &dev, InterruptManager &interrupts)
{
    drv::Driver *driver = nullptr;

    switch (dev.vendorId) {
        case 0x1022: // AMD
            switch (dev.deviceId) {
                case 0x2000: // am79c973
                    kprint("AMD am79c973\n");
                    driver = new drv::amd_am79c973(dev, interrupts);
                    break;
            }
            break;

        case 0x10ec: // Realtek
            switch (dev.deviceId) {
                case 0x8139: // rtl8139
                    kprint("rtl8139\n");
                    driver = new drv::rtl8139(dev, interrupts);
                    break;
            }

        case 0x8086: // Intel
            break;
    }

    switch (dev.classId) {
        case 0x03: // graphics
            switch (dev.subclassId) {
                case 0x00: // VGA
                    kprint("VGA\n");
                    break;
            }
            break;
    }

    return driver;
}

PCIDeviceDescr::PCIDeviceDescr(PCIController &pciController,
                               PCIFunctionAddr functionAddr)
    : functionAddr(functionAddr), pciController(pciController)
{
    vendorId = pciController.read(functionAddr, 0x00);
    deviceId = pciController.read(functionAddr, 0x02);

    classId = pciController.read(functionAddr, 0x0b);
    subclassId = pciController.read(functionAddr, 0x0a);
    interfaceId = pciController.read(functionAddr, 0x09);

    revision = pciController.read(functionAddr, 0x08);
    interrupt = pciController.read(functionAddr, 0x3c);
}

void
PCIDeviceDescr::enableBusMaster()
{
    const std::uint32_t command = pciController.read(functionAddr, 0x04);
    pciController.write(functionAddr, 0x04, command | 0x4);
}
Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/tos

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/tos

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master