/* * Copyright © 2009 Keith Packard * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #include #include #include #include #include #include #include #include #include #include #include "ccdbg-debug.h" #include "cc-usb.h" #define CC_NUM_HEX_READ 64 /* * AltOS has different buffer sizes for in/out packets */ #define CC_IN_BUF 65536 #define CC_OUT_BUF 64 #define DEFAULT_TTY "/dev/ttyACM0" struct cc_hex_read { uint8_t *buf; int len; }; struct cc_usb { int fd; uint8_t in_buf[CC_IN_BUF]; int in_pos; int in_count; uint8_t out_buf[CC_OUT_BUF]; int out_count; struct cc_hex_read hex_buf[CC_NUM_HEX_READ]; int hex_count; int show_input; int remote; }; #define NOT_HEX 0xff static uint8_t cc_hex_nibble(uint8_t c) { if ('0' <= c && c <= '9') return c - '0'; if ('a' <= c && c <= 'f') return c - 'a' + 10; if ('A' <= c && c <= 'F') return c - 'A' + 10; return NOT_HEX; } /* * Take raw input bytes, parse them as hex * and write them to the waiting buffer */ static void cc_handle_hex_read(struct cc_usb *cc) { uint8_t h, l; int hex_pos; hex_pos = 0; while (hex_pos < cc->hex_count && cc->in_pos < cc->in_count) { /* * Skip to next hex character */ while (cc->in_pos < cc->in_count && cc_hex_nibble(cc->in_buf[cc->in_pos]) == NOT_HEX) cc->in_pos++; /* * Make sure we have two characters left */ if (cc->in_count - cc->in_pos < 2) break; /* * Parse hex number */ h = cc_hex_nibble(cc->in_buf[cc->in_pos]); l = cc_hex_nibble(cc->in_buf[cc->in_pos+1]); if (h == NOT_HEX || l == NOT_HEX) { fprintf(stderr, "hex read error\n"); break; } cc->in_pos += 2; /* * Store hex number */ *cc->hex_buf[hex_pos].buf++ = (h << 4) | l; if (--cc->hex_buf[hex_pos].len <= 0) hex_pos++; } /* Move pending hex reads to the start of the array */ if (hex_pos) { memmove(cc->hex_buf, cc->hex_buf + hex_pos, (cc->hex_count - hex_pos) * sizeof (cc->hex_buf[0])); cc->hex_count -= hex_pos; } } static void cc_usb_dbg(int indent, uint8_t *bytes, int len) { static int eol = 1; int i; uint8_t c; ccdbg_debug(CC_DEBUG_BITBANG, "<<<%d bytes>>>", len); while (len--) { c = *bytes++; if (eol) { for (i = 0; i < indent; i++) ccdbg_debug(CC_DEBUG_BITBANG, " "); eol = 0; } switch (c) { case '\r': ccdbg_debug(CC_DEBUG_BITBANG, "\\r"); break; case '\n': eol = 1; ccdbg_debug(CC_DEBUG_BITBANG, "\\n\n"); break; default: if (c < ' ' || c > '~') ccdbg_debug(CC_DEBUG_BITBANG, "\\%02x", c); else ccdbg_debug(CC_DEBUG_BITBANG, "%c", c); } } } int cc_default_timeout = 5000; /* * Flush pending writes, fill pending reads */ static int _cc_usb_sync(struct cc_usb *cc, int wait_for_input, int write_timeout) { int ret; struct pollfd fds; int timeout; fds.fd = cc->fd; for (;;) { if (cc->hex_count || cc->out_count) timeout = write_timeout; else if (wait_for_input && cc->in_pos == cc->in_count) timeout = wait_for_input; else timeout = 0; fds.events = 0; /* Move remaining bytes to the start of the input buffer */ if (cc->in_pos) { memmove(cc->in_buf, cc->in_buf + cc->in_pos, cc->in_count - cc->in_pos); cc->in_count -= cc->in_pos; cc->in_pos = 0; } if (cc->in_count < CC_IN_BUF) fds.events |= POLLIN; if (cc->out_count) fds.events |= POLLOUT; ret = poll(&fds, 1, timeout); if (ret == 0) { if (timeout) return -1; break; } if (ret < 0) { perror("poll"); return -1; } if (fds.revents & POLLIN) { ret = read(cc->fd, cc->in_buf + cc->in_count, CC_IN_BUF - cc->in_count); if (ret > 0) { cc_usb_dbg(24, cc->in_buf + cc->in_count, ret); cc->in_count += ret; if (cc->hex_count) cc_handle_hex_read(cc); if (cc->show_input && cc->in_count) { write(2, cc->in_buf, cc->in_count); cc->in_count = 0; } } else if (ret <= 0) { perror("read"); return -1; } } if (fds.revents & POLLOUT) { ret = write(cc->fd, cc->out_buf, cc->out_count); if (ret > 0) { cc_usb_dbg(0, cc->out_buf, ret); memmove(cc->out_buf, cc->out_buf + ret, cc->out_count - ret); cc->out_count -= ret; } else if (ret < 0) perror("write"); } } return 0; } void cc_usb_sync(struct cc_usb *cc) { if (_cc_usb_sync(cc, 0, cc_default_timeout) < 0) { fprintf(stderr, "USB link timeout\n"); exit(1); } } void cc_usb_printf(struct cc_usb *cc, char *format, ...) { char buf[1024], *b; va_list ap; int ret, this_time; /* sprintf to a local buffer */ va_start(ap, format); ret = vsnprintf(buf, sizeof(buf), format, ap); va_end(ap); if (ret > sizeof(buf)) { fprintf(stderr, "printf overflow for format %s\n", format); } /* flush local buffer to the wire */ b = buf; while (ret > 0) { this_time = ret; if (this_time > CC_OUT_BUF - cc->out_count) this_time = CC_OUT_BUF - cc->out_count; memcpy(cc->out_buf + cc->out_count, b, this_time); cc->out_count += this_time; ret -= this_time; b += this_time; while (cc->out_count >= CC_OUT_BUF) cc_usb_sync(cc); } } int cc_usb_getchar_timeout(struct cc_usb *cc, int timeout) { while (cc->in_pos == cc->in_count) { if (_cc_usb_sync(cc, timeout, cc_default_timeout) < 0) { fprintf(stderr, "USB link timeout\n"); exit(1); } } return cc->in_buf[cc->in_pos++]; } int cc_usb_getchar(struct cc_usb *cc) { return cc_usb_getchar_timeout(cc, cc_default_timeout); } void cc_usb_getline(struct cc_usb *cc, char *line, int max) { int c; while ((c = cc_usb_getchar(cc)) != '\n') { switch (c) { case '\r': break; default: if (max > 1) { *line++ = c; max--; } break; } } *line++ = '\0'; } int cc_usb_send_bytes(struct cc_usb *cc, uint8_t *bytes, int len) { int this_len; int ret = len; while (len) { this_len = len; if (this_len > 8) this_len = 8; len -= this_len; cc_usb_printf(cc, "P"); while (this_len--) cc_usb_printf (cc, " %02x", (*bytes++) & 0xff); cc_usb_printf(cc, "\n"); } return ret; } void cc_queue_read(struct cc_usb *cc, uint8_t *buf, int len) { struct cc_hex_read *hex_buf; /* At the start of a command sequence, flush any pending input */ if (cc->hex_count == 0) { cc_usb_sync(cc); cc->in_count = 0; } while (cc->hex_count >= CC_NUM_HEX_READ) cc_usb_sync(cc); hex_buf = &cc->hex_buf[cc->hex_count++]; hex_buf->buf = buf; hex_buf->len = len; } int cc_usb_recv_bytes(struct cc_usb *cc, uint8_t *buf, int len) { cc_queue_read(cc, buf, len); cc_usb_printf(cc, "G %x\n", len); return len; } int cc_usb_write_memory(struct cc_usb *cc, uint16_t addr, uint8_t *bytes, int len) { cc_usb_printf(cc, "O %x %x\n", len, addr); while (len--) cc_usb_printf(cc, "%02x", *bytes++); cc_usb_sync(cc); return 0; } int cc_usb_read_memory(struct cc_usb *cc, uint16_t addr, uint8_t *bytes, int len) { int i; cc_queue_read(cc, bytes, len); cc_usb_printf(cc, "I %x %x\n", len, addr); cc_usb_sync(cc); for (i = 0; i < len; i++) { if ((i & 15) == 0) { if (i) ccdbg_debug(CC_DEBUG_MEMORY, "\n"); ccdbg_debug(CC_DEBUG_MEMORY, "\t%04x", addr + i); } ccdbg_debug(CC_DEBUG_MEMORY, " %02x", bytes[i]); } ccdbg_debug(CC_DEBUG_MEMORY, "\n"); return 0; } int cc_usb_debug_mode(struct cc_usb *cc) { cc_usb_sync(cc); cc_usb_printf(cc, "D\n"); return 1; } int cc_usb_reset(struct cc_usb *cc) { cc_usb_sync(cc); cc_usb_printf(cc, "R\n"); return 1; } void cc_usb_open_remote(struct cc_usb *cc, int freq, char *call) { if (!cc->remote) { fprintf (stderr, "freq %dkHz\n", freq); fprintf (stderr, "call %s\n", call); cc_usb_printf(cc, "\nc F %d\nc c %s\np\nE 0\n", freq, call); do { cc->in_count = cc->in_pos = 0; _cc_usb_sync(cc, 100, cc_default_timeout); } while (cc->in_count > 0); cc->remote = 1; } } void cc_usb_close_remote(struct cc_usb *cc) { if (cc->remote) { cc_usb_printf(cc, "~"); cc->remote = 0; } } static struct termios save_termios; #include struct cc_usb * cc_usb_open(char *tty) { struct cc_usb *cc; struct termios termios; int i; if (!tty) tty = DEFAULT_TTY; cc = calloc (sizeof (struct cc_usb), 1); if (!cc) return NULL; i = 0; for (;;) { cc->fd = open(tty, O_RDWR | O_NONBLOCK); if (cc->fd >= 0) break; i++; if (errno == EBUSY || errno == EPERM || errno == EACCES) { fprintf(stderr, "open failed, pausing"); perror(tty); if (i < 20) { sleep(3); continue; } } perror(tty); free (cc); return NULL; } tcgetattr(cc->fd, &termios); save_termios = termios; cfmakeraw(&termios); cfsetospeed(&termios, B9600); cfsetispeed(&termios, B9600); tcsetattr(cc->fd, TCSAFLUSH, &termios); cc_usb_printf(cc, "\nE 0\n"); do { cc->in_count = cc->in_pos = 0; _cc_usb_sync(cc, 100, cc_default_timeout); } while (cc->in_count > 0); return cc; } void cc_usb_close(struct cc_usb *cc) { cc_usb_close_remote(cc); cc_usb_sync(cc); tcsetattr(cc->fd, TCSAFLUSH, &save_termios); close (cc->fd); free (cc); } int cc_usb_write(struct cc_usb *cc, void *buf, int c) { uint8_t *b; int this_time; b = buf; cc->show_input = 1; while (c > 0) { this_time = c; if (this_time > CC_OUT_BUF - cc->out_count) this_time = CC_OUT_BUF - cc->out_count; memcpy(cc->out_buf + cc->out_count, b, this_time); cc->out_count += this_time; c -= this_time; b += this_time; while (cc->out_count >= CC_OUT_BUF) { _cc_usb_sync(cc, 0, -1); } } return 1; }