Explorar o código

Firmware Update Bootloader

Keir Fraser %!s(int64=5) %!d(string=hai) anos
pai
achega
ac588f09e0

+ 1 - 0
.gitignore

@@ -11,4 +11,5 @@
 *.upd
 *.adf
 *.scp
+*.pyc
 Greaseweazle-*

+ 26 - 8
Makefile

@@ -1,25 +1,28 @@
 
-export FW_VER := 0.1
+export FW_MAJOR := 0
+export FW_MINOR := 1
 
 PROJ = Greaseweazle
-VER := v$(FW_VER)
+VER := v$(FW_MAJOR).$(FW_MINOR)
 
-SUBDIRS += src
+SUBDIRS += src bootloader
 
 .PHONY: all clean dist mrproper flash start serial
 
 ifneq ($(RULES_MK),y)
 export ROOT := $(CURDIR)
+
 all:
-	$(MAKE) -C src -f $(ROOT)/Rules.mk $(PROJ).elf $(PROJ).bin $(PROJ).hex
-	cp -a src/$(PROJ).hex $(PROJ)-$(VER).hex
+	$(MAKE) -f $(ROOT)/Rules.mk $@
 
 clean:
+	rm -f *.hex *.upd scripts/greaseweazle/*.pyc
+	rm -rf scripts/greaseweazle/__pycache__
 	$(MAKE) -f $(ROOT)/Rules.mk $@
 
 dist:
 	rm -rf $(PROJ)-*
-	mkdir -p $(PROJ)-$(VER)/scripts
+	mkdir -p $(PROJ)-$(VER)/scripts/greaseweazle
 	$(MAKE) clean
 	$(MAKE) all
 	cp -a $(PROJ)-$(VER).hex $(PROJ)-$(VER)/
@@ -27,20 +30,35 @@ dist:
 	cp -a COPYING $(PROJ)-$(VER)/
 	cp -a README.md $(PROJ)-$(VER)/
 	cp -a scripts/49-greaseweazle.rules $(PROJ)-$(VER)/scripts/.
-	cp -a scripts/gw.py $(PROJ)-$(VER)/
+	cp -a scripts/gw.py $(PROJ)-$(VER)/scripts/.
+	cp -a scripts/greaseweazle/*.py $(PROJ)-$(VER)/scripts/*.py
 #	cp -a RELEASE_NOTES $(PROJ)-$(VER)/
 	zip -r $(PROJ)-$(VER).zip $(PROJ)-$(VER)
 
 mrproper: clean
 	rm -rf $(PROJ)-*
 
+else
+
+all: scripts/greaseweazle/version.py
+	$(MAKE) -C src -f $(ROOT)/Rules.mk $(PROJ).elf $(PROJ).bin $(PROJ).hex
+	bootloader=y $(MAKE) -C bootloader -f $(ROOT)/Rules.mk \
+		Bootloader.elf Bootloader.bin Bootloader.hex
+	srec_cat bootloader/Bootloader.hex -Intel src/$(PROJ).hex -Intel \
+	-o $(PROJ)-$(VER).hex -Intel
+	$(PYTHON) ./scripts/mk_update.py src/$(PROJ).bin $(PROJ)-$(VER).upd
+
+scripts/greaseweazle/version.py: Makefile
+	echo "major = $(FW_MAJOR)" >$@
+	echo "minor = $(FW_MINOR)" >>$@
+
 endif
 
 BAUD=115200
 DEV=/dev/ttyUSB0
 
 flash: all
-	sudo stm32flash -b $(BAUD) -w src/$(PROJ).hex $(DEV)
+	sudo stm32flash -b $(BAUD) -w $(PROJ)-$(VER).hex $(DEV)
 
 start:
 	sudo stm32flash -b $(BAUD) -g 0 $(DEV)

+ 7 - 1
Rules.mk

@@ -3,6 +3,8 @@ CC = $(TOOL_PREFIX)gcc
 OBJCOPY = $(TOOL_PREFIX)objcopy
 LD = $(TOOL_PREFIX)ld
 
+PYTHON = python
+
 ifneq ($(VERBOSE),1)
 TOOL_PREFIX := @$(TOOL_PREFIX)
 endif
@@ -18,6 +20,10 @@ ifneq ($(debug),y)
 FLAGS += -DNDEBUG
 endif
 
+ifeq ($(bootloader),y)
+FLAGS += -DBOOTLOADER=1
+endif
+
 FLAGS += -MMD -MF .$(@F).d
 DEPS = .*.d
 
@@ -84,7 +90,7 @@ build.o: $(OBJS)
 	$(CC) $(AFLAGS) -c $< -o $@
 
 clean:: $(addprefix _clean_,$(SUBDIRS) $(SUBDIRS-n) $(SUBDIRS-))
-	rm -f *~ *.o *.elf *.hex *.bin *.ld $(DEPS)
+	rm -f *.orig *.rej *~ *.o *.elf *.hex *.bin *.ld $(DEPS)
 _clean_%: FORCE
 	$(MAKE) -f $(ROOT)/Rules.mk -C $* clean
 

+ 7 - 0
bootloader/Bootloader.ld.S

@@ -0,0 +1,7 @@
+#define FLASH_BASE 0x08000000
+#define FLASH_LEN  8K
+
+#define RAM_BASE   0x20000000
+#define RAM_LEN    20K
+
+#include "../scripts/stm32f1.ld.S"

+ 18 - 0
bootloader/Makefile

@@ -0,0 +1,18 @@
+RPATH = ../src
+
+OBJS += board.o
+OBJS += build_info.o
+OBJS += crc.o
+OBJS += vectors.o
+OBJS += fw_update.o
+OBJS += string.o
+OBJS += stm32f10x.o
+OBJS += util.o
+OBJS += fpec.o
+
+OBJS-$(debug) += console.o
+
+SUBDIRS += usb
+
+.PHONY: $(RPATH)/build_info.c
+build_info.o: CFLAGS += -DFW_MAJOR=$(FW_MAJOR) -DFW_MINOR=$(FW_MINOR)

+ 8 - 0
bootloader/usb/Makefile

@@ -0,0 +1,8 @@
+RPATH = ../../src/usb
+
+OBJS += config.o
+OBJS += core.o
+OBJS += cdc_acm.o
+OBJS += hw_f1.o
+
+$(OBJS): CFLAGS += -include $(ROOT)/src/usb/defs.h

+ 0 - 32
inc/cancellation.h

@@ -1,32 +0,0 @@
-/*
- * cancellation.h
- * 
- * Asynchronously-cancellable function calls.
- * 
- * Written & released by Keir Fraser <keir.xen@gmail.com>
- * 
- * This is free and unencumbered software released into the public domain.
- * See the file COPYING for more details, or visit <http://unlicense.org>.
- */
-
-struct cancellation {
-    uint32_t *sp;
-};
-
-#define cancellation_is_active(c) ((c)->sp != NULL)
-
-/* Execute fn() in a wrapped cancellable environment. */
-int call_cancellable_fn(struct cancellation *c, int (*fn)(void *), void *arg);
-
-/* From IRQ content: stop running fn() and immediately return -1. */
-void cancel_call(struct cancellation *c);
-
-/*
- * Local variables:
- * mode: C
- * c-file-style: "Linux"
- * c-basic-offset: 4
- * tab-width: 4
- * indent-tabs-mode: nil
- * End:
- */

