/* probe serial port for PnP/Legacy devices
*
* Copyright 1999 Red Hat, Inc.
*
* This software may be freely redistributed under the terms of the GNU
* public license.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/


#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>

#include <linux/serial.h>
#include "serial.h"

/* character strings ARE null-terminated in the following structure    */
/* these elements are marked with a (string) in the comment            */
/* If PnP device sent 6 bit data stream, we've xlated by a 0x20 offset */
/* When computing checksum, must remove this offset                    */
struct pnp_com_id {
   unsigned char xlate_6bit;      /* does this contain xlated data */
   unsigned char other_id[17];    /* backward compatibility with pre-PNP */
   unsigned char other_len;       /* length of the other_id */
   unsigned char pnp_rev[2];      /* PnP revision bytes */
   unsigned char pnp_rev_str[8];  /* PnP revision (string version) */
   unsigned char eisa_id[4];      /* EISA Mfr identifier (string) */
   unsigned char product_id[5];   /* Mfr determined product ID (string) */
   unsigned char serial_number[9];/* Optional dev serial number (string) */
   unsigned char class_name[33];  /* Optional PnP Class name (string) */
   unsigned char driver_id[42];   /* Optional compat device IDs (string) */
   unsigned char user_name[42];   /* Optional verbose product descr (string)*/
   unsigned char checksum[2];     /* Optional checksum */
};

/* there are two possible bytes to signify the start of a PnP ID string */
#define BeginPnP1 0x28
#define BeginPnP2 0x08

/* Likewise, two possible stop bytes */
#define EndPnP1   0x29
#define EndPnP2   0x09

/* these chars indicate extensions to the base dev id exist */
#define ExtendPnP1 0x5c
#define ExtendPnP2 0x3c

#define PNP_COM_MAXLEN 256

/* results from initiating hardware probe of a hardware device */
#define PNP_COM_FATAL      1       /* system error, check errno  */
#define PNP_COM_FAIL       2       /* probe ok, but found nothing */
#define PNP_COM_OK         3       /* probe ok, we found it      */

/* types of devices we might find */
/* if PNP_COM_PNPDEV is NOT set, its a legacy device */
#define PNP_COM_MOUSE      1       /* its a mouse */
#define PNP_COM_MODEM      2       /* its a modem */
#define PNP_COM_OTHER      4       /* device is there, cant tell what kind */
#define PNP_COM_NOEXIST    8       /* no device seen */
#define PNP_COM_PNPDEV     512     /* its a PNP device */

/* level of debugging output */
/* current any value > 0 dumps all available debugging output */
static int debug_level=0;

static void serialFreeDevice(struct serialDevice *dev) {
       if (dev->pnpmfr) free(dev->pnpmfr);
       if (dev->pnpmodel) free(dev->pnpmodel);
       if (dev->pnpcompat) free(dev->pnpcompat);
       if (dev->pnpdesc) free(dev->pnpdesc);
       freeDevice((struct device *)dev);
}

static void serialWriteDevice(FILE *file, struct serialDevice *dev)
{
       writeDevice(file, (struct device *) dev);
       if (dev->pnpmfr)
         fprintf(file,"pnpmfr: %s\n",dev->pnpmfr);
       if (dev->pnpmodel)
         fprintf(file,"pnpmodel: %s\n",dev->pnpmodel);
       if (dev->pnpcompat)
         fprintf(file,"pnpcompat: %s\n",dev->pnpcompat);
       if (dev->pnpdesc)
         fprintf(file,"pnpdesc: %s\n",dev->pnpdesc);
}

static int serialCompareDevice( struct serialDevice *dev1, struct serialDevice *dev2)
{
       int x;

       x = compareDevice((struct device *)dev1, (struct device *)dev2);
       if (x && x!=2) return x;
       if (dev1->pnpmfr && dev2->pnpmfr && strcmp(dev1->pnpmfr,dev2->pnpmfr))
         return 1;
       if ((!dev1->pnpmfr || !dev2->pnpmfr) && (dev1->pnpmfr != dev2->pnpmfr))
         return 1;
       if (dev1->pnpmodel && dev2->pnpmodel && strcmp(dev1->pnpmodel,dev2->pnpmodel))
         return 1;
       if ((!dev1->pnpmodel || !dev2->pnpmodel) && (dev1->pnpmodel != dev2->pnpmodel))
         return 1;
       if (dev1->pnpcompat && dev2->pnpcompat && strcmp(dev1->pnpcompat,dev2->pnpcompat))
         return 1;
       if ((!dev1->pnpcompat || !dev2->pnpcompat) && (dev1->pnpcompat != dev2->pnpcompat))
         return 1;
       if (dev1->pnpdesc && dev2->pnpdesc && strcmp(dev1->pnpdesc,dev2->pnpdesc))
         return 1;
       if ((!dev1->pnpdesc || !dev2->pnpdesc) && (dev1->pnpdesc != dev2->pnpdesc))
         return 1;
       return x;
}


struct serialDevice * serialNewDevice(struct serialDevice *dev) {
       struct serialDevice *ret;

