diff options
Diffstat (limited to 'drivers/net/mlx4/icm.c')
-rw-r--r-- | drivers/net/mlx4/icm.c | 379 |
1 files changed, 379 insertions, 0 deletions
diff --git a/drivers/net/mlx4/icm.c b/drivers/net/mlx4/icm.c new file mode 100644 index 000000000000..e96feaed6ed4 --- /dev/null +++ b/drivers/net/mlx4/icm.c | |||
@@ -0,0 +1,379 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2005 Mellanox Technologies. All rights reserved. | ||
3 | * Copyright (c) 2006, 2007 Cisco Systems, Inc. All rights reserved. | ||
4 | * | ||
5 | * This software is available to you under a choice of one of two | ||
6 | * licenses. You may choose to be licensed under the terms of the GNU | ||
7 | * General Public License (GPL) Version 2, available from the file | ||
8 | * COPYING in the main directory of this source tree, or the | ||
9 | * OpenIB.org BSD license below: | ||
10 | * | ||
11 | * Redistribution and use in source and binary forms, with or | ||
12 | * without modification, are permitted provided that the following | ||
13 | * conditions are met: | ||
14 | * | ||
15 | * - Redistributions of source code must retain the above | ||
16 | * copyright notice, this list of conditions and the following | ||
17 | * disclaimer. | ||
18 | * | ||
19 | * - Redistributions in binary form must reproduce the above | ||
20 | * copyright notice, this list of conditions and the following | ||
21 | * disclaimer in the documentation and/or other materials | ||
22 | * provided with the distribution. | ||
23 | * | ||
24 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
25 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
26 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
27 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | ||
28 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | ||
29 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | ||
30 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
31 | * SOFTWARE. | ||
32 | */ | ||
33 | |||
34 | #include <linux/init.h> | ||
35 | #include <linux/errno.h> | ||
36 | |||
37 | #include <linux/mlx4/cmd.h> | ||
38 | |||
39 | #include "mlx4.h" | ||
40 | #include "icm.h" | ||
41 | #include "fw.h" | ||
42 | |||
43 | /* | ||
44 | * We allocate in as big chunks as we can, up to a maximum of 256 KB | ||
45 | * per chunk. | ||
46 | */ | ||
47 | enum { | ||
48 | MLX4_ICM_ALLOC_SIZE = 1 << 18, | ||
49 | MLX4_TABLE_CHUNK_SIZE = 1 << 18 | ||
50 | }; | ||
51 | |||
52 | void mlx4_free_icm(struct mlx4_dev *dev, struct mlx4_icm *icm) | ||
53 | { | ||
54 | struct mlx4_icm_chunk *chunk, *tmp; | ||
55 | int i; | ||
56 | |||
57 | list_for_each_entry_safe(chunk, tmp, &icm->chunk_list, list) { | ||
58 | if (chunk->nsg > 0) | ||
59 | pci_unmap_sg(dev->pdev, chunk->mem, chunk->npages, | ||
60 | PCI_DMA_BIDIRECTIONAL); | ||
61 | |||
62 | for (i = 0; i < chunk->npages; ++i) | ||
63 | __free_pages(chunk->mem[i].page, | ||
64 | get_order(chunk->mem[i].length)); | ||
65 | |||
66 | kfree(chunk); | ||
67 | } | ||
68 | |||
69 | kfree(icm); | ||
70 | } | ||
71 | |||
72 | struct mlx4_icm *mlx4_alloc_icm(struct mlx4_dev *dev, int npages, | ||
73 | gfp_t gfp_mask) | ||
74 | { | ||
75 | struct mlx4_icm *icm; | ||
76 | struct mlx4_icm_chunk *chunk = NULL; | ||
77 | int cur_order; | ||
78 | |||
79 | icm = kmalloc(sizeof *icm, gfp_mask & ~(__GFP_HIGHMEM | __GFP_NOWARN)); | ||
80 | if (!icm) | ||
81 | return icm; | ||
82 | |||
83 | icm->refcount = 0; | ||
84 | INIT_LIST_HEAD(&icm->chunk_list); | ||
85 | |||
86 | cur_order = get_order(MLX4_ICM_ALLOC_SIZE); | ||
87 | |||
88 | while (npages > 0) { | ||
89 | if (!chunk) { | ||
90 | chunk = kmalloc(sizeof *chunk, | ||
91 | gfp_mask & ~(__GFP_HIGHMEM | __GFP_NOWARN)); | ||
92 | if (!chunk) | ||
93 | goto fail; | ||
94 | |||
95 | chunk->npages = 0; | ||
96 | chunk->nsg = 0; | ||
97 | list_add_tail(&chunk->list, &icm->chunk_list); | ||
98 | } | ||
99 | |||
100 | while (1 << cur_order > npages) | ||
101 | --cur_order; | ||
102 | |||
103 | chunk->mem[chunk->npages].page = alloc_pages(gfp_mask, cur_order); | ||
104 | if (chunk->mem[chunk->npages].page) { | ||
105 | chunk->mem[chunk->npages].length = PAGE_SIZE << cur_order; | ||
106 | chunk->mem[chunk->npages].offset = 0; | ||
107 | |||
108 | if (++chunk->npages == MLX4_ICM_CHUNK_LEN) { | ||
109 | chunk->nsg = pci_map_sg(dev->pdev, chunk->mem, | ||
110 | chunk->npages, | ||
111 | PCI_DMA_BIDIRECTIONAL); | ||
112 | |||
113 | if (chunk->nsg <= 0) | ||
114 | goto fail; | ||
115 | |||
116 | chunk = NULL; | ||
117 | } | ||
118 | |||
119 | npages -= 1 << cur_order; | ||
120 | } else { | ||
121 | --cur_order; | ||
122 | if (cur_order < 0) | ||
123 | goto fail; | ||
124 | } | ||
125 | } | ||
126 | |||
127 | if (chunk) { | ||
128 | chunk->nsg = pci_map_sg(dev->pdev, chunk->mem, | ||
129 | chunk->npages, | ||
130 | PCI_DMA_BIDIRECTIONAL); | ||
131 | |||
132 | if (chunk->nsg <= 0) | ||
133 | goto fail; | ||
134 | } | ||
135 | |||
136 | return icm; | ||
137 | |||
138 | fail: | ||
139 | mlx4_free_icm(dev, icm); | ||
140 | return NULL; | ||
141 | } | ||
142 | |||
143 | static int mlx4_MAP_ICM(struct mlx4_dev *dev, struct mlx4_icm *icm, u64 virt) | ||
144 | { | ||
145 | return mlx4_map_cmd(dev, MLX4_CMD_MAP_ICM, icm, virt); | ||
146 | } | ||
147 | |||
148 | int mlx4_UNMAP_ICM(struct mlx4_dev *dev, u64 virt, u32 page_count) | ||
149 | { | ||
150 | return mlx4_cmd(dev, virt, page_count, 0, MLX4_CMD_UNMAP_ICM, | ||
151 | MLX4_CMD_TIME_CLASS_B); | ||
152 | } | ||
153 | |||
154 | int mlx4_MAP_ICM_page(struct mlx4_dev *dev, u64 dma_addr, u64 virt) | ||
155 | { | ||
156 | struct mlx4_cmd_mailbox *mailbox; | ||
157 | __be64 *inbox; | ||
158 | int err; | ||
159 | |||
160 | mailbox = mlx4_alloc_cmd_mailbox(dev); | ||
161 | if (IS_ERR(mailbox)) | ||
162 | return PTR_ERR(mailbox); | ||
163 | inbox = mailbox->buf; | ||
164 | |||
165 | inbox[0] = cpu_to_be64(virt); | ||
166 | inbox[1] = cpu_to_be64(dma_addr); | ||
167 | |||
168 | err = mlx4_cmd(dev, mailbox->dma, 1, 0, MLX4_CMD_MAP_ICM, | ||
169 | MLX4_CMD_TIME_CLASS_B); | ||
170 | |||
171 | mlx4_free_cmd_mailbox(dev, mailbox); | ||
172 | |||
173 | if (!err) | ||
174 | mlx4_dbg(dev, "Mapped page at %llx to %llx for ICM.\n", | ||
175 | (unsigned long long) dma_addr, (unsigned long long) virt); | ||
176 | |||
177 | return err; | ||
178 | } | ||
179 | |||
180 | int mlx4_MAP_ICM_AUX(struct mlx4_dev *dev, struct mlx4_icm *icm) | ||
181 | { | ||
182 | return mlx4_map_cmd(dev, MLX4_CMD_MAP_ICM_AUX, icm, -1); | ||
183 | } | ||
184 | |||
185 | int mlx4_UNMAP_ICM_AUX(struct mlx4_dev *dev) | ||
186 | { | ||
187 | return mlx4_cmd(dev, 0, 0, 0, MLX4_CMD_UNMAP_ICM_AUX, MLX4_CMD_TIME_CLASS_B); | ||
188 | } | ||
189 | |||
190 | int mlx4_table_get(struct mlx4_dev *dev, struct mlx4_icm_table *table, int obj) | ||
191 | { | ||
192 | int i = (obj & (table->num_obj - 1)) / (MLX4_TABLE_CHUNK_SIZE / table->obj_size); | ||
193 | int ret = 0; | ||
194 | |||
195 | mutex_lock(&table->mutex); | ||
196 | |||
197 | if (table->icm[i]) { | ||
198 | ++table->icm[i]->refcount; | ||
199 | goto out; | ||
200 | } | ||
201 | |||
202 | table->icm[i] = mlx4_alloc_icm(dev, MLX4_TABLE_CHUNK_SIZE >> PAGE_SHIFT, | ||
203 | (table->lowmem ? GFP_KERNEL : GFP_HIGHUSER) | | ||
204 | __GFP_NOWARN); | ||
205 | if (!table->icm[i]) { | ||
206 | ret = -ENOMEM; | ||
207 | goto out; | ||
208 | } | ||
209 | |||
210 | if (mlx4_MAP_ICM(dev, table->icm[i], table->virt + | ||
211 | (u64) i * MLX4_TABLE_CHUNK_SIZE)) { | ||
212 | mlx4_free_icm(dev, table->icm[i]); | ||
213 | table->icm[i] = NULL; | ||
214 | ret = -ENOMEM; | ||
215 | goto out; | ||
216 | } | ||
217 | |||
218 | ++table->icm[i]->refcount; | ||
219 | |||
220 | out: | ||
221 | mutex_unlock(&table->mutex); | ||
222 | return ret; | ||
223 | } | ||
224 | |||
225 | void mlx4_table_put(struct mlx4_dev *dev, struct mlx4_icm_table *table, int obj) | ||
226 | { | ||
227 | int i; | ||
228 | |||
229 | i = (obj & (table->num_obj - 1)) / (MLX4_TABLE_CHUNK_SIZE / table->obj_size); | ||
230 | |||
231 | mutex_lock(&table->mutex); | ||
232 | |||
233 | if (--table->icm[i]->refcount == 0) { | ||
234 | mlx4_UNMAP_ICM(dev, table->virt + i * MLX4_TABLE_CHUNK_SIZE, | ||
235 | MLX4_TABLE_CHUNK_SIZE / MLX4_ICM_PAGE_SIZE); | ||
236 | mlx4_free_icm(dev, table->icm[i]); | ||
237 | table->icm[i] = NULL; | ||
238 | } | ||
239 | |||
240 | mutex_unlock(&table->mutex); | ||
241 | } | ||
242 | |||
243 | void *mlx4_table_find(struct mlx4_icm_table *table, int obj) | ||
244 | { | ||
245 | int idx, offset, i; | ||
246 | struct mlx4_icm_chunk *chunk; | ||
247 | struct mlx4_icm *icm; | ||
248 | struct page *page = NULL; | ||
249 | |||
250 | if (!table->lowmem) | ||
251 | return NULL; | ||
252 | |||
253 | mutex_lock(&table->mutex); | ||
254 | |||
255 | idx = obj & (table->num_obj - 1); | ||
256 | icm = table->icm[idx / (MLX4_TABLE_CHUNK_SIZE / table->obj_size)]; | ||
257 | offset = idx % (MLX4_TABLE_CHUNK_SIZE / table->obj_size); | ||
258 | |||
259 | if (!icm) | ||
260 | goto out; | ||
261 | |||
262 | list_for_each_entry(chunk, &icm->chunk_list, list) { | ||
263 | for (i = 0; i < chunk->npages; ++i) { | ||
264 | if (chunk->mem[i].length > offset) { | ||
265 | page = chunk->mem[i].page; | ||
266 | goto out; | ||
267 | } | ||
268 | offset -= chunk->mem[i].length; | ||
269 | } | ||
270 | } | ||
271 | |||
272 | out: | ||
273 | mutex_unlock(&table->mutex); | ||
274 | return page ? lowmem_page_address(page) + offset : NULL; | ||
275 | } | ||
276 | |||
277 | int mlx4_table_get_range(struct mlx4_dev *dev, struct mlx4_icm_table *table, | ||
278 | int start, int end) | ||
279 | { | ||
280 | int inc = MLX4_TABLE_CHUNK_SIZE / table->obj_size; | ||
281 | int i, err; | ||
282 | |||
283 | for (i = start; i <= end; i += inc) { | ||
284 | err = mlx4_table_get(dev, table, i); | ||
285 | if (err) | ||
286 | goto fail; | ||
287 | } | ||
288 | |||
289 | return 0; | ||
290 | |||
291 | fail: | ||
292 | while (i > start) { | ||
293 | i -= inc; | ||
294 | mlx4_table_put(dev, table, i); | ||
295 | } | ||
296 | |||
297 | return err; | ||
298 | } | ||
299 | |||
300 | void mlx4_table_put_range(struct mlx4_dev *dev, struct mlx4_icm_table *table, | ||
301 | int start, int end) | ||
302 | { | ||
303 | int i; | ||
304 | |||
305 | for (i = start; i <= end; i += MLX4_TABLE_CHUNK_SIZE / table->obj_size) | ||
306 | mlx4_table_put(dev, table, i); | ||
307 | } | ||
308 | |||
309 | int mlx4_init_icm_table(struct mlx4_dev *dev, struct mlx4_icm_table *table, | ||
310 | u64 virt, int obj_size, int nobj, int reserved, | ||
311 | int use_lowmem) | ||
312 | { | ||
313 | int obj_per_chunk; | ||
314 | int num_icm; | ||
315 | unsigned chunk_size; | ||
316 | int i; | ||
317 | |||
318 | obj_per_chunk = MLX4_TABLE_CHUNK_SIZE / obj_size; | ||
319 | num_icm = (nobj + obj_per_chunk - 1) / obj_per_chunk; | ||
320 | |||
321 | table->icm = kcalloc(num_icm, sizeof *table->icm, GFP_KERNEL); | ||
322 | if (!table->icm) | ||
323 | return -ENOMEM; | ||
324 | table->virt = virt; | ||
325 | table->num_icm = num_icm; | ||
326 | table->num_obj = nobj; | ||
327 | table->obj_size = obj_size; | ||
328 | table->lowmem = use_lowmem; | ||
329 | mutex_init(&table->mutex); | ||
330 | |||
331 | for (i = 0; i * MLX4_TABLE_CHUNK_SIZE < reserved * obj_size; ++i) { | ||
332 | chunk_size = MLX4_TABLE_CHUNK_SIZE; | ||
333 | if ((i + 1) * MLX4_TABLE_CHUNK_SIZE > nobj * obj_size) | ||
334 | chunk_size = PAGE_ALIGN(nobj * obj_size - i * MLX4_TABLE_CHUNK_SIZE); | ||
335 | |||
336 | table->icm[i] = mlx4_alloc_icm(dev, chunk_size >> PAGE_SHIFT, | ||
337 | (use_lowmem ? GFP_KERNEL : GFP_HIGHUSER) | | ||
338 | __GFP_NOWARN); | ||
339 | if (!table->icm[i]) | ||
340 | goto err; | ||
341 | if (mlx4_MAP_ICM(dev, table->icm[i], virt + i * MLX4_TABLE_CHUNK_SIZE)) { | ||
342 | mlx4_free_icm(dev, table->icm[i]); | ||
343 | table->icm[i] = NULL; | ||
344 | goto err; | ||
345 | } | ||
346 | |||
347 | /* | ||
348 | * Add a reference to this ICM chunk so that it never | ||
349 | * gets freed (since it contains reserved firmware objects). | ||
350 | */ | ||
351 | ++table->icm[i]->refcount; | ||
352 | } | ||
353 | |||
354 | return 0; | ||
355 | |||
356 | err: | ||
357 | for (i = 0; i < num_icm; ++i) | ||
358 | if (table->icm[i]) { | ||
359 | mlx4_UNMAP_ICM(dev, virt + i * MLX4_TABLE_CHUNK_SIZE, | ||
360 | MLX4_TABLE_CHUNK_SIZE / MLX4_ICM_PAGE_SIZE); | ||
361 | mlx4_free_icm(dev, table->icm[i]); | ||
362 | } | ||
363 | |||
364 | return -ENOMEM; | ||
365 | } | ||
366 | |||
367 | void mlx4_cleanup_icm_table(struct mlx4_dev *dev, struct mlx4_icm_table *table) | ||
368 | { | ||
369 | int i; | ||
370 | |||
371 | for (i = 0; i < table->num_icm; ++i) | ||
372 | if (table->icm[i]) { | ||
373 | mlx4_UNMAP_ICM(dev, table->virt + i * MLX4_TABLE_CHUNK_SIZE, | ||
374 | MLX4_TABLE_CHUNK_SIZE / MLX4_ICM_PAGE_SIZE); | ||
375 | mlx4_free_icm(dev, table->icm[i]); | ||
376 | } | ||
377 | |||
378 | kfree(table->icm); | ||
379 | } | ||