Logo Search packages:      
Sourcecode: libgphoto2 version File versions  Download package

serial.c

/****************************************************************************
 *
 * File: serial.c
 *
 * Serial communication layer.
 *
 ****************************************************************************/

/****************************************************************************
 *
 * include files
 *
 ****************************************************************************/

#include "config.h"

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

#include <gphoto2/gphoto2.h>

#ifdef ENABLE_NLS
#  include <libintl.h>
#  undef _
#  define _(String) dgettext (GETTEXT_PACKAGE, String)
#  ifdef gettext_noop
#    define N_(String) gettext_noop (String)
#  else
#    define N_(String) (String)
#  endif
#else
#  define textdomain(String) (String)
#  define gettext(String) (String)
#  define dgettext(Domain,Message) (Message)
#  define dcgettext(Domain,Message,Type) (Message)
#  define bindtextdomain(Domain,Directory) (Domain)
#  define _(String) (String)
#  define N_(String) (String)
#endif

#include "library.h"
#include "canon.h"
#include "serial.h"
#include "util.h"
#include "crc.h"

#ifndef MAX
# define MAX(a, b) ((a) > (b) ? (a) : (b))
#endif
#ifndef MIN
# define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif


#ifdef __GNUC__
# define __unused__ __attribute__((unused))
#else
# define __unused__
#endif


/**
 * serial_flush_input
 * @gdev: serial port to use
 *
 * Dummy function.
 *
 */
static void
serial_flush_input (GPPort __unused__ *gdev)
{
}

/**
 * serial_flush_output
 * @gdev: serial port to use
 *
 * Dummy function.
 *
 */
static void
serial_flush_output (GPPort __unused__ *gdev)
{
}

/**
 * canon_serial_change_speed
 * @gdev: serial port to use
 * @speed: the new speed
 *
 * Changes the speed of the communication.
 *
 * Returns: 1 on success.
 *          0 on any error.
 *
 */

static int
canon_serial_change_speed (GPPort *gdev, int speed)
{
      gp_port_settings settings;

      /* set speed */
      gp_port_get_settings (gdev, &settings);
      settings.serial.speed = speed;
      gp_port_set_settings (gdev, settings);

      usleep (70000);

      return 1;
}


/**
 * canon_serial_get_cts
 * @gdev: serial port to use
 *
 * Gets the status of the CTS (Clear To Send) line on the serial port.
 *
 * CTS is "1" when the camera is ON, and "0" when it is OFF.
 *
 * Returns: 1 on CTS high.
 *          0 on CTS low.
 *
 */
static int
canon_serial_get_cts (GPPort *gdev)
{
      GPLevel level;

      gp_port_get_pin (gdev, PIN_CTS, &level);
      return (level);
}

/**
 * canon_serial_init
 * @camera: Camera object to initialize
 *
 * Initializes the given serial device by setting speed, parity, etc.
 *
 * Returns: %GP_OK
 *
 */

int
canon_serial_init (Camera *camera)
{
      GPPortSettings settings;

      GP_DEBUG ("Initializing the (serial) camera.");

      /* Get the current settings */
      gp_port_get_settings (camera->port, &settings);

      /* Adjust the current settings */
      settings.serial.speed = 9600;
      settings.serial.bits = 8;
      settings.serial.parity = 0;
      settings.serial.stopbits = 1;

      /* Set the new settings */
      gp_port_set_settings (camera->port, settings);

      return GP_OK;
}

/**
 * canon_serial_send
 * @camera: Camera object to work with
 * @buf:    the raw data buffer to send
 * @len:    the length of the buffer
 * @sleep:  time in usec to wait between characters
 *
 * Send the given buffer with given length over the serial line.
 *
 * Returns: 0 on success, -1 on error.
 *
 */
static int
canon_serial_send (Camera *camera, const unsigned char *buf, int len, int sleep)
{
      int i;

      /* the A50 does not like to get too much data in a row at 115200
       * The S10 and S20 do not have this problem */
      if (sleep > 0 && camera->pl->slow_send == 1) {
            for (i = 0; i < len; i++) {
                  gp_port_write (camera->port, (char *) buf, 1);
                  buf++;
                  usleep (sleep);
            }
      } else {
            gp_port_write (camera->port, (char *) buf, len);
      }

      return 0;
}


/**
 * serial_set_timeout
 * @gdev: serial port to use
 * @to:   timeout in milliseconds
 *
 * Sets the timeout, in miliseconds.
 *
 */
static void
serial_set_timeout (GPPort *gdev, int to)
{
      gp_port_set_timeout (gdev, to);
}

/**
 * canon_serial_get_byte
 * @gdev: serial port to use
 *
 * Gets the next byte from the serial line.
 * Actually the function reads chunks of data and keeps them in a cache.
 * Only one byte per call will be returned.
 *
 * Returns: the byte on success, -1 on error.
 *
 */
static int
canon_serial_get_byte (GPPort *gdev)
{
      static unsigned char cache[512];
      static unsigned char *cachep = cache;
      static unsigned char *cachee = cache;
      int recv;

      /* if still data in cache, get it */
      if (cachep < cachee) {
            return (int) *cachep++;
      }

      recv = gp_port_read (gdev, (char *)cache, 1);
      if (recv < 0)           /* An error occurred */
            return -1;

      cachep = cache;
      cachee = cache + recv;

      if (recv) {
            return (int) *cachep++;
      }

      return -1;
}

/* ------------------------- Frame-level processing ------------------------- */

/**
 * canon_serial_send_frame
 * @camera: Camera object to work with
 * @pkt: Data to send to camera
 * @len: Length of packet
 *
 * Sends a frame of data to camera
 *
 * Returns: 1 if canon_serial_send() succeeds, 0 if it fails
 *
 */
static int
canon_serial_send_frame (Camera *camera, const unsigned char *pkt, int len)
{
      static unsigned char buffer[2100];

      /* worst case: two maximum-sized packets (~1020 bytes, full of data
         that needs to be escaped */
      unsigned char *p;

      p = buffer;
      *p++ = CANON_FBEG;
      while (len--) {
            if (p < buffer ||
                (unsigned int)(p - buffer) >= sizeof (buffer) - 1) {
                  GP_DEBUG ("FATAL ERROR: send buffer overflow");
                  return -1;
            }
            if (*pkt != CANON_FBEG && *pkt != CANON_FEND && *pkt != CANON_ESC)
                  *p++ = *pkt++;
            else {
                  *p++ = CANON_ESC;
                  *p++ = *pkt++ ^ CANON_XOR;
            }
      }
      *p++ = CANON_FEND;

      return !canon_serial_send (camera, buffer, p - buffer, USLEEP2);
}

