/*
 * Copyright (c) 2000-2001 Silicon Graphics, Inc.
 * All Rights Reserved.
 *
 * 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.
 *
 * This program is distributed in the hope that it would 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 the Free Software Foundation,
 * Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
#ifndef RING_H
#define RING_H

/* ring - readahead/writeahead abstraction
 *
 * the ring is conceptually an ordered set of messages circulating between the
 * client thread and the I/O worker thread. a message can be in one of four
 * places: on the ready queue, held by the client, on the active queue, or held
 * by the worker. The client and worker can each hold at most one message at a
 * time. all others must be on one of the two queues. the messages must
 * circulate in that order: ready, client, active, worker, ready, ...
 * initially all messages are on the ready queue, with status set to
 * INIT. The client uses ring_get to remove a message from the ready queue.
 * the client can then use the message to read or write. to read, the client
 * sets the op field to READ, and places the message on the active queue. the
 * worker will remove messages from the active queue, invoke the client-supplied
 * read function with the message's buffer, record the read function's return
 * value in the message, set the message status to OK (read function returned 0)
 * or ERROR (read returned non-zero), and place the message on the ready queue.
 * the client will see the message after removing all messages ahead of it on
 * the ready queue. to write, the client follows almost the same procedure,
 * except the client fills the buffer and sets the op to WRITE prior to placing
 * the message on the active queue.
 *
 * if the client-supplied read or write function returns an error, the worker
 * will set the message status to ERROR. the worker will pass all subsequent
 * messages appearing on the active queue directly to the ready queue with
 * no I/O done and the message status set to IGNORE. the worker will remain
 * in this state until a reset is performed (see below).
 *
 * The client may at anytime place a NOP msg on the ring. the worker does
 * nothing with this mmessage other than to place it back on the ready queue
 * with NOPACK status. This is useful for inhibiting read-ahead.
 *
 * To flush the ring, the client must repetatively place TRACE messages on the
 * active queue until it sees an IGNORE msg on the ready queue. the worker will
 * simply transfer TRACErs from active to ready with no other action taken
 * (other than to set the message status to IGNORE).
 *
 * the client may at any time reset the ring. the reset will return to the
 * client when the current I/O being executed by the worker completes, and
 * all messages have been wiped clean and placed on the ready queue with
 * status set to INIT. the ring_reset function accomplishes this internally by
 * placing a RESET message on the active QUEUE, and continuing to remove
 * messages from the ready queue (without placing them on the active queue)
 * until the RESET message is seen the worker responds to a reset message by
 * setting the status to RESETACK, queueing the message on the ready queue, and
 * waiting for a message from the active queue. ring_reset will then re-
 * initialize the ring and return. note that the client may be holding one
 * message at the time the reset is called. if so, it must pass a pointer to
 * that message into the reset call. otherwise it must pass in NULL.
 *
 * the ring_destroy function may be invoked to shut down the ring and kill the
 * worker thread. it simply places a DIE message on the active queue, and waits
 * for a DIEACK response. it then de-allocates all semaphores memory allocated
 * by ring_create.
 *
 * the message structure includes a 64 bit field for the convenience
 * of the client. it is not perturbed during any ring operations.
 *
 * the ring maintains four performance metering values: the number of times
 * the worker and client attempted to get a message, and the number of times
 * those attempts resulting in blocking.
 */


/* ring_msg - structure of messages managed by ring
 */
enum ring_op { RING_OP_NONE,
	       RING_OP_READ,
	       RING_OP_WRITE,
	       RING_OP_NOP,
	       RING_OP_TRACE,
	       RING_OP_RESET,
	       RING_OP_DIE };

typedef enum ring_op ring_op_t;

enum ring_stat { RING_STAT_INIT,
	         RING_STAT_OK,
	         RING_STAT_ERROR,
	         RING_STAT_NOPACK,
	         RING_STAT_IGNORE,
	         RING_STAT_RESETACK,
	         RING_STAT_DIEACK };

typedef enum ring_stat ring_stat_t;

enum ring_loc { RING_LOC_READY,
	        RING_LOC_ACTIVE,
	        RING_LOC_CLIENT,
	        RING_LOC_SLAVE };

typedef enum ring_loc ring_loc_t;

struct ring_msg {
	ring_op_t rm_op;
	ring_stat_t rm_stat;
	int rm_rval;
	off64_t rm_user;
	char *rm_bufp;
/* ALL BELOW PRIVATE!!! */
	size_t rm_mix;
	ring_loc_t rm_loc;
};

typedef struct ring_msg ring_msg_t;


/* ring - instantiation of a ring
 */
struct ring {
	off64_t r_client_msgcnt;
	off64_t r_worker_msgcnt;
	off64_t r_client_blkcnt;
	off64_t r_worker_blkcnt;
	time32_t r_first_io_time;
	off64_t r_all_io_cnt;
/* ALL BELOW PRIVATE!!! */
	size_t r_len;
	ring_msg_t *r_msgp;
	size_t r_ready_in_ix;
	size_t r_ready_out_ix;
	qsemh_t r_ready_qsemh;
	size_t r_active_in_ix;
	size_t r_active_out_ix;
	qsemh_t r_active_qsemh;
	size_t r_client_cnt;
	size_t r_worker_cnt;
	int (*r_readfunc)(void *contextp, char *bufp);
	int (*r_writefunc)(void *contextp, char *bufp);
	void *r_clientctxp;
};

typedef struct ring ring_t;


/* ring_create - creates a ring. parameters supply the length of the ring,
 * the read/write buffer size, the drive index, a function for reading, a
 * function for writing, and a pointer to client context for the read and
 * write functions. returns a pointer to a ring if successful, a NULL
 * pointer if not. the read and write functions should return 0 on success,
 * or an error code on failure which will be recorded in the rm_rval field
 * of the message invoking the failed operation. if null pointer returned,
 * the location pointed to by rvalp will contain one of the following:
 * ENOMEM - could not allocate some portion of the ring memory;
 * E2BIG - insufficient physical memory available for pinning;
 * EPERM - exceeds allowed amount of pinned down memory.
 */
extern ring_t *ring_create(size_t ringlen,
			    size_t bufsz,
			    bool_t pinpr,
			    ix_t drive_index,
			    int (*readfunc)(void *clientctxp, char *bufp),
			    int (*writefunc)(void *clientctxp, char *bufp),
			    void *clientctxp,
			    int *rvalp);


/* ring_get - get a message off the ready queue
 */
extern ring_msg_t *ring_get(ring_t *ringp);


/* ring_put - put a message on the active queue
 */
extern void ring_put(ring_t *ringp, ring_msg_t *msgp);


/* ring_reset - re-initialize the ring, after the current I/O completes.
 * msgp must be NULL if the client is not currently holding a ring message.
 * otherwise it must point to that message.
 */
extern void ring_reset(ring_t *ringp, ring_msg_t *msgp);

/* ring_destroy - de-allocates ring
 */
extern void ring_destroy(ring_t *ringp);

#endif /* RING_H */
