1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * AMD CDX bus driver MSI support |
4 | * |
5 | * Copyright (C) 2022-2023, Advanced Micro Devices, Inc. |
6 | */ |
7 | |
8 | #include <linux/of.h> |
9 | #include <linux/of_device.h> |
10 | #include <linux/of_address.h> |
11 | #include <linux/of_irq.h> |
12 | #include <linux/irq.h> |
13 | #include <linux/irqdomain.h> |
14 | #include <linux/msi.h> |
15 | #include <linux/cdx/cdx_bus.h> |
16 | |
17 | #include "cdx.h" |
18 | |
19 | static void cdx_msi_write_msg(struct irq_data *irq_data, struct msi_msg *msg) |
20 | { |
21 | struct msi_desc *msi_desc = irq_data_get_msi_desc(d: irq_data); |
22 | struct cdx_device *cdx_dev = to_cdx_device(msi_desc->dev); |
23 | |
24 | /* We would not operate on msg here rather we wait for irq_bus_sync_unlock() |
25 | * to be called from preemptible task context. |
26 | */ |
27 | msi_desc->msg = *msg; |
28 | cdx_dev->msi_write_pending = true; |
29 | } |
30 | |
31 | static void cdx_msi_write_irq_lock(struct irq_data *irq_data) |
32 | { |
33 | struct msi_desc *msi_desc = irq_data_get_msi_desc(d: irq_data); |
34 | struct cdx_device *cdx_dev = to_cdx_device(msi_desc->dev); |
35 | |
36 | mutex_lock(&cdx_dev->irqchip_lock); |
37 | } |
38 | |
39 | static void cdx_msi_write_irq_unlock(struct irq_data *irq_data) |
40 | { |
41 | struct msi_desc *msi_desc = irq_data_get_msi_desc(d: irq_data); |
42 | struct cdx_device *cdx_dev = to_cdx_device(msi_desc->dev); |
43 | struct cdx_controller *cdx = cdx_dev->cdx; |
44 | struct cdx_device_config dev_config; |
45 | |
46 | if (!cdx_dev->msi_write_pending) { |
47 | mutex_unlock(lock: &cdx_dev->irqchip_lock); |
48 | return; |
49 | } |
50 | |
51 | cdx_dev->msi_write_pending = false; |
52 | mutex_unlock(lock: &cdx_dev->irqchip_lock); |
53 | |
54 | dev_config.msi.msi_index = msi_desc->msi_index; |
55 | dev_config.msi.data = msi_desc->msg.data; |
56 | dev_config.msi.addr = ((u64)(msi_desc->msg.address_hi) << 32) | msi_desc->msg.address_lo; |
57 | |
58 | /* |
59 | * dev_configure() is a controller callback which can interact with |
60 | * Firmware or other entities, and can sleep, so invoke this function |
61 | * outside of the mutex held region. |
62 | */ |
63 | dev_config.type = CDX_DEV_MSI_CONF; |
64 | if (cdx->ops->dev_configure) |
65 | cdx->ops->dev_configure(cdx, cdx_dev->bus_num, cdx_dev->dev_num, &dev_config); |
66 | } |
67 | |
68 | int cdx_enable_msi(struct cdx_device *cdx_dev) |
69 | { |
70 | struct cdx_controller *cdx = cdx_dev->cdx; |
71 | struct cdx_device_config dev_config; |
72 | |
73 | dev_config.type = CDX_DEV_MSI_ENABLE; |
74 | dev_config.msi_enable = true; |
75 | if (cdx->ops->dev_configure) { |
76 | return cdx->ops->dev_configure(cdx, cdx_dev->bus_num, cdx_dev->dev_num, |
77 | &dev_config); |
78 | } |
79 | |
80 | return -EOPNOTSUPP; |
81 | } |
82 | EXPORT_SYMBOL_GPL(cdx_enable_msi); |
83 | |
84 | void cdx_disable_msi(struct cdx_device *cdx_dev) |
85 | { |
86 | struct cdx_controller *cdx = cdx_dev->cdx; |
87 | struct cdx_device_config dev_config; |
88 | |
89 | dev_config.type = CDX_DEV_MSI_ENABLE; |
90 | dev_config.msi_enable = false; |
91 | if (cdx->ops->dev_configure) |
92 | cdx->ops->dev_configure(cdx, cdx_dev->bus_num, cdx_dev->dev_num, &dev_config); |
93 | } |
94 | EXPORT_SYMBOL_GPL(cdx_disable_msi); |
95 | |
96 | static struct irq_chip cdx_msi_irq_chip = { |
97 | .name = "CDX-MSI" , |
98 | .irq_mask = irq_chip_mask_parent, |
99 | .irq_unmask = irq_chip_unmask_parent, |
100 | .irq_eoi = irq_chip_eoi_parent, |
101 | .irq_set_affinity = msi_domain_set_affinity, |
102 | .irq_write_msi_msg = cdx_msi_write_msg, |
103 | .irq_bus_lock = cdx_msi_write_irq_lock, |
104 | .irq_bus_sync_unlock = cdx_msi_write_irq_unlock |
105 | }; |
106 | |
107 | /* Convert an msi_desc to a unique identifier within the domain. */ |
108 | static irq_hw_number_t cdx_domain_calc_hwirq(struct cdx_device *dev, |
109 | struct msi_desc *desc) |
110 | { |
111 | return ((irq_hw_number_t)dev->msi_dev_id << 10) | desc->msi_index; |
112 | } |
113 | |
114 | static void cdx_msi_set_desc(msi_alloc_info_t *arg, struct msi_desc *desc) |
115 | { |
116 | arg->desc = desc; |
117 | arg->hwirq = cdx_domain_calc_hwirq(to_cdx_device(desc->dev), desc); |
118 | } |
119 | |
120 | static int cdx_msi_prepare(struct irq_domain *msi_domain, |
121 | struct device *dev, |
122 | int nvec, msi_alloc_info_t *info) |
123 | { |
124 | struct cdx_device *cdx_dev = to_cdx_device(dev); |
125 | struct device *parent = cdx_dev->cdx->dev; |
126 | struct msi_domain_info *msi_info; |
127 | u32 dev_id; |
128 | int ret; |
129 | |
130 | /* Retrieve device ID from requestor ID using parent device */ |
131 | ret = of_map_id(np: parent->of_node, id: cdx_dev->msi_dev_id, map_name: "msi-map" , map_mask_name: "msi-map-mask" , |
132 | NULL, id_out: &dev_id); |
133 | if (ret) { |
134 | dev_err(dev, "of_map_id failed for MSI: %d\n" , ret); |
135 | return ret; |
136 | } |
137 | |
138 | #ifdef GENERIC_MSI_DOMAIN_OPS |
139 | /* Set the device Id to be passed to the GIC-ITS */ |
140 | info->scratchpad[0].ul = dev_id; |
141 | #endif |
142 | |
143 | msi_info = msi_get_domain_info(domain: msi_domain->parent); |
144 | |
145 | return msi_info->ops->msi_prepare(msi_domain->parent, dev, nvec, info); |
146 | } |
147 | |
148 | static struct msi_domain_ops cdx_msi_ops = { |
149 | .msi_prepare = cdx_msi_prepare, |
150 | .set_desc = cdx_msi_set_desc |
151 | }; |
152 | |
153 | static struct msi_domain_info cdx_msi_domain_info = { |
154 | .ops = &cdx_msi_ops, |
155 | .chip = &cdx_msi_irq_chip, |
156 | .flags = MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS | |
157 | MSI_FLAG_ALLOC_SIMPLE_MSI_DESCS | MSI_FLAG_FREE_MSI_DESCS |
158 | }; |
159 | |
160 | struct irq_domain *cdx_msi_domain_init(struct device *dev) |
161 | { |
162 | struct device_node *np = dev->of_node; |
163 | struct fwnode_handle *fwnode_handle; |
164 | struct irq_domain *cdx_msi_domain; |
165 | struct device_node *parent_node; |
166 | struct irq_domain *parent; |
167 | |
168 | fwnode_handle = of_node_to_fwnode(node: np); |
169 | |
170 | parent_node = of_parse_phandle(np, phandle_name: "msi-map" , index: 1); |
171 | if (!parent_node) { |
172 | dev_err(dev, "msi-map not present on cdx controller\n" ); |
173 | return NULL; |
174 | } |
175 | |
176 | parent = irq_find_matching_fwnode(fwnode: of_node_to_fwnode(node: parent_node), bus_token: DOMAIN_BUS_NEXUS); |
177 | if (!parent || !msi_get_domain_info(domain: parent)) { |
178 | dev_err(dev, "unable to locate ITS domain\n" ); |
179 | return NULL; |
180 | } |
181 | |
182 | cdx_msi_domain = msi_create_irq_domain(fwnode: fwnode_handle, info: &cdx_msi_domain_info, parent); |
183 | if (!cdx_msi_domain) { |
184 | dev_err(dev, "unable to create CDX-MSI domain\n" ); |
185 | return NULL; |
186 | } |
187 | |
188 | dev_dbg(dev, "CDX-MSI domain created\n" ); |
189 | |
190 | return cdx_msi_domain; |
191 | } |
192 | EXPORT_SYMBOL_NS_GPL(cdx_msi_domain_init, CDX_BUS_CONTROLLER); |
193 | |