root/sys/dev/raidframe/rf_sstf.c
/*      $NetBSD: rf_sstf.c,v 1.18 2021/07/23 20:18:24 oster Exp $       */
/*
 * Copyright (c) 1995 Carnegie-Mellon University.
 * All rights reserved.
 *
 * Author: Jim Zelenka
 *
 * Permission to use, copy, modify and distribute this software and
 * its documentation is hereby granted, provided that both the copyright
 * notice and this permission notice appear in all copies of the
 * software, derivative works or modified versions, and any portions
 * thereof, and that both notices appear in supporting documentation.
 *
 * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
 * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND
 * FOR ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
 *
 * Carnegie Mellon requests users of this software to return to
 *
 *  Software Distribution Coordinator  or  Software.Distribution@CS.CMU.EDU
 *  School of Computer Science
 *  Carnegie Mellon University
 *  Pittsburgh PA 15213-3890
 *
 * any improvements or extensions that they make and grant Carnegie the
 * rights to redistribute these changes.
 */

/*******************************************************************************
 *
 * sstf.c --  prioritized shortest seek time first disk queueing code
 *
 ******************************************************************************/

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: rf_sstf.c,v 1.18 2021/07/23 20:18:24 oster Exp $");

#include <dev/raidframe/raidframevar.h>

#include "rf_alloclist.h"
#include "rf_stripelocks.h"
#include "rf_layout.h"
#include "rf_diskqueue.h"
#include "rf_sstf.h"
#include "rf_debugMem.h"
#include "rf_general.h"
#include "rf_options.h"
#include "rf_raid.h"

#define DIR_LEFT   1
#define DIR_RIGHT  2
#define DIR_EITHER 3

#define SNUM_DIFF(_a_,_b_) (((_a_)>(_b_))?((_a_)-(_b_)):((_b_)-(_a_)))

#define QSUM(_sstfq_) (((_sstfq_)->lopri.qlen)+((_sstfq_)->left.qlen)+((_sstfq_)->right.qlen))


static void
do_sstf_ord_q(RF_DiskQueueData_t **,
    RF_DiskQueueData_t **,
    RF_DiskQueueData_t *);

static RF_DiskQueueData_t *
closest_to_arm(RF_SstfQ_t *,
    RF_SectorNum_t,
    int *,
    int);
static void do_dequeue(RF_SstfQ_t *, RF_DiskQueueData_t *);


static void
do_sstf_ord_q(RF_DiskQueueData_t **queuep, RF_DiskQueueData_t **tailp, RF_DiskQueueData_t *req)
{
        RF_DiskQueueData_t *r, *s;

        if (*queuep == NULL) {
                *queuep = req;
                *tailp = req;
                req->next = NULL;
                req->prev = NULL;
                return;
        }
        if (req->sectorOffset <= (*queuep)->sectorOffset) {
                req->next = *queuep;
                req->prev = NULL;
                (*queuep)->prev = req;
                *queuep = req;
                return;
        }
        if (req->sectorOffset > (*tailp)->sectorOffset) {
                /* optimization */
                r = NULL;
                s = *tailp;
                goto q_at_end;
        }
        for (s = NULL, r = *queuep; r; s = r, r = r->next) {
                if (r->sectorOffset >= req->sectorOffset) {
                        /* insert after s, before r */
                        RF_ASSERT(s);
                        req->next = r;
                        r->prev = req;
                        s->next = req;
                        req->prev = s;
                        return;
                }
        }
q_at_end:
        /* insert after s, at end of queue */
        RF_ASSERT(r == NULL);
        RF_ASSERT(s);
        RF_ASSERT(s == (*tailp));
        req->next = NULL;
        req->prev = s;
        s->next = req;
        *tailp = req;
}
/* for removing from head-of-queue */
#define DO_HEAD_DEQ(_r_,_q_) { \
        _r_ = (_q_)->queue; \
        RF_ASSERT((_r_) != NULL); \
        (_q_)->queue = (_r_)->next; \
        (_q_)->qlen--; \
        if ((_q_)->qlen == 0) { \
                RF_ASSERT((_r_) == (_q_)->qtail); \
                RF_ASSERT((_q_)->queue == NULL); \
                (_q_)->qtail = NULL; \
        } \
        else { \
                RF_ASSERT((_q_)->queue->prev == (_r_)); \
                (_q_)->queue->prev = NULL; \
        } \
}

/* for removing from end-of-queue */
#define DO_TAIL_DEQ(_r_,_q_) { \
        _r_ = (_q_)->qtail; \
        RF_ASSERT((_r_) != NULL); \
        (_q_)->qtail = (_r_)->prev; \
        (_q_)->qlen--; \
        if ((_q_)->qlen == 0) { \
                RF_ASSERT((_r_) == (_q_)->queue); \
                RF_ASSERT((_q_)->qtail == NULL); \
                (_q_)->queue = NULL; \
        } \
        else { \
                RF_ASSERT((_q_)->qtail->next == (_r_)); \
                (_q_)->qtail->next = NULL; \
        } \
}

#define DO_BEST_DEQ(_l_,_r_,_q_) { \
        if (SNUM_DIFF((_q_)->queue->sectorOffset,_l_) \
                < SNUM_DIFF((_q_)->qtail->sectorOffset,_l_)) \
        { \
                DO_HEAD_DEQ(_r_,_q_); \
        } \
        else { \
                DO_TAIL_DEQ(_r_,_q_); \
        } \
}

static RF_DiskQueueData_t *
closest_to_arm(RF_SstfQ_t *queue, RF_SectorNum_t arm_pos, int *dir, int allow_reverse)
{
        RF_SectorNum_t best_pos_l = 0, this_pos_l = 0, last_pos = 0;
        RF_SectorNum_t best_pos_r = 0, this_pos_r = 0;
        RF_DiskQueueData_t *r, *best_l, *best_r;

        best_r = best_l = NULL;
        for (r = queue->queue; r; r = r->next) {
                if (r->sectorOffset < arm_pos) {
                        if (best_l == NULL) {
                                best_l = r;
                                last_pos = best_pos_l = this_pos_l;
                        } else {
                                this_pos_l = arm_pos - r->sectorOffset;
                                if (this_pos_l < best_pos_l) {
                                        best_l = r;
                                        last_pos = best_pos_l = this_pos_l;
                                } else {
                                        last_pos = this_pos_l;
                                }
                        }
                } else {
                        if (best_r == NULL) {
                                best_r = r;
                                last_pos = best_pos_r = this_pos_r;
                        } else {
                                this_pos_r = r->sectorOffset - arm_pos;
                                if (this_pos_r < best_pos_r) {
                                        best_r = r;
                                        last_pos = best_pos_r = this_pos_r;
                                } else {
                                        last_pos = this_pos_r;
                                }
                                if (this_pos_r > last_pos) {
                                        /* getting farther away */
                                        break;
                                }
                        }
                }
        }
        if ((best_r == NULL) && (best_l == NULL))
                return (NULL);
        if ((*dir == DIR_RIGHT) && best_r)
                return (best_r);
        if ((*dir == DIR_LEFT) && best_l)
                return (best_l);
        if (*dir == DIR_EITHER) {
                if (best_l == NULL)
                        return (best_r);
                if (best_r == NULL)
                        return (best_l);
                if (best_pos_r < best_pos_l)
                        return (best_r);
                else
                        return (best_l);
        }
        /*
         * Nothing in the direction we want to go. Reverse or
         * reset the arm. We know we have an I/O in the other
         * direction.
         */
        if (allow_reverse) {
                if (*dir == DIR_RIGHT) {
                        *dir = DIR_LEFT;
                        return (best_l);
                } else {
                        *dir = DIR_RIGHT;
                        return (best_r);
                }
        }
        /*
         * Reset (beginning of queue).
         */
        RF_ASSERT(*dir == DIR_RIGHT);
        return (queue->queue);
}

