/* $Id: parport_probe.c,v 1.1 1999/07/03 08:56:17 davem Exp $
 * Parallel port device probing code
 *
 * Authors:    Carsten Gross, carsten@sol.wohnheim.uni-ulm.de
 *             Philip Blundell <Philip.Blundell@pobox.com>
 */

#include <linux/parport.h>
#include <linux/ctype.h>
#include <asm/uaccess.h>

static struct {
	char *token;
	char *descr;
} classes[] = {
	{ "",            "Legacy device" },
	{ "PRINTER",     "Printer" },
	{ "MODEM",       "Modem" },
	{ "NET",         "Network device" },
	{ "HDC",       	 "Hard disk" },
	{ "PCMCIA",      "PCMCIA" },
	{ "MEDIA",       "Multimedia device" },
	{ "FDC",         "Floppy disk" },
	{ "PORTS",       "Ports" },
	{ "SCANNER",     "Scanner" },
	{ "DIGICAM",     "Digital camera" },
	{ "",            "Unknown device" },
	{ "",            "Unspecified" },
	{ "SCSIADAPTER", "SCSI adapter" },
	{ NULL,          NULL }
};

static void pretty_print(struct parport *port, int device)
{
	struct parport_device_info *info = &port->probe_info[device + 1];

	printk(KERN_INFO "%s", port->name);

	if (device >= 0)
		printk (" (addr %d)", device);

	printk (": %s", classes[info->class].descr);
	if (info->class)
		printk(", %s %s", info->mfr, info->model);

	printk("\n");
}

static char *strdup(char *str)
{
	int n = strlen(str)+1;
	char *s = kmalloc(n, GFP_KERNEL);
	if (!s) return NULL;
	return strcpy(s, str);
}

static void parse_data(struct parport *port, int device, char *str)
{
	char *txt = kmalloc(strlen(str)+1, GFP_KERNEL);
	char *p = txt, *q;
	int guessed_class = PARPORT_CLASS_UNSPEC;
	struct parport_device_info *info = &port->probe_info[device + 1];

	if (!txt) {
		printk("%s probe: memory squeeze\n", port->name);
		return;
	}
	strcpy(txt, str);
	while (p) {
		char *sep;
		q = strchr(p, ';');
		if (q) *q = 0;
		sep = strchr(p, ':');
		if (sep) {
			char *u = p;
			*(sep++) = 0;
			while (*u) {
				*u = toupper(*u);
				u++;
			}
			if (!strcmp(p, "MFG") || !strcmp(p, "MANUFACTURER")) {
				if (info->mfr)
					kfree (info->mfr);
				info->mfr = strdup(sep);
			} else if (!strcmp(p, "MDL") || !strcmp(p, "MODEL")) {
				if (info->model)
					kfree (info->model);
				info->model = strdup(sep);
			} else if (!strcmp(p, "CLS") || !strcmp(p, "CLASS")) {
				int i;
				if (info->class_name)
					kfree (info->class_name);
				info->class_name = strdup(sep);
				for (u = sep; *u; u++)
					*u = toupper(*u);
				for (i = 0; classes[i].token; i++) {
					if (!strcmp(classes[i].token, sep)) {
						info->class = i;
						goto rock_on;
					}
				}
				printk(KERN_WARNING "%s probe: warning, class '%s' not understood.\n", port->name, sep);
				info->class = PARPORT_CLASS_OTHER;
			} else if (!strcmp(p, "CMD") ||
				   !strcmp(p, "COMMAND SET")) {
				if (info->cmdset)
					kfree (info->cmdset);
				info->cmdset = strdup(sep);
				/* if it speaks printer language, it's
				   probably a printer */
				if (strstr(sep, "PJL") || strstr(sep, "PCL"))
					guessed_class = PARPORT_CLASS_PRINTER;
			} else if (!strcmp(p, "DES") || !strcmp(p, "DESCRIPTION")) {
				if (info->description)
					kfree (info->description);
				info->description = strdup(sep);
			}
		}
	rock_on:
		if (q) p = q+1; else p=NULL;
	}

	/* If the device didn't tell us its class, maybe we have managed to
	   guess one from the things it did say. */
	if (info->class == PARPORT_CLASS_UNSPEC)
		info->class = guessed_class;

	pretty_print (port, device);

	kfree(txt);
}

/* Get Std 1284 Device ID. */
ssize_t parport_device_id (int devnum, char *buffer, size_t len)
{
	ssize_t retval = -ENXIO;
	struct pardevice *dev = parport_open (devnum, "Device ID probe",
					      NULL, NULL, NULL, 0, NULL);
	if (!dev)
		return -ENXIO;

	parport_claim_or_block (dev);

	/* Negotiate to compatibility mode, and then to device ID mode.
	 * (This is in case we are already in device ID mode.) */
	parport_negotiate (dev->port, IEEE1284_MODE_COMPAT);
	retval = parport_negotiate (dev->port,
				    IEEE1284_MODE_NIBBLE | IEEE1284_DEVICEID);

	if (!retval) {
		int idlen;
		unsigned char length[2];
		mm_segment_t oldfs = get_fs ();
		set_fs (get_ds ());

		/* First two bytes are MSB,LSB of inclusive length. */
		retval = parport_read (dev->port, length, 2);

		if (retval != 2) goto restore_fs;

		idlen = (length[0] << 8) + length[1] - 2;
		if (idlen < len)
			len = idlen;
		retval = parport_read (dev->port, buffer, len);

		if (retval != len)
			printk (KERN_DEBUG "%s: only read %Zd of %Zd ID bytes\n",
				dev->port->name, retval,
				len);

		/* Some printer manufacturers mistakenly believe that
                   the length field is supposed to be _exclusive_. */
		/* In addition, there are broken devices out there
                   that don't even finish off with a semi-colon. */
		if (buffer[len - 1] != ';') {
			ssize_t diff;
			diff = parport_read (dev->port, buffer + len, 2);
			retval += diff;

			if (diff)
				printk (KERN_DEBUG
					"%s: device reported incorrect "
					"length field (%d, should be %Zd)\n",
					dev->port->name, idlen, retval);
			else {
				/* One semi-colon short of a device ID. */
				buffer[len++] = ';';
				printk (KERN_DEBUG "%s: faking semi-colon\n",
					dev->port->name);

				/* If we get here, I don't think we
                                   need to worry about the possible
                                   standard violation of having read
                                   more than we were told to.  The
                                   device is non-compliant anyhow. */
			}
		}

	restore_fs:
		buffer[len] = '\0';
		set_fs (oldfs);
		parport_negotiate (dev->port, IEEE1284_MODE_COMPAT);
	}
	parport_release (dev);

	if (retval > 2)
		parse_data (dev->port, dev->daisy, buffer);

	parport_close (dev);
	return retval;
}