/**
 * canon_serial_recv_frame
 * @camera: Camera object to work with
 * @len:    to receive the length of the buffer
 *
 * Receive a frame from the camera
 *
 * Returns: a buffer containing a frame from the camera, or NULL on error.
 *          On success, @len will contain the length of the buffer.
 *
 */
static unsigned char *
canon_serial_recv_frame (Camera *camera, int *len)
{
      static unsigned char buffer[5000];

      /* more than enough :-) (allow for a few run-together packets) */
      unsigned char *p = buffer;
      int c;

      while ((c = canon_serial_get_byte (camera->port)) != CANON_FBEG) {
            if (c == -1)
                  return NULL;
      }
      while ((c = canon_serial_get_byte (camera->port)) != CANON_FEND) {
            if (c < 0)
                  return NULL;
            if (c == CANON_ESC)
                  c = canon_serial_get_byte (camera->port) ^ CANON_XOR;
            if (p < buffer ||
                (unsigned int)(p - buffer) >= sizeof (buffer)) {
                  GP_DEBUG ("FATAL ERROR: receive buffer overflow");
                  return NULL;
            }
            *p++ = c;
      }

      /* If you don't want to see the data dumped, change the frontend to
       * set a lower debug level
       */
      gp_log (GP_LOG_DATA, "canon", "RECV (without CANON_FBEG and CANON_FEND bytes)");
      gp_log_data ("canon", (char *)buffer, p - buffer);

      if (len)
            *len = p - buffer;
      return buffer;
}

/* ------------------------ Packet-level processing ------------------------- */

/**
 * canon_serial_send_packet
 * @camera: Camera object to work with
 * @type: 
 * @seq: 
 * @pkt: data to send to camera
 * @len: length of data
 *
 * frames a packet (generates CRC, packs with sequence number and
 * length) and sends it to the camera through the serial port using
 * canon_serial_send_frame().
 *
 * Returns: status from canon_serial_send_frame()
 *
 */
static int
canon_serial_send_packet (Camera *camera, unsigned char type, unsigned char seq,
                    unsigned char *pkt, int len)
{
      unsigned char *hdr = pkt - PKT_HDR_LEN;
      unsigned short crc;

      hdr[PKT_TYPE] = type;
      hdr[PKT_SEQ] = seq;
      hdr[PKT_LEN_LSB] = len & 0xff;
      hdr[PKT_LEN_MSB] = len >> 8;

      if (type == PKT_NACK) {
            hdr[PKT_TYPE] = PKT_ACK;
            hdr[PKT_TYPE + 1] = '\xff';   /* PKTACK_NACK; */
      }

      if (type == PKT_UPLOAD_EOT) {
            hdr[PKT_TYPE] = PKT_EOT;
            hdr[PKT_TYPE + 1] = 0x3;
            len = 2;
      }

      if (type == PKT_EOT || type == PKT_ACK || type == PKT_NACK)
            len = 2;    /* @@@ hack */
      crc = canon_psa50_gen_crc (hdr, len + PKT_HDR_LEN);
      pkt[len] = crc & 0xff;
      pkt[len + 1] = crc >> 8;

      return canon_serial_send_frame (camera, hdr, len + PKT_HDR_LEN + 2);
}

/**
 * canon_serial_recv_packet
 * @camera: Camera object to work with
 * @type: Type of packet
 * @seq: Sequence number of packet
 * @len: length of data received
 *
 * Receives a packet from the serial port using
 * canon_serial_send_frame(), decodes frame information (type,
 * sequence number, and length), and returns it stripped of frame
 * information.
 *
 * Returns: packet data (or NULL if failure). Type in @type, sequence
 *   number in @seq, and length in @len.
 *
 */
static unsigned char *
canon_serial_recv_packet (Camera *camera, unsigned char *type, unsigned char *seq, int *len)
{
      unsigned char *pkt;
      unsigned short crc;
      int raw_length, length = 0;

      pkt = canon_serial_recv_frame (camera, &raw_length);
      if (!pkt)
            return NULL;
      if (raw_length < PKT_HDR_LEN) {
            GP_DEBUG ("ERROR: packet truncated");
            return NULL;
      }
      if (pkt[PKT_TYPE] == PKT_MSG) {
            length = pkt[PKT_LEN_LSB] | (pkt[PKT_LEN_MSB] << 8);
            if (length + PKT_HDR_LEN > raw_length - 2) {
                  GP_DEBUG ("ERROR: invalid length");
                  /*fprintf(stderr,"Sending NACK");
                     canon_serial_send_packet(PKT_NACK,camera->pl->seq_rx++,camera->pl->psa50_eot+PKT_HDR_LEN,0); */
                  camera->pl->receive_error = ERROR_RECEIVED;
                  return NULL;
            }
      }
      crc = pkt[raw_length - 2] | (pkt[raw_length - 1] << 8);
      if (!canon_psa50_chk_crc (pkt, raw_length - 2, crc)) {
            GP_DEBUG ("ERROR: CRC error");
            return NULL;
      }
      *type = pkt[PKT_TYPE];
      if (seq)
            *seq = pkt[PKT_SEQ];
      if (len)
            *len = length;
      if (*type == PKT_ACK || *type == PKT_EOT)
            return pkt;
      return pkt + PKT_HDR_LEN;
}

/* ----------------------- Message-level processing ------------------------ */


/**
 * canon_serial_wait_for_ack
 * @camera: Camera object to work with
 *
 * Waits for an "ACK" from the camera.
 *
 * Returns:
 *  1 : ACK received
 *  0 : communication error (no reply received for example)
 * -1 : NACK received.
 */