       ret = malloc(sizeof(struct serialDevice));
       memset(ret,'\0',sizeof(struct serialDevice));
       ret=(struct serialDevice *)newDevice((struct device *)dev,(struct device *)ret);
       ret->bus = BUS_SERIAL;
       ret->newDevice = serialNewDevice;
       ret->freeDevice = serialFreeDevice;
       ret->writeDevice = serialWriteDevice;
       ret->compareDevice = serialCompareDevice;
       if (dev && dev->bus == BUS_SERIAL) {
               if (dev->pnpmfr)
                 ret->pnpmfr=strdup(dev->pnpmfr);
               if (dev->pnpmodel)
                 ret->pnpmodel=strdup(dev->pnpmodel);
               if (dev->pnpcompat)
                 ret->pnpcompat=strdup(dev->pnpcompat);
               if (dev->pnpdesc)
                 ret->pnpdesc=strdup(dev->pnpdesc);
       }
       return ret;
}

/* UNUSED */
void print_status_lines( int fd ) {
   int modem_lines;

  ioctl(fd, TIOCMGET, &modem_lines);

  printf("DTR : %s\n",(modem_lines & TIOCM_DTR ? "On" : "Off"));
  printf("RTS : %s\n",(modem_lines & TIOCM_RTS ? "On" : "Off"));
  printf("CTS : %s\n",(modem_lines & TIOCM_CTS ? "On" : "Off"));
  printf("DSR : %s\n",(modem_lines & TIOCM_DSR ? "On" : "Off"));
  printf("CD  : %s\n",(modem_lines & TIOCM_CD  ? "On" : "Off"));
  printf("RI  : %s\n",(modem_lines & TIOCM_RI  ? "On" : "Off"));

}


/* UNUSED except in debug */
/* outputs data in a hex table, 8 values per row */
void print_hex_data( unsigned char *data, int len ) {
       int i, j, pos;

       if (len == 0) {
                   printf("No data to print.\n");
                   return;
       }

       pos = 0;
       for (i=0; i< len; i+=8) {
                   printf("0x%.4x ", i);
                   for (j=i; j < len && j < i+8; j++) {
                                   printf("0x%.2x ",data[pos++]);
                   }
                   printf("\n");
       }
}


/*
* wait_input - wait until there is data available on fd,
* for the length of time specified by *timo (indefinite
* if timo is NULL).
*/

int wait_for_input (int fd, struct timeval *timo) {
   fd_set ready;
   int n;

   FD_ZERO(&ready);
   FD_SET(fd, &ready);

   n = select(fd+1, &ready, NULL, &ready, timo);
   return n;
}

/* UNUSED */
/* read characters into the buffer buf, until one of: */
/*      char_timeout expired before next character arrives */
/*      total_timeout expires                             */
/*      maxlen characters are retrieved                   */
/*                                                        */
/* returns < 0 if it fails                                */
/* otherwise the # of characters received is returned     */
/* char_timeout is in microseconds (millionths of a sec)  */
/* total_timeout is in seconds                            */
int timed_serial_read(int fd, int char_timeout, int total_timeout,
                     unsigned char *buf, int maxlen ) {

   int done, pos, starttime, temp;
   struct timeval timo;
   unsigned char intbuf[2];

   /* start reading */
   done = 0;
   pos  = 0;
   starttime=time(NULL);
   memset(buf, 0, maxlen);
   while (!done) {
       timo.tv_sec=0;
       timo.tv_usec=char_timeout;
       if (wait_for_input(fd, &timo) > 0) {
           temp = read( fd, intbuf, 1 );
           if (temp < 0) {
               if (errno != EAGAIN)
                   return -1;
           } else {
               buf[pos++] = intbuf[0];
               buf[pos] = 0;
           }
       } else
           done = 1;

       /* shouldnt run more than 5 seconds */
       if (time(NULL)-starttime > total_timeout )
           done = 1;

       if (pos > maxlen)
           done = 1;
   }
   return pos;
}


int open_serial_port( char *port ) {
   int fd;

   fd = open( port, O_RDWR | O_NONBLOCK);
   if (fd < 0)
       return -1;

   /* reset file so it is no longer in non-blocking mode */
   if (fcntl(fd, F_SETFL, 0) < 0) {
       close(fd);
       return -1;
   }

   return fd;
}

/* <0 means ioctl error occurred */
int get_serial_lines( int fd ) {
   int modem_lines;

   ioctl(fd, TIOCMGET, &modem_lines);
   return modem_lines;
}

/* <0 means ioctl error occurred */
int set_serial_lines( int fd, int modem_lines ) {
   return ioctl(fd, TIOCMSET, &modem_lines);
}

/* set serial port to 1200 baud, 'nbits' bits, 1 stop, no parity */
int setup_serial_port( int fd, int nbits, struct termios *attr ) {

   attr->c_iflag = IGNBRK | IGNPAR;
   attr->c_cflag = 0;
   attr->c_cflag &= ~(CSIZE | CSTOPB | PARENB | PARODD | PARENB);
   attr->c_cflag |=  CREAD | CLOCAL; /*| CRTSCTS ; */
   if (nbits == 7)
       attr->c_cflag |= CS7 | CSTOPB;
   else
       attr->c_cflag |= CS8;
   attr->c_oflag = 0;
   attr->c_lflag = 0;

   attr->c_cc[VMIN] = 1;
   attr->c_cc[VTIME] = 5;

   cfsetospeed( attr, B1200);
   cfsetispeed( attr, B1200);
   return tcsetattr(fd, TCSANOW, attr);
}

/* Initialize the serial port to a known state *before* probing. This is
* apparently required for some Logitech mice, who will stubbornly refuse
* to respond to PnP probes after they've been opened by gpm or XFree.
*/

int init_port(int fd) {
       struct termios attr;

       if (tcgetattr(fd,&attr))
         return 1;

       cfsetospeed(&attr, B2400);
       cfsetispeed(&attr, B2400);
       attr.c_iflag = IXON | ICRNL;
       attr.c_cflag = CLOCAL | HUPCL | CREAD | B9600 | CS8;
       attr.c_oflag = 0;
       attr.c_lflag = 0;
       return tcsetattr(fd, TCSANOW, &attr);
}


/* Request for PnP info from serial device                      */
/* See page 6 of the pnpcom doc from Microsoft                  */
/* Return code tells us what happened                           */
/*                                                              */
/* PNP_COM_FATAL      - error, errno has reason                 */
/* PNP_COM_OK         - probe initiated successfully            */
/* PNP_COM_FAIL       - DSR never came on - try alterntives     */
/*                          means (ATI9?) to get PnP string     */
int init_pnp_com_seq1( int fd ) {
   int modem_lines;
   int temp;
   int dsr_status;
   int rc = PNP_COM_OK;
   struct termios portattr;

   if (init_port(fd))
         return PNP_COM_FATAL;

   modem_lines = get_serial_lines(fd);

   /* turn off RTS */
   modem_lines &= ~TIOCM_RTS;
   set_serial_lines(fd, modem_lines);

   /* wait 200ms for DSR=1 */
   usleep(200000);

   dsr_status = get_serial_lines(fd) & TIOCM_DSR;
   /* see if we got DSR coming up */

   if (!dsr_status) {
       /* turn DTR and RTS back on and try alternative methods */
       modem_lines |= TIOCM_DTR | TIOCM_RTS;
       set_serial_lines(fd, modem_lines);
       rc = PNP_COM_OK;
   }

   /* COM port Setup, 1st phase */
   /* now we set port to be 1200 baud, 7 bits, no parity, 1 stop bit */
   temp = tcgetattr(fd, &portattr);
   if (temp < 0)
       return PNP_COM_FATAL;
   /* goto 1200 baud, etc etc as PnP requires */
   temp = setup_serial_port( fd, 7, &portattr );
   if (temp < 0)
       return PNP_COM_FATAL;

   /* we drop DTR and RTS */
   modem_lines &= ~( TIOCM_RTS | TIOCM_DTR);
   set_serial_lines(fd, modem_lines);
   usleep(200000);

   /* bring DTR back up */
   modem_lines |= TIOCM_DTR;
   set_serial_lines(fd, modem_lines);
   usleep(200000);

   /* now entering next phase */
   modem_lines |= TIOCM_RTS;
   set_serial_lines(fd, modem_lines);
   usleep(200000);

   return rc;
}


/* Request for PnP info from serial device                      */
/* Uses ATI9 code, may not do anything but return 'ERROR'       */
/* Return code tells us what happened                           */
/*                                                              */
/* PNP_COM_FATAL      - error, errno has reason                 */
/* PNP_COM_OK         - probe initiated successfully            */
/* PNP_COM_FAIL       - DSR never came on - try alterntives     */
/*                          means (ATI9?) to get PnP string     */
int init_pnp_com_ati9( int fd ) {
   int modem_lines;
   int temp;
   int done;
   int respindex;
   int starttime;
   unsigned char resp[100], buf[2];
   struct timeval timo;
   struct termios portattr;

   modem_lines = get_serial_lines(fd);

   /* turn off RTS */
   modem_lines &= ~TIOCM_RTS;
   set_serial_lines(fd, modem_lines);

   /* wait 200ms for DSR=1 */
   usleep(200000);

   /* now we set port to be 1200 baud, 8 bits, no parity, 1 stop bit */
   temp = tcgetattr(fd, &portattr);
   if (temp < 0) {
       modem_lines |= TIOCM_DTR | TIOCM_RTS;
       set_serial_lines(fd, modem_lines);
       return PNP_COM_FATAL;
   }

   /* goto 1200 baud, 8 bits */
   temp = setup_serial_port( fd, 8, &portattr );
   if (temp < 0) {
       modem_lines |= TIOCM_DTR | TIOCM_RTS;
       set_serial_lines(fd, modem_lines);
       return PNP_COM_FATAL;
   }

   /* turn on DTR and RTS */
   modem_lines = get_serial_lines(fd);
   modem_lines |= TIOCM_RTS | TIOCM_DTR;
   set_serial_lines(fd, modem_lines);
   usleep(200000);

   /* send the 'AT' command */
   if (debug_level > 0)
       printf("Sending ATI9 command to modem\n");

   write(fd, "ATI9\r", 5);

   /* start reading - read the AT command back */
   done     = 0;
   respindex= 0;
   starttime=time(NULL);
   memset(resp, 0, sizeof(resp));
   while (!done) {
       timo.tv_sec=0;
       timo.tv_usec=250000;
       if (wait_for_input(fd, &timo) > 0) {
           temp = read( fd, buf, 1 );
           if (temp < 0) {
               if (errno != EAGAIN)
                   return PNP_COM_FATAL;
           } else {
               resp[respindex++] = buf[0];
               resp[respindex] = 0;
           }
       } else
           done = 1;

       /* shouldnt run more than 5 seconds */
       if (time(NULL)-starttime > 5 )
           done = 1;

       if (respindex > 6)
           done = 1;

       if (strstr(resp, "ATI9\r"))
           done = 1;

       if (debug_level > 0)
           printf("ATI9 probe ->%d \"%s\"\n",respindex, resp);
   }

   /* see if we saw the 'OK' response */
   if (strstr(resp, "("))
       return PNP_COM_OK;
   else
       return PNP_COM_FAIL;

   return PNP_COM_OK;
}

