mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-09 01:28:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
504
drivers/target/tcm_fc/tfc_sess.c
Normal file
504
drivers/target/tcm_fc/tfc_sess.c
Normal file
|
@ -0,0 +1,504 @@
|
|||
/*
|
||||
* Copyright (c) 2010 Cisco Systems, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
/* XXX TBD some includes may be extraneous */
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/utsname.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/configfs.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/hash.h>
|
||||
#include <linux/rcupdate.h>
|
||||
#include <linux/rculist.h>
|
||||
#include <linux/kref.h>
|
||||
#include <asm/unaligned.h>
|
||||
#include <scsi/scsi.h>
|
||||
#include <scsi/scsi_host.h>
|
||||
#include <scsi/scsi_device.h>
|
||||
#include <scsi/scsi_cmnd.h>
|
||||
#include <scsi/libfc.h>
|
||||
|
||||
#include <target/target_core_base.h>
|
||||
#include <target/target_core_fabric.h>
|
||||
#include <target/target_core_configfs.h>
|
||||
#include <target/configfs_macros.h>
|
||||
|
||||
#include "tcm_fc.h"
|
||||
|
||||
static void ft_sess_delete_all(struct ft_tport *);
|
||||
|
||||
/*
|
||||
* Lookup or allocate target local port.
|
||||
* Caller holds ft_lport_lock.
|
||||
*/
|
||||
static struct ft_tport *ft_tport_get(struct fc_lport *lport)
|
||||
{
|
||||
struct ft_tpg *tpg;
|
||||
struct ft_tport *tport;
|
||||
int i;
|
||||
|
||||
tport = rcu_dereference_protected(lport->prov[FC_TYPE_FCP],
|
||||
lockdep_is_held(&ft_lport_lock));
|
||||
if (tport && tport->tpg)
|
||||
return tport;
|
||||
|
||||
tpg = ft_lport_find_tpg(lport);
|
||||
if (!tpg)
|
||||
return NULL;
|
||||
|
||||
if (tport) {
|
||||
tport->tpg = tpg;
|
||||
tpg->tport = tport;
|
||||
return tport;
|
||||
}
|
||||
|
||||
tport = kzalloc(sizeof(*tport), GFP_KERNEL);
|
||||
if (!tport)
|
||||
return NULL;
|
||||
|
||||
tport->lport = lport;
|
||||
tport->tpg = tpg;
|
||||
tpg->tport = tport;
|
||||
for (i = 0; i < FT_SESS_HASH_SIZE; i++)
|
||||
INIT_HLIST_HEAD(&tport->hash[i]);
|
||||
|
||||
rcu_assign_pointer(lport->prov[FC_TYPE_FCP], tport);
|
||||
return tport;
|
||||
}
|
||||
|
||||
/*
|
||||
* Delete a target local port.
|
||||
* Caller holds ft_lport_lock.
|
||||
*/
|
||||
static void ft_tport_delete(struct ft_tport *tport)
|
||||
{
|
||||
struct fc_lport *lport;
|
||||
struct ft_tpg *tpg;
|
||||
|
||||
ft_sess_delete_all(tport);
|
||||
lport = tport->lport;
|
||||
BUG_ON(tport != lport->prov[FC_TYPE_FCP]);
|
||||
RCU_INIT_POINTER(lport->prov[FC_TYPE_FCP], NULL);
|
||||
|
||||
tpg = tport->tpg;
|
||||
if (tpg) {
|
||||
tpg->tport = NULL;
|
||||
tport->tpg = NULL;
|
||||
}
|
||||
kfree_rcu(tport, rcu);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add local port.
|
||||
* Called thru fc_lport_iterate().
|
||||
*/
|
||||
void ft_lport_add(struct fc_lport *lport, void *arg)
|
||||
{
|
||||
mutex_lock(&ft_lport_lock);
|
||||
ft_tport_get(lport);
|
||||
mutex_unlock(&ft_lport_lock);
|
||||
}
|
||||
|
||||
/*
|
||||
* Delete local port.
|
||||
* Called thru fc_lport_iterate().
|
||||
*/
|
||||
void ft_lport_del(struct fc_lport *lport, void *arg)
|
||||
{
|
||||
struct ft_tport *tport;
|
||||
|
||||
mutex_lock(&ft_lport_lock);
|
||||
tport = lport->prov[FC_TYPE_FCP];
|
||||
if (tport)
|
||||
ft_tport_delete(tport);
|
||||
mutex_unlock(&ft_lport_lock);
|
||||
}
|
||||
|
||||
/*
|
||||
* Notification of local port change from libfc.
|
||||
* Create or delete local port and associated tport.
|
||||
*/
|
||||
int ft_lport_notify(struct notifier_block *nb, unsigned long event, void *arg)
|
||||
{
|
||||
struct fc_lport *lport = arg;
|
||||
|
||||
switch (event) {
|
||||
case FC_LPORT_EV_ADD:
|
||||
ft_lport_add(lport, NULL);
|
||||
break;
|
||||
case FC_LPORT_EV_DEL:
|
||||
ft_lport_del(lport, NULL);
|
||||
break;
|
||||
}
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Hash function for FC_IDs.
|
||||
*/
|
||||
static u32 ft_sess_hash(u32 port_id)
|
||||
{
|
||||
return hash_32(port_id, FT_SESS_HASH_BITS);
|
||||
}
|
||||
|
||||
/*
|
||||
* Find session in local port.
|
||||
* Sessions and hash lists are RCU-protected.
|
||||
* A reference is taken which must be eventually freed.
|
||||
*/
|
||||
static struct ft_sess *ft_sess_get(struct fc_lport *lport, u32 port_id)
|
||||
{
|
||||
struct ft_tport *tport;
|
||||
struct hlist_head *head;
|
||||
struct ft_sess *sess;
|
||||
|
||||
rcu_read_lock();
|
||||
tport = rcu_dereference(lport->prov[FC_TYPE_FCP]);
|
||||
if (!tport)
|
||||
goto out;
|
||||
|
||||
head = &tport->hash[ft_sess_hash(port_id)];
|
||||
hlist_for_each_entry_rcu(sess, head, hash) {
|
||||
if (sess->port_id == port_id) {
|
||||
kref_get(&sess->kref);
|
||||
rcu_read_unlock();
|
||||
pr_debug("port_id %x found %p\n", port_id, sess);
|
||||
return sess;
|
||||
}
|
||||
}
|
||||
out:
|
||||
rcu_read_unlock();
|
||||
pr_debug("port_id %x not found\n", port_id);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate session and enter it in the hash for the local port.
|
||||
* Caller holds ft_lport_lock.
|
||||
*/
|
||||
static struct ft_sess *ft_sess_create(struct ft_tport *tport, u32 port_id,
|
||||
struct ft_node_acl *acl)
|
||||
{
|
||||
struct ft_sess *sess;
|
||||
struct hlist_head *head;
|
||||
|
||||
head = &tport->hash[ft_sess_hash(port_id)];
|
||||
hlist_for_each_entry_rcu(sess, head, hash)
|
||||
if (sess->port_id == port_id)
|
||||
return sess;
|
||||
|
||||
sess = kzalloc(sizeof(*sess), GFP_KERNEL);
|
||||
if (!sess)
|
||||
return NULL;
|
||||
|
||||
sess->se_sess = transport_init_session_tags(TCM_FC_DEFAULT_TAGS,
|
||||
sizeof(struct ft_cmd),
|
||||
TARGET_PROT_NORMAL);
|
||||
if (IS_ERR(sess->se_sess)) {
|
||||
kfree(sess);
|
||||
return NULL;
|
||||
}
|
||||
sess->se_sess->se_node_acl = &acl->se_node_acl;
|
||||
sess->tport = tport;
|
||||
sess->port_id = port_id;
|
||||
kref_init(&sess->kref); /* ref for table entry */
|
||||
hlist_add_head_rcu(&sess->hash, head);
|
||||
tport->sess_count++;
|
||||
|
||||
pr_debug("port_id %x sess %p\n", port_id, sess);
|
||||
|
||||
transport_register_session(&tport->tpg->se_tpg, &acl->se_node_acl,
|
||||
sess->se_sess, sess);
|
||||
return sess;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unhash the session.
|
||||
* Caller holds ft_lport_lock.
|
||||
*/
|
||||
static void ft_sess_unhash(struct ft_sess *sess)
|
||||
{
|
||||
struct ft_tport *tport = sess->tport;
|
||||
|
||||
hlist_del_rcu(&sess->hash);
|
||||
BUG_ON(!tport->sess_count);
|
||||
tport->sess_count--;
|
||||
sess->port_id = -1;
|
||||
sess->params = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Delete session from hash.
|
||||
* Caller holds ft_lport_lock.
|
||||
*/
|
||||
static struct ft_sess *ft_sess_delete(struct ft_tport *tport, u32 port_id)
|
||||
{
|
||||
struct hlist_head *head;
|
||||
struct ft_sess *sess;
|
||||
|
||||
head = &tport->hash[ft_sess_hash(port_id)];
|
||||
hlist_for_each_entry_rcu(sess, head, hash) {
|
||||
if (sess->port_id == port_id) {
|
||||
ft_sess_unhash(sess);
|
||||
return sess;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Delete all sessions from tport.
|
||||
* Caller holds ft_lport_lock.
|
||||
*/
|
||||
static void ft_sess_delete_all(struct ft_tport *tport)
|
||||
{
|
||||
struct hlist_head *head;
|
||||
struct ft_sess *sess;
|
||||
|
||||
for (head = tport->hash;
|
||||
head < &tport->hash[FT_SESS_HASH_SIZE]; head++) {
|
||||
hlist_for_each_entry_rcu(sess, head, hash) {
|
||||
ft_sess_unhash(sess);
|
||||
transport_deregister_session_configfs(sess->se_sess);
|
||||
ft_sess_put(sess); /* release from table */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* TCM ops for sessions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Determine whether session is allowed to be shutdown in the current context.
|
||||
* Returns non-zero if the session should be shutdown.
|
||||
*/
|
||||
int ft_sess_shutdown(struct se_session *se_sess)
|
||||
{
|
||||
struct ft_sess *sess = se_sess->fabric_sess_ptr;
|
||||
|
||||
pr_debug("port_id %x\n", sess->port_id);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove session and send PRLO.
|
||||
* This is called when the ACL is being deleted or queue depth is changing.
|
||||
*/
|
||||
void ft_sess_close(struct se_session *se_sess)
|
||||
{
|
||||
struct ft_sess *sess = se_sess->fabric_sess_ptr;
|
||||
u32 port_id;
|
||||
|
||||
mutex_lock(&ft_lport_lock);
|
||||
port_id = sess->port_id;
|
||||
if (port_id == -1) {
|
||||
mutex_unlock(&ft_lport_lock);
|
||||
return;
|
||||
}
|
||||
pr_debug("port_id %x\n", port_id);
|
||||
ft_sess_unhash(sess);
|
||||
mutex_unlock(&ft_lport_lock);
|
||||
transport_deregister_session_configfs(se_sess);
|
||||
ft_sess_put(sess);
|
||||
/* XXX Send LOGO or PRLO */
|
||||
synchronize_rcu(); /* let transport deregister happen */
|
||||
}
|
||||
|
||||
u32 ft_sess_get_index(struct se_session *se_sess)
|
||||
{
|
||||
struct ft_sess *sess = se_sess->fabric_sess_ptr;
|
||||
|
||||
return sess->port_id; /* XXX TBD probably not what is needed */
|
||||
}
|
||||
|
||||
u32 ft_sess_get_port_name(struct se_session *se_sess,
|
||||
unsigned char *buf, u32 len)
|
||||
{
|
||||
struct ft_sess *sess = se_sess->fabric_sess_ptr;
|
||||
|
||||
return ft_format_wwn(buf, len, sess->port_name);
|
||||
}
|
||||
|
||||
/*
|
||||
* libfc ops involving sessions.
|
||||
*/
|
||||
|
||||
static int ft_prli_locked(struct fc_rport_priv *rdata, u32 spp_len,
|
||||
const struct fc_els_spp *rspp, struct fc_els_spp *spp)
|
||||
{
|
||||
struct ft_tport *tport;
|
||||
struct ft_sess *sess;
|
||||
struct ft_node_acl *acl;
|
||||
u32 fcp_parm;
|
||||
|
||||
tport = ft_tport_get(rdata->local_port);
|
||||
if (!tport)
|
||||
goto not_target; /* not a target for this local port */
|
||||
|
||||
acl = ft_acl_get(tport->tpg, rdata);
|
||||
if (!acl)
|
||||
goto not_target; /* no target for this remote */
|
||||
|
||||
if (!rspp)
|
||||
goto fill;
|
||||
|
||||
if (rspp->spp_flags & (FC_SPP_OPA_VAL | FC_SPP_RPA_VAL))
|
||||
return FC_SPP_RESP_NO_PA;
|
||||
|
||||
/*
|
||||
* If both target and initiator bits are off, the SPP is invalid.
|
||||
*/
|
||||
fcp_parm = ntohl(rspp->spp_params);
|
||||
if (!(fcp_parm & (FCP_SPPF_INIT_FCN | FCP_SPPF_TARG_FCN)))
|
||||
return FC_SPP_RESP_INVL;
|
||||
|
||||
/*
|
||||
* Create session (image pair) only if requested by
|
||||
* EST_IMG_PAIR flag and if the requestor is an initiator.
|
||||
*/
|
||||
if (rspp->spp_flags & FC_SPP_EST_IMG_PAIR) {
|
||||
spp->spp_flags |= FC_SPP_EST_IMG_PAIR;
|
||||
if (!(fcp_parm & FCP_SPPF_INIT_FCN))
|
||||
return FC_SPP_RESP_CONF;
|
||||
sess = ft_sess_create(tport, rdata->ids.port_id, acl);
|
||||
if (!sess)
|
||||
return FC_SPP_RESP_RES;
|
||||
if (!sess->params)
|
||||
rdata->prli_count++;
|
||||
sess->params = fcp_parm;
|
||||
sess->port_name = rdata->ids.port_name;
|
||||
sess->max_frame = rdata->maxframe_size;
|
||||
|
||||
/* XXX TBD - clearing actions. unit attn, see 4.10 */
|
||||
}
|
||||
|
||||
/*
|
||||
* OR in our service parameters with other provider (initiator), if any.
|
||||
*/
|
||||
fill:
|
||||
fcp_parm = ntohl(spp->spp_params);
|
||||
fcp_parm &= ~FCP_SPPF_RETRY;
|
||||
spp->spp_params = htonl(fcp_parm | FCP_SPPF_TARG_FCN);
|
||||
return FC_SPP_RESP_ACK;
|
||||
|
||||
not_target:
|
||||
fcp_parm = ntohl(spp->spp_params);
|
||||
fcp_parm &= ~FCP_SPPF_TARG_FCN;
|
||||
spp->spp_params = htonl(fcp_parm);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* tcm_fcp_prli() - Handle incoming or outgoing PRLI for the FCP target
|
||||
* @rdata: remote port private
|
||||
* @spp_len: service parameter page length
|
||||
* @rspp: received service parameter page (NULL for outgoing PRLI)
|
||||
* @spp: response service parameter page
|
||||
*
|
||||
* Returns spp response code.
|
||||
*/
|
||||
static int ft_prli(struct fc_rport_priv *rdata, u32 spp_len,
|
||||
const struct fc_els_spp *rspp, struct fc_els_spp *spp)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&ft_lport_lock);
|
||||
ret = ft_prli_locked(rdata, spp_len, rspp, spp);
|
||||
mutex_unlock(&ft_lport_lock);
|
||||
pr_debug("port_id %x flags %x ret %x\n",
|
||||
rdata->ids.port_id, rspp ? rspp->spp_flags : 0, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ft_sess_free(struct kref *kref)
|
||||
{
|
||||
struct ft_sess *sess = container_of(kref, struct ft_sess, kref);
|
||||
|
||||
transport_deregister_session(sess->se_sess);
|
||||
kfree_rcu(sess, rcu);
|
||||
}
|
||||
|
||||
void ft_sess_put(struct ft_sess *sess)
|
||||
{
|
||||
int sess_held = atomic_read(&sess->kref.refcount);
|
||||
|
||||
BUG_ON(!sess_held);
|
||||
kref_put(&sess->kref, ft_sess_free);
|
||||
}
|
||||
|
||||
static void ft_prlo(struct fc_rport_priv *rdata)
|
||||
{
|
||||
struct ft_sess *sess;
|
||||
struct ft_tport *tport;
|
||||
|
||||
mutex_lock(&ft_lport_lock);
|
||||
tport = rcu_dereference_protected(rdata->local_port->prov[FC_TYPE_FCP],
|
||||
lockdep_is_held(&ft_lport_lock));
|
||||
|
||||
if (!tport) {
|
||||
mutex_unlock(&ft_lport_lock);
|
||||
return;
|
||||
}
|
||||
sess = ft_sess_delete(tport, rdata->ids.port_id);
|
||||
if (!sess) {
|
||||
mutex_unlock(&ft_lport_lock);
|
||||
return;
|
||||
}
|
||||
mutex_unlock(&ft_lport_lock);
|
||||
transport_deregister_session_configfs(sess->se_sess);
|
||||
ft_sess_put(sess); /* release from table */
|
||||
rdata->prli_count--;
|
||||
/* XXX TBD - clearing actions. unit attn, see 4.10 */
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle incoming FCP request.
|
||||
* Caller has verified that the frame is type FCP.
|
||||
*/
|
||||
static void ft_recv(struct fc_lport *lport, struct fc_frame *fp)
|
||||
{
|
||||
struct ft_sess *sess;
|
||||
u32 sid = fc_frame_sid(fp);
|
||||
|
||||
pr_debug("sid %x\n", sid);
|
||||
|
||||
sess = ft_sess_get(lport, sid);
|
||||
if (!sess) {
|
||||
pr_debug("sid %x sess lookup failed\n", sid);
|
||||
/* TBD XXX - if FCP_CMND, send PRLO */
|
||||
fc_frame_free(fp);
|
||||
return;
|
||||
}
|
||||
ft_recv_req(sess, fp); /* must do ft_sess_put() */
|
||||
}
|
||||
|
||||
/*
|
||||
* Provider ops for libfc.
|
||||
*/
|
||||
struct fc4_prov ft_prov = {
|
||||
.prli = ft_prli,
|
||||
.prlo = ft_prlo,
|
||||
.recv = ft_recv,
|
||||
.module = THIS_MODULE,
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue