root/usr/src/uts/common/inet/ip/ip_squeue.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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 * Copyright 2017 Joyent, Inc.
 * Copyright 2026 Oxide Computer Company
 */

/*
 * IP interface to squeues.
 *
 * IP uses squeues to force serialization of packets, both incoming and
 * outgoing. Each squeue is associated with a connection instance (conn_t)
 * above, and a soft ring (if enabled) below. Each CPU will have a default
 * squeue for outbound connections, and each soft ring of an interface will
 * have an squeue to which it sends incoming packets. squeues are never
 * destroyed, and if they become unused they are kept around against future
 * needs.
 *
 * IP organizes its squeues using squeue sets (squeue_set_t). For each CPU
 * in the system there will be one squeue set, all of whose squeues will be
 * bound to that CPU, plus one additional set known as the unbound set. Sets
 * associated with CPUs will have one default squeue, for outbound
 * connections, and a linked list of squeues used by various NICs for inbound
 * packets. The unbound set also has a linked list of squeues, but no default
 * squeue.
 *
 * When a CPU goes offline its squeue set is destroyed, and all its squeues
 * are moved to the unbound set. When a CPU comes online, a new squeue set is
 * created and the default set is searched for a default squeue formerly bound
 * to this CPU. If no default squeue is found, a new one is created.
 *
 * Two fields of the squeue_t, namely sq_next and sq_set, are owned by IP
 * and not the squeue code. squeue.c will not touch them, and we can modify
 * them without holding the squeue lock because of the guarantee that squeues
 * are never destroyed. ip_squeue locks must be held, however.
 *
 * All the squeue sets are protected by a single lock, the sqset_lock. This
 * is also used to protect the sq_next and sq_set fields of an squeue_t.
 *
 * The lock order is: cpu_lock --> ill_lock --> sqset_lock --> sq_lock
 *
 * There are two modes of associating connection with squeues. The first mode
 * associates each connection with the CPU that creates the connection (either
 * during open time or during accept time). The second mode associates each
 * connection with a random CPU, effectively distributing load over all CPUs
 * and all squeues in the system. The mode is controlled by the
 * ip_squeue_fanout variable.
 *
 * NOTE: The fact that there is an association between each connection and
 * squeue and squeue and CPU does not mean that each connection is always
 * processed on this CPU and on this CPU only. Any thread calling squeue_enter()
 * may process the connection on whatever CPU it is scheduled. The squeue to CPU
 * binding is only relevant for the worker thread.
 *
 * INTERFACE:
 *
 * squeue_t *ip_squeue_get(ill_rx_ring_t)
 *
 * Returns the squeue associated with an ill receive ring. If the ring is
 * not bound to a CPU, and we're currently servicing the interrupt which
 * generated the packet, then bind the squeue to CPU.
 *
 *
 * DR Notes
 * ========
 *
 * The ip_squeue_init() registers a call-back function with the CPU DR
 * subsystem using register_cpu_setup_func(). The call-back function does two
 * things:
 *
 * o When the CPU is going off-line or unconfigured, the worker thread is
 *      unbound from the CPU. This allows the CPU unconfig code to move it to
 *      another CPU.
 *
 * o When the CPU is going online, it creates a new squeue for this CPU if
 *      necessary and binds the squeue worker thread to this CPU.
 *
 * TUNABLES:
 *
 * ip_squeue_fanout: used when TCP calls IP_SQUEUE_GET(). If 1, then
 * pick the default squeue from a random CPU, otherwise use our CPU's default
 * squeue.
 *
 * ip_squeue_fanout can be accessed and changed using ndd on /dev/tcp or
 * /dev/ip.
 */

#include <sys/types.h>
#include <sys/debug.h>
#include <sys/kmem.h>
#include <sys/cpuvar.h>
#include <sys/cmn_err.h>

