1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * AMD Passthru DMA device driver |
4 | * -- Based on the CCP driver |
5 | * |
6 | * Copyright (C) 2016,2021 Advanced Micro Devices, Inc. |
7 | * |
8 | * Author: Sanjay R Mehta <sanju.mehta@amd.com> |
9 | * Author: Tom Lendacky <thomas.lendacky@amd.com> |
10 | * Author: Gary R Hook <gary.hook@amd.com> |
11 | */ |
12 | |
13 | #include <linux/device.h> |
14 | #include <linux/dma-mapping.h> |
15 | #include <linux/delay.h> |
16 | #include <linux/interrupt.h> |
17 | #include <linux/kernel.h> |
18 | #include <linux/kthread.h> |
19 | #include <linux/module.h> |
20 | #include <linux/pci_ids.h> |
21 | #include <linux/pci.h> |
22 | #include <linux/spinlock.h> |
23 | |
24 | #include "ptdma.h" |
25 | |
26 | struct pt_msix { |
27 | int msix_count; |
28 | struct msix_entry msix_entry; |
29 | }; |
30 | |
31 | /* |
32 | * pt_alloc_struct - allocate and initialize the pt_device struct |
33 | * |
34 | * @dev: device struct of the PTDMA |
35 | */ |
36 | static struct pt_device *pt_alloc_struct(struct device *dev) |
37 | { |
38 | struct pt_device *pt; |
39 | |
40 | pt = devm_kzalloc(dev, size: sizeof(*pt), GFP_KERNEL); |
41 | |
42 | if (!pt) |
43 | return NULL; |
44 | pt->dev = dev; |
45 | |
46 | INIT_LIST_HEAD(list: &pt->cmd); |
47 | |
48 | return pt; |
49 | } |
50 | |
51 | static int pt_get_msix_irqs(struct pt_device *pt) |
52 | { |
53 | struct pt_msix *pt_msix = pt->pt_msix; |
54 | struct device *dev = pt->dev; |
55 | struct pci_dev *pdev = to_pci_dev(dev); |
56 | int ret; |
57 | |
58 | pt_msix->msix_entry.entry = 0; |
59 | |
60 | ret = pci_enable_msix_range(dev: pdev, entries: &pt_msix->msix_entry, minvec: 1, maxvec: 1); |
61 | if (ret < 0) |
62 | return ret; |
63 | |
64 | pt_msix->msix_count = ret; |
65 | |
66 | pt->pt_irq = pt_msix->msix_entry.vector; |
67 | |
68 | return 0; |
69 | } |
70 | |
71 | static int pt_get_msi_irq(struct pt_device *pt) |
72 | { |
73 | struct device *dev = pt->dev; |
74 | struct pci_dev *pdev = to_pci_dev(dev); |
75 | int ret; |
76 | |
77 | ret = pci_enable_msi(dev: pdev); |
78 | if (ret) |
79 | return ret; |
80 | |
81 | pt->pt_irq = pdev->irq; |
82 | |
83 | return 0; |
84 | } |
85 | |
86 | static int pt_get_irqs(struct pt_device *pt) |
87 | { |
88 | struct device *dev = pt->dev; |
89 | int ret; |
90 | |
91 | ret = pt_get_msix_irqs(pt); |
92 | if (!ret) |
93 | return 0; |
94 | |
95 | /* Couldn't get MSI-X vectors, try MSI */ |
96 | dev_err(dev, "could not enable MSI-X (%d), trying MSI\n" , ret); |
97 | ret = pt_get_msi_irq(pt); |
98 | if (!ret) |
99 | return 0; |
100 | |
101 | /* Couldn't get MSI interrupt */ |
102 | dev_err(dev, "could not enable MSI (%d)\n" , ret); |
103 | |
104 | return ret; |
105 | } |
106 | |
107 | static void pt_free_irqs(struct pt_device *pt) |
108 | { |
109 | struct pt_msix *pt_msix = pt->pt_msix; |
110 | struct device *dev = pt->dev; |
111 | struct pci_dev *pdev = to_pci_dev(dev); |
112 | |
113 | if (pt_msix->msix_count) |
114 | pci_disable_msix(dev: pdev); |
115 | else if (pt->pt_irq) |
116 | pci_disable_msi(dev: pdev); |
117 | |
118 | pt->pt_irq = 0; |
119 | } |
120 | |
121 | static int pt_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) |
122 | { |
123 | struct pt_device *pt; |
124 | struct pt_msix *pt_msix; |
125 | struct device *dev = &pdev->dev; |
126 | void __iomem * const *iomap_table; |
127 | int bar_mask; |
128 | int ret = -ENOMEM; |
129 | |
130 | pt = pt_alloc_struct(dev); |
131 | if (!pt) |
132 | goto e_err; |
133 | |
134 | pt_msix = devm_kzalloc(dev, size: sizeof(*pt_msix), GFP_KERNEL); |
135 | if (!pt_msix) |
136 | goto e_err; |
137 | |
138 | pt->pt_msix = pt_msix; |
139 | pt->dev_vdata = (struct pt_dev_vdata *)id->driver_data; |
140 | if (!pt->dev_vdata) { |
141 | ret = -ENODEV; |
142 | dev_err(dev, "missing driver data\n" ); |
143 | goto e_err; |
144 | } |
145 | |
146 | ret = pcim_enable_device(pdev); |
147 | if (ret) { |
148 | dev_err(dev, "pcim_enable_device failed (%d)\n" , ret); |
149 | goto e_err; |
150 | } |
151 | |
152 | bar_mask = pci_select_bars(dev: pdev, IORESOURCE_MEM); |
153 | ret = pcim_iomap_regions(pdev, mask: bar_mask, name: "ptdma" ); |
154 | if (ret) { |
155 | dev_err(dev, "pcim_iomap_regions failed (%d)\n" , ret); |
156 | goto e_err; |
157 | } |
158 | |
159 | iomap_table = pcim_iomap_table(pdev); |
160 | if (!iomap_table) { |
161 | dev_err(dev, "pcim_iomap_table failed\n" ); |
162 | ret = -ENOMEM; |
163 | goto e_err; |
164 | } |
165 | |
166 | pt->io_regs = iomap_table[pt->dev_vdata->bar]; |
167 | if (!pt->io_regs) { |
168 | dev_err(dev, "ioremap failed\n" ); |
169 | ret = -ENOMEM; |
170 | goto e_err; |
171 | } |
172 | |
173 | ret = pt_get_irqs(pt); |
174 | if (ret) |
175 | goto e_err; |
176 | |
177 | pci_set_master(dev: pdev); |
178 | |
179 | ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(48)); |
180 | if (ret) { |
181 | ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); |
182 | if (ret) { |
183 | dev_err(dev, "dma_set_mask_and_coherent failed (%d)\n" , |
184 | ret); |
185 | goto e_err; |
186 | } |
187 | } |
188 | |
189 | dev_set_drvdata(dev, data: pt); |
190 | |
191 | if (pt->dev_vdata) |
192 | ret = pt_core_init(pt); |
193 | |
194 | if (ret) |
195 | goto e_err; |
196 | |
197 | return 0; |
198 | |
199 | e_err: |
200 | dev_err(dev, "initialization failed ret = %d\n" , ret); |
201 | |
202 | return ret; |
203 | } |
204 | |
205 | static void pt_pci_remove(struct pci_dev *pdev) |
206 | { |
207 | struct device *dev = &pdev->dev; |
208 | struct pt_device *pt = dev_get_drvdata(dev); |
209 | |
210 | if (!pt) |
211 | return; |
212 | |
213 | if (pt->dev_vdata) |
214 | pt_core_destroy(pt); |
215 | |
216 | pt_free_irqs(pt); |
217 | } |
218 | |
219 | static const struct pt_dev_vdata dev_vdata[] = { |
220 | { |
221 | .bar = 2, |
222 | }, |
223 | }; |
224 | |
225 | static const struct pci_device_id pt_pci_table[] = { |
226 | { PCI_VDEVICE(AMD, 0x1498), (kernel_ulong_t)&dev_vdata[0] }, |
227 | /* Last entry must be zero */ |
228 | { 0, } |
229 | }; |
230 | MODULE_DEVICE_TABLE(pci, pt_pci_table); |
231 | |
232 | static struct pci_driver pt_pci_driver = { |
233 | .name = "ptdma" , |
234 | .id_table = pt_pci_table, |
235 | .probe = pt_pci_probe, |
236 | .remove = pt_pci_remove, |
237 | }; |
238 | |
239 | module_pci_driver(pt_pci_driver); |
240 | |
241 | MODULE_AUTHOR("Sanjay R Mehta <sanju.mehta@amd.com>" ); |
242 | MODULE_LICENSE("GPL" ); |
243 | MODULE_DESCRIPTION("AMD PassThru DMA driver" ); |
244 | |