root/src/libs/compat/freebsd_network/pci.cpp
/*
 * Copyright 2022, Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 */

extern "C" {
#include "device.h"

#include <compat/machine/resource.h>
#include <compat/dev/pci/pcireg.h>
#include <compat/dev/pci/pcivar.h>
}

#include <PCI.h>


//#define DEBUG_PCI
#ifdef DEBUG_PCI
#       define TRACE_PCI(dev, format, args...) device_printf(dev, format , ##args)
#else
#       define TRACE_PCI(dev, format, args...) do { } while (0)
#endif


pci_module_info *gPci;


status_t
init_pci()
{
        if (gPci != NULL)
                return B_OK;

        status_t status = get_module(B_PCI_MODULE_NAME, (module_info **)&gPci);
        if (status != B_OK)
                return status;

        return B_OK;
}


void
uninit_pci()
{
        if (gPci != NULL)
                put_module(B_PCI_MODULE_NAME);
}


pci_info*
get_device_pci_info(device_t device)
{
        struct root_device_softc* root_softc = (struct root_device_softc*)device->root->softc;
        if (root_softc->bus != root_device_softc::BUS_pci)
                return NULL;
        return &root_softc->pci_info;
}


uint32_t
pci_read_config(device_t dev, int offset, int size)
{
        pci_info* info = get_device_pci_info(dev);

        uint32_t value = gPci->read_pci_config(info->bus, info->device,
                info->function, offset, size);
        TRACE_PCI(dev, "pci_read_config(%i, %i) = 0x%x\n", offset, size, value);
        return value;
}


void
pci_write_config(device_t dev, int offset, uint32_t value, int size)
{
        pci_info* info = get_device_pci_info(dev);

        TRACE_PCI(dev, "pci_write_config(%i, 0x%x, %i)\n", offset, value, size);

        gPci->write_pci_config(info->bus, info->device, info->function, offset,
                size, value);
}


uint16_t
pci_get_vendor(device_t dev)
{
        return pci_read_config(dev, PCI_vendor_id, 2);
}


uint16_t
pci_get_device(device_t dev)
{
        return pci_read_config(dev, PCI_device_id, 2);
}


uint16_t
pci_get_subvendor(device_t dev)
{
        return pci_read_config(dev, PCI_subsystem_vendor_id, 2);
}


uint16_t
pci_get_subdevice(device_t dev)
{
        return pci_read_config(dev, PCI_subsystem_id, 2);
}


uint8_t
pci_get_revid(device_t dev)
{
        return pci_read_config(dev, PCI_revision, 1);
}


uint32_t
pci_get_domain(device_t dev)
{
        return 0;
}

uint32_t
pci_get_devid(device_t dev)
{
        return pci_read_config(dev, PCI_device_id, 2) << 16 |
                pci_read_config(dev, PCI_vendor_id, 2);
}

uint8_t
pci_get_cachelnsz(device_t dev)
{
        return pci_read_config(dev, PCI_line_size, 1);
}

uint8_t *
pci_get_ether(device_t dev)
{
        /* used in if_dc to get the MAC from CardBus CIS for Xircom card */
        return NULL; /* NULL is handled in the caller correctly */
}

uint8_t
pci_get_bus(device_t dev)
{
        pci_info *info
                = &((struct root_device_softc *)dev->root->softc)->pci_info;
        return info->bus;
}


uint8_t
pci_get_slot(device_t dev)
{
        pci_info *info
                = &((struct root_device_softc *)dev->root->softc)->pci_info;
        return info->device;
}


uint8_t
pci_get_function(device_t dev)
{
        pci_info* info = get_device_pci_info(dev);
        return info->function;
}


device_t
pci_find_dbsf(uint32_t domain, uint8_t bus, uint8_t slot, uint8_t func)
{
        // We don't support that yet - if we want to support the multi port
        // feature of the Broadcom BCM 570x driver, we would have to change
        // that.
        return NULL;
}


static void
pci_set_command_bit(device_t dev, uint16_t bit)
{
        uint16_t command = pci_read_config(dev, PCI_command, 2);
        pci_write_config(dev, PCI_command, command | bit, 2);
}


