%{
/*
 * yacc_config.y 1.30 1997/04/15 16:00:00 (David Hinds)\n"
 */
    
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <sys/types.h>

#include <pcmcia/cs_types.h>
#include <pcmcia/cs.h>
#include <pcmcia/cistpl.h>
#include <pcmcia/ds.h>
    
#include "cardmgr.h"

/* from lex_config, for nice error messages */
extern char *current_file;
extern int current_lineno;

static int add_binding(card_info_t *card, char *name, int fn);
static int add_module(device_info_t *card, char *name);

%}

%token DEVICE CARD ANONYMOUS TUPLE MANFID VERSION FUNCTION
%token BIND TO NEEDS_MTD MODULE OPTS CLASS
%token REGION JEDEC DTYPE DEFAULT MTD
%token INCLUDE EXCLUDE RESERVE IRQ_NO PORT MEMORY
%token STRING NUMBER

%union {
    char *str;
    long num;
    struct device_info_t *device;
    struct card_info_t *card;
    struct mtd_ident_t *mtd;
    struct adjust_list_t *adjust;
}

%type <str> STRING
%type <num> NUMBER
%type <adjust> adjust resource
%type <device> device needs_mtd module class
%type <card> card anonymous tuple manfid version function bind
%type <mtd> region jedec dtype default mtd
%%

list:	  /* nothing */
	| list adjust
		{
		    adjust_list_t **tail = &root_adjust;
		    while (*tail != NULL) tail = &(*tail)->next;
		    *tail = $2;
		}
	| list device
		{
		    $2->next = root_device;
		    root_device = $2;
		}
	| list mtd
		{
		    if ($2->mtd_type == 0) {
			yyerror("no ID method for this card");
			YYERROR;
		    }
		    if ($2->module == NULL) {
			yyerror("no MTD module specified");
			YYERROR;
		    }
		    $2->next = root_mtd;
		    root_mtd = $2;
		}
	| list card
		{
		    if ($2->ident_type == 0) {
			yyerror("no ID method for this card");
			YYERROR;
		    }
		    if ($2->functions == 0) {
			yyerror("no function bindings");
			YYERROR;
		    }
		    if ($2->ident_type == FUNC_IDENT) {
			$2->next = root_func;
			root_func = $2;
		    }
		    else {
			$2->next = root_card;
			root_card = $2;
		    }
		}
	| list opts
	| list error
	;

adjust:   INCLUDE resource
		{
		    $2->adj.Action = ADD_MANAGED_RESOURCE;
		    $$ = $2;
		}
	| EXCLUDE resource
		{
		    $2->adj.Action = REMOVE_MANAGED_RESOURCE;
		    $$ = $2;
		}
	| RESERVE resource
		{
		    $2->adj.Action = ADD_MANAGED_RESOURCE;
		    $2->adj.Attributes |= RES_RESERVED;
		    $$ = $2;
		}
	| adjust ',' resource
		{
		    $3->adj.Action = $1->adj.Action;
		    $3->adj.Attributes = $1->adj.Attributes;
		    $3->next = $1;
		    $$ = $3;
		}
	;

resource: IRQ_NO NUMBER
		{
		    $$ = calloc(sizeof(adjust_list_t), 1);
		    $$->adj.Resource = RES_IRQ;
		    $$->adj.resource.irq.IRQ = $2;
		}
	| PORT NUMBER '-' NUMBER
		{
		    if (($4 < $2) || ($4 > 0xffff)) {
			yyerror("invalid port range");
			YYERROR;
		    }
		    $$ = calloc(sizeof(adjust_list_t), 1);
		    $$->adj.Resource = RES_IO_RANGE;
		    $$->adj.resource.io.BasePort = $2;
		    $$->adj.resource.io.NumPorts = $4 - $2 + 1;
		}
	| MEMORY NUMBER '-' NUMBER
		{
		    if ($4 < $2) {
			yyerror("invalid address range");
			YYERROR;
		    }
		    $$ = calloc(sizeof(adjust_list_t), 1);
		    $$->adj.Resource = RES_MEMORY_RANGE;
		    $$->adj.resource.memory.Base = (caddr_t)$2;
		    $$->adj.resource.memory.Size = $4 - $2 + 1;
		}
	;

device:	  DEVICE STRING
		{
		    $$ = calloc(sizeof(device_info_t), 1);
		    strcpy($$->dev_info, $2);
		    free($2);
		}
	| needs_mtd
	| module
	| class
	;

card:	  CARD STRING
		{
		    $$ = calloc(sizeof(card_info_t), 1);
		    $$->name = $2;
		}
	| anonymous
	| tuple
	| manfid
	| version
	| function
	| bind
	;

anonymous: card ANONYMOUS
		{
		    if ($1->ident_type != 0) {
			yyerror("ID method already defined");
			YYERROR;
		    }
		    if (blank_card) {
			yyerror("Anonymous card already defined");
			YYERROR;
		    }
		    $1->ident_type = BLANK_IDENT;
		    blank_card = $1;
		}
	;

tuple:	  card TUPLE NUMBER ',' NUMBER ',' STRING
		{
		    if ($1->ident_type != 0) {
			yyerror("ID method already defined");
			YYERROR;
		    }
		    $1->ident_type = TUPLE_IDENT;
		    $1->tuple.code = $3;
		    $1->tuple.ofs = $5;
		    $1->tuple.info = $7;
		}
	;

manfid:	  card MANFID NUMBER ',' NUMBER
		{
		    if ($1->ident_type != 0) {
			yyerror("ID method already defined");
			YYERROR;
		    }
		    $1->ident_type = MANFID_IDENT;
		    $1->manfid.manf = $3;
		    $1->manfid.card = $5;
		}

