/* config.c: PnP config tool device configuration view handler module */
/*
 * $Header: /root/pnp/tools/RCS/config.c,v 1.2 1996/07/04 21:26:14 root Exp $
 *
 * $Log: config.c,v $
 * Revision 1.2  1996/07/04  21:26:14  root
 * ioctl used to reread config after successful set-config
 *
 * Revision 1.1  1996/06/24  23:31:22  root
 * Initial revision
 *
 *
 */

/*
 * (c) Copyright 1996  D.W.Howells <dwh@nexor.co.uk>,
 */

#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <errno.h>
#include "tools.h"

static const char *irqch = "x123456789ABCDEF", *typch = "EeLl";
static int typtr[] = { 1, 3, 0, 2 };
static u_char revtyptr[] = { 2, 0, 3, 1 };

struct possibility {
    struct possibility *next;
    struct possibility *prev;
    pnp_possibility p;
};

static struct possibility *poss_list = NULL;
static struct possibility *current = NULL;
static int poss_num, poss_count;

static void read_poss(struct pnp_index *);
static void destroy_poss();
static void show_irq(struct pnp_index *, pnp_possibility *, pnp_config *, int);
static void show_dma(struct pnp_index *, pnp_possibility *, pnp_config *, int);
static void show_io(struct pnp_index *, pnp_possibility *, pnp_config *, int);
static void show_mem(struct pnp_index *, pnp_possibility *, pnp_config *, int);

/*****************************************************************************/
struct editable {
    int endstop;
    int line;
    int col;
    int num;
    int (*edtest)(pnp_possibility*,struct editable*);
    void (*edconf)(pnp_possibility*,pnp_config*,struct editable*,int);
};

#define QTEST(NAME,MB) \
 static int NAME(pnp_possibility *poss, struct editable *ed) { \
	return (poss->pp_s.MB>>ed->num) & 0x01; \
 }
QTEST(edtest_irq,irq)
QTEST(edtest_dma,dma)
QTEST(edtest_io,io)
QTEST(edtest_mem,mem)
static void edconf_irq(pnp_possibility *, pnp_config *, struct editable *,int);
static void edconf_itype(pnp_possibility *,pnp_config *,struct editable *,int);
static void edconf_dma(pnp_possibility *, pnp_config *, struct editable *,int);
static void edconf_io(pnp_possibility *, pnp_config *, struct editable *,int);
static void edconf_mem(pnp_possibility *, pnp_config *, struct editable *,int);

static struct editable fields[] = {
{ 1 },
{ 0, 0,  0,  0, edtest_irq, edconf_irq },
{ 0, 0,  24, 0, edtest_irq, edconf_itype },
{ 0, 1,  0,  1, edtest_irq, edconf_irq },
{ 0, 1,  24, 1, edtest_irq, edconf_itype },

{ 0, 3,  0,  0, edtest_dma, edconf_dma },
{ 0, 4,  0,  1, edtest_dma, edconf_dma },

{ 0, 6,  0,  0, edtest_io, edconf_io },
{ 0, 7,  0,  1, edtest_io, edconf_io },
{ 0, 8,  0,  2, edtest_io, edconf_io },
{ 0, 9,  0,  3, edtest_io, edconf_io },
{ 0, 10, 0,  4, edtest_io, edconf_io },
{ 0, 11, 0,  5, edtest_io, edconf_io },
{ 0, 12, 0,  6, edtest_io, edconf_io },
{ 0, 13, 0,  7, edtest_io, edconf_io },

{ 0, 15, 0,  0, edtest_mem, edconf_mem },
{ 0, 16, 0,  1, edtest_mem, edconf_mem },
{ 0, 17, 0,  2, edtest_mem, edconf_mem },
{ 0, 18, 0,  3, edtest_mem, edconf_mem },
{ 1 }
};

static struct editable *currfield;