/* See if this is a legacy mouse device                         */
/* Only called if the PnP probe above failed                    */
/* We turn off the mouse via RS232 lines, then turn it on       */
/* If it spits out an 'M' character (at 1200 baud, 7N1)         */
/* it could be a mouse.                                         */
/*                                                              */
/* Return code tells us what happened                           */
/*                                                              */
/* PNP_COM_FATAL     - error, errno has reason                  */
/* PNP_COM_OK        - probe saw 'M'                            */
/* PNP_COM_FAIL      - Never saw the 'M' response               */

int find_legacy_mouse( int fd ) {
   int modem_lines;
   int temp;
   int done;
   int starttime;
   unsigned char resp[2];
   struct timeval timo;
   struct termios portattr;

   /* now we set port to be 1200 baud, 7 bits, no parity, 1 stop bit */
   temp = tcgetattr(fd, &portattr);
   if (temp < 0)
       return PNP_COM_FATAL;

   /* goto 1200 baud, etc etc*/
   temp = setup_serial_port( fd, 7, &portattr );
   if (temp < 0)
       return PNP_COM_FATAL;

   /* we drop DTR and RTS */
   modem_lines = get_serial_lines(fd);
   modem_lines &= ~( TIOCM_RTS | TIOCM_DTR);
   set_serial_lines(fd, modem_lines);
   usleep(200000);

   /* bring them DTR back up */
   modem_lines |= TIOCM_DTR | TIOCM_RTS;
   set_serial_lines(fd, modem_lines);

   /* start reading - after first character we quit */
   done     = 0;
   starttime=time(NULL);
   while (!done) {
       timo.tv_sec=0;
       timo.tv_usec=250000;
       if (wait_for_input(fd, &timo) > 0) {
           temp = read( fd, resp, 1 );
           if (temp < 0) {
               if (errno != EAGAIN)
                   return PNP_COM_FATAL;
           } else {
               done = 1;
           }
       } else
           done = 1;


       /* shouldnt run more than 2 seconds */
       if (time(NULL)-starttime > 2 )
           done = 1;
   }
   if (*resp == 'M')
       return PNP_COM_OK;
   else
       return PNP_COM_FAIL;
}

/* See if this is a legacy modem device                         */
/* Only called if the PnP probe above failed                    */
/* We send a '!AT' and see if we get an 'OK' back               */
/*                                                              */
/* Return code tells us what happened                           */
/*                                                              */
/* PNP_COM_FATAL     - error, errno has reason                  */
/* PNP_COM_OK        - probe saw 'OK'                           */
/* PNP_COM_FAIL      - Never saw the 'OK' response              */
int find_legacy_modem( int fd ) {
   int modem_lines;
   int temp;
   int done;
   int respindex;
   int starttime;
   unsigned char resp[10], buf[2];
   struct timeval timo;
   struct termios portattr;

   /* now we set port to be 1200 baud, 8 bits, no parity, 1 stop bit */
   temp = tcgetattr(fd, &portattr);
   if (temp < 0)
       return PNP_COM_FATAL;

   /* goto 1200 baud, 8 bits */
   temp = setup_serial_port( fd, 8, &portattr );
   if (temp < 0)
       return PNP_COM_FATAL;

   /* turn on DTR and RTS */
   modem_lines = get_serial_lines(fd);
   modem_lines |= TIOCM_RTS | TIOCM_DTR;
   set_serial_lines(fd, modem_lines);
   usleep(200000);

   /* send the 'AT' command */
   if (debug_level > 0)
       printf("Sending AT command to modem\n");

   write(fd, "AT\r", 3);

   /* start reading - we'll get AT command back first, then modem response */
   done     = 0;
   respindex= 0;
   starttime=time(NULL);
   memset(resp, 0, sizeof(resp));
   while (!done) {
       timo.tv_sec=0;
       timo.tv_usec=250000;
       if (wait_for_input(fd, &timo) > 0) {
           temp = read( fd, buf, 1 );
           if (temp < 0) {
               if (errno != EAGAIN)
                   return PNP_COM_FATAL;
           } else {
               resp[respindex++] = buf[0];
           }
       } else
           done = 1;

       /* shouldnt run more than 5 seconds */
       if (time(NULL)-starttime > 5 )
           done = 1;

       if (respindex > 9)
           done = 1;
   }

   /* see if we saw the 'OK' response */
   if (strstr(resp, "OK"))
       return PNP_COM_OK;
   else
       return PNP_COM_FAIL;
}