+ 62 - 0
inc/cdc_acm_protocol.h

@@ -0,0 +1,62 @@
+/*
+ * cdc_acm_protocol.h
+ * 
+ * Greaseweazle protocol over CDC ACM streams.
+ * 
+ * Written & released by Keir Fraser <keir.xen@gmail.com>
+ * 
+ * This is free and unencumbered software released into the public domain.
+ * See the file COPYING for more details, or visit <http://unlicense.org>.
+ */
+
+/* CMD_GET_INFO, length=3, 0. Returns 32 bytes after ACK. */
+#define CMD_GET_INFO        0
+/* CMD_SEEK, length=3, cyl# */
+#define CMD_SEEK            1
+/* CMD_SIDE, length=3, side# (0=bottom) */
+#define CMD_SIDE            2
+/* CMD_SET_DELAYS, length=2+4*2, <delay_params> */
+#define CMD_SET_DELAYS      3
+/* CMD_GET_DELAYS, length=2. Returns 4*2 bytes after ACK. */
+#define CMD_GET_DELAYS      4
+/* CMD_MOTOR, length=3, motor_state */
+#define CMD_MOTOR           5
+/* CMD_READ_FLUX, length=3, #revs. Returns flux readings until EOStream. */
+#define CMD_READ_FLUX       6
+/* CMD_WRITE_FLUX, length=2. Host follows with flux readings until EOStream. */
+#define CMD_WRITE_FLUX      7
+/* CMD_GET_FLUX_STATUS, length=2. Last read/write status returned in ACK. */
+#define CMD_GET_FLUX_STATUS 8
+/* CMD_GET_READ_INFO, length=2. Returns 7*8 bytes after ACK. */
+#define CMD_GET_READ_INFO   9
+
+/* [BOOTLOADER] CMD_UPDATE, length=6, <update_len>. 
+ * Host follows with <update_len> bytes.
+ * Bootloader finally returns a status byte, 0 on success. */
+#define CMD_UPDATE          1
+
+#define ACK_OKAY            0
+#define ACK_BAD_COMMAND     1
+#define ACK_NO_INDEX        2
+#define ACK_NO_TRK0         3
+#define ACK_FLUX_OVERFLOW   4
+#define ACK_FLUX_UNDERFLOW  5
+#define ACK_WRPROT          6
+
+struct __packed gw_info {
+    uint8_t fw_major;
+    uint8_t fw_minor;
+    uint8_t max_rev;
+    uint8_t max_cmd;
+    uint32_t sample_freq;
+};
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "Linux"
+ * c-basic-offset: 4
+ * tab-width: 4
+ * indent-tabs-mode: nil
+ * End:
+ */

+ 2 - 1
inc/decls.h

@@ -20,8 +20,9 @@
 #include "intrinsics.h"
 
 #include "time.h"
-#include "cancellation.h"
 #include "timer.h"
+#include "usb.h"
+#include "cdc_acm_protocol.h"
 
 /*
  * Local variables:

+ 52 - 0
inc/usb.h

@@ -0,0 +1,52 @@
+/*
+ * usb.h
+ * 
+ * USB stack entry points and callbacks.
+ * 
+ * Written & released by Keir Fraser <keir.xen@gmail.com>
+ * 
+ * This is free and unencumbered software released into the public domain.
+ * See the file COPYING for more details, or visit <http://unlicense.org>.
+ */
+
+/* Full Speed Max Packet Size */
+#define USB_FS_MPS 64
+
+/* Class-specific callback hooks */
+struct usb_class_ops {
+    void (*reset)(void);
+    void (*configure)(void);
+};
+extern const struct usb_class_ops usb_cdc_acm_ops;
+
+/* USB Endpoints for CDC ACM communications. */
+#define EP_RX 2
+#define EP_TX 3
+
+/* Main entry points for USB processing. */
+void usb_init(void);
+void usb_process(void);
+
+/* Does OUT endpoint have data ready? If so return packet length, else -1. */
+int ep_rx_ready(uint8_t ep);
+
+/* Consume the next OUT packet, returning @len bytes. 
+ * REQUIRES: ep_rx_ready(@ep) >= @len */
+void usb_read(uint8_t ep, void *buf, uint32_t len);
+
+/* Is IN endpoint ready for next packet? */
+bool_t ep_tx_ready(uint8_t ep);
+
+/* Queue the next IN packet, with the given payload data. 
+ * REQUIRES: ep_tx_ready(@ep) == TRUE */
+void usb_write(uint8_t ep, const void *buf, uint32_t len);
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "Linux"
+ * c-basic-offset: 4
+ * tab-width: 4
+ * indent-tabs-mode: nil
+ * End:
+ */

