| /* $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 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")) { |
| 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; |
| } |
| } |
| 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")) { |
| 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 lenghts 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 %d 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 %d of %d, 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, "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 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; |
| } |