/*
 *	Network Queueing System (NQS)
 *  This version of NQS is Copyright (C) 1992  John Roman
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 1, or (at your option)
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
/*
*  PROJECT:     Network Queueing System
*  AUTHOR:      John Roman
*
*  Modification history:
*
*       Version Who     When            Description
*       -------+-------+---------------+-------------------------
*       V01.10  JRR                     Initial version.
*       V01.2   JRR     12-Feb-1992	Fixed declaration of static routines.
*       V01.3   JRR     02-Mar-1992	Added Cosmic V2 changes.
*	V01.4	JRR	17-Jun-1992	Added header.
*	V01.5	JRR			Placeholder.
*	V01.6	JRR			Placeholder
*	V01.7	JRR	01-Mar-1994	Added support for Solaris.
*/
/*++ nqs_dsc.c - Network Queueing System
 *
 * $Source: /usr2/jrroma/nqs/nqs-3.35.6/src/RCS/nqs_dsc.c,v $
 *
 * DESCRIPTION:
 *
 *	This module contains the 3 functions:
 *
 *		dsc_reqcom()
 *		dsc_sched()
 *		dsc_spawn()
 *
 *	which control the scheduling, and spawning of NQS device requests.
 *	This module can be modified to implement appropriate scheduling
 *	algorithms for a particular installation as necessary.
 *
 *
 *	Author:
 *	-------
 *	Brent A. Kingsbury, Sterling Software Incorporated.
 *	August 12, 1985.
 *
 *
 * STANDARDS VIOLATIONS:
 *   None.
 *
 * REVISION HISTORY: ($Revision: 1.7 $ $Date: 1994/03/30 20:36:33 $ $State: Exp $)
 * $Log: nqs_dsc.c,v $
 * Revision 1.7  1994/03/30  20:36:33  jrroma
 * Version 3.35.6
 *
 * Revision 1.6  92/12/22  15:39:52  jrroma
 * Version 3.30
 * 
 * Revision 1.5  92/06/18  17:31:01  jrroma
 * Added gnu header
 * 
 * Revision 1.4  92/03/02  13:48:00  jrroma
 * Added Cosmic V2 changes.
 * 
 * Revision 1.3  92/02/12  16:15:14  jrroma
 * *** empty log message ***
 * 
 * Revision 1.2  92/02/12  13:38:05  jrroma
 * Fixed declaration of static routines.
 * 
 * Revision 1.1  92/02/12  13:37:06  jrroma
 * Initial revision
 * 
 *
 */

#include "nqs.h"			/* NQS constants and data types */
#include "nqsxvars.h"			/* NQS global variables */
#include <string.h>
#ifndef __CEXTRACT__
#if __STDC__

static int complex_space_4deviceq ( struct nqsqueue *queue );
static struct request *sel_request ( struct device *device, struct nqsqueue *tie_loser );
static void update_devqueues ( void );

#else /* __STDC__ */

static int complex_space_4deviceq (/* struct nqsqueue *queue */);
static struct request *sel_request (/* struct device *device, struct nqsqueue *tie_loser */);
static void update_devqueues (/* void */);

#endif /* __STDC__ */
#endif /* __CEXTRACT__ */

/*** dsc_reqcom
 *
 *
 *	void dsc_reqcom():
 *
 *	This function is invoked whenever a running device req completes
 *	execution.  This function is free to spawn more device reqs from
 *	ANY device queue.
 *
 *	The internal device structure AND the NQS database image of the
 *	device that was handling the request have been updated to indicate
 *	an idle state.
 *
 *	All queues and devices modified by the invocation of this procedure
 *	have had their corresponding NQS database images updated (to match
 *	their internal states) upon return from this procedure.
 */
void dsc_reqcom (
	struct device *device,		/* Device that just finished */
					/* handling the request */
	struct request *request)	/* The completed device request */
{
	register struct request *newreq;/* Ptr to new request to be spawned */
        register struct request *tagreq;/* Current tagged request */
        struct nqsqueue *queue;            /* Current queue */
        struct device *curdevice, *tagdevice;
        int  curpriority;               /* Current priority */


	if (Shutdown) return;		/* Do not spawn any new device */
					/* requests if NQS is shutting down */
        /*
         *  If this queue is in a complex, try to schedule
         *  a batch request
         *
         *  Since we just finished a device request go to a
         *  batch request so we don't starve it.  Then, try
         *  to spawn a device request.
         */
        if (request->queue->v1.device.qcomplex[0] !=
                (struct qcomplex *) 0) {
                bsc_spawn();

                /*
                 *  Since we have a complex, we must go through
                 *  a modified dsc_spawn so a particular device on the
                 *  mux will not be starved.  To do this, we must
                 *  augment spawning so that the current device is not
                 *  selected first.  This will prevent one queue from
                 *  starving the rest.  That is why the current
                 *  device is always checked last.  In addition,
                 *  if there is a request in a higher priority queue
                 *  then run it before the rest.  Otherwise, it will
                 *  be a round robin scheduling algorithm.
                 */
                tagreq = (struct request * ) 0;
                queue = device->curque;
                curdevice = device;
                curpriority = -1;
                do {
                        device = device->next;  /* Check the next device */
                        if (device == (struct device *) 0)
                                device = Devset;
                        newreq = sel_request (device, queue);
                        if (newreq != (struct request *) 0) {
                                /*
                                 * If no requests have been assigned,
                                 * then tag this request
                                 */

                                if (curpriority == -1) {
                                        tagreq = newreq;
                                        tagdevice = device;
                                        curpriority = tagreq->queue->
                                                        q.priority;
                                }

                                /*
                                 * Only tag this request if it has a
                                 * higher pri.  If it is equal pri, it
                                 * will be round robin.
                                 */
                                else if (newreq->queue->q.priority >
                                        curpriority) {
                                        tagreq = newreq;
                                        tagdevice = device;
                                        curpriority = tagreq->queue->
                                                        q.priority;
                                }
                        }
                } while (device != curdevice);
                if (tagreq != (struct request *) 0) {
                        /*
                         * There is a request that can be run on
                         * the device from the selected queue.
                         * Spawn it.
                         */
                        nqs_spawn (tagreq, tagdevice);
                }
                update_devqueues();   /* Update db image of queues */
        }       /* end of if queue in complex */
        else {
                newreq = sel_request (device, request->queue);
                if (newreq != (struct request *) 0) {
                        /*
                         *  There is a request that can be run on
                         *  the specified device.  Spawn it.
                         */
                        nqs_spawn (newreq, device);
			nqs_spawn (newreq, device);
		}
		update_devqueues();	/* Update database image of queues */
	}
}

/*** dsc_sched
 *
 *
 *	int dsc_sched():
 *
 *	This function is invoked whenever a device req must be evaluated
 *	and assigned a priority by the NQS device scheduling policies
 *	(which are implemented by this function).
 *
 *	The priority assigned to the req must be in the interval [0..32767].
 *	All device reqs with priority >= N within a given device queue, will,
 *	depending upon the precise scheduling criteria defined in:
 *
 *		dsc_spawn()  and
 *		dsc_reqcom()
 *
 *	be spawned before any device reqs in the same queue with a priority
 *	value < N.
 *	
 *	Returns:
 *		The assigned priority value for the specified device request.
 */
int dsc_sched (struct rawreq *rawreq)
{
	if (rawreq->rpriority == -1) {
		/*
		 *  The user did not specify a priority; assign a
		 *  default priority.
		 */
		return (Defdevpri);
	}
	return (rawreq->rpriority);	/* For now, just the intra-queue */
}					/* Priority */


/*** dsc_spawn
 *
 *
 *	void dsc_spawn():
 *
 *	This function is invoked whenever request activity indicates that
 *	it MAY be possible to spawn a device request.  It is up to the
 *	discretion of the device request scheduling/spawning algorithm
 *	implemented here to determine whether or not device req(s) should
 *	be spawned.
 *
 *	All queues and devices modified by the invocation of this procedure
 *	have had their corresponding NQS database images updated (to match
 *	their internal states) upon return from this procedure.
 */
void dsc_spawn()
{
	register struct device *device;	/* Ptr to device */
	register struct request *newreq;/* Ptr to new request to be spawned */

	if (Shutdown) return;		/* Do not spawn any new device */
					/* requests if NQS is shutting down */
	device = Devset;
	/*
	 *  Walk the device set looking for enabled devices which are not
	 *  doing anything.
	 */
	while (device != (struct device *) 0) {
		newreq = sel_request (device, (struct nqsqueue *) 0);
		if (newreq != (struct request *) 0) {
			/*
			 *  There is a request that can be run on the
			 *  device from the selected queue.  Spawn it.
			 */
			nqs_spawn (newreq, device);
		}
		device = device->next;	/* Check the next device */
	}
	update_devqueues();		/* Update database image of queues */
}


