/* proc.c: PnP BIOS system /proc configuration file interface */
/*
 * $Header: /fs/fs2/usr/src/linux/drivers/pnp/RCS/proc.c,v 1.6 1996/07/04 22:21:45 root Exp $
 *
 * $Log: proc.c,v $
 * Revision 1.6  1996/07/04 22:21:45  root
 * numerical ID spec and activation added to /proc/pnpconf
 *
 * Revision 1.5  1996/06/15  12:39:27  root
 * ioctl access added
 *
 * Revision 1.4  1996/06/12  18:56:34  root
 * standard internal i/f & BIOS read/query
 *
 * Revision 1.3  1996/06/09  10:40:34  root
 * card configuration now done
 *
 * Revision 1.2  1996/06/02  14:07:23  root
 * current config read implemented on ISA cards
 *
 * Revision 1.1  1996/06/02  12:02:35  root
 * Initial revision
 *
 *
 */

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

#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/malloc.h>
#include <linux/proc_fs.h>
#include <asm/bitops.h>
#include "pnp_if.h"

extern void pnp_inc_mod_usage(void);
extern void pnp_dec_mod_usage(void);

/* PnP device config details */
extern u_int pnp_sysdev_num;	/* number of system devices */
extern u_char *pnp_sysdev_list;	/* list of system device nodes */

extern int pnp_try_reconfigure(pnp_device *, pnp_config *);

static int proc_pnpconf_open(struct inode *, struct file *);
static void proc_pnpconf_rel(struct inode *, struct file *);

static int proc_pnpconf_read(struct inode *, struct file *, char *, int);
static int proc_pnpconf_write(struct inode *, struct file *, const char *,int);
static int proc_pnpconf_ioctl(struct inode *, struct file *, unsigned int,
			      unsigned long);

static pnp_device *pnpconf_getdev(char **);
static int pnpconf_query(pnp_device *, int, char *, int);
static int pnpconf_read(pnp_device *, int, char *, int);
static int pnpconf_reconf(pnp_device *, char *cp);

/*****************************************************************************/
static struct file_operations proc_pnp_operations = {
	NULL,			/* lseek - default */
	proc_pnpconf_read,	/* read */
	proc_pnpconf_write,	/* write */
	NULL,			/* readdir */
	NULL,			/* select */
	proc_pnpconf_ioctl,	/* ioctl */
	NULL,			/* mmap */
	proc_pnpconf_open,	/* open */
	proc_pnpconf_rel,	/* release */
	NULL			/* fsync */
};

/* /proc/pnpconf inode ops table */
static struct inode_operations proc_pnp_inode_operations = {
	&proc_pnp_operations,	/* file-ops */
};


/*****************************************************************************/
struct proc_dir_entry proc_dir_pnpconf = {
    0,  7, "pnpconf",
    S_IFREG | S_IRUGO | S_IWUSR, 1, 0, 0,
    0, &proc_pnp_inode_operations
};

/*****************************************************************************/
/* open /proc/pnpconf */
static int proc_pnpconf_open(struct inode *inode, struct file *file)
{
    /* check that it's not open already */
    if (inode->i_count>1)
	return -EBUSY;	/* it is */

    pnp_inc_mod_usage();

    file->f_pos = 0;
    file->private_data = (void*)0;

    return 0;
} /* end proc_pnpconf_open() */

static void proc_pnpconf_rel(struct inode *inode, struct file *file)
{
    pnp_dec_mod_usage();

} /* end proc_pnpconf_rel() */

/*****************************************************************************/
/* read reply data back from /proc/pnpconf */
static int proc_pnpconf_read(struct inode *inode, struct file *file,
			     char *buffer, int size)
{
    pnp_device *device;
    int rt;
    char *page;
    csn_t csn;
    u_char dev;

    /* return if nothing to read */
    if (!file->private_data)
	return 0;

    /* find the appropriate device struct */
    csn = ((int)file->private_data >> 8) & 0xFF;
    dev = (int)file->private_data & 0xFF;
    for (device=pnp_device_list; device; device=device->pd_next)
	if (device->pd_dev==dev &&
	    device->pd_card &&
	    device->pd_card->pc_csn==csn
	    )
	    break;

    if (!device) {
	file->private_data = (void*)0;
	file->f_pos = 0;
	return -ENODEV;
    }

    /* allocate a page to use as a buffer */
    page = (char*) get_free_page(GFP_KERNEL);
    if (!page)
	return -ENOMEM;

    rt = 0;
    switch (((int)file->private_data >> 16) & 0xFF) {
     case 'q':
	rt = pnpconf_query(device,file->f_pos,page,
			   size>PAGE_SIZE-81 ? PAGE_SIZE-81 : size
			   );
	break;

     case 'r':
	rt = pnpconf_read(device,file->f_pos,page,
			  size>PAGE_SIZE-81 ? PAGE_SIZE-81 : size
			  );
	break;

     default:
	rt = -EIO;
	break;
    }

    /* fill the buffer if data available */
    if (rt>0) {
	memcpy_tofs(buffer,page,rt);
	file->f_pos += rt;
    }

    free_page((u_long)page);

    if (rt<=0) {
	file->private_data = (void*)0;
	file->f_pos = 0;
    }

    return rt;
} /* end proc_pnpconf_read() */