#include <inet/common.h>
#include <inet/ip.h>
#include <netinet/ip6.h>
#include <inet/ip_if.h>
#include <inet/ip_ire.h>
#include <inet/nd.h>
#include <inet/ipclassifier.h>
#include <sys/types.h>
#include <sys/conf.h>
#include <sys/sunddi.h>
#include <sys/dlpi.h>
#include <sys/squeue_impl.h>
#include <sys/tihdr.h>
#include <inet/udp_impl.h>
#include <sys/strsubr.h>
#include <sys/zone.h>
#include <sys/dld.h>
#include <sys/atomic.h>

/*
 * List of all created squeue sets. The list and its size are protected by
 * sqset_lock.
 */
static squeue_set_t     **sqset_global_list; /* list 0 is the unbound list */
static uint_t           sqset_global_size;
kmutex_t                sqset_lock;

static void (*ip_squeue_create_callback)(squeue_t *) = NULL;

static squeue_t *ip_squeue_create(pri_t);
static squeue_set_t *ip_squeue_set_create(processorid_t);
static int ip_squeue_cpu_setup(cpu_setup_t, int, void *);
static void ip_squeue_set_move(squeue_t *, squeue_set_t *);
static void ip_squeue_set_destroy(cpu_t *);
static void ip_squeue_clean(void *, mblk_t *, void *);

#define CPU_ISON(c) (c != NULL && CPU_ACTIVE(c) && (c->cpu_flags & CPU_EXISTS))

static squeue_t *
ip_squeue_create(pri_t pri)
{
        squeue_t *sqp;

        sqp = squeue_create(pri);
        ASSERT(sqp != NULL);
        if (ip_squeue_create_callback != NULL)
                ip_squeue_create_callback(sqp);
        return (sqp);
}

/*
 * Create a new squeue_set. If id == -1, then we're creating the unbound set,
 * which should only happen once when we are first initialized. Otherwise id
 * is the id of the CPU that needs a set, either because we are initializing
 * or because the CPU has come online.
 *
 * If id != -1, then we need at a minimum to provide a default squeue for the
 * new set. We search the unbound set for candidates, and if none are found we
 * create a new one.
 */
static squeue_set_t *
ip_squeue_set_create(processorid_t id)
{
        squeue_set_t    *sqs;
        squeue_set_t    *src = sqset_global_list[0];
        squeue_t        **lastsqp, *sq;
        squeue_t        **defaultq_lastp = NULL;

        sqs = kmem_zalloc(sizeof (squeue_set_t), KM_SLEEP);
        sqs->sqs_cpuid = id;

        if (id == -1) {
                ASSERT(sqset_global_size == 0);
                sqset_global_list[0] = sqs;
                sqset_global_size = 1;
                return (sqs);
        }

        /*
         * When we create an squeue set id != -1, we need to give it a
         * default squeue, in order to support fanout of conns across
         * CPUs. Try to find a former default squeue that matches this
         * cpu id on the unbound squeue set. If no such squeue is found,
         * find some non-default TCP squeue that is free. If still no such
         * candidate is found, create a new squeue.
         */

        ASSERT(MUTEX_HELD(&cpu_lock));
        mutex_enter(&sqset_lock);
        lastsqp = &src->sqs_head;

        while (*lastsqp) {
                if ((*lastsqp)->sq_bind == id &&
                    (*lastsqp)->sq_state & SQS_DEFAULT) {
                        /*
                         * Exact match. Former default squeue of cpu 'id'
                         */
                        ASSERT(!((*lastsqp)->sq_state & SQS_ILL_BOUND));
                        defaultq_lastp = lastsqp;
                        break;
                }
                if (defaultq_lastp == NULL &&
                    !((*lastsqp)->sq_state & (SQS_ILL_BOUND | SQS_DEFAULT))) {
                        /*
                         * A free non-default TCP squeue
                         */
                        defaultq_lastp = lastsqp;
                }
                lastsqp = &(*lastsqp)->sq_next;
        }

        if (defaultq_lastp != NULL) {
                /* Remove from src set and set SQS_DEFAULT */
                sq = *defaultq_lastp;
                *defaultq_lastp = sq->sq_next;
                sq->sq_next = NULL;
                if (!(sq->sq_state & SQS_DEFAULT)) {
                        mutex_enter(&sq->sq_lock);
                        sq->sq_state |= SQS_DEFAULT;
                        mutex_exit(&sq->sq_lock);
                }
        } else {
                sq = ip_squeue_create(SQUEUE_DEFAULT_PRIORITY);
                sq->sq_state |= SQS_DEFAULT;
        }

        sq->sq_set = sqs;
        sqs->sqs_default = sq;
        squeue_bind(sq, id); /* this locks squeue mutex */

        ASSERT(sqset_global_size <= NCPU);
        sqset_global_list[sqset_global_size++] = sqs;
        mutex_exit(&sqset_lock);
        return (sqs);
}