/*** sel_request
 *
 *
 *	struct request *sel_request():
 *
 *	Determine the request that should be served next by the specified
 *	device.
 *
 *	Returns:
 *		A pointer to the first runnable request residing in the
 *		highest priority queue that should should be served next
 *		by the specified device.  If there are no queues containing
 *		requests to be started on the specified, device, or the
 *		device is disabled, or the device is actively serving a
 *		current request, then (struct request *) 0 is returned.
 */
static struct request *sel_request (
	struct device *device,		/* Device */
	struct nqsqueue *tie_loser)	/* If two equal priority */
					/* queues are tied for highest*/
					/* priority, then do NOT spawn*/
					/* a request from this queue so */
					/* that we have round-robin */
					/* scheduling between queues at */
					/* the same priority. */
					/* (This argument can be NULL) */
{
	register struct qdevmap *map;	/* Pointer to queue/device mapping */
	register struct nqsqueue *highest;	/* Highest priority queue */
					/* served by device */
	register struct nqsqueue *queue;	/* Device queue set walking */
	register short is_a_tie;	/* BOOLEAN non-zero if tie for first */
	register int priority;		/* Winning high priority value */
	register struct qdevmap *map_loser;
					/* Ptr to map entry of loser for */
					/* the device */
	register struct request *wreq;	/* Used to walk request set for queue */
	struct request *newreq;		/* Chosen request to be run next */
	char *cp;			/* Pointer to forms name */

	if ((device->status & DEV_ENABLED) == 0 ||
	    (device->status & DEV_ACTIVE)) return ((struct request*) 0);
	/*
	 *  The device is enabled, but is inactive.
	 */
	newreq = (struct request *) 0;	/* Not yet known */
	highest = (struct nqsqueue *) 0;	/* Not yet known */
	map_loser = (struct qdevmap *)0;/* Not yet known */
	map = device->dqmaps;		/* First mapping */
	/*
	 *  Loop over all device/queue mappings, to find the highest
	 *  priority queue served by this device, that also has a
	 *  request that can be run on the specified device.
	 */
	while (map != (struct qdevmap *) 0) {
	    queue = map->queue;
	    if (queue == tie_loser) map_loser = map;
	    if (highest == (struct nqsqueue *) 0) {
		if (queue->q.queuedcount &&
                   (queue->q.status & QUE_RUNNING &&
                    complex_space_4deviceq(queue))) {
		    wreq = queue->queuedset;
		    while (wreq != (struct request *) 0) {
			/*
			 *  Loop over queued requests.
			 */
			if (wreq->v1.req.v2.dev.forms [0] != '\0') {
			    cp = wreq->v1.req.v2.dev.forms;
			}
			else cp = Defprifor;	/* Default */
			if ((*cp=='\0') || (strcmp (device->forms, cp) == 0)) {
			    /*
			     *  There exists a request in the
			     *  queue that can run on the
			     *  specified device.
			     */
			    is_a_tie = 0;	/* No ties */
			    highest = queue;
			    newreq = wreq;
			    priority = queue->q.priority;
			    wreq = (struct request *) 0;
						/* Exit loop */
			}
			else wreq = wreq->next;
		    }
		}
	    }
            else if (queue->q.queuedcount &&
                    (queue->q.status & QUE_RUNNING &&
                     complex_space_4deviceq(queue))) {
		/*
		 *  This queue has requests that could run.
		 */
		if (queue->q.priority > priority) {
		    /*
		     *  This queue has a higher priority than
		     *  the current chosen queue.
		     */
		    wreq = queue->queuedset;
		    while (wreq != (struct request *) 0) {
			/*
			 *  This request is not blocked from
			 *  execution.
			 */
			if (wreq->v1.req.v2.dev.forms [0] != '\0') {
			    cp = wreq->v1.req.v2.dev.forms;
			}
			else cp = Defprifor;	/* Default */
			if ((*cp=='\0') || (strcmp (device->forms, cp) == 0)) {
			    /*
			     *  There exists a request in the
			     *  queue that can run on the
			     *  specified device.
			     */
			    is_a_tie = 0;	/* No ties */
			    highest = queue;
			    newreq = wreq;
			    priority = queue->q.priority;
			    wreq = (struct request *) 0;
						/* Exit loop */
			}
			else wreq = wreq->next;
		    }
		}
		else if (priority == queue->q.priority) {
		    /*
		     *  This queue has a priority equal to
		     *  the currently chosen queue.
		     */
		    wreq = queue->queuedset;
		    while (wreq != (struct request *) 0) {
			/*
			 *  This request is not blocked from
			 *  execution.
			 */
			if (wreq->v1.req.v2.dev.forms [0] != '\0') {
			    cp = wreq->v1.req.v2.dev.forms;
			}
			else cp = Defprifor;	/* Default */
			if ((*cp=='\0') || (strcmp (device->forms, cp) == 0)) {
			    /*
			     *  There exists a request in the
			     *  queue that can run on the
			     *  specified device.
			     */
			    is_a_tie = 1;	/* Tie for 1st */
			    wreq = (struct request *) 0;
						/* Exit loop */
			}
			else wreq = wreq->next;
		    }
		}
	    }
	    map = map->nextdq;	/* Examine next device/queue mapping */
	}
	if (highest != (struct nqsqueue *) 0 &&
	    map_loser != (struct qdevmap *) 0) {
	    /*
	     *  A queue exists with runable request(s), and we may have to
	     *  break a tie by implementing "round-robin" scheduling.
	     */
	    if (tie_loser->q.priority == priority && is_a_tie) {
		/*
		 *  Tie break required; do "round-robin" scheduling.
		 */
		map = map_loser;
		newreq = (struct request *) 0;	/* Request unknown */
		do {
		    /*
		     *  Locate the next queue whose priority is
		     *  equal to the loser queue, and whose status
		     *  is running, with requests in the queued set.
		     */
		    do {
			map = map->nextdq;
			if (map == (struct qdevmap *) 0) map = device->dqmaps;
			highest = map->queue;
		    } while (highest->q.priority != priority ||
			    (highest->q.status & QUE_RUNNING) == 0 ||
			     highest->q.queuedcount == 0);
		    /*
		     *  Now, determine if the queue has request(s)
		     *  that can run on the specified device.
		     */
		    wreq = highest->queuedset;
		    while (wreq != (struct request *) 0) {
			/*
			 *  This queue has a request that is ready
			 *  to run.
			 */
			if (wreq->v1.req.v2.dev.forms [0]) {
			    cp = wreq->v1.req.v2.dev.forms;
			}
			else cp = Defprifor;	/* Default */
			if ((*cp=='\0') || (strcmp (device->forms, cp) == 0)) {
			    /*
			     *  There exists a request in the
			     *  queue that can run on the
			     *  specified device.
			     */
			    newreq = wreq;	/* Ptr to req*/
			    wreq = (struct request *) 0;
						/* Exit loop */
			}
			else wreq = wreq->next;
		    }
		    /*
		     *  Keep looking until we've found the proper
		     *  queue and request.
		     */
		} while (newreq == (struct request *) 0);
	    }
	}
	return (newreq);
}