void   *
rf_SstfCreate(
        RF_SectorCount_t sect_per_disk,
        RF_AllocListElem_t *cl_list,
        RF_ShutdownList_t **listp)
{
        RF_Sstf_t *sstfq;

        sstfq = RF_MallocAndAdd(sizeof(*sstfq), cl_list);
        sstfq->dir = DIR_EITHER;
        sstfq->allow_reverse = 1;
        return ((void *) sstfq);
}

void   *
rf_ScanCreate(
        RF_SectorCount_t sect_per_disk,
        RF_AllocListElem_t *cl_list,
        RF_ShutdownList_t **listp)
{
        RF_Sstf_t *scanq;

        scanq = RF_MallocAndAdd(sizeof(*scanq), cl_list);
        scanq->dir = DIR_RIGHT;
        scanq->allow_reverse = 1;
        return ((void *) scanq);
}

void   *
rf_CscanCreate(
        RF_SectorCount_t sect_per_disk,
        RF_AllocListElem_t *cl_list,
        RF_ShutdownList_t **listp)
{
        RF_Sstf_t *cscanq;

        cscanq = RF_MallocAndAdd(sizeof(*cscanq), cl_list);
        cscanq->dir = DIR_RIGHT;
        return ((void *) cscanq);
}

void
rf_SstfEnqueue(void *qptr, RF_DiskQueueData_t *req, int priority)
{
        RF_Sstf_t *sstfq;

        sstfq = (RF_Sstf_t *) qptr;

        if (priority == RF_IO_LOW_PRIORITY) {
#if RF_DEBUG_QUEUE
                if (rf_sstfDebug || rf_scanDebug || rf_cscanDebug) {
                        RF_DiskQueue_t *dq;
                        dq = (RF_DiskQueue_t *) req->queue;
                        printf("raid%d: ENQ lopri %d queues are %d,%d,%d\n",
                               req->raidPtr->raidid,
                               dq->col,
                               sstfq->left.qlen, sstfq->right.qlen,
                               sstfq->lopri.qlen);
                }
#endif
                do_sstf_ord_q(&sstfq->lopri.queue, &sstfq->lopri.qtail, req);
                sstfq->lopri.qlen++;
        } else {
                if (req->sectorOffset < sstfq->last_sector) {
                        do_sstf_ord_q(&sstfq->left.queue, &sstfq->left.qtail, req);
                        sstfq->left.qlen++;
                } else {
                        do_sstf_ord_q(&sstfq->right.queue, &sstfq->right.qtail, req);
                        sstfq->right.qlen++;
                }
        }
}

static void
do_dequeue(RF_SstfQ_t *queue, RF_DiskQueueData_t *req)
{
        RF_DiskQueueData_t *req2;

#if RF_DEBUG_QUEUE
        if (rf_sstfDebug || rf_scanDebug || rf_cscanDebug) {
                printf("raid%d: do_dequeue\n", req->raidPtr->raidid);
        }
#endif
        if (req == queue->queue) {
                DO_HEAD_DEQ(req2, queue);
                RF_ASSERT(req2 == req);
        } else
                if (req == queue->qtail) {
                        DO_TAIL_DEQ(req2, queue);
                        RF_ASSERT(req2 == req);
                } else {
                        /* dequeue from middle of list */
                        RF_ASSERT(req->next);
                        RF_ASSERT(req->prev);
                        queue->qlen--;
                        req->next->prev = req->prev;
                        req->prev->next = req->next;
                        req->next = req->prev = NULL;
                }
}

