mei: csc: support controller with separate PCI device

Intel PCI driver for chassis controller embedded in Intel graphics
devices.

An MEI device here called CSC can be embedded in discrete
Intel graphics devices having separate PCI device, to support a range
of chassis tasks such as graphics card firmware update and security tasks.

Reviewed-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Signed-off-by: Alexander Usyskin <alexander.usyskin@intel.com>
Link: https://patch.msgid.link/20260201094358.1440593-7-alexander.usyskin@intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Alexander Usyskin 2026-02-01 11:43:57 +02:00 committed by Greg Kroah-Hartman
parent 60ca15971a
commit 72fdf0bbd3
8 changed files with 311 additions and 1 deletions

View File

@ -58,6 +58,17 @@ config INTEL_MEI_GSC
tasks such as graphics card firmware update and security
tasks.
config INTEL_MEI_CSC
tristate "Intel MEI CSC embedded device"
depends on INTEL_MEI_ME
help
Intel PCI driver for the chassis controller embedded in Intel graphics devices.
An MEI device here called CSC can be embedded in discrete
Intel graphics devices, to support a range of chassis
tasks such as graphics card firmware update and security
tasks.
config INTEL_MEI_VSC_HW
tristate "Intel visual sensing controller device transport driver"
depends on ACPI && SPI

View File

@ -21,6 +21,9 @@ mei-me-objs += hw-me.o
obj-$(CONFIG_INTEL_MEI_GSC) += mei-gsc.o
mei-gsc-objs := gsc-me.o
obj-$(CONFIG_INTEL_MEI_CSC) += mei-csc.o
mei-csc-objs := pci-csc.o
obj-$(CONFIG_INTEL_MEI_TXE) += mei-txe.o
mei-txe-objs := pci-txe.o
mei-txe-objs += hw-txe.o

View File

@ -124,6 +124,8 @@
#define PCI_DEVICE_ID_INTEL_MEI_NVL_S 0x6E68 /* Nova Lake Point S */
#define PCI_DEVICE_ID_INTEL_MEI_CRI 0x6766 /* Crescent Island */
/*
* MEI HW Section
*/
@ -134,6 +136,7 @@
# define PCI_CFG_HFS_1_OPMODE_MSK 0xf0000 /* OP MODE Mask: SPS <= 4.0 */
# define PCI_CFG_HFS_1_OPMODE_SPS 0xf0000 /* SPS SKU : SPS <= 4.0 */
#define PCI_CFG_HFS_2 0x48
# define PCI_CFG_HFS_2_D3_BLOCK BIT(7)
# define PCI_CFG_HFS_2_PM_CMOFF_TO_CMX_ERROR 0x1000000 /* CMoff->CMx wake after an error */
# define PCI_CFG_HFS_2_PM_CM_RESET_ERROR 0x5000000 /* CME reset due to exception */
# define PCI_CFG_HFS_2_PM_EVENT_MASK 0xf000000

View File

