drm/i915: SDVO hotplug have different interrupt status bits for i915/i965/g4x
[linux-2.6.git] / drivers / target / target_core_transport.c
index 35bae7d..69f3f7d 100644 (file)
@@ -37,6 +37,7 @@
 #include <linux/in.h>
 #include <linux/cdrom.h>
 #include <linux/module.h>
+#include <linux/ratelimit.h>
 #include <asm/unaligned.h>
 #include <net/sock.h>
 #include <net/tcp.h>
@@ -245,13 +246,14 @@ struct se_session *transport_init_session(void)
        INIT_LIST_HEAD(&se_sess->sess_cmd_list);
        INIT_LIST_HEAD(&se_sess->sess_wait_list);
        spin_lock_init(&se_sess->sess_cmd_lock);
+       kref_init(&se_sess->sess_kref);
 
        return se_sess;
 }
 EXPORT_SYMBOL(transport_init_session);
 
 /*
- * Called with spin_lock_bh(&struct se_portal_group->session_lock called.
+ * Called with spin_lock_irqsave(&struct se_portal_group->session_lock called.
  */
 void __transport_register_session(
        struct se_portal_group *se_tpg,
@@ -280,6 +282,8 @@ void __transport_register_session(
                                        &buf[0], PR_REG_ISID_LEN);
                        se_sess->sess_bin_isid = get_unaligned_be64(&buf[0]);
                }
+               kref_get(&se_nacl->acl_kref);
+
                spin_lock_irq(&se_nacl->nacl_sess_lock);
                /*
                 * The se_nacl->nacl_sess pointer will be set to the
@@ -304,12 +308,48 @@ void transport_register_session(
        struct se_session *se_sess,
        void *fabric_sess_ptr)
 {
-       spin_lock_bh(&se_tpg->session_lock);
+       unsigned long flags;
+
+       spin_lock_irqsave(&se_tpg->session_lock, flags);
        __transport_register_session(se_tpg, se_nacl, se_sess, fabric_sess_ptr);
-       spin_unlock_bh(&se_tpg->session_lock);
+       spin_unlock_irqrestore(&se_tpg->session_lock, flags);
 }
 EXPORT_SYMBOL(transport_register_session);
 
+static void target_release_session(struct kref *kref)
+{
+       struct se_session *se_sess = container_of(kref,
+                       struct se_session, sess_kref);
+       struct se_portal_group *se_tpg = se_sess->se_tpg;
+
+       se_tpg->se_tpg_tfo->close_session(se_sess);
+}
+
+void target_get_session(struct se_session *se_sess)
+{
+       kref_get(&se_sess->sess_kref);
+}
+EXPORT_SYMBOL(target_get_session);
+
+int target_put_session(struct se_session *se_sess)
+{
+       return kref_put(&se_sess->sess_kref, target_release_session);
+}
+EXPORT_SYMBOL(target_put_session);
+
+static void target_complete_nacl(struct kref *kref)
+{
+       struct se_node_acl *nacl = container_of(kref,
+                               struct se_node_acl, acl_kref);
+
+       complete(&nacl->acl_free_comp);
+}
+
+void target_put_nacl(struct se_node_acl *nacl)
+{
+       kref_put(&nacl->acl_kref, target_complete_nacl);
+}
+
 void transport_deregister_session_configfs(struct se_session *se_sess)
 {
        struct se_node_acl *se_nacl;
@@ -320,7 +360,8 @@ void transport_deregister_session_configfs(struct se_session *se_sess)
        se_nacl = se_sess->se_node_acl;
        if (se_nacl) {
                spin_lock_irqsave(&se_nacl->nacl_sess_lock, flags);
-               list_del(&se_sess->sess_acl_list);
+               if (se_nacl->acl_stop == 0)
+                       list_del(&se_sess->sess_acl_list);
                /*
                 * If the session list is empty, then clear the pointer.
                 * Otherwise, set the struct se_session pointer from the tail
@@ -347,13 +388,16 @@ EXPORT_SYMBOL(transport_free_session);
 void transport_deregister_session(struct se_session *se_sess)
 {
        struct se_portal_group *se_tpg = se_sess->se_tpg;
+       struct target_core_fabric_ops *se_tfo;
        struct se_node_acl *se_nacl;
        unsigned long flags;
+       bool comp_nacl = true;
 
        if (!se_tpg) {
                transport_free_session(se_sess);
                return;
        }
+       se_tfo = se_tpg->se_tpg_tfo;
 
        spin_lock_irqsave(&se_tpg->session_lock, flags);
        list_del(&se_sess->sess_list);
@@ -366,29 +410,34 @@ void transport_deregister_session(struct se_session *se_sess)
         * struct se_node_acl if it had been previously dynamically generated.
         */
        se_nacl = se_sess->se_node_acl;
