text: /*
   * This file is part of the UCB release of Plan 9. It is subject to the license
   * terms in the LICENSE file found in the top-level directory of this
   * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
   * part of the UCB release of Plan 9, including this file, may be copied,
   * modified, propagated, or distributed except according to the terms contained
   * in the LICENSE file.
   */
  
  #include <u.h>
  #include <lib9.h>
  #include <envvars.h>
  #include <bio.h>
  
  #include "pci.h"
  #include "vga.h"
  
  Biobuf stdout;
  
  static int iflag, lflag, pflag, rflag;
  
  static char *dbname = "/lib/vgadb";
  static char monitordb[128];
  
  static void
  dump(Vga* vga)
  {
  	Ctlr *ctlr;
  	Attr *attr;
  	
  	if(vga->mode)
  		dbdumpmode(vga->mode);
  
  	for(attr = vga->attr; attr; attr = attr->next)
  		Bprint(&stdout, "vga->attr: %s=%s\n", attr->attr, attr->val);
  
  	for(ctlr = vga->link; ctlr; ctlr = ctlr->link){
  		if(ctlr->dump == 0)
  			continue;
  
  		trace("%s->dump\n", ctlr->name);
  		if(ctlr->flag && ctlr->flag != Fsnarf){
  			printitem(ctlr->name, "flag");
  			printflag(ctlr->flag);
  			Bprint(&stdout, "\n");
  		}
  		(*ctlr->dump)(vga, ctlr);
  		ctlr->flag |= Fdump;
  	}
  	Bprint(&stdout, "\n");
  }
  
  void
  resyncinit(Vga* vga, Ctlr* ctlr, uint32_t on, uint32_t off)
  {
  	Ctlr *link;
  
  	trace("%s->resyncinit on 0x%8.8luX off 0x%8.8luX\n",
  		ctlr->name, on, off);
  
  	for(link = vga->link; link; link = link->link){
  		link->flag |= on;
  		link->flag &= ~off;
  		if(link == ctlr)
  			continue;
  
  		if(link->init == 0 || (link->flag & Finit) == 0)
  			continue;
  		link->flag &= ~Finit;
  		trace("%s->init 0x%8.8luX\n", link->name, link->flag);
  		(*link->init)(vga, link);
  	}
  }
  
  void
  sequencer(Vga* vga, int on)
  {
  	static uint8_t seq01;
  	static int state = 1;
  	char *s;
  
  	if(on)
  		s = "on";
  	else
  		s = "off";
  	trace("sequencer->enter %s\n", s);
  	if(on){
  		if(vga)
  			seq01 = vga->sequencer[0x01];
  		if(state == 0){
  			seq01 |= 0x01;
  			vgaxo(Seqx, 0x01, seq01);
  			vgaxo(Seqx, 0x00, 0x03);
  		}
  	}
  	else{
  		vgaxo(Seqx, 0x00, 0x01);
  		seq01 = vgaxi(Seqx, 0x01);
  		vgaxo(Seqx, 0x01, seq01|0x20);
  	}
  	state = on;
  	trace("sequencer->leave %s\n", s);
  }
  
  static void
  linear(Vga* vga)
  {
  	char buf[256];
  	char *p;
  
  	/*
  	 * Set up for linear addressing: try to allocate the
  	 * kernel memory map then read the base-address back.
  	 * vga->linear is a compatibility hack.
  	 */
  	if(vga->linear == 0){
  		vga->ctlr->flag &= ~Ulinear;
  		return;
  	}
  	if(vga->ctlr->flag & Ulinear){
  		/*
  		 * If there's already an aperture don't bother trying
  		 * to set up a new one.
  		 */
  		vgactlr("addr", buf);
  		if(atoi(buf)==0 && (buf[0]!='p' || buf[1]!=' ' || atoi(buf+2)==0)){
  			sprint(buf, "0x%lux 0x%lux", vga->apz ? vga->apz : vga->vmz, vga->vma);
  			vgactlw("linear", buf);
  			vgactlr("addr", buf);
  		}
  		trace("linear->addr %s\n", buf);
  		/*
  		 * old: addr 0x12345678
  		 * new: addr p 0x12345678 v 0x82345678 size 0x123
  		 */
  		if(buf[0]=='p' && buf[1]==' '){
  			vga->vmb = strtoul(buf+2, 0, 0);
  			p = strstr(buf, "size");
  			if(p)
  				vga->apz = strtoul(p+4, 0, 0);
  		}else
  			vga->vmb = strtoul(buf, 0, 0);
  	}
  	else
  		vgactlw("linear", "0");
  }
  
  char*
  chanstr[32+1] = {
  [1]	"k1",
  [2]	"k2",
  [4]	"k4",
  [8]	"m8",
  [16]	"r5g6b5",
  [24]	"r8g8b8",
  [32]	"x8r8g8b8",
  };
  
  static void
  usage(void)
  {
  	fprint(2, "usage: aux/vga [ -BcdilpvV ] [ -b bios-id ] [ -m monitor ] [ -x db ] [ mode [ virtualsize ] ]\n");
  	exits("usage");
  }
  
  static void
  wiremach(void)
  {
  	char file[128];
  	int32_t f, pid;
  
  	pid = getpid();
  	sprint(file, "#p/%d/ctl", pid);
  	f = open(file, OWRITE);
  	if(f < 0){
  		sprint(file, "/proc/%d/ctl", pid);
  		f = open(file, OWRITE);
  	}
  	if(f < 0)
  		fprint(2, "wiremach: cannot open neither #p/%d/ctl nor /proc/%d/ctl", pid, pid);
  	if(write(f, "wired 0", 7) < 7){
  		fprint(2, "%s: cannot wire to mach 0: %r", argv0);
  		exits("wire");
  	}
  	close(f);
  }
  
  void
  main(int argc, char** argv)
  {
  	char *bios, buf[256], sizeb[256], *p, *vsize, *psize;
  	char *type, *vtype;
  	int fd, virtual, len;
  	Ctlr *ctlr;
  	Vga *vga;
  
  	fmtinstall('H', encodefmt);
  	Binit(&stdout, 1, OWRITE);
  
  	bios = getenv("vgactlr");
  	if((type = getenv("monitor")) == 0)
  		type = "vga";
  	psize = vsize = "640x480x8";
  
  	ARGBEGIN{
  	default:
  		usage();
  		break;
  	case 'b':
  		bios = EARGF(usage());
  		break;
  	case 'B':
  		dumpbios(0x10000);
  		exits(0);
  	case 'c':
  		cflag = 1;
  		break;
  	case 'd':
  		dflag = 1;
  		break;
  	case 'i':
  		iflag = 1;
  		break;
  	case 'l':
  		lflag = 1;
  		break;
  	case 'm':
  		type = EARGF(usage());
  		break;
  	case 'p':
  		pflag = 1;
  		break;
  	case 'r':
  		/*
  		 * rflag > 1 means "leave me alone, I know what I'm doing."
  		 */
  		rflag++;
  		break;
  	case 'v':
  		vflag = 1;
  		break;
  	case 'V':
  		vflag = 1;
  		Vflag = 1;
  		break;
  	case 'x':
  		dbname = EARGF(usage());
  		break;
  	}ARGEND
  
  	virtual = 0;
  	switch(argc){
  	default:
  		usage();
  		break;
  	case 1:
  		vsize = psize = argv[0];
  		break;
  	case 2:
  		psize = argv[0];
  		vsize = argv[1];
  		virtual = 1;
  		break;
  	case 0:
  		break;
  	}
  	
  	wiremach();
  
  	if(lflag && strcmp(vsize, "text") == 0){
  		vesatextmode();
  		vgactlw("textmode", "");
  		exits(0);
  	}
  
  	vga = alloc(sizeof(Vga));
  	if(bios){
  		if((vga->offset = strtol(bios, &p, 0)) == 0 || *p++ != '=')
  			error("main: bad BIOS string format - %s\n", bios);
  		len = strlen(p);
  		vga->bios = alloc(len+1);
  		strncpy(vga->bios, p, len);
  		trace("main->BIOS %s\n", bios);
  	}
  
  	/*
  	 * Try to identify the VGA card and grab
  	 * registers.  Print them out if requested.
  	 * If monitor=vesa or our vga controller can't be found
  	 * in vgadb, try vesa modes; failing that, try vga.
  	 */
  	if(strcmp(type, "vesa") == 0 || dbctlr(dbname, vga) == 0 ||
  	    vga->ctlr == 0)
  		if(dbvesa(vga) == 0 || vga->ctlr == 0){
  			Bprint(&stdout, "%s: controller not in %s, not vesa\n",
  				argv0, dbname);
  			dumpbios(256);
  			type = "vga";
  			vsize = psize = "640x480x1";
  			virtual = 0;
  			vga->ctlr = &generic;
  			vga->link = &generic;
  		}
  
  	trace("main->snarf\n");
  	for(ctlr = vga->link; ctlr; ctlr = ctlr->link){
  		if(ctlr->snarf == 0)
  			continue;
  		trace("%s->snarf\n", ctlr->name);
  		(*ctlr->snarf)(vga, ctlr);
  	}
  
  	if(pflag)
  		dump(vga);
  
  	for(ctlr = vga->link; ctlr; ctlr = ctlr->link)
  		if(ctlr->flag & Ferror)
  			error("%r\n");
  
  	if(iflag || lflag){
  		if(getenv(type))
  			snprint(monitordb, sizeof monitordb, "/env/%s", type);
  		else
  			strecpy(monitordb, monitordb+sizeof monitordb, dbname);
  
  		if(vga->vesa){
  			strcpy(monitordb, "vesa bios");
  			vga->mode = dbvesamode(psize);
  		}else
  			vga->mode = dbmode(monitordb, type, psize);
  		if(vga->mode == 0)
  			error("main: %s@%s not in %s\n", type, psize, monitordb);
  
  		if(virtual){
  			if((p = strchr(vsize, 'x')) == nil)
  				error("bad virtual size %s\n", vsize);
  			vga->virtx = atoi(vsize);
  			vga->virty = atoi(p+1);
  			if(vga->virtx < vga->mode->x || vga->virty < vga->mode->y)
  				error("virtual size smaller than physical size\n");
  			vga->panning = 1;
  		}
  		else{
  			vga->virtx = vga->mode->x;
  			vga->virty = vga->mode->y;
  			vga->panning = 0;
  		}
  
  		trace("vmf %d vmdf %d vf1 %lud vbw %lud\n",
  			vga->mode->frequency, vga->mode->deffrequency,
  			vga->f[1], vga->mode->videobw);
  		if(vga->mode->frequency == 0 && vga->mode->videobw != 0 && vga->f[1] != 0){
  			/*
  			 * boost clock as much as possible subject
  			 * to video and memory bandwidth constraints
  			 */
  			uint32_t bytes, freq, membw;
  			double rr;
  
  			freq = vga->mode->videobw;
  			if(freq > vga->f[1])
  				freq = vga->f[1];
  
  			rr = (double)freq/(vga->mode->ht*vga->mode->vt);
  			if(rr > 85.0)		/* >85Hz is ridiculous */
  				rr = 85.0;
  
  			bytes = (vga->mode->x*vga->mode->y*vga->mode->z)/8;
  			membw = rr*bytes;
  			if(vga->membw != 0 && membw > vga->membw){
  				membw = vga->membw;
  				rr = (double)membw/bytes;
  			}
  
  			freq = rr*(vga->mode->ht*vga->mode->vt);
  			vga->mode->frequency = freq;
  
  			trace("using frequency %lud rr %.2f membw %lud\n",
  				freq, rr, membw);
  		}
  		else if(vga->mode->frequency == 0)
  			vga->mode->frequency = vga->mode->deffrequency;
  
  		for(ctlr = vga->link; ctlr; ctlr = ctlr->link){
  			if(ctlr->options == 0)
  				continue;
  			trace("%s->options\n", ctlr->name);
  			(*ctlr->options)(vga, ctlr);
  		}
  
  		/*
  		 * skip init for vesa - vesa will do the registers for us
  		 */
  		if(!vga->vesa)
  		for(ctlr = vga->link; ctlr; ctlr = ctlr->link){
  			if(ctlr->init == 0)
  				continue;
  			trace("%s->init\n", ctlr->name);
  			(*ctlr->init)(vga, ctlr);
  		}
  
  		if(strcmp(vga->mode->chan, "") == 0){
  			if(vga->mode->z < nelem(chanstr) && chanstr[vga->mode->z])
  				strcpy(vga->mode->chan, chanstr[vga->mode->z]);
  			else
  				error("%s: unknown channel type to use for depth %d\n", vga->ctlr->name, vga->mode->z);
  		}
  
  		if(iflag || pflag)
  			dump(vga);
  
  		if(lflag){
  			trace("main->load\n");
  			if(vga->vmz && (vga->virtx*vga->virty*vga->mode->z)/8 > vga->vmz)
  				error("%s: not enough video memory - %lud\n",
  					vga->ctlr->name, vga->vmz);
  
  			if(vga->ctlr->type)
  				vtype = vga->ctlr->type;
  			else if(p = strchr(vga->ctlr->name, '-')){
  				strncpy(buf, vga->ctlr->name, p - vga->ctlr->name);
  				buf[p - vga->ctlr->name] = 0;
  				vtype = buf;
  			}
  			else
  				vtype = vga->ctlr->name;
  			vgactlw("type", vtype);
  
  			/*
  			 * VESA must be set up before linear.
  			 * Set type to vesa for linear.
  			 */
  			if(vga->vesa){
  				vesa.load(vga, vga->vesa);
  				if(vga->vesa->flag&Ferror)
  					error("vesa load error\n");
  				vgactlw("type", vesa.name);
  			}
  
  			/*
  			 * The new draw device needs linear mode set
  			 * before size.
  			 */
  			linear(vga);
  
  			/*
  			 * Linear is over so switch to other driver for
  			 * acceleration.
  			 */
  			if(vga->vesa)
  				vgactlw("type", vtype);
  
  			sprint(buf, "%ludx%ludx%d %s",
  				vga->virtx, vga->virty,
  				vga->mode->z, vga->mode->chan);
  			if(rflag){
  				vgactlr("size", sizeb);
  				if(rflag < 2 && strcmp(buf, sizeb) != 0)
  					error("bad refresh: %s != %s\n",
  						buf, sizeb);
  			}
  			else
  				vgactlw("size", buf);
  
  			/*
  			 * No fiddling with registers if VESA set 
  			 * things up already.  Sorry.
  			 */
  			if(!vga->vesa){
  				/*
  				 * Turn off the display during the load.
  				 */
  				sequencer(vga, 0);
  	
  				for(ctlr = vga->link; ctlr; ctlr = ctlr->link){
  					if(ctlr->load == 0 || ctlr == &vesa)
  						continue;
  					trace("%s->load\n", ctlr->name);
  					(*ctlr->load)(vga, ctlr);
  				}
  	
  				sequencer(vga, 1);
  			}
  
  			vgactlw("drawinit", "");
  
  			if(vga->hwgc == 0 || cflag)
  				vgactlw("hwgc", "soft");
  			else
  				vgactlw("hwgc", vga->hwgc->name);
  
  			/* might as well initialize the cursor */
  			if((fd = open("/dev/cursor", OWRITE)) >= 0){
  				write(fd, buf, 0);
  				close(fd);
  			}
  
  			if(vga->virtx != vga->mode->x || vga->virty != vga->mode->y){
  				sprint(buf, "%dx%d", vga->mode->x, vga->mode->y);
  				vgactlw("actualsize", buf);
  				if(vga->panning)
  					vgactlw("panning", "on");
  			}
  
  			if(pflag)
  				dump(vga);
  		}
  	}
  
  	trace("main->exits\n");
  	exits(0);
  }
  