/*****************************************************************************/
/* configure a device */
void configure_device()
{
    pnpioctl pioc;
    struct editable *te;
    struct pnp_index *curr = index_list[index_current];
    int loop;

    read_poss(curr);
    if (!poss_list)
	return;
    current = poss_list;
    poss_num = 0;

    pioc = curr->pi_ioc;

    mvwprintw(topwin,0,0," Configure %s: ",curr->pi_ident);
    if (curr->pi_device && curr->pi_device->pd_desc)
	waddstr(topwin,curr->pi_device->pd_desc);
    wclrtoeol(topwin);
    wrefresh(topwin);
    touchwin(extrawin);
    wrefresh(extrawin);
    mvwprintw(barwin,getmaxy(extrawin)-1,0,
	      " PgUp/Dn - Alternatives  Up/Dn - Change fields"
	      "  Left/Right - Configure");
    wclrtoeol(barwin);
    wrefresh(barwin);

    /*=======================================================================*/
 redisplay:
    wattron(topwin,A_REVERSE);
    mvwprintw(topwin,0,73,"%03d/%03d",poss_num+1,poss_count);
    wattroff(topwin,A_REVERSE);
    wrefresh(topwin);

    wmove(confwin,0,0);
    wclrtobot(confwin);
    touchwin(vconfwin);
    wrefresh(vconfwin);

    show_irq(curr,&current->p,&pioc.pioc.config,0);
    show_irq(curr,&current->p,&pioc.pioc.config,1);
    show_dma(curr,&current->p,&pioc.pioc.config,0);
    show_dma(curr,&current->p,&pioc.pioc.config,1);
    for (loop=0; loop<8; loop++)
	show_io(curr,&current->p,&pioc.pioc.config,loop);
    for (loop=0; loop<4; loop++)
	show_mem(curr,&current->p,&pioc.pioc.config,loop);

    currfield = &fields[1];
    wattrset(confwin,COLOR_PAIR(3)|A_BOLD);
    mvwaddch(confwin,currfield->line,currfield->col,ACS_DIAMOND);
    touchwin(vconfwin);
    wrefresh(vconfwin);

    /*=======================================================================*/
    while (1) {
	wmove(barwin,0,COLS-1);
	wrefresh(barwin);
	pioc.pioc.config.pc_force = 0;
	switch (getch()) {
	 case 'Q':
	 case 'q':
	    destroy_poss();
	    return;

	 case 6:	/* ^F */
	    pioc.pioc.config.pc_force = 1;

	 case '\n':
	 case '\r':
	 case 20:	/* ^T */
	    pioc.pioc_req = PNPREQ_SET;
	    if (ioctl(pnpconf,PNPIOC_REQUEST,&pioc)<0) {
		switch (errno) {
		 case ENOMEM:
		    popup("Error","Out of Kernel Memory","");
		    break;
		 case ENXIO:
		    popup("Error","Configuration Unavailable","");
		    break;
		 default:
		    popup("Error","Internal Driver Error",sys_errlist[errno]);
		    break;
		}
		wrefresh(vconfwin);
		break;
	    }
	    curr->pi_ioc.pioc_req = PNPREQ_READ;
	    ioctl(pnpconf,PNPIOC_REQUEST,&curr->pi_ioc);
	    destroy_poss();
	    return;

	 case KEY_PPAGE:
	    if (current->prev) {
		current = current->prev;
		poss_num--;
		goto redisplay;
	    }
	    break;

	 case KEY_NPAGE:
	    if (current->next) {
		current = current->next;
		poss_num++;
		goto redisplay;
	    }
	    break;

	 case KEY_UP:
	    te = currfield - 1;
	    while (!te->endstop &&
		   !te->edtest(&current->p,te)
		   )
		te--;
	    if (te->endstop)
		break;
	    wattrset(confwin,COLOR_PAIR(2));
	    mvwaddch(confwin,currfield->line,currfield->col,' ');
	    currfield = te;
	    wattrset(confwin,COLOR_PAIR(3)|A_BOLD);
	    mvwaddch(confwin,currfield->line,currfield->col,ACS_DIAMOND);
	    touchwin(vconfwin);
	    wrefresh(vconfwin);
	    break;

	 case KEY_DOWN:
	    te = currfield + 1;
	    while (!te->endstop &&
		   !te->edtest(&current->p,te)
		   )
		te++;
	    if (te->endstop)
		break;
	    wattrset(confwin,COLOR_PAIR(2));
	    mvwaddch(confwin,currfield->line,currfield->col,' ');
	    currfield = te;
	    wattrset(confwin,COLOR_PAIR(3)|A_BOLD);
	    mvwaddch(confwin,currfield->line,currfield->col,ACS_DIAMOND);
	    touchwin(vconfwin);
	    wrefresh(vconfwin);
	    break;

	 case KEY_LEFT:
	    currfield->edconf(&current->p,&pioc.pioc.config,currfield,-1);
	    break;

	 case KEY_RIGHT:
	    currfield->edconf(&current->p,&pioc.pioc.config,currfield,1);
	    break;

	 case 12:	/* ^L */
	 case 'L':
	 case 'l':
	    touchwin(stdscr); wrefresh(stdscr);
	    touchwin(topwin); wrefresh(topwin);
	    touchwin(vconfwin); wrefresh(vconfwin); wrefresh(confwin);
	    touchwin(extrawin); wrefresh(extrawin);
	    touchwin(barwin); wrefresh(barwin);
	    break;
	}
    }

} /* end configure_device() */

