freebsd-skq/sys/dev/ocs_fc/ocs_io.c
Kenneth D. Merry ef270ab1b6 Bring in the Broadcom/Emulex Fibre Channel driver, ocs_fc(4).
The ocs_fc(4) driver supports the following hardware:

Emulex 16/8G FC GEN 5 HBAS
	LPe15004 FC Host Bus Adapters
	LPe160XX FC Host Bus Adapters

Emulex 32/16G FC GEN 6 HBAS
	LPe3100X FC Host Bus Adapters
	LPe3200X FC Host Bus Adapters

The driver supports target and initiator mode, and also supports FC-Tape.

Note that the driver only currently works on little endian platforms.  It
is only included in the module build for amd64 and i386, and in GENERIC
on amd64 only.

Submitted by:	Ram Kishore Vegesna <ram.vegesna@broadcom.com>
Reviewed by:	mav
MFC after:	5 days
Relnotes:	yes
Sponsored by:	Broadcom
Differential Revision:	https://reviews.freebsd.org/D11423
2018-03-30 15:28:25 +00:00

492 lines
15 KiB
C

/*-
* Copyright (c) 2017 Broadcom. All rights reserved.
* The term "Broadcom" refers to Broadcom Limited and/or its subsidiaries.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* $FreeBSD$
*/
/**
* @file
* Provide IO object allocation.
*/
/*!
* @defgroup io_alloc IO allocation
*/
#include "ocs.h"
#include "ocs_scsi.h"
#include "ocs_els.h"
#include "ocs_utils.h"
void ocs_mgmt_io_list(ocs_textbuf_t *textbuf, void *io);
void ocs_mgmt_io_get_all(ocs_textbuf_t *textbuf, void *io);
int ocs_mgmt_io_get(ocs_textbuf_t *textbuf, char *parent, char *name, void *io);
static ocs_mgmt_functions_t io_mgmt_functions = {
.get_list_handler = ocs_mgmt_io_list,
.get_handler = ocs_mgmt_io_get,
.get_all_handler = ocs_mgmt_io_get_all,
};
/**
* @brief IO pool.
*
* Structure encapsulating a pool of IO objects.
*
*/
struct ocs_io_pool_s {
ocs_t *ocs; /* Pointer to device object */
ocs_lock_t lock; /* IO pool lock */
uint32_t io_num_ios; /* Total IOs allocated */
ocs_pool_t *pool;
};
/**
* @brief Create a pool of IO objects.
*
* @par Description
* This function allocates memory in larger chucks called
* "slabs" which are a fixed size. It calculates the number of IO objects that
* fit within each "slab" and determines the number of "slabs" required to
* allocate the number of IOs requested. Each of the slabs is allocated and
* then it grabs each IO object within the slab and adds it to the free list.
* Individual command, response and SGL DMA buffers are allocated for each IO.
*
* "Slabs"
* +----------------+
* | |
* +----------------+ |
* | IO | |
* +----------------+ |
* | ... | |
* +----------------+__+
* | IO |
* +----------------+
*
* @param ocs Driver instance's software context.
* @param num_io Number of IO contexts to allocate.
* @param num_sgl Number of SGL entries to allocate for each IO.
*
* @return Returns a pointer to a new ocs_io_pool_t on success,
* or NULL on failure.
*/
ocs_io_pool_t *
ocs_io_pool_create(ocs_t *ocs, uint32_t num_io, uint32_t num_sgl)
{
uint32_t i = 0;
int32_t rc = -1;
ocs_io_pool_t *io_pool;
/* Allocate the IO pool */
io_pool = ocs_malloc(ocs, sizeof(*io_pool), OCS_M_ZERO | OCS_M_NOWAIT);
if (io_pool == NULL) {
ocs_log_err(ocs, "allocate of IO pool failed\n");
return NULL;;
}
io_pool->ocs = ocs;
io_pool->io_num_ios = num_io;
/* initialize IO pool lock */
ocs_lock_init(ocs, &io_pool->lock, "io_pool lock[%d]", ocs->instance_index);
io_pool->pool = ocs_pool_alloc(ocs, sizeof(ocs_io_t), io_pool->io_num_ios, FALSE);
for (i = 0; i < io_pool->io_num_ios; i++) {
ocs_io_t *io = ocs_pool_get_instance(io_pool->pool, i);
io->tag = i;
io->instance_index = i;
io->ocs = ocs;
/* allocate a command/response dma buffer */
if (ocs->enable_ini) {
rc = ocs_dma_alloc(ocs, &io->cmdbuf, SCSI_CMD_BUF_LENGTH, OCS_MIN_DMA_ALIGNMENT);
if (rc) {
ocs_log_err(ocs, "ocs_dma_alloc cmdbuf failed\n");
ocs_io_pool_free(io_pool);
return NULL;
}
}
/* Allocate a response buffer */
rc = ocs_dma_alloc(ocs, &io->rspbuf, SCSI_RSP_BUF_LENGTH, OCS_MIN_DMA_ALIGNMENT);
if (rc) {
ocs_log_err(ocs, "ocs_dma_alloc cmdbuf failed\n");
ocs_io_pool_free(io_pool);
return NULL;
}
/* Allocate SGL */
io->sgl = ocs_malloc(ocs, sizeof(*io->sgl) * num_sgl, OCS_M_NOWAIT | OCS_M_ZERO);
if (io->sgl == NULL) {
ocs_log_err(ocs, "malloc sgl's failed\n");
ocs_io_pool_free(io_pool);
return NULL;
}
io->sgl_allocated = num_sgl;
io->sgl_count = 0;
/* Make IO backend call to initialize IO */
ocs_scsi_tgt_io_init(io);
ocs_scsi_ini_io_init(io);
rc = ocs_dma_alloc(ocs, &io->els_req, OCS_ELS_REQ_LEN, OCS_MIN_DMA_ALIGNMENT);
if (rc) {
ocs_log_err(ocs, "ocs_dma_alloc els_req failed\n");
ocs_io_pool_free(io_pool);
return NULL;
}
rc = ocs_dma_alloc(ocs, &io->els_rsp, OCS_ELS_GID_PT_RSP_LEN, OCS_MIN_DMA_ALIGNMENT);
if (rc) {
ocs_log_err(ocs, "ocs_dma_alloc els_rsp failed\n");
ocs_io_pool_free(io_pool);
return NULL;
}
}
return io_pool;
}
/**
* @brief Free IO objects pool
*
* @par Description
* The pool of IO objects are freed.
*
* @param io_pool Pointer to IO pool object.
*
* @return Returns 0 on success, or a negative error code value on failure.
*/
int32_t
ocs_io_pool_free(ocs_io_pool_t *io_pool)
{
ocs_t *ocs;
uint32_t i;
ocs_io_t *io;
if (io_pool != NULL) {
ocs = io_pool->ocs;
for (i = 0; i < io_pool->io_num_ios; i++) {
io = ocs_pool_get_instance(io_pool->pool, i);
if (!io)
continue;
ocs_scsi_tgt_io_exit(io);
ocs_scsi_ini_io_exit(io);
if (io->sgl) {
ocs_free(ocs, io->sgl, sizeof(*io->sgl) * io->sgl_allocated);
}
ocs_dma_free(ocs, &io->cmdbuf);
ocs_dma_free(ocs, &io->rspbuf);
ocs_dma_free(ocs, &io->els_req);
ocs_dma_free(ocs, &io->els_rsp);
}
if (io_pool->pool != NULL) {
ocs_pool_free(io_pool->pool);
}
ocs_lock_free(&io_pool->lock);
ocs_free(ocs, io_pool, sizeof(*io_pool));
ocs->xport->io_pool = NULL;
}
return 0;
}
uint32_t ocs_io_pool_allocated(ocs_io_pool_t *io_pool)
{
return io_pool->io_num_ios;
}
/**
* @ingroup io_alloc
* @brief Allocate an object used to track an IO.
*
* @param io_pool Pointer to the IO pool.
*
* @return Returns the pointer to a new object, or NULL if none available.
*/
ocs_io_t *
ocs_io_pool_io_alloc(ocs_io_pool_t *io_pool)
{
ocs_io_t *io = NULL;
ocs_t *ocs;
ocs_assert(io_pool, NULL);
ocs = io_pool->ocs;
ocs_lock(&io_pool->lock);
if ((io = ocs_pool_get(io_pool->pool)) != NULL) {
ocs_unlock(&io_pool->lock);
io->io_type = OCS_IO_TYPE_MAX;
io->hio_type = OCS_HW_IO_MAX;
io->hio = NULL;
io->transferred = 0;
io->ocs = ocs;
io->timeout = 0;
io->sgl_count = 0;
io->tgt_task_tag = 0;
io->init_task_tag = 0;
io->hw_tag = 0;
io->display_name = "pending";
io->seq_init = 0;
io->els_req_free = 0;
io->mgmt_functions = &io_mgmt_functions;
io->io_free = 0;
ocs_atomic_add_return(&ocs->xport->io_active_count, 1);
ocs_atomic_add_return(&ocs->xport->io_total_alloc, 1);
} else {
ocs_unlock(&io_pool->lock);
}
return io;
}
/**
* @ingroup io_alloc
* @brief Free an object used to track an IO.
*
* @param io_pool Pointer to IO pool object.
* @param io Pointer to the IO object.
*/
void
ocs_io_pool_io_free(ocs_io_pool_t *io_pool, ocs_io_t *io)
{
ocs_t *ocs;
ocs_hw_io_t *hio = NULL;
ocs_assert(io_pool);
ocs = io_pool->ocs;
ocs_lock(&io_pool->lock);
hio = io->hio;
io->hio = NULL;
ocs_pool_put(io_pool->pool, io);
ocs_unlock(&io_pool->lock);
if (hio) {
ocs_hw_io_free(&ocs->hw, hio);
}
io->io_free = 1;
ocs_atomic_sub_return(&ocs->xport->io_active_count, 1);
ocs_atomic_add_return(&ocs->xport->io_total_free, 1);
}
/**
* @ingroup io_alloc
* @brief Find an I/O given it's node and ox_id.
*
* @param ocs Driver instance's software context.
* @param node Pointer to node.
* @param ox_id OX_ID to find.
* @param rx_id RX_ID to find (0xffff for unassigned).
*/
ocs_io_t *
ocs_io_find_tgt_io(ocs_t *ocs, ocs_node_t *node, uint16_t ox_id, uint16_t rx_id)
{
ocs_io_t *io = NULL;
ocs_lock(&node->active_ios_lock);
ocs_list_foreach(&node->active_ios, io)
if ((io->cmd_tgt && (io->init_task_tag == ox_id)) &&
((rx_id == 0xffff) || (io->tgt_task_tag == rx_id))) {
break;
}
ocs_unlock(&node->active_ios_lock);
return io;
}
/**
* @ingroup io_alloc
* @brief Return IO context given the instance index.
*
* @par Description
* Returns a pointer to the IO context given by the instance index.
*
* @param ocs Pointer to driver structure.
* @param index IO instance index to return.
*
* @return Returns a pointer to the IO context, or NULL if not found.
*/
ocs_io_t *
ocs_io_get_instance(ocs_t *ocs, uint32_t index)
{
ocs_xport_t *xport = ocs->xport;
ocs_io_pool_t *io_pool = xport->io_pool;
return ocs_pool_get_instance(io_pool->pool, index);
}
/**
* @brief Generate IO context ddump data.
*
* The ddump data for an IO context is generated.
*
* @param textbuf Pointer to text buffer.
* @param io Pointer to IO context.
*
* @return None.
*/
void
ocs_ddump_io(ocs_textbuf_t *textbuf, ocs_io_t *io)
{
ocs_ddump_section(textbuf, "io", io->instance_index);
ocs_ddump_value(textbuf, "display_name", "%s", io->display_name);
ocs_ddump_value(textbuf, "node_name", "%s", io->node->display_name);
ocs_ddump_value(textbuf, "ref_count", "%d", ocs_ref_read_count(&io->ref));
ocs_ddump_value(textbuf, "io_type", "%d", io->io_type);
ocs_ddump_value(textbuf, "hio_type", "%d", io->hio_type);
ocs_ddump_value(textbuf, "cmd_tgt", "%d", io->cmd_tgt);
ocs_ddump_value(textbuf, "cmd_ini", "%d", io->cmd_ini);
ocs_ddump_value(textbuf, "send_abts", "%d", io->send_abts);
ocs_ddump_value(textbuf, "init_task_tag", "0x%x", io->init_task_tag);
ocs_ddump_value(textbuf, "tgt_task_tag", "0x%x", io->tgt_task_tag);
ocs_ddump_value(textbuf, "hw_tag", "0x%x", io->hw_tag);
ocs_ddump_value(textbuf, "tag", "0x%x", io->tag);
ocs_ddump_value(textbuf, "timeout", "%d", io->timeout);
ocs_ddump_value(textbuf, "tmf_cmd", "%d", io->tmf_cmd);
ocs_ddump_value(textbuf, "abort_rx_id", "0x%x", io->abort_rx_id);
ocs_ddump_value(textbuf, "busy", "%d", ocs_io_busy(io));
ocs_ddump_value(textbuf, "transferred", "%zu", io->transferred);
ocs_ddump_value(textbuf, "auto_resp", "%d", io->auto_resp);
ocs_ddump_value(textbuf, "exp_xfer_len", "%d", io->exp_xfer_len);
ocs_ddump_value(textbuf, "xfer_req", "%d", io->xfer_req);
ocs_ddump_value(textbuf, "seq_init", "%d", io->seq_init);
ocs_ddump_value(textbuf, "alloc_link", "%d", ocs_list_on_list(&io->io_alloc_link));
ocs_ddump_value(textbuf, "pending_link", "%d", ocs_list_on_list(&io->io_pending_link));
ocs_ddump_value(textbuf, "backend_link", "%d", ocs_list_on_list(&io->link));
if (io->hio) {
ocs_ddump_value(textbuf, "hw_tag", "%#x", io->hio->reqtag);
ocs_ddump_value(textbuf, "hw_xri", "%#x", io->hio->indicator);
ocs_ddump_value(textbuf, "hw_type", "%#x", io->hio->type);
} else {
ocs_ddump_value(textbuf, "hw_tag", "%s", "pending");
ocs_ddump_value(textbuf, "hw_xri", "%s", "pending");
ocs_ddump_value(textbuf, "hw_type", "%s", "pending");
}
ocs_scsi_ini_ddump(textbuf, OCS_SCSI_DDUMP_IO, io);
ocs_scsi_tgt_ddump(textbuf, OCS_SCSI_DDUMP_IO, io);
ocs_ddump_endsection(textbuf, "io", io->instance_index);
}
void
ocs_mgmt_io_list(ocs_textbuf_t *textbuf, void *object)
{
/* Readonly values */
ocs_mgmt_emit_property_name(textbuf, MGMT_MODE_RD, "display_name");
ocs_mgmt_emit_property_name(textbuf, MGMT_MODE_RD, "init_task_tag");
ocs_mgmt_emit_property_name(textbuf, MGMT_MODE_RD, "tag");
ocs_mgmt_emit_property_name(textbuf, MGMT_MODE_RD, "transferred");
ocs_mgmt_emit_property_name(textbuf, MGMT_MODE_RD, "auto_resp");
ocs_mgmt_emit_property_name(textbuf, MGMT_MODE_RD, "exp_xfer_len");
ocs_mgmt_emit_property_name(textbuf, MGMT_MODE_RD, "xfer_req");
}
int
ocs_mgmt_io_get(ocs_textbuf_t *textbuf, char *parent, char *name, void *object)
{
char qualifier[80];
int retval = -1;
ocs_io_t *io = (ocs_io_t *) object;
snprintf(qualifier, sizeof(qualifier), "%s/io[%d]", parent, io->instance_index);
/* If it doesn't start with my qualifier I don't know what to do with it */
if (ocs_strncmp(name, qualifier, strlen(qualifier)) == 0) {
char *unqualified_name = name + strlen(qualifier) +1;
/* See if it's a value I can supply */
if (ocs_strcmp(unqualified_name, "display_name") == 0) {
ocs_mgmt_emit_string(textbuf, MGMT_MODE_RD, "display_name", io->display_name);
retval = 0;
} else if (ocs_strcmp(unqualified_name, "init_task_tag") == 0) {
ocs_mgmt_emit_int(textbuf, MGMT_MODE_RD, "init_task_tag", "0x%x", io->init_task_tag);
retval = 0;
} else if (ocs_strcmp(unqualified_name, "tgt_task_tag") == 0) {
ocs_mgmt_emit_int(textbuf, MGMT_MODE_RD, "tgt_task_tag", "0x%x", io->tgt_task_tag);
retval = 0;
} else if (ocs_strcmp(unqualified_name, "hw_tag") == 0) {
ocs_mgmt_emit_int(textbuf, MGMT_MODE_RD, "hw_tag", "0x%x", io->hw_tag);
retval = 0;
} else if (ocs_strcmp(unqualified_name, "tag") == 0) {
ocs_mgmt_emit_int(textbuf, MGMT_MODE_RD, "tag", "0x%x", io->tag);
retval = 0;
} else if (ocs_strcmp(unqualified_name, "transferred") == 0) {
ocs_mgmt_emit_int(textbuf, MGMT_MODE_RD, "transferred", "%zu", io->transferred);
retval = 0;
} else if (ocs_strcmp(unqualified_name, "auto_resp") == 0) {
ocs_mgmt_emit_boolean(textbuf, MGMT_MODE_RD, "auto_resp", io->auto_resp);
retval = 0;
} else if (ocs_strcmp(unqualified_name, "exp_xfer_len") == 0) {
ocs_mgmt_emit_int(textbuf, MGMT_MODE_RD, "exp_xfer_len", "%d", io->exp_xfer_len);
retval = 0;
} else if (ocs_strcmp(unqualified_name, "xfer_req") == 0) {
ocs_mgmt_emit_int(textbuf, MGMT_MODE_RD, "xfer_req", "%d", io->xfer_req);
retval = 0;
}
}
return retval;
}
void
ocs_mgmt_io_get_all(ocs_textbuf_t *textbuf, void *object)
{
ocs_io_t *io = (ocs_io_t *) object;
ocs_mgmt_emit_string(textbuf, MGMT_MODE_RD, "display_name", io->display_name);
ocs_mgmt_emit_int(textbuf, MGMT_MODE_RD, "init_task_tag", "0x%x", io->init_task_tag);
ocs_mgmt_emit_int(textbuf, MGMT_MODE_RD, "tgt_task_tag", "0x%x", io->tgt_task_tag);
ocs_mgmt_emit_int(textbuf, MGMT_MODE_RD, "hw_tag", "0x%x", io->hw_tag);
ocs_mgmt_emit_int(textbuf, MGMT_MODE_RD, "tag", "0x%x", io->tag);
ocs_mgmt_emit_int(textbuf, MGMT_MODE_RD, "transferred", "%zu", io->transferred);
ocs_mgmt_emit_boolean(textbuf, MGMT_MODE_RD, "auto_resp", io->auto_resp);
ocs_mgmt_emit_int(textbuf, MGMT_MODE_RD, "exp_xfer_len", "%d", io->exp_xfer_len);
ocs_mgmt_emit_int(textbuf, MGMT_MODE_RD, "xfer_req", "%d", io->xfer_req);
}