/*****************************************************************************/
/*
 * come here if a command is written to /proc/pnpconf
 *
 * Possible commands (must fit all in one buffer):
 *   "r <device>" : set up to read configuration of device
 *   "q <device>" : set up to read possible configs of device
 *   "w <device> <config>" : try to configure a device
 *   "a <device> [0|1]" : (dis)activate a device
 *   "v <device>" : read vendor allocated registers on device
 *
 * <device> is an identifier, such at CTL0031 (see /proc/pnp)
 *
 * <config> is a set of:
 *  irq[#]=@[:LlEe]  : #=dev IRQ position, @=IRQ number, LlEe=hi/lo level/edge
 *  io[#]=@          : #=dev IO position, @=base address of range
 *  mm[#]=@-$[s|w|l] : #=posn, @=base, $=top, s=8-bit, w=16-bit, l=32-bit
 *  dma[#]=@	     : #=posn, @=DMA channel
 *  vr#=@            : #=vendor register, @=8-bit value to write in
 * All numbers to be in hex (no need for '0x' prefix), including IRQ number
 * In all cases, equal sign onwards can be missed off to indicate disablement
 */
static int proc_pnpconf_write(struct inode *inode, struct file *file,
			      const char *buffer, int size)
{
    pnp_device *device;
    char *command, *cp;
    int rt;

    /* clear any pending read operations */
    file->f_pos = 0;
    file->private_data = (void*)0;

    /* must be valid */
    if (size==0)
	return -EINVAL;

    if (size>4000)
	return -E2BIG;

    /* allocate a buffer to hold it */
    command = (char*) kmalloc(size+1,GFP_KERNEL);
    if (!command)
	return -ENOMEM;

    /* copy the command into kernel space and stick NUL on the end */
    memcpy_fromfs(command,buffer,size);
    if (command[size-1]=='\n')
	command[size-1] = '\0';
    else
	command[size] = '\0';

    /* deal with the command appropriately (as lowercase) */
    cp = command;
    rt = size;
    switch (*(cp++)|0x20) {
	/*===================================================================*/
     case 'a':
	/* parse an activate command */
	if (*(cp++)!=' ') { rt = -EINVAL; break; }
	device = pnpconf_getdev(&cp);
	if (!device || !cp) { rt = -ENODEV; break; }
	while (*cp==' ') cp++;
	if ((cp[0]!='0' && cp[0]!='1') || cp[1]) { rt = -EINVAL; break; }

	printk("do active on %p: %c\n",device,cp[0]);
	if (cp[0]=='0')
	    pnp_drv_deactivate(device);
	else
	    pnp_drv_activate(device);
	break;

	/*===================================================================*/
     case 'q':
	/* parse a query command */
	if (*(cp++)!=' ') { rt = -EINVAL; break; }
	device = pnpconf_getdev(&cp);
	if (!device) { rt = -ENODEV; break; }
	while (*cp==' ') cp++;
	if (*cp) { rt = -EINVAL; break; }

	/* note what the next read will do */
	file->private_data = (void*)(('q'<<16) +
				     (device->pd_card->pc_csn<<8) +
				     device->pd_dev
				     );
	break;

	/*===================================================================*/
     case 'r':
	/* parse a read command */
	if (*(cp++)!=' ') { rt = -EINVAL; break; }
	device = pnpconf_getdev(&cp);
	if (!device) { rt = -ENODEV; break; }
	while (*cp==' ') cp++;
	if (*cp) { rt = -EINVAL; break; }

	/* note what the next read will do */
	file->private_data = (void*)(('r'<<16) +
				     (device->pd_card->pc_csn<<8) +
				     device->pd_dev
				     );
	break;

	/*===================================================================*/
#if 0
     case 'v':
	/* parse a vendor command */
	rt = pnpconf_vendor(file,command);
	break;
#endif

	/*===================================================================*/
     case 'w':
	/* parse a write command */
	if (*(cp++)!=' ') { rt = -EINVAL; break; }
	device = pnpconf_getdev(&cp);
	if (!device) { rt = -ENODEV; break; }
	while (*cp==' ') cp++;

	/* reconfigure */
	rt = pnpconf_reconf(device,cp);
	if (rt<0)
	    break;

	if (!rt) rt = size;

	/* the next read will read back the configuration */
	file->private_data = (void*)(('r'<<16) +
				     (device->pd_card->pc_csn<<8) +
				     device->pd_dev
				     );
	break;

	/*===================================================================*/
     default:
	rt = -EINVAL;
	break;
    }

    kfree(command);
    return rt;

} /* end proc_pnpconf_write() */

