[comp.unix.xenix] Microport UNIX EGA graphics routines

walters@osubem.UUCP (walters) (08/27/87)

--------

Following are routines for reading and writing to EGA graphics memory
under Microport UNIX.  

First off, let me thank all of you who responded to my plea for help
about EGA graphics and Microport UNIX.  Almost all replys were helpfull. 
Following are two sets of routines, one for doing graphics on the EGA
under Microport System V/AT and the other to save the text screen. 
There are several prerequisites to using these routines.  Some obvious
and some not so obvious. 

1) An EGA card and a version of Microport that supports shared memory
(for most people thats >= 2.2.0). 

2) Shared memory segments are created are bootup with a file in
/etc/rc.d like /etc/rc.d/shm.rc with the following lines. 

	/etc/shmcreate 0xa0000 a0000 65535		# ega high res
	/etc/shmcreate 0xb8000 b8000 32768		# cga

3) A working version of /etc/shmcreate.  The version that came with
2.2.0L didn't work but there was a patch posted in the microp
newsletter.  (I patched mine then lost the patch)

4) Read/write access to /dev/mem.  A potential security hole.

The routines are pretty obvious and are commented.  They must be
compiled with the large memory model.  A small demo is given below. 
Compile with

	cc -Ml mtest.c mega.c mcgasr.c -o mtest

For a more complex application it would be a good idea to catch signals
and call ega_close() in your cleanup routine.  Otherwise, if something
happens you won't get your text screen back.  If this happens, you still
can type in commands you just can't SEE them. 

Now I understand why most people don't submit code they have written, it
takes forever to make it presentable.  The *best* way to do all of this
non-sense would be to put it in a device driver to make it fast.  Oh
well, a project for the future.  Please send bug reports to me and
flames to /dev/null.  Enjoy. 

--
Harold G. Walters                 Internet: walters@ce.okstate.edu
School of Civil Engineering       Uucp: {cbosgd, ihnp4, rutgers, seismo,
Oklahoma State University               uiucdcs}!okstate!osubem!walters
Stillwater, OK  74078	   "Ignorance is temporary, stupidity is forever."

/* mtest.c */

main()
{
	int x, y, i;

	if (ega_open() < 0)
		exit(1);
	ega_clear(4);
	ega_wdot_start();
	for (y = 100; y <= 300; y++)
		for (x = 100; x <= 300; x++)
			ega_wdot_multi(x, y, 5);
	ega_wdot_stop();

	i = ega_rdot(100, 100);
	sleep(5);
	ega_close();
	printf(">>> %d <<<\n", i);
	exit(0);
}
/*eof*mtest.c*/
	
/*mcgasr.c***************************************************************/     
/*									*/
/*				mega					*/
/*									*/
/*		  Copyright (c) 1987 Harold G. Walters			*/
/*									*/
/*	This software may be copied and/or redistributed without	*/
/*	permission as long as this copyright notice is included		*/
/*	prominently in any copy and/or redistribution. 			*/
/*									*/
/*	This software is provided as is.  No claims are made for	*/
/*	this software regarding its fitness and/or correctness for	*/
/* 	any purpose.  Use of this software is at the user's own risk.	*/
/*									*/
/************************************************************************/

/*
*	This software assumes that shared memory has been
*	set up to allow access to the EGA card.  Normally
*	done by /etc/shmcreate during bootup.
*
*	/etc/shmcreate 0xa0000 a0000 65535		# ega high res
*
*	Write access to /dev/mem is also required
*/

#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/io_op.h>

/*	Port Addresses */
  
#define seq_addr	0x3c4
#define seq_data	0x3c5
#define crtc_addr	0x3d4
#define crtc_addr_b	0x3b4
#define crtc_data	0x3d5
#define graph_1_pos	0x3cc
#define graph_2_pos	0x3ca
#define graph_addr	0x3ce
#define graph_data	0x3cf
#define misc_output	0x3c2
#define in_stat_o	0x3c2
#define input_status_b	0x3ba
#define input_status	0x3da
#define attr_read	0x3da	
#define attr_write	0x3c0