/* retrieve the PnP ID string */
/* timeout after 3 seconds   */
/* should probably set a 200 msec timeout per char, as spec says */
/* if no char received, we're done                              */
int read_pnp_string( int fd, unsigned char *pnp_string, int *pnp_len, int pnp_stringbuf_size ) {
   int     pnp_index;
   int     temp, done, counter;
   int     seen_start;
   time_t  starttime;
   struct timeval timo;
   unsigned char buf[80];
   unsigned char end_char;

   /* see if we have any input waiting */
   pnp_index  =0;
   seen_start = 0;
   done       = 0;
   end_char   = 0;
   starttime=time(NULL);
   while (!done) {
       timo.tv_sec=0;
       timo.tv_usec=250000;
       if (wait_for_input(fd, &timo) > 0) {
           temp = read( fd, buf, 1 );
           if (temp < 0) {
               if (errno != EAGAIN)
                   return PNP_COM_FATAL;
           } else {
               for (counter=0; counter < temp; counter++) {
                   pnp_string[pnp_index++] = buf[counter];
                   if (seen_start) {
                       if (buf[counter] == end_char) {
                           done=1;
                           break;
                       }
                   } else {
                       if (buf[counter] == BeginPnP1) {
                           seen_start = 1;
                           end_char   = EndPnP1;
                       } else if (buf[counter] == BeginPnP2) {
                           seen_start = 1;
                           end_char   = EndPnP2;
                       }
                   }
               }
           }
       } else
           done = 1;

       /* shouldnt run more than 4 seconds */
       if (time(NULL)-starttime > 4 )
           done = 1;

       if (pnp_index >= pnp_stringbuf_size)
           done = 1;
   }
   pnp_string[pnp_index] = 0;
  *pnp_len=pnp_index;
   return 0;
}

/* UNUSED */
/* simple little helper function */
void xlate_memcpy( void *dest, void *src, int len, int xlate_flag ) {
 unsigned char *d, *s;
 int i;

 for (i=0,d=dest,s=src; i<len; i++, d++, s++)
  *d = (*s) + ((xlate_flag) ? 0x20 : 0 );
}

