forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
279 lines
7.4 KiB
279 lines
7.4 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* Parallel port device probing code |
|
* |
|
* Authors: Carsten Gross, [email protected] |
|
* Philip Blundell <[email protected]> |
|
*/ |
|
|
|
#include <linux/module.h> |
|
#include <linux/parport.h> |
|
#include <linux/ctype.h> |
|
#include <linux/string.h> |
|
#include <linux/slab.h> |
|
#include <linux/uaccess.h> |
|
|
|
static const struct { |
|
const char *token; |
|
const 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]; |
|
|
|
pr_info("%s", port->name); |
|
|
|
if (device >= 0) |
|
pr_cont(" (addr %d)", device); |
|
|
|
pr_cont(": %s", classes[info->class].descr); |
|
if (info->class) |
|
pr_cont(", %s %s", info->mfr, info->model); |
|
|
|
pr_cont("\n"); |
|
} |
|
|
|
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) { |
|
pr_warn("%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")) { |
|
kfree(info->mfr); |
|
info->mfr = kstrdup(sep, GFP_KERNEL); |
|
} else if (!strcmp(p, "MDL") || !strcmp(p, "MODEL")) { |
|
kfree(info->model); |
|
info->model = kstrdup(sep, GFP_KERNEL); |
|
} else if (!strcmp(p, "CLS") || !strcmp(p, "CLASS")) { |
|
int i; |
|
|
|
kfree(info->class_name); |
|
info->class_name = kstrdup(sep, GFP_KERNEL); |
|
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; |
|
} |
|
} |
|
pr_warn("%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")) { |
|
kfree(info->cmdset); |
|
info->cmdset = kstrdup(sep, GFP_KERNEL); |
|
/* 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")) { |
|
kfree(info->description); |
|
info->description = kstrdup(sep, GFP_KERNEL); |
|
} |
|
} |
|
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); |
|
} |
|
|
|
/* Read up to count-1 bytes of device id. Terminate buffer with |
|
* '\0'. Buffer begins with two Device ID length bytes as given by |
|
* device. */ |
|
static ssize_t parport_read_device_id (struct parport *port, char *buffer, |
|
size_t count) |
|
{ |
|
unsigned char length[2]; |
|
unsigned lelen, belen; |
|
size_t idlens[4]; |
|
unsigned numidlens; |
|
unsigned current_idlen; |
|
ssize_t retval; |
|
size_t len; |
|
|
|
/* First two bytes are MSB,LSB of inclusive length. */ |
|
retval = parport_read (port, length, 2); |
|
|
|
if (retval < 0) |
|
return retval; |
|
if (retval != 2) |
|
return -EIO; |
|
|
|
if (count < 2) |
|
return 0; |
|
memcpy(buffer, length, 2); |
|
len = 2; |
|
|
|
/* Some devices wrongly send LE length, and some send it two |
|
* bytes short. Construct a sorted array of lengths to try. */ |
|
belen = (length[0] << 8) + length[1]; |
|
lelen = (length[1] << 8) + length[0]; |
|
idlens[0] = min(belen, lelen); |
|
idlens[1] = idlens[0]+2; |
|
if (belen != lelen) { |
|
int off = 2; |
|
/* Don't try lengths of 0x100 and 0x200 as 1 and 2 */ |
|
if (idlens[0] <= 2) |
|
off = 0; |
|
idlens[off] = max(belen, lelen); |
|
idlens[off+1] = idlens[off]+2; |
|
numidlens = off+2; |
|
} |
|
else { |
|
/* Some devices don't truly implement Device ID, but |
|
* just return constant nibble forever. This catches |
|
* also those cases. */ |
|
if (idlens[0] == 0 || idlens[0] > 0xFFF) { |
|
printk(KERN_DEBUG "%s: reported broken Device ID length of %#zX bytes\n", |
|
port->name, idlens[0]); |
|
return -EIO; |
|
} |
|
numidlens = 2; |
|
} |
|
|
|
/* Try to respect the given ID length despite all the bugs in |
|
* the ID length. Read according to shortest possible ID |
|
* first. */ |
|
for (current_idlen = 0; current_idlen < numidlens; ++current_idlen) { |
|
size_t idlen = idlens[current_idlen]; |
|
if (idlen+1 >= count) |
|
break; |
|
|
|
retval = parport_read (port, buffer+len, idlen-len); |
|
|
|
if (retval < 0) |
|
return retval; |
|
len += retval; |
|
|
|
if (port->physport->ieee1284.phase != IEEE1284_PH_HBUSY_DAVAIL) { |
|
if (belen != len) { |
|
printk(KERN_DEBUG "%s: Device ID was %zd bytes while device told it would be %d bytes\n", |
|
port->name, len, belen); |
|
} |
|
goto done; |
|
} |
|
|
|
/* This might end reading the Device ID too |
|
* soon. Hopefully the needed fields were already in |
|
* the first 256 bytes or so that we must have read so |
|
* far. */ |
|
if (buffer[len-1] == ';') { |
|
printk(KERN_DEBUG "%s: Device ID reading stopped before device told data not available. Current idlen %u of %u, len bytes %02X %02X\n", |
|
port->name, current_idlen, numidlens, |
|
length[0], length[1]); |
|
goto done; |
|
} |
|
} |
|
if (current_idlen < numidlens) { |
|
/* Buffer not large enough, read to end of buffer. */ |
|
size_t idlen, len2; |
|
if (len+1 < count) { |
|
retval = parport_read (port, buffer+len, count-len-1); |
|
if (retval < 0) |
|
return retval; |
|
len += retval; |
|
} |
|
/* Read the whole ID since some devices would not |
|
* otherwise give back the Device ID from beginning |
|
* next time when asked. */ |
|
idlen = idlens[current_idlen]; |
|
len2 = len; |
|
while(len2 < idlen && retval > 0) { |
|
char tmp[4]; |
|
retval = parport_read (port, tmp, |
|
min(sizeof tmp, idlen-len2)); |
|
if (retval < 0) |
|
return retval; |
|
len2 += retval; |
|
} |
|
} |
|
/* In addition, there are broken devices out there that don't |
|
even finish off with a semi-colon. We do not need to care |
|
about those at this time. */ |
|
done: |
|
buffer[len] = '\0'; |
|
return len; |
|
} |
|
|
|
/* Get Std 1284 Device ID. */ |
|
ssize_t parport_device_id (int devnum, char *buffer, size_t count) |
|
{ |
|
ssize_t retval = -ENXIO; |
|
struct pardevice *dev = parport_open(devnum, daisy_dev_name); |
|
if (!dev) |
|
return -ENXIO; |
|
|
|
parport_claim_or_block (dev); |
|
|
|
/* Negotiate to compatibility mode, and then to device ID |
|
* mode. (This so that we start form beginning of device ID if |
|
* already in device ID mode.) */ |
|
parport_negotiate (dev->port, IEEE1284_MODE_COMPAT); |
|
retval = parport_negotiate (dev->port, |
|
IEEE1284_MODE_NIBBLE | IEEE1284_DEVICEID); |
|
|
|
if (!retval) { |
|
retval = parport_read_device_id (dev->port, buffer, count); |
|
parport_negotiate (dev->port, IEEE1284_MODE_COMPAT); |
|
if (retval > 2) |
|
parse_data (dev->port, dev->daisy, buffer+2); |
|
} |
|
|
|
parport_release (dev); |
|
parport_close (dev); |
|
return retval; |
|
}
|
|
|