summaryrefslogtreecommitdiff
path: root/drivers/misc
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-07-27 15:53:05 +0100
committerMark Brown <broonie@kernel.org>2026-07-27 15:53:05 +0100
commit7a4d8f4733f8bf98677c8562a7477f32fb507ec7 (patch)
treebfdf0d64102778875fda5eb51b889fb9e6dc9e4f /drivers/misc
parent58a63fb3b3e074080cd9c29a3b7cb55ebfa5ab3e (diff)
parent0957fbab972a9499626ac457fd924b24491c6315 (diff)
downloadlinux-next-7a4d8f4733f8bf98677c8562a7477f32fb507ec7.tar.gz
linux-next-7a4d8f4733f8bf98677c8562a7477f32fb507ec7.zip
Merge branch 'char-misc-next' of https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc.git
# Conflicts: # rust/kernel/task.rs
Diffstat (limited to 'drivers/misc')
-rw-r--r--drivers/misc/Kconfig1
-rw-r--r--drivers/misc/Makefile1
-rw-r--r--drivers/misc/ad525x_dpot-i2c.c1
-rw-r--r--drivers/misc/ad525x_dpot-spi.c1
-rw-r--r--drivers/misc/ad525x_dpot.c177
-rw-r--r--drivers/misc/ad525x_dpot.h3
-rw-r--r--drivers/misc/amd-sbi/Kconfig13
-rw-r--r--drivers/misc/amd-sbi/Makefile3
-rw-r--r--drivers/misc/amd-sbi/rmi-core.c4
-rw-r--r--drivers/misc/amd-sbi/tsi-core.c207
-rw-r--r--drivers/misc/amd-sbi/tsi-core.h29
-rw-r--r--drivers/misc/amd-sbi/tsi.c234
-rw-r--r--drivers/misc/bcm-vk/bcm_vk_dev.c15
-rw-r--r--drivers/misc/bcm-vk/bcm_vk_msg.c24
-rw-r--r--drivers/misc/cardreader/rtsx_pcr.c2
-rw-r--r--drivers/misc/enclosure.c4
-rw-r--r--drivers/misc/genwqe/card_utils.c2
-rw-r--r--drivers/misc/hi6421v600-irq.c5
-rw-r--r--drivers/misc/hpilo.c7
-rw-r--r--drivers/misc/ibmasm/r_heartbeat.c4
-rw-r--r--drivers/misc/ibmvmc.c17
-rw-r--r--drivers/misc/issei/Kconfig29
-rw-r--r--drivers/misc/issei/Makefile15
-rw-r--r--drivers/misc/issei/cdev.c446
-rw-r--r--drivers/misc/issei/cdev.h16
-rw-r--r--drivers/misc/issei/dma.c154
-rw-r--r--drivers/misc/issei/dma.h69
-rw-r--r--drivers/misc/issei/fw_client.c240
-rw-r--r--drivers/misc/issei/fw_client.h45
-rw-r--r--drivers/misc/issei/ham.c163
-rw-r--r--drivers/misc/issei/ham.h20
-rw-r--r--drivers/misc/issei/host_client.c519
-rw-r--r--drivers/misc/issei/host_client.h75
-rw-r--r--drivers/misc/issei/hw_heci.c550
-rw-r--r--drivers/misc/issei/hw_heci.h47
-rw-r--r--drivers/misc/issei/hw_heci_regs.h35
-rw-r--r--drivers/misc/issei/hw_msg.h163
-rw-r--r--drivers/misc/issei/issei_dev.h163
-rw-r--r--drivers/misc/issei/main.c296
-rw-r--r--drivers/misc/issei/pci_heci.c151
-rw-r--r--drivers/misc/keba/cp500.c17
-rw-r--r--drivers/misc/lan966x_pci.c1
-rw-r--r--drivers/misc/mei/gsc-me.c4
-rw-r--r--drivers/misc/mei/mei_lb.c4
-rw-r--r--drivers/misc/mrvl_cn10k_dpi.c4
-rw-r--r--drivers/misc/nsm.c5
-rw-r--r--drivers/misc/open-dice.c51
-rw-r--r--drivers/misc/pch_phub.c59
-rw-r--r--drivers/misc/rp1/rp1_pci.c27
-rw-r--r--drivers/misc/sgi-gru/grukservices.c4
-rw-r--r--drivers/misc/tps6594-esm.c2
-rw-r--r--drivers/misc/tps6594-pfsm.c2
-rw-r--r--drivers/misc/xilinx_sdfec.c78
53 files changed, 3969 insertions, 239 deletions
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 390256ed91f4..e594edf86941 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -651,4 +651,5 @@ source "drivers/misc/mchp_pci1xxxx/Kconfig"
source "drivers/misc/keba/Kconfig"
source "drivers/misc/amd-sbi/Kconfig"
source "drivers/misc/rp1/Kconfig"
+source "drivers/misc/issei/Kconfig"
endmenu
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index fed47c7672b9..086ac3f75935 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -74,3 +74,4 @@ obj-$(CONFIG_MCHP_LAN966X_PCI) += lan966x-pci.o
obj-y += keba/
obj-y += amd-sbi/
obj-$(CONFIG_MISC_RP1) += rp1/
+obj-$(CONFIG_INTEL_SSEI) += issei/
diff --git a/drivers/misc/ad525x_dpot-i2c.c b/drivers/misc/ad525x_dpot-i2c.c
index 469478f7a1d3..896ad61bb9e1 100644
--- a/drivers/misc/ad525x_dpot-i2c.c
+++ b/drivers/misc/ad525x_dpot-i2c.c
@@ -105,6 +105,7 @@ MODULE_DEVICE_TABLE(i2c, ad_dpot_id);
static struct i2c_driver ad_dpot_i2c_driver = {
.driver = {
.name = "ad_dpot",
+ .dev_groups = ad_dpot_groups,
},
.probe = ad_dpot_i2c_probe,
.remove = ad_dpot_i2c_remove,
diff --git a/drivers/misc/ad525x_dpot-spi.c b/drivers/misc/ad525x_dpot-spi.c
index 263055bda48b..1ebe629715a8 100644
--- a/drivers/misc/ad525x_dpot-spi.c
+++ b/drivers/misc/ad525x_dpot-spi.c
@@ -131,6 +131,7 @@ MODULE_DEVICE_TABLE(spi, ad_dpot_spi_id);
static struct spi_driver ad_dpot_spi_driver = {
.driver = {
.name = "ad_dpot",
+ .dev_groups = ad_dpot_groups,
},
.probe = ad_dpot_spi_probe,
.remove = ad_dpot_spi_remove,
diff --git a/drivers/misc/ad525x_dpot.c b/drivers/misc/ad525x_dpot.c
index 57bead9fba1b..a4e22fd4a107 100644
--- a/drivers/misc/ad525x_dpot.c
+++ b/drivers/misc/ad525x_dpot.c
@@ -630,66 +630,132 @@ static struct attribute *ad525x_attributes_commands[] = {
NULL
};
-static const struct attribute_group ad525x_group_commands = {
- .attrs = ad525x_attributes_commands,
+static struct attribute *ad525x_attributes[] = {
+ &dev_attr_rdac0.attr,
+ &dev_attr_rdac1.attr,
+ &dev_attr_rdac2.attr,
+ &dev_attr_rdac3.attr,
+ &dev_attr_rdac4.attr,
+ &dev_attr_rdac5.attr,
+ &dev_attr_eeprom0.attr,
+ &dev_attr_eeprom1.attr,
+ &dev_attr_eeprom2.attr,
+ &dev_attr_eeprom3.attr,
+ &dev_attr_eeprom4.attr,
+ &dev_attr_eeprom5.attr,
+ &dev_attr_tolerance0.attr,
+ &dev_attr_tolerance1.attr,
+ &dev_attr_tolerance2.attr,
+ &dev_attr_tolerance3.attr,
+ &dev_attr_tolerance4.attr,
+ &dev_attr_tolerance5.attr,
+ &dev_attr_otp0.attr,
+ &dev_attr_otp1.attr,
+ &dev_attr_otp2.attr,
+ &dev_attr_otp3.attr,
+ &dev_attr_otp4.attr,
+ &dev_attr_otp5.attr,
+ &dev_attr_otp0en.attr,
+ &dev_attr_otp1en.attr,
+ &dev_attr_otp2en.attr,
+ &dev_attr_otp3en.attr,
+ &dev_attr_otp4en.attr,
+ &dev_attr_otp5en.attr,
+ &dev_attr_inc_all.attr,
+ &dev_attr_dec_all.attr,
+ &dev_attr_inc_all_6db.attr,
+ &dev_attr_dec_all_6db.attr,
+ NULL
};
-static int ad_dpot_add_files(struct device *dev,
- unsigned int features, unsigned int rdac)
+static int ad525x_attr_index(struct attribute *attr,
+ const struct attribute * const *attrs)
{
- int err = sysfs_create_file(&dev->kobj,
- dpot_attrib_wipers[rdac]);
- if (features & F_CMD_EEP)
- err |= sysfs_create_file(&dev->kobj,
- dpot_attrib_eeprom[rdac]);
- if (features & F_CMD_TOL)
- err |= sysfs_create_file(&dev->kobj,
- dpot_attrib_tolerance[rdac]);
- if (features & F_CMD_OTP) {
- err |= sysfs_create_file(&dev->kobj,
- dpot_attrib_otp_en[rdac]);
- err |= sysfs_create_file(&dev->kobj,
- dpot_attrib_otp[rdac]);
- }
+ int i;
- if (err)
- dev_err(dev, "failed to register sysfs hooks for RDAC%d\n",
- rdac);
+ for (i = 0; attrs[i]; i++)
+ if (attr == attrs[i])
+ return i;
- return err;
+ return -ENOENT;
}
-static inline void ad_dpot_remove_files(struct device *dev,
- unsigned int features, unsigned int rdac)
+static bool ad525x_is_command_attr(struct attribute *attr)
{
- sysfs_remove_file(&dev->kobj,
- dpot_attrib_wipers[rdac]);
- if (features & F_CMD_EEP)
- sysfs_remove_file(&dev->kobj,
- dpot_attrib_eeprom[rdac]);
- if (features & F_CMD_TOL)
- sysfs_remove_file(&dev->kobj,
- dpot_attrib_tolerance[rdac]);
- if (features & F_CMD_OTP) {
- sysfs_remove_file(&dev->kobj,
- dpot_attrib_otp_en[rdac]);
- sysfs_remove_file(&dev->kobj,
- dpot_attrib_otp[rdac]);
+ int i;
+
+ for (i = 0; ad525x_attributes_commands[i]; i++) {
+ if (attr == ad525x_attributes_commands[i])
+ return true;
}
+
+ return false;
+}
+
+static umode_t ad525x_is_visible(struct kobject *kobj, struct attribute *attr,
+ int n)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct dpot_data *data = dev_get_drvdata(dev);
+ int rdac;
+
+ if (!data)
+ return 0;
+
+ rdac = ad525x_attr_index(attr, dpot_attrib_wipers);
+ if (rdac >= 0)
+ return data->wipers & BIT(rdac) ? attr->mode : 0;
+
+ rdac = ad525x_attr_index(attr, dpot_attrib_eeprom);
+ if (rdac >= 0)
+ return (data->wipers & BIT(rdac)) && (data->feat & F_CMD_EEP) ?
+ attr->mode : 0;
+
+ rdac = ad525x_attr_index(attr, dpot_attrib_tolerance);
+ if (rdac >= 0)
+ return (data->wipers & BIT(rdac)) && (data->feat & F_CMD_TOL) ?
+ attr->mode : 0;
+
+ rdac = ad525x_attr_index(attr, dpot_attrib_otp);
+ if (rdac >= 0)
+ return (data->wipers & BIT(rdac)) && (data->feat & F_CMD_OTP) ?
+ attr->mode : 0;
+
+ rdac = ad525x_attr_index(attr, dpot_attrib_otp_en);
+ if (rdac >= 0)
+ return (data->wipers & BIT(rdac)) && (data->feat & F_CMD_OTP) ?
+ attr->mode : 0;
+
+ if (ad525x_is_command_attr(attr))
+ return data->feat & F_CMD_INC ? attr->mode : 0;
+
+ return attr->mode;
}
+static const struct attribute_group ad525x_group = {
+ .attrs = ad525x_attributes,
+ .is_visible = ad525x_is_visible,
+};
+
+const struct attribute_group *ad_dpot_groups[] = {
+ &ad525x_group,
+ NULL
+};
+EXPORT_SYMBOL(ad_dpot_groups);
+
int ad_dpot_probe(struct device *dev,
struct ad_dpot_bus_data *bdata, unsigned long devid,
const char *name)
{
struct dpot_data *data;
- int i, err = 0;
+ int i;
data = kzalloc_obj(struct dpot_data);
if (!data) {
- err = -ENOMEM;
- goto exit;
+ dev_err(dev, "failed to create client for %s ID 0x%lX\n",
+ name, devid);
+ return -ENOMEM;
}
dev_set_drvdata(dev, data);
@@ -705,51 +771,22 @@ int ad_dpot_probe(struct device *dev,
data->wipers = DPOT_WIPERS(devid);
for (i = DPOT_RDAC0; i < MAX_RDACS; i++)
- if (data->wipers & (1 << i)) {
- err = ad_dpot_add_files(dev, data->feat, i);
- if (err)
- goto exit_remove_files;
+ if (data->wipers & BIT(i)) {
/* power-up midscale */
if (data->feat & F_RDACS_WONLY)
data->rdac_cache[i] = data->max_pos / 2;
}
- if (data->feat & F_CMD_INC)
- err = sysfs_create_group(&dev->kobj, &ad525x_group_commands);
-
- if (err) {
- dev_err(dev, "failed to register sysfs hooks\n");
- goto exit_free;
- }
-
dev_info(dev, "%s %d-Position Digital Potentiometer registered\n",
name, data->max_pos);
return 0;
-
-exit_remove_files:
- for (i = DPOT_RDAC0; i < MAX_RDACS; i++)
- if (data->wipers & (1 << i))
- ad_dpot_remove_files(dev, data->feat, i);
-
-exit_free:
- kfree(data);
- dev_set_drvdata(dev, NULL);
-exit:
- dev_err(dev, "failed to create client for %s ID 0x%lX\n",
- name, devid);
- return err;
}
EXPORT_SYMBOL(ad_dpot_probe);
void ad_dpot_remove(struct device *dev)
{
struct dpot_data *data = dev_get_drvdata(dev);
- int i;
-
- for (i = DPOT_RDAC0; i < MAX_RDACS; i++)
- if (data->wipers & (1 << i))
- ad_dpot_remove_files(dev, data->feat, i);
kfree(data);
}
diff --git a/drivers/misc/ad525x_dpot.h b/drivers/misc/ad525x_dpot.h
index 72a9d6801937..2e877c89523b 100644
--- a/drivers/misc/ad525x_dpot.h
+++ b/drivers/misc/ad525x_dpot.h
@@ -10,6 +10,8 @@
#include <linux/types.h>
+struct attribute_group;
+
#define DPOT_CONF(features, wipers, max_pos, uid) \
(((features) << 18) | (((wipers) & 0xFF) << 10) | \
((max_pos & 0xF) << 6) | (uid & 0x3F))
@@ -210,5 +212,6 @@ struct ad_dpot_bus_data {
int ad_dpot_probe(struct device *dev, struct ad_dpot_bus_data *bdata,
unsigned long devid, const char *name);
void ad_dpot_remove(struct device *dev);
+extern const struct attribute_group *ad_dpot_groups[];
#endif
diff --git a/drivers/misc/amd-sbi/Kconfig b/drivers/misc/amd-sbi/Kconfig
index 30e7fad7356c..1a96b71f8506 100644
--- a/drivers/misc/amd-sbi/Kconfig
+++ b/drivers/misc/amd-sbi/Kconfig
@@ -20,3 +20,16 @@ config AMD_SBRMI_HWMON
This provides support for RMI device hardware monitoring. If enabled,
a hardware monitoring device will be created for each socket in
the system.
+
+config AMD_SBTSI
+ tristate "AMD side band TSI support"
+ depends on I3C_OR_I2C
+ depends on ARM || ARM64 || COMPILE_TEST
+ select AUXILIARY_BUS
+ help
+ Enables support for the AMD SB-TSI (Side Band Temperature Sensor
+ Interface) driver, which provides access to emulated CPU temperature
+ sensors on AMD SoCs via an I2C/I3C connected BMC device.
+
+ This driver can also be built as a module. If so, the module will
+ be called sbtsi.
diff --git a/drivers/misc/amd-sbi/Makefile b/drivers/misc/amd-sbi/Makefile
index 38eaaa651fd9..ce9321f5c601 100644
--- a/drivers/misc/amd-sbi/Makefile
+++ b/drivers/misc/amd-sbi/Makefile
@@ -2,3 +2,6 @@
sbrmi-i2c-objs += rmi-i2c.o rmi-core.o
sbrmi-i2c-$(CONFIG_AMD_SBRMI_HWMON) += rmi-hwmon.o
obj-$(CONFIG_AMD_SBRMI_I2C) += sbrmi-i2c.o
+# SBTSI Configuration
+sbtsi-objs += tsi.o tsi-core.o
+obj-$(CONFIG_AMD_SBTSI) += sbtsi.o
diff --git a/drivers/misc/amd-sbi/rmi-core.c b/drivers/misc/amd-sbi/rmi-core.c
index d4238ebad3c6..95c9109101b5 100644
--- a/drivers/misc/amd-sbi/rmi-core.c
+++ b/drivers/misc/amd-sbi/rmi-core.c
@@ -581,6 +581,8 @@ int create_misc_rmi_device(struct sbrmi_data *data,
GFP_KERNEL,
"sbrmi-%x",
data->dev_static_addr);
+ if (!data->sbrmi_misc_dev.name)
+ return -ENOMEM;
data->sbrmi_misc_dev.minor = MISC_DYNAMIC_MINOR;
data->sbrmi_misc_dev.fops = &sbrmi_fops;
data->sbrmi_misc_dev.parent = dev;
@@ -588,6 +590,8 @@ int create_misc_rmi_device(struct sbrmi_data *data,
GFP_KERNEL,
"sbrmi-%x",
data->dev_static_addr);
+ if (!data->sbrmi_misc_dev.nodename)
+ return -ENOMEM;
data->sbrmi_misc_dev.mode = 0600;
return misc_register(&data->sbrmi_misc_dev);
diff --git a/drivers/misc/amd-sbi/tsi-core.c b/drivers/misc/amd-sbi/tsi-core.c
new file mode 100644
index 000000000000..5c178702c67a
--- /dev/null
+++ b/drivers/misc/amd-sbi/tsi-core.c
@@ -0,0 +1,207 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * tsi-core.c - file defining SB-TSI protocols compliant
+ * AMD SoC device.
+ *
+ * Copyright (C) 2026 Advanced Micro Devices, Inc.
+ */
+
+#include <linux/fs.h>
+#include <linux/ioctl.h>
+#include <linux/module.h>
+#include <linux/uaccess.h>
+#include <uapi/misc/amd-apml.h>
+#include "tsi-core.h"
+
+static inline struct sbtsi_i3c_priv *to_sbtsi_i3c_priv(struct sbtsi_data *data)
+{
+ return container_of(data, struct sbtsi_i3c_priv, data);
+}
+
+void sbtsi_data_release(struct kref *kref)
+{
+ struct sbtsi_data *data = container_of(kref, struct sbtsi_data, kref);
+
+ mutex_destroy(&data->lock);
+ if (data->is_i3c)
+ kfree(to_sbtsi_i3c_priv(data));
+ else
+ kfree(data);
+}
+
+/* I2C transfer function */
+static int sbtsi_i2c_xfer(struct sbtsi_data *data, u8 reg, u8 *val, bool is_read)
+{
+ if (is_read) {
+ int ret = i2c_smbus_read_byte_data(data->client, reg);
+
+ if (ret < 0)
+ return ret;
+ *val = ret;
+ return 0;
+ }
+ return i2c_smbus_write_byte_data(data->client, reg, *val);
+}
+
+/* I3C read transfer function */
+static int sbtsi_i3c_read(struct sbtsi_data *data, u8 reg, u8 *val)
+{
+ struct sbtsi_i3c_priv *priv = to_sbtsi_i3c_priv(data);
+ struct i3c_xfer xfers[2] = { };
+ int ret;
+
+ priv->tx[0] = reg;
+
+ /* Write the register address (DMA_TO_DEVICE). */
+ xfers[0].rnw = false;
+ xfers[0].len = 1;
+ xfers[0].data.out = priv->tx;
+
+ /* Read the data byte into a separate buffer (DMA_FROM_DEVICE). */
+ xfers[1].rnw = true;
+ xfers[1].len = 1;
+ xfers[1].data.in = &priv->rx;
+
+ ret = i3c_device_do_xfers(data->i3cdev, xfers, 2, I3C_SDR);
+ if (ret)
+ return ret;
+
+ *val = priv->rx;
+ return ret;
+}
+
+/* I3C write transfer function */
+static int sbtsi_i3c_write(struct sbtsi_data *data, u8 reg, u8 val)
+{
+ struct sbtsi_i3c_priv *priv = to_sbtsi_i3c_priv(data);
+ struct i3c_xfer xfers = {
+ .rnw = false,
+ .len = 2,
+ .data.out = priv->tx,
+ };
+
+ priv->tx[0] = reg;
+ priv->tx[1] = val;
+
+ return i3c_device_do_xfers(data->i3cdev, &xfers, 1, I3C_SDR);
+}
+
+/* Unified transfer function for I2C and I3C access */
+int sbtsi_xfer(struct sbtsi_data *data, u8 reg, u8 *val, bool is_read)
+{
+ if (data->is_i3c)
+ return is_read ? sbtsi_i3c_read(data, reg, val)
+ : sbtsi_i3c_write(data, reg, *val);
+ return sbtsi_i2c_xfer(data, reg, val, is_read);
+}
+EXPORT_SYMBOL_GPL(sbtsi_xfer);
+
+/*
+ * The mutex protects against concurrent register transfers to the device
+ * over the shared bus.
+ */
+static int sbtsi_xfer_ioctl(struct sbtsi_data *data, u8 reg, u8 *val, bool is_read)
+{
+ guard(sbtsi)(data);
+
+ if (data->detached)
+ return -ENODEV;
+
+ return sbtsi_xfer(data, reg, val, is_read);
+}
+
+static int apml_tsi_reg_xfer(struct sbtsi_data *data,
+ struct apml_tsi_xfer_msg __user *arg)
+{
+ struct apml_tsi_xfer_msg msg = { 0 };
+ int ret;
+
+ if (copy_from_user(&msg, arg, sizeof(struct apml_tsi_xfer_msg)))
+ return -EFAULT;
+
+ /*
+ * rflag is a boolean direction flag (0 = write, 1 = read). Reject
+ * any other value so the upper values stay reserved for future
+ * extensions instead of being silently treated as a read.
+ */
+ if (msg.pad || msg.rflag > 1)
+ return -EINVAL;
+
+ ret = sbtsi_xfer_ioctl(data, msg.reg_addr, &msg.data_in_out, msg.rflag);
+
+ if (msg.rflag && !ret) {
+ if (copy_to_user(arg, &msg, sizeof(struct apml_tsi_xfer_msg)))
+ return -EFAULT;
+ }
+ return ret;
+}
+
+static int sbtsi_open(struct inode *inode, struct file *fp)
+{
+ struct sbtsi_data *data;
+
+ data = container_of(fp->private_data, struct sbtsi_data, sbtsi_misc_dev);
+ scoped_guard(sbtsi, data) {
+ if (data->detached)
+ return -ENODEV;
+ }
+
+ kref_get(&data->kref);
+
+ return 0;
+}
+
+static int sbtsi_release(struct inode *inode, struct file *fp)
+{
+ struct sbtsi_data *data;
+
+ data = container_of(fp->private_data, struct sbtsi_data, sbtsi_misc_dev);
+ kref_put(&data->kref, sbtsi_data_release);
+ return 0;
+}
+
+static long sbtsi_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ struct sbtsi_data *data;
+
+ data = container_of(fp->private_data, struct sbtsi_data, sbtsi_misc_dev);
+ switch (cmd) {
+ case SBTSI_IOCTL_REG_XFER_CMD:
+ return apml_tsi_reg_xfer(data, argp);
+ default:
+ return -ENOTTY;
+ }
+}
+
+static const struct file_operations sbtsi_fops = {
+ .owner = THIS_MODULE,
+ .open = sbtsi_open,
+ .release = sbtsi_release,
+ .unlocked_ioctl = sbtsi_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+};
+
+int create_misc_tsi_device(struct sbtsi_data *data, struct device *dev)
+{
+ int ret;
+
+ data->sbtsi_misc_dev.name = devm_kasprintf(dev, GFP_KERNEL,
+ "sbtsi-%x", data->dev_addr);
+ if (!data->sbtsi_misc_dev.name)
+ return -ENOMEM;
+ data->sbtsi_misc_dev.minor = MISC_DYNAMIC_MINOR;
+ data->sbtsi_misc_dev.fops = &sbtsi_fops;
+ data->sbtsi_misc_dev.parent = dev;
+ data->sbtsi_misc_dev.nodename = devm_kasprintf(dev, GFP_KERNEL,
+ "sbtsi-%x", data->dev_addr);
+ if (!data->sbtsi_misc_dev.nodename)
+ return -ENOMEM;
+ data->sbtsi_misc_dev.mode = 0600;
+
+ ret = misc_register(&data->sbtsi_misc_dev);
+ if (ret)
+ return ret;
+
+ return 0;
+}
diff --git a/drivers/misc/amd-sbi/tsi-core.h b/drivers/misc/amd-sbi/tsi-core.h
new file mode 100644
index 000000000000..4cf55c46230e
--- /dev/null
+++ b/drivers/misc/amd-sbi/tsi-core.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * AMD SBTSI core driver private definitions.
+ *
+ * Copyright (C) 2026 Advanced Micro Devices, Inc.
+ */
+
+#ifndef _LINUX_TSI_CORE_H_
+#define _LINUX_TSI_CORE_H_
+
+#include <linux/cache.h>
+#include <linux/misc/tsi.h>
+
+/**
+ * struct sbtsi_i3c_priv - per-device state for I3C SBTSI (includes DMA-safe buffers)
+ * @data: public device state exposed via dev_set_drvdata()
+ * @tx: outgoing I3C bytes (DMA_TO_DEVICE); [0] register address, [1] value
+ * @rx: incoming I3C data byte (DMA_FROM_DEVICE)
+ */
+struct sbtsi_i3c_priv {
+ struct sbtsi_data data;
+ u8 tx[2];
+ u8 rx __aligned(ARCH_DMA_MINALIGN);
+};
+
+int create_misc_tsi_device(struct sbtsi_data *data, struct device *dev);
+
+void sbtsi_data_release(struct kref *kref);
+#endif /* _LINUX_TSI_CORE_H_ */
diff --git a/drivers/misc/amd-sbi/tsi.c b/drivers/misc/amd-sbi/tsi.c
new file mode 100644
index 000000000000..f06f417f451c
--- /dev/null
+++ b/drivers/misc/amd-sbi/tsi.c
@@ -0,0 +1,234 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * tsi.c - AMD SBTSI I2C/I3C core driver. Probes the SBTSI device over I2C/I3C
+ * and publishes an auxiliary device on the auxiliary bus.
+ *
+ * Copyright (C) 2026 Advanced Micro Devices, Inc.
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/bitfield.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include "tsi-core.h"
+
+#define SBTSI_REG_CONFIG 0x03 /* RO */
+
+/*
+ * Bit for reporting value with temperature measurement range.
+ * bit == 0: Use default temperature range (0C to 255.875C).
+ * bit == 1: Use extended temperature range (-49C to +206.875C).
+ */
+#define SBTSI_CONFIG_EXT_RANGE_SHIFT 2
+
+/*
+ * ReadOrder bit specifies the reading order of integer and decimal part of
+ * CPU temperature for atomic reads. If bit == 0, reading integer part triggers
+ * latching of the decimal part, so integer part should be read first.
+ */
+#define SBTSI_CONFIG_READ_ORDER_SHIFT 5
+
+static void sbtsi_adev_release(struct device *dev)
+{
+ kfree(to_auxiliary_dev(dev));
+}
+
+static void sbtsi_unregister_hwmon_adev(void *_adev)
+{
+ struct auxiliary_device *adev = _adev;
+
+ auxiliary_device_delete(adev);
+ auxiliary_device_uninit(adev);
+}
+
+static void sbtsi_misc_unregister(void *arg)
+{
+ struct sbtsi_data *data = arg;
+
+ misc_deregister(&data->sbtsi_misc_dev);
+
+ guard(sbtsi)(data);
+ data->detached = true;
+}
+
+static void sbtsi_driver_unref(void *arg)
+{
+ struct sbtsi_data *data = arg;
+
+ kref_put(&data->kref, sbtsi_data_release);
+}
+
+/*
+ * Create and publish an auxiliary device. The hwmon driver in
+ * drivers/hwmon/sbtsi_temp.c binds to this device.
+ *
+ * @dev: I2C device (parent of the auxiliary device)
+ * @dev_addr: I2C address — used as the auxiliary device instance ID so that
+ * each socket gets a unique name.
+ */
+static int sbtsi_create_hwmon_adev(struct device *dev, u8 dev_addr)
+{
+ struct auxiliary_device *adev;
+ int ret;
+
+ adev = kzalloc_obj(*adev);
+ if (!adev)
+ return -ENOMEM;
+
+ adev->name = AMD_SBTSI_AUX_HWMON;
+ adev->id = dev_addr;
+ adev->dev.parent = dev;
+ adev->dev.release = sbtsi_adev_release;
+
+ ret = auxiliary_device_init(adev);
+ if (ret) {
+ kfree(adev);
+ return ret;
+ }
+
+ ret = __auxiliary_device_add(adev, AMD_SBTSI_ADEV);
+ if (ret) {
+ auxiliary_device_uninit(adev);
+ return ret;
+ }
+
+ return devm_add_action_or_reset(dev, sbtsi_unregister_hwmon_adev, adev);
+}
+
+static int sbtsi_probe_common(struct device *dev, struct sbtsi_data *data)
+{
+ u8 val;
+ int err;
+
+ mutex_init(&data->lock);
+ kref_init(&data->kref);
+
+ err = devm_add_action_or_reset(dev, sbtsi_driver_unref, data);
+ if (err)
+ return err;
+
+ err = sbtsi_xfer(data, SBTSI_REG_CONFIG, &val, true);
+ if (err)
+ return err;
+
+ data->ext_range_mode = FIELD_GET(BIT(SBTSI_CONFIG_EXT_RANGE_SHIFT), val);
+ data->read_order = FIELD_GET(BIT(SBTSI_CONFIG_READ_ORDER_SHIFT), val);
+
+ dev_set_drvdata(dev, data);
+ err = sbtsi_create_hwmon_adev(dev, data->dev_addr);
+ if (err < 0)
+ return err;
+
+ err = create_misc_tsi_device(data, dev);
+ if (err)
+ return err;
+
+ return devm_add_action_or_reset(dev, sbtsi_misc_unregister, data);
+}
+
+static int sbtsi_i2c_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct sbtsi_data *data;
+
+ data = kzalloc_obj(*data);
+ if (!data)
+ return -ENOMEM;
+
+ data->is_i3c = false;
+ data->client = client;
+
+ /* In a multi-socket system, devices that are otherwise identical do not
+ * share the same static address; each instance resides at a unique I2C
+ * client address on the same or different bus. Use the I2C client
+ * address as the auxiliary device instance ID to ensure each socket
+ * receives a distinct auxiliary device name.
+ */
+ data->dev_addr = client->addr;
+ return sbtsi_probe_common(dev, data);
+}
+
+static const struct i2c_device_id sbtsi_id[] = {
+ { .name = "sbtsi" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, sbtsi_id);
+
+static const struct of_device_id __maybe_unused sbtsi_of_match[] = {
+ {
+ .compatible = "amd,sbtsi",
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(of, sbtsi_of_match);
+
+static struct i2c_driver sbtsi_driver = {
+ .driver = {
+ .name = "sbtsi-i2c",
+ .of_match_table = of_match_ptr(sbtsi_of_match),
+ },
+ .probe = sbtsi_i2c_probe,
+ .id_table = sbtsi_id,
+};
+
+static int sbtsi_i3c_probe(struct i3c_device *i3cdev)
+{
+ struct device *dev = i3cdev_to_dev(i3cdev);
+ struct i3c_device_info devinfo;
+ struct sbtsi_i3c_priv *i3c_priv;
+ struct sbtsi_data *data;
+
+ /*
+ * AMD OOB devices differ on basis of Instance ID,
+ * for SBTSI, instance ID is 0.
+ * As the device Id match is not on basis of Instance ID,
+ * add the below check to probe the SBTSI device only and
+ * not other OOB devices.
+ */
+ i3c_device_get_info(i3cdev, &devinfo);
+ if (I3C_PID_INSTANCE_ID(devinfo.pid) != 0)
+ return -ENXIO;
+
+ i3c_priv = kzalloc_obj(*i3c_priv);
+ if (!i3c_priv)
+ return -ENOMEM;
+
+ data = &i3c_priv->data;
+ data->i3cdev = i3cdev;
+ data->is_i3c = true;
+ /*
+ * In a multi-socket system, otherwise identical devices do not share
+ * the same address; each instance is enumerated with a distinct dynamic
+ * (assigned) address on the I3C bus. Use that address (passed in as
+ * dev_addr) as the auxiliary device instance ID so that every socket
+ * gets a unique auxiliary device name.
+ */
+ data->dev_addr = devinfo.dyn_addr;
+
+ return sbtsi_probe_common(dev, data);
+}
+
+static const struct i3c_device_id sbtsi_i3c_id[] = {
+ /* PID for AMD SBTSI device */
+ I3C_DEVICE_EXTRA_INFO(0x112, 0x0, 0x1, NULL), /* Socket:0, Turin and Genoa */
+ I3C_DEVICE_EXTRA_INFO(0x0, 0x0, 0x118, NULL), /* Socket:0, Venice */
+ I3C_DEVICE_EXTRA_INFO(0x0, 0x100, 0x118, NULL), /* Socket:1, Venice */
+ I3C_DEVICE_EXTRA_INFO(0x112, 0x0, 0x119, NULL), /* Socket:0, Venice */
+ I3C_DEVICE_EXTRA_INFO(0x112, 0x100, 0x119, NULL), /* Socket:1, Venice */
+ {}
+};
+MODULE_DEVICE_TABLE(i3c, sbtsi_i3c_id);
+
+static struct i3c_driver sbtsi_i3c_driver = {
+ .driver = {
+ .name = "sbtsi-i3c",
+ },
+ .probe = sbtsi_i3c_probe,
+ .id_table = sbtsi_i3c_id,
+};
+
+module_i3c_i2c_driver(sbtsi_i3c_driver, &sbtsi_driver);
+
+MODULE_DESCRIPTION("AMD SB-TSI I2C/I3C core driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/misc/bcm-vk/bcm_vk_dev.c b/drivers/misc/bcm-vk/bcm_vk_dev.c
index 5773ffb46f0f..54b40b4d556d 100644
--- a/drivers/misc/bcm-vk/bcm_vk_dev.c
+++ b/drivers/misc/bcm-vk/bcm_vk_dev.c
@@ -1370,21 +1370,15 @@ static int bcm_vk_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
err = devm_request_irq(dev, pci_irq_vector(pdev, vk->num_irqs),
bcm_vk_msgq_irqhandler,
IRQF_SHARED, DRV_MODULE_NAME, vk);
- if (err) {
- dev_err(dev, "failed to request msgq IRQ %d for MSIX %d\n",
- pdev->irq + vk->num_irqs, vk->num_irqs + 1);
+ if (err)
goto err_irq;
- }
}
/* one irq for notification from VK */
err = devm_request_irq(dev, pci_irq_vector(pdev, vk->num_irqs),
bcm_vk_notf_irqhandler,
IRQF_SHARED, DRV_MODULE_NAME, vk);
- if (err) {
- dev_err(dev, "failed to request notf IRQ %d for MSIX %d\n",
- pdev->irq + vk->num_irqs, vk->num_irqs + 1);
+ if (err)
goto err_irq;
- }
vk->num_irqs++;
for (i = 0;
@@ -1393,11 +1387,8 @@ static int bcm_vk_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
err = devm_request_irq(dev, pci_irq_vector(pdev, vk->num_irqs),
bcm_vk_tty_irqhandler,
IRQF_SHARED, DRV_MODULE_NAME, vk);
- if (err) {
- dev_err(dev, "failed request tty IRQ %d for MSIX %d\n",
- pdev->irq + vk->num_irqs, vk->num_irqs + 1);
+ if (err)
goto err_irq;
- }
bcm_vk_tty_set_irq_enabled(vk, i);
}
diff --git a/drivers/misc/bcm-vk/bcm_vk_msg.c b/drivers/misc/bcm-vk/bcm_vk_msg.c
index 3916ec07ecad..17114092a284 100644
--- a/drivers/misc/bcm-vk/bcm_vk_msg.c
+++ b/drivers/misc/bcm-vk/bcm_vk_msg.c
@@ -9,6 +9,7 @@
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/module.h>
+#include <linux/overflow.h>
#include <linux/poll.h>
#include <linux/sizes.h>
#include <linux/spinlock.h>
@@ -108,7 +109,8 @@ u32 msgq_avail_space(const struct bcm_vk_msgq __iomem *msgq,
bool bcm_vk_drv_access_ok(struct bcm_vk *vk)
{
- return (!!atomic_read(&vk->msgq_inited));
+ /* Pair with the release store after message queue initialization. */
+ return !!atomic_read_acquire(&vk->msgq_inited);
}
void bcm_vk_set_host_alert(struct bcm_vk *vk, u32 bit_mask)
@@ -501,7 +503,8 @@ int bcm_vk_sync_msgq(struct bcm_vk *vk, bool force_sync)
msgq++;
}
}
- atomic_set(&vk->msgq_inited, 1);
+ /* Publish message queue info before allowing driver access. */
+ atomic_set_release(&vk->msgq_inited, 1);
return ret;
}
@@ -1088,6 +1091,7 @@ ssize_t bcm_vk_write(struct file *p_file,
u32 q_num;
u32 msg_size;
u32 msgq_size;
+ size_t entry_size;
if (!bcm_vk_drv_access_ok(vk))
return -EPERM;
@@ -1095,20 +1099,26 @@ ssize_t bcm_vk_write(struct file *p_file,
dev_dbg(dev, "Msg count %zu\n", count);
/* first, do sanity check where count should be multiple of basic blk */
- if (count & (VK_MSGQ_BLK_SIZE - 1)) {
- dev_err(dev, "Failure with size %zu not multiple of %zu\n",
+ if (!count || count & (VK_MSGQ_BLK_SIZE - 1)) {
+ dev_err(dev, "Failure with size %zu not a positive multiple of %zu\n",
count, VK_MSGQ_BLK_SIZE);
rc = -EINVAL;
goto write_err;
}
+ if (check_add_overflow(sizeof(*entry), count, &entry_size) ||
+ check_add_overflow(entry_size, vk->ib_sgl_size, &entry_size)) {
+ rc = -EOVERFLOW;
+ goto write_err;
+ }
+
/* allocate the work entry + buffer for size count and inband sgl */
- entry = kzalloc(sizeof(*entry) + count + vk->ib_sgl_size,
- GFP_KERNEL);
+ entry = kzalloc(entry_size, GFP_KERNEL);
if (!entry) {
rc = -ENOMEM;
goto write_err;
}
+ entry->to_v_blks = count >> VK_MSGQ_BLK_SZ_SHIFT;
/* now copy msg from user space, and then formulate the work entry */
if (copy_from_user(&entry->to_v_msg[0], buf, count)) {
@@ -1116,7 +1126,6 @@ ssize_t bcm_vk_write(struct file *p_file,
goto write_free_ent;
}
- entry->to_v_blks = count >> VK_MSGQ_BLK_SZ_SHIFT;
entry->ctx = ctx;
/* do a check on the blk size which could not exceed queue space */
@@ -1353,4 +1362,3 @@ void bcm_vk_msg_remove(struct bcm_vk *vk)
bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->to_v_msg_chan, NULL);
bcm_vk_drain_all_pend(&vk->pdev->dev, &vk->to_h_msg_chan, NULL);
}
-
diff --git a/drivers/misc/cardreader/rtsx_pcr.c b/drivers/misc/cardreader/rtsx_pcr.c
index c4d54ca2fa80..c6e602523538 100644
--- a/drivers/misc/cardreader/rtsx_pcr.c
+++ b/drivers/misc/cardreader/rtsx_pcr.c
@@ -1196,6 +1196,8 @@ static int rtsx_pci_init_hw(struct rtsx_pcr *pcr)
/* Gating real mcu clock */
err = rtsx_pci_write_register(pcr, RTS5261_FW_CFG1,
RTS5261_MCU_CLOCK_GATING, 0);
+ if (err < 0)
+ return err;
err = rtsx_pci_write_register(pcr, RTS5261_REG_FPDCTL,
SSC_POWER_DOWN, 0);
} else {
diff --git a/drivers/misc/enclosure.c b/drivers/misc/enclosure.c
index cf6382981777..de457378c501 100644
--- a/drivers/misc/enclosure.c
+++ b/drivers/misc/enclosure.c
@@ -184,8 +184,8 @@ EXPORT_SYMBOL_GPL(enclosure_unregister);
static void enclosure_link_name(struct enclosure_component *cdev, char *name)
{
- strcpy(name, "enclosure_device:");
- strcat(name, dev_name(&cdev->cdev));
+ snprintf(name, ENCLOSURE_NAME_SIZE, "enclosure_device:%s",
+ dev_name(&cdev->cdev));
}
static void enclosure_remove_links(struct enclosure_component *cdev)
diff --git a/drivers/misc/genwqe/card_utils.c b/drivers/misc/genwqe/card_utils.c
index a2c4a9b4f871..e3b5fb337182 100644
--- a/drivers/misc/genwqe/card_utils.c
+++ b/drivers/misc/genwqe/card_utils.c
@@ -413,7 +413,7 @@ int genwqe_setup_sgl(struct genwqe_dev *cd, struct genwqe_sgl *sgl,
size -= size_to_map;
map_offs = 0;
- if (prev_daddr == daddr) {
+ if (last_s && prev_daddr == daddr) {
u32 prev_len = be32_to_cpu(last_s->len);
/* pr_info("daddr combining: "
diff --git a/drivers/misc/hi6421v600-irq.c b/drivers/misc/hi6421v600-irq.c
index 5ba40222eb12..5f65ca91b342 100644
--- a/drivers/misc/hi6421v600-irq.c
+++ b/drivers/misc/hi6421v600-irq.c
@@ -274,11 +274,8 @@ static int hi6421v600_irq_probe(struct platform_device *pdev)
NULL,
IRQF_TRIGGER_LOW | IRQF_SHARED | IRQF_NO_SUSPEND,
"pmic", priv);
- if (ret < 0) {
- dev_err(dev, "Failed to start IRQ handling thread: error %d\n",
- ret);
+ if (ret < 0)
return ret;
- }
return 0;
}
diff --git a/drivers/misc/hpilo.c b/drivers/misc/hpilo.c
index ff3f03ea577e..9b54a81b43d8 100644
--- a/drivers/misc/hpilo.c
+++ b/drivers/misc/hpilo.c
@@ -160,11 +160,16 @@ static int ilo_pkt_dequeue(struct ilo_hwinfo *hw, struct ccb *ccb,
ret = fifo_dequeue(hw, fifobar, &entry);
if (ret) {
+ int pkt_len;
+
pkt_id = get_entry_id(entry);
+ pkt_len = get_entry_len(entry);
+ if (pkt_id >= NR_QENTRY || pkt_len > desc_mem_sz(1))
+ return 0;
if (id)
*id = pkt_id;
if (len)
- *len = get_entry_len(entry);
+ *len = pkt_len;
if (pkt)
*pkt = (void *)(desc + desc_mem_sz(pkt_id));
}
diff --git a/drivers/misc/ibmasm/r_heartbeat.c b/drivers/misc/ibmasm/r_heartbeat.c
index 21c9b6a6f2c3..8d3fa0ce4b6c 100644
--- a/drivers/misc/ibmasm/r_heartbeat.c
+++ b/drivers/misc/ibmasm/r_heartbeat.c
@@ -51,12 +51,12 @@ int ibmasm_start_reverse_heartbeat(struct service_processor *sp, struct reverse_
int times_failed = 0;
int result = 1;
- cmd = ibmasm_new_command(sp, sizeof rhb_dot_cmd);
+ cmd = ibmasm_new_command(sp, sizeof(rhb_dot_cmd));
if (!cmd)
return -ENOMEM;
while (times_failed < 3) {
- memcpy(cmd->buffer, (void *)&rhb_dot_cmd, sizeof rhb_dot_cmd);
+ memcpy(cmd->buffer, (void *)&rhb_dot_cmd, sizeof(rhb_dot_cmd));
cmd->status = IBMASM_CMD_PENDING;
ibmasm_exec_command(sp, cmd);
ibmasm_wait_for_response(cmd, IBMASM_CMD_TIMEOUT_NORMAL);
diff --git a/drivers/misc/ibmvmc.c b/drivers/misc/ibmvmc.c
index beb18c34f20d..28bf4c352317 100644
--- a/drivers/misc/ibmvmc.c
+++ b/drivers/misc/ibmvmc.c
@@ -1040,7 +1040,7 @@ static ssize_t ibmvmc_write(struct file *file, const char *buffer,
size_t count, loff_t *ppos)
{
struct inode *inode;
- struct ibmvmc_buffer *vmc_buffer;
+ struct ibmvmc_buffer *vmc_buffer = NULL;
struct ibmvmc_file_session *session;
struct crq_server_adapter *adapter;
struct ibmvmc_hmc *hmc;
@@ -1130,9 +1130,15 @@ static ssize_t ibmvmc_write(struct file *file, const char *buffer,
dev_dbg(adapter->dev, "write: file = 0x%lx, count = 0x%lx\n",
(unsigned long)file, (unsigned long)count);
- ibmvmc_send_msg(adapter, vmc_buffer, hmc, count);
+ if (ibmvmc_send_msg(adapter, vmc_buffer, hmc, count)) {
+ ret = -EIO;
+ goto out;
+ }
+ vmc_buffer = NULL;
ret = p - buffer;
out:
+ if (vmc_buffer)
+ vmc_buffer->free = 1;
spin_unlock_irqrestore(&hmc->lock, flags);
return (ssize_t)(ret);
}
@@ -1653,6 +1659,13 @@ static int ibmvmc_recv_msg(struct crq_server_adapter *adapter,
return -1;
}
+ if (msg_len > buffer->size) {
+ dev_err(adapter->dev, "Recv_msg: msg_len 0x%lx exceeds buffer size 0x%x\n",
+ msg_len, buffer->size);
+ spin_unlock_irqrestore(&hmc->lock, flags);
+ return -1;
+ }
+
/* RDMA the data into the partition. */
rc = h_copy_rdma(msg_len,
adapter->riobn,
diff --git a/drivers/misc/issei/Kconfig b/drivers/misc/issei/Kconfig
new file mode 100644
index 000000000000..12a3fd809969
--- /dev/null
+++ b/drivers/misc/issei/Kconfig
@@ -0,0 +1,29 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2023-2026 Intel Corporation
+config INTEL_SSEI
+ tristate "Intel Silicon Security Engine Interface"
+ help
+ The ISSEI (Intel Silicon Security Engine Interface)
+ subsystem provides a communication channel between the host and the
+ Silicon Security Engine.
+ Enable this driver to get SPDM and other features on Intel client CPUs
+ released in 2024 (Lunar Lake) or later.
+
+ If selected, the /dev/isseiX device will be created.
+ If in doubt, select N.
+
+if INTEL_SSEI
+
+config INTEL_SSEI_HW_HECI
+ tristate "Intel Silicon Security Engine Interface Hardware"
+ depends on X86 && PCI
+ help
+ HECI interface of communication channel between
+ the host and the Silicon Security Engine.
+
+ Implementation of ISSEI communication channel over
+ the HECI hardware PCI device.
+ This device is available on Intel client CPUs released in 2024
+ (Lunar Lake) or later.
+
+endif
diff --git a/drivers/misc/issei/Makefile b/drivers/misc/issei/Makefile
new file mode 100644
index 000000000000..8fac360c3342
--- /dev/null
+++ b/drivers/misc/issei/Makefile
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2023-2026 Intel Corporation
+ccflags-y += -DDEFAULT_SYMBOL_NAMESPACE='"INTEL_SSEI"'
+
+obj-$(CONFIG_INTEL_SSEI) += issei.o
+issei-objs += cdev.o
+issei-objs += dma.o
+issei-objs += fw_client.o
+issei-objs += host_client.o
+issei-objs += ham.o
+issei-objs += main.o
+
+obj-$(CONFIG_INTEL_SSEI_HW_HECI) += issei-heci.o
+issei-heci-objs := pci_heci.o
+issei-heci-objs += hw_heci.o
diff --git a/drivers/misc/issei/cdev.c b/drivers/misc/issei/cdev.c
new file mode 100644
index 000000000000..b7d65e2d0813
--- /dev/null
+++ b/drivers/misc/issei/cdev.c
@@ -0,0 +1,446 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2023-2026 Intel Corporation */
+#include <linux/atomic.h>
+#include <linux/cdev.h>
+#include <linux/cleanup.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/fs.h>
+#include <linux/issei.h>
+#include <linux/kobject.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/poll.h>
+#include <linux/sched/signal.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/wait.h>
+#include <linux/xarray.h>
+
+#include "issei_dev.h"
+#include "host_client.h"
+#include "cdev.h"
+
+struct class *issei_class;
+static dev_t issei_devt;
+
+#define ISSEI_MAX_DEVS MINORMASK
+
+static DEFINE_XARRAY_ALLOC(issei_minor_xa);
+
+static int issei_open(struct inode *inode, struct file *fp)
+{
+ struct issei_host_client *cl;
+ struct issei_device *idev;
+
+ xa_lock(&issei_minor_xa);
+ idev = xa_load(&issei_minor_xa, iminor(inode));
+ if (idev)
+ get_device(&idev->dev);
+ xa_unlock(&issei_minor_xa);
+ if (!idev)
+ return -ENODEV;
+
+ cl = issei_cl_create(idev, fp);
+ if (IS_ERR(cl)) {
+ put_device(&idev->dev);
+ return PTR_ERR(cl);
+ }
+ fp->private_data = cl;
+
+ return nonseekable_open(inode, fp);
+}
+
+static int issei_release(struct inode *inode, struct file *fp)
+{
+ struct issei_host_client *cl = fp->private_data;
+ struct issei_device *idev = cl->idev;
+
+ issei_cl_remove(cl);
+ put_device(&idev->dev);
+
+ return 0;
+}
+
+static long issei_ioctl(struct file *file, unsigned int cmd, unsigned long data)
+{
+ struct issei_host_client *cl = file->private_data;
+ struct issei_connect_client_data conn;
+ struct issei_device *idev = cl->idev;
+ int ret;
+
+ switch (cmd) {
+ case IOCTL_ISSEI_CONNECT_CLIENT:
+ dev_dbg(&idev->dev, "IOCTL_ISSEI_CONNECT_CLIENT\n");
+
+ if (idev->rst_state != ISSEI_RST_STATE_DONE) {
+ dev_dbg(&idev->dev, "Device is in transition\n");
+ return -ENODEV;
+ }
+
+ if (copy_from_user(&conn, (char __user *)data, sizeof(conn))) {
+ dev_dbg(&idev->dev, "failed to copy data from userland\n");
+ return -EFAULT;
+ }
+
+ ret = issei_cl_connect(cl, (uuid_t *)&conn.in_client_uuid,
+ &conn.out_client_properties.max_msg_length,
+ &conn.out_client_properties.protocol_version,
+ &conn.out_client_properties.flags);
+ if (ret)
+ return ret;
+
+ if (copy_to_user((char __user *)data, &conn, sizeof(conn))) {
+ dev_dbg(&idev->dev, "failed to copy data to userland\n");
+ issei_cl_disconnect(cl);
+ return -EFAULT;
+ }
+ return 0;
+
+ case IOCTL_ISSEI_DISCONNECT_CLIENT:
+ dev_dbg(&idev->dev, "IOCTL_ISSEI_DISCONNECT_CLIENT\n");
+
+ if (idev->rst_state != ISSEI_RST_STATE_DONE) {
+ dev_dbg(&idev->dev, "Device is in transition\n");
+ return -ENODEV;
+ }
+
+ return issei_cl_disconnect(cl);
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static ssize_t issei_write(struct file *file, const char __user *ubuf,
+ size_t length, loff_t *offset)
+{
+ struct issei_host_client *cl = file->private_data;
+ struct issei_device *idev = cl->idev;
+ ssize_t ret;
+
+ if (!length)
+ return 0;
+
+ if (idev->rst_state != ISSEI_RST_STATE_DONE) {
+ dev_dbg(&idev->dev, "Device is in transition\n");
+ return -EBUSY;
+ }
+
+ /* sanity check */
+ if (length > idev->dma.length.h2f) {
+ dev_dbg(&idev->dev, "Write is too big %zu > %zu\n",
+ length, idev->dma.length.h2f);
+ return -EFBIG;
+ }
+
+ u8 *buf __free(kfree) = memdup_user(ubuf, length);
+ if (IS_ERR(buf)) {
+ dev_dbg(&idev->dev, "failed to copy data from userland\n");
+ return PTR_ERR(buf);
+ }
+
+ do {
+ ret = issei_cl_write(cl, buf, length);
+ if (ret < 0 && ret != -EAGAIN)
+ return ret;
+ /* buf is consumed by issei_cl_write on success */
+ if (ret >= 0)
+ retain_and_null_ptr(buf);
+ if (wait_event_interruptible(cl->write_wait, issei_cl_check_write(cl) != 1)) {
+ issei_cl_clean_all_wbuf(cl);
+ if (signal_pending(current))
+ return -EINTR;
+ return -ERESTARTSYS;
+ }
+ } while (ret == -EAGAIN);
+
+ return ret;
+}
+
+static ssize_t issei_read(struct file *file, char __user *ubuf,
+ size_t length, loff_t *offset)
+{
+ struct issei_host_client *cl = file->private_data;
+ struct issei_device *idev = cl->idev;
+ u8 *data = NULL;
+ ssize_t ret;
+
+ if (!length)
+ return 0;
+
+ if (idev->rst_state != ISSEI_RST_STATE_DONE) {
+ dev_dbg(&idev->dev, "Device is in transition\n");
+ return -EBUSY;
+ }
+
+ /* sanity check */
+ if (length > idev->dma.length.f2h) {
+ dev_dbg(&idev->dev, "Read is too big %zu > %zu\n",
+ length, idev->dma.length.f2h);
+ return -EFBIG;
+ }
+
+ ret = issei_cl_read(cl, &data, length);
+ if (ret < 0) {
+ if (ret != -ENOENT)
+ return ret;
+
+ if (wait_event_interruptible(cl->read_wait, issei_cl_check_read(cl) != 0)) {
+ if (signal_pending(current))
+ return -EINTR;
+ return -ERESTARTSYS;
+ }
+
+ ret = issei_cl_read(cl, &data, length);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (copy_to_user(ubuf, data, ret)) {
+ dev_dbg(&idev->dev, "failed to copy data to userland\n");
+ ret = -EFAULT;
+ } else {
+ *offset = 0;
+ }
+
+ kfree(data);
+
+ return ret;
+}
+
+static __poll_t issei_poll(struct file *file, poll_table *wait)
+{
+ __poll_t req_events = poll_requested_events(wait);
+ struct issei_host_client *cl = file->private_data;
+ struct issei_device *idev = cl->idev;
+ __poll_t mask = 0;
+ int ret;
+
+ if (idev->rst_state != ISSEI_RST_STATE_DONE) {
+ dev_dbg(&idev->dev, "Device is in transition\n");
+ return EPOLLERR;
+ }
+
+ if (req_events & (EPOLLIN | EPOLLRDNORM)) {
+ poll_wait(file, &cl->read_wait, wait);
+ ret = issei_cl_check_read(cl);
+ if (ret == 1)
+ mask |= EPOLLIN | EPOLLRDNORM;
+ else if (ret < 0)
+ mask |= EPOLLERR;
+ }
+
+ if (req_events & (EPOLLOUT | EPOLLWRNORM)) {
+ poll_wait(file, &cl->write_wait, wait);
+ ret = issei_cl_check_write(cl);
+ if (ret == 0)
+ mask |= EPOLLOUT | EPOLLWRNORM;
+ else if (ret < 0)
+ mask |= EPOLLERR;
+ }
+
+ return mask;
+}
+
+static ssize_t fw_ver_show(struct device *device,
+ struct device_attribute *attr, char *buf)
+{
+ struct issei_device *idev = dev_get_drvdata(device);
+
+ return sysfs_emit(buf, "%u.%u.%u.%u\n", idev->fw_version[0], idev->fw_version[1],
+ idev->fw_version[2], idev->fw_version[3]);
+}
+static DEVICE_ATTR_RO(fw_ver);
+
+static struct attribute *issei_attrs[] = {
+ &dev_attr_fw_ver.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(issei);
+
+static const struct file_operations issei_fops = {
+ .owner = THIS_MODULE,
+ .open = issei_open,
+ .unlocked_ioctl = issei_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .write = issei_write,
+ .read = issei_read,
+ .release = issei_release,
+ .poll = issei_poll,
+};
+
+static void issei_device_release(struct device *dev)
+{
+ kfree(dev_get_drvdata(dev));
+}
+
+static void issei_device_init(struct issei_device *idev, struct device *parent,
+ const struct issei_dma_length *dma_length,
+ const struct issei_hw_ops *ops)
+{
+ idev->parent = parent;
+ idev->power_down = false;
+ init_waitqueue_head(&idev->wait_has_data);
+ idev->has_data = false;
+ init_waitqueue_head(&idev->wait_rst_state);
+ idev->rst_state = ISSEI_RST_STATE_INIT;
+
+ mutex_init(&idev->client_lock);
+ INIT_LIST_HEAD(&idev->host_client_list);
+ idev->host_client_last_id = 0;
+ idev->host_client_count = 0;
+ INIT_LIST_HEAD(&idev->fw_client_list);
+ INIT_LIST_HEAD(&idev->write_queue);
+ idev->last_write_ts = 0;
+
+ idev->dma.length = *dma_length;
+
+ idev->ops = ops;
+}
+
+/**
+ * issei_register: register issei character device
+ * @hw_size: size of the hardware structure to allocate
+ * @parent: parent device
+ * @dma_length: structure with DMA sizes
+ * @ops: hardware-related operations
+ *
+ * Return: pointer allocated to issei_device structure, error on failure
+ */
+struct issei_device *issei_register(size_t hw_size, struct device *parent,
+ const struct issei_dma_length *dma_length,
+ const struct issei_hw_ops *ops)
+{
+ struct issei_device *idev;
+ u32 minor;
+ int ret, devno;
+
+ idev = kzalloc(sizeof(*idev) + hw_size, GFP_KERNEL);
+ if (!idev)
+ return ERR_PTR(-ENOMEM);
+
+ issei_device_init(idev, parent, dma_length, ops);
+
+ ret = xa_alloc(&issei_minor_xa, &minor, idev, XA_LIMIT(0, ISSEI_MAX_DEVS), GFP_KERNEL);
+ if (ret < 0) {
+ dev_err(&idev->dev, "Failed to allocate minor. ret = %d\n", ret);
+ kfree(idev);
+ return ERR_PTR(ret);
+ }
+
+ idev->minor = minor;
+ devno = MKDEV(MAJOR(issei_devt), idev->minor);
+
+ device_initialize(&idev->dev);
+ idev->dev.devt = devno;
+ idev->dev.class = issei_class;
+ idev->dev.parent = parent;
+ idev->dev.groups = issei_groups;
+ idev->dev.release = issei_device_release;
+ dev_set_drvdata(&idev->dev, idev);
+
+ idev->cdev = cdev_alloc();
+ if (!idev->cdev) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ idev->cdev->ops = &issei_fops;
+ if (parent->driver)
+ idev->cdev->owner = parent->driver->owner;
+ cdev_set_parent(idev->cdev, &idev->dev.kobj);
+
+ ret = cdev_add(idev->cdev, devno, 1);
+ if (ret) {
+ dev_err(parent, "unable to add device %d:%u ret = %d\n",
+ MAJOR(issei_devt), idev->minor, ret);
+ goto err_del_cdev;
+ }
+
+ ret = dev_set_name(&idev->dev, "issei%u", idev->minor);
+ if (ret) {
+ dev_err(parent, "unable to set name to device %d:%u ret = %d\n",
+ MAJOR(issei_devt), idev->minor, ret);
+ goto err_del_cdev;
+ }
+
+ ret = device_add(&idev->dev);
+ if (ret) {
+ dev_err(parent, "unable to add device %d:%u ret = %d\n",
+ MAJOR(issei_devt), idev->minor, ret);
+ goto err_del_cdev;
+ }
+
+ idev->fw_clients = kset_create_and_add("fw_clients", NULL, &idev->dev.kobj);
+ if (!idev->fw_clients) {
+ ret = -ENOMEM;
+ goto err_del_dev;
+ }
+
+ return idev;
+
+err_del_dev:
+ device_del(&idev->dev);
+err_del_cdev:
+ cdev_del(idev->cdev);
+err:
+ put_device(&idev->dev);
+ xa_erase(&issei_minor_xa, minor);
+
+ return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(issei_register);
+
+/**
+ * issei_deregister: remove issei character device
+ * @idev: the device structure
+ */
+void issei_deregister(struct issei_device *idev)
+{
+ u32 minor = idev->minor;
+
+ cdev_del(idev->cdev);
+
+ kset_unregister(idev->fw_clients);
+
+ device_del(&idev->dev);
+
+ put_device(&idev->dev);
+
+ xa_erase(&issei_minor_xa, minor);
+}
+EXPORT_SYMBOL_GPL(issei_deregister);
+
+static int __init issei_cdev_init(void)
+{
+ int ret;
+
+ issei_class = class_create("issei");
+ if (IS_ERR(issei_class)) {
+ pr_err("couldn't create class\n");
+ return PTR_ERR(issei_class);
+ }
+
+ ret = alloc_chrdev_region(&issei_devt, 0, ISSEI_MAX_DEVS, "issei");
+ if (ret < 0) {
+ pr_err("unable to allocate char dev region\n");
+ class_destroy(issei_class);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void __exit issei_cdev_exit(void)
+{
+ unregister_chrdev_region(issei_devt, ISSEI_MAX_DEVS);
+ class_destroy(issei_class);
+}
+
+module_init(issei_cdev_init);
+module_exit(issei_cdev_exit);
+
+MODULE_DESCRIPTION("Intel(R) Silicon Security Engine Interface");
+MODULE_LICENSE("GPL");
diff --git a/drivers/misc/issei/cdev.h b/drivers/misc/issei/cdev.h
new file mode 100644
index 000000000000..30075a624f2d
--- /dev/null
+++ b/drivers/misc/issei/cdev.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2023-2026 Intel Corporation */
+#ifndef _ISSEI_CDEV_H_
+#define _ISSEI_CDEV_H_
+
+struct device;
+struct issei_device;
+struct issei_dma_length;
+struct issei_hw_ops;
+
+struct issei_device *issei_register(size_t hw_size, struct device *parent,
+ const struct issei_dma_length *dma_length,
+ const struct issei_hw_ops *ops);
+void issei_deregister(struct issei_device *idev);
+
+#endif /* _ISSEI_CDEV_H_ */
diff --git a/drivers/misc/issei/dma.c b/drivers/misc/issei/dma.c
new file mode 100644
index 000000000000..457d28f31c06
--- /dev/null
+++ b/drivers/misc/issei/dma.c
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2023-2026 Intel Corporation */
+#include <linux/dev_printk.h>
+#include <linux/dma-mapping.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/timekeeping.h>
+
+#include "issei_dev.h"
+#include "hw_msg.h"
+
+static inline size_t __issei_dma_size(const struct issei_dma *dma)
+{
+ return dma->length.h2f + dma->length.f2h + dma->length.ctl;
+}
+
+/**
+ * issei_dmam_setup - setup DMA buffer and clean it
+ * @idev: issei device object
+ *
+ * Return: 0 on success, <0 on failures
+ */
+int issei_dmam_setup(struct issei_device *idev)
+{
+ struct issei_dma *dma = &idev->dma;
+ size_t size;
+
+ size = __issei_dma_size(dma);
+ if (!size)
+ return -EINVAL;
+
+ if (!dma->vaddr)
+ dma->vaddr = dmam_alloc_coherent(idev->parent, size, &dma->daddr,
+ GFP_KERNEL | __GFP_ZERO);
+ if (dma->vaddr)
+ memset(dma->vaddr, 0, size);
+ return dma->vaddr ? 0 : -ENOMEM;
+}
+
+static inline struct control_buffer *__dma_get_ctl_buf(struct issei_dma *dma)
+{
+ return dma->vaddr + dma->length.h2f + dma->length.f2h;
+}
+
+static bool __issei_dma_is_read_busy(struct issei_dma *dma)
+{
+ struct control_buffer *ctl = __dma_get_ctl_buf(dma);
+
+ return ctl->f2h_counter_wr != ctl->f2h_counter_rd;
+}
+
+static bool __issei_dma_is_write_busy(struct issei_dma *dma)
+{
+ struct control_buffer *ctl = __dma_get_ctl_buf(dma);
+
+ return ctl->h2f_counter_wr != ctl->h2f_counter_rd;
+}
+
+static void __issei_dma_read_finalize(struct issei_device *idev)
+{
+ struct control_buffer *ctl = __dma_get_ctl_buf(&idev->dma);
+
+ dev_dbg(&idev->dev, "ctl->f2h_counter_rd %u\n", ctl->f2h_counter_rd);
+ /* No need to check overflow - the firmware counters overflow the same way */
+ ctl->f2h_counter_rd++;
+}
+
+static void __issei_dma_write_finalize(struct issei_device *idev)
+{
+ struct control_buffer *ctl = __dma_get_ctl_buf(&idev->dma);
+
+ dev_dbg(&idev->dev, "ctl->h2f_counter_wr %u\n", ctl->h2f_counter_wr);
+ /* No need to check overflow - the firmware counters overflow the same way */
+ ctl->h2f_counter_wr++;
+}
+
+/**
+ * issei_dma_write - write data package to DMA
+ * @idev: issei device object
+ * @data: data atructure
+ *
+ * Return: 0 on success, <0 on failures
+ */
+int issei_dma_write(struct issei_device *idev, const struct issei_dma_data *data)
+{
+ u8 *write_buf = idev->dma.vaddr;
+ struct ham_message_header *hdr = (struct ham_message_header *)write_buf;
+
+ if (data->length > idev->dma.length.h2f - sizeof(*hdr)) {
+ dev_err(&idev->dev, "Message is too big\n");
+ return -EMSGSIZE;
+ }
+
+ if (__issei_dma_is_write_busy(&idev->dma)) {
+ if (ktime_ms_delta(ktime_get(), idev->last_write_ts) > ISSEI_WRITE_TIMEOUT_MSEC) {
+ dev_err(&idev->dev, "Write stuck in queue\n");
+ return -EIO;
+ }
+ dev_info(&idev->dev, "Write is busy\n");
+ return -EBUSY;
+ }
+
+ hdr->length = data->length;
+ hdr->fw_id = data->fw_id;
+ hdr->host_id = data->host_id;
+ hdr->flags = data->flags;
+ hdr->status = data->status;
+ hdr->reserved = 0;
+
+ memcpy(write_buf + sizeof(*hdr), data->buf, data->length);
+
+ __issei_dma_write_finalize(idev);
+ idev->last_write_ts = ktime_get();
+ return 0;
+}
+
+/**
+ * issei_dma_read - read data package from DMA
+ * @idev: issei device object
+ * @data: data atructure
+ *
+ * Return: %0 on success, <0 on failures
+ */
+int issei_dma_read(struct issei_device *idev, struct issei_dma_data *data)
+{
+ u8 *read_buf = idev->dma.vaddr + idev->dma.length.h2f;
+ struct ham_message_header *hdr = (struct ham_message_header *)read_buf;
+
+ if (!__issei_dma_is_read_busy(&idev->dma)) {
+ dev_dbg(&idev->dev, "Nothing to read\n");
+ return -ENODATA;
+ }
+
+ dev_dbg(&idev->dev, "Reading header\n");
+ data->length = hdr->length;
+ data->fw_id = hdr->fw_id;
+ data->host_id = hdr->host_id;
+ data->flags = hdr->flags;
+ data->status = hdr->status;
+
+ if (data->length > idev->dma.length.f2h - sizeof(*hdr)) {
+ dev_err(&idev->dev, "Message length %u is bigger than buffer %zu\n",
+ data->length, idev->dma.length.f2h - sizeof(*hdr));
+ return -EIO;
+ }
+
+ dev_dbg(&idev->dev, "Reading data (size %u)\n", data->length);
+ data->buf = kmemdup(read_buf + sizeof(*hdr), data->length, GFP_KERNEL);
+ if (!data->buf)
+ return -ENOMEM;
+ __issei_dma_read_finalize(idev);
+ return 0;
+}
diff --git a/drivers/misc/issei/dma.h b/drivers/misc/issei/dma.h
new file mode 100644
index 000000000000..e6b7aeb50ae6
--- /dev/null
+++ b/drivers/misc/issei/dma.h
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2023-2026 Intel Corporation */
+#ifndef _ISSEI_DMA_H_
+#define _ISSEI_DMA_H_
+
+#include <linux/types.h>
+
+struct issei_device;
+
+/**
+ * struct issei_dma_length - sizes of DMA memory portions
+ * @h2f: host to firmware buffer size
+ * @f2h: firmware to host buffer size
+ * @ctl: control buffer size
+ */
+struct issei_dma_length {
+ size_t h2f;
+ size_t f2h;
+ size_t ctl;
+};
+
+/**
+ * struct issei_dma - DMA memory structure
+ * @vaddr: virtual address
+ * @daddr: physical address
+ * @length: memory sizes structure
+ */
+struct issei_dma {
+ void *vaddr;
+ dma_addr_t daddr;
+ struct issei_dma_length length;
+};
+
+/* Operation statuses */
+#define HAMS_SUCCESS 0x00
+#define HAMS_PROTOCOL_NOT_SUPPORTED 0x01
+#define HAMS_DEPRECATED_BUS_MSG 0x02
+#define HAMS_CLIENT_NOT_EXISTS 0x03
+#define HAMS_MSG_TOO_BIG 0x04
+#define HAMS_MSG_NOT_CONSUMED 0x05
+#define HAMS_CORRUPTED_BUS_MSG 0x06
+#define HAMS_CORRUPTED_HEADER 0x07
+#define HAMS_INVALID_LENGTH 0x08
+#define HAMS_SHARED_MEMORY_SIZE_UNSUPPORTED 0x09
+#define HAMS_GENERAL_FATAL_ERROR 0xff
+
+/**
+ * struct issei_dma_data - data passed through channel
+ * @fw_id: firmware client id
+ * @host_id: host client id
+ * @flags: flags bitmap
+ * @status: operation status
+ * @length: data length
+ * @buf: pointer to data buffer
+ */
+struct issei_dma_data {
+ u16 fw_id;
+ u16 host_id;
+ u32 flags;
+ u32 status;
+ u32 length;
+ void *buf;
+};
+
+int issei_dmam_setup(struct issei_device *idev);
+int issei_dma_write(struct issei_device *idev, const struct issei_dma_data *data);
+int issei_dma_read(struct issei_device *idev, struct issei_dma_data *data);
+
+#endif /*_ISSEI_DMA_H_*/
diff --git a/drivers/misc/issei/fw_client.c b/drivers/misc/issei/fw_client.c
new file mode 100644
index 000000000000..b8e48dbf1c9c
--- /dev/null
+++ b/drivers/misc/issei/fw_client.c
@@ -0,0 +1,240 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2023-2026 Intel Corporation */
+#include <linux/bug.h>
+#include <linux/cleanup.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/dev_printk.h>
+#include <linux/kobject.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/sprintf.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/uuid.h>
+
+#include "issei_dev.h"
+#include "fw_client.h"
+
+/*
+ * Specific attribute handlers for fw_clients kset.
+ * Provide only show function as all fw_client attributes are read-only.
+ */
+
+struct issei_fw_cl_attr {
+ struct attribute attr;
+ ssize_t (*show)(struct issei_fw_client *fw_cl, const struct issei_fw_cl_attr *attr,
+ char *buf);
+};
+#define to_issei_fw_cl_attr(x) container_of_const(x, struct issei_fw_cl_attr, attr)
+
+static ssize_t fw_cl_attr_show(struct kobject *kobj, struct attribute *attr, char *buf)
+{
+ const struct issei_fw_cl_attr *issei_attr;
+ struct issei_fw_client *fw_cl;
+
+ issei_attr = to_issei_fw_cl_attr(attr);
+ fw_cl = to_issei_fw_client(kobj);
+
+ if (!issei_attr->show)
+ return -EIO;
+
+ return issei_attr->show(fw_cl, issei_attr, buf);
+}
+
+static const struct sysfs_ops fw_cl_sysfs_ops = {
+ .show = fw_cl_attr_show,
+};
+
+#define FW_CL_ATTR_RO(_name) \
+ struct issei_fw_cl_attr fw_cl_attr_##_name = __ATTR_RO(_name)
+
+/* fw_client attributes */
+
+static ssize_t id_show(struct issei_fw_client *fw_cl,
+ const struct issei_fw_cl_attr *attr, char *buf)
+{
+ return sysfs_emit(buf, "%u\n", fw_cl->id);
+}
+static FW_CL_ATTR_RO(id);
+
+static ssize_t ver_show(struct issei_fw_client *fw_cl,
+ const struct issei_fw_cl_attr *attr, char *buf)
+{
+ return sysfs_emit(buf, "%u\n", fw_cl->ver);
+}
+static FW_CL_ATTR_RO(ver);
+
+static ssize_t uuid_show(struct issei_fw_client *fw_cl,
+ const struct issei_fw_cl_attr *attr, char *buf)
+{
+ return sysfs_emit(buf, "%pUb\n", &fw_cl->uuid);
+}
+static FW_CL_ATTR_RO(uuid);
+
+static ssize_t mtu_show(struct issei_fw_client *fw_cl,
+ const struct issei_fw_cl_attr *attr, char *buf)
+{
+ return sysfs_emit(buf, "%u\n", fw_cl->mtu);
+}
+static FW_CL_ATTR_RO(mtu);
+
+static const struct attribute *const fw_cl_attrs[] = {
+ &fw_cl_attr_id.attr,
+ &fw_cl_attr_ver.attr,
+ &fw_cl_attr_uuid.attr,
+ &fw_cl_attr_mtu.attr,
+ NULL,
+};
+
+static const struct attribute_group fw_cl_group = {
+ .attrs_const = fw_cl_attrs,
+};
+__ATTRIBUTE_GROUPS(fw_cl);
+
+static void issei_fw_cl_init(struct issei_fw_client *fw_cl, u16 id, u8 ver, const uuid_t *uuid,
+ u32 mtu, u32 flags)
+{
+ INIT_LIST_HEAD(&fw_cl->list);
+ fw_cl->id = id;
+ fw_cl->ver = ver;
+ fw_cl->uuid = *uuid;
+ fw_cl->mtu = mtu;
+ fw_cl->flags = flags;
+}
+
+static void fw_cl_release(struct kobject *kobj)
+{
+ struct issei_fw_client *fw_cl = to_issei_fw_client(kobj);
+
+ kfree(fw_cl);
+}
+
+static const struct kobj_type fw_client_ktype = {
+ .sysfs_ops = &fw_cl_sysfs_ops,
+ .release = fw_cl_release,
+ .default_groups = fw_cl_groups,
+};
+
+/**
+ * issei_fw_cl_create - create firmware client object and add to list
+ * @idev: issei device object
+ * @id: firmware client id
+ * @ver: firmware client version
+ * @uuid: firmware client unique id
+ * @mtu: firmware client maximum message size
+ * @flags: firmware client flags
+ *
+ * Should be called under idev->client_lock
+ *
+ * Return: pointer to newly created object on success, ERR_PTR on failure
+ */
+struct issei_fw_client *issei_fw_cl_create(struct issei_device *idev, u16 id, u8 ver,
+ const uuid_t *uuid, u32 mtu, u32 flags)
+{
+ int ret;
+ struct issei_fw_client *fw_cl = kzalloc_obj(*fw_cl);
+
+ if (!fw_cl)
+ return ERR_PTR(-ENOMEM);
+
+ WARN_ON(!mutex_is_locked(&idev->client_lock));
+
+ issei_fw_cl_init(fw_cl, id, ver, uuid, mtu, flags);
+ fw_cl->kobj.kset = idev->fw_clients;
+
+ ret = kobject_init_and_add(&fw_cl->kobj, &fw_client_ktype, NULL, "%u", id);
+ if (ret) {
+ kobject_put(&fw_cl->kobj);
+ return ERR_PTR(ret);
+ }
+
+ list_add_tail(&fw_cl->list, &idev->fw_client_list);
+
+ dev_dbg(&idev->dev, "FW client %pUb created\n", uuid);
+
+ kobject_uevent(&fw_cl->kobj, KOBJ_ADD);
+
+ return fw_cl;
+}
+
+static void __issei_fw_cl_remove(struct issei_device *idev, struct issei_fw_client *fw_cl)
+{
+ WARN(fw_cl->cl, "Removing connected client!\n");
+
+ dev_dbg(&idev->dev, "FW client %pUb will be removed\n", &fw_cl->uuid);
+
+ list_del(&fw_cl->list);
+ kobject_put(&fw_cl->kobj);
+}
+
+/**
+ * issei_fw_cl_remove_all - remove all firmware client objects
+ * @idev: issei device object
+ */
+void issei_fw_cl_remove_all(struct issei_device *idev)
+{
+ struct issei_fw_client *fw_cl, *next;
+
+ guard(mutex)(&idev->client_lock);
+
+ list_for_each_entry_safe(fw_cl, next, &idev->fw_client_list, list)
+ __issei_fw_cl_remove(idev, fw_cl);
+}
+
+/**
+ * issei_fw_cl_find_by_uuid - find firmware client by uuid
+ * @idev: issei device object
+ * @uuid: uuid to search by it
+ *
+ * Should be called under idev->client_lock
+ *
+ * Return: pointer to firmware client object if found, NULL on failure
+ */
+struct issei_fw_client *issei_fw_cl_find_by_uuid(struct issei_device *idev, const uuid_t *uuid)
+{
+ struct issei_fw_client *fw_cl;
+
+ WARN_ON(!mutex_is_locked(&idev->client_lock));
+
+ list_for_each_entry(fw_cl, &idev->fw_client_list, list) {
+ if (uuid_equal(&fw_cl->uuid, uuid)) {
+ kobject_get(&fw_cl->kobj);
+ return fw_cl;
+ }
+ }
+ return NULL;
+}
+
+/**
+ * issei_fw_cl_connect - connect firmware and host client
+ * @fw_cl: firmware client
+ * @cl: host client
+ *
+ * Should be called under idev->client_lock
+ *
+ * Return: 0 on success, -EBUSY if already connected
+ */
+int issei_fw_cl_connect(struct issei_fw_client *fw_cl, struct issei_host_client *cl)
+{
+ if (fw_cl->cl)
+ return -EBUSY;
+
+ kobject_get(&fw_cl->kobj);
+ fw_cl->cl = cl;
+ return 0;
+}
+
+/**
+ * issei_fw_cl_disconnect - disconnect firmware and host client
+ * @fw_cl: firmware client
+ *
+ * Should be called under idev->client_lock
+ */
+void issei_fw_cl_disconnect(struct issei_fw_client *fw_cl)
+{
+ WARN_ON(!fw_cl->cl);
+
+ fw_cl->cl = NULL;
+ kobject_put(&fw_cl->kobj);
+}
diff --git a/drivers/misc/issei/fw_client.h b/drivers/misc/issei/fw_client.h
new file mode 100644
index 000000000000..377f733d7e91
--- /dev/null
+++ b/drivers/misc/issei/fw_client.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2023-2026 Intel Corporation */
+#ifndef _ISSEI_FW_CLIENT_H_
+#define _ISSEI_FW_CLIENT_H_
+
+#include <linux/device.h>
+#include <linux/kobject.h>
+#include <linux/types.h>
+#include <linux/uuid.h>
+
+struct issei_device;
+struct issei_host_client;
+
+/**
+ * struct issei_fw_client - represents firmware queue
+ * @kobj: associated kobject
+ * @list: link in firmware clients list
+ * @id: firmware client id
+ * @ver: firmware client version
+ * @uuid: firmware client protocol id
+ * @mtu: firmware client maximum buffer size
+ * @flags: firmware client flags
+ * @cl: pointer to host client, if connected
+ */
+struct issei_fw_client {
+ struct kobject kobj;
+ struct list_head list;
+ u16 id;
+ u8 ver;
+ uuid_t uuid;
+ u32 mtu;
+ u32 flags;
+ struct issei_host_client *cl;
+};
+#define to_issei_fw_client(x) container_of(x, struct issei_fw_client, kobj)
+
+struct issei_fw_client *issei_fw_cl_create(struct issei_device *idev, u16 id, u8 ver,
+ const uuid_t *uuid, u32 mtu, u32 flags);
+void issei_fw_cl_remove_all(struct issei_device *idev);
+struct issei_fw_client *issei_fw_cl_find_by_uuid(struct issei_device *idev, const uuid_t *uuid);
+
+int issei_fw_cl_connect(struct issei_fw_client *fw_cl, struct issei_host_client *cl);
+void issei_fw_cl_disconnect(struct issei_fw_client *fw_cl);
+
+#endif /* _ISSEI_FW_CLIENT_H_ */
diff --git a/drivers/misc/issei/ham.c b/drivers/misc/issei/ham.c
new file mode 100644
index 000000000000..17eae91f077d
--- /dev/null
+++ b/drivers/misc/issei/ham.c
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2023-2026 Intel Corporation */
+#include <linux/dev_printk.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include "dma.h"
+#include "fw_client.h"
+#include "ham.h"
+#include "hw_msg.h"
+#include "issei_dev.h"
+
+static int __issei_ham_send_msg(struct issei_device *idev, u32 length, void *buf)
+{
+ struct issei_dma_data data = { };
+ int ret;
+
+ data.length = length;
+ data.buf = buf;
+ ret = issei_dma_write(idev, &data);
+ if (ret)
+ return ret;
+ return idev->ops->irq_write_generate(idev);
+}
+
+/**
+ * issei_ham_send_start_req - send start request to firmware
+ * @idev: issei device object
+ *
+ * Return: 0 on success, <0 on failures
+ */
+int issei_ham_send_start_req(struct issei_device *idev)
+{
+ struct ham_start_message_req req;
+
+ req.header.cmd = HAM_BUS_CMD_START_REQ;
+ req.supported_version = ISSEI_SUPPORTED_PROTOCOL_VER;
+ req.heci_capabilities_length = 0;
+
+ return __issei_ham_send_msg(idev, sizeof(req), &req);
+}
+
+/**
+ * issei_ham_send_clients_req - send clients request to firmware
+ * @idev: issei device object
+ *
+ * Return: 0 on success, <0 on failures
+ */
+int issei_ham_send_clients_req(struct issei_device *idev)
+{
+ struct ham_get_clients_req req;
+
+ req.header.cmd = HAM_BUS_CMD_CLIENT_REQ;
+
+ return __issei_ham_send_msg(idev, sizeof(req), &req);
+}
+
+static int issei_ham_start_rsp(struct issei_device *idev, const u8 *buf, size_t length)
+{
+ struct ham_start_message_res *res = (struct ham_start_message_res *)buf;
+ int ret;
+
+ if (idev->rst_state != ISSEI_RST_STATE_START) {
+ dev_err(&idev->dev, "Wrong state %d != %d\n",
+ idev->rst_state, ISSEI_RST_STATE_START);
+ return -EPROTO;
+ }
+
+ if (length < sizeof(*res)) {
+ dev_err(&idev->dev, "Small start response size %zu < %zu\n",
+ length, sizeof(*res));
+ return -EPROTO;
+ }
+
+ if (length - sizeof(*res) != res->heci_capabilities_length) {
+ dev_err(&idev->dev, "Wrong start response size %zu != %u\n",
+ length - sizeof(*res), res->heci_capabilities_length);
+ return -EPROTO;
+ }
+
+ memcpy(idev->fw_version, res->fw_version, sizeof(idev->fw_version));
+ idev->fw_protocol_ver = res->supported_version;
+ dev_dbg(&idev->dev, "FW protocol: %u FW version %u.%u.%u.%u", idev->fw_protocol_ver,
+ idev->fw_version[0], idev->fw_version[1],
+ idev->fw_version[2], idev->fw_version[3]);
+
+ ret = issei_ham_send_clients_req(idev);
+ if (ret == -EBUSY)
+ ret = 0;
+
+ return ret;
+}
+
+static int issei_ham_client_rsp(struct issei_device *idev, const u8 *buf, size_t length)
+{
+ struct ham_get_clients_res *res = (struct ham_get_clients_res *)buf;
+ struct ham_client_properties *client;
+
+ if (idev->rst_state != ISSEI_RST_STATE_CLIENT_ENUM) {
+ dev_err(&idev->dev, "Wrong state %d != %d\n",
+ idev->rst_state, ISSEI_RST_STATE_CLIENT_ENUM);
+ return -EPROTO;
+ }
+
+ if (length < sizeof(*res)) {
+ dev_err(&idev->dev, "Small response size %zu < %zu\n", length, sizeof(*res));
+ return -EPROTO;
+ }
+
+ if (length - sizeof(*res) != res->client_count * sizeof(struct ham_client_properties)) {
+ dev_err(&idev->dev, "Wrong response size %zu < %zu\n",
+ length - sizeof(*res),
+ res->client_count * sizeof(struct ham_client_properties));
+ return -EPROTO;
+ }
+
+ guard(mutex)(&idev->client_lock);
+
+ for (size_t i = 0; i < res->client_count; i++) {
+ client = &res->clients_props[i];
+ dev_dbg(&idev->dev, "client: id = %u ver = %u uuid = %pUb mtu = %u flags = %u",
+ client->client_number, client->protocol_ver, &client->client_uuid,
+ client->client_mtu, client->flags);
+ issei_fw_cl_create(idev, client->client_number, client->protocol_ver,
+ &client->client_uuid, client->client_mtu, client->flags);
+ }
+ return 0;
+}
+
+static int __issei_ham_process_ham_rsp(struct issei_device *idev, const u8 *buf, size_t length)
+{
+ struct ham_bus_message *hdr = (struct ham_bus_message *)buf;
+
+ switch (hdr->cmd) {
+ case HAM_BUS_CMD_START_RSP:
+ return issei_ham_start_rsp(idev, buf, length);
+
+ case HAM_BUS_CMD_CLIENT_RSP:
+ return issei_ham_client_rsp(idev, buf, length);
+
+ default:
+ dev_err(&idev->dev, "Unexpected command 0x%x", hdr->cmd);
+ return -EPROTO;
+ }
+}
+
+/**
+ * issei_ham_process_ham_rsp - process response from firmware and release buffer
+ * @idev: issei device object
+ * @buf: response buffer
+ * @length: response buffer length
+ *
+ * Return: 0 on success, <0 on failures
+ */
+int issei_ham_process_ham_rsp(struct issei_device *idev, const u8 *buf, size_t length)
+{
+ int ret;
+
+ ret = __issei_ham_process_ham_rsp(idev, buf, length);
+ kfree(buf);
+ return ret;
+}
diff --git a/drivers/misc/issei/ham.h b/drivers/misc/issei/ham.h
new file mode 100644
index 000000000000..37ea64bb2920
--- /dev/null
+++ b/drivers/misc/issei/ham.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2023-2026 Intel Corporation */
+#ifndef _ISSEI_HAM_H_
+#define _ISSEI_HAM_H_
+
+#include <linux/types.h>
+
+struct issei_device;
+
+int issei_ham_send_start_req(struct issei_device *idev);
+int issei_ham_send_clients_req(struct issei_device *idev);
+
+static inline bool issei_is_ham_rsp(u16 fw_id, u16 host_id)
+{
+ return fw_id == 0 && host_id == 0;
+}
+
+int issei_ham_process_ham_rsp(struct issei_device *idev, const u8 *buf, size_t length);
+
+#endif /* _ISSEI_HAM_H_ */
diff --git a/drivers/misc/issei/host_client.c b/drivers/misc/issei/host_client.c
new file mode 100644
index 000000000000..8f36d1c319ec
--- /dev/null
+++ b/drivers/misc/issei/host_client.c
@@ -0,0 +1,519 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2023-2026 Intel Corporation */
+#include <linux/cleanup.h>
+#include <linux/dev_printk.h>
+#include <linux/err.h>
+#include <linux/fs.h>
+#include <linux/kobject.h>
+#include <linux/list.h>
+#include <linux/overflow.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/wait.h>
+#include <linux/uuid.h>
+
+#include "fw_client.h"
+#include "host_client.h"
+#include "issei_dev.h"
+
+static inline u8 __issei_cl_fw_id(const struct issei_host_client *cl)
+{
+ return cl->fw_cl ? cl->fw_cl->id : 0;
+}
+
+#define ISSEI_CL_FMT "cl:host=%02d fw=%02d "
+
+#define cl_dbg(_dev_, _cl_, format, arg...) do { \
+ struct issei_host_client *_l_cl_ = _cl_; \
+ dev_dbg(&(_dev_)->dev, ISSEI_CL_FMT format, _l_cl_->id, \
+ __issei_cl_fw_id(_l_cl_), ##arg); \
+} while (0)
+
+#define cl_warn(_dev_, _cl_, format, arg...) do { \
+ struct issei_host_client *_l_cl_ = _cl_; \
+ dev_warn(&(_dev_)->dev, ISSEI_CL_FMT format, _l_cl_->id, \
+ __issei_cl_fw_id(_l_cl_), ##arg); \
+} while (0)
+
+#define cl_err(_dev_, _cl_, format, arg...) do { \
+ struct issei_host_client *_l_cl_ = _cl_; \
+ dev_err(&(_dev_)->dev, ISSEI_CL_FMT format, _l_cl_->id, \
+ __issei_cl_fw_id(_l_cl_), ##arg); \
+} while (0)
+
+static void __issei_cl_clean_wbuf(struct issei_write_buf *wbuf)
+{
+ list_del(&wbuf->list);
+ kfree(wbuf->data);
+ kfree(wbuf);
+}
+
+static void __issei_cl_release_rbuf(struct issei_host_client *cl)
+{
+ cl->read_data = NULL;
+ cl->read_data_size = 0;
+}
+
+static void __issei_cl_clean_rbuf(struct issei_host_client *cl)
+{
+ kfree(cl->read_data);
+ __issei_cl_release_rbuf(cl);
+}
+
+static void __issei_cl_clean_all_wbuf(struct issei_device *idev, struct issei_host_client *cl)
+{
+ struct issei_write_buf *wbuf, *next;
+
+ if (!cl->write_in_progress)
+ return;
+ list_for_each_entry_safe(wbuf, next, &idev->write_queue, list) {
+ if (wbuf->cl == cl) {
+ __issei_cl_clean_wbuf(wbuf);
+ break;
+ }
+ }
+ cl->write_in_progress = false;
+ /* synchronized under host client mutex */
+ if (waitqueue_active(&cl->write_wait))
+ wake_up_interruptible(&cl->write_wait);
+}
+
+static struct issei_host_client *__issei_cl_by_id(struct issei_device *idev, u16 id)
+{
+ struct issei_host_client *cl;
+
+ list_for_each_entry(cl, &idev->host_client_list, list) {
+ if (cl->id == id)
+ return cl;
+ }
+ return NULL;
+}
+
+static void __issei_cl_disconnect(struct issei_device *idev, struct issei_host_client *cl)
+{
+ if (cl->state == ISSEI_HOST_CL_STATE_DISCONNECTED)
+ return;
+
+ __issei_cl_clean_all_wbuf(idev, cl);
+
+ if (!WARN_ON(!cl->fw_cl)) {
+ issei_fw_cl_disconnect(cl->fw_cl);
+ cl->fw_cl = NULL;
+ }
+ cl->state = ISSEI_HOST_CL_STATE_DISCONNECTED;
+
+ if (cl->read_data)
+ __issei_cl_clean_rbuf(cl);
+ /* synchronized under host client mutex */
+ if (waitqueue_active(&cl->read_wait))
+ wake_up_interruptible(&cl->read_wait);
+ cl_dbg(idev, cl, "Disconnected\n");
+}
+
+static void __issei_cl_init(struct issei_host_client *cl, struct issei_device *idev,
+ u16 id, struct file *fp)
+{
+ INIT_LIST_HEAD(&cl->list);
+ cl->idev = idev;
+ cl->id = id;
+ cl->state = ISSEI_HOST_CL_STATE_DISCONNECTED;
+ cl->fp = fp;
+ init_waitqueue_head(&cl->write_wait);
+ init_waitqueue_head(&cl->read_wait);
+ __issei_cl_release_rbuf(cl);
+}
+
+/**
+ * issei_cl_create - create the host client
+ * @idev: issei device
+ * @fp: file pointer to associate with host client
+ *
+ * Return: client pointer on success, ERR_PTR on error
+ */
+struct issei_host_client *issei_cl_create(struct issei_device *idev, struct file *fp)
+{
+ struct issei_host_client *cl;
+ u16 id;
+
+ guard(mutex)(&idev->client_lock);
+
+ if (idev->host_client_count == ISSEI_HOST_CLIENTS_MAX) {
+ dev_err(&idev->dev, "Maximum open clients %d is reached.\n",
+ ISSEI_HOST_CLIENTS_MAX);
+ return ERR_PTR(-EMFILE);
+ }
+
+ do {
+ if (check_add_overflow(idev->host_client_last_id, 1, &id)) /* overflow */
+ id = 1;
+ idev->host_client_last_id = id;
+ /* Not an endless loop as we have less clients then id's */
+ } while (__issei_cl_by_id(idev, id));
+
+ cl = kzalloc_obj(*cl);
+ if (!cl)
+ return ERR_PTR(-ENOMEM);
+
+ __issei_cl_init(cl, idev, id, fp);
+ list_add_tail(&cl->list, &idev->host_client_list);
+ idev->host_client_count++;
+
+ cl_dbg(idev, cl, "Created\n");
+ return cl;
+}
+
+/**
+ * issei_cl_remove - disconnect and free the host client
+ * @cl: host client
+ */
+void issei_cl_remove(struct issei_host_client *cl)
+{
+ struct issei_device *idev;
+
+ /* don't shout on error exit path */
+ if (!cl)
+ return;
+
+ idev = cl->idev;
+
+ guard(mutex)(&idev->client_lock);
+
+ idev->host_client_count--;
+ list_del(&cl->list);
+
+ __issei_cl_disconnect(idev, cl);
+
+ cl_dbg(idev, cl, "Removed\n");
+ kfree(cl);
+}
+
+/**
+ * issei_cl_connect - connect between FW and host client
+ * @cl: host client
+ * @uuid: FW client unique ID
+ * @mtu: memory for FW client max message size
+ * @ver: memory for FW client version
+ * @flags: memory for FW client flags
+ *
+ * Search for firmware client by UUID and connect it to provided
+ * host client, if not already connected to some client.
+ *
+ * Return: 0 on success, <0 on error
+ */
+int issei_cl_connect(struct issei_host_client *cl, const uuid_t *uuid, u32 *mtu, u8 *ver,
+ u32 *flags)
+{
+ struct issei_device *idev = cl->idev;
+ struct issei_fw_client *fw_cl;
+ int ret;
+
+ guard(mutex)(&idev->client_lock);
+
+ if (cl->state == ISSEI_HOST_CL_STATE_CONNECTED) {
+ cl_err(idev, cl, "Already connected\n");
+ return -EISCONN;
+ }
+
+ fw_cl = issei_fw_cl_find_by_uuid(idev, uuid);
+ if (!fw_cl) {
+ cl_dbg(idev, cl, "FW client %pUb not found\n", uuid);
+ return -ENOTTY;
+ }
+
+ ret = issei_fw_cl_connect(fw_cl, cl); /* calls kobject_get for fw_cl on success */
+ kobject_put(&fw_cl->kobj);
+ if (ret) {
+ cl_err(idev, cl, "FW client is already connected ret = %d\n", ret);
+ return ret;
+ }
+
+ cl->fw_cl = fw_cl;
+ cl->state = ISSEI_HOST_CL_STATE_CONNECTED;
+
+ *mtu = fw_cl->mtu;
+ *ver = fw_cl->ver;
+ *flags = fw_cl->flags;
+ cl_dbg(idev, cl, "Connected\n");
+ return 0;
+}
+
+/**
+ * issei_cl_disconnect - disconnect between FW and host client
+ * @cl: host client
+ *
+ * Return: 0 on success, -ENOTCONN if not connected
+ */
+int issei_cl_disconnect(struct issei_host_client *cl)
+{
+ struct issei_device *idev = cl->idev;
+
+ guard(mutex)(&idev->client_lock);
+
+ if (cl->state != ISSEI_HOST_CL_STATE_CONNECTED)
+ return -ENOTCONN;
+ __issei_cl_disconnect(idev, cl);
+ return 0;
+}
+
+/**
+ * issei_cl_all_disconnect - disconnect all FW clients
+ * @idev: issei device
+ */
+void issei_cl_all_disconnect(struct issei_device *idev)
+{
+ struct issei_host_client *cl;
+
+ guard(mutex)(&idev->client_lock);
+
+ list_for_each_entry(cl, &idev->host_client_list, list)
+ __issei_cl_disconnect(idev, cl);
+}
+
+/**
+ * issei_cl_write - enqueue write request
+ * @cl: host client
+ * @buf: buffer to write
+ * @buf_size: buffer size
+ *
+ * Add write request to the write queue and wakes working thread.
+ * This call takes ownership of buf memory, if succeeded.
+ *
+ * Return: size of data on success, <0 on error
+ */
+ssize_t issei_cl_write(struct issei_host_client *cl, const u8 *buf, size_t buf_size)
+{
+ struct issei_device *idev = cl->idev;
+ struct issei_write_buf *wbuf;
+
+ guard(mutex)(&idev->client_lock);
+
+ if (cl->state != ISSEI_HOST_CL_STATE_CONNECTED)
+ return -ENOTCONN;
+
+ if (cl->write_in_progress) {
+ cl_dbg(idev, cl, "Another write is in progress\n");
+ return -EAGAIN;
+ }
+
+ if (buf_size > cl->fw_cl->mtu) {
+ cl_err(idev, cl, "Write is too big %zu > %u\n", buf_size, cl->fw_cl->mtu);
+ return -EFBIG;
+ }
+
+ wbuf = kmalloc_obj(*wbuf);
+ if (!wbuf)
+ return -ENOMEM;
+ wbuf->cl = cl;
+ wbuf->data = buf;
+ wbuf->data_size = buf_size;
+ list_add_tail(&wbuf->list, &idev->write_queue);
+ cl->write_in_progress = true;
+ cl_dbg(idev, cl, "Write queued %zu bytes\n", buf_size);
+
+ issei_poke_process_thread(idev);
+
+ return buf_size;
+}
+
+/**
+ * issei_cl_write_from_queue - writes first request from queue to firmware
+ * @idev: issei device
+ *
+ * Tries to write first request from the write queue to firmware.
+ * Releases buf memory, if succeeded.
+ *
+ * Return: 0 on success, <0 on error
+ */
+int issei_cl_write_from_queue(struct issei_device *idev)
+{
+ struct issei_write_buf *wbuf;
+ struct issei_dma_data data;
+ struct issei_host_client *cl;
+ int ret;
+
+ guard(mutex)(&idev->client_lock);
+
+ wbuf = list_first_entry_or_null(&idev->write_queue, struct issei_write_buf, list);
+ if (!wbuf)
+ return 0;
+
+ cl = wbuf->cl;
+
+ data.fw_id = cl->fw_cl->id;
+ data.host_id = cl->id;
+ data.flags = 0;
+ data.status = 0;
+ data.length = wbuf->data_size;
+ data.buf = (void *)wbuf->data;
+ ret = issei_dma_write(idev, &data);
+ if (ret == -EBUSY)
+ return 0;
+ if (ret == -EIO)
+ return ret;
+ if (ret >= 0)
+ idev->ops->irq_write_generate(idev);
+ cl->write_in_progress = false;
+ /* synchronized under host client mutex */
+ if (waitqueue_active(&cl->write_wait))
+ wake_up_interruptible(&cl->write_wait);
+ cl_dbg(idev, cl, "Write %zu bytes\n", wbuf->data_size);
+ __issei_cl_clean_wbuf(wbuf);
+ return 0;
+}
+
+static struct issei_host_client *__issei_cl_read_buf_check(struct issei_device *idev, u16 fw_id,
+ u16 host_id, size_t buf_size)
+{
+ struct issei_host_client *cl;
+
+ cl = __issei_cl_by_id(idev, host_id);
+ if (!cl) {
+ dev_dbg(&idev->dev, "No client %u\n", host_id);
+ return ERR_PTR(-ENOTTY);
+ }
+
+ if (cl->state != ISSEI_HOST_CL_STATE_CONNECTED) {
+ cl_dbg(idev, cl, "Not connected\n");
+ return ERR_PTR(-ENODEV);
+ }
+ if (cl->fw_cl->id != fw_id) {
+ cl_dbg(idev, cl, "Wrong firmware client %u ?= %u\n", cl->fw_cl->id, fw_id);
+ return ERR_PTR(-ENODEV);
+ }
+
+ if (buf_size > cl->fw_cl->mtu) {
+ cl_err(idev, cl, "Read is too big %zu > %u\n", buf_size, cl->fw_cl->mtu);
+ __issei_cl_disconnect(idev, cl);
+ return NULL;
+ }
+
+ if (cl->read_data) {
+ cl_err(idev, cl, "Previous data was not read by user-space, disconnecting\n");
+ __issei_cl_disconnect(idev, cl);
+ return NULL;
+ }
+
+ return cl;
+}
+
+/**
+ * issei_cl_read_buf - process data from firmware
+ * @idev: issei device
+ * @fw_id: firmware client id
+ * @host_id: host client id
+ * @buf: buffer with data
+ * @buf_size: buffer size
+ *
+ * Puts data from firmware into provided host client storage.
+ * Free buffer or consume it.
+ *
+ * Return: 0 on success or recoverable error, <0 on unrecoverable error
+ */
+int issei_cl_read_buf(struct issei_device *idev, u16 fw_id, u16 host_id, u8 *buf, size_t buf_size)
+{
+ struct issei_host_client *cl;
+
+ guard(mutex)(&idev->client_lock);
+
+ cl = __issei_cl_read_buf_check(idev, fw_id, host_id, buf_size);
+ if (IS_ERR_OR_NULL(cl)) {
+ kfree(buf);
+ return PTR_ERR(cl);
+ }
+
+ cl->read_data = buf;
+ cl->read_data_size = buf_size;
+
+ /* synchronized under host client mutex */
+ if (waitqueue_active(&cl->read_wait))
+ wake_up_interruptible(&cl->read_wait);
+ cl_dbg(idev, cl, "Read %zu bytes\n", buf_size);
+
+ return 0;
+}
+
+/**
+ * issei_cl_read - read data from queue to provided buffer
+ * @cl: host client
+ * @buf: buffer to store data
+ * @buf_size: buffer size
+ *
+ * Tries to take data buffer from client and return it to caller.
+ * The caller receives ownership of the data buffer.
+ *
+ * Return: read data size on success, <0 on error
+ */
+ssize_t issei_cl_read(struct issei_host_client *cl, u8 **buf, size_t buf_size)
+{
+ struct issei_device *idev = cl->idev;
+ size_t read_data_size;
+
+ guard(mutex)(&idev->client_lock);
+
+ if (cl->state != ISSEI_HOST_CL_STATE_CONNECTED)
+ return -ENOTCONN;
+
+ if (!cl->read_data)
+ return -ENOENT;
+
+ if (cl->read_data_size > buf_size) {
+ cl_err(idev, cl, "Buffer is too small %zu > %zu\n",
+ cl->read_data_size, buf_size);
+ return -EFBIG;
+ }
+
+ *buf = cl->read_data;
+ read_data_size = cl->read_data_size;
+ cl_dbg(idev, cl, "Read by client %zu bytes\n", read_data_size);
+ __issei_cl_release_rbuf(cl);
+ return read_data_size;
+}
+
+/**
+ * issei_cl_check_read - check if client has data to read
+ *
+ * @cl: host client
+ *
+ * Return: 1 - data available, 0 - no data, < 0 on error
+ */
+int issei_cl_check_read(struct issei_host_client *cl)
+{
+ struct issei_device *idev = cl->idev;
+
+ guard(mutex)(&idev->client_lock);
+
+ if (cl->state != ISSEI_HOST_CL_STATE_CONNECTED)
+ return -ENOTCONN;
+ if (!cl->read_data)
+ return 0;
+ return 1;
+}
+
+/**
+ * issei_cl_check_write - check if client is ready to write
+ *
+ * @cl: host client
+ *
+ * Return: 1 - can not write, 0 - can write, < 0 on error
+ */
+int issei_cl_check_write(struct issei_host_client *cl)
+{
+ struct issei_device *idev = cl->idev;
+
+ guard(mutex)(&idev->client_lock);
+
+ if (cl->state != ISSEI_HOST_CL_STATE_CONNECTED)
+ return -ENOTCONN;
+ if (cl->write_in_progress)
+ return 1;
+ return 0;
+}
+
+void issei_cl_clean_all_wbuf(struct issei_host_client *cl)
+{
+ struct issei_device *idev = cl->idev;
+
+ guard(mutex)(&idev->client_lock);
+
+ __issei_cl_clean_all_wbuf(idev, cl);
+}
diff --git a/drivers/misc/issei/host_client.h b/drivers/misc/issei/host_client.h
new file mode 100644
index 000000000000..05e2f4ade9c3
--- /dev/null
+++ b/drivers/misc/issei/host_client.h
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2023-2026 Intel Corporation */
+#ifndef _ISSEI_HOST_CLIENT_H_
+#define _ISSEI_HOST_CLIENT_H_
+
+#include <linux/types.h>
+#include <linux/uuid.h>
+#include <linux/wait.h>
+
+struct file;
+
+struct issei_device;
+struct issei_fw_client;
+
+/**
+ * enum issei_host_client_state - host client states
+ * @ISSEI_HOST_CL_STATE_DISCONNECTED: host client is disconnected
+ * @ISSEI_HOST_CL_STATE_CONNECTED: host client is connected
+ */
+enum issei_host_client_state {
+ ISSEI_HOST_CL_STATE_DISCONNECTED,
+ ISSEI_HOST_CL_STATE_CONNECTED,
+};
+
+/**
+ * struct issei_host_client - represents host client
+ * @list: link in host clients list
+ * @idev: issei parent device
+ * @id: host client id
+ * @fp: file associated with client
+ *
+ * @write_wait: waitqueue for pending write data
+ * @write_in_progress: indicator for write in process
+ *
+ * @state: host client state
+ * @fw_cl: pointer to firmware client, if connected
+ *
+ * @read_wait: waitqueue for read object
+ * @read_data: received data pointer
+ * @read_data_size: received data size
+ */
+struct issei_host_client {
+ struct list_head list;
+ struct issei_device *idev;
+ u16 id;
+ const struct file *fp;
+
+ wait_queue_head_t write_wait;
+ bool write_in_progress;
+
+ enum issei_host_client_state state;
+ struct issei_fw_client *fw_cl;
+
+ wait_queue_head_t read_wait;
+ u8 *read_data;
+ size_t read_data_size;
+};
+
+struct issei_host_client *issei_cl_create(struct issei_device *idev, struct file *fp);
+void issei_cl_remove(struct issei_host_client *cl);
+
+int issei_cl_connect(struct issei_host_client *cl, const uuid_t *uuid, u32 *mtu, u8 *ver,
+ u32 *flags);
+int issei_cl_disconnect(struct issei_host_client *cl);
+void issei_cl_all_disconnect(struct issei_device *idev);
+
+ssize_t issei_cl_write(struct issei_host_client *cl, const u8 *buf, size_t buf_size);
+int issei_cl_write_from_queue(struct issei_device *idev);
+int issei_cl_read_buf(struct issei_device *idev, u16 fw_id, u16 host_id, u8 *buf, size_t buf_size);
+ssize_t issei_cl_read(struct issei_host_client *cl, u8 **buf, size_t buf_size);
+int issei_cl_check_read(struct issei_host_client *cl);
+int issei_cl_check_write(struct issei_host_client *cl);
+void issei_cl_clean_all_wbuf(struct issei_host_client *cl);
+
+#endif /* ISSEI_HOST_CLIENT_H_ */
diff --git a/drivers/misc/issei/hw_heci.c b/drivers/misc/issei/hw_heci.c
new file mode 100644
index 000000000000..35c55de5c67a
--- /dev/null
+++ b/drivers/misc/issei/hw_heci.c
@@ -0,0 +1,550 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2023-2026 Intel Corporation */
+#include <linux/align.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/cleanup.h>
+#include <linux/dev_printk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/pci.h>
+#include <linux/sizes.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
+#include "hw_heci.h"
+#include "hw_heci_regs.h"
+#include "hw_msg.h"
+
+/**
+ * heci_reg_read - Reads 32bit data from the issei heci device
+ * @hw: the heci hardware structure
+ * @offset: offset from which to read the data
+ *
+ * Return: register value (u32)
+ */
+static inline u32 heci_reg_read(const struct issei_heci_hw *hw, unsigned long offset)
+{
+ return ioread32(hw->mem_addr + offset);
+}
+
+/**
+ * heci_reg_write - Writes 32bit data to the issei heci device
+ *
+ * @hw: the heci hardware structure
+ * @offset: offset from which to write the data
+ * @value: register value to write (u32)
+ */
+static inline void heci_reg_write(const struct issei_heci_hw *hw, unsigned long offset, u32 value)
+{
+ iowrite32(value, hw->mem_addr + offset);
+}
+
+/**
+ * heci_fwcbrw_read - Reads 32bit data from heci circular buffer
+ * @hw: the heci hardware structure
+ *
+ * Return: FW_CB_RW register value (u32)
+ */
+static inline u32 heci_fwcbrw_read(const struct issei_heci_hw *hw)
+{
+ return heci_reg_read(hw, FW_CB_RW);
+}
+
+/**
+ * heci_hcbww_write - write 32bit data to the host circular buffer
+ * @hw: the heci hardware structure
+ * @data: 32bit data to be written to the host circular buffer
+ */
+static inline void heci_hcbww_write(const struct issei_heci_hw *hw, u32 data)
+{
+ heci_reg_write(hw, H_CB_WW, data);
+}
+
+/**
+ * heci_irq_src - Filters IRQ source bits from the host CSR
+ * @hcsr: host CSR register value
+ *
+ * Return: interrupt source bits of host CSR
+ */
+static inline u32 heci_irq_src(u32 hcsr)
+{
+ return hcsr & H_CSR_IS;
+}
+
+/**
+ * heci_hcsr_read - Reads 32bit data from the host CSR
+ * @idev: the device structure
+ *
+ * Return: H_CSR register value (u32)
+ */
+static inline u32 heci_hcsr_read(const struct issei_device *idev)
+{
+ return heci_reg_read(to_heci_hw(idev), H_CSR);
+}
+
+/**
+ * heci_hcsr_write - writes H_CSR register to device
+ * @idev: the device structure
+ * @reg: new register value
+ */
+static inline void heci_hcsr_write(struct issei_device *idev, u32 reg)
+{
+ heci_reg_write(to_heci_hw(idev), H_CSR, reg);
+}
+
+/**
+ * heci_hcsr_set - writes H_CSR register to the heci device
+ * @idev: the device structure
+ * @reg: new register value
+ *
+ * Writes H_CSR register to the heci device
+ * and ignores the H_IS bit for it is write-one-to-zero.
+ *
+ */
+static inline void heci_hcsr_set(struct issei_device *idev, u32 reg)
+{
+ reg &= ~H_CSR_IS;
+ heci_hcsr_write(idev, reg);
+}
+
+/**
+ * heci_hcsr_set_hig - set host interrupt (set H_CSR_IG)
+ * @idev: the device structure
+ */
+static inline void heci_hcsr_set_hig(struct issei_device *idev)
+{
+ u32 reg;
+
+ reg = heci_hcsr_read(idev) | H_CSR_IG;
+ heci_hcsr_set(idev, reg);
+}
+
+/**
+ * heci_fwcsr_read - Reads 32bit data from the FW CSR
+ * @idev: the device structure
+ *
+ * Return: FW_CSR_HA register value (u32)
+ */
+static inline u32 heci_fwcsr_read(const struct issei_device *idev)
+{
+ return heci_reg_read(to_heci_hw(idev), FW_CSR_HA);
+}
+
+/**
+ * heci_count_full_read_slots - counts read full slots.
+ * @idev: the device structure
+ *
+ * Return: -EOVERFLOW if overflow, otherwise filled slots count
+ */
+static int heci_count_full_read_slots(struct issei_device *idev)
+{
+ u8 buffer_depth, filled_slots;
+ u8 read_ptr, write_ptr;
+ u32 reg;
+
+ reg = heci_fwcsr_read(idev);
+ buffer_depth = (u8)FIELD_GET(FW_CSR_CBD, reg);
+ read_ptr = (u8)FIELD_GET(FW_CSR_CBRP, reg);
+ write_ptr = (u8)FIELD_GET(FW_CSR_CBWP, reg);
+ filled_slots = write_ptr - read_ptr;
+
+ /* check for overflow */
+ if (filled_slots > buffer_depth)
+ return -EOVERFLOW;
+
+ dev_dbg(&idev->dev, "filled_slots = %08x\n", filled_slots);
+ return filled_slots;
+}
+
+/**
+ * heci_irq_disable - disables heci device interrupts
+ * @idev: the device structure
+ * @reg: supplied hcsr register value
+ *
+ * disables heci device interrupts using supplied hcsr register value.
+ */
+static inline void heci_irq_disable(struct issei_device *idev, u32 reg)
+{
+ reg &= ~H_CSR_IE;
+ heci_hcsr_set(idev, reg);
+}
+
+/**
+ * heci_irq_clear - clear and stop interrupts
+ * @idev: the device structure
+ * @reg: supplied hcsr register value
+ */
+static inline void heci_irq_clear(struct issei_device *idev, u32 reg)
+{
+ if (heci_irq_src(reg))
+ heci_hcsr_write(idev, reg);
+}
+
+/**
+ * issei_heci_irq_clear - clear and stop interrupts
+ * @idev: the device structure
+ */
+static void issei_heci_irq_clear(struct issei_device *idev)
+{
+ u32 reg = heci_hcsr_read(idev);
+
+ heci_irq_clear(idev, reg);
+}
+
+/**
+ * issei_heci_irq_enable - enables heci device interrupts
+ * @idev: the device structure
+ */
+static void issei_heci_irq_enable(struct issei_device *idev)
+{
+ u32 reg;
+
+ reg = heci_hcsr_read(idev) | H_CSR_IE;
+ heci_hcsr_set(idev, reg);
+}
+
+/**
+ * issei_heci_irq_disable - disables heci device interrupts
+ * @idev: the device structure
+ */
+static void issei_heci_irq_disable(struct issei_device *idev)
+{
+ u32 reg = heci_hcsr_read(idev);
+
+ heci_irq_disable(idev, reg);
+}
+
+static void issei_heci_irq_sync(struct issei_device *idev)
+{
+ synchronize_irq(to_heci_hw(idev)->irq);
+}
+
+/**
+ * issei_heci_hw_reset_release - release device from the reset
+ * @idev: the device structure
+ */
+static void issei_heci_hw_reset_release(struct issei_device *idev)
+{
+ u32 reg = heci_hcsr_read(idev);
+
+ reg |= H_CSR_IG;
+ reg &= ~H_CSR_RST;
+ heci_hcsr_set(idev, reg);
+}
+
+/**
+ * heci_hw_is_ready - check whether the hw has turned ready
+ * @idev: the device structure
+ *
+ * Return: bool
+ */
+static bool heci_hw_is_ready(struct issei_device *idev)
+{
+ u32 reg = heci_fwcsr_read(idev);
+
+ return reg & FW_CSR_RDY;
+}
+
+/**
+ * heci_hw_is_resetting - check whether the hw is in reset
+ * @idev: the device structure
+ *
+ * Return: bool
+ */
+static bool heci_hw_is_resetting(struct issei_device *idev)
+{
+ u32 reg = heci_fwcsr_read(idev);
+
+ return reg & FW_CSR_RST;
+}
+
+/**
+ * issei_heci_host_set_ready - enable device
+ * @idev: the device structure
+ */
+static void issei_heci_host_set_ready(struct issei_device *idev)
+{
+ u32 reg = heci_hcsr_read(idev);
+
+ reg |= H_CSR_IE | H_CSR_IG | H_CSR_RDY;
+ heci_hcsr_set(idev, reg);
+}
+
+/**
+ * issei_heci_hw_reset - resets fw via heci csr register.
+ * @idev: the device structure
+ * @enable: if interrupt should be enabled after reset.
+ *
+ * Return: 0 on success an error code otherwise
+ */
+static int issei_heci_hw_reset(struct issei_device *idev, bool enable)
+{
+ u32 reg;
+
+ if (enable)
+ issei_heci_irq_enable(idev);
+
+ reg = heci_hcsr_read(idev);
+ /*
+ * H_CSR_RST may be found lit before reset is started,
+ * for example if preceding reset flow hasn't completed.
+ * In that case asserting H_CSR_RST will be ignored, therefore
+ * we need to clean H_CSR_RST bit to start a successful reset sequence.
+ */
+ if (reg & H_CSR_RST) {
+ dev_warn(&idev->dev, "H_CSR_RST is set = 0x%08X", reg);
+ reg &= ~H_CSR_RST;
+ heci_hcsr_set(idev, reg);
+ reg = heci_hcsr_read(idev);
+ }
+
+ reg |= H_CSR_RST | H_CSR_IG | H_CSR_IS;
+
+ if (!enable)
+ reg &= ~H_CSR_IE;
+
+ heci_hcsr_write(idev, reg);
+
+ /*
+ * Host reads the H_CSR once to ensure that the
+ * posted write to H_CSR completes.
+ */
+ reg = heci_hcsr_read(idev);
+
+ if (!(reg & H_CSR_RST))
+ dev_warn(&idev->dev, "H_CSR_RST is not set = 0x%08X", reg);
+
+ if (reg & H_CSR_RDY)
+ dev_warn(&idev->dev, "H_CSR_RDY is not cleared 0x%08X", reg);
+
+ if (!enable)
+ issei_heci_hw_reset_release(idev);
+ return 0;
+}
+
+/**
+ * issei_heci_irq_write_generate - generate interrupt to signal write completion.
+ * @idev: the device structure
+ *
+ * Return: 0 on success, -EIO if hardware is not ready and requires reset
+ */
+static int issei_heci_irq_write_generate(struct issei_device *idev)
+{
+ struct issei_heci_hw *hw = to_heci_hw(idev);
+
+ scoped_guard(spinlock_irqsave, &hw->access_lock)
+ heci_hcsr_set_hig(idev);
+ if (!heci_hw_is_ready(idev))
+ return -EIO;
+ return 0;
+}
+
+/**
+ * issei_heci_hw_config - initial hardware configuration.
+ * @idev: the device structure
+ *
+ * Return: 0 always
+ */
+static int issei_heci_hw_config(struct issei_device *idev)
+{
+ struct issei_heci_hw *hw = to_heci_hw(idev);
+ u32 reg;
+
+ /* Doesn't change in runtime */
+ reg = heci_hcsr_read(idev);
+ hw->hbuf_depth = FIELD_GET(H_CSR_CBD, reg);
+
+ return 0;
+}
+
+/**
+ * heci_write_hbuf - write to hardware buffer
+ * @idev: the device structure
+ * @data: data to write
+ * @data_len: data size
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int heci_write_hbuf(struct issei_device *idev, const void *data, size_t data_len)
+{
+ struct issei_heci_hw *hw = to_heci_hw(idev);
+
+ if (!IS_ALIGNED(data_len, CB_SLOT_SIZE)) {
+ dev_err(&idev->dev, "Data size %zu not aligned to slot size %lu\n",
+ data_len, CB_SLOT_SIZE);
+ return -EINVAL;
+ }
+
+ scoped_guard(spinlock_irqsave, &hw->access_lock) {
+ const u32 *reg_buf = data;
+ size_t i;
+
+ for (i = 0; i < data_len / CB_SLOT_SIZE; i++)
+ heci_hcbww_write(hw, reg_buf[i]);
+ }
+
+ return issei_heci_irq_write_generate(idev);
+}
+
+/**
+ * heci_read_hbuf - read data from hardware buffer
+ * @idev: the device structure
+ * @data: buffer to store read data
+ * @data_len: buffer size
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int heci_read_hbuf(struct issei_device *idev, void *data, size_t data_len)
+{
+ struct issei_heci_hw *hw = to_heci_hw(idev);
+ u32 *reg_buf = data;
+
+ if (!IS_ALIGNED(data_len, CB_SLOT_SIZE)) {
+ dev_err(&idev->dev, "Data size %zu not aligned to slot size %lu\n",
+ data_len, CB_SLOT_SIZE);
+ return -EINVAL;
+ }
+
+ scoped_guard(spinlock_irqsave, &hw->access_lock) {
+ for (; data_len >= CB_SLOT_SIZE; data_len -= CB_SLOT_SIZE)
+ *reg_buf++ = heci_fwcbrw_read(hw);
+ }
+ return 0;
+}
+
+/**
+ * issei_heci_setup_message_send - send setup message to firmware
+ * @idev: the device structure
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int issei_heci_setup_message_send(struct issei_device *idev)
+{
+ struct ham_setup_shared_memory_req req = {
+ .msg_id = HAM_CB_MESSAGE_ID_REQ,
+ .ver = HAM_CB_MESSAGE_VER,
+ .reserved = 0,
+ .buffer_physical_address = idev->dma.daddr,
+ .host_to_fw_section_length = idev->dma.length.h2f,
+ .fw_to_host_section_length = idev->dma.length.f2h,
+ .control_length = idev->dma.length.ctl
+ };
+ int ret;
+
+ ret = heci_write_hbuf(idev, &req, sizeof(req));
+ if (ret)
+ dev_err(&idev->dev, "Shared memory req write failed ret = %d\n", ret);
+
+ return ret;
+}
+
+/**
+ * issei_heci_setup_message_recv - receive setup message response from firmware
+ * @idev: the device structure
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int issei_heci_setup_message_recv(struct issei_device *idev)
+{
+ struct ham_setup_shared_memory_res res;
+ int ret;
+
+ if (heci_count_full_read_slots(idev) != sizeof(res) / CB_SLOT_SIZE) {
+ dev_dbg(&idev->dev, "Setup response is not fully received\n");
+ return -ENODATA;
+ }
+
+ ret = heci_read_hbuf(idev, &res, sizeof(res));
+ if (ret) {
+ dev_err(&idev->dev, "Shared memory res read failed ret = %d\n", ret);
+ return ret;
+ }
+
+ if (res.msg_id != HAM_CB_MESSAGE_ID_RES) {
+ dev_err(&idev->dev, "Shared memory res header 0x%x != 0x%x\n",
+ res.msg_id, HAM_CB_MESSAGE_ID_RES);
+ return -EPROTO;
+ }
+ if (res.status != 0) {
+ dev_err(&idev->dev, "Shared memory res status %d != 0\n", res.status);
+ return -EPROTO;
+ }
+
+ return 0;
+}
+
+static const struct issei_hw_ops hw_heci_ops = {
+ .irq_clear = issei_heci_irq_clear,
+ .irq_enable = issei_heci_irq_enable,
+ .irq_disable = issei_heci_irq_disable,
+ .irq_sync = issei_heci_irq_sync,
+
+ .hw_reset = issei_heci_hw_reset,
+ .hw_config = issei_heci_hw_config,
+ .hw_reset_release = issei_heci_hw_reset_release,
+ .host_set_ready = issei_heci_host_set_ready,
+
+ .hw_is_ready = heci_hw_is_ready,
+
+ .setup_message_send = issei_heci_setup_message_send,
+ .setup_message_recv = issei_heci_setup_message_recv,
+ .irq_write_generate = issei_heci_irq_write_generate,
+};
+
+const struct issei_hw_ops *issei_heci_get_ops(void)
+{
+ return &hw_heci_ops;
+}
+
+irqreturn_t issei_heci_irq_quick_handler(int irq, void *dev_id)
+{
+ struct issei_device *idev = dev_id;
+ struct issei_heci_hw *hw = to_heci_hw(idev);
+ u32 reg;
+
+ reg = heci_hcsr_read(idev);
+ if (!heci_irq_src(reg))
+ return IRQ_NONE;
+
+ scoped_guard(spinlock_irqsave, &hw->access_lock) {
+ reg = heci_hcsr_read(idev);
+ heci_irq_clear(idev, reg);
+
+ if (heci_hw_is_resetting(idev))
+ heci_hcsr_set_hig(idev);
+ }
+
+ dev_dbg(&idev->dev, "interrupt source 0x%08X\n", heci_irq_src(reg));
+
+ issei_poke_process_thread(idev);
+
+ return IRQ_HANDLED;
+}
+
+static const struct hw_heci_cfg hw_heci_pch_cfg = {
+ .dma_length.h2f = SZ_64K,
+ .dma_length.f2h = SZ_64K,
+ .dma_length.ctl = SZ_4K,
+};
+
+const struct hw_heci_cfg *issei_heci_get_cfg(kernel_ulong_t idx)
+{
+ return &hw_heci_pch_cfg;
+}
+
+/**
+ * issei_heci_dev_init - initializes the issei device structure with hw_heci
+ * @idev: device structure
+ * @mem_addr: memory address on bar
+ * @cfg: per device generation config
+ */
+void issei_heci_dev_init(struct issei_device *idev,
+ void __iomem *mem_addr, const struct hw_heci_cfg *cfg)
+{
+ struct issei_heci_hw *hw = to_heci_hw(idev);
+
+ spin_lock_init(&hw->access_lock);
+ hw->mem_addr = mem_addr;
+ hw->cfg = cfg;
+}
diff --git a/drivers/misc/issei/hw_heci.h b/drivers/misc/issei/hw_heci.h
new file mode 100644
index 000000000000..0ae2faeedf12
--- /dev/null
+++ b/drivers/misc/issei/hw_heci.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2023-2026 Intel Corporation */
+#ifndef _ISSEI_HW_HECI_H_
+#define _ISSEI_HW_HECI_H_
+#include <linux/irqreturn.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
+#include "issei_dev.h"
+
+/*
+ * hw_heci_cfg - issei heci device configuration
+ *
+ * @dma_length: DMA area length
+ */
+struct hw_heci_cfg {
+ const struct issei_dma_length dma_length;
+};
+
+/**
+ * struct issei_heci_hw - issei heci hw specific data
+ *
+ * @cfg: per device generation config and ops
+ * @mem_addr: io memory address
+ * @irq: device irq number
+ * @access_lock: spinlock to protect hw access
+ * @hbuf_depth: depth of hardware host/write buffer in slots
+ */
+struct issei_heci_hw {
+ const struct hw_heci_cfg *cfg;
+ void __iomem *mem_addr;
+ int irq;
+ spinlock_t access_lock;
+ u8 hbuf_depth;
+};
+
+#define to_heci_hw(dev) ((struct issei_heci_hw *)(dev)->hw)
+
+const struct hw_heci_cfg *issei_heci_get_cfg(kernel_ulong_t idx);
+const struct issei_hw_ops *issei_heci_get_ops(void);
+
+void issei_heci_dev_init(struct issei_device *idev,
+ void __iomem *mem_addr, const struct hw_heci_cfg *cfg);
+
+irqreturn_t issei_heci_irq_quick_handler(int irq, void *dev_id);
+
+#endif /* _ISSEI_HW_HECI_H_ */
diff --git a/drivers/misc/issei/hw_heci_regs.h b/drivers/misc/issei/hw_heci_regs.h
new file mode 100644
index 000000000000..a991b670a9d3
--- /dev/null
+++ b/drivers/misc/issei/hw_heci_regs.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2023-2026 Intel Corporation */
+#ifndef _ISSEI_HW_HECI_REGS_H_
+#define _ISSEI_HW_HECI_REGS_H_
+
+#include <linux/bits.h>
+
+/* H_CB_WW - Host Circular Buffer (CB) Write Window register */
+#define H_CB_WW 0x0
+/* H_CSR - Host Control Status register */
+#define H_CSR 0x4
+#define H_CSR_CBD GENMASK(31, 24) /* Host Circular Buffer Depth */
+#define H_CSR_CBWP GENMASK(23, 16) /* Host Circular Buffer Write Pointer */
+#define H_CSR_CBRP GENMASK(15, 8) /* Host Circular Buffer Read Pointer */
+#define H_CSR_RST BIT(4) /* Host Reset */
+#define H_CSR_RDY BIT(3) /* Host Ready */
+#define H_CSR_IG BIT(2) /* Host Interrupt Generate */
+#define H_CSR_IS BIT(1) /* Host Interrupt Status */
+#define H_CSR_IE BIT(0) /* Host Interrupt Enable */
+/* FW_CB_RW - FW Circular Buffer Read Window register (read only) */
+#define FW_CB_RW 0x8
+/* FW_CSR_HA - FW Control Status Host Access register (read only) */
+#define FW_CSR_HA 0xC
+#define FW_CSR_CBD GENMASK(31, 24) /* FW CB (Circular Buffer) Depth */
+#define FW_CSR_CBWP GENMASK(23, 16) /* FW CB Write Pointer */
+#define FW_CSR_CBRP GENMASK(15, 8) /* FW CB Read Pointer */
+#define FW_CSR_RST BIT(4) /* FW Reset */
+#define FW_CSR_RDY BIT(3) /* FW Ready */
+#define FW_CSR_IG BIT(2) /* FW Interrupt Generate */
+#define FW_CSR_IS BIT(1) /* FW Interrupt Status */
+#define FW_CSR_IE BIT(0) /* FW Interrupt Enable */
+
+#define CB_SLOT_SIZE sizeof(u32) /* Circular Buffer windows size */
+
+#endif /* _ISSEI_HW_HECI_REGS_H_ */
diff --git a/drivers/misc/issei/hw_msg.h b/drivers/misc/issei/hw_msg.h
new file mode 100644
index 000000000000..28fd3775f64c
--- /dev/null
+++ b/drivers/misc/issei/hw_msg.h
@@ -0,0 +1,163 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2023-2026 Intel Corporation */
+#ifndef _ISSEI_HW_MSG_H_
+#define _ISSEI_HW_MSG_H_
+
+#include <linux/types.h>
+#include <linux/uuid.h>
+
+#define HAM_CB_MESSAGE_ID_REQ 0x8086cafe
+#define HAM_CB_MESSAGE_ID_RES 0xcafe8086
+#define HAM_CB_MESSAGE_VER 0x1
+
+/**
+ * struct ham_setup_shared_memory_req - shared memory setup request
+ * @msg_id: message id, should be %HAM_CB_MESSAGE_ID_REQ
+ * @ver: message version (%HAM_CB_MESSAGE_VER)
+ * @reserved: reserved
+ * @buffer_physical_address: physical address of DMA buffer
+ * @host_to_fw_section_length: memory size for host to fw communication
+ * @fw_to_host_section_length: memory size for fw to host communication
+ * @control_length: memory size for control buffer
+ */
+struct ham_setup_shared_memory_req {
+ u32 msg_id;
+ u16 ver;
+ u16 reserved;
+ u64 buffer_physical_address;
+ u32 host_to_fw_section_length;
+ u32 fw_to_host_section_length;
+ u32 control_length;
+} __packed __aligned(4);
+
+/**
+ * struct ham_setup_shared_memory_res - shared memory setup response
+ * @msg_id: message id, should be %HAM_CB_MESSAGE_ID_RES
+ * @status: operation status
+ */
+struct ham_setup_shared_memory_res {
+ u32 msg_id;
+ u32 status;
+};
+
+/**
+ * struct control_buffer - control buffer structure
+ * @h2f_counter_wr: write counter host to fw
+ * @h2f_counter_rd: read counter host to fw
+ * @f2h_counter_wr: write counter fw to host
+ * @f2h_counter_rd: read counter fw to host
+ */
+struct control_buffer {
+ u32 h2f_counter_wr;
+ u32 h2f_counter_rd;
+ u32 f2h_counter_wr;
+ u32 f2h_counter_rd;
+};
+
+/* HAM messages over DMA */
+
+/**
+ * struct ham_message_header - message header over DMA
+ * @length: message length (payload only, not including header)
+ * @fw_id: firmware client id (0 means Bus Message)
+ * @host_id: host client id (0 means Bus Message)
+ * @flags: message flags
+ * @status: operation status
+ * @reserved: reserved
+ */
+struct ham_message_header {
+ u32 length;
+ u16 fw_id;
+ u16 host_id;
+ u32 flags;
+ u32 status;
+ u32 reserved;
+};
+
+/* Bus Commands */
+#define HAM_BUS_CMD_START_REQ 0x00
+#define HAM_BUS_CMD_START_RSP 0x80
+#define HAM_BUS_CMD_CLIENT_REQ 0x01
+#define HAM_BUS_CMD_CLIENT_RSP 0x81
+
+/**
+ * struct ham_bus_message - bus message header
+ * @cmd: command code
+ */
+struct ham_bus_message {
+ u32 cmd;
+};
+
+#define HAM_SUPPORTED_VERSION 0x01
+
+/**
+ * struct ham_start_message_req - start message
+ * @header: bus message header (%HAM_BUS_CMD_START_REQ)
+ * @supported_version: supported protocol version
+ * @heci_capabilities_length: protocol capabilities length in bytes
+ * @heci_capabilities: protocol capabilities data
+ */
+struct ham_start_message_req {
+ struct ham_bus_message header;
+ u16 supported_version;
+ u8 heci_capabilities_length;
+ u8 heci_capabilities[] __counted_by(heci_capabilities_length);
+} __packed;
+
+/**
+ * struct ham_start_message_res - start message response
+ * @header: bus message header (%HAM_BUS_CMD_START_RSP)
+ * @fw_version: firmware version (four u16 blocks)
+ * @supported_version: supported protocol version
+ * @heci_capabilities_length: protocol capabilities length in bytes
+ * @heci_capabilities: protocol capabilities data
+ */
+struct ham_start_message_res {
+ struct ham_bus_message header;
+ u16 fw_version[4];
+ u16 supported_version;
+ u8 heci_capabilities_length;
+ u8 heci_capabilities[] __counted_by(heci_capabilities_length);
+} __packed;
+
+/**
+ * struct ham_get_clients_req - clients list request
+ * @header: bus message header (%HAM_BUS_CMD_CLIENT_REQ)
+ */
+struct ham_get_clients_req {
+ struct ham_bus_message header;
+};
+
+/**
+ * struct ham_client_properties - single client properties
+ * @client_number: client id in firmware
+ * @protocol_ver: client protocol version
+ * @reserved: reserved
+ * @client_uuid: protocol name (UUID)
+ * @client_mtu: max message length supported by client
+ * @flags: client flags
+ */
+struct ham_client_properties {
+ u16 client_number;
+ u8 protocol_ver;
+ u8 reserved;
+ uuid_t client_uuid;
+ u32 client_mtu;
+ u32 flags;
+};
+
+/**
+ * struct ham_get_clients_res - client properties response
+ * @header: bus message header (%HAM_BUS_CMD_CLIENT_RSP)
+ * @client_count: number of clients in firmware
+ * @reserved: reserved
+ * @clients_props: list of client properties
+ */
+struct ham_get_clients_res {
+ struct ham_bus_message header;
+ u16 client_count;
+ u16 reserved;
+ struct ham_client_properties clients_props[] __counted_by(client_count);
+};
+
+#endif /* _ISSEI_HW_MSG_H_ */
diff --git a/drivers/misc/issei/issei_dev.h b/drivers/misc/issei/issei_dev.h
new file mode 100644
index 000000000000..b47c4a7c2da4
--- /dev/null
+++ b/drivers/misc/issei/issei_dev.h
@@ -0,0 +1,163 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2023-2026 Intel Corporation */
+#ifndef _ISSEI_DEV_H_
+#define _ISSEI_DEV_H_
+
+#include <linux/device.h>
+#include <linux/mutex.h>
+#include <linux/sched.h>
+#include <linux/time64.h>
+#include <linux/types.h>
+#include <linux/wait.h>
+
+#include "dma.h"
+
+struct cdev;
+struct kset;
+
+struct issei_device;
+struct issei_host_client;
+
+extern struct class *issei_class;
+
+#define ISSEI_HOST_CLIENTS_MAX 255
+
+#define ISSEI_SUPPORTED_PROTOCOL_VER 1
+
+#define ISSEI_MAX_CONSEC_RESET 3
+
+#define ISSEI_RST_HW_READY_TIMEOUT_MSEC (2 * MSEC_PER_SEC)
+#define ISSEI_RST_STEP_TIMEOUT_MSEC (2 * MSEC_PER_SEC)
+#define ISSEI_STOP_TIMEOUT_MSEC 500
+#define ISSEI_WRITE_TIMEOUT_MSEC (MSEC_PER_SEC)
+
+/**
+ * struct issei_write_buf - write buffer object
+ * @list: linked list pointer
+ * @cl: host client that requested this write
+ * @data: data to write
+ * @data_size: data size
+ */
+struct issei_write_buf {
+ struct list_head list;
+ struct issei_host_client *cl;
+ const u8 *data;
+ size_t data_size;
+};
+
+/**
+ * struct issei_hw_ops - callbacks for hardware operations
+ * @irq_clear: clear irq
+ * @irq_enable: enable irq
+ * @irq_disable: disable irq
+ * @irq_sync: sync irq
+ * @hw_reset: initiate hardware reset
+ * @hw_config: initial hardware config
+ * @hw_is_ready: check if hardware is ready
+ * @hw_reset_release: release hardware from reset
+ * @host_set_ready: set host ready indicator
+ * @setup_message_send: send setup message
+ * @setup_message_recv: receive setup message
+ * @irq_write_generate: generate interrupt on write complete
+ */
+struct issei_hw_ops {
+ void (*irq_clear)(struct issei_device *idev);
+ void (*irq_enable)(struct issei_device *idev);
+ void (*irq_disable)(struct issei_device *idev);
+ void (*irq_sync)(struct issei_device *idev);
+ int (*hw_reset)(struct issei_device *idev, bool enable);
+ int (*hw_config)(struct issei_device *idev);
+ bool (*hw_is_ready)(struct issei_device *idev);
+ void (*hw_reset_release)(struct issei_device *idev);
+ void (*host_set_ready)(struct issei_device *idev);
+ int (*setup_message_send)(struct issei_device *idev);
+ int (*setup_message_recv)(struct issei_device *idev);
+ int (*irq_write_generate)(struct issei_device *idev);
+};
+
+/**
+ * enum issei_rst_state: driver reset flow states
+ * @ISSEI_RST_STATE_INIT: initial state
+ * @ISSEI_RST_STATE_HW_READY: waiting for HW to be ready
+ * @ISSEI_RST_STATE_SETUP: waiting for channel setup completion
+ * @ISSEI_RST_STATE_START: waiting for start handshake completion
+ * @ISSEI_RST_STATE_CLIENT_ENUM: waiting for client enumeration
+ * @ISSEI_RST_STATE_DONE: reset flow is done
+ * @ISSEI_RST_STATE_DISABLED: flow is disabled
+ */
+enum issei_rst_state {
+ ISSEI_RST_STATE_INIT,
+ ISSEI_RST_STATE_HW_READY,
+ ISSEI_RST_STATE_SETUP,
+ ISSEI_RST_STATE_START,
+ ISSEI_RST_STATE_CLIENT_ENUM,
+ ISSEI_RST_STATE_DONE,
+ ISSEI_RST_STATE_DISABLED,
+};
+
+/**
+ * struct issei_device - issei device
+ * @parent: parent device object
+ * @dev: associated device object
+ * @cdev: character device
+ * @minor: allocated minor number
+ * @wait_has_data: wait queue for data
+ * @has_data: there are data to process
+ * @power_down: device is powering down
+ * @wait_rst_state: waitqueue for reset state processing
+ * @rst_state: reset state
+ * @fw_protocol_ver: protocol version
+ * @fw_version: firmware version
+ * @process_thread: worker thread
+ * @reset_count: number of consecutive link reset attempts
+ * @all_reset_count: cumilative number of link reset attempts
+ * @client_lock: mutex to protect client lists and write queue
+ * @host_client_list: host clients list
+ * @host_client_last_id: last allocated host client id
+ * @host_client_count: number of active host clients
+ * @fw_client_list: firmware clients list
+ * @write_queue: write queue
+ * @last_write_ts: last write timestamp
+ * @dma: DMA memory configuration
+ * @ops: hardware operations
+ * @hw: hw-specific data
+ */
+struct issei_device {
+ struct device *parent;
+ struct device dev;
+ struct cdev *cdev;
+ u32 minor;
+ wait_queue_head_t wait_has_data;
+ bool has_data;
+ bool power_down;
+ wait_queue_head_t wait_rst_state;
+ enum issei_rst_state rst_state;
+ u16 fw_protocol_ver;
+ u16 fw_version[4];
+ /* reset flow */
+ struct task_struct *process_thread;
+ u8 reset_count;
+ u8 all_reset_count;
+ /* clients */
+ struct mutex client_lock;
+ struct list_head host_client_list;
+ u16 host_client_last_id;
+ u8 host_client_count;
+ struct kset *fw_clients;
+ struct list_head fw_client_list;
+ struct list_head write_queue;
+ ktime_t last_write_ts;
+ struct issei_dma dma;
+ const struct issei_hw_ops *ops;
+ char hw[];
+};
+
+static inline void issei_poke_process_thread(struct issei_device *idev)
+{
+ WRITE_ONCE(idev->has_data, true);
+ wake_up_interruptible(&idev->wait_has_data);
+}
+
+int issei_start(struct issei_device *idev);
+void issei_stop(struct issei_device *idev);
+#endif /* _ISSEI_DEV_H_ */
diff --git a/drivers/misc/issei/main.c b/drivers/misc/issei/main.c
new file mode 100644
index 000000000000..5987fb340250
--- /dev/null
+++ b/drivers/misc/issei/main.c
@@ -0,0 +1,296 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2023-2026 Intel Corporation */
+#include <linux/atomic.h>
+#include <linux/device.h>
+#include <linux/dev_printk.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/jiffies.h>
+#include <linux/kthread.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+
+#include "cdev.h"
+#include "fw_client.h"
+#include "host_client.h"
+#include "ham.h"
+#include "issei_dev.h"
+
+static void issei_rst_state_set(struct issei_device *idev, enum issei_rst_state state)
+{
+ idev->rst_state = state;
+ /* wake up the thread */
+ if (waitqueue_active(&idev->wait_rst_state))
+ wake_up(&idev->wait_rst_state);
+}
+
+static int issei_reset(struct issei_device *idev)
+{
+ int ret;
+
+ idev->ops->irq_clear(idev);
+
+ issei_cl_all_disconnect(idev);
+ issei_fw_cl_remove_all(idev);
+ /* No need to check for overflow here, the counter is used only for info */
+ idev->all_reset_count++;
+ ret = idev->ops->hw_reset(idev, !idev->power_down);
+ issei_dmam_setup(idev);
+ if (ret) {
+ dev_err(&idev->dev, "hw_reset failed ret = %d\n", ret);
+ return ret;
+ }
+
+ if (idev->power_down) {
+ dev_dbg(&idev->dev, "powering down: end of reset\n");
+ issei_rst_state_set(idev, ISSEI_RST_STATE_DISABLED);
+ return -ENODEV;
+ }
+ return 0;
+}
+
+static int issei_process_read_msg(struct issei_device *idev)
+{
+ struct issei_dma_data data = {};
+ int ret;
+
+ ret = issei_dma_read(idev, &data);
+ if (ret)
+ return ret;
+
+ dev_dbg(&idev->dev, "Processing response %u %u %u %u\n", data.fw_id, data.host_id,
+ data.status, data.length);
+ if (data.status != HAMS_SUCCESS) {
+ dev_err(&idev->dev, "Command failed with status 0x%02X", data.status);
+ kfree(data.buf);
+ ret = -EIO;
+ } else {
+ if (issei_is_ham_rsp(data.fw_id, data.host_id))
+ ret = issei_ham_process_ham_rsp(idev, data.buf, data.length);
+ else
+ ret = issei_cl_read_buf(idev, data.fw_id, data.host_id,
+ data.buf, data.length);
+ }
+ idev->ops->irq_write_generate(idev);
+ return ret;
+}
+
+static int issei_process_write_msg(struct issei_device *idev)
+{
+ if (idev->rst_state != ISSEI_RST_STATE_DONE)
+ return 0;
+
+ return issei_cl_write_from_queue(idev);
+}
+
+static int issei_process_thread(void *_dev)
+{
+ long timeout, old_timeout = MAX_SCHEDULE_TIMEOUT;
+ struct issei_device *idev = _dev;
+ int ret;
+
+ while (!kthread_should_stop()) {
+ dev_dbg(&idev->dev, "process_work in %d\n", idev->rst_state);
+ if (!idev->ops->hw_is_ready(idev) && idev->rst_state > ISSEI_RST_STATE_HW_READY) {
+ if (!idev->power_down)
+ dev_dbg(&idev->dev, "HW not ready, resetting\n");
+ idev->rst_state = ISSEI_RST_STATE_INIT;
+ }
+ if (idev->power_down)
+ idev->rst_state = ISSEI_RST_STATE_INIT;
+ WRITE_ONCE(idev->has_data, false);
+ dev_dbg(&idev->dev, "reset_step in %d\n", idev->rst_state);
+ timeout = MAX_SCHEDULE_TIMEOUT;
+ ret = 0;
+ switch (idev->rst_state) {
+ case ISSEI_RST_STATE_DISABLED:
+ if (idev->power_down) {
+ dev_dbg(&idev->dev, "Interrupt in power down?\n");
+ break;
+ }
+ idev->rst_state = ISSEI_RST_STATE_INIT;
+ fallthrough;
+
+ case ISSEI_RST_STATE_INIT:
+ idev->ops->irq_clear(idev);
+ idev->ops->irq_sync(idev);
+
+ if (!idev->power_down) {
+ idev->reset_count++;
+ if (idev->reset_count > ISSEI_MAX_CONSEC_RESET) {
+ dev_err(&idev->dev, "reset: reached maximal consecutive resets: disabling the device\n");
+ issei_rst_state_set(idev, ISSEI_RST_STATE_DISABLED);
+ break;
+ }
+ }
+
+ ret = issei_reset(idev);
+ if (idev->power_down) {
+ dev_dbg(&idev->dev, "Powering down\n");
+ return 0;
+ }
+ if (ret)
+ break;
+
+ idev->rst_state = ISSEI_RST_STATE_HW_READY;
+ timeout = msecs_to_jiffies(ISSEI_RST_HW_READY_TIMEOUT_MSEC);
+ break;
+
+ case ISSEI_RST_STATE_HW_READY:
+ if (!idev->ops->hw_is_ready(idev)) {
+ dev_dbg(&idev->dev, "HW is not ready?\n");
+ timeout = old_timeout;
+ break;
+ }
+
+ dev_dbg(&idev->dev, "HW is ready\n");
+ idev->ops->hw_reset_release(idev);
+ idev->ops->host_set_ready(idev);
+ ret = idev->ops->setup_message_send(idev);
+ if (ret)
+ break;
+
+ idev->rst_state = ISSEI_RST_STATE_SETUP;
+ timeout = msecs_to_jiffies(ISSEI_RST_STEP_TIMEOUT_MSEC);
+ break;
+
+ case ISSEI_RST_STATE_SETUP:
+ ret = idev->ops->setup_message_recv(idev);
+ if (ret) {
+ if (ret == -ENODATA) {
+ ret = 0;
+ timeout = old_timeout;
+ }
+ } else {
+ timeout = msecs_to_jiffies(ISSEI_RST_STEP_TIMEOUT_MSEC);
+ ret = issei_ham_send_start_req(idev);
+ idev->rst_state = ISSEI_RST_STATE_START;
+ }
+ break;
+
+ case ISSEI_RST_STATE_START:
+ ret = issei_process_read_msg(idev);
+ if (!ret) {
+ timeout = msecs_to_jiffies(ISSEI_RST_STEP_TIMEOUT_MSEC);
+ idev->rst_state = ISSEI_RST_STATE_CLIENT_ENUM;
+ } else if (ret == -ENODATA) {
+ ret = 0;
+ timeout = old_timeout;
+ }
+ break;
+
+ case ISSEI_RST_STATE_CLIENT_ENUM:
+ ret = issei_process_read_msg(idev);
+ if (ret) {
+ if (ret == -ENODATA) {
+ ret = 0;
+ timeout = old_timeout;
+ }
+ } else {
+ idev->reset_count = 0;
+ idev->rst_state = ISSEI_RST_STATE_DONE;
+ dev_dbg(&idev->dev, "Reset finished successfully\n");
+ }
+ break;
+
+ case ISSEI_RST_STATE_DONE:
+ ret = issei_process_read_msg(idev);
+ if (ret != 0 && ret != -ENODATA)
+ break;
+
+ ret = issei_process_write_msg(idev);
+ break;
+ }
+
+ if (ret) {
+ dev_warn(&idev->dev, "Process failed ret = %d\n", ret);
+ idev->rst_state = ISSEI_RST_STATE_INIT;
+ continue;
+ }
+
+ /*
+ * Every thread that has data to process sets the 'has_data' flag and
+ * triggers the wait queue.
+ * The processing thread, in each loop iteration, resets 'has_data'
+ * and processes all available data.
+ *
+ * After processing, the thread waits for 'has_data' to be set again.
+ *
+ * If the wait function times out but 'has_data' becomes 1 before
+ * the subsequent atomic read check, this is acceptable from a flow
+ * perspective - the thread will continue processing the data.
+ *
+ * The 'has_data' flag cannot become 0 between the wait function and
+ * the atomic read check, since only this thread is allowed to reset it to 0.
+ */
+
+ old_timeout = wait_event_interruptible_timeout(idev->wait_has_data,
+ READ_ONCE(idev->has_data),
+ timeout);
+ if (idev->rst_state == ISSEI_RST_STATE_DISABLED)
+ continue;
+
+ if (!READ_ONCE(idev->has_data)) {
+ dev_warn(&idev->dev, "Timed out at state %d, resetting\n",
+ idev->rst_state);
+ idev->rst_state = ISSEI_RST_STATE_INIT;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * issei_start - configure HW device and start processing thread.
+ * @idev: the device structure
+ *
+ * Return: 0 on success, < 0 on failure
+ */
+int issei_start(struct issei_device *idev)
+{
+ int ret;
+
+ idev->power_down = false;
+
+ ret = issei_dmam_setup(idev);
+ if (ret)
+ return ret;
+
+ idev->ops->irq_clear(idev);
+
+ ret = idev->ops->hw_config(idev);
+ if (ret)
+ return ret;
+
+ idev->process_thread = kthread_run(issei_process_thread, idev,
+ "kisseiprocess/%s", dev_name(&idev->dev));
+ if (IS_ERR(idev->process_thread)) {
+ ret = PTR_ERR(idev->process_thread);
+ dev_err(&idev->dev, "unable to create process thread. ret = %d\n", ret);
+ return ret;
+ }
+
+ issei_poke_process_thread(idev);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(issei_start);
+
+/**
+ * issei_stop - stop interrupts and processing thread.
+ * @idev: the device structure
+ */
+void issei_stop(struct issei_device *idev)
+{
+ idev->power_down = true;
+
+ idev->ops->irq_clear(idev);
+ idev->ops->irq_sync(idev);
+
+ issei_poke_process_thread(idev);
+
+ wait_event_timeout(idev->wait_rst_state,
+ (idev->rst_state == ISSEI_RST_STATE_DISABLED),
+ msecs_to_jiffies(ISSEI_STOP_TIMEOUT_MSEC));
+ kthread_stop(idev->process_thread);
+}
+EXPORT_SYMBOL_GPL(issei_stop);
diff --git a/drivers/misc/issei/pci_heci.c b/drivers/misc/issei/pci_heci.c
new file mode 100644
index 000000000000..86c567a4bf0f
--- /dev/null
+++ b/drivers/misc/issei/pci_heci.c
@@ -0,0 +1,151 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2023-2026 Intel Corporation */
+#include <linux/device.h>
+#include <linux/dev_printk.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/pm.h>
+
+#include "cdev.h"
+#include "hw_heci.h"
+#include "hw_heci_regs.h"
+
+static int issei_heci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+ struct device *dev = &pdev->dev;
+ const struct hw_heci_cfg *cfg;
+ struct issei_device *idev;
+ struct issei_heci_hw *hw;
+ char __iomem *registers;
+ int err;
+
+ cfg = issei_heci_get_cfg(ent->driver_data);
+ if (!cfg)
+ return dev_err_probe(dev, -ENODEV, "no usable configuration.\n");
+
+ 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");
+
+ idev = issei_register(sizeof(*hw), dev, &cfg->dma_length, issei_heci_get_ops());
+ if (IS_ERR(idev))
+ return dev_err_probe(dev, PTR_ERR(idev), "register failure.\n");
+
+ issei_heci_dev_init(idev, registers, cfg);
+
+ pci_set_drvdata(pdev, idev);
+
+ err = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI);
+ if (err < 0) {
+ dev_err_probe(dev, err, "pci_alloc_irq_vectors failure.\n");
+ goto deregister;
+ }
+
+ hw = to_heci_hw(idev);
+ hw->irq = pci_irq_vector(pdev, 0);
+
+ err = request_threaded_irq(hw->irq,
+ issei_heci_irq_quick_handler,
+ NULL,
+ IRQF_SHARED, KBUILD_MODNAME, idev);
+ if (err)
+ goto release_irq;
+
+ err = issei_start(idev);
+ if (err) {
+ dev_err_probe(dev, err, "init hw failure.\n");
+ goto free_irq;
+ }
+
+ return 0;
+
+free_irq:
+ idev->ops->irq_disable(idev);
+ free_irq(hw->irq, idev);
+release_irq:
+ pci_free_irq_vectors(pdev);
+deregister:
+ issei_deregister(idev);
+ return err;
+}
+
+static void issei_heci_shutdown(struct pci_dev *pdev)
+{
+ struct issei_device *idev = pci_get_drvdata(pdev);
+ struct issei_heci_hw *hw = to_heci_hw(idev);
+
+ issei_stop(idev);
+
+ idev->ops->irq_disable(idev);
+ free_irq(hw->irq, idev);
+ pci_free_irq_vectors(pdev);
+}
+
+static void issei_heci_remove(struct pci_dev *pdev)
+{
+ issei_heci_shutdown(pdev);
+
+ issei_deregister(pci_get_drvdata(pdev));
+}
+
+static int issei_heci_pm_suspend(struct device *device)
+{
+ struct issei_device *idev = dev_get_drvdata(device);
+
+ issei_stop(idev);
+ idev->ops->irq_disable(idev);
+
+ return 0;
+}
+
+static int issei_heci_pm_resume(struct device *device)
+{
+ struct issei_device *idev = dev_get_drvdata(device);
+
+ return issei_start(idev);
+}
+
+static const struct dev_pm_ops issei_heci_pm_ops = {
+ SYSTEM_SLEEP_PM_OPS(issei_heci_pm_suspend, issei_heci_pm_resume)
+};
+
+static const struct pci_device_id heci_pci_tbl[] = {
+ {PCI_VDEVICE(INTEL, 0xA85D)}, /* Lunar Lake M */
+ {PCI_VDEVICE(INTEL, 0xE35D)}, /* Panther Lake H */
+ {PCI_VDEVICE(INTEL, 0xE45D)}, /* Panther Lake P */
+ {PCI_VDEVICE(INTEL, 0xD470)}, /* Nova Lake S */
+ {PCI_VDEVICE(INTEL, 0xD358)}, /* Nova Lake H */
+ {PCI_VDEVICE(INTEL, 0x4D5D)}, /* Wildcat Lake */
+ {}
+};
+MODULE_DEVICE_TABLE(pci, heci_pci_tbl);
+
+static struct pci_driver issei_heci_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = heci_pci_tbl,
+ .probe = issei_heci_probe,
+ .remove = issei_heci_remove,
+ .shutdown = issei_heci_shutdown,
+ .driver = {
+ .pm = &issei_heci_pm_ops,
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ },
+};
+module_pci_driver(issei_heci_driver);
+
+MODULE_DESCRIPTION("Intel(R) Silicon Security Engine Interface - HECI");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("INTEL_SSEI");
diff --git a/drivers/misc/keba/cp500.c b/drivers/misc/keba/cp500.c
index 6c65fbf22e75..c2ca63e40814 100644
--- a/drivers/misc/keba/cp500.c
+++ b/drivers/misc/keba/cp500.c
@@ -887,7 +887,7 @@ static int cp500_probe(struct pci_dev *pci_dev, const struct pci_device_id *id)
else
return -ENODEV;
- ret = pci_enable_device(pci_dev);
+ ret = pcim_enable_device(pci_dev);
if (ret)
return ret;
pci_set_master(pci_dev);
@@ -896,18 +896,15 @@ static int cp500_probe(struct pci_dev *pci_dev, const struct pci_device_id *id)
startup.end = startup.start + cp500->devs->startup.size - 1;
cp500->system_startup_addr = devm_ioremap_resource(&pci_dev->dev,
&startup);
- if (IS_ERR(cp500->system_startup_addr)) {
- ret = PTR_ERR(cp500->system_startup_addr);
- goto out_disable;
- }
+ if (IS_ERR(cp500->system_startup_addr))
+ return PTR_ERR(cp500->system_startup_addr);
cp500->msix_num = pci_alloc_irq_vectors(pci_dev, CP500_NUM_MSIX_NO_MMI,
CP500_NUM_MSIX, PCI_IRQ_MSIX);
if (cp500->msix_num < CP500_NUM_MSIX_NO_MMI) {
dev_err(&pci_dev->dev,
"Hardware does not support enough MSI-X interrupts\n");
- ret = -ENODEV;
- goto out_disable;
+ return -ENODEV;
}
cp500_vers = ioread32(cp500->system_startup_addr + CP500_VERSION_REG);
@@ -937,9 +934,6 @@ out_unregister_nvmem:
nvmem_unregister_notifier(&cp500->nvmem_notifier);
out_free_irq:
pci_free_irq_vectors(pci_dev);
-out_disable:
- pci_clear_master(pci_dev);
- pci_disable_device(pci_dev);
return ret;
}
@@ -962,9 +956,6 @@ static void cp500_remove(struct pci_dev *pci_dev)
pci_set_drvdata(pci_dev, 0);
pci_free_irq_vectors(pci_dev);
-
- pci_clear_master(pci_dev);
- pci_disable_device(pci_dev);
}
static struct pci_device_id cp500_ids[] = {
diff --git a/drivers/misc/lan966x_pci.c b/drivers/misc/lan966x_pci.c
index 0bb90c0943bf..b0949c653e5b 100644
--- a/drivers/misc/lan966x_pci.c
+++ b/drivers/misc/lan966x_pci.c
@@ -183,6 +183,7 @@ static int lan966x_pci_probe(struct pci_dev *pdev, const struct pci_device_id *i
return 0;
err_unload_overlay:
+ of_platform_depopulate(dev);
lan966x_pci_unload_overlay(data);
return ret;
}
diff --git a/drivers/misc/mei/gsc-me.c b/drivers/misc/mei/gsc-me.c
index 73d5beeb9c34..376840b2c354 100644
--- a/drivers/misc/mei/gsc-me.c
+++ b/drivers/misc/mei/gsc-me.c
@@ -101,10 +101,8 @@ static int mei_gsc_probe(struct auxiliary_device *aux_dev,
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);
+ if (ret)
goto err;
- }
}
ret = mei_register(dev, device);
diff --git a/drivers/misc/mei/mei_lb.c b/drivers/misc/mei/mei_lb.c
index f6a258c2b838..9fa69acf28d5 100644
--- a/drivers/misc/mei/mei_lb.c
+++ b/drivers/misc/mei/mei_lb.c
@@ -281,7 +281,7 @@ static int mei_lb_check_response_v2(const struct device *dev, ssize_t bytes,
bytes, sizeof(rsp->rheader));
return -ENOMSG;
}
- if (rsp->rheader.header.command_id != MEI_LB2_CMD) {
+ if (rsp->rheader.header.command_id != cpu_to_le32(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;
@@ -327,7 +327,7 @@ static int mei_lb_push_payload_v2(struct device *dev, struct mei_cl_device *clde
if (sent_data + chunk_size == payload_size)
last_chunk = MEI_LB2_FLAG_LST_CHUNK;
- req->header.command_id = MEI_LB2_CMD;
+ req->header.command_id = cpu_to_le32(MEI_LB2_CMD);
req->type = cpu_to_le32(type);
req->flags = cpu_to_le32(flags | first_chunk | last_chunk);
req->reserved = 0;
diff --git a/drivers/misc/mrvl_cn10k_dpi.c b/drivers/misc/mrvl_cn10k_dpi.c
index 7d5433121ff6..d26c53af05eb 100644
--- a/drivers/misc/mrvl_cn10k_dpi.c
+++ b/drivers/misc/mrvl_cn10k_dpi.c
@@ -470,10 +470,8 @@ static int dpi_irq_init(struct dpipf *dpi)
ret = devm_request_irq(dev, pci_irq_vector(pdev, DPI_MBOX_PF_VF_INT_IDX),
dpi_mbox_intr_handler, 0, "dpi-mbox", dpi);
- if (ret) {
- dev_err(dev, "DPI: request_irq failed for mbox; err=%d\n", ret);
+ if (ret)
return ret;
- }
dpi_reg_write(dpi, DPI_MBOX_VF_PF_INT_ENA_W1S, GENMASK_ULL(31, 0));
diff --git a/drivers/misc/nsm.c b/drivers/misc/nsm.c
index 3960506eb7ab..7d691f841deb 100644
--- a/drivers/misc/nsm.c
+++ b/drivers/misc/nsm.c
@@ -243,7 +243,7 @@ static int nsm_sendrecv_msg_locked(struct nsm *nsm)
goto cleanup;
}
- msg->resp.len = len;
+ msg->resp.len = min_t(unsigned int, len, sizeof(msg->resp.data));
rc = 0;
@@ -365,9 +365,8 @@ static long nsm_dev_ioctl(struct file *file, unsigned int cmd,
return -EINVAL;
/* Copy user argument struct to kernel argument struct */
- r = -EFAULT;
if (copy_from_user(&raw, argp, _IOC_SIZE(cmd)))
- return r;
+ return -EFAULT;
mutex_lock(&nsm->lock);
diff --git a/drivers/misc/open-dice.c b/drivers/misc/open-dice.c
index 45060fb4ea27..303b35b03cb4 100644
--- a/drivers/misc/open-dice.c
+++ b/drivers/misc/open-dice.c
@@ -2,6 +2,7 @@
/*
* Copyright (C) 2021 - Google LLC
* Author: David Brazdil <dbrazdil@google.com>
+ * Author: Song Guo <songguo@google.com>
*
* Driver for Open Profile for DICE.
*
@@ -19,6 +20,7 @@
* close(fd);
*/
+#include <linux/acpi.h>
#include <linux/io.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
@@ -31,7 +33,8 @@
struct open_dice_drvdata {
struct mutex lock;
char name[16];
- struct reserved_mem *rmem;
+ phys_addr_t mem_base;
+ resource_size_t mem_size;
struct miscdevice misc;
};
@@ -45,14 +48,14 @@ static int open_dice_wipe(struct open_dice_drvdata *drvdata)
void *kaddr;
mutex_lock(&drvdata->lock);
- kaddr = devm_memremap(drvdata->misc.this_device, drvdata->rmem->base,
- drvdata->rmem->size, MEMREMAP_WC);
+ kaddr = devm_memremap(drvdata->misc.this_device, drvdata->mem_base,
+ drvdata->mem_size, MEMREMAP_WC);
if (IS_ERR(kaddr)) {
mutex_unlock(&drvdata->lock);
return PTR_ERR(kaddr);
}
- memset(kaddr, 0, drvdata->rmem->size);
+ memset(kaddr, 0, drvdata->mem_size);
devm_memunmap(drvdata->misc.this_device, kaddr);
mutex_unlock(&drvdata->lock);
return 0;
@@ -64,7 +67,7 @@ static int open_dice_wipe(struct open_dice_drvdata *drvdata)
static ssize_t open_dice_read(struct file *filp, char __user *ptr, size_t len,
loff_t *off)
{
- unsigned long val = to_open_dice_drvdata(filp)->rmem->size;
+ unsigned long val = to_open_dice_drvdata(filp)->mem_size;
return simple_read_from_buffer(ptr, len, off, &val, sizeof(val));
}
@@ -102,8 +105,8 @@ static int open_dice_mmap_prepare(struct vm_area_desc *desc)
/* Create write-combine mapping so all clients observe a wipe. */
desc->page_prot = pgprot_writecombine(desc->page_prot);
vma_desc_set_flags(desc, VMA_DONTCOPY_BIT, VMA_DONTDUMP_BIT);
- mmap_action_simple_ioremap(desc, drvdata->rmem->base,
- drvdata->rmem->size);
+ mmap_action_simple_ioremap(desc, drvdata->mem_base,
+ drvdata->mem_size);
return 0;
}
@@ -118,22 +121,40 @@ static int __init open_dice_probe(struct platform_device *pdev)
{
static unsigned int dev_idx;
struct device *dev = &pdev->dev;
- struct reserved_mem *rmem;
struct open_dice_drvdata *drvdata;
+ phys_addr_t mem_base;
+ resource_size_t mem_size;
int ret;
- rmem = of_reserved_mem_lookup(dev->of_node);
- if (!rmem) {
- dev_err(dev, "failed to lookup reserved memory\n");
+ if (dev->of_node) {
+ struct reserved_mem *rmem = of_reserved_mem_lookup(dev->of_node);
+
+ if (!rmem) {
+ dev_err(dev, "failed to lookup reserved memory\n");
+ return -EINVAL;
+ }
+ mem_base = rmem->base;
+ mem_size = rmem->size;
+ } else if (is_acpi_node(dev->fwnode)) {
+ struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+ if (!res) {
+ dev_err(dev, "failed to get MMIO resource\n");
+ return -EINVAL;
+ }
+ mem_base = res->start;
+ mem_size = resource_size(res);
+ } else {
+ dev_err(dev, "device not supported (no DT or ACPI node)\n");
return -EINVAL;
}
- if (!rmem->size || (rmem->size > ULONG_MAX)) {
+ if (!mem_size || (mem_size > ULONG_MAX)) {
dev_err(dev, "invalid memory region size\n");
return -EINVAL;
}
- if (!PAGE_ALIGNED(rmem->base) || !PAGE_ALIGNED(rmem->size)) {
+ if (!PAGE_ALIGNED(mem_base) || !PAGE_ALIGNED(mem_size)) {
dev_err(dev, "memory region must be page-aligned\n");
return -EINVAL;
}
@@ -143,7 +164,8 @@ static int __init open_dice_probe(struct platform_device *pdev)
return -ENOMEM;
*drvdata = (struct open_dice_drvdata){
- .rmem = rmem,
+ .mem_base = mem_base,
+ .mem_size = mem_size,
.misc = (struct miscdevice){
.parent = dev,
.name = drvdata->name,
@@ -207,3 +229,4 @@ module_exit(open_dice_exit);
MODULE_DESCRIPTION("Driver for Open Profile for DICE.");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("David Brazdil <dbrazdil@google.com>");
+MODULE_AUTHOR("Song Guo <songguo@google.com>");
diff --git a/drivers/misc/pch_phub.c b/drivers/misc/pch_phub.c
index 19c4fa017f24..15785597da40 100644
--- a/drivers/misc/pch_phub.c
+++ b/drivers/misc/pch_phub.c
@@ -83,22 +83,16 @@
#define PCH_PHUB_OROM_SIZE 15360
+enum pch_phub_type {
+ PCH_EG20T,
+ PCH_ML7213,
+ PCH_ML7223M,
+ PCH_ML7223N,
+ PCH_ML7831,
+};
+
/**
* struct pch_phub_reg - PHUB register structure
- * @phub_id_reg: PHUB_ID register val
- * @q_pri_val_reg: QUEUE_PRI_VAL register val
- * @rc_q_maxsize_reg: RC_QUEUE_MAXSIZE register val
- * @bri_q_maxsize_reg: BRI_QUEUE_MAXSIZE register val
- * @comp_resp_timeout_reg: COMP_RESP_TIMEOUT register val
- * @bus_slave_control_reg: BUS_SLAVE_CONTROL_REG register val
- * @deadlock_avoid_type_reg: DEADLOCK_AVOID_TYPE register val
- * @intpin_reg_wpermit_reg0: INTPIN_REG_WPERMIT register 0 val
- * @intpin_reg_wpermit_reg1: INTPIN_REG_WPERMIT register 1 val
- * @intpin_reg_wpermit_reg2: INTPIN_REG_WPERMIT register 2 val
- * @intpin_reg_wpermit_reg3: INTPIN_REG_WPERMIT register 3 val
- * @int_reduce_control_reg: INT_REDUCE_CONTROL registers val
- * @clkcfg_reg: CLK CFG register val
- * @funcsel_reg: Function select register value
* @pch_phub_base_address: Register base address
* @pch_phub_extrom_base_address: external rom base address
* @pch_mac_start_address: MAC address area start address
@@ -107,25 +101,11 @@
* @pdev: pointer to pci device struct
*/
struct pch_phub_reg {
- u32 phub_id_reg;
- u32 q_pri_val_reg;
- u32 rc_q_maxsize_reg;
- u32 bri_q_maxsize_reg;
- u32 comp_resp_timeout_reg;
- u32 bus_slave_control_reg;
- u32 deadlock_avoid_type_reg;
- u32 intpin_reg_wpermit_reg0;
- u32 intpin_reg_wpermit_reg1;
- u32 intpin_reg_wpermit_reg2;
- u32 intpin_reg_wpermit_reg3;
- u32 int_reduce_control_reg[MAX_NUM_INT_REDUCE_CONTROL_REG];
- u32 clkcfg_reg;
- u32 funcsel_reg;
void __iomem *pch_phub_base_address;
void __iomem *pch_phub_extrom_base_address;
u32 pch_mac_start_address;
u32 pch_opt_rom_start_address;
- int ioh_type;
+ enum pch_phub_type ioh_type;
struct pci_dev *pdev;
};
@@ -344,10 +324,19 @@ static int pch_phub_write_gbe_mac_addr(struct pch_phub_reg *chip, u8 *data)
int retval;
int i;
- if ((chip->ioh_type == 1) || (chip->ioh_type == 5)) /* EG20T or ML7831*/
+ switch (chip->ioh_type) {
+ case PCH_EG20T:
+ case PCH_ML7831:
retval = pch_phub_gbe_serial_rom_conf(chip);
- else /* ML7223 */
+ break;
+
+ case PCH_ML7213:
+ case PCH_ML7223M:
+ case PCH_ML7223N:
retval = pch_phub_gbe_serial_rom_conf_mp(chip);
+ break;
+ }
+
if (retval)
return retval;
@@ -537,14 +526,6 @@ static const struct bin_attribute pch_bin_attr = {
.write = pch_phub_bin_write,
};
-enum {
- PCH_EG20T,
- PCH_ML7213,
- PCH_ML7223M,
- PCH_ML7223N,
- PCH_ML7831,
-};
-
static int pch_phub_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
diff --git a/drivers/misc/rp1/rp1_pci.c b/drivers/misc/rp1/rp1_pci.c
index 81685e3f3296..94ad022cd138 100644
--- a/drivers/misc/rp1/rp1_pci.c
+++ b/drivers/misc/rp1/rp1_pci.c
@@ -166,6 +166,9 @@ static void rp1_unregister_interrupts(struct pci_dev *pdev)
struct rp1_dev *rp1 = pci_get_drvdata(pdev);
int irq, i;
+ for (i = 0; i < RP1_INT_END; i++)
+ irq_set_chained_handler_and_data(pci_irq_vector(pdev, i), NULL, NULL);
+
if (rp1->domain) {
for (i = 0; i < RP1_INT_END; i++) {
irq = irq_find_mapping(rp1->domain, i);
@@ -191,13 +194,13 @@ static int rp1_probe(struct pci_dev *pdev, const struct pci_device_id *id)
if (!rp1_node) {
dev_err(dev, "Missing of_node for device\n");
err = -EINVAL;
- goto err_put_node;
+ goto err_out;
}
rp1 = devm_kzalloc(&pdev->dev, sizeof(*rp1), GFP_KERNEL);
if (!rp1) {
err = -ENOMEM;
- goto err_put_node;
+ goto err_out;
}
rp1->pdev = pdev;
@@ -206,21 +209,21 @@ static int rp1_probe(struct pci_dev *pdev, const struct pci_device_id *id)
dev_err(&pdev->dev,
"Not initialized - is the firmware running?\n");
err = -EINVAL;
- goto err_put_node;
+ goto err_out;
}
err = pcim_enable_device(pdev);
if (err < 0) {
err = dev_err_probe(&pdev->dev, err,
"Enabling PCI device has failed");
- goto err_put_node;
+ goto err_out;
}
rp1->bar1 = pcim_iomap(pdev, 1, 0);
if (!rp1->bar1) {
dev_err(&pdev->dev, "Cannot map PCI BAR\n");
err = -EIO;
- goto err_put_node;
+ goto err_out;
}
pci_set_master(pdev);
@@ -230,16 +233,16 @@ static int rp1_probe(struct pci_dev *pdev, const struct pci_device_id *id)
if (err < 0) {
err = dev_err_probe(&pdev->dev, err,
"Failed to allocate MSI-X vectors\n");
- goto err_put_node;
+ goto err_out;
} else if (err != RP1_INT_END) {
dev_err(&pdev->dev, "Cannot allocate enough interrupts\n");
err = -EINVAL;
- goto err_put_node;
+ goto err_out;
}
pci_set_drvdata(pdev, rp1);
- rp1->domain = irq_domain_add_linear(rp1_node, RP1_INT_END,
- &rp1_domain_ops, rp1);
+ rp1->domain = irq_domain_create_linear(of_fwnode_handle(rp1_node), RP1_INT_END,
+ &rp1_domain_ops, rp1);
if (!rp1->domain) {
dev_err(&pdev->dev, "Error creating IRQ domain\n");
err = -ENOMEM;
@@ -267,15 +270,11 @@ static int rp1_probe(struct pci_dev *pdev, const struct pci_device_id *id)
goto err_unregister_interrupts;
}
- of_node_put(rp1_node);
-
return 0;
err_unregister_interrupts:
rp1_unregister_interrupts(pdev);
-err_put_node:
- of_node_put(rp1_node);
-
+err_out:
return err;
}
diff --git a/drivers/misc/sgi-gru/grukservices.c b/drivers/misc/sgi-gru/grukservices.c
index 205945ce9e86..c16012ee976f 100644
--- a/drivers/misc/sgi-gru/grukservices.c
+++ b/drivers/misc/sgi-gru/grukservices.c
@@ -411,11 +411,14 @@ int gru_get_cb_exception_detail(void *cb,
cbe = get_cbe(GRUBASE(cb), cbrnum);
gru_flush_cache(cbe); /* CBE not coherent */
sync_core();
+ excdet->cb = (unsigned long)cb;
excdet->opc = cbe->opccpy;
excdet->exopc = cbe->exopccpy;
excdet->ecause = cbe->ecause;
excdet->exceptdet0 = cbe->idef1upd;
excdet->exceptdet1 = cbe->idef3upd;
+ excdet->cbrstate = cbe->cbrstate;
+ excdet->cbrexecstatus = cbe->cbrexecstatus;
gru_flush_cache(cbe);
return 0;
}
@@ -1154,4 +1157,3 @@ void gru_kservices_exit(void)
if (gru_free_kernel_contexts())
BUG();
}
-
diff --git a/drivers/misc/tps6594-esm.c b/drivers/misc/tps6594-esm.c
index 2fbd3fbdf713..50e121669c6d 100644
--- a/drivers/misc/tps6594-esm.c
+++ b/drivers/misc/tps6594-esm.c
@@ -62,7 +62,7 @@ static int tps6594_esm_probe(struct platform_device *pdev)
tps6594_esm_isr, IRQF_ONESHOT,
pdev->resource[i].name, pdev);
if (ret)
- return dev_err_probe(dev, ret, "Failed to request irq\n");
+ return ret;
}
ret = regmap_set_bits(tps->regmap, TPS6594_REG_ESM_SOC_MODE_CFG,
diff --git a/drivers/misc/tps6594-pfsm.c b/drivers/misc/tps6594-pfsm.c
index 44fa81d6cec2..c3a0d6367f2c 100644
--- a/drivers/misc/tps6594-pfsm.c
+++ b/drivers/misc/tps6594-pfsm.c
@@ -308,7 +308,7 @@ static int tps6594_pfsm_probe(struct platform_device *pdev)
tps6594_pfsm_isr, IRQF_ONESHOT,
pdev->resource[i].name, pdev);
if (ret)
- return dev_err_probe(dev, ret, "Failed to request irq\n");
+ return ret;
}
platform_set_drvdata(pdev, pfsm);
diff --git a/drivers/misc/xilinx_sdfec.c b/drivers/misc/xilinx_sdfec.c
index 3135ba3a58ee..a19eed39c516 100644
--- a/drivers/misc/xilinx_sdfec.c
+++ b/drivers/misc/xilinx_sdfec.c
@@ -456,10 +456,25 @@ static int xsdfec_get_turbo(struct xsdfec_dev *xsdfec, void __user *arg)
return err;
}
+static int xsdfec_ldpc_reg_addr(struct xsdfec_dev *xsdfec, u32 base, u32 high,
+ u32 offset, u32 *addr)
+{
+ if (high < base || offset > (high - base) / XSDFEC_LDPC_REG_JUMP) {
+ dev_dbg(xsdfec->dev,
+ "LDPC register offset %u outside space 0x%x-0x%x",
+ offset, base, high);
+ return -EINVAL;
+ }
+
+ *addr = base + offset * XSDFEC_LDPC_REG_JUMP;
+ return 0;
+}
+
static int xsdfec_reg0_write(struct xsdfec_dev *xsdfec, u32 n, u32 k, u32 psize,
u32 offset)
{
u32 wdata;
+ u32 addr;
if (n < XSDFEC_REG0_N_MIN || n > XSDFEC_REG0_N_MAX || psize == 0 ||
(n > XSDFEC_REG0_N_MUL_P * psize) || n <= k || ((n % psize) != 0)) {
@@ -476,17 +491,11 @@ static int xsdfec_reg0_write(struct xsdfec_dev *xsdfec, u32 n, u32 k, u32 psize,
k = k << XSDFEC_REG0_K_LSB;
wdata = k | n;
- if (XSDFEC_LDPC_CODE_REG0_ADDR_BASE + (offset * XSDFEC_LDPC_REG_JUMP) >
- XSDFEC_LDPC_CODE_REG0_ADDR_HIGH) {
- dev_dbg(xsdfec->dev, "Writing outside of LDPC reg0 space 0x%x",
- XSDFEC_LDPC_CODE_REG0_ADDR_BASE +
- (offset * XSDFEC_LDPC_REG_JUMP));
+ if (xsdfec_ldpc_reg_addr(xsdfec, XSDFEC_LDPC_CODE_REG0_ADDR_BASE,
+ XSDFEC_LDPC_CODE_REG0_ADDR_HIGH, offset,
+ &addr))
return -EINVAL;
- }
- xsdfec_regwrite(xsdfec,
- XSDFEC_LDPC_CODE_REG0_ADDR_BASE +
- (offset * XSDFEC_LDPC_REG_JUMP),
- wdata);
+ xsdfec_regwrite(xsdfec, addr, wdata);
return 0;
}
@@ -494,6 +503,7 @@ static int xsdfec_reg1_write(struct xsdfec_dev *xsdfec, u32 psize,
u32 no_packing, u32 nm, u32 offset)
{
u32 wdata;
+ u32 addr;
if (psize < XSDFEC_REG1_PSIZE_MIN || psize > XSDFEC_REG1_PSIZE_MAX) {
dev_dbg(xsdfec->dev, "Psize is not in range");
@@ -510,17 +520,11 @@ static int xsdfec_reg1_write(struct xsdfec_dev *xsdfec, u32 psize,
nm = (nm << XSDFEC_REG1_NM_LSB) & XSDFEC_REG1_NM_MASK;
wdata = nm | no_packing | psize;
- if (XSDFEC_LDPC_CODE_REG1_ADDR_BASE + (offset * XSDFEC_LDPC_REG_JUMP) >
- XSDFEC_LDPC_CODE_REG1_ADDR_HIGH) {
- dev_dbg(xsdfec->dev, "Writing outside of LDPC reg1 space 0x%x",
- XSDFEC_LDPC_CODE_REG1_ADDR_BASE +
- (offset * XSDFEC_LDPC_REG_JUMP));
+ if (xsdfec_ldpc_reg_addr(xsdfec, XSDFEC_LDPC_CODE_REG1_ADDR_BASE,
+ XSDFEC_LDPC_CODE_REG1_ADDR_HIGH, offset,
+ &addr))
return -EINVAL;
- }
- xsdfec_regwrite(xsdfec,
- XSDFEC_LDPC_CODE_REG1_ADDR_BASE +
- (offset * XSDFEC_LDPC_REG_JUMP),
- wdata);
+ xsdfec_regwrite(xsdfec, addr, wdata);
return 0;
}
@@ -529,6 +533,7 @@ static int xsdfec_reg2_write(struct xsdfec_dev *xsdfec, u32 nlayers, u32 nmqc,
u32 max_schedule, u32 offset)
{
u32 wdata;
+ u32 addr;
if (nlayers < XSDFEC_REG2_NLAYERS_MIN ||
nlayers > XSDFEC_REG2_NLAYERS_MAX) {
@@ -563,17 +568,11 @@ static int xsdfec_reg2_write(struct xsdfec_dev *xsdfec, u32 nlayers, u32 nmqc,
wdata = (max_schedule | no_final_parity | special_qc | norm_type |
nmqc | nlayers);
- if (XSDFEC_LDPC_CODE_REG2_ADDR_BASE + (offset * XSDFEC_LDPC_REG_JUMP) >
- XSDFEC_LDPC_CODE_REG2_ADDR_HIGH) {
- dev_dbg(xsdfec->dev, "Writing outside of LDPC reg2 space 0x%x",
- XSDFEC_LDPC_CODE_REG2_ADDR_BASE +
- (offset * XSDFEC_LDPC_REG_JUMP));
+ if (xsdfec_ldpc_reg_addr(xsdfec, XSDFEC_LDPC_CODE_REG2_ADDR_BASE,
+ XSDFEC_LDPC_CODE_REG2_ADDR_HIGH, offset,
+ &addr))
return -EINVAL;
- }
- xsdfec_regwrite(xsdfec,
- XSDFEC_LDPC_CODE_REG2_ADDR_BASE +
- (offset * XSDFEC_LDPC_REG_JUMP),
- wdata);
+ xsdfec_regwrite(xsdfec, addr, wdata);
return 0;
}
@@ -581,20 +580,15 @@ static int xsdfec_reg3_write(struct xsdfec_dev *xsdfec, u8 sc_off, u8 la_off,
u16 qc_off, u32 offset)
{
u32 wdata;
+ u32 addr;
wdata = ((qc_off << XSDFEC_REG3_QC_OFF_LSB) |
(la_off << XSDFEC_REG3_LA_OFF_LSB) | sc_off);
- if (XSDFEC_LDPC_CODE_REG3_ADDR_BASE + (offset * XSDFEC_LDPC_REG_JUMP) >
- XSDFEC_LDPC_CODE_REG3_ADDR_HIGH) {
- dev_dbg(xsdfec->dev, "Writing outside of LDPC reg3 space 0x%x",
- XSDFEC_LDPC_CODE_REG3_ADDR_BASE +
- (offset * XSDFEC_LDPC_REG_JUMP));
+ if (xsdfec_ldpc_reg_addr(xsdfec, XSDFEC_LDPC_CODE_REG3_ADDR_BASE,
+ XSDFEC_LDPC_CODE_REG3_ADDR_HIGH, offset,
+ &addr))
return -EINVAL;
- }
- xsdfec_regwrite(xsdfec,
- XSDFEC_LDPC_CODE_REG3_ADDR_BASE +
- (offset * XSDFEC_LDPC_REG_JUMP),
- wdata);
+ xsdfec_regwrite(xsdfec, addr, wdata);
return 0;
}
@@ -1390,10 +1384,8 @@ static int xsdfec_probe(struct platform_device *pdev)
err = devm_request_threaded_irq(dev, xsdfec->irq, NULL,
xsdfec_irq_thread, IRQF_ONESHOT,
"xilinx-sdfec16", xsdfec);
- if (err < 0) {
- dev_err(dev, "unable to request IRQ%d", xsdfec->irq);
+ if (err < 0)
goto err_xsdfec_dev;
- }
}
err = ida_alloc(&dev_nrs, GFP_KERNEL);