/[pcsclite]/trunk/Drivers/ccid/src/ccid_serial.c
ViewVC logotype

Diff of /trunk/Drivers/ccid/src/ccid_serial.c

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1496 by rousseau, Mon May 2 13:37:13 2005 UTC revision 2983 by rousseau, Sun Jun 8 08:31:47 2008 UTC
# Line 3  Line 3 
3   * Copyright (C) 2001-2004 Ludovic Rousseau <ludovic.rousseau@free.fr>   * Copyright (C) 2001-2004 Ludovic Rousseau <ludovic.rousseau@free.fr>
4   *   *
5   * Thanks to Niki W. Waibel <niki.waibel@gmx.net> for a prototype version   * Thanks to Niki W. Waibel <niki.waibel@gmx.net> for a prototype version
6   *   *
7      This library is free software; you can redistribute it and/or      This library is free software; you can redistribute it and/or
8      modify it under the terms of the GNU Lesser General Public      modify it under the terms of the GNU Lesser General Public
9      License as published by the Free Software Foundation; either      License as published by the Free Software Foundation; either
# Line 14  Line 14 
14      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15      Lesser General Public License for more details.      Lesser General Public License for more details.
16    
17      You should have received a copy of the GNU Lesser General Public          You should have received a copy of the GNU Lesser General Public License
18      License along with this library; if not, write to the Free Software          along with this library; if not, write to the Free Software Foundation,
19      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA          Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20   */   */
21    
22  /*  /*
# Line 33  Line 33 
33  #include <sys/time.h>  #include <sys/time.h>
34  #include <sys/types.h>  #include <sys/types.h>
35  #include <sys/ioctl.h>  #include <sys/ioctl.h>
36  #include <PCSC/ifdhandler.h>  #include <ifdhandler.h>
37    
38  #include "defs.h"  #include "defs.h"
39  #include "ccid_ifdhandler.h"  #include "ccid_ifdhandler.h"
# Line 42  Line 42 
42  #include "ccid.h"  #include "ccid.h"
43  #include "utils.h"  #include "utils.h"
44  #include "commands.h"  #include "commands.h"
45    #include "parser.h"
46    
47  #define SYNC 0x03  #define SYNC 0x03
48  #define CTRL_ACK 0x06  #define CTRL_ACK 0x06
# Line 108  typedef struct Line 109  typedef struct
109          /*@null@*/ char *device;          /*@null@*/ char *device;
110    
111          /*          /*
112             * Number of slots using the same device
113             */
114            int real_nb_opened_slots;
115            int *nb_opened_slots;
116    
117            /*
118             * does the reader echoes the serial communication bytes?
119             */
120            int echo;
121    
122            /*
123           * serial communication buffer           * serial communication buffer
124           */           */
125          unsigned char buffer[GEMPCTWIN_MAXBUF];          unsigned char buffer[GEMPCTWIN_MAXBUF];
# Line 132  typedef struct Line 144  typedef struct
144  /* The _serialDevice structure must be defined before including ccid_serial.h */  /* The _serialDevice structure must be defined before including ccid_serial.h */
145  #include "ccid_serial.h"  #include "ccid_serial.h"
146    
147  unsigned int SerialDataRates[] = {  /* data rates supported by the GemPC Twin (serial and PCMCIA) */
148                  10753,  unsigned int SerialTwinDataRates[] = { ISO_DATA_RATES, 0 };
149                  14337,  
150                  15625,  /* data rates supported by the GemPC PinPad, GemCore Pos Pro & SIM Pro */
151                  17204,  unsigned int SerialExtendedDataRates[] = { ISO_DATA_RATES, 500000, 0 };
152                  20833,  
153                  21505,  /* data rates supported by the secondary slots on the GemCore Pos Pro & SIM Pro */
154                  23438,  unsigned int SerialCustomDataRates[] = { GEMPLUS_CUSTOM_DATA_RATES, 0 };
                 25806,  
                 28674,  
                 31250,  
                 32258,  
                 34409,  
                 39063,  
                 41667,  
                 43011,  
                 46875,  
                 52083,  
                 53763,  
                 57348,  
                 62500,  
                 64516,  
                 68817,  
                 71685,  
                 78125,  
                 83333,  
                 86022,  
                 93750,  
                 104167,  
                 107527,  
                 114695,  
                 125000,  
                 129032,  
                 143369,  
                 156250,  
                 166667,  
                 172043,  
                 215054,  
                 229391,  
                 250000,  
                 344086,  
                 0  
         };  
