diff --git a/drivers/misc/mei/Kconfig b/drivers/misc/mei/Kconfig index 5902dd1ee44b..8d192c3a1d59 100644 --- a/drivers/misc/mei/Kconfig +++ b/drivers/misc/mei/Kconfig @@ -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 diff --git a/drivers/misc/mei/Makefile b/drivers/misc/mei/Makefile index a203ed766b33..9a6aa335921e 100644 --- a/drivers/misc/mei/Makefile +++ b/drivers/misc/mei/Makefile @@ -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 diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h index 840e1fd2714c..9b1675f98404 100644 --- a/drivers/misc/mei/hw-me-regs.h +++ b/drivers/misc/mei/hw-me-regs.h @@ -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 diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index 72a7cfb2989f..3412a7b5b0e8 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -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", ®); + 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) diff --git a/drivers/misc/mei/hw-me.h b/drivers/misc/mei/hw-me.h index 9df5899d2602..8da8662a9d61 100644 --- a/drivers/misc/mei/hw-me.h +++ b/drivers/misc/mei/hw-me.h @@ -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, }; diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index f54991b40fc7..766f119f7ed0 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -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)); diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 0bf8d552c3ea..1796c6793a94 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -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 */ diff --git a/drivers/misc/mei/pci-csc.c b/drivers/misc/mei/pci-csc.c new file mode 100644 index 000000000000..15e170b1e0b6 --- /dev/null +++ b/drivers/misc/mei/pci-csc.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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");