static int
canon_serial_wait_for_ack (Camera *camera)
{
      unsigned char *pkt;
      unsigned char type, seq, old_seq;
      int len;

      while (1) {
            pkt = canon_serial_recv_packet (camera, &type, &seq, &len);
            if (!pkt)
                  return 0;
            if (seq == camera->pl->seq_tx && type == PKT_ACK) {
                  if (pkt[2] == PKTACK_NACK) {
                        GP_DEBUG ("ERROR: NACK received");
                        return -1;
                  }
                  camera->pl->seq_tx++;
                  return 1;
            }
            old_seq = '\0';
            if (type == PKT_EOT) {
                  old_seq = pkt[0];
                  if (camera->pl->receive_error == NOERROR) {
                        GP_DEBUG ("Old EOT received, sending corresponding ACK");
                        if (!canon_serial_send_packet
                            (camera, PKT_ACK, old_seq,
                             camera->pl->psa50_eot + PKT_HDR_LEN, 0))
                              return 0;
                        pkt = canon_serial_recv_packet (camera, &type, &seq, &len);
                        if (!pkt)
                              return 0;
                        if (seq == old_seq && type == PKT_ACK) {
                              if (pkt[2] == PKTACK_NACK) {
                                    GP_DEBUG ("Old EOT acknowledged");
                                    return -1;
                              }
                              return 1;
                        }
                  }
            }
            /* error already aknowledged, we skip the following ones */
            if (camera->pl->receive_error == ERROR_RECEIVED) {
                  if (!canon_serial_send_packet
                      (camera, PKT_NACK, old_seq, camera->pl->psa50_eot + PKT_HDR_LEN,
                       0))
                        return 0;
                  return 1;
            }

            GP_DEBUG ("ERROR: ACK format or sequence error, retrying");
            GP_DEBUG ("Sending NACK");
            canon_serial_send_packet (camera, PKT_NACK, camera->pl->seq_rx++,
                                camera->pl->psa50_eot + PKT_HDR_LEN, 0);
            camera->pl->receive_error = ERROR_RECEIVED;

/*
 * just keep on trying. protocol seems to retransmit EOTs, so we may get
 * some old EOTs when we're actually expecting ACKs.
 */
      }
}

/**
 * canon_serial_send_msg
 * @camera: Camera object to work with
 * @mtype:  message type.
 * @dir:    direction.
 * @ap:     message payload (list of arguments, see 'man va_start'
 *
 * Sends a message to the camera.
 *
 * Returns:
 *   -1 on error
 *    0 if canon_serial_send_packet() fails
 *    1 on good ACK received
 */
static int
canon_serial_send_msg (Camera *camera, unsigned char mtype, unsigned char dir, va_list * ap)
{
      unsigned char buffer[MAX_PKT_PAYLOAD + 2];      /* allow space for CRC */
      unsigned char upload_buffer[MAX_PKT_PAYLOAD + 2];
      unsigned char *pkt, *pkt2, *pos;
      int total, good_ack, try;

      memset (buffer, 0, PKT_HDR_LEN + MSG_HDR_LEN);

      pkt = buffer + PKT_HDR_LEN;
      pkt[MSG_02] = 2;
      pkt[MSG_MTYPE] = mtype;
      pkt[MSG_DIR] = dir;

      pos = pkt + MSG_HDR_LEN;
      total = 0;

      while (1) {
            const unsigned char *str;
            int len;

            str = va_arg (*ap, unsigned char *);

            if (!str)
                  break;
            len = va_arg (*ap, int);

            if (pos + len - pkt > MAX_MSG_SIZE && camera->pl->uploading != 1) {
                  GP_DEBUG ("FATAL ERROR: message too big (%i)", pos + len - pkt);
                  return -1;
            }
            memcpy (pos, str, len);
            pos += len;
      }

      total = pos - pkt;

      pkt[MSG_LEN_LSB] = total & 0xff;
      pkt[MSG_LEN_MSB] = total >> 8;

      if (camera->pl->uploading == 1) {
            memset (upload_buffer, 0, PKT_HDR_LEN + MSG_HDR_LEN);
            pkt2 = upload_buffer;
            memcpy (pkt2, pkt + UPLOAD_DATA_BLOCK, total - UPLOAD_DATA_BLOCK);
            for (try = 0; try < MAX_TRIES; try++) {
                  canon_serial_send_packet (camera, PKT_MSG, 0, pkt, UPLOAD_DATA_BLOCK);
                  canon_serial_send_packet (camera, PKT_MSG, 0x1, pkt2,
                                      total - UPLOAD_DATA_BLOCK);
                  if (!canon_serial_send_packet
                      (camera, PKT_UPLOAD_EOT, camera->pl->seq_tx,
                       camera->pl->psa50_eot + PKT_HDR_LEN, 1))
                        return 0;
                  if (!canon_serial_send_packet
                      (camera, PKT_UPLOAD_EOT, camera->pl->seq_tx,
                       camera->pl->psa50_eot + PKT_HDR_LEN, 1))
                        return 0;

                  good_ack = canon_serial_wait_for_ack (camera);
                  if (good_ack == 1)
                        return good_ack;
            }
            return -1;
      } else {
            pkt[MSG_LEN_LSB] = total & 0xff;
            pkt[MSG_LEN_MSB] = total >> 8;
            for (try = 1; try < MAX_TRIES; try++) {
                  if (!canon_serial_send_packet (camera, PKT_MSG, 0, pkt, total))
                        return 0;
                  if (!canon_serial_send_packet
                      (camera, PKT_EOT, camera->pl->seq_tx,
                       camera->pl->psa50_eot + PKT_HDR_LEN, 1))
                        return 0;
                  good_ack = canon_serial_wait_for_ack (camera);
                  if (good_ack == -1) {
                        GP_DEBUG ("NACK received, retrying command");
                  } else if (good_ack == 1) {
                        return good_ack;
                  } else {
                        GP_DEBUG ("No ACK received, retrying command");
                        if (try == 2) {
                              /* is the camera still there? */
                              if (!canon_serial_send_packet
                                  (camera, PKT_EOT, camera->pl->seq_tx,
                                   camera->pl->psa50_eot + PKT_HDR_LEN, 0))
                                    return 0;
                              good_ack = canon_serial_wait_for_ack (camera);
                              if (good_ack == 0) {
                                    camera->pl->receive_error = FATAL_ERROR;
                                    GP_DEBUG ("ERROR: FATAL ERROR");
                                    clear_readiness (camera);
                                    return -1;
                              }
                        }
                  }
            }
            return -1;
      }
}

/**
 * canon_serial_recv_msg
 * @camera: Camera object to work with
 * @mtype:  message type.
 * @dir:    direction.
 * @total:  payload length (set by this function).
 * @context: context for error reporting
 *
 * Receives a message from the camera.
 *
 * See the "Protocol" file for an explanation of the various
 * elements needed to handle a message.
 *
 * Returns:
 *  char* pointer to the message payload; NULL on failure.
 *
 */
