/* Copyright (c) 1993, 1999, Oracle and/or its affiliates. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

 
/* 
 * dga_db.c - the client side code for DGA double buffering
 */


#include <stdio.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <X11/Xlib.h>

#include <sys/fbio.h>

#ifndef FBIOVRTOFFSET
#ifdef SVR4
#define FBIOVRTOFFSET	(FIOC | 38)
#else
#define FBIOVRTOFFSET   _IOR(F, 38, int)
#endif
#endif

#include <netdb.h>
#include <sys/stat.h>
#include <unistd.h>

#include "dga_incls.h"

#define	DGA_WIN_LOCK_NOMODIF(win)			\
{							\
    if ((((_Dga_window)(win))->w_lockcnt)++ == 0) {	\
	DGA_LOCK(win);					\
	}						\
}

#define DGA_WIN_UNLOCK_NOMODIF(win)		\
{						\
    if (--(((_Dga_window)(win))->w_lockcnt) == 0) \
	DGA_UNLOCK(win);			\
}
    
extern void *_dga_is_X_window(Dga_token token, Display **dpyp, Window *winp);

#ifdef _LP64
static int _dga_db_vrtfunc_internal(Dga_window);
#else  /* _LP64 */
#if defined(__STDC__)
static int _dga_db_vrtfunc_internal(Dga_window);
#else
static int _dga_db_vrtfunc_internal();
#endif
#endif /* _LP64 */

static u_int *dga_vrt_access();
static void dga_vrt_release();

int 
dga_db_access(wg_clientpi)
     Dga_window wg_clientpi ;
{
    _Dga_window wg_clientp = (struct dga_window *)wg_clientpi;
     WXINFO	*infop = wx_infop(wg_clientp) ;
     u_int	*cpage;
     u_int	*dga_vrt_access();

    if (wg_clientp->db_enabled)
	return 0;
    if (infop->wx_dbuf.number_buffers < 2)
	return 1;

    cpage = dga_vrt_access(wg_clientp->w_devfd);

    if (cpage)
    {
	wg_clientp->db_enabled = 1;
	wg_clientp->db_vrtcntrp = cpage;
	wg_clientp->db_lastvrtcntr = *cpage;
	wg_clientp->db_swapint = 1;
	wg_clientp->vrt_func = _dga_db_vrtfunc_internal ;
	if ((infop->wx_dbuf.display_buffer < 0) ||
	 (infop->wx_dbuf.display_buffer > (infop->wx_dbuf.number_buffers - 1)))
		infop->wx_dbuf.display_buffer = 0;
	if ((infop->wx_dbuf.read_buffer < 0) ||
	   (infop->wx_dbuf.read_buffer > (infop->wx_dbuf.number_buffers - 1)))
		infop->wx_dbuf.read_buffer = 1;
	if ((infop->wx_dbuf.write_buffer < 0) ||
	    (infop->wx_dbuf.write_buffer > (infop->wx_dbuf.number_buffers - 1)))
		infop->wx_dbuf.write_buffer = 1;
	return(0);
    }
    else
	return(1);
}

void
dga_db_release(wg_clientpi)
     Dga_window wg_clientpi ;
{
     void	dga_vrt_release();
    _Dga_window wg_clientp = (struct dga_window *)wg_clientpi;
    
     if (wg_clientp->db_vrtcntrp)
         dga_vrt_release(wg_clientp->db_vrtcntrp);
     wg_clientp->db_enabled = 0;
 
}


void
dga_db_write(wg_clientpi,buffer,writefunc,data)
     Dga_window wg_clientpi ;
     int buffer;
#ifdef _LP64
     int (*writefunc)(void*, Dga_window, int);
#else  /* _LP64 */
#if defined (__STDC__)
     int (*writefunc)(void*, Dga_window, int);
#else
    int (*writefunc)();
#endif
#endif /* _LP64 */
     void* data;
{
    _Dga_window wg_clientp = (struct dga_window *)wg_clientpi;
    
     DGA_WIN_LOCK_NOMODIF(wg_clientp);
     if (writefunc)
	(*writefunc) (data,wg_clientpi,buffer);
     wx_infop(wg_clientp)->wx_dbuf.write_buffer = buffer; 
     DGA_WIN_UNLOCK_NOMODIF(wg_clientp);
}