155    
156  /* no need to initialize to 0 since it is static */  /* no need to initialize to 0 since it is static */
157  static _serialDevice serialDevice[CCID_DRIVER_MAX_READERS];  static _serialDevice serialDevice[CCID_DRIVER_MAX_READERS];
# Line 188  static int get_bytes(unsigned int reader Line 165  static int get_bytes(unsigned int reader
165    
166    
167  /*****************************************************************************  /*****************************************************************************
168   *   *
169   *                              WriteSerial: Send bytes to the card reader   *                              WriteSerial: Send bytes to the card reader
170   *   *
171   *****************************************************************************/   *****************************************************************************/
172  status_t WriteSerial(unsigned int reader_index, unsigned int length,  status_t WriteSerial(unsigned int reader_index, unsigned int length,
173          unsigned char *buffer)          unsigned char *buffer)
174  {  {
175          int i;          unsigned int i;
176          unsigned char lrc;          unsigned char lrc;
177          unsigned char low_level_buffer[GEMPCTWIN_MAXBUF];          unsigned char low_level_buffer[GEMPCTWIN_MAXBUF];
178    
 #ifdef DEBUG_LEVEL_COMM  
179          char debug_header[] = "-> 123456 ";          char debug_header[] = "-> 123456 ";
180    
181          sprintf(debug_header, "-> %06X ", reader_index);          sprintf(debug_header, "-> %06X ", reader_index);
 #endif  
182    
183          if (length > GEMPCTWIN_MAXBUF-3)          if (length > GEMPCTWIN_MAXBUF-3)
184          {          {
# Line 225  status_t WriteSerial(unsigned int reader Line 200  status_t WriteSerial(unsigned int reader
200                  lrc ^= low_level_buffer[i];                  lrc ^= low_level_buffer[i];
201          low_level_buffer[length+2] = lrc;          low_level_buffer[length+2] = lrc;
202    
 #ifdef DEBUG_LEVEL_COMM  
203          DEBUG_XXD(debug_header, low_level_buffer, length+3);          DEBUG_XXD(debug_header, low_level_buffer, length+3);
 #endif  
204    
205          if (write(serialDevice[reader_index].fd, low_level_buffer,          if (write(serialDevice[reader_index].fd, low_level_buffer,
206                  length+3) != length+3)                  length+3) != length+3)
# Line 241  status_t WriteSerial(unsigned int reader Line 214  status_t WriteSerial(unsigned int reader
214    
215    
216  /*****************************************************************************  /*****************************************************************************
217   *   *
218   *                              ReadSerial: Receive bytes from the card reader   *                              ReadSerial: Receive bytes from the card reader
219   *   *
220   *****************************************************************************/   *****************************************************************************/
221  status_t ReadSerial(unsigned int reader_index,  status_t ReadSerial(unsigned int reader_index,
222          /*@unused@*/ unsigned int *length, unsigned char *buffer)          unsigned int *length, unsigned char *buffer)
223  {  {
224          unsigned char c;          unsigned char c;
225          int rv;          int rv;
# Line 255  status_t ReadSerial(unsigned int reader_ Line 228  status_t ReadSerial(unsigned int reader_
228          int i;          int i;
229    
230          /* we get the echo first */          /* we get the echo first */
231          echo = TRUE;          echo = serialDevice[reader_index].echo;
232    
233  start:  start:
234          DEBUG_COMM("start");          DEBUG_COMM("start");
# Line 269  start: Line 242  start:
242                  goto sync;                  goto sync;
243    
244          if (c >= 0x80)          if (c >= 0x80)
245          {          {
246                  DEBUG_COMM2("time request: 0x%02X", c);                  DEBUG_COMM2("time request: 0x%02X", c);
247                  goto start;                  goto start;
248          }          }
# Line 340  ack: Line 313  ack:
313          if ((rv = get_bytes(reader_index, buffer+5, to_read-5)) != STATUS_SUCCESS)          if ((rv = get_bytes(reader_index, buffer+5, to_read-5)) != STATUS_SUCCESS)
314                  return rv;                  return rv;
315    
316  #ifdef DEBUG_LEVEL_COMM          DEBUG_XXD("frame: ", buffer, to_read);
                 DEBUG_XXD("frame: ", buffer, to_read);  
 #endif  
317    
318          /* lrc */          /* lrc */
319          DEBUG_COMM("lrc");          DEBUG_COMM("lrc");
# Line 362  ack: Line 333  ack:
333                  goto start;                  goto start;
334          }          }
335    
336            /* length of data read */
337            *length = to_read;
338    
339          return STATUS_SUCCESS;          return STATUS_SUCCESS;
340  } /* ReadSerial */  } /* ReadSerial */
341    
342    
343  /*****************************************************************************  /*****************************************************************************
344   *   *
345   *                              get_bytes: get n bytes   *                              get_bytes: get n bytes
346   *   *
347   *****************************************************************************/   *****************************************************************************/
# Line 421  int get_bytes(unsigned int reader_index, Line 395  int get_bytes(unsigned int reader_index,
395    
396    
397  /*****************************************************************************  /*****************************************************************************
398   *   *
399   *                              ReadChunk: read a minimum number of bytes   *                              ReadChunk: read a minimum number of bytes
400   *   *
401   *****************************************************************************/   *****************************************************************************/
# Line 435  static int ReadChunk(unsigned int reader Line 409  static int ReadChunk(unsigned int reader
409          struct timeval t;          struct timeval t;
410          int i, rv = 0;          int i, rv = 0;
411          int already_read;          int already_read;
 #ifdef DEBUG_LEVEL_COMM  
412          char debug_header[] = "<- 123456 ";          char debug_header[] = "<- 123456 ";
413    
414          sprintf(debug_header, "<- %06X ", reader_index);          sprintf(debug_header, "<- %06X ", reader_index);
 #endif  
415    
416          already_read = 0;          already_read = 0;
417          while (already_read < min_length)          while (already_read < min_length)
# Line 470  static int ReadChunk(unsigned int reader Line 442  static int ReadChunk(unsigned int reader
442                          return -1;                          return -1;
443                  }                  }
444    
 #ifdef DEBUG_LEVEL_COMM  