/*
 * Obtain a free squeue and set its worker and poll thread priorities, if
 * required. A free squeue is one that is not already bound to an ill_t
 * and that is not marked as "default". If a free squeue does not exist,
 * then one is created.
 */
squeue_t *
ip_squeue_getfree(pri_t pri)
{
        squeue_set_t    *sqs = sqset_global_list[0];
        squeue_t        *sq;

        mutex_enter(&sqset_lock);
        for (sq = sqs->sqs_head; sq != NULL; sq = sq->sq_next) {
                /*
                 * Select a non-default TCP squeue that is free i.e. not
                 * bound to any ill.
                 */
                if (!(sq->sq_state & (SQS_DEFAULT | SQS_ILL_BOUND)))
                        break;
        }

        if (sq == NULL) {
                sq = ip_squeue_create(pri);
                sq->sq_set = sqs;
                sq->sq_next = sqs->sqs_head;
                sqs->sqs_head = sq;
        }

        ASSERT(!(sq->sq_state & (SQS_POLL_THR_CONTROL | SQS_WORKER_THR_CONTROL |
            SQS_POLL_CLEANUP_DONE | SQS_POLL_QUIESCE_DONE |
            SQS_POLL_THR_QUIESCED)));

        mutex_enter(&sq->sq_lock);
        sq->sq_state |= SQS_ILL_BOUND;
        mutex_exit(&sq->sq_lock);
        mutex_exit(&sqset_lock);

        if (sq->sq_priority != pri) {
                thread_lock(sq->sq_worker);
                (void) thread_change_pri(sq->sq_worker, pri, 0);
                thread_unlock(sq->sq_worker);

                thread_lock(sq->sq_poll_thr);
                (void) thread_change_pri(sq->sq_poll_thr, pri, 0);
                thread_unlock(sq->sq_poll_thr);

                sq->sq_priority = pri;
        }
        return (sq);
}

/*
 * Initialize IP squeues.
 */
void
ip_squeue_init(void (*callback)(squeue_t *))
{
        int i;
        squeue_set_t    *sqs;

        ASSERT(sqset_global_list == NULL);

        ip_squeue_create_callback = callback;
        squeue_init();
        mutex_init(&sqset_lock, NULL, MUTEX_DEFAULT, NULL);
        sqset_global_list =
            kmem_zalloc(sizeof (squeue_set_t *) * (NCPU+1), KM_SLEEP);
        sqset_global_size = 0;
        /*
         * We are called at system boot time and we don't
         * expect memory allocation failure.
         */
        sqs = ip_squeue_set_create(-1);
        ASSERT(sqs != NULL);

        mutex_enter(&cpu_lock);
        /* Create squeue for each active CPU available */
        for (i = 0; i < NCPU; i++) {
                cpu_t *cp = cpu_get(i);
                if (CPU_ISON(cp) && cp->cpu_squeue_set == NULL) {
                        /*
                         * We are called at system boot time and we don't
                         * expect memory allocation failure then
                         */
                        cp->cpu_squeue_set = ip_squeue_set_create(cp->cpu_id);
                        ASSERT(cp->cpu_squeue_set != NULL);
                }
        }

        register_cpu_setup_func(ip_squeue_cpu_setup, NULL);
        mutex_exit(&cpu_lock);
}

