root/usr/src/uts/sun4v/io/vio_util.c
/*
 * 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
 * or http://www.opensolaris.org/os/licensing.
 * 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 (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
 */

#include <sys/types.h>
#include <sys/sysmacros.h>
#include <sys/errno.h>
#include <sys/kmem.h>
#include <sys/ksynch.h>
#include <sys/stream.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/vio_util.h>

static int vio_pool_cleanup_retries = 10;       /* Max retries to free pool */
static int vio_pool_cleanup_delay = 10000;      /* 10ms */

/*
 * Create a pool of mblks from which future vio_allocb() requests
 * will be serviced.
 *
 * NOTE: num_mblks has to non-zero and a power-of-2
 *
 * Returns
 *      0 on success
 *      EINVAL if num_mblks is zero or not a power of 2.
 *      ENOSPC if the pool could not be created due to alloc failures.
 */
int
vio_create_mblks(uint64_t num_mblks, size_t mblk_size, uint8_t *mblk_datap,
    vio_mblk_pool_t **poolp)
{
        vio_mblk_pool_t         *vmplp;
        vio_mblk_t              *vmp;
        uint8_t                 *datap;
        int                     i;
        int                     rv;

        if (!(num_mblks) || (!ISP2(num_mblks))) {
                *poolp = 0;
                return (EINVAL);
        }

        vmplp = kmem_zalloc(sizeof (*vmplp), KM_SLEEP);
        vmplp->quelen = num_mblks;
        vmplp->quemask = num_mblks - 1; /* expects quelen is power-of-2 */
        vmplp->mblk_size = mblk_size;

        mutex_init(&vmplp->hlock, NULL, MUTEX_DRIVER,
            DDI_INTR_PRI(DDI_INTR_SOFTPRI_DEFAULT));
        mutex_init(&vmplp->tlock, NULL, MUTEX_DRIVER,
            DDI_INTR_PRI(DDI_INTR_SOFTPRI_DEFAULT));

        vmplp->basep = kmem_zalloc(num_mblks * sizeof (vio_mblk_t), KM_SLEEP);
        if (mblk_datap == NULL) {
                vmplp->datap = kmem_zalloc(num_mblks * mblk_size, KM_SLEEP);
        } else {
                vmplp->datap = mblk_datap;
                vmplp->flag |= VMPL_FLAG_CLIENT_DATA;
        }
        vmplp->nextp = NULL;

        /* create a queue of pointers to free vio_mblk_t's */
        vmplp->quep = kmem_zalloc(vmplp->quelen *
            sizeof (vio_mblk_t *), KM_SLEEP);
        vmplp->head = 0;
        vmplp->tail =  0;

        for (i = 0, datap = vmplp->datap; i < num_mblks; i++) {

                vmp = &(vmplp->basep[i]);
                vmp->vmplp = vmplp;
                vmp->datap = datap;
                vmp->reclaim.free_func = vio_freeb;
                vmp->reclaim.free_arg = (caddr_t)vmp;
                vmp->mp = desballoc(vmp->datap, mblk_size, BPRI_MED,
                    &vmp->reclaim);

                if (vmp->mp == NULL) {
                        /* reset tail */
                        vmplp->tail = vmplp->head;

                        /*
                         * vio_destroy_mblks() frees mblks that have been
                         * allocated so far and then destroys the pool.
                         */
                        rv = vio_destroy_mblks(vmplp);
                        ASSERT(rv == 0);

                        *poolp = NULL;
                        return (ENOSPC);
                }

                vmp->index = i;
                vmp->state = VIO_MBLK_FREE;

                /* put this vmp on the free stack */
                vmplp->quep[vmplp->tail] = vmp;
                vmplp->tail = (vmplp->tail + 1) & vmplp->quemask;

                datap += mblk_size;
        }

        *poolp = vmplp;
        return (0);
}

/*
 * Destroy the pool of mblks. This can only succeed when
 * all allocated mblks have been returned to the pool.
 *
 * It is up to the caller to ensure that no further mblks are
 * requested from the pool after destroy has been invoked.
 *
 * Returns 0 on success, EINVAL if handle is invalid, or
 * EBUSY if not all mblks reclaimed yet.
 */