-       if (se_nacl) {
-               spin_lock_irqsave(&se_tpg->acl_node_lock, flags);
-               if (se_nacl->dynamic_node_acl) {
-                       if (!se_tpg->se_tpg_tfo->tpg_check_demo_mode_cache(
-                                       se_tpg)) {
-                               list_del(&se_nacl->acl_list);
-                               se_tpg->num_node_acls--;
-                               spin_unlock_irqrestore(&se_tpg->acl_node_lock, flags);
-
-                               core_tpg_wait_for_nacl_pr_ref(se_nacl);
-                               core_free_device_list_for_node(se_nacl, se_tpg);
-                               se_tpg->se_tpg_tfo->tpg_release_fabric_acl(se_tpg,
-                                               se_nacl);
-                               spin_lock_irqsave(&se_tpg->acl_node_lock, flags);
-                       }
+
+       spin_lock_irqsave(&se_tpg->acl_node_lock, flags);
+       if (se_nacl && se_nacl->dynamic_node_acl) {
+               if (!se_tfo->tpg_check_demo_mode_cache(se_tpg)) {
+                       list_del(&se_nacl->acl_list);
+                       se_tpg->num_node_acls--;
+                       spin_unlock_irqrestore(&se_tpg->acl_node_lock, flags);
+                       core_tpg_wait_for_nacl_pr_ref(se_nacl);
+                       core_free_device_list_for_node(se_nacl, se_tpg);
+                       se_tfo->tpg_release_fabric_acl(se_tpg, se_nacl);
+
+                       comp_nacl = false;
+                       spin_lock_irqsave(&se_tpg->acl_node_lock, flags);
                }
-               spin_unlock_irqrestore(&se_tpg->acl_node_lock, flags);
        }
-
-       transport_free_session(se_sess);
+       spin_unlock_irqrestore(&se_tpg->acl_node_lock, flags);
 
        pr_debug("TARGET_CORE[%s]: Deregistered fabric_sess\n",
                se_tpg->se_tpg_tfo->get_fabric_name());
+       /*
+        * If last kref is dropping now for an explict NodeACL, awake sleeping
+        * ->acl_free_comp caller to wakeup configfs se_node_acl->acl_group
+        * removal context.
+        */
+       if (se_nacl && comp_nacl == true)
+               target_put_nacl(se_nacl);
+
+       transport_free_session(se_sess);
 }
 EXPORT_SYMBOL(transport_deregister_session);
 
@@ -699,17 +748,24 @@ void transport_complete_task(struct se_task *task, int success)
                spin_unlock_irqrestore(&cmd->t_state_lock, flags);
                return;
        }
