/* Copyright 1999-2003 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. * * probe serial port for PnP/Legacy devices */ #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 <libgen.h> #include <sys/time.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/ioctl.h> #include <sys/sysmacros.h> #include <asm/types.h> #include <linux/serial.h> #include "serial.h" #include "kudzu.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 */ 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; } /* * wait_input - wait until there is data available on fd, * for the length of time specified by *timo (indefinite * if timo is NULL). */ static 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; } static int open_serial_port( char *port ) { int fd; DEBUG("opening serial port %s...", port); fd = open( port, O_RDWR | O_NONBLOCK); if (fd < 0) { DEBUG("failed.\n"); return -1; } else { DEBUG("successful.\n"); } /* reset file so it is no longer in non-blocking mode */ if (fcntl(fd, F_SETFL, 0) < 0) { close(fd); DEBUG("Failed to set port to non-blocking mode\n"); return -1; } return fd; } /* <0 means ioctl error occurred */ static int get_serial_lines( int fd ) { int modem_lines; ioctl(fd, TIOCMGET, &modem_lines); return modem_lines; } /* <0 means ioctl error occurred */ static 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 */ static int setup_serial_port( int fd, int nbits, struct termios *attr ) { DEBUG("setting up serial port\n"); 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; if (cfsetospeed( attr, B1200)) return -1; if (cfsetispeed( attr, B1200)) return -1; 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. */ static 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 */ static int init_pnp_com_seq1( int fd ) { int modem_lines; int temp; int dsr_status; int rc = PNP_COM_OK; struct termios portattr; DEBUG("initializing 1st PNP sequence\n"); 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); /* Wait for response, 1st 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 */ static 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; DEBUG("Querying ATI9 info from modem\n"); 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 */ DEBUG("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; DEBUG("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 */ static 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; DEBUG("looking for a legacy mouse\n"); /* 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') { DEBUG("Found legacy mouse\n"); 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 */ static 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; DEBUG("looking for a legacy modem\n"); /* 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 */ DEBUG("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 */ static 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; DEBUG("Attempting to read PNP ID string\n"); /* 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_FAIL; } 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 3 seconds */ if (time(NULL)-starttime > 3 ) done = 1; if (pnp_index >= pnp_stringbuf_size) done = 1; } pnp_string[pnp_index] = 0; *pnp_len=pnp_index; return PNP_COM_OK; } /* parse the PnP ID string into components */ static 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); } } static int attempt_pnp_retrieve(int fd, char *pnp_string, int *pnp_strlen, int pnp_stringbuf_size) { int pnp_probe_status; struct pnp_com_id pnp_id; int 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 ); DEBUG("\nPNP string = |%s|\n\n",pnp_string); 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; } /* normal PNP detection has failed. */ /* 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; DEBUG("Probing for serial ports\n"); 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) { #ifdef __powerpc__ // we could have gotten an error for another reason - like EINVAL // skipping ttyS0 on PPC - which is where most modems reside if (errno == ENOTTY) { #endif if (ioctl (fd, TIOCGSERIAL, &si) >= 0) { if (si.line > 0) { console = 1 << si.line; } else { console = 0; #ifdef __powerpc__ } #endif } } 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)) { DEBUG("Port %s in use, skipping probe.\n", port); 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)) { DEBUG("Port %s in use, skipping probe.\n", port); continue; } } } if ((fd=open_serial_port(port)) < 0) { continue; } /* save the current state of the port */ temp = tcgetattr(fd, &origattr); if (temp < 0) { DEBUG("unable to retrieve port attributes...no port present?\n"); 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->type=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; DEBUG("PnP ID string for serial device on port %s\n",port); 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->type = CLASS_MOUSE; else if (!strncmp(foo, "C", 1)) serdev->type = CLASS_MODEM; else if (!strncmp(pnp_id.class_name, "Modem", 5)) serdev->type = CLASS_MODEM; else serdev->type = CLASS_OTHER; if (serdev->type == 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 { DEBUG("No PNP data received.\n"); /* 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_MOUSE) { serdev=serialNewDevice(NULL); serdev->type = 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 { DEBUG("Didn't see a legacy mouse.\n"); temp = find_legacy_modem(fd); if (temp == PNP_COM_FATAL) { goto endprobe; } else if (temp == PNP_COM_OK) { DEBUG("Legacy modem signature seen.\n"); if (probeClass & CLASS_MODEM) { serdev=serialNewDevice(NULL); serdev->type = 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 { DEBUG("Didnt see a legacy modem, game over.\n"); } } } endprobe: DEBUG("Restoring original port attributes\n"); tcsetattr(fd, TCSANOW, &origattr); tcflush(fd, TCIOFLUSH); close(fd); } } return devlist; }