+ 1 - 23
inc/util.h

@@ -61,11 +61,6 @@ size_t strlen(const char *s);
 size_t strnlen(const char *s, size_t maxlen);
 int strcmp(const char *s1, const char *s2);
 int strncmp(const char *s1, const char *s2, size_t n);
-char *strrchr(const char *s, int c);
-int tolower(int c);
-int isspace(int c);
-
-long int strtol(const char *nptr, char **endptr, int base);
 
 int vsnprintf(char *str, size_t size, const char *format, va_list ap)
     __attribute__ ((format (printf, 3, 0)));
@@ -82,12 +77,6 @@ int snprintf(char *str, size_t size, const char *format, ...)
 #define htobe16(x) _rev16(x)
 #define htobe32(x) _rev32(x)
 
-/* Arena-based memory allocation */
-void *arena_alloc(uint32_t sz);
-uint32_t arena_total(void);
-uint32_t arena_avail(void);
-void arena_init(void);
-
 /* Board-specific callouts */
 void board_init(void);
 extern uint8_t board_id;
@@ -115,26 +104,15 @@ static inline int printk(const char *format, ...) { return 0; }
 
 #endif
 
-/* USB */
-void usb_init(void);
-void usb_process(void);
-int ep_rx_ready(uint8_t ep);
-void usb_read(uint8_t ep, void *buf, uint32_t len);
-bool_t ep_tx_ready(uint8_t ep);
-void usb_write(uint8_t ep, const void *buf, uint32_t len);
-#define USB_FS_MPS 64 /* Full Speed Max Packet Size */
-
 /* Floppy */
 void floppy_init(void);
-void floppy_reset(void);
-void floppy_configured(void);
 void floppy_process(void);
 
 /* CRC-CCITT */
 uint16_t crc16_ccitt(const void *buf, size_t len, uint16_t crc);
 
 /* Build info. */
-extern const char fw_ver[];
+extern const uint8_t fw_major, fw_minor;
 
 /* Text/data/BSS address ranges. */
 extern char _stext[], _etext[];

+ 0 - 0
scripts/greaseweazle/__init__.py


+ 2 - 0
scripts/greaseweazle/version.py

@@ -0,0 +1,2 @@
+major = 0
+minor = 1

+ 58 - 6
scripts/gw.py

@@ -7,9 +7,12 @@
 # This is free and unencumbered software released into the public domain.
 # See the file COPYING for more details, or visit <http://unlicense.org>.
 
+import crcmod.predefined
 import sys, struct, argparse, serial, collections
 from timeit import default_timer as timer
 
+from greaseweazle import version
+
 # 40MHz
 scp_freq = 40000000
 
@@ -24,6 +27,9 @@ CMD_WRITE_FLUX      = 7
 CMD_GET_FLUX_STATUS = 8
 CMD_GET_READ_INFO   = 9
 
+# Bootloader-specific:
+CMD_UPDATE          = 1
+
 ACK_OKAY            = 0
 ACK_BAD_COMMAND     = 1
 ACK_NO_INDEX        = 2
@@ -38,7 +44,8 @@ ack_str = [
   "Flux Overflow", "Flux Underflow", "Disk is Write Protected" ]
 
 class CmdError(Exception):
-  def __init__(self, code):
+  def __init__(self, cmd, code):
+    self.cmd = cmd
     self.code = code
   def __str__(self):
     if self.code <= ACK_MAX:
@@ -50,7 +57,7 @@ def send_cmd(cmd):
   (c,r) = struct.unpack("2B", ser.read(2))
   assert c == cmd[0]
   if r != 0:
-    raise CmdError(r)
+    raise CmdError(c,r)
 
 def get_fw_info():
   send_cmd(struct.pack("3B", CMD_GET_INFO, 3, 0))
@@ -300,11 +307,33 @@ def write(args):
     write_flux(flux)
   print()
 
