diff options
51 files changed, 4905 insertions, 0 deletions
diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..3421536 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,12 @@ +[submodule "lib/cmsis_device"] + path = lib/cmsis_device + url = https://github.com/STMicroelectronics/cmsis_device_f3 +[submodule "lib/cmsis_core"] + path = lib/cmsis_core + url = https://github.com/STMicroelectronics/cmsis_core +[submodule "lib/hal_driver"] + path = lib/hal_driver + url = https://github.com/STMicroelectronics/stm32f3xx_hal_driver +[submodule "lib/musl"] + path = lib/musl + url = git://git.musl-libc.org/musl diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d51d2c1 --- /dev/null +++ b/Makefile @@ -0,0 +1,160 @@ + +######################################################################################################################## +# Dependency directories +######################################################################################################################## + +CMSIS_DEVICE_DIR ?= lib/cmsis_device +CMSIS_CORE_DIR ?= lib/cmsis_core +HAL_DIR ?= lib/hal_driver +MUSL_DIR ?= lib/musl + +######################################################################################################################## +# Sources +######################################################################################################################## + +C_SOURCES := src/main.c +C_SOURCES += src/cobs.c +C_SOURCES += src/clocks.c +C_SOURCES += src/bootloader.c +C_SOURCES += src/interrupts.c +C_SOURCES += src/system_stm32f3xx.c +C_SOURCES += config/fe_config.c + +MUSL_SOURCES += string/strlen.c + +C_SOURCES += $(addprefix $(MUSL_DIR)/src/,$(MUSL_SOURCES)) + +CXX_SOURCES += + +BUILDDIR ?= build +BINARY := fenris_f302r8tx.elf +LDSCRIPT := stm32f302r8tx.ld + +######################################################################################################################## +# Build parameters +######################################################################################################################## + +PREFIX ?= arm-none-eabi- + +DEBUG ?= 1 + +CC := $(PREFIX)gcc +CXX := $(PREFIX)g++ +LD := $(PREFIX)gcc +AR := $(PREFIX)ar +AS := $(PREFIX)as +SIZE := $(PREFIX)size +NM := $(PREFIX)nm +OBJCOPY := $(PREFIX)objcopy +OBJDUMP := $(PREFIX)objdump +GDB := $(PREFIX)gdb + +HOST_CC ?= $(HOST_PREFIX)gcc +HOST_CXX ?= $(HOST_PREFIX)g++ +HOST_LD ?= $(HOST_PREFIX)gcc +HOST_AR ?= $(HOST_PREFIX)ar +HOST_AS ?= $(HOST_PREFIX)as +HOST_OBJCOPY ?= $(HOST_PREFIX)objcopy +HOST_OBJDUMP ?= $(HOST_PREFIX)objdump + +PYTHON3 ?= python3 +DOT ?= dot + +CMSIS_DIR_ABS := $(abspath $(CMSIS_DIR)) + +ARCH_FLAGS ?= -mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 +SYSTEM_FLAGS ?= -nostdlib -ffreestanding -nostartfiles + +CFLAGS += -Iinclude -Iconfig +CFLAGS += -I$(BUILDDIR) +CFLAGS += -I$(CMSIS_DEVICE_DIR)/Include -I$(CMSIS_CORE_DIR)/Include +CFLAGS += -I$(abspath include/musl_include_shims) + +CFLAGS += -Os -std=gnu11 -g -DSTM32F302x8 -DSTM32F3 -DDEBUG=$(DEBUG) +CFLAGS += $(ARCH_FLAGS) $(SYSTEM_FLAGS) +CFLAGS += -fno-common -ffunction-sections -fdata-sections + +# for musl +CFLAGS += -Dhidden= + +CXXFLAGS += -Os -g +CXXFLAGS += $(ARCH_FLAGS) $(SYSTEM_FLAGS) +CXXFLAGS += -fno-common -ffunction-sections -fdata-sections +CXXFLAGS += -Wall -Wextra -Wshadow -Wundef -Wredundant-decls +CXXFLAGS += -I. + +LDFLAGS += $(ARCH_FLAGS) $(SYSTEM_FLAGS) + +LIBS += -lgcc +LDFLAGS += -Wl,--gc-sections + +LINKMEM_FLAGS ?= --trim-stubs=startup_stm32f302x8.o --trace-sections .isr_vector --highlight-subdirs $(BUILDDIR) + +OBJS := $(addprefix $(BUILDDIR)/,$(C_SOURCES:.c=.o) $(CXX_SOURCES:.cpp=.o)) + +ALL_OBJS := $(OBJS) +ALL_OBJS += $(BUILDDIR)/startup_stm32f302x8.o + +######################################################################################################################## +# Rules +######################################################################################################################## + +all: binsize + +.PHONY: binsize +binsize: $(BUILDDIR)/$(BINARY) $(BUILDDIR)/$(BINARY:.elf=-symbol-sizes.pdf) + $(LD) -T$(LDSCRIPT) $(LDFLAGS) -Wl,--print-memory-usage -o /dev/null $(ALL_OBJS) $(LIBS) + @echo + @echo "▐▬▬▬▌ SyMbOL sIzE HiGhScORe LiSt ▐▬▬▬▌" + $(NM) --print-size --size-sort --radix=d $< | tail -n 20 + +$(BUILDDIR)/generated: ; mkdir -p $@ + +.PRECIOUS: $(BUILDDIR)/$(BINARY) +$(BUILDDIR)/$(BINARY) $(BUILDDIR)/$(BINARY:.elf=.map) &: $(ALL_OBJS) + $(LD) -T$(LDSCRIPT) $(LDFLAGS) -o $@ -Wl,-Map=$(BUILDDIR)/$(BINARY:.elf=.map) $^ $(LIBS) + +build/$(BINARY:.elf=-symbol-sizes.dot): $(ALL_OBJS) + $(PYTHON3) tools/linkmem.py $(LINKMEM_FLAGS) $(LD) -T$(LDSCRIPT) $(LDFLAGS) $^ $(LIBS) > $@ + +%.pdf: %.dot + $(DOT) -T pdf $< -o $@ + +%.dot: %.elf + r2 -a arm -qc 'aa;agRd' $< 2>/dev/null >$@ + +$(BUILDDIR)/%.o: %.s + mkdir -p $(@D) + $(CC) $(COMMON_CFLAGS) $(CFLAGS) $(INT_CFLAGS) -o $@ -c $< + +$(BUILDDIR)/src/%.o: src/%.c + mkdir -p $(@D) + $(CC) $(COMMON_CFLAGS) $(CFLAGS) $(INT_CFLAGS) -o $@ -c $< + +$(BUILDDIR)/src/%.o: src/%.cpp + mkdir -p $(@D) + $(CXX) $(CXXFLAGS) -o $@ -c $< + +$(BUILDDIR)/generated/%.o: $(BUILDDIR)/generated/%.c + mkdir -p $(@D) + $(CC) $(COMMON_CFLAGS) $(CFLAGS) $(INT_CFLAGS) -o $@ -c $< + +$(BUILDDIR)/%.o: %.c + mkdir -p $(@D) + $(CC) $(COMMON_CFLAGS) $(CFLAGS) $(EXT_CFLAGS) -o $@ -c $< + +clean: + rm -rf $(BUILDDIR)/src + rm -rf $(BUILDDIR)/generated + rm -f $(BUILDDIR)/$(BINARY) + rm -f $(BUILDDIR)/$(BINARY:.elf=.map) + rm -f $(BUILDDIR)/$(BINARY:.elf=-symbol-sizes.dot) + rm -f $(BUILDDIR)/$(BINARY:.elf=-symbol-sizes.pdf) + rm -f $(BUILDDIR)/tools/freq_meas_test + +mrproper: clean + rm -rf build + +.PHONY: clean mrproper + +-include $(OBJS:.o=.d) diff --git a/config/fe_config.c b/config/fe_config.c new file mode 100644 index 0000000..f486cf0 --- /dev/null +++ b/config/fe_config.c @@ -0,0 +1,17 @@ +#ifndef __FE_CONFIG_H__ +#define __FE_CONFIG_H__ + +#include <fe_config_backend.h> + +const struct fe_config_def fe_config = { + .bootloader_enable_pin = { FE_CONFIG_GPIOB, 0 }, + .bootloader_enable_level = 1, + .signature_disable_pin = { FE_CONFIG_GPIOB, 1 }, + .signature_disable_level = 1, + .usart = FE_CONFIG_USART1_PB7, + .baudrate = 115200, + /* The following string happens to be a valid COBS frame. */ + .welcome_string = "Fenris Bootloader\r\n(c) 2020 jaseg\r\nSee https://git.jaseg.de/fenris \r\n", +}; + +#endif /* __FE_CONFIG_H__ */ diff --git a/include/bootloader.h b/include/bootloader.h new file mode 100644 index 0000000..c1b9820 --- /dev/null +++ b/include/bootloader.h @@ -0,0 +1,22 @@ +#ifndef __FE_BOOTLOADER_H__ +#define __FE_BOOTLOADER_H__ + +#include <fe_global.h> + +void fe_jump_to_application(void) __attribute__ ((noreturn)); +void fe_system_reset(void) __attribute__ ((noreturn)); +bool fe_check_img_valid(void); + +void flash_unlock(void); +void flash_lock(void); +int flash_erase_page(size_t addr); +int flash_write(size_t addr, char *buf, size_t len); +int erase_user_flash(void); + +#define PAGE_SIZE 0x2000 + +extern size_t flash_base; +extern size_t flash_size; +extern size_t bootloader_size; + +#endif /* __FE_BOOTLOADER_H__ */ diff --git a/include/cobs.h b/include/cobs.h new file mode 100644 index 0000000..8c4f429 --- /dev/null +++ b/include/cobs.h @@ -0,0 +1,23 @@ +#ifndef __COBS_H__ +#define __COBS_H__ + +#include <stdint.h> +#include <unistd.h> +#include <string.h> + + +struct cobs_decode_state { + size_t p; + size_t c; +}; + + +ssize_t cobs_encode(char *dst, size_t dstlen, char *src, size_t srclen); +ssize_t cobs_decode(char *dst, size_t dstlen, char *src, size_t srclen); + +int cobs_encode_usart(int (*output)(void*, char), void *userdata, char *src, size_t srclen); + +void cobs_decode_incremental_initialize(struct cobs_decode_state *state); +int cobs_decode_incremental(struct cobs_decode_state *state, char *dst, size_t dstlen, char src); + +#endif//__COBS_H__ diff --git a/include/fe_clocks.h b/include/fe_clocks.h new file mode 100644 index 0000000..681feae --- /dev/null +++ b/include/fe_clocks.h @@ -0,0 +1,14 @@ +#ifndef __FE_CLOCKS_H__ +#define __FE_CLOCKS_H__ + +extern unsigned int sysclk_speed; +extern unsigned int ahb_speed; +extern unsigned int apb1_speed; +extern unsigned int apb2_speed; +extern unsigned int apb1_timer_speed; +extern unsigned int apb2_timer_speed; + +void fe_config_clocks(void); +void delay_ms(int ms); + +#endif /* __FE_CLOCKS_H__ */ diff --git a/include/fe_config_backend.h b/include/fe_config_backend.h new file mode 100644 index 0000000..e0d6690 --- /dev/null +++ b/include/fe_config_backend.h @@ -0,0 +1,40 @@ +#ifndef __FE_CONFIG_BACKEND_H__ +#define __FE_CONFIG_BACKEND_H__ + +#include <fe_global.h> + +#define FE_CONFIG_GPIOA RCC_AHBENR_GPIOAEN, GPIOA +#define FE_CONFIG_GPIOB RCC_AHBENR_GPIOBEN, GPIOB +#define FE_CONFIG_GPIOC RCC_AHBENR_GPIOCEN, GPIOC +#define FE_CONFIG_GPIOD RCC_AHBENR_GPIODEN, GPIOD +#define FE_CONFIG_GPIOF RCC_AHBENR_GPIOFEN, GPIOF + +enum fe_config_usart { + FE_CONFIG_USART1_PB7, + FE_CONFIG_USART_COUNT +}; + +struct fe_config_gpiodef { + uint32_t rcc_ahbenr_flags; + GPIO_TypeDef *gpio; + int pin_number; +}; + +struct fe_config_def { + struct fe_config_gpiodef bootloader_enable_pin; + int bootloader_enable_level; + + struct fe_config_gpiodef signature_disable_pin; + int signature_disable_level; + + enum fe_config_usart usart; + int baudrate; + + const char *welcome_string; +}; + +extern const struct fe_config_def fe_config; + +void gpio_config(GPIO_TypeDef *gpio, int pin, int mode, int speed, int pullups, int alt); + +#endif /* __FE_CONFIG_BACKEND_H__ */ diff --git a/include/fe_global.h b/include/fe_global.h new file mode 100644 index 0000000..78a269f --- /dev/null +++ b/include/fe_global.h @@ -0,0 +1,19 @@ +#ifndef __FE_GLOBAL_H__ +#define __FE_GLOBAL_H__ + +#include <stdint.h> +#include <stdbool.h> +#include <assert.h> +#include <sys/types.h> + +#include <stm32f302x8.h> + +#define UNUSED(x) ((void) x) +#define ARRAY_LENGTH(x) (sizeof(x) / sizeof(x[0])) +#define unused_a __attribute__((unused)) + +extern uint64_t sys_time_millis; + +void __libc_init_array(void); + +#endif /* __FE_GLOBAL_H__ */ diff --git a/include/fe_interrupts.h b/include/fe_interrupts.h new file mode 100644 index 0000000..9a85fe0 --- /dev/null +++ b/include/fe_interrupts.h @@ -0,0 +1,14 @@ +#ifndef __FE_INTERRUPTS_H__ +#define __FE_INTERRUPTS_H__ + +void NMI_Handler(void); +void HardFault_Handler(void); +void MemManage_Handler(void); +void BusFault_Handler(void); +void UsageFault_Handler(void); +void SVC_Handler(void); +void DebugMon_Handler(void); +void PendSV_Handler(void); +void SysTick_Handler(void); + +#endif /* __FE_INTERRUPTS_H__ */ diff --git a/include/musl_include_shims/bits/alltypes.h b/include/musl_include_shims/bits/alltypes.h new file mode 100644 index 0000000..581ca85 --- /dev/null +++ b/include/musl_include_shims/bits/alltypes.h @@ -0,0 +1,23 @@ + +/* shim file for musl */ + +#ifndef __MUSL_SHIM_BITS_ALLTYPES_H__ +#define __MUSL_SHIM_BITS_ALLTYPES_H__ + +#define _REDIR_TIME64 1 +#define _Addr int +#define _Int64 long long +#define _Reg int + +#define __BYTE_ORDER 1234 + +#define __LONG_MAX 0x7fffffffL + +#ifndef __cplusplus +typedef unsigned wchar_t; +#endif + +typedef float float_t; +typedef double double_t; + +#endif /* __MUSL_SHIM_BITS_ALLTYPES_H__ */ diff --git a/include/musl_include_shims/endian.h b/include/musl_include_shims/endian.h new file mode 100644 index 0000000..172c432 --- /dev/null +++ b/include/musl_include_shims/endian.h @@ -0,0 +1,80 @@ +#ifndef _ENDIAN_H +#define _ENDIAN_H + +#include <features.h> + +#define __NEED_uint16_t +#define __NEED_uint32_t +#define __NEED_uint64_t + +#include <bits/alltypes.h> + +#define __PDP_ENDIAN 3412 + +#define BIG_ENDIAN __BIG_ENDIAN +#define LITTLE_ENDIAN __LITTLE_ENDIAN +#define PDP_ENDIAN __PDP_ENDIAN +#define BYTE_ORDER __BYTE_ORDER + +static __inline uint16_t __bswap16(uint16_t __x) +{ + return __x<<8 | __x>>8; +} + +static __inline uint32_t __bswap32(uint32_t __x) +{ + return __x>>24 | __x>>8&0xff00 | __x<<8&0xff0000 | __x<<24; +} + +static __inline uint64_t __bswap64(uint64_t __x) +{ + return __bswap32(__x)+0ULL<<32 | __bswap32(__x>>32); +} + +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define htobe16(x) __bswap16(x) +#define be16toh(x) __bswap16(x) +#define htobe32(x) __bswap32(x) +#define be32toh(x) __bswap32(x) +#define htobe64(x) __bswap64(x) +#define be64toh(x) __bswap64(x) +#define htole16(x) (uint16_t)(x) +#define le16toh(x) (uint16_t)(x) +#define htole32(x) (uint32_t)(x) +#define le32toh(x) (uint32_t)(x) +#define htole64(x) (uint64_t)(x) +#define le64toh(x) (uint64_t)(x) +#else +#define htobe16(x) (uint16_t)(x) +#define be16toh(x) (uint16_t)(x) +#define htobe32(x) (uint32_t)(x) +#define be32toh(x) (uint32_t)(x) +#define htobe64(x) (uint64_t)(x) +#define be64toh(x) (uint64_t)(x) +#define htole16(x) __bswap16(x) +#define le16toh(x) __bswap16(x) +#define htole32(x) __bswap32(x) +#define le32toh(x) __bswap32(x) +#define htole64(x) __bswap64(x) +#define le64toh(x) __bswap64(x) +#endif + +#if defined(_GNU_SOURCE) || defined(_BSD_SOURCE) +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define betoh16(x) __bswap16(x) +#define betoh32(x) __bswap32(x) +#define betoh64(x) __bswap64(x) +#define letoh16(x) (uint16_t)(x) +#define letoh32(x) (uint32_t)(x) +#define letoh64(x) (uint64_t)(x) +#else +#define betoh16(x) (uint16_t)(x) +#define betoh32(x) (uint32_t)(x) +#define betoh64(x) (uint64_t)(x) +#define letoh16(x) __bswap16(x) +#define letoh32(x) __bswap32(x) +#define letoh64(x) __bswap64(x) +#endif +#endif + +#endif diff --git a/include/musl_include_shims/features.h b/include/musl_include_shims/features.h new file mode 100644 index 0000000..85cfb72 --- /dev/null +++ b/include/musl_include_shims/features.h @@ -0,0 +1,40 @@ +#ifndef _FEATURES_H +#define _FEATURES_H + +#if defined(_ALL_SOURCE) && !defined(_GNU_SOURCE) +#define _GNU_SOURCE 1 +#endif + +#if defined(_DEFAULT_SOURCE) && !defined(_BSD_SOURCE) +#define _BSD_SOURCE 1 +#endif + +#if !defined(_POSIX_SOURCE) && !defined(_POSIX_C_SOURCE) \ + && !defined(_XOPEN_SOURCE) && !defined(_GNU_SOURCE) \ + && !defined(_BSD_SOURCE) && !defined(__STRICT_ANSI__) +#define _BSD_SOURCE 1 +#define _XOPEN_SOURCE 700 +#endif + +#if __STDC_VERSION__ >= 199901L +#define __restrict restrict +#elif !defined(__GNUC__) +#define __restrict +#endif + +#if __STDC_VERSION__ >= 199901L || defined(__cplusplus) +#define __inline inline +#elif !defined(__GNUC__) +#define __inline +#endif + +#if __STDC_VERSION__ >= 201112L +#elif defined(__GNUC__) +#define _Noreturn __attribute__((__noreturn__)) +#else +#define _Noreturn +#endif + +#define __REDIR(x,y) __typeof__(x) x __asm__(#y) + +#endif diff --git a/include/musl_include_shims/fp_arch.h b/include/musl_include_shims/fp_arch.h new file mode 100644 index 0000000..f5bab6d --- /dev/null +++ b/include/musl_include_shims/fp_arch.h @@ -0,0 +1,6 @@ +#ifndef __MUSL_SHIM_FP_ARCH_H__ +#define __MUSL_SHIM_FP_ARCH_H__ + +#define hidden + +#endif /* __MUSL_SHIM_FP_ARCH_H__ */ diff --git a/include/musl_include_shims/libm.h b/include/musl_include_shims/libm.h new file mode 100644 index 0000000..d48135d --- /dev/null +++ b/include/musl_include_shims/libm.h @@ -0,0 +1,270 @@ +#ifndef _LIBM_H +#define _LIBM_H + +#include <stdint.h> +#include <float.h> +#include <math.h> +#include <endian.h> +#include "fp_arch.h" + +#if LDBL_MANT_DIG == 53 && LDBL_MAX_EXP == 1024 +#elif LDBL_MANT_DIG == 64 && LDBL_MAX_EXP == 16384 && __BYTE_ORDER == __LITTLE_ENDIAN +union ldshape { + long double f; + struct { + uint64_t m; + uint16_t se; + } i; +}; +#elif LDBL_MANT_DIG == 64 && LDBL_MAX_EXP == 16384 && __BYTE_ORDER == __BIG_ENDIAN +/* This is the m68k variant of 80-bit long double, and this definition only works + * on archs where the alignment requirement of uint64_t is <= 4. */ +union ldshape { + long double f; + struct { + uint16_t se; + uint16_t pad; + uint64_t m; + } i; +}; +#elif LDBL_MANT_DIG == 113 && LDBL_MAX_EXP == 16384 && __BYTE_ORDER == __LITTLE_ENDIAN +union ldshape { + long double f; + struct { + uint64_t lo; + uint32_t mid; + uint16_t top; + uint16_t se; + } i; + struct { + uint64_t lo; + uint64_t hi; + } i2; +}; +#elif LDBL_MANT_DIG == 113 && LDBL_MAX_EXP == 16384 && __BYTE_ORDER == __BIG_ENDIAN +union ldshape { + long double f; + struct { + uint16_t se; + uint16_t top; + uint32_t mid; + uint64_t lo; + } i; + struct { + uint64_t hi; + uint64_t lo; + } i2; +}; +#else +#error Unsupported long double representation +#endif + +/* Support non-nearest rounding mode. */ +#define WANT_ROUNDING 1 +/* Support signaling NaNs. */ +#define WANT_SNAN 0 + +#if WANT_SNAN +#error SNaN is unsupported +#else +#define issignalingf_inline(x) 0 +#define issignaling_inline(x) 0 +#endif + +#ifndef TOINT_INTRINSICS +#define TOINT_INTRINSICS 0 +#endif + +#if TOINT_INTRINSICS +/* Round x to nearest int in all rounding modes, ties have to be rounded + consistently with converttoint so the results match. If the result + would be outside of [-2^31, 2^31-1] then the semantics is unspecified. */ +static double_t roundtoint(double_t); + +/* Convert x to nearest int in all rounding modes, ties have to be rounded + consistently with roundtoint. If the result is not representible in an + int32_t then the semantics is unspecified. */ +static int32_t converttoint(double_t); +#endif + +/* Helps static branch prediction so hot path can be better optimized. */ +#ifdef __GNUC__ +#define predict_true(x) __builtin_expect(!!(x), 1) +#define predict_false(x) __builtin_expect(x, 0) +#else +#define predict_true(x) (x) +#define predict_false(x) (x) +#endif + +/* Evaluate an expression as the specified type. With standard excess + precision handling a type cast or assignment is enough (with + -ffloat-store an assignment is required, in old compilers argument + passing and return statement may not drop excess precision). */ + +static inline float eval_as_float(float x) +{ + float y = x; + return y; +} + +static inline double eval_as_double(double x) +{ + double y = x; + return y; +} + +/* fp_barrier returns its input, but limits code transformations + as if it had a side-effect (e.g. observable io) and returned + an arbitrary value. */ + +#ifndef fp_barrierf +#define fp_barrierf fp_barrierf +static inline float fp_barrierf(float x) +{ + volatile float y = x; + return y; +} +#endif + +#ifndef fp_barrier +#define fp_barrier fp_barrier +static inline double fp_barrier(double x) +{ + volatile double y = x; + return y; +} +#endif + +#ifndef fp_barrierl +#define fp_barrierl fp_barrierl +static inline long double fp_barrierl(long double x) +{ + volatile long double y = x; + return y; +} +#endif + +/* fp_force_eval ensures that the input value is computed when that's + otherwise unused. To prevent the constant folding of the input + expression, an additional fp_barrier may be needed or a compilation + mode that does so (e.g. -frounding-math in gcc). Then it can be + used to evaluate an expression for its fenv side-effects only. */ + +#ifndef fp_force_evalf +#define fp_force_evalf fp_force_evalf +static inline void fp_force_evalf(float x) +{ + volatile float y; + y = x; +} +#endif + +#ifndef fp_force_eval +#define fp_force_eval fp_force_eval +static inline void fp_force_eval(double x) +{ + volatile double y; + y = x; +} +#endif + +#ifndef fp_force_evall +#define fp_force_evall fp_force_evall +static inline void fp_force_evall(long double x) +{ + volatile long double y; + y = x; +} +#endif + +#define FORCE_EVAL(x) do { \ + if (sizeof(x) == sizeof(float)) { \ + fp_force_evalf(x); \ + } else if (sizeof(x) == sizeof(double)) { \ + fp_force_eval(x); \ + } else { \ + fp_force_evall(x); \ + } \ +} while(0) + +#define asuint(f) ((union{float _f; uint32_t _i;}){f})._i +#define asfloat(i) ((union{uint32_t _i; float _f;}){i})._f +#define asuint64(f) ((union{double _f; uint64_t _i;}){f})._i +#define asdouble(i) ((union{uint64_t _i; double _f;}){i})._f + +#define EXTRACT_WORDS(hi,lo,d) \ +do { \ + uint64_t __u = asuint64(d); \ + (hi) = __u >> 32; \ + (lo) = (uint32_t)__u; \ +} while (0) + +#define GET_HIGH_WORD(hi,d) \ +do { \ + (hi) = asuint64(d) >> 32; \ +} while (0) + +#define GET_LOW_WORD(lo,d) \ +do { \ + (lo) = (uint32_t)asuint64(d); \ +} while (0) + +#define INSERT_WORDS(d,hi,lo) \ +do { \ + (d) = asdouble(((uint64_t)(hi)<<32) | (uint32_t)(lo)); \ +} while (0) + +#define SET_HIGH_WORD(d,hi) \ + INSERT_WORDS(d, hi, (uint32_t)asuint64(d)) + +#define SET_LOW_WORD(d,lo) \ + INSERT_WORDS(d, asuint64(d)>>32, lo) + +#define GET_FLOAT_WORD(w,d) \ +do { \ + (w) = asuint(d); \ +} while (0) + +#define SET_FLOAT_WORD(d,w) \ +do { \ + (d) = asfloat(w); \ +} while (0) + +hidden int __rem_pio2_large(double*,double*,int,int,int); + +hidden int __rem_pio2(double,double*); +hidden double __sin(double,double,int); +hidden double __cos(double,double); +hidden double __tan(double,double,int); +hidden double __expo2(double); + +hidden int __rem_pio2f(float,double*); +hidden float __sindf(double); +hidden float __cosdf(double); +hidden float __tandf(double,int); +hidden float __expo2f(float); + +hidden int __rem_pio2l(long double, long double *); +hidden long double __sinl(long double, long double, int); +hidden long double __cosl(long double, long double); +hidden long double __tanl(long double, long double, int); + +hidden long double __polevll(long double, const long double *, int); +hidden long double __p1evll(long double, const long double *, int); + +hidden double __lgamma_r(double, int *); +hidden float __lgammaf_r(float, int *); + +/* error handling functions */ +hidden float __math_xflowf(uint32_t, float); +hidden float __math_uflowf(uint32_t); +hidden float __math_oflowf(uint32_t); +hidden float __math_divzerof(uint32_t); +hidden float __math_invalidf(float); +hidden double __math_xflow(uint32_t, double); +hidden double __math_uflow(uint32_t); +hidden double __math_oflow(uint32_t); +hidden double __math_divzero(uint32_t); +hidden double __math_invalid(double); + +#endif diff --git a/include/stm32_hal_conf.h b/include/stm32_hal_conf.h new file mode 100644 index 0000000..0b5d0c3 --- /dev/null +++ b/include/stm32_hal_conf.h @@ -0,0 +1,337 @@ +/** + ****************************************************************************** + * @file stm32f3xx_hal_conf.h + * @author MCD Application Team + * @brief HAL configuration file. + ****************************************************************************** + * @attention + * + * <h2><center>© Copyright (c) 2016 STMicroelectronics. + * All rights reserved.</center></h2> + * + * This software component is licensed by ST under BSD 3-Clause license, + * the "License"; You may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * opensource.org/licenses/BSD-3-Clause + * + ****************************************************************************** + */ + +/* Define to prevent recursive inclusion -------------------------------------*/ +#ifndef __STM32F3xx_HAL_CONF_H +#define __STM32F3xx_HAL_CONF_H + +#ifdef __cplusplus + extern "C" { +#endif + +/* Exported types ------------------------------------------------------------*/ +/* Exported constants --------------------------------------------------------*/ + +/* ########################## Module Selection ############################## */ +/** + * @brief This is the list of modules to be used in the HAL driver + */ +#define HAL_MODULE_ENABLED +// #define HAL_ADC_MODULE_ENABLED +// #define HAL_CAN_MODULE_ENABLED +/* #define HAL_CAN_LEGACY_MODULE_ENABLED */ +// #define HAL_CEC_MODULE_ENABLED +// #define HAL_COMP_MODULE_ENABLED +// #define HAL_CORTEX_MODULE_ENABLED +// #define HAL_CRC_MODULE_ENABLED +// #define HAL_DAC_MODULE_ENABLED +// #define HAL_DMA_MODULE_ENABLED +#define HAL_FLASH_MODULE_ENABLED +// #define HAL_GPIO_MODULE_ENABLED +// #define HAL_EXTI_MODULE_ENABLED +// #define HAL_HRTIM_MODULE_ENABLED +// #define HAL_I2C_MODULE_ENABLED +// #define HAL_I2S_MODULE_ENABLED +// #define HAL_IRDA_MODULE_ENABLED +// #define HAL_IWDG_MODULE_ENABLED +// #define HAL_OPAMP_MODULE_ENABLED +// #define HAL_PCD_MODULE_ENABLED +// #define HAL_PWR_MODULE_ENABLED +// #define HAL_RCC_MODULE_ENABLED +// #define HAL_RTC_MODULE_ENABLED +// #define HAL_SDADC_MODULE_ENABLED +// #define HAL_SMARTCARD_MODULE_ENABLED +// #define HAL_SMBUS_MODULE_ENABLED +// #define HAL_SPI_MODULE_ENABLED +// #define HAL_TIM_MODULE_ENABLED +// #define HAL_TSC_MODULE_ENABLED +// #define HAL_UART_MODULE_ENABLED +// #define HAL_USART_MODULE_ENABLED +// #define HAL_WWDG_MODULE_ENABLED + +/* ########################## HSE/HSI Values adaptation ##################### */ +/** + * @brief Adjust the value of External High Speed oscillator (HSE) used in your application. + * This value is used by the RCC HAL module to compute the system frequency + * (when HSE is used as system clock source, directly or through the PLL). + */ +#if !defined (HSE_VALUE) + #define HSE_VALUE (8000000U) /*!< Value of the External oscillator in Hz */ +#endif /* HSE_VALUE */ + +/** + * @brief In the following line adjust the External High Speed oscillator (HSE) Startup + * Timeout value + */ +#if !defined (HSE_STARTUP_TIMEOUT) + #define HSE_STARTUP_TIMEOUT (100U) /*!< Time out for HSE start up, in ms */ +#endif /* HSE_STARTUP_TIMEOUT */ + +/** + * @brief Internal High Speed oscillator (HSI) value. + * This value is used by the RCC HAL module to compute the system frequency + * (when HSI is used as system clock source, directly or through the PLL). + */ +#if !defined (HSI_VALUE) + #define HSI_VALUE (8000000U) /*!< Value of the Internal oscillator in Hz*/ +#endif /* HSI_VALUE */ + +/** + * @brief In the following line adjust the Internal High Speed oscillator (HSI) Startup + * Timeout value + */ +#if !defined (HSI_STARTUP_TIMEOUT) + #define HSI_STARTUP_TIMEOUT (5000U) /*!< Time out for HSI start up */ +#endif /* HSI_STARTUP_TIMEOUT */ + +/** + * @brief Internal Low Speed oscillator (LSI) value. + */ +#if !defined (LSI_VALUE) + #define LSI_VALUE (40000U) +#endif /* LSI_VALUE */ /*!< Value of the Internal Low Speed oscillator in Hz + The real value may vary depending on the variations + in voltage and temperature. */ +/** + * @brief External Low Speed oscillator (LSE) value. + */ +#if !defined (LSE_VALUE) + #define LSE_VALUE (32768U) /*!< Value of the External Low Speed oscillator in Hz */ +#endif /* LSE_VALUE */ + +/** + * @brief Time out for LSE start up value in ms. + */ +#if !defined (LSE_STARTUP_TIMEOUT) + #define LSE_STARTUP_TIMEOUT (5000U) /*!< Time out for LSE start up, in ms */ +#endif /* LSE_STARTUP_TIMEOUT */ + +/** + * @brief External clock source for I2S peripheral + * This value is used by the I2S HAL module to compute the I2S clock source + * frequency, this source is inserted directly through I2S_CKIN pad. + * - External clock generated through external PLL component on EVAL 303 (based on MCO or crystal) + * - External clock not generated on EVAL 373 + */ +#if !defined (EXTERNAL_CLOCK_VALUE) + #define EXTERNAL_CLOCK_VALUE (8000000U) /*!< Value of the External oscillator in Hz*/ +#endif /* EXTERNAL_CLOCK_VALUE */ + +/* Tip: To avoid modifying this file each time you need to use different HSE, + === you can define the HSE value in your toolchain compiler preprocessor. */ + +/* ########################### System Configuration ######################### */ +/** + * @brief This is the HAL system configuration section + */ +#define VDD_VALUE (3300U) /*!< Value of VDD in mv */ +#define TICK_INT_PRIORITY ((uint32_t)(1U<<__NVIC_PRIO_BITS) - 1U) /*!< tick interrupt priority (lowest by default) */ +#define USE_RTOS 0U +#define PREFETCH_ENABLE 1U +#define INSTRUCTION_CACHE_ENABLE 0U +#define DATA_CACHE_ENABLE 0U +#define USE_SPI_CRC 1U + +#define USE_HAL_ADC_REGISTER_CALLBACKS 0U /* ADC register callback disabled */ +#define USE_HAL_CAN_REGISTER_CALLBACKS 0U /* CAN register callback disabled */ +#define USE_HAL_COMP_REGISTER_CALLBACKS 0U /* COMP register callback disabled */ +#define USE_HAL_CEC_REGISTER_CALLBACKS 0U /* CEC register callback disabled */ +#define USE_HAL_DAC_REGISTER_CALLBACKS 0U /* DAC register callback disabled */ +#define USE_HAL_SRAM_REGISTER_CALLBACKS 0U /* SRAM register callback disabled */ +#define USE_HAL_SMBUS_REGISTER_CALLBACKS 0U /* SMBUS register callback disabled */ +#define USE_HAL_SDADC_REGISTER_CALLBACKS 0U /* SDADC register callback disabled */ +#define USE_HAL_NAND_REGISTER_CALLBACKS 0U /* NAND register callback disabled */ +#define USE_HAL_NOR_REGISTER_CALLBACKS 0U /* NOR register callback disabled */ +#define USE_HAL_PCCARD_REGISTER_CALLBACKS 0U /* PCCARD register callback disabled */ +#define USE_HAL_HRTIM_REGISTER_CALLBACKS 0U /* HRTIM register callback disabled */ +#define USE_HAL_I2C_REGISTER_CALLBACKS 0U /* I2C register callback disabled */ +#define USE_HAL_UART_REGISTER_CALLBACKS 0U /* UART register callback disabled */ +#define USE_HAL_USART_REGISTER_CALLBACKS 0U /* USART register callback disabled */ +#define USE_HAL_IRDA_REGISTER_CALLBACKS 0U /* IRDA register callback disabled */ +#define USE_HAL_SMARTCARD_REGISTER_CALLBACKS 0U /* SMARTCARD register callback disabled */ +#define USE_HAL_WWDG_REGISTER_CALLBACKS 0U /* WWDG register callback disabled */ +#define USE_HAL_OPAMP_REGISTER_CALLBACKS 0U /* OPAMP register callback disabled */ +#define USE_HAL_RTC_REGISTER_CALLBACKS 0U /* RTC register callback disabled */ +#define USE_HAL_SPI_REGISTER_CALLBACKS 0U /* SPI register callback disabled */ +#define USE_HAL_I2S_REGISTER_CALLBACKS 0U /* I2S register callback disabled */ +#define USE_HAL_TIM_REGISTER_CALLBACKS 0U /* TIM register callback disabled */ +#define USE_HAL_TSC_REGISTER_CALLBACKS 0U /* TSC register callback disabled */ +#define USE_HAL_PCD_REGISTER_CALLBACKS 0U /* PCD register callback disabled */ + +/* ########################## Assert Selection ############################## */ +/** + * @brief Uncomment the line below to expanse the "assert_param" macro in the + * HAL drivers code + */ +/*#define USE_FULL_ASSERT 1*/ + +/* Includes ------------------------------------------------------------------*/ +/** + * @brief Include module's header file + */ + +#ifdef HAL_RCC_MODULE_ENABLED + #include "stm32f3xx_hal_rcc.h" +#endif /* HAL_RCC_MODULE_ENABLED */ + +#ifdef HAL_GPIO_MODULE_ENABLED + #include "stm32f3xx_hal_gpio.h" +#endif /* HAL_GPIO_MODULE_ENABLED */ + +#ifdef HAL_EXTI_MODULE_ENABLED + #include "stm32f3xx_hal_exti.h" +#endif /* HAL_EXTI_MODULE_ENABLED */ + +#ifdef HAL_DMA_MODULE_ENABLED + #include "stm32f3xx_hal_dma.h" +#endif /* HAL_DMA_MODULE_ENABLED */ + +#ifdef HAL_CORTEX_MODULE_ENABLED + #include "stm32f3xx_hal_cortex.h" +#endif /* HAL_CORTEX_MODULE_ENABLED */ + +#ifdef HAL_ADC_MODULE_ENABLED + #include "stm32f3xx_hal_adc.h" +#endif /* HAL_ADC_MODULE_ENABLED */ + +#ifdef HAL_CAN_MODULE_ENABLED + #include "stm32f3xx_hal_can.h" +#endif /* HAL_CAN_MODULE_ENABLED */ + +#ifdef HAL_CAN_LEGACY_MODULE_ENABLED + #include "stm32f3xx_hal_can_legacy.h" +#endif /* HAL_CAN_LEGACY_MODULE_ENABLED */ + +#ifdef HAL_CEC_MODULE_ENABLED + #include "stm32f3xx_hal_cec.h" +#endif /* HAL_CEC_MODULE_ENABLED */ + +#ifdef HAL_COMP_MODULE_ENABLED + #include "stm32f3xx_hal_comp.h" +#endif /* HAL_COMP_MODULE_ENABLED */ + +#ifdef HAL_CRC_MODULE_ENABLED + #include "stm32f3xx_hal_crc.h" +#endif /* HAL_CRC_MODULE_ENABLED */ + +#ifdef HAL_DAC_MODULE_ENABLED + #include "stm32f3xx_hal_dac.h" +#endif /* HAL_DAC_MODULE_ENABLED */ + +#ifdef HAL_FLASH_MODULE_ENABLED + #include "stm32f3xx_hal_flash.h" +#endif /* HAL_FLASH_MODULE_ENABLED */ + +#ifdef HAL_HRTIM_MODULE_ENABLED + #include "stm32f3xx_hal_hrtim.h" +#endif /* HAL_HRTIM_MODULE_ENABLED */ + +#ifdef HAL_I2C_MODULE_ENABLED + #include "stm32f3xx_hal_i2c.h" +#endif /* HAL_I2C_MODULE_ENABLED */ + +#ifdef HAL_I2S_MODULE_ENABLED + #include "stm32f3xx_hal_i2s.h" +#endif /* HAL_I2S_MODULE_ENABLED */ + +#ifdef HAL_IRDA_MODULE_ENABLED + #include "stm32f3xx_hal_irda.h" +#endif /* HAL_IRDA_MODULE_ENABLED */ + +#ifdef HAL_IWDG_MODULE_ENABLED + #include "stm32f3xx_hal_iwdg.h" +#endif /* HAL_IWDG_MODULE_ENABLED */ + +#ifdef HAL_OPAMP_MODULE_ENABLED + #include "stm32f3xx_hal_opamp.h" +#endif /* HAL_OPAMP_MODULE_ENABLED */ + +#ifdef HAL_PCD_MODULE_ENABLED + #include "stm32f3xx_hal_pcd.h" +#endif /* HAL_PCD_MODULE_ENABLED */ + +#ifdef HAL_PWR_MODULE_ENABLED + #include "stm32f3xx_hal_pwr.h" +#endif /* HAL_PWR_MODULE_ENABLED */ + +#ifdef HAL_RTC_MODULE_ENABLED + #include "stm32f3xx_hal_rtc.h" +#endif /* HAL_RTC_MODULE_ENABLED */ + +#ifdef HAL_SDADC_MODULE_ENABLED + #include "stm32f3xx_hal_sdadc.h" +#endif /* HAL_SDADC_MODULE_ENABLED */ + +#ifdef HAL_SMARTCARD_MODULE_ENABLED + #include "stm32f3xx_hal_smartcard.h" +#endif /* HAL_SMARTCARD_MODULE_ENABLED */ + +#ifdef HAL_SMBUS_MODULE_ENABLED + #include "stm32f3xx_hal_smbus.h" +#endif /* HAL_SMBUS_MODULE_ENABLED */ + +#ifdef HAL_SPI_MODULE_ENABLED + #include "stm32f3xx_hal_spi.h" +#endif /* HAL_SPI_MODULE_ENABLED */ + +#ifdef HAL_TIM_MODULE_ENABLED + #include "stm32f3xx_hal_tim.h" +#endif /* HAL_TIM_MODULE_ENABLED */ + +#ifdef HAL_TSC_MODULE_ENABLED + #include "stm32f3xx_hal_tsc.h" +#endif /* HAL_TSC_MODULE_ENABLED */ + +#ifdef HAL_UART_MODULE_ENABLED + #include "stm32f3xx_hal_uart.h" +#endif /* HAL_UART_MODULE_ENABLED */ + +#ifdef HAL_USART_MODULE_ENABLED + #include "stm32f3xx_hal_usart.h" +#endif /* HAL_USART_MODULE_ENABLED */ + +#ifdef HAL_WWDG_MODULE_ENABLED + #include "stm32f3xx_hal_wwdg.h" +#endif /* HAL_WWDG_MODULE_ENABLED */ + +/* Exported macro ------------------------------------------------------------*/ +#ifdef USE_FULL_ASSERT +/** + * @brief The assert_param macro is used for function's parameters check. + * @param expr: If expr is false, it calls assert_failed function + * which reports the name of the source file and the source + * line number of the call that failed. + * If expr is true, it returns no value. + * @retval None + */ + #define assert_param(expr) ((expr) ? (void)0U : assert_failed((uint8_t *)__FILE__, __LINE__)) +/* Exported functions ------------------------------------------------------- */ + void assert_failed(uint8_t* file, uint32_t line); +#else + #define assert_param(expr) ((void)0U) +#endif /* USE_FULL_ASSERT */ + +#ifdef __cplusplus +} +#endif + +#endif /* __STM32F3xx_HAL_CONF_H */ + + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/lib/cmsis_core b/lib/cmsis_core new file mode 160000 +Subproject 96d6da4e252b06dcfdc041e7df23e86161c3300 diff --git a/lib/cmsis_device b/lib/cmsis_device new file mode 160000 +Subproject 5e4ee5ed7a7b6c85176bb70a9fd3c72d6eb99f1 diff --git a/lib/hal_driver b/lib/hal_driver new file mode 160000 +Subproject 1761b6207318ede021706e75aae78f452d72b6f diff --git a/lib/musl b/lib/musl new file mode 160000 +Subproject a5aff1972c9e3981566414b09a28e331ccd2be5 diff --git a/src/bootloader.c b/src/bootloader.c new file mode 100644 index 0000000..f6e313a --- /dev/null +++ b/src/bootloader.c @@ -0,0 +1,99 @@ + +#include <fe_global.h> +#include <fe_config_backend.h> +#include <bootloader.h> +#include <core_cm4.h> + +typedef void (*void_func)(void); + +extern int _Flash_Base; +size_t flash_base = (size_t)&_Flash_Base; +extern int _Flash_Size; +size_t flash_size = (size_t)&_Flash_Size; +extern int _Bootloader_Size; +size_t bootloader_size = (size_t)&_Bootloader_Size; + + +bool fe_check_img_valid(void) { + uint32_t *end_of_flash = (uint32_t *)(flash_base + flash_size); + end_of_flash -= 1; + return *end_of_flash != 0xffffffff; +} + +void fe_jump_to_application() { + uint32_t *user_isr_vector = (uint32_t *)flash_base; + void_func user_reset_vector = (void_func)user_isr_vector[1]; + + SCB->VTOR = (uint32_t)flash_base; + __set_MSP(user_isr_vector[0]); + user_reset_vector(); +} + +void fe_system_reset(void) { + SCB->AIRCR |= SCB_AIRCR_SYSRESETREQ_Msk; +} + +void flash_unlock() { + FLASH->KEYR = 0x45670123; + FLASH->KEYR = 0xCDEF89AB; +} + +void flash_lock() { + FLASH->CR |= FLASH_CR_LOCK; +} + +int flash_erase_page(size_t addr) { + while (FLASH->SR & FLASH_SR_BSY) + ; + + FLASH->CR |= FLASH_CR_PER; + FLASH->AR = addr; + FLASH->CR |= FLASH_CR_STRT; + + /* RM0365, pg. 63: The software should start checking if the BSY bit equals ‘0’ at least one CPU cycle after setting + * the STRT bit */ + asm volatile ("nop"); + + while (FLASH->SR & FLASH_SR_BSY) + ; + + if (FLASH->SR & FLASH_SR_EOP) { + FLASH->SR = FLASH_SR_EOP; + return 0; + } + return -1; +} + +int flash_write(size_t addr, char *buf, size_t len) { + assert((len&1) == 0); + assert((addr&1) == 0); + + uint16_t *dst = (uint16_t *)addr; + uint16_t *src = (uint16_t *)src; + len /= 2; + while (len--) { + *dst++ = *src++; + while (FLASH->SR & FLASH_SR_BSY) + ; + } + + if (FLASH->SR & FLASH_SR_EOP) + FLASH->SR = FLASH_SR_EOP; + else + return 1; + + return 0; +} + +int erase_user_flash() { + assert ((_Bootloader_Size & (PAGE_SIZE-1)) == 0); + size_t first_page = _Flash_Base + _Bootloader_Size; + size_t npages = (_Flash_Size - _Bootloader_Size) / PAGE_SIZE; + + int rc = 0; + while (npages--) + rc |= flash_erase_page(first_page + npages); /* TODO error handling */ + return rc; +} + + diff --git a/src/clocks.c b/src/clocks.c new file mode 100644 index 0000000..a8cb8e4 --- /dev/null +++ b/src/clocks.c @@ -0,0 +1,83 @@ +#include <fe_global.h> +#include <fe_clocks.h> + +unsigned int sysclk_speed; +unsigned int ahb_speed; +unsigned int apb1_speed; +unsigned int apb2_speed; +unsigned int apb1_timer_speed; +unsigned int apb2_timer_speed; + + +void delay_ms(int ms) { + uint32_t init_val = SysTick->VAL; + uint32_t wait_end = sys_time_millis + ms; + + while (sys_time_millis < wait_end) + ; + + while (SysTick->VAL >= init_val) { + if (sys_time_millis > wait_end) + return; + } +} + +void fe_config_clocks() +{ + /* 8MHz HSI clock as PLL source. */ +#define HSI_SPEED 8000000 + /* PLL output = HSI / 2 * PLL_MUL */ +#define PLL_MUL 16 + + /* Check that we came out of reset correctly */ + if (((RCC->CFGR & RCC_CFGR_SWS_Msk) >> RCC_CFGR_SW_Pos) != 0) + asm volatile ("bkpt"); + if (RCC->CR & RCC_CR_HSEON) + asm volatile ("bkpt"); + if (RCC->CR & RCC_CR_PLLON) + asm volatile ("bkpt"); + + RCC->CFGR = 0; + RCC->CFGR |= (0<<RCC_CFGR_PLLSRC_Pos); /* PLL input: HSI /2 */ + RCC->CFGR |= ((PLL_MUL-2)<<RCC_CFGR_PLLMUL_Pos); + sysclk_speed = HSI_SPEED / 2 * PLL_MUL; + + /* set AHB prescaler to /1 */ + RCC->CFGR |= (0 << RCC_CFGR_HPRE_Pos); + ahb_speed = sysclk_speed; + + /* set ABP1 prescaler to 2 -> 36MHz */ + RCC->CFGR |= (4 << RCC_CFGR_PPRE1_Pos); + apb1_speed = sysclk_speed / 2; + apb1_timer_speed = apb1_speed * 2; + + /* set ABP2 prescaler to 2 -> 36MHz */ + RCC->CFGR |= (4 << RCC_CFGR_PPRE2_Pos); + apb2_speed = sysclk_speed / 2; + apb2_timer_speed = apb2_speed * 2; + + /* Configure PLL */ + RCC->CR |= RCC_CR_PLLON; + + /* Wait for main PLL */ + while(!(RCC->CR & RCC_CR_PLLRDY)) + ; + + /* Configure Flash: enable prefetch; set latency = 2 wait states + * See reference manual (RM0365), Section 4.5.1 + */ + FLASH->ACR = FLASH_ACR_PRFTBE | (2<<FLASH_ACR_LATENCY_Pos); + + /* Select PLL as system clock source */ + RCC->CFGR &= ~RCC_CFGR_SW_Msk; + RCC->CFGR |= 2 << RCC_CFGR_SW_Pos; + + /* Wait for clock to switch over */ + while ((RCC->CFGR & RCC_CFGR_SWS_Msk)>>RCC_CFGR_SWS_Pos != 2) + ; + + SystemCoreClockUpdate(); + SysTick_Config(SystemCoreClock / 1000); /* 1 ms ticks */ + + NVIC_SetPriority(SysTick_IRQn, 32); +} diff --git a/src/cobs.c b/src/cobs.c new file mode 100644 index 0000000..f6fb84a --- /dev/null +++ b/src/cobs.c @@ -0,0 +1,211 @@ + +#include "cobs.h" + +int cobs_encode_usart(int (*output)(void*, char), void *userdata, char *src, size_t srclen) { + if (srclen > 254) + return -1; + + size_t p = 0; + while (p <= srclen) { + + char val; + if (p != 0 && src[p-1] != 0) { + val = src[p-1]; + + } else { + size_t q = p; + while (q < srclen && src[q] != 0) + q++; + val = (char)q-p+1; + } + + int rv = output(userdata, val); + if (rv) + return rv; + p++; + } + + int rv = output(userdata, 0); + if (rv) + return rv; + + return 0; +} + +/*@ requires \valid(dst + (0..dstlen-1)); + @ requires \valid_read(src + (0..srclen-1)); + @ requires \separated(dst + (0..dstlen-1), src + (0..srclen-1)); + @ + @ behavior maybe_valid_frame: + @ assumes 1 <= srclen <= dstlen <= 65535; + @ assumes \exists integer j; j > 0 && \forall integer i; 0 <= i < j ==> src[i] != 0; + @ assumes \exists integer i; 0 <= i < srclen && src[i] == 0; + @ assigns dst[0..dstlen-1]; + @ ensures \result >= 0 || \result == -3; + @ ensures \result >= 0 ==> src[\result+1] == 0; + @ ensures \result >= 0 ==> (\forall integer i; 0 <= i < \result ==> src[i] != 0); + @ + @ behavior invalid_frame: + @ assumes 1 <= srclen <= dstlen <= 65535; + @ assumes src[0] == 0 || \forall integer i; 0 <= i < srclen ==> src[i] != 0; + @ assigns dst[0..dstlen-1]; + @ ensures \result == -2; + @ + @ behavior invalid_buffers: + @ assumes dstlen < 0 || dstlen > 65535 + @ || srclen < 1 || srclen > 65535 + @ || dstlen < srclen; + @ assigns \nothing; + @ ensures \result == -1; + @ + @ complete behaviors; + @ disjoint behaviors; + @*/ +ssize_t cobs_decode(char *dst, size_t dstlen, char *src, size_t srclen) { + if (dstlen > 65535 || srclen > 65535) + return -1; + + if (srclen < 1) + return -1; + + if (dstlen < srclen) + return -1; + + size_t p = 1; + size_t c = (unsigned char)src[0]; + //@ assert 0 <= c < 256; + //@ assert 0 <= c; + //@ assert c < 256; + if (c == 0) + return -2; /* invalid framing. An empty frame would be [...] 00 01 00, not [...] 00 00 */ + //@ assert c >= 0; + //@ assert c != 0; + //@ assert c <= 257; + //@ assert c > 0; + //@ assert c >= 0 && c != 0 ==> c > 0; + + /*@ //loop invariant \forall integer i; 0 <= i <= p ==> (i == srclen || src[i] != 0); + @ loop invariant \forall integer i; 1 <= i < p ==> src[i] != 0; + @ loop invariant c > 0; + @ loop invariant 1 <= p <= srclen <= dstlen <= 65535; + @ loop invariant \separated(dst + (0..dstlen-1), src + (0..srclen-1)); + @ loop invariant \valid_read(src + (0..srclen-1)); + @ loop invariant \forall integer i; 1 <= i <= srclen ==> \valid(dst + i - 1); + @ loop assigns dst[0..dstlen-1], p, c; + @ loop variant srclen-p; + @*/ + while (p < srclen && src[p]) { + char val; + c--; + + //@ assert src[p] != 0; + if (c == 0) { + c = (unsigned char)src[p]; + val = 0; + } else { + val = src[p]; + } + + //@ assert 0 <= p-1 <= dstlen-1; + dst[p-1] = val; + p++; + } + + if (p == srclen) + return -2; /* Invalid framing. The terminating null byte should always be present in the input buffer. */ + + if (c != 1) + return -3; /* Invalid framing. The skip counter does not hit the end of the frame. */ + + //@ assert 0 < p <= srclen <= 65535; + //@ assert src[p] == 0; + //@ assert \forall integer i; 1 <= i < p ==> src[i] != 0; + return p-1; +} + +void cobs_decode_incremental_initialize(struct cobs_decode_state *state) { + state->p = 0; + state->c = 0; +} + +int cobs_decode_incremental(struct cobs_decode_state *state, char *dst, size_t dstlen, char src) { + if (state->p == 0) { + if (src == 0) + goto empty_errout; /* invalid framing. An empty frame would be [...] 00 01 00, not [...] 00 00 */ + state->c = (unsigned char)src; + state->p++; + return -1; + } + + if (!src) { + if (state->c != 1) + goto errout; /* Invalid framing. The skip counter does not hit the end of the frame. */ + int rv = state->p-1; + cobs_decode_incremental_initialize(state); + return rv; + } + + char val; + state->c--; + + if (state->c == 0) { + state->c = (unsigned char)src; + val = 0; + } else { + val = src; + } + + size_t pos = state->p-1; + if (pos >= dstlen) + return -4; /* output buffer too small */ + dst[pos] = val; + state->p++; + return -1; + +errout: + cobs_decode_incremental_initialize(state); + return -2; + +empty_errout: + cobs_decode_incremental_initialize(state); + return -3; +} + +#ifdef VALIDATION +/*@ + @ requires 0 <= d < 256; + @ assigns \nothing; + @*/ +size_t test(char foo, unsigned int d) { + unsigned int c = (unsigned char)foo; + if (c != 0) { + //@ assert c < 256; + //@ assert c >= 0; + //@ assert c != 0; + //@ assert c > 0; + } + if (d != 0) { + //@ assert d >= 0; + //@ assert d != 0; + //@ assert d > 0; + } + return c + d; +} + +#include <__fc_builtin.h> + +void main(void) { + char inbuf[254]; + char cobsbuf[256]; + char outbuf[256]; + + size_t range = Frama_C_interval(0, sizeof(inbuf)); + Frama_C_make_unknown((char *)inbuf, range); + + cobs_encode(cobsbuf, sizeof(cobsbuf), inbuf, sizeof(inbuf)); + cobs_decode(outbuf, sizeof(outbuf), cobsbuf, sizeof(cobsbuf)); + + //@ assert \forall integer i; 0 <= i < sizeof(inbuf) ==> outbuf[i] == inbuf[i]; +} +#endif//VALIDATION + diff --git a/src/interrupts.c b/src/interrupts.c new file mode 100644 index 0000000..00ee749 --- /dev/null +++ b/src/interrupts.c @@ -0,0 +1,62 @@ +#include "fe_global.h" +#include "fe_interrupts.h" + +uint64_t sys_time_millis; + +/******************************************************************************/ +/* Cortex-M4 CPU Interrupts */ +/******************************************************************************/ + +void NMI_Handler(void) +{ +} + +void HardFault_Handler(void) +{ + while (42) { + asm volatile ("bkpt #13"); + } +} + +void MemManage_Handler(void) +{ + while (42) { + asm volatile ("bkpt #12"); + } +} + +void BusFault_Handler(void) +{ + while (42) { + asm volatile ("bkpt #11"); + } +} + +void UsageFault_Handler(void) +{ + while (42) { + asm volatile ("bkpt #10"); + } +} + +void SVC_Handler(void) +{ +} + +void DebugMon_Handler(void) +{ +} + +void PendSV_Handler(void) +{ +} + +void SysTick_Handler(void) +{ + sys_time_millis += 1; +} + +/******************************************************************************/ +/* STM32 Peripheral interrupts */ +/******************************************************************************/ + diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..f9992f5 --- /dev/null +++ b/src/main.c @@ -0,0 +1,370 @@ +#include <fe_global.h> +#include <fe_clocks.h> +#include <bootloader.h> +#include <cobs.h> + +/* User configuration */ +#include <fe_config_backend.h> + +struct usart_config { + uint32_t rx_rcc_ahbenr_flags; + GPIO_TypeDef *rx_gpio; + int rx_pin; + int rx_alt; + + uint32_t tx_rcc_ahbenr_flags; + GPIO_TypeDef *tx_gpio; + int tx_pin; + int tx_alt; + + uint32_t apb1enr_mask; + uint32_t apb2enr_mask; + USART_TypeDef *usart; +}; + +struct usart_config usart_gpios[FE_CONFIG_USART_COUNT] = { + [FE_CONFIG_USART1_PB7] = {FE_CONFIG_GPIOB, 7, 7, FE_CONFIG_GPIOB, 6, 7, 0, RCC_APB2ENR_USART1EN, USART1}, +}; + +void run_bootloader(void); +int bootloader_handle_cmd(USART_TypeDef *us, char *buf, size_t len); +void usart_init(struct usart_config *uc); +void usart_putc(USART_TypeDef *us, char c); +void usart_puts(USART_TypeDef *us, const char *data); +int usart_rx(USART_TypeDef *us); +int usart_tx_framed(USART_TypeDef * us, char *buf, size_t len); + +void __libc_init_array(void) { /* we don't need this. */ } + +void __assert_func (unused_a const char *file, unused_a int line, unused_a const char *function, unused_a const char *expr) { + asm volatile ("bkpt #69"); + while(1) {} +} + +void gpio_config(GPIO_TypeDef *gpio, int pin, int mode, int speed, int pullups, int alt) { + gpio->MODER &= ~(3<<(2*pin)); + gpio->MODER |= mode<<(2*pin); + gpio->OSPEEDR &= ~(3<<(2*pin)); + gpio->OSPEEDR |= speed<<(2*pin); + gpio->PUPDR &= ~(3<<(2*pin)); + gpio->PUPDR |= pullups<<(2*pin); + if (pin < 8) { + gpio->AFR[0] &= ~(0xf << (4*pin)); + gpio->AFR[0] |= alt << (4*pin); + } else { + pin -= 8; + gpio->AFR[1] &= ~(0xf << (4*pin)); + gpio->AFR[1] |= alt << (4*pin); + } +} + +int main(void) +{ + const struct fe_config_def *c = &fe_config; + + uint32_t old_moder = c->bootloader_enable_pin.gpio->MODER; + uint32_t old_ospeedr = c->bootloader_enable_pin.gpio->OSPEEDR; + uint32_t old_pupdr = c->bootloader_enable_pin.gpio->PUPDR; + RCC->AHBENR |= c->bootloader_enable_pin.rcc_ahbenr_flags; + int pullups = c->bootloader_enable_level ? 2 : 1; + gpio_config(c->bootloader_enable_pin.gpio, c->bootloader_enable_pin.pin_number, 0, 0, pullups, 0); + + delay_ms(50); + int enable_pin_state = ((c->bootloader_enable_pin.gpio->IDR >> c->bootloader_enable_pin.pin_number) & 1); + if (enable_pin_state == c->bootloader_enable_level || !fe_check_img_valid()) { + fe_config_clocks(); + run_bootloader(); + fe_system_reset(); + + } else { + c->bootloader_enable_pin.gpio->MODER = old_moder; + c->bootloader_enable_pin.gpio->OSPEEDR = old_ospeedr; + c->bootloader_enable_pin.gpio->PUPDR = old_pupdr; + fe_jump_to_application(); + } + + /* Should never be reached. */ + assert(0); + return 0; +} + +#define MAX_RX_SIZE 512 +char rx_buf[MAX_RX_SIZE]; + +void run_bootloader() { + assert(fe_config.usart <= FE_CONFIG_USART_COUNT); + struct usart_config *uc = &usart_gpios[fe_config.usart]; + + usart_init(uc); + usart_puts(uc->usart, fe_config.welcome_string); + usart_putc(uc->usart, 0x00); + + struct cobs_decode_state cobs_st; + cobs_decode_incremental_initialize(&cobs_st); + while (42) { + int c = usart_rx(uc->usart); + if (c == -2) { + /* ignore errors for now */ + continue; + } else if (c == -1) { + /* We received nothing */ + continue; + } + + int rc = cobs_decode_incremental(&cobs_st, rx_buf, sizeof(rx_buf), c); + if (rc == -1) { + continue; + + } else if (rc < 0) { + /* Ignore errors for now */ + continue; + } + + (void)bootloader_handle_cmd(uc->usart, rx_buf, rc); /* ignore errors for now */ + } +} + +enum bootloader_cmd { + /* Generic commands */ + FE_CMD_REPLY = 0, + FE_CMD_PING = 1, + FE_CMD_IDENTIFY = 2, + + /* Bootloader commands */ + FE_CMD_ERASE = 16 + 0, + FE_CMD_WRITE_BLOCK = 16 + 1, + FE_CMD_REBOOT = 16 + 2, + + /* Reserved commands */ + FE_CMD_RESERVED0 = 'e', /* 0x65 / 101 Reserved for welcome string */ +}; + +enum error_codes { + FE_SUCCESS = 0, + FE_ECMD = 1, + FE_ESIZE = 2, + FE_EINVAL = 3, + FE_ESYS = 4, +}; + +#define FE_MAX_PACKET_SIZE 254 +struct cmd_header { + uint8_t cmd; /* Command code, see enum bootloader_cmd */ + uint32_t tag; /* Tag for request/response identification. Echoed back to requestor in response. */ +} __attribute__((packed)); + +#define FE_MAX_PAYLOAD_SIZE (FE_MAX_PACKET_SIZE - sizeof(struct cmd_header)) + +struct id_response { + uint8_t major_version; + uint8_t minor_version; + uint32_t idcode; + uint8_t serial[6]; + char id_string[FE_MAX_PAYLOAD_SIZE - 2]; +}; + +struct simple_response { + int32_t return_code; +}; + +struct cmd_packet { + struct cmd_header hdr; + union { + uint8_t data[FE_MAX_PAYLOAD_SIZE]; + struct id_response id_response; + struct simple_response simple_response; + }; +}; + +static const uint8_t major_version = 1; +static const uint8_t minor_version = 0; +static const char *id_string = "Fenris Bootloader"; + +static int err_simple(USART_TypeDef *us, struct cmd_packet *pkt, int rc); +static int err_simple(USART_TypeDef *us, struct cmd_packet *pkt, int rc) { + struct simple_response *res = &pkt->simple_response; + pkt->hdr.cmd = FE_CMD_REPLY; + /* leave tag unaffected */ + res->return_code = rc; + return usart_tx_framed(us, (char *)pkt, sizeof(struct cmd_header) + sizeof(struct simple_response)); +} + +static size_t flash_write_addr = 0; + +int bootloader_handle_cmd(USART_TypeDef *us, char *buf, size_t len) { + struct cmd_packet *pkt = (struct cmd_packet *)buf; + int rc = 0; + if (len < sizeof(struct cmd_header)) { + memset(buf, 0, sizeof(struct cmd_header)); + return err_simple(us, pkt, -FE_ESIZE); + } + int payload_len = len-sizeof(struct cmd_header); + + pkt->hdr.cmd = FE_CMD_REPLY; + + switch (pkt->hdr.cmd) { + case FE_CMD_PING: + return usart_tx_framed(us, buf, len); + + case FE_CMD_IDENTIFY: { + if (payload_len != 0) + return err_simple(us, pkt, -FE_ESIZE); + + struct id_response *res = &pkt->id_response; + memcpy(res->serial, (void*)UID_BASE, 12); + res->idcode = DBGMCU->IDCODE; + res->major_version = major_version; + res->minor_version = minor_version; + strncpy(res->id_string, id_string, sizeof(res->id_string)); + len = sizeof(struct cmd_header) + 2 + strlen(id_string) + 1; + if (len < FE_MAX_PACKET_SIZE) + return usart_tx_framed(us, buf, len); + return -10; + } + + case FE_CMD_ERASE: { + if (payload_len != 4) + return err_simple(us, pkt, -FE_ESIZE); + + if (*((uint32_t*)pkt->data) != 0x54454c44) + return err_simple(us, pkt, -FE_EINVAL); + + flash_unlock(); + + if (erase_user_flash()) { + flash_lock(); + return err_simple(us, pkt, -FE_ESYS); + } + + flash_lock(); + flash_write_addr = flash_base; + + return err_simple(us, pkt, 0); + } + + case FE_CMD_WRITE_BLOCK: { + if (payload_len == 0) + return err_simple(us, pkt, -FE_ESIZE); + + if ((payload_len&1) != 0) + return err_simple(us, pkt, -FE_ESIZE); + + if (flash_write_addr + payload_len == flash_size) { + /* FIXME sig check */ + if (0) { /* invalid signature */ + return err_simple(us, pkt, -FE_EINVAL); + } + + } else if (flash_write_addr + payload_len > flash_size) { + return err_simple(us, pkt, -FE_EINVAL); + } + + if (!flash_write(flash_write_addr, pkt->data, payload_len)) + return err_simple(us, pkt, -FE_ESYS); + flash_write_addr += payload_len; + } + case FE_CMD_REBOOT: { + fe_system_reset(); + } + + default: { + return err_simple(us, pkt, -FE_ECMD); + } + } +} + +void usart_init(struct usart_config *uc) { + RCC->AHBENR |= uc->rx_rcc_ahbenr_flags | uc->tx_rcc_ahbenr_flags; + gpio_config(uc->rx_gpio, uc->rx_pin, 2, 3, 1, uc->rx_alt); + gpio_config(uc->tx_gpio, uc->tx_pin, 2, 3, 0, uc->tx_alt); + + RCC->APB1ENR |= uc->apb1enr_mask; + RCC->APB2ENR |= uc->apb2enr_mask; + + uc->usart->CR1 = USART_CR1_TE | USART_CR1_RE; + + int bus_speed = uc->apb1enr_mask ? apb1_speed : apb2_speed; + uc->usart->BRR = bus_speed * 16 / fe_config.baudrate / 16; + uc->usart->CR1 |= USART_CR1_UE; +} + +void usart_putc(USART_TypeDef *us, char c) { + while (!(us->ISR & USART_ISR_TXE)) + ; + us->TDR = c; +} + +void usart_puts(USART_TypeDef *us, const char *data) { + for (const char *c = data; *c; c++) + usart_putc(us, *c); +} + +int usart_rx(USART_TypeDef *us) { + if ((us->ISR & USART_ISR_ORE) || (us->ISR & USART_ISR_PE)) { + /* Ignore overruns or parity errors */ + us->ICR = USART_ICR_ORECF | USART_ICR_PECF; + return -2; + } + + if (us->ISR & USART_ISR_RXNE) + return us->RDR; + + return -1; +} + +static int cobs_encode_usart_output(void *userdata, char c); +static int cobs_encode_usart_output(void *userdata, char c) { + usart_putc((USART_TypeDef *)userdata, c); + return 0; +} + +int usart_tx_framed(USART_TypeDef * us, char *buf, size_t len) { + return cobs_encode_usart(cobs_encode_usart_output, us, buf, len); +} + +void *memcpy(void *restrict dest, const void *restrict src, size_t n) +{ + unsigned char *d = dest; + const unsigned char *s = src; + + for (; n; n--) *d++ = *s++; + return dest; +} + +void *memset(void *dest, int c, size_t n) +{ + unsigned char *s = dest; + size_t k; + + /* Fill head and tail with minimal branching. Each + * conditional ensures that all the subsequently used + * offsets are well-defined and in the dest region. */ + + if (!n) return dest; + s[0] = c; + s[n-1] = c; + if (n <= 2) return dest; + s[1] = c; + s[2] = c; + s[n-2] = c; + s[n-3] = c; + if (n <= 6) return dest; + s[3] = c; + s[n-4] = c; + if (n <= 8) return dest; + + /* Advance pointer to align it at a 4-byte boundary, + * and truncate n to a multiple of 4. The previous code + * already took care of any head/tail that get cut off + * by the alignment. */ + + k = -(uintptr_t)s & 3; + s += k; + n -= k; + n &= -4; + + /* Pure C fallback with no aliasing violations. */ + for (; n; n--, s++) *s = c; + + return dest; +} diff --git a/src/system_stm32f3xx.c b/src/system_stm32f3xx.c new file mode 100644 index 0000000..9e75a9e --- /dev/null +++ b/src/system_stm32f3xx.c @@ -0,0 +1,298 @@ +/** + ****************************************************************************** + * @file system_stm32f3xx.c + * @author MCD Application Team + * @brief CMSIS Cortex-M4 Device Peripheral Access Layer System Source File. + * + * 1. This file provides two functions and one global variable to be called from + * user application: + * - SystemInit(): This function is called at startup just after reset and + * before branch to main program. This call is made inside + * the "startup_stm32f3xx.s" file. + * + * - SystemCoreClock variable: Contains the core clock (HCLK), it can be used + * by the user application to setup the SysTick + * timer or configure other parameters. + * + * - SystemCoreClockUpdate(): Updates the variable SystemCoreClock and must + * be called whenever the core clock is changed + * during program execution. + * + * 2. After each device reset the HSI (8 MHz) is used as system clock source. + * Then SystemInit() function is called, in "startup_stm32f3xx.s" file, to + * configure the system clock before to branch to main program. + * + * 3. This file configures the system clock as follows: + *============================================================================= + * Supported STM32F3xx device + *----------------------------------------------------------------------------- + * System Clock source | HSI + *----------------------------------------------------------------------------- + * SYSCLK(Hz) | 8000000 + *----------------------------------------------------------------------------- + * HCLK(Hz) | 8000000 + *----------------------------------------------------------------------------- + * AHB Prescaler | 1 + *----------------------------------------------------------------------------- + * APB2 Prescaler | 1 + *----------------------------------------------------------------------------- + * APB1 Prescaler | 1 + *----------------------------------------------------------------------------- + * USB Clock | DISABLE + *----------------------------------------------------------------------------- + *============================================================================= + ****************************************************************************** + * @attention + * + * <h2><center>© Copyright (c) 2016 STMicroelectronics. + * All rights reserved.</center></h2> + * + * This software component is licensed by ST under BSD 3-Clause license, + * the "License"; You may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * opensource.org/licenses/BSD-3-Clause + * + ****************************************************************************** + */ + +/** @addtogroup CMSIS + * @{ + */ + +/** @addtogroup stm32f3xx_system + * @{ + */ + +/** @addtogroup STM32F3xx_System_Private_Includes + * @{ + */ + +#include "stm32f3xx.h" + +/** + * @} + */ + +/** @addtogroup STM32F3xx_System_Private_TypesDefinitions + * @{ + */ + +/** + * @} + */ + +/** @addtogroup STM32F3xx_System_Private_Defines + * @{ + */ +#if !defined (HSE_VALUE) + #define HSE_VALUE ((uint32_t)8000000) /*!< Default value of the External oscillator in Hz. + This value can be provided and adapted by the user application. */ +#endif /* HSE_VALUE */ + +#if !defined (HSI_VALUE) + #define HSI_VALUE ((uint32_t)8000000) /*!< Default value of the Internal oscillator in Hz. + This value can be provided and adapted by the user application. */ +#endif /* HSI_VALUE */ + +/*!< Uncomment the following line if you need to relocate your vector Table in + Internal SRAM. */ +/* #define VECT_TAB_SRAM */ +#define VECT_TAB_OFFSET 0x0 /*!< Vector Table base offset field. + This value must be a multiple of 0x200. */ +/** + * @} + */ + +/** @addtogroup STM32F3xx_System_Private_Macros + * @{ + */ + +/** + * @} + */ + +/** @addtogroup STM32F3xx_System_Private_Variables + * @{ + */ + /* This variable is updated in three ways: + 1) by calling CMSIS function SystemCoreClockUpdate() + 2) by calling HAL API function HAL_RCC_GetHCLKFreq() + 3) each time HAL_RCC_ClockConfig() is called to configure the system clock frequency + Note: If you use this function to configure the system clock there is no need to + call the 2 first functions listed above, since SystemCoreClock variable is + updated automatically. + */ +uint32_t SystemCoreClock = 8000000; + +const uint8_t AHBPrescTable[16] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 6, 7, 8, 9}; +const uint8_t APBPrescTable[8] = {0, 0, 0, 0, 1, 2, 3, 4}; + +/** + * @} + */ + +/** @addtogroup STM32F3xx_System_Private_FunctionPrototypes + * @{ + */ + +/** + * @} + */ + +/** @addtogroup STM32F3xx_System_Private_Functions + * @{ + */ + +/** + * @brief Setup the microcontroller system + * Initialize the FPU setting, vector table location and the PLL configuration is reset. + * @param None + * @retval None + */ +void SystemInit(void) +{ + /* FPU settings ------------------------------------------------------------*/ + #if (__FPU_PRESENT == 1) && (__FPU_USED == 1) + SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full Access */ + #endif + + /* Reset the RCC clock configuration to the default reset state ------------*/ + /* Set HSION bit */ + RCC->CR |= (uint32_t)0x00000001; + + /* Reset CFGR register */ + RCC->CFGR &= 0xF87FC00C; + + /* Reset HSEON, CSSON and PLLON bits */ + RCC->CR &= (uint32_t)0xFEF6FFFF; + + /* Reset HSEBYP bit */ + RCC->CR &= (uint32_t)0xFFFBFFFF; + + /* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE bits */ + RCC->CFGR &= (uint32_t)0xFF80FFFF; + + /* Reset PREDIV1[3:0] bits */ + RCC->CFGR2 &= (uint32_t)0xFFFFFFF0; + + /* Reset USARTSW[1:0], I2CSW and TIMs bits */ + RCC->CFGR3 &= (uint32_t)0xFF00FCCC; + + /* Disable all interrupts */ + RCC->CIR = 0x00000000; + +#ifdef VECT_TAB_SRAM + SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */ +#else + SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */ +#endif +} + +/** + * @brief Update SystemCoreClock variable according to Clock Register Values. + * The SystemCoreClock variable contains the core clock (HCLK), it can + * be used by the user application to setup the SysTick timer or configure + * other parameters. + * + * @note Each time the core clock (HCLK) changes, this function must be called + * to update SystemCoreClock variable value. Otherwise, any configuration + * based on this variable will be incorrect. + * + * @note - The system frequency computed by this function is not the real + * frequency in the chip. It is calculated based on the predefined + * constant and the selected clock source: + * + * - If SYSCLK source is HSI, SystemCoreClock will contain the HSI_VALUE(*) + * + * - If SYSCLK source is HSE, SystemCoreClock will contain the HSE_VALUE(**) + * + * - If SYSCLK source is PLL, SystemCoreClock will contain the HSE_VALUE(**) + * or HSI_VALUE(*) multiplied/divided by the PLL factors. + * + * (*) HSI_VALUE is a constant defined in stm32f3xx_hal.h file (default value + * 8 MHz) but the real value may vary depending on the variations + * in voltage and temperature. + * + * (**) HSE_VALUE is a constant defined in stm32f3xx_hal.h file (default value + * 8 MHz), user has to ensure that HSE_VALUE is same as the real + * frequency of the crystal used. Otherwise, this function may + * have wrong result. + * + * - The result of this function could be not correct when using fractional + * value for HSE crystal. + * + * @param None + * @retval None + */ +void SystemCoreClockUpdate (void) +{ + uint32_t tmp = 0, pllmull = 0, pllsource = 0, predivfactor = 0; + + /* Get SYSCLK source -------------------------------------------------------*/ + tmp = RCC->CFGR & RCC_CFGR_SWS; + + switch (tmp) + { + case RCC_CFGR_SWS_HSI: /* HSI used as system clock */ + SystemCoreClock = HSI_VALUE; + break; + case RCC_CFGR_SWS_HSE: /* HSE used as system clock */ + SystemCoreClock = HSE_VALUE; + break; + case RCC_CFGR_SWS_PLL: /* PLL used as system clock */ + /* Get PLL clock source and multiplication factor ----------------------*/ + pllmull = RCC->CFGR & RCC_CFGR_PLLMUL; + pllsource = RCC->CFGR & RCC_CFGR_PLLSRC; + pllmull = ( pllmull >> 18) + 2; + +#if defined (STM32F302xE) || defined (STM32F303xE) || defined (STM32F398xx) + predivfactor = (RCC->CFGR2 & RCC_CFGR2_PREDIV) + 1; + if (pllsource == RCC_CFGR_PLLSRC_HSE_PREDIV) + { + /* HSE oscillator clock selected as PREDIV1 clock entry */ + SystemCoreClock = (HSE_VALUE / predivfactor) * pllmull; + } + else + { + /* HSI oscillator clock selected as PREDIV1 clock entry */ + SystemCoreClock = (HSI_VALUE / predivfactor) * pllmull; + } +#else + if (pllsource == RCC_CFGR_PLLSRC_HSI_DIV2) + { + /* HSI oscillator clock divided by 2 selected as PLL clock entry */ + SystemCoreClock = (HSI_VALUE >> 1) * pllmull; + } + else + { + predivfactor = (RCC->CFGR2 & RCC_CFGR2_PREDIV) + 1; + /* HSE oscillator clock selected as PREDIV1 clock entry */ + SystemCoreClock = (HSE_VALUE / predivfactor) * pllmull; + } +#endif /* STM32F302xE || STM32F303xE || STM32F398xx */ + break; + default: /* HSI used as system clock */ + SystemCoreClock = HSI_VALUE; + break; + } + /* Compute HCLK clock frequency ----------------*/ + /* Get HCLK prescaler */ + tmp = AHBPrescTable[((RCC->CFGR & RCC_CFGR_HPRE) >> 4)]; + /* HCLK clock frequency */ + SystemCoreClock >>= tmp; +} + +/** + * @} + */ + +/** + * @} + */ + +/** + * @} + */ + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ + diff --git a/startup_stm32f302x8.s b/startup_stm32f302x8.s new file mode 100644 index 0000000..e91164e --- /dev/null +++ b/startup_stm32f302x8.s @@ -0,0 +1,414 @@ +/** + ****************************************************************************** + * @file startup_stm32f302x8.s + * @author MCD Application Team + * @brief STM32F302x6/STM32F302x8 devices vector table for GCC toolchain. + * This module performs: + * - Set the initial SP + * - Set the initial PC == Reset_Handler, + * - Set the vector table entries with the exceptions ISR address, + * - Configure the clock system + * - Branches to main in the C library (which eventually + * calls main()). + * After Reset the Cortex-M4 processor is in Thread mode, + * priority is Privileged, and the Stack is set to Main. + ****************************************************************************** + * @attention + * + * <h2><center>© Copyright (c) 2016 STMicroelectronics. + * All rights reserved.</center></h2> + * + * This software component is licensed by ST under BSD 3-Clause license, + * the "License"; You may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * opensource.org/licenses/BSD-3-Clause + * + ****************************************************************************** + */ + + .syntax unified + .cpu cortex-m4 + .fpu softvfp + .thumb + +.global g_pfnVectors +.global Default_Handler + +/* start address for the initialization values of the .data section. +defined in linker script */ +.word _sidata +/* start address for the .data section. defined in linker script */ +.word _sdata +/* end address for the .data section. defined in linker script */ +.word _edata +/* start address for the .bss section. defined in linker script */ +.word _sbss +/* end address for the .bss section. defined in linker script */ +.word _ebss + +.equ BootRAM, 0xF1E0F85F +/** + * @brief This is the code that gets called when the processor first + * starts execution following a reset event. Only the absolutely + * necessary set is performed, after which the application + * supplied main() routine is called. + * @param None + * @retval : None +*/ + + .section .text.Reset_Handler + .weak Reset_Handler + .type Reset_Handler, %function +Reset_Handler: + ldr sp, =_estack /* Atollic update: set stack pointer */ + +/* Copy the data segment initializers from flash to SRAM */ + movs r1, #0 + b LoopCopyDataInit + +CopyDataInit: + ldr r3, =_sidata + ldr r3, [r3, r1] + str r3, [r0, r1] + adds r1, r1, #4 + +LoopCopyDataInit: + ldr r0, =_sdata + ldr r3, =_edata + adds r2, r0, r1 + cmp r2, r3 + bcc CopyDataInit + ldr r2, =_sbss + b LoopFillZerobss +/* Zero fill the bss segment. */ +FillZerobss: + movs r3, #0 + str r3, [r2], #4 + +LoopFillZerobss: + ldr r3, = _ebss + cmp r2, r3 + bcc FillZerobss + +/* Call the clock system intitialization function.*/ + bl SystemInit +/* Call static constructors */ + bl __libc_init_array +/* Call the application's entry point.*/ + bl main + +LoopForever: + b LoopForever + +.size Reset_Handler, .-Reset_Handler + +/** + * @brief This is the code that gets called when the processor receives an + * unexpected interrupt. This simply enters an infinite loop, preserving + * the system state for examination by a debugger. + * + * @param None + * @retval : None +*/ + .section .text.Default_Handler,"ax",%progbits +Default_Handler: +Infinite_Loop: + b Infinite_Loop + .size Default_Handler, .-Default_Handler +/****************************************************************************** +* +* The minimal vector table for a Cortex-M4. Note that the proper constructs +* must be placed on this to ensure that it ends up at physical address +* 0x0000.0000. +* +******************************************************************************/ + .section .isr_vector,"a",%progbits + .type g_pfnVectors, %object + .size g_pfnVectors, .-g_pfnVectors + + +g_pfnVectors: + .word _estack + .word Reset_Handler + .word NMI_Handler + .word HardFault_Handler + .word MemManage_Handler + .word BusFault_Handler + .word UsageFault_Handler + .word 0 + .word 0 + .word 0 + .word 0 + .word SVC_Handler + .word DebugMon_Handler + .word 0 + .word PendSV_Handler + .word SysTick_Handler + .word WWDG_IRQHandler + .word PVD_IRQHandler + .word TAMP_STAMP_IRQHandler + .word RTC_WKUP_IRQHandler + .word FLASH_IRQHandler + .word RCC_IRQHandler + .word EXTI0_IRQHandler + .word EXTI1_IRQHandler + .word EXTI2_TSC_IRQHandler + .word EXTI3_IRQHandler + .word EXTI4_IRQHandler + .word DMA1_Channel1_IRQHandler + .word DMA1_Channel2_IRQHandler + .word DMA1_Channel3_IRQHandler + .word DMA1_Channel4_IRQHandler + .word DMA1_Channel5_IRQHandler + .word DMA1_Channel6_IRQHandler + .word DMA1_Channel7_IRQHandler + .word ADC1_IRQHandler + .word USB_HP_CAN_TX_IRQHandler + .word USB_LP_CAN_RX0_IRQHandler + .word CAN_RX1_IRQHandler + .word CAN_SCE_IRQHandler + .word EXTI9_5_IRQHandler + .word TIM1_BRK_TIM15_IRQHandler + .word TIM1_UP_TIM16_IRQHandler + .word TIM1_TRG_COM_TIM17_IRQHandler + .word TIM1_CC_IRQHandler + .word TIM2_IRQHandler + .word 0 + .word 0 + .word I2C1_EV_IRQHandler + .word I2C1_ER_IRQHandler + .word I2C2_EV_IRQHandler + .word I2C2_ER_IRQHandler + .word 0 + .word SPI2_IRQHandler + .word USART1_IRQHandler + .word USART2_IRQHandler + .word USART3_IRQHandler + .word EXTI15_10_IRQHandler + .word RTC_Alarm_IRQHandler + .word USBWakeUp_IRQHandler + .word 0 + .word 0 + .word 0 + .word 0 + .word 0 + .word 0 + .word 0 + .word 0 + .word SPI3_IRQHandler + .word 0 + .word 0 + .word TIM6_DAC_IRQHandler + .word 0 + .word 0 + .word 0 + .word 0 + .word 0 + .word 0 + .word 0 + .word 0 + .word 0 + .word COMP2_IRQHandler + .word COMP4_6_IRQHandler + .word 0 + .word 0 + .word 0 + .word 0 + .word 0 + .word 0 + .word I2C3_EV_IRQHandler + .word I2C3_ER_IRQHandler + .word USB_HP_IRQHandler + .word USB_LP_IRQHandler + .word USBWakeUp_RMP_IRQHandler + .word 0 + .word 0 + .word 0 + .word 0 + .word FPU_IRQHandler + +/******************************************************************************* +* +* Provide weak aliases for each Exception handler to the Default_Handler. +* As they are weak aliases, any function with the same name will override +* this definition. +* +*******************************************************************************/ + + .weak NMI_Handler + .thumb_set NMI_Handler,Default_Handler + + .weak HardFault_Handler + .thumb_set HardFault_Handler,Default_Handler + + .weak MemManage_Handler + .thumb_set MemManage_Handler,Default_Handler + + .weak BusFault_Handler + .thumb_set BusFault_Handler,Default_Handler + + .weak UsageFault_Handler + .thumb_set UsageFault_Handler,Default_Handler + + .weak SVC_Handler + .thumb_set SVC_Handler,Default_Handler + + .weak DebugMon_Handler + .thumb_set DebugMon_Handler,Default_Handler + + .weak PendSV_Handler + .thumb_set PendSV_Handler,Default_Handler + + .weak SysTick_Handler + .thumb_set SysTick_Handler,Default_Handler + + .weak WWDG_IRQHandler + .thumb_set WWDG_IRQHandler,Default_Handler + + .weak PVD_IRQHandler + .thumb_set PVD_IRQHandler,Default_Handler + + .weak TAMP_STAMP_IRQHandler + .thumb_set TAMP_STAMP_IRQHandler,Default_Handler + + .weak RTC_WKUP_IRQHandler + .thumb_set RTC_WKUP_IRQHandler,Default_Handler + + .weak FLASH_IRQHandler + .thumb_set FLASH_IRQHandler,Default_Handler + + .weak RCC_IRQHandler + .thumb_set RCC_IRQHandler,Default_Handler + + .weak EXTI0_IRQHandler + .thumb_set EXTI0_IRQHandler,Default_Handler + + .weak EXTI1_IRQHandler + .thumb_set EXTI1_IRQHandler,Default_Handler + + .weak EXTI2_TSC_IRQHandler + .thumb_set EXTI2_TSC_IRQHandler,Default_Handler + + .weak EXTI3_IRQHandler + .thumb_set EXTI3_IRQHandler,Default_Handler + + .weak EXTI4_IRQHandler + .thumb_set EXTI4_IRQHandler,Default_Handler + + .weak DMA1_Channel1_IRQHandler + .thumb_set DMA1_Channel1_IRQHandler,Default_Handler + + .weak DMA1_Channel2_IRQHandler + .thumb_set DMA1_Channel2_IRQHandler,Default_Handler + + .weak DMA1_Channel3_IRQHandler + .thumb_set DMA1_Channel3_IRQHandler,Default_Handler + + .weak DMA1_Channel4_IRQHandler + .thumb_set DMA1_Channel4_IRQHandler,Default_Handler + + .weak DMA1_Channel5_IRQHandler + .thumb_set DMA1_Channel5_IRQHandler,Default_Handler + + .weak DMA1_Channel6_IRQHandler + .thumb_set DMA1_Channel6_IRQHandler,Default_Handler + + .weak DMA1_Channel7_IRQHandler + .thumb_set DMA1_Channel7_IRQHandler,Default_Handler + + .weak ADC1_IRQHandler + .thumb_set ADC1_IRQHandler,Default_Handler + + .weak USB_HP_CAN_TX_IRQHandler + .thumb_set USB_HP_CAN_TX_IRQHandler,Default_Handler + + .weak USB_LP_CAN_RX0_IRQHandler + .thumb_set USB_LP_CAN_RX0_IRQHandler,Default_Handler + + .weak CAN_RX1_IRQHandler + .thumb_set CAN_RX1_IRQHandler,Default_Handler + + .weak CAN_SCE_IRQHandler + .thumb_set CAN_SCE_IRQHandler,Default_Handler + + .weak EXTI9_5_IRQHandler + .thumb_set EXTI9_5_IRQHandler,Default_Handler + + .weak TIM1_BRK_TIM15_IRQHandler + .thumb_set TIM1_BRK_TIM15_IRQHandler,Default_Handler + + .weak TIM1_UP_TIM16_IRQHandler + .thumb_set TIM1_UP_TIM16_IRQHandler,Default_Handler + + .weak TIM1_TRG_COM_TIM17_IRQHandler + .thumb_set TIM1_TRG_COM_TIM17_IRQHandler,Default_Handler + + .weak TIM1_CC_IRQHandler + .thumb_set TIM1_CC_IRQHandler,Default_Handler + + .weak TIM2_IRQHandler + .thumb_set TIM2_IRQHandler,Default_Handler + + .weak I2C1_EV_IRQHandler + .thumb_set I2C1_EV_IRQHandler,Default_Handler + + .weak I2C1_ER_IRQHandler + .thumb_set I2C1_ER_IRQHandler,Default_Handler + + .weak I2C2_EV_IRQHandler + .thumb_set I2C2_EV_IRQHandler,Default_Handler + + .weak I2C2_ER_IRQHandler + .thumb_set I2C2_ER_IRQHandler,Default_Handler + + .weak SPI2_IRQHandler + .thumb_set SPI2_IRQHandler,Default_Handler + + .weak USART1_IRQHandler + .thumb_set USART1_IRQHandler,Default_Handler + + .weak USART2_IRQHandler + .thumb_set USART2_IRQHandler,Default_Handler + + .weak USART3_IRQHandler + .thumb_set USART3_IRQHandler,Default_Handler + + .weak EXTI15_10_IRQHandler + .thumb_set EXTI15_10_IRQHandler,Default_Handler + + .weak RTC_Alarm_IRQHandler + .thumb_set RTC_Alarm_IRQHandler,Default_Handler + + .weak USBWakeUp_IRQHandler + .thumb_set USBWakeUp_IRQHandler,Default_Handler + + .weak SPI3_IRQHandler + .thumb_set SPI3_IRQHandler,Default_Handler + + .weak TIM6_DAC_IRQHandler + .thumb_set TIM6_DAC_IRQHandler,Default_Handler + + .weak COMP2_IRQHandler + .thumb_set COMP2_IRQHandler,Default_Handler + + .weak COMP4_6_IRQHandler + .thumb_set COMP4_6_IRQHandler,Default_Handler + + .weak I2C3_EV_IRQHandler + .thumb_set I2C3_EV_IRQHandler,Default_Handler + + .weak I2C3_ER_IRQHandler + .thumb_set I2C3_ER_IRQHandler,Default_Handler + + .weak USB_HP_IRQHandler + .thumb_set USB_HP_IRQHandler,Default_Handler + + .weak USB_LP_IRQHandler + .thumb_set USB_LP_IRQHandler,Default_Handler + + .weak USBWakeUp_RMP_IRQHandler + .thumb_set USBWakeUp_RMP_IRQHandler,Default_Handler + + .weak FPU_IRQHandler + .thumb_set FPU_IRQHandler,Default_Handler +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/stm32f302r8tx.ld b/stm32f302r8tx.ld new file mode 100644 index 0000000..f8275ec --- /dev/null +++ b/stm32f302r8tx.ld @@ -0,0 +1,201 @@ +/* +****************************************************************************** +** + +** File : LinkerScript.ld +** +** Author : Auto-generated by Ac6 System Workbench +** +** Abstract : Linker script for STM32F302R8Tx series +** 64Kbytes FLASH and 16Kbytes RAM +** +** Set heap size, stack size and stack location according +** to application requirements. +** +** Set memory bank area and size if external memory is used. +** +** Target : STMicroelectronics STM32 +** +** Distribution: The file is distributed “as is,” without any warranty +** of any kind. +** +***************************************************************************** +** @attention +** +** <h2><center>© COPYRIGHT(c) 2014 Ac6</center></h2> +** +** Redistribution and use in source and binary forms, with or without modification, +** are permitted provided that the following conditions are met: +** 1. Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright notice, +** this list of conditions and the following disclaimer in the documentation +** and/or other materials provided with the distribution. +** 3. Neither the name of Ac6 nor the names of its contributors +** may be used to endorse or promote products derived from this software +** without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +** DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +** OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +***************************************************************************** +*/ + +/* Entry Point */ +ENTRY(Reset_Handler) + +/* Highest address of the user mode stack */ +_estack = 0x20004000; /* end of RAM */ +/* Generate a link error if heap and stack don't fit into RAM */ +_Min_Heap_Size = 0x200; /* required amount of heap */ +_Min_Stack_Size = 0x400; /* required amount of stack */ + +_Flash_Base = 0x08000000; +_Flash_Size = 64K; +_Bootloader_Size = 0x2000; /* Reserver 8k for bootloader */ + +/* Specify the memory areas */ +MEMORY +{ +FLASH (rx) : ORIGIN = _Flash_Base, LENGTH = _Bootloader_Size +USER_FLASH (xrw): ORIGIN = _Flash_Base + _Bootloader_Size, LENGTH = _Flash_Size - _Bootloader_Size +RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 16K +} + +/* Define output sections */ +SECTIONS +{ + /* The startup code goes first into FLASH */ + .isr_vector : + { + . = ALIGN(4); + KEEP(*(.isr_vector)) /* Startup code */ + . = ALIGN(4); + } >FLASH + + /* The program code and other data goes into FLASH */ + .text : + { + . = ALIGN(4); + *(.text) /* .text sections (code) */ + *(.text*) /* .text* sections (code) */ + *(.glue_7) /* glue arm to thumb code */ + *(.glue_7t) /* glue thumb to arm code */ + *(.eh_frame) + + KEEP (*(.init)) + KEEP (*(.fini)) + + . = ALIGN(4); + _etext = .; /* define a global symbols at end of code */ + } >FLASH + + /* Constant data goes into FLASH */ + .rodata : + { + . = ALIGN(4); + *(.rodata) /* .rodata sections (constants, strings, etc.) */ + *(.rodata*) /* .rodata* sections (constants, strings, etc.) */ + . = ALIGN(4); + } >FLASH + + .ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH + .ARM : { + __exidx_start = .; + *(.ARM.exidx*) + __exidx_end = .; + } >FLASH + + .preinit_array : + { + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP (*(.preinit_array*)) + PROVIDE_HIDDEN (__preinit_array_end = .); + } >FLASH + .init_array : + { + PROVIDE_HIDDEN (__init_array_start = .); + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array*)) + PROVIDE_HIDDEN (__init_array_end = .); + } >FLASH + .fini_array : + { + PROVIDE_HIDDEN (__fini_array_start = .); + KEEP (*(SORT(.fini_array.*))) + KEEP (*(.fini_array*)) + PROVIDE_HIDDEN (__fini_array_end = .); + } >FLASH + + /* used by the startup to initialize data */ + _sidata = LOADADDR(.data); + + /* Initialized data sections goes into RAM, load LMA copy after code */ + .data : + { + . = ALIGN(4); + _sdata = .; /* create a global symbol at data start */ + *(.data) /* .data sections */ + *(.data*) /* .data* sections */ + + . = ALIGN(4); + _edata = .; /* define a global symbol at data end */ + } >RAM AT> FLASH + + _user_flash_start = ORIGIN(USER_FLASH); + _user_flash_size = LENGTH(USER_FLASH); + .user_flash : { + . = ALIGN(4); + *(.user_flash) + } >USER_FLASH + + + /* Uninitialized data section */ + . = ALIGN(4); + .bss : + { + /* This is used by the startup in order to initialize the .bss secion */ + _sbss = .; /* define a global symbol at bss start */ + __bss_start__ = _sbss; + *(.bss) + *(.bss*) + *(COMMON) + + . = ALIGN(4); + _ebss = .; /* define a global symbol at bss end */ + __bss_end__ = _ebss; + } >RAM + + /* User_heap_stack section, used to check that there is enough RAM left */ + ._user_heap_stack : + { + . = ALIGN(8); + PROVIDE ( end = . ); + PROVIDE ( _end = . ); + . = . + _Min_Heap_Size; + . = . + _Min_Stack_Size; + . = ALIGN(8); + } >RAM + + + + /* Remove information from the standard libraries */ + /DISCARD/ : + { + libc.a ( * ) + libm.a ( * ) + libgcc.a ( * ) + } + + .ARM.attributes 0 : { *(.ARM.attributes) } +} + + diff --git a/tools/__pycache__/dsss_demod_test_waveform_gen.cpython-38.pyc b/tools/__pycache__/dsss_demod_test_waveform_gen.cpython-38.pyc Binary files differnew file mode 100644 index 0000000..1512990 --- /dev/null +++ b/tools/__pycache__/dsss_demod_test_waveform_gen.cpython-38.pyc diff --git a/tools/__pycache__/presig_gen.cpython-38.pyc b/tools/__pycache__/presig_gen.cpython-38.pyc Binary files differnew file mode 100644 index 0000000..b28b1a9 --- /dev/null +++ b/tools/__pycache__/presig_gen.cpython-38.pyc diff --git a/tools/butter_filter_gen.py b/tools/butter_filter_gen.py new file mode 100644 index 0000000..0bb81bc --- /dev/null +++ b/tools/butter_filter_gen.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 + +import math +import sys +import contextlib + +import scipy.signal as sig +import numpy as np + + +@contextlib.contextmanager +def wrap(left='{', right='}', file=None, end=''): + print(left, file=file, end=end) + yield + print(right, file=file, end=end) + +@contextlib.contextmanager +def print_include_guards(macro_name): + print(f'#ifndef {macro_name}') + print(f'#define {macro_name}') + print() + yield + print() + print(f'#endif /* {macro_name} */') + +macro_float = lambda f: f'{f}'.replace('.', 'F').replace('-', 'N').replace('+', 'P') + +ordinal = lambda n: "%d%s" % (n,"tsnrhtdd"[(n//10%10!=1)*(n%10<4)*n%10::4]) + +SI_TABLE = {-18: 'a', -15: 'f', -12: 'p', -9: 'n', -6: 'µ', -3: 'm', 0: '', 3: 'k', 6: 'M', 9: 'G', 12: 'T', 15: 'P', 18: 'E'} +def siprefix(x, space=' ', unit=''): + l = math.log10(x)//3*3 + if l in SI_TABLE: + return f'{x/10**l}{space}{SI_TABLE[l]}{unit}' + return f'{x}{space}{unit}' + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('-m', '--macro-name', default='butter_filter', help='Prefix for output macro names') + parser.add_argument('fc', type=float, help='Corner frequency [Hz]') + parser.add_argument('fs', type=float, help='Sampling rate [Hz]') + parser.add_argument('n', type=int, nargs='?', default=6, help='Filter order') + args = parser.parse_args() + + sos = sig.butter(args.n, args.fc, fs=args.fs, output='sos') + + print('/* THIS IS A GENERATED FILE. DO NOT EDIT! */') + print() + with print_include_guards(f'__BUTTER_FILTER_GENERATED_{args.n}_{macro_float(args.fc)}_{macro_float(args.fs)}__'): + + print(f'/* {ordinal(args.n)} order Butterworth IIR filter coefficients') + print(f' *') + print(f' * corner frequency f_c = {siprefix(args.fc)}Hz') + print(f' * sampling rate f_s = {siprefix(args.fs)}Hz') + print(f' */') + print() + print(f'#define {args.macro_name.upper()}_ORDER {args.n}') + print(f'#define {args.macro_name.upper()}_CLEN {(args.n+1)//2}') + + # scipy.signal.butter by default returns extremely small bs for the first biquad and large ones for subsequent + # sections. Balance magnitudes to reduce possible rounding errors. + first_biquad_bs = sos[0][:3] + approx_mag = round(math.log10(np.mean(first_biquad_bs))) + mags = [approx_mag // len(sos)] * len(sos) + mags[0] += approx_mag - sum(mags) + sos[0][:3] /= 10**approx_mag + sos = np.array([ sec * np.array([10**mag, 10**mag, 10**mag, 1, 1, 1]) for mag, sec in zip(mags, sos) ]) + + ones = np.ones([100000]) + _, steady_state = sig.sosfilt(sos, ones, zi=np.zeros([(args.n+1)//2, 2])) + + print(f'#define {args.macro_name.upper()}_COEFF ', end='') + for sec in sos: + bs, ases = sec[:3], sec[4:6] + + with wrap(): + print('.b=', end='') + with wrap(): + print(', '.join(f'{v}' for v in bs), end='') + print(', .a=', end='') + with wrap(): + print(', '.join(f'{v}' for v in ases), end='') + print(', ', end='') + print() + + print(f'#define {args.macro_name.upper()}_STEADY_STATE ', end='') + for sec in steady_state: + with wrap(): + print(', '.join(f'{v}' for v in sec), end='') + print(', ', end='') + print() + diff --git a/tools/crypto_test.c b/tools/crypto_test.c new file mode 100644 index 0000000..410fac2 --- /dev/null +++ b/tools/crypto_test.c @@ -0,0 +1,46 @@ + +#include <stdint.h> +#include <math.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/fcntl.h> + +#include "crypto.h" + +void oob_trigger_activated(enum trigger_domain domain, int serial) { + printf("oob_trigger_activated(%d, %d)\n", domain, serial); + fflush(stdout); +} + +void print_usage() { + fprintf(stderr, "Usage: crypto_test [auth_key_hex]\n"); +} + +int main(int argc, char **argv) { + if (argc != 2) { + fprintf(stderr, "Error: Invalid arguments.\n"); + print_usage(); + return 1; + } + + uint8_t auth_key[16]; + + for (size_t i=0; argv[1][i+0] != '\0' && argv[1][i+1] != '\0' && i/2<sizeof(auth_key); i+= 2) { + char buf[3] = { argv[1][i+0], argv[1][i+1], 0}; + char *endptr; + auth_key[i/2] = strtoul(buf, &endptr, 16); + if (!endptr || *endptr != '\0') { + fprintf(stderr, "Invalid authkey\n"); + return 1; + } + } + + printf("rc=%d\n", oob_message_received(auth_key)); + + return 0; +} diff --git a/tools/crypto_test_runner.py b/tools/crypto_test_runner.py new file mode 100644 index 0000000..34c8b59 --- /dev/null +++ b/tools/crypto_test_runner.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +import subprocess +from os import path +import binascii +import re + +import presig_gen + +def do_test(domain, value, height, root_key, binary, expect_fail=False): + auth = presig_gen.gen_at_height(domain, value, height, root_key) + auth = binascii.hexlify(auth).decode() + + output = subprocess.check_output([binary, auth]) + *lines, rc_line = output.decode().splitlines() + rc = int(re.match('^rc=(\d+)$', rc_line).group(1)) + assert expect_fail == (rc == 0) + +def run_tests(root_key, max_height, binary): + for domain, value in { + 'all': 'all', + 'vendor': presig_gen.TEST_VENDOR, + 'series': presig_gen.TEST_SERIES, + 'country': presig_gen.TEST_COUNTRY, + 'region': presig_gen.TEST_REGION, + }.items(): + for height in range(max_height): + do_test(domain, value, height, root_key, binary) + do_test(domain, 'fail', height, root_key, binary, expect_fail=True) + do_test('fail', 'fail', height, root_key, binary, expect_fail=True) + do_test('', '', height, root_key, binary, expect_fail=True) + do_test(domain, value, max_height, root_key, binary, expect_fail=True) + do_test(domain, value, max_height+1, root_key, binary, expect_fail=True) + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('keyfile', help='Root key file') + parser.add_argument('max_height', type=int, default=8, nargs='?', help='Height of generated prekeys') + default_binary = path.abspath(path.join(path.dirname(__file__), '../build/tools/crypto_test')) + parser.add_argument('binary', default=default_binary, nargs='?', help='crypto_test binary to use') + args = parser.parse_args() + + with open(args.keyfile, 'r') as f: + root_key = binascii.unhexlify(f.read().strip()) + + run_tests(root_key, args.max_height, args.binary) diff --git a/tools/cwt_wavelet_header_gen.py b/tools/cwt_wavelet_header_gen.py new file mode 100644 index 0000000..8be785b --- /dev/null +++ b/tools/cwt_wavelet_header_gen.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 + +import textwrap + +import scipy.signal as sig +import numpy as np + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('n', type=int, help='Window size') + parser.add_argument('w', type=float, help='Wavelet width') + parser.add_argument('-v', '--variable', default='cwt_ricker_table', help='Name for alias variable pointing to generated wavelet LUT') + args = parser.parse_args() + + print(f'/* CWT Ricker wavelet LUT for {args.n} sample window of width {args.w}. */') + varname = f'cwt_ricker_{args.n}_window_{str(args.w).replace(".", "F")}' + print(f'const float {varname}[{args.n}] = {{') + + win = sig.ricker(args.n, args.w) + par = ' '.join(f'{f:>015.12e}f,' for f in win) + print(textwrap.fill(par, + initial_indent=' '*4, subsequent_indent=' '*4, + width=120, + replace_whitespace=False, drop_whitespace=False)) + print('};') + print() + print(f'const float * const {args.variable} __attribute__((weak)) = {varname};') + diff --git a/tools/dsss_demod_test.c b/tools/dsss_demod_test.c new file mode 100644 index 0000000..f7df111 --- /dev/null +++ b/tools/dsss_demod_test.c @@ -0,0 +1,109 @@ + +#include <stdint.h> +#include <math.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/fcntl.h> + +#include "dsss_demod.h" + +void handle_dsss_received(symbol_t data[static TRANSMISSION_SYMBOLS]) { + printf("data sequence received: [ "); + for (size_t i=0; i<TRANSMISSION_SYMBOLS; i++) { + //printf("%+3d", ((data[i]&1) ? 1 : -1) * (data[i]>>1)); + printf("%2d", data[i]); + if (i+1 < TRANSMISSION_SYMBOLS) + printf(", "); + } + printf(" ]\n"); +} + +void print_usage() { + fprintf(stderr, "Usage: dsss_demod_test [test_data.bin] [optional recording channel number]\n"); +} + +int main(int argc, char **argv) { + if (argc != 2 && argc != 3) { + fprintf(stderr, "Error: Invalid arguments.\n"); + print_usage(); + return 1; + } + + int fd = open(argv[1], O_RDONLY); + struct stat st; + if (fstat(fd, &st)) { + fprintf(stderr, "Error querying test data file size: %s\n", strerror(errno)); + return 2; + } + + if (st.st_size < 0 || st.st_size > 10000000) { + fprintf(stderr, "Error reading test data: too much test data (size=%zd)\n", st.st_size); + return 2; + } + + if (st.st_size % sizeof(float) != 0) { + fprintf(stderr, "Error reading test data: file size is not divisible by %zd (size=%zd)\n", sizeof(float), st.st_size); + return 2; + } + + char *buf = malloc(st.st_size); + if (!buf) { + fprintf(stderr, "Error allocating memory"); + return 2; + } + + int record_channel = -1; + if (argc == 3) { + char *endptr; + record_channel = strtoul(argv[2], &endptr, 10); + if (!endptr || *endptr != '\0') { + fprintf(stderr, "Invalid channel number \"%s\"\n", argv[2]); + return 1; + } + } + + if (record_channel != -1) + fprintf(stderr, "Reading %zd samples test data...", st.st_size/sizeof(float)); + ssize_t nread = 0; + while (nread < st.st_size) { + ssize_t rc = read(fd, buf + nread, st.st_size - nread); + + if (rc == -EINTR || rc == -EAGAIN) + continue; + + if (rc < 0) { + fprintf(stderr, "\nError reading test data: %s\n", strerror(errno)); + return 2; + } + + if (rc == 0) { + fprintf(stderr, "\nError reading test data: Unexpected end of file\n"); + return 2; + } + + nread += rc; + } + if (record_channel != -1) + fprintf(stderr, " done.\n"); + + const size_t n_samples = st.st_size / sizeof(float); + float *buf_f = (float *)buf; + + if (record_channel != -1) + fprintf(stderr, "Starting simulation.\n"); + + struct dsss_demod_state demod; + dsss_demod_init(&demod); + for (size_t i=0; i<n_samples; i++) { + //fprintf(stderr, "Iteration %zd/%zd\n", i, n_samples); + dsss_demod_step(&demod, buf_f[i], i); + } + + free(buf); + return 0; +} diff --git a/tools/dsss_demod_test_runner.py b/tools/dsss_demod_test_runner.py new file mode 100644 index 0000000..d3c3cfc --- /dev/null +++ b/tools/dsss_demod_test_runner.py @@ -0,0 +1,241 @@ +#!/usr/bin/env python3 + +import os +import sys +from os import path +import subprocess +import json +from collections import namedtuple, defaultdict +from tqdm import tqdm +import uuid +import multiprocessing +import sqlite3 +import time +from urllib.parse import urlparse +import tempfile +import itertools + +import numpy as np +np.set_printoptions(linewidth=240) + +from dsss_demod_test_waveform_gen import load_noise_gen, modulate as dsss_modulate + + +def build_test_binary(nbits, thf, decimation, symbols, cachedir): + build_id = str(uuid.uuid4()) + builddir = path.join(cachedir, build_id) + os.mkdir(builddir) + + cwd = path.join(path.dirname(__file__), '..') + + env = os.environ.copy() + env['BUILDDIR'] = path.abspath(builddir) + env['DSSS_GOLD_CODE_NBITS'] = str(nbits) + env['DSSS_DECIMATION'] = str(decimation) + env['DSSS_THRESHOLD_FACTOR'] = str(thf) + env['DSSS_WAVELET_WIDTH'] = str(0.73 * decimation) + env['DSSS_WAVELET_LUT_SIZE'] = str(10 * decimation) + env['TRANSMISSION_SYMBOLS'] = str(symbols) + + with open(path.join(builddir, 'make_stdout.txt'), 'w') as stdout,\ + open(path.join(builddir, 'make_stderr.txt'), 'w') as stderr: + subprocess.run(['make', 'clean', os.path.abspath(path.join(builddir, 'tools/dsss_demod_test'))], + env=env, cwd=cwd, check=True, stdout=stdout, stderr=stderr) + + return build_id + +def sequence_matcher(test_data, decoded, max_shift=3): + match_result = [] + for shift in range(-max_shift, max_shift): + failures = -shift if shift < 0 else 0 # we're skipping the first $shift symbols + a = test_data if shift > 0 else test_data[-shift:] + b = decoded if shift < 0 else decoded[shift:] + for i, (ref, found) in enumerate(itertools.zip_longest(a, b)): + if ref is None: # end of signal + break + if ref != found: + failures += 1 + match_result.append(failures) + failures = min(match_result) + return failures/len(test_data) + +ResultParams = namedtuple('ResultParams', ['nbits', 'thf', 'decimation', 'symbols', 'seed', 'amplitude', 'background']) + +def run_test(seed, amplitude_spec, background, nbits, decimation, symbols, thfs, lookup_binary, cachedir): + noise_gen, noise_params = load_noise_gen(background) + + test_data = np.random.RandomState(seed=seed).randint(0, 2 * (2**nbits), symbols) + + signal = np.repeat(dsss_modulate(test_data, nbits) * 2.0 - 1, decimation) + # We're re-using the seed here. This is not a problem. + noise = noise_gen(seed, len(signal), *noise_params) + amplitudes = amplitude_spec[0] * 10 ** np.linspace(0, amplitude_spec[1], amplitude_spec[2]) + # DEBUG + my_pid = multiprocessing.current_process().pid + wql = len(amplitudes) * len(thfs) + print(f'[{my_pid}] starting, got workqueue of length {wql}') + i = 0 + # Map lsb to sign to match test program + # test_data = (test_data>>1) * (2*(test_data&1) - 1) + # END DEBUG + + output = [] + for amp in amplitudes: + with tempfile.NamedTemporaryFile(dir=cachedir) as f: + waveform = signal*amp + noise + f.write(waveform.astype('float32').tobytes()) + f.flush() + # DEBUG + fcopy = f'/tmp/test-{path.basename(f.name)}' + import shutil + shutil.copy(f.name, fcopy) + # END DEBUG + + for thf in thfs: + rpars = ResultParams(nbits, thf, decimation, symbols, seed, amp, background) + cmdline = [lookup_binary(nbits, thf, decimation, symbols), f.name] + # DEBUG + starttime = time.time() + # END DEBUG + try: + proc = subprocess.run(cmdline, stdout=subprocess.PIPE, encoding='utf-8', check=True, timeout=300) + + lines = proc.stdout.splitlines() + matched = [ l.partition('[')[2].partition(']')[0] + for l in lines if l.strip().startswith('data sequence received:') ] + matched = [ [ int(elem) for elem in l.split(',') ] for l in matched ] + + ser = min(sequence_matcher(test_data, match) for match in matched) if matched else None + output.append((rpars, ser)) + # DEBUG + #print(f'[{my_pid}] ran {i}/{wql}: time={time.time() - starttime}\n {ser=}\n {rpars}\n {" ".join(cmdline)}\n {fcopy}', flush=True) + i += 1 + # END DEBUG + + except subprocess.TimeoutExpired: + output.append((rpars, None)) + # DEBUG + print(f'[{my_pid}] ran {i}/{wql}: Timeout!\n {rpars}\n {" ".join(cmdline)}\n {fcopy}', flush=True) + i += 1 + # END DEBUG + print(f'[{my_pid}] finished.') + return output + +def parallel_generator(db, table, columns, builder, param_list, desc, context={}, params_mapper=lambda *args: args, + disable_cache=False): + with multiprocessing.Pool(multiprocessing.cpu_count()) as pool: + with db as conn: + jobs = [] + for params in param_list: + found_res = conn.execute( + f'SELECT result FROM {table} WHERE ({",".join(columns)}) = ({",".join("?"*len(columns))})', + params_mapper(*params)).fetchone() + + if found_res and not disable_cache: + yield params, json.loads(*found_res) + + else: + jobs.append((params, pool.apply_async(builder, params, context))) + + pool.close() + print('Using', len(param_list) - len(jobs), 'cached jobs', flush=True) + with tqdm(total=len(jobs), desc=desc) as tq: + for i, (params, res) in enumerate(jobs): + # DEBUG + print('Got result', i, params, res) + # END DEBUG + tq.update(1) + result = res.get() + with db as conn: + conn.execute(f'INSERT INTO {table} VALUES ({"?,"*len(params)}?,?)', + (*params_mapper(*params), json.dumps(result), timestamp())) + yield params, result + pool.join() + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('-d', '--dump', help='Write results to JSON file') + parser.add_argument('-c', '--cachedir', default='dsss_test_cache', help='Directory to store build output and data in') + parser.add_argument('-n', '--no-cache', action='store_true', help='Disable result cache') + parser.add_argument('-b', '--batches', type=int, default=1, help='Number of batches to split the computation into') + parser.add_argument('-i', '--index', type=int, default=0, help='Batch index to compute') + parser.add_argument('-p', '--prepare', action='store_true', help='Prepare mode: compile runners, then exit.') + args = parser.parse_args() + + DecoderParams = namedtuple('DecoderParams', ['nbits', 'thf', 'decimation', 'symbols']) +# dec_paramses = [ DecoderParams(nbits=nbits, thf=thf, decimation=decimation, symbols=20) +# for nbits in [5, 6] +# for thf in [4.5, 4.0, 5.0] +# for decimation in [10, 5, 22] ] + dec_paramses = [ DecoderParams(nbits=nbits, thf=thf, decimation=decimation, symbols=100) + for nbits in [5, 6] + for thf in [3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0, 6.5, 7.0, 7.5, 8.0, 8.5, 9.0, 9.5, 10.0] + for decimation in [1, 2, 3, 4, 5, 6, 9, 10, 11, 12, 16, 22, 30, 40, 50] ] +# dec_paramses = [ DecoderParams(nbits=nbits, thf=thf, decimation=decimation, symbols=100) +# for nbits in [5, 6, 7, 8] +# for thf in [1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0, 6.5, 7.0, 7.5, 8.0, 8.5, 9.0, 9.5, 10.0] +# for decimation in [1, 2, 3, 4, 5, 6, 9, 10, 11, 12, 16, 22, 30, 40, 50] ] + + build_cache_dir = path.join(args.cachedir, 'builds') + data_cache_dir = path.join(args.cachedir, 'data') + os.makedirs(build_cache_dir, exist_ok=True) + os.makedirs(data_cache_dir, exist_ok=True) + + build_db = sqlite3.connect(path.join(args.cachedir, 'build_db.sqlite3')) + build_db.execute('CREATE TABLE IF NOT EXISTS builds (nbits, thf, decimation, symbols, result, timestamp)') + timestamp = lambda: int(time.time()*1000) + + builds = dict(parallel_generator(build_db, table='builds', columns=['nbits', 'thf', 'decimation', 'symbols'], + builder=build_test_binary, param_list=dec_paramses, desc='Building decoders', + context=dict(cachedir=build_cache_dir))) + print('Done building decoders.') + if args.prepare: + sys.exit(0) + + GeneratorParams = namedtuple('GeneratorParams', ['seed', 'amplitude_spec', 'background']) + gen_params = [ GeneratorParams(rep, (5e-3, 1, 5), background) + #GeneratorParams(rep, (0.05e-3, 3.5, 50), background) + for rep in range(50) + for background in ['meas://fmeas_export_ocxo_2day.bin', 'synth://grid_freq_psd_spl_108pt.json'] ] +# gen_params = [ GeneratorParams(rep, (5e-3, 1, 5), background) +# for rep in range(1) +# for background in ['meas://fmeas_export_ocxo_2day.bin'] ] + + data_db = sqlite3.connect(path.join(args.cachedir, 'data_db.sqlite3')) + data_db.execute('CREATE TABLE IF NOT EXISTS waveforms' + '(seed, amplitude_spec, background, nbits, decimation, symbols, thresholds, result, timestamp)') + + 'SELECT FROM waveforms GROUP BY (amplitude_spec, background, nbits, decimation, symbols, thresholds, result)' + + dec_param_groups = defaultdict(lambda: []) + for nbits, thf, decimation, symbols in dec_paramses: + dec_param_groups[(nbits, decimation, symbols)].append(thf) + waveform_params = [ (*gp, *dp, thfs) for gp in gen_params for dp, thfs in dec_param_groups.items() ] + print(f'Generated {len(waveform_params)} parameter sets') + + # Separate out our batch + waveform_params = waveform_params[args.index::args.batches] + + def lookup_binary(*params): + return path.join(build_cache_dir, builds[tuple(params)], 'tools/dsss_demod_test') + + def params_mapper(seed, amplitude_spec, background, nbits, decimation, symbols, thresholds): + amplitude_spec = ','.join(str(x) for x in amplitude_spec) + thresholds = ','.join(str(x) for x in thresholds) + return seed, amplitude_spec, background, nbits, decimation, symbols, thresholds + + results = [] + for _params, chunk in parallel_generator(data_db, 'waveforms', + ['seed', 'amplitude_spec', 'background', 'nbits', 'decimation', 'symbols', 'thresholds'], + params_mapper=params_mapper, + builder=run_test, + param_list=waveform_params, desc='Simulating demodulation', + context=dict(cachedir=data_cache_dir, lookup_binary=lookup_binary), + disable_cache=args.no_cache): + results += chunk + + if args.dump: + with open(args.dump, 'w') as f: + json.dump(results, f) + diff --git a/tools/dsss_demod_test_waveform_gen.py b/tools/dsss_demod_test_waveform_gen.py new file mode 100644 index 0000000..414c553 --- /dev/null +++ b/tools/dsss_demod_test_waveform_gen.py @@ -0,0 +1,86 @@ + +from os import path +import json +import functools + +import numpy as np +import numbers +import math +from scipy import signal as sig +import scipy.fftpack + +sampling_rate = 10 # sp/s + +# From https://github.com/mubeta06/python/blob/master/signal_processing/sp/gold.py +preferred_pairs = {5:[[2],[1,2,3]], 6:[[5],[1,4,5]], 7:[[4],[4,5,6]], + 8:[[1,2,3,6,7],[1,2,7]], 9:[[5],[3,5,6]], + 10:[[2,5,9],[3,4,6,8,9]], 11:[[9],[3,6,9]]} + +def gen_gold(seq1, seq2): + gold = [seq1, seq2] + for shift in range(len(seq1)): + gold.append(seq1 ^ np.roll(seq2, -shift)) + return gold + +def gold(n): + n = int(n) + if not n in preferred_pairs: + raise KeyError('preferred pairs for %s bits unknown' % str(n)) + t0, t1 = preferred_pairs[n] + (seq0, _st0), (seq1, _st1) = sig.max_len_seq(n, taps=t0), sig.max_len_seq(n, taps=t1) + return gen_gold(seq0, seq1) + +def modulate(data, nbits=5): + # 0, 1 -> -1, 1 + mask = np.array(gold(nbits))*2 - 1 + + sel = mask[data>>1] + data_lsb_centered = ((data&1)*2 - 1) + + signal = (np.multiply(sel, np.tile(data_lsb_centered, (2**nbits-1, 1)).T).flatten() + 1) // 2 + return np.hstack([ np.zeros(len(mask)), signal, np.zeros(len(mask)) ]) + +def load_noise_meas_params(capture_file): + with open(capture_file, 'rb') as f: + meas_data = np.copy(np.frombuffer(f.read(), dtype='float32')) + meas_data -= np.mean(meas_data) + return (meas_data,) + +def mains_noise_measured(seed, n, meas_data): + last_valid = len(meas_data) - n + st = np.random.RandomState(seed) + start = st.randint(last_valid) + return meas_data[start:start+n] + 50.00 + +def load_noise_synth_params(specfile): + with open(specfile) as f: + d = json.load(f) + return {'spl_x': np.linspace(*d['x_spec']), + 'spl_N': d['x_spec'][2], + 'psd_spl': (d['t'], d['c'], d['k']) } + +def mains_noise_synthetic(seed, n, psd_spl, spl_N, spl_x): + st = np.random.RandomState(seed) + noise = st.normal(size=spl_N) * 2 + spec = scipy.fftpack.fft(noise) **2 + + spec *= np.exp(scipy.interpolate.splev(spl_x, psd_spl)) + + spec **= 1/2 + + renoise = scipy.fftpack.ifft(spec) + return renoise[10000:][:n] + 50.00 + +@functools.lru_cache() +def load_noise_gen(url): + schema, refpath = url.split('://') + if not path.isabs(refpath): + refpath = path.abspath(path.join(path.dirname(__file__), refpath)) + + if schema == 'meas': + return mains_noise_measured, load_noise_meas_params(refpath) + elif schema == 'synth': + return mains_noise_synthetic, load_noise_synth_params(refpath) + else: + raise ValueError('Invalid schema', schema) + diff --git a/tools/e2e_test.c b/tools/e2e_test.c new file mode 100644 index 0000000..935f70d --- /dev/null +++ b/tools/e2e_test.c @@ -0,0 +1,111 @@ + +#include <stdint.h> +#include <math.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/fcntl.h> + +#include "freq_meas.h" +#include "dsss_demod.h" + +typedef uint16_t adc_data_t; + +void handle_dsss_received(uint8_t data[static TRANSMISSION_SYMBOLS]) { + printf("data sequence received: [ "); + for (size_t i=0; i<TRANSMISSION_SYMBOLS; i++) { + printf("%+3d", ((data[i]&1) ? 1 : -1) * (data[i]>>1)); + if (i+1 < TRANSMISSION_SYMBOLS) + printf(", "); + } + printf(" ]\n"); +} + +void print_usage(void); +void print_usage() { + fprintf(stderr, "Usage: e2e_test [emulated_adc_data.bin]\n"); +} + +int main(int argc, char **argv) { + if (argc != 2) { + fprintf(stderr, "Error: Invalid arguments.\n"); + print_usage(); + return 1; + } + + int fd = open(argv[1], O_RDONLY); + struct stat st; + if (fstat(fd, &st)) { + fprintf(stderr, "Error querying test data file size: %s\n", strerror(errno)); + return 2; + } + + if (st.st_size < 0 || st.st_size > 100000000) { + fprintf(stderr, "Error reading test data: too much test data (size=%zd)\n", st.st_size); + return 2; + } + + if (st.st_size % sizeof(adc_data_t) != 0) { + fprintf(stderr, "Error reading test data: file size is not divisible by %zd (size=%zd)\n", sizeof(adc_data_t), st.st_size); + return 2; + } + + char *buf = malloc(st.st_size); + if (!buf) { + fprintf(stderr, "Error allocating memory"); + return 2; + } + + const size_t n_samples = st.st_size / sizeof(adc_data_t); + fprintf(stderr, "Reading %zd samples test data...", n_samples); + ssize_t nread = 0; + while (nread < st.st_size) { + ssize_t rc = read(fd, buf + nread, st.st_size - nread); + + if (rc == -EINTR || rc == -EAGAIN) + continue; + + if (rc < 0) { + fprintf(stderr, "\nError reading test data: %s\n", strerror(errno)); + return 2; + } + + if (rc == 0) { + fprintf(stderr, "\nError reading test data: Unexpected end of file\n"); + return 2; + } + + nread += rc; + } + fprintf(stderr, " done. Read %zd bytes.\n", nread); + + adc_data_t *buf_d = (adc_data_t *)buf; + + struct dsss_demod_state demod; + dsss_demod_init(&demod); + + fprintf(stderr, "Starting simulation.\n"); + size_t iterations = (n_samples-FMEAS_FFT_LEN)/(FMEAS_FFT_LEN/2); + for (size_t i=0; i<iterations; i++) { + + /* + fprintf(stderr, "Iteration %zd/%zd\n", i, iterations); + */ + float res = NAN; + int rc = adc_buf_measure_freq(buf_d + i*(FMEAS_FFT_LEN/2), &res); + if (rc) + printf("ERROR: Simulation error in iteration %zd at position %zd: %d\n", i, i*(FMEAS_FFT_LEN/2), rc); + + dsss_demod_step(&demod, res, i); + /* + printf("%09zd %12f\n", i, res); + */ + } + + free(buf); + return 0; +} diff --git a/tools/fft_window_header_gen.py b/tools/fft_window_header_gen.py new file mode 100644 index 0000000..7df2ee3 --- /dev/null +++ b/tools/fft_window_header_gen.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 + +import textwrap + +import scipy.signal as sig +import numpy as np + +WINDOW_TYPES = [ + 'boxcar', + 'triang', + 'blackman', + 'hamming', + 'hann', + 'bartlett', + 'flattop', + 'parzen', + 'bohman', + 'blackmanharris', + 'nuttall', + 'barthann', + 'kaiser', + 'gaussian', + 'general_gaussian', + 'slepian', + 'dpss', + 'chebwin', + 'exponential', + 'tukey', + ] + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('window', choices=WINDOW_TYPES, help='Type of window function to use') + parser.add_argument('n', type=int, help='Width of window in samples') + parser.add_argument('window_args', nargs='*', type=float, + help='''Window argument(s) if required. See https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.get_window.html#scipy.signal.get_window for details.''') + parser.add_argument('-v', '--variable', default='fft_window_table', help='Name for alias variable pointing to generated window') + args = parser.parse_args() + + print(f'/* FTT window table for {args.n} sample {args.window} window.') + if args.window_args: + print(f' * Window arguments were: ({" ,".join(str(arg) for arg in args.window_args)})') + print(f' */') + winargs = ''.join(f'_{arg:.4g}'.replace('.', 'F') for arg in args.window_args) + varname = f'fft_{args.n}_window_{args.window}{winargs}' + print(f'const float {varname}[{args.n}] = {{') + + win = sig.get_window(args.window if not args.window_args else (args.window, *args.window_args), + Nx=args.n, fftbins=True) + par = ' '.join(f'{f:>013.8g},' for f in win) + print(textwrap.fill(par, + initial_indent=' '*4, subsequent_indent=' '*4, + width=120, + replace_whitespace=False, drop_whitespace=False)) + print('};') + print() + print(f'const float * const {args.variable} __attribute__((weak)) = {varname};') + diff --git a/tools/fmeas_export_ocxo_2day.bin b/tools/fmeas_export_ocxo_2day.bin Binary files differnew file mode 100644 index 0000000..c0cd8a8 --- /dev/null +++ b/tools/fmeas_export_ocxo_2day.bin diff --git a/tools/freq_meas_test.c b/tools/freq_meas_test.c new file mode 100644 index 0000000..e2900ad --- /dev/null +++ b/tools/freq_meas_test.c @@ -0,0 +1,106 @@ + +#include <stdint.h> +#include <math.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/fcntl.h> + +#include "freq_meas.h" + +void print_usage(void); + +void print_usage() { + fprintf(stderr, "Usage: freq_meas_test [test_data.bin]\n"); +} + +int main(int argc, char **argv) { + if (argc != 2) { + fprintf(stderr, "Error: Invalid arguments.\n"); + print_usage(); + return 1; + } + + int fd = open(argv[1], O_RDONLY); + struct stat st; + if (fstat(fd, &st)) { + fprintf(stderr, "Error querying test data file size: %s\n", strerror(errno)); + return 2; + } + + if (st.st_size < 0 || st.st_size > 1000000) { + fprintf(stderr, "Error reading test data: too much test data (size=%zd)\n", st.st_size); + return 2; + } + + if (st.st_size % sizeof(float) != 0) { + fprintf(stderr, "Error reading test data: file size is not divisible by %zd (size=%zd)\n", sizeof(float), st.st_size); + return 2; + } + + char *buf = malloc(st.st_size); + if (!buf) { + fprintf(stderr, "Error allocating memory"); + return 2; + } + + fprintf(stderr, "Reading %zd samples test data...", st.st_size/sizeof(float)); + ssize_t nread = 0; + while (nread < st.st_size) { + ssize_t rc = read(fd, buf + nread, st.st_size - nread); + + if (rc == -EINTR || rc == -EAGAIN) + continue; + + if (rc < 0) { + fprintf(stderr, "\nError reading test data: %s\n", strerror(errno)); + return 2; + } + + if (rc == 0) { + fprintf(stderr, "\nError reading test data: Unexpected end of file\n"); + return 2; + } + + nread += rc; + } + fprintf(stderr, " done.\n"); + + const size_t n_samples = st.st_size / sizeof(float); + float *buf_f = (float *)buf; + + int16_t *sim_adc_buf = calloc(sizeof(int16_t), n_samples); + if (!sim_adc_buf) { + fprintf(stderr, "Error allocating memory\n"); + return 2; + } + + fprintf(stderr, "Converting and truncating test data..."); + for (size_t i=0; i<n_samples; i++) + /* Note on scaling: We can't simply scale by 0x8000 (1/2 full range) here. Our test data is nominally 1Vp-p but + * certain tests such as the interharmonics one can have some samples exceeding that range. */ + sim_adc_buf[i] = buf_f[i] * (0x4000-1); + fprintf(stderr, " done.\n"); + + fprintf(stderr, "Starting simulation.\n"); + + size_t iterations = (n_samples-FMEAS_FFT_LEN)/(FMEAS_FFT_LEN/2); + for (size_t i=0; i<iterations; i++) { + + fprintf(stderr, "Iteration %zd/%zd\n", i, iterations); + float res = NAN; + int rc = adc_buf_measure_freq(sim_adc_buf + i*(FMEAS_FFT_LEN/2), &res); + if (rc) + printf("ERROR: Simulation error in iteration %zd at position %zd: %d\n", i, i*(FMEAS_FFT_LEN/2), rc); + + printf("%09zd %12f\n", i, res); + } + + free(buf); + free(sim_adc_buf); + return 0; +} diff --git a/tools/freq_meas_test_runner.py b/tools/freq_meas_test_runner.py new file mode 100644 index 0000000..779922a --- /dev/null +++ b/tools/freq_meas_test_runner.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 + +import os +from os import path +import subprocess +import json + +import numpy as np +np.set_printoptions(linewidth=240) + + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser() + parser.add_argument(metavar='test_data_directory', dest='dir', help='Directory with test data .bin files') + default_binary = path.abspath(path.join(path.dirname(__file__), '../build/tools/freq_meas_test')) + parser.add_argument(metavar='test_binary', dest='binary', nargs='?', default=default_binary) + parser.add_argument('-d', '--dump', help='Write raw measurements to JSON file') + args = parser.parse_args() + + bin_files = [ path.join(args.dir, d) for d in os.listdir(args.dir) if d.lower().endswith('.bin') ] + + savedata = {} + for p in bin_files: + output = subprocess.check_output([args.binary, p], stderr=subprocess.DEVNULL) + measurements = np.array([ float(value) for _offset, value in [ line.split() for line in output.splitlines() ] ]) + savedata[p] = list(measurements) + + # Cut off first and last sample for mean and RMS calculations as these show boundary effects. + measurements = measurements[1:-1] + mean = np.mean(measurements) + rms = np.sqrt(np.mean(np.square(measurements - mean))) + + print(f'{path.basename(p):<60}: mean={mean:<8.4f}Hz rms={rms*1000:.3f}mHz') + + if args.dump: + with open(args.dump, 'w') as f: + json.dump(savedata, f) + diff --git a/tools/gold_code_header_gen.py b/tools/gold_code_header_gen.py new file mode 100644 index 0000000..fa98fce --- /dev/null +++ b/tools/gold_code_header_gen.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 + +import sys +import math +import textwrap +import contextlib + +import numpy as np +import scipy.signal as sig + +# From https://github.com/mubeta06/python/blob/master/signal_processing/sp/gold.py +preferred_pairs = {5:[[2],[1,2,3]], 6:[[5],[1,4,5]], 7:[[4],[4,5,6]], + 8:[[1,2,3,6,7],[1,2,7]], 9:[[5],[3,5,6]], + 10:[[2,5,9],[3,4,6,8,9]], 11:[[9],[3,6,9]]} + +def gen_gold(seq1, seq2): + gold = [seq1, seq2] + for shift in range(len(seq1)): + gold.append(seq1 ^ np.roll(seq2, -shift)) + return gold + +def gold(n): + n = int(n) + if not n in preferred_pairs: + raise KeyError('preferred pairs for %s bits unknown' % str(n)) + t0, t1 = preferred_pairs[n] + (seq0, _st0), (seq1, _st1) = sig.max_len_seq(n, taps=t0), sig.max_len_seq(n, taps=t1) + return gen_gold(seq0, seq1) + +@contextlib.contextmanager +def print_include_guards(macro_name): + print(f'#ifndef {macro_name}') + print(f'#define {macro_name}') + yield + print(f'#endif /* {macro_name} */') + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser(add_help=False) + parser.add_argument('n', type=int, choices=preferred_pairs, help='bit width of shift register. Generate 2**n + 1 sequences of length 2**n - 1.') + parser.add_argument('-v', '--variable', default='gold_code_table', help='Name for weak alias of generated table') + parser.add_argument('-h', '--header', action='store_true', help='Generate header file') + parser.add_argument('-c', '--source', action='store_true', help='Generate table source file') + args = parser.parse_args() + + if not args.header != args.source: + print('Exactly one of --header and --source must be given.', file=sys.stderr) + sys.exit(1) + + nbytes = math.ceil((2**args.n-1)/8) + + if args.source: + print('/* THIS IS A GENERATED FILE. DO NOT EDIT! */') + print('#include <unistd.h>') + print('#include <stdint.h>') + print() + print(f'/* {args.n} bit gold sequences: {2**args.n+1} sequences of length {2**args.n-1} bit.') + print(f' *') + print(f' * Each code is packed left-aligned into {nbytes} bytes in big-endian byte order.') + print(f' */') + print(f'const uint8_t {args.variable}[{2**args.n+1}][{nbytes}] = {{') + for i, code in enumerate(gold(args.n)): + par = '{' + ' '.join(f'0x{d:02x},' for d in np.packbits(code)) + f'}}, /* {i: 3d} "{"".join(str(x) for x in code)}" */' + print(textwrap.fill(par, initial_indent=' '*4, subsequent_indent=' '*4, width=120)) + print('};') + print() + else: + print('/* THIS IS A GENERATED FILE. DO NOT EDIT! */') + with print_include_guards(f'__GOLD_CODE_GENERATED_HEADER_{args.n}__'): + print(f'extern const uint8_t {args.variable}[{2**args.n+1}][{nbytes}];') diff --git a/tools/grid_freq_psd_spl_108pt.json b/tools/grid_freq_psd_spl_108pt.json new file mode 100644 index 0000000..5a0ff41 --- /dev/null +++ b/tools/grid_freq_psd_spl_108pt.json @@ -0,0 +1 @@ +{"x_spec": [3.2595692805152726e-05, 5.0, 613575], "t": [3.2595692805152726e-05, 3.2595692805152726e-05, 3.2595692805152726e-05, 3.2595692805152726e-05, 0.0001423024947075771, 0.00015800362803968106, 0.00017543716661470822, 0.00019479425764873777, 0.0002162871388378975, 0.00024015146540428407, 0.00026664889389955537, 0.00029606995109590574, 0.00032873721941990017, 0.0003650088738553592, 0.0004052826090950758, 0.00045000000000000004, 0.000499651343175437, 0.0005547810327489297, 0.0006159935292916862, 0.0006839599873288199, 0.0007594256141046668, 0.0008432178402871724, 0.0009362553921977272, 0.0010395583650374223, 0.0011542594075560205, 0.001281616140796111, 0.0014230249470757708, 0.001580036280396809, 0.0017543716661470824, 0.0019479425764873776, 0.002162871388378975, 0.0024015146540428403, 0.002666488938995554, 0.002960699510959057, 0.0032873721941990056, 0.0036500887385535925, 0.004052826090950754, 0.0045000000000000005, 0.00499651343175437, 0.005547810327489296, 0.006159935292916869, 0.0068395998732882, 0.007594256141046669, 0.008432178402871724, 0.009362553921977271, 0.010395583650374221, 0.011542594075560205, 0.012816161407961109, 0.014230249470757707, 0.01580036280396809, 0.017543716661470823, 0.01947942576487376, 0.02162871388378975, 0.024015146540428405, 0.026664889389955565, 0.02960699510959057, 0.03287372194199005, 0.036500887385535925, 0.04052826090950754, 0.045, 0.0499651343175437, 0.05547810327489296, 0.06159935292916863, 0.06839599873288206, 0.07594256141046668, 0.08432178402871732, 0.09362553921977272, 0.10395583650374222, 0.11542594075560206, 0.12816161407961107, 0.14230249470757705, 0.15800362803968088, 0.1754371666147082, 0.1947942576487376, 0.21628713883789774, 0.24015146540428406, 0.26664889389955565, 0.2960699510959057, 0.32873721941990053, 0.36500887385535924, 0.40528260909507535, 0.45, 0.499651343175437, 0.5547810327489296, 0.6159935292916868, 0.6839599873288206, 0.7594256141046669, 0.8432178402871732, 0.9362553921977271, 1.0395583650374223, 1.1542594075560206, 1.2816161407961109, 1.4230249470757708, 1.5800362803968104, 1.7543716661470823, 1.9479425764873777, 2.162871388378975, 2.4015146540428405, 2.6664889389955535, 2.960699510959057, 3.287372194199002, 3.6500887385535927, 4.052826090950758, 4.5, 5.0, 5.0, 5.0, 5.0], "c": [0.7720161468716866, -0.5547528253056444, 0.30706059086000753, 0.19422577014134906, -1.1954636661840032, 0.9215976941641111, -0.6668136393976918, -1.341269161156733, -0.16311330594842666, -1.7639636752234251, -1.238385544822954, -0.32649555618555554, -0.03086589610280171, -2.358195657381619, -0.5759152419849985, 0.1892225800004134, -1.8122889670546236, -0.8109120798216202, -0.5500991736738969, -4.680192969256771, -2.8007700704649876, 0.16866469558571784, -1.1040811840849307, -3.0243574268705546, -4.018139927365795, -4.100581028618109, -0.556354762846191, -7.414377514669229, 1.36396325920194, -6.002559557058508, -2.2113451390305365, -4.578944771104116, -4.372644849632638, -3.945339124673235, -4.778747958903158, -2.370174137632325, -5.7372466088109295, -4.707506574819875, -4.834404729330929, -5.005244244061701, -5.82644896783577, -4.717966026411524, -6.146374820241562, -4.972788381244952, -5.854957092953355, -5.702174935205885, -6.222035857079607, -6.2128389666872, -6.212821706753751, -6.253599689326325, -6.681685577659057, -6.372364384360678, -6.771223202540934, -6.856809137231159, -6.986412256164045, -7.190466178818742, -7.577896455149433, -7.515731696006047, -7.598155006351761, -7.824526916149126, -8.141496591776512, -8.36794927682997, -8.80307396767114, -8.828816533544659, -9.357524260470413, -9.658130054343863, -10.005768472049466, -10.499801262514108, -11.028689820560558, -11.413688641742898, -11.906162042727946, -12.232342460719975, -12.438432746733596, -13.088338100203112, -12.308710772618745, -11.685074853925329, -11.397838681243094, -12.265219694936695, -13.600359694898529, -14.031425961884718, -12.236885080485473, -13.527508426900974, -13.698402018452601, -13.397911198962568, -14.144410560196603, -13.905769594095293, -14.410874830544122, -14.531727635304264, -14.59275291853806, -14.35404826562502, -14.58670053318149, -14.432515268864977, -14.363428024828353, -14.429222027493264, -14.73947634127499, -14.717315405960353, -14.678539669792505, -14.825278423641382, -14.80936417940876, -14.943375264882789, -14.680885181815674, -14.54841244844906, -14.634365225950589, -14.609444790868906, 0.0, 0.0, 0.0, 0.0], "k": 3}
\ No newline at end of file diff --git a/tools/hum_generator.py b/tools/hum_generator.py new file mode 100644 index 0000000..fbcabac --- /dev/null +++ b/tools/hum_generator.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python +# coding: utf-8 + +import binascii +import struct + +import numpy as np +import pydub + +from dsss_demod_test_waveform_gen import load_noise_gen, modulate as dsss_modulate + +np.set_printoptions(linewidth=240) + +def generate_noisy_signal( + test_data=32, + test_nbits=5, + test_decimation=10, + test_signal_amplitude=20e-3, + noise_level=10e-3, + noise_spec='synth://grid_freq_psd_spl_108pt.json', + seed=0): + + #test_data = np.random.RandomState(seed=0).randint(0, 2 * (2**test_nbits), test_duration) + #test_data = np.array([0, 1, 2, 3] * 50) + if isinstance(test_data, int): + test_data = np.array(range(test_data)) + + + signal = np.repeat(dsss_modulate(test_data, test_nbits) * 2.0 - 1, test_decimation) + + noise_gen, noise_params = load_noise_gen(noise_spec) + noise = noise_gen(seed, len(signal), **noise_params) + return np.absolute(noise + signal*test_signal_amplitude) + +def write_raw_frequencies_bin(outfile, **kwargs): + with open(outfile, 'wb') as f: + for x in generate_noisy_signal(**kwargs): + f.write(struct.pack('f', x)) + +def synthesize_sine(freqs, freqs_sampling_rate=10.0, output_sampling_rate=44100): + duration = len(freqs) / freqs_sampling_rate # seconds + afreq_out = np.interp(np.linspace(0, duration, int(duration*output_sampling_rate)), np.linspace(0, duration, len(freqs)), freqs) + return np.sin(np.cumsum(2*np.pi * afreq_out / output_sampling_rate)) + +def write_flac(filename, signal, sampling_rate=44100): + signal -= np.min(signal) + signal /= np.max(signal) + signal -= 0.5 + signal *= 2**16 - 1 + le_bytes = signal.astype(np.int16).tobytes() + seg = pydub.AudioSegment(data=le_bytes, sample_width=2, frame_rate=sampling_rate, channels=1) + seg.export(filename, format='flac') + +def write_synthetic_hum_flac(filename, output_sampling_rate=44100, freqs_sampling_rate=10.0, **kwargs): + signal = generate_noisy_signal(**kwargs) + print(signal) + write_flac(filename, synthesize_sine(signal, freqs_sampling_rate, output_sampling_rate), + sampling_rate=output_sampling_rate) + +def emulate_adc_signal(adc_bits=12, adc_offset=0.4, adc_amplitude=0.25, freq_sampling_rate=10.0, output_sampling_rate=1000, **kwargs): + signal = synthesize_sine(generate_noisy_signal(), freq_sampling_rate, output_sampling_rate) + signal = signal*adc_amplitude + adc_offset + smin, smax = np.min(signal), np.max(signal) + if smin < 0.0 or smax > 1.0: + raise UserWarning('Amplitude or offset too large: Signal out of bounds with min/max [{smin}, {smax}] of ADC range') + signal *= 2**adc_bits -1 + return signal + +def save_adc_signal(fn, signal, dtype=np.uint16): + with open(fn, 'wb') as f: + f.write(signal.astype(dtype).tobytes()) + +def write_emulated_adc_signal_bin(filename, **kwargs): + save_adc_signal(filename, emulate_adc_signal(**kwargs)) + +def hum_cmd(args): + write_synthetic_hum_flac(args.out_flac, + output_sampling_rate=args.audio_sampling_rate, + freqs_sampling_rate=args.frequency_sampling_rate, + test_data = np.array(list(binascii.unhexlify(args.data))), + test_nbits = args.symbol_bits, + test_decimation = args.decimation, + test_signal_amplitude = args.signal_level/1e3, + noise_level = args.noise_level/1e3, + noise_spec=args.noise_spec, + seed = args.random_seed) + + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser() + cmd_parser = parser.add_subparsers(required=True) + hum_parser = cmd_parser.add_parser('hum', help='Generated artificial modulated mains hum') + # output parameters + hum_parser.add_argument('-a', '--audio-sampling-rate', type=int, default=44100) + + # modulation parameters + hum_parser.add_argument('-f', '--frequency-sampling-rate', type=float, default=10.0*100/128) + hum_parser.add_argument('-b', '--symbol-bits', type=int, default=5, help='bits per symbol (excluding sign bit)') + hum_parser.add_argument('-n', '--noise-level', type=float, default=1.0, help='Scale synthetic noise level') + hum_parser.add_argument('-s', '--signal-level', type=float, default=20.0, help='Synthetic noise level in mHz') + hum_parser.add_argument('-d', '--decimation', type=int, default=10, help='DSSS modulation decimation in frequency measurement cycles') + hum_parser.add_argument('-r', '--random-seed', type=int, default=0) + hum_parser.add_argument('--noise-spec', type=str, default='synth://grid_freq_psd_spl_108pt.json') + hum_parser.add_argument('out_flac', metavar='out.flac', help='FLAC output file') + hum_parser.add_argument('data', help='modulation data hex string') + hum_parser.set_defaults(func=hum_cmd) + + args = parser.parse_args() + args.func(args) + diff --git a/tools/ldparser.py b/tools/ldparser.py new file mode 100644 index 0000000..c620fe2 --- /dev/null +++ b/tools/ldparser.py @@ -0,0 +1,126 @@ + +import sys + +import pyparsing as pp +from pyparsing import pyparsing_common as ppc + +LPAREN, RPAREN, LBRACE, RBRACE, LBROK, RBROK, COLON, SEMICOLON, EQUALS, COMMA = map(pp.Suppress, '(){}<>:;=,') + +parse_suffix_int = lambda lit: int(lit[:-1]) * (10**(3*(1 + 'kmgtpe'.find(lit[-1].lower())))) +si_suffix = pp.oneOf('k m g t p e', caseless=True) + +numeric_literal = pp.Regex('0x[0-9a-fA-F]+').setName('hex int').setParseAction(pp.tokenMap(int, 16)) \ + | (pp.Regex('[0-9]+[kKmMgGtTpPeE]')).setName('size int').setParseAction(pp.tokenMap(parse_suffix_int)) \ + | pp.Word(pp.nums).setName('int').setParseAction(pp.tokenMap(int)) +access_def = pp.Regex('[rR]?[wW]?[xX]?').setName('access literal').setParseAction(pp.tokenMap(str.lower)) + +origin_expr = pp.Suppress(pp.CaselessKeyword('ORIGIN')) + EQUALS + numeric_literal +length_expr = pp.Suppress(pp.CaselessKeyword('LENGTH')) + EQUALS + numeric_literal +mem_expr = pp.Group(ppc.identifier + LPAREN + access_def + RPAREN + COLON + origin_expr + COMMA + length_expr) +mem_contents = pp.ZeroOrMore(mem_expr) + +mem_toplevel = pp.CaselessKeyword("MEMORY") + pp.Group(LBRACE + pp.Optional(mem_contents, []) + RBRACE) + +glob = pp.Word(pp.alphanums + '._*') +match_expr = pp.Forward() +assignment = pp.Forward() +funccall = pp.Group(pp.Word(pp.alphas + '_') + LPAREN + (assignment | numeric_literal | match_expr | glob | ppc.identifier) + RPAREN + pp.Optional(SEMICOLON)) +value = numeric_literal | funccall | ppc.identifier | '.' +formula = (value + pp.oneOf('+ = * / %') + value) | value +# suppress stray semicolons +assignment << (SEMICOLON | pp.Group((ppc.identifier | '.') + EQUALS + (formula | value) + pp.Optional(SEMICOLON))) +match_expr << (glob + LPAREN + pp.OneOrMore(funccall | glob) + RPAREN) + +section_contents = pp.ZeroOrMore(assignment | funccall | match_expr); + +section_name = pp.Regex('\.[a-zA-Z0-9_.]+') +section_def = pp.Group(section_name + pp.Optional(numeric_literal) + COLON + LBRACE + pp.Group(section_contents) + + RBRACE + pp.Optional(RBROK + ppc.identifier + pp.Optional('AT' + RBROK + ppc.identifier))) +sec_contents = pp.ZeroOrMore(section_def | assignment) + +sections_toplevel = pp.Group(pp.CaselessKeyword("SECTIONS").suppress() + LBRACE + sec_contents + RBRACE) + +toplevel_elements = mem_toplevel | funccall | sections_toplevel | assignment +ldscript = pp.Group(pp.ZeroOrMore(toplevel_elements)) +ldscript.ignore(pp.cppStyleComment) + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('linker_script', type=argparse.FileType('r')) + args = parser.parse_args() + + #print(mem_expr.parseString('FLASH (rx) : ORIGIN = 0x0800000, LENGTH = 512K', parseAll=True)) + # print(ldscript.parseString(''' + # /* Entry Point */ + # ENTRY(Reset_Handler) + # + # /* Highest address of the user mode stack */ + # _estack = 0x20020000; /* end of RAM */ + # /* Generate a link error if heap and stack don't fit into RAM */ + # _Min_Heap_Size = 0x200;; /* required amount of heap */ + # _Min_Stack_Size = 0x400;; /* required amount of stack */ + # ''', parseAll=True)) + + print(ldscript.parseFile(args.linker_script, parseAll=True)) + #print(funccall.parseString('KEEP(*(.isr_vector))')) + #print(section_contents.parseString(''' + # . = ALIGN(4); + # KEEP(*(.isr_vector)) /* Startup code */ + # . = ALIGN(4); + # ''', parseAll=True)) + + #print(section_def.parseString(''' + # .text : + # { + # . = ALIGN(4); + # *(.text) /* .text sections (code) */ + # *(.text*) /* .text* sections (code) */ + # *(.glue_7) /* glue arm to thumb code */ + # *(.glue_7t) /* glue thumb to arm code */ + # *(.eh_frame) + # + # KEEP (*(.init)) + # KEEP (*(.fini)) + # + # . = ALIGN(4); + # _etext = .; /* define a global symbols at end of code */ + # } >FLASH + # ''', parseAll=True)) + + #print(section_def.parseString('.ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH', parseAll=True)) + + #print(assignment.parseString('__preinit_array_start = .', parseAll=True)) + #print(assignment.parseString('a = 23', parseAll=True)) + #print(funccall.parseString('foo (a=23)', parseAll=True)) + #print(funccall.parseString('PROVIDE_HIDDEN (__preinit_array_start = .);', parseAll=True)) + #print(section_def.parseString(''' + # .preinit_array : + # { + # PROVIDE_HIDDEN (__preinit_array_start = .); + # KEEP (*(.preinit_array*)) + # PROVIDE_HIDDEN (__preinit_array_end = .); + # } >FLASH''', parseAll=True)) + #print(match_expr.parseString('*(SORT(.init_array.*))', parseAll=True)) + #print(funccall.parseString('KEEP (*(SORT(.init_array.*)))', parseAll=True)) + #print(section_def.parseString(''' + # .init_array : + # { + # PROVIDE_HIDDEN (__init_array_start = .); + # KEEP (*(SORT(.init_array.*))) + # KEEP (*(.init_array*)) + # PROVIDE_HIDDEN (__init_array_end = .); + # } >FLASH + # ''', parseAll=True)) + + #print(match_expr.parseString('*(.ARM.extab* .gnu.linkonce.armextab.*)', parseAll=True)) + #print(formula.parseString('. + _Min_Heap_Size', parseAll=True)) + #print(assignment.parseString('. = . + _Min_Heap_Size;', parseAll=True)) + #print(sections_toplevel.parseString(''' + # SECTIONS + # { + # .ARMattributes : { } + # } + # ''', parseAll=True)) + #sys.exit(0) + diff --git a/tools/linkmem.py b/tools/linkmem.py new file mode 100644 index 0000000..934a571 --- /dev/null +++ b/tools/linkmem.py @@ -0,0 +1,276 @@ + +import tempfile +import os +from os import path +import sys +import re +import subprocess +from contextlib import contextmanager +from collections import defaultdict +import colorsys + +import cxxfilt +from elftools.elf.elffile import ELFFile +from elftools.elf.enums import ENUM_ST_SHNDX +from elftools.elf.descriptions import describe_symbol_type, describe_sh_type +import libarchive +import matplotlib.cm + +@contextmanager +def chdir(newdir): + old_cwd = os.getcwd() + try: + os.chdir(newdir) + yield + finally: + os.chdir(old_cwd) + +def keep_last(it, first=None): + last = first + for elem in it: + yield last, elem + last = elem + +def delim(start, end, it, first_only=True): + found = False + for elem in it: + if end(elem): + if first_only: + return + found = False + elif start(elem): + found = True + elif found: + yield elem + +def delim_prefix(start, end, it): + yield from delim(lambda l: l.startswith(start), lambda l: end is not None and l.startswith(end), it) + +def trace_source_files(linker, cmdline, trace_sections=[], total_sections=['.text', '.data', '.rodata']): + with tempfile.TemporaryDirectory() as tempdir: + out_path = path.join(tempdir, 'output.elf') + output = subprocess.check_output([linker, '-o', out_path, f'-Wl,--print-map', *cmdline]) + lines = [ line.strip() for line in output.decode().splitlines() ] + # FIXME also find isr vector table references + + defs = {} + objs = defaultdict(lambda: 0) + aliases = {} + sec_name = None + last_loc = None + last_sym = None + line_cont = None + for last_line, line in keep_last(delim_prefix('Linker script and memory map', 'OUTPUT', lines), first=''): + if not line or line.startswith('LOAD '): + sec_name = None + continue + + # first part of continuation line + if m := re.match('^(\.[0-9a-zA-Z-_.]+)$', line): + line_cont = line + sec_name = None + continue + + if line_cont: + line = line_cont + ' ' + line + line_cont = None + + # -ffunction-sections/-fdata-sections section + if m := re.match('^(\.[0-9a-zA-Z-_.]+)\.([0-9a-zA-Z-_.]+)\s+(0x[0-9a-f]+)\s+(0x[0-9a-f]+)\s+(\S+)$', line): + sec, sym, loc, size, obj = m.groups() + *_, sym = sym.rpartition('.') + sym = cxxfilt.demangle(sym) + size = int(size, 16) + obj = path.abspath(obj) + + if sec not in total_sections: + size = 0 + + objs[obj] += size + defs[sym] = (sec, size, obj) + + sec_name, last_loc, last_sym = sec, loc, sym + continue + + # regular (no -ffunction-sections/-fdata-sections) section + if m := re.match('^(\.[0-9a-zA-Z-_]+)\s+(0x[0-9a-f]+)\s+(0x[0-9a-f]+)\s+(\S+)$', line): + sec, _loc, size, obj = m.groups() + size = int(size, 16) + obj = path.abspath(obj) + + if sec in total_sections: + objs[obj] += size + + sec_name = sec + last_loc, last_sym = None, None + continue + + # symbol def + if m := re.match('^(0x[0-9a-f]+)\s+(\S+)$', line): + loc, sym = m.groups() + sym = cxxfilt.demangle(sym) + loc = int(loc, 16) + if sym in defs: + continue + + if loc == last_loc: + assert last_sym is not None + aliases[sym] = last_sym + else: + assert sec_name + defs[sym] = (sec_name, None, obj) + last_loc, last_sym = loc, sym + + continue + + refs = defaultdict(lambda: set()) + for sym, (sec, size, obj) in defs.items(): + fn, _, member = re.match('^([^()]+)(\((.+)\))?$', obj).groups() + fn = path.abspath(fn) + + if member: + subprocess.check_call(['ar', 'x', '--output', tempdir, fn, member]) + fn = path.join(tempdir, member) + + with open(fn, 'rb') as f: + elf = ELFFile(f) + + symtab = elf.get_section_by_name('.symtab') + + symtab_demangled = { cxxfilt.demangle(nsym.name).replace(' ', ''): i + for i, nsym in enumerate(symtab.iter_symbols()) } + + s = set() + sec_map = { sec.name: i for i, sec in enumerate(elf.iter_sections()) } + matches = [ i for name, i in sec_map.items() if re.match(f'\.rel\..*\.{sym}', name) ] + if matches: + sec = elf.get_section(matches[0]) + for reloc in sec.iter_relocations(): + refsym = symtab.get_symbol(reloc['r_info_sym']) + name = refsym.name if refsym.name else elf.get_section(refsym['st_shndx']).name.split('.')[-1] + s.add(name) + refs[sym] = s + + for tsec in trace_sections: + matches = [ i for name, i in sec_map.items() if name == f'.rel{tsec}' ] + s = set() + if matches: + sec = elf.get_section(matches[0]) + for reloc in sec.iter_relocations(): + refsym = symtab.get_symbol(reloc['r_info_sym']) + s.add(refsym.name) + refs[tsec.replace('.', '_')] |= s + + return objs, aliases, defs, refs + +@contextmanager +def wrap(leader='', print=print, left='{', right='}'): + print(leader, left) + yield lambda *args, **kwargs: print(' ', *args, **kwargs) + print(right) + +def mangle(name): + return re.sub('[^a-zA-Z0-9_]', '_', name) + +hexcolor = lambda r, g, b, *_a: f'#{int(r*255):02x}{int(g*255):02x}{int(b*255):02x}' +def vhex(val): + r,g,b,_a = matplotlib.cm.viridis(1.0-val) + fc = hexcolor(r, g, b) + h,s,v = colorsys.rgb_to_hsv(r,g,b) + cc = '#000000' if v > 0.8 else '#ffffff' + return fc, cc + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('--trace-sections', type=str, action='append', default=[]) + parser.add_argument('--trim-stubs', type=str, action='append', default=[]) + parser.add_argument('--highlight-subdirs', type=str, default=None) + parser.add_argument('linker_binary') + parser.add_argument('linker_args', nargs=argparse.REMAINDER) + args = parser.parse_args() + + trace_sections = args.trace_sections + trace_sections_mangled = { sec.replace('.', '_') for sec in trace_sections } + objs, aliases, syms, refs = trace_source_files(args.linker_binary, args.linker_args, trace_sections) + + clusters = defaultdict(lambda: []) + for sym, (sec, size, obj) in syms.items(): + clusters[obj].append((sym, sec, size)) + + max_ssize = max(size or 0 for _sec, size, _obj in syms.values()) + max_osize = max(objs.values()) + + subdir_prefix = path.abspath(args.highlight_subdirs) + '/' if args.highlight_subdirs else '### NO HIGHLIGHT ###' + first_comp = lambda le_path: path.dirname(le_path).partition(os.sep)[0] + subdir_colors = sorted({ first_comp(obj[len(subdir_prefix):]) for obj in objs if obj.startswith(subdir_prefix) }) + subdir_colors = { path: hexcolor(*matplotlib.cm.Pastel1(i/len(subdir_colors))) for i, path in enumerate(subdir_colors) } + + subdir_sizes = defaultdict(lambda: 0) + for obj, size in objs.items(): + if not isinstance(size, int): + continue + if obj.startswith(subdir_prefix): + subdir_sizes[first_comp(obj[len(subdir_prefix):])] += size + else: + subdir_sizes['<others>'] += size + + print('Subdir sizes:', file=sys.stderr) + for subdir, size in sorted(subdir_sizes.items(), key=lambda x: x[1]): + print(f'{subdir:>20}: {size:>6,d} B', file=sys.stderr) + + def lookup_highlight(path): + if args.highlight_subdirs: + if obj.startswith(subdir_prefix): + highlight_head = first_comp(path[len(subdir_prefix):]) + return subdir_colors[highlight_head], highlight_head + else: + return '#e0e0e0', None + else: + return '#ddf7f4', None + + with wrap('digraph G', print) as lvl1print: + print('size="23.4,16.5!";') + print('graph [fontsize=40];') + print('node [fontsize=40];') + #print('ratio="fill";') + + print('rankdir=LR;') + print('ranksep=5;') + print('nodesep=0.2;') + print() + + for i, (obj, obj_syms) in enumerate(clusters.items()): + with wrap(f'subgraph cluster_{i}', lvl1print) as lvl2print: + print('style = "filled";') + highlight_color, highlight_head = lookup_highlight(obj) + print(f'bgcolor = "{highlight_color}";') + print('pencolor = none;') + fc, cc = vhex(objs[obj]/max_osize) + highlight_subdir_part = f'<font face="carlito" color="{cc}" point-size="40">{highlight_head} / </font>' if highlight_head else '' + lvl2print(f'label = <<table border="0"><tr><td border="0" cellpadding="5" bgcolor="{fc}">' + f'{highlight_subdir_part}' + f'<font face="carlito" color="{cc}"><b>{path.basename(obj)} ({objs[obj]}B)</b></font>' + f'</td></tr></table>>;') + lvl2print() + for sym, sec, size in obj_syms: + has_size = isinstance(size, int) and size > 0 + size_s = f' ({size}B)' if has_size else '' + fc, cc = vhex(size/max_ssize) if has_size else ('#ffffff', '#000000') + shape = 'box' if sec == '.text' else 'oval' + lvl2print(f'{mangle(sym)}[label = "{sym}{size_s}", style="rounded,filled", shape="{shape}", fillcolor="{fc}", fontname="carlito", fontcolor="{cc}" color=none];') + lvl1print() + + edges = set() + for start, ends in refs.items(): + for end in ends: + end = aliases.get(end, end) + if (start in syms or start in trace_sections_mangled) and end in syms: + edges.add((start, end)) + + for start, end in edges: + lvl1print(f'{mangle(start)} -> {mangle(end)} [style="bold", color="#333333"];') + + for sec in trace_sections: + lvl1print(f'{sec.replace(".", "_")} [label = "section {sec}", shape="box", style="filled,bold"];') + diff --git a/tools/linksize.py b/tools/linksize.py new file mode 100644 index 0000000..c41a951 --- /dev/null +++ b/tools/linksize.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 + +def parse_linker_script(data): + pass + +def link(groups): + defined_symbols = {} + undefined_symbols = set() + for group, files in groups: + while True: + found_something = False + + for fn in files: + symbols = load_symbols(fn) + for symbol in symbols: + if symbol in defined_symbols: + + if not group or not found_something: + break + + +if __name__ == '__main__': + + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('-T', '--script', type=str, help='Linker script to use') + parser.add_argument('-o', '--output', type=str, help='Output file to produce') + args, rest = parser.parse_known_intermixed_args() + print(rest) + + addprefix = lambda *xs: [ prefix + opt for opt in xs for prefix in ('', '-Wl,') ] + START_GROUP = addprefix('-(', '--start-group') + END_GROUP = addprefix('-)', '--end-group') + GROUP_OPTS = [*START_GROUP, *END_GROUP] + input_files = [ arg for arg in rest if not arg.startswith('-') or arg in GROUP_OPTS ] + + def input_file_iter(input_files): + group = False + files = [] + for arg in input_files: + if arg in START_GROUP: + assert not group + + if files: + yield False, files # nested -Wl,--start-group + group, files = True, [] + + elif arg in END_GROUP: + assert group # missing -Wl,--start-group + if files: + yield True, files + group, files = False, [] + + else: + files.append(arg) + + assert not group # missing -Wl,--end-group + if files: + yield False, files + + + diff --git a/tools/linktracer.py b/tools/linktracer.py new file mode 100644 index 0000000..0c53a60 --- /dev/null +++ b/tools/linktracer.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 + +import re +import subprocess +import tempfile +import pprint + +ARCHIVE_RE = r'([^(]*)(\([^)]*\))?' + +def trace_source_files(linker, cmdline): + with tempfile.NamedTemporaryFile() as mapfile: + output = subprocess.check_output([linker, f'-Wl,--Map={mapfile.name}', *cmdline]) + + # intentionally use generator here + idx = 0 + lines = [ line.rstrip() for line in mapfile.read().decode().splitlines() if line.strip() ] + + for idx, line in enumerate(lines[idx:], start=idx): + #print('Dropping', line) + if line == 'Linker script and memory map': + break + + idx += 1 + objects = [] + symbols = {} + sections = {} + current_object = None + last_offset = None + last_symbol = None + cont_sec = None + cont_ind = None + current_section = None + for idx, line in enumerate(lines[idx:], start=idx): + print(f'Processing >{line}') + if line.startswith('LOAD'): + _load, obj = line.split() + objects.append(obj) + continue + + if line.startswith('OUTPUT'): + break + + m = re.match(r'^( ?)([^ ]+)? +(0x[0-9a-z]+) +(0x[0-9a-z]+)?(.*)?$', line) + if m is None: + m = re.match(r'^( ?)([^ ]+)?$', line) + if m: + cont_ind, cont_sec = m.groups() + else: + cont_ind, cont_sec = None, None + last_offset, last_symbol = None, None + continue + indent, sec, offx, size, sym_or_src = m.groups() + if sec is None: + sec = cont_sec + ind = cont_ind + cont_sec = None + cont_ind = None + print(f'vals: indent={indent} sec={sec} offx={offx} size={size} sym_or_src={sym_or_src}') + if not re.match('^[a-zA-Z_0-9<>():*]+$', sym_or_src): + continue + + if indent == '': + print(f'Section: {sec} 0x{size:x}') + current_section = sec + sections[sec] = size + last_offset = None + last_symbol = None + continue + + if offx is not None: + offx = int(offx, 16) + if size is not None: + size = int(size, 16) + + if size is not None and sym_or_src is not None: + # archive/object line + archive, _member = re.match(ARCHIVE_RE, sym_or_src).groups() + current_object = archive + last_offset = offx + else: + if sym_or_src is not None: + assert size is None + if last_offset is not None: + last_size = offx - last_offset + symbols[last_symbol] = (last_size, current_section) + print(f'Symbol: {last_symbol} 0x{last_size:x} @{current_section}') + last_offset = offx + last_symbol = sym_or_src + + idx += 1 + + for idx, line in enumerate(lines[idx:], start=idx): + if line == 'Cross Reference Table': + break + + idx += 1 + + # map which symbol was pulled from which object in the end + used_defs = {} + for line in lines: + *left, right = line.split() + + archive, _member = re.match(ARCHIVE_RE, right).groups() + if left: + used_defs[''.join(left)] = archive + + #pprint.pprint(symbols) + + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('linker_binary') + parser.add_argument('linker_args', nargs=argparse.REMAINDER) + args = parser.parse_args() + + source_files = trace_source_files(args.linker_binary, args.linker_args) + diff --git a/tools/mapparse.py b/tools/mapparse.py new file mode 100644 index 0000000..c1f460a --- /dev/null +++ b/tools/mapparse.py @@ -0,0 +1,129 @@ + +import re +from collections import defaultdict, namedtuple + +Section = namedtuple('Section', ['name', 'offset', 'objects']) +ObjectEntry = namedtuple('ObjectEntry', ['filename', 'object', 'offset', 'size']) +FileEntry = namedtuple('FileEntry', ['section', 'object', 'offset', 'length']) + +class Memory: + def __init__(self, name, origin, length, attrs=''): + self.name, self.origin, self.length, self.attrs = name, origin, length, attrs + self.sections = {} + self.files = defaultdict(lambda: []) + self.totals = defaultdict(lambda: 0) + + def add_toplevel(self, name, offx, length): + self.sections[name] = Section(offx, length, []) + + def add_obj(self, name, offx, length, fn, obj): + base_section, sep, subsec = name[1:].partition('.') + base_section = '.'+base_section + if base_section in self.sections: + sec = secname, secoffx, secobjs = self.sections[base_section] + secobjs.append(ObjectEntry(fn, obj, offx, length)) + else: + sec = None + self.files[fn].append(FileEntry(sec, obj, offx, length)) + self.totals[fn] += length + +class MapFile: + def __init__(self, s): + self._lines = s.splitlines() + self.memcfg = {} + self.defaultmem = Memory('default', 0, 0xffffffffffffffff) + self._parse() + + def __getitem__(self, offx_or_name): + ''' Lookup a memory area by name or address ''' + if offx_or_name in self.memcfg: + return self.memcfg[offx_or_name] + + elif isinstance(offx_or_name, int): + for mem in self.memcfg.values(): + if mem.origin <= offx_or_name < mem.origin+mem.length: + return mem + else: + return self.defaultmem + + raise ValueError('Invalid argument type for indexing') + + def _skip(self, regex): + matcher = re.compile(regex) + for l in self: + if matcher.match(l): + break + + def __iter__(self): + while self._lines: + yield self._lines.pop(0) + + def _parse(self): + self._skip('^Memory Configuration') + + # Parse memory segmentation info + self._skip('^Name') + for l in self: + if not l: + break + name, origin, length, *attrs = l.split() + if not name.startswith('*'): + self.memcfg[name] = Memory(name, int(origin, 16), int(length, 16), attrs[0] if attrs else '') + + # Parse section information + toplevel_m = re.compile('^(\.[a-zA-Z0-9_.]+)\s+(0x[0-9a-fA-F]+)\s+(0x[0-9a-fA-F]+)') + secondlevel_m = re.compile('^ (\.[a-zA-Z0-9_.]+)\s+(0x[0-9a-fA-F]+)\s+(0x[0-9a-fA-F]+)\s+(.*)$') + secondlevel_linebreak_m = re.compile('^ (\.[a-zA-Z0-9_.]+)\n') + filelike = re.compile('^(/?[^()]*\.[a-zA-Z0-9-_]+)(\(.*\))?') + linebreak_section = None + for l in self: + # Toplevel section + match = toplevel_m.match(l) + if match: + name, offx, length = match.groups() + offx, length = int(offx, 16), int(length, 16) + self[offx].add_toplevel(name, offx, length) + + match = secondlevel_linebreak_m.match(l) + if match: + linebreak_section, = match.groups() + continue + + if linebreak_section: + l = ' {} {}'.format(linebreak_section, l) + linebreak_section = None + + # Second-level section + match = secondlevel_m.match(l) + if match: + name, offx, length, misc = match.groups() + match = filelike.match(misc) + if match: + fn, obj = match.groups() + obj = obj.strip('()') if obj else None + offx, length = int(offx, 16), int(length, 16) + self[offx].add_obj(name, offx, length, fn, obj) + + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser(description='Parser GCC map file') + parser.add_argument('mapfile', type=argparse.FileType('r'), help='The GCC .map file to parse') + parser.add_argument('-m', '--memory', type=str, help='The memory segments to print, comma-separated') + args = parser.parse_args() + mf = MapFile(args.mapfile.read()) + args.mapfile.close() + + mems = args.memory.split(',') if args.memory else mf.memcfg.keys() + + for name in mems: + mem = mf.memcfg[name] + print('Symbols by file for memory', name) + for tot, fn in reversed(sorted( (tot, fn) for fn, tot in mem.totals.items() )): + print(' {:>8} {}'.format(tot, fn)) + for length, offx, sec, obj in reversed(sorted(( (length, offx, sec, obj) for sec, obj, offx, length in + mem.files[fn] ), key=lambda e: e[0] )): + name = sec.name if sec else None + print(' {:>8} {:>#08x} {}'.format(length, offx, obj)) + #print('{:>16} 0x{:016x} 0x{:016x} ({:>24}) {}'.format(name, origin, length, length, attrs)) + diff --git a/tools/presig_gen.py b/tools/presig_gen.py new file mode 100644 index 0000000..c5dafe7 --- /dev/null +++ b/tools/presig_gen.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 + +import os +import sys +import textwrap +import uuid +import hmac +import binascii +import time +from datetime import datetime + +LINKING_KEY_SIZE = 15 +PRESIG_VERSION = '000.001' +DOMAINS = ['all', 'country', 'region', 'vendor', 'series'] + +def format_hex(data, indent=4, wrap=True): + indent = ' '*indent + par = ', '.join(f'0x{b:02x}' for b in data) + par = textwrap.fill(par, width=120, + initial_indent=indent, subsequent_indent=indent, + replace_whitespace=False, drop_whitespace=False) + if wrap: + return f'{{\n{par}\n}}' + return par + +def domain_string(domain, value): + return f'smart reset domain string v{PRESIG_VERSION}: domain:{domain}={value}' + +def keygen_cmd(args): + if os.path.exists(args.keyfile) and not args.force: + print("Error: keyfile already exists. We won't overwrite it. Instead please remove it manually.", + file=sys.stderr) + return 1 + + root_key = os.urandom(LINKING_KEY_SIZE) + + with open(args.keyfile, 'wb') as f: + f.write(binascii.hexlify(root_key)) + f.write(b'\n') + return 0 + +def gen_at_height(domain, value, height, key): + # nanananananana BLOCKCHAIN! + + ds = domain_string(domain, value).encode('utf-8') + + for height in range(height+1): + key = hmac.digest(key, ds, 'sha512')[:LINKING_KEY_SIZE] + + return key + +def auth_cmd(args): + with open(args.keyfile, 'r') as f: + root_key = binascii.unhexlify(f.read().strip()) + + vals = [ (domain, getattr(args, domain)) for domain in DOMAINS if getattr(args, domain) is not None ] + if not vals: + vals = [('all', 'all')] + for domain, value in vals: + auth = gen_at_height(domain, value, args.height, root_key) + print(f'{domain}="{value}" @{args.height}: {binascii.hexlify(auth).decode()}') + + +def prekey_cmd(args): + with open(args.keyfile, 'r') as f: + root_key = binascii.unhexlify(f.read().strip()) + + print('#include <stdint.h>') + print('#include <assert.h>') + print() + print('#include "crypto.h"') + print() + + bundle_id = uuid.uuid4().bytes + print(f'/* bundle id {binascii.hexlify(bundle_id).decode()} */') + print(f'uint8_t presig_bundle_id[16] = {format_hex(bundle_id)};') + print() + print(f'/* generated on {datetime.now()} */') + print(f'uint64_t bundle_timestamp = {int(time.time())};') + print() + print(f'int presig_height = {args.max_height};') + print() + + print('const char *presig_domain_strings[_TRIGGER_DOMAIN_COUNT] = {') + for domain in DOMAINS: + ds = domain_string(domain, getattr(args, domain)) + assert '"' not in ds + print(f' [TRIGGER_DOMAIN_{domain.upper()}] = "{ds}",') + print('};') + print() + + print('uint8_t presig_keys[_TRIGGER_DOMAIN_COUNT][PRESIG_MSG_LEN] = {') + for domain in DOMAINS: + key = gen_at_height(domain, getattr(args, domain), args.max_height, root_key) + print(f' [TRIGGER_DOMAIN_{domain.upper()}] = {{{format_hex(key, indent=0, wrap=False)}}},') + print('};') + + print() + print('static inline void __hack_asserts_only(void) {') + print(f' static_assert(_TRIGGER_DOMAIN_COUNT == {len(DOMAINS)});') + print(f' static_assert(PRESIG_MSG_LEN == {LINKING_KEY_SIZE});') + print('}') + print() + + + +TEST_VENDOR = 'Darthenschmidt Cyberei und Verschleierungstechnik GmbH' +TEST_SERIES = 'Frobnicator v0.23.7' +TEST_REGION = 'Neuland' +TEST_COUNTRY = 'Germany' + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('keyfile', help='Key file to use') + + subparsers = parser.add_subparsers(title='subcommands') + keygen_parser = subparsers.add_parser('keygen', help='Generate a new key') + keygen_parser.add_argument('-f', '--force', action='store_true', help='Force overwriting existing keyfile') + keygen_parser.set_defaults(func=keygen_cmd) + + auth_parser = subparsers.add_parser('auth', help='Generate one-time authentication string') + auth_parser.add_argument('height', type=int, help='Authentication string height, counting from 0 (root key)') + auth_parser.set_defaults(func=auth_cmd) + auth_parser.add_argument('-a', '--all', action='store_const', const='all', help='Vendor name for vendor domain') + auth_parser.add_argument('-v', '--vendor', type=str, nargs='?', const=TEST_VENDOR, help='Vendor name for vendor domain') + auth_parser.add_argument('-s', '--series', type=str, nargs='?', const=TEST_SERIES, help='Series identifier for series domain') + auth_parser.add_argument('-r', '--region', type=str, nargs='?', const=TEST_REGION, help='Region name for region domain') + auth_parser.add_argument('-c', '--country', type=str, nargs='?', const=TEST_COUNTRY, help='Country name for country domain') + + prekey_parser = subparsers.add_parser('prekey', help='Generate prekey data .C source code file') + prekey_parser.add_argument('-m', '--max-height', type=int, default=8, help='Height of generated prekey') + prekey_parser.add_argument('-v', '--vendor', type=str, default=TEST_VENDOR, help='Vendor name for vendor domain') + prekey_parser.add_argument('-s', '--series', type=str, default=TEST_SERIES, help='Series identifier for series domain') + prekey_parser.add_argument('-r', '--region', type=str, default=TEST_REGION, help='Region name for region domain') + prekey_parser.add_argument('-c', '--country', type=str, default=TEST_COUNTRY, help='Country name for country domain') + prekey_parser.set_defaults(func=prekey_cmd, all='all') + + args = parser.parse_args() + sys.exit(args.func(args)) + diff --git a/tools/reed_solomon.py b/tools/reed_solomon.py new file mode 100644 index 0000000..c4ca6e4 --- /dev/null +++ b/tools/reed_solomon.py @@ -0,0 +1,91 @@ +import os, sys +import ctypes as C +import argparse +import binascii +import numpy as np +import timeit +import statistics + +lib = C.CDLL('rslib.so') + +lib.rslib_encode.argtypes = [C.c_int, C.c_size_t, C.POINTER(C.c_char), C.POINTER(C.c_char)] +lib.rslib_decode.argtypes = [C.c_int, C.c_size_t, C.POINTER(C.c_char)] +lib.rslib_gexp.argtypes = [C.c_int, C.c_int] +lib.rslib_gexp.restype = C.c_int +lib.rslib_decode.restype = C.c_int +lib.rslib_npar.restype = C.c_size_t + +def npar(): + return lib.rslib_npar() + +def encode(data: bytes, nbits=8): + out = C.create_string_buffer(len(data) + lib.rslib_npar()) + lib.rslib_encode(nbits, len(data), data, out) + return out.raw + +def decode(data: bytes, nbits=8): + inout = C.create_string_buffer(data) + lib.rslib_decode(nbits, len(data), inout) + return inout.raw[:-lib.rslib_npar() - 1] + +def cmdline_func_test(args, print=lambda *args, **kwargs: None, benchmark=False): + st = np.random.RandomState(seed=args.seed) + + lfsr = [lib.rslib_gexp(i, args.bits) for i in range(2**args.bits - 1)] + print('LFSR', len(set(lfsr)), lfsr) + assert all(0 < x < 2**args.bits for x in lfsr) + assert len(set(lfsr)) == 2**args.bits - 1 + + print('Seed', args.seed) + for i in range(args.repeat): + print(f'Run {i}') + test_data = bytes(st.randint(2**args.bits, size=args.message_length, dtype=np.uint8)) + print(' Raw:', binascii.hexlify(test_data).decode()) + encoded = encode(test_data, nbits=args.bits) + print(' Encoded:', binascii.hexlify(encoded).decode()) + + indices = st.permutation(len(encoded)) + encoded = list(encoded) + for pos in indices[:args.errors]: + encoded[pos] = st.randint(2**args.bits) + encoded = bytes(encoded) + print(' Modified:', ''.join(f'\033[91m{b:02x}\033[0m' if pos in indices[:args.errors] else f'{b:02x}' for pos, b in enumerate(encoded))) + + if benchmark: + rpt = 10000 + delta = timeit.timeit('decode(encoded, nbits=args.bits)', + globals={'args': args, 'decode': decode, 'encoded': encoded}, + number=rpt)/rpt + print(f'Decoding runtime: {delta*1e6:.3f}μs') + decoded = decode(encoded, nbits=args.bits) + print(' Decoded:', binascii.hexlify(decoded).decode()) + print(' Delta:', binascii.hexlify( + bytes(x^y for x, y in zip(test_data, decoded)) + ).decode().replace('0', '.')) + assert test_data == decoded + +def cmdline_func_encode(args, **kwargs): + data = np.frombuffer(binascii.unhexlify(args.hex_str), dtype=np.uint8) + # Map 8 bit input to 6 bit symbol string + data = np.packbits(np.pad(np.unpackbits(data).reshape((-1, 6)), ((0,0),(2, 0))).flatten()) + encoded = encode(data.tobytes(), nbits=args.bits) + print('symbol array:', ', '.join(f'0x{x:02x}' for x in encoded)) + print('hex string:', binascii.hexlify(encoded).decode()) + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + cmd_parser = parser.add_subparsers(required=True) + test_parser = cmd_parser.add_parser('test', help='Test reed-solomon implementation') + test_parser.add_argument('-m', '--message-length', type=int, default=6, help='Test message (plaintext) length in bytes') + test_parser.add_argument('-e', '--errors', type=int, default=2, help='Number of byte errors to insert into simulation') + test_parser.add_argument('-r', '--repeat', type=int, default=1000, help='Repeat experiment -r times') + test_parser.add_argument('-b', '--bits', type=int, default=8, help='Symbol bit size') + test_parser.add_argument('-s', '--seed', type=int, default=0, help='Random seed') + test_parser.set_defaults(func=cmdline_func_test) + enc_parser = cmd_parser.add_parser('encode', help='RS-Encode given hex string') + enc_parser.set_defaults(func=cmdline_func_encode) + enc_parser.add_argument('-b', '--bits', type=int, default=8, help='Symbol bit size') + enc_parser.add_argument('hex_str', type=str, help='Input data as hex string') + args = parser.parse_args() + args.func(args) + |