/*****************************************************************************/
/* display an IRQ config */
static void show_irq(struct pnp_index *ix, pnp_possibility *poss,
		     pnp_config *conf, int irq)
{
    int loop;
    u_short list;

    wmove(confwin,irq,1);

    wattrset(confwin,COLOR_PAIR(2));
    wprintw(confwin,"irq%d: ",irq);

    if (!((poss->pp_s.irq>>irq)&0x01)) {
	wprintw(confwin,"n/a");
	touchwin(vconfwin);
	wrefresh(confwin);
	return;
    }

    list = *(u_short*)poss->pp_irq[irq].nums;
    for (loop=0; loop<16; loop++) {
	wattrset(confwin,
		 loop!=conf->pc_irq[irq].num ?
		 COLOR_PAIR(6)|A_BOLD : COLOR_PAIR(3)
		 );
	waddch(confwin,
	       list&0x0001 ? irqch[loop] : '-'
	       );
	list >>= 1;
    }

    wattrset(confwin,COLOR_PAIR(2));
    wprintw(confwin,"  type: ");

    list = poss->pp_irq[irq].type;
    for (loop=3; loop>=0; loop--) {
	wattrset(confwin,
		 loop!=typtr[conf->pc_irq[irq].type] ?
		 COLOR_PAIR(6)|A_BOLD : COLOR_PAIR(3)
		 );
	waddch(confwin,
	       list&0x08 ? typch[loop] : '-'
	       );
	list <<= 1;
    }

    if (ix->pi_device && ix->pi_device->pd_irq[irq]) {
	wattrset(confwin,COLOR_PAIR(4));
	wprintw(confwin,"  %s",ix->pi_device->pd_irq[irq]);
    }

    touchwin(vconfwin);
    wrefresh(confwin);

} /* end show_irq() */

/*****************************************************************************/
/* display an DMA config */
static void show_dma(struct pnp_index *ix, pnp_possibility *poss,
		     pnp_config *conf, int dma)
{
    const char *dmach = "0123x567";
    int loop;
    u_short list;

    wmove(confwin,dma+3,1);

    wattrset(confwin,COLOR_PAIR(2));
    wprintw(confwin,"dma%d: ",dma);

    if (!((poss->pp_s.dma>>dma)&0x01)) {
	wprintw(confwin,"n/a");
	touchwin(vconfwin);
	wrefresh(confwin);
	return;
    }

    list = poss->pp_dma[dma].chans[0];
    for (loop=0; loop<8; loop++) {
	wattrset(confwin,
		 loop!=conf->pc_dma[dma].chan ?
		 COLOR_PAIR(6)|A_BOLD : COLOR_PAIR(3)
		 );
	waddch(confwin,
	       list&0x0001 ? dmach[loop] : '-'
	       );
	list >>= 1;
    }

    if (ix->pi_device && ix->pi_device->pd_dma[dma]) {
	wattrset(confwin,COLOR_PAIR(4));
	wprintw(confwin,"  %-8s",ix->pi_device->pd_dma[dma]);
    }

    touchwin(vconfwin);
    wrefresh(confwin);

} /* end show_dma() */

