#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include "depmod.h"
#include "tables.h"
#include "util.h"

/* Turn /lib/modules/2.5.49/kernel/foo.ko(.gz) => foo */
static void make_shortname(char *dest, const char *src)
{
	char *ext;
	const char *bname;

	bname = my_basename(src);
	strcpy(dest, bname);
	ext = strchr(dest, '.');
	if (ext)
		*ext = '\0';
}

/* We set driver_data to zero */
static void output_pci_entry(struct pci_device_id *pci, char *name, FILE *out,
			     int conv)
{
	fprintf(out,
		"%-20s 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x 0x0\n",
		name,
		END(pci->vendor, conv),
		END(pci->device, conv),
		END(pci->subvendor, conv),
		END(pci->subdevice, conv),
		END(pci->class, conv),
		END(pci->class_mask, conv));
}

int output_pci_table(struct module *modules, FILE *out, char *dirname)
{
	struct module *i;

	fprintf(out, "# pci module         vendor     device     subvendor"
		"  subdevice  class      class_mask driver_data\n");

	for (i = modules; i; i = i->next) {
		struct pci_device_id *e;
		char shortname[strlen(i->pathname) + 1];
		struct module_tables *t = &i->tables;

		if (!t->pci_table)
			continue;

		make_shortname(shortname, i->pathname);
		for (e = t->pci_table; e->vendor; e = (void *)e + t->pci_size)
			output_pci_entry(e, shortname, out, i->file->conv);
	}
	return 1;
}

/* We set driver_info to zero */
static void output_usb_entry(struct usb_device_id *usb, char *name, FILE *out,
			     int conv)
{
	fprintf(out, "%-20s 0x%04x      0x%04x   0x%04x    0x%04x"
		"       0x%04x       0x%02x         0x%02x"
		"            0x%02x            0x%02x"
		"            0x%02x               0x%02x"
		"               0x0\n",
		name,
		END(usb->match_flags, conv),
		END(usb->idVendor, conv),
		END(usb->idProduct, conv),
		END(usb->bcdDevice_lo, conv),
		END(usb->bcdDevice_hi, conv),
		END(usb->bDeviceClass, conv),
		END(usb->bDeviceSubClass, conv),
		END(usb->bDeviceProtocol, conv),
		END(usb->bInterfaceClass, conv),
		END(usb->bInterfaceSubClass, conv),
		END(usb->bInterfaceProtocol, conv));
}

int output_usb_table(struct module *modules, FILE *out, char *dirname)
{
	struct module *i;

	fprintf(out, "# usb module         ");
	/* Requires all users to be on kernel 2.4.0 or later */
	fprintf(out, "match_flags ");
	fprintf(out, "idVendor idProduct bcdDevice_lo bcdDevice_hi"
		" bDeviceClass bDeviceSubClass bDeviceProtocol"
		" bInterfaceClass bInterfaceSubClass"
		" bInterfaceProtocol driver_info\n");

	for (i = modules; i; i = i->next) {
		struct usb_device_id *e;
		char shortname[strlen(i->pathname) + 1];
		struct module_tables *t = &i->tables;

		if (!t->usb_table)
			continue;

		make_shortname(shortname, i->pathname);
		for (e = t->usb_table;
		     e->idVendor || e->bDeviceClass || e->bInterfaceClass;
		     e = (void *)e + t->usb_size)
			output_usb_entry(e, shortname, out, i->file->conv);
	}
	return 1;
}

static void output_ieee1394_entry(struct ieee1394_device_id *fw, char *name,
				  FILE *out, int conv)
{
	fprintf(out, "%-20s 0x%08x  0x%06x  0x%06x 0x%06x     0x%06x\n",
		name,
		END(fw->match_flags, conv),
		END(fw->vendor_id, conv),
		END(fw->model_id, conv),
		END(fw->specifier_id, conv),
		END(fw->version, conv));
}

int output_ieee1394_table(struct module *modules, FILE *out, char *dirname)
{
	struct module *i;

	fprintf(out, "# ieee1394 module    ");
	fprintf(out, "match_flags vendor_id model_id specifier_id version\n");

	for (i = modules; i; i = i->next) {
		struct ieee1394_device_id *fw;
		char shortname[strlen(i->pathname) + 1];
		struct module_tables *t = &i->tables;

		if (!t->ieee1394_table)
			continue;

		make_shortname(shortname, i->pathname);
		for (fw = t->ieee1394_table; fw->match_flags;
		     fw = (void *) fw + t->ieee1394_size)
			output_ieee1394_entry(fw, shortname, out, i->file->conv);
	}
	return 1;
}


/* We set driver_data to zero */
static void output_ccw_entry(struct ccw_device_id *ccw, char *name, FILE *out,
			     int conv)
{
	fprintf(out, "%-20s 0x%04x      0x%04x  0x%02x      0x%04x  0x%02x\n",
		name, END(ccw->match_flags, conv),
		END(ccw->cu_type, conv),  END(ccw->cu_model, conv),
		END(ccw->dev_type, conv), END(ccw->dev_model, conv));
}

int output_ccw_table(struct module *modules, FILE *out, char *dirname)
{
	struct module *i;

	fprintf(out, "# ccw module         ");
	fprintf(out, "match_flags cu_type cu_model dev_type dev_model\n");

	for (i = modules; i; i = i->next) {
		struct ccw_device_id *e;
		char shortname[strlen(i->pathname) + 1];
		struct module_tables *t = &i->tables;

		if (!t->ccw_table)
			continue;

		make_shortname(shortname, i->pathname);
		for (e = t->ccw_table;
		     e->cu_type || e->cu_model || e->dev_type || e->dev_model;
		     e = (void *) e + t->ccw_size)
			output_ccw_entry(e, shortname, out, i->file->conv);
	}
	return 1;
}

#define ISAPNP_VENDOR(a,b,c)	(((((a)-'A'+1)&0x3f)<<2)|\
				((((b)-'A'+1)&0x18)>>3)|((((b)-'A'+1)&7)<<13)|\
				((((c)-'A'+1)&0x1f)<<8))
#define ISAPNP_DEVICE(x)	((((x)&0xf000)>>8)|\
				 (((x)&0x0f00)>>8)|\
				 (((x)&0x00f0)<<8)|\
				 (((x)&0x000f)<<8))

static void put_isapnp_id(FILE *out, const char *id)
{
	unsigned short vendor, device;

	vendor = ISAPNP_VENDOR(id[0], id[1], id[2]);
	device = (unsigned short)strtol(&id[3], NULL, 16);
	device = ISAPNP_DEVICE(device);
	fprintf(out, " 0x%04x     0x%04x    ", vendor, device);
}

int output_isapnp_table(struct module *modules, FILE *out, char *dirname)
{
	struct module *i;

	fprintf(out, "# isapnp module      ");
	fprintf(out, "cardvendor carddevice driver_data vendor     function   ...\n");

	for (i = modules; i; i = i->next) {
		char shortname[strlen(i->pathname) + 1];
		struct module_tables *t = &i->tables;

		if (t->pnp_table) {
			struct pnp_device_id *id;
			make_shortname(shortname, i->pathname);
			for (id = t->pnp_table;
			     id->id[0];
			     id = (void *)id + t->pnp_size) {
				fprintf(out, "%-20s", shortname);
				fprintf(out, " 0xffff     0xffff    ");
				fprintf(out, " 0x00000000 "); /* driver_data */
				put_isapnp_id(out, id->id);
				fprintf(out, "\n");
			}
		}
		if (t->pnp_card_table) {
			void *id;
			make_shortname(shortname, i->pathname);
			for (id = t->pnp_card_table;
			     ((char *)id)[0];
			     id += t->pnp_card_size) {
				int idx;
				struct pnp_card_devid *devid
					= id + t->pnp_card_offset;

				fprintf(out, "%-20s", shortname);
				put_isapnp_id(out, id);
				fprintf(out, " 0x00000000 "); /* driver_data */
				for (idx = 0; idx < 8; idx++) {
					if (!devid->devid[idx][0])
						break;
					put_isapnp_id(out, devid->devid[idx]);
				}
				fprintf(out, "\n");
			}
		}
	}
	return 1;
}

#define MATCH_bustype   1
#define MATCH_vendor    2
#define MATCH_product   4
#define MATCH_version   8

#define MATCH_evbit     0x010
#define MATCH_keybit    0x020
#define MATCH_relbit    0x040
#define MATCH_absbit    0x080
#define MATCH_mscbit    0x100
#define MATCH_ledbit    0x200
#define MATCH_sndbit    0x400
#define MATCH_ffbit     0x800
#define MATCH_swbit     0x1000

#define MATCH(x) (END(input->match_flags, conv) & MATCH_ ## x)
#define PRINT_SCALAR(n) fprintf(out, "  0x%lx", MATCH(n) ? END(input->n, conv) : 0l)
#define PRINT_ARRAY64(n) do {						    \
	fprintf(out, "  ");						    \
	if (MATCH(n))							    \
		output_input_bits_64(out, input->n, sizeof(input->n), conv); \
	else								    \
		fprintf(out, "%d", 0);					    \
	} while (0)

#define PRINT_ARRAY32(n) do {						    \
	fprintf(out, "  ");						    \
	if (MATCH(n))							    \
		output_input_bits_32(out, input->n, sizeof(input->n), conv); \
	else								    \
		fprintf(out, "%d", 0);					    \
	} while (0)

static void output_input_bits_32(FILE *out, unsigned int *bits, int size,
				 int conv)
{
	int i, j;

	size /= sizeof(*bits);
	for (i = size - 1; i >= 0; i--)
		 if (END(bits[i], conv))
			 break;
	if (i < 0)
		i = 0;
	fprintf(out, "%x", END(bits[i], conv));
	for (j = i - 1; j >= 0; j--)
		fprintf(out, ":%x", END(bits[j], conv));
}

static void output_input_bits_64(FILE *out, unsigned long long *bits, int size,
				 int conv)
{
	int i, j;

	size /= sizeof(*bits);
	for (i = size - 1; i >= 0; i--)
		 if (END(bits[i], conv))
			 break;
	if (i < 0)
		i = 0;
	fprintf(out, "%llx", END(bits[i], conv));
	for (j = i - 1; j >= 0; j--)
		fprintf(out, ":%llx", END(bits[j], conv));
}

/* Formats are too different to */
static int output_input_entry_32(struct input_device_id_32 *input,
				 char *name, FILE *out, int conv)
{
	if (!input->match_flags && !input->driver_info)
		return 1;

	fprintf(out, "%-20s0x%x", name, END(input->match_flags, conv));

	PRINT_SCALAR(bustype);
	PRINT_SCALAR(vendor);
	PRINT_SCALAR(product);
	PRINT_SCALAR(version);

	PRINT_ARRAY32(evbit);
	PRINT_ARRAY32(keybit);
	PRINT_ARRAY32(relbit);
	PRINT_ARRAY32(absbit);
	PRINT_ARRAY32(mscbit);
	PRINT_ARRAY32(ledbit);
	PRINT_ARRAY32(sndbit);
	PRINT_ARRAY32(ffbit);
	PRINT_ARRAY32(swbit);

	fprintf(out, "  0x%x\n", END(input->driver_info, conv));
	return 0;
}

static int output_input_entry_32_old(struct input_device_id_old_32 *input,
				     char *name, FILE *out, int conv)
{
	if (!input->match_flags && !input->driver_info)
		return 1;

	fprintf(out, "%-20s0x%x", name, END(input->match_flags, conv));

	PRINT_SCALAR(bustype);
	PRINT_SCALAR(vendor);
	PRINT_SCALAR(product);
	PRINT_SCALAR(version);

	PRINT_ARRAY32(evbit);
	PRINT_ARRAY32(keybit);
	PRINT_ARRAY32(relbit);
	PRINT_ARRAY32(absbit);
	PRINT_ARRAY32(mscbit);
	PRINT_ARRAY32(ledbit);
	PRINT_ARRAY32(sndbit);
	PRINT_ARRAY32(ffbit);

	fprintf(out, "  0x%x\n", END(input->driver_info, conv));
	return 0;
}

static int output_input_entry_64(struct input_device_id_64 *input,
				 char *name, FILE *out, int conv)
{
	if (!input->match_flags && !input->driver_info)
		return 1;

	fprintf(out, "%-20s0x%llx", name, END(input->match_flags, conv));

	PRINT_SCALAR(bustype);
	PRINT_SCALAR(vendor);
	PRINT_SCALAR(product);
	PRINT_SCALAR(version);

	PRINT_ARRAY64(evbit);
	PRINT_ARRAY64(keybit);
	PRINT_ARRAY64(relbit);
	PRINT_ARRAY64(absbit);
	PRINT_ARRAY64(mscbit);
	PRINT_ARRAY64(ledbit);
	PRINT_ARRAY64(sndbit);
	PRINT_ARRAY64(ffbit);
	PRINT_ARRAY64(swbit);

	fprintf(out, "  0x%llx\n", END(input->driver_info, conv));
	return 0;
}

static int output_input_entry_64_old(struct input_device_id_old_64 *input,
				     char *name, FILE *out, int conv)
{
	if (!input->match_flags && !input->driver_info)
		return 1;

	fprintf(out, "%-20s0x%llx", name, END(input->match_flags, conv));

	PRINT_SCALAR(bustype);
	PRINT_SCALAR(vendor);
	PRINT_SCALAR(product);
	PRINT_SCALAR(version);

	PRINT_ARRAY64(evbit);
	PRINT_ARRAY64(keybit);
	PRINT_ARRAY64(relbit);
	PRINT_ARRAY64(absbit);
	PRINT_ARRAY64(mscbit);
	PRINT_ARRAY64(ledbit);
	PRINT_ARRAY64(sndbit);
	PRINT_ARRAY64(ffbit);

	fprintf(out, "  0x%llx\n", END(input->driver_info, conv));
	return 0;
}

int output_input_table(struct module *modules, FILE *out, char *dirname)
{
	struct module *i;

	fprintf(out, "# module         matchBits");
	fprintf(out, " bustype vendor product version evBits keyBits relBits");
	fprintf(out, " absBits mscBits ledBits sndBits ffBits [swBits] driver_info\n");

	for (i = modules; i; i = i->next) {
		void *p;
		char shortname[strlen(i->pathname) + 1];
		int done = 0;
		struct module_tables *t = &i->tables;
		int conv = i->file->conv;

		if (!t->input_table)
			continue;

		make_shortname(shortname, i->pathname);
		/* Guess what size it really is, based on size of
		 * whole table.  Table changed in 2.6.14.  This is a hack. */
		if (t->input_size == sizeof(struct input_device_id_old_64)) {
			if ((t->input_table_size % t->input_size) != 0) {
				t->input_size
					= sizeof(struct input_device_id_64);
			}
		} else {
			if ((t->input_table_size % t->input_size) != 0) {
				t->input_size
					= sizeof(struct input_device_id_32);
			}
		}

		for (p = t->input_table; !done; p += t->input_size) {
			switch (t->input_size) {
			case sizeof(struct input_device_id_old_64):
				done = output_input_entry_64_old(p,
								 shortname,
								 out, conv);
				break;
			case sizeof(struct input_device_id_64):
				done = output_input_entry_64(p, shortname,
							     out, conv);
				break;
			case sizeof(struct input_device_id_old_32):
				done = output_input_entry_32_old(p,
								 shortname,
								 out, conv);
				break;
			case sizeof(struct input_device_id_32):
				done = output_input_entry_32(p, shortname,
							     out, conv);
				break;
			}
		}				
	}
	return 1;
}

static void output_serio_entry(struct serio_device_id *serio, char *name, FILE *out)
{
	fprintf(out,
		"%-20s 0x%02x 0x%02x  0x%02x 0x%02x\n",
		name,
		serio->type,
		serio->extra,
		serio->id,
		serio->proto);
}


int output_serio_table(struct module *modules, FILE *out, char *dirname)
{
	struct module *i;

	fprintf(out, "# serio module       type extra id   proto\n");

	for (i = modules; i; i = i->next) {
		struct serio_device_id *e;
		char shortname[strlen(i->pathname) + 1];
		struct module_tables *t = &i->tables;

		if (!t->serio_table)
			continue;

		make_shortname(shortname, i->pathname);
		for (e = t->serio_table; e->type || e->proto; e = (void *)e + t->serio_size)
			output_serio_entry(e, shortname, out);
	}
	return 1;
}


static void
strip_whitespace (char *str, char chr)
{
	int i;
	if (!str)
		return;
	for (i = strlen (str); i >= 0; --i)
		if (isspace (*str))
			*str = chr;
}

/* We set driver_data to zero */
static void output_of_entry(struct of_device_id *dev, char *name, FILE *out)
{
	char *ofname = NULL, *type = NULL, *compatible = NULL;
	if (dev->name[0]) {
		ofname = strdup (dev->name);
		strip_whitespace (ofname, '_');
	}

	if (dev->type[0]) {
		type = strdup (dev->type);
		strip_whitespace (type, '_');
       }

	if (dev->compatible[0]) {
		compatible = strdup (dev->compatible);
		strip_whitespace (compatible, '_');
	}

        fprintf (out, "%-20s %-20s %-20s %s\n",
                name, ofname ? ofname : "*", type ? type : "*",
                compatible ? compatible : "*");
	
	free(ofname);
	free(type);
	free(compatible);
}

int output_of_table(struct module *modules, FILE *out, char *dirname)
{
	struct module *i;

        fprintf (out, "# of module          name                 type                 compatible\n");                                 
	for (i = modules; i; i = i->next) {
		struct of_device_id *e;
		char shortname[strlen(i->pathname) + 1];
		struct module_tables *t = &i->tables;

		if (!t->of_table)
			continue;

		make_shortname(shortname, i->pathname);
		for (e = t->of_table; e->name[0]|e->type[0]|e->compatible[0];
                     e = (void *)e + t->of_size)
			output_of_entry(e, shortname, out);
	}
	return 1;
}