@ -224,6 +224,15 @@ static int mei_me_fw_status(struct mei_device *dev,
return 0;
}
static bool mei_csc_pg_blocked(struct mei_device *dev)
{
struct mei_me_hw *hw = to_me_hw(dev);
u32 reg = 0;
hw->read_fws(dev, PCI_CFG_HFS_2, "PCI_CFG_HFS_2", &reg);
return (reg & PCI_CFG_HFS_2_D3_BLOCK) == PCI_CFG_HFS_2_D3_BLOCK;
}
/**
* mei_me_hw_config - configure hw dependent settings
*
@ -1206,6 +1215,7 @@ static int mei_me_hw_reset(struct mei_device *dev, bool intr_enable)
return ret;
} else {
hw->pg_state = MEI_PG_OFF;
dev->pg_blocked = mei_csc_pg_blocked(dev);
}
}
@ -1294,6 +1304,7 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id)
{
struct mei_device *dev = (struct mei_device *) dev_id;
struct list_head cmpl_list;
bool pg_blocked;
s32 slots;
u32 hcsr;
int rets = 0;
@ -1351,6 +1362,14 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id)
}
goto end;
}
pg_blocked = mei_csc_pg_blocked(dev);
if (pg_blocked && !dev->pg_blocked) /* PG block requested */
pm_request_resume(&dev->dev);
else if (!pg_blocked && dev->pg_blocked) /* PG block lifted */
pm_request_autosuspend(&dev->dev);
dev->pg_blocked = pg_blocked;
/* check slots available for reading */
slots = mei_count_full_read_slots(dev);
while (slots > 0) {
@ -1726,6 +1745,13 @@ static const struct mei_cfg mei_me_gscfi_cfg = {
MEI_CFG_FW_VER_SUPP,
};
/* Chassis System Controller Firmware Interface */
static const struct mei_cfg mei_me_csc_cfg = {
MEI_CFG_TYPE_GSCFI,
MEI_CFG_PCH8_HFS,
MEI_CFG_FW_VER_SUPP,
};
/*
* mei_cfg_list - A list of platform platform specific configurations.
* Note: has to be synchronized with enum mei_cfg_idx.
@ -1748,6 +1774,7 @@ static const struct mei_cfg *const mei_cfg_list[] = {
[MEI_ME_PCH15_SPS_CFG] = &mei_me_pch15_sps_cfg,
[MEI_ME_GSC_CFG] = &mei_me_gsc_cfg,
[MEI_ME_GSCFI_CFG] = &mei_me_gscfi_cfg,
[MEI_ME_CSC_CFG] = &mei_me_csc_cfg,
};
const struct mei_cfg *mei_me_get_cfg(kernel_ulong_t idx)

View File

@ -104,6 +104,7 @@ static inline bool mei_me_hw_use_polling(const struct mei_me_hw *hw)
* SPS firmware exclusion.
* @MEI_ME_GSC_CFG: Graphics System Controller
* @MEI_ME_GSCFI_CFG: Graphics System Controller Firmware Interface
* @MEI_ME_CSC_CFG: Chassis System Controller Firmware Interface
* @MEI_ME_NUM_CFG: Upper Sentinel.
*/
enum mei_cfg_idx {
@ -124,6 +125,7 @@ enum mei_cfg_idx {
MEI_ME_PCH15_SPS_CFG,
MEI_ME_GSC_CFG,
MEI_ME_GSCFI_CFG,
MEI_ME_CSC_CFG,
MEI_ME_NUM_CFG,
};

View File

@ -344,13 +344,15 @@ EXPORT_SYMBOL_GPL(mei_stop);
bool mei_write_is_idle(struct mei_device *dev)
{
bool idle = (dev->dev_state == MEI_DEV_ENABLED &&
!dev->pg_blocked &&
list_empty(&dev->ctrl_wr_list) &&
list_empty(&dev->write_list) &&
list_empty(&dev->write_waiting_list));
dev_dbg(&dev->dev, "write pg: is idle[%d] state=%s ctrl=%d write=%d wwait=%d\n",
dev_dbg(&dev->dev, "write pg: is idle[%d] state=%s blocked=%d ctrl=%d write=%d wwait=%d\n",
idle,
mei_dev_state_str(dev->dev_state),
dev->pg_blocked,
list_empty(&dev->ctrl_wr_list),
list_empty(&dev->write_list),
list_empty(&dev->write_waiting_list));

View File

@ -490,6 +490,7 @@ struct mei_dev_timeouts {
* @timer_work : MEI timer delayed work (timeouts)
*
* @recvd_hw_ready : hw ready message received flag
* @pg_blocked : low power mode is not allowed
*
* @wait_hw_ready : wait queue for receive HW ready message form FW
* @wait_pg : wait queue for receive PG message from FW
@ -575,6 +576,8 @@ struct mei_device {
struct delayed_work timer_work;
bool recvd_hw_ready;
bool pg_blocked;
/*
* waiting queue for receive message from FW
*/

259
drivers/misc/mei/pci-csc.c Normal file
View File

@ -0,0 +1,259 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2025-2026, Intel Corporation. All rights reserved.
* Intel Management Engine Interface (Intel MEI) Linux driver
* for CSC platforms.
*/
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/pm_runtime.h>
#include <linux/sched.h>
#include <linux/types.h>
#include "client.h"
#include "hw-me-regs.h"
#include "hw-me.h"
#include "mei_dev.h"
#include "mei-trace.h"
#define MEI_CSC_HECI2_OFFSET 0x1000
static int mei_csc_read_fws(const struct mei_device *mdev, int where, const char *name, u32 *val)
{
struct mei_me_hw *hw = to_me_hw(mdev);
*val = ioread32(hw->mem_addr + where + 0xC00);
trace_mei_reg_read(&mdev->dev, name, where, *val);
return 0;
}
static int mei_csc_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
struct device *dev = &pdev->dev;
const struct mei_cfg *cfg;
char __iomem *registers;
struct mei_device *mdev;
struct mei_me_hw *hw;
int err;
cfg = mei_me_get_cfg(ent->driver_data);
if (!cfg)
return -ENODEV;
err = pcim_enable_device(pdev);
if (err)
return dev_err_probe(dev, err, "Failed to enable PCI device.\n");
pci_set_master(pdev);
registers = pcim_iomap_region(pdev, 0, KBUILD_MODNAME);
if (IS_ERR(registers))
return dev_err_probe(dev, PTR_ERR(registers), "Failed to get PCI region.\n");
err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
if (err)
return dev_err_probe(dev, err, "No usable DMA configuration.\n");
/* allocates and initializes the mei dev structure */
mdev = mei_me_dev_init(dev, cfg, false);
if (!mdev)
return -ENOMEM;
hw = to_me_hw(mdev);
/*
* Both HECI1 and HECI2 are on this device, but only HECI2 is supported.
*/
hw->mem_addr = registers + MEI_CSC_HECI2_OFFSET;
hw->read_fws = mei_csc_read_fws;
/*
* mei_register() assumes ownership of mdev.
* No need to release it explicitly in error path.
*/
err = mei_register(mdev, dev);
if (err)
return err;
pci_set_drvdata(pdev, mdev);
err = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_INTX | PCI_IRQ_MSI);
if (err < 0) {
dev_err_probe(dev, err, "Failed to allocate IRQ.\n");
goto err_mei_unreg;
}
hw->irq = pci_irq_vector(pdev, 0);
/* request and enable interrupt */
err = request_threaded_irq(hw->irq,
mei_me_irq_quick_handler, mei_me_irq_thread_handler,
IRQF_SHARED | IRQF_ONESHOT, KBUILD_MODNAME, mdev);
if (err)
goto err_free_irq_vectors;
/*
* Continue to char device setup in spite of firmware handshake failure.
* In order to provide access to the firmware status registers to the user
* space via sysfs. The firmware status registers required to understand
* firmware error state and possible recovery flow.
*/
if (mei_start(mdev))
dev_warn(dev, "Failed to initialize HECI hardware.\n");
pm_runtime_set_autosuspend_delay(dev, MEI_ME_RPM_TIMEOUT);
pm_runtime_use_autosuspend(dev);
/*
* MEI requires to resume from runtime suspend mode
* in order to perform link reset flow upon system suspend.
*/
dev_pm_set_driver_flags(dev, DPM_FLAG_NO_DIRECT_COMPLETE);
pm_runtime_allow(dev);
pm_runtime_put_noidle(dev);
return 0;
err_free_irq_vectors:
pci_free_irq_vectors(pdev);
err_mei_unreg:
mei_deregister(mdev);
return err;
}
static void mei_csc_shutdown(struct pci_dev *pdev)
{
struct mei_device *mdev = pci_get_drvdata(pdev);
struct mei_me_hw *hw = to_me_hw(mdev);
pm_runtime_get_noresume(&pdev->dev);
mei_stop(mdev);
mei_disable_interrupts(mdev);
free_irq(hw->irq, mdev);
pci_free_irq_vectors(pdev);
}
static void mei_csc_remove(struct pci_dev *pdev)
{
struct mei_device *mdev = pci_get_drvdata(pdev);
mei_csc_shutdown(pdev);
mei_deregister(mdev);
}
static int mei_csc_pci_prepare(struct device *dev)
{
pm_runtime_resume(dev);
return 0;
}
static int mei_csc_pci_suspend(struct device *dev)
{
struct mei_device *mdev = dev_get_drvdata(dev);
mei_stop(mdev);
mei_disable_interrupts(mdev);
return 0;
}
static int mei_csc_pci_resume(struct device *dev)
{
struct mei_device *mdev = dev_get_drvdata(dev);
int err;
err = mei_restart(mdev);
if (err)
return err;
/* Start timer if stopped in suspend */
schedule_delayed_work(&mdev->timer_work, HZ);
return 0;
}
static void mei_csc_pci_complete(struct device *dev)
{
pm_runtime_suspend(dev);
}
static int mei_csc_pm_runtime_idle(struct device *dev)
{
struct mei_device *mdev = dev_get_drvdata(dev);
return mei_write_is_idle(mdev) ? 0 : -EBUSY;
}
static int mei_csc_pm_runtime_suspend(struct device *dev)
{
struct mei_device *mdev = dev_get_drvdata(dev);
struct mei_me_hw *hw = to_me_hw(mdev);
guard(mutex)(&mdev->device_lock);
if (!mei_write_is_idle(mdev))
return -EAGAIN;
hw->pg_state = MEI_PG_ON;
return 0;
}
static int mei_csc_pm_runtime_resume(struct device *dev)
{
struct mei_device *mdev = dev_get_drvdata(dev);
struct mei_me_hw *hw = to_me_hw(mdev);
irqreturn_t irq_ret;
scoped_guard(mutex, &mdev->device_lock)
hw->pg_state = MEI_PG_OFF;
/* Process all queues that wait for resume */
irq_ret = mei_me_irq_thread_handler(1, mdev);
if (irq_ret != IRQ_HANDLED)
dev_err(dev, "thread handler fail %d\n", irq_ret);
return 0;
}
static const struct dev_pm_ops mei_csc_pm_ops = {
.prepare = pm_sleep_ptr(mei_csc_pci_prepare),
.complete = pm_sleep_ptr(mei_csc_pci_complete),
SYSTEM_SLEEP_PM_OPS(mei_csc_pci_suspend, mei_csc_pci_resume)
RUNTIME_PM_OPS(mei_csc_pm_runtime_suspend,
mei_csc_pm_runtime_resume, mei_csc_pm_runtime_idle)
};
static const struct pci_device_id mei_csc_pci_tbl[] = {
{ PCI_DEVICE_DATA(INTEL, MEI_CRI, MEI_ME_CSC_CFG) },
{}
};
MODULE_DEVICE_TABLE(pci, mei_csc_pci_tbl);
static struct pci_driver mei_csc_driver = {
.name = KBUILD_MODNAME,
.id_table = mei_csc_pci_tbl,
.probe = mei_csc_probe,
.remove = mei_csc_remove,
.shutdown = mei_csc_shutdown,
.driver = {
.pm = &mei_csc_pm_ops,
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
}
};
module_pci_driver(mei_csc_driver);
MODULE_DESCRIPTION("Intel(R) Management Engine Interface for discrete graphics (CSC)");
MODULE_LICENSE("GPL");