2N/A/*
2N/A * CDDL HEADER START
2N/A *
2N/A * The contents of this file are subject to the terms of the
2N/A * Common Development and Distribution License (the "License").
2N/A * You may not use this file except in compliance with the License.
2N/A *
2N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2N/A * or http://www.opensolaris.org/os/licensing.
2N/A * See the License for the specific language governing permissions
2N/A * and limitations under the License.
2N/A *
2N/A * When distributing Covered Code, include this CDDL HEADER in each
2N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2N/A * If applicable, add the following below this CDDL HEADER, with the
2N/A * fields enclosed by brackets "[]" replaced with your own identifying
2N/A * information: Portions Copyright [yyyy] [name of copyright owner]
2N/A *
2N/A * CDDL HEADER END
2N/A */
2N/A
2N/A/*
2N/A * Copyright (c) 2002-2003, Network Appliance, Inc. All rights reserved.
2N/A */
2N/A
2N/A/*
2N/A * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
2N/A * Use is subject to license terms.
2N/A */
2N/A
2N/A
2N/A/*
2N/A *
2N/A * MODULE: dapl_ia_open.c
2N/A *
2N/A * PURPOSE: Interface Adapter management
2N/A * Description: Interfaces in this file are completely described in
2N/A * the DAPL 1.1 API, Chapter 6, section 2
2N/A *
2N/A * $Id: dapl_ia_open.c,v 1.30 2003/07/31 14:04:17 jlentini Exp $
2N/A */
2N/A
2N/A#include "dapl.h"
2N/A#include "dapl_provider.h"
2N/A#include "dapl_evd_util.h"
2N/A#include "dapl_hca_util.h"
2N/A#include "dapl_ia_util.h"
2N/A#include "dapl_adapter_util.h"
2N/A#include <sys/systeminfo.h>
2N/A#include <libdevinfo.h>
2N/A
2N/A
2N/A/*
2N/A * LOCAL PROTOTYPES
2N/A */
2N/A#if defined(IBHOSTS_NAMING)
2N/Avoid dapli_assign_hca_ip_address(
2N/A DAPL_HCA *hca_ptr,
2N/A char *device_name);
2N/A#endif /* IBHOSTS_NAMING */
2N/A
2N/Astatic void dapli_hca_cleanup(DAPL_HCA *hca_ptr, DAT_BOOLEAN dec_ref);
2N/A
2N/A/*
2N/A * Determine whether the platform supports RO (Relaxed ordering)
2N/A * Return B_TRUE if it does support RO and B_FALSE if it does not support RO
2N/A *
2N/A * udapl_ro_disallowed is an out paramter returning whether or not
2N/A * relaxed ordering should be disabled (regardless of whether the platform
2N/A * is capable of supporting relaxed ordering)
2N/A *
2N/A */
2N/Astatic boolean_t
2N/Adapl_ro_disallowed(void)
2N/A{
2N/A static const char * const non_ro_capable_platforms[] = {
2N/A "i86pc",
2N/A "i86xpv",
2N/A "SUNW,Sun-Fire-V215",
2N/A "SUNW,Sun-Fire-V245",
2N/A "SUNW,Sun-Fire-V445",
2N/A "SUNW,Sun-Fire-T1000",
2N/A "SUNW,Sun-Fire-T200",
2N/A "SUNW,Sun-Blade-T6300",
2N/A "SUNW,Sun-Blade-T6320",
2N/A "SUNW,SPARC-Enterprise-T1000",
2N/A "SUNW,SPARC-Enterprise-T2000",
2N/A "SUNW,SPARC-Enterprise-T5120",
2N/A "SUNW,SPARC-Enterprise-T5220",
2N/A NULL
2N/A };
2N/A char platform[256 + 1];
2N/A register int i;
2N/A register const char *cp;
2N/A int ret;
2N/A di_node_t root_node, node;
2N/A boolean_t ro_disallowed;
2N/A static const char *ro_disallowed_property =
2N/A "pci-relaxed-ordering-disallowed";
2N/A int bool;
2N/A int *boolp = &bool;
2N/A
2N/A ret = sysinfo(SI_PLATFORM, platform, sizeof (platform));
2N/A if ((ret != -1) && (ret <= sizeof (platform))) {
2N/A for (i = 0; (cp = non_ro_capable_platforms[i]) != NULL; ++i) {
2N/A if (strcmp(platform, cp) == 0)
2N/A return (B_TRUE);
2N/A }
2N/A }
2N/A
2N/A /*
2N/A * This function only finds and looks at the FIRST udapl node.
2N/A * It is assumed that there can only be one such node.
2N/A */
2N/A if ((root_node = di_init("/", DINFOSUBTREE | DINFOPROP)) == DI_NODE_NIL)
2N/A return (B_FALSE);
2N/A
2N/A node = di_drv_first_node("daplt", root_node);
2N/A if (node != DI_NODE_NIL) {
2N/A ret = di_prop_lookup_ints(DDI_DEV_T_ANY, node,
2N/A ro_disallowed_property, &boolp);
2N/A switch (ret) {
2N/A case 0:
2N/A case 1:
2N/A ro_disallowed = B_TRUE;
2N/A break;
2N/A default:
2N/A ro_disallowed = B_FALSE;
2N/A break;
2N/A }
2N/A
2N/A }
2N/A else
2N/A ro_disallowed = B_FALSE;
2N/A
2N/A di_fini(root_node);
2N/A
2N/A return (ro_disallowed);
2N/A}
2N/A
2N/A/*
2N/A * dapl_ia_open
2N/A *
2N/A * DAPL Requirements Version xxx, 6.2.1.1
2N/A *
2N/A * Open a provider and return a handle. The handle enables the user
2N/A * to invoke operations on this provider.
2N/A *
2N/A * The dat_ia_open call is actually part of the DAT registration module.
2N/A * That function maps the DAT_NAME parameter of dat_ia_open to a DAT_PROVIDER,
2N/A * and calls this function.
2N/A *
2N/A * Input:
2N/A * provider
2N/A * async_evd_qlen
2N/A * async_evd_handle_ptr
2N/A *
2N/A * Output:
2N/A * async_evd_handle
2N/A * ia_handle
2N/A *
2N/A * Return Values:
2N/A * DAT_SUCCESS
2N/A * DAT_INSUFFICIENT_RESOURCES
2N/A * DAT_INVALID_PARAMETER
2N/A * DAT_INVALID_HANDLE
2N/A * DAT_NAME_NOT_FOUND (returned by dat registry if necessary)
2N/A */
2N/ADAT_RETURN
2N/Adapl_ia_open(
2N/A IN const DAT_NAME_PTR name,
2N/A IN DAT_COUNT async_evd_qlen,
2N/A INOUT DAT_EVD_HANDLE *async_evd_handle_ptr,
2N/A OUT DAT_IA_HANDLE *ia_handle_ptr,
2N/A IN boolean_t ro_aware_client)
2N/A{
2N/A DAT_RETURN dat_status;
2N/A DAT_PROVIDER *provider;
2N/A DAPL_HCA *hca_ptr;
2N/A DAPL_IA *ia_ptr;
2N/A DAPL_EVD *evd_ptr;
2N/A boolean_t ro_disallowed;
2N/A
2N/A dat_status = DAT_SUCCESS;
2N/A hca_ptr = NULL;
2N/A ia_ptr = NULL;
2N/A
2N/A dapl_dbg_log(DAPL_DBG_TYPE_API,
2N/A "dapl_ia_open(%s, %d, %p, %p, %d)\n",
2N/A name,
2N/A async_evd_qlen,
2N/A async_evd_handle_ptr,
2N/A ia_handle_ptr,
2N/A ro_aware_client);
2N/A
2N/A dat_status = dapl_provider_list_search(name, &provider);
2N/A if (DAT_SUCCESS != dat_status) {
2N/A dapl_dbg_log(DAPL_DBG_TYPE_API,
2N/A "dapl_ia_open: dapl_provider_list_search(\"%s\") returned "
2N/A "%d\n",
2N/A name,
2N/A dat_status);
2N/A
2N/A dat_status = DAT_ERROR(DAT_INVALID_PARAMETER, DAT_INVALID_ARG1);
2N/A goto bail;
2N/A }
2N/A
2N/A /* ia_handle_ptr and async_evd_handle_ptr cannot be NULL */
2N/A if (ia_handle_ptr == NULL) {
2N/A dat_status = DAT_ERROR(DAT_INVALID_PARAMETER, DAT_INVALID_ARG4);
2N/A goto bail;
2N/A }
2N/A if (async_evd_handle_ptr == NULL) {
2N/A dat_status = DAT_ERROR(DAT_INVALID_PARAMETER, DAT_INVALID_ARG3);
2N/A goto bail;
2N/A }
2N/A
2N/A /* initialize the caller's OUT param */
2N/A *ia_handle_ptr = DAT_HANDLE_NULL;
2N/A
2N/A /* get the hca_ptr */
2N/A hca_ptr = (DAPL_HCA *)provider->extension;
2N/A
2N/A /*
2N/A * Open the HCA if it has not been done before.
2N/A */
2N/A dapl_os_lock(&hca_ptr->lock);
2N/A if (hca_ptr->ib_hca_handle == IB_INVALID_HANDLE) {
2N/A /* register with the HW */
2N/A dat_status = dapls_ib_open_hca(hca_ptr,
2N/A &hca_ptr->ib_hca_handle);
2N/A
2N/A if (dat_status != DAT_SUCCESS) {
2N/A dapl_dbg_log(DAPL_DBG_TYPE_ERR,
2N/A "dapls_ib_open_hca failed %d\n", dat_status);
2N/A dapl_os_unlock(&hca_ptr->lock);
2N/A goto bail;
2N/A }
2N/A
2N/A /* create a cq domain for this HCA */
2N/A dat_status = dapls_ib_cqd_create(hca_ptr);
2N/A
2N/A if (dat_status != DAT_SUCCESS) {
2N/A dapl_dbg_log(DAPL_DBG_TYPE_ERR,
2N/A "ERR: Cannot allocate CQD: err %x\n", dat_status);
2N/A dapli_hca_cleanup(hca_ptr, DAT_FALSE);
2N/A dapl_os_unlock(&hca_ptr->lock);
2N/A goto bail;
2N/A }
2N/A /*
2N/A * Obtain the IP address associated with this name and HCA.
2N/A */
2N/A
2N/A#ifdef IBHOSTS_NAMING
2N/A dapli_assign_hca_ip_address(hca_ptr, name);
2N/A#endif /* IBHOSTS_NAMING */
2N/A
2N/A /*
2N/A * Obtain IA attributes from the HCA to limit certain
2N/A * operations.
2N/A * If using DAPL_ATS naming, ib_query_hca will also set the ip
2N/A * address.
2N/A */
2N/A dat_status = dapls_ib_query_hca(hca_ptr,
2N/A &hca_ptr->ia_attr,
2N/A NULL,
2N/A &hca_ptr->hca_address, NULL);
2N/A if (dat_status != DAT_SUCCESS) {
2N/A dapli_hca_cleanup(hca_ptr, DAT_FALSE);
2N/A dapl_os_unlock(&hca_ptr->lock);
2N/A goto bail;
2N/A }
2N/A }
2N/A
2N/A /* is the IA going to use the ConnectX? */
2N/A if (hca_ptr->hermon_resize_cq != 0) {
2N/A /*
2N/A * We are running with a ConnectX.
2N/A * Determine whether platform is RO capable.
2N/A * If platform support RO and client does not
2N/A * support RO and we are not disabling RO, reject the open.
2N/A */
2N/A ro_disallowed = dapl_ro_disallowed();
2N/A
2N/A if (! ro_aware_client && ! ro_disallowed) {
2N/A dapl_dbg_log(DAPL_DBG_TYPE_API,
2N/A "dapl_ia_open: failing ro_disallowed %d "
2N/A "ro_aware_client %d \n",
2N/A ro_disallowed, ro_aware_client);
2N/A
2N/A dat_status = DAT_ERROR(DAT_INVALID_PARAMETER,
2N/A DAT_INVALID_RO_COOKIE);
2N/A dapli_hca_cleanup(hca_ptr, DAT_FALSE);
2N/A dapl_os_unlock(&hca_ptr->lock);
2N/A goto bail;
2N/A }
2N/A } else {
2N/A /* We are not running with a Connect X */
2N/A ro_disallowed = B_TRUE;
2N/A }
2N/A
2N/A
2N/A /* Take a reference on the hca_handle */
2N/A dapl_os_atomic_inc(&hca_ptr->handle_ref_count);
2N/A dapl_os_unlock(&hca_ptr->lock);
2N/A
2N/A /* Allocate and initialize ia structure */
2N/A ia_ptr = dapl_ia_alloc(provider, hca_ptr);
2N/A if (!ia_ptr) {
2N/A dapl_os_lock(&hca_ptr->lock);
2N/A dapli_hca_cleanup(hca_ptr, DAT_TRUE);
2N/A dapl_os_unlock(&hca_ptr->lock);
2N/A dat_status = DAT_ERROR(DAT_INSUFFICIENT_RESOURCES,
2N/A DAT_RESOURCE_MEMORY);
2N/A goto bail;
2N/A }
2N/A
2N/A /*
2N/A * Note when we should be disabling relaxed ordering.
2N/A * If the property indicates that we should not use relaxed ordering
2N/A * we remember that fact. If the platform is supposed to be
2N/A * non relaxed ordering capable, we disable relaxed ordering as
2N/A * well, just in case the property or the list indicating that
2N/A * this platform is not relaxed ordering capable is mistaken.
2N/A */
2N/A if (ro_disallowed)
2N/A ia_ptr->dapl_flags |= DAPL_DISABLE_RO;
2N/A
2N/A /*
2N/A * we need an async EVD for this IA
2N/A * use the one passed in (if non-NULL) or create one
2N/A */
2N/A
2N/A evd_ptr = (DAPL_EVD *) *async_evd_handle_ptr;
2N/A if (evd_ptr) {
2N/A if (DAPL_BAD_HANDLE(evd_ptr, DAPL_MAGIC_EVD) ||
2N/A ! (evd_ptr->evd_flags & DAT_EVD_ASYNC_FLAG)) {
2N/A dat_status = DAT_ERROR(DAT_INVALID_HANDLE,
2N/A DAT_INVALID_HANDLE_EVD_ASYNC);
2N/A goto bail;
2N/A }
2N/A /*
2N/A * InfiniBand allows only 1 asychronous event handler per HCA
2N/A * (see InfiniBand Spec, release 1.1, vol I, section 11.5.2,
2N/A * page 559).
2N/A *
2N/A * We only need to make sure that this EVD's CQ belongs to
2N/A * the same HCA as is being opened.
2N/A */
2N/A
2N/A if (evd_ptr->header.owner_ia->hca_ptr->ib_hca_handle !=
2N/A hca_ptr->ib_hca_handle) {
2N/A dat_status = DAT_ERROR(DAT_INVALID_HANDLE,
2N/A DAT_INVALID_HANDLE_EVD_ASYNC);
2N/A goto bail;
2N/A }
2N/A
2N/A ia_ptr->cleanup_async_error_evd = DAT_FALSE;
2N/A ia_ptr->async_error_evd = evd_ptr;
2N/A } else {
2N/A /*
2N/A * Verify we have >0 length, and let the provider check the
2N/A * size
2N/A */
2N/A if (async_evd_qlen <= 0) {
2N/A dat_status = DAT_ERROR(DAT_INVALID_PARAMETER,
2N/A DAT_INVALID_ARG2);
2N/A goto bail;
2N/A }
2N/A dat_status = dapls_evd_internal_create(ia_ptr,
2N/A NULL, /* CNO ptr */
2N/A async_evd_qlen,
2N/A DAT_EVD_ASYNC_FLAG,
2N/A &evd_ptr);
2N/A if (dat_status != DAT_SUCCESS) {
2N/A goto bail;
2N/A }
2N/A
2N/A dapl_os_atomic_inc(&evd_ptr->evd_ref_count);
2N/A
2N/A dapl_os_lock(&hca_ptr->lock);
2N/A if (hca_ptr->async_evd != (DAPL_EVD *) 0) {
2N/A#if 0
2N/A /*
2N/A * The async EVD for this HCA has already been assigned.
2N/A * It's an error to try and assign another one.
2N/A *
2N/A * However, we need to somehow allow multiple IAs
2N/A * off of the same HCA. The right way to do this
2N/A * is by dispatching events off the HCA to the
2N/A * appropriate IA, but we aren't there yet. So for
2N/A * now we create the EVD but don't connect it to
2N/A * anything.
2N/A */
2N/A dapl_os_atomic_dec(&evd_ptr->evd_ref_count);
2N/A dapl_evd_free(evd_ptr);
2N/A dat_status = DAT_ERROR(DAT_INVALID_PARAMETER,
2N/A DAT_INVALID_ARG4);
2N/A goto bail;
2N/A#endif
2N/A dapl_os_unlock(&hca_ptr->lock);
2N/A } else {
2N/A hca_ptr->async_evd = evd_ptr;
2N/A dapl_os_unlock(&hca_ptr->lock);
2N/A
2N/A /*
2N/A * Register the handlers associated with the async EVD.
2N/A */
2N/A dat_status = dapls_ia_setup_callbacks(ia_ptr, evd_ptr);
2N/A if (dat_status != DAT_SUCCESS) {
2N/A /* Assign the EVD so it gets cleaned up */
2N/A ia_ptr->cleanup_async_error_evd = DAT_TRUE;
2N/A ia_ptr->async_error_evd = evd_ptr;
2N/A goto bail;
2N/A }
2N/A }
2N/A
2N/A ia_ptr->cleanup_async_error_evd = DAT_TRUE;
2N/A ia_ptr->async_error_evd = evd_ptr;
2N/A }
2N/A
2N/A dat_status = DAT_SUCCESS;
2N/A *ia_handle_ptr = ia_ptr;
2N/A *async_evd_handle_ptr = evd_ptr;
2N/A
2N/Abail:
2N/A if (dat_status != DAT_SUCCESS) {
2N/A if (ia_ptr) {
2N/A /* This will release the async EVD if needed. */
2N/A (void) dapl_ia_close(ia_ptr, DAT_CLOSE_ABRUPT_FLAG);
2N/A }
2N/A }
2N/A
2N/A dapl_dbg_log(DAPL_DBG_TYPE_RTN,
2N/A "dapl_ia_open () returns 0x%x\n",
2N/A dat_status);
2N/A
2N/A return (dat_status);
2N/A}
2N/A
2N/A/*
2N/A * dapli_hca_cleanup
2N/A *
2N/A * Clean up partially allocated HCA stuff. Strictly to make cleanup
2N/A * simple.
2N/A */
2N/Avoid
2N/Adapli_hca_cleanup(
2N/A DAPL_HCA *hca_ptr,
2N/A DAT_BOOLEAN dec_ref)
2N/A{
2N/A (void) dapls_ib_close_hca(hca_ptr->ib_hca_handle);
2N/A hca_ptr->ib_hca_handle = IB_INVALID_HANDLE;
2N/A if (dec_ref == DAT_TRUE) {
2N/A dapl_os_atomic_dec(&hca_ptr->handle_ref_count);
2N/A }
2N/A}
2N/A
2N/A#if defined(IBHOSTS_NAMING)
2N/A
2N/Achar *dapli_get_adapter_num(
2N/A char *device_name);
2N/A
2N/Avoid dapli_setup_dummy_addr(
2N/A IN DAPL_HCA *hca_ptr,
2N/A IN char *hca_name);
2N/A/*
2N/A * dapli_assign_hca_ip_address
2N/A *
2N/A * Obtain the IP address of the passed in name, which represents a
2N/A * port on the hca. There are three methods here to obtain the
2N/A * appropriate IP address, each with their own shortcoming:
2N/A * 1) IPOIB_NAMING. Requires the implementation of the IPoIB
2N/A * interface defined in include/dapl/ipoib_names.h. This is
2N/A * not the recommended interface as IPoIB is limited at
2N/A * the point we need to obtain an IP address on the
2N/A * passive side of a connection. The code supporting this
2N/A * implementation has been removed.
2N/A *
2N/A * 2) IBHOSTS. An entry exists in DNS and in the /etc/dapl/ibhosts
2N/A * file. The immediate drawback here is that we must dictate
2N/A * how to name the interface, which is a stated DAPL non-goal.
2N/A * In the broader perspective, this method requires us to xmit
2N/A * the IP address in the private data of a connection, which has
2N/A * other fun problems. This is the default method and is known to
2N/A * work, but it has problems.
2N/A *
2N/A * 3) Obtain the IP address from the driver, which has registered
2N/A * the address with the SA for retrieval.
2N/A *
2N/A *
2N/A * Input:
2N/A * hca_ptr Pointer to HCA structure
2N/A * device_name Name of device as reported by the provider
2N/A *
2N/A * Output:
2N/A * none
2N/A *
2N/A * Returns:
2N/A * char * to string number
2N/A */
2N/Avoid
2N/Adapli_assign_hca_ip_address(
2N/A DAPL_HCA *hca_ptr,
2N/A char *device_name)
2N/A{
2N/A char *adapter_num;
2N/A#define NAMELEN 128
2N/A struct addrinfo *addr;
2N/A char hostname[NAMELEN];
2N/A char *str;
2N/A int rc;
2N/A
2N/A /*
2N/A * Obtain the IP address of the adapter. This is a simple
2N/A * scheme that creates a name that must appear available to
2N/A * DNS, e.g. it must be in the local site DNS or in the local
2N/A * /etc/hosts file, etc.
2N/A *
2N/A * <hostname>-ib<index>
2N/A *
2N/A * This scheme obviously doesn't work with adapters from
2N/A * multiple vendors, but will suffice in common installations.
2N/A */
2N/A
2N/A rc = gethostname(hostname, NAMELEN);
2N/A /*
2N/A * Strip off domain info if it exists (e.g. mynode.mydomain.com)
2N/A */
2N/A for (str = hostname; *str && *str != '.'; ) {
2N/A str++;
2N/A }
2N/A if (*str == '.') {
2N/A *str = '\0';
2N/A }
2N/A dapl_os_strcat(hostname, "-ib");
2N/A adapter_num = dapli_get_adapter_num(device_name);
2N/A dapl_os_strcat(hostname, adapter_num);
2N/A
2N/A rc = dapls_osd_getaddrinfo(hostname, &addr);
2N/A
2N/A if (rc != 0) {
2N/A /* Not registered in DNS, provide a dummy value */
2N/A dapli_setup_dummy_addr(hca_ptr, hostname);
2N/A } else {
2N/A /*
2N/A * hca_address is defined as a DAT_SOCK_ADDR6 whereas ai_addr
2N/A * is a sockaddr
2N/A */
2N/A (void) dapl_os_memcpy((void *)&hca_ptr->hca_address,
2N/A (void *)(addr->ai_addr), sizeof (DAT_SOCK_ADDR6));
2N/A }
2N/A}
2N/A
2N/A
2N/A/*
2N/A * dapli_stup_dummy_addr
2N/A *
2N/A * Set up a dummy local address for the HCA. Things are not going
2N/A * to work too well if this happens.
2N/A * We call this routine if:
2N/A * - remote host adapter name is not in DNS
2N/A * - IPoIB implementation is not correctly set up
2N/A * - Similar nonsense.
2N/A *
2N/A * Input:
2N/A * hca_ptr
2N/A * rhost_name Name of remote adapter
2N/A *
2N/A * Output:
2N/A * none
2N/A *
2N/A * Returns:
2N/A * none
2N/A */
2N/Avoid
2N/Adapli_setup_dummy_addr(
2N/A IN DAPL_HCA *hca_ptr,
2N/A IN char *rhost_name)
2N/A{
2N/A struct sockaddr_in *si;
2N/A
2N/A /* Not registered in DNS, provide a dummy value */
2N/A dapl_dbg_log(DAPL_DBG_TYPE_ERR, "WARNING: <%s> not registered in DNS,"
2N/A " using dummy IP value\n", rhost_name);
2N/A si = (struct sockaddr_in *)&hca_ptr->hca_address;
2N/A si->sin_family = AF_INET;
2N/A si->sin_addr.s_addr = 0x01020304;
2N/A}
2N/A
2N/A
2N/A/*
2N/A * dapls_get_adapter_num
2N/A *
2N/A * Given a device name, return a string of the device number
2N/A *
2N/A * Input:
2N/A * device_name Name of device as reported by the provider
2N/A *
2N/A * Output:
2N/A * none
2N/A *
2N/A * Returns:
2N/A * char * to string number
2N/A */
2N/Achar *
2N/Adapli_get_adapter_num(
2N/A char *device_name)
2N/A{
2N/A static char *zero = "0";
2N/A char *p;
2N/A
2N/A /*
2N/A * Optimisticaly simple algorithm: the device number appears at
2N/A * the end of the device name string. Device that do not end
2N/A * in a number are by default "0".
2N/A */
2N/A
2N/A for (p = device_name; *p; p++) {
2N/A if (isdigit(*p)) {
2N/A return (p);
2N/A }
2N/A }
2N/A
2N/A return (zero);
2N/A}
2N/A#endif /* IBHOSTS_NAMING */
2N/A
2N/A
2N/A/*
2N/A * Local variables:
2N/A * c-indent-level: 4
2N/A * c-basic-offset: 4
2N/A * tab-width: 8
2N/A * End:
2N/A */