/*	Address Register Values */

#define g_set_reset	0x0
#define g_enbl_set	0x1
#define g_clr_comp	0x2
#define g_data_rot	0x3
#define g_read_map	0x4
#define g_mode		0x5
#define g_misc		0x6
#define g_color		0x7
#define g_bit_mask	0x8

#define s_reset		0x0
#define s_clock		0x1
#define s_map		0x2
#define s_cgen		0x3
#define s_nem		0x4
	
#define write_mode_0	0x0
#define write_mode_1	0x1
#define write_mode_2	0x2

#define g_default	0xff
#define s_default	0xf

/* shm stuff */
#define EGAKEY		0xa0000
#define EGALEN		65535
#define EGAFLAGS	0

/* EGA extrema */
#define X_MIN	0
#define X_MAX	639
#define Y_MIN	0
#define Y_MAX	349

/* default colors */
#define BLACK   0x00
#define BLUE    0x01
#define GREEN   0x02
#define CYAN    0x03
#define RED     0x04
#define MAGENTA 0x05
#define YELLOW  0x06 
#define WHITE   0x07
#define BRIGHT	0x08

static int dev_mem_fd;	/* must have read/write access to /dev/mem */
static char *ega_ram;

/* output byte data to addr */
#define outb(addr, data) \
{ \
	io_op_t poke; \
	poke.io_port = addr; \
	poke.io_byte = data; \
	ioctl(dev_mem_fd, IOCIOP_WB, &poke); \
}

/* set modes as defined in console.7 NOT as in IBM EGA manual */
void ega_set_mode(m)
int m;
{
	static char buf[10];
	
	if (m < 0)	/* text mode */
		sprintf(buf, "%1c[=h", 0x1b);
	else
		sprintf(buf, "%1c[=%dh", 0x1b, m);
	write(1, buf, strlen(buf));
	return;
}

/* does all of the initialization for EGA graphics */
int ega_open()
{
	void ega_save_bp();
	extern char *shmat();
	int shmid;
	
	if (cga_save() < 0)	/* save text screen (optional) */
	{
		return(-1);
	}
	if ((dev_mem_fd = open("/dev/mem", 2)) == -1) 
	{
		perror("ega_open: /dev/mem");
		return(-1);
	}
	if ((shmid = shmget((key_t) EGAKEY, EGALEN, EGAFLAGS)) == -1)
	{
		perror("ega_open: shmget");
		return(-1);
	}
	if ((ega_ram = shmat(shmid, (char *) 0, 0)) == (char *) -1)
	{
		perror("ega_open: shmat");
		return(-1);
	}
	ega_set_mode(10);
	ega_save_bp();	/* see below */
	return(0);
}

/*
* its probably a good idea to call ega_close in your cleanup routine
* if you catch signals (if you don't text mode won't get restored)
*/

/* cleans up and restores text screen */
int ega_close()
{
	void ega_rest_bp();
	int stat = 0;
	
	ega_rest_bp();		/* see below */
	if (shmdt(ega_ram) == -1)
	{
		perror("ega_close: shmdt");
		stat = -1;
	}
	if (close(dev_mem_fd) == -1)
	{
		perror("ega_close: /dev/mem");
		stat = -1;
	}
	ega_set_mode(-1);
	if (cga_rest() < 0)	/* restore cga text (optional) */
		stat = -1;
	return(stat);
}

/*
* the strategy behind the ega_wdot_{start, multi, stop} triplet is
* to minimize writes to /dev/mem to speed things up by
* 1) buffering writes inside a byte (good for rows)
* 2) write out new bit mask only when it changes (for columns)
*/

static int last_mask, cur_mask, last_col, first;
static unsigned int last_byte;

/*
void dodot(byte, mask, col)
int byte, mask, col;
{
	if (last_mask != mask)
	{
		outb(graph_addr, g_bit_mask);
		outb(graph_data, mask);
		last_mask = mask;
	}
	mask = ega_ram[byte];
	ega_ram[byte] = col;
	return;
}
*/

