root/drivers/xen/xen-scsiback.c
/*
 * Xen SCSI backend driver
 *
 * Copyright (c) 2008, FUJITSU Limited
 *
 * Based on the blkback driver code.
 * Adaption to kernel taget core infrastructure taken from vhost/scsi.c
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation; or, when distributed
 * separately from the Linux kernel or incorporated into other
 * software packages, subject to the following license:
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this source file (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy, modify,
 * merge, publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#define pr_fmt(fmt) "xen-pvscsi: " fmt

#include <linux/module.h>
#include <linux/utsname.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/list.h>
#include <linux/gfp.h>
#include <linux/delay.h>
#include <linux/spinlock.h>
#include <linux/configfs.h>

#include <generated/utsrelease.h>

#include <scsi/scsi_host.h> /* SG_ALL */

#include <target/target_core_base.h>
#include <target/target_core_fabric.h>

#include <asm/hypervisor.h>

#include <xen/xen.h>
#include <xen/balloon.h>
#include <xen/events.h>
#include <xen/xenbus.h>
#include <xen/grant_table.h>
#include <xen/page.h>

#include <xen/interface/grant_table.h>
#include <xen/interface/io/vscsiif.h>

#define VSCSI_VERSION   "v0.1"
#define VSCSI_NAMELEN   32

struct ids_tuple {
        unsigned int hst;               /* host    */
        unsigned int chn;               /* channel */
        unsigned int tgt;               /* target  */
        unsigned int lun;               /* LUN     */
};

struct v2p_entry {
        struct ids_tuple v;             /* translate from */
        struct scsiback_tpg *tpg;       /* translate to   */
        unsigned int lun;
        struct kref kref;
        struct list_head l;
};

struct vscsibk_info {
        struct xenbus_device *dev;

        domid_t domid;
        unsigned int irq;

        struct vscsiif_back_ring ring;

        spinlock_t ring_lock;
        atomic_t nr_unreplied_reqs;

        spinlock_t v2p_lock;
        struct list_head v2p_entry_lists;

        wait_queue_head_t waiting_to_free;

        struct gnttab_page_cache free_pages;
};

/* theoretical maximum of grants for one request */
#define VSCSI_MAX_GRANTS        (SG_ALL + VSCSIIF_SG_TABLESIZE)

/*
 * VSCSI_GRANT_BATCH is the maximum number of grants to be processed in one
 * call to map/unmap grants. Don't choose it too large, as there are arrays
 * with VSCSI_GRANT_BATCH elements allocated on the stack.
 */
#define VSCSI_GRANT_BATCH       16

struct vscsibk_pend {
        uint16_t rqid;

        uint8_t cmnd[VSCSIIF_MAX_COMMAND_SIZE];
        uint8_t cmd_len;

        uint8_t sc_data_direction;
        uint16_t n_sg;          /* real length of SG list */
        uint16_t n_grants;      /* SG pages and potentially SG list */
        uint32_t data_len;
        uint32_t result;

        struct vscsibk_info *info;
        struct v2p_entry *v2p;
        struct scatterlist *sgl;

        uint8_t sense_buffer[VSCSIIF_SENSE_BUFFERSIZE];

        grant_handle_t grant_handles[VSCSI_MAX_GRANTS];
        struct page *pages[VSCSI_MAX_GRANTS];

        struct se_cmd se_cmd;

        struct completion tmr_done;
};

#define VSCSI_DEFAULT_SESSION_TAGS      128

struct scsiback_nexus {
        /* Pointer to TCM session for I_T Nexus */
        struct se_session *tvn_se_sess;
};

struct scsiback_tport {
        /* SCSI protocol the tport is providing */
        u8 tport_proto_id;
        /* Binary World Wide unique Port Name for pvscsi Target port */
        u64 tport_wwpn;
        /* ASCII formatted WWPN for pvscsi Target port */
        char tport_name[VSCSI_NAMELEN];
        /* Returned by scsiback_make_tport() */
        struct se_wwn tport_wwn;
};

struct scsiback_tpg {
        /* scsiback port target portal group tag for TCM */
        u16 tport_tpgt;
        /* track number of TPG Port/Lun Links wrt explicit I_T Nexus shutdown */
        int tv_tpg_port_count;
        /* xen-pvscsi references to tpg_nexus, protected by tv_tpg_mutex */
        int tv_tpg_fe_count;
        /* list for scsiback_list */
        struct list_head tv_tpg_list;
        /* Used to protect access for tpg_nexus */
        struct mutex tv_tpg_mutex;
        /* Pointer to the TCM pvscsi I_T Nexus for this TPG endpoint */
        struct scsiback_nexus *tpg_nexus;
        /* Pointer back to scsiback_tport */
        struct scsiback_tport *tport;
        /* Returned by scsiback_make_tpg() */
        struct se_portal_group se_tpg;
        /* alias used in xenstore */
        char param_alias[VSCSI_NAMELEN];
        /* list of info structures related to this target portal group */
        struct list_head info_list;
};

#define SCSIBACK_INVALID_HANDLE (~0)

static bool log_print_stat;
module_param(log_print_stat, bool, 0644);

static int scsiback_max_buffer_pages = 1024;
module_param_named(max_buffer_pages, scsiback_max_buffer_pages, int, 0644);
MODULE_PARM_DESC(max_buffer_pages,
"Maximum number of free pages to keep in backend buffer");

/* Global spinlock to protect scsiback TPG list */
static DEFINE_MUTEX(scsiback_mutex);
static LIST_HEAD(scsiback_list);

static void scsiback_get(struct vscsibk_info *info)
{
        atomic_inc(&info->nr_unreplied_reqs);
}

static void scsiback_put(struct vscsibk_info *info)
{
        if (atomic_dec_and_test(&info->nr_unreplied_reqs))
                wake_up(&info->waiting_to_free);
}

static unsigned long vaddr_page(struct page *page)
{
        unsigned long pfn = page_to_pfn(page);

        return (unsigned long)pfn_to_kaddr(pfn);
}

static unsigned long vaddr(struct vscsibk_pend *req, int seg)
{
        return vaddr_page(req->pages[seg]);
}

static void scsiback_print_status(char *sense_buffer, int errors,
                                        struct vscsibk_pend *pending_req)
{
        struct scsiback_tpg *tpg = pending_req->v2p->tpg;

        pr_err("[%s:%d] cmnd[0]=%02x -> st=%02x msg=%02x host=%02x\n",
               tpg->tport->tport_name, pending_req->v2p->lun,
               pending_req->cmnd[0], errors & 0xff, COMMAND_COMPLETE,
               host_byte(errors));
}

static void scsiback_fast_flush_area(struct vscsibk_pend *req)
{
        struct gnttab_unmap_grant_ref unmap[VSCSI_GRANT_BATCH];
        struct page *pages[VSCSI_GRANT_BATCH];
        unsigned int i, invcount = 0;
        grant_handle_t handle;
        int err;

        kfree(req->sgl);
        req->sgl = NULL;
        req->n_sg = 0;

        if (!req->n_grants)
                return;

        for (i = 0; i < req->n_grants; i++) {
                handle = req->grant_handles[i];
                if (handle == SCSIBACK_INVALID_HANDLE)
                        continue;
                gnttab_set_unmap_op(&unmap[invcount], vaddr(req, i),
                                    GNTMAP_host_map, handle);
                req->grant_handles[i] = SCSIBACK_INVALID_HANDLE;
                pages[invcount] = req->pages[i];
                put_page(pages[invcount]);
                invcount++;
                if (invcount < VSCSI_GRANT_BATCH)
                        continue;
                err = gnttab_unmap_refs(unmap, NULL, pages, invcount);
                BUG_ON(err);
                invcount = 0;
        }

        if (invcount) {
                err = gnttab_unmap_refs(unmap, NULL, pages, invcount);
                BUG_ON(err);
        }

        gnttab_page_cache_put(&req->info->free_pages, req->pages,
                              req->n_grants);
        req->n_grants = 0;
}

