cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

nhi_ops.c (4411B)


      1// SPDX-License-Identifier: GPL-2.0
      2/*
      3 * NHI specific operations
      4 *
      5 * Copyright (C) 2019, Intel Corporation
      6 * Author: Mika Westerberg <mika.westerberg@linux.intel.com>
      7 */
      8
      9#include <linux/delay.h>
     10#include <linux/suspend.h>
     11
     12#include "nhi.h"
     13#include "nhi_regs.h"
     14#include "tb.h"
     15
     16/* Ice Lake specific NHI operations */
     17
     18#define ICL_LC_MAILBOX_TIMEOUT	500 /* ms */
     19
     20static int check_for_device(struct device *dev, void *data)
     21{
     22	return tb_is_switch(dev);
     23}
     24
     25static bool icl_nhi_is_device_connected(struct tb_nhi *nhi)
     26{
     27	struct tb *tb = pci_get_drvdata(nhi->pdev);
     28	int ret;
     29
     30	ret = device_for_each_child(&tb->root_switch->dev, NULL,
     31				    check_for_device);
     32	return ret > 0;
     33}
     34
     35static int icl_nhi_force_power(struct tb_nhi *nhi, bool power)
     36{
     37	u32 vs_cap;
     38
     39	/*
     40	 * The Thunderbolt host controller is present always in Ice Lake
     41	 * but the firmware may not be loaded and running (depending
     42	 * whether there is device connected and so on). Each time the
     43	 * controller is used we need to "Force Power" it first and wait
     44	 * for the firmware to indicate it is up and running. This "Force
     45	 * Power" is really not about actually powering on/off the
     46	 * controller so it is accessible even if "Force Power" is off.
     47	 *
     48	 * The actual power management happens inside shared ACPI power
     49	 * resources using standard ACPI methods.
     50	 */
     51	pci_read_config_dword(nhi->pdev, VS_CAP_22, &vs_cap);
     52	if (power) {
     53		vs_cap &= ~VS_CAP_22_DMA_DELAY_MASK;
     54		vs_cap |= 0x22 << VS_CAP_22_DMA_DELAY_SHIFT;
     55		vs_cap |= VS_CAP_22_FORCE_POWER;
     56	} else {
     57		vs_cap &= ~VS_CAP_22_FORCE_POWER;
     58	}
     59	pci_write_config_dword(nhi->pdev, VS_CAP_22, vs_cap);
     60
     61	if (power) {
     62		unsigned int retries = 350;
     63		u32 val;
     64
     65		/* Wait until the firmware tells it is up and running */
     66		do {
     67			pci_read_config_dword(nhi->pdev, VS_CAP_9, &val);
     68			if (val & VS_CAP_9_FW_READY)
     69				return 0;
     70			usleep_range(3000, 3100);
     71		} while (--retries);
     72
     73		return -ETIMEDOUT;
     74	}
     75
     76	return 0;
     77}
     78
     79static void icl_nhi_lc_mailbox_cmd(struct tb_nhi *nhi, enum icl_lc_mailbox_cmd cmd)
     80{
     81	u32 data;
     82
     83	data = (cmd << VS_CAP_19_CMD_SHIFT) & VS_CAP_19_CMD_MASK;
     84	pci_write_config_dword(nhi->pdev, VS_CAP_19, data | VS_CAP_19_VALID);
     85}
     86
     87static int icl_nhi_lc_mailbox_cmd_complete(struct tb_nhi *nhi, int timeout)
     88{
     89	unsigned long end;
     90	u32 data;
     91
     92	if (!timeout)
     93		goto clear;
     94
     95	end = jiffies + msecs_to_jiffies(timeout);
     96	do {
     97		pci_read_config_dword(nhi->pdev, VS_CAP_18, &data);
     98		if (data & VS_CAP_18_DONE)
     99			goto clear;
    100		usleep_range(1000, 1100);
    101	} while (time_before(jiffies, end));
    102
    103	return -ETIMEDOUT;
    104
    105clear:
    106	/* Clear the valid bit */
    107	pci_write_config_dword(nhi->pdev, VS_CAP_19, 0);
    108	return 0;
    109}
    110
    111static void icl_nhi_set_ltr(struct tb_nhi *nhi)
    112{
    113	u32 max_ltr, ltr;
    114
    115	pci_read_config_dword(nhi->pdev, VS_CAP_16, &max_ltr);
    116	max_ltr &= 0xffff;
    117	/* Program the same value for both snoop and no-snoop */
    118	ltr = max_ltr << 16 | max_ltr;
    119	pci_write_config_dword(nhi->pdev, VS_CAP_15, ltr);
    120}
    121
    122static int icl_nhi_suspend(struct tb_nhi *nhi)
    123{
    124	struct tb *tb = pci_get_drvdata(nhi->pdev);
    125	int ret;
    126
    127	if (icl_nhi_is_device_connected(nhi))
    128		return 0;
    129
    130	if (tb_switch_is_icm(tb->root_switch)) {
    131		/*
    132		 * If there is no device connected we need to perform
    133		 * both: a handshake through LC mailbox and force power
    134		 * down before entering D3.
    135		 */
    136		icl_nhi_lc_mailbox_cmd(nhi, ICL_LC_PREPARE_FOR_RESET);
    137		ret = icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT);
    138		if (ret)
    139			return ret;
    140	}
    141
    142	return icl_nhi_force_power(nhi, false);
    143}
    144
    145static int icl_nhi_suspend_noirq(struct tb_nhi *nhi, bool wakeup)
    146{
    147	struct tb *tb = pci_get_drvdata(nhi->pdev);
    148	enum icl_lc_mailbox_cmd cmd;
    149
    150	if (!pm_suspend_via_firmware())
    151		return icl_nhi_suspend(nhi);
    152
    153	if (!tb_switch_is_icm(tb->root_switch))
    154		return 0;
    155
    156	cmd = wakeup ? ICL_LC_GO2SX : ICL_LC_GO2SX_NO_WAKE;
    157	icl_nhi_lc_mailbox_cmd(nhi, cmd);
    158	return icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT);
    159}
    160
    161static int icl_nhi_resume(struct tb_nhi *nhi)
    162{
    163	int ret;
    164
    165	ret = icl_nhi_force_power(nhi, true);
    166	if (ret)
    167		return ret;
    168
    169	icl_nhi_set_ltr(nhi);
    170	return 0;
    171}
    172
    173static void icl_nhi_shutdown(struct tb_nhi *nhi)
    174{
    175	icl_nhi_force_power(nhi, false);
    176}
    177
    178const struct tb_nhi_ops icl_nhi_ops = {
    179	.init = icl_nhi_resume,
    180	.suspend_noirq = icl_nhi_suspend_noirq,
    181	.resume_noirq = icl_nhi_resume,
    182	.runtime_suspend = icl_nhi_suspend,
    183	.runtime_resume = icl_nhi_resume,
    184	.shutdown = icl_nhi_shutdown,
    185};