504 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Copyright (c) 2010 Cisco Systems, Inc.4 */5 6/* XXX TBD some includes may be extraneous */7 8#include <linux/module.h>9#include <linux/moduleparam.h>10#include <linux/utsname.h>11#include <linux/init.h>12#include <linux/slab.h>13#include <linux/kthread.h>14#include <linux/types.h>15#include <linux/string.h>16#include <linux/configfs.h>17#include <linux/ctype.h>18#include <linux/hash.h>19#include <linux/rcupdate.h>20#include <linux/rculist.h>21#include <linux/kref.h>22#include <linux/unaligned.h>23#include <scsi/libfc.h>24 25#include <target/target_core_base.h>26#include <target/target_core_fabric.h>27 28#include "tcm_fc.h"29 30#define TFC_SESS_DBG(lport, fmt, args...) \31 pr_debug("host%u: rport %6.6x: " fmt, \32 (lport)->host->host_no, \33 (lport)->port_id, ##args )34 35static void ft_sess_delete_all(struct ft_tport *);36 37/*38 * Lookup or allocate target local port.39 * Caller holds ft_lport_lock.40 */41static struct ft_tport *ft_tport_get(struct fc_lport *lport)42{43 struct ft_tpg *tpg;44 struct ft_tport *tport;45 int i;46 47 tport = rcu_dereference_protected(lport->prov[FC_TYPE_FCP],48 lockdep_is_held(&ft_lport_lock));49 if (tport && tport->tpg)50 return tport;51 52 tpg = ft_lport_find_tpg(lport);53 if (!tpg)54 return NULL;55 56 if (tport) {57 tport->tpg = tpg;58 tpg->tport = tport;59 return tport;60 }61 62 tport = kzalloc(sizeof(*tport), GFP_KERNEL);63 if (!tport)64 return NULL;65 66 tport->lport = lport;67 tport->tpg = tpg;68 tpg->tport = tport;69 for (i = 0; i < FT_SESS_HASH_SIZE; i++)70 INIT_HLIST_HEAD(&tport->hash[i]);71 72 rcu_assign_pointer(lport->prov[FC_TYPE_FCP], tport);73 return tport;74}75 76/*77 * Delete a target local port.78 * Caller holds ft_lport_lock.79 */80static void ft_tport_delete(struct ft_tport *tport)81{82 struct fc_lport *lport;83 struct ft_tpg *tpg;84 85 ft_sess_delete_all(tport);86 lport = tport->lport;87 lport->service_params &= ~FCP_SPPF_TARG_FCN;88 BUG_ON(tport != lport->prov[FC_TYPE_FCP]);89 RCU_INIT_POINTER(lport->prov[FC_TYPE_FCP], NULL);90 91 tpg = tport->tpg;92 if (tpg) {93 tpg->tport = NULL;94 tport->tpg = NULL;95 }96 kfree_rcu(tport, rcu);97}98 99/*100 * Add local port.101 * Called thru fc_lport_iterate().102 */103void ft_lport_add(struct fc_lport *lport, void *arg)104{105 mutex_lock(&ft_lport_lock);106 ft_tport_get(lport);107 lport->service_params |= FCP_SPPF_TARG_FCN;108 mutex_unlock(&ft_lport_lock);109}110 111/*112 * Delete local port.113 * Called thru fc_lport_iterate().114 */115void ft_lport_del(struct fc_lport *lport, void *arg)116{117 struct ft_tport *tport;118 119 mutex_lock(&ft_lport_lock);120 tport = lport->prov[FC_TYPE_FCP];121 if (tport)122 ft_tport_delete(tport);123 mutex_unlock(&ft_lport_lock);124}125 126/*127 * Notification of local port change from libfc.128 * Create or delete local port and associated tport.129 */130int ft_lport_notify(struct notifier_block *nb, unsigned long event, void *arg)131{132 struct fc_lport *lport = arg;133 134 switch (event) {135 case FC_LPORT_EV_ADD:136 ft_lport_add(lport, NULL);137 break;138 case FC_LPORT_EV_DEL:139 ft_lport_del(lport, NULL);140 break;141 }142 return NOTIFY_DONE;143}144 145/*146 * Hash function for FC_IDs.147 */148static u32 ft_sess_hash(u32 port_id)149{150 return hash_32(port_id, FT_SESS_HASH_BITS);151}152 153/*154 * Find session in local port.155 * Sessions and hash lists are RCU-protected.156 * A reference is taken which must be eventually freed.157 */158static struct ft_sess *ft_sess_get(struct fc_lport *lport, u32 port_id)159{160 struct ft_tport *tport;161 struct hlist_head *head;162 struct ft_sess *sess;163 char *reason = "no session created";164 165 rcu_read_lock();166 tport = rcu_dereference(lport->prov[FC_TYPE_FCP]);167 if (!tport) {168 reason = "not an FCP port";169 goto out;170 }171 172 head = &tport->hash[ft_sess_hash(port_id)];173 hlist_for_each_entry_rcu(sess, head, hash) {174 if (sess->port_id == port_id) {175 kref_get(&sess->kref);176 rcu_read_unlock();177 TFC_SESS_DBG(lport, "port_id %x found %p\n",178 port_id, sess);179 return sess;180 }181 }182out:183 rcu_read_unlock();184 TFC_SESS_DBG(lport, "port_id %x not found, %s\n",185 port_id, reason);186 return NULL;187}188 189static int ft_sess_alloc_cb(struct se_portal_group *se_tpg,190 struct se_session *se_sess, void *p)191{192 struct ft_sess *sess = p;193 struct ft_tport *tport = sess->tport;194 struct hlist_head *head = &tport->hash[ft_sess_hash(sess->port_id)];195 196 TFC_SESS_DBG(tport->lport, "port_id %x sess %p\n", sess->port_id, sess);197 hlist_add_head_rcu(&sess->hash, head);198 tport->sess_count++;199 200 return 0;201}202 203/*204 * Allocate session and enter it in the hash for the local port.205 * Caller holds ft_lport_lock.206 */207static struct ft_sess *ft_sess_create(struct ft_tport *tport, u32 port_id,208 struct fc_rport_priv *rdata)209{210 struct se_portal_group *se_tpg = &tport->tpg->se_tpg;211 struct ft_sess *sess;212 struct hlist_head *head;213 unsigned char initiatorname[TRANSPORT_IQN_LEN];214 215 ft_format_wwn(&initiatorname[0], TRANSPORT_IQN_LEN, rdata->ids.port_name);216 217 head = &tport->hash[ft_sess_hash(port_id)];218 hlist_for_each_entry_rcu(sess, head, hash)219 if (sess->port_id == port_id)220 return sess;221 222 sess = kzalloc(sizeof(*sess), GFP_KERNEL);223 if (!sess)224 return ERR_PTR(-ENOMEM);225 226 kref_init(&sess->kref); /* ref for table entry */227 sess->tport = tport;228 sess->port_id = port_id;229 230 sess->se_sess = target_setup_session(se_tpg, TCM_FC_DEFAULT_TAGS,231 sizeof(struct ft_cmd),232 TARGET_PROT_NORMAL, &initiatorname[0],233 sess, ft_sess_alloc_cb);234 if (IS_ERR(sess->se_sess)) {235 int rc = PTR_ERR(sess->se_sess);236 kfree(sess);237 sess = ERR_PTR(rc);238 }239 return sess;240}241 242/*243 * Unhash the session.244 * Caller holds ft_lport_lock.245 */246static void ft_sess_unhash(struct ft_sess *sess)247{248 struct ft_tport *tport = sess->tport;249 250 hlist_del_rcu(&sess->hash);251 BUG_ON(!tport->sess_count);252 tport->sess_count--;253 sess->port_id = -1;254 sess->params = 0;255}256 257/*258 * Delete session from hash.259 * Caller holds ft_lport_lock.260 */261static struct ft_sess *ft_sess_delete(struct ft_tport *tport, u32 port_id)262{263 struct hlist_head *head;264 struct ft_sess *sess;265 266 head = &tport->hash[ft_sess_hash(port_id)];267 hlist_for_each_entry_rcu(sess, head, hash) {268 if (sess->port_id == port_id) {269 ft_sess_unhash(sess);270 return sess;271 }272 }273 return NULL;274}275 276static void ft_close_sess(struct ft_sess *sess)277{278 target_stop_session(sess->se_sess);279 target_wait_for_sess_cmds(sess->se_sess);280 ft_sess_put(sess);281}282 283/*284 * Delete all sessions from tport.285 * Caller holds ft_lport_lock.286 */287static void ft_sess_delete_all(struct ft_tport *tport)288{289 struct hlist_head *head;290 struct ft_sess *sess;291 292 for (head = tport->hash;293 head < &tport->hash[FT_SESS_HASH_SIZE]; head++) {294 hlist_for_each_entry_rcu(sess, head, hash) {295 ft_sess_unhash(sess);296 ft_close_sess(sess); /* release from table */297 }298 }299}300 301/*302 * TCM ops for sessions.303 */304 305/*306 * Remove session and send PRLO.307 * This is called when the ACL is being deleted or queue depth is changing.308 */309void ft_sess_close(struct se_session *se_sess)310{311 struct ft_sess *sess = se_sess->fabric_sess_ptr;312 u32 port_id;313 314 mutex_lock(&ft_lport_lock);315 port_id = sess->port_id;316 if (port_id == -1) {317 mutex_unlock(&ft_lport_lock);318 return;319 }320 TFC_SESS_DBG(sess->tport->lport, "port_id %x close session\n", port_id);321 ft_sess_unhash(sess);322 mutex_unlock(&ft_lport_lock);323 ft_close_sess(sess);324 /* XXX Send LOGO or PRLO */325 synchronize_rcu(); /* let transport deregister happen */326}327 328u32 ft_sess_get_index(struct se_session *se_sess)329{330 struct ft_sess *sess = se_sess->fabric_sess_ptr;331 332 return sess->port_id; /* XXX TBD probably not what is needed */333}334 335u32 ft_sess_get_port_name(struct se_session *se_sess,336 unsigned char *buf, u32 len)337{338 struct ft_sess *sess = se_sess->fabric_sess_ptr;339 340 return ft_format_wwn(buf, len, sess->port_name);341}342 343/*344 * libfc ops involving sessions.345 */346 347static int ft_prli_locked(struct fc_rport_priv *rdata, u32 spp_len,348 const struct fc_els_spp *rspp, struct fc_els_spp *spp)349{350 struct ft_tport *tport;351 struct ft_sess *sess;352 u32 fcp_parm;353 354 tport = ft_tport_get(rdata->local_port);355 if (!tport)356 goto not_target; /* not a target for this local port */357 358 if (!rspp)359 goto fill;360 361 if (rspp->spp_flags & (FC_SPP_OPA_VAL | FC_SPP_RPA_VAL))362 return FC_SPP_RESP_NO_PA;363 364 /*365 * If both target and initiator bits are off, the SPP is invalid.366 */367 fcp_parm = ntohl(rspp->spp_params);368 if (!(fcp_parm & (FCP_SPPF_INIT_FCN | FCP_SPPF_TARG_FCN)))369 return FC_SPP_RESP_INVL;370 371 /*372 * Create session (image pair) only if requested by373 * EST_IMG_PAIR flag and if the requestor is an initiator.374 */375 if (rspp->spp_flags & FC_SPP_EST_IMG_PAIR) {376 spp->spp_flags |= FC_SPP_EST_IMG_PAIR;377 if (!(fcp_parm & FCP_SPPF_INIT_FCN))378 return FC_SPP_RESP_CONF;379 sess = ft_sess_create(tport, rdata->ids.port_id, rdata);380 if (IS_ERR(sess)) {381 if (PTR_ERR(sess) == -EACCES) {382 spp->spp_flags &= ~FC_SPP_EST_IMG_PAIR;383 return FC_SPP_RESP_CONF;384 } else385 return FC_SPP_RESP_RES;386 }387 if (!sess->params)388 rdata->prli_count++;389 sess->params = fcp_parm;390 sess->port_name = rdata->ids.port_name;391 sess->max_frame = rdata->maxframe_size;392 393 /* XXX TBD - clearing actions. unit attn, see 4.10 */394 }395 396 /*397 * OR in our service parameters with other provider (initiator), if any.398 */399fill:400 fcp_parm = ntohl(spp->spp_params);401 fcp_parm &= ~FCP_SPPF_RETRY;402 spp->spp_params = htonl(fcp_parm | FCP_SPPF_TARG_FCN);403 return FC_SPP_RESP_ACK;404 405not_target:406 fcp_parm = ntohl(spp->spp_params);407 fcp_parm &= ~FCP_SPPF_TARG_FCN;408 spp->spp_params = htonl(fcp_parm);409 return 0;410}411 412/**413 * ft_prli() - Handle incoming or outgoing PRLI for the FCP target414 * @rdata: remote port private415 * @spp_len: service parameter page length416 * @rspp: received service parameter page (NULL for outgoing PRLI)417 * @spp: response service parameter page418 *419 * Returns spp response code.420 */421static int ft_prli(struct fc_rport_priv *rdata, u32 spp_len,422 const struct fc_els_spp *rspp, struct fc_els_spp *spp)423{424 int ret;425 426 mutex_lock(&ft_lport_lock);427 ret = ft_prli_locked(rdata, spp_len, rspp, spp);428 mutex_unlock(&ft_lport_lock);429 TFC_SESS_DBG(rdata->local_port, "port_id %x flags %x ret %x\n",430 rdata->ids.port_id, rspp ? rspp->spp_flags : 0, ret);431 return ret;432}433 434static void ft_sess_free(struct kref *kref)435{436 struct ft_sess *sess = container_of(kref, struct ft_sess, kref);437 438 target_remove_session(sess->se_sess);439 kfree_rcu(sess, rcu);440}441 442void ft_sess_put(struct ft_sess *sess)443{444 int sess_held = kref_read(&sess->kref);445 446 BUG_ON(!sess_held);447 kref_put(&sess->kref, ft_sess_free);448}449 450static void ft_prlo(struct fc_rport_priv *rdata)451{452 struct ft_sess *sess;453 struct ft_tport *tport;454 455 mutex_lock(&ft_lport_lock);456 tport = rcu_dereference_protected(rdata->local_port->prov[FC_TYPE_FCP],457 lockdep_is_held(&ft_lport_lock));458 459 if (!tport) {460 mutex_unlock(&ft_lport_lock);461 return;462 }463 sess = ft_sess_delete(tport, rdata->ids.port_id);464 if (!sess) {465 mutex_unlock(&ft_lport_lock);466 return;467 }468 mutex_unlock(&ft_lport_lock);469 ft_close_sess(sess); /* release from table */470 rdata->prli_count--;471 /* XXX TBD - clearing actions. unit attn, see 4.10 */472}473 474/*475 * Handle incoming FCP request.476 * Caller has verified that the frame is type FCP.477 */478static void ft_recv(struct fc_lport *lport, struct fc_frame *fp)479{480 struct ft_sess *sess;481 u32 sid = fc_frame_sid(fp);482 483 TFC_SESS_DBG(lport, "recv sid %x\n", sid);484 485 sess = ft_sess_get(lport, sid);486 if (!sess) {487 TFC_SESS_DBG(lport, "sid %x sess lookup failed\n", sid);488 /* TBD XXX - if FCP_CMND, send PRLO */489 fc_frame_free(fp);490 return;491 }492 ft_recv_req(sess, fp); /* must do ft_sess_put() */493}494 495/*496 * Provider ops for libfc.497 */498struct fc4_prov ft_prov = {499 .prli = ft_prli,500 .prlo = ft_prlo,501 .recv = ft_recv,502 .module = THIS_MODULE,503};504