/* parse the PnP ID string into components */
int parse_pnp_string( unsigned char *pnp_id_string, int pnp_len,
                    struct pnp_com_id *pnp_id ) {
   unsigned char *p1, *p2;
   unsigned char *start;
   unsigned char *end;
   unsigned char *curpos;
   unsigned char *endfield;
   unsigned char *temppos;
   unsigned char *pnp_string;
   unsigned char end_char;

   int no_more_extensions=0;
   int stage;
   int len;
   unsigned short int checksum;
   char hex_checksum[5];

   char extension_delims[] = {EndPnP1, EndPnP2, ExtendPnP1, ExtendPnP2, 0};
   char end_delims[] = {EndPnP1, EndPnP2, 0};

   /* clear out pnp_id */
   memset(pnp_id, 0, sizeof(*pnp_id));

   /* copy pnp_string to temp space */
   pnp_string = alloca(pnp_len+1);
   memcpy(pnp_string, pnp_id_string, pnp_len+1);

   /* first find the start of the PnP part of string */
   p1 = memchr( pnp_string, BeginPnP1, pnp_len );
   p2 = memchr( pnp_string, BeginPnP2, pnp_len );

   /* use the one which points nearest to start of the string */
   /* and is actually defined                                */
   if ( p1 && p2 ) {
       start = (p1 < p2) ? p1 : p2;
   } else if (p1)
       start = p1;
   else if (p2)
       start = p2;
   else
       start = NULL;

   /* if no start then we're done */
   if (!start)
       return -1;

   /* the length of the initial part cannot be more than 17 bytes */
   if ((start - pnp_string) > 17)
       return -1;

   /* setup end character we are looking for based on the start character */
   if (start == p2) {
     pnp_id->xlate_6bit = 1;
     end_char = EndPnP2;
     /* we need to xlate data in PnP fields */
     /* remember to skip the revision fields (bytes 1 and 2 after start) */
     temppos=start;
     while (1) {
         if (*temppos == EndPnP2) {
            *temppos += 0x20;
             break;
         } else if (temppos != start+1 && temppos != start+2 )
            *temppos += 0x20;

         temppos++;
     }
   } else {
     pnp_id->xlate_6bit = 0;
     end_char = EndPnP1;
   }

   /* move everything before the start of the PnP block */
   memcpy(pnp_id->other_id, pnp_string, start-pnp_string);
   pnp_id->other_len = start - pnp_string;

   /* now we get the PnP fields - all were zero'd out above */
   curpos = start+1;
   memcpy(pnp_id->pnp_rev,curpos,2);       curpos += 2;
   memcpy(pnp_id->eisa_id,curpos,3);       curpos += 3;
   memcpy(pnp_id->product_id,curpos,4);    curpos += 4;
   /* now we see if have extension fields */
   no_more_extensions = 0;
   stage = 0;
   while (!no_more_extensions) {
       if (*curpos == ExtendPnP1 || *curpos == ExtendPnP2) {
           curpos++;
           endfield = strpbrk(curpos, extension_delims);
           if (!endfield)
               return -1;
           /* if we reached the end of all PnP data, back off */
           /* cause there is a checksum at the end of extension data */
           if (*endfield == EndPnP1 || *endfield == EndPnP2)
               endfield -= 2;
       } else
           break;

       len = endfield - curpos;
       switch (stage) {
         case 0:
           if (len != 8 && len != 0 )
               return -1;

           memcpy(pnp_id->serial_number,curpos,len);
           curpos += len;
           break;

         case 1:
           if (len > 33)
               return -1;
           memcpy(pnp_id->class_name, curpos, len);
           curpos = endfield;
           break;

         case 2:
           if (len > 41)
               return -1;
           memcpy(pnp_id->driver_id, curpos, len);
           curpos = endfield;
           break;

         case 3:
           if (len > 41)
               return -1;
           memcpy(pnp_id->user_name, curpos, len);
           curpos = endfield;
           break;
       }
       stage++;
   }

   /* now find the end of all PnP data */
   end = strpbrk(curpos, end_delims);
   if (!end)
       return -1;

   /* if we had any extensions, we expect an checksum */
   if (stage != 0) {
       /* copy checksum into struct */
       memcpy(pnp_id->checksum, curpos, 2);

       /* compute the checksum as the sum of all PnP bytes, excluding */
       /* the two byte checksum. */
       checksum = 0;
       for (temppos=start; temppos <= end; temppos++) {
           /* skip checksum in calculation */
           if (temppos == (end-2) || temppos == (end-1))
               continue;
           /* dont xlate the revision at start */
           if (temppos != (start+1) && temppos != (start+2))
               checksum += *temppos - ((pnp_id->xlate_6bit) ? 0x20 : 0);
           else
               checksum += *temppos;
       }
       sprintf(hex_checksum, "%.2X", checksum & 0xff);
       if (strncmp(hex_checksum, pnp_id->checksum, 2))
           return -1;
   }

   /* checksum was ok, so we're done */
   return 0;
}

/* UNUSED except for debugging */
void print_pnp_id( struct pnp_com_id id ) {
   int i;
   int extensions_exist;
   int revision_temp;

   if (id.other_len != 0) {
       printf("Detected non-PnP data stream at start.\n");
       printf("  Length   = 0x%x\n",id.other_len);
       printf("  Contents =");
       for (i=0; i<id.other_len; i++)
           printf(" 0x%x",id.other_id[i]);
       printf("\n");
   } else
       printf("Non-PnP data stream not detected at start.\n");


   /* parse PnP revision bytes into a string values (eg. "1.00") */
   revision_temp = ((id.pnp_rev[0]&0x3f) << 6)+(id.pnp_rev[1]&0x3f);
   sprintf(id.pnp_rev_str, "%d.%d",revision_temp/100,revision_temp % 100);

   printf("\nPnP Required fields:\n");
   printf("    Revision       = %s\n",id.pnp_rev_str);
   printf("    Manufacturer   = %s\n",id.eisa_id);
   printf("    Product ID     = %s\n",id.product_id);

   extensions_exist = id.serial_number[0] || id.class_name[0] ||
                      id.driver_id[0]     || id.user_name[0];

   if (extensions_exist) {
       printf("\nPnP extension field(s) exist:\n");
       if (id.serial_number[0])
           printf("    Serial Number   = %s\n",id.serial_number);
       if (id.class_name[0])
           printf("    PnP class name  = %s\n",id.class_name);
       if (id.driver_id[0])
           printf("    PnP Compatible  = %s\n",id.driver_id);
       if (id.user_name[0])
           printf("    PnP Description = %s\n",id.user_name);
   }
}