int
vio_destroy_mblks(vio_mblk_pool_t *vmplp)
{
        uint64_t        i;
        uint64_t        num_mblks;
        vio_mblk_t      *vmp;
        int             pool_cleanup_retries = 0;


        if (vmplp == NULL)
                return (EINVAL);

        /*
         * We can only destroy the pool once all the mblks have
         * been reclaimed.
         */
        do {
                if (vmplp->head == vmplp->tail) {
                        break;
                }

                /* some mblks still in use */
                drv_usecwait(vio_pool_cleanup_delay);
        } while (++pool_cleanup_retries < vio_pool_cleanup_retries);

        if (vmplp->head != vmplp->tail) {
                return (EBUSY);
        }

        num_mblks = vmplp->quelen;

        /*
         * Set pool flag to tell vio_freeb() which is invoked from freeb(),
         * that it is being called in the context of vio_destroy_mblks().
         * This results in freeing only mblk_t and dblk_t structures for
         * each mp. The associated data buffers are freed below as one big
         * chunk through kmem_free(vmplp->datap).
         */
        vmplp->flag |= VMPL_FLAG_DESTROYING;
        for (i = 0; i < num_mblks; i++) {
                vmp = &(vmplp->basep[i]);
                /*
                 * It is possible that mblks have been allocated only upto
                 * a certain index and the entire quelen has not been
                 * initialized. This might happen due to desballoc() failure
                 * while creating the pool. The below check handles this
                 * condition.
                 */
                if (vmp->mp != NULL)
                        freeb(vmp->mp);
        }
        vmplp->flag &= ~(VMPL_FLAG_DESTROYING);

        kmem_free(vmplp->basep, num_mblks * sizeof (vio_mblk_t));
        if ((vmplp->flag & VMPL_FLAG_CLIENT_DATA) == 0) {
                kmem_free(vmplp->datap, num_mblks * vmplp->mblk_size);
        }
        kmem_free(vmplp->quep, num_mblks * sizeof (vio_mblk_t *));

        mutex_destroy(&vmplp->hlock);
        mutex_destroy(&vmplp->tlock);

        kmem_free(vmplp, sizeof (*vmplp));

        return (0);
}

/*
 * Allocate a vio_mblk from the free pool if one is available.
 * Otherwise returns NULL.
 */
vio_mblk_t *
vio_allocb(vio_mblk_pool_t *vmplp)
{
        vio_mblk_t      *vmp = NULL;
        uint32_t        head;

        mutex_enter(&vmplp->hlock);
        head = (vmplp->head + 1) & vmplp->quemask;
        if (head != vmplp->tail) {
                /* we have free mblks */
                vmp = vmplp->quep[vmplp->head];
                vmplp->head = head;
                ASSERT(vmp->state == VIO_MBLK_FREE);
                vmp->state = VIO_MBLK_BOUND;
        }
        mutex_exit(&vmplp->hlock);

        return (vmp);
}

/*
 * Return a mblk to the free pool. Invoked when the upper IP
 * layers do freemsg() etc on the mblk they were passed.
 */
void
vio_freeb(void *arg)
{
        vio_mblk_t      *vmp = (vio_mblk_t *)arg;
        vio_mblk_pool_t *vmplp = vmp->vmplp;

        if (vmplp->flag & VMPL_FLAG_DESTROYING) {
                /*
                 * This flag indicates that freeb() is being called from
                 * vio_destroy_mblks().
                 * We don't need to alloc a new mblk_t/dblk_t pair for
                 * this data buffer, return from here and the data buffer
                 * itself will be freed in vio_destroy_mblks().
                 */
                return;
        }

        vmp->mp = desballoc(vmp->datap, vmplp->mblk_size,
            BPRI_MED, &vmp->reclaim);
        vmp->state = VIO_MBLK_FREE;

        mutex_enter(&vmplp->tlock);
        vmplp->quep[vmplp->tail] = vmp;
        vmplp->tail = (vmplp->tail + 1) & vmplp->quemask;
        mutex_exit(&vmplp->tlock);
}


/*
 * This function searches the given mblk pool for mblks that are in the
 * BOUND state and moves them to the FREE state. Note that only clients that
 * are operating in RxDringData mode use this function. This allows such
 * clients to reclaim buffers that are provided to the peer as shared memory,
 * before calling vio_destroy_mblks(). We don't need this in other cases
 * as the buffer is locally managed.
 */
void
vio_clobber_pool(vio_mblk_pool_t *vmplp)
{
        uint64_t        num_mblks = vmplp->quelen;
        uint64_t        i;
        vio_mblk_t      *vmp;

        mutex_enter(&vmplp->hlock);
        mutex_enter(&vmplp->tlock);
        for (i = 0; i < num_mblks; i++) {
                vmp = &(vmplp->basep[i]);
                if ((vmp->state & VIO_MBLK_BOUND) != 0) {
                        /* put this vmp on the free stack */
                        vmp->state = VIO_MBLK_FREE;
                        ASSERT(vmplp->tail != vmplp->head);
                        vmplp->quep[vmplp->tail] = vmp;
                        vmplp->tail = (vmplp->tail + 1) & vmplp->quemask;
                }
        }
        mutex_exit(&vmplp->tlock);
        mutex_exit(&vmplp->hlock);
}

/*
 * Create a multiple pools of mblks from which future vio_allocb()
 * or vio_multipool_allocb() requests will be serviced.
 *
 * Arguments:
 *      vmultip -- A pointer to vio_multi_pool_t structure.
 *      num_pools -- Number of the pools.
 *      ... -- Variable arguments consisting a list of buffer sizes for
 *              each pool and list of number of buffers for each pool.
 *
 * NOTE: The restrictions of vio_create_mblks() apply to this interface also.
 *
 * Returns 0 on success or an error returned by vio_create_mblks().
 */