/*****************************************************************************/
/* extract a device identifier from a command */
static pnp_device *pnpconf_getdev(char **command)
{
    pnp_device *device;
    char *cp, *cp2, ch;
    u_int id = 0;

    /* skip over any leading spaces */
    cp = *command;
    while (*cp==' ') cp++;

    /*=======================================================================*/
    /* if the first char is an '@', then we should have a csn/dev num */
    if (*cp=='@') {
	cp++;
	id = simple_strtoul(cp,&cp,16);

	/* now look up the ID */
	for (device=pnp_device_list; device; device=device->pd_next)
	    if (device->pd_card->pc_csn==(csn_t)((id>>8)&0xFF) &&
		device->pd_dev==(u_char)(id&0xFF)
		)
		break;
	*command = cp;

	return device;
    }

    /*=======================================================================*/
    /* end of address must be 7 chars away */
    cp2 = strchr(cp,' ');
    if (cp2 ? cp2-cp!=7 : strlen(cp)!=7)
	return NULL;

    /* get the vendor ID bits */
    if (*cp>='A' && *cp<='Z')		ch = *cp - 'A' + 1;
    else if (*cp>='a' && *cp<='z')	ch = *cp - 'a' + 1;
    else				return NULL;
    ((u_char*)&id)[0] |= ch << 2;
    cp++;

    if (*cp>='A' && *cp<='Z')		ch = *cp - 'A' + 1;
    else if (*cp>='a' && *cp<='z')	ch = *cp - 'a' + 1;
    else				return NULL;
    ((u_char*)&id)[0] |= ch >> 3;
    ((u_char*)&id)[1] |= ch << 5;
    cp++;

    if (*cp>='A' && *cp<='Z')		ch = *cp - 'A' + 1;
    else if (*cp>='a' && *cp<='z')	ch = *cp - 'a' + 1;
    else				return NULL;
    ((u_char*)&id)[1] |= ch;
    cp++;

    /*=======================================================================*/
    /* get the product ID/revision bits */
    if (*cp>='0' && *cp<='9')		ch = *cp - '0';
    else if (*cp>='A' && *cp<='F')	ch = *cp - 'A' + 0x0A;
    else if (*cp>='a' && *cp<='f')	ch = *cp - 'a' + 0x0A;
    else return NULL;
    ((u_char*)&id)[2] |= ch<<4;
    cp++;

    if (*cp>='0' && *cp<='9')		ch = *cp - '0';
    else if (*cp>='A' && *cp<='F')	ch = *cp - 'A' + 0x0A;
    else if (*cp>='a' && *cp<='f')	ch = *cp - 'a' + 0x0A;
    else return NULL;
    ((u_char*)&id)[2] |= ch;
    cp++;

    if (*cp>='0' && *cp<='9')		ch = *cp - '0';
    else if (*cp>='A' && *cp<='F')	ch = *cp - 'A' + 0x0A;
    else if (*cp>='a' && *cp<='f')	ch = *cp - 'a' + 0x0A;
    else return NULL;
    ((u_char*)&id)[3] |= ch<<4;
    cp++;

    if (*cp>='0' && *cp<='9')		ch = *cp - '0';
    else if (*cp>='A' && *cp<='F')	ch = *cp - 'A' + 0x0A;
    else if (*cp>='a' && *cp<='f')	ch = *cp - 'a' + 0x0A;
    else return NULL;
    ((u_char*)&id)[3] |= ch;
    cp++;
    *command = cp;

    /*=======================================================================*/
    /* now look up the ID */
    for (device=pnp_device_list; device; device=device->pd_next)
	if (device->pd_id==id)
	    break;

    return device;

} /* end pnpconf_getdev() */

