diff options
| author | Mark Brown <broonie@kernel.org> | 2026-07-27 15:53:05 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-07-27 15:53:05 +0100 |
| commit | 7a4d8f4733f8bf98677c8562a7477f32fb507ec7 (patch) | |
| tree | bfdf0d64102778875fda5eb51b889fb9e6dc9e4f /drivers/misc | |
| parent | 58a63fb3b3e074080cd9c29a3b7cb55ebfa5ab3e (diff) | |
| parent | 0957fbab972a9499626ac457fd924b24491c6315 (diff) | |
| download | linux-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')
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); |
