Commit 0e439ba3 authored by Nipun Gupta's avatar Nipun Gupta Committed by Greg Kroah-Hartman
Browse files

cdx: add MSI support for CDX bus



Add CDX-MSI domain per CDX controller with gic-its domain as
a parent, to support MSI for CDX devices. CDX devices allocate
MSIs from the CDX domain. Also, introduce APIs to alloc and free
IRQs for CDX domain.

In CDX subsystem firmware is a controller for all devices and
their configuration. CDX bus controller sends all the write_msi_msg
commands to firmware running on RPU and the firmware interfaces with
actual devices to pass this information to devices

Since, CDX controller is the only way to communicate with the Firmware
for MSI write info, CDX domain per controller required in contrast to
having a CDX domain per device.

Co-developed-by: default avatarNikhil Agarwal <nikhil.agarwal@amd.com>
Signed-off-by: default avatarNikhil Agarwal <nikhil.agarwal@amd.com>
Co-developed-by: default avatarAbhijit Gangurde <abhijit.gangurde@amd.com>
Signed-off-by: default avatarAbhijit Gangurde <abhijit.gangurde@amd.com>
Signed-off-by: default avatarNipun Gupta <nipun.gupta@amd.com>
Reviewed-by: default avatarPieter Jansen van Vuuren <pieter.jansen-van-vuuren@amd.com>
Reviewed-by: default avatarThomas Gleixner <tglx@linutronix.de>
Tested-by: default avatarNikhil Agarwal <nikhil.agarwal@amd.com>
Link: https://lore.kernel.org/r/20240226082816.100872-1-nipun.gupta@amd.com


Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent e3a59056
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -8,3 +8,7 @@
ccflags-y += -DDEFAULT_SYMBOL_NAMESPACE=CDX_BUS

obj-$(CONFIG_CDX_BUS) += cdx.o controller/

ifdef CONFIG_GENERIC_MSI_IRQ
obj-$(CONFIG_CDX_BUS) += cdx_msi.o
endif
+20 −0
Original line number Diff line number Diff line
@@ -56,6 +56,7 @@
 */