static void scsiback_free_translation_entry(struct kref *kref)
{
        struct v2p_entry *entry = container_of(kref, struct v2p_entry, kref);
        struct scsiback_tpg *tpg = entry->tpg;

        mutex_lock(&tpg->tv_tpg_mutex);
        tpg->tv_tpg_fe_count--;
        mutex_unlock(&tpg->tv_tpg_mutex);

        kfree(entry);
}

static int32_t scsiback_result(int32_t result)
{
        int32_t host_status;

        switch (XEN_VSCSIIF_RSLT_HOST(result)) {
        case DID_OK:
                host_status = XEN_VSCSIIF_RSLT_HOST_OK;
                break;
        case DID_NO_CONNECT:
                host_status = XEN_VSCSIIF_RSLT_HOST_NO_CONNECT;
                break;
        case DID_BUS_BUSY:
                host_status = XEN_VSCSIIF_RSLT_HOST_BUS_BUSY;
                break;
        case DID_TIME_OUT:
                host_status = XEN_VSCSIIF_RSLT_HOST_TIME_OUT;
                break;
        case DID_BAD_TARGET:
                host_status = XEN_VSCSIIF_RSLT_HOST_BAD_TARGET;
                break;
        case DID_ABORT:
                host_status = XEN_VSCSIIF_RSLT_HOST_ABORT;
                break;
        case DID_PARITY:
                host_status = XEN_VSCSIIF_RSLT_HOST_PARITY;
                break;
        case DID_ERROR:
                host_status = XEN_VSCSIIF_RSLT_HOST_ERROR;
                break;
        case DID_RESET:
                host_status = XEN_VSCSIIF_RSLT_HOST_RESET;
                break;
        case DID_BAD_INTR:
                host_status = XEN_VSCSIIF_RSLT_HOST_BAD_INTR;
                break;
        case DID_PASSTHROUGH:
                host_status = XEN_VSCSIIF_RSLT_HOST_PASSTHROUGH;
                break;
        case DID_SOFT_ERROR:
                host_status = XEN_VSCSIIF_RSLT_HOST_SOFT_ERROR;
                break;
        case DID_IMM_RETRY:
                host_status = XEN_VSCSIIF_RSLT_HOST_IMM_RETRY;
                break;
        case DID_REQUEUE:
                host_status = XEN_VSCSIIF_RSLT_HOST_REQUEUE;
                break;
        case DID_TRANSPORT_DISRUPTED:
                host_status = XEN_VSCSIIF_RSLT_HOST_TRANSPORT_DISRUPTED;
                break;
        case DID_TRANSPORT_FAILFAST:
                host_status = XEN_VSCSIIF_RSLT_HOST_TRANSPORT_FAILFAST;
                break;
        case DID_TRANSPORT_MARGINAL:
                host_status = XEN_VSCSIIF_RSLT_HOST_TRANSPORT_MARGINAL;
                break;
        default:
                host_status = XEN_VSCSIIF_RSLT_HOST_ERROR;
                break;
        }

        return (host_status << 16) | (result & 0x00ffff);
}

static void scsiback_send_response(struct vscsibk_info *info,
                        char *sense_buffer, int32_t result, uint32_t resid,
                        uint16_t rqid)
{
        struct vscsiif_response *ring_res;
        int notify;
        struct scsi_sense_hdr sshdr;
        unsigned long flags;
        unsigned len;

        spin_lock_irqsave(&info->ring_lock, flags);

        ring_res = RING_GET_RESPONSE(&info->ring, info->ring.rsp_prod_pvt);
        info->ring.rsp_prod_pvt++;

        ring_res->rslt   = scsiback_result(result);
        ring_res->rqid   = rqid;

        if (sense_buffer != NULL &&
            scsi_normalize_sense(sense_buffer, VSCSIIF_SENSE_BUFFERSIZE,
                                 &sshdr)) {
                len = min_t(unsigned, 8 + sense_buffer[7],
                            VSCSIIF_SENSE_BUFFERSIZE);
                memcpy(ring_res->sense_buffer, sense_buffer, len);
                ring_res->sense_len = len;
        } else {
                ring_res->sense_len = 0;
        }

        ring_res->residual_len = resid;

        RING_PUSH_RESPONSES_AND_CHECK_NOTIFY(&info->ring, notify);
        spin_unlock_irqrestore(&info->ring_lock, flags);

        if (notify)
                notify_remote_via_irq(info->irq);
}

static void scsiback_do_resp_with_sense(char *sense_buffer, int32_t result,
                        uint32_t resid, struct vscsibk_pend *pending_req)
{
        scsiback_send_response(pending_req->info, sense_buffer, result,
                               resid, pending_req->rqid);

        if (pending_req->v2p)
                kref_put(&pending_req->v2p->kref,
                         scsiback_free_translation_entry);
}

static void scsiback_cmd_done(struct vscsibk_pend *pending_req)
{
        struct vscsibk_info *info = pending_req->info;
        unsigned char *sense_buffer;
        unsigned int resid;
        int errors;

        sense_buffer = pending_req->sense_buffer;
        resid        = pending_req->se_cmd.residual_count;
        errors       = pending_req->result;

        if (errors && log_print_stat)
                scsiback_print_status(sense_buffer, errors, pending_req);

        scsiback_fast_flush_area(pending_req);
        scsiback_do_resp_with_sense(sense_buffer, errors, resid, pending_req);
        scsiback_put(info);
        /*
         * Drop the extra KREF_ACK reference taken by target_submit_cmd_map_sgls()
         * ahead of scsiback_check_stop_free() ->  transport_generic_free_cmd()
         * final se_cmd->cmd_kref put.
         */
        target_put_sess_cmd(&pending_req->se_cmd);
}

static void scsiback_cmd_exec(struct vscsibk_pend *pending_req)
{
        struct se_cmd *se_cmd = &pending_req->se_cmd;
        struct se_session *sess = pending_req->v2p->tpg->tpg_nexus->tvn_se_sess;

        scsiback_get(pending_req->info);
        se_cmd->tag = pending_req->rqid;
        target_init_cmd(se_cmd, sess, pending_req->sense_buffer,
                        pending_req->v2p->lun, pending_req->data_len, 0,
                        pending_req->sc_data_direction, TARGET_SCF_ACK_KREF);

        if (target_submit_prep(se_cmd, pending_req->cmnd, pending_req->sgl,
                               pending_req->n_sg, NULL, 0, NULL, 0, GFP_KERNEL))
                return;

        target_submit(se_cmd);
}

static int scsiback_gnttab_data_map_batch(struct gnttab_map_grant_ref *map,
        struct page **pg, grant_handle_t *grant, int cnt)
{
        int err, i;

        if (!cnt)
                return 0;

        err = gnttab_map_refs(map, NULL, pg, cnt);
        for (i = 0; i < cnt; i++) {
                if (unlikely(map[i].status != GNTST_okay)) {
                        pr_err("invalid buffer -- could not remap it\n");
                        map[i].handle = SCSIBACK_INVALID_HANDLE;
                        if (!err)
                                err = -ENOMEM;
                } else {
                        get_page(pg[i]);
                }
                grant[i] = map[i].handle;
        }
        return err;
}

static int scsiback_gnttab_data_map_list(struct vscsibk_pend *pending_req,
                        struct scsiif_request_segment *seg, struct page **pg,
                        grant_handle_t *grant, int cnt, u32 flags)
{
        int mapcount = 0, i, err = 0;
        struct gnttab_map_grant_ref map[VSCSI_GRANT_BATCH];
        struct vscsibk_info *info = pending_req->info;

        for (i = 0; i < cnt; i++) {
                if (gnttab_page_cache_get(&info->free_pages, pg + mapcount)) {
                        gnttab_page_cache_put(&info->free_pages, pg, mapcount);
                        pr_err("no grant page\n");
                        return -ENOMEM;
                }
                gnttab_set_map_op(&map[mapcount], vaddr_page(pg[mapcount]),
                                  flags, seg[i].gref, info->domid);
                mapcount++;
                if (mapcount < VSCSI_GRANT_BATCH)
                        continue;
                err = scsiback_gnttab_data_map_batch(map, pg, grant, mapcount);
                pg += mapcount;
                grant += mapcount;
                pending_req->n_grants += mapcount;
                if (err)
                        return err;
                mapcount = 0;
        }
        err = scsiback_gnttab_data_map_batch(map, pg, grant, mapcount);
        pending_req->n_grants += mapcount;
        return err;
}

