diff options
Diffstat (limited to 'drivers/misc/mic/host/mic_smpt.c')
-rw-r--r-- | drivers/misc/mic/host/mic_smpt.c | 442 |
1 files changed, 442 insertions, 0 deletions
diff --git a/drivers/misc/mic/host/mic_smpt.c b/drivers/misc/mic/host/mic_smpt.c new file mode 100644 index 000000000000..fae474c4899e --- /dev/null +++ b/drivers/misc/mic/host/mic_smpt.c | |||
@@ -0,0 +1,442 @@ | |||
1 | /* | ||
2 | * Intel MIC Platform Software Stack (MPSS) | ||
3 | * | ||
4 | * Copyright(c) 2013 Intel Corporation. | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License, version 2, as | ||
8 | * published by the Free Software Foundation. | ||
9 | * | ||
10 | * This program is distributed in the hope that it will be useful, but | ||
11 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
13 | * General Public License for more details. | ||
14 | * | ||
15 | * The full GNU General Public License is included in this distribution in | ||
16 | * the file called "COPYING". | ||
17 | * | ||
18 | * Intel MIC Host driver. | ||
19 | * | ||
20 | */ | ||
21 | #include <linux/pci.h> | ||
22 | |||
23 | #include "../common/mic_dev.h" | ||
24 | #include "mic_device.h" | ||
25 | #include "mic_smpt.h" | ||
26 | |||
27 | static inline u64 mic_system_page_mask(struct mic_device *mdev) | ||
28 | { | ||
29 | return (1ULL << mdev->smpt->info.page_shift) - 1ULL; | ||
30 | } | ||
31 | |||
32 | static inline u8 mic_sys_addr_to_smpt(struct mic_device *mdev, dma_addr_t pa) | ||
33 | { | ||
34 | return (pa - mdev->smpt->info.base) >> mdev->smpt->info.page_shift; | ||
35 | } | ||
36 | |||
37 | static inline u64 mic_smpt_to_pa(struct mic_device *mdev, u8 index) | ||
38 | { | ||
39 | return mdev->smpt->info.base + (index * mdev->smpt->info.page_size); | ||
40 | } | ||
41 | |||
42 | static inline u64 mic_smpt_offset(struct mic_device *mdev, dma_addr_t pa) | ||
43 | { | ||
44 | return pa & mic_system_page_mask(mdev); | ||
45 | } | ||
46 | |||
47 | static inline u64 mic_smpt_align_low(struct mic_device *mdev, dma_addr_t pa) | ||
48 | { | ||
49 | return ALIGN(pa - mic_system_page_mask(mdev), | ||
50 | mdev->smpt->info.page_size); | ||
51 | } | ||
52 | |||
53 | static inline u64 mic_smpt_align_high(struct mic_device *mdev, dma_addr_t pa) | ||
54 | { | ||
55 | return ALIGN(pa, mdev->smpt->info.page_size); | ||
56 | } | ||
57 | |||
58 | /* Total Cumulative system memory accessible by MIC across all SMPT entries */ | ||
59 | static inline u64 mic_max_system_memory(struct mic_device *mdev) | ||
60 | { | ||
61 | return mdev->smpt->info.num_reg * mdev->smpt->info.page_size; | ||
62 | } | ||
63 | |||
64 | /* Maximum system memory address accessible by MIC */ | ||
65 | static inline u64 mic_max_system_addr(struct mic_device *mdev) | ||
66 | { | ||
67 | return mdev->smpt->info.base + mic_max_system_memory(mdev) - 1ULL; | ||
68 | } | ||
69 | |||
70 | /* Check if the DMA address is a MIC system memory address */ | ||
71 | static inline bool | ||
72 | mic_is_system_addr(struct mic_device *mdev, dma_addr_t pa) | ||
73 | { | ||
74 | return pa >= mdev->smpt->info.base && pa <= mic_max_system_addr(mdev); | ||
75 | } | ||
76 | |||
77 | /* Populate an SMPT entry and update the reference counts. */ | ||
78 | static void mic_add_smpt_entry(int spt, s64 *ref, u64 addr, | ||
79 | int entries, struct mic_device *mdev) | ||
80 | { | ||
81 | struct mic_smpt_info *smpt_info = mdev->smpt; | ||
82 | int i; | ||
83 | |||
84 | for (i = spt; i < spt + entries; i++, | ||
85 | addr += smpt_info->info.page_size) { | ||
86 | if (!smpt_info->entry[i].ref_count && | ||
87 | (smpt_info->entry[i].dma_addr != addr)) { | ||
88 | mdev->smpt_ops->set(mdev, addr, i); | ||
89 | smpt_info->entry[i].dma_addr = addr; | ||
90 | } | ||
91 | smpt_info->entry[i].ref_count += ref[i - spt]; | ||
92 | } | ||
93 | } | ||
94 | |||
95 | /* | ||
96 | * Find an available MIC address in MIC SMPT address space | ||
97 | * for a given DMA address and size. | ||
98 | */ | ||
99 | static dma_addr_t mic_smpt_op(struct mic_device *mdev, u64 dma_addr, | ||
100 | int entries, s64 *ref, size_t size) | ||
101 | { | ||
102 | int spt; | ||
103 | int ae = 0; | ||
104 | int i; | ||
105 | unsigned long flags; | ||
106 | dma_addr_t mic_addr = 0; | ||
107 | dma_addr_t addr = dma_addr; | ||
108 | struct mic_smpt_info *smpt_info = mdev->smpt; | ||
109 | |||
110 | spin_lock_irqsave(&smpt_info->smpt_lock, flags); | ||
111 | |||
112 | /* find existing entries */ | ||
113 | for (i = 0; i < smpt_info->info.num_reg; i++) { | ||
114 | if (smpt_info->entry[i].dma_addr == addr) { | ||
115 | ae++; | ||
116 | addr += smpt_info->info.page_size; | ||
117 | } else if (ae) /* cannot find contiguous entries */ | ||
118 | goto not_found; | ||
119 | |||
120 | if (ae == entries) | ||
121 | goto found; | ||
122 | } | ||
123 | |||
124 | /* find free entry */ | ||
125 | for (ae = 0, i = 0; i < smpt_info->info.num_reg; i++) { | ||
126 | ae = (smpt_info->entry[i].ref_count == 0) ? ae + 1 : 0; | ||
127 | if (ae == entries) | ||
128 | goto found; | ||
129 | } | ||
130 | |||
131 | not_found: | ||
132 | spin_unlock_irqrestore(&smpt_info->smpt_lock, flags); | ||
133 | return mic_addr; | ||
134 | |||
135 | found: | ||
136 | spt = i - entries + 1; | ||
137 | mic_addr = mic_smpt_to_pa(mdev, spt); | ||
138 | mic_add_smpt_entry(spt, ref, dma_addr, entries, mdev); | ||
139 | smpt_info->map_count++; | ||
140 | smpt_info->ref_count += (s64)size; | ||
141 | spin_unlock_irqrestore(&smpt_info->smpt_lock, flags); | ||
142 | return mic_addr; | ||
143 | } | ||
144 | |||
145 | /* | ||
146 | * Returns number of smpt entries needed for dma_addr to dma_addr + size | ||
147 | * also returns the reference count array for each of those entries | ||
148 | * and the starting smpt address | ||
149 | */ | ||
150 | static int mic_get_smpt_ref_count(struct mic_device *mdev, dma_addr_t dma_addr, | ||
151 | size_t size, s64 *ref, u64 *smpt_start) | ||
152 | { | ||
153 | u64 start = dma_addr; | ||
154 | u64 end = dma_addr + size; | ||
155 | int i = 0; | ||
156 | |||
157 | while (start < end) { | ||
158 | ref[i++] = min(mic_smpt_align_high(mdev, start + 1), | ||
159 | end) - start; | ||
160 | start = mic_smpt_align_high(mdev, start + 1); | ||
161 | } | ||
162 | |||
163 | if (smpt_start) | ||
164 | *smpt_start = mic_smpt_align_low(mdev, dma_addr); | ||
165 | |||
166 | return i; | ||
167 | } | ||
168 | |||
169 | /* | ||
170 | * mic_to_dma_addr - Converts a MIC address to a DMA address. | ||
171 | * | ||
172 | * @mdev: pointer to mic_device instance. | ||
173 | * @mic_addr: MIC address. | ||
174 | * | ||
175 | * returns a DMA address. | ||
176 | */ | ||
177 | static dma_addr_t | ||
178 | mic_to_dma_addr(struct mic_device *mdev, dma_addr_t mic_addr) | ||
179 | { | ||
180 | struct mic_smpt_info *smpt_info = mdev->smpt; | ||
181 | int spt; | ||
182 | dma_addr_t dma_addr; | ||
183 | |||
184 | if (!mic_is_system_addr(mdev, mic_addr)) { | ||
185 | dev_err(mdev->sdev->parent, | ||
186 | "mic_addr is invalid. mic_addr = 0x%llx\n", mic_addr); | ||
187 | return -EINVAL; | ||
188 | } | ||
189 | spt = mic_sys_addr_to_smpt(mdev, mic_addr); | ||
190 | dma_addr = smpt_info->entry[spt].dma_addr + | ||
191 | mic_smpt_offset(mdev, mic_addr); | ||
192 | return dma_addr; | ||
193 | } | ||
194 | |||
195 | /** | ||
196 | * mic_map - Maps a DMA address to a MIC physical address. | ||
197 | * | ||
198 | * @mdev: pointer to mic_device instance. | ||
199 | * @dma_addr: DMA address. | ||
200 | * @size: Size of the region to be mapped. | ||
201 | * | ||
202 | * This API converts the DMA address provided to a DMA address understood | ||
203 | * by MIC. Caller should check for errors by calling mic_map_error(..). | ||
204 | * | ||
205 | * returns DMA address as required by MIC. | ||
206 | */ | ||
207 | dma_addr_t mic_map(struct mic_device *mdev, dma_addr_t dma_addr, size_t size) | ||
208 | { | ||
209 | dma_addr_t mic_addr = 0; | ||
210 | int num_entries; | ||
211 | s64 *ref; | ||
212 | u64 smpt_start; | ||
213 | |||
214 | if (!size || size > mic_max_system_memory(mdev)) | ||
215 | return mic_addr; | ||
216 | |||
217 | ref = kmalloc(mdev->smpt->info.num_reg * sizeof(s64), GFP_KERNEL); | ||
218 | if (!ref) | ||
219 | return mic_addr; | ||
220 | |||
221 | num_entries = mic_get_smpt_ref_count(mdev, dma_addr, size, | ||
222 | ref, &smpt_start); | ||
223 | |||
224 | /* Set the smpt table appropriately and get 16G aligned mic address */ | ||
225 | mic_addr = mic_smpt_op(mdev, smpt_start, num_entries, ref, size); | ||
226 | |||
227 | kfree(ref); | ||
228 | |||
229 | /* | ||
230 | * If mic_addr is zero then its an error case | ||
231 | * since mic_addr can never be zero. | ||
232 | * else generate mic_addr by adding the 16G offset in dma_addr | ||
233 | */ | ||
234 | if (!mic_addr && MIC_FAMILY_X100 == mdev->family) { | ||
235 | dev_err(mdev->sdev->parent, | ||
236 | "mic_map failed dma_addr 0x%llx size 0x%lx\n", | ||
237 | dma_addr, size); | ||
238 | return mic_addr; | ||
239 | } else { | ||
240 | return mic_addr + mic_smpt_offset(mdev, dma_addr); | ||
241 | } | ||
242 | } | ||
243 | |||
244 | /** | ||
245 | * mic_unmap - Unmaps a MIC physical address. | ||
246 | * | ||
247 | * @mdev: pointer to mic_device instance. | ||
248 | * @mic_addr: MIC physical address. | ||
249 | * @size: Size of the region to be unmapped. | ||
250 | * | ||
251 | * This API unmaps the mappings created by mic_map(..). | ||
252 | * | ||
253 | * returns None. | ||
254 | */ | ||
255 | void mic_unmap(struct mic_device *mdev, dma_addr_t mic_addr, size_t size) | ||
256 | { | ||
257 | struct mic_smpt_info *smpt_info = mdev->smpt; | ||
258 | s64 *ref; | ||
259 | int num_smpt; | ||
260 | int spt; | ||
261 | int i; | ||
262 | unsigned long flags; | ||
263 | |||
264 | if (!size) | ||
265 | return; | ||
266 | |||
267 | if (!mic_is_system_addr(mdev, mic_addr)) { | ||
268 | dev_err(mdev->sdev->parent, | ||
269 | "invalid address: 0x%llx\n", mic_addr); | ||
270 | return; | ||
271 | } | ||
272 | |||
273 | spt = mic_sys_addr_to_smpt(mdev, mic_addr); | ||
274 | ref = kmalloc(mdev->smpt->info.num_reg * sizeof(s64), GFP_KERNEL); | ||
275 | if (!ref) | ||
276 | return; | ||
277 | |||
278 | /* Get number of smpt entries to be mapped, ref count array */ | ||
279 | num_smpt = mic_get_smpt_ref_count(mdev, mic_addr, size, ref, NULL); | ||
280 | |||
281 | spin_lock_irqsave(&smpt_info->smpt_lock, flags); | ||
282 | smpt_info->unmap_count++; | ||
283 | smpt_info->ref_count -= (s64)size; | ||
284 | |||
285 | for (i = spt; i < spt + num_smpt; i++) { | ||
286 | smpt_info->entry[i].ref_count -= ref[i - spt]; | ||
287 | if (smpt_info->entry[i].ref_count < 0) | ||
288 | dev_warn(mdev->sdev->parent, | ||
289 | "ref count for entry %d is negative\n", i); | ||
290 | } | ||
291 | spin_unlock_irqrestore(&smpt_info->smpt_lock, flags); | ||
292 | kfree(ref); | ||
293 | } | ||
294 | |||
295 | /** | ||
296 | * mic_map_single - Maps a virtual address to a MIC physical address. | ||
297 | * | ||
298 | * @mdev: pointer to mic_device instance. | ||
299 | * @va: Kernel direct mapped virtual address. | ||
300 | * @size: Size of the region to be mapped. | ||
301 | * | ||
302 | * This API calls pci_map_single(..) for the direct mapped virtual address | ||
303 | * and then converts the DMA address provided to a DMA address understood | ||
304 | * by MIC. Caller should check for errors by calling mic_map_error(..). | ||
305 | * | ||
306 | * returns DMA address as required by MIC. | ||
307 | */ | ||
308 | dma_addr_t mic_map_single(struct mic_device *mdev, void *va, size_t size) | ||
309 | { | ||
310 | dma_addr_t mic_addr = 0; | ||
311 | struct pci_dev *pdev = container_of(mdev->sdev->parent, | ||
312 | struct pci_dev, dev); | ||
313 | dma_addr_t dma_addr = | ||
314 | pci_map_single(pdev, va, size, PCI_DMA_BIDIRECTIONAL); | ||
315 | |||
316 | if (!pci_dma_mapping_error(pdev, dma_addr)) { | ||
317 | mic_addr = mic_map(mdev, dma_addr, size); | ||
318 | if (!mic_addr) { | ||
319 | dev_err(mdev->sdev->parent, | ||
320 | "mic_map failed dma_addr 0x%llx size 0x%lx\n", | ||
321 | dma_addr, size); | ||
322 | pci_unmap_single(pdev, dma_addr, | ||
323 | size, PCI_DMA_BIDIRECTIONAL); | ||
324 | } | ||
325 | } | ||
326 | return mic_addr; | ||
327 | } | ||
328 | |||
329 | /** | ||
330 | * mic_unmap_single - Unmaps a MIC physical address. | ||
331 | * | ||
332 | * @mdev: pointer to mic_device instance. | ||
333 | * @mic_addr: MIC physical address. | ||
334 | * @size: Size of the region to be unmapped. | ||
335 | * | ||
336 | * This API unmaps the mappings created by mic_map_single(..). | ||
337 | * | ||
338 | * returns None. | ||
339 | */ | ||
340 | void | ||
341 | mic_unmap_single(struct mic_device *mdev, dma_addr_t mic_addr, size_t size) | ||
342 | { | ||
343 | struct pci_dev *pdev = container_of(mdev->sdev->parent, | ||
344 | struct pci_dev, dev); | ||
345 | dma_addr_t dma_addr = mic_to_dma_addr(mdev, mic_addr); | ||
346 | mic_unmap(mdev, mic_addr, size); | ||
347 | pci_unmap_single(pdev, dma_addr, size, PCI_DMA_BIDIRECTIONAL); | ||
348 | } | ||
349 | |||
350 | /** | ||
351 | * mic_smpt_init - Initialize MIC System Memory Page Tables. | ||
352 | * | ||
353 | * @mdev: pointer to mic_device instance. | ||
354 | * | ||
355 | * returns 0 for success and -errno for error. | ||
356 | */ | ||
357 | int mic_smpt_init(struct mic_device *mdev) | ||
358 | { | ||
359 | int i, err = 0; | ||
360 | dma_addr_t dma_addr; | ||
361 | struct mic_smpt_info *smpt_info; | ||
362 | |||
363 | mdev->smpt = kmalloc(sizeof(*mdev->smpt), GFP_KERNEL); | ||
364 | if (!mdev->smpt) | ||
365 | return -ENOMEM; | ||
366 | |||
367 | smpt_info = mdev->smpt; | ||
368 | mdev->smpt_ops->init(mdev); | ||
369 | smpt_info->entry = kmalloc_array(smpt_info->info.num_reg, | ||
370 | sizeof(*smpt_info->entry), GFP_KERNEL); | ||
371 | if (!smpt_info->entry) { | ||
372 | err = -ENOMEM; | ||
373 | goto free_smpt; | ||
374 | } | ||
375 | spin_lock_init(&smpt_info->smpt_lock); | ||
376 | for (i = 0; i < smpt_info->info.num_reg; i++) { | ||
377 | dma_addr = i * smpt_info->info.page_size; | ||
378 | smpt_info->entry[i].dma_addr = dma_addr; | ||
379 | smpt_info->entry[i].ref_count = 0; | ||
380 | mdev->smpt_ops->set(mdev, dma_addr, i); | ||
381 | } | ||
382 | smpt_info->ref_count = 0; | ||
383 | smpt_info->map_count = 0; | ||
384 | smpt_info->unmap_count = 0; | ||
385 | return 0; | ||
386 | free_smpt: | ||
387 | kfree(smpt_info); | ||
388 | return err; | ||
389 | } | ||
390 | |||
391 | /** | ||
392 | * mic_smpt_uninit - UnInitialize MIC System Memory Page Tables. | ||
393 | * | ||
394 | * @mdev: pointer to mic_device instance. | ||
395 | * | ||
396 | * returns None. | ||
397 | */ | ||
398 | void mic_smpt_uninit(struct mic_device *mdev) | ||
399 | { | ||
400 | struct mic_smpt_info *smpt_info = mdev->smpt; | ||
401 | int i; | ||
402 | |||
403 | dev_dbg(mdev->sdev->parent, | ||
404 | "nodeid %d SMPT ref count %lld map %lld unmap %lld\n", | ||
405 | mdev->id, smpt_info->ref_count, | ||
406 | smpt_info->map_count, smpt_info->unmap_count); | ||
407 | |||
408 | for (i = 0; i < smpt_info->info.num_reg; i++) { | ||
409 | dev_dbg(mdev->sdev->parent, | ||
410 | "SMPT entry[%d] dma_addr = 0x%llx ref_count = %lld\n", | ||
411 | i, smpt_info->entry[i].dma_addr, | ||
412 | smpt_info->entry[i].ref_count); | ||
413 | if (smpt_info->entry[i].ref_count) | ||
414 | dev_warn(mdev->sdev->parent, | ||
415 | "ref count for entry %d is not zero\n", i); | ||
416 | } | ||
417 | kfree(smpt_info->entry); | ||
418 | kfree(smpt_info); | ||
419 | } | ||
420 | |||
421 | /** | ||
422 | * mic_smpt_restore - Restore MIC System Memory Page Tables. | ||
423 | * | ||
424 | * @mdev: pointer to mic_device instance. | ||
425 | * | ||
426 | * Restore the SMPT registers to values previously stored in the | ||
427 | * SW data structures. Some MIC steppings lose register state | ||
428 | * across resets and this API should be called for performing | ||
429 | * a restore operation if required. | ||
430 | * | ||
431 | * returns None. | ||
432 | */ | ||
433 | void mic_smpt_restore(struct mic_device *mdev) | ||
434 | { | ||
435 | int i; | ||
436 | dma_addr_t dma_addr; | ||
437 | |||
438 | for (i = 0; i < mdev->smpt->info.num_reg; i++) { | ||
439 | dma_addr = mdev->smpt->entry[i].dma_addr; | ||
440 | mdev->smpt_ops->set(mdev, dma_addr, i); | ||
441 | } | ||
442 | } | ||