/*****************************************************************************/
/* display an IO config */
static void show_io(struct pnp_index *ix, pnp_possibility *poss,
		     pnp_config *conf, int io)
{
    wmove(confwin,io+6,1);

    wattrset(confwin,COLOR_PAIR(2));
    wprintw(confwin,"io%d: ",io);

    if (!((poss->pp_s.io>>io)&0x01)) {
	wprintw(confwin,"n/a");
	touchwin(vconfwin);
	wrefresh(confwin);
	return;
    }

    wattrset(confwin,COLOR_PAIR(6)|A_BOLD);
    wprintw(confwin,"%04x-%04x",
	    conf->pc_io[io].base,
	    conf->pc_io[io].base + poss->pp_io[io].range - 1
	    );

    wattrset(confwin,COLOR_PAIR(2));
    wprintw(confwin," (base: %04x->%04x ++",
	    poss->pp_io[io].low,
	    poss->pp_io[io].high);
    /*waddch(confwin,ACS_PLMINUS);*/
    wprintw(confwin,"%04x)",poss->pp_io[io].align);

    if (ix->pi_device && ix->pi_device->pd_io[io]) {
	wattrset(confwin,COLOR_PAIR(4));
	wprintw(confwin,"  %-8s",ix->pi_device->pd_io[io]);
    }

    touchwin(vconfwin);
    wrefresh(confwin);

} /* end show_io() */

/*****************************************************************************/
/* display a MEM config */
static void show_mem(struct pnp_index *ix, pnp_possibility *poss,
		     pnp_config *conf, int mem)
{
    wmove(confwin,mem+15,1);

    wattrset(confwin,COLOR_PAIR(2));
    wprintw(confwin,"mem%d: ",mem);

    if (!((poss->pp_s.mem>>mem)&0x01)) {
	wprintw(confwin,"n/a");
	touchwin(vconfwin);
	wrefresh(confwin);
	return;
    }

    wattrset(confwin,COLOR_PAIR(6)|A_BOLD);
    wprintw(confwin,"%08x-%08x",
	    conf->pc_mem[mem].base,
	    conf->pc_mem[mem].base + poss->pp_mem[mem].range - 1
	    );

    wattrset(confwin,COLOR_PAIR(2));
    wprintw(confwin," (base: %08x->%08x ++",
	    poss->pp_mem[mem].low,
	    poss->pp_mem[mem].high);
    /*waddch(confwin,ACS_PLMINUS);*/
    wprintw(confwin,"%08x)",poss->pp_mem[mem].align);

    if (ix->pi_device && ix->pi_device->pd_mem[mem]) {
	wattrset(confwin,COLOR_PAIR(4));
	wprintw(confwin,"  %-8s",ix->pi_device->pd_mem[mem]);
    }

    touchwin(vconfwin);
    wrefresh(confwin);

} /* end show_mem() */

/*****************************************************************************/
/* read all possibilities */
static void read_poss(struct pnp_index *curr)
{
    pnpioctl pioc;
    struct possibility **poss = &poss_list, *prev = NULL;

    poss_count = 0;

    pioc.pioc_req = PNPREQ_QUERY;
    pioc.pioc_csn = curr->pi_csn;
    pioc.pioc_dev = curr->pi_dev;

    while (1) {
	/* read the next dependency block */
	pioc.pioc_dep = poss_count;
	if (ioctl(pnpconf,PNPIOC_REQUEST,&pioc)<0)
	    return;

	/* store it here */
	*poss = (struct possibility *) malloc(sizeof(struct possibility));
	if (!*poss)
	    return;

	(*poss)->next = NULL;
	(*poss)->prev = prev;
	memcpy(&(*poss)->p,&pioc.pioc.poss,sizeof(pnp_possibility));

	prev = *poss;
	poss = &(*poss)->next;
	poss_count++;
    }

} /* end read_poss() */

