]> oss.titaniummirror.com Git - tinyos-2.x.git/blobdiff - support/sdk/c/serialsource.c
Merge devel code into the trunk.
[tinyos-2.x.git] / support / sdk / c / serialsource.c
diff --git a/support/sdk/c/serialsource.c b/support/sdk/c/serialsource.c
new file mode 100644 (file)
index 0000000..5f777f3
--- /dev/null
@@ -0,0 +1,832 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <termios.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/time.h>
+#include <stdio.h>
+#ifdef __CYGWIN__
+#include <windows.h>
+#include <io.h>
+#else
+#include <stdint.h>
+#endif
+
+/* C implementation of the mote serial protocol. See
+   net.tinyos.packet.Packetizer for more details */
+
+#undef DEBUG
+
+#include "serialsource.h"
+#include "serialprotocol.h"
+
+typedef int bool;
+
+enum {
+#ifndef __CYGWIN__
+  FALSE = 0,
+  TRUE = 1,
+#endif
+  BUFSIZE = 256,
+  MTU = 256,
+  ACK_TIMEOUT = 1000000, /* in us */
+  SYNC_BYTE = SERIAL_HDLC_FLAG_BYTE,
+  ESCAPE_BYTE = SERIAL_HDLC_CTLESC_BYTE,
+
+  P_ACK = SERIAL_SERIAL_PROTO_ACK,
+  P_PACKET_ACK = SERIAL_SERIAL_PROTO_PACKET_ACK,
+  P_PACKET_NO_ACK = SERIAL_SERIAL_PROTO_PACKET_NOACK,
+  P_UNKNOWN = SERIAL_SERIAL_PROTO_PACKET_UNKNOWN
+};
+
+struct packet_list
+{
+  uint8_t *packet;
+  int len;
+  struct packet_list *next;
+};
+
+struct serial_source {
+  int fd;
+  bool non_blocking;
+  void (*message)(serial_source_msg problem);
+
+  /* Receive state */
+  struct {
+    uint8_t buffer[BUFSIZE];
+    int bufpos, bufused;
+    uint8_t packet[MTU];
+    bool in_sync, escaped;
+    int count;
+    struct packet_list *queue[256]; // indexed by protocol
+  } recv;
+  struct {
+    uint8_t seqno;
+    uint8_t *escaped;
+    int escapeptr;
+    uint16_t crc;
+  } send;
+};
+
+static tcflag_t parse_baudrate(int requested)
+{
+  int baudrate;
+
+  switch (requested)
+    {
+#ifdef B50
+    case 50: baudrate = B50; break;
+#endif
+#ifdef B75
+    case 75: baudrate = B75; break;
+#endif
+#ifdef B110
+    case 110: baudrate = B110; break;
+#endif
+#ifdef B134
+    case 134: baudrate = B134; break;
+#endif
+#ifdef B150
+    case 150: baudrate = B150; break;
+#endif
+#ifdef B200
+    case 200: baudrate = B200; break;
+#endif
+#ifdef B300
+    case 300: baudrate = B300; break;
+#endif
+#ifdef B600
+    case 600: baudrate = B600; break;
+#endif
+#ifdef B1200
+    case 1200: baudrate = B1200; break;
+#endif
+#ifdef B1800
+    case 1800: baudrate = B1800; break;
+#endif
+#ifdef B2400
+    case 2400: baudrate = B2400; break;
+#endif
+#ifdef B4800
+    case 4800: baudrate = B4800; break;
+#endif
+#ifdef B9600
+    case 9600: baudrate = B9600; break;
+#endif
+#ifdef B19200
+    case 19200: baudrate = B19200; break;
+#endif
+#ifdef B38400
+    case 38400: baudrate = B38400; break;
+#endif
+#ifdef B57600
+    case 57600: baudrate = B57600; break;
+#endif
+#ifdef B115200
+    case 115200: baudrate = B115200; break;
+#endif
+#ifdef B230400
+    case 230400: baudrate = B230400; break;
+#endif
+#ifdef B460800
+    case 460800: baudrate = B460800; break;
+#endif
+#ifdef B500000
+    case 500000: baudrate = B500000; break;
+#endif
+#ifdef B576000
+    case 576000: baudrate = B576000; break;
+#endif
+#ifdef B921600
+    case 921600: baudrate = B921600; break;
+#endif
+#ifdef B1000000
+    case 1000000: baudrate = B1000000; break;
+#endif
+#ifdef B1152000
+    case 1152000: baudrate = B1152000; break;
+#endif
+#ifdef B1500000
+    case 1500000: baudrate = B1500000; break;
+#endif
+#ifdef B2000000
+    case 2000000: baudrate = B2000000; break;
+#endif
+#ifdef B2500000
+    case 2500000: baudrate = B2500000; break;
+#endif
+#ifdef B3000000
+    case 3000000: baudrate = B3000000; break;
+#endif
+#ifdef B3500000
+    case 3500000: baudrate = B3500000; break;
+#endif
+#ifdef B4000000
+    case 4000000: baudrate = B4000000; break;
+#endif
+    default:
+      baudrate = 0;
+    }
+  return baudrate;
+}
+
+#ifdef DEBUG
+static void dump(const char *msg, unsigned char *packet, int len)
+{
+  int i;
+
+  printf("%s", msg);
+  for (i = 0; i < len; i++)
+    printf(" %02x", packet[i]);
+  putchar('\n');
+}
+#endif
+
+static void message(serial_source src, serial_source_msg msg)
+{
+  if (src->message)
+    src->message(msg);
+}
+
+/* Work around buggy usb serial driver (returns 0 when no data is
+   available, independent of the blocking/non-blocking mode) */
+static int buggyread(serial_source src, void *buffer, int n)
+{
+  fd_set fds;
+  int cnt;
+
+  if (src->non_blocking)
+    {
+      cnt = read(src->fd, buffer, n);
+      if (cnt == 0)
+       {
+         cnt = -1;
+         errno = EAGAIN;
+         printf("foo\n");
+       }
+      return cnt;
+    }
+  else
+    for (;;)
+      {
+       FD_ZERO(&fds);
+       FD_SET(src->fd, &fds);
+       cnt = select(src->fd + 1, &fds, NULL, NULL, NULL);
+       if (cnt < 0)
+         return -1;
+
+       cnt = read(src->fd, buffer, n);
+       if (cnt != 0)
+         return cnt;
+      }
+}
+
+serial_source open_serial_source(const char *device, int baud_rate,
+                                int non_blocking,
+                                void (*message)(serial_source_msg problem))
+/* Effects: opens serial port device at specified baud_rate. If non_blocking
+     is true, read_serial_packet calls will be non-blocking (writes are
+     always blocking, for now at least)
+   Returns: descriptor for serial forwarder at host:port, or
+     NULL for failure (bad device or bad baud rate)
+ */
+{
+  struct termios newtio;
+  int fd;
+  tcflag_t baudflag = parse_baudrate(baud_rate);
+
+  if (!baudflag)
+    return NULL;
+
+  fd = open(device, O_RDWR | O_NOCTTY | O_NONBLOCK);
+  if (fd < 0)
+    return NULL;
+
+#ifdef __CYGWIN__
+  /* For some very mysterious reason, this incantation is necessary to make
+     the serial port work under some windows machines */
+  HANDLE handle = (HANDLE)get_osfhandle(fd);
+  DCB dcb;
+  if (!(GetCommState(handle, &dcb) && SetCommState(handle, &dcb)))
+    {
+      close(fd);
+      return NULL;
+    }
+#endif
+  /* Serial port setting */
+  memset(&newtio, 0, sizeof(newtio));
+  newtio.c_cflag = CS8 | CLOCAL | CREAD;
+  newtio.c_iflag = IGNPAR | IGNBRK;
+  cfsetispeed(&newtio, baudflag);
+  cfsetospeed(&newtio, baudflag);
+
+  /* Raw output_file */
+  newtio.c_oflag = 0;
+
+  if (tcflush(fd, TCIFLUSH) >= 0 &&
+      tcsetattr(fd, TCSANOW, &newtio) >= 0)
+    {
+      serial_source src = malloc(sizeof *src);
+
+      if (src)
+       {
+         memset(src, 0, sizeof *src);
+         src->fd = fd;
+         src->non_blocking = non_blocking;
+         src->message = message;
+         src->send.seqno = 37;
+
+         return src;
+       }
+    }
+  close(fd);
+
+  return NULL;
+}
+
+int serial_source_fd(serial_source src)
+/* Returns: the file descriptor used by serial source src (useful when
+     non-blocking reads were requested)
+*/
+{
+  return src->fd;
+}
+
+int close_serial_source(serial_source src)
+/* Effects: closes serial source src
+   Returns: 0 if successful, -1 if some problem occured (but source is
+     considered closed anyway)
+ */
+{
+  int ok = close(src->fd);
+
+  free(src);
+
+  return ok;
+}
+
+static int source_wait(serial_source src, struct timeval *deadline)
+/* Effects: waits until deadline for some data on source. deadline
+     can be NULL for indefinite waiting.
+   Returns: 0 if data is available, -1 if the deadline expires
+*/
+{
+  struct timeval tv;
+  fd_set fds;
+  int cnt;
+
+  if (src->recv.bufpos < src->recv.bufused)
+    return 0;
+
+  for (;;)
+    {
+      if (deadline)
+       {
+         gettimeofday(&tv, NULL);
+         tv.tv_sec = deadline->tv_sec - tv.tv_sec;
+         tv.tv_usec = deadline->tv_usec - tv.tv_usec;
+         if (tv.tv_usec < 0)
+           {
+             tv.tv_usec += 1000000;
+             tv.tv_sec--;
+           }
+         if (tv.tv_sec < 0)
+           return -1;
+       }
+
+      FD_ZERO(&fds);
+      FD_SET(src->fd, &fds);
+      cnt = select(src->fd + 1, &fds, NULL, NULL, deadline ? &tv : NULL);
+      if (cnt < 0)
+       {
+         if (errno == EINTR)
+           continue;
+         message(src, msg_unix_error);
+         return -1;
+       }
+      if (cnt == 0)
+       return -1;
+      return 0;
+    }
+}
+
+static int source_write(serial_source src, const void *buffer, int count)
+{
+  int actual = 0;
+
+  if (fcntl(src->fd, F_SETFL, 0) < 0)
+    {
+      message(src, msg_unix_error);
+      return -1;
+    }
+  while (count > 0)
+    {
+      int n = write(src->fd, buffer, count);
+
+      if (n < 0 && errno == EINTR)
+       continue;
+      if (n < 0)
+       {
+         message(src, msg_unix_error);
+         actual = -1;
+         break;
+       }
+
+      count -= n;
+      actual += n;
+      buffer += n;
+    }
+  if (fcntl(src->fd, F_SETFL, O_NONBLOCK) < 0)
+    {
+      message(src, msg_unix_error);
+      /* We're in trouble, but there's no obvious fix. */
+    }
+  return actual;
+}
+
+static void push_protocol_packet(serial_source src,
+                                uint8_t type, uint8_t *packet, uint8_t len)
+{
+  /* I'm assuming short queues */
+  struct packet_list *entry = malloc(sizeof *entry), **last;
+
+  if (!entry)
+    {
+      message(src, msg_no_memory);
+      free(packet);
+      return;
+    }
+
+  entry->packet = packet;
+  entry->len = len;
+  entry->next = NULL;
+
+  last = &src->recv.queue[type];
+  while (*last)
+    last = &(*last)->next;
+  *last = entry;
+}
+
+static struct packet_list *pop_protocol_packet(serial_source src, uint8_t type)
+{
+  struct packet_list *entry = src->recv.queue[type];
+
+  if (entry)
+    src->recv.queue[type] = entry->next;
+
+  return entry;
+}
+
+static bool packet_available(serial_source src, uint8_t type)
+{
+  return src->recv.queue[type] != NULL;
+}
+
+int serial_source_empty(serial_source src)
+/* Returns: true if serial source does not contain any pending data, i.e.,
+     if the result is true and there is no data available on the source's
+     file descriptor, then read_serial_packet will:
+       - return NULL if the source is non-blocking
+       - block if it is blocking
+
+    (Note: the presence of this calls allows the serial_source to do some
+    internal buffering)
+*/
+{
+  return src->recv.bufpos >= src->recv.bufused &&
+    !packet_available(src, P_PACKET_NO_ACK);
+}
+
+/* Slow implementation of crc function */
+static uint16_t crc_byte(uint16_t crc, uint8_t b)
+{
+  uint8_t i;
+  
+  crc = crc ^ b << 8;
+  i = 8;
+  do
+    if (crc & 0x8000)
+      crc = crc << 1 ^ 0x1021;
+    else
+      crc = crc << 1;
+  while (--i);
+
+  return crc;
+}
+
+static uint16_t crc_packet(uint8_t *data, int len)
+{
+  uint16_t crc = 0;
+
+  while (len-- > 0)
+    crc = crc_byte(crc, *data++);
+
+  return crc;
+}
+
+static int read_byte(serial_source src)
+/* Returns: next byte (>= 0), or -1 if no data available and the source
+     is non-blocking.
+*/
+{
+  if (src->recv.bufpos >= src->recv.bufused)
+    {
+      for (;;)
+       {
+         int n = buggyread(src, src->recv.buffer, sizeof src->recv.buffer);
+
+         if (n == 0) /* Can't occur because of buggyread bug workaround */
+           {
+             message(src, msg_closed);
+             return -1;
+           }
+         if (n > 0)
+           {
+#ifdef DEBUG
+             dump("raw", src->recv.buffer, n);
+#endif
+             src->recv.bufpos = 0;
+             src->recv.bufused = n;
+             break;
+           }
+         if (errno == EAGAIN)
+           return -1;
+         if (errno != EINTR)
+           message(src, msg_unix_error);
+       }
+    }
+  //printf("in %02x\n", src->recv.buffer[src->recv.bufpos]);
+  return src->recv.buffer[src->recv.bufpos++];
+}
+
+static void process_packet(serial_source src, uint8_t *packet, int len);
+static int write_framed_packet(serial_source src,
+                              uint8_t packet_type, uint8_t first_byte,
+                              const uint8_t *packet, int count);
+
+static void read_and_process(serial_source src)
+/* Effects: reads and processes up to one packet.
+*/
+{
+  uint8_t *packet = src->recv.packet;
+
+  for (;;)
+    {
+      int byte = read_byte(src);
+
+      if (byte < 0)
+       return;
+
+      if (!src->recv.in_sync)
+       {
+         if (byte == SYNC_BYTE)
+           {
+             src->recv.in_sync = TRUE;
+             message(src, msg_sync);
+             src->recv.count = 0;
+             src->recv.escaped = FALSE;
+           }
+         continue;
+       }
+      if (src->recv.count >= MTU)
+       {
+         message(src, msg_too_long);
+         src->recv.in_sync = FALSE;
+         continue;
+       }
+      if (src->recv.escaped)
+       {
+         if (byte == SYNC_BYTE)
+           {
+             /* sync byte following escape is an error, resync */
+             message(src, msg_bad_sync);
+             src->recv.in_sync = FALSE;
+             continue;
+           }
+         byte ^= 0x20;
+         src->recv.escaped = FALSE;
+       }
+      else if (byte == ESCAPE_BYTE)
+       {
+         src->recv.escaped = TRUE;
+         continue;
+       }
+      else if (byte == SYNC_BYTE)
+       {
+         int count = src->recv.count;
+         uint8_t *received;
+         uint16_t read_crc, computed_crc;
+
+         src->recv.count = 0; /* ready for next packet */
+
+         if (count < 4)
+           /* frames that are too small are ignored */
+           continue;
+
+         received = malloc(count - 2);
+         if (!received)
+           {
+             message(src, msg_no_memory);
+             continue;
+           }
+         memcpy(received, packet, count - 2);
+
+         read_crc = packet[count - 2] | packet[count - 1] << 8;
+         computed_crc = crc_packet(received, count - 2);
+
+#ifdef DEBUG
+         dump("received", packet, count);
+         printf("  crc %x comp %x\n", read_crc, computed_crc);
+#endif
+         if (read_crc == computed_crc) 
+           {
+             process_packet(src, received, count - 2);
+             return; /* give rest of world chance to do something */
+           }
+         else
+           {
+             message(src, msg_bad_crc);
+             /* We don't lose sync here. If we did, garbage on the line
+                at startup will cause loss of the first packet. */
+             continue;
+           }
+       }
+      packet[src->recv.count++] = byte;
+    }
+}
+
+static void process_packet(serial_source src, uint8_t *packet, int len)
+{
+  int packet_type = packet[0], offset = 1;
+
+  if (packet_type == P_PACKET_ACK)
+    {
+      /* send ack */
+      write_framed_packet(src, P_ACK, packet[1], NULL, 0);
+      /* And merge with un-acked packets */
+      packet_type = P_PACKET_NO_ACK;
+      offset = 2;
+    }
+  /* packet must remain a valid pointer to pass to free. So we move the
+     data rather than pass an internal pointer */
+  memmove(packet, packet + offset, len - offset);
+  push_protocol_packet(src, packet_type, packet, len - offset);
+}
+
+void *read_serial_packet(serial_source src, int *len)
+/* Effects: Read the serial source src. If a packet is available, return it.
+     If in blocking mode and no packet is available, wait for one.
+   Returns: the packet read (in newly allocated memory), with *len is
+     set to the packet length, or NULL if no packet is yet available and
+     the serial source is in non-blocking mode
+*/
+{
+  for (;;)
+    {
+      struct packet_list *entry;
+
+      read_and_process(src);
+      entry = pop_protocol_packet(src, P_PACKET_NO_ACK);
+      if (entry)
+       {
+         uint8_t *packet = entry->packet;
+
+         *len = entry->len;
+         free(entry);
+
+         return packet;
+       }
+      if (src->non_blocking && serial_source_empty(src))
+       return NULL;
+      source_wait(src, NULL);
+    }
+}
+
+/* The escaper does the sync bytes+escape-like encoding+crc of packets */
+
+static void escape_add(serial_source src, uint8_t b)
+{
+  src->send.escaped[src->send.escapeptr++] = b;
+}
+
+static int init_escaper(serial_source src, int count)
+{
+  src->send.escaped = malloc(count * 2 + 2);
+  if (!src->send.escaped)
+    {
+      message(src, msg_no_memory);
+      return -1;
+    }
+  src->send.escapeptr = 0;
+  src->send.crc = 0;
+
+  escape_add(src, SYNC_BYTE);
+
+  return 0;
+}
+
+static void terminate_escaper(serial_source src)
+{
+  escape_add(src, SYNC_BYTE);
+}
+
+static void escape_byte(serial_source src, uint8_t b)
+{
+  src->send.crc = crc_byte(src->send.crc, b);
+  if (b == SYNC_BYTE || b == ESCAPE_BYTE)
+    {
+      escape_add(src, ESCAPE_BYTE);
+      escape_add(src, b ^ 0x20);
+    }
+  else
+    escape_add(src, b);
+}
+
+static void free_escaper(serial_source src)
+{
+  free(src->send.escaped);
+}
+
+// Write a packet of type 'packetType', first byte 'firstByte'
+// and bytes 2..'count'+1 in 'packet'
+static int write_framed_packet(serial_source src,
+                              uint8_t packet_type, uint8_t first_byte,
+                              const uint8_t *packet, int count)
+{
+  int i, crc;
+
+#ifdef DEBUG
+  printf("writing %02x %02x", packet_type, first_byte);
+  dump("", packet, count);
+#endif
+
+  if (init_escaper(src, count + 4) < 0)
+    return -1;
+       
+  escape_byte(src, packet_type);
+  escape_byte(src, first_byte);
+  for (i = 0; i < count; i++)
+    escape_byte(src, packet[i]);
+
+  crc = src->send.crc;
+  escape_byte(src, crc & 0xff);
+  escape_byte(src, crc >> 8);
+  
+  terminate_escaper(src);
+
+#ifdef DEBUG
+  dump("encoded", src->send.escaped, src->send.escapeptr);
+#endif
+
+  if (source_write(src, src->send.escaped, src->send.escapeptr) < 0)
+    {
+      free_escaper(src);
+      return -1;
+    }
+  free_escaper(src);
+  return 0;
+}
+
+static void add_timeval(struct timeval *tv, long us)
+/* Specialised for this app */
+{
+  tv->tv_sec += us / 1000000;
+  tv->tv_usec += us % 1000000;
+  if (tv->tv_usec > 1000000)
+    {
+      tv->tv_usec -= 1000000;
+      tv->tv_sec++;
+    }
+}
+
+int write_serial_packet(serial_source src, const void *packet, int len)
+/* Effects: writes len byte packet to serial source src
+   Returns: 0 if packet successfully written, 1 if successfully written
+     but not acknowledged, -1 otherwise
+*/
+{
+  struct timeval deadline;
+
+  src->send.seqno++;
+  if (write_framed_packet(src, P_PACKET_ACK, src->send.seqno, packet, len) < 0)
+    return -1;
+
+  gettimeofday(&deadline, NULL);
+  add_timeval(&deadline, ACK_TIMEOUT);
+  for (;;) 
+    {
+      struct packet_list *entry;
+      
+      read_and_process(src);
+      entry = pop_protocol_packet(src, P_ACK);
+      if (entry)
+       {
+         uint8_t acked = entry->packet[0];
+
+         free(entry->packet);
+         free(entry);
+         if (acked == src->send.seqno)
+           return 0;
+       }
+      else if (source_wait(src, &deadline) < 0)
+       return 1;
+    }
+}
+
+/* This somewhat convoluted code allows us to use a common baudrate table
+   with the Java code. This could be improved if we generated the Java
+   code from a common table.
+*/
+
+struct pargs {
+  char *name;
+  int rate;
+};
+
+static void padd(struct pargs *args, const char *name, int baudrate)
+{
+  if (!strcmp(args->name, name))
+    args->rate = baudrate;
+}
+
+static void init(void) { }
+
+int platform_baud_rate(char *platform_name)
+/* Returns: The baud rate of the specified platform, or -1 for unknown
+     platforms
+*/
+{
+  /* The Java code looks like Platform.add(Platform.x, "name", baudrate); 
+     Fake up some C stuff which will make that work right. */
+  struct pargs args;
+  struct {
+    void (*add)(struct pargs *args, const char *name, int baudrate);
+    struct pargs *x;
+  } Platform = { padd, &args };
+  static struct {
+    struct {
+      int packet;
+    } tinyos;
+  } net;
+
+  if (isdigit(platform_name[0]))
+    return atoi(platform_name);
+
+  args.name = platform_name;
+  args.rate = -1;
+
+#define class
+#define BaudRate
+#define static
+#define void
+#define throws ;
+#define Exception
+#define package
+#include "../java/net/tinyos/packet/BaudRate.java"
+
+  return args.rate;
+}