/* macro is barely faster than the subroutine */

#define dodot(byte, mask, col) \
{ \
	register unsigned int lb; \
	register int lc, junk; \
	if (last_mask != mask) \
	{ \
		outb(graph_addr, g_bit_mask); \
		outb(graph_data, mask); \
		last_mask = mask; \
	} \
	lb = byte; \
	lc = col; \
	junk = ega_ram[lb]; \
	ega_ram[lb] = lc; \
}

/* call before calling ega_wdot_multi */
void ega_wdot_start()
{
	outb(graph_addr, g_mode);
	outb(graph_data, write_mode_2);	/* let the EGA do the work */
	first = 1;			/* must be restored to mode 0 later */
	last_mask = -1;
	return;
}

/* write a pixel to the graphics screen at x, y in color col */
/* may be called only after calling ega_wdot_start */
void ega_wdot_multi(x, y, col)
int x, y, col;
{
	register unsigned int byte;
	register int mask;

	if (x < X_MIN || x > X_MAX || y < Y_MIN || y > Y_MAX)
		return;
	byte = (Y_MAX - y) * 80 + (x >> 3);
	mask = 1 << ((x & 7) ^ 7);
	if (first)
	{
		cur_mask = mask;
		last_col = col;
		last_byte = byte;
		first = 0;
	}
	if (last_col == col && last_byte == byte)
	{
		cur_mask |= mask;
	}
	else
	{
		dodot(last_byte, cur_mask, last_col);
		cur_mask = mask;
		last_byte = byte;
		last_col = col;
	}
	return;
}

/* call after ega_wdot_start and ega_wdot_multi to restore default modes */
void ega_wdot_stop()
{
	dodot(last_byte, cur_mask, last_col);
	outb(graph_addr, g_mode);
	outb(graph_data, write_mode_0);
	outb(graph_addr, g_bit_mask);
	outb(graph_data, g_default);
	return;
}

/* all in one function (slllooooooowwwwwww) */
void ega_wdot(x, y, col)
int x, y, col;
{
	register unsigned int byte;
	register int mask;
	
	if (x < X_MIN || x > X_MAX || y < Y_MIN || y > Y_MAX)
		return;
	outb(graph_addr, g_mode);
	outb(graph_data, write_mode_2);
	byte = (Y_MAX - y) * 80 + (x >> 3);
	mask = 1 << ((x & 7) ^ 7);
	outb(graph_addr, g_bit_mask);
	outb(graph_data, mask);
	mask = ega_ram[byte];
	ega_ram[byte] = col;
	outb(graph_addr, g_mode);
	outb(graph_data, write_mode_0);
	outb(graph_addr, g_bit_mask);
	outb(graph_data, g_default);
	return;
}

/* read a pixel at x, y and return color */
int ega_rdot(x, y)
int x, y;
{
	register unsigned int byte;
	register int mask, val, i;
	
	if (x < X_MIN || x > X_MAX || y < Y_MIN || y > Y_MAX)
		return(-1);
	val = 0;
	byte = (Y_MAX - y) * 80 + (x >> 3);
	mask = 1 << ((x & 7) ^ 7);
		outb(graph_addr, g_read_map);
	outb(graph_data, 0);
	if (((int) ega_ram[byte]) & mask)
		val |= (1 << 0);
	outb(graph_addr, g_read_map);
	outb(graph_data, 1);
	if (((int) ega_ram[byte]) & mask)
		val |= (1 << 1);
	outb(graph_addr, g_read_map);
	outb(graph_data, 2);
	if (((int) ega_ram[byte]) & mask)
		val |= (1 << 2);
	outb(graph_addr, g_read_map);
	outb(graph_data, 3);
	if (((int) ega_ram[byte]) & mask)
		val |= (1 << 3);
	return(val);
}