static int scsiback_gnttab_data_map(struct vscsiif_request *ring_req,
                                        struct vscsibk_pend *pending_req)
{
        u32 flags;
        int i, err, n_segs, i_seg = 0;
        struct page **pg;
        struct scsiif_request_segment *seg;
        unsigned long end_seg = 0;
        unsigned int nr_segments = (unsigned int)ring_req->nr_segments;
        unsigned int nr_sgl = 0;
        struct scatterlist *sg;
        grant_handle_t *grant;

        pending_req->n_sg = 0;
        pending_req->n_grants = 0;
        pending_req->data_len = 0;

        nr_segments &= ~VSCSIIF_SG_GRANT;
        if (!nr_segments)
                return 0;

        if (nr_segments > VSCSIIF_SG_TABLESIZE) {
                pr_debug("invalid parameter nr_seg = %d\n",
                        ring_req->nr_segments);
                return -EINVAL;
        }

        if (ring_req->nr_segments & VSCSIIF_SG_GRANT) {
                err = scsiback_gnttab_data_map_list(pending_req, ring_req->seg,
                        pending_req->pages, pending_req->grant_handles,
                        nr_segments, GNTMAP_host_map | GNTMAP_readonly);
                if (err)
                        return err;
                nr_sgl = nr_segments;
                nr_segments = 0;
                for (i = 0; i < nr_sgl; i++) {
                        n_segs = ring_req->seg[i].length /
                                 sizeof(struct scsiif_request_segment);
                        if ((unsigned)ring_req->seg[i].offset +
                            (unsigned)ring_req->seg[i].length > PAGE_SIZE ||
                            n_segs * sizeof(struct scsiif_request_segment) !=
                            ring_req->seg[i].length)
                                return -EINVAL;
                        nr_segments += n_segs;
                }
                if (nr_segments > SG_ALL) {
                        pr_debug("invalid nr_seg = %d\n", nr_segments);
                        return -EINVAL;
                }
        }

        /* free of (sgl) in fast_flush_area() */
        pending_req->sgl = kmalloc_objs(struct scatterlist, nr_segments);
        if (!pending_req->sgl)
                return -ENOMEM;

        sg_init_table(pending_req->sgl, nr_segments);
        pending_req->n_sg = nr_segments;

        flags = GNTMAP_host_map;
        if (pending_req->sc_data_direction == DMA_TO_DEVICE)
                flags |= GNTMAP_readonly;

        pg = pending_req->pages + nr_sgl;
        grant = pending_req->grant_handles + nr_sgl;
        if (!nr_sgl) {
                seg = ring_req->seg;
                err = scsiback_gnttab_data_map_list(pending_req, seg,
                        pg, grant, nr_segments, flags);
                if (err)
                        return err;
        } else {
                for (i = 0; i < nr_sgl; i++) {
                        seg = (struct scsiif_request_segment *)(
                              vaddr(pending_req, i) + ring_req->seg[i].offset);
                        n_segs = ring_req->seg[i].length /
                                 sizeof(struct scsiif_request_segment);
                        err = scsiback_gnttab_data_map_list(pending_req, seg,
                                pg, grant, n_segs, flags);
                        if (err)
                                return err;
                        pg += n_segs;
                        grant += n_segs;
                }
                end_seg = vaddr(pending_req, 0) + ring_req->seg[0].offset;
                seg = (struct scsiif_request_segment *)end_seg;
                end_seg += ring_req->seg[0].length;
                pg = pending_req->pages + nr_sgl;
        }

        for_each_sg(pending_req->sgl, sg, nr_segments, i) {
                sg_set_page(sg, pg[i], seg->length, seg->offset);
                pending_req->data_len += seg->length;
                seg++;
                if (nr_sgl && (unsigned long)seg >= end_seg) {
                        i_seg++;
                        end_seg = vaddr(pending_req, i_seg) +
                                  ring_req->seg[i_seg].offset;
                        seg = (struct scsiif_request_segment *)end_seg;
                        end_seg += ring_req->seg[i_seg].length;
                }
                if (sg->offset >= PAGE_SIZE ||
                    sg->length > PAGE_SIZE ||
                    sg->offset + sg->length > PAGE_SIZE)
                        return -EINVAL;
        }

        return 0;
}

static void scsiback_disconnect(struct vscsibk_info *info)
{
        wait_event(info->waiting_to_free,
                atomic_read(&info->nr_unreplied_reqs) == 0);

        unbind_from_irqhandler(info->irq, info);
        info->irq = 0;
        xenbus_unmap_ring_vfree(info->dev, info->ring.sring);
}

static void scsiback_device_action(struct vscsibk_pend *pending_req,
        enum tcm_tmreq_table act, int tag)
{
        struct scsiback_tpg *tpg = pending_req->v2p->tpg;
        struct scsiback_nexus *nexus = tpg->tpg_nexus;
        struct se_cmd *se_cmd = &pending_req->se_cmd;
        u64 unpacked_lun = pending_req->v2p->lun;
        int rc, err = XEN_VSCSIIF_RSLT_RESET_FAILED;

        init_completion(&pending_req->tmr_done);

        rc = target_submit_tmr(&pending_req->se_cmd, nexus->tvn_se_sess,
                               &pending_req->sense_buffer[0],
                               unpacked_lun, NULL, act, GFP_KERNEL,
                               tag, TARGET_SCF_ACK_KREF);
        if (rc)
                goto err;

        wait_for_completion(&pending_req->tmr_done);

        err = (se_cmd->se_tmr_req->response == TMR_FUNCTION_COMPLETE) ?
                XEN_VSCSIIF_RSLT_RESET_SUCCESS : XEN_VSCSIIF_RSLT_RESET_FAILED;

        scsiback_do_resp_with_sense(NULL, err, 0, pending_req);
        transport_generic_free_cmd(&pending_req->se_cmd, 0);
        return;

err:
        scsiback_do_resp_with_sense(NULL, err, 0, pending_req);
}

/*
  Perform virtual to physical translation
*/
static struct v2p_entry *scsiback_do_translation(struct vscsibk_info *info,
                        struct ids_tuple *v)
{
        struct v2p_entry *entry;
        struct list_head *head = &(info->v2p_entry_lists);
        unsigned long flags;

        spin_lock_irqsave(&info->v2p_lock, flags);
        list_for_each_entry(entry, head, l) {
                if ((entry->v.chn == v->chn) &&
                    (entry->v.tgt == v->tgt) &&
                    (entry->v.lun == v->lun)) {
                        kref_get(&entry->kref);
                        goto out;
                }
        }
        entry = NULL;

out:
        spin_unlock_irqrestore(&info->v2p_lock, flags);
        return entry;
}

static struct vscsibk_pend *scsiback_get_pend_req(struct vscsiif_back_ring *ring,
                                struct v2p_entry *v2p)
{
        struct scsiback_tpg *tpg = v2p->tpg;
        struct scsiback_nexus *nexus = tpg->tpg_nexus;
        struct se_session *se_sess = nexus->tvn_se_sess;
        struct vscsibk_pend *req;
        int tag, cpu, i;

        tag = sbitmap_queue_get(&se_sess->sess_tag_pool, &cpu);
        if (tag < 0) {
                pr_err("Unable to obtain tag for vscsiif_request\n");
                return ERR_PTR(-ENOMEM);
        }

        req = &((struct vscsibk_pend *)se_sess->sess_cmd_map)[tag];
        memset(req, 0, sizeof(*req));
        req->se_cmd.map_tag = tag;
        req->se_cmd.map_cpu = cpu;

        for (i = 0; i < VSCSI_MAX_GRANTS; i++)
                req->grant_handles[i] = SCSIBACK_INVALID_HANDLE;

