- iSCSI Target 端的用户态实现 TCMU 介绍
- TCMU 原理剖析
- TCMU 对接自定义后端存储代码示例讲解
target_core_user.h
查看结构的定义。struct tcmu_mailbox {
__u16 version; // 2 如果是别的值,用户空间应该废弃)
__u16 flags; //TCMU_MAILBOX_FLAG_CAP_OOOC: 标志支持out-of-order completion
__u32 cmdr_off; //command ring在内存区域的起始位置的偏移量。
__u32 cmdr_size; //command ring区域的大小。这不需要2的幂来表示。
__u32 cmd_head; //由内核修改,表示一个command已经放置到ring中。
/* Updated by user. On its own cacheline */
__u32 cmd_tail __attribute__((__aligned__(ALIGN_SIZE))); //由用户空间修改,表示一个command已经处理完成。
} __attribute__((packed));
TCMU_OP_CMD
和 TCMU_OP_PAD
。struct tcmu_cmd_entry
。struct tcmu_cmd_entry {
struct tcmu_cmd_entry_hdr hdr;
union {
struct {
uint32_t iov_cnt; // The size of iov array.
uint32_t iov_bidi_cnt; // size of iov array for Data-In
uint32_t iov_dif_cnt; //
uint64_t cdb_off; // Command data block offset
uint64_t __pad1;
uint64_t __pad2;
struct iovec iov[0]; // iov array - buffer
} req;
struct {
uint8_t scsi_status; // Command executed, return status.
uint8_t __pad1;
uint16_t __pad2;
uint32_t __pad3;
char sense_buffer[TCMU_SENSE_BUFFERSIZE]; // response data
} rsp;
};
} __attribute__((packed));
/*
* Only a few opcodes, and length is 8-byte aligned, so use low bits for opcode.
*/
struct tcmu_cmd_entry_hdr {
__u32 len_op;
__u16 cmd_id;
__u8 kflags;
#define TCMU_UFLAG_UNKNOWN_OP 0x1
__u8 uflags;
} __attribute__((packed));
[外链图片转存失败(img-5kR76M2X-1564232269967)(https://github.com/zjs1224522500/files-and-images/blob/master/blog/pic/User-Kernel-Communication.png?raw=true)]
struct tcmur_handler {
const char *name; /* Human-friendly name */
const char *subtype; /* Name for cfgstring matching */
const char *cfg_desc; /* Description of this backstore's config string */
void *opaque; /* Handler private data. */
/*
* As much as possible, check that the cfgstring will result
* in a working device when given to us as dev->cfgstring in
* the ->open() call.
*
* This function is optional but gives configuration tools a
* chance to warn users in advance if the device they're
* trying to create is invalid.
*
* Returns true if string is valid. Only if false, set *reason
* to a string that says why. The string will be free()ed.
* Suggest using asprintf().
*/
bool (*check_config)(const char *cfgstring, char **reason);
int (*reconfig)(struct tcmu_device *dev, struct tcmulib_cfg_info *cfg);
/* Per-device added/removed callbacks */
int (*open)(struct tcmu_device *dev, bool reopen);
void (*close)(struct tcmu_device *dev);
/*
* If > 0, runner will execute up to nr_threads IO callouts from
* threads.
* if 0, runner will call IO callouts from the cmd proc thread or
* completion context for compound commands.
*/
int nr_threads;
/*
* Async handle_cmd only handlers return:
*
* - TCMU_STS_OK if the command has been executed successfully
* - TCMU_STS_NOT_HANDLED if opcode is not handled
* - TCMU_STS_ASYNC_HANDLED if opcode is handled asynchronously
* - Non TCMU_STS_OK code indicating failure
* - TCMU_STS_PASSTHROUGH_ERR For handlers that require low level
* SCSI processing and want to setup their own sense buffers.
*
* Handlers that set nr_threads > 0 and async handlers
* that implement handle_cmd and the IO callouts below return:
*
* - TCMU_STS_OK if the handler has queued the command.
* - TCMU_STS_NOT_HANDLED if the command is not supported.
* - TCMU_STS_NO_RESOURCE if the handler was not able to allocate
* resources for the command.
*
* If TCMU_STS_OK is returned from the callout the handler must call
* the tcmulib_cmd->done function with TCMU_STS return code.
*/
handle_cmd_fn_t handle_cmd;
/*
* Below callbacks are only executed by generic_handle_cmd.
* Returns:
* - TCMU_STS_OK if the handler has queued the command.
* - TCMU_STS_NO_RESOURCE if the handler was not able to allocate
* resources for the command.
*
* If TCMU_STS_OK is returned from the callout the handler must call
* the tcmulib_cmd->done function with TCMU_STS return code.
*/
rw_fn_t write;
rw_fn_t read;
flush_fn_t flush;
unmap_fn_t unmap;
/*
* If the lock is acquired and the tag is not TCMU_INVALID_LOCK_TAG,
* it must be associated with the lock and returned by get_lock_tag on
* local and remote nodes. When unlock is successful, the tag
* associated with the lock must be deleted.
*
* Returns a TCMU_STS indicating success/failure.
*/
int (*lock)(struct tcmu_device *dev, uint16_t tag);
int (*unlock)(struct tcmu_device *dev);
/*
* Return tag set in lock call in tag buffer and a TCMU_STS
* indicating success/failure.
*/
int (*get_lock_tag)(struct tcmu_device *dev, uint16_t *tag);
/*
* Must return TCMUR_DEV_LOCK state value.
*/
int (*get_lock_state)(struct tcmu_device *dev);
/*
* internal field, don't touch this
*
* indicates to tcmu-runner whether this is an internal handler loaded
* via dlopen or an external handler registered via dbus. In the
* latter case opaque will point to a struct dbus_info.
*/
bool _is_dbus_handler;
/*
* Update the logdir called by dynamic config thread.
*/
bool (*update_logdir)(void);
};
file_optical.c
为例:/*
* Return scsi status or TCMU_STS_NOT_HANDLED
*/
static int fbo_handle_cmd(struct tcmu_device *dev, struct tcmulib_cmd *cmd)
{
uint8_t *cdb = cmd->cdb;
struct iovec *iovec = cmd->iovec;
size_t iov_cnt = cmd->iov_cnt;
uint8_t *sense = cmd->sense_buf;
struct fbo_state *state = tcmur_dev_get_private(dev);
bool do_verify = false;
int ret;
/* Check for format in progress */
/* Certain commands can be executed even if a format is in progress */
if (state->flags & FBO_FORMATTING &&
cdb[0] != INQUIRY &&
cdb[0] != REQUEST_SENSE &&
cdb[0] != GET_CONFIGURATION &&
cdb[0] != GPCMD_GET_EVENT_STATUS_NOTIFICATION) {
tcmu_sense_set_key_specific_info(sense, state->format_progress);
ret = TCMU_STS_FRMT_IN_PROGRESS;
return ret;
}
switch(cdb[0]) {
case TEST_UNIT_READY:
ret = tcmu_emulate_test_unit_ready(cdb, iovec, iov_cnt);
break;
case REQUEST_SENSE:
ret = fbo_emulate_request_sense(dev, cdb, iovec, iov_cnt, sense);
break;
case FORMAT_UNIT:
ret = fbo_emulate_format_unit(dev, cdb, iovec, iov_cnt, sense);
break;
case READ_6:
case READ_10:
case READ_12:
ret = fbo_read(dev, cdb, iovec, iov_cnt, sense);
break;
case WRITE_VERIFY:
do_verify = true;
case WRITE_6:
case WRITE_10:
case WRITE_12:
ret = fbo_write(dev, cdb, iovec, iov_cnt, sense, do_verify);
break;
case INQUIRY:
ret = fbo_emulate_inquiry(cdb, iovec, iov_cnt, sense);
break;
case MODE_SELECT:
case MODE_SELECT_10:
ret = fbo_emulate_mode_select(cdb, iovec, iov_cnt, sense);
break;
case MODE_SENSE:
case MODE_SENSE_10:
ret = fbo_emulate_mode_sense(cdb, iovec, iov_cnt, sense);
break;
case START_STOP:
ret = tcmu_emulate_start_stop(dev, cdb);
break;
case ALLOW_MEDIUM_REMOVAL:
ret = fbo_emulate_allow_medium_removal(dev, cdb, sense);
break;
case READ_FORMAT_CAPACITIES:
ret = fbo_emulate_read_format_capacities(dev, cdb, iovec,
iov_cnt, sense);
break;
case READ_CAPACITY:
if ((cdb[1] & 0x01) || (cdb[8] & 0x01))
/* Reserved bits for MM logical units */
return TCMU_STS_INVALID_CDB;
else
ret = tcmu_emulate_read_capacity_10(state->num_lbas,
state->block_size,
cdb, iovec,
iov_cnt);
break;
case VERIFY:
ret = fbo_verify(dev, cdb, iovec, iov_cnt, sense);
break;
case SYNCHRONIZE_CACHE:
ret = fbo_synchronize_cache(dev, cdb, sense);
break;
case READ_TOC:
ret = fbo_emulate_read_toc(dev, cdb, iovec, iov_cnt, sense);
break;
case GET_CONFIGURATION:
ret = fbo_emulate_get_configuration(dev, cdb, iovec, iov_cnt,
sense);
break;
case GPCMD_GET_EVENT_STATUS_NOTIFICATION:
ret = fbo_emulate_get_event_status_notification(dev, cdb,
iovec, iov_cnt,
sense);
break;
case READ_DISC_INFORMATION:
ret = fbo_emulate_read_disc_information(dev, cdb, iovec,
iov_cnt, sense);
break;
case READ_DVD_STRUCTURE:
ret = fbo_emulate_read_dvd_structure(dev, cdb, iovec, iov_cnt,
sense);
break;
case MECHANISM_STATUS:
ret = fbo_emulate_mechanism_status(dev, cdb, iovec, iov_cnt,
sense);
break;
default:
ret = TCMU_STS_NOT_HANDLED;
}
return ret;
}
/*
* Copyright (c) 2014 Red Hat, Inc.
*
* This file is licensed to you under your choice of the GNU Lesser
* General Public License, version 2.1 or any later version (LGPLv2.1 or
* later), or the Apache License 2.0.
*/
/*
* Example code to demonstrate how a TCMU handler might work.
*
* Using the example of backing a device by a file to demonstrate:
*
* 1) Registering with tcmu-runner
* 2) Parsing the handler-specific config string as needed for setup
* 3) Opening resources as needed
* 4) Handling SCSI commands and using the handler API
*/
#define _GNU_SOURCE
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "scsi_defs.h"
#include "libtcmu.h"
#include "tcmu-runner.h"
#include "tcmur_device.h"
struct file_state {
int fd;
};
static int file_open(struct tcmu_device *dev, bool reopen)
{
struct file_state *state;
char *config;
state = calloc(1, sizeof(*state));
if (!state)
return -ENOMEM;
// Init the file state of tcmu
tcmur_dev_set_private(dev, state);
// Move the pointer to the first '/' location in path string
config = strchr(tcmu_dev_get_cfgstring(dev), '/');
if (!config) {
tcmu_err("no configuration found in cfgstring\n");
goto err;
}
config += 1; /* get past '/' */
// Enable the tcmu write cache.(Set the value of tcmu_device as true)
tcmu_dev_set_write_cache_enabled(dev, 1);
// Open the file with path.(With mode)
state->fd = open(config, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
if (state->fd == -1) {
tcmu_err("could not open %s: %m\n", config);
goto err;
}
tcmu_dbg("config %s\n", tcmu_dev_get_cfgstring(dev));
return 0;
err:
free(state);
return -EINVAL;
}
static void file_close(struct tcmu_device *dev)
{
// Get the file state of tcmu_device.
struct file_state *state = tcmur_dev_get_private(dev);
// Close the file
close(state->fd);
// free the state
free(state);
}
/**
*
* @param *dev tcmu device
* @param *cmd Command line interface.(not used in this method)
* @param *iov buffer array to syore the data
* @param iov_cnt buffer array size
* @param length read length
* @param offset start address
*
* return the size of read data.
*/
static int file_read(struct tcmu_device *dev, struct tcmulib_cmd *cmd,
struct iovec *iov, size_t iov_cnt, size_t length,
off_t offset)
{
// Get the file state of tecmu_device
struct file_state *state = tcmur_dev_get_private(dev);
size_t remaining = length;
ssize_t ret;
// Read the file in loop
while (remaining) {
// Read the data and store in the iov array. Return the data size.
ret = preadv(state->fd, iov, iov_cnt, offset);
if (ret < 0) {
tcmu_err("read failed: %m\n");
ret = TCMU_STS_RD_ERR;
goto done;
}
if (ret == 0) {
/* EOF, then zeros the iovecs left */
tcmu_iovec_zero(iov, iov_cnt);
break;
}
// Consume the iov array.
tcmu_iovec_seek(iov, ret);
// Move the offset
offset += ret;
// Change the length and continute to read file.
remaining -= ret;
}
// Read finished, and status is OK.
ret = TCMU_STS_OK;
done:
return ret;
}
/**
*
* @param *dev tcmu device
* @param *cmd Command line interface.(not used in this method)
* @param *iov buffer array to syore the data
* @param iov_cnt buffer array size
* @param length write length
* @param offset start address
*
* return the size of read data.
*/
static int file_write(struct tcmu_device *dev, struct tcmulib_cmd *cmd,
struct iovec *iov, size_t iov_cnt, size_t length,
off_t offset)
{
// Get the file state of tecmu_device
struct file_state *state = tcmur_dev_get_private(dev);
size_t remaining = length;
ssize_t ret;
// Write the file in loop
while (remaining) {
// Wirte the data in the iov array to file. Return the data size.
ret = pwritev(state->fd, iov, iov_cnt, offset);
if (ret < 0) {
tcmu_err("write failed: %m\n");
ret = TCMU_STS_WR_ERR;
goto done;
}
// Consume an inv array.
tcmu_iovec_seek(iov, ret);
// Move the offset.
offset += ret;
// Change the length and continue to write.
remaining -= ret;
}
ret = TCMU_STS_OK;
done:
return ret;
}
static int file_flush(struct tcmu_device *dev, struct tcmulib_cmd *cmd)
{
// Get the file state of tcmu_device.
struct file_state *state = tcmur_dev_get_private(dev);
int ret;
// Sync(Flush) the data in page cache to disk.
if (fsync(state->fd)) {
tcmu_err("sync failed\n");
ret = TCMU_STS_WR_ERR;
goto done;
}
ret = TCMU_STS_OK;
done:
return ret;
}
static int file_reconfig(struct tcmu_device *dev, struct tcmulib_cfg_info *cfg)
{
switch (cfg->type) {
// Extend or Reduce the size of file.
case TCMULIB_CFG_DEV_SIZE:
/*
* TODO - For open/reconfig we should make sure the FS the
* file is on is large enough for the requested size. For
* now assume we can grow the file and return 0.
*/
return 0;
case TCMULIB_CFG_DEV_CFGSTR:
// Handle the write cache.
case TCMULIB_CFG_WRITE_CACHE:
default:
return -EOPNOTSUPP;
}
}
static const char file_cfg_desc[] =
"The path to the file to use as a backstore.";
// Init the tcmu_handler with given static method defined in this class.
static struct tcmur_handler file_handler = {
.cfg_desc = file_cfg_desc,
.reconfig = file_reconfig,
.open = file_open,
.close = file_close,
.read = file_read,
.write = file_write,
.flush = file_flush,
.name = "File-backed Handler (example code)",
.subtype = "file",
.nr_threads = 2,
};
/* Entry point must be named "handler_init". */
int handler_init(void)
{
// Regist the file_handler to running_handler list
return tcmur_register_handler(&file_handler);
}