+def update(args):
+  with open(args.file, "rb") as f:
+    dat = f.read()
+  (sig, maj, min, pad1, pad2, crc) = struct.unpack(">2s4BH", dat[-8:])
+  if len(dat) & 3 != 0 or sig != b'GW' or pad1 != 0 or pad2 != 0:
+    print("%s: Bad update file" % (args.file))
+    return
+  crc16 = crcmod.predefined.Crc('crc-ccitt-false')
+  crc16.update(dat)
+  if crc16.crcValue != 0:
+    print("%s: Bad CRC" % (args.file))
+  print("Updating to v%u.%u..." % (maj, min))
+  send_cmd(struct.pack("<2BI", CMD_UPDATE, 6, len(dat)))
+  ser.write(dat)
+  (ack,) = struct.unpack("B", ser.read(1))
+  if ack != 0:
+    print("** UPDATE FAILED: Please retry!")
+    return
+  print("Done.")
+  print("** Now remove the Programming Jumper and reconnect.")
+
 def main(argv):
 
   actions = {
     "read" : read,
-    "write" : write
+    "write" : write,
+    "update" : update
   }
   
   parser = argparse.ArgumentParser(
@@ -323,6 +352,12 @@ def main(argv):
   parser.add_argument("device", help="serial device")
   args = parser.parse_args(argv[1:])
 
+  if not args.action in actions:
+    print("** Action \"%s\" is not recognised" % args.action)
+    print("Valid actions: ", end="")
+    print(", ".join(str(key) for key in actions.keys()))
+    return
+  
   global ser
   ser = serial.Serial(args.device)
   ser.send_break()
@@ -330,19 +365,36 @@ def main(argv):
 
   global sample_freq
   info = get_fw_info()
-  #print_fw_info(info)
   sample_freq = info[4]
+  update_mode = (info[2] == 0)
+
+  print("** Greaseweazle %sv%u.%u"
+        % (("","Bootloader ")[update_mode], info[0], info[1]))
+
+  if update_mode and args.action != "update":
+    print("Greaseweazle is in Firmware Update Mode:")
+    print(" The only available action is \"update <update_file>\"")
+    if info[4] & 1:
+      print(" Remove the Update Jumper for normal operation")
+    else:
+      print(" Main firmware is erased: You *must* perform an update!")
+    return
+
+  if not update_mode and args.action == "update":
+    print("Greaseweazle is in Normal Mode:")
+    print(" To \"update\" you must install the Update Jumper")
+    return
   
   #set_delays(step_delay=3)
   #print_delays(get_delays())
 
   actions[args.action](args)
 
-  motor(False)
+  if not update_mode:
+    motor(False)
   
 if __name__ == "__main__":
   try:
     main(sys.argv)
   except CmdError as error:
     print("Command Failed: %s" % error)
-    motor(False)

+ 36 - 0
scripts/mk_update.py

@@ -0,0 +1,36 @@
+# mk_update.py
+#
+# Convert a raw firmware binary into an update file for our bootloader:
+#  N bytes: <raw binary data>
+#  2 bytes: 'GW'
+#  2 bytes: major, minor
+#  2 bytes: 0, 0
+#  2 bytes: CRC16-CCITT, seed 0xFFFF, stored big endian
+# 
+# Written & released by Keir Fraser <keir.xen@gmail.com>
+# 
+# This is free and unencumbered software released into the public domain.
+# See the file COPYING for more details, or visit <http://unlicense.org>.
+
+import crcmod.predefined
+import struct, sys
+
+from greaseweazle import version
+
+def main(argv):
+    in_f = open(argv[1], "rb")
+    out_f = open(argv[2], "wb")
+    in_dat = in_f.read()
+    in_len = len(in_dat)
+    assert (in_len & 3) == 0, "input is not longword padded"
+    crc16 = crcmod.predefined.Crc('crc-ccitt-false')
+    out_f.write(in_dat)
+    crc16.update(in_dat)
+    in_dat = struct.pack("2s4B", b'GW', version.major, version.minor, 0, 0)
+    out_f.write(in_dat)
+    crc16.update(in_dat)
+    in_dat = struct.pack(">H", crc16.crcValue)
+    out_f.write(in_dat)
+
+if __name__ == "__main__":
+    main(sys.argv)

+ 56 - 0
scripts/stm32f1.ld.S

@@ -0,0 +1,56 @@
+ENTRY(vector_table)
+
+MEMORY
+{
+  FLASH (rx)      : ORIGIN = FLASH_BASE, LENGTH = FLASH_LEN
+  RAM (rwx)       : ORIGIN = RAM_BASE, LENGTH = RAM_LEN
+}
+REGION_ALIAS("RO", FLASH);
+REGION_ALIAS("RW", RAM);
+
+SECTIONS
+{
+  .text : {
+    _stext = .;
+    *(.vector_table)
+    *(.text)
+    *(.text*)
+    *(.rodata)
+    *(.rodata*)
+    KEEP (*(.init))
+    KEEP (*(.fini))
+    . = ALIGN(4);
+    _etext = .;
+  } >RO
+
+  .data : AT (_etext) {
+    . = ALIGN(4);
+    _sdat = .;
+    *(.data)
+    *(.data*)
+    . = ALIGN(4);
+    _edat = .;
+    _ldat = LOADADDR(.data);
+  } >RW
+
+  .bss : {
+    . = ALIGN(8);
+    _irq_stackbottom = .;
+    . = . + 512;
+    _irq_stacktop = .;
+    _thread_stackbottom = .;
+    . = . + 1024;
+    _thread_stacktop = .;
+    _sbss = .;
+    *(.bss)
+    *(.bss*)
+    . = ALIGN(4);
+    _ebss = .;
+  } >RW
+
+  /DISCARD/ : {
+    *(.eh_frame)
+  }
+
+  .ARM.attributes 0 : { *(.ARM.attributes) }
+}

+ 5 - 54
src/Greaseweazle.ld.S

@@ -1,56 +1,7 @@
-ENTRY(vector_table)
+#define FLASH_BASE 0x08002000
+#define FLASH_LEN  56K
 
-MEMORY
-{
-  FLASH (rx)      : ORIGIN = 0x08000000, LENGTH = 64K
-  RAM (rwx)       : ORIGIN = 0x20000000, LENGTH = 20K
-}
-REGION_ALIAS("RO", FLASH);
-REGION_ALIAS("RW", RAM);
+#define RAM_BASE   0x20000000
+#define RAM_LEN    20K
 
-SECTIONS
-{
-  .text : {
-    _stext = .;
-    *(.vector_table)
-    *(.text)
-    *(.text*)
-    *(.rodata)
-    *(.rodata*)
-    KEEP (*(.init))
-    KEEP (*(.fini))
-    . = ALIGN(4);
-    _etext = .;
-  } >RO
-
-  .data : AT (_etext) {
-    . = ALIGN(4);
-    _sdat = .;
-    *(.data)
-    *(.data*)
-    . = ALIGN(4);
-    _edat = .;
-    _ldat = LOADADDR(.data);
-  } >RW
-
-  .bss : {
-    . = ALIGN(8);
-    _irq_stackbottom = .;
-    . = . + 512;
-    _irq_stacktop = .;
-    _thread_stackbottom = .;
-    . = . + 1024;
-    _thread_stacktop = .;
-    _sbss = .;
-    *(.bss)
-    *(.bss*)
-    . = ALIGN(4);
-    _ebss = .;
-  } >RW
-
-  /DISCARD/ : {
-    *(.eh_frame)
-  }
-
-  .ARM.attributes 0 : { *(.ARM.attributes) }
-}
+#include "../scripts/stm32f1.ld.S"

+ 1 - 4
src/Makefile

@@ -1,8 +1,5 @@
-OBJS += arena.o
 OBJS += board.o
 OBJS += build_info.o
-OBJS += cancellation.o
-OBJS += crc.o
 OBJS += vectors.o
 OBJS += main.o
 OBJS += string.o
@@ -17,4 +14,4 @@ OBJS-$(debug) += console.o
 SUBDIRS += usb
 
 .PHONY: build_info.c
-build_info.o: CFLAGS += -DFW_VER="\"$(FW_VER)\""
+build_info.o: CFLAGS += -DFW_MAJOR=$(FW_MAJOR) -DFW_MINOR=$(FW_MINOR)

+ 0 - 51
src/arena.c

@@ -1,51 +0,0 @@
-/*
- * arena.c
- * 
- * Arena-based memory allocation. Only one arena, for now.
- * 
- * Written & released by Keir Fraser <keir.xen@gmail.com>
- * 
- * This is free and unencumbered software released into the public domain.
- * See the file COPYING for more details, or visit <http://unlicense.org>.
- */
-
-#define ram_kb 20
-#define ram_bytes (ram_kb*1024)
-
-#define heap_bot (_ebss)
-#define heap_top ((char *)0x20000000 + ram_bytes)
-
-static char *heap_p;
-
-void *arena_alloc(uint32_t sz)
-{
-    void *p = heap_p;
-    heap_p += (sz + 3) & ~3;
-    ASSERT(heap_p <= heap_top);
-    return p;
-}
-
-uint32_t arena_total(void)
-{
-    return heap_top - heap_bot;
-}
-
-uint32_t arena_avail(void)
-{
-    return heap_top - heap_p;
-}
-
-void arena_init(void)
-{
-    heap_p = heap_bot;
-}
-
-/*
- * Local variables:
- * mode: C
- * c-file-style: "Linux"
- * c-basic-offset: 4
- * tab-width: 4
- * indent-tabs-mode: nil
- * End:
- */

+ 2 - 1
src/build_info.c

@@ -7,7 +7,8 @@
  * See the file COPYING for more details, or visit <http://unlicense.org>.
  */
 
-const char fw_ver[] = FW_VER;
+const uint8_t fw_major = FW_MAJOR;
+const uint8_t fw_minor = FW_MINOR;
 
 /*
  * Local variables:

+ 0 - 86
src/cancellation.c

@@ -1,86 +0,0 @@
-/*
- * cancellation.c
- * 
- * Asynchronously-cancellable function calls.
- * 
- * Written & released by Keir Fraser <keir.xen@gmail.com>
- * 
- * This is free and unencumbered software released into the public domain.
- * See the file COPYING for more details, or visit <http://unlicense.org>.
- */
-
-asm (
-    ".global call_cancellable_fn\n"
-    ".thumb_func \n"
-    "call_cancellable_fn:\n"
-    "    stmdb.w sp!, {r0, r4, r5, r6, r7, r8, r9, r10, r11, lr}\n"
-    "    str     sp, [r0]\n" /* c->sp = PSP */
-    "    mov     r0, r2\n"   /* r0 = arg */
-    "    blx     r1\n"       /* (*fn)(arg) */
-    "    ldr     r2, [sp]\n"
-    "    movs    r1, #0\n"
-    "    str     r1, [r2]\n" /* c->sp = NULL */
-    "do_cancel:\n"
-    "    add     sp, #4\n"
-    "    ldmia.w sp!, {r4, r5, r6, r7, r8, r9, r10, r11, pc}\n"
-    );
-
-void do_cancel(void);
-
-/* An exception context for cancel_call(), when initially called from Thread 
- * context. */
-void EXC_sv_call(void) __attribute__((alias("EXC_do_cancel")));
-static struct cancellation *exc_cancel;
-static void EXC_do_cancel(void)
-{
-    cancel_call(exc_cancel);
-    exc_cancel = NULL;
-}
-
-void cancel_call(struct cancellation *c)
-{
-    struct exception_frame *frame;
-    uint32_t *new_frame;
-
-    /* Bail if the cancellable context is inactive/cancelled. */
-    if (c->sp == NULL)
-        return;
-
-    /* Switch to exception context if we are not there already. */
-    if (!in_exception()) {
-        exc_cancel = c;
-        sv_call(0);
-        ASSERT(0); /* unreachable */
-    }
-
-    /* Modify return frame: Jump to exit of call_cancellable_fn() with
-     * return code -1 and clean xPSR. */
-    frame = (struct exception_frame *)read_special(psp);
-    frame->r0 = -1;
-    frame->pc = (uint32_t)do_cancel;
-    frame->psr &= 1u<<24; /* Preserve Thumb mode; clear everything else */
-
-    /* Find new frame address, set STKALIGN if misaligned. */
-    new_frame = c->sp - 8;
-    if ((uint32_t)new_frame & 4) {
-        new_frame--;
-        frame->psr |= 1u<<9;
-    }
-
-    /* Copy the stack frame and update Process SP. */
-    memmove(new_frame, frame, 32);
-    write_special(psp, new_frame);
-
-    /* Do this work at most once per invocation of call_cancellable_fn. */
-    c->sp = NULL;
-}
-
-/*
- * Local variables:
- * mode: C
- * c-file-style: "Linux"
- * c-basic-offset: 4
- * tab-width: 4
- * indent-tabs-mode: nil
- * End:
- */

+ 10 - 44
src/floppy.c

@@ -100,10 +100,6 @@ static enum {
     ST_write_flux_drain,
 } floppy_state = ST_inactive;
 
-/* USB Endpoints for CDC ACM communications. */
-#define EP_RX 2
-#define EP_TX 3
-
 static uint8_t u_buf[8192];
 static uint32_t u_cons, u_prod;
 #define U_MASK(x) ((x)&(sizeof(u_buf)-1))
@@ -226,7 +222,7 @@ static void floppy_flux_end(void)
     gpio_configure_pin(gpio_data, pin_wdata, GPO_bus);    
 }
 
