diff options
| author | Tim Chen <tim.c.chen@linux.intel.com> | 2026-09-21 17:37:22 -0700 |
|---|---|---|
| committer | Ingo Molnar <mingo@kernel.org> | 2026-09-22 10:49:53 +0200 |
| commit | 0d6526f82c3cdefcca47f73f5fc08dc6f335eac6 (patch) | |
| tree | 176dc2d685debcfb082c151ee94f2425ac96b364 /drivers/crypto/ccp/psp-dev.c | |
| download | linux-stable-0d6526f82c3cdefcca47f73f5fc08dc6f335eac6.tar.gz linux-stable-0d6526f82c3cdefcca47f73f5fc08dc6f335eac6.zip | |
sched/cache: Keep nr_pref_llc_running in the runnable domain, to fix LLC mis-scheduling buggrafted
alb_break_llc() decides whether to break LLC preference during active
load balance. It does so by testing that every runnable fair task on the
source rq prefers its LLC:
env->src_rq->nr_pref_llc_running == env->src_rq->cfs.h_nr_runnable
But the two counters cover different sets. nr_pref_llc_running is updated
in account_llc_enqueue()/account_llc_dequeue(), next to cfs_rq->nr_queued,
so it follows queued tasks. h_nr_runnable is updated in set_delayed()/
clear_delayed() and drops delay-dequeued tasks.
So under DELAY_DEQUEUE, a preferring task that goes to sleep stays counted
in nr_pref_llc_running while h_nr_runnable falls. The equality then breaks,
alb_break_llc() returns false, and active balance is free to pull a task
off its preferred LLC. Active balance only moves runnable tasks, and this
is the only LLC check it consults: once the stopper runs, LBF_ACTIVE_LB
skips the per-task test in can_migrate_task(). The runnable set is the one
we want.
Fix it on the counter side. A task should be counted in
nr_pref_llc_running exactly while it is both queued on its preferred LLC
(pref_llc_queued) and runnable (!sched_delayed). Define that membership
once in task_pref_llc_runnable(), and adjust the counter only through
pref_llc_running_inc()/pref_llc_running_dec() from the four sites that
change either input: account_llc_enqueue(), account_llc_dequeue(),
set_delayed() and clear_delayed(). Gating every update on the same
predicate keeps the delay, wake and dequeue paths from double-counting
or underflowing; see the comments at those sites for the ordering.
nr_llc_running and sd->llc_counts are not touched and stay on queued
semantics.
Fixes: 714059f79ff0 ("sched/cache: Handle moving single tasks to/from their preferred LLC")
Closes: https://lore.kernel.org/lkml/20260827135000.735138-1-zhanxusheng@xiaomi.com/
Reported-by: Zhan Xusheng <zhanxusheng@xiaomi.com>
Suggested-by: Chen Yu <yu.c.chen@intel.com>
Signed-off-by: Tim Chen <tim.c.chen@linux.intel.com>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Reviewed-by: Kayra Cizmeci <kayracizmeci@gmail.com>
Cc: <stable@kernel.org> # v7.2.x
Link: https://patch.msgid.link/06af61afedac32e6477f57feb4d658f6c411c3af.1790035273.git.tim.c.chen@linux.intel.com
Diffstat (limited to 'drivers/crypto/ccp/psp-dev.c')
| -rw-r--r-- | drivers/crypto/ccp/psp-dev.c | 381 |
1 files changed, 381 insertions, 0 deletions
diff --git a/drivers/crypto/ccp/psp-dev.c b/drivers/crypto/ccp/psp-dev.c new file mode 100644 index 000000000..b14ce5106 --- /dev/null +++ b/drivers/crypto/ccp/psp-dev.c @@ -0,0 +1,381 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AMD Platform Security Processor (PSP) interface + * + * Copyright (C) 2016,2019 Advanced Micro Devices, Inc. + * + * Author: Brijesh Singh <brijesh.singh@amd.com> + */ + +#include <linux/kernel.h> +#include <linux/irqreturn.h> +#include <linux/mutex.h> +#include <linux/bitfield.h> +#include <linux/delay.h> + +#include "sp-dev.h" +#include "psp-dev.h" +#include "sev-dev.h" +#include "tee-dev.h" +#include "sfs.h" +#include "platform-access.h" +#include "dbc.h" +#include "hsti.h" + +struct psp_device *psp_master; + +#define PSP_C2PMSG_17_CMDRESP_CMD GENMASK(19, 16) + +static int psp_mailbox_poll(const void __iomem *cmdresp_reg, unsigned int *cmdresp, + unsigned int timeout_msecs) +{ + while (true) { + *cmdresp = ioread32(cmdresp_reg); + if (FIELD_GET(PSP_CMDRESP_RESP, *cmdresp)) + return 0; + + if (!timeout_msecs--) + break; + + usleep_range(1000, 1100); + } + + return -ETIMEDOUT; +} + +int psp_mailbox_command(struct psp_device *psp, enum psp_cmd cmd, void *cmdbuff, + unsigned int timeout_msecs, unsigned int *cmdresp) +{ + void __iomem *cmdresp_reg, *cmdbuff_lo_reg, *cmdbuff_hi_reg; + int ret; + + if (!psp || !psp->vdata || !psp->vdata->cmdresp_reg || + !psp->vdata->cmdbuff_addr_lo_reg || !psp->vdata->cmdbuff_addr_hi_reg) + return -ENODEV; + + cmdresp_reg = psp->io_regs + psp->vdata->cmdresp_reg; + cmdbuff_lo_reg = psp->io_regs + psp->vdata->cmdbuff_addr_lo_reg; + cmdbuff_hi_reg = psp->io_regs + psp->vdata->cmdbuff_addr_hi_reg; + + mutex_lock(&psp->mailbox_mutex); + + /* Ensure mailbox is ready for a command */ + ret = -EBUSY; + if (psp_mailbox_poll(cmdresp_reg, cmdresp, 0)) + goto unlock; + + if (cmdbuff) { + iowrite32(lower_32_bits(__psp_pa(cmdbuff)), cmdbuff_lo_reg); + iowrite32(upper_32_bits(__psp_pa(cmdbuff)), cmdbuff_hi_reg); + } + + *cmdresp = FIELD_PREP(PSP_C2PMSG_17_CMDRESP_CMD, cmd); + iowrite32(*cmdresp, cmdresp_reg); + + ret = psp_mailbox_poll(cmdresp_reg, cmdresp, timeout_msecs); + +unlock: + mutex_unlock(&psp->mailbox_mutex); + + return ret; +} + +int psp_extended_mailbox_cmd(struct psp_device *psp, unsigned int timeout_msecs, + struct psp_ext_request *req) +{ + unsigned int reg; + int ret; + + print_hex_dump_debug("->psp ", DUMP_PREFIX_OFFSET, 16, 2, req, + req->header.payload_size, false); + + ret = psp_mailbox_command(psp, PSP_CMD_TEE_EXTENDED_CMD, (void *)req, + timeout_msecs, ®); + if (ret) { + return ret; + } else if (FIELD_GET(PSP_CMDRESP_STS, reg)) { + req->header.status = FIELD_GET(PSP_CMDRESP_STS, reg); + return -EIO; + } + + print_hex_dump_debug("<-psp ", DUMP_PREFIX_OFFSET, 16, 2, req, + req->header.payload_size, false); + + return 0; +} + +static struct psp_device *psp_alloc_struct(struct sp_device *sp) +{ + struct device *dev = sp->dev; + struct psp_device *psp; + + psp = devm_kzalloc(dev, sizeof(*psp), GFP_KERNEL); + if (!psp) + return NULL; + + psp->dev = dev; + psp->sp = sp; + + snprintf(psp->name, sizeof(psp->name), "psp-%u", sp->ord); + + return psp; +} + +static irqreturn_t psp_irq_handler(int irq, void *data) +{ + struct psp_device *psp = data; + unsigned int status; + + /* Read the interrupt status: */ + status = ioread32(psp->io_regs + psp->vdata->intsts_reg); + + /* Clear the interrupt status by writing the same value we read. */ + iowrite32(status, psp->io_regs + psp->vdata->intsts_reg); + + /* invoke subdevice interrupt handlers */ + if (status) { + if (psp->sev_irq_handler) + psp->sev_irq_handler(irq, psp->sev_irq_data, status); + } + + return IRQ_HANDLED; +} + +static unsigned int psp_get_capability(struct psp_device *psp) +{ + unsigned int val = ioread32(psp->io_regs + psp->vdata->feature_reg); + + /* + * Check for a access to the registers. If this read returns + * 0xffffffff, it's likely that the system is running a broken + * BIOS which disallows access to the device. Stop here and + * fail the PSP initialization (but not the load, as the CCP + * could get properly initialized). + */ + if (val == 0xffffffff) { + dev_notice(psp->dev, "psp: unable to access the device: you might be running a broken BIOS.\n"); + return -ENODEV; + } + psp->capability.raw = val; + + return 0; +} + +static int psp_check_sev_support(struct psp_device *psp) +{ + /* Check if device supports SEV feature */ + if (!psp->capability.sev) { + dev_dbg(psp->dev, "psp does not support SEV\n"); + return -ENODEV; + } + + return 0; +} + +static int psp_check_tee_support(struct psp_device *psp) +{ + /* Check if device supports TEE feature */ + if (!psp->capability.tee) { + dev_dbg(psp->dev, "psp does not support TEE\n"); + return -ENODEV; + } + + return 0; +} + +static int psp_check_sfs_support(struct psp_device *psp) +{ + /* Check if device supports SFS feature */ + if (!psp->capability.sfs) { + dev_dbg(psp->dev, "psp does not support SFS\n"); + return -ENODEV; + } + + return 0; +} + +static int psp_init(struct psp_device *psp) +{ + int ret; + + if (!psp_check_sev_support(psp)) { + ret = sev_dev_init(psp); + if (ret) + return ret; + } + + if (!psp_check_tee_support(psp)) { + ret = tee_dev_init(psp); + if (ret) + return ret; + } + + if (!psp_check_sfs_support(psp)) { + ret = sfs_dev_init(psp); + if (ret) + return ret; + } + + if (psp->vdata->platform_access) { + ret = platform_access_dev_init(psp); + if (ret) + return ret; + } + + /* dbc must come after platform access as it tests the feature */ + if (PSP_FEATURE(psp, DBC) || + psp->capability.dbc_thru_ext) { + ret = dbc_dev_init(psp); + if (ret) + return ret; + } + + /* HSTI uses platform access on some systems. */ + ret = psp_init_hsti(psp); + if (ret) + return ret; + + return 0; +} + +int psp_dev_init(struct sp_device *sp) +{ + struct device *dev = sp->dev; + struct psp_device *psp; + int ret; + + ret = -ENOMEM; + psp = psp_alloc_struct(sp); + if (!psp) + goto e_err; + + sp->psp_data = psp; + + psp->vdata = (struct psp_vdata *)sp->dev_vdata->psp_vdata; + if (!psp->vdata) { + ret = -ENODEV; + dev_err(dev, "missing driver data\n"); + goto e_err; + } + + psp->io_regs = sp->io_map; + mutex_init(&psp->mailbox_mutex); + + ret = psp_get_capability(psp); + if (ret) + goto e_disable; + + /* Disable and clear interrupts until ready */ + iowrite32(0, psp->io_regs + psp->vdata->inten_reg); + iowrite32(-1, psp->io_regs + psp->vdata->intsts_reg); + + /* Request an irq */ + ret = sp_request_psp_irq(psp->sp, psp_irq_handler, psp->name, psp); + if (ret) { + dev_err(dev, "psp: unable to allocate an IRQ\n"); + goto e_err; + } + + /* master device must be set for platform access */ + if (psp->sp->set_psp_master_device) + psp->sp->set_psp_master_device(psp->sp); + + ret = psp_init(psp); + if (ret) + goto e_irq; + + /* Enable interrupt */ + iowrite32(-1, psp->io_regs + psp->vdata->inten_reg); + + dev_notice(dev, "psp enabled\n"); + + return 0; + +e_irq: + if (sp->clear_psp_master_device) + sp->clear_psp_master_device(sp); + + sp_free_psp_irq(psp->sp, psp); +e_err: + sp->psp_data = NULL; + + dev_notice(dev, "psp initialization failed\n"); + + return ret; + +e_disable: + sp->psp_data = NULL; + + return ret; +} + +void psp_dev_destroy(struct sp_device *sp) +{ + struct psp_device *psp = sp->psp_data; + + if (!psp) + return; + + dbc_dev_destroy(psp); + + platform_access_dev_destroy(psp); + + sfs_dev_destroy(psp); + + tee_dev_destroy(psp); + + sev_dev_destroy(psp); + + sp_free_psp_irq(sp, psp); + + if (sp->clear_psp_master_device) + sp->clear_psp_master_device(sp); +} + +void psp_set_sev_irq_handler(struct psp_device *psp, psp_irq_handler_t handler, + void *data) +{ + psp->sev_irq_data = data; + psp->sev_irq_handler = handler; +} + +void psp_clear_sev_irq_handler(struct psp_device *psp) +{ + psp_set_sev_irq_handler(psp, NULL, NULL); +} + +struct psp_device *psp_get_master_device(void) +{ + struct sp_device *sp = sp_get_psp_master_device(); + + return sp ? sp->psp_data : NULL; +} + +int psp_restore(struct sp_device *sp) +{ + struct psp_device *psp = sp->psp_data; + int ret = 0; + + if (psp->tee_data) + ret = tee_restore(psp); + + return ret; +} + +void psp_pci_init(void) +{ + psp_master = psp_get_master_device(); + + if (!psp_master) + return; + + sev_pci_init(); +} + +void psp_pci_exit(void) +{ + if (!psp_master) + return; + + sev_pci_exit(); +} |
