diff options
Diffstat (limited to 'drivers/scsi/cxgb3i/cxgb3i_ddp.c')
-rw-r--r-- | drivers/scsi/cxgb3i/cxgb3i_ddp.c | 770 |
1 files changed, 770 insertions, 0 deletions
diff --git a/drivers/scsi/cxgb3i/cxgb3i_ddp.c b/drivers/scsi/cxgb3i/cxgb3i_ddp.c new file mode 100644 index 000000000000..1a41f04264f7 --- /dev/null +++ b/drivers/scsi/cxgb3i/cxgb3i_ddp.c | |||
@@ -0,0 +1,770 @@ | |||
1 | /* | ||
2 | * cxgb3i_ddp.c: Chelsio S3xx iSCSI DDP Manager. | ||
3 | * | ||
4 | * Copyright (c) 2008 Chelsio Communications, Inc. | ||
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 as published by | ||
8 | * the Free Software Foundation. | ||
9 | * | ||
10 | * Written by: Karen Xie (kxie@chelsio.com) | ||
11 | */ | ||
12 | |||
13 | #include <linux/skbuff.h> | ||
14 | |||
15 | /* from cxgb3 LLD */ | ||
16 | #include "common.h" | ||
17 | #include "t3_cpl.h" | ||
18 | #include "t3cdev.h" | ||
19 | #include "cxgb3_ctl_defs.h" | ||
20 | #include "cxgb3_offload.h" | ||
21 | #include "firmware_exports.h" | ||
22 | |||
23 | #include "cxgb3i_ddp.h" | ||
24 | |||
25 | #define DRV_MODULE_NAME "cxgb3i_ddp" | ||
26 | #define DRV_MODULE_VERSION "1.0.0" | ||
27 | #define DRV_MODULE_RELDATE "Dec. 1, 2008" | ||
28 | |||
29 | static char version[] = | ||
30 | "Chelsio S3xx iSCSI DDP " DRV_MODULE_NAME | ||
31 | " v" DRV_MODULE_VERSION " (" DRV_MODULE_RELDATE ")\n"; | ||
32 | |||
33 | MODULE_AUTHOR("Karen Xie <kxie@chelsio.com>"); | ||
34 | MODULE_DESCRIPTION("cxgb3i ddp pagepod manager"); | ||
35 | MODULE_LICENSE("GPL"); | ||
36 | MODULE_VERSION(DRV_MODULE_VERSION); | ||
37 | |||
38 | #define ddp_log_error(fmt...) printk(KERN_ERR "cxgb3i_ddp: ERR! " fmt) | ||
39 | #define ddp_log_warn(fmt...) printk(KERN_WARNING "cxgb3i_ddp: WARN! " fmt) | ||
40 | #define ddp_log_info(fmt...) printk(KERN_INFO "cxgb3i_ddp: " fmt) | ||
41 | |||
42 | #ifdef __DEBUG_CXGB3I_DDP__ | ||
43 | #define ddp_log_debug(fmt, args...) \ | ||
44 | printk(KERN_INFO "cxgb3i_ddp: %s - " fmt, __func__ , ## args) | ||
45 | #else | ||
46 | #define ddp_log_debug(fmt...) | ||
47 | #endif | ||
48 | |||
49 | /* | ||
50 | * iSCSI Direct Data Placement | ||
51 | * | ||
52 | * T3 h/w can directly place the iSCSI Data-In or Data-Out PDU's payload into | ||
53 | * pre-posted final destination host-memory buffers based on the Initiator | ||
54 | * Task Tag (ITT) in Data-In or Target Task Tag (TTT) in Data-Out PDUs. | ||
55 | * | ||
56 | * The host memory address is programmed into h/w in the format of pagepod | ||
57 | * entries. | ||
58 | * The location of the pagepod entry is encoded into ddp tag which is used or | ||
59 | * is the base for ITT/TTT. | ||
60 | */ | ||
61 | |||
62 | #define DDP_PGIDX_MAX 4 | ||
63 | #define DDP_THRESHOLD 2048 | ||
64 | static unsigned char ddp_page_order[DDP_PGIDX_MAX] = {0, 1, 2, 4}; | ||
65 | static unsigned char ddp_page_shift[DDP_PGIDX_MAX] = {12, 13, 14, 16}; | ||
66 | static unsigned char page_idx = DDP_PGIDX_MAX; | ||
67 | |||
68 | static LIST_HEAD(cxgb3i_ddp_list); | ||
69 | static DEFINE_RWLOCK(cxgb3i_ddp_rwlock); | ||
70 | |||
71 | /* | ||
72 | * functions to program the pagepod in h/w | ||
73 | */ | ||
74 | static inline void ulp_mem_io_set_hdr(struct sk_buff *skb, unsigned int addr) | ||
75 | { | ||
76 | struct ulp_mem_io *req = (struct ulp_mem_io *)skb->head; | ||
77 | |||
78 | req->wr.wr_lo = 0; | ||
79 | req->wr.wr_hi = htonl(V_WR_OP(FW_WROPCODE_BYPASS)); | ||
80 | req->cmd_lock_addr = htonl(V_ULP_MEMIO_ADDR(addr >> 5) | | ||
81 | V_ULPTX_CMD(ULP_MEM_WRITE)); | ||
82 | req->len = htonl(V_ULP_MEMIO_DATA_LEN(PPOD_SIZE >> 5) | | ||
83 | V_ULPTX_NFLITS((PPOD_SIZE >> 3) + 1)); | ||
84 | } | ||
85 | |||
86 | static int set_ddp_map(struct cxgb3i_ddp_info *ddp, struct pagepod_hdr *hdr, | ||
87 | unsigned int idx, unsigned int npods, | ||
88 | struct cxgb3i_gather_list *gl) | ||
89 | { | ||
90 | unsigned int pm_addr = (idx << PPOD_SIZE_SHIFT) + ddp->llimit; | ||
91 | int i; | ||
92 | |||
93 | for (i = 0; i < npods; i++, idx++, pm_addr += PPOD_SIZE) { | ||
94 | struct sk_buff *skb = ddp->gl_skb[idx]; | ||
95 | struct pagepod *ppod; | ||
96 | int j, pidx; | ||
97 | |||
98 | /* hold on to the skb until we clear the ddp mapping */ | ||
99 | skb_get(skb); | ||
100 | |||
101 | ulp_mem_io_set_hdr(skb, pm_addr); | ||
102 | ppod = (struct pagepod *) | ||
103 | (skb->head + sizeof(struct ulp_mem_io)); | ||
104 | memcpy(&(ppod->hdr), hdr, sizeof(struct pagepod)); | ||
105 | for (pidx = 4 * i, j = 0; j < 5; ++j, ++pidx) | ||
106 | ppod->addr[j] = pidx < gl->nelem ? | ||
107 | cpu_to_be64(gl->phys_addr[pidx]) : 0UL; | ||
108 | |||
109 | skb->priority = CPL_PRIORITY_CONTROL; | ||
110 | cxgb3_ofld_send(ddp->tdev, skb); | ||
111 | } | ||
112 | return 0; | ||
113 | } | ||
114 | |||
115 | static int clear_ddp_map(struct cxgb3i_ddp_info *ddp, unsigned int idx, | ||
116 | unsigned int npods) | ||
117 | { | ||
118 | unsigned int pm_addr = (idx << PPOD_SIZE_SHIFT) + ddp->llimit; | ||
119 | int i; | ||
120 | |||
121 | for (i = 0; i < npods; i++, idx++, pm_addr += PPOD_SIZE) { | ||
122 | struct sk_buff *skb = ddp->gl_skb[idx]; | ||
123 | |||
124 | ddp->gl_skb[idx] = NULL; | ||
125 | memset((skb->head + sizeof(struct ulp_mem_io)), 0, PPOD_SIZE); | ||
126 | ulp_mem_io_set_hdr(skb, pm_addr); | ||
127 | skb->priority = CPL_PRIORITY_CONTROL; | ||
128 | cxgb3_ofld_send(ddp->tdev, skb); | ||
129 | } | ||
130 | return 0; | ||
131 | } | ||
132 | |||
133 | static inline int ddp_find_unused_entries(struct cxgb3i_ddp_info *ddp, | ||
134 | int start, int max, int count, | ||
135 | struct cxgb3i_gather_list *gl) | ||
136 | { | ||
137 | unsigned int i, j; | ||
138 | |||
139 | spin_lock(&ddp->map_lock); | ||
140 | for (i = start; i <= max;) { | ||
141 | for (j = 0; j < count; j++) { | ||
142 | if (ddp->gl_map[i + j]) | ||
143 | break; | ||
144 | } | ||
145 | if (j == count) { | ||
146 | for (j = 0; j < count; j++) | ||
147 | ddp->gl_map[i + j] = gl; | ||
148 | spin_unlock(&ddp->map_lock); | ||
149 | return i; | ||
150 | } | ||
151 | i += j + 1; | ||
152 | } | ||
153 | spin_unlock(&ddp->map_lock); | ||
154 | return -EBUSY; | ||
155 | } | ||
156 | |||
157 | static inline void ddp_unmark_entries(struct cxgb3i_ddp_info *ddp, | ||
158 | int start, int count) | ||
159 | { | ||
160 | spin_lock(&ddp->map_lock); | ||
161 | memset(&ddp->gl_map[start], 0, | ||
162 | count * sizeof(struct cxgb3i_gather_list *)); | ||
163 | spin_unlock(&ddp->map_lock); | ||
164 | } | ||
165 | |||
166 | static inline void ddp_free_gl_skb(struct cxgb3i_ddp_info *ddp, | ||
167 | int idx, int count) | ||
168 | { | ||
169 | int i; | ||
170 | |||
171 | for (i = 0; i < count; i++, idx++) | ||
172 | if (ddp->gl_skb[idx]) { | ||
173 | kfree_skb(ddp->gl_skb[idx]); | ||
174 | ddp->gl_skb[idx] = NULL; | ||
175 | } | ||
176 | } | ||
177 | |||
178 | static inline int ddp_alloc_gl_skb(struct cxgb3i_ddp_info *ddp, int idx, | ||
179 | int count, gfp_t gfp) | ||
180 | { | ||
181 | int i; | ||
182 | |||
183 | for (i = 0; i < count; i++) { | ||
184 | struct sk_buff *skb = alloc_skb(sizeof(struct ulp_mem_io) + | ||
185 | PPOD_SIZE, gfp); | ||
186 | if (skb) { | ||
187 | ddp->gl_skb[idx + i] = skb; | ||
188 | skb_put(skb, sizeof(struct ulp_mem_io) + PPOD_SIZE); | ||
189 | } else { | ||
190 | ddp_free_gl_skb(ddp, idx, i); | ||
191 | return -ENOMEM; | ||
192 | } | ||
193 | } | ||
194 | return 0; | ||
195 | } | ||
196 | |||
197 | /** | ||
198 | * cxgb3i_ddp_find_page_index - return ddp page index for a given page size. | ||
199 | * @pgsz: page size | ||
200 | * return the ddp page index, if no match is found return DDP_PGIDX_MAX. | ||
201 | */ | ||
202 | int cxgb3i_ddp_find_page_index(unsigned long pgsz) | ||
203 | { | ||
204 | int i; | ||
205 | |||
206 | for (i = 0; i < DDP_PGIDX_MAX; i++) { | ||
207 | if (pgsz == (1UL << ddp_page_shift[i])) | ||
208 | return i; | ||
209 | } | ||
210 | ddp_log_debug("ddp page size 0x%lx not supported.\n", pgsz); | ||
211 | return DDP_PGIDX_MAX; | ||
212 | } | ||
213 | EXPORT_SYMBOL_GPL(cxgb3i_ddp_find_page_index); | ||
214 | |||
215 | static inline void ddp_gl_unmap(struct pci_dev *pdev, | ||
216 | struct cxgb3i_gather_list *gl) | ||
217 | { | ||
218 | int i; | ||
219 | |||
220 | for (i = 0; i < gl->nelem; i++) | ||
221 | pci_unmap_page(pdev, gl->phys_addr[i], PAGE_SIZE, | ||
222 | PCI_DMA_FROMDEVICE); | ||
223 | } | ||
224 | |||
225 | static inline int ddp_gl_map(struct pci_dev *pdev, | ||
226 | struct cxgb3i_gather_list *gl) | ||
227 | { | ||
228 | int i; | ||
229 | |||
230 | for (i = 0; i < gl->nelem; i++) { | ||
231 | gl->phys_addr[i] = pci_map_page(pdev, gl->pages[i], 0, | ||
232 | PAGE_SIZE, | ||
233 | PCI_DMA_FROMDEVICE); | ||
234 | if (unlikely(pci_dma_mapping_error(pdev, gl->phys_addr[i]))) | ||
235 | goto unmap; | ||
236 | } | ||
237 | |||
238 | return i; | ||
239 | |||
240 | unmap: | ||
241 | if (i) { | ||
242 | unsigned int nelem = gl->nelem; | ||
243 | |||
244 | gl->nelem = i; | ||
245 | ddp_gl_unmap(pdev, gl); | ||
246 | gl->nelem = nelem; | ||
247 | } | ||
248 | return -ENOMEM; | ||
249 | } | ||
250 | |||
251 | /** | ||
252 | * cxgb3i_ddp_make_gl - build ddp page buffer list | ||
253 | * @xferlen: total buffer length | ||
254 | * @sgl: page buffer scatter-gather list | ||
255 | * @sgcnt: # of page buffers | ||
256 | * @pdev: pci_dev, used for pci map | ||
257 | * @gfp: allocation mode | ||
258 | * | ||
259 | * construct a ddp page buffer list from the scsi scattergather list. | ||
260 | * coalesce buffers as much as possible, and obtain dma addresses for | ||
261 | * each page. | ||
262 | * | ||
263 | * Return the cxgb3i_gather_list constructed from the page buffers if the | ||
264 | * memory can be used for ddp. Return NULL otherwise. | ||
265 | */ | ||
266 | struct cxgb3i_gather_list *cxgb3i_ddp_make_gl(unsigned int xferlen, | ||
267 | struct scatterlist *sgl, | ||
268 | unsigned int sgcnt, | ||
269 | struct pci_dev *pdev, | ||
270 | gfp_t gfp) | ||
271 | { | ||
272 | struct cxgb3i_gather_list *gl; | ||
273 | struct scatterlist *sg = sgl; | ||
274 | struct page *sgpage = sg_page(sg); | ||
275 | unsigned int sglen = sg->length; | ||
276 | unsigned int sgoffset = sg->offset; | ||
277 | unsigned int npages = (xferlen + sgoffset + PAGE_SIZE - 1) >> | ||
278 | PAGE_SHIFT; | ||
279 | int i = 1, j = 0; | ||
280 | |||
281 | if (xferlen < DDP_THRESHOLD) { | ||
282 | ddp_log_debug("xfer %u < threshold %u, no ddp.\n", | ||
283 | xferlen, DDP_THRESHOLD); | ||
284 | return NULL; | ||
285 | } | ||
286 | |||
287 | gl = kzalloc(sizeof(struct cxgb3i_gather_list) + | ||
288 | npages * (sizeof(dma_addr_t) + sizeof(struct page *)), | ||
289 | gfp); | ||
290 | if (!gl) | ||
291 | return NULL; | ||
292 | |||
293 | gl->pages = (struct page **)&gl->phys_addr[npages]; | ||
294 | gl->length = xferlen; | ||
295 | gl->offset = sgoffset; | ||
296 | gl->pages[0] = sgpage; | ||
297 | |||
298 | sg = sg_next(sg); | ||
299 | while (sg) { | ||
300 | struct page *page = sg_page(sg); | ||
301 | |||
302 | if (sgpage == page && sg->offset == sgoffset + sglen) | ||
303 | sglen += sg->length; | ||
304 | else { | ||
305 | /* make sure the sgl is fit for ddp: | ||
306 | * each has the same page size, and | ||
307 | * all of the middle pages are used completely | ||
308 | */ | ||
309 | if ((j && sgoffset) || | ||
310 | ((i != sgcnt - 1) && | ||
311 | ((sglen + sgoffset) & ~PAGE_MASK))) | ||
312 | goto error_out; | ||
313 | |||
314 | j++; | ||
315 | if (j == gl->nelem || sg->offset) | ||
316 | goto error_out; | ||
317 | gl->pages[j] = page; | ||
318 | sglen = sg->length; | ||
319 | sgoffset = sg->offset; | ||
320 | sgpage = page; | ||
321 | } | ||
322 | i++; | ||
323 | sg = sg_next(sg); | ||
324 | } | ||
325 | gl->nelem = ++j; | ||
326 | |||
327 | if (ddp_gl_map(pdev, gl) < 0) | ||
328 | goto error_out; | ||
329 | |||
330 | return gl; | ||
331 | |||
332 | error_out: | ||
333 | kfree(gl); | ||
334 | return NULL; | ||
335 | } | ||
336 | EXPORT_SYMBOL_GPL(cxgb3i_ddp_make_gl); | ||
337 | |||
338 | /** | ||
339 | * cxgb3i_ddp_release_gl - release a page buffer list | ||
340 | * @gl: a ddp page buffer list | ||
341 | * @pdev: pci_dev used for pci_unmap | ||
342 | * free a ddp page buffer list resulted from cxgb3i_ddp_make_gl(). | ||
343 | */ | ||
344 | void cxgb3i_ddp_release_gl(struct cxgb3i_gather_list *gl, | ||
345 | struct pci_dev *pdev) | ||
346 | { | ||
347 | ddp_gl_unmap(pdev, gl); | ||
348 | kfree(gl); | ||
349 | } | ||
350 | EXPORT_SYMBOL_GPL(cxgb3i_ddp_release_gl); | ||
351 | |||
352 | /** | ||
353 | * cxgb3i_ddp_tag_reserve - set up ddp for a data transfer | ||
354 | * @tdev: t3cdev adapter | ||
355 | * @tid: connection id | ||
356 | * @tformat: tag format | ||
357 | * @tagp: the s/w tag, if ddp setup is successful, it will be updated with | ||
358 | * ddp/hw tag | ||
359 | * @gl: the page momory list | ||
360 | * @gfp: allocation mode | ||
361 | * | ||
362 | * ddp setup for a given page buffer list and construct the ddp tag. | ||
363 | * return 0 if success, < 0 otherwise. | ||
364 | */ | ||
365 | int cxgb3i_ddp_tag_reserve(struct t3cdev *tdev, unsigned int tid, | ||
366 | struct cxgb3i_tag_format *tformat, u32 *tagp, | ||
367 | struct cxgb3i_gather_list *gl, gfp_t gfp) | ||
368 | { | ||
369 | struct cxgb3i_ddp_info *ddp = tdev->ulp_iscsi; | ||
370 | struct pagepod_hdr hdr; | ||
371 | unsigned int npods; | ||
372 | int idx = -1, idx_max; | ||
373 | int err = -ENOMEM; | ||
374 | u32 sw_tag = *tagp; | ||
375 | u32 tag; | ||
376 | |||
377 | if (page_idx >= DDP_PGIDX_MAX || !ddp || !gl || !gl->nelem || | ||
378 | gl->length < DDP_THRESHOLD) { | ||
379 | ddp_log_debug("pgidx %u, xfer %u/%u, NO ddp.\n", | ||
380 | page_idx, gl->length, DDP_THRESHOLD); | ||
381 | return -EINVAL; | ||
382 | } | ||
383 | |||
384 | npods = (gl->nelem + PPOD_PAGES_MAX - 1) >> PPOD_PAGES_SHIFT; | ||
385 | idx_max = ddp->nppods - npods + 1; | ||
386 | |||
387 | if (ddp->idx_last == ddp->nppods) | ||
388 | idx = ddp_find_unused_entries(ddp, 0, idx_max, npods, gl); | ||
389 | else { | ||
390 | idx = ddp_find_unused_entries(ddp, ddp->idx_last + 1, | ||
391 | idx_max, npods, gl); | ||
392 | if (idx < 0 && ddp->idx_last >= npods) | ||
393 | idx = ddp_find_unused_entries(ddp, 0, | ||
394 | ddp->idx_last - npods + 1, | ||
395 | npods, gl); | ||
396 | } | ||
397 | if (idx < 0) { | ||
398 | ddp_log_debug("xferlen %u, gl %u, npods %u NO DDP.\n", | ||
399 | gl->length, gl->nelem, npods); | ||
400 | return idx; | ||
401 | } | ||
402 | |||
403 | err = ddp_alloc_gl_skb(ddp, idx, npods, gfp); | ||
404 | if (err < 0) | ||
405 | goto unmark_entries; | ||
406 | |||
407 | tag = cxgb3i_ddp_tag_base(tformat, sw_tag); | ||
408 | tag |= idx << PPOD_IDX_SHIFT; | ||
409 | |||
410 | hdr.rsvd = 0; | ||
411 | hdr.vld_tid = htonl(F_PPOD_VALID | V_PPOD_TID(tid)); | ||
412 | hdr.pgsz_tag_clr = htonl(tag & ddp->rsvd_tag_mask); | ||
413 | hdr.maxoffset = htonl(gl->length); | ||
414 | hdr.pgoffset = htonl(gl->offset); | ||
415 | |||
416 | err = set_ddp_map(ddp, &hdr, idx, npods, gl); | ||
417 | if (err < 0) | ||
418 | goto free_gl_skb; | ||
419 | |||
420 | ddp->idx_last = idx; | ||
421 | ddp_log_debug("xfer %u, gl %u,%u, tid 0x%x, 0x%x -> 0x%x(%u,%u).\n", | ||
422 | gl->length, gl->nelem, gl->offset, tid, sw_tag, tag, | ||
423 | idx, npods); | ||
424 | *tagp = tag; | ||
425 | return 0; | ||
426 | |||
427 | free_gl_skb: | ||
428 | ddp_free_gl_skb(ddp, idx, npods); | ||
429 | unmark_entries: | ||
430 | ddp_unmark_entries(ddp, idx, npods); | ||
431 | return err; | ||
432 | } | ||
433 | EXPORT_SYMBOL_GPL(cxgb3i_ddp_tag_reserve); | ||
434 | |||
435 | /** | ||
436 | * cxgb3i_ddp_tag_release - release a ddp tag | ||
437 | * @tdev: t3cdev adapter | ||
438 | * @tag: ddp tag | ||
439 | * ddp cleanup for a given ddp tag and release all the resources held | ||
440 | */ | ||
441 | void cxgb3i_ddp_tag_release(struct t3cdev *tdev, u32 tag) | ||
442 | { | ||
443 | struct cxgb3i_ddp_info *ddp = tdev->ulp_iscsi; | ||
444 | u32 idx; | ||
445 | |||
446 | if (!ddp) { | ||
447 | ddp_log_error("release ddp tag 0x%x, ddp NULL.\n", tag); | ||
448 | return; | ||
449 | } | ||
450 | |||
451 | idx = (tag >> PPOD_IDX_SHIFT) & ddp->idx_mask; | ||
452 | if (idx < ddp->nppods) { | ||
453 | struct cxgb3i_gather_list *gl = ddp->gl_map[idx]; | ||
454 | unsigned int npods; | ||
455 | |||
456 | if (!gl) { | ||
457 | ddp_log_error("release ddp 0x%x, idx 0x%x, gl NULL.\n", | ||
458 | tag, idx); | ||
459 | return; | ||
460 | } | ||
461 | npods = (gl->nelem + PPOD_PAGES_MAX - 1) >> PPOD_PAGES_SHIFT; | ||
462 | ddp_log_debug("ddp tag 0x%x, release idx 0x%x, npods %u.\n", | ||
463 | tag, idx, npods); | ||
464 | clear_ddp_map(ddp, idx, npods); | ||
465 | ddp_unmark_entries(ddp, idx, npods); | ||
466 | cxgb3i_ddp_release_gl(gl, ddp->pdev); | ||
467 | } else | ||
468 | ddp_log_error("ddp tag 0x%x, idx 0x%x > max 0x%x.\n", | ||
469 | tag, idx, ddp->nppods); | ||
470 | } | ||
471 | EXPORT_SYMBOL_GPL(cxgb3i_ddp_tag_release); | ||
472 | |||
473 | static int setup_conn_pgidx(struct t3cdev *tdev, unsigned int tid, int pg_idx, | ||
474 | int reply) | ||
475 | { | ||
476 | struct sk_buff *skb = alloc_skb(sizeof(struct cpl_set_tcb_field), | ||
477 | GFP_KERNEL); | ||
478 | struct cpl_set_tcb_field *req; | ||
479 | u64 val = pg_idx < DDP_PGIDX_MAX ? pg_idx : 0; | ||
480 | |||
481 | if (!skb) | ||
482 | return -ENOMEM; | ||
483 | |||
484 | /* set up ulp submode and page size */ | ||
485 | req = (struct cpl_set_tcb_field *)skb_put(skb, sizeof(*req)); | ||
486 | req->wr.wr_hi = htonl(V_WR_OP(FW_WROPCODE_FORWARD)); | ||
487 | OPCODE_TID(req) = htonl(MK_OPCODE_TID(CPL_SET_TCB_FIELD, tid)); | ||
488 | req->reply = V_NO_REPLY(reply ? 0 : 1); | ||
489 | req->cpu_idx = 0; | ||
490 | req->word = htons(31); | ||
491 | req->mask = cpu_to_be64(0xF0000000); | ||
492 | req->val = cpu_to_be64(val << 28); | ||
493 | skb->priority = CPL_PRIORITY_CONTROL; | ||
494 | |||
495 | cxgb3_ofld_send(tdev, skb); | ||
496 | return 0; | ||
497 | } | ||
498 | |||
499 | /** | ||
500 | * cxgb3i_setup_conn_host_pagesize - setup the conn.'s ddp page size | ||
501 | * @tdev: t3cdev adapter | ||
502 | * @tid: connection id | ||
503 | * @reply: request reply from h/w | ||
504 | * set up the ddp page size based on the host PAGE_SIZE for a connection | ||
505 | * identified by tid | ||
506 | */ | ||
507 | int cxgb3i_setup_conn_host_pagesize(struct t3cdev *tdev, unsigned int tid, | ||
508 | int reply) | ||
509 | { | ||
510 | return setup_conn_pgidx(tdev, tid, page_idx, reply); | ||
511 | } | ||
512 | EXPORT_SYMBOL_GPL(cxgb3i_setup_conn_host_pagesize); | ||
513 | |||
514 | /** | ||
515 | * cxgb3i_setup_conn_pagesize - setup the conn.'s ddp page size | ||
516 | * @tdev: t3cdev adapter | ||
517 | * @tid: connection id | ||
518 | * @reply: request reply from h/w | ||
519 | * @pgsz: ddp page size | ||
520 | * set up the ddp page size for a connection identified by tid | ||
521 | */ | ||
522 | int cxgb3i_setup_conn_pagesize(struct t3cdev *tdev, unsigned int tid, | ||
523 | int reply, unsigned long pgsz) | ||
524 | { | ||
525 | int pgidx = cxgb3i_ddp_find_page_index(pgsz); | ||
526 | |||
527 | return setup_conn_pgidx(tdev, tid, pgidx, reply); | ||
528 | } | ||
529 | EXPORT_SYMBOL_GPL(cxgb3i_setup_conn_pagesize); | ||
530 | |||
531 | /** | ||
532 | * cxgb3i_setup_conn_digest - setup conn. digest setting | ||
533 | * @tdev: t3cdev adapter | ||
534 | * @tid: connection id | ||
535 | * @hcrc: header digest enabled | ||
536 | * @dcrc: data digest enabled | ||
537 | * @reply: request reply from h/w | ||
538 | * set up the iscsi digest settings for a connection identified by tid | ||
539 | */ | ||
540 | int cxgb3i_setup_conn_digest(struct t3cdev *tdev, unsigned int tid, | ||
541 | int hcrc, int dcrc, int reply) | ||
542 | { | ||
543 | struct sk_buff *skb = alloc_skb(sizeof(struct cpl_set_tcb_field), | ||
544 | GFP_KERNEL); | ||
545 | struct cpl_set_tcb_field *req; | ||
546 | u64 val = (hcrc ? 1 : 0) | (dcrc ? 2 : 0); | ||
547 | |||
548 | if (!skb) | ||
549 | return -ENOMEM; | ||
550 | |||
551 | /* set up ulp submode and page size */ | ||
552 | req = (struct cpl_set_tcb_field *)skb_put(skb, sizeof(*req)); | ||
553 | req->wr.wr_hi = htonl(V_WR_OP(FW_WROPCODE_FORWARD)); | ||
554 | OPCODE_TID(req) = htonl(MK_OPCODE_TID(CPL_SET_TCB_FIELD, tid)); | ||
555 | req->reply = V_NO_REPLY(reply ? 0 : 1); | ||
556 | req->cpu_idx = 0; | ||
557 | req->word = htons(31); | ||
558 | req->mask = cpu_to_be64(0x0F000000); | ||
559 | req->val = cpu_to_be64(val << 24); | ||
560 | skb->priority = CPL_PRIORITY_CONTROL; | ||
561 | |||
562 | cxgb3_ofld_send(tdev, skb); | ||
563 | return 0; | ||
564 | } | ||
565 | EXPORT_SYMBOL_GPL(cxgb3i_setup_conn_digest); | ||
566 | |||
567 | static int ddp_init(struct t3cdev *tdev) | ||
568 | { | ||
569 | struct cxgb3i_ddp_info *ddp; | ||
570 | struct ulp_iscsi_info uinfo; | ||
571 | unsigned int ppmax, bits; | ||
572 | int i, err; | ||
573 | static int vers_printed; | ||
574 | |||
575 | if (!vers_printed) { | ||
576 | printk(KERN_INFO "%s", version); | ||
577 | vers_printed = 1; | ||
578 | } | ||
579 | |||
580 | err = tdev->ctl(tdev, ULP_ISCSI_GET_PARAMS, &uinfo); | ||
581 | if (err < 0) { | ||
582 | ddp_log_error("%s, failed to get iscsi param err=%d.\n", | ||
583 | tdev->name, err); | ||
584 | return err; | ||
585 | } | ||
586 | |||
587 | ppmax = (uinfo.ulimit - uinfo.llimit + 1) >> PPOD_SIZE_SHIFT; | ||
588 | bits = __ilog2_u32(ppmax) + 1; | ||
589 | if (bits > PPOD_IDX_MAX_SIZE) | ||
590 | bits = PPOD_IDX_MAX_SIZE; | ||
591 | ppmax = (1 << (bits - 1)) - 1; | ||
592 | |||
593 | ddp = cxgb3i_alloc_big_mem(sizeof(struct cxgb3i_ddp_info) + | ||
594 | ppmax * | ||
595 | (sizeof(struct cxgb3i_gather_list *) + | ||
596 | sizeof(struct sk_buff *)), | ||
597 | GFP_KERNEL); | ||
598 | if (!ddp) { | ||
599 | ddp_log_warn("%s unable to alloc ddp 0x%d, ddp disabled.\n", | ||
600 | tdev->name, ppmax); | ||
601 | return 0; | ||
602 | } | ||
603 | ddp->gl_map = (struct cxgb3i_gather_list **)(ddp + 1); | ||
604 | ddp->gl_skb = (struct sk_buff **)(((char *)ddp->gl_map) + | ||
605 | ppmax * | ||
606 | sizeof(struct cxgb3i_gather_list *)); | ||
607 | spin_lock_init(&ddp->map_lock); | ||
608 | |||
609 | ddp->tdev = tdev; | ||
610 | ddp->pdev = uinfo.pdev; | ||
611 | ddp->max_txsz = min_t(unsigned int, uinfo.max_txsz, ULP2_MAX_PKT_SIZE); | ||
612 | ddp->max_rxsz = min_t(unsigned int, uinfo.max_rxsz, ULP2_MAX_PKT_SIZE); | ||
613 | ddp->llimit = uinfo.llimit; | ||
614 | ddp->ulimit = uinfo.ulimit; | ||
615 | ddp->nppods = ppmax; | ||
616 | ddp->idx_last = ppmax; | ||
617 | ddp->idx_bits = bits; | ||
618 | ddp->idx_mask = (1 << bits) - 1; | ||
619 | ddp->rsvd_tag_mask = (1 << (bits + PPOD_IDX_SHIFT)) - 1; | ||
620 | |||
621 | uinfo.tagmask = ddp->idx_mask << PPOD_IDX_SHIFT; | ||
622 | for (i = 0; i < DDP_PGIDX_MAX; i++) | ||
623 | uinfo.pgsz_factor[i] = ddp_page_order[i]; | ||
624 | uinfo.ulimit = uinfo.llimit + (ppmax << PPOD_SIZE_SHIFT); | ||
625 | |||
626 | err = tdev->ctl(tdev, ULP_ISCSI_SET_PARAMS, &uinfo); | ||
627 | if (err < 0) { | ||
628 | ddp_log_warn("%s unable to set iscsi param err=%d, " | ||
629 | "ddp disabled.\n", tdev->name, err); | ||
630 | goto free_ddp_map; | ||
631 | } | ||
632 | |||
633 | tdev->ulp_iscsi = ddp; | ||
634 | |||
635 | /* add to the list */ | ||
636 | write_lock(&cxgb3i_ddp_rwlock); | ||
637 | list_add_tail(&ddp->list, &cxgb3i_ddp_list); | ||
638 | write_unlock(&cxgb3i_ddp_rwlock); | ||
639 | |||
640 | ddp_log_info("nppods %u (0x%x ~ 0x%x), bits %u, mask 0x%x,0x%x " | ||
641 | "pkt %u,%u.\n", | ||
642 | ppmax, ddp->llimit, ddp->ulimit, ddp->idx_bits, | ||
643 | ddp->idx_mask, ddp->rsvd_tag_mask, | ||
644 | ddp->max_txsz, ddp->max_rxsz); | ||
645 | return 0; | ||
646 | |||
647 | free_ddp_map: | ||
648 | cxgb3i_free_big_mem(ddp); | ||
649 | return err; | ||
650 | } | ||
651 | |||
652 | /** | ||
653 | * cxgb3i_adapter_ddp_init - initialize the adapter's ddp resource | ||
654 | * @tdev: t3cdev adapter | ||
655 | * @tformat: tag format | ||
656 | * @txsz: max tx pkt size, filled in by this func. | ||
657 | * @rxsz: max rx pkt size, filled in by this func. | ||
658 | * initialize the ddp pagepod manager for a given adapter if needed and | ||
659 | * setup the tag format for a given iscsi entity | ||
660 | */ | ||
661 | int cxgb3i_adapter_ddp_init(struct t3cdev *tdev, | ||
662 | struct cxgb3i_tag_format *tformat, | ||
663 | unsigned int *txsz, unsigned int *rxsz) | ||
664 | { | ||
665 | struct cxgb3i_ddp_info *ddp; | ||
666 | unsigned char idx_bits; | ||
667 | |||
668 | if (!tformat) | ||
669 | return -EINVAL; | ||
670 | |||
671 | if (!tdev->ulp_iscsi) { | ||
672 | int err = ddp_init(tdev); | ||
673 | if (err < 0) | ||
674 | return err; | ||
675 | } | ||
676 | ddp = (struct cxgb3i_ddp_info *)tdev->ulp_iscsi; | ||
677 | |||
678 | idx_bits = 32 - tformat->sw_bits; | ||
679 | tformat->rsvd_bits = ddp->idx_bits; | ||
680 | tformat->rsvd_shift = PPOD_IDX_SHIFT; | ||
681 | tformat->rsvd_mask = (1 << tformat->rsvd_bits) - 1; | ||
682 | |||
683 | ddp_log_info("tag format: sw %u, rsvd %u,%u, mask 0x%x.\n", | ||
684 | tformat->sw_bits, tformat->rsvd_bits, | ||
685 | tformat->rsvd_shift, tformat->rsvd_mask); | ||
686 | |||
687 | *txsz = ddp->max_txsz; | ||
688 | *rxsz = ddp->max_rxsz; | ||
689 | ddp_log_info("ddp max pkt size: %u, %u.\n", | ||
690 | ddp->max_txsz, ddp->max_rxsz); | ||
691 | return 0; | ||
692 | } | ||
693 | EXPORT_SYMBOL_GPL(cxgb3i_adapter_ddp_init); | ||
694 | |||
695 | static void ddp_release(struct cxgb3i_ddp_info *ddp) | ||
696 | { | ||
697 | int i = 0; | ||
698 | struct t3cdev *tdev = ddp->tdev; | ||
699 | |||
700 | tdev->ulp_iscsi = NULL; | ||
701 | while (i < ddp->nppods) { | ||
702 | struct cxgb3i_gather_list *gl = ddp->gl_map[i]; | ||
703 | if (gl) { | ||
704 | int npods = (gl->nelem + PPOD_PAGES_MAX - 1) | ||
705 | >> PPOD_PAGES_SHIFT; | ||
706 | |||
707 | kfree(gl); | ||
708 | ddp_free_gl_skb(ddp, i, npods); | ||
709 | } else | ||
710 | i++; | ||
711 | } | ||
712 | cxgb3i_free_big_mem(ddp); | ||
713 | } | ||
714 | |||
715 | /** | ||
716 | * cxgb3i_adapter_ddp_cleanup - release the adapter's ddp resource | ||
717 | * @tdev: t3cdev adapter | ||
718 | * release all the resource held by the ddp pagepod manager for a given | ||
719 | * adapter if needed | ||
720 | */ | ||
721 | void cxgb3i_adapter_ddp_cleanup(struct t3cdev *tdev) | ||
722 | { | ||
723 | struct cxgb3i_ddp_info *ddp; | ||
724 | |||
725 | /* remove from the list */ | ||
726 | write_lock(&cxgb3i_ddp_rwlock); | ||
727 | list_for_each_entry(ddp, &cxgb3i_ddp_list, list) { | ||
728 | if (ddp->tdev == tdev) { | ||
729 | list_del(&ddp->list); | ||
730 | break; | ||
731 | } | ||
732 | } | ||
733 | write_unlock(&cxgb3i_ddp_rwlock); | ||
734 | |||
735 | if (ddp) | ||
736 | ddp_release(ddp); | ||
737 | } | ||
738 | EXPORT_SYMBOL_GPL(cxgb3i_adapter_ddp_cleanup); | ||
739 | |||
740 | /** | ||
741 | * cxgb3i_ddp_init_module - module init entry point | ||
742 | * initialize any driver wide global data structures | ||
743 | */ | ||
744 | static int __init cxgb3i_ddp_init_module(void) | ||
745 | { | ||
746 | page_idx = cxgb3i_ddp_find_page_index(PAGE_SIZE); | ||
747 | ddp_log_info("system PAGE_SIZE %lu, ddp idx %u.\n", | ||
748 | PAGE_SIZE, page_idx); | ||
749 | return 0; | ||
750 | } | ||
751 | |||
752 | /** | ||
753 | * cxgb3i_ddp_exit_module - module cleanup/exit entry point | ||
754 | * go through the ddp list and release any resource held. | ||
755 | */ | ||
756 | static void __exit cxgb3i_ddp_exit_module(void) | ||
757 | { | ||
758 | struct cxgb3i_ddp_info *ddp; | ||
759 | |||
760 | /* release all ddp manager if there is any */ | ||
761 | write_lock(&cxgb3i_ddp_rwlock); | ||
762 | list_for_each_entry(ddp, &cxgb3i_ddp_list, list) { | ||
763 | list_del(&ddp->list); | ||
764 | ddp_release(ddp); | ||
765 | } | ||
766 | write_unlock(&cxgb3i_ddp_rwlock); | ||
767 | } | ||
768 | |||
769 | module_init(cxgb3i_ddp_init_module); | ||
770 | module_exit(cxgb3i_ddp_exit_module); | ||