/*****************************************************************************/
static const char *memrom[] = { "", "ROM " };
static const char *memshad[] = { "", "shadow " };
static const char *memstat[] = { "8b ", "16b ", "8/16/32 ", "32b " };
static const char *memsupp[] = { "hi-adr ", "range-len " };
static const char *memcach[] = { "", "cache " };
static const char *memrw[] = { "ro ", "rw " };

/* query the possible states of configuration for a device */
static int pnpconf_query(pnp_device *device, int offs, char *page, int size)
{
    u_char *pr;
    int ix = 0;
    u_short len, tmp;
    u_char res, cont = 1, ldev = 1;
    u_char irq[2] = {0}, io[2] = {0}, mem[2] = {0}, m32[2] = {0}, dma[2] = {0};

    /* refer to the resource list */
    if (!device->pd_card->pc_resources)
	return 0;

    pr = device->pd_card->pc_resources + device->pd_resoffset;
    offs = -offs;

    /*=======================================================================*/
    /* read the resource list */
    cont = 1;
    while (cont) {
	res = *pr;
	pr++;

	/* is it large or small format? */
	if (res&0x80) {
	    /*---------------------------------------------------------------*/
	    /* large - get size */
	    len = *(u_short*)pr;
	    pr += 2;

	    /* deal with each command */
	    switch ((enum PNP_LARGE_RES_TAG)(res&0x7F)) {
	     case PNPLTAG_ANSI_ID:
		tmp = len>72 ? 72 : len;
		tmp =
		    sprintf(page," id: '%.*s'\n",tmp,pr);
		break;

	     case PNPLTAG_MEM:
		tmp =
		    sprintf(page,
			    " mem32[%d]: %s%s%s%s%s%s base:%04x00-%04x00 "
			    "al:%06x len:%04x00\n",
			    mem[ix]++,
			    memrom[(*pr>>6)&0x01],
			    memshad[(*pr>>5)&0x01],
			    memstat[(*pr>>3)&0x03],
			    memsupp[(*pr>>2)&0x01],
			    memcach[(*pr>>1)&0x01],
			    memrw[*pr&0x01],
			    *(u_short*)(pr+1),
			    *(u_short*)(pr+3),
			    *(u_short*)(pr+5)>0 ? *(u_short*)(pr+5) : 0x10000,
			    *(u_short*)(pr+7)
			    );
		break;

	     case PNPLTAG_MEM32:
		tmp =
		    sprintf(page,
			    " mem32[%d]: %s%s%s%s%s%s base:%08x-%08x al:%08x"
			    " len:%08x\n",
			    m32[ix]++,
			    memrom[(*pr>>6)&0x01],
			    memshad[(*pr>>5)&0x01],
			    memstat[(*pr>>3)&0x03],
			    memsupp[(*pr>>2)&0x01],
			    memcach[(*pr>>1)&0x01],
			    memrw[*pr&0x01],
			    *(u_int*)(pr+1),
			    *(u_int*)(pr+5),
			    *(u_int*)(pr+9),
			    *(u_int*)(pr+13)
			    );
		break;

	     case PNPLTAG_MEM32_FIXED:
		tmp =
		    sprintf(page,
			    " mem32fix: %s%s%s%s%s%s base:%08x len:%08x\n",
			    memrom[(*pr>>6)&0x01],
			    memshad[(*pr>>5)&0x01],
			    memstat[(*pr>>3)&0x03],
			    memsupp[(*pr>>2)&0x01],
			    memcach[(*pr>>1)&0x01],
			    memrw[*pr&0x01],
			    *(u_int*)(pr+1),
			    *(u_int*)(pr+5)
			    );
		break;

	     case PNPLTAG_UNICODE_ID:
	     case PNPLTAG_VENDOR_DEF:
	     default:
		tmp =
		    sprintf(page,
			    " unknown large: %02x L:%d\n",res&0x7F,len
			    );
		break;
	    }
	}
	else {
	    /*---------------------------------------------------------------*/
	    /* small - read res data */
	    len = res & 0x7;

	    /* switch on command */
	    switch ((enum PNP_SMALL_RES_TAG)((res&0x78)>>3)) {
	     case PNPSTAG_PNPVERSION:
		tmp =
		    sprintf(page,
			    " version: %02x vendor:%02x\n",pr[0],pr[1]);
		break;

	     case PNPSTAG_LOGICAL_DEV_ID:
		/* break on LDEV not as first item */
		cont = ldev;
		if (!cont)
		    break;
		tmp = pr[0]<<8 | pr[1];
		tmp =
		    sprintf(page,
			    "LOG DEV ID: %c%c%c%02x%02x boot:%c"
			    " cmds:%02x%02x\n",
			    ((tmp>>10)&0x1F)+'A'-1,
			    ((tmp>>5)&0x1F)+'A'-1,
			    ((tmp>>0)&0x1F)+'A'-1,
			    pr[2],
			    pr[3],
			    pr[4]&0x01 ? 'y' : 'n',
			    pr[5],
			    pr[4]&0xFE
			    );
		break;

	     case PNPSTAG_COMPATIBLE_DEV_ID:
		tmp = pr[0]<<8 | pr[1];
		tmp =
		    sprintf(page,
			    " compat dev: %c%c%c%02x%02x\n",
			    ((tmp>>10)&0x1F)+'A'-1,
			    ((tmp>>5)&0x1F)+'A'-1,
			    ((tmp>>0)&0x1F)+'A'-1,
			    pr[2],
			    pr[3]
			    );
		break;

	     case PNPSTAG_IRQ:
		tmp = (len==3) ? pr[2] : 0x01;
		tmp =
		    sprintf(page,
			    " irq[%d]: "
			    "i:%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c E:%c%c%c%c\n",
			    irq[ix]++,
			    test_bit(0x0,pr) ? '0' : '-',
			    test_bit(0x1,pr) ? '1' : '-',
			    test_bit(0x2,pr) ? '2' : '-',
			    test_bit(0x3,pr) ? '3' : '-',
			    test_bit(0x4,pr) ? '4' : '-',
			    test_bit(0x5,pr) ? '5' : '-',
			    test_bit(0x6,pr) ? '6' : '-',
			    test_bit(0x7,pr) ? '7' : '-',
			    test_bit(0x8,pr) ? '8' : '-',
			    test_bit(0x9,pr) ? '9' : '-',
			    test_bit(0xA,pr) ? 'A' : '-',
			    test_bit(0xB,pr) ? 'B' : '-',
			    test_bit(0xC,pr) ? 'C' : '-',
			    test_bit(0xD,pr) ? 'D' : '-',
			    test_bit(0xE,pr) ? 'E' : '-',
			    test_bit(0xF,pr) ? 'F' : '-',
			    test_bit(0x3,&tmp) ? 'l' : '-',
			    test_bit(0x2,&tmp) ? 'L' : '-',
			    test_bit(0x1,&tmp) ? 'e' : '-',
			    test_bit(0x0,&tmp) ? 'E' : '-'
			    );
		break;

	     case PNPSTAG_DMA:
		tmp =
		    sprintf(page,
			    " dma[%d]: d:%c%c%c%c%c%c%c%c sp:%d mo:%c%c "
			    "bus:%c tran:%d\n",
			    dma[ix]++,
			    test_bit(0x0,pr) ? '0' : '-',
			    test_bit(0x1,pr) ? '1' : '-',
			    test_bit(0x2,pr) ? '2' : '-',
			    test_bit(0x3,pr) ? '3' : '-',
			    test_bit(0x4,pr) ? '4' : '-',
			    test_bit(0x5,pr) ? '5' : '-',
			    test_bit(0x6,pr) ? '6' : '-',
			    test_bit(0x7,pr) ? '7' : '-',
			    (pr[1]&0x60)>>5,
			    test_bit(0x4,pr+1) ? 'w' : '-',
			    test_bit(0x3,pr+1) ? 'b' : '-',
			    test_bit(0x2,pr+1) ? 'y' : 'n',
			    pr[1]&0x03
			    );
		break;

	     case PNPSTAG_START_DEP:
		irq[1] = irq[0];
		io[1] = io[0];
		mem[1] = mem[0];
		m32[1] = m32[0];
		dma[1] = dma[0];
		ix = 1;
		if (len==1)
		    tmp = sprintf(page,"START DEP: p:%d\n",pr[0]);
		else
		    tmp = sprintf(page,"START DEP\n");
		break;

	     case PNPSTAG_END_DEP:
		ix = 0;
		tmp = sprintf(page,"END DEP\n");
		break;

	     case PNPSTAG_IO:
		tmp =
		    sprintf(page,
			    " io[%d]: 16:%c %04x-%04x a:%02x N:%02x\n",
			    io[ix]++,
			    test_bit(0,pr) ? 'y' : 'n',
			    *(u_short*)(pr+1),
			    *(u_short*)(pr+3),
			    pr[5],
			    pr[6]
			    );
		break;

	     case PNPSTAG_IO_FIXED:
		tmp =
		    sprintf(page," iofix: %04x N:%02x\n",
			    *(u_short*)(pr),
			    pr[2]
			    );
		break;

	     case PNPSTAG_VENDOR_DEF:
		tmp = sprintf(page," vendor def: N:%d\n",len);
		break;

	     case PNPSTAG_END:
		cont = 0;
		break;

	     default:
		tmp =
		    sprintf(page," unknown small: %02x N:%d\n",
			    (res&0x78)>>3,len
			    );
		break;
	    }
	}
	ldev = 0;
	pr += len;

	/*===================================================================*/
	if (!cont)
	    break;

	/* sort out how much should remain in the buffer */
	if (tmp+offs<0) {
	    offs += tmp;
	    continue;
	}
	if (offs<0) {
	    tmp -= -offs;
	    memmove(page,page-offs,tmp);
	    offs = 0;
	}
	offs += tmp;
	if (offs>=size)
	    return size;

	page += tmp;
	size -= tmp;

    } /* end while cont */

    return offs>0 ? offs : 0;

} /* end pnpconf_query() */