/* fast clear screen to color col */
void ega_clear(col)
int col;
{
	register unsigned int byte;
	register char on;
	register int i, tmp;
	
	outb(graph_addr, g_mode);
	outb(graph_data, write_mode_0);
	outb(graph_addr, g_bit_mask);
	outb(graph_data, g_default);
	for (i = 0; i < 4; i++)
	{
		outb(seq_addr, s_map);
		tmp = 1 << i;
		outb(seq_data, tmp);
		if (tmp & col)
			on = 0xff;
		else
			on = 0;		
		for (byte = 0; byte < EGALEN; byte++)
		{
			ega_ram[byte] = on;
		}
	}
	outb(seq_addr, s_map);
	outb(seq_data, s_default);
	return;
}

/* 
* the following routines save the first 8k of all four bit planes
* to enable a restore to text mode (chars, attributes, char generator map)
* I'm not sure if everyone needs these
*/
		
#define MAXBP 8192
static char bp[4][MAXBP];

/* save 'em */
void ega_save_bp()
{
	register int byte, i;
	
	for (i = 0; i < 4; i++)
	{
		outb(graph_addr, g_read_map);
		outb(graph_data, i);
		for (byte = 0; byte < MAXBP; byte++)
		{
			bp[i][byte] = ega_ram[byte];
		}
	}
	return;
}

/* restore 'em */	
void ega_rest_bp()
{
	register int byte, i;
	
	outb(graph_addr, g_mode);
	outb(graph_data, write_mode_0);
	outb(graph_addr, g_bit_mask);
	outb(graph_data, g_default);
	for (i = 0; i < 4; i++)
	{
		outb(seq_addr, s_map);
		outb(seq_data, (1 << i));
		for (byte = 0; byte < MAXBP; byte++)
		{
			ega_ram[byte] = bp[i][byte];
		}
	}
	outb(seq_addr, s_map);
	outb(seq_data, s_default);
	return;
}
/*eof*mega.c*/

/*mcgasr.c***************************************************************/     
/*									*/
/*				mega					*/
/*									*/
/*		  Copyright (c) 1987 Harold G. Walters			*/
/*									*/
/*	This software may be copied and/or redistributed without	*/
/*	permission as long as this copyright notice is included		*/
/*	prominently in any copy and/or redistribution. 			*/
/*									*/
/*	This software is provided as is.  No claims are made for	*/
/*	this software regarding its fitness and/or correctness for	*/
/* 	any purpose.  Use of this software is at the user's own risk.	*/
/*									*/
/************************************************************************/

/*
*	This software assumes that shared memory has been
*	set up to allow access to the EGA card (CGA mapped memory).  
*	Normally done by /etc/shmcreate during bootup.
*
*	/etc/shmcreate 0xb8000 b8000 32768		# cga
*/

#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/io_op.h>

/* shm stuff */
#define CGAKEY		0xb8000
#define CGALEN		4096
#define CGAFLAGS	0

/* default colors */
#define BLACK   0x00
#define BLUE    0x01
#define GREEN   0x02
#define CYAN    0x03
#define RED     0x04
#define MAGENTA 0x05
#define YELLOW  0x06 
#define WHITE   0x07
#define BRIGHT	0x08

#define cga_attrib(f, b) (((b & 15) << 4) | (f & 15))

static char *cga_ram;
static char cbuf[CGALEN];

/* save the console text screen */
int cga_save()
{
	extern char *shmat();
	int shmid;
	register unsigned int i;
	
	if ((shmid = shmget((key_t) CGAKEY, CGALEN, CGAFLAGS)) == -1)
	{
		perror("cga_open: shmget");
		return(-1);
	}
	if ((cga_ram = shmat(shmid, (char *) 0, 0)) == (char *) -1)
	{
		perror("cga_open: shmat");
		return(-1);
	}
	for (i = 0; i < CGALEN; i++)
		cbuf[i] = cga_ram[i];
	return(0);
}

/* restore the console text screen */
int cga_rest()
{
	register unsigned int i;

	for (i = 0; i < CGALEN; i++)
		cga_ram[i] = cbuf[i];
	if (shmdt(cga_ram) == -1)
	{
		perror("cga_close: shmdt");
		return(-1);
	}
	return(0);
}
/*eof*mcgasr.c*/

-------