RF_DiskQueueData_t *
rf_SstfDequeue(void *qptr)
{
        RF_DiskQueueData_t *req = NULL;
        RF_Sstf_t *sstfq;

        sstfq = (RF_Sstf_t *) qptr;

#if RF_DEBUG_QUEUE
        if (rf_sstfDebug) {
                RF_DiskQueue_t *dq;
                dq = (RF_DiskQueue_t *) req->queue;
                RF_ASSERT(QSUM(sstfq) == dq->queueLength);
                printf("raid%d: sstf: Dequeue %d queues are %d,%d,%d\n",
                       req->raidPtr->raidid, dq->col,
                       sstfq->left.qlen, sstfq->right.qlen, sstfq->lopri.qlen);
        }
#endif
        if (sstfq->left.queue == NULL) {
                RF_ASSERT(sstfq->left.qlen == 0);
                if (sstfq->right.queue == NULL) {
                        RF_ASSERT(sstfq->right.qlen == 0);
                        if (sstfq->lopri.queue == NULL) {
                                RF_ASSERT(sstfq->lopri.qlen == 0);
                                return (NULL);
                        }
#if RF_DEBUG_QUEUE
                        if (rf_sstfDebug) {
                                printf("raid%d: sstf: check for close lopri",
                                       req->raidPtr->raidid);
                        }
#endif
                        req = closest_to_arm(&sstfq->lopri, sstfq->last_sector,
                            &sstfq->dir, sstfq->allow_reverse);
#if RF_DEBUG_QUEUE
                        if (rf_sstfDebug) {
                                printf("raid%d: sstf: closest_to_arm said %lx",
                                       req->raidPtr->raidid, (long) req);
                        }
#endif
                        if (req == NULL)
                                return (NULL);
                        do_dequeue(&sstfq->lopri, req);
                } else {
                        DO_BEST_DEQ(sstfq->last_sector, req, &sstfq->right);
                }
        } else {
                if (sstfq->right.queue == NULL) {
                        RF_ASSERT(sstfq->right.qlen == 0);
                        DO_BEST_DEQ(sstfq->last_sector, req, &sstfq->left);
                } else {
                        if (SNUM_DIFF(sstfq->last_sector, sstfq->right.queue->sectorOffset)
                            < SNUM_DIFF(sstfq->last_sector, sstfq->left.qtail->sectorOffset)) {
                                DO_HEAD_DEQ(req, &sstfq->right);
                        } else {
                                DO_TAIL_DEQ(req, &sstfq->left);
                        }
                }
        }
        RF_ASSERT(req);
        sstfq->last_sector = req->sectorOffset;
        return (req);
}

RF_DiskQueueData_t *
rf_ScanDequeue(void *qptr)
{
        RF_DiskQueueData_t *req = NULL;
        RF_Sstf_t *scanq;

        scanq = (RF_Sstf_t *) qptr;

#if RF_DEBUG_QUEUE
        if (rf_scanDebug) {
                RF_DiskQueue_t *dq;
                dq = (RF_DiskQueue_t *) req->queue;
                RF_ASSERT(QSUM(scanq) == dq->queueLength);
                printf("raid%d: scan: Dequeue %d queues are %d,%d,%d\n",
                       req->raidPtr->raidid, dq->col,
                       scanq->left.qlen, scanq->right.qlen, scanq->lopri.qlen);
        }
#endif
        if (scanq->left.queue == NULL) {
                RF_ASSERT(scanq->left.qlen == 0);
                if (scanq->right.queue == NULL) {
                        RF_ASSERT(scanq->right.qlen == 0);
                        if (scanq->lopri.queue == NULL) {
                                RF_ASSERT(scanq->lopri.qlen == 0);
                                return (NULL);
                        }
                        req = closest_to_arm(&scanq->lopri, scanq->last_sector,
                            &scanq->dir, scanq->allow_reverse);
                        if (req == NULL)
                                return (NULL);
                        do_dequeue(&scanq->lopri, req);
                } else {
                        scanq->dir = DIR_RIGHT;
                        DO_HEAD_DEQ(req, &scanq->right);
                }
        } else
                if (scanq->right.queue == NULL) {
                        RF_ASSERT(scanq->right.qlen == 0);
                        RF_ASSERT(scanq->left.queue);
                        scanq->dir = DIR_LEFT;
                        DO_TAIL_DEQ(req, &scanq->left);
                } else {
                        RF_ASSERT(scanq->right.queue);
                        RF_ASSERT(scanq->left.queue);
                        if (scanq->dir == DIR_RIGHT) {
                                DO_HEAD_DEQ(req, &scanq->right);
                        } else {
                                DO_TAIL_DEQ(req, &scanq->left);
                        }
                }
        RF_ASSERT(req);
        scanq->last_sector = req->sectorOffset;
        return (req);
}

