diff options
author | Kiran Patil <kiran.patil@intel.com> | 2011-04-18 19:24:14 -0400 |
---|---|---|
committer | James Bottomley <jbottomley@parallels.com> | 2011-05-17 02:52:46 -0400 |
commit | 3699d92a4d7b649bde67dff3cc681400992e0254 (patch) | |
tree | 728d226d93278025ed7c8ab35265877a000d7c3e /drivers | |
parent | 8a025bbc8ff897c36c6e2b91c2e852ea5e569394 (diff) |
[SCSI] tcm_fc: Adding FC_FC4 provider (tcm_fc) for FCoE target (TCM - target core) support
This is a comprehensive patch for FC-FC4 provider. tcm_fc is a FC-FC4
provider which glues target core (TCM) with Fiber channel library
(libfc). tcm_fc uses existing FC4 provider hooks from Fiber channel
library. This Fiber channel library is used by FCoE (transport - FC
over Ethernet) protocol driver as well.
Combination of modules such as Fiber channel library, tcm_fc, TCM
target core, and FCoE protocol driver enables functional FCoE target.
This patch includes initial commit for tcm_fc plus additional
enhancement, bug fixes.
This tcm_fc module essentially contains 3 entry points such as "prli",
"prlo", "recv". When process login request (ELS_PRLI) request is
received, Fiber channel library (libfc) module calls passive providers
(FC-FC4, tcm_fc) (if any registered) "prli" function. Likewise when
LOGO request is received, "prlo" function of passive provider is
invoked by libfc. For all other request (e.g. any read/write, task
management, LUN inquiry commands), "recv" function of passiver
provider is invoked by libfc. Those passive providers "prli, prlo,
recv" functions interact with TCM target core for requested operation.
This module was primarily developed by "Joe Eykholt" and there were
significant contributions from the people listed under signed-off.
Signed-off-by: Joe Eykholt <jeykholt@cisco.com>
Signed-off-by: Nicholas A. Bellinger <nab@linux-iscsi.org>
Signed-off-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Yi Zou <yi.zou@intel.com>
Signed-off-by: Kiran Patil <kiran.patil@intel.com>
Acked-by: Robert Love <robert.w.love@intel.com>
Signed-off-by: James Bottomley <jbottomley@parallels.com>
Signed-off-by: James Bottomley <James.Bottomley@suse.de>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/target/Kconfig | 1 | ||||
-rw-r--r-- | drivers/target/Makefile | 2 | ||||
-rw-r--r-- | drivers/target/tcm_fc/Kconfig | 5 | ||||
-rw-r--r-- | drivers/target/tcm_fc/Makefile | 15 | ||||
-rw-r--r-- | drivers/target/tcm_fc/tcm_fc.h | 215 | ||||
-rw-r--r-- | drivers/target/tcm_fc/tfc_cmd.c | 696 | ||||
-rw-r--r-- | drivers/target/tcm_fc/tfc_conf.c | 677 | ||||
-rw-r--r-- | drivers/target/tcm_fc/tfc_io.c | 374 | ||||
-rw-r--r-- | drivers/target/tcm_fc/tfc_sess.c | 541 |
9 files changed, 2526 insertions, 0 deletions
diff --git a/drivers/target/Kconfig b/drivers/target/Kconfig index 9ef2dbbfa62b..5cb0f0ef6af0 100644 --- a/drivers/target/Kconfig +++ b/drivers/target/Kconfig | |||
@@ -30,5 +30,6 @@ config TCM_PSCSI | |||
30 | passthrough access to Linux/SCSI device | 30 | passthrough access to Linux/SCSI device |
31 | 31 | ||
32 | source "drivers/target/loopback/Kconfig" | 32 | source "drivers/target/loopback/Kconfig" |
33 | source "drivers/target/tcm_fc/Kconfig" | ||
33 | 34 | ||
34 | endif | 35 | endif |
diff --git a/drivers/target/Makefile b/drivers/target/Makefile index 1178bbfc68fe..21df808a992c 100644 --- a/drivers/target/Makefile +++ b/drivers/target/Makefile | |||
@@ -24,3 +24,5 @@ obj-$(CONFIG_TCM_PSCSI) += target_core_pscsi.o | |||
24 | 24 | ||
25 | # Fabric modules | 25 | # Fabric modules |
26 | obj-$(CONFIG_LOOPBACK_TARGET) += loopback/ | 26 | obj-$(CONFIG_LOOPBACK_TARGET) += loopback/ |
27 | |||
28 | obj-$(CONFIG_TCM_FC) += tcm_fc/ | ||
diff --git a/drivers/target/tcm_fc/Kconfig b/drivers/target/tcm_fc/Kconfig new file mode 100644 index 000000000000..40caf458e89e --- /dev/null +++ b/drivers/target/tcm_fc/Kconfig | |||
@@ -0,0 +1,5 @@ | |||
1 | config TCM_FC | ||
2 | tristate "TCM_FC fabric Plugin" | ||
3 | depends on LIBFC | ||
4 | help | ||
5 | Say Y here to enable the TCM FC plugin for accessing FC fabrics in TCM | ||
diff --git a/drivers/target/tcm_fc/Makefile b/drivers/target/tcm_fc/Makefile new file mode 100644 index 000000000000..7a5c2b64cf65 --- /dev/null +++ b/drivers/target/tcm_fc/Makefile | |||
@@ -0,0 +1,15 @@ | |||
1 | EXTRA_CFLAGS += -I$(srctree)/drivers/target/ \ | ||
2 | -I$(srctree)/drivers/scsi/ \ | ||
3 | -I$(srctree)/include/scsi/ \ | ||
4 | -I$(srctree)/drivers/target/tcm_fc/ | ||
5 | |||
6 | tcm_fc-y += tfc_cmd.o \ | ||
7 | tfc_conf.o \ | ||
8 | tfc_io.o \ | ||
9 | tfc_sess.o | ||
10 | |||
11 | obj-$(CONFIG_TCM_FC) += tcm_fc.o | ||
12 | |||
13 | ifdef CONFIGFS_TCM_FC_DEBUG | ||
14 | EXTRA_CFLAGS += -DTCM_FC_DEBUG | ||
15 | endif | ||
diff --git a/drivers/target/tcm_fc/tcm_fc.h b/drivers/target/tcm_fc/tcm_fc.h new file mode 100644 index 000000000000..defff32b7880 --- /dev/null +++ b/drivers/target/tcm_fc/tcm_fc.h | |||
@@ -0,0 +1,215 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2010 Cisco Systems, Inc. | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify it | ||
5 | * under the terms and conditions of the GNU General Public License, | ||
6 | * version 2, as published by the Free Software Foundation. | ||
7 | * | ||
8 | * This program is distributed in the hope it will be useful, but WITHOUT | ||
9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
10 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
11 | * more details. | ||
12 | * | ||
13 | * You should have received a copy of the GNU General Public License along with | ||
14 | * this program; if not, write to the Free Software Foundation, Inc., | ||
15 | * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. | ||
16 | */ | ||
17 | #ifndef __TCM_FC_H__ | ||
18 | #define __TCM_FC_H__ | ||
19 | |||
20 | #define FT_VERSION "0.3" | ||
21 | |||
22 | #define FT_NAMELEN 32 /* length of ASCII WWPNs including pad */ | ||
23 | #define FT_TPG_NAMELEN 32 /* max length of TPG name */ | ||
24 | #define FT_LUN_NAMELEN 32 /* max length of LUN name */ | ||
25 | |||
26 | /* | ||
27 | * Debug options. | ||
28 | */ | ||
29 | #define FT_DEBUG_CONF 0x01 /* configuration messages */ | ||
30 | #define FT_DEBUG_SESS 0x02 /* session messages */ | ||
31 | #define FT_DEBUG_TM 0x04 /* TM operations */ | ||
32 | #define FT_DEBUG_IO 0x08 /* I/O commands */ | ||
33 | #define FT_DEBUG_DATA 0x10 /* Data transfer */ | ||
34 | |||
35 | extern unsigned int ft_debug_logging; /* debug options */ | ||
36 | |||
37 | #define FT_DEBUG(mask, fmt, args...) \ | ||
38 | do { \ | ||
39 | if (ft_debug_logging & (mask)) \ | ||
40 | printk(KERN_INFO "tcm_fc: %s: " fmt, \ | ||
41 | __func__, ##args); \ | ||
42 | } while (0) | ||
43 | |||
44 | #define FT_CONF_DBG(fmt, args...) FT_DEBUG(FT_DEBUG_CONF, fmt, ##args) | ||
45 | #define FT_SESS_DBG(fmt, args...) FT_DEBUG(FT_DEBUG_SESS, fmt, ##args) | ||
46 | #define FT_TM_DBG(fmt, args...) FT_DEBUG(FT_DEBUG_TM, fmt, ##args) | ||
47 | #define FT_IO_DBG(fmt, args...) FT_DEBUG(FT_DEBUG_IO, fmt, ##args) | ||
48 | #define FT_DATA_DBG(fmt, args...) FT_DEBUG(FT_DEBUG_DATA, fmt, ##args) | ||
49 | |||
50 | struct ft_transport_id { | ||
51 | __u8 format; | ||
52 | __u8 __resvd1[7]; | ||
53 | __u8 wwpn[8]; | ||
54 | __u8 __resvd2[8]; | ||
55 | } __attribute__((__packed__)); | ||
56 | |||
57 | /* | ||
58 | * Session (remote port). | ||
59 | */ | ||
60 | struct ft_sess { | ||
61 | u32 port_id; /* for hash lookup use only */ | ||
62 | u32 params; | ||
63 | u16 max_frame; /* maximum frame size */ | ||
64 | u64 port_name; /* port name for transport ID */ | ||
65 | struct ft_tport *tport; | ||
66 | struct se_session *se_sess; | ||
67 | struct hlist_node hash; /* linkage in ft_sess_hash table */ | ||
68 | struct rcu_head rcu; | ||
69 | struct kref kref; /* ref for hash and outstanding I/Os */ | ||
70 | }; | ||
71 | |||
72 | /* | ||
73 | * Hash table of sessions per local port. | ||
74 | * Hash lookup by remote port FC_ID. | ||
75 | */ | ||
76 | #define FT_SESS_HASH_BITS 6 | ||
77 | #define FT_SESS_HASH_SIZE (1 << FT_SESS_HASH_BITS) | ||
78 | |||
79 | /* | ||
80 | * Per local port data. | ||
81 | * This is created only after a TPG exists that allows target function | ||
82 | * for the local port. If the TPG exists, this is allocated when | ||
83 | * we're notified that the local port has been created, or when | ||
84 | * the first PRLI provider callback is received. | ||
85 | */ | ||
86 | struct ft_tport { | ||
87 | struct fc_lport *lport; | ||
88 | struct ft_tpg *tpg; /* NULL if TPG deleted before tport */ | ||
89 | u32 sess_count; /* number of sessions in hash */ | ||
90 | struct rcu_head rcu; | ||
91 | struct hlist_head hash[FT_SESS_HASH_SIZE]; /* list of sessions */ | ||
92 | }; | ||
93 | |||
94 | /* | ||
95 | * Node ID and authentication. | ||
96 | */ | ||
97 | struct ft_node_auth { | ||
98 | u64 port_name; | ||
99 | u64 node_name; | ||
100 | }; | ||
101 | |||
102 | /* | ||
103 | * Node ACL for FC remote port session. | ||
104 | */ | ||
105 | struct ft_node_acl { | ||
106 | struct ft_node_auth node_auth; | ||
107 | struct se_node_acl se_node_acl; | ||
108 | }; | ||
109 | |||
110 | struct ft_lun { | ||
111 | u32 index; | ||
112 | char name[FT_LUN_NAMELEN]; | ||
113 | }; | ||
114 | |||
115 | /* | ||
116 | * Target portal group (local port). | ||
117 | */ | ||
118 | struct ft_tpg { | ||
119 | u32 index; | ||
120 | struct ft_lport_acl *lport_acl; | ||
121 | struct ft_tport *tport; /* active tport or NULL */ | ||
122 | struct list_head list; /* linkage in ft_lport_acl tpg_list */ | ||
123 | struct list_head lun_list; /* head of LUNs */ | ||
124 | struct se_portal_group se_tpg; | ||
125 | struct task_struct *thread; /* processing thread */ | ||
126 | struct se_queue_obj qobj; /* queue for processing thread */ | ||
127 | }; | ||
128 | |||
129 | struct ft_lport_acl { | ||
130 | u64 wwpn; | ||
131 | char name[FT_NAMELEN]; | ||
132 | struct list_head list; | ||
133 | struct list_head tpg_list; | ||
134 | struct se_wwn fc_lport_wwn; | ||
135 | }; | ||
136 | |||
137 | enum ft_cmd_state { | ||
138 | FC_CMD_ST_NEW = 0, | ||
139 | FC_CMD_ST_REJ | ||
140 | }; | ||
141 | |||
142 | /* | ||
143 | * Commands | ||
144 | */ | ||
145 | struct ft_cmd { | ||
146 | enum ft_cmd_state state; | ||
147 | u16 lun; /* LUN from request */ | ||
148 | struct ft_sess *sess; /* session held for cmd */ | ||
149 | struct fc_seq *seq; /* sequence in exchange mgr */ | ||
150 | struct se_cmd se_cmd; /* Local TCM I/O descriptor */ | ||
151 | struct fc_frame *req_frame; | ||
152 | unsigned char *cdb; /* pointer to CDB inside frame */ | ||
153 | u32 write_data_len; /* data received on writes */ | ||
154 | struct se_queue_req se_req; | ||
155 | /* Local sense buffer */ | ||
156 | unsigned char ft_sense_buffer[TRANSPORT_SENSE_BUFFER]; | ||
157 | u32 was_ddp_setup:1; /* Set only if ddp is setup */ | ||
158 | struct scatterlist *sg; /* Set only if DDP is setup */ | ||
159 | u32 sg_cnt; /* No. of item in scatterlist */ | ||
160 | }; | ||
161 | |||
162 | extern struct list_head ft_lport_list; | ||
163 | extern struct mutex ft_lport_lock; | ||
164 | extern struct fc4_prov ft_prov; | ||
165 | extern struct target_fabric_configfs *ft_configfs; | ||
166 | |||
167 | /* | ||
168 | * Fabric methods. | ||
169 | */ | ||
170 | |||
171 | /* | ||
172 | * Session ops. | ||
173 | */ | ||
174 | void ft_sess_put(struct ft_sess *); | ||
175 | int ft_sess_shutdown(struct se_session *); | ||
176 | void ft_sess_close(struct se_session *); | ||
177 | void ft_sess_stop(struct se_session *, int, int); | ||
178 | int ft_sess_logged_in(struct se_session *); | ||
179 | u32 ft_sess_get_index(struct se_session *); | ||
180 | u32 ft_sess_get_port_name(struct se_session *, unsigned char *, u32); | ||
181 | void ft_sess_set_erl0(struct se_session *); | ||
182 | |||
183 | void ft_lport_add(struct fc_lport *, void *); | ||
184 | void ft_lport_del(struct fc_lport *, void *); | ||
185 | int ft_lport_notify(struct notifier_block *, unsigned long, void *); | ||
186 | |||
187 | /* | ||
188 | * IO methods. | ||
189 | */ | ||
190 | void ft_check_stop_free(struct se_cmd *); | ||
191 | void ft_release_cmd(struct se_cmd *); | ||
192 | int ft_queue_status(struct se_cmd *); | ||
193 | int ft_queue_data_in(struct se_cmd *); | ||
194 | int ft_write_pending(struct se_cmd *); | ||
195 | int ft_write_pending_status(struct se_cmd *); | ||
196 | u32 ft_get_task_tag(struct se_cmd *); | ||
197 | int ft_get_cmd_state(struct se_cmd *); | ||
198 | void ft_new_cmd_failure(struct se_cmd *); | ||
199 | int ft_queue_tm_resp(struct se_cmd *); | ||
200 | int ft_is_state_remove(struct se_cmd *); | ||
201 | |||
202 | /* | ||
203 | * other internal functions. | ||
204 | */ | ||
205 | int ft_thread(void *); | ||
206 | void ft_recv_req(struct ft_sess *, struct fc_frame *); | ||
207 | struct ft_tpg *ft_lport_find_tpg(struct fc_lport *); | ||
208 | struct ft_node_acl *ft_acl_get(struct ft_tpg *, struct fc_rport_priv *); | ||
209 | |||
210 | void ft_recv_write_data(struct ft_cmd *, struct fc_frame *); | ||
211 | void ft_dump_cmd(struct ft_cmd *, const char *caller); | ||
212 | |||
213 | ssize_t ft_format_wwn(char *, size_t, u64); | ||
214 | |||
215 | #endif /* __TCM_FC_H__ */ | ||
diff --git a/drivers/target/tcm_fc/tfc_cmd.c b/drivers/target/tcm_fc/tfc_cmd.c new file mode 100644 index 000000000000..49e51778f733 --- /dev/null +++ b/drivers/target/tcm_fc/tfc_cmd.c | |||
@@ -0,0 +1,696 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2010 Cisco Systems, Inc. | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify it | ||
5 | * under the terms and conditions of the GNU General Public License, | ||
6 | * version 2, as published by the Free Software Foundation. | ||
7 | * | ||
8 | * This program is distributed in the hope it will be useful, but WITHOUT | ||
9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
10 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
11 | * more details. | ||
12 | * | ||
13 | * You should have received a copy of the GNU General Public License along with | ||
14 | * this program; if not, write to the Free Software Foundation, Inc., | ||
15 | * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. | ||
16 | */ | ||
17 | |||
18 | /* XXX TBD some includes may be extraneous */ | ||
19 | |||
20 | #include <linux/module.h> | ||
21 | #include <linux/moduleparam.h> | ||
22 | #include <linux/version.h> | ||
23 | #include <generated/utsrelease.h> | ||
24 | #include <linux/utsname.h> | ||
25 | #include <linux/init.h> | ||
26 | #include <linux/slab.h> | ||
27 | #include <linux/kthread.h> | ||
28 | #include <linux/types.h> | ||
29 | #include <linux/string.h> | ||
30 | #include <linux/configfs.h> | ||
31 | #include <linux/ctype.h> | ||
32 | #include <linux/hash.h> | ||
33 | #include <asm/unaligned.h> | ||
34 | #include <scsi/scsi.h> | ||
35 | #include <scsi/scsi_host.h> | ||
36 | #include <scsi/scsi_device.h> | ||
37 | #include <scsi/scsi_cmnd.h> | ||
38 | #include <scsi/libfc.h> | ||
39 | #include <scsi/fc_encode.h> | ||
40 | |||
41 | #include <target/target_core_base.h> | ||
42 | #include <target/target_core_transport.h> | ||
43 | #include <target/target_core_fabric_ops.h> | ||
44 | #include <target/target_core_device.h> | ||
45 | #include <target/target_core_tpg.h> | ||
46 | #include <target/target_core_configfs.h> | ||
47 | #include <target/target_core_base.h> | ||
48 | #include <target/target_core_tmr.h> | ||
49 | #include <target/configfs_macros.h> | ||
50 | |||
51 | #include "tcm_fc.h" | ||
52 | |||
53 | /* | ||
54 | * Dump cmd state for debugging. | ||
55 | */ | ||
56 | void ft_dump_cmd(struct ft_cmd *cmd, const char *caller) | ||
57 | { | ||
58 | struct fc_exch *ep; | ||
59 | struct fc_seq *sp; | ||
60 | struct se_cmd *se_cmd; | ||
61 | struct se_mem *mem; | ||
62 | struct se_transport_task *task; | ||
63 | |||
64 | if (!(ft_debug_logging & FT_DEBUG_IO)) | ||
65 | return; | ||
66 | |||
67 | se_cmd = &cmd->se_cmd; | ||
68 | printk(KERN_INFO "%s: cmd %p state %d sess %p seq %p se_cmd %p\n", | ||
69 | caller, cmd, cmd->state, cmd->sess, cmd->seq, se_cmd); | ||
70 | printk(KERN_INFO "%s: cmd %p cdb %p\n", | ||
71 | caller, cmd, cmd->cdb); | ||
72 | printk(KERN_INFO "%s: cmd %p lun %d\n", caller, cmd, cmd->lun); | ||
73 | |||
74 | task = T_TASK(se_cmd); | ||
75 | printk(KERN_INFO "%s: cmd %p task %p se_num %u buf %p len %u se_cmd_flags <0x%x>\n", | ||
76 | caller, cmd, task, task->t_tasks_se_num, | ||
77 | task->t_task_buf, se_cmd->data_length, se_cmd->se_cmd_flags); | ||
78 | if (task->t_mem_list) | ||
79 | list_for_each_entry(mem, task->t_mem_list, se_list) | ||
80 | printk(KERN_INFO "%s: cmd %p mem %p page %p " | ||
81 | "len 0x%x off 0x%x\n", | ||
82 | caller, cmd, mem, | ||
83 | mem->se_page, mem->se_len, mem->se_off); | ||
84 | sp = cmd->seq; | ||
85 | if (sp) { | ||
86 | ep = fc_seq_exch(sp); | ||
87 | printk(KERN_INFO "%s: cmd %p sid %x did %x " | ||
88 | "ox_id %x rx_id %x seq_id %x e_stat %x\n", | ||
89 | caller, cmd, ep->sid, ep->did, ep->oxid, ep->rxid, | ||
90 | sp->id, ep->esb_stat); | ||
91 | } | ||
92 | print_hex_dump(KERN_INFO, "ft_dump_cmd ", DUMP_PREFIX_NONE, | ||
93 | 16, 4, cmd->cdb, MAX_COMMAND_SIZE, 0); | ||
94 | } | ||
95 | |||
96 | /* | ||
97 | * Get LUN from CDB. | ||
98 | */ | ||
99 | static int ft_get_lun_for_cmd(struct ft_cmd *cmd, u8 *lunp) | ||
100 | { | ||
101 | u64 lun; | ||
102 | |||
103 | lun = lunp[1]; | ||
104 | switch (lunp[0] >> 6) { | ||
105 | case 0: | ||
106 | break; | ||
107 | case 1: | ||
108 | lun |= (lunp[0] & 0x3f) << 8; | ||
109 | break; | ||
110 | default: | ||
111 | return -1; | ||
112 | } | ||
113 | if (lun >= TRANSPORT_MAX_LUNS_PER_TPG) | ||
114 | return -1; | ||
115 | cmd->lun = lun; | ||
116 | return transport_get_lun_for_cmd(&cmd->se_cmd, NULL, lun); | ||
117 | } | ||
118 | |||
119 | static void ft_queue_cmd(struct ft_sess *sess, struct ft_cmd *cmd) | ||
120 | { | ||
121 | struct se_queue_obj *qobj; | ||
122 | unsigned long flags; | ||
123 | |||
124 | qobj = &sess->tport->tpg->qobj; | ||
125 | spin_lock_irqsave(&qobj->cmd_queue_lock, flags); | ||
126 | list_add_tail(&cmd->se_req.qr_list, &qobj->qobj_list); | ||
127 | spin_unlock_irqrestore(&qobj->cmd_queue_lock, flags); | ||
128 | atomic_inc(&qobj->queue_cnt); | ||
129 | wake_up_interruptible(&qobj->thread_wq); | ||
130 | } | ||
131 | |||
132 | static struct ft_cmd *ft_dequeue_cmd(struct se_queue_obj *qobj) | ||
133 | { | ||
134 | unsigned long flags; | ||
135 | struct se_queue_req *qr; | ||
136 | |||
137 | spin_lock_irqsave(&qobj->cmd_queue_lock, flags); | ||
138 | if (list_empty(&qobj->qobj_list)) { | ||
139 | spin_unlock_irqrestore(&qobj->cmd_queue_lock, flags); | ||
140 | return NULL; | ||
141 | } | ||
142 | qr = list_first_entry(&qobj->qobj_list, struct se_queue_req, qr_list); | ||
143 | list_del(&qr->qr_list); | ||
144 | atomic_dec(&qobj->queue_cnt); | ||
145 | spin_unlock_irqrestore(&qobj->cmd_queue_lock, flags); | ||
146 | return container_of(qr, struct ft_cmd, se_req); | ||
147 | } | ||
148 | |||
149 | static void ft_free_cmd(struct ft_cmd *cmd) | ||
150 | { | ||
151 | struct fc_frame *fp; | ||
152 | struct fc_lport *lport; | ||
153 | |||
154 | if (!cmd) | ||
155 | return; | ||
156 | fp = cmd->req_frame; | ||
157 | lport = fr_dev(fp); | ||
158 | if (fr_seq(fp)) | ||
159 | lport->tt.seq_release(fr_seq(fp)); | ||
160 | fc_frame_free(fp); | ||
161 | ft_sess_put(cmd->sess); /* undo get from lookup at recv */ | ||
162 | kfree(cmd); | ||
163 | } | ||
164 | |||
165 | void ft_release_cmd(struct se_cmd *se_cmd) | ||
166 | { | ||
167 | struct ft_cmd *cmd = container_of(se_cmd, struct ft_cmd, se_cmd); | ||
168 | |||
169 | ft_free_cmd(cmd); | ||
170 | } | ||
171 | |||
172 | void ft_check_stop_free(struct se_cmd *se_cmd) | ||
173 | { | ||
174 | transport_generic_free_cmd(se_cmd, 0, 1, 0); | ||
175 | } | ||
176 | |||
177 | /* | ||
178 | * Send response. | ||
179 | */ | ||
180 | int ft_queue_status(struct se_cmd *se_cmd) | ||
181 | { | ||
182 | struct ft_cmd *cmd = container_of(se_cmd, struct ft_cmd, se_cmd); | ||
183 | struct fc_frame *fp; | ||
184 | struct fcp_resp_with_ext *fcp; | ||
185 | struct fc_lport *lport; | ||
186 | struct fc_exch *ep; | ||
187 | size_t len; | ||
188 | |||
189 | ft_dump_cmd(cmd, __func__); | ||
190 | ep = fc_seq_exch(cmd->seq); | ||
191 | lport = ep->lp; | ||
192 | len = sizeof(*fcp) + se_cmd->scsi_sense_length; | ||
193 | fp = fc_frame_alloc(lport, len); | ||
194 | if (!fp) { | ||
195 | /* XXX shouldn't just drop it - requeue and retry? */ | ||
196 | return 0; | ||
197 | } | ||
198 | fcp = fc_frame_payload_get(fp, len); | ||
199 | memset(fcp, 0, len); | ||
200 | fcp->resp.fr_status = se_cmd->scsi_status; | ||
201 | |||
202 | len = se_cmd->scsi_sense_length; | ||
203 | if (len) { | ||
204 | fcp->resp.fr_flags |= FCP_SNS_LEN_VAL; | ||
205 | fcp->ext.fr_sns_len = htonl(len); | ||
206 | memcpy((fcp + 1), se_cmd->sense_buffer, len); | ||
207 | } | ||
208 | |||
209 | /* | ||
210 | * Test underflow and overflow with one mask. Usually both are off. | ||
211 | * Bidirectional commands are not handled yet. | ||
212 | */ | ||
213 | if (se_cmd->se_cmd_flags & (SCF_OVERFLOW_BIT | SCF_UNDERFLOW_BIT)) { | ||
214 | if (se_cmd->se_cmd_flags & SCF_OVERFLOW_BIT) | ||
215 | fcp->resp.fr_flags |= FCP_RESID_OVER; | ||
216 | else | ||
217 | fcp->resp.fr_flags |= FCP_RESID_UNDER; | ||
218 | fcp->ext.fr_resid = cpu_to_be32(se_cmd->residual_count); | ||
219 | } | ||
220 | |||
221 | /* | ||
222 | * Send response. | ||
223 | */ | ||
224 | cmd->seq = lport->tt.seq_start_next(cmd->seq); | ||
225 | fc_fill_fc_hdr(fp, FC_RCTL_DD_CMD_STATUS, ep->did, ep->sid, FC_TYPE_FCP, | ||
226 | FC_FC_EX_CTX | FC_FC_LAST_SEQ | FC_FC_END_SEQ, 0); | ||
227 | |||
228 | lport->tt.seq_send(lport, cmd->seq, fp); | ||
229 | lport->tt.exch_done(cmd->seq); | ||
230 | return 0; | ||
231 | } | ||
232 | |||
233 | int ft_write_pending_status(struct se_cmd *se_cmd) | ||
234 | { | ||
235 | struct ft_cmd *cmd = container_of(se_cmd, struct ft_cmd, se_cmd); | ||
236 | |||
237 | return cmd->write_data_len != se_cmd->data_length; | ||
238 | } | ||
239 | |||
240 | /* | ||
241 | * Send TX_RDY (transfer ready). | ||
242 | */ | ||
243 | int ft_write_pending(struct se_cmd *se_cmd) | ||
244 | { | ||
245 | struct ft_cmd *cmd = container_of(se_cmd, struct ft_cmd, se_cmd); | ||
246 | struct fc_frame *fp; | ||
247 | struct fcp_txrdy *txrdy; | ||
248 | struct fc_lport *lport; | ||
249 | struct fc_exch *ep; | ||
250 | struct fc_frame_header *fh; | ||
251 | u32 f_ctl; | ||
252 | |||
253 | ft_dump_cmd(cmd, __func__); | ||
254 | |||
255 | ep = fc_seq_exch(cmd->seq); | ||
256 | lport = ep->lp; | ||
257 | fp = fc_frame_alloc(lport, sizeof(*txrdy)); | ||
258 | if (!fp) | ||
259 | return PYX_TRANSPORT_OUT_OF_MEMORY_RESOURCES; | ||
260 | |||
261 | txrdy = fc_frame_payload_get(fp, sizeof(*txrdy)); | ||
262 | memset(txrdy, 0, sizeof(*txrdy)); | ||
263 | txrdy->ft_burst_len = htonl(se_cmd->data_length); | ||
264 | |||
265 | cmd->seq = lport->tt.seq_start_next(cmd->seq); | ||
266 | fc_fill_fc_hdr(fp, FC_RCTL_DD_DATA_DESC, ep->did, ep->sid, FC_TYPE_FCP, | ||
267 | FC_FC_EX_CTX | FC_FC_END_SEQ | FC_FC_SEQ_INIT, 0); | ||
268 | |||
269 | fh = fc_frame_header_get(fp); | ||
270 | f_ctl = ntoh24(fh->fh_f_ctl); | ||
271 | |||
272 | /* Only if it is 'Exchange Responder' */ | ||
273 | if (f_ctl & FC_FC_EX_CTX) { | ||
274 | /* Target is 'exchange responder' and sending XFER_READY | ||
275 | * to 'exchange initiator (initiator)' | ||
276 | */ | ||
277 | if ((ep->xid <= lport->lro_xid) && | ||
278 | (fh->fh_r_ctl == FC_RCTL_DD_DATA_DESC)) { | ||
279 | if (se_cmd->se_cmd_flags & SCF_SCSI_DATA_SG_IO_CDB) { | ||
280 | /* | ||
281 | * Map se_mem list to scatterlist, so that | ||
282 | * DDP can be setup. DDP setup function require | ||
283 | * scatterlist. se_mem_list is internal to | ||
284 | * TCM/LIO target | ||
285 | */ | ||
286 | transport_do_task_sg_chain(se_cmd); | ||
287 | cmd->sg = T_TASK(se_cmd)->t_tasks_sg_chained; | ||
288 | cmd->sg_cnt = | ||
289 | T_TASK(se_cmd)->t_tasks_sg_chained_no; | ||
290 | } | ||
291 | if (cmd->sg && lport->tt.ddp_setup(lport, ep->xid, | ||
292 | cmd->sg, cmd->sg_cnt)) | ||
293 | cmd->was_ddp_setup = 1; | ||
294 | } | ||
295 | } | ||
296 | lport->tt.seq_send(lport, cmd->seq, fp); | ||
297 | return 0; | ||
298 | } | ||
299 | |||
300 | u32 ft_get_task_tag(struct se_cmd *se_cmd) | ||
301 | { | ||
302 | struct ft_cmd *cmd = container_of(se_cmd, struct ft_cmd, se_cmd); | ||
303 | |||
304 | return fc_seq_exch(cmd->seq)->rxid; | ||
305 | } | ||
306 | |||
307 | int ft_get_cmd_state(struct se_cmd *se_cmd) | ||
308 | { | ||
309 | struct ft_cmd *cmd = container_of(se_cmd, struct ft_cmd, se_cmd); | ||
310 | |||
311 | return cmd->state; | ||
312 | } | ||
313 | |||
314 | int ft_is_state_remove(struct se_cmd *se_cmd) | ||
315 | { | ||
316 | return 0; /* XXX TBD */ | ||
317 | } | ||
318 | |||
319 | void ft_new_cmd_failure(struct se_cmd *se_cmd) | ||
320 | { | ||
321 | /* XXX TBD */ | ||
322 | printk(KERN_INFO "%s: se_cmd %p\n", __func__, se_cmd); | ||
323 | } | ||
324 | |||
325 | /* | ||
326 | * FC sequence response handler for follow-on sequences (data) and aborts. | ||
327 | */ | ||
328 | static void ft_recv_seq(struct fc_seq *sp, struct fc_frame *fp, void *arg) | ||
329 | { | ||
330 | struct ft_cmd *cmd = arg; | ||
331 | struct fc_frame_header *fh; | ||
332 | |||
333 | if (IS_ERR(fp)) { | ||
334 | /* XXX need to find cmd if queued */ | ||
335 | cmd->se_cmd.t_state = TRANSPORT_REMOVE; | ||
336 | cmd->seq = NULL; | ||
337 | transport_generic_free_cmd(&cmd->se_cmd, 0, 1, 0); | ||
338 | return; | ||
339 | } | ||
340 | |||
341 | fh = fc_frame_header_get(fp); | ||
342 | |||
343 | switch (fh->fh_r_ctl) { | ||
344 | case FC_RCTL_DD_SOL_DATA: /* write data */ | ||
345 | ft_recv_write_data(cmd, fp); | ||
346 | break; | ||
347 | case FC_RCTL_DD_UNSOL_CTL: /* command */ | ||
348 | case FC_RCTL_DD_SOL_CTL: /* transfer ready */ | ||
349 | case FC_RCTL_DD_DATA_DESC: /* transfer ready */ | ||
350 | default: | ||
351 | printk(KERN_INFO "%s: unhandled frame r_ctl %x\n", | ||
352 | __func__, fh->fh_r_ctl); | ||
353 | fc_frame_free(fp); | ||
354 | transport_generic_free_cmd(&cmd->se_cmd, 0, 1, 0); | ||
355 | break; | ||
356 | } | ||
357 | } | ||
358 | |||
359 | /* | ||
360 | * Send a FCP response including SCSI status and optional FCP rsp_code. | ||
361 | * status is SAM_STAT_GOOD (zero) iff code is valid. | ||
362 | * This is used in error cases, such as allocation failures. | ||
363 | */ | ||
364 | static void ft_send_resp_status(struct fc_lport *lport, | ||
365 | const struct fc_frame *rx_fp, | ||
366 | u32 status, enum fcp_resp_rsp_codes code) | ||
367 | { | ||
368 | struct fc_frame *fp; | ||
369 | struct fc_seq *sp; | ||
370 | const struct fc_frame_header *fh; | ||
371 | size_t len; | ||
372 | struct fcp_resp_with_ext *fcp; | ||
373 | struct fcp_resp_rsp_info *info; | ||
374 | |||
375 | fh = fc_frame_header_get(rx_fp); | ||
376 | FT_IO_DBG("FCP error response: did %x oxid %x status %x code %x\n", | ||
377 | ntoh24(fh->fh_s_id), ntohs(fh->fh_ox_id), status, code); | ||
378 | len = sizeof(*fcp); | ||
379 | if (status == SAM_STAT_GOOD) | ||
380 | len += sizeof(*info); | ||
381 | fp = fc_frame_alloc(lport, len); | ||
382 | if (!fp) | ||
383 | return; | ||
384 | fcp = fc_frame_payload_get(fp, len); | ||
385 | memset(fcp, 0, len); | ||
386 | fcp->resp.fr_status = status; | ||
387 | if (status == SAM_STAT_GOOD) { | ||
388 | fcp->ext.fr_rsp_len = htonl(sizeof(*info)); | ||
389 | fcp->resp.fr_flags |= FCP_RSP_LEN_VAL; | ||
390 | info = (struct fcp_resp_rsp_info *)(fcp + 1); | ||
391 | info->rsp_code = code; | ||
392 | } | ||
393 | |||
394 | fc_fill_reply_hdr(fp, rx_fp, FC_RCTL_DD_CMD_STATUS, 0); | ||
395 | sp = fr_seq(fp); | ||
396 | if (sp) | ||
397 | lport->tt.seq_send(lport, sp, fp); | ||
398 | else | ||
399 | lport->tt.frame_send(lport, fp); | ||
400 | } | ||
401 | |||
402 | /* | ||
403 | * Send error or task management response. | ||
404 | * Always frees the cmd and associated state. | ||
405 | */ | ||
406 | static void ft_send_resp_code(struct ft_cmd *cmd, enum fcp_resp_rsp_codes code) | ||
407 | { | ||
408 | ft_send_resp_status(cmd->sess->tport->lport, | ||
409 | cmd->req_frame, SAM_STAT_GOOD, code); | ||
410 | ft_free_cmd(cmd); | ||
411 | } | ||
412 | |||
413 | /* | ||
414 | * Handle Task Management Request. | ||
415 | */ | ||
416 | static void ft_send_tm(struct ft_cmd *cmd) | ||
417 | { | ||
418 | struct se_tmr_req *tmr; | ||
419 | struct fcp_cmnd *fcp; | ||
420 | u8 tm_func; | ||
421 | |||
422 | fcp = fc_frame_payload_get(cmd->req_frame, sizeof(*fcp)); | ||
423 | |||
424 | switch (fcp->fc_tm_flags) { | ||
425 | case FCP_TMF_LUN_RESET: | ||
426 | tm_func = TMR_LUN_RESET; | ||
427 | if (ft_get_lun_for_cmd(cmd, fcp->fc_lun) < 0) { | ||
428 | ft_dump_cmd(cmd, __func__); | ||
429 | transport_send_check_condition_and_sense(&cmd->se_cmd, | ||
430 | cmd->se_cmd.scsi_sense_reason, 0); | ||
431 | ft_sess_put(cmd->sess); | ||
432 | return; | ||
433 | } | ||
434 | break; | ||
435 | case FCP_TMF_TGT_RESET: | ||
436 | tm_func = TMR_TARGET_WARM_RESET; | ||
437 | break; | ||
438 | case FCP_TMF_CLR_TASK_SET: | ||
439 | tm_func = TMR_CLEAR_TASK_SET; | ||
440 | break; | ||
441 | case FCP_TMF_ABT_TASK_SET: | ||
442 | tm_func = TMR_ABORT_TASK_SET; | ||
443 | break; | ||
444 | case FCP_TMF_CLR_ACA: | ||
445 | tm_func = TMR_CLEAR_ACA; | ||
446 | break; | ||
447 | default: | ||
448 | /* | ||
449 | * FCP4r01 indicates having a combination of | ||
450 | * tm_flags set is invalid. | ||
451 | */ | ||
452 | FT_TM_DBG("invalid FCP tm_flags %x\n", fcp->fc_tm_flags); | ||
453 | ft_send_resp_code(cmd, FCP_CMND_FIELDS_INVALID); | ||
454 | return; | ||
455 | } | ||
456 | |||
457 | FT_TM_DBG("alloc tm cmd fn %d\n", tm_func); | ||
458 | tmr = core_tmr_alloc_req(&cmd->se_cmd, cmd, tm_func); | ||
459 | if (!tmr) { | ||
460 | FT_TM_DBG("alloc failed\n"); | ||
461 | ft_send_resp_code(cmd, FCP_TMF_FAILED); | ||
462 | return; | ||
463 | } | ||
464 | cmd->se_cmd.se_tmr_req = tmr; | ||
465 | transport_generic_handle_tmr(&cmd->se_cmd); | ||
466 | } | ||
467 | |||
468 | /* | ||
469 | * Send status from completed task management request. | ||
470 | */ | ||
471 | int ft_queue_tm_resp(struct se_cmd *se_cmd) | ||
472 | { | ||
473 | struct ft_cmd *cmd = container_of(se_cmd, struct ft_cmd, se_cmd); | ||
474 | struct se_tmr_req *tmr = se_cmd->se_tmr_req; | ||
475 | enum fcp_resp_rsp_codes code; | ||
476 | |||
477 | switch (tmr->response) { | ||
478 | case TMR_FUNCTION_COMPLETE: | ||
479 | code = FCP_TMF_CMPL; | ||
480 | break; | ||
481 | case TMR_LUN_DOES_NOT_EXIST: | ||
482 | code = FCP_TMF_INVALID_LUN; | ||
483 | break; | ||
484 | case TMR_FUNCTION_REJECTED: | ||
485 | code = FCP_TMF_REJECTED; | ||
486 | break; | ||
487 | case TMR_TASK_DOES_NOT_EXIST: | ||
488 | case TMR_TASK_STILL_ALLEGIANT: | ||
489 | case TMR_TASK_FAILOVER_NOT_SUPPORTED: | ||
490 | case TMR_TASK_MGMT_FUNCTION_NOT_SUPPORTED: | ||
491 | case TMR_FUNCTION_AUTHORIZATION_FAILED: | ||
492 | default: | ||
493 | code = FCP_TMF_FAILED; | ||
494 | break; | ||
495 | } | ||
496 | FT_TM_DBG("tmr fn %d resp %d fcp code %d\n", | ||
497 | tmr->function, tmr->response, code); | ||
498 | ft_send_resp_code(cmd, code); | ||
499 | return 0; | ||
500 | } | ||
501 | |||
502 | /* | ||
503 | * Handle incoming FCP command. | ||
504 | */ | ||
505 | static void ft_recv_cmd(struct ft_sess *sess, struct fc_frame *fp) | ||
506 | { | ||
507 | struct ft_cmd *cmd; | ||
508 | struct fc_lport *lport = sess->tport->lport; | ||
509 | |||
510 | cmd = kzalloc(sizeof(*cmd), GFP_ATOMIC); | ||
511 | if (!cmd) | ||
512 | goto busy; | ||
513 | cmd->sess = sess; | ||
514 | cmd->seq = lport->tt.seq_assign(lport, fp); | ||
515 | if (!cmd->seq) { | ||
516 | kfree(cmd); | ||
517 | goto busy; | ||
518 | } | ||
519 | cmd->req_frame = fp; /* hold frame during cmd */ | ||
520 | ft_queue_cmd(sess, cmd); | ||
521 | return; | ||
522 | |||
523 | busy: | ||
524 | FT_IO_DBG("cmd or seq allocation failure - sending BUSY\n"); | ||
525 | ft_send_resp_status(lport, fp, SAM_STAT_BUSY, 0); | ||
526 | fc_frame_free(fp); | ||
527 | ft_sess_put(sess); /* undo get from lookup */ | ||
528 | } | ||
529 | |||
530 | |||
531 | /* | ||
532 | * Handle incoming FCP frame. | ||
533 | * Caller has verified that the frame is type FCP. | ||
534 | */ | ||
535 | void ft_recv_req(struct ft_sess *sess, struct fc_frame *fp) | ||
536 | { | ||
537 | struct fc_frame_header *fh = fc_frame_header_get(fp); | ||
538 | |||
539 | switch (fh->fh_r_ctl) { | ||
540 | case FC_RCTL_DD_UNSOL_CMD: /* command */ | ||
541 | ft_recv_cmd(sess, fp); | ||
542 | break; | ||
543 | case FC_RCTL_DD_SOL_DATA: /* write data */ | ||
544 | case FC_RCTL_DD_UNSOL_CTL: | ||
545 | case FC_RCTL_DD_SOL_CTL: | ||
546 | case FC_RCTL_DD_DATA_DESC: /* transfer ready */ | ||
547 | case FC_RCTL_ELS4_REQ: /* SRR, perhaps */ | ||
548 | default: | ||
549 | printk(KERN_INFO "%s: unhandled frame r_ctl %x\n", | ||
550 | __func__, fh->fh_r_ctl); | ||
551 | fc_frame_free(fp); | ||
552 | ft_sess_put(sess); /* undo get from lookup */ | ||
553 | break; | ||
554 | } | ||
555 | } | ||
556 | |||
557 | /* | ||
558 | * Send new command to target. | ||
559 | */ | ||
560 | static void ft_send_cmd(struct ft_cmd *cmd) | ||
561 | { | ||
562 | struct fc_frame_header *fh = fc_frame_header_get(cmd->req_frame); | ||
563 | struct se_cmd *se_cmd; | ||
564 | struct fcp_cmnd *fcp; | ||
565 | int data_dir; | ||
566 | u32 data_len; | ||
567 | int task_attr; | ||
568 | int ret; | ||
569 | |||
570 | fcp = fc_frame_payload_get(cmd->req_frame, sizeof(*fcp)); | ||
571 | if (!fcp) | ||
572 | goto err; | ||
573 | |||
574 | if (fcp->fc_flags & FCP_CFL_LEN_MASK) | ||
575 | goto err; /* not handling longer CDBs yet */ | ||
576 | |||
577 | if (fcp->fc_tm_flags) { | ||
578 | task_attr = FCP_PTA_SIMPLE; | ||
579 | data_dir = DMA_NONE; | ||
580 | data_len = 0; | ||
581 | } else { | ||
582 | switch (fcp->fc_flags & (FCP_CFL_RDDATA | FCP_CFL_WRDATA)) { | ||
583 | case 0: | ||
584 | data_dir = DMA_NONE; | ||
585 | break; | ||
586 | case FCP_CFL_RDDATA: | ||
587 | data_dir = DMA_FROM_DEVICE; | ||
588 | break; | ||
589 | case FCP_CFL_WRDATA: | ||
590 | data_dir = DMA_TO_DEVICE; | ||
591 | break; | ||
592 | case FCP_CFL_WRDATA | FCP_CFL_RDDATA: | ||
593 | goto err; /* TBD not supported by tcm_fc yet */ | ||
594 | } | ||
595 | |||
596 | /* FCP_PTA_ maps 1:1 to TASK_ATTR_ */ | ||
597 | task_attr = fcp->fc_pri_ta & FCP_PTA_MASK; | ||
598 | data_len = ntohl(fcp->fc_dl); | ||
599 | cmd->cdb = fcp->fc_cdb; | ||
600 | } | ||
601 | |||
602 | se_cmd = &cmd->se_cmd; | ||
603 | /* | ||
604 | * Initialize struct se_cmd descriptor from target_core_mod | ||
605 | * infrastructure | ||
606 | */ | ||
607 | transport_init_se_cmd(se_cmd, &ft_configfs->tf_ops, cmd->sess->se_sess, | ||
608 | data_len, data_dir, task_attr, | ||
609 | &cmd->ft_sense_buffer[0]); | ||
610 | /* | ||
611 | * Check for FCP task management flags | ||
612 | */ | ||
613 | if (fcp->fc_tm_flags) { | ||
614 | ft_send_tm(cmd); | ||
615 | return; | ||
616 | } | ||
617 | |||
618 | fc_seq_exch(cmd->seq)->lp->tt.seq_set_resp(cmd->seq, ft_recv_seq, cmd); | ||
619 | |||
620 | ret = ft_get_lun_for_cmd(cmd, fcp->fc_lun); | ||
621 | if (ret < 0) { | ||
622 | ft_dump_cmd(cmd, __func__); | ||
623 | transport_send_check_condition_and_sense(&cmd->se_cmd, | ||
624 | cmd->se_cmd.scsi_sense_reason, 0); | ||
625 | return; | ||
626 | } | ||
627 | |||
628 | ret = transport_generic_allocate_tasks(se_cmd, cmd->cdb); | ||
629 | |||
630 | FT_IO_DBG("r_ctl %x alloc task ret %d\n", fh->fh_r_ctl, ret); | ||
631 | ft_dump_cmd(cmd, __func__); | ||
632 | |||
633 | if (ret == -1) { | ||
634 | transport_send_check_condition_and_sense(se_cmd, | ||
635 | TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE, 0); | ||
636 | transport_generic_free_cmd(se_cmd, 0, 1, 0); | ||
637 | return; | ||
638 | } | ||
639 | if (ret == -2) { | ||
640 | if (se_cmd->se_cmd_flags & SCF_SCSI_RESERVATION_CONFLICT) | ||
641 | ft_queue_status(se_cmd); | ||
642 | else | ||
643 | transport_send_check_condition_and_sense(se_cmd, | ||
644 | se_cmd->scsi_sense_reason, 0); | ||
645 | transport_generic_free_cmd(se_cmd, 0, 1, 0); | ||
646 | return; | ||
647 | } | ||
648 | transport_generic_handle_cdb(se_cmd); | ||
649 | return; | ||
650 | |||
651 | err: | ||
652 | ft_send_resp_code(cmd, FCP_CMND_FIELDS_INVALID); | ||
653 | return; | ||
654 | } | ||
655 | |||
656 | /* | ||
657 | * Handle request in the command thread. | ||
658 | */ | ||
659 | static void ft_exec_req(struct ft_cmd *cmd) | ||
660 | { | ||
661 | FT_IO_DBG("cmd state %x\n", cmd->state); | ||
662 | switch (cmd->state) { | ||
663 | case FC_CMD_ST_NEW: | ||
664 | ft_send_cmd(cmd); | ||
665 | break; | ||
666 | default: | ||
667 | break; | ||
668 | } | ||
669 | } | ||
670 | |||
671 | /* | ||
672 | * Processing thread. | ||
673 | * Currently one thread per tpg. | ||
674 | */ | ||
675 | int ft_thread(void *arg) | ||
676 | { | ||
677 | struct ft_tpg *tpg = arg; | ||
678 | struct se_queue_obj *qobj = &tpg->qobj; | ||
679 | struct ft_cmd *cmd; | ||
680 | int ret; | ||
681 | |||
682 | set_user_nice(current, -20); | ||
683 | |||
684 | while (!kthread_should_stop()) { | ||
685 | ret = wait_event_interruptible(qobj->thread_wq, | ||
686 | atomic_read(&qobj->queue_cnt) || kthread_should_stop()); | ||
687 | if (ret < 0 || kthread_should_stop()) | ||
688 | goto out; | ||
689 | cmd = ft_dequeue_cmd(qobj); | ||
690 | if (cmd) | ||
691 | ft_exec_req(cmd); | ||
692 | } | ||
693 | |||
694 | out: | ||
695 | return 0; | ||
696 | } | ||
diff --git a/drivers/target/tcm_fc/tfc_conf.c b/drivers/target/tcm_fc/tfc_conf.c new file mode 100644 index 000000000000..fcdbbffe88cc --- /dev/null +++ b/drivers/target/tcm_fc/tfc_conf.c | |||
@@ -0,0 +1,677 @@ | |||
1 | /******************************************************************************* | ||
2 | * Filename: tcm_fc.c | ||
3 | * | ||
4 | * This file contains the configfs implementation for TCM_fc fabric node. | ||
5 | * Based on tcm_loop_configfs.c | ||
6 | * | ||
7 | * Copyright (c) 2010 Cisco Systems, Inc. | ||
8 | * Copyright (c) 2009,2010 Rising Tide, Inc. | ||
9 | * Copyright (c) 2009,2010 Linux-iSCSI.org | ||
10 | * | ||
11 | * Copyright (c) 2009,2010 Nicholas A. Bellinger <nab@linux-iscsi.org> | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or modify | ||
14 | * it under the terms of the GNU General Public License as published by | ||
15 | * the Free Software Foundation; either version 2 of the License, or | ||
16 | * (at your option) any later version. | ||
17 | * | ||
18 | * This program is distributed in the hope that it will be useful, | ||
19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
21 | * GNU General Public License for more details. | ||
22 | ****************************************************************************/ | ||
23 | |||
24 | #include <linux/module.h> | ||
25 | #include <linux/moduleparam.h> | ||
26 | #include <linux/version.h> | ||
27 | #include <generated/utsrelease.h> | ||
28 | #include <linux/utsname.h> | ||
29 | #include <linux/init.h> | ||
30 | #include <linux/slab.h> | ||
31 | #include <linux/kthread.h> | ||
32 | #include <linux/types.h> | ||
33 | #include <linux/string.h> | ||
34 | #include <linux/configfs.h> | ||
35 | #include <linux/ctype.h> | ||
36 | #include <asm/unaligned.h> | ||
37 | #include <scsi/scsi.h> | ||
38 | #include <scsi/scsi_host.h> | ||
39 | #include <scsi/scsi_device.h> | ||
40 | #include <scsi/scsi_cmnd.h> | ||
41 | #include <scsi/libfc.h> | ||
42 | |||
43 | #include <target/target_core_base.h> | ||
44 | #include <target/target_core_transport.h> | ||
45 | #include <target/target_core_fabric_ops.h> | ||
46 | #include <target/target_core_fabric_configfs.h> | ||
47 | #include <target/target_core_fabric_lib.h> | ||
48 | #include <target/target_core_device.h> | ||
49 | #include <target/target_core_tpg.h> | ||
50 | #include <target/target_core_configfs.h> | ||
51 | #include <target/target_core_base.h> | ||
52 | #include <target/configfs_macros.h> | ||
53 | |||
54 | #include "tcm_fc.h" | ||
55 | |||
56 | struct target_fabric_configfs *ft_configfs; | ||
57 | |||
58 | LIST_HEAD(ft_lport_list); | ||
59 | DEFINE_MUTEX(ft_lport_lock); | ||
60 | |||
61 | unsigned int ft_debug_logging; | ||
62 | module_param_named(debug_logging, ft_debug_logging, int, S_IRUGO|S_IWUSR); | ||
63 | MODULE_PARM_DESC(debug_logging, "a bit mask of logging levels"); | ||
64 | |||
65 | /* | ||
66 | * Parse WWN. | ||
67 | * If strict, we require lower-case hex and colon separators to be sure | ||
68 | * the name is the same as what would be generated by ft_format_wwn() | ||
69 | * so the name and wwn are mapped one-to-one. | ||
70 | */ | ||
71 | static ssize_t ft_parse_wwn(const char *name, u64 *wwn, int strict) | ||
72 | { | ||
73 | const char *cp; | ||
74 | char c; | ||
75 | u32 nibble; | ||
76 | u32 byte = 0; | ||
77 | u32 pos = 0; | ||
78 | u32 err; | ||
79 | |||
80 | *wwn = 0; | ||
81 | for (cp = name; cp < &name[FT_NAMELEN - 1]; cp++) { | ||
82 | c = *cp; | ||
83 | if (c == '\n' && cp[1] == '\0') | ||
84 | continue; | ||
85 | if (strict && pos++ == 2 && byte++ < 7) { | ||
86 | pos = 0; | ||
87 | if (c == ':') | ||
88 | continue; | ||
89 | err = 1; | ||
90 | goto fail; | ||
91 | } | ||
92 | if (c == '\0') { | ||
93 | err = 2; | ||
94 | if (strict && byte != 8) | ||
95 | goto fail; | ||
96 | return cp - name; | ||
97 | } | ||
98 | err = 3; | ||
99 | if (isdigit(c)) | ||
100 | nibble = c - '0'; | ||
101 | else if (isxdigit(c) && (islower(c) || !strict)) | ||
102 | nibble = tolower(c) - 'a' + 10; | ||
103 | else | ||
104 | goto fail; | ||
105 | *wwn = (*wwn << 4) | nibble; | ||
106 | } | ||
107 | err = 4; | ||
108 | fail: | ||
109 | FT_CONF_DBG("err %u len %zu pos %u byte %u\n", | ||
110 | err, cp - name, pos, byte); | ||
111 | return -1; | ||
112 | } | ||
113 | |||
114 | ssize_t ft_format_wwn(char *buf, size_t len, u64 wwn) | ||
115 | { | ||
116 | u8 b[8]; | ||
117 | |||
118 | put_unaligned_be64(wwn, b); | ||
119 | return snprintf(buf, len, | ||
120 | "%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x", | ||
121 | b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]); | ||
122 | } | ||
123 | |||
124 | static ssize_t ft_wwn_show(void *arg, char *buf) | ||
125 | { | ||
126 | u64 *wwn = arg; | ||
127 | ssize_t len; | ||
128 | |||
129 | len = ft_format_wwn(buf, PAGE_SIZE - 2, *wwn); | ||
130 | buf[len++] = '\n'; | ||
131 | return len; | ||
132 | } | ||
133 | |||
134 | static ssize_t ft_wwn_store(void *arg, const char *buf, size_t len) | ||
135 | { | ||
136 | ssize_t ret; | ||
137 | u64 wwn; | ||
138 | |||
139 | ret = ft_parse_wwn(buf, &wwn, 0); | ||
140 | if (ret > 0) | ||
141 | *(u64 *)arg = wwn; | ||
142 | return ret; | ||
143 | } | ||
144 | |||
145 | /* | ||
146 | * ACL auth ops. | ||
147 | */ | ||
148 | |||
149 | static ssize_t ft_nacl_show_port_name( | ||
150 | struct se_node_acl *se_nacl, | ||
151 | char *page) | ||
152 | { | ||
153 | struct ft_node_acl *acl = container_of(se_nacl, | ||
154 | struct ft_node_acl, se_node_acl); | ||
155 | |||
156 | return ft_wwn_show(&acl->node_auth.port_name, page); | ||
157 | } | ||
158 | |||
159 | static ssize_t ft_nacl_store_port_name( | ||
160 | struct se_node_acl *se_nacl, | ||
161 | const char *page, | ||
162 | size_t count) | ||
163 | { | ||
164 | struct ft_node_acl *acl = container_of(se_nacl, | ||
165 | struct ft_node_acl, se_node_acl); | ||
166 | |||
167 | return ft_wwn_store(&acl->node_auth.port_name, page, count); | ||
168 | } | ||
169 | |||
170 | TF_NACL_BASE_ATTR(ft, port_name, S_IRUGO | S_IWUSR); | ||
171 | |||
172 | static ssize_t ft_nacl_show_node_name( | ||
173 | struct se_node_acl *se_nacl, | ||
174 | char *page) | ||
175 | { | ||
176 | struct ft_node_acl *acl = container_of(se_nacl, | ||
177 | struct ft_node_acl, se_node_acl); | ||
178 | |||
179 | return ft_wwn_show(&acl->node_auth.node_name, page); | ||
180 | } | ||
181 | |||
182 | static ssize_t ft_nacl_store_node_name( | ||
183 | struct se_node_acl *se_nacl, | ||
184 | const char *page, | ||
185 | size_t count) | ||
186 | { | ||
187 | struct ft_node_acl *acl = container_of(se_nacl, | ||
188 | struct ft_node_acl, se_node_acl); | ||
189 | |||
190 | return ft_wwn_store(&acl->node_auth.node_name, page, count); | ||
191 | } | ||
192 | |||
193 | TF_NACL_BASE_ATTR(ft, node_name, S_IRUGO | S_IWUSR); | ||
194 | |||
195 | static struct configfs_attribute *ft_nacl_base_attrs[] = { | ||
196 | &ft_nacl_port_name.attr, | ||
197 | &ft_nacl_node_name.attr, | ||
198 | NULL, | ||
199 | }; | ||
200 | |||
201 | /* | ||
202 | * ACL ops. | ||
203 | */ | ||
204 | |||
205 | /* | ||
206 | * Add ACL for an initiator. The ACL is named arbitrarily. | ||
207 | * The port_name and/or node_name are attributes. | ||
208 | */ | ||
209 | static struct se_node_acl *ft_add_acl( | ||
210 | struct se_portal_group *se_tpg, | ||
211 | struct config_group *group, | ||
212 | const char *name) | ||
213 | { | ||
214 | struct ft_node_acl *acl; | ||
215 | struct ft_tpg *tpg; | ||
216 | u64 wwpn; | ||
217 | u32 q_depth; | ||
218 | |||
219 | FT_CONF_DBG("add acl %s\n", name); | ||
220 | tpg = container_of(se_tpg, struct ft_tpg, se_tpg); | ||
221 | |||
222 | if (ft_parse_wwn(name, &wwpn, 1) < 0) | ||
223 | return ERR_PTR(-EINVAL); | ||
224 | |||
225 | acl = kzalloc(sizeof(struct ft_node_acl), GFP_KERNEL); | ||
226 | if (!(acl)) | ||
227 | return ERR_PTR(-ENOMEM); | ||
228 | acl->node_auth.port_name = wwpn; | ||
229 | |||
230 | q_depth = 32; /* XXX bogus default - get from tpg? */ | ||
231 | return core_tpg_add_initiator_node_acl(&tpg->se_tpg, | ||
232 | &acl->se_node_acl, name, q_depth); | ||
233 | } | ||
234 | |||
235 | static void ft_del_acl(struct se_node_acl *se_acl) | ||
236 | { | ||
237 | struct se_portal_group *se_tpg = se_acl->se_tpg; | ||
238 | struct ft_tpg *tpg; | ||
239 | struct ft_node_acl *acl = container_of(se_acl, | ||
240 | struct ft_node_acl, se_node_acl); | ||
241 | |||
242 | FT_CONF_DBG("del acl %s\n", | ||
243 | config_item_name(&se_acl->acl_group.cg_item)); | ||
244 | |||
245 | tpg = container_of(se_tpg, struct ft_tpg, se_tpg); | ||
246 | FT_CONF_DBG("del acl %p se_acl %p tpg %p se_tpg %p\n", | ||
247 | acl, se_acl, tpg, &tpg->se_tpg); | ||
248 | |||
249 | core_tpg_del_initiator_node_acl(&tpg->se_tpg, se_acl, 1); | ||
250 | kfree(acl); | ||
251 | } | ||
252 | |||
253 | struct ft_node_acl *ft_acl_get(struct ft_tpg *tpg, struct fc_rport_priv *rdata) | ||
254 | { | ||
255 | struct ft_node_acl *found = NULL; | ||
256 | struct ft_node_acl *acl; | ||
257 | struct se_portal_group *se_tpg = &tpg->se_tpg; | ||
258 | struct se_node_acl *se_acl; | ||
259 | |||
260 | spin_lock_bh(&se_tpg->acl_node_lock); | ||
261 | list_for_each_entry(se_acl, &se_tpg->acl_node_list, acl_list) { | ||
262 | acl = container_of(se_acl, struct ft_node_acl, se_node_acl); | ||
263 | FT_CONF_DBG("acl %p port_name %llx\n", | ||
264 | acl, (unsigned long long)acl->node_auth.port_name); | ||
265 | if (acl->node_auth.port_name == rdata->ids.port_name || | ||
266 | acl->node_auth.node_name == rdata->ids.node_name) { | ||
267 | FT_CONF_DBG("acl %p port_name %llx matched\n", acl, | ||
268 | (unsigned long long)rdata->ids.port_name); | ||
269 | found = acl; | ||
270 | /* XXX need to hold onto ACL */ | ||
271 | break; | ||
272 | } | ||
273 | } | ||
274 | spin_unlock_bh(&se_tpg->acl_node_lock); | ||
275 | return found; | ||
276 | } | ||
277 | |||
278 | struct se_node_acl *ft_tpg_alloc_fabric_acl(struct se_portal_group *se_tpg) | ||
279 | { | ||
280 | struct ft_node_acl *acl; | ||
281 | |||
282 | acl = kzalloc(sizeof(*acl), GFP_KERNEL); | ||
283 | if (!(acl)) { | ||
284 | printk(KERN_ERR "Unable to allocate struct ft_node_acl\n"); | ||
285 | return NULL; | ||
286 | } | ||
287 | FT_CONF_DBG("acl %p\n", acl); | ||
288 | return &acl->se_node_acl; | ||
289 | } | ||
290 | |||
291 | static void ft_tpg_release_fabric_acl(struct se_portal_group *se_tpg, | ||
292 | struct se_node_acl *se_acl) | ||
293 | { | ||
294 | struct ft_node_acl *acl = container_of(se_acl, | ||
295 | struct ft_node_acl, se_node_acl); | ||
296 | |||
297 | FT_CONF_DBG(KERN_INFO "acl %p\n", acl); | ||
298 | kfree(acl); | ||
299 | } | ||
300 | |||
301 | /* | ||
302 | * local_port port_group (tpg) ops. | ||
303 | */ | ||
304 | static struct se_portal_group *ft_add_tpg( | ||
305 | struct se_wwn *wwn, | ||
306 | struct config_group *group, | ||
307 | const char *name) | ||
308 | { | ||
309 | struct ft_lport_acl *lacl; | ||
310 | struct ft_tpg *tpg; | ||
311 | unsigned long index; | ||
312 | int ret; | ||
313 | |||
314 | FT_CONF_DBG("tcm_fc: add tpg %s\n", name); | ||
315 | |||
316 | /* | ||
317 | * Name must be "tpgt_" followed by the index. | ||
318 | */ | ||
319 | if (strstr(name, "tpgt_") != name) | ||
320 | return NULL; | ||
321 | if (strict_strtoul(name + 5, 10, &index) || index > UINT_MAX) | ||
322 | return NULL; | ||
323 | |||
324 | lacl = container_of(wwn, struct ft_lport_acl, fc_lport_wwn); | ||
325 | tpg = kzalloc(sizeof(*tpg), GFP_KERNEL); | ||
326 | if (!tpg) | ||
327 | return NULL; | ||
328 | tpg->index = index; | ||
329 | tpg->lport_acl = lacl; | ||
330 | INIT_LIST_HEAD(&tpg->lun_list); | ||
331 | transport_init_queue_obj(&tpg->qobj); | ||
332 | |||
333 | ret = core_tpg_register(&ft_configfs->tf_ops, wwn, &tpg->se_tpg, | ||
334 | (void *)tpg, TRANSPORT_TPG_TYPE_NORMAL); | ||
335 | if (ret < 0) { | ||
336 | kfree(tpg); | ||
337 | return NULL; | ||
338 | } | ||
339 | |||
340 | tpg->thread = kthread_run(ft_thread, tpg, "ft_tpg%lu", index); | ||
341 | if (IS_ERR(tpg->thread)) { | ||
342 | kfree(tpg); | ||
343 | return NULL; | ||
344 | } | ||
345 | |||
346 | mutex_lock(&ft_lport_lock); | ||
347 | list_add_tail(&tpg->list, &lacl->tpg_list); | ||
348 | mutex_unlock(&ft_lport_lock); | ||
349 | |||
350 | return &tpg->se_tpg; | ||
351 | } | ||
352 | |||
353 | static void ft_del_tpg(struct se_portal_group *se_tpg) | ||
354 | { | ||
355 | struct ft_tpg *tpg = container_of(se_tpg, struct ft_tpg, se_tpg); | ||
356 | |||
357 | FT_CONF_DBG("del tpg %s\n", | ||
358 | config_item_name(&tpg->se_tpg.tpg_group.cg_item)); | ||
359 | |||
360 | kthread_stop(tpg->thread); | ||
361 | |||
362 | /* Wait for sessions to be freed thru RCU, for BUG_ON below */ | ||
363 | synchronize_rcu(); | ||
364 | |||
365 | mutex_lock(&ft_lport_lock); | ||
366 | list_del(&tpg->list); | ||
367 | if (tpg->tport) { | ||
368 | tpg->tport->tpg = NULL; | ||
369 | tpg->tport = NULL; | ||
370 | } | ||
371 | mutex_unlock(&ft_lport_lock); | ||
372 | |||
373 | core_tpg_deregister(se_tpg); | ||
374 | kfree(tpg); | ||
375 | } | ||
376 | |||
377 | /* | ||
378 | * Verify that an lport is configured to use the tcm_fc module, and return | ||
379 | * the target port group that should be used. | ||
380 | * | ||
381 | * The caller holds ft_lport_lock. | ||
382 | */ | ||
383 | struct ft_tpg *ft_lport_find_tpg(struct fc_lport *lport) | ||
384 | { | ||
385 | struct ft_lport_acl *lacl; | ||
386 | struct ft_tpg *tpg; | ||
387 | |||
388 | list_for_each_entry(lacl, &ft_lport_list, list) { | ||
389 | if (lacl->wwpn == lport->wwpn) { | ||
390 | list_for_each_entry(tpg, &lacl->tpg_list, list) | ||
391 | return tpg; /* XXX for now return first entry */ | ||
392 | return NULL; | ||
393 | } | ||
394 | } | ||
395 | return NULL; | ||
396 | } | ||
397 | |||
398 | /* | ||
399 | * target config instance ops. | ||
400 | */ | ||
401 | |||
402 | /* | ||
403 | * Add lport to allowed config. | ||
404 | * The name is the WWPN in lower-case ASCII, colon-separated bytes. | ||
405 | */ | ||
406 | static struct se_wwn *ft_add_lport( | ||
407 | struct target_fabric_configfs *tf, | ||
408 | struct config_group *group, | ||
409 | const char *name) | ||
410 | { | ||
411 | struct ft_lport_acl *lacl; | ||
412 | struct ft_lport_acl *old_lacl; | ||
413 | u64 wwpn; | ||
414 | |||
415 | FT_CONF_DBG("add lport %s\n", name); | ||
416 | if (ft_parse_wwn(name, &wwpn, 1) < 0) | ||
417 | return NULL; | ||
418 | lacl = kzalloc(sizeof(*lacl), GFP_KERNEL); | ||
419 | if (!lacl) | ||
420 | return NULL; | ||
421 | lacl->wwpn = wwpn; | ||
422 | INIT_LIST_HEAD(&lacl->tpg_list); | ||
423 | |||
424 | mutex_lock(&ft_lport_lock); | ||
425 | list_for_each_entry(old_lacl, &ft_lport_list, list) { | ||
426 | if (old_lacl->wwpn == wwpn) { | ||
427 | mutex_unlock(&ft_lport_lock); | ||
428 | kfree(lacl); | ||
429 | return NULL; | ||
430 | } | ||
431 | } | ||
432 | list_add_tail(&lacl->list, &ft_lport_list); | ||
433 | ft_format_wwn(lacl->name, sizeof(lacl->name), wwpn); | ||
434 | mutex_unlock(&ft_lport_lock); | ||
435 | |||
436 | return &lacl->fc_lport_wwn; | ||
437 | } | ||
438 | |||
439 | static void ft_del_lport(struct se_wwn *wwn) | ||
440 | { | ||
441 | struct ft_lport_acl *lacl = container_of(wwn, | ||
442 | struct ft_lport_acl, fc_lport_wwn); | ||
443 | |||
444 | FT_CONF_DBG("del lport %s\n", | ||
445 | config_item_name(&wwn->wwn_group.cg_item)); | ||
446 | mutex_lock(&ft_lport_lock); | ||
447 | list_del(&lacl->list); | ||
448 | mutex_unlock(&ft_lport_lock); | ||
449 | |||
450 | kfree(lacl); | ||
451 | } | ||
452 | |||
453 | static ssize_t ft_wwn_show_attr_version( | ||
454 | struct target_fabric_configfs *tf, | ||
455 | char *page) | ||
456 | { | ||
457 | return sprintf(page, "TCM FC " FT_VERSION " on %s/%s on " | ||
458 | ""UTS_RELEASE"\n", utsname()->sysname, utsname()->machine); | ||
459 | } | ||
460 | |||
461 | TF_WWN_ATTR_RO(ft, version); | ||
462 | |||
463 | static struct configfs_attribute *ft_wwn_attrs[] = { | ||
464 | &ft_wwn_version.attr, | ||
465 | NULL, | ||
466 | }; | ||
467 | |||
468 | static char *ft_get_fabric_name(void) | ||
469 | { | ||
470 | return "fc"; | ||
471 | } | ||
472 | |||
473 | static char *ft_get_fabric_wwn(struct se_portal_group *se_tpg) | ||
474 | { | ||
475 | struct ft_tpg *tpg = se_tpg->se_tpg_fabric_ptr; | ||
476 | |||
477 | return tpg->lport_acl->name; | ||
478 | } | ||
479 | |||
480 | static u16 ft_get_tag(struct se_portal_group *se_tpg) | ||
481 | { | ||
482 | struct ft_tpg *tpg = se_tpg->se_tpg_fabric_ptr; | ||
483 | |||
484 | /* | ||
485 | * This tag is used when forming SCSI Name identifier in EVPD=1 0x83 | ||
486 | * to represent the SCSI Target Port. | ||
487 | */ | ||
488 | return tpg->index; | ||
489 | } | ||
490 | |||
491 | static u32 ft_get_default_depth(struct se_portal_group *se_tpg) | ||
492 | { | ||
493 | return 1; | ||
494 | } | ||
495 | |||
496 | static int ft_check_false(struct se_portal_group *se_tpg) | ||
497 | { | ||
498 | return 0; | ||
499 | } | ||
500 | |||
501 | static void ft_set_default_node_attr(struct se_node_acl *se_nacl) | ||
502 | { | ||
503 | } | ||
504 | |||
505 | static u16 ft_get_fabric_sense_len(void) | ||
506 | { | ||
507 | return 0; | ||
508 | } | ||
509 | |||
510 | static u16 ft_set_fabric_sense_len(struct se_cmd *se_cmd, u32 sense_len) | ||
511 | { | ||
512 | return 0; | ||
513 | } | ||
514 | |||
515 | static u32 ft_tpg_get_inst_index(struct se_portal_group *se_tpg) | ||
516 | { | ||
517 | struct ft_tpg *tpg = se_tpg->se_tpg_fabric_ptr; | ||
518 | |||
519 | return tpg->index; | ||
520 | } | ||
521 | |||
522 | static u64 ft_pack_lun(unsigned int index) | ||
523 | { | ||
524 | WARN_ON(index >= 256); | ||
525 | /* Caller wants this byte-swapped */ | ||
526 | return cpu_to_le64((index & 0xff) << 8); | ||
527 | } | ||
528 | |||
529 | static struct target_core_fabric_ops ft_fabric_ops = { | ||
530 | .get_fabric_name = ft_get_fabric_name, | ||
531 | .get_fabric_proto_ident = fc_get_fabric_proto_ident, | ||
532 | .tpg_get_wwn = ft_get_fabric_wwn, | ||
533 | .tpg_get_tag = ft_get_tag, | ||
534 | .tpg_get_default_depth = ft_get_default_depth, | ||
535 | .tpg_get_pr_transport_id = fc_get_pr_transport_id, | ||
536 | .tpg_get_pr_transport_id_len = fc_get_pr_transport_id_len, | ||
537 | .tpg_parse_pr_out_transport_id = fc_parse_pr_out_transport_id, | ||
538 | .tpg_check_demo_mode = ft_check_false, | ||
539 | .tpg_check_demo_mode_cache = ft_check_false, | ||
540 | .tpg_check_demo_mode_write_protect = ft_check_false, | ||
541 | .tpg_check_prod_mode_write_protect = ft_check_false, | ||
542 | .tpg_alloc_fabric_acl = ft_tpg_alloc_fabric_acl, | ||
543 | .tpg_release_fabric_acl = ft_tpg_release_fabric_acl, | ||
544 | .tpg_get_inst_index = ft_tpg_get_inst_index, | ||
545 | .check_stop_free = ft_check_stop_free, | ||
546 | .release_cmd_to_pool = ft_release_cmd, | ||
547 | .release_cmd_direct = ft_release_cmd, | ||
548 | .shutdown_session = ft_sess_shutdown, | ||
549 | .close_session = ft_sess_close, | ||
550 | .stop_session = ft_sess_stop, | ||
551 | .fall_back_to_erl0 = ft_sess_set_erl0, | ||
552 | .sess_logged_in = ft_sess_logged_in, | ||
553 | .sess_get_index = ft_sess_get_index, | ||
554 | .sess_get_initiator_sid = NULL, | ||
555 | .write_pending = ft_write_pending, | ||
556 | .write_pending_status = ft_write_pending_status, | ||
557 | .set_default_node_attributes = ft_set_default_node_attr, | ||
558 | .get_task_tag = ft_get_task_tag, | ||
559 | .get_cmd_state = ft_get_cmd_state, | ||
560 | .new_cmd_failure = ft_new_cmd_failure, | ||
561 | .queue_data_in = ft_queue_data_in, | ||
562 | .queue_status = ft_queue_status, | ||
563 | .queue_tm_rsp = ft_queue_tm_resp, | ||
564 | .get_fabric_sense_len = ft_get_fabric_sense_len, | ||
565 | .set_fabric_sense_len = ft_set_fabric_sense_len, | ||
566 | .is_state_remove = ft_is_state_remove, | ||
567 | .pack_lun = ft_pack_lun, | ||
568 | /* | ||
569 | * Setup function pointers for generic logic in | ||
570 | * target_core_fabric_configfs.c | ||
571 | */ | ||
572 | .fabric_make_wwn = &ft_add_lport, | ||
573 | .fabric_drop_wwn = &ft_del_lport, | ||
574 | .fabric_make_tpg = &ft_add_tpg, | ||
575 | .fabric_drop_tpg = &ft_del_tpg, | ||
576 | .fabric_post_link = NULL, | ||
577 | .fabric_pre_unlink = NULL, | ||
578 | .fabric_make_np = NULL, | ||
579 | .fabric_drop_np = NULL, | ||
580 | .fabric_make_nodeacl = &ft_add_acl, | ||
581 | .fabric_drop_nodeacl = &ft_del_acl, | ||
582 | }; | ||
583 | |||
584 | int ft_register_configfs(void) | ||
585 | { | ||
586 | struct target_fabric_configfs *fabric; | ||
587 | int ret; | ||
588 | |||
589 | /* | ||
590 | * Register the top level struct config_item_type with TCM core | ||
591 | */ | ||
592 | fabric = target_fabric_configfs_init(THIS_MODULE, "fc"); | ||
593 | if (!fabric) { | ||
594 | printk(KERN_INFO "%s: target_fabric_configfs_init() failed!\n", | ||
595 | __func__); | ||
596 | return -1; | ||
597 | } | ||
598 | fabric->tf_ops = ft_fabric_ops; | ||
599 | |||
600 | /* Allowing support for task_sg_chaining */ | ||
601 | fabric->tf_ops.task_sg_chaining = 1; | ||
602 | |||
603 | /* | ||
604 | * Setup default attribute lists for various fabric->tf_cit_tmpl | ||
605 | */ | ||
606 | TF_CIT_TMPL(fabric)->tfc_wwn_cit.ct_attrs = ft_wwn_attrs; | ||
607 | TF_CIT_TMPL(fabric)->tfc_tpg_base_cit.ct_attrs = NULL; | ||
608 | TF_CIT_TMPL(fabric)->tfc_tpg_attrib_cit.ct_attrs = NULL; | ||
609 | TF_CIT_TMPL(fabric)->tfc_tpg_param_cit.ct_attrs = NULL; | ||
610 | TF_CIT_TMPL(fabric)->tfc_tpg_np_base_cit.ct_attrs = NULL; | ||
611 | TF_CIT_TMPL(fabric)->tfc_tpg_nacl_base_cit.ct_attrs = | ||
612 | ft_nacl_base_attrs; | ||
613 | TF_CIT_TMPL(fabric)->tfc_tpg_nacl_attrib_cit.ct_attrs = NULL; | ||
614 | TF_CIT_TMPL(fabric)->tfc_tpg_nacl_auth_cit.ct_attrs = NULL; | ||
615 | TF_CIT_TMPL(fabric)->tfc_tpg_nacl_param_cit.ct_attrs = NULL; | ||
616 | /* | ||
617 | * register the fabric for use within TCM | ||
618 | */ | ||
619 | ret = target_fabric_configfs_register(fabric); | ||
620 | if (ret < 0) { | ||
621 | FT_CONF_DBG("target_fabric_configfs_register() for" | ||
622 | " FC Target failed!\n"); | ||
623 | printk(KERN_INFO | ||
624 | "%s: target_fabric_configfs_register() failed!\n", | ||
625 | __func__); | ||
626 | target_fabric_configfs_free(fabric); | ||
627 | return -1; | ||
628 | } | ||
629 | |||
630 | /* | ||
631 | * Setup our local pointer to *fabric. | ||
632 | */ | ||
633 | ft_configfs = fabric; | ||
634 | return 0; | ||
635 | } | ||
636 | |||
637 | void ft_deregister_configfs(void) | ||
638 | { | ||
639 | if (!ft_configfs) | ||
640 | return; | ||
641 | target_fabric_configfs_deregister(ft_configfs); | ||
642 | ft_configfs = NULL; | ||
643 | } | ||
644 | |||
645 | static struct notifier_block ft_notifier = { | ||
646 | .notifier_call = ft_lport_notify | ||
647 | }; | ||
648 | |||
649 | static int __init ft_init(void) | ||
650 | { | ||
651 | if (ft_register_configfs()) | ||
652 | return -1; | ||
653 | if (fc_fc4_register_provider(FC_TYPE_FCP, &ft_prov)) { | ||
654 | ft_deregister_configfs(); | ||
655 | return -1; | ||
656 | } | ||
657 | blocking_notifier_chain_register(&fc_lport_notifier_head, &ft_notifier); | ||
658 | fc_lport_iterate(ft_lport_add, NULL); | ||
659 | return 0; | ||
660 | } | ||
661 | |||
662 | static void __exit ft_exit(void) | ||
663 | { | ||
664 | blocking_notifier_chain_unregister(&fc_lport_notifier_head, | ||
665 | &ft_notifier); | ||
666 | fc_fc4_deregister_provider(FC_TYPE_FCP, &ft_prov); | ||
667 | fc_lport_iterate(ft_lport_del, NULL); | ||
668 | ft_deregister_configfs(); | ||
669 | synchronize_rcu(); | ||
670 | } | ||
671 | |||
672 | #ifdef MODULE | ||
673 | MODULE_DESCRIPTION("FC TCM fabric driver " FT_VERSION); | ||
674 | MODULE_LICENSE("GPL"); | ||
675 | module_init(ft_init); | ||
676 | module_exit(ft_exit); | ||
677 | #endif /* MODULE */ | ||
diff --git a/drivers/target/tcm_fc/tfc_io.c b/drivers/target/tcm_fc/tfc_io.c new file mode 100644 index 000000000000..4c3c0efbe13f --- /dev/null +++ b/drivers/target/tcm_fc/tfc_io.c | |||
@@ -0,0 +1,374 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2010 Cisco Systems, Inc. | ||
3 | * | ||
4 | * Portions based on tcm_loop_fabric_scsi.c and libfc/fc_fcp.c | ||
5 | * | ||
6 | * Copyright (c) 2007 Intel Corporation. All rights reserved. | ||
7 | * Copyright (c) 2008 Red Hat, Inc. All rights reserved. | ||
8 | * Copyright (c) 2008 Mike Christie | ||
9 | * Copyright (c) 2009 Rising Tide, Inc. | ||
10 | * Copyright (c) 2009 Linux-iSCSI.org | ||
11 | * Copyright (c) 2009 Nicholas A. Bellinger <nab@linux-iscsi.org> | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or modify it | ||
14 | * under the terms and conditions of the GNU General Public License, | ||
15 | * version 2, as published by the Free Software Foundation. | ||
16 | * | ||
17 | * This program is distributed in the hope it will be useful, but WITHOUT | ||
18 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
19 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
20 | * more details. | ||
21 | * | ||
22 | * You should have received a copy of the GNU General Public License along with | ||
23 | * this program; if not, write to the Free Software Foundation, Inc., | ||
24 | * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. | ||
25 | */ | ||
26 | |||
27 | /* XXX TBD some includes may be extraneous */ | ||
28 | |||
29 | #include <linux/module.h> | ||
30 | #include <linux/moduleparam.h> | ||
31 | #include <linux/version.h> | ||
32 | #include <generated/utsrelease.h> | ||
33 | #include <linux/utsname.h> | ||
34 | #include <linux/init.h> | ||
35 | #include <linux/slab.h> | ||
36 | #include <linux/kthread.h> | ||
37 | #include <linux/types.h> | ||
38 | #include <linux/string.h> | ||
39 | #include <linux/configfs.h> | ||
40 | #include <linux/ctype.h> | ||
41 | #include <linux/hash.h> | ||
42 | #include <asm/unaligned.h> | ||
43 | #include <scsi/scsi.h> | ||
44 | #include <scsi/scsi_host.h> | ||
45 | #include <scsi/scsi_device.h> | ||
46 | #include <scsi/scsi_cmnd.h> | ||
47 | #include <scsi/libfc.h> | ||
48 | #include <scsi/fc_encode.h> | ||
49 | |||
50 | #include <target/target_core_base.h> | ||
51 | #include <target/target_core_transport.h> | ||
52 | #include <target/target_core_fabric_ops.h> | ||
53 | #include <target/target_core_device.h> | ||
54 | #include <target/target_core_tpg.h> | ||
55 | #include <target/target_core_configfs.h> | ||
56 | #include <target/target_core_base.h> | ||
57 | #include <target/configfs_macros.h> | ||
58 | |||
59 | #include "tcm_fc.h" | ||
60 | |||
61 | /* | ||
62 | * Deliver read data back to initiator. | ||
63 | * XXX TBD handle resource problems later. | ||
64 | */ | ||
65 | int ft_queue_data_in(struct se_cmd *se_cmd) | ||
66 | { | ||
67 | struct ft_cmd *cmd = container_of(se_cmd, struct ft_cmd, se_cmd); | ||
68 | struct se_transport_task *task; | ||
69 | struct fc_frame *fp = NULL; | ||
70 | struct fc_exch *ep; | ||
71 | struct fc_lport *lport; | ||
72 | struct se_mem *mem; | ||
73 | size_t remaining; | ||
74 | u32 f_ctl = FC_FC_EX_CTX | FC_FC_REL_OFF; | ||
75 | u32 mem_off; | ||
76 | u32 fh_off = 0; | ||
77 | u32 frame_off = 0; | ||
78 | size_t frame_len = 0; | ||
79 | size_t mem_len; | ||
80 | size_t tlen; | ||
81 | size_t off_in_page; | ||
82 | struct page *page; | ||
83 | int use_sg; | ||
84 | int error; | ||
85 | void *page_addr; | ||
86 | void *from; | ||
87 | void *to = NULL; | ||
88 | |||
89 | ep = fc_seq_exch(cmd->seq); | ||
90 | lport = ep->lp; | ||
91 | cmd->seq = lport->tt.seq_start_next(cmd->seq); | ||
92 | |||
93 | task = T_TASK(se_cmd); | ||
94 | BUG_ON(!task); | ||
95 | remaining = se_cmd->data_length; | ||
96 | |||
97 | /* | ||
98 | * Setup to use first mem list entry if any. | ||
99 | */ | ||
100 | if (task->t_tasks_se_num) { | ||
101 | mem = list_first_entry(task->t_mem_list, | ||
102 | struct se_mem, se_list); | ||
103 | mem_len = mem->se_len; | ||
104 | mem_off = mem->se_off; | ||
105 | page = mem->se_page; | ||
106 | } else { | ||
107 | mem = NULL; | ||
108 | mem_len = remaining; | ||
109 | mem_off = 0; | ||
110 | page = NULL; | ||
111 | } | ||
112 | |||
113 | /* no scatter/gather in skb for odd word length due to fc_seq_send() */ | ||
114 | use_sg = !(remaining % 4); | ||
115 | |||
116 | while (remaining) { | ||
117 | if (!mem_len) { | ||
118 | BUG_ON(!mem); | ||
119 | mem = list_entry(mem->se_list.next, | ||
120 | struct se_mem, se_list); | ||
121 | mem_len = min((size_t)mem->se_len, remaining); | ||
122 | mem_off = mem->se_off; | ||
123 | page = mem->se_page; | ||
124 | } | ||
125 | if (!frame_len) { | ||
126 | /* | ||
127 | * If lport's has capability of Large Send Offload LSO) | ||
128 | * , then allow 'frame_len' to be as big as 'lso_max' | ||
129 | * if indicated transfer length is >= lport->lso_max | ||
130 | */ | ||
131 | frame_len = (lport->seq_offload) ? lport->lso_max : | ||
132 | cmd->sess->max_frame; | ||
133 | frame_len = min(frame_len, remaining); | ||
134 | fp = fc_frame_alloc(lport, use_sg ? 0 : frame_len); | ||
135 | if (!fp) | ||
136 | return -ENOMEM; | ||
137 | to = fc_frame_payload_get(fp, 0); | ||
138 | fh_off = frame_off; | ||
139 | frame_off += frame_len; | ||
140 | /* | ||
141 | * Setup the frame's max payload which is used by base | ||
142 | * driver to indicate HW about max frame size, so that | ||
143 | * HW can do fragmentation appropriately based on | ||
144 | * "gso_max_size" of underline netdev. | ||
145 | */ | ||
146 | fr_max_payload(fp) = cmd->sess->max_frame; | ||
147 | } | ||
148 | tlen = min(mem_len, frame_len); | ||
149 | |||
150 | if (use_sg) { | ||
151 | if (!mem) { | ||
152 | BUG_ON(!task->t_task_buf); | ||
153 | page_addr = task->t_task_buf + mem_off; | ||
154 | /* | ||
155 | * In this case, offset is 'offset_in_page' of | ||
156 | * (t_task_buf + mem_off) instead of 'mem_off'. | ||
157 | */ | ||
158 | off_in_page = offset_in_page(page_addr); | ||
159 | page = virt_to_page(page_addr); | ||
160 | tlen = min(tlen, PAGE_SIZE - off_in_page); | ||
161 | } else | ||
162 | off_in_page = mem_off; | ||
163 | BUG_ON(!page); | ||
164 | get_page(page); | ||
165 | skb_fill_page_desc(fp_skb(fp), | ||
166 | skb_shinfo(fp_skb(fp))->nr_frags, | ||
167 | page, off_in_page, tlen); | ||
168 | fr_len(fp) += tlen; | ||
169 | fp_skb(fp)->data_len += tlen; | ||
170 | fp_skb(fp)->truesize += | ||
171 | PAGE_SIZE << compound_order(page); | ||
172 | } else if (mem) { | ||
173 | BUG_ON(!page); | ||
174 | from = kmap_atomic(page + (mem_off >> PAGE_SHIFT), | ||
175 | KM_SOFTIRQ0); | ||
176 | page_addr = from; | ||
177 | from += mem_off & ~PAGE_MASK; | ||
178 | tlen = min(tlen, (size_t)(PAGE_SIZE - | ||
179 | (mem_off & ~PAGE_MASK))); | ||
180 | memcpy(to, from, tlen); | ||
181 | kunmap_atomic(page_addr, KM_SOFTIRQ0); | ||
182 | to += tlen; | ||
183 | } else { | ||
184 | from = task->t_task_buf + mem_off; | ||
185 | memcpy(to, from, tlen); | ||
186 | to += tlen; | ||
187 | } | ||
188 | |||
189 | mem_off += tlen; | ||
190 | mem_len -= tlen; | ||
191 | frame_len -= tlen; | ||
192 | remaining -= tlen; | ||
193 | |||
194 | if (frame_len && | ||
195 | (skb_shinfo(fp_skb(fp))->nr_frags < FC_FRAME_SG_LEN)) | ||
196 | continue; | ||
197 | if (!remaining) | ||
198 | f_ctl |= FC_FC_END_SEQ; | ||
199 | fc_fill_fc_hdr(fp, FC_RCTL_DD_SOL_DATA, ep->did, ep->sid, | ||
200 | FC_TYPE_FCP, f_ctl, fh_off); | ||
201 | error = lport->tt.seq_send(lport, cmd->seq, fp); | ||
202 | if (error) { | ||
203 | /* XXX For now, initiator will retry */ | ||
204 | if (printk_ratelimit()) | ||
205 | printk(KERN_ERR "%s: Failed to send frame %p, " | ||
206 | "xid <0x%x>, remaining <0x%x>, " | ||
207 | "lso_max <0x%x>\n", | ||
208 | __func__, fp, ep->xid, | ||
209 | remaining, lport->lso_max); | ||
210 | } | ||
211 | } | ||
212 | return ft_queue_status(se_cmd); | ||
213 | } | ||
214 | |||
215 | /* | ||
216 | * Receive write data frame. | ||
217 | */ | ||
218 | void ft_recv_write_data(struct ft_cmd *cmd, struct fc_frame *fp) | ||
219 | { | ||
220 | struct se_cmd *se_cmd = &cmd->se_cmd; | ||
221 | struct fc_seq *seq = cmd->seq; | ||
222 | struct fc_exch *ep; | ||
223 | struct fc_lport *lport; | ||
224 | struct se_transport_task *task; | ||
225 | struct fc_frame_header *fh; | ||
226 | struct se_mem *mem; | ||
227 | u32 mem_off; | ||
228 | u32 rel_off; | ||
229 | size_t frame_len; | ||
230 | size_t mem_len; | ||
231 | size_t tlen; | ||
232 | struct page *page; | ||
233 | void *page_addr; | ||
234 | void *from; | ||
235 | void *to; | ||
236 | u32 f_ctl; | ||
237 | void *buf; | ||
238 | |||
239 | task = T_TASK(se_cmd); | ||
240 | BUG_ON(!task); | ||
241 | |||
242 | fh = fc_frame_header_get(fp); | ||
243 | if (!(ntoh24(fh->fh_f_ctl) & FC_FC_REL_OFF)) | ||
244 | goto drop; | ||
245 | |||
246 | /* | ||
247 | * Doesn't expect even single byte of payload. Payload | ||
248 | * is expected to be copied directly to user buffers | ||
249 | * due to DDP (Large Rx offload) feature, hence | ||
250 | * BUG_ON if BUF is non-NULL | ||
251 | */ | ||
252 | buf = fc_frame_payload_get(fp, 1); | ||
253 | if (cmd->was_ddp_setup && buf) { | ||
254 | printk(KERN_INFO "%s: When DDP was setup, not expected to" | ||
255 | "receive frame with payload, Payload shall be" | ||
256 | "copied directly to buffer instead of coming " | ||
257 | "via. legacy receive queues\n", __func__); | ||
258 | BUG_ON(buf); | ||
259 | } | ||
260 | |||
261 | /* | ||
262 | * If ft_cmd indicated 'ddp_setup', in that case only the last frame | ||
263 | * should come with 'TSI bit being set'. If 'TSI bit is not set and if | ||
264 | * data frame appears here, means error condition. In both the cases | ||
265 | * release the DDP context (ddp_put) and in error case, as well | ||
266 | * initiate error recovery mechanism. | ||
267 | */ | ||
268 | ep = fc_seq_exch(seq); | ||
269 | if (cmd->was_ddp_setup) { | ||
270 | BUG_ON(!ep); | ||
271 | lport = ep->lp; | ||
272 | BUG_ON(!lport); | ||
273 | } | ||
274 | if (cmd->was_ddp_setup && ep->xid != FC_XID_UNKNOWN) { | ||
275 | f_ctl = ntoh24(fh->fh_f_ctl); | ||
276 | /* | ||
277 | * If TSI bit set in f_ctl, means last write data frame is | ||
278 | * received successfully where payload is posted directly | ||
279 | * to user buffer and only the last frame's header is posted | ||
280 | * in legacy receive queue | ||
281 | */ | ||
282 | if (f_ctl & FC_FC_SEQ_INIT) { /* TSI bit set in FC frame */ | ||
283 | cmd->write_data_len = lport->tt.ddp_done(lport, | ||
284 | ep->xid); | ||
285 | goto last_frame; | ||
286 | } else { | ||
287 | /* | ||
288 | * Updating the write_data_len may be meaningless at | ||
289 | * this point, but just in case if required in future | ||
290 | * for debugging or any other purpose | ||
291 | */ | ||
292 | printk(KERN_ERR "%s: Received frame with TSI bit not" | ||
293 | " being SET, dropping the frame, " | ||
294 | "cmd->sg <%p>, cmd->sg_cnt <0x%x>\n", | ||
295 | __func__, cmd->sg, cmd->sg_cnt); | ||
296 | cmd->write_data_len = lport->tt.ddp_done(lport, | ||
297 | ep->xid); | ||
298 | lport->tt.seq_exch_abort(cmd->seq, 0); | ||
299 | goto drop; | ||
300 | } | ||
301 | } | ||
302 | |||
303 | rel_off = ntohl(fh->fh_parm_offset); | ||
304 | frame_len = fr_len(fp); | ||
305 | if (frame_len <= sizeof(*fh)) | ||
306 | goto drop; | ||
307 | frame_len -= sizeof(*fh); | ||
308 | from = fc_frame_payload_get(fp, 0); | ||
309 | if (rel_off >= se_cmd->data_length) | ||
310 | goto drop; | ||
311 | if (frame_len + rel_off > se_cmd->data_length) | ||
312 | frame_len = se_cmd->data_length - rel_off; | ||
313 | |||
314 | /* | ||
315 | * Setup to use first mem list entry if any. | ||
316 | */ | ||
317 | if (task->t_tasks_se_num) { | ||
318 | mem = list_first_entry(task->t_mem_list, | ||
319 | struct se_mem, se_list); | ||
320 | mem_len = mem->se_len; | ||
321 | mem_off = mem->se_off; | ||
322 | page = mem->se_page; | ||
323 | } else { | ||
324 | mem = NULL; | ||
325 | page = NULL; | ||
326 | mem_off = 0; | ||
327 | mem_len = frame_len; | ||
328 | } | ||
329 | |||
330 | while (frame_len) { | ||
331 | if (!mem_len) { | ||
332 | BUG_ON(!mem); | ||
333 | mem = list_entry(mem->se_list.next, | ||
334 | struct se_mem, se_list); | ||
335 | mem_len = mem->se_len; | ||
336 | mem_off = mem->se_off; | ||
337 | page = mem->se_page; | ||
338 | } | ||
339 | if (rel_off >= mem_len) { | ||
340 | rel_off -= mem_len; | ||
341 | mem_len = 0; | ||
342 | continue; | ||
343 | } | ||
344 | mem_off += rel_off; | ||
345 | mem_len -= rel_off; | ||
346 | rel_off = 0; | ||
347 | |||
348 | tlen = min(mem_len, frame_len); | ||
349 | |||
350 | if (mem) { | ||
351 | to = kmap_atomic(page + (mem_off >> PAGE_SHIFT), | ||
352 | KM_SOFTIRQ0); | ||
353 | page_addr = to; | ||
354 | to += mem_off & ~PAGE_MASK; | ||
355 | tlen = min(tlen, (size_t)(PAGE_SIZE - | ||
356 | (mem_off & ~PAGE_MASK))); | ||
357 | memcpy(to, from, tlen); | ||
358 | kunmap_atomic(page_addr, KM_SOFTIRQ0); | ||
359 | } else { | ||
360 | to = task->t_task_buf + mem_off; | ||
361 | memcpy(to, from, tlen); | ||
362 | } | ||
363 | from += tlen; | ||
364 | frame_len -= tlen; | ||
365 | mem_off += tlen; | ||
366 | mem_len -= tlen; | ||
367 | cmd->write_data_len += tlen; | ||
368 | } | ||
369 | last_frame: | ||
370 | if (cmd->write_data_len == se_cmd->data_length) | ||
371 | transport_generic_handle_data(se_cmd); | ||
372 | drop: | ||
373 | fc_frame_free(fp); | ||
374 | } | ||
diff --git a/drivers/target/tcm_fc/tfc_sess.c b/drivers/target/tcm_fc/tfc_sess.c new file mode 100644 index 000000000000..a3bd57f2ea32 --- /dev/null +++ b/drivers/target/tcm_fc/tfc_sess.c | |||
@@ -0,0 +1,541 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2010 Cisco Systems, Inc. | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify it | ||
5 | * under the terms and conditions of the GNU General Public License, | ||
6 | * version 2, as published by the Free Software Foundation. | ||
7 | * | ||
8 | * This program is distributed in the hope it will be useful, but WITHOUT | ||
9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
10 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
11 | * more details. | ||
12 | * | ||
13 | * You should have received a copy of the GNU General Public License along with | ||
14 | * this program; if not, write to the Free Software Foundation, Inc., | ||
15 | * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. | ||
16 | */ | ||
17 | |||
18 | /* XXX TBD some includes may be extraneous */ | ||
19 | |||
20 | #include <linux/module.h> | ||
21 | #include <linux/moduleparam.h> | ||
22 | #include <linux/version.h> | ||
23 | #include <generated/utsrelease.h> | ||
24 | #include <linux/utsname.h> | ||
25 | #include <linux/init.h> | ||
26 | #include <linux/slab.h> | ||
27 | #include <linux/kthread.h> | ||
28 | #include <linux/types.h> | ||
29 | #include <linux/string.h> | ||
30 | #include <linux/configfs.h> | ||
31 | #include <linux/ctype.h> | ||
32 | #include <linux/hash.h> | ||
33 | #include <linux/rcupdate.h> | ||
34 | #include <linux/rculist.h> | ||
35 | #include <linux/kref.h> | ||
36 | #include <asm/unaligned.h> | ||
37 | #include <scsi/scsi.h> | ||
38 | #include <scsi/scsi_host.h> | ||
39 | #include <scsi/scsi_device.h> | ||
40 | #include <scsi/scsi_cmnd.h> | ||
41 | #include <scsi/libfc.h> | ||
42 | |||
43 | #include <target/target_core_base.h> | ||
44 | #include <target/target_core_transport.h> | ||
45 | #include <target/target_core_fabric_ops.h> | ||
46 | #include <target/target_core_device.h> | ||
47 | #include <target/target_core_tpg.h> | ||
48 | #include <target/target_core_configfs.h> | ||
49 | #include <target/target_core_base.h> | ||
50 | #include <target/configfs_macros.h> | ||
51 | |||
52 | #include <scsi/libfc.h> | ||
53 | #include "tcm_fc.h" | ||
54 | |||
55 | static void ft_sess_delete_all(struct ft_tport *); | ||
56 | |||
57 | /* | ||
58 | * Lookup or allocate target local port. | ||
59 | * Caller holds ft_lport_lock. | ||
60 | */ | ||
61 | static struct ft_tport *ft_tport_create(struct fc_lport *lport) | ||
62 | { | ||
63 | struct ft_tpg *tpg; | ||
64 | struct ft_tport *tport; | ||
65 | int i; | ||
66 | |||
67 | tport = rcu_dereference(lport->prov[FC_TYPE_FCP]); | ||
68 | if (tport && tport->tpg) | ||
69 | return tport; | ||
70 | |||
71 | tpg = ft_lport_find_tpg(lport); | ||
72 | if (!tpg) | ||
73 | return NULL; | ||
74 | |||
75 | if (tport) { | ||
76 | tport->tpg = tpg; | ||
77 | return tport; | ||
78 | } | ||
79 | |||
80 | tport = kzalloc(sizeof(*tport), GFP_KERNEL); | ||
81 | if (!tport) | ||
82 | return NULL; | ||
83 | |||
84 | tport->lport = lport; | ||
85 | tport->tpg = tpg; | ||
86 | tpg->tport = tport; | ||
87 | for (i = 0; i < FT_SESS_HASH_SIZE; i++) | ||
88 | INIT_HLIST_HEAD(&tport->hash[i]); | ||
89 | |||
90 | rcu_assign_pointer(lport->prov[FC_TYPE_FCP], tport); | ||
91 | return tport; | ||
92 | } | ||
93 | |||
94 | /* | ||
95 | * Free tport via RCU. | ||
96 | */ | ||
97 | static void ft_tport_rcu_free(struct rcu_head *rcu) | ||
98 | { | ||
99 | struct ft_tport *tport = container_of(rcu, struct ft_tport, rcu); | ||
100 | |||
101 | kfree(tport); | ||
102 | } | ||
103 | |||
104 | /* | ||
105 | * Delete a target local port. | ||
106 | * Caller holds ft_lport_lock. | ||
107 | */ | ||
108 | static void ft_tport_delete(struct ft_tport *tport) | ||
109 | { | ||
110 | struct fc_lport *lport; | ||
111 | struct ft_tpg *tpg; | ||
112 | |||
113 | ft_sess_delete_all(tport); | ||
114 | lport = tport->lport; | ||
115 | BUG_ON(tport != lport->prov[FC_TYPE_FCP]); | ||
116 | rcu_assign_pointer(lport->prov[FC_TYPE_FCP], NULL); | ||
117 | |||
118 | tpg = tport->tpg; | ||
119 | if (tpg) { | ||
120 | tpg->tport = NULL; | ||
121 | tport->tpg = NULL; | ||
122 | } | ||
123 | call_rcu(&tport->rcu, ft_tport_rcu_free); | ||
124 | } | ||
125 | |||
126 | /* | ||
127 | * Add local port. | ||
128 | * Called thru fc_lport_iterate(). | ||
129 | */ | ||
130 | void ft_lport_add(struct fc_lport *lport, void *arg) | ||
131 | { | ||
132 | mutex_lock(&ft_lport_lock); | ||
133 | ft_tport_create(lport); | ||
134 | mutex_unlock(&ft_lport_lock); | ||
135 | } | ||
136 | |||
137 | /* | ||
138 | * Delete local port. | ||
139 | * Called thru fc_lport_iterate(). | ||
140 | */ | ||
141 | void ft_lport_del(struct fc_lport *lport, void *arg) | ||
142 | { | ||
143 | struct ft_tport *tport; | ||
144 | |||
145 | mutex_lock(&ft_lport_lock); | ||
146 | tport = lport->prov[FC_TYPE_FCP]; | ||
147 | if (tport) | ||
148 | ft_tport_delete(tport); | ||
149 | mutex_unlock(&ft_lport_lock); | ||
150 | } | ||
151 | |||
152 | /* | ||
153 | * Notification of local port change from libfc. | ||
154 | * Create or delete local port and associated tport. | ||
155 | */ | ||
156 | int ft_lport_notify(struct notifier_block *nb, unsigned long event, void *arg) | ||
157 | { | ||
158 | struct fc_lport *lport = arg; | ||
159 | |||
160 | switch (event) { | ||
161 | case FC_LPORT_EV_ADD: | ||
162 | ft_lport_add(lport, NULL); | ||
163 | break; | ||
164 | case FC_LPORT_EV_DEL: | ||
165 | ft_lport_del(lport, NULL); | ||
166 | break; | ||
167 | } | ||
168 | return NOTIFY_DONE; | ||
169 | } | ||
170 | |||
171 | /* | ||
172 | * Hash function for FC_IDs. | ||
173 | */ | ||
174 | static u32 ft_sess_hash(u32 port_id) | ||
175 | { | ||
176 | return hash_32(port_id, FT_SESS_HASH_BITS); | ||
177 | } | ||
178 | |||
179 | /* | ||
180 | * Find session in local port. | ||
181 | * Sessions and hash lists are RCU-protected. | ||
182 | * A reference is taken which must be eventually freed. | ||
183 | */ | ||
184 | static struct ft_sess *ft_sess_get(struct fc_lport *lport, u32 port_id) | ||
185 | { | ||
186 | struct ft_tport *tport; | ||
187 | struct hlist_head *head; | ||
188 | struct hlist_node *pos; | ||
189 | struct ft_sess *sess; | ||
190 | |||
191 | rcu_read_lock(); | ||
192 | tport = rcu_dereference(lport->prov[FC_TYPE_FCP]); | ||
193 | if (!tport) | ||
194 | goto out; | ||
195 | |||
196 | head = &tport->hash[ft_sess_hash(port_id)]; | ||
197 | hlist_for_each_entry_rcu(sess, pos, head, hash) { | ||
198 | if (sess->port_id == port_id) { | ||
199 | kref_get(&sess->kref); | ||
200 | rcu_read_unlock(); | ||
201 | FT_SESS_DBG("port_id %x found %p\n", port_id, sess); | ||
202 | return sess; | ||
203 | } | ||
204 | } | ||
205 | out: | ||
206 | rcu_read_unlock(); | ||
207 | FT_SESS_DBG("port_id %x not found\n", port_id); | ||
208 | return NULL; | ||
209 | } | ||
210 | |||
211 | /* | ||
212 | * Allocate session and enter it in the hash for the local port. | ||
213 | * Caller holds ft_lport_lock. | ||
214 | */ | ||
215 | static struct ft_sess *ft_sess_create(struct ft_tport *tport, u32 port_id, | ||
216 | struct ft_node_acl *acl) | ||
217 | { | ||
218 | struct ft_sess *sess; | ||
219 | struct hlist_head *head; | ||
220 | struct hlist_node *pos; | ||
221 | |||
222 | head = &tport->hash[ft_sess_hash(port_id)]; | ||
223 | hlist_for_each_entry_rcu(sess, pos, head, hash) | ||
224 | if (sess->port_id == port_id) | ||
225 | return sess; | ||
226 | |||
227 | sess = kzalloc(sizeof(*sess), GFP_KERNEL); | ||
228 | if (!sess) | ||
229 | return NULL; | ||
230 | |||
231 | sess->se_sess = transport_init_session(); | ||
232 | if (!sess->se_sess) { | ||
233 | kfree(sess); | ||
234 | return NULL; | ||
235 | } | ||
236 | sess->se_sess->se_node_acl = &acl->se_node_acl; | ||
237 | sess->tport = tport; | ||
238 | sess->port_id = port_id; | ||
239 | kref_init(&sess->kref); /* ref for table entry */ | ||
240 | hlist_add_head_rcu(&sess->hash, head); | ||
241 | tport->sess_count++; | ||
242 | |||
243 | FT_SESS_DBG("port_id %x sess %p\n", port_id, sess); | ||
244 | |||
245 | transport_register_session(&tport->tpg->se_tpg, &acl->se_node_acl, | ||
246 | sess->se_sess, sess); | ||
247 | return sess; | ||
248 | } | ||
249 | |||
250 | /* | ||
251 | * Unhash the session. | ||
252 | * Caller holds ft_lport_lock. | ||
253 | */ | ||
254 | static void ft_sess_unhash(struct ft_sess *sess) | ||
255 | { | ||
256 | struct ft_tport *tport = sess->tport; | ||
257 | |||
258 | hlist_del_rcu(&sess->hash); | ||
259 | BUG_ON(!tport->sess_count); | ||
260 | tport->sess_count--; | ||
261 | sess->port_id = -1; | ||
262 | sess->params = 0; | ||
263 | } | ||
264 | |||
265 | /* | ||
266 | * Delete session from hash. | ||
267 | * Caller holds ft_lport_lock. | ||
268 | */ | ||
269 | static struct ft_sess *ft_sess_delete(struct ft_tport *tport, u32 port_id) | ||
270 | { | ||
271 | struct hlist_head *head; | ||
272 | struct hlist_node *pos; | ||
273 | struct ft_sess *sess; | ||
274 | |||
275 | head = &tport->hash[ft_sess_hash(port_id)]; | ||
276 | hlist_for_each_entry_rcu(sess, pos, head, hash) { | ||
277 | if (sess->port_id == port_id) { | ||
278 | ft_sess_unhash(sess); | ||
279 | return sess; | ||
280 | } | ||
281 | } | ||
282 | return NULL; | ||
283 | } | ||
284 | |||
285 | /* | ||
286 | * Delete all sessions from tport. | ||
287 | * Caller holds ft_lport_lock. | ||
288 | */ | ||
289 | static void ft_sess_delete_all(struct ft_tport *tport) | ||
290 | { | ||
291 | struct hlist_head *head; | ||
292 | struct hlist_node *pos; | ||
293 | struct ft_sess *sess; | ||
294 | |||
295 | for (head = tport->hash; | ||
296 | head < &tport->hash[FT_SESS_HASH_SIZE]; head++) { | ||
297 | hlist_for_each_entry_rcu(sess, pos, head, hash) { | ||
298 | ft_sess_unhash(sess); | ||
299 | transport_deregister_session_configfs(sess->se_sess); | ||
300 | ft_sess_put(sess); /* release from table */ | ||
301 | } | ||
302 | } | ||
303 | } | ||
304 | |||
305 | /* | ||
306 | * TCM ops for sessions. | ||
307 | */ | ||
308 | |||
309 | /* | ||
310 | * Determine whether session is allowed to be shutdown in the current context. | ||
311 | * Returns non-zero if the session should be shutdown. | ||
312 | */ | ||
313 | int ft_sess_shutdown(struct se_session *se_sess) | ||
314 | { | ||
315 | struct ft_sess *sess = se_sess->fabric_sess_ptr; | ||
316 | |||
317 | FT_SESS_DBG("port_id %x\n", sess->port_id); | ||
318 | return 1; | ||
319 | } | ||
320 | |||
321 | /* | ||
322 | * Remove session and send PRLO. | ||
323 | * This is called when the ACL is being deleted or queue depth is changing. | ||
324 | */ | ||
325 | void ft_sess_close(struct se_session *se_sess) | ||
326 | { | ||
327 | struct ft_sess *sess = se_sess->fabric_sess_ptr; | ||
328 | struct fc_lport *lport; | ||
329 | u32 port_id; | ||
330 | |||
331 | mutex_lock(&ft_lport_lock); | ||
332 | lport = sess->tport->lport; | ||
333 | port_id = sess->port_id; | ||
334 | if (port_id == -1) { | ||
335 | mutex_lock(&ft_lport_lock); | ||
336 | return; | ||
337 | } | ||
338 | FT_SESS_DBG("port_id %x\n", port_id); | ||
339 | ft_sess_unhash(sess); | ||
340 | mutex_unlock(&ft_lport_lock); | ||
341 | transport_deregister_session_configfs(se_sess); | ||
342 | ft_sess_put(sess); | ||
343 | /* XXX Send LOGO or PRLO */ | ||
344 | synchronize_rcu(); /* let transport deregister happen */ | ||
345 | } | ||
346 | |||
347 | void ft_sess_stop(struct se_session *se_sess, int sess_sleep, int conn_sleep) | ||
348 | { | ||
349 | struct ft_sess *sess = se_sess->fabric_sess_ptr; | ||
350 | |||
351 | FT_SESS_DBG("port_id %x\n", sess->port_id); | ||
352 | } | ||
353 | |||
354 | int ft_sess_logged_in(struct se_session *se_sess) | ||
355 | { | ||
356 | struct ft_sess *sess = se_sess->fabric_sess_ptr; | ||
357 | |||
358 | return sess->port_id != -1; | ||
359 | } | ||
360 | |||
361 | u32 ft_sess_get_index(struct se_session *se_sess) | ||
362 | { | ||
363 | struct ft_sess *sess = se_sess->fabric_sess_ptr; | ||
364 | |||
365 | return sess->port_id; /* XXX TBD probably not what is needed */ | ||
366 | } | ||
367 | |||
368 | u32 ft_sess_get_port_name(struct se_session *se_sess, | ||
369 | unsigned char *buf, u32 len) | ||
370 | { | ||
371 | struct ft_sess *sess = se_sess->fabric_sess_ptr; | ||
372 | |||
373 | return ft_format_wwn(buf, len, sess->port_name); | ||
374 | } | ||
375 | |||
376 | void ft_sess_set_erl0(struct se_session *se_sess) | ||
377 | { | ||
378 | /* XXX TBD called when out of memory */ | ||
379 | } | ||
380 | |||
381 | /* | ||
382 | * libfc ops involving sessions. | ||
383 | */ | ||
384 | |||
385 | static int ft_prli_locked(struct fc_rport_priv *rdata, u32 spp_len, | ||
386 | const struct fc_els_spp *rspp, struct fc_els_spp *spp) | ||
387 | { | ||
388 | struct ft_tport *tport; | ||
389 | struct ft_sess *sess; | ||
390 | struct ft_node_acl *acl; | ||
391 | u32 fcp_parm; | ||
392 | |||
393 | tport = ft_tport_create(rdata->local_port); | ||
394 | if (!tport) | ||
395 | return 0; /* not a target for this local port */ | ||
396 | |||
397 | acl = ft_acl_get(tport->tpg, rdata); | ||
398 | if (!acl) | ||
399 | return 0; | ||
400 | |||
401 | if (!rspp) | ||
402 | goto fill; | ||
403 | |||
404 | if (rspp->spp_flags & (FC_SPP_OPA_VAL | FC_SPP_RPA_VAL)) | ||
405 | return FC_SPP_RESP_NO_PA; | ||
406 | |||
407 | /* | ||
408 | * If both target and initiator bits are off, the SPP is invalid. | ||
409 | */ | ||
410 | fcp_parm = ntohl(rspp->spp_params); | ||
411 | if (!(fcp_parm & (FCP_SPPF_INIT_FCN | FCP_SPPF_TARG_FCN))) | ||
412 | return FC_SPP_RESP_INVL; | ||
413 | |||
414 | /* | ||
415 | * Create session (image pair) only if requested by | ||
416 | * EST_IMG_PAIR flag and if the requestor is an initiator. | ||
417 | */ | ||
418 | if (rspp->spp_flags & FC_SPP_EST_IMG_PAIR) { | ||
419 | spp->spp_flags |= FC_SPP_EST_IMG_PAIR; | ||
420 | if (!(fcp_parm & FCP_SPPF_INIT_FCN)) | ||
421 | return FC_SPP_RESP_CONF; | ||
422 | sess = ft_sess_create(tport, rdata->ids.port_id, acl); | ||
423 | if (!sess) | ||
424 | return FC_SPP_RESP_RES; | ||
425 | if (!sess->params) | ||
426 | rdata->prli_count++; | ||
427 | sess->params = fcp_parm; | ||
428 | sess->port_name = rdata->ids.port_name; | ||
429 | sess->max_frame = rdata->maxframe_size; | ||
430 | |||
431 | /* XXX TBD - clearing actions. unit attn, see 4.10 */ | ||
432 | } | ||
433 | |||
434 | /* | ||
435 | * OR in our service parameters with other provider (initiator), if any. | ||
436 | * TBD XXX - indicate RETRY capability? | ||
437 | */ | ||
438 | fill: | ||
439 | fcp_parm = ntohl(spp->spp_params); | ||
440 | spp->spp_params = htonl(fcp_parm | FCP_SPPF_TARG_FCN); | ||
441 | return FC_SPP_RESP_ACK; | ||
442 | } | ||
443 | |||
444 | /** | ||
445 | * tcm_fcp_prli() - Handle incoming or outgoing PRLI for the FCP target | ||
446 | * @rdata: remote port private | ||
447 | * @spp_len: service parameter page length | ||
448 | * @rspp: received service parameter page (NULL for outgoing PRLI) | ||
449 | * @spp: response service parameter page | ||
450 | * | ||
451 | * Returns spp response code. | ||
452 | */ | ||
453 | static int ft_prli(struct fc_rport_priv *rdata, u32 spp_len, | ||
454 | const struct fc_els_spp *rspp, struct fc_els_spp *spp) | ||
455 | { | ||
456 | int ret; | ||
457 | |||
458 | mutex_lock(&ft_lport_lock); | ||
459 | ret = ft_prli_locked(rdata, spp_len, rspp, spp); | ||
460 | mutex_unlock(&ft_lport_lock); | ||
461 | FT_SESS_DBG("port_id %x flags %x ret %x\n", | ||
462 | rdata->ids.port_id, rspp ? rspp->spp_flags : 0, ret); | ||
463 | return ret; | ||
464 | } | ||
465 | |||
466 | static void ft_sess_rcu_free(struct rcu_head *rcu) | ||
467 | { | ||
468 | struct ft_sess *sess = container_of(rcu, struct ft_sess, rcu); | ||
469 | |||
470 | transport_deregister_session(sess->se_sess); | ||
471 | kfree(sess); | ||
472 | } | ||
473 | |||
474 | static void ft_sess_free(struct kref *kref) | ||
475 | { | ||
476 | struct ft_sess *sess = container_of(kref, struct ft_sess, kref); | ||
477 | |||
478 | call_rcu(&sess->rcu, ft_sess_rcu_free); | ||
479 | } | ||
480 | |||
481 | void ft_sess_put(struct ft_sess *sess) | ||
482 | { | ||
483 | int sess_held = atomic_read(&sess->kref.refcount); | ||
484 | |||
485 | BUG_ON(!sess_held); | ||
486 | kref_put(&sess->kref, ft_sess_free); | ||
487 | } | ||
488 | |||
489 | static void ft_prlo(struct fc_rport_priv *rdata) | ||
490 | { | ||
491 | struct ft_sess *sess; | ||
492 | struct ft_tport *tport; | ||
493 | |||
494 | mutex_lock(&ft_lport_lock); | ||
495 | tport = rcu_dereference(rdata->local_port->prov[FC_TYPE_FCP]); | ||
496 | if (!tport) { | ||
497 | mutex_unlock(&ft_lport_lock); | ||
498 | return; | ||
499 | } | ||
500 | sess = ft_sess_delete(tport, rdata->ids.port_id); | ||
501 | if (!sess) { | ||
502 | mutex_unlock(&ft_lport_lock); | ||
503 | return; | ||
504 | } | ||
505 | mutex_unlock(&ft_lport_lock); | ||
506 | transport_deregister_session_configfs(sess->se_sess); | ||
507 | ft_sess_put(sess); /* release from table */ | ||
508 | rdata->prli_count--; | ||
509 | /* XXX TBD - clearing actions. unit attn, see 4.10 */ | ||
510 | } | ||
511 | |||
512 | /* | ||
513 | * Handle incoming FCP request. | ||
514 | * Caller has verified that the frame is type FCP. | ||
515 | */ | ||
516 | static void ft_recv(struct fc_lport *lport, struct fc_frame *fp) | ||
517 | { | ||
518 | struct ft_sess *sess; | ||
519 | u32 sid = fc_frame_sid(fp); | ||
520 | |||
521 | FT_SESS_DBG("sid %x\n", sid); | ||
522 | |||
523 | sess = ft_sess_get(lport, sid); | ||
524 | if (!sess) { | ||
525 | FT_SESS_DBG("sid %x sess lookup failed\n", sid); | ||
526 | /* TBD XXX - if FCP_CMND, send PRLO */ | ||
527 | fc_frame_free(fp); | ||
528 | return; | ||
529 | } | ||
530 | ft_recv_req(sess, fp); /* must do ft_sess_put() */ | ||
531 | } | ||
532 | |||
533 | /* | ||
534 | * Provider ops for libfc. | ||
535 | */ | ||
536 | struct fc4_prov ft_prov = { | ||
537 | .prli = ft_prli, | ||
538 | .prlo = ft_prlo, | ||
539 | .recv = ft_recv, | ||
540 | .module = THIS_MODULE, | ||
541 | }; | ||