diff options
Diffstat (limited to 'drivers/scsi/bnx2fc')
-rw-r--r-- | drivers/scsi/bnx2fc/57xx_hsi_bnx2fc.h | 1080 | ||||
-rw-r--r-- | drivers/scsi/bnx2fc/Kconfig | 11 | ||||
-rw-r--r-- | drivers/scsi/bnx2fc/Makefile | 3 | ||||
-rw-r--r-- | drivers/scsi/bnx2fc/bnx2fc.h | 517 | ||||
-rw-r--r-- | drivers/scsi/bnx2fc/bnx2fc_constants.h | 206 | ||||
-rw-r--r-- | drivers/scsi/bnx2fc/bnx2fc_debug.h | 70 | ||||
-rw-r--r-- | drivers/scsi/bnx2fc/bnx2fc_els.c | 515 | ||||
-rw-r--r-- | drivers/scsi/bnx2fc/bnx2fc_fcoe.c | 2461 | ||||
-rw-r--r-- | drivers/scsi/bnx2fc/bnx2fc_hwi.c | 1877 | ||||
-rw-r--r-- | drivers/scsi/bnx2fc/bnx2fc_io.c | 1856 | ||||
-rw-r--r-- | drivers/scsi/bnx2fc/bnx2fc_tgt.c | 844 |
11 files changed, 9440 insertions, 0 deletions
diff --git a/drivers/scsi/bnx2fc/57xx_hsi_bnx2fc.h b/drivers/scsi/bnx2fc/57xx_hsi_bnx2fc.h new file mode 100644 index 000000000000..69d031d98469 --- /dev/null +++ b/drivers/scsi/bnx2fc/57xx_hsi_bnx2fc.h @@ -0,0 +1,1080 @@ +#ifndef __57XX_FCOE_HSI_LINUX_LE__ +#define __57XX_FCOE_HSI_LINUX_LE__ + +/* + * common data for all protocols + */ +struct b577xx_doorbell_hdr { + u8 header; +#define B577XX_DOORBELL_HDR_RX (0x1<<0) +#define B577XX_DOORBELL_HDR_RX_SHIFT 0 +#define B577XX_DOORBELL_HDR_DB_TYPE (0x1<<1) +#define B577XX_DOORBELL_HDR_DB_TYPE_SHIFT 1 +#define B577XX_DOORBELL_HDR_DPM_SIZE (0x3<<2) +#define B577XX_DOORBELL_HDR_DPM_SIZE_SHIFT 2 +#define B577XX_DOORBELL_HDR_CONN_TYPE (0xF<<4) +#define B577XX_DOORBELL_HDR_CONN_TYPE_SHIFT 4 +}; + +/* + * doorbell message sent to the chip + */ +struct b577xx_doorbell_set_prod { +#if defined(__BIG_ENDIAN) + u16 prod; + u8 zero_fill1; + struct b577xx_doorbell_hdr header; +#elif defined(__LITTLE_ENDIAN) + struct b577xx_doorbell_hdr header; + u8 zero_fill1; + u16 prod; +#endif +}; + + +struct regpair { + __le32 lo; + __le32 hi; +}; + + +/* + * Fixed size structure in order to plant it in Union structure + */ +struct fcoe_abts_rsp_union { + u32 r_ctl; + u32 abts_rsp_payload[7]; +}; + + +/* + * 4 regs size + */ +struct fcoe_bd_ctx { + u32 buf_addr_hi; + u32 buf_addr_lo; +#if defined(__BIG_ENDIAN) + u16 rsrv0; + u16 buf_len; +#elif defined(__LITTLE_ENDIAN) + u16 buf_len; + u16 rsrv0; +#endif +#if defined(__BIG_ENDIAN) + u16 rsrv1; + u16 flags; +#elif defined(__LITTLE_ENDIAN) + u16 flags; + u16 rsrv1; +#endif +}; + + +struct fcoe_cleanup_flow_info { +#if defined(__BIG_ENDIAN) + u16 reserved1; + u16 task_id; +#elif defined(__LITTLE_ENDIAN) + u16 task_id; + u16 reserved1; +#endif + u32 reserved2[7]; +}; + + +struct fcoe_fcp_cmd_payload { + u32 opaque[8]; +}; + +struct fcoe_fc_hdr { +#if defined(__BIG_ENDIAN) + u8 cs_ctl; + u8 s_id[3]; +#elif defined(__LITTLE_ENDIAN) + u8 s_id[3]; + u8 cs_ctl; +#endif +#if defined(__BIG_ENDIAN) + u8 r_ctl; + u8 d_id[3]; +#elif defined(__LITTLE_ENDIAN) + u8 d_id[3]; + u8 r_ctl; +#endif +#if defined(__BIG_ENDIAN) + u8 seq_id; + u8 df_ctl; + u16 seq_cnt; +#elif defined(__LITTLE_ENDIAN) + u16 seq_cnt; + u8 df_ctl; + u8 seq_id; +#endif +#if defined(__BIG_ENDIAN) + u8 type; + u8 f_ctl[3]; +#elif defined(__LITTLE_ENDIAN) + u8 f_ctl[3]; + u8 type; +#endif + u32 parameters; +#if defined(__BIG_ENDIAN) + u16 ox_id; + u16 rx_id; +#elif defined(__LITTLE_ENDIAN) + u16 rx_id; + u16 ox_id; +#endif +}; + +struct fcoe_fc_frame { + struct fcoe_fc_hdr fc_hdr; + u32 reserved0[2]; +}; + +union fcoe_cmd_flow_info { + struct fcoe_fcp_cmd_payload fcp_cmd_payload; + struct fcoe_fc_frame mp_fc_frame; +}; + + + +struct fcoe_fcp_rsp_flags { + u8 flags; +#define FCOE_FCP_RSP_FLAGS_FCP_RSP_LEN_VALID (0x1<<0) +#define FCOE_FCP_RSP_FLAGS_FCP_RSP_LEN_VALID_SHIFT 0 +#define FCOE_FCP_RSP_FLAGS_FCP_SNS_LEN_VALID (0x1<<1) +#define FCOE_FCP_RSP_FLAGS_FCP_SNS_LEN_VALID_SHIFT 1 +#define FCOE_FCP_RSP_FLAGS_FCP_RESID_OVER (0x1<<2) +#define FCOE_FCP_RSP_FLAGS_FCP_RESID_OVER_SHIFT 2 +#define FCOE_FCP_RSP_FLAGS_FCP_RESID_UNDER (0x1<<3) +#define FCOE_FCP_RSP_FLAGS_FCP_RESID_UNDER_SHIFT 3 +#define FCOE_FCP_RSP_FLAGS_FCP_CONF_REQ (0x1<<4) +#define FCOE_FCP_RSP_FLAGS_FCP_CONF_REQ_SHIFT 4 +#define FCOE_FCP_RSP_FLAGS_FCP_BIDI_FLAGS (0x7<<5) +#define FCOE_FCP_RSP_FLAGS_FCP_BIDI_FLAGS_SHIFT 5 +}; + + +struct fcoe_fcp_rsp_payload { + struct regpair reserved0; + u32 fcp_resid; +#if defined(__BIG_ENDIAN) + u16 retry_delay_timer; + struct fcoe_fcp_rsp_flags fcp_flags; + u8 scsi_status_code; +#elif defined(__LITTLE_ENDIAN) + u8 scsi_status_code; + struct fcoe_fcp_rsp_flags fcp_flags; + u16 retry_delay_timer; +#endif + u32 fcp_rsp_len; + u32 fcp_sns_len; +}; + + +/* + * Fixed size structure in order to plant it in Union structure + */ +struct fcoe_fcp_rsp_union { + struct fcoe_fcp_rsp_payload payload; + struct regpair reserved0; +}; + + +struct fcoe_fcp_xfr_rdy_payload { + u32 burst_len; + u32 data_ro; +}; + +struct fcoe_read_flow_info { + struct fcoe_fc_hdr fc_data_in_hdr; + u32 reserved[2]; +}; + +struct fcoe_write_flow_info { + struct fcoe_fc_hdr fc_data_out_hdr; + struct fcoe_fcp_xfr_rdy_payload fcp_xfr_payload; +}; + +union fcoe_rsp_flow_info { + struct fcoe_fcp_rsp_union fcp_rsp; + struct fcoe_abts_rsp_union abts_rsp; +}; + +/* + * 32 bytes used for general purposes + */ +union fcoe_general_task_ctx { + union fcoe_cmd_flow_info cmd_info; + struct fcoe_read_flow_info read_info; + struct fcoe_write_flow_info write_info; + union fcoe_rsp_flow_info rsp_info; + struct fcoe_cleanup_flow_info cleanup_info; + u32 comp_info[8]; +}; + + +/* + * FCoE KCQ CQE parameters + */ +union fcoe_kcqe_params { + u32 reserved0[4]; +}; + +/* + * FCoE KCQ CQE + */ +struct fcoe_kcqe { + u32 fcoe_conn_id; + u32 completion_status; + u32 fcoe_conn_context_id; + union fcoe_kcqe_params params; +#if defined(__BIG_ENDIAN) + u8 flags; +#define FCOE_KCQE_RESERVED0 (0x7<<0) +#define FCOE_KCQE_RESERVED0_SHIFT 0 +#define FCOE_KCQE_RAMROD_COMPLETION (0x1<<3) +#define FCOE_KCQE_RAMROD_COMPLETION_SHIFT 3 +#define FCOE_KCQE_LAYER_CODE (0x7<<4) +#define FCOE_KCQE_LAYER_CODE_SHIFT 4 +#define FCOE_KCQE_LINKED_WITH_NEXT (0x1<<7) +#define FCOE_KCQE_LINKED_WITH_NEXT_SHIFT 7 + u8 op_code; + u16 qe_self_seq; +#elif defined(__LITTLE_ENDIAN) + u16 qe_self_seq; + u8 op_code; + u8 flags; +#define FCOE_KCQE_RESERVED0 (0x7<<0) +#define FCOE_KCQE_RESERVED0_SHIFT 0 +#define FCOE_KCQE_RAMROD_COMPLETION (0x1<<3) +#define FCOE_KCQE_RAMROD_COMPLETION_SHIFT 3 +#define FCOE_KCQE_LAYER_CODE (0x7<<4) +#define FCOE_KCQE_LAYER_CODE_SHIFT 4 +#define FCOE_KCQE_LINKED_WITH_NEXT (0x1<<7) +#define FCOE_KCQE_LINKED_WITH_NEXT_SHIFT 7 +#endif +}; + +/* + * FCoE KWQE header + */ +struct fcoe_kwqe_header { +#if defined(__BIG_ENDIAN) + u8 flags; +#define FCOE_KWQE_HEADER_RESERVED0 (0xF<<0) +#define FCOE_KWQE_HEADER_RESERVED0_SHIFT 0 +#define FCOE_KWQE_HEADER_LAYER_CODE (0x7<<4) +#define FCOE_KWQE_HEADER_LAYER_CODE_SHIFT 4 +#define FCOE_KWQE_HEADER_RESERVED1 (0x1<<7) +#define FCOE_KWQE_HEADER_RESERVED1_SHIFT 7 + u8 op_code; +#elif defined(__LITTLE_ENDIAN) + u8 op_code; + u8 flags; +#define FCOE_KWQE_HEADER_RESERVED0 (0xF<<0) +#define FCOE_KWQE_HEADER_RESERVED0_SHIFT 0 +#define FCOE_KWQE_HEADER_LAYER_CODE (0x7<<4) +#define FCOE_KWQE_HEADER_LAYER_CODE_SHIFT 4 +#define FCOE_KWQE_HEADER_RESERVED1 (0x1<<7) +#define FCOE_KWQE_HEADER_RESERVED1_SHIFT 7 +#endif +}; + +/* + * FCoE firmware init request 1 + */ +struct fcoe_kwqe_init1 { +#if defined(__BIG_ENDIAN) + struct fcoe_kwqe_header hdr; + u16 num_tasks; +#elif defined(__LITTLE_ENDIAN) + u16 num_tasks; + struct fcoe_kwqe_header hdr; +#endif + u32 task_list_pbl_addr_lo; + u32 task_list_pbl_addr_hi; + u32 dummy_buffer_addr_lo; + u32 dummy_buffer_addr_hi; +#if defined(__BIG_ENDIAN) + u16 rq_num_wqes; + u16 sq_num_wqes; +#elif defined(__LITTLE_ENDIAN) + u16 sq_num_wqes; + u16 rq_num_wqes; +#endif +#if defined(__BIG_ENDIAN) + u16 cq_num_wqes; + u16 rq_buffer_log_size; +#elif defined(__LITTLE_ENDIAN) + u16 rq_buffer_log_size; + u16 cq_num_wqes; +#endif +#if defined(__BIG_ENDIAN) + u8 flags; +#define FCOE_KWQE_INIT1_LOG_PAGE_SIZE (0xF<<0) +#define FCOE_KWQE_INIT1_LOG_PAGE_SIZE_SHIFT 0 +#define FCOE_KWQE_INIT1_LOG_CACHED_PBES_PER_FUNC (0x7<<4) +#define FCOE_KWQE_INIT1_LOG_CACHED_PBES_PER_FUNC_SHIFT 4 +#define FCOE_KWQE_INIT1_RESERVED1 (0x1<<7) +#define FCOE_KWQE_INIT1_RESERVED1_SHIFT 7 + u8 num_sessions_log; + u16 mtu; +#elif defined(__LITTLE_ENDIAN) + u16 mtu; + u8 num_sessions_log; + u8 flags; +#define FCOE_KWQE_INIT1_LOG_PAGE_SIZE (0xF<<0) +#define FCOE_KWQE_INIT1_LOG_PAGE_SIZE_SHIFT 0 +#define FCOE_KWQE_INIT1_LOG_CACHED_PBES_PER_FUNC (0x7<<4) +#define FCOE_KWQE_INIT1_LOG_CACHED_PBES_PER_FUNC_SHIFT 4 +#define FCOE_KWQE_INIT1_RESERVED1 (0x1<<7) +#define FCOE_KWQE_INIT1_RESERVED1_SHIFT 7 +#endif +}; + +/* + * FCoE firmware init request 2 + */ +struct fcoe_kwqe_init2 { +#if defined(__BIG_ENDIAN) + struct fcoe_kwqe_header hdr; + u16 reserved0; +#elif defined(__LITTLE_ENDIAN) + u16 reserved0; + struct fcoe_kwqe_header hdr; +#endif + u32 hash_tbl_pbl_addr_lo; + u32 hash_tbl_pbl_addr_hi; + u32 t2_hash_tbl_addr_lo; + u32 t2_hash_tbl_addr_hi; + u32 t2_ptr_hash_tbl_addr_lo; + u32 t2_ptr_hash_tbl_addr_hi; + u32 free_list_count; +}; + +/* + * FCoE firmware init request 3 + */ +struct fcoe_kwqe_init3 { +#if defined(__BIG_ENDIAN) + struct fcoe_kwqe_header hdr; + u16 reserved0; +#elif defined(__LITTLE_ENDIAN) + u16 reserved0; + struct fcoe_kwqe_header hdr; +#endif + u32 error_bit_map_lo; + u32 error_bit_map_hi; +#if defined(__BIG_ENDIAN) + u8 reserved21[3]; + u8 cached_session_enable; +#elif defined(__LITTLE_ENDIAN) + u8 cached_session_enable; + u8 reserved21[3]; +#endif + u32 reserved2[4]; +}; + +/* + * FCoE connection offload request 1 + */ +struct fcoe_kwqe_conn_offload1 { +#if defined(__BIG_ENDIAN) + struct fcoe_kwqe_header hdr; + u16 fcoe_conn_id; +#elif defined(__LITTLE_ENDIAN) + u16 fcoe_conn_id; + struct fcoe_kwqe_header hdr; +#endif + u32 sq_addr_lo; + u32 sq_addr_hi; + u32 rq_pbl_addr_lo; + u32 rq_pbl_addr_hi; + u32 rq_first_pbe_addr_lo; + u32 rq_first_pbe_addr_hi; +#if defined(__BIG_ENDIAN) + u16 reserved0; + u16 rq_prod; +#elif defined(__LITTLE_ENDIAN) + u16 rq_prod; + u16 reserved0; +#endif +}; + +/* + * FCoE connection offload request 2 + */ +struct fcoe_kwqe_conn_offload2 { +#if defined(__BIG_ENDIAN) + struct fcoe_kwqe_header hdr; + u16 tx_max_fc_pay_len; +#elif defined(__LITTLE_ENDIAN) + u16 tx_max_fc_pay_len; + struct fcoe_kwqe_header hdr; +#endif + u32 cq_addr_lo; + u32 cq_addr_hi; + u32 xferq_addr_lo; + u32 xferq_addr_hi; + u32 conn_db_addr_lo; + u32 conn_db_addr_hi; + u32 reserved1; +}; + +/* + * FCoE connection offload request 3 + */ +struct fcoe_kwqe_conn_offload3 { +#if defined(__BIG_ENDIAN) + struct fcoe_kwqe_header hdr; + u16 vlan_tag; +#define FCOE_KWQE_CONN_OFFLOAD3_VLAN_ID (0xFFF<<0) +#define FCOE_KWQE_CONN_OFFLOAD3_VLAN_ID_SHIFT 0 +#define FCOE_KWQE_CONN_OFFLOAD3_CFI (0x1<<12) +#define FCOE_KWQE_CONN_OFFLOAD3_CFI_SHIFT 12 +#define FCOE_KWQE_CONN_OFFLOAD3_PRIORITY (0x7<<13) +#define FCOE_KWQE_CONN_OFFLOAD3_PRIORITY_SHIFT 13 +#elif defined(__LITTLE_ENDIAN) + u16 vlan_tag; +#define FCOE_KWQE_CONN_OFFLOAD3_VLAN_ID (0xFFF<<0) +#define FCOE_KWQE_CONN_OFFLOAD3_VLAN_ID_SHIFT 0 +#define FCOE_KWQE_CONN_OFFLOAD3_CFI (0x1<<12) +#define FCOE_KWQE_CONN_OFFLOAD3_CFI_SHIFT 12 +#define FCOE_KWQE_CONN_OFFLOAD3_PRIORITY (0x7<<13) +#define FCOE_KWQE_CONN_OFFLOAD3_PRIORITY_SHIFT 13 + struct fcoe_kwqe_header hdr; +#endif +#if defined(__BIG_ENDIAN) + u8 tx_max_conc_seqs_c3; + u8 s_id[3]; +#elif defined(__LITTLE_ENDIAN) + u8 s_id[3]; + u8 tx_max_conc_seqs_c3; +#endif +#if defined(__BIG_ENDIAN) + u8 flags; +#define FCOE_KWQE_CONN_OFFLOAD3_B_MUL_N_PORT_IDS (0x1<<0) +#define FCOE_KWQE_CONN_OFFLOAD3_B_MUL_N_PORT_IDS_SHIFT 0 +#define FCOE_KWQE_CONN_OFFLOAD3_B_E_D_TOV_RES (0x1<<1) +#define FCOE_KWQE_CONN_OFFLOAD3_B_E_D_TOV_RES_SHIFT 1 +#define FCOE_KWQE_CONN_OFFLOAD3_B_CONT_INCR_SEQ_CNT (0x1<<2) +#define FCOE_KWQE_CONN_OFFLOAD3_B_CONT_INCR_SEQ_CNT_SHIFT 2 +#define FCOE_KWQE_CONN_OFFLOAD3_B_CONF_REQ (0x1<<3) +#define FCOE_KWQE_CONN_OFFLOAD3_B_CONF_REQ_SHIFT 3 +#define FCOE_KWQE_CONN_OFFLOAD3_B_REC_VALID (0x1<<4) +#define FCOE_KWQE_CONN_OFFLOAD3_B_REC_VALID_SHIFT 4 +#define FCOE_KWQE_CONN_OFFLOAD3_B_C2_VALID (0x1<<5) +#define FCOE_KWQE_CONN_OFFLOAD3_B_C2_VALID_SHIFT 5 +#define FCOE_KWQE_CONN_OFFLOAD3_B_ACK_0 (0x1<<6) +#define FCOE_KWQE_CONN_OFFLOAD3_B_ACK_0_SHIFT 6 +#define FCOE_KWQE_CONN_OFFLOAD3_B_VLAN_FLAG (0x1<<7) +#define FCOE_KWQE_CONN_OFFLOAD3_B_VLAN_FLAG_SHIFT 7 + u8 d_id[3]; +#elif defined(__LITTLE_ENDIAN) + u8 d_id[3]; + u8 flags; +#define FCOE_KWQE_CONN_OFFLOAD3_B_MUL_N_PORT_IDS (0x1<<0) +#define FCOE_KWQE_CONN_OFFLOAD3_B_MUL_N_PORT_IDS_SHIFT 0 +#define FCOE_KWQE_CONN_OFFLOAD3_B_E_D_TOV_RES (0x1<<1) +#define FCOE_KWQE_CONN_OFFLOAD3_B_E_D_TOV_RES_SHIFT 1 +#define FCOE_KWQE_CONN_OFFLOAD3_B_CONT_INCR_SEQ_CNT (0x1<<2) +#define FCOE_KWQE_CONN_OFFLOAD3_B_CONT_INCR_SEQ_CNT_SHIFT 2 +#define FCOE_KWQE_CONN_OFFLOAD3_B_CONF_REQ (0x1<<3) +#define FCOE_KWQE_CONN_OFFLOAD3_B_CONF_REQ_SHIFT 3 +#define FCOE_KWQE_CONN_OFFLOAD3_B_REC_VALID (0x1<<4) +#define FCOE_KWQE_CONN_OFFLOAD3_B_REC_VALID_SHIFT 4 +#define FCOE_KWQE_CONN_OFFLOAD3_B_C2_VALID (0x1<<5) +#define FCOE_KWQE_CONN_OFFLOAD3_B_C2_VALID_SHIFT 5 +#define FCOE_KWQE_CONN_OFFLOAD3_B_ACK_0 (0x1<<6) +#define FCOE_KWQE_CONN_OFFLOAD3_B_ACK_0_SHIFT 6 +#define FCOE_KWQE_CONN_OFFLOAD3_B_VLAN_FLAG (0x1<<7) +#define FCOE_KWQE_CONN_OFFLOAD3_B_VLAN_FLAG_SHIFT 7 +#endif + u32 reserved; + u32 confq_first_pbe_addr_lo; + u32 confq_first_pbe_addr_hi; +#if defined(__BIG_ENDIAN) + u16 rx_max_fc_pay_len; + u16 tx_total_conc_seqs; +#elif defined(__LITTLE_ENDIAN) + u16 tx_total_conc_seqs; + u16 rx_max_fc_pay_len; +#endif +#if defined(__BIG_ENDIAN) + u8 rx_open_seqs_exch_c3; + u8 rx_max_conc_seqs_c3; + u16 rx_total_conc_seqs; +#elif defined(__LITTLE_ENDIAN) + u16 rx_total_conc_seqs; + u8 rx_max_conc_seqs_c3; + u8 rx_open_seqs_exch_c3; +#endif +}; + +/* + * FCoE connection offload request 4 + */ +struct fcoe_kwqe_conn_offload4 { +#if defined(__BIG_ENDIAN) + struct fcoe_kwqe_header hdr; + u8 reserved2; + u8 e_d_tov_timer_val; +#elif defined(__LITTLE_ENDIAN) + u8 e_d_tov_timer_val; + u8 reserved2; + struct fcoe_kwqe_header hdr; +#endif + u8 src_mac_addr_lo32[4]; +#if defined(__BIG_ENDIAN) + u8 dst_mac_addr_hi16[2]; + u8 src_mac_addr_hi16[2]; +#elif defined(__LITTLE_ENDIAN) + u8 src_mac_addr_hi16[2]; + u8 dst_mac_addr_hi16[2]; +#endif + u8 dst_mac_addr_lo32[4]; + u32 lcq_addr_lo; + u32 lcq_addr_hi; + u32 confq_pbl_base_addr_lo; + u32 confq_pbl_base_addr_hi; +}; + +/* + * FCoE connection enable request + */ +struct fcoe_kwqe_conn_enable_disable { +#if defined(__BIG_ENDIAN) + struct fcoe_kwqe_header hdr; + u16 reserved0; +#elif defined(__LITTLE_ENDIAN) + u16 reserved0; + struct fcoe_kwqe_header hdr; +#endif + u8 src_mac_addr_lo32[4]; +#if defined(__BIG_ENDIAN) + u16 vlan_tag; +#define FCOE_KWQE_CONN_ENABLE_DISABLE_VLAN_ID (0xFFF<<0) +#define FCOE_KWQE_CONN_ENABLE_DISABLE_VLAN_ID_SHIFT 0 +#define FCOE_KWQE_CONN_ENABLE_DISABLE_CFI (0x1<<12) +#define FCOE_KWQE_CONN_ENABLE_DISABLE_CFI_SHIFT 12 +#define FCOE_KWQE_CONN_ENABLE_DISABLE_PRIORITY (0x7<<13) +#define FCOE_KWQE_CONN_ENABLE_DISABLE_PRIORITY_SHIFT 13 + u8 src_mac_addr_hi16[2]; +#elif defined(__LITTLE_ENDIAN) + u8 src_mac_addr_hi16[2]; + u16 vlan_tag; +#define FCOE_KWQE_CONN_ENABLE_DISABLE_VLAN_ID (0xFFF<<0) +#define FCOE_KWQE_CONN_ENABLE_DISABLE_VLAN_ID_SHIFT 0 +#define FCOE_KWQE_CONN_ENABLE_DISABLE_CFI (0x1<<12) +#define FCOE_KWQE_CONN_ENABLE_DISABLE_CFI_SHIFT 12 +#define FCOE_KWQE_CONN_ENABLE_DISABLE_PRIORITY (0x7<<13) +#define FCOE_KWQE_CONN_ENABLE_DISABLE_PRIORITY_SHIFT 13 +#endif + u8 dst_mac_addr_lo32[4]; +#if defined(__BIG_ENDIAN) + u16 reserved1; + u8 dst_mac_addr_hi16[2]; +#elif defined(__LITTLE_ENDIAN) + u8 dst_mac_addr_hi16[2]; + u16 reserved1; +#endif +#if defined(__BIG_ENDIAN) + u8 vlan_flag; + u8 s_id[3]; +#elif defined(__LITTLE_ENDIAN) + u8 s_id[3]; + u8 vlan_flag; +#endif +#if defined(__BIG_ENDIAN) + u8 reserved3; + u8 d_id[3]; +#elif defined(__LITTLE_ENDIAN) + u8 d_id[3]; + u8 reserved3; +#endif + u32 context_id; + u32 conn_id; + u32 reserved4; +}; + +/* + * FCoE connection destroy request + */ +struct fcoe_kwqe_conn_destroy { +#if defined(__BIG_ENDIAN) + struct fcoe_kwqe_header hdr; + u16 reserved0; +#elif defined(__LITTLE_ENDIAN) + u16 reserved0; + struct fcoe_kwqe_header hdr; +#endif + u32 context_id; + u32 conn_id; + u32 reserved1[5]; +}; + +/* + * FCoe destroy request + */ +struct fcoe_kwqe_destroy { +#if defined(__BIG_ENDIAN) + struct fcoe_kwqe_header hdr; + u16 reserved0; +#elif defined(__LITTLE_ENDIAN) + u16 reserved0; + struct fcoe_kwqe_header hdr; +#endif + u32 reserved1[7]; +}; + +/* + * FCoe statistics request + */ +struct fcoe_kwqe_stat { +#if defined(__BIG_ENDIAN) + struct fcoe_kwqe_header hdr; + u16 reserved0; +#elif defined(__LITTLE_ENDIAN) + u16 reserved0; + struct fcoe_kwqe_header hdr; +#endif + u32 stat_params_addr_lo; + u32 stat_params_addr_hi; + u32 reserved1[5]; +}; + +/* + * FCoE KWQ WQE + */ +union fcoe_kwqe { + struct fcoe_kwqe_init1 init1; + struct fcoe_kwqe_init2 init2; + struct fcoe_kwqe_init3 init3; + struct fcoe_kwqe_conn_offload1 conn_offload1; + struct fcoe_kwqe_conn_offload2 conn_offload2; + struct fcoe_kwqe_conn_offload3 conn_offload3; + struct fcoe_kwqe_conn_offload4 conn_offload4; + struct fcoe_kwqe_conn_enable_disable conn_enable_disable; + struct fcoe_kwqe_conn_destroy conn_destroy; + struct fcoe_kwqe_destroy destroy; + struct fcoe_kwqe_stat statistics; +}; + +struct fcoe_mul_sges_ctx { + struct regpair cur_sge_addr; +#if defined(__BIG_ENDIAN) + u8 sgl_size; + u8 cur_sge_idx; + u16 cur_sge_off; +#elif defined(__LITTLE_ENDIAN) + u16 cur_sge_off; + u8 cur_sge_idx; + u8 sgl_size; +#endif +}; + +struct fcoe_s_stat_ctx { + u8 flags; +#define FCOE_S_STAT_CTX_ACTIVE (0x1<<0) +#define FCOE_S_STAT_CTX_ACTIVE_SHIFT 0 +#define FCOE_S_STAT_CTX_ACK_ABORT_SEQ_COND (0x1<<1) +#define FCOE_S_STAT_CTX_ACK_ABORT_SEQ_COND_SHIFT 1 +#define FCOE_S_STAT_CTX_ABTS_PERFORMED (0x1<<2) +#define FCOE_S_STAT_CTX_ABTS_PERFORMED_SHIFT 2 +#define FCOE_S_STAT_CTX_SEQ_TIMEOUT (0x1<<3) +#define FCOE_S_STAT_CTX_SEQ_TIMEOUT_SHIFT 3 +#define FCOE_S_STAT_CTX_P_RJT (0x1<<4) +#define FCOE_S_STAT_CTX_P_RJT_SHIFT 4 +#define FCOE_S_STAT_CTX_ACK_EOFT (0x1<<5) +#define FCOE_S_STAT_CTX_ACK_EOFT_SHIFT 5 +#define FCOE_S_STAT_CTX_RSRV1 (0x3<<6) +#define FCOE_S_STAT_CTX_RSRV1_SHIFT 6 +}; + +struct fcoe_seq_ctx { +#if defined(__BIG_ENDIAN) + u16 low_seq_cnt; + struct fcoe_s_stat_ctx s_stat; + u8 seq_id; +#elif defined(__LITTLE_ENDIAN) + u8 seq_id; + struct fcoe_s_stat_ctx s_stat; + u16 low_seq_cnt; +#endif +#if defined(__BIG_ENDIAN) + u16 err_seq_cnt; + u16 high_seq_cnt; +#elif defined(__LITTLE_ENDIAN) + u16 high_seq_cnt; + u16 err_seq_cnt; +#endif + u32 low_exp_ro; + u32 high_exp_ro; +}; + + +struct fcoe_single_sge_ctx { + struct regpair cur_buf_addr; +#if defined(__BIG_ENDIAN) + u16 reserved0; + u16 cur_buf_rem; +#elif defined(__LITTLE_ENDIAN) + u16 cur_buf_rem; + u16 reserved0; +#endif +}; + +union fcoe_sgl_ctx { + struct fcoe_single_sge_ctx single_sge; + struct fcoe_mul_sges_ctx mul_sges; +}; + + + +/* + * FCoE SQ element + */ +struct fcoe_sqe { + u16 wqe; +#define FCOE_SQE_TASK_ID (0x7FFF<<0) +#define FCOE_SQE_TASK_ID_SHIFT 0 +#define FCOE_SQE_TOGGLE_BIT (0x1<<15) +#define FCOE_SQE_TOGGLE_BIT_SHIFT 15 +}; + + + +struct fcoe_task_ctx_entry_tx_only { + union fcoe_sgl_ctx sgl_ctx; +}; + +struct fcoe_task_ctx_entry_txwr_rxrd { +#if defined(__BIG_ENDIAN) + u16 verify_tx_seq; + u8 init_flags; +#define FCOE_TASK_CTX_ENTRY_TXWR_RXRD_TASK_TYPE (0x7<<0) +#define FCOE_TASK_CTX_ENTRY_TXWR_RXRD_TASK_TYPE_SHIFT 0 +#define FCOE_TASK_CTX_ENTRY_TXWR_RXRD_DEV_TYPE (0x1<<3) +#define FCOE_TASK_CTX_ENTRY_TXWR_RXRD_DEV_TYPE_SHIFT 3 +#define FCOE_TASK_CTX_ENTRY_TXWR_RXRD_CLASS_TYPE (0x1<<4) +#define FCOE_TASK_CTX_ENTRY_TXWR_RXRD_CLASS_TYPE_SHIFT 4 +#define FCOE_TASK_CTX_ENTRY_TXWR_RXRD_SINGLE_SGE (0x1<<5) +#define FCOE_TASK_CTX_ENTRY_TXWR_RXRD_SINGLE_SGE_SHIFT 5 +#define FCOE_TASK_CTX_ENTRY_TXWR_RXRD_RSRV5 (0x3<<6) +#define FCOE_TASK_CTX_ENTRY_TXWR_RXRD_RSRV5_SHIFT 6 + u8 tx_flags; +#define FCOE_TASK_CTX_ENTRY_TXWR_RXRD_TX_STATE (0xF<<0) +#define FCOE_TASK_CTX_ENTRY_TXWR_RXRD_TX_STATE_SHIFT 0 +#define FCOE_TASK_CTX_ENTRY_TXWR_RXRD_RSRV4 (0xF<<4) +#define FCOE_TASK_CTX_ENTRY_TXWR_RXRD_RSRV4_SHIFT 4 +#elif defined(__LITTLE_ENDIAN) + u8 tx_flags; +#define FCOE_TASK_CTX_ENTRY_TXWR_RXRD_TX_STATE (0xF<<0) +#define FCOE_TASK_CTX_ENTRY_TXWR_RXRD_TX_STATE_SHIFT 0 +#define FCOE_TASK_CTX_ENTRY_TXWR_RXRD_RSRV4 (0xF<<4) +#define FCOE_TASK_CTX_ENTRY_TXWR_RXRD_RSRV4_SHIFT 4 + u8 init_flags; +#define FCOE_TASK_CTX_ENTRY_TXWR_RXRD_TASK_TYPE (0x7<<0) +#define FCOE_TASK_CTX_ENTRY_TXWR_RXRD_TASK_TYPE_SHIFT 0 +#define FCOE_TASK_CTX_ENTRY_TXWR_RXRD_DEV_TYPE (0x1<<3) +#define FCOE_TASK_CTX_ENTRY_TXWR_RXRD_DEV_TYPE_SHIFT 3 +#define FCOE_TASK_CTX_ENTRY_TXWR_RXRD_CLASS_TYPE (0x1<<4) +#define FCOE_TASK_CTX_ENTRY_TXWR_RXRD_CLASS_TYPE_SHIFT 4 +#define FCOE_TASK_CTX_ENTRY_TXWR_RXRD_SINGLE_SGE (0x1<<5) +#define FCOE_TASK_CTX_ENTRY_TXWR_RXRD_SINGLE_SGE_SHIFT 5 +#define FCOE_TASK_CTX_ENTRY_TXWR_RXRD_RSRV5 (0x3<<6) +#define FCOE_TASK_CTX_ENTRY_TXWR_RXRD_RSRV5_SHIFT 6 + u16 verify_tx_seq; +#endif +}; + +/* + * Common section. Both TX and RX processing might write and read from it in + * different flows + */ +struct fcoe_task_ctx_entry_tx_rx_cmn { + u32 data_2_trns; + union fcoe_general_task_ctx general; +#if defined(__BIG_ENDIAN) + u16 tx_low_seq_cnt; + struct fcoe_s_stat_ctx tx_s_stat; + u8 tx_seq_id; +#elif defined(__LITTLE_ENDIAN) + u8 tx_seq_id; + struct fcoe_s_stat_ctx tx_s_stat; + u16 tx_low_seq_cnt; +#endif + u32 common_flags; +#define FCOE_TASK_CTX_ENTRY_TX_RX_CMN_CID (0xFFFFFF<<0) +#define FCOE_TASK_CTX_ENTRY_TX_RX_CMN_CID_SHIFT 0 +#define FCOE_TASK_CTX_ENTRY_TX_RX_CMN_VALID (0x1<<24) +#define FCOE_TASK_CTX_ENTRY_TX_RX_CMN_VALID_SHIFT 24 +#define FCOE_TASK_CTX_ENTRY_TX_RX_CMN_SEQ_INIT (0x1<<25) +#define FCOE_TASK_CTX_ENTRY_TX_RX_CMN_SEQ_INIT_SHIFT 25 +#define FCOE_TASK_CTX_ENTRY_TX_RX_CMN_PEND_XFER (0x1<<26) +#define FCOE_TASK_CTX_ENTRY_TX_RX_CMN_PEND_XFER_SHIFT 26 +#define FCOE_TASK_CTX_ENTRY_TX_RX_CMN_PEND_CONF (0x1<<27) +#define FCOE_TASK_CTX_ENTRY_TX_RX_CMN_PEND_CONF_SHIFT 27 +#define FCOE_TASK_CTX_ENTRY_TX_RX_CMN_EXP_FIRST_FRAME (0x1<<28) +#define FCOE_TASK_CTX_ENTRY_TX_RX_CMN_EXP_FIRST_FRAME_SHIFT 28 +#define FCOE_TASK_CTX_ENTRY_TX_RX_CMN_RSRV (0x7<<29) +#define FCOE_TASK_CTX_ENTRY_TX_RX_CMN_RSRV_SHIFT 29 +}; + +struct fcoe_task_ctx_entry_rxwr_txrd { +#if defined(__BIG_ENDIAN) + u16 rx_id; + u16 rx_flags; +#define FCOE_TASK_CTX_ENTRY_RXWR_TXRD_RX_STATE (0xF<<0) +#define FCOE_TASK_CTX_ENTRY_RXWR_TXRD_RX_STATE_SHIFT 0 +#define FCOE_TASK_CTX_ENTRY_RXWR_TXRD_NUM_RQ_WQE (0x7<<4) +#define FCOE_TASK_CTX_ENTRY_RXWR_TXRD_NUM_RQ_WQE_SHIFT 4 +#define FCOE_TASK_CTX_ENTRY_RXWR_TXRD_CONF_REQ (0x1<<7) +#define FCOE_TASK_CTX_ENTRY_RXWR_TXRD_CONF_REQ_SHIFT 7 +#define FCOE_TASK_CTX_ENTRY_RXWR_TXRD_MISS_FRAME (0x1<<8) +#define FCOE_TASK_CTX_ENTRY_RXWR_TXRD_MISS_FRAME_SHIFT 8 +#define FCOE_TASK_CTX_ENTRY_RXWR_TXRD_RESERVED0 (0x7F<<9) +#define FCOE_TASK_CTX_ENTRY_RXWR_TXRD_RESERVED0_SHIFT 9 +#elif defined(__LITTLE_ENDIAN) + u16 rx_flags; +#define FCOE_TASK_CTX_ENTRY_RXWR_TXRD_RX_STATE (0xF<<0) +#define FCOE_TASK_CTX_ENTRY_RXWR_TXRD_RX_STATE_SHIFT 0 +#define FCOE_TASK_CTX_ENTRY_RXWR_TXRD_NUM_RQ_WQE (0x7<<4) +#define FCOE_TASK_CTX_ENTRY_RXWR_TXRD_NUM_RQ_WQE_SHIFT 4 +#define FCOE_TASK_CTX_ENTRY_RXWR_TXRD_CONF_REQ (0x1<<7) +#define FCOE_TASK_CTX_ENTRY_RXWR_TXRD_CONF_REQ_SHIFT 7 +#define FCOE_TASK_CTX_ENTRY_RXWR_TXRD_MISS_FRAME (0x1<<8) +#define FCOE_TASK_CTX_ENTRY_RXWR_TXRD_MISS_FRAME_SHIFT 8 +#define FCOE_TASK_CTX_ENTRY_RXWR_TXRD_RESERVED0 (0x7F<<9) +#define FCOE_TASK_CTX_ENTRY_RXWR_TXRD_RESERVED0_SHIFT 9 + u16 rx_id; +#endif +}; + +struct fcoe_task_ctx_entry_rx_only { + struct fcoe_seq_ctx seq_ctx; + struct fcoe_seq_ctx ooo_seq_ctx; + u32 rsrv3; + union fcoe_sgl_ctx sgl_ctx; +}; + +struct fcoe_task_ctx_entry { + struct fcoe_task_ctx_entry_tx_only tx_wr_only; + struct fcoe_task_ctx_entry_txwr_rxrd tx_wr_rx_rd; + struct fcoe_task_ctx_entry_tx_rx_cmn cmn; + struct fcoe_task_ctx_entry_rxwr_txrd rx_wr_tx_rd; + struct fcoe_task_ctx_entry_rx_only rx_wr_only; + u32 reserved[4]; +}; + + +/* + * FCoE XFRQ element + */ +struct fcoe_xfrqe { + u16 wqe; +#define FCOE_XFRQE_TASK_ID (0x7FFF<<0) +#define FCOE_XFRQE_TASK_ID_SHIFT 0 +#define FCOE_XFRQE_TOGGLE_BIT (0x1<<15) +#define FCOE_XFRQE_TOGGLE_BIT_SHIFT 15 +}; + + +/* + * FCoE CONFQ element + */ +struct fcoe_confqe { +#if defined(__BIG_ENDIAN) + u16 rx_id; + u16 ox_id; +#elif defined(__LITTLE_ENDIAN) + u16 ox_id; + u16 rx_id; +#endif + u32 param; +}; + + +/* + * FCoE conection data base + */ +struct fcoe_conn_db { +#if defined(__BIG_ENDIAN) + u16 rsrv0; + u16 rq_prod; +#elif defined(__LITTLE_ENDIAN) + u16 rq_prod; + u16 rsrv0; +#endif + u32 rsrv1; + struct regpair cq_arm; +}; + + +/* + * FCoE CQ element + */ +struct fcoe_cqe { + u16 wqe; +#define FCOE_CQE_CQE_INFO (0x3FFF<<0) +#define FCOE_CQE_CQE_INFO_SHIFT 0 +#define FCOE_CQE_CQE_TYPE (0x1<<14) +#define FCOE_CQE_CQE_TYPE_SHIFT 14 +#define FCOE_CQE_TOGGLE_BIT (0x1<<15) +#define FCOE_CQE_TOGGLE_BIT_SHIFT 15 +}; + + +/* + * FCoE error/warning resporting entry + */ +struct fcoe_err_report_entry { + u32 err_warn_bitmap_lo; + u32 err_warn_bitmap_hi; + u32 tx_buf_off; + u32 rx_buf_off; + struct fcoe_fc_hdr fc_hdr; +}; + + +/* + * FCoE hash table entry (32 bytes) + */ +struct fcoe_hash_table_entry { +#if defined(__BIG_ENDIAN) + u8 d_id_0; + u8 s_id_2; + u8 s_id_1; + u8 s_id_0; +#elif defined(__LITTLE_ENDIAN) + u8 s_id_0; + u8 s_id_1; + u8 s_id_2; + u8 d_id_0; +#endif +#if defined(__BIG_ENDIAN) + u16 dst_mac_addr_hi; + u8 d_id_2; + u8 d_id_1; +#elif defined(__LITTLE_ENDIAN) + u8 d_id_1; + u8 d_id_2; + u16 dst_mac_addr_hi; +#endif + u32 dst_mac_addr_lo; +#if defined(__BIG_ENDIAN) + u16 vlan_id; + u16 src_mac_addr_hi; +#elif defined(__LITTLE_ENDIAN) + u16 src_mac_addr_hi; + u16 vlan_id; +#endif + u32 src_mac_addr_lo; +#if defined(__BIG_ENDIAN) + u16 reserved1; + u8 reserved0; + u8 vlan_flag; +#elif defined(__LITTLE_ENDIAN) + u8 vlan_flag; + u8 reserved0; + u16 reserved1; +#endif + u32 reserved2; + u32 field_id; +#define FCOE_HASH_TABLE_ENTRY_CID (0xFFFFFF<<0) +#define FCOE_HASH_TABLE_ENTRY_CID_SHIFT 0 +#define FCOE_HASH_TABLE_ENTRY_RESERVED3 (0x7F<<24) +#define FCOE_HASH_TABLE_ENTRY_RESERVED3_SHIFT 24 +#define FCOE_HASH_TABLE_ENTRY_VALID (0x1<<31) +#define FCOE_HASH_TABLE_ENTRY_VALID_SHIFT 31 +}; + +/* + * FCoE pending work request CQE + */ +struct fcoe_pend_wq_cqe { + u16 wqe; +#define FCOE_PEND_WQ_CQE_TASK_ID (0x3FFF<<0) +#define FCOE_PEND_WQ_CQE_TASK_ID_SHIFT 0 +#define FCOE_PEND_WQ_CQE_CQE_TYPE (0x1<<14) +#define FCOE_PEND_WQ_CQE_CQE_TYPE_SHIFT 14 +#define FCOE_PEND_WQ_CQE_TOGGLE_BIT (0x1<<15) +#define FCOE_PEND_WQ_CQE_TOGGLE_BIT_SHIFT 15 +}; + + +/* + * FCoE RX statistics parameters section#0 + */ +struct fcoe_rx_stat_params_section0 { + u32 fcoe_ver_cnt; + u32 fcoe_rx_pkt_cnt; + u32 fcoe_rx_byte_cnt; + u32 fcoe_rx_drop_pkt_cnt; +}; + + +/* + * FCoE RX statistics parameters section#1 + */ +struct fcoe_rx_stat_params_section1 { + u32 fc_crc_cnt; + u32 eofa_del_cnt; + u32 miss_frame_cnt; + u32 seq_timeout_cnt; + u32 drop_seq_cnt; + u32 fcoe_rx_drop_pkt_cnt; + u32 fcp_rx_pkt_cnt; + u32 reserved0; +}; + + +/* + * FCoE TX statistics parameters + */ +struct fcoe_tx_stat_params { + u32 fcoe_tx_pkt_cnt; + u32 fcoe_tx_byte_cnt; + u32 fcp_tx_pkt_cnt; + u32 reserved0; +}; + +/* + * FCoE statistics parameters + */ +struct fcoe_statistics_params { + struct fcoe_tx_stat_params tx_stat; + struct fcoe_rx_stat_params_section0 rx_stat0; + struct fcoe_rx_stat_params_section1 rx_stat1; +}; + + +/* + * FCoE t2 hash table entry (64 bytes) + */ +struct fcoe_t2_hash_table_entry { + struct fcoe_hash_table_entry data; + struct regpair next; + struct regpair reserved0[3]; +}; + +/* + * FCoE unsolicited CQE + */ +struct fcoe_unsolicited_cqe { + u16 wqe; +#define FCOE_UNSOLICITED_CQE_SUBTYPE (0x3<<0) +#define FCOE_UNSOLICITED_CQE_SUBTYPE_SHIFT 0 +#define FCOE_UNSOLICITED_CQE_PKT_LEN (0xFFF<<2) +#define FCOE_UNSOLICITED_CQE_PKT_LEN_SHIFT 2 +#define FCOE_UNSOLICITED_CQE_CQE_TYPE (0x1<<14) +#define FCOE_UNSOLICITED_CQE_CQE_TYPE_SHIFT 14 +#define FCOE_UNSOLICITED_CQE_TOGGLE_BIT (0x1<<15) +#define FCOE_UNSOLICITED_CQE_TOGGLE_BIT_SHIFT 15 +}; + + + +#endif /* __57XX_FCOE_HSI_LINUX_LE__ */ diff --git a/drivers/scsi/bnx2fc/Kconfig b/drivers/scsi/bnx2fc/Kconfig new file mode 100644 index 000000000000..6a38080e35ed --- /dev/null +++ b/drivers/scsi/bnx2fc/Kconfig @@ -0,0 +1,11 @@ +config SCSI_BNX2X_FCOE + tristate "Broadcom NetXtreme II FCoE support" + depends on PCI + select NETDEVICES + select NETDEV_1000 + select LIBFC + select LIBFCOE + select CNIC + ---help--- + This driver supports FCoE offload for the Broadcom NetXtreme II + devices. diff --git a/drivers/scsi/bnx2fc/Makefile b/drivers/scsi/bnx2fc/Makefile new file mode 100644 index 000000000000..a92695a25176 --- /dev/null +++ b/drivers/scsi/bnx2fc/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_SCSI_BNX2X_FCOE) += bnx2fc.o + +bnx2fc-y := bnx2fc_els.o bnx2fc_fcoe.o bnx2fc_hwi.o bnx2fc_io.o bnx2fc_tgt.o diff --git a/drivers/scsi/bnx2fc/bnx2fc.h b/drivers/scsi/bnx2fc/bnx2fc.h new file mode 100644 index 000000000000..b6d350ac4288 --- /dev/null +++ b/drivers/scsi/bnx2fc/bnx2fc.h @@ -0,0 +1,517 @@ +#ifndef _BNX2FC_H_ +#define _BNX2FC_H_ +/* bnx2fc.h: Broadcom NetXtreme II Linux FCoE offload driver. + * + * Copyright (c) 2008 - 2010 Broadcom Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation. + * + * Written by: Bhanu Prakash Gollapudi (bprakash@broadcom.com) + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/skbuff.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/if_ether.h> +#include <linux/if_vlan.h> +#include <linux/kthread.h> +#include <linux/crc32.h> +#include <linux/cpu.h> +#include <linux/types.h> +#include <linux/list.h> +#include <linux/delay.h> +#include <linux/timer.h> +#include <linux/errno.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <linux/dma-mapping.h> +#include <linux/workqueue.h> +#include <linux/mutex.h> +#include <linux/spinlock.h> +#include <linux/bitops.h> +#include <linux/log2.h> +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <linux/io.h> + +#include <scsi/scsi.h> +#include <scsi/scsi_host.h> +#include <scsi/scsi_device.h> +#include <scsi/scsi_cmnd.h> +#include <scsi/scsi_eh.h> +#include <scsi/scsi_tcq.h> +#include <scsi/libfc.h> +#include <scsi/libfcoe.h> +#include <scsi/fc_encode.h> +#include <scsi/scsi_transport.h> +#include <scsi/scsi_transport_fc.h> +#include <scsi/fc/fc_fip.h> +#include <scsi/fc/fc_fc2.h> +#include <scsi/fc_frame.h> +#include <scsi/fc/fc_fcoe.h> +#include <scsi/fc/fc_fcp.h> + +#include "57xx_hsi_bnx2fc.h" +#include "bnx2fc_debug.h" +#include "../../net/cnic_if.h" +#include "bnx2fc_constants.h" + +#define BNX2FC_NAME "bnx2fc" +#define BNX2FC_VERSION "1.0.1" + +#define PFX "bnx2fc: " + +#define BNX2X_DOORBELL_PCI_BAR 2 + +#define BNX2FC_MAX_BD_LEN 0xffff +#define BNX2FC_BD_SPLIT_SZ 0x8000 +#define BNX2FC_MAX_BDS_PER_CMD 256 + +#define BNX2FC_SQ_WQES_MAX 256 + +#define BNX2FC_SCSI_MAX_SQES ((3 * BNX2FC_SQ_WQES_MAX) / 8) +#define BNX2FC_TM_MAX_SQES ((BNX2FC_SQ_WQES_MAX) / 2) +#define BNX2FC_ELS_MAX_SQES (BNX2FC_TM_MAX_SQES - 1) + +#define BNX2FC_RQ_WQES_MAX 16 +#define BNX2FC_CQ_WQES_MAX (BNX2FC_SQ_WQES_MAX + BNX2FC_RQ_WQES_MAX) + +#define BNX2FC_NUM_MAX_SESS 128 +#define BNX2FC_NUM_MAX_SESS_LOG (ilog2(BNX2FC_NUM_MAX_SESS)) + +#define BNX2FC_MAX_OUTSTANDING_CMNDS 2048 +#define BNX2FC_CAN_QUEUE BNX2FC_MAX_OUTSTANDING_CMNDS +#define BNX2FC_ELSTM_XIDS BNX2FC_CAN_QUEUE +#define BNX2FC_MIN_PAYLOAD 256 +#define BNX2FC_MAX_PAYLOAD 2048 +#define BNX2FC_MFS \ + (BNX2FC_MAX_PAYLOAD + sizeof(struct fc_frame_header)) +#define BNX2FC_MINI_JUMBO_MTU 2500 + + +#define BNX2FC_RQ_BUF_SZ 256 +#define BNX2FC_RQ_BUF_LOG_SZ (ilog2(BNX2FC_RQ_BUF_SZ)) + +#define BNX2FC_SQ_WQE_SIZE (sizeof(struct fcoe_sqe)) +#define BNX2FC_CQ_WQE_SIZE (sizeof(struct fcoe_cqe)) +#define BNX2FC_RQ_WQE_SIZE (BNX2FC_RQ_BUF_SZ) +#define BNX2FC_XFERQ_WQE_SIZE (sizeof(struct fcoe_xfrqe)) +#define BNX2FC_CONFQ_WQE_SIZE (sizeof(struct fcoe_confqe)) +#define BNX2FC_5771X_DB_PAGE_SIZE 128 + +#define BNX2FC_MAX_TASKS \ + (BNX2FC_MAX_OUTSTANDING_CMNDS + BNX2FC_ELSTM_XIDS) +#define BNX2FC_TASK_SIZE 128 +#define BNX2FC_TASKS_PER_PAGE (PAGE_SIZE/BNX2FC_TASK_SIZE) +#define BNX2FC_TASK_CTX_ARR_SZ (BNX2FC_MAX_TASKS/BNX2FC_TASKS_PER_PAGE) + +#define BNX2FC_MAX_ROWS_IN_HASH_TBL 8 +#define BNX2FC_HASH_TBL_CHUNK_SIZE (16 * 1024) + +#define BNX2FC_MAX_SEQS 255 + +#define BNX2FC_READ (1 << 1) +#define BNX2FC_WRITE (1 << 0) + +#define BNX2FC_MIN_XID 0 +#define BNX2FC_MAX_XID \ + (BNX2FC_MAX_OUTSTANDING_CMNDS + BNX2FC_ELSTM_XIDS - 1) +#define FCOE_MIN_XID (BNX2FC_MAX_XID + 1) +#define FCOE_MAX_XID (FCOE_MIN_XID + 4095) +#define BNX2FC_MAX_LUN 0xFFFF +#define BNX2FC_MAX_FCP_TGT 256 +#define BNX2FC_MAX_CMD_LEN 16 + +#define BNX2FC_TM_TIMEOUT 60 /* secs */ +#define BNX2FC_IO_TIMEOUT 20000UL /* msecs */ + +#define BNX2FC_WAIT_CNT 120 +#define BNX2FC_FW_TIMEOUT (3 * HZ) +#define PORT_MAX 2 + +#define CMD_SCSI_STATUS(Cmnd) ((Cmnd)->SCp.Status) + +/* FC FCP Status */ +#define FC_GOOD 0 + +#define BNX2FC_RNID_HBA 0x7 + +/* bnx2fc driver uses only one instance of fcoe_percpu_s */ +extern struct fcoe_percpu_s bnx2fc_global; + +extern struct workqueue_struct *bnx2fc_wq; + +struct bnx2fc_percpu_s { + struct task_struct *iothread; + struct list_head work_list; + spinlock_t fp_work_lock; +}; + + +struct bnx2fc_hba { + struct list_head link; + struct cnic_dev *cnic; + struct pci_dev *pcidev; + struct net_device *netdev; + struct net_device *phys_dev; + unsigned long reg_with_cnic; + #define BNX2FC_CNIC_REGISTERED 1 + struct packet_type fcoe_packet_type; + struct packet_type fip_packet_type; + struct bnx2fc_cmd_mgr *cmd_mgr; + struct workqueue_struct *timer_work_queue; + struct kref kref; + spinlock_t hba_lock; + struct mutex hba_mutex; + unsigned long adapter_state; + #define ADAPTER_STATE_UP 0 + #define ADAPTER_STATE_GOING_DOWN 1 + #define ADAPTER_STATE_LINK_DOWN 2 + #define ADAPTER_STATE_READY 3 + u32 flags; + unsigned long init_done; + #define BNX2FC_FW_INIT_DONE 0 + #define BNX2FC_CTLR_INIT_DONE 1 + #define BNX2FC_CREATE_DONE 2 + struct fcoe_ctlr ctlr; + u8 vlan_enabled; + int vlan_id; + u32 next_conn_id; + struct fcoe_task_ctx_entry **task_ctx; + dma_addr_t *task_ctx_dma; + struct regpair *task_ctx_bd_tbl; + dma_addr_t task_ctx_bd_dma; + + int hash_tbl_segment_count; + void **hash_tbl_segments; + void *hash_tbl_pbl; + dma_addr_t hash_tbl_pbl_dma; + struct fcoe_t2_hash_table_entry *t2_hash_tbl; + dma_addr_t t2_hash_tbl_dma; + char *t2_hash_tbl_ptr; + dma_addr_t t2_hash_tbl_ptr_dma; + + char *dummy_buffer; + dma_addr_t dummy_buf_dma; + + struct fcoe_statistics_params *stats_buffer; + dma_addr_t stats_buf_dma; + + /* + * PCI related info. + */ + u16 pci_did; + u16 pci_vid; + u16 pci_sdid; + u16 pci_svid; + u16 pci_func; + u16 pci_devno; + + struct task_struct *l2_thread; + + /* linkdown handling */ + wait_queue_head_t shutdown_wait; + int wait_for_link_down; + + /*destroy handling */ + struct timer_list destroy_timer; + wait_queue_head_t destroy_wait; + + /* Active list of offloaded sessions */ + struct bnx2fc_rport *tgt_ofld_list[BNX2FC_NUM_MAX_SESS]; + int num_ofld_sess; + + /* statistics */ + struct completion stat_req_done; +}; + +#define bnx2fc_from_ctlr(fip) container_of(fip, struct bnx2fc_hba, ctlr) + +struct bnx2fc_cmd_mgr { + struct bnx2fc_hba *hba; + u16 next_idx; + struct list_head *free_list; + spinlock_t *free_list_lock; + struct io_bdt **io_bdt_pool; + struct bnx2fc_cmd **cmds; +}; + +struct bnx2fc_rport { + struct fcoe_port *port; + struct fc_rport *rport; + struct fc_rport_priv *rdata; + void __iomem *ctx_base; +#define DPM_TRIGER_TYPE 0x40 + u32 fcoe_conn_id; + u32 context_id; + u32 sid; + + unsigned long flags; +#define BNX2FC_FLAG_SESSION_READY 0x1 +#define BNX2FC_FLAG_OFFLOADED 0x2 +#define BNX2FC_FLAG_DISABLED 0x3 +#define BNX2FC_FLAG_DESTROYED 0x4 +#define BNX2FC_FLAG_OFLD_REQ_CMPL 0x5 +#define BNX2FC_FLAG_DESTROY_CMPL 0x6 +#define BNX2FC_FLAG_CTX_ALLOC_FAILURE 0x7 +#define BNX2FC_FLAG_UPLD_REQ_COMPL 0x8 +#define BNX2FC_FLAG_EXPL_LOGO 0x9 + + u32 max_sqes; + u32 max_rqes; + u32 max_cqes; + + struct fcoe_sqe *sq; + dma_addr_t sq_dma; + u16 sq_prod_idx; + u8 sq_curr_toggle_bit; + u32 sq_mem_size; + + struct fcoe_cqe *cq; + dma_addr_t cq_dma; + u32 cq_cons_idx; + u8 cq_curr_toggle_bit; + u32 cq_mem_size; + + void *rq; + dma_addr_t rq_dma; + u32 rq_prod_idx; + u32 rq_cons_idx; + u32 rq_mem_size; + + void *rq_pbl; + dma_addr_t rq_pbl_dma; + u32 rq_pbl_size; + + struct fcoe_xfrqe *xferq; + dma_addr_t xferq_dma; + u32 xferq_mem_size; + + struct fcoe_confqe *confq; + dma_addr_t confq_dma; + u32 confq_mem_size; + + void *confq_pbl; + dma_addr_t confq_pbl_dma; + u32 confq_pbl_size; + + struct fcoe_conn_db *conn_db; + dma_addr_t conn_db_dma; + u32 conn_db_mem_size; + + struct fcoe_sqe *lcq; + dma_addr_t lcq_dma; + u32 lcq_mem_size; + + void *ofld_req[4]; + dma_addr_t ofld_req_dma[4]; + void *enbl_req; + dma_addr_t enbl_req_dma; + + spinlock_t tgt_lock; + spinlock_t cq_lock; + atomic_t num_active_ios; + u32 flush_in_prog; + unsigned long work_time_slice; + unsigned long timestamp; + struct list_head free_task_list; + struct bnx2fc_cmd *pending_queue[BNX2FC_SQ_WQES_MAX+1]; + atomic_t pi; + atomic_t ci; + struct list_head active_cmd_queue; + struct list_head els_queue; + struct list_head io_retire_queue; + struct list_head active_tm_queue; + + struct timer_list ofld_timer; + wait_queue_head_t ofld_wait; + + struct timer_list upld_timer; + wait_queue_head_t upld_wait; +}; + +struct bnx2fc_mp_req { + u8 tm_flags; + + u32 req_len; + void *req_buf; + dma_addr_t req_buf_dma; + struct fcoe_bd_ctx *mp_req_bd; + dma_addr_t mp_req_bd_dma; + struct fc_frame_header req_fc_hdr; + + u32 resp_len; + void *resp_buf; + dma_addr_t resp_buf_dma; + struct fcoe_bd_ctx *mp_resp_bd; + dma_addr_t mp_resp_bd_dma; + struct fc_frame_header resp_fc_hdr; +}; + +struct bnx2fc_els_cb_arg { + struct bnx2fc_cmd *aborted_io_req; + struct bnx2fc_cmd *io_req; + u16 l2_oxid; +}; + +/* bnx2fc command structure */ +struct bnx2fc_cmd { + struct list_head link; + u8 on_active_queue; + u8 on_tmf_queue; + u8 cmd_type; +#define BNX2FC_SCSI_CMD 1 +#define BNX2FC_TASK_MGMT_CMD 2 +#define BNX2FC_ABTS 3 +#define BNX2FC_ELS 4 +#define BNX2FC_CLEANUP 5 + u8 io_req_flags; + struct kref refcount; + struct fcoe_port *port; + struct bnx2fc_rport *tgt; + struct scsi_cmnd *sc_cmd; + struct bnx2fc_cmd_mgr *cmd_mgr; + struct bnx2fc_mp_req mp_req; + void (*cb_func)(struct bnx2fc_els_cb_arg *cb_arg); + struct bnx2fc_els_cb_arg *cb_arg; + struct delayed_work timeout_work; /* timer for ULP timeouts */ + struct completion tm_done; + int wait_for_comp; + u16 xid; + struct fcoe_task_ctx_entry *task; + struct io_bdt *bd_tbl; + struct fcp_rsp *rsp; + size_t data_xfer_len; + unsigned long req_flags; +#define BNX2FC_FLAG_ISSUE_RRQ 0x1 +#define BNX2FC_FLAG_ISSUE_ABTS 0x2 +#define BNX2FC_FLAG_ABTS_DONE 0x3 +#define BNX2FC_FLAG_TM_COMPL 0x4 +#define BNX2FC_FLAG_TM_TIMEOUT 0x5 +#define BNX2FC_FLAG_IO_CLEANUP 0x6 +#define BNX2FC_FLAG_RETIRE_OXID 0x7 +#define BNX2FC_FLAG_EH_ABORT 0x8 +#define BNX2FC_FLAG_IO_COMPL 0x9 +#define BNX2FC_FLAG_ELS_DONE 0xa +#define BNX2FC_FLAG_ELS_TIMEOUT 0xb + u32 fcp_resid; + u32 fcp_rsp_len; + u32 fcp_sns_len; + u8 cdb_status; /* SCSI IO status */ + u8 fcp_status; /* FCP IO status */ + u8 fcp_rsp_code; + u8 scsi_comp_flags; +}; + +struct io_bdt { + struct bnx2fc_cmd *io_req; + struct fcoe_bd_ctx *bd_tbl; + dma_addr_t bd_tbl_dma; + u16 bd_valid; +}; + +struct bnx2fc_work { + struct list_head list; + struct bnx2fc_rport *tgt; + u16 wqe; +}; +struct bnx2fc_unsol_els { + struct fc_lport *lport; + struct fc_frame *fp; + struct work_struct unsol_els_work; +}; + + + +struct bnx2fc_cmd *bnx2fc_elstm_alloc(struct bnx2fc_rport *tgt, int type); +void bnx2fc_cmd_release(struct kref *ref); +int bnx2fc_queuecommand(struct Scsi_Host *host, struct scsi_cmnd *sc_cmd); +int bnx2fc_send_fw_fcoe_init_msg(struct bnx2fc_hba *hba); +int bnx2fc_send_fw_fcoe_destroy_msg(struct bnx2fc_hba *hba); +int bnx2fc_send_session_ofld_req(struct fcoe_port *port, + struct bnx2fc_rport *tgt); +int bnx2fc_send_session_disable_req(struct fcoe_port *port, + struct bnx2fc_rport *tgt); +int bnx2fc_send_session_destroy_req(struct bnx2fc_hba *hba, + struct bnx2fc_rport *tgt); +int bnx2fc_map_doorbell(struct bnx2fc_rport *tgt); +void bnx2fc_indicate_kcqe(void *context, struct kcqe *kcq[], + u32 num_cqe); +int bnx2fc_setup_task_ctx(struct bnx2fc_hba *hba); +void bnx2fc_free_task_ctx(struct bnx2fc_hba *hba); +int bnx2fc_setup_fw_resc(struct bnx2fc_hba *hba); +void bnx2fc_free_fw_resc(struct bnx2fc_hba *hba); +struct bnx2fc_cmd_mgr *bnx2fc_cmd_mgr_alloc(struct bnx2fc_hba *hba, + u16 min_xid, u16 max_xid); +void bnx2fc_cmd_mgr_free(struct bnx2fc_cmd_mgr *cmgr); +void bnx2fc_get_link_state(struct bnx2fc_hba *hba); +char *bnx2fc_get_next_rqe(struct bnx2fc_rport *tgt, u8 num_items); +void bnx2fc_return_rqe(struct bnx2fc_rport *tgt, u8 num_items); +int bnx2fc_get_paged_crc_eof(struct sk_buff *skb, int tlen); +int bnx2fc_send_rrq(struct bnx2fc_cmd *aborted_io_req); +int bnx2fc_send_adisc(struct bnx2fc_rport *tgt, struct fc_frame *fp); +int bnx2fc_send_logo(struct bnx2fc_rport *tgt, struct fc_frame *fp); +int bnx2fc_send_rls(struct bnx2fc_rport *tgt, struct fc_frame *fp); +int bnx2fc_initiate_cleanup(struct bnx2fc_cmd *io_req); +int bnx2fc_initiate_abts(struct bnx2fc_cmd *io_req); +void bnx2fc_cmd_timer_set(struct bnx2fc_cmd *io_req, + unsigned int timer_msec); +int bnx2fc_init_mp_req(struct bnx2fc_cmd *io_req); +void bnx2fc_init_cleanup_task(struct bnx2fc_cmd *io_req, + struct fcoe_task_ctx_entry *task, + u16 orig_xid); +void bnx2fc_init_mp_task(struct bnx2fc_cmd *io_req, + struct fcoe_task_ctx_entry *task); +void bnx2fc_init_task(struct bnx2fc_cmd *io_req, + struct fcoe_task_ctx_entry *task); +void bnx2fc_add_2_sq(struct bnx2fc_rport *tgt, u16 xid); +void bnx2fc_ring_doorbell(struct bnx2fc_rport *tgt); +int bnx2fc_eh_abort(struct scsi_cmnd *sc_cmd); +int bnx2fc_eh_host_reset(struct scsi_cmnd *sc_cmd); +int bnx2fc_eh_target_reset(struct scsi_cmnd *sc_cmd); +int bnx2fc_eh_device_reset(struct scsi_cmnd *sc_cmd); +void bnx2fc_rport_event_handler(struct fc_lport *lport, + struct fc_rport_priv *rport, + enum fc_rport_event event); +void bnx2fc_process_scsi_cmd_compl(struct bnx2fc_cmd *io_req, + struct fcoe_task_ctx_entry *task, + u8 num_rq); +void bnx2fc_process_cleanup_compl(struct bnx2fc_cmd *io_req, + struct fcoe_task_ctx_entry *task, + u8 num_rq); +void bnx2fc_process_abts_compl(struct bnx2fc_cmd *io_req, + struct fcoe_task_ctx_entry *task, + u8 num_rq); +void bnx2fc_process_tm_compl(struct bnx2fc_cmd *io_req, + struct fcoe_task_ctx_entry *task, + u8 num_rq); +void bnx2fc_process_els_compl(struct bnx2fc_cmd *els_req, + struct fcoe_task_ctx_entry *task, + u8 num_rq); +void bnx2fc_build_fcp_cmnd(struct bnx2fc_cmd *io_req, + struct fcp_cmnd *fcp_cmnd); + + + +void bnx2fc_flush_active_ios(struct bnx2fc_rport *tgt); +struct fc_seq *bnx2fc_elsct_send(struct fc_lport *lport, u32 did, + struct fc_frame *fp, unsigned int op, + void (*resp)(struct fc_seq *, + struct fc_frame *, + void *), + void *arg, u32 timeout); +int bnx2fc_process_new_cqes(struct bnx2fc_rport *tgt); +void bnx2fc_process_cq_compl(struct bnx2fc_rport *tgt, u16 wqe); +struct bnx2fc_rport *bnx2fc_tgt_lookup(struct fcoe_port *port, + u32 port_id); +void bnx2fc_process_l2_frame_compl(struct bnx2fc_rport *tgt, + unsigned char *buf, + u32 frame_len, u16 l2_oxid); +int bnx2fc_send_stat_req(struct bnx2fc_hba *hba); + +#endif diff --git a/drivers/scsi/bnx2fc/bnx2fc_constants.h b/drivers/scsi/bnx2fc/bnx2fc_constants.h new file mode 100644 index 000000000000..fe7769173c43 --- /dev/null +++ b/drivers/scsi/bnx2fc/bnx2fc_constants.h @@ -0,0 +1,206 @@ +#ifndef __BNX2FC_CONSTANTS_H_ +#define __BNX2FC_CONSTANTS_H_ + +/** + * This file defines HSI constants for the FCoE flows + */ + +/* KWQ/KCQ FCoE layer code */ +#define FCOE_KWQE_LAYER_CODE (7) + +/* KWQ (kernel work queue) request op codes */ +#define FCOE_KWQE_OPCODE_INIT1 (0) +#define FCOE_KWQE_OPCODE_INIT2 (1) +#define FCOE_KWQE_OPCODE_INIT3 (2) +#define FCOE_KWQE_OPCODE_OFFLOAD_CONN1 (3) +#define FCOE_KWQE_OPCODE_OFFLOAD_CONN2 (4) +#define FCOE_KWQE_OPCODE_OFFLOAD_CONN3 (5) +#define FCOE_KWQE_OPCODE_OFFLOAD_CONN4 (6) +#define FCOE_KWQE_OPCODE_ENABLE_CONN (7) +#define FCOE_KWQE_OPCODE_DISABLE_CONN (8) +#define FCOE_KWQE_OPCODE_DESTROY_CONN (9) +#define FCOE_KWQE_OPCODE_DESTROY (10) +#define FCOE_KWQE_OPCODE_STAT (11) + +/* KCQ (kernel completion queue) response op codes */ +#define FCOE_KCQE_OPCODE_INIT_FUNC (0x10) +#define FCOE_KCQE_OPCODE_DESTROY_FUNC (0x11) +#define FCOE_KCQE_OPCODE_STAT_FUNC (0x12) +#define FCOE_KCQE_OPCODE_OFFLOAD_CONN (0x15) +#define FCOE_KCQE_OPCODE_ENABLE_CONN (0x16) +#define FCOE_KCQE_OPCODE_DISABLE_CONN (0x17) +#define FCOE_KCQE_OPCODE_DESTROY_CONN (0x18) +#define FCOE_KCQE_OPCODE_CQ_EVENT_NOTIFICATION (0x20) +#define FCOE_KCQE_OPCODE_FCOE_ERROR (0x21) + +/* KCQ (kernel completion queue) completion status */ +#define FCOE_KCQE_COMPLETION_STATUS_SUCCESS (0x0) +#define FCOE_KCQE_COMPLETION_STATUS_ERROR (0x1) +#define FCOE_KCQE_COMPLETION_STATUS_INVALID_OPCODE (0x2) +#define FCOE_KCQE_COMPLETION_STATUS_CTX_ALLOC_FAILURE (0x3) +#define FCOE_KCQE_COMPLETION_STATUS_CTX_FREE_FAILURE (0x4) +#define FCOE_KCQE_COMPLETION_STATUS_NIC_ERROR (0x5) + +/* Unsolicited CQE type */ +#define FCOE_UNSOLICITED_FRAME_CQE_TYPE 0 +#define FCOE_ERROR_DETECTION_CQE_TYPE 1 +#define FCOE_WARNING_DETECTION_CQE_TYPE 2 + +/* Task context constants */ +/* After driver has initialize the task in case timer services required */ +#define FCOE_TASK_TX_STATE_INIT 0 +/* In case timer services are required then shall be updated by Xstorm after + * start processing the task. In case no timer facilities are required then the + * driver would initialize the state to this value */ +#define FCOE_TASK_TX_STATE_NORMAL 1 +/* Task is under abort procedure. Updated in order to stop processing of + * pending WQEs on this task */ +#define FCOE_TASK_TX_STATE_ABORT 2 +/* For E_D_T_TOV timer expiration in Xstorm (Class 2 only) */ +#define FCOE_TASK_TX_STATE_ERROR 3 +/* For REC_TOV timer expiration indication received from Xstorm */ +#define FCOE_TASK_TX_STATE_WARNING 4 +/* For completed unsolicited task */ +#define FCOE_TASK_TX_STATE_UNSOLICITED_COMPLETED 5 +/* For exchange cleanup request task */ +#define FCOE_TASK_TX_STATE_EXCHANGE_CLEANUP 6 +/* For sequence cleanup request task */ +#define FCOE_TASK_TX_STATE_SEQUENCE_CLEANUP 7 +/* Mark task as aborted and indicate that ABTS was not transmitted */ +#define FCOE_TASK_TX_STATE_BEFORE_ABTS_TX 8 +/* Mark task as aborted and indicate that ABTS was transmitted */ +#define FCOE_TASK_TX_STATE_AFTER_ABTS_TX 9 +/* For completion the ABTS task. */ +#define FCOE_TASK_TX_STATE_ABTS_TX_COMPLETED 10 +/* Mark task as aborted and indicate that Exchange cleanup was not transmitted + */ +#define FCOE_TASK_TX_STATE_BEFORE_EXCHANGE_CLEANUP_TX 11 +/* Mark task as aborted and indicate that Exchange cleanup was transmitted */ +#define FCOE_TASK_TX_STATE_AFTER_EXCHANGE_CLEANUP_TX 12 + +#define FCOE_TASK_RX_STATE_NORMAL 0 +#define FCOE_TASK_RX_STATE_COMPLETED 1 +/* Obsolete: Intermediate completion (middle path with local completion) */ +#define FCOE_TASK_RX_STATE_INTER_COMP 2 +/* For REC_TOV timer expiration indication received from Xstorm */ +#define FCOE_TASK_RX_STATE_WARNING 3 +/* For E_D_T_TOV timer expiration in Ustorm */ +#define FCOE_TASK_RX_STATE_ERROR 4 +/* ABTS ACC arrived wait for local completion to finally complete the task. */ +#define FCOE_TASK_RX_STATE_ABTS_ACC_ARRIVED 5 +/* local completion arrived wait for ABTS ACC to finally complete the task. */ +#define FCOE_TASK_RX_STATE_ABTS_LOCAL_COMP_ARRIVED 6 +/* Special completion indication in case of task was aborted. */ +#define FCOE_TASK_RX_STATE_ABTS_COMPLETED 7 +/* Special completion indication in case of task was cleaned. */ +#define FCOE_TASK_RX_STATE_EXCHANGE_CLEANUP_COMPLETED 8 +/* Special completion indication (in task requested the exchange cleanup) in + * case cleaned task is in non-valid. */ +#define FCOE_TASK_RX_STATE_ABORT_CLEANUP_COMPLETED 9 +/* Special completion indication (in task requested the sequence cleanup) in + * case cleaned task was already returned to normal. */ +#define FCOE_TASK_RX_STATE_IGNORED_SEQUENCE_CLEANUP 10 +/* Exchange cleanup arrived wait until xfer will be handled to finally + * complete the task. */ +#define FCOE_TASK_RX_STATE_EXCHANGE_CLEANUP_ARRIVED 11 +/* Xfer handled, wait for exchange cleanup to finally complete the task. */ +#define FCOE_TASK_RX_STATE_EXCHANGE_CLEANUP_HANDLED_XFER 12 + +#define FCOE_TASK_TYPE_WRITE 0 +#define FCOE_TASK_TYPE_READ 1 +#define FCOE_TASK_TYPE_MIDPATH 2 +#define FCOE_TASK_TYPE_UNSOLICITED 3 +#define FCOE_TASK_TYPE_ABTS 4 +#define FCOE_TASK_TYPE_EXCHANGE_CLEANUP 5 +#define FCOE_TASK_TYPE_SEQUENCE_CLEANUP 6 + +#define FCOE_TASK_DEV_TYPE_DISK 0 +#define FCOE_TASK_DEV_TYPE_TAPE 1 + +#define FCOE_TASK_CLASS_TYPE_3 0 +#define FCOE_TASK_CLASS_TYPE_2 1 + +/* Everest FCoE connection type */ +#define B577XX_FCOE_CONNECTION_TYPE 4 + +/* Error codes for Error Reporting in fast path flows */ +/* XFER error codes */ +#define FCOE_ERROR_CODE_XFER_OOO_RO 0 +#define FCOE_ERROR_CODE_XFER_RO_NOT_ALIGNED 1 +#define FCOE_ERROR_CODE_XFER_NULL_BURST_LEN 2 +#define FCOE_ERROR_CODE_XFER_RO_GREATER_THAN_DATA2TRNS 3 +#define FCOE_ERROR_CODE_XFER_INVALID_PAYLOAD_SIZE 4 +#define FCOE_ERROR_CODE_XFER_TASK_TYPE_NOT_WRITE 5 +#define FCOE_ERROR_CODE_XFER_PEND_XFER_SET 6 +#define FCOE_ERROR_CODE_XFER_OPENED_SEQ 7 +#define FCOE_ERROR_CODE_XFER_FCTL 8 + +/* FCP RSP error codes */ +#define FCOE_ERROR_CODE_FCP_RSP_BIDI_FLAGS_SET 9 +#define FCOE_ERROR_CODE_FCP_RSP_UNDERFLOW 10 +#define FCOE_ERROR_CODE_FCP_RSP_OVERFLOW 11 +#define FCOE_ERROR_CODE_FCP_RSP_INVALID_LENGTH_FIELD 12 +#define FCOE_ERROR_CODE_FCP_RSP_INVALID_SNS_FIELD 13 +#define FCOE_ERROR_CODE_FCP_RSP_INVALID_PAYLOAD_SIZE 14 +#define FCOE_ERROR_CODE_FCP_RSP_PEND_XFER_SET 15 +#define FCOE_ERROR_CODE_FCP_RSP_OPENED_SEQ 16 +#define FCOE_ERROR_CODE_FCP_RSP_FCTL 17 +#define FCOE_ERROR_CODE_FCP_RSP_LAST_SEQ_RESET 18 +#define FCOE_ERROR_CODE_FCP_RSP_CONF_REQ_NOT_SUPPORTED_YET 19 + +/* FCP DATA error codes */ +#define FCOE_ERROR_CODE_DATA_OOO_RO 20 +#define FCOE_ERROR_CODE_DATA_EXCEEDS_DEFINED_MAX_FRAME_SIZE 21 +#define FCOE_ERROR_CODE_DATA_EXCEEDS_DATA2TRNS 22 +#define FCOE_ERROR_CODE_DATA_SOFI3_SEQ_ACTIVE_SET 23 +#define FCOE_ERROR_CODE_DATA_SOFN_SEQ_ACTIVE_RESET 24 +#define FCOE_ERROR_CODE_DATA_EOFN_END_SEQ_SET 25 +#define FCOE_ERROR_CODE_DATA_EOFT_END_SEQ_RESET 26 +#define FCOE_ERROR_CODE_DATA_TASK_TYPE_NOT_READ 27 +#define FCOE_ERROR_CODE_DATA_FCTL 28 + +/* Middle path error codes */ +#define FCOE_ERROR_CODE_MIDPATH_TYPE_NOT_ELS 29 +#define FCOE_ERROR_CODE_MIDPATH_SOFI3_SEQ_ACTIVE_SET 30 +#define FCOE_ERROR_CODE_MIDPATH_SOFN_SEQ_ACTIVE_RESET 31 +#define FCOE_ERROR_CODE_MIDPATH_EOFN_END_SEQ_SET 32 +#define FCOE_ERROR_CODE_MIDPATH_EOFT_END_SEQ_RESET 33 +#define FCOE_ERROR_CODE_MIDPATH_ELS_REPLY_FCTL 34 +#define FCOE_ERROR_CODE_MIDPATH_INVALID_REPLY 35 +#define FCOE_ERROR_CODE_MIDPATH_ELS_REPLY_RCTL 36 + +/* ABTS error codes */ +#define FCOE_ERROR_CODE_ABTS_REPLY_F_CTL 37 +#define FCOE_ERROR_CODE_ABTS_REPLY_DDF_RCTL_FIELD 38 +#define FCOE_ERROR_CODE_ABTS_REPLY_INVALID_BLS_RCTL 39 +#define FCOE_ERROR_CODE_ABTS_REPLY_INVALID_RCTL 40 +#define FCOE_ERROR_CODE_ABTS_REPLY_RCTL_GENERAL_MISMATCH 41 + +/* Common error codes */ +#define FCOE_ERROR_CODE_COMMON_MIDDLE_FRAME_WITH_PAD 42 +#define FCOE_ERROR_CODE_COMMON_SEQ_INIT_IN_TCE 43 +#define FCOE_ERROR_CODE_COMMON_FC_HDR_RX_ID_MISMATCH 44 +#define FCOE_ERROR_CODE_COMMON_INCORRECT_SEQ_CNT 45 +#define FCOE_ERROR_CODE_COMMON_DATA_FC_HDR_FCP_TYPE_MISMATCH 46 +#define FCOE_ERROR_CODE_COMMON_DATA_NO_MORE_SGES 47 +#define FCOE_ERROR_CODE_COMMON_OPTIONAL_FC_HDR 48 +#define FCOE_ERROR_CODE_COMMON_READ_TCE_OX_ID_TOO_BIG 49 +#define FCOE_ERROR_CODE_COMMON_DATA_WAS_NOT_TRANSMITTED 50 + +/* Unsolicited Rx error codes */ +#define FCOE_ERROR_CODE_UNSOLICITED_TYPE_NOT_ELS 51 +#define FCOE_ERROR_CODE_UNSOLICITED_TYPE_NOT_BLS 52 +#define FCOE_ERROR_CODE_UNSOLICITED_FCTL_ELS 53 +#define FCOE_ERROR_CODE_UNSOLICITED_FCTL_BLS 54 +#define FCOE_ERROR_CODE_UNSOLICITED_R_CTL 55 + +#define FCOE_ERROR_CODE_RW_TASK_DDF_RCTL_INFO_FIELD 56 +#define FCOE_ERROR_CODE_RW_TASK_INVALID_RCTL 57 +#define FCOE_ERROR_CODE_RW_TASK_RCTL_GENERAL_MISMATCH 58 + +/* Timer error codes */ +#define FCOE_ERROR_CODE_E_D_TOV_TIMER_EXPIRATION 60 +#define FCOE_ERROR_CODE_REC_TOV_TIMER_EXPIRATION 61 + + +#endif /* BNX2FC_CONSTANTS_H_ */ diff --git a/drivers/scsi/bnx2fc/bnx2fc_debug.h b/drivers/scsi/bnx2fc/bnx2fc_debug.h new file mode 100644 index 000000000000..7f6aff68cc53 --- /dev/null +++ b/drivers/scsi/bnx2fc/bnx2fc_debug.h @@ -0,0 +1,70 @@ +#ifndef __BNX2FC_DEBUG__ +#define __BNX2FC_DEBUG__ + +/* Log level bit mask */ +#define LOG_IO 0x01 /* scsi cmd error, cleanup */ +#define LOG_TGT 0x02 /* Session setup, cleanup, etc' */ +#define LOG_HBA 0x04 /* lport events, link, mtu, etc' */ +#define LOG_ELS 0x08 /* ELS logs */ +#define LOG_MISC 0x10 /* fcoe L2 frame related logs*/ +#define LOG_ALL 0xff /* LOG all messages */ + +extern unsigned int bnx2fc_debug_level; + +#define BNX2FC_CHK_LOGGING(LEVEL, CMD) \ + do { \ + if (unlikely(bnx2fc_debug_level & LEVEL)) \ + do { \ + CMD; \ + } while (0); \ + } while (0) + +#define BNX2FC_ELS_DBG(fmt, arg...) \ + BNX2FC_CHK_LOGGING(LOG_ELS, \ + printk(KERN_ALERT PFX fmt, ##arg)) + +#define BNX2FC_MISC_DBG(fmt, arg...) \ + BNX2FC_CHK_LOGGING(LOG_MISC, \ + printk(KERN_ALERT PFX fmt, ##arg)) + +#define BNX2FC_IO_DBG(io_req, fmt, arg...) \ + do { \ + if (!io_req || !io_req->port || !io_req->port->lport || \ + !io_req->port->lport->host) \ + BNX2FC_CHK_LOGGING(LOG_IO, \ + printk(KERN_ALERT PFX "NULL " fmt, ##arg)); \ + else \ + BNX2FC_CHK_LOGGING(LOG_IO, \ + shost_printk(KERN_ALERT, \ + (io_req)->port->lport->host, \ + PFX "xid:0x%x " fmt, \ + (io_req)->xid, ##arg)); \ + } while (0) + +#define BNX2FC_TGT_DBG(tgt, fmt, arg...) \ + do { \ + if (!tgt || !tgt->port || !tgt->port->lport || \ + !tgt->port->lport->host || !tgt->rport) \ + BNX2FC_CHK_LOGGING(LOG_TGT, \ + printk(KERN_ALERT PFX "NULL " fmt, ##arg)); \ + else \ + BNX2FC_CHK_LOGGING(LOG_TGT, \ + shost_printk(KERN_ALERT, \ + (tgt)->port->lport->host, \ + PFX "port:%x " fmt, \ + (tgt)->rport->port_id, ##arg)); \ + } while (0) + + +#define BNX2FC_HBA_DBG(lport, fmt, arg...) \ + do { \ + if (!lport || !lport->host) \ + BNX2FC_CHK_LOGGING(LOG_HBA, \ + printk(KERN_ALERT PFX "NULL " fmt, ##arg)); \ + else \ + BNX2FC_CHK_LOGGING(LOG_HBA, \ + shost_printk(KERN_ALERT, lport->host, \ + PFX fmt, ##arg)); \ + } while (0) + +#endif diff --git a/drivers/scsi/bnx2fc/bnx2fc_els.c b/drivers/scsi/bnx2fc/bnx2fc_els.c new file mode 100644 index 000000000000..7a11a255157f --- /dev/null +++ b/drivers/scsi/bnx2fc/bnx2fc_els.c @@ -0,0 +1,515 @@ +/* + * bnx2fc_els.c: Broadcom NetXtreme II Linux FCoE offload driver. + * This file contains helper routines that handle ELS requests + * and responses. + * + * Copyright (c) 2008 - 2010 Broadcom Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation. + * + * Written by: Bhanu Prakash Gollapudi (bprakash@broadcom.com) + */ + +#include "bnx2fc.h" + +static void bnx2fc_logo_resp(struct fc_seq *seq, struct fc_frame *fp, + void *arg); +static void bnx2fc_flogi_resp(struct fc_seq *seq, struct fc_frame *fp, + void *arg); +static int bnx2fc_initiate_els(struct bnx2fc_rport *tgt, unsigned int op, + void *data, u32 data_len, + void (*cb_func)(struct bnx2fc_els_cb_arg *cb_arg), + struct bnx2fc_els_cb_arg *cb_arg, u32 timer_msec); + +static void bnx2fc_rrq_compl(struct bnx2fc_els_cb_arg *cb_arg) +{ + struct bnx2fc_cmd *orig_io_req; + struct bnx2fc_cmd *rrq_req; + int rc = 0; + + BUG_ON(!cb_arg); + rrq_req = cb_arg->io_req; + orig_io_req = cb_arg->aborted_io_req; + BUG_ON(!orig_io_req); + BNX2FC_ELS_DBG("rrq_compl: orig xid = 0x%x, rrq_xid = 0x%x\n", + orig_io_req->xid, rrq_req->xid); + + kref_put(&orig_io_req->refcount, bnx2fc_cmd_release); + + if (test_and_clear_bit(BNX2FC_FLAG_ELS_TIMEOUT, &rrq_req->req_flags)) { + /* + * els req is timed out. cleanup the IO with FW and + * drop the completion. Remove from active_cmd_queue. + */ + BNX2FC_ELS_DBG("rrq xid - 0x%x timed out, clean it up\n", + rrq_req->xid); + + if (rrq_req->on_active_queue) { + list_del_init(&rrq_req->link); + rrq_req->on_active_queue = 0; + rc = bnx2fc_initiate_cleanup(rrq_req); + BUG_ON(rc); + } + } + kfree(cb_arg); +} +int bnx2fc_send_rrq(struct bnx2fc_cmd *aborted_io_req) +{ + + struct fc_els_rrq rrq; + struct bnx2fc_rport *tgt = aborted_io_req->tgt; + struct fc_lport *lport = tgt->rdata->local_port; + struct bnx2fc_els_cb_arg *cb_arg = NULL; + u32 sid = tgt->sid; + u32 r_a_tov = lport->r_a_tov; + unsigned long start = jiffies; + int rc; + + BNX2FC_ELS_DBG("Sending RRQ orig_xid = 0x%x\n", + aborted_io_req->xid); + memset(&rrq, 0, sizeof(rrq)); + + cb_arg = kzalloc(sizeof(struct bnx2fc_els_cb_arg), GFP_NOIO); + if (!cb_arg) { + printk(KERN_ERR PFX "Unable to allocate cb_arg for RRQ\n"); + rc = -ENOMEM; + goto rrq_err; + } + + cb_arg->aborted_io_req = aborted_io_req; + + rrq.rrq_cmd = ELS_RRQ; + hton24(rrq.rrq_s_id, sid); + rrq.rrq_ox_id = htons(aborted_io_req->xid); + rrq.rrq_rx_id = htons(aborted_io_req->task->rx_wr_tx_rd.rx_id); + +retry_rrq: + rc = bnx2fc_initiate_els(tgt, ELS_RRQ, &rrq, sizeof(rrq), + bnx2fc_rrq_compl, cb_arg, + r_a_tov); + if (rc == -ENOMEM) { + if (time_after(jiffies, start + (10 * HZ))) { + BNX2FC_ELS_DBG("rrq Failed\n"); + rc = FAILED; + goto rrq_err; + } + msleep(20); + goto retry_rrq; + } +rrq_err: + if (rc) { + BNX2FC_ELS_DBG("RRQ failed - release orig io req 0x%x\n", + aborted_io_req->xid); + kfree(cb_arg); + spin_lock_bh(&tgt->tgt_lock); + kref_put(&aborted_io_req->refcount, bnx2fc_cmd_release); + spin_unlock_bh(&tgt->tgt_lock); + } + return rc; +} + +static void bnx2fc_l2_els_compl(struct bnx2fc_els_cb_arg *cb_arg) +{ + struct bnx2fc_cmd *els_req; + struct bnx2fc_rport *tgt; + struct bnx2fc_mp_req *mp_req; + struct fc_frame_header *fc_hdr; + unsigned char *buf; + void *resp_buf; + u32 resp_len, hdr_len; + u16 l2_oxid; + int frame_len; + int rc = 0; + + l2_oxid = cb_arg->l2_oxid; + BNX2FC_ELS_DBG("ELS COMPL - l2_oxid = 0x%x\n", l2_oxid); + + els_req = cb_arg->io_req; + if (test_and_clear_bit(BNX2FC_FLAG_ELS_TIMEOUT, &els_req->req_flags)) { + /* + * els req is timed out. cleanup the IO with FW and + * drop the completion. libfc will handle the els timeout + */ + if (els_req->on_active_queue) { + list_del_init(&els_req->link); + els_req->on_active_queue = 0; + rc = bnx2fc_initiate_cleanup(els_req); + BUG_ON(rc); + } + goto free_arg; + } + + tgt = els_req->tgt; + mp_req = &(els_req->mp_req); + fc_hdr = &(mp_req->resp_fc_hdr); + resp_len = mp_req->resp_len; + resp_buf = mp_req->resp_buf; + + buf = kzalloc(PAGE_SIZE, GFP_ATOMIC); + if (!buf) { + printk(KERN_ERR PFX "Unable to alloc mp buf\n"); + goto free_arg; + } + hdr_len = sizeof(*fc_hdr); + if (hdr_len + resp_len > PAGE_SIZE) { + printk(KERN_ERR PFX "l2_els_compl: resp len is " + "beyond page size\n"); + goto free_buf; + } + memcpy(buf, fc_hdr, hdr_len); + memcpy(buf + hdr_len, resp_buf, resp_len); + frame_len = hdr_len + resp_len; + + bnx2fc_process_l2_frame_compl(tgt, buf, frame_len, l2_oxid); + +free_buf: + kfree(buf); +free_arg: + kfree(cb_arg); +} + +int bnx2fc_send_adisc(struct bnx2fc_rport *tgt, struct fc_frame *fp) +{ + struct fc_els_adisc *adisc; + struct fc_frame_header *fh; + struct bnx2fc_els_cb_arg *cb_arg; + struct fc_lport *lport = tgt->rdata->local_port; + u32 r_a_tov = lport->r_a_tov; + int rc; + + fh = fc_frame_header_get(fp); + cb_arg = kzalloc(sizeof(struct bnx2fc_els_cb_arg), GFP_ATOMIC); + if (!cb_arg) { + printk(KERN_ERR PFX "Unable to allocate cb_arg for ADISC\n"); + return -ENOMEM; + } + + cb_arg->l2_oxid = ntohs(fh->fh_ox_id); + + BNX2FC_ELS_DBG("send ADISC: l2_oxid = 0x%x\n", cb_arg->l2_oxid); + adisc = fc_frame_payload_get(fp, sizeof(*adisc)); + /* adisc is initialized by libfc */ + rc = bnx2fc_initiate_els(tgt, ELS_ADISC, adisc, sizeof(*adisc), + bnx2fc_l2_els_compl, cb_arg, 2 * r_a_tov); + if (rc) + kfree(cb_arg); + return rc; +} + +int bnx2fc_send_logo(struct bnx2fc_rport *tgt, struct fc_frame *fp) +{ + struct fc_els_logo *logo; + struct fc_frame_header *fh; + struct bnx2fc_els_cb_arg *cb_arg; + struct fc_lport *lport = tgt->rdata->local_port; + u32 r_a_tov = lport->r_a_tov; + int rc; + + fh = fc_frame_header_get(fp); + cb_arg = kzalloc(sizeof(struct bnx2fc_els_cb_arg), GFP_ATOMIC); + if (!cb_arg) { + printk(KERN_ERR PFX "Unable to allocate cb_arg for LOGO\n"); + return -ENOMEM; + } + + cb_arg->l2_oxid = ntohs(fh->fh_ox_id); + + BNX2FC_ELS_DBG("Send LOGO: l2_oxid = 0x%x\n", cb_arg->l2_oxid); + logo = fc_frame_payload_get(fp, sizeof(*logo)); + /* logo is initialized by libfc */ + rc = bnx2fc_initiate_els(tgt, ELS_LOGO, logo, sizeof(*logo), + bnx2fc_l2_els_compl, cb_arg, 2 * r_a_tov); + if (rc) + kfree(cb_arg); + return rc; +} + +int bnx2fc_send_rls(struct bnx2fc_rport *tgt, struct fc_frame *fp) +{ + struct fc_els_rls *rls; + struct fc_frame_header *fh; + struct bnx2fc_els_cb_arg *cb_arg; + struct fc_lport *lport = tgt->rdata->local_port; + u32 r_a_tov = lport->r_a_tov; + int rc; + + fh = fc_frame_header_get(fp); + cb_arg = kzalloc(sizeof(struct bnx2fc_els_cb_arg), GFP_ATOMIC); + if (!cb_arg) { + printk(KERN_ERR PFX "Unable to allocate cb_arg for LOGO\n"); + return -ENOMEM; + } + + cb_arg->l2_oxid = ntohs(fh->fh_ox_id); + + rls = fc_frame_payload_get(fp, sizeof(*rls)); + /* rls is initialized by libfc */ + rc = bnx2fc_initiate_els(tgt, ELS_RLS, rls, sizeof(*rls), + bnx2fc_l2_els_compl, cb_arg, 2 * r_a_tov); + if (rc) + kfree(cb_arg); + return rc; +} + +static int bnx2fc_initiate_els(struct bnx2fc_rport *tgt, unsigned int op, + void *data, u32 data_len, + void (*cb_func)(struct bnx2fc_els_cb_arg *cb_arg), + struct bnx2fc_els_cb_arg *cb_arg, u32 timer_msec) +{ + struct fcoe_port *port = tgt->port; + struct bnx2fc_hba *hba = port->priv; + struct fc_rport *rport = tgt->rport; + struct fc_lport *lport = port->lport; + struct bnx2fc_cmd *els_req; + struct bnx2fc_mp_req *mp_req; + struct fc_frame_header *fc_hdr; + struct fcoe_task_ctx_entry *task; + struct fcoe_task_ctx_entry *task_page; + int rc = 0; + int task_idx, index; + u32 did, sid; + u16 xid; + + rc = fc_remote_port_chkready(rport); + if (rc) { + printk(KERN_ALERT PFX "els 0x%x: rport not ready\n", op); + rc = -EINVAL; + goto els_err; + } + if (lport->state != LPORT_ST_READY || !(lport->link_up)) { + printk(KERN_ALERT PFX "els 0x%x: link is not ready\n", op); + rc = -EINVAL; + goto els_err; + } + if (!(test_bit(BNX2FC_FLAG_SESSION_READY, &tgt->flags)) || + (test_bit(BNX2FC_FLAG_EXPL_LOGO, &tgt->flags))) { + printk(KERN_ERR PFX "els 0x%x: tgt not ready\n", op); + rc = -EINVAL; + goto els_err; + } + els_req = bnx2fc_elstm_alloc(tgt, BNX2FC_ELS); + if (!els_req) { + rc = -ENOMEM; + goto els_err; + } + + els_req->sc_cmd = NULL; + els_req->port = port; + els_req->tgt = tgt; + els_req->cb_func = cb_func; + cb_arg->io_req = els_req; + els_req->cb_arg = cb_arg; + + mp_req = (struct bnx2fc_mp_req *)&(els_req->mp_req); + rc = bnx2fc_init_mp_req(els_req); + if (rc == FAILED) { + printk(KERN_ALERT PFX "ELS MP request init failed\n"); + spin_lock_bh(&tgt->tgt_lock); + kref_put(&els_req->refcount, bnx2fc_cmd_release); + spin_unlock_bh(&tgt->tgt_lock); + rc = -ENOMEM; + goto els_err; + } else { + /* rc SUCCESS */ + rc = 0; + } + + /* Set the data_xfer_len to the size of ELS payload */ + mp_req->req_len = data_len; + els_req->data_xfer_len = mp_req->req_len; + + /* Fill ELS Payload */ + if ((op >= ELS_LS_RJT) && (op <= ELS_AUTH_ELS)) { + memcpy(mp_req->req_buf, data, data_len); + } else { + printk(KERN_ALERT PFX "Invalid ELS op 0x%x\n", op); + els_req->cb_func = NULL; + els_req->cb_arg = NULL; + spin_lock_bh(&tgt->tgt_lock); + kref_put(&els_req->refcount, bnx2fc_cmd_release); + spin_unlock_bh(&tgt->tgt_lock); + rc = -EINVAL; + } + + if (rc) + goto els_err; + + /* Fill FC header */ + fc_hdr = &(mp_req->req_fc_hdr); + + did = tgt->rport->port_id; + sid = tgt->sid; + + __fc_fill_fc_hdr(fc_hdr, FC_RCTL_ELS_REQ, did, sid, + FC_TYPE_ELS, FC_FC_FIRST_SEQ | FC_FC_END_SEQ | + FC_FC_SEQ_INIT, 0); + + /* Obtain exchange id */ + xid = els_req->xid; + task_idx = xid/BNX2FC_TASKS_PER_PAGE; + index = xid % BNX2FC_TASKS_PER_PAGE; + + /* Initialize task context for this IO request */ + task_page = (struct fcoe_task_ctx_entry *) hba->task_ctx[task_idx]; + task = &(task_page[index]); + bnx2fc_init_mp_task(els_req, task); + + spin_lock_bh(&tgt->tgt_lock); + + if (!test_bit(BNX2FC_FLAG_SESSION_READY, &tgt->flags)) { + printk(KERN_ERR PFX "initiate_els.. session not ready\n"); + els_req->cb_func = NULL; + els_req->cb_arg = NULL; + kref_put(&els_req->refcount, bnx2fc_cmd_release); + spin_unlock_bh(&tgt->tgt_lock); + return -EINVAL; + } + + if (timer_msec) + bnx2fc_cmd_timer_set(els_req, timer_msec); + bnx2fc_add_2_sq(tgt, xid); + + els_req->on_active_queue = 1; + list_add_tail(&els_req->link, &tgt->els_queue); + + /* Ring doorbell */ + bnx2fc_ring_doorbell(tgt); + spin_unlock_bh(&tgt->tgt_lock); + +els_err: + return rc; +} + +void bnx2fc_process_els_compl(struct bnx2fc_cmd *els_req, + struct fcoe_task_ctx_entry *task, u8 num_rq) +{ + struct bnx2fc_mp_req *mp_req; + struct fc_frame_header *fc_hdr; + u64 *hdr; + u64 *temp_hdr; + + BNX2FC_ELS_DBG("Entered process_els_compl xid = 0x%x" + "cmd_type = %d\n", els_req->xid, els_req->cmd_type); + + if (test_and_set_bit(BNX2FC_FLAG_ELS_DONE, + &els_req->req_flags)) { + BNX2FC_ELS_DBG("Timer context finished processing this " + "els - 0x%x\n", els_req->xid); + /* This IO doesnt receive cleanup completion */ + kref_put(&els_req->refcount, bnx2fc_cmd_release); + return; + } + + /* Cancel the timeout_work, as we received the response */ + if (cancel_delayed_work(&els_req->timeout_work)) + kref_put(&els_req->refcount, + bnx2fc_cmd_release); /* drop timer hold */ + + if (els_req->on_active_queue) { + list_del_init(&els_req->link); + els_req->on_active_queue = 0; + } + + mp_req = &(els_req->mp_req); + fc_hdr = &(mp_req->resp_fc_hdr); + + hdr = (u64 *)fc_hdr; + temp_hdr = (u64 *) + &task->cmn.general.cmd_info.mp_fc_frame.fc_hdr; + hdr[0] = cpu_to_be64(temp_hdr[0]); + hdr[1] = cpu_to_be64(temp_hdr[1]); + hdr[2] = cpu_to_be64(temp_hdr[2]); + + mp_req->resp_len = task->rx_wr_only.sgl_ctx.mul_sges.cur_sge_off; + + /* Parse ELS response */ + if ((els_req->cb_func) && (els_req->cb_arg)) { + els_req->cb_func(els_req->cb_arg); + els_req->cb_arg = NULL; + } + + kref_put(&els_req->refcount, bnx2fc_cmd_release); +} + +static void bnx2fc_flogi_resp(struct fc_seq *seq, struct fc_frame *fp, + void *arg) +{ + struct fcoe_ctlr *fip = arg; + struct fc_exch *exch = fc_seq_exch(seq); + struct fc_lport *lport = exch->lp; + u8 *mac; + struct fc_frame_header *fh; + u8 op; + + if (IS_ERR(fp)) + goto done; + + mac = fr_cb(fp)->granted_mac; + if (is_zero_ether_addr(mac)) { + fh = fc_frame_header_get(fp); + if (fh->fh_type != FC_TYPE_ELS) { + printk(KERN_ERR PFX "bnx2fc_flogi_resp:" + "fh_type != FC_TYPE_ELS\n"); + fc_frame_free(fp); + return; + } + op = fc_frame_payload_op(fp); + if (lport->vport) { + if (op == ELS_LS_RJT) { + printk(KERN_ERR PFX "bnx2fc_flogi_resp is LS_RJT\n"); + fc_vport_terminate(lport->vport); + fc_frame_free(fp); + return; + } + } + if (fcoe_ctlr_recv_flogi(fip, lport, fp)) { + fc_frame_free(fp); + return; + } + } + fip->update_mac(lport, mac); +done: + fc_lport_flogi_resp(seq, fp, lport); +} + +static void bnx2fc_logo_resp(struct fc_seq *seq, struct fc_frame *fp, + void *arg) +{ + struct fcoe_ctlr *fip = arg; + struct fc_exch *exch = fc_seq_exch(seq); + struct fc_lport *lport = exch->lp; + static u8 zero_mac[ETH_ALEN] = { 0 }; + + if (!IS_ERR(fp)) + fip->update_mac(lport, zero_mac); + fc_lport_logo_resp(seq, fp, lport); +} + +struct fc_seq *bnx2fc_elsct_send(struct fc_lport *lport, u32 did, + struct fc_frame *fp, unsigned int op, + void (*resp)(struct fc_seq *, + struct fc_frame *, + void *), + void *arg, u32 timeout) +{ + struct fcoe_port *port = lport_priv(lport); + struct bnx2fc_hba *hba = port->priv; + struct fcoe_ctlr *fip = &hba->ctlr; + struct fc_frame_header *fh = fc_frame_header_get(fp); + + switch (op) { + case ELS_FLOGI: + case ELS_FDISC: + return fc_elsct_send(lport, did, fp, op, bnx2fc_flogi_resp, + fip, timeout); + case ELS_LOGO: + /* only hook onto fabric logouts, not port logouts */ + if (ntoh24(fh->fh_d_id) != FC_FID_FLOGI) + break; + return fc_elsct_send(lport, did, fp, op, bnx2fc_logo_resp, + fip, timeout); + } + return fc_elsct_send(lport, did, fp, op, resp, arg, timeout); +} diff --git a/drivers/scsi/bnx2fc/bnx2fc_fcoe.c b/drivers/scsi/bnx2fc/bnx2fc_fcoe.c new file mode 100644 index 000000000000..e2e647509a73 --- /dev/null +++ b/drivers/scsi/bnx2fc/bnx2fc_fcoe.c @@ -0,0 +1,2461 @@ +/* bnx2fc_fcoe.c: Broadcom NetXtreme II Linux FCoE offload driver. + * This file contains the code that interacts with libfc, libfcoe, + * cnic modules to create FCoE instances, send/receive non-offloaded + * FIP/FCoE packets, listen to link events etc. + * + * Copyright (c) 2008 - 2010 Broadcom Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation. + * + * Written by: Bhanu Prakash Gollapudi (bprakash@broadcom.com) + */ + +#include "bnx2fc.h" + +static struct list_head adapter_list; +static u32 adapter_count; +static DEFINE_MUTEX(bnx2fc_dev_lock); +DEFINE_PER_CPU(struct bnx2fc_percpu_s, bnx2fc_percpu); + +#define DRV_MODULE_NAME "bnx2fc" +#define DRV_MODULE_VERSION BNX2FC_VERSION +#define DRV_MODULE_RELDATE "Mar 17, 2011" + + +static char version[] __devinitdata = + "Broadcom NetXtreme II FCoE Driver " DRV_MODULE_NAME \ + " v" DRV_MODULE_VERSION " (" DRV_MODULE_RELDATE ")\n"; + + +MODULE_AUTHOR("Bhanu Prakash Gollapudi <bprakash@broadcom.com>"); +MODULE_DESCRIPTION("Broadcom NetXtreme II BCM57710 FCoE Driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRV_MODULE_VERSION); + +#define BNX2FC_MAX_QUEUE_DEPTH 256 +#define BNX2FC_MIN_QUEUE_DEPTH 32 +#define FCOE_WORD_TO_BYTE 4 + +static struct scsi_transport_template *bnx2fc_transport_template; +static struct scsi_transport_template *bnx2fc_vport_xport_template; + +struct workqueue_struct *bnx2fc_wq; + +/* bnx2fc structure needs only one instance of the fcoe_percpu_s structure. + * Here the io threads are per cpu but the l2 thread is just one + */ +struct fcoe_percpu_s bnx2fc_global; +DEFINE_SPINLOCK(bnx2fc_global_lock); + +static struct cnic_ulp_ops bnx2fc_cnic_cb; +static struct libfc_function_template bnx2fc_libfc_fcn_templ; +static struct scsi_host_template bnx2fc_shost_template; +static struct fc_function_template bnx2fc_transport_function; +static struct fc_function_template bnx2fc_vport_xport_function; +static int bnx2fc_create(struct net_device *netdev, enum fip_state fip_mode); +static int bnx2fc_destroy(struct net_device *net_device); +static int bnx2fc_enable(struct net_device *netdev); +static int bnx2fc_disable(struct net_device *netdev); + +static void bnx2fc_recv_frame(struct sk_buff *skb); + +static void bnx2fc_start_disc(struct bnx2fc_hba *hba); +static int bnx2fc_shost_config(struct fc_lport *lport, struct device *dev); +static int bnx2fc_net_config(struct fc_lport *lp); +static int bnx2fc_lport_config(struct fc_lport *lport); +static int bnx2fc_em_config(struct fc_lport *lport); +static int bnx2fc_bind_adapter_devices(struct bnx2fc_hba *hba); +static void bnx2fc_unbind_adapter_devices(struct bnx2fc_hba *hba); +static int bnx2fc_bind_pcidev(struct bnx2fc_hba *hba); +static void bnx2fc_unbind_pcidev(struct bnx2fc_hba *hba); +static struct fc_lport *bnx2fc_if_create(struct bnx2fc_hba *hba, + struct device *parent, int npiv); +static void bnx2fc_destroy_work(struct work_struct *work); + +static struct bnx2fc_hba *bnx2fc_hba_lookup(struct net_device *phys_dev); +static struct bnx2fc_hba *bnx2fc_find_hba_for_cnic(struct cnic_dev *cnic); + +static int bnx2fc_fw_init(struct bnx2fc_hba *hba); +static void bnx2fc_fw_destroy(struct bnx2fc_hba *hba); + +static void bnx2fc_port_shutdown(struct fc_lport *lport); +static void bnx2fc_stop(struct bnx2fc_hba *hba); +static int __init bnx2fc_mod_init(void); +static void __exit bnx2fc_mod_exit(void); + +unsigned int bnx2fc_debug_level; +module_param_named(debug_logging, bnx2fc_debug_level, int, S_IRUGO|S_IWUSR); + +static int bnx2fc_cpu_callback(struct notifier_block *nfb, + unsigned long action, void *hcpu); +/* notification function for CPU hotplug events */ +static struct notifier_block bnx2fc_cpu_notifier = { + .notifier_call = bnx2fc_cpu_callback, +}; + +static void bnx2fc_clean_rx_queue(struct fc_lport *lp) +{ + struct fcoe_percpu_s *bg; + struct fcoe_rcv_info *fr; + struct sk_buff_head *list; + struct sk_buff *skb, *next; + struct sk_buff *head; + + bg = &bnx2fc_global; + spin_lock_bh(&bg->fcoe_rx_list.lock); + list = &bg->fcoe_rx_list; + head = list->next; + for (skb = head; skb != (struct sk_buff *)list; + skb = next) { + next = skb->next; + fr = fcoe_dev_from_skb(skb); + if (fr->fr_dev == lp) { + __skb_unlink(skb, list); + kfree_skb(skb); + } + } + spin_unlock_bh(&bg->fcoe_rx_list.lock); +} + +int bnx2fc_get_paged_crc_eof(struct sk_buff *skb, int tlen) +{ + int rc; + spin_lock(&bnx2fc_global_lock); + rc = fcoe_get_paged_crc_eof(skb, tlen, &bnx2fc_global); + spin_unlock(&bnx2fc_global_lock); + + return rc; +} + +static void bnx2fc_abort_io(struct fc_lport *lport) +{ + /* + * This function is no-op for bnx2fc, but we do + * not want to leave it as NULL either, as libfc + * can call the default function which is + * fc_fcp_abort_io. + */ +} + +static void bnx2fc_cleanup(struct fc_lport *lport) +{ + struct fcoe_port *port = lport_priv(lport); + struct bnx2fc_hba *hba = port->priv; + struct bnx2fc_rport *tgt; + int i; + + BNX2FC_MISC_DBG("Entered %s\n", __func__); + mutex_lock(&hba->hba_mutex); + spin_lock_bh(&hba->hba_lock); + for (i = 0; i < BNX2FC_NUM_MAX_SESS; i++) { + tgt = hba->tgt_ofld_list[i]; + if (tgt) { + /* Cleanup IOs belonging to requested vport */ + if (tgt->port == port) { + spin_unlock_bh(&hba->hba_lock); + BNX2FC_TGT_DBG(tgt, "flush/cleanup\n"); + bnx2fc_flush_active_ios(tgt); + spin_lock_bh(&hba->hba_lock); + } + } + } + spin_unlock_bh(&hba->hba_lock); + mutex_unlock(&hba->hba_mutex); +} + +static int bnx2fc_xmit_l2_frame(struct bnx2fc_rport *tgt, + struct fc_frame *fp) +{ + struct fc_rport_priv *rdata = tgt->rdata; + struct fc_frame_header *fh; + int rc = 0; + + fh = fc_frame_header_get(fp); + BNX2FC_TGT_DBG(tgt, "Xmit L2 frame rport = 0x%x, oxid = 0x%x, " + "r_ctl = 0x%x\n", rdata->ids.port_id, + ntohs(fh->fh_ox_id), fh->fh_r_ctl); + if ((fh->fh_type == FC_TYPE_ELS) && + (fh->fh_r_ctl == FC_RCTL_ELS_REQ)) { + + switch (fc_frame_payload_op(fp)) { + case ELS_ADISC: + rc = bnx2fc_send_adisc(tgt, fp); + break; + case ELS_LOGO: + rc = bnx2fc_send_logo(tgt, fp); + break; + case ELS_RLS: + rc = bnx2fc_send_rls(tgt, fp); + break; + default: + break; + } + } else if ((fh->fh_type == FC_TYPE_BLS) && + (fh->fh_r_ctl == FC_RCTL_BA_ABTS)) + BNX2FC_TGT_DBG(tgt, "ABTS frame\n"); + else { + BNX2FC_TGT_DBG(tgt, "Send L2 frame type 0x%x " + "rctl 0x%x thru non-offload path\n", + fh->fh_type, fh->fh_r_ctl); + return -ENODEV; + } + if (rc) + return -ENOMEM; + else + return 0; +} + +/** + * bnx2fc_xmit - bnx2fc's FCoE frame transmit function + * + * @lport: the associated local port + * @fp: the fc_frame to be transmitted + */ +static int bnx2fc_xmit(struct fc_lport *lport, struct fc_frame *fp) +{ + struct ethhdr *eh; + struct fcoe_crc_eof *cp; + struct sk_buff *skb; + struct fc_frame_header *fh; + struct bnx2fc_hba *hba; + struct fcoe_port *port; + struct fcoe_hdr *hp; + struct bnx2fc_rport *tgt; + struct fcoe_dev_stats *stats; + u8 sof, eof; + u32 crc; + unsigned int hlen, tlen, elen; + int wlen, rc = 0; + + port = (struct fcoe_port *)lport_priv(lport); + hba = port->priv; + + fh = fc_frame_header_get(fp); + + skb = fp_skb(fp); + if (!lport->link_up) { + BNX2FC_HBA_DBG(lport, "bnx2fc_xmit link down\n"); + kfree_skb(skb); + return 0; + } + + if (unlikely(fh->fh_r_ctl == FC_RCTL_ELS_REQ)) { + if (!hba->ctlr.sel_fcf) { + BNX2FC_HBA_DBG(lport, "FCF not selected yet!\n"); + kfree_skb(skb); + return -EINVAL; + } + if (fcoe_ctlr_els_send(&hba->ctlr, lport, skb)) + return 0; + } + + sof = fr_sof(fp); + eof = fr_eof(fp); + + /* + * Snoop the frame header to check if the frame is for + * an offloaded session + */ + /* + * tgt_ofld_list access is synchronized using + * both hba mutex and hba lock. Atleast hba mutex or + * hba lock needs to be held for read access. + */ + + spin_lock_bh(&hba->hba_lock); + tgt = bnx2fc_tgt_lookup(port, ntoh24(fh->fh_d_id)); + if (tgt && (test_bit(BNX2FC_FLAG_SESSION_READY, &tgt->flags))) { + /* This frame is for offloaded session */ + BNX2FC_HBA_DBG(lport, "xmit: Frame is for offloaded session " + "port_id = 0x%x\n", ntoh24(fh->fh_d_id)); + spin_unlock_bh(&hba->hba_lock); + rc = bnx2fc_xmit_l2_frame(tgt, fp); + if (rc != -ENODEV) { + kfree_skb(skb); + return rc; + } + } else { + spin_unlock_bh(&hba->hba_lock); + } + + elen = sizeof(struct ethhdr); + hlen = sizeof(struct fcoe_hdr); + tlen = sizeof(struct fcoe_crc_eof); + wlen = (skb->len - tlen + sizeof(crc)) / FCOE_WORD_TO_BYTE; + + skb->ip_summed = CHECKSUM_NONE; + crc = fcoe_fc_crc(fp); + + /* copy port crc and eof to the skb buff */ + if (skb_is_nonlinear(skb)) { + skb_frag_t *frag; + if (bnx2fc_get_paged_crc_eof(skb, tlen)) { + kfree_skb(skb); + return -ENOMEM; + } + frag = &skb_shinfo(skb)->frags[skb_shinfo(skb)->nr_frags - 1]; + cp = kmap_atomic(frag->page, KM_SKB_DATA_SOFTIRQ) + + frag->page_offset; + } else { + cp = (struct fcoe_crc_eof *)skb_put(skb, tlen); + } + + memset(cp, 0, sizeof(*cp)); + cp->fcoe_eof = eof; + cp->fcoe_crc32 = cpu_to_le32(~crc); + if (skb_is_nonlinear(skb)) { + kunmap_atomic(cp, KM_SKB_DATA_SOFTIRQ); + cp = NULL; + } + + /* adjust skb network/transport offsets to match mac/fcoe/port */ + skb_push(skb, elen + hlen); + skb_reset_mac_header(skb); + skb_reset_network_header(skb); + skb->mac_len = elen; + skb->protocol = htons(ETH_P_FCOE); + skb->dev = hba->netdev; + + /* fill up mac and fcoe headers */ + eh = eth_hdr(skb); + eh->h_proto = htons(ETH_P_FCOE); + if (hba->ctlr.map_dest) + fc_fcoe_set_mac(eh->h_dest, fh->fh_d_id); + else + /* insert GW address */ + memcpy(eh->h_dest, hba->ctlr.dest_addr, ETH_ALEN); + + if (unlikely(hba->ctlr.flogi_oxid != FC_XID_UNKNOWN)) + memcpy(eh->h_source, hba->ctlr.ctl_src_addr, ETH_ALEN); + else + memcpy(eh->h_source, port->data_src_addr, ETH_ALEN); + + hp = (struct fcoe_hdr *)(eh + 1); + memset(hp, 0, sizeof(*hp)); + if (FC_FCOE_VER) + FC_FCOE_ENCAPS_VER(hp, FC_FCOE_VER); + hp->fcoe_sof = sof; + + /* fcoe lso, mss is in max_payload which is non-zero for FCP data */ + if (lport->seq_offload && fr_max_payload(fp)) { + skb_shinfo(skb)->gso_type = SKB_GSO_FCOE; + skb_shinfo(skb)->gso_size = fr_max_payload(fp); + } else { + skb_shinfo(skb)->gso_type = 0; + skb_shinfo(skb)->gso_size = 0; + } + + /*update tx stats */ + stats = per_cpu_ptr(lport->dev_stats, get_cpu()); + stats->TxFrames++; + stats->TxWords += wlen; + put_cpu(); + + /* send down to lld */ + fr_dev(fp) = lport; + if (port->fcoe_pending_queue.qlen) + fcoe_check_wait_queue(lport, skb); + else if (fcoe_start_io(skb)) + fcoe_check_wait_queue(lport, skb); + + return 0; +} + +/** + * bnx2fc_rcv - This is bnx2fc's receive function called by NET_RX_SOFTIRQ + * + * @skb: the receive socket buffer + * @dev: associated net device + * @ptype: context + * @olddev: last device + * + * This function receives the packet and builds FC frame and passes it up + */ +static int bnx2fc_rcv(struct sk_buff *skb, struct net_device *dev, + struct packet_type *ptype, struct net_device *olddev) +{ + struct fc_lport *lport; + struct bnx2fc_hba *hba; + struct fc_frame_header *fh; + struct fcoe_rcv_info *fr; + struct fcoe_percpu_s *bg; + unsigned short oxid; + + hba = container_of(ptype, struct bnx2fc_hba, fcoe_packet_type); + lport = hba->ctlr.lp; + + if (unlikely(lport == NULL)) { + printk(KERN_ALERT PFX "bnx2fc_rcv: lport is NULL\n"); + goto err; + } + + if (unlikely(eth_hdr(skb)->h_proto != htons(ETH_P_FCOE))) { + printk(KERN_ALERT PFX "bnx2fc_rcv: Wrong FC type frame\n"); + goto err; + } + + /* + * Check for minimum frame length, and make sure required FCoE + * and FC headers are pulled into the linear data area. + */ + if (unlikely((skb->len < FCOE_MIN_FRAME) || + !pskb_may_pull(skb, FCOE_HEADER_LEN))) + goto err; + + skb_set_transport_header(skb, sizeof(struct fcoe_hdr)); + fh = (struct fc_frame_header *) skb_transport_header(skb); + + oxid = ntohs(fh->fh_ox_id); + + fr = fcoe_dev_from_skb(skb); + fr->fr_dev = lport; + fr->ptype = ptype; + + bg = &bnx2fc_global; + spin_lock_bh(&bg->fcoe_rx_list.lock); + + __skb_queue_tail(&bg->fcoe_rx_list, skb); + if (bg->fcoe_rx_list.qlen == 1) + wake_up_process(bg->thread); + + spin_unlock_bh(&bg->fcoe_rx_list.lock); + + return 0; +err: + kfree_skb(skb); + return -1; +} + +static int bnx2fc_l2_rcv_thread(void *arg) +{ + struct fcoe_percpu_s *bg = arg; + struct sk_buff *skb; + + set_user_nice(current, -20); + set_current_state(TASK_INTERRUPTIBLE); + while (!kthread_should_stop()) { + schedule(); + spin_lock_bh(&bg->fcoe_rx_list.lock); + while ((skb = __skb_dequeue(&bg->fcoe_rx_list)) != NULL) { + spin_unlock_bh(&bg->fcoe_rx_list.lock); + bnx2fc_recv_frame(skb); + spin_lock_bh(&bg->fcoe_rx_list.lock); + } + __set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_bh(&bg->fcoe_rx_list.lock); + } + __set_current_state(TASK_RUNNING); + return 0; +} + + +static void bnx2fc_recv_frame(struct sk_buff *skb) +{ + u32 fr_len; + struct fc_lport *lport; + struct fcoe_rcv_info *fr; + struct fcoe_dev_stats *stats; + struct fc_frame_header *fh; + struct fcoe_crc_eof crc_eof; + struct fc_frame *fp; + struct fc_lport *vn_port; + struct fcoe_port *port; + u8 *mac = NULL; + u8 *dest_mac = NULL; + struct fcoe_hdr *hp; + + fr = fcoe_dev_from_skb(skb); + lport = fr->fr_dev; + if (unlikely(lport == NULL)) { + printk(KERN_ALERT PFX "Invalid lport struct\n"); + kfree_skb(skb); + return; + } + + if (skb_is_nonlinear(skb)) + skb_linearize(skb); + mac = eth_hdr(skb)->h_source; + dest_mac = eth_hdr(skb)->h_dest; + + /* Pull the header */ + hp = (struct fcoe_hdr *) skb_network_header(skb); + fh = (struct fc_frame_header *) skb_transport_header(skb); + skb_pull(skb, sizeof(struct fcoe_hdr)); + fr_len = skb->len - sizeof(struct fcoe_crc_eof); + + stats = per_cpu_ptr(lport->dev_stats, get_cpu()); + stats->RxFrames++; + stats->RxWords += fr_len / FCOE_WORD_TO_BYTE; + + fp = (struct fc_frame *)skb; + fc_frame_init(fp); + fr_dev(fp) = lport; + fr_sof(fp) = hp->fcoe_sof; + if (skb_copy_bits(skb, fr_len, &crc_eof, sizeof(crc_eof))) { + put_cpu(); + kfree_skb(skb); + return; + } + fr_eof(fp) = crc_eof.fcoe_eof; + fr_crc(fp) = crc_eof.fcoe_crc32; + if (pskb_trim(skb, fr_len)) { + put_cpu(); + kfree_skb(skb); + return; + } + + fh = fc_frame_header_get(fp); + + vn_port = fc_vport_id_lookup(lport, ntoh24(fh->fh_d_id)); + if (vn_port) { + port = lport_priv(vn_port); + if (compare_ether_addr(port->data_src_addr, dest_mac) + != 0) { + BNX2FC_HBA_DBG(lport, "fpma mismatch\n"); + put_cpu(); + kfree_skb(skb); + return; + } + } + if (fh->fh_r_ctl == FC_RCTL_DD_SOL_DATA && + fh->fh_type == FC_TYPE_FCP) { + /* Drop FCP data. We dont this in L2 path */ + put_cpu(); + kfree_skb(skb); + return; + } + if (fh->fh_r_ctl == FC_RCTL_ELS_REQ && + fh->fh_type == FC_TYPE_ELS) { + switch (fc_frame_payload_op(fp)) { + case ELS_LOGO: + if (ntoh24(fh->fh_s_id) == FC_FID_FLOGI) { + /* drop non-FIP LOGO */ + put_cpu(); + kfree_skb(skb); + return; + } + break; + } + } + if (le32_to_cpu(fr_crc(fp)) != + ~crc32(~0, skb->data, fr_len)) { + if (stats->InvalidCRCCount < 5) + printk(KERN_WARNING PFX "dropping frame with " + "CRC error\n"); + stats->InvalidCRCCount++; + put_cpu(); + kfree_skb(skb); + return; + } + put_cpu(); + fc_exch_recv(lport, fp); +} + +/** + * bnx2fc_percpu_io_thread - thread per cpu for ios + * + * @arg: ptr to bnx2fc_percpu_info structure + */ +int bnx2fc_percpu_io_thread(void *arg) +{ + struct bnx2fc_percpu_s *p = arg; + struct bnx2fc_work *work, *tmp; + LIST_HEAD(work_list); + + set_user_nice(current, -20); + set_current_state(TASK_INTERRUPTIBLE); + while (!kthread_should_stop()) { + schedule(); + spin_lock_bh(&p->fp_work_lock); + while (!list_empty(&p->work_list)) { + list_splice_init(&p->work_list, &work_list); + spin_unlock_bh(&p->fp_work_lock); + + list_for_each_entry_safe(work, tmp, &work_list, list) { + list_del_init(&work->list); + bnx2fc_process_cq_compl(work->tgt, work->wqe); + kfree(work); + } + + spin_lock_bh(&p->fp_work_lock); + } + __set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_bh(&p->fp_work_lock); + } + __set_current_state(TASK_RUNNING); + + return 0; +} + +static struct fc_host_statistics *bnx2fc_get_host_stats(struct Scsi_Host *shost) +{ + struct fc_host_statistics *bnx2fc_stats; + struct fc_lport *lport = shost_priv(shost); + struct fcoe_port *port = lport_priv(lport); + struct bnx2fc_hba *hba = port->priv; + struct fcoe_statistics_params *fw_stats; + int rc = 0; + + fw_stats = (struct fcoe_statistics_params *)hba->stats_buffer; + if (!fw_stats) + return NULL; + + bnx2fc_stats = fc_get_host_stats(shost); + + init_completion(&hba->stat_req_done); + if (bnx2fc_send_stat_req(hba)) + return bnx2fc_stats; + rc = wait_for_completion_timeout(&hba->stat_req_done, (2 * HZ)); + if (!rc) { + BNX2FC_HBA_DBG(lport, "FW stat req timed out\n"); + return bnx2fc_stats; + } + bnx2fc_stats->invalid_crc_count += fw_stats->rx_stat1.fc_crc_cnt; + bnx2fc_stats->tx_frames += fw_stats->tx_stat.fcoe_tx_pkt_cnt; + bnx2fc_stats->tx_words += (fw_stats->tx_stat.fcoe_tx_byte_cnt) / 4; + bnx2fc_stats->rx_frames += fw_stats->rx_stat0.fcoe_rx_pkt_cnt; + bnx2fc_stats->rx_words += (fw_stats->rx_stat0.fcoe_rx_byte_cnt) / 4; + + bnx2fc_stats->dumped_frames = 0; + bnx2fc_stats->lip_count = 0; + bnx2fc_stats->nos_count = 0; + bnx2fc_stats->loss_of_sync_count = 0; + bnx2fc_stats->loss_of_signal_count = 0; + bnx2fc_stats->prim_seq_protocol_err_count = 0; + + return bnx2fc_stats; +} + +static int bnx2fc_shost_config(struct fc_lport *lport, struct device *dev) +{ + struct fcoe_port *port = lport_priv(lport); + struct bnx2fc_hba *hba = port->priv; + struct Scsi_Host *shost = lport->host; + int rc = 0; + + shost->max_cmd_len = BNX2FC_MAX_CMD_LEN; + shost->max_lun = BNX2FC_MAX_LUN; + shost->max_id = BNX2FC_MAX_FCP_TGT; + shost->max_channel = 0; + if (lport->vport) + shost->transportt = bnx2fc_vport_xport_template; + else + shost->transportt = bnx2fc_transport_template; + + /* Add the new host to SCSI-ml */ + rc = scsi_add_host(lport->host, dev); + if (rc) { + printk(KERN_ERR PFX "Error on scsi_add_host\n"); + return rc; + } + if (!lport->vport) + fc_host_max_npiv_vports(lport->host) = USHRT_MAX; + sprintf(fc_host_symbolic_name(lport->host), "%s v%s over %s", + BNX2FC_NAME, BNX2FC_VERSION, + hba->netdev->name); + + return 0; +} + +static void bnx2fc_link_speed_update(struct fc_lport *lport) +{ + struct fcoe_port *port = lport_priv(lport); + struct bnx2fc_hba *hba = port->priv; + struct net_device *netdev = hba->netdev; + struct ethtool_cmd ecmd = { ETHTOOL_GSET }; + + if (!dev_ethtool_get_settings(netdev, &ecmd)) { + lport->link_supported_speeds &= + ~(FC_PORTSPEED_1GBIT | FC_PORTSPEED_10GBIT); + if (ecmd.supported & (SUPPORTED_1000baseT_Half | + SUPPORTED_1000baseT_Full)) + lport->link_supported_speeds |= FC_PORTSPEED_1GBIT; + if (ecmd.supported & SUPPORTED_10000baseT_Full) + lport->link_supported_speeds |= FC_PORTSPEED_10GBIT; + + if (ecmd.speed == SPEED_1000) + lport->link_speed = FC_PORTSPEED_1GBIT; + if (ecmd.speed == SPEED_10000) + lport->link_speed = FC_PORTSPEED_10GBIT; + } + return; +} +static int bnx2fc_link_ok(struct fc_lport *lport) +{ + struct fcoe_port *port = lport_priv(lport); + struct bnx2fc_hba *hba = port->priv; + struct net_device *dev = hba->phys_dev; + int rc = 0; + + if ((dev->flags & IFF_UP) && netif_carrier_ok(dev)) + clear_bit(ADAPTER_STATE_LINK_DOWN, &hba->adapter_state); + else { + set_bit(ADAPTER_STATE_LINK_DOWN, &hba->adapter_state); + rc = -1; + } + return rc; +} + +/** + * bnx2fc_get_link_state - get network link state + * + * @hba: adapter instance pointer + * + * updates adapter structure flag based on netdev state + */ +void bnx2fc_get_link_state(struct bnx2fc_hba *hba) +{ + if (test_bit(__LINK_STATE_NOCARRIER, &hba->netdev->state)) + set_bit(ADAPTER_STATE_LINK_DOWN, &hba->adapter_state); + else + clear_bit(ADAPTER_STATE_LINK_DOWN, &hba->adapter_state); +} + +static int bnx2fc_net_config(struct fc_lport *lport) +{ + struct bnx2fc_hba *hba; + struct fcoe_port *port; + u64 wwnn, wwpn; + + port = lport_priv(lport); + hba = port->priv; + + /* require support for get_pauseparam ethtool op. */ + if (!hba->phys_dev->ethtool_ops || + !hba->phys_dev->ethtool_ops->get_pauseparam) + return -EOPNOTSUPP; + + if (fc_set_mfs(lport, BNX2FC_MFS)) + return -EINVAL; + + skb_queue_head_init(&port->fcoe_pending_queue); + port->fcoe_pending_queue_active = 0; + setup_timer(&port->timer, fcoe_queue_timer, (unsigned long) lport); + + bnx2fc_link_speed_update(lport); + + if (!lport->vport) { + wwnn = fcoe_wwn_from_mac(hba->ctlr.ctl_src_addr, 1, 0); + BNX2FC_HBA_DBG(lport, "WWNN = 0x%llx\n", wwnn); + fc_set_wwnn(lport, wwnn); + + wwpn = fcoe_wwn_from_mac(hba->ctlr.ctl_src_addr, 2, 0); + BNX2FC_HBA_DBG(lport, "WWPN = 0x%llx\n", wwpn); + fc_set_wwpn(lport, wwpn); + } + + return 0; +} + +static void bnx2fc_destroy_timer(unsigned long data) +{ + struct bnx2fc_hba *hba = (struct bnx2fc_hba *)data; + + BNX2FC_HBA_DBG(hba->ctlr.lp, "ERROR:bnx2fc_destroy_timer - " + "Destroy compl not received!!\n"); + hba->flags |= BNX2FC_FLAG_DESTROY_CMPL; + wake_up_interruptible(&hba->destroy_wait); +} + +/** + * bnx2fc_indicate_netevent - Generic netdev event handler + * + * @context: adapter structure pointer + * @event: event type + * + * Handles NETDEV_UP, NETDEV_DOWN, NETDEV_GOING_DOWN,NETDEV_CHANGE and + * NETDEV_CHANGE_MTU events + */ +static void bnx2fc_indicate_netevent(void *context, unsigned long event) +{ + struct bnx2fc_hba *hba = (struct bnx2fc_hba *)context; + struct fc_lport *lport = hba->ctlr.lp; + struct fc_lport *vport; + u32 link_possible = 1; + + if (!test_bit(BNX2FC_CREATE_DONE, &hba->init_done)) { + BNX2FC_MISC_DBG("driver not ready. event=%s %ld\n", + hba->netdev->name, event); + return; + } + + /* + * ASSUMPTION: + * indicate_netevent cannot be called from cnic unless bnx2fc + * does register_device + */ + BUG_ON(!lport); + + BNX2FC_HBA_DBG(lport, "enter netevent handler - event=%s %ld\n", + hba->netdev->name, event); + + switch (event) { + case NETDEV_UP: + BNX2FC_HBA_DBG(lport, "Port up, adapter_state = %ld\n", + hba->adapter_state); + if (!test_bit(ADAPTER_STATE_UP, &hba->adapter_state)) + printk(KERN_ERR "indicate_netevent: "\ + "adapter is not UP!!\n"); + break; + + case NETDEV_DOWN: + BNX2FC_HBA_DBG(lport, "Port down\n"); + clear_bit(ADAPTER_STATE_GOING_DOWN, &hba->adapter_state); + clear_bit(ADAPTER_STATE_UP, &hba->adapter_state); + link_possible = 0; + break; + + case NETDEV_GOING_DOWN: + BNX2FC_HBA_DBG(lport, "Port going down\n"); + set_bit(ADAPTER_STATE_GOING_DOWN, &hba->adapter_state); + link_possible = 0; + break; + + case NETDEV_CHANGE: + BNX2FC_HBA_DBG(lport, "NETDEV_CHANGE\n"); + break; + + default: + printk(KERN_ERR PFX "Unkonwn netevent %ld", event); + return; + } + + bnx2fc_link_speed_update(lport); + + if (link_possible && !bnx2fc_link_ok(lport)) { + printk(KERN_ERR "indicate_netevent: call ctlr_link_up\n"); + fcoe_ctlr_link_up(&hba->ctlr); + } else { + printk(KERN_ERR "indicate_netevent: call ctlr_link_down\n"); + if (fcoe_ctlr_link_down(&hba->ctlr)) { + clear_bit(ADAPTER_STATE_READY, &hba->adapter_state); + mutex_lock(&lport->lp_mutex); + list_for_each_entry(vport, &lport->vports, list) + fc_host_port_type(vport->host) = + FC_PORTTYPE_UNKNOWN; + mutex_unlock(&lport->lp_mutex); + fc_host_port_type(lport->host) = FC_PORTTYPE_UNKNOWN; + per_cpu_ptr(lport->dev_stats, + get_cpu())->LinkFailureCount++; + put_cpu(); + fcoe_clean_pending_queue(lport); + + init_waitqueue_head(&hba->shutdown_wait); + BNX2FC_HBA_DBG(lport, "indicate_netevent " + "num_ofld_sess = %d\n", + hba->num_ofld_sess); + hba->wait_for_link_down = 1; + BNX2FC_HBA_DBG(lport, "waiting for uploads to " + "compl proc = %s\n", + current->comm); + wait_event_interruptible(hba->shutdown_wait, + (hba->num_ofld_sess == 0)); + BNX2FC_HBA_DBG(lport, "wakeup - num_ofld_sess = %d\n", + hba->num_ofld_sess); + hba->wait_for_link_down = 0; + + if (signal_pending(current)) + flush_signals(current); + } + } +} + +static int bnx2fc_libfc_config(struct fc_lport *lport) +{ + + /* Set the function pointers set by bnx2fc driver */ + memcpy(&lport->tt, &bnx2fc_libfc_fcn_templ, + sizeof(struct libfc_function_template)); + fc_elsct_init(lport); + fc_exch_init(lport); + fc_rport_init(lport); + fc_disc_init(lport); + return 0; +} + +static int bnx2fc_em_config(struct fc_lport *lport) +{ + struct fcoe_port *port = lport_priv(lport); + struct bnx2fc_hba *hba = port->priv; + + if (!fc_exch_mgr_alloc(lport, FC_CLASS_3, FCOE_MIN_XID, + FCOE_MAX_XID, NULL)) { + printk(KERN_ERR PFX "em_config:fc_exch_mgr_alloc failed\n"); + return -ENOMEM; + } + + hba->cmd_mgr = bnx2fc_cmd_mgr_alloc(hba, BNX2FC_MIN_XID, + BNX2FC_MAX_XID); + + if (!hba->cmd_mgr) { + printk(KERN_ERR PFX "em_config:bnx2fc_cmd_mgr_alloc failed\n"); + fc_exch_mgr_free(lport); + return -ENOMEM; + } + return 0; +} + +static int bnx2fc_lport_config(struct fc_lport *lport) +{ + lport->link_up = 0; + lport->qfull = 0; + lport->max_retry_count = 3; + lport->max_rport_retry_count = 3; + lport->e_d_tov = 2 * 1000; + lport->r_a_tov = 10 * 1000; + + /* REVISIT: enable when supporting tape devices + lport->service_params = (FCP_SPPF_INIT_FCN | FCP_SPPF_RD_XRDY_DIS | + FCP_SPPF_RETRY | FCP_SPPF_CONF_COMPL); + */ + lport->service_params = (FCP_SPPF_INIT_FCN | FCP_SPPF_RD_XRDY_DIS); + lport->does_npiv = 1; + + memset(&lport->rnid_gen, 0, sizeof(struct fc_els_rnid_gen)); + lport->rnid_gen.rnid_atype = BNX2FC_RNID_HBA; + + /* alloc stats structure */ + if (fc_lport_init_stats(lport)) + return -ENOMEM; + + /* Finish fc_lport configuration */ + fc_lport_config(lport); + + return 0; +} + +/** + * bnx2fc_fip_recv - handle a received FIP frame. + * + * @skb: the received skb + * @dev: associated &net_device + * @ptype: the &packet_type structure which was used to register this handler. + * @orig_dev: original receive &net_device, in case @ dev is a bond. + * + * Returns: 0 for success + */ +static int bnx2fc_fip_recv(struct sk_buff *skb, struct net_device *dev, + struct packet_type *ptype, + struct net_device *orig_dev) +{ + struct bnx2fc_hba *hba; + hba = container_of(ptype, struct bnx2fc_hba, fip_packet_type); + fcoe_ctlr_recv(&hba->ctlr, skb); + return 0; +} + +/** + * bnx2fc_update_src_mac - Update Ethernet MAC filters. + * + * @fip: FCoE controller. + * @old: Unicast MAC address to delete if the MAC is non-zero. + * @new: Unicast MAC address to add. + * + * Remove any previously-set unicast MAC filter. + * Add secondary FCoE MAC address filter for our OUI. + */ +static void bnx2fc_update_src_mac(struct fc_lport *lport, u8 *addr) +{ + struct fcoe_port *port = lport_priv(lport); + + memcpy(port->data_src_addr, addr, ETH_ALEN); +} + +/** + * bnx2fc_get_src_mac - return the ethernet source address for an lport + * + * @lport: libfc port + */ +static u8 *bnx2fc_get_src_mac(struct fc_lport *lport) +{ + struct fcoe_port *port; + + port = (struct fcoe_port *)lport_priv(lport); + return port->data_src_addr; +} + +/** + * bnx2fc_fip_send - send an Ethernet-encapsulated FIP frame. + * + * @fip: FCoE controller. + * @skb: FIP Packet. + */ +static void bnx2fc_fip_send(struct fcoe_ctlr *fip, struct sk_buff *skb) +{ + skb->dev = bnx2fc_from_ctlr(fip)->netdev; + dev_queue_xmit(skb); +} + +static int bnx2fc_vport_create(struct fc_vport *vport, bool disabled) +{ + struct Scsi_Host *shost = vport_to_shost(vport); + struct fc_lport *n_port = shost_priv(shost); + struct fcoe_port *port = lport_priv(n_port); + struct bnx2fc_hba *hba = port->priv; + struct net_device *netdev = hba->netdev; + struct fc_lport *vn_port; + + if (!test_bit(BNX2FC_FW_INIT_DONE, &hba->init_done)) { + printk(KERN_ERR PFX "vn ports cannot be created on" + "this hba\n"); + return -EIO; + } + mutex_lock(&bnx2fc_dev_lock); + vn_port = bnx2fc_if_create(hba, &vport->dev, 1); + mutex_unlock(&bnx2fc_dev_lock); + + if (IS_ERR(vn_port)) { + printk(KERN_ERR PFX "bnx2fc_vport_create (%s) failed\n", + netdev->name); + return -EIO; + } + + if (disabled) { + fc_vport_set_state(vport, FC_VPORT_DISABLED); + } else { + vn_port->boot_time = jiffies; + fc_lport_init(vn_port); + fc_fabric_login(vn_port); + fc_vport_setlink(vn_port); + } + return 0; +} + +static int bnx2fc_vport_destroy(struct fc_vport *vport) +{ + struct Scsi_Host *shost = vport_to_shost(vport); + struct fc_lport *n_port = shost_priv(shost); + struct fc_lport *vn_port = vport->dd_data; + struct fcoe_port *port = lport_priv(vn_port); + + mutex_lock(&n_port->lp_mutex); + list_del(&vn_port->list); + mutex_unlock(&n_port->lp_mutex); + queue_work(bnx2fc_wq, &port->destroy_work); + return 0; +} + +static int bnx2fc_vport_disable(struct fc_vport *vport, bool disable) +{ + struct fc_lport *lport = vport->dd_data; + + if (disable) { + fc_vport_set_state(vport, FC_VPORT_DISABLED); + fc_fabric_logoff(lport); + } else { + lport->boot_time = jiffies; + fc_fabric_login(lport); + fc_vport_setlink(lport); + } + return 0; +} + + +static int bnx2fc_netdev_setup(struct bnx2fc_hba *hba) +{ + struct net_device *netdev = hba->netdev; + struct net_device *physdev = hba->phys_dev; + struct netdev_hw_addr *ha; + int sel_san_mac = 0; + + /* setup Source MAC Address */ + rcu_read_lock(); + for_each_dev_addr(physdev, ha) { + BNX2FC_MISC_DBG("net_config: ha->type = %d, fip_mac = ", + ha->type); + printk(KERN_INFO "%2x:%2x:%2x:%2x:%2x:%2x\n", ha->addr[0], + ha->addr[1], ha->addr[2], ha->addr[3], + ha->addr[4], ha->addr[5]); + + if ((ha->type == NETDEV_HW_ADDR_T_SAN) && + (is_valid_ether_addr(ha->addr))) { + memcpy(hba->ctlr.ctl_src_addr, ha->addr, ETH_ALEN); + sel_san_mac = 1; + BNX2FC_MISC_DBG("Found SAN MAC\n"); + } + } + rcu_read_unlock(); + + if (!sel_san_mac) + return -ENODEV; + + hba->fip_packet_type.func = bnx2fc_fip_recv; + hba->fip_packet_type.type = htons(ETH_P_FIP); + hba->fip_packet_type.dev = netdev; + dev_add_pack(&hba->fip_packet_type); + + hba->fcoe_packet_type.func = bnx2fc_rcv; + hba->fcoe_packet_type.type = __constant_htons(ETH_P_FCOE); + hba->fcoe_packet_type.dev = netdev; + dev_add_pack(&hba->fcoe_packet_type); + + return 0; +} + +static int bnx2fc_attach_transport(void) +{ + bnx2fc_transport_template = + fc_attach_transport(&bnx2fc_transport_function); + + if (bnx2fc_transport_template == NULL) { + printk(KERN_ERR PFX "Failed to attach FC transport\n"); + return -ENODEV; + } + + bnx2fc_vport_xport_template = + fc_attach_transport(&bnx2fc_vport_xport_function); + if (bnx2fc_vport_xport_template == NULL) { + printk(KERN_ERR PFX + "Failed to attach FC transport for vport\n"); + fc_release_transport(bnx2fc_transport_template); + bnx2fc_transport_template = NULL; + return -ENODEV; + } + return 0; +} +static void bnx2fc_release_transport(void) +{ + fc_release_transport(bnx2fc_transport_template); + fc_release_transport(bnx2fc_vport_xport_template); + bnx2fc_transport_template = NULL; + bnx2fc_vport_xport_template = NULL; +} + +static void bnx2fc_interface_release(struct kref *kref) +{ + struct bnx2fc_hba *hba; + struct net_device *netdev; + struct net_device *phys_dev; + + hba = container_of(kref, struct bnx2fc_hba, kref); + BNX2FC_HBA_DBG(hba->ctlr.lp, "Interface is being released\n"); + + netdev = hba->netdev; + phys_dev = hba->phys_dev; + + /* tear-down FIP controller */ + if (test_and_clear_bit(BNX2FC_CTLR_INIT_DONE, &hba->init_done)) + fcoe_ctlr_destroy(&hba->ctlr); + + /* Free the command manager */ + if (hba->cmd_mgr) { + bnx2fc_cmd_mgr_free(hba->cmd_mgr); + hba->cmd_mgr = NULL; + } + dev_put(netdev); + module_put(THIS_MODULE); +} + +static inline void bnx2fc_interface_get(struct bnx2fc_hba *hba) +{ + kref_get(&hba->kref); +} + +static inline void bnx2fc_interface_put(struct bnx2fc_hba *hba) +{ + kref_put(&hba->kref, bnx2fc_interface_release); +} +static void bnx2fc_interface_destroy(struct bnx2fc_hba *hba) +{ + bnx2fc_unbind_pcidev(hba); + kfree(hba); +} + +/** + * bnx2fc_interface_create - create a new fcoe instance + * + * @cnic: pointer to cnic device + * + * Creates a new FCoE instance on the given device which include allocating + * hba structure, scsi_host and lport structures. + */ +static struct bnx2fc_hba *bnx2fc_interface_create(struct cnic_dev *cnic) +{ + struct bnx2fc_hba *hba; + int rc; + + hba = kzalloc(sizeof(*hba), GFP_KERNEL); + if (!hba) { + printk(KERN_ERR PFX "Unable to allocate hba structure\n"); + return NULL; + } + spin_lock_init(&hba->hba_lock); + mutex_init(&hba->hba_mutex); + + hba->cnic = cnic; + rc = bnx2fc_bind_pcidev(hba); + if (rc) + goto bind_err; + hba->phys_dev = cnic->netdev; + /* will get overwritten after we do vlan discovery */ + hba->netdev = hba->phys_dev; + + init_waitqueue_head(&hba->shutdown_wait); + init_waitqueue_head(&hba->destroy_wait); + + return hba; +bind_err: + printk(KERN_ERR PFX "create_interface: bind error\n"); + kfree(hba); + return NULL; +} + +static int bnx2fc_interface_setup(struct bnx2fc_hba *hba, + enum fip_state fip_mode) +{ + int rc = 0; + struct net_device *netdev = hba->netdev; + struct fcoe_ctlr *fip = &hba->ctlr; + + dev_hold(netdev); + kref_init(&hba->kref); + + hba->flags = 0; + + /* Initialize FIP */ + memset(fip, 0, sizeof(*fip)); + fcoe_ctlr_init(fip, fip_mode); + hba->ctlr.send = bnx2fc_fip_send; + hba->ctlr.update_mac = bnx2fc_update_src_mac; + hba->ctlr.get_src_addr = bnx2fc_get_src_mac; + set_bit(BNX2FC_CTLR_INIT_DONE, &hba->init_done); + + rc = bnx2fc_netdev_setup(hba); + if (rc) + goto setup_err; + + hba->next_conn_id = 0; + + memset(hba->tgt_ofld_list, 0, sizeof(hba->tgt_ofld_list)); + hba->num_ofld_sess = 0; + + return 0; + +setup_err: + fcoe_ctlr_destroy(&hba->ctlr); + dev_put(netdev); + bnx2fc_interface_put(hba); + return rc; +} + +/** + * bnx2fc_if_create - Create FCoE instance on a given interface + * + * @hba: FCoE interface to create a local port on + * @parent: Device pointer to be the parent in sysfs for the SCSI host + * @npiv: Indicates if the port is vport or not + * + * Creates a fc_lport instance and a Scsi_Host instance and configure them. + * + * Returns: Allocated fc_lport or an error pointer + */ +static struct fc_lport *bnx2fc_if_create(struct bnx2fc_hba *hba, + struct device *parent, int npiv) +{ + struct fc_lport *lport = NULL; + struct fcoe_port *port; + struct Scsi_Host *shost; + struct fc_vport *vport = dev_to_vport(parent); + int rc = 0; + + /* Allocate Scsi_Host structure */ + if (!npiv) { + lport = libfc_host_alloc(&bnx2fc_shost_template, + sizeof(struct fcoe_port)); + } else { + lport = libfc_vport_create(vport, + sizeof(struct fcoe_port)); + } + + if (!lport) { + printk(KERN_ERR PFX "could not allocate scsi host structure\n"); + return NULL; + } + shost = lport->host; + port = lport_priv(lport); + port->lport = lport; + port->priv = hba; + INIT_WORK(&port->destroy_work, bnx2fc_destroy_work); + + /* Configure fcoe_port */ + rc = bnx2fc_lport_config(lport); + if (rc) + goto lp_config_err; + + if (npiv) { + vport = dev_to_vport(parent); + printk(KERN_ERR PFX "Setting vport names, 0x%llX 0x%llX\n", + vport->node_name, vport->port_name); + fc_set_wwnn(lport, vport->node_name); + fc_set_wwpn(lport, vport->port_name); + } + /* Configure netdev and networking properties of the lport */ + rc = bnx2fc_net_config(lport); + if (rc) { + printk(KERN_ERR PFX "Error on bnx2fc_net_config\n"); + goto lp_config_err; + } + + rc = bnx2fc_shost_config(lport, parent); + if (rc) { + printk(KERN_ERR PFX "Couldnt configure shost for %s\n", + hba->netdev->name); + goto lp_config_err; + } + + /* Initialize the libfc library */ + rc = bnx2fc_libfc_config(lport); + if (rc) { + printk(KERN_ERR PFX "Couldnt configure libfc\n"); + goto shost_err; + } + fc_host_port_type(lport->host) = FC_PORTTYPE_UNKNOWN; + + /* Allocate exchange manager */ + if (!npiv) { + rc = bnx2fc_em_config(lport); + if (rc) { + printk(KERN_ERR PFX "Error on bnx2fc_em_config\n"); + goto shost_err; + } + } + + bnx2fc_interface_get(hba); + return lport; + +shost_err: + scsi_remove_host(shost); +lp_config_err: + scsi_host_put(lport->host); + return NULL; +} + +static void bnx2fc_netdev_cleanup(struct bnx2fc_hba *hba) +{ + /* Dont listen for Ethernet packets anymore */ + __dev_remove_pack(&hba->fcoe_packet_type); + __dev_remove_pack(&hba->fip_packet_type); + synchronize_net(); +} + +static void bnx2fc_if_destroy(struct fc_lport *lport) +{ + struct fcoe_port *port = lport_priv(lport); + struct bnx2fc_hba *hba = port->priv; + + BNX2FC_HBA_DBG(hba->ctlr.lp, "ENTERED bnx2fc_if_destroy\n"); + /* Stop the transmit retry timer */ + del_timer_sync(&port->timer); + + /* Free existing transmit skbs */ + fcoe_clean_pending_queue(lport); + + bnx2fc_interface_put(hba); + + /* Free queued packets for the receive thread */ + bnx2fc_clean_rx_queue(lport); + + /* Detach from scsi-ml */ + fc_remove_host(lport->host); + scsi_remove_host(lport->host); + + /* + * Note that only the physical lport will have the exchange manager. + * for vports, this function is NOP + */ + fc_exch_mgr_free(lport); + + /* Free memory used by statistical counters */ + fc_lport_free_stats(lport); + + /* Release Scsi_Host */ + scsi_host_put(lport->host); +} + +/** + * bnx2fc_destroy - Destroy a bnx2fc FCoE interface + * + * @buffer: The name of the Ethernet interface to be destroyed + * @kp: The associated kernel parameter + * + * Called from sysfs. + * + * Returns: 0 for success + */ +static int bnx2fc_destroy(struct net_device *netdev) +{ + struct bnx2fc_hba *hba = NULL; + struct net_device *phys_dev; + int rc = 0; + + rtnl_lock(); + + mutex_lock(&bnx2fc_dev_lock); + /* obtain physical netdev */ + if (netdev->priv_flags & IFF_802_1Q_VLAN) + phys_dev = vlan_dev_real_dev(netdev); + else { + printk(KERN_ERR PFX "Not a vlan device\n"); + rc = -ENODEV; + goto netdev_err; + } + + hba = bnx2fc_hba_lookup(phys_dev); + if (!hba || !hba->ctlr.lp) { + rc = -ENODEV; + printk(KERN_ERR PFX "bnx2fc_destroy: hba or lport not found\n"); + goto netdev_err; + } + + if (!test_bit(BNX2FC_CREATE_DONE, &hba->init_done)) { + printk(KERN_ERR PFX "bnx2fc_destroy: Create not called\n"); + goto netdev_err; + } + + bnx2fc_netdev_cleanup(hba); + + bnx2fc_stop(hba); + + bnx2fc_if_destroy(hba->ctlr.lp); + + destroy_workqueue(hba->timer_work_queue); + + if (test_bit(BNX2FC_FW_INIT_DONE, &hba->init_done)) + bnx2fc_fw_destroy(hba); + + clear_bit(BNX2FC_CREATE_DONE, &hba->init_done); +netdev_err: + mutex_unlock(&bnx2fc_dev_lock); + rtnl_unlock(); + return rc; +} + +static void bnx2fc_destroy_work(struct work_struct *work) +{ + struct fcoe_port *port; + struct fc_lport *lport; + + port = container_of(work, struct fcoe_port, destroy_work); + lport = port->lport; + + BNX2FC_HBA_DBG(lport, "Entered bnx2fc_destroy_work\n"); + + bnx2fc_port_shutdown(lport); + rtnl_lock(); + mutex_lock(&bnx2fc_dev_lock); + bnx2fc_if_destroy(lport); + mutex_unlock(&bnx2fc_dev_lock); + rtnl_unlock(); +} + +static void bnx2fc_unbind_adapter_devices(struct bnx2fc_hba *hba) +{ + bnx2fc_free_fw_resc(hba); + bnx2fc_free_task_ctx(hba); +} + +/** + * bnx2fc_bind_adapter_devices - binds bnx2fc adapter with the associated + * pci structure + * + * @hba: Adapter instance + */ +static int bnx2fc_bind_adapter_devices(struct bnx2fc_hba *hba) +{ + if (bnx2fc_setup_task_ctx(hba)) + goto mem_err; + + if (bnx2fc_setup_fw_resc(hba)) + goto mem_err; + + return 0; +mem_err: + bnx2fc_unbind_adapter_devices(hba); + return -ENOMEM; +} + +static int bnx2fc_bind_pcidev(struct bnx2fc_hba *hba) +{ + struct cnic_dev *cnic; + + if (!hba->cnic) { + printk(KERN_ERR PFX "cnic is NULL\n"); + return -ENODEV; + } + cnic = hba->cnic; + hba->pcidev = cnic->pcidev; + if (hba->pcidev) + pci_dev_get(hba->pcidev); + + return 0; +} + +static void bnx2fc_unbind_pcidev(struct bnx2fc_hba *hba) +{ + if (hba->pcidev) + pci_dev_put(hba->pcidev); + hba->pcidev = NULL; +} + + + +/** + * bnx2fc_ulp_start - cnic callback to initialize & start adapter instance + * + * @handle: transport handle pointing to adapter struture + * + * This function maps adapter structure to pcidev structure and initiates + * firmware handshake to enable/initialize on-chip FCoE components. + * This bnx2fc - cnic interface api callback is used after following + * conditions are met - + * a) underlying network interface is up (marked by event NETDEV_UP + * from netdev + * b) bnx2fc adatper structure is registered. + */ +static void bnx2fc_ulp_start(void *handle) +{ + struct bnx2fc_hba *hba = handle; + struct fc_lport *lport = hba->ctlr.lp; + + BNX2FC_MISC_DBG("Entered %s\n", __func__); + mutex_lock(&bnx2fc_dev_lock); + + if (test_bit(BNX2FC_FW_INIT_DONE, &hba->init_done)) + goto start_disc; + + if (test_bit(BNX2FC_CREATE_DONE, &hba->init_done)) + bnx2fc_fw_init(hba); + +start_disc: + mutex_unlock(&bnx2fc_dev_lock); + + BNX2FC_MISC_DBG("bnx2fc started.\n"); + + /* Kick off Fabric discovery*/ + if (test_bit(BNX2FC_CREATE_DONE, &hba->init_done)) { + printk(KERN_ERR PFX "ulp_init: start discovery\n"); + lport->tt.frame_send = bnx2fc_xmit; + bnx2fc_start_disc(hba); + } +} + +static void bnx2fc_port_shutdown(struct fc_lport *lport) +{ + BNX2FC_MISC_DBG("Entered %s\n", __func__); + fc_fabric_logoff(lport); + fc_lport_destroy(lport); +} + +static void bnx2fc_stop(struct bnx2fc_hba *hba) +{ + struct fc_lport *lport; + struct fc_lport *vport; + + BNX2FC_MISC_DBG("ENTERED %s - init_done = %ld\n", __func__, + hba->init_done); + if (test_bit(BNX2FC_FW_INIT_DONE, &hba->init_done) && + test_bit(BNX2FC_CREATE_DONE, &hba->init_done)) { + lport = hba->ctlr.lp; + bnx2fc_port_shutdown(lport); + BNX2FC_HBA_DBG(lport, "bnx2fc_stop: waiting for %d " + "offloaded sessions\n", + hba->num_ofld_sess); + wait_event_interruptible(hba->shutdown_wait, + (hba->num_ofld_sess == 0)); + mutex_lock(&lport->lp_mutex); + list_for_each_entry(vport, &lport->vports, list) + fc_host_port_type(vport->host) = FC_PORTTYPE_UNKNOWN; + mutex_unlock(&lport->lp_mutex); + fc_host_port_type(lport->host) = FC_PORTTYPE_UNKNOWN; + fcoe_ctlr_link_down(&hba->ctlr); + fcoe_clean_pending_queue(lport); + + mutex_lock(&hba->hba_mutex); + clear_bit(ADAPTER_STATE_UP, &hba->adapter_state); + clear_bit(ADAPTER_STATE_GOING_DOWN, &hba->adapter_state); + + clear_bit(ADAPTER_STATE_READY, &hba->adapter_state); + mutex_unlock(&hba->hba_mutex); + } +} + +static int bnx2fc_fw_init(struct bnx2fc_hba *hba) +{ +#define BNX2FC_INIT_POLL_TIME (1000 / HZ) + int rc = -1; + int i = HZ; + + rc = bnx2fc_bind_adapter_devices(hba); + if (rc) { + printk(KERN_ALERT PFX + "bnx2fc_bind_adapter_devices failed - rc = %d\n", rc); + goto err_out; + } + + rc = bnx2fc_send_fw_fcoe_init_msg(hba); + if (rc) { + printk(KERN_ALERT PFX + "bnx2fc_send_fw_fcoe_init_msg failed - rc = %d\n", rc); + goto err_unbind; + } + + /* + * Wait until the adapter init message is complete, and adapter + * state is UP. + */ + while (!test_bit(ADAPTER_STATE_UP, &hba->adapter_state) && i--) + msleep(BNX2FC_INIT_POLL_TIME); + + if (!test_bit(ADAPTER_STATE_UP, &hba->adapter_state)) { + printk(KERN_ERR PFX "bnx2fc_start: %s failed to initialize. " + "Ignoring...\n", + hba->cnic->netdev->name); + rc = -1; + goto err_unbind; + } + + + /* Mark HBA to indicate that the FW INIT is done */ + set_bit(BNX2FC_FW_INIT_DONE, &hba->init_done); + return 0; + +err_unbind: + bnx2fc_unbind_adapter_devices(hba); +err_out: + return rc; +} + +static void bnx2fc_fw_destroy(struct bnx2fc_hba *hba) +{ + if (test_and_clear_bit(BNX2FC_FW_INIT_DONE, &hba->init_done)) { + if (bnx2fc_send_fw_fcoe_destroy_msg(hba) == 0) { + init_timer(&hba->destroy_timer); + hba->destroy_timer.expires = BNX2FC_FW_TIMEOUT + + jiffies; + hba->destroy_timer.function = bnx2fc_destroy_timer; + hba->destroy_timer.data = (unsigned long)hba; + add_timer(&hba->destroy_timer); + wait_event_interruptible(hba->destroy_wait, + (hba->flags & + BNX2FC_FLAG_DESTROY_CMPL)); + /* This should never happen */ + if (signal_pending(current)) + flush_signals(current); + + del_timer_sync(&hba->destroy_timer); + } + bnx2fc_unbind_adapter_devices(hba); + } +} + +/** + * bnx2fc_ulp_stop - cnic callback to shutdown adapter instance + * + * @handle: transport handle pointing to adapter structure + * + * Driver checks if adapter is already in shutdown mode, if not start + * the shutdown process. + */ +static void bnx2fc_ulp_stop(void *handle) +{ + struct bnx2fc_hba *hba = (struct bnx2fc_hba *)handle; + + printk(KERN_ERR "ULP_STOP\n"); + + mutex_lock(&bnx2fc_dev_lock); + bnx2fc_stop(hba); + bnx2fc_fw_destroy(hba); + mutex_unlock(&bnx2fc_dev_lock); +} + +static void bnx2fc_start_disc(struct bnx2fc_hba *hba) +{ + struct fc_lport *lport; + int wait_cnt = 0; + + BNX2FC_MISC_DBG("Entered %s\n", __func__); + /* Kick off FIP/FLOGI */ + if (!test_bit(BNX2FC_FW_INIT_DONE, &hba->init_done)) { + printk(KERN_ERR PFX "Init not done yet\n"); + return; + } + + lport = hba->ctlr.lp; + BNX2FC_HBA_DBG(lport, "calling fc_fabric_login\n"); + + if (!bnx2fc_link_ok(lport)) { + BNX2FC_HBA_DBG(lport, "ctlr_link_up\n"); + fcoe_ctlr_link_up(&hba->ctlr); + fc_host_port_type(lport->host) = FC_PORTTYPE_NPORT; + set_bit(ADAPTER_STATE_READY, &hba->adapter_state); + } + + /* wait for the FCF to be selected before issuing FLOGI */ + while (!hba->ctlr.sel_fcf) { + msleep(250); + /* give up after 3 secs */ + if (++wait_cnt > 12) + break; + } + fc_lport_init(lport); + fc_fabric_login(lport); +} + + +/** + * bnx2fc_ulp_init - Initialize an adapter instance + * + * @dev : cnic device handle + * Called from cnic_register_driver() context to initialize all + * enumerated cnic devices. This routine allocates adapter structure + * and other device specific resources. + */ +static void bnx2fc_ulp_init(struct cnic_dev *dev) +{ + struct bnx2fc_hba *hba; + int rc = 0; + + BNX2FC_MISC_DBG("Entered %s\n", __func__); + /* bnx2fc works only when bnx2x is loaded */ + if (!test_bit(CNIC_F_BNX2X_CLASS, &dev->flags)) { + printk(KERN_ERR PFX "bnx2fc FCoE not supported on %s," + " flags: %lx\n", + dev->netdev->name, dev->flags); + return; + } + + /* Configure FCoE interface */ + hba = bnx2fc_interface_create(dev); + if (!hba) { + printk(KERN_ERR PFX "hba initialization failed\n"); + return; + } + + /* Add HBA to the adapter list */ + mutex_lock(&bnx2fc_dev_lock); + list_add_tail(&hba->link, &adapter_list); + adapter_count++; + mutex_unlock(&bnx2fc_dev_lock); + + clear_bit(BNX2FC_CNIC_REGISTERED, &hba->reg_with_cnic); + rc = dev->register_device(dev, CNIC_ULP_FCOE, + (void *) hba); + if (rc) + printk(KERN_ALERT PFX "register_device failed, rc = %d\n", rc); + else + set_bit(BNX2FC_CNIC_REGISTERED, &hba->reg_with_cnic); +} + + +static int bnx2fc_disable(struct net_device *netdev) +{ + struct bnx2fc_hba *hba; + struct net_device *phys_dev; + struct ethtool_drvinfo drvinfo; + int rc = 0; + + rtnl_lock(); + + mutex_lock(&bnx2fc_dev_lock); + + /* obtain physical netdev */ + if (netdev->priv_flags & IFF_802_1Q_VLAN) + phys_dev = vlan_dev_real_dev(netdev); + else { + printk(KERN_ERR PFX "Not a vlan device\n"); + rc = -ENODEV; + goto nodev; + } + + /* verify if the physical device is a netxtreme2 device */ + if (phys_dev->ethtool_ops && phys_dev->ethtool_ops->get_drvinfo) { + memset(&drvinfo, 0, sizeof(drvinfo)); + phys_dev->ethtool_ops->get_drvinfo(phys_dev, &drvinfo); + if (strcmp(drvinfo.driver, "bnx2x")) { + printk(KERN_ERR PFX "Not a netxtreme2 device\n"); + rc = -ENODEV; + goto nodev; + } + } else { + printk(KERN_ERR PFX "unable to obtain drv_info\n"); + rc = -ENODEV; + goto nodev; + } + + printk(KERN_ERR PFX "phys_dev is netxtreme2 device\n"); + + /* obtain hba and initialize rest of the structure */ + hba = bnx2fc_hba_lookup(phys_dev); + if (!hba || !hba->ctlr.lp) { + rc = -ENODEV; + printk(KERN_ERR PFX "bnx2fc_disable: hba or lport not found\n"); + } else { + fcoe_ctlr_link_down(&hba->ctlr); + fcoe_clean_pending_queue(hba->ctlr.lp); + } + +nodev: + mutex_unlock(&bnx2fc_dev_lock); + rtnl_unlock(); + return rc; +} + + +static int bnx2fc_enable(struct net_device *netdev) +{ + struct bnx2fc_hba *hba; + struct net_device *phys_dev; + struct ethtool_drvinfo drvinfo; + int rc = 0; + + rtnl_lock(); + + BNX2FC_MISC_DBG("Entered %s\n", __func__); + mutex_lock(&bnx2fc_dev_lock); + + /* obtain physical netdev */ + if (netdev->priv_flags & IFF_802_1Q_VLAN) + phys_dev = vlan_dev_real_dev(netdev); + else { + printk(KERN_ERR PFX "Not a vlan device\n"); + rc = -ENODEV; + goto nodev; + } + /* verify if the physical device is a netxtreme2 device */ + if (phys_dev->ethtool_ops && phys_dev->ethtool_ops->get_drvinfo) { + memset(&drvinfo, 0, sizeof(drvinfo)); + phys_dev->ethtool_ops->get_drvinfo(phys_dev, &drvinfo); + if (strcmp(drvinfo.driver, "bnx2x")) { + printk(KERN_ERR PFX "Not a netxtreme2 device\n"); + rc = -ENODEV; + goto nodev; + } + } else { + printk(KERN_ERR PFX "unable to obtain drv_info\n"); + rc = -ENODEV; + goto nodev; + } + + /* obtain hba and initialize rest of the structure */ + hba = bnx2fc_hba_lookup(phys_dev); + if (!hba || !hba->ctlr.lp) { + rc = -ENODEV; + printk(KERN_ERR PFX "bnx2fc_enable: hba or lport not found\n"); + } else if (!bnx2fc_link_ok(hba->ctlr.lp)) + fcoe_ctlr_link_up(&hba->ctlr); + +nodev: + mutex_unlock(&bnx2fc_dev_lock); + rtnl_unlock(); + return rc; +} + +/** + * bnx2fc_create - Create bnx2fc FCoE interface + * + * @buffer: The name of Ethernet interface to create on + * @kp: The associated kernel param + * + * Called from sysfs. + * + * Returns: 0 for success + */ +static int bnx2fc_create(struct net_device *netdev, enum fip_state fip_mode) +{ + struct bnx2fc_hba *hba; + struct net_device *phys_dev; + struct fc_lport *lport; + struct ethtool_drvinfo drvinfo; + int rc = 0; + int vlan_id; + + BNX2FC_MISC_DBG("Entered bnx2fc_create\n"); + if (fip_mode != FIP_MODE_FABRIC) { + printk(KERN_ERR "fip mode not FABRIC\n"); + return -EIO; + } + + rtnl_lock(); + + mutex_lock(&bnx2fc_dev_lock); + + if (!try_module_get(THIS_MODULE)) { + rc = -EINVAL; + goto mod_err; + } + + /* obtain physical netdev */ + if (netdev->priv_flags & IFF_802_1Q_VLAN) { + phys_dev = vlan_dev_real_dev(netdev); + vlan_id = vlan_dev_vlan_id(netdev); + } else { + printk(KERN_ERR PFX "Not a vlan device\n"); + rc = -EINVAL; + goto netdev_err; + } + /* verify if the physical device is a netxtreme2 device */ + if (phys_dev->ethtool_ops && phys_dev->ethtool_ops->get_drvinfo) { + memset(&drvinfo, 0, sizeof(drvinfo)); + phys_dev->ethtool_ops->get_drvinfo(phys_dev, &drvinfo); + if (strcmp(drvinfo.driver, "bnx2x")) { + printk(KERN_ERR PFX "Not a netxtreme2 device\n"); + rc = -EINVAL; + goto netdev_err; + } + } else { + printk(KERN_ERR PFX "unable to obtain drv_info\n"); + rc = -EINVAL; + goto netdev_err; + } + + /* obtain hba and initialize rest of the structure */ + hba = bnx2fc_hba_lookup(phys_dev); + if (!hba) { + rc = -ENODEV; + printk(KERN_ERR PFX "bnx2fc_create: hba not found\n"); + goto netdev_err; + } + + if (!test_bit(BNX2FC_FW_INIT_DONE, &hba->init_done)) { + rc = bnx2fc_fw_init(hba); + if (rc) + goto netdev_err; + } + + if (test_bit(BNX2FC_CREATE_DONE, &hba->init_done)) { + rc = -EEXIST; + goto netdev_err; + } + + /* update netdev with vlan netdev */ + hba->netdev = netdev; + hba->vlan_id = vlan_id; + hba->vlan_enabled = 1; + + rc = bnx2fc_interface_setup(hba, fip_mode); + if (rc) { + printk(KERN_ERR PFX "bnx2fc_interface_setup failed\n"); + goto ifput_err; + } + + hba->timer_work_queue = + create_singlethread_workqueue("bnx2fc_timer_wq"); + if (!hba->timer_work_queue) { + printk(KERN_ERR PFX "ulp_init could not create timer_wq\n"); + rc = -EINVAL; + goto ifput_err; + } + + lport = bnx2fc_if_create(hba, &hba->pcidev->dev, 0); + if (!lport) { + printk(KERN_ERR PFX "Failed to create interface (%s)\n", + netdev->name); + bnx2fc_netdev_cleanup(hba); + rc = -EINVAL; + goto if_create_err; + } + + lport->boot_time = jiffies; + + /* Make this master N_port */ + hba->ctlr.lp = lport; + + set_bit(BNX2FC_CREATE_DONE, &hba->init_done); + printk(KERN_ERR PFX "create: START DISC\n"); + bnx2fc_start_disc(hba); + /* + * Release from kref_init in bnx2fc_interface_setup, on success + * lport should be holding a reference taken in bnx2fc_if_create + */ + bnx2fc_interface_put(hba); + /* put netdev that was held while calling dev_get_by_name */ + mutex_unlock(&bnx2fc_dev_lock); + rtnl_unlock(); + return 0; + +if_create_err: + destroy_workqueue(hba->timer_work_queue); +ifput_err: + bnx2fc_interface_put(hba); +netdev_err: + module_put(THIS_MODULE); +mod_err: + mutex_unlock(&bnx2fc_dev_lock); + rtnl_unlock(); + return rc; +} + +/** + * bnx2fc_find_hba_for_cnic - maps cnic instance to bnx2fc adapter instance + * + * @cnic: Pointer to cnic device instance + * + **/ +static struct bnx2fc_hba *bnx2fc_find_hba_for_cnic(struct cnic_dev *cnic) +{ + struct list_head *list; + struct list_head *temp; + struct bnx2fc_hba *hba; + + /* Called with bnx2fc_dev_lock held */ + list_for_each_safe(list, temp, &adapter_list) { + hba = (struct bnx2fc_hba *)list; + if (hba->cnic == cnic) + return hba; + } + return NULL; +} + +static struct bnx2fc_hba *bnx2fc_hba_lookup(struct net_device *phys_dev) +{ + struct list_head *list; + struct list_head *temp; + struct bnx2fc_hba *hba; + + /* Called with bnx2fc_dev_lock held */ + list_for_each_safe(list, temp, &adapter_list) { + hba = (struct bnx2fc_hba *)list; + if (hba->phys_dev == phys_dev) + return hba; + } + printk(KERN_ERR PFX "hba_lookup: hba NULL\n"); + return NULL; +} + +/** + * bnx2fc_ulp_exit - shuts down adapter instance and frees all resources + * + * @dev cnic device handle + */ +static void bnx2fc_ulp_exit(struct cnic_dev *dev) +{ + struct bnx2fc_hba *hba; + + BNX2FC_MISC_DBG("Entered bnx2fc_ulp_exit\n"); + + if (!test_bit(CNIC_F_BNX2X_CLASS, &dev->flags)) { + printk(KERN_ERR PFX "bnx2fc port check: %s, flags: %lx\n", + dev->netdev->name, dev->flags); + return; + } + + mutex_lock(&bnx2fc_dev_lock); + hba = bnx2fc_find_hba_for_cnic(dev); + if (!hba) { + printk(KERN_ERR PFX "bnx2fc_ulp_exit: hba not found, dev 0%p\n", + dev); + mutex_unlock(&bnx2fc_dev_lock); + return; + } + + list_del_init(&hba->link); + adapter_count--; + + if (test_bit(BNX2FC_CREATE_DONE, &hba->init_done)) { + /* destroy not called yet, move to quiesced list */ + bnx2fc_netdev_cleanup(hba); + bnx2fc_if_destroy(hba->ctlr.lp); + } + mutex_unlock(&bnx2fc_dev_lock); + + bnx2fc_ulp_stop(hba); + /* unregister cnic device */ + if (test_and_clear_bit(BNX2FC_CNIC_REGISTERED, &hba->reg_with_cnic)) + hba->cnic->unregister_device(hba->cnic, CNIC_ULP_FCOE); + bnx2fc_interface_destroy(hba); +} + +/** + * bnx2fc_fcoe_reset - Resets the fcoe + * + * @shost: shost the reset is from + * + * Returns: always 0 + */ +static int bnx2fc_fcoe_reset(struct Scsi_Host *shost) +{ + struct fc_lport *lport = shost_priv(shost); + fc_lport_reset(lport); + return 0; +} + + +static bool bnx2fc_match(struct net_device *netdev) +{ + mutex_lock(&bnx2fc_dev_lock); + if (netdev->priv_flags & IFF_802_1Q_VLAN) { + struct net_device *phys_dev = vlan_dev_real_dev(netdev); + + if (bnx2fc_hba_lookup(phys_dev)) { + mutex_unlock(&bnx2fc_dev_lock); + return true; + } + } + mutex_unlock(&bnx2fc_dev_lock); + return false; +} + + +static struct fcoe_transport bnx2fc_transport = { + .name = {"bnx2fc"}, + .attached = false, + .list = LIST_HEAD_INIT(bnx2fc_transport.list), + .match = bnx2fc_match, + .create = bnx2fc_create, + .destroy = bnx2fc_destroy, + .enable = bnx2fc_enable, + .disable = bnx2fc_disable, +}; + +/** + * bnx2fc_percpu_thread_create - Create a receive thread for an + * online CPU + * + * @cpu: cpu index for the online cpu + */ +static void bnx2fc_percpu_thread_create(unsigned int cpu) +{ + struct bnx2fc_percpu_s *p; + struct task_struct *thread; + + p = &per_cpu(bnx2fc_percpu, cpu); + + thread = kthread_create(bnx2fc_percpu_io_thread, + (void *)p, + "bnx2fc_thread/%d", cpu); + /* bind thread to the cpu */ + if (likely(!IS_ERR(p->iothread))) { + kthread_bind(thread, cpu); + p->iothread = thread; + wake_up_process(thread); + } +} + +static void bnx2fc_percpu_thread_destroy(unsigned int cpu) +{ + struct bnx2fc_percpu_s *p; + struct task_struct *thread; + struct bnx2fc_work *work, *tmp; + LIST_HEAD(work_list); + + BNX2FC_MISC_DBG("destroying io thread for CPU %d\n", cpu); + + /* Prevent any new work from being queued for this CPU */ + p = &per_cpu(bnx2fc_percpu, cpu); + spin_lock_bh(&p->fp_work_lock); + thread = p->iothread; + p->iothread = NULL; + + + /* Free all work in the list */ + list_for_each_entry_safe(work, tmp, &work_list, list) { + list_del_init(&work->list); + bnx2fc_process_cq_compl(work->tgt, work->wqe); + kfree(work); + } + + spin_unlock_bh(&p->fp_work_lock); + + if (thread) + kthread_stop(thread); +} + +/** + * bnx2fc_cpu_callback - Handler for CPU hotplug events + * + * @nfb: The callback data block + * @action: The event triggering the callback + * @hcpu: The index of the CPU that the event is for + * + * This creates or destroys per-CPU data for fcoe + * + * Returns NOTIFY_OK always. + */ +static int bnx2fc_cpu_callback(struct notifier_block *nfb, + unsigned long action, void *hcpu) +{ + unsigned cpu = (unsigned long)hcpu; + + switch (action) { + case CPU_ONLINE: + case CPU_ONLINE_FROZEN: + printk(PFX "CPU %x online: Create Rx thread\n", cpu); + bnx2fc_percpu_thread_create(cpu); + break; + case CPU_DEAD: + case CPU_DEAD_FROZEN: + printk(PFX "CPU %x offline: Remove Rx thread\n", cpu); + bnx2fc_percpu_thread_destroy(cpu); + break; + default: + break; + } + return NOTIFY_OK; +} + +/** + * bnx2fc_mod_init - module init entry point + * + * Initialize driver wide global data structures, and register + * with cnic module + **/ +static int __init bnx2fc_mod_init(void) +{ + struct fcoe_percpu_s *bg; + struct task_struct *l2_thread; + int rc = 0; + unsigned int cpu = 0; + struct bnx2fc_percpu_s *p; + + printk(KERN_INFO PFX "%s", version); + + /* register as a fcoe transport */ + rc = fcoe_transport_attach(&bnx2fc_transport); + if (rc) { + printk(KERN_ERR "failed to register an fcoe transport, check " + "if libfcoe is loaded\n"); + goto out; + } + + INIT_LIST_HEAD(&adapter_list); + mutex_init(&bnx2fc_dev_lock); + adapter_count = 0; + + /* Attach FC transport template */ + rc = bnx2fc_attach_transport(); + if (rc) + goto detach_ft; + + bnx2fc_wq = alloc_workqueue("bnx2fc", 0, 0); + if (!bnx2fc_wq) { + rc = -ENOMEM; + goto release_bt; + } + + bg = &bnx2fc_global; + skb_queue_head_init(&bg->fcoe_rx_list); + l2_thread = kthread_create(bnx2fc_l2_rcv_thread, + (void *)bg, + "bnx2fc_l2_thread"); + if (IS_ERR(l2_thread)) { + rc = PTR_ERR(l2_thread); + goto free_wq; + } + wake_up_process(l2_thread); + spin_lock_bh(&bg->fcoe_rx_list.lock); + bg->thread = l2_thread; + spin_unlock_bh(&bg->fcoe_rx_list.lock); + + for_each_possible_cpu(cpu) { + p = &per_cpu(bnx2fc_percpu, cpu); + INIT_LIST_HEAD(&p->work_list); + spin_lock_init(&p->fp_work_lock); + } + + for_each_online_cpu(cpu) { + bnx2fc_percpu_thread_create(cpu); + } + + /* Initialize per CPU interrupt thread */ + register_hotcpu_notifier(&bnx2fc_cpu_notifier); + + cnic_register_driver(CNIC_ULP_FCOE, &bnx2fc_cnic_cb); + + return 0; + +free_wq: + destroy_workqueue(bnx2fc_wq); +release_bt: + bnx2fc_release_transport(); +detach_ft: + fcoe_transport_detach(&bnx2fc_transport); +out: + return rc; +} + +static void __exit bnx2fc_mod_exit(void) +{ + LIST_HEAD(to_be_deleted); + struct bnx2fc_hba *hba, *next; + struct fcoe_percpu_s *bg; + struct task_struct *l2_thread; + struct sk_buff *skb; + unsigned int cpu = 0; + + /* + * NOTE: Since cnic calls register_driver routine rtnl_lock, + * it will have higher precedence than bnx2fc_dev_lock. + * unregister_device() cannot be called with bnx2fc_dev_lock + * held. + */ + mutex_lock(&bnx2fc_dev_lock); + list_splice(&adapter_list, &to_be_deleted); + INIT_LIST_HEAD(&adapter_list); + adapter_count = 0; + mutex_unlock(&bnx2fc_dev_lock); + + /* Unregister with cnic */ + list_for_each_entry_safe(hba, next, &to_be_deleted, link) { + list_del_init(&hba->link); + printk(KERN_ERR PFX "MOD_EXIT:destroy hba = 0x%p, kref = %d\n", + hba, atomic_read(&hba->kref.refcount)); + bnx2fc_ulp_stop(hba); + /* unregister cnic device */ + if (test_and_clear_bit(BNX2FC_CNIC_REGISTERED, + &hba->reg_with_cnic)) + hba->cnic->unregister_device(hba->cnic, CNIC_ULP_FCOE); + bnx2fc_interface_destroy(hba); + } + cnic_unregister_driver(CNIC_ULP_FCOE); + + /* Destroy global thread */ + bg = &bnx2fc_global; + spin_lock_bh(&bg->fcoe_rx_list.lock); + l2_thread = bg->thread; + bg->thread = NULL; + while ((skb = __skb_dequeue(&bg->fcoe_rx_list)) != NULL) + kfree_skb(skb); + + spin_unlock_bh(&bg->fcoe_rx_list.lock); + + if (l2_thread) + kthread_stop(l2_thread); + + unregister_hotcpu_notifier(&bnx2fc_cpu_notifier); + + /* Destroy per cpu threads */ + for_each_online_cpu(cpu) { + bnx2fc_percpu_thread_destroy(cpu); + } + + destroy_workqueue(bnx2fc_wq); + /* + * detach from scsi transport + * must happen after all destroys are done + */ + bnx2fc_release_transport(); + + /* detach from fcoe transport */ + fcoe_transport_detach(&bnx2fc_transport); +} + +module_init(bnx2fc_mod_init); +module_exit(bnx2fc_mod_exit); + +static struct fc_function_template bnx2fc_transport_function = { + .show_host_node_name = 1, + .show_host_port_name = 1, + .show_host_supported_classes = 1, + .show_host_supported_fc4s = 1, + .show_host_active_fc4s = 1, + .show_host_maxframe_size = 1, + + .show_host_port_id = 1, + .show_host_supported_speeds = 1, + .get_host_speed = fc_get_host_speed, + .show_host_speed = 1, + .show_host_port_type = 1, + .get_host_port_state = fc_get_host_port_state, + .show_host_port_state = 1, + .show_host_symbolic_name = 1, + + .dd_fcrport_size = (sizeof(struct fc_rport_libfc_priv) + + sizeof(struct bnx2fc_rport)), + .show_rport_maxframe_size = 1, + .show_rport_supported_classes = 1, + + .show_host_fabric_name = 1, + .show_starget_node_name = 1, + .show_starget_port_name = 1, + .show_starget_port_id = 1, + .set_rport_dev_loss_tmo = fc_set_rport_loss_tmo, + .show_rport_dev_loss_tmo = 1, + .get_fc_host_stats = bnx2fc_get_host_stats, + + .issue_fc_host_lip = bnx2fc_fcoe_reset, + + .terminate_rport_io = fc_rport_terminate_io, + + .vport_create = bnx2fc_vport_create, + .vport_delete = bnx2fc_vport_destroy, + .vport_disable = bnx2fc_vport_disable, +}; + +static struct fc_function_template bnx2fc_vport_xport_function = { + .show_host_node_name = 1, + .show_host_port_name = 1, + .show_host_supported_classes = 1, + .show_host_supported_fc4s = 1, + .show_host_active_fc4s = 1, + .show_host_maxframe_size = 1, + + .show_host_port_id = 1, + .show_host_supported_speeds = 1, + .get_host_speed = fc_get_host_speed, + .show_host_speed = 1, + .show_host_port_type = 1, + .get_host_port_state = fc_get_host_port_state, + .show_host_port_state = 1, + .show_host_symbolic_name = 1, + + .dd_fcrport_size = (sizeof(struct fc_rport_libfc_priv) + + sizeof(struct bnx2fc_rport)), + .show_rport_maxframe_size = 1, + .show_rport_supported_classes = 1, + + .show_host_fabric_name = 1, + .show_starget_node_name = 1, + .show_starget_port_name = 1, + .show_starget_port_id = 1, + .set_rport_dev_loss_tmo = fc_set_rport_loss_tmo, + .show_rport_dev_loss_tmo = 1, + .get_fc_host_stats = fc_get_host_stats, + .issue_fc_host_lip = bnx2fc_fcoe_reset, + .terminate_rport_io = fc_rport_terminate_io, +}; + +/** + * scsi_host_template structure used while registering with SCSI-ml + */ +static struct scsi_host_template bnx2fc_shost_template = { + .module = THIS_MODULE, + .name = "Broadcom Offload FCoE Initiator", + .queuecommand = bnx2fc_queuecommand, + .eh_abort_handler = bnx2fc_eh_abort, /* abts */ + .eh_device_reset_handler = bnx2fc_eh_device_reset, /* lun reset */ + .eh_target_reset_handler = bnx2fc_eh_target_reset, /* tgt reset */ + .eh_host_reset_handler = fc_eh_host_reset, + .slave_alloc = fc_slave_alloc, + .change_queue_depth = fc_change_queue_depth, + .change_queue_type = fc_change_queue_type, + .this_id = -1, + .cmd_per_lun = 3, + .can_queue = BNX2FC_CAN_QUEUE, + .use_clustering = ENABLE_CLUSTERING, + .sg_tablesize = BNX2FC_MAX_BDS_PER_CMD, + .max_sectors = 512, +}; + +static struct libfc_function_template bnx2fc_libfc_fcn_templ = { + .frame_send = bnx2fc_xmit, + .elsct_send = bnx2fc_elsct_send, + .fcp_abort_io = bnx2fc_abort_io, + .fcp_cleanup = bnx2fc_cleanup, + .rport_event_callback = bnx2fc_rport_event_handler, +}; + +/** + * bnx2fc_cnic_cb - global template of bnx2fc - cnic driver interface + * structure carrying callback function pointers + */ +static struct cnic_ulp_ops bnx2fc_cnic_cb = { + .owner = THIS_MODULE, + .cnic_init = bnx2fc_ulp_init, + .cnic_exit = bnx2fc_ulp_exit, + .cnic_start = bnx2fc_ulp_start, + .cnic_stop = bnx2fc_ulp_stop, + .indicate_kcqes = bnx2fc_indicate_kcqe, + .indicate_netevent = bnx2fc_indicate_netevent, +}; diff --git a/drivers/scsi/bnx2fc/bnx2fc_hwi.c b/drivers/scsi/bnx2fc/bnx2fc_hwi.c new file mode 100644 index 000000000000..1b680e288c56 --- /dev/null +++ b/drivers/scsi/bnx2fc/bnx2fc_hwi.c @@ -0,0 +1,1877 @@ +/* bnx2fc_hwi.c: Broadcom NetXtreme II Linux FCoE offload driver. + * This file contains the code that low level functions that interact + * with 57712 FCoE firmware. + * + * Copyright (c) 2008 - 2010 Broadcom Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation. + * + * Written by: Bhanu Prakash Gollapudi (bprakash@broadcom.com) + */ + +#include "bnx2fc.h" + +DECLARE_PER_CPU(struct bnx2fc_percpu_s, bnx2fc_percpu); + +static void bnx2fc_fastpath_notification(struct bnx2fc_hba *hba, + struct fcoe_kcqe *new_cqe_kcqe); +static void bnx2fc_process_ofld_cmpl(struct bnx2fc_hba *hba, + struct fcoe_kcqe *ofld_kcqe); +static void bnx2fc_process_enable_conn_cmpl(struct bnx2fc_hba *hba, + struct fcoe_kcqe *ofld_kcqe); +static void bnx2fc_init_failure(struct bnx2fc_hba *hba, u32 err_code); +static void bnx2fc_process_conn_destroy_cmpl(struct bnx2fc_hba *hba, + struct fcoe_kcqe *conn_destroy); + +int bnx2fc_send_stat_req(struct bnx2fc_hba *hba) +{ + struct fcoe_kwqe_stat stat_req; + struct kwqe *kwqe_arr[2]; + int num_kwqes = 1; + int rc = 0; + + memset(&stat_req, 0x00, sizeof(struct fcoe_kwqe_stat)); + stat_req.hdr.op_code = FCOE_KWQE_OPCODE_STAT; + stat_req.hdr.flags = + (FCOE_KWQE_LAYER_CODE << FCOE_KWQE_HEADER_LAYER_CODE_SHIFT); + + stat_req.stat_params_addr_lo = (u32) hba->stats_buf_dma; + stat_req.stat_params_addr_hi = (u32) ((u64)hba->stats_buf_dma >> 32); + + kwqe_arr[0] = (struct kwqe *) &stat_req; + + if (hba->cnic && hba->cnic->submit_kwqes) + rc = hba->cnic->submit_kwqes(hba->cnic, kwqe_arr, num_kwqes); + + return rc; +} + +/** + * bnx2fc_send_fw_fcoe_init_msg - initiates initial handshake with FCoE f/w + * + * @hba: adapter structure pointer + * + * Send down FCoE firmware init KWQEs which initiates the initial handshake + * with the f/w. + * + */ +int bnx2fc_send_fw_fcoe_init_msg(struct bnx2fc_hba *hba) +{ + struct fcoe_kwqe_init1 fcoe_init1; + struct fcoe_kwqe_init2 fcoe_init2; + struct fcoe_kwqe_init3 fcoe_init3; + struct kwqe *kwqe_arr[3]; + int num_kwqes = 3; + int rc = 0; + + if (!hba->cnic) { + printk(KERN_ALERT PFX "hba->cnic NULL during fcoe fw init\n"); + return -ENODEV; + } + + /* fill init1 KWQE */ + memset(&fcoe_init1, 0x00, sizeof(struct fcoe_kwqe_init1)); + fcoe_init1.hdr.op_code = FCOE_KWQE_OPCODE_INIT1; + fcoe_init1.hdr.flags = (FCOE_KWQE_LAYER_CODE << + FCOE_KWQE_HEADER_LAYER_CODE_SHIFT); + + fcoe_init1.num_tasks = BNX2FC_MAX_TASKS; + fcoe_init1.sq_num_wqes = BNX2FC_SQ_WQES_MAX; + fcoe_init1.rq_num_wqes = BNX2FC_RQ_WQES_MAX; + fcoe_init1.rq_buffer_log_size = BNX2FC_RQ_BUF_LOG_SZ; + fcoe_init1.cq_num_wqes = BNX2FC_CQ_WQES_MAX; + fcoe_init1.dummy_buffer_addr_lo = (u32) hba->dummy_buf_dma; + fcoe_init1.dummy_buffer_addr_hi = (u32) ((u64)hba->dummy_buf_dma >> 32); + fcoe_init1.task_list_pbl_addr_lo = (u32) hba->task_ctx_bd_dma; + fcoe_init1.task_list_pbl_addr_hi = + (u32) ((u64) hba->task_ctx_bd_dma >> 32); + fcoe_init1.mtu = BNX2FC_MINI_JUMBO_MTU; + + fcoe_init1.flags = (PAGE_SHIFT << + FCOE_KWQE_INIT1_LOG_PAGE_SIZE_SHIFT); + + fcoe_init1.num_sessions_log = BNX2FC_NUM_MAX_SESS_LOG; + + /* fill init2 KWQE */ + memset(&fcoe_init2, 0x00, sizeof(struct fcoe_kwqe_init2)); + fcoe_init2.hdr.op_code = FCOE_KWQE_OPCODE_INIT2; + fcoe_init2.hdr.flags = (FCOE_KWQE_LAYER_CODE << + FCOE_KWQE_HEADER_LAYER_CODE_SHIFT); + + fcoe_init2.hash_tbl_pbl_addr_lo = (u32) hba->hash_tbl_pbl_dma; + fcoe_init2.hash_tbl_pbl_addr_hi = (u32) + ((u64) hba->hash_tbl_pbl_dma >> 32); + + fcoe_init2.t2_hash_tbl_addr_lo = (u32) hba->t2_hash_tbl_dma; + fcoe_init2.t2_hash_tbl_addr_hi = (u32) + ((u64) hba->t2_hash_tbl_dma >> 32); + + fcoe_init2.t2_ptr_hash_tbl_addr_lo = (u32) hba->t2_hash_tbl_ptr_dma; + fcoe_init2.t2_ptr_hash_tbl_addr_hi = (u32) + ((u64) hba->t2_hash_tbl_ptr_dma >> 32); + + fcoe_init2.free_list_count = BNX2FC_NUM_MAX_SESS; + + /* fill init3 KWQE */ + memset(&fcoe_init3, 0x00, sizeof(struct fcoe_kwqe_init3)); + fcoe_init3.hdr.op_code = FCOE_KWQE_OPCODE_INIT3; + fcoe_init3.hdr.flags = (FCOE_KWQE_LAYER_CODE << + FCOE_KWQE_HEADER_LAYER_CODE_SHIFT); + fcoe_init3.error_bit_map_lo = 0xffffffff; + fcoe_init3.error_bit_map_hi = 0xffffffff; + + + kwqe_arr[0] = (struct kwqe *) &fcoe_init1; + kwqe_arr[1] = (struct kwqe *) &fcoe_init2; + kwqe_arr[2] = (struct kwqe *) &fcoe_init3; + + if (hba->cnic && hba->cnic->submit_kwqes) + rc = hba->cnic->submit_kwqes(hba->cnic, kwqe_arr, num_kwqes); + + return rc; +} +int bnx2fc_send_fw_fcoe_destroy_msg(struct bnx2fc_hba *hba) +{ + struct fcoe_kwqe_destroy fcoe_destroy; + struct kwqe *kwqe_arr[2]; + int num_kwqes = 1; + int rc = -1; + + /* fill destroy KWQE */ + memset(&fcoe_destroy, 0x00, sizeof(struct fcoe_kwqe_destroy)); + fcoe_destroy.hdr.op_code = FCOE_KWQE_OPCODE_DESTROY; + fcoe_destroy.hdr.flags = (FCOE_KWQE_LAYER_CODE << + FCOE_KWQE_HEADER_LAYER_CODE_SHIFT); + kwqe_arr[0] = (struct kwqe *) &fcoe_destroy; + + if (hba->cnic && hba->cnic->submit_kwqes) + rc = hba->cnic->submit_kwqes(hba->cnic, kwqe_arr, num_kwqes); + return rc; +} + +/** + * bnx2fc_send_session_ofld_req - initiates FCoE Session offload process + * + * @port: port structure pointer + * @tgt: bnx2fc_rport structure pointer + */ +int bnx2fc_send_session_ofld_req(struct fcoe_port *port, + struct bnx2fc_rport *tgt) +{ + struct fc_lport *lport = port->lport; + struct bnx2fc_hba *hba = port->priv; + struct kwqe *kwqe_arr[4]; + struct fcoe_kwqe_conn_offload1 ofld_req1; + struct fcoe_kwqe_conn_offload2 ofld_req2; + struct fcoe_kwqe_conn_offload3 ofld_req3; + struct fcoe_kwqe_conn_offload4 ofld_req4; + struct fc_rport_priv *rdata = tgt->rdata; + struct fc_rport *rport = tgt->rport; + int num_kwqes = 4; + u32 port_id; + int rc = 0; + u16 conn_id; + + /* Initialize offload request 1 structure */ + memset(&ofld_req1, 0x00, sizeof(struct fcoe_kwqe_conn_offload1)); + + ofld_req1.hdr.op_code = FCOE_KWQE_OPCODE_OFFLOAD_CONN1; + ofld_req1.hdr.flags = + (FCOE_KWQE_LAYER_CODE << FCOE_KWQE_HEADER_LAYER_CODE_SHIFT); + + + conn_id = (u16)tgt->fcoe_conn_id; + ofld_req1.fcoe_conn_id = conn_id; + + + ofld_req1.sq_addr_lo = (u32) tgt->sq_dma; + ofld_req1.sq_addr_hi = (u32)((u64) tgt->sq_dma >> 32); + + ofld_req1.rq_pbl_addr_lo = (u32) tgt->rq_pbl_dma; + ofld_req1.rq_pbl_addr_hi = (u32)((u64) tgt->rq_pbl_dma >> 32); + + ofld_req1.rq_first_pbe_addr_lo = (u32) tgt->rq_dma; + ofld_req1.rq_first_pbe_addr_hi = + (u32)((u64) tgt->rq_dma >> 32); + + ofld_req1.rq_prod = 0x8000; + + /* Initialize offload request 2 structure */ + memset(&ofld_req2, 0x00, sizeof(struct fcoe_kwqe_conn_offload2)); + + ofld_req2.hdr.op_code = FCOE_KWQE_OPCODE_OFFLOAD_CONN2; + ofld_req2.hdr.flags = + (FCOE_KWQE_LAYER_CODE << FCOE_KWQE_HEADER_LAYER_CODE_SHIFT); + + ofld_req2.tx_max_fc_pay_len = rdata->maxframe_size; + + ofld_req2.cq_addr_lo = (u32) tgt->cq_dma; + ofld_req2.cq_addr_hi = (u32)((u64)tgt->cq_dma >> 32); + + ofld_req2.xferq_addr_lo = (u32) tgt->xferq_dma; + ofld_req2.xferq_addr_hi = (u32)((u64)tgt->xferq_dma >> 32); + + ofld_req2.conn_db_addr_lo = (u32)tgt->conn_db_dma; + ofld_req2.conn_db_addr_hi = (u32)((u64)tgt->conn_db_dma >> 32); + + /* Initialize offload request 3 structure */ + memset(&ofld_req3, 0x00, sizeof(struct fcoe_kwqe_conn_offload3)); + + ofld_req3.hdr.op_code = FCOE_KWQE_OPCODE_OFFLOAD_CONN3; + ofld_req3.hdr.flags = + (FCOE_KWQE_LAYER_CODE << FCOE_KWQE_HEADER_LAYER_CODE_SHIFT); + + ofld_req3.vlan_tag = hba->vlan_id << + FCOE_KWQE_CONN_OFFLOAD3_VLAN_ID_SHIFT; + ofld_req3.vlan_tag |= 3 << FCOE_KWQE_CONN_OFFLOAD3_PRIORITY_SHIFT; + + port_id = fc_host_port_id(lport->host); + if (port_id == 0) { + BNX2FC_HBA_DBG(lport, "ofld_req: port_id = 0, link down?\n"); + return -EINVAL; + } + + /* + * Store s_id of the initiator for further reference. This will + * be used during disable/destroy during linkdown processing as + * when the lport is reset, the port_id also is reset to 0 + */ + tgt->sid = port_id; + ofld_req3.s_id[0] = (port_id & 0x000000FF); + ofld_req3.s_id[1] = (port_id & 0x0000FF00) >> 8; + ofld_req3.s_id[2] = (port_id & 0x00FF0000) >> 16; + + port_id = rport->port_id; + ofld_req3.d_id[0] = (port_id & 0x000000FF); + ofld_req3.d_id[1] = (port_id & 0x0000FF00) >> 8; + ofld_req3.d_id[2] = (port_id & 0x00FF0000) >> 16; + + ofld_req3.tx_total_conc_seqs = rdata->max_seq; + + ofld_req3.tx_max_conc_seqs_c3 = rdata->max_seq; + ofld_req3.rx_max_fc_pay_len = lport->mfs; + + ofld_req3.rx_total_conc_seqs = BNX2FC_MAX_SEQS; + ofld_req3.rx_max_conc_seqs_c3 = BNX2FC_MAX_SEQS; + ofld_req3.rx_open_seqs_exch_c3 = 1; + + ofld_req3.confq_first_pbe_addr_lo = tgt->confq_dma; + ofld_req3.confq_first_pbe_addr_hi = (u32)((u64) tgt->confq_dma >> 32); + + /* set mul_n_port_ids supported flag to 0, until it is supported */ + ofld_req3.flags = 0; + /* + ofld_req3.flags |= (((lport->send_sp_features & FC_SP_FT_MNA) ? 1:0) << + FCOE_KWQE_CONN_OFFLOAD3_B_MUL_N_PORT_IDS_SHIFT); + */ + /* Info from PLOGI response */ + ofld_req3.flags |= (((rdata->sp_features & FC_SP_FT_EDTR) ? 1 : 0) << + FCOE_KWQE_CONN_OFFLOAD3_B_E_D_TOV_RES_SHIFT); + + ofld_req3.flags |= (((rdata->sp_features & FC_SP_FT_SEQC) ? 1 : 0) << + FCOE_KWQE_CONN_OFFLOAD3_B_CONT_INCR_SEQ_CNT_SHIFT); + + /* vlan flag */ + ofld_req3.flags |= (hba->vlan_enabled << + FCOE_KWQE_CONN_OFFLOAD3_B_VLAN_FLAG_SHIFT); + + /* C2_VALID and ACK flags are not set as they are not suppported */ + + + /* Initialize offload request 4 structure */ + memset(&ofld_req4, 0x00, sizeof(struct fcoe_kwqe_conn_offload4)); + ofld_req4.hdr.op_code = FCOE_KWQE_OPCODE_OFFLOAD_CONN4; + ofld_req4.hdr.flags = + (FCOE_KWQE_LAYER_CODE << FCOE_KWQE_HEADER_LAYER_CODE_SHIFT); + + ofld_req4.e_d_tov_timer_val = lport->e_d_tov / 20; + + + ofld_req4.src_mac_addr_lo32[0] = port->data_src_addr[5]; + /* local mac */ + ofld_req4.src_mac_addr_lo32[1] = port->data_src_addr[4]; + ofld_req4.src_mac_addr_lo32[2] = port->data_src_addr[3]; + ofld_req4.src_mac_addr_lo32[3] = port->data_src_addr[2]; + ofld_req4.src_mac_addr_hi16[0] = port->data_src_addr[1]; + ofld_req4.src_mac_addr_hi16[1] = port->data_src_addr[0]; + ofld_req4.dst_mac_addr_lo32[0] = hba->ctlr.dest_addr[5];/* fcf mac */ + ofld_req4.dst_mac_addr_lo32[1] = hba->ctlr.dest_addr[4]; + ofld_req4.dst_mac_addr_lo32[2] = hba->ctlr.dest_addr[3]; + ofld_req4.dst_mac_addr_lo32[3] = hba->ctlr.dest_addr[2]; + ofld_req4.dst_mac_addr_hi16[0] = hba->ctlr.dest_addr[1]; + ofld_req4.dst_mac_addr_hi16[1] = hba->ctlr.dest_addr[0]; + + ofld_req4.lcq_addr_lo = (u32) tgt->lcq_dma; + ofld_req4.lcq_addr_hi = (u32)((u64) tgt->lcq_dma >> 32); + + ofld_req4.confq_pbl_base_addr_lo = (u32) tgt->confq_pbl_dma; + ofld_req4.confq_pbl_base_addr_hi = + (u32)((u64) tgt->confq_pbl_dma >> 32); + + kwqe_arr[0] = (struct kwqe *) &ofld_req1; + kwqe_arr[1] = (struct kwqe *) &ofld_req2; + kwqe_arr[2] = (struct kwqe *) &ofld_req3; + kwqe_arr[3] = (struct kwqe *) &ofld_req4; + + if (hba->cnic && hba->cnic->submit_kwqes) + rc = hba->cnic->submit_kwqes(hba->cnic, kwqe_arr, num_kwqes); + + return rc; +} + +/** + * bnx2fc_send_session_enable_req - initiates FCoE Session enablement + * + * @port: port structure pointer + * @tgt: bnx2fc_rport structure pointer + */ +static int bnx2fc_send_session_enable_req(struct fcoe_port *port, + struct bnx2fc_rport *tgt) +{ + struct kwqe *kwqe_arr[2]; + struct bnx2fc_hba *hba = port->priv; + struct fcoe_kwqe_conn_enable_disable enbl_req; + struct fc_lport *lport = port->lport; + struct fc_rport *rport = tgt->rport; + int num_kwqes = 1; + int rc = 0; + u32 port_id; + + memset(&enbl_req, 0x00, + sizeof(struct fcoe_kwqe_conn_enable_disable)); + enbl_req.hdr.op_code = FCOE_KWQE_OPCODE_ENABLE_CONN; + enbl_req.hdr.flags = + (FCOE_KWQE_LAYER_CODE << FCOE_KWQE_HEADER_LAYER_CODE_SHIFT); + + enbl_req.src_mac_addr_lo32[0] = port->data_src_addr[5]; + /* local mac */ + enbl_req.src_mac_addr_lo32[1] = port->data_src_addr[4]; + enbl_req.src_mac_addr_lo32[2] = port->data_src_addr[3]; + enbl_req.src_mac_addr_lo32[3] = port->data_src_addr[2]; + enbl_req.src_mac_addr_hi16[0] = port->data_src_addr[1]; + enbl_req.src_mac_addr_hi16[1] = port->data_src_addr[0]; + + enbl_req.dst_mac_addr_lo32[0] = hba->ctlr.dest_addr[5];/* fcf mac */ + enbl_req.dst_mac_addr_lo32[1] = hba->ctlr.dest_addr[4]; + enbl_req.dst_mac_addr_lo32[2] = hba->ctlr.dest_addr[3]; + enbl_req.dst_mac_addr_lo32[3] = hba->ctlr.dest_addr[2]; + enbl_req.dst_mac_addr_hi16[0] = hba->ctlr.dest_addr[1]; + enbl_req.dst_mac_addr_hi16[1] = hba->ctlr.dest_addr[0]; + + port_id = fc_host_port_id(lport->host); + if (port_id != tgt->sid) { + printk(KERN_ERR PFX "WARN: enable_req port_id = 0x%x," + "sid = 0x%x\n", port_id, tgt->sid); + port_id = tgt->sid; + } + enbl_req.s_id[0] = (port_id & 0x000000FF); + enbl_req.s_id[1] = (port_id & 0x0000FF00) >> 8; + enbl_req.s_id[2] = (port_id & 0x00FF0000) >> 16; + + port_id = rport->port_id; + enbl_req.d_id[0] = (port_id & 0x000000FF); + enbl_req.d_id[1] = (port_id & 0x0000FF00) >> 8; + enbl_req.d_id[2] = (port_id & 0x00FF0000) >> 16; + enbl_req.vlan_tag = hba->vlan_id << + FCOE_KWQE_CONN_ENABLE_DISABLE_VLAN_ID_SHIFT; + enbl_req.vlan_tag |= 3 << FCOE_KWQE_CONN_ENABLE_DISABLE_PRIORITY_SHIFT; + enbl_req.vlan_flag = hba->vlan_enabled; + enbl_req.context_id = tgt->context_id; + enbl_req.conn_id = tgt->fcoe_conn_id; + + kwqe_arr[0] = (struct kwqe *) &enbl_req; + + if (hba->cnic && hba->cnic->submit_kwqes) + rc = hba->cnic->submit_kwqes(hba->cnic, kwqe_arr, num_kwqes); + return rc; +} + +/** + * bnx2fc_send_session_disable_req - initiates FCoE Session disable + * + * @port: port structure pointer + * @tgt: bnx2fc_rport structure pointer + */ +int bnx2fc_send_session_disable_req(struct fcoe_port *port, + struct bnx2fc_rport *tgt) +{ + struct bnx2fc_hba *hba = port->priv; + struct fcoe_kwqe_conn_enable_disable disable_req; + struct kwqe *kwqe_arr[2]; + struct fc_rport *rport = tgt->rport; + int num_kwqes = 1; + int rc = 0; + u32 port_id; + + memset(&disable_req, 0x00, + sizeof(struct fcoe_kwqe_conn_enable_disable)); + disable_req.hdr.op_code = FCOE_KWQE_OPCODE_DISABLE_CONN; + disable_req.hdr.flags = + (FCOE_KWQE_LAYER_CODE << FCOE_KWQE_HEADER_LAYER_CODE_SHIFT); + + disable_req.src_mac_addr_lo32[0] = port->data_src_addr[5]; + disable_req.src_mac_addr_lo32[2] = port->data_src_addr[3]; + disable_req.src_mac_addr_lo32[3] = port->data_src_addr[2]; + disable_req.src_mac_addr_hi16[0] = port->data_src_addr[1]; + disable_req.src_mac_addr_hi16[1] = port->data_src_addr[0]; + + disable_req.dst_mac_addr_lo32[0] = hba->ctlr.dest_addr[5];/* fcf mac */ + disable_req.dst_mac_addr_lo32[1] = hba->ctlr.dest_addr[4]; + disable_req.dst_mac_addr_lo32[2] = hba->ctlr.dest_addr[3]; + disable_req.dst_mac_addr_lo32[3] = hba->ctlr.dest_addr[2]; + disable_req.dst_mac_addr_hi16[0] = hba->ctlr.dest_addr[1]; + disable_req.dst_mac_addr_hi16[1] = hba->ctlr.dest_addr[0]; + + port_id = tgt->sid; + disable_req.s_id[0] = (port_id & 0x000000FF); + disable_req.s_id[1] = (port_id & 0x0000FF00) >> 8; + disable_req.s_id[2] = (port_id & 0x00FF0000) >> 16; + + + port_id = rport->port_id; + disable_req.d_id[0] = (port_id & 0x000000FF); + disable_req.d_id[1] = (port_id & 0x0000FF00) >> 8; + disable_req.d_id[2] = (port_id & 0x00FF0000) >> 16; + disable_req.context_id = tgt->context_id; + disable_req.conn_id = tgt->fcoe_conn_id; + disable_req.vlan_tag = hba->vlan_id << + FCOE_KWQE_CONN_ENABLE_DISABLE_VLAN_ID_SHIFT; + disable_req.vlan_tag |= + 3 << FCOE_KWQE_CONN_ENABLE_DISABLE_PRIORITY_SHIFT; + disable_req.vlan_flag = hba->vlan_enabled; + + kwqe_arr[0] = (struct kwqe *) &disable_req; + + if (hba->cnic && hba->cnic->submit_kwqes) + rc = hba->cnic->submit_kwqes(hba->cnic, kwqe_arr, num_kwqes); + + return rc; +} + +/** + * bnx2fc_send_session_destroy_req - initiates FCoE Session destroy + * + * @port: port structure pointer + * @tgt: bnx2fc_rport structure pointer + */ +int bnx2fc_send_session_destroy_req(struct bnx2fc_hba *hba, + struct bnx2fc_rport *tgt) +{ + struct fcoe_kwqe_conn_destroy destroy_req; + struct kwqe *kwqe_arr[2]; + int num_kwqes = 1; + int rc = 0; + + memset(&destroy_req, 0x00, sizeof(struct fcoe_kwqe_conn_destroy)); + destroy_req.hdr.op_code = FCOE_KWQE_OPCODE_DESTROY_CONN; + destroy_req.hdr.flags = + (FCOE_KWQE_LAYER_CODE << FCOE_KWQE_HEADER_LAYER_CODE_SHIFT); + + destroy_req.context_id = tgt->context_id; + destroy_req.conn_id = tgt->fcoe_conn_id; + + kwqe_arr[0] = (struct kwqe *) &destroy_req; + + if (hba->cnic && hba->cnic->submit_kwqes) + rc = hba->cnic->submit_kwqes(hba->cnic, kwqe_arr, num_kwqes); + + return rc; +} + +static void bnx2fc_unsol_els_work(struct work_struct *work) +{ + struct bnx2fc_unsol_els *unsol_els; + struct fc_lport *lport; + struct fc_frame *fp; + + unsol_els = container_of(work, struct bnx2fc_unsol_els, unsol_els_work); + lport = unsol_els->lport; + fp = unsol_els->fp; + fc_exch_recv(lport, fp); + kfree(unsol_els); +} + +void bnx2fc_process_l2_frame_compl(struct bnx2fc_rport *tgt, + unsigned char *buf, + u32 frame_len, u16 l2_oxid) +{ + struct fcoe_port *port = tgt->port; + struct fc_lport *lport = port->lport; + struct bnx2fc_unsol_els *unsol_els; + struct fc_frame_header *fh; + struct fc_frame *fp; + struct sk_buff *skb; + u32 payload_len; + u32 crc; + u8 op; + + + unsol_els = kzalloc(sizeof(*unsol_els), GFP_ATOMIC); + if (!unsol_els) { + BNX2FC_TGT_DBG(tgt, "Unable to allocate unsol_work\n"); + return; + } + + BNX2FC_TGT_DBG(tgt, "l2_frame_compl l2_oxid = 0x%x, frame_len = %d\n", + l2_oxid, frame_len); + + payload_len = frame_len - sizeof(struct fc_frame_header); + + fp = fc_frame_alloc(lport, payload_len); + if (!fp) { + printk(KERN_ERR PFX "fc_frame_alloc failure\n"); + return; + } + + fh = (struct fc_frame_header *) fc_frame_header_get(fp); + /* Copy FC Frame header and payload into the frame */ + memcpy(fh, buf, frame_len); + + if (l2_oxid != FC_XID_UNKNOWN) + fh->fh_ox_id = htons(l2_oxid); + + skb = fp_skb(fp); + + if ((fh->fh_r_ctl == FC_RCTL_ELS_REQ) || + (fh->fh_r_ctl == FC_RCTL_ELS_REP)) { + + if (fh->fh_type == FC_TYPE_ELS) { + op = fc_frame_payload_op(fp); + if ((op == ELS_TEST) || (op == ELS_ESTC) || + (op == ELS_FAN) || (op == ELS_CSU)) { + /* + * No need to reply for these + * ELS requests + */ + printk(KERN_ERR PFX "dropping ELS 0x%x\n", op); + kfree_skb(skb); + return; + } + } + crc = fcoe_fc_crc(fp); + fc_frame_init(fp); + fr_dev(fp) = lport; + fr_sof(fp) = FC_SOF_I3; + fr_eof(fp) = FC_EOF_T; + fr_crc(fp) = cpu_to_le32(~crc); + unsol_els->lport = lport; + unsol_els->fp = fp; + INIT_WORK(&unsol_els->unsol_els_work, bnx2fc_unsol_els_work); + queue_work(bnx2fc_wq, &unsol_els->unsol_els_work); + } else { + BNX2FC_HBA_DBG(lport, "fh_r_ctl = 0x%x\n", fh->fh_r_ctl); + kfree_skb(skb); + } +} + +static void bnx2fc_process_unsol_compl(struct bnx2fc_rport *tgt, u16 wqe) +{ + u8 num_rq; + struct fcoe_err_report_entry *err_entry; + unsigned char *rq_data; + unsigned char *buf = NULL, *buf1; + int i; + u16 xid; + u32 frame_len, len; + struct bnx2fc_cmd *io_req = NULL; + struct fcoe_task_ctx_entry *task, *task_page; + struct bnx2fc_hba *hba = tgt->port->priv; + int task_idx, index; + int rc = 0; + + + BNX2FC_TGT_DBG(tgt, "Entered UNSOL COMPLETION wqe = 0x%x\n", wqe); + switch (wqe & FCOE_UNSOLICITED_CQE_SUBTYPE) { + case FCOE_UNSOLICITED_FRAME_CQE_TYPE: + frame_len = (wqe & FCOE_UNSOLICITED_CQE_PKT_LEN) >> + FCOE_UNSOLICITED_CQE_PKT_LEN_SHIFT; + + num_rq = (frame_len + BNX2FC_RQ_BUF_SZ - 1) / BNX2FC_RQ_BUF_SZ; + + spin_lock_bh(&tgt->tgt_lock); + rq_data = (unsigned char *)bnx2fc_get_next_rqe(tgt, num_rq); + spin_unlock_bh(&tgt->tgt_lock); + + if (rq_data) { + buf = rq_data; + } else { + buf1 = buf = kmalloc((num_rq * BNX2FC_RQ_BUF_SZ), + GFP_ATOMIC); + + if (!buf1) { + BNX2FC_TGT_DBG(tgt, "Memory alloc failure\n"); + break; + } + + for (i = 0; i < num_rq; i++) { + spin_lock_bh(&tgt->tgt_lock); + rq_data = (unsigned char *) + bnx2fc_get_next_rqe(tgt, 1); + spin_unlock_bh(&tgt->tgt_lock); + len = BNX2FC_RQ_BUF_SZ; + memcpy(buf1, rq_data, len); + buf1 += len; + } + } + bnx2fc_process_l2_frame_compl(tgt, buf, frame_len, + FC_XID_UNKNOWN); + + if (buf != rq_data) + kfree(buf); + spin_lock_bh(&tgt->tgt_lock); + bnx2fc_return_rqe(tgt, num_rq); + spin_unlock_bh(&tgt->tgt_lock); + break; + + case FCOE_ERROR_DETECTION_CQE_TYPE: + /* + * In case of error reporting CQE a single RQ entry + * is consumed. + */ + spin_lock_bh(&tgt->tgt_lock); + num_rq = 1; + err_entry = (struct fcoe_err_report_entry *) + bnx2fc_get_next_rqe(tgt, 1); + xid = err_entry->fc_hdr.ox_id; + BNX2FC_TGT_DBG(tgt, "Unsol Error Frame OX_ID = 0x%x\n", xid); + BNX2FC_TGT_DBG(tgt, "err_warn_bitmap = %08x:%08x\n", + err_entry->err_warn_bitmap_hi, + err_entry->err_warn_bitmap_lo); + BNX2FC_TGT_DBG(tgt, "buf_offsets - tx = 0x%x, rx = 0x%x\n", + err_entry->tx_buf_off, err_entry->rx_buf_off); + + bnx2fc_return_rqe(tgt, 1); + + if (xid > BNX2FC_MAX_XID) { + BNX2FC_TGT_DBG(tgt, "xid(0x%x) out of FW range\n", + xid); + spin_unlock_bh(&tgt->tgt_lock); + break; + } + + task_idx = xid / BNX2FC_TASKS_PER_PAGE; + index = xid % BNX2FC_TASKS_PER_PAGE; + task_page = (struct fcoe_task_ctx_entry *) + hba->task_ctx[task_idx]; + task = &(task_page[index]); + + io_req = (struct bnx2fc_cmd *)hba->cmd_mgr->cmds[xid]; + if (!io_req) { + spin_unlock_bh(&tgt->tgt_lock); + break; + } + + if (io_req->cmd_type != BNX2FC_SCSI_CMD) { + printk(KERN_ERR PFX "err_warn: Not a SCSI cmd\n"); + spin_unlock_bh(&tgt->tgt_lock); + break; + } + + if (test_and_clear_bit(BNX2FC_FLAG_IO_CLEANUP, + &io_req->req_flags)) { + BNX2FC_IO_DBG(io_req, "unsol_err: cleanup in " + "progress.. ignore unsol err\n"); + spin_unlock_bh(&tgt->tgt_lock); + break; + } + + /* + * If ABTS is already in progress, and FW error is + * received after that, do not cancel the timeout_work + * and let the error recovery continue by explicitly + * logging out the target, when the ABTS eventually + * times out. + */ + if (!test_and_set_bit(BNX2FC_FLAG_ISSUE_ABTS, + &io_req->req_flags)) { + /* + * Cancel the timeout_work, as we received IO + * completion with FW error. + */ + if (cancel_delayed_work(&io_req->timeout_work)) + kref_put(&io_req->refcount, + bnx2fc_cmd_release); /* timer hold */ + + rc = bnx2fc_initiate_abts(io_req); + if (rc != SUCCESS) { + BNX2FC_IO_DBG(io_req, "err_warn: initiate_abts " + "failed. issue cleanup\n"); + rc = bnx2fc_initiate_cleanup(io_req); + BUG_ON(rc); + } + } else + printk(KERN_ERR PFX "err_warn: io_req (0x%x) already " + "in ABTS processing\n", xid); + spin_unlock_bh(&tgt->tgt_lock); + break; + + case FCOE_WARNING_DETECTION_CQE_TYPE: + /* + *In case of warning reporting CQE a single RQ entry + * is consumes. + */ + spin_lock_bh(&tgt->tgt_lock); + num_rq = 1; + err_entry = (struct fcoe_err_report_entry *) + bnx2fc_get_next_rqe(tgt, 1); + xid = cpu_to_be16(err_entry->fc_hdr.ox_id); + BNX2FC_TGT_DBG(tgt, "Unsol Warning Frame OX_ID = 0x%x\n", xid); + BNX2FC_TGT_DBG(tgt, "err_warn_bitmap = %08x:%08x", + err_entry->err_warn_bitmap_hi, + err_entry->err_warn_bitmap_lo); + BNX2FC_TGT_DBG(tgt, "buf_offsets - tx = 0x%x, rx = 0x%x", + err_entry->tx_buf_off, err_entry->rx_buf_off); + + bnx2fc_return_rqe(tgt, 1); + spin_unlock_bh(&tgt->tgt_lock); + break; + + default: + printk(KERN_ERR PFX "Unsol Compl: Invalid CQE Subtype\n"); + break; + } +} + +void bnx2fc_process_cq_compl(struct bnx2fc_rport *tgt, u16 wqe) +{ + struct fcoe_task_ctx_entry *task; + struct fcoe_task_ctx_entry *task_page; + struct fcoe_port *port = tgt->port; + struct bnx2fc_hba *hba = port->priv; + struct bnx2fc_cmd *io_req; + int task_idx, index; + u16 xid; + u8 cmd_type; + u8 rx_state = 0; + u8 num_rq; + + spin_lock_bh(&tgt->tgt_lock); + xid = wqe & FCOE_PEND_WQ_CQE_TASK_ID; + if (xid >= BNX2FC_MAX_TASKS) { + printk(KERN_ALERT PFX "ERROR:xid out of range\n"); + spin_unlock_bh(&tgt->tgt_lock); + return; + } + task_idx = xid / BNX2FC_TASKS_PER_PAGE; + index = xid % BNX2FC_TASKS_PER_PAGE; + task_page = (struct fcoe_task_ctx_entry *)hba->task_ctx[task_idx]; + task = &(task_page[index]); + + num_rq = ((task->rx_wr_tx_rd.rx_flags & + FCOE_TASK_CTX_ENTRY_RXWR_TXRD_NUM_RQ_WQE) >> + FCOE_TASK_CTX_ENTRY_RXWR_TXRD_NUM_RQ_WQE_SHIFT); + + io_req = (struct bnx2fc_cmd *)hba->cmd_mgr->cmds[xid]; + + if (io_req == NULL) { + printk(KERN_ERR PFX "ERROR? cq_compl - io_req is NULL\n"); + spin_unlock_bh(&tgt->tgt_lock); + return; + } + + /* Timestamp IO completion time */ + cmd_type = io_req->cmd_type; + + /* optimized completion path */ + if (cmd_type == BNX2FC_SCSI_CMD) { + rx_state = ((task->rx_wr_tx_rd.rx_flags & + FCOE_TASK_CTX_ENTRY_RXWR_TXRD_RX_STATE) >> + FCOE_TASK_CTX_ENTRY_RXWR_TXRD_RX_STATE_SHIFT); + + if (rx_state == FCOE_TASK_RX_STATE_COMPLETED) { + bnx2fc_process_scsi_cmd_compl(io_req, task, num_rq); + spin_unlock_bh(&tgt->tgt_lock); + return; + } + } + + /* Process other IO completion types */ + switch (cmd_type) { + case BNX2FC_SCSI_CMD: + if (rx_state == FCOE_TASK_RX_STATE_ABTS_COMPLETED) + bnx2fc_process_abts_compl(io_req, task, num_rq); + else if (rx_state == + FCOE_TASK_RX_STATE_EXCHANGE_CLEANUP_COMPLETED) + bnx2fc_process_cleanup_compl(io_req, task, num_rq); + else + printk(KERN_ERR PFX "Invalid rx state - %d\n", + rx_state); + break; + + case BNX2FC_TASK_MGMT_CMD: + BNX2FC_IO_DBG(io_req, "Processing TM complete\n"); + bnx2fc_process_tm_compl(io_req, task, num_rq); + break; + + case BNX2FC_ABTS: + /* + * ABTS request received by firmware. ABTS response + * will be delivered to the task belonging to the IO + * that was aborted + */ + BNX2FC_IO_DBG(io_req, "cq_compl- ABTS sent out by fw\n"); + kref_put(&io_req->refcount, bnx2fc_cmd_release); + break; + + case BNX2FC_ELS: + BNX2FC_IO_DBG(io_req, "cq_compl - call process_els_compl\n"); + bnx2fc_process_els_compl(io_req, task, num_rq); + break; + + case BNX2FC_CLEANUP: + BNX2FC_IO_DBG(io_req, "cq_compl- cleanup resp rcvd\n"); + kref_put(&io_req->refcount, bnx2fc_cmd_release); + break; + + default: + printk(KERN_ERR PFX "Invalid cmd_type %d\n", cmd_type); + break; + } + spin_unlock_bh(&tgt->tgt_lock); +} + +struct bnx2fc_work *bnx2fc_alloc_work(struct bnx2fc_rport *tgt, u16 wqe) +{ + struct bnx2fc_work *work; + work = kzalloc(sizeof(struct bnx2fc_work), GFP_ATOMIC); + if (!work) + return NULL; + + INIT_LIST_HEAD(&work->list); + work->tgt = tgt; + work->wqe = wqe; + return work; +} + +int bnx2fc_process_new_cqes(struct bnx2fc_rport *tgt) +{ + struct fcoe_cqe *cq; + u32 cq_cons; + struct fcoe_cqe *cqe; + u16 wqe; + bool more_cqes_found = false; + + /* + * cq_lock is a low contention lock used to protect + * the CQ data structure from being freed up during + * the upload operation + */ + spin_lock_bh(&tgt->cq_lock); + + if (!tgt->cq) { + printk(KERN_ERR PFX "process_new_cqes: cq is NULL\n"); + spin_unlock_bh(&tgt->cq_lock); + return 0; + } + cq = tgt->cq; + cq_cons = tgt->cq_cons_idx; + cqe = &cq[cq_cons]; + + do { + more_cqes_found ^= true; + + while (((wqe = cqe->wqe) & FCOE_CQE_TOGGLE_BIT) == + (tgt->cq_curr_toggle_bit << + FCOE_CQE_TOGGLE_BIT_SHIFT)) { + + /* new entry on the cq */ + if (wqe & FCOE_CQE_CQE_TYPE) { + /* Unsolicited event notification */ + bnx2fc_process_unsol_compl(tgt, wqe); + } else { + struct bnx2fc_work *work = NULL; + struct bnx2fc_percpu_s *fps = NULL; + unsigned int cpu = wqe % num_possible_cpus(); + + fps = &per_cpu(bnx2fc_percpu, cpu); + spin_lock_bh(&fps->fp_work_lock); + if (unlikely(!fps->iothread)) + goto unlock; + + work = bnx2fc_alloc_work(tgt, wqe); + if (work) + list_add_tail(&work->list, + &fps->work_list); +unlock: + spin_unlock_bh(&fps->fp_work_lock); + + /* Pending work request completion */ + if (fps->iothread && work) + wake_up_process(fps->iothread); + else + bnx2fc_process_cq_compl(tgt, wqe); + } + cqe++; + tgt->cq_cons_idx++; + + if (tgt->cq_cons_idx == BNX2FC_CQ_WQES_MAX) { + tgt->cq_cons_idx = 0; + cqe = cq; + tgt->cq_curr_toggle_bit = + 1 - tgt->cq_curr_toggle_bit; + } + } + /* Re-arm CQ */ + if (more_cqes_found) { + tgt->conn_db->cq_arm.lo = -1; + wmb(); + } + } while (more_cqes_found); + + /* + * Commit tgt->cq_cons_idx change to the memory + * spin_lock implies full memory barrier, no need to smp_wmb + */ + + spin_unlock_bh(&tgt->cq_lock); + return 0; +} + +/** + * bnx2fc_fastpath_notification - process global event queue (KCQ) + * + * @hba: adapter structure pointer + * @new_cqe_kcqe: pointer to newly DMA'd KCQ entry + * + * Fast path event notification handler + */ +static void bnx2fc_fastpath_notification(struct bnx2fc_hba *hba, + struct fcoe_kcqe *new_cqe_kcqe) +{ + u32 conn_id = new_cqe_kcqe->fcoe_conn_id; + struct bnx2fc_rport *tgt = hba->tgt_ofld_list[conn_id]; + + if (!tgt) { + printk(KERN_ALERT PFX "conn_id 0x%x not valid\n", conn_id); + return; + } + + bnx2fc_process_new_cqes(tgt); +} + +/** + * bnx2fc_process_ofld_cmpl - process FCoE session offload completion + * + * @hba: adapter structure pointer + * @ofld_kcqe: connection offload kcqe pointer + * + * handle session offload completion, enable the session if offload is + * successful. + */ +static void bnx2fc_process_ofld_cmpl(struct bnx2fc_hba *hba, + struct fcoe_kcqe *ofld_kcqe) +{ + struct bnx2fc_rport *tgt; + struct fcoe_port *port; + u32 conn_id; + u32 context_id; + int rc; + + conn_id = ofld_kcqe->fcoe_conn_id; + context_id = ofld_kcqe->fcoe_conn_context_id; + tgt = hba->tgt_ofld_list[conn_id]; + if (!tgt) { + printk(KERN_ALERT PFX "ERROR:ofld_cmpl: No pending ofld req\n"); + return; + } + BNX2FC_TGT_DBG(tgt, "Entered ofld compl - context_id = 0x%x\n", + ofld_kcqe->fcoe_conn_context_id); + port = tgt->port; + if (hba != tgt->port->priv) { + printk(KERN_ALERT PFX "ERROR:ofld_cmpl: HBA mis-match\n"); + goto ofld_cmpl_err; + } + /* + * cnic has allocated a context_id for this session; use this + * while enabling the session. + */ + tgt->context_id = context_id; + if (ofld_kcqe->completion_status) { + if (ofld_kcqe->completion_status == + FCOE_KCQE_COMPLETION_STATUS_CTX_ALLOC_FAILURE) { + printk(KERN_ERR PFX "unable to allocate FCoE context " + "resources\n"); + set_bit(BNX2FC_FLAG_CTX_ALLOC_FAILURE, &tgt->flags); + } + goto ofld_cmpl_err; + } else { + + /* now enable the session */ + rc = bnx2fc_send_session_enable_req(port, tgt); + if (rc) { + printk(KERN_ALERT PFX "enable session failed\n"); + goto ofld_cmpl_err; + } + } + return; +ofld_cmpl_err: + set_bit(BNX2FC_FLAG_OFLD_REQ_CMPL, &tgt->flags); + wake_up_interruptible(&tgt->ofld_wait); +} + +/** + * bnx2fc_process_enable_conn_cmpl - process FCoE session enable completion + * + * @hba: adapter structure pointer + * @ofld_kcqe: connection offload kcqe pointer + * + * handle session enable completion, mark the rport as ready + */ + +static void bnx2fc_process_enable_conn_cmpl(struct bnx2fc_hba *hba, + struct fcoe_kcqe *ofld_kcqe) +{ + struct bnx2fc_rport *tgt; + u32 conn_id; + u32 context_id; + + context_id = ofld_kcqe->fcoe_conn_context_id; + conn_id = ofld_kcqe->fcoe_conn_id; + tgt = hba->tgt_ofld_list[conn_id]; + if (!tgt) { + printk(KERN_ALERT PFX "ERROR:enbl_cmpl: No pending ofld req\n"); + return; + } + + BNX2FC_TGT_DBG(tgt, "Enable compl - context_id = 0x%x\n", + ofld_kcqe->fcoe_conn_context_id); + + /* + * context_id should be the same for this target during offload + * and enable + */ + if (tgt->context_id != context_id) { + printk(KERN_ALERT PFX "context id mis-match\n"); + return; + } + if (hba != tgt->port->priv) { + printk(KERN_ALERT PFX "bnx2fc-enbl_cmpl: HBA mis-match\n"); + goto enbl_cmpl_err; + } + if (ofld_kcqe->completion_status) { + goto enbl_cmpl_err; + } else { + /* enable successful - rport ready for issuing IOs */ + set_bit(BNX2FC_FLAG_OFFLOADED, &tgt->flags); + set_bit(BNX2FC_FLAG_OFLD_REQ_CMPL, &tgt->flags); + wake_up_interruptible(&tgt->ofld_wait); + } + return; + +enbl_cmpl_err: + set_bit(BNX2FC_FLAG_OFLD_REQ_CMPL, &tgt->flags); + wake_up_interruptible(&tgt->ofld_wait); +} + +static void bnx2fc_process_conn_disable_cmpl(struct bnx2fc_hba *hba, + struct fcoe_kcqe *disable_kcqe) +{ + + struct bnx2fc_rport *tgt; + u32 conn_id; + + conn_id = disable_kcqe->fcoe_conn_id; + tgt = hba->tgt_ofld_list[conn_id]; + if (!tgt) { + printk(KERN_ALERT PFX "ERROR: disable_cmpl: No disable req\n"); + return; + } + + BNX2FC_TGT_DBG(tgt, PFX "disable_cmpl: conn_id %d\n", conn_id); + + if (disable_kcqe->completion_status) { + printk(KERN_ALERT PFX "ERROR: Disable failed with cmpl status %d\n", + disable_kcqe->completion_status); + return; + } else { + /* disable successful */ + BNX2FC_TGT_DBG(tgt, "disable successful\n"); + clear_bit(BNX2FC_FLAG_OFFLOADED, &tgt->flags); + set_bit(BNX2FC_FLAG_DISABLED, &tgt->flags); + set_bit(BNX2FC_FLAG_UPLD_REQ_COMPL, &tgt->flags); + wake_up_interruptible(&tgt->upld_wait); + } +} + +static void bnx2fc_process_conn_destroy_cmpl(struct bnx2fc_hba *hba, + struct fcoe_kcqe *destroy_kcqe) +{ + struct bnx2fc_rport *tgt; + u32 conn_id; + + conn_id = destroy_kcqe->fcoe_conn_id; + tgt = hba->tgt_ofld_list[conn_id]; + if (!tgt) { + printk(KERN_ALERT PFX "destroy_cmpl: No destroy req\n"); + return; + } + + BNX2FC_TGT_DBG(tgt, "destroy_cmpl: conn_id %d\n", conn_id); + + if (destroy_kcqe->completion_status) { + printk(KERN_ALERT PFX "Destroy conn failed, cmpl status %d\n", + destroy_kcqe->completion_status); + return; + } else { + /* destroy successful */ + BNX2FC_TGT_DBG(tgt, "upload successful\n"); + clear_bit(BNX2FC_FLAG_DISABLED, &tgt->flags); + set_bit(BNX2FC_FLAG_DESTROYED, &tgt->flags); + set_bit(BNX2FC_FLAG_UPLD_REQ_COMPL, &tgt->flags); + wake_up_interruptible(&tgt->upld_wait); + } +} + +static void bnx2fc_init_failure(struct bnx2fc_hba *hba, u32 err_code) +{ + switch (err_code) { + case FCOE_KCQE_COMPLETION_STATUS_INVALID_OPCODE: + printk(KERN_ERR PFX "init_failure due to invalid opcode\n"); + break; + + case FCOE_KCQE_COMPLETION_STATUS_CTX_ALLOC_FAILURE: + printk(KERN_ERR PFX "init failed due to ctx alloc failure\n"); + break; + + case FCOE_KCQE_COMPLETION_STATUS_NIC_ERROR: + printk(KERN_ERR PFX "init_failure due to NIC error\n"); + break; + + default: + printk(KERN_ERR PFX "Unknown Error code %d\n", err_code); + } +} + +/** + * bnx2fc_indicae_kcqe - process KCQE + * + * @hba: adapter structure pointer + * @kcqe: kcqe pointer + * @num_cqe: Number of completion queue elements + * + * Generic KCQ event handler + */ +void bnx2fc_indicate_kcqe(void *context, struct kcqe *kcq[], + u32 num_cqe) +{ + struct bnx2fc_hba *hba = (struct bnx2fc_hba *)context; + int i = 0; + struct fcoe_kcqe *kcqe = NULL; + + while (i < num_cqe) { + kcqe = (struct fcoe_kcqe *) kcq[i++]; + + switch (kcqe->op_code) { + case FCOE_KCQE_OPCODE_CQ_EVENT_NOTIFICATION: + bnx2fc_fastpath_notification(hba, kcqe); + break; + + case FCOE_KCQE_OPCODE_OFFLOAD_CONN: + bnx2fc_process_ofld_cmpl(hba, kcqe); + break; + + case FCOE_KCQE_OPCODE_ENABLE_CONN: + bnx2fc_process_enable_conn_cmpl(hba, kcqe); + break; + + case FCOE_KCQE_OPCODE_INIT_FUNC: + if (kcqe->completion_status != + FCOE_KCQE_COMPLETION_STATUS_SUCCESS) { + bnx2fc_init_failure(hba, + kcqe->completion_status); + } else { + set_bit(ADAPTER_STATE_UP, &hba->adapter_state); + bnx2fc_get_link_state(hba); + printk(KERN_INFO PFX "[%.2x]: FCOE_INIT passed\n", + (u8)hba->pcidev->bus->number); + } + break; + + case FCOE_KCQE_OPCODE_DESTROY_FUNC: + if (kcqe->completion_status != + FCOE_KCQE_COMPLETION_STATUS_SUCCESS) { + + printk(KERN_ERR PFX "DESTROY failed\n"); + } else { + printk(KERN_ERR PFX "DESTROY success\n"); + } + hba->flags |= BNX2FC_FLAG_DESTROY_CMPL; + wake_up_interruptible(&hba->destroy_wait); + break; + + case FCOE_KCQE_OPCODE_DISABLE_CONN: + bnx2fc_process_conn_disable_cmpl(hba, kcqe); + break; + + case FCOE_KCQE_OPCODE_DESTROY_CONN: + bnx2fc_process_conn_destroy_cmpl(hba, kcqe); + break; + + case FCOE_KCQE_OPCODE_STAT_FUNC: + if (kcqe->completion_status != + FCOE_KCQE_COMPLETION_STATUS_SUCCESS) + printk(KERN_ERR PFX "STAT failed\n"); + complete(&hba->stat_req_done); + break; + + case FCOE_KCQE_OPCODE_FCOE_ERROR: + /* fall thru */ + default: + printk(KERN_ALERT PFX "unknown opcode 0x%x\n", + kcqe->op_code); + } + } +} + +void bnx2fc_add_2_sq(struct bnx2fc_rport *tgt, u16 xid) +{ + struct fcoe_sqe *sqe; + + sqe = &tgt->sq[tgt->sq_prod_idx]; + + /* Fill SQ WQE */ + sqe->wqe = xid << FCOE_SQE_TASK_ID_SHIFT; + sqe->wqe |= tgt->sq_curr_toggle_bit << FCOE_SQE_TOGGLE_BIT_SHIFT; + + /* Advance SQ Prod Idx */ + if (++tgt->sq_prod_idx == BNX2FC_SQ_WQES_MAX) { + tgt->sq_prod_idx = 0; + tgt->sq_curr_toggle_bit = 1 - tgt->sq_curr_toggle_bit; + } +} + +void bnx2fc_ring_doorbell(struct bnx2fc_rport *tgt) +{ + struct b577xx_doorbell_set_prod ev_doorbell; + u32 msg; + + wmb(); + + memset(&ev_doorbell, 0, sizeof(struct b577xx_doorbell_set_prod)); + ev_doorbell.header.header = B577XX_DOORBELL_HDR_DB_TYPE; + + ev_doorbell.prod = tgt->sq_prod_idx | + (tgt->sq_curr_toggle_bit << 15); + ev_doorbell.header.header |= B577XX_FCOE_CONNECTION_TYPE << + B577XX_DOORBELL_HDR_CONN_TYPE_SHIFT; + msg = *((u32 *)&ev_doorbell); + writel(cpu_to_le32(msg), tgt->ctx_base); + + mmiowb(); + +} + +int bnx2fc_map_doorbell(struct bnx2fc_rport *tgt) +{ + u32 context_id = tgt->context_id; + struct fcoe_port *port = tgt->port; + u32 reg_off; + resource_size_t reg_base; + struct bnx2fc_hba *hba = port->priv; + + reg_base = pci_resource_start(hba->pcidev, + BNX2X_DOORBELL_PCI_BAR); + reg_off = BNX2FC_5771X_DB_PAGE_SIZE * + (context_id & 0x1FFFF) + DPM_TRIGER_TYPE; + tgt->ctx_base = ioremap_nocache(reg_base + reg_off, 4); + if (!tgt->ctx_base) + return -ENOMEM; + return 0; +} + +char *bnx2fc_get_next_rqe(struct bnx2fc_rport *tgt, u8 num_items) +{ + char *buf = (char *)tgt->rq + (tgt->rq_cons_idx * BNX2FC_RQ_BUF_SZ); + + if (tgt->rq_cons_idx + num_items > BNX2FC_RQ_WQES_MAX) + return NULL; + + tgt->rq_cons_idx += num_items; + + if (tgt->rq_cons_idx >= BNX2FC_RQ_WQES_MAX) + tgt->rq_cons_idx -= BNX2FC_RQ_WQES_MAX; + + return buf; +} + +void bnx2fc_return_rqe(struct bnx2fc_rport *tgt, u8 num_items) +{ + /* return the rq buffer */ + u32 next_prod_idx = tgt->rq_prod_idx + num_items; + if ((next_prod_idx & 0x7fff) == BNX2FC_RQ_WQES_MAX) { + /* Wrap around RQ */ + next_prod_idx += 0x8000 - BNX2FC_RQ_WQES_MAX; + } + tgt->rq_prod_idx = next_prod_idx; + tgt->conn_db->rq_prod = tgt->rq_prod_idx; +} + +void bnx2fc_init_cleanup_task(struct bnx2fc_cmd *io_req, + struct fcoe_task_ctx_entry *task, + u16 orig_xid) +{ + u8 task_type = FCOE_TASK_TYPE_EXCHANGE_CLEANUP; + struct bnx2fc_rport *tgt = io_req->tgt; + u32 context_id = tgt->context_id; + + memset(task, 0, sizeof(struct fcoe_task_ctx_entry)); + + /* Tx Write Rx Read */ + task->tx_wr_rx_rd.tx_flags = FCOE_TASK_TX_STATE_EXCHANGE_CLEANUP << + FCOE_TASK_CTX_ENTRY_TXWR_RXRD_TX_STATE_SHIFT; + task->tx_wr_rx_rd.init_flags = task_type << + FCOE_TASK_CTX_ENTRY_TXWR_RXRD_TASK_TYPE_SHIFT; + task->tx_wr_rx_rd.init_flags |= FCOE_TASK_CLASS_TYPE_3 << + FCOE_TASK_CTX_ENTRY_TXWR_RXRD_CLASS_TYPE_SHIFT; + /* Common */ + task->cmn.common_flags = context_id << + FCOE_TASK_CTX_ENTRY_TX_RX_CMN_CID_SHIFT; + task->cmn.general.cleanup_info.task_id = orig_xid; + + +} + +void bnx2fc_init_mp_task(struct bnx2fc_cmd *io_req, + struct fcoe_task_ctx_entry *task) +{ + struct bnx2fc_mp_req *mp_req = &(io_req->mp_req); + struct bnx2fc_rport *tgt = io_req->tgt; + struct fc_frame_header *fc_hdr; + u8 task_type = 0; + u64 *hdr; + u64 temp_hdr[3]; + u32 context_id; + + + /* Obtain task_type */ + if ((io_req->cmd_type == BNX2FC_TASK_MGMT_CMD) || + (io_req->cmd_type == BNX2FC_ELS)) { + task_type = FCOE_TASK_TYPE_MIDPATH; + } else if (io_req->cmd_type == BNX2FC_ABTS) { + task_type = FCOE_TASK_TYPE_ABTS; + } + + memset(task, 0, sizeof(struct fcoe_task_ctx_entry)); + + /* Setup the task from io_req for easy reference */ + io_req->task = task; + + BNX2FC_IO_DBG(io_req, "Init MP task for cmd_type = %d task_type = %d\n", + io_req->cmd_type, task_type); + + /* Tx only */ + if ((task_type == FCOE_TASK_TYPE_MIDPATH) || + (task_type == FCOE_TASK_TYPE_UNSOLICITED)) { + task->tx_wr_only.sgl_ctx.mul_sges.cur_sge_addr.lo = + (u32)mp_req->mp_req_bd_dma; + task->tx_wr_only.sgl_ctx.mul_sges.cur_sge_addr.hi = + (u32)((u64)mp_req->mp_req_bd_dma >> 32); + task->tx_wr_only.sgl_ctx.mul_sges.sgl_size = 1; + BNX2FC_IO_DBG(io_req, "init_mp_task - bd_dma = 0x%llx\n", + (unsigned long long)mp_req->mp_req_bd_dma); + } + + /* Tx Write Rx Read */ + task->tx_wr_rx_rd.tx_flags = FCOE_TASK_TX_STATE_INIT << + FCOE_TASK_CTX_ENTRY_TXWR_RXRD_TX_STATE_SHIFT; + task->tx_wr_rx_rd.init_flags = task_type << + FCOE_TASK_CTX_ENTRY_TXWR_RXRD_TASK_TYPE_SHIFT; + task->tx_wr_rx_rd.init_flags |= FCOE_TASK_DEV_TYPE_DISK << + FCOE_TASK_CTX_ENTRY_TXWR_RXRD_DEV_TYPE_SHIFT; + task->tx_wr_rx_rd.init_flags |= FCOE_TASK_CLASS_TYPE_3 << + FCOE_TASK_CTX_ENTRY_TXWR_RXRD_CLASS_TYPE_SHIFT; + + /* Common */ + task->cmn.data_2_trns = io_req->data_xfer_len; + context_id = tgt->context_id; + task->cmn.common_flags = context_id << + FCOE_TASK_CTX_ENTRY_TX_RX_CMN_CID_SHIFT; + task->cmn.common_flags |= 1 << + FCOE_TASK_CTX_ENTRY_TX_RX_CMN_VALID_SHIFT; + task->cmn.common_flags |= 1 << + FCOE_TASK_CTX_ENTRY_TX_RX_CMN_EXP_FIRST_FRAME_SHIFT; + + /* Rx Write Tx Read */ + fc_hdr = &(mp_req->req_fc_hdr); + if (task_type == FCOE_TASK_TYPE_MIDPATH) { + fc_hdr->fh_ox_id = cpu_to_be16(io_req->xid); + fc_hdr->fh_rx_id = htons(0xffff); + task->rx_wr_tx_rd.rx_id = 0xffff; + } else if (task_type == FCOE_TASK_TYPE_UNSOLICITED) { + fc_hdr->fh_rx_id = cpu_to_be16(io_req->xid); + } + + /* Fill FC Header into middle path buffer */ + hdr = (u64 *) &task->cmn.general.cmd_info.mp_fc_frame.fc_hdr; + memcpy(temp_hdr, fc_hdr, sizeof(temp_hdr)); + hdr[0] = cpu_to_be64(temp_hdr[0]); + hdr[1] = cpu_to_be64(temp_hdr[1]); + hdr[2] = cpu_to_be64(temp_hdr[2]); + + /* Rx Only */ + if (task_type == FCOE_TASK_TYPE_MIDPATH) { + + task->rx_wr_only.sgl_ctx.mul_sges.cur_sge_addr.lo = + (u32)mp_req->mp_resp_bd_dma; + task->rx_wr_only.sgl_ctx.mul_sges.cur_sge_addr.hi = + (u32)((u64)mp_req->mp_resp_bd_dma >> 32); + task->rx_wr_only.sgl_ctx.mul_sges.sgl_size = 1; + } +} + +void bnx2fc_init_task(struct bnx2fc_cmd *io_req, + struct fcoe_task_ctx_entry *task) +{ + u8 task_type; + struct scsi_cmnd *sc_cmd = io_req->sc_cmd; + struct io_bdt *bd_tbl = io_req->bd_tbl; + struct bnx2fc_rport *tgt = io_req->tgt; + u64 *fcp_cmnd; + u64 tmp_fcp_cmnd[4]; + u32 context_id; + int cnt, i; + int bd_count; + + memset(task, 0, sizeof(struct fcoe_task_ctx_entry)); + + /* Setup the task from io_req for easy reference */ + io_req->task = task; + + if (sc_cmd->sc_data_direction == DMA_TO_DEVICE) + task_type = FCOE_TASK_TYPE_WRITE; + else + task_type = FCOE_TASK_TYPE_READ; + + /* Tx only */ + if (task_type == FCOE_TASK_TYPE_WRITE) { + task->tx_wr_only.sgl_ctx.mul_sges.cur_sge_addr.lo = + (u32)bd_tbl->bd_tbl_dma; + task->tx_wr_only.sgl_ctx.mul_sges.cur_sge_addr.hi = + (u32)((u64)bd_tbl->bd_tbl_dma >> 32); + task->tx_wr_only.sgl_ctx.mul_sges.sgl_size = + bd_tbl->bd_valid; + } + + /*Tx Write Rx Read */ + /* Init state to NORMAL */ + task->tx_wr_rx_rd.tx_flags = FCOE_TASK_TX_STATE_NORMAL << + FCOE_TASK_CTX_ENTRY_TXWR_RXRD_TX_STATE_SHIFT; + task->tx_wr_rx_rd.init_flags = task_type << + FCOE_TASK_CTX_ENTRY_TXWR_RXRD_TASK_TYPE_SHIFT; + task->tx_wr_rx_rd.init_flags |= FCOE_TASK_DEV_TYPE_DISK << + FCOE_TASK_CTX_ENTRY_TXWR_RXRD_DEV_TYPE_SHIFT; + task->tx_wr_rx_rd.init_flags |= FCOE_TASK_CLASS_TYPE_3 << + FCOE_TASK_CTX_ENTRY_TXWR_RXRD_CLASS_TYPE_SHIFT; + + /* Common */ + task->cmn.data_2_trns = io_req->data_xfer_len; + context_id = tgt->context_id; + task->cmn.common_flags = context_id << + FCOE_TASK_CTX_ENTRY_TX_RX_CMN_CID_SHIFT; + task->cmn.common_flags |= 1 << + FCOE_TASK_CTX_ENTRY_TX_RX_CMN_VALID_SHIFT; + task->cmn.common_flags |= 1 << + FCOE_TASK_CTX_ENTRY_TX_RX_CMN_EXP_FIRST_FRAME_SHIFT; + + /* Set initiative ownership */ + task->cmn.common_flags |= FCOE_TASK_CTX_ENTRY_TX_RX_CMN_SEQ_INIT; + + /* Set initial seq counter */ + task->cmn.tx_low_seq_cnt = 1; + + /* Set state to "waiting for the first packet" */ + task->cmn.common_flags |= FCOE_TASK_CTX_ENTRY_TX_RX_CMN_EXP_FIRST_FRAME; + + /* Fill FCP_CMND IU */ + fcp_cmnd = (u64 *) + task->cmn.general.cmd_info.fcp_cmd_payload.opaque; + bnx2fc_build_fcp_cmnd(io_req, (struct fcp_cmnd *)&tmp_fcp_cmnd); + + /* swap fcp_cmnd */ + cnt = sizeof(struct fcp_cmnd) / sizeof(u64); + + for (i = 0; i < cnt; i++) { + *fcp_cmnd = cpu_to_be64(tmp_fcp_cmnd[i]); + fcp_cmnd++; + } + + /* Rx Write Tx Read */ + task->rx_wr_tx_rd.rx_id = 0xffff; + + /* Rx Only */ + if (task_type == FCOE_TASK_TYPE_READ) { + + bd_count = bd_tbl->bd_valid; + if (bd_count == 1) { + + struct fcoe_bd_ctx *fcoe_bd_tbl = bd_tbl->bd_tbl; + + task->rx_wr_only.sgl_ctx.single_sge.cur_buf_addr.lo = + fcoe_bd_tbl->buf_addr_lo; + task->rx_wr_only.sgl_ctx.single_sge.cur_buf_addr.hi = + fcoe_bd_tbl->buf_addr_hi; + task->rx_wr_only.sgl_ctx.single_sge.cur_buf_rem = + fcoe_bd_tbl->buf_len; + task->tx_wr_rx_rd.init_flags |= 1 << + FCOE_TASK_CTX_ENTRY_TXWR_RXRD_SINGLE_SGE_SHIFT; + } else { + + task->rx_wr_only.sgl_ctx.mul_sges.cur_sge_addr.lo = + (u32)bd_tbl->bd_tbl_dma; + task->rx_wr_only.sgl_ctx.mul_sges.cur_sge_addr.hi = + (u32)((u64)bd_tbl->bd_tbl_dma >> 32); + task->rx_wr_only.sgl_ctx.mul_sges.sgl_size = + bd_tbl->bd_valid; + } + } +} + +/** + * bnx2fc_setup_task_ctx - allocate and map task context + * + * @hba: pointer to adapter structure + * + * allocate memory for task context, and associated BD table to be used + * by firmware + * + */ +int bnx2fc_setup_task_ctx(struct bnx2fc_hba *hba) +{ + int rc = 0; + struct regpair *task_ctx_bdt; + dma_addr_t addr; + int i; + + /* + * Allocate task context bd table. A page size of bd table + * can map 256 buffers. Each buffer contains 32 task context + * entries. Hence the limit with one page is 8192 task context + * entries. + */ + hba->task_ctx_bd_tbl = dma_alloc_coherent(&hba->pcidev->dev, + PAGE_SIZE, + &hba->task_ctx_bd_dma, + GFP_KERNEL); + if (!hba->task_ctx_bd_tbl) { + printk(KERN_ERR PFX "unable to allocate task context BDT\n"); + rc = -1; + goto out; + } + memset(hba->task_ctx_bd_tbl, 0, PAGE_SIZE); + + /* + * Allocate task_ctx which is an array of pointers pointing to + * a page containing 32 task contexts + */ + hba->task_ctx = kzalloc((BNX2FC_TASK_CTX_ARR_SZ * sizeof(void *)), + GFP_KERNEL); + if (!hba->task_ctx) { + printk(KERN_ERR PFX "unable to allocate task context array\n"); + rc = -1; + goto out1; + } + + /* + * Allocate task_ctx_dma which is an array of dma addresses + */ + hba->task_ctx_dma = kmalloc((BNX2FC_TASK_CTX_ARR_SZ * + sizeof(dma_addr_t)), GFP_KERNEL); + if (!hba->task_ctx_dma) { + printk(KERN_ERR PFX "unable to alloc context mapping array\n"); + rc = -1; + goto out2; + } + + task_ctx_bdt = (struct regpair *)hba->task_ctx_bd_tbl; + for (i = 0; i < BNX2FC_TASK_CTX_ARR_SZ; i++) { + + hba->task_ctx[i] = dma_alloc_coherent(&hba->pcidev->dev, + PAGE_SIZE, + &hba->task_ctx_dma[i], + GFP_KERNEL); + if (!hba->task_ctx[i]) { + printk(KERN_ERR PFX "unable to alloc task context\n"); + rc = -1; + goto out3; + } + memset(hba->task_ctx[i], 0, PAGE_SIZE); + addr = (u64)hba->task_ctx_dma[i]; + task_ctx_bdt->hi = cpu_to_le32((u64)addr >> 32); + task_ctx_bdt->lo = cpu_to_le32((u32)addr); + task_ctx_bdt++; + } + return 0; + +out3: + for (i = 0; i < BNX2FC_TASK_CTX_ARR_SZ; i++) { + if (hba->task_ctx[i]) { + + dma_free_coherent(&hba->pcidev->dev, PAGE_SIZE, + hba->task_ctx[i], hba->task_ctx_dma[i]); + hba->task_ctx[i] = NULL; + } + } + + kfree(hba->task_ctx_dma); + hba->task_ctx_dma = NULL; +out2: + kfree(hba->task_ctx); + hba->task_ctx = NULL; +out1: + dma_free_coherent(&hba->pcidev->dev, PAGE_SIZE, + hba->task_ctx_bd_tbl, hba->task_ctx_bd_dma); + hba->task_ctx_bd_tbl = NULL; +out: + return rc; +} + +void bnx2fc_free_task_ctx(struct bnx2fc_hba *hba) +{ + int i; + + if (hba->task_ctx_bd_tbl) { + dma_free_coherent(&hba->pcidev->dev, PAGE_SIZE, + hba->task_ctx_bd_tbl, + hba->task_ctx_bd_dma); + hba->task_ctx_bd_tbl = NULL; + } + + if (hba->task_ctx) { + for (i = 0; i < BNX2FC_TASK_CTX_ARR_SZ; i++) { + if (hba->task_ctx[i]) { + dma_free_coherent(&hba->pcidev->dev, PAGE_SIZE, + hba->task_ctx[i], + hba->task_ctx_dma[i]); + hba->task_ctx[i] = NULL; + } + } + kfree(hba->task_ctx); + hba->task_ctx = NULL; + } + + kfree(hba->task_ctx_dma); + hba->task_ctx_dma = NULL; +} + +static void bnx2fc_free_hash_table(struct bnx2fc_hba *hba) +{ + int i; + int segment_count; + int hash_table_size; + u32 *pbl; + + segment_count = hba->hash_tbl_segment_count; + hash_table_size = BNX2FC_NUM_MAX_SESS * BNX2FC_MAX_ROWS_IN_HASH_TBL * + sizeof(struct fcoe_hash_table_entry); + + pbl = hba->hash_tbl_pbl; + for (i = 0; i < segment_count; ++i) { + dma_addr_t dma_address; + + dma_address = le32_to_cpu(*pbl); + ++pbl; + dma_address += ((u64)le32_to_cpu(*pbl)) << 32; + ++pbl; + dma_free_coherent(&hba->pcidev->dev, + BNX2FC_HASH_TBL_CHUNK_SIZE, + hba->hash_tbl_segments[i], + dma_address); + + } + + if (hba->hash_tbl_pbl) { + dma_free_coherent(&hba->pcidev->dev, PAGE_SIZE, + hba->hash_tbl_pbl, + hba->hash_tbl_pbl_dma); + hba->hash_tbl_pbl = NULL; + } +} + +static int bnx2fc_allocate_hash_table(struct bnx2fc_hba *hba) +{ + int i; + int hash_table_size; + int segment_count; + int segment_array_size; + int dma_segment_array_size; + dma_addr_t *dma_segment_array; + u32 *pbl; + + hash_table_size = BNX2FC_NUM_MAX_SESS * BNX2FC_MAX_ROWS_IN_HASH_TBL * + sizeof(struct fcoe_hash_table_entry); + + segment_count = hash_table_size + BNX2FC_HASH_TBL_CHUNK_SIZE - 1; + segment_count /= BNX2FC_HASH_TBL_CHUNK_SIZE; + hba->hash_tbl_segment_count = segment_count; + + segment_array_size = segment_count * sizeof(*hba->hash_tbl_segments); + hba->hash_tbl_segments = kzalloc(segment_array_size, GFP_KERNEL); + if (!hba->hash_tbl_segments) { + printk(KERN_ERR PFX "hash table pointers alloc failed\n"); + return -ENOMEM; + } + dma_segment_array_size = segment_count * sizeof(*dma_segment_array); + dma_segment_array = kzalloc(dma_segment_array_size, GFP_KERNEL); + if (!dma_segment_array) { + printk(KERN_ERR PFX "hash table pointers (dma) alloc failed\n"); + return -ENOMEM; + } + + for (i = 0; i < segment_count; ++i) { + hba->hash_tbl_segments[i] = + dma_alloc_coherent(&hba->pcidev->dev, + BNX2FC_HASH_TBL_CHUNK_SIZE, + &dma_segment_array[i], + GFP_KERNEL); + if (!hba->hash_tbl_segments[i]) { + printk(KERN_ERR PFX "hash segment alloc failed\n"); + while (--i >= 0) { + dma_free_coherent(&hba->pcidev->dev, + BNX2FC_HASH_TBL_CHUNK_SIZE, + hba->hash_tbl_segments[i], + dma_segment_array[i]); + hba->hash_tbl_segments[i] = NULL; + } + kfree(dma_segment_array); + return -ENOMEM; + } + memset(hba->hash_tbl_segments[i], 0, + BNX2FC_HASH_TBL_CHUNK_SIZE); + } + + hba->hash_tbl_pbl = dma_alloc_coherent(&hba->pcidev->dev, + PAGE_SIZE, + &hba->hash_tbl_pbl_dma, + GFP_KERNEL); + if (!hba->hash_tbl_pbl) { + printk(KERN_ERR PFX "hash table pbl alloc failed\n"); + kfree(dma_segment_array); + return -ENOMEM; + } + memset(hba->hash_tbl_pbl, 0, PAGE_SIZE); + + pbl = hba->hash_tbl_pbl; + for (i = 0; i < segment_count; ++i) { + u64 paddr = dma_segment_array[i]; + *pbl = cpu_to_le32((u32) paddr); + ++pbl; + *pbl = cpu_to_le32((u32) (paddr >> 32)); + ++pbl; + } + pbl = hba->hash_tbl_pbl; + i = 0; + while (*pbl && *(pbl + 1)) { + u32 lo; + u32 hi; + lo = *pbl; + ++pbl; + hi = *pbl; + ++pbl; + ++i; + } + kfree(dma_segment_array); + return 0; +} + +/** + * bnx2fc_setup_fw_resc - Allocate and map hash table and dummy buffer + * + * @hba: Pointer to adapter structure + * + */ +int bnx2fc_setup_fw_resc(struct bnx2fc_hba *hba) +{ + u64 addr; + u32 mem_size; + int i; + + if (bnx2fc_allocate_hash_table(hba)) + return -ENOMEM; + + mem_size = BNX2FC_NUM_MAX_SESS * sizeof(struct regpair); + hba->t2_hash_tbl_ptr = dma_alloc_coherent(&hba->pcidev->dev, mem_size, + &hba->t2_hash_tbl_ptr_dma, + GFP_KERNEL); + if (!hba->t2_hash_tbl_ptr) { + printk(KERN_ERR PFX "unable to allocate t2 hash table ptr\n"); + bnx2fc_free_fw_resc(hba); + return -ENOMEM; + } + memset(hba->t2_hash_tbl_ptr, 0x00, mem_size); + + mem_size = BNX2FC_NUM_MAX_SESS * + sizeof(struct fcoe_t2_hash_table_entry); + hba->t2_hash_tbl = dma_alloc_coherent(&hba->pcidev->dev, mem_size, + &hba->t2_hash_tbl_dma, + GFP_KERNEL); + if (!hba->t2_hash_tbl) { + printk(KERN_ERR PFX "unable to allocate t2 hash table\n"); + bnx2fc_free_fw_resc(hba); + return -ENOMEM; + } + memset(hba->t2_hash_tbl, 0x00, mem_size); + for (i = 0; i < BNX2FC_NUM_MAX_SESS; i++) { + addr = (unsigned long) hba->t2_hash_tbl_dma + + ((i+1) * sizeof(struct fcoe_t2_hash_table_entry)); + hba->t2_hash_tbl[i].next.lo = addr & 0xffffffff; + hba->t2_hash_tbl[i].next.hi = addr >> 32; + } + + hba->dummy_buffer = dma_alloc_coherent(&hba->pcidev->dev, + PAGE_SIZE, &hba->dummy_buf_dma, + GFP_KERNEL); + if (!hba->dummy_buffer) { + printk(KERN_ERR PFX "unable to alloc MP Dummy Buffer\n"); + bnx2fc_free_fw_resc(hba); + return -ENOMEM; + } + + hba->stats_buffer = dma_alloc_coherent(&hba->pcidev->dev, + PAGE_SIZE, + &hba->stats_buf_dma, + GFP_KERNEL); + if (!hba->stats_buffer) { + printk(KERN_ERR PFX "unable to alloc Stats Buffer\n"); + bnx2fc_free_fw_resc(hba); + return -ENOMEM; + } + memset(hba->stats_buffer, 0x00, PAGE_SIZE); + + return 0; +} + +void bnx2fc_free_fw_resc(struct bnx2fc_hba *hba) +{ + u32 mem_size; + + if (hba->stats_buffer) { + dma_free_coherent(&hba->pcidev->dev, PAGE_SIZE, + hba->stats_buffer, hba->stats_buf_dma); + hba->stats_buffer = NULL; + } + + if (hba->dummy_buffer) { + dma_free_coherent(&hba->pcidev->dev, PAGE_SIZE, + hba->dummy_buffer, hba->dummy_buf_dma); + hba->dummy_buffer = NULL; + } + + if (hba->t2_hash_tbl_ptr) { + mem_size = BNX2FC_NUM_MAX_SESS * sizeof(struct regpair); + dma_free_coherent(&hba->pcidev->dev, mem_size, + hba->t2_hash_tbl_ptr, + hba->t2_hash_tbl_ptr_dma); + hba->t2_hash_tbl_ptr = NULL; + } + + if (hba->t2_hash_tbl) { + mem_size = BNX2FC_NUM_MAX_SESS * + sizeof(struct fcoe_t2_hash_table_entry); + dma_free_coherent(&hba->pcidev->dev, mem_size, + hba->t2_hash_tbl, hba->t2_hash_tbl_dma); + hba->t2_hash_tbl = NULL; + } + bnx2fc_free_hash_table(hba); +} diff --git a/drivers/scsi/bnx2fc/bnx2fc_io.c b/drivers/scsi/bnx2fc/bnx2fc_io.c new file mode 100644 index 000000000000..d3fc302c241a --- /dev/null +++ b/drivers/scsi/bnx2fc/bnx2fc_io.c @@ -0,0 +1,1856 @@ +/* bnx2fc_io.c: Broadcom NetXtreme II Linux FCoE offload driver. + * IO manager and SCSI IO processing. + * + * Copyright (c) 2008 - 2010 Broadcom Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation. + * + * Written by: Bhanu Prakash Gollapudi (bprakash@broadcom.com) + */ + +#include "bnx2fc.h" + +#define RESERVE_FREE_LIST_INDEX num_possible_cpus() + +static int bnx2fc_split_bd(struct bnx2fc_cmd *io_req, u64 addr, int sg_len, + int bd_index); +static int bnx2fc_map_sg(struct bnx2fc_cmd *io_req); +static void bnx2fc_build_bd_list_from_sg(struct bnx2fc_cmd *io_req); +static int bnx2fc_post_io_req(struct bnx2fc_rport *tgt, + struct bnx2fc_cmd *io_req); +static void bnx2fc_unmap_sg_list(struct bnx2fc_cmd *io_req); +static void bnx2fc_free_mp_resc(struct bnx2fc_cmd *io_req); +static void bnx2fc_parse_fcp_rsp(struct bnx2fc_cmd *io_req, + struct fcoe_fcp_rsp_payload *fcp_rsp, + u8 num_rq); + +void bnx2fc_cmd_timer_set(struct bnx2fc_cmd *io_req, + unsigned int timer_msec) +{ + struct bnx2fc_hba *hba = io_req->port->priv; + + if (queue_delayed_work(hba->timer_work_queue, &io_req->timeout_work, + msecs_to_jiffies(timer_msec))) + kref_get(&io_req->refcount); +} + +static void bnx2fc_cmd_timeout(struct work_struct *work) +{ + struct bnx2fc_cmd *io_req = container_of(work, struct bnx2fc_cmd, + timeout_work.work); + struct fc_lport *lport; + struct fc_rport_priv *rdata; + u8 cmd_type = io_req->cmd_type; + struct bnx2fc_rport *tgt = io_req->tgt; + int logo_issued; + int rc; + + BNX2FC_IO_DBG(io_req, "cmd_timeout, cmd_type = %d," + "req_flags = %lx\n", cmd_type, io_req->req_flags); + + spin_lock_bh(&tgt->tgt_lock); + if (test_and_clear_bit(BNX2FC_FLAG_ISSUE_RRQ, &io_req->req_flags)) { + clear_bit(BNX2FC_FLAG_RETIRE_OXID, &io_req->req_flags); + /* + * ideally we should hold the io_req until RRQ complets, + * and release io_req from timeout hold. + */ + spin_unlock_bh(&tgt->tgt_lock); + bnx2fc_send_rrq(io_req); + return; + } + if (test_and_clear_bit(BNX2FC_FLAG_RETIRE_OXID, &io_req->req_flags)) { + BNX2FC_IO_DBG(io_req, "IO ready for reuse now\n"); + goto done; + } + + switch (cmd_type) { + case BNX2FC_SCSI_CMD: + if (test_and_clear_bit(BNX2FC_FLAG_EH_ABORT, + &io_req->req_flags)) { + /* Handle eh_abort timeout */ + BNX2FC_IO_DBG(io_req, "eh_abort timed out\n"); + complete(&io_req->tm_done); + } else if (test_bit(BNX2FC_FLAG_ISSUE_ABTS, + &io_req->req_flags)) { + /* Handle internally generated ABTS timeout */ + BNX2FC_IO_DBG(io_req, "ABTS timed out refcnt = %d\n", + io_req->refcount.refcount.counter); + if (!(test_and_set_bit(BNX2FC_FLAG_ABTS_DONE, + &io_req->req_flags))) { + + lport = io_req->port->lport; + rdata = io_req->tgt->rdata; + logo_issued = test_and_set_bit( + BNX2FC_FLAG_EXPL_LOGO, + &tgt->flags); + kref_put(&io_req->refcount, bnx2fc_cmd_release); + spin_unlock_bh(&tgt->tgt_lock); + + /* Explicitly logo the target */ + if (!logo_issued) { + BNX2FC_IO_DBG(io_req, "Explicit " + "logo - tgt flags = 0x%lx\n", + tgt->flags); + + mutex_lock(&lport->disc.disc_mutex); + lport->tt.rport_logoff(rdata); + mutex_unlock(&lport->disc.disc_mutex); + } + return; + } + } else { + /* Hanlde IO timeout */ + BNX2FC_IO_DBG(io_req, "IO timed out. issue ABTS\n"); + if (test_and_set_bit(BNX2FC_FLAG_IO_COMPL, + &io_req->req_flags)) { + BNX2FC_IO_DBG(io_req, "IO completed before " + " timer expiry\n"); + goto done; + } + + if (!test_and_set_bit(BNX2FC_FLAG_ISSUE_ABTS, + &io_req->req_flags)) { + rc = bnx2fc_initiate_abts(io_req); + if (rc == SUCCESS) + goto done; + /* + * Explicitly logo the target if + * abts initiation fails + */ + lport = io_req->port->lport; + rdata = io_req->tgt->rdata; + logo_issued = test_and_set_bit( + BNX2FC_FLAG_EXPL_LOGO, + &tgt->flags); + kref_put(&io_req->refcount, bnx2fc_cmd_release); + spin_unlock_bh(&tgt->tgt_lock); + + if (!logo_issued) { + BNX2FC_IO_DBG(io_req, "Explicit " + "logo - tgt flags = 0x%lx\n", + tgt->flags); + + + mutex_lock(&lport->disc.disc_mutex); + lport->tt.rport_logoff(rdata); + mutex_unlock(&lport->disc.disc_mutex); + } + return; + } else { + BNX2FC_IO_DBG(io_req, "IO already in " + "ABTS processing\n"); + } + } + break; + case BNX2FC_ELS: + + if (test_bit(BNX2FC_FLAG_ISSUE_ABTS, &io_req->req_flags)) { + BNX2FC_IO_DBG(io_req, "ABTS for ELS timed out\n"); + + if (!test_and_set_bit(BNX2FC_FLAG_ABTS_DONE, + &io_req->req_flags)) { + lport = io_req->port->lport; + rdata = io_req->tgt->rdata; + logo_issued = test_and_set_bit( + BNX2FC_FLAG_EXPL_LOGO, + &tgt->flags); + kref_put(&io_req->refcount, bnx2fc_cmd_release); + spin_unlock_bh(&tgt->tgt_lock); + + /* Explicitly logo the target */ + if (!logo_issued) { + BNX2FC_IO_DBG(io_req, "Explicitly logo" + "(els)\n"); + mutex_lock(&lport->disc.disc_mutex); + lport->tt.rport_logoff(rdata); + mutex_unlock(&lport->disc.disc_mutex); + } + return; + } + } else { + /* + * Handle ELS timeout. + * tgt_lock is used to sync compl path and timeout + * path. If els compl path is processing this IO, we + * have nothing to do here, just release the timer hold + */ + BNX2FC_IO_DBG(io_req, "ELS timed out\n"); + if (test_and_set_bit(BNX2FC_FLAG_ELS_DONE, + &io_req->req_flags)) + goto done; + + /* Indicate the cb_func that this ELS is timed out */ + set_bit(BNX2FC_FLAG_ELS_TIMEOUT, &io_req->req_flags); + + if ((io_req->cb_func) && (io_req->cb_arg)) { + io_req->cb_func(io_req->cb_arg); + io_req->cb_arg = NULL; + } + } + break; + default: + printk(KERN_ERR PFX "cmd_timeout: invalid cmd_type %d\n", + cmd_type); + break; + } + +done: + /* release the cmd that was held when timer was set */ + kref_put(&io_req->refcount, bnx2fc_cmd_release); + spin_unlock_bh(&tgt->tgt_lock); +} + +static void bnx2fc_scsi_done(struct bnx2fc_cmd *io_req, int err_code) +{ + /* Called with host lock held */ + struct scsi_cmnd *sc_cmd = io_req->sc_cmd; + + /* + * active_cmd_queue may have other command types as well, + * and during flush operation, we want to error back only + * scsi commands. + */ + if (io_req->cmd_type != BNX2FC_SCSI_CMD) + return; + + BNX2FC_IO_DBG(io_req, "scsi_done. err_code = 0x%x\n", err_code); + bnx2fc_unmap_sg_list(io_req); + io_req->sc_cmd = NULL; + if (!sc_cmd) { + printk(KERN_ERR PFX "scsi_done - sc_cmd NULL. " + "IO(0x%x) already cleaned up\n", + io_req->xid); + return; + } + sc_cmd->result = err_code << 16; + + BNX2FC_IO_DBG(io_req, "sc=%p, result=0x%x, retries=%d, allowed=%d\n", + sc_cmd, host_byte(sc_cmd->result), sc_cmd->retries, + sc_cmd->allowed); + scsi_set_resid(sc_cmd, scsi_bufflen(sc_cmd)); + sc_cmd->SCp.ptr = NULL; + sc_cmd->scsi_done(sc_cmd); +} + +struct bnx2fc_cmd_mgr *bnx2fc_cmd_mgr_alloc(struct bnx2fc_hba *hba, + u16 min_xid, u16 max_xid) +{ + struct bnx2fc_cmd_mgr *cmgr; + struct io_bdt *bdt_info; + struct bnx2fc_cmd *io_req; + size_t len; + u32 mem_size; + u16 xid; + int i; + int num_ios, num_pri_ios; + size_t bd_tbl_sz; + int arr_sz = num_possible_cpus() + 1; + + if (max_xid <= min_xid || max_xid == FC_XID_UNKNOWN) { + printk(KERN_ERR PFX "cmd_mgr_alloc: Invalid min_xid 0x%x \ + and max_xid 0x%x\n", min_xid, max_xid); + return NULL; + } + BNX2FC_MISC_DBG("min xid 0x%x, max xid 0x%x\n", min_xid, max_xid); + + num_ios = max_xid - min_xid + 1; + len = (num_ios * (sizeof(struct bnx2fc_cmd *))); + len += sizeof(struct bnx2fc_cmd_mgr); + + cmgr = kzalloc(len, GFP_KERNEL); + if (!cmgr) { + printk(KERN_ERR PFX "failed to alloc cmgr\n"); + return NULL; + } + + cmgr->free_list = kzalloc(sizeof(*cmgr->free_list) * + arr_sz, GFP_KERNEL); + if (!cmgr->free_list) { + printk(KERN_ERR PFX "failed to alloc free_list\n"); + goto mem_err; + } + + cmgr->free_list_lock = kzalloc(sizeof(*cmgr->free_list_lock) * + arr_sz, GFP_KERNEL); + if (!cmgr->free_list_lock) { + printk(KERN_ERR PFX "failed to alloc free_list_lock\n"); + goto mem_err; + } + + cmgr->hba = hba; + cmgr->cmds = (struct bnx2fc_cmd **)(cmgr + 1); + + for (i = 0; i < arr_sz; i++) { + INIT_LIST_HEAD(&cmgr->free_list[i]); + spin_lock_init(&cmgr->free_list_lock[i]); + } + + /* + * Pre-allocated pool of bnx2fc_cmds. + * Last entry in the free list array is the free list + * of slow path requests. + */ + xid = BNX2FC_MIN_XID; + num_pri_ios = num_ios - BNX2FC_ELSTM_XIDS; + for (i = 0; i < num_ios; i++) { + io_req = kzalloc(sizeof(*io_req), GFP_KERNEL); + + if (!io_req) { + printk(KERN_ERR PFX "failed to alloc io_req\n"); + goto mem_err; + } + + INIT_LIST_HEAD(&io_req->link); + INIT_DELAYED_WORK(&io_req->timeout_work, bnx2fc_cmd_timeout); + + io_req->xid = xid++; + if (i < num_pri_ios) + list_add_tail(&io_req->link, + &cmgr->free_list[io_req->xid % + num_possible_cpus()]); + else + list_add_tail(&io_req->link, + &cmgr->free_list[num_possible_cpus()]); + io_req++; + } + + /* Allocate pool of io_bdts - one for each bnx2fc_cmd */ + mem_size = num_ios * sizeof(struct io_bdt *); + cmgr->io_bdt_pool = kmalloc(mem_size, GFP_KERNEL); + if (!cmgr->io_bdt_pool) { + printk(KERN_ERR PFX "failed to alloc io_bdt_pool\n"); + goto mem_err; + } + + mem_size = sizeof(struct io_bdt); + for (i = 0; i < num_ios; i++) { + cmgr->io_bdt_pool[i] = kmalloc(mem_size, GFP_KERNEL); + if (!cmgr->io_bdt_pool[i]) { + printk(KERN_ERR PFX "failed to alloc " + "io_bdt_pool[%d]\n", i); + goto mem_err; + } + } + + /* Allocate an map fcoe_bdt_ctx structures */ + bd_tbl_sz = BNX2FC_MAX_BDS_PER_CMD * sizeof(struct fcoe_bd_ctx); + for (i = 0; i < num_ios; i++) { + bdt_info = cmgr->io_bdt_pool[i]; + bdt_info->bd_tbl = dma_alloc_coherent(&hba->pcidev->dev, + bd_tbl_sz, + &bdt_info->bd_tbl_dma, + GFP_KERNEL); + if (!bdt_info->bd_tbl) { + printk(KERN_ERR PFX "failed to alloc " + "bdt_tbl[%d]\n", i); + goto mem_err; + } + } + + return cmgr; + +mem_err: + bnx2fc_cmd_mgr_free(cmgr); + return NULL; +} + +void bnx2fc_cmd_mgr_free(struct bnx2fc_cmd_mgr *cmgr) +{ + struct io_bdt *bdt_info; + struct bnx2fc_hba *hba = cmgr->hba; + size_t bd_tbl_sz; + u16 min_xid = BNX2FC_MIN_XID; + u16 max_xid = BNX2FC_MAX_XID; + int num_ios; + int i; + + num_ios = max_xid - min_xid + 1; + + /* Free fcoe_bdt_ctx structures */ + if (!cmgr->io_bdt_pool) + goto free_cmd_pool; + + bd_tbl_sz = BNX2FC_MAX_BDS_PER_CMD * sizeof(struct fcoe_bd_ctx); + for (i = 0; i < num_ios; i++) { + bdt_info = cmgr->io_bdt_pool[i]; + if (bdt_info->bd_tbl) { + dma_free_coherent(&hba->pcidev->dev, bd_tbl_sz, + bdt_info->bd_tbl, + bdt_info->bd_tbl_dma); + bdt_info->bd_tbl = NULL; + } + } + + /* Destroy io_bdt pool */ + for (i = 0; i < num_ios; i++) { + kfree(cmgr->io_bdt_pool[i]); + cmgr->io_bdt_pool[i] = NULL; + } + + kfree(cmgr->io_bdt_pool); + cmgr->io_bdt_pool = NULL; + +free_cmd_pool: + kfree(cmgr->free_list_lock); + + /* Destroy cmd pool */ + if (!cmgr->free_list) + goto free_cmgr; + + for (i = 0; i < num_possible_cpus() + 1; i++) { + struct list_head *list; + struct list_head *tmp; + + list_for_each_safe(list, tmp, &cmgr->free_list[i]) { + struct bnx2fc_cmd *io_req = (struct bnx2fc_cmd *)list; + list_del(&io_req->link); + kfree(io_req); + } + } + kfree(cmgr->free_list); +free_cmgr: + /* Free command manager itself */ + kfree(cmgr); +} + +struct bnx2fc_cmd *bnx2fc_elstm_alloc(struct bnx2fc_rport *tgt, int type) +{ + struct fcoe_port *port = tgt->port; + struct bnx2fc_hba *hba = port->priv; + struct bnx2fc_cmd_mgr *cmd_mgr = hba->cmd_mgr; + struct bnx2fc_cmd *io_req; + struct list_head *listp; + struct io_bdt *bd_tbl; + int index = RESERVE_FREE_LIST_INDEX; + u32 max_sqes; + u16 xid; + + max_sqes = tgt->max_sqes; + switch (type) { + case BNX2FC_TASK_MGMT_CMD: + max_sqes = BNX2FC_TM_MAX_SQES; + break; + case BNX2FC_ELS: + max_sqes = BNX2FC_ELS_MAX_SQES; + break; + default: + break; + } + + /* + * NOTE: Free list insertions and deletions are protected with + * cmgr lock + */ + spin_lock_bh(&cmd_mgr->free_list_lock[index]); + if ((list_empty(&(cmd_mgr->free_list[index]))) || + (tgt->num_active_ios.counter >= max_sqes)) { + BNX2FC_TGT_DBG(tgt, "No free els_tm cmds available " + "ios(%d):sqes(%d)\n", + tgt->num_active_ios.counter, tgt->max_sqes); + if (list_empty(&(cmd_mgr->free_list[index]))) + printk(KERN_ERR PFX "elstm_alloc: list_empty\n"); + spin_unlock_bh(&cmd_mgr->free_list_lock[index]); + return NULL; + } + + listp = (struct list_head *) + cmd_mgr->free_list[index].next; + list_del_init(listp); + io_req = (struct bnx2fc_cmd *) listp; + xid = io_req->xid; + cmd_mgr->cmds[xid] = io_req; + atomic_inc(&tgt->num_active_ios); + spin_unlock_bh(&cmd_mgr->free_list_lock[index]); + + INIT_LIST_HEAD(&io_req->link); + + io_req->port = port; + io_req->cmd_mgr = cmd_mgr; + io_req->req_flags = 0; + io_req->cmd_type = type; + + /* Bind io_bdt for this io_req */ + /* Have a static link between io_req and io_bdt_pool */ + bd_tbl = io_req->bd_tbl = cmd_mgr->io_bdt_pool[xid]; + bd_tbl->io_req = io_req; + + /* Hold the io_req against deletion */ + kref_init(&io_req->refcount); + return io_req; +} +static struct bnx2fc_cmd *bnx2fc_cmd_alloc(struct bnx2fc_rport *tgt) +{ + struct fcoe_port *port = tgt->port; + struct bnx2fc_hba *hba = port->priv; + struct bnx2fc_cmd_mgr *cmd_mgr = hba->cmd_mgr; + struct bnx2fc_cmd *io_req; + struct list_head *listp; + struct io_bdt *bd_tbl; + u32 max_sqes; + u16 xid; + int index = get_cpu(); + + max_sqes = BNX2FC_SCSI_MAX_SQES; + /* + * NOTE: Free list insertions and deletions are protected with + * cmgr lock + */ + spin_lock_bh(&cmd_mgr->free_list_lock[index]); + if ((list_empty(&cmd_mgr->free_list[index])) || + (tgt->num_active_ios.counter >= max_sqes)) { + spin_unlock_bh(&cmd_mgr->free_list_lock[index]); + put_cpu(); + return NULL; + } + + listp = (struct list_head *) + cmd_mgr->free_list[index].next; + list_del_init(listp); + io_req = (struct bnx2fc_cmd *) listp; + xid = io_req->xid; + cmd_mgr->cmds[xid] = io_req; + atomic_inc(&tgt->num_active_ios); + spin_unlock_bh(&cmd_mgr->free_list_lock[index]); + put_cpu(); + + INIT_LIST_HEAD(&io_req->link); + + io_req->port = port; + io_req->cmd_mgr = cmd_mgr; + io_req->req_flags = 0; + + /* Bind io_bdt for this io_req */ + /* Have a static link between io_req and io_bdt_pool */ + bd_tbl = io_req->bd_tbl = cmd_mgr->io_bdt_pool[xid]; + bd_tbl->io_req = io_req; + + /* Hold the io_req against deletion */ + kref_init(&io_req->refcount); + return io_req; +} + +void bnx2fc_cmd_release(struct kref *ref) +{ + struct bnx2fc_cmd *io_req = container_of(ref, + struct bnx2fc_cmd, refcount); + struct bnx2fc_cmd_mgr *cmd_mgr = io_req->cmd_mgr; + int index; + + if (io_req->cmd_type == BNX2FC_SCSI_CMD) + index = io_req->xid % num_possible_cpus(); + else + index = RESERVE_FREE_LIST_INDEX; + + + spin_lock_bh(&cmd_mgr->free_list_lock[index]); + if (io_req->cmd_type != BNX2FC_SCSI_CMD) + bnx2fc_free_mp_resc(io_req); + cmd_mgr->cmds[io_req->xid] = NULL; + /* Delete IO from retire queue */ + list_del_init(&io_req->link); + /* Add it to the free list */ + list_add(&io_req->link, + &cmd_mgr->free_list[index]); + atomic_dec(&io_req->tgt->num_active_ios); + spin_unlock_bh(&cmd_mgr->free_list_lock[index]); + +} + +static void bnx2fc_free_mp_resc(struct bnx2fc_cmd *io_req) +{ + struct bnx2fc_mp_req *mp_req = &(io_req->mp_req); + struct bnx2fc_hba *hba = io_req->port->priv; + size_t sz = sizeof(struct fcoe_bd_ctx); + + /* clear tm flags */ + mp_req->tm_flags = 0; + if (mp_req->mp_req_bd) { + dma_free_coherent(&hba->pcidev->dev, sz, + mp_req->mp_req_bd, + mp_req->mp_req_bd_dma); + mp_req->mp_req_bd = NULL; + } + if (mp_req->mp_resp_bd) { + dma_free_coherent(&hba->pcidev->dev, sz, + mp_req->mp_resp_bd, + mp_req->mp_resp_bd_dma); + mp_req->mp_resp_bd = NULL; + } + if (mp_req->req_buf) { + dma_free_coherent(&hba->pcidev->dev, PAGE_SIZE, + mp_req->req_buf, + mp_req->req_buf_dma); + mp_req->req_buf = NULL; + } + if (mp_req->resp_buf) { + dma_free_coherent(&hba->pcidev->dev, PAGE_SIZE, + mp_req->resp_buf, + mp_req->resp_buf_dma); + mp_req->resp_buf = NULL; + } +} + +int bnx2fc_init_mp_req(struct bnx2fc_cmd *io_req) +{ + struct bnx2fc_mp_req *mp_req; + struct fcoe_bd_ctx *mp_req_bd; + struct fcoe_bd_ctx *mp_resp_bd; + struct bnx2fc_hba *hba = io_req->port->priv; + dma_addr_t addr; + size_t sz; + + mp_req = (struct bnx2fc_mp_req *)&(io_req->mp_req); + memset(mp_req, 0, sizeof(struct bnx2fc_mp_req)); + + mp_req->req_len = sizeof(struct fcp_cmnd); + io_req->data_xfer_len = mp_req->req_len; + mp_req->req_buf = dma_alloc_coherent(&hba->pcidev->dev, PAGE_SIZE, + &mp_req->req_buf_dma, + GFP_ATOMIC); + if (!mp_req->req_buf) { + printk(KERN_ERR PFX "unable to alloc MP req buffer\n"); + bnx2fc_free_mp_resc(io_req); + return FAILED; + } + + mp_req->resp_buf = dma_alloc_coherent(&hba->pcidev->dev, PAGE_SIZE, + &mp_req->resp_buf_dma, + GFP_ATOMIC); + if (!mp_req->resp_buf) { + printk(KERN_ERR PFX "unable to alloc TM resp buffer\n"); + bnx2fc_free_mp_resc(io_req); + return FAILED; + } + memset(mp_req->req_buf, 0, PAGE_SIZE); + memset(mp_req->resp_buf, 0, PAGE_SIZE); + + /* Allocate and map mp_req_bd and mp_resp_bd */ + sz = sizeof(struct fcoe_bd_ctx); + mp_req->mp_req_bd = dma_alloc_coherent(&hba->pcidev->dev, sz, + &mp_req->mp_req_bd_dma, + GFP_ATOMIC); + if (!mp_req->mp_req_bd) { + printk(KERN_ERR PFX "unable to alloc MP req bd\n"); + bnx2fc_free_mp_resc(io_req); + return FAILED; + } + mp_req->mp_resp_bd = dma_alloc_coherent(&hba->pcidev->dev, sz, + &mp_req->mp_resp_bd_dma, + GFP_ATOMIC); + if (!mp_req->mp_req_bd) { + printk(KERN_ERR PFX "unable to alloc MP resp bd\n"); + bnx2fc_free_mp_resc(io_req); + return FAILED; + } + /* Fill bd table */ + addr = mp_req->req_buf_dma; + mp_req_bd = mp_req->mp_req_bd; + mp_req_bd->buf_addr_lo = (u32)addr & 0xffffffff; + mp_req_bd->buf_addr_hi = (u32)((u64)addr >> 32); + mp_req_bd->buf_len = PAGE_SIZE; + mp_req_bd->flags = 0; + + /* + * MP buffer is either a task mgmt command or an ELS. + * So the assumption is that it consumes a single bd + * entry in the bd table + */ + mp_resp_bd = mp_req->mp_resp_bd; + addr = mp_req->resp_buf_dma; + mp_resp_bd->buf_addr_lo = (u32)addr & 0xffffffff; + mp_resp_bd->buf_addr_hi = (u32)((u64)addr >> 32); + mp_resp_bd->buf_len = PAGE_SIZE; + mp_resp_bd->flags = 0; + + return SUCCESS; +} + +static int bnx2fc_initiate_tmf(struct scsi_cmnd *sc_cmd, u8 tm_flags) +{ + struct fc_lport *lport; + struct fc_rport *rport = starget_to_rport(scsi_target(sc_cmd->device)); + struct fc_rport_libfc_priv *rp = rport->dd_data; + struct fcoe_port *port; + struct bnx2fc_hba *hba; + struct bnx2fc_rport *tgt; + struct bnx2fc_cmd *io_req; + struct bnx2fc_mp_req *tm_req; + struct fcoe_task_ctx_entry *task; + struct fcoe_task_ctx_entry *task_page; + struct Scsi_Host *host = sc_cmd->device->host; + struct fc_frame_header *fc_hdr; + struct fcp_cmnd *fcp_cmnd; + int task_idx, index; + int rc = SUCCESS; + u16 xid; + u32 sid, did; + unsigned long start = jiffies; + + lport = shost_priv(host); + port = lport_priv(lport); + hba = port->priv; + + if (rport == NULL) { + printk(KERN_ALERT PFX "device_reset: rport is NULL\n"); + rc = FAILED; + goto tmf_err; + } + + rc = fc_block_scsi_eh(sc_cmd); + if (rc) + return rc; + + if (lport->state != LPORT_ST_READY || !(lport->link_up)) { + printk(KERN_ERR PFX "device_reset: link is not ready\n"); + rc = FAILED; + goto tmf_err; + } + /* rport and tgt are allocated together, so tgt should be non-NULL */ + tgt = (struct bnx2fc_rport *)&rp[1]; + + if (!(test_bit(BNX2FC_FLAG_SESSION_READY, &tgt->flags))) { + printk(KERN_ERR PFX "device_reset: tgt not offloaded\n"); + rc = FAILED; + goto tmf_err; + } +retry_tmf: + io_req = bnx2fc_elstm_alloc(tgt, BNX2FC_TASK_MGMT_CMD); + if (!io_req) { + if (time_after(jiffies, start + HZ)) { + printk(KERN_ERR PFX "tmf: Failed TMF"); + rc = FAILED; + goto tmf_err; + } + msleep(20); + goto retry_tmf; + } + /* Initialize rest of io_req fields */ + io_req->sc_cmd = sc_cmd; + io_req->port = port; + io_req->tgt = tgt; + + tm_req = (struct bnx2fc_mp_req *)&(io_req->mp_req); + + rc = bnx2fc_init_mp_req(io_req); + if (rc == FAILED) { + printk(KERN_ERR PFX "Task mgmt MP request init failed\n"); + kref_put(&io_req->refcount, bnx2fc_cmd_release); + goto tmf_err; + } + + /* Set TM flags */ + io_req->io_req_flags = 0; + tm_req->tm_flags = tm_flags; + + /* Fill FCP_CMND */ + bnx2fc_build_fcp_cmnd(io_req, (struct fcp_cmnd *)tm_req->req_buf); + fcp_cmnd = (struct fcp_cmnd *)tm_req->req_buf; + memset(fcp_cmnd->fc_cdb, 0, sc_cmd->cmd_len); + fcp_cmnd->fc_dl = 0; + + /* Fill FC header */ + fc_hdr = &(tm_req->req_fc_hdr); + sid = tgt->sid; + did = rport->port_id; + __fc_fill_fc_hdr(fc_hdr, FC_RCTL_DD_UNSOL_CMD, did, sid, + FC_TYPE_FCP, FC_FC_FIRST_SEQ | FC_FC_END_SEQ | + FC_FC_SEQ_INIT, 0); + /* Obtain exchange id */ + xid = io_req->xid; + + BNX2FC_TGT_DBG(tgt, "Initiate TMF - xid = 0x%x\n", xid); + task_idx = xid/BNX2FC_TASKS_PER_PAGE; + index = xid % BNX2FC_TASKS_PER_PAGE; + + /* Initialize task context for this IO request */ + task_page = (struct fcoe_task_ctx_entry *) hba->task_ctx[task_idx]; + task = &(task_page[index]); + bnx2fc_init_mp_task(io_req, task); + + sc_cmd->SCp.ptr = (char *)io_req; + + /* Obtain free SQ entry */ + spin_lock_bh(&tgt->tgt_lock); + bnx2fc_add_2_sq(tgt, xid); + + /* Enqueue the io_req to active_tm_queue */ + io_req->on_tmf_queue = 1; + list_add_tail(&io_req->link, &tgt->active_tm_queue); + + init_completion(&io_req->tm_done); + io_req->wait_for_comp = 1; + + /* Ring doorbell */ + bnx2fc_ring_doorbell(tgt); + spin_unlock_bh(&tgt->tgt_lock); + + rc = wait_for_completion_timeout(&io_req->tm_done, + BNX2FC_TM_TIMEOUT * HZ); + spin_lock_bh(&tgt->tgt_lock); + + io_req->wait_for_comp = 0; + if (!(test_bit(BNX2FC_FLAG_TM_COMPL, &io_req->req_flags))) + set_bit(BNX2FC_FLAG_TM_TIMEOUT, &io_req->req_flags); + + spin_unlock_bh(&tgt->tgt_lock); + + if (!rc) { + printk(KERN_ERR PFX "task mgmt command failed...\n"); + rc = FAILED; + } else { + printk(KERN_ERR PFX "task mgmt command success...\n"); + rc = SUCCESS; + } +tmf_err: + return rc; +} + +int bnx2fc_initiate_abts(struct bnx2fc_cmd *io_req) +{ + struct fc_lport *lport; + struct bnx2fc_rport *tgt = io_req->tgt; + struct fc_rport *rport = tgt->rport; + struct fc_rport_priv *rdata = tgt->rdata; + struct bnx2fc_hba *hba; + struct fcoe_port *port; + struct bnx2fc_cmd *abts_io_req; + struct fcoe_task_ctx_entry *task; + struct fcoe_task_ctx_entry *task_page; + struct fc_frame_header *fc_hdr; + struct bnx2fc_mp_req *abts_req; + int task_idx, index; + u32 sid, did; + u16 xid; + int rc = SUCCESS; + u32 r_a_tov = rdata->r_a_tov; + + /* called with tgt_lock held */ + BNX2FC_IO_DBG(io_req, "Entered bnx2fc_initiate_abts\n"); + + port = io_req->port; + hba = port->priv; + lport = port->lport; + + if (!test_bit(BNX2FC_FLAG_SESSION_READY, &tgt->flags)) { + printk(KERN_ERR PFX "initiate_abts: tgt not offloaded\n"); + rc = FAILED; + goto abts_err; + } + + if (rport == NULL) { + printk(KERN_ALERT PFX "initiate_abts: rport is NULL\n"); + rc = FAILED; + goto abts_err; + } + + if (lport->state != LPORT_ST_READY || !(lport->link_up)) { + printk(KERN_ERR PFX "initiate_abts: link is not ready\n"); + rc = FAILED; + goto abts_err; + } + + abts_io_req = bnx2fc_elstm_alloc(tgt, BNX2FC_ABTS); + if (!abts_io_req) { + printk(KERN_ERR PFX "abts: couldnt allocate cmd\n"); + rc = FAILED; + goto abts_err; + } + + /* Initialize rest of io_req fields */ + abts_io_req->sc_cmd = NULL; + abts_io_req->port = port; + abts_io_req->tgt = tgt; + abts_io_req->data_xfer_len = 0; /* No data transfer for ABTS */ + + abts_req = (struct bnx2fc_mp_req *)&(abts_io_req->mp_req); + memset(abts_req, 0, sizeof(struct bnx2fc_mp_req)); + + /* Fill FC header */ + fc_hdr = &(abts_req->req_fc_hdr); + + /* Obtain oxid and rxid for the original exchange to be aborted */ + fc_hdr->fh_ox_id = htons(io_req->xid); + fc_hdr->fh_rx_id = htons(io_req->task->rx_wr_tx_rd.rx_id); + + sid = tgt->sid; + did = rport->port_id; + + __fc_fill_fc_hdr(fc_hdr, FC_RCTL_BA_ABTS, did, sid, + FC_TYPE_BLS, FC_FC_FIRST_SEQ | FC_FC_END_SEQ | + FC_FC_SEQ_INIT, 0); + + xid = abts_io_req->xid; + BNX2FC_IO_DBG(abts_io_req, "ABTS io_req\n"); + task_idx = xid/BNX2FC_TASKS_PER_PAGE; + index = xid % BNX2FC_TASKS_PER_PAGE; + + /* Initialize task context for this IO request */ + task_page = (struct fcoe_task_ctx_entry *) hba->task_ctx[task_idx]; + task = &(task_page[index]); + bnx2fc_init_mp_task(abts_io_req, task); + + /* + * ABTS task is a temporary task that will be cleaned up + * irrespective of ABTS response. We need to start the timer + * for the original exchange, as the CQE is posted for the original + * IO request. + * + * Timer for ABTS is started only when it is originated by a + * TM request. For the ABTS issued as part of ULP timeout, + * scsi-ml maintains the timers. + */ + + /* if (test_bit(BNX2FC_FLAG_ISSUE_ABTS, &io_req->req_flags))*/ + bnx2fc_cmd_timer_set(io_req, 2 * r_a_tov); + + /* Obtain free SQ entry */ + bnx2fc_add_2_sq(tgt, xid); + + /* Ring doorbell */ + bnx2fc_ring_doorbell(tgt); + +abts_err: + return rc; +} + +int bnx2fc_initiate_cleanup(struct bnx2fc_cmd *io_req) +{ + struct fc_lport *lport; + struct bnx2fc_rport *tgt = io_req->tgt; + struct bnx2fc_hba *hba; + struct fcoe_port *port; + struct bnx2fc_cmd *cleanup_io_req; + struct fcoe_task_ctx_entry *task; + struct fcoe_task_ctx_entry *task_page; + int task_idx, index; + u16 xid, orig_xid; + int rc = 0; + + /* ASSUMPTION: called with tgt_lock held */ + BNX2FC_IO_DBG(io_req, "Entered bnx2fc_initiate_cleanup\n"); + + port = io_req->port; + hba = port->priv; + lport = port->lport; + + cleanup_io_req = bnx2fc_elstm_alloc(tgt, BNX2FC_CLEANUP); + if (!cleanup_io_req) { + printk(KERN_ERR PFX "cleanup: couldnt allocate cmd\n"); + rc = -1; + goto cleanup_err; + } + + /* Initialize rest of io_req fields */ + cleanup_io_req->sc_cmd = NULL; + cleanup_io_req->port = port; + cleanup_io_req->tgt = tgt; + cleanup_io_req->data_xfer_len = 0; /* No data transfer for cleanup */ + + xid = cleanup_io_req->xid; + + task_idx = xid/BNX2FC_TASKS_PER_PAGE; + index = xid % BNX2FC_TASKS_PER_PAGE; + + /* Initialize task context for this IO request */ + task_page = (struct fcoe_task_ctx_entry *) hba->task_ctx[task_idx]; + task = &(task_page[index]); + orig_xid = io_req->xid; + + BNX2FC_IO_DBG(io_req, "CLEANUP io_req xid = 0x%x\n", xid); + + bnx2fc_init_cleanup_task(cleanup_io_req, task, orig_xid); + + /* Obtain free SQ entry */ + bnx2fc_add_2_sq(tgt, xid); + + /* Ring doorbell */ + bnx2fc_ring_doorbell(tgt); + +cleanup_err: + return rc; +} + +/** + * bnx2fc_eh_target_reset: Reset a target + * + * @sc_cmd: SCSI command + * + * Set from SCSI host template to send task mgmt command to the target + * and wait for the response + */ +int bnx2fc_eh_target_reset(struct scsi_cmnd *sc_cmd) +{ + return bnx2fc_initiate_tmf(sc_cmd, FCP_TMF_TGT_RESET); +} + +/** + * bnx2fc_eh_device_reset - Reset a single LUN + * + * @sc_cmd: SCSI command + * + * Set from SCSI host template to send task mgmt command to the target + * and wait for the response + */ +int bnx2fc_eh_device_reset(struct scsi_cmnd *sc_cmd) +{ + return bnx2fc_initiate_tmf(sc_cmd, FCP_TMF_LUN_RESET); +} + +/** + * bnx2fc_eh_abort - eh_abort_handler api to abort an outstanding + * SCSI command + * + * @sc_cmd: SCSI_ML command pointer + * + * SCSI abort request handler + */ +int bnx2fc_eh_abort(struct scsi_cmnd *sc_cmd) +{ + struct fc_rport *rport = starget_to_rport(scsi_target(sc_cmd->device)); + struct fc_rport_libfc_priv *rp = rport->dd_data; + struct bnx2fc_cmd *io_req; + struct fc_lport *lport; + struct bnx2fc_rport *tgt; + int rc = FAILED; + + + rc = fc_block_scsi_eh(sc_cmd); + if (rc) + return rc; + + lport = shost_priv(sc_cmd->device->host); + if ((lport->state != LPORT_ST_READY) || !(lport->link_up)) { + printk(KERN_ALERT PFX "eh_abort: link not ready\n"); + return rc; + } + + tgt = (struct bnx2fc_rport *)&rp[1]; + + BNX2FC_TGT_DBG(tgt, "Entered bnx2fc_eh_abort\n"); + + spin_lock_bh(&tgt->tgt_lock); + io_req = (struct bnx2fc_cmd *)sc_cmd->SCp.ptr; + if (!io_req) { + /* Command might have just completed */ + printk(KERN_ERR PFX "eh_abort: io_req is NULL\n"); + spin_unlock_bh(&tgt->tgt_lock); + return SUCCESS; + } + BNX2FC_IO_DBG(io_req, "eh_abort - refcnt = %d\n", + io_req->refcount.refcount.counter); + + /* Hold IO request across abort processing */ + kref_get(&io_req->refcount); + + BUG_ON(tgt != io_req->tgt); + + /* Remove the io_req from the active_q. */ + /* + * Task Mgmt functions (LUN RESET & TGT RESET) will not + * issue an ABTS on this particular IO req, as the + * io_req is no longer in the active_q. + */ + if (tgt->flush_in_prog) { + printk(KERN_ALERT PFX "eh_abort: io_req (xid = 0x%x) " + "flush in progress\n", io_req->xid); + kref_put(&io_req->refcount, bnx2fc_cmd_release); + spin_unlock_bh(&tgt->tgt_lock); + return SUCCESS; + } + + if (io_req->on_active_queue == 0) { + printk(KERN_ALERT PFX "eh_abort: io_req (xid = 0x%x) " + "not on active_q\n", io_req->xid); + /* + * This condition can happen only due to the FW bug, + * where we do not receive cleanup response from + * the FW. Handle this case gracefully by erroring + * back the IO request to SCSI-ml + */ + bnx2fc_scsi_done(io_req, DID_ABORT); + + kref_put(&io_req->refcount, bnx2fc_cmd_release); + spin_unlock_bh(&tgt->tgt_lock); + return SUCCESS; + } + + /* + * Only eh_abort processing will remove the IO from + * active_cmd_q before processing the request. this is + * done to avoid race conditions between IOs aborted + * as part of task management completion and eh_abort + * processing + */ + list_del_init(&io_req->link); + io_req->on_active_queue = 0; + /* Move IO req to retire queue */ + list_add_tail(&io_req->link, &tgt->io_retire_queue); + + init_completion(&io_req->tm_done); + io_req->wait_for_comp = 1; + + if (!test_and_set_bit(BNX2FC_FLAG_ISSUE_ABTS, &io_req->req_flags)) { + /* Cancel the current timer running on this io_req */ + if (cancel_delayed_work(&io_req->timeout_work)) + kref_put(&io_req->refcount, + bnx2fc_cmd_release); /* drop timer hold */ + set_bit(BNX2FC_FLAG_EH_ABORT, &io_req->req_flags); + rc = bnx2fc_initiate_abts(io_req); + } else { + printk(KERN_ALERT PFX "eh_abort: io_req (xid = 0x%x) " + "already in abts processing\n", io_req->xid); + kref_put(&io_req->refcount, bnx2fc_cmd_release); + spin_unlock_bh(&tgt->tgt_lock); + return SUCCESS; + } + if (rc == FAILED) { + kref_put(&io_req->refcount, bnx2fc_cmd_release); + spin_unlock_bh(&tgt->tgt_lock); + return rc; + } + spin_unlock_bh(&tgt->tgt_lock); + + wait_for_completion(&io_req->tm_done); + + spin_lock_bh(&tgt->tgt_lock); + io_req->wait_for_comp = 0; + if (!(test_and_set_bit(BNX2FC_FLAG_ABTS_DONE, + &io_req->req_flags))) { + /* Let the scsi-ml try to recover this command */ + printk(KERN_ERR PFX "abort failed, xid = 0x%x\n", + io_req->xid); + rc = FAILED; + } else { + /* + * We come here even when there was a race condition + * between timeout and abts completion, and abts + * completion happens just in time. + */ + BNX2FC_IO_DBG(io_req, "abort succeeded\n"); + rc = SUCCESS; + bnx2fc_scsi_done(io_req, DID_ABORT); + kref_put(&io_req->refcount, bnx2fc_cmd_release); + } + + /* release the reference taken in eh_abort */ + kref_put(&io_req->refcount, bnx2fc_cmd_release); + spin_unlock_bh(&tgt->tgt_lock); + return rc; +} + +void bnx2fc_process_cleanup_compl(struct bnx2fc_cmd *io_req, + struct fcoe_task_ctx_entry *task, + u8 num_rq) +{ + BNX2FC_IO_DBG(io_req, "Entered process_cleanup_compl " + "refcnt = %d, cmd_type = %d\n", + io_req->refcount.refcount.counter, io_req->cmd_type); + bnx2fc_scsi_done(io_req, DID_ERROR); + kref_put(&io_req->refcount, bnx2fc_cmd_release); +} + +void bnx2fc_process_abts_compl(struct bnx2fc_cmd *io_req, + struct fcoe_task_ctx_entry *task, + u8 num_rq) +{ + u32 r_ctl; + u32 r_a_tov = FC_DEF_R_A_TOV; + u8 issue_rrq = 0; + struct bnx2fc_rport *tgt = io_req->tgt; + + BNX2FC_IO_DBG(io_req, "Entered process_abts_compl xid = 0x%x" + "refcnt = %d, cmd_type = %d\n", + io_req->xid, + io_req->refcount.refcount.counter, io_req->cmd_type); + + if (test_and_set_bit(BNX2FC_FLAG_ABTS_DONE, + &io_req->req_flags)) { + BNX2FC_IO_DBG(io_req, "Timer context finished processing" + " this io\n"); + return; + } + + /* Do not issue RRQ as this IO is already cleanedup */ + if (test_and_set_bit(BNX2FC_FLAG_IO_CLEANUP, + &io_req->req_flags)) + goto io_compl; + + /* + * For ABTS issued due to SCSI eh_abort_handler, timeout + * values are maintained by scsi-ml itself. Cancel timeout + * in case ABTS issued as part of task management function + * or due to FW error. + */ + if (test_bit(BNX2FC_FLAG_ISSUE_ABTS, &io_req->req_flags)) + if (cancel_delayed_work(&io_req->timeout_work)) + kref_put(&io_req->refcount, + bnx2fc_cmd_release); /* drop timer hold */ + + r_ctl = task->cmn.general.rsp_info.abts_rsp.r_ctl; + + switch (r_ctl) { + case FC_RCTL_BA_ACC: + /* + * Dont release this cmd yet. It will be relesed + * after we get RRQ response + */ + BNX2FC_IO_DBG(io_req, "ABTS response - ACC Send RRQ\n"); + issue_rrq = 1; + break; + + case FC_RCTL_BA_RJT: + BNX2FC_IO_DBG(io_req, "ABTS response - RJT\n"); + break; + default: + printk(KERN_ERR PFX "Unknown ABTS response\n"); + break; + } + + if (issue_rrq) { + BNX2FC_IO_DBG(io_req, "Issue RRQ after R_A_TOV\n"); + set_bit(BNX2FC_FLAG_ISSUE_RRQ, &io_req->req_flags); + } + set_bit(BNX2FC_FLAG_RETIRE_OXID, &io_req->req_flags); + bnx2fc_cmd_timer_set(io_req, r_a_tov); + +io_compl: + if (io_req->wait_for_comp) { + if (test_and_clear_bit(BNX2FC_FLAG_EH_ABORT, + &io_req->req_flags)) + complete(&io_req->tm_done); + } else { + /* + * We end up here when ABTS is issued as + * in asynchronous context, i.e., as part + * of task management completion, or + * when FW error is received or when the + * ABTS is issued when the IO is timed + * out. + */ + + if (io_req->on_active_queue) { + list_del_init(&io_req->link); + io_req->on_active_queue = 0; + /* Move IO req to retire queue */ + list_add_tail(&io_req->link, &tgt->io_retire_queue); + } + bnx2fc_scsi_done(io_req, DID_ERROR); + kref_put(&io_req->refcount, bnx2fc_cmd_release); + } +} + +static void bnx2fc_lun_reset_cmpl(struct bnx2fc_cmd *io_req) +{ + struct scsi_cmnd *sc_cmd = io_req->sc_cmd; + struct bnx2fc_rport *tgt = io_req->tgt; + struct list_head *list; + struct list_head *tmp; + struct bnx2fc_cmd *cmd; + int tm_lun = sc_cmd->device->lun; + int rc = 0; + int lun; + + /* called with tgt_lock held */ + BNX2FC_IO_DBG(io_req, "Entered bnx2fc_lun_reset_cmpl\n"); + /* + * Walk thru the active_ios queue and ABORT the IO + * that matches with the LUN that was reset + */ + list_for_each_safe(list, tmp, &tgt->active_cmd_queue) { + BNX2FC_TGT_DBG(tgt, "LUN RST cmpl: scan for pending IOs\n"); + cmd = (struct bnx2fc_cmd *)list; + lun = cmd->sc_cmd->device->lun; + if (lun == tm_lun) { + /* Initiate ABTS on this cmd */ + if (!test_and_set_bit(BNX2FC_FLAG_ISSUE_ABTS, + &cmd->req_flags)) { + /* cancel the IO timeout */ + if (cancel_delayed_work(&io_req->timeout_work)) + kref_put(&io_req->refcount, + bnx2fc_cmd_release); + /* timer hold */ + rc = bnx2fc_initiate_abts(cmd); + /* abts shouldnt fail in this context */ + WARN_ON(rc != SUCCESS); + } else + printk(KERN_ERR PFX "lun_rst: abts already in" + " progress for this IO 0x%x\n", + cmd->xid); + } + } +} + +static void bnx2fc_tgt_reset_cmpl(struct bnx2fc_cmd *io_req) +{ + struct bnx2fc_rport *tgt = io_req->tgt; + struct list_head *list; + struct list_head *tmp; + struct bnx2fc_cmd *cmd; + int rc = 0; + + /* called with tgt_lock held */ + BNX2FC_IO_DBG(io_req, "Entered bnx2fc_tgt_reset_cmpl\n"); + /* + * Walk thru the active_ios queue and ABORT the IO + * that matches with the LUN that was reset + */ + list_for_each_safe(list, tmp, &tgt->active_cmd_queue) { + BNX2FC_TGT_DBG(tgt, "TGT RST cmpl: scan for pending IOs\n"); + cmd = (struct bnx2fc_cmd *)list; + /* Initiate ABTS */ + if (!test_and_set_bit(BNX2FC_FLAG_ISSUE_ABTS, + &cmd->req_flags)) { + /* cancel the IO timeout */ + if (cancel_delayed_work(&io_req->timeout_work)) + kref_put(&io_req->refcount, + bnx2fc_cmd_release); /* timer hold */ + rc = bnx2fc_initiate_abts(cmd); + /* abts shouldnt fail in this context */ + WARN_ON(rc != SUCCESS); + + } else + printk(KERN_ERR PFX "tgt_rst: abts already in progress" + " for this IO 0x%x\n", cmd->xid); + } +} + +void bnx2fc_process_tm_compl(struct bnx2fc_cmd *io_req, + struct fcoe_task_ctx_entry *task, u8 num_rq) +{ + struct bnx2fc_mp_req *tm_req; + struct fc_frame_header *fc_hdr; + struct scsi_cmnd *sc_cmd = io_req->sc_cmd; + u64 *hdr; + u64 *temp_hdr; + void *rsp_buf; + + /* Called with tgt_lock held */ + BNX2FC_IO_DBG(io_req, "Entered process_tm_compl\n"); + + if (!(test_bit(BNX2FC_FLAG_TM_TIMEOUT, &io_req->req_flags))) + set_bit(BNX2FC_FLAG_TM_COMPL, &io_req->req_flags); + else { + /* TM has already timed out and we got + * delayed completion. Ignore completion + * processing. + */ + return; + } + + tm_req = &(io_req->mp_req); + fc_hdr = &(tm_req->resp_fc_hdr); + hdr = (u64 *)fc_hdr; + temp_hdr = (u64 *) + &task->cmn.general.cmd_info.mp_fc_frame.fc_hdr; + hdr[0] = cpu_to_be64(temp_hdr[0]); + hdr[1] = cpu_to_be64(temp_hdr[1]); + hdr[2] = cpu_to_be64(temp_hdr[2]); + + tm_req->resp_len = task->rx_wr_only.sgl_ctx.mul_sges.cur_sge_off; + + rsp_buf = tm_req->resp_buf; + + if (fc_hdr->fh_r_ctl == FC_RCTL_DD_CMD_STATUS) { + bnx2fc_parse_fcp_rsp(io_req, + (struct fcoe_fcp_rsp_payload *) + rsp_buf, num_rq); + if (io_req->fcp_rsp_code == 0) { + /* TM successful */ + if (tm_req->tm_flags & FCP_TMF_LUN_RESET) + bnx2fc_lun_reset_cmpl(io_req); + else if (tm_req->tm_flags & FCP_TMF_TGT_RESET) + bnx2fc_tgt_reset_cmpl(io_req); + } + } else { + printk(KERN_ERR PFX "tmf's fc_hdr r_ctl = 0x%x\n", + fc_hdr->fh_r_ctl); + } + if (!sc_cmd->SCp.ptr) { + printk(KERN_ALERT PFX "tm_compl: SCp.ptr is NULL\n"); + return; + } + switch (io_req->fcp_status) { + case FC_GOOD: + if (io_req->cdb_status == 0) { + /* Good IO completion */ + sc_cmd->result = DID_OK << 16; + } else { + /* Transport status is good, SCSI status not good */ + sc_cmd->result = (DID_OK << 16) | io_req->cdb_status; + } + if (io_req->fcp_resid) + scsi_set_resid(sc_cmd, io_req->fcp_resid); + break; + + default: + BNX2FC_IO_DBG(io_req, "process_tm_compl: fcp_status = %d\n", + io_req->fcp_status); + break; + } + + sc_cmd = io_req->sc_cmd; + io_req->sc_cmd = NULL; + + /* check if the io_req exists in tgt's tmf_q */ + if (io_req->on_tmf_queue) { + + list_del_init(&io_req->link); + io_req->on_tmf_queue = 0; + } else { + + printk(KERN_ALERT PFX "Command not on active_cmd_queue!\n"); + return; + } + + sc_cmd->SCp.ptr = NULL; + sc_cmd->scsi_done(sc_cmd); + + kref_put(&io_req->refcount, bnx2fc_cmd_release); + if (io_req->wait_for_comp) { + BNX2FC_IO_DBG(io_req, "tm_compl - wake up the waiter\n"); + complete(&io_req->tm_done); + } +} + +static int bnx2fc_split_bd(struct bnx2fc_cmd *io_req, u64 addr, int sg_len, + int bd_index) +{ + struct fcoe_bd_ctx *bd = io_req->bd_tbl->bd_tbl; + int frag_size, sg_frags; + + sg_frags = 0; + while (sg_len) { + if (sg_len >= BNX2FC_BD_SPLIT_SZ) + frag_size = BNX2FC_BD_SPLIT_SZ; + else + frag_size = sg_len; + bd[bd_index + sg_frags].buf_addr_lo = addr & 0xffffffff; + bd[bd_index + sg_frags].buf_addr_hi = addr >> 32; + bd[bd_index + sg_frags].buf_len = (u16)frag_size; + bd[bd_index + sg_frags].flags = 0; + + addr += (u64) frag_size; + sg_frags++; + sg_len -= frag_size; + } + return sg_frags; + +} + +static int bnx2fc_map_sg(struct bnx2fc_cmd *io_req) +{ + struct scsi_cmnd *sc = io_req->sc_cmd; + struct fcoe_bd_ctx *bd = io_req->bd_tbl->bd_tbl; + struct scatterlist *sg; + int byte_count = 0; + int sg_count = 0; + int bd_count = 0; + int sg_frags; + unsigned int sg_len; + u64 addr; + int i; + + sg_count = scsi_dma_map(sc); + scsi_for_each_sg(sc, sg, sg_count, i) { + sg_len = sg_dma_len(sg); + addr = sg_dma_address(sg); + if (sg_len > BNX2FC_MAX_BD_LEN) { + sg_frags = bnx2fc_split_bd(io_req, addr, sg_len, + bd_count); + } else { + + sg_frags = 1; + bd[bd_count].buf_addr_lo = addr & 0xffffffff; + bd[bd_count].buf_addr_hi = addr >> 32; + bd[bd_count].buf_len = (u16)sg_len; + bd[bd_count].flags = 0; + } + bd_count += sg_frags; + byte_count += sg_len; + } + if (byte_count != scsi_bufflen(sc)) + printk(KERN_ERR PFX "byte_count = %d != scsi_bufflen = %d, " + "task_id = 0x%x\n", byte_count, scsi_bufflen(sc), + io_req->xid); + return bd_count; +} + +static void bnx2fc_build_bd_list_from_sg(struct bnx2fc_cmd *io_req) +{ + struct scsi_cmnd *sc = io_req->sc_cmd; + struct fcoe_bd_ctx *bd = io_req->bd_tbl->bd_tbl; + int bd_count; + + if (scsi_sg_count(sc)) + bd_count = bnx2fc_map_sg(io_req); + else { + bd_count = 0; + bd[0].buf_addr_lo = bd[0].buf_addr_hi = 0; + bd[0].buf_len = bd[0].flags = 0; + } + io_req->bd_tbl->bd_valid = bd_count; +} + +static void bnx2fc_unmap_sg_list(struct bnx2fc_cmd *io_req) +{ + struct scsi_cmnd *sc = io_req->sc_cmd; + + if (io_req->bd_tbl->bd_valid && sc) { + scsi_dma_unmap(sc); + io_req->bd_tbl->bd_valid = 0; + } +} + +void bnx2fc_build_fcp_cmnd(struct bnx2fc_cmd *io_req, + struct fcp_cmnd *fcp_cmnd) +{ + struct scsi_cmnd *sc_cmd = io_req->sc_cmd; + char tag[2]; + + memset(fcp_cmnd, 0, sizeof(struct fcp_cmnd)); + + int_to_scsilun(sc_cmd->device->lun, + (struct scsi_lun *) fcp_cmnd->fc_lun); + + + fcp_cmnd->fc_dl = htonl(io_req->data_xfer_len); + memcpy(fcp_cmnd->fc_cdb, sc_cmd->cmnd, sc_cmd->cmd_len); + + fcp_cmnd->fc_cmdref = 0; + fcp_cmnd->fc_pri_ta = 0; + fcp_cmnd->fc_tm_flags = io_req->mp_req.tm_flags; + fcp_cmnd->fc_flags = io_req->io_req_flags; + + if (scsi_populate_tag_msg(sc_cmd, tag)) { + switch (tag[0]) { + case HEAD_OF_QUEUE_TAG: + fcp_cmnd->fc_pri_ta = FCP_PTA_HEADQ; + break; + case ORDERED_QUEUE_TAG: + fcp_cmnd->fc_pri_ta = FCP_PTA_ORDERED; + break; + default: + fcp_cmnd->fc_pri_ta = FCP_PTA_SIMPLE; + break; + } + } else { + fcp_cmnd->fc_pri_ta = 0; + } +} + +static void bnx2fc_parse_fcp_rsp(struct bnx2fc_cmd *io_req, + struct fcoe_fcp_rsp_payload *fcp_rsp, + u8 num_rq) +{ + struct scsi_cmnd *sc_cmd = io_req->sc_cmd; + struct bnx2fc_rport *tgt = io_req->tgt; + u8 rsp_flags = fcp_rsp->fcp_flags.flags; + u32 rq_buff_len = 0; + int i; + unsigned char *rq_data; + unsigned char *dummy; + int fcp_sns_len = 0; + int fcp_rsp_len = 0; + + io_req->fcp_status = FC_GOOD; + io_req->fcp_resid = fcp_rsp->fcp_resid; + + io_req->scsi_comp_flags = rsp_flags; + CMD_SCSI_STATUS(sc_cmd) = io_req->cdb_status = + fcp_rsp->scsi_status_code; + + /* Fetch fcp_rsp_info and fcp_sns_info if available */ + if (num_rq) { + + /* + * We do not anticipate num_rq >1, as the linux defined + * SCSI_SENSE_BUFFERSIZE is 96 bytes + 8 bytes of FCP_RSP_INFO + * 256 bytes of single rq buffer is good enough to hold this. + */ + + if (rsp_flags & + FCOE_FCP_RSP_FLAGS_FCP_RSP_LEN_VALID) { + fcp_rsp_len = rq_buff_len + = fcp_rsp->fcp_rsp_len; + } + + if (rsp_flags & + FCOE_FCP_RSP_FLAGS_FCP_SNS_LEN_VALID) { + fcp_sns_len = fcp_rsp->fcp_sns_len; + rq_buff_len += fcp_rsp->fcp_sns_len; + } + + io_req->fcp_rsp_len = fcp_rsp_len; + io_req->fcp_sns_len = fcp_sns_len; + + if (rq_buff_len > num_rq * BNX2FC_RQ_BUF_SZ) { + /* Invalid sense sense length. */ + printk(KERN_ALERT PFX "invalid sns length %d\n", + rq_buff_len); + /* reset rq_buff_len */ + rq_buff_len = num_rq * BNX2FC_RQ_BUF_SZ; + } + + rq_data = bnx2fc_get_next_rqe(tgt, 1); + + if (num_rq > 1) { + /* We do not need extra sense data */ + for (i = 1; i < num_rq; i++) + dummy = bnx2fc_get_next_rqe(tgt, 1); + } + + /* fetch fcp_rsp_code */ + if ((fcp_rsp_len == 4) || (fcp_rsp_len == 8)) { + /* Only for task management function */ + io_req->fcp_rsp_code = rq_data[3]; + printk(KERN_ERR PFX "fcp_rsp_code = %d\n", + io_req->fcp_rsp_code); + } + + /* fetch sense data */ + rq_data += fcp_rsp_len; + + if (fcp_sns_len > SCSI_SENSE_BUFFERSIZE) { + printk(KERN_ERR PFX "Truncating sense buffer\n"); + fcp_sns_len = SCSI_SENSE_BUFFERSIZE; + } + + memset(sc_cmd->sense_buffer, 0, sizeof(sc_cmd->sense_buffer)); + if (fcp_sns_len) + memcpy(sc_cmd->sense_buffer, rq_data, fcp_sns_len); + + /* return RQ entries */ + for (i = 0; i < num_rq; i++) + bnx2fc_return_rqe(tgt, 1); + } +} + +/** + * bnx2fc_queuecommand - Queuecommand function of the scsi template + * + * @host: The Scsi_Host the command was issued to + * @sc_cmd: struct scsi_cmnd to be executed + * + * This is the IO strategy routine, called by SCSI-ML + **/ +int bnx2fc_queuecommand(struct Scsi_Host *host, + struct scsi_cmnd *sc_cmd) +{ + struct fc_lport *lport = shost_priv(host); + struct fc_rport *rport = starget_to_rport(scsi_target(sc_cmd->device)); + struct fc_rport_libfc_priv *rp = rport->dd_data; + struct bnx2fc_rport *tgt; + struct bnx2fc_cmd *io_req; + int rc = 0; + int rval; + + rval = fc_remote_port_chkready(rport); + if (rval) { + sc_cmd->result = rval; + sc_cmd->scsi_done(sc_cmd); + return 0; + } + + if ((lport->state != LPORT_ST_READY) || !(lport->link_up)) { + rc = SCSI_MLQUEUE_HOST_BUSY; + goto exit_qcmd; + } + + /* rport and tgt are allocated together, so tgt should be non-NULL */ + tgt = (struct bnx2fc_rport *)&rp[1]; + + if (!test_bit(BNX2FC_FLAG_SESSION_READY, &tgt->flags)) { + /* + * Session is not offloaded yet. Let SCSI-ml retry + * the command. + */ + rc = SCSI_MLQUEUE_TARGET_BUSY; + goto exit_qcmd; + } + + io_req = bnx2fc_cmd_alloc(tgt); + if (!io_req) { + rc = SCSI_MLQUEUE_HOST_BUSY; + goto exit_qcmd; + } + io_req->sc_cmd = sc_cmd; + + if (bnx2fc_post_io_req(tgt, io_req)) { + printk(KERN_ERR PFX "Unable to post io_req\n"); + rc = SCSI_MLQUEUE_HOST_BUSY; + goto exit_qcmd; + } +exit_qcmd: + return rc; +} + +void bnx2fc_process_scsi_cmd_compl(struct bnx2fc_cmd *io_req, + struct fcoe_task_ctx_entry *task, + u8 num_rq) +{ + struct fcoe_fcp_rsp_payload *fcp_rsp; + struct bnx2fc_rport *tgt = io_req->tgt; + struct scsi_cmnd *sc_cmd; + struct Scsi_Host *host; + + + /* scsi_cmd_cmpl is called with tgt lock held */ + + if (test_and_set_bit(BNX2FC_FLAG_IO_COMPL, &io_req->req_flags)) { + /* we will not receive ABTS response for this IO */ + BNX2FC_IO_DBG(io_req, "Timer context finished processing " + "this scsi cmd\n"); + } + + /* Cancel the timeout_work, as we received IO completion */ + if (cancel_delayed_work(&io_req->timeout_work)) + kref_put(&io_req->refcount, + bnx2fc_cmd_release); /* drop timer hold */ + + sc_cmd = io_req->sc_cmd; + if (sc_cmd == NULL) { + printk(KERN_ERR PFX "scsi_cmd_compl - sc_cmd is NULL\n"); + return; + } + + /* Fetch fcp_rsp from task context and perform cmd completion */ + fcp_rsp = (struct fcoe_fcp_rsp_payload *) + &(task->cmn.general.rsp_info.fcp_rsp.payload); + + /* parse fcp_rsp and obtain sense data from RQ if available */ + bnx2fc_parse_fcp_rsp(io_req, fcp_rsp, num_rq); + + host = sc_cmd->device->host; + if (!sc_cmd->SCp.ptr) { + printk(KERN_ERR PFX "SCp.ptr is NULL\n"); + return; + } + io_req->sc_cmd = NULL; + + if (io_req->on_active_queue) { + list_del_init(&io_req->link); + io_req->on_active_queue = 0; + /* Move IO req to retire queue */ + list_add_tail(&io_req->link, &tgt->io_retire_queue); + } else { + /* This should not happen, but could have been pulled + * by bnx2fc_flush_active_ios(), or during a race + * between command abort and (late) completion. + */ + BNX2FC_IO_DBG(io_req, "xid not on active_cmd_queue\n"); + if (io_req->wait_for_comp) + if (test_and_clear_bit(BNX2FC_FLAG_EH_ABORT, + &io_req->req_flags)) + complete(&io_req->tm_done); + } + + bnx2fc_unmap_sg_list(io_req); + + switch (io_req->fcp_status) { + case FC_GOOD: + if (io_req->cdb_status == 0) { + /* Good IO completion */ + sc_cmd->result = DID_OK << 16; + } else { + /* Transport status is good, SCSI status not good */ + BNX2FC_IO_DBG(io_req, "scsi_cmpl: cdb_status = %d" + " fcp_resid = 0x%x\n", + io_req->cdb_status, io_req->fcp_resid); + sc_cmd->result = (DID_OK << 16) | io_req->cdb_status; + } + if (io_req->fcp_resid) + scsi_set_resid(sc_cmd, io_req->fcp_resid); + break; + default: + printk(KERN_ALERT PFX "scsi_cmd_compl: fcp_status = %d\n", + io_req->fcp_status); + break; + } + sc_cmd->SCp.ptr = NULL; + sc_cmd->scsi_done(sc_cmd); + kref_put(&io_req->refcount, bnx2fc_cmd_release); +} + +static int bnx2fc_post_io_req(struct bnx2fc_rport *tgt, + struct bnx2fc_cmd *io_req) +{ + struct fcoe_task_ctx_entry *task; + struct fcoe_task_ctx_entry *task_page; + struct scsi_cmnd *sc_cmd = io_req->sc_cmd; + struct fcoe_port *port = tgt->port; + struct bnx2fc_hba *hba = port->priv; + struct fc_lport *lport = port->lport; + struct fcoe_dev_stats *stats; + int task_idx, index; + u16 xid; + + /* Initialize rest of io_req fields */ + io_req->cmd_type = BNX2FC_SCSI_CMD; + io_req->port = port; + io_req->tgt = tgt; + io_req->data_xfer_len = scsi_bufflen(sc_cmd); + sc_cmd->SCp.ptr = (char *)io_req; + + stats = per_cpu_ptr(lport->dev_stats, get_cpu()); + if (sc_cmd->sc_data_direction == DMA_FROM_DEVICE) { + io_req->io_req_flags = BNX2FC_READ; + stats->InputRequests++; + stats->InputBytes += io_req->data_xfer_len; + } else if (sc_cmd->sc_data_direction == DMA_TO_DEVICE) { + io_req->io_req_flags = BNX2FC_WRITE; + stats->OutputRequests++; + stats->OutputBytes += io_req->data_xfer_len; + } else { + io_req->io_req_flags = 0; + stats->ControlRequests++; + } + put_cpu(); + + xid = io_req->xid; + + /* Build buffer descriptor list for firmware from sg list */ + bnx2fc_build_bd_list_from_sg(io_req); + + task_idx = xid / BNX2FC_TASKS_PER_PAGE; + index = xid % BNX2FC_TASKS_PER_PAGE; + + /* Initialize task context for this IO request */ + task_page = (struct fcoe_task_ctx_entry *) hba->task_ctx[task_idx]; + task = &(task_page[index]); + bnx2fc_init_task(io_req, task); + + spin_lock_bh(&tgt->tgt_lock); + + if (tgt->flush_in_prog) { + printk(KERN_ERR PFX "Flush in progress..Host Busy\n"); + kref_put(&io_req->refcount, bnx2fc_cmd_release); + spin_unlock_bh(&tgt->tgt_lock); + return -EAGAIN; + } + + if (!test_bit(BNX2FC_FLAG_SESSION_READY, &tgt->flags)) { + printk(KERN_ERR PFX "Session not ready...post_io\n"); + kref_put(&io_req->refcount, bnx2fc_cmd_release); + spin_unlock_bh(&tgt->tgt_lock); + return -EAGAIN; + } + + /* Time IO req */ + bnx2fc_cmd_timer_set(io_req, BNX2FC_IO_TIMEOUT); + /* Obtain free SQ entry */ + bnx2fc_add_2_sq(tgt, xid); + + /* Enqueue the io_req to active_cmd_queue */ + + io_req->on_active_queue = 1; + /* move io_req from pending_queue to active_queue */ + list_add_tail(&io_req->link, &tgt->active_cmd_queue); + + /* Ring doorbell */ + bnx2fc_ring_doorbell(tgt); + spin_unlock_bh(&tgt->tgt_lock); + return 0; +} diff --git a/drivers/scsi/bnx2fc/bnx2fc_tgt.c b/drivers/scsi/bnx2fc/bnx2fc_tgt.c new file mode 100644 index 000000000000..7cc05e4e82d5 --- /dev/null +++ b/drivers/scsi/bnx2fc/bnx2fc_tgt.c @@ -0,0 +1,844 @@ +/* bnx2fc_tgt.c: Broadcom NetXtreme II Linux FCoE offload driver. + * Handles operations such as session offload/upload etc, and manages + * session resources such as connection id and qp resources. + * + * Copyright (c) 2008 - 2010 Broadcom Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation. + * + * Written by: Bhanu Prakash Gollapudi (bprakash@broadcom.com) + */ + +#include "bnx2fc.h" +static void bnx2fc_upld_timer(unsigned long data); +static void bnx2fc_ofld_timer(unsigned long data); +static int bnx2fc_init_tgt(struct bnx2fc_rport *tgt, + struct fcoe_port *port, + struct fc_rport_priv *rdata); +static u32 bnx2fc_alloc_conn_id(struct bnx2fc_hba *hba, + struct bnx2fc_rport *tgt); +static int bnx2fc_alloc_session_resc(struct bnx2fc_hba *hba, + struct bnx2fc_rport *tgt); +static void bnx2fc_free_session_resc(struct bnx2fc_hba *hba, + struct bnx2fc_rport *tgt); +static void bnx2fc_free_conn_id(struct bnx2fc_hba *hba, u32 conn_id); + +static void bnx2fc_upld_timer(unsigned long data) +{ + + struct bnx2fc_rport *tgt = (struct bnx2fc_rport *)data; + + BNX2FC_TGT_DBG(tgt, "upld_timer - Upload compl not received!!\n"); + /* fake upload completion */ + clear_bit(BNX2FC_FLAG_OFFLOADED, &tgt->flags); + set_bit(BNX2FC_FLAG_UPLD_REQ_COMPL, &tgt->flags); + wake_up_interruptible(&tgt->upld_wait); +} + +static void bnx2fc_ofld_timer(unsigned long data) +{ + + struct bnx2fc_rport *tgt = (struct bnx2fc_rport *)data; + + BNX2FC_TGT_DBG(tgt, "entered bnx2fc_ofld_timer\n"); + /* NOTE: This function should never be called, as + * offload should never timeout + */ + /* + * If the timer has expired, this session is dead + * Clear offloaded flag and logout of this device. + * Since OFFLOADED flag is cleared, this case + * will be considered as offload error and the + * port will be logged off, and conn_id, session + * resources are freed up in bnx2fc_offload_session + */ + clear_bit(BNX2FC_FLAG_OFFLOADED, &tgt->flags); + set_bit(BNX2FC_FLAG_OFLD_REQ_CMPL, &tgt->flags); + wake_up_interruptible(&tgt->ofld_wait); +} + +static void bnx2fc_offload_session(struct fcoe_port *port, + struct bnx2fc_rport *tgt, + struct fc_rport_priv *rdata) +{ + struct fc_lport *lport = rdata->local_port; + struct fc_rport *rport = rdata->rport; + struct bnx2fc_hba *hba = port->priv; + int rval; + int i = 0; + + /* Initialize bnx2fc_rport */ + /* NOTE: tgt is already bzero'd */ + rval = bnx2fc_init_tgt(tgt, port, rdata); + if (rval) { + printk(KERN_ERR PFX "Failed to allocate conn id for " + "port_id (%6x)\n", rport->port_id); + goto ofld_err; + } + + /* Allocate session resources */ + rval = bnx2fc_alloc_session_resc(hba, tgt); + if (rval) { + printk(KERN_ERR PFX "Failed to allocate resources\n"); + goto ofld_err; + } + + /* + * Initialize FCoE session offload process. + * Upon completion of offload process add + * rport to list of rports + */ +retry_ofld: + clear_bit(BNX2FC_FLAG_OFLD_REQ_CMPL, &tgt->flags); + rval = bnx2fc_send_session_ofld_req(port, tgt); + if (rval) { + printk(KERN_ERR PFX "ofld_req failed\n"); + goto ofld_err; + } + + /* + * wait for the session is offloaded and enabled. 3 Secs + * should be ample time for this process to complete. + */ + setup_timer(&tgt->ofld_timer, bnx2fc_ofld_timer, (unsigned long)tgt); + mod_timer(&tgt->ofld_timer, jiffies + BNX2FC_FW_TIMEOUT); + + wait_event_interruptible(tgt->ofld_wait, + (test_bit( + BNX2FC_FLAG_OFLD_REQ_CMPL, + &tgt->flags))); + if (signal_pending(current)) + flush_signals(current); + + del_timer_sync(&tgt->ofld_timer); + + if (!(test_bit(BNX2FC_FLAG_OFFLOADED, &tgt->flags))) { + if (test_and_clear_bit(BNX2FC_FLAG_CTX_ALLOC_FAILURE, + &tgt->flags)) { + BNX2FC_TGT_DBG(tgt, "ctx_alloc_failure, " + "retry ofld..%d\n", i++); + msleep_interruptible(1000); + if (i > 3) { + i = 0; + goto ofld_err; + } + goto retry_ofld; + } + goto ofld_err; + } + if (bnx2fc_map_doorbell(tgt)) { + printk(KERN_ERR PFX "map doorbell failed - no mem\n"); + /* upload will take care of cleaning up sess resc */ + lport->tt.rport_logoff(rdata); + } + return; + +ofld_err: + /* couldn't offload the session. log off from this rport */ + BNX2FC_TGT_DBG(tgt, "bnx2fc_offload_session - offload error\n"); + lport->tt.rport_logoff(rdata); + /* Free session resources */ + bnx2fc_free_session_resc(hba, tgt); + if (tgt->fcoe_conn_id != -1) + bnx2fc_free_conn_id(hba, tgt->fcoe_conn_id); +} + +void bnx2fc_flush_active_ios(struct bnx2fc_rport *tgt) +{ + struct bnx2fc_cmd *io_req; + struct list_head *list; + struct list_head *tmp; + int rc; + int i = 0; + BNX2FC_TGT_DBG(tgt, "Entered flush_active_ios - %d\n", + tgt->num_active_ios.counter); + + spin_lock_bh(&tgt->tgt_lock); + tgt->flush_in_prog = 1; + + list_for_each_safe(list, tmp, &tgt->active_cmd_queue) { + i++; + io_req = (struct bnx2fc_cmd *)list; + list_del_init(&io_req->link); + io_req->on_active_queue = 0; + BNX2FC_IO_DBG(io_req, "cmd_queue cleanup\n"); + + if (cancel_delayed_work(&io_req->timeout_work)) { + if (test_and_clear_bit(BNX2FC_FLAG_EH_ABORT, + &io_req->req_flags)) { + /* Handle eh_abort timeout */ + BNX2FC_IO_DBG(io_req, "eh_abort for IO " + "cleaned up\n"); + complete(&io_req->tm_done); + } + kref_put(&io_req->refcount, + bnx2fc_cmd_release); /* drop timer hold */ + } + + set_bit(BNX2FC_FLAG_IO_COMPL, &io_req->req_flags); + set_bit(BNX2FC_FLAG_IO_CLEANUP, &io_req->req_flags); + rc = bnx2fc_initiate_cleanup(io_req); + BUG_ON(rc); + } + + list_for_each_safe(list, tmp, &tgt->els_queue) { + i++; + io_req = (struct bnx2fc_cmd *)list; + list_del_init(&io_req->link); + io_req->on_active_queue = 0; + + BNX2FC_IO_DBG(io_req, "els_queue cleanup\n"); + + if (cancel_delayed_work(&io_req->timeout_work)) + kref_put(&io_req->refcount, + bnx2fc_cmd_release); /* drop timer hold */ + + if ((io_req->cb_func) && (io_req->cb_arg)) { + io_req->cb_func(io_req->cb_arg); + io_req->cb_arg = NULL; + } + + rc = bnx2fc_initiate_cleanup(io_req); + BUG_ON(rc); + } + + list_for_each_safe(list, tmp, &tgt->io_retire_queue) { + i++; + io_req = (struct bnx2fc_cmd *)list; + list_del_init(&io_req->link); + + BNX2FC_IO_DBG(io_req, "retire_queue flush\n"); + + if (cancel_delayed_work(&io_req->timeout_work)) + kref_put(&io_req->refcount, bnx2fc_cmd_release); + + clear_bit(BNX2FC_FLAG_ISSUE_RRQ, &io_req->req_flags); + } + + BNX2FC_TGT_DBG(tgt, "IOs flushed = %d\n", i); + i = 0; + spin_unlock_bh(&tgt->tgt_lock); + /* wait for active_ios to go to 0 */ + while ((tgt->num_active_ios.counter != 0) && (i++ < BNX2FC_WAIT_CNT)) + msleep(25); + if (tgt->num_active_ios.counter != 0) + printk(KERN_ERR PFX "CLEANUP on port 0x%x:" + " active_ios = %d\n", + tgt->rdata->ids.port_id, tgt->num_active_ios.counter); + spin_lock_bh(&tgt->tgt_lock); + tgt->flush_in_prog = 0; + spin_unlock_bh(&tgt->tgt_lock); +} + +static void bnx2fc_upload_session(struct fcoe_port *port, + struct bnx2fc_rport *tgt) +{ + struct bnx2fc_hba *hba = port->priv; + + BNX2FC_TGT_DBG(tgt, "upload_session: active_ios = %d\n", + tgt->num_active_ios.counter); + + /* + * Called with hba->hba_mutex held. + * This is a blocking call + */ + clear_bit(BNX2FC_FLAG_UPLD_REQ_COMPL, &tgt->flags); + bnx2fc_send_session_disable_req(port, tgt); + + /* + * wait for upload to complete. 3 Secs + * should be sufficient time for this process to complete. + */ + setup_timer(&tgt->upld_timer, bnx2fc_upld_timer, (unsigned long)tgt); + mod_timer(&tgt->upld_timer, jiffies + BNX2FC_FW_TIMEOUT); + + BNX2FC_TGT_DBG(tgt, "waiting for disable compl\n"); + wait_event_interruptible(tgt->upld_wait, + (test_bit( + BNX2FC_FLAG_UPLD_REQ_COMPL, + &tgt->flags))); + + if (signal_pending(current)) + flush_signals(current); + + del_timer_sync(&tgt->upld_timer); + + /* + * traverse thru the active_q and tmf_q and cleanup + * IOs in these lists + */ + BNX2FC_TGT_DBG(tgt, "flush/upload - disable wait flags = 0x%lx\n", + tgt->flags); + bnx2fc_flush_active_ios(tgt); + + /* Issue destroy KWQE */ + if (test_bit(BNX2FC_FLAG_DISABLED, &tgt->flags)) { + BNX2FC_TGT_DBG(tgt, "send destroy req\n"); + clear_bit(BNX2FC_FLAG_UPLD_REQ_COMPL, &tgt->flags); + bnx2fc_send_session_destroy_req(hba, tgt); + + /* wait for destroy to complete */ + setup_timer(&tgt->upld_timer, + bnx2fc_upld_timer, (unsigned long)tgt); + mod_timer(&tgt->upld_timer, jiffies + BNX2FC_FW_TIMEOUT); + + wait_event_interruptible(tgt->upld_wait, + (test_bit( + BNX2FC_FLAG_UPLD_REQ_COMPL, + &tgt->flags))); + + if (!(test_bit(BNX2FC_FLAG_DESTROYED, &tgt->flags))) + printk(KERN_ERR PFX "ERROR!! destroy timed out\n"); + + BNX2FC_TGT_DBG(tgt, "destroy wait complete flags = 0x%lx\n", + tgt->flags); + if (signal_pending(current)) + flush_signals(current); + + del_timer_sync(&tgt->upld_timer); + + } else + printk(KERN_ERR PFX "ERROR!! DISABLE req timed out, destroy" + " not sent to FW\n"); + + /* Free session resources */ + bnx2fc_free_session_resc(hba, tgt); + bnx2fc_free_conn_id(hba, tgt->fcoe_conn_id); +} + +static int bnx2fc_init_tgt(struct bnx2fc_rport *tgt, + struct fcoe_port *port, + struct fc_rport_priv *rdata) +{ + + struct fc_rport *rport = rdata->rport; + struct bnx2fc_hba *hba = port->priv; + + tgt->rport = rport; + tgt->rdata = rdata; + tgt->port = port; + + if (hba->num_ofld_sess >= BNX2FC_NUM_MAX_SESS) { + BNX2FC_TGT_DBG(tgt, "exceeded max sessions. logoff this tgt\n"); + tgt->fcoe_conn_id = -1; + return -1; + } + + tgt->fcoe_conn_id = bnx2fc_alloc_conn_id(hba, tgt); + if (tgt->fcoe_conn_id == -1) + return -1; + + BNX2FC_TGT_DBG(tgt, "init_tgt - conn_id = 0x%x\n", tgt->fcoe_conn_id); + + tgt->max_sqes = BNX2FC_SQ_WQES_MAX; + tgt->max_rqes = BNX2FC_RQ_WQES_MAX; + tgt->max_cqes = BNX2FC_CQ_WQES_MAX; + + /* Initialize the toggle bit */ + tgt->sq_curr_toggle_bit = 1; + tgt->cq_curr_toggle_bit = 1; + tgt->sq_prod_idx = 0; + tgt->cq_cons_idx = 0; + tgt->rq_prod_idx = 0x8000; + tgt->rq_cons_idx = 0; + atomic_set(&tgt->num_active_ios, 0); + + tgt->work_time_slice = 2; + + spin_lock_init(&tgt->tgt_lock); + spin_lock_init(&tgt->cq_lock); + + /* Initialize active_cmd_queue list */ + INIT_LIST_HEAD(&tgt->active_cmd_queue); + + /* Initialize IO retire queue */ + INIT_LIST_HEAD(&tgt->io_retire_queue); + + INIT_LIST_HEAD(&tgt->els_queue); + + /* Initialize active_tm_queue list */ + INIT_LIST_HEAD(&tgt->active_tm_queue); + + init_waitqueue_head(&tgt->ofld_wait); + init_waitqueue_head(&tgt->upld_wait); + + return 0; +} + +/** + * This event_callback is called after successful completion of libfc + * initiated target login. bnx2fc can proceed with initiating the session + * establishment. + */ +void bnx2fc_rport_event_handler(struct fc_lport *lport, + struct fc_rport_priv *rdata, + enum fc_rport_event event) +{ + struct fcoe_port *port = lport_priv(lport); + struct bnx2fc_hba *hba = port->priv; + struct fc_rport *rport = rdata->rport; + struct fc_rport_libfc_priv *rp; + struct bnx2fc_rport *tgt; + u32 port_id; + + BNX2FC_HBA_DBG(lport, "rport_event_hdlr: event = %d, port_id = 0x%x\n", + event, rdata->ids.port_id); + switch (event) { + case RPORT_EV_READY: + if (!rport) { + printk(KERN_ALERT PFX "rport is NULL: ERROR!\n"); + break; + } + + rp = rport->dd_data; + if (rport->port_id == FC_FID_DIR_SERV) { + /* + * bnx2fc_rport structure doesnt exist for + * directory server. + * We should not come here, as lport will + * take care of fabric login + */ + printk(KERN_ALERT PFX "%x - rport_event_handler ERROR\n", + rdata->ids.port_id); + break; + } + + if (rdata->spp_type != FC_TYPE_FCP) { + BNX2FC_HBA_DBG(lport, "not FCP type target." + " not offloading\n"); + break; + } + if (!(rdata->ids.roles & FC_RPORT_ROLE_FCP_TARGET)) { + BNX2FC_HBA_DBG(lport, "not FCP_TARGET" + " not offloading\n"); + break; + } + + /* + * Offlaod process is protected with hba mutex. + * Use the same mutex_lock for upload process too + */ + mutex_lock(&hba->hba_mutex); + tgt = (struct bnx2fc_rport *)&rp[1]; + + /* This can happen when ADISC finds the same target */ + if (test_bit(BNX2FC_FLAG_OFFLOADED, &tgt->flags)) { + BNX2FC_TGT_DBG(tgt, "already offloaded\n"); + mutex_unlock(&hba->hba_mutex); + return; + } + + /* + * Offload the session. This is a blocking call, and will + * wait until the session is offloaded. + */ + bnx2fc_offload_session(port, tgt, rdata); + + BNX2FC_TGT_DBG(tgt, "OFFLOAD num_ofld_sess = %d\n", + hba->num_ofld_sess); + + if (test_bit(BNX2FC_FLAG_OFFLOADED, &tgt->flags)) { + /* + * Session is offloaded and enabled. Map + * doorbell register for this target + */ + BNX2FC_TGT_DBG(tgt, "sess offloaded\n"); + /* This counter is protected with hba mutex */ + hba->num_ofld_sess++; + + set_bit(BNX2FC_FLAG_SESSION_READY, &tgt->flags); + } else { + /* + * Offload or enable would have failed. + * In offload/enable completion path, the + * rport would have already been removed + */ + BNX2FC_TGT_DBG(tgt, "Port is being logged off as " + "offloaded flag not set\n"); + } + mutex_unlock(&hba->hba_mutex); + break; + case RPORT_EV_LOGO: + case RPORT_EV_FAILED: + case RPORT_EV_STOP: + port_id = rdata->ids.port_id; + if (port_id == FC_FID_DIR_SERV) + break; + + if (!rport) { + printk(KERN_ALERT PFX "%x - rport not created Yet!!\n", + port_id); + break; + } + rp = rport->dd_data; + mutex_lock(&hba->hba_mutex); + /* + * Perform session upload. Note that rdata->peers is already + * removed from disc->rports list before we get this event. + */ + tgt = (struct bnx2fc_rport *)&rp[1]; + + if (!(test_bit(BNX2FC_FLAG_OFFLOADED, &tgt->flags))) { + mutex_unlock(&hba->hba_mutex); + break; + } + clear_bit(BNX2FC_FLAG_SESSION_READY, &tgt->flags); + + bnx2fc_upload_session(port, tgt); + hba->num_ofld_sess--; + BNX2FC_TGT_DBG(tgt, "UPLOAD num_ofld_sess = %d\n", + hba->num_ofld_sess); + /* + * Try to wake up the linkdown wait thread. If num_ofld_sess + * is 0, the waiting therad wakes up + */ + if ((hba->wait_for_link_down) && + (hba->num_ofld_sess == 0)) { + wake_up_interruptible(&hba->shutdown_wait); + } + if (test_bit(BNX2FC_FLAG_EXPL_LOGO, &tgt->flags)) { + printk(KERN_ERR PFX "Relogin to the tgt\n"); + mutex_lock(&lport->disc.disc_mutex); + lport->tt.rport_login(rdata); + mutex_unlock(&lport->disc.disc_mutex); + } + mutex_unlock(&hba->hba_mutex); + + break; + + case RPORT_EV_NONE: + break; + } +} + +/** + * bnx2fc_tgt_lookup() - Lookup a bnx2fc_rport by port_id + * + * @port: fcoe_port struct to lookup the target port on + * @port_id: The remote port ID to look up + */ +struct bnx2fc_rport *bnx2fc_tgt_lookup(struct fcoe_port *port, + u32 port_id) +{ + struct bnx2fc_hba *hba = port->priv; + struct bnx2fc_rport *tgt; + struct fc_rport_priv *rdata; + int i; + + for (i = 0; i < BNX2FC_NUM_MAX_SESS; i++) { + tgt = hba->tgt_ofld_list[i]; + if ((tgt) && (tgt->port == port)) { + rdata = tgt->rdata; + if (rdata->ids.port_id == port_id) { + if (rdata->rp_state != RPORT_ST_DELETE) { + BNX2FC_TGT_DBG(tgt, "rport " + "obtained\n"); + return tgt; + } else { + printk(KERN_ERR PFX "rport 0x%x " + "is in DELETED state\n", + rdata->ids.port_id); + return NULL; + } + } + } + } + return NULL; +} + + +/** + * bnx2fc_alloc_conn_id - allocates FCOE Connection id + * + * @hba: pointer to adapter structure + * @tgt: pointer to bnx2fc_rport structure + */ +static u32 bnx2fc_alloc_conn_id(struct bnx2fc_hba *hba, + struct bnx2fc_rport *tgt) +{ + u32 conn_id, next; + + /* called with hba mutex held */ + + /* + * tgt_ofld_list access is synchronized using + * both hba mutex and hba lock. Atleast hba mutex or + * hba lock needs to be held for read access. + */ + + spin_lock_bh(&hba->hba_lock); + next = hba->next_conn_id; + conn_id = hba->next_conn_id++; + if (hba->next_conn_id == BNX2FC_NUM_MAX_SESS) + hba->next_conn_id = 0; + + while (hba->tgt_ofld_list[conn_id] != NULL) { + conn_id++; + if (conn_id == BNX2FC_NUM_MAX_SESS) + conn_id = 0; + + if (conn_id == next) { + /* No free conn_ids are available */ + spin_unlock_bh(&hba->hba_lock); + return -1; + } + } + hba->tgt_ofld_list[conn_id] = tgt; + tgt->fcoe_conn_id = conn_id; + spin_unlock_bh(&hba->hba_lock); + return conn_id; +} + +static void bnx2fc_free_conn_id(struct bnx2fc_hba *hba, u32 conn_id) +{ + /* called with hba mutex held */ + spin_lock_bh(&hba->hba_lock); + hba->tgt_ofld_list[conn_id] = NULL; + hba->next_conn_id = conn_id; + spin_unlock_bh(&hba->hba_lock); +} + +/** + *bnx2fc_alloc_session_resc - Allocate qp resources for the session + * + */ +static int bnx2fc_alloc_session_resc(struct bnx2fc_hba *hba, + struct bnx2fc_rport *tgt) +{ + dma_addr_t page; + int num_pages; + u32 *pbl; + + /* Allocate and map SQ */ + tgt->sq_mem_size = tgt->max_sqes * BNX2FC_SQ_WQE_SIZE; + tgt->sq_mem_size = (tgt->sq_mem_size + (PAGE_SIZE - 1)) & PAGE_MASK; + + tgt->sq = dma_alloc_coherent(&hba->pcidev->dev, tgt->sq_mem_size, + &tgt->sq_dma, GFP_KERNEL); + if (!tgt->sq) { + printk(KERN_ALERT PFX "unable to allocate SQ memory %d\n", + tgt->sq_mem_size); + goto mem_alloc_failure; + } + memset(tgt->sq, 0, tgt->sq_mem_size); + + /* Allocate and map CQ */ + tgt->cq_mem_size = tgt->max_cqes * BNX2FC_CQ_WQE_SIZE; + tgt->cq_mem_size = (tgt->cq_mem_size + (PAGE_SIZE - 1)) & PAGE_MASK; + + tgt->cq = dma_alloc_coherent(&hba->pcidev->dev, tgt->cq_mem_size, + &tgt->cq_dma, GFP_KERNEL); + if (!tgt->cq) { + printk(KERN_ALERT PFX "unable to allocate CQ memory %d\n", + tgt->cq_mem_size); + goto mem_alloc_failure; + } + memset(tgt->cq, 0, tgt->cq_mem_size); + + /* Allocate and map RQ and RQ PBL */ + tgt->rq_mem_size = tgt->max_rqes * BNX2FC_RQ_WQE_SIZE; + tgt->rq_mem_size = (tgt->rq_mem_size + (PAGE_SIZE - 1)) & PAGE_MASK; + + tgt->rq = dma_alloc_coherent(&hba->pcidev->dev, tgt->rq_mem_size, + &tgt->rq_dma, GFP_KERNEL); + if (!tgt->rq) { + printk(KERN_ALERT PFX "unable to allocate RQ memory %d\n", + tgt->rq_mem_size); + goto mem_alloc_failure; + } + memset(tgt->rq, 0, tgt->rq_mem_size); + + tgt->rq_pbl_size = (tgt->rq_mem_size / PAGE_SIZE) * sizeof(void *); + tgt->rq_pbl_size = (tgt->rq_pbl_size + (PAGE_SIZE - 1)) & PAGE_MASK; + + tgt->rq_pbl = dma_alloc_coherent(&hba->pcidev->dev, tgt->rq_pbl_size, + &tgt->rq_pbl_dma, GFP_KERNEL); + if (!tgt->rq_pbl) { + printk(KERN_ALERT PFX "unable to allocate RQ PBL %d\n", + tgt->rq_pbl_size); + goto mem_alloc_failure; + } + + memset(tgt->rq_pbl, 0, tgt->rq_pbl_size); + num_pages = tgt->rq_mem_size / PAGE_SIZE; + page = tgt->rq_dma; + pbl = (u32 *)tgt->rq_pbl; + + while (num_pages--) { + *pbl = (u32)page; + pbl++; + *pbl = (u32)((u64)page >> 32); + pbl++; + page += PAGE_SIZE; + } + + /* Allocate and map XFERQ */ + tgt->xferq_mem_size = tgt->max_sqes * BNX2FC_XFERQ_WQE_SIZE; + tgt->xferq_mem_size = (tgt->xferq_mem_size + (PAGE_SIZE - 1)) & + PAGE_MASK; + + tgt->xferq = dma_alloc_coherent(&hba->pcidev->dev, tgt->xferq_mem_size, + &tgt->xferq_dma, GFP_KERNEL); + if (!tgt->xferq) { + printk(KERN_ALERT PFX "unable to allocate XFERQ %d\n", + tgt->xferq_mem_size); + goto mem_alloc_failure; + } + memset(tgt->xferq, 0, tgt->xferq_mem_size); + + /* Allocate and map CONFQ & CONFQ PBL */ + tgt->confq_mem_size = tgt->max_sqes * BNX2FC_CONFQ_WQE_SIZE; + tgt->confq_mem_size = (tgt->confq_mem_size + (PAGE_SIZE - 1)) & + PAGE_MASK; + + tgt->confq = dma_alloc_coherent(&hba->pcidev->dev, tgt->confq_mem_size, + &tgt->confq_dma, GFP_KERNEL); + if (!tgt->confq) { + printk(KERN_ALERT PFX "unable to allocate CONFQ %d\n", + tgt->confq_mem_size); + goto mem_alloc_failure; + } + memset(tgt->confq, 0, tgt->confq_mem_size); + + tgt->confq_pbl_size = + (tgt->confq_mem_size / PAGE_SIZE) * sizeof(void *); + tgt->confq_pbl_size = + (tgt->confq_pbl_size + (PAGE_SIZE - 1)) & PAGE_MASK; + + tgt->confq_pbl = dma_alloc_coherent(&hba->pcidev->dev, + tgt->confq_pbl_size, + &tgt->confq_pbl_dma, GFP_KERNEL); + if (!tgt->confq_pbl) { + printk(KERN_ALERT PFX "unable to allocate CONFQ PBL %d\n", + tgt->confq_pbl_size); + goto mem_alloc_failure; + } + + memset(tgt->confq_pbl, 0, tgt->confq_pbl_size); + num_pages = tgt->confq_mem_size / PAGE_SIZE; + page = tgt->confq_dma; + pbl = (u32 *)tgt->confq_pbl; + + while (num_pages--) { + *pbl = (u32)page; + pbl++; + *pbl = (u32)((u64)page >> 32); + pbl++; + page += PAGE_SIZE; + } + + /* Allocate and map ConnDB */ + tgt->conn_db_mem_size = sizeof(struct fcoe_conn_db); + + tgt->conn_db = dma_alloc_coherent(&hba->pcidev->dev, + tgt->conn_db_mem_size, + &tgt->conn_db_dma, GFP_KERNEL); + if (!tgt->conn_db) { + printk(KERN_ALERT PFX "unable to allocate conn_db %d\n", + tgt->conn_db_mem_size); + goto mem_alloc_failure; + } + memset(tgt->conn_db, 0, tgt->conn_db_mem_size); + + + /* Allocate and map LCQ */ + tgt->lcq_mem_size = (tgt->max_sqes + 8) * BNX2FC_SQ_WQE_SIZE; + tgt->lcq_mem_size = (tgt->lcq_mem_size + (PAGE_SIZE - 1)) & + PAGE_MASK; + + tgt->lcq = dma_alloc_coherent(&hba->pcidev->dev, tgt->lcq_mem_size, + &tgt->lcq_dma, GFP_KERNEL); + + if (!tgt->lcq) { + printk(KERN_ALERT PFX "unable to allocate lcq %d\n", + tgt->lcq_mem_size); + goto mem_alloc_failure; + } + memset(tgt->lcq, 0, tgt->lcq_mem_size); + + /* Arm CQ */ + tgt->conn_db->cq_arm.lo = -1; + tgt->conn_db->rq_prod = 0x8000; + + return 0; + +mem_alloc_failure: + bnx2fc_free_session_resc(hba, tgt); + bnx2fc_free_conn_id(hba, tgt->fcoe_conn_id); + return -ENOMEM; +} + +/** + * bnx2i_free_session_resc - free qp resources for the session + * + * @hba: adapter structure pointer + * @tgt: bnx2fc_rport structure pointer + * + * Free QP resources - SQ/RQ/CQ/XFERQ memory and PBL + */ +static void bnx2fc_free_session_resc(struct bnx2fc_hba *hba, + struct bnx2fc_rport *tgt) +{ + BNX2FC_TGT_DBG(tgt, "Freeing up session resources\n"); + + if (tgt->ctx_base) { + iounmap(tgt->ctx_base); + tgt->ctx_base = NULL; + } + /* Free LCQ */ + if (tgt->lcq) { + dma_free_coherent(&hba->pcidev->dev, tgt->lcq_mem_size, + tgt->lcq, tgt->lcq_dma); + tgt->lcq = NULL; + } + /* Free connDB */ + if (tgt->conn_db) { + dma_free_coherent(&hba->pcidev->dev, tgt->conn_db_mem_size, + tgt->conn_db, tgt->conn_db_dma); + tgt->conn_db = NULL; + } + /* Free confq and confq pbl */ + if (tgt->confq_pbl) { + dma_free_coherent(&hba->pcidev->dev, tgt->confq_pbl_size, + tgt->confq_pbl, tgt->confq_pbl_dma); + tgt->confq_pbl = NULL; + } + if (tgt->confq) { + dma_free_coherent(&hba->pcidev->dev, tgt->confq_mem_size, + tgt->confq, tgt->confq_dma); + tgt->confq = NULL; + } + /* Free XFERQ */ + if (tgt->xferq) { + dma_free_coherent(&hba->pcidev->dev, tgt->xferq_mem_size, + tgt->xferq, tgt->xferq_dma); + tgt->xferq = NULL; + } + /* Free RQ PBL and RQ */ + if (tgt->rq_pbl) { + dma_free_coherent(&hba->pcidev->dev, tgt->rq_pbl_size, + tgt->rq_pbl, tgt->rq_pbl_dma); + tgt->rq_pbl = NULL; + } + if (tgt->rq) { + dma_free_coherent(&hba->pcidev->dev, tgt->rq_mem_size, + tgt->rq, tgt->rq_dma); + tgt->rq = NULL; + } + /* Free CQ */ + spin_lock_bh(&tgt->cq_lock); + if (tgt->cq) { + dma_free_coherent(&hba->pcidev->dev, tgt->cq_mem_size, + tgt->cq, tgt->cq_dma); + tgt->cq = NULL; + } + spin_unlock_bh(&tgt->cq_lock); + /* Free SQ */ + if (tgt->sq) { + dma_free_coherent(&hba->pcidev->dev, tgt->sq_mem_size, + tgt->sq, tgt->sq_dma); + tgt->sq = NULL; + } +} |