RF_DiskQueueData_t *
rf_CscanDequeue(void *qptr)
{
        RF_DiskQueueData_t *req = NULL;
        RF_Sstf_t *cscanq;

        cscanq = (RF_Sstf_t *) qptr;

        RF_ASSERT(cscanq->dir == DIR_RIGHT);
#if RF_DEBUG_QUEUE
        if (rf_cscanDebug) {
                RF_DiskQueue_t *dq;
                dq = (RF_DiskQueue_t *) req->queue;
                RF_ASSERT(QSUM(cscanq) == dq->queueLength);
                printf("raid%d: scan: Dequeue %d queues are %d,%d,%d\n",
                       req->raidPtr->raidid, dq->col,
                       cscanq->left.qlen, cscanq->right.qlen,
                       cscanq->lopri.qlen);
        }
#endif
        if (cscanq->right.queue) {
                DO_HEAD_DEQ(req, &cscanq->right);
        } else {
                RF_ASSERT(cscanq->right.qlen == 0);
                if (cscanq->left.queue == NULL) {
                        RF_ASSERT(cscanq->left.qlen == 0);
                        if (cscanq->lopri.queue == NULL) {
                                RF_ASSERT(cscanq->lopri.qlen == 0);
                                return (NULL);
                        }
                        req = closest_to_arm(&cscanq->lopri, cscanq->last_sector,
                            &cscanq->dir, cscanq->allow_reverse);
                        if (req == NULL)
                                return (NULL);
                        do_dequeue(&cscanq->lopri, req);
                } else {
                        /*
                         * There's I/Os to the left of the arm. Swing
                         * on back (swap queues).
                         */
                        cscanq->right = cscanq->left;
                        cscanq->left.qlen = 0;
                        cscanq->left.queue = cscanq->left.qtail = NULL;
                        DO_HEAD_DEQ(req, &cscanq->right);
                }
        }
        RF_ASSERT(req);
        cscanq->last_sector = req->sectorOffset;
        return (req);
}

int
rf_SstfPromote(void *qptr, RF_StripeNum_t parityStripeID, RF_ReconUnitNum_t which_ru)
{
        RF_DiskQueueData_t *r, *next;
        RF_Sstf_t *sstfq;
        int     n;

        sstfq = (RF_Sstf_t *) qptr;

        n = 0;
        for (r = sstfq->lopri.queue; r; r = next) {
                next = r->next;
#if RF_DEBUG_QUEUE
                if (rf_sstfDebug || rf_scanDebug || rf_cscanDebug) {
                        printf("raid%d: check promote %lx\n",
                               r->raidPtr->raidid, (long) r);
                }
#endif
                if ((r->parityStripeID == parityStripeID)
                    && (r->which_ru == which_ru)) {
                        do_dequeue(&sstfq->lopri, r);
                        rf_SstfEnqueue(qptr, r, RF_IO_NORMAL_PRIORITY);
                        n++;
                }
        }
#if RF_DEBUG_QUEUE
        if (rf_sstfDebug || rf_scanDebug || rf_cscanDebug) {
                printf("raid%d: promoted %d matching I/Os queues are %d,%d,%d\n",
                       r->raidPtr->raidid, n, sstfq->left.qlen,
                       sstfq->right.qlen, sstfq->lopri.qlen);
        }
#endif
        return (n);
}