linux/drivers/cdx/cdx_msi.c
Nipun Gupta 0e439ba38e 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: Nikhil Agarwal <nikhil.agarwal@amd.com>
Signed-off-by: Nikhil Agarwal <nikhil.agarwal@amd.com>
Co-developed-by: Abhijit Gangurde <abhijit.gangurde@amd.com>
Signed-off-by: Abhijit Gangurde <abhijit.gangurde@amd.com>
Signed-off-by: Nipun Gupta <nipun.gupta@amd.com>
Reviewed-by: Pieter Jansen van Vuuren <pieter.jansen-van-vuuren@amd.com>
Reviewed-by: Thomas Gleixner <tglx@linutronix.de>
Tested-by: Nikhil Agarwal <nikhil.agarwal@amd.com>
Link: https://lore.kernel.org/r/20240226082816.100872-1-nipun.gupta@amd.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2024-03-07 21:52:03 +00:00

193 lines
5.4 KiB
C

// 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);