/*
 * Get a default squeue, either from the current CPU or a CPU derived by hash
 * from the index argument, depending upon the setting of ip_squeue_fanout.
 */
squeue_t *
ip_squeue_random(uint_t index)
{
        squeue_set_t *sqs = NULL;
        squeue_t *sq;

        /*
         * The minimum value of sqset_global_size is 2, one for the unbound
         * squeue set and another for the squeue set of the zeroth CPU.
         * Even though the value could be changing, it can never go below 2,
         * so the assert does not need the lock protection.
         */
        ASSERT(sqset_global_size > 1);

        /* Protect against changes to sqset_global_list */
        mutex_enter(&sqset_lock);

        if (!ip_squeue_fanout)
                sqs = CPU->cpu_squeue_set;

        /*
         * sqset_global_list[0] corresponds to the unbound squeue set.
         * The computation below picks a set other than the unbound set.
         */
        if (sqs == NULL)
                sqs = sqset_global_list[(index % (sqset_global_size - 1)) + 1];
        sq = sqs->sqs_default;

        mutex_exit(&sqset_lock);
        ASSERT(sq);
        return (sq);
}

/*
 * Move squeue from its current set to newset. Not used for default squeues.
 * Bind or unbind the worker thread as appropriate.
 */

static void
ip_squeue_set_move(squeue_t *sq, squeue_set_t *newset)
{
        squeue_set_t    *set;
        squeue_t        **lastsqp;
        processorid_t   cpuid = newset->sqs_cpuid;

        ASSERT(!(sq->sq_state & SQS_DEFAULT));
        ASSERT(!MUTEX_HELD(&sq->sq_lock));
        ASSERT(MUTEX_HELD(&sqset_lock));

        set = sq->sq_set;
        if (set == newset)
                return;

        lastsqp = &set->sqs_head;
        while (*lastsqp != sq)
                lastsqp = &(*lastsqp)->sq_next;

        *lastsqp = sq->sq_next;
        sq->sq_next = newset->sqs_head;
        newset->sqs_head = sq;
        sq->sq_set = newset;
        if (cpuid == -1)
                squeue_unbind(sq);
        else
                squeue_bind(sq, cpuid);
}

/*
 * Move squeue from its current set to cpuid's set and bind to cpuid.
 */

int
ip_squeue_cpu_move(squeue_t *sq, processorid_t cpuid)
{
        cpu_t *cpu;
        squeue_set_t *set;

        if (sq->sq_state & SQS_DEFAULT)
                return (-1);

        ASSERT(MUTEX_HELD(&cpu_lock));

        cpu = cpu_get(cpuid);
        if (!CPU_ISON(cpu))
                return (-1);

        mutex_enter(&sqset_lock);
        set = cpu->cpu_squeue_set;
        if (set != NULL)
                ip_squeue_set_move(sq, set);
        mutex_exit(&sqset_lock);
        return ((set == NULL) ? -1 : 0);
}

/*
 * The mac layer is calling, asking us to move an squeue to a
 * new CPU. This routine is called with cpu_lock held.
 */
void
ip_squeue_bind_ring(ill_t *ill, ill_rx_ring_t *rx_ring, processorid_t cpuid)
{
        ASSERT(ILL_MAC_PERIM_HELD(ill));
        ASSERT(rx_ring->rr_ill == ill);

        mutex_enter(&ill->ill_lock);
        if (rx_ring->rr_ring_state == RR_FREE ||
            rx_ring->rr_ring_state == RR_FREE_INPROG) {
                mutex_exit(&ill->ill_lock);
                return;
        }

        if (ip_squeue_cpu_move(rx_ring->rr_sqp, cpuid) != -1)
                rx_ring->rr_ring_state = RR_SQUEUE_BOUND;

        mutex_exit(&ill->ill_lock);
}