static unsigned char *
canon_serial_recv_msg (Camera *camera, unsigned char mtype, unsigned char dir, unsigned int *total,
                   GPContext *context)
{
      static unsigned char *msg = NULL;
      static int msg_size = 512;    /* initial allocation/2 */
      unsigned char *frag;
      unsigned char type, seq;
      int len, length = 0, msg_pos = 0;

      while (1) {
            frag = canon_serial_recv_packet (camera, &type, NULL, &len);
            if (!frag)
                  return NULL;
            if (type == PKT_MSG)
                  break;
            /* uploading is special */
/*              if (type == PKT_ACK && mtype == 0x3 && dir == 0x21) break; */
            if (type == PKT_EOT) {
                  GP_DEBUG ("Old EOT received sending corresponding ACK");
                  canon_serial_send_packet (camera, PKT_ACK, frag[0],
                                      camera->pl->psa50_eot + PKT_HDR_LEN, 0);
            }
            GP_DEBUG ("ERROR: protocol error, retrying");
      }
      /* we keep the fragment only if there was no error */
      if (camera->pl->receive_error == NOERROR) {
            length = frag[MSG_LEN_LSB] | (frag[MSG_LEN_MSB] << 8);
            /* while uploading we expect 2 ACKs and a message 0x3 0x21
             * not always in the same order */
/*
            if (type == PKT_ACK && mtype == 0x3 && dir == 0x21) {
                  GP_DEBUG("ignoring ACK received while waiting for MSG");
                  return frag;
            } 
*/
            if (len < MSG_HDR_LEN || frag[MSG_02] != 2) {
                  GP_DEBUG ("ERROR: message format error");
                  return NULL;
            }

            if (frag[MSG_MTYPE] != mtype || frag[MSG_DIR] != dir) {
                  if (frag[MSG_MTYPE] == '\x01' && frag[MSG_DIR] == '\x00'
                      && memcmp (frag + 12, "\x30\x00\x00\x30", 4)) {
                        gp_context_error (context,
                                      _("Battery exhausted, camera off."));
                        camera->pl->receive_error = ERROR_LOWBATT;
                  } else {
                        gp_context_error (context, _("ERROR: unexpected message"));
                  }
                  return NULL;
            }
            frag += MSG_HDR_LEN;
            len -= MSG_HDR_LEN;
      }
      while (1) {
            if (camera->pl->receive_error == NOERROR) {
                  if (msg_pos + len > length) {
                        gp_context_error (context, _("ERROR: message overrun"));
                        return NULL;
                  }
                  if (msg_pos + len > msg_size || !msg) {
                        msg_size *= 2;
                        msg = realloc (msg, msg_size);
                        if (!msg) {
                              perror ("realloc");
                              exit (1);
                        }
                  }
                  memcpy (msg + msg_pos, frag, len);
                  msg_pos += len;
            }
            frag = canon_serial_recv_packet (camera, &type, &seq, &len);
            if (!frag)
                  return NULL;
            if (type == PKT_EOT) {
                  /* in case of error we don't want to stop as the camera will send
                     the 1st packet of the sequence again */
                  if (camera->pl->receive_error == ERROR_RECEIVED) {
                        camera->pl->seq_rx = seq;
                        canon_serial_send_packet (camera, PKT_NACK, camera->pl->seq_rx,
                                            camera->pl->psa50_eot + PKT_HDR_LEN,
                                            0);
                        camera->pl->receive_error = ERROR_ADDRESSED;
                  } else {
                        if (seq == camera->pl->seq_rx)
                              break;
                        gp_context_error (context, _("ERROR: out of sequence."));
                        return NULL;
                  }
            }
            if (type != PKT_MSG && camera->pl->receive_error == NOERROR) {
                  gp_context_error (context, _("ERROR: unexpected packet type."));
                  return NULL;
            }
            if (type == PKT_EOT && camera->pl->receive_error == ERROR_RECEIVED) {
                  camera->pl->receive_error = ERROR_ADDRESSED;
            }
            if (type == PKT_MSG && camera->pl->receive_error == ERROR_ADDRESSED) {
                  msg_pos = 0;
                  length = frag[MSG_LEN_LSB] | (frag[MSG_LEN_MSB] << 8);
                  if (len < MSG_HDR_LEN || frag[MSG_02] != 2) {
                        gp_context_error (context, _("ERROR: message format error."));
                        return NULL;
                  }

                  if (frag[MSG_MTYPE] != mtype || frag[MSG_DIR] != dir) {
                        if (frag[MSG_MTYPE] == '\x01' && frag[MSG_DIR] == '\x00'
                            && memcmp (frag + 12, "\x30\x00\x00\x30", 4)) {
                              gp_context_error (context,
                                            _("Battery exhausted, camera off."));
                              camera->pl->receive_error = ERROR_LOWBATT;
                        } else {
                              gp_context_error (context,
                                            _("ERROR: unexpected message2."));
                        }
                        return NULL;
                  }
                  frag += MSG_HDR_LEN;
                  len -= MSG_HDR_LEN;
                  camera->pl->receive_error = NOERROR;
            }
      }
      if (camera->pl->receive_error == ERROR_ADDRESSED) {
            camera->pl->receive_error = NOERROR;
      }
      if (camera->pl->receive_error == NOERROR) {
            /*we want to be sure the camera U N D E R S T A N D S our packets */
            if (camera->pl->uploading == 1 && camera->pl->md->model == CANON_CLASS_1)
                  camera->pl->slow_send = 1;
            if (!canon_serial_send_packet
                (camera, PKT_ACK, camera->pl->seq_rx++,
                 camera->pl->psa50_eot + PKT_HDR_LEN, 0)) {
                  if (camera->pl->uploading == 1
                      && camera->pl->md->model == CANON_CLASS_1)
                        camera->pl->slow_send = 0;
                  return NULL;
            }
            if (camera->pl->uploading == 1 && camera->pl->md->model == CANON_CLASS_1)
                  camera->pl->slow_send = 0;
            if (total)
                  *total = msg_pos;
            return msg;
      }

      return NULL;
}