/*****************************************************************************/
static void destroy_poss()
{
    struct possibility *tp;

    while (poss_list) {
	tp = poss_list;
	poss_list = tp->next;
	free(tp);
    }
} /* end destroy_poss() */

/*****************************************************************************/
static void edconf_irq(pnp_possibility *poss, pnp_config *conf,
		      struct editable *field, int adj)
{
    int loop, irq, tmp;
    u_short list;

    irq = field->num;

    /* select a new choice */
    list = *(u_short*)poss->pp_irq[irq].nums;
    if (adj<0) {
	/* look for new choice before current */
	tmp = -1;
	for (loop=0; loop<conf->pc_irq[irq].num; loop++) {
	    if (list&0x01)
		tmp = loop;
	    list >>= 1;
	}
	/* nothing on the left */
	if (tmp<0)
	    return;
	loop = tmp;
    } else {
	/* look for new choice after current */
	loop = conf->pc_irq[irq].num + 1;
	list >>= loop;
	for (; loop<16; loop++) {
	    if (list&0x01)
		break;
	    list >>= 1;
	}
	/* nothing further right */
	if (loop>=16)
	    return;
    }

    /* unhighlight old choice */
    list = *(u_short*)poss->pp_irq[irq].nums >> conf->pc_irq[irq].num;
    wattrset(confwin,COLOR_PAIR(6)|A_BOLD);
    mvwaddch(confwin,
	     field->line,7+conf->pc_irq[irq].num,
	     list&0x01 ? irqch[conf->pc_irq[irq].num] : '-'
	     );

    /* set new choice */
    conf->pc_irq[irq].num = loop;

    /* highlight new choice */
    wattrset(confwin,COLOR_PAIR(3));
    mvwaddch(confwin,
	     field->line,7+conf->pc_irq[irq].num,
	     irqch[conf->pc_irq[irq].num]);

    touchwin(vconfwin);
    wrefresh(confwin);

} /* end edconf_irq() */

/*****************************************************************************/
static void edconf_itype(pnp_possibility *poss, pnp_config *conf,
			 struct editable *field, int adj)
{
    int loop, irq, tmp, type;
    u_short list;

    irq = field->num;
    type = typtr[conf->pc_irq[irq].type&0x03];
    adj = -adj;

    /* select a new choice */
    list = poss->pp_irq[irq].type;
    if (adj<0) {
	/* look for new choice before current */
	tmp = -1;
	for (loop=0; loop<type; loop++) {
	    if (list&0x01)
		tmp = loop;
	    list >>= 1;
	}
	/* nothing on the left */
	if (tmp<0)
	    return;
	loop = tmp;
    } else {
	/* look for new choice after current */
	loop = type + 1;
	list >>= loop;
	for (; loop<4; loop++) {
	    if (list&0x01)
		break;
	    list >>= 1;
	}
	/* nothing further right */
	if (loop>=4)
	    return;
    }

    /* unhighlight old choice */
    list = poss->pp_irq[irq].type >> type;
    wattrset(confwin,COLOR_PAIR(6)|A_BOLD);
    mvwaddch(confwin,
	     field->line,34-type,
	     list&0x01 ? typch[type] : '-'
	     );

    /* set new choice */
    conf->pc_irq[irq].type = revtyptr[loop];

    /* highlight new choice */
    wattrset(confwin,COLOR_PAIR(3));
    mvwaddch(confwin,
	     field->line,34-loop,
	     typch[loop]);

    touchwin(vconfwin);
    wrefresh(confwin);

} /* end edconf_itype() */

/*****************************************************************************/
static void edconf_dma(pnp_possibility *poss, pnp_config *conf,
		      struct editable *field, int adj)
{
    int loop, dma, tmp;
    u_short list;

    dma = field->num;

    /* select a new choice */
    list = poss->pp_dma[dma].chans[0];
    if (adj<0) {
	/* look for new choice before current */
	tmp = -1;
	for (loop=0; loop<conf->pc_dma[dma].chan; loop++) {
	    if (list&0x01)
		tmp = loop;
	    list >>= 1;
	}
	/* nothing on the left */
	if (tmp<0)
	    return;
	loop = tmp;
    } else {
	/* look for new choice after current */
	loop = conf->pc_dma[dma].chan + 1;
	list >>= loop;
	for (; loop<8; loop++) {
	    if (list&0x01)
		break;
	    list >>= 1;
	}
	/* nothing further right */
	if (loop>=8)
	    return;
    }

