/*
* 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
*/
/*
*/
#include <sys/sysmacros.h>
#include <sys/iscsi_protocol.h>
/*
* Routines for InfiniBand transport for iSER
*
* This file contains the routines to interface with the IBT API to attach and
* allocate IB resources, handle async events, and post recv work requests.
*
*/
static int iser_ib_init_hcas(void);
static int iser_ib_fini_hcas(void);
static iser_sbind_t *iser_ib_get_bind(
static int iser_ib_activate_port(
static void iser_ib_post_recv_task(void *arg);
NULL,
"iSER"
};
/*
* iser_ib_init
*
* This function registers the HCA drivers with IBTF and registers and binds
* iSER as a service with IBTF.
*/
int
iser_ib_init(void)
{
int status;
/* Register with IBTF */
&iser_state->is_ibhdl);
if (status != DDI_SUCCESS) {
status);
return (DDI_FAILURE);
}
/* Create the global work request kmem_cache */
/* Populate our list of HCAs */
status = iser_ib_init_hcas();
if (status != DDI_SUCCESS) {
/* HCAs failed to initialize, tear it down */
return (DDI_FAILURE);
}
/* Target will register iSER as a service with IBTF when required */
/* Target will bind this service when it comes online */
return (DDI_SUCCESS);
}
/*
* iser_ib_fini
*
* This function unbinds and degisters the iSER service from IBTF
*/
int
iser_ib_fini(void)
{
/* IDM would have already disabled all the services */
/* Teardown the HCA list and associated resources */
if (iser_ib_fini_hcas() != DDI_SUCCESS)
return (DDI_FAILURE);
/* Teardown the global work request kmem_cache */
/* Deregister with IBTF */
}
return (DDI_SUCCESS);
}
/*
* iser_ib_register_service
*
* This function registers the iSER service using the RDMA-Aware Service ID.
*/
int
{
int status;
/* Set up IBTI client callback handler from the CM */
/* Register the service on the specified port */
return (status);
}
/*
* iser_ib_bind_service
*
* This function binds a given iSER service on all available HCA ports. The
* current specification does not allow user to specify transport bindings
* for each iscsi target. The ULP invokes this function to bind the target
* to all available iser ports after checking for the presence of an IB HCA.
* iSER is "configured" whenever an IB-capable IP address exists. The lack
* of active IB ports is a less-fatal condition, and sockets would be used
* as the transport even though an Infiniband HCA is configured but unusable.
*
*/
int
{
int num_ports = 0;
int num_binds = 0;
int status;
int i;
/* Register the iSER service on all available ports */
for (i = 0; i < hca->hca_num_ports; i++) {
num_ports++;
/*
* Move on. We will attempt to bind service
* in our async handler if the port comes up
* at a later time.
*/
continue;
}
/* If the port is already bound, skip */
if (iser_ib_get_bind(
if (status != IBT_SUCCESS) {
"iser_ib_bind_service: "
"iser_ib_activate_port failure "
"(0x%x)", status);
continue;
}
}
num_binds++;
}
}
if (num_binds) {
return (ISER_STATUS_SUCCESS);
} else if (num_inactive_binds) {
"service, HCA ports are not active.");
/*
* still considered success, the async handler will bind
* the service when the port comes up at a later time
*/
return (ISER_STATUS_SUCCESS);
} else {
return (ISER_STATUS_FAIL);
}
}
/*
* iser_ib_unbind_service
*
* This function unbinds a given service on a all HCA ports
*/
void
{
}
}
}
/* ARGSUSED */
void
{
}
}
/*
* iser_ib_get_paths
* This function finds the IB path between the local and the remote address.
*
*/
int
{
int status;
if (status != IBT_SUCCESS) {
"failure: status (%d)", status);
return (status);
}
} else {
}
return (ISER_STATUS_SUCCESS);
}
/*
* iser_ib_alloc_channel_nopathlookup
*
* This function allocates a reliable connected channel. This function does
* not invoke ibt_get_ip_paths() to do the path lookup. The HCA GUID and
* port are input to this function.
*/
{
/* Lookup the hca using the gid in the path info */
return (NULL);
}
"to alloc channel on HCA(%llx) %d",
return (NULL);
}
"chanhdl (0x%p), HCA(%llx) %d",
return (chan);
}
/*
* iser_ib_alloc_channel_pathlookup
*
* This function allocates a reliable connected channel but first invokes
* ibt_get_ip_paths() with the given local and remote addres to get the
* HCA lgid and the port number.
*/
{
int status;
/* Lookup a path to the given destination */
if (status != ISER_STATUS_SUCCESS) {
"Path lookup IP:[%llx to %llx] failed: status (%d)",
status);
return (NULL);
}
/* get the local gid from the path info */
/* get the hca port from the path info */
/* Lookup the hca using the gid in the path info */
"to lookup HCA (%llx) handle",
return (NULL);
}
"to alloc channel from IP:[%llx to %llx] on HCA (%llx) %d",
return (NULL);
}
"chanhdl (0x%p), IP:[%llx to %llx], lgid (%llx:%llx), HCA(%llx) %d",
(void *)chan->ic_chanhdl,
return (chan);
}
/*
* iser_ib_alloc_rc_channel
*
* This function allocates a reliable communication channel using the specified
* channel attributes.
*/
{
int status;
/* Set up the iSER channel handle with HCA */
/*
* Determine the queue sizes, based upon the HCA query data.
* For our Work Queues, we will use either our default value,
* or the HCA's maximum value, whichever is smaller.
*/
/*
* For our Completion Queues, we again check the device maximum.
* We want to end up with CQs that are the next size up from the
* WQs they are servicing so that they have some overhead.
*/
} else {
}
} else {
}
/* Initialize the iSER channel's QP handle */
/* Set up the Send Completion Queue */
if (status != ISER_STATUS_SUCCESS) {
return (NULL);
}
/* Set up the Receive Completion Queue */
if (status != ISER_STATUS_SUCCESS) {
return (NULL);
}
/* Setup the channel arguments */
if (status != IBT_SUCCESS) {
"ibt_alloc_rc_channel: status (%d)", status);
return (NULL);
}
/* Set the 'channel' as the client private data */
return (chan);
}
/*
* iser_ib_open_rc_channel
* This function opens a RC connection on the given allocated RC channel
*/
int
{
int status;
/*
* For connection establishment, the initiator sends a CM REQ using the
* iSER RDMA-Aware Service ID. Included are the source and destination
* IP addresses, and the src port.
*/
/*
* The CM Private Data field defines the iSER connection parameters
* such as zero based virtual address exception (ZBVAE) and Send with
* invalidate Exception (SIE).
*
* Solaris IBT does not currently support ZBVAE or SIE.
*/
iser_priv_data.rsvd1 = 0;
sizeof (iser_private_data_t), &iser_priv_data);
if (status != IBT_SUCCESS) {
return (status);
}
/*
* Set the SID we are attempting to connect to, based upon the
* remote port number.
*/
/* Set up the args for the channel open */
if (status != IBT_SUCCESS) {
return (status);
}
return (IDM_STATUS_SUCCESS);
}
/*
* iser_ib_close_rc_channel
* This function closes the RC channel related to this iser_chan handle.
* We invoke this in a non-blocking, no callbacks context.
*/
void
{
int status;
if (status != IBT_SUCCESS) {
"ibt_close_rc_channel failed: status (%d)", status);
}
}
/*
* iser_ib_free_rc_channel
*
* This function tears down an RC channel's QP initialization and frees it.
* Note that we do not need synchronization here; the channel has been
* closed already, so we should only have completion polling occuring. Once
* complete, we are free to free the IBTF channel, WQ and CQ resources, and
* our own related resources.
*/
void
{
/* Ensure the SQ is empty */
while (chan->ic_sq_post_count != 0) {
}
/* Ensure the RQ is empty */
}
/* Free our QP handle */
(void) iser_ib_fini_qp(iser_qp);
/* Free the IBT channel resources */
/* Free the CQs */
/* Free the chan handle */
}
/*
* iser_ib_post_recv
*
* This function handles keeping the RQ full on a given channel.
* This routine will mostly be run on a taskq, and will check the
* current fill level of the RQ, and post as many WRs as necessary
* to fill it again.
*/
int
{
int status;
/* Pull our iSER channel handle from the private data */
/*
* Caller must check that chan->ic_conn->ic_stage indicates
* the connection is active (not closing, not closed) and
* it must hold the mutex cross the check and the call to this function
*/
(void *)chanhdl, DDI_NOSLEEP);
if (status != DDI_SUCCESS) {
}
return (status);
}
static void
{
/* Pull our iSER channel handle from the private data */
}
void
{
int status, i;
/* Pull our iSER channel handle from the private data */
/* Bail out if the connection is closed; no need for more recv WRs */
return;
}
/* get the QP handle from the iser_chan */
"HCA handle");
return;
}
/* check for space to post on the RQ */
if (rq_space == 0) {
/* The RQ is full, clear the pending flag and return */
return;
}
/* Keep track of the lowest value for rq_min_post_level */
/* we've room to post, so pull from the msg cache */
"available in msg cache currently");
/*
* There are no messages on the cache. Wait a half-
* second, then try again.
*/
if (status != DDI_SUCCESS) {
"redispatch routine");
/* Failed to dispatch, clear pending flag */
}
return;
}
"messages not allocated: requested (%d) allocated (%d)",
/* We got some, but not all, of our requested depth */
}
/*
* Now, walk through the allocated WRs and post them,
* ISER_IB_RQ_POST_MAX (or less) at a time.
*/
while (total_num) {
/* determine the number to post on this iteration */
/* build a list of WRs from the msg list */
for (i = 0; i < npost; i++) {
}
/* post the list to the RQ */
nposted = 0;
"failed: requested (%d) posted (%d) status (%d)",
break;
}
/* decrement total number to post by the number posted */
}
if (total_num != 0) {
"failed to post (%d) WRs", total_num);
} else {
}
/*
* Now that we've filled the RQ, check that all of the recv WRs
* haven't just been immediately consumed. If so, taskqpending is
* still B_TRUE, so we need to fire off a taskq thread to post
* more WRs.
*/
if (status != DDI_SUCCESS) {
"dispatch followup routine");
/* Failed to dispatch, clear pending flag */
}
} else {
/*
* We're done, we've filled the RQ. Clear the taskq
* flag so that we can run again.
*/
}
}
/*
* iser_ib_handle_portup_event()
* This handles the IBT_EVENT_PORT_UP unaffiliated asynchronous event.
*
* To facilitate a seamless bringover of the port and configure the CM service
* for inbound iSER service requests on this newly active port, the existing
* IDM services will be checked for iSER support.
* If an iSER service was already created, then this service will simply be
* bound to the gid of the newly active port. If on the other hand, the CM
* service did not exist, i.e. only socket communication, then a new CM
* service will be first registered with the saved service parameters and
* then bound to the newly active port.
*
*/
/* ARGSUSED */
static void
{
int status;
/*
* Query all ports on the HCA and update the port information
* maintainted in the iser_hca_t structure
*/
/* HCA is just made available, first port on that HCA */
"iser_ib_alloc_hca failed: HCA(0x%llx) port(%d)",
return;
}
} else {
if (status != IBT_SUCCESS) {
"status(0x%x): iser_ib_update_hcaports failed: "
"HCA(0x%llx) port(%d)", status,
return;
}
}
/*
* Iterate through the global list of IDM target services
* and check for existing iSER CM service.
*/
/* Establish a new CM service for iSER requests */
if (status != IBT_SUCCESS) {
"status(0x%x): iser_tgt_svc_create failed: "
"HCA(0x%llx) port(%d)", status,
continue;
}
}
if (status != IBT_SUCCESS) {
"status(0x%x): Bind service on port "
"(%llx:%llx) failed",
continue;
}
}
}
/*
* iser_ib_handle_portdown_event()
* This handles the IBT_EVENT_PORT_DOWN unaffiliated asynchronous error.
*
* Unconfigure the CM service on the deactivated port and teardown the
* connections that are using the CM service.
*/
/* ARGSUSED */
static void
{
int status;
/*
* Query all ports on the HCA and update the port information
* maintainted in the iser_hca_t structure
*/
if (status != IBT_SUCCESS) {
"ibt_ib_update_hcaports failed: HCA(0x%llx) port(%d)",
return;
}
/* get the gid of the new port */
}
/*
* iser_ib_handle_hca_detach_event()
* Quiesce all activity bound for the port, teardown the connection, unbind
* iSER services on all ports and release the HCA handle.
*/
/* ARGSUSED */
static void
{
int i, status;
for (i = 0; i < hca->hca_num_ports; i++) {
}
/*
* Update the HCA list maintained in the iser_state. Free the
* resources allocated to the HCA, i.e. caches, protection domain
*/
if (status != DDI_SUCCESS) {
"Failed to free hca(%p)", (void *)hca);
}
/* No way to return status to IBT if this fails */
}
}
}
/*
* iser_ib_async_handler
* An IBT Asynchronous Event handler is registered it with the framework and
* passed via the ibt_attach() routine. This function handles the following
* asynchronous events.
* IBT_EVENT_PORT_UP
* IBT_ERROR_PORT_DOWN
* IBT_HCA_ATTACH_EVENT
* IBT_HCA_DETACH_EVENT
*/
/* ARGSUSED */
void
{
switch (code) {
case IBT_EVENT_PORT_UP:
break;
case IBT_ERROR_PORT_DOWN:
break;
case IBT_HCA_ATTACH_EVENT:
/*
* A new HCA device is available for use, ignore this
* event because the corresponding IBT_EVENT_PORT_UP
* events will get triggered and handled accordingly.
*/
break;
case IBT_HCA_DETACH_EVENT:
break;
default:
break;
}
}
/*
* iser_ib_init_hcas
*
* This function opens all the HCA devices, gathers the HCA state information
* and adds the HCA handle for each HCA found in the iser_soft_state.
*/
static int
iser_ib_init_hcas(void)
{
int num_hcas;
int i;
/* Retrieve the HCA list */
if (num_hcas == 0) {
/*
* This shouldn't happen, but might if we have all HCAs
* detach prior to initialization.
*/
return (DDI_FAILURE);
}
/* Initialize the hcalist lock */
/* Create the HCA list */
for (i = 0; i < num_hcas; i++) {
/* This shouldn't happen, teardown and fail */
(void) iser_ib_fini_hcas();
return (DDI_FAILURE);
}
}
/* Free the IBT HCA list */
/* Check that we've initialized at least one HCA */
"any HCAs");
(void) iser_ib_fini_hcas();
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*
* iser_ib_fini_hcas
*
* Teardown the iSER HCA list initialized above.
*/
static int
iser_ib_fini_hcas(void)
{
int status;
if (status != IBT_SUCCESS) {
"HCA during fini");
return (DDI_FAILURE);
}
}
return (DDI_SUCCESS);
}
/*
* iser_ib_alloc_hca
*
* This function opens the given HCA device, gathers the HCA state information
* and adds the HCA handle
*/
static iser_hca_t *
{
int status;
/* Allocate an iser_hca_t HCA handle */
/* Open this HCA */
if (status != IBT_SUCCESS) {
return (NULL);
}
/* Query the HCA */
if (status != IBT_SUCCESS) {
"failure: guid (0x%llx) status (0x%x)",
return (NULL);
}
/* Query all ports on the HCA */
&hca->hca_port_info_sz);
if (status != IBT_SUCCESS) {
"ibt_query_hca_ports failure: guid (0x%llx) "
return (NULL);
}
/* Allocate a single PD on this HCA */
if (status != IBT_SUCCESS) {
"failure: guid (0x%llx) status (0x%x)",
return (NULL);
}
/* Initialize the message and data MR caches for this HCA */
return (hca);
}
static int
{
int status;
if (hca->hca_failed)
return (DDI_FAILURE);
/*
* Free the memory regions before freeing
* the associated protection domain
*/
if (status != IBT_SUCCESS) {
"status=0x%x", status);
goto out_caches;
}
if (status != IBT_SUCCESS) {
"status=0x%x", status);
goto out_pd;
}
return (DDI_SUCCESS);
/*
* We only managed to partially tear down the HCA, try to put it back
* like it was before returning.
*/
if (status != IBT_SUCCESS) {
/* Report error and exit */
"status=0x%x", status);
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
static int
{
int status;
if (status != IBT_SUCCESS) {
return (status);
}
return (IBT_SUCCESS);
}
/*
* iser_ib_gid2hca
* Given a gid, find the corresponding hca
*/
{
int i;
for (i = 0; i < hca->hca_num_ports; i++) {
gid.gid_prefix) &&
return (hca);
}
}
}
return (NULL);
}
/*
* iser_ib_guid2hca
* Given a HCA guid, find the corresponding HCA
*/
{
return (hca);
}
}
return (NULL);
}
/*
* iser_ib_conv_sockaddr2ibtaddr
* This function converts a socket address into the IBT format
*/
{
} else {
case AF_INET:
break;
case AF_INET6:
break;
default:
}
}
}
/*
* iser_ib_conv_ibtaddr2sockaddr
* This function converts an IBT ip address handle to a sockaddr
*/
{
case AF_INET:
case AF_UNSPEC:
break;
case AF_INET6:
break;
default:
}
}
/*
* iser_ib_setup_cq
* This function sets up the Completion Queue size and allocates the specified
* Completion Queue
*/
static int
{
int status;
/* Allocate a Completion Queue */
if (status != IBT_SUCCESS) {
status);
return (status);
}
return (ISER_STATUS_SUCCESS);
}
/*
* iser_ib_setup_chanargs
*
*/
static void
{
/*
* Set up the size of the channels send queue, receive queue and the
* maximum number of elements in a scatter gather list of work requests
* posted to the send and receive queues.
*/
/*
* All Work requests signaled on a WR basis will receive a send
* request completion.
*/
/* Enable RDMA read and RDMA write on the channel end points */
/* Set the local hca port on which the channel is allocated */
/* Set the Send and Receive Completion Queue handles */
/* Set the protection domain associated with the channel */
/* No SRQ usage */
}
/*
* iser_ib_init_qp
* Initialize the QP handle
*/
void
{
/* Initialize the handle lock */
/* Record queue sizes */
/* Initialize the RQ monitoring data */
/* Initialize the taskq flag */
}
/*
* iser_ib_fini_qp
* Teardown the QP handle
*/
void
{
/* Destroy the handle lock */
}
static int
{
int status;
/*
* Save the address of the service bind handle in the
* iser_svc_t to undo the service binding at a later time
*/
if (status != IBT_SUCCESS) {
"Bind service(%llx) on port(%llx:%llx) failed",
return (status);
}
return (IBT_SUCCESS);
}
static void
{
/*
* Iterate through the global list of IDM target connections.
* Issue a TRANSPORT_FAIL for any connections on this port, and
* if there is a bound service running on the port, tear it down.
*/
/* this is not an iSER connection, skip it */
continue;
}
/* this iSER connection is on a different port */
continue;
}
/* Fail the transport for this connection */
/* initiator connection, nothing else to do */
continue;
}
/* Check for a service binding */
/* This service is still bound, tear it down */
}
}
}
static iser_sbind_t *
{
return (is_sbind);
}
}
return (NULL);
}