-void floppy_reset(void)
+static void floppy_reset(void)
 {
     unsigned int i;
 
@@ -311,44 +307,7 @@ void floppy_init(void)
     dma_wdata.cmar = (uint32_t)(unsigned long)dma.buf;
 }
 
-
-/* CMD_GET_INFO, length=3, 0. Returns 32 bytes after ACK. */
-#define CMD_GET_INFO        0
-/* CMD_SEEK, length=3, cyl# */
-#define CMD_SEEK            1
-/* CMD_SIDE, length=3, side# (0=bottom) */
-#define CMD_SIDE            2
-/* CMD_SET_DELAYS, length=2+4*2, <delay_params> */
-#define CMD_SET_DELAYS      3
-/* CMD_GET_DELAYS, length=2. Returns 4*2 bytes after ACK. */
-#define CMD_GET_DELAYS      4
-/* CMD_MOTOR, length=3, motor_state */
-#define CMD_MOTOR           5
-/* CMD_READ_FLUX, length=3, #revs. Returns flux readings until EOStream. */
-#define CMD_READ_FLUX       6
-/* CMD_WRITE_FLUX, length=2. Host follows with flux readings until EOStream. */
-#define CMD_WRITE_FLUX      7
-/* CMD_GET_FLUX_STATUS, length=2. Last read/write status returned in ACK. */
-#define CMD_GET_FLUX_STATUS 8
-/* CMD_GET_READ_INFO, length=2. Returns 7*8 bytes after ACK. */
-#define CMD_GET_READ_INFO   9
-
-#define ACK_OKAY            0
-#define ACK_BAD_COMMAND     1
-#define ACK_NO_INDEX        2
-#define ACK_NO_TRK0         3
-#define ACK_FLUX_OVERFLOW   4
-#define ACK_FLUX_UNDERFLOW  5
-#define ACK_WRPROT          6
-
-const static struct __packed gw_info {
-    uint8_t fw_major;
-    uint8_t fw_minor;
-    uint8_t max_rev;
-    uint8_t max_cmd;
-    uint32_t sample_freq;
-} gw_info = {
-    .fw_major = 0, .fw_minor = 1,
+static struct gw_info gw_info = {
     .max_rev = 7, .max_cmd = CMD_GET_READ_INFO,
     .sample_freq = SYSCLK_MHZ * 1000000u
 };
@@ -805,6 +764,8 @@ static void process_command(void)
         if (len != 3) goto bad_command;
         if (idx != 0) goto bad_command;
         memset(&u_buf[2], 0, 32);
+        gw_info.fw_major = fw_major;
+        gw_info.fw_minor = fw_minor;
         memcpy(&u_buf[2], &gw_info, sizeof(gw_info));
         resp_sz += 32;
         break;
@@ -878,7 +839,7 @@ bad_command:
     goto out;
 }
 