void *
ip_squeue_add_ring(ill_t *ill, void *mrp)
{
        mac_rx_fifo_t           *mrfp = (mac_rx_fifo_t *)mrp;
        ill_rx_ring_t           *rx_ring, *ring_tbl;
        int                     ip_rx_index;
        squeue_t                *sq = NULL;
        pri_t                   pri;

        ASSERT(ILL_MAC_PERIM_HELD(ill));
        ASSERT(mrfp->mrf_type == MAC_RX_FIFO);
        ASSERT(ill->ill_dld_capab != NULL);

        ring_tbl = ill->ill_dld_capab->idc_poll.idp_ring_tbl;

        mutex_enter(&ill->ill_lock);
        for (ip_rx_index = 0; ip_rx_index < ILL_MAX_RINGS; ip_rx_index++) {
                rx_ring = &ring_tbl[ip_rx_index];
                if (rx_ring->rr_ring_state == RR_FREE)
                        break;
        }

        if (ip_rx_index == ILL_MAX_RINGS) {
                /*
                 * We ran out of ILL_MAX_RINGS worth rx_ring structures. If
                 * we have devices which can overwhelm this limit,
                 * ILL_MAX_RING should be made configurable. Meanwhile it
                 * cause no panic because driver will pass ip_input a NULL
                 * handle which will make IP allocate the default squeue and
                 * Polling mode will not be used for this ring.
                 */
                cmn_err(CE_NOTE,
                    "Reached maximum number of receiving rings (%d) for %s\n",
                    ILL_MAX_RINGS, ill->ill_name);
                mutex_exit(&ill->ill_lock);
                return (NULL);
        }

        bzero(rx_ring, sizeof (ill_rx_ring_t));
        rx_ring->rr_rx = mrfp->mrf_receive;
        rx_ring->rr_ip_accept = (ill->ill_isv6 != 0) ?
            (ip_accept_t)ip_accept_tcp_v6 :
            (ip_accept_t)ip_accept_tcp;

        rx_ring->rr_intr_handle = mrfp->mrf_intr_handle;
        rx_ring->rr_intr_enable = (ip_mac_intr_enable_t)mrfp->mrf_intr_enable;
        rx_ring->rr_intr_disable =
            (ip_mac_intr_disable_t)mrfp->mrf_intr_disable;
        rx_ring->rr_rx_handle = mrfp->mrf_rx_arg;
        rx_ring->rr_ill = ill;

        pri = mrfp->mrf_flow_priority;

        sq = ip_squeue_getfree(pri);

        mutex_enter(&sq->sq_lock);
        sq->sq_rx_ring = rx_ring;
        rx_ring->rr_sqp = sq;

        sq->sq_state |= SQS_POLL_CAPAB;

        rx_ring->rr_ring_state = RR_SQUEUE_UNBOUND;
        sq->sq_ill = ill;
        mutex_exit(&sq->sq_lock);
        mutex_exit(&ill->ill_lock);

        DTRACE_PROBE4(ill__ring__add, char *, ill->ill_name, ill_t *, ill, int,
            ip_rx_index, void *, mrfp->mrf_rx_arg);

        /* Assign the squeue to the specified CPU as well */
        mutex_enter(&cpu_lock);
        (void) ip_squeue_bind_ring(ill, rx_ring, mrfp->mrf_cpu_id);
        mutex_exit(&cpu_lock);

        return (rx_ring);
}

/*
 * sanitize the squeue etc. Some of the processing
 * needs to be done from inside the perimeter.
 */