/**
 * canon_serial_dialogue:
 * @camera: camera with which to communicate
 * @context: context for error reporting
 * @mtype : type
 * @dir   : direction
 * @len   : length of the received payload
 * @Varargs: The rest of the arguments will be put together to
 *          fill up the payload of the request message.
 *
 * Higher level function: sends a message and waits for a
 * reply from the camera.
 *
 * Payload: each argument after "len" goes by 2: the variable itself,
 * and the next argument has to be its length. You also have to finish
 * the list by a "NULL".
 *
 * Example: To send a string called "name" :
 * canon_serial_dialogue(0x05,0x12,&len,name,strlen(name)+1,NULL);
 *
 * Returns: buffer received from canon_serial_recv_msg(), NULL if failure
 *
 */
unsigned char *
canon_serial_dialogue (Camera *camera, GPContext *context, unsigned char mtype,
                   unsigned char dir, unsigned int *len, ...)
{
      va_list ap;
      int okay, try;
      unsigned char *good_ack;

      for (try = 1; try < MAX_TRIES; try++) {
            va_start (ap, len);
            okay = canon_serial_send_msg (camera, mtype, dir, &ap);
            va_end (ap);
            if (!okay)
                  return NULL;
            /* while uploading we receive 2 ACKs and 1 confirmation message
             * The first ACK has already been received if we are here */
            if (camera->pl->uploading == 1) {
                  camera->pl->seq_tx--;
                  good_ack =
                        canon_serial_recv_msg (camera, mtype, dir ^ DIR_REVERSE, len,
                                           context);
                  if (!good_ack)
                        return NULL;
                  if (good_ack[0] == camera->pl->seq_tx && good_ack[1] == 0x5) {
                        GP_DEBUG ("ACK received waiting for the confirmation message");
                        good_ack =
                              canon_serial_recv_msg (camera, mtype,
                                                 dir ^ DIR_REVERSE, len,
                                                 context);
                  } else {
                        okay = canon_serial_wait_for_ack (camera);
                        if (okay == 1)
                              return good_ack;
                  }
            } else
                  good_ack =
                        canon_serial_recv_msg (camera, mtype, dir ^ DIR_REVERSE, len,
                                           context);

            if (good_ack)
                  return good_ack;
            if (camera->pl->receive_error == NOERROR) {
                  GP_DEBUG ("Resending message...");
                  camera->pl->seq_tx--;
            }
            if (camera->pl->receive_error == FATAL_ERROR)
                  break;
      }
      return NULL;
}

/* ----------------------- Command-level processing ------------------------ */


/**
 * canon_serial_end:
 * @camera: the camera to switch off
 *
 * Switches the @camera off
 *
 * Returns: %GP_OK
 *
 */
static int
canon_serial_end (Camera *camera)
{
      canon_serial_send (camera, (unsigned char *)"\xC0\x00\x02\x55\x2C\xC1", 6, USLEEP2);
      canon_serial_send (camera, (unsigned char *)"\xC0\x00\x04\x01\x00\x00\x00\x24\xC6\xC1", 8, USLEEP2);
      return GP_OK;
}

/**
 * canon_serial_off:
 * @camera: the camera to switch off
 *
 * Switches the #camera off, and resets the serial driver to 9600 bauds,
 * in order to be ready to switch the camera back on again if wanted.
 * Should better be named psa50_serial_off
 *
 * Returns: %GP_OK
 *
 */
int
canon_serial_off (Camera *camera)
{
      canon_serial_send (camera, (unsigned char *)"\xC0\x00\x02\x55\x2C\xC1", 6, USLEEP2);
      canon_serial_send (camera, (unsigned char *)"\xC0\x00\x04\x01\x00\x00\x00\x24\xC6\xC1", 8, USLEEP2);
      canon_serial_change_speed (camera->port, 9600);
      return GP_OK;
}



/**
 * canon_serial_error_type
 * @camera: Camera object to work with
 *
 * logs a debug message corresponding
 * to the error encountered
 *
 */
void
canon_serial_error_type (Camera *camera)
{
      switch (camera->pl->receive_error) {
            case ERROR_LOWBATT:
                  GP_DEBUG ("ERROR: no battery left, Bailing out!");
                  break;
            case FATAL_ERROR:
                  GP_DEBUG ("ERROR: camera connection lost!");
                  break;
            default:
                  GP_DEBUG ("ERROR: malformed message");
                  break;
      }
}

/**
 * canon_serial_put_file
 * @camera: Camera object to work with
 * @file: CameraFile object to upload
 * @destname: name file should have on camera
 * @destpath: pathname for directory to put file
 * @context: context for error reporting
 *
 * Uploads file to @camera via serial port
 *
 * Returns: gphoto2 error code
 *
 */
int
canon_serial_put_file (Camera *camera, CameraFile *file, char *destname, char *destpath,
                   GPContext *context)
{
      unsigned char *msg;
      char filename[64];
      char buf[4096];
      int offset = 0;
      char offset2[4];
      int block_len;
      char block_len2[4];
      unsigned int sent = 0;
      int i, j = 0;
      unsigned int len, hdr_len;
      unsigned long int size;
      const char *data, *name;
      unsigned int id;

      camera->pl->uploading = 1;
      gp_file_get_name (file, &name);
      for (i = 0; name[i]; i++)
            filename[i] = toupper (name[i]);
      filename[i] = '\0';

      hdr_len = HDR_FIXED_LEN + strlen (name) + strlen (destpath);

      gp_file_get_data_and_size (file, &data, &size);

      id = gp_context_progress_start (context, size, _("Uploading file..."));
      while (sent < size) {

            if (size < DATA_BLOCK)
                  block_len = size;
            else if ((size - sent < DATA_BLOCK))
                  block_len = size - sent;
            else
                  block_len = DATA_BLOCK;

            offset = sent;

            for (i = 0; i < 4; i++) {
                  offset2[i] = (offset >> (8 * i)) & 0xff;
                  block_len2[i] = (block_len >> (8 * i)) & 0xff;
            }

            for (i = 0; i < DATA_BLOCK; i++) {
                  buf[i] = data[j];
                  j++;
            }

            msg = canon_serial_dialogue (camera, context, 0x3, 0x11, &len,
                                   "\x02\x00\x00\x00", 4, offset2, 4, block_len2, 4,
                                   destpath, strlen (destpath), destname,
                                   strlen (destname) + 1, buf, block_len, NULL);
            if (!msg) {
                  camera->pl->uploading = 0;
                  return GP_ERROR;
            }
            sent += block_len;
            gp_context_progress_update (context, id, sent);
      }
      gp_context_progress_stop (context, id);
      camera->pl->uploading = 0;
      return GP_OK;
}