-void floppy_configured(void)
+static void floppy_configured(void)
 {
     floppy_state = ST_command_wait;
     u_cons = u_prod = 0;
@@ -952,6 +913,11 @@ void floppy_process(void)
     }
 }
 
+const struct usb_class_ops usb_cdc_acm_ops = {
+    .reset = floppy_reset,
+    .configure = floppy_configured
+};
+
 /*
  * INTERRUPT HANDLERS
  */

+ 66 - 0
src/fpec.c

@@ -0,0 +1,66 @@
+/*
+ * fpec.c
+ * 
+ * STM32F10x Flash Memory Program/Erase Controller (FPEC).
+ * 
+ * Written & released by Keir Fraser <keir.xen@gmail.com>
+ * 
+ * This is free and unencumbered software released into the public domain.
+ * See the file COPYING for more details, or visit <http://unlicense.org>.
+ */
+
+static void fpec_wait_and_clear(void)
+{
+    while (flash->sr & FLASH_SR_BSY)
+        continue;
+    flash->sr = FLASH_SR_EOP | FLASH_SR_WRPRTERR | FLASH_SR_PGERR;
+    flash->cr = 0;
+}
+
+void fpec_init(void)
+{
+    /* Erases and writes require the HSI oscillator. */
+    rcc->cr |= RCC_CR_HSION;
+    while (!(rcc->cr & RCC_CR_HSIRDY))
+        cpu_relax();
+
+    /* Unlock the FPEC. */
+    if (flash->cr & FLASH_CR_LOCK) {
+        flash->keyr = 0x45670123;
+        flash->keyr = 0xcdef89ab;
+    }
+
+    fpec_wait_and_clear();
+}
+
+void fpec_page_erase(uint32_t flash_address)
+{
+    fpec_wait_and_clear();
+    flash->cr |= FLASH_CR_PER;
+    flash->ar = flash_address;
+    flash->cr |= FLASH_CR_STRT;
+    fpec_wait_and_clear();
+}
+
+void fpec_write(const void *data, unsigned int size, uint32_t flash_address)
+{
+    uint16_t *_f = (uint16_t *)flash_address;
+    const uint16_t *_d = data;
+
+    fpec_wait_and_clear();
+    for (; size != 0; size -= 2) {
+        flash->cr |= FLASH_CR_PG;
+        *_f++ = *_d++; 
+        fpec_wait_and_clear();
+   }
+}
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "Linux"
+ * c-basic-offset: 4
+ * tab-width: 4
+ * indent-tabs-mode: nil
+ * End:
+ */

+ 249 - 0
src/fw_update.c

