Commit | Line | Data |
---|---|---|
ee2636b8 SM |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Copyright © 2018 Intel Corporation. | |
4 | * | |
5 | * Authors: Gayatri Kammela <gayatri.kammela@intel.com> | |
6 | * Sohil Mehta <sohil.mehta@intel.com> | |
7 | * Jacob Pan <jacob.jun.pan@linux.intel.com> | |
8 | */ | |
9 | ||
10 | #include <linux/debugfs.h> | |
11 | #include <linux/dmar.h> | |
12 | #include <linux/intel-iommu.h> | |
13 | #include <linux/pci.h> | |
14 | ||
15 | #include <asm/irq_remapping.h> | |
16 | ||
6825d3ea GK |
17 | struct iommu_regset { |
18 | int offset; | |
19 | const char *regs; | |
20 | }; | |
21 | ||
22 | #define IOMMU_REGSET_ENTRY(_reg_) \ | |
23 | { DMAR_##_reg_##_REG, __stringify(_reg_) } | |
24 | static const struct iommu_regset iommu_regs[] = { | |
25 | IOMMU_REGSET_ENTRY(VER), | |
26 | IOMMU_REGSET_ENTRY(CAP), | |
27 | IOMMU_REGSET_ENTRY(ECAP), | |
28 | IOMMU_REGSET_ENTRY(GCMD), | |
29 | IOMMU_REGSET_ENTRY(GSTS), | |
30 | IOMMU_REGSET_ENTRY(RTADDR), | |
31 | IOMMU_REGSET_ENTRY(CCMD), | |
32 | IOMMU_REGSET_ENTRY(FSTS), | |
33 | IOMMU_REGSET_ENTRY(FECTL), | |
34 | IOMMU_REGSET_ENTRY(FEDATA), | |
35 | IOMMU_REGSET_ENTRY(FEADDR), | |
36 | IOMMU_REGSET_ENTRY(FEUADDR), | |
37 | IOMMU_REGSET_ENTRY(AFLOG), | |
38 | IOMMU_REGSET_ENTRY(PMEN), | |
39 | IOMMU_REGSET_ENTRY(PLMBASE), | |
40 | IOMMU_REGSET_ENTRY(PLMLIMIT), | |
41 | IOMMU_REGSET_ENTRY(PHMBASE), | |
42 | IOMMU_REGSET_ENTRY(PHMLIMIT), | |
43 | IOMMU_REGSET_ENTRY(IQH), | |
44 | IOMMU_REGSET_ENTRY(IQT), | |
45 | IOMMU_REGSET_ENTRY(IQA), | |
46 | IOMMU_REGSET_ENTRY(ICS), | |
47 | IOMMU_REGSET_ENTRY(IRTA), | |
48 | IOMMU_REGSET_ENTRY(PQH), | |
49 | IOMMU_REGSET_ENTRY(PQT), | |
50 | IOMMU_REGSET_ENTRY(PQA), | |
51 | IOMMU_REGSET_ENTRY(PRS), | |
52 | IOMMU_REGSET_ENTRY(PECTL), | |
53 | IOMMU_REGSET_ENTRY(PEDATA), | |
54 | IOMMU_REGSET_ENTRY(PEADDR), | |
55 | IOMMU_REGSET_ENTRY(PEUADDR), | |
56 | IOMMU_REGSET_ENTRY(MTRRCAP), | |
57 | IOMMU_REGSET_ENTRY(MTRRDEF), | |
58 | IOMMU_REGSET_ENTRY(MTRR_FIX64K_00000), | |
59 | IOMMU_REGSET_ENTRY(MTRR_FIX16K_80000), | |
60 | IOMMU_REGSET_ENTRY(MTRR_FIX16K_A0000), | |
61 | IOMMU_REGSET_ENTRY(MTRR_FIX4K_C0000), | |
62 | IOMMU_REGSET_ENTRY(MTRR_FIX4K_C8000), | |
63 | IOMMU_REGSET_ENTRY(MTRR_FIX4K_D0000), | |
64 | IOMMU_REGSET_ENTRY(MTRR_FIX4K_D8000), | |
65 | IOMMU_REGSET_ENTRY(MTRR_FIX4K_E0000), | |
66 | IOMMU_REGSET_ENTRY(MTRR_FIX4K_E8000), | |
67 | IOMMU_REGSET_ENTRY(MTRR_FIX4K_F0000), | |
68 | IOMMU_REGSET_ENTRY(MTRR_FIX4K_F8000), | |
69 | IOMMU_REGSET_ENTRY(MTRR_PHYSBASE0), | |
70 | IOMMU_REGSET_ENTRY(MTRR_PHYSMASK0), | |
71 | IOMMU_REGSET_ENTRY(MTRR_PHYSBASE1), | |
72 | IOMMU_REGSET_ENTRY(MTRR_PHYSMASK1), | |
73 | IOMMU_REGSET_ENTRY(MTRR_PHYSBASE2), | |
74 | IOMMU_REGSET_ENTRY(MTRR_PHYSMASK2), | |
75 | IOMMU_REGSET_ENTRY(MTRR_PHYSBASE3), | |
76 | IOMMU_REGSET_ENTRY(MTRR_PHYSMASK3), | |
77 | IOMMU_REGSET_ENTRY(MTRR_PHYSBASE4), | |
78 | IOMMU_REGSET_ENTRY(MTRR_PHYSMASK4), | |
79 | IOMMU_REGSET_ENTRY(MTRR_PHYSBASE5), | |
80 | IOMMU_REGSET_ENTRY(MTRR_PHYSMASK5), | |
81 | IOMMU_REGSET_ENTRY(MTRR_PHYSBASE6), | |
82 | IOMMU_REGSET_ENTRY(MTRR_PHYSMASK6), | |
83 | IOMMU_REGSET_ENTRY(MTRR_PHYSBASE7), | |
84 | IOMMU_REGSET_ENTRY(MTRR_PHYSMASK7), | |
85 | IOMMU_REGSET_ENTRY(MTRR_PHYSBASE8), | |
86 | IOMMU_REGSET_ENTRY(MTRR_PHYSMASK8), | |
87 | IOMMU_REGSET_ENTRY(MTRR_PHYSBASE9), | |
88 | IOMMU_REGSET_ENTRY(MTRR_PHYSMASK9), | |
89 | IOMMU_REGSET_ENTRY(VCCAP), | |
90 | IOMMU_REGSET_ENTRY(VCMD), | |
91 | IOMMU_REGSET_ENTRY(VCRSP), | |
92 | }; | |
93 | ||
94 | static int iommu_regset_show(struct seq_file *m, void *unused) | |
95 | { | |
96 | struct dmar_drhd_unit *drhd; | |
97 | struct intel_iommu *iommu; | |
98 | unsigned long flag; | |
99 | int i, ret = 0; | |
100 | u64 value; | |
101 | ||
102 | rcu_read_lock(); | |
103 | for_each_active_iommu(iommu, drhd) { | |
104 | if (!drhd->reg_base_addr) { | |
105 | seq_puts(m, "IOMMU: Invalid base address\n"); | |
106 | ret = -EINVAL; | |
107 | goto out; | |
108 | } | |
109 | ||
110 | seq_printf(m, "IOMMU: %s Register Base Address: %llx\n", | |
111 | iommu->name, drhd->reg_base_addr); | |
112 | seq_puts(m, "Name\t\t\tOffset\t\tContents\n"); | |
113 | /* | |
114 | * Publish the contents of the 64-bit hardware registers | |
115 | * by adding the offset to the pointer (virtual address). | |
116 | */ | |
117 | raw_spin_lock_irqsave(&iommu->register_lock, flag); | |
118 | for (i = 0 ; i < ARRAY_SIZE(iommu_regs); i++) { | |
119 | value = dmar_readq(iommu->reg + iommu_regs[i].offset); | |
120 | seq_printf(m, "%-16s\t0x%02x\t\t0x%016llx\n", | |
121 | iommu_regs[i].regs, iommu_regs[i].offset, | |
122 | value); | |
123 | } | |
124 | raw_spin_unlock_irqrestore(&iommu->register_lock, flag); | |
125 | seq_putc(m, '\n'); | |
126 | } | |
127 | out: | |
128 | rcu_read_unlock(); | |
129 | ||
130 | return ret; | |
131 | } | |
132 | DEFINE_SHOW_ATTRIBUTE(iommu_regset); | |
133 | ||
18f99c9b SM |
134 | static void ctx_tbl_entry_show(struct seq_file *m, struct intel_iommu *iommu, |
135 | int bus) | |
136 | { | |
137 | struct context_entry *context; | |
138 | int devfn; | |
139 | ||
140 | seq_printf(m, " Context Table Entries for Bus: %d\n", bus); | |
141 | seq_puts(m, " Entry\tB:D.F\tHigh\tLow\n"); | |
142 | ||
143 | for (devfn = 0; devfn < 256; devfn++) { | |
144 | context = iommu_context_addr(iommu, bus, devfn, 0); | |
145 | if (!context) | |
146 | return; | |
147 | ||
148 | if (!context_present(context)) | |
149 | continue; | |
150 | ||
151 | seq_printf(m, " %-5d\t%02x:%02x.%x\t%-6llx\t%llx\n", devfn, | |
152 | bus, PCI_SLOT(devfn), PCI_FUNC(devfn), | |
153 | context[0].hi, context[0].lo); | |
154 | } | |
155 | } | |
156 | ||
157 | static void root_tbl_entry_show(struct seq_file *m, struct intel_iommu *iommu) | |
158 | { | |
159 | unsigned long flags; | |
160 | int bus; | |
161 | ||
162 | spin_lock_irqsave(&iommu->lock, flags); | |
163 | seq_printf(m, "IOMMU %s: Root Table Address:%llx\n", iommu->name, | |
164 | (u64)virt_to_phys(iommu->root_entry)); | |
165 | seq_puts(m, "Root Table Entries:\n"); | |
166 | ||
167 | for (bus = 0; bus < 256; bus++) { | |
168 | if (!(iommu->root_entry[bus].lo & 1)) | |
169 | continue; | |
170 | ||
171 | seq_printf(m, " Bus: %d H: %llx L: %llx\n", bus, | |
172 | iommu->root_entry[bus].hi, | |
173 | iommu->root_entry[bus].lo); | |
174 | ||
175 | ctx_tbl_entry_show(m, iommu, bus); | |
176 | seq_putc(m, '\n'); | |
177 | } | |
178 | spin_unlock_irqrestore(&iommu->lock, flags); | |
179 | } | |
180 | ||
181 | static int dmar_translation_struct_show(struct seq_file *m, void *unused) | |
182 | { | |
183 | struct dmar_drhd_unit *drhd; | |
184 | struct intel_iommu *iommu; | |
185 | ||
186 | rcu_read_lock(); | |
187 | for_each_active_iommu(iommu, drhd) { | |
188 | root_tbl_entry_show(m, iommu); | |
189 | seq_putc(m, '\n'); | |
190 | } | |
191 | rcu_read_unlock(); | |
192 | ||
193 | return 0; | |
194 | } | |
195 | DEFINE_SHOW_ATTRIBUTE(dmar_translation_struct); | |
196 | ||
a6d268c6 SM |
197 | #ifdef CONFIG_IRQ_REMAP |
198 | static void ir_tbl_remap_entry_show(struct seq_file *m, | |
199 | struct intel_iommu *iommu) | |
200 | { | |
201 | struct irte *ri_entry; | |
202 | unsigned long flags; | |
203 | int idx; | |
204 | ||
205 | seq_puts(m, " Entry SrcID DstID Vct IRTE_high\t\tIRTE_low\n"); | |
206 | ||
207 | raw_spin_lock_irqsave(&irq_2_ir_lock, flags); | |
208 | for (idx = 0; idx < INTR_REMAP_TABLE_ENTRIES; idx++) { | |
209 | ri_entry = &iommu->ir_table->base[idx]; | |
210 | if (!ri_entry->present || ri_entry->p_pst) | |
211 | continue; | |
212 | ||
213 | seq_printf(m, " %-5d %02x:%02x.%01x %08x %02x %016llx\t%016llx\n", | |
214 | idx, PCI_BUS_NUM(ri_entry->sid), | |
215 | PCI_SLOT(ri_entry->sid), PCI_FUNC(ri_entry->sid), | |
216 | ri_entry->dest_id, ri_entry->vector, | |
217 | ri_entry->high, ri_entry->low); | |
218 | } | |
219 | raw_spin_unlock_irqrestore(&irq_2_ir_lock, flags); | |
220 | } | |
221 | ||
222 | static void ir_tbl_posted_entry_show(struct seq_file *m, | |
223 | struct intel_iommu *iommu) | |
224 | { | |
225 | struct irte *pi_entry; | |
226 | unsigned long flags; | |
227 | int idx; | |
228 | ||
229 | seq_puts(m, " Entry SrcID PDA_high PDA_low Vct IRTE_high\t\tIRTE_low\n"); | |
230 | ||
231 | raw_spin_lock_irqsave(&irq_2_ir_lock, flags); | |
232 | for (idx = 0; idx < INTR_REMAP_TABLE_ENTRIES; idx++) { | |
233 | pi_entry = &iommu->ir_table->base[idx]; | |
234 | if (!pi_entry->present || !pi_entry->p_pst) | |
235 | continue; | |
236 | ||
237 | seq_printf(m, " %-5d %02x:%02x.%01x %08x %08x %02x %016llx\t%016llx\n", | |
238 | idx, PCI_BUS_NUM(pi_entry->sid), | |
239 | PCI_SLOT(pi_entry->sid), PCI_FUNC(pi_entry->sid), | |
240 | pi_entry->pda_h, pi_entry->pda_l << 6, | |
241 | pi_entry->vector, pi_entry->high, | |
242 | pi_entry->low); | |
243 | } | |
244 | raw_spin_unlock_irqrestore(&irq_2_ir_lock, flags); | |
245 | } | |
246 | ||
247 | /* | |
248 | * For active IOMMUs go through the Interrupt remapping | |
249 | * table and print valid entries in a table format for | |
250 | * Remapped and Posted Interrupts. | |
251 | */ | |
252 | static int ir_translation_struct_show(struct seq_file *m, void *unused) | |
253 | { | |
254 | struct dmar_drhd_unit *drhd; | |
255 | struct intel_iommu *iommu; | |
256 | u64 irta; | |
257 | ||
258 | rcu_read_lock(); | |
259 | for_each_active_iommu(iommu, drhd) { | |
260 | if (!ecap_ir_support(iommu->ecap)) | |
261 | continue; | |
262 | ||
263 | seq_printf(m, "Remapped Interrupt supported on IOMMU: %s\n", | |
264 | iommu->name); | |
265 | ||
266 | if (iommu->ir_table) { | |
267 | irta = virt_to_phys(iommu->ir_table->base); | |
268 | seq_printf(m, " IR table address:%llx\n", irta); | |
269 | ir_tbl_remap_entry_show(m, iommu); | |
270 | } else { | |
271 | seq_puts(m, "Interrupt Remapping is not enabled\n"); | |
272 | } | |
273 | seq_putc(m, '\n'); | |
274 | } | |
275 | ||
276 | seq_puts(m, "****\n\n"); | |
277 | ||
278 | for_each_active_iommu(iommu, drhd) { | |
279 | if (!cap_pi_support(iommu->cap)) | |
280 | continue; | |
281 | ||
282 | seq_printf(m, "Posted Interrupt supported on IOMMU: %s\n", | |
283 | iommu->name); | |
284 | ||
285 | if (iommu->ir_table) { | |
286 | irta = virt_to_phys(iommu->ir_table->base); | |
287 | seq_printf(m, " IR table address:%llx\n", irta); | |
288 | ir_tbl_posted_entry_show(m, iommu); | |
289 | } else { | |
290 | seq_puts(m, "Interrupt Remapping is not enabled\n"); | |
291 | } | |
292 | seq_putc(m, '\n'); | |
293 | } | |
294 | rcu_read_unlock(); | |
295 | ||
296 | return 0; | |
297 | } | |
298 | DEFINE_SHOW_ATTRIBUTE(ir_translation_struct); | |
299 | #endif | |
300 | ||
ee2636b8 SM |
301 | void __init intel_iommu_debugfs_init(void) |
302 | { | |
6825d3ea GK |
303 | struct dentry *intel_iommu_debug = debugfs_create_dir("intel", |
304 | iommu_debugfs_dir); | |
305 | ||
306 | debugfs_create_file("iommu_regset", 0444, intel_iommu_debug, NULL, | |
307 | &iommu_regset_fops); | |
18f99c9b SM |
308 | debugfs_create_file("dmar_translation_struct", 0444, intel_iommu_debug, |
309 | NULL, &dmar_translation_struct_fops); | |
a6d268c6 SM |
310 | #ifdef CONFIG_IRQ_REMAP |
311 | debugfs_create_file("ir_translation_struct", 0444, intel_iommu_debug, | |
312 | NULL, &ir_translation_struct_fops); | |
313 | #endif | |
ee2636b8 | 314 | } |