/**
 * canon_serial_get_file:
 * @camera: camera to lock keys on
 * @name: name of file to fetch
 * @length: to receive length of image data
 * @context: context for error reporting
 *
 * Get a file from a USB_connected Canon camera.
 *
 * Returns: buffer containing file data (or NULL on failure); length
 * in @length.
 *
 */
unsigned char *
canon_serial_get_file (Camera *camera, const char *name, unsigned int *length, GPContext *context)
{
      unsigned char *file = NULL;
      unsigned char *msg;
      unsigned char name_len;
      unsigned int total = 0, expect = 0, size, id;
      unsigned int len;

      if (camera->pl->receive_error == FATAL_ERROR) {
            GP_DEBUG ("ERROR: can't continue a fatal error condition detected");
            return NULL;
      }
      name_len = strlen (name) + 1;
      msg = canon_serial_dialogue (camera, context, 0x1, 0x11, &len, "\x00\x00\x00\x00", 5,
                             &name_len, 1, "\x00", 2, name, strlen (name) + 1, NULL);
      if (!msg) {
            canon_serial_error_type (camera);
            return NULL;
      }
      id = gp_context_progress_start (context, le32atoh (msg + 4), _("Getting file..."));
      while (msg) {
            if (len < 20 || le32atoh (msg)) {
                  break;
            }
            if (!file) {
                  total = le32atoh (msg + 4);

                  if (total > camera->pl->md->max_picture_size) {
                        GP_DEBUG ("ERROR: %d is too big", total);
                        break;
                  }
                  file = malloc (total);
                  if (!file) {
                        perror ("malloc");
                        break;
                  }
                  if (length)
                        *length = total;
            }
            size = le32atoh (msg + 12);
            if (le32atoh (msg + 8) != expect || expect + size > total || size > len - 20) {
                  GP_DEBUG ("ERROR: doesn't fit");
                  break;
            }
            memcpy (file + expect, msg + 20, size);
            expect += size;
            gp_context_progress_update (context, id, expect);
            if ((expect == total) != le32atoh (msg + 16)) {
                  GP_DEBUG ("ERROR: end mark != end of data");
                  break;
            }
            if (expect == total) {
                  gp_context_progress_stop (context, id);
                  return file;
            }
            msg = canon_serial_recv_msg (camera, 0x1, 0x21, &len, context);
      }
      free (file);
      file = NULL;
      return NULL;
}

/**
 * canon_serial_get_dirents:
 * @camera: camera to initialize
 * @dirent_data: to receive directory data
 * @dirents_length: to receive length of @dirent_data
 * @path: pathname of directory to list
 * @context: context for error reporting
 *
 * Lists a directory.
 *
 * Returns: gphoto2 error code
 *
 */
int
canon_serial_get_dirents (Camera *camera, unsigned char **dirent_data,
                    unsigned int *dirents_length, const char *path, GPContext *context)
{
      unsigned char *p, *temp_ch, *data = NULL;
      unsigned int mallocd_bytes, total_size;

      *dirent_data = NULL;

      /* fetch all directory entries, the first one is a little special */
      p = canon_serial_dialogue (camera, context, 0xb, 0x11, dirents_length, "", 1, path,
                           strlen (path) + 1, "\x00", 2, NULL);
      if (p == NULL) {
            gp_context_error (context,
                          _("canon_serial_get_dirents: "
                          "canon_serial_dialogue failed to fetch directory entries"));
            return GP_ERROR;
      }

      /* In the RS232 implementation, we should never get less than 5 bytes */
      if (*dirents_length < 5) {
            gp_context_error (context,
                          _("canon_serial_get_dirents: "
                          "Initial dirent packet too short (only %i bytes)"),
                          *dirents_length);
            return GP_ERROR;
      }

      /* don't use GP_DEBUG since we log this with GP_LOG_DATA */
      gp_log (GP_LOG_DATA, "canon",
            "canon_serial_get_dirents: "
            "dirent packet received from canon_serial_dialogue:");
      gp_log_data ("canon", (char *)p, *dirents_length);

      /* the first five bytes is only for the RS232 implementation
       * of this command, we do not need to copy them so therefore
       * we don't need to malloc() them either
       */
      mallocd_bytes = MAX (1024, *dirents_length - 5);
      data = malloc (mallocd_bytes);
      if (!data) {
            gp_context_error (context,
                          _("canon_serial_get_dirents: "
                          "Could not allocate %i bytes of memory"), mallocd_bytes);
            return GP_ERROR_NO_MEMORY;
      }

      /* the first five bytes is only for the RS232 implementation
       * of this command, do not copy them
       */
      memcpy (data, p + 5, (*dirents_length - 5));
      total_size = *dirents_length;

      /* p[4] indicates this is not the last packet,
       * read additional packets until there are no more
       * directory entries to read
       */
      while (!p[4]) {
            GP_DEBUG ("p[4] is %i", (int) p[4]);
            p = canon_serial_recv_msg (camera, 0xb, 0x21, dirents_length, context);
            if (p == NULL) {
                  gp_context_error (context,
                                _("canon_serial_get_dirents: "
                                "Failed to read another directory entry"));
                  free (data);
                  data = NULL;
                  return GP_ERROR;
            }

            /* don't use GP_DEBUG since we log this with GP_LOG_DATA */
            gp_log (GP_LOG_DATA, "canon",
                  "canon_serial_get_dirents: "
                  "dirent packet received from canon_serial_recv_msg:");
            gp_log_data ("canon", (char *)p, *dirents_length);

            /* the first five bytes is only for the RS232 implementation,
             * don't count them when checking dirent size
             */
            if (*dirents_length - 5 < CANON_MINIMUM_DIRENT_SIZE) {
                  gp_context_error (context,
                                _("canon_serial_get_dirents: "
                                "Truncated directory entry received"));
                  free (data);
                  data = NULL;
                  return GP_ERROR;
            }

            /* check if we need to allocate some more memory,
             * the first five bytes is only for the RS232
             * implementation of this command, don't need to
             * malloc for them.  note that we ensured earlier in this
             * function that *dirents_length >= 5.
             */
            if (total_size + (unsigned int)(*dirents_length - 5) > mallocd_bytes) {
                  /* we allocate 1024 bytes chunks instead
                   * of the exact number of bytes needed.
                   * this is OK since we will free this
                   * before returning from canon_int_list_directory
                   * (our caller).
                   */
                  mallocd_bytes += MAX (1024, *dirents_length);

                  /* check if we are reading unrealistic ammounts
                   * of directory entries so that we don't loop
                   * forever. 1024 * 1024 is picked out of the blue.
                   */
                  if (mallocd_bytes > 1024 * 1024) {
                        gp_context_error (context,
                                      _("canon_serial_get_dirents: "
                                      "Too many dirents, we must be looping."));
                        free (data);
                        data = NULL;
                        return GP_ERROR;
                  }

                  temp_ch = realloc (data, mallocd_bytes);
                  if (!temp_ch) {
                        gp_context_error (context,
                                      _("canon_serial_get_dirents: "
                                      "Could not resize dirent buffer "
                                      "to %i bytes"), mallocd_bytes);
                        free (data);
                        data = NULL;
                        return GP_ERROR;
                  }
                  data = temp_ch;
            }

            /* the first five bytes is only for the RS232
             * implementation of this command, don't copy them.
             */
            memcpy (data + total_size, p + 5, (*dirents_length - 5));
            total_size += (*dirents_length - 5);
      }
      GP_DEBUG ("OK - this was last dirent");

      *dirent_data = data;
      return GP_OK;
}

