/*
BSD 3-Clause License

Copyright (c) 2021, Intel Corporation
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

* Neither the name of the copyright holder nor the names of its
  contributors may be used to endorse or promote products derived from
  this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "uncore_pmu_discovery.h"
#include "pci.h"
#include "mmio.h"
#include "iostream"
#include "utils.h"

namespace pcm {

constexpr auto UNCORE_DISCOVERY_MAP_SIZE = 0x80000;

UncorePMUDiscovery::UncorePMUDiscovery()
{
    if (safe_getenv("PCM_NO_UNCORE_PMU_DISCOVERY") == std::string("1"))
    {
        return;
    }
    unsigned socket = 0;
    auto processTables = [&socket,this](const uint64 bar)
    {
        constexpr auto UncoreDiscoverySize = 3UL;
        union UncoreGlobalDiscovery {
            GlobalPMU pmu;
            uint64 table[UncoreDiscoverySize];
        };
        MMIORange range(bar, UNCORE_DISCOVERY_MAP_SIZE); // mmio range with UNCORE_DISCOVERY_MAP_SIZE bytes
        UncoreGlobalDiscovery global;
        auto copyTable = [&range,&UncoreDiscoverySize,&bar](uint64 * table, const size_t offset)
        {
            for (size_t i = 0; i < UncoreDiscoverySize; ++i)
            {
                const auto pos = offset + i * sizeof(uint64);
                assert(pos < UNCORE_DISCOVERY_MAP_SIZE);
                table[i] = range.read64(pos);
                if (table[i] == ~0ULL)
                {
                    std::cerr << "Failed to read memory at 0x" << std::hex << bar << " + 0x" << pos << "\n";
                    throw std::exception();
                }
            }
        };
        copyTable(global.table, 0);
        globalPMUs.push_back(global.pmu);
        union UncoreUnitDiscovery {
            BoxPMU pmu;
            uint64 table[UncoreDiscoverySize];
        };
        UncoreUnitDiscovery unit;
        const auto step = global.pmu.stride * 8;
        BoxPMUMap boxPMUMap;
        for (size_t u = 0; u < global.pmu.maxUnits; ++u)
        {
            copyTable(unit.table, (u+1) * step);
            if (unit.table[0] == 0 && unit.table[1] == 0)
            {
                // invalid entry
                continue;
            }
            // unit.pmu.print();
            boxPMUMap[unit.pmu.boxType].push_back(unit.pmu);
        }
        boxPMUs.push_back(boxPMUMap);
        ++socket;
    };
    try {
    forAllIntelDevices(
        [&processTables](const uint32 group, const uint32 bus, const uint32 device, const uint32 function, const uint32 /* device_id */)
        {
            uint32 status{0};
            PciHandleType h(group, bus, device, function);
            h.read32(6, &status); // read status
            if (status & 0x10) // has capability list
            {
                // std::cout << "Intel device scan. found "<< std::hex << group << ":" << bus << ":" << device << ":" << function << " " << device_id << " with capability list\n";
                union {
                    struct {
                        uint32 id:16;
                        uint32 version:4;
                        uint32 next:12;
                    } fields;
                    uint32 value;
                } header;
                uint64 offset = 0x100;
                do
                {
                    if (offset == 0 || h.read32(offset, &header.value) != sizeof(uint32) || header.value == 0)
                    {
                        return;
                    }
                    // std::cout << "offset " << offset << "\n";
                    if (header.fields.id == 0x23) // UNCORE_EXT_CAP_ID_DISCOVERY
                    {
                        // std::cout << "found UNCORE_EXT_CAP_ID_DISCOVERY\n";
                        uint32 entryID = 0;
                        constexpr auto UNCORE_DISCOVERY_DVSEC_OFFSET = 8;
                        if (h.read32(offset + UNCORE_DISCOVERY_DVSEC_OFFSET, &entryID) == sizeof(uint32)) // read at UNCORE_DISCOVERY_DVSEC_OFFSET
                        {
                            entryID &= 0xffff; // apply UNCORE_DISCOVERY_DVSEC_ID_MASK
                            if (entryID == 1) // UNCORE_DISCOVERY_DVSEC_ID_PMON
                            {
                                // std::cout << "found UNCORE_DISCOVERY_DVSEC_ID_PMON\n";
                                uint32 bir = 0;
                                if (h.read32(offset + UNCORE_DISCOVERY_DVSEC_OFFSET + 4, &bir) == sizeof(uint32)) // read "bir" value (2:0)
                                {
                                    bir &= 7;
                                    auto barOffset = 0x10 + bir * 4;
                                    uint32 bar = 0;
                                    if (h.read32(barOffset, &bar) == sizeof(uint32) && bar != 0) // read bar
                                    {
                                        bar &= ~4095;
                                        processTables(bar);
                                        return;
                                    }
                                    else
                                    {
                                        std::cerr << "Error: can't read bar from offset " << barOffset << " \n";
                                    }
                                }
                                else
                                {
                                    std::cerr << "Error: can't read bir\n";
                                }
                            }
                        }
                    }
                    offset = header.fields.next & ~3;
                } while (1);
            }
        });
    } catch (...)
    {
        std::cerr << "WARNING: enumeration of devices in UncorePMUDiscovery failed\n";
    }

    if (safe_getenv("PCM_PRINT_UNCORE_PMU_DISCOVERY") == std::string("1"))
    {
        for (size_t s = 0; s < boxPMUs.size(); ++s)
        {
            std::cout << "Socket " << s << " global PMU:\n";
            std::cout << "    ";
            globalPMUs[s].print();
            std::cout << "Socket " << s << " unit PMUs:\n";
            for (const auto & pmuType : boxPMUs[s])
            {
                const auto n = pmuType.second.size();
                std::cout << "   PMU type " << pmuType.first << " (" << n << " boxes)"<<  "\n";
                for (size_t i = 0; i < n ; ++i)
                {
                    std::cout << "        ";
                    pmuType.second[i].print();
                }
            }
        }
    }
}

} // namespace pcm
