123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402 |
- /*
- * cxgb3i_pdu.c: Chelsio S3xx iSCSI driver.
- *
- * Copyright (c) 2008 Chelsio Communications, Inc.
- * Copyright (c) 2008 Mike Christie
- * Copyright (c) 2008 Red Hat, Inc. All rights reserved.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation.
- *
- * Written by: Karen Xie (kxie@chelsio.com)
- */
- #include <linux/skbuff.h>
- #include <linux/crypto.h>
- #include <scsi/scsi_cmnd.h>
- #include <scsi/scsi_host.h>
- #include "cxgb3i.h"
- #include "cxgb3i_pdu.h"
- #ifdef __DEBUG_CXGB3I_RX__
- #define cxgb3i_rx_debug cxgb3i_log_debug
- #else
- #define cxgb3i_rx_debug(fmt...)
- #endif
- #ifdef __DEBUG_CXGB3I_TX__
- #define cxgb3i_tx_debug cxgb3i_log_debug
- #else
- #define cxgb3i_tx_debug(fmt...)
- #endif
- static struct page *pad_page;
- /*
- * pdu receive, interact with libiscsi_tcp
- */
- static inline int read_pdu_skb(struct iscsi_conn *conn, struct sk_buff *skb,
- unsigned int offset, int offloaded)
- {
- int status = 0;
- int bytes_read;
- bytes_read = iscsi_tcp_recv_skb(conn, skb, offset, offloaded, &status);
- switch (status) {
- case ISCSI_TCP_CONN_ERR:
- return -EIO;
- case ISCSI_TCP_SUSPENDED:
- /* no transfer - just have caller flush queue */
- return bytes_read;
- case ISCSI_TCP_SKB_DONE:
- /*
- * pdus should always fit in the skb and we should get
- * segment done notifcation.
- */
- iscsi_conn_printk(KERN_ERR, conn, "Invalid pdu or skb.");
- return -EFAULT;
- case ISCSI_TCP_SEGMENT_DONE:
- return bytes_read;
- default:
- iscsi_conn_printk(KERN_ERR, conn, "Invalid iscsi_tcp_recv_skb "
- "status %d\n", status);
- return -EINVAL;
- }
- }
- static int cxgb3i_conn_read_pdu_skb(struct iscsi_conn *conn,
- struct sk_buff *skb)
- {
- struct iscsi_tcp_conn *tcp_conn = conn->dd_data;
- bool offloaded = 0;
- unsigned int offset;
- int rc;
- cxgb3i_rx_debug("conn 0x%p, skb 0x%p, len %u, flag 0x%x.\n",
- conn, skb, skb->len, skb_ulp_mode(skb));
- if (!iscsi_tcp_recv_segment_is_hdr(tcp_conn)) {
- iscsi_conn_failure(conn, ISCSI_ERR_PROTO);
- return -EIO;
- }
- if (conn->hdrdgst_en && (skb_ulp_mode(skb) & ULP2_FLAG_HCRC_ERROR)) {
- iscsi_conn_failure(conn, ISCSI_ERR_HDR_DGST);
- return -EIO;
- }
- if (conn->datadgst_en && (skb_ulp_mode(skb) & ULP2_FLAG_DCRC_ERROR)) {
- iscsi_conn_failure(conn, ISCSI_ERR_DATA_DGST);
- return -EIO;
- }
- /* iscsi hdr */
- rc = read_pdu_skb(conn, skb, 0, 0);
- if (rc <= 0)
- return rc;
- if (iscsi_tcp_recv_segment_is_hdr(tcp_conn))
- return 0;
- offset = rc;
- if (conn->hdrdgst_en)
- offset += ISCSI_DIGEST_SIZE;
- /* iscsi data */
- if (skb_ulp_mode(skb) & ULP2_FLAG_DATA_DDPED) {
- cxgb3i_rx_debug("skb 0x%p, opcode 0x%x, data %u, ddp'ed, "
- "itt 0x%x.\n",
- skb,
- tcp_conn->in.hdr->opcode & ISCSI_OPCODE_MASK,
- tcp_conn->in.datalen,
- ntohl(tcp_conn->in.hdr->itt));
- offloaded = 1;
- } else {
- cxgb3i_rx_debug("skb 0x%p, opcode 0x%x, data %u, NOT ddp'ed, "
- "itt 0x%x.\n",
- skb,
- tcp_conn->in.hdr->opcode & ISCSI_OPCODE_MASK,
- tcp_conn->in.datalen,
- ntohl(tcp_conn->in.hdr->itt));
- offset += sizeof(struct cpl_iscsi_hdr_norss);
- }
- rc = read_pdu_skb(conn, skb, offset, offloaded);
- if (rc < 0)
- return rc;
- else
- return 0;
- }
- /*
- * pdu transmit, interact with libiscsi_tcp
- */
- static inline void tx_skb_setmode(struct sk_buff *skb, int hcrc, int dcrc)
- {
- u8 submode = 0;
- if (hcrc)
- submode |= 1;
- if (dcrc)
- submode |= 2;
- skb_ulp_mode(skb) = (ULP_MODE_ISCSI << 4) | submode;
- }
- void cxgb3i_conn_cleanup_task(struct iscsi_task *task)
- {
- struct iscsi_tcp_task *tcp_task = task->dd_data;
- /* never reached the xmit task callout */
- if (tcp_task->dd_data)
- kfree_skb(tcp_task->dd_data);
- tcp_task->dd_data = NULL;
- /* MNC - Do we need a check in case this is called but
- * cxgb3i_conn_alloc_pdu has never been called on the task */
- cxgb3i_release_itt(task, task->hdr_itt);
- iscsi_tcp_cleanup_task(task);
- }
- /*
- * We do not support ahs yet
- */
- int cxgb3i_conn_alloc_pdu(struct iscsi_task *task, u8 opcode)
- {
- struct iscsi_tcp_task *tcp_task = task->dd_data;
- struct sk_buff *skb;
- task->hdr = NULL;
- /* always allocate rooms for AHS */
- skb = alloc_skb(sizeof(struct iscsi_hdr) + ISCSI_MAX_AHS_SIZE +
- TX_HEADER_LEN, GFP_ATOMIC);
- if (!skb)
- return -ENOMEM;
- cxgb3i_tx_debug("task 0x%p, opcode 0x%x, skb 0x%p.\n",
- task, opcode, skb);
- tcp_task->dd_data = skb;
- skb_reserve(skb, TX_HEADER_LEN);
- task->hdr = (struct iscsi_hdr *)skb->data;
- task->hdr_max = sizeof(struct iscsi_hdr);
- /* data_out uses scsi_cmd's itt */
- if (opcode != ISCSI_OP_SCSI_DATA_OUT)
- cxgb3i_reserve_itt(task, &task->hdr->itt);
- return 0;
- }
- int cxgb3i_conn_init_pdu(struct iscsi_task *task, unsigned int offset,
- unsigned int count)
- {
- struct iscsi_tcp_task *tcp_task = task->dd_data;
- struct sk_buff *skb = tcp_task->dd_data;
- struct iscsi_conn *conn = task->conn;
- struct page *pg;
- unsigned int datalen = count;
- int i, padlen = iscsi_padding(count);
- skb_frag_t *frag;
- cxgb3i_tx_debug("task 0x%p,0x%p, offset %u, count %u, skb 0x%p.\n",
- task, task->sc, offset, count, skb);
- skb_put(skb, task->hdr_len);
- tx_skb_setmode(skb, conn->hdrdgst_en, datalen ? conn->datadgst_en : 0);
- if (!count)
- return 0;
- if (task->sc) {
- struct scatterlist *sg;
- struct scsi_data_buffer *sdb;
- unsigned int sgoffset = offset;
- struct page *sgpg;
- unsigned int sglen;
- sdb = scsi_out(task->sc);
- sg = sdb->table.sgl;
- for_each_sg(sdb->table.sgl, sg, sdb->table.nents, i) {
- cxgb3i_tx_debug("sg %d, page 0x%p, len %u offset %u\n",
- i, sg_page(sg), sg->length, sg->offset);
- if (sgoffset < sg->length)
- break;
- sgoffset -= sg->length;
- }
- sgpg = sg_page(sg);
- sglen = sg->length - sgoffset;
- do {
- int j = skb_shinfo(skb)->nr_frags;
- unsigned int copy;
- if (!sglen) {
- sg = sg_next(sg);
- sgpg = sg_page(sg);
- sgoffset = 0;
- sglen = sg->length;
- ++i;
- }
- copy = min(sglen, datalen);
- if (j && skb_can_coalesce(skb, j, sgpg,
- sg->offset + sgoffset)) {
- skb_shinfo(skb)->frags[j - 1].size += copy;
- } else {
- get_page(sgpg);
- skb_fill_page_desc(skb, j, sgpg,
- sg->offset + sgoffset, copy);
- }
- sgoffset += copy;
- sglen -= copy;
- datalen -= copy;
- } while (datalen);
- } else {
- pg = virt_to_page(task->data);
- while (datalen) {
- i = skb_shinfo(skb)->nr_frags;
- frag = &skb_shinfo(skb)->frags[i];
- get_page(pg);
- frag->page = pg;
- frag->page_offset = 0;
- frag->size = min((unsigned int)PAGE_SIZE, datalen);
- skb_shinfo(skb)->nr_frags++;
- datalen -= frag->size;
- pg++;
- }
- }
- if (padlen) {
- i = skb_shinfo(skb)->nr_frags;
- frag = &skb_shinfo(skb)->frags[i];
- frag->page = pad_page;
- frag->page_offset = 0;
- frag->size = padlen;
- skb_shinfo(skb)->nr_frags++;
- }
- datalen = count + padlen;
- skb->data_len += datalen;
- skb->truesize += datalen;
- skb->len += datalen;
- return 0;
- }
- int cxgb3i_conn_xmit_pdu(struct iscsi_task *task)
- {
- struct iscsi_tcp_task *tcp_task = task->dd_data;
- struct sk_buff *skb = tcp_task->dd_data;
- struct iscsi_tcp_conn *tcp_conn = task->conn->dd_data;
- struct cxgb3i_conn *cconn = tcp_conn->dd_data;
- unsigned int datalen;
- int err;
- if (!skb)
- return 0;
- datalen = skb->data_len;
- tcp_task->dd_data = NULL;
- err = cxgb3i_c3cn_send_pdus(cconn->cep->c3cn, skb);
- cxgb3i_tx_debug("task 0x%p, skb 0x%p, len %u/%u, rv %d.\n",
- task, skb, skb->len, skb->data_len, err);
- if (err > 0) {
- int pdulen = err;
- if (task->conn->hdrdgst_en)
- pdulen += ISCSI_DIGEST_SIZE;
- if (datalen && task->conn->datadgst_en)
- pdulen += ISCSI_DIGEST_SIZE;
- task->conn->txdata_octets += pdulen;
- return 0;
- }
- if (err < 0 && err != -EAGAIN) {
- kfree_skb(skb);
- cxgb3i_tx_debug("itt 0x%x, skb 0x%p, len %u/%u, xmit err %d.\n",
- task->itt, skb, skb->len, skb->data_len, err);
- iscsi_conn_printk(KERN_ERR, task->conn, "xmit err %d.\n", err);
- iscsi_conn_failure(task->conn, ISCSI_ERR_XMIT_FAILED);
- return err;
- }
- /* reset skb to send when we are called again */
- tcp_task->dd_data = skb;
- return -EAGAIN;
- }
- int cxgb3i_pdu_init(void)
- {
- pad_page = alloc_page(GFP_KERNEL);
- if (!pad_page)
- return -ENOMEM;
- memset(page_address(pad_page), 0, PAGE_SIZE);
- return 0;
- }
- void cxgb3i_pdu_cleanup(void)
- {
- if (pad_page) {
- __free_page(pad_page);
- pad_page = NULL;
- }
- }
- void cxgb3i_conn_pdu_ready(struct s3_conn *c3cn)
- {
- struct sk_buff *skb;
- unsigned int read = 0;
- struct iscsi_conn *conn = c3cn->user_data;
- int err = 0;
- cxgb3i_rx_debug("cn 0x%p.\n", c3cn);
- read_lock(&c3cn->callback_lock);
- if (unlikely(!conn || conn->suspend_rx)) {
- cxgb3i_rx_debug("conn 0x%p, id %d, suspend_rx %lu!\n",
- conn, conn ? conn->id : 0xFF,
- conn ? conn->suspend_rx : 0xFF);
- read_unlock(&c3cn->callback_lock);
- return;
- }
- skb = skb_peek(&c3cn->receive_queue);
- while (!err && skb) {
- __skb_unlink(skb, &c3cn->receive_queue);
- read += skb_ulp_pdulen(skb);
- err = cxgb3i_conn_read_pdu_skb(conn, skb);
- __kfree_skb(skb);
- skb = skb_peek(&c3cn->receive_queue);
- }
- read_unlock(&c3cn->callback_lock);
- if (c3cn) {
- c3cn->copied_seq += read;
- cxgb3i_c3cn_rx_credits(c3cn, read);
- }
- conn->rxdata_octets += read;
- }
- void cxgb3i_conn_tx_open(struct s3_conn *c3cn)
- {
- struct iscsi_conn *conn = c3cn->user_data;
- cxgb3i_tx_debug("cn 0x%p.\n", c3cn);
- if (conn) {
- cxgb3i_tx_debug("cn 0x%p, cid %d.\n", c3cn, conn->id);
- scsi_queue_work(conn->session->host, &conn->xmitwork);
- }
- }
- void cxgb3i_conn_closing(struct s3_conn *c3cn)
- {
- struct iscsi_conn *conn;
- read_lock(&c3cn->callback_lock);
- conn = c3cn->user_data;
- if (conn && c3cn->state != C3CN_STATE_ESTABLISHED)
- iscsi_conn_failure(conn, ISCSI_ERR_CONN_FAILED);
- read_unlock(&c3cn->callback_lock);
- }
|