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/mfd/lpc_sch.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/mfd/lpc_sch.c')
| -rw-r--r-- | drivers/mfd/lpc_sch.c | 188 |
1 files changed, 188 insertions, 0 deletions
diff --git a/drivers/mfd/lpc_sch.c b/drivers/mfd/lpc_sch.c new file mode 100644 index 000000000..d069165f9 --- /dev/null +++ b/drivers/mfd/lpc_sch.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * lpc_sch.c - LPC interface for Intel Poulsbo SCH + * + * LPC bridge function of the Intel SCH contains many other + * functional units, such as Interrupt controllers, Timers, + * Power Management, System Management, GPIO, RTC, and LPC + * Configuration Registers. + * + * Copyright (c) 2010 CompuLab Ltd + * Copyright (c) 2014 Intel Corp. + * Author: Denis Turischev <denis@compulab.co.il> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/acpi.h> +#include <linux/pci.h> +#include <linux/mfd/core.h> + +#define SMBASE 0x40 +#define SMBUS_IO_SIZE 64 + +#define GPIO_BASE 0x44 +#define GPIO_IO_SIZE 64 +#define GPIO_IO_SIZE_CENTERTON 128 + +#define WDTBASE 0x84 +#define WDT_IO_SIZE 64 + +enum sch_chipsets { + LPC_SCH = 0, /* Intel Poulsbo SCH */ + LPC_ITC, /* Intel Tunnel Creek */ + LPC_CENTERTON, /* Intel Centerton */ + LPC_QUARK_X1000, /* Intel Quark X1000 */ +}; + +struct lpc_sch_info { + unsigned int io_size_smbus; + unsigned int io_size_gpio; + unsigned int io_size_wdt; +}; + +static struct lpc_sch_info sch_chipset_info[] = { + [LPC_SCH] = { + .io_size_smbus = SMBUS_IO_SIZE, + .io_size_gpio = GPIO_IO_SIZE, + }, + [LPC_ITC] = { + .io_size_smbus = SMBUS_IO_SIZE, + .io_size_gpio = GPIO_IO_SIZE, + .io_size_wdt = WDT_IO_SIZE, + }, + [LPC_CENTERTON] = { + .io_size_smbus = SMBUS_IO_SIZE, + .io_size_gpio = GPIO_IO_SIZE_CENTERTON, + .io_size_wdt = WDT_IO_SIZE, + }, + [LPC_QUARK_X1000] = { + .io_size_gpio = GPIO_IO_SIZE, + .io_size_wdt = WDT_IO_SIZE, + }, +}; + +static const struct pci_device_id lpc_sch_ids[] = { + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_SCH_LPC), .driver_data = LPC_SCH }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ITC_LPC), .driver_data = LPC_ITC }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_CENTERTON_ILB), .driver_data = LPC_CENTERTON }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_QUARK_X1000_ILB), .driver_data = LPC_QUARK_X1000 }, + { } +}; +MODULE_DEVICE_TABLE(pci, lpc_sch_ids); + +#define LPC_NO_RESOURCE 1 +#define LPC_SKIP_RESOURCE 2 + +static int lpc_sch_get_io(struct pci_dev *pdev, int where, const char *name, + struct resource *res, int size) +{ + unsigned int base_addr_cfg; + unsigned short base_addr; + + if (size == 0) + return LPC_NO_RESOURCE; + + pci_read_config_dword(pdev, where, &base_addr_cfg); + base_addr = 0; + if (!(base_addr_cfg & (1 << 31))) + dev_warn(&pdev->dev, "Decode of the %s I/O range disabled\n", + name); + else + base_addr = (unsigned short)base_addr_cfg; + + if (base_addr == 0) { + dev_warn(&pdev->dev, "I/O space for %s uninitialized\n", name); + return LPC_SKIP_RESOURCE; + } + + res->start = base_addr; + res->end = base_addr + size - 1; + res->flags = IORESOURCE_IO; + + return 0; +} + +static int lpc_sch_populate_cell(struct pci_dev *pdev, int where, + const char *name, int size, int id, + struct mfd_cell *cell) +{ + struct resource *res; + int ret; + + res = devm_kzalloc(&pdev->dev, sizeof(*res), GFP_KERNEL); + if (!res) + return -ENOMEM; + + ret = lpc_sch_get_io(pdev, where, name, res, size); + if (ret) + return ret; + + memset(cell, 0, sizeof(*cell)); + + cell->name = name; + cell->resources = res; + cell->num_resources = 1; + cell->ignore_resource_conflicts = true; + cell->id = id; + + return 0; +} + +static int lpc_sch_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + struct mfd_cell lpc_sch_cells[3]; + struct lpc_sch_info *info = &sch_chipset_info[id->driver_data]; + unsigned int cells = 0; + int ret; + + ret = lpc_sch_populate_cell(dev, SMBASE, "isch_smbus", + info->io_size_smbus, + id->device, &lpc_sch_cells[cells]); + if (ret < 0) + return ret; + if (ret == 0) + cells++; + + ret = lpc_sch_populate_cell(dev, GPIO_BASE, "sch_gpio", + info->io_size_gpio, + id->device, &lpc_sch_cells[cells]); + if (ret < 0) + return ret; + if (ret == 0) + cells++; + + ret = lpc_sch_populate_cell(dev, WDTBASE, "ie6xx_wdt", + info->io_size_wdt, + id->device, &lpc_sch_cells[cells]); + if (ret < 0) + return ret; + if (ret == 0) + cells++; + + if (cells == 0) { + dev_err(&dev->dev, "All decode registers disabled.\n"); + return -ENODEV; + } + + return mfd_add_devices(&dev->dev, 0, lpc_sch_cells, cells, NULL, 0, NULL); +} + +static void lpc_sch_remove(struct pci_dev *dev) +{ + mfd_remove_devices(&dev->dev); +} + +static struct pci_driver lpc_sch_driver = { + .name = "lpc_sch", + .id_table = lpc_sch_ids, + .probe = lpc_sch_probe, + .remove = lpc_sch_remove, +}; + +module_pci_driver(lpc_sch_driver); + +MODULE_AUTHOR("Denis Turischev <denis@compulab.co.il>"); +MODULE_DESCRIPTION("LPC interface for Intel Poulsbo SCH"); +MODULE_LICENSE("GPL"); |
