1
0
Fork 0
alistair23-linux/drivers/scsi/cxgbi/cxgb3i/cxgb3i.c

1406 lines
41 KiB
C
Raw Normal View History

/*
* cxgb3i_offload.c: Chelsio S3xx iscsi offloaded tcp connection management
*
* Copyright (C) 2003-2015 Chelsio Communications. All rights reserved.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the LICENSE file included in this
* release for licensing terms and conditions.
*
* Written by: Dimitris Michailidis (dm@chelsio.com)
* Karen Xie (kxie@chelsio.com)
*/
#define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <scsi/scsi_host.h>
#include "common.h"
#include "t3_cpl.h"
#include "t3cdev.h"
#include "cxgb3_defs.h"
#include "cxgb3_ctl_defs.h"
#include "cxgb3_offload.h"
#include "firmware_exports.h"
#include "cxgb3i.h"
static unsigned int dbg_level;
#include "../libcxgbi.h"
#define DRV_MODULE_NAME "cxgb3i"
#define DRV_MODULE_DESC "Chelsio T3 iSCSI Driver"
#define DRV_MODULE_VERSION "2.0.1-ko"
#define DRV_MODULE_RELDATE "Apr. 2015"
static char version[] =
DRV_MODULE_DESC " " DRV_MODULE_NAME
" v" DRV_MODULE_VERSION " (" DRV_MODULE_RELDATE ")\n";
MODULE_AUTHOR("Chelsio Communications, Inc.");
MODULE_DESCRIPTION(DRV_MODULE_DESC);
MODULE_VERSION(DRV_MODULE_VERSION);
MODULE_LICENSE("GPL");
module_param(dbg_level, uint, 0644);
MODULE_PARM_DESC(dbg_level, "debug flag (default=0)");
static int cxgb3i_rcv_win = 256 * 1024;
module_param(cxgb3i_rcv_win, int, 0644);
MODULE_PARM_DESC(cxgb3i_rcv_win, "TCP receive window in bytes (default=256KB)");
static int cxgb3i_snd_win = 128 * 1024;
module_param(cxgb3i_snd_win, int, 0644);
MODULE_PARM_DESC(cxgb3i_snd_win, "TCP send window in bytes (default=128KB)");
static int cxgb3i_rx_credit_thres = 10 * 1024;
module_param(cxgb3i_rx_credit_thres, int, 0644);
MODULE_PARM_DESC(cxgb3i_rx_credit_thres,
"RX credits return threshold in bytes (default=10KB)");
static unsigned int cxgb3i_max_connect = 8 * 1024;
module_param(cxgb3i_max_connect, uint, 0644);
MODULE_PARM_DESC(cxgb3i_max_connect, "Max. # of connections (default=8092)");
static unsigned int cxgb3i_sport_base = 20000;
module_param(cxgb3i_sport_base, uint, 0644);
MODULE_PARM_DESC(cxgb3i_sport_base, "starting port number (default=20000)");
static void cxgb3i_dev_open(struct t3cdev *);
static void cxgb3i_dev_close(struct t3cdev *);
static void cxgb3i_dev_event_handler(struct t3cdev *, u32, u32);
static struct cxgb3_client t3_client = {
.name = DRV_MODULE_NAME,
.handlers = cxgb3i_cpl_handlers,
.add = cxgb3i_dev_open,
.remove = cxgb3i_dev_close,
.event_handler = cxgb3i_dev_event_handler,
};
static struct scsi_host_template cxgb3i_host_template = {
.module = THIS_MODULE,
.name = DRV_MODULE_NAME,
.proc_name = DRV_MODULE_NAME,
.can_queue = CXGB3I_SCSI_HOST_QDEPTH,
.queuecommand = iscsi_queuecommand,
.change_queue_depth = scsi_change_queue_depth,
.sg_tablesize = SG_ALL,
.max_sectors = 0xFFFF,
.cmd_per_lun = ISCSI_DEF_CMD_PER_LUN,
.eh_timed_out = iscsi_eh_cmd_timed_out,
.eh_abort_handler = iscsi_eh_abort,
.eh_device_reset_handler = iscsi_eh_device_reset,
.eh_target_reset_handler = iscsi_eh_recover_target,
.target_alloc = iscsi_target_alloc,
.use_clustering = DISABLE_CLUSTERING,
.this_id = -1,
.track_queue_depth = 1,
};
static struct iscsi_transport cxgb3i_iscsi_transport = {
.owner = THIS_MODULE,
.name = DRV_MODULE_NAME,
/* owner and name should be set already */
.caps = CAP_RECOVERY_L0 | CAP_MULTI_R2T | CAP_HDRDGST
| CAP_DATADGST | CAP_DIGEST_OFFLOAD |
CAP_PADDING_OFFLOAD | CAP_TEXT_NEGO,
.attr_is_visible = cxgbi_attr_is_visible,
.get_host_param = cxgbi_get_host_param,
.set_host_param = cxgbi_set_host_param,
/* session management */
.create_session = cxgbi_create_session,
.destroy_session = cxgbi_destroy_session,
.get_session_param = iscsi_session_get_param,
/* connection management */
.create_conn = cxgbi_create_conn,
.bind_conn = cxgbi_bind_conn,
.destroy_conn = iscsi_tcp_conn_teardown,
.start_conn = iscsi_conn_start,
.stop_conn = iscsi_conn_stop,
.get_conn_param = iscsi_conn_get_param,
.set_param = cxgbi_set_conn_param,
.get_stats = cxgbi_get_conn_stats,
/* pdu xmit req from user space */
.send_pdu = iscsi_conn_send_pdu,
/* task */
.init_task = iscsi_tcp_task_init,
.xmit_task = iscsi_tcp_task_xmit,
.cleanup_task = cxgbi_cleanup_task,
/* pdu */
.alloc_pdu = cxgbi_conn_alloc_pdu,
.init_pdu = cxgbi_conn_init_pdu,
.xmit_pdu = cxgbi_conn_xmit_pdu,
.parse_pdu_itt = cxgbi_parse_pdu_itt,
/* TCP connect/disconnect */
.get_ep_param = cxgbi_get_ep_param,
.ep_connect = cxgbi_ep_connect,
.ep_poll = cxgbi_ep_poll,
.ep_disconnect = cxgbi_ep_disconnect,
/* Error recovery timeout call */
.session_recovery_timedout = iscsi_session_recovery_timedout,
};
static struct scsi_transport_template *cxgb3i_stt;
/*
* CPL (Chelsio Protocol Language) defines a message passing interface between
* the host driver and Chelsio asic.
* The section below implments CPLs that related to iscsi tcp connection
* open/close/abort and data send/receive.
*/
static int push_tx_frames(struct cxgbi_sock *csk, int req_completion);
static void send_act_open_req(struct cxgbi_sock *csk, struct sk_buff *skb,
const struct l2t_entry *e)
{
unsigned int wscale = cxgbi_sock_compute_wscale(csk->rcv_win);
struct cpl_act_open_req *req = (struct cpl_act_open_req *)skb->head;
skb->priority = CPL_PRIORITY_SETUP;
req->wr.wr_hi = htonl(V_WR_OP(FW_WROPCODE_FORWARD));
OPCODE_TID(req) = htonl(MK_OPCODE_TID(CPL_ACT_OPEN_REQ, csk->atid));
req->local_port = csk->saddr.sin_port;
req->peer_port = csk->daddr.sin_port;
req->local_ip = csk->saddr.sin_addr.s_addr;
req->peer_ip = csk->daddr.sin_addr.s_addr;
req->opt0h = htonl(V_KEEP_ALIVE(1) | F_TCAM_BYPASS |
V_WND_SCALE(wscale) | V_MSS_IDX(csk->mss_idx) |
V_L2T_IDX(e->idx) | V_TX_CHANNEL(e->smt_idx));
req->opt0l = htonl(V_ULP_MODE(ULP2_MODE_ISCSI) |
V_RCV_BUFSIZ(csk->rcv_win >> 10));
log_debug(1 << CXGBI_DBG_TOE | 1 << CXGBI_DBG_SOCK,
"csk 0x%p,%u,0x%lx,%u, %pI4:%u-%pI4:%u, %u,%u,%u.\n",
csk, csk->state, csk->flags, csk->atid,
&req->local_ip, ntohs(req->local_port),
&req->peer_ip, ntohs(req->peer_port),
csk->mss_idx, e->idx, e->smt_idx);
l2t_send(csk->cdev->lldev, skb, csk->l2t);
}
static inline void act_open_arp_failure(struct t3cdev *dev, struct sk_buff *skb)
{
cxgbi_sock_act_open_req_arp_failure(NULL, skb);
}
/*
* CPL connection close request: host ->
*
* Close a connection by sending a CPL_CLOSE_CON_REQ message and queue it to
* the write queue (i.e., after any unsent txt data).
*/
static void send_close_req(struct cxgbi_sock *csk)
{
struct sk_buff *skb = csk->cpl_close;
struct cpl_close_con_req *req = (struct cpl_close_con_req *)skb->head;
unsigned int tid = csk->tid;
log_debug(1 << CXGBI_DBG_TOE | 1 << CXGBI_DBG_SOCK,
"csk 0x%p,%u,0x%lx,%u.\n",
csk, csk->state, csk->flags, csk->tid);
csk->cpl_close = NULL;
req->wr.wr_hi = htonl(V_WR_OP(FW_WROPCODE_OFLD_CLOSE_CON));
req->wr.wr_lo = htonl(V_WR_TID(tid));
OPCODE_TID(req) = htonl(MK_OPCODE_TID(CPL_CLOSE_CON_REQ, tid));
req->rsvd = htonl(csk->write_seq);
cxgbi_sock_skb_entail(csk, skb);
if (csk->state >= CTP_ESTABLISHED)
push_tx_frames(csk, 1);
}
/*
* CPL connection abort request: host ->
*
* Send an ABORT_REQ message. Makes sure we do not send multiple ABORT_REQs
* for the same connection and also that we do not try to send a message
* after the connection has closed.
*/
static void abort_arp_failure(struct t3cdev *tdev, struct sk_buff *skb)
{
struct cpl_abort_req *req = cplhdr(skb);
log_debug(1 << CXGBI_DBG_TOE | 1 << CXGBI_DBG_SOCK,
"t3dev 0x%p, tid %u, skb 0x%p.\n",
tdev, GET_TID(req), skb);
req->cmd = CPL_ABORT_NO_RST;
cxgb3_ofld_send(tdev, skb);
}
static void send_abort_req(struct cxgbi_sock *csk)
{
struct sk_buff *skb = csk->cpl_abort_req;
struct cpl_abort_req *req;
if (unlikely(csk->state == CTP_ABORTING || !skb))
return;
cxgbi_sock_set_state(csk, CTP_ABORTING);
cxgbi_sock_set_flag(csk, CTPF_ABORT_RPL_PENDING);
/* Purge the send queue so we don't send anything after an abort. */
cxgbi_sock_purge_write_queue(csk);
csk->cpl_abort_req = NULL;
req = (struct cpl_abort_req *)skb->head;
skb->priority = CPL_PRIORITY_DATA;
set_arp_failure_handler(skb, abort_arp_failure);
req->wr.wr_hi = htonl(V_WR_OP(FW_WROPCODE_OFLD_HOST_ABORT_CON_REQ));
req->wr.wr_lo = htonl(V_WR_TID(csk->tid));
OPCODE_TID(req) = htonl(MK_OPCODE_TID(CPL_ABORT_REQ, csk->tid));
req->rsvd0 = htonl(csk->snd_nxt);
req->rsvd1 = !cxgbi_sock_flag(csk, CTPF_TX_DATA_SENT);
req->cmd = CPL_ABORT_SEND_RST;
log_debug(1 << CXGBI_DBG_TOE | 1 << CXGBI_DBG_SOCK,
"csk 0x%p,%u,0x%lx,%u, snd_nxt %u, 0x%x.\n",
csk, csk->state, csk->flags, csk->tid, csk->snd_nxt,
req->rsvd1);
l2t_send(csk->cdev->lldev, skb, csk->l2t);
}
/*
* CPL connection abort reply: host ->
*
* Send an ABORT_RPL message in response of the ABORT_REQ received.
*/
static void send_abort_rpl(struct cxgbi_sock *csk, int rst_status)
{
struct sk_buff *skb = csk->cpl_abort_rpl;
struct cpl_abort_rpl *rpl = (struct cpl_abort_rpl *)skb->head;
log_debug(1 << CXGBI_DBG_TOE | 1 << CXGBI_DBG_SOCK,
"csk 0x%p,%u,0x%lx,%u, status %d.\n",
csk, csk->state, csk->flags, csk->tid, rst_status);
csk->cpl_abort_rpl = NULL;
skb->priority = CPL_PRIORITY_DATA;
rpl->wr.wr_hi = htonl(V_WR_OP(FW_WROPCODE_OFLD_HOST_ABORT_CON_RPL));
rpl->wr.wr_lo = htonl(V_WR_TID(csk->tid));
OPCODE_TID(rpl) = htonl(MK_OPCODE_TID(CPL_ABORT_RPL, csk->tid));
rpl->cmd = rst_status;
cxgb3_ofld_send(csk->cdev->lldev, skb);
}
/*
* CPL connection rx data ack: host ->
* Send RX credits through an RX_DATA_ACK CPL message. Returns the number of
* credits sent.
*/
static u32 send_rx_credits(struct cxgbi_sock *csk, u32 credits)
{
struct sk_buff *skb;
struct cpl_rx_data_ack *req;
u32 dack = F_RX_DACK_CHANGE | V_RX_DACK_MODE(1);
log_debug(1 << CXGBI_DBG_TOE | 1 << CXGBI_DBG_PDU_RX,
"csk 0x%p,%u,0x%lx,%u, credit %u, dack %u.\n",
csk, csk->state, csk->flags, csk->tid, credits, dack);
skb = alloc_wr(sizeof(*req), 0, GFP_ATOMIC);
if (!skb) {
pr_info("csk 0x%p, credit %u, OOM.\n", csk, credits);
return 0;
}
req = (struct cpl_rx_data_ack *)skb->head;
req->wr.wr_hi = htonl(V_WR_OP(FW_WROPCODE_FORWARD));
OPCODE_TID(req) = htonl(MK_OPCODE_TID(CPL_RX_DATA_ACK, csk->tid));
req->credit_dack = htonl(F_RX_DACK_CHANGE | V_RX_DACK_MODE(1) |
V_RX_CREDITS(credits));
skb->priority = CPL_PRIORITY_ACK;
cxgb3_ofld_send(csk->cdev->lldev, skb);
return credits;
}
/*
* CPL connection tx data: host ->
*
* Send iscsi PDU via TX_DATA CPL message. Returns the number of
* credits sent.
* Each TX_DATA consumes work request credit (wrs), so we need to keep track of
* how many we've used so far and how many are pending (i.e., yet ack'ed by T3).
*/
static unsigned int wrlen __read_mostly;
static unsigned int skb_wrs[SKB_WR_LIST_SIZE] __read_mostly;
static void init_wr_tab(unsigned int wr_len)
{
int i;
if (skb_wrs[1]) /* already initialized */
return;
for (i = 1; i < SKB_WR_LIST_SIZE; i++) {
int sgl_len = (3 * i) / 2 + (i & 1);
sgl_len += 3;
skb_wrs[i] = (sgl_len <= wr_len
? 1 : 1 + (sgl_len - 2) / (wr_len - 1));
}
wrlen = wr_len * 8;
}
static inline void make_tx_data_wr(struct cxgbi_sock *csk, struct sk_buff *skb,
int len, int req_completion)
{
struct tx_data_wr *req;
struct l2t_entry *l2t = csk->l2t;
skb_reset_transport_header(skb);
req = __skb_push(skb, sizeof(*req));
req->wr_hi = htonl(V_WR_OP(FW_WROPCODE_OFLD_TX_DATA) |
(req_completion ? F_WR_COMPL : 0));
req->wr_lo = htonl(V_WR_TID(csk->tid));
/* len includes the length of any HW ULP additions */
req->len = htonl(len);
/* V_TX_ULP_SUBMODE sets both the mode and submode */
req->flags = htonl(V_TX_ULP_SUBMODE(cxgbi_skcb_ulp_mode(skb)) |
V_TX_SHOVE((skb_peek(&csk->write_queue) ? 0 : 1)));
req->sndseq = htonl(csk->snd_nxt);
req->param = htonl(V_TX_PORT(l2t->smt_idx));
if (!cxgbi_sock_flag(csk, CTPF_TX_DATA_SENT)) {
req->flags |= htonl(V_TX_ACK_PAGES(2) | F_TX_INIT |
V_TX_CPU_IDX(csk->rss_qid));
/* sendbuffer is in units of 32KB. */
req->param |= htonl(V_TX_SNDBUF(csk->snd_win >> 15));
cxgbi_sock_set_flag(csk, CTPF_TX_DATA_SENT);
}
}
/**
* push_tx_frames -- start transmit
* @c3cn: the offloaded connection
* @req_completion: request wr_ack or not
*
* Prepends TX_DATA_WR or CPL_CLOSE_CON_REQ headers to buffers waiting in a
* connection's send queue and sends them on to T3. Must be called with the
* connection's lock held. Returns the amount of send buffer space that was
* freed as a result of sending queued data to T3.
*/
static void arp_failure_skb_discard(struct t3cdev *dev, struct sk_buff *skb)
{
kfree_skb(skb);
}
static int push_tx_frames(struct cxgbi_sock *csk, int req_completion)
{
int total_size = 0;
struct sk_buff *skb;
if (unlikely(csk->state < CTP_ESTABLISHED ||
csk->state == CTP_CLOSE_WAIT_1 || csk->state >= CTP_ABORTING)) {
log_debug(1 << CXGBI_DBG_TOE | 1 << CXGBI_DBG_PDU_TX,
"csk 0x%p,%u,0x%lx,%u, in closing state.\n",
csk, csk->state, csk->flags, csk->tid);
return 0;
}
while (csk->wr_cred && (skb = skb_peek(&csk->write_queue)) != NULL) {
int len = skb->len; /* length before skb_push */
int frags = skb_shinfo(skb)->nr_frags + (len != skb->data_len);
int wrs_needed = skb_wrs[frags];
if (wrs_needed > 1 && len + sizeof(struct tx_data_wr) <= wrlen)
wrs_needed = 1;
WARN_ON(frags >= SKB_WR_LIST_SIZE || wrs_needed < 1);
if (csk->wr_cred < wrs_needed) {
log_debug(1 << CXGBI_DBG_PDU_TX,
"csk 0x%p, skb len %u/%u, frag %u, wr %d<%u.\n",
csk, skb->len, skb->data_len, frags,
wrs_needed, csk->wr_cred);
break;
}
__skb_unlink(skb, &csk->write_queue);
skb->priority = CPL_PRIORITY_DATA;
skb->csum = wrs_needed; /* remember this until the WR_ACK */
csk->wr_cred -= wrs_needed;
csk->wr_una_cred += wrs_needed;
cxgbi_sock_enqueue_wr(csk, skb);
log_debug(1 << CXGBI_DBG_TOE | 1 << CXGBI_DBG_PDU_TX,
"csk 0x%p, enqueue, skb len %u/%u, frag %u, wr %d, "
"left %u, unack %u.\n",
csk, skb->len, skb->data_len, frags, skb->csum,
csk->wr_cred, csk->wr_una_cred);
if (likely(cxgbi_skcb_test_flag(skb, SKCBF_TX_NEED_HDR))) {
if ((req_completion &&
csk->wr_una_cred == wrs_needed) ||
csk->wr_una_cred >= csk->wr_max_cred / 2) {
req_completion = 1;
csk->wr_una_cred = 0;
}
len += cxgbi_ulp_extra_len(cxgbi_skcb_ulp_mode(skb));
make_tx_data_wr(csk, skb, len, req_completion);
csk->snd_nxt += len;
cxgbi_skcb_clear_flag(skb, SKCBF_TX_NEED_HDR);
}
total_size += skb->truesize;
log_debug(1 << CXGBI_DBG_TOE | 1 << CXGBI_DBG_PDU_TX,
"csk 0x%p, tid 0x%x, send skb 0x%p.\n",
csk, csk->tid, skb);
set_arp_failure_handler(skb, arp_failure_skb_discard);
l2t_send(csk->cdev->lldev, skb, csk->l2t);
}
return total_size;
}
/*
* Process a CPL_ACT_ESTABLISH message: -> host
* Updates connection state from an active establish CPL message. Runs with
* the connection lock held.
*/
static inline void free_atid(struct cxgbi_sock *csk)
{
if (cxgbi_sock_flag(csk, CTPF_HAS_ATID)) {
cxgb3_free_atid(csk->cdev->lldev, csk->atid);
cxgbi_sock_clear_flag(csk, CTPF_HAS_ATID);
cxgbi_sock_put(csk);
}
}
static int do_act_establish(struct t3cdev *tdev, struct sk_buff *skb, void *ctx)
{
struct cxgbi_sock *csk = ctx;
struct cpl_act_establish *req = cplhdr(skb);
unsigned int tid = GET_TID(req);
unsigned int atid = G_PASS_OPEN_TID(ntohl(req->tos_tid));
u32 rcv_isn = ntohl(req->rcv_isn); /* real RCV_ISN + 1 */
log_debug(1 << CXGBI_DBG_TOE | 1 << CXGBI_DBG_SOCK,
"atid 0x%x,tid 0x%x, csk 0x%p,%u,0x%lx, isn %u.\n",
atid, atid, csk, csk->state, csk->flags, rcv_isn);
cxgbi_sock_get(csk);
cxgbi_sock_set_flag(csk, CTPF_HAS_TID);
csk->tid = tid;
cxgb3_insert_tid(csk->cdev->lldev, &t3_client, csk, tid);
free_atid(csk);
csk->rss_qid = G_QNUM(ntohs(skb->csum));
spin_lock_bh(&csk->lock);
if (csk->retry_timer.function) {
del_timer(&csk->retry_timer);
csk->retry_timer.function = NULL;
}
if (unlikely(csk->state != CTP_ACTIVE_OPEN))
pr_info("csk 0x%p,%u,0x%lx,%u, got EST.\n",
csk, csk->state, csk->flags, csk->tid);
csk->copied_seq = csk->rcv_wup = csk->rcv_nxt = rcv_isn;
if (csk->rcv_win > (M_RCV_BUFSIZ << 10))
csk->rcv_wup -= csk->rcv_win - (M_RCV_BUFSIZ << 10);
cxgbi_sock_established(csk, ntohl(req->snd_isn), ntohs(req->tcp_opt));
if (unlikely(cxgbi_sock_flag(csk, CTPF_ACTIVE_CLOSE_NEEDED)))
/* upper layer has requested closing */
send_abort_req(csk);
else {
if (skb_queue_len(&csk->write_queue))
push_tx_frames(csk, 1);
cxgbi_conn_tx_open(csk);
}
spin_unlock_bh(&csk->lock);
__kfree_skb(skb);
return 0;
}
/*
* Process a CPL_ACT_OPEN_RPL message: -> host
* Handle active open failures.
*/
static int act_open_rpl_status_to_errno(int status)
{
switch (status) {
case CPL_ERR_CONN_RESET:
return -ECONNREFUSED;
case CPL_ERR_ARP_MISS:
return -EHOSTUNREACH;
case CPL_ERR_CONN_TIMEDOUT:
return -ETIMEDOUT;
case CPL_ERR_TCAM_FULL:
return -ENOMEM;
case CPL_ERR_CONN_EXIST:
return -EADDRINUSE;
default:
return -EIO;
}
}
static void act_open_retry_timer(unsigned long data)
{
struct sk_buff *skb;
struct cxgbi_sock *csk = (struct cxgbi_sock *)data;
log_debug(1 << CXGBI_DBG_TOE | 1 << CXGBI_DBG_SOCK,
"csk 0x%p,%u,0x%lx,%u.\n",
csk, csk->state, csk->flags, csk->tid);
cxgbi_sock_get(csk);
spin_lock_bh(&csk->lock);
skb = alloc_wr(sizeof(struct cpl_act_open_req), 0, GFP_ATOMIC);
if (!skb)
cxgbi_sock_fail_act_open(csk, -ENOMEM);
else {
skb->sk = (struct sock *)csk;
set_arp_failure_handler(skb, act_open_arp_failure);
send_act_open_req(csk, skb, csk->l2t);
}
spin_unlock_bh(&csk->lock);
cxgbi_sock_put(csk);
}
static int do_act_open_rpl(struct t3cdev *tdev, struct sk_buff *skb, void *ctx)
{
struct cxgbi_sock *csk = ctx;
struct cpl_act_open_rpl *rpl = cplhdr(skb);
pr_info("csk 0x%p,%u,0x%lx,%u, status %u, %pI4:%u-%pI4:%u.\n",
csk, csk->state, csk->flags, csk->atid, rpl->status,
&csk->saddr.sin_addr.s_addr, ntohs(csk->saddr.sin_port),
&csk->daddr.sin_addr.s_addr, ntohs(csk->daddr.sin_port));
if (rpl->status != CPL_ERR_TCAM_FULL &&
rpl->status != CPL_ERR_CONN_EXIST &&
rpl->status != CPL_ERR_ARP_MISS)
cxgb3_queue_tid_release(tdev, GET_TID(rpl));
cxgbi_sock_get(csk);
spin_lock_bh(&csk->lock);
if (rpl->status == CPL_ERR_CONN_EXIST &&
csk->retry_timer.function != act_open_retry_timer) {
csk->retry_timer.function = act_open_retry_timer;
mod_timer(&csk->retry_timer, jiffies + HZ / 2);
} else
cxgbi_sock_fail_act_open(csk,
act_open_rpl_status_to_errno(rpl->status));
spin_unlock_bh(&csk->lock);
cxgbi_sock_put(csk);
__kfree_skb(skb);
return 0;
}
/*
* Process PEER_CLOSE CPL messages: -> host
* Handle peer FIN.
*/
static int do_peer_close(struct t3cdev *cdev, struct sk_buff *skb, void *ctx)
{
struct cxgbi_sock *csk = ctx;
log_debug(1 << CXGBI_DBG_TOE | 1 << CXGBI_DBG_SOCK,
"csk 0x%p,%u,0x%lx,%u.\n",
csk, csk->state, csk->flags, csk->tid);
cxgbi_sock_rcv_peer_close(csk);
__kfree_skb(skb);
return 0;
}
/*
* Process CLOSE_CONN_RPL CPL message: -> host
* Process a peer ACK to our FIN.
*/
static int do_close_con_rpl(struct t3cdev *cdev, struct sk_buff *skb,
void *ctx)
{
struct cxgbi_sock *csk = ctx;
struct cpl_close_con_rpl *rpl = cplhdr(skb);
log_debug(1 << CXGBI_DBG_TOE | 1 << CXGBI_DBG_SOCK,
"csk 0x%p,%u,0x%lx,%u, snxt %u.\n",
csk, csk->state, csk->flags, csk->tid, ntohl(rpl->snd_nxt));
cxgbi_sock_rcv_close_conn_rpl(csk, ntohl(rpl->snd_nxt));
__kfree_skb(skb);
return 0;
}
/*
* Process ABORT_REQ_RSS CPL message: -> host
* Process abort requests. If we are waiting for an ABORT_RPL we ignore this
* request except that we need to reply to it.
*/
static int abort_status_to_errno(struct cxgbi_sock *csk, int abort_reason,
int *need_rst)
{
switch (abort_reason) {
case CPL_ERR_BAD_SYN: /* fall through */
case CPL_ERR_CONN_RESET:
return csk->state > CTP_ESTABLISHED ? -EPIPE : -ECONNRESET;
case CPL_ERR_XMIT_TIMEDOUT:
case CPL_ERR_PERSIST_TIMEDOUT:
case CPL_ERR_FINWAIT2_TIMEDOUT:
case CPL_ERR_KEEPALIVE_TIMEDOUT:
return -ETIMEDOUT;
default:
return -EIO;
}
}
static int do_abort_req(struct t3cdev *cdev, struct sk_buff *skb, void *ctx)
{
const struct cpl_abort_req_rss *req = cplhdr(skb);
struct cxgbi_sock *csk = ctx;
int rst_status = CPL_ABORT_NO_RST;
log_debug(1 << CXGBI_DBG_TOE | 1 << CXGBI_DBG_SOCK,
"csk 0x%p,%u,0x%lx,%u.\n",
csk, csk->state, csk->flags, csk->tid);
if (req->status == CPL_ERR_RTX_NEG_ADVICE ||
req->status == CPL_ERR_PERSIST_NEG_ADVICE) {
goto done;
}
cxgbi_sock_get(csk);
spin_lock_bh(&csk->lock);
if (!cxgbi_sock_flag(csk, CTPF_ABORT_REQ_RCVD)) {
cxgbi_sock_set_flag(csk, CTPF_ABORT_REQ_RCVD);
cxgbi_sock_set_state(csk, CTP_ABORTING);
goto out;
}
cxgbi_sock_clear_flag(csk, CTPF_ABORT_REQ_RCVD);
send_abort_rpl(csk, rst_status);
if (!cxgbi_sock_flag(csk, CTPF_ABORT_RPL_PENDING)) {
csk->err = abort_status_to_errno(csk, req->status, &rst_status);
cxgbi_sock_closed(csk);
}
out:
spin_unlock_bh(&csk->lock);
cxgbi_sock_put(csk);
done:
__kfree_skb(skb);
return 0;
}
/*
* Process ABORT_RPL_RSS CPL message: -> host
* Process abort replies. We only process these messages if we anticipate
* them as the coordination between SW and HW in this area is somewhat lacking
* and sometimes we get ABORT_RPLs after we are done with the connection that
* originated the ABORT_REQ.
*/
static int do_abort_rpl(struct t3cdev *cdev, struct sk_buff *skb, void *ctx)
{
struct cpl_abort_rpl_rss *rpl = cplhdr(skb);
struct cxgbi_sock *csk = ctx;
log_debug(1 << CXGBI_DBG_TOE | 1 << CXGBI_DBG_SOCK,
"status 0x%x, csk 0x%p, s %u, 0x%lx.\n",
rpl->status, csk, csk ? csk->state : 0,
csk ? csk->flags : 0UL);
/*
* Ignore replies to post-close aborts indicating that the abort was
* requested too late. These connections are terminated when we get
* PEER_CLOSE or CLOSE_CON_RPL and by the time the abort_rpl_rss
* arrives the TID is either no longer used or it has been recycled.
*/
if (rpl->status == CPL_ERR_ABORT_FAILED)
goto rel_skb;
/*
* Sometimes we've already closed the connection, e.g., a post-close
* abort races with ABORT_REQ_RSS, the latter frees the connection
* expecting the ABORT_REQ will fail with CPL_ERR_ABORT_FAILED,
* but FW turns the ABORT_REQ into a regular one and so we get
* ABORT_RPL_RSS with status 0 and no connection.
*/
if (csk)
cxgbi_sock_rcv_abort_rpl(csk);
rel_skb:
__kfree_skb(skb);
return 0;
}
/*
* Process RX_ISCSI_HDR CPL message: -> host
* Handle received PDUs, the payload could be DDP'ed. If not, the payload
* follow after the bhs.
*/
static int do_iscsi_hdr(struct t3cdev *t3dev, struct sk_buff *skb, void *ctx)
{
struct cxgbi_sock *csk = ctx;
struct cpl_iscsi_hdr *hdr_cpl = cplhdr(skb);
struct cpl_iscsi_hdr_norss data_cpl;
struct cpl_rx_data_ddp_norss ddp_cpl;
unsigned int hdr_len, data_len, status;
unsigned int len;
int err;
log_debug(1 << CXGBI_DBG_TOE | 1 << CXGBI_DBG_PDU_RX,
"csk 0x%p,%u,0x%lx,%u, skb 0x%p,%u.\n",
csk, csk->state, csk->flags, csk->tid, skb, skb->len);
spin_lock_bh(&csk->lock);
if (unlikely(csk->state >= CTP_PASSIVE_CLOSE)) {
log_debug(1 << CXGBI_DBG_TOE | 1 << CXGBI_DBG_SOCK,
"csk 0x%p,%u,0x%lx,%u, bad state.\n",
csk, csk->state, csk->flags, csk->tid);
if (csk->state != CTP_ABORTING)
goto abort_conn;
else
goto discard;
}
cxgbi_skcb_tcp_seq(skb) = ntohl(hdr_cpl->seq);
cxgbi_skcb_flags(skb) = 0;
skb_reset_transport_header(skb);
__skb_pull(skb, sizeof(struct cpl_iscsi_hdr));
len = hdr_len = ntohs(hdr_cpl->len);
/* msg coalesce is off or not enough data received */
if (skb->len <= hdr_len) {
pr_err("%s: tid %u, CPL_ISCSI_HDR, skb len %u < %u.\n",
csk->cdev->ports[csk->port_id]->name, csk->tid,
skb->len, hdr_len);
goto abort_conn;
}
cxgbi_skcb_set_flag(skb, SKCBF_RX_COALESCED);
err = skb_copy_bits(skb, skb->len - sizeof(ddp_cpl), &ddp_cpl,
sizeof(ddp_cpl));
if (err < 0) {
pr_err("%s: tid %u, copy cpl_ddp %u-%zu failed %d.\n",
csk->cdev->ports[csk->port_id]->name, csk->tid,
skb->len, sizeof(ddp_cpl), err);
goto abort_conn;
}
cxgbi_skcb_set_flag(skb, SKCBF_RX_STATUS);
cxgbi_skcb_rx_pdulen(skb) = ntohs(ddp_cpl.len);
cxgbi_skcb_rx_ddigest(skb) = ntohl(ddp_cpl.ulp_crc);
status = ntohl(ddp_cpl.ddp_status);
log_debug(1 << CXGBI_DBG_TOE | 1 << CXGBI_DBG_PDU_RX,
"csk 0x%p, skb 0x%p,%u, pdulen %u, status 0x%x.\n",
csk, skb, skb->len, cxgbi_skcb_rx_pdulen(skb), status);
if (status & (1 << CPL_RX_DDP_STATUS_HCRC_SHIFT))
cxgbi_skcb_set_flag(skb, SKCBF_RX_HCRC_ERR);
if (status & (1 << CPL_RX_DDP_STATUS_DCRC_SHIFT))
cxgbi_skcb_set_flag(skb, SKCBF_RX_DCRC_ERR);
if (status & (1 << CPL_RX_DDP_STATUS_PAD_SHIFT))
cxgbi_skcb_set_flag(skb, SKCBF_RX_PAD_ERR);
if (skb->len > (hdr_len + sizeof(ddp_cpl))) {
err = skb_copy_bits(skb, hdr_len, &data_cpl, sizeof(data_cpl));
if (err < 0) {
pr_err("%s: tid %u, cp %zu/%u failed %d.\n",
csk->cdev->ports[csk->port_id]->name,
csk->tid, sizeof(data_cpl), skb->len, err);
goto abort_conn;
}
data_len = ntohs(data_cpl.len);
log_debug(1 << CXGBI_DBG_DDP | 1 << CXGBI_DBG_PDU_RX,
"skb 0x%p, pdu not ddp'ed %u/%u, status 0x%x.\n",
skb, data_len, cxgbi_skcb_rx_pdulen(skb), status);
len += sizeof(data_cpl) + data_len;
} else if (status & (1 << CPL_RX_DDP_STATUS_DDP_SHIFT))
cxgbi_skcb_set_flag(skb, SKCBF_RX_DATA_DDPD);
csk->rcv_nxt = ntohl(ddp_cpl.seq) + cxgbi_skcb_rx_pdulen(skb);
__pskb_trim(skb, len);
__skb_queue_tail(&csk->receive_queue, skb);
cxgbi_conn_pdu_ready(csk);
spin_unlock_bh(&csk->lock);
return 0;
abort_conn:
send_abort_req(csk);
discard:
spin_unlock_bh(&csk->lock);
__kfree_skb(skb);
return 0;
}
/*
* Process TX_DATA_ACK CPL messages: -> host
* Process an acknowledgment of WR completion. Advance snd_una and send the
* next batch of work requests from the write queue.
*/
static int do_wr_ack(struct t3cdev *cdev, struct sk_buff *skb, void *ctx)
{
struct cxgbi_sock *csk = ctx;
struct cpl_wr_ack *hdr = cplhdr(skb);
log_debug(1 << CXGBI_DBG_TOE | 1 << CXGBI_DBG_PDU_RX,
"csk 0x%p,%u,0x%lx,%u, cr %u.\n",
csk, csk->state, csk->flags, csk->tid, ntohs(hdr->credits));
cxgbi_sock_rcv_wr_ack(csk, ntohs(hdr->credits), ntohl(hdr->snd_una), 1);
__kfree_skb(skb);
return 0;
}
/*
* for each connection, pre-allocate skbs needed for close/abort requests. So
* that we can service the request right away.
*/
static int alloc_cpls(struct cxgbi_sock *csk)
{
csk->cpl_close = alloc_wr(sizeof(struct cpl_close_con_req), 0,
GFP_KERNEL);
if (!csk->cpl_close)
return -ENOMEM;
csk->cpl_abort_req = alloc_wr(sizeof(struct cpl_abort_req), 0,
GFP_KERNEL);
if (!csk->cpl_abort_req)
goto free_cpl_skbs;
csk->cpl_abort_rpl = alloc_wr(sizeof(struct cpl_abort_rpl), 0,
GFP_KERNEL);
if (!csk->cpl_abort_rpl)
goto free_cpl_skbs;
return 0;
free_cpl_skbs:
cxgbi_sock_free_cpl_skbs(csk);
return -ENOMEM;
}
/**
* release_offload_resources - release offload resource
* @c3cn: the offloaded iscsi tcp connection.
* Release resources held by an offload connection (TID, L2T entry, etc.)
*/
static void l2t_put(struct cxgbi_sock *csk)
{
struct t3cdev *t3dev = (struct t3cdev *)csk->cdev->lldev;
if (csk->l2t) {
[SCSI] cxgb3i: convert cdev->l2opt to use rcu to prevent NULL dereference This oops was reported recently: d:mon> e cpu 0xd: Vector: 300 (Data Access) at [c0000000fd4c7120] pc: d00000000076f194: .t3_l2t_get+0x44/0x524 [cxgb3] lr: d000000000b02108: .init_act_open+0x150/0x3d4 [cxgb3i] sp: c0000000fd4c73a0 msr: 8000000000009032 dar: 0 dsisr: 40000000 current = 0xc0000000fd640d40 paca = 0xc00000000054ff80 pid = 5085, comm = iscsid d:mon> t [c0000000fd4c7450] d000000000b02108 .init_act_open+0x150/0x3d4 [cxgb3i] [c0000000fd4c7500] d000000000e45378 .cxgbi_ep_connect+0x784/0x8e8 [libcxgbi] [c0000000fd4c7650] d000000000db33f0 .iscsi_if_rx+0x71c/0xb18 [scsi_transport_iscsi2] [c0000000fd4c7740] c000000000370c9c .netlink_data_ready+0x40/0xa4 [c0000000fd4c77c0] c00000000036f010 .netlink_sendskb+0x4c/0x9c [c0000000fd4c7850] c000000000370c18 .netlink_sendmsg+0x358/0x39c [c0000000fd4c7950] c00000000033be24 .sock_sendmsg+0x114/0x1b8 [c0000000fd4c7b50] c00000000033d208 .sys_sendmsg+0x218/0x2ac [c0000000fd4c7d70] c00000000033f55c .sys_socketcall+0x228/0x27c [c0000000fd4c7e30] c0000000000086a4 syscall_exit+0x0/0x40 --- Exception: c01 (System Call) at 00000080da560cfc The root cause was an EEH error, which sent us down the offload_close path in the cxgb3 driver, which in turn sets cdev->l2opt to NULL, without regard for upper layer driver (like the cxgbi drivers) which might have execution contexts in the middle of its use. The result is the oops above, when t3_l2t_get attempts to dereference L2DATA(cdev)->nentries in arp_hash right after the EEH error handler sets it to NULL. The fix is to prevent the setting of the NULL pointer until after there are no further users of it. The t3cdev->l2opt pointer is now converted to be an rcu pointer and the L2DATA macro is now called under the protection of the rcu_read_lock(). When the EEH error path: t3_adapter_error->offload_close->cxgb3_offload_deactivate Is exectured, setting of that l2opt pointer to NULL, is now gated on an rcu quiescence point, preventing, allowing L2DATA callers to safely check for a NULL pointer without concern that the underlying data will be freeded before the pointer is dereferenced. This has been tested by the reporter and shown to fix the reproted oops [nhorman: fix up unitinialised variable reported by Dan Carpenter] Signed-off-by: Neil Horman <nhorman@tuxdriver.com> Reviewed-by: Karen Xie <kxie@chelsio.com> Cc: stable@kernel.org Signed-off-by: James Bottomley <JBottomley@Parallels.com>
2011-09-06 11:59:13 -06:00
l2t_release(t3dev, csk->l2t);
csk->l2t = NULL;
cxgbi_sock_put(csk);
}
}
static void release_offload_resources(struct cxgbi_sock *csk)
{
struct t3cdev *t3dev = (struct t3cdev *)csk->cdev->lldev;
log_debug(1 << CXGBI_DBG_TOE | 1 << CXGBI_DBG_SOCK,
"csk 0x%p,%u,0x%lx,%u.\n",
csk, csk->state, csk->flags, csk->tid);
csk->rss_qid = 0;
cxgbi_sock_free_cpl_skbs(csk);
if (csk->wr_cred != csk->wr_max_cred) {
cxgbi_sock_purge_wr_queue(csk);
cxgbi_sock_reset_wr_list(csk);
}
l2t_put(csk);
if (cxgbi_sock_flag(csk, CTPF_HAS_ATID))
free_atid(csk);
else if (cxgbi_sock_flag(csk, CTPF_HAS_TID)) {
cxgb3_remove_tid(t3dev, (void *)csk, csk->tid);
cxgbi_sock_clear_flag(csk, CTPF_HAS_TID);
cxgbi_sock_put(csk);
}
csk->dst = NULL;
csk->cdev = NULL;
}
static void update_address(struct cxgbi_hba *chba)
{
if (chba->ipv4addr) {
if (chba->vdev &&
chba->ipv4addr != cxgb3i_get_private_ipv4addr(chba->vdev)) {
cxgb3i_set_private_ipv4addr(chba->vdev, chba->ipv4addr);
cxgb3i_set_private_ipv4addr(chba->ndev, 0);
pr_info("%s set %pI4.\n",
chba->vdev->name, &chba->ipv4addr);
} else if (chba->ipv4addr !=
cxgb3i_get_private_ipv4addr(chba->ndev)) {
cxgb3i_set_private_ipv4addr(chba->ndev, chba->ipv4addr);
pr_info("%s set %pI4.\n",
chba->ndev->name, &chba->ipv4addr);
}
} else if (cxgb3i_get_private_ipv4addr(chba->ndev)) {
if (chba->vdev)
cxgb3i_set_private_ipv4addr(chba->vdev, 0);
cxgb3i_set_private_ipv4addr(chba->ndev, 0);
}
}
static int init_act_open(struct cxgbi_sock *csk)
{
struct dst_entry *dst = csk->dst;
struct cxgbi_device *cdev = csk->cdev;
struct t3cdev *t3dev = (struct t3cdev *)cdev->lldev;
struct net_device *ndev = cdev->ports[csk->port_id];
struct cxgbi_hba *chba = cdev->hbas[csk->port_id];
struct sk_buff *skb = NULL;
log_debug(1 << CXGBI_DBG_TOE | 1 << CXGBI_DBG_SOCK,
"csk 0x%p,%u,0x%lx.\n", csk, csk->state, csk->flags);
update_address(chba);
if (chba->ipv4addr)
csk->saddr.sin_addr.s_addr = chba->ipv4addr;
csk->rss_qid = 0;
csk->l2t = t3_l2t_get(t3dev, dst, ndev,
&csk->daddr.sin_addr.s_addr);
if (!csk->l2t) {
pr_err("NO l2t available.\n");
return -EINVAL;
}
cxgbi_sock_get(csk);
csk->atid = cxgb3_alloc_atid(t3dev, &t3_client, csk);
if (csk->atid < 0) {
pr_err("NO atid available.\n");
goto rel_resource;
}
cxgbi_sock_set_flag(csk, CTPF_HAS_ATID);
cxgbi_sock_get(csk);
skb = alloc_wr(sizeof(struct cpl_act_open_req), 0, GFP_KERNEL);
if (!skb)
goto rel_resource;
skb->sk = (struct sock *)csk;
set_arp_failure_handler(skb, act_open_arp_failure);
csk->snd_win = cxgb3i_snd_win;
csk->rcv_win = cxgb3i_rcv_win;
csk->wr_max_cred = csk->wr_cred = T3C_DATA(t3dev)->max_wrs - 1;
csk->wr_una_cred = 0;
csk->mss_idx = cxgbi_sock_select_mss(csk, dst_mtu(dst));
cxgbi_sock_reset_wr_list(csk);
csk->err = 0;
log_debug(1 << CXGBI_DBG_TOE | 1 << CXGBI_DBG_SOCK,
"csk 0x%p,%u,0x%lx, %pI4:%u-%pI4:%u.\n",
csk, csk->state, csk->flags,
&csk->saddr.sin_addr.s_addr, ntohs(csk->saddr.sin_port),
&csk->daddr.sin_addr.s_addr, ntohs(csk->daddr.sin_port));
cxgbi_sock_set_state(csk, CTP_ACTIVE_OPEN);
send_act_open_req(csk, skb, csk->l2t);
return 0;
rel_resource:
if (skb)
__kfree_skb(skb);
return -EINVAL;
}
cxgb3_cpl_handler_func cxgb3i_cpl_handlers[NUM_CPL_CMDS] = {
[CPL_ACT_ESTABLISH] = do_act_establish,
[CPL_ACT_OPEN_RPL] = do_act_open_rpl,
[CPL_PEER_CLOSE] = do_peer_close,
[CPL_ABORT_REQ_RSS] = do_abort_req,
[CPL_ABORT_RPL_RSS] = do_abort_rpl,
[CPL_CLOSE_CON_RPL] = do_close_con_rpl,
[CPL_TX_DMA_ACK] = do_wr_ack,
[CPL_ISCSI_HDR] = do_iscsi_hdr,
};
/**
* cxgb3i_ofld_init - allocate and initialize resources for each adapter found
* @cdev: cxgbi adapter
*/
static int cxgb3i_ofld_init(struct cxgbi_device *cdev)
{
struct t3cdev *t3dev = (struct t3cdev *)cdev->lldev;
struct adap_ports port;
struct ofld_page_info rx_page_info;
unsigned int wr_len;
int rc;
if (t3dev->ctl(t3dev, GET_WR_LEN, &wr_len) < 0 ||
t3dev->ctl(t3dev, GET_PORTS, &port) < 0 ||
t3dev->ctl(t3dev, GET_RX_PAGE_INFO, &rx_page_info) < 0) {
pr_warn("t3 0x%p, offload up, ioctl failed.\n", t3dev);
return -EINVAL;
}
if (cxgb3i_max_connect > CXGBI_MAX_CONN)
cxgb3i_max_connect = CXGBI_MAX_CONN;
rc = cxgbi_device_portmap_create(cdev, cxgb3i_sport_base,
cxgb3i_max_connect);
if (rc < 0)
return rc;
init_wr_tab(wr_len);
cdev->csk_release_offload_resources = release_offload_resources;
cdev->csk_push_tx_frames = push_tx_frames;
cdev->csk_send_abort_req = send_abort_req;
cdev->csk_send_close_req = send_close_req;
cdev->csk_send_rx_credits = send_rx_credits;
cdev->csk_alloc_cpls = alloc_cpls;
cdev->csk_init_act_open = init_act_open;
pr_info("cdev 0x%p, offload up, added.\n", cdev);
return 0;
}
/*
* functions to program the pagepod in h/w
*/
static inline void ulp_mem_io_set_hdr(struct sk_buff *skb, unsigned int addr)
{
struct ulp_mem_io *req = (struct ulp_mem_io *)skb->head;
memset(req, 0, sizeof(*req));
req->wr.wr_hi = htonl(V_WR_OP(FW_WROPCODE_BYPASS));
req->cmd_lock_addr = htonl(V_ULP_MEMIO_ADDR(addr >> 5) |
V_ULPTX_CMD(ULP_MEM_WRITE));
req->len = htonl(V_ULP_MEMIO_DATA_LEN(IPPOD_SIZE >> 5) |
V_ULPTX_NFLITS((IPPOD_SIZE >> 3) + 1));
}
static struct cxgbi_ppm *cdev2ppm(struct cxgbi_device *cdev)
{
return ((struct t3cdev *)cdev->lldev)->ulp_iscsi;
}
static int ddp_set_map(struct cxgbi_ppm *ppm, struct cxgbi_sock *csk,
struct cxgbi_task_tag_info *ttinfo)
{
unsigned int idx = ttinfo->idx;
unsigned int npods = ttinfo->npods;
struct scatterlist *sg = ttinfo->sgl;
struct cxgbi_pagepod *ppod;
struct ulp_mem_io *req;
unsigned int sg_off;
unsigned int pm_addr = (idx << PPOD_SIZE_SHIFT) + ppm->llimit;
int i;
for (i = 0; i < npods; i++, idx++, pm_addr += IPPOD_SIZE) {
struct sk_buff *skb = alloc_wr(sizeof(struct ulp_mem_io) +
IPPOD_SIZE, 0, GFP_ATOMIC);
if (!skb)
return -ENOMEM;
ulp_mem_io_set_hdr(skb, pm_addr);
req = (struct ulp_mem_io *)skb->head;
ppod = (struct cxgbi_pagepod *)(req + 1);
sg_off = i * PPOD_PAGES_MAX;
cxgbi_ddp_set_one_ppod(ppod, ttinfo, &sg,
&sg_off);
skb->priority = CPL_PRIORITY_CONTROL;
cxgb3_ofld_send(ppm->lldev, skb);
}
return 0;
}
static void ddp_clear_map(struct cxgbi_device *cdev, struct cxgbi_ppm *ppm,
struct cxgbi_task_tag_info *ttinfo)
{
unsigned int idx = ttinfo->idx;
unsigned int pm_addr = (idx << PPOD_SIZE_SHIFT) + ppm->llimit;
unsigned int npods = ttinfo->npods;
int i;
log_debug(1 << CXGBI_DBG_DDP,
"cdev 0x%p, clear idx %u, npods %u.\n",
cdev, idx, npods);
for (i = 0; i < npods; i++, idx++, pm_addr += IPPOD_SIZE) {
struct sk_buff *skb = alloc_wr(sizeof(struct ulp_mem_io) +
IPPOD_SIZE, 0, GFP_ATOMIC);
if (!skb) {
pr_err("cdev 0x%p, clear ddp, %u,%d/%u, skb OOM.\n",
cdev, idx, i, npods);
continue;
}
ulp_mem_io_set_hdr(skb, pm_addr);
skb->priority = CPL_PRIORITY_CONTROL;
cxgb3_ofld_send(ppm->lldev, skb);
}
}
static int ddp_setup_conn_pgidx(struct cxgbi_sock *csk,
unsigned int tid, int pg_idx, bool reply)
{
struct sk_buff *skb = alloc_wr(sizeof(struct cpl_set_tcb_field), 0,
GFP_KERNEL);
struct cpl_set_tcb_field *req;
u64 val = pg_idx < DDP_PGIDX_MAX ? pg_idx : 0;
log_debug(1 << CXGBI_DBG_DDP,
"csk 0x%p, tid %u, pg_idx %d.\n", csk, tid, pg_idx);
if (!skb)
return -ENOMEM;
/* set up ulp submode and page size */
req = (struct cpl_set_tcb_field *)skb->head;
req->wr.wr_hi = htonl(V_WR_OP(FW_WROPCODE_FORWARD));
OPCODE_TID(req) = htonl(MK_OPCODE_TID(CPL_SET_TCB_FIELD, tid));
req->reply = V_NO_REPLY(reply ? 0 : 1);
req->cpu_idx = 0;
req->word = htons(31);
req->mask = cpu_to_be64(0xF0000000);
req->val = cpu_to_be64(val << 28);
skb->priority = CPL_PRIORITY_CONTROL;
cxgb3_ofld_send(csk->cdev->lldev, skb);
return 0;
}
/**
* cxgb3i_setup_conn_digest - setup conn. digest setting
* @csk: cxgb tcp socket
* @tid: connection id
* @hcrc: header digest enabled
* @dcrc: data digest enabled
* @reply: request reply from h/w
* set up the iscsi digest settings for a connection identified by tid
*/
static int ddp_setup_conn_digest(struct cxgbi_sock *csk, unsigned int tid,
int hcrc, int dcrc, int reply)
{
struct sk_buff *skb = alloc_wr(sizeof(struct cpl_set_tcb_field), 0,
GFP_KERNEL);
struct cpl_set_tcb_field *req;
u64 val = (hcrc ? 1 : 0) | (dcrc ? 2 : 0);
log_debug(1 << CXGBI_DBG_DDP,
"csk 0x%p, tid %u, crc %d,%d.\n", csk, tid, hcrc, dcrc);
if (!skb)
return -ENOMEM;
/* set up ulp submode and page size */
req = (struct cpl_set_tcb_field *)skb->head;
req->wr.wr_hi = htonl(V_WR_OP(FW_WROPCODE_FORWARD));
OPCODE_TID(req) = htonl(MK_OPCODE_TID(CPL_SET_TCB_FIELD, tid));
req->reply = V_NO_REPLY(reply ? 0 : 1);
req->cpu_idx = 0;
req->word = htons(31);
req->mask = cpu_to_be64(0x0F000000);
req->val = cpu_to_be64(val << 24);
skb->priority = CPL_PRIORITY_CONTROL;
cxgb3_ofld_send(csk->cdev->lldev, skb);
return 0;
}
/**
* cxgb3i_ddp_init - initialize the cxgb3 adapter's ddp resource
* @cdev: cxgb3i adapter
* initialize the ddp pagepod manager for a given adapter
*/
static int cxgb3i_ddp_init(struct cxgbi_device *cdev)
{
struct t3cdev *tdev = (struct t3cdev *)cdev->lldev;
struct net_device *ndev = cdev->ports[0];
struct cxgbi_tag_format tformat;
unsigned int ppmax, tagmask = 0;
struct ulp_iscsi_info uinfo;
int i, err;
err = tdev->ctl(tdev, ULP_ISCSI_GET_PARAMS, &uinfo);
if (err < 0) {
pr_err("%s, failed to get iscsi param %d.\n",
ndev->name, err);
return err;
}
if (uinfo.llimit >= uinfo.ulimit) {
pr_warn("T3 %s, iscsi NOT enabled %u ~ %u!\n",
ndev->name, uinfo.llimit, uinfo.ulimit);
return -EACCES;
}
ppmax = (uinfo.ulimit - uinfo.llimit + 1) >> PPOD_SIZE_SHIFT;
tagmask = cxgbi_tagmask_set(ppmax);
pr_info("T3 %s: 0x%x~0x%x, 0x%x, tagmask 0x%x -> 0x%x.\n",
ndev->name, uinfo.llimit, uinfo.ulimit, ppmax, uinfo.tagmask,
tagmask);
memset(&tformat, 0, sizeof(struct cxgbi_tag_format));
for (i = 0; i < 4; i++)
tformat.pgsz_order[i] = uinfo.pgsz_factor[i];
cxgbi_tagmask_check(tagmask, &tformat);
cxgbi_ddp_ppm_setup(&tdev->ulp_iscsi, cdev, &tformat, ppmax,
uinfo.llimit, uinfo.llimit, 0);
if (!(cdev->flags & CXGBI_FLAG_DDP_OFF)) {
uinfo.tagmask = tagmask;
uinfo.ulimit = uinfo.llimit + (ppmax << PPOD_SIZE_SHIFT);
err = tdev->ctl(tdev, ULP_ISCSI_SET_PARAMS, &uinfo);
if (err < 0) {
pr_err("T3 %s fail to set iscsi param %d.\n",
ndev->name, err);
cdev->flags |= CXGBI_FLAG_DDP_OFF;
}
err = 0;
}
cdev->csk_ddp_setup_digest = ddp_setup_conn_digest;
cdev->csk_ddp_setup_pgidx = ddp_setup_conn_pgidx;
cdev->csk_ddp_set_map = ddp_set_map;
cdev->csk_ddp_clear_map = ddp_clear_map;
cdev->cdev2ppm = cdev2ppm;
cdev->tx_max_size = min_t(unsigned int, ULP2_MAX_PDU_PAYLOAD,
uinfo.max_txsz - ISCSI_PDU_NONPAYLOAD_LEN);
cdev->rx_max_size = min_t(unsigned int, ULP2_MAX_PDU_PAYLOAD,
uinfo.max_rxsz - ISCSI_PDU_NONPAYLOAD_LEN);
return 0;
}
static void cxgb3i_dev_close(struct t3cdev *t3dev)
{
struct cxgbi_device *cdev = cxgbi_device_find_by_lldev(t3dev);
if (!cdev || cdev->flags & CXGBI_FLAG_ADAPTER_RESET) {
pr_info("0x%p close, f 0x%x.\n", cdev, cdev ? cdev->flags : 0);
return;
}
cxgbi_device_unregister(cdev);
}
/**
* cxgb3i_dev_open - init a t3 adapter structure and any h/w settings
* @t3dev: t3cdev adapter
*/
static void cxgb3i_dev_open(struct t3cdev *t3dev)
{
struct cxgbi_device *cdev = cxgbi_device_find_by_lldev(t3dev);
struct adapter *adapter = tdev2adap(t3dev);
int i, err;
if (cdev) {
pr_info("0x%p, updating.\n", cdev);
return;
}
cdev = cxgbi_device_register(0, adapter->params.nports);
if (!cdev) {
pr_warn("device 0x%p register failed.\n", t3dev);
return;
}
cdev->flags = CXGBI_FLAG_DEV_T3 | CXGBI_FLAG_IPV4_SET;
cdev->lldev = t3dev;
cdev->pdev = adapter->pdev;
cdev->ports = adapter->port;
cdev->nports = adapter->params.nports;
cdev->mtus = adapter->params.mtus;
cdev->nmtus = NMTUS;
cdev->rx_credit_thres = cxgb3i_rx_credit_thres;
cdev->skb_tx_rsvd = CXGB3I_TX_HEADER_LEN;
cdev->skb_rx_extra = sizeof(struct cpl_iscsi_hdr_norss);
cdev->itp = &cxgb3i_iscsi_transport;
err = cxgb3i_ddp_init(cdev);
if (err) {
pr_info("0x%p ddp init failed\n", cdev);
goto err_out;
}
err = cxgb3i_ofld_init(cdev);
if (err) {
pr_info("0x%p offload init failed\n", cdev);
goto err_out;
}
err = cxgbi_hbas_add(cdev, CXGB3I_MAX_LUN, CXGBI_MAX_CONN,
&cxgb3i_host_template, cxgb3i_stt);
if (err)
goto err_out;
for (i = 0; i < cdev->nports; i++)
cdev->hbas[i]->ipv4addr =
cxgb3i_get_private_ipv4addr(cdev->ports[i]);
pr_info("cdev 0x%p, f 0x%x, t3dev 0x%p open, err %d.\n",
cdev, cdev ? cdev->flags : 0, t3dev, err);
return;
err_out:
cxgbi_device_unregister(cdev);
}
static void cxgb3i_dev_event_handler(struct t3cdev *t3dev, u32 event, u32 port)
{
struct cxgbi_device *cdev = cxgbi_device_find_by_lldev(t3dev);
log_debug(1 << CXGBI_DBG_TOE,
"0x%p, cdev 0x%p, event 0x%x, port 0x%x.\n",
t3dev, cdev, event, port);
if (!cdev)
return;
switch (event) {
case OFFLOAD_STATUS_DOWN:
cdev->flags |= CXGBI_FLAG_ADAPTER_RESET;
break;
case OFFLOAD_STATUS_UP:
cdev->flags &= ~CXGBI_FLAG_ADAPTER_RESET;
break;
}
}
/**
* cxgb3i_init_module - module init entry point
*
* initialize any driver wide global data structures and register itself
* with the cxgb3 module
*/
static int __init cxgb3i_init_module(void)
{
int rc;
printk(KERN_INFO "%s", version);
rc = cxgbi_iscsi_init(&cxgb3i_iscsi_transport, &cxgb3i_stt);
if (rc < 0)
return rc;
cxgb3_register_client(&t3_client);
return 0;
}
/**
* cxgb3i_exit_module - module cleanup/exit entry point
*
* go through the driver hba list and for each hba, release any resource held.
* and unregisters iscsi transport and the cxgb3 module
*/
static void __exit cxgb3i_exit_module(void)
{
cxgb3_unregister_client(&t3_client);
cxgbi_device_unregister_all(CXGBI_FLAG_DEV_T3);
cxgbi_iscsi_cleanup(&cxgb3i_iscsi_transport, &cxgb3i_stt);
}
module_init(cxgb3i_init_module);
module_exit(cxgb3i_exit_module);