        return req;
}

static struct vscsibk_pend *prepare_pending_reqs(struct vscsibk_info *info,
                                struct vscsiif_back_ring *ring,
                                struct vscsiif_request *ring_req)
{
        struct vscsibk_pend *pending_req;
        struct v2p_entry *v2p;
        struct ids_tuple vir;

        /* request range check from frontend */
        if ((ring_req->sc_data_direction != DMA_BIDIRECTIONAL) &&
                (ring_req->sc_data_direction != DMA_TO_DEVICE) &&
                (ring_req->sc_data_direction != DMA_FROM_DEVICE) &&
                (ring_req->sc_data_direction != DMA_NONE)) {
                pr_debug("invalid parameter data_dir = %d\n",
                        ring_req->sc_data_direction);
                return ERR_PTR(-EINVAL);
        }
        if (ring_req->cmd_len > VSCSIIF_MAX_COMMAND_SIZE) {
                pr_debug("invalid parameter cmd_len = %d\n",
                        ring_req->cmd_len);
                return ERR_PTR(-EINVAL);
        }

        vir.chn = ring_req->channel;
        vir.tgt = ring_req->id;
        vir.lun = ring_req->lun;

        v2p = scsiback_do_translation(info, &vir);
        if (!v2p) {
                pr_debug("the v2p of (chn:%d, tgt:%d, lun:%d) doesn't exist.\n",
                         vir.chn, vir.tgt, vir.lun);
                return ERR_PTR(-ENODEV);
        }

        pending_req = scsiback_get_pend_req(ring, v2p);
        if (IS_ERR(pending_req)) {
                kref_put(&v2p->kref, scsiback_free_translation_entry);
                return ERR_PTR(-ENOMEM);
        }
        pending_req->rqid = ring_req->rqid;
        pending_req->info = info;
        pending_req->v2p = v2p;
        pending_req->sc_data_direction = ring_req->sc_data_direction;
        pending_req->cmd_len = ring_req->cmd_len;
        memcpy(pending_req->cmnd, ring_req->cmnd, pending_req->cmd_len);

        return pending_req;
}

static int scsiback_do_cmd_fn(struct vscsibk_info *info,
                              unsigned int *eoi_flags)
{
        struct vscsiif_back_ring *ring = &info->ring;
        struct vscsiif_request ring_req;
        struct vscsibk_pend *pending_req;
        RING_IDX rc, rp;
        int more_to_do;
        uint32_t result;

        rc = ring->req_cons;
        rp = ring->sring->req_prod;
        rmb();  /* guest system is accessing ring, too */

        if (RING_REQUEST_PROD_OVERFLOW(ring, rp)) {
                rc = ring->rsp_prod_pvt;
                pr_warn("Dom%d provided bogus ring requests (%#x - %#x = %u). Halting ring processing\n",
                           info->domid, rp, rc, rp - rc);
                return -EINVAL;
        }

        while ((rc != rp)) {
                *eoi_flags &= ~XEN_EOI_FLAG_SPURIOUS;

                if (RING_REQUEST_CONS_OVERFLOW(ring, rc))
                        break;

                RING_COPY_REQUEST(ring, rc, &ring_req);
                ring->req_cons = ++rc;

                pending_req = prepare_pending_reqs(info, ring, &ring_req);
                if (IS_ERR(pending_req)) {
                        switch (PTR_ERR(pending_req)) {
                        case -ENODEV:
                                result = DID_NO_CONNECT;
                                break;
                        default:
                                result = DID_ERROR;
                                break;
                        }
                        scsiback_send_response(info, NULL, result << 16, 0,
                                               ring_req.rqid);
                        return 1;
                }

                switch (ring_req.act) {
                case VSCSIIF_ACT_SCSI_CDB:
                        if (scsiback_gnttab_data_map(&ring_req, pending_req)) {
                                scsiback_fast_flush_area(pending_req);
                                scsiback_do_resp_with_sense(NULL,
                                                DID_ERROR << 16, 0, pending_req);
                                transport_generic_free_cmd(&pending_req->se_cmd, 0);
                        } else {
                                scsiback_cmd_exec(pending_req);
                        }
                        break;
                case VSCSIIF_ACT_SCSI_ABORT:
                        scsiback_device_action(pending_req, TMR_ABORT_TASK,
                                ring_req.ref_rqid);
                        break;
                case VSCSIIF_ACT_SCSI_RESET:
                        scsiback_device_action(pending_req, TMR_LUN_RESET, 0);
                        break;
                default:
                        pr_err_ratelimited("invalid request\n");
                        scsiback_do_resp_with_sense(NULL, DID_ERROR << 16, 0,
                                                    pending_req);
                        transport_generic_free_cmd(&pending_req->se_cmd, 0);
                        break;
                }

                /* Yield point for this unbounded loop. */
                cond_resched();
        }

        gnttab_page_cache_shrink(&info->free_pages, scsiback_max_buffer_pages);

        RING_FINAL_CHECK_FOR_REQUESTS(&info->ring, more_to_do);
        return more_to_do;
}

static irqreturn_t scsiback_irq_fn(int irq, void *dev_id)
{
        struct vscsibk_info *info = dev_id;
        int rc;
        unsigned int eoi_flags = XEN_EOI_FLAG_SPURIOUS;

        while ((rc = scsiback_do_cmd_fn(info, &eoi_flags)) > 0)
                cond_resched();

        /* In case of a ring error we keep the event channel masked. */
        if (!rc)
                xen_irq_lateeoi(irq, eoi_flags);

        return IRQ_HANDLED;
}

static int scsiback_init_sring(struct vscsibk_info *info, grant_ref_t ring_ref,
                        evtchn_port_t evtchn)
{
        void *area;
        struct vscsiif_sring *sring;
        int err;

        if (info->irq)
                return -1;

        err = xenbus_map_ring_valloc(info->dev, &ring_ref, 1, &area);
        if (err)
                return err;

        sring = (struct vscsiif_sring *)area;
        BACK_RING_INIT(&info->ring, sring, PAGE_SIZE);

        err = bind_interdomain_evtchn_to_irq_lateeoi(info->dev, evtchn);
        if (err < 0)
                goto unmap_page;

        info->irq = err;

        err = request_threaded_irq(info->irq, NULL, scsiback_irq_fn,
                                   IRQF_ONESHOT, "vscsiif-backend", info);
        if (err)
                goto free_irq;

        return 0;

free_irq:
        unbind_from_irqhandler(info->irq, info);
        info->irq = 0;
unmap_page:
        xenbus_unmap_ring_vfree(info->dev, area);

        return err;
}

static int scsiback_map(struct vscsibk_info *info)
{
        struct xenbus_device *dev = info->dev;
        unsigned int ring_ref;
        evtchn_port_t evtchn;
        int err;

        err = xenbus_gather(XBT_NIL, dev->otherend,
                        "ring-ref", "%u", &ring_ref,
                        "event-channel", "%u", &evtchn, NULL);
        if (err) {
                xenbus_dev_fatal(dev, err, "reading %s ring", dev->otherend);
                return err;
        }

        return scsiback_init_sring(info, ring_ref, evtchn);
}

/*
  Check for a translation entry being present
*/
static struct v2p_entry *scsiback_chk_translation_entry(
        struct vscsibk_info *info, struct ids_tuple *v)
{
        struct list_head *head = &(info->v2p_entry_lists);
        struct v2p_entry *entry;

        list_for_each_entry(entry, head, l)
                if ((entry->v.chn == v->chn) &&
                    (entry->v.tgt == v->tgt) &&
                    (entry->v.lun == v->lun))
                        return entry;

        return NULL;
}

/*
  Add a new translation entry
*/
static int scsiback_add_translation_entry(struct vscsibk_info *info,
                                          char *phy, struct ids_tuple *v)
{
        int err = 0;
        struct v2p_entry *new;
        unsigned long flags;
        char *lunp;
        unsigned long long unpacked_lun;
        struct se_lun *se_lun;
        struct scsiback_tpg *tpg_entry, *tpg = NULL;
        char *error = "doesn't exist";

        lunp = strrchr(phy, ':');
        if (!lunp) {
                pr_err("illegal format of physical device %s\n", phy);
                return -EINVAL;
        }
        *lunp = 0;
        lunp++;
        err = kstrtoull(lunp, 10, &unpacked_lun);
        if (err < 0) {
                pr_err("lun number not valid: %s\n", lunp);
                return err;
        }

        mutex_lock(&scsiback_mutex);
        list_for_each_entry(tpg_entry, &scsiback_list, tv_tpg_list) {
                if (!strcmp(phy, tpg_entry->tport->tport_name) ||
                    !strcmp(phy, tpg_entry->param_alias)) {
                        mutex_lock(&tpg_entry->se_tpg.tpg_lun_mutex);
                        hlist_for_each_entry(se_lun, &tpg_entry->se_tpg.tpg_lun_hlist, link) {
                                if (se_lun->unpacked_lun == unpacked_lun) {
                                        if (!tpg_entry->tpg_nexus)
                                                error = "nexus undefined";
                                        else
                                                tpg = tpg_entry;
                                        break;
                                }
                        }
                        mutex_unlock(&tpg_entry->se_tpg.tpg_lun_mutex);
                        break;
                }
        }
        if (tpg) {
                mutex_lock(&tpg->tv_tpg_mutex);
                tpg->tv_tpg_fe_count++;
                mutex_unlock(&tpg->tv_tpg_mutex);
        }
        mutex_unlock(&scsiback_mutex);

        if (!tpg) {
                pr_err("%s:%llu %s\n", phy, unpacked_lun, error);
                return -ENODEV;
        }

        new = kmalloc_obj(struct v2p_entry);
        if (new == NULL) {
                err = -ENOMEM;
                goto out_free;
        }

        spin_lock_irqsave(&info->v2p_lock, flags);

        /* Check double assignment to identical virtual ID */
        if (scsiback_chk_translation_entry(info, v)) {
                pr_warn("Virtual ID is already used. Assignment was not performed.\n");
                err = -EEXIST;
                goto out;
        }

        /* Create a new translation entry and add to the list */
        kref_init(&new->kref);
        new->v = *v;
        new->tpg = tpg;
        new->lun = unpacked_lun;
        list_add_tail(&new->l, &info->v2p_entry_lists);

out:
        spin_unlock_irqrestore(&info->v2p_lock, flags);

out_free:
        if (err) {
                mutex_lock(&tpg->tv_tpg_mutex);
                tpg->tv_tpg_fe_count--;
                mutex_unlock(&tpg->tv_tpg_mutex);
                kfree(new);
        }

        return err;
}

/*
  Delete the translation entry specified
*/
static int scsiback_del_translation_entry(struct vscsibk_info *info,
                                          struct ids_tuple *v)
{
        struct v2p_entry *entry;
        unsigned long flags;

        spin_lock_irqsave(&info->v2p_lock, flags);
        /* Find out the translation entry specified */
        entry = scsiback_chk_translation_entry(info, v);
        if (entry)
                list_del(&entry->l);

        spin_unlock_irqrestore(&info->v2p_lock, flags);

        if (!entry)
                return -ENOENT;

        kref_put(&entry->kref, scsiback_free_translation_entry);
        return 0;
}

static void scsiback_do_add_lun(struct vscsibk_info *info, const char *state,
                                char *phy, struct ids_tuple *vir, int try)
{
        struct v2p_entry *entry;
        unsigned long flags;
        int err;

        if (try) {
                spin_lock_irqsave(&info->v2p_lock, flags);
                entry = scsiback_chk_translation_entry(info, vir);
                spin_unlock_irqrestore(&info->v2p_lock, flags);
                if (entry)
                        return;
        }
        if (!scsiback_add_translation_entry(info, phy, vir)) {
                if (xenbus_printf(XBT_NIL, info->dev->nodename, state,
                                  "%d", XenbusStateInitialised)) {
                        pr_err("xenbus_printf error %s\n", state);
                        scsiback_del_translation_entry(info, vir);
                }
        } else if (!try) {
                err = xenbus_printf(XBT_NIL, info->dev->nodename, state,
                              "%d", XenbusStateClosed);
                if (err)
                        xenbus_dev_error(info->dev, err,
                                "%s: writing %s", __func__, state);
        }
}

static void scsiback_do_del_lun(struct vscsibk_info *info, const char *state,
                                struct ids_tuple *vir)
{
        if (!scsiback_del_translation_entry(info, vir)) {
                if (xenbus_printf(XBT_NIL, info->dev->nodename, state,
                                  "%d", XenbusStateClosed))
                        pr_err("xenbus_printf error %s\n", state);
        }
}

#define VSCSIBACK_OP_ADD_OR_DEL_LUN     1
#define VSCSIBACK_OP_UPDATEDEV_STATE    2

static void scsiback_do_1lun_hotplug(struct vscsibk_info *info, int op,
                                     char *ent)
{
        int err;
        struct ids_tuple vir;
        char *val;
        int device_state;
        char phy[VSCSI_NAMELEN];
        char str[64];
        char state[64];
        struct xenbus_device *dev = info->dev;

        /* read status */
        snprintf(state, sizeof(state), "vscsi-devs/%s/state", ent);
        err = xenbus_scanf(XBT_NIL, dev->nodename, state, "%u", &device_state);
        if (XENBUS_EXIST_ERR(err))
                return;

        /* physical SCSI device */
        snprintf(str, sizeof(str), "vscsi-devs/%s/p-dev", ent);
        val = xenbus_read(XBT_NIL, dev->nodename, str, NULL);
        if (IS_ERR(val)) {
                err = xenbus_printf(XBT_NIL, dev->nodename, state,
                              "%d", XenbusStateClosed);
                if (err)
                        xenbus_dev_error(info->dev, err,
                                "%s: writing %s", __func__, state);
                return;
        }
        strscpy(phy, val, VSCSI_NAMELEN);
        kfree(val);

        /* virtual SCSI device */
        snprintf(str, sizeof(str), "vscsi-devs/%s/v-dev", ent);
        err = xenbus_scanf(XBT_NIL, dev->nodename, str, "%u:%u:%u:%u",
                           &vir.hst, &vir.chn, &vir.tgt, &vir.lun);
        if (XENBUS_EXIST_ERR(err)) {
                err = xenbus_printf(XBT_NIL, dev->nodename, state,
                              "%d", XenbusStateClosed);
                if (err)
                        xenbus_dev_error(info->dev, err,
                                "%s: writing %s", __func__, state);
                return;
        }

        switch (op) {
        case VSCSIBACK_OP_ADD_OR_DEL_LUN:
                switch (device_state) {
                case XenbusStateInitialising:
                        scsiback_do_add_lun(info, state, phy, &vir, 0);
                        break;
                case XenbusStateConnected:
                        scsiback_do_add_lun(info, state, phy, &vir, 1);
                        break;
                case XenbusStateClosing:
                        scsiback_do_del_lun(info, state, &vir);
                        break;
                default:
                        break;
                }
                break;

        case VSCSIBACK_OP_UPDATEDEV_STATE:
                if (device_state == XenbusStateInitialised) {
                        /* modify vscsi-devs/dev-x/state */
                        if (xenbus_printf(XBT_NIL, dev->nodename, state,
                                          "%d", XenbusStateConnected)) {
                                pr_err("xenbus_printf error %s\n", str);
                                scsiback_del_translation_entry(info, &vir);
                                xenbus_printf(XBT_NIL, dev->nodename, state,
                                              "%d", XenbusStateClosed);
                        }
                }
                break;
        /* When it is necessary, processing is added here. */
        default:
                break;
        }
}

static void scsiback_do_lun_hotplug(struct vscsibk_info *info, int op)
{
        int i;
        char **dir;
        unsigned int ndir = 0;

        dir = xenbus_directory(XBT_NIL, info->dev->nodename, "vscsi-devs",
                               &ndir);
        if (IS_ERR(dir))
                return;

        for (i = 0; i < ndir; i++)
                scsiback_do_1lun_hotplug(info, op, dir[i]);

        kfree(dir);
}

static void scsiback_frontend_changed(struct xenbus_device *dev,
                                        enum xenbus_state frontend_state)
{
        struct vscsibk_info *info = dev_get_drvdata(&dev->dev);

        switch (frontend_state) {
        case XenbusStateInitialising:
                break;

        case XenbusStateInitialised:
                if (scsiback_map(info))
                        break;

                scsiback_do_lun_hotplug(info, VSCSIBACK_OP_ADD_OR_DEL_LUN);
                xenbus_switch_state(dev, XenbusStateConnected);
                break;

        case XenbusStateConnected:
                scsiback_do_lun_hotplug(info, VSCSIBACK_OP_UPDATEDEV_STATE);

                if (dev->state == XenbusStateConnected)
                        break;

                xenbus_switch_state(dev, XenbusStateConnected);
                break;

        case XenbusStateClosing:
                if (info->irq)
                        scsiback_disconnect(info);

                xenbus_switch_state(dev, XenbusStateClosing);
                break;

        case XenbusStateClosed:
                xenbus_switch_state(dev, XenbusStateClosed);
                if (xenbus_dev_is_online(dev))
                        break;
                fallthrough;    /* if not online */
        case XenbusStateUnknown:
                device_unregister(&dev->dev);
                break;

        case XenbusStateReconfiguring:
                scsiback_do_lun_hotplug(info, VSCSIBACK_OP_ADD_OR_DEL_LUN);
                xenbus_switch_state(dev, XenbusStateReconfigured);

                break;

        default:
                xenbus_dev_fatal(dev, -EINVAL, "saw state %d at frontend",
                                        frontend_state);
                break;
        }
}

/*
  Release the translation entry specfied
*/
static void scsiback_release_translation_entry(struct vscsibk_info *info)
{
        struct v2p_entry *entry, *tmp;
        struct list_head *head = &(info->v2p_entry_lists);
        struct list_head tmp_list;
        unsigned long flags;

        spin_lock_irqsave(&info->v2p_lock, flags);

        list_cut_before(&tmp_list, head, head);

        spin_unlock_irqrestore(&info->v2p_lock, flags);

        list_for_each_entry_safe(entry, tmp, &tmp_list, l) {
                list_del(&entry->l);
                kref_put(&entry->kref, scsiback_free_translation_entry);
        }
}

static void scsiback_remove(struct xenbus_device *dev)
{
        struct vscsibk_info *info = dev_get_drvdata(&dev->dev);

        if (info->irq)
                scsiback_disconnect(info);

        scsiback_release_translation_entry(info);

        gnttab_page_cache_shrink(&info->free_pages, 0);

        dev_set_drvdata(&dev->dev, NULL);
        kfree(info);
}

static int scsiback_probe(struct xenbus_device *dev,
                           const struct xenbus_device_id *id)
{
        int err;

        struct vscsibk_info *info = kzalloc_obj(struct vscsibk_info);

        pr_debug("%s %p %d\n", __func__, dev, dev->otherend_id);

        if (!info) {
                xenbus_dev_fatal(dev, -ENOMEM, "allocating backend structure");
                return -ENOMEM;
        }
        info->dev = dev;
        dev_set_drvdata(&dev->dev, info);

        info->domid = dev->otherend_id;
        spin_lock_init(&info->ring_lock);
        atomic_set(&info->nr_unreplied_reqs, 0);
        init_waitqueue_head(&info->waiting_to_free);
        info->dev = dev;
        info->irq = 0;
        INIT_LIST_HEAD(&info->v2p_entry_lists);
        spin_lock_init(&info->v2p_lock);
        gnttab_page_cache_init(&info->free_pages);

        err = xenbus_printf(XBT_NIL, dev->nodename, "feature-sg-grant", "%u",
                            SG_ALL);
        if (err)
                xenbus_dev_error(dev, err, "writing feature-sg-grant");

        err = xenbus_switch_state(dev, XenbusStateInitWait);
        if (err)
                goto fail;

        return 0;

fail:
        pr_warn("%s failed\n", __func__);
        scsiback_remove(dev);

        return err;
}

static char *scsiback_dump_proto_id(struct scsiback_tport *tport)
{
        switch (tport->tport_proto_id) {
        case SCSI_PROTOCOL_SAS:
                return "SAS";
        case SCSI_PROTOCOL_FCP:
                return "FCP";
        case SCSI_PROTOCOL_ISCSI:
                return "iSCSI";
        default:
                break;
        }

        return "Unknown";
}

static char *scsiback_get_fabric_wwn(struct se_portal_group *se_tpg)
{
        struct scsiback_tpg *tpg = container_of(se_tpg,
                                struct scsiback_tpg, se_tpg);
        struct scsiback_tport *tport = tpg->tport;

        return &tport->tport_name[0];
}

static u16 scsiback_get_tag(struct se_portal_group *se_tpg)
{
        struct scsiback_tpg *tpg = container_of(se_tpg,
                                struct scsiback_tpg, se_tpg);
        return tpg->tport_tpgt;
}

static struct se_wwn *
scsiback_make_tport(struct target_fabric_configfs *tf,
                     struct config_group *group,
                     const char *name)
{
        struct scsiback_tport *tport;
        char *ptr;
        u64 wwpn = 0;
        int off = 0;

        tport = kzalloc_obj(struct scsiback_tport);
        if (!tport)
                return ERR_PTR(-ENOMEM);

        tport->tport_wwpn = wwpn;
        /*
         * Determine the emulated Protocol Identifier and Target Port Name
         * based on the incoming configfs directory name.
         */
        ptr = strstr(name, "naa.");
        if (ptr) {
                tport->tport_proto_id = SCSI_PROTOCOL_SAS;
                goto check_len;
        }
        ptr = strstr(name, "fc.");
        if (ptr) {
                tport->tport_proto_id = SCSI_PROTOCOL_FCP;
                off = 3; /* Skip over "fc." */
                goto check_len;
        }
        ptr = strstr(name, "iqn.");
        if (ptr) {
                tport->tport_proto_id = SCSI_PROTOCOL_ISCSI;
                goto check_len;
        }

        pr_err("Unable to locate prefix for emulated Target Port: %s\n", name);
        kfree(tport);
        return ERR_PTR(-EINVAL);

check_len:
        if (strlen(name) >= VSCSI_NAMELEN) {
                pr_err("Emulated %s Address: %s, exceeds max: %d\n", name,
                        scsiback_dump_proto_id(tport), VSCSI_NAMELEN);
                kfree(tport);
                return ERR_PTR(-EINVAL);
        }
        snprintf(&tport->tport_name[0], VSCSI_NAMELEN, "%s", &name[off]);

        pr_debug("Allocated emulated Target %s Address: %s\n",
                 scsiback_dump_proto_id(tport), name);

        return &tport->tport_wwn;
}

static void scsiback_drop_tport(struct se_wwn *wwn)
{
        struct scsiback_tport *tport = container_of(wwn,
                                struct scsiback_tport, tport_wwn);

        pr_debug("Deallocating emulated Target %s Address: %s\n",
                 scsiback_dump_proto_id(tport), tport->tport_name);

        kfree(tport);
}

static int scsiback_check_stop_free(struct se_cmd *se_cmd)
{
        return transport_generic_free_cmd(se_cmd, 0);
}

static void scsiback_release_cmd(struct se_cmd *se_cmd)
{
        target_free_tag(se_cmd->se_sess, se_cmd);
}

static int scsiback_write_pending(struct se_cmd *se_cmd)
{
        /* Go ahead and process the write immediately */
        target_execute_cmd(se_cmd);

        return 0;
}

static int scsiback_queue_data_in(struct se_cmd *se_cmd)
{
        struct vscsibk_pend *pending_req = container_of(se_cmd,
                                struct vscsibk_pend, se_cmd);

        pending_req->result = SAM_STAT_GOOD;
        scsiback_cmd_done(pending_req);
        return 0;
}

static int scsiback_queue_status(struct se_cmd *se_cmd)
{
        struct vscsibk_pend *pending_req = container_of(se_cmd,
                                struct vscsibk_pend, se_cmd);

        if (se_cmd->sense_buffer &&
            ((se_cmd->se_cmd_flags & SCF_TRANSPORT_TASK_SENSE) ||
             (se_cmd->se_cmd_flags & SCF_EMULATED_TASK_SENSE)))
                pending_req->result = SAM_STAT_CHECK_CONDITION;
        else
                pending_req->result = se_cmd->scsi_status;

        scsiback_cmd_done(pending_req);
        return 0;
}

static void scsiback_queue_tm_rsp(struct se_cmd *se_cmd)
{
        struct vscsibk_pend *pending_req = container_of(se_cmd,
                                struct vscsibk_pend, se_cmd);

        complete(&pending_req->tmr_done);
}

static void scsiback_aborted_task(struct se_cmd *se_cmd)
{
}

static ssize_t scsiback_tpg_param_alias_show(struct config_item *item,
                                             char *page)
{
        struct se_portal_group *se_tpg = param_to_tpg(item);
        struct scsiback_tpg *tpg = container_of(se_tpg, struct scsiback_tpg,
                                                se_tpg);
        ssize_t rb;

        mutex_lock(&tpg->tv_tpg_mutex);
        rb = snprintf(page, PAGE_SIZE, "%s\n", tpg->param_alias);
        mutex_unlock(&tpg->tv_tpg_mutex);

        return rb;
}

static ssize_t scsiback_tpg_param_alias_store(struct config_item *item,
                                              const char *page, size_t count)
{
        struct se_portal_group *se_tpg = param_to_tpg(item);
        struct scsiback_tpg *tpg = container_of(se_tpg, struct scsiback_tpg,
                                                se_tpg);
        int len;

        if (strlen(page) >= VSCSI_NAMELEN) {
                pr_err("param alias: %s, exceeds max: %d\n", page,
                        VSCSI_NAMELEN);
                return -EINVAL;
        }

        mutex_lock(&tpg->tv_tpg_mutex);
        len = snprintf(tpg->param_alias, VSCSI_NAMELEN, "%s", page);
        if (tpg->param_alias[len - 1] == '\n')
                tpg->param_alias[len - 1] = '\0';
        mutex_unlock(&tpg->tv_tpg_mutex);

        return count;
}

CONFIGFS_ATTR(scsiback_tpg_param_, alias);

static struct configfs_attribute *scsiback_param_attrs[] = {
        &scsiback_tpg_param_attr_alias,
        NULL,
};

static int scsiback_alloc_sess_cb(struct se_portal_group *se_tpg,
                                  struct se_session *se_sess, void *p)
{
        struct scsiback_tpg *tpg = container_of(se_tpg,
                                struct scsiback_tpg, se_tpg);

        tpg->tpg_nexus = p;
        return 0;
}

static int scsiback_make_nexus(struct scsiback_tpg *tpg,
                                const char *name)
{
        struct scsiback_nexus *tv_nexus;
        int ret = 0;

        mutex_lock(&tpg->tv_tpg_mutex);
        if (tpg->tpg_nexus) {
                pr_debug("tpg->tpg_nexus already exists\n");
                ret = -EEXIST;
                goto out_unlock;
        }

        tv_nexus = kzalloc_obj(struct scsiback_nexus);
        if (!tv_nexus) {
                ret = -ENOMEM;
                goto out_unlock;
        }

        tv_nexus->tvn_se_sess = target_setup_session(&tpg->se_tpg,
                                                     VSCSI_DEFAULT_SESSION_TAGS,
                                                     sizeof(struct vscsibk_pend),
                                                     TARGET_PROT_NORMAL, name,
                                                     tv_nexus, scsiback_alloc_sess_cb);
        if (IS_ERR(tv_nexus->tvn_se_sess)) {
                kfree(tv_nexus);
                ret = -ENOMEM;
                goto out_unlock;
        }

out_unlock:
        mutex_unlock(&tpg->tv_tpg_mutex);
        return ret;
}

static int scsiback_drop_nexus(struct scsiback_tpg *tpg)
{
        struct se_session *se_sess;
        struct scsiback_nexus *tv_nexus;

        mutex_lock(&tpg->tv_tpg_mutex);
        tv_nexus = tpg->tpg_nexus;
        if (!tv_nexus) {
                mutex_unlock(&tpg->tv_tpg_mutex);
                return -ENODEV;
        }

        se_sess = tv_nexus->tvn_se_sess;
        if (!se_sess) {
                mutex_unlock(&tpg->tv_tpg_mutex);
                return -ENODEV;
        }

        if (tpg->tv_tpg_port_count != 0) {
                mutex_unlock(&tpg->tv_tpg_mutex);
                pr_err("Unable to remove xen-pvscsi I_T Nexus with active TPG port count: %d\n",
                        tpg->tv_tpg_port_count);
                return -EBUSY;
        }

        if (tpg->tv_tpg_fe_count != 0) {
                mutex_unlock(&tpg->tv_tpg_mutex);
                pr_err("Unable to remove xen-pvscsi I_T Nexus with active TPG frontend count: %d\n",
                        tpg->tv_tpg_fe_count);
                return -EBUSY;
        }

        pr_debug("Removing I_T Nexus to emulated %s Initiator Port: %s\n",
                scsiback_dump_proto_id(tpg->tport),
                tv_nexus->tvn_se_sess->se_node_acl->initiatorname);

        /*
         * Release the SCSI I_T Nexus to the emulated xen-pvscsi Target Port
         */
        target_remove_session(se_sess);
        tpg->tpg_nexus = NULL;
        mutex_unlock(&tpg->tv_tpg_mutex);

        kfree(tv_nexus);
        return 0;
}

static ssize_t scsiback_tpg_nexus_show(struct config_item *item, char *page)
{
        struct se_portal_group *se_tpg = to_tpg(item);
        struct scsiback_tpg *tpg = container_of(se_tpg,
                                struct scsiback_tpg, se_tpg);
        struct scsiback_nexus *tv_nexus;
        ssize_t ret;

        mutex_lock(&tpg->tv_tpg_mutex);
        tv_nexus = tpg->tpg_nexus;
        if (!tv_nexus) {
                mutex_unlock(&tpg->tv_tpg_mutex);
                return -ENODEV;
        }
        ret = snprintf(page, PAGE_SIZE, "%s\n",
                        tv_nexus->tvn_se_sess->se_node_acl->initiatorname);
        mutex_unlock(&tpg->tv_tpg_mutex);

        return ret;
}

static ssize_t scsiback_tpg_nexus_store(struct config_item *item,
                const char *page, size_t count)
{
        struct se_portal_group *se_tpg = to_tpg(item);
        struct scsiback_tpg *tpg = container_of(se_tpg,
                                struct scsiback_tpg, se_tpg);
        struct scsiback_tport *tport_wwn = tpg->tport;
        unsigned char i_port[VSCSI_NAMELEN], *ptr, *port_ptr;
        int ret;
        /*
         * Shutdown the active I_T nexus if 'NULL' is passed.
         */
        if (!strncmp(page, "NULL", 4)) {
                ret = scsiback_drop_nexus(tpg);
                return (!ret) ? count : ret;
        }
        /*
         * Otherwise make sure the passed virtual Initiator port WWN matches
         * the fabric protocol_id set in scsiback_make_tport(), and call
         * scsiback_make_nexus().
         */
        if (strlen(page) >= VSCSI_NAMELEN) {
                pr_err("Emulated NAA Sas Address: %s, exceeds max: %d\n",
                        page, VSCSI_NAMELEN);
                return -EINVAL;
        }
        snprintf(&i_port[0], VSCSI_NAMELEN, "%s", page);

        ptr = strstr(i_port, "naa.");
        if (ptr) {
                if (tport_wwn->tport_proto_id != SCSI_PROTOCOL_SAS) {
                        pr_err("Passed SAS Initiator Port %s does not match target port protoid: %s\n",
                                i_port, scsiback_dump_proto_id(tport_wwn));
                        return -EINVAL;
                }
                port_ptr = &i_port[0];
                goto check_newline;
        }
        ptr = strstr(i_port, "fc.");
        if (ptr) {
                if (tport_wwn->tport_proto_id != SCSI_PROTOCOL_FCP) {
                        pr_err("Passed FCP Initiator Port %s does not match target port protoid: %s\n",
                                i_port, scsiback_dump_proto_id(tport_wwn));
                        return -EINVAL;
                }
                port_ptr = &i_port[3]; /* Skip over "fc." */
                goto check_newline;
        }
        ptr = strstr(i_port, "iqn.");
        if (ptr) {
                if (tport_wwn->tport_proto_id != SCSI_PROTOCOL_ISCSI) {
                        pr_err("Passed iSCSI Initiator Port %s does not match target port protoid: %s\n",
                                i_port, scsiback_dump_proto_id(tport_wwn));
                        return -EINVAL;
                }
                port_ptr = &i_port[0];
                goto check_newline;
        }
        pr_err("Unable to locate prefix for emulated Initiator Port: %s\n",
                i_port);
        return -EINVAL;
        /*
         * Clear any trailing newline for the NAA WWN
         */
check_newline:
        if (i_port[strlen(i_port) - 1] == '\n')
                i_port[strlen(i_port) - 1] = '\0';

        ret = scsiback_make_nexus(tpg, port_ptr);
        if (ret < 0)
                return ret;

        return count;
}