/**
 * canon_serial_ready:
 * @camera: camera to get ready
 * @context: context for error reporting
 *
 * serial part of canon_int_ready
 *
 * Returns: gphoto2 error code
 *
 */
int
canon_serial_ready (Camera *camera, GPContext *context)
{
      unsigned char type, seq;
      int good_ack, speed, try, len, i;
      unsigned char *pkt;
      int res;
      char cam_id_str[2000];
      unsigned int id;

      GP_DEBUG ("canon_int_ready()");

      serial_set_timeout (camera->port, 900);   /* 1 second is the delay for awakening the camera */
      serial_flush_input (camera->port);
      serial_flush_output (camera->port);

      camera->pl->receive_error = NOERROR;

      /* First of all, we must check if the camera is already on */
      /*      cts=canon_serial_get_cts();
         GP_DEBUG("cts : %i",cts);
         if (cts==32) {  CTS == 32  when the camera is connected. */
      if (camera->pl->first_init == 0 && camera->pl->cached_ready == 1) {
            /* First case, the serial speed of the camera is the same as
             * ours, so let's try to send a ping packet : */
            if (!canon_serial_send_packet
                (camera, PKT_EOT, camera->pl->seq_tx, camera->pl->psa50_eot + PKT_HDR_LEN,
                 0))
                  return GP_ERROR;
            good_ack = canon_serial_wait_for_ack (camera);
            GP_DEBUG ("good_ack = %i", good_ack);
            if (good_ack == 0) {
                  /* no answer from the camera, let's try
                   * at the speed saved in the settings... */
                  speed = camera->pl->speed;
                  if (speed != 9600) {
                        if (!canon_serial_change_speed (camera->port, speed)) {
                              gp_context_error (context, _("Error changing speed."));
                        }
                  }
                  if (!canon_serial_send_packet
                      (camera, PKT_EOT, camera->pl->seq_tx,
                       camera->pl->psa50_eot + PKT_HDR_LEN, 0))
                        return GP_ERROR;
                  good_ack = canon_serial_wait_for_ack (camera);
                  if (good_ack == 0) {
                        gp_context_status (context, _("Resetting protocol..."));
                        canon_serial_off (camera);
                        sleep (3);  /* The camera takes a while to switch off */
                        return canon_int_ready (camera, context);
                  }
                  if (good_ack == -1) {
                        GP_DEBUG ("Received a NACK!");
                        return GP_ERROR;
                  }
                  gp_context_status (context, _("Camera OK."));
                  return 1;
            }
            if (good_ack == -1) {
                  GP_DEBUG ("Received a NACK !\n");
                  return GP_ERROR;
            }
            GP_DEBUG ("Camera replied to ping, proceed.\n");
            return GP_OK;
      }

      /* Camera was off... */

      gp_context_status (context, _("Looking for camera ..."));
      if (camera->pl->receive_error == FATAL_ERROR) {
            /* we try to recover from an error
               we go back to 9600bps */
            if (!canon_serial_change_speed (camera->port, 9600)) {
                  GP_DEBUG ("ERROR: Error changing speed");
                  return GP_ERROR;
            }
            camera->pl->receive_error = NOERROR;
      }
      id = gp_context_progress_start (context, MAX_TRIES, _("Trying to contact camera..."));
      for (try = 0; try < MAX_TRIES; try++) {
            if (canon_serial_send (camera, (unsigned char *)"\x55\x55\x55\x55\x55\x55\x55\x55", 8, USLEEP1)
                < 0) {
                  gp_context_error (context, _("Communication error 1"));
                  return GP_ERROR;
            }
            pkt = canon_serial_recv_frame (camera, &len);
            gp_context_progress_update (context, id, try + 1);
            if (pkt)
                  break;
      }
      gp_context_progress_stop (context, id);
      if (try == MAX_TRIES) {
            gp_context_error (context, _("No response from camera"));
            return GP_ERROR;
      }
      if (!pkt) {
            gp_context_error (context, _("No response from camera"));
            return GP_ERROR;
      }
      if (len < 40 && strncmp ((char *)pkt + 26, "Canon", 5)) {
            gp_context_error (context, _("Unrecognized response"));
            return GP_ERROR;
      }
      strncpy (cam_id_str, (char *)pkt + 26, sizeof (cam_id_str) - 1);

      GP_DEBUG ("cam_id_str : '%s'", cam_id_str);

      camera->pl->first_init = 0;

      /* Compare what the camera identified itself as with our list
       * of known models
       *
       * We iterate over the model list testing id_str, even if we
       * don't actually use id_str, but serial_id_string.
       */
      for (i = 0; models[i].id_str != NULL; i++) {
            if ((models[i].serial_id_string != NULL) && 
                !strcmp (models[i].serial_id_string, cam_id_str)) {
                  GP_DEBUG ("canon_serial_ready: Serial ID string matches '%s'",
                          models[i].serial_id_string);
                  gp_context_status (context, _("Detected a \"%s\" aka \"%s\""), 
                                 models[i].id_str, models[i].serial_id_string);
                  camera->pl->md = (struct canonCamModelData *) &models[i];
                  break;
            }
      }

      if (models[i].id_str == NULL) {
            gp_context_error (context, _("Unknown model \"%s\""), cam_id_str);
            return GP_ERROR_MODEL_NOT_FOUND;
      }

      /* take care of some model specific things */
      switch (camera->pl->md->model) {
            case CANON_CLASS_3:
            case CANON_CLASS_1:
                  if (camera->pl->speed > 57600)
                        camera->pl->slow_send = 1;
                  break;
            default:
                  break;
      }

      /* 5 seconds  delay should  be enough for   big flash cards.   By
       * experience, one or two seconds is too  little, as a large flash
       * card needs more access time. */
      serial_set_timeout (camera->port, 5000);
      (void) canon_serial_recv_packet (camera, &type, &seq, NULL);
      if (type != PKT_EOT || seq) {
            gp_context_error (context, _("Bad EOT"));
            return GP_ERROR;
      }
      camera->pl->seq_tx = 0;
      camera->pl->seq_rx = 1;
      if (!canon_serial_send_frame (camera, (unsigned char *)"\x00\x05\x00\x00\x00\x00\xdb\xd1", 8)) {
            gp_context_error (context, _("Communication error 2"));
            return GP_ERROR;
      }
      res = 0;
      switch (camera->pl->speed) {
            case 9600:
                  res = canon_serial_send_frame (camera, SPEED_9600, 12);
                  break;
            case 19200:
                  res = canon_serial_send_frame (camera, SPEED_19200, 12);
                  break;
            case 38400:
                  res = canon_serial_send_frame (camera, SPEED_38400, 12);
                  break;
            case 57600:
                  res = canon_serial_send_frame (camera, SPEED_57600, 12);
                  break;
            case 115200:
                  res = canon_serial_send_frame (camera, SPEED_115200, 12);
                  break;
      }

      if (!res || !canon_serial_send_frame (camera, (unsigned char *)"\x00\x04\x01\x00\x00\x00\x24\xc6", 8)) {
            gp_context_error (context, _("Communication error 3"));
            return GP_ERROR;
      }
      speed = camera->pl->speed;
      gp_context_status (context, _("Changing speed... wait..."));
      if (!canon_serial_wait_for_ack (camera))
            return GP_ERROR;
      if (speed != 9600) {
            if (!canon_serial_change_speed (camera->port, speed)) {
                  gp_context_status (context, _("Error changing speed"));
            } else {
                  GP_DEBUG ("speed changed");
            }

      }
      for (try = 1; try < MAX_TRIES; try++) {
            canon_serial_send_packet (camera, PKT_EOT, camera->pl->seq_tx,
                                camera->pl->psa50_eot + PKT_HDR_LEN, 0);
            if (!canon_serial_wait_for_ack (camera)) {
                  gp_context_status (context,
                                 _("Error waiting for ACK during initialization, retrying"));
            } else
                  break;
      }

      if (try == MAX_TRIES) {
            gp_context_error (context, _("Error waiting ACK during initialization"));
            return GP_ERROR;
      }

      gp_context_status (context, _("Connected to camera"));
      /* Now is a good time to ask the camera for its owner
       * name (and Model String as well)  */
      canon_int_identify_camera (camera, context);
      canon_int_get_time (camera, NULL, context);

      return GP_OK;
}