void
dga_db_read(wg_clientpi,buffer,readfunc,data)
     Dga_window wg_clientpi ;
     int buffer;
#ifdef _LP64
     int (*readfunc)(void*, Dga_window, int);
#else  /* _LP64 */
#if defined (__STDC__)
     int (*readfunc)(void*, Dga_window, int);
#else
    int (*readfunc)();
#endif
#endif /* _LP64 */
     void* data;
{
    _Dga_window wg_clientp = (struct dga_window *)wg_clientpi;
    
     DGA_WIN_LOCK_NOMODIF(wg_clientp);
     wx_infop(wg_clientp)->wx_dbuf.read_buffer = buffer; 
     if (readfunc)
	(*readfunc) (data,wg_clientpi,buffer);
     DGA_WIN_UNLOCK_NOMODIF(wg_clientp);
}


void
dga_db_display(wg_clientpi,buffer,visfunc,data)
     Dga_window wg_clientpi ;
     int buffer;
#ifdef _LP64
     int (*visfunc)(void*, Dga_window, int);
#else  /* _LP64 */
#if defined (__STDC__)
     int (*visfunc)(void*, Dga_window, int);
#else
    int (*visfunc)();
#endif
#endif /* _LP64 */
     void* data;
{
    _Dga_window wg_clientp = (struct dga_window *)wg_clientpi;
    
    if (!dga_db_interval_check(wg_clientpi))
       dga_db_interval_wait(wg_clientpi);
    if (visfunc) 
	(*visfunc)(data, wg_clientpi, buffer);
    if (wg_clientp->db_vrtcntrp)
        wg_clientp->db_lastvrtcntr = *wg_clientp->db_vrtcntrp;
    wx_infop(wg_clientp)->wx_dbuf.display_buffer = buffer;
}

void
dga_db_interval(wg_clientpi,interval)
     Dga_window wg_clientpi ;
     int interval;		/* number of milliseconds */
{
     int ref_rate;
     float rr;
     _Dga_window wg_clientp = (struct dga_window *)wg_clientpi;

     ref_rate  = wx_infop(wg_clientp)->w_refresh_period;
    if (ref_rate == 0) ref_rate = 66000;

    if (interval < 0)
	interval = 0;
    if (interval > ref_rate)
	interval = ref_rate;
    rr = ((float)ref_rate)* 0.001;
    wg_clientp->db_swapint = rr * ((float)interval* 0.001);

    if (wg_clientp->db_swapint == 0)
        wg_clientp->db_swapint = 1;
}


void
dga_db_interval_wait(wg_clientpi)
     Dga_window wg_clientpi ;
{
    _Dga_window wg_clientp = (struct dga_window *)wg_clientpi;
    u_int *counter = wg_clientp->db_vrtcntrp;

    /* Do a block if necessary and if vrt_func has been supplied */

    if (!wg_clientp->vrt_func || !wg_clientp->db_vrtcntrp) return;

    while (((u_int) (*counter - wg_clientp->db_lastvrtcntr))
          < wg_clientp->db_swapint)
    {
	if ((*(wg_clientp->vrt_func))(wg_clientpi) < 0) return ;;
    }
    return;
}

int
dga_db_interval_check(wg_clientpi)
     Dga_window wg_clientpi ;
{
    _Dga_window wg_clientp = (struct dga_window *)wg_clientpi;
     u_int *counter = wg_clientp->db_vrtcntrp;

     if ((wg_clientp->db_vrtcntrp) && 
	 ((u_int) (*counter - wg_clientp->db_lastvrtcntr))
          < wg_clientp->db_swapint)
        return(0);
     else
   	return(1);
}

int
dga_db_write_inquire(wg_clientpi)
     Dga_window wg_clientpi ;
{
    _Dga_window wg_clientp = (struct dga_window *)wg_clientpi;
     return(wx_infop(wg_clientp)->wx_dbuf.write_buffer);
}

int
dga_db_read_inquire(wg_clientpi)
     Dga_window wg_clientpi ;
{
    _Dga_window wg_clientp = (struct dga_window *)wg_clientpi;
    
     return(wx_infop(wg_clientp)->wx_dbuf.read_buffer);
}