/*****************************************************************************/
/* read the current state of configuration for a device */
static int pnpconf_read(pnp_device *device, int offs, char *page, int size)
{
    pnp_config conf;
    int loop;
    char *cp;

    /* actually get the configuration */
    loop = device->pd_card->pc_interface->pi_get_config(device,&conf);
    if (loop<0)
	return loop;

    cp = page;

    /* note activation */
    cp += sprintf(cp,"active: %s\n",conf.pc_active&0x01?"yes":"no");

    /* note IRQ's */
    if (conf.pc_s.irq) {
	for (loop=0; loop<2; loop++) {
	    if (test_bit(loop,&conf.pc_s.irq)) {
		cp += sprintf(cp,"irq%d=%x:%c ",
			      loop,
			      conf.pc_irq[loop].num,
			      "elEL"[conf.pc_irq[loop].type&0x03]
			      );
	    }
	}
	cp += sprintf(cp,"\n");
    }

    /* note DMA's */
    if (conf.pc_s.dma) {
	for (loop=0; loop<2; loop++)
	    if (test_bit(loop,&conf.pc_s.dma))
		cp += sprintf(cp,"dma%d=%x ",loop,conf.pc_dma[loop].chan);
	cp += sprintf(cp,"\n");
    }

    /* note IO's */
    if (conf.pc_s.io) {
	for (loop=0; loop<8; loop++)
	    if (test_bit(loop,&conf.pc_s.io))
		cp += sprintf(cp,"io%d=%04x ",loop,conf.pc_io[loop].base);
	cp += sprintf(cp,"\n");
    }

    /* note MEM/MEM32's */
    if (conf.pc_s.mem) {
	for (loop=0; loop<4; loop++)
	    if (test_bit(loop,&conf.pc_s.mem))
		cp += sprintf(cp,"mm%d=%08x-%08x%c ",
			      loop,
			      (u_int)conf.pc_mem[loop].base,
			      (u_int)conf.pc_mem[loop].top,
			      "sw?l"[conf.pc_mem[loop].type&0x3]
			      );
	cp += sprintf(cp,"\n");
    }

    /*=======================================================================*/
    /* throw away the bits already read */
    if (cp-page<=offs)
	return 0;

    size = cp-page-offs>=size ? size : cp-page-offs;

    if (offs>0)
	memmove(page,page+offs,size);

    return size;

} /* end pnpconf_read() */

/*****************************************************************************/
/*
 * Begin by parsing a string of the following, and building up a config record
 *
 *  irq[#]=@[:{LlEe}] : #=dev IRQ position, @=IRQ number, LlEe=hi/lo level/edge
 *  io[#]=@           : #=dev IO position, @=base address of range
 *  mm[#]=@[-$][:{bwl}] : #=posn, @=base, $=top, s=8-bit, w=16-bit, l=32-bit
 *  dma[#]=@	      : #=posn, @=DMA channel
 *  vr#=@             : #=vendor register, @=8-bit value to write in
 * All numbers to be in hex (no need for '0x' prefix), including IRQ number
 * In all cases, =n means disablement
 */
static int pnpconf_reconf(pnp_device *device, char *cp)
{
    pnp_config conf;
    int ix;
    u_char irq=0, dma=0, io=0, mem=0;
    u_int d;

    /* nothing yet */
    conf.pc_s.irq = 0;
    conf.pc_s.dma = 0;
    conf.pc_s.io = 0;
    conf.pc_s.mem = 0;
    conf.pc_force = 0;

    /*=======================================================================*/
    /* parse things */
    while (*cp) {
	if (*cp==' ') { cp++; continue; }

	/* determine the command by 1st & second lowercase letters */
	switch (((cp[0]<<8)+cp[1])|0x2020) {
	    /*---------------------------------------------------------------*/
	    /* IRQ */
	 case ('i'<<8)+'r':
	    if ((cp[2]|0x20)!='q') return -EINVAL;
	    cp += 3;
	    if (cp[0]>='0' && cp[0]<='1' && cp[1]=='=') {
		ix = *cp - '0';
		cp += 2;
	    }
	    else if (cp[0]=='=') {
		if (irq>=2)
		    return -ENOENT;
		ix = irq++;
		cp++;
	    }
	    else
		return -EINVAL;

	    if (set_bit(ix,&conf.pc_s.irq))
		return -EEXIST;

	    /* get the IRQ number */
	    if (*cp=='n') {
		cp++;
		d = 0;
	    } else {
		d = simple_strtoul(cp,&cp,16);
	    }
	    if (d>=16)
		return -ERANGE;
	    conf.pc_irq[ix].num = d;

	    /* get the type if present */
	    conf.pc_irq[ix].type = 0xFF;
	    if (cp[0]==':') {
		cp++;
		switch(cp[0]) {
		 case 'l':	conf.pc_irq[ix].type = 1;	break;
		 case 'L':	conf.pc_irq[ix].type = 3;	break;
		 case 'e':	conf.pc_irq[ix].type = 0;	break;
		 case 'E':	conf.pc_irq[ix].type = 2;	break;
		 default:
		    return -EINVAL;
		}
		cp++;
	    }
	    break;

	    /*---------------------------------------------------------------*/
	    /* DMA */
	 case ('d'<<8)+'m':
	    if ((cp[2]|0x20)!='a') return -EINVAL;
	    cp += 3;
	    if (cp[0]>='0' && cp[0]<='1' && cp[1]=='=') {
		ix = *cp - '0';
		cp += 2;
	    }
	    else if (cp[0]=='=') {
		if (dma>=2)
		    return -ENOENT;
		ix = dma++;
		cp++;
	    }
	    else
		return -EINVAL;

	    if (set_bit(ix,&conf.pc_s.dma))
		return -EEXIST;

	    /* get the DMA channel */
	    if (*cp=='n') {
		cp++;
		d = 4;
	    } else {
		d = simple_strtoul(cp,&cp,16);
	    }
	    if (d>=8)
		return -ERANGE;
	    conf.pc_dma[ix].chan = d;
	    break;

	    /*---------------------------------------------------------------*/
	    /* IO */
	 case ('i'<<8)+'o':
	    cp += 2;
	    if (cp[0]>='0' && cp[0]<='7' && cp[1]=='=') {
		ix = *cp - '0';
		cp += 2;
	    }
	    else if (cp[0]=='=') {
		if (io>=8)
		    return -ENOENT;
		ix = io++;
		cp++;
	    }
	    else
		return -EINVAL;

	    if (set_bit(ix,&conf.pc_s.io))
		return -EEXIST;

	    /* get the IO port */
	    if (*cp=='n') {
		cp++;
		d = 0;
	    } else {
		d = simple_strtoul(cp,&cp,16);
	    }
	    if (d>0xFFFF)
		return -ERANGE;
	    conf.pc_io[ix].base = d;
	    break;

	    /*---------------------------------------------------------------*/
	    /* MEM/MEM32 */
	 case ('m'<<8)+'m':
	    cp += 2;
	    if (cp[0]>='0' && cp[0]<='3' && cp[1]=='=') {
		ix = *cp - '0';
		cp += 2;
	    }
	    else if (cp[0]=='=') {
		if (mem>=4)
		    return -ENOENT;
		ix = mem++;
		cp++;
	    }
	    else
		return -EINVAL;

	    if (set_bit(ix,&conf.pc_s.mem))
		return -EEXIST;

	    /* get the base address */
	    if (*cp=='n') {
		cp = 0;
		d = 0;
	    } else {
		d = simple_strtoul(cp,&cp,16);
	    }
	    conf.pc_mem[ix].base = (void*)d;


	    /* get the top address if present */
	    if (cp[0]=='-') {
		cp++;
		d = simple_strtoul(cp,&cp,16);
		conf.pc_mem[ix].top = (void*)d;
	    } else {
		conf.pc_mem[ix].top = (void*)0;
	    }

	    /* and then the size flag if present */
	    conf.pc_mem[ix].type = 0xFF;
	    if (cp[0]==':') {
		cp++;
		switch(cp[0]) {
		 case 'b':	conf.pc_mem[ix].type = 0;	break;
		 case 'w':	conf.pc_mem[ix].type = 1;	break;
		 case 'l':	conf.pc_mem[ix].type = 3;	break;
		 default:
		    return -EINVAL;
		}
		cp++;
	    }
	    break;

	    /*---------------------------------------------------------------*/
	    /* see if we should ignore the kernel's opinion */
	 case ('f'<<8)+'o':
	    cp += 2;
	    if ((*(cp++)|0x20)=='r' &&
		(*(cp++)|0x20)=='c' &&
		(*(cp++)|0x20)=='e'
		) {
		conf.pc_force = 1;
		break;
	    }
	    return -EINVAL;

	 default:
	    return -EINVAL;

	} /* end switch on arg key */

	/* must be a space or NUL next */
	if (cp[0] && cp[0]!=' ')
	    return -EINVAL;
    } /* end while parse */

    /*=======================================================================*/
    return pnp_try_reconfigure(device,&conf);

} /* end pnpconf_reconf() */

/*****************************************************************************/
static int proc_pnpconf_ioctl(struct inode *inode, struct file *file,
			      unsigned int cmd, unsigned long data)
{
    pnp_device *device;
    pnpioctl *pioc;
    int rt;

    /* make sure the structure is readable for now */
    rt = verify_area(VERIFY_READ,(void*)data,sizeof(pnpioctl));
    if (rt)
	return rt;

    /* copy the whole thing into kernel memory */
    pioc = (pnpioctl *) kmalloc(sizeof(*pioc),GFP_KERNEL);
    if (!pioc)
	return -ENOMEM;
    memcpy_fromfs(pioc,(const void*)data,sizeof(*pioc));

    /* see if we'll need to copy data back */
    switch (pioc->pioc_req) {
     case PNPREQ_QUERY:
     case PNPREQ_READ:
	/* need to return data */
	rt = verify_area(VERIFY_WRITE,(void*)data,sizeof(pnpioctl));
	if (rt) {
	    kfree(pioc);
	    return rt;
	}
	break;

     case PNPREQ_SET:
     case PNPREQ_ACTIVATE:
     case PNPREQ_DEACTIVATE:
	break;

     default:
	kfree(pioc);
	return -EINVAL;
    }

    /* search for the device indicated */
    for (device=pnp_device_list; device; device=device->pd_next)
	if (device->pd_dev==pioc->pioc_dev &&
	    device->pd_card->pc_csn==pioc->pioc_csn)
	    break;
    if (!device) {
	kfree(pioc);
	return -ENODEV;
    }

    /* do the requested deed */
    switch (pioc->pioc_req) {
     case PNPREQ_QUERY:
	rt = pnp_get_res_dep_block(device,pioc->pioc_dep,&pioc->pioc.poss);
	memcpy_tofs((void*)data,pioc,sizeof(*pioc));
	break;

     case PNPREQ_READ:
	rt = device->pd_card->pc_interface->pi_get_config(device,
							  &pioc->pioc.config);
	memcpy_tofs((void*)data,pioc,sizeof(*pioc));
	break;

     case PNPREQ_SET:
	rt = pnp_try_reconfigure(device,&pioc->pioc.config);
	break;

     case PNPREQ_ACTIVATE:
	pnp_drv_activate(device);
	rt = 0;
	break;

     case PNPREQ_DEACTIVATE:
	pnp_drv_deactivate(device);
	rt = 0;
	break;

     default:
	rt = ENOSYS;
	break;
    }

    kfree(pioc);
    return rt;

} /* end proc_pnpconf_ioctl() */
