/* dump.c: PnP BIOS system /proc interface */
/*
 * $Header: /root/pnp/module/RCS/proc_ix.c,v 1.3 1996/06/12 18:56:34 root Exp $
 *
 * $Log: proc_ix.c,v $
 * Revision 1.3  1996/06/12  18:56:34  root
 * standard internal i/f & BIOS read/query
 *
 * Revision 1.2  1996/06/04  21:20:48  root
 * EISA ID order normalized
 *
 * Revision 1.1  1996/05/28  20:37:55  root
 * Read-only index version works
 *
 *
 */

/*
 * (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"

static void pnp_get_ansi_id(pnp_card *, pnp_device *, char *,int);

/*****************************************************************************/
static int pnp_list_get_info(char *, char **, off_t, int, int);

struct proc_dir_entry proc_dir_pnp_list = {
    0,  3, "pnp",
    S_IFREG | S_IRUGO, 1, 0, 0,
    0, &proc_net_inode_operations,
    pnp_list_get_info
};

/*****************************************************************************/
/* write out a list of modules and drivers on /proc/net/streams
 *
 * format of the entries:
SRC. CSN-DV IDENT.. DRIVER.. DATA..............................................
BIOS     00 TBA0400 kernel   type=07,01,00 attr=0180
CARD  01    CTL0028 -        Creative SB16 PnP
ISA   01-01 CTL0031 SB16     Audio
ISA   01-02 CTL2011 sbpcd    IDE
ISA   01-03 PNPffff none     Reserved
ISA   01-04 CTL7001 none     Game
 *
 */
static const char procbanner[] = {
    "Linux Plug'n'Play subsystem (c) D.W.Howells 1996\n"
	"SRC  CSN-DV IDENT   DRIVER   "
	    "DATA\n"
};
#define procformat_len 80
#define procformat_data 50

/*****************************************************************************/
static
int pnp_list_get_info(char *page, char **start, off_t pos, int room, int rdwr)
{
    pnp_card *card;
    pnp_device *device;
    char *cp = page, *end = page+room, data[51];
    int tmp;

    *start = NULL;

    /* do we need to show the banner again? */
    if (pos<sizeof(procbanner)-1) {
	strcpy(cp,procbanner);
	*start = cp + pos;
	cp += sizeof(procbanner)-1;
	pos = 0;
    } else {
	pos -= (sizeof(procbanner)-1);
    }

    /* see if we can return now */
    if (cp>=end)
	return end-*start;

    /* work out how many entries to skip */
    tmp = pos / procformat_len;
    pos %= procformat_len;

    if (!*start) {
	*start = page + pos;
    }

    /*=======================================================================*/
    /* skip shown cards */
    for (card=pnp_card_list; card && tmp>0; card=card->pc_next)
	    tmp--;

    /* add unshown cards to the buffer */
    for (; card; card=card->pc_next) {
	u_short id =
	    ((u_char*)&card->pc_id)[0]<<8 | ((u_char*)&card->pc_id)[1];
	pnp_get_ansi_id(card,NULL,data,50);
	cp += sprintf(cp,
		      "CARD  %02x    %c%c%c%02x%02x pnp      %-50.50s\n",
		      card->pc_csn,
		      ((id>>10)&0x1F)+'A'-1,
		      ((id>>5)&0x1F)+'A'-1,
		      ((id>>0)&0x1F)+'A'-1,
		      ((u_char*)&card->pc_id)[2],
		      ((u_char*)&card->pc_id)[3],
		      data
		      );
	if (cp>=end)
	    return end-*start;
    }
    
    /*=======================================================================*/
    /* skip shown devices */
    for (device=pnp_device_list; device && tmp>0; device=device->pd_next)
	    tmp--;

    /* add unshown devices to the buffer */
    for (; device; device=device->pd_next) {
	u_short id =
	    ((u_char*)&device->pd_id)[0]<<8 | ((u_char*)&device->pd_id)[1];
	if (device->pd_card->pc_interface->pi_comment)
	    device->pd_card->pc_interface->pi_comment(device,data,50);
	else
	    data[0] = '\0';
	pnp_get_ansi_id(device->pd_card,device,
			data+strlen(data), 50-strlen(data));
	cp += sprintf(cp,
		      "%-4.4s  %02x-%02x %c%c%c%02x%02x %-8.8s %-50.50s\n",
		      device->pd_card->pc_interface->pi_ident,
		      device->pd_card->pc_csn, device->pd_dev,
		      ((id>>10)&0x1F)+'A'-1,
		      ((id>>5)&0x1F)+'A'-1,
		      ((id>>0)&0x1F)+'A'-1,
		      ((u_char*)&device->pd_id)[2],
		      ((u_char*)&device->pd_id)[3],
		      device->pd_driver ?
		      device->pd_driver->pdr_name : "none",
		      data
		      );
	if (cp>=end)
	    return end-*start;
    }
    
    return cp>*start ? cp-*start : 0;
} /* end pnp_list_get_info() */

/*****************************************************************************/
/* look for an ANSI_ID tag before any logical devices */
static void pnp_get_ansi_id(pnp_card *card, pnp_device *device,
			    char *data, int size)
{
    u_char *ptr;
    int loop;

    if (size<0)
	return;

    /* treat the PnP BIOS card specially */
    if (card->pc_csn==0 && !device) {
	sprintf(data,"%.*s",size,"PnP BIOS");
	return;
    }

    data[0] = '\0';
    if (!card->pc_resources)
	return;

    ptr = card->pc_resources;
    if (device) {
	ptr += device->pd_resoffset;
	ptr += (*ptr&0x07) + 1;		/* begins with logdev desc */
    }

    /* read from the resource list till the end tag is found */
    while (1) {
	/* stop when the end tag or a logial device ID is found */
	if ((*ptr&0x78)==(PNPSTAG_END<<3) ||
	    (*ptr&0x78)==(PNPSTAG_LOGICAL_DEV_ID<<3)
	    )
	    break;

	/* if found an ansi ID string, place in buffer and return */
	if ((*ptr&0x80) && (*ptr&0x7F)==PNPLTAG_ANSI_ID) {
	    ptr++;
	    loop = *(u_short*)ptr;
	    ptr += 2;
	    if (loop>size) loop = size;
	    strncpy(data,ptr,loop);
	    data[loop] = '\0';
	    return;
	}

	/* skip large descriptors */
	if (*ptr&0x80) {
	    ptr++;
	    ptr += *(u_short*)ptr + 2;
	    continue;
	}

	/* skip small descriptors */
	ptr += (*ptr&0x07) + 1;
    }

} /* end pnp_get_ansi_id() */