-
-       if (cmd->transport_state & CMD_T_FAILED) {
+       /*
+        * Check for case where an explict ABORT_TASK has been received
+        * and transport_wait_for_tasks() will be waiting for completion..
+        */
+       if (cmd->transport_state & CMD_T_ABORTED &&
+           cmd->transport_state & CMD_T_STOP) {
+               spin_unlock_irqrestore(&cmd->t_state_lock, flags);
+               complete(&cmd->t_transport_stop_comp);
+               return;
+       } else if (cmd->transport_state & CMD_T_FAILED) {
                cmd->scsi_sense_reason = TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE;
                INIT_WORK(&cmd->work, target_complete_failure_work);
        } else {
-               cmd->transport_state |= CMD_T_COMPLETE;
                INIT_WORK(&cmd->work, target_complete_ok_work);
        }
 
        cmd->t_state = TRANSPORT_COMPLETE;
-       cmd->transport_state |= CMD_T_ACTIVE;
+       cmd->transport_state |= (CMD_T_COMPLETE | CMD_T_ACTIVE);
        spin_unlock_irqrestore(&cmd->t_state_lock, flags);
 
        queue_work(target_completion_wq, &cmd->work);
@@ -1686,6 +1742,14 @@ void target_submit_cmd(struct se_cmd *se_cmd, struct se_session *se_sess,
 }
 EXPORT_SYMBOL(target_submit_cmd);
 
+static void target_complete_tmr_failure(struct work_struct *work)
+{
+       struct se_cmd *se_cmd = container_of(work, struct se_cmd, work);
+
+       se_cmd->se_tmr_req->response = TMR_LUN_DOES_NOT_EXIST;
+       se_cmd->se_tfo->queue_tm_rsp(se_cmd);
+}
+
 /**
  * target_submit_tmr - lookup unpacked lun and submit uninitialized se_cmd
  *                     for TMR CDBs
@@ -1696,13 +1760,17 @@ EXPORT_SYMBOL(target_submit_cmd);
  * @unpacked_lun: unpacked LUN to reference for struct se_lun
  * @fabric_context: fabric context for TMR req
  * @tm_type: Type of TM request
+ * @gfp: gfp type for caller
+ * @tag: referenced task tag for TMR_ABORT_TASK
+ * @flags: submit cmd flags
  *
  * Callable from all contexts.
  **/
 
-void target_submit_tmr(struct se_cmd *se_cmd, struct se_session *se_sess,
+int target_submit_tmr(struct se_cmd *se_cmd, struct se_session *se_sess,
                unsigned char *sense, u32 unpacked_lun,
-               void *fabric_tmr_ptr, unsigned char tm_type, int flags)
+               void *fabric_tmr_ptr, unsigned char tm_type,
+               gfp_t gfp, unsigned int tag, int flags)
 {
        struct se_portal_group *se_tpg;
        int ret;
@@ -1712,25 +1780,32 @@ void target_submit_tmr(struct se_cmd *se_cmd, struct se_session *se_sess,
 
        transport_init_se_cmd(se_cmd, se_tpg->se_tpg_tfo, se_sess,
                              0, DMA_NONE, MSG_SIMPLE_TAG, sense);
+       /*
+        * FIXME: Currently expect caller to handle se_cmd->se_tmr_req
+        * allocation failure.
+        */
+       ret = core_tmr_alloc_req(se_cmd, fabric_tmr_ptr, tm_type, gfp);
+       if (ret < 0)
+               return -ENOMEM;
+
+       if (tm_type == TMR_ABORT_TASK)
+               se_cmd->se_tmr_req->ref_task_tag = tag;
 
        /* See target_submit_cmd for commentary */
        target_get_sess_cmd(se_sess, se_cmd, (flags & TARGET_SCF_ACK_KREF));
 
-       ret = core_tmr_alloc_req(se_cmd, fabric_tmr_ptr, tm_type, GFP_KERNEL);
-       if (ret < 0) {
-               dump_stack();
-               /* FIXME XXX */
-               return;
-       }
-
        ret = transport_lookup_tmr_lun(se_cmd, unpacked_lun);
        if (ret) {
-               transport_send_check_condition_and_sense(se_cmd,
-                       se_cmd->scsi_sense_reason, 0);
-               transport_generic_free_cmd(se_cmd, 0);
-               return;
+               /*
+                * For callback during failure handling, push this work off
+                * to process context with TMR_LUN_DOES_NOT_EXIST status.
+                */
+               INIT_WORK(&se_cmd->work, target_complete_tmr_failure);
+               schedule_work(&se_cmd->work);
+               return 0;
        }
        transport_generic_handle_tmr(se_cmd);
+       return 0;
 }
 EXPORT_SYMBOL(target_submit_tmr);
 
@@ -1900,6 +1975,7 @@ void transport_generic_request_failure(struct se_cmd *cmd)
        case TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE:
        case TCM_UNKNOWN_MODE_PAGE:
        case TCM_WRITE_PROTECTED:
+       case TCM_ADDRESS_OUT_OF_RANGE:
        case TCM_CHECK_CONDITION_ABORT_CMD:
        case TCM_CHECK_CONDITION_UNIT_ATTENTION:
        case TCM_CHECK_CONDITION_NOT_READY:
@@ -2364,7 +2440,7 @@ static void transport_xor_callback(struct se_cmd *cmd)
 
        offset = 0;
        for_each_sg(cmd->t_bidi_data_sg, sg, cmd->t_bidi_data_nents, count) {
-               addr = kmap_atomic(sg_page(sg), KM_USER0);
+               addr = kmap_atomic(sg_page(sg));
                if (!addr)
                        goto out;
 
@@ -2372,7 +2448,7 @@ static void transport_xor_callback(struct se_cmd *cmd)
                        *(addr + sg->offset + i) ^= *(buf + offset + i);
 
                offset += sg->length;
-               kunmap_atomic(addr, KM_USER0);
+               kunmap_atomic(addr);
        }
 
 out:
@@ -2559,6 +2635,7 @@ static int transport_generic_cmd_sequencer(
                                        cmd, cdb, pr_reg_type) != 0) {
                        cmd->se_cmd_flags |= SCF_SCSI_CDB_EXCEPTION;
                        cmd->se_cmd_flags |= SCF_SCSI_RESERVATION_CONFLICT;
+                       cmd->scsi_status = SAM_STAT_RESERVATION_CONFLICT;
                        cmd->scsi_sense_reason = TCM_RESERVATION_CONFLICT;
                        return -EBUSY;
                }
@@ -2866,7 +2943,7 @@ static int transport_generic_cmd_sequencer(
 
                        pr_err("Unsupported SA: 0x%02x\n",
                                cmd->t_task_cdb[1] & 0x1f);
-                       goto out_unsupported_cdb;
+                       goto out_invalid_cdb_field;
                }
                /*FALLTHROUGH*/
        case ACCESS_CONTROL_IN:
@@ -3089,15 +3166,27 @@ static int transport_generic_cmd_sequencer(
                        /* Returns CHECK_CONDITION + INVALID_CDB_FIELD */
                        goto out_invalid_cdb_field;
                }
-
+               /*
+                * For the overflow case keep the existing fabric provided
+                * ->data_length.  Otherwise for the underflow case, reset
+                * ->data_length to the smaller SCSI expected data transfer
+                * length.
+                */
                if (size > cmd->data_length) {
                        cmd->se_cmd_flags |= SCF_OVERFLOW_BIT;
                        cmd->residual_count = (size - cmd->data_length);
                } else {
                        cmd->se_cmd_flags |= SCF_UNDERFLOW_BIT;
                        cmd->residual_count = (cmd->data_length - size);
+                       cmd->data_length = size;
                }
-               cmd->data_length = size;
+       }
+
+       if (cmd->se_cmd_flags & SCF_SCSI_DATA_SG_IO_CDB &&
+           sectors > dev->se_sub_dev->se_dev_attrib.fabric_max_sectors) {
+               printk_ratelimited(KERN_ERR "SCSI OP %02xh with too big sectors %u\n",
+                                  cdb[0], sectors);
+               goto out_invalid_cdb_field;
        }
 
        /* reject any command that we don't have a handler for */