int
pci_enable_busmaster(device_t dev)
{
        // We do this a bit later than FreeBSD does.
        if (pci_get_powerstate(dev) != PCI_POWERSTATE_D0)
                pci_set_powerstate(dev, PCI_POWERSTATE_D0);

        pci_set_command_bit(dev, PCI_command_master);
        return 0;
}


int
pci_enable_io(device_t dev, int space)
{
        /* adapted from FreeBSD's pci_enable_io_method */
        int bit = 0;

        switch (space) {
                case SYS_RES_IOPORT:
                        bit = PCI_command_io;
                        break;
                case SYS_RES_MEMORY:
                        bit = PCI_command_memory;
                        break;
                default:
                        return EINVAL;
        }

        pci_set_command_bit(dev, bit);
        if (pci_read_config(dev, PCI_command, 2) & bit)
                return 0;

        device_printf(dev, "pci_enable_io(%d) failed.\n", space);

        return ENXIO;
}


int
pci_find_cap(device_t dev, int capability, int *capreg)
{
        pci_info* info = get_device_pci_info(dev);
        uint8 offset;
        status_t status;

        status = gPci->find_pci_capability(info->bus, info->device, info->function,
                capability, &offset);
        *capreg = offset;
        return status;
}


int
pci_find_extcap(device_t dev, int capability, int *capreg)
{
        pci_info* info = get_device_pci_info(dev);
        uint16 offset;
        status_t status;

        status = gPci->find_pci_extended_capability(info->bus, info->device, info->function,
                capability, &offset);
        *capreg = offset;
        return status;
}


int
pci_msi_count(device_t dev)
{
        pci_info* info = get_device_pci_info(dev);
        return gPci->get_msi_count(info->bus, info->device, info->function);
}


int
pci_alloc_msi(device_t dev, int *count)
{
        pci_info* info = get_device_pci_info(dev);
        uint32 startVector = 0;
        if (gPci->configure_msi(info->bus, info->device, info->function, *count,
                        &startVector) != B_OK) {
                return ENODEV;
        }

        ((struct root_device_softc *)dev->root->softc)->is_msi = true;
        info->u.h0.interrupt_line = startVector;
        return EOK;
}


int
pci_release_msi(device_t dev)
{
        pci_info* info = get_device_pci_info(dev);
        gPci->unconfigure_msi(info->bus, info->device, info->function);
        ((struct root_device_softc *)dev->root->softc)->is_msi = false;
        ((struct root_device_softc *)dev->root->softc)->is_msix = false;
        return EOK;
}


int
pci_msix_table_bar(device_t dev)
{
        pci_info* info = get_device_pci_info(dev);

        uint8 capability_offset;
        if (gPci->find_pci_capability(info->bus, info->device, info->function,
                        PCI_cap_id_msix, &capability_offset) != B_OK)
                return -1;

        uint32 table_value = gPci->read_pci_config(info->bus, info->device, info->function,
                capability_offset + PCI_msix_table, 4);

        uint32 bar = table_value & PCI_msix_bir_mask;
        return PCIR_BAR(bar);
}


int
pci_msix_count(device_t dev)
{
        pci_info* info = get_device_pci_info(dev);
        return gPci->get_msix_count(info->bus, info->device, info->function);
}


int
pci_alloc_msix(device_t dev, int *count)
{
        pci_info* info = get_device_pci_info(dev);
        uint32 startVector = 0;
        if (gPci->configure_msix(info->bus, info->device, info->function, *count,
                        &startVector) != B_OK) {
                return ENODEV;
        }

        ((struct root_device_softc *)dev->root->softc)->is_msix = true;
        info->u.h0.interrupt_line = startVector;
        return EOK;
}


int
pci_get_max_read_req(device_t dev)
{
        int cap;
        uint16_t val;

        if (pci_find_extcap(dev, PCIY_EXPRESS, &cap) != 0)
                return (0);
        val = pci_read_config(dev, cap + PCIR_EXPRESS_DEVICE_CTL, 2);
        val &= PCIM_EXP_CTL_MAX_READ_REQUEST;
        val >>= 12;
        return (1 << (val + 7));
}


int
pci_set_max_read_req(device_t dev, int size)
{
        int cap;
        uint16_t val;

        if (pci_find_extcap(dev, PCIY_EXPRESS, &cap) != 0)
                return (0);
        if (size < 128)
                size = 128;
        if (size > 4096)
                size = 4096;
        size = (1 << (fls(size) - 1));
        val = pci_read_config(dev, cap + PCIR_EXPRESS_DEVICE_CTL, 2);
        val &= ~PCIM_EXP_CTL_MAX_READ_REQUEST;
        val |= (fls(size) - 8) << 12;
        pci_write_config(dev, cap + PCIR_EXPRESS_DEVICE_CTL, val, 2);
        return (size);
}


int
pci_get_powerstate(device_t dev)
{
        int capabilityRegister;
        uint16 status;
        int powerState = PCI_POWERSTATE_D0;

        if (pci_find_extcap(dev, PCIY_PMG, &capabilityRegister) != EOK)
                return powerState;

        status = pci_read_config(dev, capabilityRegister + PCIR_POWER_STATUS, 2);
        switch (status & PCI_pm_mask) {
                case PCI_pm_state_d0:
                        break;
                case PCI_pm_state_d1:
                        powerState = PCI_POWERSTATE_D1;
                        break;
                case PCI_pm_state_d2:
                        powerState = PCI_POWERSTATE_D2;
                        break;
                case PCI_pm_state_d3:
                        powerState = PCI_POWERSTATE_D3;
                        break;
                default:
                        powerState = PCI_POWERSTATE_UNKNOWN;
                        break;
        }

        TRACE_PCI(dev, "%s: D%i\n", __func__, powerState);
        return powerState;
}


int
pci_set_powerstate(device_t dev, int newPowerState)
{
        int capabilityRegister;
        int oldPowerState;
        uint8 currentPowerManagementStatus;
        uint8 newPowerManagementStatus;
        uint16 powerManagementCapabilities;
        bigtime_t stateTransitionDelayInUs = 0;

        if (pci_find_extcap(dev, PCIY_PMG, &capabilityRegister) != EOK)
                return EOPNOTSUPP;

        oldPowerState = pci_get_powerstate(dev);
        if (oldPowerState == newPowerState)
                return EOK;

        switch (max_c(oldPowerState, newPowerState)) {
                case PCI_POWERSTATE_D2:
                        stateTransitionDelayInUs = 200;
                        break;
                case PCI_POWERSTATE_D3:
                        stateTransitionDelayInUs = 10000;
                        break;
        }

        currentPowerManagementStatus = pci_read_config(dev, capabilityRegister
                + PCIR_POWER_STATUS, 2);
        newPowerManagementStatus = currentPowerManagementStatus & ~PCI_pm_mask;
        powerManagementCapabilities = pci_read_config(dev, capabilityRegister
                + PCIR_POWER_CAP, 2);

        switch (newPowerState) {
                case PCI_POWERSTATE_D0:
                        newPowerManagementStatus |= PCIM_PSTAT_D0;
                        break;
                case PCI_POWERSTATE_D1:
                        if ((powerManagementCapabilities & PCI_pm_d1supp) == 0)
                                return EOPNOTSUPP;
                        newPowerManagementStatus |= PCIM_PSTAT_D1;
                        break;
                case PCI_POWERSTATE_D2:
                        if ((powerManagementCapabilities & PCI_pm_d2supp) == 0)
                                return EOPNOTSUPP;
                        newPowerManagementStatus |= PCIM_PSTAT_D2;
                        break;
                case PCI_POWERSTATE_D3:
                        newPowerManagementStatus |= PCIM_PSTAT_D3;
                        break;
                default:
                        return EINVAL;
        }

        TRACE_PCI(dev, "%s: D%i -> D%i\n", __func__, oldPowerState, newPowerState);
        pci_write_config(dev, capabilityRegister + PCIR_POWER_STATUS,
                newPowerManagementStatus, 2);
        if (stateTransitionDelayInUs != 0)
                snooze(stateTransitionDelayInUs);

        return EOK;
}