/**
 * canon_serial_get_thumbnail:
 * @camera: camera to work on
 * @name: file name (complete canon path) of file to get thumbnail for
 * @data: pointer to data pointer
 * @length: pointer to data length
 * @context: context for error reporting
 *
 * This is just the serial specific part extracted from the older
 * canon_get_thumbnail() routine. 
 *
 * Returns: gphoto2 error code
 *
 */
int
canon_serial_get_thumbnail (Camera *camera, const char *name, unsigned char **data,
                      unsigned int *length, GPContext *context)
{
      unsigned int expect = 0, size, payload_length, total_file_size;
      unsigned int total = 0, id;
      unsigned char *msg;

      CON_CHECK_PARAM_NULL (length);
      CON_CHECK_PARAM_NULL (data);
      *length = 0;
      *data = NULL;

      if (camera->pl->receive_error == FATAL_ERROR) {
            gp_context_error (context,
                          _("ERROR: a fatal error condition was detected, can't continue "));
            return GP_ERROR;
      }

      payload_length = strlen (name) + 1;
      msg = canon_serial_dialogue (camera, context, 0x1, 0x11, &total_file_size,
                             "\x01\x00\x00\x00\x00", 5, &payload_length, 1, "\x00", 2,
                             name, strlen (name) + 1, NULL);
      if (!msg) {
            canon_serial_error_type (camera);
            return GP_ERROR;
      }


      total = le32atoh (msg + 4);
      if (total > 2000000) {  /* 2 MB thumbnails ? unlikely ... */
            gp_context_error (context, _("ERROR: %d is too big"), total);
            return GP_ERROR;
      }
      *data = malloc (total);
      if (!*data) {
            perror ("malloc");
            return GP_ERROR;
      }
      *length = total;

      id = gp_context_progress_start (context, total, _("Getting thumbnail..."));
      while (msg) {
            if (total_file_size < 20 || le32atoh (msg)) {
                  return GP_ERROR;
            }
            size = le32atoh (msg + 12);
            if (le32atoh (msg + 8) != expect || expect + size > total
                || size > total_file_size - 20) {
                  GP_DEBUG ("ERROR: doesn't fit");
                  return GP_ERROR;
            }
            memcpy (*data + expect, msg + 20, size);
            expect += size;
            gp_context_progress_update (context, id, expect);
            if ((expect == total) != le32atoh (msg + 16)) {
                  GP_DEBUG ("ERROR: end mark != end of data");
                  return GP_ERROR;
            }
            if (expect == total) {
                  /* We finished receiving the file. Parse the header and
                     return just the thumbnail */
                  break;
            }
            msg = canon_serial_recv_msg (camera, 0x1, 0x21, &total_file_size, context);
      }
      gp_context_progress_stop (context, id);
      return GP_OK;
}

/****************************************************************************
 *
 * End of file: serial.c
 *
 ****************************************************************************/

/*
 * Local Variables:
 * c-file-style:"linux"
 * indent-tabs-mode:t
 * End:
 */

Generated by  Doxygen 1.6.0   Back to index