503 lines
10 KiB
C
503 lines
10 KiB
C
/*
|
|
* Copyright © 2009 Keith Packard <keithp@keithp.com>
|
|
*
|
|
* 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 <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <stdarg.h>
|
|
#include <poll.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <termios.h>
|
|
#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 <errno.h>
|
|
|
|
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;
|
|
}
|