version:  card VERSION STRING
		{
		    if ($1->ident_type != 0) {
			yyerror("ID method already defined\n");
			YYERROR;
		    }
		    $1->ident_type = VERS_1_IDENT;
		    $1->vers.ns = 1;
		    $1->vers.pi[0] = $3;
		}
	| version ',' STRING
		{
		    if ($1->vers.ns == 4) {
			yyerror("too many version strings");
			YYERROR;
		    }
		    $1->vers.pi[$1->vers.ns] = $3;
		    $1->vers.ns++;
		}
	;

function: card FUNCTION NUMBER
		{
		    if ($1->ident_type != 0) {
			yyerror("ID method already defined\n");
			YYERROR;
		    }
		    $1->ident_type = FUNC_IDENT;
		    $1->func.funcid = $3;
		}
	;

bind:	  card BIND STRING
		{
		    if (add_binding($1, $3, 0) != 0)
			YYERROR;
		}
	| card BIND STRING TO NUMBER
		{
		    if (add_binding($1, $3, $5) != 0)
			YYERROR;
		}
	| bind ',' STRING
		{
		    if (add_binding($1, $3, 0) != 0)
			YYERROR;
		}
	| bind ',' STRING TO NUMBER
		{
		    if (add_binding($1, $3, $5) != 0)
			YYERROR;
		}
	;

needs_mtd: device NEEDS_MTD
		{
		    $1->needs_mtd = 1;
		}
	;

opts:	  MODULE STRING OPTS STRING
		{
		    device_info_t *d;
		    int i, found = 0;
		    for (d = root_device; d; d = d->next) {
			for (i = 0; i < d->modules; i++)
			    if (strcmp($2, d->module[i]) == 0) break;
			if (i < d->modules) {
			    if (d->opts[i])
				free(d->opts[i]);
			    d->opts[i] = strdup($4);
			    found = 1;
			}
		    }
		    free($2); free($4);
		    if (!found) {
			yyerror("module name not found!");
			YYERROR;
		    }
		}
	;

module:	  device MODULE STRING
		{
		    if (add_module($1, $3) != 0)
			YYERROR;
		}
	| module OPTS STRING
		{
		    if ($1->opts[$1->modules-1] == NULL)
			$1->opts[$1->modules-1] = $3;
		    else {
			yyerror("too many options");
			YYERROR;
		    }
		}
	| module ',' STRING
		{
		    if (add_module($1, $3) != 0)
			YYERROR;
		}
	;

class:	  device CLASS STRING
		{
		    if ($1->class != NULL) {
			yyerror("extra class string");
			YYERROR;
		    }
		    $1->class = $3;
		}
	;

region:	  REGION STRING
		{
		    $$ = calloc(sizeof(mtd_ident_t), 1);
		    $$->name = $2;
		}
	| dtype
	| jedec
	| default
	| mtd
	;

dtype:	  region DTYPE NUMBER
		{
		    if ($1->mtd_type != 0) {
			yyerror("ID method already defined");
			YYERROR;
		    }
		    $1->mtd_type = DTYPE_MTD;
		    $1->dtype = $3;
		}
	;

jedec:	  region JEDEC NUMBER NUMBER
		{
		    if ($1->mtd_type != 0) {
			yyerror("ID method already defined");
			YYERROR;
		    }
		    $1->mtd_type = JEDEC_MTD;
		    $1->jedec_mfr = $3;
		    $1->jedec_info = $4;
		}
	;

default:  region DEFAULT
		{
		    if ($1->mtd_type != 0) {
			yyerror("ID method already defined");
			YYERROR;
		    }
		    if (default_mtd) {
			yyerror("Default MTD already defined");
			YYERROR;
		    }
		    $1->mtd_type = DEFAULT_MTD;
		    default_mtd = $1;
		}
	;

mtd:	  region MTD STRING
		{
		    if ($1->module != NULL) {
			yyerror("extra MTD entry");
			YYERROR;
		    }
		    $1->module = $3;
		}
	;

%%
void yyerror(char *msg, ...)
{
     va_list ap;
     char str[256];

     va_start(ap, msg);
     sprintf(str, "config error, file '%s' line %d: ",
	     current_file, current_lineno);
     vsprintf(str+strlen(str), msg, ap);
#ifdef DEBUG
     fprintf(stderr, "%s\n", str);
#else
     syslog(LOG_INFO, "%s", str);
#endif
     va_end(ap);
}

static int add_binding(card_info_t *card, char *name, int fn)
{
    device_info_t *dev = root_device;
    if (card->functions == MAX_FUNCTIONS) {
	yyerror("too many bindings\n");
	return -1;
    }
    for (; dev; dev = dev->next)
	if (strcmp((char *)dev->dev_info, name) == 0) break;
    if (dev == NULL) {
	yyerror("unknown device: %s", name);
	return -1;
    }
    card->device[card->functions] = dev;
    card->dev_fn[card->functions] = fn;
    card->functions++;
    free(name);
    return 0;
}

static int add_module(device_info_t *dev, char *name)
{
    if (dev->modules == MAX_MODULES) {
	yyerror("too many modules");
	return -1;
    }
    dev->module[dev->modules] = name;
    dev->opts[dev->modules] = NULL;
    dev->modules++;
    return 0;
}

#ifdef DEBUG
adjust_list_t *root_adjust = NULL;
device_info_t *root_device = NULL;
card_info_t *root_card = NULL, *blank_card = NULL, *root_func = NULL;
mtd_ident_t *root_mtd = NULL, *default_mtd = NULL;

void main(int argc, char *argv[])
{
    if (argc > 1)
	parse_configfile(argv[1]);
}
#endif
