diff --git a/Makefile b/Makefile index c984d1a62..7263ec5d6 100644 --- a/Makefile +++ b/Makefile @@ -45,7 +45,7 @@ QLA_DIR=qla2x00t_git/qla2x00-target QLA_OLD_INI_DIR=qla2x00t QLA_OLD_DIR=qla2x00t/qla2x00-target LSI_DIR=mpt -USR_DIR=usr/fileio +USR_DIR=usr SRP_DIR=srpt SCST_LOCAL_DIR=scst_local MVSAS_DIR=mvsas_tgt @@ -129,7 +129,7 @@ help: @echo " scst_local_install : scst_local target: install" @echo " scst_local_uninstall : scst_local target: uninstall" @echo "" - @echo " usr : make user space fileio_tgt target" + @echo " usr : make user space targets" @echo " usr_clean : usr target: clean " @echo " usr_extraclean : usr target: clean + clean dependencies" @echo " usr_install : usr target: install" diff --git a/README b/README index 04f0fd0b5..aa14955cd 100644 --- a/README +++ b/README @@ -48,9 +48,15 @@ qla2x00t_git 3. Symlink drivers/scsi/qla2xxx subdirectory in the cloned git tree to the qla2x00t_git subdirectory -Thats all. Now "make all" and other common and QLA specific root -Makefile targets "magically" start working. For detail instructions how -to setup QLA target build environment see its README or HOWTO. +Thats all. Now run either "make all" to build the SCST core and all +target drivers with in-tree QLogic drivers, or the following command +with standalone QLogic drivers: + +BUILD_2X_MODULE=y CONFIG_SCSI_QLA_FC=y CONFIG_SCSI_QLA2XXX_TARGET=y make all + +and other common and QLA specific root Makefile targets "magically" start +working. For detail instructions how to setup QLA target build environment see +its README or HOWTO. You can still build the old driver using qla_old* root Makefile targets. diff --git a/doc/Makefile b/doc/Makefile index d2b95bd76..c1b59b8cc 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -36,7 +36,9 @@ rtf: $(RTFS) $(COMMAND)rtf $(<) clean: + mv "Using the DLM as a Distributed In-Memory Database.pdf" "Using the DLM as a Distributed In-Memory Database.pdf_" rm -f *.txt *.html *.tex *.dvi *.ps *.pdf *.info *.lyx *.rtf + mv "Using the DLM as a Distributed In-Memory Database.pdf_" "Using the DLM as a Distributed In-Memory Database.pdf" extraclean: clean rm -f *.orig *.rej diff --git a/doc/Using the DLM as a Distributed In-Memory Database.pdf b/doc/Using the DLM as a Distributed In-Memory Database.pdf new file mode 100644 index 000000000..19be45865 Binary files /dev/null and b/doc/Using the DLM as a Distributed In-Memory Database.pdf differ diff --git a/fcst/fcst.h b/fcst/fcst.h index 9f2276b01..771937a3a 100644 --- a/fcst/fcst.h +++ b/fcst/fcst.h @@ -26,7 +26,7 @@ #include "scst.h" #endif -#define FT_VERSION "3.1.0-pre" +#define FT_VERSION "3.2.0-pre" #define FT_MODULE "fcst" #define FT_MAX_HW_PENDING_TIME 20 /* max I/O time in seconds */ diff --git a/iscsi-scst/README b/iscsi-scst/README index 95580266d..74a546018 100644 --- a/iscsi-scst/README +++ b/iscsi-scst/README @@ -1,7 +1,7 @@ iSCSI SCST target driver ======================== -Version 3.1.0, XX XXXXX 2014 +Version 3.2.0, XX XXXXX 2014 ---------------------------- ISCSI-SCST is a deeply reworked fork of iSCSI Enterprise Target (IET) diff --git a/iscsi-scst/include/iscsi_scst_ver.h b/iscsi-scst/include/iscsi_scst_ver.h index 566425c85..9c60851f5 100644 --- a/iscsi-scst/include/iscsi_scst_ver.h +++ b/iscsi-scst/include/iscsi_scst_ver.h @@ -21,4 +21,4 @@ #define ISCSI_VERSION_STRING_SUFFIX #endif -#define ISCSI_VERSION_STRING "3.1.0-pre1" ISCSI_VERSION_STRING_SUFFIX +#define ISCSI_VERSION_STRING "3.2.0-pre1" ISCSI_VERSION_STRING_SUFFIX diff --git a/iscsi-scst/kernel/config.c b/iscsi-scst/kernel/config.c index f774ebcd3..2e39e2523 100644 --- a/iscsi-scst/kernel/config.c +++ b/iscsi-scst/kernel/config.c @@ -1297,6 +1297,7 @@ unsigned long iscsi_get_flow_ctrl_or_mgmt_dbg_log_flag(struct iscsi_cmnd *cmnd) flag = TRACE_MGMT_DEBUG; else { int status = scst_cmd_get_status(cmnd->scst_cmd); + if ((status == SAM_STAT_TASK_SET_FULL) || (status == SAM_STAT_BUSY)) flag = TRACE_FLOW_CONTROL; diff --git a/iscsi-scst/kernel/conn.c b/iscsi-scst/kernel/conn.c index 057747296..641d1bf79 100644 --- a/iscsi-scst/kernel/conn.c +++ b/iscsi-scst/kernel/conn.c @@ -525,6 +525,7 @@ static void conn_rsp_timer_fn(unsigned long arg) if (!list_empty(&conn->write_timeout_list)) { unsigned long timeout_time; + cmnd = list_first_entry(&conn->write_timeout_list, struct iscsi_cmnd, write_timeout_list_entry); @@ -583,7 +584,7 @@ static void conn_nop_in_delayed_work_fn(struct work_struct *work) #endif { #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 20) - struct iscsi_conn *conn = (struct iscsi_conn *)p; + struct iscsi_conn *conn = p; #else struct iscsi_conn *conn = container_of(work, struct iscsi_conn, nop_in_delayed_work.work); @@ -834,6 +835,7 @@ void conn_free(struct iscsi_conn *conn) /* Just in case if new conn gets freed before the old one */ if (test_bit(ISCSI_CONN_REINSTATING, &conn->conn_aflags)) { struct iscsi_conn *c; + TRACE_MGMT_DBG("Freeing being reinstated conn %p", conn); list_for_each_entry(c, &session->conn_list, conn_list_entry) { diff --git a/iscsi-scst/kernel/digest.c b/iscsi-scst/kernel/digest.c index 5d682aab5..cbfff039e 100644 --- a/iscsi-scst/kernel/digest.c +++ b/iscsi-scst/kernel/digest.c @@ -76,6 +76,7 @@ static __be32 evaluate_crc32_from_sg(struct scatterlist *sg, int nbytes, while (nbytes > 0) { int d = min(nbytes, (int)(sg->length)); + crc = crc32c(crc, sg_virt(sg), d); nbytes -= d; sg++; diff --git a/iscsi-scst/kernel/iscsi.c b/iscsi-scst/kernel/iscsi.c index 9563bb8e5..ae0be8538 100644 --- a/iscsi-scst/kernel/iscsi.c +++ b/iscsi-scst/kernel/iscsi.c @@ -138,6 +138,7 @@ static inline int cmnd_read_size(struct iscsi_cmnd *cmnd) if (ahdr != NULL) { uint8_t *p = (uint8_t *)ahdr; unsigned int size = 0; + do { int s; @@ -477,6 +478,7 @@ void cmnd_done(struct iscsi_cmnd *cmnd) { /* It can be for some aborted commands */ struct scst_cmd *scst_cmd = cmnd->scst_cmd; + TRACE_DBG("cmd %p AFTER_PREPROC", cmnd); cmnd->scst_state = ISCSI_CMD_STATE_RESTARTED; cmnd->scst_cmd = NULL; @@ -1095,6 +1097,7 @@ static void iscsi_tcp_send_data_rsp(struct iscsi_cmnd *req, u8 *sense, send_data_rsp(req, status, is_send_status); } else { struct iscsi_cmnd *rsp; + send_data_rsp(req, 0, 0); if (is_send_status) { rsp = create_status_rsp(req, status, sense, @@ -1229,6 +1232,7 @@ static int create_reject_rsp(struct iscsi_cmnd *req, int reason, bool get_data) if (req->scst_cmd == NULL) { /* BUSY status must be already set */ struct iscsi_scsi_rsp_hdr *rsp_hdr1; + rsp_hdr1 = (struct iscsi_scsi_rsp_hdr *)&req->main_rsp->pdu.bhs; sBUG_ON(rsp_hdr1->cmd_status == 0); /* @@ -1531,6 +1535,7 @@ int iscsi_preliminary_complete(struct iscsi_cmnd *req, #ifdef CONFIG_SCST_DEBUG { struct iscsi_hdr *req_hdr = &req->pdu.bhs; + TRACE_DBG_FLAG(iscsi_get_flow_ctrl_or_mgmt_dbg_log_flag(orig_req), "Prelim completed req %p, orig_req %p (FINAL %x, " "outstanding_r2t %d)", req, orig_req, @@ -1748,8 +1753,7 @@ out: static int iscsi_pre_exec(struct scst_cmd *scst_cmd) { int res = SCST_PREPROCESS_STATUS_SUCCESS; - struct iscsi_cmnd *req = (struct iscsi_cmnd *) - scst_cmd_get_tgt_priv(scst_cmd); + struct iscsi_cmnd *req = scst_cmd_get_tgt_priv(scst_cmd); struct iscsi_cmnd *c, *t; TRACE_ENTRY(); @@ -2055,6 +2059,7 @@ static int scsi_cmnd_start(struct iscsi_cmnd *req) if ((req_hdr->flags & ISCSI_CMD_READ) && (req_hdr->flags & ISCSI_CMD_WRITE)) { int sz = cmnd_read_size(req); + if (unlikely(sz < 0)) { PRINT_ERROR("%s", "BIDI data transfer, but initiator " "not supplied Bidirectional Read Expected Data " @@ -2117,6 +2122,7 @@ static int scsi_cmnd_start(struct iscsi_cmnd *req) if (ahdr != NULL) { uint8_t *p = (uint8_t *)ahdr; unsigned int size = 0; + do { int s; @@ -2517,6 +2523,7 @@ static void iscsi_cmnd_abort_fn(struct work_struct *work) */ list_for_each_entry(conn, &session->conn_list, conn_list_entry) { struct iscsi_cmnd *c; + spin_lock_bh(&conn->cmd_list_lock); list_for_each_entry(c, &conn->cmd_list, cmd_list_entry) { if (c == cmnd) { @@ -3376,8 +3383,7 @@ static void iscsi_tcp_preprocessing_done(struct iscsi_cmnd *req) static void iscsi_preprocessing_done(struct scst_cmd *scst_cmd) { - struct iscsi_cmnd *req = (struct iscsi_cmnd *) - scst_cmd_get_tgt_priv(scst_cmd); + struct iscsi_cmnd *req = scst_cmd_get_tgt_priv(scst_cmd); req->conn->transport->iscsit_preprocessing_done(req); } @@ -3489,8 +3495,7 @@ static void iscsi_tcp_conn_close(struct iscsi_conn *conn, int flags) static int iscsi_xmit_response(struct scst_cmd *scst_cmd) { int is_send_status = scst_cmd_get_is_send_status(scst_cmd); - struct iscsi_cmnd *req = (struct iscsi_cmnd *) - scst_cmd_get_tgt_priv(scst_cmd); + struct iscsi_cmnd *req = scst_cmd_get_tgt_priv(scst_cmd); struct iscsi_conn *conn = req->conn; int status = scst_cmd_get_status(scst_cmd); u8 *sense = scst_cmd_get_sense_buffer(scst_cmd); @@ -3574,6 +3579,7 @@ static int iscsi_xmit_response(struct scst_cmd *scst_cmd) is_send_status); } else if (is_send_status) { struct iscsi_cmnd *rsp; + rsp = create_status_rsp(req, status, sense, sense_len); iscsi_cmnd_init_write(rsp, 0); } @@ -3768,8 +3774,7 @@ static inline int iscsi_get_mgmt_response(int status) static void iscsi_task_mgmt_fn_done(struct scst_mgmt_cmd *scst_mcmd) { int fn = scst_mgmt_cmd_get_fn(scst_mcmd); - struct iscsi_cmnd *req = (struct iscsi_cmnd *) - scst_mgmt_cmd_get_tgt_priv(scst_mcmd); + struct iscsi_cmnd *req = scst_mgmt_cmd_get_tgt_priv(scst_mcmd); int status = iscsi_get_mgmt_response(scst_mgmt_cmd_get_status(scst_mcmd)); if ((status == ISCSI_RESPONSE_UNKNOWN_TASK) && diff --git a/iscsi-scst/kernel/iscsi.h b/iscsi-scst/kernel/iscsi.h index 7a4ca36ab..986f5132c 100644 --- a/iscsi-scst/kernel/iscsi.h +++ b/iscsi-scst/kernel/iscsi.h @@ -693,6 +693,7 @@ static inline bool cmnd_get_check(struct iscsi_cmnd *cmnd) { int r = atomic_inc_return(&cmnd->ref_cnt); int res; + if (unlikely(r == 1)) { TRACE_DBG("cmnd %p is being destroyed", cmnd); atomic_dec(&cmnd->ref_cnt); diff --git a/iscsi-scst/kernel/isert-scst/iser.h b/iscsi-scst/kernel/isert-scst/iser.h index 3f8113908..90fa056a2 100644 --- a/iscsi-scst/kernel/isert-scst/iser.h +++ b/iscsi-scst/kernel/isert-scst/iser.h @@ -284,7 +284,7 @@ void isert_conn_free(struct isert_connection *isert_conn); void isert_conn_disconnect(struct isert_connection *isert_conn); void isert_post_drain(struct isert_connection *isert_conn); -static inline struct isert_connection *isert_conn_alloc(void) +static inline struct isert_connection *isert_conn_zalloc(void) { return kmem_cache_zalloc(isert_conn_cache, GFP_KERNEL); } diff --git a/iscsi-scst/kernel/isert-scst/iser_buf.c b/iscsi-scst/kernel/isert-scst/iser_buf.c index b84a68f68..daa78c72a 100644 --- a/iscsi-scst/kernel/isert-scst/iser_buf.c +++ b/iscsi-scst/kernel/isert-scst/iser_buf.c @@ -49,8 +49,8 @@ static int isert_buf_alloc_pg(struct ib_device *ib_dev, struct page *page; isert_buf->sg_cnt = DIV_ROUND_UP(size, PAGE_SIZE); - isert_buf->sg = kmalloc(sizeof(*isert_buf->sg) * isert_buf->sg_cnt, - GFP_KERNEL); + isert_buf->sg = kmalloc_array(isert_buf->sg_cnt, sizeof(*isert_buf->sg), + GFP_KERNEL); if (unlikely(!isert_buf->sg)) { pr_err("Failed to allocate buffer SG\n"); res = -ENOMEM; @@ -294,6 +294,7 @@ out: void isert_wr_release(struct isert_wr *wr) { struct isert_buf *isert_buf = wr->buf; + if (isert_buf && isert_buf->is_alloced) { struct isert_device *isert_dev = wr->isert_dev; struct ib_device *ib_dev; diff --git a/iscsi-scst/kernel/isert-scst/iser_datamover.c b/iscsi-scst/kernel/isert-scst/iser_datamover.c index 19fc58640..165ab071b 100644 --- a/iscsi-scst/kernel/isert-scst/iser_datamover.c +++ b/iscsi-scst/kernel/isert-scst/iser_datamover.c @@ -63,7 +63,8 @@ int isert_get_peer_addr(struct iscsi_conn *iscsi_conn, struct sockaddr *sa, size_t *addr_len) { int ret; - struct isert_connection *isert_conn = (struct isert_connection *)iscsi_conn; + struct isert_connection *isert_conn = container_of(iscsi_conn, + struct isert_connection, iscsi); struct sockaddr *peer_sa = (struct sockaddr *)&isert_conn->peer_addr; ret = isert_get_addr_size(peer_sa, addr_len); @@ -79,7 +80,8 @@ int isert_get_target_addr(struct iscsi_conn *iscsi_conn, struct sockaddr *sa, size_t *addr_len) { int ret; - struct isert_connection *isert_conn = (struct isert_connection *)iscsi_conn; + struct isert_connection *isert_conn = container_of(iscsi_conn, + struct isert_connection, iscsi); struct sockaddr *self_sa = (struct sockaddr *)&isert_conn->self_addr; ret = isert_get_addr_size(self_sa, addr_len); @@ -106,14 +108,17 @@ int isert_portal_remove(void *portal_h) void isert_free_connection(struct iscsi_conn *iscsi_conn) { - struct isert_connection *isert_conn = (struct isert_connection *)iscsi_conn; + struct isert_connection *isert_conn = container_of(iscsi_conn, + struct isert_connection, iscsi); + isert_post_drain(isert_conn); isert_conn_free(isert_conn); } struct iscsi_cmnd *isert_alloc_login_rsp_pdu(struct iscsi_conn *iscsi_conn) { - struct isert_connection *isert_conn = (struct isert_connection *)iscsi_conn; + struct isert_connection *isert_conn = container_of(iscsi_conn, + struct isert_connection, iscsi); struct isert_cmnd *isert_pdu = isert_conn->login_rsp_pdu; isert_tx_pdu_init(isert_pdu, isert_conn); @@ -123,7 +128,8 @@ struct iscsi_cmnd *isert_alloc_login_rsp_pdu(struct iscsi_conn *iscsi_conn) static struct iscsi_cmnd *isert_alloc_scsi_pdu(struct iscsi_conn *iscsi_conn, int fake) { - struct isert_connection *isert_conn = (struct isert_connection *)iscsi_conn; + struct isert_connection *isert_conn = container_of(iscsi_conn, + struct isert_connection, iscsi); struct isert_cmnd *isert_pdu; again: @@ -153,8 +159,10 @@ struct iscsi_cmnd *isert_alloc_scsi_fake_pdu(struct iscsi_conn *iscsi_conn) void isert_release_tx_pdu(struct iscsi_cmnd *iscsi_pdu) { - struct isert_cmnd *isert_pdu = (struct isert_cmnd *)iscsi_pdu; - struct isert_connection *isert_conn = (struct isert_connection *)iscsi_pdu->conn; + struct isert_cmnd *isert_pdu = container_of(iscsi_pdu, + struct isert_cmnd, iscsi); + struct isert_connection *isert_conn = container_of(iscsi_pdu->conn, + struct isert_connection, iscsi); isert_tx_pdu_init_iscsi(isert_pdu); @@ -165,16 +173,17 @@ void isert_release_tx_pdu(struct iscsi_cmnd *iscsi_pdu) void isert_release_rx_pdu(struct iscsi_cmnd *iscsi_pdu) { - struct isert_cmnd *isert_pdu = (struct isert_cmnd *)iscsi_pdu; + struct isert_cmnd *isert_pdu = container_of(iscsi_pdu, + struct isert_cmnd, iscsi); - if (likely(!isert_pdu->is_fake_rx)) - isert_rx_pdu_done(isert_pdu); + isert_rx_pdu_done(isert_pdu); } /* if last transition into FF (Fully Featured) state */ int isert_login_rsp_tx(struct iscsi_cmnd *login_rsp, int last, int discovery) { - struct isert_connection *isert_conn = (struct isert_connection *)login_rsp->conn; + struct isert_connection *isert_conn = container_of(login_rsp->conn, + struct isert_connection, iscsi); int err; if (last && !discovery) { @@ -202,7 +211,8 @@ int isert_set_session_params(struct iscsi_conn *iscsi_conn, struct iscsi_sess_params *sess_params, struct iscsi_tgt_params *tgt_params) { - struct isert_connection *isert_conn = (struct isert_connection *)iscsi_conn; + struct isert_connection *isert_conn = container_of(iscsi_conn, + struct isert_connection, iscsi); isert_conn->queue_depth = tgt_params->queued_cmnds; @@ -217,8 +227,10 @@ int isert_set_session_params(struct iscsi_conn *iscsi_conn, int isert_pdu_tx(struct iscsi_cmnd *iscsi_cmnd) { - struct isert_cmnd *isert_cmnd = (struct isert_cmnd *)iscsi_cmnd; - struct isert_connection *isert_conn = (struct isert_connection *)iscsi_cmnd->conn; + struct isert_cmnd *isert_cmnd = container_of(iscsi_cmnd, + struct isert_cmnd, iscsi); + struct isert_connection *isert_conn = container_of(iscsi_cmnd->conn, + struct isert_connection, iscsi); int err; isert_tx_pdu_convert_from_iscsi(isert_cmnd, iscsi_cmnd); @@ -229,8 +241,10 @@ int isert_pdu_tx(struct iscsi_cmnd *iscsi_cmnd) int isert_request_data_out(struct iscsi_cmnd *iscsi_cmnd) { - struct isert_cmnd *isert_cmnd = (struct isert_cmnd *)iscsi_cmnd; - struct isert_connection *isert_conn = (struct isert_connection *)iscsi_cmnd->conn; + struct isert_cmnd *isert_cmnd = container_of(iscsi_cmnd, + struct isert_cmnd, iscsi); + struct isert_connection *isert_conn = container_of(iscsi_cmnd->conn, + struct isert_connection, iscsi); int ret; ret = isert_prepare_rdma(isert_cmnd, isert_conn, ISER_WR_RDMA_READ); @@ -245,9 +259,12 @@ int isert_request_data_out(struct iscsi_cmnd *iscsi_cmnd) int isert_send_data_in(struct iscsi_cmnd *iscsi_cmnd, struct iscsi_cmnd *iscsi_rsp) { - struct isert_cmnd *isert_cmnd = (struct isert_cmnd *)iscsi_cmnd; - struct isert_connection *isert_conn = (struct isert_connection *)iscsi_cmnd->conn; - struct isert_cmnd *isert_rsp = (struct isert_cmnd *)iscsi_rsp; + struct isert_cmnd *isert_cmnd = container_of(iscsi_cmnd, + struct isert_cmnd, iscsi); + struct isert_connection *isert_conn = container_of(iscsi_cmnd->conn, + struct isert_connection, iscsi); + struct isert_cmnd *isert_rsp = container_of(iscsi_rsp, + struct isert_cmnd, iscsi); int ret; ret = isert_prepare_rdma(isert_cmnd, isert_conn, ISER_WR_RDMA_WRITE); @@ -262,7 +279,8 @@ int isert_send_data_in(struct iscsi_cmnd *iscsi_cmnd, int isert_close_connection(struct iscsi_conn *iscsi_conn) { - struct isert_connection *isert_conn = (struct isert_connection *)iscsi_conn; + struct isert_connection *isert_conn = container_of(iscsi_conn, + struct isert_connection, iscsi); isert_conn_disconnect(isert_conn); @@ -276,14 +294,16 @@ int isert_task_abort(struct iscsi_cmnd *cmnd) void *isert_get_priv(struct iscsi_conn *iscsi_conn) { - struct isert_connection *isert_conn = (struct isert_connection *)iscsi_conn; + struct isert_connection *isert_conn = container_of(iscsi_conn, + struct isert_connection, iscsi); return isert_conn->priv_data; } void isert_set_priv(struct iscsi_conn *iscsi_conn, void *priv) { - struct isert_connection *isert_conn = (struct isert_connection *)iscsi_conn; + struct isert_connection *isert_conn = container_of(iscsi_conn, + struct isert_connection, iscsi); isert_conn->priv_data = priv; } diff --git a/iscsi-scst/kernel/isert-scst/iser_pdu.c b/iscsi-scst/kernel/isert-scst/iser_pdu.c index 4a23d06c9..0c6e81b32 100644 --- a/iscsi-scst/kernel/isert-scst/iser_pdu.c +++ b/iscsi-scst/kernel/isert-scst/iser_pdu.c @@ -82,6 +82,7 @@ static int isert_rx_pdu_init(struct isert_cmnd *isert_pdu, { struct iscsi_cmnd *iscsi_cmnd = &isert_pdu->iscsi; int err = isert_pdu_rx_buf_init(isert_pdu, isert_conn); + if (unlikely(err < 0)) return err; iscsi_cmnd->conn = &isert_conn->iscsi; @@ -174,14 +175,14 @@ static int isert_alloc_for_rdma(struct isert_cmnd *pdu, int sge_cnt, int i, ret = 0; int wr_cnt; - sg_pool = kmalloc(sizeof(*sg_pool) * sge_cnt, GFP_KERNEL); + sg_pool = kmalloc_array(sge_cnt, sizeof(*sg_pool), GFP_KERNEL); if (unlikely(sg_pool == NULL)) { ret = -ENOMEM; goto out; } wr_cnt = DIV_ROUND_UP(sge_cnt, isert_conn->max_sge); - wr = kmalloc(sizeof(*wr) * wr_cnt, GFP_KERNEL); + wr = kmalloc_array(wr_cnt, sizeof(*wr), GFP_KERNEL); if (unlikely(wr == NULL)) { ret = -ENOMEM; goto out_free_sg_pool; @@ -478,7 +479,8 @@ clean_pdus: static int isert_reinit_rx_pdu(struct isert_cmnd *pdu) { - struct isert_connection *isert_conn = (struct isert_connection *)pdu->iscsi.conn; + struct isert_connection *isert_conn = container_of(pdu->iscsi.conn, + struct isert_connection, iscsi); pdu->is_rstag_valid = 0; pdu->is_wstag_valid = 0; @@ -491,7 +493,8 @@ static int isert_reinit_rx_pdu(struct isert_cmnd *pdu) int isert_rx_pdu_done(struct isert_cmnd *pdu) { int err; - struct isert_connection *isert_conn = (struct isert_connection *)pdu->iscsi.conn; + struct isert_connection *isert_conn = container_of(pdu->iscsi.conn, + struct isert_connection, iscsi); TRACE_ENTRY(); diff --git a/iscsi-scst/kernel/isert-scst/iser_rdma.c b/iscsi-scst/kernel/isert-scst/iser_rdma.c index b645c4cf9..8fd02ab4a 100644 --- a/iscsi-scst/kernel/isert-scst/iser_rdma.c +++ b/iscsi-scst/kernel/isert-scst/iser_rdma.c @@ -136,8 +136,10 @@ void isert_post_drain(struct isert_connection *isert_conn) err = ib_post_send(isert_conn->qp, &isert_conn->drain_wr.send_wr, &bad_wr); if (unlikely(err)) { pr_err("Failed to post drain wr, err:%d\n", err); - /* We need to decrement iser_conn->kref in order to be able to cleanup - * the connection */ + /* + * We need to decrement iser_conn->kref in order to be + * able to cleanup the connection. + */ set_bit(ISERT_DRAIN_FAILED, &isert_conn->flags); isert_conn_free(isert_conn); } @@ -352,7 +354,9 @@ static void isert_send_completion_handler(struct isert_wr *wr) struct isert_cmnd *isert_pdu = wr->pdu; struct iscsi_cmnd *iscsi_pdu = &isert_pdu->iscsi; struct iscsi_cmnd *iscsi_req_pdu = iscsi_pdu->parent_req; - struct isert_cmnd *isert_req_pdu = (struct isert_cmnd *)iscsi_req_pdu; + struct isert_cmnd *isert_req_pdu = container_of(iscsi_req_pdu, + struct isert_cmnd, iscsi); + TRACE_ENTRY(); @@ -633,9 +637,11 @@ static void isert_handle_wc_error(struct ib_wc *wc) isert_buf->dma_dir); isert_buf->sg_cnt = 0; } - /* RDMA-WR and SEND response of a READ task - are sent together, so when receiving RDMA-WR error, - wait until SEND error arrives to complete the task */ + /* + * RDMA-WR and SEND response of a READ task + * are sent together, so when receiving RDMA-WR error, + * wait until SEND error arrives to complete the task. + */ break; default: pr_err("unexpected opcode %d, wc:%p wr_id:%p conn:%p\n", @@ -866,7 +872,8 @@ static struct isert_device *isert_device_create(struct ib_device *ib_dev) isert_dev->num_cqs = min_t(int, num_online_cpus(), ib_dev->num_comp_vectors); - isert_dev->cq_qps = kzalloc(sizeof(*isert_dev->cq_qps) * isert_dev->num_cqs, + isert_dev->cq_qps = kcalloc(isert_dev->num_cqs, + sizeof(*isert_dev->cq_qps), GFP_KERNEL); if (unlikely(isert_dev->cq_qps == NULL)) { pr_err("Failed to allocate iser cq_qps\n"); @@ -1142,7 +1149,7 @@ static struct isert_connection *isert_conn_create(struct rdma_cm_id *cm_id, TRACE_ENTRY(); - isert_conn = isert_conn_alloc(); + isert_conn = isert_conn_zalloc(); if (unlikely(!isert_conn)) { pr_err("Unable to allocate iser conn, cm_id:%p\n", cm_id); err = -ENOMEM; diff --git a/iscsi-scst/kernel/isert-scst/isert.c b/iscsi-scst/kernel/isert-scst/isert.c index 5cdc28901..f16792555 100644 --- a/iscsi-scst/kernel/isert-scst/isert.c +++ b/iscsi-scst/kernel/isert-scst/isert.c @@ -46,6 +46,7 @@ #endif #include "isert_dbg.h" #include "isert.h" +#include "iser.h" #include "iser_datamover.h" #if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) @@ -190,6 +191,9 @@ static struct iscsi_cmnd *isert_cmnd_alloc(struct iscsi_conn *conn, static void isert_cmnd_free(struct iscsi_cmnd *cmnd) { + struct isert_cmnd *isert_cmnd = container_of(cmnd, struct isert_cmnd, + iscsi); + TRACE_ENTRY(); #ifdef CONFIG_SCST_EXTRACHECKS @@ -211,7 +215,7 @@ static void isert_cmnd_free(struct iscsi_cmnd *cmnd) sBUG(); } #endif - if (cmnd->parent_req) + if (cmnd->parent_req || isert_cmnd->is_fake_rx) isert_release_tx_pdu(cmnd); else isert_release_rx_pdu(cmnd); @@ -282,9 +286,11 @@ static void isert_free_conn(struct iscsi_conn *conn) int isert_handle_close_connection(struct iscsi_conn *conn) { isert_mark_conn_closed(conn, 0); - /* Take care of case where our connection is being closed - * without being connected to a session - if connection allocation - * failed for some reason */ + /* + * Take care of case where our connection is being closed without + * being connected to a session - if connection allocation failed for + * some reason. + */ if (unlikely(!conn->session)) isert_free_connection(conn); else diff --git a/iscsi-scst/kernel/isert-scst/isert_login.c b/iscsi-scst/kernel/isert-scst/isert_login.c index f92eafe30..0a3720306 100644 --- a/iscsi-scst/kernel/isert-scst/isert_login.c +++ b/iscsi-scst/kernel/isert-scst/isert_login.c @@ -567,6 +567,7 @@ static ssize_t isert_read(struct file *filp, char __user *buf, size_t count, if (will_read_block(dev)) { int ret; + if (filp->f_flags & O_NONBLOCK) return -EAGAIN; ret = wait_event_freezable(dev->waitqueue, @@ -804,11 +805,8 @@ int isert_login_req_rx(struct iscsi_cmnd *login_req) switch (dev->state) { case CS_INIT: case CS_RSP_FINISHED: - if (unlikely(dev->login_req != NULL)) { + if (unlikely(dev->login_req != NULL)) sBUG(); - res = -EINVAL; - goto out; - } break; case CS_REQ_BHS: /* Got login request before done handling old one */ @@ -825,8 +823,6 @@ int isert_login_req_rx(struct iscsi_cmnd *login_req) default: sBUG(); - res = -EINVAL; - goto out; } @@ -956,7 +952,7 @@ int __init isert_init_login_devs(unsigned int ndevs) * allocate the devices -- we can't have them static, as the number * can be specified at load time */ - isert_conn_devices = kzalloc(n_devs * sizeof(struct isert_conn_dev), + isert_conn_devices = kcalloc(n_devs, sizeof(struct isert_conn_dev), GFP_KERNEL); if (unlikely(!isert_conn_devices)) { res = -ENOMEM; diff --git a/iscsi-scst/kernel/nthread.c b/iscsi-scst/kernel/nthread.c index fad769804..2d471ecac 100644 --- a/iscsi-scst/kernel/nthread.c +++ b/iscsi-scst/kernel/nthread.c @@ -100,6 +100,7 @@ again: for (i = 0; i < cmnd->sg_cnt; i++) { struct page *page = sg_page(&cmnd->sg[i]); + TRACE_CONN_CLOSE_DBG("page %p, net_priv %p, " "_count %d", page, page->net_priv, atomic_read(&page->_count)); @@ -282,8 +283,10 @@ static void trace_conn_close(struct iscsi_conn *conn) cmnd->sg); if (cmnd->sg != NULL) { int i; + for (i = 0; i < cmnd->sg_cnt; i++) { struct page *page = sg_page(&cmnd->sg[i]); + TRACE_CONN_CLOSE_DBG("page %p, " "net_priv %p, _count %d", page, page->net_priv, @@ -301,6 +304,7 @@ static void trace_conn_close(struct iscsi_conn *conn) atomic_read(&rsp->net_ref_cnt), rsp->sg); if (rsp->sg != cmnd->sg && rsp->sg) { int i; + for (i = 0; i < rsp->sg_cnt; i++) { TRACE_CONN_CLOSE_DBG(" page %p, " "net_priv %p, _count %d", @@ -330,7 +334,7 @@ void iscsi_task_mgmt_affected_cmds_done(struct scst_mgmt_cmd *scst_mcmd) switch (fn) { case SCST_NEXUS_LOSS_SESS: { - struct iscsi_conn *conn = (struct iscsi_conn *)priv; + struct iscsi_conn *conn = priv; struct iscsi_session *sess = conn->session; struct iscsi_conn *c; @@ -459,6 +463,7 @@ static void close_conn(struct iscsi_conn *conn) spin_lock(&session->sn_lock); if (session->tm_rsp && session->tm_rsp->conn == conn) { struct iscsi_cmnd *tm_rsp = session->tm_rsp; + iscsi_drop_delayed_tm_rsp(tm_rsp); spin_unlock(&session->sn_lock); mutex_unlock(&target->target_mutex); @@ -579,7 +584,7 @@ static void close_conn(struct iscsi_conn *conn) static int close_conn_thr(void *arg) { - struct iscsi_conn *conn = (struct iscsi_conn *)arg; + struct iscsi_conn *conn = arg; TRACE_ENTRY(); @@ -789,6 +794,7 @@ static int iscsi_rx_check_ddigest(struct iscsi_conn *conn) res = digest_rx_data(cmnd); if (unlikely(res != 0)) { struct iscsi_cmnd *orig_req; + if (cmnd_opcode(cmnd) == ISCSI_OP_SCSI_DATA_OUT) orig_req = cmnd->cmd_req; else @@ -908,6 +914,7 @@ static int process_read_io(struct iscsi_conn *conn, int *closed) res = do_recv(conn); if (res == 0) { int psz = ((cmnd->pdu.datasize + 3) & -4) - cmnd->pdu.datasize; + if (psz != 0) { TRACE_DBG("padding %d bytes", psz); iscsi_conn_init_read(conn, @@ -1132,7 +1139,7 @@ static inline void __iscsi_get_page_callback(struct iscsi_cmnd *cmd) void iscsi_get_page_callback(struct page *page) { - struct iscsi_cmnd *cmd = (struct iscsi_cmnd *)page->net_priv; + struct iscsi_cmnd *cmd = page->net_priv; TRACE_NET_PAGE("page %p, _count %d", page, atomic_read(&page->_count)); @@ -1148,8 +1155,10 @@ static inline void __iscsi_put_page_callback(struct iscsi_cmnd *cmd) if (atomic_dec_and_test(&cmd->net_ref_cnt)) { int i, sg_cnt = cmd->sg_cnt; + for (i = 0; i < sg_cnt; i++) { struct page *page = sg_page(&cmd->sg[i]); + TRACE_NET_PAGE("Clearing page %p", page); if (page->net_priv == cmd) page->net_priv = NULL; @@ -1161,7 +1170,7 @@ static inline void __iscsi_put_page_callback(struct iscsi_cmnd *cmd) void iscsi_put_page_callback(struct page *page) { - struct iscsi_cmnd *cmd = (struct iscsi_cmnd *)page->net_priv; + struct iscsi_cmnd *cmd = page->net_priv; TRACE_NET_PAGE("page %p, _count %d", page, atomic_read(&page->_count)); @@ -1216,9 +1225,11 @@ void req_add_to_write_timeout_list(struct iscsi_cmnd *req) unsigned long req_tt = iscsi_get_timeout_time(req); struct iscsi_cmnd *r; bool inserted = false; + list_for_each_entry(r, &conn->write_timeout_list, write_timeout_list_entry) { unsigned long tt = iscsi_get_timeout_time(r); + if (time_after(tt, req_tt)) { TRACE_DBG("Add NOP IN req %p (tt %ld) before " "req %p (tt %ld)", req, req_tt, r, tt); @@ -1250,6 +1261,7 @@ void req_add_to_write_timeout_list(struct iscsi_cmnd *req) if (!timer_pending(&conn->rsp_timer)) { unsigned long timeout_time; + if (unlikely(conn->conn_tm_active || test_bit(ISCSI_CMD_ABORTED, &req->prelim_compl_flags))) { @@ -1270,6 +1282,7 @@ void req_add_to_write_timeout_list(struct iscsi_cmnd *req) &req->prelim_compl_flags))) { unsigned long timeout_time = jiffies + ISCSI_TM_DATA_WAIT_TIMEOUT + ISCSI_ADD_SCHED_TIME; + set_conn_tm_active = true; if (time_after(conn->rsp_timer.expires, timeout_time)) { TRACE_MGMT_DBG("Mod timer on %ld (conn %p)", @@ -1448,6 +1461,7 @@ retry: #if defined(CONFIG_TCP_ZERO_COPY_TRANSFER_COMPLETION_NOTIFICATION) { static DEFINE_SPINLOCK(net_priv_lock); + spin_lock(&net_priv_lock); if (unlikely(page->net_priv != NULL)) { if (page->net_priv != ref_cmd) { diff --git a/iscsi-scst/kernel/session.c b/iscsi-scst/kernel/session.c index 52dd682be..5b28d0fe4 100644 --- a/iscsi-scst/kernel/session.c +++ b/iscsi-scst/kernel/session.c @@ -199,6 +199,7 @@ int __add_session(struct iscsi_target *target, list_for_each_entry_reverse(sess, &target->session_list, session_list_entry) { union iscsi_sid s = *(union iscsi_sid *)&sess->sid; + s.id.tsih = 0; if ((sid.id64 == s.id64) && (strcmp(info->initiator_name, sess->initiator_name) == 0)) { @@ -332,6 +333,7 @@ int session_free(struct iscsi_session *session, bool del) if (session->sess_reinstating) { struct iscsi_session *s; + TRACE_MGMT_DBG("Freeing being reinstated sess %p", session); list_for_each_entry(s, &session->target->session_list, session_list_entry) { diff --git a/iscsi-scst/kernel/target.c b/iscsi-scst/kernel/target.c index 0677b7428..7a3e0a546 100644 --- a/iscsi-scst/kernel/target.c +++ b/iscsi-scst/kernel/target.c @@ -298,6 +298,7 @@ void target_del_session(struct iscsi_target *target, if (!list_empty(&session->conn_list)) { struct iscsi_conn *conn, *tc; + list_for_each_entry_safe(conn, tc, &session->conn_list, conn_list_entry) { TRACE_MGMT_DBG("Del session: closing conn %p", conn); @@ -616,6 +617,7 @@ ssize_t iscsi_sysfs_del_target(const char *target_name) /* We don't want to have tgt visible after the mutex unlock */ { struct iscsi_target *tgt; + mutex_lock(&target_mgmt_mutex); tgt = target_lookup_by_name(target_name); if (tgt == NULL) { diff --git a/nightly/conf/nightly.conf b/nightly/conf/nightly.conf index bdb6dc7a6..bdee4697d 100644 --- a/nightly/conf/nightly.conf +++ b/nightly/conf/nightly.conf @@ -3,8 +3,9 @@ ABT_DETAILS="x86_64" ABT_JOBS=5 ABT_KERNELS=" \ -4.2.5 \ -4.1.12-nc \ +4.3 \ +4.2.5-nc \ +4.1.12-nc \ 4.0.9-nc \ 3.19.7-nc \ 3.18.19-nc \ diff --git a/qla2x00t/qla2x00-target/Makefile_in-tree-4.3 b/qla2x00t/qla2x00-target/Makefile_in-tree-4.3 new file mode 100644 index 000000000..9657aee84 --- /dev/null +++ b/qla2x00t/qla2x00-target/Makefile_in-tree-4.3 @@ -0,0 +1,5 @@ +ccflags-y += -Idrivers/scsi/qla2xxx + +qla2x00tgt-y := qla2x00t.o + +obj-$(CONFIG_SCST_QLA_TGT_ADDON) += qla2x00tgt.o diff --git a/qla2x00t/qla2x00-target/README b/qla2x00t/qla2x00-target/README index ddd612033..129742c28 100644 --- a/qla2x00t/qla2x00-target/README +++ b/qla2x00t/qla2x00-target/README @@ -1,7 +1,7 @@ Target driver for QLogic 2[2-6]xx/8[1-3]xx Fibre Channel cards ============================================================== -Version 3.1.0, XX XXXXX 2015 +Version 3.2.0, XX XXXXX 2015 ---------------------------- This is target driver for QLogic 2[2-6]xx/8[1-3]xx Fibre Channel cards. @@ -20,7 +20,7 @@ necessary callbacks, but it's still capable to work as initiator only. Mode, when a host acts as the initiator and the target simultaneously, is supported as well. -This version is compatible with SCST core version 3.1.0 and higher and +This version is compatible with SCST core version 3.2.0 and higher and Linux kernel 2.6.26 and higher. Sorry, kernels below 2.6.26 are not supported, because it's too hard to backport used initiator driver to older kernels. diff --git a/qla2x00t/qla2x00-target/qla2x00t.c b/qla2x00t/qla2x00-target/qla2x00t.c index a817faf0c..dad9e2711 100644 --- a/qla2x00t/qla2x00-target/qla2x00t.c +++ b/qla2x00t/qla2x00-target/qla2x00t.c @@ -308,6 +308,7 @@ static inline struct q2t_sess *q2t_find_sess_by_loop_id(struct q2t_tgt *tgt, uint16_t loop_id) { struct q2t_sess *sess; + list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) { if (loop_id == sess->loop_id) { EXTRACHECKS_BUG_ON(sess->deleted); @@ -322,6 +323,7 @@ static inline struct q2t_sess *q2t_find_sess_by_s_id_include_deleted( struct q2t_tgt *tgt, const uint8_t *s_id) { struct q2t_sess *sess; + list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) { if ((sess->s_id.b.al_pa == s_id[2]) && (sess->s_id.b.area == s_id[1]) && @@ -346,6 +348,7 @@ static inline struct q2t_sess *q2t_find_sess_by_s_id(struct q2t_tgt *tgt, const uint8_t *s_id) { struct q2t_sess *sess; + list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) { if ((sess->s_id.b.al_pa == s_id[2]) && (sess->s_id.b.area == s_id[1]) && @@ -362,6 +365,7 @@ static inline struct q2t_sess *q2t_find_sess_by_s_id_le(struct q2t_tgt *tgt, const uint8_t *s_id) { struct q2t_sess *sess; + list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) { if ((sess->s_id.b.al_pa == s_id[0]) && (sess->s_id.b.area == s_id[1]) && @@ -378,6 +382,7 @@ static inline struct q2t_sess *q2t_find_sess_by_port_name(struct q2t_tgt *tgt, const uint8_t *port_name) { struct q2t_sess *sess; + list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) { if ((sess->port_name[0] == port_name[0]) && (sess->port_name[1] == port_name[1]) && @@ -399,6 +404,7 @@ static inline struct q2t_sess *q2t_find_sess_by_port_name_include_deleted( struct q2t_tgt *tgt, const uint8_t *port_name) { struct q2t_sess *sess; + list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) { if ((sess->port_name[0] == port_name[0]) && (sess->port_name[1] == port_name[1]) && @@ -446,6 +452,7 @@ static inline int q2t_issue_marker(scsi_qla_host_t *vha, int vha_locked) /* Send marker if required */ if (unlikely(vha->marker_needed != 0)) { int rc = qla2x00_issue_marker(vha, vha_locked); + if (rc != QLA_SUCCESS) { PRINT_ERROR("qla2x00t(%ld): issue_marker() " "failed", vha->host_no); @@ -468,6 +475,7 @@ static inline scsi_qla_host_t *q2t_find_host_by_d_id(scsi_qla_host_t *vha, if (IS_FWI2_CAPABLE(ha)) { uint8_t vp_idx; + sBUG_ON(ha->tgt_vp_map == NULL); vp_idx = ha->tgt_vp_map[d_id[2]].idx; if (likely(test_bit(vp_idx, ha->vp_idx_map))) @@ -547,6 +555,7 @@ static void q24_try_to_dequeue_unknown_atios(struct qla_hw_data *ha) list_for_each_entry_safe(u, t, &ha->unknown_atio_list, unknown_atio_list_entry) { scsi_qla_host_t *host, *vha = u->vha; + sBUG_ON(vha->hw != ha); host = q2t_find_host_by_d_id(vha, u->atio7.fcp_hdr.d_id); if (host != NULL) { @@ -610,7 +619,8 @@ static void q24_atio_pkt_all_vps(scsi_qla_host_t *vha, atio7_entry_t *atio) case ATIO_TYPE7: { scsi_qla_host_t *host = q2t_find_host_by_d_id(vha, atio->fcp_hdr.d_id); - if (unlikely(NULL == host)) { + + if (unlikely(host == NULL)) { /* * It might happen, because there is a small gap between * requesting the DPC thread to update loop and actual @@ -628,8 +638,10 @@ static void q24_atio_pkt_all_vps(scsi_qla_host_t *vha, atio7_entry_t *atio) case IMMED_NOTIFY_TYPE: { scsi_qla_host_t *host = vha; + if (IS_FWI2_CAPABLE(ha)) { notify24xx_entry_t *entry = (notify24xx_entry_t *)atio; + if ((entry->vp_index != 0xFF) && (entry->nport_handle != 0xFFFF)) { host = q2t_find_host_by_vp_idx(vha, @@ -671,6 +683,7 @@ static void q2t_response_pkt_all_vps(scsi_qla_host_t *vha, response_t *pkt) ctio7_fw_entry_t *entry = (ctio7_fw_entry_t *)pkt; scsi_qla_host_t *host = q2t_find_host_by_vp_idx(vha, entry->vp_index); + if (unlikely(!host)) { PRINT_ERROR("qla2x00t(%ld): Response pkt (CTIO_TYPE7) " "received, with unknown vp_index %d", @@ -684,8 +697,10 @@ static void q2t_response_pkt_all_vps(scsi_qla_host_t *vha, response_t *pkt) case IMMED_NOTIFY_TYPE: { scsi_qla_host_t *host = vha; + if (IS_FWI2_CAPABLE(ha)) { notify24xx_entry_t *entry = (notify24xx_entry_t *)pkt; + host = q2t_find_host_by_vp_idx(vha, entry->vp_index); if (unlikely(!host)) { PRINT_ERROR("qla2x00t(%ld): Response pkt " @@ -702,9 +717,11 @@ static void q2t_response_pkt_all_vps(scsi_qla_host_t *vha, response_t *pkt) case NOTIFY_ACK_TYPE: { scsi_qla_host_t *host = vha; + if (IS_FWI2_CAPABLE(ha)) { nack24xx_entry_t *entry = (nack24xx_entry_t *)pkt; - if (0xFF != entry->vp_index) { + + if (entry->vp_index != 0xFF) { host = q2t_find_host_by_vp_idx(vha, entry->vp_index); if (unlikely(!host)) { @@ -726,6 +743,7 @@ static void q2t_response_pkt_all_vps(scsi_qla_host_t *vha, response_t *pkt) abts24_recv_entry_t *entry = (abts24_recv_entry_t *)pkt; scsi_qla_host_t *host = q2t_find_host_by_vp_idx(vha, entry->vp_index); + if (unlikely(!host)) { PRINT_ERROR("qla2x00t(%ld): Response pkt " "(ABTS_RECV_24XX) received, with unknown " @@ -741,6 +759,7 @@ static void q2t_response_pkt_all_vps(scsi_qla_host_t *vha, response_t *pkt) abts24_resp_entry_t *entry = (abts24_resp_entry_t *)pkt; scsi_qla_host_t *host = q2t_find_host_by_vp_idx(vha, entry->vp_index); + if (unlikely(!host)) { PRINT_ERROR("qla2x00t(%ld): Response pkt " "(ABTS_RECV_24XX) received, with unknown " @@ -882,6 +901,7 @@ static int q2t_reset(scsi_qla_host_t *vha, void *iocb, int mcmd) if (IS_FWI2_CAPABLE(ha)) { notify24xx_entry_t *n = (notify24xx_entry_t *)iocb; + if ((le16_to_cpu(n->status) == IMM_NTFY_ELS) && ((n->status_subcode == ELS_TPRLO) || (n->status_subcode == ELS_LOGO))) { @@ -1154,6 +1174,7 @@ retry: res = -1; for (i = 0; i < entries; i++) { struct gid_list_info *gid = (struct gid_list_info *)id_iter; + if ((gid->al_pa == s_id[2]) && (gid->area == s_id[1]) && (gid->domain == s_id[0])) { @@ -2053,6 +2074,7 @@ static int __q24_handle_abts(scsi_qla_host_t *vha, abts24_recv_entry_t *abts, */ while (!list_empty(&ha->unknown_atio_list)) { struct q2t_unknown_atio *u; + u = list_first_entry(&ha->unknown_atio_list, struct q2t_unknown_atio, unknown_atio_list_entry); TRACE_MGMT_DBG("qla2x00t(%ld): Clearing unknown " @@ -3304,6 +3326,7 @@ static int __q2t_rdy_to_xfer(struct q2t_cmd *cmd) if (IS_FWI2_CAPABLE(ha)) { ctio7_status0_entry_t *pkt; + res = q24_build_ctio_pkt(&prm); if (unlikely(res != SCST_TGT_RES_SUCCESS)) goto out_unlock_free_unmap; @@ -3314,6 +3337,7 @@ static int __q2t_rdy_to_xfer(struct q2t_cmd *cmd) p = pkt; } else { ctio_common_entry_t *pkt; + q2x_build_ctio_pkt(&prm); pkt = (ctio_common_entry_t *)prm.pkt; pkt->flags = cpu_to_le16(OF_FAST_POST | OF_DATA_OUT); @@ -3573,6 +3597,7 @@ static int q2t_prepare_srr_ctio(scsi_qla_host_t *vha, struct q2t_cmd *cmd, sc, sc->srr_id); if (tgt->imm_srr_id == tgt->ctio_srr_id) { int found = 0; + list_for_each_entry(imm, &tgt->srr_imm_list, srr_list_entry) { if (imm->srr_id == sc->srr_id) { @@ -3600,6 +3625,7 @@ static int q2t_prepare_srr_ctio(scsi_qla_host_t *vha, struct q2t_cmd *cmd, spin_unlock(&tgt->srr_lock); } else { struct srr_imm *ti; + PRINT_ERROR("qla2x00t(%ld): Unable to allocate SRR CTIO entry", vha->host_no); spin_lock(&tgt->srr_lock); @@ -3643,6 +3669,7 @@ static bool q2t_term_ctio_exchange(scsi_qla_host_t *vha, void *ctio, } if (ctio != NULL) { ctio7_fw_entry_t *c = (ctio7_fw_entry_t *)ctio; + term = !(c->flags & cpu_to_le16(OF_TERM_EXCH)); } else term = true; @@ -3654,6 +3681,7 @@ static bool q2t_term_ctio_exchange(scsi_qla_host_t *vha, void *ctio, #if 0 /* Seems, it isn't needed. If enable it, add support for NULL cmd! */ if (ctio != NULL) { ctio_common_entry_t *c = (ctio_common_entry_t *)ctio; + term = !(c->flags & cpu_to_le16(CTIO7_FLAGS_TERMINATE)); } else term = true; @@ -3673,6 +3701,7 @@ static inline struct q2t_cmd *q2t_get_cmd(scsi_qla_host_t *vha, uint32_t handle) handle--; if (vha->cmds[handle] != NULL) { struct q2t_cmd *cmd = vha->cmds[handle]; + vha->cmds[handle] = NULL; return cmd; } else @@ -3728,6 +3757,7 @@ static struct q2t_cmd *q2t_ctio_to_cmd(scsi_qla_host_t *vha, uint32_t handle, goto out; } else { ctio_common_entry_t *c = (ctio_common_entry_t *)ctio; + loop_id = GET_TARGET_ID(ha, c); tag = c->rx_id; } @@ -4103,6 +4133,7 @@ static int q2t_send_cmd_to_scst(scsi_qla_host_t *vha, atio_t *atio) if (IS_FWI2_CAPABLE(ha)) { atio7_entry_t *a = (atio7_entry_t *)atio; + sess = q2t_find_sess_by_s_id(tgt, a->fcp_hdr.s_id); if (unlikely(sess == NULL)) { TRACE_MGMT_DBG("qla2x00t(%ld): Unable to find " @@ -4166,6 +4197,7 @@ static int q2t_issue_task_mgmt(struct q2t_sess *sess, uint8_t *lun, */ while (!list_empty(&ha->unknown_atio_list)) { struct q2t_unknown_atio *u; + u = list_first_entry(&ha->unknown_atio_list, struct q2t_unknown_atio, unknown_atio_list_entry); TRACE_MGMT_DBG("qla2x00t(%ld): Clearing unknown " @@ -4300,12 +4332,14 @@ static int q2t_handle_task_mgmt(scsi_qla_host_t *vha, void *iocb) tgt = vha->tgt; if (IS_FWI2_CAPABLE(ha)) { atio7_entry_t *a = (atio7_entry_t *)iocb; + lun = (uint8_t *)&a->fcp_cmnd.lun; lun_size = sizeof(a->fcp_cmnd.lun); fn = a->fcp_cmnd.task_mgmt_flags; sess = q2t_find_sess_by_s_id(tgt, a->fcp_hdr.s_id); } else { notify_entry_t *n = (notify_entry_t *)iocb; + /* make it be in network byte order */ lun_data = swab16(le16_to_cpu(n->lun)); lun = (uint8_t *)&lun_data; @@ -4468,6 +4502,7 @@ static int q24_handle_els(scsi_qla_host_t *vha, notify24xx_entry_t *iocb) case ELS_ADISC: { struct q2t_tgt *tgt = vha->tgt; + if (tgt->link_reinit_iocb_pending) { q24_send_notify_ack(vha, &tgt->link_reinit_iocb, 0, 0, 0); tgt->link_reinit_iocb_pending = 0; @@ -4506,6 +4541,7 @@ static int q2t_cut_cmd_data_head(struct q2t_cmd *cmd, unsigned int offset) l += cmd->sg[i].length; if (l > offset) { int sg_offs = l - cmd->sg[i].length; + first_sg = i; if (cmd->sg[i].offset == 0) { first_page_offs = offset % PAGE_SIZE; @@ -4541,7 +4577,7 @@ static int q2t_cut_cmd_data_head(struct q2t_cmd *cmd, unsigned int offset) first_sg, first_page, first_page_offs, cmd->bufflen, cmd->sg_cnt); - sg = kmalloc(cnt * sizeof(sg[0]), GFP_KERNEL); + sg = kmalloc_array(cnt, sizeof(sg[0]), GFP_KERNEL); if (sg == NULL) { PRINT_ERROR("qla2x00t(%ld): Unable to allocate cut " "SG (len %zd)", cmd->tgt->vha->host_no, @@ -4555,6 +4591,7 @@ static int q2t_cut_cmd_data_head(struct q2t_cmd *cmd, unsigned int offset) cur_src = first_sg; if (first_page_offs != 0) { int fpgs; + sg_set_page(&sg[cur_dst], &sg_page(&cmd->sg[cur_src])[first_page], PAGE_SIZE - first_page_offs, first_page_offs); bufflen += sg[cur_dst].length; @@ -4657,6 +4694,7 @@ static void q24_handle_srr(scsi_qla_host_t *vha, struct srr_ctio *sctio, (scst_cmd_get_data_direction(&cmd->scst_cmd) & SCST_DATA_READ)) { uint32_t offset; int xmit_type; + offset = le32_to_cpu(imm->imm.notify_entry24.srr_rel_offs); if (q2t_srr_adjust_data(cmd, offset, &xmit_type) != 0) goto out_reject; @@ -4681,6 +4719,7 @@ static void q24_handle_srr(scsi_qla_host_t *vha, struct srr_ctio *sctio, (scst_cmd_get_data_direction(&cmd->scst_cmd) & SCST_DATA_WRITE)) { uint32_t offset; int xmit_type; + offset = le32_to_cpu(imm->imm.notify_entry24.srr_rel_offs); if (q2t_srr_adjust_data(cmd, offset, &xmit_type) != 0) goto out_reject; @@ -4751,6 +4790,7 @@ static void q2x_handle_srr(scsi_qla_host_t *vha, struct srr_ctio *sctio, if (q2t_has_data(cmd)) { uint32_t offset; int xmit_type; + offset = le32_to_cpu(imm->imm.notify_entry.srr_rel_offs); if (q2t_srr_adjust_data(cmd, offset, &xmit_type) != 0) goto out_reject; @@ -4773,6 +4813,7 @@ static void q2x_handle_srr(scsi_qla_host_t *vha, struct srr_ctio *sctio, if (q2t_has_data(cmd)) { uint32_t offset; int xmit_type; + offset = le32_to_cpu(imm->imm.notify_entry.srr_rel_offs); if (q2t_srr_adjust_data(cmd, offset, &xmit_type) != 0) goto out_reject; @@ -4949,6 +4990,7 @@ static void q2t_prepare_srr_imm(scsi_qla_host_t *vha, void *iocb) imm->srr_id, iocb24->srr_ui); if (tgt->imm_srr_id == tgt->ctio_srr_id) { int found = 0; + list_for_each_entry(sctio, &tgt->srr_ctio_list, srr_list_entry) { if (sctio->srr_id == imm->srr_id) { @@ -5064,6 +5106,7 @@ static void q2t_handle_imm_notify(scsi_qla_host_t *vha, void *iocb) case IMM_NTFY_LIP_LINK_REINIT: { struct q2t_tgt *tgt = vha->tgt; + TRACE(TRACE_MGMT, "qla2x00t(%ld): LINK REINIT (loop %#x, " "subcode %x)", vha->host_no, le16_to_cpu(iocb24->nport_handle), @@ -5359,6 +5402,7 @@ static void q24_atio_pkt(scsi_qla_host_t *vha, atio7_entry_t *atio) case IMMED_NOTIFY_TYPE: { notify_entry_t *pkt = (notify_entry_t *)atio; + if (unlikely(pkt->entry_status != 0)) { PRINT_ERROR("qla2x00t(%ld): Received ATIO packet %x " "with error status %x", vha->host_no, @@ -5426,6 +5470,7 @@ static void q2t_response_pkt(scsi_qla_host_t *vha, response_t *pkt) case CTIO_TYPE7: { ctio7_fw_entry_t *entry = (ctio7_fw_entry_t *)pkt; + TRACE_DBG("CTIO_TYPE7: instance %ld", vha->host_no); TRACE_BUFFER("Incoming CTIO7 packet data", entry, @@ -5440,6 +5485,7 @@ static void q2t_response_pkt(scsi_qla_host_t *vha, response_t *pkt) { atio_entry_t *atio; int rc; + atio = (atio_entry_t *)pkt; TRACE_DBG("ACCEPT_TGT_IO instance %ld status %04x " "lun %04x read/write %d data_length %04x " @@ -5481,6 +5527,7 @@ static void q2t_response_pkt(scsi_qla_host_t *vha, response_t *pkt) case CONTINUE_TGT_IO_TYPE: { ctio_common_entry_t *entry = (ctio_common_entry_t *)pkt; + TRACE_DBG("CONTINUE_TGT_IO: instance %ld", vha->host_no); TRACE_BUFFER("Incoming CTIO packet data", entry, REQUEST_ENTRY_SIZE); @@ -5493,6 +5540,7 @@ static void q2t_response_pkt(scsi_qla_host_t *vha, response_t *pkt) case CTIO_A64_TYPE: { ctio_common_entry_t *entry = (ctio_common_entry_t *)pkt; + TRACE_DBG("CTIO_A64: instance %ld", vha->host_no); TRACE_BUFFER("Incoming CTIO_A64 packet data", entry, REQUEST_ENTRY_SIZE); @@ -5510,6 +5558,7 @@ static void q2t_response_pkt(scsi_qla_host_t *vha, response_t *pkt) case NOTIFY_ACK_TYPE: if (tgt->notify_ack_expected > 0) { nack_entry_t *entry = (nack_entry_t *)pkt; + TRACE_DBG("NOTIFY_ACK seq %08x status %x", le16_to_cpu(entry->seq_id), le16_to_cpu(entry->status)); @@ -5538,6 +5587,7 @@ static void q2t_response_pkt(scsi_qla_host_t *vha, response_t *pkt) if (tgt->abts_resp_expected > 0) { abts24_resp_fw_entry_t *entry = (abts24_resp_fw_entry_t *)pkt; + TRACE_DBG("ABTS_RESP_24XX: compl_status %x", entry->compl_status); TRACE_BUFF_FLAG(TRACE_BUFF, "Incoming ABTS_RESP " @@ -5574,6 +5624,7 @@ static void q2t_response_pkt(scsi_qla_host_t *vha, response_t *pkt) case MODIFY_LUN_TYPE: if (tgt->modify_lun_expected > 0) { modify_lun_entry_t *entry = (modify_lun_entry_t *)pkt; + TRACE_DBG("MODIFY_LUN %x, imm %c%d, cmd %c%d", entry->status, (entry->operators & MODIFY_LUN_IMM_ADD) ? '+' @@ -5599,6 +5650,7 @@ static void q2t_response_pkt(scsi_qla_host_t *vha, response_t *pkt) case ENABLE_LUN_TYPE: { elun_entry_t *entry = (elun_entry_t *)pkt; + TRACE_DBG("ENABLE_LUN %x imm %u cmd %u ", entry->status, entry->immed_notify_count, entry->command_count); @@ -5835,8 +5887,10 @@ static void q2t_exec_sess_work(struct q2t_tgt *tgt, case Q2T_SESS_WORK_CMD: { struct q2t_cmd *cmd = prm->cmd; + if (IS_FWI2_CAPABLE(ha)) { atio7_entry_t *a = (atio7_entry_t *)&cmd->atio; + s_id = a->fcp_hdr.s_id; } else loop_id = GET_TARGET_ID(ha, (atio_entry_t *)&cmd->atio); @@ -5896,6 +5950,7 @@ send: case Q2T_SESS_WORK_CMD: { struct q2t_cmd *cmd = prm->cmd; + if (tgt->tm_to_unknown) { /* * Cmd might be already aborted behind us, so be safe @@ -5923,12 +5978,14 @@ send: if (IS_FWI2_CAPABLE(ha)) { atio7_entry_t *a = &prm->tm_iocb2; + iocb = a; lun = (uint8_t *)&a->fcp_cmnd.lun; lun_size = sizeof(a->fcp_cmnd.lun); fn = a->fcp_cmnd.task_mgmt_flags; } else { notify_entry_t *n = &prm->tm_iocb; + iocb = n; /* make it be in network byte order */ lun_data = swab16(le16_to_cpu(n->lun)); @@ -5962,6 +6019,7 @@ out_term: case Q2T_SESS_WORK_CMD: { struct q2t_cmd *cmd = prm->cmd; + TRACE_MGMT_DBG("Terminating work cmd %p", cmd); /* * cmd has not sent to SCST yet, so pass NULL as the second @@ -6195,8 +6253,10 @@ static int q2t_add_target(scsi_qla_host_t *vha) scst_get_tgt_name(tgt->scst_tgt)); if (vha->vp_idx == 0) { int i = 0; + while (1) { const struct attribute *a = q2t_hw_tgt_attrs[i]; + if (a == NULL) break; rc = sysfs_create_file(scst_sysfs_get_tgt_kobj(tgt->scst_tgt), a); @@ -6209,8 +6269,10 @@ static int q2t_add_target(scsi_qla_host_t *vha) } } else { int i = 0; + while (1) { const struct attribute *a = q2t_npiv_tgt_attrs[i]; + if (a == NULL) break; rc = sysfs_create_file(scst_sysfs_get_tgt_kobj(tgt->scst_tgt), a); diff --git a/qla2x00t/qla2x00-target/qla2x00t.h b/qla2x00t/qla2x00-target/qla2x00t.h index 5ff441f4b..242fe5554 100644 --- a/qla2x00t/qla2x00-target/qla2x00t.h +++ b/qla2x00t/qla2x00-target/qla2x00t.h @@ -31,7 +31,7 @@ /* Version numbers, the same as for the kernel */ #define Q2T_VERSION(a, b, c, d) (((a) << 030) + ((b) << 020) + (c) << 010 + (d)) #define Q2T_VERSION_CODE Q2T_VERSION(4, 1, 0, 0) -#define Q2T_VERSION_STRING "3.1.0-pre1" +#define Q2T_VERSION_STRING "3.2.0-pre1" #define Q2T_PROC_VERSION_NAME "version" #define Q2T_MAX_CDB_LEN 16 diff --git a/qla2x00t/qla2x_tgt.h b/qla2x00t/qla2x_tgt.h index 4ce125311..99956b747 100644 --- a/qla2x00t/qla2x_tgt.h +++ b/qla2x00t/qla2x_tgt.h @@ -124,6 +124,7 @@ qla2x00_send_enable_lun(scsi_qla_host_t *vha, bool enable) if (!IS_FWI2_CAPABLE(ha)) { unsigned long flags; + spin_lock_irqsave(&ha->hardware_lock, flags); __qla2x00_send_enable_lun(vha, enable); spin_unlock_irqrestore(&ha->hardware_lock, flags); @@ -159,6 +160,7 @@ static inline bool qla_firmware_active(scsi_qla_host_t *vha) { struct qla_hw_data *ha = vha->hw; struct scsi_qla_host *base_vha = pci_get_drvdata(ha->pdev); + return qla_tgt_mode_enabled(base_vha) || qla_ini_mode_enabled(base_vha); } diff --git a/qla2x00t/qla2x_tgt_def.h b/qla2x00t/qla2x_tgt_def.h index 28bf9ee47..d96d6f1b5 100644 --- a/qla2x00t/qla2x_tgt_def.h +++ b/qla2x00t/qla2x_tgt_def.h @@ -148,7 +148,7 @@ typedef struct { uint16_t reserved_5; uint16_t timeout; /* 0 = 30 seconds, 0xFFFF = disable */ uint16_t reserved_6[20]; -} __attribute__((packed)) elun_entry_t; +} __packed elun_entry_t; #define ENABLE_LUN_SUCCESS 0x01 #define ENABLE_LUN_RC_NONZERO 0x04 #define ENABLE_LUN_INVALID_REQUEST 0x06 @@ -179,7 +179,7 @@ typedef struct { uint16_t reserved_5; uint16_t timeout; /* 0 = 30 seconds, 0xFFFF = disable */ uint16_t reserved_7[20]; -} __attribute__((packed)) modify_lun_entry_t; +} __packed modify_lun_entry_t; #define MODIFY_LUN_SUCCESS 0x01 #define MODIFY_LUN_CMD_ADD BIT_0 #define MODIFY_LUN_CMD_SUB BIT_1 @@ -219,7 +219,7 @@ typedef struct { uint16_t srr_ox_id; uint8_t reserved_2[30]; uint16_t ox_id; -} __attribute__((packed)) notify_entry_t; +} __packed notify_entry_t; #endif #ifndef NOTIFY_ACK_TYPE @@ -250,7 +250,7 @@ typedef struct { uint8_t srr_reject_code_expl; uint8_t reserved_2[26]; uint16_t ox_id; -} __attribute__((packed)) nack_entry_t; +} __packed nack_entry_t; #define NOTIFY_ACK_SRR_FLAGS_ACCEPT 0 #define NOTIFY_ACK_SRR_FLAGS_REJECT 1 @@ -287,7 +287,7 @@ typedef struct { uint8_t initiator_port_name[WWN_SIZE]; /* on qla23xx */ uint16_t reserved_32[6]; uint16_t ox_id; -} __attribute__((packed)) atio_entry_t; +} __packed atio_entry_t; #endif #ifndef CONTINUE_TGT_IO_TYPE @@ -314,7 +314,7 @@ typedef struct { uint16_t scsi_status; uint32_t transfer_length; uint32_t dseg_0_address[0]; -} __attribute__((packed)) ctio_common_entry_t; +} __packed ctio_common_entry_t; #define ATIO_PATH_INVALID 0x07 #define ATIO_CANT_PROV_CAP 0x16 #define ATIO_CDB_VALID 0x3D @@ -333,7 +333,7 @@ typedef struct { uint32_t dseg_1_length; /* Data segment 1 length. */ uint32_t dseg_2_address; /* Data segment 2 address. */ uint32_t dseg_2_length; /* Data segment 2 length. */ -} __attribute__((packed)) ctio_entry_t; +} __packed ctio_entry_t; #define CTIO_SUCCESS 0x01 #define CTIO_ABORTED 0x02 #define CTIO_INVALID_RX_ID 0x08 @@ -371,7 +371,7 @@ typedef struct { uint16_t scsi_status; uint16_t response_length; uint8_t sense_data[26]; -} __attribute__((packed)) ctio_ret_entry_t; +} __packed ctio_ret_entry_t; #endif #define ATIO_TYPE7 0x06 /* Accept target I/O entry for 24xx */ @@ -389,7 +389,7 @@ typedef struct { uint16_t ox_id; uint16_t rx_id; uint32_t parameter; -} __attribute__((packed)) fcp_hdr_t; +} __packed fcp_hdr_t; typedef struct { uint8_t d_id[3]; @@ -404,7 +404,7 @@ typedef struct { uint16_t rx_id; uint16_t ox_id; uint32_t parameter; -} __attribute__((packed)) fcp_hdr_le_t; +} __packed fcp_hdr_le_t; #define F_CTL_EXCH_CONTEXT_RESP BIT_23 #define F_CTL_SEQ_CONTEXT_RESIP BIT_22 @@ -449,7 +449,7 @@ typedef struct { */ uint8_t add_cdb[4]; /* uint32_t data_length; */ -} __attribute__((packed)) fcp_cmnd_t; +} __packed fcp_cmnd_t; /* * ISP queue - Accept Target I/O (ATIO) type 7 entry for 24xx structure @@ -470,7 +470,7 @@ typedef struct { #define ATIO_EXCHANGE_ADDRESS_UNKNOWN 0xFFFFFFFF fcp_hdr_t fcp_hdr; fcp_cmnd_t fcp_cmnd; -} __attribute__((packed)) atio7_entry_t; +} __packed atio7_entry_t; #define CTIO_TYPE7 0x12 /* Continue target I/O entry (for 24xx) */ @@ -494,7 +494,7 @@ typedef struct { uint8_t initiator_id[3]; uint8_t reserved; uint32_t exchange_addr; -} __attribute__((packed)) ctio7_common_entry_t; +} __packed ctio7_common_entry_t; typedef struct { ctio7_common_entry_t common; @@ -509,7 +509,7 @@ typedef struct { uint32_t reserved3; uint32_t dseg_0_address[2]; /* Data segment 0 address. */ uint32_t dseg_0_length; /* Data segment 0 length. */ -} __attribute__((packed)) ctio7_status0_entry_t; +} __packed ctio7_status0_entry_t; typedef struct { ctio7_common_entry_t common; @@ -521,7 +521,7 @@ typedef struct { uint16_t response_len; uint16_t reserved; uint8_t sense_data[24]; -} __attribute__((packed)) ctio7_status1_entry_t; +} __packed ctio7_status1_entry_t; typedef struct { uint8_t entry_type; /* Entry type. */ @@ -542,7 +542,7 @@ typedef struct { uint16_t reserved3; uint32_t relative_offset; uint8_t reserved4[24]; -} __attribute__((packed)) ctio7_fw_entry_t; +} __packed ctio7_fw_entry_t; /* CTIO7 flags values */ #define CTIO7_FLAGS_SEND_STATUS BIT_15 @@ -589,7 +589,7 @@ typedef struct { uint8_t reserved_7; uint16_t reserved_8; uint16_t ox_id; -} __attribute__((packed)) notify24xx_entry_t; +} __packed notify24xx_entry_t; #define ELS_PLOGI 0x3 #define ELS_FLOGI 0x4 @@ -627,13 +627,13 @@ typedef struct { uint8_t srr_reject_code; uint8_t reserved_5[7]; uint16_t ox_id; -} __attribute__((packed)) nack24xx_entry_t; +} __packed nack24xx_entry_t; /* * ISP queue - ABTS received/response entries structure definition for 24xx. */ #define ABTS_RECV_24XX 0x54 /* ABTS received (for 24xx) */ -#define ABTS_RESP_24XX 0x55 /* ABTS responce (for 24xx) */ +#define ABTS_RESP_24XX 0x55 /* ABTS response (for 24xx) */ typedef struct { uint8_t entry_type; /* Entry type. */ @@ -655,7 +655,7 @@ typedef struct { fcp_hdr_le_t fcp_hdr_le; uint8_t reserved_4[16]; uint32_t exchange_addr_to_abort; -} __attribute__((packed)) abts24_recv_entry_t; +} __packed abts24_recv_entry_t; #define ABTS_PARAM_ABORT_SEQ BIT_0 @@ -669,7 +669,7 @@ typedef struct { uint16_t ox_id; uint16_t high_seq_cnt; uint16_t low_seq_cnt; -} __attribute__((packed)) ba_acc_le_t; +} __packed ba_acc_le_t; typedef struct { uint8_t vendor_uniq; @@ -678,7 +678,7 @@ typedef struct { #define BA_RJT_REASON_CODE_INVALID_COMMAND 0x1 #define BA_RJT_REASON_CODE_UNABLE_TO_PERFORM 0x9 uint8_t reserved; -} __attribute__((packed)) ba_rjt_le_t; +} __packed ba_rjt_le_t; typedef struct { uint8_t entry_type; /* Entry type. */ @@ -703,10 +703,10 @@ typedef struct { union { ba_acc_le_t ba_acct; ba_rjt_le_t ba_rjt; - } __attribute__((packed)) payload; + } __packed payload; uint32_t reserved_4; uint32_t exchange_addr_to_abort; -} __attribute__((packed)) abts24_resp_entry_t; +} __packed abts24_resp_entry_t; typedef struct { uint8_t entry_type; /* Entry type. */ @@ -734,7 +734,7 @@ typedef struct { #define ABTS_RESP_SUBCODE_ERR_ABORTED_EXCH_NOT_TERM 0x1E uint32_t error_subcode2; uint32_t exchange_addr_to_abort; -} __attribute__((packed)) abts24_resp_fw_entry_t; +} __packed abts24_resp_fw_entry_t; /********************************************************************\ * Type Definitions used by initiator & target halves diff --git a/qla2x00t/qla_bsg.h b/qla2x00t/qla_bsg.h index 70caa63a8..1128cf0d1 100644 --- a/qla2x00t/qla_bsg.h +++ b/qla2x00t/qla_bsg.h @@ -126,12 +126,12 @@ struct qla84_msg_mgmt { struct qla_bsg_a84_mgmt { struct qla84_msg_mgmt mgmt; -} __attribute__ ((packed)); +} __packed; struct qla_scsi_addr { uint16_t bus; uint16_t target; -} __attribute__ ((packed)); +} __packed; struct qla_ext_dest_addr { union { @@ -144,13 +144,13 @@ struct qla_ext_dest_addr { #define EXT_DEF_TYPE_WWPN 2 uint16_t lun; uint16_t padding[2]; -} __attribute__ ((packed)); +} __packed; struct qla_port_param { struct qla_ext_dest_addr fc_scsi_addr; uint16_t mode; uint16_t speed; -} __attribute__ ((packed)); +} __packed; /* FRU VPD */ diff --git a/qla2x00t/qla_def.h b/qla2x00t/qla_def.h index 3f2dcb32d..83944e879 100644 --- a/qla2x00t/qla_def.h +++ b/qla2x00t/qla_def.h @@ -361,7 +361,7 @@ struct device_reg_2xxx { uint16_t mailbox6; uint16_t mailbox7; uint16_t unused_2[59]; /* Gap */ - } __attribute__((packed)) isp2100; + } __packed isp2100; struct { /* Request Queue */ uint16_t req_q_in; /* In-Pointer */ @@ -412,7 +412,7 @@ struct device_reg_2xxx { uint16_t mailbox31; uint16_t fb_cmd; uint16_t unused_4[10]; /* Gap */ - } __attribute__((packed)) isp2300; + } __packed isp2300; } u; uint16_t fpm_diag_config; @@ -469,7 +469,7 @@ struct device_reg_2xxx { uint16_t mailbox21; uint16_t mailbox22; uint16_t mailbox23; /* Also probe reg. */ - } __attribute__((packed)) isp2200; + } __packed isp2200; } u_end; }; diff --git a/qla2x00t/qla_isr.c b/qla2x00t/qla_isr.c index 8e3652d2a..5f2923dbe 100644 --- a/qla2x00t/qla_isr.c +++ b/qla2x00t/qla_isr.c @@ -2632,8 +2632,8 @@ qla24xx_enable_msix(struct qla_hw_data *ha, struct rsp_que *rsp) struct qla_msix_entry *qentry; scsi_qla_host_t *vha = pci_get_drvdata(ha->pdev); - entries = kzalloc(sizeof(struct msix_entry) * ha->msix_count, - GFP_KERNEL); + entries = kcalloc(ha->msix_count, sizeof(struct msix_entry), + GFP_KERNEL); if (!entries) { ql_log(ql_log_warn, vha, 0x00bc, "Failed to allocate memory for msix_entry.\n"); @@ -2664,8 +2664,8 @@ msix_failed: } ha->max_rsp_queues = ha->msix_count - 1; } - ha->msix_entries = kzalloc(sizeof(struct qla_msix_entry) * - ha->msix_count, GFP_KERNEL); + ha->msix_entries = kcalloc(ha->msix_count, + sizeof(struct qla_msix_entry), GFP_KERNEL); if (!ha->msix_entries) { ql_log(ql_log_fatal, vha, 0x00c8, "Failed to allocate memory for ha->msix_entries.\n"); diff --git a/qla2x00t/qla_nx.c b/qla2x00t/qla_nx.c index f5b5d837e..6978785de 100644 --- a/qla2x00t/qla_nx.c +++ b/qla2x00t/qla_nx.c @@ -1222,7 +1222,7 @@ qla82xx_pinit_from_rom(scsi_qla_host_t *vha) ql_log(ql_log_info, vha, 0x0072, "%d CRB init values found in ROM.\n", n); - buf = kmalloc(n * sizeof(struct crb_addr_pair), GFP_KERNEL); + buf = kmalloc_array(n, sizeof(struct crb_addr_pair), GFP_KERNEL); if (buf == NULL) { ql_log(ql_log_fatal, vha, 0x010c, "Unable to allocate memory.\n"); diff --git a/qla2x00t/qla_os.c b/qla2x00t/qla_os.c index 401778dcf..e5434f236 100644 --- a/qla2x00t/qla_os.c +++ b/qla2x00t/qla_os.c @@ -367,7 +367,7 @@ static int qla2x00_alloc_queues(struct qla_hw_data *ha, struct req_que *req, struct rsp_que *rsp) { scsi_qla_host_t *vha = pci_get_drvdata(ha->pdev); - ha->req_q_map = kzalloc(sizeof(struct req_que *) * ha->max_req_queues, + ha->req_q_map = kcalloc(ha->max_req_queues, sizeof(struct req_que *), GFP_KERNEL); if (!ha->req_q_map) { ql_log(ql_log_fatal, vha, 0x003b, @@ -375,7 +375,7 @@ static int qla2x00_alloc_queues(struct qla_hw_data *ha, struct req_que *req, goto fail_req_map; } - ha->rsp_q_map = kzalloc(sizeof(struct rsp_que *) * ha->max_rsp_queues, + ha->rsp_q_map = kcalloc(ha->max_rsp_queues, sizeof(struct rsp_que *), GFP_KERNEL); if (!ha->rsp_q_map) { ql_log(ql_log_fatal, vha, 0x003c, @@ -3286,8 +3286,9 @@ qla2x00_mem_alloc(struct qla_hw_data *ha, uint16_t req_len, uint16_t rsp_len, #ifdef CONFIG_SCSI_QLA2XXX_TARGET if (IS_FWI2_CAPABLE(ha)) { - ha->tgt_vp_map = kzalloc(sizeof(struct qla_tgt_vp_map) * - MAX_MULTI_ID_FABRIC, GFP_KERNEL); + ha->tgt_vp_map = kcalloc(MAX_MULTI_ID_FABRIC, + sizeof(struct qla_tgt_vp_map), + GFP_KERNEL); if (!ha->tgt_vp_map) goto fail_free_init_cb; @@ -3433,8 +3434,9 @@ qla2x00_mem_alloc(struct qla_hw_data *ha, uint16_t req_len, uint16_t rsp_len, (*rsp)->ring); /* Allocate memory for NVRAM data for vports */ if (ha->nvram_npiv_size) { - ha->npiv_info = kzalloc(sizeof(struct qla_npiv_entry) * - ha->nvram_npiv_size, GFP_KERNEL); + ha->npiv_info = kcalloc(ha->nvram_npiv_size, + sizeof(struct qla_npiv_entry), + GFP_KERNEL); if (!ha->npiv_info) { ql_log_pci(ql_log_fatal, ha->pdev, 0x002d, "Failed to allocate memory for npiv_info.\n"); diff --git a/scripts/generate-kernel-patch b/scripts/generate-kernel-patch index af2f3a825..ddb35ebf1 100755 --- a/scripts/generate-kernel-patch +++ b/scripts/generate-kernel-patch @@ -302,7 +302,8 @@ do done -scst_03_public_headers="scst/include/scst.h scst/include/scst_const.h scst/include/backport.h" +scst_03_public_headers="scst/include/scst.h scst/include/scst_const.h \ +scst/include/scst_event.h scst/include/backport.h" scst_04_main="scst/src/scst_main.c scst/src/scst_module.c scst/src/scst_priv.h" scst_05_targ="scst/src/scst_targ.c" scst_06_lib="scst/src/scst_lib.c" diff --git a/scst.spec.in b/scst.spec.in index 4d3b55898..e7138ceef 100644 --- a/scst.spec.in +++ b/scst.spec.in @@ -8,7 +8,8 @@ # version number when building on a koji build server. # - Otherwise use the version number of the running kernel. %{!?kversion: - %{?kdir:%define kversion %{expand:%%(make -sC %{kdir} kernelversion)}} + %{?kdir:%define kversion %{expand:%%( + make -sC "%{kdir}" kernelversion | grep -v ^make)}} %{!?kdir: %define kversion %{expand:%%( if rpm --quiet -q kernel-headers; then diff --git a/scst/README b/scst/README index 73331a9ec..1177aba07 100644 --- a/scst/README +++ b/scst/README @@ -1,7 +1,7 @@ Generic SCSI target mid-level for Linux (SCST) ============================================== -Version 3.1.0, XX XXXXX 2014 +Version 3.2.0, XX XXXXX 2015 ---------------------------- SCST is designed to provide unified, consistent interface between SCSI @@ -508,6 +508,8 @@ documentation for your dev handlers for more info about it as well as SysfsRules file for more info about common to all dev handlers rules. SCST dev handlers can have the following common entries: + - block - allows to temporary block and unblock this device. See below. + - exported - subdirectory containing links to all LUNs where this device was exported. @@ -531,6 +533,35 @@ SCST dev handlers can have the following common entries: - type - SCSI type of this device +Attribute "block" allows to temporary block and unblock this device. +"Blocking" means that no new commands for this device will go into the +execution stage, but instead will be suspended just before it. The +blocked state is not reached until queue of the corresponding device is +completely drained. You can also call this state "frozen". It is useful +in many cases, like consistent snapshots and graceful shutdown. + +On write "block" entry allows the following 3 types of parameters: + + - 1 - block device synchronously, i.e. don't return until this device + becomes blocked, i.e. until queue of it is not completely drained. Can + be called as many times as needed. + + - 11 params - block device asynchronously, i.e. return immediately. + Notification about completing is delivered using SCST_EVENT_EXT_BLOCKING_DONE + event. "Params" delivered to it as is in "data" payload. Can be + called as many times as needed. Alternatively, status of blocking could be + polled by reading this attributes until the second number reaches 0 + (see below). + + - 0 - unblock this device. + +Reading from "block" entry returns two numbers separated by space: + +1. How many times this device was blocked, i.e. how many times writing +"0" to it is needed to unblock this device. + +2. Boolean (0 or 1) if blocking, if any, is done (0) or still pending (1). + See below for more information about other entries of this subdirectory of the standard SCST dev handlers. @@ -1257,6 +1288,7 @@ Each vdisk_fileio's device has the following attributes in For example: /sys/kernel/scst_tgt/devices/disk1 +|-- block |-- blocksize |-- exported | |-- export0 -> ../../../targets/iscsi/iqn.2006-10.net.vlnb:tgt/luns/0 @@ -1420,16 +1452,18 @@ persistent reservations from this device are released, upon reconnect the initiators will see it. -Implicit ALUA Support ---------------------- +ALUA Support +------------ -SCST supports implicit asymmetric logical unit access (ALUA). Implicit ALUA is -a feature defined by the ANSI T10 SCSI committee that allows a target to tell -the initiator which path to use in a multipath setup. The redundant paths -between initiator and target can be used either for redundancy or for load -sharing purposes. The target can either be a single target system running SCST -with multiple communication interfaces or two target systems each running SCST -and configured in a high availability setup. +SCST supports both implicit and explicit asymmetric logical unit access +(ALUA). ALUA is a feature defined by the ANSI T10 SCSI committee. It +allows a target to tell the initiator which path to use in a multipath +setup plus, in the explicit case, control state of each path via SET +TARGET PORT GROUPS SCSI command. The redundant paths between initiator +and target can be used either for redundancy or for load sharing +purposes. The target can either be a single target system running SCST +with multiple communication interfaces or two target systems each +running SCST and configured in a high availability setup. In the SPC-4 standard the following concepts are defined related to ALUA: * Relative target port ID. A number between 1 and 65535 that uniquely @@ -1450,17 +1484,17 @@ ANSI T10 standard called SPC-4. ALUA support in SCST .................... -SCST allows to define implicit ALUA settings for each unique combination of -SCST device and SCST target. An initiator however queries ALUA settings by -sending an appropriate SCSI command to a specific LUN of an SCST target. Each -such LUN maps uniquely to an SCST device. For hardware SCST target drivers, -e.g. ib_srpt, there is a one-to-one correspondence between SCST target and -SCSI target port. With other SCST targets, e.g. iSCSI-SCST, by default the -only relationship between SCST targets and SCSI target ports is that all SCST -targets defined on a system are visible via all SCSI target ports. See also -the iSCSI-SCST documentation about the allowed_portal attribute for -information about how to associate iSCSI targets with a single physical -interface. +SCST allows to define ALUA settings for each unique combination of SCST +device and SCST target. An initiator however queries ALUA settings by +sending an appropriate SCSI command to a specific LUN of an SCST target. +Each such LUN maps uniquely to an SCST device. For hardware SCST target +drivers, e.g. ib_srpt, there is a one-to-one correspondence between SCST +target and SCSI target port. With other SCST targets, e.g. iSCSI-SCST, +by default the only relationship between SCST targets and SCSI target +ports is that all SCST targets defined on a system are visible via all +SCSI target ports. See also the iSCSI-SCST documentation about the +allowed_portal attribute for information about how to associate iSCSI +targets with a single physical interface. Notes: - In a H.A. setup it is the responsibility of the user to synchronize ALUA @@ -1483,7 +1517,7 @@ Notes: Configuring ALUA in SCST ........................ -SCST allows to configure the following settings related to implicit ALUA +SCST allows to configure the following settings related to ALUA for each unique combination of SCST target and virtual SCST device (vdisk_fileio, vdisk_blockio, vcdrom, ...): * The target port group asymmetric access state. SCST supports all ALUA port @@ -1581,15 +1615,62 @@ DEVICE_GROUP dgroup2 { } } +Explicit ALUA +............. + +To enable explicit ALUA you need in addition to the above settings set +expl_alua device attribute to 1 (by default it is 0). Also you need to +run stpgd and supply to it path to a script or program, which will +perform actual path state switching on SET TARGET PORT GROUPS command, +for instance, by calling drbdadm. For more information see stpgd README +as well as sample script scst_on_stpg. + +DRBD compatibility +.................. + +DRBD does not allow to open its device on the secondary as well as does +not allow to perform primary to secondary transition, if this device is +open. + +SCST BLOCKIO handler has all the necessary support for this behavior. If +you write new ALUA state in the "state" attribute, SCST BLOCKIO handler +before transition closes the open devices' handles and after transition +reopens them, if the new state is active or nonoptimized. + +Thus, the recommended implicit ALUA state change procedure for primary +to secondary transition is: + +1. Block all involved SCST devices using "block" sysfs attribute (see +above). Wait until the blocking finished. + +2. Change the ALUA state to "transitioning". At this moment all open +file handles will be closed. + +3. Perform the DRBD state transition + +4. Change the ALUA state to your desired secondary state. + +5. Unblock the blocked on step 1 devices. + +Optionally, if your initiators support Transitioning ALUA state, for +more responsive behavior the blocked devices can be unblocked +immediately after step (2). However, not all initiators correctly +behave, if they receive ASYMMETRIC STATE TRANSITION sense. + +For the secondary to primary transition procedure is similar. + +In case of explicit ALUA, SCST automatically performs the necessary +devices blocking around sending SCST_EVENT_STPG_USER_INVOKE event. Checking the Target Configuration ................................. -One way to verify the implicit ALUA configuration from a Linux initiator is -via the commands provided in the sg3_utils package. The first step is to -verify whether for a certain LUN implicit ALUA has been configured on the -target. This is possible by checking whether the TPGS=1 text appears in the -sg_inq output, where /dev/sdb is a device node created by the ib_srp initiator: +One way to verify the ALUA configuration from a Linux initiator is via +the commands provided in the sg3_utils package. The first step is to +verify whether for a certain LUN ALUA has been configured on the target. +This is possible by checking whether the TPGS=1 text appears in the +sg_inq output, where /dev/sdb is a device node created by the ib_srp +initiator: # sg_inq /dev/sdb standard INQUIRY: @@ -1649,9 +1730,11 @@ Device Identification VPD page: Initiator Support ................. -On Linux systems implicit ALUA support is provided by the scsi_dh_alua kernel -driver in combination with the user space multipathd daemon. You will have to -modify at least the following in /etc/multipath.conf to enable implicit ALUA: +On Linux systems ALUA support is provided by the scsi_dh_alua kernel +driver in combination with the user space multipathd daemon. You will +have to modify at least the following in /etc/multipath.conf to enable +ALUA: + * hardware_handler "1 alua" * prio alua * path_grouping_policy group_by_prio @@ -1700,6 +1783,126 @@ information about ALUA support in Windows Server, see also: (http://msdn.microsoft.com/en-us/library/gg607458%28v=vs.85%29.aspx). +VAAI +---- + +SCST supports all 3 VAAI SCSI commands: WRITE SAME, COMPARE AND WRITE +(ATS) and EXTENDED COPY. Additionally, it supports not directly related +to VAAI Thin Provisioning capabilities, particularly, UNMAP SCSI +commands, WRITE SAME with UNMAP bit as well as thin provisioning related +devices' sysfs attributes (see above). + +In some cases dev handlers should perform some manual actions to fully +benefit from SCST VAAI implementation. Those actions described in the +implementation notes below. For vdisk and fileio_tgt handlers they have +already been implemented. + + +Implementation notes +.................... + +WRITE SAME +~~~~~~~~~~ + +WRITE SAME command supports 2 modes: + +1. Manual writing mode. In this mode WRITE SAME generates a set of +internal WRITE(16) SCSI commands to perform requested writing. + +2. Remap mode. In this mode a dev handler, if supported, can remap being +written blocks to a single block and then tell SCST to manually write +parts of the requested area, which for some reason can not be remapped. + +In both cases dev handlers should call from WRITE SAME command handler +scst_write_same() function. This function as the second argument gets +array of descriptors where to write the requested block of data. Last +element in this array must have len 0. If this argument is NULL, then +the whole area will be manually written by SCST. This value should be +used by dev handlers not supporting remapping blocks. + +User space dev handlers should use SCST_EXEC_REPLY_DO_WRITE_SAME +reply_type of SCST_USER_EXEC subcommand. See scst_user doc for more +info. + + +COMPARE AND WRITE +~~~~~~~~~~~~~~~~~ + +COMPARE AND WRITE implemented by SCST a set of read, compare and write +actions done in atomic manner against affected blocks as well as regular +RESERVE SCSI commands. Particularly, COMPARE AND WRITE doesn't need any +queue flushing and unlimited number of COMPARE AND WRITE commands on +different blocks can be executed simultaneously. + +The read and write actions implemented as generation of internal +READ(16) and WRITE(16) SCSI commands. + +COMPARE AND WRITE command is completely transparent to dev handlers +(they only see the corresponding READ(16) and WRITE(16) commands), so +doesn't require any manual actions from them. + + +EXTENDED COPY +~~~~~~~~~~~~~ + +SCST implements EXTENDED COPY via internal Copy Manager target. This +target has the following specific attributes in its sysfs: + + - allow_not_connected_copy - if not set (default), an initiator can +perform copy only between devices it has direct access to via any +target/session. If set, any initiator can copy between any devices in +the system. + + - mgmt - this attribute allows to control data from which devices can +be copied using Copy Manager. By default, devices belonging to dev +handlers with flag auto_cm_assignment_possible set are auto assigned to +the Copy Manager on the registration. Currently, only vdisk has this +flag set, so all other devices (pass-through, user space, etc.) should +be assigned to the Copy Manager manually. + +Mgmt attribute supports the following commands: + + - add [vname|H:C:I:L] - adds device with name vname or H:C:I:L numbers +(pass-through) to the Copy Manager. + + - del [vname|H:C:I:L] - deletes device with name vname or H:C:I:L numbers +(pass-through) from the Copy Manager. + + +Internally SCST implements EXTENDED COPY as generation of sets of +internal READ(16) and WRITE(16) SCSI commands. Dev handlers don't need +any manual actions to use it. + + +Also SCST provides for dev handlers possibility to remap blocks instead +of copy them, if they support this feature. It allows them to perform +EXTENDED COPY command much faster by just metadata update of their +backend storage, which supposed to be nearly instantaneous. + +To use this feature, a dev handler should setup ext_copy_remap() +callback in its struct scst_dev_type. This callback is called by SCST +during EXTENDED COPY command processing to let the dev handler try to +remap affected blocks at first. + +Upon finish, the dev handler should call scst_ext_copy_remap_done(). In +case of error, the dev handler should set the corresponding sense to cmd +and then also call scst_ext_copy_remap_done(cmd, NULL, 0). + +If dev handler is not able to remap any part of the segment, if should +kmalloc(), then fill all leftover subsegments and supply them to +scst_ext_copy_remap_done(). SCST then will copy the subsegments using +internal copy machine, then kfree() the supplied array. If the dev +handler is not able to remap the whole segment, it can simply directly +supply the original segment to scst_ext_copy_remap_done(). + +It is highly recommended that in normal circumstances dev handlers call +scst_ext_copy_remap_done() from another thread context than one where +ext_copy_remap() callback was originally called, because otherwise there +could be recursion in the segments processing. Hopefully, this thread +context switch is natural for such potentially long operation as +EXTENDED COPY. + + Caching ------- diff --git a/scst/README.dlm b/scst/README.dlm index 7191d269a..435e6d8bb 100644 --- a/scst/README.dlm +++ b/scst/README.dlm @@ -143,33 +143,6 @@ change the t10_dev_id if cluster mode has been enabled. Testing ------- -A quick test to verify whether PR synchronization is working is as follows: -* Start SCST and enable cluster_mode for a volume on both nodes. -* Log in from an initiator system to the two target systems. -* Look up the names of the paths on the initiator system with lsscsi. Assume - that these names are /dev/sdb and /dev/sdc. -* Run the following commands on the initiator system: - # sg_persist -s /dev/sdc - # sg_persist -n -o --register --param-sark 0xabc123 /dev/sdb - # sg_persist -s /dev/sdc - SCST_BIO ram0 310 - Peripheral device type: disk - PR generation=0x1 - Key=0xabc123 - All target ports bit clear - Relative port address: 0x1 - not reservation holder - Transport Id of initiator: - iSCSI name and session id: iqn... -* Run the following command on a target system: - # dlm_tool ls - dlm lockspaces - name scst-179ee149-ram0 - id 0x2b2d1c39 - flags 0x00000000 - change member 2 joined 1 remove 0 failed 0 seq 1,1 - members 1 2 - Two examples of test suites for the cluster PR support code are: * The SCSI conformance tests in the libiscsi project. * The Windows Cluster Validation Tests diff --git a/scst/README_in-tree b/scst/README_in-tree index 14255f774..8f8205f61 100644 --- a/scst/README_in-tree +++ b/scst/README_in-tree @@ -367,6 +367,8 @@ documentation for your dev handlers for more info about it as well as SysfsRules file for more info about common to all dev handlers rules. SCST dev handlers can have the following common entries: + - block - allows to temporary block and unblock this device. See below. + - exported - subdirectory containing links to all LUNs where this device was exported. @@ -390,6 +392,35 @@ SCST dev handlers can have the following common entries: - type - SCSI type of this device +Attribute "block" allows to temporary block and unblock this device. +"Blocking" means that no new commands for this device will go into the +execution stage, but instead will be suspended just before it. The +blocked state is not reached until queue of the corresponding device is +completely drained. You can also call this state "frozen". It is useful +in many cases, like consistent snapshots and graceful shutdown. + +On write "block" entry allows the following 3 types of parameters: + + - 1 - block device synchronously, i.e. don't return until this device + becomes blocked, i.e. until queue of it is not completely drained. Can + be called as many times as needed. + + - 11 params - block device asynchronously, i.e. return immediately. + Notification about completing is delivered using SCST_EVENT_EXT_BLOCKING_DONE + event. "Params" delivered to it as is in "data" payload. Can be + called as many times as needed. Alternatively, status of blocking could be + polled by reading this attributes until the second number reaches 0 + (see below). + + - 0 - unblock this device. + +Reading from "block" entry returns two numbers separated by space: + +1. How many times this device was blocked, i.e. how many times writing +"0" to it is needed to unblock this device. + +2. Boolean (0 or 1) if blocking, if any, is done (0) or still pending (1). + See below for more information about other entries of this subdirectory of the standard SCST dev handlers. @@ -1110,6 +1141,7 @@ Each vdisk_fileio's device has the following attributes in For example: /sys/kernel/scst_tgt/devices/disk1 +|-- block |-- blocksize |-- exported | |-- export0 -> ../../../targets/iscsi/iqn.2006-10.net.vlnb:tgt/luns/0 @@ -1268,16 +1300,18 @@ persistent reservations from this device are released, upon reconnect the initiators will see it. -Implicit ALUA Support ---------------------- +ALUA Support +------------ -SCST supports implicit asymmetric logical unit access (ALUA). Implicit ALUA is -a feature defined by the ANSI T10 SCSI committee that allows a target to tell -the initiator which path to use in a multipath setup. The redundant paths -between initiator and target can be used either for redundancy or for load -sharing purposes. The target can either be a single target system running SCST -with multiple communication interfaces or two target systems each running SCST -and configured in a high availability setup. +SCST supports both implicit and explicit asymmetric logical unit access +(ALUA). ALUA is a feature defined by the ANSI T10 SCSI committee. It +allows a target to tell the initiator which path to use in a multipath +setup plus, in the explicit case, control state of each path via SET +TARGET PORT GROUPS SCSI command. The redundant paths between initiator +and target can be used either for redundancy or for load sharing +purposes. The target can either be a single target system running SCST +with multiple communication interfaces or two target systems each +running SCST and configured in a high availability setup. In the SPC-4 standard the following concepts are defined related to ALUA: * Relative target port ID. A number between 1 and 65535 that uniquely @@ -1298,17 +1332,17 @@ ANSI T10 standard called SPC-4. ALUA support in SCST .................... -SCST allows to define implicit ALUA settings for each unique combination of -SCST device and SCST target. An initiator however queries ALUA settings by -sending an appropriate SCSI command to a specific LUN of an SCST target. Each -such LUN maps uniquely to an SCST device. For hardware SCST target drivers, -e.g. ib_srpt, there is a one-to-one correspondence between SCST target and -SCSI target port. With other SCST targets, e.g. iSCSI-SCST, by default the -only relationship between SCST targets and SCSI target ports is that all SCST -targets defined on a system are visible via all SCSI target ports. See also -the iSCSI-SCST documentation about the allowed_portal attribute for -information about how to associate iSCSI targets with a single physical -interface. +SCST allows to define ALUA settings for each unique combination of SCST +device and SCST target. An initiator however queries ALUA settings by +sending an appropriate SCSI command to a specific LUN of an SCST target. +Each such LUN maps uniquely to an SCST device. For hardware SCST target +drivers, e.g. ib_srpt, there is a one-to-one correspondence between SCST +target and SCSI target port. With other SCST targets, e.g. iSCSI-SCST, +by default the only relationship between SCST targets and SCSI target +ports is that all SCST targets defined on a system are visible via all +SCSI target ports. See also the iSCSI-SCST documentation about the +allowed_portal attribute for information about how to associate iSCSI +targets with a single physical interface. Notes: - In a H.A. setup it is the responsibility of the user to synchronize ALUA @@ -1331,7 +1365,7 @@ Notes: Configuring ALUA in SCST ........................ -SCST allows to configure the following settings related to implicit ALUA +SCST allows to configure the following settings related to ALUA for each unique combination of SCST target and virtual SCST device (vdisk_fileio, vdisk_blockio, vcdrom, ...): * The target port group asymmetric access state. SCST supports all ALUA port @@ -1429,15 +1463,62 @@ DEVICE_GROUP dgroup2 { } } +Explicit ALUA +............. + +To enable explicit ALUA you need in addition to the above settings set +expl_alua device attribute to 1 (by default it is 0). Also you need to +run stpgd and supply to it path to a script or program, which will +perform actual path state switching on SET TARGET PORT GROUPS command, +for instance, by calling drbdadm. For more information see stpgd README +as well as sample script scst_on_stpg. + +DRBD compatibility +.................. + +DRBD does not allow to open its device on the secondary as well as does +not allow to perform primary to secondary transition, if this device is +open. + +SCST BLOCKIO handler has all the necessary support for this behavior. If +you write new ALUA state in the "state" attribute, SCST BLOCKIO handler +before transition closes the open devices' handles and after transition +reopens them, if the new state is active or nonoptimized. + +Thus, the recommended implicit ALUA state change procedure for primary +to secondary transition is: + +1. Block all involved SCST devices using "block" sysfs attribute (see +above). Wait until the blocking finished. + +2. Change the ALUA state to "transitioning". At this moment all open +file handles will be closed. + +3. Perform the DRBD state transition + +4. Change the ALUA state to your desired secondary state. + +5. Unblock the blocked on step 1 devices. + +Optionally, if your initiators support Transitioning ALUA state, for +more responsive behavior the blocked devices can be unblocked +immediately after step (2). However, not all initiators correctly +behave, if they receive ASYMMETRIC STATE TRANSITION sense. + +For the secondary to primary transition procedure is similar. + +In case of explicit ALUA, SCST automatically performs the necessary +devices blocking around sending SCST_EVENT_STPG_USER_INVOKE event. Checking the Target Configuration ................................. -One way to verify the implicit ALUA configuration from a Linux initiator is -via the commands provided in the sg3_utils package. The first step is to -verify whether for a certain LUN implicit ALUA has been configured on the -target. This is possible by checking whether the TPGS=1 text appears in the -sg_inq output, where /dev/sdb is a device node created by the ib_srp initiator: +One way to verify the ALUA configuration from a Linux initiator is via +the commands provided in the sg3_utils package. The first step is to +verify whether for a certain LUN ALUA has been configured on the target. +This is possible by checking whether the TPGS=1 text appears in the +sg_inq output, where /dev/sdb is a device node created by the ib_srp +initiator: # sg_inq /dev/sdb standard INQUIRY: @@ -1497,9 +1578,11 @@ Device Identification VPD page: Initiator Support ................. -On Linux systems implicit ALUA support is provided by the scsi_dh_alua kernel -driver in combination with the user space multipathd daemon. You will have to -modify at least the following in /etc/multipath.conf to enable implicit ALUA: +On Linux systems ALUA support is provided by the scsi_dh_alua kernel +driver in combination with the user space multipathd daemon. You will +have to modify at least the following in /etc/multipath.conf to enable +ALUA: + * hardware_handler "1 alua" * prio alua * path_grouping_policy group_by_prio @@ -1548,6 +1631,126 @@ information about ALUA support in Windows Server, see also: (http://msdn.microsoft.com/en-us/library/gg607458%28v=vs.85%29.aspx). +VAAI +---- + +SCST supports all 3 VAAI SCSI commands: WRITE SAME, COMPARE AND WRITE +(ATS) and EXTENDED COPY. Additionally, it supports not directly related +to VAAI Thin Provisioning capabilities, particularly, UNMAP SCSI +commands, WRITE SAME with UNMAP bit as well as thin provisioning related +devices' sysfs attributes (see above). + +In some cases dev handlers should perform some manual actions to fully +benefit from SCST VAAI implementation. Those actions described in the +implementation notes below. For vdisk and fileio_tgt handlers they have +already been implemented. + + +Implementation notes +.................... + +WRITE SAME +~~~~~~~~~~ + +WRITE SAME command supports 2 modes: + +1. Manual writing mode. In this mode WRITE SAME generates a set of +internal WRITE(16) SCSI commands to perform requested writing. + +2. Remap mode. In this mode a dev handler, if supported, can remap being +written blocks to a single block and then tell SCST to manually write +parts of the requested area, which for some reason can not be remapped. + +In both cases dev handlers should call from WRITE SAME command handler +scst_write_same() function. This function as the second argument gets +array of descriptors where to write the requested block of data. Last +element in this array must have len 0. If this argument is NULL, then +the whole area will be manually written by SCST. This value should be +used by dev handlers not supporting remapping blocks. + +User space dev handlers should use SCST_EXEC_REPLY_DO_WRITE_SAME +reply_type of SCST_USER_EXEC subcommand. See scst_user doc for more +info. + + +COMPARE AND WRITE +~~~~~~~~~~~~~~~~~ + +COMPARE AND WRITE implemented by SCST a set of read, compare and write +actions done in atomic manner against affected blocks as well as regular +RESERVE SCSI commands. Particularly, COMPARE AND WRITE doesn't need any +queue flushing and unlimited number of COMPARE AND WRITE commands on +different blocks can be executed simultaneously. + +The read and write actions implemented as generation of internal +READ(16) and WRITE(16) SCSI commands. + +COMPARE AND WRITE command is completely transparent to dev handlers +(they only see the corresponding READ(16) and WRITE(16) commands), so +doesn't require any manual actions from them. + + +EXTENDED COPY +~~~~~~~~~~~~~ + +SCST implements EXTENDED COPY via internal Copy Manager target. This +target has the following specific attributes in its sysfs: + + - allow_not_connected_copy - if not set (default), an initiator can +perform copy only between devices it has direct access to via any +target/session. If set, any initiator can copy between any devices in +the system. + + - mgmt - this attribute allows to control data from which devices can +be copied using Copy Manager. By default, devices belonging to dev +handlers with flag auto_cm_assignment_possible set are auto assigned to +the Copy Manager on the registration. Currently, only vdisk has this +flag set, so all other devices (pass-through, user space, etc.) should +be assigned to the Copy Manager manually. + +Mgmt attribute supports the following commands: + + - add [vname|H:C:I:L] - adds device with name vname or H:C:I:L numbers +(pass-through) to the Copy Manager. + + - del [vname|H:C:I:L] - deletes device with name vname or H:C:I:L numbers +(pass-through) from the Copy Manager. + + +Internally SCST implements EXTENDED COPY as generation of sets of +internal READ(16) and WRITE(16) SCSI commands. Dev handlers don't need +any manual actions to use it. + + +Also SCST provides for dev handlers possibility to remap blocks instead +of copy them, if they support this feature. It allows them to perform +EXTENDED COPY command much faster by just metadata update of their +backend storage, which supposed to be nearly instantaneous. + +To use this feature, a dev handler should setup ext_copy_remap() +callback in its struct scst_dev_type. This callback is called by SCST +during EXTENDED COPY command processing to let the dev handler try to +remap affected blocks at first. + +Upon finish, the dev handler should call scst_ext_copy_remap_done(). In +case of error, the dev handler should set the corresponding sense to cmd +and then also call scst_ext_copy_remap_done(cmd, NULL, 0). + +If dev handler is not able to remap any part of the segment, if should +kmalloc(), then fill all leftover subsegments and supply them to +scst_ext_copy_remap_done(). SCST then will copy the subsegments using +internal copy machine, then kfree() the supplied array. If the dev +handler is not able to remap the whole segment, it can simply directly +supply the original segment to scst_ext_copy_remap_done(). + +It is highly recommended that in normal circumstances dev handlers call +scst_ext_copy_remap_done() from another thread context than one where +ext_copy_remap() callback was originally called, because otherwise there +could be recursion in the segments processing. Hopefully, this thread +context switch is natural for such potentially long operation as +EXTENDED COPY. + + Caching ------- diff --git a/scst/include/backport.h b/scst/include/backport.h index d8ce44ef3..f349bb80a 100644 --- a/scst/include/backport.h +++ b/scst/include/backport.h @@ -255,6 +255,32 @@ static inline void hex2bin(u8 *dst, const char *src, size_t count) } #endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 39) +static inline int __must_check kstrtoull(const char *s, unsigned int base, + unsigned long long *res) +{ + return strict_strtoull(s, base, res); +} + +static inline int __must_check kstrtoll(const char *s, unsigned int base, + long long *res) +{ + return strict_strtoll(s, base, res); +} + +static inline int __must_check kstrtoul(const char *s, unsigned int base, + unsigned long *res) +{ + return strict_strtoul(s, base, res); +} + +static inline int __must_check kstrtol(const char *s, unsigned int base, + long *res) +{ + return strict_strtol(s, base, res); +} +#endif + /* */ #ifndef __list_for_each @@ -401,6 +427,15 @@ static inline void sg_set_page(struct scatterlist *sg, struct page *page, (__flags), NULL, NULL) #endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 4, 0) +static inline void *kmalloc_array(size_t n, size_t size, gfp_t flags) +{ + if (size != 0 && n > ULONG_MAX / size) + return NULL; + return kmalloc(n * size, flags); +} +#endif + /* */ #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 18, 0) diff --git a/scst/include/scst.h b/scst/include/scst.h index f80106866..c39629f40 100644 --- a/scst/include/scst.h +++ b/scst/include/scst.h @@ -684,6 +684,7 @@ struct scst_acg; struct scst_acg_dev; struct scst_acn; struct scst_aen; +struct scst_ext_copy_seg_descr; struct scst_opcode_descriptor; /* @@ -1330,6 +1331,12 @@ struct scst_dev_type { */ unsigned pr_cmds_notifications:1; + /* + * Set if the Copy Manager can auto assing to devices of this + * template on their registration. + */ + unsigned auto_cm_assignment_possible:1; + /* * Called to parse CDB from the cmd and initialize * cmd->bufflen and cmd->data_direction (both - REQUIRED). @@ -1420,6 +1427,50 @@ struct scst_dev_type { */ void (*on_free_cmd)(struct scst_cmd *cmd); + /* + * Called during EXTENDED COPY command processing to let dev handler + * try to remap blocks at first. Upon finish, the dev handler supposed + * to call scst_ext_copy_remap_done(). See description of this + * function for more details. + * + * In case of error, the dev handler should set the corresponding sense + * to cmd and then call scst_ext_copy_remap_done(cmd, NULL, 0). + * + * It is highly recommended that in the normal circumstances + * scst_ext_copy_remap_done() called from another thread context, + * because otherwise there will be recursion in the segments processing. + * Hopefully, this thread context switch is natural for such + * potentially long operation. + * + * OPTIONAL + */ + void (*ext_copy_remap)(struct scst_cmd *cmd, + struct scst_ext_copy_seg_descr *descr); + + /* + * Called to notify dev handler that a ALUA state change is about to + * be started. Can be used to close open file handlers, which might + * prevent the state switch. + * + * Called under scst_dg_mutex and no activities on the dev handler level. + * + * OPTIONAL + */ + void (*on_alua_state_change_start)(struct scst_device *dev, + enum scst_tg_state old_state, enum scst_tg_state new_state); + + /* + * Called to notify dev handler that a ALUA state change is about to + * be finished. Can be used to (re)open file handlers closed in + * on_alua_state_change_start(). + * + * Called under scst_dg_mutex and no activities on the dev handler level. + * + * OPTIONAL + */ + void (*on_alua_state_change_finish)(struct scst_device *dev, + enum scst_tg_state old_state, enum scst_tg_state new_state); + /* * Called to notify dev handler that a task management command received * @@ -1918,6 +1969,14 @@ struct scst_session { /* Used if scst_unregister_session() called in wait mode */ struct completion *shutdown_compl; + /* + * Keep list IDs for Extended Copy commands sent over this session. + * Protected by scst_cm_lock. + */ + struct list_head sess_cm_list_id_list; + + struct delayed_work sess_cm_list_id_cleanup_work; + /* sysfs release completion */ struct completion *sess_kobj_release_cmpl; @@ -2110,12 +2169,24 @@ struct scst_cmd { /* Set if cmd is internally generated */ unsigned int internal:1; + /* Set if local events should be checked for internally generated cmd */ + unsigned int internal_check_local_events:1; + + /* Set if the blocking machinery should be bypassed for this cmd */ + unsigned int bypass_blocking:1; + /* Set if the device was blocked by scst_check_blocked_dev() */ unsigned int unblock_dev:1; /* Set if scst_dec_on_dev_cmd() call is needed on the cmd's finish */ unsigned int dec_on_dev_needed:1; + /* Set if cmd is on dev's exec_cmd_list */ + unsigned int on_dev_exec_list:1; + + /* Set if this cmd passed check for SCSI atomicity */ + unsigned int scsi_atomicity_checked:1; + /* Set if cmd is queued as hw pending */ unsigned int cmd_hw_pending:1; @@ -2208,12 +2279,21 @@ struct scst_cmd { /* Set if cmd was pre-alloced by target driver */ unsigned int pre_alloced:1; + /* Set if cmd was already ALUA checked in TRANSITIONING state */ + unsigned int already_transitioning:1; + /* Set if scst_cmd_set_write_not_received_data_len() was called */ unsigned int write_not_received_set:1; /* Set if cmd has LINK bit set in CDB */ unsigned int cmd_linked:1; + /* Set if cmd is on scst_global_stpg_list */ + unsigned int cmd_on_global_stpg_list:1; + + /* Set if cmd was globally STPG blocked in __scst_check_blocked_dev() */ + unsigned int cmd_global_stpg_blocked:1; + /**************************************************************/ /* cmd's async flags */ @@ -2269,6 +2349,15 @@ struct scst_cmd { unsigned short cdb_len; uint8_t cdb_buf[SCST_MAX_CDB_SIZE]; + /* List entry for dev's dev_exec_cmd_list */ + struct list_head dev_exec_cmd_list_entry; + + /* + * Array of blocked by this cmd SCSI atomic cmds with size + * scsi_atomic_blocked_cmds_count. Protected by dev->dev_lock. + */ + struct scst_cmd **scsi_atomic_blocked_cmds; + uint8_t lba_off; /* LBA offset in cdb */ uint8_t lba_len; /* LBA length in cdb */ uint8_t len_off; /* length offset in cdb */ @@ -2400,6 +2489,18 @@ struct scst_cmd { /* Used for storage of dev handler private stuff */ void *dh_priv; + /* + * Number of waiting for this cmd to finish commands + * with SCSI atomic guarantees. Protected by dev->dev_lock. + */ + int scsi_atomic_blockers; + + /* + * How many SCSI atomic cmds this cmd blocked, i.e. size of array + * scsi_atomic_blocked_cmds. Protected by dev->dev_lock. + */ + int scsi_atomic_blocked_cmds_count; + /* List entry for dev's blocked_cmd_list */ struct list_head blocked_cmd_list_entry; @@ -2429,6 +2530,11 @@ struct scst_cmd { void *cmd_data_descriptors; int cmd_data_descriptors_cnt; }; + + /* STPG commands global serialization */ + struct { + struct list_head global_stpg_list_entry; + }; }; #if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) @@ -2440,6 +2546,7 @@ struct scst_cmd { uint64_t restart_waiting_time, rdy_to_xfer_time; uint64_t pre_exec_time, exec_time, dev_done_time; uint64_t xmit_time; + bool exec_time_counting; #endif #ifdef CONFIG_SCST_DEBUG_TM @@ -2611,6 +2718,20 @@ struct scst_cl_ops { void (*reserve)(struct scst_device *dev, struct scst_session *sess); }; +/* + * Extended, e.g. via sysfs, blockers + */ +typedef void (*ext_blocker_done_fn_t) (struct scst_device *dev, + uint8_t *data, int len); + +struct scst_ext_blocker { + struct list_head ext_blockers_list_entry; + + ext_blocker_done_fn_t ext_blocker_done_fn; + int ext_blocker_data_len; + uint8_t ext_blocker_data[]; +}; + /* * SCST device */ @@ -2631,6 +2752,26 @@ struct scst_device { /* Set, if a strictly serialized cmd is waiting blocked */ unsigned int strictly_serialized_cmd_waiting:1; + /* + * Set, if this device is being unregistered. Useful to let sysfs + * attributes know when they should exit immediately to prevent + * possible deadlocks with their device unregistration waiting for + * their kobj last put. + */ + unsigned int dev_unregistering:1; + + /* + * Set if ext blocking is pending. It if just shortcut for + * !list_empty(&dev->ext_blockers_list) to save a cache miss. + */ + unsigned int ext_blocking_pending:1; + + /* Used to serialize invocations of __scst_ext_blocking_done() */ + unsigned int ext_unblock_scheduled:1; + + /* Set if this device was blocked during STPG command processing */ + unsigned int stpg_ext_blocked:1; + /* Set if this device does not support DIF IP checking */ unsigned int dev_dif_ip_not_supported:1; @@ -2744,15 +2885,23 @@ struct scst_device { */ int on_dev_cmd_count; + /* + * How many atomic SCSI commands being executed. Protected by dev_lock. + */ + int dev_scsi_atomic_cmd_active; + + /* + * List of all being executed on the dev commands. + * Protected by dev_lock. + */ + struct list_head dev_exec_cmd_list; + /* Memory limits for this device */ struct scst_mem_lim dev_mem_lim; /* List of commands with lock, if dedicated threads are used */ struct scst_cmd_threads dev_cmd_threads; - /* Operations that depend on whether or not cluster mode is enabled */ - const struct scst_cl_ops *cl_ops; - /************************************************************* ** T10-PI fields. Read-only, hence no protection. *************************************************************/ @@ -2770,9 +2919,11 @@ struct scst_device { int (*dev_dif_fn)(struct scst_cmd *cmd); __be16 dev_dif_static_app_tag; /* fixed APP TAG for all blocks in dev */ - __be32 dev_dif_static_app_ref_tag; /* fixed APP TAG part from REF - * TAG for all blocks in dev. - * Valid only with dif type 3 */ + /* + * Fixed APP TAG part from REF TAG for all blocks in dev. Valid only + * with dif type 3. + */ + __be32 dev_dif_static_app_ref_tag; /* Cache to optimize scst_parse_*protect() routines */ enum scst_dif_actions dev_dif_rd_actions; @@ -2784,6 +2935,9 @@ struct scst_device { /* Set if reserved via the SPC-2 SCSI RESERVE command. */ struct scst_session *reserved_by; + /* Operations that depend on whether or not cluster mode is enabled */ + const struct scst_cl_ops *cl_ops; + /********************************************************************** * Persistent reservation fields. Protected as follows: * - Reading PR data must be protected via scst_pr_read_lock() / @@ -2854,6 +3008,15 @@ struct scst_device { /* List of blocked commands, protected by dev_lock. */ struct list_head blocked_cmd_list; + /* Number of ext blocking requests, protected by dev_lock */ + int ext_blocks_cnt; + + /* List of ext blockers, protected by dev_lock */ + struct list_head ext_blockers_list; + + /* Work to notify ext blockers out of dev_lock context */ + struct work_struct ext_blockers_work; + /* MAXIMUM WRITE SAME LENGTH in bytes */ uint64_t max_write_same_len; @@ -2969,7 +3132,10 @@ struct scst_tgt_dev { atomic_t tgt_dev_cmd_count ____cacheline_aligned_in_smp; /* ALUA command filter */ - bool (*alua_filter)(struct scst_cmd *cmd); +#define SCST_ALUA_CHECK_OK 0 +#define SCST_ALUA_CHECK_DELAYED 1 +#define SCST_ALUA_CHECK_ERROR -1 + int (*alua_filter)(struct scst_cmd *cmd); struct scst_order_data *curr_order_data; struct scst_order_data tgt_dev_order_data; @@ -3176,6 +3342,8 @@ struct scst_acn { * @kobj: For making this object visible in sysfs. * @dev_kobj: Sysfs devices directory. * @tg_kobj: Sysfs target groups directory. + * @stpg_transport_id Initiator transport ID for STPG originating I_T nexus, if any + * @stpg_rel_tgt_id Relative target ID for STPG originating I_T nexus, if any * * Each device is member of zero or one device groups. With each device group * there are zero or more target groups associated. @@ -3188,6 +3356,8 @@ struct scst_dev_group { struct kobject kobj; struct kobject *dev_kobj; struct kobject *tg_kobj; + uint8_t *stpg_transport_id; + uint16_t stpg_rel_tgt_id; }; /** @@ -3300,6 +3470,7 @@ extern const struct scst_opcode_descriptor scst_op_descr_stpg; extern const struct scst_opcode_descriptor scst_op_descr_send_diagnostic; extern const struct scst_opcode_descriptor scst_op_descr_inquiry; +extern const struct scst_opcode_descriptor scst_op_descr_extended_copy; extern const struct scst_opcode_descriptor scst_op_descr_tur; extern const struct scst_opcode_descriptor scst_op_descr_reserve6; extern const struct scst_opcode_descriptor scst_op_descr_release6; @@ -3326,7 +3497,6 @@ extern const struct scst_opcode_descriptor scst_op_descr_report_supp_opcodes; &scst_op_descr_report_supp_opcodes, \ &scst_op_descr_report_supp_tm_fns, - #ifndef smp_mb__after_set_bit /* There is no smp_mb__after_set_bit() in the kernel */ #define smp_mb__after_set_bit() smp_mb() @@ -3655,9 +3825,12 @@ static inline void scst_sess_set_tgt_priv(struct scst_session *sess, } uint16_t scst_lookup_tg_id(struct scst_device *dev, struct scst_tgt *tgt); -bool scst_impl_alua_configured(struct scst_device *dev); +bool scst_alua_configured(struct scst_device *dev); int scst_tg_get_group_info(void **buf, uint32_t *response_length, struct scst_device *dev, uint8_t data_format); +int scst_tg_set_group_info(struct scst_cmd *cmd); +const char *scst_alua_state_name(enum scst_tg_state s); +void scst_stpg_del_unblock_next(struct scst_cmd *cmd); /* * Get/set functions for dev's static DIF APP TAG @@ -5453,7 +5626,7 @@ struct scst_data_descriptor { uint64_t sdd_blocks; }; -void scst_write_same(struct scst_cmd *cmd); +void scst_write_same(struct scst_cmd *cmd, struct scst_data_descriptor *where); __be64 scst_pack_lun(const uint64_t lun, enum scst_lun_addr_method addr_method); uint64_t scst_unpack_lun(const uint8_t *lun, int len); @@ -5478,4 +5651,32 @@ int scst_pr_set_cluster_mode(struct scst_device *dev, bool cluster_mode, int scst_pr_init_dev(struct scst_device *dev); void scst_pr_clear_dev(struct scst_device *dev); +struct scst_ext_copy_data_descr { + uint64_t src_lba; + uint64_t dst_lba; + int data_len; /* in bytes */ +}; + +struct scst_ext_copy_seg_descr { +#define SCST_EXT_COPY_SEG_DATA 0 +#if 0 /* not implemented yet */ +#define SCST_EXT_COPY_SEG_PR_REG 1 +#define SCST_EXT_COPY_SEG_PR_MOVE 2 +#endif + int type; + union { + struct { + struct scst_tgt_dev *src_tgt_dev; + struct scst_tgt_dev *dst_tgt_dev; + struct scst_ext_copy_data_descr data_descr; + }; + }; + /* Internal, don't touch! */ + int tgt_descr_offs; +}; + +void scst_ext_copy_remap_done(struct scst_cmd *ec_cmd, + struct scst_ext_copy_data_descr *dds, int dds_cnt); +int scst_ext_copy_get_cur_seg_data_len(struct scst_cmd *ec_cmd); + #endif /* __SCST_H */ diff --git a/scst/include/scst_const.h b/scst/include/scst_const.h index 39a829b58..b0a2fc51b 100644 --- a/scst/include/scst_const.h +++ b/scst/include/scst_const.h @@ -50,7 +50,7 @@ #else #define SCST_VERSION_STRING_SUFFIX #endif -#define SCST_VERSION_NAME "3.1.0-pre1" +#define SCST_VERSION_NAME "3.2.0-pre1" #define SCST_VERSION_STRING SCST_VERSION_NAME SCST_VERSION_STRING_SUFFIX #define SCST_CONST_VERSION SCST_CONST_INTF_VER @@ -228,9 +228,11 @@ enum scst_cdb_flags { #ifdef CONFIG_SCST_TEST_IO_IN_SIRQ SCST_TEST_IO_IN_SIRQ_ALLOWED = 0x8000, #endif - SCST_SERIALIZED = 0x10000, - SCST_STRICTLY_SERIALIZED = 0x20000|SCST_SERIALIZED, - SCST_DESCRIPTORS_BASED = 0x40000, + SCST_SERIALIZED = 0x10000, + SCST_STRICTLY_SERIALIZED = 0x20000|SCST_SERIALIZED, + SCST_CAN_GEN_3PARTY_COMMANDS = 0x40000, + SCST_DESCRIPTORS_BASED = 0x80000, + SCST_SCSI_ATOMIC = 0x100000, }; /************************************************************* @@ -290,9 +292,9 @@ static inline int scst_sense_response_code(const uint8_t *sense) /* NOT_READY is 2 */ #define scst_sense_format_in_progress NOT_READY, 0x04, 0x04 -#define scst_sense_tp_transitioning NOT_READY, 0x04, 0x0A -#define scst_sense_tp_standby NOT_READY, 0x04, 0x0B -#define scst_sense_tp_unav NOT_READY, 0x04, 0x0C +#define scst_sense_alua_transitioning NOT_READY, 0x04, 0x0A +#define scst_sense_alua_standby NOT_READY, 0x04, 0x0B +#define scst_sense_alua_unav NOT_READY, 0x04, 0x0C #define scst_sense_no_medium NOT_READY, 0x3a, 0 /* MEDIUM_ERROR is 3 */ @@ -301,8 +303,11 @@ static inline int scst_sense_response_code(const uint8_t *sense) /* HARDWARE_ERROR is 4 */ #define scst_sense_hardw_error HARDWARE_ERROR, 0x44, 0 /* non-retriable */ +#define scst_sense_set_target_pgs_failed HARDWARE_ERROR, 0x67, 0xA /* ILLEGAL_REQUEST is 5 */ +#define scst_sense_operation_in_progress ILLEGAL_REQUEST, 0x0, 0x16 +#define scst_sense_parameter_list_length_invalid ILLEGAL_REQUEST, 0x1A, 0 #define scst_sense_invalid_opcode ILLEGAL_REQUEST, 0x20, 0 #define scst_sense_block_out_range_error ILLEGAL_REQUEST, 0x21, 0 /* Don't use it directly, use scst_set_invalid_field_in_cdb() instead! */ @@ -312,6 +317,11 @@ static inline int scst_sense_response_code(const uint8_t *sense) #define scst_sense_invalid_field_in_parm_list ILLEGAL_REQUEST, 0x26, 0 #define scst_sense_parameter_value_invalid ILLEGAL_REQUEST, 0x26, 2 #define scst_sense_invalid_release ILLEGAL_REQUEST, 0x26, 4 +#define scst_sense_too_many_target_descriptors ILLEGAL_REQUEST, 0x26, 6 +#define scst_sense_unsupported_tgt_descr_type ILLEGAL_REQUEST, 0x26, 7 +#define scst_sense_too_many_segment_descriptors ILLEGAL_REQUEST, 0x26, 8 +#define scst_sense_unsupported_seg_descr_type ILLEGAL_REQUEST, 0x26, 9 +#define scst_sense_inline_data_length_exceeded ILLEGAL_REQUEST, 0x26, 0xB #define scst_sense_saving_params_unsup ILLEGAL_REQUEST, 0x39, 0 #define scst_sense_invalid_message ILLEGAL_REQUEST, 0x49, 0 #define scst_sense_parameter_list_length_invalid ILLEGAL_REQUEST, 0x1A, 0 @@ -419,6 +429,14 @@ static inline int scst_sense_response_code(const uint8_t *sense) #define WRITE_SAME_16 0x93 #endif +#ifndef EXTENDED_COPY +#define EXTENDED_COPY 0x83 +#endif + +#ifndef RECEIVE_COPY_RESULTS +#define RECEIVE_COPY_RESULTS 0x84 +#endif + #ifndef SYNCHRONIZE_CACHE_16 #define SYNCHRONIZE_CACHE_16 0x91 #endif @@ -588,6 +606,12 @@ enum { #define SCSI_TRANSPORTID_PROTOCOLID_ISCSI 5 #define SCSI_TRANSPORTID_PROTOCOLID_SAS 6 +/* + * SCST extension. Added, because there is no TransportID for Copy Manager + * defined anywhere in the SCSI standards. + */ +#define SCST_TRANSPORTID_PROTOCOLID_COPY_MGR 0xC + /** * enum scst_tg_state - SCSI target port group asymmetric access state. * diff --git a/scst/include/scst_event.h b/scst/include/scst_event.h new file mode 100644 index 000000000..9d7750e8b --- /dev/null +++ b/scst/include/scst_event.h @@ -0,0 +1,157 @@ +/* + * include/scst_event.h + * + * Copyright (C) 2014 - 2015 SanDisk Corporation + * + * Contains constants and data structures for scst_event module. + */ + +#ifndef __SCST_EVENT_H +#define __SCST_EVENT_H + +#ifdef __KERNEL__ +#include +#endif + +#ifdef INSIDE_KERNEL_TREE +#include +#else +#include "scst_const.h" +#endif + +#define SCST_EVENT_NAME "scst_event" +#define SCST_EVENT_PATH "/dev/" +#define SCST_EVENT_DEV SCST_EVENT_PATH SCST_EVENT_NAME +#define SCST_EVENT_VERSION_NAME SCST_VERSION_NAME +#define SCST_EVENT_VERSION \ + SCST_EVENT_VERSION_NAME "$Revision: 2454 $" SCST_CONST_VERSION + +#ifndef aligned_u64 +#define aligned_u64 uint64_t __attribute__((aligned(8))) +#endif + +/* + * Due to variable size, this structure must always be last in any outer + * structure! + */ +struct scst_event { + int32_t payload_len; + + uint32_t event_code; + + uint32_t event_id; /* ID uniquely identifying this event */ + + /* + * Event's issuer's name, i.e. SCST name of the corresponding module + * (target driver or dev handler). SCST_EVENT_SCST_CORE_ISSUER for SCST + * core. + */ + char issuer_name[SCST_MAX_NAME]; + + uint8_t payload[]; /* event's payload */ +}; + +#ifdef __KERNEL__ +typedef void (*scst_event_done_notify_fn) (struct scst_event *event, + void *priv, int status); + +struct scst_event_entry { + struct list_head events_list_entry; + scst_event_done_notify_fn event_notify_fn; + void *notify_fn_priv; + unsigned long event_timeout; /* in jiffies */ + int *pqueued_events_cnt; + union { + struct work_struct scst_event_queue_work; + struct delayed_work event_timeout_work; + }; + + struct scst_event event; + /* event's payload */ +}; +#endif + +struct scst_event_user { + int32_t max_event_size; + struct scst_event out_event; +}; + +struct scst_event_notify_done { + uint32_t event_id; + int status; +}; + +/* IOCTLs */ +#define SCST_EVENT_ALLOW_EVENT _IOW('u', 1, struct scst_event) +#define SCST_EVENT_DISALLOW_EVENT _IOW('u', 2, struct scst_event) +#define SCST_EVENT_GET_NEXT_EVENT _IOWR('u', 3, struct scst_event_user) +#define SCST_EVENT_NOTIFY_DONE _IOW('u', 4, struct scst_event_notify_done) + +#ifdef __KERNEL__ +void scst_event_queue(uint32_t event_code, const char *issuer_name, + struct scst_event_entry *e); +#endif + +/************************************************************* + ** SCST events + *************************************************************/ + +/* SCST core issuer name */ +#define SCST_EVENT_SCST_CORE_ISSUER "SCST core" + +/** SCST core's events **/ + +#define SCST_EVENT_LUN_NOT_FOUND 1 +struct scst_event_lun_not_found_payload { + aligned_u64 lun; + uint8_t initiator_name[SCST_MAX_EXTERNAL_NAME]; + uint8_t target_name[SCST_MAX_EXTERNAL_NAME]; +}; + +#define SCST_EVENT_NEGATIVE_LUNS_INQUIRY 2 +struct scst_event_negative_luns_inquiry_payload { + uint8_t initiator_name[SCST_MAX_EXTERNAL_NAME]; + uint8_t target_name[SCST_MAX_EXTERNAL_NAME]; +}; + +#define SCST_EVENT_EXT_BLOCKING_DONE 3 +struct scst_event_ext_blocking_done_payload { + uint8_t device_name[SCST_MAX_EXTERNAL_NAME]; + uint32_t data_len; + uint8_t data[]; +}; + +#define SCST_EVENT_TM_FN_RECEIVED 4 +struct scst_event_tm_fn_received_payload { + uint32_t fn; /* TM fn */ + aligned_u64 lun; + uint8_t device_name[SCST_MAX_EXTERNAL_NAME]; + uint8_t initiator_name[SCST_MAX_EXTERNAL_NAME]; + uint8_t target_name[SCST_MAX_EXTERNAL_NAME]; + uint8_t session_sysfs_name[SCST_MAX_EXTERNAL_NAME]; + struct { /* if ABORT TASK, then tag and CDB of cmd to abort */ + aligned_u64 cmd_to_abort_tag; + uint8_t cdb[SCST_MAX_CDB_SIZE]; + }; +}; + +#define SCST_EVENT_STPG_USER_INVOKE 5 +struct scst_event_stpg_descr { + uint16_t group_id; + /* + * Better to keep below fields as small as possible to fit + * in single page as many descriptors as possible. + */ + uint8_t prev_state[32]; + uint8_t new_state[32]; + uint8_t dg_name[64]; + uint8_t tg_name[64]; +}; +struct scst_event_stpg_payload { + aligned_u64 stpg_cmd_tag; + uint8_t device_name[64]; + uint16_t stpg_descriptors_cnt; + struct scst_event_stpg_descr stpg_descriptors[0]; +}; + +#endif /* __SCST_EVENT_H */ diff --git a/scst/include/scst_user.h b/scst/include/scst_user.h index 0a2e53f92..1ec529b11 100644 --- a/scst/include/scst_user.h +++ b/scst/include/scst_user.h @@ -71,10 +71,11 @@ #define UCMD_STATE_BUF_ALLOCING 2 #define UCMD_STATE_EXECING 3 #define UCMD_STATE_ON_FREEING 4 -#define UCMD_STATE_ON_FREE_SKIPPED 5 -#define UCMD_STATE_ON_CACHE_FREEING 6 -#define UCMD_STATE_TM_RECEIVED_EXECING 7 -#define UCMD_STATE_TM_DONE_EXECING 8 +#define UCMD_STATE_ON_CACHE_FREEING 5 +#define UCMD_STATE_EXT_COPY_REMAPPING 6 +#define UCMD_STATE_ON_FREE_SKIPPED 7 +#define UCMD_STATE_TM_RECEIVED_EXECING 8 +#define UCMD_STATE_TM_DONE_EXECING 9 #define UCMD_STATE_ATTACH_SESS 0x20 #define UCMD_STATE_DETACH_SESS 0x21 @@ -96,6 +97,8 @@ struct scst_user_opt { uint8_t d_sense; uint8_t has_own_order_mgmt; + + uint8_t ext_copy_remap_supported; }; struct scst_user_dev_desc { @@ -212,6 +215,19 @@ struct scst_user_tm { uint8_t cmd_sn_set; }; +struct scst_user_ext_copy_data_descr { + aligned_u64 src_lba; + aligned_u64 dst_lba; + int32_t data_len; /* in bytes */ +}; + +struct scst_user_ext_copy_remap { + aligned_u64 sess_h; + aligned_u64 src_sess_h; + aligned_u64 dst_sess_h; + struct scst_user_ext_copy_data_descr data_descr; +}; + struct scst_user_get_cmd { uint32_t cmd_h; uint32_t subcode; @@ -224,6 +240,7 @@ struct scst_user_get_cmd { struct scst_user_scsi_on_free_cmd on_free_cmd; struct scst_user_on_cached_mem_free on_cached_mem_free; struct scst_user_tm tm_cmd; + struct scst_user_ext_copy_remap remap_cmd; }; }; @@ -253,6 +270,15 @@ struct scst_user_scsi_cmd_reply_alloc_mem { aligned_u64 pbuf; }; +/* + * Same as struct scst_data_descriptor, but suitable to pass kernel/user + * space boundary + */ +struct scst_user_data_descriptor { + aligned_u64 usdd_lba; + aligned_u64 usdd_blocks; +}; + /* Be careful adding new members here, this structure is allocated on stack! */ struct scst_user_scsi_cmd_reply_exec { int32_t resp_data_len; @@ -260,8 +286,26 @@ struct scst_user_scsi_cmd_reply_exec { #define SCST_EXEC_REPLY_BACKGROUND 0 #define SCST_EXEC_REPLY_COMPLETED 1 +#define SCST_EXEC_REPLY_DO_WRITE_SAME 2 uint8_t reply_type; + uint8_t status; + union { + struct { + uint8_t sense_len; + aligned_u64 psense_buffer; + }; + struct { + uint16_t ws_descriptors_len; + aligned_u64 ws_descriptors; + }; + }; +}; + +/* Be careful adding new members here, this structure is allocated on stack! */ +struct scst_user_ext_copy_reply_remap { + aligned_u64 remap_descriptors; + uint16_t remap_descriptors_len; uint8_t status; uint8_t sense_len; aligned_u64 psense_buffer; @@ -276,6 +320,7 @@ struct scst_user_reply_cmd { struct scst_user_scsi_cmd_reply_parse parse_reply; struct scst_user_scsi_cmd_reply_alloc_mem alloc_reply; struct scst_user_scsi_cmd_reply_exec exec_reply; + struct scst_user_ext_copy_reply_remap remap_reply; }; }; @@ -344,5 +389,7 @@ struct scst_user_get_multi { _IOWR('s', UCMD_STATE_TM_RECEIVED_EXECING, struct scst_user_tm) #define SCST_USER_TASK_MGMT_DONE \ _IOWR('s', UCMD_STATE_TM_DONE_EXECING, struct scst_user_tm) +#define SCST_USER_EXT_COPY_REMAP \ + _IOWR('s', UCMD_STATE_EXT_COPY_REMAPPING, struct scst_user_ext_copy_remap) #endif /* __SCST_USER_H */ diff --git a/scst/kernel/in-tree/Kconfig.drivers.Linux-4.3.patch b/scst/kernel/in-tree/Kconfig.drivers.Linux-4.3.patch new file mode 100644 index 000000000..39d30a55e --- /dev/null +++ b/scst/kernel/in-tree/Kconfig.drivers.Linux-4.3.patch @@ -0,0 +1,13 @@ +diff --git a/drivers/Kconfig b/drivers/Kconfig +index c70d6e4..0a4ed1b 100644 +--- a/drivers/Kconfig ++++ b/drivers/Kconfig +@@ -26,6 +26,8 @@ source "drivers/ide/Kconfig" + + source "drivers/scsi/Kconfig" + ++source "drivers/scst/Kconfig" ++ + source "drivers/ata/Kconfig" + + source "drivers/md/Kconfig" diff --git a/scst/kernel/in-tree/Makefile.dev_handlers-4.3 b/scst/kernel/in-tree/Makefile.dev_handlers-4.3 new file mode 100644 index 000000000..f933b36f7 --- /dev/null +++ b/scst/kernel/in-tree/Makefile.dev_handlers-4.3 @@ -0,0 +1,14 @@ +ccflags-y += -Wno-unused-parameter + +obj-m := scst_cdrom.o scst_changer.o scst_disk.o scst_modisk.o scst_tape.o \ + scst_vdisk.o scst_raid.o scst_processor.o scst_user.o + +obj-$(CONFIG_SCST_DISK) += scst_disk.o +obj-$(CONFIG_SCST_TAPE) += scst_tape.o +obj-$(CONFIG_SCST_CDROM) += scst_cdrom.o +obj-$(CONFIG_SCST_MODISK) += scst_modisk.o +obj-$(CONFIG_SCST_CHANGER) += scst_changer.o +obj-$(CONFIG_SCST_RAID) += scst_raid.o +obj-$(CONFIG_SCST_PROCESSOR) += scst_processor.o +obj-$(CONFIG_SCST_VDISK) += scst_vdisk.o +obj-$(CONFIG_SCST_USER) += scst_user.o diff --git a/scst/kernel/in-tree/Makefile.drivers.Linux-4.3.patch b/scst/kernel/in-tree/Makefile.drivers.Linux-4.3.patch new file mode 100644 index 000000000..6bbacd12f --- /dev/null +++ b/scst/kernel/in-tree/Makefile.drivers.Linux-4.3.patch @@ -0,0 +1,12 @@ +diff --git a/drivers/Makefile b/drivers/Makefile +index 527a6da..db2c24f 100644 +--- a/drivers/Makefile ++++ b/drivers/Makefile +@@ -137,6 +137,7 @@ obj-$(CONFIG_SSB) += ssb/ + obj-$(CONFIG_BCMA) += bcma/ + obj-$(CONFIG_VHOST_RING) += vhost/ + obj-$(CONFIG_VLYNQ) += vlynq/ ++obj-$(CONFIG_SCST) += scst/ + obj-$(CONFIG_STAGING) += staging/ + obj-y += platform/ + #common clk code diff --git a/scst/kernel/in-tree/Makefile.scst-4.3 b/scst/kernel/in-tree/Makefile.scst-4.3 new file mode 100644 index 000000000..53af5f388 --- /dev/null +++ b/scst/kernel/in-tree/Makefile.scst-4.3 @@ -0,0 +1,13 @@ +ccflags-y += -Wno-unused-parameter + +scst-y += scst_main.o +scst-y += scst_pres.o +scst-y += scst_targ.o +scst-y += scst_lib.o +scst-y += scst_sysfs.o +scst-y += scst_mem.o +scst-y += scst_tg.o +scst-y += scst_debug.o + +obj-$(CONFIG_SCST) += scst.o dev_handlers/ fcst/ iscsi-scst/ qla2xxx-target/ \ + srpt/ scst_local/ diff --git a/scst/src/Makefile b/scst/src/Makefile index 351bb956e..51d83b986 100644 --- a/scst/src/Makefile +++ b/scst/src/Makefile @@ -56,6 +56,8 @@ ifdef CONFIG_DLM_MODULE scst-y += scst_dlm.o endif scst-y += scst_tg.o +scst-y += scst_event.o +scst-y += scst_copy_mgr.o obj-$(CONFIG_SCST) += scst.o dev_handlers/ obj-$(BUILD_DEV) += $(DEV_HANDLERS_DIR)/ diff --git a/scst/src/dev_handlers/Makefile b/scst/src/dev_handlers/Makefile index d5187a677..c59b81f88 100644 --- a/scst/src/dev_handlers/Makefile +++ b/scst/src/dev_handlers/Makefile @@ -92,6 +92,8 @@ EXTRA_CFLAGS += -DCONFIG_SCST_EXTRACHECKS #EXTRA_CFLAGS += -DCONFIG_SCST_TRACING EXTRA_CFLAGS += -DCONFIG_SCST_DEBUG -g -fno-inline -fno-inline-functions +#EXTRA_CFLAGS += -DCONFIG_DEBUG_EXT_COPY_REMAP + clean: rm -f *.o *.ko .*.cmd *.mod.c .*.d .depend Modules.symvers \ Module.symvers Module.markers modules.order diff --git a/scst/src/dev_handlers/scst_cdrom.c b/scst/src/dev_handlers/scst_cdrom.c index a08999b4f..f1860f8d7 100644 --- a/scst/src/dev_handlers/scst_cdrom.c +++ b/scst/src/dev_handlers/scst_cdrom.c @@ -123,6 +123,7 @@ static int cdrom_attach(struct scst_device *dev) if (rc == 0) { uint32_t sector_size = get_unaligned_be32(&buffer[4]); + if (sector_size == 0) dev->block_shift = CDROM_DEF_BLOCK_SHIFT; else diff --git a/scst/src/dev_handlers/scst_disk.c b/scst/src/dev_handlers/scst_disk.c index ff6c581f1..d294a138b 100644 --- a/scst/src/dev_handlers/scst_disk.c +++ b/scst/src/dev_handlers/scst_disk.c @@ -217,6 +217,7 @@ static int disk_attach(struct scst_device *dev) } if (rc == 0) { uint32_t sector_size = get_unaligned_be32(&buffer[4]); + if (sector_size == 0) dev->block_shift = DISK_DEF_BLOCK_SHIFT; else @@ -476,6 +477,7 @@ split: if (unlikely(sg_is_chain(&sg[j]))) { bool reset_start_sg = (start_sg == &sg[j]); + sg = sg_chain_ptr(&sg[j]); j = 0; if (reset_start_sg) diff --git a/scst/src/dev_handlers/scst_modisk.c b/scst/src/dev_handlers/scst_modisk.c index 850f8efe2..80913010c 100644 --- a/scst/src/dev_handlers/scst_modisk.c +++ b/scst/src/dev_handlers/scst_modisk.c @@ -222,6 +222,7 @@ static int modisk_attach(struct scst_device *dev) if (rc == 0) { uint32_t sector_size = get_unaligned_be32(&buffer[4]); + if (sector_size == 0) dev->block_shift = MODISK_DEF_BLOCK_SHIFT; else diff --git a/scst/src/dev_handlers/scst_tape.c b/scst/src/dev_handlers/scst_tape.c index 4daa84d84..1b56b01ed 100644 --- a/scst/src/dev_handlers/scst_tape.c +++ b/scst/src/dev_handlers/scst_tape.c @@ -206,6 +206,7 @@ static int tape_attach(struct scst_device *dev) if (rc == 0) { int medium_type, mode, speed, density; + if (buffer[3] == 8) dev->block_size = get_unaligned_be24(&buffer[9]); else @@ -299,6 +300,7 @@ static int tape_done(struct scst_cmd *cmd) (cmd->sense[2] & 0xe0)) { /* EOF, EOM, or ILI */ unsigned int TransferLength, Residue = 0; + if ((cmd->sense[2] & 0x0f) == BLANK_CHECK) /* No need for EOM in this case */ cmd->sense[2] &= 0xcf; @@ -312,6 +314,7 @@ static int tape_done(struct scst_cmd *cmd) cmd->cdb[1], TransferLength, Residue); if (TransferLength > Residue) { int resp_data_len = TransferLength - Residue; + if (cmd->cdb[1] & 1) { /* * No need for locks here, since diff --git a/scst/src/dev_handlers/scst_user.c b/scst/src/dev_handlers/scst_user.c index dc53e0591..74364ad51 100644 --- a/scst/src/dev_handlers/scst_user.c +++ b/scst/src/dev_handlers/scst_user.c @@ -70,6 +70,7 @@ struct scst_user_dev { unsigned int swp:1; unsigned int d_sense:1; unsigned int has_own_order_mgmt:1; + unsigned int ext_copy_remap_supported:1; int (*generic_parse)(struct scst_cmd *cmd); @@ -260,6 +261,7 @@ static inline bool ucmd_get_check(struct scst_user_cmd *ucmd) { int r = atomic_inc_return(&ucmd->ucmd_ref); int res; + if (unlikely(r == 1)) { TRACE_DBG("ucmd %p is being destroyed", ucmd); atomic_dec(&ucmd->ucmd_ref); @@ -550,6 +552,7 @@ static int dev_user_alloc_sg(struct scst_user_cmd *ucmd, int cached_buff) } else { /* Make out_sg->offset 0 */ int len = cmd->bufflen + ucmd->first_page_offset; + out_sg_pages = (len >> PAGE_SHIFT) + ((len & ~PAGE_MASK) != 0); orig_bufflen = (out_sg_pages << PAGE_SHIFT) + cmd->out_bufflen; pool = dev->pool; @@ -611,6 +614,7 @@ static int dev_user_alloc_sg(struct scst_user_cmd *ucmd, int cached_buff) if (unlikely(cmd->sg_cnt > cmd->tgt_dev->max_sg_cnt)) { static int ll; + if ((ll < 10) || TRACING_MINOR()) { PRINT_INFO("Unable to complete command due to " "SG IO count limitation (requested %d, " @@ -949,6 +953,37 @@ static int dev_user_exec(struct scst_cmd *cmd) return res; } +static void dev_user_ext_copy_remap(struct scst_cmd *cmd, + struct scst_ext_copy_seg_descr *seg) +{ + struct scst_user_cmd *ucmd = cmd->dh_priv; + + TRACE_ENTRY(); + + TRACE_DBG("Preparing EXT_COPY_REMAP for user space (ucmd=%p, h=%d, " + "seg %p)", ucmd, ucmd->h, seg); + + ucmd->user_cmd_payload_len = + offsetof(struct scst_user_get_cmd, remap_cmd) + + sizeof(ucmd->user_cmd.remap_cmd); + ucmd->user_cmd.cmd_h = ucmd->h; + ucmd->user_cmd.subcode = SCST_USER_EXT_COPY_REMAP; + ucmd->user_cmd.remap_cmd.sess_h = (unsigned long)cmd->tgt_dev; + + ucmd->user_cmd.remap_cmd.src_sess_h = (unsigned long)seg->src_tgt_dev; + ucmd->user_cmd.remap_cmd.dst_sess_h = (unsigned long)seg->dst_tgt_dev; + ucmd->user_cmd.remap_cmd.data_descr.src_lba = seg->data_descr.src_lba; + ucmd->user_cmd.remap_cmd.data_descr.dst_lba = seg->data_descr.dst_lba; + ucmd->user_cmd.remap_cmd.data_descr.data_len = seg->data_descr.data_len; + + ucmd->state = UCMD_STATE_EXT_COPY_REMAPPING; + + dev_user_add_to_ready(ucmd); + + TRACE_EXIT(); + return; +} + static void dev_user_free_sgv(struct scst_user_cmd *ucmd) { if (ucmd->sgv != NULL) { @@ -1058,6 +1093,7 @@ static void dev_user_set_block_size(struct scst_cmd *cmd, int block_size) dev->block_size = block_size; else { struct scst_user_dev *udev = cmd->dev->dh_priv; + dev->block_size = udev->def_block_size; } dev->block_shift = -1; /* not used */ @@ -1180,7 +1216,8 @@ static void dev_user_add_to_ready(struct scst_user_cmd *ucmd) &dev->ready_cmd_list); } do_wake |= ((ucmd->state == UCMD_STATE_ON_CACHE_FREEING) || - (ucmd->state == UCMD_STATE_ON_FREEING)); + (ucmd->state == UCMD_STATE_ON_FREEING) || + (ucmd->state == UCMD_STATE_EXT_COPY_REMAPPING)); } if (do_wake) { @@ -1210,8 +1247,9 @@ static int dev_user_map_buf(struct scst_user_cmd *ucmd, unsigned long ubuff, ucmd->num_data_pages = num_pg; - ucmd->data_pages = kmalloc(sizeof(*ucmd->data_pages) * ucmd->num_data_pages, - GFP_KERNEL); + ucmd->data_pages = kmalloc_array(ucmd->num_data_pages, + sizeof(*ucmd->data_pages), + GFP_KERNEL); if (ucmd->data_pages == NULL) { TRACE(TRACE_OUT_OF_MEM, "Unable to allocate data_pages array " "(num_data_pages=%d)", ucmd->num_data_pages); @@ -1278,6 +1316,7 @@ static int dev_user_process_reply_alloc(struct scst_user_cmd *ucmd, if (likely(reply->alloc_reply.pbuf != 0)) { int pages; + if (ucmd->buff_cached) { if (unlikely((reply->alloc_reply.pbuf & ~PAGE_MASK) != 0)) { PRINT_ERROR("Supplied pbuf %llx isn't " @@ -1444,6 +1483,254 @@ static int dev_user_process_reply_on_cache_free(struct scst_user_cmd *ucmd) return res; } +static int dev_user_process_reply_ext_copy_remap(struct scst_user_cmd *ucmd, + struct scst_user_reply_cmd *reply) +{ + int res = 0, rc, count = 0, i, len; + struct scst_user_ext_copy_reply_remap *rreply = &reply->remap_reply; + struct scst_cmd *cmd = ucmd->cmd; + uint8_t *buf; + struct scst_user_ext_copy_data_descr *uleft; + struct scst_ext_copy_data_descr *left = NULL; + + TRACE_ENTRY(); + + if (unlikely((rreply->status != 0) || (rreply->sense_len != 0))) + goto out_status; + + if (rreply->remap_descriptors_len == 0) { + TRACE_DBG("No remap leftovers (ucmd %p, cmd %p)", ucmd, cmd); + goto out_done; + } + + if (unlikely(rreply->remap_descriptors_len > PAGE_SIZE)) { + PRINT_ERROR("Too many leftover REMAP descriptors (len %d, ucmd %p, " + "cmd %p)", rreply->remap_descriptors_len, ucmd, cmd); + res = -EOVERFLOW; + goto out_hw_err; + } + + buf = kzalloc(rreply->remap_descriptors_len, GFP_KERNEL); + if (unlikely(buf == NULL)) { + PRINT_ERROR("Unable to alloc leftover remap descriptors buf " + "(size %d)", rreply->remap_descriptors_len); + goto out_busy; + } + + rc = copy_from_user(buf, + (void __user *)(unsigned long)rreply->remap_descriptors, + rreply->remap_descriptors_len); + if (unlikely(rc != 0)) { + PRINT_ERROR("Failed to copy %d leftover remap descriptors' " + "bytes", rc); + res = -EFAULT; + goto out_free_buf; + } + + uleft = (struct scst_user_ext_copy_data_descr *)buf; + count = rreply->remap_descriptors_len / sizeof(*uleft); + if (unlikely((rreply->remap_descriptors_len % sizeof(*uleft)) != 0)) { + PRINT_ERROR("Invalid leftover remap descriptors (ucmd %p, " + "cmd %p, count %d)", ucmd, cmd, count); + res = -EINVAL; + goto out_hw_err_free_buf; + } + + left = kmalloc_array(count, sizeof(*left), GFP_KERNEL); + if (unlikely(left == NULL)) { + PRINT_ERROR("Unable to alloc leftover remap descriptors " + "(size %zd, count %d)", sizeof(*left) * count, count); + goto out_busy_free_buf; + } + + TRACE_DBG("count %d", count); + + len = 0; + for (i = 0; i < count; i++) { + TRACE_DBG("src_lba %lld, dst_lba %lld, data_len %d (len %d)", + (long long)uleft[i].src_lba, (long long)uleft[i].dst_lba, + uleft[i].data_len, len); + if (unlikely(uleft[i].data_len == 0)) { + PRINT_ERROR("Invalid data descr %d len %d (cmd %p)", i, + uleft[i].data_len, cmd); + res = -EINVAL; + goto out_hw_err_free_buf; + } + left[i].src_lba = uleft[i].src_lba; + left[i].dst_lba = uleft[i].dst_lba; + left[i].data_len = uleft[i].data_len; + len += uleft[i].data_len; + } + + if (unlikely(len > scst_ext_copy_get_cur_seg_data_len(cmd))) { + PRINT_ERROR("Invalid data descr len %d (cmd %p, seg descr len %d)", + len, cmd, scst_ext_copy_get_cur_seg_data_len(cmd)); + res = -EINVAL; + goto out_hw_err_free_buf; + } + +out_free_buf: + kfree(buf); + +out_done: + if (unlikely(res != 0)) { + kfree(left); + left = NULL; + count = 0; + } + + scst_ext_copy_remap_done(cmd, left, count); + + TRACE_EXIT_RES(res); + return res; + +out_hw_err: + scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error)); + goto out_done; + +out_hw_err_free_buf: + scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error)); + goto out_free_buf; + +out_busy_free_buf: + scst_set_busy(cmd); + goto out_free_buf; + +out_busy: + scst_set_busy(cmd); + goto out_done; + +out_status: + TRACE_DBG("Remap finished with status %d (ucmd %p, cmd %p)", + rreply->status, ucmd, cmd); + + if (rreply->sense_len != 0) { + int sense_len; + + res = scst_alloc_sense(cmd, 0); + if (res != 0) + goto out_hw_err; + + sense_len = min_t(int, cmd->sense_buflen, rreply->sense_len); + + rc = copy_from_user(cmd->sense, + (void __user *)(unsigned long)rreply->psense_buffer, + sense_len); + if (rc != 0) { + PRINT_ERROR("Failed to copy %d sense's bytes", rc); + res = -EFAULT; + goto out_hw_err; + } + cmd->sense_valid_len = sense_len; + } + scst_set_cmd_error_status(cmd, rreply->status); + goto out_done; +} + +static int dev_user_process_ws_reply(struct scst_user_cmd *ucmd, + struct scst_user_scsi_cmd_reply_exec *ereply) +{ + int res = 0, rc, count, i; + struct scst_cmd *cmd = ucmd->cmd; + uint8_t *buf; + struct scst_user_data_descriptor *uwhere; + struct scst_data_descriptor *where; + + TRACE_ENTRY(); + + if (unlikely(cmd->cdb[0] != WRITE_SAME) && + unlikely(cmd->cdb[0] != WRITE_SAME_16)) { + PRINT_ERROR("Request to process WRITE SAME for not WRITE SAME " + "CDB (ucmd %p, cmd %p, op %s)", ucmd, cmd, + scst_get_opcode_name(cmd)); + res = -EINVAL; + goto out_hw_err; + } + + if (unlikely(ereply->status != 0)) { + PRINT_ERROR("Request to process WRITE SAME with not 0 status " + "(ucmd %p, cmd %p, status %d)", ucmd, cmd, ereply->status); + res = -EINVAL; + goto out_hw_err; + } + + if (ereply->ws_descriptors_len == 0) { + scst_write_same(cmd, NULL); + goto out; + } + + if (unlikely(ereply->ws_descriptors_len > PAGE_SIZE)) { + PRINT_ERROR("Too many WRITE SAME descriptors (len %d, ucmd %p, " + "cmd %p)", ereply->ws_descriptors_len, ucmd, cmd); + res = -EOVERFLOW; + goto out_hw_err; + } + + buf = kzalloc(ereply->ws_descriptors_len, GFP_KERNEL); + if (unlikely(buf == NULL)) { + PRINT_ERROR("Unable to alloc WS descriptors buf (size %d)", + ereply->ws_descriptors_len); + goto out_busy; + } + + rc = copy_from_user(buf, + (void __user *)(unsigned long)ereply->ws_descriptors, + ereply->ws_descriptors_len); + if (unlikely(rc != 0)) { + PRINT_ERROR("Failed to copy %d WS descriptors' bytes", rc); + res = -EFAULT; + goto out_free_buf; + } + + uwhere = (struct scst_user_data_descriptor *)buf; + count = ereply->ws_descriptors_len / sizeof(*uwhere); + if (unlikely((ereply->ws_descriptors_len % sizeof(*uwhere)) != 0) || + unlikely(uwhere[count-1].usdd_blocks != 0)) { + PRINT_ERROR("Invalid WS descriptors (ucmd %p, cmd %p)", + ucmd, cmd); + res = -EINVAL; + goto out_free_buf; + } + + where = kmalloc_array(count, sizeof(*where), GFP_KERNEL); + if (unlikely(where == NULL)) { + PRINT_ERROR("Unable to alloc WS descriptors where (size %zd)", + sizeof(*where) * count); + goto out_busy_free_buf; + } + + for (i = 0; i < count; i++) { + where[i].sdd_lba = uwhere[i].usdd_lba; + where[i].sdd_blocks = uwhere[i].usdd_blocks; + } + kfree(buf); + + scst_write_same(cmd, where); + +out: + TRACE_EXIT_RES(res); + return res; + +out_free_buf: + kfree(buf); + +out_hw_err: + scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error)); + +out_compl: + cmd->completed = 1; + cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_DIRECT); + /* !! At this point cmd can be already freed !! */ + goto out; + +out_busy_free_buf: + kfree(buf); + +out_busy: + scst_set_busy(cmd); + goto out_compl; +} + static int dev_user_process_reply_exec(struct scst_user_cmd *ucmd, struct scst_user_reply_cmd *reply) { @@ -1480,6 +1767,9 @@ static int dev_user_process_reply_exec(struct scst_user_cmd *ucmd, ucmd->background_exec = 1; TRACE_DBG("Background ucmd %p", ucmd); goto out_compl; + } else if (ereply->reply_type == SCST_EXEC_REPLY_DO_WRITE_SAME) { + res = dev_user_process_ws_reply(ucmd, ereply); + goto out; } else goto out_inval; @@ -1491,6 +1781,7 @@ static int dev_user_process_reply_exec(struct scst_user_cmd *ucmd, if (ereply->resp_data_len != 0) { if (ucmd->ubuff == 0) { int pages, rc; + if (unlikely(ereply->pbuf == 0)) goto out_busy; if (ucmd->buff_cached) { @@ -1657,6 +1948,10 @@ unlock_process: res = dev_user_process_reply_on_cache_free(ucmd); break; + case UCMD_STATE_EXT_COPY_REMAPPING: + res = dev_user_process_reply_ext_copy_remap(ucmd, reply); + break; + case UCMD_STATE_TM_RECEIVED_EXECING: case UCMD_STATE_TM_DONE_EXECING: res = dev_user_process_reply_tm_exec(ucmd, @@ -2036,6 +2331,7 @@ static int dev_user_reply_get_cmd(struct file *file, void __user *arg) if (ureply != 0) { unsigned long u = (unsigned long)ureply; + rc = copy_from_user(&reply, (void __user *)u, sizeof(reply)); if (unlikely(rc != 0)) { PRINT_ERROR("Failed to copy %d user's bytes", rc); @@ -2188,6 +2484,7 @@ static long dev_user_ioctl(struct file *file, unsigned int cmd, case SCST_USER_REGISTER_DEVICE: { struct scst_user_dev_desc *dev_desc; + TRACE_DBG("%s", "REGISTER_DEVICE"); dev_desc = kmalloc(sizeof(*dev_desc), GFP_KERNEL); if (dev_desc == NULL) { @@ -2223,6 +2520,7 @@ static long dev_user_ioctl(struct file *file, unsigned int cmd, case SCST_USER_SET_OPTIONS: { struct scst_user_opt opt; + TRACE_DBG("%s", "SET_OPTIONS"); rc = copy_from_user(&opt, (void __user *)arg, sizeof(opt)); if (rc != 0) { @@ -2353,6 +2651,7 @@ static void dev_user_unjam_cmd(struct scst_user_cmd *ucmd, int busy, break; case UCMD_STATE_EXECING: + case UCMD_STATE_EXT_COPY_REMAPPING: if (flags != NULL) spin_unlock_irqrestore(&dev->udev_cmd_threads.cmd_list_lock, *flags); @@ -2371,8 +2670,13 @@ static void dev_user_unjam_cmd(struct scst_user_cmd *ucmd, int busy, SCST_LOAD_SENSE(scst_sense_lun_not_supported)); } - ucmd->cmd->scst_cmd_done(ucmd->cmd, SCST_CMD_STATE_DEFAULT, - SCST_CONTEXT_THREAD); + if (state == UCMD_STATE_EXECING) + ucmd->cmd->scst_cmd_done(ucmd->cmd, SCST_CMD_STATE_DEFAULT, + SCST_CONTEXT_THREAD); + else { + sBUG_ON(state != UCMD_STATE_EXT_COPY_REMAPPING); + scst_ext_copy_remap_done(ucmd->cmd, NULL, 0); + } /* !! At this point cmd and ucmd can be already freed !! */ if (flags != NULL) @@ -2911,6 +3215,11 @@ static void dev_user_setup_functions(struct scst_user_dev *dev) dev->devtype.dev_alloc_data_buf = dev_user_alloc_data_buf; dev->devtype.dev_done = NULL; + if (dev->ext_copy_remap_supported) + dev->devtype.ext_copy_remap = dev_user_ext_copy_remap; + else + dev->devtype.ext_copy_remap = NULL; + if (dev->parse_type != SCST_USER_PARSE_CALL) { switch (dev->devtype.type) { case TYPE_DISK: @@ -3364,9 +3673,10 @@ static int __dev_user_set_opt(struct scst_user_dev *dev, TRACE_DBG("dev %s, parse_type %x, on_free_cmd_type %x, " "memory_reuse_type %x, partial_transfers_type %x, " - "partial_len %d", dev->name, opt->parse_type, - opt->on_free_cmd_type, opt->memory_reuse_type, - opt->partial_transfers_type, opt->partial_len); + "partial_len %d, opt->ext_copy_remap_supported %d", + dev->name, opt->parse_type, opt->on_free_cmd_type, + opt->memory_reuse_type, opt->partial_transfers_type, + opt->partial_len, opt->ext_copy_remap_supported); if (opt->parse_type > SCST_USER_MAX_PARSE_OPT || opt->on_free_cmd_type > SCST_USER_MAX_ON_FREE_CMD_OPT || @@ -3385,12 +3695,13 @@ static int __dev_user_set_opt(struct scst_user_dev *dev, ((opt->qerr == SCST_QERR_2_RESERVED) || (opt->qerr > SCST_QERR_3_ABORT_THIS_NEXUS_ONLY)) || (opt->swp > 1) || (opt->tas > 1) || (opt->has_own_order_mgmt > 1) || - (opt->d_sense > 1)) { + (opt->d_sense > 1) || (opt->ext_copy_remap_supported > 1)) { PRINT_ERROR("Invalid SCSI option (tst %x, tmf_only %x, " "queue_alg %x, qerr %x, swp %x, tas %x, d_sense %d, " - "has_own_order_mgmt %x)", + "has_own_order_mgmt %x, ext_copy_remap_supported %d)", opt->tst, opt->tmf_only, opt->queue_alg, opt->qerr, - opt->swp, opt->tas, opt->d_sense, opt->has_own_order_mgmt); + opt->swp, opt->tas, opt->d_sense, opt->has_own_order_mgmt, + opt->ext_copy_remap_supported); res = -EINVAL; goto out; } @@ -3419,6 +3730,7 @@ static int __dev_user_set_opt(struct scst_user_dev *dev, dev->tas = opt->tas; dev->d_sense = opt->d_sense; dev->has_own_order_mgmt = opt->has_own_order_mgmt; + dev->ext_copy_remap_supported = opt->ext_copy_remap_supported; if (dev->sdev != NULL) { dev->sdev->tst = opt->tst; dev->sdev->tmf_only = opt->tmf_only; @@ -3475,6 +3787,7 @@ static int dev_user_get_opt(struct file *file, void __user *arg) if (unlikely(res != 0)) goto out; + memset(&opt, 0, sizeof(opt)); opt.parse_type = dev->parse_type; opt.on_free_cmd_type = dev->on_free_cmd_type; opt.memory_reuse_type = dev->memory_reuse_type; @@ -3488,12 +3801,14 @@ static int dev_user_get_opt(struct file *file, void __user *arg) opt.swp = dev->swp; opt.d_sense = dev->d_sense; opt.has_own_order_mgmt = dev->has_own_order_mgmt; + opt.ext_copy_remap_supported = dev->ext_copy_remap_supported; TRACE_DBG("dev %s, parse_type %x, on_free_cmd_type %x, " "memory_reuse_type %x, partial_transfers_type %x, " - "partial_len %d", dev->name, opt.parse_type, - opt.on_free_cmd_type, opt.memory_reuse_type, - opt.partial_transfers_type, opt.partial_len); + "partial_len %d, ext_copy_remap_supported %d", dev->name, + opt.parse_type, opt.on_free_cmd_type, opt.memory_reuse_type, + opt.partial_transfers_type, opt.partial_len, + opt.ext_copy_remap_supported); rc = copy_to_user(arg, &opt, sizeof(opt)); if (unlikely(rc != 0)) { @@ -3563,6 +3878,7 @@ static int dev_user_exit_dev(struct scst_user_dev *dev) static int __dev_user_release(void *arg) { struct scst_user_dev *dev = arg; + dev_user_exit_dev(dev); kmem_cache_free(user_dev_cachep, dev); return 0; @@ -3633,6 +3949,7 @@ static int dev_user_process_cleanup(struct scst_user_dev *dev) #ifdef CONFIG_SCST_EXTRACHECKS { int i; + for (i = 0; i < (int)ARRAY_SIZE(dev->ucmd_hash); i++) { struct list_head *head = &dev->ucmd_hash[i]; struct scst_user_cmd *ucmd2, *tmp; @@ -3674,6 +3991,7 @@ static ssize_t dev_user_sysfs_commands_show(struct kobject *kobj, for (i = 0; i < (int)ARRAY_SIZE(udev->ucmd_hash); i++) { struct list_head *head = &udev->ucmd_hash[i]; struct scst_user_cmd *ucmd; + list_for_each_entry(ucmd, head, hash_list_entry) { ppos = pos; pos += scnprintf(&buf[pos], diff --git a/scst/src/dev_handlers/scst_vdisk.c b/scst/src/dev_handlers/scst_vdisk.c index d88437ce1..bf980ebb5 100644 --- a/scst/src/dev_handlers/scst_vdisk.c +++ b/scst/src/dev_handlers/scst_vdisk.c @@ -79,7 +79,7 @@ static struct scst_trace_log vdisk_local_trace_tbl[] = { #define SCST_FIO_VENDOR "SCST_FIO" #define SCST_BIO_VENDOR "SCST_BIO" /* 4 byte ASCII Product Revision Level - left aligned */ -#define SCST_FIO_REV " 310" +#define SCST_FIO_REV " 320" #define MAX_USN_LEN (20+1) /* For '\0' */ #define MAX_INQ_VEND_SPECIFIC_LEN (INQ_BUF_SZ - 96) @@ -112,15 +112,13 @@ static struct scst_trace_log vdisk_local_trace_tbl[] = { #define DEF_REMOVABLE 0 #define DEF_ROTATIONAL 1 #define DEF_THIN_PROVISIONED 0 +#define DEF_EXPL_ALUA 0 #define VDISK_NULLIO_SIZE (5LL*1024*1024*1024*1024/2) #define DEF_TST SCST_TST_1_SEP_TASK_SETS #define DEF_TMF_ONLY 0 -#define NO_CAW_LEN_LIM 0xff -#define DEF_CAW_LEN_LIM 0xfe - /* * Since we can't control backstorage device's reordering, we have to always * report unrestricted reordering. @@ -178,6 +176,9 @@ struct scst_vdisk_dev { unsigned int tst:3; unsigned int format_active:1; unsigned int discard_zeroes_data:1; + unsigned int expl_alua:1; + unsigned int reexam_pending:1; + unsigned int size_key:1; struct file *fd; struct file *dif_fd; @@ -189,10 +190,10 @@ struct scst_vdisk_dev { uint64_t format_progress_to_do, format_progress_done; int virt_id; - char name[64+1]; /* Name of the virtual device, - must be <= SCSI Model + 1 */ - char *filename; /* File name, protected by - scst_mutex and suspended activities */ + /* Name of the virtual device, must be <= SCSI Model + 1 */ + char name[64+1]; + /* File name, protected by scst_mutex and suspended activities */ + char *filename; uint16_t command_set_version; unsigned int initial_cluster_mode:1; @@ -223,10 +224,6 @@ struct scst_vdisk_dev { /* Unmap INQUIRY parameters */ uint32_t unmap_opt_gran, unmap_align, unmap_max_lba_cnt; - /* Block limits INQUIRY parameters */ - uint8_t caw_len_lim; - struct mutex caw_mutex; - struct scst_device *dev; struct list_head vdev_list_entry; @@ -299,9 +296,14 @@ static int fileio_alloc_data_buf(struct scst_cmd *cmd); static int vdisk_parse(struct scst_cmd *); static int vcdrom_parse(struct scst_cmd *); static int non_fileio_parse(struct scst_cmd *); -static int vdisk_exec(struct scst_cmd *cmd); +static int fileio_exec(struct scst_cmd *cmd); static int vcdrom_exec(struct scst_cmd *cmd); -static int non_fileio_exec(struct scst_cmd *cmd); +static int blockio_exec(struct scst_cmd *cmd); +static int nullio_exec(struct scst_cmd *cmd); +static void blockio_on_alua_state_change_start(struct scst_device *dev, + enum scst_tg_state old_state, enum scst_tg_state new_state); +static void blockio_on_alua_state_change_finish(struct scst_device *dev, + enum scst_tg_state old_state, enum scst_tg_state new_state); static void fileio_on_free_cmd(struct scst_cmd *cmd); static enum compl_status_e nullio_exec_read(struct vdisk_cmd_params *p); static enum compl_status_e blockio_exec_read(struct vdisk_cmd_params *p); @@ -324,6 +326,7 @@ static enum compl_status_e vdisk_exec_read_capacity(struct vdisk_cmd_params *p); static enum compl_status_e vdisk_exec_read_capacity16(struct vdisk_cmd_params *p); static enum compl_status_e vdisk_exec_get_lba_status(struct vdisk_cmd_params *p); static enum compl_status_e vdisk_exec_report_tpgs(struct vdisk_cmd_params *p); +static enum compl_status_e vdisk_exec_set_tpgs(struct vdisk_cmd_params *p); static enum compl_status_e vdisk_exec_inquiry(struct vdisk_cmd_params *p); static enum compl_status_e vdisk_exec_request_sense(struct vdisk_cmd_params *p); static enum compl_status_e vdisk_exec_mode_sense(struct vdisk_cmd_params *p); @@ -333,7 +336,6 @@ static enum compl_status_e vdisk_exec_read_toc(struct vdisk_cmd_params *p); static enum compl_status_e vdisk_exec_prevent_allow_medium_removal(struct vdisk_cmd_params *p); static enum compl_status_e vdisk_exec_unmap(struct vdisk_cmd_params *p); static enum compl_status_e vdisk_exec_write_same(struct vdisk_cmd_params *p); -static enum compl_status_e vdisk_exec_caw(struct vdisk_cmd_params *p); static int vdisk_fsync(loff_t loff, loff_t len, struct scst_device *dev, gfp_t gfp_flags, struct scst_cmd *cmd, bool async); @@ -357,6 +359,10 @@ static ssize_t vcdrom_del_device(const char *device_name); static void vdisk_task_mgmt_fn_done(struct scst_mgmt_cmd *mcmd, struct scst_tgt_dev *tgt_dev); static uint64_t vdisk_gen_dev_id_num(const char *virt_dev_name); +#ifdef CONFIG_DEBUG_EXT_COPY_REMAP +static void vdev_ext_copy_remap(struct scst_cmd *cmd, + struct scst_ext_copy_seg_descr *descr); +#endif static int vdisk_unmap_range(struct scst_cmd *cmd, struct scst_vdisk_dev *virt_dev, uint64_t start_lba, uint32_t blocks); @@ -384,6 +390,10 @@ static ssize_t vdisk_sysfs_tst_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf); static ssize_t vdisk_sysfs_rotational_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf); +static ssize_t vdisk_sysfs_expl_alua_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf); +static ssize_t vdisk_sysfs_expl_alua_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count); static ssize_t vdisk_sysfs_nv_cache_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf); static ssize_t vdisk_sysfs_o_direct_show(struct kobject *kobj, @@ -476,6 +486,9 @@ static struct kobj_attribute vdisk_tst_attr = __ATTR(tst, S_IRUGO, vdisk_sysfs_tst_show, NULL); static struct kobj_attribute vdisk_rotational_attr = __ATTR(rotational, S_IRUGO, vdisk_sysfs_rotational_show, NULL); +static struct kobj_attribute vdisk_expl_alua_attr = + __ATTR(expl_alua, S_IWUSR|S_IRUGO, vdisk_sysfs_expl_alua_show, + vdisk_sysfs_expl_alua_store); static struct kobj_attribute vdisk_nv_cache_attr = __ATTR(nv_cache, S_IRUGO, vdisk_sysfs_nv_cache_show, NULL); static struct kobj_attribute vdisk_o_direct_attr = @@ -545,6 +558,7 @@ static const struct attribute *vdisk_fileio_attrs[] = { &vdisk_tp_attr.attr, &vdisk_tst_attr.attr, &vdisk_rotational_attr.attr, + &vdisk_expl_alua_attr.attr, &vdisk_nv_cache_attr.attr, &vdisk_o_direct_attr.attr, &vdisk_removable_attr.attr, @@ -567,11 +581,12 @@ static const struct attribute *vdisk_fileio_attrs[] = { }; static const struct attribute *vdisk_blockio_attrs[] = { - &vdev_size_ro_attr.attr, - &vdev_size_mb_ro_attr.attr, + &vdev_size_rw_attr.attr, + &vdev_size_mb_rw_attr.attr, &vdisk_blocksize_attr.attr, &vdisk_rd_only_attr.attr, &vdisk_wt_attr.attr, + &vdisk_expl_alua_attr.attr, &vdisk_nv_cache_attr.attr, &vdisk_tst_attr.attr, &vdisk_removable_attr.attr, @@ -668,15 +683,19 @@ static struct scst_dev_type vdisk_file_devtype = { .threads_num = -1, .parse_atomic = 1, .dev_done_atomic = 1, + .auto_cm_assignment_possible = 1, .attach = vdisk_attach, .detach = vdisk_detach, .attach_tgt = vdisk_attach_tgt, .detach_tgt = vdisk_detach_tgt, .parse = vdisk_parse, .dev_alloc_data_buf = fileio_alloc_data_buf, - .exec = vdisk_exec, + .exec = fileio_exec, .on_free_cmd = fileio_on_free_cmd, .task_mgmt_fn_done = vdisk_task_mgmt_fn_done, +#ifdef CONFIG_DEBUG_EXT_COPY_REMAP + .ext_copy_remap = vdev_ext_copy_remap, +#endif .get_supported_opcodes = vdisk_get_supported_opcodes, .devt_priv = (void *)fileio_ops, #ifdef CONFIG_SCST_PROC @@ -725,12 +744,15 @@ static struct scst_dev_type vdisk_blk_devtype = { #ifdef CONFIG_SCST_PROC .no_proc = 1, #endif + .auto_cm_assignment_possible = 1, .attach = vdisk_attach, .detach = vdisk_detach, .attach_tgt = vdisk_attach_tgt, .detach_tgt = vdisk_detach_tgt, .parse = non_fileio_parse, - .exec = non_fileio_exec, + .exec = blockio_exec, + .on_alua_state_change_start = blockio_on_alua_state_change_start, + .on_alua_state_change_finish = blockio_on_alua_state_change_finish, .task_mgmt_fn_done = vdisk_task_mgmt_fn_done, .get_supported_opcodes = vdisk_get_supported_opcodes, .devt_priv = (void *)blockio_ops, @@ -773,12 +795,13 @@ static struct scst_dev_type vdisk_null_devtype = { #ifdef CONFIG_SCST_PROC .no_proc = 1, #endif + .auto_cm_assignment_possible = 1, .attach = vdisk_attach, .detach = vdisk_detach, .attach_tgt = vdisk_attach_tgt, .detach_tgt = vdisk_detach_tgt, .parse = non_fileio_parse, - .exec = non_fileio_exec, + .exec = nullio_exec, .task_mgmt_fn_done = vdisk_task_mgmt_fn_done, .devt_priv = (void *)nullio_ops, .get_supported_opcodes = vdisk_get_supported_opcodes, @@ -816,6 +839,7 @@ static struct scst_dev_type vcdrom_devtype = { .threads_num = -1, .parse_atomic = 1, .dev_done_atomic = 1, + .auto_cm_assignment_possible = 1, .attach = vdisk_attach, .detach = vdisk_detach, .attach_tgt = vdisk_attach_tgt, @@ -895,8 +919,13 @@ static struct file *vdev_open_fd(const struct scst_vdisk_dev *virt_dev, TRACE_DBG("Opening file %s, flags 0x%x", name, open_flags); fd = filp_open(name, O_LARGEFILE | open_flags, 0600); - if (IS_ERR(fd)) - PRINT_ERROR("filp_open(%s) failed: %d", name, (int)PTR_ERR(fd)); + if (IS_ERR(fd)) { + if (PTR_ERR(fd) == -EMEDIUMTYPE) + TRACE(TRACE_MINOR, "Unable to open %s with EMEDIUMTYPE, " + "DRBD passive?", name); + else + PRINT_ERROR("filp_open(%s) failed: %d", name, (int)PTR_ERR(fd)); + } TRACE_EXIT(); return fd; @@ -914,8 +943,12 @@ static void vdisk_blockio_check_flush_support(struct scst_vdisk_dev *virt_dev) fd = filp_open(virt_dev->filename, O_LARGEFILE, 0600); if (IS_ERR(fd)) { - PRINT_ERROR("filp_open(%s) failed: %ld", - virt_dev->filename, PTR_ERR(fd)); + if ((PTR_ERR(fd) == -EMEDIUMTYPE) && virt_dev->blockio) + TRACE(TRACE_MINOR, "Unable to open %s with EMEDIUMTYPE, " + "DRBD passive?", virt_dev->filename); + else + PRINT_ERROR("filp_open(%s) failed: %ld", + virt_dev->filename, PTR_ERR(fd)); goto out; } @@ -955,8 +988,12 @@ static void vdisk_check_tp_support(struct scst_vdisk_dev *virt_dev) fd = filp_open(virt_dev->filename, O_LARGEFILE, 0600); if (IS_ERR(fd)) { - PRINT_ERROR("filp_open(%s) failed: %ld", - virt_dev->filename, PTR_ERR(fd)); + if ((PTR_ERR(fd) == -EMEDIUMTYPE) && virt_dev->blockio) + TRACE(TRACE_MINOR, "Unable to open %s with EMEDIUMTYPE, " + "DRBD passive?", virt_dev->filename); + else + PRINT_ERROR("filp_open(%s) failed: %ld", + virt_dev->filename, PTR_ERR(fd)); goto check; } fd_open = true; @@ -999,6 +1036,7 @@ check: if (virt_dev->thin_provisioned) { int block_shift = virt_dev->dev->block_shift; + if (virt_dev->blockio) { struct request_queue *q; @@ -1058,7 +1096,11 @@ static int vdisk_get_file_size(const char *filename, bool blockio, fd = filp_open(filename, O_LARGEFILE | O_RDONLY, 0600); if (IS_ERR(fd)) { res = PTR_ERR(fd); - PRINT_ERROR("filp_open(%s) failed: %d", filename, res); + if ((res == -EMEDIUMTYPE) && blockio) + TRACE(TRACE_MINOR, "Unable to open %s with EMEDIUMTYPE, " + "DRBD passive?", filename); + else + PRINT_ERROR("filp_open(%s) failed: %d", filename, res); goto out; } @@ -1070,11 +1112,11 @@ static int vdisk_get_file_size(const char *filename, bool blockio, goto out_close; } - if (S_ISREG(inode->i_mode)) - /* Nothing to do */; - else if (S_ISBLK(inode->i_mode)) + if (S_ISREG(inode->i_mode)) { + /* Nothing to do */ + } else if (S_ISBLK(inode->i_mode)) { inode = inode->i_bdev->bd_inode; - else { + } else { res = -EINVAL; goto out_close; } @@ -1258,11 +1300,7 @@ static int __vdev_load_mode_pages(struct scst_vdisk_dev *virt_dev, char *params) if (scst_get_next_lexem(¶m)[0] != '\0') goto out_too_many; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtoul(pp, 0, &val); -#else - res = strict_strtoul(pp, 0, &val); -#endif if (res != 0) goto out_strtoul_failed; @@ -1481,8 +1519,14 @@ static int vdisk_reexamine(struct scst_vdisk_dev *virt_dev) res = vdisk_get_file_size(virt_dev->filename, virt_dev->blockio, &file_size); - if (res < 0) + if (res < 0) { + if ((res == -EMEDIUMTYPE) && virt_dev->blockio) { + TRACE_DBG("Reexam pending (dev %s)", virt_dev->name); + virt_dev->reexam_pending = 1; + res = 0; + } goto out; + } virt_dev->file_size = file_size; vdisk_blockio_check_flush_support(virt_dev); vdisk_check_tp_support(virt_dev); @@ -1697,15 +1741,12 @@ static int vdisk_open_fd(struct scst_vdisk_dev *virt_dev, bool read_only) { int res; - lockdep_assert_held(&scst_mutex); sBUG_ON(!virt_dev->filename); virt_dev->fd = vdev_open_fd(virt_dev, virt_dev->filename, read_only); if (IS_ERR(virt_dev->fd)) { res = PTR_ERR(virt_dev->fd); virt_dev->fd = NULL; - PRINT_ERROR("filp_open(%s) failed: %d", - virt_dev->filename, res); goto out; } virt_dev->bdev = virt_dev->blockio ? file_inode(virt_dev->fd)->i_bdev : @@ -1733,8 +1774,6 @@ out_close_fd: static void vdisk_close_fd(struct scst_vdisk_dev *virt_dev) { - lockdep_assert_held(&scst_mutex); - if (virt_dev->fd) { filp_close(virt_dev->fd, NULL); virt_dev->fd = NULL; @@ -1756,14 +1795,21 @@ static int vdisk_attach_tgt(struct scst_tgt_dev *tgt_dev) lockdep_assert_held(&scst_mutex); - if (virt_dev->tgt_dev_cnt++ > 0) + virt_dev->tgt_dev_cnt++; + + if (virt_dev->fd != NULL) goto out; if (!virt_dev->nullio && !virt_dev->cdrom_empty) { res = vdisk_open_fd(virt_dev, tgt_dev->dev->dev_rd_only); if (res != 0) { - virt_dev->tgt_dev_cnt--; - goto out; + if ((res == -EMEDIUMTYPE) && virt_dev->blockio) { + /* It's OK, it will be reopen on exec */ + res = 0; + } else { + virt_dev->tgt_dev_cnt--; + goto out; + } } } else { virt_dev->fd = NULL; @@ -1810,6 +1856,7 @@ static enum compl_status_e vdisk_synchronize_cache(struct vdisk_cmd_params *p) if (data_len == 0) { struct scst_vdisk_dev *virt_dev = dev->dh_priv; + data_len = virt_dev->file_size - ((loff_t)scst_cmd_get_lba(cmd) << dev->block_shift); } @@ -1877,6 +1924,17 @@ static enum compl_status_e vdisk_exec_maintenance_in(struct vdisk_cmd_params *p) return CMD_SUCCEEDED; } +static enum compl_status_e vdisk_exec_maintenance_out(struct vdisk_cmd_params *p) +{ + switch (p->cmd->cdb[1] & 0x1f) { + case MO_SET_TARGET_PGS: + return vdisk_exec_set_tpgs(p); + } + scst_set_invalid_field_in_cdb(p->cmd, 1, + 0 | SCST_INVAL_FIELD_BIT_OFFS_VALID); + return CMD_SUCCEEDED; +} + static enum compl_status_e vdisk_exec_send_diagnostic(struct vdisk_cmd_params *p) { return CMD_SUCCEEDED; @@ -1943,6 +2001,7 @@ static int vdisk_format_dif(struct scst_cmd *cmd, uint64_t start_lba, i = -1; while (1) { int len = min_t(size_t, (size_t)left, PAGE_SIZE); + full_len += len; i++; iv_count++; @@ -2152,6 +2211,7 @@ static enum compl_status_e vdisk_exec_format_unit(struct vdisk_cmd_params *p) if (virt_dev->thin_provisioned) { int rc = vdisk_unmap_range(cmd, virt_dev, 0, virt_dev->nblocks); + if (rc != 0) goto finished; } @@ -2591,8 +2651,8 @@ static const struct scst_opcode_descriptor scst_op_descr_read_toc = { [UNMAP] = vdisk_exec_unmap, \ [WRITE_SAME] = vdisk_exec_write_same, \ [WRITE_SAME_16] = vdisk_exec_write_same, \ - [COMPARE_AND_WRITE] = vdisk_exec_caw, \ [MAINTENANCE_IN] = vdisk_exec_maintenance_in, \ + [MAINTENANCE_OUT] = vdisk_exec_maintenance_out, \ [SEND_DIAGNOSTIC] = vdisk_exec_send_diagnostic, \ [FORMAT_UNIT] = vdisk_exec_format_unit, @@ -2711,12 +2771,14 @@ static vdisk_op_fn nullio_ops[256] = { &scst_op_descr_write_same16, \ &scst_op_descr_unmap, \ &scst_op_descr_format_unit, \ + &scst_op_descr_extended_copy, \ &scst_op_descr_cwr, static const struct scst_opcode_descriptor *vdisk_opcode_descriptors[] = { SHARED_OPCODE_DESCRIPTORS VDISK_OPCODE_DESCRIPTORS SCST_OPCODE_DESCRIPTORS + &scst_op_descr_stpg, /* must be last, see vdisk_get_supported_opcodes()! */ }; static const struct scst_opcode_descriptor *vdisk_opcode_descriptors_type2[] = { @@ -2741,6 +2803,9 @@ static int vdisk_get_supported_opcodes(struct scst_cmd *cmd, const struct scst_opcode_descriptor ***out_supp_opcodes, int *out_supp_opcodes_cnt) { + struct scst_device *dev = cmd->dev; + struct scst_vdisk_dev *virt_dev = dev->dh_priv; + if (cmd->dev->dev_dif_type != 2) { *out_supp_opcodes = vdisk_opcode_descriptors; *out_supp_opcodes_cnt = ARRAY_SIZE(vdisk_opcode_descriptors); @@ -2748,6 +2813,10 @@ static int vdisk_get_supported_opcodes(struct scst_cmd *cmd, *out_supp_opcodes = vdisk_opcode_descriptors_type2; *out_supp_opcodes_cnt = ARRAY_SIZE(vdisk_opcode_descriptors_type2); } + if (!virt_dev->expl_alua) { + (*out_supp_opcodes_cnt)--; + sBUG_ON((*out_supp_opcodes)[*out_supp_opcodes_cnt]->od_serv_action != MO_SET_TARGET_PGS); + } return 0; } @@ -2868,7 +2937,6 @@ static bool vdisk_parse_offset(struct vdisk_cmd_params *p, struct scst_cmd *cmd) case WRITE_10: case WRITE_12: case WRITE_16: - case COMPARE_AND_WRITE: fua = (cdb[1] & 0x8); if (fua) { TRACE(TRACE_ORDER, "FUA: loff=%lld, " @@ -2937,6 +3005,7 @@ out: static int vcdrom_parse(struct scst_cmd *cmd) { int res, rc; + rc = scst_cdrom_generic_parse(cmd); if (rc != 0) { res = scst_get_cmd_abnormal_done_state(cmd); @@ -3252,7 +3321,7 @@ static struct scatterlist *alloc_sg(size_t size, unsigned off, gfp_t gfp_mask, sg_cnt = PAGE_ALIGN(size + off) >> PAGE_SHIFT; sg = sg_cnt <= small_sg_size ? small_sg : - kmalloc(sg_cnt * sizeof(*sg), gfp_mask); + kmalloc_array(sg_cnt, sizeof(*sg), gfp_mask); if (!sg) goto out; @@ -3414,7 +3483,7 @@ out_invalid_opcode: goto out_compl; } -static int vdisk_exec(struct scst_cmd *cmd) +static int fileio_exec(struct scst_cmd *cmd) { struct scst_vdisk_dev *virt_dev = cmd->dev->dh_priv; const vdisk_op_fn *ops = virt_dev->vdev_devt->devt_priv; @@ -3457,8 +3526,112 @@ out: return; } -/* blockio and nullio */ -static int non_fileio_exec(struct scst_cmd *cmd) +/* + * Functionally identical to scst_tg_accept_standby(), but separated, because, + * generally, they are checking for different things. Better to keep different + * things separately. + */ +static bool vdisk_no_fd_allowed_commands(const struct scst_cmd *cmd) +{ + bool res; + + TRACE_ENTRY(); + + switch (cmd->cdb[0]) { + case TEST_UNIT_READY: + case GET_EVENT_STATUS_NOTIFICATION: + case INQUIRY: + case MODE_SENSE: + case MODE_SENSE_10: + case READ_CAPACITY: + case REPORT_LUNS: + case REQUEST_SENSE: + case RELEASE: + case RELEASE_10: + case RESERVE: + case RESERVE_10: + case READ_BUFFER: + case WRITE_BUFFER: + case MODE_SELECT: + case MODE_SELECT_10: + case LOG_SELECT: + case LOG_SENSE: + case RECEIVE_DIAGNOSTIC: + case SEND_DIAGNOSTIC: + case PERSISTENT_RESERVE_IN: + case PERSISTENT_RESERVE_OUT: + res = true; + goto out; + case SERVICE_ACTION_IN_16: + switch (cmd->cdb[1] & 0x1f) { + case SAI_READ_CAPACITY_16: + res = true; + goto out; + } + break; + case MAINTENANCE_IN: + switch (cmd->cdb[1] & 0x1f) { + case MI_REPORT_TARGET_PGS: + res = true; + goto out; + } + break; + case MAINTENANCE_OUT: + switch (cmd->cdb[1] & 0x1f) { + case MO_SET_TARGET_PGS: + res = true; + goto out; + } + break; + } + + res = false; + +out: + TRACE_EXIT_RES(res); + return res; +} + +static int blockio_exec(struct scst_cmd *cmd) +{ + struct scst_vdisk_dev *virt_dev = cmd->dev->dh_priv; + const vdisk_op_fn *ops = virt_dev->vdev_devt->devt_priv; + struct vdisk_cmd_params p; + int res; + + EXTRACHECKS_BUG_ON(!ops); + + memset(&p, 0, sizeof(p)); + if (unlikely(!vdisk_parse_offset(&p, cmd))) + goto err; + + if (unlikely(virt_dev->fd == NULL)) { + if (!vdisk_no_fd_allowed_commands(cmd)) { + /* We should not get here */ + PRINT_WARNING("Closed FD on exec. Secondary DRBD or not " + "blocked dev before ALUA state change? " + "(cmd %p, op %s, dev %s)", cmd, cmd->op_name, + cmd->dev->virt_name); + scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_no_medium)); + goto err; + } + } + + cmd->dh_priv = &p; + res = vdev_do_job(cmd, ops); + cmd->dh_priv = NULL; + +out: + return res; + +err: + res = SCST_EXEC_COMPLETED; + cmd->completed = 1; + cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME); + goto out; +} + +static int nullio_exec(struct scst_cmd *cmd) { struct scst_vdisk_dev *virt_dev = cmd->dev->dh_priv; const vdisk_op_fn *ops = virt_dev->vdev_devt->devt_priv; @@ -3737,7 +3910,7 @@ static enum compl_status_e vdisk_exec_write_same(struct vdisk_cmd_params *p) if (cmd->cdb[ctrl_offs] & 0x8) vdisk_exec_write_same_unmap(p); else { - scst_write_same(cmd); + scst_write_same(cmd, NULL); res = RUNNING_ASYNC; } @@ -4010,7 +4183,7 @@ static int vdisk_block_limits(uint8_t *buf, struct scst_cmd *cmd, buf[1] = 0xB0; buf[3] = 0x3C; buf[4] = 1; /* WSNZ set */ - buf[5] = virt_dev->caw_len_lim; + buf[5] = 0xFF; /* No MAXIMUM COMPARE AND WRITE LENGTH limit */ /* Optimal transfer granuality is PAGE_SIZE */ put_unaligned_be16(max_t(int, PAGE_SIZE / dev->block_size, 1), &buf[6]); @@ -4093,8 +4266,12 @@ static int vdisk_inq(uint8_t *buf, struct scst_cmd *cmd, buf[4] = 31;/* n - 4 = 35 - 4 = 31 for full 36 byte data */ if (cmd->dev->dev_dif_mode != SCST_DIF_MODE_NONE) buf[5] |= 1; /* PROTECT */ - if (scst_impl_alua_configured(cmd->dev)) + if (scst_alua_configured(cmd->dev)) { buf[5] |= SCST_INQ_TPGS_MODE_IMPLICIT; + if (virt_dev->expl_alua) + buf[5] |= SCST_INQ_TPGS_MODE_EXPLICIT; + } + buf[5] |= 8; /* 3PC */ buf[6] = 0x10; /* MultiP 1 */ buf[7] = 2; /* CMDQUE 1, BQue 0 => commands queuing supported */ @@ -4136,6 +4313,7 @@ static int vdisk_inq(uint8_t *buf, struct scst_cmd *cmd, /* Physical transport */ if (cmd->tgtt->get_phys_transport_version != NULL) { uint16_t v = cmd->tgtt->get_phys_transport_version(cmd->tgt); + if (v != 0) { put_unaligned_be16(v, &buf[58 + num]); num += 2; @@ -4208,19 +4386,19 @@ static enum compl_status_e vdisk_exec_inquiry(struct vdisk_cmd_params *p) SCSI_INQ_PQ_CON << 5 | dev->type; /* Vital Product */ if (cmd->cdb[1] & EVPD) { - if (0 == cmd->cdb[2]) { + if (cmd->cdb[2] == 0) { resp_len = vdisk_sup_vpd(buf, cmd, virt_dev); - } else if (0x80 == cmd->cdb[2]) { + } else if (cmd->cdb[2] == 0x80) { resp_len = vdisk_usn_vpd(buf, cmd, virt_dev); - } else if (0x83 == cmd->cdb[2]) { + } else if (cmd->cdb[2] == 0x83) { resp_len = vdisk_dev_id_vpd(buf, cmd, virt_dev); - } else if (0x86 == cmd->cdb[2]) { + } else if (cmd->cdb[2] == 0x86) { resp_len = vdisk_ext_inq(buf, cmd, virt_dev); - } else if ((0xB0 == cmd->cdb[2]) && (dev->type == TYPE_DISK)) { + } else if (cmd->cdb[2] == 0xB0 && dev->type == TYPE_DISK) { resp_len = vdisk_block_limits(buf, cmd, virt_dev); - } else if ((0xB1 == cmd->cdb[2]) && (dev->type == TYPE_DISK)) { + } else if (cmd->cdb[2] == 0xB1 && dev->type == TYPE_DISK) { resp_len = vdisk_bdev_char(buf, cmd, virt_dev); - } else if ((0xB2 == cmd->cdb[2]) && (dev->type == TYPE_DISK) && + } else if (cmd->cdb[2] == 0xB2 && dev->type == TYPE_DISK && virt_dev->thin_provisioned) { resp_len = vdisk_tp_vpd(buf, cmd, virt_dev); } else { @@ -4327,7 +4505,7 @@ static int vdisk_err_recov_pg(unsigned char *p, int pcontrol, 1, 0, 0xff, 0xff}; memcpy(p, err_recov_pg, sizeof(err_recov_pg)); - if (1 == pcontrol) + if (pcontrol == 1) memset(p + 2, 0, sizeof(err_recov_pg) - 2); return sizeof(err_recov_pg); } @@ -4339,7 +4517,7 @@ static int vdisk_disconnect_pg(unsigned char *p, int pcontrol, 0, 0, 0, 0, 0, 0, 0, 0}; memcpy(p, disconnect_pg, sizeof(disconnect_pg)); - if (1 == pcontrol) + if (pcontrol == 1) memset(p + 2, 0, sizeof(disconnect_pg) - 2); return sizeof(disconnect_pg); } @@ -4366,7 +4544,7 @@ static int vdisk_rigid_geo_pg(unsigned char *p, int pcontrol, memcpy(&n, p + 2, sizeof(u32)); n = n | ((__force u32)cpu_to_be32(ncyl) >> 8); memcpy(p + 2, &n, sizeof(u32)); - if (1 == pcontrol) + if (pcontrol == 1) memset(p + 2, 0, sizeof(geo_m_pg) - 2); return sizeof(geo_m_pg); } @@ -4381,7 +4559,7 @@ static int vdisk_format_pg(unsigned char *p, int pcontrol, memcpy(p, format_pg, sizeof(format_pg)); put_unaligned_be16(DEF_SECTORS, &p[10]); put_unaligned_be16(virt_dev->dev->block_size, &p[12]); - if (1 == pcontrol) + if (pcontrol == 1) memset(p + 2, 0, sizeof(format_pg) - 2); return sizeof(format_pg); } @@ -4500,7 +4678,7 @@ static int vdisk_iec_m_pg(unsigned char *p, int pcontrol, const unsigned char iec_m_pg[] = {0x1c, 0xa, 0x08, 0, 0, 0, 0, 0, 0, 0, 0x0, 0x0}; memcpy(p, iec_m_pg, sizeof(iec_m_pg)); - if (1 == pcontrol) + if (pcontrol == 1) memset(p + 2, 0, sizeof(iec_m_pg) - 2); return sizeof(iec_m_pg); } @@ -4537,7 +4715,7 @@ static enum compl_status_e vdisk_exec_mode_sense(struct vdisk_cmd_params *p) pcontrol = (cmd->cdb[2] & 0xc0) >> 6; pcode = cmd->cdb[2] & 0x3f; subpcode = cmd->cdb[3]; - msense_6 = (MODE_SENSE == cmd->cdb[0]); + msense_6 = (cmd->cdb[0] == MODE_SENSE); dev_spec = cmd->tgt_dev->tgt_dev_rd_only ? WP : 0; if (type != TYPE_ROM) @@ -4547,7 +4725,7 @@ static enum compl_status_e vdisk_exec_mode_sense(struct vdisk_cmd_params *p) if (unlikely(length <= 0)) goto out_free; - if (!vdev_saved_mode_pages_enabled && (0x3 == pcontrol)) { + if (!vdev_saved_mode_pages_enabled && (pcontrol == 0x3)) { TRACE_DBG("%s", "MODE SENSE: Saving values not supported"); scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_saving_params_unsup)); @@ -4564,7 +4742,7 @@ static enum compl_status_e vdisk_exec_mode_sense(struct vdisk_cmd_params *p) offset = 8; } - if (0 != subpcode) { + if (subpcode != 0) { /* TODO: Control Extension page */ TRACE_DBG("%s", "MODE SENSE: Only subpage 0 is supported"); scst_set_invalid_field_in_cdb(cmd, 3, 0); @@ -4677,6 +4855,9 @@ static int vdisk_set_wt(struct scst_vdisk_dev *virt_dev, int wt, bool read_only) virt_dev->wt_flag = wt; spin_unlock(&virt_dev->flags_lock); + if (virt_dev->fd == NULL) + goto out; + /* * MODE SELECT is strictly serialized command, so it's safe here * to reopen fd. @@ -4696,8 +4877,7 @@ static int vdisk_set_wt(struct scst_vdisk_dev *virt_dev, int wt, bool read_only) } } - if (virt_dev->fd) - filp_close(virt_dev->fd, NULL); + filp_close(virt_dev->fd, NULL); if (virt_dev->dif_fd) filp_close(virt_dev->dif_fd, NULL); @@ -4947,7 +5127,7 @@ static enum compl_status_e vdisk_exec_mode_select(struct vdisk_cmd_params *p) goto out; } - mselect_6 = (MODE_SELECT == cmd->cdb[0]); + mselect_6 = (cmd->cdb[0] == MODE_SELECT); type = cmd->dev->type; length = scst_get_buf_full_sense(cmd, &address); @@ -5054,6 +5234,7 @@ static enum compl_status_e vdisk_exec_read_capacity(struct vdisk_cmd_params *p) if ((cmd->cdb[8] & 1) == 0) { uint32_t lba = get_unaligned_be32(&cmd->cdb[2]); + if (lba != 0) { TRACE_DBG("PMI zero and LBA not zero (cmd %p)", cmd); scst_set_invalid_field_in_cdb(cmd, 2, 0); @@ -5129,6 +5310,7 @@ static enum compl_status_e vdisk_exec_read_capacity16(struct vdisk_cmd_params *p if ((cmd->cdb[14] & 1) == 0) { uint32_t lba = get_unaligned_be32(&cmd->cdb[2]); + if (lba != 0) { TRACE_DBG("PMI zero and LBA not zero (cmd %p)", cmd); scst_set_invalid_field_in_cdb(cmd, 2, 0); @@ -5239,6 +5421,36 @@ out: return CMD_SUCCEEDED; } +/* SPC-4 SET TARGET PORT GROUPS command */ +static enum compl_status_e vdisk_exec_set_tpgs(struct vdisk_cmd_params *p) +{ + struct scst_cmd *cmd = p->cmd; + struct scst_device *dev = cmd->dev; + struct scst_vdisk_dev *virt_dev = dev->dh_priv; + int res = CMD_SUCCEEDED, rc; + + TRACE_ENTRY(); + + if (!virt_dev->expl_alua) { + PRINT_ERROR("SET TARGET PORT GROUPS: not explicit ALUA mode " + "(dev %s)", dev->virt_name); + /* Invalid opcode, i.e. SA field */ + scst_set_invalid_field_in_cdb(cmd, 1, + 0 | SCST_INVAL_FIELD_BIT_OFFS_VALID); + goto out; + } + + rc = scst_tg_set_group_info(cmd); + if (rc == 0) + res = RUNNING_ASYNC; + else + scst_stpg_del_unblock_next(cmd); + +out: + TRACE_EXIT_RES(res); + return res; +} + static enum compl_status_e vdisk_exec_read_toc(struct vdisk_cmd_params *p) { struct scst_cmd *cmd = p->cmd; @@ -5288,8 +5500,10 @@ static enum compl_status_e vdisk_exec_read_toc(struct vdisk_cmd_params *p) off = 4; if (cmd->cdb[6] <= 1) { /* Fistr TOC Track Descriptor */ - /* ADDR 0x10 - Q Sub-channel encodes current position data - CONTROL 0x04 - Data track, recoreded uninterrupted */ + /* + * ADDR 0x10 - Q Sub-channel encodes current position data + * CONTROL 0x04 - Data track, recoreded uninterrupted + */ buffer[off+1] = 0x14; /* Track Number */ buffer[off+2] = 0x01; @@ -5494,7 +5708,8 @@ static struct iovec *vdisk_alloc_iv(struct scst_cmd *cmd, p->iv_count = 0; /* It can't be called in atomic context */ p->iv = (iv_count <= ARRAY_SIZE(p->small_iv)) ? p->small_iv : - kmalloc(sizeof(*p->iv) * iv_count, cmd->cmd_gfp_mask); + kmalloc_array(iv_count, sizeof(*p->iv), + cmd->cmd_gfp_mask); if (p->iv == NULL) { PRINT_ERROR("Unable to allocate iv (%d)", iv_count); goto out; @@ -5580,6 +5795,7 @@ static int vdev_read_dif_tags(struct vdisk_cmd_params *p) iv = vdisk_alloc_iv(cmd, p); if (iv == NULL) { unsigned long flags; + /* To protect sense setting against blockio data reads */ spin_lock_irqsave(&vdev_err_lock, flags); scst_set_busy(cmd); @@ -5628,6 +5844,7 @@ static int vdev_read_dif_tags(struct vdisk_cmd_params *p) &loff); if ((err < 0) || (err < full_len)) { unsigned long flags; + PRINT_ERROR("DIF readv() returned %lld from %zd " "(offs %lld, dev %s)", (long long)err, full_len, (long long)loff, cmd->dev->virt_name); @@ -5711,6 +5928,7 @@ static int vdev_write_dif_tags(struct vdisk_cmd_params *p) iv = vdisk_alloc_iv(cmd, p); if (iv == NULL) { unsigned long flags; + /* To protect sense setting against blockio data writes */ spin_lock_irqsave(&vdev_err_lock, flags); scst_set_busy(cmd); @@ -5781,9 +5999,10 @@ restart: * value less, than requested. Let's restart. */ int e = eiv_count; - TRACE_MGMT_DBG("DIF write() returned %lld from %zd " - "(iv_count=%d)", err, full_len, - eiv_count); + + TRACE_MGMT_DBG("DIF write() returned %d from %zd " + "(iv_count=%d)", (int)err, full_len, + eiv_count); if (err == 0) { PRINT_INFO("Suspicious: DIF write() returned 0 from " "%zd (iv_count=%d)", full_len, eiv_count); @@ -6115,9 +6334,10 @@ restart: * value less, than requested. Let's restart. */ int e = eiv_count; - TRACE_MGMT_DBG("write() returned %lld from %zd " - "(iv_count=%d)", err, full_len, - eiv_count); + + TRACE_MGMT_DBG("write() returned %d from %zd " + "(iv_count=%d)", (int)err, full_len, + eiv_count); if (err == 0) { PRINT_INFO("Suspicious: write() returned 0 from " "%zd (iv_count=%d)", full_len, eiv_count); @@ -6481,6 +6701,7 @@ static void blockio_exec_rw(struct vdisk_cmd_params *p, bool write, bool fua) #if 0 { static int err_inj_cntr; + if (++err_inj_cntr % 256 == 0) { PRINT_INFO("blockio_exec_rw() error injection"); scst_set_busy(cmd); @@ -6711,6 +6932,7 @@ static int vdisk_blockio_flush(struct block_device *bdev, gfp_t gfp_mask, #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 37) if (async) { struct bio *bio = bio_alloc(gfp_mask, 0); + if (bio == NULL) { res = -ENOMEM; goto out_rep; @@ -6921,21 +7143,6 @@ static ssize_t fileio_read_sync(struct file *fd, void *buf, size_t len, return ret; } -/* Note: Updates *@loff if writing succeeded. */ -static ssize_t fileio_write_sync(struct file *fd, void *buf, size_t len, - loff_t *loff) -{ - mm_segment_t old_fs; - ssize_t ret; - - old_fs = get_fs(); - set_fs(get_ds()); - ret = vfs_write(fd, (char __force __user *)buf, len, loff); - set_fs(old_fs); - - return ret; -} - /* Note: Updates *@loff if reading succeeded except for NULLIO devices. */ static ssize_t vdev_read_sync(struct scst_vdisk_dev *virt_dev, void *buf, size_t len, loff_t *loff) @@ -6957,27 +7164,6 @@ static ssize_t vdev_read_sync(struct scst_vdisk_dev *virt_dev, void *buf, } } -/* Note: Updates *@loff if reading succeeded except for NULLIO devices. */ -static ssize_t vdev_write_sync(struct scst_vdisk_dev *virt_dev, void *buf, - size_t len, loff_t *loff) -{ - ssize_t written, res; - - if (virt_dev->nullio) { - return len; - } else if (virt_dev->blockio) { - for (written = 0; written < len; written += res) { - res = blockio_rw_sync(virt_dev, buf + written, - len - written, loff, WRITE_SYNC); - if (res < 0) - return res; - } - return written; - } else { - return fileio_write_sync(virt_dev->fd, buf, len, loff); - } -} - static enum compl_status_e vdev_exec_verify(struct vdisk_cmd_params *p) { struct scst_cmd *cmd = p->cmd; @@ -7079,123 +7265,6 @@ out: return CMD_SUCCEEDED; } -/* COMPARE AND WRITE */ -static enum compl_status_e vdisk_exec_caw(struct vdisk_cmd_params *p) -{ - struct scst_cmd *cmd = p->cmd; - struct scst_device *dev = cmd->dev; - struct scst_vdisk_dev *virt_dev = dev->dh_priv; - uint32_t data_len = scst_cmd_get_data_len(cmd); - int length, i; - uint8_t *caw_buf = NULL, *read_buf = NULL; - loff_t loff, read, written; - - if (unlikely(cmd->cdb[1] & 0xE0)) { - TRACE_DBG("%s", "WRPROTECT not supported"); - scst_set_invalid_field_in_cdb(cmd, 1, - SCST_INVAL_FIELD_BIT_OFFS_VALID | 5); - goto out; - } - - /* - * A NUMBER OF LOGICAL BLOCKS field set to zero specifies that no read - * operations shall be performed, no logical block data shall be - * transferred from the Data-Out Buffer, no compare operations shall - * be performed, and no write operations shall be performed. This - * condition shall not be considered an error. - */ - if (data_len == 0) - goto out; - - if (virt_dev->caw_len_lim != NO_CAW_LEN_LIM && - (data_len > virt_dev->caw_len_lim << dev->block_shift)) { - PRINT_ERROR("COMPARE AND WRITE: data length %u exceeds" - " limit %u << %u = %u", data_len, - virt_dev->caw_len_lim, dev->block_shift, - virt_dev->caw_len_lim << dev->block_shift); - scst_set_invalid_field_in_cdb(cmd, 13, 0); - goto out; - } - - length = scst_get_buf_full(cmd, &caw_buf); - read_buf = vmalloc(data_len); - if (length < 0 || !read_buf) { - PRINT_ERROR("scst_get_buf_full() failed: %d", length); - if (length == -ENOMEM || !read_buf) - scst_set_busy(cmd); - else - scst_set_cmd_error(cmd, - SCST_LOAD_SENSE(scst_sense_internal_failure)); - goto out; - } - - if (length != 2 * data_len) { - scst_set_invalid_field_in_cdb(cmd, 13, 0); - goto out; - } - - mutex_lock(&virt_dev->caw_mutex); - - loff = p->loff; - read = vdev_read_sync(virt_dev, read_buf, data_len, &loff); - if (read < data_len) { - PRINT_ERROR("COMPARE AND WRITE / READ returned %lld from %d", - read, data_len); - if (read == -EAGAIN) - scst_set_busy(cmd); - else - scst_set_cmd_error(cmd, - SCST_LOAD_SENSE(scst_sense_read_error)); - goto unlock; - } - - if (memcmp(caw_buf, read_buf, data_len) != 0) { - for (i = 0; i < data_len && caw_buf[i] == read_buf[i]; i++) - ; - /* - * SBC-3 $5.2: if the compare operation does not indicate a - * match, then terminate the command with CHECK CONDITION - * status with the sense key set to MISCOMPARE and the - * additional sense code set to MISCOMPARE DURING VERIFY - * OPERATION. In the sense data (see 4.18 and SPC-4) the - * offset from the start of the Data-Out Buffer to the first - * byte of data that was not equal shall be reported in the - * INFORMATION field. - */ - scst_set_cmd_error_and_inf(cmd, - SCST_LOAD_SENSE(scst_sense_miscompare_error), i); - goto unlock; - } - - loff = p->loff; - written = vdev_write_sync(virt_dev, caw_buf + data_len, data_len, - &loff); - if (written < data_len) { - PRINT_ERROR("COMPARE AND WRITE / WRITE wrote %lld / %d", - written, data_len); - if (written == -EAGAIN) - scst_set_busy(cmd); - else - scst_set_cmd_error(cmd, - SCST_LOAD_SENSE(scst_sense_write_error)); - goto unlock; - } - if (p->fua) - vdisk_fsync(p->loff, scst_cmd_get_data_len(cmd), cmd->dev, - cmd->cmd_gfp_mask, cmd, false); - -unlock: - mutex_unlock(&virt_dev->caw_mutex); - -out: - if (read_buf) - vfree(read_buf); - if (caw_buf) - scst_put_buf_full(cmd, caw_buf); - - return CMD_SUCCEEDED; -} - static enum compl_status_e blockio_exec_write_verify(struct vdisk_cmd_params *p) { /* Not yet implemented */ @@ -7223,6 +7292,71 @@ static enum compl_status_e nullio_exec_verify(struct vdisk_cmd_params *p) return CMD_SUCCEEDED; } +static void blockio_on_alua_state_change_start(struct scst_device *dev, + enum scst_tg_state old_state, enum scst_tg_state new_state) +{ + struct scst_vdisk_dev *virt_dev = dev->dh_priv; + + TRACE_ENTRY(); + + /* + * As required for on_alua_state_change_* callbacks, + * no parallel fd activities could be here. + */ + + TRACE_MGMT_DBG("ALUA state change from %s to %s started, closing FD (dev %s)", + scst_alua_state_name(old_state), scst_alua_state_name(new_state), + dev->virt_name); + + /* Just in case always close */ + vdisk_close_fd(virt_dev); + + TRACE_EXIT(); + return; +} + +static void blockio_on_alua_state_change_finish(struct scst_device *dev, + enum scst_tg_state old_state, enum scst_tg_state new_state) +{ + struct scst_vdisk_dev *virt_dev = dev->dh_priv; + + TRACE_ENTRY(); + + /* + * As required for on_alua_state_change_* callbacks, + * no parallel fd activities could be here. + */ + + if ((new_state == SCST_TG_STATE_OPTIMIZED) || + (new_state == SCST_TG_STATE_NONOPTIMIZED)) { + /* Try non-optimized as well, it might be new redirection device */ + int rc; + + TRACE_MGMT_DBG("ALUA state change from %s to %s finished (dev %s), " + "reopenning FD", scst_alua_state_name(old_state), + scst_alua_state_name(new_state), dev->virt_name); + + rc = vdisk_open_fd(virt_dev, dev->dev_rd_only); + if (rc == 0) { + if (virt_dev->reexam_pending) { + rc = vdisk_reexamine(virt_dev); + WARN_ON(rc != 0); + virt_dev->reexam_pending = 0; + } + } else { + PRINT_ERROR("Unable to open fd on ALUA state change " + "to %s (dev %s)", dev->virt_name, + scst_alua_state_name(new_state)); + } + } else + TRACE_DBG("ALUA state change from %s to %s finished (dev %s)", + scst_alua_state_name(old_state), scst_alua_state_name(new_state), + dev->virt_name); + + TRACE_EXIT(); + return; +} + static void vdisk_task_mgmt_fn_done(struct scst_mgmt_cmd *mcmd, struct scst_tgt_dev *tgt_dev) { @@ -7257,6 +7391,7 @@ static void vdisk_task_mgmt_fn_done(struct scst_mgmt_cmd *mcmd, } else if (mcmd->fn == SCST_PR_ABORT_ALL) { struct scst_device *dev = tgt_dev->dev; struct scst_vdisk_dev *virt_dev = dev->dh_priv; + spin_lock(&virt_dev->flags_lock); virt_dev->prevent_allow_medium_removal = 0; spin_unlock(&virt_dev->flags_lock); @@ -7266,6 +7401,74 @@ static void vdisk_task_mgmt_fn_done(struct scst_mgmt_cmd *mcmd, return; } +#ifdef CONFIG_DEBUG_EXT_COPY_REMAP +static void vdev_ext_copy_remap(struct scst_cmd *cmd, + struct scst_ext_copy_seg_descr *seg) +{ + struct scst_ext_copy_data_descr *d; + static int shift; + static DEFINE_SPINLOCK(lock); + int s; + + TRACE_ENTRY(); + + if (seg->data_descr.data_len <= 4096) { + /* No way to split */ + goto out_done; + } + + d = kzalloc(sizeof(*d)*2, GFP_KERNEL); + if (d == NULL) + goto out_busy; + + spin_lock(&lock); + + shift += 4096; + + if (shift >= seg->data_descr.data_len) { + shift = 0; + s = 0; + } else + s = shift; + + TRACE_DBG("cmd %p, seg %p, data_len %d, shift %d, s %d", cmd, seg, + seg->data_descr.data_len, shift, s); + + spin_unlock(&lock); + + if (s == 0) + goto out_free_done; + + d[0].data_len = s; + d[0].src_lba = seg->data_descr.src_lba; + d[0].dst_lba = seg->data_descr.dst_lba; + + d[1].data_len = seg->data_descr.data_len - s; + d[1].src_lba = seg->data_descr.src_lba + (s >> seg->src_tgt_dev->dev->block_shift); + d[1].dst_lba = seg->data_descr.dst_lba + (s >> seg->dst_tgt_dev->dev->block_shift); + + scst_ext_copy_remap_done(cmd, d, 2); + +out: + TRACE_EXIT(); + return; + +out_busy: + scst_set_busy(cmd); + +out_free_done: + kfree(d); + +out_done: +#if 1 + scst_ext_copy_remap_done(cmd, &seg->data_descr, 1); +#else + scst_ext_copy_remap_done(cmd, NULL, 0); +#endif + goto out; +} +#endif + static void vdisk_report_registering(const struct scst_vdisk_dev *virt_dev) { enum { buf_size = 256 }; @@ -7355,6 +7558,11 @@ static int vdisk_resync_size(struct scst_vdisk_dev *virt_dev) sBUG_ON(virt_dev->nullio); sBUG_ON(!virt_dev->filename); + if (virt_dev->fd == NULL) { + res = -EMEDIUMTYPE; + goto out; + } + res = vdisk_get_file_size(virt_dev->filename, virt_dev->blockio, &file_size); if (res != 0) @@ -7373,6 +7581,8 @@ static int vdisk_resync_size(struct scst_vdisk_dev *virt_dev) virt_dev->file_size = file_size; virt_dev->nblocks = virt_dev->file_size >> virt_dev->dev->block_shift; + virt_dev->size_key = 0; + PRINT_INFO("New size of SCSI target virtual disk %s " "(fs=%lldMB, bs=%d, nblocks=%lld, cyln=%lld%s)", virt_dev->name, virt_dev->file_size >> 20, @@ -7458,7 +7668,6 @@ static int vdev_create(struct scst_dev_type *devt, } spin_lock_init(&virt_dev->flags_lock); - mutex_init(&virt_dev->caw_mutex); virt_dev->vdev_devt = devt; @@ -7469,7 +7678,7 @@ static int vdev_create(struct scst_dev_type *devt, virt_dev->rotational = DEF_ROTATIONAL; virt_dev->thin_provisioned = DEF_THIN_PROVISIONED; virt_dev->tst = DEF_TST; - virt_dev->caw_len_lim = DEF_CAW_LEN_LIM; + virt_dev->expl_alua = DEF_EXPL_ALUA; virt_dev->blk_shift = DEF_DISK_BLOCK_SHIFT; @@ -7678,11 +7887,7 @@ static int vdev_parse_add_dev_params(struct scst_vdisk_dev *virt_dev, continue; } -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtoull(pp, 0, &val); -#else - res = strict_strtoull(pp, 0, &val); -#endif if (res != 0) { PRINT_ERROR("strtoull() for %s failed: %d (device %s)", pp, res, virt_dev->name); @@ -8223,6 +8428,7 @@ static int vcdrom_change(struct scst_vdisk_dev *virt_dev, if (!virt_dev->cdrom_empty) { char *fn = kstrdup(filename, GFP_KERNEL); + if (fn == NULL) { PRINT_ERROR("%s", "Allocation of filename failed"); res = -ENOMEM; @@ -8235,7 +8441,7 @@ static int vcdrom_change(struct scst_vdisk_dev *virt_dev, virt_dev->blockio, &err); if (res != 0) goto out_free_fn; - if (virt_dev->tgt_dev_cnt > 0) { + if (virt_dev->fd == NULL) { res = vdisk_open_fd(virt_dev, true); if (res != 0) goto out_free_fn; @@ -8390,6 +8596,7 @@ static int vdev_size_process_store(struct scst_sysfs_work_item *work) struct scst_vdisk_dev *virt_dev; unsigned long long new_size; int size_shift, res = -EINVAL; + bool queue_ua; if (sscanf(work->buf, "%d %lld", &size_shift, &new_size) != 2 || new_size > (ULLONG_MAX >> size_shift)) @@ -8407,19 +8614,20 @@ static int vdev_size_process_store(struct scst_sysfs_work_item *work) goto resume; virt_dev = dev->dh_priv; - if (!virt_dev->nullio) { - res = -EPERM; - sBUG(); - } else if ((new_size & ((1 << virt_dev->blk_shift) - 1)) == 0) { + + queue_ua = (virt_dev->fd != NULL); + + if ((new_size & ((1 << virt_dev->blk_shift) - 1)) == 0) { virt_dev->file_size = new_size; virt_dev->nblocks = virt_dev->file_size >> dev->block_shift; + virt_dev->size_key = 1; } else { res = -EINVAL; } mutex_unlock(&scst_mutex); - if (res == 0) + if ((res == 0) || queue_ua) scst_capacity_data_changed(dev); resume: @@ -8440,7 +8648,6 @@ static ssize_t vdev_size_store(struct kobject *kobj, char *new_size; int res = -ENOMEM; - new_size = kasprintf(GFP_KERNEL, "%d %.*s", size_shift, (int)count, buf); if (!new_size) @@ -8480,14 +8687,19 @@ static ssize_t vdev_size_show(struct kobject *kobj, struct kobj_attribute *attr, struct scst_device *dev; struct scst_vdisk_dev *virt_dev; unsigned long long size; + bool key; dev = container_of(kobj, struct scst_device, dev_kobj); virt_dev = dev->dh_priv; size = ACCESS_ONCE(virt_dev->file_size); + if (virt_dev->nullio && size == VDISK_NULLIO_SIZE) + key = false; + else + key = virt_dev->size_key; + return sprintf(buf, "%llu\n%s", size >> size_shift, - virt_dev->nullio && size != VDISK_NULLIO_SIZE ? - SCST_SYSFS_KEY_MARK "\n" : ""); + key ? SCST_SYSFS_KEY_MARK "\n" : ""); } static ssize_t vdev_sysfs_size_show(struct kobject *kobj, @@ -8588,6 +8800,56 @@ static ssize_t vdisk_sysfs_tp_show(struct kobject *kobj, return pos; } +static ssize_t vdisk_sysfs_expl_alua_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + struct scst_device *dev; + struct scst_vdisk_dev *virt_dev; + int pos; + + TRACE_ENTRY(); + + dev = container_of(kobj, struct scst_device, dev_kobj); + virt_dev = dev->dh_priv; + pos = sprintf(buf, "%d\n%s", virt_dev->expl_alua, + virt_dev->expl_alua != DEF_EXPL_ALUA ? + SCST_SYSFS_KEY_MARK "\n" : ""); + + TRACE_EXIT_RES(pos); + return pos; +} + +static ssize_t vdisk_sysfs_expl_alua_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct scst_device *dev; + struct scst_vdisk_dev *virt_dev; + char ch[16]; + unsigned long expl_alua; + int res; + + TRACE_ENTRY(); + + dev = container_of(kobj, struct scst_device, dev_kobj); + virt_dev = dev->dh_priv; + sprintf(ch, "%.*s", min_t(int, sizeof(ch) - 1, count), buf); + res = kstrtoul(ch, 0, &expl_alua); + if (res < 0) + goto out; + + spin_lock(&virt_dev->flags_lock); + virt_dev->expl_alua = !!expl_alua; + spin_unlock(&virt_dev->flags_lock); + + res = count; + +out: + TRACE_EXIT_RES(res); + return res; +} + static ssize_t vdisk_sysfs_nv_cache_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { @@ -8663,11 +8925,7 @@ static ssize_t vdev_sysfs_rz_store(struct kobject *kobj, char ch[16]; sprintf(ch, "%.*s", min_t(int, sizeof(ch) - 1, count), buf); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtol(ch, 0, &read_zero); -#else - res = strict_strtol(ch, 0, &read_zero); -#endif if (res) goto out; res = -EINVAL; @@ -8881,11 +9139,7 @@ static int vdev_sysfs_process_cluster_mode_store( * have been dropped before invoking .detach(). */ virt_dev = dev->dh_priv; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtol(work->buf, 0, &clm); -#else - res = strict_strtol(work->buf, 0, &clm); -#endif if (res) goto unlock; res = -EINVAL; @@ -10388,6 +10642,7 @@ static void exit_scst_vdisk(struct scst_dev_type *devtype) static void init_ops(vdisk_op_fn *ops, int count) { int i; + for (i = 0; i < count; i++) if (ops[i] == NULL) ops[i] = vdisk_invalid_opcode; diff --git a/scst/src/scst_copy_mgr.c b/scst/src/scst_copy_mgr.c new file mode 100644 index 000000000..8b0ffb2f3 --- /dev/null +++ b/scst/src/scst_copy_mgr.c @@ -0,0 +1,3750 @@ +/* + * Copyright (C) 2013 - 2015 SanDisk Corporation + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "scst.h" +#include "scst_priv.h" +#include "scst_pres.h" + +#ifdef CONFIG_SCST_PROC +#warning In the PROCFS build EXTENDED COPY not supported +#else + +#define SCST_CM_NAME "copy_manager" +#define SCST_CM_TGT_NAME (SCST_CM_NAME "_tgt") +#define SCST_CM_SESS_NAME (SCST_CM_NAME "_sess") + +#define SCST_CM_TID_SIZE 24 +#define SCST_CM_TID_ID "COPY_MGR" + +#define SCST_CM_RETRIES_WAIT HZ +#define SCST_CM_MAX_RETRIES_TIME (30*HZ) +#define SCST_CM_ID_KEEP_TIME (5*HZ) + +#define SCST_CM_MAX_EACH_IO_SIZE (512*1024) + +/* Too big value is not too good for the blocking machinery */ +#define SCST_CM_MAX_TGT_DESCR_CNT 5 + +#define SCST_CM_MAX_SEG_DESCR_CNT \ + (((PAGE_SIZE * 2) - sizeof(struct scst_cm_ec_cmd_priv)) / \ + sizeof(struct scst_ext_copy_seg_descr)) + +/* MAXIMUM DESCRIPTOR LIST LENGTH */ +#define SCST_MAX_SEG_DESC_LEN 0xFFFF + +static struct scst_tgt *scst_cm_tgt; +static struct scst_session *scst_cm_sess; + +/* Protected by scst_mutex */ +static unsigned int scst_cm_next_lun; + +static DEFINE_MUTEX(scst_cm_mutex); + +/* Protected by scst_cm_mutex */ +static LIST_HEAD(scst_cm_desig_list); + +struct scst_cm_desig { + struct list_head cm_desig_list_entry; + + struct scst_tgt_dev *desig_tgt_dev; + + int desig_len; + uint8_t desig[]; +}; + +/* It's IRQ and inner for sess_list_lock */ +static spinlock_t scst_cm_lock; + +/* Necessary fields protected by scst_cm_lock */ +struct scst_cm_list_id { + struct list_head sess_cm_list_id_entry; + + int cm_lid; + +#define SCST_CM_LIST_ID_STATE_ACTIVE 0 +#define SCST_CM_LIST_ID_STATE_DONE 1 +#define SCST_CM_LIST_ID_STATE_PENDING_FREE 2 + int cm_list_id_state; + + unsigned int cm_done:1; + unsigned int cm_can_be_immed_free:1; + + int cm_segs_processed; + int64_t cm_written_size; /* in bytes */ + + unsigned long cm_time_to_free; /* in jiffies */ + + int cm_status; + unsigned short cm_sense_len; + uint8_t cm_sense[SCST_SENSE_BUFFERSIZE]; +}; + +struct scst_cm_internal_cmd_priv { + /* Must be the first for scst_finish_internal_cmd()! */ + scst_i_finish_fn_t cm_finish_fn; + + /* Internal cmd itself. Needed for unified cleanups and aborts. */ + struct scst_cmd *cm_cmd; + + /* E.g. rcmd for WRITEs, ec_cmd for READs, etc. */ + struct scst_cmd *cm_orig_cmd; + + struct list_head cm_internal_cmd_list_entry; +}; + +struct scst_cm_dev_entry { + struct list_head cm_sorted_devs_list_entry; + struct scst_cmd *cm_fcmd; /* can point to the real EC cmd as well! */ +}; + +/* + * Most fields here not protected, since can be accessed only from a single + * thread at time. + */ +struct scst_cm_ec_cmd_priv { + struct scst_cm_list_id *cm_list_id; + + /* + * List of all devices involved in this EC cmd (structs + * scst_cm_dev_entry), sorted by their dev's address. + */ + struct list_head cm_sorted_devs_list; + + /* + * List of all generated internal commands. Where needed, protected + * by scst_cm_lock. + */ + struct list_head cm_internal_cmd_list; + +#define SCST_CM_ERROR_NONE 0 +#define SCST_CM_ERROR_READ 1 +#define SCST_CM_ERROR_WRITE 2 + int cm_error; + + struct mutex cm_mutex; + + int cm_cur_in_flight; /* commands */ + + /** + ** READ commands stuff + **/ + struct scst_tgt_dev *cm_read_tgt_dev; + int64_t cm_start_read_lba; /* in blocks */ + int64_t cm_cur_read_lba; /* in blocks */ + int cm_left_to_read; /* in blocks */ + int cm_max_each_read;/* in blocks */ + + /** + ** WRITE commands stuff + **/ + struct scst_tgt_dev *cm_write_tgt_dev; + int64_t cm_start_write_lba; /* in blocks */ + int64_t cm_written; /* in bytes */ + + /** + ** Current data descriptors and their count + **/ + const struct scst_ext_copy_data_descr *cm_data_descrs; + int cm_data_descrs_cnt; + int cm_cur_data_descr; + + int cm_cur_seg_descr; + + /** + ** Parsed descriptors. Number of them is in + ** cmd->cmd_data_descriptors_cnt + **/ + struct scst_ext_copy_seg_descr cm_seg_descrs[]; +}; + +#define SCST_ALLOW_NOT_CONN_COPY_DEF 0 +/* Not protected, because no need */ +static bool scst_cm_allow_not_connected_copy = SCST_ALLOW_NOT_CONN_COPY_DEF; + +#define SCST_CM_STATUS_CMD_SUCCEEDED 0 +#define SCST_CM_STATUS_RETRY 1 +#define SCST_CM_STATUS_CMD_FAILED -1 + +typedef void (*scst_cm_retry_fn_t)(struct scst_cmd *cmd); + +struct scst_cm_retry { + struct scst_cmd *cm_retry_cmd; + struct delayed_work cm_retry_work; + scst_cm_retry_fn_t cm_retry_fn; +}; + +static void scst_cm_retry_work_fn(struct delayed_work *work) +{ + struct scst_cm_retry *retry = container_of(work, struct scst_cm_retry, + cm_retry_work); + + TRACE_ENTRY(); + + TRACE_DBG("Retrying cmd %p", retry->cm_retry_cmd); + + retry->cm_retry_fn(retry->cm_retry_cmd); + + __scst_cmd_put(retry->cm_retry_cmd); + + kfree(retry); + + TRACE_EXIT(); + return; +} + +/* + * Checks if cmd finished successfully and performs/schedules retry, if necessary. + * Returns one of SCST_CM_STATUS_* codes. + */ +static int scst_cm_err_check_retry(struct scst_cmd *cmd, + unsigned long start_time, scst_cm_retry_fn_t retry_fn) +{ + int res = SCST_CM_STATUS_CMD_SUCCEEDED; + unsigned long cur_time, max_retry_time, next_retry_time; + struct scst_cm_retry *retry; + bool imm_retry = false; + + TRACE_ENTRY(); + + /* cmd->dev and tgt_dev can be NULL here! */ + + TRACE_DBG("cmd %p, status %d, aborted %d", cmd, cmd->status, + scst_cmd_aborted(cmd)); + + if (likely((cmd->status == 0) && !scst_cmd_aborted(cmd))) + goto out; + + cur_time = jiffies; + max_retry_time = start_time + SCST_CM_MAX_RETRIES_TIME; + + mutex_lock(&scst_cm_mutex); + + if (test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags)) { + if (test_bit(SCST_CMD_ABORTED_OTHER, &cmd->cmd_flags)) { + TRACE_MGMT_DBG("Cmd %p aborted by other initiator, " + "retry possible", cmd); + goto try_retry; + } else { + TRACE_MGMT_DBG("Cmd %p aborted, no retry ", cmd); + goto out_err_unlock; + } + } + + if ((cmd->status == SAM_STAT_BUSY) || + (cmd->status == SAM_STAT_TASK_SET_FULL) || + (cmd->status == SAM_STAT_RESERVATION_CONFLICT) || + (cmd->status == SAM_STAT_ACA_ACTIVE)) { + TRACE_DBG("Cmd %p finished with status %d, retry possible", + cmd, cmd->status); + goto try_retry; + } + + if ((cmd->status == SAM_STAT_CHECK_CONDITION) && + scst_sense_valid(cmd->sense) && + scst_analyze_sense(cmd->sense, cmd->sense_valid_len, + SCST_SENSE_KEY_VALID, UNIT_ATTENTION, 0, 0)) { + TRACE_DBG("Cmd %p finished with UA, immediate retry " + "possible", cmd); + imm_retry = true; + goto try_retry; + } + +out_err_unlock: + mutex_unlock(&scst_cm_mutex); + +out_failed: + res = SCST_CM_STATUS_CMD_FAILED; + +out: + TRACE_EXIT_RES(res); + return res; + +try_retry: + if (time_after_eq(cur_time, max_retry_time)) + goto out_err_unlock; + + next_retry_time = cur_time + SCST_CM_RETRIES_WAIT; + + TRACE_DBG("Retrying cmd %p (imm_retry %d, next_retry_time %ld, " + "cur_time %ld, " "start_time %ld, max_retry_time %ld): going " + "to sleep", cmd, imm_retry, next_retry_time, cur_time, + start_time, max_retry_time); + + mutex_unlock(&scst_cm_mutex); + + if (retry_fn == NULL) + goto out_retry_done; + + /* Wait before retry */ + + retry = kzalloc(sizeof(*retry), GFP_KERNEL); + if (retry == NULL) { + PRINT_ERROR("Unable to allocate retry struct"); + scst_set_busy(cmd); + goto out_failed; + } + retry->cm_retry_cmd = cmd; + __scst_cmd_get(cmd); + INIT_DELAYED_WORK(&retry->cm_retry_work, + (void (*)(struct work_struct *))scst_cm_retry_work_fn); + retry->cm_retry_fn = retry_fn; + + if (imm_retry) { + /* Let's use work to avoid possible recursion */ + TRACE_DBG("Immediate retry (cmd %p)", cmd); + schedule_work(&retry->cm_retry_work.work); + } else { + TRACE_DBG("Scheduling cmd %p retry", cmd); + schedule_delayed_work(&retry->cm_retry_work, + next_retry_time - cur_time); + } + +out_retry_done: + res = SCST_CM_STATUS_RETRY; + goto out; +} + +static bool scst_cm_is_ec_cmd_done(struct scst_cmd *ec_cmd) +{ + bool res; + + TRACE_ENTRY(); + + if (unlikely(test_bit(SCST_CMD_ABORTED, &ec_cmd->cmd_flags))) { + TRACE_MGMT_DBG("EC cmd %p aborted", ec_cmd); + res = true; + } else if (unlikely(ec_cmd->completed)) { + TRACE_MGMT_DBG("EC cmd %p already completed with status (%d)", + ec_cmd, ec_cmd->status); + res = true; + } else + res = false; + + TRACE_EXIT_RES(res); + return res; +} + +/* + * cm_mutex suppose to be locked or no activities on this ec_cmd's priv. + * + * Returns 0 on success or -ENOENT, if there's no more data descriptors in + * ec_cmd, or other negative error code. For other error codes cmd status + * and sense supposed to be set. + */ +static int scst_cm_setup_this_data_descr(struct scst_cmd *ec_cmd) +{ + int res; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + struct scst_ext_copy_seg_descr *sd; + const struct scst_ext_copy_data_descr *dd; + + TRACE_ENTRY(); + + TRACE_DBG("ec_cmd %p, cm_cur_data_descr %d", ec_cmd, + priv->cm_cur_data_descr); + + EXTRACHECKS_BUG_ON(priv->cm_cur_data_descr > priv->cm_data_descrs_cnt); + + if (priv->cm_cur_data_descr == priv->cm_data_descrs_cnt) { + TRACE_DBG("No more data desriptors for ec_cmd %p", ec_cmd); + res = -ENOENT; + goto out; + } + + sd = &priv->cm_seg_descrs[priv->cm_cur_seg_descr]; + dd = &priv->cm_data_descrs[priv->cm_cur_data_descr]; + + EXTRACHECKS_BUG_ON(dd->data_len == 0); + + priv->cm_read_tgt_dev = sd->src_tgt_dev; + priv->cm_start_read_lba = dd->src_lba; + priv->cm_cur_read_lba = dd->src_lba; + priv->cm_left_to_read = dd->data_len >> sd->src_tgt_dev->dev->block_shift; + priv->cm_max_each_read = SCST_CM_MAX_EACH_IO_SIZE >> sd->src_tgt_dev->dev->block_shift; + + priv->cm_write_tgt_dev = sd->dst_tgt_dev; + priv->cm_start_write_lba = dd->dst_lba; + + TRACE_DBG("len %d, src_lba %lld, dst_lba %lld", dd->data_len, + (long long)dd->src_lba, (long long)dd->dst_lba); + + if (unlikely((dd->data_len & (sd->src_tgt_dev->dev->block_size-1)) != 0) || + unlikely((dd->data_len & (sd->dst_tgt_dev->dev->block_size-1)) != 0)) { + PRINT_ERROR("Data len %d is not even for block size (src block " + "size %d, dst block size %d)", dd->data_len, + sd->src_tgt_dev->dev->block_size, + sd->dst_tgt_dev->dev->block_size); + scst_set_cmd_error(ec_cmd, SCST_LOAD_SENSE(scst_sense_hardw_error)); + res = -EINVAL; + goto out; + } + + res = 0; + +out: + TRACE_EXIT_RES(res); + return res; +} + +/* + * cm_mutex suppose to be locked or no activities on this ec_cmd's priv. + * + * Returns 0 on success or -ENOENT, if there's no more data descriptors in + * ec_cmd, or other negative error code. For other error codes cmd status + * and sense supposed to be set. + */ +static int scst_cm_setup_next_data_descr(struct scst_cmd *ec_cmd) +{ + int res; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + + TRACE_ENTRY(); + + TRACE_DBG("ec_cmd %p", ec_cmd); + + priv->cm_cur_data_descr++; + + res = scst_cm_setup_this_data_descr(ec_cmd); + + TRACE_EXIT_RES(res); + return res; +} + +/* + * cm_mutex suppose to be locked or no activities on this ec_cmd's priv. + * + * Returns 0 on success or -ENOENT, if there are no data descriptors in + * ec_cmd, or other negative error code. For other error codes cmd status + * and sense supposed to be set. + */ +static int scst_cm_setup_first_data_descr(struct scst_cmd *ec_cmd) +{ + int res; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + + TRACE_ENTRY(); + + TRACE_DBG("ec_cmd %p", ec_cmd); + + priv->cm_cur_data_descr = 0; + + res = scst_cm_setup_this_data_descr(ec_cmd); + + TRACE_EXIT_RES(res); + return res; +} + +static void scst_cm_destroy_data_descrs(struct scst_cmd *ec_cmd) +{ + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + + TRACE_ENTRY(); + + TRACE_DBG("ec_cmd %p, data_descrs %p, data_descrs_cnt %d ", ec_cmd, + priv->cm_data_descrs, priv->cm_data_descrs_cnt); + + if (priv->cm_data_descrs != &priv->cm_seg_descrs[priv->cm_cur_seg_descr].data_descr) { + TRACE_DBG_FLAG(TRACE_DEBUG|TRACE_MEMORY, "Freeing " + "data_descrs %p", priv->cm_data_descrs); + kfree(priv->cm_data_descrs); + } + + priv->cm_data_descrs = NULL; + priv->cm_data_descrs_cnt = 0; + + TRACE_EXIT(); + return; +} + +/* + * cm_mutex suppose to be locked or no activities on this ec_cmd's priv. + * + * Returns 0 on success or -ENOENT, if there are no data descriptors in + * ec_cmd, or other negative error code. For other error codes cmd status + * and sense supposed to be set. + */ +static int scst_cm_setup_data_descrs(struct scst_cmd *ec_cmd, + const struct scst_ext_copy_data_descr *dds, int dds_cnt) +{ + int res; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + + TRACE_ENTRY(); + + TRACE_DBG("ec_cmd %p, dds %p, dds_cnt %d", ec_cmd, dds, dds_cnt); + + EXTRACHECKS_BUG_ON(dds_cnt == 0); + + priv->cm_data_descrs = dds; + priv->cm_data_descrs_cnt = dds_cnt; + + res = scst_cm_setup_first_data_descr(ec_cmd); + if (unlikely(res != 0)) + goto out_destr; + +out: + TRACE_EXIT_RES(res); + return res; + +out_destr: + scst_cm_destroy_data_descrs(ec_cmd); + goto out; +} + +/* + * cm_mutex suppose to be locked or no activities on this ec_cmd's priv. + * + * Returns 0 on success or -ENOENT, if there's no more seg descriptors in + * ec_cmd, or other negative error code. For other error codes cmd status + * and sense supposed to be set. + */ +static int scst_cm_setup_seg_descr(struct scst_cmd *ec_cmd) +{ + int res; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + struct scst_ext_copy_seg_descr *sd; + + TRACE_ENTRY(); + + TRACE_DBG("ec_cmd %p, cm_cur_seg_descr %d", ec_cmd, + priv->cm_cur_seg_descr); + + EXTRACHECKS_BUG_ON(priv->cm_cur_seg_descr > ec_cmd->cmd_data_descriptors_cnt); + + while (1) { + if (priv->cm_cur_seg_descr == ec_cmd->cmd_data_descriptors_cnt) + goto out_enoent; + + sd = &priv->cm_seg_descrs[priv->cm_cur_seg_descr]; + EXTRACHECKS_BUG_ON(sd->type != SCST_EXT_COPY_SEG_DATA); + if (sd->data_descr.data_len != 0) + break; + + priv->cm_cur_seg_descr++; + TRACE_DBG("ec_cmd %p, cm_cur_seg_descr %d", ec_cmd, + priv->cm_cur_seg_descr); + } + + if (priv->cm_list_id != NULL) { + /* SCSI: including the being processed one */ + priv->cm_list_id->cm_segs_processed = priv->cm_cur_seg_descr + 1; + } + + res = 0; + +out: + TRACE_EXIT_RES(res); + return res; + +out_enoent: + TRACE_DBG("ec_cmd %p finished", ec_cmd); + res = -ENOENT; + goto out; +} + +/* cm_mutex suppose to be locked or no activities on this ec_cmd's priv */ +static void scst_cm_advance_seg_descr(struct scst_cmd *ec_cmd) +{ + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + + TRACE_ENTRY(); + + TRACE_DBG("ec_cmd %p", ec_cmd); + + EXTRACHECKS_BUG_ON(priv->cm_cur_in_flight != 0); + + scst_cm_destroy_data_descrs(ec_cmd); + + priv->cm_cur_seg_descr++; + + TRACE_EXIT(); + return; +} + +static void scst_cm_prepare_final_sense(struct scst_cmd *ec_cmd) +{ + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + uint8_t *fsense = NULL; + int d_sense = scst_get_cmd_dev_d_sense(ec_cmd); + bool copy_sense = false; + int sense_to_copy = ec_cmd->sense_valid_len; + int sense_len = ec_cmd->sense_valid_len; + + TRACE_ENTRY(); + + if (likely(priv->cm_error == SCST_CM_ERROR_NONE)) + goto out; + + TRACE_DBG("ec_cmd %p, cm_error %d, sense_to_copy %d", ec_cmd, + priv->cm_error, sense_to_copy); + + if (sense_to_copy > (SCST_SENSE_BUFFERSIZE - 18)) { + PRINT_WARNING("Too small sense buffer, %d bytes will be " + "truncated (ec_cmd %p)", + sense_to_copy - (SCST_SENSE_BUFFERSIZE-18), ec_cmd); + sense_to_copy = SCST_SENSE_BUFFERSIZE - 18; + } + + if ((priv->cm_error == SCST_CM_ERROR_WRITE) && + (ec_cmd->status != SAM_STAT_CHECK_CONDITION)) { + int rc; + struct scst_ext_copy_seg_descr *sd = &priv->cm_seg_descrs[priv->cm_cur_seg_descr]; + + /* THIRD PARTY DEVICE FAILURE */ + + rc = scst_alloc_sense(ec_cmd, 0); + if (rc != 0) + goto out; + + TRACE_DBG("d_sense %d, cm_cur_seg_descr %d, cur_data_descr %d, " + "tgt_descr_offs %d", d_sense, priv->cm_cur_seg_descr, + priv->cm_cur_data_descr, sd->tgt_descr_offs); + + if (d_sense) { + /* Descriptor format */ + ec_cmd->sense[0] = 0x72; + ec_cmd->sense[1] = COPY_ABORTED; + ec_cmd->sense[2] = 0xD; /* ASC */ + ec_cmd->sense[3] = 1; /* ASCQ */ + ec_cmd->sense[7] = 20; /* additional Sense Length */ + + ec_cmd->sense[8] = 1; /* Command specific descriptor */ + ec_cmd->sense[9] = 0xA; + put_unaligned_be16(priv->cm_cur_seg_descr, &ec_cmd->sense[14]); + + ec_cmd->sense[20] = 2; /* Sense key specific descriptor */ + ec_cmd->sense[21] = 6; + ec_cmd->sense[24] = 0x80; + put_unaligned_be16(sd->tgt_descr_offs, &ec_cmd->sense[25]); + + ec_cmd->sense_valid_len = 16; + } else { + /* Fixed format */ + ec_cmd->sense[0] = 0x70; + ec_cmd->sense[2] = COPY_ABORTED; + ec_cmd->sense[7] = 0x0a; /* additional Sense Length */ + ec_cmd->sense[12] = 0xD; /* ASC */ + ec_cmd->sense[13] = 1; /* ASCQ */ + + put_unaligned_be16(priv->cm_cur_seg_descr, &ec_cmd->sense[10]); + + ec_cmd->sense[15] = 0x80; + put_unaligned_be16(sd->tgt_descr_offs, &ec_cmd->sense[16]); + + ec_cmd->sense_valid_len = 18; + } + + ec_cmd->status = SAM_STAT_CHECK_CONDITION; + goto out; + } + + fsense = mempool_alloc(scst_sense_mempool, GFP_KERNEL); + if (fsense == NULL) { + PRINT_ERROR("Allocation of the intermediate Extended Copy " + "sense buffer failed. Reported sense data can be " + "incorrect (ec_cmd %p)", ec_cmd); + goto out; + } + memset(fsense, 0, SCST_SENSE_BUFFERSIZE); + + if (d_sense) { + /* Descriptor format */ + fsense[0] = 0x72; + fsense[1] = COPY_ABORTED; + fsense[7] = 12; /* additional Sense Length */ + + fsense[8] = 1; /* Command specific descriptor */ + fsense[9] = 0xA; + put_unaligned_be16(priv->cm_cur_seg_descr, &fsense[14]); + + sense_len = 20; + } else { + /* Fixed format */ + fsense[0] = 0x70; + fsense[2] = COPY_ABORTED; + fsense[7] = 0x0a; /* additional Sense Length */ + + put_unaligned_be16(priv->cm_cur_seg_descr, &fsense[10]); + + if (priv->cm_error == SCST_CM_ERROR_READ) { + fsense[8] = 18; + copy_sense = scst_sense_valid(ec_cmd->sense); + } else if (priv->cm_error == SCST_CM_ERROR_WRITE) { + fsense[9] = 18; + copy_sense = scst_sense_valid(ec_cmd->sense); + } else + sBUG(); + + if (copy_sense) { + TRACE_DBG("Copying %db of old sense", sense_to_copy); + fsense[7] += 1 + sense_to_copy; + fsense[17] = ec_cmd->status; + memcpy(&fsense[18], ec_cmd->sense, sense_to_copy); + } + + sense_len = fsense[7] + 8; + TRACE_DBG("New sense len %d", sense_len); + } + + ec_cmd->status = SAM_STAT_CHECK_CONDITION; + if (ec_cmd->sense != NULL) { + memcpy(ec_cmd->sense, fsense, sense_len); + ec_cmd->sense_valid_len = sense_len; + } else + scst_alloc_set_sense(ec_cmd, 0, fsense, sense_len); + + mempool_free(fsense, scst_sense_mempool); + +out: + TRACE_EXIT(); + return; +} + +static void scst_cm_store_list_id_details(struct scst_cmd *ec_cmd) +{ + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + struct scst_cm_list_id *l = priv->cm_list_id; + + TRACE_ENTRY(); + + if (l != NULL) { + TRACE_DBG("List id %p done (status %d, sense valid %d, sense " + "len %d)", l, ec_cmd->status, scst_sense_valid(ec_cmd->sense), + ec_cmd->sense_valid_len); + spin_lock_irq(&scst_cm_lock); + l->cm_list_id_state = SCST_CM_LIST_ID_STATE_DONE; + if (ec_cmd->status != 0) { + l->cm_status = ec_cmd->status; + if (scst_sense_valid(ec_cmd->sense)) { + int len = ec_cmd->sense_valid_len; + if (len > sizeof(l->cm_sense)) { + PRINT_WARNING("EC command's sense is " + "too big (%d) with max allowed " + "%d, truncating", len, + (int)sizeof(l->cm_sense)); + len = sizeof(l->cm_sense); + } + l->cm_sense_len = ec_cmd->sense_valid_len; + memcpy(l->cm_sense, ec_cmd->sense, len); + } + } + spin_unlock_irq(&scst_cm_lock); + } + + TRACE_EXIT(); + return; +} + +static void scst_cm_ec_cmd_done(struct scst_cmd *ec_cmd) +{ +#ifdef CONFIG_SCST_EXTRACHECKS + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; +#endif + + TRACE_ENTRY(); + + TRACE_DBG("ec_cmd %p finished with status %d", ec_cmd, ec_cmd->status); + + EXTRACHECKS_BUG_ON(priv->cm_cur_in_flight != 0); + EXTRACHECKS_BUG_ON(priv->cm_data_descrs != NULL); + + scst_cm_prepare_final_sense(ec_cmd); + scst_cm_store_list_id_details(ec_cmd); + + ec_cmd->completed = 1; /* for success */ + ec_cmd->scst_cmd_done(ec_cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_THREAD); + + TRACE_EXIT(); + return; +} + +static void scst_cm_ec_sched_next_seg(struct scst_cmd *ec_cmd) +{ + TRACE_ENTRY(); + + /* No lock is needed, because it's all supposed to be calm */ + + scst_cm_advance_seg_descr(ec_cmd); + + /* + * There's recursion possible here, if dev handlers call after + * remapping scst_ext_copy_remap_done() from the same thread context. + */ + scst_cm_ext_copy_exec(ec_cmd); + + TRACE_EXIT(); + return; +} + +static void scst_cm_in_flight_cmd_finished(struct scst_cmd *ec_cmd) +{ + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + int f; + + TRACE_ENTRY(); + + mutex_lock(&priv->cm_mutex); + + priv->cm_cur_in_flight--; + f = priv->cm_cur_in_flight; + + mutex_unlock(&priv->cm_mutex); + + TRACE_DBG("ec_cmd %p, priv->cm_cur_in_flight %d", ec_cmd, f); + + if (f > 0) + goto out; + + if (priv->cm_list_id != NULL) + priv->cm_list_id->cm_written_size += priv->cm_written; + + scst_cm_ec_sched_next_seg(ec_cmd); + +out: + TRACE_EXIT(); + return; +} + +static int scst_cm_add_to_internal_cmd_list(struct scst_cmd *cmd, + struct scst_cmd *ec_cmd, struct scst_cmd *orig_cmd, + scst_i_finish_fn_t finish_fn) +{ + int res; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + struct scst_cm_internal_cmd_priv *p; + + TRACE_ENTRY(); + + EXTRACHECKS_BUG_ON(ec_cmd == cmd); + + p = kzalloc(sizeof(*p), GFP_KERNEL); + if (p == NULL) { + PRINT_ERROR("Unable to alloc scst_cm_internal_cmd_priv " + "(size %d)", (int)sizeof(*p)); + goto out_enomem; + } + + p->cm_finish_fn = finish_fn; + p->cm_orig_cmd = orig_cmd; + p->cm_cmd = cmd; + + TRACE_DBG("Adding internal cmd %p (priv %p, ec_cmd %p, orig_cmd %p)", + cmd, p, ec_cmd, orig_cmd); + spin_lock_irq(&scst_cm_lock); + list_add_tail(&p->cm_internal_cmd_list_entry, &priv->cm_internal_cmd_list); + spin_unlock_irq(&scst_cm_lock); + + cmd->tgt_i_priv = p; + + res = 0; + +out: + TRACE_EXIT_RES(res); + return res; + +out_enomem: + scst_set_busy(ec_cmd); + res = -ENOMEM; + goto out; +} + +static void scst_cm_del_free_from_internal_cmd_list(struct scst_cmd *cmd, + bool unblock_dev) +{ + struct scst_cm_internal_cmd_priv *p = cmd->tgt_i_priv; + + TRACE_ENTRY(); + + TRACE_DBG("Deleting/freeing internal cmd %p (op %s, priv %p, " + "orig_cmd %p)", cmd, scst_get_opcode_name(cmd), p, + p->cm_orig_cmd); + + spin_lock_irq(&scst_cm_lock); + list_del(&p->cm_internal_cmd_list_entry); + spin_unlock_irq(&scst_cm_lock); + + if (unblock_dev) { + TRACE_DBG("dev %p (internal cmd %p)", cmd->dev, cmd); + scst_check_unblock_dev(cmd); + } + + cmd->tgt_i_priv = NULL; + + kfree(p); + + TRACE_EXIT(); + return; +} + +static void scst_cm_read_cmd_finished(struct scst_cmd *rcmd); + +/* cm_mutex suppose to be locked */ +static int __scst_cm_push_single_read(struct scst_cmd *ec_cmd, + int64_t lba, int blocks) +{ + int res; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + uint8_t read_cdb[32]; + struct scst_device *rdev = priv->cm_read_tgt_dev->dev; + int block_shift = rdev->block_shift; + int len = blocks << block_shift; + struct scst_cmd *rcmd; + int cdb_len; + bool check_dif = (rdev->dev_dif_mode & SCST_DIF_MODE_DEV); + + TRACE_ENTRY(); + + if (unlikely(scst_cm_is_ec_cmd_done(ec_cmd))) { + TRACE_MGMT_DBG("EC cmd %p done: aborting further read " + "commands", ec_cmd); + priv->cm_left_to_read = 0; + res = -EPIPE; + goto out; + } + + memset(read_cdb, 0, sizeof(read_cdb)); + if (rdev->dev_dif_type != 2 || !check_dif) { + read_cdb[0] = READ_16; + if (check_dif) + read_cdb[1] |= 0x20; + put_unaligned_be64(lba, &read_cdb[2]); + put_unaligned_be32(blocks, &read_cdb[10]); + cdb_len = 16; + } else { + read_cdb[0] = VARIABLE_LENGTH_CMD; + put_unaligned_be16(SUBCODE_READ_32, &read_cdb[8]); + read_cdb[7] = 0x18; + cdb_len = 32; + read_cdb[10] = 0x20; + put_unaligned_be64(lba, &read_cdb[12]); + put_unaligned_be32(blocks, &read_cdb[28]); + put_unaligned_be32(lba & 0xFFFF, &read_cdb[20]); + /* No app tag check */ + } + + rcmd = __scst_create_prepare_internal_cmd(read_cdb, + cdb_len, SCST_CMD_QUEUE_SIMPLE, + priv->cm_read_tgt_dev, GFP_KERNEL, false); + if (rcmd == NULL) { + res = -ENOMEM; + goto out_busy; + } + + rcmd->internal_check_local_events = 1; + + rcmd->expected_data_direction = SCST_DATA_READ; + rcmd->expected_transfer_len_full = len; + if (check_dif != 0) + rcmd->expected_transfer_len_full += len >> (block_shift - SCST_DIF_TAG_SHIFT); + rcmd->expected_values_set = 1; + + res = scst_cm_add_to_internal_cmd_list(rcmd, ec_cmd, ec_cmd, + scst_cm_read_cmd_finished); + if (res != 0) + goto out_free_rcmd; + + TRACE_DBG("Adding ec_cmd's (%p) READ rcmd %p (lba %lld, blocks %d, " + "check_dif %d) to active cmd list", ec_cmd, rcmd, + (long long)rcmd->lba, blocks, check_dif); + spin_lock_irq(&rcmd->cmd_threads->cmd_list_lock); + list_add_tail(&rcmd->cmd_list_entry, &rcmd->cmd_threads->active_cmd_list); + spin_unlock_irq(&rcmd->cmd_threads->cmd_list_lock); + + res = 0; + +out: + TRACE_EXIT_RES(res); + return res; + +out_busy: + scst_set_busy(ec_cmd); + goto out; + +out_free_rcmd: + __scst_cmd_put(rcmd); + goto out; +} + +static void scst_cm_read_retry_fn(struct scst_cmd *rcmd) +{ + struct scst_cm_internal_cmd_priv *p = rcmd->tgt_i_priv; + struct scst_cmd *ec_cmd = p->cm_orig_cmd; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + int rc; + + TRACE_ENTRY(); + + mutex_lock(&priv->cm_mutex); + + rc = __scst_cm_push_single_read(ec_cmd, rcmd->lba, + rcmd->data_len >> priv->cm_read_tgt_dev->dev->block_shift); + + /* ec_cmd can get dead after we will drop cm_mutex! */ + scst_cm_del_free_from_internal_cmd_list(rcmd, false); + + mutex_unlock(&priv->cm_mutex); + + if (rc == 0) + wake_up(&priv->cm_read_tgt_dev->active_cmd_threads->cmd_list_waitQ); + else + scst_cm_in_flight_cmd_finished(ec_cmd); + + TRACE_EXIT(); + return; +} + +static int scst_cm_push_single_write(struct scst_cmd *ec_cmd, + int64_t lba, int blocks, struct scst_cmd *rcmd); + +static void scst_cm_write_retry_fn(struct scst_cmd *wcmd) +{ + struct scst_cm_internal_cmd_priv *p = wcmd->tgt_i_priv; + struct scst_cmd *rcmd = p->cm_orig_cmd; + struct scst_cm_internal_cmd_priv *rp = rcmd->tgt_i_priv; + struct scst_cmd *ec_cmd = rp->cm_orig_cmd; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + int rc; + + TRACE_ENTRY(); + + mutex_lock(&priv->cm_mutex); + + rc = scst_cm_push_single_write(ec_cmd, wcmd->lba, + wcmd->data_len >> priv->cm_write_tgt_dev->dev->block_shift, + rcmd); + + /* ec_cmd can get dead after we will drop cm_mutex! */ + scst_cm_del_free_from_internal_cmd_list(wcmd, false); + + mutex_unlock(&priv->cm_mutex); + + __scst_cmd_put(rcmd); + + if (rc != 0) + scst_cm_in_flight_cmd_finished(ec_cmd); + + TRACE_EXIT(); + return; +} + +static int scst_cm_push_single_read(struct scst_cmd *ec_cmd, int blocks, + bool inc_cur_in_flight); + +static void scst_cm_write_cmd_finished(struct scst_cmd *wcmd) +{ + struct scst_cm_internal_cmd_priv *p = wcmd->tgt_i_priv; + struct scst_cmd *rcmd = p->cm_orig_cmd; + struct scst_cm_internal_cmd_priv *rp = rcmd->tgt_i_priv; + struct scst_cmd *ec_cmd = rp->cm_orig_cmd; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + int rc, blocks; + + TRACE_ENTRY(); + + TRACE_DBG("Write cmd %p finished (ec_cmd %p, rcmd %p, cm_cur_in_flight %d)", + wcmd, rcmd, ec_cmd, priv->cm_cur_in_flight); + + EXTRACHECKS_BUG_ON(wcmd->cdb[0] != WRITE_16); + + if (unlikely(scst_cm_is_ec_cmd_done(ec_cmd))) + goto out_finished; + + rc = scst_cm_err_check_retry(wcmd, ec_cmd->start_time, scst_cm_write_retry_fn); + if (likely(rc == SCST_CM_STATUS_CMD_SUCCEEDED)) + goto cont; + else if (rc == SCST_CM_STATUS_RETRY) + goto out; + else { + TRACE_DBG("Write cmd %p (ec_cmd %p) finished not successfully", + wcmd, ec_cmd); + if (wcmd->status == SAM_STAT_CHECK_CONDITION) + rc = scst_set_cmd_error_sense(ec_cmd, wcmd->sense, + wcmd->sense_valid_len); + else { + sBUG_ON(wcmd->sense != NULL); + rc = scst_set_cmd_error_status(ec_cmd, wcmd->status); + } + if (rc != 0) { + /* + * UAs should be retried unconditionally, but during + * limited time. If we are here, we need to requeue it. + */ + WARN_ON(scst_is_ua_sense(wcmd->sense, wcmd->sense_valid_len)); + sBUG_ON(priv->cm_error == SCST_CM_ERROR_NONE); + } else + priv->cm_error = SCST_CM_ERROR_WRITE; + goto out_finished; + } + +cont: + priv->cm_written += wcmd->data_len; + TRACE_DBG("ec_cmd %p, cm_written %lld (data_len %lld)", ec_cmd, + (long long)priv->cm_written, (long long)wcmd->data_len); + + wcmd->sg = NULL; + wcmd->sg_cnt = 0; + + mutex_lock(&priv->cm_mutex); + + if (priv->cm_left_to_read == 0) { + if (priv->cm_cur_data_descr >= priv->cm_data_descrs_cnt) + goto out_unlock_finished; + + rc = scst_cm_setup_next_data_descr(ec_cmd); + if (rc != 0) + goto out_unlock_finished; + } + + EXTRACHECKS_BUG_ON(priv->cm_left_to_read == 0); + + blocks = min_t(int, priv->cm_left_to_read, priv->cm_max_each_read); + + rc = scst_cm_push_single_read(ec_cmd, blocks, false); + if (rc != 0) + goto out_unlock_finished; + + scst_cm_del_free_from_internal_cmd_list(wcmd, false); + scst_cm_del_free_from_internal_cmd_list(rcmd, false); + + mutex_unlock(&priv->cm_mutex); + + wake_up(&rcmd->cmd_threads->cmd_list_waitQ); + +out_put: + __scst_cmd_put(rcmd); + +out: + TRACE_EXIT(); + return; + +out_unlock_finished: + mutex_unlock(&priv->cm_mutex); + +out_finished: + scst_cm_del_free_from_internal_cmd_list(wcmd, false); + scst_cm_del_free_from_internal_cmd_list(rcmd, false); + + scst_cm_in_flight_cmd_finished(ec_cmd); + goto out_put; +} + +static int scst_cm_push_single_write(struct scst_cmd *ec_cmd, + int64_t lba, int blocks, struct scst_cmd *rcmd) +{ + int res; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + uint8_t write16_cdb[16]; + struct scst_cmd *wcmd; + int len; + + TRACE_ENTRY(); + + len = blocks << priv->cm_write_tgt_dev->dev->block_shift; + + /* + * ToDo: if rcmd is coming with tags SG, use it after updating ref and + * app tags instead of regenerating guard tags again with WRITE(16) + */ + + memset(write16_cdb, 0, sizeof(write16_cdb)); + write16_cdb[0] = WRITE_16; + put_unaligned_be64(lba, &write16_cdb[2]); + put_unaligned_be32(blocks, &write16_cdb[10]); + + wcmd = __scst_create_prepare_internal_cmd(write16_cdb, + sizeof(write16_cdb), SCST_CMD_QUEUE_SIMPLE, + priv->cm_write_tgt_dev, GFP_KERNEL, false); + if (wcmd == NULL) { + res = -ENOMEM; + goto out_busy; + } + + wcmd->internal_check_local_events = 1; + + wcmd->expected_data_direction = SCST_DATA_WRITE; + wcmd->expected_transfer_len_full = len; + wcmd->expected_values_set = 1; + + res = scst_cm_add_to_internal_cmd_list(wcmd, ec_cmd, rcmd, + scst_cm_write_cmd_finished); + if (res != 0) + goto out_free_wcmd; + + __scst_cmd_get(rcmd); + + wcmd->tgt_i_sg = rcmd->sg; + wcmd->tgt_i_sg_cnt = rcmd->sg_cnt; + wcmd->tgt_i_data_buf_alloced = 1; + + TRACE_DBG("Adding EC (%p) WRITE(16) cmd %p (lba %lld, blocks %d) to " + "active cmd list", ec_cmd, wcmd, (long long)wcmd->lba, blocks); + spin_lock_irq(&wcmd->cmd_threads->cmd_list_lock); + list_add_tail(&wcmd->cmd_list_entry, &wcmd->cmd_threads->active_cmd_list); + wake_up(&wcmd->cmd_threads->cmd_list_waitQ); + spin_unlock_irq(&wcmd->cmd_threads->cmd_list_lock); + + res = 0; + +out: + TRACE_EXIT_RES(res); + return res; + +out_busy: + scst_set_busy(ec_cmd); + goto out; + +out_free_wcmd: + __scst_cmd_put(wcmd); + goto out; +} + +static void scst_cm_read_cmd_finished(struct scst_cmd *rcmd) +{ + struct scst_cm_internal_cmd_priv *p = rcmd->tgt_i_priv; + struct scst_cmd *ec_cmd = p->cm_orig_cmd; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + int64_t lba; + int rc, len, blocks; + + TRACE_ENTRY(); + + TRACE_DBG("READ cmd %p finished (ec_cmd %p, p %p)", rcmd, ec_cmd, p); + + if (unlikely(scst_cm_is_ec_cmd_done(ec_cmd))) + goto out_finished; + + rc = scst_cm_err_check_retry(rcmd, ec_cmd->start_time, scst_cm_read_retry_fn); + if (likely(rc == SCST_CM_STATUS_CMD_SUCCEEDED)) + goto cont; + else if (rc == SCST_CM_STATUS_RETRY) + goto out; + else { + TRACE_DBG("Read cmd %p (ec_cmd %p) finished not successfully", + rcmd, ec_cmd); + if (rcmd->status == SAM_STAT_CHECK_CONDITION) + rc = scst_set_cmd_error_sense(ec_cmd, rcmd->sense, + rcmd->sense_valid_len); + else { + sBUG_ON(rcmd->sense != NULL); + rc = scst_set_cmd_error_status(ec_cmd, rcmd->status); + } + if (rc != 0) { + /* + * UAs should be retried unconditionally, but during + * limited time. If we are here, we need to requeue it. + */ + WARN_ON(scst_is_ua_sense(rcmd->sense, rcmd->sense_valid_len)); + sBUG_ON(priv->cm_error == SCST_CM_ERROR_NONE); + } else + priv->cm_error = SCST_CM_ERROR_READ; + goto out_finished; + } + +cont: + lba = rcmd->lba - priv->cm_start_read_lba; + lba <<= priv->cm_read_tgt_dev->dev->block_shift; + lba >>= priv->cm_write_tgt_dev->dev->block_shift; + lba += priv->cm_start_write_lba; + + len = rcmd->data_len; + blocks = len >> priv->cm_write_tgt_dev->dev->block_shift; + + TRACE_DBG("rcmd->lba %lld, start_read_lba %lld, read shift %d, write " + "shift %d, start_write_lba %lld, lba %lld, len %d, blocks %d", + (long long)rcmd->lba, (long long)priv->cm_start_read_lba, + priv->cm_read_tgt_dev->dev->block_shift, + priv->cm_write_tgt_dev->dev->block_shift, + (long long)priv->cm_start_write_lba, lba, len, blocks); + + rc = scst_cm_push_single_write(ec_cmd, lba, blocks, rcmd); + if (rc != 0) + goto out_finished; + +out: + TRACE_EXIT(); + return; + +out_finished: + scst_cm_del_free_from_internal_cmd_list(rcmd, false); + + scst_cm_in_flight_cmd_finished(ec_cmd); + goto out; +} + +/* cm_mutex suppose to be locked */ +static int scst_cm_push_single_read(struct scst_cmd *ec_cmd, int blocks, + bool inc_cur_in_flight) +{ + int res; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + + TRACE_ENTRY(); + + TRACE_DBG("ec_cmd %p, cm_cur_read_lba %lld, cm_left_to_read %d, " + "blocks %d", ec_cmd, (long long)priv->cm_cur_read_lba, + priv->cm_left_to_read, blocks); + + res = __scst_cm_push_single_read(ec_cmd, priv->cm_cur_read_lba, blocks); + if (res != 0) + goto out; + + priv->cm_cur_read_lba += blocks; + priv->cm_left_to_read -= blocks; + + if (inc_cur_in_flight) { + priv->cm_cur_in_flight++; + TRACE_DBG("ec_cmd %p, new cm_cur_in_flight %d", ec_cmd, + priv->cm_cur_in_flight); + } + +out: + TRACE_EXIT_RES(res); + return res; +} + +/* + * Generates original bunch of internal READ commands. In case of error + * directly finishes ec_cmd, so it might be dead upon return! + */ +static void scst_cm_gen_reads(struct scst_cmd *ec_cmd) +{ + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + int cnt = 0; + + TRACE_ENTRY(); + + mutex_lock(&priv->cm_mutex); + + while (1) { + int rc; + while ((priv->cm_left_to_read > 0) && + (priv->cm_cur_in_flight < SCST_MAX_IN_FLIGHT_INTERNAL_COMMANDS)) { + int blocks; + + blocks = min_t(int, priv->cm_left_to_read, priv->cm_max_each_read); + + rc = scst_cm_push_single_read(ec_cmd, blocks, true); + if (rc != 0) + goto out_err; + + cnt++; + } + + if (priv->cm_cur_in_flight == SCST_MAX_IN_FLIGHT_INTERNAL_COMMANDS) + break; + + rc = scst_cm_setup_next_data_descr(ec_cmd); + if (rc != 0) + goto out_err; + } + + EXTRACHECKS_BUG_ON(cnt == 0); + +out_wake: + if (cnt != 0) + wake_up(&priv->cm_read_tgt_dev->active_cmd_threads->cmd_list_waitQ); + + mutex_unlock(&priv->cm_mutex); + +out: + TRACE_EXIT(); + return; + +out_err: + if (priv->cm_cur_in_flight != 0) + goto out_wake; + else { + mutex_unlock(&priv->cm_mutex); + scst_cm_ec_cmd_done(ec_cmd); + } + goto out; +} + +/* cm_mutex suppose to be locked or no activities on this ec_cmd's priv */ +static void scst_cm_process_data_descrs(struct scst_cmd *ec_cmd, + const struct scst_ext_copy_data_descr *dds, int dds_cnt) +{ + int rc; + + TRACE_ENTRY(); + + rc = scst_cm_setup_data_descrs(ec_cmd, dds, dds_cnt); + if (rc != 0) + goto out_done; + + scst_cm_gen_reads(ec_cmd); + + /* ec_cmd can be dead here! */ + +out: + TRACE_EXIT(); + return; + +out_done: + scst_cm_ec_cmd_done(ec_cmd); + goto out; +} + +/** + * scst_ext_copy_get_cur_seg_data_len() - return current segment data len + * @ec_cmd: EXTENDED COPY command + */ +int scst_ext_copy_get_cur_seg_data_len(struct scst_cmd *ec_cmd) +{ + int res; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + + TRACE_ENTRY(); + + res = priv->cm_seg_descrs[priv->cm_cur_seg_descr].data_descr.data_len; + + TRACE_EXIT_RES(res); + return res; +} +EXPORT_SYMBOL_GPL(scst_ext_copy_get_cur_seg_data_len); + +/** + * scst_ext_copy_remap_done() - dev handler done with remapping this segment + * @ec_cmd: EXTENDED COPY command + * @dds: Leftover data descriptors + * @dds_cnt: Count of the leftover data descriptors + * + * Called by dev handlers from inside ext_copy_remap() callback upon finish. + * All not finished spaces should be k?alloc() as array of data descriptors + * in dds argument with count dds_count. SCST core then will copy them using + * internal copy machine and then kfree() dds. + * + * dds can point to &descr->data_descr, where descr is pointer supplied to + * ext_copy_remap(). In this case SCST core will not kfree() it. + * + * If dds is NULL, then all data have been remapped, so SCST core will switch + * to the next segment descriptor, if any. + */ +void scst_ext_copy_remap_done(struct scst_cmd *ec_cmd, + struct scst_ext_copy_data_descr *dds, int dds_cnt) +{ + TRACE_ENTRY(); + + scst_set_exec_time(ec_cmd); + + if (dds == NULL) + scst_cm_ec_sched_next_seg(ec_cmd); + else + scst_cm_process_data_descrs(ec_cmd, dds, dds_cnt); + + /* ec_cmd can be dead here! */ + + TRACE_EXIT(); + return; +} +EXPORT_SYMBOL_GPL(scst_ext_copy_remap_done); + +static int scst_cm_try_to_remap(struct scst_cmd *ec_cmd); + +static void scst_cm_remap_retry_fn(struct scst_cmd *cmd) +{ + struct scst_cmd *ec_cmd = cmd->tgt_i_priv; + int rc; + + TRACE_ENTRY(); + + rc = scst_cm_try_to_remap(ec_cmd); + sBUG_ON(rc != 0); + + TRACE_EXIT(); + return; +} + +/* + * Tries to remap data before copying. + * + * Returns !=0 if remapping is not possible, or not 0 otherwise. + */ +static int scst_cm_try_to_remap(struct scst_cmd *ec_cmd) +{ + int res, rc; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + struct scst_ext_copy_seg_descr *sd = &priv->cm_seg_descrs[priv->cm_cur_seg_descr]; + struct scst_ext_copy_data_descr *dd = &sd->data_descr; + struct scst_dev_type *handler = ec_cmd->dev->handler; + uint8_t cdb[16]; + struct scst_cmd *cmd; + + TRACE_ENTRY(); + + if (handler->ext_copy_remap == NULL) { + res = 1; + goto out; + } + + res = 0; + + /* !! priv data descriptors fields are not setup yet !! */ + + TRACE_DBG("Checking reservations on read dev %s (ec_cmd %p)", + sd->src_tgt_dev->dev->virt_name, ec_cmd); + + memset(cdb, 0, sizeof(cdb)); + cdb[0] = READ_16; + put_unaligned_be64(dd->src_lba, &cdb[2]); + put_unaligned_be32(dd->data_len >> sd->src_tgt_dev->dev->block_shift, &cdb[10]); + + cmd = __scst_create_prepare_internal_cmd(cdb, + sizeof(cdb), SCST_CMD_QUEUE_SIMPLE, + sd->src_tgt_dev, GFP_KERNEL, true); + if (cmd == NULL) + goto out_busy; + + cmd->internal_check_local_events = 1; + cmd->tgt_i_priv = ec_cmd; /* needed for retries */ + + rc = __scst_check_local_events(cmd, false); + if (unlikely(rc != 0)) + goto out_check_retry; + + __scst_cmd_put(cmd); + + TRACE_DBG("Checking reservations on write dev %s (ec_cmd %p)", + sd->dst_tgt_dev->dev->virt_name, ec_cmd); + + memset(cdb, 0, sizeof(cdb)); + cdb[0] = WRITE_16; + put_unaligned_be64(dd->dst_lba, &cdb[2]); + put_unaligned_be32(dd->data_len >> sd->dst_tgt_dev->dev->block_shift, &cdb[10]); + + cmd = __scst_create_prepare_internal_cmd(cdb, + sizeof(cdb), SCST_CMD_QUEUE_SIMPLE, + sd->dst_tgt_dev, GFP_KERNEL, true); + if (cmd == NULL) + goto out_busy; + + cmd->internal_check_local_events = 1; + cmd->tgt_i_priv = ec_cmd; /* needed for retries */ + + rc = __scst_check_local_events(cmd, false); + if (unlikely(rc != 0)) + goto out_check_retry; + + __scst_cmd_put(cmd); + + TRACE_DBG("Calling ext_copy_remap() for dev %s (ec_cmd %p)", + sd->dst_tgt_dev->dev->virt_name, ec_cmd); + + scst_set_exec_start(ec_cmd); + handler->ext_copy_remap(ec_cmd, sd); + +out: + TRACE_EXIT_RES(res); + return res; + +out_check_retry: + rc = scst_cm_err_check_retry(cmd, ec_cmd->start_time, scst_cm_remap_retry_fn); + sBUG_ON(rc == SCST_CM_STATUS_CMD_SUCCEEDED); + if (rc == SCST_CM_STATUS_CMD_FAILED) { + TRACE_DBG("Remap check cmd %p (ec_cmd %p, op %s) failed", + cmd, ec_cmd, scst_get_opcode_name(cmd)); + if (cmd->status == SAM_STAT_CHECK_CONDITION) + rc = scst_set_cmd_error_sense(ec_cmd, cmd->sense, + cmd->sense_valid_len); + else { + sBUG_ON(cmd->sense != NULL); + rc = scst_set_cmd_error_status(ec_cmd, cmd->status); + } + if (rc != 0) { + /* + * UAs should be retried unconditionally, but during + * limited time. If we are here, we need to requeue it. + */ + WARN_ON(scst_is_ua_sense(cmd->sense, cmd->sense_valid_len)); + } else { + if (cmd->cdb[0] == READ_16) + priv->cm_error = SCST_CM_ERROR_READ; + else { + EXTRACHECKS_BUG_ON(cmd->cdb[0] != WRITE_16); + priv->cm_error = SCST_CM_ERROR_WRITE; + } + } + __scst_cmd_put(cmd); + goto out_done; + } + __scst_cmd_put(cmd); + goto out; + +out_busy: + res = -ENOMEM; + scst_set_busy(ec_cmd); + +out_done: + scst_cm_ec_cmd_done(ec_cmd); + goto out; +} + +static void scst_cm_process_cur_seg_descr(struct scst_cmd *ec_cmd) +{ + int rc; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + + TRACE_ENTRY(); + + rc = scst_cm_try_to_remap(ec_cmd); + if (rc == 0) + goto out; + + /* No remapping supported */ + + scst_ext_copy_remap_done(ec_cmd, + &priv->cm_seg_descrs[priv->cm_cur_seg_descr].data_descr, 1); + +out: + TRACE_EXIT(); + return; +} + +int scst_cm_ext_copy_exec(struct scst_cmd *ec_cmd) +{ + int res = SCST_EXEC_COMPLETED, rc; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + + TRACE_ENTRY(); + + if (unlikely(priv == NULL)) + goto out_local_done; + + if (unlikely(scst_cm_is_ec_cmd_done(ec_cmd))) { + TRACE_DBG("ec_cmd %p done", ec_cmd); + goto out_done; + } + + rc = scst_cm_setup_seg_descr(ec_cmd); + if (rc != 0) + goto out_err_done; + + scst_cm_process_cur_seg_descr(ec_cmd); + +out: + TRACE_EXIT(); + return res; + +out_local_done: + ec_cmd->completed = 1; /* for success */ + ec_cmd->scst_cmd_done(ec_cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_THREAD); + goto out; + +out_err_done: + sBUG_ON((rc != -ENOENT) && !ec_cmd->completed); + +out_done: + scst_cm_ec_cmd_done(ec_cmd); + goto out; +} + +bool scst_cm_ec_cmd_overlap(struct scst_cmd *ec_cmd, struct scst_cmd *cmd) +{ + bool res = false; + int i; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + + TRACE_ENTRY(); + + TRACE_DBG("ec_cmd %p, cmd %p", ec_cmd, cmd); + + EXTRACHECKS_BUG_ON(ec_cmd->cdb[0] != EXTENDED_COPY); + + if ((cmd->op_flags & SCST_LBA_NOT_VALID) != 0) + goto out; + + for (i = 0; i < ec_cmd->cmd_data_descriptors_cnt; i++) { + struct scst_ext_copy_seg_descr *sd = &priv->cm_seg_descrs[i]; + + TRACE_DBG("type %d, dst_dev %p, dev %p", sd->type, + sd->dst_tgt_dev->dev, cmd->dev); + + if (sd->type != SCST_EXT_COPY_SEG_DATA) + continue; + if (sd->dst_tgt_dev->dev != cmd->dev) + continue; + + res = scst_lba1_inside_lba2(sd->data_descr.dst_lba, cmd->lba, + cmd->data_len >> cmd->dev->block_shift); + if (res) + goto out; + res = scst_lba1_inside_lba2(cmd->lba, sd->data_descr.dst_lba, + sd->data_descr.data_len >> sd->dst_tgt_dev->dev->block_shift); + if (res) + goto out; + } + +out: + TRACE_EXIT_RES(res); + return res; +} + +/* + * No locks. Returns true if cmd should be blocked. + */ +bool scst_cm_check_block_all_devs(struct scst_cmd *cmd) +{ + bool res = false; + struct scst_cmd *ec_cmd; + struct scst_cm_ec_cmd_priv *d; + struct scst_cm_dev_entry *e; + + TRACE_ENTRY(); + + /* + * EXTENDED COPY command creates a new challenge: it can operate on + * several devices at the same time. Hence, it opens a wide possibility + * of deadlocks like the following. + * + * Device A received EXTENDED COPY command EC1 to copy data from device + * A to device B. It started doing the job by generating SCSI READ and + * WRITE commands to devices A and B. In the middle of the work device B + * received its EXTENDED COPY command EC2 to copy data from device B + * to device A. It also started doing its work in parallel with EC1. + * Then, device A received a serialized or SCSI atomic command, which + * can not proceed until EC1 finished and don't let EC2 generated + * commands to proceed ahead of it. Then, if device B received similar + * serialized or SCSI atomic command, blocking EC2 generated commands, + * we would have a deadlock. + * + * So, we can not allow EXTENDED COPY generated SCSI commands be + * blocked. From other side, we have to honor serialized and SCSI atomic + * commands as well as all other devices blocking events. + * + * To handle it, we, from one side, generate all EXTENDED COPY read and + * writes commands as internal commands, so they bypass blocking + * checking, and from another side, create "fantom" EXTENDED COPY + * commands on all participating devices, then pass "check blocking" on + * all them at once. If any device blocked its command, then undo check + * blocking on all previously processed devices. After the blocked + * command resumed, we retry again. For SCSI atomic commands we check + * each if any of the (fantom) EXTENDED COPY commands conflict with + * existing SCSI atomic commands. If yes, then restart on the conflicting + * (fantom) EXTENDED COPY resume as described above. Then we check all + * newly coming SCSI atomic commands if they conflict with existing + * (fantom) EXTENDED COPY commands. + */ + + if (cmd->internal) { + struct scst_cm_internal_cmd_priv *p = cmd->tgt_i_priv; + + /* cmd is a resumed phantom EXTENDED COPY command */ + + ec_cmd = p->cm_orig_cmd; + + TRACE_BLOCK("Rewaking blocked EC cmd %p (fcmd %p)", + ec_cmd, cmd); + + scst_check_unblock_dev(cmd); + + spin_lock_irq(&ec_cmd->cmd_threads->cmd_list_lock); + list_add_tail(&ec_cmd->cmd_list_entry, + &ec_cmd->cmd_threads->active_cmd_list); + wake_up(&ec_cmd->cmd_threads->cmd_list_waitQ); + spin_unlock_irq(&ec_cmd->cmd_threads->cmd_list_lock); + + res = true; + goto out; + } + + /* cmd is a real ready for exec EXTENDED COPY command */ + + ec_cmd = cmd; + /* + * This could be restart of previously blocked ec_cmd, so + * check unblock it. + */ + TRACE_DBG("Check unblocking ec_cmd %p", ec_cmd); + scst_check_unblock_dev(ec_cmd); + + d = ec_cmd->cmd_data_descriptors; + if (d == NULL) { + spin_lock_bh(&ec_cmd->dev->dev_lock); + res = scst_do_check_blocked_dev(ec_cmd); + spin_unlock_bh(&ec_cmd->dev->dev_lock); + goto out; + } + + local_bh_disable(); + +#if !defined(__CHECKER__) + list_for_each_entry(e, &d->cm_sorted_devs_list, cm_sorted_devs_list_entry) { + spin_lock(&e->cm_fcmd->dev->dev_lock); + } +#endif + + list_for_each_entry(e, &d->cm_sorted_devs_list, cm_sorted_devs_list_entry) { + TRACE_DBG("dev %p (fcmd %p)", e->cm_fcmd->dev, e->cm_fcmd); + res = scst_do_check_blocked_dev(e->cm_fcmd); + if (unlikely(res)) { + TRACE_BLOCK("fcmd %p (ec_cmd %p) blocked, undo " + "check blocking devices", e->cm_fcmd, ec_cmd); + break; + } + } + + if (unlikely(res)) { + struct scst_cmd *blocked_cmd = e->cm_fcmd; + list_for_each_entry(e, &d->cm_sorted_devs_list, + cm_sorted_devs_list_entry) { + if (e->cm_fcmd == blocked_cmd) + break; + __scst_check_unblock_dev(e->cm_fcmd); + e->cm_fcmd->state = SCST_CMD_STATE_EXEC_CHECK_BLOCKING; + } + } + +#if !defined(__CHECKER__) + list_for_each_entry_reverse(e, &d->cm_sorted_devs_list, + cm_sorted_devs_list_entry) { + spin_unlock(&e->cm_fcmd->dev->dev_lock); + } +#endif + + local_bh_enable(); + +out: + TRACE_EXIT_RES(res); + return res; +} + +void scst_cm_abort_ec_cmd(struct scst_cmd *ec_cmd) +{ + struct scst_cm_ec_cmd_priv *p = ec_cmd->cmd_data_descriptors; + struct scst_cm_internal_cmd_priv *ip; + unsigned long flags; + + TRACE_ENTRY(); + + EXTRACHECKS_BUG_ON(ec_cmd->cdb[0] != EXTENDED_COPY); + + spin_lock_irqsave(&scst_cm_lock, flags); + + if (p == NULL) + goto out_unlock; + + TRACE_MGMT_DBG("Aborting fantom and internal commands of ec_cmd %p", + ec_cmd); + + list_for_each_entry(ip, &p->cm_internal_cmd_list, + cm_internal_cmd_list_entry) { + struct scst_cmd *c = ip->cm_cmd; + TRACE_MGMT_DBG("Aborting (f)cmd %p", c); + set_bit(SCST_CMD_ABORTED, &c->cmd_flags); + } + +out_unlock: + spin_unlock_irqrestore(&scst_cm_lock, flags); + + TRACE_EXIT(); + return; +} + +static void scst_cm_del_free_list_id(struct scst_cm_list_id *l, bool locked) +{ + unsigned long flags = 0; + + TRACE_ENTRY(); + + TRACE_DBG("Freeing list id %p", l); + +#if !defined(__CHECKER__) + if (!locked) + spin_lock_irqsave(&scst_cm_lock, flags); +#endif + +#ifdef CONFIG_SMP + EXTRACHECKS_BUG_ON(!spin_is_locked(&scst_cm_lock)); +#endif + + list_del(&l->sess_cm_list_id_entry); + +#if !defined(__CHECKER__) + if (!locked) + spin_unlock_irqrestore(&scst_cm_lock, flags); +#endif + + kfree(l); + + TRACE_EXIT(); + return; +} + +static void scst_cm_sched_del_list_id(struct scst_cmd *ec_cmd) +{ + struct scst_session *sess = ec_cmd->sess; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + struct scst_cm_list_id *l = priv->cm_list_id; + unsigned long flags; + + TRACE_ENTRY(); + + spin_lock_irqsave(&scst_cm_lock, flags); + + if (l->cm_list_id_state != SCST_CM_LIST_ID_STATE_DONE) { + /* + * It can happen for preliminary EC command finish, e.g., + * because of local event like UA. + */ + l->cm_can_be_immed_free = 1; + } + + l->cm_done = 1; + + /* + * Barrier to sync with scst_abort_cmd() and + * scst_mgmt_affected_cmds_done() calling scst_cm_free_pending_list_ids(). + * It has nothing common with cm_done set above. Just in case, actually. + */ + smp_rmb(); + if (test_bit(SCST_CMD_ABORTED, &ec_cmd->cmd_flags) || + l->cm_can_be_immed_free) { + TRACE_DBG("List id %p can be immed freed", l); + scst_cm_del_free_list_id(l, true); + spin_unlock_irqrestore(&scst_cm_lock, flags); + goto out; + } + + l->cm_list_id_state = SCST_CM_LIST_ID_STATE_PENDING_FREE; + l->cm_time_to_free = jiffies + SCST_CM_ID_KEEP_TIME; + + spin_unlock_irqrestore(&scst_cm_lock, flags); + + TRACE_DBG("Schedule pending free list id %p", l); + + schedule_delayed_work(&sess->sess_cm_list_id_cleanup_work, + SCST_CM_ID_KEEP_TIME); + +out: + TRACE_EXIT(); + return; +} + +static struct scst_cm_list_id *scst_cm_add_list_id(struct scst_cmd *cmd, + int list_id) +{ + struct scst_cm_list_id *res; + struct scst_session *sess = cmd->sess; + struct scst_cm_list_id *l; + + TRACE_ENTRY(); + + res = kzalloc(sizeof(*res), GFP_KERNEL); + if (res == NULL) { + TRACE(TRACE_OUT_OF_MEM, "Unable to allocate list_id"); + scst_set_busy(cmd); + goto out; + } + + res->cm_lid = list_id; + res->cm_list_id_state = SCST_CM_LIST_ID_STATE_ACTIVE; + + spin_lock_irq(&scst_cm_lock); + + list_for_each_entry(l, &sess->sess_cm_list_id_list, sess_cm_list_id_entry) { + if (l->cm_lid == list_id) { + if (l->cm_list_id_state == SCST_CM_LIST_ID_STATE_PENDING_FREE) { + scst_cm_del_free_list_id(l, true); + break; + } else { + TRACE_DBG("List id %d already exists", list_id); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_operation_in_progress)); + goto out_unlock_free; + } + } + } + + TRACE_DBG("Adding list id %p (id %d)", res, list_id); + list_add_tail(&res->sess_cm_list_id_entry, &sess->sess_cm_list_id_list); + + spin_unlock_irq(&scst_cm_lock); + +out: + TRACE_EXIT(); + return res; + +out_unlock_free: + spin_unlock_irq(&scst_cm_lock); + kfree(res); + res = NULL; + goto out; +} + +void sess_cm_list_id_cleanup_work_fn(struct delayed_work *work) +{ + struct scst_session *sess = container_of(work, + struct scst_session, sess_cm_list_id_cleanup_work); + struct scst_cm_list_id *l, *t; + unsigned long cur_time = jiffies; + unsigned long flags; + + TRACE_ENTRY(); + + /* + * We assume that EC commands finish _more or less_ in order, so + * there's no need to scan the whole list each time. + */ + + spin_lock_irqsave(&scst_cm_lock, flags); + list_for_each_entry_safe(l, t, &sess->sess_cm_list_id_list, sess_cm_list_id_entry) { + if (l->cm_list_id_state != SCST_CM_LIST_ID_STATE_PENDING_FREE) + break; + if (time_after_eq(cur_time, l->cm_time_to_free)) + scst_cm_del_free_list_id(l, true); + else { + TRACE_DBG("Reschedule pending free list ids cleanup"); + schedule_delayed_work(&sess->sess_cm_list_id_cleanup_work, + l->cm_time_to_free - cur_time); + } + } + spin_unlock_irqrestore(&scst_cm_lock, flags); + + TRACE_EXIT(); + return; +} + +void scst_cm_free_pending_list_ids(struct scst_session *sess) +{ + struct scst_cm_list_id *l, *t; + + TRACE_ENTRY(); + + cancel_delayed_work_sync(&sess->sess_cm_list_id_cleanup_work); + + spin_lock_irq(&scst_cm_lock); + list_for_each_entry_safe(l, t, &sess->sess_cm_list_id_list, sess_cm_list_id_entry) { + TRACE_DBG("List id %p, state %d", l, l->cm_list_id_state); + if (l->cm_list_id_state == SCST_CM_LIST_ID_STATE_PENDING_FREE) + scst_cm_del_free_list_id(l, true); + } + spin_unlock_irq(&scst_cm_lock); + + TRACE_EXIT(); + return; +} + +static void scst_cm_copy_status(struct scst_cmd *cmd) +{ + ssize_t length = 0; + uint8_t *buf, tbuf[12]; + int list_id; + struct scst_cm_list_id *l; + struct scst_session *sess = cmd->sess; + bool found = false; + + TRACE_ENTRY(); + + list_id = cmd->cdb[2]; + + spin_lock_irq(&scst_cm_lock); + list_for_each_entry(l, &sess->sess_cm_list_id_list, sess_cm_list_id_entry) { + if (l->cm_lid == list_id) { + TRACE_DBG("list id %p found (id %d)", l, list_id); + found = true; + break; + } + } + if (found) { + l->cm_can_be_immed_free = 1; + + memset(tbuf, 0, sizeof(tbuf)); + put_unaligned_be32(8, &tbuf[0]); + if (l->cm_list_id_state == SCST_CM_LIST_ID_STATE_ACTIVE) + tbuf[4] = 0; /* in progress */ + else if (l->cm_status == 0) + tbuf[4] = 1; /* finished, no errors */ + else + tbuf[4] = 2; /* finished with errors */ + put_unaligned_be16(l->cm_segs_processed, &tbuf[5]); + tbuf[7] = 1; /* KBs */ + put_unaligned_be32(l->cm_written_size >> 10, &tbuf[8]); + + if (l->cm_done) + scst_cm_del_free_list_id(l, true); + } + + l = NULL; /* after unlock it can be immediately get dead */ + + spin_unlock_irq(&scst_cm_lock); + + if (!found) + goto out_list_id_not_found; + + length = scst_get_buf_full_sense(cmd, &buf); + if (unlikely(length <= 0)) + goto out; + + length = min_t(int, (int)sizeof(tbuf), length); + + memcpy(buf, tbuf, length); + scst_set_resp_data_len(cmd, length); + + scst_put_buf_full(cmd, buf); + +out: + TRACE_EXIT(); + return; + +out_list_id_not_found: + TRACE_DBG("list_id %d not found", list_id); + scst_set_invalid_field_in_cdb(cmd, 2, 0); + goto out; +} + +static void scst_cm_failed_seg_details(struct scst_cmd *cmd) +{ + ssize_t length = 0; + uint8_t *buf, *tbuf; + int list_id, size; + struct scst_cm_list_id *l; + struct scst_session *sess = cmd->sess; + bool found = false; + + TRACE_ENTRY(); + + list_id = cmd->cdb[2]; + + size = 60 + SCST_SENSE_BUFFERSIZE; + + tbuf = kzalloc(size, GFP_KERNEL); + if (tbuf == NULL) { + TRACE(TRACE_OUT_OF_MEM, "Unable to allocate FAILED SEGMENTS " + "DETAILS buffer (size %d)", size); + goto out_busy; + } + + spin_lock_irq(&scst_cm_lock); + list_for_each_entry(l, &sess->sess_cm_list_id_list, sess_cm_list_id_entry) { + if (l->cm_lid == list_id) { + TRACE_DBG("list id %p found (id %d)", l, list_id); + found = true; + break; + } + } + if (found) { + if (l->cm_list_id_state == SCST_CM_LIST_ID_STATE_ACTIVE) { + found = false; + goto skip; + } + + if ((cmd->bufflen == 0) || + ((l->cm_status == 0) && (cmd->bufflen >= 60)) || + ((l->cm_status != 0) && (cmd->bufflen >= 60 + SCST_SENSE_BUFFERSIZE))) + l->cm_can_be_immed_free = 1; + + if (l->cm_status == 0) + size = 60; + else + size = 60 + l->cm_sense_len; + + TRACE_DBG("l %p, status %d, sense_len %d, size %d", l, + l->cm_status, l->cm_sense_len, size); + + put_unaligned_be32(size-3, &tbuf[0]); + tbuf[56] = l->cm_status; + EXTRACHECKS_BUG_ON(l->cm_sense_len > SCST_SENSE_BUFFERSIZE); + BUILD_BUG_ON(sizeof(l->cm_sense) != SCST_SENSE_BUFFERSIZE); + put_unaligned_be16(l->cm_sense_len, &tbuf[58]); + if (l->cm_sense_len > 0) + memcpy(&tbuf[60], l->cm_sense, l->cm_sense_len); + + if (l->cm_can_be_immed_free && l->cm_done) + scst_cm_del_free_list_id(l, true); + } + +skip: + l = NULL; /* after unlock it can be immediately get dead */ + + spin_unlock_irq(&scst_cm_lock); + + if (!found) + goto out_list_id_not_found; + + length = scst_get_buf_full_sense(cmd, &buf); + if (unlikely(length <= 0)) + goto out_free; + + length = min_t(int, size, length); + + memcpy(buf, tbuf, length); + scst_set_resp_data_len(cmd, length); + + scst_put_buf_full(cmd, buf); + +out_free: + kfree(tbuf); + + TRACE_EXIT(); + return; + +out_list_id_not_found: + TRACE_DBG("list_id %d not found", list_id); + scst_set_invalid_field_in_cdb(cmd, 2, 0); + goto out_free; + +out_busy: + scst_set_busy(cmd); + goto out_free; +} + +static void scst_cm_oper_parameters(struct scst_cmd *cmd) +{ + ssize_t length = 0; + uint8_t *buf, tbuf[44+2] /* 2 descriptors implemented */; + + TRACE_ENTRY(); + + memset(tbuf, 0, sizeof(tbuf)); + + /* AVAILABLE DATA */ + put_unaligned_be32(sizeof(tbuf) - 4, &tbuf[0]); + + /* SNLID */ + tbuf[4] = 1; + + /* MAXIMUM TARGET DESCRIPTOR COUNT */ + put_unaligned_be16(SCST_CM_MAX_TGT_DESCR_CNT, &tbuf[8]); + + /* MAXIMUM SEGMENT DESCRIPTOR COUNT */ + put_unaligned_be16(SCST_CM_MAX_SEG_DESCR_CNT, &tbuf[10]); + + /* MAXIMUM DESCRIPTOR LIST LENGTH */ + put_unaligned_be32(SCST_MAX_SEG_DESC_LEN, &tbuf[12]); + + /* MAXIMUM SEGMENT LENGTH: 256MB */ + put_unaligned_be32(256*1024*1024, &tbuf[16]); + + /* No inline and held data. No stream device max data size. */ + + /* TOTAL CONCURRENT COPIES */ + put_unaligned_be16(0xFFFF, &tbuf[34]); + + /* MAXIMUM CONCURRENT COPIES */ + tbuf[36] = 0xFF; + + /* DATA SEGMENT GRANULARITY */ + tbuf[37] = 16; /* 64K */ + + /* 2 descriptor codes implemented */ + tbuf[43] = 2; + + /* Implemented descriptor codes */ + tbuf[44] = 2; + tbuf[45] = 0xE4; + + length = scst_get_buf_full_sense(cmd, &buf); + if (unlikely(length <= 0)) + goto out; + + length = min_t(int, (int)sizeof(tbuf), length); + + memcpy(buf, tbuf, length); + scst_set_resp_data_len(cmd, length); + + scst_put_buf_full(cmd, buf); + +out: + TRACE_EXIT(); + return; +} + +int scst_cm_rcv_copy_res_exec(struct scst_cmd *cmd) +{ + int res = SCST_EXEC_COMPLETED, action; + + TRACE_ENTRY(); + + action = cmd->cdb[1] & 0x1F; + + switch (action) { + case 0: /* copy status */ + scst_cm_copy_status(cmd); + break; + case 3: /* operational parameters */ + scst_cm_oper_parameters(cmd); + break; + case 4: /* failed segment details */ + scst_cm_failed_seg_details(cmd); + break; + default: + TRACE(TRACE_MINOR, "%s: action %d not supported", cmd->op_name, + action); + scst_set_invalid_field_in_cdb(cmd, 1, + SCST_INVAL_FIELD_BIT_OFFS_VALID | 0); + break; + } + + cmd->completed = 1; /* for success */ + cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_THREAD); + + TRACE_EXIT_RES(res); + return res; +} + +struct scst_cm_init_inq_priv { + /* Must be the first for scst_finish_internal_cmd()! */ + scst_i_finish_fn_t cm_init_inq_finish_fn; + + struct scst_device *dev; +}; + +static int scst_cm_send_init_inquiry(struct scst_device *dev, + unsigned int unpacked_lun, struct scst_cm_init_inq_priv *priv); + +static void scst_cm_inq_retry_fn(struct scst_cmd *cmd) +{ + struct scst_cm_init_inq_priv *priv = cmd->tgt_i_priv; + + TRACE_ENTRY(); + + /* cmd->dev can be NULL here! */ + + scst_cm_send_init_inquiry(priv->dev, cmd->lun, priv); + + TRACE_EXIT(); + return; +} + +static void scst_cm_init_inq_finish(struct scst_cmd *cmd) +{ + int length, page_len, off, rc; + uint8_t *buf; + struct scst_cm_init_inq_priv *priv = cmd->tgt_i_priv; + struct scst_device *dev = priv->dev; + + TRACE_ENTRY(); + + /* cmd->dev can be NULL here! */ + + rc = scst_cm_err_check_retry(cmd, cmd->start_time, scst_cm_inq_retry_fn); + if (rc == SCST_CM_STATUS_RETRY) + goto out; + + spin_lock_bh(&dev->dev_lock); + scst_unblock_dev(dev); + spin_unlock_bh(&dev->dev_lock); + + kfree(priv); + priv = NULL; + cmd->tgt_i_priv = NULL; + + if (rc != SCST_CM_STATUS_CMD_SUCCEEDED) { + PRINT_CRIT_ERROR("Unable to perform initial INQUIRY for device " + "%s. Copy manager for this device will be disabled", + dev->virt_name); + goto out; + } + + length = scst_get_buf_full(cmd, &buf); + TRACE_DBG("length %d", length); + if (unlikely(length <= 0)) { + if (length < 0) + PRINT_ERROR("scst_get_buf_full() failed: %d", length); + goto out; + } + + TRACE_BUFF_FLAG(TRACE_DEBUG, "buf", buf, length); + + if (buf[0] != 0) { + TRACE(TRACE_MINOR, "Not supported dev type %x, ignoring", buf[0]); + goto out_put; + } + + if (buf[1] != 0x83) { + PRINT_WARNING("Incorrect page code %x, ignoring", buf[1]); + goto out_put; + } + + page_len = get_unaligned_be16(&buf[2]); + if (page_len+3 > cmd->resp_data_len) { + PRINT_WARNING("Page len (%d) doesn't match resp len (%d), ignoring", + page_len+3, cmd->resp_data_len); + goto out_put; + } + + off = 4; + while (off < page_len) { + int des_len, des_alloc_len; + struct scst_cm_desig *des; + + if (off + 3 >= page_len) { + PRINT_WARNING("Too small page len %d, (off %d), ignoring", + page_len, off); + goto out_put; + } + + des_len = buf[off + 3]; + if ((off + des_len) > page_len) { + PRINT_WARNING("Too small buf len %d (off %d, des_len %d), " + "ignoring", page_len, off, des_len); + goto out_put; + } + + des_len += 4; + + if (((buf[off] & 0xF0) != 0) || ((buf[off+1] & 0xF0) != 0)) { + TRACE_DBG("Unsupported designator (%x, %x), " + "ignoring", buf[off] & 0xF0, buf[off+1] & 0xF0); + goto next; + } + + des_alloc_len = sizeof(*des) + des_len; + des = kzalloc(des_alloc_len, GFP_KERNEL); + if (des == NULL) { + PRINT_CRIT_ERROR("Unable to allocate designator (len %d, " + "type %x), ignoring it", des_alloc_len, + buf[off+1] & 0xF); + goto out_put; + } + + des->desig_tgt_dev = cmd->tgt_dev; + + des->desig_len = des_len; + memcpy(des->desig, &buf[off], des_len); + + TRACE_DBG("des %p, len %d", des, des->desig_len); + TRACE_BUFF_FLAG(TRACE_DEBUG, "des", des->desig, des->desig_len); + + mutex_lock(&scst_cm_mutex); + list_add_tail(&des->cm_desig_list_entry, &scst_cm_desig_list); + mutex_unlock(&scst_cm_mutex); +next: + off += des_len; + TRACE_DBG("off %d", off); + } + +out_put: + scst_put_buf_full(cmd, buf); + +out: + TRACE_EXIT(); + return; +} + +static int scst_cm_send_init_inquiry(struct scst_device *dev, + unsigned int unpacked_lun, struct scst_cm_init_inq_priv *priv) +{ + int res; + static const uint8_t inq_cdb[6] = { INQUIRY, 1, 0x83, 0x10, 0, 0 }; + __be64 lun; + struct scst_cmd *cmd; + + TRACE_ENTRY(); + + if (priv == NULL) { + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (priv == NULL) { + PRINT_ERROR("Unable to alloc priv"); + res = -ENOMEM; + goto out; + } + priv->cm_init_inq_finish_fn = scst_cm_init_inq_finish; + priv->dev = dev; + } + + lun = scst_pack_lun(unpacked_lun, scst_cm_sess->acg->addr_method); + + cmd = scst_rx_cmd(scst_cm_sess, (const uint8_t *)&lun, + sizeof(lun), inq_cdb, sizeof(inq_cdb), false); + if (cmd == NULL) { + res = -ENOMEM; + goto out_free; + } + + scst_cmd_set_expected(cmd, SCST_DATA_READ, 4096); + scst_cmd_set_queue_type(cmd, SCST_CMD_QUEUE_HEAD_OF_QUEUE); + scst_cmd_set_tgt_priv(cmd, priv); + + cmd->bypass_blocking = 1; + + scst_cmd_init_done(cmd, SCST_CONTEXT_THREAD); + + res = 0; + +out: + TRACE_EXIT_RES(res); + return res; + +out_free: + kfree(priv); + goto out; +} + +/* scst_mutex supposed to be held and activities suspended */ +static bool scst_cm_is_lun_free(unsigned int lun) +{ + bool res = true; + struct list_head *head = &scst_cm_sess->sess_tgt_dev_list[SESS_TGT_DEV_LIST_HASH_FN(lun)]; + struct scst_tgt_dev *tgt_dev; + + TRACE_ENTRY(); + + list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) { + if (tgt_dev->lun == lun) { + res = false; + break; + } + } + + TRACE_EXIT_RES(res); + return res; +} + +/* scst_mutex supposed to be held and activities suspended */ +static int scst_cm_dev_register(struct scst_device *dev) +{ + int res, i; + unsigned int lun; + struct scst_acg_dev *acg_dev; + + TRACE_ENTRY(); + + for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { + struct scst_tgt_dev *tgt_dev; + struct list_head *head = &scst_cm_sess->sess_tgt_dev_list[i]; + list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) { + if (tgt_dev->dev == dev) { + PRINT_ERROR("Copy Manager already registered " + "device %s", dev->virt_name); + res = -EEXIST; + goto out; + } + } + } + + while (1) { + lun = scst_cm_next_lun++; + if (lun == SCST_MAX_LUN) + continue; + if (scst_cm_is_lun_free(lun)) + break; + }; + + res = scst_acg_add_lun(scst_cm_tgt->default_acg, + scst_cm_tgt->tgt_luns_kobj, dev, lun, false, false, &acg_dev); + if (res != 0) + goto out_err; + + spin_lock_bh(&dev->dev_lock); + scst_block_dev(dev); + spin_unlock_bh(&dev->dev_lock); + + res = scst_cm_send_init_inquiry(dev, lun, NULL); + if (res != 0) + goto out_unblock; + +out: + TRACE_EXIT_RES(res); + return res; + +out_unblock: + spin_lock_bh(&dev->dev_lock); + scst_unblock_dev(dev); + spin_unlock_bh(&dev->dev_lock); + scst_acg_del_lun(scst_cm_tgt->default_acg, lun, false); + +out_err: + scst_cm_next_lun--; + goto out; +} + +/* scst_mutex supposed to be held and activities suspended */ +static void scst_cm_dev_unregister(struct scst_device *dev) +{ + int i; + struct scst_cm_desig *des, *t; + + TRACE_ENTRY(); + + list_for_each_entry_safe(des, t, &scst_cm_desig_list, cm_desig_list_entry) { + if (des->desig_tgt_dev->dev == dev) { + TRACE_DBG("Deleting des %p", des); + list_del(&des->cm_desig_list_entry); + kfree(des); + } + } + + for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { + struct scst_tgt_dev *tgt_dev; + struct list_head *head = &scst_cm_sess->sess_tgt_dev_list[i]; + list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) { + if (tgt_dev->dev == dev) { + scst_acg_del_lun(scst_cm_tgt->default_acg, + tgt_dev->lun, false); + break; + } + } + } + + TRACE_EXIT(); + return; +} + +/* scst_mutex supposed to be held and activities suspended */ +int scst_cm_on_dev_register(struct scst_device *dev) +{ + int res = 0; + + TRACE_ENTRY(); + + if (!dev->handler->auto_cm_assignment_possible) + goto out; + + res = scst_cm_dev_register(dev); + +out: + TRACE_EXIT_RES(res); + return res; +} + +/* scst_mutex supposed to be held and activities suspended */ +void scst_cm_on_dev_unregister(struct scst_device *dev) +{ + TRACE_ENTRY(); + + scst_cm_dev_unregister(dev); + + TRACE_EXIT(); + return; +} + +/* scst_mutex supposed to be locked */ +static bool scst_cm_check_access_acg(const char *initiator_name, + const struct scst_device *dev, const struct scst_acg *acg, + bool default_acg) +{ + bool res = true; + struct scst_acg_dev *acg_dev; + + TRACE_ENTRY(); + + list_for_each_entry(acg_dev, &acg->acg_dev_list, acg_dev_list_entry) { + if (acg_dev->dev == dev) { + struct scst_acn *acn; + if (default_acg) + goto found; + list_for_each_entry(acn, &acg->acn_list, acn_list_entry) { + if (strcmp(acn->name, initiator_name) == 0) + goto found; + } + } + } + + res = false; + +found: + TRACE_EXIT_RES(res); + return res; +} + +static bool scst_cm_check_access(const char *initiator_name, + const struct scst_device *dev, bool *read_only) +{ + bool res = true; + struct scst_tgt_template *tgtt; + + TRACE_ENTRY(); + + if (scst_cm_allow_not_connected_copy) + goto out_rd_only; + + /* ToDo: make it hash based */ + + /* + * We can't use scst_mutex on commands processing path, because + * otherwise we can fall in a deadlock with kthread_stop() in + * scst_del_threads() waiting for this command to finish. + */ + mutex_lock(&scst_mutex2); + + list_for_each_entry(tgtt, &scst_template_list, scst_template_list_entry) { + struct scst_tgt *tgt; + + list_for_each_entry(tgt, &tgtt->tgt_list, tgt_list_entry) { + struct scst_acg *acg; + + if (tgt == scst_cm_tgt) + continue; + + TRACE_DBG("Checking tgt %s", tgt->tgt_name); + + if (scst_cm_check_access_acg(initiator_name, dev, tgt->default_acg, true)) + goto out_unlock_rd_only; + + list_for_each_entry(acg, &tgt->tgt_acg_list, acg_list_entry) { + if (scst_cm_check_access_acg(initiator_name, dev, acg, false)) + goto out_unlock_rd_only; + } + } + } + + res = false; + PRINT_WARNING("Initiator %s not allowed to use device %s in EXTENDED " + "COPY command", initiator_name, dev->virt_name); + +out_unlock_rd_only: + mutex_unlock(&scst_mutex2); + +out_rd_only: + *read_only = dev->dev_rd_only || dev->swp; + + TRACE_EXIT_RES(res); + return res; +} + +struct scst_cm_tgt_descr { + struct scst_tgt_dev *tgt_dev; + unsigned int read_only:1; + int param_offs; +}; + +static int scst_cm_parse_id_tgt_descr(struct scst_cmd *cmd, const uint8_t *seg, + int offs, struct scst_cm_tgt_descr *tgt_descr) +{ + int res = 32; + struct scst_cm_desig *des; + int block; + bool read_only = false; + + TRACE_ENTRY(); + + TRACE_BUFF_FLAG(TRACE_DEBUG, "seg", seg, 32); + + EXTRACHECKS_BUG_ON(seg[0] != 0xE4); + + if ((seg[1] & 0xC0) != 0) { + PRINT_WARNING("LU ID %x not supported", seg[1] & 0xC0); + scst_set_invalid_field_in_parm_list(cmd, offs+1, + SCST_INVAL_FIELD_BIT_OFFS_VALID | 6); + goto out_err; + } + + if ((seg[1] & 0x20) != 0) { + TRACE_DBG("NULL tgt descriptor"); + tgt_descr->tgt_dev = NULL; + goto out; + } + + if ((seg[1] & 0xF) != 0) { + PRINT_WARNING("PERIPHERAL DEVICE TYPE %d not supported", + seg[1] & 0xF); + scst_set_invalid_field_in_parm_list(cmd, offs+1, + SCST_INVAL_FIELD_BIT_OFFS_VALID | 0); + goto out_err; + } + + block = get_unaligned_be24(&seg[29]); + + mutex_lock(&scst_cm_mutex); + + /* ToDo: make it hash based */ + + list_for_each_entry(des, &scst_cm_desig_list, cm_desig_list_entry) { + TRACE_DBG("des %p (tgt_dev %p, lun %lld)", des, des->desig_tgt_dev, + (unsigned long long)des->desig_tgt_dev->lun); + if (seg[4] != des->desig[0]) + continue; + if (seg[5] != des->desig[1]) + continue; + if (seg[7] > des->desig[3]) + continue; + if (memcmp(&des->desig[4], &seg[8], min_t(int, seg[7], des->desig[3])) == 0) { + TRACE_DBG("Tgt_dev %p (lun %lld) found", + des->desig_tgt_dev, + (unsigned long long)des->desig_tgt_dev->lun); + + mutex_unlock(&scst_cm_mutex); + + if (block != des->desig_tgt_dev->dev->block_size) { + PRINT_WARNING("Block size %d doesn't match %d", block, + des->desig_tgt_dev->dev->block_size); + scst_set_invalid_field_in_parm_list(cmd, offs+29, 0); + goto out_err; + } + + if (!scst_cm_check_access(cmd->sess->initiator_name, + des->desig_tgt_dev->dev, &read_only)) + goto out_not_found; + + tgt_descr->tgt_dev = des->desig_tgt_dev; + tgt_descr->read_only = read_only; + TRACE_DBG("Found des %p (tgt_dev %p, read_only %d)", + des, tgt_descr->tgt_dev, tgt_descr->read_only); + goto out; + } + } + + mutex_unlock(&scst_cm_mutex); + + TRACE(TRACE_MINOR|TRACE_SCSI, "Target descriptor designator not found " + "(initiator %s, offs %d)", cmd->sess->initiator_name, offs); + TRACE_BUFF_FLAG(TRACE_MINOR|TRACE_SCSI, "Designator", seg, 32); + +out_not_found: + scst_set_invalid_field_in_parm_list(cmd, offs, 0); + +out_err: + res = -1; + +out: + TRACE_EXIT_RES(res); + return res; +} + +static int scst_cm_set_seg_err_sense(struct scst_cmd *cmd, int asc, int ascq, + int seg_num, int offs) +{ + int res, d_sense = scst_get_cmd_dev_d_sense(cmd); + + TRACE_ENTRY(); + + TRACE_DBG("cmd %p, seg %d, offs %d (d_sense %d)", cmd, seg_num, offs, + d_sense); + + res = scst_set_cmd_error_status(cmd, SAM_STAT_CHECK_CONDITION); + if (res != 0) + goto out; + + res = scst_alloc_sense(cmd, 1); + if (res != 0) { + PRINT_ERROR("Lost COPY ABORTED sense data"); + goto out; + } + + sBUG_ON(cmd->sense_buflen < 18); + BUILD_BUG_ON(SCST_SENSE_BUFFERSIZE < 18); + + if (d_sense) { + /* Descriptor format */ + cmd->sense[0] = 0x72; + cmd->sense[1] = COPY_ABORTED; + cmd->sense[2] = asc; + cmd->sense[3] = ascq; + cmd->sense[7] = 20; /* additional Sense Length */ + + cmd->sense[8] = 1; /* Command specific descriptor */ + cmd->sense[9] = 0x0A; + put_unaligned_be16(seg_num, &cmd->sense[14]); + + cmd->sense[20] = 2; /* Sense key specific descriptor */ + cmd->sense[21] = 6; + cmd->sense[24] = 0xA0; + put_unaligned_be16(offs, &cmd->sense[25]); + + cmd->sense_valid_len = 28; + } else { + /* Fixed format */ + cmd->sense[0] = 0x70; + cmd->sense[2] = COPY_ABORTED; + cmd->sense[7] = 0x0a; /* additional Sense Length */ + cmd->sense[12] = asc; + cmd->sense[13] = ascq; + + put_unaligned_be16(seg_num, &cmd->sense[10]); + + cmd->sense[15] = 0xA0; + put_unaligned_be16(offs, &cmd->sense[16]); + + cmd->sense_valid_len = 18; + } + + TRACE_BUFFER("Sense set", cmd->sense, cmd->sense_valid_len); + +out: + TRACE_EXIT_RES(res); + return res; +} + +static void scst_cm_fantom_cmd_finished(struct scst_cmd *cmd) +{ + TRACE_ENTRY(); + + TRACE_DBG("Fantom cmd %p", cmd); + + EXTRACHECKS_BUG_ON(!cmd->internal); + + /* Nothing to do */ + + TRACE_EXIT(); + return; +} + +static int scst_cm_add_to_descr_list(struct scst_cmd *ec_cmd, + struct scst_tgt_dev *tgt_dev) +{ + int res; + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + struct scst_cm_dev_entry *e, *t; + struct scst_cmd *fcmd; + bool added; + + TRACE_ENTRY(); + + /* Check if we already have this device in the list */ + list_for_each_entry(e, &priv->cm_sorted_devs_list, cm_sorted_devs_list_entry) { + if (e->cm_fcmd->dev == tgt_dev->dev) { + TRACE_DBG("Dev %p is already in cm_sorted_devs_list", + tgt_dev->dev); + goto out_success; + } + } + + e = kzalloc(sizeof(*e), GFP_KERNEL); + if (e == NULL) { + PRINT_ERROR("Unable to allocate scst_cm_dev_entry (size %d)", + (int)sizeof(*e)); + goto out_enomem; + } + + if (ec_cmd->dev == tgt_dev->dev) { + fcmd = ec_cmd; + goto skip_fcmd_create; + } + + fcmd = __scst_create_prepare_internal_cmd(ec_cmd->cdb, + ec_cmd->cdb_len, SCST_CMD_QUEUE_SIMPLE, tgt_dev, + GFP_KERNEL, true); + if (fcmd == NULL) { + goto out_enomem_free_e; + } + + fcmd->expected_data_direction = ec_cmd->expected_data_direction; + fcmd->expected_transfer_len_full = ec_cmd->expected_transfer_len_full; + fcmd->expected_values_set = 1; + + fcmd->cmd_data_descriptors = ec_cmd->cmd_data_descriptors; + fcmd->cmd_data_descriptors_cnt = ec_cmd->cmd_data_descriptors_cnt; + + fcmd->state = SCST_CMD_STATE_EXEC_CHECK_BLOCKING; + + res = scst_cm_add_to_internal_cmd_list(fcmd, ec_cmd, ec_cmd, + scst_cm_fantom_cmd_finished); + if (res != 0) + goto out_free_cmd; + +skip_fcmd_create: + TRACE_DBG("ec_cmd %p, e %p, fcmd %p, tgt_dev %p (dev %p)", ec_cmd, e, fcmd, + tgt_dev, tgt_dev->dev); + + e->cm_fcmd = fcmd; + + added = false; + list_for_each_entry_reverse(t, &priv->cm_sorted_devs_list, cm_sorted_devs_list_entry) { + EXTRACHECKS_BUG_ON(t->cm_fcmd->dev == tgt_dev->dev); + if (((unsigned long)e->cm_fcmd->dev) > ((unsigned long)t->cm_fcmd->dev)) { + __list_add(&e->cm_sorted_devs_list_entry, + &t->cm_sorted_devs_list_entry, + t->cm_sorted_devs_list_entry.next); + added = true; + break; + } + } + if (!added) + list_add(&e->cm_sorted_devs_list_entry, + &priv->cm_sorted_devs_list); + +#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_EXTRACHECKS) + { + struct scst_cm_dev_entry *tp = NULL; + list_for_each_entry(t, &priv->cm_sorted_devs_list, cm_sorted_devs_list_entry) { + TRACE_DBG("t %p, cm dev %p", t, t->cm_fcmd->dev); + if (tp != NULL) { + if (((unsigned long)t->cm_fcmd->dev) <= ((unsigned long)tp->cm_fcmd->dev)) { + list_for_each_entry(t, &priv->cm_sorted_devs_list, cm_sorted_devs_list_entry) { + printk(KERN_EMERG "%s: t %p, cm dev %p\n", __func__, + t, t->cm_fcmd->dev); + } + sBUG(); + break; + } + tp = t; + } + tp = t; + } + } +#endif + +out_success: + res = 0; + +out: + TRACE_EXIT_RES(res); + return res; + +out_free_cmd: + __scst_cmd_put(fcmd); + +out_enomem_free_e: + kfree(e); + +out_enomem: + scst_set_busy(ec_cmd); + res = -ENOMEM; + goto out; +} + +static int scst_cm_parse_b2b_seg_descr(struct scst_cmd *ec_cmd, + const uint8_t *seg, const struct scst_cm_tgt_descr *tgt_descrs, + int tgt_descrs_cnt, int seg_num) +{ + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + struct scst_ext_copy_seg_descr *d = &priv->cm_seg_descrs[seg_num]; + int res = 28, rc, len, src_des_idx, tgt_des_idx, blocks, dc; + const struct scst_cm_tgt_descr *src_des, *tgt_des; + + TRACE_ENTRY(); + + TRACE_BUFF_FLAG(TRACE_DEBUG, "seg", seg, 28); + + EXTRACHECKS_BUG_ON(seg[0] != 2); + + len = get_unaligned_be16(&seg[2]); + if (len != 0x18) { + PRINT_WARNING("Incorrect B2B segment descriptor len %d", len); + scst_cm_set_seg_err_sense(ec_cmd, 0, 0, seg_num, 2); + goto out_err; + } + + src_des_idx = get_unaligned_be16(&seg[4]); + if (src_des_idx >= tgt_descrs_cnt) { + PRINT_WARNING("Invalid src descriptor index %d", src_des_idx); + scst_cm_set_seg_err_sense(ec_cmd, 0, 0, seg_num, 4); + goto out_err; + } + + src_des = &tgt_descrs[src_des_idx]; + if (src_des->tgt_dev == NULL) { + PRINT_WARNING("Segment with NULL src tgt device"); + /* COPY TARGET DEVICE NOT REACHABLE */ + scst_cm_set_seg_err_sense(ec_cmd, 0xD, 2, seg_num, 4); + goto out_err; + } + + tgt_des_idx = get_unaligned_be16(&seg[6]); + if (tgt_des_idx >= tgt_descrs_cnt) { + PRINT_WARNING("Invalid tgt descriptor index %d", tgt_des_idx); + scst_cm_set_seg_err_sense(ec_cmd, 0, 0, seg_num, 6); + goto out_err; + } + + tgt_des = &tgt_descrs[tgt_des_idx]; + if (tgt_des->tgt_dev == NULL) { + PRINT_WARNING("Segment with NULL tgt device"); + /* COPY TARGET DEVICE NOT REACHABLE */ + scst_cm_set_seg_err_sense(ec_cmd, 0xD, 2, seg_num, 6); + goto out_err; + } + if (tgt_des->read_only){ + PRINT_WARNING("Target descriptor refers to read-only device"); + scst_cm_set_seg_err_sense(ec_cmd, 0, 0, seg_num, 6); + goto out_err; + } + + dc = (seg[1] >> 1) & 1; + blocks = get_unaligned_be16(&seg[10]); + if (dc) + len = blocks << tgt_des->tgt_dev->dev->block_shift; + else + len = blocks << src_des->tgt_dev->dev->block_shift; + + if (unlikely((len & (src_des->tgt_dev->dev->block_size-1)) != 0) || + unlikely((len & (tgt_des->tgt_dev->dev->block_size-1)) != 0)) { + PRINT_WARNING("Data len %d is not even for block size (src block " + "size %d, dst block size %d)", len, + src_des->tgt_dev->dev->block_size, + tgt_des->tgt_dev->dev->block_size); + scst_cm_set_seg_err_sense(ec_cmd, 0, 0, seg_num, 10); + goto out_err; + } + + d->type = SCST_EXT_COPY_SEG_DATA; + d->data_descr.data_len = len; + d->src_tgt_dev = src_des->tgt_dev; + d->data_descr.src_lba = get_unaligned_be64(&seg[12]); + d->dst_tgt_dev = tgt_des->tgt_dev; + d->data_descr.dst_lba = get_unaligned_be64(&seg[20]); + d->tgt_descr_offs = tgt_des->param_offs; + + TRACE(TRACE_DEBUG|TRACE_SCSI, "ec_cmd %p, src dev %s, dst dev %s, " + "len %d, src_lba %lld, dst_lba %lld", ec_cmd, + d->src_tgt_dev->dev->virt_name, d->dst_tgt_dev->dev->virt_name, + len, (long long)d->data_descr.src_lba, + (long long)d->data_descr.dst_lba); + + TRACE_DBG("src tgt_dev %p, dst tgt_dev %p, tgt_descr_offs %d", + d->src_tgt_dev, d->dst_tgt_dev, d->tgt_descr_offs); + + rc = scst_cm_add_to_descr_list(ec_cmd, src_des->tgt_dev); + if (rc != 0) { + res = rc; + goto out; + } + + rc = scst_cm_add_to_descr_list(ec_cmd, tgt_des->tgt_dev); + if (rc != 0) { + res = rc; + goto out; + } + +out: + TRACE_EXIT_RES(res); + return res; + +out_err: + res = -1; + goto out; +} + +static void scst_cm_free_ec_priv(struct scst_cmd *ec_cmd, bool unblock_dev) +{ + struct scst_cm_ec_cmd_priv *p = ec_cmd->cmd_data_descriptors; + struct scst_cm_dev_entry *e, *t; + struct scst_cm_internal_cmd_priv *ip, *it; + unsigned long flags; + + TRACE_ENTRY(); + + list_for_each_entry_safe(e, t, &p->cm_sorted_devs_list, + cm_sorted_devs_list_entry) { + TRACE_DBG("Deleting e %p", e); + list_del(&e->cm_sorted_devs_list_entry); + kfree(e); + } + + list_for_each_entry_safe(ip, it, &p->cm_internal_cmd_list, + cm_internal_cmd_list_entry) { + struct scst_cmd *c = ip->cm_cmd; + scst_cm_del_free_from_internal_cmd_list(c, unblock_dev); + __scst_cmd_put(c); + } + + /* Lock to sync with scst_cm_abort_ec_cmd() */ + spin_lock_irqsave(&scst_cm_lock, flags); + ec_cmd->cmd_data_descriptors = NULL; + ec_cmd->cmd_data_descriptors_cnt = 0; + spin_unlock_irqrestore(&scst_cm_lock, flags); + + kfree(p); + + TRACE_EXIT(); + return; +} + +int scst_cm_parse_descriptors(struct scst_cmd *ec_cmd) +{ + int res = 0, rc; + struct scst_cm_list_id *plist_id = NULL; + ssize_t length = 0; + uint8_t *buf; + int list_id, list_id_usage, len, tgt_len, seg_len; + struct scst_cm_ec_cmd_priv *p; + int tgt_cnt, seg_cnt, i, offs, t; + struct scst_cm_tgt_descr *tgt_descrs; + + TRACE_ENTRY(); + + EXTRACHECKS_BUG_ON(ec_cmd->cmd_data_descriptors != NULL); + + length = scst_get_buf_full_sense(ec_cmd, &buf); + if (unlikely(length <= 0)) { + if (length == 0) + goto out_put; + else + goto out_abn; + } + + if (length < 16) { + PRINT_WARNING("Too small EXTENDED COPY data len %d", (int)length); + scst_set_invalid_field_in_cdb(ec_cmd, 10, 0); + goto out_abn_put; + } + + list_id = buf[0]; + list_id_usage = (buf[1] & 0x18) >> 3; + + TRACE_BUFF_FLAG(TRACE_DEBUG, "buf", buf, length); + + TRACE_DBG("list_id %d, list_id_usage %d", list_id, list_id_usage); + + switch (list_id_usage) { + case 0: + case 2: + plist_id = scst_cm_add_list_id(ec_cmd, list_id); + if (plist_id == NULL) + goto out_abn_put; + break; + case 3: + if (list_id != 0) { + PRINT_WARNING("Invalid list ID %d with list ID usage %d", + list_id, list_id_usage); + scst_set_invalid_field_in_parm_list(ec_cmd, 0, 0); + goto out_abn_put; + } + break; + default: + PRINT_WARNING("Invalid list ID usage %d, rejecting", list_id_usage); + scst_set_invalid_field_in_parm_list(ec_cmd, 1, + SCST_INVAL_FIELD_BIT_OFFS_VALID | 3); + goto out_abn_put; + } + + len = get_unaligned_be32(&buf[12]); + if (len != 0) { + PRINT_WARNING("Inline data not supported (len %d)", len); + scst_set_cmd_error(ec_cmd, SCST_LOAD_SENSE(scst_sense_inline_data_length_exceeded)); + goto out_del_abn_put; + } + + tgt_len = get_unaligned_be16(&buf[2]); + seg_len = get_unaligned_be32(&buf[8]); + + if (tgt_len == 0) { + if (seg_len == 0) + goto out_del_put; + else { + PRINT_WARNING("Zero target descriptors with non-zero " + "segments len (%d)", seg_len); + scst_set_invalid_field_in_parm_list(ec_cmd, 2, 0); + goto out_del_abn_put; + } + } + + if ((tgt_len + seg_len + 16) > length) { + PRINT_WARNING("Parameters truncation"); + scst_set_cmd_error(ec_cmd, + SCST_LOAD_SENSE(scst_sense_parameter_list_length_invalid)); + goto out_del_abn_put; + } + + if ((tgt_len + seg_len + 16) != length) { + PRINT_WARNING("Unexpected inline data"); + scst_set_cmd_error(ec_cmd, + SCST_LOAD_SENSE(scst_sense_inline_data_length_exceeded)); + goto out_del_abn_put; + } + + if ((tgt_len % 32) != 0) { + PRINT_WARNING("Invalid tgt len %d", tgt_len); + scst_set_invalid_field_in_parm_list(ec_cmd, 2, 0); + goto out_del_abn_put; + } + + tgt_cnt = tgt_len/32; + if (tgt_cnt > SCST_CM_MAX_TGT_DESCR_CNT) { + PRINT_WARNING("Too many target descriptors %d", tgt_cnt); + scst_set_cmd_error(ec_cmd, + SCST_LOAD_SENSE(scst_sense_too_many_target_descriptors)); + goto out_del_abn_put; + } + + TRACE_DBG("tgt_cnt %d", tgt_cnt); + + tgt_descrs = kcalloc(tgt_cnt, sizeof(*tgt_descrs), GFP_KERNEL); + if (tgt_descrs == NULL) { + TRACE(TRACE_OUT_OF_MEM, "Unable to allocate tgt_descrs " + "(count %d, size %zd)", tgt_cnt, + sizeof(*tgt_descrs) * tgt_cnt); + scst_set_busy(ec_cmd); + goto out_del_abn_put; + } + + offs = 16; + for (i = 0; i < tgt_cnt; i++) { + TRACE_DBG("offs %d", offs); + switch (buf[offs]) { + case 0xE4: /* identification descriptor target descriptor format */ + rc = scst_cm_parse_id_tgt_descr(ec_cmd, &buf[offs], offs, + &tgt_descrs[i]); + if (rc <= 0) + goto out_free_tgt_descr; + break; + default: + PRINT_WARNING("Not supported target descriptor %x", buf[offs]); + scst_set_cmd_error(ec_cmd, SCST_LOAD_SENSE(scst_sense_unsupported_tgt_descr_type)); + goto out_free_tgt_descr; + } + tgt_descrs[i].param_offs = offs; + offs += rc; + } + + WARN_ON(offs != tgt_len + 16); + + t = offs; + seg_cnt = 0; + while (offs < length) { + if (seg_cnt == SCST_CM_MAX_SEG_DESCR_CNT) { + PRINT_WARNING("Too many segment descriptors"); + scst_set_cmd_error(ec_cmd, + SCST_LOAD_SENSE( + scst_sense_too_many_segment_descriptors)); + goto out_free_tgt_descr; + } + switch (buf[offs]) { + case 2: /* block device to block device segment descriptor */ + offs += 28; + break; + default: + PRINT_WARNING("Not supported segment descriptor %x", buf[offs]); + scst_set_cmd_error(ec_cmd, + SCST_LOAD_SENSE(scst_sense_unsupported_seg_descr_type)); + goto out_free_tgt_descr; + } + seg_cnt++; + } + offs = t; + + TRACE_DBG("seg_cnt %d", seg_cnt); + + p = kzalloc(sizeof(*p) + seg_cnt * sizeof(struct scst_ext_copy_seg_descr), GFP_KERNEL); + if (p == NULL) { + TRACE(TRACE_OUT_OF_MEM, "Unable to allocate Extended Copy " + "descriptors (seg_cnt %d)", seg_cnt); + scst_set_busy(ec_cmd); + goto out_free_tgt_descr; + } + + p->cm_list_id = plist_id; + plist_id = NULL; + INIT_LIST_HEAD(&p->cm_sorted_devs_list); + INIT_LIST_HEAD(&p->cm_internal_cmd_list); + p->cm_error = SCST_CM_ERROR_NONE; + mutex_init(&p->cm_mutex); + + ec_cmd->cmd_data_descriptors = p; + ec_cmd->cmd_data_descriptors_cnt = seg_cnt; + + res = scst_cm_add_to_descr_list(ec_cmd, ec_cmd->tgt_dev); + if (res != 0) + goto out_free_p; + + for (i = 0; i < seg_cnt; i++) { + TRACE_DBG("offs %d", offs); + switch (buf[offs]) { + case 2: /* block device to block device segment descriptor */ + rc = scst_cm_parse_b2b_seg_descr(ec_cmd, &buf[offs], + tgt_descrs, tgt_cnt, i); + if (rc <= 0) { + if (rc == -ENOMEM) + goto out_free_p; + else { + /* + * We may need to keep list_id for a + * while for further FAILED SEGMENT + * DETAILS of RECEIVE COPY RESULTS + */ + scst_cm_store_list_id_details(ec_cmd); + goto out_free_tgt_descr; + } + } + EXTRACHECKS_BUG_ON(rc != 28); + break; + default: + sBUG(); + } + offs += rc; + } + + kfree(tgt_descrs); + +out_del_put: + if (plist_id != NULL) + scst_cm_del_free_list_id(plist_id, false); + +out_put: + scst_put_buf_full(ec_cmd, buf); + +out: + TRACE_EXIT_RES(res); + return res; + +out_free_p: + scst_cm_free_ec_priv(ec_cmd, false); + +out_free_tgt_descr: + kfree(tgt_descrs); + +out_del_abn_put: + if (plist_id != NULL) + scst_cm_del_free_list_id(plist_id, false); + +out_abn_put: + scst_put_buf_full(ec_cmd, buf); + +out_abn: + scst_set_cmd_abnormal_done_state(ec_cmd); + res = -1; + goto out; +} + +void scst_cm_free_descriptors(struct scst_cmd *ec_cmd) +{ + struct scst_cm_ec_cmd_priv *priv = ec_cmd->cmd_data_descriptors; + + TRACE_ENTRY(); + + TRACE_DBG("cmd %p (internal %d)", ec_cmd, ec_cmd->internal); + + if (priv == NULL) { + /* It can be for early errors */ + goto out; + } + + if (ec_cmd->internal) + goto out; + + if (priv->cm_list_id != NULL) + scst_cm_sched_del_list_id(ec_cmd); + + scst_cm_free_ec_priv(ec_cmd, true); + +out: + TRACE_EXIT(); + return; +} + +#ifndef CONFIG_SCST_PROC + +static ssize_t scst_cm_allow_not_conn_copy_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + ssize_t res; + + TRACE_ENTRY(); + + res = sprintf(buf, "%d\n%s", scst_cm_allow_not_connected_copy, + (scst_cm_allow_not_connected_copy == SCST_ALLOW_NOT_CONN_COPY_DEF) ? + "" : SCST_SYSFS_KEY_MARK "\n"); + + TRACE_EXIT_RES(res); + return res; +} + +static ssize_t scst_cm_allow_not_conn_copy_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buffer, size_t size) +{ + ssize_t res; + unsigned long val; + + TRACE_ENTRY(); + + res = kstrtoul(buffer, 0, &val); + if (res != 0) { + PRINT_ERROR("strtoul() for %s failed: %zd", buffer, res); + goto out; + } + + scst_cm_allow_not_connected_copy = val; + + res = size; + +out: + TRACE_EXIT_RES(res); + return res; +} + +static struct kobj_attribute scst_cm_allow_not_conn_copy_attr = + __ATTR(allow_not_connected_copy, S_IRUGO|S_IWUSR, + scst_cm_allow_not_conn_copy_show, + scst_cm_allow_not_conn_copy_store); + +static ssize_t scst_cm_mgmt_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + static const char help[] = + "Usage: echo \"add H:C:I:L\" >mgmt\n" + " echo \"add VNAME\" >mgmt\n" + " echo \"del H:C:I:L\" >mgmt\n" + " echo \"del VNAME\" >mgmt\n"; + + return sprintf(buf, "%s", help); +} + +static int scst_cm_mgmt(struct scst_sysfs_work_item *work) +{ + int res = 0; + char *pp, *action, *devstr; + unsigned int host, channel, id, lun; + char *buf = work->buf; + bool vdev; + struct scst_device *d, *dev = NULL; + + TRACE_ENTRY(); + + TRACE_DBG("buffer %s", buf); + + pp = buf; + action = scst_get_next_lexem(&pp); + devstr = scst_get_next_lexem(&pp); + if (*devstr == '\0') { + PRINT_ERROR("%s", "Device required"); + res = -EINVAL; + goto out; + } + + if (*scst_get_next_lexem(&pp) != '\0') { + PRINT_ERROR("%s", "Too many parameters"); + res = -EINVAL; + goto out_syntax_err; + } + + if (sscanf(devstr, "%u:%u:%u:%u", &host, &channel, &id, &lun) != 4) { + vdev = true; + TRACE_DBG("Virt dev %s", devstr); + } else { + vdev = false; + TRACE_DBG("Pass-through dev %d:%d:%d:%d", host, channel, id, lun); + } + + res = scst_suspend_activity(SCST_SUSPEND_TIMEOUT_USER); + if (res != 0) + goto out; + + res = mutex_lock_interruptible(&scst_mutex); + if (res != 0) + goto out_resume; + + list_for_each_entry(d, &scst_dev_list, dev_list_entry) { + if (vdev) { + if ((d->scsi_dev == NULL) && + (strcmp(d->virt_name, devstr) == 0)) { + dev = d; + break; + } + } else if (d->scsi_dev != NULL && + d->scsi_dev->host->host_no == host && + d->scsi_dev->channel == channel && + d->scsi_dev->id == id && + d->scsi_dev->lun == lun) { + dev = d; + break; + } + } + if (dev == NULL) { + PRINT_ERROR("Device %s not found", devstr); + res = -EINVAL; + goto out_unlock; + } else + TRACE_DBG("Dev %p (%s) found", dev, dev->virt_name); + + if (strcasecmp("add", action) == 0) + res = scst_cm_dev_register(dev); + else if (strcasecmp("del", action) == 0) + scst_cm_dev_unregister(dev); + else { + PRINT_ERROR("Action %s not understood", action); + res = -EINVAL; + } + +out_unlock: + mutex_unlock(&scst_mutex); + +out_resume: + scst_resume_activity(); + +out: + TRACE_EXIT_RES(res); + return res; + +out_syntax_err: + PRINT_ERROR("Syntax error on \"%s\"", buf); + res = -EINVAL; + goto out; +} + +static ssize_t scst_cm_mgmt_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buffer, size_t size) +{ + int res; + struct scst_sysfs_work_item *work; + char *i_buf; + + TRACE_ENTRY(); + + i_buf = kasprintf(GFP_KERNEL, "%.*s", (int)size, buffer); + if (i_buf == NULL) { + PRINT_ERROR("Unable to alloc intermediate buffer with size %zd", + size+1); + res = -ENOMEM; + goto out; + } + + res = scst_alloc_sysfs_work(scst_cm_mgmt, false, &work); + if (res != 0) + goto out; + + work->buf = i_buf; + + res = scst_sysfs_queue_wait_work(work); + if (res == 0) + res = size; + +out: + TRACE_EXIT_RES(res); + return res; +} + +static struct kobj_attribute scst_cm_mgmt_attr = + __ATTR(mgmt, S_IRUGO|S_IWUSR, scst_cm_mgmt_show, scst_cm_mgmt_store); + +static const struct attribute *scst_cm_attrs[] = { + &scst_cm_allow_not_conn_copy_attr.attr, + &scst_cm_mgmt_attr.attr, + NULL, +}; + +#endif /* #ifndef CONFIG_SCST_PROC */ + +static int scst_cm_get_initiator_port_transport_id(struct scst_tgt *tgt, + struct scst_session *scst_sess, uint8_t **transport_id) +{ + int res = 0; + uint8_t *tid = NULL; + + TRACE_ENTRY(); + + BUILD_BUG_ON((sizeof(SCST_CM_TID_ID)+3) > SCST_CM_TID_SIZE); + BUILD_BUG_ON(TID_COMMON_SIZE != SCST_CM_TID_SIZE); + + if (scst_sess == NULL) { + res = SCST_TRANSPORTID_PROTOCOLID_COPY_MGR; + goto out; + } + + tid = kzalloc(SCST_CM_TID_SIZE, GFP_KERNEL); + if (tid == NULL) { + PRINT_ERROR("Allocation of TransportID (size %d) failed", + SCST_CM_TID_SIZE); + res = -ENOMEM; + goto out; + } + + tid[0] = SCST_TRANSPORTID_PROTOCOLID_COPY_MGR; + memcpy(&tid[2], SCST_CM_TID_ID, sizeof(SCST_CM_TID_ID)); + + *transport_id = tid; + +out: + TRACE_EXIT_RES(res); + return res; +} + +static int scst_cm_release(struct scst_tgt *tgt) +{ + TRACE_ENTRY(); + TRACE_EXIT(); + return 0; +} + +static int scst_cm_xmit_response(struct scst_cmd *cmd) +{ + int res = SCST_TGT_RES_SUCCESS; + scst_i_finish_fn_t f = (void *) *((unsigned long long **)cmd->tgt_i_priv); + + TRACE_ENTRY(); + + /* + * Used for CM-only generated commands, i.e. commands generated without + * any external command, like INQUIRY. + */ + + f(cmd); + scst_tgt_cmd_done(cmd, SCST_CONTEXT_SAME); + + TRACE_EXIT_RES(res); + return res; +} + +static void scst_cm_task_mgmt_fn_done(struct scst_mgmt_cmd *scst_mcmd) +{ + /* Nothing to do */ + return; +} + +static struct scst_tgt_template scst_cm_tgtt = { + .name = SCST_CM_NAME, +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 20) + .sg_tablesize = SG_MAX_SINGLE_ALLOC, +#else + .sg_tablesize = 0xffff, +#endif +#ifndef CONFIG_SCST_PROC + .enabled_attr_not_needed = 1, +#endif + .dif_supported = 1, + .hw_dif_type1_supported = 1, + .hw_dif_type2_supported = 1, + .hw_dif_type3_supported = 1, + .release = scst_cm_release, + .xmit_response = scst_cm_xmit_response, + .task_mgmt_fn_done = scst_cm_task_mgmt_fn_done, + .get_initiator_port_transport_id = scst_cm_get_initiator_port_transport_id, +#ifndef CONFIG_SCST_PROC + .tgtt_attrs = scst_cm_attrs, +#endif +}; + +int __init scst_cm_init(void) +{ + int res = 0; + + TRACE_ENTRY(); + + spin_lock_init(&scst_cm_lock); + + res = scst_register_target_template(&scst_cm_tgtt); + if (res != 0) { + PRINT_ERROR("Unable to register copy manager template: %d", res); + goto out; + } + + scst_cm_tgt = scst_register_target(&scst_cm_tgtt, SCST_CM_TGT_NAME); + if (scst_cm_tgt == NULL) { + PRINT_ERROR("%s", "scst_register_target() failed"); + res = -EFAULT; + goto out_unreg_tgtt; + } + + scst_cm_sess = scst_register_session(scst_cm_tgt, false, + SCST_CM_SESS_NAME, NULL, NULL, NULL); + if (scst_cm_sess == NULL) { + PRINT_ERROR("%s", "scst_register_session() failed"); + res = -EFAULT; + goto out_unreg_tgt; + } + +out: + TRACE_EXIT_RES(res); + return res; + +out_unreg_tgt: + scst_unregister_target(scst_cm_tgt); + +out_unreg_tgtt: + scst_unregister_target_template(&scst_cm_tgtt); + goto out; +} + +void __exit scst_cm_exit(void) +{ + TRACE_ENTRY(); + + scst_unregister_session(scst_cm_sess, true, NULL); + scst_unregister_target(scst_cm_tgt); + scst_unregister_target_template(&scst_cm_tgtt); + + TRACE_EXIT(); + return; +} + +#endif /* #ifndef CONFIG_SCST_PROC */ diff --git a/scst/src/scst_debug.c b/scst/src/scst_debug.c index e470cce10..176e6dcad 100644 --- a/scst/src/scst_debug.c +++ b/scst/src/scst_debug.c @@ -232,6 +232,10 @@ const char *debug_transport_id_to_initiator_name(const uint8_t *transport_id) transport_id[8], transport_id[9], transport_id[10], transport_id[11]); break; + case SCST_TRANSPORTID_PROTOCOLID_COPY_MGR: + scnprintf(name_buf, SIZEOF_NAME_BUF, + "%s", &transport_id[2]); + break; default: scnprintf(name_buf, SIZEOF_NAME_BUF, "(Not known protocol ID %x)", transport_id[0] & 0x0f); diff --git a/scst/src/scst_event.c b/scst/src/scst_event.c new file mode 100644 index 000000000..368f726c5 --- /dev/null +++ b/scst/src/scst_event.c @@ -0,0 +1,1107 @@ +/* + * scst_event.c + * + * Copyright (C) 2014 - 2015 SanDisk Corporation + * + */ + +#include +#include +#include +#include +#include +#include + +#ifdef INSIDE_KERNEL_TREE +#include +#include +#else +#include "scst.h" +#include "scst_event.h" +#endif + +#include "scst_priv.h" + +static struct class *scst_event_sysfs_class; + +static int scst_event_major; + +#define SCST_MAX_EVENTS 2048 +#define SCST_MAX_PAYLOAD 3*1024 +#define SCST_DEFAULT_EVENT_TIMEOUT (60*HZ) + +struct scst_event_priv { + struct list_head privs_list_entry; + int allowed_events_cnt; + int queued_events_cnt; + unsigned int going_to_exit:1; + unsigned int blocking:1; + pid_t owner_pid; + /* + * WARNING: payloads in events in the allowed list queued AS IS from the + * user space, so they are UNTRUSTED and can NOT be used in any other + * way, except as BLOBs for comparison! + */ + struct list_head allowed_events_list; + struct list_head queued_events_list; + wait_queue_head_t queued_events_waitQ; + struct list_head processing_events_list; +}; + +static DEFINE_MUTEX(scst_event_mutex); +static LIST_HEAD(scst_event_privs_list); + +/* + * Compares events e1_wild and e2, where e1_wild can have wildcard matching, + * i.e.: + * - event_code 0 - any event code + * - issuer_name "*" - any issuer name + * - payload_len 0 - any payload + */ +static bool scst_event_cmp(const struct scst_event *e1_wild, + const struct scst_event *e2) +{ + int res = false; + + TRACE_ENTRY(); + + if ((e1_wild->event_code != e2->event_code) && + (e1_wild->event_code != 0)) + goto out; + + if ((strcmp(e1_wild->issuer_name, e2->issuer_name) != 0) && + (strcmp(e1_wild->issuer_name, "*") != 0)) + goto out; + + if (e1_wild->payload_len == 0) + goto out_true; + + if ((e1_wild->payload_len != e2->payload_len) || + (memcmp(e1_wild->payload, e2->payload, e1_wild->payload_len) != 0)) + goto out; + +out_true: + res = true; + +out: + TRACE_EXIT_RES(res); + return res; +} + +static void scst_event_timeout_fn(struct delayed_work *work) +{ + struct scst_event_entry *event_entry = container_of(work, + struct scst_event_entry, event_timeout_work); + + TRACE_ENTRY(); + + TRACE_MGMT_DBG("Timeout of event %d (issuer %s, id %u, entry %p)", + event_entry->event.event_code, event_entry->event.issuer_name, + event_entry->event.event_id, event_entry); + + mutex_lock(&scst_event_mutex); + + if (list_empty(&event_entry->events_list_entry)) { + /* It's done already and about to be freed */ + mutex_unlock(&scst_event_mutex); + goto out; + } + list_del_init(&event_entry->events_list_entry); + + (*event_entry->pqueued_events_cnt)--; + + mutex_unlock(&scst_event_mutex); + + TRACE_DBG("Calling notify_fn of event_entry %p", event_entry); + event_entry->event_notify_fn(&event_entry->event, + event_entry->notify_fn_priv, -ETIMEDOUT); + + TRACE_MEM("Freeing event entry %p", event_entry); + kfree(event_entry); + +out: + TRACE_EXIT(); + return; +} + +static int scst_clone_event(const struct scst_event_entry *orig_entry, + struct scst_event_entry **new_event_entry) +{ + int res = 0; + const struct scst_event *event = &orig_entry->event; + struct scst_event_entry *event_entry; + int event_entry_len = sizeof(*event_entry) + event->payload_len; + + TRACE_ENTRY(); + + event_entry = kzalloc(event_entry_len, GFP_KERNEL); + if (event_entry == NULL) { + PRINT_ERROR("Unable to clone event entry (size %d, event %d, " + "issuer %s", event_entry_len, event->event_code, + event->issuer_name); + res = -ENOMEM; + goto out; + } + + TRACE_MEM("event_entry %p (len %d) allocated", event_entry, event_entry_len); + + memcpy(&event_entry->event, event, sizeof(*event) + event->payload_len); + + WARN_ON(orig_entry->event_notify_fn != NULL); + + *new_event_entry = event_entry; + +out: + TRACE_EXIT_RES(res); + return res; +} + +static void __scst_event_queue(struct scst_event_entry *event_entry) +{ + const struct scst_event *event = &event_entry->event; + struct scst_event_priv *priv; + struct scst_event_entry *allowed_entry; + bool queued = false; + int rc = 0; + static atomic_t base_event_id = ATOMIC_INIT(0); + + TRACE_ENTRY(); + + mutex_lock(&scst_event_mutex); + + list_for_each_entry(priv, &scst_event_privs_list, privs_list_entry) { + list_for_each_entry(allowed_entry, &priv->allowed_events_list, + events_list_entry) { + if (scst_event_cmp(&allowed_entry->event, event)) { + struct scst_event_entry *new_event_entry; + + if (priv->queued_events_cnt >= SCST_MAX_EVENTS) { + PRINT_ERROR("Too many queued events %d, " + "event %d, issuer %s is lost.", + priv->queued_events_cnt, + event->event_code, + event->issuer_name); + rc = -EMFILE; + break; + } + + if (!queued) + new_event_entry = event_entry; + else if (event_entry->event_notify_fn == NULL) { + rc = scst_clone_event(event_entry, &new_event_entry); + if (rc != 0) + goto done; + } else { + PRINT_WARNING("Event %d can be queued only once, " + "dublicated receiver pid %d will miss it!", + event->event_code, priv->owner_pid); + break; + } + + INIT_DELAYED_WORK(&new_event_entry->event_timeout_work, + (void (*)(struct work_struct *))scst_event_timeout_fn); + if (new_event_entry->event_notify_fn != NULL) { + new_event_entry->event.event_id = atomic_inc_return(&base_event_id); + if (new_event_entry->event_timeout == 0) + new_event_entry->event_timeout = SCST_DEFAULT_EVENT_TIMEOUT; + schedule_delayed_work(&new_event_entry->event_timeout_work, + new_event_entry->event_timeout); + } + + list_add_tail(&new_event_entry->events_list_entry, + &priv->queued_events_list); + priv->queued_events_cnt++; + new_event_entry->pqueued_events_cnt = &priv->queued_events_cnt; + queued = true; + + TRACE_DBG("event %d queued (issuer %s, id %u, " + "entry %p)", new_event_entry->event.event_code, + new_event_entry->event.issuer_name, + new_event_entry->event.event_id, new_event_entry); + + wake_up_all(&priv->queued_events_waitQ); + break; + } + } + } +done: + mutex_unlock(&scst_event_mutex); + + if (!queued) { + if (event_entry->event_notify_fn != NULL) { + if (rc == 0) + rc = -ENOENT; + TRACE_DBG("Calling notify_fn of event_entry %p (rc %d)", + event_entry, rc); + event_entry->event_notify_fn(&event_entry->event, + event_entry->notify_fn_priv, rc); + } + + TRACE_MEM("Freeing orphan event entry %p", event_entry); + kfree(event_entry); + } + + TRACE_EXIT(); + return; +} + +static void scst_event_queue_work_fn(struct work_struct *work) +{ + struct scst_event_entry *e = container_of(work, + struct scst_event_entry, scst_event_queue_work); + + TRACE_ENTRY(); + + __scst_event_queue(e); + + TRACE_EXIT(); + return; +} + +/* Can be called on IRQ with any lock held */ +void scst_event_queue(uint32_t event_code, const char *issuer_name, + struct scst_event_entry *e) +{ + TRACE_ENTRY(); + + INIT_WORK(&e->scst_event_queue_work, scst_event_queue_work_fn); + + TRACE_DBG("Scheduling event entry %p", e); + + e->event.event_code = event_code; + strlcpy(e->event.issuer_name, issuer_name, sizeof(e->event.issuer_name)); + + schedule_work(&e->scst_event_queue_work); + + TRACE_EXIT(); + return; +} +EXPORT_SYMBOL_GPL(scst_event_queue); + +/* Might be called on IRQ with any lock held */ +int scst_event_queue_lun_not_found(const struct scst_cmd *cmd) +{ + int res = 0, event_entry_len; + struct scst_event_entry *event_entry; + struct scst_event *event; + struct scst_event_lun_not_found_payload *payload; + + TRACE_ENTRY(); + + event_entry_len = sizeof(*event_entry) + sizeof(*payload); + event_entry = kzalloc(event_entry_len, GFP_ATOMIC); + if (event_entry == NULL) { + PRINT_ERROR("Unable to allocate event (size %d). LUN not found " + "event is lost (LUN %lld, initiator %s, target %s)!", + event_entry_len, (unsigned long long int)cmd->lun, + cmd->sess->initiator_name, cmd->tgt->tgt_name); + res = -ENOMEM; + goto out; + } + + TRACE_MEM("event_entry %p (len %d) allocated", event_entry, + event_entry_len); + + event = &event_entry->event; + + event->payload_len = sizeof(*payload); + payload = (struct scst_event_lun_not_found_payload *)event->payload; + + payload->lun = cmd->lun; + strlcpy(payload->initiator_name, cmd->sess->initiator_name, + sizeof(payload->initiator_name)); + strlcpy(payload->target_name, cmd->tgt->tgt_name, + sizeof(payload->target_name)); + + scst_event_queue(SCST_EVENT_LUN_NOT_FOUND, + SCST_EVENT_SCST_CORE_ISSUER, event_entry); + +out: + TRACE_EXIT_RES(res); + return res; +} + +int scst_event_queue_negative_luns_inquiry(const struct scst_tgt *tgt, + const char *initiator_name) +{ + int res = 0, event_entry_len; + struct scst_event_entry *event_entry; + struct scst_event *event; + struct scst_event_negative_luns_inquiry_payload *payload; + + TRACE_ENTRY(); + + event_entry_len = sizeof(*event_entry) + sizeof(*payload); + event_entry = kzalloc(event_entry_len, GFP_ATOMIC); + if (event_entry == NULL) { + PRINT_ERROR("Unable to allocate event (size %d). NEGATIVE LUNS " + "INQUIRY event is lost (initiator %s, target %s)!", + event_entry_len, initiator_name, tgt->tgt_name); + res = -ENOMEM; + goto out; + } + + TRACE_MEM("event_entry %p (len %d) allocated", event_entry, + event_entry_len); + + event = &event_entry->event; + + event->payload_len = sizeof(*payload); + payload = (struct scst_event_negative_luns_inquiry_payload *)event->payload; + + strlcpy(payload->initiator_name, initiator_name, + sizeof(payload->initiator_name)); + strlcpy(payload->target_name, tgt->tgt_name, + sizeof(payload->target_name)); + + scst_event_queue(SCST_EVENT_NEGATIVE_LUNS_INQUIRY, + SCST_EVENT_SCST_CORE_ISSUER, event_entry); + +out: + TRACE_EXIT_RES(res); + return res; +} + +/* No locks */ +int scst_event_queue_ext_blocking_done(struct scst_device *dev, void *data, int len) +{ + int res, event_entry_len; + struct scst_event_entry *event_entry; + struct scst_event *event; + struct scst_event_ext_blocking_done_payload *payload; + + TRACE_ENTRY(); + + event_entry_len = sizeof(*event_entry) + sizeof(*payload) + len; + event_entry = kzalloc(event_entry_len, GFP_ATOMIC); + if (event_entry == NULL) { + PRINT_CRIT_ERROR("Unable to allocate event. Ext blocking " + "done event is lost (device %s, size %zd)!", dev->virt_name, + sizeof(*event_entry) + sizeof(*payload) + len); + res = -ENOMEM; + goto out; + } + + TRACE_MEM("event_entry %p (len %d) allocated", event_entry, + event_entry_len); + + event = &event_entry->event; + + event->payload_len = sizeof(*payload) + len; + payload = (struct scst_event_ext_blocking_done_payload *)event->payload; + + strlcpy(payload->device_name, dev->virt_name, sizeof(payload->device_name)); + if (len > 0) + memcpy(payload->data, data, len); + + scst_event_queue(SCST_EVENT_EXT_BLOCKING_DONE, + SCST_EVENT_SCST_CORE_ISSUER, event_entry); + res = 0; + +out: + TRACE_EXIT_RES(res); + return res; +} + +/* No locks */ +int scst_event_queue_tm_fn_received(struct scst_mgmt_cmd *mcmd) +{ + int res = 0, event_entry_len; + struct scst_event_entry *event_entry; + struct scst_event *event; + struct scst_event_tm_fn_received_payload *payload; + + TRACE_ENTRY(); + + event_entry_len = sizeof(*event_entry) + sizeof(*payload); + event_entry = kzalloc(event_entry_len, GFP_KERNEL); + if (event_entry == NULL) { + PRINT_CRIT_ERROR("Unable to allocate event (size %d). External " + "TM fn received event is lost!", event_entry_len); + res = -ENOMEM; + goto out; + } + + TRACE_MEM("event_entry %p (len %d) allocated", event_entry, + event_entry_len); + + event = &event_entry->event; + + event->payload_len = sizeof(*payload); + payload = (struct scst_event_tm_fn_received_payload *)event->payload; + + payload->fn = mcmd->fn; + payload->lun = mcmd->lun; + if (mcmd->mcmd_tgt_dev != NULL) + strlcpy(payload->device_name, mcmd->mcmd_tgt_dev->dev->virt_name, + sizeof(payload->device_name)); + strlcpy(payload->initiator_name, mcmd->sess->initiator_name, + sizeof(payload->initiator_name)); + strlcpy(payload->target_name, mcmd->sess->tgt->tgt_name, + sizeof(payload->target_name)); + strlcpy(payload->session_sysfs_name, mcmd->sess->sess_name, + sizeof(payload->session_sysfs_name)); + if (mcmd->cmd_to_abort != NULL) { + payload->cmd_to_abort_tag = mcmd->cmd_to_abort->tag; + strlcpy(payload->cdb, mcmd->cmd_to_abort->cdb, + sizeof(payload->cdb)); + } + + scst_event_queue(SCST_EVENT_TM_FN_RECEIVED, + SCST_EVENT_SCST_CORE_ISSUER, event_entry); + +out: + TRACE_EXIT_RES(res); + return res; +} + +/* scst_event_mutex supposed to be held. Can release/reacquire it inside */ +static void scst_release_event_entry(struct scst_event_entry *e) +{ + TRACE_ENTRY(); + + TRACE_DBG("Deleting event entry %p", e); + list_del(&e->events_list_entry); + + if (e->event_notify_fn != NULL) { + mutex_unlock(&scst_event_mutex); + + cancel_delayed_work_sync(&e->event_timeout_work); + + TRACE_DBG("Calling notify_fn of event_entry %p", e); + e->event_notify_fn(&e->event, e->notify_fn_priv, -EFAULT); + + mutex_lock(&scst_event_mutex); + } + + TRACE_MEM("Freeing notified event entry %p", e); + kfree(e); + + TRACE_EXIT(); + return; +} + +static int scst_event_release(struct inode *inode, struct file *file) +{ + struct scst_event_priv *priv; + struct scst_event_entry *e, *et; + + TRACE_ENTRY(); + + mutex_lock(&scst_event_mutex); + + priv = file->private_data; + if (priv == NULL) { + mutex_unlock(&scst_event_mutex); + goto out; + } + file->private_data = NULL; + + list_del(&priv->privs_list_entry); + + mutex_unlock(&scst_event_mutex); + + TRACE_DBG("Going to release event priv %p", priv); + + priv->going_to_exit = 1; + wake_up_all(&priv->queued_events_waitQ); + + list_for_each_entry_safe(e, et, &priv->allowed_events_list, + events_list_entry) { + TRACE_MEM("Deleting allowed event entry %p", e); + list_del(&e->events_list_entry); + kfree(e); + } + + mutex_lock(&scst_event_mutex); /* to sync with timeout_work */ + while (!list_empty(&priv->queued_events_list)) { + e = list_entry(priv->queued_events_list.next, + typeof(*e), events_list_entry); + scst_release_event_entry(e); + } + while (!list_empty(&priv->processing_events_list)) { + e = list_entry(priv->processing_events_list.next, + typeof(*e), events_list_entry); + scst_release_event_entry(e); + } + mutex_unlock(&scst_event_mutex); + + TRACE_MEM("Deleting priv %p", priv); + kfree(priv); + + module_put(THIS_MODULE); + +out: + TRACE_EXIT(); + return 0; +} + +/* + * scst_event_mutex supposed to be held. Caller supposed to free returned + * out_event_entry using kfree(). This function returnes event_entry, not + * plain event, because this entry can then be queued in some list. + */ +static int scst_event_get_event_from_user(void __user *arg, + struct scst_event_entry **out_event_entry) +{ + int res, rc, event_entry_len; + uint32_t payload_len; + struct scst_event_entry *event_entry; + + TRACE_ENTRY(); + + res = get_user(payload_len, (uint32_t __user *)arg); + if (res != 0) { + PRINT_ERROR("Failed to get payload len: %d", res); + goto out; + } + + if (payload_len > SCST_MAX_PAYLOAD) { + PRINT_ERROR("Payload len %d is too big (max %d)", payload_len, + SCST_MAX_PAYLOAD); + res = -EINVAL; + goto out; + } + + TRACE_DBG("payload_len %d", payload_len); + + event_entry_len = sizeof(*event_entry) + payload_len; + + event_entry = kzalloc(event_entry_len, GFP_KERNEL); + if (event_entry == NULL) { + PRINT_ERROR("Unable to allocate event entry (size %d)", + event_entry_len); + res = -ENOMEM; + goto out; + } + + TRACE_MEM("Allocated event entry %p", event_entry); + + rc = copy_from_user(&event_entry->event, arg, + sizeof(event_entry->event) + payload_len); + if (rc != 0) { + PRINT_ERROR("Failed to copy %d user's bytes", rc); + res = -EFAULT; + goto out_free; + } + + event_entry->event.issuer_name[sizeof(event_entry->event.issuer_name)-1] = '\0'; + + TRACE_DBG("user event: event_code %d, issuer_name %s", + event_entry->event.event_code, event_entry->event.issuer_name); + + *out_event_entry = event_entry; + +out: + TRACE_EXIT_RES(res); + return res; + +out_free: + TRACE_MEM("Deleting event entry %p", event_entry); + kfree(event_entry); + goto out; +} + +/* scst_event_mutex supposed to be held */ +static int scst_event_allow_event(struct scst_event_priv *priv, void __user *arg) +{ + int res; + struct scst_event_entry *event_entry, *e; + + TRACE_ENTRY(); + + res = scst_event_get_event_from_user(arg, &event_entry); + if (res != 0) + goto out; + + list_for_each_entry(e, &priv->allowed_events_list, events_list_entry) { + if (scst_event_cmp(&event_entry->event, &e->event)) { + PRINT_WARNING("Allowed event (event_code %d, " + "issuer_name %s) already exists", + e->event.event_code, e->event.issuer_name); + res = -EEXIST; + goto out_free; + } + } + + if (priv->allowed_events_cnt >= SCST_MAX_EVENTS) { + PRINT_ERROR("Too many allowed events %d", + priv->allowed_events_cnt); + res = -EMFILE; + goto out_free; + } + + list_add_tail(&event_entry->events_list_entry, + &priv->allowed_events_list); + priv->allowed_events_cnt++; + res = 0; + +out: + TRACE_EXIT_RES(res); + return res; + +out_free: + TRACE_MEM("Deleting event entry %p", event_entry); + kfree(event_entry); + goto out; +} + +/* scst_event_mutex supposed to be held */ +static int scst_event_disallow_event(struct scst_event_priv *priv, + void __user *arg) +{ + int res; + struct scst_event_entry *event_entry, *e, *et; + bool found = false; + + TRACE_ENTRY(); + + res = scst_event_get_event_from_user(arg, &event_entry); + if (res != 0) + goto out; + + /* For wildcard events we might delete several events */ + list_for_each_entry_safe(e, et, &priv->allowed_events_list, + events_list_entry) { + if (scst_event_cmp(&event_entry->event, &e->event)) { + PRINT_INFO("Deleting allowed event (event_code %d, " + "issuer_name %s)", e->event.event_code, + e->event.issuer_name); + TRACE_MEM("Deleting event entry %p", e); + list_del(&e->events_list_entry); + kfree(e); + priv->allowed_events_cnt--; + found = true; + } + } + if (!found) { + PRINT_WARNING("Allowed event (event_code %d, issuer_name %s) " + "not found", event_entry->event.event_code, + event_entry->event.issuer_name); + res = -ENOENT; + } else + res = 0; + + TRACE_MEM("Deleting event entry %p", e); + kfree(event_entry); + +out: + TRACE_EXIT_RES(res); + return res; +} + +/* scst_event_mutex supposed to be held. Might drop it, then get back. */ +static int scst_event_user_next_event(struct scst_event_priv *priv, + void __user *arg) +{ + int res, rc; + int32_t max_event_size, needed_size; + struct scst_event_entry *event_entry; + struct scst_event_user __user *event_user = arg; + + TRACE_ENTRY(); + + res = get_user(max_event_size, (int32_t __user *)arg); + if (res != 0) { + PRINT_ERROR("Failed to get max event size: %d", res); + goto out; + }; + + /* Waiting for at least one event, if blocking */ + while (list_empty(&priv->queued_events_list)) { + mutex_unlock(&scst_event_mutex); + wait_event_interruptible(priv->queued_events_waitQ, + (!list_empty(&priv->queued_events_list) || priv->going_to_exit || + !priv->blocking || signal_pending(current))); + mutex_lock(&scst_event_mutex); + if (priv->going_to_exit || signal_pending(current)) { + res = -EINTR; + TRACE_DBG("Signal pending or going_to_exit (%d), returning", + priv->going_to_exit); + goto out; + } else if (list_empty(&priv->queued_events_list) && !priv->blocking) { + res = -EAGAIN; + TRACE_DBG("Nothing pending, returning %d", res); + goto out; + } + } + + EXTRACHECKS_BUG_ON(list_empty(&priv->queued_events_list)); + + event_entry = list_entry(priv->queued_events_list.next, + struct scst_event_entry, events_list_entry); + + needed_size = sizeof(event_entry->event) + event_entry->event.payload_len; + + if (needed_size > max_event_size) { + TRACE_DBG("Too big event (size %d, max size %d)", needed_size, + max_event_size); + res = put_user(needed_size, (int32_t __user *)arg); + if (res == 0) + res = -ENOSPC; + goto out; + } + + rc = copy_to_user(&event_user->out_event, &event_entry->event, needed_size); + if (rc != 0) { + PRINT_ERROR("Copy to user failed (%d)", rc); + res = -EFAULT; + goto out; + } + + if (event_entry->event_notify_fn) { + TRACE_DBG("Moving event entry %p to processing events list", + event_entry); + list_move_tail(&event_entry->events_list_entry, + &priv->processing_events_list); + } else { + TRACE_MEM("Deleting event entry %p", event_entry); + list_del(&event_entry->events_list_entry); + kfree(event_entry); + } + + priv->queued_events_cnt--; + + res = 0; + +out: + TRACE_EXIT_RES(res); + return res; +} + +/* scst_event_mutex supposed to be held. Might drop it, then get back. */ +static int scst_event_user_notify_done(struct scst_event_priv *priv, + void __user *arg) +{ + int res, rc; + struct scst_event_notify_done n; + struct scst_event_entry *e; + bool found = false; + + TRACE_ENTRY(); + + rc = copy_from_user(&n, arg, sizeof(n)); + if (rc != 0) { + PRINT_ERROR("Failed to copy %d user's bytes of notify done", rc); + res = -EFAULT; + goto out; + } + + res = 0; + + list_for_each_entry(e, &priv->processing_events_list, events_list_entry) { + if (e->event.event_id == n.event_id) { + found = true; + break; + } + } + if (!found) { + PRINT_ERROR("Waiting event for id %u not found", n.event_id); + res = -ENOENT; + goto out; + } + + list_del_init(&e->events_list_entry); + + mutex_unlock(&scst_event_mutex); + + cancel_delayed_work_sync(&e->event_timeout_work); + + if (e->event_notify_fn != NULL) { + TRACE_DBG("Calling notify_fn of event_entry %p", e); + e->event_notify_fn(&e->event, e->notify_fn_priv, n.status); + } + + TRACE_MEM("Freeing event entry %p", e); + kfree(e); + + /* Lock it back, because we expected to do so */ + mutex_lock(&scst_event_mutex); + +out: + TRACE_EXIT_RES(res); + return res; +} + +/* scst_event_mutex supposed to be held */ +static int scst_event_create_priv(struct file *file) +{ + int res; + struct scst_event_priv *priv; + + TRACE_ENTRY(); + + EXTRACHECKS_BUG_ON(file->private_data != NULL); + + if (!try_module_get(THIS_MODULE)) { + PRINT_ERROR("Fail to get module"); + res = -ETXTBSY; + goto out; + } + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (priv == NULL) { + PRINT_ERROR("Unable to allocate priv (size %zd)", + sizeof(*priv)); + res = -ENOMEM; + goto out; + } + + TRACE_MEM("priv %p allocated", priv); + + priv->owner_pid = current->pid; + INIT_LIST_HEAD(&priv->allowed_events_list); + init_waitqueue_head(&priv->queued_events_waitQ); + INIT_LIST_HEAD(&priv->queued_events_list); + INIT_LIST_HEAD(&priv->processing_events_list); + if (file->f_flags & O_NONBLOCK) { + TRACE_DBG("%s", "Non-blocking operations"); + priv->blocking = 0; + } else + priv->blocking = 1; + + list_add_tail(&priv->privs_list_entry, &scst_event_privs_list); + + file->private_data = priv; + + res = 0; + +out: + TRACE_EXIT_RES(res); + return res; +} + +static long scst_event_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + long res; + struct scst_event_priv *priv; + + TRACE_ENTRY(); + + mutex_lock(&scst_event_mutex); + + priv = file->private_data; + if (unlikely(priv == NULL)) { + /* This is the first time we are here */ + res = scst_event_create_priv(file); + if (res != 0) + goto out_unlock; + priv = file->private_data; + } + + TRACE_DBG("priv %p", priv); + + /* + * Handler functions called under scst_event_mutex for their + * convenience only, because they would need to reacquire it back again + * anyway. It has nothing common with protecting private_data, which + * protected from release() by the file reference counting. + */ + + switch (cmd) { + case SCST_EVENT_ALLOW_EVENT: + TRACE_DBG("%s", "ALLOW_EVENT"); + res = scst_event_allow_event(priv, (void __user *)arg); + break; + + case SCST_EVENT_DISALLOW_EVENT: + TRACE_DBG("%s", "DISALLOW_EVENT"); + res = scst_event_disallow_event(priv, (void __user *)arg); + break; + + case SCST_EVENT_GET_NEXT_EVENT: + TRACE_DBG("%s", "GET_NEXT_EVENT"); + res = scst_event_user_next_event(priv, (void __user *)arg); + break; + + case SCST_EVENT_NOTIFY_DONE: + TRACE_DBG("%s", "NOTIFY_DONE"); + res = scst_event_user_notify_done(priv, (void __user *)arg); + break; + + default: + PRINT_ERROR("Invalid ioctl cmd %x", cmd); + res = -EINVAL; + goto out_unlock; + } + +out_unlock: + mutex_unlock(&scst_event_mutex); + + TRACE_EXIT_RES(res); + return res; +} + +static unsigned int scst_event_poll(struct file *file, poll_table *wait) +{ + int res = 0; + struct scst_event_priv *priv; + + TRACE_ENTRY(); + + mutex_lock(&scst_event_mutex); + + priv = file->private_data; + if (unlikely(priv == NULL)) { + PRINT_ERROR("At least one allowed event must be set"); + res = -EINVAL; + goto out_unlock; + } + + if (!list_empty(&priv->queued_events_list)) { + res |= POLLIN | POLLRDNORM; + goto out_unlock; + } + + mutex_unlock(&scst_event_mutex); + + TRACE_DBG("Before poll_wait() (priv %p)", priv); + poll_wait(file, &priv->queued_events_waitQ, wait); + TRACE_DBG("After poll_wait() (priv %p)", priv); + + mutex_lock(&scst_event_mutex); + + if (!list_empty(&priv->queued_events_list)) { + res |= POLLIN | POLLRDNORM; + goto out_unlock; + } + +out_unlock: + mutex_unlock(&scst_event_mutex); + + TRACE_EXIT_HRES(res); + return res; +} + +#if 0 +#define CONFIG_EVENTS_WAIT_TEST +#endif + +#ifdef CONFIG_EVENTS_WAIT_TEST +static void scst_event_test_notify_fn(struct scst_event *event, + void *priv, int status) +{ + TRACE_ENTRY(); + + PRINT_INFO("Notification for event %u (id %d) received with status %d " + "(priv %p)", event->event_code, event->event_id, status, + priv); + + TRACE_EXIT(); + return; +} + +static ssize_t event_wait_test_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int res = 0, event_entry_len; + struct scst_event_entry *event_entry; + + TRACE_ENTRY(); + + event_entry_len = sizeof(*event_entry); + event_entry = kzalloc(event_entry_len, GFP_KERNEL); + if (event_entry == NULL) { + PRINT_ERROR("Unable to allocate event (size %d). Test " + "event is lost!", event_entry_len); + res = -ENOMEM; + goto out; + } + + TRACE_MEM("event_entry %p (len %d) allocated", event_entry, + event_entry_len); + + event_entry->event_notify_fn = scst_event_test_notify_fn; + event_entry->event_timeout = 10*HZ; + + scst_event_queue(0x12345, SCST_EVENT_SCST_CORE_ISSUER, event_entry); + + if (res == 0) + res = count; + +out: + TRACE_EXIT_RES(res); + return res; +} + +static struct kobj_attribute event_wait_test_attr = + __ATTR(event_wait_test, S_IWUSR, NULL, event_wait_test_store); + +#endif /* #ifdef CONFIG_EVENTS_WAIT_TEST */ + +static const struct file_operations scst_event_fops = { + .poll = scst_event_poll, + .unlocked_ioctl = scst_event_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = scst_event_ioctl, +#endif + .release = scst_event_release, +}; + +int scst_event_init(void) +{ + int res = 0; + struct device *dev; + + TRACE_ENTRY(); + + scst_event_sysfs_class = class_create(THIS_MODULE, SCST_EVENT_NAME); + if (IS_ERR(scst_event_sysfs_class)) { + PRINT_ERROR("%s", "Unable create sysfs class for SCST event"); + res = PTR_ERR(scst_event_sysfs_class); + goto out; + } + + scst_event_major = register_chrdev(0, SCST_EVENT_NAME, &scst_event_fops); + if (scst_event_major < 0) { + PRINT_ERROR("register_chrdev() failed: %d", res); + res = scst_event_major; + goto out_class; + } + + dev = device_create(scst_event_sysfs_class, NULL, + MKDEV(scst_event_major, 0), + NULL, + SCST_EVENT_NAME); + if (IS_ERR(dev)) { + res = PTR_ERR(dev); + goto out_chrdev; + } + +#ifdef CONFIG_EVENTS_WAIT_TEST + sysfs_create_file(kernel_kobj, &event_wait_test_attr.attr); +#endif + +out: + TRACE_EXIT_RES(res); + return res; + + +out_chrdev: + unregister_chrdev(scst_event_major, SCST_EVENT_NAME); + +out_class: + class_destroy(scst_event_sysfs_class); + goto out; +} + +void scst_event_exit(void) +{ + TRACE_ENTRY(); + +#ifdef CONFIG_EVENTS_WAIT_TEST + sysfs_remove_file(kernel_kobj, &event_wait_test_attr.attr); +#endif + + unregister_chrdev(scst_event_major, SCST_EVENT_NAME); + + device_destroy(scst_event_sysfs_class, MKDEV(scst_event_major, 0)); + class_destroy(scst_event_sysfs_class); + + /* Wait for all pending being queued events to process */ + flush_scheduled_work(); + + TRACE_EXIT(); + return; +} diff --git a/scst/src/scst_lib.c b/scst/src/scst_lib.c index e65a1b388..ff5441160 100644 --- a/scst/src/scst_lib.c +++ b/scst/src/scst_lib.c @@ -63,6 +63,19 @@ #include "scst_mem.h" #include "scst_pres.h" +/* + * List and IRQ lock to globally serialize all STPG commands. Needed to + * prevent deadlock, if (1) a device group contains multiple devices and + * (2) STPG commands comes to 2 or more of them at about the same time. + * In this case they will be waiting for each other to finish all pending + * commands, i.e. the STPG commands waiting for each other. Strict + * serialization is per device, so can not help here. + * + * ToDo: make it per device group. + */ +static DEFINE_SPINLOCK(scst_global_stpg_list_lock); +static LIST_HEAD(scst_global_stpg_list); + static void scst_put_acg_work(struct work_struct *work); static void scst_del_acn(struct scst_acn *acn); static void scst_free_acn(struct scst_acn *acn, bool reassign); @@ -144,6 +157,12 @@ static int sg_copy(struct scatterlist *dst_sg, struct scatterlist *src_sg, #endif static void scst_free_descriptors(struct scst_cmd *cmd); +static bool sg_cmp(struct scatterlist *dst_sg, struct scatterlist *src_sg, + int nents_to_cmp, size_t cmp_len, int *miscompare_offs +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 4, 0) + , enum km_type d_km_type, enum km_type s_km_type +#endif + ); const struct scst_opcode_descriptor scst_op_descr_inquiry = { .od_opcode = INQUIRY, @@ -155,6 +174,18 @@ const struct scst_opcode_descriptor scst_op_descr_inquiry = { }; EXPORT_SYMBOL(scst_op_descr_inquiry); +const struct scst_opcode_descriptor scst_op_descr_extended_copy = { + .od_opcode = EXTENDED_COPY, + .od_support = 3, /* supported as in the standard */ + .od_cdb_size = 16, + .od_nominal_timeout = SCST_DEFAULT_NOMINAL_TIMEOUT_SEC, + .od_recommended_timeout = SCST_GENERIC_DISK_REG_TIMEOUT/HZ, + .od_cdb_usage_bits = { EXTENDED_COPY, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0xFF, 0xFF, 0xFF, 0xFF, 0, + SCST_OD_DEFAULT_CONTROL_BYTE }, +}; +EXPORT_SYMBOL(scst_op_descr_extended_copy); + const struct scst_opcode_descriptor scst_op_descr_tur = { .od_opcode = TEST_UNIT_READY, .od_support = 3, /* supported as in the standard */ @@ -250,7 +281,7 @@ const struct scst_opcode_descriptor scst_op_descr_stpg = { .od_cdb_size = 12, .od_nominal_timeout = SCST_DEFAULT_NOMINAL_TIMEOUT_SEC, .od_recommended_timeout = SCST_GENERIC_DISK_SMALL_TIMEOUT/HZ, - .od_cdb_usage_bits = { MAINTENANCE_IN, MO_SET_TARGET_PGS, 0, 0, 0, 0, + .od_cdb_usage_bits = { MAINTENANCE_OUT, MO_SET_TARGET_PGS, 0, 0, 0, 0, 0xFF, 0xFF, 0xFF, 0xFF, 0, SCST_OD_DEFAULT_CONTROL_BYTE }, }; EXPORT_SYMBOL(scst_op_descr_stpg); @@ -451,27 +482,31 @@ static int get_cdb_info_write_same16(struct scst_cmd *cmd, const struct scst_sdbops *sdbops); static int get_cdb_info_compare_and_write(struct scst_cmd *cmd, const struct scst_sdbops *sdbops); +static int get_cdb_info_ext_copy(struct scst_cmd *cmd, + const struct scst_sdbops *sdbops); static int get_cdb_info_apt(struct scst_cmd *cmd, const struct scst_sdbops *sdbops); static int get_cdb_info_min(struct scst_cmd *cmd, const struct scst_sdbops *sdbops); +static int get_cdb_info_mo(struct scst_cmd *cmd, + const struct scst_sdbops *sdbops); static int get_cdb_info_var_len(struct scst_cmd *cmd, const struct scst_sdbops *sdbops); /* -+=====================================-============-======- -| Command name | Operation | Type | -| | code | | -|-------------------------------------+------------+------+ - -+=========================================================+ -|Key: M = command implementation is mandatory. | -| O = command implementation is optional. | -| V = Vendor-specific | -| R = Reserved | -| ' '= DON'T use for this device | -+=========================================================+ -*/ + * +=====================================-============-======- + * | Command name | Operation | Type | + * | | code | | + * |-------------------------------------+------------+------+ + * + * +=========================================================+ + * |Key: M = command implementation is mandatory. | + * | O = command implementation is optional. | + * | V = Vendor-specific | + * | R = Reserved | + * | ' '= DON'T use for this device | + * +=========================================================+ + */ #define SCST_CDB_MANDATORY 'M' /* mandatory */ #define SCST_CDB_OPTIONAL 'O' /* optional */ @@ -548,7 +583,8 @@ static const struct scst_sdbops scst_scsi_op_table[] = { * |||||||||| |||+----> Optical card (F) * |||||||||| |||| * |||||||||| |||| - * 0123456789ABCDEF -> TYPE_???? */ + * 0123456789ABCDEF -> TYPE_???? + */ /* 6-bytes length CDB */ {.ops = 0x00, .devkey = "MMMMMMMMMMMMMMMM", @@ -744,7 +780,8 @@ static const struct scst_sdbops scst_scsi_op_table[] = { .info_op_name = "RESERVE", .info_data_direction = SCST_DATA_NONE, .info_op_flags = SCST_SMALL_TIMEOUT|SCST_LOCAL_CMD|SCST_SERIALIZED| - SCST_WRITE_EXCL_ALLOWED|SCST_EXCL_ACCESS_ALLOWED, + SCST_WRITE_EXCL_ALLOWED|SCST_EXCL_ACCESS_ALLOWED| + SCST_SCSI_ATOMIC/* see comment in scst_cmd_overlap() */, .get_cdb_info = get_cdb_info_none}, {.ops = 0x17, .devkey = "MMMMMMMMMMMMMMMM", .info_op_name = "RELEASE", @@ -1157,7 +1194,8 @@ static const struct scst_sdbops scst_scsi_op_table[] = { .info_op_name = "RESERVE(10)", .info_data_direction = SCST_DATA_NONE, .info_op_flags = SCST_SMALL_TIMEOUT|SCST_LOCAL_CMD|SCST_SERIALIZED| - SCST_WRITE_EXCL_ALLOWED|SCST_EXCL_ACCESS_ALLOWED, + SCST_WRITE_EXCL_ALLOWED|SCST_EXCL_ACCESS_ALLOWED| + SCST_SCSI_ATOMIC/* see comment in scst_cmd_overlap() */, .get_cdb_info = get_cdb_info_none}, {.ops = 0x57, .devkey = "OOOOOOOOOOOOOOOO", .info_op_name = "RELEASE(10)", @@ -1235,16 +1273,18 @@ static const struct scst_sdbops scst_scsi_op_table[] = { .info_op_flags = SCST_WRITE_MEDIUM, .info_len_off = 10, .info_len_len = 4, .get_cdb_info = get_cdb_info_len_4}, - {.ops = 0x83, .devkey = "OOOOOOOOOOOOOOOO", + {.ops = 0x83, .devkey = "O ", /* implemented only for disks */ .info_op_name = "EXTENDED COPY", .info_data_direction = SCST_DATA_WRITE, - .info_op_flags = SCST_WRITE_MEDIUM, + .info_op_flags = SCST_WRITE_MEDIUM|SCST_CAN_GEN_3PARTY_COMMANDS| + SCST_LOCAL_CMD|SCST_DESCRIPTORS_BASED, .info_len_off = 10, .info_len_len = 4, - .get_cdb_info = get_cdb_info_len_4}, - {.ops = 0x84, .devkey = "OOOOOOOOOOOOOOOO", + .get_cdb_info = get_cdb_info_ext_copy}, + {.ops = 0x84, .devkey = "O ", /* implemented only for disks */ .info_op_name = "RECEIVE COPY RESULT", .info_data_direction = SCST_DATA_READ, - .info_op_flags = SCST_WRITE_EXCL_ALLOWED|SCST_EXCL_ACCESS_ALLOWED, + .info_op_flags = SCST_FULLY_LOCAL_CMD|SCST_LOCAL_CMD| + SCST_WRITE_EXCL_ALLOWED|SCST_EXCL_ACCESS_ALLOWED, .info_len_off = 10, .info_len_len = 4, .get_cdb_info = get_cdb_info_len_4}, {.ops = 0x85, .devkey = "O O O ", @@ -1279,8 +1319,9 @@ static const struct scst_sdbops scst_scsi_op_table[] = { {.ops = 0x89, .devkey = "O ", .info_op_name = "COMPARE AND WRITE", .info_data_direction = SCST_DATA_WRITE, - .info_op_flags = SCST_TRANSFER_LEN_TYPE_FIXED|SCST_WRITE_MEDIUM| - SCST_SERIALIZED, + .info_op_flags = SCST_TRANSFER_LEN_TYPE_FIXED| + SCST_FULLY_LOCAL_CMD|SCST_LOCAL_CMD| + SCST_WRITE_MEDIUM|SCST_SCSI_ATOMIC, .info_lba_off = 2, .info_lba_len = 8, .info_len_off = 13, .info_len_len = 1, .get_cdb_info = get_cdb_info_compare_and_write}, @@ -1412,12 +1453,12 @@ static const struct scst_sdbops scst_scsi_op_table[] = { .info_op_flags = FLAG_NONE, .info_len_off = 8, .info_len_len = 2, .get_cdb_info = get_cdb_info_len_2}, - {.ops = 0xA4, .devkey = " O ", + {.ops = 0xA4, .devkey = "OOO O OOOO MO O", .info_op_name = "MAINTENANCE(OUT)", .info_data_direction = SCST_DATA_WRITE, .info_op_flags = FLAG_NONE, .info_len_off = 6, .info_len_len = 4, - .get_cdb_info = get_cdb_info_len_4}, + .get_cdb_info = get_cdb_info_mo}, {.ops = 0xA5, .devkey = " M ", .info_op_name = "MOVE MEDIUM", .info_data_direction = SCST_DATA_NONE, @@ -1987,10 +2028,14 @@ int scst_set_cmd_error_and_inf(struct scst_cmd *cmd, int key, int asc, switch (cmd->sense[0] & 0x7f) { case 0x70: + { /* Fixed format */ + uint32_t i = information; + cmd->sense[0] |= 0x80; /* Information field is valid */ - put_unaligned_be32(information, &cmd->sense[3]); + put_unaligned_be32(i, &cmd->sense[3]); break; + } case 0x72: /* Descriptor format */ cmd->sense[7] = 12; /* additional sense length */ @@ -2913,6 +2958,7 @@ next: mutex_lock(&sess->tgt_dev_list_mutex); for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { struct scst_tgt_dev *t; + head = &sess->sess_tgt_dev_list[i]; list_for_each_entry_safe(tgt_dev, t, head, @@ -2987,8 +3033,10 @@ void scst_check_reassign_sessions(void) list_for_each_entry(tgtt, &scst_template_list, scst_template_list_entry) { struct scst_tgt *tgt; + list_for_each_entry(tgt, &tgtt->tgt_list, tgt_list_entry) { struct scst_session *sess; + list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) { scst_check_reassign_sess(sess); @@ -3301,6 +3349,140 @@ static void scst_adjust_sg(struct scst_cmd *cmd, bool reg_sg, return; } +static int __scst_adjust_sg_get_tail(struct scst_cmd *cmd, + struct scatterlist *sg, int *sg_cnt, + struct scatterlist **res_sg, int *res_sg_cnt, + int adjust_len, struct scst_orig_sg_data *orig_sg, int must_left) +{ + int res = -ENOENT, i, j, l; + + TRACE_ENTRY(); + + TRACE_DBG("cmd %p, sg_cnt %d, sg %p", cmd, *sg_cnt, sg); + + l = 0; + for (i = 0, j = 0; i < *sg_cnt; i++, j++) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24) + TRACE_DBG("i %d, j %d, sg %p, page_link %lx, len %d", i, j, + sg, sg[j].page_link, sg->length); +#else + TRACE_DBG("i %d, j %d, sg %p", i, j, sg); +#endif + if (unlikely(sg_is_chain(&sg[j]))) { + sg = sg_chain_ptr(&sg[j]); + j = 0; + } + l += sg[j].length; + if (l >= adjust_len) { + int offs = adjust_len - (l - sg[j].length); + + TRACE_DBG_FLAG(TRACE_SG_OP|TRACE_MEMORY|TRACE_DEBUG, + "cmd %p (tag %llu), sg %p, adjust_len %d, i %d, " + "j %d, sg[j].length %d, offs %d", + cmd, (unsigned long long)cmd->tag, + sg, adjust_len, i, j, sg[j].length, offs); + + if (offs == sg[j].length) { + j++; + offs = 0; + } + + orig_sg->p_orig_sg_cnt = sg_cnt; + orig_sg->orig_sg_cnt = *sg_cnt; + orig_sg->orig_sg_entry = &sg[j]; + orig_sg->orig_entry_offs = sg[j].offset; + orig_sg->orig_entry_len = sg[j].length; + + sg[j].offset += offs; + sg[j].length -= offs; + *res_sg = &sg[j]; + *res_sg_cnt = *sg_cnt - j; + + TRACE_DBG("j %d, sg %p, off %d, len %d, cnt %d " + "(offs %d)", j, &sg[j], sg[j].offset, + sg[j].length, *res_sg_cnt, offs); + + res = 0; + break; + } + } + + if (res != 0) + goto out; + +#ifdef CONFIG_SCST_EXTRACHECKS + l = 0; + sg = *res_sg; + for (i = 0; i < *res_sg_cnt; i++) + l += sg[i].length; + + if (l != must_left) { + PRINT_ERROR("Incorrect length %d of adjusted sg (cmd %p, " + "expected %d)", l, cmd, must_left); + res = -EINVAL; + scst_check_restore_sg_buff(cmd); + goto out; + } +#endif + +out: + TRACE_EXIT_RES(res); + return res; +} + +/* + * Returns in res_sg the tail of cmd's adjusted on adjust_len, i.e. tail + * of it. In res_sg_cnt sg_cnt of res_sg returned. Cmd only used to store + * cmd->sg restore information. + * + * Parameter must_left defines how many bytes must left in res_sg to consider + * operation successful. + * + * Returns 0 on success or error code otherwise. + * + * NOTE! Before scst_restore_sg_buff() called cmd->sg is corrupted and + * can NOT be used! + */ +static int scst_adjust_sg_get_tail(struct scst_cmd *cmd, + struct scatterlist **res_sg, int *res_sg_cnt, + struct scatterlist **res_dif_sg, int *res_dif_sg_cnt, + int adjust_len, int must_left) +{ + int res; + + TRACE_ENTRY(); + + EXTRACHECKS_BUG_ON(cmd->sg_buff_modified || cmd->dif_sg_buff_modified); + + res = __scst_adjust_sg_get_tail(cmd, cmd->sg, &cmd->sg_cnt, res_sg, + res_sg_cnt, adjust_len, &cmd->orig_sg, must_left); + if (res != 0) + goto out; + + cmd->sg_buff_modified = 1; + + if (cmd->dif_sg != NULL) { + adjust_len >>= (cmd->dev->block_shift - SCST_DIF_TAG_SHIFT); + must_left >>= (cmd->dev->block_shift - SCST_DIF_TAG_SHIFT); + + TRACE_DBG("DIF adjust_len %d, must_left %d", adjust_len, must_left); + + res = __scst_adjust_sg_get_tail(cmd, cmd->dif_sg, &cmd->dif_sg_cnt, + res_dif_sg, res_dif_sg_cnt, adjust_len, + &cmd->orig_dif_sg, must_left); + if (res != 0) { + scst_restore_sg_buff(cmd); + goto out; + } + + cmd->dif_sg_buff_modified = 1; + } + +out: + TRACE_EXIT_RES(res); + return res; +} + /** * scst_restore_sg_buff() - restores modified sg buffer * @@ -3589,6 +3771,7 @@ bool __scst_get_resid(struct scst_cmd *cmd, int *resid, int *bidi_out_resid) if (cmd->expected_data_direction & SCST_DATA_READ) { int resp = cmd->resp_data_len; + if (cmd->tgt_dif_data_expected) resp += (resp >> cmd->dev->block_shift) << SCST_DIF_TAG_SHIFT; *resid = cmd->expected_transfer_len_full - resp; @@ -3601,6 +3784,7 @@ bool __scst_get_resid(struct scst_cmd *cmd, int *resid, int *bidi_out_resid) } } else if (cmd->expected_data_direction & SCST_DATA_WRITE) { int wl = cmd->write_len; + if (cmd->tgt_dif_data_expected) wl += (wl >> cmd->dev->block_shift) << SCST_DIF_TAG_SHIFT; if (wl < cmd->expected_transfer_len_full) @@ -3609,6 +3793,7 @@ bool __scst_get_resid(struct scst_cmd *cmd, int *resid, int *bidi_out_resid) *resid = cmd->write_len - cmd->bufflen; if (cmd->tgt_dif_data_expected) { int r = *resid; + if (r < 0) r = -r; r += (r >> cmd->dev->block_shift) << SCST_DIF_TAG_SHIFT; @@ -3805,6 +3990,7 @@ static bool __scst_is_relative_target_port_id_unique(uint16_t id, list_for_each_entry(tgtt, &scst_template_list, scst_template_list_entry) { struct scst_tgt *tgt; + list_for_each_entry(tgt, &tgtt->tgt_list, tgt_list_entry) { if (tgt == t) continue; @@ -3950,6 +4136,7 @@ void scst_free_tgt(struct scst_tgt *tgt) static void scst_init_order_data(struct scst_order_data *order_data) { int i; + spin_lock_init(&order_data->sn_lock); INIT_LIST_HEAD(&order_data->deferred_cmd_list); INIT_LIST_HEAD(&order_data->skipped_sn_list); @@ -3962,6 +4149,8 @@ static void scst_init_order_data(struct scst_order_data *order_data) return; } +static void scst_ext_blocking_done_fn(struct work_struct *work); + static int scst_dif_none(struct scst_cmd *cmd); #ifdef CONFIG_SCST_DIF_INJECT_CORRUPTED_TAGS static int scst_dif_none_type1(struct scst_cmd *cmd); @@ -3990,9 +4179,12 @@ int scst_alloc_device(gfp_t gfp_mask, struct scst_device **out_dev) #endif scst_init_mem_lim(&dev->dev_mem_lim); spin_lock_init(&dev->dev_lock); + INIT_LIST_HEAD(&dev->dev_exec_cmd_list); INIT_LIST_HEAD(&dev->blocked_cmd_list); INIT_LIST_HEAD(&dev->dev_tgt_dev_list); INIT_LIST_HEAD(&dev->dev_acg_dev_list); + INIT_LIST_HEAD(&dev->ext_blockers_list); + INIT_WORK(&dev->ext_blockers_work, scst_ext_blocking_done_fn); dev->dev_double_ua_possible = 1; dev->queue_alg = SCST_QUEUE_ALG_1_UNRESTRICTED_REORDER; @@ -4018,6 +4210,9 @@ void scst_free_device(struct scst_device *dev) { TRACE_ENTRY(); + EXTRACHECKS_BUG_ON(dev->dev_scsi_atomic_cmd_active != 0); + EXTRACHECKS_BUG_ON(!list_empty(&dev->dev_exec_cmd_list)); + #ifdef CONFIG_SCST_EXTRACHECKS if (!list_empty(&dev->dev_tgt_dev_list) || !list_empty(&dev->dev_acg_dev_list)) { @@ -4027,6 +4222,9 @@ void scst_free_device(struct scst_device *dev) } #endif + /* Ensure that ext_blockers_work is done */ + flush_scheduled_work(); + scst_deinit_threads(&dev->dev_cmd_threads); scst_pr_cleanup(dev); @@ -4543,6 +4741,7 @@ static void scst_free_acg(struct scst_acg *acg) list_for_each_entry_safe(acg_dev, acg_dev_tmp, &acg->acg_dev_list, acg_dev_list_entry) { struct scst_tgt_dev *tgt_dev, *tt; + list_for_each_entry_safe(tgt_dev, tt, &acg_dev->dev->dev_tgt_dev_list, dev_tgt_dev_list_entry) { @@ -4854,6 +5053,7 @@ int scst_tgt_dev_setup_threads(struct scst_tgt_dev *tgt_dev) if (dev->threads_num == 0) { struct scst_tgt_dev *shared_io_tgt_dev; + tgt_dev->active_cmd_threads = &scst_main_cmd_threads; shared_io_tgt_dev = scst_find_shared_io_tgt_dev(tgt_dev); @@ -5315,6 +5515,7 @@ void scst_sess_free_tgt_devs(struct scst_session *sess) mutex_lock(&sess->tgt_dev_list_mutex); for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { struct list_head *head = &sess->sess_tgt_dev_list[i]; + list_for_each_entry_safe(tgt_dev, t, head, sess_tgt_dev_list_entry) { scst_del_tgt_dev(tgt_dev); @@ -5467,13 +5668,12 @@ int scst_acg_remove_name(struct scst_acg *acg, const char *name, bool reassign) } #endif -static struct scst_cmd *scst_create_prepare_internal_cmd( - struct scst_cmd *orig_cmd, const uint8_t *cdb, - unsigned int cdb_len, enum scst_cmd_queue_type queue_type) +struct scst_cmd *__scst_create_prepare_internal_cmd(const uint8_t *cdb, + unsigned int cdb_len, enum scst_cmd_queue_type queue_type, + struct scst_tgt_dev *tgt_dev, gfp_t gfp_mask, bool fantom) { struct scst_cmd *res; int rc; - gfp_t gfp_mask = scst_cmd_atomic(orig_cmd) ? GFP_ATOMIC : orig_cmd->cmd_gfp_mask; unsigned long flags; TRACE_ENTRY(); @@ -5482,30 +5682,34 @@ static struct scst_cmd *scst_create_prepare_internal_cmd( if (res == NULL) goto out; - res->cmd_threads = orig_cmd->cmd_threads; - res->sess = orig_cmd->sess; - res->atomic = scst_cmd_atomic(orig_cmd); + res->cmd_threads = tgt_dev->active_cmd_threads; + res->sess = tgt_dev->sess; res->internal = 1; - res->tgtt = orig_cmd->tgtt; - res->tgt = orig_cmd->tgt; - res->dev = orig_cmd->dev; - res->devt = orig_cmd->devt; - res->tgt_dev = orig_cmd->tgt_dev; - res->cur_order_data = orig_cmd->tgt_dev->curr_order_data; - res->lun = orig_cmd->lun; + res->tgtt = tgt_dev->sess->tgt->tgtt; + res->tgt = tgt_dev->sess->tgt; + res->dev = tgt_dev->dev; + res->devt = tgt_dev->dev->handler; + res->tgt_dev = tgt_dev; + res->cur_order_data = tgt_dev->curr_order_data; + res->lun = tgt_dev->lun; res->queue_type = queue_type; res->data_direction = SCST_DATA_UNKNOWN; - /* - * We need to keep it here to be able to abort during TM processing. - * They should be aborted to (1) speed up TM processing and (2) to - * guarantee that after a TM command finished the affected device(s) - * is/are in a quiescent state with all affected commands finished and - * others - blocked. - */ - spin_lock_irqsave(&res->sess->sess_list_lock, flags); - list_add_tail(&res->sess_cmd_list_entry, &res->sess->sess_cmd_list); - spin_unlock_irqrestore(&res->sess->sess_list_lock, flags); + if (!fantom) { + /* + * We need to keep it here to be able to abort during TM + * processing. They should be aborted to (1) speed up TM + * processing and (2) to guarantee that after a TM command + * finished the affected device(s) is/are in a quiescent state + * with all affected commands finished and others - blocked. + * + * Fantom commands are exception, because they don't do any + * real work. + */ + spin_lock_irqsave(&res->sess->sess_list_lock, flags); + list_add_tail(&res->sess_cmd_list_entry, &res->sess->sess_cmd_list); + spin_unlock_irqrestore(&res->sess->sess_list_lock, flags); + } scst_sess_get(res->sess); if (res->tgt_dev != NULL) @@ -5526,6 +5730,27 @@ out: return res; } +static struct scst_cmd *scst_create_prepare_internal_cmd( + struct scst_cmd *orig_cmd, const uint8_t *cdb, + unsigned int cdb_len, enum scst_cmd_queue_type queue_type) +{ + struct scst_cmd *res; + gfp_t gfp_mask = scst_cmd_atomic(orig_cmd) ? GFP_ATOMIC : orig_cmd->cmd_gfp_mask; + + TRACE_ENTRY(); + + res = __scst_create_prepare_internal_cmd(cdb, cdb_len, queue_type, + orig_cmd->tgt_dev, gfp_mask, false); + if (res == NULL) + goto out; + + res->atomic = scst_cmd_atomic(orig_cmd); + +out: + TRACE_EXIT_HRES((unsigned long)res); + return res; +} + static void scst_prelim_finish_internal_cmd(struct scst_cmd *cmd) { unsigned long flags; @@ -5639,6 +5864,11 @@ static void scst_complete_request_sense(struct scst_cmd *req_cmd) return; } +struct scst_ws_sg_tail { + struct scatterlist *sg; + int sg_cnt; +}; + struct scst_write_same_priv { /* Must be the first for scst_finish_internal_cmd()! */ scst_i_finish_fn_t ws_finish_fn; @@ -5647,8 +5877,13 @@ struct scst_write_same_priv { struct mutex ws_mutex; - int64_t ws_cur_lba; /* in blocks */ + /* 0 len terminated */ + struct scst_data_descriptor *ws_descriptors; + + int ws_cur_descr; + int ws_left_to_send; /* in blocks */ + int64_t ws_cur_lba; /* in blocks */ __be16 guard_tag; __be16 app_tag; @@ -5657,10 +5892,12 @@ struct scst_write_same_priv { unsigned int inc_ref_tag:1; int ws_max_each;/* in blocks */ - int ws_cur_in_flight; + int ws_cur_in_flight; /* commands */ - int ws_sg_cnt; - struct scatterlist *ws_sg; + struct scst_ws_sg_tail *ws_sg_tails; + + struct scatterlist *ws_sg_full; + int ws_sg_full_cnt; }; #ifdef CONFIG_SCST_EXTRACHECKS @@ -5683,7 +5920,8 @@ static int scst_ws_push_single_write(struct scst_write_same_priv *wsp, { struct scst_cmd *ws_cmd = wsp->ws_orig_cmd; struct scst_device *dev = ws_cmd->dev; - struct scatterlist *ws_sg = wsp->ws_sg; + struct scatterlist *ws_sg; + int ws_sg_cnt; int res; uint8_t write_cdb[32]; int write_cdb_len; @@ -5693,10 +5931,18 @@ static int scst_ws_push_single_write(struct scst_write_same_priv *wsp, TRACE_ENTRY(); + if (blocks == wsp->ws_max_each) { + ws_sg = wsp->ws_sg_full; + ws_sg_cnt = wsp->ws_sg_full_cnt; + } else { + ws_sg = wsp->ws_sg_tails[wsp->ws_cur_descr].sg; + ws_sg_cnt = wsp->ws_sg_tails[wsp->ws_cur_descr].sg_cnt; + } + #ifdef CONFIG_SCST_EXTRACHECKS - if (len != sg_data_length(wsp->ws_sg, wsp->ws_sg_cnt)) + if (len != sg_data_length(ws_sg, ws_sg_cnt)) WARN_ONCE(true, "lba %lld: %d <> %lld\n", lba, len, - sg_data_length(wsp->ws_sg, wsp->ws_sg_cnt)); + sg_data_length(ws_sg, ws_sg_cnt)); #endif if (unlikely(test_bit(SCST_CMD_ABORTED, &ws_cmd->cmd_flags)) || @@ -5715,6 +5961,7 @@ static int scst_ws_push_single_write(struct scst_write_same_priv *wsp, write_cdb[1] = ws_cmd->cdb[1]; if (needs_dif) { uint8_t wrprotect = ws_cmd->cdb[1] & 0xE0; + if (wrprotect == 0) wrprotect = 0x20; write_cdb[1] |= wrprotect; @@ -5724,6 +5971,7 @@ static int scst_ws_push_single_write(struct scst_write_same_priv *wsp, } else { /* There might be WRITE SAME(16) with WRPROTECT 0 here */ uint8_t wrprotect; + if (ws_cmd->cdb_len != 32) wrprotect = ws_cmd->cdb[1] & 0xE0; else @@ -5786,6 +6034,7 @@ static int scst_ws_push_single_write(struct scst_write_same_priv *wsp, for_each_sg(dif_sg, s, dif_sg_cnt, i) { int left = (s->length - s->offset) >> SCST_DIF_TAG_SHIFT; struct t10_pi_tuple *t = sg_virt(s); + TRACE_DBG("sg %p, offset %d, length %d, left %d", s, s->offset, s->length, left); while (left > 0) { @@ -5801,7 +6050,7 @@ static int scst_ws_push_single_write(struct scst_write_same_priv *wsp, } cmd->tgt_i_sg = ws_sg; - cmd->tgt_i_sg_cnt = wsp->ws_sg_cnt; + cmd->tgt_i_sg_cnt = ws_sg_cnt; cmd->tgt_i_data_buf_alloced = 1; wsp->ws_cur_lba += blocks; @@ -5830,6 +6079,7 @@ out_busy: static void scst_ws_finished(struct scst_write_same_priv *wsp) { struct scst_cmd *ws_cmd = wsp->ws_orig_cmd; + int i; TRACE_ENTRY(); @@ -5837,9 +6087,20 @@ static void scst_ws_finished(struct scst_write_same_priv *wsp) sBUG_ON(wsp->ws_cur_in_flight != 0); - if (sg_page(&wsp->ws_sg[0]) != sg_page(ws_cmd->sg)) - __free_page(sg_page(&wsp->ws_sg[0])); - kfree(wsp->ws_sg); + if (wsp->ws_sg_full && + sg_page(&wsp->ws_sg_full[0]) != sg_page(ws_cmd->sg)) + __free_page(sg_page(&wsp->ws_sg_full[0])); + else if (wsp->ws_sg_tails && wsp->ws_sg_tails[0].sg_cnt != 0 && + sg_page(&wsp->ws_sg_tails[0].sg[0]) != sg_page(ws_cmd->sg)) + __free_page(sg_page(&wsp->ws_sg_tails[0].sg[0])); + kfree(wsp->ws_sg_full); + if (wsp->ws_sg_tails) { + for (i = 0; wsp->ws_descriptors[i].sdd_blocks != 0; i++) + if (wsp->ws_sg_tails[i].sg_cnt != 0) + kfree(wsp->ws_sg_tails[i].sg); + kfree(wsp->ws_sg_tails); + } + kfree(wsp->ws_descriptors); kfree(wsp); ws_cmd->completed = 1; /* for success */ @@ -5849,52 +6110,6 @@ static void scst_ws_finished(struct scst_write_same_priv *wsp) return; } -/* - * If there is a tail with fewer segments than ws_max_each, adjust the SG - * vector and submit a WRITE command for the tail after all other in-flight - * commands have finished. - */ -static void scst_ws_process_tail(struct scst_write_same_priv *wsp) -{ - struct scst_cmd *ws_cmd = wsp->ws_orig_cmd; - struct scatterlist *sg; - unsigned left; - int i; - - TRACE_ENTRY(); - - lockdep_assert_held(&wsp->ws_mutex); - EXTRACHECKS_BUG_ON(wsp->ws_cur_in_flight > 0); - EXTRACHECKS_BUG_ON(wsp->ws_left_to_send >= wsp->ws_max_each); - - wsp->ws_max_each = wsp->ws_left_to_send; - left = wsp->ws_left_to_send << ws_cmd->dev->block_shift; - for_each_sg(wsp->ws_sg, sg, wsp->ws_sg_cnt, i) { - u32 len = min(left, sg->length); - - if (sg->length > len) { - TRACE_DBG("Processing WS tail of %d << %d = %d bytes - adjusted length of element %d from %d to %d", - wsp->ws_left_to_send, - ws_cmd->dev->block_shift, - wsp->ws_left_to_send << - ws_cmd->dev->block_shift, i, - sg->length, len); - sg->length = len; - sg_mark_end(sg); -#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 0, 0) - /* Old versions of sg_mark_end() clear page_link. */ - BUG_ON(sg_page(sg) == NULL); -#endif - wsp->ws_sg_cnt = i + 1; - break; - } - left -= len; - } - - TRACE_EXIT(); - return; -} - /* Must be called in a thread context and no locks */ static void scst_ws_write_cmd_finished(struct scst_cmd *cmd) { @@ -5919,6 +6134,7 @@ static void scst_ws_write_cmd_finished(struct scst_cmd *cmd) if (cmd->status != 0) { int rc; + TRACE_DBG("Write cmd %p (ws cmd %p) finished not successfully", cmd, ws_cmd); sBUG_ON(cmd->resp_data_len != 0); @@ -5936,14 +6152,17 @@ static void scst_ws_write_cmd_finished(struct scst_cmd *cmd) } } - if (wsp->ws_left_to_send == 0) - goto out_check_finish; - - if (wsp->ws_left_to_send < wsp->ws_max_each) { - if (wsp->ws_cur_in_flight > 0) + if (wsp->ws_left_to_send == 0) { + if (wsp->ws_descriptors[wsp->ws_cur_descr+1].sdd_blocks != 0) { + wsp->ws_cur_descr++; + wsp->ws_cur_lba = wsp->ws_descriptors[wsp->ws_cur_descr].sdd_lba; + wsp->ws_left_to_send = wsp->ws_descriptors[wsp->ws_cur_descr].sdd_blocks; + TRACE_DBG("wsp %p, cur descr %d, cur lba %lld, left %d", + wsp, wsp->ws_cur_descr, (long long)wsp->ws_cur_lba, + wsp->ws_left_to_send); + } + if (wsp->ws_left_to_send == 0) goto out_check_finish; - else - scst_ws_process_tail(wsp); } blocks = min_t(int, wsp->ws_left_to_send, wsp->ws_max_each); @@ -5975,18 +6194,15 @@ static void scst_ws_gen_writes(struct scst_write_same_priv *wsp) { struct scst_cmd *ws_cmd = wsp->ws_orig_cmd; int cnt = 0; + int rc; TRACE_ENTRY(); mutex_lock(&wsp->ws_mutex); - if (wsp->ws_left_to_send < wsp->ws_max_each) - scst_ws_process_tail(wsp); - +again: while (wsp->ws_left_to_send >= wsp->ws_max_each && wsp->ws_cur_in_flight < SCST_MAX_IN_FLIGHT_INTERNAL_COMMANDS) { - int rc; - rc = scst_ws_push_single_write(wsp, wsp->ws_cur_lba, wsp->ws_max_each); if (rc != 0) @@ -5994,6 +6210,26 @@ static void scst_ws_gen_writes(struct scst_write_same_priv *wsp) cnt++; } + if (wsp->ws_left_to_send > 0 && + wsp->ws_cur_in_flight < SCST_MAX_IN_FLIGHT_INTERNAL_COMMANDS) { + rc = scst_ws_push_single_write(wsp, wsp->ws_cur_lba, + wsp->ws_left_to_send); + if (rc != 0) + goto out_err; + + cnt++; + } + + if ((wsp->ws_left_to_send == 0) && + (wsp->ws_descriptors[wsp->ws_cur_descr+1].sdd_blocks != 0)) { + wsp->ws_cur_descr++; + wsp->ws_cur_lba = wsp->ws_descriptors[wsp->ws_cur_descr].sdd_lba; + wsp->ws_left_to_send = wsp->ws_descriptors[wsp->ws_cur_descr].sdd_blocks; + TRACE_DBG("wsp %p, cur descr %d, cur lba %lld, left %d", + wsp, wsp->ws_cur_descr, (long long)wsp->ws_cur_lba, + wsp->ws_left_to_send); + goto again; + } out_wake: if (cnt != 0) @@ -6015,21 +6251,79 @@ out_err: } } +static int scst_ws_sg_init(struct scatterlist **ws_sg, int ws_sg_cnt, + struct page *pg, unsigned int offset, unsigned int length, + unsigned int total_bytes) +{ + struct scatterlist *sg; + int i; + + *ws_sg = kmalloc_array(ws_sg_cnt, sizeof(**ws_sg), GFP_KERNEL); + if (*ws_sg == NULL) { + PRINT_ERROR("Unable to alloc sg for %d entries", ws_sg_cnt); + return -ENOMEM; + } + sg_init_table(*ws_sg, ws_sg_cnt); + for_each_sg(*ws_sg, sg, ws_sg_cnt, i) { + u32 len = min(total_bytes, length); + + sg_set_page(sg, pg, len, offset); + total_bytes -= len; + } + sBUG_ON(total_bytes != 0); /* crash here to avoid data corruption */ + + return 0; +} + +static int scst_ws_sg_tails_get(struct scst_data_descriptor *where, struct scst_write_same_priv *wsp) +{ + int i; + + TRACE_ENTRY(); + + for (i = 0; where[i].sdd_blocks != 0; i++) { + if (where[i].sdd_blocks / wsp->ws_max_each != 0) { + wsp->ws_sg_full_cnt = wsp->ws_max_each; + break; + } + } + while (where[i].sdd_blocks != 0) + i++; + + wsp->ws_sg_tails = kcalloc(i + 1, sizeof(*wsp->ws_sg_tails), + GFP_KERNEL); + if (wsp->ws_sg_tails == NULL) { + PRINT_ERROR("Unable to allocate ws_sg_tails (size %zd)", + sizeof(*wsp->ws_sg_tails)*i); + return -ENOMEM; + } + for (i = 0; where[i].sdd_blocks != 0; i++) + wsp->ws_sg_tails[i].sg_cnt = where[i].sdd_blocks % wsp->ws_max_each; + + TRACE_EXIT(); + return 0; +} + /* * Library function to perform WRITE SAME in a generic manner. On exit, cmd * always completed with sense set, if necessary. + * + * Parameter "where" is an array of descriptors where to write the same + * block. Last element in this array has len 0. It must be allocated by + * k?alloc() and will be kfree() by this function on finish. */ -void scst_write_same(struct scst_cmd *cmd) +void scst_write_same(struct scst_cmd *cmd, struct scst_data_descriptor *where) { struct scst_write_same_priv *wsp; int i, rc; struct page *pg = NULL; - struct scatterlist *sg; - unsigned int offset, length, mult, ws_sg_blocks, left; + unsigned int offset, length, mult, ws_sg_full_blocks, ws_sg_tail_blocks; uint8_t ctrl_offs = (cmd->cdb_len < 32) ? 1 : 10; TRACE_ENTRY(); + scst_set_exec_time(cmd); + if (unlikely(cmd->data_len <= 0)) { scst_set_invalid_field_in_cdb(cmd, cmd->len_off, 0); goto out_done; @@ -6058,6 +6352,20 @@ void scst_write_same(struct scst_cmd *cmd) goto out_done; } + if (where == NULL) { + where = kzalloc(sizeof(*where) << 1, GFP_KERNEL); + if (where == NULL) { + PRINT_ERROR("Unable to allocate ws_priv (size %zd, cmd %p)", + sizeof(*where) << 1, cmd); + goto out_busy; + } + where->sdd_lba = cmd->lba; + where->sdd_blocks = cmd->data_len >> cmd->dev->block_shift; + } + + if (unlikely(where->sdd_blocks == 0)) + goto out_done; + rc = scst_dif_process_write(cmd); if (unlikely(rc != 0)) goto out_done; @@ -6073,10 +6381,15 @@ void scst_write_same(struct scst_cmd *cmd) wsp->ws_finish_fn = scst_ws_write_cmd_finished; wsp->ws_orig_cmd = cmd; - wsp->ws_cur_lba = cmd->lba; - wsp->ws_left_to_send = cmd->data_len >> cmd->dev->block_shift; + wsp->ws_descriptors = where; + wsp->ws_cur_descr = 0; + wsp->ws_cur_lba = where[0].sdd_lba; + wsp->ws_left_to_send = where[0].sdd_blocks; wsp->ws_max_each = SCST_MAX_EACH_INTERNAL_IO_SIZE >> cmd->dev->block_shift; + if (scst_ws_sg_tails_get(where, wsp) == -ENOMEM) + goto out_busy; + if (cmd->bufflen <= PAGE_SIZE / 2) pg = alloc_page(GFP_KERNEL); if (pg) { @@ -6101,22 +6414,28 @@ void scst_write_same(struct scst_cmd *cmd) mult = 1; } - ws_sg_blocks = min_t(int, wsp->ws_left_to_send, wsp->ws_max_each); - wsp->ws_sg_cnt = (ws_sg_blocks + mult - 1) / mult; - wsp->ws_sg = kmalloc(wsp->ws_sg_cnt * sizeof(*wsp->ws_sg), GFP_KERNEL); - if (wsp->ws_sg == NULL) { - PRINT_ERROR("Unable to alloc sg for %d entries", wsp->ws_sg_cnt); - goto out_free; + if (wsp->ws_sg_full_cnt != 0) { + ws_sg_full_blocks = wsp->ws_sg_full_cnt; + wsp->ws_sg_full_cnt = (ws_sg_full_blocks + mult - 1) / mult; + TRACE_DBG("Allocating %d SGs", wsp->ws_sg_full_cnt); + rc = scst_ws_sg_init(&wsp->ws_sg_full, wsp->ws_sg_full_cnt, pg, + offset, length, + ws_sg_full_blocks << cmd->dev->block_shift); + if (rc != 0) + goto out_free; } - sg_init_table(wsp->ws_sg, wsp->ws_sg_cnt); - left = ws_sg_blocks << cmd->dev->block_shift; - for_each_sg(wsp->ws_sg, sg, wsp->ws_sg_cnt, i) { - u32 len = min(left, length); - - sg_set_page(sg, pg, len, offset); - left -= len; + for (i = 0; wsp->ws_descriptors[i].sdd_blocks != 0; i++) { + if (wsp->ws_sg_tails[i].sg_cnt != 0) { + ws_sg_tail_blocks = wsp->ws_sg_tails[i].sg_cnt; + wsp->ws_sg_tails[i].sg_cnt = (ws_sg_tail_blocks + mult - 1) / mult; + TRACE_DBG("Allocating %d tail SGs for descriptor[%d] ", wsp->ws_sg_tails[i].sg_cnt, i); + rc = scst_ws_sg_init(&wsp->ws_sg_tails[i].sg, wsp->ws_sg_tails[i].sg_cnt, pg, + offset, length, + ws_sg_tail_blocks << cmd->dev->block_shift); + if (rc != 0) + goto out_free; + } } - WARN_ON_ONCE(left != 0); if (scst_cmd_needs_dif_buf(cmd)) { struct t10_pi_tuple *t; @@ -6139,7 +6458,7 @@ void scst_write_same(struct scst_cmd *cmd) wsp->inc_ref_tag = 0; break; default: - sBUG_ON(1); + sBUG(); break; } @@ -6161,11 +6480,266 @@ out_busy: scst_set_busy(cmd); out_done: + kfree(where); cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_THREAD); goto out; } EXPORT_SYMBOL_GPL(scst_write_same); +struct scst_cwr_priv { + /* Must be the first for scst_finish_internal_cmd()! */ + scst_i_finish_fn_t cwr_finish_fn; + + struct scst_cmd *cwr_orig_cmd; +}; + +static void scst_cwr_finished(struct scst_cwr_priv *cwrp) +{ + struct scst_cmd *cwr_cmd = cwrp->cwr_orig_cmd; + + TRACE_ENTRY(); + + TRACE_DBG("cwr cmd %p finished with status %d", cwr_cmd, cwr_cmd->status); + + kfree(cwrp); + + cwr_cmd->completed = 1; /* for success */ + cwr_cmd->scst_cmd_done(cwr_cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_THREAD); + + TRACE_EXIT(); + return; +} + +static void scst_cwr_write_cmd_finished(struct scst_cmd *cmd) +{ + struct scst_cwr_priv *cwrp = cmd->tgt_i_priv; + struct scst_cmd *cwr_cmd = cwrp->cwr_orig_cmd; + + TRACE_ENTRY(); + + TRACE_DBG("WRITE cmd %p finished (cwr cmd %p)", cmd, cwr_cmd); + + EXTRACHECKS_BUG_ON(cmd->cdb[0] != WRITE_16); + + if (cmd->status != 0) { + int rc; + + TRACE_DBG("WRITE cmd %p (cwr cmd %p) finished not successfully", + cmd, cwr_cmd); + sBUG_ON(cmd->resp_data_len != 0); + if (cmd->status == SAM_STAT_CHECK_CONDITION) + rc = scst_set_cmd_error_sense(cwr_cmd, cmd->sense, + cmd->sense_valid_len); + else { + sBUG_ON(cmd->sense != NULL); + rc = scst_set_cmd_error_status(cwr_cmd, cmd->status); + } + if (rc != 0) { + /* Requeue possible UA */ + if (scst_is_ua_sense(cmd->sense, cmd->sense_valid_len)) + scst_requeue_ua(cmd, NULL, 0); + } + } + + scst_cwr_finished(cwrp); + + TRACE_EXIT(); + return; +} + +static void scst_cwr_read_cmd_finished(struct scst_cmd *cmd) +{ + struct scst_cwr_priv *cwrp = cmd->tgt_i_priv; + struct scst_cmd *cwr_cmd = cwrp->cwr_orig_cmd; + bool c, dif; + uint8_t write16_cdb[16]; + struct scst_cmd *wcmd; + int data_len, miscompare_offs, rc; + + TRACE_ENTRY(); + + TRACE_DBG("READ cmd %p finished (cwr cmd %p)", cmd, cwr_cmd); + + if (cmd->status != 0) { + int rc; + + TRACE_DBG("Read cmd %p (cwr cmd %p) finished not successfully", + cmd, cwr_cmd); + sBUG_ON(cmd->resp_data_len != 0); + if (cmd->status == SAM_STAT_CHECK_CONDITION) + rc = scst_set_cmd_error_sense(cwr_cmd, cmd->sense, + cmd->sense_valid_len); + else { + sBUG_ON(cmd->sense != NULL); + rc = scst_set_cmd_error_status(cwr_cmd, cmd->status); + } + if (rc != 0) { + /* Requeue possible UA */ + if (scst_is_ua_sense(cmd->sense, cmd->sense_valid_len)) + scst_requeue_ua(cmd, NULL, 0); + } + goto out_finish; + } + + if (unlikely(test_bit(SCST_CMD_ABORTED, &cwr_cmd->cmd_flags))) { + TRACE_MGMT_DBG("cwr cmd %p aborted", cwr_cmd); + goto out_finish; + } + + c = sg_cmp(cmd->sg, cwr_cmd->sg, 0, 0, &miscompare_offs +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 4, 0) + , KM_USER0, KM_USER1 +#endif + ); + if (!c) { + scst_set_cmd_error_and_inf(cwr_cmd, + SCST_LOAD_SENSE(scst_sense_miscompare_error), miscompare_offs); + goto out_finish; + } + + data_len = cwr_cmd->data_len; + + /* We ignore the read PI checks as required by SBC */ + rc = scst_dif_process_write(cmd); + if (unlikely(rc != 0)) + goto out_finish; + + memset(write16_cdb, 0, sizeof(write16_cdb)); + write16_cdb[0] = WRITE_16; + write16_cdb[1] = cwr_cmd->cdb[1]; + put_unaligned_be64(cwr_cmd->lba, &write16_cdb[2]); + put_unaligned_be32(data_len >> cmd->dev->block_shift, &write16_cdb[10]); + + dif = scst_cmd_needs_dif_buf(cwr_cmd) && ((cwr_cmd->cdb[1] & 0xE0) != 0); + if (dif) + write16_cdb[1] |= cwr_cmd->cdb[1] & 0xE0; + + wcmd = scst_create_prepare_internal_cmd(cwr_cmd, write16_cdb, + sizeof(write16_cdb), SCST_CMD_QUEUE_HEAD_OF_QUEUE); + if (wcmd == NULL) + goto out_busy; + + wcmd->expected_data_direction = SCST_DATA_WRITE; + wcmd->expected_transfer_len_full = data_len; + if (dif) + wcmd->expected_transfer_len_full += + data_len >> (cmd->dev->block_shift - SCST_DIF_TAG_SHIFT); + wcmd->expected_values_set = 1; + + wcmd->tgt_i_priv = cwrp; + + rc = scst_adjust_sg_get_tail(cwr_cmd, &wcmd->tgt_i_sg, + &wcmd->tgt_i_sg_cnt, &wcmd->tgt_i_dif_sg, + &wcmd->tgt_i_dif_sg_cnt, data_len, data_len); + /* + * It must not happen, because get_cdb_info_compare_and_write() + * supposed to ensure that. + */ + EXTRACHECKS_BUG_ON(rc != 0); + + wcmd->tgt_i_data_buf_alloced = 1; + + cwrp->cwr_finish_fn = scst_cwr_write_cmd_finished; + + TRACE_DBG("Adding WRITE(16) wcmd %p to head of active cmd list", wcmd); + spin_lock_irq(&wcmd->cmd_threads->cmd_list_lock); + list_add(&wcmd->cmd_list_entry, &wcmd->cmd_threads->active_cmd_list); + wake_up(&wcmd->cmd_threads->cmd_list_waitQ); + spin_unlock_irq(&wcmd->cmd_threads->cmd_list_lock); + +out: + TRACE_EXIT(); + return; + +out_busy: + scst_set_busy(cwr_cmd); + +out_finish: + scst_cwr_finished(cwrp); + goto out; +} + +int scst_cmp_wr_local(struct scst_cmd *cmd) +{ + int res = SCST_EXEC_COMPLETED; + struct scst_cwr_priv *cwrp; + uint8_t read16_cdb[16]; + struct scst_cmd *rcmd; + int data_len; + + TRACE_ENTRY(); + + /* COMPARE AND WRITE is SBC only command */ + EXTRACHECKS_BUG_ON(cmd->dev->type != TYPE_DISK); + + cmd->status = 0; + cmd->msg_status = 0; + cmd->host_status = DID_OK; + cmd->driver_status = 0; + + if (unlikely(cmd->bufflen == 0)) { + TRACE(TRACE_MINOR, "Zero bufflen (cmd %p)", cmd); + goto out_done; + } + + /* ToDo: HWALIGN'ed kmem_cache */ + cwrp = kzalloc(sizeof(*cwrp), GFP_KERNEL); + if (cwrp == NULL) { + PRINT_ERROR("Unable to allocate cwr_priv (size %zd, cmd %p)", + sizeof(*cwrp), cmd); + goto out_busy; + } + + cwrp->cwr_orig_cmd = cmd; + cwrp->cwr_finish_fn = scst_cwr_read_cmd_finished; + + data_len = cmd->data_len; + + /* + * As required by SBC, DIF PI, if any, is not checked for the read part + */ + + memset(read16_cdb, 0, sizeof(read16_cdb)); + read16_cdb[0] = READ_16; + read16_cdb[1] = cmd->cdb[1] & ~0xE0; /* as required, see above */ + put_unaligned_be64(cmd->lba, &read16_cdb[2]); + put_unaligned_be32(data_len >> cmd->dev->block_shift, &read16_cdb[10]); + + rcmd = scst_create_prepare_internal_cmd(cmd, read16_cdb, + sizeof(read16_cdb), SCST_CMD_QUEUE_HEAD_OF_QUEUE); + if (rcmd == NULL) { + res = -ENOMEM; + goto out_free; + } + + rcmd->expected_data_direction = SCST_DATA_READ; + rcmd->expected_transfer_len_full = data_len; + rcmd->expected_values_set = 1; + + rcmd->tgt_i_priv = cwrp; + + TRACE_DBG("Adding READ(16) cmd %p to head of active cmd list", rcmd); + spin_lock_irq(&rcmd->cmd_threads->cmd_list_lock); + list_add(&rcmd->cmd_list_entry, &rcmd->cmd_threads->active_cmd_list); + wake_up(&rcmd->cmd_threads->cmd_list_waitQ); + spin_unlock_irq(&rcmd->cmd_threads->cmd_list_lock); + +out: + TRACE_EXIT_RES(res); + return res; + +out_free: + kfree(cwrp); + +out_busy: + scst_set_busy(cmd); + +out_done: + cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_THREAD); + goto out; +} + + int scst_finish_internal_cmd(struct scst_cmd *cmd) { int res; @@ -6197,6 +6771,7 @@ int scst_finish_internal_cmd(struct scst_cmd *cmd) scst_complete_request_sense(cmd); else { scst_i_finish_fn_t f = (void *) *((unsigned long long **)cmd->tgt_i_priv); + f(cmd); } @@ -6300,6 +6875,7 @@ struct scst_session *scst_alloc_session(struct scst_tgt *tgt, gfp_t gfp_mask, mutex_init(&sess->tgt_dev_list_mutex); for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { struct list_head *head = &sess->sess_tgt_dev_list[i]; + INIT_LIST_HEAD(head); } spin_lock_init(&sess->sess_list_lock); @@ -6307,6 +6883,9 @@ struct scst_session *scst_alloc_session(struct scst_tgt *tgt, gfp_t gfp_mask, sess->tgt = tgt; INIT_LIST_HEAD(&sess->init_deferred_cmd_list); INIT_LIST_HEAD(&sess->init_deferred_mcmd_list); + INIT_LIST_HEAD(&sess->sess_cm_list_id_list); + INIT_DELAYED_WORK(&sess->sess_cm_list_id_cleanup_work, + (void (*)(struct work_struct *))sess_cm_list_id_cleanup_work_fn); #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20)) INIT_DELAYED_WORK(&sess->hw_pending_work, scst_hw_pending_work_fn); #else @@ -6512,6 +7091,7 @@ int scst_pre_init_cmd(struct scst_cmd *cmd, const uint8_t *cdb, { int i; uint8_t *b = (uint8_t *)cmd; + for (i = 0; i < sizeof(*cmd); i++) EXTRACHECKS_BUG_ON(b[i] != 0); } @@ -6634,6 +7214,60 @@ static void scst_destroy_cmd(struct scst_cmd *cmd) return; } +/* No locks */ +void scst_stpg_del_unblock_next(struct scst_cmd *cmd) +{ + struct scst_cmd *c; + + TRACE_ENTRY(); + + spin_lock_irq(&scst_global_stpg_list_lock); + + EXTRACHECKS_BUG_ON(!cmd->cmd_on_global_stpg_list); + + TRACE_DBG("STPG cmd %p: unblocking next", cmd); + + list_del(&cmd->global_stpg_list_entry); + cmd->cmd_on_global_stpg_list = 0; + + if (list_empty(&scst_global_stpg_list)) { + TRACE_DBG("No more STPG commands to unblock"); + spin_unlock_irq(&scst_global_stpg_list_lock); + goto out; + } + + c = list_first_entry(&scst_global_stpg_list, typeof(*c), + global_stpg_list_entry); + + spin_unlock_irq(&scst_global_stpg_list_lock); + + spin_lock_bh(&c->dev->dev_lock); + + if (!c->cmd_global_stpg_blocked) { + TRACE_DBG("STPG cmd %p is not cmd_global_stpg_blocked", c); + spin_unlock_bh(&c->dev->dev_lock); + goto out; + } + + TRACE_BLOCK("Unblocking serialized STPG cmd %p", c); + + list_del(&c->blocked_cmd_list_entry); + c->cmd_global_stpg_blocked = 0; + + spin_unlock_bh(&c->dev->dev_lock); + + spin_lock_irq(&c->cmd_threads->cmd_list_lock); + list_add(&c->cmd_list_entry, + &c->cmd_threads->active_cmd_list); + wake_up(&c->cmd_threads->cmd_list_waitQ); + spin_unlock_irq(&c->cmd_threads->cmd_list_lock); + +out: + TRACE_EXIT(); + return; +} +EXPORT_SYMBOL_GPL(scst_stpg_del_unblock_next); + /* No locks supposed to be held */ void scst_free_cmd(struct scst_cmd *cmd) { @@ -6647,7 +7281,8 @@ void scst_free_cmd(struct scst_cmd *cmd) if (unlikely(test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags))) TRACE_MGMT_DBG("Freeing aborted cmd %p", cmd); - EXTRACHECKS_BUG_ON(cmd->unblock_dev || cmd->dec_on_dev_needed); + EXTRACHECKS_BUG_ON(cmd->unblock_dev || cmd->dec_on_dev_needed || + cmd->on_dev_exec_list); /* * Target driver can already free sg buffer before calling @@ -6658,6 +7293,7 @@ void scst_free_cmd(struct scst_cmd *cmd) if (likely(cmd->dev != NULL)) { struct scst_dev_type *devt = cmd->devt; + if (devt->on_free_cmd != NULL) { TRACE_DBG("Calling dev handler %s on_free_cmd(%p)", devt->name, cmd); @@ -7689,6 +8325,246 @@ EXPORT_SYMBOL(scst_scsi_exec_async); #endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 30) */ +/* + * Can switch to the next dst_sg element, so, to cmp to strictly only + * one dst_sg element, it must be either last in the chain, or + * cmp_len == dst_sg->length. + */ +static int sg_cmp_elem(struct scatterlist **pdst_sg, size_t *pdst_len, + size_t *pdst_offs, struct scatterlist *src_sg, + size_t cmp_len, int *miscompare_offs, +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 4, 0) + enum km_type d_km_type, enum km_type s_km_type, +#endif + bool *cmp_res) +{ + int res = 0; + struct scatterlist *dst_sg; + size_t src_len, dst_len, src_offs, dst_offs; + struct page *src_page, *dst_page; + void *saddr, *daddr; + + *cmp_res = true; + + dst_sg = *pdst_sg; + dst_len = *pdst_len; + dst_offs = *pdst_offs; + dst_page = sg_page(dst_sg); + + src_page = sg_page(src_sg); + src_len = src_sg->length; + src_offs = src_sg->offset; + + do { + size_t n; + int rc; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 4, 0) + saddr = kmap_atomic(src_page + (src_offs >> PAGE_SHIFT), s_km_type) + + (src_offs & ~PAGE_MASK); + daddr = kmap_atomic(dst_page + (dst_offs >> PAGE_SHIFT), d_km_type) + + (dst_offs & ~PAGE_MASK); +#else + saddr = kmap_atomic(src_page + (src_offs >> PAGE_SHIFT)) + + (src_offs & ~PAGE_MASK); + daddr = kmap_atomic(dst_page + (dst_offs >> PAGE_SHIFT)) + + (dst_offs & ~PAGE_MASK); +#endif + + if (((src_offs & ~PAGE_MASK) == 0) && + ((dst_offs & ~PAGE_MASK) == 0) && + (src_len >= PAGE_SIZE) && (dst_len >= PAGE_SIZE) && + (cmp_len >= PAGE_SIZE)) + n = PAGE_SIZE; + else { + n = min_t(size_t, PAGE_SIZE - (dst_offs & ~PAGE_MASK), + PAGE_SIZE - (src_offs & ~PAGE_MASK)); + n = min(n, src_len); + n = min(n, dst_len); + n = min_t(size_t, n, cmp_len); + } + + /* Optimized for rare miscompares */ + + rc = memcmp(daddr, saddr, n); + if (rc != 0) { + *cmp_res = false; + if (miscompare_offs != NULL) { + int i, len = n; + unsigned long saddr_int = (unsigned long)saddr; + unsigned long daddr_int = (unsigned long)daddr; + int align_size = sizeof(unsigned long long); + int align_mask = align_size-1; + + *miscompare_offs = -1; + + if (((saddr_int & align_mask) == 0) && + ((daddr_int & align_mask) == 0) && + ((len & align_mask) == 0)) { + /* Fast path: both buffers and len aligned */ + unsigned long long *s = saddr; + unsigned long long *d = daddr; + + EXTRACHECKS_BUG_ON(len % align_size != 0); + len /= align_size; + for (i = 0; i < len; i++) { + if (s[i] != d[i]) { + uint8_t *s8 = (uint8_t *)&s[i]; + uint8_t *d8 = (uint8_t *)&d[i]; + int j; + + for (j = 0; j < align_size; j++) { + if (s8[j] != d8[j]) { + *miscompare_offs = i * align_size + j; + break; + } + } + break; + } + } + } else { + uint8_t *s = saddr; + uint8_t *d = daddr; + + for (i = 0; i < len; i++) { + if (s[i] != d[i]) { + *miscompare_offs = i; + break; + } + } + } + EXTRACHECKS_BUG_ON(*miscompare_offs == -1); + } + goto out_unmap; + } + + dst_offs += n; + src_offs += n; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 4, 0) + kunmap_atomic(saddr, s_km_type); + kunmap_atomic(daddr, d_km_type); +#else + kunmap_atomic(saddr); + kunmap_atomic(daddr); +#endif + + res += n; + cmp_len -= n; + if (cmp_len == 0) + goto out; + + src_len -= n; + dst_len -= n; + if (dst_len == 0) { + dst_sg = sg_next_inline(dst_sg); + if (dst_sg == NULL) + goto out; + dst_page = sg_page(dst_sg); + dst_len = dst_sg->length; + dst_offs = dst_sg->offset; + } + } while (src_len > 0); + +out: + *pdst_sg = dst_sg; + *pdst_len = dst_len; + *pdst_offs = dst_offs; + return res; + +out_unmap: +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 4, 0) + kunmap_atomic(saddr, s_km_type); + kunmap_atomic(daddr, d_km_type); +#else + kunmap_atomic(saddr); + kunmap_atomic(daddr); +#endif + goto out; +} + +/** + * sg_cmp - compare 2 SG vectors + * @dst_sg: SG 1 + * @src_sg: SG 2 + * @nents_to_cmp: maximum number of entries to compare + * @cmp_len: maximum amount of data to compare. If 0, then compare all. + * @miscompare_offs: offset of the first miscompare. Can be NULL. + * @d_km_type: kmap_atomic type for SG 1 + * @s_km_type: kmap_atomic type for SG 2 + * + * Description: + * Data from the first SG vector will be compired with the second SG + * vector. End of the vectors will be determined by sg_next() returning + * NULL. Returns true if both vectors have identical data, false otherwise. + * + * Note! It ignores size of the vectors, so SGs with different size with + * the same data in min(sg1_size, sg2_size) size will match! + */ +static bool sg_cmp(struct scatterlist *dst_sg, struct scatterlist *src_sg, + int nents_to_cmp, size_t cmp_len, int *miscompare_offs +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 4, 0) + , enum km_type d_km_type, enum km_type s_km_type +#endif + ) +{ + bool res = true; + size_t dst_len, dst_offs; + int compared_all = 0; + + TRACE_ENTRY(); + + TRACE_DBG("dst_sg %p, src_sg %p, nents_to_cmp %d, cmp_len %zd", + dst_sg, src_sg, nents_to_cmp, cmp_len); + + if (cmp_len == 0) + cmp_len = 0x7FFFFFFF; /* cmp all */ + + if (nents_to_cmp == 0) + nents_to_cmp = 0x7FFFFFFF; /* cmp all */ + + dst_len = dst_sg->length; + dst_offs = dst_sg->offset; + + do { + int compared; + + TRACE_DBG("dst_sg %p, dst_len %zd, dst_offs %zd, src_sg %p, " + "nents_to_cmp %d, cmp_len %zd, compared_all %d", + dst_sg, dst_len, dst_offs, src_sg, nents_to_cmp, + cmp_len, compared_all); + + compared = sg_cmp_elem(&dst_sg, &dst_len, &dst_offs, + src_sg, cmp_len, miscompare_offs, +#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 4, 0) + d_km_type, s_km_type, +#endif + &res); + if (!res) { + if (miscompare_offs != NULL) { + *miscompare_offs += compared_all; + TRACE_DBG("miscompare_offs %d", + *miscompare_offs); + } + goto out; + } + cmp_len -= compared; + compared_all += compared; + if ((cmp_len == 0) || (dst_sg == NULL)) + goto out; + + nents_to_cmp--; + if (nents_to_cmp == 0) + goto out; + + src_sg = sg_next_inline(src_sg); + } while (src_sg != NULL); + +out: + TRACE_EXIT_RES(res); + return res; +} + /** * scst_copy_sg() - copy data between the command's SGs * @@ -8019,6 +8895,7 @@ static int scst_verify_dif_type1(struct scst_cmd *cmd) if ((checks & SCST_DIF_CHECK_GUARD_TAG) && !cmd->internal) { __be16 crc = crc_fn(cur_buf, block_size); + if (t->guard_tag != crc) { PRINT_WARNING("GUARD TAG check failed, " "expected 0x%x, seeing 0x%x " @@ -8341,6 +9218,7 @@ static int scst_do_dif(struct scst_cmd *cmd, case SCST_DIF_ACTION_PASS_CHECK: { enum scst_dif_actions checks = scst_get_dif_checks(cmd->cmd_dif_actions); + if (likely(checks != SCST_DIF_ACTION_NONE)) res = verify_fn(cmd); else @@ -8467,6 +9345,7 @@ static int scst_verify_dif_type2(struct scst_cmd *cmd) if ((checks & SCST_DIF_CHECK_GUARD_TAG) && !cmd->internal) { __be16 crc = crc_fn(cur_buf, block_size); + if (t->guard_tag != crc) { PRINT_WARNING("GUARD TAG check failed, " "expected 0x%x, seeing 0x%x " @@ -8684,6 +9563,7 @@ static int scst_verify_dif_type3(struct scst_cmd *cmd) if ((checks & SCST_DIF_CHECK_GUARD_TAG) && !cmd->internal) { __be16 crc = crc_fn(cur_buf, block_size); + if (t->guard_tag != crc) { PRINT_WARNING("GUARD TAG check failed, " "expected 0x%x, seeing 0x%x " @@ -10139,10 +11019,12 @@ static int get_cdb_info_lba_3_len_1_256_read(struct scst_cmd *cmd, const struct scst_sdbops *sdbops) { int res = get_cdb_info_lba_3_len_1_256(cmd, sdbops); + if (res != 0) goto out; else { struct scst_device *dev = cmd->dev; + if ((dev->dev_dif_mode != SCST_DIF_MODE_NONE) && !dev->dpicz) cmd->cmd_dif_actions = dev->dev_dif_rd_prot0_actions | SCST_DIF_CHECK_GUARD_TAG | dev->dif_app_chk | @@ -10156,10 +11038,12 @@ static int get_cdb_info_lba_3_len_1_256_write(struct scst_cmd *cmd, const struct scst_sdbops *sdbops) { int res = get_cdb_info_lba_3_len_1_256(cmd, sdbops); + if (res != 0) goto out; else { struct scst_device *dev = cmd->dev; + if (dev->dev_dif_mode != SCST_DIF_MODE_NONE) cmd->cmd_dif_actions = dev->dev_dif_wr_prot0_actions | SCST_DIF_CHECK_GUARD_TAG | dev->dif_app_chk | @@ -10267,6 +11151,7 @@ static int get_cdb_info_read_10(struct scst_cmd *cmd, const struct scst_sdbops *sdbops) { int res = get_cdb_info_lba_4_len_2(cmd, sdbops); + if (res != 0) return res; else { @@ -10282,6 +11167,7 @@ static int get_cdb_info_lba_4_len_2_wrprotect(struct scst_cmd *cmd, const struct scst_sdbops *sdbops) { int res = get_cdb_info_lba_4_len_2(cmd, sdbops); + if (res != 0) return res; else @@ -10307,6 +11193,7 @@ static int get_cdb_info_lba_4_len_4_rdprotect(struct scst_cmd *cmd, const struct scst_sdbops *sdbops) { int res = get_cdb_info_lba_4_len_4(cmd, sdbops); + if (res != 0) return res; else @@ -10317,6 +11204,7 @@ static int get_cdb_info_lba_4_len_4_wrprotect(struct scst_cmd *cmd, const struct scst_sdbops *sdbops) { int res = get_cdb_info_lba_4_len_4(cmd, sdbops); + if (res != 0) return res; else @@ -10342,6 +11230,7 @@ static int get_cdb_info_read_16(struct scst_cmd *cmd, const struct scst_sdbops *sdbops) { int res = get_cdb_info_lba_8_len_4(cmd, sdbops); + if (res != 0) return res; else { @@ -10357,6 +11246,7 @@ static int get_cdb_info_lba_8_len_4_wrprotect(struct scst_cmd *cmd, const struct scst_sdbops *sdbops) { int res = get_cdb_info_lba_8_len_4(cmd, sdbops); + if (res != 0) return res; else @@ -10367,6 +11257,7 @@ static int get_cdb_info_lba_8_len_4_wrprotect32(struct scst_cmd *cmd, const struct scst_sdbops *sdbops) { int res = get_cdb_info_lba_8_len_4(cmd, sdbops); + if (res != 0) return res; else @@ -10377,6 +11268,7 @@ static int get_cdb_info_lba_8_len_4_rdprotect32(struct scst_cmd *cmd, const struct scst_sdbops *sdbops) { int res = get_cdb_info_lba_8_len_4(cmd, sdbops); + if (res != 0) return res; else @@ -10561,6 +11453,20 @@ static int get_cdb_info_compare_and_write(struct scst_cmd *cmd, return scst_parse_wrprotect(cmd); } +static int get_cdb_info_ext_copy(struct scst_cmd *cmd, + const struct scst_sdbops *sdbops) +{ + if (unlikely(cmd->cdb[1] != 0)) { + PRINT_WARNING("Not supported %s service action 0x%x", + scst_get_opcode_name(cmd), cmd->cdb[1]); + scst_set_invalid_field_in_cdb(cmd, 1, + 0 | SCST_INVAL_FIELD_BIT_OFFS_VALID); + return 1; + } + + return get_cdb_info_len_4(cmd, sdbops); +} + /** * get_cdb_info_apt() - Parse ATA PASS-THROUGH CDB. * @@ -10688,6 +11594,29 @@ static int get_cdb_info_min(struct scst_cmd *cmd, return get_cdb_info_len_4(cmd, sdbops); } +/* Parse MAINTENANCE (OUT) */ +static int get_cdb_info_mo(struct scst_cmd *cmd, + const struct scst_sdbops *sdbops) +{ + switch (cmd->cdb[1] & 0x1f) { + case MO_SET_TARGET_PGS: + { + unsigned long flags; + + cmd->op_name = "SET TARGET PORT GROUPS"; + cmd->op_flags |= SCST_STRICTLY_SERIALIZED; + spin_lock_irqsave(&scst_global_stpg_list_lock, flags); + TRACE_DBG("Adding STPG cmd %p to global_stpg_list", cmd); + cmd->cmd_on_global_stpg_list = 1; + list_add_tail(&cmd->global_stpg_list_entry, &scst_global_stpg_list); + spin_unlock_irqrestore(&scst_global_stpg_list_lock, flags); + break; + } + } + + return get_cdb_info_len_4(cmd, sdbops); +} + /** * scst_get_cdb_info() - fill various info about the command's CDB * @@ -11039,6 +11968,7 @@ int scst_tape_generic_parse(struct scst_cmd *cmd) if (cmd->op_flags & SCST_TRANSFER_LEN_TYPE_FIXED && cmd->cdb[1] & 1) { int block_size = cmd->dev->block_size; + cmd->bufflen = cmd->bufflen * block_size; cmd->data_len = cmd->data_len * block_size; cmd->out_bufflen = cmd->out_bufflen * block_size; @@ -11477,6 +12407,7 @@ static void scst_check_internal_sense(struct scst_device *dev, int result, if (host_byte(result) == DID_RESET) { int sl; + TRACE(TRACE_MGMT, "DID_RESET received for device %s, " "triggering reset UA", dev->virt_name); sl = scst_set_sense(sense, sense_len, dev->d_sense, @@ -11661,6 +12592,7 @@ again: for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { struct list_head *head = &sess->sess_tgt_dev_list[i]; struct scst_tgt_dev *tgt_dev; + list_for_each_entry_rcu(tgt_dev, head, sess_tgt_dev_list_entry) { /* Lockdep triggers here a false positive.. */ @@ -11703,6 +12635,7 @@ again: list_for_each_entry_rcu(tgt_dev, head, sess_tgt_dev_list_entry) { struct scst_tgt_dev_UA *ua; + list_for_each_entry(ua, &tgt_dev->UA_list, UA_list_entry) { if (ua->global_UA && @@ -11734,6 +12667,7 @@ out_unlock: for (i = SESS_TGT_DEV_LIST_HASH_SIZE-1; i >= 0; i--) { struct list_head *head = &sess->sess_tgt_dev_list[i]; struct scst_tgt_dev *tgt_dev; + list_for_each_entry_rcu(tgt_dev, head, sess_tgt_dev_list_entry) { spin_unlock(&tgt_dev->tgt_dev_lock); @@ -12096,7 +13030,7 @@ bool __scst_check_blocked_dev(struct scst_cmd *cmd) TRACE_ENTRY(); EXTRACHECKS_BUG_ON(cmd->unblock_dev); - EXTRACHECKS_BUG_ON(cmd->internal); + EXTRACHECKS_BUG_ON(cmd->internal && cmd->cdb[0] != EXTENDED_COPY); if (unlikely(test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags))) goto out; @@ -12108,10 +13042,27 @@ bool __scst_check_blocked_dev(struct scst_cmd *cmd) scst_get_opcode_name(cmd), dev->virt_name); goto out_block; } else if ((cmd->op_flags & SCST_STRICTLY_SERIALIZED) == SCST_STRICTLY_SERIALIZED) { - TRACE_BLOCK("cmd %p (tag %llu, op %s): blocking further " - "cmds on dev %s due to strict serialization", cmd, - (unsigned long long int)cmd->tag, + TRACE_BLOCK("Strictly serialized cmd %p (tag %llu, op %s, dev %s)", + cmd, (unsigned long long int)cmd->tag, scst_get_opcode_name(cmd), dev->virt_name); + + if ((cmd->cdb[0] == MAINTENANCE_OUT) && + ((cmd->cdb[1] & 0x1f) == MO_SET_TARGET_PGS)) { + const struct scst_cmd *c; + /* + * It's OK to do that without lock, because we + * are interested only in comparison + */ + c = list_first_entry(&scst_global_stpg_list, typeof(*c), + global_stpg_list_entry); + if (cmd != c) { + TRACE_BLOCK("Blocking serialized STPG cmd %p " + "(head %p)", cmd, c); + cmd->cmd_global_stpg_blocked = 1; + goto out_block; + } + } + scst_block_dev(dev); if (dev->on_dev_cmd_count > 1) { TRACE_BLOCK("Delaying strictly serialized cmd %p " @@ -12154,7 +13105,7 @@ void scst_unblock_dev(struct scst_device *dev) { TRACE_ENTRY(); - TRACE_BLOCK("Device UNBLOCK(new %d), dev %s", + TRACE_BLOCK("Device UNBLOCK (new %d), dev %s", dev->block_count-1, dev->virt_name); if (--dev->block_count == 0) { @@ -12164,10 +13115,17 @@ void scst_unblock_dev(struct scst_device *dev) local_irq_save_nort(flags); list_for_each_entry_safe(cmd, tcmd, &dev->blocked_cmd_list, blocked_cmd_list_entry) { - bool strictly_serialized; + bool strictly_serialized = + ((cmd->op_flags & SCST_STRICTLY_SERIALIZED) == SCST_STRICTLY_SERIALIZED); + + if (dev->strictly_serialized_cmd_waiting && + !strictly_serialized) + continue; + list_del(&cmd->blocked_cmd_list_entry); - TRACE_BLOCK("Adding blocked cmd %p to active cmd " - "list", cmd); + cmd->cmd_global_stpg_blocked = 0; + + TRACE_BLOCK("Adding blocked cmd %p to active cmd list", cmd); spin_lock(&cmd->cmd_threads->cmd_list_lock); if (cmd->queue_type == SCST_CMD_QUEUE_HEAD_OF_QUEUE) list_add(&cmd->cmd_list_entry, @@ -12175,9 +13133,9 @@ void scst_unblock_dev(struct scst_device *dev) else list_add_tail(&cmd->cmd_list_entry, &cmd->cmd_threads->active_cmd_list); - strictly_serialized = ((cmd->op_flags & SCST_STRICTLY_SERIALIZED) == SCST_STRICTLY_SERIALIZED); wake_up(&cmd->cmd_threads->cmd_list_waitQ); spin_unlock(&cmd->cmd_threads->cmd_list_lock); + if (dev->strictly_serialized_cmd_waiting && strictly_serialized) break; } @@ -12846,7 +13804,7 @@ static int scst_parse_unmap_descriptors(struct scst_cmd *cmd) if (cnt == 0) goto out_put; - pd = kzalloc(sizeof(*pd) * cnt, GFP_KERNEL); + pd = kcalloc(cnt, sizeof(*pd), GFP_KERNEL); if (pd == NULL) { PRINT_ERROR("Unable to kmalloc UNMAP %d descriptors", cnt+1); scst_set_busy(cmd); @@ -12858,6 +13816,7 @@ static int scst_parse_unmap_descriptors(struct scst_cmd *cmd) i = 0; while ((offset - 8) < descriptor_len) { struct scst_data_descriptor *d = &pd[i]; + d->sdd_lba = get_unaligned_be64(&address[offset]); offset += 8; d->sdd_blocks = get_unaligned_be32(&address[offset]); @@ -12907,6 +13866,9 @@ int scst_parse_descriptors(struct scst_cmd *cmd) case UNMAP: res = scst_parse_unmap_descriptors(cmd); break; + case EXTENDED_COPY: + res = scst_cm_parse_descriptors(cmd); + break; default: sBUG(); } @@ -12923,6 +13885,9 @@ static void scst_free_descriptors(struct scst_cmd *cmd) case UNMAP: scst_free_unmap_descriptors(cmd); break; + case EXTENDED_COPY: + scst_cm_free_descriptors(cmd); + break; default: sBUG(); break; @@ -13223,11 +14188,7 @@ int scst_restore_global_mode_pages(struct scst_device *dev, char *params, if (scst_get_next_lexem(¶m)[0] != '\0') goto out_too_many; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtoul(pp, 0, &val); -#else - res = strict_strtoul(pp, 0, &val); -#endif if (res != 0) goto out_strtoul_failed; @@ -13279,6 +14240,247 @@ out_too_many: } EXPORT_SYMBOL_GPL(scst_restore_global_mode_pages); +/* Must be called under dev_lock and BHs off. Might release it, then reacquire. */ +void __scst_ext_blocking_done(struct scst_device *dev) +{ + bool stop; + + TRACE_ENTRY(); + + TRACE_BLOCK("Notifying ext blockers for dev %s (ext_blocks_cnt %d)", + dev->virt_name, dev->ext_blocks_cnt); + + stop = list_empty(&dev->ext_blockers_list); + while (!stop) { + struct scst_ext_blocker *b; + + b = list_first_entry(&dev->ext_blockers_list, typeof(*b), + ext_blockers_list_entry); + + TRACE_DBG("Notifying async ext blocker %p (cnt %d)", b, + dev->ext_blocks_cnt); + + list_del(&b->ext_blockers_list_entry); + + stop = list_empty(&dev->ext_blockers_list); + if (stop) + dev->ext_blocking_pending = 0; + + spin_unlock_bh(&dev->dev_lock); + + b->ext_blocker_done_fn(dev, b->ext_blocker_data, + b->ext_blocker_data_len); + + kfree(b); + + spin_lock_bh(&dev->dev_lock); + } + + TRACE_EXIT(); + return; +} + +static void scst_ext_blocking_done_fn(struct work_struct *work) +{ + struct scst_device *dev = container_of(work, struct scst_device, + ext_blockers_work); + + TRACE_ENTRY(); + + spin_lock_bh(&dev->dev_lock); + + WARN_ON(!dev->ext_unblock_scheduled); + + /* We might have new ext blocker any time w/o dev_lock */ + while (!list_empty(&dev->ext_blockers_list)) + __scst_ext_blocking_done(dev); + + WARN_ON(!dev->ext_unblock_scheduled); + dev->ext_unblock_scheduled = 0; + + spin_unlock_bh(&dev->dev_lock); + + TRACE_EXIT(); + return; +} + +/* Must be called under dev_lock and BHs off */ +void scst_ext_blocking_done(struct scst_device *dev) +{ + TRACE_ENTRY(); + + lockdep_assert_held(&dev->dev_lock); + + if (dev->ext_unblock_scheduled) + goto out; + + TRACE_DBG("Scheduling ext_blockers_work for dev %s", dev->virt_name); + + dev->ext_unblock_scheduled = 1; + schedule_work(&dev->ext_blockers_work); + +out: + TRACE_EXIT(); + return; +} + +static void scst_sync_ext_blocking_done(struct scst_device *dev, + uint8_t *data, int len) +{ + wait_queue_head_t *w; + + TRACE_ENTRY(); + + w = (void *)*((unsigned long *)data); + wake_up_all(w); + + TRACE_EXIT(); + return; +} + +int scst_ext_block_dev(struct scst_device *dev, bool sync, + ext_blocker_done_fn_t done_fn, const uint8_t *priv, int priv_len) +{ + int res; + struct scst_ext_blocker *b; + + TRACE_ENTRY(); + + if (sync) + priv_len = sizeof(void *); + + b = kzalloc(sizeof(*b) + priv_len, GFP_KERNEL); + if (b == NULL) { + PRINT_ERROR("Unable to alloc struct scst_ext_blocker with data " + "(size %zd)", sizeof(*b) + priv_len); + res = -ENOMEM; + goto out; + } + + TRACE_MGMT_DBG("New (%d) %s ext blocker %p for dev %s", dev->ext_blocks_cnt+1, + sync ? "sync" : "async", b, dev->virt_name); + + spin_lock_bh(&dev->dev_lock); + + if (dev->strictly_serialized_cmd_waiting) { + /* + * Avoid deadlock when this strictly serialized cmd + * will not proceed stopped on our blocking, so our + * blocking does not proceed as well. + */ + TRACE_DBG("Unstrictlyserialize dev %s", dev->virt_name); + dev->strictly_serialized_cmd_waiting = 0; + /* We will reuse blocking done by the strictly serialized cmd */ + } else + scst_block_dev(dev); + + dev->ext_blocks_cnt++; + TRACE_DBG("ext_blocks_cnt %d", dev->ext_blocks_cnt); + + if (sync && (dev->on_dev_cmd_count == 0)) { + TRACE_DBG("No commands to wait for sync blocking (dev %s)", + dev->virt_name); + spin_unlock_bh(&dev->dev_lock); + goto out_free_success; + } + + list_add_tail(&b->ext_blockers_list_entry, &dev->ext_blockers_list); + dev->ext_blocking_pending = 1; + + if (sync) { + DECLARE_WAIT_QUEUE_HEAD_ONSTACK(w); + + b->ext_blocker_done_fn = scst_sync_ext_blocking_done; + *((void **)&b->ext_blocker_data[0]) = &w; + + wait_event_locked(w, (dev->on_dev_cmd_count == 0), + lock_bh, dev->dev_lock); + + spin_unlock_bh(&dev->dev_lock); + } else { + b->ext_blocker_done_fn = done_fn; + if (priv_len > 0) { + b->ext_blocker_data_len = priv_len; + memcpy(b->ext_blocker_data, priv, priv_len); + } + if (dev->on_dev_cmd_count == 0) { + TRACE_DBG("No commands to wait for async blocking " + "(dev %s)", dev->virt_name); + if (!dev->ext_unblock_scheduled) + __scst_ext_blocking_done(dev); + spin_unlock_bh(&dev->dev_lock); + } else + spin_unlock_bh(&dev->dev_lock); + } + +out_success: + res = 0; + +out: + TRACE_EXIT_RES(res); + return res; + +out_free_success: + sBUG_ON(!sync); + kfree(b); + goto out_success; +} + +void scst_ext_unblock_dev(struct scst_device *dev, bool stpg) +{ + TRACE_ENTRY(); + + spin_lock_bh(&dev->dev_lock); + + if (dev->ext_blocks_cnt == 0) { + TRACE_DBG("Nothing to unblock (dev %s)", dev->virt_name); + goto out_unlock; + } + + if ((dev->ext_blocks_cnt == 1) && dev->stpg_ext_blocked && !stpg) { + TRACE_DBG("Can not unblock internal STPG ext block (dev %s)", + dev->virt_name); + goto out_unlock; + } + + dev->ext_blocks_cnt--; + TRACE_MGMT_DBG("Ext unblocking for dev %s (left: %d, pending %d)", + dev->virt_name, dev->ext_blocks_cnt, + dev->ext_blocking_pending); + + if ((dev->ext_blocks_cnt == 0) && dev->ext_blocking_pending) { + int rc; + /* Wait pending ext blocking to finish */ + spin_unlock_bh(&dev->dev_lock); + TRACE_DBG("Ext unblock (dev %s): still pending...", + dev->virt_name); + rc = scst_ext_block_dev(dev, true, NULL, NULL, 0); + if (rc != 0) { + /* Oops, have to poll */ + PRINT_WARNING("scst_ext_block_dev(dev %s) failed, " + "switch to polling", dev->virt_name); + spin_lock_bh(&dev->dev_lock); + while (dev->ext_blocking_pending) { + spin_unlock_bh(&dev->dev_lock); + msleep(10); + spin_lock_bh(&dev->dev_lock); + } + spin_unlock_bh(&dev->dev_lock); + } else { + TRACE_DBG("Ext unblock: pending done, unblocking..."); + scst_ext_unblock_dev(dev, stpg); + } + spin_lock_bh(&dev->dev_lock); + } + + scst_unblock_dev(dev); + +out_unlock: + spin_unlock_bh(&dev->dev_lock); + + TRACE_EXIT(); + return; +} /* Abstract vfs_unlink() for different kernel versions (as possible) */ #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 39) @@ -13359,11 +14561,11 @@ int scst_copy_file(const char *src, const char *dest) inode = file_inode(file_src); - if (S_ISREG(inode->i_mode)) - /* Nothing to do */; - else if (S_ISBLK(inode->i_mode)) + if (S_ISREG(inode->i_mode)) { + /* Nothing to do */ + } else if (S_ISBLK(inode->i_mode)) { inode = inode->i_bdev->bd_inode; - else { + } else { PRINT_ERROR("Invalid file mode 0x%x", inode->i_mode); res = -EINVAL; set_fs(old_fs); @@ -13558,11 +14760,11 @@ static int __scst_read_file_transactional(const char *file_name, inode = file_inode(file); - if (S_ISREG(inode->i_mode)) - /* Nothing to do */; - else if (S_ISBLK(inode->i_mode)) + if (S_ISREG(inode->i_mode)) { + /* Nothing to do */ + } else if (S_ISBLK(inode->i_mode)) { inode = inode->i_bdev->bd_inode; - else { + } else { PRINT_ERROR("Invalid file mode 0x%x", inode->i_mode); res = -EINVAL; goto out_close; @@ -13833,6 +15035,7 @@ static void tm_dbg_deinit_tgt_dev(struct scst_tgt_dev *tgt_dev) { if (tm_dbg_tgt_dev == tgt_dev) { unsigned long flags; + TRACE_MGMT_DBG("Deinit TM debugging tgt_dev %p", tgt_dev); del_timer_sync(&tm_dbg_timer); spin_lock_irqsave(&scst_tm_dbg_lock, flags); @@ -13857,6 +15060,7 @@ static void tm_dbg_delay_cmd(struct scst_cmd *cmd) case TM_DBG_STATE_ABORT: if (tm_dbg_delayed_cmds_count == 0) { unsigned long d = 58*HZ + (scst_random() % (4*HZ)); + TRACE_MGMT_DBG("STATE ABORT: delaying cmd %p (tag %llu)" " for %ld.%ld seconds (%ld HZ), " "tm_dbg_on_state_passes=%d", cmd, cmd->tag, @@ -13902,6 +15106,7 @@ void tm_dbg_check_released_cmds(void) { if (tm_dbg_flags.tm_dbg_release) { struct scst_cmd *cmd, *tc; + spin_lock_irq(&scst_tm_dbg_lock); list_for_each_entry_safe_reverse(cmd, tc, &tm_dbg_delayed_cmd_list, cmd_list_entry) { @@ -14083,6 +15288,7 @@ void scst_check_debug_sn(struct scst_cmd *cmd) /* To simulate from time to time queue flushing */ if (!in_interrupt() && (scst_random() % 120) == 8) { int t = scst_random() % 1200; + TRACE_SN("Delaying IO on %d ms", t); msleep(t); } @@ -14162,6 +15368,12 @@ void scst_set_pre_exec_time(struct scst_cmd *cmd) TRACE_DBG("cmd %p: pre_exec_time %lld", cmd, cmd->pre_exec_time); } +void scst_set_exec_start(struct scst_cmd *cmd) +{ + cmd->exec_time_counting = true; + scst_set_cur_start(cmd); +} + void scst_set_exec_time(struct scst_cmd *cmd) { cmd->exec_time += scst_get_usec() - cmd->curr_start; diff --git a/scst/src/scst_main.c b/scst/src/scst_main.c index 7b50d6fe9..4d05ac476 100644 --- a/scst/src/scst_main.c +++ b/scst/src/scst_main.c @@ -596,6 +596,7 @@ EXPORT_SYMBOL(scst_register_target); static inline int test_sess_list(struct scst_tgt *tgt) { int res; + mutex_lock(&scst_mutex); res = list_empty(&tgt->sysfs_sess_list); mutex_unlock(&scst_mutex); @@ -637,6 +638,7 @@ void scst_unregister_target(struct scst_tgt *tgt) again: { struct scst_session *sess; + list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) { if (sess->shut_phase == SCST_SESS_SPH_READY) { /* @@ -870,6 +872,7 @@ static void __printf(2, 3) scst_to_syslog(void *arg, const char *fmt, ...) int scst_get_cmd_counter(void) { int i, res = 0; + for (i = 0; i < (int)ARRAY_SIZE(scst_percpu_infos); i++) res += atomic_read(&scst_percpu_infos[i].cpu_cmd_count); return res; @@ -1467,6 +1470,10 @@ int scst_register_virtual_device(struct scst_dev_type *dev_handler, list_add_tail(&dev->dev_list_entry, &scst_dev_list); + res = scst_cm_on_dev_register(dev); + if (res != 0) + goto out_unreg; + mutex_unlock(&scst_mutex); scst_resume_activity(); @@ -1478,6 +1485,12 @@ out: TRACE_EXIT_RES(res); return res; +out_unreg: + dev->dev_unregistering = 1; + list_del(&dev->dev_list_entry); + scst_assign_dev_handler(dev, &scst_null_devtype); + goto out_pr_clear_dev; + #ifndef CONFIG_SCST_PROC out_lock_pr_clear_dev: mutex_lock(&scst_mutex); @@ -1528,6 +1541,10 @@ void scst_unregister_virtual_device(int id) goto out_unlock; } + dev->dev_unregistering = 1; + + scst_cm_on_dev_unregister(dev); + list_del_init(&dev->dev_list_entry); scst_pr_clear_dev(dev); @@ -1861,6 +1878,7 @@ int scst_add_threads(struct scst_cmd_threads *cmd_threads, if (tgt_dev != NULL) { struct scst_tgt_dev *t; + list_for_each_entry(t, &tgt_dev->dev->dev_tgt_dev_list, dev_tgt_dev_list_entry) { if (t == tgt_dev) @@ -2431,7 +2449,8 @@ static int __init init_scst(void) struct scsi_sense_hdr *shdr; struct scst_order_data *o; struct scst_cmd *c; - BUILD_BUG_ON(SCST_SENSE_BUFFERSIZE < sizeof(*shdr)); + + BUILD_BUG_ON(sizeof(*shdr) > SCST_SENSE_BUFFERSIZE); BUILD_BUG_ON(sizeof(o->curr_sn) != sizeof(o->expected_sn)); BUILD_BUG_ON(sizeof(c->sn) != sizeof(o->expected_sn)); } @@ -2578,14 +2597,19 @@ static int __init init_scst(void) goto out_destroy_sense_mempool; } - res = scst_sysfs_init(); + res = scst_event_init(); if (res != 0) goto out_destroy_aen_mempool; + res = scst_sysfs_init(); + if (res != 0) + goto out_event_exit; + scst_tg_init(); if (scst_max_cmd_mem == 0) { struct sysinfo si; + si_meminfo(&si); #if BITS_PER_LONG == 32 scst_max_cmd_mem = min( @@ -2651,6 +2675,15 @@ static int __init init_scst(void) goto out_thread_free; #endif + res = scst_cm_init(); + if (res != 0) +#ifdef CONFIG_SCST_PROC + goto out_proc_cleanup; +#else + goto out_thread_free; +#endif + + PRINT_INFO("SCST version %s loaded successfully (max mem for " "commands %dMB, per device %dMB)", SCST_VERSION_STRING, scst_max_cmd_mem, scst_max_dev_cmd_mem); @@ -2661,6 +2694,11 @@ out: TRACE_EXIT_RES(res); return res; +#ifdef CONFIG_SCST_PROC +out_proc_cleanup: + scst_proc_cleanup_module(); +#endif + out_thread_free: scst_stop_global_threads(); @@ -2678,6 +2716,9 @@ out_destroy_sgv_pool: out_sysfs_cleanup: scst_sysfs_cleanup(); +out_event_exit: + scst_event_exit(); + out_destroy_aen_mempool: mempool_destroy(scst_aen_mempool); @@ -2740,6 +2781,8 @@ static void __exit exit_scst(void) /* ToDo: unregister_cpu_notifier() */ + scst_cm_exit(); + #ifdef CONFIG_SCST_PROC scst_proc_cleanup_module(); #endif @@ -2759,6 +2802,8 @@ static void __exit exit_scst(void) scst_sysfs_cleanup(); + scst_event_exit(); + #define DEINIT_CACHEP(p) do { \ kmem_cache_destroy(p); \ p = NULL; \ diff --git a/scst/src/scst_mem.c b/scst/src/scst_mem.c index 1706acb78..9800d1a65 100644 --- a/scst/src/scst_mem.c +++ b/scst/src/scst_mem.c @@ -675,6 +675,7 @@ static int sgv_alloc_sg_entries(struct scatterlist *sg, int pages, pg = 0; for (i = 0; i < pages; i++) { int n = PAGE_ALIGN(sg[i].length) >> PAGE_SHIFT; + trans_tbl[i].pg_count = pg; for (j = 0; j < n; j++) trans_tbl[pg++].sg_num = i+1; @@ -972,6 +973,7 @@ struct scatterlist *sgv_pool_alloc(struct sgv_pool *pool, unsigned int size, pages = PAGE_ALIGN(size) >> PAGE_SHIFT; if (pool->single_alloc_pages == 0) { int pages_order = get_order(size); + cache_num = pages_order; pages_to_alloc = (1 << pages_order); } else { @@ -1128,6 +1130,7 @@ struct scatterlist *sgv_pool_alloc(struct sgv_pool *pool, unsigned int size, success: if (cache_num >= 0) { int sg; + atomic_inc(&pool->cache_acc[cache_num].total_alloc); if (sgv_pool_clustered(pool)) cnt = obj->trans_tbl[pages-1].sg_num; @@ -1255,10 +1258,12 @@ void sgv_pool_free(struct sgv_pool_obj *obj, struct scst_mem_lim *mem_lim) { struct scatterlist *sg = obj->sg_entries; int i; + for (i = 0; i < obj->sg_count; i++) { struct page *p = sg_page(&sg[i]); int len = sg[i].length; int pages = PAGE_ALIGN(len) >> PAGE_SHIFT; + while (pages > 0) { if (page_count(p) != 1) { PRINT_WARNING("Freeing page %p with " @@ -1327,7 +1332,7 @@ struct scatterlist *scst_alloc_sg(int size, gfp_t gfp_mask, int *count) } } - res = kmalloc(pages*sizeof(*res), gfp_mask); + res = kmalloc_array(pages, sizeof(*res), gfp_mask); if (res == NULL) { TRACE(TRACE_OUT_OF_MEM, "Unable to allocate sg for %d pages", pages); diff --git a/scst/src/scst_pres.c b/scst/src/scst_pres.c index acc04481d..8ce467d6e 100644 --- a/scst/src/scst_pres.c +++ b/scst/src/scst_pres.c @@ -103,6 +103,7 @@ static inline void tid_secure(uint8_t *tid) { if ((tid[0] & 0x0f) == SCSI_TRANSPORTID_PROTOCOLID_ISCSI) { int size = scst_tid_size(tid); + tid[size - 1] = '\0'; } @@ -110,7 +111,7 @@ static inline void tid_secure(uint8_t *tid) } /* Returns false if tid's are not equal, true otherwise */ -static bool tid_equal(const uint8_t *tid_a, const uint8_t *tid_b) +bool tid_equal(const uint8_t *tid_a, const uint8_t *tid_b) { int len; @@ -137,6 +138,7 @@ static bool tid_equal(const uint8_t *tid_a, const uint8_t *tid_b) else if (tid_a_fmt == 0x40) { if (tid_a_fmt != tid_b_fmt) { uint8_t *p = strnchr(tid_a, tid_a_max, ','); + if (p == NULL) goto out_error; tid_a_len = p - tid_a; @@ -153,6 +155,7 @@ static bool tid_equal(const uint8_t *tid_a, const uint8_t *tid_b) else if (tid_b_fmt == 0x40) { if (tid_a_fmt != tid_b_fmt) { uint8_t *p = strnchr(tid_b, tid_b_max, ','); + if (p == NULL) goto out_error; tid_b_len = p - tid_b; @@ -243,6 +246,7 @@ void scst_pr_dump_prs(struct scst_device *dev, bool force) else { struct scst_dev_registrant *reg; int i = 0; + list_for_each_entry(reg, &dev->dev_registrants_list, dev_registrants_list_entry) { PRINT_INFO(" [%d] registrant %s/%d, key %016llx " @@ -256,6 +260,7 @@ void scst_pr_dump_prs(struct scst_device *dev, bool force) if (dev->pr_is_set) { struct scst_dev_registrant *holder = dev->pr_holder; + if (holder != NULL) PRINT_INFO("Reservation holder is %s/%d (key %016llx, " "scope %x, type %x, reg %p, tgt_dev %p)", @@ -629,6 +634,7 @@ static void scst_pr_abort_reg(struct scst_device *dev, if ((reg->tgt_dev != pr_cmd->tgt_dev) && !dev->tas) { uint8_t sense_buffer[SCST_STANDARD_SENSE_LEN]; int sl; + sl = scst_set_sense(sense_buffer, sizeof(sense_buffer), dev->d_sense, SCST_LOAD_SENSE(scst_sense_cleared_by_another_ini_UA)); @@ -688,11 +694,11 @@ static int scst_pr_do_load_device_file(struct scst_device *dev, inode = file_inode(file); - if (S_ISREG(inode->i_mode)) - /* Nothing to do */; - else if (S_ISBLK(inode->i_mode)) + if (S_ISREG(inode->i_mode)) { + /* Nothing to do */ + } else if (S_ISBLK(inode->i_mode)) { inode = inode->i_bdev->bd_inode; - else { + } else { PRINT_ERROR("Invalid file mode 0x%x", inode->i_mode); goto out_close; } diff --git a/scst/src/scst_pres.h b/scst/src/scst_pres.h index 2decf5b4c..5ee98a3ef 100644 --- a/scst/src/scst_pres.h +++ b/scst/src/scst_pres.h @@ -131,6 +131,8 @@ void scst_pr_read_full_status(struct scst_cmd *cmd, uint8_t *buffer, int buffer_size); int scst_tid_size(const uint8_t *tid); +bool tid_equal(const uint8_t *tid_a, const uint8_t *tid_b); + struct scst_dev_registrant *scst_pr_find_reg(struct scst_device *dev, const uint8_t *transport_id, const uint16_t rel_tgt_id); struct scst_dev_registrant *scst_pr_add_registrant(struct scst_device *dev, diff --git a/scst/src/scst_priv.h b/scst/src/scst_priv.h index f40861829..db0c00352 100644 --- a/scst/src/scst_priv.h +++ b/scst/src/scst_priv.h @@ -458,7 +458,6 @@ struct scst_dev_group *scst_lookup_dg_by_kobj(struct kobject *kobj); int scst_dg_dev_add(struct scst_dev_group *dg, const char *name); int scst_dg_dev_remove_by_name(struct scst_dev_group *dg, const char *name); int scst_dg_dev_remove_by_dev(struct scst_device *dev); -const char *scst_alua_state_name(enum scst_tg_state s); enum scst_tg_state scst_alua_name_to_state(const char *n); int scst_tg_add(struct scst_dev_group *dg, const char *name); int scst_tg_remove_by_name(struct scst_dev_group *dg, const char *name); @@ -716,10 +715,18 @@ void scst_gen_aen_or_ua(struct scst_tgt_dev *tgt_dev, * we don't care about all commands that already on the device. */ -extern void scst_block_dev(struct scst_device *dev); -extern void scst_unblock_dev(struct scst_device *dev); - +void scst_block_dev(struct scst_device *dev); +void scst_unblock_dev(struct scst_device *dev); +bool scst_do_check_blocked_dev(struct scst_cmd *cmd); bool __scst_check_blocked_dev(struct scst_cmd *cmd); +void __scst_check_unblock_dev(struct scst_cmd *cmd); +void scst_check_unblock_dev(struct scst_cmd *cmd); + +int scst_ext_block_dev(struct scst_device *dev, bool sync, + ext_blocker_done_fn_t done_fn, const uint8_t *priv, int priv_len); +void scst_ext_unblock_dev(struct scst_device *dev, bool stpg); +void __scst_ext_blocking_done(struct scst_device *dev); +void scst_ext_blocking_done(struct scst_device *dev); /* * Increases global SCST ref counters which prevent from entering into suspended @@ -758,6 +765,7 @@ static inline atomic_t *scst_get(void) static inline void scst_put(atomic_t *a) { int f; + f = atomic_dec_and_test(a); /* See comment about smp_mb() in scst_suspend_activity() */ if (unlikely(test_bit(SCST_FLAG_SUSPENDED, &scst_flags)) && f) { @@ -815,6 +823,8 @@ void scst_unthrottle_cmd(struct scst_cmd *cmd); int scst_do_internal_parsing(struct scst_cmd *cmd); int scst_parse_descriptors(struct scst_cmd *cmd); +int scst_cmp_wr_local(struct scst_cmd *cmd); + int scst_pr_init(struct scst_device *dev); void scst_pr_cleanup(struct scst_device *dev); @@ -826,6 +836,87 @@ void scst_vfs_unlink_and_put(struct path *path); int scst_copy_file(const char *src, const char *dest); +struct scst_cmd *__scst_create_prepare_internal_cmd(const uint8_t *cdb, + unsigned int cdb_len, enum scst_cmd_queue_type queue_type, + struct scst_tgt_dev *tgt_dev, gfp_t gfp_mask, bool fantom); + +static inline bool scst_lba1_inside_lba2(int64_t lba1, + int64_t lba2, int64_t lba2_blocks) +{ + bool res; + + TRACE_DBG("lba1 %lld, lba2 %lld, lba2_blocks %lld", (long long)lba1, + (long long)lba2, (long long)lba2_blocks); + + if ((lba1 >= lba2) && (lba1 < (lba2 + lba2_blocks))) + res = true; + else + res = false; + + TRACE_EXIT_RES(res); + return res; +} + +#ifndef CONFIG_SCST_PROC + +int scst_cm_on_dev_register(struct scst_device *dev); +void scst_cm_on_dev_unregister(struct scst_device *dev); + +int scst_cm_parse_descriptors(struct scst_cmd *cmd); +void scst_cm_free_descriptors(struct scst_cmd *cmd); + +int scst_cm_ext_copy_exec(struct scst_cmd *cmd); +int scst_cm_rcv_copy_res_exec(struct scst_cmd *cmd); + +void sess_cm_list_id_cleanup_work_fn(struct delayed_work *work); +void scst_cm_free_pending_list_ids(struct scst_session *sess); + +bool scst_cm_check_block_all_devs(struct scst_cmd *cmd); +void scst_cm_abort_ec_cmd(struct scst_cmd *ec_cmd); + +bool scst_cm_ec_cmd_overlap(struct scst_cmd *ec_cmd, struct scst_cmd *cmd); + +int scst_cm_init(void); +void scst_cm_exit(void); + +#else /* #ifndef CONFIG_SCST_PROC */ + +static inline int scst_cm_on_dev_register(struct scst_device *dev) { return 0; } +static inline void scst_cm_on_dev_unregister(struct scst_device *dev) {} + +static inline int scst_cm_parse_descriptors(struct scst_cmd *cmd) +{ + scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_invalid_opcode)); + scst_set_cmd_abnormal_done_state(cmd); + return -1; +} +static inline void scst_cm_free_descriptors(struct scst_cmd *cmd) {} + +static inline int scst_cm_ext_copy_exec(struct scst_cmd *cmd) +{ + return SCST_EXEC_NOT_COMPLETED; +} +static inline int scst_cm_rcv_copy_res_exec(struct scst_cmd *cmd) +{ + return SCST_EXEC_NOT_COMPLETED; +} + +static inline void sess_cm_list_id_cleanup_work_fn(struct work_struct *work) {} +static inline void scst_cm_free_pending_list_ids(struct scst_session *sess) {} + +static inline bool scst_cm_check_block_all_devs(struct scst_cmd *cmd) { return false; } +static inline void scst_cm_abort_ec_cmd(struct scst_cmd *ec_cmd) {} + +static inline bool scst_cm_ec_cmd_overlap(struct scst_cmd *ec_cmd, struct scst_cmd *cmd) +{ + return false; +} + +static inline int scst_cm_init(void) { return 0; } +static inline void scst_cm_exit(void) {} + +#endif /* #ifndef CONFIG_SCST_PROC */ + #ifdef CONFIG_SCST_DEBUG_TM extern void tm_dbg_check_released_cmds(void); extern int tm_dbg_check_cmd(struct scst_cmd *cmd); @@ -863,6 +954,15 @@ int gen_relative_target_port_id(uint16_t *id); bool scst_is_relative_target_port_id_unique(uint16_t id, const struct scst_tgt *t); +int scst_event_init(void); +void scst_event_exit(void); + +int scst_event_queue_lun_not_found(const struct scst_cmd *cmd); +int scst_event_queue_negative_luns_inquiry(const struct scst_tgt *tgt, + const char *initiator_name); +int scst_event_queue_ext_blocking_done(struct scst_device *dev, void *data, int len); +int scst_event_queue_tm_fn_received(struct scst_mgmt_cmd *mcmd); + typedef void __printf(2, 3) (*scst_show_fn)(void *arg, const char *fmt, ...); void scst_trace_cmds(scst_show_fn show, void *arg); void scst_trace_mcmds(scst_show_fn show, void *arg); @@ -876,6 +976,7 @@ void scst_set_alloc_buf_time(struct scst_cmd *cmd); void scst_set_restart_waiting_time(struct scst_cmd *cmd); void scst_set_rdy_to_xfer_time(struct scst_cmd *cmd); void scst_set_pre_exec_time(struct scst_cmd *cmd); +void scst_set_exec_start(struct scst_cmd *cmd); void scst_set_exec_time(struct scst_cmd *cmd); void scst_set_dev_done_time(struct scst_cmd *cmd); void scst_set_xmit_time(struct scst_cmd *cmd); @@ -890,6 +991,7 @@ static inline void scst_set_alloc_buf_time(struct scst_cmd *cmd) {} static inline void scst_set_restart_waiting_time(struct scst_cmd *cmd) {} static inline void scst_set_rdy_to_xfer_time(struct scst_cmd *cmd) {} static inline void scst_set_pre_exec_time(struct scst_cmd *cmd) {} +static inline void scst_set_exec_start(struct scst_cmd *cmd) {} static inline void scst_set_exec_time(struct scst_cmd *cmd) {} static inline void scst_set_dev_done_time(struct scst_cmd *cmd) {} static inline void scst_set_xmit_time(struct scst_cmd *cmd) {} diff --git a/scst/src/scst_sysfs.c b/scst/src/scst_sysfs.c index 75e1baf85..57c493d76 100644 --- a/scst/src/scst_sysfs.c +++ b/scst/src/scst_sysfs.c @@ -272,11 +272,7 @@ static int scst_write_trace(const char *buf, size_t length, } break; case SCST_TRACE_ACTION_VALUE: -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtoul(p, 0, &level); -#else - res = strict_strtoul(p, 0, &level); -#endif if (res != 0) { PRINT_ERROR("Invalid trace value \"%s\"", p); res = -EINVAL; @@ -668,9 +664,11 @@ static int scst_check_tgt_acg_ptrs(struct scst_tgt *tgt, struct scst_acg *acg) list_for_each_entry(tgtt, &scst_template_list, scst_template_list_entry) { struct scst_tgt *t; + list_for_each_entry(t, &tgtt->tgt_list, tgt_list_entry) { if (t == tgt) { struct scst_acg *a; + if (acg == NULL) goto out; if (acg == tgt->default_acg) @@ -783,8 +781,8 @@ static ssize_t scst_show(struct kobject *kobj, struct attribute *attr, char *buf) { struct kobj_attribute *kobj_attr; - kobj_attr = container_of(attr, struct kobj_attribute, attr); + kobj_attr = container_of(attr, struct kobj_attribute, attr); return kobj_attr->show(kobj, kobj_attr, buf); } @@ -792,8 +790,8 @@ static ssize_t scst_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t count) { struct kobj_attribute *kobj_attr; - kobj_attr = container_of(attr, struct kobj_attribute, attr); + kobj_attr = container_of(attr, struct kobj_attribute, attr); if (kobj_attr->store) return kobj_attr->store(kobj, kobj_attr, buf, count); else @@ -1073,6 +1071,7 @@ static ssize_t scst_tgtt_dif_capable_show(struct kobject *kobj, if (tgtt->supported_dif_block_sizes) { const int *p = tgtt->supported_dif_block_sizes; int j; + pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, "Supported blocks: "); j = pos; @@ -1445,11 +1444,7 @@ static int __scst_process_luns_mgmt_store(char *buffer, break; case SCST_LUN_ACTION_DEL: p = scst_get_next_lexem(&pp); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtoul(p, 0, &virt_lun); -#else - res = strict_strtoul(p, 0, &virt_lun); -#endif if (res != 0) goto out_unlock; @@ -1758,11 +1753,7 @@ static ssize_t __scst_acg_io_grouping_type_store(struct scst_acg *acg, min_t(int, strlen(SCST_IO_GROUPING_NEVER_STR), count)) == 0) io_grouping_type = SCST_IO_GROUPING_NEVER; else { -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtol(buf, 0, &io_grouping_type); -#else - res = strict_strtol(buf, 0, &io_grouping_type); -#endif if ((res != 0) || (io_grouping_type <= 0)) { PRINT_ERROR("Unknown or not allowed I/O grouping type " "%s", buf); @@ -1892,6 +1883,7 @@ static ssize_t __scst_acg_black_hole_store(struct scst_acg *acg, for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { struct list_head *head = &sess->sess_tgt_dev_list[i]; struct scst_tgt_dev *tgt_dev; + list_for_each_entry_rcu(tgt_dev, head, sess_tgt_dev_list_entry) { if (t != SCST_ACG_BLACK_HOLE_NONE) @@ -2472,11 +2464,7 @@ static ssize_t scst_rel_tgt_id_store(struct kobject *kobj, tgt = container_of(kobj, struct scst_tgt, tgt_kobj); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtoul(buf, 0, &rel_tgt_id); -#else - res = strict_strtoul(buf, 0, &rel_tgt_id); -#endif if (res != 0) { PRINT_ERROR("%s", "Wrong rel_tgt_id"); res = -EINVAL; @@ -2564,9 +2552,11 @@ static ssize_t scst_tgt_forwarding_store(struct kobject *kobj, list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) { int i; + for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { struct list_head *head = &sess->sess_tgt_dev_list[i]; struct scst_tgt_dev *tgt_dev; + list_for_each_entry(tgt_dev, head, sess_tgt_dev_list_entry) { if (tgt->tgt_forwarding) set_bit(SCST_TGT_DEV_FORWARDING, &tgt_dev->tgt_dev_flags); @@ -2729,6 +2719,7 @@ static ssize_t scst_tgt_dif_capable_show(struct kobject *kobj, if (tgt->tgt_supported_dif_block_sizes) { const int *p = tgt->tgt_supported_dif_block_sizes; int j; + pos += scnprintf(&buf[pos], SCST_SYSFS_BLOCK_SIZE - pos, "Supported blocks: "); j = pos; @@ -3339,13 +3330,9 @@ static ssize_t scst_dev_sysfs_threads_num_store(struct kobject *kobj, dev = container_of(kobj, struct scst_device, dev_kobj); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtol(buf, 0, &newtn); -#else - res = strict_strtol(buf, 0, &newtn); -#endif if (res != 0) { - PRINT_ERROR("strict_strtol() for %s failed: %d ", buf, res); + PRINT_ERROR("kstrtol() for %s failed: %d ", buf, res); goto out; } if (newtn < 0) { @@ -3473,8 +3460,137 @@ static struct kobj_attribute dev_threads_pool_type_attr = scst_dev_sysfs_threads_pool_type_show, scst_dev_sysfs_threads_pool_type_store); +static ssize_t scst_dev_block_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int pos = 0; + struct scst_device *dev; + + TRACE_ENTRY(); + + dev = container_of(kobj, struct scst_device, dev_kobj); + + pos = sprintf(buf, "%d %d\n", ACCESS_ONCE(dev->ext_blocks_cnt), + dev->ext_blocking_pending); + + TRACE_EXIT_RES(pos); + return pos; +} + +static void scst_sysfs_ext_blocking_done(struct scst_device *dev, + uint8_t *data, int len) +{ + scst_event_queue_ext_blocking_done(dev, data, len); +} + +static ssize_t scst_dev_block_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int res, data_len = 0, pos = 0; + struct scst_device *dev; + const char *p = buf, *data_start = NULL; + bool sync; + + TRACE_ENTRY(); + + dev = container_of(kobj, struct scst_device, dev_kobj); + + switch (*p) { + case '0': + p++; + pos++; + while ((pos < count) && isspace(*p) && (*p != '\0')) { + p++; + pos++; + } + if ((pos != count) && (*p != '\0')) { + PRINT_ERROR("Parse error on %c", *p); + res = -EINVAL; + goto out; + } + + TRACE_MGMT_DBG("Sysfs unblocking (dev %s)", dev->virt_name); + + scst_ext_unblock_dev(dev, false); + res = 0; + goto out; + case '1': + p++; + pos++; + while ((pos < count) && isspace(*p) && (*p != '\0')) { + p++; + pos++; + } + if ((pos == count) || (*p == '\0')) { + data_len = sizeof(void *); + sync = true; + break; + } else if (*p != '1') { + PRINT_ERROR("Parse error on %c", *p); + res = -EINVAL; + goto out; + } + + sync = false; + + p++; + pos++; + if ((pos == count) || (*p == '\0')) + break; + + while ((pos < count) && isspace(*p) && (*p != '\0')) { + p++; + pos++; + } + if ((pos == count) || (*p == '\0')) + break; + + data_start = p; + while ((pos < count) && (*p != '\0')) { + p++; + pos++; + data_len++; + } + /* Skip trailing spaces, if any */ + while (isspace(*(p-1))) { + p--; + data_len--; + } + break; + default: + PRINT_ERROR("Illegal blocking value %c", *p); + res = -EINVAL; + goto out; + } + + TRACE_MGMT_DBG("Sysfs blocking dev %s (sync %d, data_start %p, " + "data_len %d)", dev->virt_name, sync, data_start, data_len); + + if (sync) + res = scst_ext_block_dev(dev, true, NULL, NULL, 0); + else + res = scst_ext_block_dev(dev, false, scst_sysfs_ext_blocking_done, + data_start, data_len); + if (res != 0) + goto out; + + res = 0; + +out: + if (res == 0) + res = count; + + TRACE_EXIT_RES(res); + return res; +} + +static struct kobj_attribute dev_block_attr = + __ATTR(block, S_IRUGO | S_IWUSR, scst_dev_block_show, + scst_dev_block_store); + static struct attribute *scst_dev_attrs[] = { &dev_type_attr.attr, + &dev_block_attr.attr, NULL, }; @@ -3771,11 +3887,7 @@ static ssize_t scst_dev_sysfs_dif_static_app_tag_store(struct kobject *kobj, dev = container_of(kobj, struct scst_device, dev_kobj); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtoull(buf, 0, &val); -#else - res = strict_strtoull(buf, 0, &val); -#endif if (res != 0) { PRINT_ERROR("strtoul() for %s failed: %d (device %s)", buf, res, dev->virt_name); @@ -4333,6 +4445,7 @@ static int scst_sess_zero_latency(struct scst_sysfs_work_item *work) for (t = SESS_TGT_DEV_LIST_HASH_SIZE-1; t >= 0; t--) { struct list_head *head = &sess->sess_tgt_dev_list[t]; struct scst_tgt_dev *tgt_dev; + list_for_each_entry_rcu(tgt_dev, head, sess_tgt_dev_list_entry) { tgt_dev->scst_time = 0; @@ -4412,6 +4525,7 @@ static int scst_sysfs_sess_get_active_commands(struct scst_session *sess) for (t = SESS_TGT_DEV_LIST_HASH_SIZE-1; t >= 0; t--) { struct list_head *head = &sess->sess_tgt_dev_list[t]; struct scst_tgt_dev *tgt_dev; + list_for_each_entry_rcu(tgt_dev, head, sess_tgt_dev_list_entry) { active_cmds += atomic_read(&tgt_dev->tgt_dev_cmd_count); @@ -4477,6 +4591,7 @@ static int scst_sysfs_sess_get_dif_checks_failed_work_fn(struct scst_sysfs_work_ for (t = SESS_TGT_DEV_LIST_HASH_SIZE-1; t >= 0; t--) { struct list_head *head = &sess->sess_tgt_dev_list[t]; struct scst_tgt_dev *tgt_dev; + list_for_each_entry_rcu(tgt_dev, head, sess_tgt_dev_list_entry) { app_failed_tgt += atomic_read(&tgt_dev->tgt_dev_dif_app_failed_tgt); ref_failed_tgt += atomic_read(&tgt_dev->tgt_dev_dif_ref_failed_tgt); @@ -4552,6 +4667,7 @@ static int scst_sess_zero_dif_checks_failed(struct scst_sysfs_work_item *work) for (t = SESS_TGT_DEV_LIST_HASH_SIZE-1; t >= 0; t--) { struct list_head *head = &sess->sess_tgt_dev_list[t]; struct scst_tgt_dev *tgt_dev; + list_for_each_entry_rcu(tgt_dev, head, sess_tgt_dev_list_entry) { atomic_set(&tgt_dev->tgt_dev_dif_app_failed_tgt, 0); @@ -6153,11 +6269,7 @@ static ssize_t scst_tg_tgt_rel_tgt_id_store(struct kobject *kobj, TRACE_ENTRY(); tg_tgt = container_of(kobj, struct scst_tg_tgt, kobj); snprintf(ch, sizeof(ch), "%.*s", min_t(int, count, sizeof(ch)-1), buf); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtoul(ch, 0, &rel_tgt_id); -#else - res = strict_strtoul(ch, 0, &rel_tgt_id); -#endif if (res) goto out; res = -EINVAL; @@ -6247,11 +6359,7 @@ static ssize_t scst_tg_group_id_store(struct kobject *kobj, TRACE_ENTRY(); tg = container_of(kobj, struct scst_target_group, kobj); snprintf(ch, sizeof(ch), "%.*s", min_t(int, count, sizeof(ch)-1), buf); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtoul(ch, 0, &group_id); -#else - res = strict_strtoul(ch, 0, &group_id); -#endif if (res) goto out; res = -EINVAL; @@ -6289,11 +6397,7 @@ static int scst_tg_preferred_store_work_fn(struct scst_sysfs_work_item *w) TRACE_ENTRY(); cmd = w->buf; tg = container_of(w->kobj, struct scst_target_group, kobj); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtoul(cmd, 0, &preferred); -#else - res = strict_strtoul(cmd, 0, &preferred); -#endif if (res) goto out; res = -EINVAL; @@ -6827,13 +6931,9 @@ static ssize_t scst_threads_store(struct kobject *kobj, TRACE_ENTRY(); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtol(buf, 0, &newtn); -#else - res = strict_strtol(buf, 0, &newtn); -#endif if (res != 0) { - PRINT_ERROR("strict_strtol() for %s failed: %d ", buf, res); + PRINT_ERROR("kstrtol() for %s failed: %d ", buf, res); goto out; } if (newtn <= 0) { @@ -6883,13 +6983,9 @@ static ssize_t scst_setup_id_store(struct kobject *kobj, TRACE_ENTRY(); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtoul(buf, 0, &val); -#else - res = strict_strtoul(buf, 0, &val); -#endif if (res != 0) { - PRINT_ERROR("strict_strtoul() for %s failed: %d ", buf, res); + PRINT_ERROR("kstrtoul() for %s failed: %d ", buf, res); goto out; } @@ -6930,13 +7026,9 @@ static ssize_t scst_max_tasklet_cmd_store(struct kobject *kobj, TRACE_ENTRY(); -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtoul(buf, 0, &val); -#else - res = strict_strtoul(buf, 0, &val); -#endif if (res != 0) { - PRINT_ERROR("strict_strtoul() for %s failed: %d ", buf, res); + PRINT_ERROR("kstrtoul() for %s failed: %d ", buf, res); goto out; } @@ -7393,10 +7485,9 @@ int scst_wait_info_completion(struct scst_sysfs_user_info *info, break; } } else if (rc != -ERESTARTSYS) { - res = rc; - PRINT_ERROR("wait_for_completion() failed: %d", - res); - goto out; + res = rc; + PRINT_ERROR("wait_for_completion() failed: %d", res); + goto out; } else { TRACE_DBG("Waiting for info %p finished with %d, " "retrying", info, rc); diff --git a/scst/src/scst_targ.c b/scst/src/scst_targ.c index d4409f95b..6b36eec31 100644 --- a/scst/src/scst_targ.c +++ b/scst/src/scst_targ.c @@ -103,25 +103,302 @@ static inline void scst_schedule_tasklet(struct scst_cmd *cmd) return; } -/* No locks */ -static bool scst_check_blocked_dev(struct scst_cmd *cmd) +static bool scst_unmap_overlap(struct scst_cmd *cmd, int64_t lba2, + int64_t lba2_blocks) +{ + bool res = false; + struct scst_data_descriptor *pd = cmd->cmd_data_descriptors; + int i; + + TRACE_ENTRY(); + + if (pd == NULL) + goto out; + + for (i = 0; pd[i].sdd_blocks != 0; i++) { + struct scst_data_descriptor *d = &pd[i]; + + TRACE_DBG("i %d, lba %lld, blocks %lld", i, + (long long)d->sdd_lba, (long long)d->sdd_blocks); + res = scst_lba1_inside_lba2(d->sdd_lba, lba2, lba2_blocks); + if (res) + goto out; + res = scst_lba1_inside_lba2(lba2, d->sdd_lba, d->sdd_blocks); + if (res) + goto out; + } + +out: + TRACE_EXIT_RES(res); + return res; +} + +static bool scst_cmd_overlap_cwr(struct scst_cmd *cwr_cmd, struct scst_cmd *cmd) +{ + bool res; + + TRACE_ENTRY(); + + TRACE_DBG("cwr_cmd %p, cmd %p (op %s, LBA valid %d, lba %lld, " + "len %lld)", cwr_cmd, cmd, scst_get_opcode_name(cmd), + (cmd->op_flags & SCST_LBA_NOT_VALID) == 0, + (long long)cmd->lba, (long long)cmd->data_len); + + EXTRACHECKS_BUG_ON(cwr_cmd->cdb[0] != COMPARE_AND_WRITE); + + /* + * In addition to requirements listed in "Model for uninterrupted + * sequences on LBA ranges" (SBC) VMware wants that COMPARE AND WRITE + * be atomic against RESERVEs, as well as RESERVEs be atomic against + * all COMPARE AND WRITE commands and only against them. + */ + + if (cmd->op_flags & SCST_LBA_NOT_VALID) { + switch (cmd->cdb[0]) { + case RESERVE: + case RESERVE_10: + res = true; + break; + case UNMAP: + res = scst_unmap_overlap(cmd, cwr_cmd->lba, + cwr_cmd->data_len); + break; + case EXTENDED_COPY: + res = scst_cm_ec_cmd_overlap(cmd, cwr_cmd); + break; + default: + res = false; + break; + } + goto out; + } + + /* If LBA valid, block_shift must be valid */ + EXTRACHECKS_BUG_ON(cmd->dev->block_shift <= 0); + + res = scst_lba1_inside_lba2(cwr_cmd->lba, cmd->lba, + cmd->data_len >> cmd->dev->block_shift); + if (res) + goto out; + + res = scst_lba1_inside_lba2(cmd->lba, cwr_cmd->lba, + cwr_cmd->data_len >> cwr_cmd->dev->block_shift); + +out: + TRACE_EXIT_RES(res); + return res; +} + +static bool scst_cmd_overlap_reserve(struct scst_cmd *reserve_cmd, + struct scst_cmd *cmd) +{ + bool res; + + TRACE_ENTRY(); + + TRACE_DBG("reserve_cmd %p, cmd %p (op %s, LBA valid %d, lba %lld, " + "len %lld)", reserve_cmd, cmd, scst_get_opcode_name(cmd), + (cmd->op_flags & SCST_LBA_NOT_VALID) == 0, + (long long)cmd->lba, (long long)cmd->data_len); + + EXTRACHECKS_BUG_ON((reserve_cmd->cdb[0] != RESERVE) && + (reserve_cmd->cdb[0] != RESERVE_10)); + + /* + * In addition to requirements listed in "Model for uninterrupted + * sequences on LBA ranges" (SBC) VMware wants that COMPARE AND WRITE + * be atomic against RESERVEs, as well as RESERVEs be atomic against + * all COMPARE AND WRITE commands and only against them. + */ + + if (cmd->cdb[0] == COMPARE_AND_WRITE) + res = true; + else + res = false; + + TRACE_EXIT_RES(res); + return res; +} + +static bool scst_cmd_overlap_atomic(struct scst_cmd *atomic_cmd, struct scst_cmd *cmd) +{ + bool res; + + TRACE_ENTRY(); + + TRACE_DBG("atomic_cmd %p (op %s), cmd %p (op %s)", atomic_cmd, + scst_get_opcode_name(atomic_cmd), cmd, scst_get_opcode_name(cmd)); + + EXTRACHECKS_BUG_ON((atomic_cmd->op_flags & SCST_SCSI_ATOMIC) == 0); + + switch (atomic_cmd->cdb[0]) { + case COMPARE_AND_WRITE: + res = scst_cmd_overlap_cwr(atomic_cmd, cmd); + break; + case RESERVE: + case RESERVE_10: + res = scst_cmd_overlap_reserve(atomic_cmd, cmd); + break; + default: + res = false; + break; + } + + TRACE_EXIT_RES(res); + return res; +} + +static bool scst_cmd_overlap(struct scst_cmd *chk_cmd, struct scst_cmd *cmd) +{ + bool res = false; + + TRACE_ENTRY(); + + TRACE_DBG("chk_cmd %p, cmd %p", chk_cmd, cmd); + + if ((chk_cmd->op_flags & SCST_SCSI_ATOMIC) != 0) + res = scst_cmd_overlap_atomic(chk_cmd, cmd); + else if ((cmd->op_flags & SCST_SCSI_ATOMIC) != 0) + res = scst_cmd_overlap_atomic(cmd, chk_cmd); + else + res = false; + + TRACE_EXIT_RES(res); + return res; +} + +/* + * dev_lock supposed to be held and BH disabled. Returns true if cmd blocked, + * hence stop processing it and go to the next command. + */ +static bool scst_check_scsi_atomicity(struct scst_cmd *chk_cmd) +{ + bool res = false; + struct scst_device *dev = chk_cmd->dev; + struct scst_cmd *cmd; + + TRACE_ENTRY(); + + /* + * chk_cmd isn't necessary SCSI atomic! For instance, if another SCSI + * atomic cmd is waiting blocked. + */ + + TRACE_DBG("chk_cmd %p (op %s, internal %d, lba %lld, len %lld)", + chk_cmd, scst_get_opcode_name(chk_cmd), chk_cmd->internal, + (long long)chk_cmd->lba, (long long)chk_cmd->data_len); + + list_for_each_entry(cmd, &dev->dev_exec_cmd_list, dev_exec_cmd_list_entry) { + if (chk_cmd == cmd) + continue; + if (scst_cmd_overlap(chk_cmd, cmd)) { + struct scst_cmd **p = cmd->scsi_atomic_blocked_cmds; + + /* + * kmalloc() allocates by at least 32 bytes increments, + * hence krealloc() on 8 bytes increments, if not all + * that space is used, does nothing. + */ + p = krealloc(p, sizeof(*p) * (cmd->scsi_atomic_blocked_cmds_count + 1), + GFP_ATOMIC); + if (p == NULL) + goto out_busy_undo; + p[cmd->scsi_atomic_blocked_cmds_count] = chk_cmd; + cmd->scsi_atomic_blocked_cmds = p; + cmd->scsi_atomic_blocked_cmds_count++; + + chk_cmd->scsi_atomic_blockers++; + + TRACE_BLOCK("Delaying cmd %p (op %s, lba %lld, " + "len %lld, blockers %d) due to overlap with " + "cmd %p (op %s, lba %lld, len %lld, blocked " + "cmds %d)", chk_cmd, scst_get_opcode_name(chk_cmd), + (long long)chk_cmd->lba, + (long long)chk_cmd->data_len, + chk_cmd->scsi_atomic_blockers, cmd, + scst_get_opcode_name(cmd), (long long)cmd->lba, + (long long)cmd->data_len, + cmd->scsi_atomic_blocked_cmds_count); + res = true; + } + } + +out: + TRACE_EXIT_RES(res); + return res; + +out_busy_undo: + list_for_each_entry(cmd, &dev->dev_exec_cmd_list, dev_exec_cmd_list_entry) { + struct scst_cmd **p = cmd->scsi_atomic_blocked_cmds; + + if ((p != NULL) && (p[cmd->scsi_atomic_blocked_cmds_count-1] == chk_cmd)) { + cmd->scsi_atomic_blocked_cmds_count--; + chk_cmd->scsi_atomic_blockers--; + } + } + sBUG_ON(chk_cmd->scsi_atomic_blockers != 0); + + scst_set_busy(chk_cmd); + scst_set_cmd_abnormal_done_state(chk_cmd); + + spin_lock_irq(&chk_cmd->cmd_threads->cmd_list_lock); + TRACE_MGMT_DBG("Adding on error chk_cmd %p back to head of active cmd " + "list", chk_cmd); + list_add(&chk_cmd->cmd_list_entry, &chk_cmd->cmd_threads->active_cmd_list); + wake_up(&chk_cmd->cmd_threads->cmd_list_waitQ); + spin_unlock_irq(&chk_cmd->cmd_threads->cmd_list_lock); + + res = false; + goto out; +} + +/* + * dev_lock supposed to be BH locked. Returns true if cmd blocked, hence stop + * processing it and go to the next command. + */ +bool scst_do_check_blocked_dev(struct scst_cmd *cmd) { bool res; struct scst_device *dev = cmd->dev; TRACE_ENTRY(); - if (unlikely(cmd->internal)) { - /* - * The original command can already block the device and must - * hold reference to it, so internal command should always pass. - */ - sBUG_ON(dev->on_dev_cmd_count == 0); - res = false; - goto out; + /* + * We want to have fairness between just unblocked previously blocked + * SCSI atomic cmds and new cmds came after them. Otherwise, the new + * cmds can bypass the SCSI atomic cmds and make them unfairly wait + * again. So, we need to always, from the beginning, have blocked SCSI + * atomic cmds on the exec list, even if they blocked, as well + * as dev's SCSI atomic cmds counter incremented. + */ + + if (likely(!cmd->on_dev_exec_list)) { + list_add_tail(&cmd->dev_exec_cmd_list_entry, &dev->dev_exec_cmd_list); + cmd->on_dev_exec_list = 1; } - spin_lock_bh(&dev->dev_lock); + /* + * After a cmd passed SCSI atomicy check, there's no need to recheck SCSI + * atomicity for this cmd in future entrances here, because then all + * future overlapping with this cmd cmds will be blocked on it. + */ + + if (unlikely(((cmd->op_flags & SCST_SCSI_ATOMIC) != 0) || + (dev->dev_scsi_atomic_cmd_active != 0)) && + !cmd->scsi_atomicity_checked) { + cmd->scsi_atomicity_checked = 1; + if ((cmd->op_flags & SCST_SCSI_ATOMIC) != 0) { + dev->dev_scsi_atomic_cmd_active++; + TRACE_DBG("cmd %p (dev %p), scsi atomic_cmd_active %d", + cmd, dev, dev->dev_scsi_atomic_cmd_active); + } + + res = scst_check_scsi_atomicity(cmd); + if (res) { + EXTRACHECKS_BUG_ON(dev->dev_scsi_atomic_cmd_active == 0); + goto out; + } + } dev->on_dev_cmd_count++; cmd->dec_on_dev_needed = 1; @@ -140,8 +417,46 @@ static bool scst_check_blocked_dev(struct scst_cmd *cmd) cmd->dec_on_dev_needed = 0; TRACE_DBG("New dec on_dev_count %d (cmd %p)", dev->on_dev_cmd_count, cmd); + goto out; } +out: + TRACE_EXIT_RES(res); + return res; +} + +/* + * No locks. Returns true if cmd blocked, hence stop processing it and go to + * the next command. + */ +static bool scst_check_blocked_dev(struct scst_cmd *cmd) +{ + bool res; + struct scst_device *dev = cmd->dev; + + TRACE_ENTRY(); + + if (unlikely((cmd->op_flags & SCST_CAN_GEN_3PARTY_COMMANDS) != 0)) { + EXTRACHECKS_BUG_ON(cmd->cdb[0] != EXTENDED_COPY); + res = scst_cm_check_block_all_devs(cmd); + goto out; + } + + if (unlikely(cmd->internal || cmd->bypass_blocking)) { + /* + * The original command can already block the device and must + * hold reference to it, so internal command should always pass. + */ + + /* Copy Manager can send internal INQUIRYs, so don't BUG on them */ + sBUG_ON((dev->on_dev_cmd_count == 0) && (cmd->cdb[0] != INQUIRY)); + + res = false; + goto out; + } + + spin_lock_bh(&dev->dev_lock); + res = scst_do_check_blocked_dev(cmd); spin_unlock_bh(&dev->dev_lock); out: @@ -149,12 +464,68 @@ out: return res; } -/* No locks */ -static void scst_check_unblock_dev(struct scst_cmd *cmd) +/* dev_lock supposed to be held and BH disabled */ +static void scst_check_unblock_scsi_atomic_cmds(struct scst_cmd *cmd) +{ + int i; + + TRACE_ENTRY(); + + EXTRACHECKS_BUG_ON(cmd->scsi_atomic_blocked_cmds_count == 0); + + for (i = 0; i < cmd->scsi_atomic_blocked_cmds_count; i++) { + struct scst_cmd *acmd = cmd->scsi_atomic_blocked_cmds[i]; + + acmd->scsi_atomic_blockers--; + if (acmd->scsi_atomic_blockers == 0) { + TRACE_BLOCK("Unblocking blocked acmd %p (blocker " + "cmd %p)", acmd, cmd); + spin_lock_irq(&acmd->cmd_threads->cmd_list_lock); + if (acmd->queue_type == SCST_CMD_QUEUE_HEAD_OF_QUEUE) + list_add(&acmd->cmd_list_entry, + &acmd->cmd_threads->active_cmd_list); + else + list_add_tail(&acmd->cmd_list_entry, + &acmd->cmd_threads->active_cmd_list); + wake_up(&acmd->cmd_threads->cmd_list_waitQ); + spin_unlock_irq(&acmd->cmd_threads->cmd_list_lock); + } + } + + kfree(cmd->scsi_atomic_blocked_cmds); + cmd->scsi_atomic_blocked_cmds = NULL; + cmd->scsi_atomic_blocked_cmds_count = 0; + + TRACE_EXIT(); + return; +} + +/* dev_lock supposed to be BH locked */ +void __scst_check_unblock_dev(struct scst_cmd *cmd) { struct scst_device *dev = cmd->dev; - spin_lock_bh(&dev->dev_lock); + TRACE_ENTRY(); + + /* + * We might be called here as part of Copy Manager's check blocking + * undo, so restore all flags in the previous state to allow + * restart of this cmd. + */ + + if (likely(cmd->on_dev_exec_list)) { + list_del(&cmd->dev_exec_cmd_list_entry); + cmd->on_dev_exec_list = 0; + } + + if (unlikely((cmd->op_flags & SCST_SCSI_ATOMIC) != 0)) { + if (likely(cmd->scsi_atomicity_checked)) { + dev->dev_scsi_atomic_cmd_active--; + TRACE_DBG("cmd %p, scsi atomic_cmd_active %d", + cmd, dev->dev_scsi_atomic_cmd_active); + cmd->scsi_atomicity_checked = 0; + } + } if (likely(cmd->dec_on_dev_needed)) { dev->on_dev_cmd_count--; @@ -163,6 +534,9 @@ static void scst_check_unblock_dev(struct scst_cmd *cmd) dev->on_dev_cmd_count, cmd); } + if (unlikely(cmd->scsi_atomic_blocked_cmds != NULL)) + scst_check_unblock_scsi_atomic_cmds(cmd); + if (unlikely(cmd->unblock_dev)) { TRACE_BLOCK("cmd %p (tag %llu): unblocking dev %s", cmd, (unsigned long long int)cmd->tag, dev->virt_name); @@ -176,7 +550,30 @@ static void scst_check_unblock_dev(struct scst_cmd *cmd) } } + if (unlikely(dev->ext_blocking_pending)) { + if (dev->on_dev_cmd_count == 0) { + TRACE_MGMT_DBG("Releasing pending dev %s extended " + "blocks", dev->virt_name); + scst_ext_blocking_done(dev); + } + } + + TRACE_EXIT(); + return; +} + +/* No locks */ +void scst_check_unblock_dev(struct scst_cmd *cmd) +{ + struct scst_device *dev = cmd->dev; + + TRACE_ENTRY(); + + spin_lock_bh(&dev->dev_lock); + __scst_check_unblock_dev(cmd); spin_unlock_bh(&dev->dev_lock); + + TRACE_EXIT(); return; } @@ -373,6 +770,7 @@ out_redirect: msleep(50); } else { unsigned long flags; + spin_lock_irqsave(&scst_init_lock, flags); TRACE_DBG("Adding cmd %p to init cmd list", cmd); list_add_tail(&cmd->cmd_list_entry, &scst_init_cmd_list); @@ -1008,6 +1406,7 @@ out_check_compl: if (unlikely(test_bit(SCST_TGT_DEV_BLACK_HOLE, &cmd->tgt_dev->tgt_dev_flags))) { struct scst_session *sess = cmd->sess; bool abort = false; + switch (sess->acg->acg_black_hole_type) { case SCST_ACG_BLACK_HOLE_CMD: case SCST_ACG_BLACK_HOLE_ALL: @@ -1410,6 +1809,7 @@ static int scst_rdy_to_xfer(struct scst_cmd *cmd) if (tgtt->on_hw_pending_cmd_timeout != NULL) { struct scst_session *sess = cmd->sess; + cmd->hw_pending_start = jiffies; cmd->cmd_hw_pending = 1; if (!test_bit(SCST_SESS_HW_PENDING_WORK_SCHEDULED, &sess->sess_aflags)) { @@ -1669,6 +2069,7 @@ static int scst_tgt_pre_exec(struct scst_cmd *cmd) if (unlikely(cmd->resid_possible)) { if (cmd->data_direction & SCST_DATA_WRITE) { bool remainder = false; + if (cmd->data_direction & SCST_DATA_READ) { if (cmd->write_len != cmd->out_bufflen) remainder = true; @@ -1706,7 +2107,17 @@ static int scst_tgt_pre_exec(struct scst_cmd *cmd) cmd->state = SCST_CMD_STATE_EXEC_CHECK_SN; - if ((cmd->tgtt->pre_exec == NULL) || unlikely(cmd->internal)) + if (unlikely(cmd->internal)) { + if (cmd->dh_data_buf_alloced && cmd->tgt_i_data_buf_alloced && + (scst_cmd_get_data_direction(cmd) & SCST_DATA_WRITE)) { + TRACE_DBG("Internal WRITE cmd %p with DH alloced data", + cmd); + scst_copy_sg(cmd, SCST_SG_COPY_FROM_TARGET); + } + goto out_descr; + } + + if (cmd->tgtt->pre_exec == NULL) goto out_descr; TRACE_DBG("Calling pre_exec(%p)", cmd); @@ -1738,6 +2149,7 @@ static int scst_tgt_pre_exec(struct scst_cmd *cmd) out_descr: if (unlikely(cmd->op_flags & SCST_DESCRIPTORS_BASED)) { int r = scst_parse_descriptors(cmd); + if (unlikely(r != 0)) goto out; } @@ -1889,7 +2301,7 @@ static int scst_report_luns_local(struct scst_cmd *cmd) cmd->driver_status = 0; if ((cmd->cdb[2] != 0) && (cmd->cdb[2] != 2)) { - TRACE(TRACE_MINOR, "Unsupported SELECT REPORT value %x in " + TRACE(TRACE_MINOR, "Unsupported SELECT REPORT value %#x in " "REPORT LUNS command", cmd->cdb[2]); scst_set_invalid_field_in_cdb(cmd, 2, 0); goto out_compl; @@ -1910,6 +2322,7 @@ static int scst_report_luns_local(struct scst_cmd *cmd) rcu_read_lock(); for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { struct list_head *head = &cmd->sess->sess_tgt_dev_list[i]; + list_for_each_entry_rcu(tgt_dev, head, sess_tgt_dev_list_entry) { struct scst_tgt_dev_UA *ua; @@ -2740,7 +3153,7 @@ out_unlock: scst_put_buf_full(cmd, buffer); out_done: - if (SCST_EXEC_COMPLETED == res) { + if (res == SCST_EXEC_COMPLETED) { if (!aborted) cmd->completed = 1; cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, @@ -2770,7 +3183,7 @@ int __scst_check_local_events(struct scst_cmd *cmd, bool preempt_tests_only) TRACE_ENTRY(); - if (unlikely(cmd->internal)) { + if (unlikely(cmd->internal && !cmd->internal_check_local_events)) { if (unlikely(test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags))) { TRACE_MGMT_DBG("ABORTED set, aborting internal " "cmd %p", cmd); @@ -2975,6 +3388,7 @@ static struct scst_cmd *scst_post_exec_sn(struct scst_cmd *cmd, if (inc_expected_sn) { bool rc = scst_inc_expected_sn(cmd); + if (!rc) goto out; if (make_active) @@ -3008,7 +3422,7 @@ static int scst_do_real_exec(struct scst_cmd *cmd) if (devt->exec) { TRACE_DBG("Calling dev handler %s exec(%p)", devt->name, cmd); - scst_set_cur_start(cmd); + scst_set_exec_start(cmd); res = devt->exec(cmd); TRACE_DBG("Dev handler %s exec() returned %d", devt->name, res); @@ -3034,7 +3448,7 @@ static int scst_do_real_exec(struct scst_cmd *cmd) goto out_error; } - scst_set_cur_start(cmd); + scst_set_exec_start(cmd); #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 30) rc = scst_exec_req(scsi_dev, cmd->cdb, cmd->cdb_len, @@ -3127,6 +3541,9 @@ static scst_local_exec_fn scst_local_fns[256] = { [PERSISTENT_RESERVE_OUT] = scst_persistent_reserve_out_local, [REPORT_LUNS] = scst_report_luns_local, [REQUEST_SENSE] = scst_request_sense_local, + [COMPARE_AND_WRITE] = scst_cmp_wr_local, + [EXTENDED_COPY] = scst_cm_ext_copy_exec, + [RECEIVE_COPY_RESULTS] = scst_cm_rcv_copy_res_exec, [MAINTENANCE_IN] = scst_maintenance_in, }; @@ -3229,15 +3646,41 @@ out_done: goto out; } +static inline bool scst_check_alua(struct scst_cmd *cmd, int *out_res) +{ + int (*alua_filter)(struct scst_cmd *cmd); + bool res = false; + + alua_filter = ACCESS_ONCE(cmd->tgt_dev->alua_filter); + if (unlikely(alua_filter)) { + int ac = alua_filter(cmd); + + if (ac != SCST_ALUA_CHECK_OK) { + if (ac != SCST_ALUA_CHECK_DELAYED) { + EXTRACHECKS_BUG_ON(cmd->status == 0); + scst_set_cmd_abnormal_done_state(cmd); + *out_res = SCST_CMD_STATE_RES_CONT_SAME; + } + res = true; + } + } + + return res; +} + static int scst_exec_check_blocking(struct scst_cmd **active_cmd) { struct scst_cmd *cmd = *active_cmd; struct scst_cmd *ref_cmd; + int res = SCST_CMD_STATE_RES_CONT_NEXT; TRACE_ENTRY(); cmd->state = SCST_CMD_STATE_EXEC_CHECK_BLOCKING; + if (unlikely(scst_check_alua(cmd, &res))) + goto out; + if (unlikely(scst_check_blocked_dev(cmd))) goto out; @@ -3251,6 +3694,7 @@ static int scst_exec_check_blocking(struct scst_cmd **active_cmd) #ifdef CONFIG_SCST_DEBUG_SN if ((scst_random() % 120) == 7) { int t = scst_random() % 200; + TRACE_SN("Delaying IO on %d ms", t); msleep(t); } @@ -3280,9 +3724,9 @@ static int scst_exec_check_blocking(struct scst_cmd **active_cmd) cmd->state = SCST_CMD_STATE_LOCAL_EXEC; rc = scst_do_local_exec(cmd); - if (likely(rc == SCST_EXEC_NOT_COMPLETED)) - /* Nothing to do */; - else { + if (likely(rc == SCST_EXEC_NOT_COMPLETED)) { + /* Nothing to do */ + } else { sBUG_ON(rc != SCST_EXEC_COMPLETED); goto done; } @@ -3301,6 +3745,9 @@ done: cmd->state = SCST_CMD_STATE_EXEC_CHECK_BLOCKING; + if (unlikely(scst_check_alua(cmd, &res))) + goto out; + if (unlikely(scst_check_blocked_dev(cmd))) break; @@ -3320,8 +3767,8 @@ done: /* !! At this point sess, dev and tgt_dev can be already freed !! */ out: - TRACE_EXIT(); - return SCST_CMD_STATE_RES_CONT_NEXT; + TRACE_EXIT_RES(res); + return res; } static int scst_exec_check_sn(struct scst_cmd **active_cmd) @@ -3421,6 +3868,7 @@ static int scst_check_sense(struct scst_cmd *cmd) } else { int sl; uint8_t sense[SCST_STANDARD_SENSE_LEN]; + TRACE(TRACE_MGMT, "DID_RESET received for device %s, " "triggering reset UA", dev->virt_name); sl = scst_set_sense(sense, sizeof(sense), dev->d_sense, @@ -3597,6 +4045,7 @@ next: if (likely(scst_cmd_completed_good(cmd))) { if (cmd->deferred_dif_read_check) { int rc = scst_dif_process_read(cmd); + if (unlikely(rc != 0)) { cmd->deferred_dif_read_check = 0; goto again; @@ -3709,6 +4158,7 @@ static int scst_mode_select_checks(struct scst_cmd *cmd) if (likely(scsi_status_is_good(cmd->status))) { int atomic = scst_cmd_atomic(cmd); + if (unlikely((cmd->cdb[0] == MODE_SELECT) || (cmd->cdb[0] == MODE_SELECT_10) || (cmd->cdb[0] == LOG_SELECT))) { @@ -3762,6 +4212,7 @@ static int scst_mode_select_checks(struct scst_cmd *cmd) SCST_SENSE_ASC_VALID, 0, 0x2F, 0))) { int atomic = scst_cmd_atomic(cmd); + if (atomic) { TRACE_DBG("Possible parameters changed UA %x: " "thread context required", cmd->sense[12]); @@ -3871,6 +4322,7 @@ static int scst_dev_done(struct scst_cmd *cmd) if (cmd->inc_expected_sn_on_done && cmd->sent_for_exec && cmd->sn_set) { bool rc = scst_inc_expected_sn(cmd); + if (rc) scst_make_deferred_commands_active(cmd->cur_order_data); } @@ -4035,6 +4487,7 @@ static int scst_xmit_response(struct scst_cmd *cmd) (cmd->data_direction & SCST_DATA_READ)) { int i, sg_cnt; struct scatterlist *sg, *sgi; + if (cmd->tgt_i_sg != NULL) { sg = cmd->tgt_i_sg; sg_cnt = cmd->tgt_i_sg_cnt; @@ -4059,6 +4512,7 @@ static int scst_xmit_response(struct scst_cmd *cmd) if (tgtt->on_hw_pending_cmd_timeout != NULL) { struct scst_session *sess = cmd->sess; + cmd->hw_pending_start = jiffies; cmd->cmd_hw_pending = 1; if (!test_bit(SCST_SESS_HW_PENDING_WORK_SCHEDULED, &sess->sess_aflags)) { @@ -4216,6 +4670,12 @@ static int scst_finish_cmd(struct scst_cmd *cmd) spin_unlock_irq(&sess->sess_list_lock); + if (unlikely(cmd->cmd_on_global_stpg_list)) { + TRACE_DBG("Unlisting being freed STPG cmd %p", cmd); + EXTRACHECKS_BUG_ON(cmd->cmd_global_stpg_blocked); + scst_stpg_del_unblock_next(cmd); + } + if (unlikely(test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags))) scst_finish_cmd_mgmt(cmd); @@ -4313,6 +4773,7 @@ again: order_data->cur_sn_slot = order_data->sn_slots; if (unlikely(atomic_read(order_data->cur_sn_slot) != 0)) { static int q; + if (q++ < 10) PRINT_WARNING("Not enough SN slots " "(dev %s)", cmd->dev->virt_name); @@ -4487,6 +4948,7 @@ static int scst_translate_lun(struct scst_cmd *cmd) "unexisting LU (initiator %s, target %s)?", (unsigned long long int)cmd->lun, cmd->sess->initiator_name, cmd->tgt->tgt_name); + scst_event_queue_lun_not_found(cmd); } scst_put(cmd->cpu_cmd_counter); } @@ -4508,7 +4970,6 @@ static int scst_translate_lun(struct scst_cmd *cmd) */ static int __scst_init_cmd(struct scst_cmd *cmd) { - bool (*alua_filter)(struct scst_cmd *cmd); int res = 0; TRACE_ENTRY(); @@ -4549,12 +5010,6 @@ static int __scst_init_cmd(struct scst_cmd *cmd) if (unlikely(failure)) goto out_busy; - alua_filter = ACCESS_ONCE(cmd->tgt_dev->alua_filter); - if (unlikely(alua_filter && !alua_filter(cmd))) { - scst_set_cmd_abnormal_done_state(cmd); - goto out; - } - /* * SCST_IMPLICIT_HQ for unknown commands not implemented for * case when set_sn_on_restart_cmd not set, because custom parse @@ -4571,6 +5026,7 @@ static int __scst_init_cmd(struct scst_cmd *cmd) else { struct scst_order_data *order_data = cmd->cur_order_data; unsigned long flags; + spin_lock_irqsave(&order_data->init_done_lock, flags); scst_cmd_set_sn(cmd); spin_unlock_irqrestore(&order_data->init_done_lock, flags); @@ -4615,6 +5071,7 @@ restart: list_for_each_entry(cmd, &scst_init_cmd_list, cmd_list_entry) { int rc; + if (susp && !test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags)) continue; if (!test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags)) { @@ -5381,6 +5838,9 @@ void scst_abort_cmd(struct scst_cmd *cmd, struct scst_mgmt_cmd *mcmd, TRACE_ENTRY(); + /* Fantom EC commands must not leak here */ + sBUG_ON((cmd->cdb[0] == EXTENDED_COPY) && cmd->internal); + /* * Help Coverity recognize that mcmd != NULL if * call_dev_task_mgmt_fn_received == true. @@ -5430,6 +5890,9 @@ void scst_abort_cmd(struct scst_cmd *cmd, struct scst_mgmt_cmd *mcmd, */ smp_mb__after_set_bit(); + if (cmd->cdb[0] == EXTENDED_COPY) + scst_cm_abort_ec_cmd(cmd); + if (cmd->tgt_dev == NULL) { spin_lock_irqsave(&scst_init_lock, flags); scst_init_poll_cnt++; @@ -5488,6 +5951,7 @@ void scst_abort_cmd(struct scst_cmd *cmd, struct scst_mgmt_cmd *mcmd, if (mstb->done_counted || mstb->finish_counted) { unsigned long t; char state_name[32]; + if (mcmd->fn != SCST_PR_ABORT_ALL) t = TRACE_MGMT; else @@ -5575,6 +6039,7 @@ static int scst_set_mcmd_next_state(struct scst_mgmt_cmd *mcmd) default: { char fn_name[16], state_name[32]; + PRINT_CRIT_ERROR("Wrong mcmd %p state %s (fn %s, " "cmd_finish_wait_count %d, cmd_done_wait_count %d)", mcmd, scst_get_mcmd_state_name(state_name, @@ -5596,11 +6061,14 @@ static int scst_set_mcmd_next_state(struct scst_mgmt_cmd *mcmd) /* IRQs supposed to be disabled */ static bool __scst_check_unblock_aborted_cmd(struct scst_cmd *cmd, - struct list_head *list_entry) + struct list_head *list_entry, bool blocked) { bool res; + if (test_bit(SCST_CMD_ABORTED, &cmd->cmd_flags)) { list_del(list_entry); + if (blocked) + cmd->cmd_global_stpg_blocked = 0; spin_lock(&cmd->cmd_threads->cmd_list_lock); list_add_tail(&cmd->cmd_list_entry, &cmd->cmd_threads->active_cmd_list); @@ -5643,7 +6111,7 @@ void scst_unblock_aborted_cmds(const struct scst_tgt *tgt, continue; if (__scst_check_unblock_aborted_cmd(cmd, - &cmd->blocked_cmd_list_entry)) { + &cmd->blocked_cmd_list_entry, true)) { TRACE_MGMT_DBG("Unblock aborted blocked cmd %p", cmd); } } @@ -5654,6 +6122,7 @@ void scst_unblock_aborted_cmds(const struct scst_tgt *tgt, list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list, dev_tgt_dev_list_entry) { struct scst_order_data *order_data = tgt_dev->curr_order_data; + spin_lock(&order_data->sn_lock); list_for_each_entry_safe(cmd, tcmd, &order_data->deferred_cmd_list, @@ -5665,7 +6134,7 @@ void scst_unblock_aborted_cmds(const struct scst_tgt *tgt, continue; if (__scst_check_unblock_aborted_cmd(cmd, - &cmd->deferred_cmd_list_entry)) { + &cmd->deferred_cmd_list_entry, false)) { TRACE_MGMT_DBG("Unblocked aborted SN " "cmd %p (sn %u)", cmd, cmd->sn); order_data->def_cmd_count--; @@ -5872,8 +6341,10 @@ static int scst_clear_task_set(struct scst_mgmt_cmd *mcmd) return res; } -/* Returns 0 if the command processing should be continued, - * >0, if it should be requeued, <0 otherwise */ +/* + * Returns 0 if the command processing should be continued, + * >0, if it should be requeued, <0 otherwise. + */ static int scst_mgmt_cmd_init(struct scst_mgmt_cmd *mcmd) { int res = 0, rc, t; @@ -5966,6 +6437,8 @@ static int scst_mgmt_cmd_init(struct scst_mgmt_cmd *mcmd) } out: + scst_event_queue_tm_fn_received(mcmd); + TRACE_EXIT_RES(res); return res; } @@ -6152,6 +6625,7 @@ static void scst_do_nexus_loss_sess(struct scst_mgmt_cmd *mcmd) rcu_read_lock(); for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { struct list_head *head = &sess->sess_tgt_dev_list[i]; + list_for_each_entry_rcu(tgt_dev, head, sess_tgt_dev_list_entry) { scst_nexus_loss(tgt_dev, @@ -6186,6 +6660,7 @@ static int scst_abort_all_nexus_loss_sess(struct scst_mgmt_cmd *mcmd, rcu_read_lock(); for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { struct list_head *head = &sess->sess_tgt_dev_list[i]; + list_for_each_entry_rcu(tgt_dev, head, sess_tgt_dev_list_entry) { __scst_abort_task_set(mcmd, tgt_dev); @@ -6220,6 +6695,7 @@ static void scst_do_nexus_loss_tgt(struct scst_mgmt_cmd *mcmd) for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { struct list_head *head = &sess->sess_tgt_dev_list[i]; struct scst_tgt_dev *tgt_dev; + list_for_each_entry_rcu(tgt_dev, head, sess_tgt_dev_list_entry) { scst_nexus_loss(tgt_dev, true); @@ -6257,6 +6733,7 @@ static int scst_abort_all_nexus_loss_tgt(struct scst_mgmt_cmd *mcmd, for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { struct list_head *head = &sess->sess_tgt_dev_list[i]; struct scst_tgt_dev *tgt_dev; + list_for_each_entry_rcu(tgt_dev, head, sess_tgt_dev_list_entry) { __scst_abort_task_set(mcmd, tgt_dev); @@ -6470,6 +6947,7 @@ static int scst_mgmt_affected_cmds_done(struct scst_mgmt_cmd *mcmd) rcu_read_lock(); for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { struct list_head *head = &sess->sess_tgt_dev_list[i]; + list_for_each_entry_rcu(tgt_dev, head, sess_tgt_dev_list_entry) { scst_call_dev_task_mgmt_fn_done(mcmd, tgt_dev); } @@ -6489,6 +6967,7 @@ static int scst_mgmt_affected_cmds_done(struct scst_mgmt_cmd *mcmd) for (i = 0; i < SESS_TGT_DEV_LIST_HASH_SIZE; i++) { struct list_head *head = &s->sess_tgt_dev_list[i]; struct scst_tgt_dev *tgt_dev; + list_for_each_entry_rcu(tgt_dev, head, sess_tgt_dev_list_entry) { if (mcmd->sess == tgt_dev->sess) @@ -6511,6 +6990,16 @@ static int scst_mgmt_affected_cmds_done(struct scst_mgmt_cmd *mcmd) tgt_done: scst_call_task_mgmt_affected_cmds_done(mcmd); + switch (mcmd->fn) { + case SCST_LUN_RESET: + case SCST_TARGET_RESET: + case SCST_NEXUS_LOSS_SESS: + case SCST_NEXUS_LOSS: + case SCST_UNREG_SESS_TM: + scst_cm_free_pending_list_ids(sess); + break; + } + res = scst_set_mcmd_next_state(mcmd); TRACE_EXIT_RES(res); @@ -6631,6 +7120,7 @@ static int scst_process_mgmt_cmd(struct scst_mgmt_cmd *mcmd) default: { char fn_name[16], state_name[32]; + PRINT_CRIT_ERROR("Wrong mcmd %p state %s (fn %s, " "cmd_finish_wait_count %d, cmd_done_wait_count " "%d)", mcmd, scst_get_mcmd_state_name(state_name, @@ -6674,6 +7164,7 @@ int scst_tm_thread(void *arg) while (!list_empty(&scst_active_mgmt_cmd_list)) { int rc; struct scst_mgmt_cmd *mcmd; + mcmd = list_first_entry(&scst_active_mgmt_cmd_list, typeof(*mcmd), mgmt_cmd_list_entry); TRACE_MGMT_DBG("Deleting mgmt cmd %p from active cmd " @@ -7107,6 +7598,9 @@ bool scst_initiator_has_luns(struct scst_tgt *tgt, const char *initiator_name) res = !list_empty(&acg->acg_dev_list); + if (!res) + scst_event_queue_negative_luns_inquiry(tgt, initiator_name); + mutex_unlock(&scst_mutex); TRACE_EXIT_RES(res); @@ -7669,6 +8163,7 @@ struct scst_cmd *scst_find_cmd_by_tag(struct scst_session *sess, { unsigned long flags; struct scst_cmd *cmd; + spin_lock_irqsave(&sess->sess_list_lock, flags); cmd = __scst_find_cmd_by_tag(sess, tag, false); spin_unlock_irqrestore(&sess->sess_list_lock, flags); diff --git a/scst/src/scst_tg.c b/scst/src/scst_tg.c index 3dc891a7a..e9f818095 100644 --- a/scst/src/scst_tg.c +++ b/scst/src/scst_tg.c @@ -16,13 +16,18 @@ */ #include +#include +#include #include #ifdef INSIDE_KERNEL_TREE #include +#include #else #include "scst.h" +#include "scst_event.h" #endif #include "scst_priv.h" +#include "scst_pres.h" struct alua_state_and_name { enum scst_tg_state s; @@ -65,6 +70,7 @@ const char *scst_alua_state_name(enum scst_tg_state s) return NULL; } +EXPORT_SYMBOL(scst_alua_state_name); enum scst_tg_state scst_alua_name_to_state(const char *n) { @@ -127,8 +133,8 @@ static struct scst_tg_tgt *__lookup_dg_tgt(struct scst_dev_group *dg, } /* Look up a target group by name in the given device group. */ -static struct scst_target_group * -__lookup_tg_by_name(struct scst_dev_group *dg, const char *name) +static struct scst_target_group *__lookup_tg_by_name(struct scst_dev_group *dg, + const char *name) { struct scst_target_group *tg; @@ -141,9 +147,24 @@ __lookup_tg_by_name(struct scst_dev_group *dg, const char *name) return NULL; } +/* Look up a target group by group ID. */ +static struct scst_target_group *__lookup_tg_by_group_id(struct scst_dev_group *dg, + uint16_t group_id) +{ + struct scst_target_group *tg; + + lockdep_assert_held(&scst_mutex); + + list_for_each_entry(tg, &dg->tg_list, entry) + if (tg->group_id == group_id) + return tg; + + return NULL; +} + /* Look up a target group by target port. */ -static struct scst_target_group * -__lookup_tg_by_tgt(struct scst_dev_group *dg, const struct scst_tgt *tgt) +static struct scst_target_group *__lookup_tg_by_tgt(struct scst_dev_group *dg, + const struct scst_tgt *tgt) { struct scst_target_group *tg; struct scst_tg_tgt *tg_tgt; @@ -253,11 +274,14 @@ static struct kobj_type scst_tg_tgt_ktype = { }; /* - * Whether or not to accept a command in the ALUA unavailable and transitioning - * states. + * Whether or not to accept a command in the ALUA standby state. */ -static bool scst_tg_accept(struct scst_cmd *cmd) +static int scst_tg_accept_standby(struct scst_cmd *cmd) { + int res; + + TRACE_ENTRY(); + switch (cmd->cdb[0]) { case TEST_UNIT_READY: case GET_EVENT_STATUS_NOTIFICATION: @@ -273,41 +297,6 @@ static bool scst_tg_accept(struct scst_cmd *cmd) case RESERVE_10: case READ_BUFFER: case WRITE_BUFFER: - return true; - case SERVICE_ACTION_IN_16: - switch (cmd->cdb[1] & 0x1f) { - case SAI_READ_CAPACITY_16: - return true; - } - break; - case MAINTENANCE_IN: - switch (cmd->cdb[1] & 0x1f) { - case MI_REPORT_TARGET_PGS: - return true; - } - break; - case MAINTENANCE_OUT: - switch (cmd->cdb[1] & 0x1f) { - case MO_SET_TARGET_PGS: - return true; - } - break; - } - - return false; -} - -/* - * Whether or not to accept a command in the ALUA standby state. - */ -static bool scst_tg_accept_standby(struct scst_cmd *cmd) -{ - bool process_cmd = scst_tg_accept(cmd); - - if (process_cmd) - return process_cmd; - - switch (cmd->cdb[0]) { case MODE_SELECT: case MODE_SELECT_10: case LOG_SELECT: @@ -316,43 +305,187 @@ static bool scst_tg_accept_standby(struct scst_cmd *cmd) case SEND_DIAGNOSTIC: case PERSISTENT_RESERVE_IN: case PERSISTENT_RESERVE_OUT: - return true; + res = SCST_ALUA_CHECK_OK; + goto out; + case SERVICE_ACTION_IN_16: + switch (cmd->cdb[1] & 0x1f) { + case SAI_READ_CAPACITY_16: + res = SCST_ALUA_CHECK_OK; + goto out; + } + break; + case MAINTENANCE_IN: + switch (cmd->cdb[1] & 0x1f) { + case MI_REPORT_TARGET_PGS: + res = SCST_ALUA_CHECK_OK; + goto out; + } + break; + case MAINTENANCE_OUT: + switch (cmd->cdb[1] & 0x1f) { + case MO_SET_TARGET_PGS: + res = SCST_ALUA_CHECK_OK; + goto out; + } + break; } - scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_tp_standby)); + scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_alua_standby)); + res = SCST_ALUA_CHECK_ERROR; - return false; +out: + TRACE_EXIT_RES(res); + return res; } - /* * Whether or not to accept a command in the ALUA unavailable state. */ -static bool scst_tg_accept_unav(struct scst_cmd *cmd) +static int scst_tg_accept_unav(struct scst_cmd *cmd) { - bool process_cmd = scst_tg_accept(cmd); + int res; - if (!process_cmd) - scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_tp_unav)); + TRACE_ENTRY(); - return process_cmd; + switch (cmd->cdb[0]) { + case TEST_UNIT_READY: + case GET_EVENT_STATUS_NOTIFICATION: + case INQUIRY: + case MODE_SENSE: + case MODE_SENSE_10: + case READ_CAPACITY: + case REPORT_LUNS: + case REQUEST_SENSE: + case RELEASE: + case RELEASE_10: + case RESERVE: + case RESERVE_10: + case READ_BUFFER: + case WRITE_BUFFER: + res = SCST_ALUA_CHECK_OK; + goto out; + case SERVICE_ACTION_IN_16: + switch (cmd->cdb[1] & 0x1f) { + case SAI_READ_CAPACITY_16: + res = SCST_ALUA_CHECK_OK; + goto out; + } + break; + case MAINTENANCE_IN: + switch (cmd->cdb[1] & 0x1f) { + case MI_REPORT_TARGET_PGS: + res = SCST_ALUA_CHECK_OK; + goto out; + } + break; + case MAINTENANCE_OUT: + switch (cmd->cdb[1] & 0x1f) { + case MO_SET_TARGET_PGS: + res = SCST_ALUA_CHECK_OK; + goto out; + } + break; + } + + scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_alua_unav)); + res = SCST_ALUA_CHECK_ERROR; + +out: + TRACE_EXIT_RES(res); + return res; +} + +struct scst_alua_retry { + struct scst_cmd *alua_retry_cmd; + struct delayed_work alua_retry_work; +}; + +static void scst_alua_transitioning_work_fn(struct delayed_work *work) +{ + struct scst_alua_retry *retry = container_of(work, struct scst_alua_retry, + alua_retry_work); + struct scst_cmd *cmd = retry->alua_retry_cmd; + + TRACE_ENTRY(); + + TRACE_DBG("Retrying transitioning cmd %p", cmd); + + spin_lock_irq(&cmd->cmd_threads->cmd_list_lock); + list_add(&cmd->cmd_list_entry, + &cmd->cmd_threads->active_cmd_list); + wake_up(&cmd->cmd_threads->cmd_list_waitQ); + spin_unlock_irq(&cmd->cmd_threads->cmd_list_lock); + + kfree(retry); + + TRACE_EXIT(); + return; } /* * Whether or not to accept a command in the ALUA transitioning state. */ -static bool scst_tg_accept_transitioning(struct scst_cmd *cmd) +static int scst_tg_accept_transitioning(struct scst_cmd *cmd) { - bool process_cmd = scst_tg_accept(cmd); + int res; - if (!process_cmd) - scst_set_cmd_error(cmd, - SCST_LOAD_SENSE(scst_sense_tp_transitioning)); + TRACE_ENTRY(); - return process_cmd; + switch (cmd->cdb[0]) { + case INQUIRY: + case READ_CAPACITY: + case REPORT_LUNS: + case REQUEST_SENSE: + case READ_BUFFER: + case WRITE_BUFFER: + res = SCST_ALUA_CHECK_OK; + goto out; + case SERVICE_ACTION_IN_16: + switch (cmd->cdb[1] & 0x1f) { + case SAI_READ_CAPACITY_16: + res = SCST_ALUA_CHECK_OK; + goto out; + } + break; + } + + if (cmd->already_transitioning) + TRACE_DBG("cmd %p already transitioned checked, failing", cmd); + else { + struct scst_alua_retry *retry; + + TRACE_DBG("ALUA transitioning: delaying cmd %p", cmd); + + retry = kzalloc(sizeof(*retry), GFP_KERNEL); + if (retry == NULL) { + TRACE_DBG("Unable to allocate ALUA retry " + "struct, failing cmd %p", cmd); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_alua_transitioning)); + res = SCST_ALUA_CHECK_ERROR; + goto out; + } + + /* No get is needed, because cmd is sync here */ + retry->alua_retry_cmd = cmd; + INIT_DELAYED_WORK(&retry->alua_retry_work, + (void (*)(struct work_struct *))scst_alua_transitioning_work_fn); + cmd->already_transitioning = 1; + schedule_delayed_work(&retry->alua_retry_work, HZ/2); + res = SCST_ALUA_CHECK_DELAYED; + goto out; + } + + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_alua_transitioning)); + res = SCST_ALUA_CHECK_ERROR; + +out: + TRACE_EXIT_RES(res); + return res; } -static bool (*scst_alua_filter[])(struct scst_cmd *cmd) = { +static int (*scst_alua_filter[])(struct scst_cmd *cmd) = { [SCST_TG_STATE_OPTIMIZED] = NULL, [SCST_TG_STATE_NONOPTIMIZED] = NULL, [SCST_TG_STATE_STANDBY] = scst_tg_accept_standby, @@ -424,7 +557,9 @@ static void scst_tg_change_tgt_dev_state(struct scst_tgt_dev *tgt_dev, { lockdep_assert_held(&scst_dg_mutex); - TRACE_MGMT_DBG("ALUA state of tgt_dev %p has changed", tgt_dev); + TRACE_MGMT_DBG("ALUA state of tgt_dev %p has changed (gen_ua %d)", + tgt_dev, gen_ua); + scst_update_tgt_dev_alua_filter(tgt_dev, state); if (gen_ua) scst_gen_aen_or_ua(tgt_dev, @@ -724,20 +859,96 @@ out: return res; } +static void scst_event_stpg_notify_fn(struct scst_event *event, + void *priv, int status) +{ + struct scst_dev_group *dg; + struct scst_cmd *cmd = (struct scst_cmd *)priv; + struct scst_event_stpg_payload *p = + (struct scst_event_stpg_payload *)event->payload; + struct scst_event_stpg_descr *d; + struct scst_dg_dev *dgd; + int i; + + TRACE_ENTRY(); + + PRINT_INFO("Notification for event %u (id %d) received " + "with status %d (priv %p)", event->event_code, + event->event_id, status, priv); + + mutex_lock(&scst_mutex); + mutex_lock(&scst_dg_mutex); + + dg = __lookup_dg_by_dev(cmd->dev); + if (!dg) { + PRINT_ERROR("STPG: unable to find DG for device %s", + cmd->dev->virt_name); + goto out_fail; + } + + list_for_each_entry(dgd, &dg->dev_list, entry) { + if (dgd->dev->stpg_ext_blocked) { + TRACE_DBG("STPG: ext unblocking dev %s", + dgd->dev->virt_name); + scst_ext_unblock_dev(dgd->dev, true); + dgd->dev->stpg_ext_blocked = 0; + } + } + + kfree(dg->stpg_transport_id); + dg->stpg_transport_id = NULL; + + if (status != 0) { + PRINT_ERROR("on_stpg script for device group %s failed with status %d", + dg->name, status); + goto out_fail; + } + + for (i = 0, d = &p->stpg_descriptors[0]; i < p->stpg_descriptors_cnt; i++, d++) { + struct scst_target_group *tg = __lookup_tg_by_group_id(dg, d->group_id); + + if (!tg) { + PRINT_ERROR("STPG: unable to find TG %d", d->group_id); + goto out_fail; + } else if (tg->state == scst_alua_name_to_state(d->prev_state)) { + PRINT_ERROR("on_stpg script did not change ALUA state" + " for device group %s / target group %s", + dg->name, tg->name); + goto out_fail; + } + } + +out_unlock: + mutex_unlock(&scst_dg_mutex); + mutex_unlock(&scst_mutex); + + scst_stpg_del_unblock_next(cmd); + + cmd->completed = 1; + cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_THREAD); + + TRACE_EXIT(); + return; + +out_fail: + scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_set_target_pgs_failed)); + goto out_unlock; +} + /* * Update the ALUA filter of those LUNs (tgt_dev) whose target port is a member * of target group @tg and that export a device that is a member of the device * group @tg->dg. */ static void __scst_tg_set_state(struct scst_target_group *tg, - enum scst_tg_state state, - struct scst_tgt *no_ua_tgt) + enum scst_tg_state state) { struct scst_dg_dev *dg_dev; struct scst_device *dev; struct scst_tgt_dev *tgt_dev; struct scst_tg_tgt *tg_tgt; struct scst_tgt *tgt; + enum scst_tg_state old_state = tg->state; sBUG_ON(state >= ARRAY_SIZE(scst_alua_filter)); lockdep_assert_held(&scst_dg_mutex); @@ -749,17 +960,26 @@ static void __scst_tg_set_state(struct scst_target_group *tg, list_for_each_entry(dg_dev, &tg->dg->dev_list, entry) { dev = dg_dev->dev; + if (dev->handler->on_alua_state_change_start != NULL) + dev->handler->on_alua_state_change_start(dev, old_state, state); list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list, dev_tgt_dev_list_entry) { tgt = tgt_dev->sess->tgt; list_for_each_entry(tg_tgt, &tg->tgt_list, entry) { if (tg_tgt->tgt == tgt) { + bool gen_ua = (state != SCST_TG_STATE_TRANSITIONING); + + if ((tg->dg->stpg_rel_tgt_id == tgt_dev->sess->tgt->rel_tgt_id) && + tid_equal(tg->dg->stpg_transport_id, tgt_dev->sess->transport_id)) + gen_ua = false; scst_tg_change_tgt_dev_state(tgt_dev, - state, tgt != no_ua_tgt); + state, gen_ua); break; } } } + if (dev->handler->on_alua_state_change_finish != NULL) + dev->handler->on_alua_state_change_finish(dev, old_state, state); } scst_check_alua_invariant(); @@ -780,7 +1000,7 @@ int scst_tg_set_state(struct scst_target_group *tg, enum scst_tg_state state) if (res) goto out; - __scst_tg_set_state(tg, state, NULL); + __scst_tg_set_state(tg, state); mutex_unlock(&scst_dg_mutex); out: @@ -945,9 +1165,16 @@ out_free: goto out; } +/* scst_dg_mutex supposed to be locked */ static void __scst_dg_dev_remove(struct scst_dev_group *dg, struct scst_dg_dev *dgdev) { + if (dgdev->dev->stpg_ext_blocked) { + TRACE_DBG("DG %s remove: unblocking STPG ext blocked " + "dev %s", dg->name, dgdev->dev->virt_name); + scst_ext_unblock_dev(dgdev->dev, true); + dgdev->dev->stpg_ext_blocked = 0; + } list_del(&dgdev->entry); scst_dg_dev_sysfs_del(dg, dgdev); scst_reset_dev_alua_filter(dgdev->dev); @@ -1080,7 +1307,7 @@ static void __scst_dg_remove(struct scst_dev_group *dg) list_del(&dg->entry); scst_dg_sysfs_del(dg); list_for_each_entry(tg, &dg->tg_list, entry) - __scst_tg_set_state(tg, SCST_TG_STATE_OPTIMIZED, NULL); + __scst_tg_set_state(tg, SCST_TG_STATE_OPTIMIZED); while (!list_empty(&dg->dev_list)) { dgdev = list_first_entry(&dg->dev_list, struct scst_dg_dev, entry); @@ -1201,10 +1428,10 @@ out_unlock: EXPORT_SYMBOL_GPL(scst_lookup_tg_id); /** - * scst_impl_alua_configured() - Whether implicit ALUA has been configured. + * scst_alua_configured() - Whether implicit ALUA has been configured. * @dev: Pointer to the SCST device to verify. */ -bool scst_impl_alua_configured(struct scst_device *dev) +bool scst_alua_configured(struct scst_device *dev) { struct scst_dev_group *dg; @@ -1214,7 +1441,7 @@ bool scst_impl_alua_configured(struct scst_device *dev) return dg != NULL; } -EXPORT_SYMBOL_GPL(scst_impl_alua_configured); +EXPORT_SYMBOL_GPL(scst_alua_configured); /** * scst_tg_get_group_info() - Build REPORT TARGET GROUPS response. @@ -1329,3 +1556,362 @@ out: return res; } EXPORT_SYMBOL_GPL(scst_tg_get_group_info); + +struct scst_stpg_wait { + atomic_t stpg_wait_left; + int status; + struct scst_dev_group *dg; + struct scst_event_entry *event_entry; +}; + +/* No locks */ +static void scst_stpg_check_blocking_done(struct scst_stpg_wait *wait) +{ + TRACE_ENTRY(); + + TRACE_DBG("wait %p, left %d", wait, atomic_read(&wait->stpg_wait_left)); + + if (atomic_dec_and_test(&wait->stpg_wait_left)) { + if (wait->status == 0) + scst_event_queue(SCST_EVENT_STPG_USER_INVOKE, + SCST_EVENT_SCST_CORE_ISSUER, wait->event_entry); + else { + wait->event_entry->event_notify_fn(&wait->event_entry->event, + wait->event_entry->notify_fn_priv, wait->status); + } + kfree(wait); + } + + TRACE_EXIT(); + return; +} + +/* No locks */ +static void scst_stpg_ext_blocking_done(struct scst_device *dev, + uint8_t *data, int len) +{ + sBUG_ON(len != sizeof(data)); + scst_stpg_check_blocking_done(*((struct scst_stpg_wait **)data)); +} + +/** + * scst_tg_set_group_info - SET TARGET PORT GROUPS implementation. + * + * Returns >=0 upon success or negative error code otherwise, for instance, + * if either an invalid group ID has been specified or the group ID + * of a target group with one, or more non-local target ports has been + * specified. In the error case the cmd has its sense set. + * + * In case of returned 0 the command completed asynchronously, i.e. upon + * return might be already dead!! + */ +int scst_tg_set_group_info(struct scst_cmd *cmd) +{ + struct scst_device *dev = cmd->dev; + uint8_t *buf; + int len; + int i, j, res = 1, tpg_desc_count, valid_desc_count; + struct scst_dev_group *dg; + struct osi { + uint16_t group_id; + struct scst_target_group *tg; + enum scst_tg_state prev_state; + enum scst_tg_state new_state; + } *osi = NULL; + int event_entry_len, payload_len; + struct scst_event_entry *event_entry; + struct scst_event *event; + struct scst_event_stpg_payload *payload; + struct scst_event_stpg_descr *descr; + + TRACE_ENTRY(); + + len = scst_get_buf_full(cmd, &buf); + if (len < 0) { + PRINT_ERROR("scst_get_buf_full() failed: %d", len); + res = len; + if (len == -ENOMEM) + scst_set_busy(cmd); + else + scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_hardw_error)); + goto out; + } + + /* + * From SPC-4: "A parameter list length of zero specifies that no data + * shall be transferred, and that no change shall be made in the + * target port asymmetric access state of any target port groups or + * target ports". + */ + if (len == 0) + goto out_put; + + tpg_desc_count = (len - 4) / 4; + /* Check for some reasonable limit */ + if (tpg_desc_count > 64) { + PRINT_ERROR("Too many STPG descriptors (%d) for dev %s", + tpg_desc_count, dev->virt_name); + res = -EINVAL; + scst_set_invalid_field_in_cdb(cmd, 6, 0); + goto out_put; + } + + TRACE_DBG("tpg_desc_count %d", tpg_desc_count); + + osi = kcalloc(tpg_desc_count, sizeof(*osi), GFP_KERNEL); + if (!osi) { + res = -ENOMEM; + scst_set_busy(cmd); + goto out_put; + } + + res = mutex_lock_interruptible(&scst_mutex); + if (res) { + PRINT_INFO("mutex_lock_interruptible() returned %d, finishing " + "cmd %p", res, cmd); + scst_set_busy(cmd); + goto out_put; + } + + res = mutex_lock_interruptible(&scst_dg_mutex); + if (res) { + PRINT_INFO("mutex_lock_interruptible() returned %d, finishing " + "cmd %p", res, cmd); + scst_set_busy(cmd); + goto out_unlock_sm_fail; + } + + dg = __lookup_dg_by_dev(dev); + if (!dg) { + res = -EINVAL; + goto out_unlock_fail; + } + + TRACE_DBG("dg %s (%p) found, dev %s", dg->name, dg, dev->virt_name); + + for (i = 4, j = 0; i + 4 <= len; i += 4, j++) { +#ifndef __CHECKER__ + /* + * Hide the statement below for smatch because otherwise it + * triggers a false positive. + */ + WARN_ON_ONCE(j >= tpg_desc_count); +#endif + osi[j].new_state = buf[i] & 0x1f; + switch (osi[j].new_state) { + case SCST_TG_STATE_OPTIMIZED: + case SCST_TG_STATE_NONOPTIMIZED: + case SCST_TG_STATE_STANDBY: + case SCST_TG_STATE_UNAVAILABLE: + case SCST_TG_STATE_OFFLINE: + break; + default: + TRACE_MGMT_DBG("Incorrect new state %d", osi[j].new_state); + res = -EINVAL; + goto out_unlock_fail; + } + + osi[j].group_id = get_unaligned_be16(&buf[i + 2]); + if (!osi[j].group_id) { + TRACE_MGMT_DBG("Invalid group_id %d", osi[j].group_id); + res = -EINVAL; + goto out_unlock_fail; + } + + osi[j].tg = __lookup_tg_by_group_id(dg, osi[j].group_id); + if (!osi[j].tg) { + TRACE_MGMT_DBG("No TG for group_id %d", osi[j].group_id); + res = -ESRCH; + goto out_unlock_fail; + } + + if (osi[j].tg->state == SCST_TG_STATE_TRANSITIONING) { + TRACE_MGMT_DBG("TG %p is transitioning", osi[j].tg); + res = -EBUSY; + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_alua_transitioning)); + /* second sense will not override the set one */ + goto out_unlock_fail; + } + osi[j].prev_state = osi[j].tg->state; + + TRACE_DBG("j %d, group_id %u, tg %s (%p), state %d", j, osi[j].group_id, + osi[j].tg->name, osi[j].tg, osi[j].tg->state); + } + + mutex_unlock(&scst_dg_mutex); + mutex_unlock(&scst_mutex); + + scst_put_buf_full(cmd, buf); + + payload_len = sizeof(*payload) + sizeof(*descr) * tpg_desc_count; + event_entry_len = sizeof(*event_entry) + payload_len; + event_entry = kzalloc(event_entry_len, GFP_KERNEL); + if (event_entry == NULL) { + PRINT_ERROR("Unable to allocate event (size %d)", event_entry_len); + res = -ENOMEM; + scst_set_busy(cmd); + goto out_free; + } + + TRACE_MEM("event_entry %p (len %d) allocated", event_entry, + event_entry_len); + + event = &event_entry->event; + event->payload_len = payload_len; + + payload = (struct scst_event_stpg_payload *)event->payload; + payload->stpg_cmd_tag = cmd->tag; + + res = 1; + + if (strlen(dev->virt_name) >= sizeof(payload->device_name)) { + PRINT_ERROR("Device name %s too long", dev->virt_name); + goto out_too_long; + } + strlcpy(payload->device_name, dev->virt_name, sizeof(payload->device_name)); + + valid_desc_count = 0; + for (j = 0, descr = &payload->stpg_descriptors[0]; j < tpg_desc_count; j++) { + if (osi[j].prev_state == osi[j].new_state) + continue; + + if (strlen(scst_alua_state_name(osi[j].prev_state)) >= sizeof(descr->prev_state)) { + PRINT_ERROR("prev state too long (%d)", osi[j].prev_state); + goto out_too_long; + } + strlcpy(descr->prev_state, scst_alua_state_name(osi[j].prev_state), + sizeof(descr->prev_state)); + + if (strlen(scst_alua_state_name(osi[j].new_state)) >= sizeof(descr->new_state)) { + PRINT_ERROR("new state too long (%d)", osi[j].new_state); + goto out_too_long; + } + strlcpy(descr->new_state, scst_alua_state_name(osi[j].new_state), + sizeof(descr->new_state)); + + if (strlen(dg->name) >= sizeof(descr->dg_name)) { + PRINT_ERROR("dg_name too long (%s)", dg->name); + goto out_too_long; + } + strlcpy(descr->dg_name, dg->name, sizeof(descr->dg_name)); + + if (strlen(osi[j].tg->name) >= sizeof(descr->tg_name)) { + PRINT_ERROR("tg_name too long (%s)", osi[j].tg->name); + goto out_too_long; + } + strlcpy(descr->tg_name, osi[j].tg->name, + sizeof(descr->tg_name)); + + descr->group_id = osi[j].group_id; + + TRACE_DBG("group_id %u, prev_state %s, new_state %s, dg_name %s, " + "tg_name %s", descr->group_id, descr->prev_state, + descr->new_state, descr->dg_name, descr->tg_name); + + valid_desc_count++; + descr++; + } + + payload->stpg_descriptors_cnt = valid_desc_count; + + if (valid_desc_count > 0) { + struct scst_dg_dev *dgd; + struct scst_stpg_wait *wait; + int rc; + + dg->stpg_rel_tgt_id = cmd->tgt->rel_tgt_id; + dg->stpg_transport_id = kmemdup(cmd->sess->transport_id, + scst_tid_size(cmd->sess->transport_id), GFP_KERNEL); + if (dg->stpg_transport_id == NULL) { + PRINT_ERROR("Unable to duplicate stpg_transport_id"); + goto out_free_event; + } + + wait = kzalloc(sizeof(*wait), GFP_KERNEL); + if (wait == NULL) { + PRINT_ERROR("Unable to allocate STPG wait struct " + "(size %zd)", sizeof(*wait)); + scst_set_busy(cmd); + res = -ENOMEM; + goto out_free_tr_id; + } + + atomic_set(&wait->stpg_wait_left, 1); + wait->event_entry = event_entry; + + event_entry->event_notify_fn = scst_event_stpg_notify_fn; + event_entry->notify_fn_priv = cmd; + + mutex_lock(&scst_dg_mutex); + list_for_each_entry(dgd, &dg->dev_list, entry) { + if (dgd->dev == dev) + continue; + + TRACE_DBG("STPG: ext blocking dev %s", dgd->dev->virt_name); + + atomic_inc(&wait->stpg_wait_left); + + spin_lock_bh(&dev->dev_lock); + WARN_ON(dgd->dev->stpg_ext_blocked); + dgd->dev->stpg_ext_blocked = 1; + spin_unlock_bh(&dev->dev_lock); + + rc = scst_ext_block_dev(dgd->dev, false, + scst_stpg_ext_blocking_done, (uint8_t *)&wait, + sizeof(wait)); + if (rc != 0) { + TRACE_DBG("scst_ext_block_dev() returned %d, " + "stepping back (cmd %p)", rc, cmd); + wait->status = rc; + wait->dg = dg; + atomic_dec(&wait->stpg_wait_left); + spin_lock_bh(&dev->dev_lock); + WARN_ON(dgd->dev->stpg_ext_blocked); + dgd->dev->stpg_ext_blocked = 0; + spin_unlock_bh(&dev->dev_lock); + break; + } + } + mutex_unlock(&scst_dg_mutex); + + scst_stpg_check_blocking_done(wait); + /* !! cmd can be already dead here !! */ + } else { + TRACE_DBG("Nothing to do"); + goto out_free_event; + } + + res = 0; + +out_free: + kfree(osi); + +out: + TRACE_EXIT_RES(res); + return res; + +out_unlock_fail: + mutex_unlock(&scst_dg_mutex); + +out_unlock_sm_fail: + mutex_unlock(&scst_mutex); + + scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_set_target_pgs_failed)); + +out_put: + scst_put_buf_full(cmd, buf); + goto out_free; + +out_too_long: + scst_set_cmd_error(cmd, SCST_LOAD_SENSE(scst_sense_set_target_pgs_failed)); + res = -EOVERFLOW; + +out_free_tr_id: + kfree(dg->stpg_transport_id); + +out_free_event: + kfree(event_entry); + goto out_free; +} +EXPORT_SYMBOL_GPL(scst_tg_set_group_info); diff --git a/scst_local/in-tree/Makefile-4.3 b/scst_local/in-tree/Makefile-4.3 new file mode 100644 index 000000000..8cbbbff63 --- /dev/null +++ b/scst_local/in-tree/Makefile-4.3 @@ -0,0 +1,2 @@ +obj-$(CONFIG_SCST_LOCAL) += scst_local.o + diff --git a/scst_local/scst_local.c b/scst_local/scst_local.c index eca959e8b..cf1cd8e31 100644 --- a/scst_local/scst_local.c +++ b/scst_local/scst_local.c @@ -88,7 +88,7 @@ static unsigned long scst_local_trace_flag = SCST_LOCAL_DEFAULT_LOG_FLAGS; #define scsi_bufflen(cmd) ((cmd)->request_bufflen) #endif -#define SCST_LOCAL_VERSION "3.1" +#define SCST_LOCAL_VERSION "3.2" static const char *scst_local_version_date = "20110901"; /* Some statistics */ @@ -446,11 +446,7 @@ static ssize_t scst_local_scsi_transport_version_store(struct kobject *kobj, if (!tgt) goto out_up; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtoul(buffer, 0, &val); -#else - res = strict_strtoul(buffer, 0, &val); -#endif if (res != 0) { PRINT_ERROR("strtoul() for %s failed: %zd", buffer, res); goto out_up; @@ -516,11 +512,7 @@ static ssize_t scst_local_phys_transport_version_store(struct kobject *kobj, if (!tgt) goto out_up; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) res = kstrtoul(buffer, 0, &val); -#else - res = strict_strtoul(buffer, 0, &val); -#endif if (res != 0) { PRINT_ERROR("strtoul() for %s failed: %zd", buffer, res); goto out_up; @@ -778,6 +770,7 @@ static ssize_t scst_local_sysfs_mgmt_cmd(char *buf) res = __scst_local_add_adapter(tgt, session_name, true); } else if (strcasecmp("del_session", command) == 0) { struct scst_local_sess *s, *sess = NULL; + list_for_each_entry(s, &tgt->sessions_list, sessions_list_entry) { if (strcmp(s->scst_sess->initiator_name, session_name) == 0) { @@ -897,8 +890,7 @@ static void scst_local_copy_sense(struct scsi_cmnd *cmnd, struct scst_cmd *scst_ TRACE_ENTRY(); - scst_cmnd_sense_len = (SCSI_SENSE_BUFFERSIZE < scst_cmnd_sense_len ? - SCSI_SENSE_BUFFERSIZE : scst_cmnd_sense_len); + scst_cmnd_sense_len = min(scst_cmnd_sense_len, SCSI_SENSE_BUFFERSIZE); memcpy(cmnd->sense_buffer, scst_cmd_get_sense_buffer(scst_cmnd), scst_cmnd_sense_len); @@ -1671,7 +1663,7 @@ static int scst_local_driver_probe(struct device *dev) TRACE_DBG("sess %p", sess); hpnt = scsi_host_alloc(&scst_lcl_ini_driver_template, sizeof(*sess)); - if (NULL == hpnt) { + if (hpnt == NULL) { PRINT_ERROR("%s", "scsi_register() failed"); ret = -ENODEV; goto out; @@ -1828,7 +1820,7 @@ static int __scst_local_add_adapter(struct scst_local_tgt *tgt, /* It's read-mostly, so cache alignment isn't needed */ sess = kzalloc(sizeof(*sess), GFP_KERNEL); - if (NULL == sess) { + if (sess == NULL) { PRINT_ERROR("Unable to alloc scst_lcl_host (size %zu)", sizeof(*sess)); res = -ENOMEM; @@ -1947,7 +1939,7 @@ static int scst_local_add_target(const char *target_name, TRACE_ENTRY(); tgt = kzalloc(sizeof(*tgt), GFP_KERNEL); - if (NULL == tgt) { + if (tgt == NULL) { PRINT_ERROR("Unable to alloc tgt (size %zu)", sizeof(*tgt)); res = -ENOMEM; goto out; diff --git a/srpt/README b/srpt/README index 1380cf354..1f0c91594 100644 --- a/srpt/README +++ b/srpt/README @@ -211,17 +211,7 @@ fe80:0000:0000:0000:0002:c903:0005:f350 Session names ------------- -The name assigned by the ib_srpt target driver to a session depends on the -mode in which it is operating. If one_target_per_port=y then the source port -GID is used as the session name. If one_target_per_port=n then the 128-bit SRP -initiator port identifier is used as the session name. This identifier is sent -by the SRP initiator to the SRP target via the SRP_LOGIN_REQ information unit. -The Linux SRP initiator (ib_srp) generates the initiator port identifier as -follows: -- The first eight bytes are the identifier extension ('initiator_ext' parameter - specified in the login string echoed into the sysfs file 'add_target'). -- The last eight bytes are the GUID of the initiator HCA port used to - communicate with the target. +The ib_srpt target driver uses the source port GID as session name. An example: @@ -242,7 +232,9 @@ fe80:0000:0000:0000:0002:c903:0003:cca8 $ (cd /sys/kernel/scst_tgt/targets/ib_srpt/[0-9a-f]* && ls -d sessions/*) sessions/fe80:0000:0000:0000:0002:c903:0003:cca7 +sessions/fe80:0000:0000:0000:0002:c903:0003:cca8 sessions/fe80:0000:0000:0000:0002:c903:0005:f34b +sessions/fe80:0000:0000:0000:0002:c903:0005:f34c LUN masking diff --git a/srpt/src/ib_srpt.c b/srpt/src/ib_srpt.c index 8467f94d4..8a8e09389 100644 --- a/srpt/src/ib_srpt.c +++ b/srpt/src/ib_srpt.c @@ -80,7 +80,7 @@ /* Name of this kernel module. */ #define DRV_NAME "ib_srpt" -#define DRV_VERSION "3.1.0-pre#" __stringify(OFED_FLAVOR) +#define DRV_VERSION "3.2.0-pre#" __stringify(OFED_FLAVOR) #define DRV_RELDATE "(not yet released)" #if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) /* Flags to be used in SCST debug tracing statements. */ @@ -169,16 +169,6 @@ module_param(use_node_guid_in_target_name, bool, 0444); MODULE_PARM_DESC(use_node_guid_in_target_name, "Use HCA node GUID as SCST target name."); -#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 31) \ - || defined(RHEL_MAJOR) && RHEL_MAJOR -0 <= 5 -static int one_target_per_port = true; -#else -static bool one_target_per_port = true; -#endif -module_param(one_target_per_port, bool, 0444); -MODULE_PARM_DESC(one_target_per_port, - "One SCST target per HCA port instead of one per HCA."); - static struct rdma_cm_id *rdma_cm_id; static int srpt_get_u64_x(char *buffer, struct kernel_param *kp) @@ -862,7 +852,7 @@ static struct srpt_ioctx **srpt_alloc_ioctx_ring(struct srpt_device *sdev, WARN_ON(ioctx_size != sizeof(struct srpt_recv_ioctx) && ioctx_size != sizeof(struct srpt_send_ioctx)); - ring = kmalloc(ring_size * sizeof(ring[0]), GFP_KERNEL); + ring = kmalloc_array(ring_size, sizeof(ring[0]), GFP_KERNEL); if (!ring) goto out; for (i = 0; i < ring_size; ++i) { @@ -1164,8 +1154,8 @@ static int srpt_get_desc_tbl(struct srpt_recv_ioctx *recv_ioctx, if (ioctx->n_rbuf == 1) ioctx->rbufs = &ioctx->single_rbuf; else { - ioctx->rbufs = - kmalloc(ioctx->n_rbuf * sizeof(*db), GFP_ATOMIC); + ioctx->rbufs = kmalloc_array(ioctx->n_rbuf, + sizeof(*db), GFP_ATOMIC); if (!ioctx->rbufs) { ioctx->n_rbuf = 0; ret = -ENOMEM; @@ -1650,7 +1640,7 @@ static int srpt_build_cmd_rsp(struct srpt_rdma_ch *ch, if (!scst_sense_valid(sense_data)) { sense_data_len = 0; } else { - BUILD_BUG_ON(MIN_MAX_RSP_SIZE <= sizeof(*srp_rsp)); + BUILD_BUG_ON(sizeof(*srp_rsp) >= MIN_MAX_RSP_SIZE); max_sense_len = ch->max_ti_iu_len - sizeof(*srp_rsp); if (sense_data_len > max_sense_len) { pr_warn("truncated sense data from %d to %d bytes\n", diff --git a/usr/Makefile b/usr/Makefile new file mode 100644 index 000000000..85bed6073 --- /dev/null +++ b/usr/Makefile @@ -0,0 +1,89 @@ +# +# Common makefile for SCSI target mid-level and its drivers +# +# Copyright (C) 2004 - 2015 Vladislav Bolkhovitin +# Copyright (C) 2004 - 2005 Leonid Stoljar +# Copyright (C) 2007 - 2015 SanDisk Corporation +# +# 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, version 2 +# of the License. +# +# 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. +# +# + +SHELL=/bin/bash +# Decide to use which kernel src. If not specified, is current running kernel. +#export KDIR=/usr/src/linux-2.6 + +FILEIO_DIR=fileio +STPGD_DIR=stpgd +EVENTS_DIR=events + +all: + cd $(FILEIO_DIR) && $(MAKE) $@ + cd $(STPGD_DIR) && $(MAKE) $@ +# cd $(EVENTS_DIR) && $(MAKE) $@ + +install: + cd $(FILEIO_DIR) && $(MAKE) $@ + cd $(STPGD_DIR) && $(MAKE) $@ +# cd $(EVENTS_DIR) && $(MAKE) $@ + +uninstall: + cd $(FILEIO_DIR) && $(MAKE) $@ + cd $(STPGD_DIR) && $(MAKE) $@ + cd $(EVENTS_DIR) && $(MAKE) $@ + +clean: + cd $(FILEIO_DIR) && $(MAKE) $@ + cd $(STPGD_DIR) && $(MAKE) $@ + cd $(EVENTS_DIR) && $(MAKE) $@ + +extraclean: + cd $(FILEIO_DIR) && $(MAKE) $@ + cd $(STPGD_DIR) && $(MAKE) $@ + cd $(EVENTS_DIR) && $(MAKE) $@ + +2release: + cd $(FILEIO_DIR) && $(MAKE) $@ + cd $(STPGD_DIR) && $(MAKE) $@ + cd $(EVENTS_DIR) && $(MAKE) $@ + +2debug: + cd $(FILEIO_DIR) && $(MAKE) $@ + cd $(STPGD_DIR) && $(MAKE) $@ + cd $(EVENTS_DIR) && $(MAKE) $@ + +2perf: + cd $(FILEIO_DIR) && $(MAKE) $@ + cd $(STPGD_DIR) && $(MAKE) $@ + cd $(EVENTS_DIR) && $(MAKE) $@ + +disable_proc: + cd $(FILEIO_DIR) && $(MAKE) $@ + cd $(STPGD_DIR) && $(MAKE) $@ + cd $(EVENTS_DIR) && $(MAKE) $@ + +enable_proc: + cd $(FILEIO_DIR) && $(MAKE) $@ + cd $(STPGD_DIR) && $(MAKE) $@ + cd $(EVENTS_DIR) && $(MAKE) $@ + +help: + @echo " all (the default) : make all" + @echo " clean : clean files" + @echo " extraclean : clean + clean dependencies" + @echo " install : install" + @echo " uninstall : uninstall" + @echo " Notes :" + @echo " - install and uninstall must be made as root." + @echo " - be sure to compile qla against the correct initiator" + @echo " driver. Read its README for details." + +.PHONY: all install uninstall clean extraclean help 2release 2debug 2perf disable_proc enable_proc diff --git a/usr/events/Makefile b/usr/events/Makefile new file mode 100644 index 000000000..ffce7d2d7 --- /dev/null +++ b/usr/events/Makefile @@ -0,0 +1,102 @@ +ifndef PREFIX + PREFIX=/usr/local +endif + +SHELL=/bin/bash + +SRCS_F = events.c debug.c +OBJS_F = $(SRCS_F:.c=.o) + +#SRCS_C = +#OBJS_C = $(SRCS_C:.c=.o) + +SCST_INC_DIR := $(shell if [ -e "$$PWD/../../scst" ]; \ + then echo "$$PWD/../../scst/include"; \ + else echo "$(DESTDIR)$(PREFIX)/include/scst"; fi) +DEBUG_INC_DIR := ../include +INSTALL_DIR := $(DESTDIR)$(PREFIX)/bin/scst + +CFLAGS += -O2 -Wall -Wextra -Wno-unused-parameter -Wstrict-prototypes \ + -I$(SCST_INC_DIR) -I$(DEBUG_INC_DIR) -D_GNU_SOURCE -D__USE_FILE_OFFSET64 \ + -D__USE_LARGEFILE64 +PROGS = events +LIBS = -lpthread + +CFLAGS += -DEXTRACHECKS +#CFLAGS += -DTRACING +CFLAGS += -DDEBUG -g -fno-inline -fno-inline-functions +CFLAGS += -W -Wno-unused-parameter +CFLAGS += $(LOCAL_CFLAGS) + +#CFLAGS += -DDEBUG_NOMEM + +all: $(PROGS) + +events: .depend_f $(OBJS_F) + $(CC) $(OBJS_F) $(LIBS) $(LOCAL_LD_FLAGS) -o $@ + +#cdrom_tgt: .depend_c $(OBJS_C) +# $(CC) $(OBJS_C) $(LIBS) $(LOCAL_LD_FLAGS) -o $@ + +ifeq (.depend_f,$(wildcard .depend_f)) +-include .depend_f +endif + +#ifeq (.depend_c,$(wildcard .depend_c)) +#-include .depend_c +#endif + +%.o: %.c Makefile + $(CC) -c -o $(@) $(CFLAGS) $(<) + +.depend_f: + $(CC) -M $(CFLAGS) $(SRCS_F) >$(@) + +#.depend_c: +# $(CC) -M $(CFLAGS) $(SRCS_C) >$(@) + +install: all + install -d $(INSTALL_DIR) + install -m 755 $(PROGS) $(INSTALL_DIR) + +uninstall: + rm -f $(INSTALL_DIR)/$(PROGS) + rm -rf $(INSTALL_DIR) + +clean: + rm -f *.o $(PROGS) .depend* + +extraclean: clean + rm -f *.orig *.rej + +2release: + sed -i.aa s/"^C\?FLAGS += \-DEXTRACHECKS"/"#CFLAGS += \-DEXTRACHECKS"/ Makefile + grep "^#CFLAGS += \-DEXTRACHECKS" Makefile >/dev/null + sed -i.aa s/"^#\?CFLAGS += \-DTRACING"/"CFLAGS += \-DTRACING"/ Makefile + grep "^CFLAGS += \-DTRACING" Makefile >/dev/null + sed -i.aa s/"^C\?FLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions"/"#CFLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions"/ Makefile + grep "^#CFLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions" Makefile >/dev/null + rm Makefile.aa + +2debug: + sed -i.aa s/"^#\?CFLAGS += \-DEXTRACHECKS"/"CFLAGS += \-DEXTRACHECKS"/ Makefile + grep "^CFLAGS += \-DEXTRACHECKS" Makefile >/dev/null + sed -i.aa s/"^C\?FLAGS += \-DTRACING"/"#CFLAGS += \-DTRACING"/ Makefile + grep "^#CFLAGS += \-DTRACING" Makefile >/dev/null + sed -i.aa s/"^#\?CFLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions"/"CFLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions"/ Makefile + grep "^CFLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions" Makefile >/dev/null + rm Makefile.aa + +2perf: + sed -i.aa s/"^C\?FLAGS += \-DEXTRACHECKS"/"#CFLAGS += \-DEXTRACHECKS"/ Makefile + grep "^#CFLAGS += \-DEXTRACHECKS" Makefile >/dev/null + sed -i.aa s/"^C\?FLAGS += \-DTRACING"/"#CFLAGS += \-DTRACING"/ Makefile + grep "^#CFLAGS += \-DTRACING" Makefile >/dev/null + sed -i.aa s/"^C\?FLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions"/"#CFLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions"/ Makefile + grep "^#CFLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions" Makefile >/dev/null + rm Makefile.aa + +release-archive: + ../../scripts/generate-release-archive events "$$(sed -n 's/^#define[[:blank:]]VERSION_STR[[:blank:]]*\"\([^\"]*\)\".*/\1/p' events.c)" + +.PHONY: all install uninstall clean extraclean 2release 2debug 2perf diff --git a/usr/events/README b/usr/events/README new file mode 100644 index 000000000..e354bb4dd --- /dev/null +++ b/usr/events/README @@ -0,0 +1,2 @@ +This is test/demo application for SCST events subsystem. For more +information about this subsystem, see scst_event.h file. diff --git a/usr/events/debug.c b/usr/events/debug.c new file mode 100644 index 000000000..03c08a556 --- /dev/null +++ b/usr/events/debug.c @@ -0,0 +1 @@ +#include "../include/debug.c" diff --git a/usr/events/events.c b/usr/events/events.c new file mode 100644 index 000000000..4cdf896e4 --- /dev/null +++ b/usr/events/events.c @@ -0,0 +1,326 @@ +/* + * events.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "version.h" +#include "debug.h" +#include "scst_event.h" + +char *app_name; + +#if defined(DEBUG) || defined(TRACING) + +#ifdef DEBUG +/*#define DEFAULT_LOG_FLAGS (TRACE_ALL & ~TRACE_MEMORY & ~TRACE_BUFF \ + & ~TRACE_FUNCTION) +#define DEFAULT_LOG_FLAGS (TRACE_ALL & ~TRACE_MEMORY & ~TRACE_BUFF & \ + ~TRACE_SCSI & ~TRACE_SCSI_SERIALIZING & ~TRACE_DEBUG) +*/ +#define DEFAULT_LOG_FLAGS (TRACE_OUT_OF_MEM | TRACE_MINOR | TRACE_PID | \ + TRACE_FUNCTION | TRACE_SPECIAL | TRACE_MGMT | TRACE_MGMT_DEBUG | \ + TRACE_TIME) + +#define TRACE_SN(args...) TRACE(TRACE_SCSI_SERIALIZING, args) + +#else /* DEBUG */ + +# ifdef TRACING +#define DEFAULT_LOG_FLAGS (TRACE_OUT_OF_MEM | TRACE_MGMT | \ + TRACE_TIME | TRACE_SPECIAL) +# else +#define DEFAULT_LOG_FLAGS 0 +# endif +#endif /* DEBUG */ + +bool log_daemon = false; /* needed for the tracing infrastructure */ +unsigned long trace_flag = DEFAULT_LOG_FLAGS; +#endif /* defined(DEBUG) || defined(TRACING) */ + +static struct option const long_options[] = { + {"allowed_event", required_argument, 0, 'e'}, + {"allowed_issuer", required_argument, 0, 'i'}, + {"non_blocking", no_argument, 0, 'n'}, +#if defined(DEBUG) || defined(TRACING) + {"debug", required_argument, 0, 'd'}, +#endif + {"version", no_argument, 0, 'v'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0}, +}; + +#define MAX_ALLOWED_EVENTS 16 +static struct scst_event allowed_events[MAX_ALLOWED_EVENTS]; +static int allowed_events_num; +static int non_blocking; + +static void usage(void) +{ + printf("Usage: %s [OPTIONS]\n", app_name); + printf("\nSCST events testing program\n"); + printf(" -e, --allowed_event=code Allowed event code, 0 - any event\n"); + printf(" -i, --allowed_issuer=issuer Allowed issuer, * - any issuer\n"); + printf(" -n, --non_blocking Use non-blocking operations\n"); +#if defined(DEBUG) || defined(TRACING) + printf(" -d, --debug=level Debug tracing level\n"); +#endif +} + +static void handle_tm_received(struct scst_event_user *event_user) +{ + struct scst_event_tm_fn_received_payload *p = (struct scst_event_tm_fn_received_payload *)event_user->out_event.payload; + + printf("fn %d, device %s\n", p->fn, p->device_name); + + return; +} + +int main(int argc, char **argv) +{ + int res = 0, i; + int ch, longindex; + int event_fd; + uint8_t event_user_buf[10240]; + struct scst_event_user *event_user = (struct scst_event_user *)event_user_buf; + struct pollfd pl; + + setlinebuf(stdout); + + res = debug_init(); + if (res != 0) + goto out; + + app_name = argv[0]; + + while ((ch = getopt_long(argc, argv, "+e:i:d:nhv", + long_options, &longindex)) >= 0) { + switch (ch) { + case 'e': + if (allowed_events[allowed_events_num].event_code != 0) { + allowed_events_num++; + if (allowed_events_num >= MAX_ALLOWED_EVENTS) { + PRINT_ERROR("Too many allowed events %d", + allowed_events_num); + exit(1); + } + } + allowed_events[allowed_events_num].event_code = atoi(optarg); + break; + case 'i': + if (allowed_events[allowed_events_num].issuer_name[0] != '\0') { + allowed_events_num++; + if (allowed_events_num >= MAX_ALLOWED_EVENTS) { + PRINT_ERROR("Too many allowed events %d", + allowed_events_num); + exit(1); + } + } + strncpy(allowed_events[allowed_events_num].issuer_name, + optarg, sizeof(allowed_events[allowed_events_num].issuer_name)); + allowed_events[allowed_events_num].issuer_name[ + sizeof(allowed_events[allowed_events_num].issuer_name)-1] = '\0'; + break; + case 'n': + non_blocking = 1; + break; +#if defined(DEBUG) || defined(TRACING) + case 'd': + trace_flag = strtol(optarg, (char **)NULL, 0); + break; +#endif + case 'v': + printf("%s version %s\n", app_name, VERSION_STR); + goto out_done; + default: + goto out_usage; + } + } + + if (allowed_events_num == 0) { + if ((allowed_events[0].event_code == 0) && + (allowed_events[0].issuer_name[0] == '\0')) + allowed_events[0].issuer_name[0] = '*'; + } + + allowed_events_num++; + +#ifdef DEBUG + PRINT_INFO("trace_flag %lx", trace_flag); +#endif + + if (non_blocking) + PRINT_INFO("%s", "NON-BLOCKING"); + + event_fd = open(SCST_EVENT_DEV, O_RDWR | (non_blocking ? O_NONBLOCK : 0)); + if (event_fd < 0) { + res = -errno; + PRINT_ERROR("Unable to open SCST event device %s (%s)", + SCST_EVENT_DEV, strerror(-res)); + goto out_done; + } + + memset(&pl, 0, sizeof(pl)); + pl.fd = event_fd; + pl.events = POLLIN; + + for (i = 0; i < allowed_events_num; i++) { + struct scst_event e; + + PRINT_INFO("Setting allowed event code %d, issuer_name %s", + allowed_events[i].event_code, + allowed_events[i].issuer_name); + memset(&e, 0, sizeof(e)); + e.event_code = allowed_events[i].event_code; + strncpy(e.issuer_name, allowed_events[i].issuer_name, + sizeof(e.issuer_name)); + e.issuer_name[sizeof(e.issuer_name)-1] = '\0'; + + res = ioctl(event_fd, SCST_EVENT_ALLOW_EVENT, &e); + if (res != 0) { + PRINT_ERROR("SCST_EVENT_ALLOW_EVENT failed: %s (res %d)", + strerror(errno), res); + goto out_done; + } + } + + while (1) { + memset(event_user_buf, 0, sizeof(event_user_buf)); + event_user->max_event_size = sizeof(event_user_buf); + res = ioctl(event_fd, SCST_EVENT_GET_NEXT_EVENT, event_user); + if (res != 0) { + res = errno; + switch (res) { + case ESRCH: + case EBUSY: + TRACE_MGMT_DBG("SCST_EVENT_GET_NEXT_EVENT returned " + "%d (%s)", res, strerror(res)); + /* go through */ + case EINTR: + continue; + case EAGAIN: + TRACE_DBG("SCST_EVENT_GET_NEXT_EVENT, returned " + "EAGAIN (%d)", res); + if (non_blocking) + break; + else + continue; + default: + PRINT_ERROR("SCST_EVENT_GET_NEXT_EVENT failed: %s (res %d)", + strerror(errno), res); + goto out_done; + } +again_poll: + res = poll(&pl, 1, 2000); + if (res > 0) + continue; + else if (res == 0) + goto again_poll; + else { + res = errno; + switch (res) { + case ESRCH: + case EBUSY: + case EAGAIN: + TRACE_MGMT_DBG("poll() returned %d " + "(%s)", res, strerror(res)); + case EINTR: + goto again_poll; + default: + PRINT_ERROR("poll() failed: %s", strerror(res)); +#if 1 + goto again_poll; +#else + goto out_close; +#endif + } + } + + } + + PRINT_INFO("\nevent_code %d, issuer_name %s", + event_user->out_event.event_code, + event_user->out_event.issuer_name); + if (event_user->out_event.payload_len != 0) + PRINT_BUFFER("payoad", event_user->out_event.payload, + event_user->out_event.payload_len); + PRINT_INFO("%s", ""); + + if (event_user->out_event.event_code == 0x12345) { + struct scst_event_notify_done d; + + PRINT_INFO("%s", "Press any key to send reply to event " + "0x12345"); + getchar(); + + memset(&d, 0, sizeof(d)); + d.event_id = event_user->out_event.event_id; + d.status = -19; + res = ioctl(event_fd, SCST_EVENT_NOTIFY_DONE, &d); + if (res != 0) + PRINT_ERROR("SCST_EVENT_NOTIFY_DONE failed: %s " + "(res %d)", strerror(errno), res); + } else if (event_user->out_event.event_code == SCST_EVENT_TM_FN_RECEIVED) + handle_tm_received(event_user); + else if (event_user->out_event.event_code == SCST_EVENT_TM_FN_RECEIVED) { + struct scst_event_notify_done d; + + memset(&d, 0, sizeof(d)); + d.event_id = event_user->out_event.event_id; + d.status = -20; + res = ioctl(event_fd, SCST_EVENT_NOTIFY_DONE, &d); + if (res != 0) + PRINT_ERROR("SCST_EVENT_NOTIFY_DONE failed: %s " + "(res %d)", strerror(errno), res); + } + +#if 0 + { + struct scst_event e; + + PRINT_INFO("Deleting allowed event code %d, issuer_name %s", + allowed_events[0].event_code, + allowed_events[0].issuer_name); + memset(&e, 0, sizeof(e)); + e.event_code = allowed_events[0].event_code; + strncpy(e.issuer_name, allowed_events[0].issuer_name, + sizeof(e.issuer_name)); + e.issuer_name[sizeof(e.issuer_name)-1] = '\0'; + + res = ioctl(event_fd, SCST_EVENT_DISALLOW_EVENT, &e); + if (res != 0) { + PRINT_ERROR("SCST_EVENT_DISALLOW_EVENT failed: " + "%s (res %d)", strerror(errno), res); + } + } +#endif + } + +out_done: + debug_done(); + +out: + return res; + +out_usage: + usage(); + goto out_done; +} diff --git a/usr/fileio/Makefile b/usr/fileio/Makefile index 7f6b23695..b91948369 100644 --- a/usr/fileio/Makefile +++ b/usr/fileio/Makefile @@ -29,10 +29,11 @@ OBJS_F = $(SRCS_F:.c=.o) SCST_INC_DIR := $(shell if [ -e "$$PWD/../../scst" ]; \ then echo "$$PWD/../../scst/include"; \ else echo "$(DESTDIR)$(PREFIX)/include/scst"; fi) +DEBUG_INC_DIR := ../include INSTALL_DIR := $(DESTDIR)$(PREFIX)/bin/scst CFLAGS += -O2 -Wall -Wextra -Wno-unused-parameter -Wstrict-prototypes \ - -I$(SCST_INC_DIR) -D_GNU_SOURCE -D__USE_FILE_OFFSET64 \ + -I$(SCST_INC_DIR) -I$(DEBUG_INC_DIR) -D_GNU_SOURCE -D__USE_FILE_OFFSET64 \ -D__USE_LARGEFILE64 PROGS = fileio_tgt LIBS = -lpthread @@ -48,6 +49,7 @@ CFLAGS += $(LOCAL_CFLAGS) #CFLAGS += -DDEBUG_TM_IGNORE #CFLAGS += -DDEBUG_TM_IGNORE -DDEBUG_TM_FN_IGNORE #CFLAGS += -DDEBUG_TM_IGNORE_ALL +CFLAGS += -DDEBUG_EXT_COPY_REMAP all: $(PROGS) diff --git a/usr/fileio/README b/usr/fileio/README index 2411bf479..31975bec8 100644 --- a/usr/fileio/README +++ b/usr/fileio/README @@ -1,7 +1,7 @@ User space FILEIO handler ========================= -Version 3.1.0, XX XXXXX 2014 +Version 3.2.0, XX XXXXX 2014 ---------------------------- User space program fileio_tgt uses interface of SCST's scst_user dev diff --git a/usr/fileio/common.c b/usr/fileio/common.c index 47b673fcc..8c7938b47 100644 --- a/usr/fileio/common.c +++ b/usr/fileio/common.c @@ -47,6 +47,7 @@ static int exec_fsync(struct vdisk_cmd *vcmd); static void exec_read(struct vdisk_cmd *vcmd, loff_t loff); static void exec_write(struct vdisk_cmd *vcmd, loff_t loff); static void exec_verify(struct vdisk_cmd *vcmd, loff_t loff); +static void exec_write_same(struct vdisk_cmd *vcmd); static int open_dev_fd(struct vdisk_dev *dev) { @@ -68,11 +69,31 @@ static int open_dev_fd(struct vdisk_dev *dev) return res; } -static inline void set_cmd_error_status(struct scst_user_scsi_cmd_reply_exec *reply, +static void set_resp_data_len(struct vdisk_cmd *vcmd, int32_t resp_data_len) +{ + struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply; + + TRACE_ENTRY(); + + if (vcmd->may_need_to_free_pbuf && (resp_data_len == 0)) { + struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd; + free((void *)(unsigned long)cmd->pbuf); + cmd->pbuf = 0; + reply->pbuf = 0; + } + + reply->resp_data_len = resp_data_len; + + TRACE_EXIT(); + return; +} + +static inline void set_cmd_error_status(struct vdisk_cmd *vcmd, int status) { + struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply; reply->status = status; - reply->resp_data_len = 0; + set_resp_data_len(vcmd, 0); return; } @@ -107,7 +128,7 @@ void set_cmd_error(struct vdisk_cmd *vcmd, int key, int asc, int ascq) EXTRACHECKS_BUG_ON(vcmd->cmd->subcode != SCST_USER_EXEC); - set_cmd_error_status(reply, SAM_STAT_CHECK_CONDITION); + set_cmd_error_status(vcmd, SAM_STAT_CHECK_CONDITION); reply->sense_len = set_sense(vcmd->sense, sizeof(vcmd->sense), key, asc, ascq); reply->psense_buffer = (unsigned long)vcmd->sense; @@ -116,11 +137,11 @@ void set_cmd_error(struct vdisk_cmd *vcmd, int key, int asc, int ascq) return; } -void set_busy(struct scst_user_scsi_cmd_reply_exec *reply) +void set_busy(struct vdisk_cmd *vcmd) { TRACE_ENTRY(); - set_cmd_error_status(reply, SAM_STAT_TASK_SET_FULL); + set_cmd_error_status(vcmd, SAM_STAT_TASK_SET_FULL); TRACE_MGMT_DBG("%s", "Sending QUEUE_FULL status"); TRACE_EXIT(); @@ -211,6 +232,9 @@ static int do_exec(struct vdisk_cmd *vcmd) TRACE_ENTRY(); + /* Must be reinitialized each time to avoid crash on stale value */ + vcmd->may_need_to_free_pbuf = 0; + switch(cmd->queue_type) { case SCST_CMD_QUEUE_ORDERED: TRACE(TRACE_ORDER, "ORDERED cmd_h %d", vcmd->cmd->cmd_h); @@ -250,6 +274,7 @@ static int do_exec(struct vdisk_cmd *vcmd) else #endif cmd->pbuf = (unsigned long)dev->alloc_fn(cmd->alloc_len); + vcmd->may_need_to_free_pbuf = 1; TRACE_MEM("Buf %"PRIx64" alloced, len %d", cmd->pbuf, cmd->alloc_len); reply->pbuf = cmd->pbuf; @@ -257,7 +282,7 @@ static int do_exec(struct vdisk_cmd *vcmd) TRACE(TRACE_OUT_OF_MEM, "Unable to allocate buffer " "(len %d)", cmd->alloc_len); #ifndef DEBUG_NOMEM - set_busy(reply); + set_busy(vcmd); #endif goto out; } @@ -408,6 +433,10 @@ static int do_exec(struct vdisk_cmd *vcmd) case READ_CAPACITY: exec_read_capacity(vcmd); break; + case WRITE_SAME_10: + case WRITE_SAME_16: + exec_write_same(vcmd); + break; case SERVICE_ACTION_IN_16: if ((cmd->cdb[1] & 0x1f) == SAI_READ_CAPACITY_16) exec_read_capacity16(vcmd); @@ -519,6 +548,49 @@ static int do_on_free_cmd(struct vdisk_cmd *vcmd) return res; } +#ifdef DEBUG_EXT_COPY_REMAP +static int do_ext_copy_remap(struct vdisk_cmd *vcmd) +{ + struct scst_user_get_cmd *cmd = vcmd->cmd; + struct scst_user_ext_copy_remap *rcmd = &cmd->remap_cmd; + struct scst_user_reply_cmd *reply = vcmd->reply; + struct scst_user_ext_copy_reply_remap *rreply = &reply->remap_reply; + static struct scst_user_ext_copy_data_descr d[1]; + int res = 0; + + TRACE_ENTRY(); + + TRACE_DBG("Ext copy remap cmd %d", cmd->cmd_h); + + memset(reply, 0, sizeof(*reply)); + reply->cmd_h = cmd->cmd_h; + reply->subcode = cmd->subcode; + + memset(rreply, 0, sizeof(*rreply)); + + /* It's only a debug code, so it's OK to use static descr */ + + memset(d, 0, sizeof(d)); + + d[0].data_len = rcmd->data_descr.data_len; + d[0].src_lba = rcmd->data_descr.src_lba; + d[0].dst_lba = rcmd->data_descr.dst_lba; + +#if 1 + rreply->remap_descriptors = (unsigned long)d; + rreply->remap_descriptors_len = sizeof(d); +#else + rreply->status = SAM_STAT_CHECK_CONDITION; + rreply->sense_len = set_sense(vcmd->sense, sizeof(vcmd->sense), + SCST_LOAD_SENSE(scst_sense_data_protect)); + rreply->psense_buffer = (unsigned long)vcmd->sense; +#endif + + TRACE_EXIT_RES(res); + return res; +} +#endif + static int do_tm(struct vdisk_cmd *vcmd, int done) { struct scst_user_get_cmd *cmd = vcmd->cmd; @@ -663,6 +735,12 @@ static int process_cmd(struct vdisk_cmd *vcmd) res = do_on_free_cmd(vcmd); break; +#ifdef DEBUG_EXT_COPY_REMAP + case SCST_USER_EXT_COPY_REMAP: + res = do_ext_copy_remap(vcmd); + break; +#endif + case SCST_USER_TASK_MGMT_RECEIVED: res = do_tm(vcmd, 0); break; @@ -699,7 +777,14 @@ void *main_loop(void *arg) struct vdisk_dev *dev = (struct vdisk_dev *)arg; struct scst_user_get_cmd cmd; struct scst_user_reply_cmd reply; - struct vdisk_cmd vcmd = { -1, &cmd, dev, &reply, {0}}; + struct vdisk_cmd vcmd = { + .fd = -1, + .cmd = &cmd, + .dev = dev, + .may_need_to_free_pbuf = 0, + .reply = &reply, + .sense = {0} + }; int scst_usr_fd = dev->scst_usr_fd; struct pollfd pl; #define MULTI_CMDS_CNT 2 @@ -741,6 +826,7 @@ void *main_loop(void *arg) } } #endif + if (use_multi) { TRACE_DBG("preplies %p (first: %p), replies_cnt %d, " "replies_done %d, cmds_cnt %d", (void *)multi.multi_cmd.preplies, @@ -901,11 +987,13 @@ static void exec_inquiry(struct vdisk_cmd *vcmd) uint64_t dev_id_num = gen_dev_id_num(dev); if (0 == cmd->cdb[2]) { /* supported vital product data pages */ - buf[3] = 3; + buf[3] = 5; buf[4] = 0x0; /* this page */ buf[5] = 0x80; /* unit serial number */ buf[6] = 0x83; /* device identification */ - resp_len = buf[3] + 4; + buf[7] = 0xB0; /* block limits */ + buf[8] = 0xB1; /* block device characteristics */ + resp_len = buf[3] + 6; } else if (0x80 == cmd->cdb[2]) { /* unit serial number */ int usn_len = strlen(dev->usn); buf[1] = 0x80; @@ -952,6 +1040,55 @@ static void exec_inquiry(struct vdisk_cmd *vcmd) buf[2] = (resp_len >> 8) & 0xFF; buf[3] = resp_len & 0xFF; resp_len += 4; + } else if (0xB0 == cmd->cdb[2]) { + /* Block Limits */ + int max_transfer; + buf[1] = 0xB0; + buf[3] = 0x3C; + buf[5] = 0xFF; /* No MAXIMUM COMPARE AND WRITE LENGTH limit */ + /* Optimal transfer granuality is PAGE_SIZE */ + max_transfer = max((int)(4096/dev->block_size), (int)1); + buf[6] = (max_transfer >> 8) & 0xff; + buf[7] = max_transfer & 0xff; + /* + * Max transfer len is min of sg limit and 8M, but we + * don't have access to them here, so let's use 1M. + */ + max_transfer = 1*1024*1024; + buf[8] = (max_transfer >> 24) & 0xff; + buf[9] = (max_transfer >> 16) & 0xff; + buf[10] = (max_transfer >> 8) & 0xff; + buf[11] = max_transfer & 0xff; + /* + * Let's have optimal transfer len 512KB. Better to not + * set it at all, because we don't have such limit, + * but some initiators may not understand that (?). + * From other side, too big transfers are not optimal, + * because SGV cache supports only <4M buffers. + */ + max_transfer = min((int)max_transfer, (int)(512*1024 / dev->block_size)); + buf[12] = (max_transfer >> 24) & 0xff; + buf[13] = (max_transfer >> 16) & 0xff; + buf[14] = (max_transfer >> 8) & 0xff; + buf[15] = max_transfer & 0xff; + resp_len = buf[3] + 4; + } else if (0xB1 == cmd->cdb[2]) { + int r; + /* Block Device Characteristics */ + buf[1] = 0xB1; + buf[3] = 0x3C; +#if 0 + if (virt_dev->rotational) { +#endif + /* 15K RPM */ + r = 0x3A98; +#if 0 + } else + r = 1; +#endif + buf[4] = (r >> 8) & 0xff; + buf[5] = r & 0xff; + resp_len = buf[3] + 4; } else { TRACE_DBG("INQUIRY: Unsupported EVPD page %x", cmd->cdb[2]); @@ -995,7 +1132,7 @@ static void exec_inquiry(struct vdisk_cmd *vcmd) memcpy(address, buf, length); if (length < reply->resp_data_len) - reply->resp_data_len = length; + set_resp_data_len(vcmd, length); out: TRACE_EXIT(); @@ -1019,7 +1156,7 @@ static void exec_request_sense(struct vdisk_cmd *vcmd) memcpy(address, b, length); if (length < reply->resp_data_len) - reply->resp_data_len = length; + set_resp_data_len(vcmd, length); TRACE_EXIT(); return; @@ -1260,7 +1397,7 @@ static void exec_mode_sense(struct vdisk_cmd *vcmd) memcpy(address, buf, offset); if (offset < reply->resp_data_len) - reply->resp_data_len = offset; + set_resp_data_len(vcmd, offset); out: TRACE_EXIT(); @@ -1391,7 +1528,7 @@ static void exec_read_capacity(struct vdisk_cmd *vcmd) memcpy(address, buffer, length); if (length < reply->resp_data_len) - reply->resp_data_len = length; + set_resp_data_len(vcmd, length); TRACE_EXIT(); return; @@ -1450,7 +1587,7 @@ static void exec_read_capacity16(struct vdisk_cmd *vcmd) memcpy(address, buffer, length); if (length < reply->resp_data_len) - reply->resp_data_len = length; + set_resp_data_len(vcmd, length); TRACE_EXIT(); return; @@ -1529,7 +1666,7 @@ static void exec_read_toc(struct vdisk_cmd *vcmd) memcpy(address, buffer, off); if (off < reply->resp_data_len) - reply->resp_data_len = off; + set_resp_data_len(vcmd, off); out: TRACE_EXIT(); @@ -1572,7 +1709,6 @@ static void exec_read(struct vdisk_cmd *vcmd, loff_t loff) { struct vdisk_dev *dev = vcmd->dev; struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd; - struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply; int length = cmd->bufflen; uint8_t *address = (uint8_t*)(unsigned long)cmd->pbuf; int fd = vcmd->fd; @@ -1601,7 +1737,7 @@ static void exec_read(struct vdisk_cmd *vcmd, loff_t loff) PRINT_ERROR("read() returned %"PRId64" from %d (errno %d)", (uint64_t)err, length, errno); if (err == -EAGAIN) - set_busy(reply); + set_busy(vcmd); else { set_cmd_error(vcmd, SCST_LOAD_SENSE(scst_sense_read_error)); @@ -1609,7 +1745,7 @@ static void exec_read(struct vdisk_cmd *vcmd, loff_t loff) goto out; } - reply->resp_data_len = cmd->bufflen; + set_resp_data_len(vcmd, cmd->bufflen); out: TRACE_EXIT(); @@ -1620,7 +1756,6 @@ static void exec_write(struct vdisk_cmd *vcmd, loff_t loff) { struct vdisk_dev *dev = vcmd->dev; struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd; - struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply; loff_t err; int length = cmd->bufflen; uint8_t *address = (uint8_t*)(unsigned long)cmd->pbuf; @@ -1653,7 +1788,7 @@ restart: PRINT_ERROR("write() returned %"PRId64" from %d (errno %d, " "cmd_h %x)", err, length, errno, vcmd->cmd->cmd_h); if (err == -EAGAIN) - set_busy(reply); + set_busy(vcmd); else { set_cmd_error(vcmd, SCST_LOAD_SENSE(scst_sense_write_error)); @@ -1682,7 +1817,6 @@ static void exec_verify(struct vdisk_cmd *vcmd, loff_t loff) { struct vdisk_dev *dev = vcmd->dev; struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd; - struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply; loff_t err; int64_t length = cmd->bufflen; uint8_t *address = (uint8_t *)(unsigned long)cmd->pbuf; @@ -1735,7 +1869,7 @@ static void exec_verify(struct vdisk_cmd *vcmd, loff_t loff) PRINT_ERROR("read() returned %"PRId64" from %"PRId64" " "(errno %d)", (uint64_t)err, len_mem, errno); if (err == -EAGAIN) - set_busy(reply); + set_busy(vcmd); else { set_cmd_error(vcmd, SCST_LOAD_SENSE(scst_sense_read_error)); @@ -1761,3 +1895,34 @@ out: TRACE_EXIT(); return; } + +static void exec_write_same(struct vdisk_cmd *vcmd) +{ + struct vdisk_dev *dev = vcmd->dev; + struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd; + struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply; + uint64_t blocks = cmd->data_len >> dev->block_shift; + static struct scst_user_data_descriptor uwhere[3]; + + TRACE_ENTRY(); + + if (blocks < 2) + goto out; + + /* It's only a debug code, so it's OK to use static descr */ + + memset(uwhere, 0, sizeof(uwhere)); + + uwhere[0].usdd_lba = cmd->lba; + uwhere[0].usdd_blocks = 1; + uwhere[1].usdd_lba = cmd->lba+1; + uwhere[1].usdd_blocks = blocks-1; + + reply->reply_type = SCST_EXEC_REPLY_DO_WRITE_SAME; + reply->ws_descriptors_len = sizeof(uwhere); + reply->ws_descriptors = (unsigned long)uwhere; + +out: + TRACE_EXIT(); + return; +} diff --git a/usr/fileio/common.h b/usr/fileio/common.h index f526bd8ca..aacdbc5da 100644 --- a/usr/fileio/common.h +++ b/usr/fileio/common.h @@ -23,10 +23,14 @@ #include "debug.h" +#ifndef WRITE_SAME_10 +#define WRITE_SAME_10 0x41 +#endif + /* 8 byte ASCII Vendor */ #define VENDOR "SCST_USR" /* 4 byte ASCII Product Revision Level - left aligned */ -#define FIO_REV " 310" +#define FIO_REV " 320" #define MAX_USN_LEN (20+1) /* For '\0' */ @@ -93,6 +97,7 @@ struct vdisk_cmd int fd; struct scst_user_get_cmd *cmd; struct vdisk_dev *dev; + unsigned int may_need_to_free_pbuf:1; struct scst_user_reply_cmd *reply; uint8_t sense[SCST_SENSE_BUFFERSIZE]; }; diff --git a/usr/fileio/debug.c b/usr/fileio/debug.c index 296025302..03c08a556 100644 --- a/usr/fileio/debug.c +++ b/usr/fileio/debug.c @@ -1,148 +1 @@ -/* - * debug.c - * - * Copyright (C) 2004 - 2015 Vladislav Bolkhovitin - * Copyright (C) 2004 - 2005 Leonid Stoljar - * Copyright (C) 2007 - 2015 SanDisk Corporation - * - * Contains helper functions for execution tracing and error reporting. - * Intended to be included in main .c file. - * - * 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, version 2 - * of the License. - * - * 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. - */ - -#include -#include -#include -#include -#include -#include - -#include "debug.h" - -pid_t gettid (void) -{ - return syscall(__NR_gettid); -} - -#if defined(DEBUG) || defined(TRACING) - -#define TRACE_BUF_SIZE 512 - -static char trace_buf[TRACE_BUF_SIZE]; -static pthread_spinlock_t trace_buf_lock; - -int debug_print_prefix(unsigned long trace_flag, const char *prefix, - const char *func, int line) -{ - int i = 0; - - pthread_spin_lock(&trace_buf_lock); - - if (trace_flag & TRACE_TIME) { - struct tm t; - time_t tt; - time(&tt); - localtime_r(&tt, &t); - i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, "%d:%d:%d ", - t.tm_hour, t.tm_min, t.tm_sec); - } - if (trace_flag & TRACE_PID) - i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, "[%d]: ", - gettid()); - if (prefix != NULL) - i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, "%s:", prefix); - if (trace_flag & TRACE_FUNCTION) - i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, "%s:", func); - if (trace_flag & TRACE_LINE) - i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, "%i:", line); - - if (i > 0) - PRINTN("%s", trace_buf); - - pthread_spin_unlock(&trace_buf_lock); - - return i; -} - -void debug_print_buffer(const void *data, int len) -{ - int z, z1, i; - const unsigned char *buf = (const unsigned char *) data; - int f = 0; - - if (buf == NULL) - return; - - pthread_spin_lock(&trace_buf_lock); - - PRINT(" (h)___0__1__2__3__4__5__6__7__8__9__A__B__C__D__E__F"); - for (z = 0, z1 = 0, i = 0; z < len; z++) { - if (z % 16 == 0) { - if (z != 0) { - i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, - " "); - for (; (z1 < z) && (i < TRACE_BUF_SIZE - 1); - z1++) { - if ((buf[z1] >= 0x20) && - (buf[z1] < 0x80)) - trace_buf[i++] = buf[z1]; - else - trace_buf[i++] = '.'; - } - trace_buf[i] = '\0'; - PRINT("%s", trace_buf); - i = 0; - f = 1; - } - i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, - "%4x: ", z); - } - i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, "%02x ", - buf[z]); - } - i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, " "); - for (; (z1 < z) && (i < TRACE_BUF_SIZE - 1); z1++) { - if ((buf[z1] > 0x20) && (buf[z1] < 0x80)) - trace_buf[i++] = buf[z1]; - else - trace_buf[i++] = '.'; - } - trace_buf[i] = '\0'; - if (f) { - PRINT("%s", trace_buf) - } else { - PRINT("%s", trace_buf); - } - - pthread_spin_unlock(&trace_buf_lock); - return; -} - -int debug_init(void) -{ - int res; - - res = pthread_spin_init(&trace_buf_lock, PTHREAD_PROCESS_PRIVATE); - if (res != 0) { - res = errno; - PRINT_ERROR("pthread_spin_init() failed: %s", strerror(res)); - } - - return res; -} - -void debug_done(void) -{ - pthread_spin_destroy(&trace_buf_lock); -} - -#endif /* DEBUG || TRACING */ +#include "../include/debug.c" diff --git a/usr/fileio/fileio.c b/usr/fileio/fileio.c index 9c2d91fe6..ec19a1980 100644 --- a/usr/fileio/fileio.c +++ b/usr/fileio/fileio.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -36,7 +37,9 @@ char *app_name; +#include "version.h" #include "common.h" +#include "debug.h" #if defined(DEBUG) || defined(TRACING) @@ -62,11 +65,11 @@ char *app_name; # endif #endif /* DEBUG */ +bool log_daemon = false; unsigned long trace_flag = DEFAULT_LOG_FLAGS; #endif /* defined(DEBUG) || defined(TRACING) */ #define DEF_BLOCK_SHIFT 9 -#define VERSION_STR "3.1.0-pre1" #define THREADS 7 #define MAX_VDEVS 10 @@ -391,6 +394,9 @@ int start(int argc, char **argv) desc.opt.queue_alg = SCST_QUEUE_ALG_1_UNRESTRICTED_REORDER; desc.opt.qerr = SCST_QERR_0_ALL_RESUME; desc.opt.d_sense = SCST_D_SENSE_0_FIXED_SENSE; +#ifdef DEBUG_EXT_COPY_REMAP + desc.opt.ext_copy_remap_supported = 1; +#endif res = ioctl(devs[i].scst_usr_fd, SCST_USER_REGISTER_DEVICE, &desc); if (res != 0) { diff --git a/usr/include/debug.c b/usr/include/debug.c new file mode 100644 index 000000000..8d77e475b --- /dev/null +++ b/usr/include/debug.c @@ -0,0 +1,147 @@ +/* + * debug.c + * + * Copyright (C) 2004 - 2015 Vladislav Bolkhovitin + * Copyright (C) 2004 - 2005 Leonid Stoljar + * Copyright (C) 2007 - 2015 SanDisk Corporation + * + * Contains helper functions for execution tracing and error reporting. + * Intended to be included in main .c file. + * + * 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, version 2 + * of the License. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include + +#include "debug.h" + +pid_t gettid(void) +{ + return syscall(__NR_gettid); +} + +#if defined(DEBUG) || defined(TRACING) + +#define TRACE_BUF_SIZE 512 + +static char trace_buf[TRACE_BUF_SIZE]; +static pthread_spinlock_t trace_buf_lock; + +int debug_print_prefix(unsigned long trace_flag, const char *prefix, + const char *func, int line) +{ + int i = 0; + + pthread_spin_lock(&trace_buf_lock); + + if (trace_flag & TRACE_TIME) { + struct tm t; + time_t tt; + + time(&tt); + localtime_r(&tt, &t); + i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, "%d:%d:%d ", + t.tm_hour, t.tm_min, t.tm_sec); + } + if (trace_flag & TRACE_PID) + i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, "[%d]: ", + gettid()); + if (prefix != NULL) + i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, "%s:", prefix); + if (trace_flag & TRACE_FUNCTION) + i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, "%s:", func); + if (trace_flag & TRACE_LINE) + i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, "%i:", line); + + if (i > 0) + PRINTN(LOG_INFO, "%s", trace_buf); + + pthread_spin_unlock(&trace_buf_lock); + + return i; +} + +void debug_print_buffer(const void *data, int len) +{ + int z, z1, i; + const unsigned char *buf = (const unsigned char *) data; + int f = 0; + + if (buf == NULL) + return; + + pthread_spin_lock(&trace_buf_lock); + + PRINT(LOG_INFO, " (h)___0__1__2__3__4__5__6__7__8__9__A__B__C__D__E__F"); + for (z = 0, z1 = 0, i = 0; z < len; z++) { + if (z % 16 == 0) { + if (z != 0) { + i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, + " "); + for (; (z1 < z) && (i < TRACE_BUF_SIZE - 1); + z1++) { + if ((buf[z1] >= 0x20) && + (buf[z1] < 0x80)) + trace_buf[i++] = buf[z1]; + else + trace_buf[i++] = '.'; + } + trace_buf[i] = '\0'; + PRINT(LOG_INFO, "%s", trace_buf); + i = 0; + f = 1; + } + i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, + "%4x: ", z); + } + i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, "%02x ", + buf[z]); + } + i += snprintf(&trace_buf[i], TRACE_BUF_SIZE - i, " "); + for (; (z1 < z) && (i < TRACE_BUF_SIZE - 1); z1++) { + if ((buf[z1] > 0x20) && (buf[z1] < 0x80)) + trace_buf[i++] = buf[z1]; + else + trace_buf[i++] = '.'; + } + trace_buf[i] = '\0'; + if (f) + PRINT(LOG_INFO, "%s", trace_buf); + else + PRINT(LOG_INFO, "%s", trace_buf); + + pthread_spin_unlock(&trace_buf_lock); +} + +int debug_init(void) +{ + int res; + + res = pthread_spin_init(&trace_buf_lock, PTHREAD_PROCESS_PRIVATE); + if (res != 0) { + res = errno; + PRINT_ERROR("pthread_spin_init() failed: %s", strerror(res)); + } + + return res; +} + +void debug_done(void) +{ + pthread_spin_destroy(&trace_buf_lock); +} + +#endif /* DEBUG || TRACING */ diff --git a/usr/fileio/debug.h b/usr/include/debug.h similarity index 54% rename from usr/fileio/debug.h rename to usr/include/debug.h index dbd0b8af1..bba07f5ef 100644 --- a/usr/fileio/debug.h +++ b/usr/include/debug.h @@ -24,6 +24,8 @@ #include #include #include +#include +#include extern pid_t gettid(void); @@ -55,8 +57,21 @@ extern pid_t gettid(void); #define TRACE_ORDER 0x00008000 #define TRACE_ALL 0xffffffff -#define PRINT(format, args...) fprintf(stdout, format "\n", ## args); -#define PRINTN(format, args...) fprintf(stdout, format, ## args); +#define PRINT(priority, format, args...) \ +do { \ + if (log_daemon) \ + syslog(priority, format "\n", ## args); \ + else \ + fprintf(stdout, format "\n", ## args); \ +} while (0) + +#define PRINTN(priority, format, args...) \ +do { \ + if (log_daemon) \ + syslog(priority, format, ## args); \ + else \ + fprintf(stdout, format, ## args); \ +} while (0) extern char *app_name; #define LOG_PREFIX app_name @@ -69,6 +84,7 @@ extern char *app_name; #if defined(DEBUG) || defined(TRACING) +extern bool log_daemon; extern unsigned long trace_flag; extern int debug_init(void); @@ -80,24 +96,23 @@ extern void debug_done(void); */ extern int debug_print_prefix(unsigned long trace_flag, const char *prefix, - const char *func, int line); + const char *func, int line); extern void debug_print_buffer(const void *data, int len); #define TRACE(trace, format, args...) \ do { \ - if (trace_flag & (trace)) \ - { \ - debug_print_prefix(trace_flag, __LOG_PREFIX, __FUNCTION__, \ - __LINE__); \ - PRINT(format, args); \ - } \ -} while(0) + if (trace_flag & (trace)) { \ + debug_print_prefix(trace_flag, __LOG_PREFIX, \ + __func__, __LINE__); \ + PRINT(LOG_DEBUG, format, args); \ + } \ +} while (0) #define PRINT_BUFFER(message, buff, len) \ do { \ - PRINT("%s:", message); \ - debug_print_buffer(buff, len); \ -} while(0) + PRINT(LOG_INFO, "%s:", message); \ + debug_print_buffer(buff, len); \ +} while (0) #else /* DEBUG || TRACING */ @@ -115,142 +130,126 @@ static inline void debug_done(void) {} #define TRACE_MEM(format, args...) \ do { \ - if (trace_flag & TRACE_MEMORY) \ - { \ - debug_print_prefix(trace_flag, NULL, __FUNCTION__, \ - __LINE__); \ - PRINT(format, args); \ - } \ -} while(0) + if (trace_flag & TRACE_MEMORY) { \ + debug_print_prefix(trace_flag, NULL, \ + __func__, __LINE__); \ + PRINT(LOG_DEBUG, format, args); \ + } \ +} while (0) #define TRACE_DBG(format, args...) \ do { \ - if (trace_flag & TRACE_DEBUG) \ - { \ - debug_print_prefix(trace_flag, NULL, __FUNCTION__, \ - __LINE__); \ - PRINT(format, args); \ - } \ -} while(0) + if (trace_flag & TRACE_DEBUG) { \ + debug_print_prefix(trace_flag, NULL, \ + __func__, __LINE__); \ + PRINT(LOG_DEBUG, format, args); \ + } \ +} while (0) #define TRACE_DBG_SPECIAL(args...) TRACE(TRACE_DEBUG|TRACE_SPECIAL, args) #define TRACE_MGMT_DBG(format, args...) \ do { \ - if (trace_flag & TRACE_MGMT_DEBUG) \ - { \ - debug_print_prefix(trace_flag, NULL, __FUNCTION__, \ - __LINE__); \ - PRINT(format, args); \ - } \ -} while(0) + if (trace_flag & TRACE_MGMT_DEBUG) { \ + debug_print_prefix(trace_flag, NULL, \ + __func__, __LINE__); \ + PRINT(LOG_DEBUG, format, args); \ + } \ +} while (0) #define TRACE_BUFFER(message, buff, len) \ do { \ - if (trace_flag & TRACE_BUFF) \ - { \ - debug_print_prefix(trace_flag, NULL, __FUNCTION__, \ - __LINE__); \ - PRINT("%s:", message); \ - debug_print_buffer(buff, len); \ - } \ -} while(0) + if (trace_flag & TRACE_BUFF) { \ + debug_print_prefix(trace_flag, NULL, \ + __func__, __LINE__); \ + PRINT(LOG_DEBUG, "%s:", message); \ + debug_print_buffer(buff, len); \ + } \ +} while (0) #define TRACE_BUFF_FLAG(flag, message, buff, len) \ do { \ - if (trace_flag & (flag)) \ - { \ - debug_print_prefix(trace_flag, NULL, __FUNCTION__, \ - __LINE__); \ - PRINT("%s:", message); \ - debug_print_buffer(buff, len); \ - } \ -} while(0) + if (trace_flag & (flag)) { \ + debug_print_prefix(trace_flag, NULL, \ + __func__, __LINE__); \ + PRINT(LOG_DEBUG, "%s:", message); \ + debug_print_buffer(buff, len); \ + } \ +} while (0) #define PRINT_INFO(format, args...) \ do { \ - debug_print_prefix(trace_flag, __LOG_PREFIX, __FUNCTION__, \ - __LINE__); \ - PRINT(format, args); \ -} while(0) + debug_print_prefix(trace_flag, __LOG_PREFIX, \ + __func__, __LINE__); \ + PRINT(LOG_INFO, format, args); \ +} while (0) #define PRINT_WARNING(format, args...) \ do { \ - debug_print_prefix(trace_flag, __LOG_PREFIX, __FUNCTION__, \ - __LINE__); \ - PRINT("***WARNING*** " format, args); \ -} while(0) + debug_print_prefix(trace_flag, __LOG_PREFIX, \ + __func__, __LINE__); \ + PRINT(LOG_WARNING, "***WARNING*** " format, args); \ +} while (0) #define PRINT_ERROR(format, args...) \ do { \ - debug_print_prefix(trace_flag, __LOG_PREFIX, __FUNCTION__, \ - __LINE__); \ - PRINT("***ERROR*** " format, args); \ -} while(0) + debug_print_prefix(trace_flag, __LOG_PREFIX, \ + __func__, __LINE__); \ + PRINT(LOG_ERR, "***ERROR*** " format, args); \ +} while (0) #define TRACE_ENTRY() \ do { \ - if (trace_flag & TRACE_ENTRYEXIT) \ - { \ - if (trace_flag & TRACE_PID) \ - { \ - PRINT("[%d]: ENTRY %s", gettid(), \ - __FUNCTION__); \ - } \ - else \ - { \ - PRINT("ENTRY %s", __FUNCTION__); \ - } \ - } \ -} while(0) + if (trace_flag & TRACE_ENTRYEXIT) { \ + if (trace_flag & TRACE_PID) { \ + PRINT(LOG_DEBUG, "[%d]: ENTRY %s", \ + gettid(), __func__); \ + } else { \ + PRINT(LOG_DEBUG, "ENTRY %s", \ + __func__); \ + } \ + } \ +} while (0) #define TRACE_EXIT() \ do { \ - if (trace_flag & TRACE_ENTRYEXIT) \ - { \ - if (trace_flag & TRACE_PID) \ - { \ - PRINT("[%d]: EXIT %s", gettid(), \ - __FUNCTION__); \ - } \ - else \ - { \ - PRINT("EXIT %s", __FUNCTION__); \ - } \ - } \ -} while(0) + if (trace_flag & TRACE_ENTRYEXIT) { \ + if (trace_flag & TRACE_PID) { \ + PRINT(LOG_DEBUG, "[%d]: EXIT %s", \ + gettid(), __func__); \ + } else { \ + PRINT(LOG_DEBUG, "EXIT %s", __func__); \ + } \ + } \ +} while (0) #define TRACE_EXIT_RES(res) \ do { \ - if (trace_flag & TRACE_ENTRYEXIT) \ - { \ - if (trace_flag & TRACE_PID) \ - { \ - PRINT("[%d]: EXIT %s: %ld", gettid(), \ - __FUNCTION__, (long)(res)); \ - } \ - else \ - { \ - PRINT("EXIT %s: %ld", __FUNCTION__, (long)(res)); \ - } \ - } \ -} while(0) + if (trace_flag & TRACE_ENTRYEXIT) { \ + if (trace_flag & TRACE_PID) { \ + PRINT(LOG_DEBUG, "[%d]: EXIT %s: %ld", \ + gettid(), __func__, \ + (long)(res)); \ + } else { \ + PRINT(LOG_DEBUG, "EXIT %s: %ld", \ + __func__, (long)(res)); \ + } \ + } \ +} while (0) #define TRACE_EXIT_HRES(res) \ do { \ - if (trace_flag & TRACE_ENTRYEXIT) \ - { \ - if (trace_flag & TRACE_PID) \ - { \ - PRINT("[%d]: EXIT %s: 0x%lx", gettid(), \ - __FUNCTION__, (long)(res)); \ - } \ - else \ - { \ - PRINT("EXIT %s: %lx", __FUNCTION__, (long)(res)); \ - } \ - } \ -} while(0) + if (trace_flag & TRACE_ENTRYEXIT) { \ + if (trace_flag & TRACE_PID) { \ + PRINT(LOG_DEBUG, "[%d]: EXIT %s: 0x%lx",\ + gettid(), __func__, \ + (long)(res)); \ + } else { \ + PRINT(LOG_DEBUG, "EXIT %s: %lx", \ + __func__, (long)(res)); \ + } \ + } \ +} while (0) #else /* DEBUG */ @@ -271,39 +270,25 @@ do { \ #ifdef LOG_PREFIX #define PRINT_INFO(format, args...) \ -do { \ - PRINT("%s: " format, LOG_PREFIX, args); \ -} while(0) + PRINT(LOG_INFO, "%s: " format, LOG_PREFIX, args) \ #define PRINT_WARNING(format, args...) \ -do { \ - PRINT("%s: ***WARNING*** " \ - format, LOG_PREFIX, args); \ -} while(0) + PRINT(LOG_WARNING, "%s: ***WARNING*** " format, \ + LOG_PREFIX, args) \ #define PRINT_ERROR(format, args...) \ -do { \ - PRINT("%s: ***ERROR*** " \ - format, LOG_PREFIX, args); \ -} while(0) + PRINT(LOG_ERR, "%s: ***ERROR*** " format, \ + LOG_PREFIX, args) \ #else -#define PRINT_INFO(format, args...) \ -do { \ - PRINT(format, args); \ -} while(0) +#define PRINT_INFO(format, args...) PRINT(LOG_INFO, format, args) #define PRINT_WARNING(format, args...) \ -do { \ - PRINT("***WARNING*** " format, args); \ -} while(0) - + PRINT(LOG_WARNING, "***WARNING*** " format, args) \ #define PRINT_ERROR(format, args...) \ -do { \ - PRINT("***ERROR*** " format, args); \ -} while(0) + PRINT(LOG_ERR, "***ERROR*** " format, args) \ #endif /* LOG_PREFIX */ diff --git a/usr/include/version.h b/usr/include/version.h new file mode 100644 index 000000000..d1a857555 --- /dev/null +++ b/usr/include/version.h @@ -0,0 +1,24 @@ +/* + * version.h + * + * Copyright (C) 2015 SanDisk Corporation + * + * Contains macroses for execution tracing and error reporting + * + * 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, version 2 + * of the License. + * + * 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. + */ + +#ifndef __VERSION_H +#define __VERSION_H + +#define VERSION_STR "3.2.0-pre1" + +#endif /* __VERSION_H */ diff --git a/usr/stpgd/Makefile b/usr/stpgd/Makefile new file mode 100644 index 000000000..0f8336c95 --- /dev/null +++ b/usr/stpgd/Makefile @@ -0,0 +1,104 @@ +ifndef PREFIX + PREFIX=/usr/local +endif + +SHELL=/bin/bash + +SRCS_F = stpgd_main.c debug.c +OBJS_F = $(SRCS_F:.c=.o) + +#SRCS_C = +#OBJS_C = $(SRCS_C:.c=.o) + +SCST_INC_DIR := $(shell if [ -e "$$PWD/../../scst" ]; \ + then echo "$$PWD/../../scst/include"; \ + else echo "$(DESTDIR)$(PREFIX)/include/scst"; fi) +DEBUG_INC_DIR := ../include +INSTALL_DIR := $(DESTDIR)$(PREFIX)/sbin +ON_STPG := $(DESTDIR)$(PREFIX)/bin/scst/scst_on_stpg + +CFLAGS += -O2 -Wall -Wextra -Wno-unused-parameter -Wstrict-prototypes \ + -I$(SCST_INC_DIR) -I$(DEBUG_INC_DIR) -D_GNU_SOURCE -D__USE_FILE_OFFSET64 \ + -D__USE_LARGEFILE64 +PROGS = stpgd +LIBS = -lpthread + +CFLAGS += -DEXTRACHECKS +#CFLAGS += -DTRACING +CFLAGS += -DDEBUG -g -fno-inline -fno-inline-functions +CFLAGS += -W -Wno-unused-parameter +CFLAGS += $(LOCAL_CFLAGS) + +#CFLAGS += -DDEBUG_NOMEM + +all: $(PROGS) + +stpgd: .depend_f $(OBJS_F) + $(CC) $(OBJS_F) $(LIBS) $(LOCAL_LD_FLAGS) -o $@ + +#cdrom_tgt: .depend_c $(OBJS_C) +# $(CC) $(OBJS_C) $(LIBS) $(LOCAL_LD_FLAGS) -o $@ + +ifeq (.depend_f,$(wildcard .depend_f)) +-include .depend_f +endif + +#ifeq (.depend_c,$(wildcard .depend_c)) +#-include .depend_c +#endif + +%.o: %.c Makefile + $(CC) -c -o $(@) $(CFLAGS) $(<) + +.depend_f: + $(CC) -M $(CFLAGS) $(SRCS_F) >$(@) + +#.depend_c: +# $(CC) -M $(CFLAGS) $(SRCS_C) >$(@) + +install: all + install -d $(INSTALL_DIR) + install -m 700 $(PROGS) $(INSTALL_DIR) + install -m 700 scst_on_stpg $(ON_STPG) + +uninstall: + rm -f $(INSTALL_DIR)/$(PROGS) + rm -rf $(INSTALL_DIR) + +clean: + rm -f *.o $(PROGS) .depend* + +extraclean: clean + rm -f *.orig *.rej + +2release: + sed -i.aa s/"^C\?FLAGS += \-DEXTRACHECKS"/"#CFLAGS += \-DEXTRACHECKS"/ Makefile + grep "^#CFLAGS += \-DEXTRACHECKS" Makefile >/dev/null + sed -i.aa s/"^#\?CFLAGS += \-DTRACING"/"CFLAGS += \-DTRACING"/ Makefile + grep "^CFLAGS += \-DTRACING" Makefile >/dev/null + sed -i.aa s/"^C\?FLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions"/"#CFLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions"/ Makefile + grep "^#CFLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions" Makefile >/dev/null + rm Makefile.aa + +2debug: + sed -i.aa s/"^#\?CFLAGS += \-DEXTRACHECKS"/"CFLAGS += \-DEXTRACHECKS"/ Makefile + grep "^CFLAGS += \-DEXTRACHECKS" Makefile >/dev/null + sed -i.aa s/"^C\?FLAGS += \-DTRACING"/"#CFLAGS += \-DTRACING"/ Makefile + grep "^#CFLAGS += \-DTRACING" Makefile >/dev/null + sed -i.aa s/"^#\?CFLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions"/"CFLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions"/ Makefile + grep "^CFLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions" Makefile >/dev/null + rm Makefile.aa + +2perf: + sed -i.aa s/"^C\?FLAGS += \-DEXTRACHECKS"/"#CFLAGS += \-DEXTRACHECKS"/ Makefile + grep "^#CFLAGS += \-DEXTRACHECKS" Makefile >/dev/null + sed -i.aa s/"^C\?FLAGS += \-DTRACING"/"#CFLAGS += \-DTRACING"/ Makefile + grep "^#CFLAGS += \-DTRACING" Makefile >/dev/null + sed -i.aa s/"^C\?FLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions"/"#CFLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions"/ Makefile + grep "^#CFLAGS += \-DDEBUG -g -fno-inline -fno-inline-functions" Makefile >/dev/null + rm Makefile.aa + +release-archive: + ../../scripts/generate-release-archive stpgd "$$(sed -n 's/^#define[[:blank:]]VERSION_STR[[:blank:]]*\"\([^\"]*\)\".*/\1/p' stpgd_main.c)" + +.PHONY: all install uninstall clean extraclean 2release 2debug 2perf diff --git a/usr/stpgd/README b/usr/stpgd/README new file mode 100644 index 000000000..64b5ad98b --- /dev/null +++ b/usr/stpgd/README @@ -0,0 +1,9 @@ +This is stpgd service called by the SCST core upon receiving SET TARGET +PORT GROUPS command via SCST events subsystem. In turn, stpgd calls +external script to actually modify ALUA state as requested by the SET +TARGET PORT GROUPS command via cluster manager and SCST sysfs. You can +find example of such script in scst_on_stpg file. + +Reason why such dual stage approach is used is, because there is no way +from inside the kernel to control execution of external programs and +there is no way to write a service calling IOCTLs on shell. diff --git a/usr/stpgd/debug.c b/usr/stpgd/debug.c new file mode 100644 index 000000000..03c08a556 --- /dev/null +++ b/usr/stpgd/debug.c @@ -0,0 +1 @@ +#include "../include/debug.c" diff --git a/usr/stpgd/scst_on_stpg b/usr/stpgd/scst_on_stpg new file mode 100644 index 000000000..cb86767b0 --- /dev/null +++ b/usr/stpgd/scst_on_stpg @@ -0,0 +1,11 @@ +#!/bin/bash +# +# Script invoked by SCST for processing a SCSI SET TARGET PORT GROUPS command +# +# Arguments: SCST_DEVICE_NAME (device name where this STPG command was +# received on), SCST_PREV_ALUA_STATE, SCST_ALUA_STATE, +# SCST_DEVICE_GROUP and SCST_TARGET_GROUP + environmental variables +# with the same names set +# + +echo $SCST_ALUA_STATE >/sys/kernel/scst_tgt/device_groups/$SCST_DEVICE_GROUP/target_groups/$SCST_TARGET_GROUP/state diff --git a/usr/stpgd/stpgd_main.c b/usr/stpgd/stpgd_main.c new file mode 100644 index 000000000..625879cfc --- /dev/null +++ b/usr/stpgd/stpgd_main.c @@ -0,0 +1,562 @@ +/* + * stpgd_main.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "version.h" +#include "debug.h" +#include "scst_event.h" + +char *app_name; + +#define DEFAULT_TRANSITION_TIME 17 + +#if defined(DEBUG) || defined(TRACING) + +#ifdef DEBUG + +#define DEFAULT_LOG_FLAGS (TRACE_OUT_OF_MEM | TRACE_MINOR | TRACE_PID | \ + TRACE_FUNCTION | TRACE_SPECIAL | TRACE_MGMT | TRACE_MGMT_DEBUG | \ + TRACE_TIME) + +#define TRACE_SN(args...) TRACE(TRACE_SCSI_SERIALIZING, args) + +#else /* DEBUG */ + +# ifdef TRACING +#define DEFAULT_LOG_FLAGS (TRACE_OUT_OF_MEM | TRACE_MGMT | \ + TRACE_TIME | TRACE_SPECIAL) +# else +#define DEFAULT_LOG_FLAGS 0 +# endif +#endif /* DEBUG */ + +#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) + +bool log_daemon = true; +unsigned long trace_flag = DEFAULT_LOG_FLAGS; +#endif /* defined(DEBUG) || defined(TRACING) */ + +int transition_timeout = DEFAULT_TRANSITION_TIME; + +static struct option const long_options[] = { + {"path", required_argument, 0, 'p'}, + {"timeout", required_argument, 0, 't'}, + {"foreground", no_argument, 0, 'f'}, +#if defined(DEBUG) || defined(TRACING) + {"debug", required_argument, 0, 'd'}, +#endif + {"version", no_argument, 0, 'v'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0}, +}; + +int stpg_init_report_pipe[2]; +char *stpg_path; + +static void usage(int status) +{ + if (status != 0) + fprintf(stderr, "Try '%s --help' for more information.\n", app_name); + else { + printf("Usage: %s [OPTIONS]\n", app_name); + printf("STPG target daemon\n"); + printf(" -f, --foreground make the program run in the foreground\n"); + printf(" -p, --path absolute path to the STPG script\n"); + printf(" -t, --timeout transition timeout\n"); +#if defined(DEBUG) || defined(TRACING) + printf(" -d, --debug=level debug tracing level\n"); +#endif + printf(" -h, --help display this help and exit\n"); + } +} + +static void stpg_handle_tm_received(struct scst_event_user *event_user) +{ + /* + * Put code to abort state transition here, if this STPG cmd, + * identified by cmd_to_abort_tag or RESET, requested to be aborted + */ +} + +int invoke_stpg(const uint8_t *device_name, + const struct scst_event_stpg_descr *descr, pid_t *out_pid) +{ + char *args[7], *env[7]; + int res = 0, ret, i; + pid_t c_pid; + + args[0] = stpg_path; + args[1] = (char *)device_name; + args[2] = (char *)descr->prev_state; + args[3] = (char *)descr->new_state; + args[4] = (char *)descr->dg_name; + args[5] = (char *)descr->tg_name; + args[6] = NULL; + + env[0] = "PATH=/bin:/usr/bin:/sbin:/usr/sbin"; + ret = asprintf(&env[1], "SCST_DEVICE_NAME=%s", device_name); + if (ret < 0) { + res = -errno; + PRINT_ERROR("asprintf() failed: %d (%s)", res, strerror(-res)); + goto out; + } + ret = asprintf(&env[2], "SCST_PREV_ALUA_STATE=%s", descr->prev_state); + if (ret < 0) { + res = -errno; + PRINT_ERROR("asprintf() failed: %d (%s)", res, strerror(-res)); + goto out; + } + ret = asprintf(&env[3], "SCST_ALUA_STATE=%s", descr->new_state); + if (ret < 0) { + res = -errno; + PRINT_ERROR("asprintf() failed: %d (%s)", res, strerror(-res)); + goto out; + } + ret = asprintf(&env[4], "SCST_DEVICE_GROUP=%s", descr->dg_name); + if (ret < 0) { + res = -errno; + PRINT_ERROR("asprintf() failed: %d (%s)", res, strerror(-res)); + goto out; + } + ret = asprintf(&env[5], "SCST_TARGET_GROUP=%s", descr->tg_name); + if (ret < 0) { + res = -errno; + PRINT_ERROR("asprintf() failed: %d (%s)", res, strerror(-res)); + goto out; + } + env[6] = NULL; + + PRINT_INFO("Invoking script %s with parameters: %s %s %s %s %s and environment: " + "%s %s %s %s %s", stpg_path, args[1], args[2], args[3], + args[4], args[5], env[1], env[2], env[3], env[4], env[5]); + + c_pid = fork(); + if (c_pid == 0) { + ret = setpgid(getpid(), getpid()); + if (ret < 0) { + res = -errno; + PRINT_ERROR("setgid failed %d (%s)", ret, strerror(-ret)); + } + TRACE_DBG("pgid %d (pid %d)", getpgid(getpid()), getpid()); + ret = execve(stpg_path, args, env); + if (ret < 0) { + res = -errno; + PRINT_ERROR("EXEC failed %d (%s)", ret, strerror(-ret)); + } + exit(0); + } else if (c_pid < 0) { + res = -errno; + PRINT_ERROR("fork() failed: %d (%s)", res, strerror(-res)); + } + + *out_pid = c_pid; + + for (i = 1; i < (signed)ARRAY_SIZE(env); i++) + free(env[i]); + +out: + return res; +} + +/* Returns 0, if the pid is still running, >0 if it was exited or <0 error code */ +int wait_until_finished(pid_t pid, unsigned long deadline, int *status, int child) +{ + int res; + time_t start, end; + double elapsed; + + TRACE_ENTRY(); + + time(&start); + do { + res = waitpid(pid, status, WNOHANG); + if (res != 0) { + if (res < 0) { + res = -errno; + PRINT_ERROR("Waitpid for pid %d (child %d) " + "failed: %d (%s)", pid, child, + errno, strerror(errno)); + } + break; + } + sleep(0.1); + time(&end); + elapsed = difftime(end, start); + } while (elapsed < deadline); + + TRACE_EXIT_RES(res); + return res; +} + +int handle_stpg_received(struct scst_event_user *event_user) +{ + const struct scst_event_stpg_payload *p = (struct scst_event_stpg_payload *)event_user->out_event.payload; + int num, k; + int res = 0; + pid_t pids[p->stpg_descriptors_cnt]; + + TRACE_DBG("device name %s, stpg_descriptors_cnt %d", p->device_name, + p->stpg_descriptors_cnt); + + for (num = 0; num < p->stpg_descriptors_cnt; num++) { + res = invoke_stpg(p->device_name, &p->stpg_descriptors[num], &pids[num]); + TRACE_DBG("num %d, res %d, pid %d", num, res, pids[num]); + if (res != 0) + break; + } + + TRACE_DBG("num %d", num); + for (k = 0; k < num; k++) { + int status = 0, rc; + + TRACE_DBG("k %d, pid %d", k, pids[k]); + + rc = wait_until_finished(pids[k], transition_timeout, &status, k); + TRACE_DBG("rc %d, status %d", rc, WEXITSTATUS(status)); + if (rc > 0) { + if (res == 0) + res = WEXITSTATUS(status); + continue; + } else if (rc < 0) { + if (res == 0) + res = rc; + continue; + } + + PRINT_WARNING("on_stpg %d (pid %d) did not finish on time - " + "sending SIGTERM", k, pids[k]); + if (res == 0) + res = -ETIMEDOUT; + rc = killpg(pids[k], SIGTERM); + if (rc < 0) + PRINT_ERROR("Failed to send SIGTERM to child %d (pid %d): %d/%s", + k, pids[k], errno, strerror(errno)); + + rc = wait_until_finished(pids[k], 1, &status, k); + if (rc != 0) + continue; + + while (1) { + PRINT_WARNING("on_stpg %d (pid %d) did not finish on time - " + "sending SIGKILL", k, pids[k]); + rc = killpg(pids[k], SIGKILL); + if (rc < 0) { + PRINT_ERROR("Failed to send SIGKILL to child %d " + "(pid %d): %d/%s", k, pids[k], errno, + strerror(errno)); + break; + } + rc = wait_until_finished(pids[k], 1, &status, k); + if (rc != 0) + break; + }; + } + + TRACE_EXIT_RES(res); + return res; +} + +static int stpg_event_loop(void) +{ + int res = 0, status; + int event_fd; + uint8_t event_user_buf[1024*1024]; + pid_t c_pid = 0; + struct pollfd pl; + struct scst_event_user *event_user = + (struct scst_event_user *)event_user_buf; + struct scst_event e1; + bool first_error = true; + + event_fd = open(SCST_EVENT_DEV, O_RDWR); + if (event_fd < 0) { + res = -errno; + PRINT_ERROR("Unable to open SCST event device %s (%s)", + SCST_EVENT_DEV, strerror(-res)); + goto out; + } + + close(stpg_init_report_pipe[0]); + + if (log_daemon) + res = write(stpg_init_report_pipe[1], &res, sizeof(res)); + + close(stpg_init_report_pipe[1]); + + memset(&pl, 0, sizeof(pl)); + pl.fd = event_fd; + pl.events = POLLIN; + + memset(&e1, 0, sizeof(e1)); + e1.event_code = SCST_EVENT_STPG_USER_INVOKE; + strncpy(e1.issuer_name, SCST_EVENT_SCST_CORE_ISSUER, + sizeof(e1.issuer_name)); + e1.issuer_name[sizeof(e1.issuer_name)-1] = '\0'; + PRINT_INFO("Setting allowed event code %d, issuer_name %s", + e1.event_code, e1.issuer_name); + + res = ioctl(event_fd, SCST_EVENT_ALLOW_EVENT, &e1); + if (res != 0) { + res = -errno; + PRINT_ERROR("SCST_EVENT_ALLOW_EVENT failed: %d (%s)", + res, strerror(-res)); + goto out; + } + + e1.event_code = SCST_EVENT_TM_FN_RECEIVED; + strncpy(e1.issuer_name, SCST_EVENT_SCST_CORE_ISSUER, + sizeof(e1.issuer_name)); + e1.issuer_name[sizeof(e1.issuer_name)-1] = '\0'; + PRINT_INFO("Setting allowed event code %d, issuer_name %s", + e1.event_code, e1.issuer_name); + res = ioctl(event_fd, SCST_EVENT_ALLOW_EVENT, &e1); + if (res != 0) { + res = -errno; + PRINT_ERROR("SCST_EVENT_ALLOW_EVENT failed: %d (%s)", + res, strerror(-res)); + goto out; + } + + while (1) { + memset(event_user_buf, 0, sizeof(event_user_buf)); + event_user->max_event_size = sizeof(event_user_buf); + res = ioctl(event_fd, SCST_EVENT_GET_NEXT_EVENT, event_user); + if (res != 0) { + res = -errno; + switch (-res) { + case ESRCH: + case EBUSY: + TRACE_MGMT_DBG("SCST_EVENT_GET_NEXT_EVENT " + "returned %d (%s)", res, strerror(res)); + /* go through */ + case EINTR: + continue; + case EAGAIN: + TRACE_DBG("SCST_EVENT_GET_NEXT_EVENT, " + "returned EAGAIN (%d)", -res); + continue; + default: + PRINT_ERROR("SCST_EVENT_GET_NEXT_EVENT " + "failed: %d (%s)", res, strerror(-res)); + if (!first_error) + goto out; + first_error = false; + continue; + } + first_error = true; +again_poll: + res = poll(&pl, 1, c_pid > 0 ? 1 : 0); + if (res > 0) + continue; + else if (res == 0) + goto again_poll; + else { + res = -errno; + switch (res) { + case ESRCH: + case EBUSY: + case EAGAIN: + TRACE_MGMT_DBG("poll() returned %d " + "(%s)", res, strerror(-res)); + case EINTR: + goto again_poll; + default: + PRINT_ERROR("poll() failed: %d (%s)", + res, strerror(-res)); + goto again_poll; + } + } + } + first_error = true; +#ifdef DEBUG + PRINT_INFO("event_code %d, issuer_name %s", + event_user->out_event.event_code, + event_user->out_event.issuer_name); +#endif + if (event_user->out_event.payload_len != 0) + TRACE_BUFFER("payload", event_user->out_event.payload, + event_user->out_event.payload_len); + + if (event_user->out_event.event_code == SCST_EVENT_STPG_USER_INVOKE) { + c_pid = fork(); + if (c_pid == -1) + PRINT_ERROR("Failed to fork: %d", c_pid); + else if (c_pid == 0) { + struct scst_event_notify_done d; + + signal(SIGCHLD, SIG_DFL); + + status = handle_stpg_received(event_user); + + memset(&d, 0, sizeof(d)); + d.event_id = event_user->out_event.event_id; + d.status = status; + res = ioctl(event_fd, SCST_EVENT_NOTIFY_DONE, &d); + if (res != 0) { + res = -errno; + PRINT_ERROR("SCST_EVENT_NOTIFY_DONE " + "failed: %s (res %d)", + strerror(-res), res); + } else + PRINT_INFO("STPG event completed with status %d", status); + exit(res); + } + } else if (event_user->out_event.event_code == SCST_EVENT_TM_FN_RECEIVED) + stpg_handle_tm_received(event_user); + else + PRINT_ERROR("Unknown event %d received", event_user->out_event.event_code); + } +out: + return res; +} + +void sig_chld(int signal) +{ + /* Check just in case */ + if (signal == SIGCHLD) { + TRACE_DBG("Cleanup zombie (pid %d)", getpid()); + wait(NULL); + } +} + +int main(int argc, char **argv) +{ + int res = 0, ch, longindex; + pid_t pid; + struct sigaction sa; + + setlinebuf(stdout); + + openlog(argv[0], LOG_PID, LOG_USER); + + res = pipe(stpg_init_report_pipe); + if (res == -1) { + res = -errno; + PRINT_ERROR("Pipe failed: %d (%s)", res, strerror(-res)); + goto out; + } + + sa.sa_handler = &sig_chld; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; + if (sigaction(SIGCHLD, &sa, 0) == -1) { + PRINT_ERROR("sigaction() failed: %d/%s", errno, strerror(errno)); + exit(1); + } + + /* + * Otherwise we could die in some later write() during the event_loop() + * instead of getting EPIPE! + */ + signal(SIGPIPE, SIG_IGN); + + res = debug_init(); + if (res != 0) + goto out; + + app_name = argv[0]; + + while ((ch = getopt_long(argc, argv, "+d:fp:t:hv", + long_options, &longindex)) >= 0) { + switch (ch) { + case 'p': + stpg_path = optarg; + break; +#if defined(DEBUG) || defined(TRACING) + case 'd': + trace_flag = strtol(optarg, (char **)NULL, 0); + break; +#endif + case 'v': + printf("%s version %s\n", app_name, VERSION_STR); + goto out_done; + case 'f': + log_daemon = false; + break; + case 't': + transition_timeout = strtol(optarg, (char **)NULL, 0); + if (transition_timeout < 0) { + printf("Invalid timeout %d\n", transition_timeout); + res = -EINVAL; + goto out_done; + } + break; + case 'h': + usage(0); + goto out_done; + default: + goto out_usage; + } + } + + if (!stpg_path) + stpg_path = "/usr/local/bin/scst/scst_on_stpg"; + + if (access(stpg_path, X_OK) == -1) { + PRINT_ERROR("Script file \" %s \"does not exist or not " + "executable", stpg_path); + res = -1; + goto out_done; + } + +#ifdef DEBUG + PRINT_INFO("trace_flag %lx", trace_flag); +#endif + if (log_daemon) { + trace_flag &= ~TRACE_TIME; + trace_flag &= ~TRACE_PID; + + pid = fork(); + if (pid < 0) { + PRINT_ERROR("starting daemon failed(%d)", pid); + res = pid; + goto out_done; + } else if (pid) { + int res1 = -1; + + close(stpg_init_report_pipe[1]); + if ((unsigned)read(stpg_init_report_pipe[0], &res1, sizeof(res1)) < sizeof(res1)) { + res = -1; + goto out_done; + } else { + res = res1; + goto out_done; + } + } + + close(0); + open("/dev/null", O_RDWR); + dup2(0, 1); + dup2(0, 2); + setsid(); + } + + res = stpg_event_loop(); + +out_done: + debug_done(); + +out: + closelog(); + return res; + +out_usage: + usage(1); + goto out_done; +}