int
vio_init_multipools(vio_multi_pool_t *vmultip, int num_pools, ...)
{
        int             i;
        int             status;
        char            *tbuf;
        va_list         vap;
        vio_mblk_pool_t *fvmp = NULL;

        /*
         * Allocate memory for all of the following in one allocation.
         *      bufsz_tbl -- sizeof (uint32_t) * num_pools
         *      nbuf_tbl  -- sizeof (uint32_t) * num_pools
         *      vmpp      -- sizeof (vio_mblk_pool_t *) * numpools
         */
        vmultip->tbsz = (sizeof (uint32_t) * num_pools) +
            (sizeof (uint32_t) * num_pools) +
            (sizeof (vio_mblk_pool_t *) * num_pools);
        tbuf = kmem_zalloc(vmultip->tbsz, KM_SLEEP);
        vmultip->bufsz_tbl = (uint32_t *)tbuf;
        vmultip->nbuf_tbl = (uint32_t *)(tbuf +
            (sizeof (uint32_t) * num_pools));
        vmultip->vmpp = (vio_mblk_pool_t **)(tbuf +
            (sizeof (uint32_t) * num_pools * 2));
        vmultip->num_pools = num_pools;

        /* initialize the array first */
        va_start(vap, num_pools);
        for (i = 0; i < num_pools; i++) {
                vmultip->bufsz_tbl[i] = va_arg(vap, uint32_t);
        }
        for (i = 0; i < num_pools; i++) {
                vmultip->nbuf_tbl[i] = va_arg(vap, uint32_t);
        }
        va_end(vap);

        for (i = 0; i < vmultip->num_pools; i++) {
                status = vio_create_mblks(vmultip->nbuf_tbl[i],
                    vmultip->bufsz_tbl[i], NULL, &vmultip->vmpp[i]);
                if (status != 0) {
                        vio_destroy_multipools(vmultip, &fvmp);
                        /* We expect to free the pools without failure here */
                        ASSERT(fvmp == NULL);
                        return (status);
                }
        }
        return (0);
}

/*
 * Destroy the multiple pools of mblks. This can only succeed when
 * all allocated mblks have been returned to the pool.
 *
 * If a pool of mblks couldn't be destroyed, then the failed vio_mblk_pool_t
 * pointers are returned via th fvmp list. Its the caller's
 * responsibility to check this list and free them later at an appropriate
 * time with vio_destroy_mblks().
 *
 * Arguments:
 *      vmultip -- A pointer to vio_multi_pool_t structure.
 *      fvmp -- A list in which the pools that couldn't be destroyed are
 *              returned.
 */
void
vio_destroy_multipools(vio_multi_pool_t *vmultip, vio_mblk_pool_t **fvmp)
{
        int i;
        vio_mblk_pool_t *vmp;

        for (i = 0; i < vmultip->num_pools; i++) {
                if ((vmp = vmultip->vmpp[i]) != NULL) {
                        if (vio_destroy_mblks(vmp)) {
                                /*
                                 * if we cannot reclaim all mblks, then
                                 * return the pool in the failed vmp
                                 * list(fvmp).
                                 */
                                vmp->nextp =  *fvmp;
                                *fvmp = vmp;
                        }
                }
        }
        if (vmultip->tbsz != 0)
                kmem_free(vmultip->bufsz_tbl, vmultip->tbsz);
        vmultip->bufsz_tbl = NULL;
        vmultip->nbuf_tbl = NULL;
        vmultip->vmpp = NULL;
        vmultip->num_pools = 0;
        vmultip->tbsz = 0;
}


/*
 * Allocate an vio_mblk from one of the free pools, but tries the pool that
 * best fits size requested first.
 */
vio_mblk_t *
vio_multipool_allocb(vio_multi_pool_t *vmultip, size_t size)
{
        int i;
        vio_mblk_t *vmp = NULL;

        /* Try allocating any size that fits */
        for (i = 0; i < vmultip->num_pools; i++) {
                if (size > vmultip->bufsz_tbl[i]) {
                        continue;
                }
                vmp = vio_allocb(vmultip->vmpp[i]);
                if (vmp != NULL) {
                        break;
                }
        }
        return (vmp);
}

/*
 * -----------------------------------------------------------------------------
 * LDoms versioning functions
 *
 * Future work: the version negotiating code in the various VIO drivers
 * could be made common and placed here.
 */

/*
 * Description:
 *      This function checks to see if the supplied version tuple (major,minor)
 *      is supported by the version 'ver', negotiated during the handshake
 *      between the client and the server (ver).
 *
 * Assumption:
 *      This function assumes that backward compatability is not broken in
 *      newer minor versions of the protocol (e.g. v1.5 & v1.1 support v1.0)
 *
 * Return Value:
 *      B_TRUE          - The (major,minor) version is supported
 *      B_FALSE         - not supported
 */
boolean_t
vio_ver_is_supported(vio_ver_t ver, uint16_t major, uint16_t minor)
{
        if ((ver.major == major) && (ver.minor >= minor))
                return (B_TRUE);

        return (B_FALSE);
}