int attempt_pnp_retrieve(int fd, char *pnp_string, int *pnp_strlen, int pnp_stringbuf_size) {
   int pnp_probe_status;
   int tried_at_prodding;
   int give_up;
   struct pnp_com_id pnp_id;

   tried_at_prodding=0;
   give_up=0;

   while (!give_up) {
       pnp_probe_status = init_pnp_com_seq1(fd);
       if (pnp_probe_status == PNP_COM_FATAL) {
           return(PNP_COM_FATAL);
       } else if (pnp_probe_status == PNP_COM_OK) {
           read_pnp_string(fd, pnp_string, pnp_strlen, pnp_stringbuf_size );

           if (debug_level > 0) {
               printf("\nPNP string = |%s|\n\n",pnp_string);
               print_hex_data(pnp_string, *pnp_strlen);
           }

           if (*pnp_strlen == 1 && pnp_string[0] == 'M') /* legacy mouse */
             return PNP_COM_OK;
           /* see if we got anything useful, if not try at command */
           /* to prod device into correct serial params       */
           if (parse_pnp_string( pnp_string, *pnp_strlen, &pnp_id )<0)
               if (!tried_at_prodding) {
                   write(fd, "AT\r", 3);
                   tried_at_prodding=1;
               } else
                   give_up = 1;
           else
               return PNP_COM_OK;
       } else
           give_up = 1;
   }

   /* try sending a ATI9 code to the modem to see if we get PnP id back */
   init_pnp_com_ati9(fd);
   read_pnp_string(fd, pnp_string, pnp_strlen, pnp_stringbuf_size );
   if (parse_pnp_string( pnp_string, *pnp_strlen, &pnp_id )<0) {
       *pnp_strlen = 0;
       pnp_string[0] = 0;
       return PNP_COM_FAIL;
   } else
       return PNP_COM_OK;
}

