aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/parport/probe.c
blob: c94963145e17991bbfeb233e8d7a63733da58698 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
/* $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 <philb@gnu.org>
 */

#include <linux/module.h>
#include <linux/parport.h>
#include <linux/ctype.h>
#include <linux/string.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(KERN_WARNING "%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;
			*(sep++) = 0;
			/* Get rid of trailing blanks */
			u = sep + strlen (sep) - 1;
			while (u >= p && *u == ' ')
				*u-- = '\0';
			u = p;
			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];

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

		if (retval != 2) goto end_id;

		idlen = (length[0] << 8) + length[1] - 2;
		/*
		 * Check if the caller-allocated buffer is large enough
		 * otherwise bail out or there will be an at least off by one.
		 */
		if (idlen + 1 < len)
			len = idlen;
		else {
			retval = -EINVAL;
			goto out;
		}
		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. */
			}
		}

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

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

out:
	parport_release (dev);
	parport_close (dev);
	return retval;
}