Adds Support for Mellanox BlueField TRIO PCIe host controller. Reviewed-by: Liming Sun <lsun@xxxxxxxxxxxx> Signed-off-by: Shravan Kumar Ramani <sramani@xxxxxxxxxxxx> --- MAINTAINERS | 5 + drivers/platform/mellanox/Kconfig | 8 + drivers/platform/mellanox/Makefile | 1 + drivers/platform/mellanox/mlxbf-trio.c | 624 +++++++++++++++++++++++++++++++++ 4 files changed, 638 insertions(+) create mode 100644 drivers/platform/mellanox/mlxbf-trio.c diff --git a/MAINTAINERS b/MAINTAINERS index eb19fad..123ad78 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10501,6 +10501,11 @@ L: platform-driver-x86@xxxxxxxxxxxxxxx S: Supported F: drivers/platform/x86/mlx-platform.c +MELLANOX BLUEFIELD TRIO DRIVER +M: Shravan Kumar Ramani <sramani@xxxxxxxxxxxx> +S: Supported +F: drivers/platform/mellanox/mlxbf-trio.c + MEMBARRIER SUPPORT M: Mathieu Desnoyers <mathieu.desnoyers@xxxxxxxxxxxx> M: "Paul E. McKenney" <paulmck@xxxxxxxxxx> diff --git a/drivers/platform/mellanox/Kconfig b/drivers/platform/mellanox/Kconfig index 530fe7e..f962015 100644 --- a/drivers/platform/mellanox/Kconfig +++ b/drivers/platform/mellanox/Kconfig @@ -44,4 +44,12 @@ config MLXBF_TMFIFO platform driver support for the TmFifo which supports console and networking based on the virtio framework. +config MLXBF_TRIO + tristate "Mellanox BlueField SoC TRIO driver" + depends on ARM64 + depends on ACPI + help + Say y here to enable TRIO driver. This driver provides platform + driver support for the TRIO PCIe Host Controller. + endif # MELLANOX_PLATFORM diff --git a/drivers/platform/mellanox/Makefile b/drivers/platform/mellanox/Makefile index a229bda1..37bbd56 100644 --- a/drivers/platform/mellanox/Makefile +++ b/drivers/platform/mellanox/Makefile @@ -4,5 +4,6 @@ # Mellanox Platform-Specific Drivers # obj-$(CONFIG_MLXBF_TMFIFO) += mlxbf-tmfifo.o +obj-$(CONFIG_MLXBF_TRIO) += mlxbf-trio.o obj-$(CONFIG_MLXREG_HOTPLUG) += mlxreg-hotplug.o obj-$(CONFIG_MLXREG_IO) += mlxreg-io.o diff --git a/drivers/platform/mellanox/mlxbf-trio.c b/drivers/platform/mellanox/mlxbf-trio.c new file mode 100644 index 0000000..1dfcc28 --- /dev/null +++ b/drivers/platform/mellanox/mlxbf-trio.c @@ -0,0 +1,624 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/acpi.h> +#include <linux/arm-smccc.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/kmod.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/sysfs.h> + +#include <uapi/linux/psci.h> + +#define DRIVER_NAME "mlxbf-trio" +#define DRIVER_DESCRIPTION "Mellanox TRIO PCIe host controller driver" + +/* SMC return codes */ +#define SMCCC_ACCESS_VIOLATION (-4) + +/* SMC function identifiers */ +#define MLNX_WRITE_REG_64 (0x8200000B) +#define MLNX_READ_REG_64 (0x8200000C) +#define MLNX_SIP_SVC_UID (0x8200ff01) +#define MLNX_SIP_SVC_VERSION (0x8200ff03) + +#define MLNX_TRIO_SVC_REQ_MAJOR 0 +#define MLNX_TRIO_SVC_MIN_MINOR 4 + +#define TRIO_NUM_IRQS 17 +#define L3C_PROF_RD_MISS__LENGTH 0x0040 +#define L3C_PROF_RD_MISS__STRIDE 0x0004 +#define L3_PROFILE_NUM (L3C_PROF_RD_MISS__LENGTH / L3C_PROF_RD_MISS__STRIDE) + +/* The PUSH_DMA_EVT_CTR wrapped. */ +#define TRIO_PUSH_DMA_EVT_CTR_INT_BIT 10 + +/* The MAP_EVT_CTR wrapped. */ +#define TRIO_MAP_EVT_CTR_INT_BIT 11 + +#define TRIO_DEV_CTL 0x0008 +#define TRIO_DEV_CTL__L3_PROFILE_OVD_SHIFT 4 +#define TRIO_DEV_CTL__L3_PROFILE_OVD_MASK 0x10 +#define TRIO_DEV_CTL__L3_PROFILE_VAL_SHIFT 5 +#define TRIO_DEV_CTL__L3_PROFILE_VAL_MASK 0x1e0 + +#define TRIO_MMIO_ERROR_INFO 0x0608 + +#define TRIO_MAP_ERR_STS 0x0810 + +#define TRIO_TILE_PIO_CPL_ERR_STS 0x09f0 + +enum trio_int_events { + TRIO_MAC_INT = 0, + TRIO_RSH_FULL_ERR_INT, + TRIO_MSG_Q_FULL_ERR_INT, + TRIO_MSG_Q_ARRIVED_INT, + TRIO_MMIO_ERR_INT, + TRIO_MAP_UNCLAIMED_INT, + TRIO_RSH_SIZE_ERR_INT, + TRIO_PIO_ECAM_ERR_INT, + TRIO_PIO_CPL_ERR_INT, + TRIO_MMIO_PROT_ERR_INT, + TRIO_PUSH_DMA_EVT_CTR_INT, + TRIO_MAP_EVT_CTR_INT, + TRIO_PIO_DISABLED_INT, + TRIO_REM_MMIO_ERR_INT, + TRIO_ERR_MSG_COR_INT, + TRIO_ERR_MSG_NONFATAL_INT, + TRIO_ERR_MSG_FATAL_INT, +}; + +struct trio_event_info { + const char *name; + int additional_info; +}; + +static const struct trio_event_info trio_events[TRIO_NUM_IRQS] = { + [TRIO_MAC_INT] = { + .name = "MAC Interrupt", + .additional_info = -1, + }, + [TRIO_RSH_FULL_ERR_INT] = { + .name = "RShim Full Error", + .additional_info = -1, + }, + [TRIO_MSG_Q_FULL_ERR_INT] = { + .name = "Msg Queue Full Error", + .additional_info = -1, + }, + [TRIO_MSG_Q_ARRIVED_INT] = { + .name = "Msg Arrived Interrupt", + .additional_info = -1, + }, + [TRIO_MMIO_ERR_INT] = { + .name = "MMIO Error", + .additional_info = TRIO_MMIO_ERROR_INFO, + }, + [TRIO_MAP_UNCLAIMED_INT] = { + .name = "Packet Unclaimed Error", + .additional_info = TRIO_MAP_ERR_STS, + }, + [TRIO_RSH_SIZE_ERR_INT] = { + .name = "RShim Size Error", + .additional_info = -1, + }, + [TRIO_PIO_ECAM_ERR_INT] = { + .name = "PIO ECAM Error", + .additional_info = -1, + }, + [TRIO_PIO_CPL_ERR_INT] = { + .name = "PIO Completion Error", + .additional_info = TRIO_TILE_PIO_CPL_ERR_STS, + }, + [TRIO_MMIO_PROT_ERR_INT] = { + .name = "MMIO Protection level Violation", + .additional_info = -1, + }, + [TRIO_PUSH_DMA_EVT_CTR_INT] = { + .name = "PUSH_DMA_CTR wrapped", + .additional_info = -1, + }, + [TRIO_MAP_EVT_CTR_INT] = { + .name = "MAP_EVT_CTR wrapped", + .additional_info = -1, + }, + [TRIO_PIO_DISABLED_INT] = { + .name = "Access to disabled PIO region", + .additional_info = -1, + }, + [TRIO_REM_MMIO_ERR_INT] = { + .name = "Remote Buffer MMIO Error", + .additional_info = -1, + }, + [TRIO_ERR_MSG_COR_INT] = { + .name = "Correctable error message received", + .additional_info = -1, + }, + [TRIO_ERR_MSG_NONFATAL_INT] = { + .name = "Nonfatal error message received", + .additional_info = -1, + }, + [TRIO_ERR_MSG_FATAL_INT] = { + .name = "Fatal error message received", + .additional_info = -1, + }, +}; + +enum l3_profile_type { + LRU_PROFILE = 0, /* 0 is the default behavior. */ + NVME_PROFILE, + L3_PROFILE_TYPE_NUM, +}; + +static const char * const l3_profiles[L3_PROFILE_TYPE_NUM] = { + [LRU_PROFILE] = "Strict_LRU", + [NVME_PROFILE] = "NVMeOF_suitable" +}; + +/* + * The default profile each L3 profile would get. + * The current setting would make profile 1 the NVMe suitable profile + * and the rest of the profiles LRU profile. + * Note that profile 0 should be configured as LRU as this is the + * default profile. + */ +static const enum l3_profile_type default_profile[L3_PROFILE_NUM] = { + [1] = NVME_PROFILE, +}; + +struct event_context { + int event_num; + int irq; + struct trio_context *trio; +}; + +/** + * trio_context - Structure for TRIO block info + * @pdev: kenrel struct representing the device + * @events: argument to be passed to the IRQ handler + * @mmio_base: Reg base addr + * @trio_index: Index of TRIO + * @bus: Name of the bus this TRIO corresponds to + * @trio_pci: PCI device this TRIO corresponds to + * @num_irqs: Number of platform_irqs for this device + * @sreg_use_smcs: Access regs with SMCs if true, else memory mapped + * @sreg_trio_tbl: Verification table for TRIO + */ +struct trio_context { + struct platform_device *pdev; + struct event_context *events; + void __iomem *mmio_base; + int trio_index; + const char *bus; + struct pci_dev *trio_pci; + u32 num_irqs; + bool sreg_use_smcs; + u32 sreg_trio_tbl; +}; + +static int secure_writeq(struct trio_context *trio, uint64_t value, + void __iomem *addr) +{ + struct arm_smccc_res res; + int status; + + arm_smccc_smc(MLNX_WRITE_REG_64, trio->sreg_trio_tbl, value, + (uintptr_t)addr, 0, 0, 0, 0, &res); + + status = res.a0; + + switch (status) { + /* + * Note: PSCI_RET_NOT_SUPPORTED is used here to maintain compatibility + * with older kernels that do not have SMCCC_RET_NOT_SUPPORTED + */ + case PSCI_RET_NOT_SUPPORTED: + dev_err(&trio->pdev->dev, "Required SMC unsupported\n"); + return -EFAULT; + case SMCCC_ACCESS_VIOLATION: + dev_err(&trio->pdev->dev, "SMC access violation\n"); + return -EFAULT; + default: + return 0; + } +} + +static int trio_writeq(struct trio_context *trio, uint64_t value, + void __iomem *addr) +{ + int ret = 0; + + if (trio->sreg_use_smcs) + ret = secure_writeq(trio, value, addr); + else + writeq(value, addr); + + return ret; +} + +static int secure_readq(struct trio_context *trio, void __iomem *addr, + uint64_t *result) +{ + struct arm_smccc_res res; + int status; + + arm_smccc_smc(MLNX_READ_REG_64, trio->sreg_trio_tbl, (uintptr_t)addr, + 0, 0, 0, 0, 0, &res); + + status = res.a0; + + switch (status) { + /* + * Note: PSCI_RET_NOT_SUPPORTED is used here to maintain compatibility + * with older kernels that do not have SMCCC_RET_NOT_SUPPORTED + */ + case PSCI_RET_NOT_SUPPORTED: + dev_err(&trio->pdev->dev, "Required SMC unsupported\n"); + return -EFAULT; + case SMCCC_ACCESS_VIOLATION: + dev_err(&trio->pdev->dev, "SMC access violation\n"); + return -EFAULT; + default: + *result = (uint64_t)res.a1; + return 0; + } +} + +static int trio_readq(struct trio_context *trio, void __iomem *addr, + uint64_t *result) +{ + int ret = 0; + + if (trio->sreg_use_smcs) + ret = secure_readq(trio, addr, result); + else + *result = readq(addr); + + return ret; +} + +static irqreturn_t trio_irq_handler(int irq, void *arg) +{ + struct event_context *ctx = (struct event_context *)arg; + struct trio_context *trio = ctx->trio; + u64 info; + + dev_err(&trio->pdev->dev, + "mlx_trio: TRIO %d received IRQ %d event %d (%s)\n", + trio->trio_index, irq, ctx->event_num, + trio_events[ctx->event_num].name); + + if (trio_events[ctx->event_num].additional_info != -1) { + trio_readq(trio, trio->mmio_base + + trio_events[ctx->event_num].additional_info, + &info); + dev_err(&trio->pdev->dev, + "mlx_trio: Addition IRQ info: %llx\n", info); + } + + return IRQ_HANDLED; +} + +static ssize_t current_profile_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev; + struct trio_context *trio; + int profile_num; + u64 tdc; + + pdev = to_platform_device(dev); + trio = platform_get_drvdata(pdev); + + if (trio_readq(trio, trio->mmio_base + TRIO_DEV_CTL, &tdc)) + return -EIO; + + if (((tdc & TRIO_DEV_CTL__L3_PROFILE_OVD_MASK) >> + TRIO_DEV_CTL__L3_PROFILE_OVD_SHIFT) == 0) + profile_num = -1; + else + profile_num = (tdc & TRIO_DEV_CTL__L3_PROFILE_VAL_MASK) >> + TRIO_DEV_CTL__L3_PROFILE_VAL_SHIFT; + + return sprintf(buf, "%d\n", profile_num); +} + +static int set_l3cache_profile(struct trio_context *trio, long profile_num) +{ + u64 tdc; + + if (trio_readq(trio, trio->mmio_base + TRIO_DEV_CTL, &tdc)) + return -EIO; + + if (profile_num == -1) { + dev_info(&trio->pdev->dev, "Unlink %s profile\n", trio->bus); + + tdc |= (0 << TRIO_DEV_CTL__L3_PROFILE_OVD_SHIFT); + } else if (profile_num < L3_PROFILE_NUM && profile_num >= 0) { + dev_info(&trio->pdev->dev, "Change %s to profile %ld\n", + trio->bus, profile_num); + + tdc |= (1 << TRIO_DEV_CTL__L3_PROFILE_OVD_SHIFT); + tdc |= (profile_num << TRIO_DEV_CTL__L3_PROFILE_VAL_SHIFT); + } else { + dev_err(&trio->pdev->dev, "Profile number out of range."); + return -EINVAL; + } + + if (trio_writeq(trio, tdc, trio->mmio_base + TRIO_DEV_CTL)) + return -EIO; + + return 0; +} + +static ssize_t current_profile_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev; + struct trio_context *trio; + long profile_num; + int err; + + pdev = container_of(dev, struct platform_device, dev); + trio = platform_get_drvdata(pdev); + + err = kstrtol(buf, 10, &profile_num); + if (err) + return err; + + err = set_l3cache_profile(trio, profile_num); + if (err) + return err; + + return count; +} + +static DEVICE_ATTR_RW(current_profile); + +static ssize_t available_profiles_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + ssize_t line_size; + ssize_t len = 0; + int i; + + for (i = 0; i < L3_PROFILE_NUM; i++) { + line_size = sprintf(buf, "%d %s\n", i, + l3_profiles[default_profile[i]]); + buf += line_size; + len += line_size; + } + return len; +} + +static DEVICE_ATTR_RO(available_profiles); + +static int trio_probe(struct platform_device *pdev) +{ + int trio_bus, trio_device, trio_function; + struct device *dev = &pdev->dev; + struct arm_smccc_res smc_res; + int i, j, ret, irq, dri_ret; + struct trio_context *trio; + struct event_context *ctx; + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_warn(dev, "%s: failed to find reg resource 0\n", __func__); + ret = -ENODEV; + goto err; + } + + trio = devm_kzalloc(dev, sizeof(struct trio_context), GFP_KERNEL); + if (!trio) { + ret = -ENOMEM; + goto err; + } + + platform_set_drvdata(pdev, trio); + trio->pdev = pdev; + + /* Determine whether to use SMCs or not. */ + if (device_property_read_u32(&pdev->dev, "sec_reg_block", + &trio->sreg_trio_tbl)) { + trio->sreg_use_smcs = false; + } else { + /* + * Ensure we have the UUID we expect for the Mellanox service. + */ + arm_smccc_smc(MLNX_SIP_SVC_UID, 0, 0, 0, 0, 0, 0, 0, &smc_res); + if (smc_res.a0 != 0x89c036b4 || smc_res.a1 != 0x11e6e7d7 || + smc_res.a2 != 0x1a009787 || smc_res.a3 != 0xc4bf00ca) { + dev_err(&pdev->dev, + "Mellanox SMC service not available\n"); + return -EINVAL; + } + + /* + * Check service version to see if we actually do support the + * needed SMCs. If we have the calls we need, mark support for + * them in the trio struct. + */ + arm_smccc_smc(MLNX_SIP_SVC_VERSION, 0, 0, 0, 0, 0, 0, 0, + &smc_res); + if (smc_res.a0 == MLNX_TRIO_SVC_REQ_MAJOR && + smc_res.a1 >= MLNX_TRIO_SVC_MIN_MINOR) { + trio->sreg_use_smcs = true; + } else { + dev_err(&pdev->dev, + "Required SMCs are not supported.\n"); + + return -EINVAL; + } + } + + if (device_property_read_string(dev, "bus_number", &trio->bus)) { + dev_warn(dev, "%s: failed to retrieve Trio bus name\n", + __func__); + ret = -ENODEV; + goto err; + } + + if (device_property_read_u32(dev, "num_irqs", &trio->num_irqs)) + trio->num_irqs = TRIO_NUM_IRQS; + + trio->events = devm_kzalloc(dev, sizeof(struct event_context) * + trio->num_irqs, + GFP_KERNEL); + if (!trio->events) { + ret = -ENOMEM; + goto err; + } + + /* Map registers */ + if (!trio->sreg_use_smcs) { + trio->mmio_base = devm_ioremap_resource(&pdev->dev, res); + + if (IS_ERR(trio->mmio_base)) { + dev_warn(dev, "%s: ioremap failed for mmio_base %llx err %p\n", + __func__, res->start, trio->mmio_base); + ret = PTR_ERR(trio->mmio_base); + goto err; + } + } else { + trio->mmio_base = (void __iomem *)res->start; + } + + for (i = 0; i < trio->num_irqs; ++i) { + ctx = &trio->events[i]; + + switch (i) { + case TRIO_PUSH_DMA_EVT_CTR_INT_BIT: + case TRIO_MAP_EVT_CTR_INT_BIT: + /* + * These events are not errors, they just indicate + * that a performance counter wrapped. We may want + * the performance counter driver to register for them. + */ + continue; + default: + break; + } + + irq = platform_get_irq(pdev, i); + if (irq < 0) { + dev_warn(dev, "%s: failed to get plat irq %d ret %d\n", + __func__, i, irq); + for (j = i - 1; j >= 0; j--) { + ctx = &trio->events[j]; + devm_free_irq(&pdev->dev, ctx->irq, ctx); + } + ret = -ENXIO; + goto err; + } + ctx->event_num = i; + ctx->trio = trio; + ctx->irq = irq; + dri_ret = devm_request_irq(&pdev->dev, irq, trio_irq_handler, 0, + dev_name(dev), ctx); + + dev_dbg(dev, "%s: request_irq returns %d %d->%d\n", __func__, + dri_ret, i, irq); + } + + /* Create the L3 cache profile on this device */ + device_create_file(dev, &dev_attr_current_profile); + device_create_file(dev, &dev_attr_available_profiles); + + /* + * Get the corresponding PCI device this trio maps to. + * If the bus number can't be read properly, no symlinks are created. + */ + if (sscanf(trio->bus, "%d:%d.%d", &trio_bus, &trio_device, + &trio_function) != 3) { + dev_warn(dev, "Device [%s] not valid\n", trio->bus); + return 0; + } + + /* trio_device is also the index of the TRIO */ + trio->trio_index = trio_device; + + /* The PCI domain/segment would always be 0 here. */ + trio->trio_pci = + pci_get_domain_bus_and_slot(0, trio_bus, + (trio_device << 3) + trio_function); + + /* Add the symlink from the TRIO to the PCI device */ + if (trio->trio_pci) { + if (sysfs_create_link(&dev->kobj, &trio->trio_pci->dev.kobj, + "pcie_slot")) { + pci_dev_put(trio->trio_pci); + trio->trio_pci = NULL; + dev_warn(dev, "Failed to create symblink for %s\n", + trio->bus); + } + } else { + dev_warn(dev, "Device %s not found\n", trio->bus); + } + + dev_info(dev, " probed\n"); + return 0; +err: + dev_warn(dev, "Error probing trio\n"); + platform_set_drvdata(pdev, NULL); + return ret; +} + +static int trio_remove(struct platform_device *pdev) +{ + struct trio_context *trio = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + int i; + + for (i = 0; i < trio->num_irqs; ++i) { + struct event_context *ctx = &trio->events[i]; + + if (ctx->irq) + devm_free_irq(&pdev->dev, ctx->irq, ctx); + } + device_remove_file(dev, &dev_attr_current_profile); + device_remove_file(dev, &dev_attr_available_profiles); + + /* Delete the symlink and decrement the reference count. */ + if (trio->trio_pci) { + sysfs_remove_link(&dev->kobj, "pcie_slot"); + pci_dev_put(trio->trio_pci); + } + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static const struct acpi_device_id trio_acpi_ids[] = { + {"MLNXBF06", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(acpi, trio_acpi_ids); +static struct platform_driver mlx_trio_driver = { + .driver = { + .name = DRIVER_NAME, + .acpi_match_table = ACPI_PTR(trio_acpi_ids), + }, + .probe = trio_probe, + .remove = trio_remove, +}; + +module_platform_driver(mlx_trio_driver); + +MODULE_DESCRIPTION(DRIVER_DESCRIPTION); +MODULE_AUTHOR("Mellanox Technologies"); +MODULE_LICENSE("GPL"); -- 2.1.2