    /* unhighlight old choice */
    list = poss->pp_dma[dma].chans[0] >> conf->pc_dma[dma].chan;
    wattrset(confwin,COLOR_PAIR(6)|A_BOLD);
    mvwaddch(confwin,
	     field->line,7+conf->pc_dma[dma].chan,
	     list&0x01 ? '0'+conf->pc_dma[dma].chan : '-'
	     );

    /* set new choice */
    conf->pc_dma[dma].chan = loop;

    /* highlight new choice */
    wattrset(confwin,COLOR_PAIR(3));
    mvwaddch(confwin,
	     field->line,7+conf->pc_dma[dma].chan,
	     '0'+conf->pc_dma[dma].chan);

    touchwin(vconfwin);
    wrefresh(confwin);

} /* end edconf_dma() */

/*****************************************************************************/
static void edconf_io(pnp_possibility *poss, pnp_config *conf,
		      struct editable *field, int adj)
{
    int io, tmp;

    io = field->num;

    /* select a new choice */
    if (adj<0) {
	/* look for new choice before current */
	tmp = conf->pc_io[io].base - poss->pp_io[io].align;
    } else {
	/* look for new choice after current */
	tmp = conf->pc_io[io].base + poss->pp_io[io].align;
    }

    /* make sure it's in the range */
    if (tmp <= poss->pp_io[io].low)
	tmp = poss->pp_io[io].low;
    if (tmp >= poss->pp_io[io].high)
	tmp = poss->pp_io[io].high;

    /* make sure of alignment */
    tmp -= poss->pp_io[io].low;
    tmp /= poss->pp_io[io].align;
    tmp *= poss->pp_io[io].align;
    tmp += poss->pp_io[io].low;

    /* set new choice */
    conf->pc_io[io].base = tmp;

    /* highlight new choice */
    wattrset(confwin,COLOR_PAIR(6)|A_BOLD);
    mvwprintw(confwin,
	      field->line,6,
	      "%04x-%04x",tmp,tmp+poss->pp_io[io].range-1);

    touchwin(vconfwin);
    wrefresh(confwin);

} /* end edconf_io() */

/*****************************************************************************/
static void edconf_mem(pnp_possibility *poss, pnp_config *conf,
		       struct editable *field, int adj)
{
    int col, mem, ch;
    char buffer[9];

    mem = field->num;
    sprintf(buffer,"%8.8x",(u_int)conf->pc_mem[mem].base);

    col = 8;

    wattrset(confwin,COLOR_PAIR(3));
    mvwaddstr(confwin,field->line,field->col+7,buffer);
    touchwin(vconfwin);
    wrefresh(confwin);

    while (1) {
	ch = getch();
	switch (ch) {
	 case KEY_DC:
	 case KEY_BACKSPACE:
	 case 0x7F:
	 case '\b':
	    if (col>0) {
		col--;
		mvwaddch(confwin,field->line,field->col+7+col,' ');
		wmove(confwin,field->line,field->col+7+col);
		touchwin(vconfwin);
		wrefresh(confwin);
		buffer[col] = 0;
	    }
	    break;

	 case '\n':
	 case '\r':
	    (u_int)conf->pc_mem[mem].base = strtoul(buffer,NULL,16);
	    show_mem(index_list[index_current],poss,conf,mem);
	    return;

	 default:
	    if ((ch>='0' && ch<='9') ||
		((ch|0x20)>='a' && (ch|0x20)<='f')
		) {
		if (ch>='A')
		    ch |= 0x20;
		if (col<8) {
		    mvwaddch(confwin,field->line,field->col+7+col,ch);
		    touchwin(vconfwin);
		    wrefresh(confwin);
		    buffer[col] = ch;
		    col++;
		}
	    }
	    break;
	}
    }
} /* end edconf_mem() */