void
ip_squeue_clean_ring(ill_t *ill, ill_rx_ring_t *rx_ring)
{
        squeue_t *sqp;

        ASSERT(ILL_MAC_PERIM_HELD(ill));
        ASSERT(rx_ring != NULL);

        /* Just clean one squeue */
        mutex_enter(&ill->ill_lock);
        if (rx_ring->rr_ring_state == RR_FREE) {
                mutex_exit(&ill->ill_lock);
                return;
        }
        rx_ring->rr_ring_state = RR_FREE_INPROG;
        sqp = rx_ring->rr_sqp;

        mutex_enter(&sqp->sq_lock);
        sqp->sq_state |= SQS_POLL_CLEANUP;
        cv_signal(&sqp->sq_worker_cv);
        mutex_exit(&ill->ill_lock);
        while (!(sqp->sq_state & SQS_POLL_CLEANUP_DONE))
                cv_wait(&sqp->sq_ctrlop_done_cv, &sqp->sq_lock);
        sqp->sq_state &= ~SQS_POLL_CLEANUP_DONE;

        ASSERT(!(sqp->sq_state & (SQS_POLL_THR_CONTROL |
            SQS_WORKER_THR_CONTROL | SQS_POLL_QUIESCE_DONE |
            SQS_POLL_THR_QUIESCED)));

        cv_signal(&sqp->sq_worker_cv);
        mutex_exit(&sqp->sq_lock);

        /*
         * Move the squeue to sqset_global_list[0] which holds the set of
         * squeues not bound to any cpu. Note that the squeue is still
         * considered bound to an ill as long as SQS_ILL_BOUND is set.
         */
        mutex_enter(&sqset_lock);
        ip_squeue_set_move(sqp, sqset_global_list[0]);
        mutex_exit(&sqset_lock);

        /*
         * CPU going offline can also trigger a move of the squeue to the
         * unbound set sqset_global_list[0]. However the squeue won't be
         * recycled for the next use as long as the SQS_ILL_BOUND flag
         * is set. Hence we clear the SQS_ILL_BOUND flag only towards the
         * end after the move.
         */
        mutex_enter(&sqp->sq_lock);
        sqp->sq_state &= ~SQS_ILL_BOUND;
        mutex_exit(&sqp->sq_lock);

        mutex_enter(&ill->ill_lock);
        rx_ring->rr_ring_state = RR_FREE;
        mutex_exit(&ill->ill_lock);
}

/*
 * Stop the squeue from polling. This needs to be done
 * from inside the perimeter.
 */
void
ip_squeue_quiesce_ring(ill_t *ill, ill_rx_ring_t *rx_ring)
{
        squeue_t *sqp;

        ASSERT(ILL_MAC_PERIM_HELD(ill));
        ASSERT(rx_ring != NULL);

        sqp = rx_ring->rr_sqp;
        mutex_enter(&sqp->sq_lock);
        sqp->sq_state |= SQS_POLL_QUIESCE;
        cv_signal(&sqp->sq_worker_cv);
        while (!(sqp->sq_state & SQS_POLL_QUIESCE_DONE))
                cv_wait(&sqp->sq_ctrlop_done_cv, &sqp->sq_lock);

        mutex_exit(&sqp->sq_lock);
}

/*
 * Restart polling etc. Needs to be inside the perimeter to
 * prevent races.
 */
void
ip_squeue_restart_ring(ill_t *ill, ill_rx_ring_t *rx_ring)
{
        squeue_t *sqp;

        ASSERT(ILL_MAC_PERIM_HELD(ill));
        ASSERT(rx_ring != NULL);

        sqp = rx_ring->rr_sqp;
        mutex_enter(&sqp->sq_lock);
        /*
         * Handle change in number of rings between the quiesce and
         * restart operations by checking for a previous quiesce before
         * attempting a restart.
         */
        if (!(sqp->sq_state & SQS_POLL_QUIESCE_DONE)) {
                mutex_exit(&sqp->sq_lock);
                return;
        }
        sqp->sq_state |= SQS_POLL_RESTART;
        cv_signal(&sqp->sq_worker_cv);
        while (!(sqp->sq_state & SQS_POLL_RESTART_DONE))
                cv_wait(&sqp->sq_ctrlop_done_cv, &sqp->sq_lock);
        sqp->sq_state &= ~SQS_POLL_RESTART_DONE;
        mutex_exit(&sqp->sq_lock);
}