/*** update_devqueues
 *
 *
 *	void update_devqueues():
 *	Update database image of device queues as necessary.
 */
static void update_devqueues()
{
	register struct nqsqueue *queue;	/* Walk queue set */

	queue = Nonnet_queueset;	/* Non-network queue set */
	while (queue != (struct nqsqueue *) 0) {
		/*
		 *  Update NQS database image of queue states as
		 *  necessary.
		 */
		if (queue->q.type == QUE_DEVICE &&
		   (queue->q.status & QUE_UPDATE)) {
			/*
			 *  This queue is a device queue, and the NQS
			 *  database image of the queue must be updated.
			 */
			udb_qorder (queue);
		}
		queue = queue->next;
	}
}
/*** complex_space_4deviceq
 *
 *
 *    int complex_space_4deviceq():
 *
 *    This function checks to see if there is room in the complex
 *    for another spawned request.
 */
static int complex_space_4deviceq(struct nqsqueue *queue)
{
       struct qcomplex *qcomplex;
       int complexno;

       for (complexno = 0; complexno < MAX_COMPLXSPERQ &&
               (qcomplex = queue->v1.device.qcomplex[complexno]) !=
               (struct qcomplex *) 0; complexno++) {
               if (qcomplex->runlimit == qcomplex->runcount)
                       return (0);     /* A complex this queue is a member */
                                       /* of has reached its run limit */
       }
       return (1);     /* We've looked at all complexes */
                       /* and there is room */
}
