From 6244c44e65a123a8448de4c2f6998e1dd3c1a781 Mon Sep 17 00:00:00 2001 From: Olaf Rempel Date: Sat, 16 Oct 2010 19:29:15 +0200 Subject: [PATCH] add linux tool --- linux/.gitignore | 3 + linux/Makefile | 22 +++ linux/filedata.c | 423 +++++++++++++++++++++++++++++++++++++++++++++++ linux/list.h | 268 ++++++++++++++++++++++++++++++ linux/twb.c | 301 +++++++++++++++++++++++++++++++++ linux/twiboot.c | 341 ++++++++++++++++++++++++++++++++++++++ linux/twiboot.h | 45 +++++ 7 files changed, 1403 insertions(+) create mode 100644 linux/.gitignore create mode 100644 linux/Makefile create mode 100644 linux/filedata.c create mode 100644 linux/list.h create mode 100644 linux/twb.c create mode 100644 linux/twiboot.c create mode 100644 linux/twiboot.h diff --git a/linux/.gitignore b/linux/.gitignore new file mode 100644 index 0000000..3e26448 --- /dev/null +++ b/linux/.gitignore @@ -0,0 +1,3 @@ +*.o +*.d +twiboot diff --git a/linux/Makefile b/linux/Makefile new file mode 100644 index 0000000..a495786 --- /dev/null +++ b/linux/Makefile @@ -0,0 +1,22 @@ +TARGET = twiboot + +CFLAGS = -Wall -Wno-unused-result -O2 -MMD -MP -MF $(*F).d + +# ------ + +SRC := $(wildcard *.c) + +all: $(TARGET) + +$(TARGET): $(SRC:.c=.o) + @echo " Linking file: $@" + @$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) > /dev/null + +%.o: %.c + @echo " Building file: $<" + @$(CC) -c $(CFLAGS) $< -o $@ + +clean: + rm -rf $(TARGET) *.o *.d + +-include $(shell find . -name \*.d 2> /dev/null) diff --git a/linux/filedata.c b/linux/filedata.c new file mode 100644 index 0000000..6854b66 --- /dev/null +++ b/linux/filedata.c @@ -0,0 +1,423 @@ +/*************************************************************************** + * Copyright (C) 10/2010 by Olaf Rempel * + * razzor@kopf-tisch.de * + * * + * 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; version 2 of the License, * + * * + * 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 "twiboot.h" + +#define FILETYPE_UNKNOWN 0 +#define FILETYPE_BINARY 1 +#define FILETYPE_INTELHEX 2 + +int dbuf_alloc(struct databuf **dbuf, uint32_t size) +{ + *dbuf = malloc(sizeof(struct databuf) + size); + if (*dbuf == NULL) { + perror("dbuf_alloc"); + return -1; + } + + memset((*dbuf)->data, 0xFF, size); + (*dbuf)->size = size; + (*dbuf)->length = 0; + return 0; +} + +void dbuf_free(struct databuf *dbuf) +{ + free(dbuf); +} + +static void dbuf_dump(struct databuf *dbuf) +{ + int pos = 0, oldskip = 0; + + while (pos < dbuf->length) { + char buf[128]; + int j, i = 0; + + int skip = 1; + for (j = 0; j < 16; j++) { + if (pos + j < dbuf->length) + i += sprintf(buf + i, "%02X", dbuf->data[pos + j]); + else + i += sprintf(buf + i, " "); + + if (j % 2) + buf[i++] = ' '; + } + + for (j = 0; j < 16; j++) { + if (pos + j < dbuf->length) { + unsigned char val = dbuf->data[pos + j]; + if (val >= 0x20 && val < 0x7F) + buf[i++] = val; + else + buf[i++] = '.'; + + if (val != 0xFF) + skip = 0; + } else { + buf[i++] = ' '; + } + } + + if (pos == 0 || (pos + 16) >= dbuf->length || skip == 0) { + buf[i++] = '\0'; + printf("%04X: %s\r\n", pos, buf); + oldskip = 0; + + } else if (skip == 1 && oldskip == 0) { + printf("****\n"); + oldskip = 1; + } + + pos += 16; + } +} + +static int binfile_getsize(const char *filename, uint32_t *size) +{ + int fd = open(filename, O_RDONLY); + if (fd < 0) { + perror("binfile_getsize(): open()"); + return -1; + } + + struct stat filestat; + if (fstat(fd, &filestat) < 0) { + perror("binfile_getsize(): fstat()"); + close(fd); + return -1; + } + + *size = filestat.st_size; + + close(fd); + return 0; +} + +static int binfile_read(const char *filename, struct databuf *dbuf) +{ + int fd = open(filename, O_RDONLY); + if (fd < 0) { + perror("binfile_read(): open()"); + return -1; + } + + ssize_t readsize = read(fd, dbuf->data, dbuf->size); + if (readsize <= 0) { + perror("binfile_read(): read()"); + close(fd); + return -1; + } + + dbuf->length = readsize; + + close(fd); + return 0; +} + +static int binfile_write(const char *filename, struct databuf *dbuf) +{ + int fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0644); + if (fd < 0) { + perror("binfile_write(): open()"); + return -1; + } + + ssize_t writesize = write(fd, dbuf->data, dbuf->length); + if (writesize != dbuf->length) { + perror("binfile_write(): write()"); + close(fd); + return -1; + } + + close(fd); + return 0; +} + +struct ihex_record { + uint8_t byte_count; + uint16_t address; + uint8_t type; + + uint8_t *data; + uint8_t chksum; +}; + +static uint8_t hex2byte(const char *ptr) +{ + int i; + uint8_t result = 0; + + for (i = 0; i < 2; i++) { + result <<= 4; + result |= (ptr[i] >= '0' && ptr[i] <= '9') ? (ptr[i] - '0') : + (((ptr[i] & 0xDF) >= 'A' && (ptr[i] & 0xDF) <= 'F') ? (ptr[i] - 'A' + 0x0A) : + 0x00); + } + + return result; +} + +static int hexfile_getrecord(FILE *stream, struct ihex_record *record) +{ + char *hexline = NULL; + size_t size; + + ssize_t length = getline(&hexline, &size, stream); + if (length == -1) { + if (!feof(stream)) { + perror("hexfile_getrecord(): getline()"); + } + return -1; + } + + if (length < 12) { + fprintf(stderr, "record too short (%d)\n", length); + free(hexline); + return -1; + } + + int pos = 0; + if (hexline[pos] != ':') { + fprintf(stderr, "invalid startcode\n"); + free(hexline); + return -1; + } + pos++; + + uint8_t chksum = 0x00; + + record->byte_count = hex2byte(&hexline[pos]); + chksum += record->byte_count; + pos += 2; + + if (record->byte_count > 0) { + record->data = malloc(record->byte_count); + if (record->data == NULL) { + perror("hexfile_getrecord(): malloc()"); + free(hexline); + return -1; + } + } + + uint8_t hiaddr = hex2byte(&hexline[pos]); + uint8_t loaddr = hex2byte(&hexline[pos +2]); + record->address = (hiaddr << 8) + loaddr; + chksum += hiaddr + loaddr; + pos += 4; + + record->type = hex2byte(&hexline[pos]); + chksum += record->type; + pos += 2; + + int i; + for (i = 0; i < record->byte_count; i++) { + record->data[i] = hex2byte(&hexline[pos]); + chksum += record->data[i]; + pos += 2; + } + + record->chksum = hex2byte(&hexline[pos]); + chksum += record->chksum; + pos += 2; + + if (chksum != 0x00) { + fprintf(stderr, "invalid checksum (0x%02X)\n", chksum); + if (record->byte_count > 0) + free(record->data); + free(hexline); + return -1; + } + + free(hexline); + return 0; +} + +static int hexfile_putrecord(FILE *stream, struct ihex_record *record) +{ + uint8_t chksum = record->byte_count; + chksum += (record->address >> 8) & 0xFF; + chksum += (record->address & 0xFF); + chksum += record->type; + + int i, len = 0; + char buf[64]; + + buf[0] = '\0'; + for (i = 0; i < record->byte_count; i++) { + len += snprintf(buf + len, sizeof(buf) - len, "%02X", record->data[i]); + chksum += record->data[i]; + } + + fprintf(stream, ":%02X%04X%02X%s%02X\n", record->byte_count, record->address, record->type, buf, (uint8_t)(0x100 - chksum)); + return -1; +} + +static int hexfile_getsize(const char *filename, uint32_t *size) +{ + *size = 0x10000; + return 0; +} + +static int hexfile_read(const char *filename, struct databuf *dbuf) +{ + FILE *stream = fopen(filename, "r"); + if (stream == NULL) { + perror("hexfile_read(): fopen()"); + return -1; + } + + while (1) { + struct ihex_record record; + memset(&record, 0x00, sizeof(struct ihex_record)); + + int result = hexfile_getrecord(stream, &record); + if (result == -1) + break; + + if (record.type == 0x00) { + if (record.address > dbuf->size || record.address + record.byte_count > dbuf->size) { + fprintf(stderr, "hexfile_read(): data out of bounds\n"); + break; + } + + memcpy(&dbuf->data[record.address], record.data, record.byte_count); + dbuf->length = record.address + record.byte_count; + } + } + + fclose(stream); + return 0; +} + +static int hexfile_write(const char *filename, struct databuf *dbuf) +{ + FILE *stream = fopen(filename, "w"); + if (stream == NULL) { + perror("hexfile_write(): fopen()"); + return -1; + } + + int i; + int addr_min = dbuf->length; + int addr_max = 0; + for (i = 0; i < dbuf->length; i++) { + if (dbuf->data[i] == 0xFF) + continue; + + if (addr_min > i) + addr_min = i; + + if (addr_max < i) + addr_max = i; + } + addr_min = addr_min & ~0x0F; + addr_max = (addr_max + 0x0F) & ~0x0F; + + struct ihex_record record; + for (i = addr_min; i < addr_max; i += 0x10) { + record.byte_count = 0x10; + record.address = i; + record.type = 0x00; + record.data = &dbuf->data[i]; + + hexfile_putrecord(stream, &record); + } + + record.byte_count = 0x00; + record.address = addr_min; + record.type = 0x01; + record.data = NULL; + hexfile_putrecord(stream, &record); + + fclose(stream); + return 0; +} + +static int get_filetype(const char *filename) +{ + const char *ext = filename + (strlen(filename) -4); + + if (ext < filename) + return FILETYPE_UNKNOWN; + + if (strncmp(ext, ".bin", 4) == 0) + return FILETYPE_BINARY; + + if (strncmp(ext, ".hex", 4) == 0) + return FILETYPE_INTELHEX; + + return FILETYPE_UNKNOWN; +} + +int file_getsize(const char *filename, uint32_t *size) +{ + switch (get_filetype(filename)) { + case FILETYPE_BINARY: + return binfile_getsize(filename, size); + + case FILETYPE_INTELHEX: + return hexfile_getsize(filename, size); + + default: + return -1; + } +} + +int file_read(const char *filename, struct databuf *dbuf) +{ + switch (get_filetype(filename)) { + case FILETYPE_BINARY: + return binfile_read(filename, dbuf); + + case FILETYPE_INTELHEX: + return hexfile_read(filename, dbuf); + + default: + return -1; + } +} + +int file_write(const char *filename, struct databuf *dbuf) +{ + if (strncmp(filename, "-", 1) == 0) { + dbuf_dump(dbuf); + return 0; + } + + switch (get_filetype(filename)) { + case FILETYPE_BINARY: + return binfile_write(filename, dbuf); + + case FILETYPE_INTELHEX: + return hexfile_write(filename, dbuf); + + default: + return -1; + } +} diff --git a/linux/list.h b/linux/list.h new file mode 100644 index 0000000..61f8d93 --- /dev/null +++ b/linux/list.h @@ -0,0 +1,268 @@ +#ifndef _LIST_H_ +#define _LIST_H_ + +/* + * stolen from linux kernel 2.6.11 (http://kernel.org/) + * linux/include/linux/stddef.h (offsetoff) + * linux/include/linux/kernel.h (container_of) + * linux/include/linux/list.h (*list*) + * linux/include/linux/netfilter_ipv4/listhelp.h (LIST_FIND) + * + * modified by Olaf Rempel + */ +#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) + +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) + +struct list_head { + struct list_head *next, *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) \ + struct list_head name = LIST_HEAD_INIT(name) + +#define INIT_LIST_HEAD(ptr) do { \ + (ptr)->next = (ptr); (ptr)->prev = (ptr); \ +} while (0) + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_add(struct list_head *new, + struct list_head *prev, + struct list_head *next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +/* + * list_add - add a new entry + * @new: new entry to be added + * @head: list head to add it after + * + * Insert a new entry after the specified head. + * This is good for implementing stacks. + */ +static inline void list_add(struct list_head *new, struct list_head *head) +{ + __list_add(new, head, head->next); +} + +/* + * list_add_tail - add a new entry + * @new: new entry to be added + * @head: list head to add it before + * + * Insert a new entry before the specified head. + * This is useful for implementing queues. + */ +static inline void list_add_tail(struct list_head *new, struct list_head *head) +{ + __list_add(new, head->prev, head); +} + +/* + * Delete a list entry by making the prev/next entries + * point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_del(struct list_head * prev, struct list_head * next) +{ + next->prev = prev; + prev->next = next; +} + +/* + * list_del - deletes entry from list. + * @entry: the element to delete from the list. + * Note: list_empty on entry does not return true after this, the entry is + * in an undefined state. + */ +static inline void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + entry->next = NULL; + entry->prev = NULL; +} + +/* + * list_del_init - deletes entry from list and reinitialize it. + * entry: the element to delete from the list. + */ +static inline void list_del_init(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + INIT_LIST_HEAD(entry); +} + +/* + * list_move - delete from one list and add as another's head + * @list: the entry to move + * @head: the head that will precede our entry + */ +static inline void list_move(struct list_head *list, struct list_head *head) +{ + __list_del(list->prev, list->next); + list_add(list, head); +} + +/* + * list_move_tail - delete from one list and add as another's tail + * @list: the entry to move + * @head: the head that will follow our entry + */ +static inline void list_move_tail(struct list_head *list, + struct list_head *head) +{ + __list_del(list->prev, list->next); + list_add_tail(list, head); +} + +/* + * list_empty - tests whether a list is empty + * @head: the list to test. + */ +static inline int list_empty(const struct list_head *head) +{ + return head->next == head; +} + +static inline void __list_splice(struct list_head *list, + struct list_head *head) +{ + struct list_head *first = list->next; + struct list_head *last = list->prev; + struct list_head *at = head->next; + + first->prev = head; + head->next = first; + + last->next = at; + at->prev = last; +} + +/* + * list_splice - join two lists + * @list: the new list to add. + * @head: the place to add it in the first list. + */ +static inline void list_splice(struct list_head *list, struct list_head *head) +{ + if (!list_empty(list)) + __list_splice(list, head); +} + +/* + * list_splice_init - join two lists and reinitialise the emptied list. + * @list: the new list to add. + * @head: the place to add it in the first list. + * + * The list at @list is reinitialised + */ +static inline void list_splice_init(struct list_head *list, + struct list_head *head) +{ + if (!list_empty(list)) { + __list_splice(list, head); + INIT_LIST_HEAD(list); + } +} + +/* + * list_entry - get the struct for this entry + * @ptr: the &struct list_head pointer. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + */ +#define list_entry(ptr, type, member) \ + container_of(ptr, type, member) + +/* + * list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + */ +#define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); pos = pos->next) + +/* + * list_for_each_prev - iterate over a list backwards + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + */ +#define list_for_each_prev(pos, head) \ + for (pos = (head)->prev; pos != (head); pos = pos->prev) + +/* + * list_for_each_safe - iterate over a list safe against removal of list entry + * @pos: the &struct list_head to use as a loop counter. + * @n: another &struct list_head to use as temporary storage + * @head: the head for your list. + */ +#define list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + +/* + * list_for_each_entry - iterate over list of given type + * @pos: the type * to use as a loop counter. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry(pos, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +/* + * list_for_each_entry_reverse - iterate backwards over list of given type. + * @pos: the type * to use as a loop counter. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_reverse(pos, head, member) \ + for (pos = list_entry((head)->prev, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.prev, typeof(*pos), member)) + +/* + * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry + * @pos: the type * to use as a loop counter. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_safe(pos, n, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member), \ + n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + + +/* Return pointer to first true entry, if any, or NULL. A macro + required to allow inlining of cmpfn. */ +#define LIST_FIND(head, cmpfn, type, args...) \ +({ \ + const struct list_head *__i, *__j = NULL; \ + \ + list_for_each(__i, (head)) \ + if (cmpfn((const type)__i , ## args)) { \ + __j = __i; \ + break; \ + } \ + (type)__j; \ +}) + +#endif /* _LIST_H_ */ diff --git a/linux/twb.c b/linux/twb.c new file mode 100644 index 0000000..2b68df9 --- /dev/null +++ b/linux/twb.c @@ -0,0 +1,301 @@ +/*************************************************************************** + * Copyright (C) 10/2010 by Olaf Rempel * + * razzor@kopf-tisch.de * + * * + * 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; version 2 of the License, * + * * + * 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 +#include + +#include "list.h" +#include "twiboot.h" + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +#define READ_BLOCK_SIZE 128 /* bytes in one flash/eeprom read request */ +#define WRITE_BLOCK_SIZE 16 /* bytes in one eeprom write request */ + +/* SLA+R */ +#define CMD_WAIT 0x00 +#define CMD_READ_VERSION 0x01 +#define CMD_READ_MEMORY 0x02 + +/* SLA+W */ +#define CMD_SWITCH_APPLICATION CMD_READ_VERSION +#define CMD_WRITE_MEMORY CMD_READ_MEMORY + +/* CMD_SWITCH_APPLICATION parameter */ +#define BOOTTYPE_BOOTLOADER 0x00 /* only in APP */ +#define BOOTTYPE_APPLICATION 0x80 + +/* CMD_{READ|WRITE}_* parameter */ +#define MEMTYPE_CHIPINFO 0x00 +#define MEMTYPE_FLASH 0x01 +#define MEMTYPE_EEPROM 0x02 +#define MEMTYPE_PARAMETERS 0x03 /* only in APP */ + + +static int twb_switch_application(struct twiboot *twb, uint8_t application) +{ + uint8_t cmd[] = { CMD_SWITCH_APPLICATION, application }; + + return (write(twb->fd, cmd, sizeof(cmd)) != sizeof(cmd)); +} + +static int twb_read_version(struct twiboot *twb) +{ + uint8_t cmd[] = { CMD_READ_VERSION }; + + if (write(twb->fd, cmd, sizeof(cmd)) != sizeof(cmd)) + return -1; + + memset(twb->version, 0, sizeof(twb->version)); + if (read(twb->fd, twb->version, sizeof(twb->version)) != sizeof(twb->version)) + return -1; + + int i; + for (i = 0; i < sizeof(twb->version); i++) + twb->version[i] &= ~0x80; + + return 0; +} + +static int twb_read_memory(struct twiboot *twb, uint8_t *buffer, uint8_t size, uint8_t memtype, uint16_t address) +{ + uint8_t cmd[] = { CMD_READ_MEMORY, memtype, (address >> 8) & 0xFF, (address & 0xFF) }; + if (write(twb->fd, cmd, sizeof(cmd)) != sizeof(cmd)) + return -1; + + return (read(twb->fd, buffer, size) != size); +} + +static int twb_write_memory(struct twiboot *twb, uint8_t *buffer, uint8_t size, uint8_t memtype, uint16_t address) +{ + int bufsize; + if (memtype == MEMTYPE_FLASH) { + if ((address & (twb->pagesize -1)) != 0x00) { + fprintf(stderr, "twb_write_memory(): address 0x%04x not aligned to pagesize 0x%02x\n", address, twb->pagesize); + return -1; + } + bufsize = 4 + twb->pagesize; + + } else { + bufsize = 4 + size; + } + + uint8_t *cmd = malloc(bufsize); + if (cmd == NULL) + return -1; + + cmd[0] = CMD_WRITE_MEMORY; + cmd[1] = memtype; + cmd[2] = (address >> 8) & 0xFF; + cmd[3] = (address & 0xFF); + memcpy(cmd +4, buffer, size); + + if (memtype == MEMTYPE_FLASH) { + memset(cmd +4 +size, 0xFF, twb->pagesize - size); + } + + int result = write(twb->fd, cmd, bufsize); + free(cmd); + + return (result != bufsize); +} + +static void twb_close_device(struct twiboot *twb) +{ + if (twb->connected) + close(twb->fd); + + if (twb->device != NULL) + free(twb->device); + + twb->device = NULL; +} + +static int twb_open_device(struct twiboot *twb) +{ + twb->fd = open(twb->device, O_RDWR); + if (twb->fd < 0) { + fprintf(stderr, "failed to open '%s': %s\n", twb->device, strerror(errno)); + return -1; + } + + unsigned long funcs; + if (ioctl(twb->fd, I2C_FUNCS, &funcs)) { + perror("ioctl(I2C_FUNCS)"); + close(twb->fd); + return -1; + } + + if (!(funcs & I2C_FUNC_I2C)) { + fprintf(stderr, "I2C_FUNC_I2C not supported on '%s'!\n", twb->device); + close(twb->fd); + return -1; + } + + if (ioctl(twb->fd, I2C_SLAVE, twb->address) < 0) { + fprintf(stderr, "failed to select slave address '%d': %s\n", twb->address, strerror(errno)); + close(twb->fd); + return -1; + } + + twb->connected = 1; + return 0; +} + +int twb_close(struct twiboot *twb) +{ + if (twb->connected) + twb_switch_application(twb, BOOTTYPE_APPLICATION); + + twb_close_device(twb); + return 0; +} + +int twb_open(struct twiboot *twb) +{ + if (twb_open_device(twb) != 0) + return -1; + + if (twb_switch_application(twb, BOOTTYPE_BOOTLOADER)) { + fprintf(stderr, "failed to switch to bootloader (invalid address?): %s\n", strerror(errno)); + twb_close(twb); + return -1; + } + + if (twb_read_version(twb)) { + fprintf(stderr, "failed to get bootloader version: %s\n", strerror(errno)); + twb_close(twb); + return -1; + } + + uint8_t chipinfo[8]; + if (twb_read_memory(twb, chipinfo, sizeof(chipinfo), MEMTYPE_CHIPINFO, 0x0000)) { + fprintf(stderr, "failed to get chipinfo: %s\n", strerror(errno)); + twb_close(twb); + return -1; + } + + memcpy(twb->signature, chipinfo, sizeof(twb->signature)); + twb->pagesize = chipinfo[3]; + twb->flashsize = (chipinfo[4] << 8) + chipinfo[5]; + twb->eepromsize = (chipinfo[6] << 8) + chipinfo[7]; + + return 0; +} + +int twb_read(struct twiboot *twb, struct databuf *dbuf, int memtype) +{ + int pos = 0; + int size = (memtype == MEMTYPE_FLASH) ? twb->flashsize : twb->eepromsize; + + while (pos < size) { + if (twb->progress_cb) + twb->progress_cb(twb->progress_msg, pos, size); + + int len = MIN(READ_BLOCK_SIZE, size - pos); + if (twb_read_memory(twb, dbuf->data + pos, len, memtype, pos)) { + if (twb->progress_cb) + twb->progress_cb(twb->progress_msg, -1, -1); + + return -1; + } + + pos += len; + } + + if (twb->progress_cb) + twb->progress_cb(twb->progress_msg, pos, size); + + dbuf->length = pos; + return 0; +} + +int twb_write(struct twiboot *twb, struct databuf *dbuf, int memtype) +{ + int pos = 0; + + while (pos < dbuf->length) { + if (twb->progress_cb) + twb->progress_cb(twb->progress_msg, pos, dbuf->length); + + int len = (memtype == MEMTYPE_FLASH) ? twb->pagesize : WRITE_BLOCK_SIZE; + + len = MIN(len, dbuf->length - pos); + if (twb_write_memory(twb, dbuf->data + pos, len, memtype, pos)) { + if (twb->progress_cb) + twb->progress_cb(twb->progress_msg, -1, -1); + + return -1; + } + + pos += len; + } + + if (twb->progress_cb) + twb->progress_cb(twb->progress_msg, pos, dbuf->length); + + return 0; +} + +int twb_verify(struct twiboot *twb, struct databuf *dbuf, int memtype) +{ + int pos = 0; + int size = (memtype == MEMTYPE_FLASH) ? twb->flashsize : twb->eepromsize; + uint8_t comp[READ_BLOCK_SIZE]; + + while (pos < size) { + if (twb->progress_cb) + twb->progress_cb(twb->progress_msg, pos, size); + + int len = MIN(READ_BLOCK_SIZE, size - pos); + if (twb_read_memory(twb, comp, len, memtype, pos)) { + if (twb->progress_cb) + twb->progress_cb(twb->progress_msg, -1, -1); + + return -1; + } + + if (memcmp(comp, dbuf->data + pos, len) != 0x00) { + if (twb->progress_cb) + twb->progress_cb(twb->progress_msg, -1, -1); + + fprintf(stderr, "verify failed at page 0x%04x!!\n", pos); + return -1; + } + + pos += len; + } + + if (twb->progress_cb) + twb->progress_cb(twb->progress_msg, pos, size); + + dbuf->length = pos; + return 0; +} diff --git a/linux/twiboot.c b/linux/twiboot.c new file mode 100644 index 0000000..0a22b1e --- /dev/null +++ b/linux/twiboot.c @@ -0,0 +1,341 @@ +/*************************************************************************** + * Copyright (C) 10/2010 by Olaf Rempel * + * razzor@kopf-tisch.de * + * * + * 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; version 2 of the License, * + * * + * 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 "list.h" +#include "twiboot.h" + +#define OP_READ 0x01 +#define OP_WRITE 0x02 + +struct operation { + struct list_head list; + + char *filename; + int flags; + + int mode; + int memtype; +}; + +static LIST_HEAD(operation_list); + +static struct option opts[] = { + {"address", 1, 0, 'a'}, // -a + {"device", 1, 0, 'd'}, // [ -d ] + {"help", 0, 0, 'h'}, // [ -h ] + {"no-progress", 0, 0, 'p'}, // [ -p ] + {"read", 1, 0, 'r'}, // [ -r : ] + {"write", 1, 0, 'w'}, // [ -w : ] + {"no-verify", 0, 0, 'n'}, // [ -n ] + {0, 0, 0, 0} +}; + +static struct operation * alloc_operation(const char *arg) +{ + struct operation *op = malloc(sizeof(struct operation)); + if (op == NULL) { + perror("malloc()"); + return NULL; + } + + if (strncmp(arg, "flash:", 6) == 0) { + op->memtype = DATATYPE_FLASH; + op->filename = strdup(arg + 6); + + } else if (strncmp(arg, "eeprom:", 7) == 0) { + op->memtype = DATATYPE_EEPROM; + op->filename = strdup(arg + 7); + + } else { + fprintf(stderr, "invalid memtype: '%s'\n", arg); + return NULL; + } + + return op; +} + +static char * check_signature(uint8_t *sig) +{ + if (sig[0] == 0x1E && sig[1] == 0x93 && sig[2] == 0x07) + return "AVR Mega 8"; + + if (sig[0] == 0x1E && sig[1] == 0x93 && sig[2] == 0x0A) + return "AVR Mega 88"; + + if (sig[0] == 0x1E && sig[1] == 0x94 && sig[2] == 0x06) + return "AVR Mega 168"; + + return "unknown"; +} + +static void progress_cb(const char *msg, int pos, int size) +{ + if (pos != -1 && size != -1) { + char stars[50]; + int i; + + for (i = 0; i < sizeof(stars); i++) + stars[i] = ((pos * 100 / size) >= (i * 100 / sizeof(stars))) ? '*' : ' '; + + printf("%-14s: [%s] (%d)\r", msg, stars, pos); + } + + if (pos == size) + printf("\n"); +} + +int main(int argc, char *argv[]) +{ + struct twiboot twb; + int verify = 1, progress = 1; + + memset(&twb, 0, sizeof(struct twiboot)); + + int arg = 0, code = 0, abort = 0; + while (code != -1) { + code = getopt_long(argc, argv, "a:d:hnpr:w:", opts, &arg); + + switch (code) { + case 'a': /* address */ + { + char *endptr; + twb.address = strtol(optarg, &endptr, 16); + if (*endptr != '\0' || twb.address < 0x01 || twb.address > 0x7F) { + fprintf(stderr, "invalid address: '%s'\n", optarg); + abort = 1; + break; + } + break; + } + + case 'd': /* device */ + if (twb.device != NULL) { + fprintf(stderr, "invalid device: '%s'\n", optarg); + abort = 1; + break; + } + + twb.device = strdup(optarg); + if (twb.device == NULL) { + perror("strdup()"); + abort = 1; + break; + } + break; + + case 'r': /* read */ + { + struct operation *op = alloc_operation(optarg); + if (op != NULL) { + op->mode = OP_READ; + list_add_tail(&op->list, &operation_list); + + } else { + abort = 1; + } + break; + } + + case 'w': /* write */ + { + struct operation *op = alloc_operation(optarg); + if (op != NULL) { + op->mode = OP_WRITE; + list_add_tail(&op->list, &operation_list); + + } else { + abort = 1; + } + break; + } + + case 'n': /* no verify */ + verify = 0; + break; + + case 'p': /* no progress */ + progress = 0; + break; + + case 'h': + case '?': /* error */ + fprintf(stderr, "Usage: twiboot [options]\n" + " -a
- selects i2c address (0x01 - 0x7F)\n" + " -d - selects i2c device (default: /dev/i2c-0)\n" + " -r : - reads flash/eeprom to file (.bin | .hex | -)\n" + " -w : - write flash/eeprom from file (.bin | .hex)\n" + " -n - disable verify after write\n" + " -p - disable progress bars\n" + "\n" + "Example: twiboot -a 0x22 -w flash:blmc.hex -w flash:blmc_eeprom.hex\n" + "\n"); + abort = 1; + break; + + default: /* unknown / all options parsed */ + break; + } + } + + if (twb.address == 0) { + fprintf(stderr, "abort: no address given\n"); + abort = 1; + } + + if (twb.device == NULL) { + twb.device = strdup("/dev/i2c-0"); + if (twb.device == NULL) { + perror("strdup()"); + abort = 1; + } + } + + if (!abort) { + if (twb_open(&twb) != 0x00) + abort = 1; + } + + if (!abort) { + printf("device : %-16s (address: 0x%02x)\n", twb.device, twb.address); + printf("version : %-16s (sig: 0x%02x 0x%02x 0x%02x => %s)\n", twb.version, twb.signature[0], twb.signature[1], twb.signature[2], check_signature(twb.signature)); + printf("flash size : 0x%04x (0x%02x bytes/page)\n", twb.flashsize, twb.pagesize); + printf("eeprom size : 0x%04x\n", twb.eepromsize); + + if (progress) { + setbuf(stdout, NULL); + twb.progress_cb = progress_cb; + } + + struct operation *op; + list_for_each_entry(op, &operation_list, list) { + abort = 1; + if (op->mode == OP_READ) { + struct databuf *dbuf; + int result; + + if (op->memtype == DATATYPE_FLASH) { + twb.progress_msg = "reading flash"; + result = dbuf_alloc(&dbuf, twb.flashsize); + } else if (op->memtype == DATATYPE_EEPROM) { + twb.progress_msg = "reading eeprom"; + result = dbuf_alloc(&dbuf, twb.eepromsize); + } + + if (result != 0x00) + break; + + result = twb_read(&twb, dbuf, op->memtype); + if (result != 0x00) { + fprintf(stderr, "failed to read from device\n"); + dbuf_free(dbuf); + break; + } + + result = file_write(op->filename, dbuf); + if (result != 0x00) { + fprintf(stderr, "failed to write file '%s'\n", op->filename); + dbuf_free(dbuf); + break; + } + + dbuf_free(dbuf); + + } else if (op->mode == OP_WRITE) { + struct databuf *dbuf; + unsigned int size; + int result; + + result = file_getsize(op->filename, &size); + if (result != 0x00) + break; + + result = dbuf_alloc(&dbuf, size); + if (result != 0x00) + break; + + result = file_read(op->filename, dbuf); + if (result != 0x00) { + fprintf(stderr, "failed to read file '%s'\n", op->filename); + dbuf_free(dbuf); + break; + } + + if (op->memtype == DATATYPE_FLASH) { + twb.progress_msg = "writing flash"; + + if (dbuf->length > twb.flashsize) { + fprintf(stderr, "invalid flash size: 0x%04x > 0x%04x\n", dbuf->length, twb.flashsize); + dbuf_free(dbuf); + break; + } + + } else if (op->memtype == DATATYPE_EEPROM) { + twb.progress_msg = "writing eeprom"; + + if (dbuf->length > twb.eepromsize) { + fprintf(stderr, "invalid eeprom size: 0x%04x > 0x%04x\n", dbuf->length, twb.eepromsize); + dbuf_free(dbuf); + break; + } + } + + result = twb_write(&twb, dbuf, op->memtype); + if (result != 0x00) { + fprintf(stderr, "failed to write to device\n"); + dbuf_free(dbuf); + break; + } + + if (verify) { + if (op->memtype == DATATYPE_FLASH) { + twb.progress_msg = "verifing flash"; + } else if (op->memtype == DATATYPE_EEPROM) { + twb.progress_msg = "verifing eeprom"; + } + + result = twb_verify(&twb, dbuf, op->memtype); + if (result != 0) { + fprintf(stderr, "failed to verify\n"); + dbuf_free(dbuf); + break; + } + } + + dbuf_free(dbuf); + } + abort = 0; + } + } + + struct operation *op, *tmp; + list_for_each_entry_safe(op, tmp, &operation_list, list) { + free(op->filename); + free(op); + } + + twb_close(&twb); + + return abort; +} diff --git a/linux/twiboot.h b/linux/twiboot.h new file mode 100644 index 0000000..9035155 --- /dev/null +++ b/linux/twiboot.h @@ -0,0 +1,45 @@ +#ifndef _TWIBOOT_H_ +#define _TWIBOOT_H_ + +#include + +struct databuf { + uint32_t size; // allocation size + uint32_t length; // used size + uint8_t data[0]; +}; + +int dbuf_alloc(struct databuf **dbuf, uint32_t size); +void dbuf_free(struct databuf *dbuf); + +int file_getsize(const char *filename, uint32_t *size); +int file_read(const char *filename, struct databuf *dbuf); +int file_write(const char *filename, struct databuf *dbuf); + +struct twiboot { + char *device; + uint8_t address; + int fd; + int connected; + + char version[16]; + uint8_t signature[3]; + uint8_t pagesize; + uint16_t flashsize; + uint16_t eepromsize; + + void (* progress_cb)(const char *msg, int pos, int max); + char *progress_msg; +}; + +int twb_open(struct twiboot *twb); +int twb_close(struct twiboot *twb); + +#define DATATYPE_FLASH 0x01 +#define DATATYPE_EEPROM 0x02 + +int twb_read(struct twiboot *twb, struct databuf *dbuf, int memtype); +int twb_verify(struct twiboot *twb, struct databuf *dbuf, int memtype); +int twb_write(struct twiboot *twb, struct databuf *dbuf, int memtype); + +#endif /* _TWIBOOT_H_ */