int
dga_db_display_inquire(wg_clientpi)
     Dga_window wg_clientpi ;
{
    _Dga_window wg_clientp = (struct dga_window *)wg_clientpi;
     return(wx_infop(wg_clientp)->wx_dbuf.display_buffer);
}

/* INTERNAL INTERFACE */
int
dga_db_display_complete(wg_clientpi, flag)
    Dga_window wg_clientpi ;
    int flag;
{
    _Dga_window wg_clientp = (struct dga_window *)wg_clientpi;
    if (wg_clientp->db_vrtcntrp && 
	(wg_clientp->db_lastvrtcntr != *(wg_clientp->db_vrtcntrp)))
	return 1;
    if (flag == 0)
	return 0;
    if (ioctl(wg_clientp->w_devfd,FBIOVERTICAL,0) < 0)
	return 0;
    return 1;
}

/* INTERNAL INTERFACE */
static u_int
*dga_vrt_access(devfd)
   int devfd;
{
        u_int *cpage;
        u_int dev_offset;
	u_int pagesize;

        dev_offset = 0;
        if (ioctl(devfd, FBIOVRTOFFSET, &dev_offset) < 0)
           return (NULL);

#ifdef SVR4
	pagesize = sysconf(_SC_PAGESIZE);
#else
	pagesize = getpagesize();
#endif

	/*
	 * the driver provides the dev_offset into the mmaped page
	 * where the vertical retrace counter word is.
	 * We will mmap a shared memory page that is on a
	 * page boundary then modify the pointer to the
	 * vertical retrace counter to reflect the exact
	 * location where the counter word exists
	 */

	cpage = (u_int *) mmap((char *)0,
				pagesize,
				PROT_READ | PROT_WRITE,
				MAP_SHARED | _MAP_NEW,
				devfd,
				(off_t) (dev_offset & ~(pagesize - 1)));

	/* if the mmap failed then return NULL otherwide return
	 * the computed address of the vertical retract counter
	 * by adding the beginning of the mmaped page to the
	 * dev_offset into the page returned by the device driver
	 */

	if (cpage == (u_int *) -1)
		return NULL;

        return ((u_int *) ((int) cpage | (dev_offset & (pagesize - 1))));
}

static void
dga_vrt_release(counter)
u_int *counter;
{
	char *counter_page;
	int pagesize;

#ifdef SVR4
	pagesize = sysconf(_SC_PAGESIZE);
#else
	pagesize = getpagesize();
#endif

	/* Unmap the page for this client.
	 * remove the offset computation and munmap the
	 * vertical retrace counter page.  we remove the
	 * offset by simply setting the lower bits of 
	 * the address to 0
	 */

	counter_page = (char *) (((int) counter) & (~(pagesize - 1)));
        munmap(counter_page, pagesize) ;
	return;
}

/* New routines that will be exposed to the public */