/*
 * sanitize all squeues associated with the ill.
 */
void
ip_squeue_clean_all(ill_t *ill)
{
        int idx;
        ill_rx_ring_t   *rx_ring;

        for (idx = 0; idx < ILL_MAX_RINGS; idx++) {
                rx_ring = &ill->ill_dld_capab->idc_poll.idp_ring_tbl[idx];
                ip_squeue_clean_ring(ill, rx_ring);
        }
}

/*
 * Used by IP to get the squeue associated with a ring. If the squeue isn't
 * yet bound to a CPU, and we're being called directly from the NIC's
 * interrupt, then we know what CPU we want to assign the squeue to, so
 * dispatch that task to a taskq.
 */
squeue_t *
ip_squeue_get(ill_rx_ring_t *ill_rx_ring)
{
        squeue_t        *sqp;

        if ((ill_rx_ring == NULL) || ((sqp = ill_rx_ring->rr_sqp) == NULL))
                return (IP_SQUEUE_GET(CPU_PSEUDO_RANDOM()));

        return (sqp);
}

/*
 * Called when a CPU goes offline. It's squeue_set_t is destroyed, and all
 * squeues are unboudn and moved to the unbound set.
 */
static void
ip_squeue_set_destroy(cpu_t *cpu)
{
        int i;
        squeue_t *sqp, *lastsqp = NULL;
        squeue_set_t *sqs, *unbound = sqset_global_list[0];

        mutex_enter(&sqset_lock);
        if ((sqs = cpu->cpu_squeue_set) == NULL) {
                mutex_exit(&sqset_lock);
                return;
        }

        /* Move all squeues to unbound set */

        for (sqp = sqs->sqs_head; sqp; lastsqp = sqp, sqp = sqp->sq_next) {
                squeue_unbind(sqp);
                sqp->sq_set = unbound;
        }
        if (sqs->sqs_head) {
                lastsqp->sq_next = unbound->sqs_head;
                unbound->sqs_head = sqs->sqs_head;
        }

        /* Also move default squeue to unbound set */

        sqp = sqs->sqs_default;
        ASSERT(sqp != NULL);
        ASSERT((sqp->sq_state & (SQS_DEFAULT|SQS_ILL_BOUND)) == SQS_DEFAULT);

        sqp->sq_next = unbound->sqs_head;
        unbound->sqs_head = sqp;
        squeue_unbind(sqp);
        sqp->sq_set = unbound;

        for (i = 1; i < sqset_global_size; i++)
                if (sqset_global_list[i] == sqs)
                        break;

        ASSERT(i < sqset_global_size);
        sqset_global_list[i] = sqset_global_list[sqset_global_size - 1];
        sqset_global_list[sqset_global_size - 1] = NULL;
        sqset_global_size--;

        mutex_exit(&sqset_lock);
        kmem_free(sqs, sizeof (*sqs));
}

/*
 * Reconfiguration callback
 */
/* ARGSUSED */
static int
ip_squeue_cpu_setup(cpu_setup_t what, int id, void *arg)
{
        cpu_t *cp = cpu_get(id);

        ASSERT(MUTEX_HELD(&cpu_lock));
        switch (what) {
        case CPU_CONFIG:
        case CPU_ON:
        case CPU_INIT:
        case CPU_CPUPART_IN:
                if (CPU_ISON(cp) && cp->cpu_squeue_set == NULL)
                        cp->cpu_squeue_set = ip_squeue_set_create(cp->cpu_id);
                break;
        case CPU_UNCONFIG:
        case CPU_OFF:
        case CPU_CPUPART_OUT:
                if (cp->cpu_squeue_set != NULL) {
                        ip_squeue_set_destroy(cp);
                        cp->cpu_squeue_set = NULL;
                }
                break;
        default:
                break;
        }
        return (0);
}