%{
/*
 * yacc_config.y 1.17 1995/05/15 05:22:17 (David Hinds)\n"
 */
    
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <sys/types.h>

#include "cs_types.h"
#include "cs.h"
#include "cistpl.h"
#include "ds.h"
    
#include "cardmgr.h"

extern int config_lineno;

%}

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

%union {
    char *str;
    int 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 module start stop
%type <card> card anonymous tuple 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 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);
		}
	| module
	| start
	| stop
	;

card:	  CARD STRING
		{
		    $$ = calloc(sizeof(card_info_t), 1);
		    $$->name = $2;
		}
	| anonymous
	| tuple
	| 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;
		}
	;

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
		{
		    device_info_t *dev = root_device;
		    if ($1->functions == MAX_FUNCTIONS) {
			yyerror("too many bindings\n");
			YYERROR;
		    }
		    for (; dev; dev = dev->next)
			if (strcmp((char *)dev->dev_info, $3) == 0) break;
		    if (dev == NULL) {
			yyerror("unknown device: %s", $3);
			YYERROR;
		    }
		    $1->device[$1->functions] = dev;
		    $1->functions++;
		    free($3);
		}
	| bind ',' STRING
		{
		    device_info_t *dev = root_device;
		    if ($1->functions == MAX_FUNCTIONS) {
			yyerror("too many bindings\n");
			YYERROR;
		    }
		    for (; dev; dev = dev->next)
			if (strcmp((char *)dev->dev_info, $3) == 0) break;
		    if (dev == NULL) {
			yyerror("unknown device: %s", $3);
			YYERROR;
		    }
		    $1->device[$1->functions] = dev;
		    $1->functions++;
		    free($3);
		}
	;

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

start:	  device START STRING
		{
		    if ($1->start_cmd != NULL) {
			yyerror("extra start command");
			YYERROR;
		    }
		    $1->start_cmd = $3;
		}
	;

stop:	  device STOP STRING
		{
		    if ($1->stop_cmd != NULL) {
			yyerror("extra stop command");
			YYERROR;
		    }
		    $1->stop_cmd = $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, line %d: ", config_lineno);
     vsprintf(str+strlen(str), msg, ap);
#ifdef DEBUG
     fprintf(stderr, "%s\n", str);
#else
     syslog(LOG_INFO, "%s", str);
#endif
     va_end(ap);
}

void parse_configfile(FILE *f)
{
     extern FILE *yyin;
     yyin = f;
     config_lineno = 1;
     yyparse();
     if (root_device == NULL)
	 syslog(LOG_INFO, "no device drivers defined");
     if (root_card == NULL)
	 syslog(LOG_INFO, "no cards defined");
}

#ifdef DEBUG
adjust_list_t *root_adjust = NULL;
device_info_t *root_device = NULL;
card_info_t *root_card = NULL;
void main(void)
{
    parse_configfile(stdin);
}
#endif