struct device *serialProbe(enum deviceClass probeClass, int probeFlags,
                          struct device *devlist) {
   int fd;
   int temp;
   int pnp_strlen;
   int devicetype=-1;
   unsigned char pnp_string[100];
   char port[20];
   struct termios origattr;
   struct pnp_com_id pnp_id;
   struct serialDevice *serdev;
   struct stat sb;
   int maj, twelve=12;
   int console=-1;
   int stdin_line=-1;
   struct serial_struct si;

   if (probeFlags & PROBE_SAFE) return devlist;

   /* Are we on a serial console? */
   fstat(0,&sb);
   maj = major(sb.st_rdev);
   if (maj != 4 && (maj < 136 || maj > 143)) {
           if (ioctl (0, TIOCLINUX, &twelve) < 0) {
                   if (ioctl (0, TIOCGSERIAL, &si) >= 0) {
                           if (si.line > 0) {
                                   stdin_line = 1 << si.line;
                           } else {
                                   stdin_line = 0;
                           }
                   } else stdin_line = 0;
           }
   }

   fd=open("/dev/console",O_RDWR);
   if (fd != -1) {
           fstat(fd,&sb);
           maj = major(sb.st_rdev);
           if (maj != 4 && (maj < 136 || maj > 143)) {
                   if (ioctl (fd, TIOCLINUX, &twelve) < 0) {
                           if (ioctl (fd, TIOCGSERIAL, &si) >= 0) {
                                   if (si.line > 0) {
                                           console = 1 << si.line;
                                   } else {
                                           console = 0;
                                   }
                           } else console = 0;
                   }
           }
           close(fd);
   }


   if (
       (probeClass == CLASS_UNSPEC) ||
       (probeClass == CLASS_OTHER) ||
       (probeClass == CLASS_MOUSE) ||
       (probeClass == CLASS_MODEM) ||
       (probeClass == CLASS_PRINTER)
       ) {
       int x;

       for (x=0; x<=3 ; x++) {
           struct stat sbuf;
           char lockfile[32];
           if (x==console || x==stdin_line) continue;
           snprintf(port,20,"/dev/ttyS%d",x);

           /* Make sure it's not in use */
           snprintf(lockfile,32,"/var/lock/LCK..ttyS%d",x);
           if (!stat(lockfile,&sbuf))
                continue;
           memset(lockfile,'\0',32);
           if (readlink("/dev/modem",lockfile,32)>0) {
                   if (!strcmp(basename(port),basename(lockfile))) {
                           snprintf(lockfile,32,"/var/lock/LCK..modem");
                           if (!stat(lockfile,&sbuf))
                             continue;
                   }
           }

           if ((fd=open_serial_port(port)) < 0) {
                   continue;
           }
           /* save the current state of the port */
           temp = tcgetattr(fd, &origattr);
           if (temp < 0) {
                   close(fd);
                   continue;
           }

           /* try twiddling RS232 control lines and see if it talks to us */
           devicetype=-1;
           pnp_strlen = 0;
           attempt_pnp_retrieve( fd, pnp_string, &pnp_strlen, sizeof(pnp_string) - 1 );

           /* see if we found any PnP signature */
           if (pnp_strlen != 0) {
               if (*pnp_string == 'M') { /* Legacy mouse */
                       if (probeClass == CLASS_MOUSE || probeClass == CLASS_UNSPEC) {
                               serdev = serialNewDevice(NULL);
                               serdev->class=CLASS_MOUSE;
                               serdev->device=strdup(port+5);
                               serdev->desc=strdup("Generic Serial Mouse");
                               serdev->driver=strdup("generic");
                               if (devlist)
                                 serdev->next = devlist;
                               devlist = (struct device *)serdev;
                               if (probeFlags & PROBE_ONE) {
                                       tcsetattr(fd, TCSANOW, &origattr);
                                       tcflush(fd, TCIOFLUSH);
                                       close(fd);
                                       return devlist;
                               }
                       }
                       tcsetattr(fd, TCSANOW, &origattr);
                       close(fd);
                       continue;
               }
               /* fill in the PnP com structure */
               if (parse_pnp_string( pnp_string, pnp_strlen, &pnp_id )<0) {
                       goto endprobe;
               } else {
                   char *foo;
                   int len;

                   if (debug_level > 0) {
                       printf("PnP ID string for serial device on port %s\n",port);
                       print_pnp_id( pnp_id );
                   }
                   serdev = serialNewDevice(NULL);
                   if (pnp_id.user_name[0]) {
                           serdev->pnpdesc = strdup(pnp_id.user_name);
                       len = strlen(pnp_id.eisa_id)+strlen(pnp_id.product_id)+strlen(pnp_id.user_name)+3;
                       foo = malloc(len);
                       snprintf(foo,len,"%s|%s %s",pnp_id.eisa_id,pnp_id.product_id,pnp_id.user_name);
                   } else {
                       len = strlen(pnp_id.eisa_id)+strlen(pnp_id.product_id)+3;
                       foo = malloc(len);
                       snprintf(foo,len,"%s|%s",pnp_id.eisa_id,pnp_id.product_id);
                   }
                   serdev->desc=strdup(foo);
                   serdev->device=strdup(port+5);
                   serdev->driver=strdup("ignore");
                       serdev->pnpmfr = strdup(pnp_id.eisa_id);
                       serdev->pnpmodel = strdup(pnp_id.product_id);

                   free(foo);
                   foo=pnp_id.product_id;
                   if (pnp_id.driver_id) {
                       if (strstr(pnp_id.driver_id,"PNP"))
                         foo = strstr(pnp_id.driver_id,"PNP")+3;
                           serdev->pnpcompat = strdup(pnp_id.driver_id);
                   }
                   if (!strncmp(foo, "0F", 2))
                     serdev->class = CLASS_MOUSE;
                   else if (!strncmp(foo, "C", 1))
                     serdev->class = CLASS_MODEM;
                   else
                     serdev->class = CLASS_OTHER;
                       if (serdev->class == probeClass || probeClass == CLASS_UNSPEC) {
                               if (devlist)
                                 serdev->next = devlist;
                               devlist = (struct device *)serdev;
                               if (probeFlags & PROBE_ONE) {
                                       tcsetattr(fd, TCSANOW, &origattr);
                                       tcflush(fd, TCIOFLUSH);
                                       close(fd);
                                       return devlist;
                               }
                       } else {
                               serdev->freeDevice(serdev);
                       }
                       goto endprobe;
               }
           } else {
               /* try to find a legacy device */

               temp = find_legacy_mouse(fd);
               if (temp == PNP_COM_FATAL) {
                       goto endprobe;
               } else if (temp == PNP_COM_OK) {
                       if (probeClass == CLASS_UNSPEC || probeClass == CLASS_MOUSE) {
                               serdev=serialNewDevice(NULL);
                               serdev->class = CLASS_MOUSE;
                               serdev->device = strdup(port+5);
                               serdev->driver= strdup("generic");
                               serdev->desc = strdup("Generic Serial Mouse");
                               if (devlist)
                                 serdev->next = devlist;
                               devlist = (struct device *)serdev;
                               if (probeFlags & PROBE_ONE) {
                                       tcsetattr(fd, TCSANOW, &origattr);
                                       tcflush(fd, TCIOFLUSH);
                                       close(fd);
                                       return devlist;
                               }
                       }
                       goto endprobe;
               } else {
                   if (debug_level > 0)
                     printf("Didnt see a legacy mouse, need to ATI it now.\n");

                   temp = find_legacy_modem(fd);
                   if (temp == PNP_COM_FATAL) {
                           goto endprobe;
                   } else if (temp == PNP_COM_OK) {
                       if (debug_level > 0)
                         printf("\nLegacy modem signature seen.\n\n");
                       if (probeClass == CLASS_UNSPEC || probeClass == CLASS_MODEM) {
                               serdev=serialNewDevice(NULL);
                               serdev->class = CLASS_MODEM;
                               serdev->device = strdup(port+5);
                               serdev->driver= strdup("ignore");
                               serdev->desc = strdup("Generic Serial Modem");
                               if (devlist)
                                 serdev->next = devlist;
                               devlist = (struct device *)serdev;
                               if (probeFlags & PROBE_ONE) {
                                       tcsetattr(fd, TCSANOW, &origattr);
                                       tcflush(fd, TCIOFLUSH);
                                       close(fd);
                                       return devlist;
                               }
                       }
                           goto endprobe;
                   } else {
                       if (debug_level > 0)
                         printf("Didnt see a legacy modem, game over.\n");
                   }
               }
           }
endprobe:
           tcsetattr(fd, TCSANOW, &origattr);
           tcflush(fd, TCIOFLUSH);
           close(fd);
       }
   }
   return devlist;
}