#include <linux/init.h>
#include <linux/irqdomain.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/of_device.h>
@@ -302,8 +303,19 @@ static int cdx_probe(struct device *dev)
{
	struct cdx_driver *cdx_drv = to_cdx_driver(dev->driver);
	struct cdx_device *cdx_dev = to_cdx_device(dev);
	struct cdx_controller *cdx = cdx_dev->cdx;
	int error;

	/*
	 * Setup MSI device data so that generic MSI alloc/free can
	 * be used by the device driver.
	 */
	if (cdx->msi_domain) {
		error = msi_setup_device_data(&cdx_dev->dev);
		if (error)
			return error;
	}

	error = cdx_drv->probe(cdx_dev);
	if (error) {
		dev_err_probe(dev, error, "%s failed\n", __func__);
@@ -787,6 +799,7 @@ int cdx_device_add(struct cdx_dev_params *dev_params)

	/* Populate CDX dev params */
	cdx_dev->req_id = dev_params->req_id;
	cdx_dev->msi_dev_id = dev_params->msi_dev_id;
	cdx_dev->vendor = dev_params->vendor;
	cdx_dev->device = dev_params->device;
	cdx_dev->subsystem_vendor = dev_params->subsys_vendor;
@@ -804,12 +817,19 @@ int cdx_device_add(struct cdx_dev_params *dev_params)
	cdx_dev->dev.bus = &cdx_bus_type;
	cdx_dev->dev.dma_mask = &cdx_dev->dma_mask;
	cdx_dev->dev.release = cdx_device_release;
	cdx_dev->msi_write_pending = false;
	mutex_init(&cdx_dev->irqchip_lock);

	/* Set Name */
	dev_set_name(&cdx_dev->dev, "cdx-%02x:%02x",
		     ((cdx->id << CDX_CONTROLLER_ID_SHIFT) | (cdx_dev->bus_num & CDX_BUS_NUM_MASK)),
		     cdx_dev->dev_num);

	if (cdx->msi_domain) {
		cdx_dev->num_msi = dev_params->num_msi;
		dev_set_msi_domain(&cdx_dev->dev, cdx->msi_domain);
	}

	ret = device_add(&cdx_dev->dev);
	if (ret) {
		dev_err(&cdx_dev->dev,
+12 −0
Original line number Diff line number Diff line
@@ -25,6 +25,8 @@
 * @req_id: Requestor ID associated with CDX device
 * @class: Class of the CDX Device
 * @revision: Revision of the CDX device
 * @msi_dev_id: MSI device ID associated with CDX device
 * @num_msi: Number of MSI's supported by the device
 */
struct cdx_dev_params {
	struct cdx_controller *cdx;
@@ -40,6 +42,8 @@ struct cdx_dev_params {
	u32 req_id;
	u32 class;
	u8 revision;
	u32 msi_dev_id;
	u32 num_msi;
};

/**
@@ -79,4 +83,12 @@ int cdx_device_add(struct cdx_dev_params *dev_params);
 */
struct device *cdx_bus_add(struct cdx_controller *cdx, u8 bus_num);

/**
 * cdx_msi_domain_init - Init the CDX bus MSI domain.
 * @dev: Device of the CDX bus controller
 *
 * Return: CDX MSI domain, NULL on failure
 */
struct irq_domain *cdx_msi_domain_init(struct device *dev);

#endif /* _CDX_H_ */

drivers/cdx/cdx_msi.c

0 → 100644
+192 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/*
 * AMD CDX bus driver MSI support
 *
 * Copyright (C) 2022-2023, Advanced Micro Devices, Inc.
 */

#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/msi.h>
#include <linux/cdx/cdx_bus.h>

#include "cdx.h"

static void cdx_msi_write_msg(struct irq_data *irq_data, struct msi_msg *msg)
{
	struct msi_desc *msi_desc = irq_data_get_msi_desc(irq_data);
	struct cdx_device *cdx_dev = to_cdx_device(msi_desc->dev);

	/* We would not operate on msg here rather we wait for irq_bus_sync_unlock()
	 * to be called from preemptible task context.
	 */
	msi_desc->msg = *msg;
	cdx_dev->msi_write_pending = true;
}

static void cdx_msi_write_irq_lock(struct irq_data *irq_data)
{
	struct msi_desc *msi_desc = irq_data_get_msi_desc(irq_data);
	struct cdx_device *cdx_dev = to_cdx_device(msi_desc->dev);

	mutex_lock(&cdx_dev->irqchip_lock);
}

static void cdx_msi_write_irq_unlock(struct irq_data *irq_data)
{
	struct msi_desc *msi_desc = irq_data_get_msi_desc(irq_data);
	struct cdx_device *cdx_dev = to_cdx_device(msi_desc->dev);
	struct cdx_controller *cdx = cdx_dev->cdx;
	struct cdx_device_config dev_config;

	if (!cdx_dev->msi_write_pending) {
		mutex_unlock(&cdx_dev->irqchip_lock);
		return;
	}

	cdx_dev->msi_write_pending = false;
	mutex_unlock(&cdx_dev->irqchip_lock);

	dev_config.msi.msi_index = msi_desc->msi_index;
	dev_config.msi.data = msi_desc->msg.data;
	dev_config.msi.addr = ((u64)(msi_desc->msg.address_hi) << 32) | msi_desc->msg.address_lo;

	/*
	 * dev_configure() is a controller callback which can interact with
	 * Firmware or other entities, and can sleep, so invoke this function
	 * outside of the mutex held region.
	 */
	dev_config.type = CDX_DEV_MSI_CONF;
	if (cdx->ops->dev_configure)
		cdx->ops->dev_configure(cdx, cdx_dev->bus_num, cdx_dev->dev_num, &dev_config);
}

int cdx_enable_msi(struct cdx_device *cdx_dev)
{
	struct cdx_controller *cdx = cdx_dev->cdx;
	struct cdx_device_config dev_config;

	dev_config.type = CDX_DEV_MSI_ENABLE;
	dev_config.msi_enable = true;
	if (cdx->ops->dev_configure) {
		return cdx->ops->dev_configure(cdx, cdx_dev->bus_num, cdx_dev->dev_num,
					       &dev_config);
	}

	return -EOPNOTSUPP;
}
EXPORT_SYMBOL_GPL(cdx_enable_msi);

void cdx_disable_msi(struct cdx_device *cdx_dev)
{
	struct cdx_controller *cdx = cdx_dev->cdx;
	struct cdx_device_config dev_config;

	dev_config.type = CDX_DEV_MSI_ENABLE;
	dev_config.msi_enable = false;
	if (cdx->ops->dev_configure)
		cdx->ops->dev_configure(cdx, cdx_dev->bus_num, cdx_dev->dev_num, &dev_config);
}
EXPORT_SYMBOL_GPL(cdx_disable_msi);

static struct irq_chip cdx_msi_irq_chip = {
	.name			= "CDX-MSI",
	.irq_mask		= irq_chip_mask_parent,
	.irq_unmask		= irq_chip_unmask_parent,
	.irq_eoi		= irq_chip_eoi_parent,
	.irq_set_affinity	= msi_domain_set_affinity,
	.irq_write_msi_msg	= cdx_msi_write_msg,
	.irq_bus_lock		= cdx_msi_write_irq_lock,
	.irq_bus_sync_unlock	= cdx_msi_write_irq_unlock
};

/* Convert an msi_desc to a unique identifier within the domain. */
static irq_hw_number_t cdx_domain_calc_hwirq(struct cdx_device *dev,
					     struct msi_desc *desc)
{
	return ((irq_hw_number_t)dev->msi_dev_id << 10) | desc->msi_index;
}

static void cdx_msi_set_desc(msi_alloc_info_t *arg, struct msi_desc *desc)
{
	arg->desc = desc;
	arg->hwirq = cdx_domain_calc_hwirq(to_cdx_device(desc->dev), desc);
}

static int cdx_msi_prepare(struct irq_domain *msi_domain,
			   struct device *dev,
			   int nvec, msi_alloc_info_t *info)
{
	struct cdx_device *cdx_dev = to_cdx_device(dev);
	struct device *parent = cdx_dev->cdx->dev;
	struct msi_domain_info *msi_info;
	u32 dev_id;
	int ret;

	/* Retrieve device ID from requestor ID using parent device */
	ret = of_map_id(parent->of_node, cdx_dev->msi_dev_id, "msi-map", "msi-map-mask",
			NULL, &dev_id);
	if (ret) {
		dev_err(dev, "of_map_id failed for MSI: %d\n", ret);
		return ret;
	}

#ifdef GENERIC_MSI_DOMAIN_OPS
	/* Set the device Id to be passed to the GIC-ITS */
	info->scratchpad[0].ul = dev_id;
#endif

	msi_info = msi_get_domain_info(msi_domain->parent);

	return msi_info->ops->msi_prepare(msi_domain->parent, dev, nvec, info);
}

static struct msi_domain_ops cdx_msi_ops = {
	.msi_prepare	= cdx_msi_prepare,
	.set_desc	= cdx_msi_set_desc
};

static struct msi_domain_info cdx_msi_domain_info = {
	.ops	= &cdx_msi_ops,
	.chip	= &cdx_msi_irq_chip,
	.flags	= MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
		  MSI_FLAG_ALLOC_SIMPLE_MSI_DESCS | MSI_FLAG_FREE_MSI_DESCS
};

struct irq_domain *cdx_msi_domain_init(struct device *dev)
{
	struct device_node *np = dev->of_node;
	struct fwnode_handle *fwnode_handle;
	struct irq_domain *cdx_msi_domain;
	struct device_node *parent_node;
	struct irq_domain *parent;

	fwnode_handle = of_node_to_fwnode(np);

	parent_node = of_parse_phandle(np, "msi-map", 1);
	if (!parent_node) {
		dev_err(dev, "msi-map not present on cdx controller\n");
		return NULL;
	}

	parent = irq_find_matching_fwnode(of_node_to_fwnode(parent_node), DOMAIN_BUS_NEXUS);
	if (!parent || !msi_get_domain_info(parent)) {
		dev_err(dev, "unable to locate ITS domain\n");
		return NULL;
	}

	cdx_msi_domain = msi_create_irq_domain(fwnode_handle, &cdx_msi_domain_info, parent);
	if (!cdx_msi_domain) {
		dev_err(dev, "unable to create CDX-MSI domain\n");
		return NULL;
	}

	dev_dbg(dev, "CDX-MSI domain created\n");

	return cdx_msi_domain;
}
EXPORT_SYMBOL_NS_GPL(cdx_msi_domain_init, CDX_BUS_CONTROLLER);
+1 −0
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@ if CDX_BUS

config CDX_CONTROLLER
	tristate "CDX bus controller"
	select GENERIC_MSI_IRQ
	select REMOTEPROC
	select RPMSG
	help
Loading