Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+113
View File
@@ -0,0 +1,113 @@
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2003-2019, Intel Corporation. All rights reserved.
config INTEL_MEI
tristate "Intel Management Engine Interface"
depends on PCI
depends on X86 || DRM_XE!=n || COMPILE_TEST
default X86_64 || MATOM
help
The Intel Management Engine (Intel ME) provides Manageability,
Security and Media services for system containing Intel chipsets.
if selected /dev/mei misc device will be created.
For more information see
<https://software.intel.com/en-us/manageability/>
if INTEL_MEI
config INTEL_MEI_ME
tristate "ME Enabled Intel Chipsets"
default y
help
MEI support for ME Enabled Intel chipsets.
Supported Chipsets are:
7 Series Chipset Family
6 Series Chipset Family
5 Series Chipset Family
4 Series Chipset Family
Mobile 4 Series Chipset Family
ICH9
82946GZ/GL
82G35 Express
82Q963/Q965
82P965/G965
Mobile PM965/GM965
Mobile GME965/GLE960
82Q35 Express
82G33/G31/P35/P31 Express
82Q33 Express
82X38/X48 Express
config INTEL_MEI_TXE
tristate "Intel Trusted Execution Environment with ME Interface"
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!=n || DRM_XE!=n || COMPILE_TEST
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_CSC
tristate "Intel MEI CSC embedded device"
depends on INTEL_MEI_ME
help
Intel PCI driver for the chassis controller embedded in Intel graphics devices.
An MEI device here called CSC can be embedded in discrete
Intel graphics devices, to support a range of chassis
tasks such as graphics card firmware update and security
tasks.
config INTEL_MEI_VSC_HW
tristate "Intel visual sensing controller device transport driver"
depends on ACPI && SPI
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!=n || COMPILE_TEST
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
+44
View File
@@ -0,0 +1,44 @@
# SPDX-License-Identifier: GPL-2.0
#
# Copyright (c) 2010-2019, Intel Corporation. All rights reserved.
# Makefile - Intel Management Engine Interface (Intel MEI) Linux driver
#
obj-$(CONFIG_INTEL_MEI) += mei.o
mei-objs := init.o
mei-objs += hbm.o
mei-objs += interrupt.o
mei-objs += client.o
mei-objs += main.o
mei-objs += dma-ring.o
mei-objs += bus.o
mei-objs += bus-fixup.o
mei-$(CONFIG_DEBUG_FS) += debugfs.o
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_CSC) += mei-csc.o
mei-csc-objs := pci-csc.o
obj-$(CONFIG_INTEL_MEI_TXE) += mei-txe.o
mei-txe-objs := pci-txe.o
mei-txe-objs += hw-txe.o
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
+590
View File
@@ -0,0 +1,590 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2013-2023, Intel Corporation. All rights reserved.
* Intel Management Engine Interface (Intel MEI) Linux driver
*/
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/slab.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)
static const uuid_le mei_nfc_info_guid = MEI_UUID_NFC_INFO;
#define MEI_UUID_NFC_HCI UUID_LE(0x0bb17a78, 0x2a8e, 0x4c50, \
0x94, 0xd4, 0x50, 0x26, 0x67, 0x23, 0x77, 0x5c)
#define MEI_UUID_WD UUID_LE(0x05B79A6F, 0x4628, 0x4D7F, \
0x89, 0x9D, 0xA9, 0x15, 0x14, 0xCB, 0x32, 0xAB)
#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)
#define MEI_UUID_PAVP UUID_LE(0xfbf6fcf1, 0x96cf, 0x4e2e, 0xA6, \
0xa6, 0x1b, 0xab, 0x8c, 0xbe, 0x36, 0xb1)
#define MEI_UUID_ANY NULL_UUID_LE
/**
* number_of_connections - determine whether an client be on the bus
* according number of connections
* We support only clients:
* 1. with single connection
* 2. and fixed clients (max_number_of_connections == 0)
*
* @cldev: me clients device
*/
static void number_of_connections(struct mei_cl_device *cldev)
{
if (cldev->me_cl->props.max_number_of_connections > 1)
cldev->do_match = 0;
}
/**
* blacklist - blacklist a client from the bus
*
* @cldev: me clients device
*/
static void blacklist(struct mei_cl_device *cldev)
{
cldev->do_match = 0;
}
/**
* whitelist - forcefully whitelist client
*
* @cldev: me clients device
*/
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;
__le16 reserved1;
u8 os_type;
u8 major;
u8 minor;
u8 reserved2;
} __packed;
struct mkhi_fw_ver_block {
u16 minor;
u8 major;
u8 platform;
u16 buildno;
u16 hotfix;
} __packed;
struct mkhi_fw_ver {
struct mkhi_fw_ver_block ver[MEI_MAX_FW_VER_BLOCKS];
} __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;
u8 buf[MKHI_OSVER_BUF_LEN];
struct mkhi_msg *req;
struct mkhi_fwcaps *fwcaps;
struct mei_os_ver *os_ver;
unsigned int mode = MEI_CL_IO_TX_BLOCKING | MEI_CL_IO_TX_INTERNAL;
memset(buf, 0, size);
req = (struct mkhi_msg *)buf;
req->hdr.group_id = MKHI_FWCAPS_GROUP_ID;
req->hdr.command = MKHI_FWCAPS_SET_OS_VER_APP_RULE_CMD;
fwcaps = (struct mkhi_fwcaps *)req->data;
fwcaps->id.rule_type = 0x0;
fwcaps->id.feature_id = MKHI_FEATURE_PTT;
fwcaps->len = sizeof(*os_ver);
os_ver = (struct mei_os_ver *)fwcaps->data;
os_ver->os_type = OSTYPE_LINUX;
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))
static int mei_fwver(struct mei_cl_device *cldev)
{
u8 buf[MKHI_FWVER_BUF_LEN];
struct mkhi_msg req;
struct mkhi_msg *rsp;
struct mkhi_fw_ver *fwver;
int bytes_recv, ret, i;
memset(buf, 0, sizeof(buf));
req.hdr.group_id = MKHI_GEN_GROUP_ID;
req.hdr.command = MKHI_GEN_GET_FW_VERSION_CMD;
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_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,
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_info(&cldev->dev, "Could not read FW version ret = %d\n", bytes_recv);
return -EIO;
}
rsp = (struct mkhi_msg *)buf;
fwver = (struct mkhi_fw_ver *)rsp->data;
memset(cldev->bus->fw_ver, 0, sizeof(cldev->bus->fw_ver));
for (i = 0; i < MEI_MAX_FW_VER_BLOCKS; i++) {
if ((size_t)bytes_recv < MKHI_FWVER_LEN(i + 1))
break;
dev_dbg(&cldev->dev, "FW version%d %d:%d.%d.%d.%d\n",
i, fwver->ver[i].platform,
fwver->ver[i].major, fwver->ver[i].minor,
fwver->ver[i].hotfix, fwver->ver[i].buildno);
cldev->bus->fw_ver[i].platform = fwver->ver[i].platform;
cldev->bus->fw_ver[i].major = fwver->ver[i].major;
cldev->bus->fw_ver[i].minor = fwver->ver[i].minor;
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;
/* No need to enable the client if nothing is needed from it */
if (!cldev->bus->fw_f_fw_ver_supported &&
!cldev->bus->hbm_f_os_supported)
return;
ret = mei_cldev_enable(cldev);
if (ret)
return;
if (cldev->bus->fw_f_fw_ver_supported) {
ret = mei_fwver(cldev);
if (ret < 0)
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_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);
}
/**
* mei_wd - wd client on the bus, change protocol version
* as the API has changed.
*
* @cldev: me clients device
*/
#if IS_ENABLED(CONFIG_INTEL_MEI_ME)
#include <linux/pci.h>
#include "hw-me-regs.h"
static void mei_wd(struct mei_cl_device *cldev)
{
struct pci_dev *pdev = to_pci_dev(cldev->dev.parent);
if (pdev->device == PCI_DEVICE_ID_INTEL_MEI_WPT_LP ||
pdev->device == PCI_DEVICE_ID_INTEL_MEI_SPT ||
pdev->device == PCI_DEVICE_ID_INTEL_MEI_SPT_H)
cldev->me_cl->props.protocol_version = 0x2;
cldev->do_match = 1;
}
#else
static inline void mei_wd(struct mei_cl_device *cldev) {}
#endif /* CONFIG_INTEL_MEI_ME */
struct mei_nfc_cmd {
u8 command;
u8 status;
u16 req_id;
u32 reserved;
u16 data_size;
u8 sub_command;
u8 data[];
} __packed;
struct mei_nfc_reply {
u8 command;
u8 status;
u16 req_id;
u32 reserved;
u16 data_size;
u8 sub_command;
u8 reply_status;
u8 data[];
} __packed;
struct mei_nfc_if_version {
u8 radio_version_sw[3];
u8 reserved[3];
u8 radio_version_hw[3];
u8 i2c_addr;
u8 fw_ivn;
u8 vendor_id;
u8 radio_type;
} __packed;
#define MEI_NFC_CMD_MAINTENANCE 0x00
#define MEI_NFC_SUBCMD_IF_VERSION 0x01
/* Vendors */
#define MEI_NFC_VENDOR_INSIDE 0x00
#define MEI_NFC_VENDOR_NXP 0x01
/* Radio types */
#define MEI_NFC_VENDOR_INSIDE_UREAD 0x00
#define MEI_NFC_VENDOR_NXP_PN544 0x01
/**
* mei_nfc_if_version - get NFC interface version
*
* @cl: host client (nfc info)
* @ver: NFC interface version to be filled in
*
* Return: 0 on success; < 0 otherwise
*/
static int mei_nfc_if_version(struct mei_cl *cl,
struct mei_nfc_if_version *ver)
{
struct mei_device *bus;
struct mei_nfc_cmd cmd = {
.command = MEI_NFC_CMD_MAINTENANCE,
.data_size = 1,
.sub_command = MEI_NFC_SUBCMD_IF_VERSION,
};
struct mei_nfc_reply *reply = NULL;
size_t if_version_length;
u8 vtag;
int bytes_recv, ret;
bus = cl->dev;
WARN_ON(mutex_is_locked(&bus->device_lock));
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 ret = %d\n", ret);
return ret;
}
/* to be sure on the stack we alloc memory */
if_version_length = sizeof(*reply) + sizeof(*ver);
reply = kzalloc(if_version_length, GFP_KERNEL);
if (!reply)
return -ENOMEM;
ret = 0;
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 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);
err:
kfree(reply);
return ret;
}
/**
* mei_nfc_radio_name - derive nfc radio name from the interface version
*
* @ver: NFC radio version
*
* Return: radio name string
*/
static const char *mei_nfc_radio_name(struct mei_nfc_if_version *ver)
{
if (ver->vendor_id == MEI_NFC_VENDOR_INSIDE) {
if (ver->radio_type == MEI_NFC_VENDOR_INSIDE_UREAD)
return "microread";
}
if (ver->vendor_id == MEI_NFC_VENDOR_NXP) {
if (ver->radio_type == MEI_NFC_VENDOR_NXP_PN544)
return "pn544";
}
return NULL;
}
/**
* mei_nfc - The nfc fixup function. The function retrieves nfc radio
* name and set is as device attribute so we can load
* the proper device driver for it
*
* @cldev: me client device (nfc)
*/
static void mei_nfc(struct mei_cl_device *cldev)
{
struct mei_device *bus;
struct mei_cl *cl;
struct mei_me_client *me_cl = NULL;
struct mei_nfc_if_version ver;
const char *radio_name = NULL;
int ret;
bus = cldev->bus;
mutex_lock(&bus->device_lock);
/* we need to connect to INFO GUID */
cl = mei_cl_alloc_linked(bus);
if (IS_ERR(cl)) {
ret = PTR_ERR(cl);
cl = NULL;
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(&cldev->dev, "Cannot find nfc info %d\n", ret);
goto out;
}
ret = mei_cl_connect(cl, me_cl, NULL);
if (ret < 0) {
dev_err(&cldev->dev, "Can't connect to the NFC INFO ME ret = %d\n",
ret);
goto out;
}
mutex_unlock(&bus->device_lock);
ret = mei_nfc_if_version(cl, &ver);
if (ret)
goto disconnect;
radio_name = mei_nfc_radio_name(&ver);
if (!radio_name) {
ret = -ENOENT;
dev_err(&cldev->dev, "Can't get the NFC interface version ret = %d\n",
ret);
goto disconnect;
}
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(&cldev->dev, "Can't disconnect the NFC INFO ME\n");
mei_cl_flush_queues(cl, NULL);
out:
mei_cl_unlink(cl);
mutex_unlock(&bus->device_lock);
mei_me_cl_put(me_cl);
kfree(cl);
if (ret)
cldev->do_match = 0;
dev_dbg(&cldev->dev, "end of fixup match = %d\n", cldev->do_match);
}
/**
* vt_support - enable on bus clients with vtag support
*
* @cldev: me clients device
*/
static void vt_support(struct mei_cl_device *cldev)
{
if (cldev->me_cl->props.vt_supported == 1)
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 {
const uuid_le uuid;
void (*hook)(struct mei_cl_device *cldev);
} mei_fixups[] = {
MEI_FIXUP(MEI_UUID_ANY, number_of_connections),
MEI_FIXUP(MEI_UUID_NFC_INFO, blacklist),
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, pxp_is_ready),
};
/**
* mei_cl_bus_dev_fixup - run fixup handlers
*
* @cldev: me client device
*/
void mei_cl_bus_dev_fixup(struct mei_cl_device *cldev)
{
struct mei_fixup *f;
const uuid_le *uuid = mei_me_cl_uuid(cldev->me_cl);
size_t i;
for (i = 0; i < ARRAY_SIZE(mei_fixups); i++) {
f = &mei_fixups[i];
if (uuid_le_cmp(f->uuid, MEI_UUID_ANY) == 0 ||
uuid_le_cmp(f->uuid, *uuid) == 0)
f->hook(cldev);
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+286
View File
@@ -0,0 +1,286 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2003-2018, Intel Corporation. All rights reserved.
* Intel Management Engine Interface (Intel MEI) Linux driver
*/
#ifndef _MEI_CLIENT_H_
#define _MEI_CLIENT_H_
#include <linux/types.h>
#include <linux/poll.h>
#include <linux/mei.h>
#include "mei_dev.h"
/*
* reference counting base function
*/
void mei_me_cl_init(struct mei_me_client *me_cl);
void mei_me_cl_put(struct mei_me_client *me_cl);
struct mei_me_client *mei_me_cl_get(struct mei_me_client *me_cl);
void mei_me_cl_add(struct mei_device *dev, struct mei_me_client *me_cl);
void mei_me_cl_del(struct mei_device *dev, struct mei_me_client *me_cl);
struct mei_me_client *mei_me_cl_by_uuid(struct mei_device *dev,
const uuid_le *uuid);
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_all(struct mei_device *dev);
/**
* mei_me_cl_is_active - check whether me client is active in the fw
*
* @me_cl: me client
*
* Return: true if the me client is active in the firmware
*/
static inline bool mei_me_cl_is_active(const struct mei_me_client *me_cl)
{
return !list_empty_careful(&me_cl->list);
}
/**
* mei_me_cl_uuid - return me client protocol name (uuid)
*
* @me_cl: me client
*
* Return: me client protocol name
*/
static inline const uuid_le *mei_me_cl_uuid(const struct mei_me_client *me_cl)
{
return &me_cl->props.protocol_name;
}
/**
* mei_me_cl_ver - return me client protocol version
*
* @me_cl: me client
*
* Return: me client protocol version
*/
static inline u8 mei_me_cl_ver(const struct mei_me_client *me_cl)
{
return me_cl->props.protocol_version;
}
/**
* mei_me_cl_max_conn - return me client max number of connections
*
* @me_cl: me client
*
* Return: me client max number of connections
*/
static inline u8 mei_me_cl_max_conn(const struct mei_me_client *me_cl)
{
return me_cl->props.max_number_of_connections;
}
/**
* mei_me_cl_fixed - return me client fixed address, if any
*
* @me_cl: me client
*
* Return: me client fixed address
*/
static inline u8 mei_me_cl_fixed(const struct mei_me_client *me_cl)
{
return me_cl->props.fixed_address;
}
/**
* mei_me_cl_vt - return me client vtag supported status
*
* @me_cl: me client
*
* Return: true if me client supports vt tagging
*/
static inline bool mei_me_cl_vt(const struct mei_me_client *me_cl)
{
return me_cl->props.vt_supported == 1;
}
/**
* mei_me_cl_max_len - return me client max msg length
*
* @me_cl: me client
*
* Return: me client max msg length
*/
static inline u32 mei_me_cl_max_len(const struct mei_me_client *me_cl)
{
return me_cl->props.max_msg_length;
}
/*
* MEI IO Functions
*/
void mei_io_cb_free(struct mei_cl_cb *priv_cb);
/*
* MEI Host Client Functions
*/
struct mei_cl *mei_cl_allocate(struct mei_device *dev);
int mei_cl_link(struct mei_cl *cl);
int mei_cl_unlink(struct mei_cl *cl);
struct mei_cl *mei_cl_alloc_linked(struct mei_device *dev);
struct mei_cl_cb *mei_cl_read_cb(struct mei_cl *cl, const struct file *fp);
void mei_cl_add_rd_completed(struct mei_cl *cl, struct mei_cl_cb *cb);
void mei_cl_del_rd_completed(struct mei_cl *cl, struct mei_cl_cb *cb);
struct mei_cl_cb *mei_cl_alloc_cb(struct mei_cl *cl, size_t length,
enum mei_cb_file_ops type,
const struct file *fp);
struct mei_cl_cb *mei_cl_enqueue_ctrl_wr_cb(struct mei_cl *cl, size_t length,
enum mei_cb_file_ops type,
const struct file *fp);
int mei_cl_flush_queues(struct mei_cl *cl, const struct file *fp);
struct mei_cl_vtag *mei_cl_vtag_alloc(struct file *fp, u8 vtag);
const struct file *mei_cl_fp_by_vtag(const struct mei_cl *cl, u8 vtag);
int mei_cl_vt_support_check(const struct mei_cl *cl);
/*
* MEI input output function prototype
*/
/**
* mei_cl_is_connected - host client is connected
*
* @cl: host client
*
* Return: true if the host client is connected
*/
static inline bool mei_cl_is_connected(const struct mei_cl *cl)
{
return cl->state == MEI_FILE_CONNECTED;
}
/**
* mei_cl_me_id - me client id
*
* @cl: host client
*
* Return: me client id or 0 if client is not connected
*/
static inline u8 mei_cl_me_id(const struct mei_cl *cl)
{
return cl->me_cl ? cl->me_cl->client_id : 0;
}
/**
* mei_cl_mtu - maximal message that client can send and receive
*
* @cl: host client
*
* Return: mtu or 0 if client is not connected
*/
static inline size_t mei_cl_mtu(const struct mei_cl *cl)
{
return cl->me_cl ? cl->me_cl->props.max_msg_length : 0;
}
/**
* mei_cl_is_fixed_address - check whether the me client uses fixed address
*
* @cl: host client
*
* Return: true if the client is connected and it has fixed me address
*/
static inline bool mei_cl_is_fixed_address(const struct mei_cl *cl)
{
return cl->me_cl && cl->me_cl->props.fixed_address;
}
/**
* mei_cl_is_single_recv_buf- check whether the me client
* uses single receiving buffer
*
* @cl: host client
*
* Return: true if single_recv_buf == 1; 0 otherwise
*/
static inline bool mei_cl_is_single_recv_buf(const struct mei_cl *cl)
{
return cl->me_cl->props.single_recv_buf;
}
/**
* mei_cl_uuid - client's uuid
*
* @cl: host client
*
* Return: return uuid of connected me client
*/
static inline const uuid_le *mei_cl_uuid(const struct mei_cl *cl)
{
return mei_me_cl_uuid(cl->me_cl);
}
/**
* mei_cl_host_addr - client's host address
*
* @cl: host client
*
* Return: 0 for fixed address client, host address for dynamic client
*/
static inline u8 mei_cl_host_addr(const struct mei_cl *cl)
{
return mei_cl_is_fixed_address(cl) ? 0 : cl->host_client_id;
}
int mei_cl_disconnect(struct mei_cl *cl);
int mei_cl_irq_disconnect(struct mei_cl *cl, struct mei_cl_cb *cb,
struct list_head *cmpl_list);
int mei_cl_connect(struct mei_cl *cl, struct mei_me_client *me_cl,
const struct file *file);
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, unsigned long timeout);
int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb,
struct list_head *cmpl_list);
void mei_cl_complete(struct mei_cl *cl, struct mei_cl_cb *cb);
void mei_host_client_init(struct mei_device *dev);
u8 mei_cl_notify_fop2req(enum mei_cb_file_ops fop);
enum mei_cb_file_ops mei_cl_notify_req2fop(u8 request);
int mei_cl_notify_request(struct mei_cl *cl,
const struct file *file, u8 request);
int mei_cl_irq_notify(struct mei_cl *cl, struct mei_cl_cb *cb,
struct list_head *cmpl_list);
int mei_cl_notify_get(struct mei_cl *cl, bool block, bool *notify_ev);
void mei_cl_notify(struct mei_cl *cl);
void mei_cl_all_disconnect(struct mei_device *dev);
int mei_cl_irq_dma_map(struct mei_cl *cl, struct mei_cl_cb *cb,
struct list_head *cmpl_list);
int mei_cl_irq_dma_unmap(struct mei_cl *cl, struct mei_cl_cb *cb,
struct list_head *cmpl_list);
int mei_cl_dma_alloc_and_map(struct mei_cl *cl, const struct file *fp,
u8 buffer_id, size_t size);
int mei_cl_dma_unmap(struct mei_cl *cl, const struct file *fp);
#define MEI_CL_FMT "cl:host=%02d me=%02d "
#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)
#define cl_warn(dev, cl, format, 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)
#endif /* _MEI_CLIENT_H_ */
+195
View File
@@ -0,0 +1,195 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2012-2022, Intel Corporation. All rights reserved
* Intel Management Engine Interface (Intel MEI) Linux driver
*/
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/mei.h>
#include "mei_dev.h"
#include "client.h"
#include "hw.h"
static int mei_dbgfs_meclients_show(struct seq_file *m, void *unused)
{
struct mei_device *dev = m->private;
struct mei_me_client *me_cl;
int i = 0;
if (!dev)
return -ENODEV;
down_read(&dev->me_clients_rwsem);
seq_puts(m, " |id|fix| UUID |con|msg len|sb|refc|vt|\n");
/* if the driver is not enabled the list won't be consistent */
if (dev->dev_state != MEI_DEV_ENABLED)
goto out;
list_for_each_entry(me_cl, &dev->me_clients, list) {
if (!mei_me_cl_get(me_cl))
continue;
seq_printf(m, "%2d|%2d|%3d|%pUl|%3d|%7d|%2d|%4d|%2d|\n",
i++, me_cl->client_id,
me_cl->props.fixed_address,
&me_cl->props.protocol_name,
me_cl->props.max_number_of_connections,
me_cl->props.max_msg_length,
me_cl->props.single_recv_buf,
kref_read(&me_cl->refcnt),
me_cl->props.vt_supported);
mei_me_cl_put(me_cl);
}
out:
up_read(&dev->me_clients_rwsem);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(mei_dbgfs_meclients);
static int mei_dbgfs_active_show(struct seq_file *m, void *unused)
{
struct mei_device *dev = m->private;
struct mei_cl *cl;
int i = 0;
if (!dev)
return -ENODEV;
mutex_lock(&dev->device_lock);
seq_puts(m, " |me|host|state|rd|wr|wrq\n");
/* if the driver is not enabled the list won't be consistent */
if (dev->dev_state != MEI_DEV_ENABLED)
goto out;
list_for_each_entry(cl, &dev->file_list, link) {
seq_printf(m, "%3d|%2d|%4d|%5d|%2d|%2d|%3u\n",
i, mei_cl_me_id(cl), cl->host_client_id, cl->state,
!list_empty(&cl->rd_completed), cl->writing_state,
cl->tx_cb_queued);
i++;
}
out:
mutex_unlock(&dev->device_lock);
return 0;
}
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;
seq_printf(m, "dev: %s\n", mei_dev_state_str(dev->dev_state));
seq_printf(m, "hbm: %s\n", mei_hbm_state_str(dev->hbm_state));
if (dev->hbm_state >= MEI_HBM_ENUM_CLIENTS &&
dev->hbm_state <= MEI_HBM_STARTED) {
seq_puts(m, "hbm features:\n");
seq_printf(m, "\tPG: %01d\n", dev->hbm_f_pg_supported);
seq_printf(m, "\tDC: %01d\n", dev->hbm_f_dc_supported);
seq_printf(m, "\tIE: %01d\n", dev->hbm_f_ie_supported);
seq_printf(m, "\tDOT: %01d\n", dev->hbm_f_dot_supported);
seq_printf(m, "\tEV: %01d\n", dev->hbm_f_ev_supported);
seq_printf(m, "\tFA: %01d\n", dev->hbm_f_fa_supported);
seq_printf(m, "\tOS: %01d\n", dev->hbm_f_os_supported);
seq_printf(m, "\tDR: %01d\n", dev->hbm_f_dr_supported);
seq_printf(m, "\tVT: %01d\n", dev->hbm_f_vt_supported);
seq_printf(m, "\tCAP: %01d\n", dev->hbm_f_cap_supported);
seq_printf(m, "\tCD: %01d\n", dev->hbm_f_cd_supported);
}
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);
static ssize_t mei_dbgfs_write_allow_fa(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct mei_device *dev;
int ret;
dev = container_of(file->private_data,
struct mei_device, allow_fixed_address);
ret = debugfs_write_file_bool(file, user_buf, count, ppos);
if (ret < 0)
return ret;
dev->override_fixed_address = true;
return ret;
}
static const struct file_operations mei_dbgfs_allow_fa_fops = {
.open = simple_open,
.read = debugfs_read_file_bool,
.write = mei_dbgfs_write_allow_fa,
.llseek = generic_file_llseek,
};
/**
* mei_dbgfs_deregister - Remove the debugfs files and directories
*
* @dev: the mei device structure
*/
void mei_dbgfs_deregister(struct mei_device *dev)
{
if (!dev->dbgfs_dir)
return;
debugfs_remove_recursive(dev->dbgfs_dir);
dev->dbgfs_dir = NULL;
}
/**
* mei_dbgfs_register - Add the debugfs files
*
* @dev: the mei device structure
* @name: the mei device name
*/
void mei_dbgfs_register(struct mei_device *dev, const char *name)
{
struct dentry *dir;
dir = debugfs_create_dir(name, NULL);
dev->dbgfs_dir = dir;
debugfs_create_file("meclients", S_IRUSR, dir, dev,
&mei_dbgfs_meclients_fops);
debugfs_create_file("active", S_IRUSR, dir, dev,
&mei_dbgfs_active_fops);
debugfs_create_file("devstate", S_IRUSR, dir, dev,
&mei_dbgfs_devstate_fops);
debugfs_create_file("allow_fixed_address", S_IRUSR | S_IWUSR, dir,
&dev->allow_fixed_address,
&mei_dbgfs_allow_fa_fops);
}
+273
View File
@@ -0,0 +1,273 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright(c) 2016-2018 Intel Corporation. All rights reserved.
*/
#include <linux/dma-mapping.h>
#include <linux/mei.h>
#include "mei_dev.h"
/**
* mei_dmam_dscr_alloc() - allocate a managed coherent buffer
* for the dma descriptor
* @dev: mei_device
* @dscr: dma descriptor
*
* Return:
* * 0 - on success or zero allocation request
* * -EINVAL - if size is not power of 2
* * -ENOMEM - of allocation has failed
*/
static int mei_dmam_dscr_alloc(struct mei_device *dev,
struct mei_dma_dscr *dscr)
{
if (!dscr->size)
return 0;
if (WARN_ON(!is_power_of_2(dscr->size)))
return -EINVAL;
if (dscr->vaddr)
return 0;
dscr->vaddr = dmam_alloc_coherent(dev->parent, dscr->size, &dscr->daddr,
GFP_KERNEL);
if (!dscr->vaddr)
return -ENOMEM;
return 0;
}
/**
* mei_dmam_dscr_free() - free a managed coherent buffer
* from the dma descriptor
* @dev: mei_device
* @dscr: dma descriptor
*/
static void mei_dmam_dscr_free(struct mei_device *dev,
struct mei_dma_dscr *dscr)
{
if (!dscr->vaddr)
return;
dmam_free_coherent(dev->parent, dscr->size, dscr->vaddr, dscr->daddr);
dscr->vaddr = NULL;
}
/**
* mei_dmam_ring_free() - free dma ring buffers
* @dev: mei device
*/
void mei_dmam_ring_free(struct mei_device *dev)
{
int i;
for (i = 0; i < DMA_DSCR_NUM; i++)
mei_dmam_dscr_free(dev, &dev->dr_dscr[i]);
}
/**
* mei_dmam_ring_alloc() - allocate dma ring buffers
* @dev: mei device
*
* Return: -ENOMEM on allocation failure 0 otherwise
*/
int mei_dmam_ring_alloc(struct mei_device *dev)
{
int i;
for (i = 0; i < DMA_DSCR_NUM; i++)
if (mei_dmam_dscr_alloc(dev, &dev->dr_dscr[i]))
goto err;
return 0;
err:
mei_dmam_ring_free(dev);
return -ENOMEM;
}
/**
* mei_dma_ring_is_allocated() - check if dma ring is allocated
* @dev: mei device
*
* Return: true if dma ring is allocated
*/
bool mei_dma_ring_is_allocated(struct mei_device *dev)
{
return !!dev->dr_dscr[DMA_DSCR_HOST].vaddr;
}
static inline
struct hbm_dma_ring_ctrl *mei_dma_ring_ctrl(struct mei_device *dev)
{
return (struct hbm_dma_ring_ctrl *)dev->dr_dscr[DMA_DSCR_CTRL].vaddr;
}
/**
* mei_dma_ring_reset() - reset the dma control block
* @dev: mei device
*/
void mei_dma_ring_reset(struct mei_device *dev)
{
struct hbm_dma_ring_ctrl *ctrl = mei_dma_ring_ctrl(dev);
if (!ctrl)
return;
memset(ctrl, 0, sizeof(*ctrl));
}
/**
* mei_dma_copy_from() - copy from dma ring into buffer
* @dev: mei device
* @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)
{
unsigned char *dbuf = dev->dr_dscr[DMA_DSCR_DEVICE].vaddr;
size_t b_offset = offset << 2;
size_t b_n = n << 2;
memcpy(buf, dbuf + b_offset, b_n);
return b_n;
}
/**
* mei_dma_copy_to() - copy to a buffer to the dma ring
* @dev: mei device
* @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)
{
unsigned char *hbuf = dev->dr_dscr[DMA_DSCR_HOST].vaddr;
size_t b_offset = offset << 2;
size_t b_n = n << 2;
memcpy(hbuf + b_offset, buf, b_n);
return b_n;
}
/**
* mei_dma_ring_read() - read data from the ring
* @dev: mei device
* @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)
{
struct hbm_dma_ring_ctrl *ctrl = mei_dma_ring_ctrl(dev);
u32 dbuf_depth;
u32 rd_idx, rem, slots;
if (WARN_ON(!ctrl))
return;
dev_dbg(&dev->dev, "reading from dma %u bytes\n", len);
if (!len)
return;
dbuf_depth = dev->dr_dscr[DMA_DSCR_DEVICE].size >> 2;
rd_idx = READ_ONCE(ctrl->dbuf_rd_idx) & (dbuf_depth - 1);
slots = mei_data2slots(len);
/* if buf is NULL we drop the packet by advancing the pointer.*/
if (!buf)
goto out;
if (rd_idx + slots > dbuf_depth) {
buf += mei_dma_copy_from(dev, buf, rd_idx, dbuf_depth - rd_idx);
rem = slots - (dbuf_depth - rd_idx);
rd_idx = 0;
} else {
rem = slots;
}
mei_dma_copy_from(dev, buf, rd_idx, rem);
out:
WRITE_ONCE(ctrl->dbuf_rd_idx, ctrl->dbuf_rd_idx + slots);
}
static inline u32 mei_dma_ring_hbuf_depth(struct mei_device *dev)
{
return dev->dr_dscr[DMA_DSCR_HOST].size >> 2;
}
/**
* mei_dma_ring_empty_slots() - calaculate number of empty slots in dma ring
* @dev: mei_device
*
* Return: number of empty slots
*/
u32 mei_dma_ring_empty_slots(struct mei_device *dev)
{
struct hbm_dma_ring_ctrl *ctrl = mei_dma_ring_ctrl(dev);
u32 wr_idx, rd_idx, hbuf_depth, empty;
if (!mei_dma_ring_is_allocated(dev))
return 0;
if (WARN_ON(!ctrl))
return 0;
/* easier to work in slots */
hbuf_depth = mei_dma_ring_hbuf_depth(dev);
rd_idx = READ_ONCE(ctrl->hbuf_rd_idx);
wr_idx = READ_ONCE(ctrl->hbuf_wr_idx);
if (rd_idx > wr_idx)
empty = rd_idx - wr_idx;
else
empty = hbuf_depth - (wr_idx - rd_idx);
return empty;
}
/**
* mei_dma_ring_write - write data to dma ring host buffer
*
* @dev: mei_device
* @buf: data will be written
* @len: data length
*/
void mei_dma_ring_write(struct mei_device *dev, unsigned char *buf, u32 len)
{
struct hbm_dma_ring_ctrl *ctrl = mei_dma_ring_ctrl(dev);
u32 hbuf_depth;
u32 wr_idx, rem, slots;
if (WARN_ON(!ctrl))
return;
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);
if (wr_idx + slots > hbuf_depth) {
buf += mei_dma_copy_to(dev, buf, wr_idx, hbuf_depth - wr_idx);
rem = slots - (hbuf_depth - wr_idx);
wr_idx = 0;
} else {
rem = slots;
}
mei_dma_copy_to(dev, buf, wr_idx, rem);
WRITE_ONCE(ctrl->hbuf_wr_idx, ctrl->hbuf_wr_idx + slots);
}
+303
View File
@@ -0,0 +1,303 @@
// 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, const char *name, u32 *val)
{
struct mei_me_hw *hw = to_me_hw(dev);
*val = ioread32(hw->mem_addr + where + 0xC00);
trace_mei_reg_read(&dev->dev, name, where, *val);
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");
@@ -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!=n || DRM_XE!=n || COMPILE_TEST
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.
@@ -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
@@ -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_obj(*comp_master);
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");
File diff suppressed because it is too large Load Diff
+58
View File
@@ -0,0 +1,58 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2003-2018, Intel Corporation. All rights reserved.
* Intel Management Engine Interface (Intel MEI) Linux driver
*/
#ifndef _MEI_HBM_H_
#define _MEI_HBM_H_
struct mei_device;
struct mei_msg_hdr;
struct mei_cl;
struct mei_dma_data;
/**
* enum mei_hbm_state - host bus message protocol state
*
* @MEI_HBM_IDLE : protocol not started
* @MEI_HBM_STARTING : start request message was sent
* @MEI_HBM_CAP_SETUP : capabilities request message was sent
* @MEI_HBM_DR_SETUP : dma ring setup request message was sent
* @MEI_HBM_ENUM_CLIENTS : enumeration request was sent
* @MEI_HBM_CLIENT_PROPERTIES : acquiring clients properties
* @MEI_HBM_STARTED : enumeration was completed
* @MEI_HBM_STOPPED : stopping exchange
*/
enum mei_hbm_state {
MEI_HBM_IDLE = 0,
MEI_HBM_STARTING,
MEI_HBM_CAP_SETUP,
MEI_HBM_DR_SETUP,
MEI_HBM_ENUM_CLIENTS,
MEI_HBM_CLIENT_PROPERTIES,
MEI_HBM_STARTED,
MEI_HBM_STOPPED,
};
const char *mei_hbm_state_str(enum mei_hbm_state state);
int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr);
void mei_hbm_idle(struct mei_device *dev);
void mei_hbm_reset(struct mei_device *dev);
int mei_hbm_start_req(struct mei_device *dev);
int mei_hbm_start_wait(struct mei_device *dev);
int mei_hbm_cl_flow_control_req(struct mei_device *dev, struct mei_cl *cl);
int mei_hbm_cl_disconnect_req(struct mei_device *dev, struct mei_cl *cl);
int mei_hbm_cl_disconnect_rsp(struct mei_device *dev, struct mei_cl *cl);
int mei_hbm_cl_connect_req(struct mei_device *dev, struct mei_cl *cl);
bool mei_hbm_version_is_supported(struct mei_device *dev);
int mei_hbm_pg(struct mei_device *dev, u8 pg_cmd);
void mei_hbm_pg_resume(struct mei_device *dev);
int mei_hbm_cl_notify_req(struct mei_device *dev,
struct mei_cl *cl, u8 request);
int mei_hbm_cl_dma_map_req(struct mei_device *dev, struct mei_cl *cl);
int mei_hbm_cl_dma_unmap_req(struct mei_device *dev, struct mei_cl *cl);
#endif /* _MEI_HBM_H_ */
+12
View File
@@ -0,0 +1,12 @@
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2019, Intel Corporation. All rights reserved.
#
config INTEL_MEI_HDCP
tristate "Intel HDCP2.2 services of ME Interface"
depends on INTEL_MEI_ME
depends on DRM_I915!=n || DRM_XE!=n || COMPILE_TEST
help
MEI Support for HDCP2.2 Services on Intel platforms.
Enables the ME FW services required for HDCP2.2 support through
I915 display driver of Intel.
@@ -0,0 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
#
# Copyright (c) 2019, Intel Corporation. All rights reserved.
#
# Makefile - HDCP client driver for Intel MEI Bus Driver.
obj-$(CONFIG_INTEL_MEI_HDCP) += mei_hdcp.o
@@ -0,0 +1,889 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright © 2019 Intel Corporation
*
* mei_hdcp.c: HDCP client driver for mei bus
*
* Author:
* Ramalingam C <ramalingam.c@intel.com>
*/
/**
* DOC: MEI_HDCP Client Driver
*
* The mei_hdcp driver acts as a translation layer between HDCP 2.2
* protocol implementer (I915) and ME FW by translating HDCP2.2
* negotiation messages to ME FW command payloads and vice versa.
*/
#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_hdcp_interface.h>
#include "mei_hdcp.h"
/**
* mei_hdcp_initiate_session() - Initiate a Wired HDCP2.2 Tx Session in ME FW
* @dev: device corresponding to the mei_cl_device
* @data: Intel HW specific hdcp data
* @ake_data: AKE_Init msg output.
*
* Return: 0 on Success, <0 on Failure.
*/
static int
mei_hdcp_initiate_session(struct device *dev, struct hdcp_port_data *data,
struct hdcp2_ake_init *ake_data)
{
struct wired_cmd_initiate_hdcp2_session_in session_init_in = { { 0 } };
struct wired_cmd_initiate_hdcp2_session_out
session_init_out = { { 0 } };
struct mei_cl_device *cldev;
ssize_t byte;
if (!dev || !data || !ake_data)
return -EINVAL;
cldev = to_mei_cl_device(dev);
session_init_in.header.api_version = HDCP_API_VERSION;
session_init_in.header.command_id = WIRED_INITIATE_HDCP2_SESSION;
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->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,
sizeof(session_init_in));
if (byte < 0) {
dev_dbg(dev, "mei_cldev_send failed. %zd\n", byte);
return byte;
}
byte = mei_cldev_recv(cldev, (u8 *)&session_init_out,
sizeof(session_init_out));
if (byte < 0) {
dev_dbg(dev, "mei_cldev_recv failed. %zd\n", byte);
return byte;
}
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);
return -EIO;
}
ake_data->msg_id = HDCP_2_2_AKE_INIT;
ake_data->tx_caps = session_init_out.tx_caps;
memcpy(ake_data->r_tx, session_init_out.r_tx, HDCP_2_2_RTX_LEN);
return 0;
}
/**
* mei_hdcp_verify_receiver_cert_prepare_km() - Verify the Receiver Certificate
* AKE_Send_Cert and prepare AKE_Stored_Km/AKE_No_Stored_Km
* @dev: device corresponding to the mei_cl_device
* @data: Intel HW specific hdcp data
* @rx_cert: AKE_Send_Cert for verification
* @km_stored: Pairing status flag output
* @ek_pub_km: AKE_Stored_Km/AKE_No_Stored_Km output msg
* @msg_sz : size of AKE_XXXXX_Km output msg
*
* Return: 0 on Success, <0 on Failure
*/
static int
mei_hdcp_verify_receiver_cert_prepare_km(struct device *dev,
struct hdcp_port_data *data,
struct hdcp2_ake_send_cert *rx_cert,
bool *km_stored,
struct hdcp2_ake_no_stored_km
*ek_pub_km,
size_t *msg_sz)
{
struct wired_cmd_verify_receiver_cert_in verify_rxcert_in = { { 0 } };
struct wired_cmd_verify_receiver_cert_out verify_rxcert_out = { { 0 } };
struct mei_cl_device *cldev;
ssize_t byte;
if (!dev || !data || !rx_cert || !km_stored || !ek_pub_km || !msg_sz)
return -EINVAL;
cldev = to_mei_cl_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 = 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->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);
memcpy(verify_rxcert_in.rx_caps, rx_cert->rx_caps, HDCP_2_2_RXCAPS_LEN);
byte = mei_cldev_send(cldev, (u8 *)&verify_rxcert_in,
sizeof(verify_rxcert_in));
if (byte < 0) {
dev_dbg(dev, "mei_cldev_send failed: %zd\n", byte);
return byte;
}
byte = mei_cldev_recv(cldev, (u8 *)&verify_rxcert_out,
sizeof(verify_rxcert_out));
if (byte < 0) {
dev_dbg(dev, "mei_cldev_recv failed: %zd\n", byte);
return byte;
}
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);
return -EIO;
}
*km_stored = !!verify_rxcert_out.km_stored;
if (verify_rxcert_out.km_stored) {
ek_pub_km->msg_id = HDCP_2_2_AKE_STORED_KM;
*msg_sz = sizeof(struct hdcp2_ake_stored_km);
} else {
ek_pub_km->msg_id = HDCP_2_2_AKE_NO_STORED_KM;
*msg_sz = sizeof(struct hdcp2_ake_no_stored_km);
}
memcpy(ek_pub_km->e_kpub_km, &verify_rxcert_out.ekm_buff,
sizeof(verify_rxcert_out.ekm_buff));
return 0;
}
/**
* mei_hdcp_verify_hprime() - Verify AKE_Send_H_prime at ME FW.
* @dev: device corresponding to the mei_cl_device
* @data: Intel HW specific hdcp data
* @rx_hprime: AKE_Send_H_prime msg for ME FW verification
*
* Return: 0 on Success, <0 on Failure
*/
static int
mei_hdcp_verify_hprime(struct device *dev, struct hdcp_port_data *data,
struct hdcp2_ake_send_hprime *rx_hprime)
{
struct wired_cmd_ake_send_hprime_in send_hprime_in = { { 0 } };
struct wired_cmd_ake_send_hprime_out send_hprime_out = { { 0 } };
struct mei_cl_device *cldev;
ssize_t byte;
if (!dev || !data || !rx_hprime)
return -EINVAL;
cldev = to_mei_cl_device(dev);
send_hprime_in.header.api_version = HDCP_API_VERSION;
send_hprime_in.header.command_id = WIRED_AKE_SEND_HPRIME;
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->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);
byte = mei_cldev_send(cldev, (u8 *)&send_hprime_in,
sizeof(send_hprime_in));
if (byte < 0) {
dev_dbg(dev, "mei_cldev_send failed. %zd\n", byte);
return byte;
}
byte = mei_cldev_recv(cldev, (u8 *)&send_hprime_out,
sizeof(send_hprime_out));
if (byte < 0) {
dev_dbg(dev, "mei_cldev_recv failed. %zd\n", byte);
return byte;
}
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;
}
return 0;
}
/**
* mei_hdcp_store_pairing_info() - Store pairing info received at ME FW
* @dev: device corresponding to the mei_cl_device
* @data: Intel HW specific hdcp data
* @pairing_info: AKE_Send_Pairing_Info msg input to ME FW
*
* Return: 0 on Success, <0 on Failure
*/
static int
mei_hdcp_store_pairing_info(struct device *dev, struct hdcp_port_data *data,
struct hdcp2_ake_send_pairing_info *pairing_info)
{
struct wired_cmd_ake_send_pairing_info_in pairing_info_in = { { 0 } };
struct wired_cmd_ake_send_pairing_info_out pairing_info_out = { { 0 } };
struct mei_cl_device *cldev;
ssize_t byte;
if (!dev || !data || !pairing_info)
return -EINVAL;
cldev = to_mei_cl_device(dev);
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 = 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->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);
byte = mei_cldev_send(cldev, (u8 *)&pairing_info_in,
sizeof(pairing_info_in));
if (byte < 0) {
dev_dbg(dev, "mei_cldev_send failed. %zd\n", byte);
return byte;
}
byte = mei_cldev_recv(cldev, (u8 *)&pairing_info_out,
sizeof(pairing_info_out));
if (byte < 0) {
dev_dbg(dev, "mei_cldev_recv failed. %zd\n", byte);
return byte;
}
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);
return -EIO;
}
return 0;
}
/**
* mei_hdcp_initiate_locality_check() - Prepare LC_Init
* @dev: device corresponding to the mei_cl_device
* @data: Intel HW specific hdcp data
* @lc_init_data: LC_Init msg output
*
* Return: 0 on Success, <0 on Failure
*/
static int
mei_hdcp_initiate_locality_check(struct device *dev,
struct hdcp_port_data *data,
struct hdcp2_lc_init *lc_init_data)
{
struct wired_cmd_init_locality_check_in lc_init_in = { { 0 } };
struct wired_cmd_init_locality_check_out lc_init_out = { { 0 } };
struct mei_cl_device *cldev;
ssize_t byte;
if (!dev || !data || !lc_init_data)
return -EINVAL;
cldev = to_mei_cl_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 = 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->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) {
dev_dbg(dev, "mei_cldev_send failed. %zd\n", byte);
return byte;
}
byte = mei_cldev_recv(cldev, (u8 *)&lc_init_out, sizeof(lc_init_out));
if (byte < 0) {
dev_dbg(dev, "mei_cldev_recv failed. %zd\n", byte);
return byte;
}
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;
}
lc_init_data->msg_id = HDCP_2_2_LC_INIT;
memcpy(lc_init_data->r_n, lc_init_out.r_n, HDCP_2_2_RN_LEN);
return 0;
}
/**
* mei_hdcp_verify_lprime() - Verify lprime.
* @dev: device corresponding to the mei_cl_device
* @data: Intel HW specific hdcp data
* @rx_lprime: LC_Send_L_prime msg for ME FW verification
*
* Return: 0 on Success, <0 on Failure
*/
static int
mei_hdcp_verify_lprime(struct device *dev, struct hdcp_port_data *data,
struct hdcp2_lc_send_lprime *rx_lprime)
{
struct wired_cmd_validate_locality_in verify_lprime_in = { { 0 } };
struct wired_cmd_validate_locality_out verify_lprime_out = { { 0 } };
struct mei_cl_device *cldev;
ssize_t byte;
if (!dev || !data || !rx_lprime)
return -EINVAL;
cldev = to_mei_cl_device(dev);
verify_lprime_in.header.api_version = HDCP_API_VERSION;
verify_lprime_in.header.command_id = WIRED_VALIDATE_LOCALITY;
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->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);
byte = mei_cldev_send(cldev, (u8 *)&verify_lprime_in,
sizeof(verify_lprime_in));
if (byte < 0) {
dev_dbg(dev, "mei_cldev_send failed. %zd\n", byte);
return byte;
}
byte = mei_cldev_recv(cldev, (u8 *)&verify_lprime_out,
sizeof(verify_lprime_out));
if (byte < 0) {
dev_dbg(dev, "mei_cldev_recv failed. %zd\n", byte);
return byte;
}
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);
return -EIO;
}
return 0;
}
/**
* mei_hdcp_get_session_key() - Prepare SKE_Send_Eks.
* @dev: device corresponding to the mei_cl_device
* @data: Intel HW specific hdcp data
* @ske_data: SKE_Send_Eks msg output from ME FW.
*
* Return: 0 on Success, <0 on Failure
*/
static int mei_hdcp_get_session_key(struct device *dev,
struct hdcp_port_data *data,
struct hdcp2_ske_send_eks *ske_data)
{
struct wired_cmd_get_session_key_in get_skey_in = { { 0 } };
struct wired_cmd_get_session_key_out get_skey_out = { { 0 } };
struct mei_cl_device *cldev;
ssize_t byte;
if (!dev || !data || !ske_data)
return -EINVAL;
cldev = to_mei_cl_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 = 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->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) {
dev_dbg(dev, "mei_cldev_send failed. %zd\n", byte);
return byte;
}
byte = mei_cldev_recv(cldev, (u8 *)&get_skey_out, sizeof(get_skey_out));
if (byte < 0) {
dev_dbg(dev, "mei_cldev_recv failed. %zd\n", byte);
return byte;
}
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;
}
ske_data->msg_id = HDCP_2_2_SKE_SEND_EKS;
memcpy(ske_data->e_dkey_ks, get_skey_out.e_dkey_ks,
HDCP_2_2_E_DKEY_KS_LEN);
memcpy(ske_data->riv, get_skey_out.r_iv, HDCP_2_2_RIV_LEN);
return 0;
}
/**
* mei_hdcp_repeater_check_flow_prepare_ack() - Validate the Downstream topology
* and prepare rep_ack.
* @dev: device corresponding to the mei_cl_device
* @data: Intel HW specific hdcp data
* @rep_topology: Receiver ID List to be validated
* @rep_send_ack : repeater ack from ME FW.
*
* Return: 0 on Success, <0 on Failure
*/
static int
mei_hdcp_repeater_check_flow_prepare_ack(struct device *dev,
struct hdcp_port_data *data,
struct hdcp2_rep_send_receiverid_list
*rep_topology,
struct hdcp2_rep_send_ack
*rep_send_ack)
{
struct wired_cmd_verify_repeater_in verify_repeater_in = { { 0 } };
struct wired_cmd_verify_repeater_out verify_repeater_out = { { 0 } };
struct mei_cl_device *cldev;
ssize_t byte;
if (!dev || !rep_topology || !rep_send_ack || !data)
return -EINVAL;
cldev = to_mei_cl_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 = 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->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);
memcpy(verify_repeater_in.seq_num_v, rep_topology->seq_num_v,
HDCP_2_2_SEQ_NUM_LEN);
memcpy(verify_repeater_in.v_prime, rep_topology->v_prime,
HDCP_2_2_V_PRIME_HALF_LEN);
memcpy(verify_repeater_in.receiver_ids, rep_topology->receiver_ids,
HDCP_2_2_RECEIVER_IDS_MAX_LEN);
byte = mei_cldev_send(cldev, (u8 *)&verify_repeater_in,
sizeof(verify_repeater_in));
if (byte < 0) {
dev_dbg(dev, "mei_cldev_send failed. %zd\n", byte);
return byte;
}
byte = mei_cldev_recv(cldev, (u8 *)&verify_repeater_out,
sizeof(verify_repeater_out));
if (byte < 0) {
dev_dbg(dev, "mei_cldev_recv failed. %zd\n", byte);
return byte;
}
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);
return -EIO;
}
memcpy(rep_send_ack->v, verify_repeater_out.v,
HDCP_2_2_V_PRIME_HALF_LEN);
rep_send_ack->msg_id = HDCP_2_2_REP_SEND_ACK;
return 0;
}
/**
* mei_hdcp_verify_mprime() - Verify mprime.
* @dev: device corresponding to the mei_cl_device
* @data: Intel HW specific hdcp data
* @stream_ready: RepeaterAuth_Stream_Ready msg for ME FW verification.
*
* Return: 0 on Success, <0 on Failure
*/
static int mei_hdcp_verify_mprime(struct device *dev,
struct hdcp_port_data *data,
struct hdcp2_rep_stream_ready *stream_ready)
{
struct wired_cmd_repeater_auth_stream_req_in *verify_mprime_in;
struct wired_cmd_repeater_auth_stream_req_out
verify_mprime_out = { { 0 } };
struct mei_cl_device *cldev;
ssize_t byte;
size_t cmd_size;
if (!dev || !stream_ready || !data)
return -EINVAL;
cldev = to_mei_cl_device(dev);
cmd_size = struct_size(verify_mprime_in, streams, data->k);
if (cmd_size == SIZE_MAX)
return -EINVAL;
verify_mprime_in = kzalloc(cmd_size, GFP_KERNEL);
if (!verify_mprime_in)
return -ENOMEM;
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 = 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->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);
memcpy(verify_mprime_in->streams, data->streams,
array_size(data->k, sizeof(*data->streams)));
verify_mprime_in->k = cpu_to_be16(data->k);
byte = mei_cldev_send(cldev, (u8 *)verify_mprime_in, cmd_size);
kfree(verify_mprime_in);
if (byte < 0) {
dev_dbg(dev, "mei_cldev_send failed. %zd\n", byte);
return byte;
}
byte = mei_cldev_recv(cldev, (u8 *)&verify_mprime_out,
sizeof(verify_mprime_out));
if (byte < 0) {
dev_dbg(dev, "mei_cldev_recv failed. %zd\n", byte);
return byte;
}
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);
return -EIO;
}
return 0;
}
/**
* mei_hdcp_enable_authentication() - Mark a port as authenticated
* through ME FW
* @dev: device corresponding to the mei_cl_device
* @data: Intel HW specific hdcp data
*
* Return: 0 on Success, <0 on Failure
*/
static int mei_hdcp_enable_authentication(struct device *dev,
struct hdcp_port_data *data)
{
struct wired_cmd_enable_auth_in enable_auth_in = { { 0 } };
struct wired_cmd_enable_auth_out enable_auth_out = { { 0 } };
struct mei_cl_device *cldev;
ssize_t byte;
if (!dev || !data)
return -EINVAL;
cldev = to_mei_cl_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 = 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->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,
sizeof(enable_auth_in));
if (byte < 0) {
dev_dbg(dev, "mei_cldev_send failed. %zd\n", byte);
return byte;
}
byte = mei_cldev_recv(cldev, (u8 *)&enable_auth_out,
sizeof(enable_auth_out));
if (byte < 0) {
dev_dbg(dev, "mei_cldev_recv failed. %zd\n", byte);
return byte;
}
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;
}
return 0;
}
/**
* mei_hdcp_close_session() - Close the Wired HDCP Tx session of ME FW per port.
* This also disables the authenticated state of the port.
* @dev: device corresponding to the mei_cl_device
* @data: Intel HW specific hdcp data
*
* Return: 0 on Success, <0 on Failure
*/
static int
mei_hdcp_close_session(struct device *dev, struct hdcp_port_data *data)
{
struct wired_cmd_close_session_in session_close_in = { { 0 } };
struct wired_cmd_close_session_out session_close_out = { { 0 } };
struct mei_cl_device *cldev;
ssize_t byte;
if (!dev || !data)
return -EINVAL;
cldev = to_mei_cl_device(dev);
session_close_in.header.api_version = HDCP_API_VERSION;
session_close_in.header.command_id = WIRED_CLOSE_SESSION;
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->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));
if (byte < 0) {
dev_dbg(dev, "mei_cldev_send failed. %zd\n", byte);
return byte;
}
byte = mei_cldev_recv(cldev, (u8 *)&session_close_out,
sizeof(session_close_out));
if (byte < 0) {
dev_dbg(dev, "mei_cldev_recv failed. %zd\n", byte);
return byte;
}
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;
}
return 0;
}
static const struct i915_hdcp_ops mei_hdcp_ops = {
.owner = THIS_MODULE,
.initiate_hdcp2_session = mei_hdcp_initiate_session,
.verify_receiver_cert_prepare_km =
mei_hdcp_verify_receiver_cert_prepare_km,
.verify_hprime = mei_hdcp_verify_hprime,
.store_pairing_info = mei_hdcp_store_pairing_info,
.initiate_locality_check = mei_hdcp_initiate_locality_check,
.verify_lprime = mei_hdcp_verify_lprime,
.get_session_key = mei_hdcp_get_session_key,
.repeater_check_flow_prepare_ack =
mei_hdcp_repeater_check_flow_prepare_ack,
.verify_mprime = mei_hdcp_verify_mprime,
.enable_hdcp_authentication = mei_hdcp_enable_authentication,
.close_hdcp_session = mei_hdcp_close_session,
};
static int mei_component_master_bind(struct device *dev)
{
struct mei_cl_device *cldev = to_mei_cl_device(dev);
struct i915_hdcp_arbiter *comp_arbiter = mei_cldev_get_drvdata(cldev);
int ret;
dev_dbg(dev, "%s\n", __func__);
comp_arbiter->ops = &mei_hdcp_ops;
comp_arbiter->hdcp_dev = dev;
ret = component_bind_all(dev, comp_arbiter);
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_hdcp_arbiter *comp_arbiter = mei_cldev_get_drvdata(cldev);
dev_dbg(dev, "%s\n", __func__);
component_unbind_all(dev, comp_arbiter);
}
static const struct component_master_ops mei_component_master_ops = {
.bind = mei_component_master_bind,
.unbind = mei_component_master_unbind,
};
/**
* mei_hdcp_component_match - compare function for matching mei hdcp.
*
* The function checks if the driver is i915, the subcomponent is HDCP
* and the grand parent of hdcp and the parent of i915 are the same
* PCH device.
*
* @dev: master device
* @subcomponent: subcomponent to match (I915_COMPONENT_HDCP)
* @data: compare data (mei hdcp device)
*
* Return:
* * 1 - if components match
* * 0 - otherwise
*/
static int mei_hdcp_component_match(struct device *dev, int subcomponent,
void *data)
{
struct device *base = 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_HDCP)
return 0;
base = base->parent;
if (!base)
return 0;
base = base->parent;
dev = dev->parent;
return (base && dev && dev == base);
}
static int mei_hdcp_probe(struct mei_cl_device *cldev,
const struct mei_cl_device_id *id)
{
struct i915_hdcp_arbiter *comp_arbiter;
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_arbiter = kzalloc_obj(*comp_arbiter);
if (!comp_arbiter) {
ret = -ENOMEM;
goto err_exit;
}
master_match = NULL;
component_match_add_typed(&cldev->dev, &master_match,
mei_hdcp_component_match, &cldev->dev);
if (IS_ERR_OR_NULL(master_match)) {
ret = -ENOMEM;
goto err_exit;
}
mei_cldev_set_drvdata(cldev, comp_arbiter);
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_arbiter);
mei_cldev_disable(cldev);
enable_err_exit:
return ret;
}
static void mei_hdcp_remove(struct mei_cl_device *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_arbiter);
mei_cldev_set_drvdata(cldev, NULL);
ret = mei_cldev_disable(cldev);
if (ret)
dev_warn(&cldev->dev, "mei_cldev_disable() failed\n");
}
#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 },
{ }
};
MODULE_DEVICE_TABLE(mei, mei_hdcp_tbl);
static struct mei_cl_driver mei_hdcp_driver = {
.id_table = mei_hdcp_tbl,
.name = KBUILD_MODNAME,
.probe = mei_hdcp_probe,
.remove = mei_hdcp_remove,
};
module_mei_cl_driver(mei_hdcp_driver);
MODULE_AUTHOR("Intel Corporation");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("MEI HDCP");
@@ -0,0 +1,14 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright © 2019 Intel Corporation
*
* Authors:
* Ramalingam C <ramalingam.c@intel.com>
*/
#ifndef __MEI_HDCP_H__
#define __MEI_HDCP_H__
#include <drm/display/drm_hdcp.h>
#endif /* __MEI_HDCP_H__ */
+236
View File
@@ -0,0 +1,236 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
/*
* Copyright (c) 2003-2022, Intel Corporation. All rights reserved.
* Intel Management Engine Interface (Intel MEI) Linux driver
*/
#ifndef _MEI_HW_MEI_REGS_H_
#define _MEI_HW_MEI_REGS_H_
#include <linux/bits.h>
/*
* MEI device IDs
*/
#define PCI_DEVICE_ID_INTEL_MEI_82946GZ 0x2974 /* 82946GZ/GL */
#define PCI_DEVICE_ID_INTEL_MEI_82G35 0x2984 /* 82G35 Express */
#define PCI_DEVICE_ID_INTEL_MEI_82Q965 0x2994 /* 82Q963/Q965 */
#define PCI_DEVICE_ID_INTEL_MEI_82G965 0x29A4 /* 82P965/G965 */
#define PCI_DEVICE_ID_INTEL_MEI_82GM965 0x2A04 /* Mobile PM965/GM965 */
#define PCI_DEVICE_ID_INTEL_MEI_82GME965 0x2A14 /* Mobile GME965/GLE960 */
#define PCI_DEVICE_ID_INTEL_MEI_ICH9_82Q35 0x29B4 /* 82Q35 Express */
#define PCI_DEVICE_ID_INTEL_MEI_ICH9_82G33 0x29C4 /* 82G33/G31/P35/P31 Express */
#define PCI_DEVICE_ID_INTEL_MEI_ICH9_82Q33 0x29D4 /* 82Q33 Express */
#define PCI_DEVICE_ID_INTEL_MEI_ICH9_82X38 0x29E4 /* 82X38/X48 Express */
#define PCI_DEVICE_ID_INTEL_MEI_ICH9_3200 0x29F4 /* 3200/3210 Server */
#define PCI_DEVICE_ID_INTEL_MEI_ICH9_6 0x28B4 /* Bearlake */
#define PCI_DEVICE_ID_INTEL_MEI_ICH9_7 0x28C4 /* Bearlake */
#define PCI_DEVICE_ID_INTEL_MEI_ICH9_8 0x28D4 /* Bearlake */
#define PCI_DEVICE_ID_INTEL_MEI_ICH9_9 0x28E4 /* Bearlake */
#define PCI_DEVICE_ID_INTEL_MEI_ICH9_10 0x28F4 /* Bearlake */
#define PCI_DEVICE_ID_INTEL_MEI_ICH9M_1 0x2A44 /* Cantiga */
#define PCI_DEVICE_ID_INTEL_MEI_ICH9M_2 0x2A54 /* Cantiga */
#define PCI_DEVICE_ID_INTEL_MEI_ICH9M_3 0x2A64 /* Cantiga */
#define PCI_DEVICE_ID_INTEL_MEI_ICH9M_4 0x2A74 /* Cantiga */
#define PCI_DEVICE_ID_INTEL_MEI_ICH10_1 0x2E04 /* Eaglelake */
#define PCI_DEVICE_ID_INTEL_MEI_ICH10_2 0x2E14 /* Eaglelake */
#define PCI_DEVICE_ID_INTEL_MEI_ICH10_3 0x2E24 /* Eaglelake */
#define PCI_DEVICE_ID_INTEL_MEI_ICH10_4 0x2E34 /* Eaglelake */
#define PCI_DEVICE_ID_INTEL_MEI_IBXPK_1 0x3B64 /* Calpella */
#define PCI_DEVICE_ID_INTEL_MEI_IBXPK_2 0x3B65 /* Calpella */
#define PCI_DEVICE_ID_INTEL_MEI_CPT_1 0x1C3A /* Couger Point */
#define PCI_DEVICE_ID_INTEL_MEI_PBG_1 0x1D3A /* C600/X79 Patsburg */
#define PCI_DEVICE_ID_INTEL_MEI_PPT_1 0x1E3A /* Panther Point */
#define PCI_DEVICE_ID_INTEL_MEI_PPT_2 0x1CBA /* Panther Point */
#define PCI_DEVICE_ID_INTEL_MEI_PPT_3 0x1DBA /* Panther Point */
#define PCI_DEVICE_ID_INTEL_MEI_LPT_H 0x8C3A /* Lynx Point H */
#define PCI_DEVICE_ID_INTEL_MEI_LPT_W 0x8D3A /* Lynx Point - Wellsburg */
#define PCI_DEVICE_ID_INTEL_MEI_LPT_LP 0x9C3A /* Lynx Point LP */
#define PCI_DEVICE_ID_INTEL_MEI_LPT_HR 0x8CBA /* Lynx Point H Refresh */
#define PCI_DEVICE_ID_INTEL_MEI_WPT_LP 0x9CBA /* Wildcat Point LP */
#define PCI_DEVICE_ID_INTEL_MEI_WPT_LP_2 0x9CBB /* Wildcat Point LP 2 */
#define PCI_DEVICE_ID_INTEL_MEI_SPT 0x9D3A /* Sunrise Point */
#define PCI_DEVICE_ID_INTEL_MEI_SPT_2 0x9D3B /* Sunrise Point 2 */
#define PCI_DEVICE_ID_INTEL_MEI_SPT_3 0x9D3E /* Sunrise Point 3 (iToutch) */
#define PCI_DEVICE_ID_INTEL_MEI_SPT_H 0xA13A /* Sunrise Point H */
#define PCI_DEVICE_ID_INTEL_MEI_SPT_H_2 0xA13B /* Sunrise Point H 2 */
#define PCI_DEVICE_ID_INTEL_MEI_LBG 0xA1BA /* Lewisburg (SPT) */
#define PCI_DEVICE_ID_INTEL_MEI_BXT_M 0x1A9A /* Broxton M */
#define PCI_DEVICE_ID_INTEL_MEI_APL_I 0x5A9A /* Apollo Lake I */
#define PCI_DEVICE_ID_INTEL_MEI_DNV_IE 0x19E5 /* Denverton IE */
#define PCI_DEVICE_ID_INTEL_MEI_GLK 0x319A /* Gemini Lake */
#define PCI_DEVICE_ID_INTEL_MEI_KBP 0xA2BA /* Kaby Point */
#define PCI_DEVICE_ID_INTEL_MEI_KBP_2 0xA2BB /* Kaby Point 2 */
#define PCI_DEVICE_ID_INTEL_MEI_KBP_3 0xA2BE /* Kaby Point 3 (iTouch) */
#define PCI_DEVICE_ID_INTEL_MEI_CNP_LP 0x9DE0 /* Cannon Point LP */
#define PCI_DEVICE_ID_INTEL_MEI_CNP_LP_3 0x9DE4 /* Cannon Point LP 3 (iTouch) */
#define PCI_DEVICE_ID_INTEL_MEI_CNP_H 0xA360 /* Cannon Point H */
#define PCI_DEVICE_ID_INTEL_MEI_CNP_H_3 0xA364 /* Cannon Point H 3 (iTouch) */
#define PCI_DEVICE_ID_INTEL_MEI_CMP_LP 0x02e0 /* Comet Point LP */
#define PCI_DEVICE_ID_INTEL_MEI_CMP_LP_3 0x02e4 /* Comet Point LP 3 (iTouch) */
#define PCI_DEVICE_ID_INTEL_MEI_CMP_V 0xA3BA /* Comet Point Lake V */
#define PCI_DEVICE_ID_INTEL_MEI_CMP_H 0x06e0 /* Comet Lake H */
#define PCI_DEVICE_ID_INTEL_MEI_CMP_H_3 0x06e4 /* Comet Lake H 3 (iTouch) */
#define PCI_DEVICE_ID_INTEL_MEI_CDF 0x18D3 /* Cedar Fork */
#define PCI_DEVICE_ID_INTEL_MEI_ICP_LP 0x34E0 /* Ice Lake Point LP */
#define PCI_DEVICE_ID_INTEL_MEI_ICP_N 0x38E0 /* Ice Lake Point N */
#define PCI_DEVICE_ID_INTEL_MEI_JSP_N 0x4DE0 /* Jasper Lake Point N */
#define PCI_DEVICE_ID_INTEL_MEI_TGP_LP 0xA0E0 /* Tiger Lake Point LP */
#define PCI_DEVICE_ID_INTEL_MEI_TGP_H 0x43E0 /* Tiger Lake Point H */
#define PCI_DEVICE_ID_INTEL_MEI_MCC 0x4B70 /* Mule Creek Canyon (EHL) */
#define PCI_DEVICE_ID_INTEL_MEI_MCC_4 0x4B75 /* Mule Creek Canyon 4 (EHL) */
#define PCI_DEVICE_ID_INTEL_MEI_EBG 0x1BE0 /* Emmitsburg WS */
#define PCI_DEVICE_ID_INTEL_MEI_ADP_S 0x7AE8 /* Alder Lake Point S */
#define PCI_DEVICE_ID_INTEL_MEI_ADP_LP 0x7A60 /* Alder Lake Point LP */
#define PCI_DEVICE_ID_INTEL_MEI_ADP_P 0x51E0 /* Alder Lake Point P */
#define PCI_DEVICE_ID_INTEL_MEI_ADP_N 0x54E0 /* Alder Lake Point N */
#define PCI_DEVICE_ID_INTEL_MEI_RPL_S 0x7A68 /* Raptor Lake Point S */
#define PCI_DEVICE_ID_INTEL_MEI_MTL_M 0x7E70 /* Meteor Lake Point M */
#define PCI_DEVICE_ID_INTEL_MEI_ARL_S 0x7F68 /* Arrow Lake Point S */
#define PCI_DEVICE_ID_INTEL_MEI_ARL_H 0x7770 /* Arrow Lake Point H */
#define PCI_DEVICE_ID_INTEL_MEI_LNL_M 0xA870 /* Lunar Lake Point M */
#define PCI_DEVICE_ID_INTEL_MEI_PTL_H 0xE370 /* Panther Lake H */
#define PCI_DEVICE_ID_INTEL_MEI_PTL_P 0xE470 /* Panther Lake P */
#define PCI_DEVICE_ID_INTEL_MEI_WCL_P 0x4D70 /* Wildcat Lake P */
#define PCI_DEVICE_ID_INTEL_MEI_NVL_S 0x6E68 /* Nova Lake Point S */
#define PCI_DEVICE_ID_INTEL_MEI_NVL_H 0xD370 /* Nova Lake Point H */
#define PCI_DEVICE_ID_INTEL_MEI_CRI 0x6766 /* Crescent Island */
/*
* MEI HW Section
*/
/* Host Firmware Status Registers in PCI Config Space */
#define PCI_CFG_HFS_1 0x40
# define PCI_CFG_HFS_1_D0I3_MSK 0x80000000
# define PCI_CFG_HFS_1_OPMODE_MSK 0xf0000 /* OP MODE Mask: SPS <= 4.0 */
# define PCI_CFG_HFS_1_OPMODE_SPS 0xf0000 /* SPS SKU : SPS <= 4.0 */
#define PCI_CFG_HFS_2 0x48
# define PCI_CFG_HFS_2_D3_BLOCK BIT(7)
# define PCI_CFG_HFS_2_PM_CMOFF_TO_CMX_ERROR 0x1000000 /* CMoff->CMx wake after an error */
# define PCI_CFG_HFS_2_PM_CM_RESET_ERROR 0x5000000 /* CME reset due to exception */
# define PCI_CFG_HFS_2_PM_EVENT_MASK 0xf000000
#define PCI_CFG_HFS_3 0x60
# define PCI_CFG_HFS_3_EXT_SKU_MSK GENMASK(3, 0) /* IOE detection bits */
# define PCI_CFG_HFS_3_EXT_SKU_IOE 0x00000001
# 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 */
/* H_CB_WW - Host Circular Buffer (CB) Write Window register */
#define H_CB_WW 0
/* H_CSR - Host Control Status register */
#define H_CSR 4
/* ME_CB_RW - ME Circular Buffer Read Window register (read only) */
#define ME_CB_RW 8
/* ME_CSR_HA - ME Control Status Host Access register (read only) */
#define ME_CSR_HA 0xC
/* H_HGC_CSR - PGI register */
#define H_HPG_CSR 0x10
/* 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
/* Host Circular Buffer Write Pointer */
#define H_CBWP 0x00FF0000
/* Host Circular Buffer Read Pointer */
#define H_CBRP 0x0000FF00
/* Host Reset */
#define H_RST 0x00000010
/* Host Ready */
#define H_RDY 0x00000008
/* Host Interrupt Generate */
#define H_IG 0x00000004
/* Host Interrupt Status */
#define H_IS 0x00000002
/* Host Interrupt Enable */
#define H_IE 0x00000001
/* Host D0I3 Interrupt Enable */
#define H_D0I3C_IE 0x00000020
/* Host D0I3 Interrupt Status */
#define H_D0I3C_IS 0x00000040
/* H_CSR masks */
#define H_CSR_IE_MASK (H_IE | H_D0I3C_IE)
#define H_CSR_IS_MASK (H_IS | H_D0I3C_IS)
/* register bits of ME_CSR_HA (ME Control Status Host Access register) */
/* ME CB (Circular Buffer) Depth HRA (Host Read Access) - host read only
access to ME_CBD */
#define ME_CBD_HRA 0xFF000000
/* ME CB Write Pointer HRA - host read only access to ME_CBWP */
#define ME_CBWP_HRA 0x00FF0000
/* ME CB Read Pointer HRA - host read only access to ME_CBRP */
#define ME_CBRP_HRA 0x0000FF00
/* ME Power Gate Isolation Capability HRA - host ready only access */
#define ME_PGIC_HRA 0x00000040
/* ME Reset HRA - host read only access to ME_RST */
#define ME_RST_HRA 0x00000010
/* ME Ready HRA - host read only access to ME_RDY */
#define ME_RDY_HRA 0x00000008
/* ME Interrupt Generate HRA - host read only access to ME_IG */
#define ME_IG_HRA 0x00000004
/* ME Interrupt Status HRA - host read only access to ME_IS */
#define ME_IS_HRA 0x00000002
/* ME Interrupt Enable HRA - host read only access to ME_IE */
#define ME_IE_HRA 0x00000001
/* TRC control shadow register */
#define ME_TRC 0x00000030
/* H_HPG_CSR register bits */
#define H_HPG_CSR_PGIHEXR 0x00000001
#define H_HPG_CSR_PGI 0x00000002
/* H_D0I3C register bits */
#define H_D0I3C_CIP 0x00000001
#define H_D0I3C_IR 0x00000002
#define H_D0I3C_I3 0x00000004
#define H_D0I3C_RR 0x00000008
#endif /* _MEI_HW_MEI_REGS_H_ */
File diff suppressed because it is too large Load Diff
+146
View File
@@ -0,0 +1,146 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2012-2022, Intel Corporation. All rights reserved.
* Intel Management Engine Interface (Intel MEI) Linux driver
*/
#ifndef _MEI_INTERFACE_H_
#define _MEI_INTERFACE_H_
#include <linux/irqreturn.h>
#include <linux/pci.h>
#include <linux/mei.h>
#include "mei_dev.h"
#include "client.h"
/*
* mei_cfg - mei device configuration
*
* @fw_status: FW status
* @quirk_probe: device exclusion quirk
* @get_kind: MEI head kind helper
* @dma_size: device DMA buffers size
* @fw_ver_supported: is fw version retrievable from FW
* @hw_trc_supported: does the hw support trc register
*/
struct mei_cfg {
const struct mei_fw_status fw_status;
bool (*quirk_probe)(const struct pci_dev *pdev);
enum mei_dev_kind (*get_kind)(const struct device *parent);
size_t dma_size[DMA_DSCR_NUM];
u32 fw_ver_supported:1;
u32 hw_trc_supported:1;
};
#define MEI_ME_RPM_TIMEOUT 500 /* ms */
/**
* struct mei_me_hw - me hw specific data
*
* @cfg: per device generation config and ops
* @mem_addr: io memory address
* @irq: irq number
* @pg_state: power gating state
* @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;
void __iomem *mem_addr;
int irq;
enum mei_pg_state pg_state;
bool d0i3_supported;
u8 hbuf_depth;
int (*read_fws)(const struct mei_device *dev, int where, const char *name, 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.
*
* Note: has to be synchronized with mei_cfg_list[]
*
* @MEI_ME_UNDEF_CFG: Lower sentinel.
* @MEI_ME_ICH_CFG: I/O Controller Hub legacy devices.
* @MEI_ME_ICH10_CFG: I/O Controller Hub platforms Gen10
* @MEI_ME_PCH6_CFG: Platform Controller Hub platforms (Gen6).
* @MEI_ME_PCH7_CFG: Platform Controller Hub platforms (Gen7).
* @MEI_ME_PCH_CPT_PBG_CFG:Platform Controller Hub workstations
* with quirk for Node Manager exclusion.
* @MEI_ME_PCH8_CFG: Platform Controller Hub Gen8 and newer
* client platforms.
* @MEI_ME_PCH8_ITOUCH_CFG:Platform Controller Hub Gen8 and newer
* client platforms (iTouch).
* @MEI_ME_PCH8_SPS_4_CFG: Platform Controller Hub Gen8 and newer
* servers platforms with quirk for
* SPS firmware exclusion.
* @MEI_ME_PCH12_CFG: Platform Controller Hub Gen12 and newer
* @MEI_ME_PCH12_SPS_4_CFG:Platform Controller Hub Gen12 up to 4.0
* servers platforms with quirk for
* SPS firmware exclusion.
* @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_CSC_CFG: Chassis System Controller Firmware Interface
* @MEI_ME_PCH22_IOE_CFG: Platform Controller Hub Gen22 and newer with IOE detection
* @MEI_ME_NUM_CFG: Upper Sentinel.
*/
enum mei_cfg_idx {
MEI_ME_UNDEF_CFG,
MEI_ME_ICH_CFG,
MEI_ME_ICH10_CFG,
MEI_ME_PCH6_CFG,
MEI_ME_PCH7_CFG,
MEI_ME_PCH_CPT_PBG_CFG,
MEI_ME_PCH8_CFG,
MEI_ME_PCH8_ITOUCH_CFG,
MEI_ME_PCH8_SPS_4_CFG,
MEI_ME_PCH12_CFG,
MEI_ME_PCH12_SPS_4_CFG,
MEI_ME_PCH12_SPS_CFG,
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_CSC_CFG,
MEI_ME_PCH22_IOE_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, 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_ */
+239
View File
@@ -0,0 +1,239 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
/*
* Copyright (c) 2013-2014, Intel Corporation. All rights reserved.
* Intel Management Engine Interface (Intel MEI) Linux driver
*/
#ifndef _MEI_HW_TXE_REGS_H_
#define _MEI_HW_TXE_REGS_H_
#include "hw.h"
#define SEC_ALIVENESS_TIMER_TIMEOUT (5 * MSEC_PER_SEC)
#define SEC_ALIVENESS_WAIT_TIMEOUT (1 * MSEC_PER_SEC)
#define SEC_RESET_WAIT_TIMEOUT (1 * MSEC_PER_SEC)
#define SEC_READY_WAIT_TIMEOUT (5 * MSEC_PER_SEC)
#define START_MESSAGE_RESPONSE_WAIT_TIMEOUT (5 * MSEC_PER_SEC)
#define RESET_CANCEL_WAIT_TIMEOUT (1 * MSEC_PER_SEC)
enum {
SEC_BAR,
BRIDGE_BAR,
NUM_OF_MEM_BARS
};
/* SeC FW Status Register
*
* FW uses this register in order to report its status to host.
* This register resides in PCI-E config space.
*/
#define PCI_CFG_TXE_FW_STS0 0x40
# define PCI_CFG_TXE_FW_STS0_WRK_ST_MSK 0x0000000F
# define PCI_CFG_TXE_FW_STS0_OP_ST_MSK 0x000001C0
# define PCI_CFG_TXE_FW_STS0_FW_INIT_CMPLT 0x00000200
# define PCI_CFG_TXE_FW_STS0_ERR_CODE_MSK 0x0000F000
# define PCI_CFG_TXE_FW_STS0_OP_MODE_MSK 0x000F0000
# define PCI_CFG_TXE_FW_STS0_RST_CNT_MSK 0x00F00000
#define PCI_CFG_TXE_FW_STS1 0x48
#define IPC_BASE_ADDR 0x80400 /* SeC IPC Base Address */
/* IPC Input Doorbell Register */
#define SEC_IPC_INPUT_DOORBELL_REG (0x0000 + IPC_BASE_ADDR)
/* IPC Input Status Register
* This register indicates whether or not processing of
* the most recent command has been completed by the SEC
* New commands and payloads should not be written by the Host
* until this indicates that the previous command has been processed.
*/
#define SEC_IPC_INPUT_STATUS_REG (0x0008 + IPC_BASE_ADDR)
# define SEC_IPC_INPUT_STATUS_RDY BIT(0)
/* IPC Host Interrupt Status Register */
#define SEC_IPC_HOST_INT_STATUS_REG (0x0010 + IPC_BASE_ADDR)
#define SEC_IPC_HOST_INT_STATUS_OUT_DB BIT(0)
#define SEC_IPC_HOST_INT_STATUS_IN_RDY BIT(1)
#define SEC_IPC_HOST_INT_STATUS_HDCP_M0_RCVD BIT(5)
#define SEC_IPC_HOST_INT_STATUS_ILL_MEM_ACCESS BIT(17)
#define SEC_IPC_HOST_INT_STATUS_AES_HKEY_ERR BIT(18)
#define SEC_IPC_HOST_INT_STATUS_DES_HKEY_ERR BIT(19)
#define SEC_IPC_HOST_INT_STATUS_TMRMTB_OVERFLOW BIT(21)
/* Convenient mask for pending interrupts */
#define SEC_IPC_HOST_INT_STATUS_PENDING \
(SEC_IPC_HOST_INT_STATUS_OUT_DB| \
SEC_IPC_HOST_INT_STATUS_IN_RDY)
/* IPC Host Interrupt Mask Register */
#define SEC_IPC_HOST_INT_MASK_REG (0x0014 + IPC_BASE_ADDR)
# define SEC_IPC_HOST_INT_MASK_OUT_DB BIT(0) /* Output Doorbell Int Mask */
# define SEC_IPC_HOST_INT_MASK_IN_RDY BIT(1) /* Input Ready Int Mask */
/* IPC Input Payload RAM */
#define SEC_IPC_INPUT_PAYLOAD_REG (0x0100 + IPC_BASE_ADDR)
/* IPC Shared Payload RAM */
#define IPC_SHARED_PAYLOAD_REG (0x0200 + IPC_BASE_ADDR)
/* SeC Address Translation Table Entry 2 - Ctrl
*
* This register resides also in SeC's PCI-E Memory space.
*/
#define SATT2_CTRL_REG 0x1040
# define SATT2_CTRL_VALID_MSK BIT(0)
# define SATT2_CTRL_BR_BASE_ADDR_REG_SHIFT 8
# define SATT2_CTRL_BRIDGE_HOST_EN_MSK BIT(12)
/* SATT Table Entry 2 SAP Base Address Register */
#define SATT2_SAP_BA_REG 0x1044
/* SATT Table Entry 2 SAP Size Register. */
#define SATT2_SAP_SIZE_REG 0x1048
/* SATT Table Entry 2 SAP Bridge Address - LSB Register */
#define SATT2_BRG_BA_LSB_REG 0x104C
/* Host High-level Interrupt Status Register */
#define HHISR_REG 0x2020
/* Host High-level Interrupt Enable Register
*
* Resides in PCI memory space. This is the top hierarchy for
* interrupts from SeC to host, aggregating both interrupts that
* arrive through HICR registers as well as interrupts
* that arrive via IPC.
*/
#define HHIER_REG 0x2024
#define IPC_HHIER_SEC BIT(0)
#define IPC_HHIER_BRIDGE BIT(1)
#define IPC_HHIER_MSK (IPC_HHIER_SEC | IPC_HHIER_BRIDGE)
/* Host High-level Interrupt Mask Register.
*
* Resides in PCI memory space.
* This is the top hierarchy for masking interrupts from SeC to host.
*/
#define HHIMR_REG 0x2028
#define IPC_HHIMR_SEC BIT(0)
#define IPC_HHIMR_BRIDGE BIT(1)
/* Host High-level IRQ Status Register */
#define HHIRQSR_REG 0x202C
/* Host Interrupt Cause Register 0 - SeC IPC Readiness
*
* This register is both an ICR to Host from PCI Memory Space
* and it is also exposed in the SeC memory space.
* This register is used by SeC's IPC driver in order
* to synchronize with host about IPC interface state.
*/
#define HICR_SEC_IPC_READINESS_REG 0x2040
#define HICR_SEC_IPC_READINESS_HOST_RDY BIT(0)
#define HICR_SEC_IPC_READINESS_SEC_RDY BIT(1)
#define HICR_SEC_IPC_READINESS_SYS_RDY \
(HICR_SEC_IPC_READINESS_HOST_RDY | \
HICR_SEC_IPC_READINESS_SEC_RDY)
#define HICR_SEC_IPC_READINESS_RDY_CLR BIT(2)
/* Host Interrupt Cause Register 1 - Aliveness Response */
/* This register is both an ICR to Host from PCI Memory Space
* and it is also exposed in the SeC memory space.
* The register may be used by SeC to ACK a host request for aliveness.
*/
#define HICR_HOST_ALIVENESS_RESP_REG 0x2044
#define HICR_HOST_ALIVENESS_RESP_ACK BIT(0)
/* Host Interrupt Cause Register 2 - SeC IPC Output Doorbell */
#define HICR_SEC_IPC_OUTPUT_DOORBELL_REG 0x2048
/* Host Interrupt Status Register.
*
* Resides in PCI memory space.
* This is the main register involved in generating interrupts
* from SeC to host via HICRs.
* The interrupt generation rules are as follows:
* An interrupt will be generated whenever for any i,
* there is a transition from a state where at least one of
* the following conditions did not hold, to a state where
* ALL the following conditions hold:
* A) HISR.INT[i]_STS == 1.
* B) HIER.INT[i]_EN == 1.
*/
#define HISR_REG 0x2060
#define HISR_INT_0_STS BIT(0)
#define HISR_INT_1_STS BIT(1)
#define HISR_INT_2_STS BIT(2)
#define HISR_INT_3_STS BIT(3)
#define HISR_INT_4_STS BIT(4)
#define HISR_INT_5_STS BIT(5)
#define HISR_INT_6_STS BIT(6)
#define HISR_INT_7_STS BIT(7)
#define HISR_INT_STS_MSK \
(HISR_INT_0_STS | HISR_INT_1_STS | HISR_INT_2_STS)
/* Host Interrupt Enable Register. Resides in PCI memory space. */
#define HIER_REG 0x2064
#define HIER_INT_0_EN BIT(0)
#define HIER_INT_1_EN BIT(1)
#define HIER_INT_2_EN BIT(2)
#define HIER_INT_3_EN BIT(3)
#define HIER_INT_4_EN BIT(4)
#define HIER_INT_5_EN BIT(5)
#define HIER_INT_6_EN BIT(6)
#define HIER_INT_7_EN BIT(7)
#define HIER_INT_EN_MSK \
(HIER_INT_0_EN | HIER_INT_1_EN | HIER_INT_2_EN)
/* SEC Memory Space IPC output payload.
*
* This register is part of the output payload which SEC provides to host.
*/
#define BRIDGE_IPC_OUTPUT_PAYLOAD_REG 0x20C0
/* SeC Interrupt Cause Register - Host Aliveness Request
* This register is both an ICR to SeC and it is also exposed
* in the host-visible PCI memory space.
* The register is used by host to request SeC aliveness.
*/
#define SICR_HOST_ALIVENESS_REQ_REG 0x214C
#define SICR_HOST_ALIVENESS_REQ_REQUESTED BIT(0)
/* SeC Interrupt Cause Register - Host IPC Readiness
*
* This register is both an ICR to SeC and it is also exposed
* in the host-visible PCI memory space.
* This register is used by the host's SeC driver uses in order
* to synchronize with SeC about IPC interface state.
*/
#define SICR_HOST_IPC_READINESS_REQ_REG 0x2150
#define SICR_HOST_IPC_READINESS_HOST_RDY BIT(0)
#define SICR_HOST_IPC_READINESS_SEC_RDY BIT(1)
#define SICR_HOST_IPC_READINESS_SYS_RDY \
(SICR_HOST_IPC_READINESS_HOST_RDY | \
SICR_HOST_IPC_READINESS_SEC_RDY)
#define SICR_HOST_IPC_READINESS_RDY_CLR BIT(2)
/* SeC Interrupt Cause Register - SeC IPC Output Status
*
* This register indicates whether or not processing of the most recent
* command has been completed by the Host.
* New commands and payloads should not be written by SeC until this
* register indicates that the previous command has been processed.
*/
#define SICR_SEC_IPC_OUTPUT_STATUS_REG 0x2154
# define SEC_IPC_OUTPUT_STATUS_RDY BIT(0)
/* MEI IPC Message payload size 64 bytes */
#define PAYLOAD_SIZE 64
/* MAX size for SATT range 32MB */
#define SATT_RANGE_MAX (32 << 20)
#endif /* _MEI_HW_TXE_REGS_H_ */
File diff suppressed because it is too large Load Diff
+63
View File
@@ -0,0 +1,63 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2013-2016, Intel Corporation. All rights reserved.
* Intel Management Engine Interface (Intel MEI) Linux driver
*/
#ifndef _MEI_HW_TXE_H_
#define _MEI_HW_TXE_H_
#include <linux/irqreturn.h>
#include "hw.h"
#include "hw-txe-regs.h"
#define MEI_TXI_RPM_TIMEOUT 500 /* ms */
/* Flatten Hierarchy interrupt cause */
#define TXE_INTR_READINESS_BIT 0 /* HISR_INT_0_STS */
#define TXE_INTR_READINESS HISR_INT_0_STS
#define TXE_INTR_ALIVENESS_BIT 1 /* HISR_INT_1_STS */
#define TXE_INTR_ALIVENESS HISR_INT_1_STS
#define TXE_INTR_OUT_DB_BIT 2 /* HISR_INT_2_STS */
#define TXE_INTR_OUT_DB HISR_INT_2_STS
#define TXE_INTR_IN_READY_BIT 8 /* beyond HISR */
#define TXE_INTR_IN_READY BIT(8)
/**
* struct mei_txe_hw - txe hardware specifics
*
* @mem_addr: SeC and BRIDGE bars
* @aliveness: aliveness (power gating) state of the hardware
* @readiness: readiness state of the hardware
* @slots: number of empty slots
* @wait_aliveness_resp: aliveness wait queue
* @intr_cause: translated interrupt cause
*/
struct mei_txe_hw {
void __iomem * const *mem_addr;
u32 aliveness;
u32 readiness;
u32 slots;
wait_queue_head_t wait_aliveness_resp;
unsigned long intr_cause;
};
#define to_txe_hw(dev) (struct mei_txe_hw *)((dev)->hw)
static inline struct mei_device *hw_txe_to_mei(struct mei_txe_hw *hw)
{
return container_of((void *)hw, struct mei_device, hw);
}
struct mei_device *mei_txe_dev_init(struct pci_dev *pdev);
irqreturn_t mei_txe_irq_quick_handler(int irq, void *dev_id);
irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id);
int mei_txe_aliveness_set_sync(struct mei_device *dev, u32 req);
#endif /* _MEI_HW_TXE_H_ */
+836
View File
@@ -0,0 +1,836 @@
/* 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_HW_TYPES_H_
#define _MEI_HW_TYPES_H_
#include <linux/mei.h>
/*
* Timeouts in Seconds
*/
#define MEI_HW_READY_TIMEOUT 2 /* Timeout on ready message */
#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
*/
#define HBM_MINOR_VERSION 2
#define HBM_MAJOR_VERSION 2
/*
* MEI version with PGI support
*/
#define HBM_MINOR_VERSION_PGI 1
#define HBM_MAJOR_VERSION_PGI 1
/*
* MEI version with Dynamic clients support
*/
#define HBM_MINOR_VERSION_DC 0
#define HBM_MAJOR_VERSION_DC 2
/*
* MEI version with immediate reply to enum request support
*/
#define HBM_MINOR_VERSION_IE 0
#define HBM_MAJOR_VERSION_IE 2
/*
* MEI version with disconnect on connection timeout support
*/
#define HBM_MINOR_VERSION_DOT 0
#define HBM_MAJOR_VERSION_DOT 2
/*
* MEI version with notification support
*/
#define HBM_MINOR_VERSION_EV 0
#define HBM_MAJOR_VERSION_EV 2
/*
* MEI version with fixed address client support
*/
#define HBM_MINOR_VERSION_FA 0
#define HBM_MAJOR_VERSION_FA 2
/*
* MEI version with OS ver message support
*/
#define HBM_MINOR_VERSION_OS 0
#define HBM_MAJOR_VERSION_OS 2
/*
* MEI version with dma ring support
*/
#define HBM_MINOR_VERSION_DR 1
#define HBM_MAJOR_VERSION_DR 2
/*
* MEI version with vm tag support
*/
#define HBM_MINOR_VERSION_VT 2
#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
#define HBM_MAJOR_VERSION_CAP 2
/*
* MEI version with client DMA support
*/
#define HBM_MINOR_VERSION_CD 2
#define HBM_MAJOR_VERSION_CD 2
/* Host bus message command opcode */
#define MEI_HBM_CMD_OP_MSK 0x7f
/* Host bus message command RESPONSE */
#define MEI_HBM_CMD_RES_MSK 0x80
/*
* MEI Bus Message Command IDs
*/
#define HOST_START_REQ_CMD 0x01
#define HOST_START_RES_CMD 0x81
#define HOST_STOP_REQ_CMD 0x02
#define HOST_STOP_RES_CMD 0x82
#define ME_STOP_REQ_CMD 0x03
#define HOST_ENUM_REQ_CMD 0x04
#define HOST_ENUM_RES_CMD 0x84
#define HOST_CLIENT_PROPERTIES_REQ_CMD 0x05
#define HOST_CLIENT_PROPERTIES_RES_CMD 0x85
#define CLIENT_CONNECT_REQ_CMD 0x06
#define CLIENT_CONNECT_RES_CMD 0x86
#define CLIENT_DISCONNECT_REQ_CMD 0x07
#define CLIENT_DISCONNECT_RES_CMD 0x87
#define MEI_FLOW_CONTROL_CMD 0x08
#define MEI_PG_ISOLATION_ENTRY_REQ_CMD 0x0a
#define MEI_PG_ISOLATION_ENTRY_RES_CMD 0x8a
#define MEI_PG_ISOLATION_EXIT_REQ_CMD 0x0b
#define MEI_PG_ISOLATION_EXIT_RES_CMD 0x8b
#define MEI_HBM_ADD_CLIENT_REQ_CMD 0x0f
#define MEI_HBM_ADD_CLIENT_RES_CMD 0x8f
#define MEI_HBM_NOTIFY_REQ_CMD 0x10
#define MEI_HBM_NOTIFY_RES_CMD 0x90
#define MEI_HBM_NOTIFICATION_CMD 0x11
#define MEI_HBM_DMA_SETUP_REQ_CMD 0x12
#define MEI_HBM_DMA_SETUP_RES_CMD 0x92
#define MEI_HBM_CAPABILITIES_REQ_CMD 0x13
#define MEI_HBM_CAPABILITIES_RES_CMD 0x93
#define MEI_HBM_CLIENT_DMA_MAP_REQ_CMD 0x14
#define MEI_HBM_CLIENT_DMA_MAP_RES_CMD 0x94
#define MEI_HBM_CLIENT_DMA_UNMAP_REQ_CMD 0x15
#define MEI_HBM_CLIENT_DMA_UNMAP_RES_CMD 0x95
/*
* MEI Stop Reason
* used by hbm_host_stop_request.reason
*/
enum mei_stop_reason_types {
DRIVER_STOP_REQUEST = 0x00,
DEVICE_D1_ENTRY = 0x01,
DEVICE_D2_ENTRY = 0x02,
DEVICE_D3_ENTRY = 0x03,
SYSTEM_S1_ENTRY = 0x04,
SYSTEM_S2_ENTRY = 0x05,
SYSTEM_S3_ENTRY = 0x06,
SYSTEM_S4_ENTRY = 0x07,
SYSTEM_S5_ENTRY = 0x08
};
/**
* enum mei_hbm_status - mei host bus messages return values
*
* @MEI_HBMS_SUCCESS : status success
* @MEI_HBMS_CLIENT_NOT_FOUND : client not found
* @MEI_HBMS_ALREADY_EXISTS : connection already established
* @MEI_HBMS_REJECTED : connection is rejected
* @MEI_HBMS_INVALID_PARAMETER : invalid parameter
* @MEI_HBMS_NOT_ALLOWED : operation not allowed
* @MEI_HBMS_ALREADY_STARTED : system is already started
* @MEI_HBMS_NOT_STARTED : system not started
*
* @MEI_HBMS_MAX : sentinel
*/
enum mei_hbm_status {
MEI_HBMS_SUCCESS = 0,
MEI_HBMS_CLIENT_NOT_FOUND = 1,
MEI_HBMS_ALREADY_EXISTS = 2,
MEI_HBMS_REJECTED = 3,
MEI_HBMS_INVALID_PARAMETER = 4,
MEI_HBMS_NOT_ALLOWED = 5,
MEI_HBMS_ALREADY_STARTED = 6,
MEI_HBMS_NOT_STARTED = 7,
MEI_HBMS_MAX
};
/*
* Client Connect Status
* used by hbm_client_connect_response.status
*/
enum mei_cl_connect_status {
MEI_CL_CONN_SUCCESS = MEI_HBMS_SUCCESS,
MEI_CL_CONN_NOT_FOUND = MEI_HBMS_CLIENT_NOT_FOUND,
MEI_CL_CONN_ALREADY_STARTED = MEI_HBMS_ALREADY_EXISTS,
MEI_CL_CONN_OUT_OF_RESOURCES = MEI_HBMS_REJECTED,
MEI_CL_CONN_MESSAGE_SMALL = MEI_HBMS_INVALID_PARAMETER,
MEI_CL_CONN_NOT_ALLOWED = MEI_HBMS_NOT_ALLOWED,
};
/*
* Client Disconnect Status
*/
enum mei_cl_disconnect_status {
MEI_CL_DISCONN_SUCCESS = MEI_HBMS_SUCCESS
};
/**
* enum mei_ext_hdr_type - extended header type used in
* extended header TLV
*
* @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
*/
struct mei_ext_hdr {
u8 type;
u8 length;
} __packed;
/**
* struct mei_ext_meta_hdr - extend header meta data
* @count: number of headers
* @size: total size of the extended header list excluding meta header
* @reserved: reserved
* @hdrs: extended headers TLV list
*/
struct mei_ext_meta_hdr {
u8 count;
u8 size;
u8 reserved[2];
u8 hdrs[];
} __packed;
/**
* struct mei_ext_hdr_vtag - extend header for vtag
*
* @hdr: standard extend header
* @vtag: virtual tag
* @reserved: reserved
*/
struct mei_ext_hdr_vtag {
struct mei_ext_hdr hdr;
u8 vtag;
u8 reserved;
} __packed;
/*
* Extended header iterator functions
*/
/**
* mei_ext_begin - extended header iterator begin
*
* @meta: meta header of the extended header list
*
* Return: The first extended header
*/
static inline struct mei_ext_hdr *mei_ext_begin(struct mei_ext_meta_hdr *meta)
{
return (struct mei_ext_hdr *)meta->hdrs;
}
/**
* mei_ext_last - check if the ext is the last one in the TLV list
*
* @meta: meta header of the extended header list
* @ext: a meta header on the list
*
* Return: true if ext is the last header on the list
*/
static inline bool mei_ext_last(struct mei_ext_meta_hdr *meta,
struct mei_ext_hdr *ext)
{
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
*
* @ext: current extend header
*
* Context: The function does not check for the overflows,
* one should call mei_ext_last before.
*
* Return: The following extend header after @ext
*/
static inline struct mei_ext_hdr *mei_ext_next(struct mei_ext_hdr *ext)
{
return (struct mei_ext_hdr *)((u8 *)ext + (ext->length * 4));
}
/**
* 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
* @host_addr: host address
* @length: message length
* @reserved: reserved
* @extended: message has extended header
* @dma_ring: message is on dma ring
* @internal: message is internal
* @msg_complete: last packet of the message
* @extension: extension of the header
*/
struct mei_msg_hdr {
u32 me_addr:8;
u32 host_addr:8;
u32 length:9;
u32 reserved:3;
u32 extended:1;
u32 dma_ring:1;
u32 internal:1;
u32 msg_complete:1;
u32 extension[];
} __packed;
/* The length is up to 9 bits */
#define MEI_MSG_MAX_LEN_MASK GENMASK(9, 0)
struct mei_bus_message {
u8 hbm_cmd;
u8 data[];
} __packed;
/**
* struct mei_hbm_cl_cmd - client specific host bus command
* CONNECT, DISCONNECT, and FlOW CONTROL
*
* @hbm_cmd: bus message command header
* @me_addr: address of the client in ME
* @host_addr: address of the client in the driver
* @data: generic data
*/
struct mei_hbm_cl_cmd {
u8 hbm_cmd;
u8 me_addr;
u8 host_addr;
u8 data;
};
struct hbm_version {
u8 minor_version;
u8 major_version;
} __packed;
struct hbm_host_version_request {
u8 hbm_cmd;
u8 reserved;
struct hbm_version host_version;
} __packed;
struct hbm_host_version_response {
u8 hbm_cmd;
u8 host_version_supported;
struct hbm_version me_max_version;
} __packed;
struct hbm_host_stop_request {
u8 hbm_cmd;
u8 reason;
u8 reserved[2];
} __packed;
struct hbm_host_stop_response {
u8 hbm_cmd;
u8 reserved[3];
} __packed;
struct hbm_me_stop_request {
u8 hbm_cmd;
u8 reason;
u8 reserved[2];
} __packed;
/**
* enum hbm_host_enum_flags - enumeration request flags (HBM version >= 2.0)
*
* @MEI_HBM_ENUM_F_ALLOW_ADD: allow dynamic clients add
* @MEI_HBM_ENUM_F_IMMEDIATE_ENUM: allow FW to send answer immediately
*/
enum hbm_host_enum_flags {
MEI_HBM_ENUM_F_ALLOW_ADD = BIT(0),
MEI_HBM_ENUM_F_IMMEDIATE_ENUM = BIT(1),
};
/**
* struct hbm_host_enum_request - enumeration request from host to fw
*
* @hbm_cmd : bus message command header
* @flags : request flags
* @reserved: reserved
*/
struct hbm_host_enum_request {
u8 hbm_cmd;
u8 flags;
u8 reserved[2];
} __packed;
struct hbm_host_enum_response {
u8 hbm_cmd;
u8 reserved[3];
u8 valid_addresses[32];
} __packed;
/**
* struct mei_client_properties - mei client properties
*
* @protocol_name: guid of the client
* @protocol_version: client protocol version
* @max_number_of_connections: number of possible connections.
* @fixed_address: fixed me address (0 if the client is dynamic)
* @single_recv_buf: 1 if all connections share a single receive buffer.
* @vt_supported: the client support vtag
* @reserved: reserved
* @max_msg_length: MTU of the client
*/
struct mei_client_properties {
uuid_le protocol_name;
u8 protocol_version;
u8 max_number_of_connections;
u8 fixed_address;
u8 single_recv_buf:1;
u8 vt_supported:1;
u8 reserved:6;
u32 max_msg_length;
} __packed;
struct hbm_props_request {
u8 hbm_cmd;
u8 me_addr;
u8 reserved[2];
} __packed;
struct hbm_props_response {
u8 hbm_cmd;
u8 me_addr;
u8 status;
u8 reserved;
struct mei_client_properties client_properties;
} __packed;
/**
* struct hbm_add_client_request - request to add a client
* might be sent by fw after enumeration has already completed
*
* @hbm_cmd: bus message command header
* @me_addr: address of the client in ME
* @reserved: reserved
* @client_properties: client properties
*/
struct hbm_add_client_request {
u8 hbm_cmd;
u8 me_addr;
u8 reserved[2];
struct mei_client_properties client_properties;
} __packed;
/**
* struct hbm_add_client_response - response to add a client
* sent by the host to report client addition status to fw
*
* @hbm_cmd: bus message command header
* @me_addr: address of the client in ME
* @status: if HBMS_SUCCESS then the client can now accept connections.
* @reserved: reserved
*/
struct hbm_add_client_response {
u8 hbm_cmd;
u8 me_addr;
u8 status;
u8 reserved;
} __packed;
/**
* struct hbm_power_gate - power gate request/response
*
* @hbm_cmd: bus message command header
* @reserved: reserved
*/
struct hbm_power_gate {
u8 hbm_cmd;
u8 reserved[3];
} __packed;
/**
* struct hbm_client_connect_request - connect/disconnect request
*
* @hbm_cmd: bus message command header
* @me_addr: address of the client in ME
* @host_addr: address of the client in the driver
* @reserved: reserved
*/
struct hbm_client_connect_request {
u8 hbm_cmd;
u8 me_addr;
u8 host_addr;
u8 reserved;
} __packed;
/**
* struct hbm_client_connect_response - connect/disconnect response
*
* @hbm_cmd: bus message command header
* @me_addr: address of the client in ME
* @host_addr: address of the client in the driver
* @status: status of the request
*/
struct hbm_client_connect_response {
u8 hbm_cmd;
u8 me_addr;
u8 host_addr;
u8 status;
} __packed;
#define MEI_FC_MESSAGE_RESERVED_LENGTH 5
struct hbm_flow_control {
u8 hbm_cmd;
u8 me_addr;
u8 host_addr;
u8 reserved[MEI_FC_MESSAGE_RESERVED_LENGTH];
} __packed;
#define MEI_HBM_NOTIFICATION_START 1
#define MEI_HBM_NOTIFICATION_STOP 0
/**
* struct hbm_notification_request - start/stop notification request
*
* @hbm_cmd: bus message command header
* @me_addr: address of the client in ME
* @host_addr: address of the client in the driver
* @start: start = 1 or stop = 0 asynchronous notifications
*/
struct hbm_notification_request {
u8 hbm_cmd;
u8 me_addr;
u8 host_addr;
u8 start;
} __packed;
/**
* struct hbm_notification_response - start/stop notification response
*
* @hbm_cmd: bus message command header
* @me_addr: address of the client in ME
* @host_addr: - address of the client in the driver
* @status: (mei_hbm_status) response status for the request
* - MEI_HBMS_SUCCESS: successful stop/start
* - MEI_HBMS_CLIENT_NOT_FOUND: if the connection could not be found.
* - MEI_HBMS_ALREADY_STARTED: for start requests for a previously
* started notification.
* - MEI_HBMS_NOT_STARTED: for stop request for a connected client for whom
* asynchronous notifications are currently disabled.
*
* @start: start = 1 or stop = 0 asynchronous notifications
* @reserved: reserved
*/
struct hbm_notification_response {
u8 hbm_cmd;
u8 me_addr;
u8 host_addr;
u8 status;
u8 start;
u8 reserved[3];
} __packed;
/**
* struct hbm_notification - notification event
*
* @hbm_cmd: bus message command header
* @me_addr: address of the client in ME
* @host_addr: address of the client in the driver
* @reserved: reserved for alignment
*/
struct hbm_notification {
u8 hbm_cmd;
u8 me_addr;
u8 host_addr;
u8 reserved;
} __packed;
/**
* struct hbm_dma_mem_dscr - dma ring
*
* @addr_hi: the high 32bits of 64 bit address
* @addr_lo: the low 32bits of 64 bit address
* @size : size in bytes (must be power of 2)
*/
struct hbm_dma_mem_dscr {
u32 addr_hi;
u32 addr_lo;
u32 size;
} __packed;
enum {
DMA_DSCR_HOST = 0,
DMA_DSCR_DEVICE = 1,
DMA_DSCR_CTRL = 2,
DMA_DSCR_NUM,
};
/**
* struct hbm_dma_setup_request - dma setup request
*
* @hbm_cmd: bus message command header
* @reserved: reserved for alignment
* @dma_dscr: dma descriptor for HOST, DEVICE, and CTRL
*/
struct hbm_dma_setup_request {
u8 hbm_cmd;
u8 reserved[3];
struct hbm_dma_mem_dscr dma_dscr[DMA_DSCR_NUM];
} __packed;
/**
* struct hbm_dma_setup_response - dma setup response
*
* @hbm_cmd: bus message command header
* @status: 0 on success; otherwise DMA setup failed.
* @reserved: reserved for alignment
*/
struct hbm_dma_setup_response {
u8 hbm_cmd;
u8 status;
u8 reserved[2];
} __packed;
/**
* struct hbm_dma_ring_ctrl - dma ring control block
*
* @hbuf_wr_idx: host circular buffer write index in slots
* @reserved1: reserved for alignment
* @hbuf_rd_idx: host circular buffer read index in slots
* @reserved2: reserved for alignment
* @dbuf_wr_idx: device circular buffer write index in slots
* @reserved3: reserved for alignment
* @dbuf_rd_idx: device circular buffer read index in slots
* @reserved4: reserved for alignment
*/
struct hbm_dma_ring_ctrl {
u32 hbuf_wr_idx;
u32 reserved1;
u32 hbuf_rd_idx;
u32 reserved2;
u32 dbuf_wr_idx;
u32 reserved3;
u32 dbuf_rd_idx;
u32 reserved4;
} __packed;
/* 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)
/**
* struct hbm_capability_request - capability request from host to fw
*
* @hbm_cmd : bus message command header
* @capability_requested: bitmask of capabilities requested by host
*/
struct hbm_capability_request {
u8 hbm_cmd;
u8 capability_requested[3];
} __packed;
/**
* struct hbm_capability_response - capability response from fw to host
*
* @hbm_cmd : bus message command header
* @capability_granted: bitmask of capabilities granted by FW
*/
struct hbm_capability_response {
u8 hbm_cmd;
u8 capability_granted[3];
} __packed;
/**
* struct hbm_client_dma_map_request - client dma map request from host to fw
*
* @hbm_cmd: bus message command header
* @client_buffer_id: client buffer id
* @reserved: reserved
* @address_lsb: DMA address LSB
* @address_msb: DMA address MSB
* @size: DMA size
*/
struct hbm_client_dma_map_request {
u8 hbm_cmd;
u8 client_buffer_id;
u8 reserved[2];
u32 address_lsb;
u32 address_msb;
u32 size;
} __packed;
/**
* 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
* @client_buffer_id: client buffer id
* @reserved: reserved
*/
struct hbm_client_dma_unmap_request {
u8 hbm_cmd;
u8 status;
u8 client_buffer_id;
u8 reserved;
} __packed;
/**
* struct hbm_client_dma_response - client dma unmap response
* from the firmware to the host
*
* @hbm_cmd: bus message command header
* @status: command status
*/
struct hbm_client_dma_response {
u8 hbm_cmd;
u8 status;
} __packed;
#endif
+433
View File
@@ -0,0 +1,433 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2012-2022, Intel Corporation. All rights reserved.
* Intel Management Engine Interface (Intel MEI) Linux driver
*/
#include <linux/export.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/delay.h>
#include <linux/mei.h>
#include "mei_dev.h"
#include "hbm.h"
#include "client.h"
const char *mei_dev_state_str(int state)
{
#define MEI_DEV_STATE(state) case MEI_DEV_##state: return #state
switch (state) {
MEI_DEV_STATE(INITIALIZING);
MEI_DEV_STATE(INIT_CLIENTS);
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:
return "unknown";
}
#undef MEI_DEV_STATE
}
const char *mei_pg_state_str(enum mei_pg_state state)
{
#define MEI_PG_STATE(state) case MEI_PG_##state: return #state
switch (state) {
MEI_PG_STATE(OFF);
MEI_PG_STATE(ON);
default:
return "unknown";
}
#undef MEI_PG_STATE
}
/**
* mei_fw_status2str - convert fw status registers to printable string
*
* @fw_status: firmware status
* @buf: string buffer at minimal size MEI_FW_STATUS_STR_SZ
* @len: buffer len must be >= MEI_FW_STATUS_STR_SZ
*
* Return: number of bytes written or -EINVAL if buffer is to small
*/
ssize_t mei_fw_status2str(struct mei_fw_status *fw_status,
char *buf, size_t len)
{
ssize_t cnt = 0;
int i;
buf[0] = '\0';
if (len < MEI_FW_STATUS_STR_SZ)
return -EINVAL;
for (i = 0; i < fw_status->count; i++)
cnt += scnprintf(buf + cnt, len - cnt, "%08X ",
fw_status->status[i]);
/* drop last space */
buf[cnt] = '\0';
return cnt;
}
EXPORT_SYMBOL_GPL(mei_fw_status2str);
/**
* mei_cancel_work - Cancel mei background jobs
*
* @dev: the device structure
*/
void mei_cancel_work(struct mei_device *dev)
{
cancel_work_sync(&dev->reset_work);
cancel_work_sync(&dev->bus_rescan_work);
cancel_delayed_work_sync(&dev->timer_work);
}
EXPORT_SYMBOL_GPL(mei_cancel_work);
/**
* mei_reset - resets host and fw.
*
* @dev: the device structure
*
* Return: 0 on success or < 0 if the reset hasn't succeeded
*/
int mei_reset(struct mei_device *dev)
{
enum mei_dev_state state = dev->dev_state;
bool interrupts_enabled;
int ret;
if (state != MEI_DEV_INITIALIZING &&
state != MEI_DEV_DISABLED &&
state != MEI_DEV_POWER_DOWN &&
state != MEI_DEV_POWER_UP) {
char fw_sts_str[MEI_FW_STATUS_STR_SZ];
mei_fw_status_str(dev, fw_sts_str, MEI_FW_STATUS_STR_SZ);
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);
/* we're already in reset, cancel the init timer
* if the reset was called due the hbm protocol error
* we need to call it before hw start
* so the hbm watchdog won't kick in
*/
mei_hbm_idle(dev);
/* enter reset flow */
interrupts_enabled = state != MEI_DEV_POWER_DOWN;
mei_set_devstate(dev, MEI_DEV_RESETTING);
dev->reset_count++;
if (dev->reset_count > MEI_MAX_CONSEC_RESET) {
dev_err(&dev->dev, "reset: reached maximal consecutive resets: disabling the device\n");
mei_set_devstate(dev, MEI_DEV_DISABLED);
return -ENODEV;
}
ret = mei_hw_reset(dev, interrupts_enabled);
/* fall through and remove the sw state even if hw reset has failed */
/* no need to clean up software state in case of power up */
if (state != MEI_DEV_INITIALIZING && state != MEI_DEV_POWER_UP)
mei_cl_all_disconnect(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);
return ret;
}
if (state == MEI_DEV_POWER_DOWN) {
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) {
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;
}
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);
mei_set_devstate(dev, MEI_DEV_RESETTING);
return ret;
}
return 0;
}
EXPORT_SYMBOL_GPL(mei_reset);
/**
* mei_start - initializes host and fw to start work.
*
* @dev: the device structure
*
* Return: 0 on success, <0 on failure.
*/
int mei_start(struct mei_device *dev)
{
int ret;
mutex_lock(&dev->device_lock);
/* acknowledge interrupt and stop interrupts */
mei_clear_interrupts(dev);
ret = mei_hw_config(dev);
if (ret)
goto err;
dev_dbg(&dev->dev, "reset in start the mei device.\n");
dev->reset_count = 0;
do {
mei_set_devstate(dev, MEI_DEV_INITIALIZING);
ret = mei_reset(dev);
if (ret == -ENODEV || dev->dev_state == MEI_DEV_DISABLED) {
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_hbm_version_is_supported(dev)) {
dev_dbg(&dev->dev, "MEI start failed.\n");
goto err;
}
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");
mei_set_devstate(dev, MEI_DEV_DISABLED);
mutex_unlock(&dev->device_lock);
return -ENODEV;
}
EXPORT_SYMBOL_GPL(mei_start);
/**
* mei_restart - restart device after suspend
*
* @dev: the device structure
*
* Return: 0 on success or -ENODEV if the restart hasn't succeeded
*/
int mei_restart(struct mei_device *dev)
{
int err;
mutex_lock(&dev->device_lock);
mei_set_devstate(dev, MEI_DEV_POWER_UP);
dev->reset_count = 0;
err = mei_reset(dev);
mutex_unlock(&dev->device_lock);
if (err == -ENODEV || dev->dev_state == MEI_DEV_DISABLED) {
dev_err(&dev->dev, "device disabled = %d\n", err);
return -ENODEV;
}
/* try to start again */
if (err)
schedule_work(&dev->reset_work);
return 0;
}
EXPORT_SYMBOL_GPL(mei_restart);
static void mei_reset_work(struct work_struct *work)
{
struct mei_device *dev =
container_of(work, struct mei_device, reset_work);
int ret;
mei_clear_interrupts(dev);
mei_synchronize_irq(dev);
mutex_lock(&dev->device_lock);
ret = mei_reset(dev);
mutex_unlock(&dev->device_lock);
if (dev->dev_state == MEI_DEV_DISABLED) {
dev_err(&dev->dev, "device disabled = %d\n", ret);
return;
}
/* retry reset in case of failure */
if (ret)
schedule_work(&dev->reset_work);
}
void mei_stop(struct mei_device *dev)
{
dev_dbg(&dev->dev, "stopping the device.\n");
mutex_lock(&dev->device_lock);
mei_set_devstate(dev, MEI_DEV_POWERING_DOWN);
mutex_unlock(&dev->device_lock);
mei_cl_bus_remove_devices(dev);
mutex_lock(&dev->device_lock);
mei_set_devstate(dev, MEI_DEV_POWER_DOWN);
mutex_unlock(&dev->device_lock);
mei_cancel_work(dev);
mei_clear_interrupts(dev);
mei_synchronize_irq(dev);
/* to catch HW-initiated reset */
mei_cancel_work(dev);
mutex_lock(&dev->device_lock);
mei_reset(dev);
/* move device to disabled state unconditionally */
mei_set_devstate(dev, MEI_DEV_DISABLED);
mutex_unlock(&dev->device_lock);
}
EXPORT_SYMBOL_GPL(mei_stop);
/**
* mei_write_is_idle - check if the write queues are idle
*
* @dev: the device structure
*
* Return: true of there is no pending write
*/
bool mei_write_is_idle(struct mei_device *dev)
{
bool idle = (dev->dev_state == MEI_DEV_ENABLED &&
!dev->pg_blocked &&
list_empty(&dev->ctrl_wr_list) &&
list_empty(&dev->write_list) &&
list_empty(&dev->write_waiting_list));
dev_dbg(&dev->dev, "write pg: is idle[%d] state=%s blocked=%d ctrl=%d write=%d wwait=%d\n",
idle,
mei_dev_state_str(dev->dev_state),
dev->pg_blocked,
list_empty(&dev->ctrl_wr_list),
list_empty(&dev->write_list),
list_empty(&dev->write_waiting_list));
return idle;
}
EXPORT_SYMBOL_GPL(mei_write_is_idle);
/**
* mei_device_init - initialize mei_device structure
*
* @dev: the mei device
* @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 *parent,
bool slow_fw,
const struct mei_hw_ops *hw_ops)
{
/* setup our list array */
INIT_LIST_HEAD(&dev->file_list);
INIT_LIST_HEAD(&dev->device_list);
INIT_LIST_HEAD(&dev->me_clients);
mutex_init(&dev->device_lock);
init_rwsem(&dev->me_clients_rwsem);
mutex_init(&dev->cl_bus_lock);
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_UNINITIALIZED;
init_waitqueue_head(&dev->wait_dev_state);
dev->reset_count = 0;
INIT_LIST_HEAD(&dev->write_list);
INIT_LIST_HEAD(&dev->write_waiting_list);
INIT_LIST_HEAD(&dev->ctrl_wr_list);
INIT_LIST_HEAD(&dev->ctrl_rd_list);
dev->tx_queue_limit = MEI_TX_QUEUE_LIMIT_DEFAULT;
INIT_DELAYED_WORK(&dev->timer_work, mei_timer);
INIT_WORK(&dev->reset_work, mei_reset_work);
INIT_WORK(&dev->bus_rescan_work, mei_cl_bus_rescan_work);
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
*/
bitmap_set(dev->host_clients_map, 0, 1);
dev->pg_event = MEI_PG_EVENT_IDLE;
dev->ops = hw_ops;
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);
+685
View File
@@ -0,0 +1,685 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2003-2018, Intel Corporation. All rights reserved.
* Intel Management Engine Interface (Intel MEI) Linux driver
*/
#include <linux/export.h>
#include <linux/kthread.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/jiffies.h>
#include <linux/slab.h>
#include <linux/pm_runtime.h>
#include <linux/mei.h>
#include "mei_dev.h"
#include "hbm.h"
#include "client.h"
/**
* mei_irq_compl_handler - dispatch complete handlers
* for the completed callbacks
*
* @dev: mei device
* @cmpl_list: list of completed cbs
*/
void mei_irq_compl_handler(struct mei_device *dev, struct list_head *cmpl_list)
{
struct mei_cl_cb *cb, *next;
struct mei_cl *cl;
list_for_each_entry_safe(cb, next, cmpl_list, list) {
cl = cb->cl;
list_del_init(&cb->list);
cl_dbg(dev, cl, "completing call back.\n");
mei_cl_complete(cl, cb);
}
}
EXPORT_SYMBOL_GPL(mei_irq_compl_handler);
/**
* mei_cl_hbm_equal - check if hbm is addressed to the client
*
* @cl: host client
* @mei_hdr: header of mei client message
*
* Return: true if matches, false otherwise
*/
static inline int mei_cl_hbm_equal(struct mei_cl *cl,
struct mei_msg_hdr *mei_hdr)
{
return mei_cl_host_addr(cl) == mei_hdr->host_addr &&
mei_cl_me_id(cl) == mei_hdr->me_addr;
}
/**
* mei_irq_discard_msg - discard received message
*
* @dev: mei device
* @hdr: message header
* @discard_len: the length of the message to discard (excluding header)
*/
static void mei_irq_discard_msg(struct mei_device *dev, struct mei_msg_hdr *hdr,
size_t discard_len)
{
if (hdr->dma_ring) {
mei_dma_ring_read(dev, NULL,
hdr->extension[dev->rd_msg_hdr_count - 2]);
discard_len = 0;
}
/*
* 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",
MEI_HDR_PRM(hdr));
}
/**
* mei_cl_irq_read_msg - process client message
*
* @cl: reading client
* @mei_hdr: header of mei client message
* @meta: extend meta header
* @cmpl_list: completion list
*
* Return: always 0
*/
static int mei_cl_irq_read_msg(struct mei_cl *cl,
struct mei_msg_hdr *mei_hdr,
struct mei_ext_meta_hdr *meta,
struct list_head *cmpl_list)
{
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;
u32 ext_len;
length = mei_hdr->length;
ext_len = 0;
if (mei_hdr->extended) {
ext_len = sizeof(*meta) + mei_slots2data(meta->size);
length -= ext_len;
}
cb = list_first_entry_or_null(&cl->rd_pending, struct mei_cl_cb, list);
if (!cb) {
if (!mei_cl_is_fixed_address(cl)) {
cl_err(dev, cl, "pending read cb not found\n");
goto discard;
}
cb = mei_cl_alloc_cb(cl, mei_cl_mtu(cl), MEI_FOP_READ, cl->fp);
if (!cb)
goto discard;
list_add_tail(&cb->list, &cl->rd_pending);
}
if (mei_hdr->extended) {
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_obj(*gsc_f2h);
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;
}
ext = mei_ext_next(ext);
} while (!mei_ext_last(meta, ext));
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",
cb->vtag, vtag_hdr->vtag);
cb->status = -EPROTO;
goto discard;
}
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;
goto discard;
}
if (mei_hdr->dma_ring)
length = mei_hdr->extension[mei_data2slots(ext_len)];
buf_sz = length + cb->buf_idx;
/* catch for integer overflow */
if (buf_sz < cb->buf_idx) {
cl_err(dev, cl, "message is too big len %d idx %zu\n",
length, cb->buf_idx);
cb->status = -EMSGSIZE;
goto discard;
}
if (cb->buf.size < buf_sz) {
cl_dbg(dev, cl, "message overflow. size %zu len %d idx %zu\n",
cb->buf.size, length, cb->buf_idx);
cb->status = -EMSGSIZE;
goto discard;
}
if (mei_hdr->dma_ring) {
mei_dma_ring_read(dev, cb->buf.data + cb->buf_idx, length);
/* for DMA read 0 length to generate interrupt to the device */
mei_read_slots(dev, cb->buf.data + cb->buf_idx, 0);
} else {
mei_read_slots(dev, cb->buf.data + cb->buf_idx, length);
}
cb->buf_idx += length;
if (mei_hdr->msg_complete) {
cl_dbg(dev, cl, "completed read length = %zu\n", cb->buf_idx);
list_move_tail(&cb->list, cmpl_list);
} else {
pm_request_autosuspend(dev->parent);
}
return 0;
discard:
if (cb)
list_move_tail(&cb->list, cmpl_list);
mei_irq_discard_msg(dev, mei_hdr, length);
return 0;
}
/**
* mei_cl_irq_disconnect_rsp - send disconnection response message
*
* @cl: client
* @cb: callback block.
* @cmpl_list: complete list.
*
* Return: 0, OK; otherwise, error.
*/
static int mei_cl_irq_disconnect_rsp(struct mei_cl *cl, struct mei_cl_cb *cb,
struct list_head *cmpl_list)
{
struct mei_device *dev = cl->dev;
u32 msg_slots;
int slots;
int ret;
msg_slots = mei_hbm2slots(sizeof(struct hbm_client_connect_response));
slots = mei_hbuf_empty_slots(dev);
if (slots < 0)
return -EOVERFLOW;
if ((u32)slots < msg_slots)
return -EMSGSIZE;
ret = mei_hbm_cl_disconnect_rsp(dev, cl);
list_move_tail(&cb->list, cmpl_list);
return ret;
}
/**
* mei_cl_irq_read - processes client read related operation from the
* interrupt thread context - request for flow control credits
*
* @cl: client
* @cb: callback block.
* @cmpl_list: complete list.
*
* Return: 0, OK; otherwise, error.
*/
static int mei_cl_irq_read(struct mei_cl *cl, struct mei_cl_cb *cb,
struct list_head *cmpl_list)
{
struct mei_device *dev = cl->dev;
u32 msg_slots;
int slots;
int ret;
if (!list_empty(&cl->rd_pending))
return 0;
msg_slots = mei_hbm2slots(sizeof(struct hbm_flow_control));
slots = mei_hbuf_empty_slots(dev);
if (slots < 0)
return -EOVERFLOW;
if ((u32)slots < msg_slots)
return -EMSGSIZE;
ret = mei_hbm_cl_flow_control_req(dev, cl);
if (ret) {
cl->status = ret;
cb->buf_idx = 0;
list_move_tail(&cb->list, cmpl_list);
return ret;
}
pm_request_autosuspend(dev->parent);
list_move_tail(&cb->list, &cl->rd_pending);
return 0;
}
static inline bool hdr_is_hbm(struct mei_msg_hdr *mei_hdr)
{
return mei_hdr->host_addr == 0 && mei_hdr->me_addr == 0;
}
static inline bool hdr_is_fixed(struct mei_msg_hdr *mei_hdr)
{
return mei_hdr->host_addr == 0 && mei_hdr->me_addr != 0;
}
static inline int hdr_is_valid(u32 msg_hdr)
{
struct mei_msg_hdr *mei_hdr;
u32 expected_len = 0;
mei_hdr = (struct mei_msg_hdr *)&msg_hdr;
if (!msg_hdr || mei_hdr->reserved)
return -EBADMSG;
if (mei_hdr->dma_ring)
expected_len += MEI_SLOT_SIZE;
if (mei_hdr->extended)
expected_len += MEI_SLOT_SIZE;
if (mei_hdr->length < expected_len)
return -EBADMSG;
return 0;
}
/**
* mei_irq_read_handler - bottom half read routine after ISR to
* handle the read processing.
*
* @dev: the device structure
* @cmpl_list: An instance of our list structure
* @slots: slots to read.
*
* Return: 0 on success, <0 on failure.
*/
int mei_irq_read_handler(struct mei_device *dev,
struct list_head *cmpl_list, s32 *slots)
{
struct mei_msg_hdr *mei_hdr;
struct mei_ext_meta_hdr *meta_hdr = NULL;
struct mei_cl *cl;
int ret;
u32 hdr_size_left;
u32 hdr_size_ext;
int i;
int ext_hdr_end;
if (!dev->rd_msg_hdr[0]) {
dev->rd_msg_hdr[0] = mei_read_hdr(dev);
dev->rd_msg_hdr_count = 1;
(*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->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));
if (mei_slots2data(*slots) < mei_hdr->length) {
dev_err(&dev->dev, "less data available than length=%08x.\n",
*slots);
/* we can't read the message */
ret = -ENODATA;
goto end;
}
ext_hdr_end = 1;
hdr_size_left = mei_hdr->length;
if (mei_hdr->extended) {
if (!dev->rd_msg_hdr[1]) {
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]);
}
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",
meta_hdr->size);
return -EBADMSG;
}
if (hdr_size_left < hdr_size_ext) {
dev_err(&dev->dev, "corrupted message header len %d\n",
mei_hdr->length);
return -EBADMSG;
}
hdr_size_left -= hdr_size_ext;
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->rd_msg_hdr[i]);
dev->rd_msg_hdr_count++;
(*slots)--;
}
}
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",
mei_hdr->length);
return -EBADMSG;
}
dev->rd_msg_hdr[ext_hdr_end] = mei_read_hdr(dev);
dev->rd_msg_hdr_count++;
(*slots)--;
mei_hdr->length -= sizeof(dev->rd_msg_hdr[ext_hdr_end]);
}
/* HBM message */
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);
goto end;
}
goto reset_slots;
}
/* find recipient cl */
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");
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 */
/* 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;
reset_slots:
/* reset the number of slots and header */
memset(dev->rd_msg_hdr, 0, sizeof(dev->rd_msg_hdr));
dev->rd_msg_hdr_count = 0;
*slots = mei_count_full_read_slots(dev);
if (*slots == -EOVERFLOW) {
/* overflow - reset */
dev_err(&dev->dev, "resetting due to slots overflow.\n");
/* set the event since message has been read */
ret = -ERANGE;
goto end;
}
end:
return ret;
}
EXPORT_SYMBOL_GPL(mei_irq_read_handler);
/**
* mei_irq_write_handler - dispatch write requests
* after irq received
*
* @dev: the device structure
* @cmpl_list: An instance of our list structure
*
* Return: 0 on success, <0 on failure.
*/
int mei_irq_write_handler(struct mei_device *dev, struct list_head *cmpl_list)
{
struct mei_cl *cl;
struct mei_cl_cb *cb, *next;
s32 slots;
int ret;
if (!mei_hbuf_acquire(dev))
return 0;
slots = mei_hbuf_empty_slots(dev);
if (slots < 0)
return -EOVERFLOW;
if (slots == 0)
return -EMSGSIZE;
/* complete all waiting for write CB */
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;
cl->status = 0;
cl_dbg(dev, cl, "MEI WRITE COMPLETE\n");
cl->writing_state = MEI_WRITE_COMPLETE;
list_move_tail(&cb->list, cmpl_list);
}
/* complete control write list CB */
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) {
case MEI_FOP_DISCONNECT:
/* send disconnect message */
ret = mei_cl_irq_disconnect(cl, cb, cmpl_list);
if (ret)
return ret;
break;
case MEI_FOP_READ:
/* send flow control message */
ret = mei_cl_irq_read(cl, cb, cmpl_list);
if (ret)
return ret;
break;
case MEI_FOP_CONNECT:
/* connect message */
ret = mei_cl_irq_connect(cl, cb, cmpl_list);
if (ret)
return ret;
break;
case MEI_FOP_DISCONNECT_RSP:
/* send disconnect resp */
ret = mei_cl_irq_disconnect_rsp(cl, cb, cmpl_list);
if (ret)
return ret;
break;
case MEI_FOP_NOTIFY_START:
case MEI_FOP_NOTIFY_STOP:
ret = mei_cl_irq_notify(cl, cb, cmpl_list);
if (ret)
return ret;
break;
case MEI_FOP_DMA_MAP:
ret = mei_cl_irq_dma_map(cl, cb, cmpl_list);
if (ret)
return ret;
break;
case MEI_FOP_DMA_UNMAP:
ret = mei_cl_irq_dma_unmap(cl, cb, cmpl_list);
if (ret)
return ret;
break;
default:
BUG();
}
}
/* complete write list CB */
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);
if (ret)
return ret;
}
return 0;
}
EXPORT_SYMBOL_GPL(mei_irq_write_handler);
/**
* mei_connect_timeout - connect/disconnect timeouts
*
* @cl: host client
*/
static void mei_connect_timeout(struct mei_cl *cl)
{
struct mei_device *dev = cl->dev;
if (cl->state == MEI_FILE_CONNECTING) {
if (dev->hbm_f_dot_supported) {
cl->state = MEI_FILE_DISCONNECT_REQUIRED;
wake_up(&cl->wait);
return;
}
}
mei_reset(dev);
}
#define MEI_STALL_TIMER_FREQ (2 * HZ)
/**
* mei_schedule_stall_timer - re-arm stall_timer work
*
* @dev: the device structure
*
* Schedule stall timer
*/
void mei_schedule_stall_timer(struct mei_device *dev)
{
schedule_delayed_work(&dev->timer_work, MEI_STALL_TIMER_FREQ);
}
/**
* mei_timer - timer function.
*
* @work: pointer to the work_struct structure
*
*/
void mei_timer(struct work_struct *work)
{
struct mei_cl *cl;
struct mei_device *dev = container_of(work,
struct mei_device, timer_work.work);
bool reschedule_timer = false;
mutex_lock(&dev->device_lock);
/* Catch interrupt stalls during HBM init handshake */
if (dev->dev_state == MEI_DEV_INIT_CLIENTS &&
dev->hbm_state != MEI_HBM_IDLE) {
if (dev->init_clients_timer) {
if (--dev->init_clients_timer == 0) {
dev_err(&dev->dev, "timer: init clients timeout hbm_state = %d.\n",
dev->hbm_state);
mei_reset(dev);
goto out;
}
reschedule_timer = true;
}
}
if (dev->dev_state != MEI_DEV_ENABLED)
goto out;
/*** connect/disconnect timeouts ***/
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");
mei_connect_timeout(cl);
goto out;
}
reschedule_timer = true;
}
}
out:
if (dev->dev_state != MEI_DEV_DISABLED && reschedule_timer)
mei_schedule_stall_timer(dev);
mutex_unlock(&dev->device_lock);
}
File diff suppressed because it is too large Load Diff
+16
View File
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2015-2016, Intel Corporation. All rights reserved.
* Intel Management Engine Interface (Intel MEI) Linux driver
*/
#include <linux/module.h>
/* sparse doesn't like tracepoint macros */
#ifndef __CHECKER__
#define CREATE_TRACE_POINTS
#include "mei-trace.h"
EXPORT_TRACEPOINT_SYMBOL(mei_reg_read);
EXPORT_TRACEPOINT_SYMBOL(mei_reg_write);
EXPORT_TRACEPOINT_SYMBOL(mei_pci_cfg_read);
#endif /* __CHECKER__ */
+85
View File
@@ -0,0 +1,85 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2015-2016, Intel Corporation. All rights reserved.
* Intel Management Engine Interface (Intel MEI) Linux driver
*/
#if !defined(_MEI_TRACE_H_) || defined(TRACE_HEADER_MULTI_READ)
#define _MEI_TRACE_H_
#include <linux/stringify.h>
#include <linux/types.h>
#include <linux/tracepoint.h>
#include <linux/device.h>
#undef TRACE_SYSTEM
#define TRACE_SYSTEM mei
TRACE_EVENT(mei_reg_read,
TP_PROTO(const struct device *dev, const char *reg, u32 offs, u32 val),
TP_ARGS(dev, reg, offs, val),
TP_STRUCT__entry(
__string(dev, dev_name(dev))
__string(reg, reg)
__field(u32, offs)
__field(u32, val)
),
TP_fast_assign(
__assign_str(dev);
__assign_str(reg);
__entry->offs = offs;
__entry->val = val;
),
TP_printk("[%s] read %s:[%#x] = %#x",
__get_str(dev), __get_str(reg), __entry->offs, __entry->val)
);
TRACE_EVENT(mei_reg_write,
TP_PROTO(const struct device *dev, const char *reg, u32 offs, u32 val),
TP_ARGS(dev, reg, offs, val),
TP_STRUCT__entry(
__string(dev, dev_name(dev))
__string(reg, reg)
__field(u32, offs)
__field(u32, val)
),
TP_fast_assign(
__assign_str(dev);
__assign_str(reg);
__entry->offs = offs;
__entry->val = val;
),
TP_printk("[%s] write %s[%#x] = %#x",
__get_str(dev), __get_str(reg), __entry->offs, __entry->val)
);
TRACE_EVENT(mei_pci_cfg_read,
TP_PROTO(const struct device *dev, const char *reg, u32 offs, u32 val, int ret),
TP_ARGS(dev, reg, offs, val, ret),
TP_STRUCT__entry(
__string(dev, dev_name(dev))
__string(reg, reg)
__field(u32, offs)
__field(u32, val)
__field(int, ret)
),
TP_fast_assign(
__assign_str(dev);
__assign_str(reg);
__entry->offs = offs;
__entry->val = val;
__entry->ret = ret;
),
TP_printk("[%s] pci cfg read %s:[%#x] = %#x, ret = %d",
__get_str(dev), __get_str(reg), __entry->offs, __entry->val, __entry->ret)
);
#endif /* _MEI_TRACE_H_ */
/* This part must be outside protection */
#undef TRACE_INCLUDE_PATH
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_PATH .
#define TRACE_INCLUDE_FILE mei-trace
#include <trace/define_trace.h>
+949
View File
@@ -0,0 +1,949 @@
/* 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_DEV_H_
#define _MEI_DEV_H_
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/poll.h>
#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"
#define MEI_SLOT_SIZE sizeof(u32)
#define MEI_RD_MSG_BUF_SIZE (128 * MEI_SLOT_SIZE)
/*
* Number of Maximum MEI Clients
*/
#define MEI_CLIENTS_MAX 256
/*
* maximum number of consecutive resets
*/
#define MEI_MAX_CONSEC_RESET 3
/*
* Number of File descriptors/handles
* that can be opened to the driver.
*
* Limit to 255: 256 Total Clients
* minus internal client for MEI Bus Messages
*/
#define MEI_MAX_OPEN_HANDLE_COUNT (MEI_CLIENTS_MAX - 1)
/* File state */
enum file_state {
MEI_FILE_UNINITIALIZED = 0,
MEI_FILE_INITIALIZING,
MEI_FILE_CONNECTING,
MEI_FILE_CONNECTED,
MEI_FILE_DISCONNECTING,
MEI_FILE_DISCONNECT_REPLY,
MEI_FILE_DISCONNECT_REQUIRED,
MEI_FILE_DISCONNECTED,
};
/* MEI device states */
enum mei_dev_state {
MEI_DEV_UNINITIALIZED = 0,
MEI_DEV_INITIALIZING,
MEI_DEV_INIT_CLIENTS,
MEI_DEV_ENABLED,
MEI_DEV_RESETTING,
MEI_DEV_DISABLED,
MEI_DEV_POWERING_DOWN,
MEI_DEV_POWER_DOWN,
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 {
MEI_IDLE,
MEI_WRITING,
MEI_WRITE_COMPLETE,
};
/**
* enum mei_cb_file_ops - file operation associated with the callback
* @MEI_FOP_READ: read
* @MEI_FOP_WRITE: write
* @MEI_FOP_CONNECT: connect
* @MEI_FOP_DISCONNECT: disconnect
* @MEI_FOP_DISCONNECT_RSP: disconnect response
* @MEI_FOP_NOTIFY_START: start notification
* @MEI_FOP_NOTIFY_STOP: stop notification
* @MEI_FOP_DMA_MAP: request client dma map
* @MEI_FOP_DMA_UNMAP: request client dma unmap
*/
enum mei_cb_file_ops {
MEI_FOP_READ = 0,
MEI_FOP_WRITE,
MEI_FOP_CONNECT,
MEI_FOP_DISCONNECT,
MEI_FOP_DISCONNECT_RSP,
MEI_FOP_NOTIFY_START,
MEI_FOP_NOTIFY_STOP,
MEI_FOP_DMA_MAP,
MEI_FOP_DMA_UNMAP,
};
/**
* enum mei_cl_io_mode - io mode between driver and fw
*
* @MEI_CL_IO_TX_BLOCKING: send is blocking
* @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),
};
/*
* Intel MEI message data struct
*/
struct mei_msg_data {
size_t size;
unsigned char *data;
};
struct mei_dma_data {
u8 buffer_id;
void *vaddr;
dma_addr_t daddr;
size_t size;
};
/**
* struct mei_dma_dscr - dma address descriptor
*
* @vaddr: dma buffer virtual address
* @daddr: dma buffer physical address
* @size : dma buffer size
*/
struct mei_dma_dscr {
void *vaddr;
dma_addr_t daddr;
size_t size;
};
/* Maximum number of processed FW status registers */
#define MEI_FW_STATUS_MAX 6
/* Minimal buffer for FW status string (8 bytes in dw + space or '\0') */
#define MEI_FW_STATUS_STR_SZ (MEI_FW_STATUS_MAX * (8 + 1))
/*
* struct mei_fw_status - storage of FW status data
*
* @count: number of actually available elements in array
* @status: FW status registers
*/
struct mei_fw_status {
int count;
u32 status[MEI_FW_STATUS_MAX];
};
/**
* struct mei_me_client - representation of me (fw) client
*
* @list: link in me client list
* @refcnt: struct reference count
* @props: client properties
* @client_id: me client id
* @tx_flow_ctrl_creds: flow control credits
* @connect_count: number connections to this client
* @bus_added: added to bus
*/
struct mei_me_client {
struct list_head list;
struct kref refcnt;
struct mei_client_properties props;
u8 client_id;
u8 tx_flow_ctrl_creds;
u8 connect_count;
u8 bus_added;
};
struct mei_cl;
/**
* struct mei_cl_cb - file operation callback structure
*
* @list: link in callback queue
* @cl: file client who is running this operation
* @fop_type: file operation type
* @buf: buffer for data associated with the callback
* @buf_idx: last read index
* @vtag: virtual tag
* @fp: pointer to file structure
* @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;
struct mei_cl *cl;
enum mei_cb_file_ops fop_type;
struct mei_msg_data buf;
size_t buf_idx;
u8 vtag;
const struct file *fp;
int status;
u32 internal:1;
u32 blocking:1;
struct mei_ext_hdr *ext_hdr;
};
/**
* struct mei_cl_vtag - file pointer to vtag mapping structure
*
* @list: link in map queue
* @fp: file pointer
* @vtag: corresponding vtag
* @pending_read: the read is pending on this file
*/
struct mei_cl_vtag {
struct list_head list;
const struct file *fp;
u8 vtag;
u8 pending_read:1;
};
/**
* struct mei_cl - me client host representation
* carried in file->private_data
*
* @link: link in the clients list
* @dev: mei parent device
* @state: file operation state
* @tx_wait: wait queue for tx completion
* @rx_wait: wait queue for rx completion
* @wait: wait queue for management operation
* @ev_wait: notification wait queue
* @ev_async: event async notification
* @status: connection status
* @me_cl: fw client connected
* @fp: file associated with client
* @host_client_id: host id
* @vtag_map: vtag map
* @tx_flow_ctrl_creds: transmit flow credentials
* @rx_flow_ctrl_creds: receive flow credentials
* @timer_count: watchdog timer for operation completion
* @notify_en: notification - enabled/disabled
* @notify_ev: pending notification event
* @tx_cb_queued: number of tx callbacks in queue
* @writing_state: state of the tx
* @rd_pending: pending read credits
* @rd_completed_lock: protects rd_completed queue
* @rd_completed: completed read
* @dma: dma settings
* @dma_mapped: dma buffer is currently mapped.
*
* @cldev: device on the mei client bus
*/
struct mei_cl {
struct list_head link;
struct mei_device *dev;
enum file_state state;
wait_queue_head_t tx_wait;
wait_queue_head_t rx_wait;
wait_queue_head_t wait;
wait_queue_head_t ev_wait;
struct fasync_struct *ev_async;
int status;
struct mei_me_client *me_cl;
const struct file *fp;
u8 host_client_id;
struct list_head vtag_map;
u8 tx_flow_ctrl_creds;
u8 rx_flow_ctrl_creds;
u8 timer_count;
u8 notify_en;
u8 notify_ev;
u8 tx_cb_queued;
enum mei_file_transaction_states writing_state;
struct list_head rd_pending;
spinlock_t rd_completed_lock; /* protects rd_completed queue */
struct list_head rd_completed;
struct mei_dma_data dma;
u8 dma_mapped;
struct mei_cl_device *cldev;
};
#define MEI_TX_QUEUE_LIMIT_DEFAULT 50
#define MEI_TX_QUEUE_LIMIT_MAX 255
#define MEI_TX_QUEUE_LIMIT_MIN 30
/**
* struct mei_hw_ops - hw specific ops
*
* @host_is_ready : query for host readiness
*
* @hw_is_ready : query if hw is ready
* @hw_reset : reset hw
* @hw_start : start hw after reset
* @hw_config : configure hw
*
* @fw_status : get fw status registers
* @trc_status : get trc status register
* @pg_state : power gating state of the device
* @pg_in_transition : is device now in pg transition
* @pg_is_enabled : is power gating enabled
*
* @intr_clear : clear pending interrupts
* @intr_enable : enable interrupts
* @intr_disable : disable interrupts
* @synchronize_irq : synchronize irqs
*
* @hbuf_free_slots : query for write buffer empty slots
* @hbuf_is_ready : query if write buffer is empty
* @hbuf_depth : query for write buffer depth
*
* @write : write a message to FW
*
* @rdbuf_full_slots : query how many slots are filled
*
* @read_hdr : get first 4 bytes (header)
* @read : read a buffer from the FW
*/
struct mei_hw_ops {
bool (*host_is_ready)(struct mei_device *dev);
bool (*hw_is_ready)(struct mei_device *dev);
int (*hw_reset)(struct mei_device *dev, bool enable);
int (*hw_start)(struct mei_device *dev);
int (*hw_config)(struct mei_device *dev);
int (*fw_status)(struct mei_device *dev, struct mei_fw_status *fw_sts);
int (*trc_status)(struct mei_device *dev, u32 *trc);
enum mei_pg_state (*pg_state)(struct mei_device *dev);
bool (*pg_in_transition)(struct mei_device *dev);
bool (*pg_is_enabled)(struct mei_device *dev);
void (*intr_clear)(struct mei_device *dev);
void (*intr_enable)(struct mei_device *dev);
void (*intr_disable)(struct mei_device *dev);
void (*synchronize_irq)(struct mei_device *dev);
int (*hbuf_free_slots)(struct mei_device *dev);
bool (*hbuf_is_ready)(struct mei_device *dev);
u32 (*hbuf_depth)(const struct mei_device *dev);
int (*write)(struct mei_device *dev,
const void *hdr, size_t hdr_len,
const void *data, size_t data_len);
int (*rdbuf_full_slots)(struct mei_device *dev);
u32 (*read_hdr)(const struct mei_device *dev);
int (*read)(struct mei_device *dev,
unsigned char *buf, unsigned long len);
};
/* 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, 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);
bool mei_cl_bus_notify_event(struct mei_cl *cl);
void mei_cl_bus_remove_devices(struct mei_device *bus);
int mei_cl_bus_init(void);
void mei_cl_bus_exit(void);
/**
* enum mei_pg_event - power gating transition events
*
* @MEI_PG_EVENT_IDLE: the driver is not in power gating transition
* @MEI_PG_EVENT_WAIT: the driver is waiting for a pg event to complete
* @MEI_PG_EVENT_RECEIVED: the driver received pg event
* @MEI_PG_EVENT_INTR_WAIT: the driver is waiting for a pg event interrupt
* @MEI_PG_EVENT_INTR_RECEIVED: the driver received pg event interrupt
*/
enum mei_pg_event {
MEI_PG_EVENT_IDLE,
MEI_PG_EVENT_WAIT,
MEI_PG_EVENT_RECEIVED,
MEI_PG_EVENT_INTR_WAIT,
MEI_PG_EVENT_INTR_RECEIVED,
};
/**
* enum mei_pg_state - device internal power gating state
*
* @MEI_PG_OFF: device is not power gated - it is active
* @MEI_PG_ON: device is power gated - it is in lower power state
*/
enum mei_pg_state {
MEI_PG_OFF = 0,
MEI_PG_ON = 1,
};
const char *mei_pg_state_str(enum mei_pg_state state);
/**
* struct mei_fw_version - MEI FW version struct
*
* @platform: platform identifier
* @major: major version field
* @minor: minor version field
* @buildno: build number version field
* @hotfix: hotfix number version field
*/
struct mei_fw_version {
u8 platform;
u8 major;
u16 minor;
u16 buildno;
u16 hotfix;
};
#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 */
};
/**
* enum mei_dev_kind - device type
*
* @MEI_DEV_KIND_MEI: basic device
* @MEI_DEV_KIND_ITOUCH: itouch support
* @MEI_DEV_KIND_GSC: discete graphics content protection
* @MEI_DEV_KIND_GSCFI: discete graphics chassis controller
* @MEI_DEV_KIND_IVSC: visual sensing controller
* @MEI_DEV_KIND_IOE: IO extender
* @MEI_DEV_KIND_MAX: sentinel
*/
enum mei_dev_kind {
MEI_DEV_KIND_MEI,
MEI_DEV_KIND_ITOUCH,
MEI_DEV_KIND_GSC,
MEI_DEV_KIND_GSCFI,
MEI_DEV_KIND_IVSC,
MEI_DEV_KIND_IOE,
MEI_DEV_KIND_MAX
};
/**
* struct mei_device - MEI private device struct
*
* @parent : device on a bus
* @dev : device object
* @cdev : character device pointer
* @minor : minor number allocated for device
*
* @write_list : write pending list
* @write_waiting_list : write completion list
* @ctrl_wr_list : pending control write list
* @ctrl_rd_list : pending control read list
* @tx_queue_limit: tx queues per client linit
*
* @file_list : list of opened handles
* @open_handle_count: number of opened handles
*
* @device_lock : big device lock
* @timer_work : MEI timer delayed work (timeouts)
*
* @recvd_hw_ready : hw ready message received flag
* @pg_blocked : low power mode is not allowed
* @read_fws_need_resume: the FW status handler needs HW woken from sleep
*
* @wait_hw_ready : wait queue for receive HW ready message form FW
* @wait_pg : wait queue for receive PG message from FW
* @wait_hbm_start : wait queue for receive HBM start message from FW
*
* @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
* @pg_domain : runtime PM domain
*
* @rd_msg_buf : control messages buffer
* @rd_msg_hdr : read message header storage
* @rd_msg_hdr_count : how many dwords were already read from header
*
* @hbuf_is_ready : query if the host host/write buffer is ready
* @dr_dscr: DMA ring descriptors: TX, RX, and CTRL
*
* @version : HBM protocol version in use
* @hbm_f_pg_supported : hbm feature pgi protocol
* @hbm_f_dc_supported : hbm feature dynamic clients
* @hbm_f_dot_supported : hbm feature disconnect on timeout
* @hbm_f_ev_supported : hbm feature event notification
* @hbm_f_fa_supported : hbm feature fixed address client
* @hbm_f_ie_supported : hbm feature immediate reply to enum request
* @hbm_f_os_supported : hbm feature support OS ver message
* @hbm_f_dr_supported : hbm feature dma ring supported
* @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
* @me_clients_map : FW clients bit map
* @host_clients_map : host clients id pool
*
* @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
*
* @device_list : mei client bus list
* @cl_bus_lock : client bus list lock
*
* @kind : kind of mei device
*
* @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 *parent;
struct device dev;
struct cdev *cdev;
int minor;
struct list_head write_list;
struct list_head write_waiting_list;
struct list_head ctrl_wr_list;
struct list_head ctrl_rd_list;
u8 tx_queue_limit;
struct list_head file_list;
long open_handle_count;
struct mutex device_lock;
struct delayed_work timer_work;
bool recvd_hw_ready;
bool pg_blocked;
bool read_fws_need_resume;
/*
* waiting queue for receive message from FW
*/
wait_queue_head_t wait_hw_ready;
wait_queue_head_t wait_pg;
wait_queue_head_t wait_hbm_start;
/*
* mei device states
*/
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;
/*
* Power Gating support
*/
enum mei_pg_event pg_event;
#ifdef CONFIG_PM
struct dev_pm_domain pg_domain;
#endif /* CONFIG_PM */
unsigned char rd_msg_buf[MEI_RD_MSG_BUF_SIZE];
u32 rd_msg_hdr[MEI_RD_MSG_BUF_SIZE];
int rd_msg_hdr_count;
/* write buffer */
bool hbuf_is_ready;
struct mei_dma_dscr dr_dscr[DMA_DSCR_NUM];
struct hbm_version version;
unsigned int hbm_f_pg_supported:1;
unsigned int hbm_f_dc_supported:1;
unsigned int hbm_f_dot_supported:1;
unsigned int hbm_f_ev_supported:1;
unsigned int hbm_f_fa_supported:1;
unsigned int hbm_f_ie_supported:1;
unsigned int hbm_f_os_supported:1;
unsigned int hbm_f_dr_supported:1;
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;
DECLARE_BITMAP(me_clients_map, MEI_CLIENTS_MAX);
DECLARE_BITMAP(host_clients_map, MEI_CLIENTS_MAX);
bool allow_fixed_address;
bool override_fixed_address;
struct mei_dev_timeouts timeouts;
struct work_struct reset_work;
struct work_struct bus_rescan_work;
/* List of bus devices */
struct list_head device_list;
struct mutex cl_bus_lock;
enum mei_dev_kind kind;
#if IS_ENABLED(CONFIG_DEBUG_FS)
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 *));
};
static inline unsigned long mei_secs_to_jiffies(unsigned long sec)
{
return msecs_to_jiffies(sec * MSEC_PER_SEC);
}
/**
* mei_data2slots - get slots number from a message length
*
* @length: size of the messages in bytes
*
* Return: number of slots
*/
static inline u32 mei_data2slots(size_t length)
{
return DIV_ROUND_UP(length, MEI_SLOT_SIZE);
}
/**
* mei_hbm2slots - get slots number from a hbm message length
* length + size of the mei message header
*
* @length: size of the messages in bytes
*
* Return: number of slots
*/
static inline u32 mei_hbm2slots(size_t length)
{
return DIV_ROUND_UP(sizeof(struct mei_msg_hdr) + length, MEI_SLOT_SIZE);
}
/**
* mei_slots2data - get data in slots - bytes from slots
*
* @slots: number of available slots
*
* Return: number of bytes in slots
*/
static inline u32 mei_slots2data(int slots)
{
return slots * MEI_SLOT_SIZE;
}
/*
* mei init function prototypes
*/
void mei_device_init(struct mei_device *dev,
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);
int mei_restart(struct mei_device *dev);
void mei_stop(struct mei_device *dev);
void mei_cancel_work(struct mei_device *dev);
void mei_set_devstate(struct mei_device *dev, enum mei_dev_state state);
int mei_dmam_ring_alloc(struct mei_device *dev);
void mei_dmam_ring_free(struct mei_device *dev);
bool mei_dma_ring_is_allocated(struct mei_device *dev);
void mei_dma_ring_reset(struct mei_device *dev);
void mei_dma_ring_read(struct mei_device *dev, unsigned char *buf, u32 len);
void mei_dma_ring_write(struct mei_device *dev, unsigned char *buf, u32 len);
u32 mei_dma_ring_empty_slots(struct mei_device *dev);
/*
* MEI interrupt functions prototype
*/
void mei_timer(struct work_struct *work);
void mei_schedule_stall_timer(struct mei_device *dev);
int mei_irq_read_handler(struct mei_device *dev,
struct list_head *cmpl_list, s32 *slots);
int mei_irq_write_handler(struct mei_device *dev, struct list_head *cmpl_list);
void mei_irq_compl_handler(struct mei_device *dev, struct list_head *cmpl_list);
/*
* Register Access Function
*/
static inline int mei_hw_config(struct mei_device *dev)
{
return dev->ops->hw_config(dev);
}
static inline enum mei_pg_state mei_pg_state(struct mei_device *dev)
{
return dev->ops->pg_state(dev);
}
static inline bool mei_pg_in_transition(struct mei_device *dev)
{
return dev->ops->pg_in_transition(dev);
}
static inline bool mei_pg_is_enabled(struct mei_device *dev)
{
return dev->ops->pg_is_enabled(dev);
}
static inline int mei_hw_reset(struct mei_device *dev, bool enable)
{
return dev->ops->hw_reset(dev, enable);
}
static inline int mei_hw_start(struct mei_device *dev)
{
return dev->ops->hw_start(dev);
}
static inline void mei_clear_interrupts(struct mei_device *dev)
{
dev->ops->intr_clear(dev);
}
static inline void mei_enable_interrupts(struct mei_device *dev)
{
dev->ops->intr_enable(dev);
}
static inline void mei_disable_interrupts(struct mei_device *dev)
{
dev->ops->intr_disable(dev);
}
static inline void mei_synchronize_irq(struct mei_device *dev)
{
dev->ops->synchronize_irq(dev);
}
static inline bool mei_host_is_ready(struct mei_device *dev)
{
return dev->ops->host_is_ready(dev);
}
static inline bool mei_hw_is_ready(struct mei_device *dev)
{
return dev->ops->hw_is_ready(dev);
}
static inline bool mei_hbuf_is_ready(struct mei_device *dev)
{
return dev->ops->hbuf_is_ready(dev);
}
static inline int mei_hbuf_empty_slots(struct mei_device *dev)
{
return dev->ops->hbuf_free_slots(dev);
}
static inline u32 mei_hbuf_depth(const struct mei_device *dev)
{
return dev->ops->hbuf_depth(dev);
}
static inline int mei_write_message(struct mei_device *dev,
const void *hdr, size_t hdr_len,
const void *data, size_t data_len)
{
return dev->ops->write(dev, hdr, hdr_len, data, data_len);
}
static inline u32 mei_read_hdr(const struct mei_device *dev)
{
return dev->ops->read_hdr(dev);
}
static inline void mei_read_slots(struct mei_device *dev,
unsigned char *buf, unsigned long len)
{
dev->ops->read(dev, buf, len);
}
static inline int mei_count_full_read_slots(struct mei_device *dev)
{
return dev->ops->rdbuf_full_slots(dev);
}
static inline int mei_trc_status(struct mei_device *dev, u32 *trc)
{
if (dev->ops->trc_status)
return dev->ops->trc_status(dev, trc);
return -EOPNOTSUPP;
}
static inline int mei_fw_status(struct mei_device *dev,
struct mei_fw_status *fw_status)
{
return dev->ops->fw_status(dev, fw_status);
}
bool mei_hbuf_acquire(struct mei_device *dev);
bool mei_write_is_idle(struct mei_device *dev);
#if IS_ENABLED(CONFIG_DEBUG_FS)
void mei_dbgfs_register(struct mei_device *dev, const char *name);
void mei_dbgfs_deregister(struct mei_device *dev);
#else
static inline void mei_dbgfs_register(struct mei_device *dev, const char *name) {}
static inline void mei_dbgfs_deregister(struct mei_device *dev) {}
#endif /* CONFIG_DEBUG_FS */
int mei_register(struct mei_device *dev, struct device *parent);
void mei_deregister(struct mei_device *dev);
#define MEI_HDR_FMT "hdr:host=%02d me=%02d len=%d dma=%1d ext=%1d internal=%1d comp=%1d"
#define MEI_HDR_PRM(hdr) \
(hdr)->host_addr, (hdr)->me_addr, \
(hdr)->length, (hdr)->dma_ring, (hdr)->extended, \
(hdr)->internal, (hdr)->msg_complete
ssize_t mei_fw_status2str(struct mei_fw_status *fw_sts, char *buf, size_t len);
/**
* mei_fw_status_str - fetch and convert fw status registers to printable string
*
* @dev: the device structure
* @buf: string buffer at minimal size MEI_FW_STATUS_STR_SZ
* @len: buffer len must be >= MEI_FW_STATUS_STR_SZ
*
* Return: number of bytes written or < 0 on failure
*/
static inline ssize_t mei_fw_status_str(struct mei_device *dev,
char *buf, size_t len)
{
struct mei_fw_status fw_status;
int ret;
buf[0] = '\0';
ret = mei_fw_status(dev, &fw_status);
if (ret)
return ret;
ret = mei_fw_status2str(&fw_status, buf, MEI_FW_STATUS_STR_SZ);
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)
{
return dev->kind == MEI_DEV_KIND_GSC;
}
/**
* 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)
{
return dev->kind == MEI_DEV_KIND_GSCFI;
}
#endif
+505
View File
@@ -0,0 +1,505 @@
// 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.
*/
/* Late Binding version 1 */
#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
#define MEI_GUID_MKHI UUID_LE(0xe2c2afa2, 0x3817, 0x4d19, \
0x9d, 0x95, 0x6, 0xb1, 0x6b, 0x58, 0x8a, 0x5d)
/**
* 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;
/* Late Binding version 2 */
#define MEI_LB2_CMD 0x01
#define MEI_LB2_HDR_FLAG_RSP 0x01
#define MEI_GUID_LB UUID_LE(0x4ed87243, 0x3980, 0x4d8e, \
0xb1, 0xf9, 0x6f, 0xb7, 0xc0, 0x14, 0x8c, 0x4d)
/**
* struct mei_lb2_header - Late Binding2 header
* @command_id:
* @flags: Flags for transport layer (e.g. MEI_LB2_HDR_FLAG_RSP)
* @reserved: Reserved for future use by authentication firmware, must be set to 0
*/
struct mei_lb2_header {
__le32 command_id;
u8 flags;
u8 reserved[3];
};
/**
* struct mei_lb2_rsp_header - Late Binding2 response header
* @header: Common command header
* @status: Status returned by authentication firmware (see &enum intel_lb_status)
*/
struct mei_lb2_rsp_header {
struct mei_lb2_header header;
__le32 status;
};
#define MEI_LB2_FLAG_FST_CHUNK 0x02
#define MEI_LB2_FLAG_LST_CHUNK 0x04
/**
* struct mei_lb2_req - Late Binding2 request
* @header: Common command header
* @type: Type of the Late Binding payload (see &enum intel_lb_type)
* @flags: Flags to be passed to the authentication firmware (MEI_LB2_FLAG_*)
* @reserved: Reserved for future use by authentication firmware, must be set to 0
* @total_payload_size: Size of whole Late Binding package in bytes
* @payload_size: Size of the payload chunk in bytes
* @payload: Data chunk to be sent to the authentication firmware
*/
struct mei_lb2_req {
struct mei_lb2_header header;
__le32 type;
__le32 flags;
__le32 reserved;
__le32 total_payload_size;
__le32 payload_size;
u8 payload[] __counted_by(payload_size);
};
/**
* struct mei_lb2_rsp - Late Binding2 response
* @rheader: Common response header
* @type: Type of the Late Binding payload (see &enum intel_lb_type)
* @reserved: Reserved for future use by authentication firmware, must be set to 0
*/
struct mei_lb2_rsp {
struct mei_lb2_rsp_header rheader;
__le32 type;
__le32 reserved[2];
};
static bool mei_lb_check_response_v1(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_v1(struct device *dev, struct mei_cl_device *cldev,
u32 type, u32 flags, const void *payload, size_t payload_size)
{
struct mei_lb_req *req = NULL;
struct mei_lb_rsp rsp;
size_t req_size;
ssize_t bytes;
int 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_v1(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:
kfree(req);
return ret;
}
static int mei_lb_check_response_v2(const struct device *dev, ssize_t bytes,
struct mei_lb2_rsp *rsp)
{
/*
* Received message size may be smaller than the full message size when
* reply contains only header with status 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->rheader)) {
dev_err(dev, "Received less than header size from the firmware: %zd < %zu\n",
bytes, sizeof(rsp->rheader));
return -ENOMSG;
}
if (rsp->rheader.header.command_id != MEI_LB2_CMD) {
dev_err(dev, "Mismatch command: 0x%x instead of 0x%x\n",
rsp->rheader.header.command_id, MEI_LB2_CMD);
return -EPROTO;
}
if (!(rsp->rheader.header.flags & MEI_LB2_HDR_FLAG_RSP)) {
dev_err(dev, "Not a response: 0x%x\n", rsp->rheader.header.flags);
return -EBADMSG;
}
if (rsp->rheader.status) {
dev_err(dev, "Error in result: 0x%x\n", rsp->rheader.status);
return (int)le32_to_cpu(rsp->rheader.status);
}
if (bytes < sizeof(*rsp)) {
dev_err(dev, "Received less than message size from the firmware: %zd < %zu\n",
bytes, sizeof(*rsp));
return -ENODATA;
}
return 0;
}
static int mei_lb_push_payload_v2(struct device *dev, struct mei_cl_device *cldev,
u32 type, u32 flags, const void *payload, size_t payload_size)
{
u32 first_chunk, last_chunk;
struct mei_lb2_rsp rsp;
size_t sent_data = 0;
size_t chunk_size;
size_t req_size;
ssize_t bytes;
int ret;
struct mei_lb2_req *req __free(kfree) = kzalloc(mei_cldev_mtu(cldev), GFP_KERNEL);
if (!req)
return -ENOMEM;
first_chunk = MEI_LB2_FLAG_FST_CHUNK;
last_chunk = 0;
do {
chunk_size = min(payload_size - sent_data, mei_cldev_mtu(cldev) - sizeof(*req));
req_size = struct_size(req, payload, chunk_size);
if (sent_data + chunk_size == payload_size)
last_chunk = MEI_LB2_FLAG_LST_CHUNK;
req->header.command_id = MEI_LB2_CMD;
req->type = cpu_to_le32(type);
req->flags = cpu_to_le32(flags | first_chunk | last_chunk);
req->reserved = 0;
req->total_payload_size = cpu_to_le32(payload_size);
req->payload_size = cpu_to_le32(chunk_size);
memcpy(req->payload, payload + sent_data, chunk_size);
dev_dbg(dev, "Sending %zu bytes from offset %zu of %zu%s%s\n",
chunk_size, sent_data, payload_size,
first_chunk ? " first" : "", last_chunk ? " last" : "");
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);
return bytes;
}
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 firmware. %zd\n",
bytes);
return bytes;
}
ret = mei_lb_check_response_v2(dev, bytes, &rsp);
if (ret)
return ret;
/* prepare for the next chunk */
sent_data += chunk_size;
first_chunk = 0;
} while (!last_chunk);
return 0;
}
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 = to_mei_cl_device(dev);
int ret;
ret = mei_cldev_enable(cldev);
if (ret) {
dev_dbg(dev, "Failed to enable firmware client. %d\n", ret);
return ret;
}
if (memcmp(&MEI_GUID_LB, mei_cldev_uuid(cldev), sizeof(uuid_le)) == 0)
ret = mei_lb_push_payload_v2(dev, cldev, type, flags, payload, payload_size);
else
ret = mei_lb_push_payload_v1(dev, cldev, type, flags, payload, payload_size);
mei_cldev_disable(cldev);
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 vendor,
* determines if MEI is standalone PCI device or the auxiliary one
* and checks the following:
* 0) PCI parent: (e.g. /sys/class/mei/mei0/device -> ../../../0000:15:00.0)
* the requester and MEI device has the same grand parent
* 1) Auxiliary parent: (e.g. /sys/class/mei/mei1/device -> ../../../xe.mei-gscfi.768)
* the requester is the parent of MEI device
*/
struct device *base = data;
struct device *basep = dev;
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 (subcomponent != INTEL_COMPONENT_LB)
return 0;
base = base->parent;
if (!base) /* MEI device */
return 0;
if (dev_is_pci(base)) {
/* case 0) PCI parent */
base = base->parent; /* bridge 1 */
if (!base)
return 0;
base = base->parent; /* bridge 2 */
basep = basep->parent; /* bridge 1 */
if (!basep)
return 0;
basep = basep->parent; /* bridge 2 */
} else {
/* case 1) Auxiliary parent */
base = base->parent; /* PCI device */
}
return !!base && !!basep && base == basep;
}
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);
}
static const struct mei_cl_device_id mei_lb_tbl[] = {
{ .uuid = MEI_GUID_MKHI, .version = 1 },
{ .uuid = MEI_GUID_LB, .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");
+55
View File
@@ -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_ */
+261
View File
@@ -0,0 +1,261 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2025-2026, Intel Corporation. All rights reserved.
* Intel Management Engine Interface (Intel MEI) Linux driver
* for CSC platforms.
*/
#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/pm_runtime.h>
#include <linux/sched.h>
#include <linux/types.h>
#include "client.h"
#include "hw-me-regs.h"
#include "hw-me.h"
#include "mei_dev.h"
#include "mei-trace.h"
#define MEI_CSC_HECI2_OFFSET 0x1000
static int mei_csc_read_fws(const struct mei_device *mdev, int where, const char *name, u32 *val)
{
struct mei_me_hw *hw = to_me_hw(mdev);
*val = ioread32(hw->mem_addr + where + 0xC00);
trace_mei_reg_read(&mdev->dev, name, where, *val);
return 0;
}
static int mei_csc_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
struct device *dev = &pdev->dev;
const struct mei_cfg *cfg;
char __iomem *registers;
struct mei_device *mdev;
struct mei_me_hw *hw;
int err;
cfg = mei_me_get_cfg(ent->driver_data);
if (!cfg)
return -ENODEV;
err = pcim_enable_device(pdev);
if (err)
return dev_err_probe(dev, err, "Failed to enable PCI device.\n");
pci_set_master(pdev);
registers = pcim_iomap_region(pdev, 0, KBUILD_MODNAME);
if (IS_ERR(registers))
return dev_err_probe(dev, PTR_ERR(registers), "Failed to get PCI region.\n");
err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
if (err)
return dev_err_probe(dev, err, "No usable DMA configuration.\n");
/* allocates and initializes the mei dev structure */
mdev = mei_me_dev_init(dev, cfg, false);
if (!mdev)
return -ENOMEM;
mdev->read_fws_need_resume = true;
hw = to_me_hw(mdev);
/*
* Both HECI1 and HECI2 are on this device, but only HECI2 is supported.
*/
hw->mem_addr = registers + MEI_CSC_HECI2_OFFSET;
hw->read_fws = mei_csc_read_fws;
/*
* mei_register() assumes ownership of mdev.
* No need to release it explicitly in error path.
*/
err = mei_register(mdev, dev);
if (err)
return err;
pci_set_drvdata(pdev, mdev);
err = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_INTX | PCI_IRQ_MSI);
if (err < 0) {
dev_err_probe(dev, err, "Failed to allocate IRQ.\n");
goto err_mei_unreg;
}
hw->irq = pci_irq_vector(pdev, 0);
/* request and enable interrupt */
err = request_threaded_irq(hw->irq,
mei_me_irq_quick_handler, mei_me_irq_thread_handler,
IRQF_SHARED | IRQF_ONESHOT, KBUILD_MODNAME, mdev);
if (err)
goto err_free_irq_vectors;
/*
* Continue to char device setup in spite of firmware handshake failure.
* In order to provide access to the firmware status registers to the user
* space via sysfs. The firmware status registers required to understand
* firmware error state and possible recovery flow.
*/
if (mei_start(mdev))
dev_warn(dev, "Failed to initialize HECI hardware.\n");
pm_runtime_set_autosuspend_delay(dev, MEI_ME_RPM_TIMEOUT);
pm_runtime_use_autosuspend(dev);
/*
* MEI requires to resume from runtime suspend mode
* in order to perform link reset flow upon system suspend.
*/
dev_pm_set_driver_flags(dev, DPM_FLAG_NO_DIRECT_COMPLETE);
pm_runtime_allow(dev);
pm_runtime_put_noidle(dev);
return 0;
err_free_irq_vectors:
pci_free_irq_vectors(pdev);
err_mei_unreg:
mei_deregister(mdev);
return err;
}
static void mei_csc_shutdown(struct pci_dev *pdev)
{
struct mei_device *mdev = pci_get_drvdata(pdev);
struct mei_me_hw *hw = to_me_hw(mdev);
pm_runtime_get_noresume(&pdev->dev);
mei_stop(mdev);
mei_disable_interrupts(mdev);
free_irq(hw->irq, mdev);
pci_free_irq_vectors(pdev);
}
static void mei_csc_remove(struct pci_dev *pdev)
{
struct mei_device *mdev = pci_get_drvdata(pdev);
mei_csc_shutdown(pdev);
mei_deregister(mdev);
}
static int mei_csc_pci_prepare(struct device *dev)
{
pm_runtime_resume(dev);
return 0;
}
static int mei_csc_pci_suspend(struct device *dev)
{
struct mei_device *mdev = dev_get_drvdata(dev);
mei_stop(mdev);
mei_disable_interrupts(mdev);
return 0;
}
static int mei_csc_pci_resume(struct device *dev)
{
struct mei_device *mdev = dev_get_drvdata(dev);
int err;
err = mei_restart(mdev);
if (err)
return err;
/* Start timer if stopped in suspend */
schedule_delayed_work(&mdev->timer_work, HZ);
return 0;
}
static void mei_csc_pci_complete(struct device *dev)
{
pm_runtime_suspend(dev);
}
static int mei_csc_pm_runtime_idle(struct device *dev)
{
struct mei_device *mdev = dev_get_drvdata(dev);
return mei_write_is_idle(mdev) ? 0 : -EBUSY;
}
static int mei_csc_pm_runtime_suspend(struct device *dev)
{
struct mei_device *mdev = dev_get_drvdata(dev);
struct mei_me_hw *hw = to_me_hw(mdev);
guard(mutex)(&mdev->device_lock);
if (!mei_write_is_idle(mdev))
return -EAGAIN;
hw->pg_state = MEI_PG_ON;
return 0;
}
static int mei_csc_pm_runtime_resume(struct device *dev)
{
struct mei_device *mdev = dev_get_drvdata(dev);
struct mei_me_hw *hw = to_me_hw(mdev);
irqreturn_t irq_ret;
scoped_guard(mutex, &mdev->device_lock)
hw->pg_state = MEI_PG_OFF;
/* Process all queues that wait for resume */
irq_ret = mei_me_irq_thread_handler(1, mdev);
if (irq_ret != IRQ_HANDLED)
dev_err(dev, "thread handler fail %d\n", irq_ret);
return 0;
}
static const struct dev_pm_ops mei_csc_pm_ops = {
.prepare = pm_sleep_ptr(mei_csc_pci_prepare),
.complete = pm_sleep_ptr(mei_csc_pci_complete),
SYSTEM_SLEEP_PM_OPS(mei_csc_pci_suspend, mei_csc_pci_resume)
RUNTIME_PM_OPS(mei_csc_pm_runtime_suspend,
mei_csc_pm_runtime_resume, mei_csc_pm_runtime_idle)
};
static const struct pci_device_id mei_csc_pci_tbl[] = {
{ PCI_DEVICE_DATA(INTEL, MEI_CRI, MEI_ME_CSC_CFG) },
{}
};
MODULE_DEVICE_TABLE(pci, mei_csc_pci_tbl);
static struct pci_driver mei_csc_driver = {
.name = KBUILD_MODNAME,
.id_table = mei_csc_pci_tbl,
.probe = mei_csc_probe,
.remove = mei_csc_remove,
.shutdown = mei_csc_shutdown,
.driver = {
.pm = &mei_csc_pm_ops,
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
}
};
module_pci_driver(mei_csc_driver);
MODULE_DESCRIPTION("Intel(R) Management Engine Interface for discrete graphics (CSC)");
MODULE_LICENSE("GPL");
+542
View File
@@ -0,0 +1,542 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2003-2022, Intel Corporation. All rights reserved.
* Intel Management Engine Interface (Intel MEI) Linux driver
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#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>
#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
#include <linux/mei.h>
#include "mei_dev.h"
#include "client.h"
#include "hw-me-regs.h"
#include "hw-me.h"
#include "mei-trace.h"
/* mei_pci_tbl - PCI Device ID Table */
static const struct pci_device_id mei_me_pci_tbl[] = {
{PCI_DEVICE_DATA(INTEL, MEI_82946GZ, MEI_ME_ICH_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_82G35, MEI_ME_ICH_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_82Q965, MEI_ME_ICH_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_82G965, MEI_ME_ICH_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_82GM965, MEI_ME_ICH_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_82GME965, MEI_ME_ICH_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_ICH9_82Q35, MEI_ME_ICH_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_ICH9_82G33, MEI_ME_ICH_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_ICH9_82Q33, MEI_ME_ICH_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_ICH9_82X38, MEI_ME_ICH_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_ICH9_3200, MEI_ME_ICH_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_ICH9_6, MEI_ME_ICH_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_ICH9_7, MEI_ME_ICH_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_ICH9_8, MEI_ME_ICH_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_ICH9_9, MEI_ME_ICH_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_ICH9_10, MEI_ME_ICH_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_ICH9M_1, MEI_ME_ICH_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_ICH9M_2, MEI_ME_ICH_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_ICH9M_3, MEI_ME_ICH_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_ICH9M_4, MEI_ME_ICH_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_ICH10_1, MEI_ME_ICH10_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_ICH10_2, MEI_ME_ICH10_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_ICH10_3, MEI_ME_ICH10_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_ICH10_4, MEI_ME_ICH10_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_IBXPK_1, MEI_ME_PCH6_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_IBXPK_2, MEI_ME_PCH6_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_CPT_1, MEI_ME_PCH_CPT_PBG_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_PBG_1, MEI_ME_PCH_CPT_PBG_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_PPT_1, MEI_ME_PCH7_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_PPT_2, MEI_ME_PCH7_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_PPT_3, MEI_ME_PCH7_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_LPT_H, MEI_ME_PCH8_SPS_4_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_LPT_W, MEI_ME_PCH8_SPS_4_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_LPT_LP, MEI_ME_PCH8_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_LPT_HR, MEI_ME_PCH8_SPS_4_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_WPT_LP, MEI_ME_PCH8_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_WPT_LP_2, MEI_ME_PCH8_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_SPT, MEI_ME_PCH8_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_SPT_2, MEI_ME_PCH8_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_SPT_3, MEI_ME_PCH8_ITOUCH_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_SPT_H, MEI_ME_PCH8_SPS_4_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_SPT_H_2, MEI_ME_PCH8_SPS_4_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_LBG, MEI_ME_PCH12_SPS_4_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_BXT_M, MEI_ME_PCH8_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_APL_I, MEI_ME_PCH8_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_DNV_IE, MEI_ME_PCH8_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_GLK, MEI_ME_PCH8_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_KBP, MEI_ME_PCH8_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_KBP_2, MEI_ME_PCH8_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_KBP_3, MEI_ME_PCH8_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_CNP_LP, MEI_ME_PCH12_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_CNP_LP_3, MEI_ME_PCH8_ITOUCH_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_CNP_H, MEI_ME_PCH12_SPS_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_CNP_H_3, MEI_ME_PCH12_SPS_ITOUCH_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_CMP_LP, MEI_ME_PCH12_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_CMP_LP_3, MEI_ME_PCH8_ITOUCH_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_CMP_V, MEI_ME_PCH12_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_CMP_H, MEI_ME_PCH12_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_CMP_H_3, MEI_ME_PCH8_ITOUCH_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_ICP_LP, MEI_ME_PCH12_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_ICP_N, MEI_ME_PCH12_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_TGP_LP, MEI_ME_PCH15_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_TGP_H, MEI_ME_PCH15_SPS_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_JSP_N, MEI_ME_PCH15_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_MCC, MEI_ME_PCH15_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_MCC_4, MEI_ME_PCH8_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_CDF, MEI_ME_PCH8_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_EBG, MEI_ME_PCH15_SPS_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_ADP_S, MEI_ME_PCH15_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_ADP_LP, MEI_ME_PCH15_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_ADP_P, MEI_ME_PCH15_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_ADP_N, MEI_ME_PCH15_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_RPL_S, MEI_ME_PCH15_SPS_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_MTL_M, MEI_ME_PCH15_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_ARL_S, MEI_ME_PCH15_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_ARL_H, MEI_ME_PCH15_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_LNL_M, MEI_ME_PCH15_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_PTL_H, MEI_ME_PCH15_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_PTL_P, MEI_ME_PCH15_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_WCL_P, MEI_ME_PCH15_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_NVL_S, MEI_ME_PCH22_IOE_CFG)},
{PCI_DEVICE_DATA(INTEL, MEI_NVL_H, MEI_ME_PCH15_CFG)},
/* required last entry */
{0, }
};
MODULE_DEVICE_TABLE(pci, mei_me_pci_tbl);
#ifdef CONFIG_PM
static inline void mei_me_set_pm_domain(struct mei_device *dev);
static inline void mei_me_unset_pm_domain(struct mei_device *dev);
#else
static inline void mei_me_set_pm_domain(struct mei_device *dev) {}
static inline void mei_me_unset_pm_domain(struct mei_device *dev) {}
#endif /* CONFIG_PM */
static int mei_me_read_fws(const struct mei_device *dev, int where, const char *name, u32 *val)
{
struct pci_dev *pdev = to_pci_dev(dev->parent);
int ret;
ret = pci_read_config_dword(pdev, where, val);
trace_mei_pci_cfg_read(&dev->dev, name, where, *val, ret);
return pcibios_err_to_errno(ret);
}
/**
* mei_me_quirk_probe - probe for devices that doesn't valid ME interface
*
* @pdev: PCI device structure
* @cfg: per generation config
*
* Return: true if ME Interface is valid, false otherwise
*/
static bool mei_me_quirk_probe(struct pci_dev *pdev,
const struct mei_cfg *cfg)
{
if (cfg->quirk_probe && cfg->quirk_probe(pdev)) {
dev_info(&pdev->dev, "Device doesn't have valid ME Interface\n");
return false;
}
return true;
}
/**
* mei_me_probe - Device Initialization Routine
*
* @pdev: PCI device structure
* @ent: entry in kcs_pci_tbl
*
* Return: 0 on success, <0 on failure.
*/
static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
const struct mei_cfg *cfg;
struct mei_device *dev;
struct mei_me_hw *hw;
unsigned int irqflags;
int err;
cfg = mei_me_get_cfg(ent->driver_data);
if (!cfg)
return -ENODEV;
if (!mei_me_quirk_probe(pdev, cfg))
return -ENODEV;
/* enable pci dev */
err = pcim_enable_device(pdev);
if (err) {
dev_err(&pdev->dev, "failed to enable pci device.\n");
goto end;
}
/* set PCI host mastering */
pci_set_master(pdev);
/* pci request regions and mapping IO device memory for mei driver */
err = pcim_iomap_regions(pdev, BIT(0), KBUILD_MODNAME);
if (err) {
dev_err(&pdev->dev, "failed to get pci regions.\n");
goto end;
}
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, false);
if (!dev) {
err = -ENOMEM;
goto end;
}
hw = to_me_hw(dev);
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;
/* request and enable interrupt */
irqflags = pci_dev_msi_enabled(pdev) ? IRQF_ONESHOT : IRQF_SHARED;
err = request_threaded_irq(pdev->irq,
mei_me_irq_quick_handler,
mei_me_irq_thread_handler,
irqflags, KBUILD_MODNAME, dev);
if (err) {
dev_err(&pdev->dev, "request_threaded_irq failure. irq = %d\n",
pdev->irq);
goto deregister;
}
if (mei_start(dev)) {
dev_err(&pdev->dev, "init hw failure.\n");
err = -ENODEV;
goto deregister;
}
pm_runtime_set_autosuspend_delay(&pdev->dev, MEI_ME_RPM_TIMEOUT);
pm_runtime_use_autosuspend(&pdev->dev);
pci_set_drvdata(pdev, dev);
/*
* MEI requires to resume from runtime suspend mode
* in order to perform link reset flow upon system suspend.
*/
dev_pm_set_driver_flags(&pdev->dev, DPM_FLAG_NO_DIRECT_COMPLETE);
/*
* ME maps runtime suspend/resume to D0i states,
* hence we need to go around native PCI runtime service which
* eventually brings the device into D3cold/hot state,
* but the mei device cannot wake up from D3 unlike from D0i3.
* To get around the PCI device native runtime pm,
* ME uses runtime pm domain handlers which take precedence
* over the driver's pm handlers.
*/
mei_me_set_pm_domain(dev);
if (mei_pg_is_enabled(dev)) {
pm_runtime_put_noidle(&pdev->dev);
if (hw->d0i3_supported)
pm_runtime_allow(&pdev->dev);
}
dev_dbg(&pdev->dev, "initialization successful.\n");
return 0;
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;
}
/**
* mei_me_shutdown - Device Removal Routine
*
* @pdev: PCI device structure
*
* mei_me_shutdown is called from the reboot notifier
* it's a simplified version of remove so we go down
* faster.
*/
static void mei_me_shutdown(struct pci_dev *pdev)
{
struct mei_device *dev = pci_get_drvdata(pdev);
dev_dbg(&pdev->dev, "shutdown\n");
mei_stop(dev);
mei_me_unset_pm_domain(dev);
mei_disable_interrupts(dev);
free_irq(pdev->irq, dev);
}
/**
* mei_me_remove - Device Removal Routine
*
* @pdev: PCI device structure
*
* mei_me_remove is called by the PCI subsystem to alert the driver
* that it should release a PCI device.
*/
static void mei_me_remove(struct pci_dev *pdev)
{
struct mei_device *dev = pci_get_drvdata(pdev);
if (mei_pg_is_enabled(dev))
pm_runtime_get_noresume(&pdev->dev);
dev_dbg(&pdev->dev, "stop\n");
mei_stop(dev);
mei_me_unset_pm_domain(dev);
mei_disable_interrupts(dev);
free_irq(pdev->irq, dev);
mei_deregister(dev);
}
#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);
dev_dbg(&pdev->dev, "suspend\n");
mei_stop(dev);
mei_disable_interrupts(dev);
free_irq(pdev->irq, dev);
pci_disable_msi(pdev);
return 0;
}
static int mei_me_pci_resume(struct device *device)
{
struct pci_dev *pdev = to_pci_dev(device);
struct mei_device *dev = pci_get_drvdata(pdev);
unsigned int irqflags;
int err;
pci_enable_msi(pdev);
irqflags = pci_dev_msi_enabled(pdev) ? IRQF_ONESHOT : IRQF_SHARED;
/* request and enable interrupt */
err = request_threaded_irq(pdev->irq,
mei_me_irq_quick_handler,
mei_me_irq_thread_handler,
irqflags, KBUILD_MODNAME, dev);
if (err) {
dev_err(&pdev->dev, "request_threaded_irq failed: irq = %d.\n",
pdev->irq);
return err;
}
err = mei_restart(dev);
if (err) {
free_irq(pdev->irq, dev);
return err;
}
/* Start timer if stopped in suspend */
schedule_delayed_work(&dev->timer_work, HZ);
return 0;
}
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 = dev_get_drvdata(device);
dev_dbg(device, "rpm: me: runtime_idle\n");
if (mei_write_is_idle(dev))
pm_runtime_autosuspend(device);
return -EBUSY;
}
static int mei_me_pm_runtime_suspend(struct device *device)
{
struct mei_device *dev = dev_get_drvdata(device);
int ret;
dev_dbg(device, "rpm: me: runtime suspend\n");
mutex_lock(&dev->device_lock);
if (mei_write_is_idle(dev))
ret = mei_me_pg_enter_sync(dev);
else
ret = -EAGAIN;
mutex_unlock(&dev->device_lock);
dev_dbg(device, "rpm: me: runtime suspend ret=%d\n", ret);
if (ret && ret != -EAGAIN)
schedule_work(&dev->reset_work);
return ret;
}
static int mei_me_pm_runtime_resume(struct device *device)
{
struct mei_device *dev = dev_get_drvdata(device);
int ret;
dev_dbg(device, "rpm: me: runtime resume\n");
mutex_lock(&dev->device_lock);
ret = mei_me_pg_exit_sync(dev);
mutex_unlock(&dev->device_lock);
dev_dbg(device, "rpm: me: runtime resume ret = %d\n", ret);
if (ret)
schedule_work(&dev->reset_work);
return ret;
}
/**
* mei_me_set_pm_domain - fill and set pm domain structure for device
*
* @dev: mei_device
*/
static inline void mei_me_set_pm_domain(struct mei_device *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;
dev->pg_domain.ops.runtime_suspend = mei_me_pm_runtime_suspend;
dev->pg_domain.ops.runtime_resume = mei_me_pm_runtime_resume;
dev->pg_domain.ops.runtime_idle = mei_me_pm_runtime_idle;
dev_pm_domain_set(&pdev->dev, &dev->pg_domain);
}
}
/**
* mei_me_unset_pm_domain - clean pm domain structure for device
*
* @dev: mei_device
*/
static inline void mei_me_unset_pm_domain(struct mei_device *dev)
{
/* stop using pm callbacks if any */
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(
mei_me_pm_runtime_suspend,
mei_me_pm_runtime_resume,
mei_me_pm_runtime_idle)
};
#define MEI_ME_PM_OPS (&mei_me_pm_ops)
#else
#define MEI_ME_PM_OPS NULL
#endif /* CONFIG_PM */
/*
* PCI driver structure
*/
static struct pci_driver mei_me_driver = {
.name = KBUILD_MODNAME,
.id_table = mei_me_pci_tbl,
.probe = mei_me_probe,
.remove = mei_me_remove,
.shutdown = mei_me_shutdown,
.driver.pm = MEI_ME_PM_OPS,
.driver.probe_type = PROBE_PREFER_ASYNCHRONOUS,
};
module_pci_driver(mei_me_driver);
MODULE_AUTHOR("Intel Corporation");
MODULE_DESCRIPTION("Intel(R) Management Engine Interface");
MODULE_LICENSE("GPL v2");
+378
View File
@@ -0,0 +1,378 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2013-2020, Intel Corporation. All rights reserved.
* Intel Management Engine Interface (Intel MEI) Linux driver
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/pci.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
#include <linux/mei.h>
#include "mei_dev.h"
#include "hw-txe.h"
static const struct pci_device_id mei_txe_pci_tbl[] = {
{PCI_VDEVICE(INTEL, 0x0F18)}, /* Baytrail */
{PCI_VDEVICE(INTEL, 0x2298)}, /* Cherrytrail */
{0, }
};
MODULE_DEVICE_TABLE(pci, mei_txe_pci_tbl);
#ifdef CONFIG_PM
static inline void mei_txe_set_pm_domain(struct mei_device *dev);
static inline void mei_txe_unset_pm_domain(struct mei_device *dev);
#else
static inline void mei_txe_set_pm_domain(struct mei_device *dev) {}
static inline void mei_txe_unset_pm_domain(struct mei_device *dev) {}
#endif /* CONFIG_PM */
/**
* mei_txe_probe - Device Initialization Routine
*
* @pdev: PCI device structure
* @ent: entry in mei_txe_pci_tbl
*
* Return: 0 on success, <0 on failure.
*/
static int mei_txe_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
struct mei_device *dev;
struct mei_txe_hw *hw;
const int mask = BIT(SEC_BAR) | BIT(BRIDGE_BAR);
int err;
/* enable pci dev */
err = pcim_enable_device(pdev);
if (err) {
dev_err(&pdev->dev, "failed to enable pci device.\n");
goto end;
}
/* set PCI host mastering */
pci_set_master(pdev);
/* pci request regions and mapping IO device memory for mei driver */
err = pcim_iomap_regions(pdev, mask, KBUILD_MODNAME);
if (err) {
dev_err(&pdev->dev, "failed to get pci regions.\n");
goto end;
}
err = dma_set_mask(&pdev->dev, DMA_BIT_MASK(36));
if (err) {
err = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32));
if (err) {
dev_err(&pdev->dev, "No suitable DMA available.\n");
goto end;
}
}
/* allocates and initializes the mei dev structure */
dev = mei_txe_dev_init(pdev);
if (!dev) {
err = -ENOMEM;
goto end;
}
dev->kind = MEI_DEV_KIND_MEI;
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 */
mei_clear_interrupts(dev);
/* request and enable interrupt */
if (pci_dev_msi_enabled(pdev))
err = request_threaded_irq(pdev->irq,
NULL,
mei_txe_irq_thread_handler,
IRQF_ONESHOT, KBUILD_MODNAME, dev);
else
err = request_threaded_irq(pdev->irq,
mei_txe_irq_quick_handler,
mei_txe_irq_thread_handler,
IRQF_SHARED, KBUILD_MODNAME, dev);
if (err) {
dev_err(&pdev->dev, "mei: request_threaded_irq failure. irq = %d\n",
pdev->irq);
goto deregister;
}
if (mei_start(dev)) {
dev_err(&pdev->dev, "init hw failure.\n");
err = -ENODEV;
goto deregister;
}
pm_runtime_set_autosuspend_delay(&pdev->dev, MEI_TXI_RPM_TIMEOUT);
pm_runtime_use_autosuspend(&pdev->dev);
pci_set_drvdata(pdev, dev);
/*
* MEI requires to resume from runtime suspend mode
* in order to perform link reset flow upon system suspend.
*/
dev_pm_set_driver_flags(&pdev->dev, DPM_FLAG_NO_DIRECT_COMPLETE);
/*
* TXE maps runtime suspend/resume to own power gating states,
* hence we need to go around native PCI runtime service which
* eventually brings the device into D3cold/hot state.
* But the TXE device cannot wake up from D3 unlike from own
* power gating. To get around PCI device native runtime pm,
* TXE uses runtime pm domain handlers which take precedence.
*/
mei_txe_set_pm_domain(dev);
pm_runtime_put_noidle(&pdev->dev);
return 0;
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;
}
/**
* mei_txe_shutdown- Device Shutdown Routine
*
* @pdev: PCI device structure
*
* mei_txe_shutdown is called from the reboot notifier
* it's a simplified version of remove so we go down
* faster.
*/
static void mei_txe_shutdown(struct pci_dev *pdev)
{
struct mei_device *dev = pci_get_drvdata(pdev);
dev_dbg(&pdev->dev, "shutdown\n");
mei_stop(dev);
mei_txe_unset_pm_domain(dev);
mei_disable_interrupts(dev);
free_irq(pdev->irq, dev);
}
/**
* mei_txe_remove - Device Removal Routine
*
* @pdev: PCI device structure
*
* mei_remove is called by the PCI subsystem to alert the driver
* that it should release a PCI device.
*/
static void mei_txe_remove(struct pci_dev *pdev)
{
struct mei_device *dev = pci_get_drvdata(pdev);
pm_runtime_get_noresume(&pdev->dev);
mei_stop(dev);
mei_txe_unset_pm_domain(dev);
mei_disable_interrupts(dev);
free_irq(pdev->irq, dev);
mei_deregister(dev);
}
#ifdef CONFIG_PM_SLEEP
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);
dev_dbg(&pdev->dev, "suspend\n");
mei_stop(dev);
mei_disable_interrupts(dev);
free_irq(pdev->irq, dev);
pci_disable_msi(pdev);
return 0;
}
static int mei_txe_pci_resume(struct device *device)
{
struct pci_dev *pdev = to_pci_dev(device);
struct mei_device *dev = pci_get_drvdata(pdev);
int err;
pci_enable_msi(pdev);
mei_clear_interrupts(dev);
/* request and enable interrupt */
if (pci_dev_msi_enabled(pdev))
err = request_threaded_irq(pdev->irq,
NULL,
mei_txe_irq_thread_handler,
IRQF_ONESHOT, KBUILD_MODNAME, dev);
else
err = request_threaded_irq(pdev->irq,
mei_txe_irq_quick_handler,
mei_txe_irq_thread_handler,
IRQF_SHARED, KBUILD_MODNAME, dev);
if (err) {
dev_err(&pdev->dev, "request_threaded_irq failed: irq = %d.\n",
pdev->irq);
return err;
}
err = mei_restart(dev);
return err;
}
#endif /* CONFIG_PM_SLEEP */
#ifdef CONFIG_PM
static int mei_txe_pm_runtime_idle(struct device *device)
{
struct mei_device *dev = dev_get_drvdata(device);
dev_dbg(device, "rpm: txe: runtime_idle\n");
if (mei_write_is_idle(dev))
pm_runtime_autosuspend(device);
return -EBUSY;
}
static int mei_txe_pm_runtime_suspend(struct device *device)
{
struct mei_device *dev = dev_get_drvdata(device);
int ret;
dev_dbg(device, "rpm: txe: runtime suspend\n");
mutex_lock(&dev->device_lock);
if (mei_write_is_idle(dev))
ret = mei_txe_aliveness_set_sync(dev, 0);
else
ret = -EAGAIN;
/* keep irq on we are staying in D0 */
dev_dbg(device, "rpm: txe: runtime suspend ret=%d\n", ret);
mutex_unlock(&dev->device_lock);
if (ret && ret != -EAGAIN)
schedule_work(&dev->reset_work);
return ret;
}
static int mei_txe_pm_runtime_resume(struct device *device)
{
struct mei_device *dev = dev_get_drvdata(device);
int ret;
dev_dbg(device, "rpm: txe: runtime resume\n");
mutex_lock(&dev->device_lock);
mei_enable_interrupts(dev);
ret = mei_txe_aliveness_set_sync(dev, 1);
mutex_unlock(&dev->device_lock);
dev_dbg(device, "rpm: txe: runtime resume ret = %d\n", ret);
if (ret)
schedule_work(&dev->reset_work);
return ret;
}
/**
* mei_txe_set_pm_domain - fill and set pm domain structure for device
*
* @dev: mei_device
*/
static inline void mei_txe_set_pm_domain(struct mei_device *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;
dev->pg_domain.ops.runtime_suspend = mei_txe_pm_runtime_suspend;
dev->pg_domain.ops.runtime_resume = mei_txe_pm_runtime_resume;
dev->pg_domain.ops.runtime_idle = mei_txe_pm_runtime_idle;
dev_pm_domain_set(&pdev->dev, &dev->pg_domain);
}
}
/**
* mei_txe_unset_pm_domain - clean pm domain structure for device
*
* @dev: mei_device
*/
static inline void mei_txe_unset_pm_domain(struct mei_device *dev)
{
/* stop using pm callbacks if any */
dev_pm_domain_set(dev->parent, NULL);
}
static const struct dev_pm_ops mei_txe_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(mei_txe_pci_suspend,
mei_txe_pci_resume)
SET_RUNTIME_PM_OPS(
mei_txe_pm_runtime_suspend,
mei_txe_pm_runtime_resume,
mei_txe_pm_runtime_idle)
};
#define MEI_TXE_PM_OPS (&mei_txe_pm_ops)
#else
#define MEI_TXE_PM_OPS NULL
#endif /* CONFIG_PM */
/*
* PCI driver structure
*/
static struct pci_driver mei_txe_driver = {
.name = KBUILD_MODNAME,
.id_table = mei_txe_pci_tbl,
.probe = mei_txe_probe,
.remove = mei_txe_remove,
.shutdown = mei_txe_shutdown,
.driver.pm = MEI_TXE_PM_OPS,
};
module_pci_driver(mei_txe_driver);
MODULE_AUTHOR("Intel Corporation");
MODULE_DESCRIPTION("Intel(R) Trusted Execution Environment Interface");
MODULE_LICENSE("GPL v2");
+459
View File
@@ -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 = 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");
+12
View File
@@ -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!=n || DRM_XE!=n || COMPILE_TEST
help
MEI Support for PXP Services on Intel platforms.
Enables the ME FW services required for PXP support through
I915 display driver of Intel.
@@ -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
+344
View File
@@ -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_obj(*comp_master);
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");
@@ -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__ */
@@ -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_obj(*fw_loader);
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");
+577
View File
@@ -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");
+50
View File
@@ -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