📄 libiscsi.c
字号:
/* * iSCSI lib functions * * Copyright (C) 2006 Red Hat, Inc. All rights reserved. * Copyright (C) 2004 - 2006 Mike Christie * Copyright (C) 2004 - 2005 Dmitry Yusupov * Copyright (C) 2004 - 2005 Alex Aizman * maintained by open-iscsi@googlegroups.com * * 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; either version 2 of the License, or * (at your option) any later version. * * 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 * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */#include <linux/types.h>#include <linux/kfifo.h>#include <linux/delay.h>#include <asm/unaligned.h>#include <net/tcp.h>#include <scsi/scsi_cmnd.h>#include <scsi/scsi_device.h>#include <scsi/scsi_eh.h>#include <scsi/scsi_tcq.h>#include <scsi/scsi_host.h>#include <scsi/scsi.h>#include <scsi/iscsi_proto.h>#include <scsi/scsi_transport.h>#include <scsi/scsi_transport_iscsi.h>#include <scsi/libiscsi.h>struct iscsi_session *class_to_transport_session(struct iscsi_cls_session *cls_session){ struct Scsi_Host *shost = iscsi_session_to_shost(cls_session); return iscsi_hostdata(shost->hostdata);}EXPORT_SYMBOL_GPL(class_to_transport_session);/* Serial Number Arithmetic, 32 bits, less than, RFC1982 */#define SNA32_CHECK 2147483648ULstatic int iscsi_sna_lt(u32 n1, u32 n2){ return n1 != n2 && ((n1 < n2 && (n2 - n1 < SNA32_CHECK)) || (n1 > n2 && (n2 - n1 < SNA32_CHECK)));}/* Serial Number Arithmetic, 32 bits, less than, RFC1982 */static int iscsi_sna_lte(u32 n1, u32 n2){ return n1 == n2 || ((n1 < n2 && (n2 - n1 < SNA32_CHECK)) || (n1 > n2 && (n2 - n1 < SNA32_CHECK)));}voidiscsi_update_cmdsn(struct iscsi_session *session, struct iscsi_nopin *hdr){ uint32_t max_cmdsn = be32_to_cpu(hdr->max_cmdsn); uint32_t exp_cmdsn = be32_to_cpu(hdr->exp_cmdsn); /* * standard specifies this check for when to update expected and * max sequence numbers */ if (iscsi_sna_lt(max_cmdsn, exp_cmdsn - 1)) return; if (exp_cmdsn != session->exp_cmdsn && !iscsi_sna_lt(exp_cmdsn, session->exp_cmdsn)) session->exp_cmdsn = exp_cmdsn; if (max_cmdsn != session->max_cmdsn && !iscsi_sna_lt(max_cmdsn, session->max_cmdsn)) { session->max_cmdsn = max_cmdsn; /* * if the window closed with IO queued, then kick the * xmit thread */ if (!list_empty(&session->leadconn->xmitqueue) || __kfifo_len(session->leadconn->mgmtqueue)) scsi_queue_work(session->host, &session->leadconn->xmitwork); }}EXPORT_SYMBOL_GPL(iscsi_update_cmdsn);void iscsi_prep_unsolicit_data_pdu(struct iscsi_cmd_task *ctask, struct iscsi_data *hdr){ struct iscsi_conn *conn = ctask->conn; memset(hdr, 0, sizeof(struct iscsi_data)); hdr->ttt = cpu_to_be32(ISCSI_RESERVED_TAG); hdr->datasn = cpu_to_be32(ctask->unsol_datasn); ctask->unsol_datasn++; hdr->opcode = ISCSI_OP_SCSI_DATA_OUT; memcpy(hdr->lun, ctask->hdr->lun, sizeof(hdr->lun)); hdr->itt = ctask->hdr->itt; hdr->exp_statsn = cpu_to_be32(conn->exp_statsn); hdr->offset = cpu_to_be32(ctask->unsol_offset); if (ctask->unsol_count > conn->max_xmit_dlength) { hton24(hdr->dlength, conn->max_xmit_dlength); ctask->data_count = conn->max_xmit_dlength; ctask->unsol_offset += ctask->data_count; hdr->flags = 0; } else { hton24(hdr->dlength, ctask->unsol_count); ctask->data_count = ctask->unsol_count; hdr->flags = ISCSI_FLAG_CMD_FINAL; }}EXPORT_SYMBOL_GPL(iscsi_prep_unsolicit_data_pdu);/** * iscsi_prep_scsi_cmd_pdu - prep iscsi scsi cmd pdu * @ctask: iscsi cmd task * * Prep basic iSCSI PDU fields for a scsi cmd pdu. The LLD should set * fields like dlength or final based on how much data it sends */static void iscsi_prep_scsi_cmd_pdu(struct iscsi_cmd_task *ctask){ struct iscsi_conn *conn = ctask->conn; struct iscsi_session *session = conn->session; struct iscsi_cmd *hdr = ctask->hdr; struct scsi_cmnd *sc = ctask->sc; hdr->opcode = ISCSI_OP_SCSI_CMD; hdr->flags = ISCSI_ATTR_SIMPLE; int_to_scsilun(sc->device->lun, (struct scsi_lun *)hdr->lun); hdr->itt = build_itt(ctask->itt, conn->id, session->age); hdr->data_length = cpu_to_be32(scsi_bufflen(sc)); hdr->cmdsn = cpu_to_be32(session->cmdsn); session->cmdsn++; hdr->exp_statsn = cpu_to_be32(conn->exp_statsn); memcpy(hdr->cdb, sc->cmnd, sc->cmd_len); if (sc->cmd_len < MAX_COMMAND_SIZE) memset(&hdr->cdb[sc->cmd_len], 0, MAX_COMMAND_SIZE - sc->cmd_len); ctask->data_count = 0; ctask->imm_count = 0; if (sc->sc_data_direction == DMA_TO_DEVICE) { hdr->flags |= ISCSI_FLAG_CMD_WRITE; /* * Write counters: * * imm_count bytes to be sent right after * SCSI PDU Header * * unsol_count bytes(as Data-Out) to be sent * without R2T ack right after * immediate data * * r2t_data_count bytes to be sent via R2T ack's * * pad_count bytes to be sent as zero-padding */ ctask->unsol_count = 0; ctask->unsol_offset = 0; ctask->unsol_datasn = 0; if (session->imm_data_en) { if (scsi_bufflen(sc) >= session->first_burst) ctask->imm_count = min(session->first_burst, conn->max_xmit_dlength); else ctask->imm_count = min(scsi_bufflen(sc), conn->max_xmit_dlength); hton24(ctask->hdr->dlength, ctask->imm_count); } else zero_data(ctask->hdr->dlength); if (!session->initial_r2t_en) { ctask->unsol_count = min((session->first_burst), (scsi_bufflen(sc))) - ctask->imm_count; ctask->unsol_offset = ctask->imm_count; } if (!ctask->unsol_count) /* No unsolicit Data-Out's */ ctask->hdr->flags |= ISCSI_FLAG_CMD_FINAL; } else { hdr->flags |= ISCSI_FLAG_CMD_FINAL; zero_data(hdr->dlength); if (sc->sc_data_direction == DMA_FROM_DEVICE) hdr->flags |= ISCSI_FLAG_CMD_READ; } conn->scsicmd_pdus_cnt++; debug_scsi("iscsi prep [%s cid %d sc %p cdb 0x%x itt 0x%x len %d " "cmdsn %d win %d]\n", sc->sc_data_direction == DMA_TO_DEVICE ? "write" : "read", conn->id, sc, sc->cmnd[0], ctask->itt, scsi_bufflen(sc), session->cmdsn, session->max_cmdsn - session->exp_cmdsn + 1);}/** * iscsi_complete_command - return command back to scsi-ml * @ctask: iscsi cmd task * * Must be called with session lock. * This function returns the scsi command to scsi-ml and returns * the cmd task to the pool of available cmd tasks. */static void iscsi_complete_command(struct iscsi_cmd_task *ctask){ struct iscsi_session *session = ctask->conn->session; struct scsi_cmnd *sc = ctask->sc; ctask->state = ISCSI_TASK_COMPLETED; ctask->sc = NULL; /* SCSI eh reuses commands to verify us */ sc->SCp.ptr = NULL; list_del_init(&ctask->running); __kfifo_put(session->cmdpool.queue, (void*)&ctask, sizeof(void*)); sc->scsi_done(sc);}static void __iscsi_get_ctask(struct iscsi_cmd_task *ctask){ atomic_inc(&ctask->refcount);}static void __iscsi_put_ctask(struct iscsi_cmd_task *ctask){ if (atomic_dec_and_test(&ctask->refcount)) iscsi_complete_command(ctask);}/** * iscsi_cmd_rsp - SCSI Command Response processing * @conn: iscsi connection * @hdr: iscsi header * @ctask: scsi command task * @data: cmd data buffer * @datalen: len of buffer * * iscsi_cmd_rsp sets up the scsi_cmnd fields based on the PDU and * then completes the command and task. **/static void iscsi_scsi_cmd_rsp(struct iscsi_conn *conn, struct iscsi_hdr *hdr, struct iscsi_cmd_task *ctask, char *data, int datalen){ struct iscsi_cmd_rsp *rhdr = (struct iscsi_cmd_rsp *)hdr; struct iscsi_session *session = conn->session; struct scsi_cmnd *sc = ctask->sc; iscsi_update_cmdsn(session, (struct iscsi_nopin*)rhdr); conn->exp_statsn = be32_to_cpu(rhdr->statsn) + 1; sc->result = (DID_OK << 16) | rhdr->cmd_status; if (rhdr->response != ISCSI_STATUS_CMD_COMPLETED) { sc->result = DID_ERROR << 16; goto out; } if (rhdr->cmd_status == SAM_STAT_CHECK_CONDITION) { uint16_t senselen; if (datalen < 2) {invalid_datalen: printk(KERN_ERR "iscsi: Got CHECK_CONDITION but " "invalid data buffer size of %d\n", datalen); sc->result = DID_BAD_TARGET << 16; goto out; } senselen = be16_to_cpu(get_unaligned((__be16 *) data)); if (datalen < senselen) goto invalid_datalen; memcpy(sc->sense_buffer, data + 2, min_t(uint16_t, senselen, SCSI_SENSE_BUFFERSIZE)); debug_scsi("copied %d bytes of sense\n", min_t(uint16_t, senselen, SCSI_SENSE_BUFFERSIZE)); } if (rhdr->flags & ISCSI_FLAG_CMD_UNDERFLOW) { int res_count = be32_to_cpu(rhdr->residual_count); if (res_count > 0 && res_count <= scsi_bufflen(sc)) scsi_set_resid(sc, res_count); else sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status; } else if (rhdr->flags & ISCSI_FLAG_CMD_BIDI_UNDERFLOW) sc->result = (DID_BAD_TARGET << 16) | rhdr->cmd_status; else if (rhdr->flags & ISCSI_FLAG_CMD_OVERFLOW) scsi_set_resid(sc, be32_to_cpu(rhdr->residual_count));out: debug_scsi("done [sc %lx res %d itt 0x%x]\n", (long)sc, sc->result, ctask->itt); conn->scsirsp_pdus_cnt++; __iscsi_put_ctask(ctask);}static void iscsi_tmf_rsp(struct iscsi_conn *conn, struct iscsi_hdr *hdr){ struct iscsi_tm_rsp *tmf = (struct iscsi_tm_rsp *)hdr; conn->exp_statsn = be32_to_cpu(hdr->statsn) + 1; conn->tmfrsp_pdus_cnt++; if (conn->tmabort_state != TMABORT_INITIAL) return; if (tmf->response == ISCSI_TMF_RSP_COMPLETE) conn->tmabort_state = TMABORT_SUCCESS; else if (tmf->response == ISCSI_TMF_RSP_NO_TASK) conn->tmabort_state = TMABORT_NOT_FOUND; else conn->tmabort_state = TMABORT_FAILED; wake_up(&conn->ehwait);}static int iscsi_handle_reject(struct iscsi_conn *conn, struct iscsi_hdr *hdr, char *data, int datalen){ struct iscsi_reject *reject = (struct iscsi_reject *)hdr; struct iscsi_hdr rejected_pdu; uint32_t itt; conn->exp_statsn = be32_to_cpu(reject->statsn) + 1; if (reject->reason == ISCSI_REASON_DATA_DIGEST_ERROR) { if (ntoh24(reject->dlength) > datalen) return ISCSI_ERR_PROTO; if (ntoh24(reject->dlength) >= sizeof(struct iscsi_hdr)) { memcpy(&rejected_pdu, data, sizeof(struct iscsi_hdr)); itt = get_itt(rejected_pdu.itt); printk(KERN_ERR "itt 0x%x had pdu (op 0x%x) rejected " "due to DataDigest error.\n", itt, rejected_pdu.opcode); } } return 0;}/** * __iscsi_complete_pdu - complete pdu * @conn: iscsi conn * @hdr: iscsi header * @data: data buffer * @datalen: len of data buffer * * Completes pdu processing by freeing any resources allocated at * queuecommand or send generic. session lock must be held and verify * itt must have been called. */int __iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr, char *data, int datalen){ struct iscsi_session *session = conn->session; int opcode = hdr->opcode & ISCSI_OPCODE_MASK, rc = 0; struct iscsi_cmd_task *ctask; struct iscsi_mgmt_task *mtask; uint32_t itt; if (hdr->itt != RESERVED_ITT) itt = get_itt(hdr->itt); else itt = ~0U; if (itt < session->cmds_max) { ctask = session->cmds[itt]; debug_scsi("cmdrsp [op 0x%x cid %d itt 0x%x len %d]\n", opcode, conn->id, ctask->itt, datalen); switch(opcode) { case ISCSI_OP_SCSI_CMD_RSP: BUG_ON((void*)ctask != ctask->sc->SCp.ptr); iscsi_scsi_cmd_rsp(conn, hdr, ctask, data, datalen); break; case ISCSI_OP_SCSI_DATA_IN: BUG_ON((void*)ctask != ctask->sc->SCp.ptr); if (hdr->flags & ISCSI_FLAG_DATA_STATUS) { conn->scsirsp_pdus_cnt++; __iscsi_put_ctask(ctask); } break; case ISCSI_OP_R2T: /* LLD handles this for now */ break; default: rc = ISCSI_ERR_BAD_OPCODE; break; } } else if (itt >= ISCSI_MGMT_ITT_OFFSET && itt < ISCSI_MGMT_ITT_OFFSET + session->mgmtpool_max) { mtask = session->mgmt_cmds[itt - ISCSI_MGMT_ITT_OFFSET]; debug_scsi("immrsp [op 0x%x cid %d itt 0x%x len %d]\n", opcode, conn->id, mtask->itt, datalen); iscsi_update_cmdsn(session, (struct iscsi_nopin*)hdr); switch(opcode) { case ISCSI_OP_LOGOUT_RSP: if (datalen) { rc = ISCSI_ERR_PROTO; break; } conn->exp_statsn = be32_to_cpu(hdr->statsn) + 1; /* fall through */ case ISCSI_OP_LOGIN_RSP: case ISCSI_OP_TEXT_RSP: /* * login related PDU's exp_statsn is handled in * userspace */ if (iscsi_recv_pdu(conn->cls_conn, hdr, data, datalen)) rc = ISCSI_ERR_CONN_FAILED; list_del(&mtask->running); if (conn->login_mtask != mtask) __kfifo_put(session->mgmtpool.queue, (void*)&mtask, sizeof(void*)); break; case ISCSI_OP_SCSI_TMFUNC_RSP: if (datalen) { rc = ISCSI_ERR_PROTO; break; } iscsi_tmf_rsp(conn, hdr); break; case ISCSI_OP_NOOP_IN: if (hdr->ttt != cpu_to_be32(ISCSI_RESERVED_TAG) || datalen) { rc = ISCSI_ERR_PROTO; break; } conn->exp_statsn = be32_to_cpu(hdr->statsn) + 1; if (iscsi_recv_pdu(conn->cls_conn, hdr, data, datalen)) rc = ISCSI_ERR_CONN_FAILED; list_del(&mtask->running); if (conn->login_mtask != mtask) __kfifo_put(session->mgmtpool.queue, (void*)&mtask, sizeof(void*)); break; default: rc = ISCSI_ERR_BAD_OPCODE; break; } } else if (itt == ~0U) { iscsi_update_cmdsn(session, (struct iscsi_nopin*)hdr); switch(opcode) { case ISCSI_OP_NOOP_IN: if (datalen) { rc = ISCSI_ERR_PROTO; break; } if (hdr->ttt == cpu_to_be32(ISCSI_RESERVED_TAG)) break; if (iscsi_recv_pdu(conn->cls_conn, hdr, NULL, 0)) rc = ISCSI_ERR_CONN_FAILED; break; case ISCSI_OP_REJECT: rc = iscsi_handle_reject(conn, hdr, data, datalen); break; case ISCSI_OP_ASYNC_EVENT: conn->exp_statsn = be32_to_cpu(hdr->statsn) + 1; if (iscsi_recv_pdu(conn->cls_conn, hdr, data, datalen)) rc = ISCSI_ERR_CONN_FAILED; break; default: rc = ISCSI_ERR_BAD_OPCODE; break; } } else rc = ISCSI_ERR_BAD_ITT; return rc;}EXPORT_SYMBOL_GPL(__iscsi_complete_pdu);int iscsi_complete_pdu(struct iscsi_conn *conn, struct iscsi_hdr *hdr, char *data, int datalen){ int rc; spin_lock(&conn->session->lock); rc = __iscsi_complete_pdu(conn, hdr, data, datalen); spin_unlock(&conn->session->lock); return rc;}EXPORT_SYMBOL_GPL(iscsi_complete_pdu);/* verify itt (itt encoding: age+cid+itt) */int iscsi_verify_itt(struct iscsi_conn *conn, struct iscsi_hdr *hdr, uint32_t *ret_itt){ struct iscsi_session *session = conn->session; struct iscsi_cmd_task *ctask; uint32_t itt; if (hdr->itt != RESERVED_ITT) { if (((__force u32)hdr->itt & ISCSI_AGE_MASK) != (session->age << ISCSI_AGE_SHIFT)) { printk(KERN_ERR "iscsi: received itt %x expected " "session age (%x)\n", (__force u32)hdr->itt, session->age & ISCSI_AGE_MASK); return ISCSI_ERR_BAD_ITT; } if (((__force u32)hdr->itt & ISCSI_CID_MASK) != (conn->id << ISCSI_CID_SHIFT)) { printk(KERN_ERR "iscsi: received itt %x, expected " "CID (%x)\n", (__force u32)hdr->itt, conn->id); return ISCSI_ERR_BAD_ITT; } itt = get_itt(hdr->itt); } else itt = ~0U;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -