mirror of
https://github.com/torvalds/linux.git
synced 2024-11-22 04:02:20 +00:00
194605d45d
In some cases, like with multiple LUN targets or where the target has to respond to transport level requests from the receiving context it can be better to defer cmd submission to a helper thread. If the backend driver blocks on something like request/tag allocation it can block the entire target submission path and other LUs and transport IO on that session. In other cases like single LUN targets with storage that can support all the commands that the target can queue, then it's best to submit the cmd to the backend from the target's cmd receiving context. Subsequent commits will allow the user to config what they prefer, but drivers like loop can't directly submit because they can be called from a context that can't sleep. And, drivers like vhost-scsi can support direct submission, but need to keep their default behavior of deferring execution to avoid possible regressions where the backend can block. Make the drivers tell LIO core if they support direct submissions and their current default, so we can prevent users from misconfiguring the system and initialize devices correctly. Signed-off-by: Mike Christie <michael.christie@oracle.com> Link: https://lore.kernel.org/r/20230928020907.5730-2-michael.christie@oracle.com Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
1892 lines
47 KiB
C
1892 lines
47 KiB
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_array(nr_segments,
|
|
sizeof(struct scatterlist), GFP_KERNEL);
|
|
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(sizeof(struct v2p_entry), GFP_KERNEL);
|
|
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);
|
|
}
|
|
|
|
static int scsiback_probe(struct xenbus_device *dev,
|
|
const struct xenbus_device_id *id)
|
|
{
|
|
int err;
|
|
|
|
struct vscsibk_info *info = kzalloc(sizeof(struct vscsibk_info),
|
|
GFP_KERNEL);
|
|
|
|
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(sizeof(struct scsiback_tport), GFP_KERNEL);
|
|
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(sizeof(struct scsiback_nexus), GFP_KERNEL);
|
|
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(sizeof(struct scsiback_tpg), GFP_KERNEL);
|
|
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>");
|