blob: 1bde86c54eb9791780d4748efed74cc9cbbeb122 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* This file contains functions to handle discovery of PMC metrics located
* in the PMC SSRAM PCI device.
*
* Copyright (c) 2023, Intel Corporation.
* All Rights Reserved.
*
*/
#include <linux/cleanup.h>
#include <linux/pci.h>
#include <linux/io-64-nonatomic-lo-hi.h>
#include "core.h"
#include "../vsec.h"
#include "../pmt/telemetry.h"
#define SSRAM_HDR_SIZE 0x100
#define SSRAM_PWRM_OFFSET 0x14
#define SSRAM_DVSEC_OFFSET 0x1C
#define SSRAM_DVSEC_SIZE 0x10
#define SSRAM_PCH_OFFSET 0x60
#define SSRAM_IOE_OFFSET 0x68
#define SSRAM_DEVID_OFFSET 0x70
/* PCH query */
#define LPM_HEADER_OFFSET 1
#define LPM_REG_COUNT 28
#define LPM_MODE_OFFSET 1
DEFINE_FREE(pmc_core_iounmap, void __iomem *, iounmap(_T));
static u32 pmc_core_find_guid(struct pmc_info *list, const struct pmc_reg_map *map)
{
for (; list->map; ++list)
if (list->map == map)
return list->guid;
return 0;
}
static int pmc_core_get_lpm_req(struct pmc_dev *pmcdev, struct pmc *pmc)
{
struct telem_endpoint *ep;
const u8 *lpm_indices;
int num_maps, mode_offset = 0;
int ret, mode, i;
int lpm_size;
u32 guid;
lpm_indices = pmc->map->lpm_reg_index;
num_maps = pmc->map->lpm_num_maps;
lpm_size = LPM_MAX_NUM_MODES * num_maps;
guid = pmc_core_find_guid(pmcdev->regmap_list, pmc->map);
if (!guid)
return -ENXIO;
ep = pmt_telem_find_and_register_endpoint(pmcdev->ssram_pcidev, guid, 0);
if (IS_ERR(ep)) {
dev_dbg(&pmcdev->pdev->dev, "couldn't get telem endpoint %ld",
PTR_ERR(ep));
return -EPROBE_DEFER;
}
pmc->lpm_req_regs = devm_kzalloc(&pmcdev->pdev->dev,
lpm_size * sizeof(u32),
GFP_KERNEL);
if (!pmc->lpm_req_regs) {
ret = -ENOMEM;
goto unregister_ep;
}
/*
* PMC Low Power Mode (LPM) table
*
* In telemetry space, the LPM table contains a 4 byte header followed
* by 8 consecutive mode blocks (one for each LPM mode). Each block
* has a 4 byte header followed by a set of registers that describe the
* IP state requirements for the given mode. The IP mapping is platform
* specific but the same for each block, making for easy analysis.
* Platforms only use a subset of the space to track the requirements
* for their IPs. Callers provide the requirement registers they use as
* a list of indices. Each requirement register is associated with an
* IP map that's maintained by the caller.
*
* Header
* +----+----------------------------+----------------------------+
* | 0 | REVISION | ENABLED MODES |
* +----+--------------+-------------+-------------+--------------+
*
* Low Power Mode 0 Block
* +----+--------------+-------------+-------------+--------------+
* | 1 | SUB ID | SIZE | MAJOR | MINOR |
* +----+--------------+-------------+-------------+--------------+
* | 2 | LPM0 Requirements 0 |
* +----+---------------------------------------------------------+
* | | ... |
* +----+---------------------------------------------------------+
* | 29 | LPM0 Requirements 27 |
* +----+---------------------------------------------------------+
*
* ...
*
* Low Power Mode 7 Block
* +----+--------------+-------------+-------------+--------------+
* | | SUB ID | SIZE | MAJOR | MINOR |
* +----+--------------+-------------+-------------+--------------+
* | 60 | LPM7 Requirements 0 |
* +----+---------------------------------------------------------+
* | | ... |
* +----+---------------------------------------------------------+
* | 87 | LPM7 Requirements 27 |
* +----+---------------------------------------------------------+
*
*/
mode_offset = LPM_HEADER_OFFSET + LPM_MODE_OFFSET;
pmc_for_each_mode(i, mode, pmcdev) {
u32 *req_offset = pmc->lpm_req_regs + (mode * num_maps);
int m;
for (m = 0; m < num_maps; m++) {
u8 sample_id = lpm_indices[m] + mode_offset;
ret = pmt_telem_read32(ep, sample_id, req_offset, 1);
if (ret) {
dev_err(&pmcdev->pdev->dev,
"couldn't read Low Power Mode requirements: %d\n", ret);
devm_kfree(&pmcdev->pdev->dev, pmc->lpm_req_regs);
goto unregister_ep;
}
++req_offset;
}
mode_offset += LPM_REG_COUNT + LPM_MODE_OFFSET;
}
unregister_ep:
pmt_telem_unregister_endpoint(ep);
return ret;
}
int pmc_core_ssram_get_lpm_reqs(struct pmc_dev *pmcdev)
{
int ret, i;
if (!pmcdev->ssram_pcidev)
return -ENODEV;
for (i = 0; i < ARRAY_SIZE(pmcdev->pmcs); ++i) {
if (!pmcdev->pmcs[i])
continue;
ret = pmc_core_get_lpm_req(pmcdev, pmcdev->pmcs[i]);
if (ret)
return ret;
}
return 0;
}
static void
pmc_add_pmt(struct pmc_dev *pmcdev, u64 ssram_base, void __iomem *ssram)
{
struct pci_dev *pcidev = pmcdev->ssram_pcidev;
struct intel_vsec_platform_info info = {};
struct intel_vsec_header *headers[2] = {};
struct intel_vsec_header header;
void __iomem *dvsec;
u32 dvsec_offset;
u32 table, hdr;
ssram = ioremap(ssram_base, SSRAM_HDR_SIZE);
if (!ssram)
return;
dvsec_offset = readl(ssram + SSRAM_DVSEC_OFFSET);
iounmap(ssram);
dvsec = ioremap(ssram_base + dvsec_offset, SSRAM_DVSEC_SIZE);
if (!dvsec)
return;
hdr = readl(dvsec + PCI_DVSEC_HEADER1);
header.id = readw(dvsec + PCI_DVSEC_HEADER2);
header.rev = PCI_DVSEC_HEADER1_REV(hdr);
header.length = PCI_DVSEC_HEADER1_LEN(hdr);
header.num_entries = readb(dvsec + INTEL_DVSEC_ENTRIES);
header.entry_size = readb(dvsec + INTEL_DVSEC_SIZE);
table = readl(dvsec + INTEL_DVSEC_TABLE);
header.tbir = INTEL_DVSEC_TABLE_BAR(table);
header.offset = INTEL_DVSEC_TABLE_OFFSET(table);
iounmap(dvsec);
headers[0] = &header;
info.caps = VSEC_CAP_TELEMETRY;
info.headers = headers;
info.base_addr = ssram_base;
info.parent = &pmcdev->pdev->dev;
intel_vsec_register(pcidev, &info);
}
static const struct pmc_reg_map *pmc_core_find_regmap(struct pmc_info *list, u16 devid)
{
for (; list->map; ++list)
if (devid == list->devid)
return list->map;
return NULL;
}
static inline u64 get_base(void __iomem *addr, u32 offset)
{
return lo_hi_readq(addr + offset) & GENMASK_ULL(63, 3);
}
static int
pmc_core_pmc_add(struct pmc_dev *pmcdev, u64 pwrm_base,
const struct pmc_reg_map *reg_map, int pmc_index)
{
struct pmc *pmc = pmcdev->pmcs[pmc_index];
if (!pwrm_base)
return -ENODEV;
/* Memory for primary PMC has been allocated in core.c */
if (!pmc) {
pmc = devm_kzalloc(&pmcdev->pdev->dev, sizeof(*pmc), GFP_KERNEL);
if (!pmc)
return -ENOMEM;
}
pmc->map = reg_map;
pmc->base_addr = pwrm_base;
pmc->regbase = ioremap(pmc->base_addr, pmc->map->regmap_length);
if (!pmc->regbase) {
devm_kfree(&pmcdev->pdev->dev, pmc);
return -ENOMEM;
}
pmcdev->pmcs[pmc_index] = pmc;
return 0;
}
static int
pmc_core_ssram_get_pmc(struct pmc_dev *pmcdev, int pmc_idx, u32 offset)
{
struct pci_dev *ssram_pcidev = pmcdev->ssram_pcidev;
void __iomem __free(pmc_core_iounmap) *tmp_ssram = NULL;
void __iomem __free(pmc_core_iounmap) *ssram = NULL;
const struct pmc_reg_map *map;
u64 ssram_base, pwrm_base;
u16 devid;
if (!pmcdev->regmap_list)
return -ENOENT;
ssram_base = ssram_pcidev->resource[0].start;
tmp_ssram = ioremap(ssram_base, SSRAM_HDR_SIZE);
if (pmc_idx != PMC_IDX_MAIN) {
/*
* The secondary PMC BARS (which are behind hidden PCI devices)
* are read from fixed offsets in MMIO of the primary PMC BAR.
*/
ssram_base = get_base(tmp_ssram, offset);
ssram = ioremap(ssram_base, SSRAM_HDR_SIZE);
if (!ssram)
return -ENOMEM;
} else {
ssram = no_free_ptr(tmp_ssram);
}
pwrm_base = get_base(ssram, SSRAM_PWRM_OFFSET);
devid = readw(ssram + SSRAM_DEVID_OFFSET);
/* Find and register and PMC telemetry entries */
pmc_add_pmt(pmcdev, ssram_base, ssram);
map = pmc_core_find_regmap(pmcdev->regmap_list, devid);
if (!map)
return -ENODEV;
return pmc_core_pmc_add(pmcdev, pwrm_base, map, pmc_idx);
}
int pmc_core_ssram_init(struct pmc_dev *pmcdev, int func)
{
struct pci_dev *pcidev;
int ret;
pcidev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(20, func));
if (!pcidev)
return -ENODEV;
ret = pcim_enable_device(pcidev);
if (ret)
goto release_dev;
pmcdev->ssram_pcidev = pcidev;
ret = pmc_core_ssram_get_pmc(pmcdev, PMC_IDX_MAIN, 0);
if (ret)
goto disable_dev;
pmc_core_ssram_get_pmc(pmcdev, PMC_IDX_IOE, SSRAM_IOE_OFFSET);
pmc_core_ssram_get_pmc(pmcdev, PMC_IDX_PCH, SSRAM_PCH_OFFSET);
return 0;
disable_dev:
pmcdev->ssram_pcidev = NULL;
pci_disable_device(pcidev);
release_dev:
pci_dev_put(pcidev);
return ret;
}
MODULE_IMPORT_NS(INTEL_VSEC);
MODULE_IMPORT_NS(INTEL_PMT_TELEMETRY);