aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/scsi/cxgb3i/cxgb3i_pdu.c
diff options
context:
space:
mode:
authorKaren Xie <kxie@chelsio.com>2008-12-09 17:15:32 -0500
committerJames Bottomley <James.Bottomley@HansenPartnership.com>2008-12-30 11:45:33 -0500
commitc3673464ebc004a3d82063cd41b9cf74d1b55db2 (patch)
treeb061ecd04da7dd3ddddad8f39a4922f437493311 /drivers/scsi/cxgb3i/cxgb3i_pdu.c
parentb632ade282895562924d18b8eedd11a825f4b08c (diff)
[SCSI] cxgb3i: Add cxgb3i iSCSI driver.
This patch implements the cxgb3i iscsi connection acceleration for the open-iscsi initiator. The cxgb3i driver offers the iscsi PDU based offload: - digest insertion and verification - payload direct-placement into host memory buffer. Signed-off-by: Karen Xie <kxie@chelsio.com> Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
Diffstat (limited to 'drivers/scsi/cxgb3i/cxgb3i_pdu.c')
-rw-r--r--drivers/scsi/cxgb3i/cxgb3i_pdu.c402
1 files changed, 402 insertions, 0 deletions
diff --git a/drivers/scsi/cxgb3i/cxgb3i_pdu.c b/drivers/scsi/cxgb3i/cxgb3i_pdu.c
new file mode 100644
index 000000000000..ce7ce8c6094c
--- /dev/null
+++ b/drivers/scsi/cxgb3i/cxgb3i_pdu.c
@@ -0,0 +1,402 @@
1/*
2 * cxgb3i_pdu.c: Chelsio S3xx iSCSI driver.
3 *
4 * Copyright (c) 2008 Chelsio Communications, Inc.
5 * Copyright (c) 2008 Mike Christie
6 * Copyright (c) 2008 Red Hat, Inc. All rights reserved.
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation.
11 *
12 * Written by: Karen Xie (kxie@chelsio.com)
13 */
14
15#include <linux/skbuff.h>
16#include <linux/crypto.h>
17#include <scsi/scsi_cmnd.h>
18#include <scsi/scsi_host.h>
19
20#include "cxgb3i.h"
21#include "cxgb3i_pdu.h"
22
23#ifdef __DEBUG_CXGB3I_RX__
24#define cxgb3i_rx_debug cxgb3i_log_debug
25#else
26#define cxgb3i_rx_debug(fmt...)
27#endif
28
29#ifdef __DEBUG_CXGB3I_TX__
30#define cxgb3i_tx_debug cxgb3i_log_debug
31#else
32#define cxgb3i_tx_debug(fmt...)
33#endif
34
35static struct page *pad_page;
36
37/*
38 * pdu receive, interact with libiscsi_tcp
39 */
40static inline int read_pdu_skb(struct iscsi_conn *conn, struct sk_buff *skb,
41 unsigned int offset, int offloaded)
42{
43 int status = 0;
44 int bytes_read;
45
46 bytes_read = iscsi_tcp_recv_skb(conn, skb, offset, offloaded, &status);
47 switch (status) {
48 case ISCSI_TCP_CONN_ERR:
49 return -EIO;
50 case ISCSI_TCP_SUSPENDED:
51 /* no transfer - just have caller flush queue */
52 return bytes_read;
53 case ISCSI_TCP_SKB_DONE:
54 /*
55 * pdus should always fit in the skb and we should get
56 * segment done notifcation.
57 */
58 iscsi_conn_printk(KERN_ERR, conn, "Invalid pdu or skb.");
59 return -EFAULT;
60 case ISCSI_TCP_SEGMENT_DONE:
61 return bytes_read;
62 default:
63 iscsi_conn_printk(KERN_ERR, conn, "Invalid iscsi_tcp_recv_skb "
64 "status %d\n", status);
65 return -EINVAL;
66 }
67}
68
69static int cxgb3i_conn_read_pdu_skb(struct iscsi_conn *conn,
70 struct sk_buff *skb)
71{
72 struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
73 bool offloaded = 0;
74 unsigned int offset;
75 int rc;
76
77 cxgb3i_rx_debug("conn 0x%p, skb 0x%p, len %u, flag 0x%x.\n",
78 conn, skb, skb->len, skb_ulp_mode(skb));
79
80 if (!iscsi_tcp_recv_segment_is_hdr(tcp_conn)) {
81 iscsi_conn_failure(conn, ISCSI_ERR_PROTO);
82 return -EIO;
83 }
84
85 if (conn->hdrdgst_en && (skb_ulp_mode(skb) & ULP2_FLAG_HCRC_ERROR)) {
86 iscsi_conn_failure(conn, ISCSI_ERR_HDR_DGST);
87 return -EIO;
88 }
89
90 if (conn->datadgst_en && (skb_ulp_mode(skb) & ULP2_FLAG_DCRC_ERROR)) {
91 iscsi_conn_failure(conn, ISCSI_ERR_DATA_DGST);
92 return -EIO;
93 }
94
95 /* iscsi hdr */
96 rc = read_pdu_skb(conn, skb, 0, 0);
97 if (rc <= 0)
98 return rc;
99
100 if (iscsi_tcp_recv_segment_is_hdr(tcp_conn))
101 return 0;
102
103 offset = rc;
104 if (conn->hdrdgst_en)
105 offset += ISCSI_DIGEST_SIZE;
106
107 /* iscsi data */
108 if (skb_ulp_mode(skb) & ULP2_FLAG_DATA_DDPED) {
109 cxgb3i_rx_debug("skb 0x%p, opcode 0x%x, data %u, ddp'ed, "
110 "itt 0x%x.\n",
111 skb,
112 tcp_conn->in.hdr->opcode & ISCSI_OPCODE_MASK,
113 tcp_conn->in.datalen,
114 ntohl(tcp_conn->in.hdr->itt));
115 offloaded = 1;
116 } else {
117 cxgb3i_rx_debug("skb 0x%p, opcode 0x%x, data %u, NOT ddp'ed, "
118 "itt 0x%x.\n",
119 skb,
120 tcp_conn->in.hdr->opcode & ISCSI_OPCODE_MASK,
121 tcp_conn->in.datalen,
122 ntohl(tcp_conn->in.hdr->itt));
123 offset += sizeof(struct cpl_iscsi_hdr_norss);
124 }
125
126 rc = read_pdu_skb(conn, skb, offset, offloaded);
127 if (rc < 0)
128 return rc;
129 else
130 return 0;
131}
132
133/*
134 * pdu transmit, interact with libiscsi_tcp
135 */
136static inline void tx_skb_setmode(struct sk_buff *skb, int hcrc, int dcrc)
137{
138 u8 submode = 0;
139
140 if (hcrc)
141 submode |= 1;
142 if (dcrc)
143 submode |= 2;
144 skb_ulp_mode(skb) = (ULP_MODE_ISCSI << 4) | submode;
145}
146
147void cxgb3i_conn_cleanup_task(struct iscsi_task *task)
148{
149 struct iscsi_tcp_task *tcp_task = task->dd_data;
150
151 /* never reached the xmit task callout */
152 if (tcp_task->dd_data)
153 kfree_skb(tcp_task->dd_data);
154 tcp_task->dd_data = NULL;
155
156 /* MNC - Do we need a check in case this is called but
157 * cxgb3i_conn_alloc_pdu has never been called on the task */
158 cxgb3i_release_itt(task, task->hdr_itt);
159 iscsi_tcp_cleanup_task(task);
160}
161
162/*
163 * We do not support ahs yet
164 */
165int cxgb3i_conn_alloc_pdu(struct iscsi_task *task, u8 opcode)
166{
167 struct iscsi_tcp_task *tcp_task = task->dd_data;
168 struct sk_buff *skb;
169
170 task->hdr = NULL;
171 /* always allocate rooms for AHS */
172 skb = alloc_skb(sizeof(struct iscsi_hdr) + ISCSI_MAX_AHS_SIZE +
173 TX_HEADER_LEN, GFP_ATOMIC);
174 if (!skb)
175 return -ENOMEM;
176
177 cxgb3i_tx_debug("task 0x%p, opcode 0x%x, skb 0x%p.\n",
178 task, opcode, skb);
179
180 tcp_task->dd_data = skb;
181 skb_reserve(skb, TX_HEADER_LEN);
182 task->hdr = (struct iscsi_hdr *)skb->data;
183 task->hdr_max = sizeof(struct iscsi_hdr);
184
185 /* data_out uses scsi_cmd's itt */
186 if (opcode != ISCSI_OP_SCSI_DATA_OUT)
187 cxgb3i_reserve_itt(task, &task->hdr->itt);
188
189 return 0;
190}
191
192int cxgb3i_conn_init_pdu(struct iscsi_task *task, unsigned int offset,
193 unsigned int count)
194{
195 struct iscsi_tcp_task *tcp_task = task->dd_data;
196 struct sk_buff *skb = tcp_task->dd_data;
197 struct iscsi_conn *conn = task->conn;
198 struct page *pg;
199 unsigned int datalen = count;
200 int i, padlen = iscsi_padding(count);
201 skb_frag_t *frag;
202
203 cxgb3i_tx_debug("task 0x%p,0x%p, offset %u, count %u, skb 0x%p.\n",
204 task, task->sc, offset, count, skb);
205
206 skb_put(skb, task->hdr_len);
207 tx_skb_setmode(skb, conn->hdrdgst_en, datalen ? conn->datadgst_en : 0);
208 if (!count)
209 return 0;
210
211 if (task->sc) {
212 struct scatterlist *sg;
213 struct scsi_data_buffer *sdb;
214 unsigned int sgoffset = offset;
215 struct page *sgpg;
216 unsigned int sglen;
217
218 sdb = scsi_out(task->sc);
219 sg = sdb->table.sgl;
220
221 for_each_sg(sdb->table.sgl, sg, sdb->table.nents, i) {
222 cxgb3i_tx_debug("sg %d, page 0x%p, len %u offset %u\n",
223 i, sg_page(sg), sg->length, sg->offset);
224
225 if (sgoffset < sg->length)
226 break;
227 sgoffset -= sg->length;
228 }
229 sgpg = sg_page(sg);
230 sglen = sg->length - sgoffset;
231
232 do {
233 int j = skb_shinfo(skb)->nr_frags;
234 unsigned int copy;
235
236 if (!sglen) {
237 sg = sg_next(sg);
238 sgpg = sg_page(sg);
239 sgoffset = 0;
240 sglen = sg->length;
241 ++i;
242 }
243 copy = min(sglen, datalen);
244 if (j && skb_can_coalesce(skb, j, sgpg,
245 sg->offset + sgoffset)) {
246 skb_shinfo(skb)->frags[j - 1].size += copy;
247 } else {
248 get_page(sgpg);
249 skb_fill_page_desc(skb, j, sgpg,
250 sg->offset + sgoffset, copy);
251 }
252 sgoffset += copy;
253 sglen -= copy;
254 datalen -= copy;
255 } while (datalen);
256 } else {
257 pg = virt_to_page(task->data);
258
259 while (datalen) {
260 i = skb_shinfo(skb)->nr_frags;
261 frag = &skb_shinfo(skb)->frags[i];
262
263 get_page(pg);
264 frag->page = pg;
265 frag->page_offset = 0;
266 frag->size = min((unsigned int)PAGE_SIZE, datalen);
267
268 skb_shinfo(skb)->nr_frags++;
269 datalen -= frag->size;
270 pg++;
271 }
272 }
273
274 if (padlen) {
275 i = skb_shinfo(skb)->nr_frags;
276 frag = &skb_shinfo(skb)->frags[i];
277 frag->page = pad_page;
278 frag->page_offset = 0;
279 frag->size = padlen;
280 skb_shinfo(skb)->nr_frags++;
281 }
282
283 datalen = count + padlen;
284 skb->data_len += datalen;
285 skb->truesize += datalen;
286 skb->len += datalen;
287 return 0;
288}
289
290int cxgb3i_conn_xmit_pdu(struct iscsi_task *task)
291{
292 struct iscsi_tcp_task *tcp_task = task->dd_data;
293 struct sk_buff *skb = tcp_task->dd_data;
294 struct iscsi_tcp_conn *tcp_conn = task->conn->dd_data;
295 struct cxgb3i_conn *cconn = tcp_conn->dd_data;
296 unsigned int datalen;
297 int err;
298
299 if (!skb)
300 return 0;
301
302 datalen = skb->data_len;
303 tcp_task->dd_data = NULL;
304 err = cxgb3i_c3cn_send_pdus(cconn->cep->c3cn, skb);
305 cxgb3i_tx_debug("task 0x%p, skb 0x%p, len %u/%u, rv %d.\n",
306 task, skb, skb->len, skb->data_len, err);
307 if (err > 0) {
308 int pdulen = err;
309
310 if (task->conn->hdrdgst_en)
311 pdulen += ISCSI_DIGEST_SIZE;
312 if (datalen && task->conn->datadgst_en)
313 pdulen += ISCSI_DIGEST_SIZE;
314
315 task->conn->txdata_octets += pdulen;
316 return 0;
317 }
318
319 if (err < 0 && err != -EAGAIN) {
320 kfree_skb(skb);
321 cxgb3i_tx_debug("itt 0x%x, skb 0x%p, len %u/%u, xmit err %d.\n",
322 task->itt, skb, skb->len, skb->data_len, err);
323 iscsi_conn_printk(KERN_ERR, task->conn, "xmit err %d.\n", err);
324 iscsi_conn_failure(task->conn, ISCSI_ERR_XMIT_FAILED);
325 return err;
326 }
327 /* reset skb to send when we are called again */
328 tcp_task->dd_data = skb;
329 return -EAGAIN;
330}
331
332int cxgb3i_pdu_init(void)
333{
334 pad_page = alloc_page(GFP_KERNEL);
335 if (!pad_page)
336 return -ENOMEM;
337 memset(page_address(pad_page), 0, PAGE_SIZE);
338 return 0;
339}
340
341void cxgb3i_pdu_cleanup(void)
342{
343 if (pad_page) {
344 __free_page(pad_page);
345 pad_page = NULL;
346 }
347}
348
349void cxgb3i_conn_pdu_ready(struct s3_conn *c3cn)
350{
351 struct sk_buff *skb;
352 unsigned int read = 0;
353 struct iscsi_conn *conn = c3cn->user_data;
354 int err = 0;
355
356 cxgb3i_rx_debug("cn 0x%p.\n", c3cn);
357
358 read_lock(&c3cn->callback_lock);
359 if (unlikely(!conn || conn->suspend_rx)) {
360 cxgb3i_rx_debug("conn 0x%p, id %d, suspend_rx %lu!\n",
361 conn, conn ? conn->id : 0xFF,
362 conn ? conn->suspend_rx : 0xFF);
363 read_unlock(&c3cn->callback_lock);
364 return;
365 }
366 skb = skb_peek(&c3cn->receive_queue);
367 while (!err && skb) {
368 __skb_unlink(skb, &c3cn->receive_queue);
369 read += skb_ulp_pdulen(skb);
370 err = cxgb3i_conn_read_pdu_skb(conn, skb);
371 __kfree_skb(skb);
372 skb = skb_peek(&c3cn->receive_queue);
373 }
374 read_unlock(&c3cn->callback_lock);
375 if (c3cn) {
376 c3cn->copied_seq += read;
377 cxgb3i_c3cn_rx_credits(c3cn, read);
378 }
379 conn->rxdata_octets += read;
380}
381
382void cxgb3i_conn_tx_open(struct s3_conn *c3cn)
383{
384 struct iscsi_conn *conn = c3cn->user_data;
385
386 cxgb3i_tx_debug("cn 0x%p.\n", c3cn);
387 if (conn) {
388 cxgb3i_tx_debug("cn 0x%p, cid %d.\n", c3cn, conn->id);
389 scsi_queue_work(conn->session->host, &conn->xmitwork);
390 }
391}
392
393void cxgb3i_conn_closing(struct s3_conn *c3cn)
394{
395 struct iscsi_conn *conn;
396
397 read_lock(&c3cn->callback_lock);
398 conn = c3cn->user_data;
399 if (conn && c3cn->state != C3CN_STATE_ESTABLISHED)
400 iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
401 read_unlock(&c3cn->callback_lock);
402}