@@ -0,0 +1,249 @@
+/*
+ * fw_update.c
+ * 
+ * Update bootloader for main firmware.
+ * 
+ * Written & released by Keir Fraser <keir.xen@gmail.com>
+ * 
+ * This is free and unencumbered software released into the public domain.
+ * See the file COPYING for more details, or visit <http://unlicense.org>.
+ */
+
+/* Main bootloader: flashes the main firmware (last 96kB of Flash). */
+#define FIRMWARE_START 0x08002000
+#define FIRMWARE_END   0x08010000
+
+int EXC_reset(void) __attribute__((alias("main")));
+
+static void canary_init(void)
+{
+    _irq_stackbottom[0] = _thread_stackbottom[0] = 0xdeadbeef;
+}
+
+static void canary_check(void)
+{
+    ASSERT(_irq_stackbottom[0] == 0xdeadbeef);
+    ASSERT(_thread_stackbottom[0] == 0xdeadbeef);
+}
+
+static void erase_old_firmware(void)
+{
+    uint32_t p;
+    for (p = FIRMWARE_START; p < FIRMWARE_END; p += FLASH_PAGE_SIZE)
+        fpec_page_erase(p);
+}
+
+static enum {
+    ST_inactive,
+    ST_command_wait,
+    ST_update,
+} state = ST_inactive;
+
+static uint8_t u_buf[256];
+static uint32_t u_prod;
+
+static struct gw_info gw_info = {
+    /* Max Revs == 0 signals that this is the Bootloader. */
+    .max_rev = 0,
+    /* Only support two commands: GET_INFO and UPDATE. */
+    .max_cmd = CMD_UPDATE,
+};
+
+static void update_reset(void)
+{
+    state = ST_inactive;
+}
+
+static void update_configure(void)
+{
+    state = ST_command_wait;
+    u_prod = 0;
+}
+
+const struct usb_class_ops usb_cdc_acm_ops = {
+    .reset = update_reset,
+    .configure = update_configure
+};
+
+static void end_command(void *ack, unsigned int ack_len)
+{
+    usb_write(EP_TX, ack, ack_len);
+    u_prod = 0;
+}
+
+static struct {
+    uint32_t len;
+    uint32_t cur;
+} update;
+
+static void update_prep(uint32_t len)
+{
+    fpec_init();
+    erase_old_firmware();
+
+    state = ST_update;
+    memset(&update, 0, sizeof(update));
+    update.len = len;
+
+    printk("Update: %u bytes\n", len);
+}
+
+static void update_continue(void)
+{
+    int len;
+
+    if ((len = ep_rx_ready(EP_RX)) >= 0) {
+        usb_read(EP_RX, &u_buf[u_prod], len);
+        u_prod += len;
+    }
+
+    if ((len = u_prod) >= 2) {
+        int nr = len & ~1;
+        fpec_write(u_buf, nr, FIRMWARE_START + update.cur);
+        update.cur += nr;
+        u_prod -= nr;
+        memcpy(u_buf, &u_buf[nr], u_prod);
+    }
+
+    if (update.cur >= update.len) {
+        uint16_t crc = crc16_ccitt((void *)FIRMWARE_START, update.len, 0xffff);
+        printk("Final CRC: %04x (%s)\n", crc, crc ? "FAIL" : "OK");
+        u_buf[0] = !!crc;
+        state = ST_command_wait;
+        end_command(u_buf, 1);
+    }
+}
+
+static void process_command(void)
+{
+    uint8_t cmd = u_buf[0];
+    uint8_t len = u_buf[1];
+    uint8_t resp_sz = 2;
+
+    switch (cmd) {
+    case CMD_GET_INFO: {
+        uint8_t idx = u_buf[2];
+        if (len != 3) goto bad_command;
+        if (idx != 0) goto bad_command;
+        memset(&u_buf[2], 0, 32);
+        gw_info.fw_major = fw_major;
+        gw_info.fw_minor = fw_minor;
+        /* sample_freq is used as flags: bit 0 indicates if we entered 
+         * the bootloader because PA14 is strapped to GND. */
+        gw_info.sample_freq = !gpio_read_pin(gpioa, 14);
+        memcpy(&u_buf[2], &gw_info, sizeof(gw_info));
+        resp_sz += 32;
+        break;
+    }
+    case CMD_UPDATE: {
+        uint32_t u_len = *(uint32_t *)&u_buf[2];
+        if (len != 6) goto bad_command;
+        if (u_len & 3) goto bad_command;
+        update_prep(u_len);
+        break;
+    }
+    default:
+        goto bad_command;
+    }
+
+    u_buf[1] = ACK_OKAY;
+out:
+    end_command(u_buf, resp_sz);
+    return;
+
+bad_command:
+    u_buf[1] = ACK_BAD_COMMAND;
+    goto out;
+}
+
+static void update_process(void)
+{
+    int len;
+
+    switch (state) {
+
+    case ST_command_wait:
+
+        len = ep_rx_ready(EP_RX);
+        if ((len >= 0) && (len < (sizeof(u_buf)-u_prod))) {
+            usb_read(EP_RX, &u_buf[u_prod], len);
+            u_prod += len;
+        }
+
+        if ((u_prod >= 2) && (u_prod >= u_buf[1]) && ep_tx_ready(EP_TX)) {
+            process_command();
+        }
+
+        break;
+
+    case ST_update:
+        update_continue();
+        break;
+
+    default:
+        break;
+
+    }
+}
+
+int main(void)
+{
+    /* Relocate DATA. Initialise BSS. */
+    if (_sdat != _ldat)
+        memcpy(_sdat, _ldat, _edat-_sdat);
+    memset(_sbss, 0, _ebss-_sbss);
+
+    /* Turn off serial-wire JTAG and reclaim the GPIOs. */
+    afio->mapr = AFIO_MAPR_SWJ_CFG_DISABLED;
+
+    /* Enable GPIOA, set all pins as floating, except PA14 = weak pull-up. */
+    rcc->apb2enr = RCC_APB2ENR_IOPAEN;
+    gpioa->odr = 0xffffu;
+    gpioa->crh = 0x48444444u;
+    gpioc->crl = 0x44444444u;
+
+    /* Enter update mode only if PA14 (DCLK) is strapped to GND. */
+    if (gpio_read_pin(gpioa, 14)) {
+        /* Nope, so jump straight at the main firmware. */
+        uint32_t sp = *(uint32_t *)FIRMWARE_START;
+        uint32_t pc = *(uint32_t *)(FIRMWARE_START + 4);
+        if (sp != ~0u) { /* only if firmware is apparently not erased */
+            asm volatile (
+                "mov sp,%0 ; blx %1"
+                :: "r" (sp), "r" (pc));
+        }
+    }
+
+    canary_init();
+    stm32_init();
+    console_init();
+    console_crash_on_input();
+    board_init();
+    delay_ms(200); /* 5v settle */
+
+    printk("\n** Greaseweazle Update Bootloader v%u.%u\n", fw_major, fw_minor);
+    printk("** Keir Fraser <keir.xen@gmail.com>\n");
+    printk("** https://github.com/keirf/Greaseweazle\n\n");
+
+    gpio_configure_pin(gpioa, 14, GPI_pull_up);
+
+    usb_init();
+
+    for (;;) {
+        canary_check();
+        usb_process();
+        update_process();
+    }
+
+    return 0;
+}
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "Linux"
+ * c-basic-offset: 4
+ * tab-width: 4
+ * indent-tabs-mode: nil
+ * End:
+ */