445                  DEBUG_XXD(debug_header, buffer + already_read, rv);                  DEBUG_XXD(debug_header, buffer + already_read, rv);
 #endif  
446    
447                  already_read += rv;                  already_read += rv;
448                  DEBUG_COMM3("read: %d, to read: %d", already_read,                  DEBUG_COMM3("read: %d, to read: %d", already_read,
# Line 484  static int ReadChunk(unsigned int reader Line 454  static int ReadChunk(unsigned int reader
454    
455    
456  /*****************************************************************************  /*****************************************************************************
457   *   *
458   *                              OpenSerial: open the port   *                              OpenSerial: open the port
459   *   *
460   *****************************************************************************/   *****************************************************************************/
# Line 495  status_t OpenSerial(unsigned int reader_ Line 465  status_t OpenSerial(unsigned int reader_
465          DEBUG_COMM3("Reader index: %X, Channel: %d", reader_index, channel);          DEBUG_COMM3("Reader index: %X, Channel: %d", reader_index, channel);
466    
467          /*          /*
468           * Conversion of old-style ifd-hanler 1.0 CHANNELID           * Conversion of old-style ifd-hanler 1.0 CHANNELID
469           */           */
470          if (channel == 0x0103F8)          if (channel == 0x0103F8)
471                  channel = 1;                  channel = 1;
# Line 521  status_t OpenSerial(unsigned int reader_ Line 491  status_t OpenSerial(unsigned int reader_
491  } /* OpenSerial */  } /* OpenSerial */
492    
493  /*****************************************************************************  /*****************************************************************************
494   *   *
495     *                              set_ccid_descriptor: init ccid descriptor
496     *                              depending on reader type specified in device.
497     *
498     *                              return: STATUS_UNSUCCESSFUL,
499     *                                              STATUS_SUCCESS,
500     *                                              -1 (Reader already used)
501     *
502     *****************************************************************************/
503    static status_t set_ccid_descriptor(unsigned int reader_index,
504            const char *reader_name, const char *dev_name)
505    {
506            long readerID;
507            int i;
508            int already_used = FALSE;
509            static int previous_reader_index = -1;
510    
511            readerID = GEMPCTWIN;
512            if (0 == strcasecmp(reader_name,"GemCorePOSPro"))
513                    readerID = GEMCOREPOSPRO;
514            else if (0 == strcasecmp(reader_name,"GemCoreSIMPro"))
515                    readerID = GEMCORESIMPRO;
516            else if (0 == strcasecmp(reader_name,"GemPCPinPad"))
517                    readerID = GEMPCPINPAD;
518    
519            /* check if the same channel is not already used to manage multi-slots readers*/
520            for (i = 0; i < CCID_DRIVER_MAX_READERS; i++)
521            {
522                    if (serialDevice[i].device
523                            && strcmp(serialDevice[i].device, dev_name) == 0)
524                    {
525                            already_used = TRUE;
526    
527                            DEBUG_COMM2("%s already used. Multi-slot reader?", dev_name);
528                            break;
529                    }
530            }
531    
532            /* this reader is already managed by us */
533            if (already_used)
534            {
535                    if ((previous_reader_index != -1)
536                            && serialDevice[previous_reader_index].device
537                            && (strcmp(serialDevice[previous_reader_index].device, dev_name) == 0)
538                            && serialDevice[previous_reader_index].ccid.bCurrentSlotIndex < serialDevice[previous_reader_index].ccid.bMaxSlotIndex)
539                    {
540                            /* we reuse the same device and the reader is multi-slot */
541                            serialDevice[reader_index] = serialDevice[previous_reader_index];
542    
543                            *serialDevice[reader_index].nb_opened_slots += 1;
544                            serialDevice[reader_index].ccid.bCurrentSlotIndex++;
545                            DEBUG_INFO2("Opening slot: %d",
546                                            serialDevice[reader_index].ccid.bCurrentSlotIndex);
547                            switch (readerID)
548                            {
549                                    case GEMCOREPOSPRO:
550                                    case GEMCORESIMPRO:
551                                            serialDevice[reader_index].ccid.arrayOfSupportedDataRates = SerialCustomDataRates;
552                                            serialDevice[reader_index].ccid.dwMaxDataRate = 125000;
553                                            break;
554    
555                                    /* GemPC Twin or GemPC Card */
556                                    default:
557                                            serialDevice[reader_index].ccid.arrayOfSupportedDataRates = SerialTwinDataRates;
558                                            serialDevice[reader_index].ccid.dwMaxDataRate = 344086;
559                                            break;
560                            }
561                            goto end;
562                    }
563                    else
564                    {
565                            DEBUG_CRITICAL2("Trying to open too many slots on %s", dev_name);
566                            return STATUS_UNSUCCESSFUL;
567                    }
568    
569            }
570    
571            /* Common to all readers */
572            serialDevice[reader_index].ccid.real_bSeq = 0;
573            serialDevice[reader_index].ccid.pbSeq = &serialDevice[reader_index].ccid.real_bSeq;
574            serialDevice[reader_index].real_nb_opened_slots = 1;
575            serialDevice[reader_index].nb_opened_slots = &serialDevice[reader_index].real_nb_opened_slots;
576            serialDevice[reader_index].ccid.bCurrentSlotIndex = 0;
577    
578            serialDevice[reader_index].ccid.dwMaxCCIDMessageLength = 271;
579            serialDevice[reader_index].ccid.dwMaxIFSD = 254;
580            serialDevice[reader_index].ccid.dwFeatures = 0x00010230;
581            serialDevice[reader_index].ccid.dwDefaultClock = 4000;
582    
583            serialDevice[reader_index].buffer_offset = 0;
584            serialDevice[reader_index].buffer_offset_last = 0;
585    
586            serialDevice[reader_index].ccid.readerID = readerID;
587            serialDevice[reader_index].ccid.bPINSupport = 0x0;
588            serialDevice[reader_index].ccid.dwMaxDataRate = 344086;
589            serialDevice[reader_index].ccid.bMaxSlotIndex = 0;
590            serialDevice[reader_index].ccid.arrayOfSupportedDataRates = SerialTwinDataRates;
591            serialDevice[reader_index].ccid.dwSlotStatus = IFD_ICC_PRESENT;
592            serialDevice[reader_index].echo = TRUE;
593    
594            /* change some values depending on the reader */
595            switch (readerID)
596            {
597                    case GEMCOREPOSPRO:
598                            serialDevice[reader_index].ccid.bMaxSlotIndex = 4;      /* 5 slots */
599                            serialDevice[reader_index].ccid.arrayOfSupportedDataRates = SerialExtendedDataRates;
600                            serialDevice[reader_index].echo = FALSE;
601                            serialDevice[reader_index].ccid.dwMaxDataRate = 500000;
602                            break;
603    
604                    case GEMCORESIMPRO:
605                            serialDevice[reader_index].ccid.bMaxSlotIndex = 1; /* 2 slots */
606                            serialDevice[reader_index].ccid.arrayOfSupportedDataRates = SerialExtendedDataRates;
607                            serialDevice[reader_index].echo = FALSE;
608                            serialDevice[reader_index].ccid.dwMaxDataRate = 500000;
609                            break;
610    
611                    case GEMPCPINPAD:
612                            serialDevice[reader_index].ccid.bPINSupport = 0x03;
613                            serialDevice[reader_index].ccid.arrayOfSupportedDataRates = SerialExtendedDataRates;
614                            serialDevice[reader_index].ccid.dwMaxDataRate = 500000;
615                            break;
616            }
617    
618    end:
619            /* memorise the current reader_index so we can detect
620             * a new OpenSerialByName on a multi slot reader */
621            previous_reader_index = reader_index;
622    
623            /* we just created a secondary slot on a multi-slot reader */
624            if (already_used)
625                    return STATUS_SECONDARY_SLOT;
626    
627            return STATUS_SUCCESS;
628    } /* set_ccid_descriptor  */
629    
630    
631    /*****************************************************************************
632     *
633   *                              OpenSerialByName: open the port   *                              OpenSerialByName: open the port
634   *   *
635   *****************************************************************************/   *****************************************************************************/
636  status_t OpenSerialByName(unsigned int reader_index, char *dev_name)  status_t OpenSerialByName(unsigned int reader_index, char *dev_name)
637  {  {
638          struct termios current_termios;          struct termios current_termios;
         int i;  
639          unsigned int reader = reader_index;          unsigned int reader = reader_index;
640            char reader_name[TOKEN_MAX_VALUE_SIZE] = "GemPCTwin";
641            char *p;
642            status_t ret;
643    
644          DEBUG_COMM3("Reader index: %X, Device: %s", reader_index, dev_name);          DEBUG_COMM3("Reader index: %X, Device: %s", reader_index, dev_name);
645    
646          /* check if the same channel is not already used */          /* parse dev_name using the pattern "device:name" */
647          for (i=0; i<CCID_DRIVER_MAX_READERS; i++)          p = strchr(dev_name, ':');
648            if (p)
649          {          {
650                  if (serialDevice[i].device &&                  /* copy the second part of the string */
651                          strcmp(serialDevice[i].device, dev_name) == 0)                  strncpy(reader_name, p+1, sizeof(reader_name));
652                  {  
653                          DEBUG_CRITICAL2("Device %s already in use", dev_name);                  /* replace ':' by '\0' so that dev_name only contains the device name */
654                          return STATUS_UNSUCCESSFUL;                  *p = '\0';
                 }  
655          }          }
656    
657            ret = set_ccid_descriptor(reader_index, reader_name, dev_name);
658            if (STATUS_UNSUCCESSFUL == ret)
659                    return STATUS_UNSUCCESSFUL;
660    
661            /* secondary slot so do not physically open the device */
662            if (STATUS_SECONDARY_SLOT == ret)
663                    return STATUS_SUCCESS;
664    
665          serialDevice[reader].fd = open(dev_name, O_RDWR | O_NOCTTY);          serialDevice[reader].fd = open(dev_name, O_RDWR | O_NOCTTY);
666    
667          if (-1 == serialDevice[reader].fd)          if (-1 == serialDevice[reader].fd)
# Line 620  status_t OpenSerialByName(unsigned int r Line 738  status_t OpenSerialByName(unsigned int r
738                  return STATUS_UNSUCCESSFUL;                  return STATUS_UNSUCCESSFUL;
739          }          }
740    
741          serialDevice[reader].ccid.real_bSeq = 0;          /* perform a command to be sure a Gemplus reader is connected
         serialDevice[reader].ccid.pbSeq = &serialDevice[reader].ccid.real_bSeq;  
         serialDevice[reader].ccid.readerID = GEMPCTWIN;  
         serialDevice[reader].ccid.dwMaxCCIDMessageLength = 271;  
         serialDevice[reader].ccid.dwMaxIFSD = 254;  
         serialDevice[reader].ccid.dwFeatures = 0x00010230;  
         serialDevice[reader].ccid.bPINSupport = 0x0;  
         serialDevice[reader].ccid.dwDefaultClock = 4000;  
         serialDevice[reader].ccid.dwMaxDataRate = 344086;  
         serialDevice[reader].ccid.bMaxSlotIndex = 0;  
         serialDevice[reader].ccid.bCurrentSlotIndex = 0;  
         serialDevice[reader].ccid.arrayOfSupportedDataRates = SerialDataRates;  
   
         serialDevice[reader].buffer_offset = 0;  
         serialDevice[reader].buffer_offset_last = 0;  
   
         /* perform a command to be sure a GemPC Twin reader is connected  
742           * get the reader firmware */           * get the reader firmware */
743          {          {
744                  unsigned char tx_buffer[] = { 0x02 };                  unsigned char tx_buffer[] = { 0x02 };
# Line 650  status_t OpenSerialByName(unsigned int r Line 752  status_t OpenSerialByName(unsigned int r
752                          rx_buffer, &rx_length))                          rx_buffer, &rx_length))
753                  {                  {
754                          DEBUG_CRITICAL("Get firmware failed. Maybe the reader is not connected");                          DEBUG_CRITICAL("Get firmware failed. Maybe the reader is not connected");
755                            CloseSerial(reader_index);
756                          return STATUS_UNSUCCESSFUL;                          return STATUS_UNSUCCESSFUL;
757                  }                  }
758    
# Line 672  status_t OpenSerialByName(unsigned int r Line 775  status_t OpenSerialByName(unsigned int r
775                          rx_buffer, &rx_length))                          rx_buffer, &rx_length))
776                  {                  {
777                          DEBUG_CRITICAL("Change card movement notification failed.");                          DEBUG_CRITICAL("Change card movement notification failed.");
778                            CloseSerial(reader_index);
779                          return STATUS_UNSUCCESSFUL;                          return STATUS_UNSUCCESSFUL;
780                  }                  }
781          }          }
# Line 681  status_t OpenSerialByName(unsigned int r Line 785  status_t OpenSerialByName(unsigned int r
785    
786    
787  /*****************************************************************************  /*****************************************************************************
788   *   *
789   *                              CloseSerial: close the port   *                              CloseSerial: close the port
790   *   *
791   *****************************************************************************/   *****************************************************************************/
# Line 689  status_t CloseSerial(unsigned int reader Line 793  status_t CloseSerial(unsigned int reader
793  {  {
794          unsigned int reader = reader_index;          unsigned int reader = reader_index;
795    
796          close(serialDevice[reader].fd);          /* device not opened */
797          serialDevice[reader].fd = -1;          if (NULL == serialDevice[reader_index].device)
798                    return STATUS_UNSUCCESSFUL;
799    
800          free(serialDevice[reader].device);          DEBUG_COMM2("Closing serial device: %s", serialDevice[reader_index].device);
801          serialDevice[reader].device = NULL;  
802            /* Decrement number of opened slot */
803            (*serialDevice[reader_index].nb_opened_slots)--;
804    
805            /* release the allocated ressources for the last slot only */
806            if (0 == *serialDevice[reader_index].nb_opened_slots)
807            {
808                    DEBUG_COMM("Last slot closed. Release resources");
809    
810                    close(serialDevice[reader].fd);
811                    serialDevice[reader].fd = -1;
812    
813                    free(serialDevice[reader].device);
814                    serialDevice[reader].device = NULL;
815            }
816    
817          return STATUS_SUCCESS;          return STATUS_SUCCESS;
818  } /* CloseSerial */  } /* CloseSerial */

Legend:
Removed from v.1496  
changed lines
  Added in v.2983

  ViewVC Help
Powered by ViewVC 1.1.5