int 
dga_db_display_done(wg_clientpi,flag, display_done_func)
Dga_window wg_clientpi;
int flag;
#ifdef _LP64
int (*display_done_func)(Dga_window);
{
    int (*vrt_func)(Dga_window);
#else  /* _LP64 */
#if defined (__STDC__)
int (*display_done_func)(Dga_window);
#else
int (*display_done_func)();
#endif
{
#if defined (__STDC__)
    int (*vrt_func)(Dga_window);
#else
    int (*vrt_func)();
#endif
#endif /* _LP64 */
    _Dga_window wg_clientp = (struct dga_window *)wg_clientpi;
    int ret_val;
    
    /* Return 1 = done, 0 = not done, -1 = error */
    ret_val = (*display_done_func)(wg_clientpi);

    /* If the user does not want to block or if the
     * display_done_func() returned a non-zero, we return
     * else we try till success after each vrt_retrace()
    */
    if ((!flag) || (ret_val != 0))
	return (ret_val);
    
    vrt_func = wg_clientp->vrt_func;
    if (!vrt_func) return 1;

    while((ret_val = (*display_done_func)(wg_clientpi)) == 0)
	if (vrt_func(wg_clientpi) < 0) return -1;
    return (ret_val);
}

/* Returns 0 on fail and non-zero on success */
int
dga_db_grab(clientpi, nbuffers, vrtfunc, vrtcounterp)
Dga_window clientpi;
int nbuffers;
#ifdef _LP64
int (*vrtfunc)(Dga_window);
#else  /* _LP64 */
#if defined (__STDC__)
int (*vrtfunc)(Dga_window);
#else
int (*vrtfunc)();
#endif
#endif /* _LP64 */
u_int *vrtcounterp;
{
    Display	*dpy;
    Window	 win;

    /*
     *  Check for an invalid Dga_window
     */
     _Dga_window clientp = (struct dga_window *)clientpi;
     WXINFO	*infop;
    
    if ((clientp == (_Dga_window) NULL)) {
#ifdef DEBUG
	(void) fprintf(stderr, "dga_db_grab: passed null pointer\n");
#endif
	return (0);
    }

    /*
     *  If the buffers has already been grabbed. Don't
     *  do anything.
     */
    if (clientp->db_enabled)
	return (1);

    /*
     *  Find out if this is an X window.  If so get the Display and window
     *  id.  
     */
     if (!_dga_is_X_window(clientp->w_token, &dpy, &win)) {
#ifdef DEBUG
	(void) fprintf(stderr, "dga_db_grab: Unsupported window type\n");
#endif
	return (0);
    }

    /*
     *  Request the server to allow DGA to the buffers associated
     *  with this Dga_window.
     */
    if (!XDgaGrabBuffers(dpy, win, nbuffers)) {
#ifdef DEBUG
	(void) fprintf(stderr, "dga_db_grab: XDgaGrabBuffers failed\n");
#endif
	return (0);
    }

    /* Now if they supplied vrtfunc, update the clientp
     */

    infop =wx_infop(clientp) ;
    clientp->vrt_func = vrtfunc;

    /* Now update the clientp pointer with other info */
    if (vrtcounterp) {
	clientp->db_enabled = 1;
	clientp->db_vrtcntrp = (u_int *)vrtcounterp;
	clientp->db_lastvrtcntr = *vrtcounterp;
	clientp->db_swapint = 1;
	if ((infop->wx_dbuf.display_buffer < 0) ||
	 (infop->wx_dbuf.display_buffer > (infop->wx_dbuf.number_buffers - 1)))
		infop->wx_dbuf.display_buffer = 0;
	if ((infop->wx_dbuf.read_buffer < 0) ||
	   (infop->wx_dbuf.read_buffer > (infop->wx_dbuf.number_buffers - 1)))
		infop->wx_dbuf.read_buffer = 1;
	if ((infop->wx_dbuf.write_buffer < 0) ||
	   (infop->wx_dbuf.write_buffer > (infop->wx_dbuf.number_buffers - 1)))
		infop->wx_dbuf.write_buffer = 1;
	return(1);
    } else {

	/* Even though they have not supplied vrtp we allow the db_grab
	 * to succeed but we null out vrtfunc and set vrtcntrp to point
	 * to itself!
	*/
	clientp->db_enabled = 1;
	clientp->vrt_func = NULL;
	clientp->db_vrtcntrp = NULL;
	return(1);
    }
    
}

/* Returns 0 on failure and non-zero on success */
int
dga_db_ungrab(clientpi)
Dga_window clientpi;
{
    _Dga_window clientp = (struct dga_window *)clientpi;    
    Display    *dpy;
    Window	win;
    int		status = -1;

    /*
     *  Check for an invalid Dga_window
     */
    if ((clientp == (Dga_window) NULL)) {
#ifdef DEBUG
        (void) fprintf(stderr, "dga_db_ungrab: passed null pointer\n");
#endif
        return (0);
    }
    
    /* If it wasm't grabbed in the first place. don't do anything */
    if (!clientp->db_enabled)
	return 1;


    /*
     *  Find out if this is an X window.  If so get the Display and window
     *  id.  
     */
     if (!_dga_is_X_window(clientp->w_token, &dpy, &win)) {
#ifdef DEBUG
        (void) fprintf(stderr, "dga_db_ungrab: Unsupported window type\n");
#endif
        return (0);
    }
    /* Mark the window as single buffered */
    clientp->db_enabled = 0;

    /* Tell server t ungrab */
    return (XDgaUnGrabBuffers(dpy, win));

    
}
/* This is just for internal use */
static int
_dga_db_vrtfunc_internal(wg_clientp)
Dga_window wg_clientp;
{
   ( ioctl(((_Dga_window)wg_clientp)->w_devfd,FBIOVERTICAL,0));
   return(1);
}