CONFIGFS_ATTR(scsiback_tpg_, nexus);

static struct configfs_attribute *scsiback_tpg_attrs[] = {
        &scsiback_tpg_attr_nexus,
        NULL,
};

static ssize_t
scsiback_wwn_version_show(struct config_item *item, char *page)
{
        return sprintf(page, "xen-pvscsi fabric module %s on %s/%s on "
                UTS_RELEASE"\n",
                VSCSI_VERSION, utsname()->sysname, utsname()->machine);
}

CONFIGFS_ATTR_RO(scsiback_wwn_, version);

static struct configfs_attribute *scsiback_wwn_attrs[] = {
        &scsiback_wwn_attr_version,
        NULL,
};

static int scsiback_port_link(struct se_portal_group *se_tpg,
                               struct se_lun *lun)
{
        struct scsiback_tpg *tpg = container_of(se_tpg,
                                struct scsiback_tpg, se_tpg);

        mutex_lock(&tpg->tv_tpg_mutex);
        tpg->tv_tpg_port_count++;
        mutex_unlock(&tpg->tv_tpg_mutex);

        return 0;
}

static void scsiback_port_unlink(struct se_portal_group *se_tpg,
                                  struct se_lun *lun)
{
        struct scsiback_tpg *tpg = container_of(se_tpg,
                                struct scsiback_tpg, se_tpg);

        mutex_lock(&tpg->tv_tpg_mutex);
        tpg->tv_tpg_port_count--;
        mutex_unlock(&tpg->tv_tpg_mutex);
}

static struct se_portal_group *
scsiback_make_tpg(struct se_wwn *wwn, const char *name)
{
        struct scsiback_tport *tport = container_of(wwn,
                        struct scsiback_tport, tport_wwn);

        struct scsiback_tpg *tpg;
        u16 tpgt;
        int ret;

        if (strstr(name, "tpgt_") != name)
                return ERR_PTR(-EINVAL);
        ret = kstrtou16(name + 5, 10, &tpgt);
        if (ret)
                return ERR_PTR(ret);

        tpg = kzalloc_obj(struct scsiback_tpg);
        if (!tpg)
                return ERR_PTR(-ENOMEM);

        mutex_init(&tpg->tv_tpg_mutex);
        INIT_LIST_HEAD(&tpg->tv_tpg_list);
        INIT_LIST_HEAD(&tpg->info_list);
        tpg->tport = tport;
        tpg->tport_tpgt = tpgt;

        ret = core_tpg_register(wwn, &tpg->se_tpg, tport->tport_proto_id);
        if (ret < 0) {
                kfree(tpg);
                return NULL;
        }
        mutex_lock(&scsiback_mutex);
        list_add_tail(&tpg->tv_tpg_list, &scsiback_list);
        mutex_unlock(&scsiback_mutex);

        return &tpg->se_tpg;
}

static void scsiback_drop_tpg(struct se_portal_group *se_tpg)
{
        struct scsiback_tpg *tpg = container_of(se_tpg,
                                struct scsiback_tpg, se_tpg);

        mutex_lock(&scsiback_mutex);
        list_del(&tpg->tv_tpg_list);
        mutex_unlock(&scsiback_mutex);
        /*
         * Release the virtual I_T Nexus for this xen-pvscsi TPG
         */
        scsiback_drop_nexus(tpg);
        /*
         * Deregister the se_tpg from TCM.
         */
        core_tpg_deregister(se_tpg);
        kfree(tpg);
}

static int scsiback_check_true(struct se_portal_group *se_tpg)
{
        return 1;
}

static const struct target_core_fabric_ops scsiback_ops = {
        .module                         = THIS_MODULE,
        .fabric_name                    = "xen-pvscsi",
        .tpg_get_wwn                    = scsiback_get_fabric_wwn,
        .tpg_get_tag                    = scsiback_get_tag,
        .tpg_check_demo_mode            = scsiback_check_true,
        .tpg_check_demo_mode_cache      = scsiback_check_true,
        .check_stop_free                = scsiback_check_stop_free,
        .release_cmd                    = scsiback_release_cmd,
        .sess_get_initiator_sid         = NULL,
        .write_pending                  = scsiback_write_pending,
        .queue_data_in                  = scsiback_queue_data_in,
        .queue_status                   = scsiback_queue_status,
        .queue_tm_rsp                   = scsiback_queue_tm_rsp,
        .aborted_task                   = scsiback_aborted_task,
        /*
         * Setup callers for generic logic in target_core_fabric_configfs.c
         */
        .fabric_make_wwn                = scsiback_make_tport,
        .fabric_drop_wwn                = scsiback_drop_tport,
        .fabric_make_tpg                = scsiback_make_tpg,
        .fabric_drop_tpg                = scsiback_drop_tpg,
        .fabric_post_link               = scsiback_port_link,
        .fabric_pre_unlink              = scsiback_port_unlink,

        .tfc_wwn_attrs                  = scsiback_wwn_attrs,
        .tfc_tpg_base_attrs             = scsiback_tpg_attrs,
        .tfc_tpg_param_attrs            = scsiback_param_attrs,

        .default_submit_type            = TARGET_DIRECT_SUBMIT,
        .direct_submit_supp             = 1,
};

static const struct xenbus_device_id scsiback_ids[] = {
        { "vscsi" },
        { "" }
};

static struct xenbus_driver scsiback_driver = {
        .ids                    = scsiback_ids,
        .probe                  = scsiback_probe,
        .remove                 = scsiback_remove,
        .otherend_changed       = scsiback_frontend_changed
};

static int __init scsiback_init(void)
{
        int ret;

        if (!xen_domain())
                return -ENODEV;

        pr_debug("xen-pvscsi: fabric module %s on %s/%s on "UTS_RELEASE"\n",
                 VSCSI_VERSION, utsname()->sysname, utsname()->machine);

        ret = xenbus_register_backend(&scsiback_driver);
        if (ret)
                goto out;

        ret = target_register_template(&scsiback_ops);
        if (ret)
                goto out_unregister_xenbus;

        return 0;

out_unregister_xenbus:
        xenbus_unregister_driver(&scsiback_driver);
out:
        pr_err("%s: error %d\n", __func__, ret);
        return ret;
}

static void __exit scsiback_exit(void)
{
        target_unregister_template(&scsiback_ops);
        xenbus_unregister_driver(&scsiback_driver);
}

module_init(scsiback_init);
module_exit(scsiback_exit);

MODULE_DESCRIPTION("Xen SCSI backend driver");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_ALIAS("xen-backend:vscsi");
MODULE_AUTHOR("Juergen Gross <jgross@suse.com>");