+ 1 - 1
src/main.c

@@ -37,7 +37,7 @@ int main(void)
     board_init();
     delay_ms(200); /* 5v settle */
 
-    printk("\n** Greaseweazle v%s\n", fw_ver);
+    printk("\n** Greaseweazle v%u.%u\n", fw_major, fw_minor);
     printk("** Keir Fraser <keir.xen@gmail.com>\n");
     printk("** https://github.com/keirf/Greaseweazle\n\n");
 

+ 9 - 3
src/usb/cdc_acm.c

@@ -76,8 +76,8 @@ bool_t cdc_acm_handle_class_request(void)
     case CDC_SEND_BREAK:
         /* wValue = #millisecs. We ignore it and return success. */
         TRC("BREAK\n");
-        floppy_reset();
-        floppy_configured();
+        usb_cdc_acm_ops.reset();
+        usb_cdc_acm_ops.configure();
         break;
 
     default:
@@ -94,6 +94,12 @@ bool_t cdc_acm_set_configuration(void)
 {
     uint8_t bulk_type = USB_EP_TYPE_BULK_DBLBUF;
 
+#ifdef BOOTLOADER
+    /* We don't bother with the complicated double-buffered endpoints. The 
+     * regular bulk endpoints are fast enough and possibly more reliable. */
+    bulk_type = USB_EP_TYPE_BULK;
+#endif
+
     /* Notification Element (D->H) */
     usb_configure_ep(0x81, USB_EP_TYPE_INTERRUPT, 0);
     /* Bulk Pipe (H->D) */
@@ -101,7 +107,7 @@ bool_t cdc_acm_set_configuration(void)
     /* Bulk Pipe (D->H) */
     usb_configure_ep(0x83, bulk_type, USB_FS_MPS);
 
-    floppy_configured();
+    usb_cdc_acm_ops.configure();
 
     return TRUE;
 }

+ 2 - 2
src/usb/hw_f1.c

@@ -259,8 +259,8 @@ void usb_configure_ep(uint8_t ep, uint8_t type, uint32_t size)
 
 static void handle_reset(void)
 {
-    /* Reinitialise floppy subsystem. */
-    floppy_reset();
+    /* Reinitialise class-specific subsystem. */
+    usb_cdc_acm_ops.reset();
 
     /* Clear endpoint soft state. */
     memset(eps, 0, sizeof(eps));

+ 0 - 92
src/util.c

@@ -9,21 +9,6 @@
  * See the file COPYING for more details, or visit <http://unlicense.org>.
  */
 
-void filename_extension(const char *filename, char *extension, size_t size)
-{
-    const char *p;
-    unsigned int i;
-
-    extension[0] = '\0';
-    if ((p = strrchr(filename, '.')) == NULL)
-        return;
-
-    for (i = 0; i < (size-1); i++)
-        if ((extension[i] = tolower(p[i+1])) == '\0')
-            break;
-    extension[i] = '\0';
-}
-
 void *memset(void *s, int c, size_t n)
 {
     char *p = s;
@@ -148,17 +133,6 @@ int strncmp(const char *s1, const char *s2, size_t n)
     return 0;
 }
 
-char *strrchr(const char *s, int c)
-{
-    char *p = NULL;
-    int d;
-    while ((d = *s)) {
-        if (d == c) p = (char *)s;
-        s++;
-    }
-    return p;
-}
-
 char *strcpy(char *dest, const char *src)
 {
     char *p = dest;
@@ -168,72 +142,6 @@ char *strcpy(char *dest, const char *src)
     return dest;
 }
 
-int tolower(int c)
-{
-    if ((c >= 'A') && (c <= 'Z'))
-        c += 'a' - 'A';
-    return c;
-}
-
-int isspace(int c)
-{
-    return (c == ' ') || (c == '\t') || (c == '\n') || (c == '\r')
-        || (c == '\f') || (c == '\v');
-}
-
-long int strtol(const char *nptr, char **endptr, int base)
-{
-    long int val = 0;
-    const char *p = nptr;
-    bool_t is_neg = FALSE;
-    int c;
-
-    /* Optional whitespace prefix. */
-    while (isspace(*p))
-        p++;
-    c = tolower(*p);
-
-    /* Optional sign prefix: +, -. */
-    if ((c == '+') || (c == '-')) {
-        is_neg = (c == '-');
-        c = tolower(*++p);
-    }
-
-    /* Optional base prefix: 0, 0x. */
-    if (c == '0') {
-        if (base == 0)
-            base = 8;
-        c = tolower(*++p);
-        if (c == 'x') {
-            if (base == 0)
-                base = 16;
-            if (base != 16)
-                goto out;
-            c = tolower(*++p);
-        }
-    }
-
-    /* Digits. */
-    for (;;) {
-        /* Convert c to a digit [0123456789abcdefghijklmnopqrstuvwxyz]. */
-        if ((c >= '0') && (c <= '9'))
-            c -= '0';
-        else if ((c >= 'a') && (c <= 'z'))
-            c -= 'a' - 10;
-        else
-            break;
-        if (c >= base)
-            break;
-        val = (val * base) + c;
-        c = tolower(*++p);
-    }
-
-out:
-    if (endptr)
-        *endptr = (char *)p;
-    return is_neg ? -val : val;
-}
-
 /*
  * Local variables:
  * mode: C