/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2013, Nexenta Systems, Inc. All rights reserved.
*/
#include <sys/sysmacros.h>
#include <sys/stmf_ioctl.h>
#include <sys/pppt_ioctl.h>
#include "pppt.h"
/*
* DDI entry points.
*/
static boolean_t pppt_drv_busy(void);
extern void pppt_ic_so_disable();
extern void stmf_ic_rx_msg(char *, size_t);
extern struct mod_ops mod_miscops;
pppt_drv_open, /* cb_open */
pppt_drv_close, /* cb_close */
nodev, /* cb_strategy */
nodev, /* cb_print */
nodev, /* cb_dump */
nodev, /* cb_read */
nodev, /* cb_write */
pppt_drv_ioctl, /* cb_ioctl */
nodev, /* cb_devmap */
nodev, /* cb_mmap */
nodev, /* cb_segmap */
nochpoll, /* cb_chpoll */
ddi_prop_op, /* cb_prop_op */
NULL, /* cb_streamtab */
D_MP, /* cb_flag */
CB_REV, /* cb_rev */
nodev, /* cb_aread */
nodev, /* cb_awrite */
};
DEVO_REV, /* devo_rev */
0, /* devo_refcnt */
pppt_drv_getinfo, /* devo_getinfo */
nulldev, /* devo_identify */
nulldev, /* devo_probe */
pppt_drv_attach, /* devo_attach */
pppt_drv_detach, /* devo_detach */
nodev, /* devo_reset */
&pppt_cb_ops, /* devo_cb_ops */
NULL, /* devo_bus_ops */
NULL, /* devo_power */
ddi_quiesce_not_needed, /* quiesce */
};
"Proxy Port Provider",
};
&modldrv,
NULL,
};
int pppt_logging = 0;
static int pppt_enable_svc(void);
static void pppt_disable_svc(void);
static void pppt_sess_destroy_task(void *ps_void);
/*
* Lock order: global --> target --> session --> task
*/
int
_init(void)
{
int rc;
return (rc);
}
return (rc);
}
int
{
}
int
_fini(void)
{
int rc;
if (rc == 0) {
}
return (rc);
}
/*
* DDI entry points.
*/
/* ARGSUSED */
static int
void **result)
{
switch (cmd) {
case DDI_INFO_DEVT2DEVINFO:
return (DDI_SUCCESS);
case DDI_INFO_DEVT2INSTANCE:
return (DDI_SUCCESS);
default:
break;
}
return (DDI_FAILURE);
}
static int
{
if (cmd != DDI_ATTACH) {
return (DDI_FAILURE);
}
if (ddi_get_instance(dip) != 0) {
/* we only allow instance 0 to attach */
return (DDI_FAILURE);
}
/* create the minor node */
DDI_PSEUDO, 0) != DDI_SUCCESS) {
"failed creating minor node");
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*ARGSUSED*/
static int
{
if (cmd != DDI_DETACH)
return (DDI_FAILURE);
if (pppt_drv_busy()) {
return (EBUSY);
}
return (DDI_SUCCESS);
}
/*ARGSUSED*/
static int
{
int rc = 0;
switch (pppt_global.global_svc_state) {
case PSS_DISABLED:
rc = pppt_enable_svc();
if (rc == 0) {
} else {
}
break;
case PSS_DISABLING:
case PSS_ENABLING:
case PSS_ENABLED:
break;
default:
break;
}
return (rc);
}
/* ARGSUSED */
static int
{
int rc = 0;
switch (pppt_global.global_svc_state) {
case PSS_ENABLED:
/*
* release the door to the daemon
*/
}
break;
default:
break;
}
return (rc);
}
static boolean_t
pppt_drv_busy(void)
{
switch (pppt_global.global_svc_state) {
case PSS_DISABLED:
case PSS_DETACHED:
return (B_FALSE);
default:
return (B_TRUE);
}
/* NOTREACHED */
}
/* ARGSUSED */
static int
int *retval)
{
int rc;
void *buf;
return (EPERM);
}
if (rc)
return (EFAULT);
return (EINVAL);
switch (cmd) {
case PPPT_MESSAGE:
/* XXX limit buf_size ? */
return (ENOMEM);
if (rc) {
return (EFAULT);
}
break;
case PPPT_INSTALL_DOOR:
if (new_handle == NULL)
return (EINVAL);
/*
* There can only be one door installed
*/
return (EBUSY);
}
break;
}
return (rc);
}
/*
* pppt_enable_svc
*
* registers all the configured targets and target portals with STMF
*/
static int
pppt_enable_svc(void)
{
int rc = 0;
/*
* Make sure that can tell if we have partially allocated
* in case we need to exit and tear down anything allocated.
*/
pppt_tgt_avl_compare, sizeof (pppt_tgt_t),
pppt_sess_avl_compare_by_id, sizeof (pppt_sess_t),
/*
* Setup STMF dbuf store. Tf buffers are associated with a particular
* lport (FC, SRP) then the dbuf_store should stored in the lport
* context, otherwise (iSCSI) the dbuf_store should be global.
*/
if (dbuf_store == NULL) {
goto tear_down_and_return;
}
/* Register port provider */
goto tear_down_and_return;
}
pp->pp_instance = 0;
goto tear_down_and_return;
}
return (0);
if (pppt_global.global_sess_taskq) {
}
if (pppt_global.global_dispatch_taskq) {
}
if (pppt_global.global_pp)
if (pp)
if (pppt_global.global_dbuf_store) {
}
return (rc);
}
/*
* pppt_disable_svc
*
* clean up all existing sessions and deregister targets from STMF
*/
static void
pppt_disable_svc(void)
{
pppt_tgt_avl_compare, sizeof (pppt_tgt_t),
}
while ((tgt->target_refcount > 0) ||
}
}
}
/*
* STMF callbacks
*/
/*ARGSUSED*/
static stmf_data_buf_t *
{
/* Get buffer */
/*
* Allocate stmf buf with private port provider section
* (pppt_buf_t)
*/
/* Fill in pppt_buf_t */
/*
* Fill in stmf_data_buf_t. DB_DONT CACHE tells
* stmf not to cache buffers but STMF doesn't do
* that yet so it's a no-op. Port providers like
* FC and SRP that have buffers associated with the
* target port would want to let STMF cache
* the buffers. Port providers like iSCSI would
* not want STMF to cache because the buffers are
* really associated with a connection, not an
* STMF target port so there is no way for STMF
* to cache the buffers effectively. These port
* providers should cache buffers internally if
* there is significant buffer setup overhead.
*
* And of course, since STMF doesn't do any internal
* caching right now anyway, all port providers should
* do what they can to minimize buffer setup overhead.
*/
return (result);
} else {
/*
* Couldn't get the stmf_data_buf_t so free the
* buffer
*/
}
return (NULL);
}
/*ARGSUSED*/
static void
{
if (pbuf->pbuf_is_immed) {
} else {
}
}
/*ARGSUSED*/
{
/*
* If we are aborting then we can ignore this request, otherwise
* add a reference.
*/
return (STMF_SUCCESS);
}
/*
* If it's not immediate data then start the transfer
*/
/* Send read data */
if (ic_msg_status != STMF_IC_MSG_SUCCESS) {
return (STMF_FAILURE);
} else {
return (STMF_SUCCESS);
}
return (STMF_FAILURE);
}
return (STMF_INVALID_ARG);
}
void
{
/*
* Caller should have taken a task hold (likely via pppt_task_lookup)
*
* Get pppt_buf_t and stmf_data_buf_t pointers
*/
/*
* COMSTAR currently requires port providers to support
* the DB_SEND_STATUS_GOOD flag even if phase collapse is
* not supported. So we will roll our own... pretend we are
* COMSTAR and ask for a status message.
*/
(status == STMF_SUCCESS)) {
/*
* It's possible the task has been aborted since the time we
* looked it up. We need to release the hold before calling
* pppt_lport_send_status and as soon as we release the hold
* the task may disappear. Calling pppt_task_done allows us
* to determine whether the task has been aborted (in which
* case we will stop processing and return) and mark the task
* "done" which will prevent the task from being aborted while
* we are trying to send the status.
*/
/* STMF will free task and buffer(s) */
return;
}
!= STMF_SUCCESS) {
/* Failed to send status */
}
} else {
}
}
/*ARGSUSED*/
{
/*
* Mark task completed. If the state indicates it was aborted
* then we don't need to respond.
*/
return (STMF_SUCCESS);
}
/*
* Send status.
*/
0,
if (ic_msg_status != STMF_IC_MSG_SUCCESS) {
return (STMF_FAILURE);
} else {
return (STMF_SUCCESS);
}
}
void
{
}
/*ARGSUSED*/
{
/*
* This task is beyond the point where abort makes sense
* and we will soon be sending status. Tell STMF to
* go away.
*/
return (STMF_BUSY);
} else {
return (STMF_ABORT_SUCCESS);
}
/*NOTREACHED*/
}
/*ARGSUSED*/
void
{
switch (cmd) {
case STMF_CMD_LPORT_ONLINE:
case STMF_CMD_LPORT_OFFLINE:
break;
default:
ASSERT(0);
break;
}
}
{
int lport_cmp;
/*
* Look for existing session for this ID
*/
return (NULL);
}
if ((lport_devid->ident_length !=
(rport->rport_tptid_sz !=
return (NULL);
} else {
if (lport_cmp != 0 ||
return (NULL);
}
return (NULL);
}
}
return (ps);
}
{
return (ps);
}
}
return (NULL);
}
/* New session */
{
*statusp = STMF_SUCCESS;
/*
* Look for existing session for this ID
*/
return (ps);
}
/*
* No session with that ID, look for another session corresponding
* to the same IT nexus.
*/
return (NULL);
}
/* Can't create session to offline target */
return (NULL);
}
/*
* Look for an existing session on this IT nexus
*/
/*
* Now check the session ID. It should not match because if
* it did we would have found it on the global session list.
* If the session ID in the command is higher than the existing
* session ID then we need to tear down the existing session.
*/
/* Invalid session ID */
return (NULL);
} else {
/* Existing session needs to be invalidated */
}
}
/* Fallthrough and create new session */
}
/*
* Allocate and fill in pppt_session_t with the appropriate data
* for the protocol.
*/
/* Fill in session fields */
0);
return (NULL);
}
STMF_SUCCESS) {
return (NULL);
}
return (ps);
}
void
{
}
void
{
}
}
{
}
int
{
return (-1);
return (1);
/* Allow multiple duplicate sessions if one is closed */
return (-1);
return (1);
return (0);
}
int
{
int result;
/* Compare by tptid size */
return (-1);
return (1);
}
/* Now compare tptid */
if (result < 0) {
return (-1);
} else if (result > 0) {
return (1);
}
return (0);
}
void
{
STMF_ABORTED, NULL);
}
}
/*
* Now that all the tasks are aborting the session refcnt should
* go to 0.
*/
}
}
pppt_task_alloc(void)
{
sizeof (stmf_data_buf_t), KM_NOSLEEP);
ptask->pt_read_xfer_msgid = 0;
}
return (ptask);
}
void
{
sizeof (stmf_data_buf_t));
}
{
/* Manually increment refcnt, sincd we hold the mutex... */
return (PPPT_STATUS_SUCCESS);
}
return (PPPT_STATUS_FAIL);
}
{
case PTS_ACTIVE:
break;
case PTS_ABORTED:
break;
case PTS_DONE:
/* Repeat calls are OK. Do nothing, return success */
break;
default:
ASSERT(0);
}
if (remove) {
/* Out of the AVL tree, so drop a reference. */
}
return (pppt_status);
}
void
{
/*
* If STMF tries to abort a task after the task state changed to
* PTS_DONE (meaning all task processing is complete from
* the port provider perspective) then we return STMF_BUSY
* from pppt_lport_abort. STMF will return after a short interval
* but our calls to stmf_send_status_done will be ignored since
* STMF is aborting the task. That's where this state comes in.
* This state essentially says we are calling stmf_send_status_done
* so we will not be touching the task again. The next time
* STMF calls pppt_lport_abort we will return a success full
* status and the abort will succeed.
*/
}
{
if (pppt_task_hold(result) !=
}
return (result);
}
}
}
return (NULL);
}
static int
{
return (-1);
return (1);
return (0);
}
static pppt_status_t
{
case PTS_ACTIVE:
break;
case PTS_DONE:
break;
case PTS_SENT_STATUS:
/*
* Already removed so leave remove set to B_FALSE
* and leave status set to PPPT_STATUS_SUCCESS.
*/
break;
case PTS_ABORTED:
break;
default:
ASSERT(0);
}
if (remove) {
/* Out of the AVL tree, so drop a reference. */
}
return (pppt_status);
}
{
} else {
}
return (pppt_status);
}
static void
{
if (freeit)
}
static void
{
}