diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2016-01-20 17:20:53 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2016-01-20 17:20:53 -0800 |
commit | 71e4634e00119b2fb8dd0da99b3f5ebbb49cc872 (patch) | |
tree | c42a37a98e99aa9ce8a2af79710e295704782a16 /drivers | |
parent | 19a3dd7621af01b7e44a70d16beab59326c38824 (diff) | |
parent | fab683eb12e71ac6057dc42dc7d1e5e71e5cba5e (diff) |
Merge branch 'for-next' of git://git.kernel.org/pub/scm/linux/kernel/git/nab/target-pending
Pull SCSI target updates from Nicholas Bellinger:
"The highlights this round include:
- Introduce configfs support for unlocked configfs_depend_item()
(krzysztof + andrezej)
- Conversion of usb-gadget target driver to new function registration
interface (andrzej + sebastian)
- Enable qla2xxx FC target mode support for Extended Logins (himansu +
giridhar)
- Enable qla2xxx FC target mode support for Exchange Offload (himansu +
giridhar)
- Add qla2xxx FC target mode irq affinity notification + selective
command queuing. (quinn + himanshu)
- Fix iscsi-target deadlock in se_node_acl configfs deletion (sagi +
nab)
- Convert se_node_acl configfs deletion + se_node_acl->queue_depth to
proper se_session->sess_kref + target_get_session() usage. (hch +
sagi + nab)
- Fix long-standing race between se_node_acl->acl_kref get and
get_initiator_node_acl() lookup. (hch + nab)
- Fix target/user block-size handling, and make sure netlink reaches
all network namespaces (sheng + andy)
Note there is an outstanding bug-fix series for remote I_T nexus port
TMR LUN_RESET has been posted and still being tested, and will likely
become post -rc1 material at this point"
* 'for-next' of git://git.kernel.org/pub/scm/linux/kernel/git/nab/target-pending: (56 commits)
scsi: qla2xxxx: avoid type mismatch in comparison
target/user: Make sure netlink would reach all network namespaces
target: Obtain se_node_acl->acl_kref during get_initiator_node_acl
target: Convert ACL change queue_depth se_session reference usage
iscsi-target: Fix potential dead-lock during node acl delete
ib_srpt: Convert acl lookup to modern get_initiator_node_acl usage
tcm_fc: Convert acl lookup to modern get_initiator_node_acl usage
tcm_fc: Wait for command completion before freeing a session
target: Fix a memory leak in target_dev_lba_map_store()
target: Support aborting tasks with a 64-bit tag
usb/gadget: Remove set-but-not-used variables
target: Remove an unused variable
target: Fix indentation in target_core_configfs.c
target/user: Allow user to set block size before enabling device
iser-target: Fix non negative ERR_PTR isert_device_get usage
target/fcoe: Add tag support to tcm_fc
qla2xxx: Check for online flag instead of active reset when transmitting responses
qla2xxx: Set all queues to 4k
qla2xxx: Disable ZIO at start time.
qla2xxx: Move atioq to a different lock to reduce lock contention
...
Diffstat (limited to 'drivers')
47 files changed, 4351 insertions, 2661 deletions
diff --git a/drivers/infiniband/ulp/isert/ib_isert.c b/drivers/infiniband/ulp/isert/ib_isert.c index 8a51c3b5d657..468c5e132563 100644 --- a/drivers/infiniband/ulp/isert/ib_isert.c +++ b/drivers/infiniband/ulp/isert/ib_isert.c @@ -350,7 +350,7 @@ isert_create_device_ib_res(struct isert_device *device) dev_attr = &device->dev_attr; ret = isert_query_device(device->ib_device, dev_attr); if (ret) - return ret; + goto out; /* asign function handlers */ if (dev_attr->device_cap_flags & IB_DEVICE_MEM_MGT_EXTENSIONS && @@ -366,7 +366,7 @@ isert_create_device_ib_res(struct isert_device *device) ret = isert_alloc_comps(device, dev_attr); if (ret) - return ret; + goto out; device->pd = ib_alloc_pd(device->ib_device); if (IS_ERR(device->pd)) { @@ -384,6 +384,9 @@ isert_create_device_ib_res(struct isert_device *device) out_cq: isert_free_comps(device); +out: + if (ret > 0) + ret = -EINVAL; return ret; } diff --git a/drivers/infiniband/ulp/srpt/ib_srpt.c b/drivers/infiniband/ulp/srpt/ib_srpt.c index 2e2fe818ca9f..bc5470c43d26 100644 --- a/drivers/infiniband/ulp/srpt/ib_srpt.c +++ b/drivers/infiniband/ulp/srpt/ib_srpt.c @@ -2370,31 +2370,6 @@ static void srpt_release_channel_work(struct work_struct *w) kfree(ch); } -static struct srpt_node_acl *__srpt_lookup_acl(struct srpt_port *sport, - u8 i_port_id[16]) -{ - struct srpt_node_acl *nacl; - - list_for_each_entry(nacl, &sport->port_acl_list, list) - if (memcmp(nacl->i_port_id, i_port_id, - sizeof(nacl->i_port_id)) == 0) - return nacl; - - return NULL; -} - -static struct srpt_node_acl *srpt_lookup_acl(struct srpt_port *sport, - u8 i_port_id[16]) -{ - struct srpt_node_acl *nacl; - - spin_lock_irq(&sport->port_acl_lock); - nacl = __srpt_lookup_acl(sport, i_port_id); - spin_unlock_irq(&sport->port_acl_lock); - - return nacl; -} - /** * srpt_cm_req_recv() - Process the event IB_CM_REQ_RECEIVED. * @@ -2412,10 +2387,10 @@ static int srpt_cm_req_recv(struct ib_cm_id *cm_id, struct srp_login_rej *rej; struct ib_cm_rep_param *rep_param; struct srpt_rdma_ch *ch, *tmp_ch; - struct srpt_node_acl *nacl; + struct se_node_acl *se_acl; u32 it_iu_len; - int i; - int ret = 0; + int i, ret = 0; + unsigned char *p; WARN_ON_ONCE(irqs_disabled()); @@ -2565,33 +2540,47 @@ static int srpt_cm_req_recv(struct ib_cm_id *cm_id, " RTR failed (error code = %d)\n", ret); goto destroy_ib; } + /* - * Use the initator port identifier as the session name. + * Use the initator port identifier as the session name, when + * checking against se_node_acl->initiatorname[] this can be + * with or without preceeding '0x'. */ snprintf(ch->sess_name, sizeof(ch->sess_name), "0x%016llx%016llx", be64_to_cpu(*(__be64 *)ch->i_port_id), be64_to_cpu(*(__be64 *)(ch->i_port_id + 8))); pr_debug("registering session %s\n", ch->sess_name); + p = &ch->sess_name[0]; - nacl = srpt_lookup_acl(sport, ch->i_port_id); - if (!nacl) { - pr_info("Rejected login because no ACL has been" - " configured yet for initiator %s.\n", ch->sess_name); + ch->sess = transport_init_session(TARGET_PROT_NORMAL); + if (IS_ERR(ch->sess)) { rej->reason = cpu_to_be32( - SRP_LOGIN_REJ_CHANNEL_LIMIT_REACHED); + SRP_LOGIN_REJ_INSUFFICIENT_RESOURCES); + pr_debug("Failed to create session\n"); goto destroy_ib; } - ch->sess = transport_init_session(TARGET_PROT_NORMAL); - if (IS_ERR(ch->sess)) { +try_again: + se_acl = core_tpg_get_initiator_node_acl(&sport->port_tpg_1, p); + if (!se_acl) { + pr_info("Rejected login because no ACL has been" + " configured yet for initiator %s.\n", ch->sess_name); + /* + * XXX: Hack to retry of ch->i_port_id without leading '0x' + */ + if (p == &ch->sess_name[0]) { + p += 2; + goto try_again; + } rej->reason = cpu_to_be32( - SRP_LOGIN_REJ_INSUFFICIENT_RESOURCES); - pr_debug("Failed to create session\n"); - goto deregister_session; + SRP_LOGIN_REJ_CHANNEL_LIMIT_REACHED); + transport_free_session(ch->sess); + goto destroy_ib; } - ch->sess->se_node_acl = &nacl->nacl; - transport_register_session(&sport->port_tpg_1, &nacl->nacl, ch->sess, ch); + ch->sess->se_node_acl = se_acl; + + transport_register_session(&sport->port_tpg_1, se_acl, ch->sess, ch); pr_debug("Establish connection sess=%p name=%s cm_id=%p\n", ch->sess, ch->sess_name, ch->cm_id); @@ -2635,8 +2624,6 @@ static int srpt_cm_req_recv(struct ib_cm_id *cm_id, release_channel: srpt_set_ch_state(ch, CH_RELEASING); transport_deregister_session_configfs(ch->sess); - -deregister_session: transport_deregister_session(ch->sess); ch->sess = NULL; @@ -3273,8 +3260,6 @@ static void srpt_add_one(struct ib_device *device) sport->port_attrib.srp_max_rsp_size = DEFAULT_MAX_RSP_SIZE; sport->port_attrib.srp_sq_size = DEF_SRPT_SQ_SIZE; INIT_WORK(&sport->work, srpt_refresh_port_work); - INIT_LIST_HEAD(&sport->port_acl_list); - spin_lock_init(&sport->port_acl_lock); if (srpt_refresh_port(sport)) { pr_err("MAD registration failed for %s-%d.\n", @@ -3508,42 +3493,15 @@ out: */ static int srpt_init_nodeacl(struct se_node_acl *se_nacl, const char *name) { - struct srpt_port *sport = - container_of(se_nacl->se_tpg, struct srpt_port, port_tpg_1); - struct srpt_node_acl *nacl = - container_of(se_nacl, struct srpt_node_acl, nacl); u8 i_port_id[16]; if (srpt_parse_i_port_id(i_port_id, name) < 0) { pr_err("invalid initiator port ID %s\n", name); return -EINVAL; } - - memcpy(&nacl->i_port_id[0], &i_port_id[0], 16); - nacl->sport = sport; - - spin_lock_irq(&sport->port_acl_lock); - list_add_tail(&nacl->list, &sport->port_acl_list); - spin_unlock_irq(&sport->port_acl_lock); - return 0; } -/* - * configfs callback function invoked for - * rmdir /sys/kernel/config/target/$driver/$port/$tpg/acls/$i_port_id - */ -static void srpt_cleanup_nodeacl(struct se_node_acl *se_nacl) -{ - struct srpt_node_acl *nacl = - container_of(se_nacl, struct srpt_node_acl, nacl); - struct srpt_port *sport = nacl->sport; - - spin_lock_irq(&sport->port_acl_lock); - list_del(&nacl->list); - spin_unlock_irq(&sport->port_acl_lock); -} - static ssize_t srpt_tpg_attrib_srp_max_rdma_size_show(struct config_item *item, char *page) { @@ -3820,7 +3778,6 @@ static const struct target_core_fabric_ops srpt_template = { .fabric_make_tpg = srpt_make_tpg, .fabric_drop_tpg = srpt_drop_tpg, .fabric_init_nodeacl = srpt_init_nodeacl, - .fabric_cleanup_nodeacl = srpt_cleanup_nodeacl, .tfc_wwn_attrs = srpt_wwn_attrs, .tfc_tpg_base_attrs = srpt_tpg_attrs, diff --git a/drivers/infiniband/ulp/srpt/ib_srpt.h b/drivers/infiniband/ulp/srpt/ib_srpt.h index 5faad8acd789..5366e0a9fd6d 100644 --- a/drivers/infiniband/ulp/srpt/ib_srpt.h +++ b/drivers/infiniband/ulp/srpt/ib_srpt.h @@ -364,11 +364,9 @@ struct srpt_port { u16 sm_lid; u16 lid; union ib_gid gid; - spinlock_t port_acl_lock; struct work_struct work; struct se_portal_group port_tpg_1; struct se_wwn port_wwn; - struct list_head port_acl_list; struct srpt_port_attrib port_attrib; }; @@ -409,15 +407,9 @@ struct srpt_device { /** * struct srpt_node_acl - Per-initiator ACL data (managed via configfs). * @nacl: Target core node ACL information. - * @i_port_id: 128-bit SRP initiator port ID. - * @sport: port information. - * @list: Element of the per-HCA ACL list. */ struct srpt_node_acl { struct se_node_acl nacl; - u8 i_port_id[16]; - struct srpt_port *sport; - struct list_head list; }; #endif /* IB_SRPT_H */ diff --git a/drivers/scsi/qla2xxx/qla_attr.c b/drivers/scsi/qla2xxx/qla_attr.c index 6b942d9e5b74..6992ebc50c87 100644 --- a/drivers/scsi/qla2xxx/qla_attr.c +++ b/drivers/scsi/qla2xxx/qla_attr.c @@ -824,6 +824,41 @@ static struct bin_attribute sysfs_reset_attr = { }; static ssize_t +qla2x00_issue_logo(struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct scsi_qla_host *vha = shost_priv(dev_to_shost(container_of(kobj, + struct device, kobj))); + int type; + int rval = 0; + port_id_t did; + + type = simple_strtol(buf, NULL, 10); + + did.b.domain = (type & 0x00ff0000) >> 16; + did.b.area = (type & 0x0000ff00) >> 8; + did.b.al_pa = (type & 0x000000ff); + + ql_log(ql_log_info, vha, 0x70e3, "portid=%02x%02x%02x done\n", + did.b.domain, did.b.area, did.b.al_pa); + + ql_log(ql_log_info, vha, 0x70e4, "%s: %d\n", __func__, type); + + rval = qla24xx_els_dcmd_iocb(vha, ELS_DCMD_LOGO, did); + return count; +} + +static struct bin_attribute sysfs_issue_logo_attr = { + .attr = { + .name = "issue_logo", + .mode = S_IWUSR, + }, + .size = 0, + .write = qla2x00_issue_logo, +}; + +static ssize_t qla2x00_sysfs_read_xgmac_stats(struct file *filp, struct kobject *kobj, struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count) @@ -937,6 +972,7 @@ static struct sysfs_entry { { "vpd", &sysfs_vpd_attr, 1 }, { "sfp", &sysfs_sfp_attr, 1 }, { "reset", &sysfs_reset_attr, }, + { "issue_logo", &sysfs_issue_logo_attr, }, { "xgmac_stats", &sysfs_xgmac_stats_attr, 3 }, { "dcbx_tlv", &sysfs_dcbx_tlv_attr, 3 }, { NULL }, diff --git a/drivers/scsi/qla2xxx/qla_dbg.c b/drivers/scsi/qla2xxx/qla_dbg.c index 34dc9a35670b..cd0d94ea7f74 100644 --- a/drivers/scsi/qla2xxx/qla_dbg.c +++ b/drivers/scsi/qla2xxx/qla_dbg.c @@ -14,25 +14,24 @@ * | Module Init and Probe | 0x017f | 0x0146 | * | | | 0x015b-0x0160 | * | | | 0x016e-0x0170 | - * | Mailbox commands | 0x118d | 0x1115-0x1116 | - * | | | 0x111a-0x111b | + * | Mailbox commands | 0x1192 | | + * | | | | * | Device Discovery | 0x2016 | 0x2020-0x2022, | * | | | 0x2011-0x2012, | * | | | 0x2099-0x20a4 | - * | Queue Command and IO tracing | 0x3075 | 0x300b | + * | Queue Command and IO tracing | 0x3074 | 0x300b | * | | | 0x3027-0x3028 | * | | | 0x303d-0x3041 | * | | | 0x302d,0x3033 | * | | | 0x3036,0x3038 | * | | | 0x303a | * | DPC Thread | 0x4023 | 0x4002,0x4013 | - * | Async Events | 0x508a | 0x502b-0x502f | - * | | | 0x5047 | + * | Async Events | 0x5089 | 0x502b-0x502f | * | | | 0x5084,0x5075 | * | | | 0x503d,0x5044 | * | | | 0x507b,0x505f | * | Timer Routines | 0x6012 | | - * | User Space Interactions | 0x70e2 | 0x7018,0x702e | + * | User Space Interactions | 0x70e65 | 0x7018,0x702e | * | | | 0x7020,0x7024 | * | | | 0x7039,0x7045 | * | | | 0x7073-0x7075 | @@ -60,15 +59,11 @@ * | | | 0xb13c-0xb140 | * | | | 0xb149 | * | MultiQ | 0xc00c | | - * | Misc | 0xd300 | 0xd016-0xd017 | - * | | | 0xd021,0xd024 | - * | | | 0xd025,0xd029 | - * | | | 0xd02a,0xd02e | - * | | | 0xd031-0xd0ff | + * | Misc | 0xd301 | 0xd031-0xd0ff | * | | | 0xd101-0xd1fe | * | | | 0xd214-0xd2fe | * | Target Mode | 0xe080 | | - * | Target Mode Management | 0xf096 | 0xf002 | + * | Target Mode Management | 0xf09b | 0xf002 | * | | | 0xf046-0xf049 | * | Target Mode Task Management | 0x1000d | | * ---------------------------------------------------------------------- diff --git a/drivers/scsi/qla2xxx/qla_def.h b/drivers/scsi/qla2xxx/qla_def.h index 388d79088b59..9872f3429e53 100644 --- a/drivers/scsi/qla2xxx/qla_def.h +++ b/drivers/scsi/qla2xxx/qla_def.h @@ -259,7 +259,7 @@ #define LOOP_DOWN_TIME 255 /* 240 */ #define LOOP_DOWN_RESET (LOOP_DOWN_TIME - 30) -#define DEFAULT_OUTSTANDING_COMMANDS 1024 +#define DEFAULT_OUTSTANDING_COMMANDS 4096 #define MIN_OUTSTANDING_COMMANDS 128 /* ISP request and response entry counts (37-65535) */ @@ -267,11 +267,13 @@ #define REQUEST_ENTRY_CNT_2200 2048 /* Number of request entries. */ #define REQUEST_ENTRY_CNT_24XX 2048 /* Number of request entries. */ #define REQUEST_ENTRY_CNT_83XX 8192 /* Number of request entries. */ +#define RESPONSE_ENTRY_CNT_83XX 4096 /* Number of response entries.*/ #define RESPONSE_ENTRY_CNT_2100 64 /* Number of response entries.*/ #define RESPONSE_ENTRY_CNT_2300 512 /* Number of response entries.*/ #define RESPONSE_ENTRY_CNT_MQ 128 /* Number of response entries.*/ #define ATIO_ENTRY_CNT_24XX 4096 /* Number of ATIO entries. */ #define RESPONSE_ENTRY_CNT_FX00 256 /* Number of response entries.*/ +#define EXTENDED_EXCH_ENTRY_CNT 32768 /* Entries for offload case */ struct req_que; struct qla_tgt_sess; @@ -309,6 +311,14 @@ struct srb_cmd { /* To identify if a srb is of T10-CRC type. @sp => srb_t pointer */ #define IS_PROT_IO(sp) (sp->flags & SRB_CRC_CTX_DSD_VALID) +struct els_logo_payload { + uint8_t opcode; + uint8_t rsvd[3]; + uint8_t s_id[3]; + uint8_t rsvd1[1]; + uint8_t wwpn[WWN_SIZE]; +}; + /* * SRB extensions. */ @@ -322,6 +332,15 @@ struct srb_iocb { uint16_t data[2]; } logio; struct { +#define ELS_DCMD_TIMEOUT 20 +#define ELS_DCMD_LOGO 0x5 + uint32_t flags; + uint32_t els_cmd; + struct completion comp; + struct els_logo_payload *els_logo_pyld; + dma_addr_t els_logo_pyld_dma; + } els_logo; + struct { /* * Values for flags field below are as * defined in tsk_mgmt_entry struct @@ -382,7 +401,7 @@ struct srb_iocb { #define SRB_FXIOCB_DCMD 10 #define SRB_FXIOCB_BCMD 11 #define SRB_ABT_CMD 12 - +#define SRB_ELS_DCMD 13 typedef struct srb { atomic_t ref_count; @@ -891,6 +910,7 @@ struct mbx_cmd_32 { #define MBC_DISABLE_VI 0x24 /* Disable VI operation. */ #define MBC_ENABLE_VI 0x25 /* Enable VI operation. */ #define MBC_GET_FIRMWARE_OPTION 0x28 /* Get Firmware Options. */ +#define MBC_GET_MEM_OFFLOAD_CNTRL_STAT 0x34 /* Memory Offload ctrl/Stat*/ #define MBC_SET_FIRMWARE_OPTION 0x38 /* Set Firmware Options. */ #define MBC_LOOP_PORT_BYPASS 0x40 /* Loop Port Bypass. */ #define MBC_LOOP_PORT_ENABLE 0x41 /* Loop Port Enable. */ @@ -2695,11 +2715,16 @@ struct isp_operations { struct scsi_qla_host; + +#define QLA83XX_RSPQ_MSIX_ENTRY_NUMBER 1 /* refer to qla83xx_msix_entries */ + struct qla_msix_entry { int have_irq; uint32_t vector; uint16_t entry; struct rsp_que *rsp; + struct irq_affinity_notify irq_notify; + int cpuid; }; #define WATCH_INTERVAL 1 /* number of seconds */ @@ -2910,12 +2935,15 @@ struct qlt_hw_data { uint32_t num_qfull_cmds_dropped; spinlock_t q_full_lock; uint32_t leak_exchg_thresh_hold; + spinlock_t sess_lock; + int rspq_vector_cpuid; + spinlock_t atio_lock ____cacheline_aligned; }; #define MAX_QFULL_CMDS_ALLOC 8192 #define Q_FULL_THRESH_HOLD_PERCENT 90 #define Q_FULL_THRESH_HOLD(ha) \ - ((ha->fw_xcb_count/100) * Q_FULL_THRESH_HOLD_PERCENT) + ((ha->cur_fw_xcb_count/100) * Q_FULL_THRESH_HOLD_PERCENT) #define LEAK_EXCHG_THRESH_HOLD_PERCENT 75 /* 75 percent */ @@ -2962,10 +2990,12 @@ struct qla_hw_data { uint32_t isp82xx_no_md_cap:1; uint32_t host_shutting_down:1; uint32_t idc_compl_status:1; - uint32_t mr_reset_hdlr_active:1; uint32_t mr_intr_valid:1; + uint32_t fawwpn_enabled:1; + uint32_t exlogins_enabled:1; + uint32_t exchoffld_enabled:1; /* 35 bits */ } flags; @@ -3237,6 +3267,21 @@ struct qla_hw_data { void *async_pd; dma_addr_t async_pd_dma; +#define ENABLE_EXTENDED_LOGIN BIT_7 + + /* Extended Logins */ + void *exlogin_buf; + dma_addr_t exlogin_buf_dma; + int exlogin_size; + +#define ENABLE_EXCHANGE_OFFLD BIT_2 + + /* Exchange Offload */ + void *exchoffld_buf; + dma_addr_t exchoffld_buf_dma; + int exchoffld_size; + int exchoffld_count; + void *swl; /* These are used by mailbox operations. */ @@ -3279,8 +3324,14 @@ struct qla_hw_data { #define RISC_START_ADDRESS_2100 0x1000 #define RISC_START_ADDRESS_2300 0x800 #define RISC_START_ADDRESS_2400 0x100000 - uint16_t fw_xcb_count; - uint16_t fw_iocb_count; + + uint16_t orig_fw_tgt_xcb_count; + uint16_t cur_fw_tgt_xcb_count; + uint16_t orig_fw_xcb_count; + uint16_t cur_fw_xcb_count; + uint16_t orig_fw_iocb_count; + uint16_t cur_fw_iocb_count; + uint16_t fw_max_fcf_count; uint32_t fw_shared_ram_start; uint32_t fw_shared_ram_end; @@ -3323,6 +3374,9 @@ struct qla_hw_data { uint32_t chain_offset; struct dentry *dfs_dir; struct dentry *dfs_fce; + struct dentry *dfs_tgt_counters; + struct dentry *dfs_fw_resource_cnt; + dma_addr_t fce_dma; void *fce; uint32_t fce_bufs; @@ -3480,6 +3534,18 @@ struct qla_hw_data { int allow_cna_fw_dump; }; +struct qla_tgt_counters { + uint64_t qla_core_sbt_cmd; + uint64_t core_qla_que_buf; + uint64_t qla_core_ret_ctio; + uint64_t core_qla_snd_status; + uint64_t qla_core_ret_sta_ctio; + uint64_t core_qla_free_cmd; + uint64_t num_q_full_sent; + uint64_t num_alloc_iocb_failed; + uint64_t num_term_xchg_sent; +}; + /* * Qlogic scsi host structure */ @@ -3595,6 +3661,10 @@ typedef struct scsi_qla_host { atomic_t generation_tick; /* Time when global fcport update has been scheduled */ int total_fcport_update_gen; + /* List of pending LOGOs, protected by tgt_mutex */ + struct list_head logo_list; + /* List of pending PLOGI acks, protected by hw lock */ + struct list_head plogi_ack_list; uint32_t vp_abort_cnt; @@ -3632,6 +3702,7 @@ typedef struct scsi_qla_host { atomic_t vref_count; struct qla8044_reset_template reset_tmplt; + struct qla_tgt_counters tgt_counters; } scsi_qla_host_t; #define SET_VP_IDX 1 diff --git a/drivers/scsi/qla2xxx/qla_dfs.c b/drivers/scsi/qla2xxx/qla_dfs.c index 15cf074ffa3c..cd8b96a4b0dd 100644 --- a/drivers/scsi/qla2xxx/qla_dfs.c +++ b/drivers/scsi/qla2xxx/qla_dfs.c @@ -13,6 +13,85 @@ static struct dentry *qla2x00_dfs_root; static atomic_t qla2x00_dfs_root_count; static int +qla_dfs_fw_resource_cnt_show(struct seq_file *s, void *unused) +{ + struct scsi_qla_host *vha = s->private; + struct qla_hw_data *ha = vha->hw; + + seq_puts(s, "FW Resource count\n\n"); + seq_printf(s, "Original TGT exchg count[%d]\n", + ha->orig_fw_tgt_xcb_count); + seq_printf(s, "current TGT exchg count[%d]\n", + ha->cur_fw_tgt_xcb_count); + seq_printf(s, "original Initiator Exchange count[%d]\n", + ha->orig_fw_xcb_count); + seq_printf(s, "Current Initiator Exchange count[%d]\n", + ha->cur_fw_xcb_count); + seq_printf(s, "Original IOCB count[%d]\n", ha->orig_fw_iocb_count); + seq_printf(s, "Current IOCB count[%d]\n", ha->cur_fw_iocb_count); + seq_printf(s, "MAX VP count[%d]\n", ha->max_npiv_vports); + seq_printf(s, "MAX FCF count[%d]\n", ha->fw_max_fcf_count); + + return 0; +} + +static int +qla_dfs_fw_resource_cnt_open(struct inode *inode, struct file *file) +{ + struct scsi_qla_host *vha = inode->i_private; + return single_open(file, qla_dfs_fw_resource_cnt_show, vha); +} + +static const struct file_operations dfs_fw_resource_cnt_ops = { + .open = qla_dfs_fw_resource_cnt_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int +qla_dfs_tgt_counters_show(struct seq_file *s, void *unused) +{ + struct scsi_qla_host *vha = s->private; + + seq_puts(s, "Target Counters\n"); + seq_printf(s, "qla_core_sbt_cmd = %lld\n", + vha->tgt_counters.qla_core_sbt_cmd); + seq_printf(s, "qla_core_ret_sta_ctio = %lld\n", + vha->tgt_counters.qla_core_ret_sta_ctio); + seq_printf(s, "qla_core_ret_ctio = %lld\n", + vha->tgt_counters.qla_core_ret_ctio); + seq_printf(s, "core_qla_que_buf = %lld\n", + vha->tgt_counters.core_qla_que_buf); + seq_printf(s, "core_qla_snd_status = %lld\n", + vha->tgt_counters.core_qla_snd_status); + seq_printf(s, "core_qla_free_cmd = %lld\n", + vha->tgt_counters.core_qla_free_cmd); + seq_printf(s, "num alloc iocb failed = %lld\n", + vha->tgt_counters.num_alloc_iocb_failed); + seq_printf(s, "num term exchange sent = %lld\n", + vha->tgt_counters.num_term_xchg_sent); + seq_printf(s, "num Q full sent = %lld\n", + vha->tgt_counters.num_q_full_sent); + + return 0; +} + +static int +qla_dfs_tgt_counters_open(struct inode *inode, struct file *file) +{ + struct scsi_qla_host *vha = inode->i_private; + return single_open(file, qla_dfs_tgt_counters_show, vha); +} + +static const struct file_operations dfs_tgt_counters_ops = { + .open = qla_dfs_tgt_counters_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int qla2x00_dfs_fce_show(struct seq_file *s, void *unused) { scsi_qla_host_t *vha = s->private; @@ -146,6 +225,22 @@ create_dir: atomic_inc(&qla2x00_dfs_root_count); create_nodes: + ha->dfs_fw_resource_cnt = debugfs_create_file("fw_resource_count", + S_IRUSR, ha->dfs_dir, vha, &dfs_fw_resource_cnt_ops); + if (!ha->dfs_fw_resource_cnt) { + ql_log(ql_log_warn, vha, 0x00fd, + "Unable to create debugFS fw_resource_count node.\n"); + goto out; + } + + ha->dfs_tgt_counters = debugfs_create_file("tgt_counters", S_IRUSR, + ha->dfs_dir, vha, &dfs_tgt_counters_ops); + if (!ha->dfs_tgt_counters) { + ql_log(ql_log_warn, vha, 0xd301, + "Unable to create debugFS tgt_counters node.\n"); + goto out; + } + ha->dfs_fce = debugfs_create_file("fce", S_IRUSR, ha->dfs_dir, vha, &dfs_fce_ops); if (!ha->dfs_fce) { @@ -161,6 +256,17 @@ int qla2x00_dfs_remove(scsi_qla_host_t *vha) { struct qla_hw_data *ha = vha->hw; + + if (ha->dfs_fw_resource_cnt) { + debugfs_remove(ha->dfs_fw_resource_cnt); + ha->dfs_fw_resource_cnt = NULL; + } + + if (ha->dfs_tgt_counters) { + debugfs_remove(ha->dfs_tgt_counters); + ha->dfs_tgt_counters = NULL; + } + if (ha->dfs_fce) { debugfs_remove(ha->dfs_fce); ha->dfs_fce = NULL; diff --git a/drivers/scsi/qla2xxx/qla_gbl.h b/drivers/scsi/qla2xxx/qla_gbl.h index 7686bfe9a4a9..0103e468e357 100644 --- a/drivers/scsi/qla2xxx/qla_gbl.h +++ b/drivers/scsi/qla2xxx/qla_gbl.h @@ -44,6 +44,8 @@ extern int qla2x00_find_new_loop_id(scsi_qla_host_t *, fc_port_t *); extern int qla2x00_fabric_login(scsi_qla_host_t *, fc_port_t *, uint16_t *); extern int qla2x00_local_device_login(scsi_qla_host_t *, fc_port_t *); +extern int qla24xx_els_dcmd_iocb(scsi_qla_host_t *, int, port_id_t); + extern void qla2x00_update_fcports(scsi_qla_host_t *); extern int qla2x00_abort_isp(scsi_qla_host_t *); @@ -117,6 +119,8 @@ extern int ql2xdontresethba; extern uint64_t ql2xmaxlun; extern int ql2xmdcapmask; extern int ql2xmdenable; +extern int ql2xexlogins; +extern int ql2xexchoffld; extern int qla2x00_loop_reset(scsi_qla_host_t *); extern void qla2x00_abort_all_cmds(scsi_qla_host_t *, int); @@ -135,6 +139,10 @@ extern int qla2x00_post_async_adisc_work(struct scsi_qla_host *, fc_port_t *, uint16_t *); extern int qla2x00_post_async_adisc_done_work(struct scsi_qla_host *, fc_port_t *, uint16_t *); +extern int qla2x00_set_exlogins_buffer(struct scsi_qla_host *); +extern void qla2x00_free_exlogin_buffer(struct qla_hw_data *); +extern int qla2x00_set_exchoffld_buffer(struct scsi_qla_host *); +extern void qla2x00_free_exchoffld_buffer(struct qla_hw_data *); extern int qla81xx_restart_mpi_firmware(scsi_qla_host_t *); @@ -323,8 +331,7 @@ extern int qla2x00_get_id_list(scsi_qla_host_t *, void *, dma_addr_t, uint16_t *); extern int -qla2x00_get_resource_cnts(scsi_qla_host_t *, uint16_t *, uint16_t *, - uint16_t *, uint16_t *, uint16_t *, uint16_t *); +qla2x00_get_resource_cnts(scsi_qla_host_t *); extern int qla2x00_get_fcal_position_map(scsi_qla_host_t *ha, char *pos_map); @@ -766,4 +773,11 @@ extern int qla8044_abort_isp(scsi_qla_host_t *); extern int qla8044_check_fw_alive(struct scsi_qla_host *); extern void qlt_host_reset_handler(struct qla_hw_data *ha); +extern int qla_get_exlogin_status(scsi_qla_host_t *, uint16_t *, + uint16_t *); +extern int qla_set_exlogin_mem_cfg(scsi_qla_host_t *vha, dma_addr_t phys_addr); +extern int qla_get_exchoffld_status(scsi_qla_host_t *, uint16_t *, uint16_t *); +extern int qla_set_exchoffld_mem_cfg(scsi_qla_host_t *, dma_addr_t); +extern void qlt_handle_abts_recv(struct scsi_qla_host *, response_t *); + #endif /* _QLA_GBL_H */ diff --git a/drivers/scsi/qla2xxx/qla_init.c b/drivers/scsi/qla2xxx/qla_init.c index 16a1935cc9c1..52a87657c7dd 100644 --- a/drivers/scsi/qla2xxx/qla_init.c +++ b/drivers/scsi/qla2xxx/qla_init.c @@ -1766,10 +1766,10 @@ qla2x00_alloc_outstanding_cmds(struct qla_hw_data *ha, struct req_que *req) (ql2xmultique_tag || ql2xmaxqueues > 1))) req->num_outstanding_cmds = DEFAULT_OUTSTANDING_COMMANDS; else { - if (ha->fw_xcb_count <= ha->fw_iocb_count) - req->num_outstanding_cmds = ha->fw_xcb_count; + if (ha->cur_fw_xcb_count <= ha->cur_fw_iocb_count) + req->num_outstanding_cmds = ha->cur_fw_xcb_count; else - req->num_outstanding_cmds = ha->fw_iocb_count; + req->num_outstanding_cmds = ha->cur_fw_iocb_count; } req->outstanding_cmds = kzalloc(sizeof(srb_t *) * @@ -1843,9 +1843,23 @@ qla2x00_setup_chip(scsi_qla_host_t *vha) ql_dbg(ql_dbg_init, vha, 0x00ca, "Starting firmware.\n"); + if (ql2xexlogins) + ha->flags.exlogins_enabled = 1; + + if (ql2xexchoffld) + ha->flags.exchoffld_enabled = 1; + rval = qla2x00_execute_fw(vha, srisc_address); /* Retrieve firmware information. */ if (rval == QLA_SUCCESS) { + rval = qla2x00_set_exlogins_buffer(vha); + if (rval != QLA_SUCCESS) + goto failed; + + rval = qla2x00_set_exchoffld_buffer(vha); + if (rval != QLA_SUCCESS) + goto failed; + enable_82xx_npiv: fw_major_version = ha->fw_major_version; if (IS_P3P_TYPE(ha)) @@ -1864,9 +1878,7 @@ enable_82xx_npiv: ha->max_npiv_vports = MIN_MULTI_ID_FABRIC - 1; } - qla2x00_get_resource_cnts(vha, NULL, - &ha->fw_xcb_count, NULL, &ha->fw_iocb_count, - &ha->max_npiv_vports, NULL); + qla2x00_get_resource_cnts(vha); /* * Allocate the array of outstanding commands @@ -2248,7 +2260,7 @@ qla2x00_init_rings(scsi_qla_host_t *vha) if (IS_FWI2_CAPABLE(ha)) { mid_init_cb->options = cpu_to_le16(BIT_1); mid_init_cb->init_cb.execution_throttle = - cpu_to_le16(ha->fw_xcb_count); + cpu_to_le16(ha->cur_fw_xcb_count); /* D-Port Status */ if (IS_DPORT_CAPABLE(ha)) mid_init_cb->init_cb.firmware_options_1 |= @@ -3053,6 +3065,26 @@ qla2x00_configure_loop(scsi_qla_host_t *vha) atomic_set(&vha->loop_state, LOOP_READY); ql_dbg(ql_dbg_disc, vha, 0x2069, "LOOP READY.\n"); + + /* + * Process any ATIO queue entries that came in + * while we weren't online. + */ + if (qla_tgt_mode_enabled(vha)) { + if (IS_QLA27XX(ha) || IS_QLA83XX(ha)) { + spin_lock_irqsave(&ha->tgt.atio_lock, + flags); + qlt_24xx_process_atio_queue(vha, 0); + spin_unlock_irqrestore( + &ha->tgt.atio_lock, flags); + } else { + spin_lock_irqsave(&ha->hardware_lock, + flags); + qlt_24xx_process_atio_queue(vha, 1); + spin_unlock_irqrestore( + &ha->hardware_lock, flags); + } + } } } @@ -4907,7 +4939,6 @@ qla2x00_restart_isp(scsi_qla_host_t *vha) struct qla_hw_data *ha = vha->hw; struct req_que *req = ha->req_q_map[0]; struct rsp_que *rsp = ha->rsp_q_map[0]; - unsigned long flags; /* If firmware needs to be loaded */ if (qla2x00_isp_firmware(vha)) { @@ -4929,17 +4960,6 @@ qla2x00_restart_isp(scsi_qla_host_t *vha) /* Issue a marker after FW becomes ready. */ qla2x00_marker(vha, req, rsp, 0, 0, MK_SYNC_ALL); - vha->flags.online = 1; - - /* - * Process any ATIO queue entries that came in - * while we weren't online. - */ - spin_lock_irqsave(&ha->hardware_lock, flags); - if (qla_tgt_mode_enabled(vha)) - qlt_24xx_process_atio_queue(vha); - spin_unlock_irqrestore(&ha->hardware_lock, flags); - set_bit(LOOP_RESYNC_NEEDED, &vha->dpc_flags); } diff --git a/drivers/scsi/qla2xxx/qla_inline.h b/drivers/scsi/qla2xxx/qla_inline.h index fee9eb7c8a60..a6b7f1588aa4 100644 --- a/drivers/scsi/qla2xxx/qla_inline.h +++ b/drivers/scsi/qla2xxx/qla_inline.h @@ -258,6 +258,8 @@ qla2x00_init_timer(srb_t *sp, unsigned long tmo) if ((IS_QLAFX00(sp->fcport->vha->hw)) && (sp->type == SRB_FXIOCB_DCMD)) init_completion(&sp->u.iocb_cmd.u.fxiocb.fxiocb_comp); + if (sp->type == SRB_ELS_DCMD) + init_completion(&sp->u.iocb_cmd.u.els_logo.comp); } static inline int diff --git a/drivers/scsi/qla2xxx/qla_iocb.c b/drivers/scsi/qla2xxx/qla_iocb.c index c49df34e9b35..b41265a75ed5 100644 --- a/drivers/scsi/qla2xxx/qla_iocb.c +++ b/drivers/scsi/qla2xxx/qla_iocb.c @@ -1868,6 +1868,7 @@ skip_cmd_array: } queuing_error: + vha->tgt_counters.num_alloc_iocb_failed++; return pkt; } @@ -2010,6 +2011,190 @@ qla24xx_tm_iocb(srb_t *sp, struct tsk_mgmt_entry *tsk) } static void +qla2x00_els_dcmd_sp_free(void *ptr, void *data) +{ + struct scsi_qla_host *vha = (scsi_qla_host_t *)ptr; + struct qla_hw_data *ha = vha->hw; + srb_t *sp = (srb_t *)data; + struct srb_iocb *elsio = &sp->u.iocb_cmd; + + kfree(sp->fcport); + + if (elsio->u.els_logo.els_logo_pyld) + dma_free_coherent(&ha->pdev->dev, DMA_POOL_SIZE, + elsio->u.els_logo.els_logo_pyld, + elsio->u.els_logo.els_logo_pyld_dma); + + del_timer(&elsio->timer); + qla2x00_rel_sp(vha, sp); +} + +static void +qla2x00_els_dcmd_iocb_timeout(void *data) +{ + srb_t *sp = (srb_t *)data; + struct srb_iocb *lio = &sp->u.iocb_cmd; + fc_port_t *fcport = sp->fcport; + struct scsi_qla_host *vha = fcport->vha; + struct qla_hw_data *ha = vha->hw; + unsigned long flags = 0; + + ql_dbg(ql_dbg_io, vha, 0x3069, + "%s Timeout, hdl=%x, portid=%02x%02x%02x\n", + sp->name, sp->handle, fcport->d_id.b.domain, fcport->d_id.b.area, + fcport->d_id.b.al_pa); + + /* Abort the exchange */ + spin_lock_irqsave(&ha->hardware_lock, flags); + if (ha->isp_ops->abort_command(sp)) { + ql_dbg(ql_dbg_io, vha, 0x3070, + "mbx abort_command failed.\n"); + } else { + ql_dbg(ql_dbg_io, vha, 0x3071, + "mbx abort_command success.\n"); + } + spin_unlock_irqrestore(&ha->hardware_lock, flags); + + complete(&lio->u.els_logo.comp); +} + +static void +qla2x00_els_dcmd_sp_done(void *data, void *ptr, int res) +{ + srb_t *sp = (srb_t *)ptr; + fc_port_t *fcport = sp->fcport; + struct srb_iocb *lio = &sp->u.iocb_cmd; + struct scsi_qla_host *vha = fcport->vha; + + ql_dbg(ql_dbg_io, vha, 0x3072, + "%s hdl=%x, portid=%02x%02x%02x done\n", + sp->name, sp->handle, fcport->d_id.b.domain, + fcport->d_id.b.area, fcport->d_id.b.al_pa); + + complete(&lio->u.els_logo.comp); +} + +int +qla24xx_els_dcmd_iocb(scsi_qla_host_t *vha, int els_opcode, + port_id_t remote_did) +{ + srb_t *sp; + fc_port_t *fcport = NULL; + struct srb_iocb *elsio = NULL; + struct qla_hw_data *ha = vha->hw; + struct els_logo_payload logo_pyld; + int rval = QLA_SUCCESS; + + fcport = qla2x00_alloc_fcport(vha, GFP_KERNEL); + if (!fcport) { + ql_log(ql_log_info, vha, 0x70e5, "fcport allocation failed\n"); + return -ENOMEM; + } + + /* Alloc SRB structure */ + sp = qla2x00_get_sp(vha, fcport, GFP_KERNEL); + if (!sp) { + kfree(fcport); + ql_log(ql_log_info, vha, 0x70e6, + "SRB allocation failed\n"); + return -ENOMEM; + } + + elsio = &sp->u.iocb_cmd; + fcport->loop_id = 0xFFFF; + fcport->d_id.b.domain = remote_did.b.domain; + fcport->d_id.b.area = remote_did.b.area; + fcport->d_id.b.al_pa = remote_did.b.al_pa; + + ql_dbg(ql_dbg_io, vha, 0x3073, "portid=%02x%02x%02x done\n", + fcport->d_id.b.domain, fcport->d_id.b.area, fcport->d_id.b.al_pa); + + sp->type = SRB_ELS_DCMD; + sp->name = "ELS_DCMD"; + sp->fcport = fcport; + qla2x00_init_timer(sp, ELS_DCMD_TIMEOUT); + elsio->timeout = qla2x00_els_dcmd_iocb_timeout; + sp->done = qla2x00_els_dcmd_sp_done; + sp->free = qla2x00_els_dcmd_sp_free; + + elsio->u.els_logo.els_logo_pyld = dma_alloc_coherent(&ha->pdev->dev, + DMA_POOL_SIZE, &elsio->u.els_logo.els_logo_pyld_dma, + GFP_KERNEL); + + if (!elsio->u.els_logo.els_logo_pyld) { + sp->free(vha, sp); + return QLA_FUNCTION_FAILED; + } + + memset(&logo_pyld, 0, sizeof(struct els_logo_payload)); + + elsio->u.els_logo.els_cmd = els_opcode; + logo_pyld.opcode = els_opcode; + logo_pyld.s_id[0] = vha->d_id.b.al_pa; + logo_pyld.s_id[1] = vha->d_id.b.area; + logo_pyld.s_id[2] = vha->d_id.b.domain; + host_to_fcp_swap(logo_pyld.s_id, sizeof(uint32_t)); + memcpy(&logo_pyld.wwpn, vha->port_name, WWN_SIZE); + + memcpy(elsio->u.els_logo.els_logo_pyld, &logo_pyld, + sizeof(struct els_logo_payload)); + + rval = qla2x00_start_sp(sp); + if (rval != QLA_SUCCESS) { + sp->free(vha, sp); + return QLA_FUNCTION_FAILED; + } + + ql_dbg(ql_dbg_io, vha, 0x3074, + "%s LOGO sent, hdl=%x, loopid=%x, portid=%02x%02x%02x.\n", + sp->name, sp->handle, fcport->loop_id, fcport->d_id.b.domain, + fcport->d_id.b.area, fcport->d_id.b.al_pa); + + wait_for_completion(&elsio->u.els_logo.comp); + + sp->free(vha, sp); + return rval; +} + +static void +qla24xx_els_logo_iocb(srb_t *sp, struct els_entry_24xx *els_iocb) +{ + scsi_qla_host_t *vha = sp->fcport->vha; + struct srb_iocb *elsio = &sp->u.iocb_cmd; + + els_iocb->entry_type = ELS_IOCB_TYPE; + els_iocb->entry_count = 1; + els_iocb->sys_define = 0; + els_iocb->entry_status = 0; + els_iocb->handle = sp->handle; + els_iocb->nport_handle = cpu_to_le16(sp->fcport->loop_id); + els_iocb->tx_dsd_count = 1; + els_iocb->vp_index = vha->vp_idx; + els_iocb->sof_type = EST_SOFI3; + els_iocb->rx_dsd_count = 0; + els_iocb->opcode = elsio->u.els_logo.els_cmd; + + els_iocb->port_id[0] = sp->fcport->d_id.b.al_pa; + els_iocb->port_id[1] = sp->fcport->d_id.b.area; + els_iocb->port_id[2] = sp->fcport->d_id.b.domain; + els_iocb->control_flags = 0; + + els_iocb->tx_byte_count = sizeof(struct els_logo_payload); + els_iocb->tx_address[0] = + cpu_to_le32(LSD(elsio->u.els_logo.els_logo_pyld_dma)); + els_iocb->tx_address[1] = + cpu_to_le32(MSD(elsio->u.els_logo.els_logo_pyld_dma)); + els_iocb->tx_len = cpu_to_le32(sizeof(struct els_logo_payload)); + + els_iocb->rx_byte_count = 0; + els_iocb->rx_address[0] = 0; + els_iocb->rx_address[1] = 0; + els_iocb->rx_len = 0; + + sp->fcport->vha->qla_stats.control_requests++; +} + +static void qla24xx_els_iocb(srb_t *sp, struct els_entry_24xx *els_iocb) { struct fc_bsg_job *bsg_job = sp->u.bsg_job; @@ -2623,6 +2808,9 @@ qla2x00_start_sp(srb_t *sp) qlafx00_abort_iocb(sp, pkt) : qla24xx_abort_iocb(sp, pkt); break; + case SRB_ELS_DCMD: + qla24xx_els_logo_iocb(sp, pkt); + break; default: break; } diff --git a/drivers/scsi/qla2xxx/qla_isr.c b/drivers/scsi/qla2xxx/qla_isr.c index ccf6a7f99024..d4d65eb0e9b4 100644 --- a/drivers/scsi/qla2xxx/qla_isr.c +++ b/drivers/scsi/qla2xxx/qla_isr.c @@ -18,6 +18,10 @@ static void qla2x00_status_entry(scsi_qla_host_t *, struct rsp_que *, void *); static void qla2x00_status_cont_entry(struct rsp_que *, sts_cont_entry_t *); static void qla2x00_error_entry(scsi_qla_host_t *, struct rsp_que *, sts_entry_t *); +static void qla_irq_affinity_notify(struct irq_affinity_notify *, + const cpumask_t *); +static void qla_irq_affinity_release(struct kref *); + /** * qla2100_intr_handler() - Process interrupts for the ISP2100 and ISP2200. @@ -1418,6 +1422,12 @@ qla24xx_els_ct_entry(scsi_qla_host_t *vha, struct req_que *req, case SRB_CT_CMD: type = "ct pass-through"; break; + case SRB_ELS_DCMD: + type = "Driver ELS logo"; + ql_dbg(ql_dbg_user, vha, 0x5047, + "Completing %s: (%p) type=%d.\n", type, sp, sp->type); + sp->done(vha, sp, 0); + return; default: ql_dbg(ql_dbg_user, vha, 0x503e, "Unrecognized SRB: (%p) type=%d.\n", sp, sp->type); @@ -2542,6 +2552,14 @@ void qla24xx_process_response_queue(struct scsi_qla_host *vha, if (!vha->flags.online) return; + if (rsp->msix->cpuid != smp_processor_id()) { + /* if kernel does not notify qla of IRQ's CPU change, + * then set it here. + */ + rsp->msix->cpuid = smp_processor_id(); + ha->tgt.rspq_vector_cpuid = rsp->msix->cpuid; + } + while (rsp->ring_ptr->signature != RESPONSE_PROCESSED) { pkt = (struct sts_entry_24xx *)rsp->ring_ptr; @@ -2587,8 +2605,14 @@ process_err: qla24xx_els_ct_entry(vha, rsp->req, pkt, ELS_IOCB_TYPE); break; case ABTS_RECV_24XX: - /* ensure that the ATIO queue is empty */ - qlt_24xx_process_atio_queue(vha); + if (IS_QLA83XX(ha) || IS_QLA27XX(ha)) { + /* ensure that the ATIO queue is empty */ + qlt_handle_abts_recv(vha, (response_t *)pkt); + break; + } else { + /* drop through */ + qlt_24xx_process_atio_queue(vha, 1); + } case ABTS_RESP_24XX: case CTIO_TYPE7: case NOTIFY_ACK_TYPE: @@ -2755,13 +2779,22 @@ qla24xx_intr_handler(int irq, void *dev_id) case INTR_RSP_QUE_UPDATE_83XX: qla24xx_process_response_queue(vha, rsp); break; - case INTR_ATIO_QUE_UPDATE: - qlt_24xx_process_atio_queue(vha); + case INTR_ATIO_QUE_UPDATE:{ + unsigned long flags2; + spin_lock_irqsave(&ha->tgt.atio_lock, flags2); + qlt_24xx_process_atio_queue(vha, 1); + spin_unlock_irqrestore(&ha->tgt.atio_lock, flags2); break; - case INTR_ATIO_RSP_QUE_UPDATE: - qlt_24xx_process_atio_queue(vha); + } + case INTR_ATIO_RSP_QUE_UPDATE: { + unsigned long flags2; + spin_lock_irqsave(&ha->tgt.atio_lock, flags2); + qlt_24xx_process_atio_queue(vha, 1); + spin_unlock_irqrestore(&ha->tgt.atio_lock, flags2); + qla24xx_process_response_queue(vha, rsp); break; + } default: ql_dbg(ql_dbg_async, vha, 0x504f, "Unrecognized interrupt type (%d).\n", stat * 0xff); @@ -2920,13 +2953,22 @@ qla24xx_msix_default(int irq, void *dev_id) case INTR_RSP_QUE_UPDATE_83XX: qla24xx_process_response_queue(vha, rsp); break; - case INTR_ATIO_QUE_UPDATE: - qlt_24xx_process_atio_queue(vha); + case INTR_ATIO_QUE_UPDATE:{ + unsigned long flags2; + spin_lock_irqsave(&ha->tgt.atio_lock, flags2); + qlt_24xx_process_atio_queue(vha, 1); + spin_unlock_irqrestore(&ha->tgt.atio_lock, flags2); break; - case INTR_ATIO_RSP_QUE_UPDATE: - qlt_24xx_process_atio_queue(vha); + } + case INTR_ATIO_RSP_QUE_UPDATE: { + unsigned long flags2; + spin_lock_irqsave(&ha->tgt.atio_lock, flags2); + qlt_24xx_process_atio_queue(vha, 1); + spin_unlock_irqrestore(&ha->tgt.atio_lock, flags2); + qla24xx_process_response_queue(vha, rsp); break; + } default: ql_dbg(ql_dbg_async, vha, 0x5051, "Unrecognized interrupt type (%d).\n", stat & 0xff); @@ -2973,8 +3015,11 @@ qla24xx_disable_msix(struct qla_hw_data *ha) for (i = 0; i < ha->msix_count; i++) { qentry = &ha->msix_entries[i]; - if (qentry->have_irq) + if (qentry->have_irq) { + /* un-register irq cpu affinity notification */ + irq_set_affinity_notifier(qentry->vector, NULL); free_irq(qentry->vector, qentry->rsp); + } } pci_disable_msix(ha->pdev); kfree(ha->msix_entries); @@ -3037,6 +3082,9 @@ qla24xx_enable_msix(struct qla_hw_data *ha, struct rsp_que *rsp) qentry->entry = entries[i].entry; qentry->have_irq = 0; qentry->rsp = NULL; + qentry->irq_notify.notify = qla_irq_affinity_notify; + qentry->irq_notify.release = qla_irq_affinity_release; + qentry->cpuid = -1; } /* Enable MSI-X vectors for the base queue */ @@ -3055,6 +3103,18 @@ qla24xx_enable_msix(struct qla_hw_data *ha, struct rsp_que *rsp) qentry->have_irq = 1; qentry->rsp = rsp; rsp->msix = qentry; + + /* Register for CPU affinity notification. */ + irq_set_affinity_notifier(qentry->vector, &qentry->irq_notify); + + /* Schedule work (ie. trigger a notification) to read cpu + * mask for this specific irq. + * kref_get is required because + * irq_affinity_notify() will do + * kref_put(). + */ + kref_get(&qentry->irq_notify.kref); + schedule_work(&qentry->irq_notify.work); } /* @@ -3234,3 +3294,47 @@ int qla25xx_request_irq(struct rsp_que *rsp) msix->rsp = rsp; return ret; } + + +/* irq_set_affinity/irqbalance will trigger notification of cpu mask update */ +static void qla_irq_affinity_notify(struct irq_affinity_notify *notify, + const cpumask_t *mask) +{ + struct qla_msix_entry *e = + container_of(notify, struct qla_msix_entry, irq_notify); + struct qla_hw_data *ha; + struct scsi_qla_host *base_vha; + + /* user is recommended to set mask to just 1 cpu */ + e->cpuid = cpumask_first(mask); + + ha = e->rsp->hw; + base_vha = pci_get_drvdata(ha->pdev); + + ql_dbg(ql_dbg_init, base_vha, 0xffff, + "%s: host %ld : vector %d cpu %d \n", __func__, + base_vha->host_no, e->vector, e->cpuid); + + if (e->have_irq) { + if ((IS_QLA83XX(ha) || IS_QLA27XX(ha)) && + (e->entry == QLA83XX_RSPQ_MSIX_ENTRY_NUMBER)) { + ha->tgt.rspq_vector_cpuid = e->cpuid; + ql_dbg(ql_dbg_init, base_vha, 0xffff, + "%s: host%ld: rspq vector %d cpu %d runtime change\n", + __func__, base_vha->host_no, e->vector, e->cpuid); + } + } +} + +static void qla_irq_affinity_release(struct kref *ref) +{ + struct irq_affinity_notify *notify = + container_of(ref, struct irq_affinity_notify, kref); + struct qla_msix_entry *e = + container_of(notify, struct qla_msix_entry, irq_notify); + struct scsi_qla_host *base_vha = pci_get_drvdata(e->rsp->hw->pdev); + + ql_dbg(ql_dbg_init, base_vha, 0xffff, + "%s: host%ld: vector %d cpu %d \n", __func__, + base_vha->host_no, e->vector, e->cpuid); +} diff --git a/drivers/scsi/qla2xxx/qla_mbx.c b/drivers/scsi/qla2xxx/qla_mbx.c index cb11e04be568..87e6758302f6 100644 --- a/drivers/scsi/qla2xxx/qla_mbx.c +++ b/drivers/scsi/qla2xxx/qla_mbx.c @@ -489,6 +489,13 @@ qla2x00_execute_fw(scsi_qla_host_t *vha, uint32_t risc_addr) EXTENDED_BB_CREDITS); } else mcp->mb[4] = 0; + + if (ha->flags.exlogins_enabled) + mcp->mb[4] |= ENABLE_EXTENDED_LOGIN; + + if (ha->flags.exchoffld_enabled) + mcp->mb[4] |= ENABLE_EXCHANGE_OFFLD; + mcp->out_mb |= MBX_4|MBX_3|MBX_2|MBX_1; mcp->in_mb |= MBX_1; } else { @@ -521,6 +528,226 @@ qla2x00_execute_fw(scsi_qla_host_t *vha, uint32_t risc_addr) } /* + * qla_get_exlogin_status + * Get extended login status + * uses the memory offload control/status Mailbox + * + * Input: + * ha: adapter state pointer. + * fwopt: firmware options + * + * Returns: + * qla2x00 local function status + * + * Context: + * Kernel context. + */ +#define FETCH_XLOGINS_STAT 0x8 +int +qla_get_exlogin_status(scsi_qla_host_t *vha, uint16_t *buf_sz, + uint16_t *ex_logins_cnt) +{ + int rval; + mbx_cmd_t mc; + mbx_cmd_t *mcp = &mc; + + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x118f, + "Entered %s\n", __func__); + + memset(mcp->mb, 0 , sizeof(mcp->mb)); + mcp->mb[0] = MBC_GET_MEM_OFFLOAD_CNTRL_STAT; + mcp->mb[1] = FETCH_XLOGINS_STAT; + mcp->out_mb = MBX_1|MBX_0; + mcp->in_mb = MBX_10|MBX_4|MBX_0; + mcp->tov = MBX_TOV_SECONDS; + mcp->flags = 0; + + rval = qla2x00_mailbox_command(vha, mcp); + if (rval != QLA_SUCCESS) { + ql_dbg(ql_dbg_mbx, vha, 0x1115, "Failed=%x.\n", rval); + } else { + *buf_sz = mcp->mb[4]; + *ex_logins_cnt = mcp->mb[10]; + + ql_log(ql_log_info, vha, 0x1190, + "buffer size 0x%x, exchange login count=%d\n", + mcp->mb[4], mcp->mb[10]); + + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1116, + "Done %s.\n", __func__); + } + + return rval; +} + +/* + * qla_set_exlogin_mem_cfg + * set extended login memory configuration + * Mbx needs to be issues before init_cb is set + * + * Input: + * ha: adapter state pointer. + * buffer: buffer pointer + * phys_addr: physical address of buffer + * size: size of buffer + * TARGET_QUEUE_LOCK must be released + * ADAPTER_STATE_LOCK must be release + * + * Returns: + * qla2x00 local funxtion status code. + * + * Context: + * Kernel context. + */ +#define CONFIG_XLOGINS_MEM 0x3 +int +qla_set_exlogin_mem_cfg(scsi_qla_host_t *vha, dma_addr_t phys_addr) +{ + int rval; + mbx_cmd_t mc; + mbx_cmd_t *mcp = &mc; + struct qla_hw_data *ha = vha->hw; + int configured_count; + + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x111a, + "Entered %s.\n", __func__); + + memset(mcp->mb, 0 , sizeof(mcp->mb)); + mcp->mb[0] = MBC_GET_MEM_OFFLOAD_CNTRL_STAT; + mcp->mb[1] = CONFIG_XLOGINS_MEM; + mcp->mb[2] = MSW(phys_addr); + mcp->mb[3] = LSW(phys_addr); + mcp->mb[6] = MSW(MSD(phys_addr)); + mcp->mb[7] = LSW(MSD(phys_addr)); + mcp->mb[8] = MSW(ha->exlogin_size); + mcp->mb[9] = LSW(ha->exlogin_size); + mcp->out_mb = MBX_9|MBX_8|MBX_7|MBX_6|MBX_3|MBX_2|MBX_1|MBX_0; + mcp->in_mb = MBX_11|MBX_0; + mcp->tov = MBX_TOV_SECONDS; + mcp->flags = 0; + rval = qla2x00_mailbox_command(vha, mcp); + if (rval != QLA_SUCCESS) { + /*EMPTY*/ + ql_dbg(ql_dbg_mbx, vha, 0x111b, "Failed=%x.\n", rval); + } else { + configured_count = mcp->mb[11]; + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x118c, + "Done %s.\n", __func__); + } + + return rval; +} + +/* + * qla_get_exchoffld_status + * Get exchange offload status + * uses the memory offload control/status Mailbox + * + * Input: + * ha: adapter state pointer. + * fwopt: firmware options + * + * Returns: + * qla2x00 local function status + * + * Context: + * Kernel context. + */ +#define FETCH_XCHOFFLD_STAT 0x2 +int +qla_get_exchoffld_status(scsi_qla_host_t *vha, uint16_t *buf_sz, + uint16_t *ex_logins_cnt) +{ + int rval; + mbx_cmd_t mc; + mbx_cmd_t *mcp = &mc; + + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1019, + "Entered %s\n", __func__); + + memset(mcp->mb, 0 , sizeof(mcp->mb)); + mcp->mb[0] = MBC_GET_MEM_OFFLOAD_CNTRL_STAT; + mcp->mb[1] = FETCH_XCHOFFLD_STAT; + mcp->out_mb = MBX_1|MBX_0; + mcp->in_mb = MBX_10|MBX_4|MBX_0; + mcp->tov = MBX_TOV_SECONDS; + mcp->flags = 0; + + rval = qla2x00_mailbox_command(vha, mcp); + if (rval != QLA_SUCCESS) { + ql_dbg(ql_dbg_mbx, vha, 0x1155, "Failed=%x.\n", rval); + } else { + *buf_sz = mcp->mb[4]; + *ex_logins_cnt = mcp->mb[10]; + + ql_log(ql_log_info, vha, 0x118e, + "buffer size 0x%x, exchange offload count=%d\n", + mcp->mb[4], mcp->mb[10]); + + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1156, + "Done %s.\n", __func__); + } + + return rval; +} + +/* + * qla_set_exchoffld_mem_cfg + * Set exchange offload memory configuration + * Mbx needs to be issues before init_cb is set + * + * Input: + * ha: adapter state pointer. + * buffer: buffer pointer + * phys_addr: physical address of buffer + * size: size of buffer + * TARGET_QUEUE_LOCK must be released + * ADAPTER_STATE_LOCK must be release + * + * Returns: + * qla2x00 local funxtion status code. + * + * Context: + * Kernel context. + */ +#define CONFIG_XCHOFFLD_MEM 0x3 +int +qla_set_exchoffld_mem_cfg(scsi_qla_host_t *vha, dma_addr_t phys_addr) +{ + int rval; + mbx_cmd_t mc; + mbx_cmd_t *mcp = &mc; + struct qla_hw_data *ha = vha->hw; + + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1157, + "Entered %s.\n", __func__); + + memset(mcp->mb, 0 , sizeof(mcp->mb)); + mcp->mb[0] = MBC_GET_MEM_OFFLOAD_CNTRL_STAT; + mcp->mb[1] = CONFIG_XCHOFFLD_MEM; + mcp->mb[2] = MSW(phys_addr); + mcp->mb[3] = LSW(phys_addr); + mcp->mb[6] = MSW(MSD(phys_addr)); + mcp->mb[7] = LSW(MSD(phys_addr)); + mcp->mb[8] = MSW(ha->exlogin_size); + mcp->mb[9] = LSW(ha->exlogin_size); + mcp->out_mb = MBX_9|MBX_8|MBX_7|MBX_6|MBX_3|MBX_2|MBX_1|MBX_0; + mcp->in_mb = MBX_11|MBX_0; + mcp->tov = MBX_TOV_SECONDS; + mcp->flags = 0; + rval = qla2x00_mailbox_command(vha, mcp); + if (rval != QLA_SUCCESS) { + /*EMPTY*/ + ql_dbg(ql_dbg_mbx, vha, 0x1158, "Failed=%x.\n", rval); + } else { + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1192, + "Done %s.\n", __func__); + } + + return rval; +} + +/* * qla2x00_get_fw_version * Get firmware version. * @@ -594,6 +821,16 @@ qla2x00_get_fw_version(scsi_qla_host_t *vha) ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x112f, "%s: Ext_FwAttributes Upper: 0x%x, Lower: 0x%x.\n", __func__, mcp->mb[17], mcp->mb[16]); + + if (ha->fw_attributes_h & 0x4) + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x118d, + "%s: Firmware supports Extended Login 0x%x\n", + __func__, ha->fw_attributes_h); + + if (ha->fw_attributes_h & 0x8) + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1191, + "%s: Firmware supports Exchange Offload 0x%x\n", + __func__, ha->fw_attributes_h); } if (IS_QLA27XX(ha)) { @@ -2383,10 +2620,9 @@ qla2x00_get_id_list(scsi_qla_host_t *vha, void *id_list, dma_addr_t id_list_dma, * Kernel context. */ int -qla2x00_get_resource_cnts(scsi_qla_host_t *vha, uint16_t *cur_xchg_cnt, - uint16_t *orig_xchg_cnt, uint16_t *cur_iocb_cnt, - uint16_t *orig_iocb_cnt, uint16_t *max_npiv_vports, uint16_t *max_fcfs) +qla2x00_get_resource_cnts(scsi_qla_host_t *vha) { + struct qla_hw_data *ha = vha->hw; int rval; mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; @@ -2414,19 +2650,16 @@ qla2x00_get_resource_cnts(scsi_qla_host_t *vha, uint16_t *cur_xchg_cnt, mcp->mb[3], mcp->mb[6], mcp->mb[7], mcp->mb[10], mcp->mb[11], mcp->mb[12]); - if (cur_xchg_cnt) - *cur_xchg_cnt = mcp->mb[3]; - if (orig_xchg_cnt) - *orig_xchg_cnt = mcp->mb[6]; - if (cur_iocb_cnt) - *cur_iocb_cnt = mcp->mb[7]; - if (orig_iocb_cnt) - *orig_iocb_cnt = mcp->mb[10]; - if (vha->hw->flags.npiv_supported && max_npiv_vports) - *max_npiv_vports = mcp->mb[11]; - if ((IS_QLA81XX(vha->hw) || IS_QLA83XX(vha->hw) || - IS_QLA27XX(vha->hw)) && max_fcfs) - *max_fcfs = mcp->mb[12]; + ha->orig_fw_tgt_xcb_count = mcp->mb[1]; + ha->cur_fw_tgt_xcb_count = mcp->mb[2]; + ha->cur_fw_xcb_count = mcp->mb[3]; + ha->orig_fw_xcb_count = mcp->mb[6]; + ha->cur_fw_iocb_count = mcp->mb[7]; + ha->orig_fw_iocb_count = mcp->mb[10]; + if (ha->flags.npiv_supported) + ha->max_npiv_vports = mcp->mb[11]; + if (IS_QLA81XX(ha) || IS_QLA83XX(ha) || IS_QLA27XX(ha)) + ha->fw_max_fcf_count = mcp->mb[12]; } return (rval); diff --git a/drivers/scsi/qla2xxx/qla_os.c b/drivers/scsi/qla2xxx/qla_os.c index 6be32fdab365..f1788db43195 100644 --- a/drivers/scsi/qla2xxx/qla_os.c +++ b/drivers/scsi/qla2xxx/qla_os.c @@ -221,6 +221,18 @@ MODULE_PARM_DESC(ql2xmdenable, "0 - MiniDump disabled. " "1 (Default) - MiniDump enabled."); +int ql2xexlogins = 0; +module_param(ql2xexlogins, uint, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(ql2xexlogins, + "Number of extended Logins. " + "0 (Default)- Disabled."); + +int ql2xexchoffld = 0; +module_param(ql2xexchoffld, uint, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(ql2xexchoffld, + "Number of exchanges to offload. " + "0 (Default)- Disabled."); + /* * SCSI host template entry points */ @@ -2324,6 +2336,9 @@ qla2x00_probe_one(struct pci_dev *pdev, const struct pci_device_id *id) ha->tgt.enable_class_2 = ql2xenableclass2; INIT_LIST_HEAD(&ha->tgt.q_full_list); spin_lock_init(&ha->tgt.q_full_lock); + spin_lock_init(&ha->tgt.sess_lock); + spin_lock_init(&ha->tgt.atio_lock); + /* Clear our data area */ ha->bars = bars; @@ -2468,7 +2483,7 @@ qla2x00_probe_one(struct pci_dev *pdev, const struct pci_device_id *id) ha->max_fibre_devices = MAX_FIBRE_DEVICES_2400; ha->mbx_count = MAILBOX_REGISTER_COUNT; req_length = REQUEST_ENTRY_CNT_83XX; - rsp_length = RESPONSE_ENTRY_CNT_2300; + rsp_length = RESPONSE_ENTRY_CNT_83XX; ha->tgt.atio_q_length = ATIO_ENTRY_CNT_24XX; ha->max_loop_id = SNS_LAST_LOOP_ID_2300; ha->init_cb_size = sizeof(struct mid_init_cb_81xx); @@ -2498,8 +2513,8 @@ qla2x00_probe_one(struct pci_dev *pdev, const struct pci_device_id *id) ha->portnum = PCI_FUNC(ha->pdev->devfn); ha->max_fibre_devices = MAX_FIBRE_DEVICES_2400; ha->mbx_count = MAILBOX_REGISTER_COUNT; - req_length = REQUEST_ENTRY_CNT_24XX; - rsp_length = RESPONSE_ENTRY_CNT_2300; + req_length = REQUEST_ENTRY_CNT_83XX; + rsp_length = RESPONSE_ENTRY_CNT_83XX; ha->tgt.atio_q_length = ATIO_ENTRY_CNT_24XX; ha->max_loop_id = SNS_LAST_LOOP_ID_2300; ha->init_cb_size = sizeof(struct mid_init_cb_81xx); @@ -3128,6 +3143,14 @@ qla2x00_remove_one(struct pci_dev *pdev) base_vha->flags.online = 0; + /* free DMA memory */ + if (ha->exlogin_buf) + qla2x00_free_exlogin_buffer(ha); + + /* free DMA memory */ + if (ha->exchoffld_buf) + qla2x00_free_exchoffld_buffer(ha); + qla2x00_destroy_deferred_work(ha); qlt_remove_target(ha, base_vha); @@ -3587,6 +3610,140 @@ fail: return -ENOMEM; } +int +qla2x00_set_exlogins_buffer(scsi_qla_host_t *vha) +{ + int rval; + uint16_t size, max_cnt, temp; + struct qla_hw_data *ha = vha->hw; + + /* Return if we don't need to alloacate any extended logins */ + if (!ql2xexlogins) + return QLA_SUCCESS; + + ql_log(ql_log_info, vha, 0xd021, "EXLOGIN count: %d.\n", ql2xexlogins); + max_cnt = 0; + rval = qla_get_exlogin_status(vha, &size, &max_cnt); + if (rval != QLA_SUCCESS) { + ql_log_pci(ql_log_fatal, ha->pdev, 0xd029, + "Failed to get exlogin status.\n"); + return rval; + } + + temp = (ql2xexlogins > max_cnt) ? max_cnt : ql2xexlogins; + ha->exlogin_size = (size * temp); + ql_log(ql_log_info, vha, 0xd024, + "EXLOGIN: max_logins=%d, portdb=0x%x, total=%d.\n", + max_cnt, size, temp); + + ql_log(ql_log_info, vha, 0xd025, "EXLOGIN: requested size=0x%x\n", + ha->exlogin_size); + + /* Get consistent memory for extended logins */ + ha->exlogin_buf = dma_alloc_coherent(&ha->pdev->dev, + ha->exlogin_size, &ha->exlogin_buf_dma, GFP_KERNEL); + if (!ha->exlogin_buf) { + ql_log_pci(ql_log_fatal, ha->pdev, 0xd02a, + "Failed to allocate memory for exlogin_buf_dma.\n"); + return -ENOMEM; + } + + /* Now configure the dma buffer */ + rval = qla_set_exlogin_mem_cfg(vha, ha->exlogin_buf_dma); + if (rval) { + ql_log(ql_log_fatal, vha, 0x00cf, + "Setup extended login buffer ****FAILED****.\n"); + qla2x00_free_exlogin_buffer(ha); + } + + return rval; +} + +/* +* qla2x00_free_exlogin_buffer +* +* Input: +* ha = adapter block pointer +*/ +void +qla2x00_free_exlogin_buffer(struct qla_hw_data *ha) +{ + if (ha->exlogin_buf) { + dma_free_coherent(&ha->pdev->dev, ha->exlogin_size, + ha->exlogin_buf, ha->exlogin_buf_dma); + ha->exlogin_buf = NULL; + ha->exlogin_size = 0; + } +} + +int +qla2x00_set_exchoffld_buffer(scsi_qla_host_t *vha) +{ + int rval; + uint16_t size, max_cnt, temp; + struct qla_hw_data *ha = vha->hw; + + /* Return if we don't need to alloacate any extended logins */ + if (!ql2xexchoffld) + return QLA_SUCCESS; + + ql_log(ql_log_info, vha, 0xd014, + "Exchange offload count: %d.\n", ql2xexlogins); + + max_cnt = 0; + rval = qla_get_exchoffld_status(vha, &size, &max_cnt); + if (rval != QLA_SUCCESS) { + ql_log_pci(ql_log_fatal, ha->pdev, 0xd012, + "Failed to get exlogin status.\n"); + return rval; + } + + temp = (ql2xexchoffld > max_cnt) ? max_cnt : ql2xexchoffld; + ha->exchoffld_size = (size * temp); + ql_log(ql_log_info, vha, 0xd016, + "Exchange offload: max_count=%d, buffers=0x%x, total=%d.\n", + max_cnt, size, temp); + + ql_log(ql_log_info, vha, 0xd017, + "Exchange Buffers requested size = 0x%x\n", ha->exchoffld_size); + + /* Get consistent memory for extended logins */ + ha->exchoffld_buf = dma_alloc_coherent(&ha->pdev->dev, + ha->exchoffld_size, &ha->exchoffld_buf_dma, GFP_KERNEL); + if (!ha->exchoffld_buf) { + ql_log_pci(ql_log_fatal, ha->pdev, 0xd013, + "Failed to allocate memory for exchoffld_buf_dma.\n"); + return -ENOMEM; + } + + /* Now configure the dma buffer */ + rval = qla_set_exchoffld_mem_cfg(vha, ha->exchoffld_buf_dma); + if (rval) { + ql_log(ql_log_fatal, vha, 0xd02e, + "Setup exchange offload buffer ****FAILED****.\n"); + qla2x00_free_exchoffld_buffer(ha); + } + + return rval; +} + +/* +* qla2x00_free_exchoffld_buffer +* +* Input: +* ha = adapter block pointer +*/ +void +qla2x00_free_exchoffld_buffer(struct qla_hw_data *ha) +{ + if (ha->exchoffld_buf) { + dma_free_coherent(&ha->pdev->dev, ha->exchoffld_size, + ha->exchoffld_buf, ha->exchoffld_buf_dma); + ha->exchoffld_buf = NULL; + ha->exchoffld_size = 0; + } +} + /* * qla2x00_free_fw_dump * Frees fw dump stuff. @@ -3766,6 +3923,8 @@ struct scsi_qla_host *qla2x00_create_host(struct scsi_host_template *sht, INIT_LIST_HEAD(&vha->list); INIT_LIST_HEAD(&vha->qla_cmd_list); INIT_LIST_HEAD(&vha->qla_sess_op_cmd_list); + INIT_LIST_HEAD(&vha->logo_list); + INIT_LIST_HEAD(&vha->plogi_ack_list); spin_lock_init(&vha->work_lock); spin_lock_init(&vha->cmd_list_lock); diff --git a/drivers/scsi/qla2xxx/qla_target.c b/drivers/scsi/qla2xxx/qla_target.c index 75514a15bea0..8075a4cdb45c 100644 --- a/drivers/scsi/qla2xxx/qla_target.c +++ b/drivers/scsi/qla2xxx/qla_target.c @@ -100,7 +100,7 @@ enum fcp_resp_rsp_codes { */ /* Predefs for callbacks handed to qla2xxx LLD */ static void qlt_24xx_atio_pkt(struct scsi_qla_host *ha, - struct atio_from_isp *pkt); + struct atio_from_isp *pkt, uint8_t); static void qlt_response_pkt(struct scsi_qla_host *ha, response_t *pkt); static int qlt_issue_task_mgmt(struct qla_tgt_sess *sess, uint32_t lun, int fn, void *iocb, int flags); @@ -118,10 +118,13 @@ static void qlt_send_notify_ack(struct scsi_qla_host *vha, struct imm_ntfy_from_isp *ntfy, uint32_t add_flags, uint16_t resp_code, int resp_code_valid, uint16_t srr_flags, uint16_t srr_reject_code, uint8_t srr_explan); +static void qlt_send_term_imm_notif(struct scsi_qla_host *vha, + struct imm_ntfy_from_isp *imm, int ha_locked); /* * Global Variables */ static struct kmem_cache *qla_tgt_mgmt_cmd_cachep; +static struct kmem_cache *qla_tgt_plogi_cachep; static mempool_t *qla_tgt_mgmt_cmd_mempool; static struct workqueue_struct *qla_tgt_wq; static DEFINE_MUTEX(qla_tgt_mutex); @@ -226,8 +229,8 @@ static inline void qlt_decr_num_pend_cmds(struct scsi_qla_host *vha) spin_unlock_irqrestore(&vha->hw->tgt.q_full_lock, flags); } -static void qlt_24xx_atio_pkt_all_vps(struct scsi_qla_host *vha, - struct atio_from_isp *atio) +static bool qlt_24xx_atio_pkt_all_vps(struct scsi_qla_host *vha, + struct atio_from_isp *atio, uint8_t ha_locked) { ql_dbg(ql_dbg_tgt, vha, 0xe072, "%s: qla_target(%d): type %x ox_id %04x\n", @@ -248,7 +251,7 @@ static void qlt_24xx_atio_pkt_all_vps(struct scsi_qla_host *vha, atio->u.isp24.fcp_hdr.d_id[2]); break; } - qlt_24xx_atio_pkt(host, atio); + qlt_24xx_atio_pkt(host, atio, ha_locked); break; } @@ -271,7 +274,7 @@ static void qlt_24xx_atio_pkt_all_vps(struct scsi_qla_host *vha, break; } } - qlt_24xx_atio_pkt(host, atio); + qlt_24xx_atio_pkt(host, atio, ha_locked); break; } @@ -282,7 +285,7 @@ static void qlt_24xx_atio_pkt_all_vps(struct scsi_qla_host *vha, break; } - return; + return false; } void qlt_response_pkt_all_vps(struct scsi_qla_host *vha, response_t *pkt) @@ -389,6 +392,131 @@ void qlt_response_pkt_all_vps(struct scsi_qla_host *vha, response_t *pkt) } +/* + * All qlt_plogi_ack_t operations are protected by hardware_lock + */ + +/* + * This is a zero-base ref-counting solution, since hardware_lock + * guarantees that ref_count is not modified concurrently. + * Upon successful return content of iocb is undefined + */ +static qlt_plogi_ack_t * +qlt_plogi_ack_find_add(struct scsi_qla_host *vha, port_id_t *id, + struct imm_ntfy_from_isp *iocb) +{ + qlt_plogi_ack_t *pla; + + list_for_each_entry(pla, &vha->plogi_ack_list, list) { + if (pla->id.b24 == id->b24) { + qlt_send_term_imm_notif(vha, &pla->iocb, 1); + pla->iocb = *iocb; + return pla; + } + } + + pla = kmem_cache_zalloc(qla_tgt_plogi_cachep, GFP_ATOMIC); + if (!pla) { + ql_dbg(ql_dbg_async, vha, 0x5088, + "qla_target(%d): Allocation of plogi_ack failed\n", + vha->vp_idx); + return NULL; + } + + pla->iocb = *iocb; + pla->id = *id; + list_add_tail(&pla->list, &vha->plogi_ack_list); + + return pla; +} + +static void qlt_plogi_ack_unref(struct scsi_qla_host *vha, qlt_plogi_ack_t *pla) +{ + BUG_ON(!pla->ref_count); + pla->ref_count--; + + if (pla->ref_count) + return; + + ql_dbg(ql_dbg_async, vha, 0x5089, + "Sending PLOGI ACK to wwn %8phC s_id %02x:%02x:%02x loop_id %#04x" + " exch %#x ox_id %#x\n", pla->iocb.u.isp24.port_name, + pla->iocb.u.isp24.port_id[2], pla->iocb.u.isp24.port_id[1], + pla->iocb.u.isp24.port_id[0], + le16_to_cpu(pla->iocb.u.isp24.nport_handle), + pla->iocb.u.isp24.exchange_address, pla->iocb.ox_id); + qlt_send_notify_ack(vha, &pla->iocb, 0, 0, 0, 0, 0, 0); + + list_del(&pla->list); + kmem_cache_free(qla_tgt_plogi_cachep, pla); +} + +static void +qlt_plogi_ack_link(struct scsi_qla_host *vha, qlt_plogi_ack_t *pla, + struct qla_tgt_sess *sess, qlt_plogi_link_t link) +{ + /* Inc ref_count first because link might already be pointing at pla */ + pla->ref_count++; + + if (sess->plogi_link[link]) + qlt_plogi_ack_unref(vha, sess->plogi_link[link]); + + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf097, + "Linking sess %p [%d] wwn %8phC with PLOGI ACK to wwn %8phC" + " s_id %02x:%02x:%02x, ref=%d\n", sess, link, sess->port_name, + pla->iocb.u.isp24.port_name, pla->iocb.u.isp24.port_id[2], + pla->iocb.u.isp24.port_id[1], pla->iocb.u.isp24.port_id[0], + pla->ref_count); + + sess->plogi_link[link] = pla; +} + +typedef struct { + /* These fields must be initialized by the caller */ + port_id_t id; + /* + * number of cmds dropped while we were waiting for + * initiator to ack LOGO initialize to 1 if LOGO is + * triggered by a command, otherwise, to 0 + */ + int cmd_count; + + /* These fields are used by callee */ + struct list_head list; +} qlt_port_logo_t; + +static void +qlt_send_first_logo(struct scsi_qla_host *vha, qlt_port_logo_t *logo) +{ + qlt_port_logo_t *tmp; + int res; + + mutex_lock(&vha->vha_tgt.tgt_mutex); + + list_for_each_entry(tmp, &vha->logo_list, list) { + if (tmp->id.b24 == logo->id.b24) { + tmp->cmd_count += logo->cmd_count; + mutex_unlock(&vha->vha_tgt.tgt_mutex); + return; + } + } + + list_add_tail(&logo->list, &vha->logo_list); + + mutex_unlock(&vha->vha_tgt.tgt_mutex); + + res = qla24xx_els_dcmd_iocb(vha, ELS_DCMD_LOGO, logo->id); + + mutex_lock(&vha->vha_tgt.tgt_mutex); + list_del(&logo->list); + mutex_unlock(&vha->vha_tgt.tgt_mutex); + + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf098, + "Finished LOGO to %02x:%02x:%02x, dropped %d cmds, res = %#x\n", + logo->id.b.domain, logo->id.b.area, logo->id.b.al_pa, + logo->cmd_count, res); +} + static void qlt_free_session_done(struct work_struct *work) { struct qla_tgt_sess *sess = container_of(work, struct qla_tgt_sess, @@ -402,14 +530,21 @@ static void qlt_free_session_done(struct work_struct *work) ql_dbg(ql_dbg_tgt_mgt, vha, 0xf084, "%s: se_sess %p / sess %p from port %8phC loop_id %#04x" - " s_id %02x:%02x:%02x logout %d keep %d plogi %d\n", + " s_id %02x:%02x:%02x logout %d keep %d els_logo %d\n", __func__, sess->se_sess, sess, sess->port_name, sess->loop_id, sess->s_id.b.domain, sess->s_id.b.area, sess->s_id.b.al_pa, sess->logout_on_delete, sess->keep_nport_handle, - sess->plogi_ack_needed); + sess->send_els_logo); BUG_ON(!tgt); + if (sess->send_els_logo) { + qlt_port_logo_t logo; + logo.id = sess->s_id; + logo.cmd_count = 0; + qlt_send_first_logo(vha, &logo); + } + if (sess->logout_on_delete) { int rc; @@ -455,9 +590,34 @@ static void qlt_free_session_done(struct work_struct *work) spin_lock_irqsave(&ha->hardware_lock, flags); - if (sess->plogi_ack_needed) - qlt_send_notify_ack(vha, &sess->tm_iocb, - 0, 0, 0, 0, 0, 0); + { + qlt_plogi_ack_t *own = + sess->plogi_link[QLT_PLOGI_LINK_SAME_WWN]; + qlt_plogi_ack_t *con = + sess->plogi_link[QLT_PLOGI_LINK_CONFLICT]; + + if (con) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf099, + "se_sess %p / sess %p port %8phC is gone," + " %s (ref=%d), releasing PLOGI for %8phC (ref=%d)\n", + sess->se_sess, sess, sess->port_name, + own ? "releasing own PLOGI" : + "no own PLOGI pending", + own ? own->ref_count : -1, + con->iocb.u.isp24.port_name, con->ref_count); + qlt_plogi_ack_unref(vha, con); + } else { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf09a, + "se_sess %p / sess %p port %8phC is gone, %s (ref=%d)\n", + sess->se_sess, sess, sess->port_name, + own ? "releasing own PLOGI" : + "no own PLOGI pending", + own ? own->ref_count : -1); + } + + if (own) + qlt_plogi_ack_unref(vha, own); + } list_del(&sess->sess_list_entry); @@ -476,7 +636,7 @@ static void qlt_free_session_done(struct work_struct *work) wake_up_all(&tgt->waitQ); } -/* ha->hardware_lock supposed to be held on entry */ +/* ha->tgt.sess_lock supposed to be held on entry */ void qlt_unreg_sess(struct qla_tgt_sess *sess) { struct scsi_qla_host *vha = sess->vha; @@ -492,7 +652,7 @@ void qlt_unreg_sess(struct qla_tgt_sess *sess) } EXPORT_SYMBOL(qlt_unreg_sess); -/* ha->hardware_lock supposed to be held on entry */ + static int qlt_reset(struct scsi_qla_host *vha, void *iocb, int mcmd) { struct qla_hw_data *ha = vha->hw; @@ -502,12 +662,15 @@ static int qlt_reset(struct scsi_qla_host *vha, void *iocb, int mcmd) int res = 0; struct imm_ntfy_from_isp *n = (struct imm_ntfy_from_isp *)iocb; struct atio_from_isp *a = (struct atio_from_isp *)iocb; + unsigned long flags; loop_id = le16_to_cpu(n->u.isp24.nport_handle); if (loop_id == 0xFFFF) { /* Global event */ atomic_inc(&vha->vha_tgt.qla_tgt->tgt_global_resets_count); + spin_lock_irqsave(&ha->tgt.sess_lock, flags); qlt_clear_tgt_db(vha->vha_tgt.qla_tgt); + spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); #if 0 /* FIXME: do we need to choose a session here? */ if (!list_empty(&ha->tgt.qla_tgt->sess_list)) { sess = list_entry(ha->tgt.qla_tgt->sess_list.next, @@ -534,7 +697,9 @@ static int qlt_reset(struct scsi_qla_host *vha, void *iocb, int mcmd) sess = NULL; #endif } else { + spin_lock_irqsave(&ha->tgt.sess_lock, flags); sess = ha->tgt.tgt_ops->find_sess_by_loop_id(vha, loop_id); + spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); } ql_dbg(ql_dbg_tgt, vha, 0xe000, @@ -556,7 +721,7 @@ static int qlt_reset(struct scsi_qla_host *vha, void *iocb, int mcmd) iocb, QLA24XX_MGMT_SEND_NACK); } -/* ha->hardware_lock supposed to be held on entry */ +/* ha->tgt.sess_lock supposed to be held on entry */ static void qlt_schedule_sess_for_deletion(struct qla_tgt_sess *sess, bool immediate) { @@ -600,7 +765,7 @@ static void qlt_schedule_sess_for_deletion(struct qla_tgt_sess *sess, sess->expires - jiffies); } -/* ha->hardware_lock supposed to be held on entry */ +/* ha->tgt.sess_lock supposed to be held on entry */ static void qlt_clear_tgt_db(struct qla_tgt *tgt) { struct qla_tgt_sess *sess; @@ -636,12 +801,12 @@ static int qla24xx_get_loop_id(struct scsi_qla_host *vha, const uint8_t *s_id, ql_dbg(ql_dbg_tgt_mgt, vha, 0xf045, "qla_target(%d): get_id_list() failed: %x\n", vha->vp_idx, rc); - res = -1; + res = -EBUSY; goto out_free_id_list; } id_iter = (char *)gid_list; - res = -1; + res = -ENOENT; for (i = 0; i < entries; i++) { struct gid_list_info *gid = (struct gid_list_info *)id_iter; if ((gid->al_pa == s_id[2]) && @@ -660,7 +825,7 @@ out_free_id_list: return res; } -/* ha->hardware_lock supposed to be held on entry */ +/* ha->tgt.sess_lock supposed to be held on entry */ static void qlt_undelete_sess(struct qla_tgt_sess *sess) { BUG_ON(sess->deleted != QLA_SESS_DELETION_PENDING); @@ -678,7 +843,7 @@ static void qlt_del_sess_work_fn(struct delayed_work *work) struct qla_tgt_sess *sess; unsigned long flags, elapsed; - spin_lock_irqsave(&ha->hardware_lock, flags); + spin_lock_irqsave(&ha->tgt.sess_lock, flags); while (!list_empty(&tgt->del_sess_list)) { sess = list_entry(tgt->del_sess_list.next, typeof(*sess), del_list_entry); @@ -699,7 +864,7 @@ static void qlt_del_sess_work_fn(struct delayed_work *work) break; } } - spin_unlock_irqrestore(&ha->hardware_lock, flags); + spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); } /* @@ -717,7 +882,7 @@ static struct qla_tgt_sess *qlt_create_sess( unsigned char be_sid[3]; /* Check to avoid double sessions */ - spin_lock_irqsave(&ha->hardware_lock, flags); + spin_lock_irqsave(&ha->tgt.sess_lock, flags); list_for_each_entry(sess, &vha->vha_tgt.qla_tgt->sess_list, sess_list_entry) { if (!memcmp(sess->port_name, fcport->port_name, WWN_SIZE)) { @@ -732,7 +897,7 @@ static struct qla_tgt_sess *qlt_create_sess( /* Cannot undelete at this point */ if (sess->deleted == QLA_SESS_DELETION_IN_PROGRESS) { - spin_unlock_irqrestore(&ha->hardware_lock, + spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); return NULL; } @@ -749,12 +914,12 @@ static struct qla_tgt_sess *qlt_create_sess( qlt_do_generation_tick(vha, &sess->generation); - spin_unlock_irqrestore(&ha->hardware_lock, flags); + spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); return sess; } } - spin_unlock_irqrestore(&ha->hardware_lock, flags); + spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); sess = kzalloc(sizeof(*sess), GFP_KERNEL); if (!sess) { @@ -799,7 +964,7 @@ static struct qla_tgt_sess *qlt_create_sess( } /* * Take an extra reference to ->sess_kref here to handle qla_tgt_sess - * access across ->hardware_lock reaquire. + * access across ->tgt.sess_lock reaquire. */ kref_get(&sess->se_sess->sess_kref); @@ -807,11 +972,11 @@ static struct qla_tgt_sess *qlt_create_sess( BUILD_BUG_ON(sizeof(sess->port_name) != sizeof(fcport->port_name)); memcpy(sess->port_name, fcport->port_name, sizeof(sess->port_name)); - spin_lock_irqsave(&ha->hardware_lock, flags); + spin_lock_irqsave(&ha->tgt.sess_lock, flags); list_add_tail(&sess->sess_list_entry, &vha->vha_tgt.qla_tgt->sess_list); vha->vha_tgt.qla_tgt->sess_count++; qlt_do_generation_tick(vha, &sess->generation); - spin_unlock_irqrestore(&ha->hardware_lock, flags); + spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); ql_dbg(ql_dbg_tgt_mgt, vha, 0xf04b, "qla_target(%d): %ssession for wwn %8phC (loop_id %d, " @@ -842,23 +1007,23 @@ void qlt_fc_port_added(struct scsi_qla_host *vha, fc_port_t *fcport) if (qla_ini_mode_enabled(vha)) return; - spin_lock_irqsave(&ha->hardware_lock, flags); + spin_lock_irqsave(&ha->tgt.sess_lock, flags); if (tgt->tgt_stop) { - spin_unlock_irqrestore(&ha->hardware_lock, flags); + spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); return; } sess = qlt_find_sess_by_port_name(tgt, fcport->port_name); if (!sess) { - spin_unlock_irqrestore(&ha->hardware_lock, flags); + spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); mutex_lock(&vha->vha_tgt.tgt_mutex); sess = qlt_create_sess(vha, fcport, false); mutex_unlock(&vha->vha_tgt.tgt_mutex); - spin_lock_irqsave(&ha->hardware_lock, flags); + spin_lock_irqsave(&ha->tgt.sess_lock, flags); } else if (sess->deleted == QLA_SESS_DELETION_IN_PROGRESS) { /* Point of no return */ - spin_unlock_irqrestore(&ha->hardware_lock, flags); + spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); return; } else { kref_get(&sess->se_sess->sess_kref); @@ -887,7 +1052,7 @@ void qlt_fc_port_added(struct scsi_qla_host *vha, fc_port_t *fcport) sess->local = 0; } ha->tgt.tgt_ops->put_sess(sess); - spin_unlock_irqrestore(&ha->hardware_lock, flags); + spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); } /* @@ -899,6 +1064,7 @@ qlt_fc_port_deleted(struct scsi_qla_host *vha, fc_port_t *fcport, int max_gen) { struct qla_tgt *tgt = vha->vha_tgt.qla_tgt; struct qla_tgt_sess *sess; + unsigned long flags; if (!vha->hw->tgt.tgt_ops) return; @@ -906,15 +1072,19 @@ qlt_fc_port_deleted(struct scsi_qla_host *vha, fc_port_t *fcport, int max_gen) if (!tgt) return; + spin_lock_irqsave(&vha->hw->tgt.sess_lock, flags); if (tgt->tgt_stop) { + spin_unlock_irqrestore(&vha->hw->tgt.sess_lock, flags); return; } sess = qlt_find_sess_by_port_name(tgt, fcport->port_name); if (!sess) { + spin_unlock_irqrestore(&vha->hw->tgt.sess_lock, flags); return; } if (max_gen - sess->generation < 0) { + spin_unlock_irqrestore(&vha->hw->tgt.sess_lock, flags); ql_dbg(ql_dbg_tgt_mgt, vha, 0xf092, "Ignoring stale deletion request for se_sess %p / sess %p" " for port %8phC, req_gen %d, sess_gen %d\n", @@ -927,6 +1097,7 @@ qlt_fc_port_deleted(struct scsi_qla_host *vha, fc_port_t *fcport, int max_gen) sess->local = 1; qlt_schedule_sess_for_deletion(sess, false); + spin_unlock_irqrestore(&vha->hw->tgt.sess_lock, flags); } static inline int test_tgt_sess_count(struct qla_tgt *tgt) @@ -984,10 +1155,10 @@ int qlt_stop_phase1(struct qla_tgt *tgt) * Lock is needed, because we still can get an incoming packet. */ mutex_lock(&vha->vha_tgt.tgt_mutex); - spin_lock_irqsave(&ha->hardware_lock, flags); + spin_lock_irqsave(&ha->tgt.sess_lock, flags); tgt->tgt_stop = 1; qlt_clear_tgt_db(tgt); - spin_unlock_irqrestore(&ha->hardware_lock, flags); + spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); mutex_unlock(&vha->vha_tgt.tgt_mutex); mutex_unlock(&qla_tgt_mutex); @@ -1040,7 +1211,7 @@ void qlt_stop_phase2(struct qla_tgt *tgt) mutex_lock(&vha->vha_tgt.tgt_mutex); spin_lock_irqsave(&ha->hardware_lock, flags); - while (tgt->irq_cmd_count != 0) { + while ((tgt->irq_cmd_count != 0) || (tgt->atio_irq_cmd_count != 0)) { spin_unlock_irqrestore(&ha->hardware_lock, flags); udelay(2); spin_lock_irqsave(&ha->hardware_lock, flags); @@ -1309,7 +1480,7 @@ static int abort_cmd_for_tag(struct scsi_qla_host *vha, uint32_t tag) list_for_each_entry(cmd, &vha->qla_cmd_list, cmd_list) { if (tag == cmd->atio.u.isp24.exchange_addr) { - cmd->state = QLA_TGT_STATE_ABORTED; + cmd->aborted = 1; spin_unlock(&vha->cmd_list_lock); return 1; } @@ -1351,7 +1522,7 @@ static void abort_cmds_for_lun(struct scsi_qla_host *vha, cmd_lun = scsilun_to_int( (struct scsi_lun *)&cmd->atio.u.isp24.fcp_cmnd.lun); if (cmd_key == key && cmd_lun == lun) - cmd->state = QLA_TGT_STATE_ABORTED; + cmd->aborted = 1; } spin_unlock(&vha->cmd_list_lock); } @@ -1435,6 +1606,7 @@ static void qlt_24xx_handle_abts(struct scsi_qla_host *vha, uint32_t tag = abts->exchange_addr_to_abort; uint8_t s_id[3]; int rc; + unsigned long flags; if (le32_to_cpu(abts->fcp_hdr_le.parameter) & ABTS_PARAM_ABORT_SEQ) { ql_dbg(ql_dbg_tgt_mgt, vha, 0xf053, @@ -1462,6 +1634,7 @@ static void qlt_24xx_handle_abts(struct scsi_qla_host *vha, s_id[1] = abts->fcp_hdr_le.s_id[1]; s_id[2] = abts->fcp_hdr_le.s_id[0]; + spin_lock_irqsave(&ha->tgt.sess_lock, flags); sess = ha->tgt.tgt_ops->find_sess_by_s_id(vha, s_id); if (!sess) { ql_dbg(ql_dbg_tgt_mgt, vha, 0xf012, @@ -1469,12 +1642,17 @@ static void qlt_24xx_handle_abts(struct scsi_qla_host *vha, vha->vp_idx); rc = qlt_sched_sess_work(vha->vha_tgt.qla_tgt, QLA_TGT_SESS_WORK_ABORT, abts, sizeof(*abts)); + + spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); + if (rc != 0) { qlt_24xx_send_abts_resp(vha, abts, FCP_TMF_REJECTED, false); } return; } + spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); + if (sess->deleted == QLA_SESS_DELETION_IN_PROGRESS) { qlt_24xx_send_abts_resp(vha, abts, FCP_TMF_REJECTED, false); @@ -1560,15 +1738,15 @@ void qlt_xmit_tm_rsp(struct qla_tgt_mgmt_cmd *mcmd) spin_lock_irqsave(&ha->hardware_lock, flags); - if (qla2x00_reset_active(vha) || mcmd->reset_count != ha->chip_reset) { + if (!vha->flags.online || mcmd->reset_count != ha->chip_reset) { /* - * Either a chip reset is active or this request was from + * Either the port is not online or this request was from * previous life, just abort the processing. */ ql_dbg(ql_dbg_async, vha, 0xe100, - "RESET-TMR active/old-count/new-count = %d/%d/%d.\n", - qla2x00_reset_active(vha), mcmd->reset_count, - ha->chip_reset); + "RESET-TMR online/active/old-count/new-count = %d/%d/%d/%d.\n", + vha->flags.online, qla2x00_reset_active(vha), + mcmd->reset_count, ha->chip_reset); ha->tgt.tgt_ops->free_mcmd(mcmd); spin_unlock_irqrestore(&ha->hardware_lock, flags); return; @@ -2510,17 +2688,22 @@ int qlt_xmit_response(struct qla_tgt_cmd *cmd, int xmit_type, spin_lock_irqsave(&ha->hardware_lock, flags); - if (qla2x00_reset_active(vha) || cmd->reset_count != ha->chip_reset) { + if (xmit_type == QLA_TGT_XMIT_STATUS) + vha->tgt_counters.core_qla_snd_status++; + else + vha->tgt_counters.core_qla_que_buf++; + + if (!vha->flags.online || cmd->reset_count != ha->chip_reset) { /* - * Either a chip reset is active or this request was from + * Either the port is not online or this request was from * previous life, just abort the processing. */ cmd->state = QLA_TGT_STATE_PROCESSED; qlt_abort_cmd_on_host_reset(cmd->vha, cmd); ql_dbg(ql_dbg_async, vha, 0xe101, - "RESET-RSP active/old-count/new-count = %d/%d/%d.\n", - qla2x00_reset_active(vha), cmd->reset_count, - ha->chip_reset); + "RESET-RSP online/active/old-count/new-count = %d/%d/%d/%d.\n", + vha->flags.online, qla2x00_reset_active(vha), + cmd->reset_count, ha->chip_reset); spin_unlock_irqrestore(&ha->hardware_lock, flags); return 0; } @@ -2651,18 +2834,18 @@ int qlt_rdy_to_xfer(struct qla_tgt_cmd *cmd) spin_lock_irqsave(&ha->hardware_lock, flags); - if (qla2x00_reset_active(vha) || (cmd->reset_count != ha->chip_reset) || + if (!vha->flags.online || (cmd->reset_count != ha->chip_reset) || (cmd->sess && cmd->sess->deleted == QLA_SESS_DELETION_IN_PROGRESS)) { /* - * Either a chip reset is active or this request was from + * Either the port is not online or this request was from * previous life, just abort the processing. */ cmd->state = QLA_TGT_STATE_NEED_DATA; qlt_abort_cmd_on_host_reset(cmd->vha, cmd); ql_dbg(ql_dbg_async, vha, 0xe102, - "RESET-XFR active/old-count/new-count = %d/%d/%d.\n", - qla2x00_reset_active(vha), cmd->reset_count, - ha->chip_reset); + "RESET-XFR online/active/old-count/new-count = %d/%d/%d/%d.\n", + vha->flags.online, qla2x00_reset_active(vha), + cmd->reset_count, ha->chip_reset); spin_unlock_irqrestore(&ha->hardware_lock, flags); return 0; } @@ -2957,12 +3140,13 @@ static int __qlt_send_term_exchange(struct scsi_qla_host *vha, ret = 1; } + vha->tgt_counters.num_term_xchg_sent++; pkt->entry_count = 1; pkt->handle = QLA_TGT_SKIP_HANDLE | CTIO_COMPLETION_HANDLE_MARK; ctio24 = (struct ctio7_to_24xx *)pkt; ctio24->entry_type = CTIO_TYPE7; - ctio24->nport_handle = cmd ? cmd->loop_id : CTIO7_NHANDLE_UNRECOGNIZED; + ctio24->nport_handle = CTIO7_NHANDLE_UNRECOGNIZED; ctio24->timeout = cpu_to_le16(QLA_TGT_TIMEOUT); ctio24->vp_index = vha->vp_idx; ctio24->initiator_id[0] = atio->u.isp24.fcp_hdr.s_id[2]; @@ -3009,7 +3193,7 @@ static void qlt_send_term_exchange(struct scsi_qla_host *vha, qlt_alloc_qfull_cmd(vha, atio, 0, 0); done: - if (cmd && ((cmd->state != QLA_TGT_STATE_ABORTED) || + if (cmd && (!cmd->aborted || !cmd->cmd_sent_to_fw)) { if (cmd->sg_mapped) qlt_unmap_sg(vha, cmd); @@ -3028,7 +3212,7 @@ static void qlt_init_term_exchange(struct scsi_qla_host *vha) struct qla_tgt_cmd *cmd, *tcmd; vha->hw->tgt.leak_exchg_thresh_hold = - (vha->hw->fw_xcb_count/100) * LEAK_EXCHG_THRESH_HOLD_PERCENT; + (vha->hw->cur_fw_xcb_count/100) * LEAK_EXCHG_THRESH_HOLD_PERCENT; cmd = tcmd = NULL; if (!list_empty(&vha->hw->tgt.q_full_list)) { @@ -3058,7 +3242,7 @@ static void qlt_chk_exch_leak_thresh_hold(struct scsi_qla_host *vha) ql_dbg(ql_dbg_tgt, vha, 0xe079, "Chip reset due to exchange starvation: %d/%d.\n", - total_leaked, vha->hw->fw_xcb_count); + total_leaked, vha->hw->cur_fw_xcb_count); if (IS_P3P_TYPE(vha->hw)) set_bit(FCOE_CTX_RESET_NEEDED, &vha->dpc_flags); @@ -3080,7 +3264,7 @@ void qlt_abort_cmd(struct qla_tgt_cmd *cmd) "(se_cmd=%p, tag=%llu)", vha->vp_idx, cmd, &cmd->se_cmd, se_cmd->tag); - cmd->state = QLA_TGT_STATE_ABORTED; + cmd->aborted = 1; cmd->cmd_flags |= BIT_6; qlt_send_term_exchange(vha, cmd, &cmd->atio, 0); @@ -3300,9 +3484,6 @@ qlt_abort_cmd_on_host_reset(struct scsi_qla_host *vha, struct qla_tgt_cmd *cmd) ha->tgt.tgt_ops->handle_data(cmd); return; - } else if (cmd->state == QLA_TGT_STATE_ABORTED) { - ql_dbg(ql_dbg_io, vha, 0xff02, - "HOST-ABORT: handle=%d, state=ABORTED.\n", handle); } else { ql_dbg(ql_dbg_io, vha, 0xff03, "HOST-ABORT: handle=%d, state=BAD(%d).\n", handle, @@ -3398,13 +3579,26 @@ static void qlt_do_ctio_completion(struct scsi_qla_host *vha, uint32_t handle, case CTIO_PORT_LOGGED_OUT: case CTIO_PORT_UNAVAILABLE: + { + int logged_out = (status & 0xFFFF); ql_dbg(ql_dbg_tgt_mgt, vha, 0xf059, - "qla_target(%d): CTIO with PORT LOGGED " - "OUT (29) or PORT UNAVAILABLE (28) status %x " + "qla_target(%d): CTIO with %s status %x " "received (state %x, se_cmd %p)\n", vha->vp_idx, + (logged_out == CTIO_PORT_LOGGED_OUT) ? + "PORT LOGGED OUT" : "PORT UNAVAILABLE", status, cmd->state, se_cmd); - break; + if (logged_out && cmd->sess) { + /* + * Session is already logged out, but we need + * to notify initiator, who's not aware of this + */ + cmd->sess->logout_on_delete = 0; + cmd->sess->send_els_logo = 1; + qlt_schedule_sess_for_deletion(cmd->sess, true); + } + break; + } case CTIO_SRR_RECEIVED: ql_dbg(ql_dbg_tgt_mgt, vha, 0xf05a, "qla_target(%d): CTIO with SRR_RECEIVED" @@ -3454,14 +3648,14 @@ static void qlt_do_ctio_completion(struct scsi_qla_host *vha, uint32_t handle, } - /* "cmd->state == QLA_TGT_STATE_ABORTED" means + /* "cmd->aborted" means * cmd is already aborted/terminated, we don't * need to terminate again. The exchange is already * cleaned up/freed at FW level. Just cleanup at driver * level. */ if ((cmd->state != QLA_TGT_STATE_NEED_DATA) && - (cmd->state != QLA_TGT_STATE_ABORTED)) { + (!cmd->aborted)) { cmd->cmd_flags |= BIT_13; if (qlt_term_ctio_exchange(vha, ctio, cmd, status)) return; @@ -3479,7 +3673,7 @@ skip_term: ha->tgt.tgt_ops->handle_data(cmd); return; - } else if (cmd->state == QLA_TGT_STATE_ABORTED) { + } else if (cmd->aborted) { cmd->cmd_flags |= BIT_18; ql_dbg(ql_dbg_tgt_mgt, vha, 0xf01e, "Aborted command %p (tag %lld) finished\n", cmd, se_cmd->tag); @@ -3491,7 +3685,7 @@ skip_term: } if (unlikely(status != CTIO_SUCCESS) && - (cmd->state != QLA_TGT_STATE_ABORTED)) { + !cmd->aborted) { ql_dbg(ql_dbg_tgt_mgt, vha, 0xf01f, "Finishing failed CTIO\n"); dump_stack(); } @@ -3553,7 +3747,7 @@ static void __qlt_do_work(struct qla_tgt_cmd *cmd) if (tgt->tgt_stop) goto out_term; - if (cmd->state == QLA_TGT_STATE_ABORTED) { + if (cmd->aborted) { ql_dbg(ql_dbg_tgt_mgt, vha, 0xf082, "cmd with tag %u is aborted\n", cmd->atio.u.isp24.exchange_addr); @@ -3589,9 +3783,9 @@ static void __qlt_do_work(struct qla_tgt_cmd *cmd) /* * Drop extra session reference from qla_tgt_handle_cmd_for_atio*( */ - spin_lock_irqsave(&ha->hardware_lock, flags); + spin_lock_irqsave(&ha->tgt.sess_lock, flags); ha->tgt.tgt_ops->put_sess(sess); - spin_unlock_irqrestore(&ha->hardware_lock, flags); + spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); return; out_term: @@ -3606,8 +3800,11 @@ out_term: qlt_decr_num_pend_cmds(vha); percpu_ida_free(&sess->se_sess->sess_tag_pool, cmd->se_cmd.map_tag); - ha->tgt.tgt_ops->put_sess(sess); spin_unlock_irqrestore(&ha->hardware_lock, flags); + + spin_lock_irqsave(&ha->tgt.sess_lock, flags); + ha->tgt.tgt_ops->put_sess(sess); + spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); } static void qlt_do_work(struct work_struct *work) @@ -3692,10 +3889,8 @@ static void qlt_create_sess_from_atio(struct work_struct *work) goto out_term; } - mutex_lock(&vha->vha_tgt.tgt_mutex); sess = qlt_make_local_sess(vha, s_id); /* sess has an extra creation ref. */ - mutex_unlock(&vha->vha_tgt.tgt_mutex); if (!sess) goto out_term; @@ -3787,13 +3982,24 @@ static int qlt_handle_cmd_for_atio(struct scsi_qla_host *vha, cmd->cmd_in_wq = 1; cmd->cmd_flags |= BIT_0; + cmd->se_cmd.cpuid = -1; spin_lock(&vha->cmd_list_lock); list_add_tail(&cmd->cmd_list, &vha->qla_cmd_list); spin_unlock(&vha->cmd_list_lock); INIT_WORK(&cmd->work, qlt_do_work); - queue_work(qla_tgt_wq, &cmd->work); + if (ha->msix_count) { + cmd->se_cmd.cpuid = ha->tgt.rspq_vector_cpuid; + if (cmd->atio.u.isp24.fcp_cmnd.rddata) + queue_work_on(smp_processor_id(), qla_tgt_wq, + &cmd->work); + else + queue_work_on(cmd->se_cmd.cpuid, qla_tgt_wq, + &cmd->work); + } else { + queue_work(qla_tgt_wq, &cmd->work); + } return 0; } @@ -3917,13 +4123,18 @@ static int qlt_handle_task_mgmt(struct scsi_qla_host *vha, void *iocb) struct qla_tgt_sess *sess; uint32_t lun, unpacked_lun; int fn; + unsigned long flags; tgt = vha->vha_tgt.qla_tgt; lun = a->u.isp24.fcp_cmnd.lun; fn = a->u.isp24.fcp_cmnd.task_mgmt_flags; + + spin_lock_irqsave(&ha->tgt.sess_lock, flags); sess = ha->tgt.tgt_ops->find_sess_by_s_id(vha, a->u.isp24.fcp_hdr.s_id); + spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); + unpacked_lun = scsilun_to_int((struct scsi_lun *)&lun); if (!sess) { @@ -3987,10 +4198,14 @@ static int qlt_abort_task(struct scsi_qla_host *vha, struct qla_hw_data *ha = vha->hw; struct qla_tgt_sess *sess; int loop_id; + unsigned long flags; loop_id = GET_TARGET_ID(ha, (struct atio_from_isp *)iocb); + spin_lock_irqsave(&ha->tgt.sess_lock, flags); sess = ha->tgt.tgt_ops->find_sess_by_loop_id(vha, loop_id); + spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); + if (sess == NULL) { ql_dbg(ql_dbg_tgt_mgt, vha, 0xf025, "qla_target(%d): task abort for unexisting " @@ -4022,15 +4237,6 @@ void qlt_logo_completion_handler(fc_port_t *fcport, int rc) } } -static void qlt_swap_imm_ntfy_iocb(struct imm_ntfy_from_isp *a, - struct imm_ntfy_from_isp *b) -{ - struct imm_ntfy_from_isp tmp; - memcpy(&tmp, a, sizeof(struct imm_ntfy_from_isp)); - memcpy(a, b, sizeof(struct imm_ntfy_from_isp)); - memcpy(b, &tmp, sizeof(struct imm_ntfy_from_isp)); -} - /* * ha->hardware_lock supposed to be held on entry (to protect tgt->sess_list) * @@ -4040,11 +4246,13 @@ static void qlt_swap_imm_ntfy_iocb(struct imm_ntfy_from_isp *a, */ static struct qla_tgt_sess * qlt_find_sess_invalidate_other(struct qla_tgt *tgt, uint64_t wwn, - port_id_t port_id, uint16_t loop_id) + port_id_t port_id, uint16_t loop_id, struct qla_tgt_sess **conflict_sess) { struct qla_tgt_sess *sess = NULL, *other_sess; uint64_t other_wwn; + *conflict_sess = NULL; + list_for_each_entry(other_sess, &tgt->sess_list, sess_list_entry) { other_wwn = wwn_to_u64(other_sess->port_name); @@ -4072,9 +4280,10 @@ qlt_find_sess_invalidate_other(struct qla_tgt *tgt, uint64_t wwn, } else { /* * Another wwn used to have our s_id/loop_id - * combo - kill the session, but don't log out + * kill the session, but don't free the loop_id */ - sess->logout_on_delete = 0; + other_sess->keep_nport_handle = 1; + *conflict_sess = other_sess; qlt_schedule_sess_for_deletion(other_sess, true); } @@ -4119,7 +4328,7 @@ static int abort_cmds_for_s_id(struct scsi_qla_host *vha, port_id_t *s_id) list_for_each_entry(cmd, &vha->qla_cmd_list, cmd_list) { uint32_t cmd_key = sid_to_key(cmd->atio.u.isp24.fcp_hdr.s_id); if (cmd_key == key) { - cmd->state = QLA_TGT_STATE_ABORTED; + cmd->aborted = 1; count++; } } @@ -4136,12 +4345,14 @@ static int qlt_24xx_handle_els(struct scsi_qla_host *vha, { struct qla_tgt *tgt = vha->vha_tgt.qla_tgt; struct qla_hw_data *ha = vha->hw; - struct qla_tgt_sess *sess = NULL; + struct qla_tgt_sess *sess = NULL, *conflict_sess = NULL; uint64_t wwn; port_id_t port_id; uint16_t loop_id; uint16_t wd3_lo; int res = 0; + qlt_plogi_ack_t *pla; + unsigned long flags; wwn = wwn_to_u64(iocb->u.isp24.port_name); @@ -4165,27 +4376,20 @@ static int qlt_24xx_handle_els(struct scsi_qla_host *vha, /* Mark all stale commands in qla_tgt_wq for deletion */ abort_cmds_for_s_id(vha, &port_id); - if (wwn) + if (wwn) { + spin_lock_irqsave(&tgt->ha->tgt.sess_lock, flags); sess = qlt_find_sess_invalidate_other(tgt, wwn, - port_id, loop_id); + port_id, loop_id, &conflict_sess); + spin_unlock_irqrestore(&tgt->ha->tgt.sess_lock, flags); + } - if (!sess || IS_SW_RESV_ADDR(sess->s_id)) { + if (IS_SW_RESV_ADDR(port_id) || (!sess && !conflict_sess)) { res = 1; break; } - if (sess->plogi_ack_needed) { - /* - * Initiator sent another PLOGI before last PLOGI could - * finish. Swap plogi iocbs and terminate old one - * without acking, new one will get acked when session - * deletion completes. - */ - ql_log(ql_log_warn, sess->vha, 0xf094, - "sess %p received double plogi.\n", sess); - - qlt_swap_imm_ntfy_iocb(iocb, &sess->tm_iocb); - + pla = qlt_plogi_ack_find_add(vha, &port_id, iocb); + if (!pla) { qlt_send_term_imm_notif(vha, iocb, 1); res = 0; @@ -4194,13 +4398,14 @@ static int qlt_24xx_handle_els(struct scsi_qla_host *vha, res = 0; - /* - * Save immediate Notif IOCB for Ack when sess is done - * and being deleted. - */ - memcpy(&sess->tm_iocb, iocb, sizeof(sess->tm_iocb)); - sess->plogi_ack_needed = 1; + if (conflict_sess) + qlt_plogi_ack_link(vha, pla, conflict_sess, + QLT_PLOGI_LINK_CONFLICT); + + if (!sess) + break; + qlt_plogi_ack_link(vha, pla, sess, QLT_PLOGI_LINK_SAME_WWN); /* * Under normal circumstances we want to release nport handle * during LOGO process to avoid nport handle leaks inside FW. @@ -4227,9 +4432,21 @@ static int qlt_24xx_handle_els(struct scsi_qla_host *vha, case ELS_PRLI: wd3_lo = le16_to_cpu(iocb->u.isp24.u.prli.wd3_lo); - if (wwn) + if (wwn) { + spin_lock_irqsave(&tgt->ha->tgt.sess_lock, flags); sess = qlt_find_sess_invalidate_other(tgt, wwn, port_id, - loop_id); + loop_id, &conflict_sess); + spin_unlock_irqrestore(&tgt->ha->tgt.sess_lock, flags); + } + + if (conflict_sess) { + ql_dbg(ql_dbg_tgt_mgt, vha, 0xf09b, + "PRLI with conflicting sess %p port %8phC\n", + conflict_sess, conflict_sess->port_name); + qlt_send_term_imm_notif(vha, iocb, 1); + res = 0; + break; + } if (sess != NULL) { if (sess->deleted) { @@ -4899,9 +5116,12 @@ static int __qlt_send_busy(struct scsi_qla_host *vha, struct qla_hw_data *ha = vha->hw; request_t *pkt; struct qla_tgt_sess *sess = NULL; + unsigned long flags; + spin_lock_irqsave(&ha->tgt.sess_lock, flags); sess = ha->tgt.tgt_ops->find_sess_by_s_id(vha, atio->u.isp24.fcp_hdr.s_id); + spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); if (!sess) { qlt_send_term_exchange(vha, NULL, atio, 1); return 0; @@ -4916,6 +5136,7 @@ static int __qlt_send_busy(struct scsi_qla_host *vha, return -ENOMEM; } + vha->tgt_counters.num_q_full_sent++; pkt->entry_count = 1; pkt->handle = QLA_TGT_SKIP_HANDLE | CTIO_COMPLETION_HANDLE_MARK; @@ -5129,11 +5350,12 @@ qlt_chk_qfull_thresh_hold(struct scsi_qla_host *vha, /* ha->hardware_lock supposed to be held on entry */ /* called via callback from qla2xxx */ static void qlt_24xx_atio_pkt(struct scsi_qla_host *vha, - struct atio_from_isp *atio) + struct atio_from_isp *atio, uint8_t ha_locked) { struct qla_hw_data *ha = vha->hw; struct qla_tgt *tgt = vha->vha_tgt.qla_tgt; int rc; + unsigned long flags; if (unlikely(tgt == NULL)) { ql_dbg(ql_dbg_io, vha, 0x3064, @@ -5145,7 +5367,7 @@ static void qlt_24xx_atio_pkt(struct scsi_qla_host *vha, * Otherwise, some commands can stuck. */ - tgt->irq_cmd_count++; + tgt->atio_irq_cmd_count++; switch (atio->u.raw.entry_type) { case ATIO_TYPE7: @@ -5155,7 +5377,11 @@ static void qlt_24xx_atio_pkt(struct scsi_qla_host *vha, "qla_target(%d): ATIO_TYPE7 " "received with UNKNOWN exchange address, " "sending QUEUE_FULL\n", vha->vp_idx); + if (!ha_locked) + spin_lock_irqsave(&ha->hardware_lock, flags); qlt_send_busy(vha, atio, SAM_STAT_TASK_SET_FULL); + if (!ha_locked) + spin_unlock_irqrestore(&ha->hardware_lock, flags); break; } @@ -5164,7 +5390,7 @@ static void qlt_24xx_atio_pkt(struct scsi_qla_host *vha, if (likely(atio->u.isp24.fcp_cmnd.task_mgmt_flags == 0)) { rc = qlt_chk_qfull_thresh_hold(vha, atio); if (rc != 0) { - tgt->irq_cmd_count--; + tgt->atio_irq_cmd_count--; return; } rc = qlt_handle_cmd_for_atio(vha, atio); @@ -5173,11 +5399,20 @@ static void qlt_24xx_atio_pkt(struct scsi_qla_host *vha, } if (unlikely(rc != 0)) { if (rc == -ESRCH) { + if (!ha_locked) + spin_lock_irqsave + (&ha->hardware_lock, flags); + #if 1 /* With TERM EXCHANGE some FC cards refuse to boot */ qlt_send_busy(vha, atio, SAM_STAT_BUSY); #else qlt_send_term_exchange(vha, NULL, atio, 1); #endif + + if (!ha_locked) + spin_unlock_irqrestore + (&ha->hardware_lock, flags); + } else { if (tgt->tgt_stop) { ql_dbg(ql_dbg_tgt, vha, 0xe059, @@ -5189,7 +5424,13 @@ static void qlt_24xx_atio_pkt(struct scsi_qla_host *vha, "qla_target(%d): Unable to send " "command to target, sending BUSY " "status.\n", vha->vp_idx); + if (!ha_locked) + spin_lock_irqsave( + &ha->hardware_lock, flags); qlt_send_busy(vha, atio, SAM_STAT_BUSY); + if (!ha_locked) + spin_unlock_irqrestore( + &ha->hardware_lock, flags); } } } @@ -5206,7 +5447,12 @@ static void qlt_24xx_atio_pkt(struct scsi_qla_host *vha, break; } ql_dbg(ql_dbg_tgt, vha, 0xe02e, "%s", "IMMED_NOTIFY ATIO"); + + if (!ha_locked) + spin_lock_irqsave(&ha->hardware_lock, flags); qlt_handle_imm_notify(vha, (struct imm_ntfy_from_isp *)atio); + if (!ha_locked) + spin_unlock_irqrestore(&ha->hardware_lock, flags); break; } @@ -5217,7 +5463,7 @@ static void qlt_24xx_atio_pkt(struct scsi_qla_host *vha, break; } - tgt->irq_cmd_count--; + tgt->atio_irq_cmd_count--; } /* ha->hardware_lock supposed to be held on entry */ @@ -5534,12 +5780,16 @@ static struct qla_tgt_sess *qlt_make_local_sess(struct scsi_qla_host *vha, int rc, global_resets; uint16_t loop_id = 0; + mutex_lock(&vha->vha_tgt.tgt_mutex); + retry: global_resets = atomic_read(&vha->vha_tgt.qla_tgt->tgt_global_resets_count); rc = qla24xx_get_loop_id(vha, s_id, &loop_id); if (rc != 0) { + mutex_unlock(&vha->vha_tgt.tgt_mutex); + if ((s_id[0] == 0xFF) && (s_id[1] == 0xFC)) { /* @@ -5550,17 +5800,27 @@ retry: "Unable to find initiator with S_ID %x:%x:%x", s_id[0], s_id[1], s_id[2]); } else - ql_dbg(ql_dbg_tgt_mgt, vha, 0xf071, + ql_log(ql_log_info, vha, 0xf071, "qla_target(%d): Unable to find " "initiator with S_ID %x:%x:%x", vha->vp_idx, s_id[0], s_id[1], s_id[2]); + + if (rc == -ENOENT) { + qlt_port_logo_t logo; + sid_to_portid(s_id, &logo.id); + logo.cmd_count = 1; + qlt_send_first_logo(vha, &logo); + } + return NULL; } fcport = qlt_get_port_database(vha, loop_id); - if (!fcport) + if (!fcport) { + mutex_unlock(&vha->vha_tgt.tgt_mutex); return NULL; + } if (global_resets != atomic_read(&vha->vha_tgt.qla_tgt->tgt_global_resets_count)) { @@ -5575,6 +5835,8 @@ retry: sess = qlt_create_sess(vha, fcport, true); + mutex_unlock(&vha->vha_tgt.tgt_mutex); + kfree(fcport); return sess; } @@ -5585,15 +5847,15 @@ static void qlt_abort_work(struct qla_tgt *tgt, struct scsi_qla_host *vha = tgt->vha; struct qla_hw_data *ha = vha->hw; struct qla_tgt_sess *sess = NULL; - unsigned long flags; + unsigned long flags = 0, flags2 = 0; uint32_t be_s_id; uint8_t s_id[3]; int rc; - spin_lock_irqsave(&ha->hardware_lock, flags); + spin_lock_irqsave(&ha->tgt.sess_lock, flags2); if (tgt->tgt_stop) - goto out_term; + goto out_term2; s_id[0] = prm->abts.fcp_hdr_le.s_id[2]; s_id[1] = prm->abts.fcp_hdr_le.s_id[1]; @@ -5602,41 +5864,47 @@ static void qlt_abort_work(struct qla_tgt *tgt, sess = ha->tgt.tgt_ops->find_sess_by_s_id(vha, (unsigned char *)&be_s_id); if (!sess) { - spin_unlock_irqrestore(&ha->hardware_lock, flags); + spin_unlock_irqrestore(&ha->tgt.sess_lock, flags2); - mutex_lock(&vha->vha_tgt.tgt_mutex); sess = qlt_make_local_sess(vha, s_id); /* sess has got an extra creation ref */ - mutex_unlock(&vha->vha_tgt.tgt_mutex); - spin_lock_irqsave(&ha->hardware_lock, flags); + spin_lock_irqsave(&ha->tgt.sess_lock, flags2); if (!sess) - goto out_term; + goto out_term2; } else { if (sess->deleted == QLA_SESS_DELETION_IN_PROGRESS) { sess = NULL; - goto out_term; + goto out_term2; } kref_get(&sess->se_sess->sess_kref); } + spin_lock_irqsave(&ha->hardware_lock, flags); + if (tgt->tgt_stop) goto out_term; rc = __qlt_24xx_handle_abts(vha, &prm->abts, sess); if (rc != 0) goto out_term; + spin_unlock_irqrestore(&ha->hardware_lock, flags); ha->tgt.tgt_ops->put_sess(sess); - spin_unlock_irqrestore(&ha->hardware_lock, flags); + spin_unlock_irqrestore(&ha->tgt.sess_lock, flags2); return; +out_term2: + spin_lock_irqsave(&ha->hardware_lock, flags); + out_term: qlt_24xx_send_abts_resp(vha, &prm->abts, FCP_TMF_REJECTED, false); + spin_unlock_irqrestore(&ha->hardware_lock, flags); + if (sess) ha->tgt.tgt_ops->put_sess(sess); - spin_unlock_irqrestore(&ha->hardware_lock, flags); + spin_unlock_irqrestore(&ha->tgt.sess_lock, flags2); } static void qlt_tmr_work(struct qla_tgt *tgt, @@ -5653,7 +5921,7 @@ static void qlt_tmr_work(struct qla_tgt *tgt, int fn; void *iocb; - spin_lock_irqsave(&ha->hardware_lock, flags); + spin_lock_irqsave(&ha->tgt.sess_lock, flags); if (tgt->tgt_stop) goto out_term; @@ -5661,14 +5929,12 @@ static void qlt_tmr_work(struct qla_tgt *tgt, s_id = prm->tm_iocb2.u.isp24.fcp_hdr.s_id; sess = ha->tgt.tgt_ops->find_sess_by_s_id(vha, s_id); if (!sess) { - spin_unlock_irqrestore(&ha->hardware_lock, flags); + spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); - mutex_lock(&vha->vha_tgt.tgt_mutex); sess = qlt_make_local_sess(vha, s_id); /* sess has got an extra creation ref */ - mutex_unlock(&vha->vha_tgt.tgt_mutex); - spin_lock_irqsave(&ha->hardware_lock, flags); + spin_lock_irqsave(&ha->tgt.sess_lock, flags); if (!sess) goto out_term; } else { @@ -5690,14 +5956,14 @@ static void qlt_tmr_work(struct qla_tgt *tgt, goto out_term; ha->tgt.tgt_ops->put_sess(sess); - spin_unlock_irqrestore(&ha->hardware_lock, flags); + spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); return; out_term: - qlt_send_term_exchange(vha, NULL, &prm->tm_iocb2, 1); + qlt_send_term_exchange(vha, NULL, &prm->tm_iocb2, 0); if (sess) ha->tgt.tgt_ops->put_sess(sess); - spin_unlock_irqrestore(&ha->hardware_lock, flags); + spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); } static void qlt_sess_work_fn(struct work_struct *work) @@ -6002,6 +6268,7 @@ qlt_enable_vha(struct scsi_qla_host *vha) struct qla_tgt *tgt = vha->vha_tgt.qla_tgt; unsigned long flags; scsi_qla_host_t *base_vha = pci_get_drvdata(ha->pdev); + int rspq_ent = QLA83XX_RSPQ_MSIX_ENTRY_NUMBER; if (!tgt) { ql_dbg(ql_dbg_tgt, vha, 0xe069, @@ -6020,6 +6287,17 @@ qlt_enable_vha(struct scsi_qla_host *vha) qla24xx_disable_vp(vha); qla24xx_enable_vp(vha); } else { + if (ha->msix_entries) { + ql_dbg(ql_dbg_tgt, vha, 0xffff, + "%s: host%ld : vector %d cpu %d\n", + __func__, vha->host_no, + ha->msix_entries[rspq_ent].vector, + ha->msix_entries[rspq_ent].cpuid); + + ha->tgt.rspq_vector_cpuid = + ha->msix_entries[rspq_ent].cpuid; + } + set_bit(ISP_ABORT_NEEDED, &base_vha->dpc_flags); qla2xxx_wake_dpc(base_vha); qla2x00_wait_for_hba_online(base_vha); @@ -6131,7 +6409,7 @@ qlt_init_atio_q_entries(struct scsi_qla_host *vha) * @ha: SCSI driver HA context */ void -qlt_24xx_process_atio_queue(struct scsi_qla_host *vha) +qlt_24xx_process_atio_queue(struct scsi_qla_host *vha, uint8_t ha_locked) { struct qla_hw_data *ha = vha->hw; struct atio_from_isp *pkt; @@ -6144,7 +6422,8 @@ qlt_24xx_process_atio_queue(struct scsi_qla_host *vha) pkt = (struct atio_from_isp *)ha->tgt.atio_ring_ptr; cnt = pkt->u.raw.entry_count; - qlt_24xx_atio_pkt_all_vps(vha, (struct atio_from_isp *)pkt); + qlt_24xx_atio_pkt_all_vps(vha, (struct atio_from_isp *)pkt, + ha_locked); for (i = 0; i < cnt; i++) { ha->tgt.atio_ring_index++; @@ -6265,10 +6544,21 @@ qlt_24xx_config_nvram_stage2(struct scsi_qla_host *vha, { struct qla_hw_data *ha = vha->hw; + if (!QLA_TGT_MODE_ENABLED()) + return; + if (ha->tgt.node_name_set) { memcpy(icb->node_name, ha->tgt.tgt_node_name, WWN_SIZE); icb->firmware_options_1 |= cpu_to_le32(BIT_14); } + + /* disable ZIO at start time. */ + if (!vha->flags.init_done) { + uint32_t tmp; + tmp = le32_to_cpu(icb->firmware_options_2); + tmp &= ~(BIT_3 | BIT_2 | BIT_1 | BIT_0); + icb->firmware_options_2 = cpu_to_le32(tmp); + } } void @@ -6359,6 +6649,15 @@ qlt_81xx_config_nvram_stage2(struct scsi_qla_host *vha, memcpy(icb->node_name, ha->tgt.tgt_node_name, WWN_SIZE); icb->firmware_options_1 |= cpu_to_le32(BIT_14); } + + /* disable ZIO at start time. */ + if (!vha->flags.init_done) { + uint32_t tmp; + tmp = le32_to_cpu(icb->firmware_options_2); + tmp &= ~(BIT_3 | BIT_2 | BIT_1 | BIT_0); + icb->firmware_options_2 = cpu_to_le32(tmp); + } + } void @@ -6428,16 +6727,59 @@ qla83xx_msix_atio_q(int irq, void *dev_id) ha = rsp->hw; vha = pci_get_drvdata(ha->pdev); - spin_lock_irqsave(&ha->hardware_lock, flags); + spin_lock_irqsave(&ha->tgt.atio_lock, flags); - qlt_24xx_process_atio_queue(vha); - qla24xx_process_response_queue(vha, rsp); + qlt_24xx_process_atio_queue(vha, 0); - spin_unlock_irqrestore(&ha->hardware_lock, flags); + spin_unlock_irqrestore(&ha->tgt.atio_lock, flags); return IRQ_HANDLED; } +static void +qlt_handle_abts_recv_work(struct work_struct *work) +{ + struct qla_tgt_sess_op *op = container_of(work, + struct qla_tgt_sess_op, work); + scsi_qla_host_t *vha = op->vha; + struct qla_hw_data *ha = vha->hw; + unsigned long flags; + + if (qla2x00_reset_active(vha) || (op->chip_reset != ha->chip_reset)) + return; + + spin_lock_irqsave(&ha->tgt.atio_lock, flags); + qlt_24xx_process_atio_queue(vha, 0); + spin_unlock_irqrestore(&ha->tgt.atio_lock, flags); + + spin_lock_irqsave(&ha->hardware_lock, flags); + qlt_response_pkt_all_vps(vha, (response_t *)&op->atio); + spin_unlock_irqrestore(&ha->hardware_lock, flags); +} + +void +qlt_handle_abts_recv(struct scsi_qla_host *vha, response_t *pkt) +{ + struct qla_tgt_sess_op *op; + + op = kzalloc(sizeof(*op), GFP_ATOMIC); + + if (!op) { + /* do not reach for ATIO queue here. This is best effort err + * recovery at this point. + */ + qlt_response_pkt_all_vps(vha, pkt); + return; + } + + memcpy(&op->atio, pkt, sizeof(*pkt)); + op->vha = vha; + op->chip_reset = vha->hw->chip_reset; + INIT_WORK(&op->work, qlt_handle_abts_recv_work); + queue_work(qla_tgt_wq, &op->work); + return; +} + int qlt_mem_alloc(struct qla_hw_data *ha) { @@ -6532,13 +6874,25 @@ int __init qlt_init(void) return -ENOMEM; } + qla_tgt_plogi_cachep = kmem_cache_create("qla_tgt_plogi_cachep", + sizeof(qlt_plogi_ack_t), + __alignof__(qlt_plogi_ack_t), + 0, NULL); + + if (!qla_tgt_plogi_cachep) { + ql_log(ql_log_fatal, NULL, 0xe06d, + "kmem_cache_create for qla_tgt_plogi_cachep failed\n"); + ret = -ENOMEM; + goto out_mgmt_cmd_cachep; + } + qla_tgt_mgmt_cmd_mempool = mempool_create(25, mempool_alloc_slab, mempool_free_slab, qla_tgt_mgmt_cmd_cachep); if (!qla_tgt_mgmt_cmd_mempool) { ql_log(ql_log_fatal, NULL, 0xe06e, "mempool_create for qla_tgt_mgmt_cmd_mempool failed\n"); ret = -ENOMEM; - goto out_mgmt_cmd_cachep; + goto out_plogi_cachep; } qla_tgt_wq = alloc_workqueue("qla_tgt_wq", 0, 0); @@ -6555,6 +6909,8 @@ int __init qlt_init(void) out_cmd_mempool: mempool_destroy(qla_tgt_mgmt_cmd_mempool); +out_plogi_cachep: + kmem_cache_destroy(qla_tgt_plogi_cachep); out_mgmt_cmd_cachep: kmem_cache_destroy(qla_tgt_mgmt_cmd_cachep); return ret; @@ -6567,5 +6923,6 @@ void qlt_exit(void) destroy_workqueue(qla_tgt_wq); mempool_destroy(qla_tgt_mgmt_cmd_mempool); + kmem_cache_destroy(qla_tgt_plogi_cachep); kmem_cache_destroy(qla_tgt_mgmt_cmd_cachep); } diff --git a/drivers/scsi/qla2xxx/qla_target.h b/drivers/scsi/qla2xxx/qla_target.h index bca584ae45b7..71b2865ba3c8 100644 --- a/drivers/scsi/qla2xxx/qla_target.h +++ b/drivers/scsi/qla2xxx/qla_target.h @@ -787,7 +787,7 @@ int qla2x00_wait_for_hba_online(struct scsi_qla_host *); #define QLA_TGT_STATE_NEED_DATA 1 /* target needs data to continue */ #define QLA_TGT_STATE_DATA_IN 2 /* Data arrived + target processing */ #define QLA_TGT_STATE_PROCESSED 3 /* target done processing */ -#define QLA_TGT_STATE_ABORTED 4 /* Command aborted */ + /* Special handles */ #define QLA_TGT_NULL_HANDLE 0 @@ -835,6 +835,7 @@ struct qla_tgt { * HW lock. */ int irq_cmd_count; + int atio_irq_cmd_count; int datasegs_per_cmd, datasegs_per_cont, sg_tablesize; @@ -883,6 +884,7 @@ struct qla_tgt { struct qla_tgt_sess_op { struct scsi_qla_host *vha; + uint32_t chip_reset; struct atio_from_isp atio; struct work_struct work; struct list_head cmd_list; @@ -896,6 +898,19 @@ enum qla_sess_deletion { QLA_SESS_DELETION_IN_PROGRESS = 2, }; +typedef enum { + QLT_PLOGI_LINK_SAME_WWN, + QLT_PLOGI_LINK_CONFLICT, + QLT_PLOGI_LINK_MAX +} qlt_plogi_link_t; + +typedef struct { + struct list_head list; + struct imm_ntfy_from_isp iocb; + port_id_t id; + int ref_count; +} qlt_plogi_ack_t; + /* * Equivilant to IT Nexus (Initiator-Target) */ @@ -907,8 +922,8 @@ struct qla_tgt_sess { unsigned int deleted:2; unsigned int local:1; unsigned int logout_on_delete:1; - unsigned int plogi_ack_needed:1; unsigned int keep_nport_handle:1; + unsigned int send_els_logo:1; unsigned char logout_completed; @@ -925,9 +940,7 @@ struct qla_tgt_sess { uint8_t port_name[WWN_SIZE]; struct work_struct free_work; - union { - struct imm_ntfy_from_isp tm_iocb; - }; + qlt_plogi_ack_t *plogi_link[QLT_PLOGI_LINK_MAX]; }; struct qla_tgt_cmd { @@ -949,6 +962,7 @@ struct qla_tgt_cmd { unsigned int term_exchg:1; unsigned int cmd_sent_to_fw:1; unsigned int cmd_in_wq:1; + unsigned int aborted:1; struct scatterlist *sg; /* cmd data buffer SG vector */ int sg_cnt; /* SG segments count */ @@ -1120,6 +1134,14 @@ static inline uint32_t sid_to_key(const uint8_t *s_id) return key; } +static inline void sid_to_portid(const uint8_t *s_id, port_id_t *p) +{ + memset(p, 0, sizeof(*p)); + p->b.domain = s_id[0]; + p->b.area = s_id[1]; + p->b.al_pa = s_id[2]; +} + /* * Exported symbols from qla_target.c LLD logic used by qla2xxx code.. */ @@ -1135,7 +1157,7 @@ extern void qlt_enable_vha(struct scsi_qla_host *); extern void qlt_vport_create(struct scsi_qla_host *, struct qla_hw_data *); extern void qlt_rff_id(struct scsi_qla_host *, struct ct_sns_req *); extern void qlt_init_atio_q_entries(struct scsi_qla_host *); -extern void qlt_24xx_process_atio_queue(struct scsi_qla_host *); +extern void qlt_24xx_process_atio_queue(struct scsi_qla_host *, uint8_t); extern void qlt_24xx_config_rings(struct scsi_qla_host *); extern void qlt_24xx_config_nvram_stage1(struct scsi_qla_host *, struct nvram_24xx *); diff --git a/drivers/scsi/qla2xxx/tcm_qla2xxx.c b/drivers/scsi/qla2xxx/tcm_qla2xxx.c index 81af294f15a7..faf0a126627f 100644 --- a/drivers/scsi/qla2xxx/tcm_qla2xxx.c +++ b/drivers/scsi/qla2xxx/tcm_qla2xxx.c @@ -284,6 +284,7 @@ static void tcm_qla2xxx_complete_free(struct work_struct *work) WARN_ON(cmd->cmd_flags & BIT_16); + cmd->vha->tgt_counters.qla_core_ret_sta_ctio++; cmd->cmd_flags |= BIT_16; transport_generic_free_cmd(&cmd->se_cmd, 0); } @@ -295,9 +296,10 @@ static void tcm_qla2xxx_complete_free(struct work_struct *work) */ static void tcm_qla2xxx_free_cmd(struct qla_tgt_cmd *cmd) { + cmd->vha->tgt_counters.core_qla_free_cmd++; cmd->cmd_in_wq = 1; INIT_WORK(&cmd->work, tcm_qla2xxx_complete_free); - queue_work(tcm_qla2xxx_free_wq, &cmd->work); + queue_work_on(smp_processor_id(), tcm_qla2xxx_free_wq, &cmd->work); } /* @@ -342,9 +344,9 @@ static int tcm_qla2xxx_shutdown_session(struct se_session *se_sess) BUG_ON(!sess); vha = sess->vha; - spin_lock_irqsave(&vha->hw->hardware_lock, flags); + spin_lock_irqsave(&vha->hw->tgt.sess_lock, flags); target_sess_cmd_list_set_waiting(se_sess); - spin_unlock_irqrestore(&vha->hw->hardware_lock, flags); + spin_unlock_irqrestore(&vha->hw->tgt.sess_lock, flags); return 1; } @@ -358,9 +360,9 @@ static void tcm_qla2xxx_close_session(struct se_session *se_sess) BUG_ON(!sess); vha = sess->vha; - spin_lock_irqsave(&vha->hw->hardware_lock, flags); + spin_lock_irqsave(&vha->hw->tgt.sess_lock, flags); qlt_unreg_sess(sess); - spin_unlock_irqrestore(&vha->hw->hardware_lock, flags); + spin_unlock_irqrestore(&vha->hw->tgt.sess_lock, flags); } static u32 tcm_qla2xxx_sess_get_index(struct se_session *se_sess) @@ -454,6 +456,7 @@ static int tcm_qla2xxx_handle_cmd(scsi_qla_host_t *vha, struct qla_tgt_cmd *cmd, return -EINVAL; } + cmd->vha->tgt_counters.qla_core_sbt_cmd++; return target_submit_cmd(se_cmd, se_sess, cdb, &cmd->sense_buffer[0], cmd->unpacked_lun, data_length, fcp_task_attr, data_dir, flags); @@ -469,6 +472,7 @@ static void tcm_qla2xxx_handle_data_work(struct work_struct *work) */ cmd->cmd_in_wq = 0; cmd->cmd_flags |= BIT_11; + cmd->vha->tgt_counters.qla_core_ret_ctio++; if (!cmd->write_data_transferred) { /* * Check if se_cmd has already been aborted via LUN_RESET, and @@ -500,7 +504,7 @@ static void tcm_qla2xxx_handle_data(struct qla_tgt_cmd *cmd) cmd->cmd_flags |= BIT_10; cmd->cmd_in_wq = 1; INIT_WORK(&cmd->work, tcm_qla2xxx_handle_data_work); - queue_work(tcm_qla2xxx_free_wq, &cmd->work); + queue_work_on(smp_processor_id(), tcm_qla2xxx_free_wq, &cmd->work); } static void tcm_qla2xxx_handle_dif_work(struct work_struct *work) @@ -643,7 +647,7 @@ static void tcm_qla2xxx_aborted_task(struct se_cmd *se_cmd) static void tcm_qla2xxx_clear_sess_lookup(struct tcm_qla2xxx_lport *, struct tcm_qla2xxx_nacl *, struct qla_tgt_sess *); /* - * Expected to be called with struct qla_hw_data->hardware_lock held + * Expected to be called with struct qla_hw_data->tgt.sess_lock held */ static void tcm_qla2xxx_clear_nacl_from_fcport_map(struct qla_tgt_sess *sess) { @@ -697,13 +701,13 @@ static void tcm_qla2xxx_put_sess(struct qla_tgt_sess *sess) if (!sess) return; - assert_spin_locked(&sess->vha->hw->hardware_lock); + assert_spin_locked(&sess->vha->hw->tgt.sess_lock); kref_put(&sess->se_sess->sess_kref, tcm_qla2xxx_release_session); } static void tcm_qla2xxx_shutdown_sess(struct qla_tgt_sess *sess) { - assert_spin_locked(&sess->vha->hw->hardware_lock); + assert_spin_locked(&sess->vha->hw->tgt.sess_lock); target_sess_cmd_list_set_waiting(sess->se_sess); } @@ -1077,7 +1081,7 @@ static struct se_portal_group *tcm_qla2xxx_npiv_make_tpg( } /* - * Expected to be called with struct qla_hw_data->hardware_lock held + * Expected to be called with struct qla_hw_data->tgt.sess_lock held */ static struct qla_tgt_sess *tcm_qla2xxx_find_sess_by_s_id( scsi_qla_host_t *vha, @@ -1116,7 +1120,7 @@ static struct qla_tgt_sess *tcm_qla2xxx_find_sess_by_s_id( } /* - * Expected to be called with struct qla_hw_data->hardware_lock held + * Expected to be called with struct qla_hw_data->tgt.sess_lock held */ static void tcm_qla2xxx_set_sess_by_s_id( struct tcm_qla2xxx_lport *lport, @@ -1182,7 +1186,7 @@ static void tcm_qla2xxx_set_sess_by_s_id( } /* - * Expected to be called with struct qla_hw_data->hardware_lock held + * Expected to be called with struct qla_hw_data->tgt.sess_lock held */ static struct qla_tgt_sess *tcm_qla2xxx_find_sess_by_loop_id( scsi_qla_host_t *vha, @@ -1221,7 +1225,7 @@ static struct qla_tgt_sess *tcm_qla2xxx_find_sess_by_loop_id( } /* - * Expected to be called with struct qla_hw_data->hardware_lock held + * Expected to be called with struct qla_hw_data->tgt.sess_lock held */ static void tcm_qla2xxx_set_sess_by_loop_id( struct tcm_qla2xxx_lport *lport, @@ -1285,7 +1289,7 @@ static void tcm_qla2xxx_set_sess_by_loop_id( } /* - * Should always be called with qla_hw_data->hardware_lock held. + * Should always be called with qla_hw_data->tgt.sess_lock held. */ static void tcm_qla2xxx_clear_sess_lookup(struct tcm_qla2xxx_lport *lport, struct tcm_qla2xxx_nacl *nacl, struct qla_tgt_sess *sess) @@ -1353,7 +1357,7 @@ static int tcm_qla2xxx_check_initiator_node_acl( struct qla_tgt_sess *sess = qla_tgt_sess; unsigned char port_name[36]; unsigned long flags; - int num_tags = (ha->fw_xcb_count) ? ha->fw_xcb_count : + int num_tags = (ha->cur_fw_xcb_count) ? ha->cur_fw_xcb_count : TCM_QLA2XXX_DEFAULT_TAGS; lport = vha->vha_tgt.target_lport_ptr; @@ -1401,12 +1405,12 @@ static int tcm_qla2xxx_check_initiator_node_acl( * And now setup the new se_nacl and session pointers into our HW lport * mappings for fabric S_ID and LOOP_ID. */ - spin_lock_irqsave(&ha->hardware_lock, flags); + spin_lock_irqsave(&ha->tgt.sess_lock, flags); tcm_qla2xxx_set_sess_by_s_id(lport, se_nacl, nacl, se_sess, qla_tgt_sess, s_id); tcm_qla2xxx_set_sess_by_loop_id(lport, se_nacl, nacl, se_sess, qla_tgt_sess, loop_id); - spin_unlock_irqrestore(&ha->hardware_lock, flags); + spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); /* * Finally register the new FC Nexus with TCM */ diff --git a/drivers/target/iscsi/iscsi_target.c b/drivers/target/iscsi/iscsi_target.c index 72204fbf2bb1..576a7a43470c 100644 --- a/drivers/target/iscsi/iscsi_target.c +++ b/drivers/target/iscsi/iscsi_target.c @@ -1333,7 +1333,7 @@ iscsit_check_dataout_hdr(struct iscsi_conn *conn, unsigned char *buf, /* * Check if a delayed TASK_ABORTED status needs to * be sent now if the ISCSI_FLAG_CMD_FINAL has been - * received with the unsolicitied data out. + * received with the unsolicited data out. */ if (hdr->flags & ISCSI_FLAG_CMD_FINAL) iscsit_stop_dataout_timer(cmd); @@ -3435,7 +3435,7 @@ iscsit_build_sendtargets_response(struct iscsi_cmd *cmd, if ((tpg->tpg_attrib.generate_node_acls == 0) && (tpg->tpg_attrib.demo_mode_discovery == 0) && - (!core_tpg_get_initiator_node_acl(&tpg->tpg_se_tpg, + (!target_tpg_has_node_acl(&tpg->tpg_se_tpg, cmd->conn->sess->sess_ops->InitiatorName))) { continue; } @@ -4459,9 +4459,6 @@ int iscsit_close_connection( return 0; } - spin_unlock_bh(&sess->conn_lock); - - return 0; } int iscsit_close_session(struct iscsi_session *sess) diff --git a/drivers/target/iscsi/iscsi_target_configfs.c b/drivers/target/iscsi/iscsi_target_configfs.c index 255204cc43e6..2f821de63049 100644 --- a/drivers/target/iscsi/iscsi_target_configfs.c +++ b/drivers/target/iscsi/iscsi_target_configfs.c @@ -725,11 +725,8 @@ static ssize_t lio_target_nacl_cmdsn_depth_store(struct config_item *item, if (iscsit_get_tpg(tpg) < 0) return -EINVAL; - /* - * iscsit_tpg_set_initiator_node_queue_depth() assumes force=1 - */ - ret = iscsit_tpg_set_initiator_node_queue_depth(tpg, - config_item_name(acl_ci), cmdsn_depth, 1); + + ret = core_tpg_set_initiator_node_queue_depth(se_nacl, cmdsn_depth); pr_debug("LIO_Target_ConfigFS: %s/%s Set CmdSN Window: %u for" "InitiatorName: %s\n", config_item_name(wwn_ci), @@ -1593,28 +1590,30 @@ static int lio_tpg_check_prot_fabric_only( } /* - * Called with spin_lock_bh(struct se_portal_group->session_lock) held.. - * - * Also, this function calls iscsit_inc_session_usage_count() on the + * This function calls iscsit_inc_session_usage_count() on the * struct iscsi_session in question. */ static int lio_tpg_shutdown_session(struct se_session *se_sess) { struct iscsi_session *sess = se_sess->fabric_sess_ptr; + struct se_portal_group *se_tpg = &sess->tpg->tpg_se_tpg; + spin_lock_bh(&se_tpg->session_lock); spin_lock(&sess->conn_lock); if (atomic_read(&sess->session_fall_back_to_erl0) || atomic_read(&sess->session_logout) || (sess->time2retain_timer_flags & ISCSI_TF_EXPIRED)) { spin_unlock(&sess->conn_lock); + spin_unlock_bh(&se_tpg->session_lock); return 0; } atomic_set(&sess->session_reinstatement, 1); spin_unlock(&sess->conn_lock); iscsit_stop_time2retain_timer(sess); - iscsit_stop_session(sess, 1, 1); + spin_unlock_bh(&se_tpg->session_lock); + iscsit_stop_session(sess, 1, 1); return 1; } diff --git a/drivers/target/iscsi/iscsi_target_erl1.c b/drivers/target/iscsi/iscsi_target_erl1.c index 2e561deb30a2..9214c9dafa2b 100644 --- a/drivers/target/iscsi/iscsi_target_erl1.c +++ b/drivers/target/iscsi/iscsi_target_erl1.c @@ -160,8 +160,7 @@ static int iscsit_handle_r2t_snack( " protocol error.\n", cmd->init_task_tag, begrun, (begrun + runlength), cmd->acked_data_sn); - return iscsit_reject_cmd(cmd, - ISCSI_REASON_PROTOCOL_ERROR, buf); + return iscsit_reject_cmd(cmd, ISCSI_REASON_PROTOCOL_ERROR, buf); } if (runlength) { @@ -628,8 +627,8 @@ int iscsit_dataout_datapduinorder_no_fbit( if (cmd->pdu_list[i].seq_no == pdu->seq_no) { if (!first_pdu) first_pdu = &cmd->pdu_list[i]; - xfer_len += cmd->pdu_list[i].length; - pdu_count++; + xfer_len += cmd->pdu_list[i].length; + pdu_count++; } else if (pdu_count) break; } diff --git a/drivers/target/iscsi/iscsi_target_parameters.c b/drivers/target/iscsi/iscsi_target_parameters.c index 2cbea2af7cd0..3a1f9a7e6bb6 100644 --- a/drivers/target/iscsi/iscsi_target_parameters.c +++ b/drivers/target/iscsi/iscsi_target_parameters.c @@ -1668,7 +1668,7 @@ void iscsi_set_session_parameters( param->value); } else if (!strcmp(param->name, INITIALR2T)) { ops->InitialR2T = !strcmp(param->value, YES); - pr_debug("InitialR2T: %s\n", + pr_debug("InitialR2T: %s\n", param->value); } else if (!strcmp(param->name, IMMEDIATEDATA)) { ops->ImmediateData = !strcmp(param->value, YES); diff --git a/drivers/target/iscsi/iscsi_target_tmr.c b/drivers/target/iscsi/iscsi_target_tmr.c index 11320df939f7..3d637055c36f 100644 --- a/drivers/target/iscsi/iscsi_target_tmr.c +++ b/drivers/target/iscsi/iscsi_target_tmr.c @@ -82,7 +82,7 @@ int iscsit_tmr_task_warm_reset( pr_err("TMR Opcode TARGET_WARM_RESET authorization" " failed for Initiator Node: %s\n", sess->se_sess->se_node_acl->initiatorname); - return -1; + return -1; } /* * Do the real work in transport_generic_do_tmr(). diff --git a/drivers/target/iscsi/iscsi_target_tpg.c b/drivers/target/iscsi/iscsi_target_tpg.c index 23c95cd14167..0814e5894a96 100644 --- a/drivers/target/iscsi/iscsi_target_tpg.c +++ b/drivers/target/iscsi/iscsi_target_tpg.c @@ -590,16 +590,6 @@ int iscsit_tpg_del_network_portal( return iscsit_tpg_release_np(tpg_np, tpg, np); } -int iscsit_tpg_set_initiator_node_queue_depth( - struct iscsi_portal_group *tpg, - unsigned char *initiatorname, - u32 queue_depth, - int force) -{ - return core_tpg_set_initiator_node_queue_depth(&tpg->tpg_se_tpg, - initiatorname, queue_depth, force); -} - int iscsit_ta_authentication(struct iscsi_portal_group *tpg, u32 authentication) { unsigned char buf1[256], buf2[256], *none = NULL; diff --git a/drivers/target/iscsi/iscsi_target_tpg.h b/drivers/target/iscsi/iscsi_target_tpg.h index 9db32bd24cd4..2da211920c18 100644 --- a/drivers/target/iscsi/iscsi_target_tpg.h +++ b/drivers/target/iscsi/iscsi_target_tpg.h @@ -26,8 +26,6 @@ extern struct iscsi_tpg_np *iscsit_tpg_add_network_portal(struct iscsi_portal_gr int); extern int iscsit_tpg_del_network_portal(struct iscsi_portal_group *, struct iscsi_tpg_np *); -extern int iscsit_tpg_set_initiator_node_queue_depth(struct iscsi_portal_group *, - unsigned char *, u32, int); extern int iscsit_ta_authentication(struct iscsi_portal_group *, u32); extern int iscsit_ta_login_timeout(struct iscsi_portal_group *, u32); extern int iscsit_ta_netif_timeout(struct iscsi_portal_group *, u32); diff --git a/drivers/target/loopback/tcm_loop.c b/drivers/target/loopback/tcm_loop.c index 4fb0eca86857..d41a5c300e31 100644 --- a/drivers/target/loopback/tcm_loop.c +++ b/drivers/target/loopback/tcm_loop.c @@ -1036,12 +1036,26 @@ static ssize_t tcm_loop_tpg_transport_status_store(struct config_item *item, return -EINVAL; } +static ssize_t tcm_loop_tpg_address_show(struct config_item *item, + char *page) +{ + struct se_portal_group *se_tpg = to_tpg(item); + struct tcm_loop_tpg *tl_tpg = container_of(se_tpg, + struct tcm_loop_tpg, tl_se_tpg); + struct tcm_loop_hba *tl_hba = tl_tpg->tl_hba; + + return snprintf(page, PAGE_SIZE, "%d:0:%d\n", + tl_hba->sh->host_no, tl_tpg->tl_tpgt); +} + CONFIGFS_ATTR(tcm_loop_tpg_, nexus); CONFIGFS_ATTR(tcm_loop_tpg_, transport_status); +CONFIGFS_ATTR_RO(tcm_loop_tpg_, address); static struct configfs_attribute *tcm_loop_tpg_attrs[] = { &tcm_loop_tpg_attr_nexus, &tcm_loop_tpg_attr_transport_status, + &tcm_loop_tpg_attr_address, NULL, }; diff --git a/drivers/target/sbp/sbp_target.c b/drivers/target/sbp/sbp_target.c index 35f7d31b29d2..3072f1aca8ec 100644 --- a/drivers/target/sbp/sbp_target.c +++ b/drivers/target/sbp/sbp_target.c @@ -39,8 +39,6 @@ #include "sbp_target.h" -static const struct target_core_fabric_ops sbp_ops; - /* FireWire address region for management and command block address handlers */ static const struct fw_address_region sbp_register_region = { .start = CSR_REGISTER_BASE + 0x10000, diff --git a/drivers/target/target_core_configfs.c b/drivers/target/target_core_configfs.c index b9b9ffde4c7a..3327c49674d3 100644 --- a/drivers/target/target_core_configfs.c +++ b/drivers/target/target_core_configfs.c @@ -278,7 +278,7 @@ EXPORT_SYMBOL(target_depend_item); void target_undepend_item(struct config_item *item) { - return configfs_undepend_item(&target_core_fabrics, item); + return configfs_undepend_item(item); } EXPORT_SYMBOL(target_undepend_item); @@ -499,6 +499,7 @@ DEF_CONFIGFS_ATTRIB_SHOW(max_unmap_lba_count); DEF_CONFIGFS_ATTRIB_SHOW(max_unmap_block_desc_count); DEF_CONFIGFS_ATTRIB_SHOW(unmap_granularity); DEF_CONFIGFS_ATTRIB_SHOW(unmap_granularity_alignment); +DEF_CONFIGFS_ATTRIB_SHOW(unmap_zeroes_data); DEF_CONFIGFS_ATTRIB_SHOW(max_write_same_len); #define DEF_CONFIGFS_ATTRIB_STORE_U32(_name) \ @@ -548,7 +549,8 @@ static ssize_t _name##_store(struct config_item *item, const char *page,\ size_t count) \ { \ printk_once(KERN_WARNING \ - "ignoring deprecated ##_name## attribute\n"); \ + "ignoring deprecated %s attribute\n", \ + __stringify(_name)); \ return count; \ } @@ -866,6 +868,39 @@ static ssize_t emulate_rest_reord_store(struct config_item *item, return count; } +static ssize_t unmap_zeroes_data_store(struct config_item *item, + const char *page, size_t count) +{ + struct se_dev_attrib *da = to_attrib(item); + bool flag; + int ret; + + ret = strtobool(page, &flag); + if (ret < 0) + return ret; + + if (da->da_dev->export_count) { + pr_err("dev[%p]: Unable to change SE Device" + " unmap_zeroes_data while export_count is %d\n", + da->da_dev, da->da_dev->export_count); + return -EINVAL; + } + /* + * We expect this value to be non-zero when generic Block Layer + * Discard supported is detected iblock_configure_device(). + */ + if (flag && !da->max_unmap_block_desc_count) { + pr_err("dev[%p]: Thin Provisioning LBPRZ will not be set" + " because max_unmap_block_desc_count is zero\n", + da->da_dev); + return -ENOSYS; + } + da->unmap_zeroes_data = flag; + pr_debug("dev[%p]: SE Device Thin Provisioning LBPRZ bit: %d\n", + da->da_dev, flag); + return 0; +} + /* * Note, this can only be called on unexported SE Device Object. */ @@ -998,6 +1033,7 @@ CONFIGFS_ATTR(, max_unmap_lba_count); CONFIGFS_ATTR(, max_unmap_block_desc_count); CONFIGFS_ATTR(, unmap_granularity); CONFIGFS_ATTR(, unmap_granularity_alignment); +CONFIGFS_ATTR(, unmap_zeroes_data); CONFIGFS_ATTR(, max_write_same_len); /* @@ -1034,6 +1070,7 @@ struct configfs_attribute *sbc_attrib_attrs[] = { &attr_max_unmap_block_desc_count, &attr_unmap_granularity, &attr_unmap_granularity_alignment, + &attr_unmap_zeroes_data, &attr_max_write_same_len, NULL, }; @@ -1980,14 +2017,14 @@ static ssize_t target_dev_lba_map_store(struct config_item *item, struct se_device *dev = to_device(item); struct t10_alua_lba_map *lba_map = NULL; struct list_head lba_list; - char *map_entries, *ptr; + char *map_entries, *orig, *ptr; char state; int pg_num = -1, pg; int ret = 0, num = 0, pg_id, alua_state; unsigned long start_lba = -1, end_lba = -1; unsigned long segment_size = -1, segment_mult = -1; - map_entries = kstrdup(page, GFP_KERNEL); + orig = map_entries = kstrdup(page, GFP_KERNEL); if (!map_entries) return -ENOMEM; @@ -2085,7 +2122,7 @@ out: } else core_alua_set_lba_map(dev, &lba_list, segment_size, segment_mult); - kfree(map_entries); + kfree(orig); return count; } diff --git a/drivers/target/target_core_device.c b/drivers/target/target_core_device.c index 88ea4e4f124b..cacd97a8cbd0 100644 --- a/drivers/target/target_core_device.c +++ b/drivers/target/target_core_device.c @@ -813,6 +813,8 @@ struct se_device *target_alloc_device(struct se_hba *hba, const char *name) dev->dev_attrib.unmap_granularity = DA_UNMAP_GRANULARITY_DEFAULT; dev->dev_attrib.unmap_granularity_alignment = DA_UNMAP_GRANULARITY_ALIGNMENT_DEFAULT; + dev->dev_attrib.unmap_zeroes_data = + DA_UNMAP_ZEROES_DATA_DEFAULT; dev->dev_attrib.max_write_same_len = DA_MAX_WRITE_SAME_LEN; xcopy_lun = &dev->xcopy_lun; diff --git a/drivers/target/target_core_iblock.c b/drivers/target/target_core_iblock.c index f29c69120054..e77d15000caa 100644 --- a/drivers/target/target_core_iblock.c +++ b/drivers/target/target_core_iblock.c @@ -138,6 +138,8 @@ static int iblock_configure_device(struct se_device *dev) q->limits.discard_granularity >> 9; dev->dev_attrib.unmap_granularity_alignment = q->limits.discard_alignment; + dev->dev_attrib.unmap_zeroes_data = + q->limits.discard_zeroes_data; pr_debug("IBLOCK: BLOCK Discard support available," " disabled by default\n"); diff --git a/drivers/target/target_core_pr.c b/drivers/target/target_core_pr.c index e7933115087a..b1795735eafc 100644 --- a/drivers/target/target_core_pr.c +++ b/drivers/target/target_core_pr.c @@ -1457,8 +1457,7 @@ static void core_scsi3_nodeacl_undepend_item(struct se_node_acl *nacl) static int core_scsi3_lunacl_depend_item(struct se_dev_entry *se_deve) { struct se_lun_acl *lun_acl; - struct se_node_acl *nacl; - struct se_portal_group *tpg; + /* * For nacl->dynamic_node_acl=1 */ @@ -1467,17 +1466,13 @@ static int core_scsi3_lunacl_depend_item(struct se_dev_entry *se_deve) if (!lun_acl) return 0; - nacl = lun_acl->se_lun_nacl; - tpg = nacl->se_tpg; - return target_depend_item(&lun_acl->se_lun_group.cg_item); } static void core_scsi3_lunacl_undepend_item(struct se_dev_entry *se_deve) { struct se_lun_acl *lun_acl; - struct se_node_acl *nacl; - struct se_portal_group *tpg; + /* * For nacl->dynamic_node_acl=1 */ @@ -1487,8 +1482,6 @@ static void core_scsi3_lunacl_undepend_item(struct se_dev_entry *se_deve) kref_put(&se_deve->pr_kref, target_pr_kref_release); return; } - nacl = lun_acl->se_lun_nacl; - tpg = nacl->se_tpg; target_undepend_item(&lun_acl->se_lun_group.cg_item); kref_put(&se_deve->pr_kref, target_pr_kref_release); diff --git a/drivers/target/target_core_sbc.c b/drivers/target/target_core_sbc.c index 98698d875742..a9057aa07176 100644 --- a/drivers/target/target_core_sbc.c +++ b/drivers/target/target_core_sbc.c @@ -141,9 +141,17 @@ sbc_emulate_readcapacity_16(struct se_cmd *cmd) * Set Thin Provisioning Enable bit following sbc3r22 in section * READ CAPACITY (16) byte 14 if emulate_tpu or emulate_tpws is enabled. */ - if (dev->dev_attrib.emulate_tpu || dev->dev_attrib.emulate_tpws) + if (dev->dev_attrib.emulate_tpu || dev->dev_attrib.emulate_tpws) { buf[14] |= 0x80; + /* + * LBPRZ signifies that zeroes will be read back from an LBA after + * an UNMAP or WRITE SAME w/ unmap bit (sbc3r36 5.16.2) + */ + if (dev->dev_attrib.unmap_zeroes_data) + buf[14] |= 0x40; + } + rbuf = transport_kmap_data_sg(cmd); if (rbuf) { memcpy(rbuf, buf, min_t(u32, sizeof(buf), cmd->data_length)); diff --git a/drivers/target/target_core_spc.c b/drivers/target/target_core_spc.c index 9413e1a949e5..0aa47babd16c 100644 --- a/drivers/target/target_core_spc.c +++ b/drivers/target/target_core_spc.c @@ -635,6 +635,18 @@ spc_emulate_evpd_b2(struct se_cmd *cmd, unsigned char *buf) if (dev->dev_attrib.emulate_tpws != 0) buf[5] |= 0x40 | 0x20; + /* + * The unmap_zeroes_data set means that the underlying device supports + * REQ_DISCARD and has the discard_zeroes_data bit set. This satisfies + * the SBC requirements for LBPRZ, meaning that a subsequent read + * will return zeroes after an UNMAP or WRITE SAME (16) to an LBA + * See sbc4r36 6.6.4. + */ + if (((dev->dev_attrib.emulate_tpu != 0) || + (dev->dev_attrib.emulate_tpws != 0)) && + (dev->dev_attrib.unmap_zeroes_data != 0)) + buf[5] |= 0x04; + return 0; } diff --git a/drivers/target/target_core_tmr.c b/drivers/target/target_core_tmr.c index 28fb3016370f..fcdcb117c60d 100644 --- a/drivers/target/target_core_tmr.c +++ b/drivers/target/target_core_tmr.c @@ -201,7 +201,7 @@ static void core_tmr_drain_tmr_list( /* * If this function was called with a valid pr_res_key * parameter (eg: for PROUT PREEMPT_AND_ABORT service action - * skip non regisration key matching TMRs. + * skip non registration key matching TMRs. */ if (target_check_cdb_and_preempt(preempt_and_abort_list, cmd)) continue; diff --git a/drivers/target/target_core_tpg.c b/drivers/target/target_core_tpg.c index 5fb9dd7f08bb..3608b1b5ecf7 100644 --- a/drivers/target/target_core_tpg.c +++ b/drivers/target/target_core_tpg.c @@ -75,9 +75,21 @@ struct se_node_acl *core_tpg_get_initiator_node_acl( unsigned char *initiatorname) { struct se_node_acl *acl; - + /* + * Obtain se_node_acl->acl_kref using fabric driver provided + * initiatorname[] during node acl endpoint lookup driven by + * new se_session login. + * + * The reference is held until se_session shutdown -> release + * occurs via fabric driver invoked transport_deregister_session() + * or transport_free_session() code. + */ mutex_lock(&tpg->acl_node_mutex); acl = __core_tpg_get_initiator_node_acl(tpg, initiatorname); + if (acl) { + if (!kref_get_unless_zero(&acl->acl_kref)) + acl = NULL; + } mutex_unlock(&tpg->acl_node_mutex); return acl; @@ -157,28 +169,25 @@ void core_tpg_add_node_to_devs( mutex_unlock(&tpg->tpg_lun_mutex); } -/* core_set_queue_depth_for_node(): - * - * - */ -static int core_set_queue_depth_for_node( - struct se_portal_group *tpg, - struct se_node_acl *acl) +static void +target_set_nacl_queue_depth(struct se_portal_group *tpg, + struct se_node_acl *acl, u32 queue_depth) { + acl->queue_depth = queue_depth; + if (!acl->queue_depth) { - pr_err("Queue depth for %s Initiator Node: %s is 0," + pr_warn("Queue depth for %s Initiator Node: %s is 0," "defaulting to 1.\n", tpg->se_tpg_tfo->get_fabric_name(), acl->initiatorname); acl->queue_depth = 1; } - - return 0; } static struct se_node_acl *target_alloc_node_acl(struct se_portal_group *tpg, const unsigned char *initiatorname) { struct se_node_acl *acl; + u32 queue_depth; acl = kzalloc(max(sizeof(*acl), tpg->se_tpg_tfo->node_acl_size), GFP_KERNEL); @@ -193,24 +202,20 @@ static struct se_node_acl *target_alloc_node_acl(struct se_portal_group *tpg, spin_lock_init(&acl->nacl_sess_lock); mutex_init(&acl->lun_entry_mutex); atomic_set(&acl->acl_pr_ref_count, 0); + if (tpg->se_tpg_tfo->tpg_get_default_depth) - acl->queue_depth = tpg->se_tpg_tfo->tpg_get_default_depth(tpg); + queue_depth = tpg->se_tpg_tfo->tpg_get_default_depth(tpg); else - acl->queue_depth = 1; + queue_depth = 1; + target_set_nacl_queue_depth(tpg, acl, queue_depth); + snprintf(acl->initiatorname, TRANSPORT_IQN_LEN, "%s", initiatorname); acl->se_tpg = tpg; acl->acl_index = scsi_get_new_index(SCSI_AUTH_INTR_INDEX); tpg->se_tpg_tfo->set_default_node_attributes(acl); - if (core_set_queue_depth_for_node(tpg, acl) < 0) - goto out_free_acl; - return acl; - -out_free_acl: - kfree(acl); - return NULL; } static void target_add_node_acl(struct se_node_acl *acl) @@ -219,7 +224,6 @@ static void target_add_node_acl(struct se_node_acl *acl) mutex_lock(&tpg->acl_node_mutex); list_add_tail(&acl->acl_list, &tpg->acl_node_list); - tpg->num_node_acls++; mutex_unlock(&tpg->acl_node_mutex); pr_debug("%s_TPG[%hu] - Added %s ACL with TCQ Depth: %d for %s" @@ -232,6 +236,25 @@ static void target_add_node_acl(struct se_node_acl *acl) acl->initiatorname); } +bool target_tpg_has_node_acl(struct se_portal_group *tpg, + const char *initiatorname) +{ + struct se_node_acl *acl; + bool found = false; + + mutex_lock(&tpg->acl_node_mutex); + list_for_each_entry(acl, &tpg->acl_node_list, acl_list) { + if (!strcmp(acl->initiatorname, initiatorname)) { + found = true; + break; + } + } + mutex_unlock(&tpg->acl_node_mutex); + + return found; +} +EXPORT_SYMBOL(target_tpg_has_node_acl); + struct se_node_acl *core_tpg_check_initiator_node_acl( struct se_portal_group *tpg, unsigned char *initiatorname) @@ -248,6 +271,15 @@ struct se_node_acl *core_tpg_check_initiator_node_acl( acl = target_alloc_node_acl(tpg, initiatorname); if (!acl) return NULL; + /* + * When allocating a dynamically generated node_acl, go ahead + * and take the extra kref now before returning to the fabric + * driver caller. + * + * Note this reference will be released at session shutdown + * time within transport_free_session() code. + */ + kref_get(&acl->acl_kref); acl->dynamic_node_acl = 1; /* @@ -318,7 +350,6 @@ void core_tpg_del_initiator_node_acl(struct se_node_acl *acl) acl->dynamic_node_acl = 0; } list_del(&acl->acl_list); - tpg->num_node_acls--; mutex_unlock(&tpg->acl_node_mutex); spin_lock_irqsave(&acl->nacl_sess_lock, flags); @@ -329,7 +360,8 @@ void core_tpg_del_initiator_node_acl(struct se_node_acl *acl) if (sess->sess_tearing_down != 0) continue; - target_get_session(sess); + if (!target_get_session(sess)) + continue; list_move(&sess->sess_acl_list, &sess_list); } spin_unlock_irqrestore(&acl->nacl_sess_lock, flags); @@ -366,108 +398,52 @@ void core_tpg_del_initiator_node_acl(struct se_node_acl *acl) * */ int core_tpg_set_initiator_node_queue_depth( - struct se_portal_group *tpg, - unsigned char *initiatorname, - u32 queue_depth, - int force) + struct se_node_acl *acl, + u32 queue_depth) { - struct se_session *sess, *init_sess = NULL; - struct se_node_acl *acl; + LIST_HEAD(sess_list); + struct se_portal_group *tpg = acl->se_tpg; + struct se_session *sess, *sess_tmp; unsigned long flags; - int dynamic_acl = 0; - - mutex_lock(&tpg->acl_node_mutex); - acl = __core_tpg_get_initiator_node_acl(tpg, initiatorname); - if (!acl) { - pr_err("Access Control List entry for %s Initiator" - " Node %s does not exists for TPG %hu, ignoring" - " request.\n", tpg->se_tpg_tfo->get_fabric_name(), - initiatorname, tpg->se_tpg_tfo->tpg_get_tag(tpg)); - mutex_unlock(&tpg->acl_node_mutex); - return -ENODEV; - } - if (acl->dynamic_node_acl) { - acl->dynamic_node_acl = 0; - dynamic_acl = 1; - } - mutex_unlock(&tpg->acl_node_mutex); - - spin_lock_irqsave(&tpg->session_lock, flags); - list_for_each_entry(sess, &tpg->tpg_sess_list, sess_list) { - if (sess->se_node_acl != acl) - continue; - - if (!force) { - pr_err("Unable to change queue depth for %s" - " Initiator Node: %s while session is" - " operational. To forcefully change the queue" - " depth and force session reinstatement" - " use the \"force=1\" parameter.\n", - tpg->se_tpg_tfo->get_fabric_name(), initiatorname); - spin_unlock_irqrestore(&tpg->session_lock, flags); - - mutex_lock(&tpg->acl_node_mutex); - if (dynamic_acl) - acl->dynamic_node_acl = 1; - mutex_unlock(&tpg->acl_node_mutex); - return -EEXIST; - } - /* - * Determine if the session needs to be closed by our context. - */ - if (!tpg->se_tpg_tfo->shutdown_session(sess)) - continue; - - init_sess = sess; - break; - } + int rc; /* * User has requested to change the queue depth for a Initiator Node. * Change the value in the Node's struct se_node_acl, and call - * core_set_queue_depth_for_node() to add the requested queue depth. - * - * Finally call tpg->se_tpg_tfo->close_session() to force session - * reinstatement to occur if there is an active session for the - * $FABRIC_MOD Initiator Node in question. + * target_set_nacl_queue_depth() to set the new queue depth. */ - acl->queue_depth = queue_depth; + target_set_nacl_queue_depth(tpg, acl, queue_depth); + + spin_lock_irqsave(&acl->nacl_sess_lock, flags); + list_for_each_entry_safe(sess, sess_tmp, &acl->acl_sess_list, + sess_acl_list) { + if (sess->sess_tearing_down != 0) + continue; + if (!target_get_session(sess)) + continue; + spin_unlock_irqrestore(&acl->nacl_sess_lock, flags); - if (core_set_queue_depth_for_node(tpg, acl) < 0) { - spin_unlock_irqrestore(&tpg->session_lock, flags); /* - * Force session reinstatement if - * core_set_queue_depth_for_node() failed, because we assume - * the $FABRIC_MOD has already the set session reinstatement - * bit from tpg->se_tpg_tfo->shutdown_session() called above. + * Finally call tpg->se_tpg_tfo->close_session() to force session + * reinstatement to occur if there is an active session for the + * $FABRIC_MOD Initiator Node in question. */ - if (init_sess) - tpg->se_tpg_tfo->close_session(init_sess); - - mutex_lock(&tpg->acl_node_mutex); - if (dynamic_acl) - acl->dynamic_node_acl = 1; - mutex_unlock(&tpg->acl_node_mutex); - return -EINVAL; + rc = tpg->se_tpg_tfo->shutdown_session(sess); + target_put_session(sess); + if (!rc) { + spin_lock_irqsave(&acl->nacl_sess_lock, flags); + continue; + } + target_put_session(sess); + spin_lock_irqsave(&acl->nacl_sess_lock, flags); } - spin_unlock_irqrestore(&tpg->session_lock, flags); - /* - * If the $FABRIC_MOD session for the Initiator Node ACL exists, - * forcefully shutdown the $FABRIC_MOD session/nexus. - */ - if (init_sess) - tpg->se_tpg_tfo->close_session(init_sess); + spin_unlock_irqrestore(&acl->nacl_sess_lock, flags); pr_debug("Successfully changed queue depth to: %d for Initiator" - " Node: %s on %s Target Portal Group: %u\n", queue_depth, - initiatorname, tpg->se_tpg_tfo->get_fabric_name(), + " Node: %s on %s Target Portal Group: %u\n", acl->queue_depth, + acl->initiatorname, tpg->se_tpg_tfo->get_fabric_name(), tpg->se_tpg_tfo->tpg_get_tag(tpg)); - mutex_lock(&tpg->acl_node_mutex); - if (dynamic_acl) - acl->dynamic_node_acl = 1; - mutex_unlock(&tpg->acl_node_mutex); - return 0; } EXPORT_SYMBOL(core_tpg_set_initiator_node_queue_depth); @@ -595,7 +571,6 @@ int core_tpg_deregister(struct se_portal_group *se_tpg) */ list_for_each_entry_safe(nacl, nacl_tmp, &node_list, acl_list) { list_del(&nacl->acl_list); - se_tpg->num_node_acls--; core_tpg_wait_for_nacl_pr_ref(nacl); core_free_device_list_for_node(nacl, se_tpg); diff --git a/drivers/target/target_core_transport.c b/drivers/target/target_core_transport.c index 4fdcee2006d1..9f3608e10f25 100644 --- a/drivers/target/target_core_transport.c +++ b/drivers/target/target_core_transport.c @@ -341,7 +341,6 @@ 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); /* @@ -384,9 +383,9 @@ static void target_release_session(struct kref *kref) se_tpg->se_tpg_tfo->close_session(se_sess); } -void target_get_session(struct se_session *se_sess) +int target_get_session(struct se_session *se_sess) { - kref_get(&se_sess->sess_kref); + return kref_get_unless_zero(&se_sess->sess_kref); } EXPORT_SYMBOL(target_get_session); @@ -432,6 +431,7 @@ void target_put_nacl(struct se_node_acl *nacl) { kref_put(&nacl->acl_kref, target_complete_nacl); } +EXPORT_SYMBOL(target_put_nacl); void transport_deregister_session_configfs(struct se_session *se_sess) { @@ -464,6 +464,15 @@ EXPORT_SYMBOL(transport_deregister_session_configfs); void transport_free_session(struct se_session *se_sess) { + struct se_node_acl *se_nacl = se_sess->se_node_acl; + /* + * Drop the se_node_acl->nacl_kref obtained from within + * core_tpg_get_initiator_node_acl(). + */ + if (se_nacl) { + se_sess->se_node_acl = NULL; + target_put_nacl(se_nacl); + } if (se_sess->sess_cmd_map) { percpu_ida_destroy(&se_sess->sess_tag_pool); kvfree(se_sess->sess_cmd_map); @@ -478,7 +487,7 @@ void transport_deregister_session(struct se_session *se_sess) const struct target_core_fabric_ops *se_tfo; struct se_node_acl *se_nacl; unsigned long flags; - bool comp_nacl = true, drop_nacl = false; + bool drop_nacl = false; if (!se_tpg) { transport_free_session(se_sess); @@ -502,7 +511,6 @@ void transport_deregister_session(struct se_session *se_sess) 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--; drop_nacl = true; } } @@ -511,18 +519,16 @@ void transport_deregister_session(struct se_session *se_sess) if (drop_nacl) { core_tpg_wait_for_nacl_pr_ref(se_nacl); core_free_device_list_for_node(se_nacl, se_tpg); + se_sess->se_node_acl = NULL; kfree(se_nacl); - comp_nacl = false; } 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 explicit NodeACL, awake sleeping * ->acl_free_comp caller to wakeup configfs se_node_acl->acl_group - * removal context. + * removal context from within transport_free_session() code. */ - if (se_nacl && comp_nacl) - target_put_nacl(se_nacl); transport_free_session(se_sess); } @@ -715,7 +721,10 @@ void target_complete_cmd(struct se_cmd *cmd, u8 scsi_status) cmd->transport_state |= (CMD_T_COMPLETE | CMD_T_ACTIVE); spin_unlock_irqrestore(&cmd->t_state_lock, flags); - queue_work(target_completion_wq, &cmd->work); + if (cmd->cpuid == -1) + queue_work(target_completion_wq, &cmd->work); + else + queue_work_on(cmd->cpuid, target_completion_wq, &cmd->work); } EXPORT_SYMBOL(target_complete_cmd); @@ -1309,7 +1318,7 @@ EXPORT_SYMBOL(target_setup_cmd_from_cdb); /* * Used by fabric module frontends to queue tasks directly. - * Many only be used from process context only + * May only be used from process context. */ int transport_handle_cdb_direct( struct se_cmd *cmd) @@ -1582,7 +1591,7 @@ static void target_complete_tmr_failure(struct work_struct *work) int target_submit_tmr(struct se_cmd *se_cmd, struct se_session *se_sess, unsigned char *sense, u64 unpacked_lun, void *fabric_tmr_ptr, unsigned char tm_type, - gfp_t gfp, unsigned int tag, int flags) + gfp_t gfp, u64 tag, int flags) { struct se_portal_group *se_tpg; int ret; diff --git a/drivers/target/target_core_user.c b/drivers/target/target_core_user.c index 5e6d6cb348fc..dd600e5ead71 100644 --- a/drivers/target/target_core_user.c +++ b/drivers/target/target_core_user.c @@ -152,6 +152,7 @@ static struct genl_family tcmu_genl_family = { .maxattr = TCMU_ATTR_MAX, .mcgrps = tcmu_mcgrps, .n_mcgrps = ARRAY_SIZE(tcmu_mcgrps), + .netnsok = true, }; static struct tcmu_cmd *tcmu_alloc_cmd(struct se_cmd *se_cmd) @@ -194,7 +195,7 @@ static struct tcmu_cmd *tcmu_alloc_cmd(struct se_cmd *se_cmd) static inline void tcmu_flush_dcache_range(void *vaddr, size_t size) { - unsigned long offset = (unsigned long) vaddr & ~PAGE_MASK; + unsigned long offset = offset_in_page(vaddr); size = round_up(size+offset, PAGE_SIZE); vaddr -= offset; @@ -840,7 +841,7 @@ static int tcmu_netlink_event(enum tcmu_genl_cmd cmd, const char *name, int mino genlmsg_end(skb, msg_header); - ret = genlmsg_multicast(&tcmu_genl_family, skb, 0, + ret = genlmsg_multicast_allns(&tcmu_genl_family, skb, 0, TCMU_MCGRP_CONFIG, GFP_KERNEL); /* We don't care if no one is listening */ @@ -917,8 +918,10 @@ static int tcmu_configure_device(struct se_device *dev) if (ret) goto err_register; + /* User can set hw_block_size before enable the device */ + if (dev->dev_attrib.hw_block_size == 0) + dev->dev_attrib.hw_block_size = 512; /* Other attributes can be configured in userspace */ - dev->dev_attrib.hw_block_size = 512; dev->dev_attrib.hw_max_sectors = 128; dev->dev_attrib.hw_queue_depth = 128; diff --git a/drivers/target/tcm_fc/tcm_fc.h b/drivers/target/tcm_fc/tcm_fc.h index 39909dadef3e..c30003bd4ff0 100644 --- a/drivers/target/tcm_fc/tcm_fc.h +++ b/drivers/target/tcm_fc/tcm_fc.h @@ -166,7 +166,6 @@ void ft_aborted_task(struct se_cmd *); */ void ft_recv_req(struct ft_sess *, struct fc_frame *); struct ft_tpg *ft_lport_find_tpg(struct fc_lport *); -struct ft_node_acl *ft_acl_get(struct ft_tpg *, struct fc_rport_priv *); void ft_recv_write_data(struct ft_cmd *, struct fc_frame *); void ft_dump_cmd(struct ft_cmd *, const char *caller); diff --git a/drivers/target/tcm_fc/tfc_conf.c b/drivers/target/tcm_fc/tfc_conf.c index 85aeaa0ad303..4d375e95841b 100644 --- a/drivers/target/tcm_fc/tfc_conf.c +++ b/drivers/target/tcm_fc/tfc_conf.c @@ -171,9 +171,31 @@ static ssize_t ft_nacl_node_name_store(struct config_item *item, CONFIGFS_ATTR(ft_nacl_, node_name); CONFIGFS_ATTR(ft_nacl_, port_name); +static ssize_t ft_nacl_tag_show(struct config_item *item, + char *page) +{ + return snprintf(page, PAGE_SIZE, "%s", acl_to_nacl(item)->acl_tag); +} + +static ssize_t ft_nacl_tag_store(struct config_item *item, + const char *page, size_t count) +{ + struct se_node_acl *se_nacl = acl_to_nacl(item); + int ret; + + ret = core_tpg_set_initiator_node_tag(se_nacl->se_tpg, se_nacl, page); + + if (ret < 0) + return ret; + return count; +} + +CONFIGFS_ATTR(ft_nacl_, tag); + static struct configfs_attribute *ft_nacl_base_attrs[] = { &ft_nacl_attr_port_name, &ft_nacl_attr_node_name, + &ft_nacl_attr_tag, NULL, }; @@ -198,31 +220,6 @@ static int ft_init_nodeacl(struct se_node_acl *nacl, const char *name) return 0; } -struct ft_node_acl *ft_acl_get(struct ft_tpg *tpg, struct fc_rport_priv *rdata) -{ - struct ft_node_acl *found = NULL; - struct ft_node_acl *acl; - struct se_portal_group *se_tpg = &tpg->se_tpg; - struct se_node_acl *se_acl; - - mutex_lock(&se_tpg->acl_node_mutex); - list_for_each_entry(se_acl, &se_tpg->acl_node_list, acl_list) { - acl = container_of(se_acl, struct ft_node_acl, se_node_acl); - pr_debug("acl %p port_name %llx\n", - acl, (unsigned long long)acl->node_auth.port_name); - if (acl->node_auth.port_name == rdata->ids.port_name || - acl->node_auth.node_name == rdata->ids.node_name) { - pr_debug("acl %p port_name %llx matched\n", acl, - (unsigned long long)rdata->ids.port_name); - found = acl; - /* XXX need to hold onto ACL */ - break; - } - } - mutex_unlock(&se_tpg->acl_node_mutex); - return found; -} - /* * local_port port_group (tpg) ops. */ diff --git a/drivers/target/tcm_fc/tfc_io.c b/drivers/target/tcm_fc/tfc_io.c index 847c1aa6fbf4..6f7c65abfe2a 100644 --- a/drivers/target/tcm_fc/tfc_io.c +++ b/drivers/target/tcm_fc/tfc_io.c @@ -154,9 +154,9 @@ int ft_queue_data_in(struct se_cmd *se_cmd) BUG_ON(!page); from = kmap_atomic(page + (mem_off >> PAGE_SHIFT)); page_addr = from; - from += mem_off & ~PAGE_MASK; + from += offset_in_page(mem_off); tlen = min(tlen, (size_t)(PAGE_SIZE - - (mem_off & ~PAGE_MASK))); + offset_in_page(mem_off))); memcpy(to, from, tlen); kunmap_atomic(page_addr); to += tlen; @@ -314,9 +314,9 @@ void ft_recv_write_data(struct ft_cmd *cmd, struct fc_frame *fp) to = kmap_atomic(page + (mem_off >> PAGE_SHIFT)); page_addr = to; - to += mem_off & ~PAGE_MASK; + to += offset_in_page(mem_off); tlen = min(tlen, (size_t)(PAGE_SIZE - - (mem_off & ~PAGE_MASK))); + offset_in_page(mem_off))); memcpy(to, from, tlen); kunmap_atomic(page_addr); diff --git a/drivers/target/tcm_fc/tfc_sess.c b/drivers/target/tcm_fc/tfc_sess.c index 7b934eac995d..e19f4c58c6fa 100644 --- a/drivers/target/tcm_fc/tfc_sess.c +++ b/drivers/target/tcm_fc/tfc_sess.c @@ -191,10 +191,15 @@ out: * Caller holds ft_lport_lock. */ static struct ft_sess *ft_sess_create(struct ft_tport *tport, u32 port_id, - struct ft_node_acl *acl) + struct fc_rport_priv *rdata) { + struct se_portal_group *se_tpg = &tport->tpg->se_tpg; + struct se_node_acl *se_acl; struct ft_sess *sess; struct hlist_head *head; + unsigned char initiatorname[TRANSPORT_IQN_LEN]; + + ft_format_wwn(&initiatorname[0], TRANSPORT_IQN_LEN, rdata->ids.port_name); head = &tport->hash[ft_sess_hash(port_id)]; hlist_for_each_entry_rcu(sess, head, hash) @@ -212,7 +217,14 @@ static struct ft_sess *ft_sess_create(struct ft_tport *tport, u32 port_id, kfree(sess); return NULL; } - sess->se_sess->se_node_acl = &acl->se_node_acl; + + se_acl = core_tpg_get_initiator_node_acl(se_tpg, &initiatorname[0]); + if (!se_acl) { + transport_free_session(sess->se_sess); + kfree(sess); + return NULL; + } + sess->se_sess->se_node_acl = se_acl; sess->tport = tport; sess->port_id = port_id; kref_init(&sess->kref); /* ref for table entry */ @@ -221,7 +233,7 @@ static struct ft_sess *ft_sess_create(struct ft_tport *tport, u32 port_id, pr_debug("port_id %x sess %p\n", port_id, sess); - transport_register_session(&tport->tpg->se_tpg, &acl->se_node_acl, + transport_register_session(&tport->tpg->se_tpg, se_acl, sess->se_sess, sess); return sess; } @@ -260,6 +272,14 @@ static struct ft_sess *ft_sess_delete(struct ft_tport *tport, u32 port_id) return NULL; } +static void ft_close_sess(struct ft_sess *sess) +{ + transport_deregister_session_configfs(sess->se_sess); + target_sess_cmd_list_set_waiting(sess->se_sess); + target_wait_for_sess_cmds(sess->se_sess); + ft_sess_put(sess); +} + /* * Delete all sessions from tport. * Caller holds ft_lport_lock. @@ -273,8 +293,7 @@ static void ft_sess_delete_all(struct ft_tport *tport) head < &tport->hash[FT_SESS_HASH_SIZE]; head++) { hlist_for_each_entry_rcu(sess, head, hash) { ft_sess_unhash(sess); - transport_deregister_session_configfs(sess->se_sess); - ft_sess_put(sess); /* release from table */ + ft_close_sess(sess); /* release from table */ } } } @@ -313,8 +332,7 @@ void ft_sess_close(struct se_session *se_sess) pr_debug("port_id %x\n", port_id); ft_sess_unhash(sess); mutex_unlock(&ft_lport_lock); - transport_deregister_session_configfs(se_sess); - ft_sess_put(sess); + ft_close_sess(sess); /* XXX Send LOGO or PRLO */ synchronize_rcu(); /* let transport deregister happen */ } @@ -343,17 +361,12 @@ static int ft_prli_locked(struct fc_rport_priv *rdata, u32 spp_len, { struct ft_tport *tport; struct ft_sess *sess; - struct ft_node_acl *acl; u32 fcp_parm; tport = ft_tport_get(rdata->local_port); if (!tport) goto not_target; /* not a target for this local port */ - acl = ft_acl_get(tport->tpg, rdata); - if (!acl) - goto not_target; /* no target for this remote */ - if (!rspp) goto fill; @@ -375,7 +388,7 @@ static int ft_prli_locked(struct fc_rport_priv *rdata, u32 spp_len, spp->spp_flags |= FC_SPP_EST_IMG_PAIR; if (!(fcp_parm & FCP_SPPF_INIT_FCN)) return FC_SPP_RESP_CONF; - sess = ft_sess_create(tport, rdata->ids.port_id, acl); + sess = ft_sess_create(tport, rdata->ids.port_id, rdata); if (!sess) return FC_SPP_RESP_RES; if (!sess->params) @@ -460,8 +473,7 @@ static void ft_prlo(struct fc_rport_priv *rdata) return; } mutex_unlock(&ft_lport_lock); - transport_deregister_session_configfs(sess->se_sess); - ft_sess_put(sess); /* release from table */ + ft_close_sess(sess); /* release from table */ rdata->prli_count--; /* XXX TBD - clearing actions. unit attn, see 4.10 */ } diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index be5aab9c13f2..af5d922a8f5d 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -205,6 +205,9 @@ config USB_F_HID config USB_F_PRINTER tristate +config USB_F_TCM + tristate + choice tristate "USB Gadget Drivers" default USB_ETH @@ -457,6 +460,20 @@ config USB_CONFIGFS_F_PRINTER For more information, see Documentation/usb/gadget_printer.txt which includes sample code for accessing the device file. +config USB_CONFIGFS_F_TCM + bool "USB Gadget Target Fabric" + depends on TARGET_CORE + depends on USB_CONFIGFS + select USB_LIBCOMPOSITE + select USB_F_TCM + help + This fabric is a USB gadget component. Two USB protocols are + supported that is BBB or BOT (Bulk Only Transport) and UAS + (USB Attached SCSI). BOT is advertised on alternative + interface 0 (primary) and UAS is on alternative interface 1. + Both protocols can work on USB2.0 and USB3.0. + UAS utilizes the USB 3.0 feature called streams support. + source "drivers/usb/gadget/legacy/Kconfig" endchoice diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile index bd7def576955..cb8c225e8549 100644 --- a/drivers/usb/gadget/function/Makefile +++ b/drivers/usb/gadget/function/Makefile @@ -44,3 +44,5 @@ usb_f_hid-y := f_hid.o obj-$(CONFIG_USB_F_HID) += usb_f_hid.o usb_f_printer-y := f_printer.o obj-$(CONFIG_USB_F_PRINTER) += usb_f_printer.o +usb_f_tcm-y := f_tcm.o +obj-$(CONFIG_USB_F_TCM) += usb_f_tcm.o diff --git a/drivers/usb/gadget/function/f_tcm.c b/drivers/usb/gadget/function/f_tcm.c new file mode 100644 index 000000000000..bad007b5a190 --- /dev/null +++ b/drivers/usb/gadget/function/f_tcm.c @@ -0,0 +1,2381 @@ +/* Target based USB-Gadget + * + * UAS protocol handling, target callbacks, configfs handling, + * BBB (USB Mass Storage Class Bulk-Only (BBB) and Transport protocol handling. + * + * Author: Sebastian Andrzej Siewior <bigeasy at linutronix dot de> + * License: GPLv2 as published by FSF. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/string.h> +#include <linux/configfs.h> +#include <linux/ctype.h> +#include <linux/usb/ch9.h> +#include <linux/usb/composite.h> +#include <linux/usb/gadget.h> +#include <linux/usb/storage.h> +#include <scsi/scsi_tcq.h> +#include <target/target_core_base.h> +#include <target/target_core_fabric.h> +#include <asm/unaligned.h> + +#include "tcm.h" +#include "u_tcm.h" +#include "configfs.h" + +#define TPG_INSTANCES 1 + +struct tpg_instance { + struct usb_function_instance *func_inst; + struct usbg_tpg *tpg; +}; + +static struct tpg_instance tpg_instances[TPG_INSTANCES]; + +static DEFINE_MUTEX(tpg_instances_lock); + +static inline struct f_uas *to_f_uas(struct usb_function *f) +{ + return container_of(f, struct f_uas, function); +} + +static void usbg_cmd_release(struct kref *); + +static inline void usbg_cleanup_cmd(struct usbg_cmd *cmd) +{ + kref_put(&cmd->ref, usbg_cmd_release); +} + +/* Start bot.c code */ + +static int bot_enqueue_cmd_cbw(struct f_uas *fu) +{ + int ret; + + if (fu->flags & USBG_BOT_CMD_PEND) + return 0; + + ret = usb_ep_queue(fu->ep_out, fu->cmd.req, GFP_ATOMIC); + if (!ret) + fu->flags |= USBG_BOT_CMD_PEND; + return ret; +} + +static void bot_status_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct usbg_cmd *cmd = req->context; + struct f_uas *fu = cmd->fu; + + usbg_cleanup_cmd(cmd); + if (req->status < 0) { + pr_err("ERR %s(%d)\n", __func__, __LINE__); + return; + } + + /* CSW completed, wait for next CBW */ + bot_enqueue_cmd_cbw(fu); +} + +static void bot_enqueue_sense_code(struct f_uas *fu, struct usbg_cmd *cmd) +{ + struct bulk_cs_wrap *csw = &fu->bot_status.csw; + int ret; + unsigned int csw_stat; + + csw_stat = cmd->csw_code; + csw->Tag = cmd->bot_tag; + csw->Status = csw_stat; + fu->bot_status.req->context = cmd; + ret = usb_ep_queue(fu->ep_in, fu->bot_status.req, GFP_ATOMIC); + if (ret) + pr_err("%s(%d) ERR: %d\n", __func__, __LINE__, ret); +} + +static void bot_err_compl(struct usb_ep *ep, struct usb_request *req) +{ + struct usbg_cmd *cmd = req->context; + struct f_uas *fu = cmd->fu; + + if (req->status < 0) + pr_err("ERR %s(%d)\n", __func__, __LINE__); + + if (cmd->data_len) { + if (cmd->data_len > ep->maxpacket) { + req->length = ep->maxpacket; + cmd->data_len -= ep->maxpacket; + } else { + req->length = cmd->data_len; + cmd->data_len = 0; + } + + usb_ep_queue(ep, req, GFP_ATOMIC); + return; + } + bot_enqueue_sense_code(fu, cmd); +} + +static void bot_send_bad_status(struct usbg_cmd *cmd) +{ + struct f_uas *fu = cmd->fu; + struct bulk_cs_wrap *csw = &fu->bot_status.csw; + struct usb_request *req; + struct usb_ep *ep; + + csw->Residue = cpu_to_le32(cmd->data_len); + + if (cmd->data_len) { + if (cmd->is_read) { + ep = fu->ep_in; + req = fu->bot_req_in; + } else { + ep = fu->ep_out; + req = fu->bot_req_out; + } + + if (cmd->data_len > fu->ep_in->maxpacket) { + req->length = ep->maxpacket; + cmd->data_len -= ep->maxpacket; + } else { + req->length = cmd->data_len; + cmd->data_len = 0; + } + req->complete = bot_err_compl; + req->context = cmd; + req->buf = fu->cmd.buf; + usb_ep_queue(ep, req, GFP_KERNEL); + } else { + bot_enqueue_sense_code(fu, cmd); + } +} + +static int bot_send_status(struct usbg_cmd *cmd, bool moved_data) +{ + struct f_uas *fu = cmd->fu; + struct bulk_cs_wrap *csw = &fu->bot_status.csw; + int ret; + + if (cmd->se_cmd.scsi_status == SAM_STAT_GOOD) { + if (!moved_data && cmd->data_len) { + /* + * the host wants to move data, we don't. Fill / empty + * the pipe and then send the csw with reside set. + */ + cmd->csw_code = US_BULK_STAT_OK; + bot_send_bad_status(cmd); + return 0; + } + + csw->Tag = cmd->bot_tag; + csw->Residue = cpu_to_le32(0); + csw->Status = US_BULK_STAT_OK; + fu->bot_status.req->context = cmd; + + ret = usb_ep_queue(fu->ep_in, fu->bot_status.req, GFP_KERNEL); + if (ret) + pr_err("%s(%d) ERR: %d\n", __func__, __LINE__, ret); + } else { + cmd->csw_code = US_BULK_STAT_FAIL; + bot_send_bad_status(cmd); + } + return 0; +} + +/* + * Called after command (no data transfer) or after the write (to device) + * operation is completed + */ +static int bot_send_status_response(struct usbg_cmd *cmd) +{ + bool moved_data = false; + + if (!cmd->is_read) + moved_data = true; + return bot_send_status(cmd, moved_data); +} + +/* Read request completed, now we have to send the CSW */ +static void bot_read_compl(struct usb_ep *ep, struct usb_request *req) +{ + struct usbg_cmd *cmd = req->context; + + if (req->status < 0) + pr_err("ERR %s(%d)\n", __func__, __LINE__); + + bot_send_status(cmd, true); +} + +static int bot_send_read_response(struct usbg_cmd *cmd) +{ + struct f_uas *fu = cmd->fu; + struct se_cmd *se_cmd = &cmd->se_cmd; + struct usb_gadget *gadget = fuas_to_gadget(fu); + int ret; + + if (!cmd->data_len) { + cmd->csw_code = US_BULK_STAT_PHASE; + bot_send_bad_status(cmd); + return 0; + } + + if (!gadget->sg_supported) { + cmd->data_buf = kmalloc(se_cmd->data_length, GFP_ATOMIC); + if (!cmd->data_buf) + return -ENOMEM; + + sg_copy_to_buffer(se_cmd->t_data_sg, + se_cmd->t_data_nents, + cmd->data_buf, + se_cmd->data_length); + + fu->bot_req_in->buf = cmd->data_buf; + } else { + fu->bot_req_in->buf = NULL; + fu->bot_req_in->num_sgs = se_cmd->t_data_nents; + fu->bot_req_in->sg = se_cmd->t_data_sg; + } + + fu->bot_req_in->complete = bot_read_compl; + fu->bot_req_in->length = se_cmd->data_length; + fu->bot_req_in->context = cmd; + ret = usb_ep_queue(fu->ep_in, fu->bot_req_in, GFP_ATOMIC); + if (ret) + pr_err("%s(%d)\n", __func__, __LINE__); + return 0; +} + +static void usbg_data_write_cmpl(struct usb_ep *, struct usb_request *); +static int usbg_prepare_w_request(struct usbg_cmd *, struct usb_request *); + +static int bot_send_write_request(struct usbg_cmd *cmd) +{ + struct f_uas *fu = cmd->fu; + struct se_cmd *se_cmd = &cmd->se_cmd; + struct usb_gadget *gadget = fuas_to_gadget(fu); + int ret; + + init_completion(&cmd->write_complete); + cmd->fu = fu; + + if (!cmd->data_len) { + cmd->csw_code = US_BULK_STAT_PHASE; + return -EINVAL; + } + + if (!gadget->sg_supported) { + cmd->data_buf = kmalloc(se_cmd->data_length, GFP_KERNEL); + if (!cmd->data_buf) + return -ENOMEM; + + fu->bot_req_out->buf = cmd->data_buf; + } else { + fu->bot_req_out->buf = NULL; + fu->bot_req_out->num_sgs = se_cmd->t_data_nents; + fu->bot_req_out->sg = se_cmd->t_data_sg; + } + + fu->bot_req_out->complete = usbg_data_write_cmpl; + fu->bot_req_out->length = se_cmd->data_length; + fu->bot_req_out->context = cmd; + + ret = usbg_prepare_w_request(cmd, fu->bot_req_out); + if (ret) + goto cleanup; + ret = usb_ep_queue(fu->ep_out, fu->bot_req_out, GFP_KERNEL); + if (ret) + pr_err("%s(%d)\n", __func__, __LINE__); + + wait_for_completion(&cmd->write_complete); + target_execute_cmd(se_cmd); +cleanup: + return ret; +} + +static int bot_submit_command(struct f_uas *, void *, unsigned int); + +static void bot_cmd_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_uas *fu = req->context; + int ret; + + fu->flags &= ~USBG_BOT_CMD_PEND; + + if (req->status < 0) + return; + + ret = bot_submit_command(fu, req->buf, req->actual); + if (ret) + pr_err("%s(%d): %d\n", __func__, __LINE__, ret); +} + +static int bot_prepare_reqs(struct f_uas *fu) +{ + int ret; + + fu->bot_req_in = usb_ep_alloc_request(fu->ep_in, GFP_KERNEL); + if (!fu->bot_req_in) + goto err; + + fu->bot_req_out = usb_ep_alloc_request(fu->ep_out, GFP_KERNEL); + if (!fu->bot_req_out) + goto err_out; + + fu->cmd.req = usb_ep_alloc_request(fu->ep_out, GFP_KERNEL); + if (!fu->cmd.req) + goto err_cmd; + + fu->bot_status.req = usb_ep_alloc_request(fu->ep_in, GFP_KERNEL); + if (!fu->bot_status.req) + goto err_sts; + + fu->bot_status.req->buf = &fu->bot_status.csw; + fu->bot_status.req->length = US_BULK_CS_WRAP_LEN; + fu->bot_status.req->complete = bot_status_complete; + fu->bot_status.csw.Signature = cpu_to_le32(US_BULK_CS_SIGN); + + fu->cmd.buf = kmalloc(fu->ep_out->maxpacket, GFP_KERNEL); + if (!fu->cmd.buf) + goto err_buf; + + fu->cmd.req->complete = bot_cmd_complete; + fu->cmd.req->buf = fu->cmd.buf; + fu->cmd.req->length = fu->ep_out->maxpacket; + fu->cmd.req->context = fu; + + ret = bot_enqueue_cmd_cbw(fu); + if (ret) + goto err_queue; + return 0; +err_queue: + kfree(fu->cmd.buf); + fu->cmd.buf = NULL; +err_buf: + usb_ep_free_request(fu->ep_in, fu->bot_status.req); +err_sts: + usb_ep_free_request(fu->ep_out, fu->cmd.req); + fu->cmd.req = NULL; +err_cmd: + usb_ep_free_request(fu->ep_out, fu->bot_req_out); + fu->bot_req_out = NULL; +err_out: + usb_ep_free_request(fu->ep_in, fu->bot_req_in); + fu->bot_req_in = NULL; +err: + pr_err("BOT: endpoint setup failed\n"); + return -ENOMEM; +} + +static void bot_cleanup_old_alt(struct f_uas *fu) +{ + if (!(fu->flags & USBG_ENABLED)) + return; + + usb_ep_disable(fu->ep_in); + usb_ep_disable(fu->ep_out); + + if (!fu->bot_req_in) + return; + + usb_ep_free_request(fu->ep_in, fu->bot_req_in); + usb_ep_free_request(fu->ep_out, fu->bot_req_out); + usb_ep_free_request(fu->ep_out, fu->cmd.req); + usb_ep_free_request(fu->ep_out, fu->bot_status.req); + + kfree(fu->cmd.buf); + + fu->bot_req_in = NULL; + fu->bot_req_out = NULL; + fu->cmd.req = NULL; + fu->bot_status.req = NULL; + fu->cmd.buf = NULL; +} + +static void bot_set_alt(struct f_uas *fu) +{ + struct usb_function *f = &fu->function; + struct usb_gadget *gadget = f->config->cdev->gadget; + int ret; + + fu->flags = USBG_IS_BOT; + + config_ep_by_speed(gadget, f, fu->ep_in); + ret = usb_ep_enable(fu->ep_in); + if (ret) + goto err_b_in; + + config_ep_by_speed(gadget, f, fu->ep_out); + ret = usb_ep_enable(fu->ep_out); + if (ret) + goto err_b_out; + + ret = bot_prepare_reqs(fu); + if (ret) + goto err_wq; + fu->flags |= USBG_ENABLED; + pr_info("Using the BOT protocol\n"); + return; +err_wq: + usb_ep_disable(fu->ep_out); +err_b_out: + usb_ep_disable(fu->ep_in); +err_b_in: + fu->flags = USBG_IS_BOT; +} + +static int usbg_bot_setup(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct f_uas *fu = to_f_uas(f); + struct usb_composite_dev *cdev = f->config->cdev; + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + int luns; + u8 *ret_lun; + + switch (ctrl->bRequest) { + case US_BULK_GET_MAX_LUN: + if (ctrl->bRequestType != (USB_DIR_IN | USB_TYPE_CLASS | + USB_RECIP_INTERFACE)) + return -ENOTSUPP; + + if (w_length < 1) + return -EINVAL; + if (w_value != 0) + return -EINVAL; + luns = atomic_read(&fu->tpg->tpg_port_count); + if (!luns) { + pr_err("No LUNs configured?\n"); + return -EINVAL; + } + /* + * If 4 LUNs are present we return 3 i.e. LUN 0..3 can be + * accessed. The upper limit is 0xf + */ + luns--; + if (luns > 0xf) { + pr_info_once("Limiting the number of luns to 16\n"); + luns = 0xf; + } + ret_lun = cdev->req->buf; + *ret_lun = luns; + cdev->req->length = 1; + return usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC); + + case US_BULK_RESET_REQUEST: + /* XXX maybe we should remove previous requests for IN + OUT */ + bot_enqueue_cmd_cbw(fu); + return 0; + } + return -ENOTSUPP; +} + +/* Start uas.c code */ + +static void uasp_cleanup_one_stream(struct f_uas *fu, struct uas_stream *stream) +{ + /* We have either all three allocated or none */ + if (!stream->req_in) + return; + + usb_ep_free_request(fu->ep_in, stream->req_in); + usb_ep_free_request(fu->ep_out, stream->req_out); + usb_ep_free_request(fu->ep_status, stream->req_status); + + stream->req_in = NULL; + stream->req_out = NULL; + stream->req_status = NULL; +} + +static void uasp_free_cmdreq(struct f_uas *fu) +{ + usb_ep_free_request(fu->ep_cmd, fu->cmd.req); + kfree(fu->cmd.buf); + fu->cmd.req = NULL; + fu->cmd.buf = NULL; +} + +static void uasp_cleanup_old_alt(struct f_uas *fu) +{ + int i; + + if (!(fu->flags & USBG_ENABLED)) + return; + + usb_ep_disable(fu->ep_in); + usb_ep_disable(fu->ep_out); + usb_ep_disable(fu->ep_status); + usb_ep_disable(fu->ep_cmd); + + for (i = 0; i < UASP_SS_EP_COMP_NUM_STREAMS; i++) + uasp_cleanup_one_stream(fu, &fu->stream[i]); + uasp_free_cmdreq(fu); +} + +static void uasp_status_data_cmpl(struct usb_ep *ep, struct usb_request *req); + +static int uasp_prepare_r_request(struct usbg_cmd *cmd) +{ + struct se_cmd *se_cmd = &cmd->se_cmd; + struct f_uas *fu = cmd->fu; + struct usb_gadget *gadget = fuas_to_gadget(fu); + struct uas_stream *stream = cmd->stream; + + if (!gadget->sg_supported) { + cmd->data_buf = kmalloc(se_cmd->data_length, GFP_ATOMIC); + if (!cmd->data_buf) + return -ENOMEM; + + sg_copy_to_buffer(se_cmd->t_data_sg, + se_cmd->t_data_nents, + cmd->data_buf, + se_cmd->data_length); + + stream->req_in->buf = cmd->data_buf; + } else { + stream->req_in->buf = NULL; + stream->req_in->num_sgs = se_cmd->t_data_nents; + stream->req_in->sg = se_cmd->t_data_sg; + } + + stream->req_in->complete = uasp_status_data_cmpl; + stream->req_in->length = se_cmd->data_length; + stream->req_in->context = cmd; + + cmd->state = UASP_SEND_STATUS; + return 0; +} + +static void uasp_prepare_status(struct usbg_cmd *cmd) +{ + struct se_cmd *se_cmd = &cmd->se_cmd; + struct sense_iu *iu = &cmd->sense_iu; + struct uas_stream *stream = cmd->stream; + + cmd->state = UASP_QUEUE_COMMAND; + iu->iu_id = IU_ID_STATUS; + iu->tag = cpu_to_be16(cmd->tag); + + /* + * iu->status_qual = cpu_to_be16(STATUS QUALIFIER SAM-4. Where R U?); + */ + iu->len = cpu_to_be16(se_cmd->scsi_sense_length); + iu->status = se_cmd->scsi_status; + stream->req_status->context = cmd; + stream->req_status->length = se_cmd->scsi_sense_length + 16; + stream->req_status->buf = iu; + stream->req_status->complete = uasp_status_data_cmpl; +} + +static void uasp_status_data_cmpl(struct usb_ep *ep, struct usb_request *req) +{ + struct usbg_cmd *cmd = req->context; + struct uas_stream *stream = cmd->stream; + struct f_uas *fu = cmd->fu; + int ret; + + if (req->status < 0) + goto cleanup; + + switch (cmd->state) { + case UASP_SEND_DATA: + ret = uasp_prepare_r_request(cmd); + if (ret) + goto cleanup; + ret = usb_ep_queue(fu->ep_in, stream->req_in, GFP_ATOMIC); + if (ret) + pr_err("%s(%d) => %d\n", __func__, __LINE__, ret); + break; + + case UASP_RECEIVE_DATA: + ret = usbg_prepare_w_request(cmd, stream->req_out); + if (ret) + goto cleanup; + ret = usb_ep_queue(fu->ep_out, stream->req_out, GFP_ATOMIC); + if (ret) + pr_err("%s(%d) => %d\n", __func__, __LINE__, ret); + break; + + case UASP_SEND_STATUS: + uasp_prepare_status(cmd); + ret = usb_ep_queue(fu->ep_status, stream->req_status, + GFP_ATOMIC); + if (ret) + pr_err("%s(%d) => %d\n", __func__, __LINE__, ret); + break; + + case UASP_QUEUE_COMMAND: + usbg_cleanup_cmd(cmd); + usb_ep_queue(fu->ep_cmd, fu->cmd.req, GFP_ATOMIC); + break; + + default: + BUG(); + } + return; + +cleanup: + usbg_cleanup_cmd(cmd); +} + +static int uasp_send_status_response(struct usbg_cmd *cmd) +{ + struct f_uas *fu = cmd->fu; + struct uas_stream *stream = cmd->stream; + struct sense_iu *iu = &cmd->sense_iu; + + iu->tag = cpu_to_be16(cmd->tag); + stream->req_status->complete = uasp_status_data_cmpl; + stream->req_status->context = cmd; + cmd->fu = fu; + uasp_prepare_status(cmd); + return usb_ep_queue(fu->ep_status, stream->req_status, GFP_ATOMIC); +} + +static int uasp_send_read_response(struct usbg_cmd *cmd) +{ + struct f_uas *fu = cmd->fu; + struct uas_stream *stream = cmd->stream; + struct sense_iu *iu = &cmd->sense_iu; + int ret; + + cmd->fu = fu; + + iu->tag = cpu_to_be16(cmd->tag); + if (fu->flags & USBG_USE_STREAMS) { + + ret = uasp_prepare_r_request(cmd); + if (ret) + goto out; + ret = usb_ep_queue(fu->ep_in, stream->req_in, GFP_ATOMIC); + if (ret) { + pr_err("%s(%d) => %d\n", __func__, __LINE__, ret); + kfree(cmd->data_buf); + cmd->data_buf = NULL; + } + + } else { + + iu->iu_id = IU_ID_READ_READY; + iu->tag = cpu_to_be16(cmd->tag); + + stream->req_status->complete = uasp_status_data_cmpl; + stream->req_status->context = cmd; + + cmd->state = UASP_SEND_DATA; + stream->req_status->buf = iu; + stream->req_status->length = sizeof(struct iu); + + ret = usb_ep_queue(fu->ep_status, stream->req_status, + GFP_ATOMIC); + if (ret) + pr_err("%s(%d) => %d\n", __func__, __LINE__, ret); + } +out: + return ret; +} + +static int uasp_send_write_request(struct usbg_cmd *cmd) +{ + struct f_uas *fu = cmd->fu; + struct se_cmd *se_cmd = &cmd->se_cmd; + struct uas_stream *stream = cmd->stream; + struct sense_iu *iu = &cmd->sense_iu; + int ret; + + init_completion(&cmd->write_complete); + cmd->fu = fu; + + iu->tag = cpu_to_be16(cmd->tag); + + if (fu->flags & USBG_USE_STREAMS) { + + ret = usbg_prepare_w_request(cmd, stream->req_out); + if (ret) + goto cleanup; + ret = usb_ep_queue(fu->ep_out, stream->req_out, GFP_ATOMIC); + if (ret) + pr_err("%s(%d)\n", __func__, __LINE__); + + } else { + + iu->iu_id = IU_ID_WRITE_READY; + iu->tag = cpu_to_be16(cmd->tag); + + stream->req_status->complete = uasp_status_data_cmpl; + stream->req_status->context = cmd; + + cmd->state = UASP_RECEIVE_DATA; + stream->req_status->buf = iu; + stream->req_status->length = sizeof(struct iu); + + ret = usb_ep_queue(fu->ep_status, stream->req_status, + GFP_ATOMIC); + if (ret) + pr_err("%s(%d)\n", __func__, __LINE__); + } + + wait_for_completion(&cmd->write_complete); + target_execute_cmd(se_cmd); +cleanup: + return ret; +} + +static int usbg_submit_command(struct f_uas *, void *, unsigned int); + +static void uasp_cmd_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_uas *fu = req->context; + int ret; + + if (req->status < 0) + return; + + ret = usbg_submit_command(fu, req->buf, req->actual); + /* + * Once we tune for performance enqueue the command req here again so + * we can receive a second command while we processing this one. Pay + * attention to properly sync STAUS endpoint with DATA IN + OUT so you + * don't break HS. + */ + if (!ret) + return; + usb_ep_queue(fu->ep_cmd, fu->cmd.req, GFP_ATOMIC); +} + +static int uasp_alloc_stream_res(struct f_uas *fu, struct uas_stream *stream) +{ + stream->req_in = usb_ep_alloc_request(fu->ep_in, GFP_KERNEL); + if (!stream->req_in) + goto out; + + stream->req_out = usb_ep_alloc_request(fu->ep_out, GFP_KERNEL); + if (!stream->req_out) + goto err_out; + + stream->req_status = usb_ep_alloc_request(fu->ep_status, GFP_KERNEL); + if (!stream->req_status) + goto err_sts; + + return 0; +err_sts: + usb_ep_free_request(fu->ep_status, stream->req_status); + stream->req_status = NULL; +err_out: + usb_ep_free_request(fu->ep_out, stream->req_out); + stream->req_out = NULL; +out: + return -ENOMEM; +} + +static int uasp_alloc_cmd(struct f_uas *fu) +{ + fu->cmd.req = usb_ep_alloc_request(fu->ep_cmd, GFP_KERNEL); + if (!fu->cmd.req) + goto err; + + fu->cmd.buf = kmalloc(fu->ep_cmd->maxpacket, GFP_KERNEL); + if (!fu->cmd.buf) + goto err_buf; + + fu->cmd.req->complete = uasp_cmd_complete; + fu->cmd.req->buf = fu->cmd.buf; + fu->cmd.req->length = fu->ep_cmd->maxpacket; + fu->cmd.req->context = fu; + return 0; + +err_buf: + usb_ep_free_request(fu->ep_cmd, fu->cmd.req); +err: + return -ENOMEM; +} + +static void uasp_setup_stream_res(struct f_uas *fu, int max_streams) +{ + int i; + + for (i = 0; i < max_streams; i++) { + struct uas_stream *s = &fu->stream[i]; + + s->req_in->stream_id = i + 1; + s->req_out->stream_id = i + 1; + s->req_status->stream_id = i + 1; + } +} + +static int uasp_prepare_reqs(struct f_uas *fu) +{ + int ret; + int i; + int max_streams; + + if (fu->flags & USBG_USE_STREAMS) + max_streams = UASP_SS_EP_COMP_NUM_STREAMS; + else + max_streams = 1; + + for (i = 0; i < max_streams; i++) { + ret = uasp_alloc_stream_res(fu, &fu->stream[i]); + if (ret) + goto err_cleanup; + } + + ret = uasp_alloc_cmd(fu); + if (ret) + goto err_free_stream; + uasp_setup_stream_res(fu, max_streams); + + ret = usb_ep_queue(fu->ep_cmd, fu->cmd.req, GFP_ATOMIC); + if (ret) + goto err_free_stream; + + return 0; + +err_free_stream: + uasp_free_cmdreq(fu); + +err_cleanup: + if (i) { + do { + uasp_cleanup_one_stream(fu, &fu->stream[i - 1]); + i--; + } while (i); + } + pr_err("UASP: endpoint setup failed\n"); + return ret; +} + +static void uasp_set_alt(struct f_uas *fu) +{ + struct usb_function *f = &fu->function; + struct usb_gadget *gadget = f->config->cdev->gadget; + int ret; + + fu->flags = USBG_IS_UAS; + + if (gadget->speed == USB_SPEED_SUPER) + fu->flags |= USBG_USE_STREAMS; + + config_ep_by_speed(gadget, f, fu->ep_in); + ret = usb_ep_enable(fu->ep_in); + if (ret) + goto err_b_in; + + config_ep_by_speed(gadget, f, fu->ep_out); + ret = usb_ep_enable(fu->ep_out); + if (ret) + goto err_b_out; + + config_ep_by_speed(gadget, f, fu->ep_cmd); + ret = usb_ep_enable(fu->ep_cmd); + if (ret) + goto err_cmd; + config_ep_by_speed(gadget, f, fu->ep_status); + ret = usb_ep_enable(fu->ep_status); + if (ret) + goto err_status; + + ret = uasp_prepare_reqs(fu); + if (ret) + goto err_wq; + fu->flags |= USBG_ENABLED; + + pr_info("Using the UAS protocol\n"); + return; +err_wq: + usb_ep_disable(fu->ep_status); +err_status: + usb_ep_disable(fu->ep_cmd); +err_cmd: + usb_ep_disable(fu->ep_out); +err_b_out: + usb_ep_disable(fu->ep_in); +err_b_in: + fu->flags = 0; +} + +static int get_cmd_dir(const unsigned char *cdb) +{ + int ret; + + switch (cdb[0]) { + case READ_6: + case READ_10: + case READ_12: + case READ_16: + case INQUIRY: + case MODE_SENSE: + case MODE_SENSE_10: + case SERVICE_ACTION_IN_16: + case MAINTENANCE_IN: + case PERSISTENT_RESERVE_IN: + case SECURITY_PROTOCOL_IN: + case ACCESS_CONTROL_IN: + case REPORT_LUNS: + case READ_BLOCK_LIMITS: + case READ_POSITION: + case READ_CAPACITY: + case READ_TOC: + case READ_FORMAT_CAPACITIES: + case REQUEST_SENSE: + ret = DMA_FROM_DEVICE; + break; + + case WRITE_6: + case WRITE_10: + case WRITE_12: + case WRITE_16: + case MODE_SELECT: + case MODE_SELECT_10: + case WRITE_VERIFY: + case WRITE_VERIFY_12: + case PERSISTENT_RESERVE_OUT: + case MAINTENANCE_OUT: + case SECURITY_PROTOCOL_OUT: + case ACCESS_CONTROL_OUT: + ret = DMA_TO_DEVICE; + break; + case ALLOW_MEDIUM_REMOVAL: + case TEST_UNIT_READY: + case SYNCHRONIZE_CACHE: + case START_STOP: + case ERASE: + case REZERO_UNIT: + case SEEK_10: + case SPACE: + case VERIFY: + case WRITE_FILEMARKS: + ret = DMA_NONE; + break; + default: +#define CMD_DIR_MSG "target: Unknown data direction for SCSI Opcode 0x%02x\n" + pr_warn(CMD_DIR_MSG, cdb[0]); +#undef CMD_DIR_MSG + ret = -EINVAL; + } + return ret; +} + +static void usbg_data_write_cmpl(struct usb_ep *ep, struct usb_request *req) +{ + struct usbg_cmd *cmd = req->context; + struct se_cmd *se_cmd = &cmd->se_cmd; + + if (req->status < 0) { + pr_err("%s() state %d transfer failed\n", __func__, cmd->state); + goto cleanup; + } + + if (req->num_sgs == 0) { + sg_copy_from_buffer(se_cmd->t_data_sg, + se_cmd->t_data_nents, + cmd->data_buf, + se_cmd->data_length); + } + + complete(&cmd->write_complete); + return; + +cleanup: + usbg_cleanup_cmd(cmd); +} + +static int usbg_prepare_w_request(struct usbg_cmd *cmd, struct usb_request *req) +{ + struct se_cmd *se_cmd = &cmd->se_cmd; + struct f_uas *fu = cmd->fu; + struct usb_gadget *gadget = fuas_to_gadget(fu); + + if (!gadget->sg_supported) { + cmd->data_buf = kmalloc(se_cmd->data_length, GFP_ATOMIC); + if (!cmd->data_buf) + return -ENOMEM; + + req->buf = cmd->data_buf; + } else { + req->buf = NULL; + req->num_sgs = se_cmd->t_data_nents; + req->sg = se_cmd->t_data_sg; + } + + req->complete = usbg_data_write_cmpl; + req->length = se_cmd->data_length; + req->context = cmd; + return 0; +} + +static int usbg_send_status_response(struct se_cmd *se_cmd) +{ + struct usbg_cmd *cmd = container_of(se_cmd, struct usbg_cmd, + se_cmd); + struct f_uas *fu = cmd->fu; + + if (fu->flags & USBG_IS_BOT) + return bot_send_status_response(cmd); + else + return uasp_send_status_response(cmd); +} + +static int usbg_send_write_request(struct se_cmd *se_cmd) +{ + struct usbg_cmd *cmd = container_of(se_cmd, struct usbg_cmd, + se_cmd); + struct f_uas *fu = cmd->fu; + + if (fu->flags & USBG_IS_BOT) + return bot_send_write_request(cmd); + else + return uasp_send_write_request(cmd); +} + +static int usbg_send_read_response(struct se_cmd *se_cmd) +{ + struct usbg_cmd *cmd = container_of(se_cmd, struct usbg_cmd, + se_cmd); + struct f_uas *fu = cmd->fu; + + if (fu->flags & USBG_IS_BOT) + return bot_send_read_response(cmd); + else + return uasp_send_read_response(cmd); +} + +static void usbg_cmd_work(struct work_struct *work) +{ + struct usbg_cmd *cmd = container_of(work, struct usbg_cmd, work); + struct se_cmd *se_cmd; + struct tcm_usbg_nexus *tv_nexus; + struct usbg_tpg *tpg; + int dir; + + se_cmd = &cmd->se_cmd; + tpg = cmd->fu->tpg; + tv_nexus = tpg->tpg_nexus; + dir = get_cmd_dir(cmd->cmd_buf); + if (dir < 0) { + transport_init_se_cmd(se_cmd, + tv_nexus->tvn_se_sess->se_tpg->se_tpg_tfo, + tv_nexus->tvn_se_sess, cmd->data_len, DMA_NONE, + cmd->prio_attr, cmd->sense_iu.sense); + goto out; + } + + if (target_submit_cmd(se_cmd, tv_nexus->tvn_se_sess, + cmd->cmd_buf, cmd->sense_iu.sense, cmd->unpacked_lun, + 0, cmd->prio_attr, dir, TARGET_SCF_UNKNOWN_SIZE) < 0) + goto out; + + return; + +out: + transport_send_check_condition_and_sense(se_cmd, + TCM_UNSUPPORTED_SCSI_OPCODE, 1); + usbg_cleanup_cmd(cmd); +} + +static int usbg_submit_command(struct f_uas *fu, + void *cmdbuf, unsigned int len) +{ + struct command_iu *cmd_iu = cmdbuf; + struct usbg_cmd *cmd; + struct usbg_tpg *tpg; + struct tcm_usbg_nexus *tv_nexus; + u32 cmd_len; + + if (cmd_iu->iu_id != IU_ID_COMMAND) { + pr_err("Unsupported type %d\n", cmd_iu->iu_id); + return -EINVAL; + } + + cmd = kzalloc(sizeof(*cmd), GFP_ATOMIC); + if (!cmd) + return -ENOMEM; + + cmd->fu = fu; + + /* XXX until I figure out why I can't free in on complete */ + kref_init(&cmd->ref); + kref_get(&cmd->ref); + + tpg = fu->tpg; + cmd_len = (cmd_iu->len & ~0x3) + 16; + if (cmd_len > USBG_MAX_CMD) + goto err; + + memcpy(cmd->cmd_buf, cmd_iu->cdb, cmd_len); + + cmd->tag = be16_to_cpup(&cmd_iu->tag); + cmd->se_cmd.tag = cmd->tag; + if (fu->flags & USBG_USE_STREAMS) { + if (cmd->tag > UASP_SS_EP_COMP_NUM_STREAMS) + goto err; + if (!cmd->tag) + cmd->stream = &fu->stream[0]; + else + cmd->stream = &fu->stream[cmd->tag - 1]; + } else { + cmd->stream = &fu->stream[0]; + } + + tv_nexus = tpg->tpg_nexus; + if (!tv_nexus) { + pr_err("Missing nexus, ignoring command\n"); + goto err; + } + + switch (cmd_iu->prio_attr & 0x7) { + case UAS_HEAD_TAG: + cmd->prio_attr = TCM_HEAD_TAG; + break; + case UAS_ORDERED_TAG: + cmd->prio_attr = TCM_ORDERED_TAG; + break; + case UAS_ACA: + cmd->prio_attr = TCM_ACA_TAG; + break; + default: + pr_debug_once("Unsupported prio_attr: %02x.\n", + cmd_iu->prio_attr); + case UAS_SIMPLE_TAG: + cmd->prio_attr = TCM_SIMPLE_TAG; + break; + } + + cmd->unpacked_lun = scsilun_to_int(&cmd_iu->lun); + + INIT_WORK(&cmd->work, usbg_cmd_work); + queue_work(tpg->workqueue, &cmd->work); + + return 0; +err: + kfree(cmd); + return -EINVAL; +} + +static void bot_cmd_work(struct work_struct *work) +{ + struct usbg_cmd *cmd = container_of(work, struct usbg_cmd, work); + struct se_cmd *se_cmd; + struct tcm_usbg_nexus *tv_nexus; + struct usbg_tpg *tpg; + int dir; + + se_cmd = &cmd->se_cmd; + tpg = cmd->fu->tpg; + tv_nexus = tpg->tpg_nexus; + dir = get_cmd_dir(cmd->cmd_buf); + if (dir < 0) { + transport_init_se_cmd(se_cmd, + tv_nexus->tvn_se_sess->se_tpg->se_tpg_tfo, + tv_nexus->tvn_se_sess, cmd->data_len, DMA_NONE, + cmd->prio_attr, cmd->sense_iu.sense); + goto out; + } + + if (target_submit_cmd(se_cmd, tv_nexus->tvn_se_sess, + cmd->cmd_buf, cmd->sense_iu.sense, cmd->unpacked_lun, + cmd->data_len, cmd->prio_attr, dir, 0) < 0) + goto out; + + return; + +out: + transport_send_check_condition_and_sense(se_cmd, + TCM_UNSUPPORTED_SCSI_OPCODE, 1); + usbg_cleanup_cmd(cmd); +} + +static int bot_submit_command(struct f_uas *fu, + void *cmdbuf, unsigned int len) +{ + struct bulk_cb_wrap *cbw = cmdbuf; + struct usbg_cmd *cmd; + struct usbg_tpg *tpg; + struct tcm_usbg_nexus *tv_nexus; + u32 cmd_len; + + if (cbw->Signature != cpu_to_le32(US_BULK_CB_SIGN)) { + pr_err("Wrong signature on CBW\n"); + return -EINVAL; + } + if (len != 31) { + pr_err("Wrong length for CBW\n"); + return -EINVAL; + } + + cmd_len = cbw->Length; + if (cmd_len < 1 || cmd_len > 16) + return -EINVAL; + + cmd = kzalloc(sizeof(*cmd), GFP_ATOMIC); + if (!cmd) + return -ENOMEM; + + cmd->fu = fu; + + /* XXX until I figure out why I can't free in on complete */ + kref_init(&cmd->ref); + kref_get(&cmd->ref); + + tpg = fu->tpg; + + memcpy(cmd->cmd_buf, cbw->CDB, cmd_len); + + cmd->bot_tag = cbw->Tag; + + tv_nexus = tpg->tpg_nexus; + if (!tv_nexus) { + pr_err("Missing nexus, ignoring command\n"); + goto err; + } + + cmd->prio_attr = TCM_SIMPLE_TAG; + cmd->unpacked_lun = cbw->Lun; + cmd->is_read = cbw->Flags & US_BULK_FLAG_IN ? 1 : 0; + cmd->data_len = le32_to_cpu(cbw->DataTransferLength); + cmd->se_cmd.tag = le32_to_cpu(cmd->bot_tag); + + INIT_WORK(&cmd->work, bot_cmd_work); + queue_work(tpg->workqueue, &cmd->work); + + return 0; +err: + kfree(cmd); + return -EINVAL; +} + +/* Start fabric.c code */ + +static int usbg_check_true(struct se_portal_group *se_tpg) +{ + return 1; +} + +static int usbg_check_false(struct se_portal_group *se_tpg) +{ + return 0; +} + +static char *usbg_get_fabric_name(void) +{ + return "usb_gadget"; +} + +static char *usbg_get_fabric_wwn(struct se_portal_group *se_tpg) +{ + struct usbg_tpg *tpg = container_of(se_tpg, + struct usbg_tpg, se_tpg); + struct usbg_tport *tport = tpg->tport; + + return &tport->tport_name[0]; +} + +static u16 usbg_get_tag(struct se_portal_group *se_tpg) +{ + struct usbg_tpg *tpg = container_of(se_tpg, + struct usbg_tpg, se_tpg); + return tpg->tport_tpgt; +} + +static u32 usbg_tpg_get_inst_index(struct se_portal_group *se_tpg) +{ + return 1; +} + +static void usbg_cmd_release(struct kref *ref) +{ + struct usbg_cmd *cmd = container_of(ref, struct usbg_cmd, + ref); + + transport_generic_free_cmd(&cmd->se_cmd, 0); +} + +static void usbg_release_cmd(struct se_cmd *se_cmd) +{ + struct usbg_cmd *cmd = container_of(se_cmd, struct usbg_cmd, + se_cmd); + kfree(cmd->data_buf); + kfree(cmd); +} + +static int usbg_shutdown_session(struct se_session *se_sess) +{ + return 0; +} + +static void usbg_close_session(struct se_session *se_sess) +{ +} + +static u32 usbg_sess_get_index(struct se_session *se_sess) +{ + return 0; +} + +/* + * XXX Error recovery: return != 0 if we expect writes. Dunno when that could be + */ +static int usbg_write_pending_status(struct se_cmd *se_cmd) +{ + return 0; +} + +static void usbg_set_default_node_attrs(struct se_node_acl *nacl) +{ +} + +static int usbg_get_cmd_state(struct se_cmd *se_cmd) +{ + return 0; +} + +static void usbg_queue_tm_rsp(struct se_cmd *se_cmd) +{ +} + +static void usbg_aborted_task(struct se_cmd *se_cmd) +{ +} + +static const char *usbg_check_wwn(const char *name) +{ + const char *n; + unsigned int len; + + n = strstr(name, "naa."); + if (!n) + return NULL; + n += 4; + len = strlen(n); + if (len == 0 || len > USBG_NAMELEN - 1) + return NULL; + return n; +} + +static int usbg_init_nodeacl(struct se_node_acl *se_nacl, const char *name) +{ + if (!usbg_check_wwn(name)) + return -EINVAL; + return 0; +} + +static struct se_portal_group *usbg_make_tpg( + struct se_wwn *wwn, + struct config_group *group, + const char *name) +{ + struct usbg_tport *tport = container_of(wwn, struct usbg_tport, + tport_wwn); + struct usbg_tpg *tpg; + unsigned long tpgt; + int ret; + struct f_tcm_opts *opts; + unsigned i; + + if (strstr(name, "tpgt_") != name) + return ERR_PTR(-EINVAL); + if (kstrtoul(name + 5, 0, &tpgt) || tpgt > UINT_MAX) + return ERR_PTR(-EINVAL); + ret = -ENODEV; + mutex_lock(&tpg_instances_lock); + for (i = 0; i < TPG_INSTANCES; ++i) + if (tpg_instances[i].func_inst && !tpg_instances[i].tpg) + break; + if (i == TPG_INSTANCES) + goto unlock_inst; + + opts = container_of(tpg_instances[i].func_inst, struct f_tcm_opts, + func_inst); + mutex_lock(&opts->dep_lock); + if (!opts->ready) + goto unlock_dep; + + if (opts->has_dep) { + if (!try_module_get(opts->dependent)) + goto unlock_dep; + } else { + ret = configfs_depend_item_unlocked( + group->cg_subsys, + &opts->func_inst.group.cg_item); + if (ret) + goto unlock_dep; + } + + tpg = kzalloc(sizeof(struct usbg_tpg), GFP_KERNEL); + ret = -ENOMEM; + if (!tpg) + goto unref_dep; + mutex_init(&tpg->tpg_mutex); + atomic_set(&tpg->tpg_port_count, 0); + tpg->workqueue = alloc_workqueue("tcm_usb_gadget", 0, 1); + if (!tpg->workqueue) + goto free_tpg; + + tpg->tport = tport; + tpg->tport_tpgt = tpgt; + + /* + * SPC doesn't assign a protocol identifier for USB-SCSI, so we + * pretend to be SAS.. + */ + ret = core_tpg_register(wwn, &tpg->se_tpg, SCSI_PROTOCOL_SAS); + if (ret < 0) + goto free_workqueue; + + tpg_instances[i].tpg = tpg; + tpg->fi = tpg_instances[i].func_inst; + mutex_unlock(&opts->dep_lock); + mutex_unlock(&tpg_instances_lock); + return &tpg->se_tpg; + +free_workqueue: + destroy_workqueue(tpg->workqueue); +free_tpg: + kfree(tpg); +unref_dep: + if (opts->has_dep) + module_put(opts->dependent); + else + configfs_undepend_item_unlocked(&opts->func_inst.group.cg_item); +unlock_dep: + mutex_unlock(&opts->dep_lock); +unlock_inst: + mutex_unlock(&tpg_instances_lock); + + return ERR_PTR(ret); +} + +static int tcm_usbg_drop_nexus(struct usbg_tpg *); + +static void usbg_drop_tpg(struct se_portal_group *se_tpg) +{ + struct usbg_tpg *tpg = container_of(se_tpg, + struct usbg_tpg, se_tpg); + unsigned i; + struct f_tcm_opts *opts; + + tcm_usbg_drop_nexus(tpg); + core_tpg_deregister(se_tpg); + destroy_workqueue(tpg->workqueue); + + mutex_lock(&tpg_instances_lock); + for (i = 0; i < TPG_INSTANCES; ++i) + if (tpg_instances[i].tpg == tpg) + break; + if (i < TPG_INSTANCES) + tpg_instances[i].tpg = NULL; + opts = container_of(tpg_instances[i].func_inst, + struct f_tcm_opts, func_inst); + mutex_lock(&opts->dep_lock); + if (opts->has_dep) + module_put(opts->dependent); + else + configfs_undepend_item_unlocked(&opts->func_inst.group.cg_item); + mutex_unlock(&opts->dep_lock); + mutex_unlock(&tpg_instances_lock); + + kfree(tpg); +} + +static struct se_wwn *usbg_make_tport( + struct target_fabric_configfs *tf, + struct config_group *group, + const char *name) +{ + struct usbg_tport *tport; + const char *wnn_name; + u64 wwpn = 0; + + wnn_name = usbg_check_wwn(name); + if (!wnn_name) + return ERR_PTR(-EINVAL); + + tport = kzalloc(sizeof(struct usbg_tport), GFP_KERNEL); + if (!(tport)) + return ERR_PTR(-ENOMEM); + + tport->tport_wwpn = wwpn; + snprintf(tport->tport_name, sizeof(tport->tport_name), "%s", wnn_name); + return &tport->tport_wwn; +} + +static void usbg_drop_tport(struct se_wwn *wwn) +{ + struct usbg_tport *tport = container_of(wwn, + struct usbg_tport, tport_wwn); + kfree(tport); +} + +/* + * If somebody feels like dropping the version property, go ahead. + */ +static ssize_t usbg_wwn_version_show(struct config_item *item, char *page) +{ + return sprintf(page, "usb-gadget fabric module\n"); +} + +CONFIGFS_ATTR_RO(usbg_wwn_, version); + +static struct configfs_attribute *usbg_wwn_attrs[] = { + &usbg_wwn_attr_version, + NULL, +}; + +static ssize_t tcm_usbg_tpg_enable_show(struct config_item *item, char *page) +{ + struct se_portal_group *se_tpg = to_tpg(item); + struct usbg_tpg *tpg = container_of(se_tpg, struct usbg_tpg, se_tpg); + + return snprintf(page, PAGE_SIZE, "%u\n", tpg->gadget_connect); +} + +static int usbg_attach(struct usbg_tpg *); +static void usbg_detach(struct usbg_tpg *); + +static ssize_t tcm_usbg_tpg_enable_store(struct config_item *item, + const char *page, size_t count) +{ + struct se_portal_group *se_tpg = to_tpg(item); + struct usbg_tpg *tpg = container_of(se_tpg, struct usbg_tpg, se_tpg); + bool op; + ssize_t ret; + + ret = strtobool(page, &op); + if (ret) + return ret; + + if ((op && tpg->gadget_connect) || (!op && !tpg->gadget_connect)) + return -EINVAL; + + if (op) + ret = usbg_attach(tpg); + else + usbg_detach(tpg); + if (ret) + return ret; + + tpg->gadget_connect = op; + + return count; +} + +static ssize_t tcm_usbg_tpg_nexus_show(struct config_item *item, char *page) +{ + struct se_portal_group *se_tpg = to_tpg(item); + struct usbg_tpg *tpg = container_of(se_tpg, struct usbg_tpg, se_tpg); + struct tcm_usbg_nexus *tv_nexus; + ssize_t ret; + + mutex_lock(&tpg->tpg_mutex); + tv_nexus = tpg->tpg_nexus; + if (!tv_nexus) { + ret = -ENODEV; + goto out; + } + ret = snprintf(page, PAGE_SIZE, "%s\n", + tv_nexus->tvn_se_sess->se_node_acl->initiatorname); +out: + mutex_unlock(&tpg->tpg_mutex); + return ret; +} + +static int tcm_usbg_make_nexus(struct usbg_tpg *tpg, char *name) +{ + struct se_portal_group *se_tpg; + struct tcm_usbg_nexus *tv_nexus; + int ret; + + mutex_lock(&tpg->tpg_mutex); + if (tpg->tpg_nexus) { + ret = -EEXIST; + pr_debug("tpg->tpg_nexus already exists\n"); + goto err_unlock; + } + se_tpg = &tpg->se_tpg; + + ret = -ENOMEM; + tv_nexus = kzalloc(sizeof(*tv_nexus), GFP_KERNEL); + if (!tv_nexus) + goto err_unlock; + tv_nexus->tvn_se_sess = transport_init_session(TARGET_PROT_NORMAL); + if (IS_ERR(tv_nexus->tvn_se_sess)) + goto err_free; + + /* + * Since we are running in 'demo mode' this call with generate a + * struct se_node_acl for the tcm_vhost struct se_portal_group with + * the SCSI Initiator port name of the passed configfs group 'name'. + */ + tv_nexus->tvn_se_sess->se_node_acl = core_tpg_check_initiator_node_acl( + se_tpg, name); + if (!tv_nexus->tvn_se_sess->se_node_acl) { +#define MAKE_NEXUS_MSG "core_tpg_check_initiator_node_acl() failed for %s\n" + pr_debug(MAKE_NEXUS_MSG, name); +#undef MAKE_NEXUS_MSG + goto err_session; + } + /* + * Now register the TCM vHost virtual I_T Nexus as active. + */ + transport_register_session(se_tpg, tv_nexus->tvn_se_sess->se_node_acl, + tv_nexus->tvn_se_sess, tv_nexus); + tpg->tpg_nexus = tv_nexus; + mutex_unlock(&tpg->tpg_mutex); + return 0; + +err_session: + transport_free_session(tv_nexus->tvn_se_sess); +err_free: + kfree(tv_nexus); +err_unlock: + mutex_unlock(&tpg->tpg_mutex); + return ret; +} + +static int tcm_usbg_drop_nexus(struct usbg_tpg *tpg) +{ + struct se_session *se_sess; + struct tcm_usbg_nexus *tv_nexus; + int ret = -ENODEV; + + mutex_lock(&tpg->tpg_mutex); + tv_nexus = tpg->tpg_nexus; + if (!tv_nexus) + goto out; + + se_sess = tv_nexus->tvn_se_sess; + if (!se_sess) + goto out; + + if (atomic_read(&tpg->tpg_port_count)) { + ret = -EPERM; +#define MSG "Unable to remove Host I_T Nexus with active TPG port count: %d\n" + pr_err(MSG, atomic_read(&tpg->tpg_port_count)); +#undef MSG + goto out; + } + + pr_debug("Removing I_T Nexus to Initiator Port: %s\n", + tv_nexus->tvn_se_sess->se_node_acl->initiatorname); + /* + * Release the SCSI I_T Nexus to the emulated vHost Target Port + */ + transport_deregister_session(tv_nexus->tvn_se_sess); + tpg->tpg_nexus = NULL; + + kfree(tv_nexus); + ret = 0; +out: + mutex_unlock(&tpg->tpg_mutex); + return ret; +} + +static ssize_t tcm_usbg_tpg_nexus_store(struct config_item *item, + const char *page, size_t count) +{ + struct se_portal_group *se_tpg = to_tpg(item); + struct usbg_tpg *tpg = container_of(se_tpg, struct usbg_tpg, se_tpg); + unsigned char i_port[USBG_NAMELEN], *ptr; + int ret; + + if (!strncmp(page, "NULL", 4)) { + ret = tcm_usbg_drop_nexus(tpg); + return (!ret) ? count : ret; + } + if (strlen(page) >= USBG_NAMELEN) { + +#define NEXUS_STORE_MSG "Emulated NAA Sas Address: %s, exceeds max: %d\n" + pr_err(NEXUS_STORE_MSG, page, USBG_NAMELEN); +#undef NEXUS_STORE_MSG + return -EINVAL; + } + snprintf(i_port, USBG_NAMELEN, "%s", page); + + ptr = strstr(i_port, "naa."); + if (!ptr) { + pr_err("Missing 'naa.' prefix\n"); + return -EINVAL; + } + + if (i_port[strlen(i_port) - 1] == '\n') + i_port[strlen(i_port) - 1] = '\0'; + + ret = tcm_usbg_make_nexus(tpg, &i_port[0]); + if (ret < 0) + return ret; + return count; +} + +CONFIGFS_ATTR(tcm_usbg_tpg_, enable); +CONFIGFS_ATTR(tcm_usbg_tpg_, nexus); + +static struct configfs_attribute *usbg_base_attrs[] = { + &tcm_usbg_tpg_attr_enable, + &tcm_usbg_tpg_attr_nexus, + NULL, +}; + +static int usbg_port_link(struct se_portal_group *se_tpg, struct se_lun *lun) +{ + struct usbg_tpg *tpg = container_of(se_tpg, struct usbg_tpg, se_tpg); + + atomic_inc(&tpg->tpg_port_count); + smp_mb__after_atomic(); + return 0; +} + +static void usbg_port_unlink(struct se_portal_group *se_tpg, + struct se_lun *se_lun) +{ + struct usbg_tpg *tpg = container_of(se_tpg, struct usbg_tpg, se_tpg); + + atomic_dec(&tpg->tpg_port_count); + smp_mb__after_atomic(); +} + +static int usbg_check_stop_free(struct se_cmd *se_cmd) +{ + struct usbg_cmd *cmd = container_of(se_cmd, struct usbg_cmd, + se_cmd); + + kref_put(&cmd->ref, usbg_cmd_release); + return 1; +} + +static const struct target_core_fabric_ops usbg_ops = { + .module = THIS_MODULE, + .name = "usb_gadget", + .get_fabric_name = usbg_get_fabric_name, + .tpg_get_wwn = usbg_get_fabric_wwn, + .tpg_get_tag = usbg_get_tag, + .tpg_check_demo_mode = usbg_check_true, + .tpg_check_demo_mode_cache = usbg_check_false, + .tpg_check_demo_mode_write_protect = usbg_check_false, + .tpg_check_prod_mode_write_protect = usbg_check_false, + .tpg_get_inst_index = usbg_tpg_get_inst_index, + .release_cmd = usbg_release_cmd, + .shutdown_session = usbg_shutdown_session, + .close_session = usbg_close_session, + .sess_get_index = usbg_sess_get_index, + .sess_get_initiator_sid = NULL, + .write_pending = usbg_send_write_request, + .write_pending_status = usbg_write_pending_status, + .set_default_node_attributes = usbg_set_default_node_attrs, + .get_cmd_state = usbg_get_cmd_state, + .queue_data_in = usbg_send_read_response, + .queue_status = usbg_send_status_response, + .queue_tm_rsp = usbg_queue_tm_rsp, + .aborted_task = usbg_aborted_task, + .check_stop_free = usbg_check_stop_free, + + .fabric_make_wwn = usbg_make_tport, + .fabric_drop_wwn = usbg_drop_tport, + .fabric_make_tpg = usbg_make_tpg, + .fabric_drop_tpg = usbg_drop_tpg, + .fabric_post_link = usbg_port_link, + .fabric_pre_unlink = usbg_port_unlink, + .fabric_init_nodeacl = usbg_init_nodeacl, + + .tfc_wwn_attrs = usbg_wwn_attrs, + .tfc_tpg_base_attrs = usbg_base_attrs, +}; + +/* Start gadget.c code */ + +static struct usb_interface_descriptor bot_intf_desc = { + .bLength = sizeof(bot_intf_desc), + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 2, + .bAlternateSetting = USB_G_ALT_INT_BBB, + .bInterfaceClass = USB_CLASS_MASS_STORAGE, + .bInterfaceSubClass = USB_SC_SCSI, + .bInterfaceProtocol = USB_PR_BULK, +}; + +static struct usb_interface_descriptor uasp_intf_desc = { + .bLength = sizeof(uasp_intf_desc), + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 4, + .bAlternateSetting = USB_G_ALT_INT_UAS, + .bInterfaceClass = USB_CLASS_MASS_STORAGE, + .bInterfaceSubClass = USB_SC_SCSI, + .bInterfaceProtocol = USB_PR_UAS, +}; + +static struct usb_endpoint_descriptor uasp_bi_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor uasp_fs_bi_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_pipe_usage_descriptor uasp_bi_pipe_desc = { + .bLength = sizeof(uasp_bi_pipe_desc), + .bDescriptorType = USB_DT_PIPE_USAGE, + .bPipeID = DATA_IN_PIPE_ID, +}; + +static struct usb_endpoint_descriptor uasp_ss_bi_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor uasp_bi_ep_comp_desc = { + .bLength = sizeof(uasp_bi_ep_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .bMaxBurst = 0, + .bmAttributes = UASP_SS_EP_COMP_LOG_STREAMS, + .wBytesPerInterval = 0, +}; + +static struct usb_ss_ep_comp_descriptor bot_bi_ep_comp_desc = { + .bLength = sizeof(bot_bi_ep_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .bMaxBurst = 0, +}; + +static struct usb_endpoint_descriptor uasp_bo_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor uasp_fs_bo_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_pipe_usage_descriptor uasp_bo_pipe_desc = { + .bLength = sizeof(uasp_bo_pipe_desc), + .bDescriptorType = USB_DT_PIPE_USAGE, + .bPipeID = DATA_OUT_PIPE_ID, +}; + +static struct usb_endpoint_descriptor uasp_ss_bo_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(0x400), +}; + +static struct usb_ss_ep_comp_descriptor uasp_bo_ep_comp_desc = { + .bLength = sizeof(uasp_bo_ep_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .bmAttributes = UASP_SS_EP_COMP_LOG_STREAMS, +}; + +static struct usb_ss_ep_comp_descriptor bot_bo_ep_comp_desc = { + .bLength = sizeof(bot_bo_ep_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, +}; + +static struct usb_endpoint_descriptor uasp_status_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor uasp_fs_status_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_pipe_usage_descriptor uasp_status_pipe_desc = { + .bLength = sizeof(uasp_status_pipe_desc), + .bDescriptorType = USB_DT_PIPE_USAGE, + .bPipeID = STATUS_PIPE_ID, +}; + +static struct usb_endpoint_descriptor uasp_ss_status_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor uasp_status_in_ep_comp_desc = { + .bLength = sizeof(uasp_status_in_ep_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .bmAttributes = UASP_SS_EP_COMP_LOG_STREAMS, +}; + +static struct usb_endpoint_descriptor uasp_cmd_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor uasp_fs_cmd_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_pipe_usage_descriptor uasp_cmd_pipe_desc = { + .bLength = sizeof(uasp_cmd_pipe_desc), + .bDescriptorType = USB_DT_PIPE_USAGE, + .bPipeID = CMD_PIPE_ID, +}; + +static struct usb_endpoint_descriptor uasp_ss_cmd_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor uasp_cmd_comp_desc = { + .bLength = sizeof(uasp_cmd_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, +}; + +static struct usb_descriptor_header *uasp_fs_function_desc[] = { + (struct usb_descriptor_header *) &bot_intf_desc, + (struct usb_descriptor_header *) &uasp_fs_bi_desc, + (struct usb_descriptor_header *) &uasp_fs_bo_desc, + + (struct usb_descriptor_header *) &uasp_intf_desc, + (struct usb_descriptor_header *) &uasp_fs_bi_desc, + (struct usb_descriptor_header *) &uasp_bi_pipe_desc, + (struct usb_descriptor_header *) &uasp_fs_bo_desc, + (struct usb_descriptor_header *) &uasp_bo_pipe_desc, + (struct usb_descriptor_header *) &uasp_fs_status_desc, + (struct usb_descriptor_header *) &uasp_status_pipe_desc, + (struct usb_descriptor_header *) &uasp_fs_cmd_desc, + (struct usb_descriptor_header *) &uasp_cmd_pipe_desc, + NULL, +}; + +static struct usb_descriptor_header *uasp_hs_function_desc[] = { + (struct usb_descriptor_header *) &bot_intf_desc, + (struct usb_descriptor_header *) &uasp_bi_desc, + (struct usb_descriptor_header *) &uasp_bo_desc, + + (struct usb_descriptor_header *) &uasp_intf_desc, + (struct usb_descriptor_header *) &uasp_bi_desc, + (struct usb_descriptor_header *) &uasp_bi_pipe_desc, + (struct usb_descriptor_header *) &uasp_bo_desc, + (struct usb_descriptor_header *) &uasp_bo_pipe_desc, + (struct usb_descriptor_header *) &uasp_status_desc, + (struct usb_descriptor_header *) &uasp_status_pipe_desc, + (struct usb_descriptor_header *) &uasp_cmd_desc, + (struct usb_descriptor_header *) &uasp_cmd_pipe_desc, + NULL, +}; + +static struct usb_descriptor_header *uasp_ss_function_desc[] = { + (struct usb_descriptor_header *) &bot_intf_desc, + (struct usb_descriptor_header *) &uasp_ss_bi_desc, + (struct usb_descriptor_header *) &bot_bi_ep_comp_desc, + (struct usb_descriptor_header *) &uasp_ss_bo_desc, + (struct usb_descriptor_header *) &bot_bo_ep_comp_desc, + + (struct usb_descriptor_header *) &uasp_intf_desc, + (struct usb_descriptor_header *) &uasp_ss_bi_desc, + (struct usb_descriptor_header *) &uasp_bi_ep_comp_desc, + (struct usb_descriptor_header *) &uasp_bi_pipe_desc, + (struct usb_descriptor_header *) &uasp_ss_bo_desc, + (struct usb_descriptor_header *) &uasp_bo_ep_comp_desc, + (struct usb_descriptor_header *) &uasp_bo_pipe_desc, + (struct usb_descriptor_header *) &uasp_ss_status_desc, + (struct usb_descriptor_header *) &uasp_status_in_ep_comp_desc, + (struct usb_descriptor_header *) &uasp_status_pipe_desc, + (struct usb_descriptor_header *) &uasp_ss_cmd_desc, + (struct usb_descriptor_header *) &uasp_cmd_comp_desc, + (struct usb_descriptor_header *) &uasp_cmd_pipe_desc, + NULL, +}; + +static struct usb_string tcm_us_strings[] = { + [USB_G_STR_INT_UAS].s = "USB Attached SCSI", + [USB_G_STR_INT_BBB].s = "Bulk Only Transport", + { }, +}; + +static struct usb_gadget_strings tcm_stringtab = { + .language = 0x0409, + .strings = tcm_us_strings, +}; + +static struct usb_gadget_strings *tcm_strings[] = { + &tcm_stringtab, + NULL, +}; + +static int tcm_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_uas *fu = to_f_uas(f); + struct usb_string *us; + struct usb_gadget *gadget = c->cdev->gadget; + struct usb_ep *ep; + struct f_tcm_opts *opts; + int iface; + int ret; + + opts = container_of(f->fi, struct f_tcm_opts, func_inst); + + mutex_lock(&opts->dep_lock); + if (!opts->can_attach) { + mutex_unlock(&opts->dep_lock); + return -ENODEV; + } + mutex_unlock(&opts->dep_lock); + us = usb_gstrings_attach(c->cdev, tcm_strings, + ARRAY_SIZE(tcm_us_strings)); + if (IS_ERR(us)) + return PTR_ERR(us); + bot_intf_desc.iInterface = us[USB_G_STR_INT_BBB].id; + uasp_intf_desc.iInterface = us[USB_G_STR_INT_UAS].id; + + iface = usb_interface_id(c, f); + if (iface < 0) + return iface; + + bot_intf_desc.bInterfaceNumber = iface; + uasp_intf_desc.bInterfaceNumber = iface; + fu->iface = iface; + ep = usb_ep_autoconfig_ss(gadget, &uasp_ss_bi_desc, + &uasp_bi_ep_comp_desc); + if (!ep) + goto ep_fail; + + fu->ep_in = ep; + + ep = usb_ep_autoconfig_ss(gadget, &uasp_ss_bo_desc, + &uasp_bo_ep_comp_desc); + if (!ep) + goto ep_fail; + fu->ep_out = ep; + + ep = usb_ep_autoconfig_ss(gadget, &uasp_ss_status_desc, + &uasp_status_in_ep_comp_desc); + if (!ep) + goto ep_fail; + fu->ep_status = ep; + + ep = usb_ep_autoconfig_ss(gadget, &uasp_ss_cmd_desc, + &uasp_cmd_comp_desc); + if (!ep) + goto ep_fail; + fu->ep_cmd = ep; + + /* Assume endpoint addresses are the same for both speeds */ + uasp_bi_desc.bEndpointAddress = uasp_ss_bi_desc.bEndpointAddress; + uasp_bo_desc.bEndpointAddress = uasp_ss_bo_desc.bEndpointAddress; + uasp_status_desc.bEndpointAddress = + uasp_ss_status_desc.bEndpointAddress; + uasp_cmd_desc.bEndpointAddress = uasp_ss_cmd_desc.bEndpointAddress; + + uasp_fs_bi_desc.bEndpointAddress = uasp_ss_bi_desc.bEndpointAddress; + uasp_fs_bo_desc.bEndpointAddress = uasp_ss_bo_desc.bEndpointAddress; + uasp_fs_status_desc.bEndpointAddress = + uasp_ss_status_desc.bEndpointAddress; + uasp_fs_cmd_desc.bEndpointAddress = uasp_ss_cmd_desc.bEndpointAddress; + + ret = usb_assign_descriptors(f, uasp_fs_function_desc, + uasp_hs_function_desc, uasp_ss_function_desc); + if (ret) + goto ep_fail; + + return 0; +ep_fail: + pr_err("Can't claim all required eps\n"); + + return -ENOTSUPP; +} + +struct guas_setup_wq { + struct work_struct work; + struct f_uas *fu; + unsigned int alt; +}; + +static void tcm_delayed_set_alt(struct work_struct *wq) +{ + struct guas_setup_wq *work = container_of(wq, struct guas_setup_wq, + work); + struct f_uas *fu = work->fu; + int alt = work->alt; + + kfree(work); + + if (fu->flags & USBG_IS_BOT) + bot_cleanup_old_alt(fu); + if (fu->flags & USBG_IS_UAS) + uasp_cleanup_old_alt(fu); + + if (alt == USB_G_ALT_INT_BBB) + bot_set_alt(fu); + else if (alt == USB_G_ALT_INT_UAS) + uasp_set_alt(fu); + usb_composite_setup_continue(fu->function.config->cdev); +} + +static int tcm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_uas *fu = to_f_uas(f); + + if ((alt == USB_G_ALT_INT_BBB) || (alt == USB_G_ALT_INT_UAS)) { + struct guas_setup_wq *work; + + work = kmalloc(sizeof(*work), GFP_ATOMIC); + if (!work) + return -ENOMEM; + INIT_WORK(&work->work, tcm_delayed_set_alt); + work->fu = fu; + work->alt = alt; + schedule_work(&work->work); + return USB_GADGET_DELAYED_STATUS; + } + return -EOPNOTSUPP; +} + +static void tcm_disable(struct usb_function *f) +{ + struct f_uas *fu = to_f_uas(f); + + if (fu->flags & USBG_IS_UAS) + uasp_cleanup_old_alt(fu); + else if (fu->flags & USBG_IS_BOT) + bot_cleanup_old_alt(fu); + fu->flags = 0; +} + +static int tcm_setup(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct f_uas *fu = to_f_uas(f); + + if (!(fu->flags & USBG_IS_BOT)) + return -EOPNOTSUPP; + + return usbg_bot_setup(f, ctrl); +} + +static inline struct f_tcm_opts *to_f_tcm_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_tcm_opts, + func_inst.group); +} + +static void tcm_attr_release(struct config_item *item) +{ + struct f_tcm_opts *opts = to_f_tcm_opts(item); + + usb_put_function_instance(&opts->func_inst); +} + +static struct configfs_item_operations tcm_item_ops = { + .release = tcm_attr_release, +}; + +static struct config_item_type tcm_func_type = { + .ct_item_ops = &tcm_item_ops, + .ct_owner = THIS_MODULE, +}; + +static void tcm_free_inst(struct usb_function_instance *f) +{ + struct f_tcm_opts *opts; + unsigned i; + + opts = container_of(f, struct f_tcm_opts, func_inst); + + mutex_lock(&tpg_instances_lock); + for (i = 0; i < TPG_INSTANCES; ++i) + if (tpg_instances[i].func_inst == f) + break; + if (i < TPG_INSTANCES) + tpg_instances[i].func_inst = NULL; + mutex_unlock(&tpg_instances_lock); + + kfree(opts); +} + +static int tcm_register_callback(struct usb_function_instance *f) +{ + struct f_tcm_opts *opts = container_of(f, struct f_tcm_opts, func_inst); + + mutex_lock(&opts->dep_lock); + opts->can_attach = true; + mutex_unlock(&opts->dep_lock); + + return 0; +} + +static void tcm_unregister_callback(struct usb_function_instance *f) +{ + struct f_tcm_opts *opts = container_of(f, struct f_tcm_opts, func_inst); + + mutex_lock(&opts->dep_lock); + unregister_gadget_item(opts-> + func_inst.group.cg_item.ci_parent->ci_parent); + opts->can_attach = false; + mutex_unlock(&opts->dep_lock); +} + +static int usbg_attach(struct usbg_tpg *tpg) +{ + struct usb_function_instance *f = tpg->fi; + struct f_tcm_opts *opts = container_of(f, struct f_tcm_opts, func_inst); + + if (opts->tcm_register_callback) + return opts->tcm_register_callback(f); + + return 0; +} + +static void usbg_detach(struct usbg_tpg *tpg) +{ + struct usb_function_instance *f = tpg->fi; + struct f_tcm_opts *opts = container_of(f, struct f_tcm_opts, func_inst); + + if (opts->tcm_unregister_callback) + opts->tcm_unregister_callback(f); +} + +static int tcm_set_name(struct usb_function_instance *f, const char *name) +{ + struct f_tcm_opts *opts = container_of(f, struct f_tcm_opts, func_inst); + + pr_debug("tcm: Activating %s\n", name); + + mutex_lock(&opts->dep_lock); + opts->ready = true; + mutex_unlock(&opts->dep_lock); + + return 0; +} + +static struct usb_function_instance *tcm_alloc_inst(void) +{ + struct f_tcm_opts *opts; + int i; + + + opts = kzalloc(sizeof(*opts), GFP_KERNEL); + if (!opts) + return ERR_PTR(-ENOMEM); + + mutex_lock(&tpg_instances_lock); + for (i = 0; i < TPG_INSTANCES; ++i) + if (!tpg_instances[i].func_inst) + break; + + if (i == TPG_INSTANCES) { + mutex_unlock(&tpg_instances_lock); + kfree(opts); + return ERR_PTR(-EBUSY); + } + tpg_instances[i].func_inst = &opts->func_inst; + mutex_unlock(&tpg_instances_lock); + + mutex_init(&opts->dep_lock); + opts->func_inst.set_inst_name = tcm_set_name; + opts->func_inst.free_func_inst = tcm_free_inst; + opts->tcm_register_callback = tcm_register_callback; + opts->tcm_unregister_callback = tcm_unregister_callback; + + config_group_init_type_name(&opts->func_inst.group, "", + &tcm_func_type); + + return &opts->func_inst; +} + +static void tcm_free(struct usb_function *f) +{ + struct f_uas *tcm = to_f_uas(f); + + kfree(tcm); +} + +static void tcm_unbind(struct usb_configuration *c, struct usb_function *f) +{ + usb_free_all_descriptors(f); +} + +static struct usb_function *tcm_alloc(struct usb_function_instance *fi) +{ + struct f_uas *fu; + unsigned i; + + mutex_lock(&tpg_instances_lock); + for (i = 0; i < TPG_INSTANCES; ++i) + if (tpg_instances[i].func_inst == fi) + break; + if (i == TPG_INSTANCES) { + mutex_unlock(&tpg_instances_lock); + return ERR_PTR(-ENODEV); + } + + fu = kzalloc(sizeof(*fu), GFP_KERNEL); + if (!fu) { + mutex_unlock(&tpg_instances_lock); + return ERR_PTR(-ENOMEM); + } + + fu->function.name = "Target Function"; + fu->function.bind = tcm_bind; + fu->function.unbind = tcm_unbind; + fu->function.set_alt = tcm_set_alt; + fu->function.setup = tcm_setup; + fu->function.disable = tcm_disable; + fu->function.free_func = tcm_free; + fu->tpg = tpg_instances[i].tpg; + mutex_unlock(&tpg_instances_lock); + + return &fu->function; +} + +DECLARE_USB_FUNCTION(tcm, tcm_alloc_inst, tcm_alloc); + +static int tcm_init(void) +{ + int ret; + + ret = usb_function_register(&tcmusb_func); + if (ret) + return ret; + + ret = target_register_template(&usbg_ops); + if (ret) + usb_function_unregister(&tcmusb_func); + + return ret; +} +module_init(tcm_init); + +static void tcm_exit(void) +{ + target_unregister_template(&usbg_ops); + usb_function_unregister(&tcmusb_func); +} +module_exit(tcm_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Sebastian Andrzej Siewior"); diff --git a/drivers/usb/gadget/legacy/tcm_usb_gadget.h b/drivers/usb/gadget/function/tcm.h index 0b749e1aa2f1..b75c6f3e1980 100644 --- a/drivers/usb/gadget/legacy/tcm_usb_gadget.h +++ b/drivers/usb/gadget/function/tcm.h @@ -16,8 +16,7 @@ #define UASP_SS_EP_COMP_NUM_STREAMS (1 << UASP_SS_EP_COMP_LOG_STREAMS) enum { - USB_G_STR_CONFIG = USB_GADGET_FIRST_AVAIL_IDX, - USB_G_STR_INT_UAS, + USB_G_STR_INT_UAS = 0, USB_G_STR_INT_BBB, }; @@ -40,6 +39,8 @@ struct usbg_tpg { u32 gadget_connect; struct tcm_usbg_nexus *tpg_nexus; atomic_t tpg_port_count; + + struct usb_function_instance *fi; }; struct usbg_tport { @@ -128,6 +129,4 @@ struct f_uas { struct usb_request *bot_req_out; }; -extern struct usbg_tpg *the_only_tpg_I_currently_have; - -#endif +#endif /* __TARGET_USB_GADGET_H__ */ diff --git a/drivers/usb/gadget/function/u_tcm.h b/drivers/usb/gadget/function/u_tcm.h new file mode 100644 index 000000000000..0bd751e0483f --- /dev/null +++ b/drivers/usb/gadget/function/u_tcm.h @@ -0,0 +1,50 @@ +/* + * u_tcm.h + * + * Utility definitions for the tcm function + * + * Copyright (c) 2015 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz <andrzej.p@xxxxxxxxxxx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef U_TCM_H +#define U_TCM_H + +#include <linux/usb/composite.h> + +/** + * @dependent: optional dependent module. Meant for legacy gadget. + * If non-null its refcount will be increased when a tpg is created and + * decreased when tpg is dropped. + * @dep_lock: lock for dependent module operations. + * @ready: true if the dependent module information is set. + * @can_attach: true a function can be bound to gadget + * @has_dep: true if there is a dependent module + * + */ +struct f_tcm_opts { + struct usb_function_instance func_inst; + struct module *dependent; + struct mutex dep_lock; + bool ready; + bool can_attach; + bool has_dep; + + /* + * Callbacks to be removed when legacy tcm gadget disappears. + * + * If you use the new function registration interface + * programmatically, you MUST set these callbacks to + * something sensible (e.g. probe/remove the composite). + */ + int (*tcm_register_callback)(struct usb_function_instance *); + void (*tcm_unregister_callback)(struct usb_function_instance *); +}; + +#endif /* U_TCM_H */ diff --git a/drivers/usb/gadget/legacy/Kconfig b/drivers/usb/gadget/legacy/Kconfig index 4d682ad7bf23..a23d1b90454c 100644 --- a/drivers/usb/gadget/legacy/Kconfig +++ b/drivers/usb/gadget/legacy/Kconfig @@ -250,6 +250,7 @@ config USB_GADGET_TARGET tristate "USB Gadget Target Fabric Module" depends on TARGET_CORE select USB_LIBCOMPOSITE + select USB_F_TCM help This fabric is an USB gadget. Two USB protocols are supported that is BBB or BOT (Bulk Only Transport) and UAS (USB Attached SCSI). BOT is diff --git a/drivers/usb/gadget/legacy/tcm_usb_gadget.c b/drivers/usb/gadget/legacy/tcm_usb_gadget.c index 7857fa411636..0b0bb98319cd 100644 --- a/drivers/usb/gadget/legacy/tcm_usb_gadget.c +++ b/drivers/usb/gadget/legacy/tcm_usb_gadget.c @@ -21,1953 +21,10 @@ #include <target/target_core_fabric.h> #include <asm/unaligned.h> -#include "tcm_usb_gadget.h" +#include "u_tcm.h" USB_GADGET_COMPOSITE_OPTIONS(); -static inline struct f_uas *to_f_uas(struct usb_function *f) -{ - return container_of(f, struct f_uas, function); -} - -static void usbg_cmd_release(struct kref *); - -static inline void usbg_cleanup_cmd(struct usbg_cmd *cmd) -{ - kref_put(&cmd->ref, usbg_cmd_release); -} - -/* Start bot.c code */ - -static int bot_enqueue_cmd_cbw(struct f_uas *fu) -{ - int ret; - - if (fu->flags & USBG_BOT_CMD_PEND) - return 0; - - ret = usb_ep_queue(fu->ep_out, fu->cmd.req, GFP_ATOMIC); - if (!ret) - fu->flags |= USBG_BOT_CMD_PEND; - return ret; -} - -static void bot_status_complete(struct usb_ep *ep, struct usb_request *req) -{ - struct usbg_cmd *cmd = req->context; - struct f_uas *fu = cmd->fu; - - usbg_cleanup_cmd(cmd); - if (req->status < 0) { - pr_err("ERR %s(%d)\n", __func__, __LINE__); - return; - } - - /* CSW completed, wait for next CBW */ - bot_enqueue_cmd_cbw(fu); -} - -static void bot_enqueue_sense_code(struct f_uas *fu, struct usbg_cmd *cmd) -{ - struct bulk_cs_wrap *csw = &fu->bot_status.csw; - int ret; - u8 *sense; - unsigned int csw_stat; - - csw_stat = cmd->csw_code; - - /* - * We can't send SENSE as a response. So we take ASC & ASCQ from our - * sense buffer and queue it and hope the host sends a REQUEST_SENSE - * command where it learns why we failed. - */ - sense = cmd->sense_iu.sense; - - csw->Tag = cmd->bot_tag; - csw->Status = csw_stat; - fu->bot_status.req->context = cmd; - ret = usb_ep_queue(fu->ep_in, fu->bot_status.req, GFP_ATOMIC); - if (ret) - pr_err("%s(%d) ERR: %d\n", __func__, __LINE__, ret); -} - -static void bot_err_compl(struct usb_ep *ep, struct usb_request *req) -{ - struct usbg_cmd *cmd = req->context; - struct f_uas *fu = cmd->fu; - - if (req->status < 0) - pr_err("ERR %s(%d)\n", __func__, __LINE__); - - if (cmd->data_len) { - if (cmd->data_len > ep->maxpacket) { - req->length = ep->maxpacket; - cmd->data_len -= ep->maxpacket; - } else { - req->length = cmd->data_len; - cmd->data_len = 0; - } - - usb_ep_queue(ep, req, GFP_ATOMIC); - return ; - } - bot_enqueue_sense_code(fu, cmd); -} - -static void bot_send_bad_status(struct usbg_cmd *cmd) -{ - struct f_uas *fu = cmd->fu; - struct bulk_cs_wrap *csw = &fu->bot_status.csw; - struct usb_request *req; - struct usb_ep *ep; - - csw->Residue = cpu_to_le32(cmd->data_len); - - if (cmd->data_len) { - if (cmd->is_read) { - ep = fu->ep_in; - req = fu->bot_req_in; - } else { - ep = fu->ep_out; - req = fu->bot_req_out; - } - - if (cmd->data_len > fu->ep_in->maxpacket) { - req->length = ep->maxpacket; - cmd->data_len -= ep->maxpacket; - } else { - req->length = cmd->data_len; - cmd->data_len = 0; - } - req->complete = bot_err_compl; - req->context = cmd; - req->buf = fu->cmd.buf; - usb_ep_queue(ep, req, GFP_KERNEL); - } else { - bot_enqueue_sense_code(fu, cmd); - } -} - -static int bot_send_status(struct usbg_cmd *cmd, bool moved_data) -{ - struct f_uas *fu = cmd->fu; - struct bulk_cs_wrap *csw = &fu->bot_status.csw; - int ret; - - if (cmd->se_cmd.scsi_status == SAM_STAT_GOOD) { - if (!moved_data && cmd->data_len) { - /* - * the host wants to move data, we don't. Fill / empty - * the pipe and then send the csw with reside set. - */ - cmd->csw_code = US_BULK_STAT_OK; - bot_send_bad_status(cmd); - return 0; - } - - csw->Tag = cmd->bot_tag; - csw->Residue = cpu_to_le32(0); - csw->Status = US_BULK_STAT_OK; - fu->bot_status.req->context = cmd; - - ret = usb_ep_queue(fu->ep_in, fu->bot_status.req, GFP_KERNEL); - if (ret) - pr_err("%s(%d) ERR: %d\n", __func__, __LINE__, ret); - } else { - cmd->csw_code = US_BULK_STAT_FAIL; - bot_send_bad_status(cmd); - } - return 0; -} - -/* - * Called after command (no data transfer) or after the write (to device) - * operation is completed - */ -static int bot_send_status_response(struct usbg_cmd *cmd) -{ - bool moved_data = false; - - if (!cmd->is_read) - moved_data = true; - return bot_send_status(cmd, moved_data); -} - -/* Read request completed, now we have to send the CSW */ -static void bot_read_compl(struct usb_ep *ep, struct usb_request *req) -{ - struct usbg_cmd *cmd = req->context; - - if (req->status < 0) - pr_err("ERR %s(%d)\n", __func__, __LINE__); - - bot_send_status(cmd, true); -} - -static int bot_send_read_response(struct usbg_cmd *cmd) -{ - struct f_uas *fu = cmd->fu; - struct se_cmd *se_cmd = &cmd->se_cmd; - struct usb_gadget *gadget = fuas_to_gadget(fu); - int ret; - - if (!cmd->data_len) { - cmd->csw_code = US_BULK_STAT_PHASE; - bot_send_bad_status(cmd); - return 0; - } - - if (!gadget->sg_supported) { - cmd->data_buf = kmalloc(se_cmd->data_length, GFP_ATOMIC); - if (!cmd->data_buf) - return -ENOMEM; - - sg_copy_to_buffer(se_cmd->t_data_sg, - se_cmd->t_data_nents, - cmd->data_buf, - se_cmd->data_length); - - fu->bot_req_in->buf = cmd->data_buf; - } else { - fu->bot_req_in->buf = NULL; - fu->bot_req_in->num_sgs = se_cmd->t_data_nents; - fu->bot_req_in->sg = se_cmd->t_data_sg; - } - - fu->bot_req_in->complete = bot_read_compl; - fu->bot_req_in->length = se_cmd->data_length; - fu->bot_req_in->context = cmd; - ret = usb_ep_queue(fu->ep_in, fu->bot_req_in, GFP_ATOMIC); - if (ret) - pr_err("%s(%d)\n", __func__, __LINE__); - return 0; -} - -static void usbg_data_write_cmpl(struct usb_ep *, struct usb_request *); -static int usbg_prepare_w_request(struct usbg_cmd *, struct usb_request *); - -static int bot_send_write_request(struct usbg_cmd *cmd) -{ - struct f_uas *fu = cmd->fu; - struct se_cmd *se_cmd = &cmd->se_cmd; - struct usb_gadget *gadget = fuas_to_gadget(fu); - int ret; - - init_completion(&cmd->write_complete); - cmd->fu = fu; - - if (!cmd->data_len) { - cmd->csw_code = US_BULK_STAT_PHASE; - return -EINVAL; - } - - if (!gadget->sg_supported) { - cmd->data_buf = kmalloc(se_cmd->data_length, GFP_KERNEL); - if (!cmd->data_buf) - return -ENOMEM; - - fu->bot_req_out->buf = cmd->data_buf; - } else { - fu->bot_req_out->buf = NULL; - fu->bot_req_out->num_sgs = se_cmd->t_data_nents; - fu->bot_req_out->sg = se_cmd->t_data_sg; - } - - fu->bot_req_out->complete = usbg_data_write_cmpl; - fu->bot_req_out->length = se_cmd->data_length; - fu->bot_req_out->context = cmd; - - ret = usbg_prepare_w_request(cmd, fu->bot_req_out); - if (ret) - goto cleanup; - ret = usb_ep_queue(fu->ep_out, fu->bot_req_out, GFP_KERNEL); - if (ret) - pr_err("%s(%d)\n", __func__, __LINE__); - - wait_for_completion(&cmd->write_complete); - target_execute_cmd(se_cmd); -cleanup: - return ret; -} - -static int bot_submit_command(struct f_uas *, void *, unsigned int); - -static void bot_cmd_complete(struct usb_ep *ep, struct usb_request *req) -{ - struct f_uas *fu = req->context; - int ret; - - fu->flags &= ~USBG_BOT_CMD_PEND; - - if (req->status < 0) - return; - - ret = bot_submit_command(fu, req->buf, req->actual); - if (ret) - pr_err("%s(%d): %d\n", __func__, __LINE__, ret); -} - -static int bot_prepare_reqs(struct f_uas *fu) -{ - int ret; - - fu->bot_req_in = usb_ep_alloc_request(fu->ep_in, GFP_KERNEL); - if (!fu->bot_req_in) - goto err; - - fu->bot_req_out = usb_ep_alloc_request(fu->ep_out, GFP_KERNEL); - if (!fu->bot_req_out) - goto err_out; - - fu->cmd.req = usb_ep_alloc_request(fu->ep_out, GFP_KERNEL); - if (!fu->cmd.req) - goto err_cmd; - - fu->bot_status.req = usb_ep_alloc_request(fu->ep_in, GFP_KERNEL); - if (!fu->bot_status.req) - goto err_sts; - - fu->bot_status.req->buf = &fu->bot_status.csw; - fu->bot_status.req->length = US_BULK_CS_WRAP_LEN; - fu->bot_status.req->complete = bot_status_complete; - fu->bot_status.csw.Signature = cpu_to_le32(US_BULK_CS_SIGN); - - fu->cmd.buf = kmalloc(fu->ep_out->maxpacket, GFP_KERNEL); - if (!fu->cmd.buf) - goto err_buf; - - fu->cmd.req->complete = bot_cmd_complete; - fu->cmd.req->buf = fu->cmd.buf; - fu->cmd.req->length = fu->ep_out->maxpacket; - fu->cmd.req->context = fu; - - ret = bot_enqueue_cmd_cbw(fu); - if (ret) - goto err_queue; - return 0; -err_queue: - kfree(fu->cmd.buf); - fu->cmd.buf = NULL; -err_buf: - usb_ep_free_request(fu->ep_in, fu->bot_status.req); -err_sts: - usb_ep_free_request(fu->ep_out, fu->cmd.req); - fu->cmd.req = NULL; -err_cmd: - usb_ep_free_request(fu->ep_out, fu->bot_req_out); - fu->bot_req_out = NULL; -err_out: - usb_ep_free_request(fu->ep_in, fu->bot_req_in); - fu->bot_req_in = NULL; -err: - pr_err("BOT: endpoint setup failed\n"); - return -ENOMEM; -} - -static void bot_cleanup_old_alt(struct f_uas *fu) -{ - if (!(fu->flags & USBG_ENABLED)) - return; - - usb_ep_disable(fu->ep_in); - usb_ep_disable(fu->ep_out); - - if (!fu->bot_req_in) - return; - - usb_ep_free_request(fu->ep_in, fu->bot_req_in); - usb_ep_free_request(fu->ep_out, fu->bot_req_out); - usb_ep_free_request(fu->ep_out, fu->cmd.req); - usb_ep_free_request(fu->ep_out, fu->bot_status.req); - - kfree(fu->cmd.buf); - - fu->bot_req_in = NULL; - fu->bot_req_out = NULL; - fu->cmd.req = NULL; - fu->bot_status.req = NULL; - fu->cmd.buf = NULL; -} - -static void bot_set_alt(struct f_uas *fu) -{ - struct usb_function *f = &fu->function; - struct usb_gadget *gadget = f->config->cdev->gadget; - int ret; - - fu->flags = USBG_IS_BOT; - - config_ep_by_speed(gadget, f, fu->ep_in); - ret = usb_ep_enable(fu->ep_in); - if (ret) - goto err_b_in; - - config_ep_by_speed(gadget, f, fu->ep_out); - ret = usb_ep_enable(fu->ep_out); - if (ret) - goto err_b_out; - - ret = bot_prepare_reqs(fu); - if (ret) - goto err_wq; - fu->flags |= USBG_ENABLED; - pr_info("Using the BOT protocol\n"); - return; -err_wq: - usb_ep_disable(fu->ep_out); -err_b_out: - usb_ep_disable(fu->ep_in); -err_b_in: - fu->flags = USBG_IS_BOT; -} - -static int usbg_bot_setup(struct usb_function *f, - const struct usb_ctrlrequest *ctrl) -{ - struct f_uas *fu = to_f_uas(f); - struct usb_composite_dev *cdev = f->config->cdev; - u16 w_value = le16_to_cpu(ctrl->wValue); - u16 w_length = le16_to_cpu(ctrl->wLength); - int luns; - u8 *ret_lun; - - switch (ctrl->bRequest) { - case US_BULK_GET_MAX_LUN: - if (ctrl->bRequestType != (USB_DIR_IN | USB_TYPE_CLASS | - USB_RECIP_INTERFACE)) - return -ENOTSUPP; - - if (w_length < 1) - return -EINVAL; - if (w_value != 0) - return -EINVAL; - luns = atomic_read(&fu->tpg->tpg_port_count); - if (!luns) { - pr_err("No LUNs configured?\n"); - return -EINVAL; - } - /* - * If 4 LUNs are present we return 3 i.e. LUN 0..3 can be - * accessed. The upper limit is 0xf - */ - luns--; - if (luns > 0xf) { - pr_info_once("Limiting the number of luns to 16\n"); - luns = 0xf; - } - ret_lun = cdev->req->buf; - *ret_lun = luns; - cdev->req->length = 1; - return usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC); - break; - - case US_BULK_RESET_REQUEST: - /* XXX maybe we should remove previous requests for IN + OUT */ - bot_enqueue_cmd_cbw(fu); - return 0; - break; - } - return -ENOTSUPP; -} - -/* Start uas.c code */ - -static void uasp_cleanup_one_stream(struct f_uas *fu, struct uas_stream *stream) -{ - /* We have either all three allocated or none */ - if (!stream->req_in) - return; - - usb_ep_free_request(fu->ep_in, stream->req_in); - usb_ep_free_request(fu->ep_out, stream->req_out); - usb_ep_free_request(fu->ep_status, stream->req_status); - - stream->req_in = NULL; - stream->req_out = NULL; - stream->req_status = NULL; -} - -static void uasp_free_cmdreq(struct f_uas *fu) -{ - usb_ep_free_request(fu->ep_cmd, fu->cmd.req); - kfree(fu->cmd.buf); - fu->cmd.req = NULL; - fu->cmd.buf = NULL; -} - -static void uasp_cleanup_old_alt(struct f_uas *fu) -{ - int i; - - if (!(fu->flags & USBG_ENABLED)) - return; - - usb_ep_disable(fu->ep_in); - usb_ep_disable(fu->ep_out); - usb_ep_disable(fu->ep_status); - usb_ep_disable(fu->ep_cmd); - - for (i = 0; i < UASP_SS_EP_COMP_NUM_STREAMS; i++) - uasp_cleanup_one_stream(fu, &fu->stream[i]); - uasp_free_cmdreq(fu); -} - -static void uasp_status_data_cmpl(struct usb_ep *ep, struct usb_request *req); - -static int uasp_prepare_r_request(struct usbg_cmd *cmd) -{ - struct se_cmd *se_cmd = &cmd->se_cmd; - struct f_uas *fu = cmd->fu; - struct usb_gadget *gadget = fuas_to_gadget(fu); - struct uas_stream *stream = cmd->stream; - - if (!gadget->sg_supported) { - cmd->data_buf = kmalloc(se_cmd->data_length, GFP_ATOMIC); - if (!cmd->data_buf) - return -ENOMEM; - - sg_copy_to_buffer(se_cmd->t_data_sg, - se_cmd->t_data_nents, - cmd->data_buf, - se_cmd->data_length); - - stream->req_in->buf = cmd->data_buf; - } else { - stream->req_in->buf = NULL; - stream->req_in->num_sgs = se_cmd->t_data_nents; - stream->req_in->sg = se_cmd->t_data_sg; - } - - stream->req_in->complete = uasp_status_data_cmpl; - stream->req_in->length = se_cmd->data_length; - stream->req_in->context = cmd; - - cmd->state = UASP_SEND_STATUS; - return 0; -} - -static void uasp_prepare_status(struct usbg_cmd *cmd) -{ - struct se_cmd *se_cmd = &cmd->se_cmd; - struct sense_iu *iu = &cmd->sense_iu; - struct uas_stream *stream = cmd->stream; - - cmd->state = UASP_QUEUE_COMMAND; - iu->iu_id = IU_ID_STATUS; - iu->tag = cpu_to_be16(cmd->tag); - - /* - * iu->status_qual = cpu_to_be16(STATUS QUALIFIER SAM-4. Where R U?); - */ - iu->len = cpu_to_be16(se_cmd->scsi_sense_length); - iu->status = se_cmd->scsi_status; - stream->req_status->context = cmd; - stream->req_status->length = se_cmd->scsi_sense_length + 16; - stream->req_status->buf = iu; - stream->req_status->complete = uasp_status_data_cmpl; -} - -static void uasp_status_data_cmpl(struct usb_ep *ep, struct usb_request *req) -{ - struct usbg_cmd *cmd = req->context; - struct uas_stream *stream = cmd->stream; - struct f_uas *fu = cmd->fu; - int ret; - - if (req->status < 0) - goto cleanup; - - switch (cmd->state) { - case UASP_SEND_DATA: - ret = uasp_prepare_r_request(cmd); - if (ret) - goto cleanup; - ret = usb_ep_queue(fu->ep_in, stream->req_in, GFP_ATOMIC); - if (ret) - pr_err("%s(%d) => %d\n", __func__, __LINE__, ret); - break; - - case UASP_RECEIVE_DATA: - ret = usbg_prepare_w_request(cmd, stream->req_out); - if (ret) - goto cleanup; - ret = usb_ep_queue(fu->ep_out, stream->req_out, GFP_ATOMIC); - if (ret) - pr_err("%s(%d) => %d\n", __func__, __LINE__, ret); - break; - - case UASP_SEND_STATUS: - uasp_prepare_status(cmd); - ret = usb_ep_queue(fu->ep_status, stream->req_status, - GFP_ATOMIC); - if (ret) - pr_err("%s(%d) => %d\n", __func__, __LINE__, ret); - break; - - case UASP_QUEUE_COMMAND: - usbg_cleanup_cmd(cmd); - usb_ep_queue(fu->ep_cmd, fu->cmd.req, GFP_ATOMIC); - break; - - default: - BUG(); - } - return; - -cleanup: - usbg_cleanup_cmd(cmd); -} - -static int uasp_send_status_response(struct usbg_cmd *cmd) -{ - struct f_uas *fu = cmd->fu; - struct uas_stream *stream = cmd->stream; - struct sense_iu *iu = &cmd->sense_iu; - - iu->tag = cpu_to_be16(cmd->tag); - stream->req_status->complete = uasp_status_data_cmpl; - stream->req_status->context = cmd; - cmd->fu = fu; - uasp_prepare_status(cmd); - return usb_ep_queue(fu->ep_status, stream->req_status, GFP_ATOMIC); -} - -static int uasp_send_read_response(struct usbg_cmd *cmd) -{ - struct f_uas *fu = cmd->fu; - struct uas_stream *stream = cmd->stream; - struct sense_iu *iu = &cmd->sense_iu; - int ret; - - cmd->fu = fu; - - iu->tag = cpu_to_be16(cmd->tag); - if (fu->flags & USBG_USE_STREAMS) { - - ret = uasp_prepare_r_request(cmd); - if (ret) - goto out; - ret = usb_ep_queue(fu->ep_in, stream->req_in, GFP_ATOMIC); - if (ret) { - pr_err("%s(%d) => %d\n", __func__, __LINE__, ret); - kfree(cmd->data_buf); - cmd->data_buf = NULL; - } - - } else { - - iu->iu_id = IU_ID_READ_READY; - iu->tag = cpu_to_be16(cmd->tag); - - stream->req_status->complete = uasp_status_data_cmpl; - stream->req_status->context = cmd; - - cmd->state = UASP_SEND_DATA; - stream->req_status->buf = iu; - stream->req_status->length = sizeof(struct iu); - - ret = usb_ep_queue(fu->ep_status, stream->req_status, - GFP_ATOMIC); - if (ret) - pr_err("%s(%d) => %d\n", __func__, __LINE__, ret); - } -out: - return ret; -} - -static int uasp_send_write_request(struct usbg_cmd *cmd) -{ - struct f_uas *fu = cmd->fu; - struct se_cmd *se_cmd = &cmd->se_cmd; - struct uas_stream *stream = cmd->stream; - struct sense_iu *iu = &cmd->sense_iu; - int ret; - - init_completion(&cmd->write_complete); - cmd->fu = fu; - - iu->tag = cpu_to_be16(cmd->tag); - - if (fu->flags & USBG_USE_STREAMS) { - - ret = usbg_prepare_w_request(cmd, stream->req_out); - if (ret) - goto cleanup; - ret = usb_ep_queue(fu->ep_out, stream->req_out, GFP_ATOMIC); - if (ret) - pr_err("%s(%d)\n", __func__, __LINE__); - - } else { - - iu->iu_id = IU_ID_WRITE_READY; - iu->tag = cpu_to_be16(cmd->tag); - - stream->req_status->complete = uasp_status_data_cmpl; - stream->req_status->context = cmd; - - cmd->state = UASP_RECEIVE_DATA; - stream->req_status->buf = iu; - stream->req_status->length = sizeof(struct iu); - - ret = usb_ep_queue(fu->ep_status, stream->req_status, - GFP_ATOMIC); - if (ret) - pr_err("%s(%d)\n", __func__, __LINE__); - } - - wait_for_completion(&cmd->write_complete); - target_execute_cmd(se_cmd); -cleanup: - return ret; -} - -static int usbg_submit_command(struct f_uas *, void *, unsigned int); - -static void uasp_cmd_complete(struct usb_ep *ep, struct usb_request *req) -{ - struct f_uas *fu = req->context; - int ret; - - if (req->status < 0) - return; - - ret = usbg_submit_command(fu, req->buf, req->actual); - /* - * Once we tune for performance enqueue the command req here again so - * we can receive a second command while we processing this one. Pay - * attention to properly sync STAUS endpoint with DATA IN + OUT so you - * don't break HS. - */ - if (!ret) - return; - usb_ep_queue(fu->ep_cmd, fu->cmd.req, GFP_ATOMIC); -} - -static int uasp_alloc_stream_res(struct f_uas *fu, struct uas_stream *stream) -{ - stream->req_in = usb_ep_alloc_request(fu->ep_in, GFP_KERNEL); - if (!stream->req_in) - goto out; - - stream->req_out = usb_ep_alloc_request(fu->ep_out, GFP_KERNEL); - if (!stream->req_out) - goto err_out; - - stream->req_status = usb_ep_alloc_request(fu->ep_status, GFP_KERNEL); - if (!stream->req_status) - goto err_sts; - - return 0; -err_sts: - usb_ep_free_request(fu->ep_status, stream->req_status); - stream->req_status = NULL; -err_out: - usb_ep_free_request(fu->ep_out, stream->req_out); - stream->req_out = NULL; -out: - return -ENOMEM; -} - -static int uasp_alloc_cmd(struct f_uas *fu) -{ - fu->cmd.req = usb_ep_alloc_request(fu->ep_cmd, GFP_KERNEL); - if (!fu->cmd.req) - goto err; - - fu->cmd.buf = kmalloc(fu->ep_cmd->maxpacket, GFP_KERNEL); - if (!fu->cmd.buf) - goto err_buf; - - fu->cmd.req->complete = uasp_cmd_complete; - fu->cmd.req->buf = fu->cmd.buf; - fu->cmd.req->length = fu->ep_cmd->maxpacket; - fu->cmd.req->context = fu; - return 0; - -err_buf: - usb_ep_free_request(fu->ep_cmd, fu->cmd.req); -err: - return -ENOMEM; -} - -static void uasp_setup_stream_res(struct f_uas *fu, int max_streams) -{ - int i; - - for (i = 0; i < max_streams; i++) { - struct uas_stream *s = &fu->stream[i]; - - s->req_in->stream_id = i + 1; - s->req_out->stream_id = i + 1; - s->req_status->stream_id = i + 1; - } -} - -static int uasp_prepare_reqs(struct f_uas *fu) -{ - int ret; - int i; - int max_streams; - - if (fu->flags & USBG_USE_STREAMS) - max_streams = UASP_SS_EP_COMP_NUM_STREAMS; - else - max_streams = 1; - - for (i = 0; i < max_streams; i++) { - ret = uasp_alloc_stream_res(fu, &fu->stream[i]); - if (ret) - goto err_cleanup; - } - - ret = uasp_alloc_cmd(fu); - if (ret) - goto err_free_stream; - uasp_setup_stream_res(fu, max_streams); - - ret = usb_ep_queue(fu->ep_cmd, fu->cmd.req, GFP_ATOMIC); - if (ret) - goto err_free_stream; - - return 0; - -err_free_stream: - uasp_free_cmdreq(fu); - -err_cleanup: - if (i) { - do { - uasp_cleanup_one_stream(fu, &fu->stream[i - 1]); - i--; - } while (i); - } - pr_err("UASP: endpoint setup failed\n"); - return ret; -} - -static void uasp_set_alt(struct f_uas *fu) -{ - struct usb_function *f = &fu->function; - struct usb_gadget *gadget = f->config->cdev->gadget; - int ret; - - fu->flags = USBG_IS_UAS; - - if (gadget->speed == USB_SPEED_SUPER) - fu->flags |= USBG_USE_STREAMS; - - config_ep_by_speed(gadget, f, fu->ep_in); - ret = usb_ep_enable(fu->ep_in); - if (ret) - goto err_b_in; - - config_ep_by_speed(gadget, f, fu->ep_out); - ret = usb_ep_enable(fu->ep_out); - if (ret) - goto err_b_out; - - config_ep_by_speed(gadget, f, fu->ep_cmd); - ret = usb_ep_enable(fu->ep_cmd); - if (ret) - goto err_cmd; - config_ep_by_speed(gadget, f, fu->ep_status); - ret = usb_ep_enable(fu->ep_status); - if (ret) - goto err_status; - - ret = uasp_prepare_reqs(fu); - if (ret) - goto err_wq; - fu->flags |= USBG_ENABLED; - - pr_info("Using the UAS protocol\n"); - return; -err_wq: - usb_ep_disable(fu->ep_status); -err_status: - usb_ep_disable(fu->ep_cmd); -err_cmd: - usb_ep_disable(fu->ep_out); -err_b_out: - usb_ep_disable(fu->ep_in); -err_b_in: - fu->flags = 0; -} - -static int get_cmd_dir(const unsigned char *cdb) -{ - int ret; - - switch (cdb[0]) { - case READ_6: - case READ_10: - case READ_12: - case READ_16: - case INQUIRY: - case MODE_SENSE: - case MODE_SENSE_10: - case SERVICE_ACTION_IN_16: - case MAINTENANCE_IN: - case PERSISTENT_RESERVE_IN: - case SECURITY_PROTOCOL_IN: - case ACCESS_CONTROL_IN: - case REPORT_LUNS: - case READ_BLOCK_LIMITS: - case READ_POSITION: - case READ_CAPACITY: - case READ_TOC: - case READ_FORMAT_CAPACITIES: - case REQUEST_SENSE: - ret = DMA_FROM_DEVICE; - break; - - case WRITE_6: - case WRITE_10: - case WRITE_12: - case WRITE_16: - case MODE_SELECT: - case MODE_SELECT_10: - case WRITE_VERIFY: - case WRITE_VERIFY_12: - case PERSISTENT_RESERVE_OUT: - case MAINTENANCE_OUT: - case SECURITY_PROTOCOL_OUT: - case ACCESS_CONTROL_OUT: - ret = DMA_TO_DEVICE; - break; - case ALLOW_MEDIUM_REMOVAL: - case TEST_UNIT_READY: - case SYNCHRONIZE_CACHE: - case START_STOP: - case ERASE: - case REZERO_UNIT: - case SEEK_10: - case SPACE: - case VERIFY: - case WRITE_FILEMARKS: - ret = DMA_NONE; - break; - default: - pr_warn("target: Unknown data direction for SCSI Opcode " - "0x%02x\n", cdb[0]); - ret = -EINVAL; - } - return ret; -} - -static void usbg_data_write_cmpl(struct usb_ep *ep, struct usb_request *req) -{ - struct usbg_cmd *cmd = req->context; - struct se_cmd *se_cmd = &cmd->se_cmd; - - if (req->status < 0) { - pr_err("%s() state %d transfer failed\n", __func__, cmd->state); - goto cleanup; - } - - if (req->num_sgs == 0) { - sg_copy_from_buffer(se_cmd->t_data_sg, - se_cmd->t_data_nents, - cmd->data_buf, - se_cmd->data_length); - } - - complete(&cmd->write_complete); - return; - -cleanup: - usbg_cleanup_cmd(cmd); -} - -static int usbg_prepare_w_request(struct usbg_cmd *cmd, struct usb_request *req) -{ - struct se_cmd *se_cmd = &cmd->se_cmd; - struct f_uas *fu = cmd->fu; - struct usb_gadget *gadget = fuas_to_gadget(fu); - - if (!gadget->sg_supported) { - cmd->data_buf = kmalloc(se_cmd->data_length, GFP_ATOMIC); - if (!cmd->data_buf) - return -ENOMEM; - - req->buf = cmd->data_buf; - } else { - req->buf = NULL; - req->num_sgs = se_cmd->t_data_nents; - req->sg = se_cmd->t_data_sg; - } - - req->complete = usbg_data_write_cmpl; - req->length = se_cmd->data_length; - req->context = cmd; - return 0; -} - -static int usbg_send_status_response(struct se_cmd *se_cmd) -{ - struct usbg_cmd *cmd = container_of(se_cmd, struct usbg_cmd, - se_cmd); - struct f_uas *fu = cmd->fu; - - if (fu->flags & USBG_IS_BOT) - return bot_send_status_response(cmd); - else - return uasp_send_status_response(cmd); -} - -static int usbg_send_write_request(struct se_cmd *se_cmd) -{ - struct usbg_cmd *cmd = container_of(se_cmd, struct usbg_cmd, - se_cmd); - struct f_uas *fu = cmd->fu; - - if (fu->flags & USBG_IS_BOT) - return bot_send_write_request(cmd); - else - return uasp_send_write_request(cmd); -} - -static int usbg_send_read_response(struct se_cmd *se_cmd) -{ - struct usbg_cmd *cmd = container_of(se_cmd, struct usbg_cmd, - se_cmd); - struct f_uas *fu = cmd->fu; - - if (fu->flags & USBG_IS_BOT) - return bot_send_read_response(cmd); - else - return uasp_send_read_response(cmd); -} - -static void usbg_cmd_work(struct work_struct *work) -{ - struct usbg_cmd *cmd = container_of(work, struct usbg_cmd, work); - struct se_cmd *se_cmd; - struct tcm_usbg_nexus *tv_nexus; - struct usbg_tpg *tpg; - int dir; - - se_cmd = &cmd->se_cmd; - tpg = cmd->fu->tpg; - tv_nexus = tpg->tpg_nexus; - dir = get_cmd_dir(cmd->cmd_buf); - if (dir < 0) { - transport_init_se_cmd(se_cmd, - tv_nexus->tvn_se_sess->se_tpg->se_tpg_tfo, - tv_nexus->tvn_se_sess, cmd->data_len, DMA_NONE, - cmd->prio_attr, cmd->sense_iu.sense); - goto out; - } - - if (target_submit_cmd(se_cmd, tv_nexus->tvn_se_sess, - cmd->cmd_buf, cmd->sense_iu.sense, cmd->unpacked_lun, - 0, cmd->prio_attr, dir, TARGET_SCF_UNKNOWN_SIZE) < 0) - goto out; - - return; - -out: - transport_send_check_condition_and_sense(se_cmd, - TCM_UNSUPPORTED_SCSI_OPCODE, 1); - usbg_cleanup_cmd(cmd); -} - -static int usbg_submit_command(struct f_uas *fu, - void *cmdbuf, unsigned int len) -{ - struct command_iu *cmd_iu = cmdbuf; - struct usbg_cmd *cmd; - struct usbg_tpg *tpg; - struct se_cmd *se_cmd; - struct tcm_usbg_nexus *tv_nexus; - u32 cmd_len; - int ret; - - if (cmd_iu->iu_id != IU_ID_COMMAND) { - pr_err("Unsupported type %d\n", cmd_iu->iu_id); - return -EINVAL; - } - - cmd = kzalloc(sizeof *cmd, GFP_ATOMIC); - if (!cmd) - return -ENOMEM; - - cmd->fu = fu; - - /* XXX until I figure out why I can't free in on complete */ - kref_init(&cmd->ref); - kref_get(&cmd->ref); - - tpg = fu->tpg; - cmd_len = (cmd_iu->len & ~0x3) + 16; - if (cmd_len > USBG_MAX_CMD) - goto err; - - memcpy(cmd->cmd_buf, cmd_iu->cdb, cmd_len); - - cmd->tag = be16_to_cpup(&cmd_iu->tag); - cmd->se_cmd.tag = cmd->tag; - if (fu->flags & USBG_USE_STREAMS) { - if (cmd->tag > UASP_SS_EP_COMP_NUM_STREAMS) - goto err; - if (!cmd->tag) - cmd->stream = &fu->stream[0]; - else - cmd->stream = &fu->stream[cmd->tag - 1]; - } else { - cmd->stream = &fu->stream[0]; - } - - tv_nexus = tpg->tpg_nexus; - if (!tv_nexus) { - pr_err("Missing nexus, ignoring command\n"); - goto err; - } - - switch (cmd_iu->prio_attr & 0x7) { - case UAS_HEAD_TAG: - cmd->prio_attr = TCM_HEAD_TAG; - break; - case UAS_ORDERED_TAG: - cmd->prio_attr = TCM_ORDERED_TAG; - break; - case UAS_ACA: - cmd->prio_attr = TCM_ACA_TAG; - break; - default: - pr_debug_once("Unsupported prio_attr: %02x.\n", - cmd_iu->prio_attr); - case UAS_SIMPLE_TAG: - cmd->prio_attr = TCM_SIMPLE_TAG; - break; - } - - se_cmd = &cmd->se_cmd; - cmd->unpacked_lun = scsilun_to_int(&cmd_iu->lun); - - INIT_WORK(&cmd->work, usbg_cmd_work); - ret = queue_work(tpg->workqueue, &cmd->work); - if (ret < 0) - goto err; - - return 0; -err: - kfree(cmd); - return -EINVAL; -} - -static void bot_cmd_work(struct work_struct *work) -{ - struct usbg_cmd *cmd = container_of(work, struct usbg_cmd, work); - struct se_cmd *se_cmd; - struct tcm_usbg_nexus *tv_nexus; - struct usbg_tpg *tpg; - int dir; - - se_cmd = &cmd->se_cmd; - tpg = cmd->fu->tpg; - tv_nexus = tpg->tpg_nexus; - dir = get_cmd_dir(cmd->cmd_buf); - if (dir < 0) { - transport_init_se_cmd(se_cmd, - tv_nexus->tvn_se_sess->se_tpg->se_tpg_tfo, - tv_nexus->tvn_se_sess, cmd->data_len, DMA_NONE, - cmd->prio_attr, cmd->sense_iu.sense); - goto out; - } - - if (target_submit_cmd(se_cmd, tv_nexus->tvn_se_sess, - cmd->cmd_buf, cmd->sense_iu.sense, cmd->unpacked_lun, - cmd->data_len, cmd->prio_attr, dir, 0) < 0) - goto out; - - return; - -out: - transport_send_check_condition_and_sense(se_cmd, - TCM_UNSUPPORTED_SCSI_OPCODE, 1); - usbg_cleanup_cmd(cmd); -} - -static int bot_submit_command(struct f_uas *fu, - void *cmdbuf, unsigned int len) -{ - struct bulk_cb_wrap *cbw = cmdbuf; - struct usbg_cmd *cmd; - struct usbg_tpg *tpg; - struct se_cmd *se_cmd; - struct tcm_usbg_nexus *tv_nexus; - u32 cmd_len; - int ret; - - if (cbw->Signature != cpu_to_le32(US_BULK_CB_SIGN)) { - pr_err("Wrong signature on CBW\n"); - return -EINVAL; - } - if (len != 31) { - pr_err("Wrong length for CBW\n"); - return -EINVAL; - } - - cmd_len = cbw->Length; - if (cmd_len < 1 || cmd_len > 16) - return -EINVAL; - - cmd = kzalloc(sizeof *cmd, GFP_ATOMIC); - if (!cmd) - return -ENOMEM; - - cmd->fu = fu; - - /* XXX until I figure out why I can't free in on complete */ - kref_init(&cmd->ref); - kref_get(&cmd->ref); - - tpg = fu->tpg; - - memcpy(cmd->cmd_buf, cbw->CDB, cmd_len); - - cmd->bot_tag = cbw->Tag; - - tv_nexus = tpg->tpg_nexus; - if (!tv_nexus) { - pr_err("Missing nexus, ignoring command\n"); - goto err; - } - - cmd->prio_attr = TCM_SIMPLE_TAG; - se_cmd = &cmd->se_cmd; - cmd->unpacked_lun = cbw->Lun; - cmd->is_read = cbw->Flags & US_BULK_FLAG_IN ? 1 : 0; - cmd->data_len = le32_to_cpu(cbw->DataTransferLength); - cmd->se_cmd.tag = le32_to_cpu(cmd->bot_tag); - - INIT_WORK(&cmd->work, bot_cmd_work); - ret = queue_work(tpg->workqueue, &cmd->work); - if (ret < 0) - goto err; - - return 0; -err: - kfree(cmd); - return -EINVAL; -} - -/* Start fabric.c code */ - -static int usbg_check_true(struct se_portal_group *se_tpg) -{ - return 1; -} - -static int usbg_check_false(struct se_portal_group *se_tpg) -{ - return 0; -} - -static char *usbg_get_fabric_name(void) -{ - return "usb_gadget"; -} - -static char *usbg_get_fabric_wwn(struct se_portal_group *se_tpg) -{ - struct usbg_tpg *tpg = container_of(se_tpg, - struct usbg_tpg, se_tpg); - struct usbg_tport *tport = tpg->tport; - - return &tport->tport_name[0]; -} - -static u16 usbg_get_tag(struct se_portal_group *se_tpg) -{ - struct usbg_tpg *tpg = container_of(se_tpg, - struct usbg_tpg, se_tpg); - return tpg->tport_tpgt; -} - -static u32 usbg_tpg_get_inst_index(struct se_portal_group *se_tpg) -{ - return 1; -} - -static void usbg_cmd_release(struct kref *ref) -{ - struct usbg_cmd *cmd = container_of(ref, struct usbg_cmd, - ref); - - transport_generic_free_cmd(&cmd->se_cmd, 0); -} - -static void usbg_release_cmd(struct se_cmd *se_cmd) -{ - struct usbg_cmd *cmd = container_of(se_cmd, struct usbg_cmd, - se_cmd); - kfree(cmd->data_buf); - kfree(cmd); - return; -} - -static int usbg_shutdown_session(struct se_session *se_sess) -{ - return 0; -} - -static void usbg_close_session(struct se_session *se_sess) -{ - return; -} - -static u32 usbg_sess_get_index(struct se_session *se_sess) -{ - return 0; -} - -/* - * XXX Error recovery: return != 0 if we expect writes. Dunno when that could be - */ -static int usbg_write_pending_status(struct se_cmd *se_cmd) -{ - return 0; -} - -static void usbg_set_default_node_attrs(struct se_node_acl *nacl) -{ - return; -} - -static int usbg_get_cmd_state(struct se_cmd *se_cmd) -{ - return 0; -} - -static void usbg_queue_tm_rsp(struct se_cmd *se_cmd) -{ -} - -static void usbg_aborted_task(struct se_cmd *se_cmd) -{ - return; -} - -static const char *usbg_check_wwn(const char *name) -{ - const char *n; - unsigned int len; - - n = strstr(name, "naa."); - if (!n) - return NULL; - n += 4; - len = strlen(n); - if (len == 0 || len > USBG_NAMELEN - 1) - return NULL; - return n; -} - -static int usbg_init_nodeacl(struct se_node_acl *se_nacl, const char *name) -{ - if (!usbg_check_wwn(name)) - return -EINVAL; - return 0; -} - -struct usbg_tpg *the_only_tpg_I_currently_have; - -static struct se_portal_group *usbg_make_tpg( - struct se_wwn *wwn, - struct config_group *group, - const char *name) -{ - struct usbg_tport *tport = container_of(wwn, struct usbg_tport, - tport_wwn); - struct usbg_tpg *tpg; - unsigned long tpgt; - int ret; - - if (strstr(name, "tpgt_") != name) - return ERR_PTR(-EINVAL); - if (kstrtoul(name + 5, 0, &tpgt) || tpgt > UINT_MAX) - return ERR_PTR(-EINVAL); - if (the_only_tpg_I_currently_have) { - pr_err("Until the gadget framework can't handle multiple\n"); - pr_err("gadgets, you can't do this here.\n"); - return ERR_PTR(-EBUSY); - } - - tpg = kzalloc(sizeof(struct usbg_tpg), GFP_KERNEL); - if (!tpg) - return ERR_PTR(-ENOMEM); - mutex_init(&tpg->tpg_mutex); - atomic_set(&tpg->tpg_port_count, 0); - tpg->workqueue = alloc_workqueue("tcm_usb_gadget", 0, 1); - if (!tpg->workqueue) { - kfree(tpg); - return NULL; - } - - tpg->tport = tport; - tpg->tport_tpgt = tpgt; - - /* - * SPC doesn't assign a protocol identifier for USB-SCSI, so we - * pretend to be SAS.. - */ - ret = core_tpg_register(wwn, &tpg->se_tpg, SCSI_PROTOCOL_SAS); - if (ret < 0) { - destroy_workqueue(tpg->workqueue); - kfree(tpg); - return NULL; - } - the_only_tpg_I_currently_have = tpg; - return &tpg->se_tpg; -} - -static void usbg_drop_tpg(struct se_portal_group *se_tpg) -{ - struct usbg_tpg *tpg = container_of(se_tpg, - struct usbg_tpg, se_tpg); - - core_tpg_deregister(se_tpg); - destroy_workqueue(tpg->workqueue); - kfree(tpg); - the_only_tpg_I_currently_have = NULL; -} - -static struct se_wwn *usbg_make_tport( - struct target_fabric_configfs *tf, - struct config_group *group, - const char *name) -{ - struct usbg_tport *tport; - const char *wnn_name; - u64 wwpn = 0; - - wnn_name = usbg_check_wwn(name); - if (!wnn_name) - return ERR_PTR(-EINVAL); - - tport = kzalloc(sizeof(struct usbg_tport), GFP_KERNEL); - if (!(tport)) - return ERR_PTR(-ENOMEM); - tport->tport_wwpn = wwpn; - snprintf(tport->tport_name, sizeof(tport->tport_name), "%s", wnn_name); - return &tport->tport_wwn; -} - -static void usbg_drop_tport(struct se_wwn *wwn) -{ - struct usbg_tport *tport = container_of(wwn, - struct usbg_tport, tport_wwn); - kfree(tport); -} - -/* - * If somebody feels like dropping the version property, go ahead. - */ -static ssize_t usbg_wwn_version_show(struct config_item *item, char *page) -{ - return sprintf(page, "usb-gadget fabric module\n"); -} - -CONFIGFS_ATTR_RO(usbg_wwn_, version); - -static struct configfs_attribute *usbg_wwn_attrs[] = { - &usbg_wwn_attr_version, - NULL, -}; - -static ssize_t tcm_usbg_tpg_enable_show(struct config_item *item, char *page) -{ - struct se_portal_group *se_tpg = to_tpg(item); - struct usbg_tpg *tpg = container_of(se_tpg, struct usbg_tpg, se_tpg); - - return snprintf(page, PAGE_SIZE, "%u\n", tpg->gadget_connect); -} - -static int usbg_attach(struct usbg_tpg *); -static void usbg_detach(struct usbg_tpg *); - -static ssize_t tcm_usbg_tpg_enable_store(struct config_item *item, - const char *page, size_t count) -{ - struct se_portal_group *se_tpg = to_tpg(item); - struct usbg_tpg *tpg = container_of(se_tpg, struct usbg_tpg, se_tpg); - unsigned long op; - ssize_t ret; - - ret = kstrtoul(page, 0, &op); - if (ret < 0) - return -EINVAL; - if (op > 1) - return -EINVAL; - - if (op && tpg->gadget_connect) - goto out; - if (!op && !tpg->gadget_connect) - goto out; - - if (op) { - ret = usbg_attach(tpg); - if (ret) - goto out; - } else { - usbg_detach(tpg); - } - tpg->gadget_connect = op; -out: - return count; -} - -static ssize_t tcm_usbg_tpg_nexus_show(struct config_item *item, char *page) -{ - struct se_portal_group *se_tpg = to_tpg(item); - struct usbg_tpg *tpg = container_of(se_tpg, struct usbg_tpg, se_tpg); - struct tcm_usbg_nexus *tv_nexus; - ssize_t ret; - - mutex_lock(&tpg->tpg_mutex); - tv_nexus = tpg->tpg_nexus; - if (!tv_nexus) { - ret = -ENODEV; - goto out; - } - ret = snprintf(page, PAGE_SIZE, "%s\n", - tv_nexus->tvn_se_sess->se_node_acl->initiatorname); -out: - mutex_unlock(&tpg->tpg_mutex); - return ret; -} - -static int tcm_usbg_make_nexus(struct usbg_tpg *tpg, char *name) -{ - struct se_portal_group *se_tpg; - struct tcm_usbg_nexus *tv_nexus; - int ret; - - mutex_lock(&tpg->tpg_mutex); - if (tpg->tpg_nexus) { - ret = -EEXIST; - pr_debug("tpg->tpg_nexus already exists\n"); - goto err_unlock; - } - se_tpg = &tpg->se_tpg; - - ret = -ENOMEM; - tv_nexus = kzalloc(sizeof(*tv_nexus), GFP_KERNEL); - if (!tv_nexus) - goto err_unlock; - tv_nexus->tvn_se_sess = transport_init_session(TARGET_PROT_NORMAL); - if (IS_ERR(tv_nexus->tvn_se_sess)) - goto err_free; - - /* - * Since we are running in 'demo mode' this call with generate a - * struct se_node_acl for the tcm_vhost struct se_portal_group with - * the SCSI Initiator port name of the passed configfs group 'name'. - */ - tv_nexus->tvn_se_sess->se_node_acl = core_tpg_check_initiator_node_acl( - se_tpg, name); - if (!tv_nexus->tvn_se_sess->se_node_acl) { - pr_debug("core_tpg_check_initiator_node_acl() failed" - " for %s\n", name); - goto err_session; - } - /* - * Now register the TCM vHost virtual I_T Nexus as active. - */ - transport_register_session(se_tpg, tv_nexus->tvn_se_sess->se_node_acl, - tv_nexus->tvn_se_sess, tv_nexus); - tpg->tpg_nexus = tv_nexus; - mutex_unlock(&tpg->tpg_mutex); - return 0; - -err_session: - transport_free_session(tv_nexus->tvn_se_sess); -err_free: - kfree(tv_nexus); -err_unlock: - mutex_unlock(&tpg->tpg_mutex); - return ret; -} - -static int tcm_usbg_drop_nexus(struct usbg_tpg *tpg) -{ - struct se_session *se_sess; - struct tcm_usbg_nexus *tv_nexus; - int ret = -ENODEV; - - mutex_lock(&tpg->tpg_mutex); - tv_nexus = tpg->tpg_nexus; - if (!tv_nexus) - goto out; - - se_sess = tv_nexus->tvn_se_sess; - if (!se_sess) - goto out; - - if (atomic_read(&tpg->tpg_port_count)) { - ret = -EPERM; - pr_err("Unable to remove Host I_T Nexus with" - " active TPG port count: %d\n", - atomic_read(&tpg->tpg_port_count)); - goto out; - } - - pr_debug("Removing I_T Nexus to Initiator Port: %s\n", - tv_nexus->tvn_se_sess->se_node_acl->initiatorname); - /* - * Release the SCSI I_T Nexus to the emulated vHost Target Port - */ - transport_deregister_session(tv_nexus->tvn_se_sess); - tpg->tpg_nexus = NULL; - - kfree(tv_nexus); - ret = 0; -out: - mutex_unlock(&tpg->tpg_mutex); - return ret; -} - -static ssize_t tcm_usbg_tpg_nexus_store(struct config_item *item, - const char *page, size_t count) -{ - struct se_portal_group *se_tpg = to_tpg(item); - struct usbg_tpg *tpg = container_of(se_tpg, struct usbg_tpg, se_tpg); - unsigned char i_port[USBG_NAMELEN], *ptr; - int ret; - - if (!strncmp(page, "NULL", 4)) { - ret = tcm_usbg_drop_nexus(tpg); - return (!ret) ? count : ret; - } - if (strlen(page) >= USBG_NAMELEN) { - pr_err("Emulated NAA Sas Address: %s, exceeds" - " max: %d\n", page, USBG_NAMELEN); - return -EINVAL; - } - snprintf(i_port, USBG_NAMELEN, "%s", page); - - ptr = strstr(i_port, "naa."); - if (!ptr) { - pr_err("Missing 'naa.' prefix\n"); - return -EINVAL; - } - - if (i_port[strlen(i_port) - 1] == '\n') - i_port[strlen(i_port) - 1] = '\0'; - - ret = tcm_usbg_make_nexus(tpg, &i_port[4]); - if (ret < 0) - return ret; - return count; -} - -CONFIGFS_ATTR(tcm_usbg_tpg_, enable); -CONFIGFS_ATTR(tcm_usbg_tpg_, nexus); - -static struct configfs_attribute *usbg_base_attrs[] = { - &tcm_usbg_tpg_attr_enable, - &tcm_usbg_tpg_attr_nexus, - NULL, -}; - -static int usbg_port_link(struct se_portal_group *se_tpg, struct se_lun *lun) -{ - struct usbg_tpg *tpg = container_of(se_tpg, struct usbg_tpg, se_tpg); - - atomic_inc(&tpg->tpg_port_count); - smp_mb__after_atomic(); - return 0; -} - -static void usbg_port_unlink(struct se_portal_group *se_tpg, - struct se_lun *se_lun) -{ - struct usbg_tpg *tpg = container_of(se_tpg, struct usbg_tpg, se_tpg); - - atomic_dec(&tpg->tpg_port_count); - smp_mb__after_atomic(); -} - -static int usbg_check_stop_free(struct se_cmd *se_cmd) -{ - struct usbg_cmd *cmd = container_of(se_cmd, struct usbg_cmd, - se_cmd); - - kref_put(&cmd->ref, usbg_cmd_release); - return 1; -} - -static const struct target_core_fabric_ops usbg_ops = { - .module = THIS_MODULE, - .name = "usb_gadget", - .get_fabric_name = usbg_get_fabric_name, - .tpg_get_wwn = usbg_get_fabric_wwn, - .tpg_get_tag = usbg_get_tag, - .tpg_check_demo_mode = usbg_check_true, - .tpg_check_demo_mode_cache = usbg_check_false, - .tpg_check_demo_mode_write_protect = usbg_check_false, - .tpg_check_prod_mode_write_protect = usbg_check_false, - .tpg_get_inst_index = usbg_tpg_get_inst_index, - .release_cmd = usbg_release_cmd, - .shutdown_session = usbg_shutdown_session, - .close_session = usbg_close_session, - .sess_get_index = usbg_sess_get_index, - .sess_get_initiator_sid = NULL, - .write_pending = usbg_send_write_request, - .write_pending_status = usbg_write_pending_status, - .set_default_node_attributes = usbg_set_default_node_attrs, - .get_cmd_state = usbg_get_cmd_state, - .queue_data_in = usbg_send_read_response, - .queue_status = usbg_send_status_response, - .queue_tm_rsp = usbg_queue_tm_rsp, - .aborted_task = usbg_aborted_task, - .check_stop_free = usbg_check_stop_free, - - .fabric_make_wwn = usbg_make_tport, - .fabric_drop_wwn = usbg_drop_tport, - .fabric_make_tpg = usbg_make_tpg, - .fabric_drop_tpg = usbg_drop_tpg, - .fabric_post_link = usbg_port_link, - .fabric_pre_unlink = usbg_port_unlink, - .fabric_init_nodeacl = usbg_init_nodeacl, - - .tfc_wwn_attrs = usbg_wwn_attrs, - .tfc_tpg_base_attrs = usbg_base_attrs, -}; - -/* Start gadget.c code */ - -static struct usb_interface_descriptor bot_intf_desc = { - .bLength = sizeof(bot_intf_desc), - .bDescriptorType = USB_DT_INTERFACE, - .bNumEndpoints = 2, - .bAlternateSetting = USB_G_ALT_INT_BBB, - .bInterfaceClass = USB_CLASS_MASS_STORAGE, - .bInterfaceSubClass = USB_SC_SCSI, - .bInterfaceProtocol = USB_PR_BULK, -}; - -static struct usb_interface_descriptor uasp_intf_desc = { - .bLength = sizeof(uasp_intf_desc), - .bDescriptorType = USB_DT_INTERFACE, - .bNumEndpoints = 4, - .bAlternateSetting = USB_G_ALT_INT_UAS, - .bInterfaceClass = USB_CLASS_MASS_STORAGE, - .bInterfaceSubClass = USB_SC_SCSI, - .bInterfaceProtocol = USB_PR_UAS, -}; - -static struct usb_endpoint_descriptor uasp_bi_desc = { - .bLength = USB_DT_ENDPOINT_SIZE, - .bDescriptorType = USB_DT_ENDPOINT, - .bEndpointAddress = USB_DIR_IN, - .bmAttributes = USB_ENDPOINT_XFER_BULK, - .wMaxPacketSize = cpu_to_le16(512), -}; - -static struct usb_endpoint_descriptor uasp_fs_bi_desc = { - .bLength = USB_DT_ENDPOINT_SIZE, - .bDescriptorType = USB_DT_ENDPOINT, - .bEndpointAddress = USB_DIR_IN, - .bmAttributes = USB_ENDPOINT_XFER_BULK, -}; - -static struct usb_pipe_usage_descriptor uasp_bi_pipe_desc = { - .bLength = sizeof(uasp_bi_pipe_desc), - .bDescriptorType = USB_DT_PIPE_USAGE, - .bPipeID = DATA_IN_PIPE_ID, -}; - -static struct usb_endpoint_descriptor uasp_ss_bi_desc = { - .bLength = USB_DT_ENDPOINT_SIZE, - .bDescriptorType = USB_DT_ENDPOINT, - .bEndpointAddress = USB_DIR_IN, - .bmAttributes = USB_ENDPOINT_XFER_BULK, - .wMaxPacketSize = cpu_to_le16(1024), -}; - -static struct usb_ss_ep_comp_descriptor uasp_bi_ep_comp_desc = { - .bLength = sizeof(uasp_bi_ep_comp_desc), - .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, - .bMaxBurst = 0, - .bmAttributes = UASP_SS_EP_COMP_LOG_STREAMS, - .wBytesPerInterval = 0, -}; - -static struct usb_ss_ep_comp_descriptor bot_bi_ep_comp_desc = { - .bLength = sizeof(bot_bi_ep_comp_desc), - .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, - .bMaxBurst = 0, -}; - -static struct usb_endpoint_descriptor uasp_bo_desc = { - .bLength = USB_DT_ENDPOINT_SIZE, - .bDescriptorType = USB_DT_ENDPOINT, - .bEndpointAddress = USB_DIR_OUT, - .bmAttributes = USB_ENDPOINT_XFER_BULK, - .wMaxPacketSize = cpu_to_le16(512), -}; - -static struct usb_endpoint_descriptor uasp_fs_bo_desc = { - .bLength = USB_DT_ENDPOINT_SIZE, - .bDescriptorType = USB_DT_ENDPOINT, - .bEndpointAddress = USB_DIR_OUT, - .bmAttributes = USB_ENDPOINT_XFER_BULK, -}; - -static struct usb_pipe_usage_descriptor uasp_bo_pipe_desc = { - .bLength = sizeof(uasp_bo_pipe_desc), - .bDescriptorType = USB_DT_PIPE_USAGE, - .bPipeID = DATA_OUT_PIPE_ID, -}; - -static struct usb_endpoint_descriptor uasp_ss_bo_desc = { - .bLength = USB_DT_ENDPOINT_SIZE, - .bDescriptorType = USB_DT_ENDPOINT, - .bEndpointAddress = USB_DIR_OUT, - .bmAttributes = USB_ENDPOINT_XFER_BULK, - .wMaxPacketSize = cpu_to_le16(0x400), -}; - -static struct usb_ss_ep_comp_descriptor uasp_bo_ep_comp_desc = { - .bLength = sizeof(uasp_bo_ep_comp_desc), - .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, - .bmAttributes = UASP_SS_EP_COMP_LOG_STREAMS, -}; - -static struct usb_ss_ep_comp_descriptor bot_bo_ep_comp_desc = { - .bLength = sizeof(bot_bo_ep_comp_desc), - .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, -}; - -static struct usb_endpoint_descriptor uasp_status_desc = { - .bLength = USB_DT_ENDPOINT_SIZE, - .bDescriptorType = USB_DT_ENDPOINT, - .bEndpointAddress = USB_DIR_IN, - .bmAttributes = USB_ENDPOINT_XFER_BULK, - .wMaxPacketSize = cpu_to_le16(512), -}; - -static struct usb_endpoint_descriptor uasp_fs_status_desc = { - .bLength = USB_DT_ENDPOINT_SIZE, - .bDescriptorType = USB_DT_ENDPOINT, - .bEndpointAddress = USB_DIR_IN, - .bmAttributes = USB_ENDPOINT_XFER_BULK, -}; - -static struct usb_pipe_usage_descriptor uasp_status_pipe_desc = { - .bLength = sizeof(uasp_status_pipe_desc), - .bDescriptorType = USB_DT_PIPE_USAGE, - .bPipeID = STATUS_PIPE_ID, -}; - -static struct usb_endpoint_descriptor uasp_ss_status_desc = { - .bLength = USB_DT_ENDPOINT_SIZE, - .bDescriptorType = USB_DT_ENDPOINT, - .bEndpointAddress = USB_DIR_IN, - .bmAttributes = USB_ENDPOINT_XFER_BULK, - .wMaxPacketSize = cpu_to_le16(1024), -}; - -static struct usb_ss_ep_comp_descriptor uasp_status_in_ep_comp_desc = { - .bLength = sizeof(uasp_status_in_ep_comp_desc), - .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, - .bmAttributes = UASP_SS_EP_COMP_LOG_STREAMS, -}; - -static struct usb_endpoint_descriptor uasp_cmd_desc = { - .bLength = USB_DT_ENDPOINT_SIZE, - .bDescriptorType = USB_DT_ENDPOINT, - .bEndpointAddress = USB_DIR_OUT, - .bmAttributes = USB_ENDPOINT_XFER_BULK, - .wMaxPacketSize = cpu_to_le16(512), -}; - -static struct usb_endpoint_descriptor uasp_fs_cmd_desc = { - .bLength = USB_DT_ENDPOINT_SIZE, - .bDescriptorType = USB_DT_ENDPOINT, - .bEndpointAddress = USB_DIR_OUT, - .bmAttributes = USB_ENDPOINT_XFER_BULK, -}; - -static struct usb_pipe_usage_descriptor uasp_cmd_pipe_desc = { - .bLength = sizeof(uasp_cmd_pipe_desc), - .bDescriptorType = USB_DT_PIPE_USAGE, - .bPipeID = CMD_PIPE_ID, -}; - -static struct usb_endpoint_descriptor uasp_ss_cmd_desc = { - .bLength = USB_DT_ENDPOINT_SIZE, - .bDescriptorType = USB_DT_ENDPOINT, - .bEndpointAddress = USB_DIR_OUT, - .bmAttributes = USB_ENDPOINT_XFER_BULK, - .wMaxPacketSize = cpu_to_le16(1024), -}; - -static struct usb_ss_ep_comp_descriptor uasp_cmd_comp_desc = { - .bLength = sizeof(uasp_cmd_comp_desc), - .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, -}; - -static struct usb_descriptor_header *uasp_fs_function_desc[] = { - (struct usb_descriptor_header *) &bot_intf_desc, - (struct usb_descriptor_header *) &uasp_fs_bi_desc, - (struct usb_descriptor_header *) &uasp_fs_bo_desc, - - (struct usb_descriptor_header *) &uasp_intf_desc, - (struct usb_descriptor_header *) &uasp_fs_bi_desc, - (struct usb_descriptor_header *) &uasp_bi_pipe_desc, - (struct usb_descriptor_header *) &uasp_fs_bo_desc, - (struct usb_descriptor_header *) &uasp_bo_pipe_desc, - (struct usb_descriptor_header *) &uasp_fs_status_desc, - (struct usb_descriptor_header *) &uasp_status_pipe_desc, - (struct usb_descriptor_header *) &uasp_fs_cmd_desc, - (struct usb_descriptor_header *) &uasp_cmd_pipe_desc, - NULL, -}; - -static struct usb_descriptor_header *uasp_hs_function_desc[] = { - (struct usb_descriptor_header *) &bot_intf_desc, - (struct usb_descriptor_header *) &uasp_bi_desc, - (struct usb_descriptor_header *) &uasp_bo_desc, - - (struct usb_descriptor_header *) &uasp_intf_desc, - (struct usb_descriptor_header *) &uasp_bi_desc, - (struct usb_descriptor_header *) &uasp_bi_pipe_desc, - (struct usb_descriptor_header *) &uasp_bo_desc, - (struct usb_descriptor_header *) &uasp_bo_pipe_desc, - (struct usb_descriptor_header *) &uasp_status_desc, - (struct usb_descriptor_header *) &uasp_status_pipe_desc, - (struct usb_descriptor_header *) &uasp_cmd_desc, - (struct usb_descriptor_header *) &uasp_cmd_pipe_desc, - NULL, -}; - -static struct usb_descriptor_header *uasp_ss_function_desc[] = { - (struct usb_descriptor_header *) &bot_intf_desc, - (struct usb_descriptor_header *) &uasp_ss_bi_desc, - (struct usb_descriptor_header *) &bot_bi_ep_comp_desc, - (struct usb_descriptor_header *) &uasp_ss_bo_desc, - (struct usb_descriptor_header *) &bot_bo_ep_comp_desc, - - (struct usb_descriptor_header *) &uasp_intf_desc, - (struct usb_descriptor_header *) &uasp_ss_bi_desc, - (struct usb_descriptor_header *) &uasp_bi_ep_comp_desc, - (struct usb_descriptor_header *) &uasp_bi_pipe_desc, - (struct usb_descriptor_header *) &uasp_ss_bo_desc, - (struct usb_descriptor_header *) &uasp_bo_ep_comp_desc, - (struct usb_descriptor_header *) &uasp_bo_pipe_desc, - (struct usb_descriptor_header *) &uasp_ss_status_desc, - (struct usb_descriptor_header *) &uasp_status_in_ep_comp_desc, - (struct usb_descriptor_header *) &uasp_status_pipe_desc, - (struct usb_descriptor_header *) &uasp_ss_cmd_desc, - (struct usb_descriptor_header *) &uasp_cmd_comp_desc, - (struct usb_descriptor_header *) &uasp_cmd_pipe_desc, - NULL, -}; - #define UAS_VENDOR_ID 0x0525 /* NetChip */ #define UAS_PRODUCT_ID 0xa4a5 /* Linux-USB File-backed Storage Gadget */ @@ -1981,13 +38,13 @@ static struct usb_device_descriptor usbg_device_desc = { .bNumConfigurations = 1, }; +#define USB_G_STR_CONFIG USB_GADGET_FIRST_AVAIL_IDX + static struct usb_string usbg_us_strings[] = { [USB_GADGET_MANUFACTURER_IDX].s = "Target Manufactor", [USB_GADGET_PRODUCT_IDX].s = "Target Product", [USB_GADGET_SERIAL_IDX].s = "000000000001", [USB_G_STR_CONFIG].s = "default config", - [USB_G_STR_INT_UAS].s = "USB Attached SCSI", - [USB_G_STR_INT_BBB].s = "Bulk Only Transport", { }, }; @@ -2001,184 +58,42 @@ static struct usb_gadget_strings *usbg_strings[] = { NULL, }; -static int guas_unbind(struct usb_composite_dev *cdev) -{ - return 0; -} - -static struct usb_configuration usbg_config_driver = { - .label = "Linux Target", - .bConfigurationValue = 1, - .bmAttributes = USB_CONFIG_ATT_SELFPOWER, -}; +static struct usb_function_instance *fi_tcm; +static struct usb_function *f_tcm; -static int usbg_bind(struct usb_configuration *c, struct usb_function *f) +static int guas_unbind(struct usb_composite_dev *cdev) { - struct f_uas *fu = to_f_uas(f); - struct usb_gadget *gadget = c->cdev->gadget; - struct usb_ep *ep; - int iface; - int ret; - - iface = usb_interface_id(c, f); - if (iface < 0) - return iface; - - bot_intf_desc.bInterfaceNumber = iface; - uasp_intf_desc.bInterfaceNumber = iface; - fu->iface = iface; - ep = usb_ep_autoconfig_ss(gadget, &uasp_ss_bi_desc, - &uasp_bi_ep_comp_desc); - if (!ep) - goto ep_fail; - fu->ep_in = ep; - - ep = usb_ep_autoconfig_ss(gadget, &uasp_ss_bo_desc, - &uasp_bo_ep_comp_desc); - if (!ep) - goto ep_fail; - fu->ep_out = ep; - - ep = usb_ep_autoconfig_ss(gadget, &uasp_ss_status_desc, - &uasp_status_in_ep_comp_desc); - if (!ep) - goto ep_fail; - fu->ep_status = ep; - - ep = usb_ep_autoconfig_ss(gadget, &uasp_ss_cmd_desc, - &uasp_cmd_comp_desc); - if (!ep) - goto ep_fail; - fu->ep_cmd = ep; - - /* Assume endpoint addresses are the same for both speeds */ - uasp_bi_desc.bEndpointAddress = uasp_ss_bi_desc.bEndpointAddress; - uasp_bo_desc.bEndpointAddress = uasp_ss_bo_desc.bEndpointAddress; - uasp_status_desc.bEndpointAddress = - uasp_ss_status_desc.bEndpointAddress; - uasp_cmd_desc.bEndpointAddress = uasp_ss_cmd_desc.bEndpointAddress; - - uasp_fs_bi_desc.bEndpointAddress = uasp_ss_bi_desc.bEndpointAddress; - uasp_fs_bo_desc.bEndpointAddress = uasp_ss_bo_desc.bEndpointAddress; - uasp_fs_status_desc.bEndpointAddress = - uasp_ss_status_desc.bEndpointAddress; - uasp_fs_cmd_desc.bEndpointAddress = uasp_ss_cmd_desc.bEndpointAddress; - - ret = usb_assign_descriptors(f, uasp_fs_function_desc, - uasp_hs_function_desc, uasp_ss_function_desc); - if (ret) - goto ep_fail; + if (!IS_ERR_OR_NULL(f_tcm)) + usb_put_function(f_tcm); return 0; -ep_fail: - pr_err("Can't claim all required eps\n"); - return -ENOTSUPP; } -static void usbg_unbind(struct usb_configuration *c, struct usb_function *f) +static int tcm_do_config(struct usb_configuration *c) { - struct f_uas *fu = to_f_uas(f); + int status; - usb_free_all_descriptors(f); - kfree(fu); -} - -struct guas_setup_wq { - struct work_struct work; - struct f_uas *fu; - unsigned int alt; -}; - -static void usbg_delayed_set_alt(struct work_struct *wq) -{ - struct guas_setup_wq *work = container_of(wq, struct guas_setup_wq, - work); - struct f_uas *fu = work->fu; - int alt = work->alt; - - kfree(work); - - if (fu->flags & USBG_IS_BOT) - bot_cleanup_old_alt(fu); - if (fu->flags & USBG_IS_UAS) - uasp_cleanup_old_alt(fu); - - if (alt == USB_G_ALT_INT_BBB) - bot_set_alt(fu); - else if (alt == USB_G_ALT_INT_UAS) - uasp_set_alt(fu); - usb_composite_setup_continue(fu->function.config->cdev); -} - -static int usbg_set_alt(struct usb_function *f, unsigned intf, unsigned alt) -{ - struct f_uas *fu = to_f_uas(f); - - if ((alt == USB_G_ALT_INT_BBB) || (alt == USB_G_ALT_INT_UAS)) { - struct guas_setup_wq *work; + f_tcm = usb_get_function(fi_tcm); + if (IS_ERR(f_tcm)) + return PTR_ERR(f_tcm); - work = kmalloc(sizeof(*work), GFP_ATOMIC); - if (!work) - return -ENOMEM; - INIT_WORK(&work->work, usbg_delayed_set_alt); - work->fu = fu; - work->alt = alt; - schedule_work(&work->work); - return USB_GADGET_DELAYED_STATUS; + status = usb_add_function(c, f_tcm); + if (status < 0) { + usb_put_function(f_tcm); + return status; } - return -EOPNOTSUPP; -} - -static void usbg_disable(struct usb_function *f) -{ - struct f_uas *fu = to_f_uas(f); - - if (fu->flags & USBG_IS_UAS) - uasp_cleanup_old_alt(fu); - else if (fu->flags & USBG_IS_BOT) - bot_cleanup_old_alt(fu); - fu->flags = 0; -} - -static int usbg_setup(struct usb_function *f, - const struct usb_ctrlrequest *ctrl) -{ - struct f_uas *fu = to_f_uas(f); - - if (!(fu->flags & USBG_IS_BOT)) - return -EOPNOTSUPP; - return usbg_bot_setup(f, ctrl); + return 0; } -static int usbg_cfg_bind(struct usb_configuration *c) -{ - struct f_uas *fu; - int ret; - - fu = kzalloc(sizeof(*fu), GFP_KERNEL); - if (!fu) - return -ENOMEM; - fu->function.name = "Target Function"; - fu->function.bind = usbg_bind; - fu->function.unbind = usbg_unbind; - fu->function.set_alt = usbg_set_alt; - fu->function.setup = usbg_setup; - fu->function.disable = usbg_disable; - fu->tpg = the_only_tpg_I_currently_have; - - bot_intf_desc.iInterface = usbg_us_strings[USB_G_STR_INT_BBB].id; - uasp_intf_desc.iInterface = usbg_us_strings[USB_G_STR_INT_UAS].id; - - ret = usb_add_function(c, &fu->function); - if (ret) - goto err; +static struct usb_configuration usbg_config_driver = { + .label = "Linux Target", + .bConfigurationValue = 1, + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, +}; - return 0; -err: - kfree(fu); - return ret; -} +static int usbg_attach(struct usb_function_instance *f); +static void usbg_detach(struct usb_function_instance *f); static int usb_target_bind(struct usb_composite_dev *cdev) { @@ -2196,8 +111,7 @@ static int usb_target_bind(struct usb_composite_dev *cdev) usbg_config_driver.iConfiguration = usbg_us_strings[USB_G_STR_CONFIG].id; - ret = usb_add_config(cdev, &usbg_config_driver, - usbg_cfg_bind); + ret = usb_add_config(cdev, &usbg_config_driver, tcm_do_config); if (ret) return ret; usb_composite_overwrite_options(cdev, &coverwrite); @@ -2213,25 +127,44 @@ static struct usb_composite_driver usbg_driver = { .unbind = guas_unbind, }; -static int usbg_attach(struct usbg_tpg *tpg) +static int usbg_attach(struct usb_function_instance *f) { return usb_composite_probe(&usbg_driver); } -static void usbg_detach(struct usbg_tpg *tpg) +static void usbg_detach(struct usb_function_instance *f) { usb_composite_unregister(&usbg_driver); } static int __init usb_target_gadget_init(void) { - return target_register_template(&usbg_ops); + struct f_tcm_opts *tcm_opts; + + fi_tcm = usb_get_function_instance("tcm"); + if (IS_ERR(fi_tcm)) + return PTR_ERR(fi_tcm); + + tcm_opts = container_of(fi_tcm, struct f_tcm_opts, func_inst); + mutex_lock(&tcm_opts->dep_lock); + tcm_opts->tcm_register_callback = usbg_attach; + tcm_opts->tcm_unregister_callback = usbg_detach; + tcm_opts->dependent = THIS_MODULE; + tcm_opts->can_attach = true; + tcm_opts->has_dep = true; + mutex_unlock(&tcm_opts->dep_lock); + + fi_tcm->set_inst_name(fi_tcm, "tcm-legacy"); + + return 0; } module_init(usb_target_gadget_init); static void __exit usb_target_gadget_exit(void) { - target_unregister_template(&usbg_ops); + if (!IS_ERR_OR_NULL(fi_tcm)) + usb_put_function_instance(fi_tcm); + } module_exit(usb_target_gadget_exit); |