|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | /* | 
|  | * Driver for FPGA Management Engine Error Management | 
|  | * | 
|  | * Copyright 2019 Intel Corporation, Inc. | 
|  | * | 
|  | * Authors: | 
|  | *   Kang Luwei <luwei.kang@intel.com> | 
|  | *   Xiao Guangrong <guangrong.xiao@linux.intel.com> | 
|  | *   Wu Hao <hao.wu@intel.com> | 
|  | *   Joseph Grecco <joe.grecco@intel.com> | 
|  | *   Enno Luebbers <enno.luebbers@intel.com> | 
|  | *   Tim Whisonant <tim.whisonant@intel.com> | 
|  | *   Ananda Ravuri <ananda.ravuri@intel.com> | 
|  | *   Mitchel, Henry <henry.mitchel@intel.com> | 
|  | */ | 
|  |  | 
|  | #include <linux/fpga-dfl.h> | 
|  | #include <linux/uaccess.h> | 
|  |  | 
|  | #include "dfl.h" | 
|  | #include "dfl-fme.h" | 
|  |  | 
|  | #define FME_ERROR_MASK		0x8 | 
|  | #define FME_ERROR		0x10 | 
|  | #define MBP_ERROR		BIT_ULL(6) | 
|  | #define PCIE0_ERROR_MASK	0x18 | 
|  | #define PCIE0_ERROR		0x20 | 
|  | #define PCIE1_ERROR_MASK	0x28 | 
|  | #define PCIE1_ERROR		0x30 | 
|  | #define FME_FIRST_ERROR		0x38 | 
|  | #define FME_NEXT_ERROR		0x40 | 
|  | #define RAS_NONFAT_ERROR_MASK	0x48 | 
|  | #define RAS_NONFAT_ERROR	0x50 | 
|  | #define RAS_CATFAT_ERROR_MASK	0x58 | 
|  | #define RAS_CATFAT_ERROR	0x60 | 
|  | #define RAS_ERROR_INJECT	0x68 | 
|  | #define INJECT_ERROR_MASK	GENMASK_ULL(2, 0) | 
|  |  | 
|  | #define ERROR_MASK		GENMASK_ULL(63, 0) | 
|  |  | 
|  | static ssize_t pcie0_errors_show(struct device *dev, | 
|  | struct device_attribute *attr, char *buf) | 
|  | { | 
|  | struct dfl_feature_platform_data *pdata = dev_get_platdata(dev); | 
|  | void __iomem *base; | 
|  | u64 value; | 
|  |  | 
|  | base = dfl_get_feature_ioaddr_by_id(dev, FME_FEATURE_ID_GLOBAL_ERR); | 
|  |  | 
|  | mutex_lock(&pdata->lock); | 
|  | value = readq(base + PCIE0_ERROR); | 
|  | mutex_unlock(&pdata->lock); | 
|  |  | 
|  | return sprintf(buf, "0x%llx\n", (unsigned long long)value); | 
|  | } | 
|  |  | 
|  | static ssize_t pcie0_errors_store(struct device *dev, | 
|  | struct device_attribute *attr, | 
|  | const char *buf, size_t count) | 
|  | { | 
|  | struct dfl_feature_platform_data *pdata = dev_get_platdata(dev); | 
|  | void __iomem *base; | 
|  | int ret = 0; | 
|  | u64 v, val; | 
|  |  | 
|  | if (kstrtou64(buf, 0, &val)) | 
|  | return -EINVAL; | 
|  |  | 
|  | base = dfl_get_feature_ioaddr_by_id(dev, FME_FEATURE_ID_GLOBAL_ERR); | 
|  |  | 
|  | mutex_lock(&pdata->lock); | 
|  | writeq(GENMASK_ULL(63, 0), base + PCIE0_ERROR_MASK); | 
|  |  | 
|  | v = readq(base + PCIE0_ERROR); | 
|  | if (val == v) | 
|  | writeq(v, base + PCIE0_ERROR); | 
|  | else | 
|  | ret = -EINVAL; | 
|  |  | 
|  | writeq(0ULL, base + PCIE0_ERROR_MASK); | 
|  | mutex_unlock(&pdata->lock); | 
|  | return ret ? ret : count; | 
|  | } | 
|  | static DEVICE_ATTR_RW(pcie0_errors); | 
|  |  | 
|  | static ssize_t pcie1_errors_show(struct device *dev, | 
|  | struct device_attribute *attr, char *buf) | 
|  | { | 
|  | struct dfl_feature_platform_data *pdata = dev_get_platdata(dev); | 
|  | void __iomem *base; | 
|  | u64 value; | 
|  |  | 
|  | base = dfl_get_feature_ioaddr_by_id(dev, FME_FEATURE_ID_GLOBAL_ERR); | 
|  |  | 
|  | mutex_lock(&pdata->lock); | 
|  | value = readq(base + PCIE1_ERROR); | 
|  | mutex_unlock(&pdata->lock); | 
|  |  | 
|  | return sprintf(buf, "0x%llx\n", (unsigned long long)value); | 
|  | } | 
|  |  | 
|  | static ssize_t pcie1_errors_store(struct device *dev, | 
|  | struct device_attribute *attr, | 
|  | const char *buf, size_t count) | 
|  | { | 
|  | struct dfl_feature_platform_data *pdata = dev_get_platdata(dev); | 
|  | void __iomem *base; | 
|  | int ret = 0; | 
|  | u64 v, val; | 
|  |  | 
|  | if (kstrtou64(buf, 0, &val)) | 
|  | return -EINVAL; | 
|  |  | 
|  | base = dfl_get_feature_ioaddr_by_id(dev, FME_FEATURE_ID_GLOBAL_ERR); | 
|  |  | 
|  | mutex_lock(&pdata->lock); | 
|  | writeq(GENMASK_ULL(63, 0), base + PCIE1_ERROR_MASK); | 
|  |  | 
|  | v = readq(base + PCIE1_ERROR); | 
|  | if (val == v) | 
|  | writeq(v, base + PCIE1_ERROR); | 
|  | else | 
|  | ret = -EINVAL; | 
|  |  | 
|  | writeq(0ULL, base + PCIE1_ERROR_MASK); | 
|  | mutex_unlock(&pdata->lock); | 
|  | return ret ? ret : count; | 
|  | } | 
|  | static DEVICE_ATTR_RW(pcie1_errors); | 
|  |  | 
|  | static ssize_t nonfatal_errors_show(struct device *dev, | 
|  | struct device_attribute *attr, char *buf) | 
|  | { | 
|  | void __iomem *base; | 
|  |  | 
|  | base = dfl_get_feature_ioaddr_by_id(dev, FME_FEATURE_ID_GLOBAL_ERR); | 
|  |  | 
|  | return sprintf(buf, "0x%llx\n", | 
|  | (unsigned long long)readq(base + RAS_NONFAT_ERROR)); | 
|  | } | 
|  | static DEVICE_ATTR_RO(nonfatal_errors); | 
|  |  | 
|  | static ssize_t catfatal_errors_show(struct device *dev, | 
|  | struct device_attribute *attr, char *buf) | 
|  | { | 
|  | void __iomem *base; | 
|  |  | 
|  | base = dfl_get_feature_ioaddr_by_id(dev, FME_FEATURE_ID_GLOBAL_ERR); | 
|  |  | 
|  | return sprintf(buf, "0x%llx\n", | 
|  | (unsigned long long)readq(base + RAS_CATFAT_ERROR)); | 
|  | } | 
|  | static DEVICE_ATTR_RO(catfatal_errors); | 
|  |  | 
|  | static ssize_t inject_errors_show(struct device *dev, | 
|  | struct device_attribute *attr, char *buf) | 
|  | { | 
|  | struct dfl_feature_platform_data *pdata = dev_get_platdata(dev); | 
|  | void __iomem *base; | 
|  | u64 v; | 
|  |  | 
|  | base = dfl_get_feature_ioaddr_by_id(dev, FME_FEATURE_ID_GLOBAL_ERR); | 
|  |  | 
|  | mutex_lock(&pdata->lock); | 
|  | v = readq(base + RAS_ERROR_INJECT); | 
|  | mutex_unlock(&pdata->lock); | 
|  |  | 
|  | return sprintf(buf, "0x%llx\n", | 
|  | (unsigned long long)FIELD_GET(INJECT_ERROR_MASK, v)); | 
|  | } | 
|  |  | 
|  | static ssize_t inject_errors_store(struct device *dev, | 
|  | struct device_attribute *attr, | 
|  | const char *buf, size_t count) | 
|  | { | 
|  | struct dfl_feature_platform_data *pdata = dev_get_platdata(dev); | 
|  | void __iomem *base; | 
|  | u8 inject_error; | 
|  | u64 v; | 
|  |  | 
|  | if (kstrtou8(buf, 0, &inject_error)) | 
|  | return -EINVAL; | 
|  |  | 
|  | if (inject_error & ~INJECT_ERROR_MASK) | 
|  | return -EINVAL; | 
|  |  | 
|  | base = dfl_get_feature_ioaddr_by_id(dev, FME_FEATURE_ID_GLOBAL_ERR); | 
|  |  | 
|  | mutex_lock(&pdata->lock); | 
|  | v = readq(base + RAS_ERROR_INJECT); | 
|  | v &= ~INJECT_ERROR_MASK; | 
|  | v |= FIELD_PREP(INJECT_ERROR_MASK, inject_error); | 
|  | writeq(v, base + RAS_ERROR_INJECT); | 
|  | mutex_unlock(&pdata->lock); | 
|  |  | 
|  | return count; | 
|  | } | 
|  | static DEVICE_ATTR_RW(inject_errors); | 
|  |  | 
|  | static ssize_t fme_errors_show(struct device *dev, | 
|  | struct device_attribute *attr, char *buf) | 
|  | { | 
|  | struct dfl_feature_platform_data *pdata = dev_get_platdata(dev); | 
|  | void __iomem *base; | 
|  | u64 value; | 
|  |  | 
|  | base = dfl_get_feature_ioaddr_by_id(dev, FME_FEATURE_ID_GLOBAL_ERR); | 
|  |  | 
|  | mutex_lock(&pdata->lock); | 
|  | value = readq(base + FME_ERROR); | 
|  | mutex_unlock(&pdata->lock); | 
|  |  | 
|  | return sprintf(buf, "0x%llx\n", (unsigned long long)value); | 
|  | } | 
|  |  | 
|  | static ssize_t fme_errors_store(struct device *dev, | 
|  | struct device_attribute *attr, | 
|  | const char *buf, size_t count) | 
|  | { | 
|  | struct dfl_feature_platform_data *pdata = dev_get_platdata(dev); | 
|  | void __iomem *base; | 
|  | u64 v, val; | 
|  | int ret = 0; | 
|  |  | 
|  | if (kstrtou64(buf, 0, &val)) | 
|  | return -EINVAL; | 
|  |  | 
|  | base = dfl_get_feature_ioaddr_by_id(dev, FME_FEATURE_ID_GLOBAL_ERR); | 
|  |  | 
|  | mutex_lock(&pdata->lock); | 
|  | writeq(GENMASK_ULL(63, 0), base + FME_ERROR_MASK); | 
|  |  | 
|  | v = readq(base + FME_ERROR); | 
|  | if (val == v) | 
|  | writeq(v, base + FME_ERROR); | 
|  | else | 
|  | ret = -EINVAL; | 
|  |  | 
|  | /* Workaround: disable MBP_ERROR if feature revision is 0 */ | 
|  | writeq(dfl_feature_revision(base) ? 0ULL : MBP_ERROR, | 
|  | base + FME_ERROR_MASK); | 
|  | mutex_unlock(&pdata->lock); | 
|  | return ret ? ret : count; | 
|  | } | 
|  | static DEVICE_ATTR_RW(fme_errors); | 
|  |  | 
|  | static ssize_t first_error_show(struct device *dev, | 
|  | struct device_attribute *attr, char *buf) | 
|  | { | 
|  | struct dfl_feature_platform_data *pdata = dev_get_platdata(dev); | 
|  | void __iomem *base; | 
|  | u64 value; | 
|  |  | 
|  | base = dfl_get_feature_ioaddr_by_id(dev, FME_FEATURE_ID_GLOBAL_ERR); | 
|  |  | 
|  | mutex_lock(&pdata->lock); | 
|  | value = readq(base + FME_FIRST_ERROR); | 
|  | mutex_unlock(&pdata->lock); | 
|  |  | 
|  | return sprintf(buf, "0x%llx\n", (unsigned long long)value); | 
|  | } | 
|  | static DEVICE_ATTR_RO(first_error); | 
|  |  | 
|  | static ssize_t next_error_show(struct device *dev, | 
|  | struct device_attribute *attr, char *buf) | 
|  | { | 
|  | struct dfl_feature_platform_data *pdata = dev_get_platdata(dev); | 
|  | void __iomem *base; | 
|  | u64 value; | 
|  |  | 
|  | base = dfl_get_feature_ioaddr_by_id(dev, FME_FEATURE_ID_GLOBAL_ERR); | 
|  |  | 
|  | mutex_lock(&pdata->lock); | 
|  | value = readq(base + FME_NEXT_ERROR); | 
|  | mutex_unlock(&pdata->lock); | 
|  |  | 
|  | return sprintf(buf, "0x%llx\n", (unsigned long long)value); | 
|  | } | 
|  | static DEVICE_ATTR_RO(next_error); | 
|  |  | 
|  | static struct attribute *fme_global_err_attrs[] = { | 
|  | &dev_attr_pcie0_errors.attr, | 
|  | &dev_attr_pcie1_errors.attr, | 
|  | &dev_attr_nonfatal_errors.attr, | 
|  | &dev_attr_catfatal_errors.attr, | 
|  | &dev_attr_inject_errors.attr, | 
|  | &dev_attr_fme_errors.attr, | 
|  | &dev_attr_first_error.attr, | 
|  | &dev_attr_next_error.attr, | 
|  | NULL, | 
|  | }; | 
|  |  | 
|  | static umode_t fme_global_err_attrs_visible(struct kobject *kobj, | 
|  | struct attribute *attr, int n) | 
|  | { | 
|  | struct device *dev = kobj_to_dev(kobj); | 
|  |  | 
|  | /* | 
|  | * sysfs entries are visible only if related private feature is | 
|  | * enumerated. | 
|  | */ | 
|  | if (!dfl_get_feature_by_id(dev, FME_FEATURE_ID_GLOBAL_ERR)) | 
|  | return 0; | 
|  |  | 
|  | return attr->mode; | 
|  | } | 
|  |  | 
|  | const struct attribute_group fme_global_err_group = { | 
|  | .name       = "errors", | 
|  | .attrs      = fme_global_err_attrs, | 
|  | .is_visible = fme_global_err_attrs_visible, | 
|  | }; | 
|  |  | 
|  | static void fme_err_mask(struct device *dev, bool mask) | 
|  | { | 
|  | struct dfl_feature_platform_data *pdata = dev_get_platdata(dev); | 
|  | void __iomem *base; | 
|  |  | 
|  | base = dfl_get_feature_ioaddr_by_id(dev, FME_FEATURE_ID_GLOBAL_ERR); | 
|  |  | 
|  | mutex_lock(&pdata->lock); | 
|  |  | 
|  | /* Workaround: keep MBP_ERROR always masked if revision is 0 */ | 
|  | if (dfl_feature_revision(base)) | 
|  | writeq(mask ? ERROR_MASK : 0, base + FME_ERROR_MASK); | 
|  | else | 
|  | writeq(mask ? ERROR_MASK : MBP_ERROR, base + FME_ERROR_MASK); | 
|  |  | 
|  | writeq(mask ? ERROR_MASK : 0, base + PCIE0_ERROR_MASK); | 
|  | writeq(mask ? ERROR_MASK : 0, base + PCIE1_ERROR_MASK); | 
|  | writeq(mask ? ERROR_MASK : 0, base + RAS_NONFAT_ERROR_MASK); | 
|  | writeq(mask ? ERROR_MASK : 0, base + RAS_CATFAT_ERROR_MASK); | 
|  |  | 
|  | mutex_unlock(&pdata->lock); | 
|  | } | 
|  |  | 
|  | static int fme_global_err_init(struct platform_device *pdev, | 
|  | struct dfl_feature *feature) | 
|  | { | 
|  | fme_err_mask(&pdev->dev, false); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void fme_global_err_uinit(struct platform_device *pdev, | 
|  | struct dfl_feature *feature) | 
|  | { | 
|  | fme_err_mask(&pdev->dev, true); | 
|  | } | 
|  |  | 
|  | static long | 
|  | fme_global_error_ioctl(struct platform_device *pdev, | 
|  | struct dfl_feature *feature, | 
|  | unsigned int cmd, unsigned long arg) | 
|  | { | 
|  | switch (cmd) { | 
|  | case DFL_FPGA_FME_ERR_GET_IRQ_NUM: | 
|  | return dfl_feature_ioctl_get_num_irqs(pdev, feature, arg); | 
|  | case DFL_FPGA_FME_ERR_SET_IRQ: | 
|  | return dfl_feature_ioctl_set_irq(pdev, feature, arg); | 
|  | default: | 
|  | dev_dbg(&pdev->dev, "%x cmd not handled", cmd); | 
|  | return -ENODEV; | 
|  | } | 
|  | } | 
|  |  | 
|  | const struct dfl_feature_id fme_global_err_id_table[] = { | 
|  | {.id = FME_FEATURE_ID_GLOBAL_ERR,}, | 
|  | {0,} | 
|  | }; | 
|  |  | 
|  | const struct dfl_feature_ops fme_global_err_ops = { | 
|  | .init = fme_global_err_init, | 
|  | .uinit = fme_global_err_uinit, | 
|  | .ioctl = fme_global_error_ioctl, | 
|  | }; |