xref: /kernel/linux/linux-6.6/drivers/parport/probe.c (revision 62306a36)
162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Parallel port device probing code
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Authors:    Carsten Gross, carsten@sol.wohnheim.uni-ulm.de
662306a36Sopenharmony_ci *             Philip Blundell <philb@gnu.org>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/module.h>
1062306a36Sopenharmony_ci#include <linux/parport.h>
1162306a36Sopenharmony_ci#include <linux/string.h>
1262306a36Sopenharmony_ci#include <linux/string_helpers.h>
1362306a36Sopenharmony_ci#include <linux/slab.h>
1462306a36Sopenharmony_ci#include <linux/uaccess.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_cistatic const struct {
1762306a36Sopenharmony_ci	const char *token;
1862306a36Sopenharmony_ci	const char *descr;
1962306a36Sopenharmony_ci} classes[] = {
2062306a36Sopenharmony_ci	{ "",            "Legacy device" },
2162306a36Sopenharmony_ci	{ "PRINTER",     "Printer" },
2262306a36Sopenharmony_ci	{ "MODEM",       "Modem" },
2362306a36Sopenharmony_ci	{ "NET",         "Network device" },
2462306a36Sopenharmony_ci	{ "HDC",       	 "Hard disk" },
2562306a36Sopenharmony_ci	{ "PCMCIA",      "PCMCIA" },
2662306a36Sopenharmony_ci	{ "MEDIA",       "Multimedia device" },
2762306a36Sopenharmony_ci	{ "FDC",         "Floppy disk" },
2862306a36Sopenharmony_ci	{ "PORTS",       "Ports" },
2962306a36Sopenharmony_ci	{ "SCANNER",     "Scanner" },
3062306a36Sopenharmony_ci	{ "DIGICAM",     "Digital camera" },
3162306a36Sopenharmony_ci	{ "",            "Unknown device" },
3262306a36Sopenharmony_ci	{ "",            "Unspecified" },
3362306a36Sopenharmony_ci	{ "SCSIADAPTER", "SCSI adapter" },
3462306a36Sopenharmony_ci	{ NULL,          NULL }
3562306a36Sopenharmony_ci};
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_cistatic void pretty_print(struct parport *port, int device)
3862306a36Sopenharmony_ci{
3962306a36Sopenharmony_ci	struct parport_device_info *info = &port->probe_info[device + 1];
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	pr_info("%s", port->name);
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	if (device >= 0)
4462306a36Sopenharmony_ci		pr_cont(" (addr %d)", device);
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	pr_cont(": %s", classes[info->class].descr);
4762306a36Sopenharmony_ci	if (info->class)
4862306a36Sopenharmony_ci		pr_cont(", %s %s", info->mfr, info->model);
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	pr_cont("\n");
5162306a36Sopenharmony_ci}
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_cistatic void parse_data(struct parport *port, int device, char *str)
5462306a36Sopenharmony_ci{
5562306a36Sopenharmony_ci	char *txt = kmalloc(strlen(str)+1, GFP_KERNEL);
5662306a36Sopenharmony_ci	char *p = txt, *q;
5762306a36Sopenharmony_ci	int guessed_class = PARPORT_CLASS_UNSPEC;
5862306a36Sopenharmony_ci	struct parport_device_info *info = &port->probe_info[device + 1];
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	if (!txt) {
6162306a36Sopenharmony_ci		pr_warn("%s probe: memory squeeze\n", port->name);
6262306a36Sopenharmony_ci		return;
6362306a36Sopenharmony_ci	}
6462306a36Sopenharmony_ci	strcpy(txt, str);
6562306a36Sopenharmony_ci	while (p) {
6662306a36Sopenharmony_ci		char *sep;
6762306a36Sopenharmony_ci		q = strchr(p, ';');
6862306a36Sopenharmony_ci		if (q) *q = 0;
6962306a36Sopenharmony_ci		sep = strchr(p, ':');
7062306a36Sopenharmony_ci		if (sep) {
7162306a36Sopenharmony_ci			char *u;
7262306a36Sopenharmony_ci			*(sep++) = 0;
7362306a36Sopenharmony_ci			/* Get rid of trailing blanks */
7462306a36Sopenharmony_ci			u = sep + strlen (sep) - 1;
7562306a36Sopenharmony_ci			while (u >= p && *u == ' ')
7662306a36Sopenharmony_ci				*u-- = '\0';
7762306a36Sopenharmony_ci			string_upper(p, p);
7862306a36Sopenharmony_ci			if (!strcmp(p, "MFG") || !strcmp(p, "MANUFACTURER")) {
7962306a36Sopenharmony_ci				kfree(info->mfr);
8062306a36Sopenharmony_ci				info->mfr = kstrdup(sep, GFP_KERNEL);
8162306a36Sopenharmony_ci			} else if (!strcmp(p, "MDL") || !strcmp(p, "MODEL")) {
8262306a36Sopenharmony_ci				kfree(info->model);
8362306a36Sopenharmony_ci				info->model = kstrdup(sep, GFP_KERNEL);
8462306a36Sopenharmony_ci			} else if (!strcmp(p, "CLS") || !strcmp(p, "CLASS")) {
8562306a36Sopenharmony_ci				int i;
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci				kfree(info->class_name);
8862306a36Sopenharmony_ci				info->class_name = kstrdup(sep, GFP_KERNEL);
8962306a36Sopenharmony_ci				string_upper(sep, sep);
9062306a36Sopenharmony_ci				for (i = 0; classes[i].token; i++) {
9162306a36Sopenharmony_ci					if (!strcmp(classes[i].token, sep)) {
9262306a36Sopenharmony_ci						info->class = i;
9362306a36Sopenharmony_ci						goto rock_on;
9462306a36Sopenharmony_ci					}
9562306a36Sopenharmony_ci				}
9662306a36Sopenharmony_ci				pr_warn("%s probe: warning, class '%s' not understood\n",
9762306a36Sopenharmony_ci					port->name, sep);
9862306a36Sopenharmony_ci				info->class = PARPORT_CLASS_OTHER;
9962306a36Sopenharmony_ci			} else if (!strcmp(p, "CMD") ||
10062306a36Sopenharmony_ci				   !strcmp(p, "COMMAND SET")) {
10162306a36Sopenharmony_ci				kfree(info->cmdset);
10262306a36Sopenharmony_ci				info->cmdset = kstrdup(sep, GFP_KERNEL);
10362306a36Sopenharmony_ci				/* if it speaks printer language, it's
10462306a36Sopenharmony_ci				   probably a printer */
10562306a36Sopenharmony_ci				if (strstr(sep, "PJL") || strstr(sep, "PCL"))
10662306a36Sopenharmony_ci					guessed_class = PARPORT_CLASS_PRINTER;
10762306a36Sopenharmony_ci			} else if (!strcmp(p, "DES") || !strcmp(p, "DESCRIPTION")) {
10862306a36Sopenharmony_ci				kfree(info->description);
10962306a36Sopenharmony_ci				info->description = kstrdup(sep, GFP_KERNEL);
11062306a36Sopenharmony_ci			}
11162306a36Sopenharmony_ci		}
11262306a36Sopenharmony_ci	rock_on:
11362306a36Sopenharmony_ci		if (q)
11462306a36Sopenharmony_ci			p = q + 1;
11562306a36Sopenharmony_ci		else
11662306a36Sopenharmony_ci			p = NULL;
11762306a36Sopenharmony_ci	}
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	/* If the device didn't tell us its class, maybe we have managed to
12062306a36Sopenharmony_ci	   guess one from the things it did say. */
12162306a36Sopenharmony_ci	if (info->class == PARPORT_CLASS_UNSPEC)
12262306a36Sopenharmony_ci		info->class = guessed_class;
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	pretty_print (port, device);
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	kfree(txt);
12762306a36Sopenharmony_ci}
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci/* Read up to count-1 bytes of device id. Terminate buffer with
13062306a36Sopenharmony_ci * '\0'. Buffer begins with two Device ID length bytes as given by
13162306a36Sopenharmony_ci * device. */
13262306a36Sopenharmony_cistatic ssize_t parport_read_device_id (struct parport *port, char *buffer,
13362306a36Sopenharmony_ci				       size_t count)
13462306a36Sopenharmony_ci{
13562306a36Sopenharmony_ci	unsigned char length[2];
13662306a36Sopenharmony_ci	unsigned lelen, belen;
13762306a36Sopenharmony_ci	size_t idlens[4];
13862306a36Sopenharmony_ci	unsigned numidlens;
13962306a36Sopenharmony_ci	unsigned current_idlen;
14062306a36Sopenharmony_ci	ssize_t retval;
14162306a36Sopenharmony_ci	size_t len;
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	/* First two bytes are MSB,LSB of inclusive length. */
14462306a36Sopenharmony_ci	retval = parport_read (port, length, 2);
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	if (retval < 0)
14762306a36Sopenharmony_ci		return retval;
14862306a36Sopenharmony_ci	if (retval != 2)
14962306a36Sopenharmony_ci		return -EIO;
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	if (count < 2)
15262306a36Sopenharmony_ci		return 0;
15362306a36Sopenharmony_ci	memcpy(buffer, length, 2);
15462306a36Sopenharmony_ci	len = 2;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	/* Some devices wrongly send LE length, and some send it two
15762306a36Sopenharmony_ci	 * bytes short. Construct a sorted array of lengths to try. */
15862306a36Sopenharmony_ci	belen = (length[0] << 8) + length[1];
15962306a36Sopenharmony_ci	lelen = (length[1] << 8) + length[0];
16062306a36Sopenharmony_ci	idlens[0] = min(belen, lelen);
16162306a36Sopenharmony_ci	idlens[1] = idlens[0]+2;
16262306a36Sopenharmony_ci	if (belen != lelen) {
16362306a36Sopenharmony_ci		int off = 2;
16462306a36Sopenharmony_ci		/* Don't try lengths of 0x100 and 0x200 as 1 and 2 */
16562306a36Sopenharmony_ci		if (idlens[0] <= 2)
16662306a36Sopenharmony_ci			off = 0;
16762306a36Sopenharmony_ci		idlens[off] = max(belen, lelen);
16862306a36Sopenharmony_ci		idlens[off+1] = idlens[off]+2;
16962306a36Sopenharmony_ci		numidlens = off+2;
17062306a36Sopenharmony_ci	}
17162306a36Sopenharmony_ci	else {
17262306a36Sopenharmony_ci		/* Some devices don't truly implement Device ID, but
17362306a36Sopenharmony_ci		 * just return constant nibble forever. This catches
17462306a36Sopenharmony_ci		 * also those cases. */
17562306a36Sopenharmony_ci		if (idlens[0] == 0 || idlens[0] > 0xFFF) {
17662306a36Sopenharmony_ci			printk(KERN_DEBUG "%s: reported broken Device ID length of %#zX bytes\n",
17762306a36Sopenharmony_ci			       port->name, idlens[0]);
17862306a36Sopenharmony_ci			return -EIO;
17962306a36Sopenharmony_ci		}
18062306a36Sopenharmony_ci		numidlens = 2;
18162306a36Sopenharmony_ci	}
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	/* Try to respect the given ID length despite all the bugs in
18462306a36Sopenharmony_ci	 * the ID length. Read according to shortest possible ID
18562306a36Sopenharmony_ci	 * first. */
18662306a36Sopenharmony_ci	for (current_idlen = 0; current_idlen < numidlens; ++current_idlen) {
18762306a36Sopenharmony_ci		size_t idlen = idlens[current_idlen];
18862306a36Sopenharmony_ci		if (idlen+1 >= count)
18962306a36Sopenharmony_ci			break;
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci		retval = parport_read (port, buffer+len, idlen-len);
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci		if (retval < 0)
19462306a36Sopenharmony_ci			return retval;
19562306a36Sopenharmony_ci		len += retval;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci		if (port->physport->ieee1284.phase != IEEE1284_PH_HBUSY_DAVAIL) {
19862306a36Sopenharmony_ci			if (belen != len) {
19962306a36Sopenharmony_ci				printk(KERN_DEBUG "%s: Device ID was %zd bytes while device told it would be %d bytes\n",
20062306a36Sopenharmony_ci				       port->name, len, belen);
20162306a36Sopenharmony_ci			}
20262306a36Sopenharmony_ci			goto done;
20362306a36Sopenharmony_ci		}
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci		/* This might end reading the Device ID too
20662306a36Sopenharmony_ci		 * soon. Hopefully the needed fields were already in
20762306a36Sopenharmony_ci		 * the first 256 bytes or so that we must have read so
20862306a36Sopenharmony_ci		 * far. */
20962306a36Sopenharmony_ci		if (buffer[len-1] == ';') {
21062306a36Sopenharmony_ci			printk(KERN_DEBUG "%s: Device ID reading stopped before device told data not available. Current idlen %u of %u, len bytes %02X %02X\n",
21162306a36Sopenharmony_ci			       port->name, current_idlen, numidlens,
21262306a36Sopenharmony_ci			       length[0], length[1]);
21362306a36Sopenharmony_ci			goto done;
21462306a36Sopenharmony_ci		}
21562306a36Sopenharmony_ci	}
21662306a36Sopenharmony_ci	if (current_idlen < numidlens) {
21762306a36Sopenharmony_ci		/* Buffer not large enough, read to end of buffer. */
21862306a36Sopenharmony_ci		size_t idlen, len2;
21962306a36Sopenharmony_ci		if (len+1 < count) {
22062306a36Sopenharmony_ci			retval = parport_read (port, buffer+len, count-len-1);
22162306a36Sopenharmony_ci			if (retval < 0)
22262306a36Sopenharmony_ci				return retval;
22362306a36Sopenharmony_ci			len += retval;
22462306a36Sopenharmony_ci		}
22562306a36Sopenharmony_ci		/* Read the whole ID since some devices would not
22662306a36Sopenharmony_ci		 * otherwise give back the Device ID from beginning
22762306a36Sopenharmony_ci		 * next time when asked. */
22862306a36Sopenharmony_ci		idlen = idlens[current_idlen];
22962306a36Sopenharmony_ci		len2 = len;
23062306a36Sopenharmony_ci		while(len2 < idlen && retval > 0) {
23162306a36Sopenharmony_ci			char tmp[4];
23262306a36Sopenharmony_ci			retval = parport_read (port, tmp,
23362306a36Sopenharmony_ci					       min(sizeof tmp, idlen-len2));
23462306a36Sopenharmony_ci			if (retval < 0)
23562306a36Sopenharmony_ci				return retval;
23662306a36Sopenharmony_ci			len2 += retval;
23762306a36Sopenharmony_ci		}
23862306a36Sopenharmony_ci	}
23962306a36Sopenharmony_ci	/* In addition, there are broken devices out there that don't
24062306a36Sopenharmony_ci	   even finish off with a semi-colon. We do not need to care
24162306a36Sopenharmony_ci	   about those at this time. */
24262306a36Sopenharmony_ci done:
24362306a36Sopenharmony_ci	buffer[len] = '\0';
24462306a36Sopenharmony_ci	return len;
24562306a36Sopenharmony_ci}
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci/* Get Std 1284 Device ID. */
24862306a36Sopenharmony_cissize_t parport_device_id (int devnum, char *buffer, size_t count)
24962306a36Sopenharmony_ci{
25062306a36Sopenharmony_ci	ssize_t retval = -ENXIO;
25162306a36Sopenharmony_ci	struct pardevice *dev = parport_open(devnum, daisy_dev_name);
25262306a36Sopenharmony_ci	if (!dev)
25362306a36Sopenharmony_ci		return -ENXIO;
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	parport_claim_or_block (dev);
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	/* Negotiate to compatibility mode, and then to device ID
25862306a36Sopenharmony_ci	 * mode. (This so that we start form beginning of device ID if
25962306a36Sopenharmony_ci	 * already in device ID mode.) */
26062306a36Sopenharmony_ci	parport_negotiate (dev->port, IEEE1284_MODE_COMPAT);
26162306a36Sopenharmony_ci	retval = parport_negotiate (dev->port,
26262306a36Sopenharmony_ci				    IEEE1284_MODE_NIBBLE | IEEE1284_DEVICEID);
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci	if (!retval) {
26562306a36Sopenharmony_ci		retval = parport_read_device_id (dev->port, buffer, count);
26662306a36Sopenharmony_ci		parport_negotiate (dev->port, IEEE1284_MODE_COMPAT);
26762306a36Sopenharmony_ci		if (retval > 2)
26862306a36Sopenharmony_ci			parse_data (dev->port, dev->daisy, buffer+2);
26962306a36Sopenharmony_ci	}
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci	parport_release (dev);
27262306a36Sopenharmony_ci	parport_close (dev);
27362306a36Sopenharmony_ci	return retval;
27462306a36Sopenharmony_ci}
275