diff options
Diffstat (limited to 'drivers/misc/mei')
39 files changed, 4953 insertions, 1264 deletions
diff --git a/drivers/misc/mei/Kconfig b/drivers/misc/mei/Kconfig index f5fd5b786607..f4eb307cd35e 100644 --- a/drivers/misc/mei/Kconfig +++ b/drivers/misc/mei/Kconfig @@ -3,6 +3,7 @@ config INTEL_MEI tristate "Intel Management Engine Interface" depends on X86 && PCI + default X86_64 || MATOM help The Intel Management Engine (Intel ME) provides Manageability, Security and Media services for system containing Intel chipsets. @@ -11,10 +12,11 @@ config INTEL_MEI For more information see <https://software.intel.com/en-us/manageability/> +if INTEL_MEI + config INTEL_MEI_ME tristate "ME Enabled Intel Chipsets" - select INTEL_MEI - depends on X86 && PCI + default y help MEI support for ME Enabled Intel chipsets. @@ -38,12 +40,62 @@ config INTEL_MEI_ME config INTEL_MEI_TXE tristate "Intel Trusted Execution Environment with ME Interface" - select INTEL_MEI - depends on X86 && PCI help MEI Support for Trusted Execution Environment device on Intel SoCs Supported SoCs: Intel Bay Trail +config INTEL_MEI_GSC + tristate "Intel MEI GSC embedded device" + depends on INTEL_MEI_ME + depends on DRM_I915 || DRM_XE + help + Intel auxiliary driver for GSC devices embedded in Intel graphics devices. + + An MEI device here called GSC can be embedded in an + 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 + depends on GPIOLIB || COMPILE_TEST + help + Intel SPI transport driver between host and Intel visual sensing + controller (IVSC) device. + + This driver can also be built as a module. If so, the module + will be called mei-vsc-hw. + +config INTEL_MEI_VSC + tristate "Intel visual sensing controller device with ME interface" + depends on INTEL_MEI_VSC_HW + help + Intel MEI over SPI driver for Intel visual sensing controller + (IVSC) device embedded in IA platform. It supports camera sharing + between IVSC for context sensing and IPU for typical media usage. + Select this config should enable transport layer for IVSC device. + + This driver can also be built as a module. If so, the module + will be called mei-vsc. + +config INTEL_MEI_LB + tristate "Intel Late Binding (LB) support on ME Interface" + depends on INTEL_MEI_ME + depends on DRM_XE + help + Enable support for Intel Late Binding (LB) via the MEI interface. + + Late Binding is a method for applying firmware updates at runtime, + allowing the Intel Xe driver to load firmware payloads such as + fan controller or voltage regulator. These firmware updates are + authenticated and versioned, and do not require firmware flashing + or system reboot. + source "drivers/misc/mei/hdcp/Kconfig" +source "drivers/misc/mei/pxp/Kconfig" +source "drivers/misc/mei/gsc_proxy/Kconfig" + +endif diff --git a/drivers/misc/mei/Makefile b/drivers/misc/mei/Makefile index f1c76f7ee804..a203ed766b33 100644 --- a/drivers/misc/mei/Makefile +++ b/drivers/misc/mei/Makefile @@ -18,6 +18,9 @@ obj-$(CONFIG_INTEL_MEI_ME) += mei-me.o mei-me-objs := pci-me.o mei-me-objs += hw-me.o +obj-$(CONFIG_INTEL_MEI_GSC) += mei-gsc.o +mei-gsc-objs := gsc-me.o + obj-$(CONFIG_INTEL_MEI_TXE) += mei-txe.o mei-txe-objs := pci-txe.o mei-txe-objs += hw-txe.o @@ -26,3 +29,13 @@ mei-$(CONFIG_EVENT_TRACING) += mei-trace.o CFLAGS_mei-trace.o = -I$(src) obj-$(CONFIG_INTEL_MEI_HDCP) += hdcp/ +obj-$(CONFIG_INTEL_MEI_PXP) += pxp/ +obj-$(CONFIG_INTEL_MEI_GSC_PROXY) += gsc_proxy/ +obj-$(CONFIG_INTEL_MEI_LB) += mei_lb.o + +obj-$(CONFIG_INTEL_MEI_VSC_HW) += mei-vsc-hw.o +mei-vsc-hw-y := vsc-tp.o +mei-vsc-hw-y += vsc-fw-loader.o + +obj-$(CONFIG_INTEL_MEI_VSC) += mei-vsc.o +mei-vsc-y := platform-vsc.o diff --git a/drivers/misc/mei/bus-fixup.c b/drivers/misc/mei/bus-fixup.c index 67844089db21..e6a1d3534663 100644 --- a/drivers/misc/mei/bus-fixup.c +++ b/drivers/misc/mei/bus-fixup.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Copyright (c) 2013-2020, Intel Corporation. All rights reserved. + * Copyright (c) 2013-2023, Intel Corporation. All rights reserved. * Intel Management Engine Interface (Intel MEI) Linux driver */ @@ -9,12 +9,13 @@ #include <linux/module.h> #include <linux/device.h> #include <linux/slab.h> -#include <linux/uuid.h> +#include <linux/mei.h> #include <linux/mei_cl_bus.h> #include "mei_dev.h" #include "client.h" +#include "mkhi.h" #define MEI_UUID_NFC_INFO UUID_LE(0xd2de1625, 0x382d, 0x417d, \ 0x48, 0xa4, 0xef, 0xab, 0xba, 0x8a, 0x12, 0x06) @@ -30,6 +31,12 @@ static const uuid_le mei_nfc_info_guid = MEI_UUID_NFC_INFO; #define MEI_UUID_MKHIF_FIX UUID_LE(0x55213584, 0x9a29, 0x4916, \ 0xba, 0xdf, 0xf, 0xb7, 0xed, 0x68, 0x2a, 0xeb) +#define MEI_UUID_IGSC_MKHI UUID_LE(0xE2C2AFA2, 0x3817, 0x4D19, \ + 0x9D, 0x95, 0x06, 0xB1, 0x6B, 0x58, 0x8A, 0x5D) + +#define MEI_UUID_IGSC_MKHI_FIX UUID_LE(0x46E0C1FB, 0xA546, 0x414F, \ + 0x91, 0x70, 0xB7, 0xF4, 0x6D, 0x57, 0xB4, 0xAD) + #define MEI_UUID_HDCP UUID_LE(0xB638AB7E, 0x94E2, 0x4EA2, \ 0xA5, 0x52, 0xD1, 0xC5, 0x4B, 0x62, 0x7F, 0x04) @@ -73,6 +80,8 @@ static void whitelist(struct mei_cl_device *cldev) cldev->do_match = 1; } +#define MKHI_SEND_MAX_TIMEOUT_MSEC 4000 + #define OSTYPE_LINUX 2 struct mei_os_ver { __le16 build; @@ -83,20 +92,6 @@ struct mei_os_ver { u8 reserved2; } __packed; -#define MKHI_FEATURE_PTT 0x10 - -struct mkhi_rule_id { - __le16 rule_type; - u8 feature_id; - u8 reserved; -} __packed; - -struct mkhi_fwcaps { - struct mkhi_rule_id id; - u8 len; - u8 data[]; -} __packed; - struct mkhi_fw_ver_block { u16 minor; u8 major; @@ -109,29 +104,13 @@ struct mkhi_fw_ver { struct mkhi_fw_ver_block ver[MEI_MAX_FW_VER_BLOCKS]; } __packed; -#define MKHI_FWCAPS_GROUP_ID 0x3 -#define MKHI_FWCAPS_SET_OS_VER_APP_RULE_CMD 6 -#define MKHI_GEN_GROUP_ID 0xFF -#define MKHI_GEN_GET_FW_VERSION_CMD 0x2 -struct mkhi_msg_hdr { - u8 group_id; - u8 command; - u8 reserved; - u8 result; -} __packed; - -struct mkhi_msg { - struct mkhi_msg_hdr hdr; - u8 data[]; -} __packed; - #define MKHI_OSVER_BUF_LEN (sizeof(struct mkhi_msg_hdr) + \ sizeof(struct mkhi_fwcaps) + \ sizeof(struct mei_os_ver)) static int mei_osver(struct mei_cl_device *cldev) { const size_t size = MKHI_OSVER_BUF_LEN; - char buf[MKHI_OSVER_BUF_LEN]; + u8 buf[MKHI_OSVER_BUF_LEN]; struct mkhi_msg *req; struct mkhi_fwcaps *fwcaps; struct mei_os_ver *os_ver; @@ -151,17 +130,16 @@ static int mei_osver(struct mei_cl_device *cldev) os_ver = (struct mei_os_ver *)fwcaps->data; os_ver->os_type = OSTYPE_LINUX; - return __mei_cl_send(cldev->cl, buf, size, 0, mode); + return __mei_cl_send_timeout(cldev->cl, buf, size, 0, mode, MKHI_SEND_MAX_TIMEOUT_MSEC); } #define MKHI_FWVER_BUF_LEN (sizeof(struct mkhi_msg_hdr) + \ sizeof(struct mkhi_fw_ver)) #define MKHI_FWVER_LEN(__num) (sizeof(struct mkhi_msg_hdr) + \ sizeof(struct mkhi_fw_ver_block) * (__num)) -#define MKHI_RCV_TIMEOUT 500 /* receive timeout in msec */ static int mei_fwver(struct mei_cl_device *cldev) { - char buf[MKHI_FWVER_BUF_LEN]; + u8 buf[MKHI_FWVER_BUF_LEN]; struct mkhi_msg req; struct mkhi_msg *rsp; struct mkhi_fw_ver *fwver; @@ -172,22 +150,22 @@ static int mei_fwver(struct mei_cl_device *cldev) req.hdr.group_id = MKHI_GEN_GROUP_ID; req.hdr.command = MKHI_GEN_GET_FW_VERSION_CMD; - ret = __mei_cl_send(cldev->cl, (u8 *)&req, sizeof(req), 0, - MEI_CL_IO_TX_BLOCKING); + ret = __mei_cl_send_timeout(cldev->cl, (u8 *)&req, sizeof(req), 0, + MEI_CL_IO_TX_BLOCKING, MKHI_SEND_MAX_TIMEOUT_MSEC); if (ret < 0) { - dev_err(&cldev->dev, "Could not send ReqFWVersion cmd\n"); + dev_info(&cldev->dev, "Could not send ReqFWVersion cmd ret = %d\n", ret); return ret; } ret = 0; bytes_recv = __mei_cl_recv(cldev->cl, buf, sizeof(buf), NULL, 0, - MKHI_RCV_TIMEOUT); + cldev->bus->timeouts.mkhi_recv); if (bytes_recv < 0 || (size_t)bytes_recv < MKHI_FWVER_LEN(1)) { /* * Should be at least one version block, * error out if nothing found */ - dev_err(&cldev->dev, "Could not read FW version\n"); + dev_info(&cldev->dev, "Could not read FW version ret = %d\n", bytes_recv); return -EIO; } @@ -208,10 +186,27 @@ static int mei_fwver(struct mei_cl_device *cldev) cldev->bus->fw_ver[i].hotfix = fwver->ver[i].hotfix; cldev->bus->fw_ver[i].buildno = fwver->ver[i].buildno; } + cldev->bus->fw_ver_received = 1; return ret; } +#define GFX_MEMORY_READY_TIMEOUT 200 /* timeout in milliseconds */ + +static int mei_gfx_memory_ready(struct mei_cl_device *cldev) +{ + struct mkhi_gfx_mem_ready req = {0}; + unsigned int mode = MEI_CL_IO_TX_INTERNAL | MEI_CL_IO_TX_BLOCKING; + + req.hdr.group_id = MKHI_GROUP_ID_GFX; + req.hdr.command = MKHI_GFX_MEMORY_READY_CMD_REQ; + req.flags = MKHI_GFX_MEM_READY_PXP_ALLOWED; + + dev_dbg(&cldev->dev, "Sending memory ready command\n"); + return __mei_cl_send_timeout(cldev->cl, (u8 *)&req, sizeof(req), 0, + mode, GFX_MEMORY_READY_TIMEOUT); +} + static void mei_mkhi_fix(struct mei_cl_device *cldev) { int ret; @@ -228,16 +223,70 @@ static void mei_mkhi_fix(struct mei_cl_device *cldev) if (cldev->bus->fw_f_fw_ver_supported) { ret = mei_fwver(cldev); if (ret < 0) - dev_err(&cldev->dev, "FW version command failed %d\n", - ret); + dev_info(&cldev->dev, "FW version command failed %d\n", + ret); } if (cldev->bus->hbm_f_os_supported) { ret = mei_osver(cldev); if (ret < 0) - dev_err(&cldev->dev, "OS version command failed %d\n", - ret); + dev_info(&cldev->dev, "OS version command failed %d\n", + ret); + } + mei_cldev_disable(cldev); +} + +static void mei_gsc_mkhi_ver(struct mei_cl_device *cldev) +{ + int ret; + + /* + * No need to enable the client if nothing is needed from it. + * No need to fill in version if it is already filled in by the fix address client. + */ + if (!cldev->bus->fw_f_fw_ver_supported || cldev->bus->fw_ver_received) + return; + + ret = mei_cldev_enable(cldev); + if (ret) + return; + + ret = mei_fwver(cldev); + if (ret < 0) + dev_info(&cldev->dev, "FW version command failed %d\n", ret); + mei_cldev_disable(cldev); +} + +static void mei_gsc_mkhi_fix_ver(struct mei_cl_device *cldev) +{ + int ret; + + /* No need to enable the client if nothing is needed from it */ + if (!cldev->bus->fw_f_fw_ver_supported && + cldev->bus->pxp_mode != MEI_DEV_PXP_INIT) + return; + + ret = mei_cldev_enable(cldev); + if (ret) + return; + + if (cldev->bus->pxp_mode == MEI_DEV_PXP_INIT) { + ret = mei_gfx_memory_ready(cldev); + if (ret < 0) { + dev_err(&cldev->dev, "memory ready command failed %d\n", ret); + } else { + dev_dbg(&cldev->dev, "memory ready command sent\n"); + cldev->bus->pxp_mode = MEI_DEV_PXP_SETUP; + } + /* we go to reset after that */ + goto out; } + + ret = mei_fwver(cldev); + if (ret < 0) + dev_info(&cldev->dev, "FW version command failed %d\n", + ret); +out: mei_cldev_disable(cldev); } @@ -337,7 +386,7 @@ static int mei_nfc_if_version(struct mei_cl *cl, ret = __mei_cl_send(cl, (u8 *)&cmd, sizeof(cmd), 0, MEI_CL_IO_TX_BLOCKING); if (ret < 0) { - dev_err(bus->dev, "Could not send IF version cmd\n"); + dev_err(&bus->dev, "Could not send IF version cmd ret = %d\n", ret); return ret; } @@ -352,15 +401,15 @@ static int mei_nfc_if_version(struct mei_cl *cl, bytes_recv = __mei_cl_recv(cl, (u8 *)reply, if_version_length, &vtag, 0, 0); if (bytes_recv < 0 || (size_t)bytes_recv < if_version_length) { - dev_err(bus->dev, "Could not read IF version\n"); + dev_err(&bus->dev, "Could not read IF version ret = %d\n", bytes_recv); ret = -EIO; goto err; } memcpy(ver, reply->data, sizeof(*ver)); - dev_info(bus->dev, "NFC MEI VERSION: IVN 0x%x Vendor ID 0x%x Type 0x%x\n", - ver->fw_ivn, ver->vendor_id, ver->radio_type); + dev_info(&bus->dev, "NFC MEI VERSION: IVN 0x%x Vendor ID 0x%x Type 0x%x\n", + ver->fw_ivn, ver->vendor_id, ver->radio_type); err: kfree(reply); @@ -414,14 +463,14 @@ static void mei_nfc(struct mei_cl_device *cldev) if (IS_ERR(cl)) { ret = PTR_ERR(cl); cl = NULL; - dev_err(bus->dev, "nfc hook alloc failed %d\n", ret); + dev_err(&cldev->dev, "nfc hook alloc failed %d\n", ret); goto out; } me_cl = mei_me_cl_by_uuid(bus, &mei_nfc_info_guid); if (!me_cl) { ret = -ENOTTY; - dev_err(bus->dev, "Cannot find nfc info %d\n", ret); + dev_err(&cldev->dev, "Cannot find nfc info %d\n", ret); goto out; } @@ -447,13 +496,13 @@ static void mei_nfc(struct mei_cl_device *cldev) goto disconnect; } - dev_dbg(bus->dev, "nfc radio %s\n", radio_name); - strlcpy(cldev->name, radio_name, sizeof(cldev->name)); + dev_dbg(&cldev->dev, "nfc radio %s\n", radio_name); + strscpy(cldev->name, radio_name, sizeof(cldev->name)); disconnect: mutex_lock(&bus->device_lock); if (mei_cl_disconnect(cl) < 0) - dev_err(bus->dev, "Can't disconnect the NFC INFO ME\n"); + dev_err(&cldev->dev, "Can't disconnect the NFC INFO ME\n"); mei_cl_flush_queues(cl, NULL); @@ -466,7 +515,7 @@ out: if (ret) cldev->do_match = 0; - dev_dbg(bus->dev, "end of fixup match = %d\n", cldev->do_match); + dev_dbg(&cldev->dev, "end of fixup match = %d\n", cldev->do_match); } /** @@ -480,6 +529,26 @@ static void vt_support(struct mei_cl_device *cldev) cldev->do_match = 1; } +/** + * pxp_is_ready - enable bus client if pxp is ready + * + * @cldev: me clients device + */ +static void pxp_is_ready(struct mei_cl_device *cldev) +{ + struct mei_device *bus = cldev->bus; + + switch (bus->pxp_mode) { + case MEI_DEV_PXP_READY: + case MEI_DEV_PXP_DEFAULT: + cldev->do_match = 1; + break; + default: + cldev->do_match = 0; + break; + } +} + #define MEI_FIXUP(_uuid, _hook) { _uuid, _hook } static struct mei_fixup { @@ -492,9 +561,11 @@ static struct mei_fixup { MEI_FIXUP(MEI_UUID_NFC_HCI, mei_nfc), MEI_FIXUP(MEI_UUID_WD, mei_wd), MEI_FIXUP(MEI_UUID_MKHIF_FIX, mei_mkhi_fix), + MEI_FIXUP(MEI_UUID_IGSC_MKHI_FIX, mei_gsc_mkhi_fix_ver), + MEI_FIXUP(MEI_UUID_IGSC_MKHI, mei_gsc_mkhi_ver), MEI_FIXUP(MEI_UUID_HDCP, whitelist), MEI_FIXUP(MEI_UUID_ANY, vt_support), - MEI_FIXUP(MEI_UUID_PAVP, whitelist), + MEI_FIXUP(MEI_UUID_PAVP, pxp_is_ready), }; /** diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c index 935acc6bbf3c..2c810ab12e62 100644 --- a/drivers/misc/mei/bus.c +++ b/drivers/misc/mei/bus.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Copyright (c) 2012-2019, Intel Corporation. All rights reserved. + * Copyright (c) 2012-2023, Intel Corporation. All rights reserved. * Intel Management Engine Interface (Intel MEI) Linux driver */ @@ -13,12 +13,13 @@ #include <linux/slab.h> #include <linux/mutex.h> #include <linux/interrupt.h> +#include <linux/scatterlist.h> #include <linux/mei_cl_bus.h> #include "mei_dev.h" #include "client.h" -#define to_mei_cl_driver(d) container_of(d, struct mei_cl_driver, driver) +#define to_mei_cl_driver(d) container_of_const(d, struct mei_cl_driver, driver) /** * __mei_cl_send - internal client send (write) @@ -31,9 +32,29 @@ * * Return: written size bytes or < 0 on error */ -ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length, u8 vtag, +ssize_t __mei_cl_send(struct mei_cl *cl, const u8 *buf, size_t length, u8 vtag, unsigned int mode) { + return __mei_cl_send_timeout(cl, buf, length, vtag, mode, MAX_SCHEDULE_TIMEOUT); +} + +/** + * __mei_cl_send_timeout - internal client send (write) + * + * @cl: host client + * @buf: buffer to send + * @length: buffer length + * @vtag: virtual tag + * @mode: sending mode + * @timeout: send timeout in milliseconds. + * effective only for blocking writes: the MEI_CL_IO_TX_BLOCKING mode bit is set. + * set timeout to the MAX_SCHEDULE_TIMEOUT to maixum allowed wait. + * + * Return: written size bytes or < 0 on error + */ +ssize_t __mei_cl_send_timeout(struct mei_cl *cl, const u8 *buf, size_t length, u8 vtag, + unsigned int mode, unsigned long timeout) +{ struct mei_device *bus; struct mei_cl_cb *cb; ssize_t rets; @@ -100,8 +121,17 @@ ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length, u8 vtag, cb->internal = !!(mode & MEI_CL_IO_TX_INTERNAL); cb->blocking = !!(mode & MEI_CL_IO_TX_BLOCKING); memcpy(cb->buf.data, buf, length); + /* hack we point data to header */ + if (mode & MEI_CL_IO_SGL) { + cb->ext_hdr = (struct mei_ext_hdr *)cb->buf.data; + cb->buf.data = NULL; + cb->buf.size = 0; + } + + rets = mei_cl_write(cl, cb, timeout); - rets = mei_cl_write(cl, cb); + if (mode & MEI_CL_IO_SGL && rets == 0) + rets = length; out: mutex_unlock(&bus->device_lock); @@ -115,8 +145,8 @@ out: * @cl: host client * @buf: buffer to receive * @length: buffer length - * @mode: io mode * @vtag: virtual tag + * @mode: io mode * @timeout: recv timeout, 0 for infinite timeout * * Return: read size in bytes of < 0 on error @@ -205,9 +235,16 @@ copy: goto free; } - r_length = min_t(size_t, length, cb->buf_idx); - memcpy(buf, cb->buf.data, r_length); + /* for the GSC type - copy the extended header to the buffer */ + if (cb->ext_hdr && cb->ext_hdr->type == MEI_EXT_HDR_GSC) { + r_length = min_t(size_t, length, cb->ext_hdr->length * sizeof(u32)); + memcpy(buf, cb->ext_hdr, r_length); + } else { + r_length = min_t(size_t, length, cb->buf_idx); + memcpy(buf, cb->buf.data, r_length); + } rets = r_length; + if (vtag) *vtag = cb->vtag; @@ -220,7 +257,7 @@ out: } /** - * mei_cldev_send_vtag - me device send with vtag (write) + * mei_cldev_send_vtag - me device send with vtag (write) * * @cldev: me client device * @buf: buffer to send @@ -232,8 +269,8 @@ out: * * < 0 on error */ -ssize_t mei_cldev_send_vtag(struct mei_cl_device *cldev, u8 *buf, size_t length, - u8 vtag) +ssize_t mei_cldev_send_vtag(struct mei_cl_device *cldev, const u8 *buf, + size_t length, u8 vtag) { struct mei_cl *cl = cldev->cl; @@ -242,6 +279,29 @@ ssize_t mei_cldev_send_vtag(struct mei_cl_device *cldev, u8 *buf, size_t length, EXPORT_SYMBOL_GPL(mei_cldev_send_vtag); /** + * mei_cldev_send_vtag_timeout - me device send with vtag and timeout (write) + * + * @cldev: me client device + * @buf: buffer to send + * @length: buffer length + * @vtag: virtual tag + * @timeout: send timeout in milliseconds, 0 for infinite timeout + * + * Return: + * * written size in bytes + * * < 0 on error + */ + +ssize_t mei_cldev_send_vtag_timeout(struct mei_cl_device *cldev, const u8 *buf, + size_t length, u8 vtag, unsigned long timeout) +{ + struct mei_cl *cl = cldev->cl; + + return __mei_cl_send_timeout(cl, buf, length, vtag, MEI_CL_IO_TX_BLOCKING, timeout); +} +EXPORT_SYMBOL_GPL(mei_cldev_send_vtag_timeout); + +/** * mei_cldev_recv_vtag - client receive with vtag (read) * * @cldev: me client device @@ -264,29 +324,49 @@ ssize_t mei_cldev_recv_vtag(struct mei_cl_device *cldev, u8 *buf, size_t length, EXPORT_SYMBOL_GPL(mei_cldev_recv_vtag); /** - * mei_cldev_recv_nonblock_vtag - non block client receive with vtag (read) + * mei_cldev_recv_timeout - client receive with timeout (read) + * + * @cldev: me client device + * @buf: buffer to receive + * @length: buffer length + * @timeout: send timeout in milliseconds, 0 for infinite timeout + * + * Return: + * * read size in bytes + * * < 0 on error + */ +ssize_t mei_cldev_recv_timeout(struct mei_cl_device *cldev, u8 *buf, size_t length, + unsigned long timeout) +{ + return mei_cldev_recv_vtag_timeout(cldev, buf, length, NULL, timeout); +} +EXPORT_SYMBOL_GPL(mei_cldev_recv_timeout); + +/** + * mei_cldev_recv_vtag_timeout - client receive with vtag (read) * * @cldev: me client device * @buf: buffer to receive * @length: buffer length * @vtag: virtual tag + * @timeout: recv timeout in milliseconds, 0 for infinite timeout * * Return: * * read size in bytes - * * -EAGAIN if function will block. - * * < 0 on other error + * * < 0 on error */ -ssize_t mei_cldev_recv_nonblock_vtag(struct mei_cl_device *cldev, u8 *buf, - size_t length, u8 *vtag) + +ssize_t mei_cldev_recv_vtag_timeout(struct mei_cl_device *cldev, u8 *buf, size_t length, + u8 *vtag, unsigned long timeout) { struct mei_cl *cl = cldev->cl; - return __mei_cl_recv(cl, buf, length, vtag, MEI_CL_IO_RX_NONBLOCK, 0); + return __mei_cl_recv(cl, buf, length, vtag, 0, timeout); } -EXPORT_SYMBOL_GPL(mei_cldev_recv_nonblock_vtag); +EXPORT_SYMBOL_GPL(mei_cldev_recv_vtag_timeout); /** - * mei_cldev_send - me device send (write) + * mei_cldev_send - me device send (write) * * @cldev: me client device * @buf: buffer to send @@ -296,43 +376,45 @@ EXPORT_SYMBOL_GPL(mei_cldev_recv_nonblock_vtag); * * written size in bytes * * < 0 on error */ -ssize_t mei_cldev_send(struct mei_cl_device *cldev, u8 *buf, size_t length) +ssize_t mei_cldev_send(struct mei_cl_device *cldev, const u8 *buf, size_t length) { return mei_cldev_send_vtag(cldev, buf, length, 0); } EXPORT_SYMBOL_GPL(mei_cldev_send); /** - * mei_cldev_recv - client receive (read) + * mei_cldev_send_timeout - me device send with timeout (write) * * @cldev: me client device - * @buf: buffer to receive + * @buf: buffer to send * @length: buffer length + * @timeout: send timeout in milliseconds, 0 for infinite timeout * - * Return: read size in bytes of < 0 on error + * Return: + * * written size in bytes + * * < 0 on error */ -ssize_t mei_cldev_recv(struct mei_cl_device *cldev, u8 *buf, size_t length) +ssize_t mei_cldev_send_timeout(struct mei_cl_device *cldev, const u8 *buf, size_t length, + unsigned long timeout) { - return mei_cldev_recv_vtag(cldev, buf, length, NULL); + return mei_cldev_send_vtag_timeout(cldev, buf, length, 0, timeout); } -EXPORT_SYMBOL_GPL(mei_cldev_recv); +EXPORT_SYMBOL_GPL(mei_cldev_send_timeout); /** - * mei_cldev_recv_nonblock - non block client receive (read) + * mei_cldev_recv - client receive (read) * * @cldev: me client device * @buf: buffer to receive * @length: buffer length * * Return: read size in bytes of < 0 on error - * -EAGAIN if function will block. */ -ssize_t mei_cldev_recv_nonblock(struct mei_cl_device *cldev, u8 *buf, - size_t length) +ssize_t mei_cldev_recv(struct mei_cl_device *cldev, u8 *buf, size_t length) { - return mei_cldev_recv_nonblock_vtag(cldev, buf, length, NULL); + return mei_cldev_recv_vtag(cldev, buf, length, NULL); } -EXPORT_SYMBOL_GPL(mei_cldev_recv_nonblock); +EXPORT_SYMBOL_GPL(mei_cldev_recv); /** * mei_cl_bus_rx_work - dispatch rx event for a bus device @@ -520,30 +602,30 @@ void mei_cldev_set_drvdata(struct mei_cl_device *cldev, void *data) EXPORT_SYMBOL_GPL(mei_cldev_set_drvdata); /** - * mei_cldev_uuid - return uuid of the underlying me client + * mei_cldev_ver - return protocol version of the underlying me client * * @cldev: mei client device * - * Return: me client uuid + * Return: me client protocol version */ -const uuid_le *mei_cldev_uuid(const struct mei_cl_device *cldev) +u8 mei_cldev_ver(const struct mei_cl_device *cldev) { - return mei_me_cl_uuid(cldev->me_cl); + return mei_me_cl_ver(cldev->me_cl); } -EXPORT_SYMBOL_GPL(mei_cldev_uuid); +EXPORT_SYMBOL_GPL(mei_cldev_ver); /** - * mei_cldev_ver - return protocol version of the underlying me client + * mei_cldev_mtu - max message that client can send and receive * * @cldev: mei client device * - * Return: me client protocol version + * Return: mtu or 0 if client is not connected */ -u8 mei_cldev_ver(const struct mei_cl_device *cldev) +size_t mei_cldev_mtu(const struct mei_cl_device *cldev) { - return mei_me_cl_ver(cldev->me_cl); + return mei_cl_mtu(cldev->cl); } -EXPORT_SYMBOL_GPL(mei_cldev_ver); +EXPORT_SYMBOL_GPL(mei_cldev_mtu); /** * mei_cldev_enabled - check whether the device is enabled @@ -552,7 +634,7 @@ EXPORT_SYMBOL_GPL(mei_cldev_ver); * * Return: true if me client is initialized and connected */ -bool mei_cldev_enabled(struct mei_cl_device *cldev) +bool mei_cldev_enabled(const struct mei_cl_device *cldev) { return mei_cl_is_connected(cldev->cl); } @@ -568,7 +650,7 @@ EXPORT_SYMBOL_GPL(mei_cldev_enabled); */ static bool mei_cl_bus_module_get(struct mei_cl_device *cldev) { - return try_module_get(cldev->bus->dev->driver->owner); + return try_module_get(cldev->bus->parent->driver->owner); } /** @@ -578,7 +660,7 @@ static bool mei_cl_bus_module_get(struct mei_cl_device *cldev) */ static void mei_cl_bus_module_put(struct mei_cl_device *cldev) { - module_put(cldev->bus->dev->driver->owner); + module_put(cldev->bus->parent->driver->owner); } /** @@ -643,6 +725,66 @@ static void mei_cl_bus_vtag_free(struct mei_cl_device *cldev) kfree(cl_vtag); } +void *mei_cldev_dma_map(struct mei_cl_device *cldev, u8 buffer_id, size_t size) +{ + struct mei_device *bus; + struct mei_cl *cl; + int ret; + + if (!cldev || !buffer_id || !size) + return ERR_PTR(-EINVAL); + + if (!IS_ALIGNED(size, MEI_FW_PAGE_SIZE)) { + dev_err(&cldev->dev, "Map size should be aligned to %lu\n", + MEI_FW_PAGE_SIZE); + return ERR_PTR(-EINVAL); + } + + cl = cldev->cl; + bus = cldev->bus; + + mutex_lock(&bus->device_lock); + if (cl->state == MEI_FILE_UNINITIALIZED) { + ret = mei_cl_link(cl); + if (ret) + goto notlinked; + /* update pointers */ + cl->cldev = cldev; + } + + ret = mei_cl_dma_alloc_and_map(cl, NULL, buffer_id, size); + if (ret) + mei_cl_unlink(cl); +notlinked: + mutex_unlock(&bus->device_lock); + if (ret) + return ERR_PTR(ret); + return cl->dma.vaddr; +} +EXPORT_SYMBOL_GPL(mei_cldev_dma_map); + +int mei_cldev_dma_unmap(struct mei_cl_device *cldev) +{ + struct mei_device *bus; + struct mei_cl *cl; + int ret; + + if (!cldev) + return -EINVAL; + + cl = cldev->cl; + bus = cldev->bus; + + mutex_lock(&bus->device_lock); + ret = mei_cl_dma_unmap(cl, NULL); + + mei_cl_flush_queues(cl, NULL); + mei_cl_unlink(cl); + mutex_unlock(&bus->device_lock); + return ret; +} +EXPORT_SYMBOL_GPL(mei_cldev_dma_unmap); + /** * mei_cldev_enable - enable me client device * create connection with me client @@ -663,7 +805,7 @@ int mei_cldev_enable(struct mei_cl_device *cldev) if (cl->state == MEI_FILE_UNINITIALIZED) { ret = mei_cl_link(cl); if (ret) - goto out; + goto notlinked; /* update pointers */ cl->cldev = cldev; } @@ -685,11 +827,14 @@ int mei_cldev_enable(struct mei_cl_device *cldev) ret = mei_cl_connect(cl, cldev->me_cl, NULL); if (ret < 0) { - dev_err(&cldev->dev, "cannot connect\n"); + dev_dbg(&cldev->dev, "cannot connect\n"); mei_cl_bus_vtag_free(cldev); } out: + if (ret) + mei_cl_unlink(cl); +notlinked: mutex_unlock(&bus->device_lock); return ret; @@ -743,19 +888,21 @@ int mei_cldev_disable(struct mei_cl_device *cldev) mei_cl_bus_vtag_free(cldev); if (!mei_cl_is_connected(cl)) { - dev_dbg(bus->dev, "Already disconnected\n"); + dev_dbg(&cldev->dev, "Already disconnected\n"); err = 0; goto out; } err = mei_cl_disconnect(cl); if (err < 0) - dev_err(bus->dev, "Could not disconnect from the ME client\n"); + dev_err(&cldev->dev, "Could not disconnect from the ME client\n"); out: - /* Flush queues and remove any pending read */ - mei_cl_flush_queues(cl, NULL); - mei_cl_unlink(cl); + /* Flush queues and remove any pending read unless we have mapped DMA */ + if (!cl->dma_mapped) { + mei_cl_flush_queues(cl, NULL); + mei_cl_unlink(cl); + } mutex_unlock(&bus->device_lock); return err; @@ -763,6 +910,131 @@ out: EXPORT_SYMBOL_GPL(mei_cldev_disable); /** + * mei_cldev_send_gsc_command - sends a gsc command, by sending + * a gsl mei message to gsc and receiving reply from gsc + * + * @cldev: me client device + * @client_id: client id to send the command to + * @fence_id: fence id to send the command to + * @sg_in: scatter gather list containing addresses for rx message buffer + * @total_in_len: total length of data in 'in' sg, can be less than the sum of buffers sizes + * @sg_out: scatter gather list containing addresses for tx message buffer + * + * Return: + * * written size in bytes + * * < 0 on error + */ +ssize_t mei_cldev_send_gsc_command(struct mei_cl_device *cldev, + u8 client_id, u32 fence_id, + struct scatterlist *sg_in, + size_t total_in_len, + struct scatterlist *sg_out) +{ + struct mei_cl *cl; + struct mei_device *bus; + ssize_t ret = 0; + + struct mei_ext_hdr_gsc_h2f *ext_hdr; + size_t buf_sz = sizeof(struct mei_ext_hdr_gsc_h2f); + int sg_out_nents, sg_in_nents; + int i; + struct scatterlist *sg; + struct mei_ext_hdr_gsc_f2h rx_msg; + unsigned int sg_len; + + if (!cldev || !sg_in || !sg_out) + return -EINVAL; + + cl = cldev->cl; + bus = cldev->bus; + + dev_dbg(&cldev->dev, "client_id %u, fence_id %u\n", client_id, fence_id); + + if (!bus->hbm_f_gsc_supported) + return -EOPNOTSUPP; + + sg_out_nents = sg_nents(sg_out); + sg_in_nents = sg_nents(sg_in); + /* at least one entry in tx and rx sgls must be present */ + if (sg_out_nents <= 0 || sg_in_nents <= 0) + return -EINVAL; + + buf_sz += (sg_out_nents + sg_in_nents) * sizeof(struct mei_gsc_sgl); + ext_hdr = kzalloc(buf_sz, GFP_KERNEL); + if (!ext_hdr) + return -ENOMEM; + + /* construct the GSC message */ + ext_hdr->hdr.type = MEI_EXT_HDR_GSC; + ext_hdr->hdr.length = buf_sz / sizeof(u32); /* length is in dw */ + + ext_hdr->client_id = client_id; + ext_hdr->addr_type = GSC_ADDRESS_TYPE_PHYSICAL_SGL; + ext_hdr->fence_id = fence_id; + ext_hdr->input_address_count = sg_in_nents; + ext_hdr->output_address_count = sg_out_nents; + ext_hdr->reserved[0] = 0; + ext_hdr->reserved[1] = 0; + + /* copy in-sgl to the message */ + for (i = 0, sg = sg_in; i < sg_in_nents; i++, sg++) { + ext_hdr->sgl[i].low = lower_32_bits(sg_dma_address(sg)); + ext_hdr->sgl[i].high = upper_32_bits(sg_dma_address(sg)); + sg_len = min_t(unsigned int, sg_dma_len(sg), PAGE_SIZE); + ext_hdr->sgl[i].length = (sg_len <= total_in_len) ? sg_len : total_in_len; + total_in_len -= ext_hdr->sgl[i].length; + } + + /* copy out-sgl to the message */ + for (i = sg_in_nents, sg = sg_out; i < sg_in_nents + sg_out_nents; i++, sg++) { + ext_hdr->sgl[i].low = lower_32_bits(sg_dma_address(sg)); + ext_hdr->sgl[i].high = upper_32_bits(sg_dma_address(sg)); + sg_len = min_t(unsigned int, sg_dma_len(sg), PAGE_SIZE); + ext_hdr->sgl[i].length = sg_len; + } + + /* send the message to GSC */ + ret = __mei_cl_send(cl, (u8 *)ext_hdr, buf_sz, 0, MEI_CL_IO_SGL); + if (ret < 0) { + dev_err(&cldev->dev, "__mei_cl_send failed, returned %zd\n", ret); + goto end; + } + if (ret != buf_sz) { + dev_err(&cldev->dev, "__mei_cl_send returned %zd instead of expected %zd\n", + ret, buf_sz); + ret = -EIO; + goto end; + } + + /* receive the reply from GSC, note that at this point sg_in should contain the reply */ + ret = __mei_cl_recv(cl, (u8 *)&rx_msg, sizeof(rx_msg), NULL, MEI_CL_IO_SGL, 0); + + if (ret != sizeof(rx_msg)) { + dev_err(&cldev->dev, "__mei_cl_recv returned %zd instead of expected %zd\n", + ret, sizeof(rx_msg)); + if (ret >= 0) + ret = -EIO; + goto end; + } + + /* check rx_msg.client_id and rx_msg.fence_id match the ones we send */ + if (rx_msg.client_id != client_id || rx_msg.fence_id != fence_id) { + dev_err(&cldev->dev, "received client_id/fence_id %u/%u instead of %u/%u sent\n", + rx_msg.client_id, rx_msg.fence_id, client_id, fence_id); + ret = -EFAULT; + goto end; + } + + dev_dbg(&cldev->dev, "gsc command: successfully written %u bytes\n", rx_msg.written); + ret = rx_msg.written; + +end: + kfree(ext_hdr); + return ret; +} +EXPORT_SYMBOL_GPL(mei_cldev_send_gsc_command); + +/** * mei_cl_device_find - find matching entry in the driver id table * * @cldev: me client device @@ -771,8 +1043,8 @@ EXPORT_SYMBOL_GPL(mei_cldev_disable); * Return: id on success; NULL if no id is matching */ static const -struct mei_cl_device_id *mei_cl_device_find(struct mei_cl_device *cldev, - struct mei_cl_driver *cldrv) +struct mei_cl_device_id *mei_cl_device_find(const struct mei_cl_device *cldev, + const struct mei_cl_driver *cldrv) { const struct mei_cl_device_id *id; const uuid_le *uuid; @@ -813,15 +1085,12 @@ struct mei_cl_device_id *mei_cl_device_find(struct mei_cl_device *cldev, * * Return: 1 if matching device was found 0 otherwise */ -static int mei_cl_device_match(struct device *dev, struct device_driver *drv) +static int mei_cl_device_match(struct device *dev, const struct device_driver *drv) { - struct mei_cl_device *cldev = to_mei_cl_device(dev); - struct mei_cl_driver *cldrv = to_mei_cl_driver(drv); + const struct mei_cl_device *cldev = to_mei_cl_device(dev); + const struct mei_cl_driver *cldrv = to_mei_cl_driver(drv); const struct mei_cl_device_id *found_id; - if (!cldev) - return 0; - if (!cldev->do_match) return 0; @@ -852,9 +1121,6 @@ static int mei_cl_device_probe(struct device *dev) cldev = to_mei_cl_device(dev); cldrv = to_mei_cl_driver(dev->driver); - if (!cldev) - return 0; - if (!cldrv || !cldrv->probe) return -ENODEV; @@ -884,7 +1150,7 @@ static int mei_cl_device_probe(struct device *dev) * * Return: 0 on success; < 0 otherwise */ -static int mei_cl_device_remove(struct device *dev) +static void mei_cl_device_remove(struct device *dev) { struct mei_cl_device *cldev = to_mei_cl_device(dev); struct mei_cl_driver *cldrv = to_mei_cl_driver(dev->driver); @@ -896,8 +1162,6 @@ static int mei_cl_device_remove(struct device *dev) mei_cl_bus_module_put(cldev); module_put(THIS_MODULE); - - return 0; } static ssize_t name_show(struct device *dev, struct device_attribute *a, @@ -905,7 +1169,7 @@ static ssize_t name_show(struct device *dev, struct device_attribute *a, { struct mei_cl_device *cldev = to_mei_cl_device(dev); - return scnprintf(buf, PAGE_SIZE, "%s", cldev->name); + return sysfs_emit(buf, "%s", cldev->name); } static DEVICE_ATTR_RO(name); @@ -915,7 +1179,7 @@ static ssize_t uuid_show(struct device *dev, struct device_attribute *a, struct mei_cl_device *cldev = to_mei_cl_device(dev); const uuid_le *uuid = mei_me_cl_uuid(cldev->me_cl); - return sprintf(buf, "%pUl", uuid); + return sysfs_emit(buf, "%pUl", uuid); } static DEVICE_ATTR_RO(uuid); @@ -925,7 +1189,7 @@ static ssize_t version_show(struct device *dev, struct device_attribute *a, struct mei_cl_device *cldev = to_mei_cl_device(dev); u8 version = mei_me_cl_ver(cldev->me_cl); - return sprintf(buf, "%02X", version); + return sysfs_emit(buf, "%02X", version); } static DEVICE_ATTR_RO(version); @@ -936,8 +1200,7 @@ static ssize_t modalias_show(struct device *dev, struct device_attribute *a, const uuid_le *uuid = mei_me_cl_uuid(cldev->me_cl); u8 version = mei_me_cl_ver(cldev->me_cl); - return scnprintf(buf, PAGE_SIZE, "mei:%s:%pUl:%02X:", - cldev->name, uuid, version); + return sysfs_emit(buf, "mei:%s:%pUl:%02X:", cldev->name, uuid, version); } static DEVICE_ATTR_RO(modalias); @@ -947,7 +1210,7 @@ static ssize_t max_conn_show(struct device *dev, struct device_attribute *a, struct mei_cl_device *cldev = to_mei_cl_device(dev); u8 maxconn = mei_me_cl_max_conn(cldev->me_cl); - return sprintf(buf, "%d", maxconn); + return sysfs_emit(buf, "%d", maxconn); } static DEVICE_ATTR_RO(max_conn); @@ -957,7 +1220,7 @@ static ssize_t fixed_show(struct device *dev, struct device_attribute *a, struct mei_cl_device *cldev = to_mei_cl_device(dev); u8 fixed = mei_me_cl_fixed(cldev->me_cl); - return sprintf(buf, "%d", fixed); + return sysfs_emit(buf, "%d", fixed); } static DEVICE_ATTR_RO(fixed); @@ -967,7 +1230,7 @@ static ssize_t vtag_show(struct device *dev, struct device_attribute *a, struct mei_cl_device *cldev = to_mei_cl_device(dev); bool vt = mei_me_cl_vt(cldev->me_cl); - return sprintf(buf, "%d", vt); + return sysfs_emit(buf, "%d", vt); } static DEVICE_ATTR_RO(vtag); @@ -977,7 +1240,7 @@ static ssize_t max_len_show(struct device *dev, struct device_attribute *a, struct mei_cl_device *cldev = to_mei_cl_device(dev); u32 maxlen = mei_me_cl_max_len(cldev->me_cl); - return sprintf(buf, "%u", maxlen); + return sysfs_emit(buf, "%u", maxlen); } static DEVICE_ATTR_RO(max_len); @@ -1002,9 +1265,9 @@ ATTRIBUTE_GROUPS(mei_cldev); * * Return: 0 on success -ENOMEM on when add_uevent_var fails */ -static int mei_cl_device_uevent(struct device *dev, struct kobj_uevent_env *env) +static int mei_cl_device_uevent(const struct device *dev, struct kobj_uevent_env *env) { - struct mei_cl_device *cldev = to_mei_cl_device(dev); + const struct mei_cl_device *cldev = to_mei_cl_device(dev); const uuid_le *uuid = mei_me_cl_uuid(cldev->me_cl); u8 version = mei_me_cl_ver(cldev->me_cl); @@ -1024,7 +1287,7 @@ static int mei_cl_device_uevent(struct device *dev, struct kobj_uevent_env *env) return 0; } -static struct bus_type mei_cl_bus_type = { +static const struct bus_type mei_cl_bus_type = { .name = "mei", .dev_groups = mei_cldev_groups, .match = mei_cl_device_match, @@ -1035,28 +1298,35 @@ static struct bus_type mei_cl_bus_type = { static struct mei_device *mei_dev_bus_get(struct mei_device *bus) { - if (bus) - get_device(bus->dev); + if (bus) { + get_device(&bus->dev); + get_device(bus->parent); + } return bus; } static void mei_dev_bus_put(struct mei_device *bus) { - if (bus) - put_device(bus->dev); + if (bus) { + put_device(bus->parent); + put_device(&bus->dev); + } } static void mei_cl_bus_dev_release(struct device *dev) { struct mei_cl_device *cldev = to_mei_cl_device(dev); + struct mei_device *mdev = cldev->cl->dev; + struct mei_cl *cl; - if (!cldev) - return; - + mei_cl_flush_queues(cldev->cl, NULL); mei_me_cl_put(cldev->me_cl); mei_dev_bus_put(cldev->bus); - mei_cl_unlink(cldev->cl); + + list_for_each_entry(cl, &mdev->file_list, link) + WARN_ON(cl == cldev->cl); + kfree(cldev->cl); kfree(cldev); } @@ -1075,7 +1345,7 @@ static const struct device_type mei_cl_device_type = { static inline void mei_cl_bus_set_name(struct mei_cl_device *cldev) { dev_set_name(&cldev->dev, "%s-%pUl", - dev_name(cldev->bus->dev), + dev_name(cldev->bus->parent), mei_me_cl_uuid(cldev->me_cl)); } @@ -1085,7 +1355,7 @@ static inline void mei_cl_bus_set_name(struct mei_cl_device *cldev) * @bus: mei device * @me_cl: me client * - * Return: allocated device structur or NULL on allocation failure + * Return: allocated device structure or NULL on allocation failure */ static struct mei_cl_device *mei_cl_bus_dev_alloc(struct mei_device *bus, struct mei_me_client *me_cl) @@ -1104,7 +1374,7 @@ static struct mei_cl_device *mei_cl_bus_dev_alloc(struct mei_device *bus, } device_initialize(&cldev->dev); - cldev->dev.parent = bus->dev; + cldev->dev.parent = bus->parent; cldev->dev.bus = &mei_cl_bus_type; cldev->dev.type = &mei_cl_device_type; cldev->bus = mei_dev_bus_get(bus); @@ -1113,6 +1383,7 @@ static struct mei_cl_device *mei_cl_bus_dev_alloc(struct mei_device *bus, mei_cl_bus_set_name(cldev); cldev->is_added = 0; INIT_LIST_HEAD(&cldev->bus_list); + device_enable_async_suspend(&cldev->dev); return cldev; } @@ -1144,13 +1415,13 @@ static bool mei_cl_bus_dev_setup(struct mei_device *bus, * * @cldev: me client device * - * Return: 0 on success; < 0 on failre + * Return: 0 on success; < 0 on failure */ static int mei_cl_bus_dev_add(struct mei_cl_device *cldev) { int ret; - dev_dbg(cldev->bus->dev, "adding %pUL:%02X\n", + dev_dbg(&cldev->dev, "adding %pUL:%02X\n", mei_me_cl_uuid(cldev->me_cl), mei_me_cl_ver(cldev->me_cl)); ret = device_add(&cldev->dev); @@ -1167,6 +1438,7 @@ static int mei_cl_bus_dev_add(struct mei_cl_device *cldev) */ static void mei_cl_bus_dev_stop(struct mei_cl_device *cldev) { + cldev->do_match = 0; if (cldev->is_added) device_release_driver(&cldev->dev); } @@ -1237,7 +1509,7 @@ static void mei_cl_bus_dev_init(struct mei_device *bus, WARN_ON(!mutex_is_locked(&bus->cl_bus_lock)); - dev_dbg(bus->dev, "initializing %pUl", mei_me_cl_uuid(me_cl)); + dev_dbg(&bus->dev, "initializing %pUl", mei_me_cl_uuid(me_cl)); if (me_cl->bus_added) return; @@ -1288,7 +1560,7 @@ static void mei_cl_bus_rescan(struct mei_device *bus) } mutex_unlock(&bus->cl_bus_lock); - dev_dbg(bus->dev, "rescan end"); + dev_dbg(&bus->dev, "rescan end"); } void mei_cl_bus_rescan_work(struct work_struct *work) diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index 96f4e59c32a5..5dc665515263 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Copyright (c) 2003-2020, Intel Corporation. All rights reserved. + * Copyright (c) 2003-2022, Intel Corporation. All rights reserved. * Intel Management Engine Interface (Intel MEI) Linux driver */ @@ -48,9 +48,9 @@ struct mei_me_client *mei_me_cl_get(struct mei_me_client *me_cl) /** * mei_me_cl_release - free me client * - * Locking: called under "dev->device_lock" lock - * * @ref: me_client refcount + * + * Locking: called under "dev->device_lock" lock */ static void mei_me_cl_release(struct kref *ref) { @@ -63,9 +63,9 @@ static void mei_me_cl_release(struct kref *ref) /** * mei_me_cl_put - decrease me client refcount and free client if necessary * - * Locking: called under "dev->device_lock" lock - * * @me_cl: me client + * + * Locking: called under "dev->device_lock" lock */ void mei_me_cl_put(struct mei_me_client *me_cl) { @@ -262,7 +262,7 @@ void mei_me_cl_rm_by_uuid(struct mei_device *dev, const uuid_le *uuid) { struct mei_me_client *me_cl; - dev_dbg(dev->dev, "remove %pUl\n", uuid); + dev_dbg(&dev->dev, "remove %pUl\n", uuid); down_write(&dev->me_clients_rwsem); me_cl = __mei_me_cl_by_uuid(dev, uuid); @@ -272,28 +272,6 @@ void mei_me_cl_rm_by_uuid(struct mei_device *dev, const uuid_le *uuid) } /** - * mei_me_cl_rm_by_uuid_id - remove all me clients matching client id - * - * @dev: the device structure - * @uuid: me client uuid - * @id: me client id - * - * Locking: called under "dev->device_lock" lock - */ -void mei_me_cl_rm_by_uuid_id(struct mei_device *dev, const uuid_le *uuid, u8 id) -{ - struct mei_me_client *me_cl; - - dev_dbg(dev->dev, "remove %pUl %d\n", uuid, id); - - down_write(&dev->me_clients_rwsem); - me_cl = __mei_me_cl_by_uuid_id(dev, uuid, id); - __mei_me_cl_del(dev, me_cl); - mei_me_cl_put(me_cl); - up_write(&dev->me_clients_rwsem); -} - -/** * mei_me_cl_rm_all - remove all me clients * * @dev: the device structure @@ -321,17 +299,18 @@ void mei_io_cb_free(struct mei_cl_cb *cb) return; list_del(&cb->list); - kfree(cb->buf.data); + kvfree(cb->buf.data); + kfree(cb->ext_hdr); kfree(cb); } /** * mei_tx_cb_enqueue - queue tx callback * - * Locking: called under "dev->device_lock" lock - * * @cb: mei callback struct * @head: an instance of list to queue on + * + * Locking: called under "dev->device_lock" lock */ static inline void mei_tx_cb_enqueue(struct mei_cl_cb *cb, struct list_head *head) @@ -343,9 +322,9 @@ static inline void mei_tx_cb_enqueue(struct mei_cl_cb *cb, /** * mei_tx_cb_dequeue - dequeue tx callback * - * Locking: called under "dev->device_lock" lock - * * @cb: mei callback struct to dequeue and free + * + * Locking: called under "dev->device_lock" lock */ static inline void mei_tx_cb_dequeue(struct mei_cl_cb *cb) { @@ -358,10 +337,10 @@ static inline void mei_tx_cb_dequeue(struct mei_cl_cb *cb) /** * mei_cl_set_read_by_fp - set pending_read flag to vtag struct for given fp * - * Locking: called under "dev->device_lock" lock - * * @cl: mei client * @fp: pointer to file structure + * + * Locking: called under "dev->device_lock" lock */ static void mei_cl_set_read_by_fp(const struct mei_cl *cl, const struct file *fp) @@ -401,6 +380,7 @@ static struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, cb->buf_idx = 0; cb->fop_type = type; cb->vtag = 0; + cb->ext_hdr = NULL; return cb; } @@ -495,7 +475,7 @@ struct mei_cl_cb *mei_cl_alloc_cb(struct mei_cl *cl, size_t length, if (length == 0) return cb; - cb->buf.data = kmalloc(roundup(length, MEI_SLOT_SIZE), GFP_KERNEL); + cb->buf.data = kvmalloc(roundup(length, MEI_SLOT_SIZE), GFP_KERNEL); if (!cb->buf.data) { mei_io_cb_free(cb); return NULL; @@ -655,12 +635,12 @@ int mei_cl_link(struct mei_cl *cl) id = find_first_zero_bit(dev->host_clients_map, MEI_CLIENTS_MAX); if (id >= MEI_CLIENTS_MAX) { - dev_err(dev->dev, "id exceeded %d", MEI_CLIENTS_MAX); + dev_err(&dev->dev, "id exceeded %d", MEI_CLIENTS_MAX); return -EMFILE; } if (dev->open_handle_count >= MEI_MAX_OPEN_HANDLE_COUNT) { - dev_err(dev->dev, "open_handle_count exceeded %d", + dev_err(&dev->dev, "open_handle_count exceeded %d", MEI_MAX_OPEN_HANDLE_COUNT); return -EMFILE; } @@ -700,6 +680,9 @@ int mei_cl_unlink(struct mei_cl *cl) cl_dbg(dev, cl, "unlink client"); + if (cl->state == MEI_FILE_UNINITIALIZED) + return 0; + if (dev->open_handle_count > 0) dev->open_handle_count--; @@ -726,9 +709,8 @@ void mei_host_client_init(struct mei_device *dev) schedule_work(&dev->bus_rescan_work); - pm_runtime_mark_last_busy(dev->dev); - dev_dbg(dev->dev, "rpm: autosuspend\n"); - pm_request_autosuspend(dev->dev); + dev_dbg(&dev->dev, "rpm: autosuspend\n"); + pm_request_autosuspend(dev->parent); } /** @@ -741,12 +723,12 @@ bool mei_hbuf_acquire(struct mei_device *dev) { if (mei_pg_state(dev) == MEI_PG_ON || mei_pg_in_transition(dev)) { - dev_dbg(dev->dev, "device is in pg\n"); + dev_dbg(&dev->dev, "device is in pg\n"); return false; } if (!dev->hbuf_is_ready) { - dev_dbg(dev->dev, "hbuf is not ready\n"); + dev_dbg(&dev->dev, "hbuf is not ready\n"); return false; } @@ -867,7 +849,7 @@ static int mei_cl_send_disconnect(struct mei_cl *cl, struct mei_cl_cb *cb) } list_move_tail(&cb->list, &dev->ctrl_rd_list); - cl->timer_count = MEI_CONNECT_TIMEOUT; + cl->timer_count = dev->timeouts.connect; mei_schedule_stall_timer(dev); return 0; @@ -942,7 +924,7 @@ static int __mei_cl_disconnect(struct mei_cl *cl) wait_event_timeout(cl->wait, cl->state == MEI_FILE_DISCONNECT_REPLY || cl->state == MEI_FILE_DISCONNECTED, - mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT)); + dev->timeouts.cl_connect); mutex_lock(&dev->device_lock); rets = cl->status; @@ -998,9 +980,9 @@ int mei_cl_disconnect(struct mei_cl *cl) return 0; } - rets = pm_runtime_get(dev->dev); + rets = pm_runtime_get(dev->parent); if (rets < 0 && rets != -EINPROGRESS) { - pm_runtime_put_noidle(dev->dev); + pm_runtime_put_noidle(dev->parent); cl_err(dev, cl, "rpm: get failed %d\n", rets); return rets; } @@ -1008,8 +990,7 @@ int mei_cl_disconnect(struct mei_cl *cl) rets = __mei_cl_disconnect(cl); cl_dbg(dev, cl, "rpm: autosuspend\n"); - pm_runtime_mark_last_busy(dev->dev); - pm_runtime_put_autosuspend(dev->dev); + pm_runtime_put_autosuspend(dev->parent); return rets; } @@ -1062,7 +1043,7 @@ static int mei_cl_send_connect(struct mei_cl *cl, struct mei_cl_cb *cb) } list_move_tail(&cb->list, &dev->ctrl_rd_list); - cl->timer_count = MEI_CONNECT_TIMEOUT; + cl->timer_count = dev->timeouts.connect; mei_schedule_stall_timer(dev); return 0; } @@ -1135,9 +1116,9 @@ int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl, goto nortpm; } - rets = pm_runtime_get(dev->dev); + rets = pm_runtime_get(dev->parent); if (rets < 0 && rets != -EINPROGRESS) { - pm_runtime_put_noidle(dev->dev); + pm_runtime_put_noidle(dev->parent); cl_err(dev, cl, "rpm: get failed %d\n", rets); goto nortpm; } @@ -1161,7 +1142,7 @@ int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl, cl->state == MEI_FILE_DISCONNECTED || cl->state == MEI_FILE_DISCONNECT_REQUIRED || cl->state == MEI_FILE_DISCONNECT_REPLY), - mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT)); + dev->timeouts.cl_connect); mutex_lock(&dev->device_lock); if (!mei_cl_is_connected(cl)) { @@ -1184,8 +1165,7 @@ int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl, rets = cl->status; out: cl_dbg(dev, cl, "rpm: autosuspend\n"); - pm_runtime_mark_last_busy(dev->dev); - pm_runtime_put_autosuspend(dev->dev); + pm_runtime_put_autosuspend(dev->parent); mei_io_cb_free(cb); @@ -1338,7 +1318,9 @@ static void mei_cl_reset_read_by_vtag(const struct mei_cl *cl, u8 vtag) struct mei_cl_vtag *vtag_l; list_for_each_entry(vtag_l, &cl->vtag_map, list) { - if (vtag_l->vtag == vtag) { + /* The client on bus has one fixed vtag map */ + if ((cl->cldev && mei_cldev_enabled(cl->cldev)) || + vtag_l->vtag == vtag) { vtag_l->pending_read = false; break; } @@ -1532,9 +1514,9 @@ int mei_cl_notify_request(struct mei_cl *cl, if (!mei_cl_is_connected(cl)) return -ENODEV; - rets = pm_runtime_get(dev->dev); + rets = pm_runtime_get(dev->parent); if (rets < 0 && rets != -EINPROGRESS) { - pm_runtime_put_noidle(dev->dev); + pm_runtime_put_noidle(dev->parent); cl_err(dev, cl, "rpm: get failed %d\n", rets); return rets; } @@ -1559,7 +1541,7 @@ int mei_cl_notify_request(struct mei_cl *cl, cl->notify_en == request || cl->status || !mei_cl_is_connected(cl), - mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT)); + dev->timeouts.cl_connect); mutex_lock(&dev->device_lock); if (cl->notify_en != request && !cl->status) @@ -1569,8 +1551,7 @@ int mei_cl_notify_request(struct mei_cl *cl, out: cl_dbg(dev, cl, "rpm: autosuspend\n"); - pm_runtime_mark_last_busy(dev->dev); - pm_runtime_put_autosuspend(dev->dev); + pm_runtime_put_autosuspend(dev->parent); mei_io_cb_free(cb); return rets; @@ -1698,9 +1679,9 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length, const struct file *fp) mei_cl_set_read_by_fp(cl, fp); - rets = pm_runtime_get(dev->dev); + rets = pm_runtime_get(dev->parent); if (rets < 0 && rets != -EINPROGRESS) { - pm_runtime_put_noidle(dev->dev); + pm_runtime_put_noidle(dev->parent); cl_err(dev, cl, "rpm: get failed %d\n", rets); goto nortpm; } @@ -1717,8 +1698,7 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length, const struct file *fp) out: cl_dbg(dev, cl, "rpm: autosuspend\n"); - pm_runtime_mark_last_busy(dev->dev); - pm_runtime_put_autosuspend(dev->dev); + pm_runtime_put_autosuspend(dev->parent); nortpm: if (rets) mei_io_cb_free(cb); @@ -1737,6 +1717,17 @@ static inline u8 mei_ext_hdr_set_vtag(void *ext, u8 vtag) return vtag_hdr->hdr.length; } +static inline bool mei_ext_hdr_is_gsc(struct mei_ext_hdr *ext) +{ + return ext && ext->type == MEI_EXT_HDR_GSC; +} + +static inline u8 mei_ext_hdr_set_gsc(struct mei_ext_hdr *ext, struct mei_ext_hdr *gsc_hdr) +{ + memcpy(ext, gsc_hdr, mei_ext_hdr_len(gsc_hdr)); + return ext->length; +} + /** * mei_msg_hdr_init - allocate and initialize mei message header * @@ -1749,14 +1740,17 @@ static struct mei_msg_hdr *mei_msg_hdr_init(const struct mei_cl_cb *cb) size_t hdr_len; struct mei_ext_meta_hdr *meta; struct mei_msg_hdr *mei_hdr; - bool is_ext, is_vtag; + bool is_ext, is_hbm, is_gsc, is_vtag; + struct mei_ext_hdr *next_ext; if (!cb) return ERR_PTR(-EINVAL); /* Extended header for vtag is attached only on the first fragment */ is_vtag = (cb->vtag && cb->buf_idx == 0); - is_ext = is_vtag; + is_hbm = cb->cl->me_cl->client_id == 0; + is_gsc = ((!is_hbm) && cb->cl->dev->hbm_f_gsc_supported && mei_ext_hdr_is_gsc(cb->ext_hdr)); + is_ext = is_vtag || is_gsc; /* Compute extended header size */ hdr_len = sizeof(*mei_hdr); @@ -1768,6 +1762,9 @@ static struct mei_msg_hdr *mei_msg_hdr_init(const struct mei_cl_cb *cb) if (is_vtag) hdr_len += sizeof(struct mei_ext_hdr_vtag); + if (is_gsc) + hdr_len += mei_ext_hdr_len(cb->ext_hdr); + setup_hdr: mei_hdr = kzalloc(hdr_len, GFP_KERNEL); if (!mei_hdr) @@ -1782,10 +1779,20 @@ setup_hdr: goto out; meta = (struct mei_ext_meta_hdr *)mei_hdr->extension; + meta->size = 0; + next_ext = (struct mei_ext_hdr *)meta->hdrs; if (is_vtag) { meta->count++; - meta->size += mei_ext_hdr_set_vtag(meta->hdrs, cb->vtag); + meta->size += mei_ext_hdr_set_vtag(next_ext, cb->vtag); + next_ext = mei_ext_next(next_ext); + } + + if (is_gsc) { + meta->count++; + meta->size += mei_ext_hdr_set_gsc(next_ext, cb->ext_hdr); + next_ext = mei_ext_next(next_ext); } + out: mei_hdr->length = hdr_len - sizeof(*mei_hdr); return mei_hdr; @@ -1809,14 +1816,14 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, struct mei_msg_hdr *mei_hdr = NULL; size_t hdr_len; size_t hbuf_len, dr_len; - size_t buf_len; + size_t buf_len = 0; size_t data_len; int hbuf_slots; u32 dr_slots; u32 dma_len; int rets; bool first_chunk; - const void *data; + const void *data = NULL; if (WARN_ON(!cl || !cl->dev)) return -ENODEV; @@ -1836,8 +1843,10 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, return 0; } - buf_len = buf->size - cb->buf_idx; - data = buf->data + cb->buf_idx; + if (buf->data) { + buf_len = buf->size - cb->buf_idx; + data = buf->data + cb->buf_idx; + } hbuf_slots = mei_hbuf_empty_slots(dev); if (hbuf_slots < 0) { rets = -EOVERFLOW; @@ -1855,9 +1864,6 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, goto err; } - cl_dbg(dev, cl, "Extended Header %d vtag = %d\n", - mei_hdr->extended, cb->vtag); - hdr_len = sizeof(*mei_hdr) + mei_hdr->length; /** @@ -1886,7 +1892,7 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, } mei_hdr->length += data_len; - if (mei_hdr->dma_ring) + if (mei_hdr->dma_ring && buf->data) mei_dma_ring_write(dev, buf->data + cb->buf_idx, buf_len); rets = mei_write_message(dev, mei_hdr, hdr_len, data, data_len); @@ -1923,10 +1929,13 @@ err: * * @cl: host client * @cb: write callback with filled data + * @timeout: send timeout in milliseconds. + * effective only for blocking writes: the cb->blocking is set. + * set timeout to the MAX_SCHEDULE_TIMEOUT to maixum allowed wait. * * Return: number of bytes sent on success, <0 on failure. */ -ssize_t mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb) +ssize_t mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, unsigned long timeout) { struct mei_device *dev; struct mei_msg_data *buf; @@ -1958,9 +1967,9 @@ ssize_t mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb) blocking = cb->blocking; data = buf->data; - rets = pm_runtime_get(dev->dev); + rets = pm_runtime_get(dev->parent); if (rets < 0 && rets != -EINPROGRESS) { - pm_runtime_put_noidle(dev->dev); + pm_runtime_put_noidle(dev->parent); cl_err(dev, cl, "rpm: get failed %zd\n", rets); goto free; } @@ -1975,14 +1984,11 @@ ssize_t mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb) mei_hdr = mei_msg_hdr_init(cb); if (IS_ERR(mei_hdr)) { - rets = -PTR_ERR(mei_hdr); + rets = PTR_ERR(mei_hdr); mei_hdr = NULL; goto err; } - cl_dbg(dev, cl, "Extended Header %d vtag = %d\n", - mei_hdr->extended, cb->vtag); - hdr_len = sizeof(*mei_hdr) + mei_hdr->length; if (rets == 0) { @@ -1999,7 +2005,7 @@ ssize_t mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb) hbuf_slots = mei_hbuf_empty_slots(dev); if (hbuf_slots < 0) { - rets = -EOVERFLOW; + buf_len = -EOVERFLOW; goto out; } @@ -2027,7 +2033,7 @@ ssize_t mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb) mei_hdr->length += data_len; - if (mei_hdr->dma_ring) + if (mei_hdr->dma_ring && buf->data) mei_dma_ring_write(dev, buf->data, buf_len); rets = mei_write_message(dev, mei_hdr, hdr_len, data, data_len); @@ -2053,11 +2059,20 @@ out: if (blocking && cl->writing_state != MEI_WRITE_COMPLETE) { mutex_unlock(&dev->device_lock); - rets = wait_event_interruptible(cl->tx_wait, - cl->writing_state == MEI_WRITE_COMPLETE || - (!mei_cl_is_connected(cl))); + rets = wait_event_interruptible_timeout(cl->tx_wait, + cl->writing_state == MEI_WRITE_COMPLETE || + (!mei_cl_is_connected(cl)), + msecs_to_jiffies(timeout)); mutex_lock(&dev->device_lock); + /* clean all queue on timeout as something fatal happened */ + if (rets == 0) { + rets = -ETIME; + mei_io_tx_list_free_cl(&dev->write_list, cl, NULL); + mei_io_tx_list_free_cl(&dev->write_waiting_list, cl, NULL); + } /* wait_event_interruptible returns -ERESTARTSYS */ + if (rets > 0) + rets = 0; if (rets) { if (signal_pending(current)) rets = -EINTR; @@ -2072,8 +2087,7 @@ out: rets = buf_len; err: cl_dbg(dev, cl, "rpm: autosuspend\n"); - pm_runtime_mark_last_busy(dev->dev); - pm_runtime_put_autosuspend(dev->dev); + pm_runtime_put_autosuspend(dev->parent); free: mei_io_cb_free(cb); @@ -2096,12 +2110,10 @@ void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb) case MEI_FOP_WRITE: mei_tx_cb_dequeue(cb); cl->writing_state = MEI_WRITE_COMPLETE; - if (waitqueue_active(&cl->tx_wait)) { + if (waitqueue_active(&cl->tx_wait)) wake_up_interruptible(&cl->tx_wait); - } else { - pm_runtime_mark_last_busy(dev->dev); - pm_request_autosuspend(dev->dev); - } + else + pm_request_autosuspend(dev->parent); break; case MEI_FOP_READ: @@ -2145,6 +2157,7 @@ void mei_cl_all_disconnect(struct mei_device *dev) list_for_each_entry(cl, &dev->file_list, link) mei_cl_set_disconnected(cl); } +EXPORT_SYMBOL_GPL(mei_cl_all_disconnect); static struct mei_cl *mei_cl_dma_map_find(struct mei_device *dev, u8 buffer_id) { @@ -2230,7 +2243,7 @@ int mei_cl_irq_dma_unmap(struct mei_cl *cl, struct mei_cl_cb *cb, static int mei_cl_dma_alloc(struct mei_cl *cl, u8 buf_id, size_t size) { - cl->dma.vaddr = dmam_alloc_coherent(cl->dev->dev, size, + cl->dma.vaddr = dmam_alloc_coherent(&cl->dev->dev, size, &cl->dma.daddr, GFP_KERNEL); if (!cl->dma.vaddr) return -ENOMEM; @@ -2244,7 +2257,7 @@ static int mei_cl_dma_alloc(struct mei_cl *cl, u8 buf_id, size_t size) static void mei_cl_dma_free(struct mei_cl *cl) { cl->dma.buffer_id = 0; - dmam_free_coherent(cl->dev->dev, + dmam_free_coherent(&cl->dev->dev, cl->dma.size, cl->dma.vaddr, cl->dma.daddr); cl->dma.size = 0; cl->dma.vaddr = NULL; @@ -2300,16 +2313,16 @@ int mei_cl_dma_alloc_and_map(struct mei_cl *cl, const struct file *fp, return -EPROTO; } - rets = pm_runtime_get(dev->dev); + rets = pm_runtime_get(dev->parent); if (rets < 0 && rets != -EINPROGRESS) { - pm_runtime_put_noidle(dev->dev); + pm_runtime_put_noidle(dev->parent); cl_err(dev, cl, "rpm: get failed %d\n", rets); return rets; } rets = mei_cl_dma_alloc(cl, buffer_id, size); if (rets) { - pm_runtime_put_noidle(dev->dev); + pm_runtime_put_noidle(dev->parent); return rets; } @@ -2327,10 +2340,12 @@ int mei_cl_dma_alloc_and_map(struct mei_cl *cl, const struct file *fp, list_move_tail(&cb->list, &dev->ctrl_rd_list); } + cl->status = 0; + mutex_unlock(&dev->device_lock); wait_event_timeout(cl->wait, cl->dma_mapped || cl->status, - mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT)); + dev->timeouts.cl_connect); mutex_lock(&dev->device_lock); if (!cl->dma_mapped && !cl->status) @@ -2343,8 +2358,7 @@ out: mei_cl_dma_free(cl); cl_dbg(dev, cl, "rpm: autosuspend\n"); - pm_runtime_mark_last_busy(dev->dev); - pm_runtime_put_autosuspend(dev->dev); + pm_runtime_put_autosuspend(dev->parent); mei_io_cb_free(cb); return rets; @@ -2383,9 +2397,9 @@ int mei_cl_dma_unmap(struct mei_cl *cl, const struct file *fp) if (!cl->dma_mapped) return -EPROTO; - rets = pm_runtime_get(dev->dev); + rets = pm_runtime_get(dev->parent); if (rets < 0 && rets != -EINPROGRESS) { - pm_runtime_put_noidle(dev->dev); + pm_runtime_put_noidle(dev->parent); cl_err(dev, cl, "rpm: get failed %d\n", rets); return rets; } @@ -2404,10 +2418,12 @@ int mei_cl_dma_unmap(struct mei_cl *cl, const struct file *fp) list_move_tail(&cb->list, &dev->ctrl_rd_list); } + cl->status = 0; + mutex_unlock(&dev->device_lock); wait_event_timeout(cl->wait, !cl->dma_mapped || cl->status, - mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT)); + dev->timeouts.cl_connect); mutex_lock(&dev->device_lock); if (cl->dma_mapped && !cl->status) @@ -2419,8 +2435,7 @@ int mei_cl_dma_unmap(struct mei_cl *cl, const struct file *fp) mei_cl_dma_free(cl); out: cl_dbg(dev, cl, "rpm: autosuspend\n"); - pm_runtime_mark_last_busy(dev->dev); - pm_runtime_put_autosuspend(dev->dev); + pm_runtime_put_autosuspend(dev->parent); mei_io_cb_free(cb); return rets; diff --git a/drivers/misc/mei/client.h b/drivers/misc/mei/client.h index b12cdcde9436..031114478bcb 100644 --- a/drivers/misc/mei/client.h +++ b/drivers/misc/mei/client.h @@ -29,8 +29,6 @@ struct mei_me_client *mei_me_cl_by_id(struct mei_device *dev, u8 client_id); struct mei_me_client *mei_me_cl_by_uuid_id(struct mei_device *dev, const uuid_le *uuid, u8 client_id); void mei_me_cl_rm_by_uuid(struct mei_device *dev, const uuid_le *uuid); -void mei_me_cl_rm_by_uuid_id(struct mei_device *dev, - const uuid_le *uuid, u8 id); void mei_me_cl_rm_all(struct mei_device *dev); /** @@ -160,7 +158,7 @@ int mei_cl_vt_support_check(const struct mei_cl *cl); * * Return: true if the host client is connected */ -static inline bool mei_cl_is_connected(struct mei_cl *cl) +static inline bool mei_cl_is_connected(const struct mei_cl *cl) { return cl->state == MEI_FILE_CONNECTED; } @@ -246,7 +244,7 @@ int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl, int mei_cl_irq_connect(struct mei_cl *cl, struct mei_cl_cb *cb, struct list_head *cmpl_list); int mei_cl_read_start(struct mei_cl *cl, size_t length, const struct file *fp); -ssize_t mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb); +ssize_t mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, unsigned long timeout); int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, struct list_head *cmpl_list); @@ -277,12 +275,12 @@ int mei_cl_dma_unmap(struct mei_cl *cl, const struct file *fp); #define MEI_CL_PRM(cl) (cl)->host_client_id, mei_cl_me_id(cl) #define cl_dbg(dev, cl, format, arg...) \ - dev_dbg((dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg) + dev_dbg(&(dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg) #define cl_warn(dev, cl, format, arg...) \ - dev_warn((dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg) + dev_warn(&(dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg) #define cl_err(dev, cl, format, arg...) \ - dev_err((dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg) + dev_err(&(dev)->dev, MEI_CL_FMT format, MEI_CL_PRM(cl), ##arg) #endif /* _MEI_CLIENT_H_ */ diff --git a/drivers/misc/mei/debugfs.c b/drivers/misc/mei/debugfs.c index 1ce61e9e24fc..3b098d4c8e3d 100644 --- a/drivers/misc/mei/debugfs.c +++ b/drivers/misc/mei/debugfs.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Copyright (c) 2012-2016, Intel Corporation. All rights reserved + * Copyright (c) 2012-2022, Intel Corporation. All rights reserved * Intel Management Engine Interface (Intel MEI) Linux driver */ @@ -86,6 +86,20 @@ out: } DEFINE_SHOW_ATTRIBUTE(mei_dbgfs_active); +static const char *mei_dev_pxp_mode_str(enum mei_dev_pxp_mode state) +{ +#define MEI_PXP_MODE(state) case MEI_DEV_PXP_##state: return #state + switch (state) { + MEI_PXP_MODE(DEFAULT); + MEI_PXP_MODE(INIT); + MEI_PXP_MODE(SETUP); + MEI_PXP_MODE(READY); + default: + return "unknown"; + } +#undef MEI_PXP_MODE +} + static int mei_dbgfs_devstate_show(struct seq_file *m, void *unused) { struct mei_device *dev = m->private; @@ -112,6 +126,9 @@ static int mei_dbgfs_devstate_show(struct seq_file *m, void *unused) seq_printf(m, "pg: %s, %s\n", mei_pg_is_enabled(dev) ? "ENABLED" : "DISABLED", mei_pg_state_str(mei_pg_state(dev))); + + seq_printf(m, "pxp: %s\n", mei_dev_pxp_mode_str(dev->pxp_mode)); + return 0; } DEFINE_SHOW_ATTRIBUTE(mei_dbgfs_devstate); diff --git a/drivers/misc/mei/dma-ring.c b/drivers/misc/mei/dma-ring.c index ef56f849b251..6277c4a5b0fd 100644 --- a/drivers/misc/mei/dma-ring.c +++ b/drivers/misc/mei/dma-ring.c @@ -30,7 +30,7 @@ static int mei_dmam_dscr_alloc(struct mei_device *dev, if (dscr->vaddr) return 0; - dscr->vaddr = dmam_alloc_coherent(dev->dev, dscr->size, &dscr->daddr, + dscr->vaddr = dmam_alloc_coherent(dev->parent, dscr->size, &dscr->daddr, GFP_KERNEL); if (!dscr->vaddr) return -ENOMEM; @@ -50,7 +50,7 @@ static void mei_dmam_dscr_free(struct mei_device *dev, if (!dscr->vaddr) return; - dmam_free_coherent(dev->dev, dscr->size, dscr->vaddr, dscr->daddr); + dmam_free_coherent(dev->parent, dscr->size, dscr->vaddr, dscr->daddr); dscr->vaddr = NULL; } @@ -124,6 +124,8 @@ void mei_dma_ring_reset(struct mei_device *dev) * @buf: data buffer * @offset: offset in slots. * @n: number of slots to copy. + * + * Return: number of bytes copied */ static size_t mei_dma_copy_from(struct mei_device *dev, unsigned char *buf, u32 offset, u32 n) @@ -144,6 +146,8 @@ static size_t mei_dma_copy_from(struct mei_device *dev, unsigned char *buf, * @buf: data buffer * @offset: offset in slots. * @n: number of slots to copy. + * + * Return: number of bytes copied */ static size_t mei_dma_copy_to(struct mei_device *dev, unsigned char *buf, u32 offset, u32 n) @@ -161,7 +165,7 @@ static size_t mei_dma_copy_to(struct mei_device *dev, unsigned char *buf, /** * mei_dma_ring_read() - read data from the ring * @dev: mei device - * @buf: buffer to read into: may be NULL in case of droping the data. + * @buf: buffer to read into: may be NULL in case of dropping the data. * @len: length to read. */ void mei_dma_ring_read(struct mei_device *dev, unsigned char *buf, u32 len) @@ -173,7 +177,7 @@ void mei_dma_ring_read(struct mei_device *dev, unsigned char *buf, u32 len) if (WARN_ON(!ctrl)) return; - dev_dbg(dev->dev, "reading from dma %u bytes\n", len); + dev_dbg(&dev->dev, "reading from dma %u bytes\n", len); if (!len) return; @@ -250,7 +254,7 @@ void mei_dma_ring_write(struct mei_device *dev, unsigned char *buf, u32 len) if (WARN_ON(!ctrl)) return; - dev_dbg(dev->dev, "writing to dma %u bytes\n", len); + dev_dbg(&dev->dev, "writing to dma %u bytes\n", len); hbuf_depth = mei_dma_ring_hbuf_depth(dev); wr_idx = READ_ONCE(ctrl->hbuf_wr_idx) & (hbuf_depth - 1); slots = mei_data2slots(len); diff --git a/drivers/misc/mei/gsc-me.c b/drivers/misc/mei/gsc-me.c new file mode 100644 index 000000000000..93cba090ea08 --- /dev/null +++ b/drivers/misc/mei/gsc-me.c @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright(c) 2019-2022, Intel Corporation. All rights reserved. + * + * Intel Management Engine Interface (Intel MEI) Linux driver + */ + +#include <linux/module.h> +#include <linux/mei_aux.h> +#include <linux/device.h> +#include <linux/irqreturn.h> +#include <linux/jiffies.h> +#include <linux/ktime.h> +#include <linux/delay.h> +#include <linux/pm_runtime.h> +#include <linux/kthread.h> + +#include "mei_dev.h" +#include "hw-me.h" +#include "hw-me-regs.h" + +#include "mei-trace.h" + +#define MEI_GSC_RPM_TIMEOUT 500 + +static int mei_gsc_read_hfs(const struct mei_device *dev, int where, u32 *val) +{ + struct mei_me_hw *hw = to_me_hw(dev); + + *val = ioread32(hw->mem_addr + where + 0xC00); + + return 0; +} + +static void mei_gsc_set_ext_op_mem(const struct mei_me_hw *hw, struct resource *mem) +{ + u32 low = lower_32_bits(mem->start); + u32 hi = upper_32_bits(mem->start); + u32 limit = (resource_size(mem) / SZ_4K) | GSC_EXT_OP_MEM_VALID; + + iowrite32(low, hw->mem_addr + H_GSC_EXT_OP_MEM_BASE_ADDR_LO_REG); + iowrite32(hi, hw->mem_addr + H_GSC_EXT_OP_MEM_BASE_ADDR_HI_REG); + iowrite32(limit, hw->mem_addr + H_GSC_EXT_OP_MEM_LIMIT_REG); +} + +static int mei_gsc_probe(struct auxiliary_device *aux_dev, + const struct auxiliary_device_id *aux_dev_id) +{ + struct mei_aux_device *adev = auxiliary_dev_to_mei_aux_dev(aux_dev); + struct mei_device *dev; + struct mei_me_hw *hw; + struct device *device; + const struct mei_cfg *cfg; + int ret; + + cfg = mei_me_get_cfg(aux_dev_id->driver_data); + if (!cfg) + return -ENODEV; + + device = &aux_dev->dev; + + dev = mei_me_dev_init(device, cfg, adev->slow_firmware); + if (!dev) { + ret = -ENOMEM; + goto err; + } + + hw = to_me_hw(dev); + hw->mem_addr = devm_ioremap_resource(device, &adev->bar); + if (IS_ERR(hw->mem_addr)) { + ret = PTR_ERR(hw->mem_addr); + goto err; + } + + hw->irq = adev->irq; + hw->read_fws = mei_gsc_read_hfs; + + dev_set_drvdata(device, dev); + + if (adev->ext_op_mem.start) { + mei_gsc_set_ext_op_mem(hw, &adev->ext_op_mem); + dev->pxp_mode = MEI_DEV_PXP_INIT; + } + + /* use polling */ + if (mei_me_hw_use_polling(hw)) { + mei_disable_interrupts(dev); + mei_clear_interrupts(dev); + init_waitqueue_head(&hw->wait_active); + hw->is_active = true; /* start in active mode for initialization */ + hw->polling_thread = kthread_run(mei_me_polling_thread, dev, + "kmegscirqd/%s", dev_name(device)); + if (IS_ERR(hw->polling_thread)) { + ret = PTR_ERR(hw->polling_thread); + dev_err(device, "unable to create kernel thread: %d\n", ret); + goto err; + } + } else { + ret = devm_request_threaded_irq(device, hw->irq, + mei_me_irq_quick_handler, + mei_me_irq_thread_handler, + IRQF_ONESHOT, KBUILD_MODNAME, dev); + if (ret) { + dev_err(device, "irq register failed %d\n", ret); + goto err; + } + } + + ret = mei_register(dev, device); + if (ret) + goto deinterrupt; + + pm_runtime_get_noresume(device); + pm_runtime_set_active(device); + pm_runtime_enable(device); + + /* Continue in spite of firmware handshake failure. + * In order to provide access to the firmware status registers to the user + * space via sysfs. + */ + if (mei_start(dev)) + dev_warn(device, "init hw failure.\n"); + + pm_runtime_set_autosuspend_delay(device, MEI_GSC_RPM_TIMEOUT); + pm_runtime_use_autosuspend(device); + + pm_runtime_put_noidle(device); + return 0; + +deinterrupt: + if (!mei_me_hw_use_polling(hw)) + devm_free_irq(device, hw->irq, dev); +err: + dev_err(device, "probe failed: %d\n", ret); + dev_set_drvdata(device, NULL); + return ret; +} + +static void mei_gsc_remove(struct auxiliary_device *aux_dev) +{ + struct mei_device *dev; + struct mei_me_hw *hw; + + dev = dev_get_drvdata(&aux_dev->dev); + hw = to_me_hw(dev); + + mei_stop(dev); + + hw = to_me_hw(dev); + if (mei_me_hw_use_polling(hw)) + kthread_stop(hw->polling_thread); + + pm_runtime_disable(&aux_dev->dev); + + mei_disable_interrupts(dev); + if (!mei_me_hw_use_polling(hw)) + devm_free_irq(&aux_dev->dev, hw->irq, dev); + + mei_deregister(dev); +} + +static int __maybe_unused mei_gsc_pm_suspend(struct device *device) +{ + struct mei_device *dev = dev_get_drvdata(device); + + mei_stop(dev); + + mei_disable_interrupts(dev); + + return 0; +} + +static int __maybe_unused mei_gsc_pm_resume(struct device *device) +{ + struct mei_device *dev = dev_get_drvdata(device); + struct auxiliary_device *aux_dev; + struct mei_aux_device *adev; + int err; + struct mei_me_hw *hw; + + hw = to_me_hw(dev); + aux_dev = to_auxiliary_dev(device); + adev = auxiliary_dev_to_mei_aux_dev(aux_dev); + if (adev->ext_op_mem.start) { + mei_gsc_set_ext_op_mem(hw, &adev->ext_op_mem); + dev->pxp_mode = MEI_DEV_PXP_INIT; + } + + err = mei_restart(dev); + if (err) + return err; + + /* Start timer if stopped in suspend */ + schedule_delayed_work(&dev->timer_work, HZ); + + return 0; +} + +static int __maybe_unused mei_gsc_pm_runtime_idle(struct device *device) +{ + struct mei_device *dev = dev_get_drvdata(device); + + if (mei_write_is_idle(dev)) + pm_runtime_autosuspend(device); + + return -EBUSY; +} + +static int __maybe_unused mei_gsc_pm_runtime_suspend(struct device *device) +{ + struct mei_device *dev = dev_get_drvdata(device); + struct mei_me_hw *hw; + int ret; + + mutex_lock(&dev->device_lock); + + if (mei_write_is_idle(dev)) { + hw = to_me_hw(dev); + hw->pg_state = MEI_PG_ON; + + if (mei_me_hw_use_polling(hw)) + hw->is_active = false; + ret = 0; + } else { + ret = -EAGAIN; + } + + mutex_unlock(&dev->device_lock); + + return ret; +} + +static int __maybe_unused mei_gsc_pm_runtime_resume(struct device *device) +{ + struct mei_device *dev = dev_get_drvdata(device); + struct mei_me_hw *hw; + irqreturn_t irq_ret; + + mutex_lock(&dev->device_lock); + + hw = to_me_hw(dev); + hw->pg_state = MEI_PG_OFF; + + if (mei_me_hw_use_polling(hw)) { + hw->is_active = true; + wake_up(&hw->wait_active); + } + + mutex_unlock(&dev->device_lock); + + irq_ret = mei_me_irq_thread_handler(1, dev); + if (irq_ret != IRQ_HANDLED) + dev_err(&dev->dev, "thread handler fail %d\n", irq_ret); + + return 0; +} + +static const struct dev_pm_ops mei_gsc_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(mei_gsc_pm_suspend, + mei_gsc_pm_resume) + SET_RUNTIME_PM_OPS(mei_gsc_pm_runtime_suspend, + mei_gsc_pm_runtime_resume, + mei_gsc_pm_runtime_idle) +}; + +static const struct auxiliary_device_id mei_gsc_id_table[] = { + { + .name = "i915.mei-gsc", + .driver_data = MEI_ME_GSC_CFG, + + }, + { + .name = "i915.mei-gscfi", + .driver_data = MEI_ME_GSCFI_CFG, + }, + { + .name = "xe.mei-gscfi", + .driver_data = MEI_ME_GSCFI_CFG, + }, + { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(auxiliary, mei_gsc_id_table); + +static struct auxiliary_driver mei_gsc_driver = { + .probe = mei_gsc_probe, + .remove = mei_gsc_remove, + .driver = { + /* auxiliary_driver_register() sets .name to be the modname */ + .pm = &mei_gsc_pm_ops, + }, + .id_table = mei_gsc_id_table +}; +module_auxiliary_driver(mei_gsc_driver); + +MODULE_AUTHOR("Intel Corporation"); +MODULE_ALIAS("auxiliary:i915.mei-gsc"); +MODULE_ALIAS("auxiliary:i915.mei-gscfi"); +MODULE_ALIAS("auxiliary:xe.mei-gscfi"); +MODULE_DESCRIPTION("Intel(R) Graphics System Controller"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/mei/gsc_proxy/Kconfig b/drivers/misc/mei/gsc_proxy/Kconfig new file mode 100644 index 000000000000..ac78b9d1eccd --- /dev/null +++ b/drivers/misc/mei/gsc_proxy/Kconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2022-2023, Intel Corporation. All rights reserved. +# +config INTEL_MEI_GSC_PROXY + tristate "Intel GSC Proxy services of ME Interface" + depends on INTEL_MEI_ME + depends on DRM_I915 + help + MEI Support for GSC Proxy Services on Intel platforms. + + MEI GSC proxy enables messaging between GSC service on + Intel graphics card and services on CSE (MEI) firmware + residing SoC or PCH. + diff --git a/drivers/misc/mei/gsc_proxy/Makefile b/drivers/misc/mei/gsc_proxy/Makefile new file mode 100644 index 000000000000..358847e9aaa9 --- /dev/null +++ b/drivers/misc/mei/gsc_proxy/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (c) 2022-2023, Intel Corporation. All rights reserved. +# +# Makefile - GSC Proxy client driver for Intel MEI Bus Driver. + +obj-$(CONFIG_INTEL_MEI_GSC_PROXY) += mei_gsc_proxy.o diff --git a/drivers/misc/mei/gsc_proxy/mei_gsc_proxy.c b/drivers/misc/mei/gsc_proxy/mei_gsc_proxy.c new file mode 100644 index 000000000000..f52fe23a6c0b --- /dev/null +++ b/drivers/misc/mei/gsc_proxy/mei_gsc_proxy.c @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2022-2023 Intel Corporation + */ + +/** + * DOC: MEI_GSC_PROXY Client Driver + * + * The mei_gsc_proxy driver acts as a translation layer between + * proxy user (I915) and ME FW by proxying messages to ME FW + */ + +#include <linux/component.h> +#include <linux/mei_cl_bus.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/uuid.h> +#include <drm/drm_connector.h> +#include <drm/intel/i915_component.h> +#include <drm/intel/i915_gsc_proxy_mei_interface.h> + +/** + * mei_gsc_proxy_send - Sends a proxy message to ME FW. + * @dev: device corresponding to the mei_cl_device + * @buf: a message buffer to send + * @size: size of the message + * Return: bytes sent on Success, <0 on Failure + */ +static int mei_gsc_proxy_send(struct device *dev, const void *buf, size_t size) +{ + ssize_t ret; + + if (!dev || !buf) + return -EINVAL; + + ret = mei_cldev_send(to_mei_cl_device(dev), buf, size); + if (ret < 0) + dev_dbg(dev, "mei_cldev_send failed. %zd\n", ret); + + return ret; +} + +/** + * mei_gsc_proxy_recv - Receives a proxy message from ME FW. + * @dev: device corresponding to the mei_cl_device + * @buf: a message buffer to contain the received message + * @size: size of the buffer + * Return: bytes received on Success, <0 on Failure + */ +static int mei_gsc_proxy_recv(struct device *dev, void *buf, size_t size) +{ + ssize_t ret; + + if (!dev || !buf) + return -EINVAL; + + ret = mei_cldev_recv(to_mei_cl_device(dev), buf, size); + if (ret < 0) + dev_dbg(dev, "mei_cldev_recv failed. %zd\n", ret); + + return ret; +} + +static const struct i915_gsc_proxy_component_ops mei_gsc_proxy_ops = { + .owner = THIS_MODULE, + .send = mei_gsc_proxy_send, + .recv = mei_gsc_proxy_recv, +}; + +static int mei_component_master_bind(struct device *dev) +{ + struct mei_cl_device *cldev = to_mei_cl_device(dev); + struct i915_gsc_proxy_component *comp_master = mei_cldev_get_drvdata(cldev); + + comp_master->ops = &mei_gsc_proxy_ops; + comp_master->mei_dev = dev; + return component_bind_all(dev, comp_master); +} + +static void mei_component_master_unbind(struct device *dev) +{ + struct mei_cl_device *cldev = to_mei_cl_device(dev); + struct i915_gsc_proxy_component *comp_master = mei_cldev_get_drvdata(cldev); + + component_unbind_all(dev, comp_master); +} + +static const struct component_master_ops mei_component_master_ops = { + .bind = mei_component_master_bind, + .unbind = mei_component_master_unbind, +}; + +/** + * mei_gsc_proxy_component_match - compare function for matching mei. + * + * The function checks if the device is pci device and + * Intel VGA adapter, the subcomponent is SW Proxy + * and the VGA is on the bus 0 reserved for built-in devices + * to reject discrete GFX. + * + * @dev: master device + * @subcomponent: subcomponent to match (I915_COMPONENT_SWPROXY) + * @data: compare data (mei pci parent) + * + * Return: + * * 1 - if components match + * * 0 - otherwise + */ +static int mei_gsc_proxy_component_match(struct device *dev, int subcomponent, + void *data) +{ + struct pci_dev *pdev; + + if (!dev_is_pci(dev)) + return 0; + + pdev = to_pci_dev(dev); + + if (pdev->class != (PCI_CLASS_DISPLAY_VGA << 8) || + pdev->vendor != PCI_VENDOR_ID_INTEL) + return 0; + + if (subcomponent != I915_COMPONENT_GSC_PROXY) + return 0; + + /* Only built-in GFX */ + return (pdev->bus->number == 0); +} + +static int mei_gsc_proxy_probe(struct mei_cl_device *cldev, + const struct mei_cl_device_id *id) +{ + struct i915_gsc_proxy_component *comp_master; + struct component_match *master_match = NULL; + int ret; + + ret = mei_cldev_enable(cldev); + if (ret < 0) { + dev_err(&cldev->dev, "mei_cldev_enable Failed. %d\n", ret); + goto enable_err_exit; + } + + comp_master = kzalloc(sizeof(*comp_master), GFP_KERNEL); + if (!comp_master) { + ret = -ENOMEM; + goto err_exit; + } + + component_match_add_typed(&cldev->dev, &master_match, + mei_gsc_proxy_component_match, NULL); + if (IS_ERR_OR_NULL(master_match)) { + ret = -ENOMEM; + goto err_exit; + } + + mei_cldev_set_drvdata(cldev, comp_master); + ret = component_master_add_with_match(&cldev->dev, + &mei_component_master_ops, + master_match); + if (ret < 0) { + dev_err(&cldev->dev, "Master comp add failed %d\n", ret); + goto err_exit; + } + + return 0; + +err_exit: + mei_cldev_set_drvdata(cldev, NULL); + kfree(comp_master); + mei_cldev_disable(cldev); +enable_err_exit: + return ret; +} + +static void mei_gsc_proxy_remove(struct mei_cl_device *cldev) +{ + struct i915_gsc_proxy_component *comp_master = mei_cldev_get_drvdata(cldev); + int ret; + + component_master_del(&cldev->dev, &mei_component_master_ops); + kfree(comp_master); + mei_cldev_set_drvdata(cldev, NULL); + + ret = mei_cldev_disable(cldev); + if (ret) + dev_warn(&cldev->dev, "mei_cldev_disable() failed %d\n", ret); +} + +#define MEI_UUID_GSC_PROXY UUID_LE(0xf73db04, 0x97ab, 0x4125, \ + 0xb8, 0x93, 0xe9, 0x4, 0xad, 0xd, 0x54, 0x64) + +static struct mei_cl_device_id mei_gsc_proxy_tbl[] = { + { .uuid = MEI_UUID_GSC_PROXY, .version = MEI_CL_VERSION_ANY }, + { } +}; +MODULE_DEVICE_TABLE(mei, mei_gsc_proxy_tbl); + +static struct mei_cl_driver mei_gsc_proxy_driver = { + .id_table = mei_gsc_proxy_tbl, + .name = KBUILD_MODNAME, + .probe = mei_gsc_proxy_probe, + .remove = mei_gsc_proxy_remove, +}; + +module_mei_cl_driver(mei_gsc_proxy_driver); + +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("MEI GSC PROXY"); diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c index 99b5c1ecc444..ccd9df5d1c7d 100644 --- a/drivers/misc/mei/hbm.c +++ b/drivers/misc/mei/hbm.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Copyright (c) 2003-2020, Intel Corporation. All rights reserved. + * Copyright (c) 2003-2022, Intel Corporation. All rights reserved. * Intel Management Engine Interface (Intel MEI) Linux driver */ #include <linux/export.h> @@ -91,6 +91,8 @@ static int mei_cl_conn_status_to_errno(enum mei_cl_connect_status status) * @dev: mei device * @hdr: mei header * @data: payload + * + * Return: >=0 on success, <0 on error */ static inline int mei_hbm_write_message(struct mei_device *dev, struct mei_msg_hdr *hdr, @@ -111,7 +113,7 @@ void mei_hbm_idle(struct mei_device *dev) } /** - * mei_hbm_reset - reset hbm counters and book keeping data structurs + * mei_hbm_reset - reset hbm counters and book keeping data structures * * @dev: the device structure */ @@ -232,12 +234,12 @@ int mei_hbm_start_wait(struct mei_device *dev) mutex_unlock(&dev->device_lock); ret = wait_event_timeout(dev->wait_hbm_start, dev->hbm_state != MEI_HBM_STARTING, - mei_secs_to_jiffies(MEI_HBM_TIMEOUT)); + dev->timeouts.hbm); mutex_lock(&dev->device_lock); if (ret == 0 && (dev->hbm_state <= MEI_HBM_STARTING)) { dev->hbm_state = MEI_HBM_IDLE; - dev_err(dev->dev, "waiting for mei start failed\n"); + dev_err(&dev->dev, "waiting for mei start failed\n"); return -ETIME; } return 0; @@ -269,13 +271,12 @@ int mei_hbm_start_req(struct mei_device *dev) dev->hbm_state = MEI_HBM_IDLE; ret = mei_hbm_write_message(dev, &mei_hdr, &req); if (ret) { - dev_err(dev->dev, "version message write failed: ret = %d\n", - ret); + dev_err(&dev->dev, "version message write failed: ret = %d\n", ret); return ret; } dev->hbm_state = MEI_HBM_STARTING; - dev->init_clients_timer = MEI_CLIENTS_INIT_TIMEOUT; + dev->init_clients_timer = dev->timeouts.client_init; mei_schedule_stall_timer(dev); return 0; } @@ -310,13 +311,12 @@ static int mei_hbm_dma_setup_req(struct mei_device *dev) ret = mei_hbm_write_message(dev, &mei_hdr, &req); if (ret) { - dev_err(dev->dev, "dma setup request write failed: ret = %d.\n", - ret); + dev_err(&dev->dev, "dma setup request write failed: ret = %d.\n", ret); return ret; } dev->hbm_state = MEI_HBM_DR_SETUP; - dev->init_clients_timer = MEI_CLIENTS_INIT_TIMEOUT; + dev->init_clients_timer = dev->timeouts.client_init; mei_schedule_stall_timer(dev); return 0; } @@ -340,18 +340,21 @@ static int mei_hbm_capabilities_req(struct mei_device *dev) req.hbm_cmd = MEI_HBM_CAPABILITIES_REQ_CMD; if (dev->hbm_f_vt_supported) req.capability_requested[0] |= HBM_CAP_VT; + if (dev->hbm_f_cd_supported) req.capability_requested[0] |= HBM_CAP_CD; + if (dev->hbm_f_gsc_supported) + req.capability_requested[0] |= HBM_CAP_GSC; + ret = mei_hbm_write_message(dev, &mei_hdr, &req); if (ret) { - dev_err(dev->dev, - "capabilities request write failed: ret = %d.\n", ret); + dev_err(&dev->dev, "capabilities request write failed: ret = %d.\n", ret); return ret; } dev->hbm_state = MEI_HBM_CAP_SETUP; - dev->init_clients_timer = MEI_CLIENTS_INIT_TIMEOUT; + dev->init_clients_timer = dev->timeouts.client_init; mei_schedule_stall_timer(dev); return 0; } @@ -380,12 +383,11 @@ static int mei_hbm_enum_clients_req(struct mei_device *dev) ret = mei_hbm_write_message(dev, &mei_hdr, &req); if (ret) { - dev_err(dev->dev, "enumeration request write failed: ret = %d.\n", - ret); + dev_err(&dev->dev, "enumeration request write failed: ret = %d.\n", ret); return ret; } dev->hbm_state = MEI_HBM_ENUM_CLIENTS; - dev->init_clients_timer = MEI_CLIENTS_INIT_TIMEOUT; + dev->init_clients_timer = dev->timeouts.client_init; mei_schedule_stall_timer(dev); return 0; } @@ -437,7 +439,7 @@ static int mei_hbm_add_cl_resp(struct mei_device *dev, u8 addr, u8 status) struct hbm_add_client_response resp; int ret; - dev_dbg(dev->dev, "adding client response\n"); + dev_dbg(&dev->dev, "adding client response\n"); mei_hbm_hdr(&mei_hdr, sizeof(resp)); @@ -448,8 +450,7 @@ static int mei_hbm_add_cl_resp(struct mei_device *dev, u8 addr, u8 status) ret = mei_hbm_write_message(dev, &mei_hdr, &resp); if (ret) - dev_err(dev->dev, "add client response write failed: ret = %d\n", - ret); + dev_err(&dev->dev, "add client response write failed: ret = %d\n", ret); return ret; } @@ -504,7 +505,7 @@ int mei_hbm_cl_notify_req(struct mei_device *dev, ret = mei_hbm_write_message(dev, &mei_hdr, &req); if (ret) - dev_err(dev->dev, "notify request failed: ret = %d\n", ret); + cl_err(dev, cl, "notify request failed: ret = %d\n", ret); return ret; } @@ -620,7 +621,7 @@ int mei_hbm_cl_dma_map_req(struct mei_device *dev, struct mei_cl *cl) ret = mei_hbm_write_message(dev, &mei_hdr, &req); if (ret) - dev_err(dev->dev, "dma map request failed: ret = %d\n", ret); + cl_err(dev, cl, "dma map request failed: ret = %d\n", ret); return ret; } @@ -648,7 +649,7 @@ int mei_hbm_cl_dma_unmap_req(struct mei_device *dev, struct mei_cl *cl) ret = mei_hbm_write_message(dev, &mei_hdr, &req); if (ret) - dev_err(dev->dev, "dma unmap request failed: ret = %d\n", ret); + cl_err(dev, cl, "dma unmap request failed: ret = %d\n", ret); return ret; } @@ -672,10 +673,14 @@ static void mei_hbm_cl_dma_map_res(struct mei_device *dev, if (!cl) return; - dev_dbg(dev->dev, "cl dma map result = %d\n", res->status); - cl->status = res->status; - if (!cl->status) + if (res->status) { + cl_err(dev, cl, "cl dma map failed %d\n", res->status); + cl->status = -EFAULT; + } else { + cl_dbg(dev, cl, "cl dma map succeeded\n"); cl->dma_mapped = 1; + cl->status = 0; + } wake_up(&cl->wait); } @@ -698,10 +703,14 @@ static void mei_hbm_cl_dma_unmap_res(struct mei_device *dev, if (!cl) return; - dev_dbg(dev->dev, "cl dma unmap result = %d\n", res->status); - cl->status = res->status; - if (!cl->status) + if (res->status) { + cl_err(dev, cl, "cl dma unmap failed %d\n", res->status); + cl->status = -EFAULT; + } else { + cl_dbg(dev, cl, "cl dma unmap succeeded\n"); cl->dma_mapped = 0; + cl->status = 0; + } wake_up(&cl->wait); } @@ -738,12 +747,12 @@ static int mei_hbm_prop_req(struct mei_device *dev, unsigned long start_idx) ret = mei_hbm_write_message(dev, &mei_hdr, &req); if (ret) { - dev_err(dev->dev, "properties request write failed: ret = %d\n", + dev_err(&dev->dev, "properties request write failed: ret = %d\n", ret); return ret; } - dev->init_clients_timer = MEI_CLIENTS_INIT_TIMEOUT; + dev->init_clients_timer = dev->timeouts.client_init; mei_schedule_stall_timer(dev); return 0; @@ -774,7 +783,7 @@ int mei_hbm_pg(struct mei_device *dev, u8 pg_cmd) ret = mei_hbm_write_message(dev, &mei_hdr, &req); if (ret) - dev_err(dev->dev, "power gate command write failed.\n"); + dev_err(&dev->dev, "power gate command write failed.\n"); return ret; } EXPORT_SYMBOL_GPL(mei_hbm_pg); @@ -833,7 +842,7 @@ static int mei_hbm_add_single_tx_flow_ctrl_creds(struct mei_device *dev, me_cl = mei_me_cl_by_id(dev, fctrl->me_addr); if (!me_cl) { - dev_err(dev->dev, "no such me client %d\n", fctrl->me_addr); + dev_err(&dev->dev, "no such me client %d\n", fctrl->me_addr); return -ENOENT; } @@ -843,7 +852,7 @@ static int mei_hbm_add_single_tx_flow_ctrl_creds(struct mei_device *dev, } me_cl->tx_flow_ctrl_creds++; - dev_dbg(dev->dev, "recv flow ctrl msg ME %d (single) creds = %d.\n", + dev_dbg(&dev->dev, "recv flow ctrl msg ME %d (single) creds = %d.\n", fctrl->me_addr, me_cl->tx_flow_ctrl_creds); rets = 0; @@ -895,7 +904,7 @@ int mei_hbm_cl_disconnect_req(struct mei_device *dev, struct mei_cl *cl) } /** - * mei_hbm_cl_disconnect_rsp - sends disconnect respose to the FW + * mei_hbm_cl_disconnect_rsp - sends disconnect response to the FW * * @dev: the device structure * @cl: a client to disconnect from @@ -1071,7 +1080,7 @@ static int mei_hbm_pg_enter_res(struct mei_device *dev) { if (mei_pg_state(dev) != MEI_PG_OFF || dev->pg_event != MEI_PG_EVENT_WAIT) { - dev_err(dev->dev, "hbm: pg entry response: state mismatch [%s, %d]\n", + dev_err(&dev->dev, "hbm: pg entry response: state mismatch [%s, %d]\n", mei_pg_state_str(mei_pg_state(dev)), dev->pg_event); return -EPROTO; } @@ -1089,7 +1098,7 @@ static int mei_hbm_pg_enter_res(struct mei_device *dev) */ void mei_hbm_pg_resume(struct mei_device *dev) { - pm_request_resume(dev->dev); + pm_request_resume(dev->parent); } EXPORT_SYMBOL_GPL(mei_hbm_pg_resume); @@ -1105,7 +1114,7 @@ static int mei_hbm_pg_exit_res(struct mei_device *dev) if (mei_pg_state(dev) != MEI_PG_ON || (dev->pg_event != MEI_PG_EVENT_WAIT && dev->pg_event != MEI_PG_EVENT_IDLE)) { - dev_err(dev->dev, "hbm: pg exit response: state mismatch [%s, %d]\n", + dev_err(&dev->dev, "hbm: pg exit response: state mismatch [%s, %d]\n", mei_pg_state_str(mei_pg_state(dev)), dev->pg_event); return -EPROTO; } @@ -1192,6 +1201,12 @@ static void mei_hbm_config_features(struct mei_device *dev) dev->version.minor_version >= HBM_MINOR_VERSION_VT)) dev->hbm_f_vt_supported = 1; + /* GSC support */ + if (dev->version.major_version > HBM_MAJOR_VERSION_GSC || + (dev->version.major_version == HBM_MAJOR_VERSION_GSC && + dev->version.minor_version >= HBM_MINOR_VERSION_GSC)) + dev->hbm_f_gsc_supported = 1; + /* Capability message Support */ dev->hbm_f_cap_supported = 0; if (dev->version.major_version > HBM_MAJOR_VERSION_CAP || @@ -1256,19 +1271,19 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) * hbm is put to idle during system reset */ if (dev->hbm_state == MEI_HBM_IDLE) { - dev_dbg(dev->dev, "hbm: state is idle ignore spurious messages\n"); + dev_dbg(&dev->dev, "hbm: state is idle ignore spurious messages\n"); return 0; } switch (mei_msg->hbm_cmd) { case HOST_START_RES_CMD: - dev_dbg(dev->dev, "hbm: start: response message received.\n"); + dev_dbg(&dev->dev, "hbm: start: response message received.\n"); dev->init_clients_timer = 0; version_res = (struct hbm_host_version_response *)mei_msg; - dev_dbg(dev->dev, "HBM VERSION: DRIVER=%02d:%02d DEVICE=%02d:%02d\n", + dev_dbg(&dev->dev, "HBM VERSION: DRIVER=%02d:%02d DEVICE=%02d:%02d\n", HBM_MAJOR_VERSION, HBM_MINOR_VERSION, version_res->me_max_version.major_version, version_res->me_max_version.minor_version); @@ -1284,11 +1299,11 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) } if (!mei_hbm_version_is_supported(dev)) { - dev_warn(dev->dev, "hbm: start: version mismatch - stopping the driver.\n"); + dev_warn(&dev->dev, "hbm: start: version mismatch - stopping the driver.\n"); dev->hbm_state = MEI_HBM_STOPPED; if (mei_hbm_stop_req(dev)) { - dev_err(dev->dev, "hbm: start: failed to send stop request\n"); + dev_err(&dev->dev, "hbm: start: failed to send stop request\n"); return -EIO; } break; @@ -1298,11 +1313,12 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) if (dev->dev_state != MEI_DEV_INIT_CLIENTS || dev->hbm_state != MEI_HBM_STARTING) { - if (dev->dev_state == MEI_DEV_POWER_DOWN) { - dev_dbg(dev->dev, "hbm: start: on shutdown, ignoring\n"); + if (dev->dev_state == MEI_DEV_POWER_DOWN || + dev->dev_state == MEI_DEV_POWERING_DOWN) { + dev_dbg(&dev->dev, "hbm: start: on shutdown, ignoring\n"); return 0; } - dev_err(dev->dev, "hbm: start: state mismatch, [%d, %d]\n", + dev_err(&dev->dev, "hbm: start: state mismatch, [%d, %d]\n", dev->dev_state, dev->hbm_state); return -EPROTO; } @@ -1316,7 +1332,7 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) if (dev->hbm_f_dr_supported) { if (mei_dmam_ring_alloc(dev)) - dev_info(dev->dev, "running w/o dma ring\n"); + dev_info(&dev->dev, "running w/o dma ring\n"); if (mei_dma_ring_is_allocated(dev)) { if (mei_hbm_dma_setup_req(dev)) return -EIO; @@ -1336,17 +1352,18 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) break; case MEI_HBM_CAPABILITIES_RES_CMD: - dev_dbg(dev->dev, "hbm: capabilities response: message received.\n"); + dev_dbg(&dev->dev, "hbm: capabilities response: message received.\n"); dev->init_clients_timer = 0; if (dev->dev_state != MEI_DEV_INIT_CLIENTS || dev->hbm_state != MEI_HBM_CAP_SETUP) { - if (dev->dev_state == MEI_DEV_POWER_DOWN) { - dev_dbg(dev->dev, "hbm: capabilities response: on shutdown, ignoring\n"); + if (dev->dev_state == MEI_DEV_POWER_DOWN || + dev->dev_state == MEI_DEV_POWERING_DOWN) { + dev_dbg(&dev->dev, "hbm: capabilities response: on shutdown, ignoring\n"); return 0; } - dev_err(dev->dev, "hbm: capabilities response: state mismatch, [%d, %d]\n", + dev_err(&dev->dev, "hbm: capabilities response: state mismatch, [%d, %d]\n", dev->dev_state, dev->hbm_state); return -EPROTO; } @@ -1357,9 +1374,12 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) if (!(capability_res->capability_granted[0] & HBM_CAP_CD)) dev->hbm_f_cd_supported = 0; + if (!(capability_res->capability_granted[0] & HBM_CAP_GSC)) + dev->hbm_f_gsc_supported = 0; + if (dev->hbm_f_dr_supported) { if (mei_dmam_ring_alloc(dev)) - dev_info(dev->dev, "running w/o dma ring\n"); + dev_info(&dev->dev, "running w/o dma ring\n"); if (mei_dma_ring_is_allocated(dev)) { if (mei_hbm_dma_setup_req(dev)) return -EIO; @@ -1375,17 +1395,18 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) break; case MEI_HBM_DMA_SETUP_RES_CMD: - dev_dbg(dev->dev, "hbm: dma setup response: message received.\n"); + dev_dbg(&dev->dev, "hbm: dma setup response: message received.\n"); dev->init_clients_timer = 0; if (dev->dev_state != MEI_DEV_INIT_CLIENTS || dev->hbm_state != MEI_HBM_DR_SETUP) { - if (dev->dev_state == MEI_DEV_POWER_DOWN) { - dev_dbg(dev->dev, "hbm: dma setup response: on shutdown, ignoring\n"); + if (dev->dev_state == MEI_DEV_POWER_DOWN || + dev->dev_state == MEI_DEV_POWERING_DOWN) { + dev_dbg(&dev->dev, "hbm: dma setup response: on shutdown, ignoring\n"); return 0; } - dev_err(dev->dev, "hbm: dma setup response: state mismatch, [%d, %d]\n", + dev_err(&dev->dev, "hbm: dma setup response: state mismatch, [%d, %d]\n", dev->dev_state, dev->hbm_state); return -EPROTO; } @@ -1396,9 +1417,9 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) u8 status = dma_setup_res->status; if (status == MEI_HBMS_NOT_ALLOWED) { - dev_dbg(dev->dev, "hbm: dma setup not allowed\n"); + dev_dbg(&dev->dev, "hbm: dma setup not allowed\n"); } else { - dev_info(dev->dev, "hbm: dma setup response: failure = %d %s\n", + dev_info(&dev->dev, "hbm: dma setup response: failure = %d %s\n", status, mei_hbm_status_str(status)); } @@ -1411,48 +1432,49 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) break; case CLIENT_CONNECT_RES_CMD: - dev_dbg(dev->dev, "hbm: client connect response: message received.\n"); + dev_dbg(&dev->dev, "hbm: client connect response: message received.\n"); mei_hbm_cl_res(dev, cl_cmd, MEI_FOP_CONNECT); break; case CLIENT_DISCONNECT_RES_CMD: - dev_dbg(dev->dev, "hbm: client disconnect response: message received.\n"); + dev_dbg(&dev->dev, "hbm: client disconnect response: message received.\n"); mei_hbm_cl_res(dev, cl_cmd, MEI_FOP_DISCONNECT); break; case MEI_FLOW_CONTROL_CMD: - dev_dbg(dev->dev, "hbm: client flow control response: message received.\n"); + dev_dbg(&dev->dev, "hbm: client flow control response: message received.\n"); fctrl = (struct hbm_flow_control *)mei_msg; mei_hbm_cl_tx_flow_ctrl_creds_res(dev, fctrl); break; case MEI_PG_ISOLATION_ENTRY_RES_CMD: - dev_dbg(dev->dev, "hbm: power gate isolation entry response received\n"); + dev_dbg(&dev->dev, "hbm: power gate isolation entry response received\n"); ret = mei_hbm_pg_enter_res(dev); if (ret) return ret; break; case MEI_PG_ISOLATION_EXIT_REQ_CMD: - dev_dbg(dev->dev, "hbm: power gate isolation exit request received\n"); + dev_dbg(&dev->dev, "hbm: power gate isolation exit request received\n"); ret = mei_hbm_pg_exit_res(dev); if (ret) return ret; break; case HOST_CLIENT_PROPERTIES_RES_CMD: - dev_dbg(dev->dev, "hbm: properties response: message received.\n"); + dev_dbg(&dev->dev, "hbm: properties response: message received.\n"); dev->init_clients_timer = 0; if (dev->dev_state != MEI_DEV_INIT_CLIENTS || dev->hbm_state != MEI_HBM_CLIENT_PROPERTIES) { - if (dev->dev_state == MEI_DEV_POWER_DOWN) { - dev_dbg(dev->dev, "hbm: properties response: on shutdown, ignoring\n"); + if (dev->dev_state == MEI_DEV_POWER_DOWN || + dev->dev_state == MEI_DEV_POWERING_DOWN) { + dev_dbg(&dev->dev, "hbm: properties response: on shutdown, ignoring\n"); return 0; } - dev_err(dev->dev, "hbm: properties response: state mismatch, [%d, %d]\n", + dev_err(&dev->dev, "hbm: properties response: state mismatch, [%d, %d]\n", dev->dev_state, dev->hbm_state); return -EPROTO; } @@ -1460,10 +1482,10 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) props_res = (struct hbm_props_response *)mei_msg; if (props_res->status == MEI_HBMS_CLIENT_NOT_FOUND) { - dev_dbg(dev->dev, "hbm: properties response: %d CLIENT_NOT_FOUND\n", + dev_dbg(&dev->dev, "hbm: properties response: %d CLIENT_NOT_FOUND\n", props_res->me_addr); } else if (props_res->status) { - dev_err(dev->dev, "hbm: properties response: wrong status = %d %s\n", + dev_err(&dev->dev, "hbm: properties response: wrong status = %d %s\n", props_res->status, mei_hbm_status_str(props_res->status)); return -EPROTO; @@ -1478,7 +1500,7 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) break; case HOST_ENUM_RES_CMD: - dev_dbg(dev->dev, "hbm: enumeration response: message received\n"); + dev_dbg(&dev->dev, "hbm: enumeration response: message received\n"); dev->init_clients_timer = 0; @@ -1490,11 +1512,12 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) if (dev->dev_state != MEI_DEV_INIT_CLIENTS || dev->hbm_state != MEI_HBM_ENUM_CLIENTS) { - if (dev->dev_state == MEI_DEV_POWER_DOWN) { - dev_dbg(dev->dev, "hbm: enumeration response: on shutdown, ignoring\n"); + if (dev->dev_state == MEI_DEV_POWER_DOWN || + dev->dev_state == MEI_DEV_POWERING_DOWN) { + dev_dbg(&dev->dev, "hbm: enumeration response: on shutdown, ignoring\n"); return 0; } - dev_err(dev->dev, "hbm: enumeration response: state mismatch, [%d, %d]\n", + dev_err(&dev->dev, "hbm: enumeration response: state mismatch, [%d, %d]\n", dev->dev_state, dev->hbm_state); return -EPROTO; } @@ -1508,77 +1531,77 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) break; case HOST_STOP_RES_CMD: - dev_dbg(dev->dev, "hbm: stop response: message received\n"); + dev_dbg(&dev->dev, "hbm: stop response: message received\n"); dev->init_clients_timer = 0; if (dev->hbm_state != MEI_HBM_STOPPED) { - dev_err(dev->dev, "hbm: stop response: state mismatch, [%d, %d]\n", + dev_err(&dev->dev, "hbm: stop response: state mismatch, [%d, %d]\n", dev->dev_state, dev->hbm_state); return -EPROTO; } mei_set_devstate(dev, MEI_DEV_POWER_DOWN); - dev_info(dev->dev, "hbm: stop response: resetting.\n"); + dev_info(&dev->dev, "hbm: stop response: resetting.\n"); /* force the reset */ return -EPROTO; case CLIENT_DISCONNECT_REQ_CMD: - dev_dbg(dev->dev, "hbm: disconnect request: message received\n"); + dev_dbg(&dev->dev, "hbm: disconnect request: message received\n"); disconnect_req = (struct hbm_client_connect_request *)mei_msg; mei_hbm_fw_disconnect_req(dev, disconnect_req); break; case ME_STOP_REQ_CMD: - dev_dbg(dev->dev, "hbm: stop request: message received\n"); + dev_dbg(&dev->dev, "hbm: stop request: message received\n"); dev->hbm_state = MEI_HBM_STOPPED; if (mei_hbm_stop_req(dev)) { - dev_err(dev->dev, "hbm: stop request: failed to send stop request\n"); + dev_err(&dev->dev, "hbm: stop request: failed to send stop request\n"); return -EIO; } break; case MEI_HBM_ADD_CLIENT_REQ_CMD: - dev_dbg(dev->dev, "hbm: add client request received\n"); + dev_dbg(&dev->dev, "hbm: add client request received\n"); /* * after the host receives the enum_resp * message clients may be added or removed */ if (dev->hbm_state <= MEI_HBM_ENUM_CLIENTS || dev->hbm_state >= MEI_HBM_STOPPED) { - dev_err(dev->dev, "hbm: add client: state mismatch, [%d, %d]\n", + dev_err(&dev->dev, "hbm: add client: state mismatch, [%d, %d]\n", dev->dev_state, dev->hbm_state); return -EPROTO; } add_cl_req = (struct hbm_add_client_request *)mei_msg; ret = mei_hbm_fw_add_cl_req(dev, add_cl_req); if (ret) { - dev_err(dev->dev, "hbm: add client: failed to send response %d\n", + dev_err(&dev->dev, "hbm: add client: failed to send response %d\n", ret); return -EIO; } - dev_dbg(dev->dev, "hbm: add client request processed\n"); + dev_dbg(&dev->dev, "hbm: add client request processed\n"); break; case MEI_HBM_NOTIFY_RES_CMD: - dev_dbg(dev->dev, "hbm: notify response received\n"); + dev_dbg(&dev->dev, "hbm: notify response received\n"); mei_hbm_cl_res(dev, cl_cmd, notify_res_to_fop(cl_cmd)); break; case MEI_HBM_NOTIFICATION_CMD: - dev_dbg(dev->dev, "hbm: notification\n"); + dev_dbg(&dev->dev, "hbm: notification\n"); mei_hbm_cl_notify(dev, cl_cmd); break; case MEI_HBM_CLIENT_DMA_MAP_RES_CMD: - dev_dbg(dev->dev, "hbm: client dma map response: message received.\n"); + dev_dbg(&dev->dev, "hbm: client dma map response: message received.\n"); client_dma_res = (struct hbm_client_dma_response *)mei_msg; mei_hbm_cl_dma_map_res(dev, client_dma_res); break; case MEI_HBM_CLIENT_DMA_UNMAP_RES_CMD: - dev_dbg(dev->dev, "hbm: client dma unmap response: message received.\n"); + dev_dbg(&dev->dev, "hbm: client dma unmap response: message received.\n"); client_dma_res = (struct hbm_client_dma_response *)mei_msg; mei_hbm_cl_dma_unmap_res(dev, client_dma_res); break; diff --git a/drivers/misc/mei/hdcp/Kconfig b/drivers/misc/mei/hdcp/Kconfig index 54e1c9526909..631dd9651d7c 100644 --- a/drivers/misc/mei/hdcp/Kconfig +++ b/drivers/misc/mei/hdcp/Kconfig @@ -3,8 +3,8 @@ # config INTEL_MEI_HDCP tristate "Intel HDCP2.2 services of ME Interface" - select INTEL_MEI_ME - depends on DRM_I915 + depends on INTEL_MEI_ME + depends on DRM_I915 || DRM_XE help MEI Support for HDCP2.2 Services on Intel platforms. diff --git a/drivers/misc/mei/hdcp/mei_hdcp.c b/drivers/misc/mei/hdcp/mei_hdcp.c index ec2a4fce8581..323f10620d90 100644 --- a/drivers/misc/mei/hdcp/mei_hdcp.c +++ b/drivers/misc/mei/hdcp/mei_hdcp.c @@ -17,13 +17,14 @@ */ #include <linux/module.h> +#include <linux/pci.h> #include <linux/slab.h> -#include <linux/uuid.h> +#include <linux/mei.h> #include <linux/mei_cl_bus.h> #include <linux/component.h> #include <drm/drm_connector.h> -#include <drm/i915_component.h> -#include <drm/i915_mei_hdcp_interface.h> +#include <drm/intel/i915_component.h> +#include <drm/intel/i915_hdcp_interface.h> #include "mei_hdcp.h" @@ -52,13 +53,13 @@ mei_hdcp_initiate_session(struct device *dev, struct hdcp_port_data *data, session_init_in.header.api_version = HDCP_API_VERSION; session_init_in.header.command_id = WIRED_INITIATE_HDCP2_SESSION; - session_init_in.header.status = ME_HDCP_STATUS_SUCCESS; + session_init_in.header.status = FW_HDCP_STATUS_SUCCESS; session_init_in.header.buffer_len = WIRED_CMD_BUF_LEN_INITIATE_HDCP2_SESSION_IN; session_init_in.port.integrated_port_type = data->port_type; - session_init_in.port.physical_port = (u8)data->fw_ddi; - session_init_in.port.attached_transcoder = (u8)data->fw_tc; + session_init_in.port.physical_port = (u8)data->hdcp_ddi; + session_init_in.port.attached_transcoder = (u8)data->hdcp_transcoder; session_init_in.protocol = data->protocol; byte = mei_cldev_send(cldev, (u8 *)&session_init_in, @@ -75,7 +76,7 @@ mei_hdcp_initiate_session(struct device *dev, struct hdcp_port_data *data, return byte; } - if (session_init_out.header.status != ME_HDCP_STATUS_SUCCESS) { + if (session_init_out.header.status != FW_HDCP_STATUS_SUCCESS) { dev_dbg(dev, "ME cmd 0x%08X Failed. Status: 0x%X\n", WIRED_INITIATE_HDCP2_SESSION, session_init_out.header.status); @@ -122,13 +123,13 @@ mei_hdcp_verify_receiver_cert_prepare_km(struct device *dev, verify_rxcert_in.header.api_version = HDCP_API_VERSION; verify_rxcert_in.header.command_id = WIRED_VERIFY_RECEIVER_CERT; - verify_rxcert_in.header.status = ME_HDCP_STATUS_SUCCESS; + verify_rxcert_in.header.status = FW_HDCP_STATUS_SUCCESS; verify_rxcert_in.header.buffer_len = WIRED_CMD_BUF_LEN_VERIFY_RECEIVER_CERT_IN; verify_rxcert_in.port.integrated_port_type = data->port_type; - verify_rxcert_in.port.physical_port = (u8)data->fw_ddi; - verify_rxcert_in.port.attached_transcoder = (u8)data->fw_tc; + verify_rxcert_in.port.physical_port = (u8)data->hdcp_ddi; + verify_rxcert_in.port.attached_transcoder = (u8)data->hdcp_transcoder; verify_rxcert_in.cert_rx = rx_cert->cert_rx; memcpy(verify_rxcert_in.r_rx, &rx_cert->r_rx, HDCP_2_2_RRX_LEN); @@ -148,7 +149,7 @@ mei_hdcp_verify_receiver_cert_prepare_km(struct device *dev, return byte; } - if (verify_rxcert_out.header.status != ME_HDCP_STATUS_SUCCESS) { + if (verify_rxcert_out.header.status != FW_HDCP_STATUS_SUCCESS) { dev_dbg(dev, "ME cmd 0x%08X Failed. Status: 0x%X\n", WIRED_VERIFY_RECEIVER_CERT, verify_rxcert_out.header.status); @@ -194,12 +195,12 @@ mei_hdcp_verify_hprime(struct device *dev, struct hdcp_port_data *data, send_hprime_in.header.api_version = HDCP_API_VERSION; send_hprime_in.header.command_id = WIRED_AKE_SEND_HPRIME; - send_hprime_in.header.status = ME_HDCP_STATUS_SUCCESS; + send_hprime_in.header.status = FW_HDCP_STATUS_SUCCESS; send_hprime_in.header.buffer_len = WIRED_CMD_BUF_LEN_AKE_SEND_HPRIME_IN; send_hprime_in.port.integrated_port_type = data->port_type; - send_hprime_in.port.physical_port = (u8)data->fw_ddi; - send_hprime_in.port.attached_transcoder = (u8)data->fw_tc; + send_hprime_in.port.physical_port = (u8)data->hdcp_ddi; + send_hprime_in.port.attached_transcoder = (u8)data->hdcp_transcoder; memcpy(send_hprime_in.h_prime, rx_hprime->h_prime, HDCP_2_2_H_PRIME_LEN); @@ -218,7 +219,7 @@ mei_hdcp_verify_hprime(struct device *dev, struct hdcp_port_data *data, return byte; } - if (send_hprime_out.header.status != ME_HDCP_STATUS_SUCCESS) { + if (send_hprime_out.header.status != FW_HDCP_STATUS_SUCCESS) { dev_dbg(dev, "ME cmd 0x%08X Failed. Status: 0x%X\n", WIRED_AKE_SEND_HPRIME, send_hprime_out.header.status); return -EIO; @@ -251,13 +252,13 @@ mei_hdcp_store_pairing_info(struct device *dev, struct hdcp_port_data *data, pairing_info_in.header.api_version = HDCP_API_VERSION; pairing_info_in.header.command_id = WIRED_AKE_SEND_PAIRING_INFO; - pairing_info_in.header.status = ME_HDCP_STATUS_SUCCESS; + pairing_info_in.header.status = FW_HDCP_STATUS_SUCCESS; pairing_info_in.header.buffer_len = WIRED_CMD_BUF_LEN_SEND_PAIRING_INFO_IN; pairing_info_in.port.integrated_port_type = data->port_type; - pairing_info_in.port.physical_port = (u8)data->fw_ddi; - pairing_info_in.port.attached_transcoder = (u8)data->fw_tc; + pairing_info_in.port.physical_port = (u8)data->hdcp_ddi; + pairing_info_in.port.attached_transcoder = (u8)data->hdcp_transcoder; memcpy(pairing_info_in.e_kh_km, pairing_info->e_kh_km, HDCP_2_2_E_KH_KM_LEN); @@ -276,7 +277,7 @@ mei_hdcp_store_pairing_info(struct device *dev, struct hdcp_port_data *data, return byte; } - if (pairing_info_out.header.status != ME_HDCP_STATUS_SUCCESS) { + if (pairing_info_out.header.status != FW_HDCP_STATUS_SUCCESS) { dev_dbg(dev, "ME cmd 0x%08X failed. Status: 0x%X\n", WIRED_AKE_SEND_PAIRING_INFO, pairing_info_out.header.status); @@ -311,12 +312,12 @@ mei_hdcp_initiate_locality_check(struct device *dev, lc_init_in.header.api_version = HDCP_API_VERSION; lc_init_in.header.command_id = WIRED_INIT_LOCALITY_CHECK; - lc_init_in.header.status = ME_HDCP_STATUS_SUCCESS; + lc_init_in.header.status = FW_HDCP_STATUS_SUCCESS; lc_init_in.header.buffer_len = WIRED_CMD_BUF_LEN_INIT_LOCALITY_CHECK_IN; lc_init_in.port.integrated_port_type = data->port_type; - lc_init_in.port.physical_port = (u8)data->fw_ddi; - lc_init_in.port.attached_transcoder = (u8)data->fw_tc; + lc_init_in.port.physical_port = (u8)data->hdcp_ddi; + lc_init_in.port.attached_transcoder = (u8)data->hdcp_transcoder; byte = mei_cldev_send(cldev, (u8 *)&lc_init_in, sizeof(lc_init_in)); if (byte < 0) { @@ -330,7 +331,7 @@ mei_hdcp_initiate_locality_check(struct device *dev, return byte; } - if (lc_init_out.header.status != ME_HDCP_STATUS_SUCCESS) { + if (lc_init_out.header.status != FW_HDCP_STATUS_SUCCESS) { dev_dbg(dev, "ME cmd 0x%08X Failed. status: 0x%X\n", WIRED_INIT_LOCALITY_CHECK, lc_init_out.header.status); return -EIO; @@ -366,13 +367,13 @@ mei_hdcp_verify_lprime(struct device *dev, struct hdcp_port_data *data, verify_lprime_in.header.api_version = HDCP_API_VERSION; verify_lprime_in.header.command_id = WIRED_VALIDATE_LOCALITY; - verify_lprime_in.header.status = ME_HDCP_STATUS_SUCCESS; + verify_lprime_in.header.status = FW_HDCP_STATUS_SUCCESS; verify_lprime_in.header.buffer_len = WIRED_CMD_BUF_LEN_VALIDATE_LOCALITY_IN; verify_lprime_in.port.integrated_port_type = data->port_type; - verify_lprime_in.port.physical_port = (u8)data->fw_ddi; - verify_lprime_in.port.attached_transcoder = (u8)data->fw_tc; + verify_lprime_in.port.physical_port = (u8)data->hdcp_ddi; + verify_lprime_in.port.attached_transcoder = (u8)data->hdcp_transcoder; memcpy(verify_lprime_in.l_prime, rx_lprime->l_prime, HDCP_2_2_L_PRIME_LEN); @@ -391,7 +392,7 @@ mei_hdcp_verify_lprime(struct device *dev, struct hdcp_port_data *data, return byte; } - if (verify_lprime_out.header.status != ME_HDCP_STATUS_SUCCESS) { + if (verify_lprime_out.header.status != FW_HDCP_STATUS_SUCCESS) { dev_dbg(dev, "ME cmd 0x%08X failed. status: 0x%X\n", WIRED_VALIDATE_LOCALITY, verify_lprime_out.header.status); @@ -425,12 +426,12 @@ static int mei_hdcp_get_session_key(struct device *dev, get_skey_in.header.api_version = HDCP_API_VERSION; get_skey_in.header.command_id = WIRED_GET_SESSION_KEY; - get_skey_in.header.status = ME_HDCP_STATUS_SUCCESS; + get_skey_in.header.status = FW_HDCP_STATUS_SUCCESS; get_skey_in.header.buffer_len = WIRED_CMD_BUF_LEN_GET_SESSION_KEY_IN; get_skey_in.port.integrated_port_type = data->port_type; - get_skey_in.port.physical_port = (u8)data->fw_ddi; - get_skey_in.port.attached_transcoder = (u8)data->fw_tc; + get_skey_in.port.physical_port = (u8)data->hdcp_ddi; + get_skey_in.port.attached_transcoder = (u8)data->hdcp_transcoder; byte = mei_cldev_send(cldev, (u8 *)&get_skey_in, sizeof(get_skey_in)); if (byte < 0) { @@ -445,7 +446,7 @@ static int mei_hdcp_get_session_key(struct device *dev, return byte; } - if (get_skey_out.header.status != ME_HDCP_STATUS_SUCCESS) { + if (get_skey_out.header.status != FW_HDCP_STATUS_SUCCESS) { dev_dbg(dev, "ME cmd 0x%08X failed. status: 0x%X\n", WIRED_GET_SESSION_KEY, get_skey_out.header.status); return -EIO; @@ -489,13 +490,13 @@ mei_hdcp_repeater_check_flow_prepare_ack(struct device *dev, verify_repeater_in.header.api_version = HDCP_API_VERSION; verify_repeater_in.header.command_id = WIRED_VERIFY_REPEATER; - verify_repeater_in.header.status = ME_HDCP_STATUS_SUCCESS; + verify_repeater_in.header.status = FW_HDCP_STATUS_SUCCESS; verify_repeater_in.header.buffer_len = WIRED_CMD_BUF_LEN_VERIFY_REPEATER_IN; verify_repeater_in.port.integrated_port_type = data->port_type; - verify_repeater_in.port.physical_port = (u8)data->fw_ddi; - verify_repeater_in.port.attached_transcoder = (u8)data->fw_tc; + verify_repeater_in.port.physical_port = (u8)data->hdcp_ddi; + verify_repeater_in.port.attached_transcoder = (u8)data->hdcp_transcoder; memcpy(verify_repeater_in.rx_info, rep_topology->rx_info, HDCP_2_2_RXINFO_LEN); @@ -520,7 +521,7 @@ mei_hdcp_repeater_check_flow_prepare_ack(struct device *dev, return byte; } - if (verify_repeater_out.header.status != ME_HDCP_STATUS_SUCCESS) { + if (verify_repeater_out.header.status != FW_HDCP_STATUS_SUCCESS) { dev_dbg(dev, "ME cmd 0x%08X failed. status: 0x%X\n", WIRED_VERIFY_REPEATER, verify_repeater_out.header.status); @@ -568,12 +569,12 @@ static int mei_hdcp_verify_mprime(struct device *dev, verify_mprime_in->header.api_version = HDCP_API_VERSION; verify_mprime_in->header.command_id = WIRED_REPEATER_AUTH_STREAM_REQ; - verify_mprime_in->header.status = ME_HDCP_STATUS_SUCCESS; + verify_mprime_in->header.status = FW_HDCP_STATUS_SUCCESS; verify_mprime_in->header.buffer_len = cmd_size - sizeof(verify_mprime_in->header); verify_mprime_in->port.integrated_port_type = data->port_type; - verify_mprime_in->port.physical_port = (u8)data->fw_ddi; - verify_mprime_in->port.attached_transcoder = (u8)data->fw_tc; + verify_mprime_in->port.physical_port = (u8)data->hdcp_ddi; + verify_mprime_in->port.attached_transcoder = (u8)data->hdcp_transcoder; memcpy(verify_mprime_in->m_prime, stream_ready->m_prime, HDCP_2_2_MPRIME_LEN); drm_hdcp_cpu_to_be24(verify_mprime_in->seq_num_m, data->seq_num_m); @@ -597,7 +598,7 @@ static int mei_hdcp_verify_mprime(struct device *dev, return byte; } - if (verify_mprime_out.header.status != ME_HDCP_STATUS_SUCCESS) { + if (verify_mprime_out.header.status != FW_HDCP_STATUS_SUCCESS) { dev_dbg(dev, "ME cmd 0x%08X failed. status: 0x%X\n", WIRED_REPEATER_AUTH_STREAM_REQ, verify_mprime_out.header.status); @@ -630,12 +631,12 @@ static int mei_hdcp_enable_authentication(struct device *dev, enable_auth_in.header.api_version = HDCP_API_VERSION; enable_auth_in.header.command_id = WIRED_ENABLE_AUTH; - enable_auth_in.header.status = ME_HDCP_STATUS_SUCCESS; + enable_auth_in.header.status = FW_HDCP_STATUS_SUCCESS; enable_auth_in.header.buffer_len = WIRED_CMD_BUF_LEN_ENABLE_AUTH_IN; enable_auth_in.port.integrated_port_type = data->port_type; - enable_auth_in.port.physical_port = (u8)data->fw_ddi; - enable_auth_in.port.attached_transcoder = (u8)data->fw_tc; + enable_auth_in.port.physical_port = (u8)data->hdcp_ddi; + enable_auth_in.port.attached_transcoder = (u8)data->hdcp_transcoder; enable_auth_in.stream_type = data->streams[0].stream_type; byte = mei_cldev_send(cldev, (u8 *)&enable_auth_in, @@ -652,7 +653,7 @@ static int mei_hdcp_enable_authentication(struct device *dev, return byte; } - if (enable_auth_out.header.status != ME_HDCP_STATUS_SUCCESS) { + if (enable_auth_out.header.status != FW_HDCP_STATUS_SUCCESS) { dev_dbg(dev, "ME cmd 0x%08X failed. status: 0x%X\n", WIRED_ENABLE_AUTH, enable_auth_out.header.status); return -EIO; @@ -684,13 +685,13 @@ mei_hdcp_close_session(struct device *dev, struct hdcp_port_data *data) session_close_in.header.api_version = HDCP_API_VERSION; session_close_in.header.command_id = WIRED_CLOSE_SESSION; - session_close_in.header.status = ME_HDCP_STATUS_SUCCESS; + session_close_in.header.status = FW_HDCP_STATUS_SUCCESS; session_close_in.header.buffer_len = WIRED_CMD_BUF_LEN_CLOSE_SESSION_IN; session_close_in.port.integrated_port_type = data->port_type; - session_close_in.port.physical_port = (u8)data->fw_ddi; - session_close_in.port.attached_transcoder = (u8)data->fw_tc; + session_close_in.port.physical_port = (u8)data->hdcp_ddi; + session_close_in.port.attached_transcoder = (u8)data->hdcp_transcoder; byte = mei_cldev_send(cldev, (u8 *)&session_close_in, sizeof(session_close_in)); @@ -706,7 +707,7 @@ mei_hdcp_close_session(struct device *dev, struct hdcp_port_data *data) return byte; } - if (session_close_out.header.status != ME_HDCP_STATUS_SUCCESS) { + if (session_close_out.header.status != FW_HDCP_STATUS_SUCCESS) { dev_dbg(dev, "Session Close Failed. status: 0x%X\n", session_close_out.header.status); return -EIO; @@ -715,7 +716,7 @@ mei_hdcp_close_session(struct device *dev, struct hdcp_port_data *data) return 0; } -static const struct i915_hdcp_component_ops mei_hdcp_ops = { +static const struct i915_hdcp_ops mei_hdcp_ops = { .owner = THIS_MODULE, .initiate_hdcp2_session = mei_hdcp_initiate_session, .verify_receiver_cert_prepare_km = @@ -735,14 +736,13 @@ static const struct i915_hdcp_component_ops mei_hdcp_ops = { static int mei_component_master_bind(struct device *dev) { struct mei_cl_device *cldev = to_mei_cl_device(dev); - struct i915_hdcp_comp_master *comp_master = - mei_cldev_get_drvdata(cldev); + struct i915_hdcp_arbiter *comp_arbiter = mei_cldev_get_drvdata(cldev); int ret; dev_dbg(dev, "%s\n", __func__); - comp_master->ops = &mei_hdcp_ops; - comp_master->mei_dev = dev; - ret = component_bind_all(dev, comp_master); + comp_arbiter->ops = &mei_hdcp_ops; + comp_arbiter->hdcp_dev = dev; + ret = component_bind_all(dev, comp_arbiter); if (ret < 0) return ret; @@ -752,11 +752,10 @@ static int mei_component_master_bind(struct device *dev) static void mei_component_master_unbind(struct device *dev) { struct mei_cl_device *cldev = to_mei_cl_device(dev); - struct i915_hdcp_comp_master *comp_master = - mei_cldev_get_drvdata(cldev); + struct i915_hdcp_arbiter *comp_arbiter = mei_cldev_get_drvdata(cldev); dev_dbg(dev, "%s\n", __func__); - component_unbind_all(dev, comp_master); + component_unbind_all(dev, comp_arbiter); } static const struct component_master_ops mei_component_master_ops = { @@ -783,9 +782,18 @@ static int mei_hdcp_component_match(struct device *dev, int subcomponent, void *data) { struct device *base = data; + struct pci_dev *pdev; - if (strcmp(dev->driver->name, "i915") || - subcomponent != I915_COMPONENT_HDCP) + if (!dev_is_pci(dev)) + return 0; + + pdev = to_pci_dev(dev); + + if (pdev->class != (PCI_CLASS_DISPLAY_VGA << 8) || + pdev->vendor != PCI_VENDOR_ID_INTEL) + return 0; + + if (subcomponent != I915_COMPONENT_HDCP) return 0; base = base->parent; @@ -801,7 +809,7 @@ static int mei_hdcp_component_match(struct device *dev, int subcomponent, static int mei_hdcp_probe(struct mei_cl_device *cldev, const struct mei_cl_device_id *id) { - struct i915_hdcp_comp_master *comp_master; + struct i915_hdcp_arbiter *comp_arbiter; struct component_match *master_match; int ret; @@ -811,8 +819,8 @@ static int mei_hdcp_probe(struct mei_cl_device *cldev, goto enable_err_exit; } - comp_master = kzalloc(sizeof(*comp_master), GFP_KERNEL); - if (!comp_master) { + comp_arbiter = kzalloc(sizeof(*comp_arbiter), GFP_KERNEL); + if (!comp_arbiter) { ret = -ENOMEM; goto err_exit; } @@ -825,7 +833,7 @@ static int mei_hdcp_probe(struct mei_cl_device *cldev, goto err_exit; } - mei_cldev_set_drvdata(cldev, comp_master); + mei_cldev_set_drvdata(cldev, comp_arbiter); ret = component_master_add_with_match(&cldev->dev, &mei_component_master_ops, master_match); @@ -838,7 +846,7 @@ static int mei_hdcp_probe(struct mei_cl_device *cldev, err_exit: mei_cldev_set_drvdata(cldev, NULL); - kfree(comp_master); + kfree(comp_arbiter); mei_cldev_disable(cldev); enable_err_exit: return ret; @@ -846,12 +854,11 @@ enable_err_exit: static void mei_hdcp_remove(struct mei_cl_device *cldev) { - struct i915_hdcp_comp_master *comp_master = - mei_cldev_get_drvdata(cldev); + struct i915_hdcp_arbiter *comp_arbiter = mei_cldev_get_drvdata(cldev); int ret; component_master_del(&cldev->dev, &mei_component_master_ops); - kfree(comp_master); + kfree(comp_arbiter); mei_cldev_set_drvdata(cldev, NULL); ret = mei_cldev_disable(cldev); @@ -859,8 +866,8 @@ static void mei_hdcp_remove(struct mei_cl_device *cldev) dev_warn(&cldev->dev, "mei_cldev_disable() failed\n"); } -#define MEI_UUID_HDCP GUID_INIT(0xB638AB7E, 0x94E2, 0x4EA2, 0xA5, \ - 0x52, 0xD1, 0xC5, 0x4B, 0x62, 0x7F, 0x04) +#define MEI_UUID_HDCP UUID_LE(0xB638AB7E, 0x94E2, 0x4EA2, 0xA5, \ + 0x52, 0xD1, 0xC5, 0x4B, 0x62, 0x7F, 0x04) static const struct mei_cl_device_id mei_hdcp_tbl[] = { { .uuid = MEI_UUID_HDCP, .version = MEI_CL_VERSION_ANY }, diff --git a/drivers/misc/mei/hdcp/mei_hdcp.h b/drivers/misc/mei/hdcp/mei_hdcp.h index 834757f5e072..0683d83ec17a 100644 --- a/drivers/misc/mei/hdcp/mei_hdcp.h +++ b/drivers/misc/mei/hdcp/mei_hdcp.h @@ -9,360 +9,6 @@ #ifndef __MEI_HDCP_H__ #define __MEI_HDCP_H__ -#include <drm/drm_hdcp.h> +#include <drm/display/drm_hdcp.h> -/* me_hdcp_status: Enumeration of all HDCP Status Codes */ -enum me_hdcp_status { - ME_HDCP_STATUS_SUCCESS = 0x0000, - - /* WiDi Generic Status Codes */ - ME_HDCP_STATUS_INTERNAL_ERROR = 0x1000, - ME_HDCP_STATUS_UNKNOWN_ERROR = 0x1001, - ME_HDCP_STATUS_INCORRECT_API_VERSION = 0x1002, - ME_HDCP_STATUS_INVALID_FUNCTION = 0x1003, - ME_HDCP_STATUS_INVALID_BUFFER_LENGTH = 0x1004, - ME_HDCP_STATUS_INVALID_PARAMS = 0x1005, - ME_HDCP_STATUS_AUTHENTICATION_FAILED = 0x1006, - - /* WiDi Status Codes */ - ME_HDCP_INVALID_SESSION_STATE = 0x6000, - ME_HDCP_SRM_FRAGMENT_UNEXPECTED = 0x6001, - ME_HDCP_SRM_INVALID_LENGTH = 0x6002, - ME_HDCP_SRM_FRAGMENT_OFFSET_INVALID = 0x6003, - ME_HDCP_SRM_VERIFICATION_FAILED = 0x6004, - ME_HDCP_SRM_VERSION_TOO_OLD = 0x6005, - ME_HDCP_RX_CERT_VERIFICATION_FAILED = 0x6006, - ME_HDCP_RX_REVOKED = 0x6007, - ME_HDCP_H_VERIFICATION_FAILED = 0x6008, - ME_HDCP_REPEATER_CHECK_UNEXPECTED = 0x6009, - ME_HDCP_TOPOLOGY_MAX_EXCEEDED = 0x600A, - ME_HDCP_V_VERIFICATION_FAILED = 0x600B, - ME_HDCP_L_VERIFICATION_FAILED = 0x600C, - ME_HDCP_STREAM_KEY_ALLOC_FAILED = 0x600D, - ME_HDCP_BASE_KEY_RESET_FAILED = 0x600E, - ME_HDCP_NONCE_GENERATION_FAILED = 0x600F, - ME_HDCP_STATUS_INVALID_E_KEY_STATE = 0x6010, - ME_HDCP_STATUS_INVALID_CS_ICV = 0x6011, - ME_HDCP_STATUS_INVALID_KB_KEY_STATE = 0x6012, - ME_HDCP_STATUS_INVALID_PAVP_MODE_ICV = 0x6013, - ME_HDCP_STATUS_INVALID_PAVP_MODE = 0x6014, - ME_HDCP_STATUS_LC_MAX_ATTEMPTS = 0x6015, - - /* New status for HDCP 2.1 */ - ME_HDCP_STATUS_MISMATCH_IN_M = 0x6016, - - /* New status code for HDCP 2.2 Rx */ - ME_HDCP_STATUS_RX_PROV_NOT_ALLOWED = 0x6017, - ME_HDCP_STATUS_RX_PROV_WRONG_SUBJECT = 0x6018, - ME_HDCP_RX_NEEDS_PROVISIONING = 0x6019, - ME_HDCP_BKSV_ICV_AUTH_FAILED = 0x6020, - ME_HDCP_STATUS_INVALID_STREAM_ID = 0x6021, - ME_HDCP_STATUS_CHAIN_NOT_INITIALIZED = 0x6022, - ME_HDCP_FAIL_NOT_EXPECTED = 0x6023, - ME_HDCP_FAIL_HDCP_OFF = 0x6024, - ME_HDCP_FAIL_INVALID_PAVP_MEMORY_MODE = 0x6025, - ME_HDCP_FAIL_AES_ECB_FAILURE = 0x6026, - ME_HDCP_FEATURE_NOT_SUPPORTED = 0x6027, - ME_HDCP_DMA_READ_ERROR = 0x6028, - ME_HDCP_DMA_WRITE_ERROR = 0x6029, - ME_HDCP_FAIL_INVALID_PACKET_SIZE = 0x6030, - ME_HDCP_H264_PARSING_ERROR = 0x6031, - ME_HDCP_HDCP2_ERRATA_VIDEO_VIOLATION = 0x6032, - ME_HDCP_HDCP2_ERRATA_AUDIO_VIOLATION = 0x6033, - ME_HDCP_TX_ACTIVE_ERROR = 0x6034, - ME_HDCP_MODE_CHANGE_ERROR = 0x6035, - ME_HDCP_STREAM_TYPE_ERROR = 0x6036, - ME_HDCP_STREAM_MANAGE_NOT_POSSIBLE = 0x6037, - - ME_HDCP_STATUS_PORT_INVALID_COMMAND = 0x6038, - ME_HDCP_STATUS_UNSUPPORTED_PROTOCOL = 0x6039, - ME_HDCP_STATUS_INVALID_PORT_INDEX = 0x603a, - ME_HDCP_STATUS_TX_AUTH_NEEDED = 0x603b, - ME_HDCP_STATUS_NOT_INTEGRATED_PORT = 0x603c, - ME_HDCP_STATUS_SESSION_MAX_REACHED = 0x603d, - - /* hdcp capable bit is not set in rx_caps(error is unique to DP) */ - ME_HDCP_STATUS_NOT_HDCP_CAPABLE = 0x6041, - - ME_HDCP_STATUS_INVALID_STREAM_COUNT = 0x6042, -}; - -#define HDCP_API_VERSION 0x00010000 - -#define HDCP_M_LEN 16 -#define HDCP_KH_LEN 16 - -/* Payload Buffer size(Excluding Header) for CMDs and corresponding response */ -/* Wired_Tx_AKE */ -#define WIRED_CMD_BUF_LEN_INITIATE_HDCP2_SESSION_IN (4 + 1) -#define WIRED_CMD_BUF_LEN_INITIATE_HDCP2_SESSION_OUT (4 + 8 + 3) - -#define WIRED_CMD_BUF_LEN_VERIFY_RECEIVER_CERT_IN (4 + 522 + 8 + 3) -#define WIRED_CMD_BUF_LEN_VERIFY_RECEIVER_CERT_MIN_OUT (4 + 1 + 3 + 16 + 16) -#define WIRED_CMD_BUF_LEN_VERIFY_RECEIVER_CERT_MAX_OUT (4 + 1 + 3 + 128) - -#define WIRED_CMD_BUF_LEN_AKE_SEND_HPRIME_IN (4 + 32) -#define WIRED_CMD_BUF_LEN_AKE_SEND_HPRIME_OUT (4) - -#define WIRED_CMD_BUF_LEN_SEND_PAIRING_INFO_IN (4 + 16) -#define WIRED_CMD_BUF_LEN_SEND_PAIRING_INFO_OUT (4) - -#define WIRED_CMD_BUF_LEN_CLOSE_SESSION_IN (4) -#define WIRED_CMD_BUF_LEN_CLOSE_SESSION_OUT (4) - -/* Wired_Tx_LC */ -#define WIRED_CMD_BUF_LEN_INIT_LOCALITY_CHECK_IN (4) -#define WIRED_CMD_BUF_LEN_INIT_LOCALITY_CHECK_OUT (4 + 8) - -#define WIRED_CMD_BUF_LEN_VALIDATE_LOCALITY_IN (4 + 32) -#define WIRED_CMD_BUF_LEN_VALIDATE_LOCALITY_OUT (4) - -/* Wired_Tx_SKE */ -#define WIRED_CMD_BUF_LEN_GET_SESSION_KEY_IN (4) -#define WIRED_CMD_BUF_LEN_GET_SESSION_KEY_OUT (4 + 16 + 8) - -/* Wired_Tx_SKE */ -#define WIRED_CMD_BUF_LEN_ENABLE_AUTH_IN (4 + 1) -#define WIRED_CMD_BUF_LEN_ENABLE_AUTH_OUT (4) - -/* Wired_Tx_Repeater */ -#define WIRED_CMD_BUF_LEN_VERIFY_REPEATER_IN (4 + 2 + 3 + 16 + 155) -#define WIRED_CMD_BUF_LEN_VERIFY_REPEATER_OUT (4 + 1 + 16) - -#define WIRED_CMD_BUF_LEN_REPEATER_AUTH_STREAM_REQ_MIN_IN (4 + 3 + \ - 32 + 2 + 2) - -#define WIRED_CMD_BUF_LEN_REPEATER_AUTH_STREAM_REQ_OUT (4) - -/* hdcp_command_id: Enumeration of all WIRED HDCP Command IDs */ -enum hdcp_command_id { - _WIDI_COMMAND_BASE = 0x00030000, - WIDI_INITIATE_HDCP2_SESSION = _WIDI_COMMAND_BASE, - HDCP_GET_SRM_STATUS, - HDCP_SEND_SRM_FRAGMENT, - - /* The wired HDCP Tx commands */ - _WIRED_COMMAND_BASE = 0x00031000, - WIRED_INITIATE_HDCP2_SESSION = _WIRED_COMMAND_BASE, - WIRED_VERIFY_RECEIVER_CERT, - WIRED_AKE_SEND_HPRIME, - WIRED_AKE_SEND_PAIRING_INFO, - WIRED_INIT_LOCALITY_CHECK, - WIRED_VALIDATE_LOCALITY, - WIRED_GET_SESSION_KEY, - WIRED_ENABLE_AUTH, - WIRED_VERIFY_REPEATER, - WIRED_REPEATER_AUTH_STREAM_REQ, - WIRED_CLOSE_SESSION, - - _WIRED_COMMANDS_COUNT, -}; - -union encrypted_buff { - u8 e_kpub_km[HDCP_2_2_E_KPUB_KM_LEN]; - u8 e_kh_km_m[HDCP_2_2_E_KH_KM_M_LEN]; - struct { - u8 e_kh_km[HDCP_KH_LEN]; - u8 m[HDCP_M_LEN]; - } __packed; -}; - -/* HDCP HECI message header. All header values are little endian. */ -struct hdcp_cmd_header { - u32 api_version; - u32 command_id; - enum me_hdcp_status status; - /* Length of the HECI message (excluding the header) */ - u32 buffer_len; -} __packed; - -/* Empty command request or response. No data follows the header. */ -struct hdcp_cmd_no_data { - struct hdcp_cmd_header header; -} __packed; - -/* Uniquely identifies the hdcp port being addressed for a given command. */ -struct hdcp_port_id { - u8 integrated_port_type; - /* physical_port is used until Gen11.5. Must be zero for Gen11.5+ */ - u8 physical_port; - /* attached_transcoder is for Gen11.5+. Set to zero for <Gen11.5 */ - u8 attached_transcoder; - u8 reserved; -} __packed; - -/* - * Data structures for integrated wired HDCP2 Tx in - * support of the AKE protocol - */ -/* HECI struct for integrated wired HDCP Tx session initiation. */ -struct wired_cmd_initiate_hdcp2_session_in { - struct hdcp_cmd_header header; - struct hdcp_port_id port; - u8 protocol; /* for HDMI vs DP */ -} __packed; - -struct wired_cmd_initiate_hdcp2_session_out { - struct hdcp_cmd_header header; - struct hdcp_port_id port; - u8 r_tx[HDCP_2_2_RTX_LEN]; - struct hdcp2_tx_caps tx_caps; -} __packed; - -/* HECI struct for ending an integrated wired HDCP Tx session. */ -struct wired_cmd_close_session_in { - struct hdcp_cmd_header header; - struct hdcp_port_id port; -} __packed; - -struct wired_cmd_close_session_out { - struct hdcp_cmd_header header; - struct hdcp_port_id port; -} __packed; - -/* HECI struct for integrated wired HDCP Tx Rx Cert verification. */ -struct wired_cmd_verify_receiver_cert_in { - struct hdcp_cmd_header header; - struct hdcp_port_id port; - struct hdcp2_cert_rx cert_rx; - u8 r_rx[HDCP_2_2_RRX_LEN]; - u8 rx_caps[HDCP_2_2_RXCAPS_LEN]; -} __packed; - -struct wired_cmd_verify_receiver_cert_out { - struct hdcp_cmd_header header; - struct hdcp_port_id port; - u8 km_stored; - u8 reserved[3]; - union encrypted_buff ekm_buff; -} __packed; - -/* HECI struct for verification of Rx's Hprime in a HDCP Tx session */ -struct wired_cmd_ake_send_hprime_in { - struct hdcp_cmd_header header; - struct hdcp_port_id port; - u8 h_prime[HDCP_2_2_H_PRIME_LEN]; -} __packed; - -struct wired_cmd_ake_send_hprime_out { - struct hdcp_cmd_header header; - struct hdcp_port_id port; -} __packed; - -/* - * HECI struct for sending in AKE pairing data generated by the Rx in an - * integrated wired HDCP Tx session. - */ -struct wired_cmd_ake_send_pairing_info_in { - struct hdcp_cmd_header header; - struct hdcp_port_id port; - u8 e_kh_km[HDCP_2_2_E_KH_KM_LEN]; -} __packed; - -struct wired_cmd_ake_send_pairing_info_out { - struct hdcp_cmd_header header; - struct hdcp_port_id port; -} __packed; - -/* Data structures for integrated wired HDCP2 Tx in support of the LC protocol*/ -/* - * HECI struct for initiating locality check with an - * integrated wired HDCP Tx session. - */ -struct wired_cmd_init_locality_check_in { - struct hdcp_cmd_header header; - struct hdcp_port_id port; -} __packed; - -struct wired_cmd_init_locality_check_out { - struct hdcp_cmd_header header; - struct hdcp_port_id port; - u8 r_n[HDCP_2_2_RN_LEN]; -} __packed; - -/* - * HECI struct for validating an Rx's LPrime value in an - * integrated wired HDCP Tx session. - */ -struct wired_cmd_validate_locality_in { - struct hdcp_cmd_header header; - struct hdcp_port_id port; - u8 l_prime[HDCP_2_2_L_PRIME_LEN]; -} __packed; - -struct wired_cmd_validate_locality_out { - struct hdcp_cmd_header header; - struct hdcp_port_id port; -} __packed; - -/* - * Data structures for integrated wired HDCP2 Tx in support of the - * SKE protocol - */ -/* HECI struct for creating session key */ -struct wired_cmd_get_session_key_in { - struct hdcp_cmd_header header; - struct hdcp_port_id port; -} __packed; - -struct wired_cmd_get_session_key_out { - struct hdcp_cmd_header header; - struct hdcp_port_id port; - u8 e_dkey_ks[HDCP_2_2_E_DKEY_KS_LEN]; - u8 r_iv[HDCP_2_2_RIV_LEN]; -} __packed; - -/* HECI struct for the Tx enable authentication command */ -struct wired_cmd_enable_auth_in { - struct hdcp_cmd_header header; - struct hdcp_port_id port; - u8 stream_type; -} __packed; - -struct wired_cmd_enable_auth_out { - struct hdcp_cmd_header header; - struct hdcp_port_id port; -} __packed; - -/* - * Data structures for integrated wired HDCP2 Tx in support of - * the repeater protocols - */ -/* - * HECI struct for verifying the downstream repeater's HDCP topology in an - * integrated wired HDCP Tx session. - */ -struct wired_cmd_verify_repeater_in { - struct hdcp_cmd_header header; - struct hdcp_port_id port; - u8 rx_info[HDCP_2_2_RXINFO_LEN]; - u8 seq_num_v[HDCP_2_2_SEQ_NUM_LEN]; - u8 v_prime[HDCP_2_2_V_PRIME_HALF_LEN]; - u8 receiver_ids[HDCP_2_2_RECEIVER_IDS_MAX_LEN]; -} __packed; - -struct wired_cmd_verify_repeater_out { - struct hdcp_cmd_header header; - struct hdcp_port_id port; - u8 content_type_supported; - u8 v[HDCP_2_2_V_PRIME_HALF_LEN]; -} __packed; - -/* - * HECI struct in support of stream management in an - * integrated wired HDCP Tx session. - */ -struct wired_cmd_repeater_auth_stream_req_in { - struct hdcp_cmd_header header; - struct hdcp_port_id port; - u8 seq_num_m[HDCP_2_2_SEQ_NUM_LEN]; - u8 m_prime[HDCP_2_2_MPRIME_LEN]; - __be16 k; - struct hdcp2_streamid_type streams[]; -} __packed; - -struct wired_cmd_repeater_auth_stream_req_out { - struct hdcp_cmd_header header; - struct hdcp_port_id port; -} __packed; #endif /* __MEI_HDCP_H__ */ diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h index cb34925e10f1..a4f75dc36929 100644 --- a/drivers/misc/mei/hw-me-regs.h +++ b/drivers/misc/mei/hw-me-regs.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (c) 2003-2019, Intel Corporation. All rights reserved. + * Copyright (c) 2003-2022, Intel Corporation. All rights reserved. * Intel Management Engine Interface (Intel MEI) Linux driver */ #ifndef _MEI_HW_MEI_REGS_H_ @@ -92,6 +92,7 @@ #define MEI_DEV_ID_CDF 0x18D3 /* Cedar Fork */ #define MEI_DEV_ID_ICP_LP 0x34E0 /* Ice Lake Point LP */ +#define MEI_DEV_ID_ICP_N 0x38E0 /* Ice Lake Point N */ #define MEI_DEV_ID_JSP_N 0x4DE0 /* Jasper Lake Point N */ @@ -106,6 +107,20 @@ #define MEI_DEV_ID_ADP_S 0x7AE8 /* Alder Lake Point S */ #define MEI_DEV_ID_ADP_LP 0x7A60 /* Alder Lake Point LP */ #define MEI_DEV_ID_ADP_P 0x51E0 /* Alder Lake Point P */ +#define MEI_DEV_ID_ADP_N 0x54E0 /* Alder Lake Point N */ + +#define MEI_DEV_ID_RPL_S 0x7A68 /* Raptor Lake Point S */ + +#define MEI_DEV_ID_MTL_M 0x7E70 /* Meteor Lake Point M */ +#define MEI_DEV_ID_ARL_S 0x7F68 /* Arrow Lake Point S */ +#define MEI_DEV_ID_ARL_H 0x7770 /* Arrow Lake Point H */ + +#define MEI_DEV_ID_LNL_M 0xA870 /* Lunar Lake Point M */ + +#define MEI_DEV_ID_PTL_H 0xE370 /* Panther Lake H */ +#define MEI_DEV_ID_PTL_P 0xE470 /* Panther Lake P */ + +#define MEI_DEV_ID_WCL_P 0x4D70 /* Wildcat Lake P */ /* * MEI HW Section @@ -117,11 +132,17 @@ # 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_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 #define PCI_CFG_HFS_3 0x60 # define PCI_CFG_HFS_3_FW_SKU_MSK 0x00000070 +# define PCI_CFG_HFS_3_FW_SKU_IGN 0x00000000 # define PCI_CFG_HFS_3_FW_SKU_SPS 0x00000060 #define PCI_CFG_HFS_4 0x64 #define PCI_CFG_HFS_5 0x68 +# define GSC_CFG_HFS_5_BOOT_TYPE_MSK 0x00000003 +# define GSC_CFG_HFS_5_BOOT_TYPE_PXP 3 #define PCI_CFG_HFS_6 0x6C /* MEI registers */ @@ -138,6 +159,11 @@ /* H_D0I3C - D0I3 Control */ #define H_D0I3C 0x800 +#define H_GSC_EXT_OP_MEM_BASE_ADDR_LO_REG 0x100 +#define H_GSC_EXT_OP_MEM_BASE_ADDR_HI_REG 0x104 +#define H_GSC_EXT_OP_MEM_LIMIT_REG 0x108 +#define GSC_EXT_OP_MEM_VALID BIT(31) + /* register bits of H_CSR (Host Control Status register) */ /* Host Circular Buffer Depth - maximum number of 32-bit entries in CB */ #define H_CBD 0xFF000000 diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index d3a6c0728645..d4612c659784 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Copyright (c) 2003-2020, Intel Corporation. All rights reserved. + * Copyright (c) 2003-2022, Intel Corporation. All rights reserved. * Intel Management Engine Interface (Intel MEI) Linux driver */ @@ -10,6 +10,7 @@ #include <linux/interrupt.h> #include <linux/pm_runtime.h> #include <linux/sizes.h> +#include <linux/delay.h> #include "mei_dev.h" #include "hbm.h" @@ -83,7 +84,7 @@ static inline u32 mei_me_mecsr_read(const struct mei_device *dev) u32 reg; reg = mei_me_reg_read(to_me_hw(dev), ME_CSR_HA); - trace_mei_reg_read(dev->dev, "ME_CSR_HA", ME_CSR_HA, reg); + trace_mei_reg_read(&dev->dev, "ME_CSR_HA", ME_CSR_HA, reg); return reg; } @@ -100,7 +101,7 @@ static inline u32 mei_hcsr_read(const struct mei_device *dev) u32 reg; reg = mei_me_reg_read(to_me_hw(dev), H_CSR); - trace_mei_reg_read(dev->dev, "H_CSR", H_CSR, reg); + trace_mei_reg_read(&dev->dev, "H_CSR", H_CSR, reg); return reg; } @@ -113,7 +114,7 @@ static inline u32 mei_hcsr_read(const struct mei_device *dev) */ static inline void mei_hcsr_write(struct mei_device *dev, u32 reg) { - trace_mei_reg_write(dev->dev, "H_CSR", H_CSR, reg); + trace_mei_reg_write(&dev->dev, "H_CSR", H_CSR, reg); mei_me_reg_write(to_me_hw(dev), H_CSR, reg); } @@ -155,7 +156,7 @@ static inline u32 mei_me_d0i3c_read(const struct mei_device *dev) u32 reg; reg = mei_me_reg_read(to_me_hw(dev), H_D0I3C); - trace_mei_reg_read(dev->dev, "H_D0I3C", H_D0I3C, reg); + trace_mei_reg_read(&dev->dev, "H_D0I3C", H_D0I3C, reg); return reg; } @@ -168,7 +169,7 @@ static inline u32 mei_me_d0i3c_read(const struct mei_device *dev) */ static inline void mei_me_d0i3c_write(struct mei_device *dev, u32 reg) { - trace_mei_reg_write(dev->dev, "H_D0I3C", H_D0I3C, reg); + trace_mei_reg_write(&dev->dev, "H_D0I3C", H_D0I3C, reg); mei_me_reg_write(to_me_hw(dev), H_D0I3C, reg); } @@ -188,7 +189,7 @@ static int mei_me_trc_status(struct mei_device *dev, u32 *trc) return -EOPNOTSUPP; *trc = mei_me_reg_read(hw, ME_TRC); - trace_mei_reg_read(dev->dev, "ME_TRC", ME_TRC, *trc); + trace_mei_reg_read(&dev->dev, "ME_TRC", ME_TRC, *trc); return 0; } @@ -216,7 +217,7 @@ static int mei_me_fw_status(struct mei_device *dev, for (i = 0; i < fw_src->count && i < MEI_FW_STATUS_MAX; i++) { ret = hw->read_fws(dev, fw_src->status[i], &fw_status->status[i]); - trace_mei_pci_cfg_read(dev->dev, "PCI_CFG_HFS_X", + trace_mei_pci_cfg_read(&dev->dev, "PCI_CFG_HFS_X", fw_src->status[i], fw_status->status[i]); if (ret) @@ -250,7 +251,7 @@ static int mei_me_hw_config(struct mei_device *dev) reg = 0; hw->read_fws(dev, PCI_CFG_HFS_1, ®); - trace_mei_pci_cfg_read(dev->dev, "PCI_CFG_HFS_1", PCI_CFG_HFS_1, reg); + trace_mei_pci_cfg_read(&dev->dev, "PCI_CFG_HFS_1", PCI_CFG_HFS_1, reg); hw->d0i3_supported = ((reg & PCI_CFG_HFS_1_D0I3_MSK) == PCI_CFG_HFS_1_D0I3_MSK); @@ -327,9 +328,12 @@ static void mei_me_intr_clear(struct mei_device *dev) */ static void mei_me_intr_enable(struct mei_device *dev) { - u32 hcsr = mei_hcsr_read(dev); + u32 hcsr; + + if (mei_me_hw_use_polling(to_me_hw(dev))) + return; - hcsr |= H_CSR_IE_MASK; + hcsr = mei_hcsr_read(dev) | H_CSR_IE_MASK; mei_hcsr_set(dev, hcsr); } @@ -354,6 +358,9 @@ static void mei_me_synchronize_irq(struct mei_device *dev) { struct mei_me_hw *hw = to_me_hw(dev); + if (mei_me_hw_use_polling(hw)) + return; + synchronize_irq(hw->irq); } @@ -380,7 +387,10 @@ static void mei_me_host_set_ready(struct mei_device *dev) { u32 hcsr = mei_hcsr_read(dev); - hcsr |= H_CSR_IE_MASK | H_IG | H_RDY; + if (!mei_me_hw_use_polling(to_me_hw(dev))) + hcsr |= H_CSR_IE_MASK; + + hcsr |= H_IG | H_RDY; mei_hcsr_set(dev, hcsr); } @@ -424,6 +434,40 @@ static bool mei_me_hw_is_resetting(struct mei_device *dev) } /** + * mei_gsc_pxp_check - check for gsc firmware entering pxp mode + * + * @dev: the device structure + */ +static void mei_gsc_pxp_check(struct mei_device *dev) +{ + struct mei_me_hw *hw = to_me_hw(dev); + u32 fwsts5 = 0; + + if (!kind_is_gsc(dev) && !kind_is_gscfi(dev)) + return; + + hw->read_fws(dev, PCI_CFG_HFS_5, &fwsts5); + trace_mei_pci_cfg_read(&dev->dev, "PCI_CFG_HFS_5", PCI_CFG_HFS_5, fwsts5); + + if ((fwsts5 & GSC_CFG_HFS_5_BOOT_TYPE_MSK) == GSC_CFG_HFS_5_BOOT_TYPE_PXP) { + if (dev->gsc_reset_to_pxp == MEI_DEV_RESET_TO_PXP_DEFAULT) + dev->gsc_reset_to_pxp = MEI_DEV_RESET_TO_PXP_PERFORMED; + } else { + dev->gsc_reset_to_pxp = MEI_DEV_RESET_TO_PXP_DEFAULT; + } + + if (dev->pxp_mode == MEI_DEV_PXP_DEFAULT) + return; + + if ((fwsts5 & GSC_CFG_HFS_5_BOOT_TYPE_MSK) == GSC_CFG_HFS_5_BOOT_TYPE_PXP) { + dev_dbg(&dev->dev, "pxp mode is ready 0x%08x\n", fwsts5); + dev->pxp_mode = MEI_DEV_PXP_READY; + } else { + dev_dbg(&dev->dev, "pxp mode is not ready 0x%08x\n", fwsts5); + } +} + +/** * mei_me_hw_ready_wait - wait until the me(hw) has turned ready * or timeout is reached * @@ -435,13 +479,15 @@ static int mei_me_hw_ready_wait(struct mei_device *dev) mutex_unlock(&dev->device_lock); wait_event_timeout(dev->wait_hw_ready, dev->recvd_hw_ready, - mei_secs_to_jiffies(MEI_HW_READY_TIMEOUT)); + dev->timeouts.hw_ready); mutex_lock(&dev->device_lock); if (!dev->recvd_hw_ready) { - dev_err(dev->dev, "wait hw ready failed\n"); + dev_err(&dev->dev, "wait hw ready failed\n"); return -ETIME; } + mei_gsc_pxp_check(dev); + mei_me_hw_reset_release(dev); dev->recvd_hw_ready = false; return 0; @@ -457,9 +503,12 @@ static int mei_me_hw_start(struct mei_device *dev) { int ret = mei_me_hw_ready_wait(dev); + if ((kind_is_gsc(dev) || kind_is_gscfi(dev)) && + dev->gsc_reset_to_pxp == MEI_DEV_RESET_TO_PXP_PERFORMED) + dev->gsc_reset_to_pxp = MEI_DEV_RESET_TO_PXP_DONE; if (ret) return ret; - dev_dbg(dev->dev, "hw is ready\n"); + dev_dbg(&dev->dev, "hw is ready\n"); mei_me_host_set_ready(dev); return ret; @@ -555,13 +604,18 @@ static int mei_me_hbuf_write(struct mei_device *dev, u32 dw_cnt; int empty_slots; - if (WARN_ON(!hdr || !data || hdr_len & 0x3)) + if (WARN_ON(!hdr || hdr_len & 0x3)) return -EINVAL; - dev_dbg(dev->dev, MEI_HDR_FMT, MEI_HDR_PRM((struct mei_msg_hdr *)hdr)); + if (!data && data_len) { + dev_err(&dev->dev, "wrong parameters null data with data_len = %zu\n", data_len); + return -EINVAL; + } + + dev_dbg(&dev->dev, MEI_HDR_FMT, MEI_HDR_PRM((struct mei_msg_hdr *)hdr)); empty_slots = mei_hbuf_empty_slots(dev); - dev_dbg(dev->dev, "empty slots = %hu.\n", empty_slots); + dev_dbg(&dev->dev, "empty slots = %d.\n", empty_slots); if (empty_slots < 0) return -EOVERFLOW; @@ -616,7 +670,7 @@ static int mei_me_count_full_read_slots(struct mei_device *dev) if (filled_slots > buffer_depth) return -EOVERFLOW; - dev_dbg(dev->dev, "filled_slots =%08x\n", filled_slots); + dev_dbg(&dev->dev, "filled_slots =%08x\n", filled_slots); return (int)filled_slots; } @@ -658,11 +712,11 @@ static void mei_me_pg_set(struct mei_device *dev) u32 reg; reg = mei_me_reg_read(hw, H_HPG_CSR); - trace_mei_reg_read(dev->dev, "H_HPG_CSR", H_HPG_CSR, reg); + trace_mei_reg_read(&dev->dev, "H_HPG_CSR", H_HPG_CSR, reg); reg |= H_HPG_CSR_PGI; - trace_mei_reg_write(dev->dev, "H_HPG_CSR", H_HPG_CSR, reg); + trace_mei_reg_write(&dev->dev, "H_HPG_CSR", H_HPG_CSR, reg); mei_me_reg_write(hw, H_HPG_CSR, reg); } @@ -677,13 +731,13 @@ static void mei_me_pg_unset(struct mei_device *dev) u32 reg; reg = mei_me_reg_read(hw, H_HPG_CSR); - trace_mei_reg_read(dev->dev, "H_HPG_CSR", H_HPG_CSR, reg); + trace_mei_reg_read(&dev->dev, "H_HPG_CSR", H_HPG_CSR, reg); WARN(!(reg & H_HPG_CSR_PGI), "PGI is not set\n"); reg |= H_HPG_CSR_PGIHEXR; - trace_mei_reg_write(dev->dev, "H_HPG_CSR", H_HPG_CSR, reg); + trace_mei_reg_write(&dev->dev, "H_HPG_CSR", H_HPG_CSR, reg); mei_me_reg_write(hw, H_HPG_CSR, reg); } @@ -697,7 +751,6 @@ static void mei_me_pg_unset(struct mei_device *dev) static int mei_me_pg_legacy_enter_sync(struct mei_device *dev) { struct mei_me_hw *hw = to_me_hw(dev); - unsigned long timeout = mei_secs_to_jiffies(MEI_PGI_TIMEOUT); int ret; dev->pg_event = MEI_PG_EVENT_WAIT; @@ -708,7 +761,8 @@ static int mei_me_pg_legacy_enter_sync(struct mei_device *dev) mutex_unlock(&dev->device_lock); wait_event_timeout(dev->wait_pg, - dev->pg_event == MEI_PG_EVENT_RECEIVED, timeout); + dev->pg_event == MEI_PG_EVENT_RECEIVED, + dev->timeouts.pgi); mutex_lock(&dev->device_lock); if (dev->pg_event == MEI_PG_EVENT_RECEIVED) { @@ -734,7 +788,6 @@ static int mei_me_pg_legacy_enter_sync(struct mei_device *dev) static int mei_me_pg_legacy_exit_sync(struct mei_device *dev) { struct mei_me_hw *hw = to_me_hw(dev); - unsigned long timeout = mei_secs_to_jiffies(MEI_PGI_TIMEOUT); int ret; if (dev->pg_event == MEI_PG_EVENT_RECEIVED) @@ -746,7 +799,8 @@ static int mei_me_pg_legacy_exit_sync(struct mei_device *dev) mutex_unlock(&dev->device_lock); wait_event_timeout(dev->wait_pg, - dev->pg_event == MEI_PG_EVENT_RECEIVED, timeout); + dev->pg_event == MEI_PG_EVENT_RECEIVED, + dev->timeouts.pgi); mutex_lock(&dev->device_lock); reply: @@ -762,7 +816,8 @@ reply: mutex_unlock(&dev->device_lock); wait_event_timeout(dev->wait_pg, - dev->pg_event == MEI_PG_EVENT_INTR_RECEIVED, timeout); + dev->pg_event == MEI_PG_EVENT_INTR_RECEIVED, + dev->timeouts.pgi); mutex_lock(&dev->device_lock); if (dev->pg_event == MEI_PG_EVENT_INTR_RECEIVED) @@ -814,7 +869,7 @@ static bool mei_me_pg_is_enabled(struct mei_device *dev) return true; notsupported: - dev_dbg(dev->dev, "pg: not supported: d0i3 = %d HGP = %d hbm version %d.%d ?= %d.%d\n", + dev_dbg(&dev->dev, "pg: not supported: d0i3 = %d HGP = %d hbm version %d.%d ?= %d.%d\n", hw->d0i3_supported, !!(reg & ME_PGIC_HRA), dev->version.major_version, @@ -877,15 +932,13 @@ static u32 mei_me_d0i3_unset(struct mei_device *dev) static int mei_me_d0i3_enter_sync(struct mei_device *dev) { struct mei_me_hw *hw = to_me_hw(dev); - unsigned long d0i3_timeout = mei_secs_to_jiffies(MEI_D0I3_TIMEOUT); - unsigned long pgi_timeout = mei_secs_to_jiffies(MEI_PGI_TIMEOUT); int ret; u32 reg; reg = mei_me_d0i3c_read(dev); if (reg & H_D0I3C_I3) { /* we are in d0i3, nothing to do */ - dev_dbg(dev->dev, "d0i3 set not needed\n"); + dev_dbg(&dev->dev, "d0i3 set not needed\n"); ret = 0; goto on; } @@ -900,7 +953,8 @@ static int mei_me_d0i3_enter_sync(struct mei_device *dev) mutex_unlock(&dev->device_lock); wait_event_timeout(dev->wait_pg, - dev->pg_event == MEI_PG_EVENT_RECEIVED, pgi_timeout); + dev->pg_event == MEI_PG_EVENT_RECEIVED, + dev->timeouts.pgi); mutex_lock(&dev->device_lock); if (dev->pg_event != MEI_PG_EVENT_RECEIVED) { @@ -913,14 +967,15 @@ static int mei_me_d0i3_enter_sync(struct mei_device *dev) reg = mei_me_d0i3_set(dev, true); if (!(reg & H_D0I3C_CIP)) { - dev_dbg(dev->dev, "d0i3 enter wait not needed\n"); + dev_dbg(&dev->dev, "d0i3 enter wait not needed\n"); ret = 0; goto on; } mutex_unlock(&dev->device_lock); wait_event_timeout(dev->wait_pg, - dev->pg_event == MEI_PG_EVENT_INTR_RECEIVED, d0i3_timeout); + dev->pg_event == MEI_PG_EVENT_INTR_RECEIVED, + dev->timeouts.d0i3); mutex_lock(&dev->device_lock); if (dev->pg_event != MEI_PG_EVENT_INTR_RECEIVED) { @@ -936,7 +991,7 @@ on: hw->pg_state = MEI_PG_ON; out: dev->pg_event = MEI_PG_EVENT_IDLE; - dev_dbg(dev->dev, "d0i3 enter ret = %d\n", ret); + dev_dbg(&dev->dev, "d0i3 enter ret = %d\n", ret); return ret; } @@ -958,7 +1013,7 @@ static int mei_me_d0i3_enter(struct mei_device *dev) reg = mei_me_d0i3c_read(dev); if (reg & H_D0I3C_I3) { /* we are in d0i3, nothing to do */ - dev_dbg(dev->dev, "already d0i3 : set not needed\n"); + dev_dbg(&dev->dev, "already d0i3 : set not needed\n"); goto on; } @@ -966,7 +1021,7 @@ static int mei_me_d0i3_enter(struct mei_device *dev) on: hw->pg_state = MEI_PG_ON; dev->pg_event = MEI_PG_EVENT_IDLE; - dev_dbg(dev->dev, "d0i3 enter\n"); + dev_dbg(&dev->dev, "d0i3 enter\n"); return 0; } @@ -980,7 +1035,6 @@ on: static int mei_me_d0i3_exit_sync(struct mei_device *dev) { struct mei_me_hw *hw = to_me_hw(dev); - unsigned long timeout = mei_secs_to_jiffies(MEI_D0I3_TIMEOUT); int ret; u32 reg; @@ -989,21 +1043,22 @@ static int mei_me_d0i3_exit_sync(struct mei_device *dev) reg = mei_me_d0i3c_read(dev); if (!(reg & H_D0I3C_I3)) { /* we are not in d0i3, nothing to do */ - dev_dbg(dev->dev, "d0i3 exit not needed\n"); + dev_dbg(&dev->dev, "d0i3 exit not needed\n"); ret = 0; goto off; } reg = mei_me_d0i3_unset(dev); if (!(reg & H_D0I3C_CIP)) { - dev_dbg(dev->dev, "d0i3 exit wait not needed\n"); + dev_dbg(&dev->dev, "d0i3 exit wait not needed\n"); ret = 0; goto off; } mutex_unlock(&dev->device_lock); wait_event_timeout(dev->wait_pg, - dev->pg_event == MEI_PG_EVENT_INTR_RECEIVED, timeout); + dev->pg_event == MEI_PG_EVENT_INTR_RECEIVED, + dev->timeouts.d0i3); mutex_lock(&dev->device_lock); if (dev->pg_event != MEI_PG_EVENT_INTR_RECEIVED) { @@ -1020,7 +1075,7 @@ off: out: dev->pg_event = MEI_PG_EVENT_IDLE; - dev_dbg(dev->dev, "d0i3 exit ret = %d\n", ret); + dev_dbg(&dev->dev, "d0i3 exit ret = %d\n", ret); return ret; } @@ -1063,7 +1118,7 @@ static void mei_me_d0i3_intr(struct mei_device *dev, u32 intr_source) * force H_RDY because it could be * wiped off during PG */ - dev_dbg(dev->dev, "d0i3 set host ready\n"); + dev_dbg(&dev->dev, "d0i3 set host ready\n"); mei_me_host_set_ready(dev); } } else { @@ -1079,7 +1134,7 @@ static void mei_me_d0i3_intr(struct mei_device *dev, u32 intr_source) * we got here because of HW initiated exit from D0i3. * Start runtime pm resume sequence to exit low power state. */ - dev_dbg(dev->dev, "d0i3 want resume\n"); + dev_dbg(&dev->dev, "d0i3 want resume\n"); mei_hbm_pg_resume(dev); } } @@ -1154,10 +1209,12 @@ static int mei_me_hw_reset(struct mei_device *dev, bool intr_enable) ret = mei_me_d0i3_exit_sync(dev); if (ret) return ret; + } else { + hw->pg_state = MEI_PG_OFF; } } - pm_runtime_set_active(dev->dev); + pm_runtime_set_active(dev->parent); hcsr = mei_hcsr_read(dev); /* H_RST may be found lit before reset is started, @@ -1166,7 +1223,7 @@ static int mei_me_hw_reset(struct mei_device *dev, bool intr_enable) * we need to clean H_RST bit to start a successful reset sequence. */ if ((hcsr & H_RST) == H_RST) { - dev_warn(dev->dev, "H_RST is set = 0x%08X", hcsr); + dev_warn(&dev->dev, "H_RST is set = 0x%08X", hcsr); hcsr &= ~H_RST; mei_hcsr_set(dev, hcsr); hcsr = mei_hcsr_read(dev); @@ -1174,7 +1231,7 @@ static int mei_me_hw_reset(struct mei_device *dev, bool intr_enable) hcsr |= H_RST | H_IG | H_CSR_IS_MASK; - if (!intr_enable) + if (!intr_enable || mei_me_hw_use_polling(to_me_hw(dev))) hcsr &= ~H_CSR_IE_MASK; dev->recvd_hw_ready = false; @@ -1187,10 +1244,10 @@ static int mei_me_hw_reset(struct mei_device *dev, bool intr_enable) hcsr = mei_hcsr_read(dev); if ((hcsr & H_RST) == 0) - dev_warn(dev->dev, "H_RST is not set = 0x%08X", hcsr); + dev_warn(&dev->dev, "H_RST is not set = 0x%08X", hcsr); if ((hcsr & H_RDY) == H_RDY) - dev_warn(dev->dev, "H_RDY is not cleared 0x%08X", hcsr); + dev_warn(&dev->dev, "H_RDY is not cleared 0x%08X", hcsr); if (!intr_enable) { mei_me_hw_reset_release(dev); @@ -1220,12 +1277,13 @@ irqreturn_t mei_me_irq_quick_handler(int irq, void *dev_id) if (!me_intr_src(hcsr)) return IRQ_NONE; - dev_dbg(dev->dev, "interrupt source 0x%08X\n", me_intr_src(hcsr)); + dev_dbg(&dev->dev, "interrupt source 0x%08X\n", me_intr_src(hcsr)); /* disable interrupts on device */ me_intr_disable(dev, hcsr); return IRQ_WAKE_THREAD; } +EXPORT_SYMBOL_GPL(mei_me_irq_quick_handler); /** * mei_me_irq_thread_handler - function called after ISR to handle the interrupt @@ -1245,7 +1303,7 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) u32 hcsr; int rets = 0; - dev_dbg(dev->dev, "function called after ISR to handle the interrupt processing.\n"); + dev_dbg(&dev->dev, "function called after ISR to handle the interrupt processing.\n"); /* initialize our complete list */ mutex_lock(&dev->device_lock); @@ -1256,8 +1314,18 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) /* check if ME wants a reset */ if (!mei_hw_is_ready(dev) && dev->dev_state != MEI_DEV_RESETTING) { - dev_warn(dev->dev, "FW not ready: resetting.\n"); - schedule_work(&dev->reset_work); + if (kind_is_gsc(dev) || kind_is_gscfi(dev)) { + dev_dbg(&dev->dev, "FW not ready: resetting: dev_state = %d\n", + dev->dev_state); + } else { + dev_warn(&dev->dev, "FW not ready: resetting: dev_state = %d\n", + dev->dev_state); + } + if (dev->dev_state == MEI_DEV_POWERING_DOWN || + dev->dev_state == MEI_DEV_POWER_DOWN) + mei_cl_all_disconnect(dev); + else if (dev->dev_state != MEI_DEV_DISABLED) + schedule_work(&dev->reset_work); goto end; } @@ -1269,18 +1337,29 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) /* check if we need to start the dev */ if (!mei_host_is_ready(dev)) { if (mei_hw_is_ready(dev)) { - dev_dbg(dev->dev, "we need to start the dev.\n"); - dev->recvd_hw_ready = true; - wake_up(&dev->wait_hw_ready); + /* synchronized by dev mutex */ + if (waitqueue_active(&dev->wait_hw_ready)) { + dev_dbg(&dev->dev, "we need to start the dev.\n"); + dev->recvd_hw_ready = true; + wake_up(&dev->wait_hw_ready); + } else if (dev->dev_state != MEI_DEV_UNINITIALIZED && + dev->dev_state != MEI_DEV_POWERING_DOWN && + dev->dev_state != MEI_DEV_POWER_DOWN) { + dev_dbg(&dev->dev, "Force link reset.\n"); + schedule_work(&dev->reset_work); + } else { + dev_dbg(&dev->dev, "Ignore this interrupt in state = %d\n", + dev->dev_state); + } } else { - dev_dbg(dev->dev, "Spurious Interrupt\n"); + dev_dbg(&dev->dev, "Spurious Interrupt\n"); } goto end; } /* check slots available for reading */ slots = mei_count_full_read_slots(dev); while (slots > 0) { - dev_dbg(dev->dev, "slots to read = %08x\n", slots); + dev_dbg(&dev->dev, "slots to read = %08x\n", slots); rets = mei_irq_read_handler(dev, &cmpl_list, &slots); /* There is a race between ME write and interrupt delivery: * Not all data is always available immediately after the @@ -1289,12 +1368,14 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) if (rets == -ENODATA) break; - if (rets && - (dev->dev_state != MEI_DEV_RESETTING && - dev->dev_state != MEI_DEV_POWER_DOWN)) { - dev_err(dev->dev, "mei_irq_read_handler ret = %d.\n", - rets); - schedule_work(&dev->reset_work); + if (rets) { + dev_err(&dev->dev, "mei_irq_read_handler ret = %d, state = %d.\n", + rets, dev->dev_state); + if (dev->dev_state != MEI_DEV_RESETTING && + dev->dev_state != MEI_DEV_DISABLED && + dev->dev_state != MEI_DEV_POWERING_DOWN && + dev->dev_state != MEI_DEV_POWER_DOWN) + schedule_work(&dev->reset_work); goto end; } } @@ -1315,11 +1396,72 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) mei_irq_compl_handler(dev, &cmpl_list); end: - dev_dbg(dev->dev, "interrupt thread end ret = %d\n", rets); + dev_dbg(&dev->dev, "interrupt thread end ret = %d\n", rets); mei_me_intr_enable(dev); mutex_unlock(&dev->device_lock); return IRQ_HANDLED; } +EXPORT_SYMBOL_GPL(mei_me_irq_thread_handler); + +#define MEI_POLLING_TIMEOUT_ACTIVE 100 +#define MEI_POLLING_TIMEOUT_IDLE 500 + +/** + * mei_me_polling_thread - interrupt register polling thread + * + * @_dev: mei device + * + * The thread monitors the interrupt source register and calls + * mei_me_irq_thread_handler() to handle the firmware + * input. + * + * The function polls in MEI_POLLING_TIMEOUT_ACTIVE timeout + * in case there was an event, in idle case the polling + * time increases yet again by MEI_POLLING_TIMEOUT_ACTIVE + * up to MEI_POLLING_TIMEOUT_IDLE. + * + * Return: always 0 + */ +int mei_me_polling_thread(void *_dev) +{ + struct mei_device *dev = _dev; + irqreturn_t irq_ret; + long polling_timeout = MEI_POLLING_TIMEOUT_ACTIVE; + + dev_dbg(&dev->dev, "kernel thread is running\n"); + while (!kthread_should_stop()) { + struct mei_me_hw *hw = to_me_hw(dev); + u32 hcsr; + + wait_event_timeout(hw->wait_active, + hw->is_active || kthread_should_stop(), + msecs_to_jiffies(MEI_POLLING_TIMEOUT_IDLE)); + + if (kthread_should_stop()) + break; + + hcsr = mei_hcsr_read(dev); + if (me_intr_src(hcsr)) { + polling_timeout = MEI_POLLING_TIMEOUT_ACTIVE; + irq_ret = mei_me_irq_thread_handler(1, dev); + if (irq_ret != IRQ_HANDLED) + dev_err(&dev->dev, "irq_ret %d\n", irq_ret); + } else { + /* + * Increase timeout by MEI_POLLING_TIMEOUT_ACTIVE + * up to MEI_POLLING_TIMEOUT_IDLE + */ + polling_timeout = clamp_val(polling_timeout + MEI_POLLING_TIMEOUT_ACTIVE, + MEI_POLLING_TIMEOUT_ACTIVE, + MEI_POLLING_TIMEOUT_IDLE); + } + + schedule_timeout_interruptible(msecs_to_jiffies(polling_timeout)); + } + + return 0; +} +EXPORT_SYMBOL_GPL(mei_me_polling_thread); static const struct mei_hw_ops mei_me_hw_ops = { @@ -1356,12 +1498,12 @@ static const struct mei_hw_ops mei_me_hw_ops = { /** * mei_me_fw_type_nm() - check for nm sku * + * @pdev: pci device + * * Read ME FW Status register to check for the Node Manager (NM) Firmware. * The NM FW is only signaled in PCI function 0. * __Note__: Deprecated by PCH8 and newer. * - * @pdev: pci device - * * Return: true in case of NM firmware */ static bool mei_me_fw_type_nm(const struct pci_dev *pdev) @@ -1382,12 +1524,12 @@ static bool mei_me_fw_type_nm(const struct pci_dev *pdev) /** * mei_me_fw_type_sps_4() - check for sps 4.0 sku * + * @pdev: pci device + * * Read ME FW Status register to check for SPS Firmware. * The SPS FW is only signaled in the PCI function 0. * __Note__: Deprecated by SPS 5.0 and newer. * - * @pdev: pci device - * * Return: true in case of SPS firmware */ static bool mei_me_fw_type_sps_4(const struct pci_dev *pdev) @@ -1405,16 +1547,16 @@ static bool mei_me_fw_type_sps_4(const struct pci_dev *pdev) .quirk_probe = mei_me_fw_type_sps_4 /** - * mei_me_fw_type_sps() - check for sps sku - * - * Read ME FW Status register to check for SPS Firmware. - * The SPS FW is only signaled in pci function 0 + * mei_me_fw_type_sps_ign() - check for sps or ign sku * * @pdev: pci device * - * Return: true in case of SPS firmware + * Read ME FW Status register to check for SPS or IGN Firmware. + * The SPS/IGN FW is only signaled in pci function 0 + * + * Return: true in case of SPS/IGN firmware */ -static bool mei_me_fw_type_sps(const struct pci_dev *pdev) +static bool mei_me_fw_type_sps_ign(const struct pci_dev *pdev) { u32 reg; u32 fw_type; @@ -1427,14 +1569,21 @@ static bool mei_me_fw_type_sps(const struct pci_dev *pdev) dev_dbg(&pdev->dev, "fw type is %d\n", fw_type); - return fw_type == PCI_CFG_HFS_3_FW_SKU_SPS; + return fw_type == PCI_CFG_HFS_3_FW_SKU_IGN || + fw_type == PCI_CFG_HFS_3_FW_SKU_SPS; } #define MEI_CFG_KIND_ITOUCH \ .kind = "itouch" -#define MEI_CFG_FW_SPS \ - .quirk_probe = mei_me_fw_type_sps +#define MEI_CFG_TYPE_GSC \ + .kind = "gsc" + +#define MEI_CFG_TYPE_GSCFI \ + .kind = "gscfi" + +#define MEI_CFG_FW_SPS_IGN \ + .quirk_probe = mei_me_fw_type_sps_ign #define MEI_CFG_FW_VER_SUPP \ .fw_ver_supported = 1 @@ -1535,7 +1684,7 @@ static const struct mei_cfg mei_me_pch12_sps_cfg = { MEI_CFG_PCH8_HFS, MEI_CFG_FW_VER_SUPP, MEI_CFG_DMA_128, - MEI_CFG_FW_SPS, + MEI_CFG_FW_SPS_IGN, }; /* Cannon Lake itouch with quirk for SPS 5.0 and newer Firmware exclusion @@ -1545,7 +1694,7 @@ static const struct mei_cfg mei_me_pch12_itouch_sps_cfg = { MEI_CFG_KIND_ITOUCH, MEI_CFG_PCH8_HFS, MEI_CFG_FW_VER_SUPP, - MEI_CFG_FW_SPS, + MEI_CFG_FW_SPS_IGN, }; /* Tiger Lake and newer devices */ @@ -1562,7 +1711,21 @@ static const struct mei_cfg mei_me_pch15_sps_cfg = { MEI_CFG_FW_VER_SUPP, MEI_CFG_DMA_128, MEI_CFG_TRC, - MEI_CFG_FW_SPS, + MEI_CFG_FW_SPS_IGN, +}; + +/* Graphics System Controller */ +static const struct mei_cfg mei_me_gsc_cfg = { + MEI_CFG_TYPE_GSC, + MEI_CFG_PCH8_HFS, + MEI_CFG_FW_VER_SUPP, +}; + +/* Graphics System Controller Firmware Interface */ +static const struct mei_cfg mei_me_gscfi_cfg = { + MEI_CFG_TYPE_GSCFI, + MEI_CFG_PCH8_HFS, + MEI_CFG_FW_VER_SUPP, }; /* @@ -1585,6 +1748,8 @@ static const struct mei_cfg *const mei_cfg_list[] = { [MEI_ME_PCH12_SPS_ITOUCH_CFG] = &mei_me_pch12_itouch_sps_cfg, [MEI_ME_PCH15_CFG] = &mei_me_pch15_cfg, [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, }; const struct mei_cfg *mei_me_get_cfg(kernel_ulong_t idx) @@ -1595,24 +1760,26 @@ const struct mei_cfg *mei_me_get_cfg(kernel_ulong_t idx) return NULL; return mei_cfg_list[idx]; -}; +} +EXPORT_SYMBOL_GPL(mei_me_get_cfg); /** * mei_me_dev_init - allocates and initializes the mei device structure * * @parent: device associated with physical device (pci/platform) * @cfg: per device generation config + * @slow_fw: configure longer timeouts as FW is slow * * Return: The mei_device pointer on success, NULL on failure. */ struct mei_device *mei_me_dev_init(struct device *parent, - const struct mei_cfg *cfg) + const struct mei_cfg *cfg, bool slow_fw) { struct mei_device *dev; struct mei_me_hw *hw; int i; - dev = devm_kzalloc(parent, sizeof(*dev) + sizeof(*hw), GFP_KERNEL); + dev = kzalloc(sizeof(*dev) + sizeof(*hw), GFP_KERNEL); if (!dev) return NULL; @@ -1621,7 +1788,7 @@ struct mei_device *mei_me_dev_init(struct device *parent, for (i = 0; i < DMA_DSCR_NUM; i++) dev->dr_dscr[i].size = cfg->dma_size[i]; - mei_device_init(dev, parent, &mei_me_hw_ops); + mei_device_init(dev, parent, slow_fw, &mei_me_hw_ops); hw->cfg = cfg; dev->fw_f_fw_ver_supported = cfg->fw_ver_supported; @@ -1630,4 +1797,4 @@ struct mei_device *mei_me_dev_init(struct device *parent, return dev; } - +EXPORT_SYMBOL_GPL(mei_me_dev_init); diff --git a/drivers/misc/mei/hw-me.h b/drivers/misc/mei/hw-me.h index 00a7132ac7a2..204b92af6c47 100644 --- a/drivers/misc/mei/hw-me.h +++ b/drivers/misc/mei/hw-me.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* - * Copyright (c) 2012-2020, Intel Corporation. All rights reserved. + * Copyright (c) 2012-2022, Intel Corporation. All rights reserved. * Intel Management Engine Interface (Intel MEI) Linux driver */ @@ -51,6 +51,9 @@ struct mei_cfg { * @d0i3_supported: di03 support * @hbuf_depth: depth of hardware host/write buffer in slots * @read_fws: read FW status register handler + * @polling_thread: interrupt polling thread + * @wait_active: the polling thread activity wait queue + * @is_active: the device is active */ struct mei_me_hw { const struct mei_cfg *cfg; @@ -60,10 +63,19 @@ struct mei_me_hw { bool d0i3_supported; u8 hbuf_depth; int (*read_fws)(const struct mei_device *dev, int where, u32 *val); + /* polling */ + struct task_struct *polling_thread; + wait_queue_head_t wait_active; + bool is_active; }; #define to_me_hw(dev) (struct mei_me_hw *)((dev)->hw) +static inline bool mei_me_hw_use_polling(const struct mei_me_hw *hw) +{ + return hw->irq < 0; +} + /** * enum mei_cfg_idx - indices to platform specific configurations. * @@ -90,10 +102,14 @@ struct mei_me_hw { * @MEI_ME_PCH12_SPS_CFG: Platform Controller Hub Gen12 5.0 and newer * servers platforms with quirk for * SPS firmware exclusion. + * @MEI_ME_PCH12_SPS_ITOUCH_CFG: Platform Controller Hub Gen12 + * client platforms (iTouch) * @MEI_ME_PCH15_CFG: Platform Controller Hub Gen15 and newer * @MEI_ME_PCH15_SPS_CFG: Platform Controller Hub Gen15 and newer * servers platforms with quirk for * SPS firmware exclusion. + * @MEI_ME_GSC_CFG: Graphics System Controller + * @MEI_ME_GSCFI_CFG: Graphics System Controller Firmware Interface * @MEI_ME_NUM_CFG: Upper Sentinel. */ enum mei_cfg_idx { @@ -112,18 +128,21 @@ enum mei_cfg_idx { MEI_ME_PCH12_SPS_ITOUCH_CFG, MEI_ME_PCH15_CFG, MEI_ME_PCH15_SPS_CFG, + MEI_ME_GSC_CFG, + MEI_ME_GSCFI_CFG, MEI_ME_NUM_CFG, }; const struct mei_cfg *mei_me_get_cfg(kernel_ulong_t idx); struct mei_device *mei_me_dev_init(struct device *parent, - const struct mei_cfg *cfg); + const struct mei_cfg *cfg, bool slow_fw); int mei_me_pg_enter_sync(struct mei_device *dev); int mei_me_pg_exit_sync(struct mei_device *dev); irqreturn_t mei_me_irq_quick_handler(int irq, void *dev_id); irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id); +int mei_me_polling_thread(void *_dev); #endif /* _MEI_INTERFACE_H_ */ diff --git a/drivers/misc/mei/hw-txe.c b/drivers/misc/mei/hw-txe.c index a4e854b9b9e6..e4688c391027 100644 --- a/drivers/misc/mei/hw-txe.c +++ b/drivers/misc/mei/hw-txe.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Copyright (c) 2013-2020, Intel Corporation. All rights reserved. + * Copyright (c) 2013-2022, Intel Corporation. All rights reserved. * Intel Management Engine Interface (Intel MEI) Linux driver */ @@ -160,7 +160,7 @@ static bool mei_txe_aliveness_set(struct mei_device *dev, u32 req) struct mei_txe_hw *hw = to_txe_hw(dev); bool do_req = hw->aliveness != req; - dev_dbg(dev->dev, "Aliveness current=%d request=%d\n", + dev_dbg(&dev->dev, "Aliveness current=%d request=%d\n", hw->aliveness, req); if (do_req) { dev->pg_event = MEI_PG_EVENT_WAIT; @@ -176,7 +176,7 @@ static bool mei_txe_aliveness_set(struct mei_device *dev, u32 req) * @dev: the device structure * * Extract HICR_HOST_ALIVENESS_RESP_ACK bit from - * from HICR_HOST_ALIVENESS_REQ register value + * HICR_HOST_ALIVENESS_REQ register value * * Return: SICR_HOST_ALIVENESS_REQ_REQUESTED bit value */ @@ -227,7 +227,7 @@ static int mei_txe_aliveness_poll(struct mei_device *dev, u32 expected) hw->aliveness = mei_txe_aliveness_get(dev); if (hw->aliveness == expected) { dev->pg_event = MEI_PG_EVENT_IDLE; - dev_dbg(dev->dev, "aliveness settled after %lld usecs\n", + dev_dbg(&dev->dev, "aliveness settled after %lld usecs\n", ktime_to_us(ktime_sub(ktime_get(), start))); return 0; } @@ -235,7 +235,7 @@ static int mei_txe_aliveness_poll(struct mei_device *dev, u32 expected) } while (ktime_before(ktime_get(), stop)); dev->pg_event = MEI_PG_EVENT_IDLE; - dev_err(dev->dev, "aliveness timed out\n"); + dev_err(&dev->dev, "aliveness timed out\n"); return -ETIME; } @@ -270,10 +270,10 @@ static int mei_txe_aliveness_wait(struct mei_device *dev, u32 expected) ret = hw->aliveness == expected ? 0 : -ETIME; if (ret) - dev_warn(dev->dev, "aliveness timed out = %ld aliveness = %d event = %d\n", + dev_warn(&dev->dev, "aliveness timed out = %ld aliveness = %d event = %d\n", err, hw->aliveness, dev->pg_event); else - dev_dbg(dev->dev, "aliveness settled after = %d msec aliveness = %d event = %d\n", + dev_dbg(&dev->dev, "aliveness settled after = %d msec aliveness = %d event = %d\n", jiffies_to_msecs(timeout - err), hw->aliveness, dev->pg_event); @@ -438,7 +438,7 @@ static void mei_txe_intr_enable(struct mei_device *dev) */ static void mei_txe_synchronize_irq(struct mei_device *dev) { - struct pci_dev *pdev = to_pci_dev(dev->dev); + struct pci_dev *pdev = to_pci_dev(dev->parent); synchronize_irq(pdev->irq); } @@ -464,7 +464,7 @@ static bool mei_txe_pending_interrupts(struct mei_device *dev) TXE_INTR_OUT_DB)); if (ret) { - dev_dbg(dev->dev, + dev_dbg(&dev->dev, "Pending Interrupts InReady=%01d Readiness=%01d, Aliveness=%01d, OutDoor=%01d\n", !!(hw->intr_cause & TXE_INTR_IN_READY), !!(hw->intr_cause & TXE_INTR_READINESS), @@ -612,7 +612,7 @@ static int mei_txe_readiness_wait(struct mei_device *dev) msecs_to_jiffies(SEC_RESET_WAIT_TIMEOUT)); mutex_lock(&dev->device_lock); if (!dev->recvd_hw_ready) { - dev_err(dev->dev, "wait for readiness failed\n"); + dev_err(&dev->dev, "wait for readiness failed\n"); return -ETIME; } @@ -638,7 +638,7 @@ static int mei_txe_fw_status(struct mei_device *dev, struct mei_fw_status *fw_status) { const struct mei_fw_status *fw_src = &mei_txe_fw_sts; - struct pci_dev *pdev = to_pci_dev(dev->dev); + struct pci_dev *pdev = to_pci_dev(dev->parent); int ret; int i; @@ -649,7 +649,7 @@ static int mei_txe_fw_status(struct mei_device *dev, for (i = 0; i < fw_src->count && i < MEI_FW_STATUS_MAX; i++) { ret = pci_read_config_dword(pdev, fw_src->status[i], &fw_status->status[i]); - trace_mei_pci_cfg_read(dev->dev, "PCI_CFG_HSF_X", + trace_mei_pci_cfg_read(&dev->dev, "PCI_CFG_HSF_X", fw_src->status[i], fw_status->status[i]); if (ret) @@ -677,7 +677,7 @@ static int mei_txe_hw_config(struct mei_device *dev) hw->aliveness = mei_txe_aliveness_get(dev); hw->readiness = mei_txe_readiness_get(dev); - dev_dbg(dev->dev, "aliveness_resp = 0x%08x, readiness = 0x%08x.\n", + dev_dbg(&dev->dev, "aliveness_resp = 0x%08x, readiness = 0x%08x.\n", hw->aliveness, hw->readiness); return 0; @@ -708,7 +708,7 @@ static int mei_txe_write(struct mei_device *dev, if (WARN_ON(!hdr || !data || hdr_len & 0x3)) return -EINVAL; - dev_dbg(dev->dev, MEI_HDR_FMT, MEI_HDR_PRM((struct mei_msg_hdr *)hdr)); + dev_dbg(&dev->dev, MEI_HDR_FMT, MEI_HDR_PRM((struct mei_msg_hdr *)hdr)); dw_cnt = mei_data2slots(hdr_len + data_len); if (dw_cnt > slots) @@ -724,7 +724,7 @@ static int mei_txe_write(struct mei_device *dev, char fw_sts_str[MEI_FW_STATUS_STR_SZ]; mei_fw_status_str(dev, fw_sts_str, MEI_FW_STATUS_STR_SZ); - dev_err(dev->dev, "Input is not ready %s\n", fw_sts_str); + dev_err(&dev->dev, "Input is not ready %s\n", fw_sts_str); return -EAGAIN; } @@ -828,13 +828,13 @@ static int mei_txe_read(struct mei_device *dev, reg_buf = (u32 *)buf; rem = len & 0x3; - dev_dbg(dev->dev, "buffer-length = %lu buf[0]0x%08X\n", + dev_dbg(&dev->dev, "buffer-length = %lu buf[0]0x%08X\n", len, mei_txe_out_data_read(dev, 0)); for (i = 0; i < len / MEI_SLOT_SIZE; i++) { /* skip header: index starts from 1 */ reg = mei_txe_out_data_read(dev, i + 1); - dev_dbg(dev->dev, "buf[%d] = 0x%08X\n", i, reg); + dev_dbg(&dev->dev, "buf[%d] = 0x%08X\n", i, reg); *reg_buf++ = reg; } @@ -879,7 +879,7 @@ static int mei_txe_hw_reset(struct mei_device *dev, bool intr_enable) */ if (aliveness_req != hw->aliveness) if (mei_txe_aliveness_poll(dev, aliveness_req) < 0) { - dev_err(dev->dev, "wait for aliveness settle failed ... bailing out\n"); + dev_err(&dev->dev, "wait for aliveness settle failed ... bailing out\n"); return -EIO; } @@ -889,7 +889,7 @@ static int mei_txe_hw_reset(struct mei_device *dev, bool intr_enable) if (aliveness_req) { mei_txe_aliveness_set(dev, 0); if (mei_txe_aliveness_poll(dev, 0) < 0) { - dev_err(dev->dev, "wait for aliveness failed ... bailing out\n"); + dev_err(&dev->dev, "wait for aliveness failed ... bailing out\n"); return -EIO; } } @@ -921,7 +921,7 @@ static int mei_txe_hw_start(struct mei_device *dev) ret = mei_txe_readiness_wait(dev); if (ret < 0) { - dev_err(dev->dev, "waiting for readiness failed\n"); + dev_err(&dev->dev, "waiting for readiness failed\n"); return ret; } @@ -937,11 +937,11 @@ static int mei_txe_hw_start(struct mei_device *dev) ret = mei_txe_aliveness_set_sync(dev, 1); if (ret < 0) { - dev_err(dev->dev, "wait for aliveness failed ... bailing out\n"); + dev_err(&dev->dev, "wait for aliveness failed ... bailing out\n"); return ret; } - pm_runtime_set_active(dev->dev); + pm_runtime_set_active(dev->parent); /* enable input ready interrupts: * SEC_IPC_HOST_INT_MASK.IPC_INPUT_READY_INT_MASK @@ -994,11 +994,7 @@ static bool mei_txe_check_and_ack_intrs(struct mei_device *dev, bool do_ack) hhisr &= ~IPC_HHIER_SEC; } - generated = generated || - (hisr & HISR_INT_STS_MSK) || - (ipc_isr & SEC_IPC_HOST_INT_STATUS_PENDING); - - if (generated && do_ack) { + if (do_ack) { /* Save the interrupt causes */ hw->intr_cause |= hisr & HISR_INT_STS_MSK; if (ipc_isr & SEC_IPC_HOST_INT_STATUS_IN_RDY) @@ -1053,7 +1049,7 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id) s32 slots; int rets = 0; - dev_dbg(dev->dev, "irq thread: Interrupt Registers HHISR|HISR|SEC=%02X|%04X|%02X\n", + dev_dbg(&dev->dev, "irq thread: Interrupt Registers HHISR|HISR|SEC=%02X|%04X|%02X\n", mei_txe_br_reg_read(hw, HHISR_REG), mei_txe_br_reg_read(hw, HISR_REG), mei_txe_sec_reg_read_silent(hw, SEC_IPC_HOST_INT_STATUS_REG)); @@ -1063,7 +1059,7 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id) mutex_lock(&dev->device_lock); INIT_LIST_HEAD(&cmpl_list); - if (pci_dev_msi_enabled(to_pci_dev(dev->dev))) + if (pci_dev_msi_enabled(to_pci_dev(dev->parent))) mei_txe_check_and_ack_intrs(dev, true); /* show irq events */ @@ -1077,17 +1073,17 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id) * or TXE driver resetting the HECI interface. */ if (test_and_clear_bit(TXE_INTR_READINESS_BIT, &hw->intr_cause)) { - dev_dbg(dev->dev, "Readiness Interrupt was received...\n"); + dev_dbg(&dev->dev, "Readiness Interrupt was received...\n"); /* Check if SeC is going through reset */ if (mei_txe_readiness_is_sec_rdy(hw->readiness)) { - dev_dbg(dev->dev, "we need to start the dev.\n"); + dev_dbg(&dev->dev, "we need to start the dev.\n"); dev->recvd_hw_ready = true; } else { dev->recvd_hw_ready = false; if (dev->dev_state != MEI_DEV_RESETTING) { - dev_warn(dev->dev, "FW not ready: resetting.\n"); + dev_warn(&dev->dev, "FW not ready: resetting.\n"); schedule_work(&dev->reset_work); goto end; @@ -1104,7 +1100,7 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id) if (test_and_clear_bit(TXE_INTR_ALIVENESS_BIT, &hw->intr_cause)) { /* Clear the interrupt cause */ - dev_dbg(dev->dev, + dev_dbg(&dev->dev, "Aliveness Interrupt: Status: %d\n", hw->aliveness); dev->pg_event = MEI_PG_EVENT_RECEIVED; if (waitqueue_active(&hw->wait_aliveness_resp)) @@ -1122,7 +1118,7 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id) if (rets && (dev->dev_state != MEI_DEV_RESETTING && dev->dev_state != MEI_DEV_POWER_DOWN)) { - dev_err(dev->dev, + dev_err(&dev->dev, "mei_irq_read_handler ret = %d.\n", rets); schedule_work(&dev->reset_work); @@ -1140,7 +1136,7 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id) dev->hbuf_is_ready = mei_hbuf_is_ready(dev); rets = mei_irq_write_handler(dev, &cmpl_list); if (rets && rets != -EMSGSIZE) - dev_err(dev->dev, "mei_irq_write_handler ret = %d.\n", + dev_err(&dev->dev, "mei_irq_write_handler ret = %d.\n", rets); dev->hbuf_is_ready = mei_hbuf_is_ready(dev); } @@ -1148,7 +1144,7 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id) mei_irq_compl_handler(dev, &cmpl_list); end: - dev_dbg(dev->dev, "interrupt thread end ret = %d\n", rets); + dev_dbg(&dev->dev, "interrupt thread end ret = %d\n", rets); mutex_unlock(&dev->device_lock); @@ -1201,11 +1197,11 @@ struct mei_device *mei_txe_dev_init(struct pci_dev *pdev) struct mei_device *dev; struct mei_txe_hw *hw; - dev = devm_kzalloc(&pdev->dev, sizeof(*dev) + sizeof(*hw), GFP_KERNEL); + dev = kzalloc(sizeof(*dev) + sizeof(*hw), GFP_KERNEL); if (!dev) return NULL; - mei_device_init(dev, &pdev->dev, &mei_txe_hw_ops); + mei_device_init(dev, &pdev->dev, false, &mei_txe_hw_ops); hw = to_txe_hw(dev); @@ -1213,48 +1209,3 @@ struct mei_device *mei_txe_dev_init(struct pci_dev *pdev) return dev; } - -/** - * mei_txe_setup_satt2 - SATT2 configuration for DMA support. - * - * @dev: the device structure - * @addr: physical address start of the range - * @range: physical range size - * - * Return: 0 on success an error code otherwise - */ -int mei_txe_setup_satt2(struct mei_device *dev, phys_addr_t addr, u32 range) -{ - struct mei_txe_hw *hw = to_txe_hw(dev); - - u32 lo32 = lower_32_bits(addr); - u32 hi32 = upper_32_bits(addr); - u32 ctrl; - - /* SATT is limited to 36 Bits */ - if (hi32 & ~0xF) - return -EINVAL; - - /* SATT has to be 16Byte aligned */ - if (lo32 & 0xF) - return -EINVAL; - - /* SATT range has to be 4Bytes aligned */ - if (range & 0x4) - return -EINVAL; - - /* SATT is limited to 32 MB range*/ - if (range > SATT_RANGE_MAX) - return -EINVAL; - - ctrl = SATT2_CTRL_VALID_MSK; - ctrl |= hi32 << SATT2_CTRL_BR_BASE_ADDR_REG_SHIFT; - - mei_txe_br_reg_write(hw, SATT2_SAP_SIZE_REG, range); - mei_txe_br_reg_write(hw, SATT2_BRG_BA_LSB_REG, lo32); - mei_txe_br_reg_write(hw, SATT2_CTRL_REG, ctrl); - dev_dbg(dev->dev, "SATT2: SAP_SIZE_OFFSET=0x%08X, BRG_BA_LSB_OFFSET=0x%08X, CTRL_OFFSET=0x%08X\n", - range, lo32, ctrl); - - return 0; -} diff --git a/drivers/misc/mei/hw-txe.h b/drivers/misc/mei/hw-txe.h index 96511b04bf88..6790e646895d 100644 --- a/drivers/misc/mei/hw-txe.h +++ b/drivers/misc/mei/hw-txe.h @@ -59,7 +59,5 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id); int mei_txe_aliveness_set_sync(struct mei_device *dev, u32 req); -int mei_txe_setup_satt2(struct mei_device *dev, phys_addr_t addr, u32 range); - #endif /* _MEI_HW_TXE_H_ */ diff --git a/drivers/misc/mei/hw.h b/drivers/misc/mei/hw.h index dfd60c916da0..3771aa09c592 100644 --- a/drivers/misc/mei/hw.h +++ b/drivers/misc/mei/hw.h @@ -1,13 +1,13 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* - * Copyright (c) 2003-2020, Intel Corporation. All rights reserved + * Copyright (c) 2003-2022, Intel Corporation. All rights reserved * Intel Management Engine Interface (Intel MEI) Linux driver */ #ifndef _MEI_HW_TYPES_H_ #define _MEI_HW_TYPES_H_ -#include <linux/uuid.h> +#include <linux/mei.h> /* * Timeouts in Seconds @@ -16,11 +16,23 @@ #define MEI_CONNECT_TIMEOUT 3 /* HPS: at least 2 seconds */ #define MEI_CL_CONNECT_TIMEOUT 15 /* HPS: Client Connect Timeout */ +#define MEI_CL_CONNECT_TIMEOUT_SLOW 30 /* HPS: Client Connect Timeout, slow FW */ #define MEI_CLIENTS_INIT_TIMEOUT 15 /* HPS: Clients Enumeration Timeout */ #define MEI_PGI_TIMEOUT 1 /* PG Isolation time response 1 sec */ #define MEI_D0I3_TIMEOUT 5 /* D0i3 set/unset max response time */ #define MEI_HBM_TIMEOUT 1 /* 1 second */ +#define MEI_HBM_TIMEOUT_SLOW 5 /* 5 second, slow FW */ + +#define MKHI_RCV_TIMEOUT 500 /* receive timeout in msec */ +#define MKHI_RCV_TIMEOUT_SLOW 10000 /* receive timeout in msec, slow FW */ + +#define MEI_LINK_RESET_WAIT_TIMEOUT_MSEC 500 /* Max wait timeout for link reset, in msec */ + +/* + * FW page size for DMA allocations + */ +#define MEI_FW_PAGE_SIZE 4096UL /* * MEI Version @@ -83,6 +95,12 @@ #define HBM_MAJOR_VERSION_VT 2 /* + * MEI version with GSC support + */ +#define HBM_MINOR_VERSION_GSC 2 +#define HBM_MAJOR_VERSION_GSC 2 + +/* * MEI version with capabilities message support */ #define HBM_MINOR_VERSION_CAP 2 @@ -219,23 +237,22 @@ enum mei_cl_disconnect_status { * * @MEI_EXT_HDR_NONE: sentinel * @MEI_EXT_HDR_VTAG: vtag header + * @MEI_EXT_HDR_GSC: gsc header */ enum mei_ext_hdr_type { MEI_EXT_HDR_NONE = 0, MEI_EXT_HDR_VTAG = 1, + MEI_EXT_HDR_GSC = 2, }; /** * struct mei_ext_hdr - extend header descriptor (TLV) * @type: enum mei_ext_hdr_type * @length: length excluding descriptor - * @ext_payload: payload of the specific extended header - * @hdr: place holder for actual header */ struct mei_ext_hdr { u8 type; u8 length; - u8 data[]; } __packed; /** @@ -269,12 +286,11 @@ struct mei_ext_hdr_vtag { * Extended header iterator functions */ /** - * mei_ext_hdr - extended header iterator begin + * mei_ext_begin - extended header iterator begin * * @meta: meta header of the extended header list * - * Return: - * The first extended header + * Return: The first extended header */ static inline struct mei_ext_hdr *mei_ext_begin(struct mei_ext_meta_hdr *meta) { @@ -295,6 +311,60 @@ static inline bool mei_ext_last(struct mei_ext_meta_hdr *meta, return (u8 *)ext >= (u8 *)meta + sizeof(*meta) + (meta->size * 4); } +struct mei_gsc_sgl { + u32 low; + u32 high; + u32 length; +} __packed; + +#define GSC_HECI_MSG_KERNEL 0 +#define GSC_HECI_MSG_USER 1 + +#define GSC_ADDRESS_TYPE_GTT 0 +#define GSC_ADDRESS_TYPE_PPGTT 1 +#define GSC_ADDRESS_TYPE_PHYSICAL_CONTINUOUS 2 /* max of 64K */ +#define GSC_ADDRESS_TYPE_PHYSICAL_SGL 3 + +/** + * struct mei_ext_hdr_gsc_h2f - extended header: gsc host to firmware interface + * + * @hdr: extended header + * @client_id: GSC_HECI_MSG_KERNEL or GSC_HECI_MSG_USER + * @addr_type: GSC_ADDRESS_TYPE_{GTT, PPGTT, PHYSICAL_CONTINUOUS, PHYSICAL_SGL} + * @fence_id: synchronization marker + * @input_address_count: number of input sgl buffers + * @output_address_count: number of output sgl buffers + * @reserved: reserved + * @sgl: sg list + */ +struct mei_ext_hdr_gsc_h2f { + struct mei_ext_hdr hdr; + u8 client_id; + u8 addr_type; + u32 fence_id; + u8 input_address_count; + u8 output_address_count; + u8 reserved[2]; + struct mei_gsc_sgl sgl[]; +} __packed; + +/** + * struct mei_ext_hdr_gsc_f2h - gsc firmware to host interface + * + * @hdr: extended header + * @client_id: GSC_HECI_MSG_KERNEL or GSC_HECI_MSG_USER + * @reserved: reserved + * @fence_id: synchronization marker + * @written: number of bytes written to firmware + */ +struct mei_ext_hdr_gsc_f2h { + struct mei_ext_hdr hdr; + u8 client_id; + u8 reserved; + u32 fence_id; + u32 written; +} __packed; + /** * mei_ext_next - following extended header on the TLV list * @@ -311,6 +381,21 @@ static inline struct mei_ext_hdr *mei_ext_next(struct mei_ext_hdr *ext) } /** + * mei_ext_hdr_len - get ext header length in bytes + * + * @ext: extend header + * + * Return: extend header length in bytes + */ +static inline u32 mei_ext_hdr_len(const struct mei_ext_hdr *ext) +{ + if (!ext) + return 0; + + return ext->length * sizeof(u32); +} + +/** * struct mei_msg_hdr - MEI BUS Interface Section * * @me_addr: device address @@ -344,7 +429,7 @@ struct mei_bus_message { } __packed; /** - * struct hbm_cl_cmd - client specific host bus command + * struct mei_hbm_cl_cmd - client specific host bus command * CONNECT, DISCONNECT, and FlOW CONTROL * * @hbm_cmd: bus message command header @@ -648,7 +733,7 @@ struct hbm_dma_setup_response { } __packed; /** - * struct mei_dma_ring_ctrl - dma ring control block + * struct hbm_dma_ring_ctrl - dma ring control block * * @hbuf_wr_idx: host circular buffer write index in slots * @reserved1: reserved for alignment @@ -672,6 +757,10 @@ struct hbm_dma_ring_ctrl { /* virtual tag supported */ #define HBM_CAP_VT BIT(0) + +/* gsc extended header support */ +#define HBM_CAP_GSC BIT(1) + /* client dma supported */ #define HBM_CAP_CD BIT(2) @@ -717,8 +806,8 @@ struct hbm_client_dma_map_request { } __packed; /** - * struct hbm_client_dma_unmap_request - * client dma unmap request from the host to the firmware + * struct hbm_client_dma_unmap_request - client dma unmap request + * from the host to the firmware * * @hbm_cmd: bus message command header * @status: unmap status @@ -733,8 +822,8 @@ struct hbm_client_dma_unmap_request { } __packed; /** - * struct hbm_client_dma_response - * client dma unmap response from the firmware to the host + * struct hbm_client_dma_response - client dma unmap response + * from the firmware to the host * * @hbm_cmd: bus message command header * @status: command status diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 5c8cb679b997..b789c4d9c709 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Copyright (c) 2012-2019, Intel Corporation. All rights reserved. + * Copyright (c) 2012-2022, Intel Corporation. All rights reserved. * Intel Management Engine Interface (Intel MEI) Linux driver */ @@ -24,6 +24,7 @@ const char *mei_dev_state_str(int state) MEI_DEV_STATE(ENABLED); MEI_DEV_STATE(RESETTING); MEI_DEV_STATE(DISABLED); + MEI_DEV_STATE(POWERING_DOWN); MEI_DEV_STATE(POWER_DOWN); MEI_DEV_STATE(POWER_UP); default: @@ -108,8 +109,13 @@ int mei_reset(struct mei_device *dev) char fw_sts_str[MEI_FW_STATUS_STR_SZ]; mei_fw_status_str(dev, fw_sts_str, MEI_FW_STATUS_STR_SZ); - dev_warn(dev->dev, "unexpected reset: dev_state = %s fw status = %s\n", - mei_dev_state_str(state), fw_sts_str); + if (kind_is_gsc(dev) || kind_is_gscfi(dev)) { + dev_dbg(&dev->dev, "unexpected reset: dev_state = %s fw status = %s\n", + mei_dev_state_str(state), fw_sts_str); + } else { + dev_warn(&dev->dev, "unexpected reset: dev_state = %s fw status = %s\n", + mei_dev_state_str(state), fw_sts_str); + } } mei_clear_interrupts(dev); @@ -127,7 +133,7 @@ int mei_reset(struct mei_device *dev) dev->reset_count++; if (dev->reset_count > MEI_MAX_CONSEC_RESET) { - dev_err(dev->dev, "reset: reached maximal consecutive resets: disabling the device\n"); + dev_err(&dev->dev, "reset: reached maximal consecutive resets: disabling the device\n"); mei_set_devstate(dev, MEI_DEV_DISABLED); return -ENODEV; } @@ -141,31 +147,42 @@ int mei_reset(struct mei_device *dev) mei_hbm_reset(dev); + /* clean stale FW version */ + dev->fw_ver_received = 0; + memset(dev->rd_msg_hdr, 0, sizeof(dev->rd_msg_hdr)); if (ret) { - dev_err(dev->dev, "hw_reset failed ret = %d\n", ret); + dev_err(&dev->dev, "hw_reset failed ret = %d\n", ret); return ret; } if (state == MEI_DEV_POWER_DOWN) { - dev_dbg(dev->dev, "powering down: end of reset\n"); + dev_dbg(&dev->dev, "powering down: end of reset\n"); mei_set_devstate(dev, MEI_DEV_DISABLED); return 0; } ret = mei_hw_start(dev); if (ret) { - dev_err(dev->dev, "hw_start failed ret = %d\n", ret); + char fw_sts_str[MEI_FW_STATUS_STR_SZ]; + + mei_fw_status_str(dev, fw_sts_str, MEI_FW_STATUS_STR_SZ); + dev_err(&dev->dev, "hw_start failed ret = %d fw status = %s\n", ret, fw_sts_str); return ret; } - dev_dbg(dev->dev, "link is established start sending messages.\n"); + if (dev->dev_state != MEI_DEV_RESETTING) { + dev_dbg(&dev->dev, "wrong state = %d on link start\n", dev->dev_state); + return 0; + } + + dev_dbg(&dev->dev, "link is established start sending messages.\n"); mei_set_devstate(dev, MEI_DEV_INIT_CLIENTS); ret = mei_hbm_start_req(dev); if (ret) { - dev_err(dev->dev, "hbm_start failed ret = %d\n", ret); + dev_err(&dev->dev, "hbm_start failed ret = %d\n", ret); mei_set_devstate(dev, MEI_DEV_RESETTING); return ret; } @@ -194,7 +211,7 @@ int mei_start(struct mei_device *dev) if (ret) goto err; - dev_dbg(dev->dev, "reset in start the mei device.\n"); + dev_dbg(&dev->dev, "reset in start the mei device.\n"); dev->reset_count = 0; do { @@ -202,37 +219,27 @@ int mei_start(struct mei_device *dev) ret = mei_reset(dev); if (ret == -ENODEV || dev->dev_state == MEI_DEV_DISABLED) { - dev_err(dev->dev, "reset failed ret = %d", ret); + dev_err(&dev->dev, "reset failed ret = %d", ret); goto err; } } while (ret); if (mei_hbm_start_wait(dev)) { - dev_err(dev->dev, "HBM haven't started"); - goto err; - } - - if (!mei_host_is_ready(dev)) { - dev_err(dev->dev, "host is not ready.\n"); - goto err; - } - - if (!mei_hw_is_ready(dev)) { - dev_err(dev->dev, "ME is not ready.\n"); + dev_err(&dev->dev, "HBM haven't started"); goto err; } if (!mei_hbm_version_is_supported(dev)) { - dev_dbg(dev->dev, "MEI start failed.\n"); + dev_dbg(&dev->dev, "MEI start failed.\n"); goto err; } - dev_dbg(dev->dev, "link layer has been established.\n"); + dev_dbg(&dev->dev, "link layer has been established.\n"); mutex_unlock(&dev->device_lock); return 0; err: - dev_err(dev->dev, "link layer initialization failed.\n"); + dev_err(&dev->dev, "link layer initialization failed.\n"); mei_set_devstate(dev, MEI_DEV_DISABLED); mutex_unlock(&dev->device_lock); return -ENODEV; @@ -260,7 +267,7 @@ int mei_restart(struct mei_device *dev) mutex_unlock(&dev->device_lock); if (err == -ENODEV || dev->dev_state == MEI_DEV_DISABLED) { - dev_err(dev->dev, "device disabled = %d\n", err); + dev_err(&dev->dev, "device disabled = %d\n", err); return -ENODEV; } @@ -289,7 +296,7 @@ static void mei_reset_work(struct work_struct *work) mutex_unlock(&dev->device_lock); if (dev->dev_state == MEI_DEV_DISABLED) { - dev_err(dev->dev, "device disabled = %d\n", ret); + dev_err(&dev->dev, "device disabled = %d\n", ret); return; } @@ -300,7 +307,7 @@ static void mei_reset_work(struct work_struct *work) void mei_stop(struct mei_device *dev) { - dev_dbg(dev->dev, "stopping the device.\n"); + dev_dbg(&dev->dev, "stopping the device.\n"); mutex_lock(&dev->device_lock); mei_set_devstate(dev, MEI_DEV_POWERING_DOWN); @@ -314,6 +321,8 @@ void mei_stop(struct mei_device *dev) mei_clear_interrupts(dev); mei_synchronize_irq(dev); + /* to catch HW-initiated reset */ + mei_cancel_work(dev); mutex_lock(&dev->device_lock); @@ -339,7 +348,7 @@ bool mei_write_is_idle(struct mei_device *dev) list_empty(&dev->write_list) && list_empty(&dev->write_waiting_list)); - dev_dbg(dev->dev, "write pg: is idle[%d] state=%s ctrl=%01d write=%01d wwait=%01d\n", + dev_dbg(&dev->dev, "write pg: is idle[%d] state=%s ctrl=%01d write=%01d wwait=%01d\n", idle, mei_dev_state_str(dev->dev_state), list_empty(&dev->ctrl_wr_list), @@ -351,14 +360,16 @@ bool mei_write_is_idle(struct mei_device *dev) EXPORT_SYMBOL_GPL(mei_write_is_idle); /** - * mei_device_init -- initialize mei_device structure + * mei_device_init - initialize mei_device structure * * @dev: the mei device - * @device: the device structure + * @parent: the parent device + * @slow_fw: configure longer timeouts as FW is slow * @hw_ops: hw operations */ void mei_device_init(struct mei_device *dev, - struct device *device, + struct device *parent, + bool slow_fw, const struct mei_hw_ops *hw_ops) { /* setup our list array */ @@ -371,7 +382,8 @@ void mei_device_init(struct mei_device *dev, init_waitqueue_head(&dev->wait_hw_ready); init_waitqueue_head(&dev->wait_pg); init_waitqueue_head(&dev->wait_hbm_start); - dev->dev_state = MEI_DEV_INITIALIZING; + dev->dev_state = MEI_DEV_UNINITIALIZED; + init_waitqueue_head(&dev->wait_dev_state); dev->reset_count = 0; INIT_LIST_HEAD(&dev->write_list); @@ -387,6 +399,9 @@ void mei_device_init(struct mei_device *dev, bitmap_zero(dev->host_clients_map, MEI_CLIENTS_MAX); dev->open_handle_count = 0; + dev->pxp_mode = MEI_DEV_PXP_DEFAULT; + dev->gsc_reset_to_pxp = MEI_DEV_RESET_TO_PXP_DEFAULT; + /* * Reserving the first client ID * 0: Reserved for MEI Bus Message communications @@ -395,7 +410,22 @@ void mei_device_init(struct mei_device *dev, dev->pg_event = MEI_PG_EVENT_IDLE; dev->ops = hw_ops; - dev->dev = device; + dev->parent = parent; + + dev->timeouts.hw_ready = mei_secs_to_jiffies(MEI_HW_READY_TIMEOUT); + dev->timeouts.connect = MEI_CONNECT_TIMEOUT; + dev->timeouts.client_init = MEI_CLIENTS_INIT_TIMEOUT; + dev->timeouts.pgi = mei_secs_to_jiffies(MEI_PGI_TIMEOUT); + dev->timeouts.d0i3 = mei_secs_to_jiffies(MEI_D0I3_TIMEOUT); + if (slow_fw) { + dev->timeouts.cl_connect = mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT_SLOW); + dev->timeouts.hbm = mei_secs_to_jiffies(MEI_HBM_TIMEOUT_SLOW); + dev->timeouts.mkhi_recv = msecs_to_jiffies(MKHI_RCV_TIMEOUT_SLOW); + } else { + dev->timeouts.cl_connect = mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT); + dev->timeouts.hbm = mei_secs_to_jiffies(MEI_HBM_TIMEOUT); + dev->timeouts.mkhi_recv = msecs_to_jiffies(MKHI_RCV_TIMEOUT); + } + dev->timeouts.link_reset_wait = msecs_to_jiffies(MEI_LINK_RESET_WAIT_TIMEOUT_MSEC); } EXPORT_SYMBOL_GPL(mei_device_init); - diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index a67f4f2d33a9..3f210413fd32 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -35,7 +35,7 @@ void mei_irq_compl_handler(struct mei_device *dev, struct list_head *cmpl_list) cl = cb->cl; list_del_init(&cb->list); - dev_dbg(dev->dev, "completing call back.\n"); + cl_dbg(dev, cl, "completing call back.\n"); mei_cl_complete(cl, cb); } } @@ -72,11 +72,11 @@ static void mei_irq_discard_msg(struct mei_device *dev, struct mei_msg_hdr *hdr, discard_len = 0; } /* - * no need to check for size as it is guarantied + * no need to check for size as it is guaranteed * that length fits into rd_msg_buf */ mei_read_slots(dev, dev->rd_msg_buf, discard_len); - dev_dbg(dev->dev, "discarding message " MEI_HDR_FMT "\n", + dev_dbg(&dev->dev, "discarding message " MEI_HDR_FMT "\n", MEI_HDR_PRM(hdr)); } @@ -98,9 +98,12 @@ static int mei_cl_irq_read_msg(struct mei_cl *cl, struct mei_device *dev = cl->dev; struct mei_cl_cb *cb; + struct mei_ext_hdr_vtag *vtag_hdr = NULL; + struct mei_ext_hdr_gsc_f2h *gsc_f2h = NULL; + size_t buf_sz; u32 length; - int ext_len; + u32 ext_len; length = mei_hdr->length; ext_len = 0; @@ -122,18 +125,24 @@ static int mei_cl_irq_read_msg(struct mei_cl *cl, } if (mei_hdr->extended) { - struct mei_ext_hdr *ext; - struct mei_ext_hdr_vtag *vtag_hdr = NULL; - - ext = mei_ext_begin(meta); + struct mei_ext_hdr *ext = mei_ext_begin(meta); do { switch (ext->type) { case MEI_EXT_HDR_VTAG: vtag_hdr = (struct mei_ext_hdr_vtag *)ext; break; + case MEI_EXT_HDR_GSC: + gsc_f2h = (struct mei_ext_hdr_gsc_f2h *)ext; + cb->ext_hdr = (struct mei_ext_hdr *)kzalloc(sizeof(*gsc_f2h), GFP_KERNEL); + if (!cb->ext_hdr) { + cb->status = -ENOMEM; + goto discard; + } + break; case MEI_EXT_HDR_NONE: fallthrough; default: + cl_err(dev, cl, "unknown extended header\n"); cb->status = -EPROTO; break; } @@ -141,12 +150,14 @@ static int mei_cl_irq_read_msg(struct mei_cl *cl, ext = mei_ext_next(ext); } while (!mei_ext_last(meta, ext)); - if (!vtag_hdr) { - cl_dbg(dev, cl, "vtag not found in extended header.\n"); + if (!vtag_hdr && !gsc_f2h) { + cl_dbg(dev, cl, "no vtag or gsc found in extended header.\n"); cb->status = -EPROTO; goto discard; } + } + if (vtag_hdr) { cl_dbg(dev, cl, "vtag: %d\n", vtag_hdr->vtag); if (cb->vtag && cb->vtag != vtag_hdr->vtag) { cl_err(dev, cl, "mismatched tag: %d != %d\n", @@ -157,6 +168,28 @@ static int mei_cl_irq_read_msg(struct mei_cl *cl, cb->vtag = vtag_hdr->vtag; } + if (gsc_f2h) { + u32 ext_hdr_len = mei_ext_hdr_len(&gsc_f2h->hdr); + + if (!dev->hbm_f_gsc_supported) { + cl_err(dev, cl, "gsc extended header is not supported\n"); + cb->status = -EPROTO; + goto discard; + } + + if (length) { + cl_err(dev, cl, "no data allowed in cb with gsc\n"); + cb->status = -EPROTO; + goto discard; + } + if (ext_hdr_len > sizeof(*gsc_f2h)) { + cl_err(dev, cl, "gsc extended header is too big %u\n", ext_hdr_len); + cb->status = -EPROTO; + goto discard; + } + memcpy(cb->ext_hdr, gsc_f2h, ext_hdr_len); + } + if (!mei_cl_is_connected(cl)) { cl_dbg(dev, cl, "not connected\n"); cb->status = -ENODEV; @@ -196,8 +229,7 @@ static int mei_cl_irq_read_msg(struct mei_cl *cl, cl_dbg(dev, cl, "completed read length = %zu\n", cb->buf_idx); list_move_tail(&cb->list, cmpl_list); } else { - pm_runtime_mark_last_busy(dev->dev); - pm_request_autosuspend(dev->dev); + pm_request_autosuspend(dev->parent); } return 0; @@ -277,8 +309,7 @@ static int mei_cl_irq_read(struct mei_cl *cl, struct mei_cl_cb *cb, return ret; } - pm_runtime_mark_last_busy(dev->dev); - pm_request_autosuspend(dev->dev); + pm_request_autosuspend(dev->parent); list_move_tail(&cb->list, &cl->rd_pending); @@ -340,21 +371,21 @@ int mei_irq_read_handler(struct mei_device *dev, dev->rd_msg_hdr[0] = mei_read_hdr(dev); dev->rd_msg_hdr_count = 1; (*slots)--; - dev_dbg(dev->dev, "slots =%08x.\n", *slots); + dev_dbg(&dev->dev, "slots =%08x.\n", *slots); ret = hdr_is_valid(dev->rd_msg_hdr[0]); if (ret) { - dev_err(dev->dev, "corrupted message header 0x%08X\n", + dev_err(&dev->dev, "corrupted message header 0x%08X\n", dev->rd_msg_hdr[0]); goto end; } } mei_hdr = (struct mei_msg_hdr *)dev->rd_msg_hdr; - dev_dbg(dev->dev, MEI_HDR_FMT, MEI_HDR_PRM(mei_hdr)); + dev_dbg(&dev->dev, MEI_HDR_FMT, MEI_HDR_PRM(mei_hdr)); if (mei_slots2data(*slots) < mei_hdr->length) { - dev_err(dev->dev, "less data available than length=%08x.\n", + dev_err(&dev->dev, "less data available than length=%08x.\n", *slots); /* we can't read the message */ ret = -ENODATA; @@ -369,18 +400,18 @@ int mei_irq_read_handler(struct mei_device *dev, dev->rd_msg_hdr[1] = mei_read_hdr(dev); dev->rd_msg_hdr_count++; (*slots)--; - dev_dbg(dev->dev, "extended header is %08x\n", dev->rd_msg_hdr[1]); + dev_dbg(&dev->dev, "extended header is %08x\n", dev->rd_msg_hdr[1]); } meta_hdr = ((struct mei_ext_meta_hdr *)&dev->rd_msg_hdr[1]); if (check_add_overflow((u32)sizeof(*meta_hdr), mei_slots2data(meta_hdr->size), &hdr_size_ext)) { - dev_err(dev->dev, "extended message size too big %d\n", + dev_err(&dev->dev, "extended message size too big %d\n", meta_hdr->size); return -EBADMSG; } if (hdr_size_left < hdr_size_ext) { - dev_err(dev->dev, "corrupted message header len %d\n", + dev_err(&dev->dev, "corrupted message header len %d\n", mei_hdr->length); return -EBADMSG; } @@ -389,7 +420,7 @@ int mei_irq_read_handler(struct mei_device *dev, ext_hdr_end = meta_hdr->size + 2; for (i = dev->rd_msg_hdr_count; i < ext_hdr_end; i++) { dev->rd_msg_hdr[i] = mei_read_hdr(dev); - dev_dbg(dev->dev, "extended header %d is %08x\n", i, + dev_dbg(&dev->dev, "extended header %d is %08x\n", i, dev->rd_msg_hdr[i]); dev->rd_msg_hdr_count++; (*slots)--; @@ -398,7 +429,7 @@ int mei_irq_read_handler(struct mei_device *dev, if (mei_hdr->dma_ring) { if (hdr_size_left != sizeof(dev->rd_msg_hdr[ext_hdr_end])) { - dev_err(dev->dev, "corrupted message header len %d\n", + dev_err(&dev->dev, "corrupted message header len %d\n", mei_hdr->length); return -EBADMSG; } @@ -413,8 +444,7 @@ int mei_irq_read_handler(struct mei_device *dev, if (hdr_is_hbm(mei_hdr)) { ret = mei_hbm_dispatch(dev, mei_hdr); if (ret) { - dev_dbg(dev->dev, "mei_hbm_dispatch failed ret = %d\n", - ret); + dev_dbg(&dev->dev, "mei_hbm_dispatch failed ret = %d\n", ret); goto end; } goto reset_slots; @@ -424,31 +454,26 @@ int mei_irq_read_handler(struct mei_device *dev, list_for_each_entry(cl, &dev->file_list, link) { if (mei_cl_hbm_equal(cl, mei_hdr)) { cl_dbg(dev, cl, "got a message\n"); - break; + ret = mei_cl_irq_read_msg(cl, mei_hdr, meta_hdr, cmpl_list); + goto reset_slots; } } /* if no recipient cl was found we assume corrupted header */ - if (&cl->link == &dev->file_list) { - /* A message for not connected fixed address clients - * should be silently discarded - * On power down client may be force cleaned, - * silently discard such messages - */ - if (hdr_is_fixed(mei_hdr) || - dev->dev_state == MEI_DEV_POWER_DOWN) { - mei_irq_discard_msg(dev, mei_hdr, mei_hdr->length); - ret = 0; - goto reset_slots; - } - dev_err(dev->dev, "no destination client found 0x%08X\n", - dev->rd_msg_hdr[0]); - ret = -EBADMSG; - goto end; + /* A message for not connected fixed address clients + * should be silently discarded + * On power down client may be force cleaned, + * silently discard such messages + */ + if (hdr_is_fixed(mei_hdr) || + dev->dev_state == MEI_DEV_POWER_DOWN) { + mei_irq_discard_msg(dev, mei_hdr, mei_hdr->length); + ret = 0; + goto reset_slots; } - - ret = mei_cl_irq_read_msg(cl, mei_hdr, meta_hdr, cmpl_list); - + dev_err(&dev->dev, "no destination client found 0x%08X\n", dev->rd_msg_hdr[0]); + ret = -EBADMSG; + goto end; reset_slots: /* reset the number of slots and header */ @@ -457,7 +482,7 @@ reset_slots: *slots = mei_count_full_read_slots(dev); if (*slots == -EOVERFLOW) { /* overflow - reset */ - dev_err(dev->dev, "resetting due to slots overflow.\n"); + dev_err(&dev->dev, "resetting due to slots overflow.\n"); /* set the event since message has been read */ ret = -ERANGE; goto end; @@ -497,7 +522,7 @@ int mei_irq_write_handler(struct mei_device *dev, struct list_head *cmpl_list) return -EMSGSIZE; /* complete all waiting for write CB */ - dev_dbg(dev->dev, "complete all waiting for write cb.\n"); + dev_dbg(&dev->dev, "complete all waiting for write cb.\n"); list_for_each_entry_safe(cb, next, &dev->write_waiting_list, list) { cl = cb->cl; @@ -509,7 +534,7 @@ int mei_irq_write_handler(struct mei_device *dev, struct list_head *cmpl_list) } /* complete control write list CB */ - dev_dbg(dev->dev, "complete control write list cb.\n"); + dev_dbg(&dev->dev, "complete control write list cb.\n"); list_for_each_entry_safe(cb, next, &dev->ctrl_wr_list, list) { cl = cb->cl; switch (cb->fop_type) { @@ -563,7 +588,7 @@ int mei_irq_write_handler(struct mei_device *dev, struct list_head *cmpl_list) } /* complete write list CB */ - dev_dbg(dev->dev, "complete write list cb.\n"); + dev_dbg(&dev->dev, "complete write list cb.\n"); list_for_each_entry_safe(cb, next, &dev->write_list, list) { cl = cb->cl; ret = mei_cl_irq_write(cl, cb, cmpl_list); @@ -598,9 +623,9 @@ static void mei_connect_timeout(struct mei_cl *cl) /** * mei_schedule_stall_timer - re-arm stall_timer work * - * Schedule stall timer - * * @dev: the device structure + * + * Schedule stall timer */ void mei_schedule_stall_timer(struct mei_device *dev) { @@ -628,7 +653,7 @@ void mei_timer(struct work_struct *work) if (dev->init_clients_timer) { if (--dev->init_clients_timer == 0) { - dev_err(dev->dev, "timer: init clients timeout hbm_state = %d.\n", + dev_err(&dev->dev, "timer: init clients timeout hbm_state = %d.\n", dev->hbm_state); mei_reset(dev); goto out; @@ -644,7 +669,7 @@ void mei_timer(struct work_struct *work) list_for_each_entry(cl, &dev->file_list, link) { if (cl->timer_count) { if (--cl->timer_count == 0) { - dev_err(dev->dev, "timer: connect/disconnect timeout.\n"); + dev_err(&dev->dev, "timer: connect/disconnect timeout.\n"); mei_connect_timeout(cl); goto out; } diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index 786f7c8f7f61..6f26d5160788 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Copyright (c) 2003-2020, Intel Corporation. All rights reserved. + * Copyright (c) 2003-2022, Intel Corporation. All rights reserved. * Intel Management Engine Interface (Intel MEI) Linux driver */ @@ -18,7 +18,6 @@ #include <linux/ioctl.h> #include <linux/cdev.h> #include <linux/sched/signal.h> -#include <linux/uuid.h> #include <linux/compat.h> #include <linux/jiffies.h> #include <linux/interrupt.h> @@ -28,7 +27,10 @@ #include "mei_dev.h" #include "client.h" -static struct class *mei_class; +static const struct class mei_class = { + .name = "mei", +}; + static dev_t mei_devt; #define MEI_MAX_DEVS MINORMASK static DEFINE_MUTEX(mei_minor_lock); @@ -49,12 +51,15 @@ static int mei_open(struct inode *inode, struct file *file) int err; - dev = container_of(inode->i_cdev, struct mei_device, cdev); + dev = idr_find(&mei_idr, iminor(inode)); + if (!dev) + return -ENODEV; + get_device(&dev->dev); mutex_lock(&dev->device_lock); if (dev->dev_state != MEI_DEV_ENABLED) { - dev_dbg(dev->dev, "dev_state != MEI_ENABLED dev_state = %s\n", + dev_dbg(&dev->dev, "dev_state != MEI_ENABLED dev_state = %s\n", mei_dev_state_str(dev->dev_state)); err = -ENODEV; goto err_unlock; @@ -75,6 +80,7 @@ static int mei_open(struct inode *inode, struct file *file) err_unlock: mutex_unlock(&dev->device_lock); + put_device(&dev->dev); return err; } @@ -150,6 +156,7 @@ out: file->private_data = NULL; mutex_unlock(&dev->device_lock); + put_device(&dev->dev); return rets; } @@ -254,7 +261,7 @@ copy_buffer: length = min_t(size_t, length, cb->buf_idx - *offset); if (copy_to_user(ubuf, cb->buf.data + *offset, length)) { - dev_dbg(dev->dev, "failed to copy data to userland\n"); + cl_dbg(dev, cl, "failed to copy data to userland\n"); rets = -EFAULT; goto free; } @@ -327,7 +334,7 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, } if (!mei_cl_is_connected(cl)) { - cl_err(dev, cl, "is not connected"); + cl_dbg(dev, cl, "is not connected"); rets = -ENODEV; goto out; } @@ -377,13 +384,13 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, rets = copy_from_user(cb->buf.data, ubuf, length); if (rets) { - dev_dbg(dev->dev, "failed to copy data from userland\n"); + cl_dbg(dev, cl, "failed to copy data from userland\n"); rets = -EFAULT; mei_io_cb_free(cb); goto out; } - rets = mei_cl_write(cl, cb); + rets = mei_cl_write(cl, cb, MAX_SCHEDULE_TIMEOUT); out: mutex_unlock(&dev->device_lock); return rets; @@ -416,10 +423,11 @@ static int mei_ioctl_connect_client(struct file *file, cl->state != MEI_FILE_DISCONNECTED) return -EBUSY; +retry: /* find ME client we're trying to connect to */ me_cl = mei_me_cl_by_uuid(dev, in_client_uuid); if (!me_cl) { - dev_dbg(dev->dev, "Cannot connect to FW Client UUID = %pUl\n", + cl_dbg(dev, cl, "Cannot connect to FW Client UUID = %pUl\n", in_client_uuid); rets = -ENOTTY; goto end; @@ -429,27 +437,46 @@ static int mei_ioctl_connect_client(struct file *file, bool forbidden = dev->override_fixed_address ? !dev->allow_fixed_address : !dev->hbm_f_fa_supported; if (forbidden) { - dev_dbg(dev->dev, "Connection forbidden to FW Client UUID = %pUl\n", + cl_dbg(dev, cl, "Connection forbidden to FW Client UUID = %pUl\n", in_client_uuid); rets = -ENOTTY; goto end; } } - dev_dbg(dev->dev, "Connect to FW Client ID = %d\n", - me_cl->client_id); - dev_dbg(dev->dev, "FW Client - Protocol Version = %d\n", - me_cl->props.protocol_version); - dev_dbg(dev->dev, "FW Client - Max Msg Len = %d\n", - me_cl->props.max_msg_length); + cl_dbg(dev, cl, "Connect to FW Client ID = %d\n", me_cl->client_id); + cl_dbg(dev, cl, "FW Client - Protocol Version = %d\n", me_cl->props.protocol_version); + cl_dbg(dev, cl, "FW Client - Max Msg Len = %d\n", me_cl->props.max_msg_length); /* prepare the output buffer */ client->max_msg_length = me_cl->props.max_msg_length; client->protocol_version = me_cl->props.protocol_version; - dev_dbg(dev->dev, "Can connect?\n"); + cl_dbg(dev, cl, "Can connect?\n"); rets = mei_cl_connect(cl, me_cl, file); + if (rets && cl->status == -EFAULT && + (dev->dev_state == MEI_DEV_RESETTING || + dev->dev_state == MEI_DEV_INIT_CLIENTS)) { + /* in link reset, wait for it completion */ + mutex_unlock(&dev->device_lock); + rets = wait_event_interruptible_timeout(dev->wait_dev_state, + dev->dev_state == MEI_DEV_ENABLED, + dev->timeouts.link_reset_wait); + mutex_lock(&dev->device_lock); + if (rets < 0) { + if (signal_pending(current)) + rets = -EINTR; + goto end; + } + if (dev->dev_state != MEI_DEV_ENABLED) { + rets = -ETIME; + goto end; + } + mei_me_cl_put(me_cl); + goto retry; + } + end: mei_me_cl_put(me_cl); return rets; @@ -458,11 +485,11 @@ end: /** * mei_vt_support_check - check if client support vtags * - * Locking: called under "dev->device_lock" lock - * * @dev: mei_device * @uuid: client UUID * + * Locking: called under "dev->device_lock" lock + * * Return: * 0 - supported * -ENOTTY - no such client @@ -478,7 +505,7 @@ static int mei_vt_support_check(struct mei_device *dev, const uuid_le *uuid) me_cl = mei_me_cl_by_uuid(dev, uuid); if (!me_cl) { - dev_dbg(dev->dev, "Cannot connect to FW Client UUID = %pUl\n", + dev_dbg(&dev->dev, "Cannot connect to FW Client UUID = %pUl\n", uuid); return -ENOTTY; } @@ -513,19 +540,19 @@ static int mei_ioctl_connect_vtag(struct file *file, cl = file->private_data; dev = cl->dev; - dev_dbg(dev->dev, "FW Client %pUl vtag %d\n", in_client_uuid, vtag); + cl_dbg(dev, cl, "FW Client %pUl vtag %d\n", in_client_uuid, vtag); switch (cl->state) { case MEI_FILE_DISCONNECTED: if (mei_cl_vtag_by_fp(cl, file) != vtag) { - dev_err(dev->dev, "reconnect with different vtag\n"); + cl_err(dev, cl, "reconnect with different vtag\n"); return -EINVAL; } break; case MEI_FILE_INITIALIZING: /* malicious connect from another thread may push vtag */ if (!IS_ERR(mei_cl_fp_by_vtag(cl, vtag))) { - dev_err(dev->dev, "vtag already filled\n"); + cl_err(dev, cl, "vtag already filled\n"); return -EINVAL; } @@ -544,7 +571,7 @@ static int mei_ioctl_connect_vtag(struct file *file, continue; /* replace cl with acquired one */ - dev_dbg(dev->dev, "replacing with existing cl\n"); + cl_dbg(dev, cl, "replacing with existing cl\n"); mei_cl_unlink(cl); kfree(cl); file->private_data = pos; @@ -571,7 +598,7 @@ static int mei_ioctl_connect_vtag(struct file *file, cl->state == MEI_FILE_DISCONNECTED || cl->state == MEI_FILE_DISCONNECT_REQUIRED || cl->state == MEI_FILE_DISCONNECT_REPLY), - mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT)); + dev->timeouts.cl_connect); mutex_lock(&dev->device_lock); } @@ -585,8 +612,8 @@ static int mei_ioctl_connect_vtag(struct file *file, } /** - * mei_ioctl_client_notify_request - - * propagate event notification request to client + * mei_ioctl_client_notify_request - propagate event notification + * request to client * * @file: pointer to file structure * @request: 0 - disable, 1 - enable @@ -642,7 +669,7 @@ static long mei_ioctl(struct file *file, unsigned int cmd, unsigned long data) struct mei_cl *cl = file->private_data; struct mei_connect_client_data conn; struct mei_connect_client_data_vtag conn_vtag; - const uuid_le *cl_uuid; + uuid_le cl_uuid; struct mei_client *props; u8 vtag; u32 notify_get, notify_req; @@ -654,7 +681,7 @@ static long mei_ioctl(struct file *file, unsigned int cmd, unsigned long data) dev = cl->dev; - dev_dbg(dev->dev, "IOCTL cmd = 0x%x", cmd); + cl_dbg(dev, cl, "IOCTL cmd = 0x%x", cmd); mutex_lock(&dev->device_lock); if (dev->dev_state != MEI_DEV_ENABLED) { @@ -664,30 +691,30 @@ static long mei_ioctl(struct file *file, unsigned int cmd, unsigned long data) switch (cmd) { case IOCTL_MEI_CONNECT_CLIENT: - dev_dbg(dev->dev, ": IOCTL_MEI_CONNECT_CLIENT.\n"); + cl_dbg(dev, cl, "IOCTL_MEI_CONNECT_CLIENT\n"); if (copy_from_user(&conn, (char __user *)data, sizeof(conn))) { - dev_dbg(dev->dev, "failed to copy data from userland\n"); + cl_dbg(dev, cl, "failed to copy data from userland\n"); rets = -EFAULT; goto out; } - cl_uuid = &conn.in_client_uuid; + cl_uuid = conn.in_client_uuid; props = &conn.out_client_properties; vtag = 0; - rets = mei_vt_support_check(dev, cl_uuid); + rets = mei_vt_support_check(dev, &cl_uuid); if (rets == -ENOTTY) goto out; if (!rets) - rets = mei_ioctl_connect_vtag(file, cl_uuid, props, + rets = mei_ioctl_connect_vtag(file, &cl_uuid, props, vtag); else - rets = mei_ioctl_connect_client(file, cl_uuid, props); + rets = mei_ioctl_connect_client(file, &cl_uuid, props); if (rets) goto out; /* if all is ok, copying the data back to user. */ if (copy_to_user((char __user *)data, &conn, sizeof(conn))) { - dev_dbg(dev->dev, "failed to copy data to userland\n"); + cl_dbg(dev, cl, "failed to copy data to userland\n"); rets = -EFAULT; goto out; } @@ -695,39 +722,39 @@ static long mei_ioctl(struct file *file, unsigned int cmd, unsigned long data) break; case IOCTL_MEI_CONNECT_CLIENT_VTAG: - dev_dbg(dev->dev, "IOCTL_MEI_CONNECT_CLIENT_VTAG\n"); + cl_dbg(dev, cl, "IOCTL_MEI_CONNECT_CLIENT_VTAG\n"); if (copy_from_user(&conn_vtag, (char __user *)data, sizeof(conn_vtag))) { - dev_dbg(dev->dev, "failed to copy data from userland\n"); + cl_dbg(dev, cl, "failed to copy data from userland\n"); rets = -EFAULT; goto out; } - cl_uuid = &conn_vtag.connect.in_client_uuid; + cl_uuid = conn_vtag.connect.in_client_uuid; props = &conn_vtag.out_client_properties; vtag = conn_vtag.connect.vtag; - rets = mei_vt_support_check(dev, cl_uuid); + rets = mei_vt_support_check(dev, &cl_uuid); if (rets == -EOPNOTSUPP) - dev_dbg(dev->dev, "FW Client %pUl does not support vtags\n", - cl_uuid); + cl_dbg(dev, cl, "FW Client %pUl does not support vtags\n", + &cl_uuid); if (rets) goto out; if (!vtag) { - dev_dbg(dev->dev, "vtag can't be zero\n"); + cl_dbg(dev, cl, "vtag can't be zero\n"); rets = -EINVAL; goto out; } - rets = mei_ioctl_connect_vtag(file, cl_uuid, props, vtag); + rets = mei_ioctl_connect_vtag(file, &cl_uuid, props, vtag); if (rets) goto out; /* if all is ok, copying the data back to user. */ if (copy_to_user((char __user *)data, &conn_vtag, sizeof(conn_vtag))) { - dev_dbg(dev->dev, "failed to copy data to userland\n"); + cl_dbg(dev, cl, "failed to copy data to userland\n"); rets = -EFAULT; goto out; } @@ -735,10 +762,10 @@ static long mei_ioctl(struct file *file, unsigned int cmd, unsigned long data) break; case IOCTL_MEI_NOTIFY_SET: - dev_dbg(dev->dev, ": IOCTL_MEI_NOTIFY_SET.\n"); + cl_dbg(dev, cl, "IOCTL_MEI_NOTIFY_SET\n"); if (copy_from_user(¬ify_req, (char __user *)data, sizeof(notify_req))) { - dev_dbg(dev->dev, "failed to copy data from userland\n"); + cl_dbg(dev, cl, "failed to copy data from userland\n"); rets = -EFAULT; goto out; } @@ -746,15 +773,15 @@ static long mei_ioctl(struct file *file, unsigned int cmd, unsigned long data) break; case IOCTL_MEI_NOTIFY_GET: - dev_dbg(dev->dev, ": IOCTL_MEI_NOTIFY_GET.\n"); + cl_dbg(dev, cl, "IOCTL_MEI_NOTIFY_GET\n"); rets = mei_ioctl_client_notify_get(file, ¬ify_get); if (rets) goto out; - dev_dbg(dev->dev, "copy connect data to user\n"); + cl_dbg(dev, cl, "copy connect data to user\n"); if (copy_to_user((char __user *)data, ¬ify_get, sizeof(notify_get))) { - dev_dbg(dev->dev, "failed to copy data to userland\n"); + cl_dbg(dev, cl, "failed to copy data to userland\n"); rets = -EFAULT; goto out; @@ -1116,7 +1143,12 @@ void mei_set_devstate(struct mei_device *dev, enum mei_dev_state state) dev->dev_state = state; - clsdev = class_find_device_by_devt(mei_class, dev->cdev.dev); + wake_up_interruptible_all(&dev->wait_dev_state); + + if (!dev->cdev) + return; + + clsdev = class_find_device_by_devt(&mei_class, dev->cdev->dev); if (clsdev) { sysfs_notify(&clsdev->kobj, NULL, "dev_state"); put_device(clsdev); @@ -1174,7 +1206,6 @@ static const struct file_operations mei_fops = { .poll = mei_poll, .fsync = mei_fsync, .fasync = mei_fasync, - .llseek = no_llseek }; /** @@ -1193,7 +1224,7 @@ static int mei_minor_get(struct mei_device *dev) if (ret >= 0) dev->minor = ret; else if (ret == -ENOSPC) - dev_err(dev->dev, "too many mei devices\n"); + dev_err(&dev->dev, "too many mei devices\n"); mutex_unlock(&mei_minor_lock); return ret; @@ -1202,56 +1233,82 @@ static int mei_minor_get(struct mei_device *dev) /** * mei_minor_free - mark device minor number as free * - * @dev: device pointer + * @minor: minor number to free */ -static void mei_minor_free(struct mei_device *dev) +static void mei_minor_free(int minor) { mutex_lock(&mei_minor_lock); - idr_remove(&mei_idr, dev->minor); + idr_remove(&mei_idr, minor); mutex_unlock(&mei_minor_lock); } +static void mei_device_release(struct device *dev) +{ + kfree(dev_get_drvdata(dev)); +} + int mei_register(struct mei_device *dev, struct device *parent) { - struct device *clsdev; /* class device */ int ret, devno; + int minor; ret = mei_minor_get(dev); if (ret < 0) return ret; + minor = dev->minor; + /* Fill in the data structures */ devno = MKDEV(MAJOR(mei_devt), dev->minor); - cdev_init(&dev->cdev, &mei_fops); - dev->cdev.owner = parent->driver->owner; + + device_initialize(&dev->dev); + dev->dev.devt = devno; + dev->dev.class = &mei_class; + dev->dev.parent = parent; + dev->dev.groups = mei_groups; + dev->dev.release = mei_device_release; + dev_set_drvdata(&dev->dev, dev); + + dev->cdev = cdev_alloc(); + if (!dev->cdev) { + ret = -ENOMEM; + goto err; + } + dev->cdev->ops = &mei_fops; + dev->cdev->owner = parent->driver->owner; + cdev_set_parent(dev->cdev, &dev->dev.kobj); /* Add the device */ - ret = cdev_add(&dev->cdev, devno, 1); + ret = cdev_add(dev->cdev, devno, 1); if (ret) { - dev_err(parent, "unable to add device %d:%d\n", + dev_err(parent, "unable to add cdev for device %d:%d\n", MAJOR(mei_devt), dev->minor); - goto err_dev_add; + goto err_del_cdev; } - clsdev = device_create_with_groups(mei_class, parent, devno, - dev, mei_groups, - "mei%d", dev->minor); + ret = dev_set_name(&dev->dev, "mei%d", dev->minor); + if (ret) { + dev_err(parent, "unable to set name to device %d:%d ret = %d\n", + MAJOR(mei_devt), dev->minor, ret); + goto err_del_cdev; + } - if (IS_ERR(clsdev)) { - dev_err(parent, "unable to create device %d:%d\n", - MAJOR(mei_devt), dev->minor); - ret = PTR_ERR(clsdev); - goto err_dev_create; + ret = device_add(&dev->dev); + if (ret) { + dev_err(parent, "unable to add device %d:%d ret = %d\n", + MAJOR(mei_devt), dev->minor, ret); + goto err_del_cdev; } - mei_dbgfs_register(dev, dev_name(clsdev)); + mei_dbgfs_register(dev, dev_name(&dev->dev)); return 0; -err_dev_create: - cdev_del(&dev->cdev); -err_dev_add: - mei_minor_free(dev); +err_del_cdev: + cdev_del(dev->cdev); +err: + put_device(&dev->dev); + mei_minor_free(minor); return ret; } EXPORT_SYMBOL_GPL(mei_register); @@ -1259,15 +1316,16 @@ EXPORT_SYMBOL_GPL(mei_register); void mei_deregister(struct mei_device *dev) { int devno; + int minor = dev->minor; - devno = dev->cdev.dev; - cdev_del(&dev->cdev); + devno = dev->cdev->dev; + cdev_del(dev->cdev); mei_dbgfs_deregister(dev); - device_destroy(mei_class, devno); + device_destroy(&mei_class, devno); - mei_minor_free(dev); + mei_minor_free(minor); } EXPORT_SYMBOL_GPL(mei_deregister); @@ -1275,12 +1333,9 @@ static int __init mei_init(void) { int ret; - mei_class = class_create(THIS_MODULE, "mei"); - if (IS_ERR(mei_class)) { - pr_err("couldn't create class\n"); - ret = PTR_ERR(mei_class); - goto err; - } + ret = class_register(&mei_class); + if (ret) + return ret; ret = alloc_chrdev_region(&mei_devt, 0, MEI_MAX_DEVS, "mei"); if (ret < 0) { @@ -1299,15 +1354,14 @@ static int __init mei_init(void) err_chrdev: unregister_chrdev_region(mei_devt, MEI_MAX_DEVS); err_class: - class_destroy(mei_class); -err: + class_unregister(&mei_class); return ret; } static void __exit mei_exit(void) { unregister_chrdev_region(mei_devt, MEI_MAX_DEVS); - class_destroy(mei_class); + class_unregister(&mei_class); mei_cl_bus_exit(); } diff --git a/drivers/misc/mei/mei-trace.h b/drivers/misc/mei/mei-trace.h index fe46ff2b9d69..5312edbf5190 100644 --- a/drivers/misc/mei/mei-trace.h +++ b/drivers/misc/mei/mei-trace.h @@ -26,7 +26,7 @@ TRACE_EVENT(mei_reg_read, __field(u32, val) ), TP_fast_assign( - __assign_str(dev, dev_name(dev)); + __assign_str(dev); __entry->reg = reg; __entry->offs = offs; __entry->val = val; @@ -45,7 +45,7 @@ TRACE_EVENT(mei_reg_write, __field(u32, val) ), TP_fast_assign( - __assign_str(dev, dev_name(dev)); + __assign_str(dev); __entry->reg = reg; __entry->offs = offs; __entry->val = val; @@ -64,7 +64,7 @@ TRACE_EVENT(mei_pci_cfg_read, __field(u32, val) ), TP_fast_assign( - __assign_str(dev, dev_name(dev)); + __assign_str(dev); __entry->reg = reg; __entry->offs = offs; __entry->val = val; diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index b7b6ef344e80..0bf8d552c3ea 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* - * Copyright (c) 2003-2019, Intel Corporation. All rights reserved. + * Copyright (c) 2003-2022, Intel Corporation. All rights reserved. * Intel Management Engine Interface (Intel MEI) Linux driver */ @@ -13,6 +13,11 @@ #include <linux/mei.h> #include <linux/mei_cl_bus.h> +static inline int uuid_le_cmp(const uuid_le u1, const uuid_le u2) +{ + return memcmp(&u1, &u2, sizeof(uuid_le)); +} + #include "hw.h" #include "hbm.h" @@ -52,7 +57,8 @@ enum file_state { /* MEI device states */ enum mei_dev_state { - MEI_DEV_INITIALIZING = 0, + MEI_DEV_UNINITIALIZED = 0, + MEI_DEV_INITIALIZING, MEI_DEV_INIT_CLIENTS, MEI_DEV_ENABLED, MEI_DEV_RESETTING, @@ -62,6 +68,34 @@ enum mei_dev_state { MEI_DEV_POWER_UP }; +/** + * enum mei_dev_pxp_mode - MEI PXP mode state + * + * @MEI_DEV_PXP_DEFAULT: PCH based device, no initialization required + * @MEI_DEV_PXP_INIT: device requires initialization, send setup message to firmware + * @MEI_DEV_PXP_SETUP: device is in setup stage, waiting for firmware response + * @MEI_DEV_PXP_READY: device initialized + */ +enum mei_dev_pxp_mode { + MEI_DEV_PXP_DEFAULT = 0, + MEI_DEV_PXP_INIT = 1, + MEI_DEV_PXP_SETUP = 2, + MEI_DEV_PXP_READY = 3, +}; + +/** + * enum mei_dev_reset_to_pxp - reset to PXP mode performed + * + * @MEI_DEV_RESET_TO_PXP_DEFAULT: before reset + * @MEI_DEV_RESET_TO_PXP_PERFORMED: reset performed + * @MEI_DEV_RESET_TO_PXP_DONE: reset processed + */ +enum mei_dev_reset_to_pxp { + MEI_DEV_RESET_TO_PXP_DEFAULT = 0, + MEI_DEV_RESET_TO_PXP_PERFORMED = 1, + MEI_DEV_RESET_TO_PXP_DONE = 2, +}; + const char *mei_dev_state_str(int state); enum mei_file_transaction_states { @@ -101,12 +135,16 @@ enum mei_cb_file_ops { * @MEI_CL_IO_TX_INTERNAL: internal communication between driver and FW * * @MEI_CL_IO_RX_NONBLOCK: recv is non-blocking + * + * @MEI_CL_IO_SGL: send command with sgl list. */ enum mei_cl_io_mode { MEI_CL_IO_TX_BLOCKING = BIT(0), MEI_CL_IO_TX_INTERNAL = BIT(1), MEI_CL_IO_RX_NONBLOCK = BIT(2), + + MEI_CL_IO_SGL = BIT(3), }; /* @@ -191,6 +229,7 @@ struct mei_cl; * @status: io status of the cb * @internal: communication between driver and FW flag * @blocking: transmission blocking mode + * @ext_hdr: extended header */ struct mei_cl_cb { struct list_head list; @@ -203,6 +242,7 @@ struct mei_cl_cb { int status; u32 internal:1; u32 blocking:1; + struct mei_ext_hdr *ext_hdr; }; /** @@ -356,8 +396,10 @@ struct mei_hw_ops { /* MEI bus API*/ void mei_cl_bus_rescan_work(struct work_struct *work); void mei_cl_bus_dev_fixup(struct mei_cl_device *dev); -ssize_t __mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length, u8 vtag, +ssize_t __mei_cl_send(struct mei_cl *cl, const u8 *buf, size_t length, u8 vtag, unsigned int mode); +ssize_t __mei_cl_send_timeout(struct mei_cl *cl, const u8 *buf, size_t length, u8 vtag, + unsigned int mode, unsigned long timeout); ssize_t __mei_cl_recv(struct mei_cl *cl, u8 *buf, size_t length, u8 *vtag, unsigned int mode, unsigned long timeout); bool mei_cl_bus_rx_event(struct mei_cl *cl); @@ -415,11 +457,24 @@ struct mei_fw_version { #define MEI_MAX_FW_VER_BLOCKS 3 +struct mei_dev_timeouts { + unsigned long hw_ready; /* Timeout on ready message, in jiffies */ + int connect; /* HPS: at least 2 seconds, in seconds */ + unsigned long cl_connect; /* HPS: Client Connect Timeout, in jiffies */ + int client_init; /* HPS: Clients Enumeration Timeout, in seconds */ + unsigned long pgi; /* PG Isolation time response, in jiffies */ + unsigned int d0i3; /* D0i3 set/unset max response time, in jiffies */ + unsigned long hbm; /* HBM operation timeout, in jiffies */ + unsigned long mkhi_recv; /* receive timeout, in jiffies */ + unsigned long link_reset_wait; /* link reset wait timeout, in jiffies */ +}; + /** * struct mei_device - MEI private device struct * - * @dev : device on a bus - * @cdev : character device + * @parent : device on a bus + * @dev : device object + * @cdev : character device pointer * @minor : minor number allocated for device * * @write_list : write pending list @@ -442,7 +497,9 @@ struct mei_fw_version { * * @reset_count : number of consecutive resets * @dev_state : device state + * @wait_dev_state: wait queue for device state change * @hbm_state : state of host bus message protocol + * @pxp_mode : PXP device mode * @init_clients_timer : HBM init handshake timeout * * @pg_event : power gating event @@ -467,10 +524,12 @@ struct mei_fw_version { * @hbm_f_vt_supported : hbm feature vtag supported * @hbm_f_cap_supported : hbm feature capabilities message supported * @hbm_f_cd_supported : hbm feature client dma supported + * @hbm_f_gsc_supported : hbm feature gsc supported * * @fw_ver : FW versions * * @fw_f_fw_ver_supported : fw feature: fw version supported + * @fw_ver_received : fw version received * * @me_clients_rwsem: rw lock over me_clients list * @me_clients : list of FW clients @@ -480,6 +539,8 @@ struct mei_fw_version { * @allow_fixed_address: allow user space to connect a fixed client * @override_fixed_address: force allow fixed address behavior * + * @timeouts: actual timeout values + * * @reset_work : work item for the device reset * @bus_rescan_work : work item for the bus rescan * @@ -490,12 +551,15 @@ struct mei_fw_version { * * @dbgfs_dir : debugfs mei root directory * + * @gsc_reset_to_pxp : state of reset to the PXP mode + * * @ops: : hw specific operations * @hw : hw specific data */ struct mei_device { - struct device *dev; - struct cdev cdev; + struct device *parent; + struct device dev; + struct cdev *cdev; int minor; struct list_head write_list; @@ -523,7 +587,9 @@ struct mei_device { */ unsigned long reset_count; enum mei_dev_state dev_state; + wait_queue_head_t wait_dev_state; enum mei_hbm_state hbm_state; + enum mei_dev_pxp_mode pxp_mode; u16 init_clients_timer; /* @@ -555,10 +621,12 @@ struct mei_device { unsigned int hbm_f_vt_supported:1; unsigned int hbm_f_cap_supported:1; unsigned int hbm_f_cd_supported:1; + unsigned int hbm_f_gsc_supported:1; struct mei_fw_version fw_ver[MEI_MAX_FW_VER_BLOCKS]; unsigned int fw_f_fw_ver_supported:1; + unsigned int fw_ver_received:1; struct rw_semaphore me_clients_rwsem; struct list_head me_clients; @@ -568,6 +636,8 @@ struct mei_device { bool allow_fixed_address; bool override_fixed_address; + struct mei_dev_timeouts timeouts; + struct work_struct reset_work; struct work_struct bus_rescan_work; @@ -581,6 +651,8 @@ struct mei_device { struct dentry *dbgfs_dir; #endif /* CONFIG_DEBUG_FS */ + enum mei_dev_reset_to_pxp gsc_reset_to_pxp; + const struct mei_hw_ops *ops; char hw[] __aligned(sizeof(void *)); }; @@ -631,7 +703,8 @@ static inline u32 mei_slots2data(int slots) * mei init function prototypes */ void mei_device_init(struct mei_device *dev, - struct device *device, + struct device *parent, + bool slow_fw, const struct mei_hw_ops *hw_ops); int mei_reset(struct mei_device *dev); int mei_start(struct mei_device *dev); @@ -824,5 +897,29 @@ static inline ssize_t mei_fw_status_str(struct mei_device *dev, return ret; } +/** + * kind_is_gsc - checks whether the device is gsc + * + * @dev: the device structure + * + * Return: whether the device is gsc + */ +static inline bool kind_is_gsc(struct mei_device *dev) +{ + /* check kind for NULL because it may be not set, like at the fist call to hw_start */ + return dev->kind && (strcmp(dev->kind, "gsc") == 0); +} +/** + * kind_is_gscfi - checks whether the device is gscfi + * + * @dev: the device structure + * + * Return: whether the device is gscfi + */ +static inline bool kind_is_gscfi(struct mei_device *dev) +{ + /* check kind for NULL because it may be not set, like at the fist call to hw_start */ + return dev->kind && (strcmp(dev->kind, "gscfi") == 0); +} #endif diff --git a/drivers/misc/mei/mei_lb.c b/drivers/misc/mei/mei_lb.c new file mode 100644 index 000000000000..78717ee8ac9a --- /dev/null +++ b/drivers/misc/mei/mei_lb.c @@ -0,0 +1,311 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2025 Intel Corporation + */ + +#include <linux/component.h> +#include <linux/mei_cl_bus.h> +#include <linux/module.h> +#include <linux/overflow.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/uuid.h> + +#include <drm/intel/i915_component.h> +#include <drm/intel/intel_lb_mei_interface.h> + +#include "mkhi.h" + +/** + * DOC: Late Binding Firmware Update/Upload + * + * Late Binding is a firmware update/upload mechanism that allows configuration + * payloads to be securely delivered and applied at runtime, rather than + * being embedded in the system firmware image (e.g., IFWI or SPI flash). + * + * This mechanism is used to update device-level configuration such as: + * - Fan controller + * - Voltage regulator (VR) + * + * Key Characteristics: + * --------------------- + * - Runtime Delivery: + * Firmware blobs are loaded by the host driver (e.g., Xe KMD) + * after the GPU or SoC has booted. + * + * - Secure and Authenticated: + * All payloads are signed and verified by the authentication firmware. + * + * - No Firmware Flashing Required: + * Updates are applied in volatile memory and do not require SPI flash + * modification or system reboot. + * + * - Re-entrant: + * Multiple updates of the same or different types can be applied + * sequentially within a single boot session. + * + * - Version Controlled: + * Each payload includes version and security version number (SVN) + * metadata to support anti-rollback enforcement. + * + * Upload Flow: + * ------------ + * 1. Host driver (KMD or user-space tool) loads the late binding firmware. + * 2. Firmware is passed to the MEI interface and forwarded to + * authentication firmware. + * 3. Authentication firmware authenticates the payload and extracts + * command and data arrays. + * 4. Authentication firmware delivers the configuration to PUnit/PCODE. + * 5. Status is returned back to the host via MEI. + */ + +#define INTEL_LB_CMD 0x12 +#define INTEL_LB_RSP (INTEL_LB_CMD | 0x80) + +#define INTEL_LB_SEND_TIMEOUT_MSEC 3000 +#define INTEL_LB_RECV_TIMEOUT_MSEC 3000 + +/** + * struct mei_lb_req - Late Binding request structure + * @header: MKHI message header (see struct mkhi_msg_hdr) + * @type: Type of the Late Binding payload + * @flags: Flags to be passed to the authentication firmware (e.g. %INTEL_LB_FLAGS_IS_PERSISTENT) + * @reserved: Reserved for future use by authentication firmware, must be set to 0 + * @payload_size: Size of the payload data in bytes + * @payload: Payload data to be sent to the authentication firmware + */ +struct mei_lb_req { + struct mkhi_msg_hdr header; + __le32 type; + __le32 flags; + __le32 reserved[2]; + __le32 payload_size; + u8 payload[] __counted_by(payload_size); +} __packed; + +/** + * struct mei_lb_rsp - Late Binding response structure + * @header: MKHI message header (see struct mkhi_msg_hdr) + * @type: Type of the Late Binding payload + * @reserved: Reserved for future use by authentication firmware, must be set to 0 + * @status: Status returned by authentication firmware (see &enum intel_lb_status) + */ +struct mei_lb_rsp { + struct mkhi_msg_hdr header; + __le32 type; + __le32 reserved[2]; + __le32 status; +} __packed; + +static bool mei_lb_check_response(const struct device *dev, ssize_t bytes, + struct mei_lb_rsp *rsp) +{ + /* + * Received message size may be smaller than the full message size when + * reply contains only MKHI header with result field set to the error code. + * Check the header size and content first to output exact error, if needed, + * and then process to the whole message. + */ + if (bytes < sizeof(rsp->header)) { + dev_err(dev, "Received less than header size from the firmware: %zd < %zu\n", + bytes, sizeof(rsp->header)); + return false; + } + if (rsp->header.group_id != MKHI_GROUP_ID_GFX) { + dev_err(dev, "Mismatch group id: 0x%x instead of 0x%x\n", + rsp->header.group_id, MKHI_GROUP_ID_GFX); + return false; + } + if (rsp->header.command != INTEL_LB_RSP) { + dev_err(dev, "Mismatch command: 0x%x instead of 0x%x\n", + rsp->header.command, INTEL_LB_RSP); + return false; + } + if (rsp->header.result) { + dev_err(dev, "Error in result: 0x%x\n", rsp->header.result); + return false; + } + if (bytes < sizeof(*rsp)) { + dev_err(dev, "Received less than message size from the firmware: %zd < %zu\n", + bytes, sizeof(*rsp)); + return false; + } + + return true; +} + +static int mei_lb_push_payload(struct device *dev, u32 type, u32 flags, + const void *payload, size_t payload_size) +{ + struct mei_cl_device *cldev; + struct mei_lb_req *req = NULL; + struct mei_lb_rsp rsp; + size_t req_size; + ssize_t bytes; + int ret; + + cldev = to_mei_cl_device(dev); + + ret = mei_cldev_enable(cldev); + if (ret) { + dev_dbg(dev, "Failed to enable firmware client. %d\n", ret); + return ret; + } + + req_size = struct_size(req, payload, payload_size); + if (req_size > mei_cldev_mtu(cldev)) { + dev_err(dev, "Payload is too big: %zu\n", payload_size); + ret = -EMSGSIZE; + goto end; + } + + req = kmalloc(req_size, GFP_KERNEL); + if (!req) { + ret = -ENOMEM; + goto end; + } + + req->header.group_id = MKHI_GROUP_ID_GFX; + req->header.command = INTEL_LB_CMD; + req->type = cpu_to_le32(type); + req->flags = cpu_to_le32(flags); + req->reserved[0] = 0; + req->reserved[1] = 0; + req->payload_size = cpu_to_le32(payload_size); + memcpy(req->payload, payload, payload_size); + + bytes = mei_cldev_send_timeout(cldev, (u8 *)req, req_size, + INTEL_LB_SEND_TIMEOUT_MSEC); + if (bytes < 0) { + dev_err(dev, "Failed to send late binding request to firmware. %zd\n", bytes); + ret = bytes; + goto end; + } + + bytes = mei_cldev_recv_timeout(cldev, (u8 *)&rsp, sizeof(rsp), + INTEL_LB_RECV_TIMEOUT_MSEC); + if (bytes < 0) { + dev_err(dev, "Failed to receive late binding reply from MEI firmware. %zd\n", + bytes); + ret = bytes; + goto end; + } + if (!mei_lb_check_response(dev, bytes, &rsp)) { + dev_err(dev, "Bad response from the firmware. header: %02x %02x %02x %02x\n", + rsp.header.group_id, rsp.header.command, + rsp.header.reserved, rsp.header.result); + ret = -EPROTO; + goto end; + } + + dev_dbg(dev, "status = %u\n", le32_to_cpu(rsp.status)); + ret = (int)le32_to_cpu(rsp.status); +end: + mei_cldev_disable(cldev); + kfree(req); + return ret; +} + +static const struct intel_lb_component_ops mei_lb_ops = { + .push_payload = mei_lb_push_payload, +}; + +static int mei_lb_component_master_bind(struct device *dev) +{ + return component_bind_all(dev, (void *)&mei_lb_ops); +} + +static void mei_lb_component_master_unbind(struct device *dev) +{ + component_unbind_all(dev, (void *)&mei_lb_ops); +} + +static const struct component_master_ops mei_lb_component_master_ops = { + .bind = mei_lb_component_master_bind, + .unbind = mei_lb_component_master_unbind, +}; + +static int mei_lb_component_match(struct device *dev, int subcomponent, + void *data) +{ + /* + * This function checks if requester is Intel %PCI_CLASS_DISPLAY_VGA or + * %PCI_CLASS_DISPLAY_OTHER device, and checks if the requester is the + * grand parent of mei_if i.e. late bind MEI device + */ + struct device *base = data; + struct pci_dev *pdev; + + if (!dev) + return 0; + + if (!dev_is_pci(dev)) + return 0; + + pdev = to_pci_dev(dev); + + if (pdev->vendor != PCI_VENDOR_ID_INTEL) + return 0; + + if (pdev->class != (PCI_CLASS_DISPLAY_VGA << 8) && + pdev->class != (PCI_CLASS_DISPLAY_OTHER << 8)) + return 0; + + if (subcomponent != INTEL_COMPONENT_LB) + return 0; + + base = base->parent; + if (!base) /* mei device */ + return 0; + + base = base->parent; /* pci device */ + + return !!base && dev == base; +} + +static int mei_lb_probe(struct mei_cl_device *cldev, + const struct mei_cl_device_id *id) +{ + struct component_match *master_match = NULL; + int ret; + + component_match_add_typed(&cldev->dev, &master_match, + mei_lb_component_match, &cldev->dev); + if (IS_ERR_OR_NULL(master_match)) + return -ENOMEM; + + ret = component_master_add_with_match(&cldev->dev, + &mei_lb_component_master_ops, + master_match); + if (ret < 0) + dev_err(&cldev->dev, "Failed to add late binding master component. %d\n", ret); + + return ret; +} + +static void mei_lb_remove(struct mei_cl_device *cldev) +{ + component_master_del(&cldev->dev, &mei_lb_component_master_ops); +} + +#define MEI_GUID_MKHI UUID_LE(0xe2c2afa2, 0x3817, 0x4d19, \ + 0x9d, 0x95, 0x6, 0xb1, 0x6b, 0x58, 0x8a, 0x5d) + +static const struct mei_cl_device_id mei_lb_tbl[] = { + { .uuid = MEI_GUID_MKHI, .version = MEI_CL_VERSION_ANY }, + { } +}; +MODULE_DEVICE_TABLE(mei, mei_lb_tbl); + +static struct mei_cl_driver mei_lb_driver = { + .id_table = mei_lb_tbl, + .name = "mei_lb", + .probe = mei_lb_probe, + .remove = mei_lb_remove, +}; + +module_mei_cl_driver(mei_lb_driver); + +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("MEI Late Binding Firmware Update/Upload"); diff --git a/drivers/misc/mei/mkhi.h b/drivers/misc/mei/mkhi.h new file mode 100644 index 000000000000..1473ea489666 --- /dev/null +++ b/drivers/misc/mei/mkhi.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2003-2022, Intel Corporation. All rights reserved. + * Intel Management Engine Interface (Intel MEI) Linux driver + */ + +#ifndef _MEI_MKHI_H_ +#define _MEI_MKHI_H_ + +#include <linux/types.h> + +#define MKHI_FEATURE_PTT 0x10 + +#define MKHI_FWCAPS_GROUP_ID 0x3 +#define MKHI_FWCAPS_SET_OS_VER_APP_RULE_CMD 6 +#define MKHI_GEN_GROUP_ID 0xFF +#define MKHI_GEN_GET_FW_VERSION_CMD 0x2 + +#define MKHI_GROUP_ID_GFX 0x30 +#define MKHI_GFX_RESET_WARN_CMD_REQ 0x0 +#define MKHI_GFX_MEMORY_READY_CMD_REQ 0x1 + +/* Allow transition to PXP mode without approval */ +#define MKHI_GFX_MEM_READY_PXP_ALLOWED 0x1 + +struct mkhi_rule_id { + __le16 rule_type; + u8 feature_id; + u8 reserved; +} __packed; + +struct mkhi_fwcaps { + struct mkhi_rule_id id; + u8 len; + u8 data[]; +} __packed; + +struct mkhi_msg_hdr { + u8 group_id; + u8 command; + u8 reserved; + u8 result; +} __packed; + +struct mkhi_msg { + struct mkhi_msg_hdr hdr; + u8 data[]; +} __packed; + +struct mkhi_gfx_mem_ready { + struct mkhi_msg_hdr hdr; + u32 flags; +} __packed; + +#endif /* _MEI_MKHI_H_ */ diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c index c3393b383e59..73cad914be9f 100644 --- a/drivers/misc/mei/pci-me.c +++ b/drivers/misc/mei/pci-me.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Copyright (c) 2003-2020, Intel Corporation. All rights reserved. + * Copyright (c) 2003-2022, Intel Corporation. All rights reserved. * Intel Management Engine Interface (Intel MEI) Linux driver */ @@ -10,6 +10,7 @@ #include <linux/errno.h> #include <linux/types.h> #include <linux/pci.h> +#include <linux/dma-mapping.h> #include <linux/sched.h> #include <linux/interrupt.h> @@ -96,6 +97,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { {MEI_PCI_DEVICE(MEI_DEV_ID_CMP_H_3, MEI_ME_PCH8_ITOUCH_CFG)}, {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP, MEI_ME_PCH12_CFG)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_N, MEI_ME_PCH12_CFG)}, {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)}, {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_H, MEI_ME_PCH15_SPS_CFG)}, @@ -112,6 +114,20 @@ static const struct pci_device_id mei_me_pci_tbl[] = { {MEI_PCI_DEVICE(MEI_DEV_ID_ADP_S, MEI_ME_PCH15_CFG)}, {MEI_PCI_DEVICE(MEI_DEV_ID_ADP_LP, MEI_ME_PCH15_CFG)}, {MEI_PCI_DEVICE(MEI_DEV_ID_ADP_P, MEI_ME_PCH15_CFG)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ADP_N, MEI_ME_PCH15_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_RPL_S, MEI_ME_PCH15_SPS_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_MTL_M, MEI_ME_PCH15_CFG)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ARL_S, MEI_ME_PCH15_CFG)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ARL_H, MEI_ME_PCH15_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_LNL_M, MEI_ME_PCH15_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_PTL_H, MEI_ME_PCH15_CFG)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_PTL_P, MEI_ME_PCH15_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_WCL_P, MEI_ME_PCH15_CFG)}, /* required last entry */ {0, } @@ -129,7 +145,7 @@ static inline void mei_me_unset_pm_domain(struct mei_device *dev) {} static int mei_me_read_fws(const struct mei_device *dev, int where, u32 *val) { - struct pci_dev *pdev = to_pci_dev(dev->dev); + struct pci_dev *pdev = to_pci_dev(dev->parent); return pci_read_config_dword(pdev, where, val); } @@ -191,21 +207,14 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent) goto end; } - if (dma_set_mask(&pdev->dev, DMA_BIT_MASK(64)) || - dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(64))) { - - err = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32)); - if (err) - err = dma_set_coherent_mask(&pdev->dev, - DMA_BIT_MASK(32)); - } + err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); if (err) { dev_err(&pdev->dev, "No usable DMA configuration, aborting\n"); goto end; } /* allocates and initializes the mei dev structure */ - dev = mei_me_dev_init(&pdev->dev, cfg); + dev = mei_me_dev_init(&pdev->dev, cfg, false); if (!dev) { err = -ENOMEM; goto end; @@ -214,6 +223,10 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent) hw->mem_addr = pcim_iomap_table(pdev)[0]; hw->read_fws = mei_me_read_fws; + err = mei_register(dev, &pdev->dev); + if (err) + goto end; + pci_enable_msi(pdev); hw->irq = pdev->irq; @@ -228,22 +241,18 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent) if (err) { dev_err(&pdev->dev, "request_threaded_irq failure. irq = %d\n", pdev->irq); - goto end; + goto deregister; } if (mei_start(dev)) { dev_err(&pdev->dev, "init hw failure.\n"); err = -ENODEV; - goto release_irq; + goto deregister; } pm_runtime_set_autosuspend_delay(&pdev->dev, MEI_ME_RPM_TIMEOUT); pm_runtime_use_autosuspend(&pdev->dev); - err = mei_register(dev, &pdev->dev); - if (err) - goto stop; - pci_set_drvdata(pdev, dev); /* @@ -273,12 +282,11 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent) return 0; -stop: - mei_stop(dev); -release_irq: +deregister: mei_cancel_work(dev); mei_disable_interrupts(dev); free_irq(pdev->irq, dev); + mei_deregister(dev); end: dev_err(&pdev->dev, "initialization failed.\n"); return err; @@ -295,11 +303,7 @@ end: */ static void mei_me_shutdown(struct pci_dev *pdev) { - struct mei_device *dev; - - dev = pci_get_drvdata(pdev); - if (!dev) - return; + struct mei_device *dev = pci_get_drvdata(pdev); dev_dbg(&pdev->dev, "shutdown\n"); mei_stop(dev); @@ -320,11 +324,7 @@ static void mei_me_shutdown(struct pci_dev *pdev) */ static void mei_me_remove(struct pci_dev *pdev) { - struct mei_device *dev; - - dev = pci_get_drvdata(pdev); - if (!dev) - return; + struct mei_device *dev = pci_get_drvdata(pdev); if (mei_pg_is_enabled(dev)) pm_runtime_get_noresume(&pdev->dev); @@ -342,14 +342,17 @@ static void mei_me_remove(struct pci_dev *pdev) } #ifdef CONFIG_PM_SLEEP +static int mei_me_pci_prepare(struct device *device) +{ + pm_runtime_resume(device); + return 0; +} + static int mei_me_pci_suspend(struct device *device) { struct pci_dev *pdev = to_pci_dev(device); struct mei_device *dev = pci_get_drvdata(pdev); - if (!dev) - return -ENODEV; - dev_dbg(&pdev->dev, "suspend\n"); mei_stop(dev); @@ -365,14 +368,10 @@ static int mei_me_pci_suspend(struct device *device) static int mei_me_pci_resume(struct device *device) { struct pci_dev *pdev = to_pci_dev(device); - struct mei_device *dev; + struct mei_device *dev = pci_get_drvdata(pdev); unsigned int irqflags; int err; - dev = pci_get_drvdata(pdev); - if (!dev) - return -ENODEV; - pci_enable_msi(pdev); irqflags = pci_dev_msi_enabled(pdev) ? IRQF_ONESHOT : IRQF_SHARED; @@ -390,26 +389,35 @@ static int mei_me_pci_resume(struct device *device) } err = mei_restart(dev); - if (err) + if (err) { + free_irq(pdev->irq, dev); return err; + } /* Start timer if stopped in suspend */ schedule_delayed_work(&dev->timer_work, HZ); return 0; } -#endif /* CONFIG_PM_SLEEP */ + +static void mei_me_pci_complete(struct device *device) +{ + pm_runtime_suspend(device); +} +#else /* CONFIG_PM_SLEEP */ + +#define mei_me_pci_prepare NULL +#define mei_me_pci_complete NULL + +#endif /* !CONFIG_PM_SLEEP */ #ifdef CONFIG_PM static int mei_me_pm_runtime_idle(struct device *device) { - struct mei_device *dev; + struct mei_device *dev = dev_get_drvdata(device); dev_dbg(device, "rpm: me: runtime_idle\n"); - dev = dev_get_drvdata(device); - if (!dev) - return -ENODEV; if (mei_write_is_idle(dev)) pm_runtime_autosuspend(device); @@ -418,15 +426,11 @@ static int mei_me_pm_runtime_idle(struct device *device) static int mei_me_pm_runtime_suspend(struct device *device) { - struct mei_device *dev; + struct mei_device *dev = dev_get_drvdata(device); int ret; dev_dbg(device, "rpm: me: runtime suspend\n"); - dev = dev_get_drvdata(device); - if (!dev) - return -ENODEV; - mutex_lock(&dev->device_lock); if (mei_write_is_idle(dev)) @@ -446,15 +450,11 @@ static int mei_me_pm_runtime_suspend(struct device *device) static int mei_me_pm_runtime_resume(struct device *device) { - struct mei_device *dev; + struct mei_device *dev = dev_get_drvdata(device); int ret; dev_dbg(device, "rpm: me: runtime resume\n"); - dev = dev_get_drvdata(device); - if (!dev) - return -ENODEV; - mutex_lock(&dev->device_lock); ret = mei_me_pg_exit_sync(dev); @@ -476,7 +476,7 @@ static int mei_me_pm_runtime_resume(struct device *device) */ static inline void mei_me_set_pm_domain(struct mei_device *dev) { - struct pci_dev *pdev = to_pci_dev(dev->dev); + struct pci_dev *pdev = to_pci_dev(dev->parent); if (pdev->dev.bus && pdev->dev.bus->pm) { dev->pg_domain.ops = *pdev->dev.bus->pm; @@ -497,10 +497,12 @@ static inline void mei_me_set_pm_domain(struct mei_device *dev) static inline void mei_me_unset_pm_domain(struct mei_device *dev) { /* stop using pm callbacks if any */ - dev_pm_domain_set(dev->dev, NULL); + dev_pm_domain_set(dev->parent, NULL); } static const struct dev_pm_ops mei_me_pm_ops = { + .prepare = mei_me_pci_prepare, + .complete = mei_me_pci_complete, SET_SYSTEM_SLEEP_PM_OPS(mei_me_pci_suspend, mei_me_pci_resume) SET_RUNTIME_PM_OPS( diff --git a/drivers/misc/mei/pci-txe.c b/drivers/misc/mei/pci-txe.c index aec0483b8e72..98d1bc2c7f4b 100644 --- a/drivers/misc/mei/pci-txe.c +++ b/drivers/misc/mei/pci-txe.c @@ -69,9 +69,9 @@ static int mei_txe_probe(struct pci_dev *pdev, const struct pci_device_id *ent) goto end; } - err = pci_set_dma_mask(pdev, DMA_BIT_MASK(36)); + err = dma_set_mask(&pdev->dev, DMA_BIT_MASK(36)); if (err) { - err = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); + err = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32)); if (err) { dev_err(&pdev->dev, "No suitable DMA available.\n"); goto end; @@ -87,6 +87,10 @@ static int mei_txe_probe(struct pci_dev *pdev, const struct pci_device_id *ent) hw = to_txe_hw(dev); hw->mem_addr = pcim_iomap_table(pdev); + err = mei_register(dev, &pdev->dev); + if (err) + goto end; + pci_enable_msi(pdev); /* clear spurious interrupts */ @@ -106,22 +110,18 @@ static int mei_txe_probe(struct pci_dev *pdev, const struct pci_device_id *ent) if (err) { dev_err(&pdev->dev, "mei: request_threaded_irq failure. irq = %d\n", pdev->irq); - goto end; + goto deregister; } if (mei_start(dev)) { dev_err(&pdev->dev, "init hw failure.\n"); err = -ENODEV; - goto release_irq; + goto deregister; } pm_runtime_set_autosuspend_delay(&pdev->dev, MEI_TXI_RPM_TIMEOUT); pm_runtime_use_autosuspend(&pdev->dev); - err = mei_register(dev, &pdev->dev); - if (err) - goto stop; - pci_set_drvdata(pdev, dev); /* @@ -144,12 +144,11 @@ static int mei_txe_probe(struct pci_dev *pdev, const struct pci_device_id *ent) return 0; -stop: - mei_stop(dev); -release_irq: +deregister: mei_cancel_work(dev); mei_disable_interrupts(dev); free_irq(pdev->irq, dev); + mei_deregister(dev); end: dev_err(&pdev->dev, "initialization failed.\n"); return err; @@ -166,11 +165,7 @@ end: */ static void mei_txe_shutdown(struct pci_dev *pdev) { - struct mei_device *dev; - - dev = pci_get_drvdata(pdev); - if (!dev) - return; + struct mei_device *dev = pci_get_drvdata(pdev); dev_dbg(&pdev->dev, "shutdown\n"); mei_stop(dev); @@ -191,13 +186,7 @@ static void mei_txe_shutdown(struct pci_dev *pdev) */ static void mei_txe_remove(struct pci_dev *pdev) { - struct mei_device *dev; - - dev = pci_get_drvdata(pdev); - if (!dev) { - dev_err(&pdev->dev, "mei: dev == NULL\n"); - return; - } + struct mei_device *dev = pci_get_drvdata(pdev); pm_runtime_get_noresume(&pdev->dev); @@ -218,9 +207,6 @@ static int mei_txe_pci_suspend(struct device *device) struct pci_dev *pdev = to_pci_dev(device); struct mei_device *dev = pci_get_drvdata(pdev); - if (!dev) - return -ENODEV; - dev_dbg(&pdev->dev, "suspend\n"); mei_stop(dev); @@ -236,13 +222,9 @@ static int mei_txe_pci_suspend(struct device *device) static int mei_txe_pci_resume(struct device *device) { struct pci_dev *pdev = to_pci_dev(device); - struct mei_device *dev; + struct mei_device *dev = pci_get_drvdata(pdev); int err; - dev = pci_get_drvdata(pdev); - if (!dev) - return -ENODEV; - pci_enable_msi(pdev); mei_clear_interrupts(dev); @@ -273,13 +255,10 @@ static int mei_txe_pci_resume(struct device *device) #ifdef CONFIG_PM static int mei_txe_pm_runtime_idle(struct device *device) { - struct mei_device *dev; + struct mei_device *dev = dev_get_drvdata(device); dev_dbg(device, "rpm: txe: runtime_idle\n"); - dev = dev_get_drvdata(device); - if (!dev) - return -ENODEV; if (mei_write_is_idle(dev)) pm_runtime_autosuspend(device); @@ -287,15 +266,11 @@ static int mei_txe_pm_runtime_idle(struct device *device) } static int mei_txe_pm_runtime_suspend(struct device *device) { - struct mei_device *dev; + struct mei_device *dev = dev_get_drvdata(device); int ret; dev_dbg(device, "rpm: txe: runtime suspend\n"); - dev = dev_get_drvdata(device); - if (!dev) - return -ENODEV; - mutex_lock(&dev->device_lock); if (mei_write_is_idle(dev)) @@ -317,15 +292,11 @@ static int mei_txe_pm_runtime_suspend(struct device *device) static int mei_txe_pm_runtime_resume(struct device *device) { - struct mei_device *dev; + struct mei_device *dev = dev_get_drvdata(device); int ret; dev_dbg(device, "rpm: txe: runtime resume\n"); - dev = dev_get_drvdata(device); - if (!dev) - return -ENODEV; - mutex_lock(&dev->device_lock); mei_enable_interrupts(dev); @@ -349,7 +320,7 @@ static int mei_txe_pm_runtime_resume(struct device *device) */ static inline void mei_txe_set_pm_domain(struct mei_device *dev) { - struct pci_dev *pdev = to_pci_dev(dev->dev); + struct pci_dev *pdev = to_pci_dev(dev->parent); if (pdev->dev.bus && pdev->dev.bus->pm) { dev->pg_domain.ops = *pdev->dev.bus->pm; @@ -370,7 +341,7 @@ static inline void mei_txe_set_pm_domain(struct mei_device *dev) static inline void mei_txe_unset_pm_domain(struct mei_device *dev) { /* stop using pm callbacks if any */ - dev_pm_domain_set(dev->dev, NULL); + dev_pm_domain_set(dev->parent, NULL); } static const struct dev_pm_ops mei_txe_pm_ops = { diff --git a/drivers/misc/mei/platform-vsc.c b/drivers/misc/mei/platform-vsc.c new file mode 100644 index 000000000000..9787b9cee71c --- /dev/null +++ b/drivers/misc/mei/platform-vsc.c @@ -0,0 +1,459 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023, Intel Corporation. + * Intel Visual Sensing Controller Interface Linux driver + */ + +#include <linux/align.h> +#include <linux/cache.h> +#include <linux/cleanup.h> +#include <linux/iopoll.h> +#include <linux/list.h> +#include <linux/mei.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/overflow.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/timekeeping.h> +#include <linux/types.h> + +#include <asm-generic/bug.h> +#include <linux/unaligned.h> + +#include "mei_dev.h" +#include "vsc-tp.h" + +#define MEI_VSC_DRV_NAME "intel_vsc" + +#define MEI_VSC_MAX_MSG_SIZE 512 + +#define MEI_VSC_POLL_DELAY_US (100 * USEC_PER_MSEC) +#define MEI_VSC_POLL_TIMEOUT_US (400 * USEC_PER_MSEC) + +#define mei_dev_to_vsc_hw(dev) ((struct mei_vsc_hw *)((dev)->hw)) + +struct mei_vsc_host_timestamp { + u64 realtime; + u64 boottime; +}; + +struct mei_vsc_hw { + struct vsc_tp *tp; + + bool fw_ready; + bool host_ready; + + atomic_t write_lock_cnt; + + u32 rx_len; + u32 rx_hdr; + + /* buffer for tx */ + char tx_buf[MEI_VSC_MAX_MSG_SIZE + sizeof(struct mei_msg_hdr)] ____cacheline_aligned; + /* buffer for rx */ + char rx_buf[MEI_VSC_MAX_MSG_SIZE + sizeof(struct mei_msg_hdr)] ____cacheline_aligned; +}; + +static int mei_vsc_read_helper(struct mei_vsc_hw *hw, u8 *buf, + u32 max_len) +{ + struct mei_vsc_host_timestamp ts = { + .realtime = ktime_to_ns(ktime_get_real()), + .boottime = ktime_to_ns(ktime_get_boottime()), + }; + + return vsc_tp_xfer(hw->tp, VSC_TP_CMD_READ, &ts, sizeof(ts), + buf, max_len); +} + +static int mei_vsc_write_helper(struct mei_vsc_hw *hw, u8 *buf, u32 len) +{ + u8 status; + + return vsc_tp_xfer(hw->tp, VSC_TP_CMD_WRITE, buf, len, &status, + sizeof(status)); +} + +static int mei_vsc_fw_status(struct mei_device *mei_dev, + struct mei_fw_status *fw_status) +{ + if (!fw_status) + return -EINVAL; + + fw_status->count = 0; + + return 0; +} + +static inline enum mei_pg_state mei_vsc_pg_state(struct mei_device *mei_dev) +{ + return MEI_PG_OFF; +} + +static void mei_vsc_intr_enable(struct mei_device *mei_dev) +{ + struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev); + + vsc_tp_intr_enable(hw->tp); +} + +static void mei_vsc_intr_disable(struct mei_device *mei_dev) +{ + struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev); + + vsc_tp_intr_disable(hw->tp); +} + +/* mei framework requires this ops */ +static void mei_vsc_intr_clear(struct mei_device *mei_dev) +{ +} + +/* wait for pending irq handler */ +static void mei_vsc_synchronize_irq(struct mei_device *mei_dev) +{ + struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev); + + vsc_tp_intr_synchronize(hw->tp); +} + +static int mei_vsc_hw_config(struct mei_device *mei_dev) +{ + return 0; +} + +static bool mei_vsc_host_is_ready(struct mei_device *mei_dev) +{ + struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev); + + return hw->host_ready; +} + +static bool mei_vsc_hw_is_ready(struct mei_device *mei_dev) +{ + struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev); + + return hw->fw_ready; +} + +static int mei_vsc_hw_start(struct mei_device *mei_dev) +{ + struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev); + int ret, rlen; + u8 buf; + + hw->host_ready = true; + + vsc_tp_intr_enable(hw->tp); + + ret = read_poll_timeout(mei_vsc_read_helper, rlen, + rlen >= 0, MEI_VSC_POLL_DELAY_US, + MEI_VSC_POLL_TIMEOUT_US, true, + hw, &buf, sizeof(buf)); + if (ret) { + dev_err(&mei_dev->dev, "wait fw ready failed: %d\n", ret); + return ret; + } + + hw->fw_ready = true; + + return 0; +} + +static bool mei_vsc_hbuf_is_ready(struct mei_device *mei_dev) +{ + struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev); + + return atomic_read(&hw->write_lock_cnt) == 0; +} + +static int mei_vsc_hbuf_empty_slots(struct mei_device *mei_dev) +{ + return MEI_VSC_MAX_MSG_SIZE / MEI_SLOT_SIZE; +} + +static u32 mei_vsc_hbuf_depth(const struct mei_device *mei_dev) +{ + return MEI_VSC_MAX_MSG_SIZE / MEI_SLOT_SIZE; +} + +static int mei_vsc_write(struct mei_device *mei_dev, + const void *hdr, size_t hdr_len, + const void *data, size_t data_len) +{ + struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev); + char *buf = hw->tx_buf; + int ret; + + if (WARN_ON(!hdr || !IS_ALIGNED(hdr_len, 4))) + return -EINVAL; + + if (!data || data_len > MEI_VSC_MAX_MSG_SIZE) + return -EINVAL; + + atomic_inc(&hw->write_lock_cnt); + + memcpy(buf, hdr, hdr_len); + memcpy(buf + hdr_len, data, data_len); + + ret = mei_vsc_write_helper(hw, buf, hdr_len + data_len); + + atomic_dec_if_positive(&hw->write_lock_cnt); + + return ret < 0 ? ret : 0; +} + +static inline u32 mei_vsc_read(const struct mei_device *mei_dev) +{ + struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev); + int ret; + + ret = mei_vsc_read_helper(hw, hw->rx_buf, sizeof(hw->rx_buf)); + if (ret < 0 || ret < sizeof(u32)) + return 0; + hw->rx_len = ret; + + hw->rx_hdr = get_unaligned_le32(hw->rx_buf); + + return hw->rx_hdr; +} + +static int mei_vsc_count_full_read_slots(struct mei_device *mei_dev) +{ + return MEI_VSC_MAX_MSG_SIZE / MEI_SLOT_SIZE; +} + +static int mei_vsc_read_slots(struct mei_device *mei_dev, unsigned char *buf, + unsigned long len) +{ + struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev); + struct mei_msg_hdr *hdr; + + hdr = (struct mei_msg_hdr *)&hw->rx_hdr; + if (len != hdr->length || hdr->length + sizeof(*hdr) != hw->rx_len) + return -EINVAL; + + memcpy(buf, hw->rx_buf + sizeof(*hdr), len); + + return 0; +} + +static bool mei_vsc_pg_in_transition(struct mei_device *mei_dev) +{ + return mei_dev->pg_event >= MEI_PG_EVENT_WAIT && + mei_dev->pg_event <= MEI_PG_EVENT_INTR_WAIT; +} + +static bool mei_vsc_pg_is_enabled(struct mei_device *mei_dev) +{ + return false; +} + +static int mei_vsc_hw_reset(struct mei_device *mei_dev, bool intr_enable) +{ + struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev); + + vsc_tp_reset(hw->tp); + + if (!intr_enable) + return 0; + + return vsc_tp_init(hw->tp, mei_dev->parent); +} + +static const struct mei_hw_ops mei_vsc_hw_ops = { + .fw_status = mei_vsc_fw_status, + .pg_state = mei_vsc_pg_state, + + .host_is_ready = mei_vsc_host_is_ready, + .hw_is_ready = mei_vsc_hw_is_ready, + .hw_reset = mei_vsc_hw_reset, + .hw_config = mei_vsc_hw_config, + .hw_start = mei_vsc_hw_start, + + .pg_in_transition = mei_vsc_pg_in_transition, + .pg_is_enabled = mei_vsc_pg_is_enabled, + + .intr_clear = mei_vsc_intr_clear, + .intr_enable = mei_vsc_intr_enable, + .intr_disable = mei_vsc_intr_disable, + .synchronize_irq = mei_vsc_synchronize_irq, + + .hbuf_free_slots = mei_vsc_hbuf_empty_slots, + .hbuf_is_ready = mei_vsc_hbuf_is_ready, + .hbuf_depth = mei_vsc_hbuf_depth, + .write = mei_vsc_write, + + .rdbuf_full_slots = mei_vsc_count_full_read_slots, + .read_hdr = mei_vsc_read, + .read = mei_vsc_read_slots, +}; + +static void mei_vsc_event_cb(void *context) +{ + struct mei_device *mei_dev = context; + struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev); + struct list_head cmpl_list; + s32 slots; + int ret; + + if (mei_dev->dev_state == MEI_DEV_RESETTING || + mei_dev->dev_state == MEI_DEV_INITIALIZING) + return; + + INIT_LIST_HEAD(&cmpl_list); + + guard(mutex)(&mei_dev->device_lock); + + while (vsc_tp_need_read(hw->tp)) { + /* check slots available for reading */ + slots = mei_count_full_read_slots(mei_dev); + + ret = mei_irq_read_handler(mei_dev, &cmpl_list, &slots); + if (ret) { + if (ret != -ENODATA) { + if (mei_dev->dev_state != MEI_DEV_RESETTING && + mei_dev->dev_state != MEI_DEV_POWER_DOWN) + schedule_work(&mei_dev->reset_work); + } + + return; + } + } + + mei_dev->hbuf_is_ready = mei_hbuf_is_ready(mei_dev); + ret = mei_irq_write_handler(mei_dev, &cmpl_list); + if (ret) + dev_err(&mei_dev->dev, "dispatch write request failed: %d\n", ret); + + mei_dev->hbuf_is_ready = mei_hbuf_is_ready(mei_dev); + mei_irq_compl_handler(mei_dev, &cmpl_list); +} + +static int mei_vsc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mei_device *mei_dev; + struct mei_vsc_hw *hw; + struct vsc_tp *tp; + int ret; + + tp = *(struct vsc_tp **)dev_get_platdata(dev); + if (!tp) + return dev_err_probe(dev, -ENODEV, "no platform data\n"); + + mei_dev = kzalloc(size_add(sizeof(*mei_dev), sizeof(*hw)), GFP_KERNEL); + if (!mei_dev) + return -ENOMEM; + + mei_device_init(mei_dev, dev, false, &mei_vsc_hw_ops); + + mei_dev->fw_f_fw_ver_supported = 0; + mei_dev->kind = "ivsc"; + + hw = mei_dev_to_vsc_hw(mei_dev); + atomic_set(&hw->write_lock_cnt, 0); + hw->tp = tp; + + platform_set_drvdata(pdev, mei_dev); + + vsc_tp_register_event_cb(tp, mei_vsc_event_cb, mei_dev); + + ret = mei_register(mei_dev, dev); + if (ret) + goto err; + + ret = mei_start(mei_dev); + if (ret) { + dev_err_probe(dev, ret, "init hw failed\n"); + goto err; + } + + pm_runtime_enable(mei_dev->parent); + + return 0; + +err: + mei_cancel_work(mei_dev); + + vsc_tp_register_event_cb(tp, NULL, NULL); + + mei_disable_interrupts(mei_dev); + + mei_deregister(mei_dev); + + return ret; +} + +static void mei_vsc_remove(struct platform_device *pdev) +{ + struct mei_device *mei_dev = platform_get_drvdata(pdev); + struct mei_vsc_hw *hw = mei_dev_to_vsc_hw(mei_dev); + + pm_runtime_disable(mei_dev->parent); + + mei_stop(mei_dev); + + vsc_tp_register_event_cb(hw->tp, NULL, NULL); + + mei_disable_interrupts(mei_dev); + + mei_deregister(mei_dev); +} + +static int mei_vsc_suspend(struct device *dev) +{ + struct mei_device *mei_dev; + int ret = 0; + + mei_dev = dev_get_drvdata(dev); + if (!mei_dev) + return -ENODEV; + + mutex_lock(&mei_dev->device_lock); + + if (!mei_write_is_idle(mei_dev)) + ret = -EAGAIN; + + mutex_unlock(&mei_dev->device_lock); + + return ret; +} + +static int mei_vsc_resume(struct device *dev) +{ + struct mei_device *mei_dev; + + mei_dev = dev_get_drvdata(dev); + if (!mei_dev) + return -ENODEV; + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(mei_vsc_pm_ops, mei_vsc_suspend, mei_vsc_resume); + +static const struct platform_device_id mei_vsc_id_table[] = { + { MEI_VSC_DRV_NAME }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(platform, mei_vsc_id_table); + +static struct platform_driver mei_vsc_drv = { + .probe = mei_vsc_probe, + .remove = mei_vsc_remove, + .id_table = mei_vsc_id_table, + .driver = { + .name = MEI_VSC_DRV_NAME, + .pm = &mei_vsc_pm_ops, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; +module_platform_driver(mei_vsc_drv); + +MODULE_AUTHOR("Wentong Wu <wentong.wu@intel.com>"); +MODULE_AUTHOR("Zhifeng Wang <zhifeng.wang@intel.com>"); +MODULE_DESCRIPTION("Intel Visual Sensing Controller Interface"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("VSC_TP"); diff --git a/drivers/misc/mei/pxp/Kconfig b/drivers/misc/mei/pxp/Kconfig new file mode 100644 index 000000000000..aa2dece4a927 --- /dev/null +++ b/drivers/misc/mei/pxp/Kconfig @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2020, Intel Corporation. All rights reserved. +# +config INTEL_MEI_PXP + tristate "Intel PXP services of ME Interface" + depends on INTEL_MEI_ME + depends on DRM_I915 || DRM_XE + help + MEI Support for PXP Services on Intel platforms. + + Enables the ME FW services required for PXP support through + I915 display driver of Intel. diff --git a/drivers/misc/mei/pxp/Makefile b/drivers/misc/mei/pxp/Makefile new file mode 100644 index 000000000000..0329950d5794 --- /dev/null +++ b/drivers/misc/mei/pxp/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (c) 2020, Intel Corporation. All rights reserved. +# +# Makefile - PXP client driver for Intel MEI Bus Driver. + +obj-$(CONFIG_INTEL_MEI_PXP) += mei_pxp.o diff --git a/drivers/misc/mei/pxp/mei_pxp.c b/drivers/misc/mei/pxp/mei_pxp.c new file mode 100644 index 000000000000..2820d389c88e --- /dev/null +++ b/drivers/misc/mei/pxp/mei_pxp.c @@ -0,0 +1,344 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright © 2020 - 2021 Intel Corporation + */ + +/** + * DOC: MEI_PXP Client Driver + * + * The mei_pxp driver acts as a translation layer between PXP + * protocol implementer (I915) and ME FW by translating PXP + * negotiation messages to ME FW command payloads and vice versa. + */ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/mei.h> +#include <linux/mei_cl_bus.h> +#include <linux/component.h> +#include <drm/drm_connector.h> +#include <drm/intel/i915_component.h> +#include <drm/intel/i915_pxp_tee_interface.h> + +#include "mei_pxp.h" + +static inline int mei_pxp_reenable(const struct device *dev, struct mei_cl_device *cldev) +{ + int ret; + + dev_warn(dev, "Trying to reset the channel...\n"); + ret = mei_cldev_disable(cldev); + if (ret < 0) + dev_warn(dev, "mei_cldev_disable failed. %d\n", ret); + /* + * Explicitly ignoring disable failure, + * enable may fix the states and succeed + */ + ret = mei_cldev_enable(cldev); + if (ret < 0) + dev_err(dev, "mei_cldev_enable failed. %d\n", ret); + return ret; +} + +/** + * mei_pxp_send_message() - Sends a PXP message to ME FW. + * @dev: device corresponding to the mei_cl_device + * @message: a message buffer to send + * @size: size of the message + * @timeout_ms: timeout in milliseconds, zero means wait indefinitely. + * + * Returns: 0 on Success, <0 on Failure with the following defined failures. + * -ENODEV: Client was not connected. + * Caller may attempt to try again immediately. + * -ENOMEM: Internal memory allocation failure experienced. + * Caller may sleep to allow kernel reclaim before retrying. + * -EINTR : Calling thread received a signal. Caller may choose + * to abandon with the same thread id. + * -ETIME : Request is timed out. + * Caller may attempt to try again immediately. + */ +static int +mei_pxp_send_message(struct device *dev, const void *message, size_t size, unsigned long timeout_ms) +{ + struct mei_cl_device *cldev; + ssize_t byte; + int ret; + + if (!dev || !message) + return -EINVAL; + + cldev = to_mei_cl_device(dev); + + byte = mei_cldev_send_timeout(cldev, message, size, timeout_ms); + if (byte < 0) { + dev_dbg(dev, "mei_cldev_send failed. %zd\n", byte); + switch (byte) { + case -ENOMEM: + fallthrough; + case -ENODEV: + fallthrough; + case -ETIME: + ret = mei_pxp_reenable(dev, cldev); + if (ret) + byte = ret; + break; + } + return byte; + } + + return 0; +} + +/** + * mei_pxp_receive_message() - Receives a PXP message from ME FW. + * @dev: device corresponding to the mei_cl_device + * @buffer: a message buffer to contain the received message + * @size: size of the buffer + * @timeout_ms: timeout in milliseconds, zero means wait indefinitely. + * + * Returns: number of bytes send on Success, <0 on Failure with the following defined failures. + * -ENODEV: Client was not connected. + * Caller may attempt to try again from send immediately. + * -ENOMEM: Internal memory allocation failure experienced. + * Caller may sleep to allow kernel reclaim before retrying. + * -EINTR : Calling thread received a signal. Caller will need to repeat calling + * (with a different owning thread) to retrieve existing unclaimed response + * (and may discard it). + * -ETIME : Request is timed out. + * Caller may attempt to try again from send immediately. + */ +static int +mei_pxp_receive_message(struct device *dev, void *buffer, size_t size, unsigned long timeout_ms) +{ + struct mei_cl_device *cldev; + ssize_t byte; + bool retry = false; + int ret; + + if (!dev || !buffer) + return -EINVAL; + + cldev = to_mei_cl_device(dev); + +retry: + byte = mei_cldev_recv_timeout(cldev, buffer, size, timeout_ms); + if (byte < 0) { + dev_dbg(dev, "mei_cldev_recv failed. %zd\n", byte); + switch (byte) { + case -ENOMEM: + /* Retry the read when pages are reclaimed */ + msleep(20); + if (!retry) { + retry = true; + goto retry; + } + fallthrough; + case -ENODEV: + fallthrough; + case -ETIME: + ret = mei_pxp_reenable(dev, cldev); + if (ret) + byte = ret; + break; + } + } + + return byte; +} + +/** + * mei_pxp_gsc_command() - sends a gsc command, by sending + * a sgl mei message to gsc and receiving reply from gsc + * + * @dev: device corresponding to the mei_cl_device + * @client_id: client id to send the command to + * @fence_id: fence id to send the command to + * @sg_in: scatter gather list containing addresses for rx message buffer + * @total_in_len: total length of data in 'in' sg, can be less than the sum of buffers sizes + * @sg_out: scatter gather list containing addresses for tx message buffer + * + * Return: bytes sent on Success, <0 on Failure + */ +static ssize_t mei_pxp_gsc_command(struct device *dev, u8 client_id, u32 fence_id, + struct scatterlist *sg_in, size_t total_in_len, + struct scatterlist *sg_out) +{ + struct mei_cl_device *cldev; + + cldev = to_mei_cl_device(dev); + + return mei_cldev_send_gsc_command(cldev, client_id, fence_id, sg_in, total_in_len, sg_out); +} + +static const struct i915_pxp_component_ops mei_pxp_ops = { + .owner = THIS_MODULE, + .send = mei_pxp_send_message, + .recv = mei_pxp_receive_message, + .gsc_command = mei_pxp_gsc_command, +}; + +static int mei_component_master_bind(struct device *dev) +{ + struct mei_cl_device *cldev = to_mei_cl_device(dev); + struct i915_pxp_component *comp_master = mei_cldev_get_drvdata(cldev); + int ret; + + comp_master->ops = &mei_pxp_ops; + comp_master->tee_dev = dev; + ret = component_bind_all(dev, comp_master); + if (ret < 0) + return ret; + + return 0; +} + +static void mei_component_master_unbind(struct device *dev) +{ + struct mei_cl_device *cldev = to_mei_cl_device(dev); + struct i915_pxp_component *comp_master = mei_cldev_get_drvdata(cldev); + + component_unbind_all(dev, comp_master); +} + +static const struct component_master_ops mei_component_master_ops = { + .bind = mei_component_master_bind, + .unbind = mei_component_master_unbind, +}; + +/** + * mei_pxp_component_match - compare function for matching mei pxp. + * + * The function checks if the driver is i915, the subcomponent is PXP + * and the grand parent of pxp and the parent of i915 are the same + * PCH device. + * + * @dev: master device + * @subcomponent: subcomponent to match (I915_COMPONENT_PXP) + * @data: compare data (mei pxp device) + * + * Return: + * * 1 - if components match + * * 0 - otherwise + */ +static int mei_pxp_component_match(struct device *dev, int subcomponent, + void *data) +{ + struct device *base = data; + struct pci_dev *pdev; + + if (!dev) + return 0; + + if (!dev_is_pci(dev)) + return 0; + + pdev = to_pci_dev(dev); + + if (pdev->vendor != PCI_VENDOR_ID_INTEL) + return 0; + + if (pdev->class != (PCI_CLASS_DISPLAY_VGA << 8) && + pdev->class != (PCI_CLASS_DISPLAY_OTHER << 8)) + return 0; + + if (subcomponent != I915_COMPONENT_PXP) + return 0; + + base = base->parent; + if (!base) /* mei device */ + return 0; + + base = base->parent; /* pci device */ + /* for dgfx */ + if (base && dev == base) + return 1; + + /* for pch */ + dev = dev->parent; + return (base && dev && dev == base); +} + +static int mei_pxp_probe(struct mei_cl_device *cldev, + const struct mei_cl_device_id *id) +{ + struct i915_pxp_component *comp_master; + struct component_match *master_match; + int ret; + + ret = mei_cldev_enable(cldev); + if (ret < 0) { + dev_err(&cldev->dev, "mei_cldev_enable Failed. %d\n", ret); + goto enable_err_exit; + } + + comp_master = kzalloc(sizeof(*comp_master), GFP_KERNEL); + if (!comp_master) { + ret = -ENOMEM; + goto err_exit; + } + + master_match = NULL; + component_match_add_typed(&cldev->dev, &master_match, + mei_pxp_component_match, &cldev->dev); + if (IS_ERR_OR_NULL(master_match)) { + ret = -ENOMEM; + goto err_exit; + } + + mei_cldev_set_drvdata(cldev, comp_master); + ret = component_master_add_with_match(&cldev->dev, + &mei_component_master_ops, + master_match); + if (ret < 0) { + dev_err(&cldev->dev, "Master comp add failed %d\n", ret); + goto err_exit; + } + + return 0; + +err_exit: + mei_cldev_set_drvdata(cldev, NULL); + kfree(comp_master); + mei_cldev_disable(cldev); +enable_err_exit: + return ret; +} + +static void mei_pxp_remove(struct mei_cl_device *cldev) +{ + struct i915_pxp_component *comp_master = mei_cldev_get_drvdata(cldev); + int ret; + + component_master_del(&cldev->dev, &mei_component_master_ops); + kfree(comp_master); + mei_cldev_set_drvdata(cldev, NULL); + + ret = mei_cldev_disable(cldev); + if (ret) + dev_warn(&cldev->dev, "mei_cldev_disable() failed\n"); +} + +/* fbf6fcf1-96cf-4e2e-a6a6-1bab8cbe36b1 : PAVP GUID*/ +#define MEI_GUID_PXP UUID_LE(0xfbf6fcf1, 0x96cf, 0x4e2e, 0xA6, \ + 0xa6, 0x1b, 0xab, 0x8c, 0xbe, 0x36, 0xb1) + +static struct mei_cl_device_id mei_pxp_tbl[] = { + { .uuid = MEI_GUID_PXP, .version = MEI_CL_VERSION_ANY }, + { } +}; +MODULE_DEVICE_TABLE(mei, mei_pxp_tbl); + +static struct mei_cl_driver mei_pxp_driver = { + .id_table = mei_pxp_tbl, + .name = KBUILD_MODNAME, + .probe = mei_pxp_probe, + .remove = mei_pxp_remove, +}; + +module_mei_cl_driver(mei_pxp_driver); + +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("MEI PXP"); diff --git a/drivers/misc/mei/pxp/mei_pxp.h b/drivers/misc/mei/pxp/mei_pxp.h new file mode 100644 index 000000000000..e7b15373fefd --- /dev/null +++ b/drivers/misc/mei/pxp/mei_pxp.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright © 2020 Intel Corporation + * + * Authors: + * Vitaly Lubart <vitaly.lubart@intel.com> + */ + +#ifndef __MEI_PXP_H__ +#define __MEI_PXP_H__ + +/* me_pxp_status: Enumeration of all PXP Status Codes */ +enum me_pxp_status { + ME_PXP_STATUS_SUCCESS = 0x0000, + +}; + +#endif /* __MEI_PXP_H__ */ diff --git a/drivers/misc/mei/vsc-fw-loader.c b/drivers/misc/mei/vsc-fw-loader.c new file mode 100644 index 000000000000..43abefa806e1 --- /dev/null +++ b/drivers/misc/mei/vsc-fw-loader.c @@ -0,0 +1,776 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023, Intel Corporation. + * Intel Visual Sensing Controller Transport Layer Linux driver + */ + +#include <linux/acpi.h> +#include <linux/align.h> +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/cleanup.h> +#include <linux/firmware.h> +#include <linux/sizes.h> +#include <linux/slab.h> +#include <linux/string_helpers.h> +#include <linux/types.h> + +#include <linux/unaligned.h> + +#include "vsc-tp.h" + +#define VSC_MAGIC_NUM 0x49505343 /* IPSC */ +#define VSC_MAGIC_FW 0x49574653 /* IWFS */ +#define VSC_MAGIC_FILE 0x46564353 /* FVCS */ + +#define VSC_ADDR_BASE 0xE0030000 +#define VSC_EFUSE_ADDR (VSC_ADDR_BASE + 0x038) +#define VSC_STRAP_ADDR (VSC_ADDR_BASE + 0x100) + +#define VSC_MAINSTEPPING_VERSION_MASK GENMASK(7, 4) +#define VSC_MAINSTEPPING_VERSION_A 0 + +#define VSC_SUBSTEPPING_VERSION_MASK GENMASK(3, 0) +#define VSC_SUBSTEPPING_VERSION_0 0 +#define VSC_SUBSTEPPING_VERSION_1 2 + +#define VSC_BOOT_IMG_OPTION_MASK GENMASK(15, 0) + +#define VSC_SKU_CFG_LOCATION 0x5001A000 +#define VSC_SKU_MAX_SIZE 4100u + +#define VSC_ACE_IMG_CNT 2 +#define VSC_CSI_IMG_CNT 4 +#define VSC_IMG_CNT_MAX 6 + +#define VSC_ROM_PKG_SIZE 256u +#define VSC_FW_PKG_SIZE 512u + +#define VSC_IMAGE_DIR "intel/vsc/" + +#define VSC_CSI_IMAGE_NAME VSC_IMAGE_DIR "ivsc_fw.bin" +#define VSC_ACE_IMAGE_NAME_FMT VSC_IMAGE_DIR "ivsc_pkg_%s_0.bin" +#define VSC_CFG_IMAGE_NAME_FMT VSC_IMAGE_DIR "ivsc_skucfg_%s_0_1.bin" + +#define VSC_IMAGE_PATH_MAX_LEN 64 + +#define VSC_SENSOR_NAME_MAX_LEN 16 + +/* command id */ +enum { + VSC_CMD_QUERY = 0, + VSC_CMD_DL_SET = 1, + VSC_CMD_DL_START = 2, + VSC_CMD_DL_CONT = 3, + VSC_CMD_DUMP_MEM = 4, + VSC_CMD_GET_CONT = 8, + VSC_CMD_CAM_BOOT = 10, +}; + +/* command ack token */ +enum { + VSC_TOKEN_BOOTLOADER_REQ = 1, + VSC_TOKEN_DUMP_RESP = 4, + VSC_TOKEN_ERROR = 7, +}; + +/* image type */ +enum { + VSC_IMG_BOOTLOADER_TYPE = 1, + VSC_IMG_CSI_EM7D_TYPE, + VSC_IMG_CSI_SEM_TYPE, + VSC_IMG_CSI_RUNTIME_TYPE, + VSC_IMG_ACE_VISION_TYPE, + VSC_IMG_ACE_CFG_TYPE, + VSC_IMG_SKU_CFG_TYPE, +}; + +/* image fragments */ +enum { + VSC_IMG_BOOTLOADER_FRAG, + VSC_IMG_CSI_SEM_FRAG, + VSC_IMG_CSI_RUNTIME_FRAG, + VSC_IMG_ACE_VISION_FRAG, + VSC_IMG_ACE_CFG_FRAG, + VSC_IMG_CSI_EM7D_FRAG, + VSC_IMG_SKU_CFG_FRAG, + VSC_IMG_FRAG_MAX +}; + +struct vsc_rom_cmd { + __le32 magic; + __u8 cmd_id; + union { + /* download start */ + struct { + __u8 img_type; + __le16 option; + __le32 img_len; + __le32 img_loc; + __le32 crc; + DECLARE_FLEX_ARRAY(__u8, res); + } __packed dl_start; + /* download set */ + struct { + __u8 option; + __le16 img_cnt; + DECLARE_FLEX_ARRAY(__le32, payload); + } __packed dl_set; + /* download continue */ + struct { + __u8 end_flag; + __le16 len; + /* 8 is the offset of payload */ + __u8 payload[VSC_ROM_PKG_SIZE - 8]; + } __packed dl_cont; + /* dump memory */ + struct { + __u8 res; + __le16 len; + __le32 addr; + DECLARE_FLEX_ARRAY(__u8, payload); + } __packed dump_mem; + /* 5 is the offset of padding */ + __u8 padding[VSC_ROM_PKG_SIZE - 5]; + } data; +}; + +struct vsc_rom_cmd_ack { + __le32 magic; + __u8 token; + __u8 type; + __u8 res[2]; + __u8 payload[]; +}; + +struct vsc_fw_cmd { + __le32 magic; + __u8 cmd_id; + union { + struct { + __le16 option; + __u8 img_type; + __le32 img_len; + __le32 img_loc; + __le32 crc; + DECLARE_FLEX_ARRAY(__u8, res); + } __packed dl_start; + struct { + __le16 option; + __u8 img_cnt; + DECLARE_FLEX_ARRAY(__le32, payload); + } __packed dl_set; + struct { + __le32 addr; + __u8 len; + DECLARE_FLEX_ARRAY(__u8, payload); + } __packed dump_mem; + struct { + __u8 resv[3]; + __le32 crc; + DECLARE_FLEX_ARRAY(__u8, payload); + } __packed boot; + /* 5 is the offset of padding */ + __u8 padding[VSC_FW_PKG_SIZE - 5]; + } data; +}; + +struct vsc_img { + __le32 magic; + __le32 option; + __le32 image_count; + __le32 image_location[VSC_IMG_CNT_MAX]; +}; + +struct vsc_fw_sign { + __le32 magic; + __le32 image_size; + __u8 image[]; +}; + +struct vsc_image_code_data { + /* fragment index */ + u8 frag_index; + /* image type */ + u8 image_type; +}; + +struct vsc_img_frag { + u8 type; + u32 location; + const u8 *data; + u32 size; +}; + +/** + * struct vsc_fw_loader - represent vsc firmware loader + * @dev: device used to request firmware + * @tp: transport layer used with the firmware loader + * @csi: CSI image + * @ace: ACE image + * @cfg: config image + * @tx_buf: tx buffer + * @rx_buf: rx buffer + * @option: command option + * @count: total image count + * @sensor_name: camera sensor name + * @frags: image fragments + */ +struct vsc_fw_loader { + struct device *dev; + struct vsc_tp *tp; + + const struct firmware *csi; + const struct firmware *ace; + const struct firmware *cfg; + + void *tx_buf; + void *rx_buf; + + u16 option; + u16 count; + + char sensor_name[VSC_SENSOR_NAME_MAX_LEN]; + + struct vsc_img_frag frags[VSC_IMG_FRAG_MAX]; +}; + +static inline u32 vsc_sum_crc(void *data, size_t size) +{ + u32 crc = 0; + size_t i; + + for (i = 0; i < size; i++) + crc += *((u8 *)data + i); + + return crc; +} + +/* get sensor name to construct image name */ +static int vsc_get_sensor_name(struct vsc_fw_loader *fw_loader, + struct device *dev) +{ + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER }; + union acpi_object obj = { + .integer.type = ACPI_TYPE_INTEGER, + .integer.value = 1, + }; + struct acpi_object_list arg_list = { + .count = 1, + .pointer = &obj, + }; + union acpi_object *ret_obj; + acpi_handle handle; + acpi_status status; + int ret = 0; + + handle = ACPI_HANDLE(dev); + if (!handle) + return -EINVAL; + + status = acpi_evaluate_object(handle, "SID", &arg_list, &buffer); + if (ACPI_FAILURE(status)) { + dev_err(dev, "can't evaluate SID method: %d\n", status); + return -ENODEV; + } + + ret_obj = buffer.pointer; + if (!ret_obj) { + dev_err(dev, "can't locate ACPI buffer\n"); + return -ENODEV; + } + + if (ret_obj->type != ACPI_TYPE_STRING) { + dev_err(dev, "found non-string entry\n"); + ret = -ENODEV; + goto out_free_buff; + } + + /* string length excludes trailing NUL */ + if (ret_obj->string.length >= sizeof(fw_loader->sensor_name)) { + dev_err(dev, "sensor name buffer too small\n"); + ret = -EINVAL; + goto out_free_buff; + } + + memcpy(fw_loader->sensor_name, ret_obj->string.pointer, + ret_obj->string.length); + + string_lower(fw_loader->sensor_name, fw_loader->sensor_name); + +out_free_buff: + ACPI_FREE(buffer.pointer); + + return ret; +} + +static int vsc_identify_silicon(struct vsc_fw_loader *fw_loader) +{ + struct vsc_rom_cmd_ack *ack = fw_loader->rx_buf; + struct vsc_rom_cmd *cmd = fw_loader->tx_buf; + u8 version, sub_version; + int ret; + + /* identify stepping information */ + cmd->magic = cpu_to_le32(VSC_MAGIC_NUM); + cmd->cmd_id = VSC_CMD_DUMP_MEM; + cmd->data.dump_mem.addr = cpu_to_le32(VSC_EFUSE_ADDR); + cmd->data.dump_mem.len = cpu_to_le16(sizeof(__le32)); + ret = vsc_tp_rom_xfer(fw_loader->tp, cmd, ack, VSC_ROM_PKG_SIZE); + if (ret || ack->token == VSC_TOKEN_ERROR) { + dev_err(fw_loader->dev, "CMD_DUMP_MEM error %d token %d\n", ret, ack->token); + return ret ?: -EINVAL; + } + + cmd->magic = cpu_to_le32(VSC_MAGIC_NUM); + cmd->cmd_id = VSC_CMD_GET_CONT; + ret = vsc_tp_rom_xfer(fw_loader->tp, cmd, ack, VSC_ROM_PKG_SIZE); + if (ret || ack->token != VSC_TOKEN_DUMP_RESP) { + dev_err(fw_loader->dev, "CMD_GETCONT error %d token %d\n", ret, ack->token); + return ret ?: -EINVAL; + } + + version = FIELD_GET(VSC_MAINSTEPPING_VERSION_MASK, ack->payload[0]); + sub_version = FIELD_GET(VSC_SUBSTEPPING_VERSION_MASK, ack->payload[0]); + + if (version != VSC_MAINSTEPPING_VERSION_A) { + dev_err(fw_loader->dev, "mainstepping mismatch expected %d got %d\n", + VSC_MAINSTEPPING_VERSION_A, version); + return -EINVAL; + } + + if (sub_version != VSC_SUBSTEPPING_VERSION_0 && + sub_version != VSC_SUBSTEPPING_VERSION_1) { + dev_err(fw_loader->dev, "substepping %d is out of supported range %d - %d\n", + sub_version, VSC_SUBSTEPPING_VERSION_0, VSC_SUBSTEPPING_VERSION_1); + return -EINVAL; + } + + dev_info(fw_loader->dev, "silicon stepping version is %u:%u\n", + version, sub_version); + + /* identify strap information */ + cmd->magic = cpu_to_le32(VSC_MAGIC_NUM); + cmd->cmd_id = VSC_CMD_DUMP_MEM; + cmd->data.dump_mem.addr = cpu_to_le32(VSC_STRAP_ADDR); + cmd->data.dump_mem.len = cpu_to_le16(sizeof(__le32)); + ret = vsc_tp_rom_xfer(fw_loader->tp, cmd, ack, VSC_ROM_PKG_SIZE); + if (ret) + return ret; + if (ack->token == VSC_TOKEN_ERROR) + return -EINVAL; + + cmd->magic = cpu_to_le32(VSC_MAGIC_NUM); + cmd->cmd_id = VSC_CMD_GET_CONT; + ret = vsc_tp_rom_xfer(fw_loader->tp, cmd, ack, VSC_ROM_PKG_SIZE); + if (ret) + return ret; + if (ack->token != VSC_TOKEN_DUMP_RESP) + return -EINVAL; + + return 0; +} + +static int vsc_identify_csi_image(struct vsc_fw_loader *fw_loader) +{ + const struct firmware *image; + struct vsc_fw_sign *sign; + struct vsc_img *img; + unsigned int i; + int ret; + + ret = request_firmware(&image, VSC_CSI_IMAGE_NAME, fw_loader->dev); + if (ret) + return ret; + + img = (struct vsc_img *)image->data; + if (!img) { + ret = -ENOENT; + goto err_release_image; + } + + if (le32_to_cpu(img->magic) != VSC_MAGIC_FILE) { + ret = -EINVAL; + goto err_release_image; + } + + if (le32_to_cpu(img->image_count) != VSC_CSI_IMG_CNT) { + ret = -EINVAL; + goto err_release_image; + } + fw_loader->count += le32_to_cpu(img->image_count) - 1; + + fw_loader->option = + FIELD_GET(VSC_BOOT_IMG_OPTION_MASK, le32_to_cpu(img->option)); + + sign = (struct vsc_fw_sign *) + (img->image_location + le32_to_cpu(img->image_count)); + + for (i = 0; i < VSC_CSI_IMG_CNT; i++) { + /* mapping from CSI image index to image code data */ + static const struct vsc_image_code_data csi_image_map[] = { + { VSC_IMG_BOOTLOADER_FRAG, VSC_IMG_BOOTLOADER_TYPE }, + { VSC_IMG_CSI_SEM_FRAG, VSC_IMG_CSI_SEM_TYPE }, + { VSC_IMG_CSI_RUNTIME_FRAG, VSC_IMG_CSI_RUNTIME_TYPE }, + { VSC_IMG_CSI_EM7D_FRAG, VSC_IMG_CSI_EM7D_TYPE }, + }; + struct vsc_img_frag *frag; + + if ((u8 *)sign + sizeof(*sign) > image->data + image->size) { + ret = -EINVAL; + goto err_release_image; + } + + if (le32_to_cpu(sign->magic) != VSC_MAGIC_FW) { + ret = -EINVAL; + goto err_release_image; + } + + if (!le32_to_cpu(img->image_location[i])) { + ret = -EINVAL; + goto err_release_image; + } + + frag = &fw_loader->frags[csi_image_map[i].frag_index]; + + frag->data = sign->image; + frag->size = le32_to_cpu(sign->image_size); + frag->location = le32_to_cpu(img->image_location[i]); + frag->type = csi_image_map[i].image_type; + + sign = (struct vsc_fw_sign *) + (sign->image + le32_to_cpu(sign->image_size)); + } + + fw_loader->csi = image; + + return 0; + +err_release_image: + release_firmware(image); + + return ret; +} + +static int vsc_identify_ace_image(struct vsc_fw_loader *fw_loader) +{ + char path[VSC_IMAGE_PATH_MAX_LEN]; + const struct firmware *image; + struct vsc_fw_sign *sign; + struct vsc_img *img; + unsigned int i; + int ret; + + snprintf(path, sizeof(path), VSC_ACE_IMAGE_NAME_FMT, + fw_loader->sensor_name); + + ret = request_firmware(&image, path, fw_loader->dev); + if (ret) + return ret; + + img = (struct vsc_img *)image->data; + if (!img) { + ret = -ENOENT; + goto err_release_image; + } + + if (le32_to_cpu(img->magic) != VSC_MAGIC_FILE) { + ret = -EINVAL; + goto err_release_image; + } + + if (le32_to_cpu(img->image_count) != VSC_ACE_IMG_CNT) { + ret = -EINVAL; + goto err_release_image; + } + fw_loader->count += le32_to_cpu(img->image_count); + + sign = (struct vsc_fw_sign *) + (img->image_location + le32_to_cpu(img->image_count)); + + for (i = 0; i < VSC_ACE_IMG_CNT; i++) { + /* mapping from ACE image index to image code data */ + static const struct vsc_image_code_data ace_image_map[] = { + { VSC_IMG_ACE_VISION_FRAG, VSC_IMG_ACE_VISION_TYPE }, + { VSC_IMG_ACE_CFG_FRAG, VSC_IMG_ACE_CFG_TYPE }, + }; + struct vsc_img_frag *frag, *last_frag; + u8 frag_index; + + if ((u8 *)sign + sizeof(*sign) > image->data + image->size) { + ret = -EINVAL; + goto err_release_image; + } + + if (le32_to_cpu(sign->magic) != VSC_MAGIC_FW) { + ret = -EINVAL; + goto err_release_image; + } + + frag_index = ace_image_map[i].frag_index; + frag = &fw_loader->frags[frag_index]; + + frag->data = sign->image; + frag->size = le32_to_cpu(sign->image_size); + frag->location = le32_to_cpu(img->image_location[i]); + frag->type = ace_image_map[i].image_type; + + if (!frag->location) { + last_frag = &fw_loader->frags[frag_index - 1]; + frag->location = + ALIGN(last_frag->location + last_frag->size, SZ_4K); + } + + sign = (struct vsc_fw_sign *) + (sign->image + le32_to_cpu(sign->image_size)); + } + + fw_loader->ace = image; + + return 0; + +err_release_image: + release_firmware(image); + + return ret; +} + +static int vsc_identify_cfg_image(struct vsc_fw_loader *fw_loader) +{ + struct vsc_img_frag *frag = &fw_loader->frags[VSC_IMG_SKU_CFG_FRAG]; + char path[VSC_IMAGE_PATH_MAX_LEN]; + const struct firmware *image; + u32 size; + int ret; + + snprintf(path, sizeof(path), VSC_CFG_IMAGE_NAME_FMT, + fw_loader->sensor_name); + + ret = request_firmware(&image, path, fw_loader->dev); + if (ret) + return ret; + + /* identify image size */ + if (image->size <= sizeof(u32) || image->size > VSC_SKU_MAX_SIZE) { + ret = -EINVAL; + goto err_release_image; + } + + size = le32_to_cpu(*((__le32 *)image->data)) + sizeof(u32); + if (image->size != size) { + ret = -EINVAL; + goto err_release_image; + } + + frag->data = image->data; + frag->size = image->size; + frag->type = VSC_IMG_SKU_CFG_TYPE; + frag->location = VSC_SKU_CFG_LOCATION; + + fw_loader->cfg = image; + + return 0; + +err_release_image: + release_firmware(image); + + return ret; +} + +static int vsc_download_bootloader(struct vsc_fw_loader *fw_loader) +{ + struct vsc_img_frag *frag = &fw_loader->frags[VSC_IMG_BOOTLOADER_FRAG]; + struct vsc_rom_cmd_ack *ack = fw_loader->rx_buf; + struct vsc_rom_cmd *cmd = fw_loader->tx_buf; + u32 len, c_len; + size_t remain; + const u8 *p; + int ret; + + cmd->magic = cpu_to_le32(VSC_MAGIC_NUM); + cmd->cmd_id = VSC_CMD_QUERY; + ret = vsc_tp_rom_xfer(fw_loader->tp, cmd, ack, VSC_ROM_PKG_SIZE); + if (ret) + return ret; + if (ack->token != VSC_TOKEN_DUMP_RESP && + ack->token != VSC_TOKEN_BOOTLOADER_REQ) + return -EINVAL; + + cmd->magic = cpu_to_le32(VSC_MAGIC_NUM); + cmd->cmd_id = VSC_CMD_DL_START; + cmd->data.dl_start.option = cpu_to_le16(fw_loader->option); + cmd->data.dl_start.img_type = frag->type; + cmd->data.dl_start.img_len = cpu_to_le32(frag->size); + cmd->data.dl_start.img_loc = cpu_to_le32(frag->location); + + c_len = offsetof(struct vsc_rom_cmd, data.dl_start.crc); + cmd->data.dl_start.crc = cpu_to_le32(vsc_sum_crc(cmd, c_len)); + + ret = vsc_tp_rom_xfer(fw_loader->tp, cmd, NULL, VSC_ROM_PKG_SIZE); + if (ret) + return ret; + + p = frag->data; + remain = frag->size; + + /* download image data */ + while (remain > 0) { + len = min(remain, sizeof(cmd->data.dl_cont.payload)); + + cmd->magic = cpu_to_le32(VSC_MAGIC_NUM); + cmd->cmd_id = VSC_CMD_DL_CONT; + cmd->data.dl_cont.len = cpu_to_le16(len); + cmd->data.dl_cont.end_flag = remain == len; + memcpy(cmd->data.dl_cont.payload, p, len); + + ret = vsc_tp_rom_xfer(fw_loader->tp, cmd, NULL, VSC_ROM_PKG_SIZE); + if (ret) + return ret; + + p += len; + remain -= len; + } + + return 0; +} + +static int vsc_download_firmware(struct vsc_fw_loader *fw_loader) +{ + struct vsc_fw_cmd *cmd = fw_loader->tx_buf; + unsigned int i, index = 0; + u32 c_len; + int ret; + + cmd->magic = cpu_to_le32(VSC_MAGIC_NUM); + cmd->cmd_id = VSC_CMD_DL_SET; + cmd->data.dl_set.img_cnt = cpu_to_le16(fw_loader->count); + put_unaligned_le16(fw_loader->option, &cmd->data.dl_set.option); + + for (i = VSC_IMG_CSI_SEM_FRAG; i <= VSC_IMG_CSI_EM7D_FRAG; i++) { + struct vsc_img_frag *frag = &fw_loader->frags[i]; + + cmd->data.dl_set.payload[index++] = cpu_to_le32(frag->location); + cmd->data.dl_set.payload[index++] = cpu_to_le32(frag->size); + } + + c_len = offsetof(struct vsc_fw_cmd, data.dl_set.payload[index]); + cmd->data.dl_set.payload[index] = cpu_to_le32(vsc_sum_crc(cmd, c_len)); + + ret = vsc_tp_rom_xfer(fw_loader->tp, cmd, NULL, VSC_FW_PKG_SIZE); + if (ret) + return ret; + + for (i = VSC_IMG_CSI_SEM_FRAG; i < VSC_IMG_FRAG_MAX; i++) { + struct vsc_img_frag *frag = &fw_loader->frags[i]; + const u8 *p; + u32 remain; + + cmd->magic = cpu_to_le32(VSC_MAGIC_NUM); + cmd->cmd_id = VSC_CMD_DL_START; + cmd->data.dl_start.img_type = frag->type; + cmd->data.dl_start.img_len = cpu_to_le32(frag->size); + cmd->data.dl_start.img_loc = cpu_to_le32(frag->location); + put_unaligned_le16(fw_loader->option, &cmd->data.dl_start.option); + + c_len = offsetof(struct vsc_fw_cmd, data.dl_start.crc); + cmd->data.dl_start.crc = cpu_to_le32(vsc_sum_crc(cmd, c_len)); + + ret = vsc_tp_rom_xfer(fw_loader->tp, cmd, NULL, VSC_FW_PKG_SIZE); + if (ret) + return ret; + + p = frag->data; + remain = frag->size; + + /* download image data */ + while (remain > 0) { + u32 len = min(remain, VSC_FW_PKG_SIZE); + + memcpy(fw_loader->tx_buf, p, len); + memset(fw_loader->tx_buf + len, 0, VSC_FW_PKG_SIZE - len); + + ret = vsc_tp_rom_xfer(fw_loader->tp, fw_loader->tx_buf, + NULL, VSC_FW_PKG_SIZE); + if (ret) + break; + + p += len; + remain -= len; + } + } + + cmd->magic = cpu_to_le32(VSC_MAGIC_NUM); + cmd->cmd_id = VSC_CMD_CAM_BOOT; + + c_len = offsetof(struct vsc_fw_cmd, data.dl_start.crc); + cmd->data.boot.crc = cpu_to_le32(vsc_sum_crc(cmd, c_len)); + + return vsc_tp_rom_xfer(fw_loader->tp, cmd, NULL, VSC_FW_PKG_SIZE); +} + +/** + * vsc_tp_init - init vsc_tp + * @tp: vsc_tp device handle + * @dev: device node for mei vsc device + * Return: 0 in case of success, negative value in case of error + */ +int vsc_tp_init(struct vsc_tp *tp, struct device *dev) +{ + struct vsc_fw_loader *fw_loader __free(kfree) = NULL; + void *tx_buf __free(kfree) = NULL; + void *rx_buf __free(kfree) = NULL; + int ret; + + fw_loader = kzalloc(sizeof(*fw_loader), GFP_KERNEL); + if (!fw_loader) + return -ENOMEM; + + tx_buf = kzalloc(VSC_FW_PKG_SIZE, GFP_KERNEL); + if (!tx_buf) + return -ENOMEM; + + rx_buf = kzalloc(VSC_FW_PKG_SIZE, GFP_KERNEL); + if (!rx_buf) + return -ENOMEM; + + fw_loader->tx_buf = tx_buf; + fw_loader->rx_buf = rx_buf; + + fw_loader->tp = tp; + fw_loader->dev = dev; + + ret = vsc_get_sensor_name(fw_loader, dev); + if (ret) + return ret; + + ret = vsc_identify_silicon(fw_loader); + if (ret) + return ret; + + ret = vsc_identify_csi_image(fw_loader); + if (ret) + return ret; + + ret = vsc_identify_ace_image(fw_loader); + if (ret) + goto err_release_csi; + + ret = vsc_identify_cfg_image(fw_loader); + if (ret) + goto err_release_ace; + + ret = vsc_download_bootloader(fw_loader); + if (!ret) + ret = vsc_download_firmware(fw_loader); + + release_firmware(fw_loader->cfg); + +err_release_ace: + release_firmware(fw_loader->ace); + +err_release_csi: + release_firmware(fw_loader->csi); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(vsc_tp_init, "VSC_TP"); diff --git a/drivers/misc/mei/vsc-tp.c b/drivers/misc/mei/vsc-tp.c new file mode 100644 index 000000000000..5ecf99883996 --- /dev/null +++ b/drivers/misc/mei/vsc-tp.c @@ -0,0 +1,577 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023, Intel Corporation. + * Intel Visual Sensing Controller Transport Layer Linux driver + */ + +#include <linux/acpi.h> +#include <linux/cleanup.h> +#include <linux/crc32.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/iopoll.h> +#include <linux/irq.h> +#include <linux/irqreturn.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> +#include <linux/types.h> +#include <linux/workqueue.h> + +#include "vsc-tp.h" + +#define VSC_TP_RESET_PIN_TOGGLE_INTERVAL_MS 20 +#define VSC_TP_ROM_BOOTUP_DELAY_MS 10 +#define VSC_TP_ROM_XFER_POLL_TIMEOUT_US (500 * USEC_PER_MSEC) +#define VSC_TP_ROM_XFER_POLL_DELAY_US (20 * USEC_PER_MSEC) +#define VSC_TP_WAIT_FW_POLL_TIMEOUT (2 * HZ) +#define VSC_TP_WAIT_FW_POLL_DELAY_US (20 * USEC_PER_MSEC) +#define VSC_TP_MAX_XFER_COUNT 5 + +#define VSC_TP_PACKET_SYNC 0x31 +#define VSC_TP_CRC_SIZE sizeof(u32) +#define VSC_TP_MAX_MSG_SIZE 2048 +/* SPI xfer timeout size */ +#define VSC_TP_XFER_TIMEOUT_BYTES 700 +#define VSC_TP_PACKET_PADDING_SIZE 1 +#define VSC_TP_PACKET_SIZE(pkt) \ + (sizeof(struct vsc_tp_packet_hdr) + le16_to_cpu((pkt)->hdr.len) + VSC_TP_CRC_SIZE) +#define VSC_TP_MAX_PACKET_SIZE \ + (sizeof(struct vsc_tp_packet_hdr) + VSC_TP_MAX_MSG_SIZE + VSC_TP_CRC_SIZE) +#define VSC_TP_MAX_XFER_SIZE \ + (VSC_TP_MAX_PACKET_SIZE + VSC_TP_XFER_TIMEOUT_BYTES) +#define VSC_TP_NEXT_XFER_LEN(len, offset) \ + (len + sizeof(struct vsc_tp_packet_hdr) + VSC_TP_CRC_SIZE - offset + VSC_TP_PACKET_PADDING_SIZE) + +struct vsc_tp_packet_hdr { + __u8 sync; + __u8 cmd; + __le16 len; + __le32 seq; +}; + +struct vsc_tp_packet { + struct vsc_tp_packet_hdr hdr; + __u8 buf[VSC_TP_MAX_XFER_SIZE - sizeof(struct vsc_tp_packet_hdr)]; +}; + +struct vsc_tp { + /* do the actual data transfer */ + struct spi_device *spi; + + /* bind with mei framework */ + struct platform_device *pdev; + + struct gpio_desc *wakeuphost; + struct gpio_desc *resetfw; + struct gpio_desc *wakeupfw; + + /* command sequence number */ + u32 seq; + + /* command buffer */ + struct vsc_tp_packet *tx_buf; + struct vsc_tp_packet *rx_buf; + + atomic_t assert_cnt; + wait_queue_head_t xfer_wait; + struct work_struct event_work; + + vsc_tp_event_cb_t event_notify; + void *event_notify_context; + struct mutex event_notify_mutex; /* protects event_notify + context */ + struct mutex mutex; /* protects command download */ +}; + +/* GPIO resources */ +static const struct acpi_gpio_params wakeuphost_gpio = { 0, 0, false }; +static const struct acpi_gpio_params wakeuphostint_gpio = { 1, 0, false }; +static const struct acpi_gpio_params resetfw_gpio = { 2, 0, false }; +static const struct acpi_gpio_params wakeupfw = { 3, 0, false }; + +static const struct acpi_gpio_mapping vsc_tp_acpi_gpios[] = { + { "wakeuphost-gpios", &wakeuphost_gpio, 1 }, + { "wakeuphostint-gpios", &wakeuphostint_gpio, 1 }, + { "resetfw-gpios", &resetfw_gpio, 1 }, + { "wakeupfw-gpios", &wakeupfw, 1 }, + {} +}; + +static irqreturn_t vsc_tp_isr(int irq, void *data) +{ + struct vsc_tp *tp = data; + + atomic_inc(&tp->assert_cnt); + + wake_up(&tp->xfer_wait); + + schedule_work(&tp->event_work); + + return IRQ_HANDLED; +} + +static void vsc_tp_event_work(struct work_struct *work) +{ + struct vsc_tp *tp = container_of(work, struct vsc_tp, event_work); + + guard(mutex)(&tp->event_notify_mutex); + + if (tp->event_notify) + tp->event_notify(tp->event_notify_context); +} + +/* wakeup firmware and wait for response */ +static int vsc_tp_wakeup_request(struct vsc_tp *tp) +{ + int ret; + + gpiod_set_value_cansleep(tp->wakeupfw, 0); + + ret = wait_event_timeout(tp->xfer_wait, + atomic_read(&tp->assert_cnt), + VSC_TP_WAIT_FW_POLL_TIMEOUT); + if (!ret) + return -ETIMEDOUT; + + return read_poll_timeout(gpiod_get_value_cansleep, ret, ret, + VSC_TP_WAIT_FW_POLL_DELAY_US, + VSC_TP_WAIT_FW_POLL_TIMEOUT, false, + tp->wakeuphost); +} + +static void vsc_tp_wakeup_release(struct vsc_tp *tp) +{ + atomic_dec_if_positive(&tp->assert_cnt); + + gpiod_set_value_cansleep(tp->wakeupfw, 1); +} + +static int vsc_tp_dev_xfer(struct vsc_tp *tp, void *obuf, void *ibuf, size_t len) +{ + struct spi_message msg = { 0 }; + struct spi_transfer xfer = { + .tx_buf = obuf, + .rx_buf = ibuf, + .len = len, + }; + + spi_message_init_with_transfers(&msg, &xfer, 1); + + return spi_sync_locked(tp->spi, &msg); +} + +static int vsc_tp_xfer_helper(struct vsc_tp *tp, struct vsc_tp_packet *pkt, + void *ibuf, u16 ilen) +{ + int ret, offset = 0, cpy_len, src_len, dst_len = sizeof(struct vsc_tp_packet_hdr); + int next_xfer_len = VSC_TP_PACKET_SIZE(pkt) + VSC_TP_XFER_TIMEOUT_BYTES; + u8 *src, *crc_src, *rx_buf = (u8 *)tp->rx_buf; + int count_down = VSC_TP_MAX_XFER_COUNT; + u32 recv_crc = 0, crc = ~0; + struct vsc_tp_packet_hdr ack; + u8 *dst = (u8 *)&ack; + bool synced = false; + + do { + ret = vsc_tp_dev_xfer(tp, pkt, rx_buf, next_xfer_len); + if (ret) + return ret; + memset(pkt, 0, VSC_TP_MAX_XFER_SIZE); + + if (synced) { + src = rx_buf; + src_len = next_xfer_len; + } else { + src = memchr(rx_buf, VSC_TP_PACKET_SYNC, next_xfer_len); + if (!src) + continue; + synced = true; + src_len = next_xfer_len - (src - rx_buf); + } + + /* traverse received data */ + while (src_len > 0) { + cpy_len = min(src_len, dst_len); + memcpy(dst, src, cpy_len); + crc_src = src; + src += cpy_len; + src_len -= cpy_len; + dst += cpy_len; + dst_len -= cpy_len; + + if (offset < sizeof(ack)) { + offset += cpy_len; + crc = crc32(crc, crc_src, cpy_len); + + if (!src_len) + continue; + + if (le16_to_cpu(ack.len)) { + dst = ibuf; + dst_len = min(ilen, le16_to_cpu(ack.len)); + } else { + dst = (u8 *)&recv_crc; + dst_len = sizeof(recv_crc); + } + } else if (offset < sizeof(ack) + le16_to_cpu(ack.len)) { + offset += cpy_len; + crc = crc32(crc, crc_src, cpy_len); + + if (src_len) { + int remain = sizeof(ack) + le16_to_cpu(ack.len) - offset; + + cpy_len = min(src_len, remain); + offset += cpy_len; + crc = crc32(crc, src, cpy_len); + src += cpy_len; + src_len -= cpy_len; + if (src_len) { + dst = (u8 *)&recv_crc; + dst_len = sizeof(recv_crc); + continue; + } + } + next_xfer_len = VSC_TP_NEXT_XFER_LEN(le16_to_cpu(ack.len), offset); + } else if (offset < sizeof(ack) + le16_to_cpu(ack.len) + VSC_TP_CRC_SIZE) { + offset += cpy_len; + + if (src_len) { + /* terminate the traverse */ + next_xfer_len = 0; + break; + } + next_xfer_len = VSC_TP_NEXT_XFER_LEN(le16_to_cpu(ack.len), offset); + } + } + } while (next_xfer_len > 0 && --count_down); + + if (next_xfer_len > 0) + return -EAGAIN; + + if (~recv_crc != crc || le32_to_cpu(ack.seq) != tp->seq) { + dev_err(&tp->spi->dev, "recv crc or seq error\n"); + return -EINVAL; + } + + if (ack.cmd == VSC_TP_CMD_ACK || ack.cmd == VSC_TP_CMD_NACK || + ack.cmd == VSC_TP_CMD_BUSY) { + dev_err(&tp->spi->dev, "recv cmd ack error\n"); + return -EAGAIN; + } + + return min(le16_to_cpu(ack.len), ilen); +} + +/** + * vsc_tp_xfer - transfer data to firmware + * @tp: vsc_tp device handle + * @cmd: the command to be sent to the device + * @obuf: the tx buffer to be sent to the device + * @olen: the length of tx buffer + * @ibuf: the rx buffer to receive from the device + * @ilen: the length of rx buffer + * Return: the length of received data in case of success, + * otherwise negative value + */ +int vsc_tp_xfer(struct vsc_tp *tp, u8 cmd, const void *obuf, size_t olen, + void *ibuf, size_t ilen) +{ + struct vsc_tp_packet *pkt = tp->tx_buf; + u32 crc; + int ret; + + if (!obuf || !ibuf || olen > VSC_TP_MAX_MSG_SIZE) + return -EINVAL; + + guard(mutex)(&tp->mutex); + + pkt->hdr.sync = VSC_TP_PACKET_SYNC; + pkt->hdr.cmd = cmd; + pkt->hdr.len = cpu_to_le16(olen); + pkt->hdr.seq = cpu_to_le32(++tp->seq); + memcpy(pkt->buf, obuf, olen); + + crc = ~crc32(~0, (u8 *)pkt, sizeof(pkt) + olen); + memcpy(pkt->buf + olen, &crc, sizeof(crc)); + + ret = vsc_tp_wakeup_request(tp); + if (unlikely(ret)) + dev_err(&tp->spi->dev, "wakeup firmware failed ret: %d\n", ret); + else + ret = vsc_tp_xfer_helper(tp, pkt, ibuf, ilen); + + vsc_tp_wakeup_release(tp); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(vsc_tp_xfer, "VSC_TP"); + +/** + * vsc_tp_rom_xfer - transfer data to rom code + * @tp: vsc_tp device handle + * @obuf: the data buffer to be sent to the device + * @ibuf: the buffer to receive data from the device + * @len: the length of tx buffer and rx buffer + * Return: 0 in case of success, negative value in case of error + */ +int vsc_tp_rom_xfer(struct vsc_tp *tp, const void *obuf, void *ibuf, size_t len) +{ + size_t words = len / sizeof(__be32); + int ret; + + if (len % sizeof(__be32) || len > VSC_TP_MAX_MSG_SIZE) + return -EINVAL; + + guard(mutex)(&tp->mutex); + + /* rom xfer is big endian */ + cpu_to_be32_array((__be32 *)tp->tx_buf, obuf, words); + + ret = read_poll_timeout(gpiod_get_value_cansleep, ret, + !ret, VSC_TP_ROM_XFER_POLL_DELAY_US, + VSC_TP_ROM_XFER_POLL_TIMEOUT_US, false, + tp->wakeuphost); + if (ret) { + dev_err(&tp->spi->dev, "wait rom failed ret: %d\n", ret); + return ret; + } + + ret = vsc_tp_dev_xfer(tp, tp->tx_buf, ibuf ? tp->rx_buf : NULL, len); + if (ret) + return ret; + + if (ibuf) + be32_to_cpu_array(ibuf, (__be32 *)tp->rx_buf, words); + + return ret; +} + +/** + * vsc_tp_reset - reset vsc transport layer + * @tp: vsc_tp device handle + */ +void vsc_tp_reset(struct vsc_tp *tp) +{ + disable_irq(tp->spi->irq); + + /* toggle reset pin */ + gpiod_set_value_cansleep(tp->resetfw, 0); + msleep(VSC_TP_RESET_PIN_TOGGLE_INTERVAL_MS); + gpiod_set_value_cansleep(tp->resetfw, 1); + + /* wait for ROM */ + msleep(VSC_TP_ROM_BOOTUP_DELAY_MS); + + /* + * Set default host wakeup pin to non-active + * to avoid unexpected host irq interrupt. + */ + gpiod_set_value_cansleep(tp->wakeupfw, 1); + + atomic_set(&tp->assert_cnt, 0); +} +EXPORT_SYMBOL_NS_GPL(vsc_tp_reset, "VSC_TP"); + +/** + * vsc_tp_need_read - check if device has data to sent + * @tp: vsc_tp device handle + * Return: true if device has data to sent, otherwise false + */ +bool vsc_tp_need_read(struct vsc_tp *tp) +{ + if (!atomic_read(&tp->assert_cnt)) + return false; + if (!gpiod_get_value_cansleep(tp->wakeuphost)) + return false; + if (!gpiod_get_value_cansleep(tp->wakeupfw)) + return false; + + return true; +} +EXPORT_SYMBOL_NS_GPL(vsc_tp_need_read, "VSC_TP"); + +/** + * vsc_tp_register_event_cb - register a callback function to receive event + * @tp: vsc_tp device handle + * @event_cb: callback function + * @context: execution context of event callback + * Return: 0 in case of success, negative value in case of error + */ +int vsc_tp_register_event_cb(struct vsc_tp *tp, vsc_tp_event_cb_t event_cb, + void *context) +{ + guard(mutex)(&tp->event_notify_mutex); + + tp->event_notify = event_cb; + tp->event_notify_context = context; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(vsc_tp_register_event_cb, "VSC_TP"); + +/** + * vsc_tp_intr_synchronize - synchronize vsc_tp interrupt + * @tp: vsc_tp device handle + */ +void vsc_tp_intr_synchronize(struct vsc_tp *tp) +{ + synchronize_irq(tp->spi->irq); +} +EXPORT_SYMBOL_NS_GPL(vsc_tp_intr_synchronize, "VSC_TP"); + +/** + * vsc_tp_intr_enable - enable vsc_tp interrupt + * @tp: vsc_tp device handle + */ +void vsc_tp_intr_enable(struct vsc_tp *tp) +{ + enable_irq(tp->spi->irq); +} +EXPORT_SYMBOL_NS_GPL(vsc_tp_intr_enable, "VSC_TP"); + +/** + * vsc_tp_intr_disable - disable vsc_tp interrupt + * @tp: vsc_tp device handle + */ +void vsc_tp_intr_disable(struct vsc_tp *tp) +{ + disable_irq(tp->spi->irq); +} +EXPORT_SYMBOL_NS_GPL(vsc_tp_intr_disable, "VSC_TP"); + +static int vsc_tp_match_any(struct acpi_device *adev, void *data) +{ + struct acpi_device **__adev = data; + + *__adev = adev; + + return 1; +} + +static int vsc_tp_probe(struct spi_device *spi) +{ + struct vsc_tp *tp; + struct platform_device_info pinfo = { + .name = "intel_vsc", + .data = &tp, + .size_data = sizeof(tp), + .id = PLATFORM_DEVID_NONE, + }; + struct device *dev = &spi->dev; + struct platform_device *pdev; + struct acpi_device *adev; + int ret; + + tp = devm_kzalloc(dev, sizeof(*tp), GFP_KERNEL); + if (!tp) + return -ENOMEM; + + tp->tx_buf = devm_kzalloc(dev, sizeof(*tp->tx_buf), GFP_KERNEL); + if (!tp->tx_buf) + return -ENOMEM; + + tp->rx_buf = devm_kzalloc(dev, sizeof(*tp->rx_buf), GFP_KERNEL); + if (!tp->rx_buf) + return -ENOMEM; + + ret = devm_acpi_dev_add_driver_gpios(dev, vsc_tp_acpi_gpios); + if (ret) + return ret; + + tp->wakeuphost = devm_gpiod_get(dev, "wakeuphostint", GPIOD_IN); + if (IS_ERR(tp->wakeuphost)) + return PTR_ERR(tp->wakeuphost); + + tp->resetfw = devm_gpiod_get(dev, "resetfw", GPIOD_OUT_HIGH); + if (IS_ERR(tp->resetfw)) + return PTR_ERR(tp->resetfw); + + tp->wakeupfw = devm_gpiod_get(dev, "wakeupfw", GPIOD_OUT_HIGH); + if (IS_ERR(tp->wakeupfw)) + return PTR_ERR(tp->wakeupfw); + + atomic_set(&tp->assert_cnt, 0); + init_waitqueue_head(&tp->xfer_wait); + tp->spi = spi; + + irq_set_status_flags(spi->irq, IRQ_DISABLE_UNLAZY); + ret = request_threaded_irq(spi->irq, NULL, vsc_tp_isr, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + dev_name(dev), tp); + if (ret) + return ret; + + mutex_init(&tp->mutex); + mutex_init(&tp->event_notify_mutex); + INIT_WORK(&tp->event_work, vsc_tp_event_work); + + /* only one child acpi device */ + ret = acpi_dev_for_each_child(ACPI_COMPANION(dev), + vsc_tp_match_any, &adev); + if (!ret) { + ret = -ENODEV; + goto err_destroy_lock; + } + + pinfo.fwnode = acpi_fwnode_handle(adev); + pdev = platform_device_register_full(&pinfo); + if (IS_ERR(pdev)) { + ret = PTR_ERR(pdev); + goto err_destroy_lock; + } + + tp->pdev = pdev; + spi_set_drvdata(spi, tp); + + return 0; + +err_destroy_lock: + free_irq(spi->irq, tp); + + cancel_work_sync(&tp->event_work); + mutex_destroy(&tp->event_notify_mutex); + mutex_destroy(&tp->mutex); + + return ret; +} + +/* Note this is also used for shutdown */ +static void vsc_tp_remove(struct spi_device *spi) +{ + struct vsc_tp *tp = spi_get_drvdata(spi); + + platform_device_unregister(tp->pdev); + + free_irq(spi->irq, tp); + + cancel_work_sync(&tp->event_work); + mutex_destroy(&tp->event_notify_mutex); + mutex_destroy(&tp->mutex); +} + +static const struct acpi_device_id vsc_tp_acpi_ids[] = { + { "INTC1009" }, /* Raptor Lake */ + { "INTC1058" }, /* Tiger Lake */ + { "INTC1094" }, /* Alder Lake */ + { "INTC10D0" }, /* Meteor Lake */ + {} +}; +MODULE_DEVICE_TABLE(acpi, vsc_tp_acpi_ids); + +static struct spi_driver vsc_tp_driver = { + .probe = vsc_tp_probe, + .remove = vsc_tp_remove, + .shutdown = vsc_tp_remove, + .driver = { + .name = "vsc-tp", + .acpi_match_table = vsc_tp_acpi_ids, + }, +}; +module_spi_driver(vsc_tp_driver); + +MODULE_AUTHOR("Wentong Wu <wentong.wu@intel.com>"); +MODULE_AUTHOR("Zhifeng Wang <zhifeng.wang@intel.com>"); +MODULE_DESCRIPTION("Intel Visual Sensing Controller Transport Layer"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/mei/vsc-tp.h b/drivers/misc/mei/vsc-tp.h new file mode 100644 index 000000000000..f9513ddc3e40 --- /dev/null +++ b/drivers/misc/mei/vsc-tp.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2023, Intel Corporation. + * Intel Visual Sensing Controller Transport Layer Linux driver + */ + +#ifndef _VSC_TP_H_ +#define _VSC_TP_H_ + +#include <linux/types.h> + +#define VSC_TP_CMD_WRITE 0x01 +#define VSC_TP_CMD_READ 0x02 + +#define VSC_TP_CMD_ACK 0x10 +#define VSC_TP_CMD_NACK 0x11 +#define VSC_TP_CMD_BUSY 0x12 + +struct vsc_tp; + +/** + * typedef vsc_event_cb_t - event callback function signature + * @context: the execution context of who registered this callback + * + * The callback function is called in interrupt context and the data + * payload is only valid during the call. If the user needs access + * the data payload later, it must copy the payload. + */ +typedef void (*vsc_tp_event_cb_t)(void *context); + +int vsc_tp_rom_xfer(struct vsc_tp *tp, const void *obuf, void *ibuf, + size_t len); + +int vsc_tp_xfer(struct vsc_tp *tp, u8 cmd, const void *obuf, size_t olen, + void *ibuf, size_t ilen); + +int vsc_tp_register_event_cb(struct vsc_tp *tp, vsc_tp_event_cb_t event_cb, + void *context); + +void vsc_tp_intr_enable(struct vsc_tp *tp); +void vsc_tp_intr_disable(struct vsc_tp *tp); +void vsc_tp_intr_synchronize(struct vsc_tp *tp); + +void vsc_tp_reset(struct vsc_tp *tp); + +bool vsc_tp_need_read(struct vsc_tp *tp); + +int vsc_tp_init(struct vsc_tp *tp, struct device *dev); + +#endif |