@@ -3590,9 +3679,9 @@ transport_generic_get_mem(struct se_cmd *cmd)
        return 0;
 
 out:
-       while (i >= 0) {
-               __free_page(sg_page(&cmd->t_data_sg[i]));
+       while (i > 0) {
                i--;
+               __free_page(sg_page(&cmd->t_data_sg[i]));
        }
        kfree(cmd->t_data_sg);
        cmd->t_data_sg = NULL;
@@ -4030,8 +4119,10 @@ void target_get_sess_cmd(struct se_session *se_sess, struct se_cmd *se_cmd,
         * fabric acknowledgement that requires two target_put_sess_cmd()
         * invocations before se_cmd descriptor release.
         */
-       if (ack_kref == true)
+       if (ack_kref == true) {
                kref_get(&se_cmd->cmd_kref);
+               se_cmd->se_cmd_flags |= SCF_ACK_KREF;
+       }
 
        spin_lock_irqsave(&se_sess->sess_cmd_lock, flags);
        list_add_tail(&se_cmd->se_cmd_list, &se_sess->sess_cmd_list);
@@ -4049,7 +4140,7 @@ static void target_release_cmd_kref(struct kref *kref)
        spin_lock_irqsave(&se_sess->sess_cmd_lock, flags);
        if (list_empty(&se_cmd->se_cmd_list)) {
                spin_unlock_irqrestore(&se_sess->sess_cmd_lock, flags);
-               WARN_ON(1);
+               se_cmd->se_tfo->release_cmd(se_cmd);
                return;
        }
        if (se_sess->sess_tearing_down && se_cmd->cmd_wait_set) {
@@ -4372,8 +4463,7 @@ bool transport_wait_for_tasks(struct se_cmd *cmd)
                cmd->transport_state &= ~CMD_T_LUN_STOP;
        }
 
-       if (!(cmd->transport_state & CMD_T_ACTIVE) ||
-            (cmd->transport_state & CMD_T_ABORTED)) {
+       if (!(cmd->transport_state & CMD_T_ACTIVE)) {
                spin_unlock_irqrestore(&cmd->t_state_lock, flags);
                return false;
        }
@@ -4571,6 +4661,15 @@ int transport_send_check_condition_and_sense(
                /* WRITE PROTECTED */
                buffer[offset+SPC_ASC_KEY_OFFSET] = 0x27;
                break;
+       case TCM_ADDRESS_OUT_OF_RANGE:
+               /* CURRENT ERROR */
+               buffer[offset] = 0x70;
+               buffer[offset+SPC_ADD_SENSE_LEN_OFFSET] = 10;
+               /* ILLEGAL REQUEST */
+               buffer[offset+SPC_SENSE_KEY_OFFSET] = ILLEGAL_REQUEST;
+               /* LOGICAL BLOCK ADDRESS OUT OF RANGE */
+               buffer[offset+SPC_ASC_KEY_OFFSET] = 0x21;
+               break;
        case TCM_CHECK_CONDITION_UNIT_ATTENTION:
                /* CURRENT ERROR */
                buffer[offset] = 0x70;
@@ -4679,7 +4778,7 @@ static int transport_generic_do_tmr(struct se_cmd *cmd)
 
        switch (tmr->function) {
        case TMR_ABORT_TASK:
-               tmr->response = TMR_FUNCTION_REJECTED;
+               core_tmr_abort_task(dev, tmr, cmd->se_sess);
                break;
        case TMR_ABORT_TASK_SET:
        case TMR_CLEAR_ACA: