├── .gitattributes ├── .gitignore ├── .gitmodules ├── .travis.yml ├── LICENSE ├── README.md ├── doc └── guide │ ├── .gitignore │ ├── Makefile │ ├── conf.py │ ├── gettingstarted.rst │ └── index.rst ├── lmic ├── aes.c ├── aes.h ├── debug.c ├── debug.h ├── hal.h ├── lce.c ├── lce.h ├── lmic.c ├── lmic.h ├── lorabase.h ├── oslmic.c ├── oslmic.h ├── peripherals.h ├── persodata.c ├── radio-sx126x.c ├── radio-sx127x.c ├── radio.c └── region.h ├── projects ├── .gitignore ├── ex-fuota │ ├── Makefile │ ├── README.rst │ ├── app.svc │ ├── fragger.py │ ├── fuota.c │ ├── hallosim.up │ ├── main.c │ └── test_exfuota.py ├── ex-join │ ├── Makefile │ ├── app.svc │ ├── main.c │ ├── test_exjoin.py │ ├── test_lwcert103eu868.py │ └── test_perso.py ├── fam-devboards.mk ├── fam-simul.mk ├── platform.mk ├── projects.gmk └── variants.mk ├── requirements.txt ├── services ├── appstart.svc ├── appstart │ └── main.c ├── eckm.svc ├── eckm │ ├── eckm.c │ └── eckm.h ├── eefs.svc ├── eefs │ ├── eefs.c │ ├── eefs.h │ ├── picofs.c │ ├── picofs.h │ └── ufid.py ├── frag.svc ├── fuota.svc ├── fuota │ ├── .gitignore │ ├── Makefile │ ├── frag.c │ ├── frag.h │ ├── frag.py │ ├── fuota.c │ ├── fuota.h │ ├── fuota_hal.h │ ├── fuota_hal_x86_64.h │ ├── fwman.c │ ├── test.c │ ├── test.py │ ├── test.up │ └── testkey.pem ├── fwman.svc ├── lwmux.svc ├── lwmux │ ├── lwmux.c │ └── lwmux.h ├── lwtest.svc ├── lwtest │ ├── test_lwcert103eu868.py │ └── testmode.c ├── perso.svc ├── perso │ ├── README.md │ ├── perso.c │ ├── perso.py │ ├── persotool.py │ ├── requirements.txt │ └── test_perso.py ├── pwrman.svc ├── pwrman │ ├── pwrman.c │ └── pwrman.h ├── uexti.svc └── uexti │ ├── uexti.c │ └── uexti.h ├── stm32 ├── CMSIS │ ├── Device │ │ └── ST │ │ │ └── STM32L0xx │ │ │ └── Include │ │ │ ├── stm32l051xx.h │ │ │ ├── stm32l052xx.h │ │ │ ├── stm32l053xx.h │ │ │ ├── stm32l061xx.h │ │ │ ├── stm32l062xx.h │ │ │ ├── stm32l063xx.h │ │ │ ├── stm32l071xx.h │ │ │ ├── stm32l072xx.h │ │ │ ├── stm32l073xx.h │ │ │ ├── stm32l081xx.h │ │ │ ├── stm32l082xx.h │ │ │ ├── stm32l083xx.h │ │ │ ├── stm32l0xx.h │ │ │ └── system_stm32l0xx.h │ └── Include │ │ ├── arm_common_tables.h │ │ ├── arm_const_structs.h │ │ ├── arm_math.h │ │ ├── core_cm0.h │ │ ├── core_cm0plus.h │ │ ├── core_cm3.h │ │ ├── core_cm4.h │ │ ├── core_cm7.h │ │ ├── core_cmFunc.h │ │ ├── core_cmInstr.h │ │ ├── core_cmSimd.h │ │ ├── core_sc000.h │ │ └── core_sc300.h ├── adc.c ├── board.h ├── brd_devboards.h ├── dma.c ├── eeprom.c ├── fw.ld ├── gpio.c ├── hal.c ├── hal_stm32.h ├── hw.h ├── i2c.c ├── leds.c ├── sleep.S ├── startup.c ├── timer.c ├── trng.c └── usart.c ├── tools ├── arch.gmk ├── openocd │ ├── flash.cfg │ ├── nucleo-l0.cfg │ └── stlink-rules.tgz └── svctool │ ├── cc.py │ └── svctool.py └── unicorn ├── board.h ├── fw.ld ├── hal.c ├── hal_unicorn.h ├── hw.h ├── peripherals.c ├── simul ├── device.py ├── devtest.py ├── eventhub.py ├── lorawan.py ├── lwtest.py ├── medium.py ├── peripherals.py ├── runtime.py ├── uuidgen.py └── vtimeloop.py ├── startup.c └── usart.c /.gitattributes: -------------------------------------------------------------------------------- 1 | *.hex -diff 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .*.swp 3 | *.pyc 4 | __pycache__ 5 | .mypy_cache 6 | /TAGS 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "basicloader"] 2 | path = basicloader 3 | url = https://github.com/mkuyper/basicloader.git 4 | [submodule "services/fuota/micro-ecc"] 5 | path = services/micro-ecc 6 | url = https://github.com/kmackay/micro-ecc.git 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: minimal 2 | 3 | dist: focal 4 | 5 | python: 3.8 6 | 7 | addons: 8 | apt: 9 | packages: 10 | - gcc-arm-none-eabi 11 | - libnewlib-arm-none-eabi 12 | - python3-pip 13 | 14 | before_install: 15 | 16 | install: 17 | - pip3 install -r basicloader/requirements.txt 18 | - pip3 install -r requirements.txt 19 | 20 | before_script: 21 | 22 | script: 23 | - make -C basicloader && cd projects/ex-join && make && VARIANT=simul make test 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2020-2020 Michael Kuyper. 2 | Copyright (C) 2016-2019 Semtech (International) AG. 3 | Copyright (C) 2014-2016 IBM Corporation. 4 | 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, 11 | this list of conditions and the following disclaimer. 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | * Neither the name of the copyright holder nor the names of its contributors 16 | may be used to endorse or promote products derived from this software 17 | without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL SEMTECH BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 25 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 26 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 27 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 28 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Basic MAC 2 | 3 | [Basic MAC](https://basicmac.io) is a portable implementation of the LoRa 4 | Alliance®'s LoRaWAN® specification in the C programming language. It is a fork 5 | of IBM's LMiC library, and supports multiple regions, which are selectable at 6 | compile and/or run time. It can handle Class A, Class B, and Class C devices. 7 | 8 | #### Status 9 | Branch | Travis CI 10 | -------|---------- 11 | [`master`](https://github.com/mkuyper/basicmac/tree/master) | [![Build Status](https://travis-ci.com/mkuyper/basicmac.svg?branch=master)](https://travis-ci.com/mkuyper/basicmac) 12 | 13 | #### Getting Started 14 | 15 | View the [Getting Started Guide](https://basicmac.io/guide/gettingstarted.html). 16 | -------------------------------------------------------------------------------- /doc/guide/.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | -------------------------------------------------------------------------------- /doc/guide/Makefile: -------------------------------------------------------------------------------- 1 | SPHINXOPTS ?= 2 | SPHINXBUILD ?= sphinx-build 3 | SOURCEDIR = . 4 | BUILDDIR = _build 5 | 6 | help: 7 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 8 | 9 | .PHONY: help Makefile 10 | 11 | %: Makefile 12 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | -------------------------------------------------------------------------------- /doc/guide/conf.py: -------------------------------------------------------------------------------- 1 | import sphinx_rtd_theme 2 | 3 | project = 'Basic MAC' 4 | copyright = '2020, Michael Kuyper' 5 | author = 'Michael Kuyper' 6 | 7 | extensions = [ 'sphinx_rtd_theme', ] 8 | 9 | templates_path = [ '_templates' ] 10 | 11 | exclude_patterns = [ '_build' ] 12 | 13 | html_theme = 'sphinx_rtd_theme' 14 | html_static_path = [ '_static' ] 15 | -------------------------------------------------------------------------------- /doc/guide/index.rst: -------------------------------------------------------------------------------- 1 | .. 2 | Copyright (C) 2020-2020 Michael Kuyper. All rights reserved. 3 | . 4 | This file is subject to the terms and conditions defined in file 'LICENSE', 5 | which is part of this source code package. 6 | 7 | 8 | Welcome! 9 | ======== 10 | 11 | Basic MAC is a portable implementation of the LoRa Alliance®'s LoRaWAN® 12 | specification in the C programming language. It is a fork of IBM's LMiC 13 | library, and supports multiple regions, which are selectable at compile and/or 14 | run time. It can handle Class A, Class B, and Class C devices. 15 | 16 | .. toctree:: 17 | :maxdepth: 1 18 | :caption: Contents: 19 | 20 | gettingstarted 21 | -------------------------------------------------------------------------------- /lmic/aes.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | // 3 | // This file is subject to the terms and conditions defined in file 'LICENSE', 4 | // which is part of this source code package. 5 | 6 | #ifndef _aes_h_ 7 | #define _aes_h_ 8 | 9 | #include "oslmic.h" 10 | 11 | // ====================================================================== 12 | // AES support 13 | // !!Keep in sync with lorabase.hpp!! 14 | // !!Keep in sync with bootloader/aes.c!! 15 | 16 | #ifndef AES_ENC // if AES_ENC is defined as macro all other values must be too 17 | #define AES_ENC 0x00 18 | #define AES_DEC 0x01 19 | #define AES_MIC 0x02 20 | #define AES_CTR 0x04 21 | #define AES_MICNOAUX 0x08 22 | #endif 23 | #ifndef AESkey // if AESkey is defined as macro all other values must be too 24 | extern u1_t* AESkey; 25 | extern u1_t* AESaux; 26 | #endif 27 | #ifndef os_aes 28 | u4_t os_aes (u1_t mode, u1_t* buf, u2_t len); 29 | #endif 30 | 31 | #endif // _aes_h_ 32 | -------------------------------------------------------------------------------- /lmic/debug.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | // Copyright (C) 2014-2016 IBM Corporation. All rights reserved. 3 | // 4 | // This file is subject to the terms and conditions defined in file 'LICENSE', 5 | // which is part of this source code package. 6 | 7 | #ifndef _debug_h_ 8 | #define _debug_h_ 9 | 10 | #ifndef CFG_DEBUG 11 | 12 | #define debug_snprintf(s,n,f,...) do { } while (0) 13 | #define debug_printf(f,...) do { } while (0) 14 | #define debug_str(s) do { } while (0) 15 | #define debug_led(val) do { } while (0) 16 | 17 | #else 18 | 19 | // write formatted string to buffer 20 | int debug_snprintf (char *str, int size, const char *format, ...); 21 | 22 | // write formatted string to USART 23 | void debug_printf (char const *format, ...); 24 | 25 | // write nul-terminated string to USART 26 | void debug_str (const char* str); 27 | 28 | // set LED state 29 | void debug_led (int val); 30 | 31 | #endif 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /lmic/hal.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2020 Michael Kuyper. All rights reserved. 2 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 3 | // Copyright (C) 2014-2016 IBM Corporation. All rights reserved. 4 | // 5 | // This file is subject to the terms and conditions defined in file 'LICENSE', 6 | // which is part of this source code package. 7 | 8 | #ifndef _hal_hpp_ 9 | #define _hal_hpp_ 10 | 11 | #ifdef HAL_IMPL_INC 12 | #include HAL_IMPL_INC 13 | #endif 14 | 15 | /* 16 | * initialize hardware (IO, SPI, TIMER, IRQ). 17 | */ 18 | void hal_init (void* bootarg); 19 | 20 | /* 21 | * set watchdog counter (in 2s units) 22 | */ 23 | void hal_watchcount (int cnt); 24 | 25 | /* 26 | * drive antenna switch (and account power consumption) 27 | */ 28 | #define HAL_ANTSW_OFF 0 29 | #define HAL_ANTSW_RX 1 30 | #define HAL_ANTSW_TX 2 31 | #define HAL_ANTSW_TX2 3 32 | void hal_ant_switch (u1_t val); 33 | 34 | /* 35 | * control radio TCXO power (0=off, 1=on) 36 | * (return if TCXO is present and in use) 37 | */ 38 | bool hal_pin_tcxo (u1_t val); 39 | 40 | /* 41 | * control radio RST pin (0=low, 1=high, 2=floating) 42 | */ 43 | void hal_pin_rst (u1_t val); 44 | 45 | /* 46 | * wait until radio BUSY pin is low 47 | */ 48 | void hal_pin_busy_wait (void); 49 | 50 | /* 51 | * set DIO0/1/2/3 interrupt mask 52 | */ 53 | #define HAL_IRQMASK_DIO0 (1<<0) 54 | #define HAL_IRQMASK_DIO1 (1<<1) 55 | #define HAL_IRQMASK_DIO2 (1<<2) 56 | #define HAL_IRQMASK_DIO3 (1<<3) 57 | void hal_irqmask_set (int mask); 58 | 59 | /* 60 | * drive radio NSS pin (on=low, off=high). 61 | */ 62 | void hal_spi_select (int on); 63 | 64 | /* 65 | * perform sequential SPI transaction with radio. 66 | * - write txlen bytes from txbuf to SPI 67 | * - read rxlen bytes into rxbuf from SPI 68 | */ 69 | void hal_spi_transact (const u1_t* txbuf, u1_t txlen, u1_t* rxbuf, u1_t rxlen); 70 | 71 | /* 72 | * disable all CPU interrupts. 73 | * - might be invoked nested 74 | * - will be followed by matching call to hal_enableIRQs() 75 | */ 76 | void hal_disableIRQs (void); 77 | 78 | /* 79 | * enable CPU interrupts. 80 | */ 81 | void hal_enableIRQs (void); 82 | 83 | /* 84 | * put system and CPU in low-power mode, sleep until target time / interrupt. 85 | */ 86 | #define HAL_SLEEP_EXACT 0 87 | #define HAL_SLEEP_APPROX 1 88 | void hal_sleep (u1_t type, u4_t targettime); 89 | 90 | /* 91 | * return 32-bit system time in ticks. 92 | */ 93 | u4_t hal_ticks (void); 94 | 95 | /* 96 | * return 64-bit system time in ticks. 97 | */ 98 | u8_t hal_xticks (void); 99 | 100 | /* 101 | * return subticks (1/1024th tick) 102 | */ 103 | s2_t hal_subticks (void); 104 | 105 | /* 106 | * busy-wait until specified timestamp (in ticks) is reached. 107 | */ 108 | void hal_waitUntil (u4_t time); 109 | 110 | /* 111 | * get current battery level 112 | */ 113 | u1_t hal_getBattLevel (void); 114 | 115 | /* 116 | * set current battery level 117 | */ 118 | void hal_setBattLevel (u1_t level); 119 | 120 | /* 121 | * perform fatal failure action. 122 | * - called by assertions 123 | * - action could be HALT or reboot 124 | */ 125 | void hal_failed (void); 126 | 127 | #ifdef CFG_DEBUG 128 | 129 | void hal_debug_suspend (void); 130 | void hal_debug_resume (void); 131 | void hal_debug_str (const char* str); 132 | void hal_debug_led (int val); 133 | 134 | #endif 135 | 136 | typedef struct { 137 | uint32_t blversion; 138 | uint32_t version; 139 | uint32_t crc; 140 | uint32_t flashsz; 141 | } hal_fwi; 142 | 143 | void hal_fwinfo (hal_fwi* fwi); 144 | 145 | void hal_pd_init (void); 146 | bool hal_pd_valid (void); 147 | u1_t* hal_joineui (void); 148 | u1_t* hal_deveui (void); 149 | u1_t* hal_nwkkey (void); 150 | u1_t* hal_appkey (void); 151 | u1_t* hal_serial (void); 152 | u4_t hal_region (void); 153 | u4_t hal_hwid (void); 154 | u4_t hal_unique (void); 155 | 156 | u4_t hal_dnonce_next (void); 157 | 158 | void hal_reboot (void); 159 | bool hal_set_update (void* ptr); 160 | 161 | void hal_logEv (uint8_t evcat, uint8_t evid, uint32_t evparam); 162 | 163 | #endif // _hal_hpp_ 164 | -------------------------------------------------------------------------------- /lmic/lce.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | // 3 | // This file is subject to the terms and conditions defined in file 'LICENSE', 4 | // which is part of this source code package. 5 | 6 | #include "oslmic.h" 7 | #include "aes.h" 8 | #include "lce.h" 9 | #include "lmic.h" 10 | 11 | 12 | bool lce_processJoinAccept (u1_t* jacc, u1_t jacclen, u2_t devnonce) { 13 | if( (jacc[0] & HDR_FTYPE) != HDR_FTYPE_JACC || (jacclen != LEN_JA && jacclen != LEN_JAEXT) ) { 14 | return 0; 15 | } 16 | os_getNwkKey(AESkey); 17 | os_aes(AES_ENC, jacc+1, jacclen-1); 18 | 19 | jacclen -= 4; 20 | u4_t mic1 = os_rmsbf4(jacc+jacclen); 21 | #if defined(CFG_lorawan11) 22 | u1_t optneg = jacc[OFF_JA_DLSET] & JA_DLS_OPTNEG; 23 | if( optneg ) { 24 | os_moveMem(jacc+OFF_JA_JOINNONCE+2, jacc+OFF_JA_JOINNONCE, jacclen-OFF_JA_JOINNONCE); 25 | os_wlsbf2(jacc+OFF_JA_JOINNONCE, devnonce); 26 | jacclen += 2; 27 | } 28 | #endif 29 | os_getNwkKey(AESkey); 30 | u4_t mic2 = os_aes(AES_MIC|AES_MICNOAUX, jacc, jacclen); 31 | #if defined(CFG_lorawan11) 32 | if( optneg ) { // Restore orig frame 33 | jacclen -= 2; 34 | os_moveMem(jacc+OFF_JA_JOINNONCE, jacc+OFF_JA_JOINNONCE+2, jacclen-OFF_JA_JOINNONCE); 35 | os_wlsbf4(jacc+jacclen, mic1); 36 | } 37 | #endif 38 | if( mic1 != mic2 ) { 39 | return 0; 40 | } 41 | u1_t* nwkskey = LMIC.lceCtx.nwkSKey; 42 | os_clearMem(nwkskey, 16); 43 | nwkskey[0] = 0x01; 44 | os_copyMem(nwkskey+1, &jacc[OFF_JA_JOINNONCE], LEN_JOINNONCE+LEN_NETID); 45 | os_wlsbf2(nwkskey+1+LEN_JOINNONCE+LEN_NETID, devnonce); 46 | os_copyMem(LMIC.lceCtx.appSKey, nwkskey, 16); 47 | LMIC.lceCtx.appSKey[0] = 0x02; 48 | #if defined(CFG_lorawan11) 49 | os_copyMem(LMIC.lceCtx.nwkSKeyDn, nwkskey, 16); 50 | LMIC.lceCtx.nwkSKeyDn[0] = 0x03; 51 | #endif 52 | 53 | os_getNwkKey(AESkey); 54 | os_aes(AES_ENC, nwkskey, 16); 55 | #if defined(CFG_lorawan11) 56 | if( optneg ) { 57 | os_getNwkKey(AESkey); 58 | os_aes(AES_ENC, LMIC.lceCtx.nwkSKeyDn, 16); 59 | os_getAppKey(AESkey); 60 | } else { 61 | os_copyMem(LMIC.lceCtx.nwkSKeyDn, nwkskey, 16); 62 | os_getNwkKey(AESkey); 63 | } 64 | #else 65 | os_getNwkKey(AESkey); 66 | #endif 67 | os_aes(AES_ENC, LMIC.lceCtx.appSKey, 16); 68 | return 1; 69 | } 70 | 71 | 72 | void lce_addMicJoinReq (u1_t* pdu, int len) { 73 | os_getNwkKey(AESkey); 74 | os_wmsbf4(pdu+len, os_aes(AES_MIC|AES_MICNOAUX, pdu, len)); // MSB because of internal structure of AES 75 | } 76 | 77 | void lce_encKey0 (u1_t* buf) { 78 | os_clearMem(AESkey,16); 79 | os_aes(AES_ENC,buf,16); 80 | } 81 | 82 | static void micB0 (u4_t devaddr, u4_t seqno, u1_t cat, int len) { 83 | os_clearMem(AESaux,16); 84 | AESaux[0] = 0x49; 85 | AESaux[5] = cat; 86 | AESaux[15] = len; 87 | os_wlsbf4(AESaux+ 6,devaddr); 88 | os_wlsbf4(AESaux+10,seqno); 89 | } 90 | 91 | bool lce_verifyMic (s1_t keyid, u4_t devaddr, u4_t seqno, u1_t* pdu, int len) { 92 | micB0(devaddr, seqno, 1, len); 93 | const u1_t* key; 94 | if( keyid == LCE_NWKSKEY ) { 95 | #if defined(CFG_lorawan11) 96 | key = LMIC.lceCtx.nwkSKeyDn; 97 | #else 98 | key = LMIC.lceCtx.nwkSKey; 99 | #endif 100 | } 101 | else if( keyid >= LCE_MCGRP_0 && keyid < LCE_MCGRP_0+LCE_MCGRP_MAX ) { 102 | key = LMIC.lceCtx.mcgroup[keyid - LCE_MCGRP_0].nwkSKeyDn; 103 | } 104 | else { 105 | // Illegal key index 106 | return 0; 107 | } 108 | os_copyMem(AESkey,key,16); 109 | return os_aes(AES_MIC, pdu, len) == os_rmsbf4(pdu+len); 110 | } 111 | 112 | void lce_addMic (s1_t keyid, u4_t devaddr, u4_t seqno, u1_t* pdu, int len) { 113 | if( keyid != LCE_NWKSKEY ) { 114 | return; // Illegal key index 115 | } 116 | micB0(devaddr, seqno, 0, len); 117 | const u1_t* key = LMIC.lceCtx.nwkSKey; 118 | os_copyMem(AESkey,key,16); 119 | // MSB because of internal structure of AES 120 | os_wmsbf4(pdu+len, os_aes(AES_MIC, pdu, len)); 121 | } 122 | 123 | u4_t lce_micKey0 (u4_t devaddr, u4_t seqno, u1_t* pdu, int len) { 124 | micB0(devaddr, seqno, 0, len); 125 | os_clearMem(AESkey,16); 126 | // MSB because of internal structure of AES 127 | u1_t mic[4]; 128 | os_wmsbf4(mic, os_aes(AES_MIC, pdu, len)); 129 | return os_rlsbf4(mic); 130 | } 131 | 132 | void lce_cipher (s1_t keyid, u4_t devaddr, u4_t seqno, int cat, u1_t* payload, int len) { 133 | if(len <= 0 || (cat==LCE_SCC_UP && (LMIC.opmode & OP_NOCRYPT)) ) { 134 | return; 135 | } 136 | const u1_t* key; 137 | if( keyid == LCE_NWKSKEY ) { 138 | #if defined(CFG_lorawan11) 139 | key = cat==LCE_SCC_DN ? LMIC.lceCtx.nwkSKeyDn : LMIC.lceCtx.nwkSKey; 140 | #else 141 | key = LMIC.lceCtx.nwkSKey; 142 | #endif 143 | } 144 | else if( keyid == LCE_APPSKEY ) { 145 | key = LMIC.lceCtx.appSKey; 146 | } 147 | else if( keyid >= LCE_MCGRP_0 && keyid < LCE_MCGRP_0+LCE_MCGRP_MAX ) { 148 | key = LMIC.lceCtx.mcgroup[keyid - LCE_MCGRP_0].appSKey; 149 | cat = LCE_SCC_DN; 150 | } 151 | else { 152 | // Illegal key index 153 | os_clearMem(payload,len); 154 | return; 155 | } 156 | micB0(devaddr, seqno, cat, 1); 157 | AESaux[0] = 0x01; 158 | os_copyMem(AESkey,key,16); 159 | os_aes(AES_CTR, payload, len); 160 | } 161 | 162 | 163 | #if defined(CFG_lorawan11) 164 | void lce_loadSessionKeys (const u1_t* nwkSKey, const u1_t* nwkSKeyDn, const u1_t* appSKey) 165 | #else 166 | void lce_loadSessionKeys (const u1_t* nwkSKey, const u1_t* appSKey) 167 | #endif 168 | { 169 | if( nwkSKey != (u1_t*)0 ) 170 | os_copyMem(LMIC.lceCtx.nwkSKey, nwkSKey, 16); 171 | #if defined(CFG_lorawan11) 172 | if( nwkSKeyDn != (u1_t*)0 ) 173 | os_copyMem(LMIC.lceCtx.nwkSKeyDn, nwkSKeyDn, 16); 174 | #endif 175 | if( appSKey != (u1_t*)0 ) 176 | os_copyMem(LMIC.lceCtx.appSKey, appSKey, 16); 177 | } 178 | 179 | 180 | void lce_init (void) { 181 | os_clearMem(&LMIC.lceCtx, sizeof(LMIC.lceCtx)); 182 | } 183 | -------------------------------------------------------------------------------- /lmic/lce.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | // 3 | // This file is subject to the terms and conditions defined in file 'LICENSE', 4 | // which is part of this source code package. 5 | 6 | #ifndef _lce_h_ 7 | #define _lce_h_ 8 | 9 | #include "oslmic.h" 10 | 11 | // Some keyids: 12 | #define LCE_APPSKEY (-2) 13 | #define LCE_NWKSKEY (-1) 14 | #define LCE_MCGRP_0 ( 0) 15 | #define LCE_MCGRP_MAX ( 2) 16 | 17 | // Stream cipher categories (lce_cipher(..,cat,..): 18 | // Distinct use of the AppSKey must use different key classes 19 | // or plain text will leak: 20 | enum { 21 | LCE_SCC_UP = 0, // std LoRaWAN uplink frame 22 | LCE_SCC_DN = 1, // std LoRaWAN downlink frame 23 | LCE_SCC_FUP = 0x40, // file upload 24 | LCE_SCC_DSE = 0x41, // data streaming engine 25 | LCE_SCC_ROSE = 0x42, // reliable octet streaming engine 26 | }; 27 | 28 | void lce_encKey0 (u1_t* buf); 29 | u4_t lce_micKey0 (u4_t devaddr, u4_t seqno, u1_t* pdu, int len); 30 | bool lce_processJoinAccept (u1_t* jacc, u1_t jacclen, u2_t devnonce); 31 | void lce_addMicJoinReq (u1_t* pdu, int len); 32 | bool lce_verifyMic (s1_t keyid, u4_t devaddr, u4_t seqno, u1_t* pdu, int len); 33 | void lce_addMic (s1_t keyid, u4_t devaddr, u4_t seqno, u1_t* pdu, int len); 34 | void lce_cipher (s1_t keyid, u4_t devaddr, u4_t seqno, int cat, u1_t* payload, int len); 35 | #if defined(CFG_lorawan11) 36 | void lce_loadSessionKeys (const u1_t* nwkSKey, const u1_t* nwkSKeyDn, const u1_t* appSKey); 37 | #else 38 | void lce_loadSessionKeys (const u1_t* nwkSKey, const u1_t* appSKey); 39 | #endif 40 | void lce_init (void); 41 | 42 | 43 | typedef struct lce_ctx_mcgrp { 44 | u1_t nwkSKeyDn[16]; // network session key for down-link 45 | u1_t appSKey[16]; // application session key 46 | } lce_ctx_mcgrp_t; 47 | 48 | typedef struct lce_ctx { 49 | u1_t nwkSKey[16]; // network session key (LoRaWAN1.1: up-link only) 50 | #if defined(CFG_lorawan11) 51 | u1_t nwkSKeyDn[16]; // network session key for down-link 52 | #endif 53 | u1_t appSKey[16]; // application session key 54 | lce_ctx_mcgrp_t mcgroup[LCE_MCGRP_MAX]; 55 | } lce_ctx_t; 56 | 57 | 58 | #endif // _lce_h_ 59 | -------------------------------------------------------------------------------- /lmic/oslmic.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | // Copyright (C) 2014-2016 IBM Corporation. All rights reserved. 3 | // 4 | // This file is subject to the terms and conditions defined in file 'LICENSE', 5 | // which is part of this source code package. 6 | 7 | #include "lmic.h" 8 | #include "aes.h" 9 | #include "peripherals.h" 10 | 11 | // RUNTIME STATE 12 | static struct { 13 | osjob_t* scheduledjobs; 14 | unsigned int exact; 15 | union { 16 | u4_t randwrds[4]; 17 | u1_t randbuf[16]; 18 | } /* anonymous */; 19 | } OS; 20 | 21 | void os_init (void* bootarg) { 22 | memset(&OS, 0x00, sizeof(OS)); 23 | hal_init(bootarg); 24 | #ifndef CFG_noradio 25 | radio_init(false); 26 | #endif 27 | LMIC_init(); 28 | } 29 | 30 | // return next random byte derived from seed buffer 31 | // (buf[0] holds index of next byte to be returned 1-16) 32 | 33 | void rng_init (void) { 34 | #ifdef PERIPH_TRNG 35 | trng_next(OS.randwrds, 4); 36 | #else 37 | memcpy(OS.randbuf, __TIME__, 8); 38 | os_getDevEui(OS.randbuf + 8); 39 | #endif 40 | } 41 | 42 | u1_t os_getRndU1 (void) { 43 | u1_t i = OS.randbuf[0]; 44 | switch( i ) { 45 | case 0: 46 | rng_init(); // lazy initialization 47 | // fall-thru 48 | case 16: 49 | os_aes(AES_ENC, OS.randbuf, 16); // encrypt seed with any key 50 | i = 0; 51 | } 52 | u1_t v = OS.randbuf[i++]; 53 | OS.randbuf[0] = i; 54 | return v; 55 | } 56 | 57 | bit_t os_cca (u2_t rps, u4_t freq) { //XXX:this belongs into os_radio module 58 | return 0; // never grant access 59 | } 60 | 61 | u1_t os_getBattLevel (void) { 62 | return hal_getBattLevel(); 63 | } 64 | 65 | ostime_t os_getTime () { 66 | return hal_ticks(); 67 | } 68 | 69 | osxtime_t os_getXTime () { 70 | return hal_xticks(); 71 | } 72 | 73 | osxtime_t os_time2XTime (ostime_t t, osxtime_t context) { 74 | return context + ((t - (ostime_t) context)); 75 | } 76 | 77 | // unlink job from queue, return 1 if removed 78 | static int unlinkjob (osjob_t** pnext, osjob_t* job) { 79 | for( ; *pnext; pnext = &((*pnext)->next)) { 80 | if(*pnext == job) { // unlink 81 | *pnext = job->next; 82 | if ((job->flags & OSJOB_FLAG_APPROX) == 0) { 83 | OS.exact -= 1; 84 | } 85 | return 1; 86 | } 87 | } 88 | return 0; 89 | } 90 | 91 | // NOTE: since the job queue might begin with jobs which already have a shortly expired deadline, we cannot use 92 | // the maximum span of ostime to schedule the next job (otherwise it would be queued in first)! 93 | #define XJOBTIME_MAX_DIFF (OSTIME_MAX_DIFF / 2) 94 | 95 | // update schedule of extended job 96 | static void extendedjobcb (osxjob_t* xjob) { 97 | hal_disableIRQs(); 98 | osxtime_t now = os_getXTime(); 99 | if (xjob->deadline - now > XJOBTIME_MAX_DIFF) { 100 | // schedule intermediate callback 101 | os_setTimedCallbackEx((osjob_t*) xjob, (ostime_t) (now + XJOBTIME_MAX_DIFF), (osjobcb_t) extendedjobcb, OSJOB_FLAG_APPROX); 102 | } else { 103 | // schedule final callback 104 | os_setTimedCallbackEx((osjob_t*) xjob, (ostime_t) xjob->deadline, xjob->func, OSJOB_FLAG_APPROX); 105 | } 106 | hal_enableIRQs(); 107 | } 108 | 109 | // schedule job far in the future (deadline may exceed max delta of ostime_t 2^31-1 ticks = 65535.99s = 18.2h) 110 | void os_setExtendedTimedCallback (osxjob_t* xjob, osxtime_t xtime, osjobcb_t cb) { 111 | hal_disableIRQs(); 112 | unlinkjob(&OS.scheduledjobs, (osjob_t*) xjob); 113 | xjob->func = cb; 114 | xjob->deadline = xtime; 115 | extendedjobcb(xjob); 116 | hal_enableIRQs(); 117 | } 118 | 119 | // clear scheduled job, return 1 if job was removed 120 | int os_clearCallback (osjob_t* job) { 121 | hal_disableIRQs(); 122 | int r = unlinkjob(&OS.scheduledjobs, job); 123 | hal_enableIRQs(); 124 | return r; 125 | } 126 | 127 | // schedule timed job 128 | void os_setTimedCallbackEx (osjob_t* job, ostime_t time, osjobcb_t cb, unsigned int flags) { 129 | osjob_t** pnext; 130 | hal_disableIRQs(); 131 | // remove if job was already queued 132 | unlinkjob(&OS.scheduledjobs, job); 133 | // fill-in job 134 | ostime_t now = os_getTime(); 135 | if( flags & OSJOB_FLAG_NOW ) { 136 | time = now; 137 | } else if ( time - now <= 0 ) { 138 | flags |= OSJOB_FLAG_NOW; 139 | } 140 | job->deadline = time; 141 | job->func = cb; 142 | job->next = NULL; 143 | job->flags = flags; 144 | if ((flags & OSJOB_FLAG_APPROX) == 0) { 145 | OS.exact += 1; 146 | } 147 | // insert into schedule 148 | for(pnext=&OS.scheduledjobs; *pnext; pnext=&((*pnext)->next)) { 149 | if((*pnext)->deadline - time > 0) { // (cmp diff, not abs!) 150 | // enqueue before next element and stop 151 | job->next = *pnext; 152 | break; 153 | } 154 | } 155 | *pnext = job; 156 | hal_enableIRQs(); 157 | } 158 | 159 | void os_runstep (void) { 160 | // check for runnable jobs 161 | hal_disableIRQs(); 162 | ostime_t now = os_getTime(); 163 | osjob_t* j = OS.scheduledjobs; 164 | ostime_t deadline; 165 | if( j ) { 166 | deadline = j->deadline; 167 | if( (deadline - now) <= 0 ) { 168 | OS.scheduledjobs = j->next; // de-queue 169 | if( (j->flags & OSJOB_FLAG_IRQDISABLED) == 0 ) { 170 | hal_enableIRQs(); 171 | } 172 | hal_watchcount(30); // max 60 sec XXX 173 | j->func(j); 174 | hal_watchcount(0); 175 | return; 176 | } 177 | } else { 178 | deadline = now + 0x7fffff00; 179 | } 180 | hal_sleep(OS.exact ? HAL_SLEEP_EXACT : HAL_SLEEP_APPROX, deadline); 181 | hal_enableIRQs(); 182 | } 183 | 184 | // execute jobs from timer and from run queue 185 | void os_runloop (void) { 186 | while (1) { 187 | os_runstep(); 188 | } 189 | } 190 | 191 | static u1_t evcatEn = 0xFF; 192 | 193 | void os_logEv (uint8_t evcat, uint8_t evid, uint32_t evparam) { 194 | if( evcat >= EVCAT_MAX && evcat < sizeof(evcatEn)*8 && (evcatEn & (1<magic == PERSODATA_MAGIC_V1 ) { 37 | uint32_t hash[8]; 38 | sha256(hash, ptr, sizeof(persodata_v1) - 32); 39 | if( memcmp(hash, ppd->hash, 32) != 0 ) { 40 | return NULL; 41 | } 42 | return ppd; 43 | } 44 | return NULL; 45 | } 46 | 47 | void hal_pd_init (void) { 48 | persodata_v1* ppd = pd_check_v1((void*) HAL_PERSODATA_BASE); 49 | if( ppd ) { 50 | pd.data = *ppd; 51 | pd.valid = true; 52 | } else { 53 | // fill defaults 54 | uint64_t eui; 55 | 56 | eui = 0xffffffaa00000000ULL | hal_unique(); 57 | memcpy(pd.data.deveui, &eui, 8); 58 | eui = 0xffffffbb00000000ULL; 59 | memcpy(pd.data.joineui, &eui, 8); 60 | memcpy(pd.data.nwkkey, "@ABCDEFGHIJKLMNO", 16); 61 | memcpy(pd.data.appkey, "`abcdefghijklmno", 16); 62 | } 63 | } 64 | 65 | bool hal_pd_valid (void) { 66 | return pd.valid; 67 | } 68 | 69 | u1_t* hal_joineui (void) { 70 | return pd.data.joineui; 71 | } 72 | 73 | u1_t* hal_deveui (void) { 74 | return pd.data.deveui; 75 | } 76 | 77 | u1_t* hal_nwkkey (void) { 78 | return pd.data.nwkkey; 79 | } 80 | 81 | u1_t* hal_appkey (void) { 82 | return pd.data.appkey; 83 | } 84 | 85 | u1_t* hal_serial (void) { 86 | return pd.data.serial; 87 | } 88 | 89 | u4_t hal_region (void) { 90 | return pd.data.region; 91 | } 92 | 93 | u4_t hal_hwid (void) { 94 | return pd.data.hwid; 95 | } 96 | 97 | #ifdef CFG_eeprom_region 98 | // provide region code 99 | u1_t os_getRegion (void) { 100 | return hal_region(); 101 | } 102 | #endif 103 | 104 | #ifdef CFG_eeprom_keys 105 | // provide device ID (8 bytes, LSBF) 106 | void os_getDevEui (u1_t* buf) { 107 | memcpy(buf, hal_deveui(), 8); 108 | } 109 | 110 | // provide join ID (8 bytes, LSBF) 111 | void os_getJoinEui (u1_t* buf) { 112 | memcpy(buf, hal_joineui(), 8); 113 | } 114 | 115 | // provide device network key (16 bytes) 116 | void os_getNwkKey (u1_t* buf) { 117 | memcpy(buf, hal_nwkkey(), 16); 118 | } 119 | 120 | // provide device application key (16 bytes) 121 | void os_getAppKey (u1_t* buf) { 122 | memcpy(buf, hal_appkey(), 16); 123 | } 124 | #endif 125 | 126 | #endif 127 | -------------------------------------------------------------------------------- /lmic/radio.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | // Copyright (C) 2014-2016 IBM Corporation. All rights reserved. 3 | // 4 | // This file is subject to the terms and conditions defined in file 'LICENSE', 5 | // which is part of this source code package. 6 | 7 | //#define DEBUG_TX 8 | //#define DEBUG_RX 9 | 10 | #include "board.h" 11 | #include "lmic.h" 12 | 13 | // ---------------------------------------- 14 | // RADIO STATE 15 | static struct { 16 | ostime_t irqtime; 17 | osjob_t irqjob; 18 | u1_t diomask; 19 | u1_t txmode; 20 | } state; 21 | 22 | // stop radio, disarm interrupts, cancel jobs 23 | static void radio_stop (void) { 24 | hal_disableIRQs(); 25 | // put radio to sleep 26 | radio_sleep(); 27 | // disable antenna switch 28 | hal_ant_switch(HAL_ANTSW_OFF); 29 | // power-down TCXO 30 | hal_pin_tcxo(0); 31 | // disable IRQs in HAL 32 | hal_irqmask_set(0); 33 | // cancel radio job 34 | os_clearCallback(&state.irqjob); 35 | // clear state 36 | state.diomask = 0; 37 | hal_enableIRQs(); 38 | } 39 | 40 | // guard timeout in case no completion interrupt is generated by radio 41 | // protected job - runs with irqs disabled! 42 | static void radio_irq_timeout (osjob_t* j) { 43 | BACKTRACE(); 44 | 45 | // stop everything (antenna switch, hal irqs, sleep, irq job) 46 | radio_stop(); 47 | 48 | // re-initialize radio if tx operation timed out 49 | if (state.txmode) { 50 | radio_init(true); 51 | } 52 | 53 | // enable IRQs! 54 | hal_enableIRQs(); 55 | 56 | debug_printf("WARNING: radio irq timeout!\r\n"); 57 | 58 | // indicate timeout 59 | LMIC.dataLen = 0; 60 | 61 | // run os job (use preset func ptr) 62 | os_setCallback(&LMIC.osjob, LMIC.osjob.func); 63 | } 64 | 65 | void radio_set_irq_timeout (ostime_t timeout) { 66 | // schedule irq-protected timeout function 67 | os_setProtectedTimedCallback(&state.irqjob, timeout, radio_irq_timeout); 68 | } 69 | 70 | // (run by irqjob) 71 | static void radio_irq_func (osjob_t* j) { 72 | // call radio-specific processing function 73 | if( radio_irq_process(state.irqtime, state.diomask) ) { 74 | // current radio operation has completed 75 | radio_stop(); // (disable antenna switch and HAL irqs, make radio sleep) 76 | 77 | // run LMIC job (use preset func ptr) 78 | os_setCallback(&LMIC.osjob, LMIC.osjob.func); 79 | } 80 | 81 | // clear irq state (job has been run) 82 | state.diomask = 0; 83 | } 84 | 85 | // called by hal exti IRQ handler 86 | // (all radio operations are performed on radio job!) 87 | void radio_irq_handler (u1_t diomask, ostime_t ticks) { 88 | BACKTRACE(); 89 | 90 | // make sure previous job has been run 91 | ASSERT( state.diomask == 0 ); 92 | 93 | // save interrupt source and time 94 | state.irqtime = ticks; 95 | state.diomask = diomask; 96 | 97 | // schedule irq job 98 | // (timeout job will be replaced, intermediate interrupts must rewind timeout!) 99 | os_setCallback(&state.irqjob, radio_irq_func); 100 | } 101 | 102 | void os_radio (u1_t mode) { 103 | switch (mode) { 104 | case RADIO_STOP: 105 | radio_stop(); 106 | break; 107 | 108 | case RADIO_TX: 109 | radio_stop(); 110 | #ifdef DEBUG_TX 111 | debug_printf("TX[fcnt=%d,freq=%.1F,sf=%d,bw=%d,pow=%d,len=%d%s]: %.80h\r\n", 112 | LMIC.seqnoUp - 1, LMIC.freq, 6, getSf(LMIC.rps) + 6, 125 << getBw(LMIC.rps), 113 | LMIC.txpow, LMIC.dataLen, 114 | (LMIC.pendTxPort != 0 && (LMIC.frame[OFF_DAT_FCT] & FCT_ADRARQ)) ? ",ADRARQ" : "", 115 | LMIC.frame, LMIC.dataLen); 116 | #endif 117 | // set timeout for tx operation (should not happen) 118 | state.txmode = 1; 119 | radio_set_irq_timeout(os_getTime() + ms2osticks(20) + LMIC_calcAirTime(LMIC.rps, LMIC.dataLen) * 110 / 100); 120 | // transmit frame now (wait for completion interrupt) 121 | radio_starttx(false); 122 | break; 123 | 124 | case RADIO_RX: 125 | radio_stop(); 126 | // set timeout for rx operation (should not happen, might be updated by radio driver) 127 | state.txmode = 0; 128 | radio_set_irq_timeout(LMIC.rxtime + ms2osticks(5) + LMIC_calcAirTime(LMIC.rps, 255) * 110 / 100); 129 | // receive frame at rxtime/now (wait for completion interrupt) 130 | radio_startrx(false); 131 | break; 132 | 133 | case RADIO_RXON: 134 | radio_stop(); 135 | // start scanning for frame now (wait for completion interrupt) 136 | state.txmode = 0; 137 | radio_startrx(true); 138 | break; 139 | 140 | case RADIO_TXCW: 141 | radio_stop(); 142 | // transmit continuous wave (until abort) 143 | radio_cw(); 144 | break; 145 | 146 | case RADIO_CCA: 147 | radio_stop(); 148 | // clear channel assessment 149 | radio_cca(); 150 | break; 151 | 152 | case RADIO_INIT: 153 | // reset and calibrate radio (uses LMIC.freq) 154 | radio_init(true); 155 | break; 156 | 157 | case RADIO_TXCONT: 158 | radio_stop(); 159 | radio_starttx(true); 160 | break; 161 | 162 | case RADIO_CAD: 163 | radio_stop(); 164 | // set timeout for cad/rx operation (should not happen, might be updated by radio driver) 165 | state.txmode = 0; 166 | radio_set_irq_timeout(os_getTime() + ms2osticks(10) + LMIC_calcAirTime(LMIC.rps, 255) * 110 / 100); 167 | // channel activity detection and rx if preamble symbol found 168 | radio_cad(); 169 | break; 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /lmic/region.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | // 3 | // This file is subject to the terms and conditions defined in file 'LICENSE', 4 | // which is part of this source code package. 5 | 6 | #ifndef _region_h_ 7 | #define _region_h_ 8 | 9 | // public region codes - DO NOT CHANGE! 10 | enum { 11 | REGCODE_UNDEF = 0, 12 | REGCODE_EU868 = 1, 13 | REGCODE_AS923 = 2, 14 | REGCODE_US915 = 3, 15 | REGCODE_AU915 = 4, 16 | REGCODE_CN470 = 5, 17 | REGCODE_IN865 = 6, 18 | }; 19 | 20 | // ------------------------------------------------ 21 | // EU868 22 | #ifdef CFG_eu868 23 | 24 | #define REG_DYN 25 | #define REG_DRTABLE_EU 26 | 27 | #endif 28 | 29 | 30 | // ------------------------------------------------ 31 | // AS923 32 | #ifdef CFG_as923 33 | 34 | #define REG_DYN 35 | #define REG_DRTABLE_EU 36 | 37 | #endif 38 | 39 | 40 | // ------------------------------------------------ 41 | // IL915 42 | #ifdef CFG_il915 43 | 44 | #define REG_DYN 45 | #define REG_DRTABLE_EU 46 | 47 | #endif 48 | 49 | 50 | // ------------------------------------------------ 51 | // KR920 52 | #ifdef CFG_kr920 53 | 54 | #define REG_DYN 55 | #define REG_DRTABLE_125kHz 56 | 57 | #endif 58 | 59 | 60 | // ------------------------------------------------ 61 | // US915 62 | #ifdef CFG_us915 63 | 64 | #define REG_FIX 65 | #define REG_DRTABLE_US 66 | 67 | #if MAX_FIX_CHNLS_125 < 64 68 | #undef MAX_FIX_CHNLS_125 69 | #define MAX_FIX_CHNLS_125 64 70 | #endif 71 | 72 | #if MAX_FIX_CHNLS_500 < 8 73 | #undef MAX_FIX_CHNLS_500 74 | #define MAX_FIX_CHNLS_500 8 75 | #endif 76 | 77 | #endif 78 | 79 | // ------------------------------------------------ 80 | // AU915 81 | #ifdef CFG_au915 82 | 83 | #define REG_FIX 84 | #define REG_DRTABLE_AU 85 | 86 | #if MAX_FIX_CHNLS_125 < 64 87 | #undef MAX_FIX_CHNLS_125 88 | #define MAX_FIX_CHNLS_125 64 89 | #endif 90 | 91 | #if MAX_FIX_CHNLS_500 < 8 92 | #undef MAX_FIX_CHNLS_500 93 | #define MAX_FIX_CHNLS_500 8 94 | #endif 95 | 96 | #endif 97 | 98 | 99 | // ------------------------------------------------ 100 | // CN470 101 | #ifdef CFG_cn470 102 | 103 | #define REG_FIX 104 | #define REG_DRTABLE_125kHz 105 | 106 | #if MAX_FIX_CHNLS_125 < 96 107 | #undef MAX_FIX_CHNLS_125 108 | #define MAX_FIX_CHNLS_125 96 109 | #endif 110 | 111 | #endif 112 | 113 | 114 | // ------------------------------------------------ 115 | // IN865 116 | #ifdef CFG_in865 117 | 118 | #define REG_DYN 119 | #define REG_DRTABLE_IN 120 | 121 | #endif 122 | 123 | 124 | // ------------------------------------------------ 125 | // Sanity checks 126 | 127 | #if !defined(REG_DYN) && !defined(REG_FIX) 128 | #error "No regions defined" 129 | #endif 130 | 131 | 132 | // ------------------------------------------------ 133 | // Derived values 134 | 135 | #if defined(REG_FIX) 136 | 137 | #ifndef MAX_FIX_CHNLS_500 138 | #define MAX_FIX_CHNLS_500 0 139 | #endif 140 | 141 | #define MAX_FIX_CHNLS (MAX_FIX_CHNLS_125 + MAX_FIX_CHNLS_500) 142 | 143 | #endif 144 | 145 | #endif 146 | -------------------------------------------------------------------------------- /projects/.gitignore: -------------------------------------------------------------------------------- 1 | Makefile.local 2 | build 3 | build-* 4 | -------------------------------------------------------------------------------- /projects/ex-fuota/Makefile: -------------------------------------------------------------------------------- 1 | TARGET := b_l072z_lrwan1 2 | #TARGET := nucleo_l073rz-sx1276mb1las 3 | #TARGET := nucleo_l073rz-sx1272mbed 4 | #TARGET := nucleo_l073rz-sx1276mb1mas 5 | #TARGET := nucleo_l053r8-sx1276mb1las 6 | #TARGET := nucleo_l053r8-sx1261mbed 7 | #TARGET := nucleo_l053r8-sx1262mbed 8 | 9 | VARIANTS := eu868 us915 hybrid simul 10 | 11 | REGIONS.simul := eu868 12 | TARGET.simul := unicorn 13 | 14 | 15 | CFLAGS += -Os 16 | CFLAGS += -g 17 | CFLAGS += -Wall -Wno-main 18 | 19 | SVCS += app 20 | 21 | DEFS += -DDEBUG_RX 22 | DEFS += -DDEBUG_TX 23 | 24 | LMICCFG += eeprom_region 25 | LMICCFG += DEBUG 26 | LMICCFG += extapi 27 | 28 | include ../projects.gmk 29 | 30 | ifeq (simul,$(VARIANT)) 31 | test: build-$(VARIANT)/$(PROJECT).hex $(BL_BUILD)/bootloader.hex 32 | PYTHONPATH=$${PYTHONPATH}:$(TOPDIR)/unicorn/simul:$(SVCSDIR)/fuota:$(TOPDIR)/basicloader/tools/fwtool \ 33 | TEST_HEXFILES='$^' \ 34 | ward $(WARDOPTS) 35 | endif 36 | 37 | 38 | .PHONY: test apptest fuotatest 39 | -------------------------------------------------------------------------------- /projects/ex-fuota/README.rst: -------------------------------------------------------------------------------- 1 | BasicMAC FUOTA 2 | ============== 3 | 4 | *BasicMAC* offers a defragmentation service (``"fuota"``) to reconstruct firmware updates from redundantly received fragments. Once fully reconstructed, the application can verify the digital signature of an update and pass it on to the *BasicLoader* boot loader. *BasicLoader* can process and install update files (``.up``) generated by ``zfwtool``. 5 | 6 | 7 | Firmware Update Files 8 | --------------------- 9 | 10 | Firmware update files (``.up``) are created from one or more firmware files (``.zfw``) using ``zfwtool``. A firmware update file contains a compressed firmware image and can be either self-contained or a delta to a previous firmware. Optionally the update can be appended with a digital signature. The firmware files (``.zfw``) are generated by the ``make`` process in the build directory and contain a binary firmware image plus some meta information. 11 | 12 | - generate self-contained firmware update file 13 | ``python zfwtool.py mkupdate -s mykey.pem --passphrase mysecret firmware.zfw firmware.up`` 14 | 15 | - generate delta firmware update file 16 | ``python zfwtool.py mkupdate -s mykey.pem --passphrase mysecret firmware2.zfw -d firmware1.zfw delta-1-2.up`` 17 | 18 | 19 | Code-Signing Keys 20 | ----------------- 21 | 22 | To sign the firmware update with ``zfwtool`` a code-signing key pair is required. The private key must be kept secret to the application owner and resides in a passphrase-protected ``.pem`` file. The public key must be embedded in the application and is used as trust anchor to verify the signature of a received update file. The key pair can be created and managed using *OpenSSL*. 23 | 24 | - generate EC key pair in passphrase-protected ``.pem`` file 25 | ``openssl ecparam -name prime256v1 -genkey | openssl ec -out mykey.pem -aes256 -passout pass:mysecret`` 26 | 27 | - print public key from passphrase-protected ``.pem`` file (last 64 bytes of DER struct as C byte array) 28 | ``openssl ec -in mykey.pem -passin pass:mysecret -pubout -outform der | xxd -i -s 27 -c 16`` 29 | 30 | - embed output in C code (``fuota.c``) 31 | :: 32 | 33 | static const unsigned char pubkey[64] = { 34 | 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 35 | 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 36 | 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 37 | 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 38 | }; 39 | 40 | 41 | FUOTA Session 42 | ------------- 43 | A FUOTA session is the process of reconstructing the original firmware update file on the device from subsequently received redundant fragments. The session state consists of the stored fragment data, a redundancy matrix, and the session parameters *session-id*, *frag-size* and *frag-cnt*. During a FUOTA session the session parameters must not be changed! The session state is stored persistently, so the FUOTA session can be resumed after the device has been reset. 44 | 45 | *session-id* 46 | Content identifier for the session in progress (e.g. firmware version). 47 | 48 | *frag-size* 49 | Size of the fragments (multiple of 4). 50 | 51 | *frag-cnt* 52 | Total number of fragments in the update file. *frag-cnt* = (*updatelen* + *frag-size* - 1) / *frag-size*. 53 | 54 | 55 | Fragmentation 56 | ------------- 57 | 58 | To deliver a firmware update over LoRaWAN, the update file (``.up``) needs to be split into smaller fragments and must be redundancy-encoded. The size of the fragments (*frag-size*) must be chosen so that it always fits in the allowed LoRaWAN payload size of the region and data rate in use. Additionally the fragment size must be a multiple of 4 bytes. See example script ``fragger.py`` for generation of fragment data and downlink payloads. 59 | 60 | 61 | Example Application ex-fuota 62 | ============================ 63 | 64 | The example application sends periodic uplinks with application data on port 15 every 60 seconds. When it receives a downlink on port 16, the payload is interpreted as FUOTA message and is processed accordingly. On reception of a FUOTA message three uplinks with the current FUOTA status will be generated on port 16 in an interval of 10 seconds. This way the application server can monitor progress and gets additional downlink opportunities to deliver more fragments. 65 | 66 | When enough fragments have been received to fully reconstruct the original update file, the digital signature is validated and the update is passed on to *BasicLoader* and a device reset is triggered. After the reset the boot loader will install the update and start the new firmware. 67 | 68 | In addition to the fragment data the FUOTA message in the downlink also holds a small header with the FUOTA session parameters and an index describing the redundancy matrix of the fragment data. 69 | 70 | The session parameters *frag-size*, *frag-cnt*, *src-crc* and *dst-crc* must not change during a FUOTA session! When these parameters change, the current session is discarded and a new session with the new parameters is started. 71 | 72 | **NOTE:** All multi-byte integers (including CRCs) are encoded LSBF! 73 | 74 | 75 | Downlink Format (port 16) 76 | ------------------------- 77 | 78 | =========== ============== ============= =========== 79 | Field Offset (bytes) Size (bytes) Description 80 | =========== ============== ============= =========== 81 | *src-crc* 0 2 short CRC of delta firmware or 0 82 | *dst-crc* 2 2 short CRC of updated firmware 83 | *frag-cnt* 4 2 total number of fragments in update file 84 | *frag-idx* 6 2 fragment index (redundancy matrix generator) 85 | *frag-data* 8 *frag-size* fragment data... 86 | =========== ============== ============= =========== 87 | 88 | 89 | Uplink Format (port 16) 90 | ----------------------- 91 | 92 | =========== ============== ============= =========== 93 | Field Offset (bytes) Size (bytes) Description 94 | =========== ============== ============= =========== 95 | *fw-crc* 0 4 CRC of current firmware 96 | *done-cnt* 4 2 number of reconstructed fragments 97 | *frag-cnt* 6 2 total number of fragments in update file 98 | =========== ============== ============= =========== 99 | -------------------------------------------------------------------------------- /projects/ex-fuota/app.svc: -------------------------------------------------------------------------------- 1 | hook.appstart: app_main 2 | hook.lwm_downlink: app_dl 3 | 4 | require: 5 | - appstart 6 | - lwmux 7 | - lwtest 8 | - eckm 9 | - perso 10 | - fuota 11 | 12 | # vim: syntax=yaml 13 | -------------------------------------------------------------------------------- /projects/ex-fuota/fragger.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020-2022 Michael Kuyper. All rights reserved. 2 | # 3 | # This file is subject to the terms and conditions defined in file 'LICENSE', 4 | # which is part of this source code package. 5 | 6 | from zfwtool import Update 7 | import frag 8 | import random 9 | import struct 10 | import sys 11 | 12 | # check usage 13 | if len(sys.argv) != 2: 14 | print('usage: %s ' % sys.argv[0]) 15 | exit() 16 | 17 | # load update file 18 | updata = open(sys.argv[1], 'rb').read() 19 | up = Update.fromfile(updata) 20 | 21 | # short CRC of updated firmware 22 | dst_crc = up.fwcrc & 0xFFFF 23 | 24 | # short CRC of referenced firmware in case of delta update or 0 25 | src_crc = struct.unpack_from(' 0 ) { 30 | // schedule next fuota status in 10 seconds 31 | os_setApproxTimedCallback(&fuotajob, os_getTime() + sec2osticks(10), nextfuotastatus); 32 | } 33 | } 34 | 35 | static bool txfuotastatus (lwm_txinfo* txinfo) { 36 | static unsigned char msgbuf[8]; 37 | report_fuota(msgbuf); 38 | txinfo->data = msgbuf; 39 | txinfo->dlen = 8; 40 | txinfo->port = 16; 41 | txinfo->txcomplete = txfuotadonecb; 42 | return true; 43 | } 44 | 45 | static void nextfuotastatus (osjob_t* job) { 46 | lwm_request_send(&fuotalwmjob, 0, txfuotastatus); 47 | } 48 | 49 | ////////////////////////////////////////////////////////////////////// 50 | // periodic uplinks 51 | ////////////////////////////////////////////////////////////////////// 52 | 53 | static void txperiodicdonecb (void) { 54 | // schedule next periodic transmission in 60 seconds 55 | os_setApproxTimedCallback(&periodicjob, os_getTime() + sec2osticks(60), nextperiodic); 56 | } 57 | 58 | static bool txperiodic (lwm_txinfo* txinfo) { 59 | txinfo->data = (unsigned char*) "hello"; 60 | txinfo->dlen = 5; 61 | txinfo->port = 15; 62 | txinfo->txcomplete = txperiodicdonecb; 63 | return true; 64 | } 65 | 66 | static void nextperiodic (osjob_t* job) { 67 | lwm_request_send(&periodiclwmjob, 0, txperiodic); 68 | } 69 | 70 | ////////////////////////////////////////////////////////////////////// 71 | 72 | 73 | // lwm_downlink hook 74 | void app_dl (int port, unsigned char* data, int dlen, unsigned int flags) { 75 | debug_printf("DL[%d]: %h\r\n", port, data, dlen); 76 | 77 | // check for FUOTA data on dedicated port 78 | if( port == 16 ) { 79 | // process fragment 80 | process_fuota(data, dlen); 81 | // respond with FUOTA status/progress 3 times to create more downlink opportunities 82 | fuotacnt = 3; 83 | nextfuotastatus(&fuotajob); 84 | } 85 | } 86 | 87 | bool app_main (osjob_t* job) { 88 | debug_printf("Hello World!\r\n"); 89 | 90 | // join network 91 | lwm_setmode(LWM_MODE_NORMAL); 92 | 93 | // initiate first uplink 94 | nextperiodic(job); 95 | 96 | // indicate that we are running 97 | return true; 98 | } 99 | -------------------------------------------------------------------------------- /projects/ex-fuota/test_exfuota.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020-2022 Michael Kuyper. All rights reserved. 2 | # 3 | # This file is subject to the terms and conditions defined in file 'LICENSE', 4 | # which is part of this source code package. 5 | 6 | import asyncio 7 | 8 | from devtest import vtime, DeviceTest 9 | 10 | from ward import fixture, test 11 | 12 | from zfwtool import Update 13 | import frag 14 | import random 15 | import struct 16 | 17 | @fixture 18 | async def createtest(_=vtime): 19 | dut = DeviceTest() 20 | dut.start() 21 | yield dut 22 | await dut.stop() 23 | 24 | 25 | @test('Join') 26 | async def _(dut=createtest): 27 | await dut.join() 28 | await dut.updf() 29 | 30 | 31 | @test('Uplink') 32 | async def _(dut=createtest): 33 | await dut.join() 34 | t1 = None 35 | for _ in range(5): 36 | m = await dut.updf() 37 | assert m.rtm['FRMPayload'] == b'hello' 38 | t0 = t1 39 | t1 = asyncio.get_event_loop().time() 40 | if t0 is not None: 41 | td = (t1 - t0) 42 | assert td >= 60 43 | assert td <= 70 44 | 45 | 46 | @test('Downlink') 47 | async def _(dut=createtest): 48 | await dut.join() 49 | m = await dut.updf() 50 | dut.dndf(m, 15, b'hi there') 51 | await asyncio.sleep(5) 52 | 53 | 54 | @test('FUOTA') 55 | async def _(dut=createtest): 56 | # load update file (self-contained update signed with testkey.pem) 57 | updata = open('hallosim.up', 'rb').read() 58 | up = Update.fromfile(updata) 59 | 60 | # short CRC of updated firmware 61 | dst_crc = up.fwcrc & 0xFFFF 62 | 63 | # short CRC of referenced firmware 64 | src_crc = 0 65 | 66 | # choose fragment size (multiple of 4, fragment data plus 8-byte header must fit LoRaWAN payload size!) 67 | frag_size = 192 68 | 69 | # initialize fragment generator 70 | fc = frag.FragCarousel.fromfile(updata, frag_size) 71 | 72 | await dut.join() 73 | for i in range(fc.cct + 20): 74 | try: 75 | m = await dut.updf() 76 | except ValueError: 77 | # device will send join-request after update complete 78 | break 79 | 80 | # print progress 81 | if m.rtm['FPort'] == 16: 82 | (fwcrc, donecnt, totalcnt) = struct.unpack('data = (unsigned char*) "hello"; 22 | txinfo->dlen = 5; 23 | txinfo->port = 15; 24 | txinfo->txcomplete = txc; 25 | return true; 26 | } 27 | 28 | static void next (osjob_t* job) { 29 | lwm_request_send(&lj, 0, tx); 30 | } 31 | 32 | void app_dl (int port, unsigned char* data, int dlen, unsigned int flags) { 33 | debug_printf("DL[%d]: %h\r\n", port, data, dlen); 34 | } 35 | 36 | bool app_main (osjob_t* job) { 37 | debug_printf("Hello World!\r\n"); 38 | 39 | // join network 40 | lwm_setmode(LWM_MODE_NORMAL); 41 | 42 | // re-use current job 43 | mainjob = job; 44 | 45 | // initiate first uplink 46 | next(mainjob); 47 | 48 | // indicate that we are running 49 | return true; 50 | } 51 | -------------------------------------------------------------------------------- /projects/ex-join/test_exjoin.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020-2020 Michael Kuyper. All rights reserved. 2 | # 3 | # This file is subject to the terms and conditions defined in file 'LICENSE', 4 | # which is part of this source code package. 5 | 6 | import asyncio 7 | 8 | from devtest import vtime, DeviceTest 9 | 10 | from ward import fixture, test 11 | 12 | @fixture 13 | async def createtest(_=vtime): 14 | dut = DeviceTest() 15 | dut.start() 16 | yield dut 17 | await dut.stop() 18 | 19 | 20 | @test('Join') 21 | async def _(dut=createtest): 22 | await dut.join() 23 | await dut.updf() 24 | 25 | 26 | @test('Uplink') 27 | async def _(dut=createtest): 28 | await dut.join() 29 | t1 = None 30 | for _ in range(5): 31 | m = await dut.updf() 32 | assert m.rtm['FRMPayload'] == b'hello' 33 | t0 = t1 34 | t1 = asyncio.get_event_loop().time() 35 | if t0 is not None: 36 | td = (t1 - t0) 37 | assert td >= 5 38 | assert td <= 10 39 | 40 | 41 | @test('Downlink') 42 | async def _(dut=createtest): 43 | await dut.join() 44 | m = await dut.updf() 45 | dut.dndf(m, 15, b'hi there') 46 | await asyncio.sleep(5) 47 | -------------------------------------------------------------------------------- /projects/ex-join/test_lwcert103eu868.py: -------------------------------------------------------------------------------- 1 | ../../services/lwtest/test_lwcert103eu868.py -------------------------------------------------------------------------------- /projects/ex-join/test_perso.py: -------------------------------------------------------------------------------- 1 | ../../services/perso/test_perso.py -------------------------------------------------------------------------------- /projects/fam-devboards.mk: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | # 3 | # This file is subject to the terms and conditions defined in file 'LICENSE', 4 | # which is part of this source code package. 5 | 6 | # ------------------------------------------------ 7 | # Family: Development Boards 8 | 9 | ifneq (,$(filter nucleo_l073rz,$(FAMILIES))) 10 | MCU := stm32l0 11 | LD_SCRIPTS += $(BL)/src/arm/stm32lx/ld/STM32L0xxZ.ld 12 | DEFS += -DSTM32L0 -DSTM32L073xx 13 | DEFS += -DCFG_nucleo_board 14 | DEFS += -DBRD_IMPL_INC='"brd_devboards.h"' 15 | OOCFGS += $(TOOLSDIR)/openocd/nucleo-l0.cfg 16 | BL_BRD := NUCLEO-L073RZ 17 | endif 18 | 19 | ifneq (,$(filter nucleo_l053r8,$(FAMILIES))) 20 | MCU := stm32l0 21 | LD_SCRIPTS += $(BL)/src/arm/stm32lx/ld/STM32L0xx8.ld 22 | DEFS += -DSTM32L0 -DSTM32L053xx 23 | DEFS += -DCFG_nucleo_board 24 | DEFS += -DBRD_IMPL_INC='"brd_devboards.h"' 25 | OOCFGS += $(TOOLSDIR)/openocd/nucleo-l0.cfg 26 | BL_BRD := NUCLEO-L053R8 27 | endif 28 | 29 | ifneq (,$(filter sx1272mbed,$(FAMILIES))) 30 | DEFS += -DCFG_sx1272mbed 31 | endif 32 | 33 | ifneq (,$(filter sx1276mb1las,$(FAMILIES))) 34 | DEFS += -DCFG_sx1276mb1las 35 | endif 36 | 37 | ifneq (,$(filter sx1276mb1mas,$(FAMILIES))) 38 | DEFS += -DCFG_sx1276mb1mas 39 | endif 40 | 41 | ifneq (,$(filter sx1261mbed,$(FAMILIES))) 42 | DEFS += -DCFG_sx1261mbed 43 | endif 44 | 45 | ifneq (,$(filter sx1262mbed,$(FAMILIES))) 46 | DEFS += -DCFG_sx1262mbed 47 | endif 48 | 49 | ifneq (,$(filter b_l072z_lrwan1,$(FAMILIES))) 50 | MCU := stm32l0 51 | LD_SCRIPTS += $(BL)/src/arm/stm32lx/ld/STM32L0xxZ.ld 52 | DEFS += -DSTM32L0 -DSTM32L072xx 53 | DEFS += -DCFG_b_l072Z_lrwan1_board 54 | DEFS += -DBRD_IMPL_INC='"brd_devboards.h"' 55 | OOCFGS += $(TOOLSDIR)/openocd/nucleo-l0.cfg 56 | BL_BRD := B-L072Z-LRWAN1 57 | endif 58 | -------------------------------------------------------------------------------- /projects/fam-simul.mk: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | # 3 | # This file is subject to the terms and conditions defined in file 'LICENSE', 4 | # which is part of this source code package. 5 | 6 | # ------------------------------------------------ 7 | # Family: Simulation 8 | 9 | ifneq (,$(filter unicorn,$(FAMILIES))) 10 | MCU := unicorn 11 | LD_SCRIPTS += $(BL)/src/arm/unicorn/ld/mem.ld 12 | CFLAGS += -Dunicorn 13 | BL_BRD := simul-unicorn 14 | endif 15 | -------------------------------------------------------------------------------- /projects/platform.mk: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | # 3 | # This file is subject to the terms and conditions defined in file 'LICENSE', 4 | # which is part of this source code package. 5 | 6 | # ------------------------------------------------ 7 | # Target 8 | # (hyphen-concatenated list of families) 9 | 10 | FAMILIES := $(subst -, ,$(TARGET)) 11 | 12 | 13 | # ------------------------------------------------ 14 | # Families 15 | 16 | FAM_MAKEFILES += $(wildcard $(TOPDIR)/projects/fam-*.mk) 17 | -include $(FAM_MAKEFILES) 18 | 19 | 20 | # ------------------------------------------------ 21 | # MCUs 22 | 23 | ifeq ($(MCU:stm32%=stm32),stm32) 24 | TOOLCHAIN := gcc 25 | CROSS_COMPILE:=arm-none-eabi- 26 | CFLAGS += -fno-common -fno-builtin -fno-exceptions -ffunction-sections -fdata-sections -fomit-frame-pointer 27 | HALDIR := $(TOPDIR)/stm32 28 | CMSIS := $(TOPDIR)/stm32/CMSIS 29 | CFLAGS += -I$(CMSIS)/Include 30 | CFLAGS += -I$(BL)/src/common 31 | CFLAGS += -I$(BL)/src/arm/stm32lx 32 | CFLAGS += -DHAL_IMPL_INC=\"hal_stm32.h\" 33 | LDFLAGS += -nostartfiles 34 | LDFLAGS += $(addprefix -T,$(LD_SCRIPTS)) 35 | OOCFGS += $(TOOLSDIR)/openocd/flash.cfg 36 | ifeq ($(MCU),stm32l0) 37 | FLAGS += -mcpu=cortex-m0plus -mthumb 38 | CFLAGS += -I$(CMSIS)/Device/ST/STM32L0xx/Include/ 39 | LD_SCRIPTS += $(HALDIR)/fw.ld 40 | OBJS_BLACKLIST += blipper.o 41 | OBJS_BLACKLIST += spi.o # these do not build yet for L0 42 | endif 43 | ifeq ($(MCU),stm32l1) 44 | FLAGS += -mcpu=cortex-m3 -mthumb 45 | CFLAGS += -I$(CMSIS)/Device/ST/STM32L1xx/Include/ 46 | LD_SCRIPTS += STM32L1.ld 47 | OBJS_BLACKLIST += i2c.o adc.o # these do not build yet for L1 48 | endif 49 | ALL += $(BUILDDIR)/$(PROJECT).hex 50 | ALL += $(BUILDDIR)/$(PROJECT).bin 51 | ALL += $(BUILDDIR)/$(PROJECT).zfw 52 | LOAD = loadhex 53 | endif 54 | 55 | ifeq ($(MCU),unicorn) 56 | TOOLCHAIN := gcc 57 | CROSS_COMPILE:=arm-none-eabi- 58 | HALDIR := $(TOPDIR)/unicorn 59 | FLAGS += -mcpu=cortex-m0plus -mthumb 60 | CFLAGS += -fno-common -fno-builtin -fno-exceptions -ffunction-sections -fdata-sections -fomit-frame-pointer 61 | CFLAGS += -I$(BL)/src/common 62 | CFLAGS += -I$(BL)/src/arm/unicorn 63 | CFLAGS += -DHAL_IMPL_INC=\"hal_unicorn.h\" 64 | LDFLAGS += -nostartfiles 65 | LDFLAGS += $(addprefix -T,$(LD_SCRIPTS)) 66 | LD_SCRIPTS += $(HALDIR)/fw.ld 67 | ALL += $(BUILDDIR)/$(PROJECT).hex 68 | LOAD = dummy 69 | endif 70 | 71 | 72 | # ------------------------------------------------ 73 | # Build tools 74 | 75 | BL ?= $(TOPDIR)/basicloader 76 | BL_BRD ?= $(error "No basic loader board set") 77 | BL_BUILD ?= $(BL)/build/boards/$(BL_BRD) 78 | 79 | ifneq (ok,$(shell python3 -c 'import sys; print("ok" if sys.hexversion >= 0x03060000 else "")')) 80 | $(error "Python 3.6 or newer required") 81 | endif 82 | 83 | SVCTOOL = $(TOOLSDIR)/svctool/svctool.py 84 | 85 | FWTOOL = $(BL)/tools/fwtool/fwtool.py 86 | ZFWTOOL = $(BL)/tools/fwtool/zfwtool.py 87 | 88 | 89 | # ------------------------------------------------ 90 | # Miscellaneous settings 91 | 92 | ifeq ($(TOOLCHAIN),gcc) 93 | CFLAGS += -MMD -MP -std=gnu11 94 | LDFLAGS += -Wl,--gc-sections -Wl,-Map,$(basename $@).map 95 | CC := $(CROSS_COMPILE)gcc 96 | AS := $(CROSS_COMPILE)as 97 | LD := $(CROSS_COMPILE)gcc 98 | HEX := $(CROSS_COMPILE)objcopy -O ihex 99 | BIN := $(CROSS_COMPILE)objcopy -O binary 100 | GDB := $(CROSS_COMPILE)gdb 101 | AR := $(CROSS_COMPILE)ar 102 | OPENOCD ?= openocd 103 | endif 104 | 105 | 106 | # ------------------------------------------------ 107 | # vim: filetype=make 108 | -------------------------------------------------------------------------------- /projects/projects.gmk: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | # 3 | # This file is subject to the terms and conditions defined in file 'LICENSE', 4 | # which is part of this source code package. 5 | 6 | -include Makefile.local 7 | -include ../Makefile.local 8 | 9 | TOPDIR ?= ../.. 10 | 11 | include $(TOPDIR)/projects/variants.mk 12 | include $(TOPDIR)/projects/platform.mk 13 | 14 | #TARGET ?= $(error "TARGET not specified (See platform.gmk for possible settings)") 15 | 16 | LMICDIR := $(TOPDIR)/lmic 17 | TOOLSDIR := $(TOPDIR)/tools 18 | COMMONDIR := $(TOPDIR)/common 19 | SVCSDIR := $(TOPDIR)/services 20 | BUILDDIR_PFX := build- 21 | 22 | PROJECT ?= $(notdir $(lastword $(CURDIR))) 23 | 24 | CFLAGS += $(FLAGS) 25 | LDFLAGS += $(FLAGS) 26 | ASFLAGS += $(FLAGS) 27 | 28 | DEFS += -DVARIANT_STR="\"$(VARIANT)\"" 29 | DEFS += -DPROJECT_STR="\"$(PROJECT)\"" 30 | DEFS += $(addprefix -DCFG_,$(REGIONS)) 31 | DEFS += $(addprefix -DCFG_,$(LMICCFG)) 32 | 33 | PDEFS += $(filter-out $(UNDEFS),$(DEFS)) 34 | CFLAGS += $(PDEFS) 35 | ASDEFS += $(PDEFS) 36 | 37 | CFLAGS += -I$(LMICDIR) -I$(HALDIR) 38 | CFLAGS += -I$(COMMONDIR) 39 | 40 | BUILDTIME := $(shell date -u +'%FT%TZ') 41 | 42 | PATCHFLAGS += $(addprefix --target=,$(HWID.$(VARIANT))) 43 | ifneq ($(RELEASE),) 44 | PATCHFLAGS += $(addprefix --version=,$(RELEASE)) 45 | else 46 | PATCHFLAGS += -v 47 | endif 48 | 49 | SRCS += $(notdir $(wildcard $(LMICDIR)/*.c $(HALDIR)/*.c *.c $(HALDIR)/*.S)) 50 | 51 | VPATH += $(LMICDIR) $(HALDIR) 52 | 53 | ifneq ($(COMMON),) 54 | SRCS += $(COMMON) 55 | VPATH += $(COMMONDIR) 56 | DEFS += $(addprefix -DCOMMON_,$(basename $(notdir $(COMMON)))) 57 | endif 58 | 59 | SVCTOOLARGS += -p $(SVCSDIR) -p . $(SVCS) 60 | SVCCHECK := $(shell $(SVCTOOL) check $(SVCTOOLARGS)) 61 | ifneq ($(SVCCHECK),) 62 | $(error $(SVCCHECK)) 63 | endif 64 | 65 | SVCDEFS_H = $(BUILDDIR)/svcdefs.h 66 | ifneq ($(SVCS),) 67 | VPATH += $(SVCSDIR) 68 | SVCSRCS := $(shell $(SVCTOOL) sources $(SVCTOOLARGS)) # only once 69 | SRCS += $(SVCSRCS) 70 | SVC_DEPS += $(SVCDEFS_H) 71 | CFLAGS += -I$(BUILDDIR) -I$(SVCSDIR) 72 | SVCSDEFS := $(shell $(SVCTOOL) defines $(SVCTOOLARGS)) # only once 73 | DEFS += $(addprefix -D,$(SVCSDEFS)) 74 | endif 75 | 76 | OBJS = $(filter-out $(addprefix $(BUILDDIR)/,$(OBJS_BLACKLIST)),$(patsubst %,$(BUILDDIR)/%.o,$(basename $(SRCS)))) 77 | 78 | ALL ?= error_all 79 | LOAD ?= error_load 80 | 81 | OOFLAGS += $(addprefix -f ,$(OOCFGS)) 82 | 83 | BUILDDIRS = $(sort $(dir $(OBJS))) 84 | 85 | MAKE_DEPS := $(MAKEFILE_LIST) # before we include all the *.d files... 86 | 87 | default: variant 88 | 89 | variant: $(VTARGET) 90 | 91 | variants: $(VTARGETS) 92 | 93 | variant-%: 94 | $(MAKE) -j8 variant VARIANT=$* 95 | 96 | all: $(ALL) 97 | 98 | error_all: 99 | $(error No output targets collected for this build) 100 | 101 | error_load: 102 | $(error No load target collected for this build) 103 | 104 | $(OBJS): $(MAKE_DEPS) 105 | 106 | $(OBJS): | $(BUILDDIRS) $(SVC_DEPS) 107 | 108 | $(BUILDDIR)/%.o: %.c 109 | $(CC) -c $(CFLAGS) $< -o $@ 110 | 111 | $(BUILDDIR)/%.o: %.s 112 | $(AS) $(ASFLAGS) $< -o $@ 113 | 114 | $(BUILDDIR)/%.o: %.S 115 | $(CC) -c $(ASFLAGS) $(ASDEFS) $< -o $@ 116 | 117 | $(BUILDDIR)/%.out: $(OBJS) 118 | $(LD) $(LDFLAGS) $^ $(LDLIBS) -o $@ 119 | 120 | $(BUILDDIR)/%.a: $(OBJS) 121 | $(AR) rcs $@ $^ -o $@ 122 | 123 | $(BUILDDIR)/%.unpatched.hex: $(BUILDDIR)/%.out 124 | $(HEX) $< $@ 125 | 126 | $(BUILDDIR)/%.zfw: $(BUILDDIR)/%.unpatched.hex 127 | $(ZFWTOOL) create --patch \ 128 | --meta project "$(PROJECT)" \ 129 | --meta variant "$(VARIANT)" \ 130 | --meta build_time "$(BUILDTIME)" \ 131 | $< $@ 132 | $(ZFWTOOL) info $@ 133 | 134 | $(BUILDDIR)/%.hex: $(BUILDDIR)/%.zfw 135 | $(ZFWTOOL) export $< $@ 136 | 137 | $(BUILDDIR)/%.bin: $(BUILDDIR)/%.zfw 138 | $(ZFWTOOL) export $< $@ 139 | 140 | $(SVCDEFS_H): $(MAKE_DEPS) | $(BUILDDIRS) 141 | $(SVCTOOL) svcdefs -o $@ -d $(SVCTOOLARGS) 142 | 143 | clean: 144 | rm -rf build/ $(BUILDDIR_PFX)* 145 | 146 | load: $(LOAD) 147 | 148 | loadhex: $(BUILDDIR)/$(PROJECT).hex 149 | $(OPENOCD) $(OOFLAGS) -c "flash_ihex $<" 150 | 151 | loadbl: 152 | $(OPENOCD) $(OOFLAGS) -c "flash_ihex $(BL_BUILD)/bootloader.hex" 153 | 154 | debug: $(BUILDDIR)/$(PROJECT).out 155 | $(GDB) $< -ex 'target remote | $(OPENOCD) $(OOFLAGS) -c "gdb_port pipe;"' -ex "monitor reset halt" 156 | 157 | $(BUILDDIRS): 158 | mkdir -p $@ 159 | 160 | .PHONY: default all clean load loadhex loadbin loadfw loadup loadbl loadosbl debug variant variants 161 | 162 | .SECONDARY: 163 | 164 | .DELETE_ON_ERROR: 165 | 166 | -include $(OBJS:.o=.d) $(SVCDEFS_H:.h=.d) 167 | 168 | # vim: filetype=make 169 | -------------------------------------------------------------------------------- /projects/variants.mk: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020-2020 Michael Kuyper. All rights reserved. 2 | # Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 3 | # 4 | # This file is subject to the terms and conditions defined in file 'LICENSE', 5 | # which is part of this source code package. 6 | 7 | VARIANTS ?= eu868 8 | 9 | RVARIANT := $(VARIANT) 10 | 11 | ifeq ($(VARIANT),) 12 | VTARGET := variants 13 | VARIANT := $(firstword $(filter $(DEFAULT_VARIANT),$(VARIANTS)) $(VARIANTS)) 14 | else 15 | VTARGET := all 16 | endif 17 | 18 | BUILDDIR = $(BUILDDIR_PFX)$(VARIANT) 19 | VTARGETS := $(addprefix variant-,$(VARIANTS)) 20 | 21 | # settings for "well-known" variants 22 | 23 | REGIONS.eu868 := eu868 24 | REGIONS.us915 := us915 25 | REGIONS.in865 := in865 26 | 27 | REGIONS.hybrid ?= eu868 us915 28 | 29 | ifneq (,$(REGIONS.$(VARIANT))) 30 | REGIONS = $(REGIONS.$(VARIANT)) 31 | endif 32 | 33 | ifneq (,$(TARGET.$(VARIANT))) 34 | TARGET = $(TARGET.$(VARIANT)) 35 | endif 36 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | bitarray 2 | cobs 3 | colorama 4 | intelhex 5 | liblora 6 | numpy 7 | unicorn 8 | git+https://github.com/mkuyper/ward.git 9 | -------------------------------------------------------------------------------- /services/appstart.svc: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020-2020 Michael Kuyper. All rights reserved. 2 | # Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 3 | # 4 | # This file is subject to the terms and conditions defined in file 'LICENSE', 5 | # which is part of this source code package. 6 | 7 | 8 | src: 9 | - appstart/main.c 10 | 11 | hooks: 12 | - bool appstart (osjob_t*) 13 | 14 | 15 | # vim: syntax=yaml 16 | -------------------------------------------------------------------------------- /services/appstart/main.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | // 3 | // This file is subject to the terms and conditions defined in file 'LICENSE', 4 | // which is part of this source code package. 5 | 6 | #include "lmic.h" 7 | 8 | #include "svcdefs.h" 9 | 10 | #ifdef SVC_backtrace 11 | #include "backtrace/backtrace.h" 12 | #endif 13 | 14 | static void initfunc (osjob_t* job) { 15 | #if defined(CFG_DEBUG) && CFG_DEBUG != 0 16 | unsigned char eui[8]; 17 | hal_fwi fwi; 18 | 19 | os_getDevEui(eui); 20 | hal_fwinfo(&fwi); 21 | 22 | debug_printf("id: %E | sn: %.16s | hw: 0x%03x | flash: %dK\r\n", 23 | eui, hal_serial(), hal_hwid(), fwi.flashsz >> 10); 24 | debug_printf("bl: v%d | fw: %s %s 0x%08x 0x%08x | boot: %s\r\n", 25 | fwi.blversion, 26 | PROJECT_STR, VARIANT_STR, fwi.version, fwi.crc, 27 | #if 0 28 | (BOOT_DEVINFO->bootmode == TABS_BOOT_UFT) ? "uft" : 29 | (BOOT_DEVINFO->bootmode == TABS_BOOT_SHIPPING) ? "ship" : 30 | (BOOT_DEVINFO->bootmode == TABS_BOOT_FLIGHT) ? "flight" : 31 | #endif 32 | "normal"); 33 | #endif 34 | 35 | #ifdef SVC_backtrace 36 | bt_print(); 37 | #endif 38 | 39 | // Application start hook 40 | SVCHOOK_appstart(job); 41 | } 42 | 43 | int main (void* bootarg) { 44 | osjob_t initjob; 45 | 46 | os_init(bootarg); 47 | os_setCallback(&initjob, initfunc); 48 | os_runloop(); 49 | 50 | // (not reached) 51 | return 0; 52 | } 53 | -------------------------------------------------------------------------------- /services/eckm.svc: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020-2020 Michael Kuyper. All rights reserved. 2 | # 3 | # This file is subject to the terms and conditions defined in file 'LICENSE', 4 | # which is part of this source code package. 5 | 6 | src: 7 | - eckm/eckm.c 8 | - micro-ecc/uECC.c 9 | 10 | require: 11 | - eefs 12 | 13 | hook.eefs_init: _eckm_init 14 | hook.eefs_fn: _eckm_eefs_fn 15 | 16 | 17 | # vim: syntax=yaml 18 | -------------------------------------------------------------------------------- /services/eckm/eckm.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2020 Michael Kuyper. All rights reserved. 2 | // 3 | // This file is subject to the terms and conditions defined in file 'LICENSE', 4 | // which is part of this source code package. 5 | 6 | #include 7 | 8 | #include "lmic.h" 9 | #include "eefs/eefs.h" 10 | #include "micro-ecc/uECC.h" 11 | 12 | #include "eckm.h" 13 | 14 | #include "svcdefs.h" // for type-checking hook functions 15 | 16 | #define CURVE uECC_secp256r1 17 | 18 | // 170eb959c8bb6770-4c85eac0 19 | static const uint8_t UFID_ECKM_CONFIG[12] = { 20 | 0x70, 0x67, 0xbb, 0xc8, 0x59, 0xb9, 0x0e, 0x17, 0xc0, 0xea, 0x85, 0x4c 21 | }; 22 | 23 | enum { 24 | F_INIT = (1 << 0), // key generated 25 | F_PAIRED = (1 << 1), // paired with join server 26 | }; 27 | 28 | typedef struct { 29 | uint32_t flags; // flags (F_*) 30 | uint8_t prikey[32]; // ECC private key 31 | uint32_t master[4]; // master key (16 bytes) 32 | uint8_t joineui[8]; // join EUI 33 | } eckm_config; 34 | 35 | static struct { 36 | uint8_t joineui[8]; 37 | uint8_t nwkkey[16]; 38 | uint8_t appkey[16]; 39 | } current; 40 | 41 | // src: 32 bytes -> 8 words or NULL 42 | // dest: 64 bytes -> 16 words 43 | static void xor64 (uint32_t* dest, uint32_t x, const uint32_t* src) { 44 | int i = 0; 45 | if( src ) { 46 | for( ; i < 8; i++ ) { 47 | dest[i] = src[i] ^ x; 48 | } 49 | } 50 | for( ; i < 16; i++ ) { 51 | dest[i] = x; 52 | } 53 | } 54 | 55 | typedef struct { 56 | uint32_t key[16]; 57 | union { 58 | uint32_t hash[8]; 59 | uint8_t msg[32]; 60 | }; 61 | } hmac_buf; 62 | 63 | // key must be NULL or 8 words 64 | static void hmac (uint32_t* hash, const uint32_t* key, hmac_buf* buf, int msglen) { 65 | xor64(buf->key, 0x36363636, key); 66 | sha256(buf->hash, (uint8_t*) buf, sizeof(buf->key) + msglen); 67 | xor64(buf->key, 0x5c5c5c5c, key); 68 | sha256(hash, (uint8_t*) buf, sizeof(buf->key) + sizeof(buf->hash)); 69 | } 70 | 71 | // key is 16 bytes (4 words) 72 | // output is 16 bytes 73 | // infolen must be < 32 74 | static void hkdf (uint8_t* dest, const uint32_t* key, const char* info, int infolen) { 75 | hmac_buf hmb; 76 | uint32_t hash[8]; 77 | 78 | for( int i = 0; i < 4; i++ ) { 79 | hmb.hash[i] = key[i]; 80 | } 81 | hmac(hash, NULL, &hmb, 16); 82 | 83 | ASSERT(infolen < sizeof(hmb.msg)); 84 | memcpy(hmb.msg, info, infolen); 85 | hmb.msg[infolen] = 0x01; 86 | hmac(hash, hash, &hmb, infolen + 1); 87 | 88 | memcpy(dest, hash, 16); 89 | } 90 | 91 | static uint32_t get_keyid (eckm_config* config) { 92 | uint32_t hash[8]; 93 | sha256(hash, (uint8_t*) config->prikey, 32); 94 | return hash[0]; 95 | } 96 | 97 | static void load (eckm_config* config) { 98 | if( eefs_read(UFID_ECKM_CONFIG, config, sizeof(eckm_config)) != sizeof(eckm_config) ) { 99 | config->flags = 0; 100 | } 101 | } 102 | 103 | static void init (void) { 104 | eckm_config config; 105 | load(&config); 106 | if( config.flags & F_PAIRED ) { 107 | memcpy(current.joineui, config.joineui, 8); 108 | hkdf(current.nwkkey, config.master, "nwkkey", 6); 109 | hkdf(current.appkey, config.master, "appkey", 6); 110 | } else { 111 | // use EEPROM settings 112 | memcpy(current.joineui, hal_joineui(), 8); 113 | memcpy(current.nwkkey, hal_nwkkey(), 16); 114 | memcpy(current.appkey, hal_appkey(), 16); 115 | } 116 | #if defined(CFG_DEBUG) 117 | debug_printf("eckm: flags = %08x\r\n", config.flags); 118 | if( config.flags & F_INIT) { 119 | debug_printf("eckm: keyid = %08X\r\n", get_keyid(&config)); 120 | } 121 | debug_printf("eckm: joineui = %E\r\n", current.joineui); 122 | #if defined(CFG_DEBUG_ECKM_KEYS) 123 | debug_printf("eckm: nwkkey = %h\r\n", current.nwkkey, 16); 124 | debug_printf("eckm: appkey = %h\r\n", current.appkey, 16); 125 | #endif 126 | #endif 127 | } 128 | 129 | static int eckm_rand (uint8_t* dest, unsigned int size) { 130 | while( size-- > 0 ) { 131 | *dest++ = os_getRndU1(); 132 | } 133 | return 1; 134 | } 135 | 136 | static bool commit (eckm_config* config) { 137 | if( eefs_save(UFID_ECKM_CONFIG, config, sizeof(eckm_config)) < 0 ) { 138 | return false; 139 | } else { 140 | init(); 141 | return true; 142 | } 143 | } 144 | 145 | bool eckm_initkey (void) { 146 | eckm_config config = { 147 | .flags = F_INIT, 148 | }; 149 | uint8_t pub[64]; 150 | 151 | uECC_set_rng(eckm_rand); 152 | if( uECC_make_key(pub, config.prikey, CURVE()) ) { 153 | return commit(&config); 154 | } 155 | return false; 156 | } 157 | 158 | bool eckm_pubkey (uint8_t* pubkey, uint32_t* keyid) { 159 | eckm_config config; 160 | load(&config); 161 | uint8_t pub[64]; 162 | if( (config.flags & F_INIT) 163 | && uECC_compute_public_key(config.prikey, pub, CURVE()) ) { 164 | if( pubkey ) { 165 | memcpy(pubkey, pub, 64); 166 | } 167 | if( keyid ) { 168 | *keyid = get_keyid(&config); 169 | } 170 | return true; 171 | } 172 | return false; 173 | } 174 | 175 | bool eckm_joineui (uint8_t* joineui) { 176 | eckm_config config; 177 | load(&config); 178 | if( (config.flags & F_PAIRED) ) { 179 | if( joineui ) { 180 | memcpy(joineui, config.joineui, 8); 181 | } 182 | return true; 183 | } 184 | return false; 185 | } 186 | 187 | bool eckm_pair (const uint8_t* joineui, const uint8_t* pubkey) { 188 | eckm_config config; 189 | load(&config); 190 | 191 | uint8_t secret[32]; 192 | 193 | if( (config.flags & F_INIT) 194 | && uECC_valid_public_key(pubkey, CURVE()) 195 | && uECC_shared_secret(pubkey, config.prikey, secret, CURVE()) ) { 196 | memcpy(config.master, secret, 16); 197 | memcpy(config.joineui, joineui, 8); 198 | config.flags |= F_PAIRED; 199 | return commit(&config); 200 | } 201 | return false; 202 | } 203 | 204 | void eckm_clear (void) { 205 | eefs_rm(UFID_ECKM_CONFIG); 206 | init(); 207 | } 208 | 209 | void _eckm_init (void) { 210 | init(); 211 | } 212 | 213 | const char* _eckm_eefs_fn (const uint8_t* ufid) { 214 | if( memcmp(ufid, UFID_ECKM_CONFIG, sizeof(UFID_ECKM_CONFIG)) == 0 ) { 215 | return "ch.mkdata.svc.eckm.config"; 216 | } 217 | return NULL; 218 | } 219 | 220 | void os_getDevEui (u1_t* buf) { 221 | memcpy(buf, hal_deveui(), 8); 222 | } 223 | 224 | void os_getJoinEui (u1_t* buf) { 225 | memcpy(buf, current.joineui, 8); 226 | } 227 | 228 | void os_getNwkKey (u1_t* buf) { 229 | memcpy(buf, current.nwkkey, 16); 230 | } 231 | 232 | void os_getAppKey (u1_t* buf) { 233 | memcpy(buf, current.appkey, 16); 234 | } 235 | -------------------------------------------------------------------------------- /services/eckm/eckm.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2020 Michael Kuyper. All rights reserved. 2 | // 3 | // This file is subject to the terms and conditions defined in file 'LICENSE', 4 | // which is part of this source code package. 5 | 6 | #ifndef _eckm_h_ 7 | #define _eckm_h_ 8 | 9 | #include 10 | 11 | // Generate a new device key-pair. This will erase any existing pairing. 12 | bool eckm_initkey (void); 13 | 14 | // Retrieve the device public key and/or key id. 15 | bool eckm_pubkey (uint8_t* pubkey, uint32_t* keyid); 16 | 17 | // Pair device with a join server. 18 | bool eckm_pair (const uint8_t* joineui, const uint8_t* pubkey); 19 | 20 | // Retrieve the join EUI. 21 | bool eckm_joineui (uint8_t* joineui); 22 | 23 | // Clear all ECKM state (erase key and pairing). 24 | void eckm_clear (void); 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /services/eefs.svc: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | # 3 | # This file is subject to the terms and conditions defined in file 'LICENSE', 4 | # which is part of this source code package. 5 | 6 | 7 | src: 8 | - eefs/eefs.c 9 | - eefs/picofs.c 10 | 11 | hooks: 12 | - void eefs_init (void) 13 | - void eefs_gc (const char* fn, int* pkeep) 14 | - const char* eefs_fn (const uint8_t* ufid) 15 | 16 | 17 | # vim: syntax=yaml 18 | -------------------------------------------------------------------------------- /services/eefs/eefs.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | // 3 | // This file is subject to the terms and conditions defined in file 'LICENSE', 4 | // which is part of this source code package. 5 | 6 | #include "lmic.h" 7 | #include "peripherals.h" 8 | 9 | #include "picofs.h" 10 | #include "eefs.h" 11 | 12 | #include "svcdefs.h" 13 | 14 | static struct { 15 | bool initialized; 16 | pfs fs; 17 | 18 | } state; 19 | 20 | #if defined(CFG_DEBUG) && CFG_DEBUG != 0 21 | static const char* fn (const uint8_t* ufid) { 22 | const char* name = SVCHOOK_eefs_fn(ufid); 23 | return name ?: "unknown"; 24 | } 25 | 26 | static void cb_debug_ls (int fh, const uint8_t* ufid, void* ctx) { 27 | unsigned int q1 = os_rlsbf4(ufid); 28 | unsigned int q2 = os_rlsbf4(ufid + 4); 29 | unsigned int i = os_rlsbf4(ufid + 8); 30 | debug_printf("eefs: %02x %4dB %08x%08x-%08x (%s)\r\n", 31 | fh, pfs_read_fh(&state.fs, fh, NULL, 0), q2, q1, i, fn(ufid)); 32 | } 33 | #endif 34 | 35 | void eefs_init (void* begin, unsigned int size) { 36 | pfs_init(&state.fs, begin, size / PFS_BLOCKSZ); 37 | state.initialized = true; 38 | #if defined(CFG_DEBUG) && CFG_DEBUG != 0 39 | pfs_ls(&state.fs, cb_debug_ls, NULL); 40 | #endif 41 | SVCHOOK_eefs_init(); 42 | } 43 | 44 | int eefs_read (const uint8_t* ufid, void* data, int sz) { 45 | ASSERT(state.initialized); 46 | return pfs_read(&state.fs, ufid, data, sz); 47 | } 48 | 49 | int eefs_save (const uint8_t* ufid, void* data, int sz) { 50 | ASSERT(state.initialized); 51 | int fh = pfs_save(&state.fs, ufid, data, sz); 52 | if( fh < 0 ) { 53 | // TODO: garbage collect 54 | fh = pfs_save(&state.fs, ufid, data, sz); 55 | } 56 | return fh; 57 | } 58 | 59 | bool eefs_rm (const uint8_t* ufid) { 60 | ASSERT(state.initialized); 61 | int fh = pfs_find(&state.fs, ufid); 62 | if( fh < 0 ) { 63 | return false; 64 | } else { 65 | pfs_rm_fh(&state.fs, fh); 66 | return true; 67 | } 68 | } 69 | 70 | // FNV32 hash (1a) 71 | void pfs_crc32 (uint32_t* fnv, unsigned char* buf, uint32_t len) { 72 | if( buf ) { 73 | uint32_t h = *fnv; 74 | while (len-- > 0) { 75 | h ^= *buf++; 76 | h += ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24)); 77 | } 78 | *fnv = h; 79 | } else { 80 | *fnv = 0x811c9dc5; // initialization value 81 | } 82 | } 83 | 84 | void pfs_write_block (void* dst, void* src, int nwords) { 85 | eeprom_copy(dst, src, nwords << 2); 86 | } 87 | 88 | uint8_t pfs_rnd_block (uint8_t nblks) { 89 | uint32_t rnd = (os_getRndU1() << 24) | (os_getRndU1() << 16) 90 | | (os_getRndU1() << 8) | os_getRndU1(); 91 | return rnd % nblks; 92 | } 93 | -------------------------------------------------------------------------------- /services/eefs/eefs.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | // 3 | // This file is subject to the terms and conditions defined in file 'LICENSE', 4 | // which is part of this source code package. 5 | 6 | #ifndef _eefs_h_ 7 | #define _eefs_h_ 8 | 9 | #include 10 | #include 11 | 12 | void eefs_init (void* begin, unsigned int size); 13 | int eefs_read (const uint8_t* ufid, void* data, int sz); 14 | int eefs_save (const uint8_t* ufid, void* data, int sz); 15 | bool eefs_rm (const uint8_t* ufid); 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /services/eefs/picofs.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | // 3 | // This file is subject to the terms and conditions defined in file 'LICENSE', 4 | // which is part of this source code package. 5 | 6 | #ifndef _picofs_h_ 7 | #define _picofs_h_ 8 | 9 | #include 10 | #include 11 | 12 | union _pfs_block; 13 | typedef union _pfs_block pfs_block; 14 | 15 | enum { 16 | PFS_BLOCKSZ = 32, 17 | }; 18 | 19 | // block allocation map 20 | typedef struct { 21 | uint32_t map[8]; // 32 B - block allocation map 22 | } pfs_alloc; 23 | 24 | // file system state 25 | typedef struct { 26 | pfs_block* bb; // base block pointer 27 | int nblks; // number of blocks 28 | int next; // next alloc search start 29 | pfs_alloc alloc; // allocation bitmap 30 | } pfs; 31 | 32 | // glue functions 33 | extern void pfs_write_block (void* dst, void* src, int nwords); 34 | extern void pfs_crc32 (uint32_t* pcrc, unsigned char* buf, uint32_t len); 35 | extern uint8_t pfs_rnd_block (uint8_t nblks); 36 | 37 | // public API 38 | void pfs_init (pfs* s, void* p, int nblks); 39 | int pfs_find (pfs* s, const uint8_t* ufid); 40 | int pfs_read (pfs* s, const uint8_t* ufid, void* data, int sz); 41 | int pfs_save (pfs* s, const uint8_t* ufid, void* data, int sz); 42 | void pfs_ls (pfs* s, void (*cb) (int fh, const uint8_t* ufid, void* ctx), void* ctx); 43 | 44 | void pfs_rm_fh (pfs* s, int fh); 45 | int pfs_read_fh (pfs* s, int fh, void* data, int sz); 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /services/eefs/ufid.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 4 | # 5 | # This file is subject to the terms and conditions defined in file 'LICENSE', 6 | # which is part of this source code package. 7 | 8 | import random 9 | import time 10 | import struct 11 | import sys 12 | 13 | # create unique file identifiers (UFIDs) 14 | 15 | class UFID: 16 | def __init__(self, q:int, i:int) -> None: 17 | self.q = q 18 | self.i = i 19 | 20 | def version(self) -> int: 21 | return self.q & 0xf 22 | 23 | def __str__(self) -> str: 24 | return '%016x-%08x' % (self.q, self.i) 25 | 26 | def to_bytes(self) -> bytes: 27 | return struct.pack(' 'UFID': 31 | q, i = struct.unpack(' 'UFID': 36 | # Version 0: 37 | # - Q: time (ms, 44b), random_1 (16b), version (4b) 38 | # - I: random_2 (32b) 39 | 40 | t = int(time.time() * 1000) 41 | r1 = random.getrandbits(16) 42 | v = 0 43 | r2 = random.getrandbits(32) 44 | 45 | q = (t << 20) | (r1 << 4) | v 46 | i = r2 47 | 48 | return UFID(q, i) 49 | 50 | 51 | if __name__ == '__main__': 52 | for name in sys.argv[1:]: 53 | u = UFID.generate() 54 | print('// %s\nstatic const uint8_t UFID_%s[12] = { %s };' % (u, name, 55 | ', '.join([('0x%02x' % x) for x in u.to_bytes()]))) 56 | -------------------------------------------------------------------------------- /services/frag.svc: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | # 3 | # This file is subject to the terms and conditions defined in file 'LICENSE', 4 | # which is part of this source code package. 5 | 6 | 7 | src: 8 | - fuota/frag.c 9 | 10 | require: 11 | - fuota 12 | - lwmux 13 | - eefs 14 | 15 | hook.lwm_downlink: _frag_dl 16 | hook.eefs_init: _frag_restore 17 | hook.eefs_fn: _frag_eefs_fn 18 | 19 | # vim: syntax=yaml 20 | -------------------------------------------------------------------------------- /services/fuota.svc: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | # 3 | # This file is subject to the terms and conditions defined in file 'LICENSE', 4 | # which is part of this source code package. 5 | 6 | 7 | src: 8 | - fuota/fuota.c 9 | 10 | # vim: syntax=yaml 11 | -------------------------------------------------------------------------------- /services/fuota/.gitignore: -------------------------------------------------------------------------------- 1 | test 2 | *.d 3 | *.o 4 | -------------------------------------------------------------------------------- /services/fuota/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS += -Wall -g 2 | CFLAGS += -std=gnu11 3 | CFLAGS += -MMD -MP -std=gnu11 4 | 5 | CFLAGS += -DFUOTA_HAL_IMPL='"fuota_hal_x86_64.h"' 6 | CFLAGS += -DFUOTA_GENERATOR 7 | 8 | OBJS := test.o fuota.o 9 | 10 | test: $(OBJS) 11 | 12 | clean: 13 | rm -f *.o *.d test 14 | 15 | .PHONY: clean 16 | 17 | -include $(OBJS:.o=.d) 18 | -------------------------------------------------------------------------------- /services/fuota/frag.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | // 3 | // This file is subject to the terms and conditions defined in file 'LICENSE', 4 | // which is part of this source code package. 5 | 6 | #ifndef _frag_h_ 7 | #define _frag_h_ 8 | 9 | void _frag_init (int nsessions, void** sbeg, void** send); 10 | 11 | int frag_get (int idx, void** pdata); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /services/fuota/frag.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright (C) 2016-2020 Semtech (International) AG. All rights reserved. 4 | # 5 | # This file is subject to the terms and conditions defined in file 'LICENSE', 6 | # which is part of this source code package. 7 | 8 | from typing import BinaryIO,Callable,List,Optional,Union 9 | 10 | import random 11 | import struct 12 | from bitarray import bitarray 13 | 14 | def bitarrayfrombytes(buf:bytes) -> bitarray: 15 | b = bitarray(endian='little') 16 | b.frombytes(buf) 17 | return b 18 | 19 | class CBGenerator: 20 | def generate(self, cct:int, cid:int) -> bitarray: 21 | raise NotImplementedError() 22 | 23 | class TrackNetGenerator(CBGenerator): 24 | @staticmethod 25 | def tn_avalanche(x:int) -> int: 26 | x = x & 0xffffffff 27 | x = (((x >> 16) ^ x) * 0x45d9f3b) & 0xffffffff 28 | x = (((x >> 16) ^ x) * 0x45d9f3b) & 0xffffffff 29 | x = (x >> 16) ^ x 30 | return x 31 | 32 | def generate(self, cct:int, cid:int) -> bitarray: 33 | nw = (cct + 31) // 32 34 | b = bitarrayfrombytes(struct.pack('<%dI' % nw, *(self.tn_avalanche((cid * nw) + i) for i in range(nw)))) 35 | return b[:cct] 36 | 37 | class FragCarousel: 38 | def __init__(self, data:bytes, csz:int, cbg:CBGenerator, pad:int=0) -> None: 39 | cct,rem = divmod(len(data), csz) 40 | if rem: 41 | raise ValueError('data length must be a multiple of chunk size') 42 | self.data = data 43 | self.csz = csz 44 | self.cct = cct 45 | self.cbg = cbg 46 | self.pad = pad 47 | self.blocks = [bitarrayfrombytes(data[b*csz:(b+1)*csz]) for b in range(cct)] 48 | 49 | def chunk(self, cid:int) -> bytes: 50 | cb = self.cbg.generate(self.cct, cid) 51 | chunk = bitarray(self.csz * 8, endian='little') 52 | chunk.setall(0) 53 | for c in range(self.cct): 54 | if cb[c]: 55 | chunk ^= self.blocks[c] 56 | return chunk.tobytes() 57 | 58 | @staticmethod 59 | def fromfile(upf:Union[bytes,str,BinaryIO], csz:int=200, 60 | cbg:CBGenerator=TrackNetGenerator(), pad:bytes=b'*') -> 'FragCarousel': 61 | if isinstance(upf, str): 62 | with open(upf, 'rb') as f: 63 | upd = bytes(f.read()) 64 | elif isinstance(upf, bytes): 65 | upd = upf 66 | else: 67 | upd = upf.read() 68 | 69 | _,rem = divmod(len(upd), csz) 70 | padlen = (csz - rem) if rem else 0 71 | upd += padlen * pad 72 | return FragCarousel(upd, csz, cbg, padlen) 73 | 74 | class DefragSession: 75 | def __init__(self, cct:int, csz:int, cbg:CBGenerator) -> None: 76 | self.cct = cct 77 | self.csz = csz 78 | self.cbg = cbg 79 | self.rows:List[Optional[bitarray]] = [None for _ in range(cct)] 80 | self.dct = 0 81 | self.unp:Optional[bytes] = None 82 | 83 | def process(self, cid:int, data:bytes) -> bool: 84 | if self.csz != len(data): 85 | raise ValueError() 86 | bits = self.cbg.generate(self.cct, cid) + bitarrayfrombytes(data) 87 | for c in reversed(range(self.cct)): 88 | if bits[c]: 89 | if self.rows[c]: 90 | bits ^= self.rows[c] 91 | else: 92 | self.rows[c] = bits 93 | self.dct += 1 94 | return True 95 | return False 96 | 97 | def complete(self) -> bool: 98 | return self.dct == self.cct 99 | 100 | def unpack(self) -> bytes: 101 | if not self.complete(): 102 | raise RuntimeError() 103 | if not self.unp: 104 | data = b'' 105 | for c in range(self.cct): 106 | for i in range(c): 107 | if self.rows[c][i]: 108 | self.rows[c] ^= self.rows[i] 109 | data += self.rows[c][self.cct:].tobytes() 110 | self.unp = data 111 | return self.unp 112 | 113 | if __name__ == '__main__': 114 | csz = 8 115 | cct = 1024-8 116 | 117 | data = bytes(random.randrange(0,256) for _ in range(cct*csz)) 118 | cbg = TrackNetGenerator() 119 | fc = FragCarousel(data, csz, cbg) 120 | ds = DefragSession(fc.cct, fc.csz, cbg) 121 | 122 | print('cct=%d' % fc.cct) 123 | 124 | cid = random.randint(0, 100000) 125 | while not ds.complete(): 126 | s = ds.process(cid, fc.chunk(cid)) 127 | print('.' if s else 'X', end='') 128 | cid += 1 129 | print() 130 | 131 | assert data == ds.unpack() 132 | -------------------------------------------------------------------------------- /services/fuota/fuota.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | // 3 | // This file is subject to the terms and conditions defined in file 'LICENSE', 4 | // which is part of this source code package. 5 | 6 | #ifndef _fuota_h_ 7 | #define _fuota_h_ 8 | 9 | #include 10 | #include 11 | 12 | enum { 13 | FUOTA_MORE = 0, 14 | FUOTA_COMPLETE = 1, 15 | FUOTA_UNPACKED = 2, 16 | FUOTA_ERROR = -1, 17 | }; 18 | 19 | struct _fuota_session; 20 | typedef struct _fuota_session fuota_session; 21 | 22 | // return the required matrix size 23 | // - chunk_ct: chunk count 24 | // - chunk_nw: number of 4-byte words per chunk 25 | size_t fuota_matrix_size (uint32_t chunk_ct, uint32_t chunk_nw); 26 | 27 | // initialize a session 28 | // - session: pointer to a single flash page that will hold the session state 29 | // - matrix: page-aligned pointer to flash area large enough to 30 | // hold matrix; use fuota_matrix_size() to determine min. size 31 | // - data: page-aligned pointer to flash area large enough to 32 | // hold chunk data; at least chunk_ct*chunk_nw*4 33 | // - sid: application specific session identifier 34 | // - chunk_ct: chunk count 35 | // - chunk_nw: number of 4-byte words per chunk 36 | // NOTE: All flash areas passed to this function (session, matrix, data) must 37 | // be in erased, unwritten condition. 38 | void fuota_init (void* session, void* matrix, void* data, uint32_t sid, 39 | uint32_t chunk_ct, uint32_t chunk_nw); 40 | 41 | // process a chunk 42 | // - session: pointer to session 43 | // - chunk_id: chunk identifier 44 | // - chunk_buf: buffer to chunk data (does not need to be aligned) 45 | int fuota_process (fuota_session* session, uint32_t chunk_id, 46 | unsigned char* chunk_buf); 47 | 48 | // get current session state 49 | int fuota_state (fuota_session* session, uint32_t* sid, 50 | uint32_t* chunk_ct, uint32_t* chunk_nw, uint32_t* complete_ct); 51 | 52 | // convenience function to check if a session has the expected parameters 53 | int fuota_check_state (fuota_session* session, uint32_t sid, 54 | uint32_t chunk_ct, uint32_t chunk_nw); 55 | 56 | // unpack the received chunks and recover the original file 57 | // - session: pointer to session 58 | void* fuota_unpack (fuota_session* session); 59 | 60 | #ifdef FUOTA_GENERATOR 61 | void fuota_gen_chunk (uint32_t* dst, uint32_t* src, uint32_t chunk_id, 62 | uint32_t chunk_ct, uint32_t chunk_nw); 63 | #endif 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /services/fuota/fuota_hal.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | // 3 | // This file is subject to the terms and conditions defined in file 'LICENSE', 4 | // which is part of this source code package. 5 | 6 | #ifndef _fuota_hal_h_ 7 | #define _fuota_hal_h_ 8 | 9 | // The functions below must be provided by the HAL. They can be implemented as 10 | // macros. 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #ifdef FUOTA_HAL_IMPL 17 | #include FUOTA_HAL_IMPL 18 | #else 19 | #include "lmic.h" 20 | #endif 21 | 22 | 23 | // ------------------------------------------------ 24 | // Flash access 25 | 26 | // Get flash default state 27 | #ifndef fuota_flash_bitdefault 28 | #error "Macro 'fuota_flash_bitdefault' must be defined by HAL" 29 | #else 30 | #if ((fuota_flash_bitdefault & ~1) != 0) 31 | #error "Macro 'fuota_flash_bitdefault' must be set to 1 or 0" 32 | #endif 33 | #endif 34 | 35 | // Get flash page size in bytes 36 | #ifndef fuota_flash_pagesz 37 | #error "Macro 'fuota_flash_pagesz' must be defined by HAL" 38 | #endif 39 | 40 | // Write words from RAM to flash memory (aligned-to-aligned) 41 | // If erase is true, erase the page when reaching a page boundary before writing 42 | #ifndef fuota_flash_write 43 | void fuota_flash_write (void* dst, void* src, uint32_t nwords, bool erase); 44 | #endif 45 | 46 | // Read words from flash memory to RAM (aligned-to-aligned) 47 | #ifndef fuota_flash_read 48 | void fuota_flash_read (void* dst, void* src, uint32_t nwords); 49 | #endif 50 | 51 | // Read a uint32_t from flash memory 52 | #ifndef fuota_flash_rd_u4 53 | uint32_t fuota_flash_rd_u4 (void* addr); 54 | #endif 55 | 56 | // Read a void* from flash memory 57 | #ifndef fuota_flash_rd_ptr 58 | void* fuota_flash_rd_ptr (void* addr); 59 | #endif 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /services/fuota/fuota_hal_x86_64.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | // 3 | // This file is subject to the terms and conditions defined in file 'LICENSE', 4 | // which is part of this source code package. 5 | 6 | #ifndef _fuota_hal_x86_64_h_ 7 | #define _fuota_hal_x86_64_h_ 8 | 9 | #define fuota_flash_pagesz 128 10 | #define fuota_flash_bitdefault 0 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /services/fuota/test.up: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkuyper/basicmac/ad231427c4f7bc19cb88821fddeb83ba130fb065/services/fuota/test.up -------------------------------------------------------------------------------- /services/fuota/testkey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHcCAQEEIFDihCr1+guDkHjv5LO5GvOUxebr3cATnJGn0l+obJfYoAoGCCqGSM49 3 | AwEHoUQDQgAE7HA26PGo1XRMn9n8NN9D2P8L8FvA5o75MUDoAXL9Bo42hnwJqShe 4 | yg6IZ0oodzTcBC4kQgKKyDqz0V2vPS8PBw== 5 | -----END EC PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /services/fwman.svc: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | # 3 | # This file is subject to the terms and conditions defined in file 'LICENSE', 4 | # which is part of this source code package. 5 | 6 | 7 | src: 8 | - fuota/fwman.c 9 | - micro-ecc/uECC.c 10 | 11 | require: 12 | - frag 13 | - lwmux 14 | 15 | hook.lwm_downlink: fwman_dl 16 | 17 | # vim: syntax=yaml 18 | -------------------------------------------------------------------------------- /services/lwmux.svc: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | # 3 | # This file is subject to the terms and conditions defined in file 'LICENSE', 4 | # which is part of this source code package. 5 | 6 | 7 | src: 8 | - lwmux/lwmux.c 9 | 10 | hooks: 11 | - void lwm_event (ev_t) 12 | - void lwm_downlink (int port, unsigned char* data, int dlen, unsigned int txrxFlags) 13 | 14 | 15 | # vim: syntax=yaml 16 | -------------------------------------------------------------------------------- /services/lwmux/lwmux.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | // 3 | // This file is subject to the terms and conditions defined in file 'LICENSE', 4 | // which is part of this source code package. 5 | 6 | #ifndef _lwmux_h_ 7 | #define _lwmux_h_ 8 | 9 | #include "lmic.h" 10 | 11 | typedef void (*lwm_complete) (void); 12 | typedef int (*lwm_jit_cb) (unsigned char* data, int dlen); 13 | 14 | typedef struct { 15 | unsigned char* data; 16 | int dlen; 17 | int port; 18 | int confirmed; 19 | lwm_complete txcomplete; 20 | lwm_jit_cb jit_cb; 21 | } lwm_txinfo; 22 | 23 | typedef bool (*lwm_tx) (lwm_txinfo*); 24 | 25 | typedef struct _lwm_job { 26 | unsigned int prio; 27 | lwm_tx txfunc; 28 | lwm_complete completefunc; 29 | struct _lwm_job* next; 30 | } lwm_job; 31 | 32 | 33 | enum { 34 | LWM_MODE_SHUTDOWN, 35 | LWM_MODE_NORMAL, 36 | #ifdef LWM_SLOTTED 37 | LWM_MODE_SLOTTED, 38 | #endif 39 | }; 40 | 41 | #define LWM_PRIO_MIN 0 42 | #define LWM_PRIO_MAX ~0 43 | 44 | int lwm_getmode (); 45 | void lwm_setmode (int mode); 46 | unsigned int lwm_setpriority (unsigned int priority); 47 | 48 | void lwm_request_send (lwm_job* job, unsigned int priority, lwm_tx txfunc); 49 | bool lwm_clear_send (lwm_job* job); 50 | 51 | void lwm_setadrprofile (int txPowAdj, const unsigned char* drlist, int n); 52 | 53 | #ifdef LWM_SLOTTED 54 | void lwm_slotparams (u4_t freq, dr_t dr, ostime_t interval, int slotsz, int missed_max, int timeouts_max); 55 | 56 | extern unsigned int lwm_slot (void); 57 | extern void lwm_bcn_setup (void); 58 | #endif 59 | 60 | enum { 61 | LWM_FLAG_ACK = TXRX_ACK, 62 | LWM_FLAG_NAK = TXRX_NACK, 63 | LWM_FLAG_DNW1 = TXRX_DNW1, 64 | LWM_FLAG_DNW2 = TXRX_DNW2, 65 | LWM_FLAG_PING = TXRX_PING, 66 | }; 67 | #define LWM_FLAG_MASK (LWM_FLAG_ACK | LWM_FLAG_NAK | LWM_FLAG_DNW1 | LWM_FLAG_DNW2 | LWM_FLAG_PING) 68 | typedef void (*lwm_downlink) (int port, unsigned char* data, int dlen, unsigned int flags); 69 | void lwm_process_dl (lwm_downlink dlfunc); 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /services/lwtest.svc: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | # 3 | # This file is subject to the terms and conditions defined in file 'LICENSE', 4 | # which is part of this source code package. 5 | 6 | 7 | src: 8 | - lwtest/testmode.c 9 | 10 | require: 11 | - lwmux 12 | 13 | hook.lwm_event: testmode_handleEvent 14 | 15 | # vim: syntax=yaml 16 | 17 | 18 | -------------------------------------------------------------------------------- /services/perso.svc: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020-2020 Michael Kuyper. All rights reserved. 2 | # 3 | # This file is subject to the terms and conditions defined in file 'LICENSE', 4 | # which is part of this source code package. 5 | 6 | 7 | src: 8 | - perso/perso.c 9 | 10 | require: 11 | - appstart 12 | 13 | hook.appstart: _perso_main:-10 14 | 15 | 16 | # vim: syntax=yaml 17 | -------------------------------------------------------------------------------- /services/perso/perso.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020-2020 Michael Kuyper. All rights reserved. 2 | # 3 | # This file is subject to the terms and conditions defined in file 'LICENSE', 4 | # which is part of this source code package. 5 | 6 | from typing import Optional, Tuple, Union 7 | 8 | import asyncio 9 | import hashlib 10 | import random 11 | import struct 12 | 13 | from binascii import crc32 14 | from cobs import cobs 15 | from dataclasses import dataclass 16 | from rtlib import Eui 17 | 18 | class PTESerialPort: 19 | def send(self, data:bytes) -> None: 20 | raise NotImplementedError 21 | 22 | async def recv(self) -> bytes: 23 | raise NotImplementedError 24 | 25 | class PTE: 26 | def __init__(self, port:PTESerialPort, *, timeout:Optional[float]=5.0) -> None: 27 | self.port = port 28 | self.tag = random.randint(0x0000, 0xffff) 29 | self.sync = False 30 | self.timeout = timeout 31 | 32 | def pack(self, cmd:int, payload:bytes) -> bytes: 33 | assert len(payload) <= 236 34 | self.tag = (self.tag + 1) & 0xffff 35 | l = len(payload) 36 | p = struct.pack(' Optional[Tuple[int,bytes]]: 43 | n = len(frame) 44 | if n < 8: 45 | return None 46 | res, tag, l = struct.unpack(' bytes: 54 | return frame + b'\0' 55 | 56 | @staticmethod 57 | def unframe(buf:bytes) -> Tuple[bytes,bytes]: 58 | n = len(buf) 59 | i = buf.find(b'\0') 60 | if i < 0: 61 | return b'', b'' 62 | else: 63 | return buf[:i], buf[i+1:] 64 | 65 | async def _xchg(self, cmd:int, payload:bytes=b'') -> Tuple[int,bytes]: 66 | p = PTE.frame(cobs.encode(self.pack(cmd, payload))) 67 | if not self.sync: 68 | self.sync = True 69 | p = b'\x55\0\0\0' + p 70 | self.port.send(p) 71 | while True: 72 | b = await self.port.recv() 73 | while b: 74 | f, b = PTE.unframe(b) 75 | try: 76 | f = cobs.decode(f) 77 | except cobs.DecodeError: 78 | continue 79 | if (t := self.unpack(f)): 80 | return t 81 | 82 | async def xchg(self, cmd:int, payload:bytes=b'') -> Tuple[int,bytes]: 83 | return await asyncio.wait_for(self._xchg(cmd, payload), timeout=self.timeout) 84 | 85 | CMD_NOP = 0x00 86 | CMD_RUN = 0x01 87 | CMD_RESET = 0x02 88 | 89 | CMD_EE_READ = 0x90 90 | CMD_EE_WRITE = 0x91 91 | 92 | RES_OK = 0x00 93 | RES_EPARAM = 0x80 94 | RES_INTERR = 0x81 95 | RES_WTX = 0xFE 96 | RES_NOIMPL = 0xFF 97 | 98 | @staticmethod 99 | def check_res(res:int, expected:Optional[int]=None) -> None: 100 | code2desc = { 101 | PTE.RES_EPARAM: 'invalid parameter', 102 | PTE.RES_INTERR: 'internal error', 103 | PTE.RES_NOIMPL: 'not implemented' } 104 | if res & 0x80: 105 | raise ValueError(f'Error response code 0x{res:02x} ({code2desc.get(res, "unknown")})') 106 | if expected is not None and res != expected: 107 | raise ValueError(f'Unexpected response code 0x{res:02x}') 108 | 109 | async def nop(self) -> None: 110 | res, pl = await self.xchg(PTE.CMD_NOP) 111 | PTE.check_res(res, expected=0x7F) 112 | if pl: 113 | raise ValueError(f'Unexpected response payload {pl.hex()}') 114 | 115 | async def reset(self) -> None: 116 | res, pl = await self.xchg(PTE.CMD_RESET) 117 | PTE.check_res(res, expected=PTE.RES_OK) 118 | if pl: 119 | raise ValueError(f'Unexpected response payload {pl.hex()}') 120 | 121 | async def ee_read(self, offset:int, length:int) -> bytes: 122 | res, pl = await self.xchg(PTE.CMD_EE_READ, struct.pack(' None: 129 | res, pl = await self.xchg(PTE.CMD_EE_WRITE, struct.pack(' Union['PersoDataV1']: 140 | if len(data) < 4: 141 | raise ValueError('Invalid data size') 142 | magic, = struct.unpack('= 0: 153 | serial = serial[:idx] 154 | return PersoDataV1(hwid, region, serial.decode('ascii'), Eui(deveui), Eui(joineui), nwkkey, appkey) 155 | 156 | raise ValueError(f'Unknown magic: 0x{magic:08x}') 157 | 158 | 159 | @dataclass 160 | class PersoDataV1: 161 | hwid:int 162 | region:int 163 | serial:str 164 | deveui:Eui 165 | joineui:Eui 166 | nwkkey:bytes 167 | appkey:bytes 168 | 169 | def pack(self) -> bytes: 170 | pd = struct.pack(PersoData.V1_FORMAT_NH, 171 | PersoData.V1_MAGIC, 172 | self.hwid, 173 | self.region, 174 | 0, # reserved 175 | self.serial.encode('ascii'), 176 | self.deveui.as_bytes(), 177 | self.joineui.as_bytes(), 178 | self.nwkkey, 179 | self.appkey) 180 | h = hashlib.sha256(pd).digest() 181 | return pd + h 182 | -------------------------------------------------------------------------------- /services/perso/persotool.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright (C) 2020-2020 Michael Kuyper. All rights reserved. 4 | # 5 | # This file is subject to the terms and conditions defined in file 'LICENSE', 6 | # which is part of this source code package. 7 | 8 | from typing import Optional 9 | 10 | import aioserial 11 | import asyncio 12 | import click 13 | import functools 14 | 15 | from perso import PTE, PTESerialPort, PersoData, PersoDataV1 16 | from rtlib import Eui 17 | 18 | class PhysicalPTESerialPort(PTESerialPort): 19 | def __init__(self, port:str, baudrate:int) -> None: 20 | self.serial = aioserial.AioSerial(port=port, baudrate=baudrate) 21 | 22 | def send(self, data:bytes) -> None: 23 | self.serial.write(data) 24 | 25 | async def recv(self) -> bytes: 26 | return await self.serial.read_until_async(b'\0') 27 | 28 | 29 | class BasedIntParamType(click.ParamType): 30 | name = "integer" 31 | 32 | def convert(self, value, param, ctx): 33 | if isinstance(value, int): 34 | return value 35 | try: 36 | return int(value, 0) 37 | except ValueError: 38 | self.fail(f"{value!r} is not a valid integer", param, ctx) 39 | 40 | class EuiParamType(click.ParamType): 41 | name = "eui" 42 | 43 | def convert(self, value, param, ctx): 44 | try: 45 | return Eui(value) 46 | except ValueError: 47 | self.fail(f"{value!r} is not a valid EUI", param, ctx) 48 | 49 | class AESKeyType(click.ParamType): 50 | name = "aeskey" 51 | 52 | def convert(self, value, param, ctx): 53 | try: 54 | key = bytes.fromhex(value) 55 | except ValueError: 56 | self.fail(f"{value!r} is not a valid AES key", param, ctx) 57 | if len(key) != 16: 58 | self.fail(f"AES key must have a length of 16 bytes", param, ctx) 59 | return key 60 | 61 | BASED_INT = BasedIntParamType() 62 | EUI = EuiParamType() 63 | AESKEY = AESKeyType() 64 | 65 | def coro(f): 66 | @functools.wraps(f) 67 | def wrapper(*args, **kwargs): 68 | return asyncio.run(f(*args, **kwargs)) 69 | return wrapper 70 | 71 | 72 | @click.group() 73 | @click.option('-p', '--port', default='/dev/ttyACM0', 74 | help='serial port') 75 | @click.option('-b', '--baud', type=int, default=115200, 76 | help='baud rate') 77 | @click.pass_context 78 | def cli(ctx:click.Context, port:str, baud:int) -> None: 79 | ctx.obj['pte'] = PTE(PhysicalPTESerialPort(port, baud)) 80 | 81 | 82 | @cli.command(help='Read personalization data from EEPROM') 83 | @click.option('-o', '--offset', type=BASED_INT, default=0x0060, 84 | help='Offset of personalization data structure in EEPROM') 85 | @click.option('-k', '--show-keys', is_flag=True, 86 | help='Show network and application keys') 87 | @click.pass_context 88 | @coro 89 | async def pdread(ctx:click.Context, offset:int, show_keys:bool): 90 | pte = ctx.obj['pte'] 91 | pd = PersoData.unpack(await pte.ee_read(offset, PersoData.V1_SIZE)) 92 | print(f'Hardware ID: 0x{pd.hwid:08x}') 93 | print(f'Region ID: 0x{pd.region:08x} ({pd.region})') 94 | print(f'Serial No: {pd.serial}') 95 | print(f'Device EUI: {pd.deveui}') 96 | print(f'Join EUI: {pd.joineui}') 97 | if show_keys: 98 | print(f'Network key: {pd.nwkkey.hex()}') 99 | print(f'App key: {pd.appkey.hex()}') 100 | 101 | 102 | @cli.command(help='Clear personalization data in EEPROM') 103 | @click.option('-o', '--offset', type=BASED_INT, default=0x0060, 104 | help='Offset of personalization data structure in EEPROM') 105 | @click.pass_context 106 | @coro 107 | async def pdclear(ctx:click.Context, offset:int): 108 | pte = ctx.obj['pte'] 109 | pd = PersoData.unpack(await pte.ee_read(offset, PersoData.V1_SIZE)) 110 | await pte.ee_write(offset, bytes(PersoData.V1_SIZE)) 111 | 112 | 113 | @cli.command(help='Write personalization data to EEPROM') 114 | @click.option('-o', '--offset', type=BASED_INT, default=0x0060, 115 | help='Offset of personalization data structure in EEPROM') 116 | @click.option('--hwid', type=BASED_INT, default=0, 117 | help='Hardware ID') 118 | @click.option('--region', type=BASED_INT, default=0, 119 | help='Region ID') 120 | @click.argument('serialno', type=str) 121 | @click.argument('deveui', type=EUI) 122 | @click.argument('joineui', type=EUI) 123 | @click.argument('nwkkey', type=AESKEY) 124 | @click.argument('appkey', type=AESKEY, required=False) 125 | @click.pass_context 126 | @coro 127 | async def pdwrite(ctx:click.Context, offset:int, hwid:int, region:int, serialno:str, deveui:Eui, joineui:Eui, nwkkey:bytes, appkey:Optional[bytes]): 128 | pte = ctx.obj['pte'] 129 | if appkey is None: 130 | appkey = nwkkey 131 | await pte.ee_write(offset, PersoDataV1(hwid, region, serialno, deveui, joineui, nwkkey, appkey).pack()) 132 | 133 | 134 | if __name__ == '__main__': 135 | cli(obj={}) 136 | -------------------------------------------------------------------------------- /services/perso/requirements.txt: -------------------------------------------------------------------------------- 1 | aioserial 2 | cobs 3 | liblora 4 | -------------------------------------------------------------------------------- /services/perso/test_perso.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020-2020 Michael Kuyper. All rights reserved. 2 | # 3 | # This file is subject to the terms and conditions defined in file 'LICENSE', 4 | # which is part of this source code package. 5 | 6 | import asyncio 7 | 8 | from devtest import vtime, DeviceTest 9 | from peripherals import FastUART, GPIO 10 | from perso import PTE, PTESerialPort, PersoData, PersoDataV1 11 | from rtlib import Eui 12 | 13 | from ward import fixture, test 14 | 15 | class SimPTESerialPort(PTESerialPort): 16 | def __init__(self, sim) -> None: 17 | self.sim = sim 18 | self.uart = None 19 | 20 | def reinit(self): 21 | self.uart = None 22 | 23 | def send(self, data:bytes) -> None: 24 | if self.uart == None: 25 | self.uart = self.sim.get_peripheral(FastUART) 26 | self.uart.send(data) 27 | 28 | async def recv(self) -> bytes: 29 | if self.uart == None: 30 | self.uart = self.sim.get_peripheral(FastUART) 31 | return await self.uart.recv() 32 | 33 | class SimPTE(PTE): 34 | def __init__(self, dut): 35 | super().__init__(SimPTESerialPort(dut.sim)) 36 | self.dut = dut 37 | 38 | def activate(self, active): 39 | self.dut.sim.get_peripheral(GPIO).drive(24, active) 40 | 41 | 42 | @fixture 43 | async def createtest(_=vtime): 44 | dut = DeviceTest() 45 | dut.start() 46 | yield SimPTE(dut) 47 | await dut.stop() 48 | 49 | 50 | @test('Enter Personalization Mode') 51 | async def _(pte=createtest): 52 | pte.activate(True) 53 | await asyncio.sleep(1) 54 | await pte.nop() 55 | 56 | 57 | @test('Reset Command') 58 | async def _(pte=createtest): 59 | pte.activate(True) 60 | await asyncio.sleep(1) 61 | await pte.reset() 62 | 63 | # Set I/O pin to re-enter perso 64 | pte.port.reinit() 65 | pte.activate(True) 66 | await asyncio.sleep(1) 67 | await pte.nop() 68 | await pte.reset() 69 | 70 | # GPIO is no longer set, so we should enter "regular" mode 71 | await pte.dut.join() 72 | await pte.dut.updf() 73 | 74 | 75 | @test('Read/Write EEPROM Command') 76 | async def _(pte=createtest): 77 | pte.activate(True) 78 | await asyncio.sleep(1) 79 | 80 | # Write new personalization data to EEPROM 81 | deveui = Eui('01-02-03-04-05-06-07-08') 82 | joineui = Eui('F1-F2-F3-F4-F5-F6-F7-F8') 83 | nwkkey = b'QWERTYUIASDFGHJK' 84 | appkey = b'qwertyuiasdfghjk' 85 | pd = PersoDataV1(0, 0, 'TestSerial', deveui, joineui, nwkkey, appkey) 86 | await pte.ee_write(0x0060, pd.pack()) 87 | 88 | # Read back personalization data 89 | pd2 = PersoData.unpack(await pte.ee_read(0x0060, PersoData.V1_SIZE)) 90 | assert pd == pd2 91 | 92 | # Reset and verify device joins with new settings 93 | await pte.reset() 94 | await pte.dut.join(deveui=deveui, nwkkey=nwkkey) 95 | await pte.dut.updf() 96 | -------------------------------------------------------------------------------- /services/pwrman.svc: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | # 3 | # This file is subject to the terms and conditions defined in file 'LICENSE', 4 | # which is part of this source code package. 5 | 6 | 7 | src: 8 | - pwrman/pwrman.c 9 | 10 | require: 11 | - eefs 12 | 13 | hook.eefs_init: _pwrman_init 14 | hook.eefs_fn: _pwrman_eefs_fn 15 | 16 | # vim: syntax=yaml 17 | -------------------------------------------------------------------------------- /services/pwrman/pwrman.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | // 3 | // This file is subject to the terms and conditions defined in file 'LICENSE', 4 | // which is part of this source code package. 5 | 6 | #include 7 | 8 | #include "lmic.h" 9 | 10 | #include "eefs/eefs.h" 11 | #include "pwrman.h" 12 | 13 | #include "svcdefs.h" // for type-checking hook functions 14 | 15 | // our basic unit is the micro-ampere hour -- 2^32 uAh = 4295 Ah 16 | 17 | // 16bc26f9da64e290-05b08ee7 18 | static const uint8_t UFID_PWRMAN_STATS[12] = { 0x90, 0xe2, 0x64, 0xda, 0xf9, 0x26, 0xbc, 0x16, 0xe7, 0x8e, 0xb0, 0x05 }; 19 | 20 | const char* _pwrman_eefs_fn (const uint8_t* ufid) { 21 | if( memcmp(ufid, UFID_PWRMAN_STATS, sizeof(UFID_PWRMAN_STATS)) == 0 ) { 22 | return "com.semtech.svc.pwrman.stats"; 23 | } 24 | return NULL; 25 | } 26 | 27 | typedef struct { 28 | uint32_t hr; 29 | uint32_t ticks; 30 | } ptime; 31 | 32 | // Volatile state 33 | static struct { 34 | ptime accu; // accumulator 35 | ptime stats[PWRMAN_C_MAX]; // consumption statistics 36 | } state; 37 | 38 | // Persistent state (eefs) 39 | typedef struct { 40 | uint32_t uah_accu; // accumulator 41 | uint32_t uah_stats[PWRMAN_C_MAX]; // total micro-amp hours used 42 | } pwrman_pstate; 43 | 44 | static void update_rtstats (void); // fwd decl 45 | 46 | static void add (ptime* ppt, uint64_t uaticks) { 47 | uaticks += ppt->ticks; 48 | uint32_t uah = uaticks / (OSTICKS_PER_SEC * 60 * 60); 49 | if( uah != 0 ) { 50 | ppt->hr += uah; 51 | uaticks -= ((int64_t) uah * (OSTICKS_PER_SEC * 60 * 60)); 52 | } 53 | ppt->ticks = uaticks; 54 | } 55 | 56 | void pwrman_consume (int ctype, uint32_t ticks, uint32_t ua) { 57 | ASSERT(ctype < PWRMAN_C_MAX); 58 | uint64_t uaticks = (uint64_t) ticks * ua; 59 | #ifdef CFG_DEBUG_pwrman 60 | debug_printf("pwrman: adding %u ticks to accu (%d/%d", 61 | (uint32_t) uaticks, 62 | state.accu.hr, state.accu.ticks); 63 | #endif 64 | add(&state.accu, uaticks); 65 | #ifdef CFG_DEBUG_pwrman 66 | debug_printf(" --> %d/%d)\r\n", 67 | state.accu.hr, state.accu.ticks); 68 | #endif 69 | add(&state.stats[ctype], uaticks); 70 | } 71 | 72 | uint32_t pwrman_accu_uah (void) { 73 | update_rtstats(); 74 | return state.accu.hr; 75 | } 76 | 77 | void pwrman_commit (void) { 78 | update_rtstats(); 79 | pwrman_pstate ps; 80 | ps.uah_accu = state.accu.hr; 81 | for( int i = 0; i < PWRMAN_C_MAX; i++ ) { 82 | ps.uah_stats[i] = state.stats[i].hr; 83 | } 84 | eefs_save(UFID_PWRMAN_STATS, &ps, sizeof(ps)); 85 | } 86 | 87 | void pwrman_reset (void) { 88 | memset(&state, 0x00, sizeof(state)); 89 | pwrman_pstate ps; 90 | memset(&ps, 0x00, sizeof(ps)); 91 | eefs_save(UFID_PWRMAN_STATS, &ps, sizeof(ps)); 92 | } 93 | 94 | void _pwrman_init (void) { 95 | pwrman_pstate ps; 96 | if( eefs_read(UFID_PWRMAN_STATS, &ps, sizeof(ps)) == sizeof(ps) ) { 97 | state.accu.hr = ps.uah_accu; 98 | for( int i = 0; i < PWRMAN_C_MAX; i++ ) { 99 | state.stats[i].hr = ps.uah_stats[i]; 100 | } 101 | } 102 | } 103 | 104 | static void update_rtstats (void) { 105 | #if defined(STM32L0) && defined(CFG_rtstats) 106 | hal_rtstats stats; 107 | hal_rtstats_collect(&stats); 108 | pwrman_consume(PWRMAN_C_RUN, stats.run_ticks, BRD_PWR_RUN_UA); 109 | pwrman_consume(PWRMAN_C_SLEEP, stats.sleep_ticks[HAL_SLEEP_S0], BRD_PWR_S0_UA); 110 | pwrman_consume(PWRMAN_C_SLEEP, stats.sleep_ticks[HAL_SLEEP_S1], BRD_PWR_S1_UA); 111 | pwrman_consume(PWRMAN_C_SLEEP, stats.sleep_ticks[HAL_SLEEP_S2], BRD_PWR_S2_UA); 112 | #endif 113 | } 114 | -------------------------------------------------------------------------------- /services/pwrman/pwrman.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | // 3 | // This file is subject to the terms and conditions defined in file 'LICENSE', 4 | // which is part of this source code package. 5 | 6 | #ifndef _pwrman_h_ 7 | #define _pwrman_h_ 8 | 9 | enum { 10 | PWRMAN_C_RUN, // MCU run 11 | PWRMAN_C_SLEEP, // MCU sleep 12 | PWRMAN_C_RX, // radio RX 13 | PWRMAN_C_TX, // radio TX 14 | PWRMAN_C_APP1, // application-specific 1 15 | PWRMAN_C_APP2, // application-specific 2 16 | PWRMAN_C_APP3, // application-specific 3 17 | PWRMAN_C_APP4, // application-specific 4 18 | 19 | PWRMAN_C_MAX 20 | }; 21 | 22 | void pwrman_consume (int ctype, uint32_t ticks, uint32_t ua); 23 | void pwrman_commit (void); 24 | void pwrman_reset (void); 25 | 26 | uint32_t pwrman_accu_uah (void); 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /services/uexti.svc: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | # 3 | # This file is subject to the terms and conditions defined in file 'LICENSE', 4 | # which is part of this source code package. 5 | 6 | 7 | src: 8 | - uexti/uexti.c 9 | 10 | hooks: 11 | - void uexti_irq (unsigned int* mask) 12 | 13 | 14 | # vim: syntax=yaml 15 | -------------------------------------------------------------------------------- /services/uexti/uexti.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | // 3 | // This file is subject to the terms and conditions defined in file 'LICENSE', 4 | // which is part of this source code package. 5 | 6 | #include "lmic.h" 7 | 8 | #include "svcdefs.h" 9 | #include "uexti.h" 10 | 11 | #include "peripherals.h" 12 | 13 | // ------------------------------------------------ 14 | // Unified External Interrupt Handler 15 | 16 | static void hook_uexti_irq (unsigned int* mask) { 17 | SVCHOOK_uexti_irq(mask); 18 | } 19 | 20 | #if defined(STM32L0) 21 | 22 | #include "board.h" 23 | 24 | #define uexti_handler 1 25 | #if (CFG_EXTI_IRQ_HANDLER != uexti_handler) 26 | #error "UEXTI requires 'uexti_handler' to be set as EXTI_IRQ_HANDLER!" 27 | #endif 28 | #undef uexti_handler 29 | 30 | void uexti_handler (void) { 31 | unsigned int m0, m1; 32 | m0 = m1 = EXTI->PR; // get pending interrupts 33 | hook_uexti_irq(&m1); // call hooks 34 | EXTI->PR = (m0 ^ m1) & m0; // clear handled interrupts 35 | } 36 | 37 | void uexti_config (unsigned int gpio, bool rising, bool falling) { 38 | gpio_cfg_extirq_ex(BRD_PORT(gpio), BRD_PIN(gpio), rising, falling); 39 | } 40 | 41 | void uexti_enable (unsigned int gpio, bool enable) { 42 | gpio_set_extirq(BRD_PIN(gpio), enable); 43 | } 44 | 45 | #elif defined(unicorn) 46 | 47 | void uexti_handler (void) { 48 | unsigned int mask = pio_irq_get(); 49 | unsigned int m0 = mask; 50 | hook_uexti_irq(&mask); // call hooks 51 | pio_irq_clear(mask ^ m0); 52 | } 53 | 54 | void uexti_config (unsigned int gpio, bool rising, bool falling) { 55 | pio_irq_config(gpio, rising, falling); 56 | } 57 | 58 | void uexti_enable (unsigned int gpio, bool enable) { 59 | pio_irq_enable(gpio, enable); 60 | } 61 | 62 | #else 63 | #error "Platform not supported by UEXTI service module" 64 | #endif 65 | -------------------------------------------------------------------------------- /services/uexti/uexti.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | // 3 | // This file is subject to the terms and conditions defined in file 'LICENSE', 4 | // which is part of this source code package. 5 | 6 | #ifndef _uexti_h_ 7 | #define _uexti_h_ 8 | 9 | #include 10 | 11 | void uexti_config (unsigned int gpio, bool rising, bool falling); 12 | void uexti_enable (unsigned int gpio, bool enable); 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /stm32/CMSIS/Device/ST/STM32L0xx/Include/system_stm32l0xx.h: -------------------------------------------------------------------------------- 1 | /** 2 | ****************************************************************************** 3 | * @file system_stm32l0xx.h 4 | * @author MCD Application Team 5 | * @version V1.2.0 6 | * @date 06-February-2015 7 | * @brief CMSIS Cortex-M0+ Device Peripheral Access Layer System Header File. 8 | ****************************************************************************** 9 | * @attention 10 | * 11 | *

© COPYRIGHT(c) 2015 STMicroelectronics

12 | * 13 | * Redistribution and use in source and binary forms, with or without modification, 14 | * are permitted provided that the following conditions are met: 15 | * 1. Redistributions of source code must retain the above copyright notice, 16 | * this list of conditions and the following disclaimer. 17 | * 2. Redistributions in binary form must reproduce the above copyright notice, 18 | * this list of conditions and the following disclaimer in the documentation 19 | * and/or other materials provided with the distribution. 20 | * 3. Neither the name of STMicroelectronics nor the names of its contributors 21 | * may be used to endorse or promote products derived from this software 22 | * without specific prior written permission. 23 | * 24 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 27 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 28 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 31 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 32 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 33 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | * 35 | ****************************************************************************** 36 | */ 37 | 38 | /** @addtogroup CMSIS 39 | * @{ 40 | */ 41 | 42 | /** @addtogroup stm32l0xx_system 43 | * @{ 44 | */ 45 | 46 | /** 47 | * @brief Define to prevent recursive inclusion 48 | */ 49 | #ifndef __SYSTEM_STM32L0XX_H 50 | #define __SYSTEM_STM32L0XX_H 51 | 52 | #ifdef __cplusplus 53 | extern "C" { 54 | #endif 55 | 56 | /** @addtogroup STM32L0xx_System_Includes 57 | * @{ 58 | */ 59 | 60 | /** 61 | * @} 62 | */ 63 | 64 | 65 | /** @addtogroup STM32L0xx_System_Exported_types 66 | * @{ 67 | */ 68 | /* This variable is updated in three ways: 69 | 1) by calling CMSIS function SystemCoreClockUpdate() 70 | 2) by calling HAL API function HAL_RCC_GetSysClockFreq() 71 | 3) each time HAL_RCC_ClockConfig() is called to configure the system clock frequency 72 | Note: If you use this function to configure the system clock; then there 73 | is no need to call the 2 first functions listed above, since SystemCoreClock 74 | variable is updated automatically. 75 | */ 76 | extern uint32_t SystemCoreClock; /*!< System Clock Frequency (Core Clock) */ 77 | 78 | /** 79 | * @} 80 | */ 81 | 82 | /** @addtogroup STM32L0xx_System_Exported_Constants 83 | * @{ 84 | */ 85 | 86 | /** 87 | * @} 88 | */ 89 | 90 | /** @addtogroup STM32L0xx_System_Exported_Macros 91 | * @{ 92 | */ 93 | 94 | /** 95 | * @} 96 | */ 97 | 98 | /** @addtogroup STM32L0xx_System_Exported_Functions 99 | * @{ 100 | */ 101 | 102 | extern void SystemInit(void); 103 | extern void SystemCoreClockUpdate(void); 104 | /** 105 | * @} 106 | */ 107 | 108 | #ifdef __cplusplus 109 | } 110 | #endif 111 | 112 | #endif /*__SYSTEM_STM32L0XX_H */ 113 | 114 | /** 115 | * @} 116 | */ 117 | 118 | /** 119 | * @} 120 | */ 121 | /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ 122 | -------------------------------------------------------------------------------- /stm32/CMSIS/Include/arm_const_structs.h: -------------------------------------------------------------------------------- 1 | /* ---------------------------------------------------------------------- 2 | * Copyright (C) 2010-2014 ARM Limited. All rights reserved. 3 | * 4 | * $Date: 31. July 2014 5 | * $Revision: V1.4.4 6 | * 7 | * Project: CMSIS DSP Library 8 | * Title: arm_const_structs.h 9 | * 10 | * Description: This file has constant structs that are initialized for 11 | * user convenience. For example, some can be given as 12 | * arguments to the arm_cfft_f32() function. 13 | * 14 | * Target Processor: Cortex-M4/Cortex-M3 15 | * 16 | * Redistribution and use in source and binary forms, with or without 17 | * modification, are permitted provided that the following conditions 18 | * are met: 19 | * - Redistributions of source code must retain the above copyright 20 | * notice, this list of conditions and the following disclaimer. 21 | * - Redistributions in binary form must reproduce the above copyright 22 | * notice, this list of conditions and the following disclaimer in 23 | * the documentation and/or other materials provided with the 24 | * distribution. 25 | * - Neither the name of ARM LIMITED nor the names of its contributors 26 | * may be used to endorse or promote products derived from this 27 | * software without specific prior written permission. 28 | * 29 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 30 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 31 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 32 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 33 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 34 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 35 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 36 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 37 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 38 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 39 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 40 | * POSSIBILITY OF SUCH DAMAGE. 41 | * -------------------------------------------------------------------- */ 42 | 43 | #ifndef _ARM_CONST_STRUCTS_H 44 | #define _ARM_CONST_STRUCTS_H 45 | 46 | #include "arm_math.h" 47 | #include "arm_common_tables.h" 48 | 49 | extern const arm_cfft_instance_f32 arm_cfft_sR_f32_len16; 50 | extern const arm_cfft_instance_f32 arm_cfft_sR_f32_len32; 51 | extern const arm_cfft_instance_f32 arm_cfft_sR_f32_len64; 52 | extern const arm_cfft_instance_f32 arm_cfft_sR_f32_len128; 53 | extern const arm_cfft_instance_f32 arm_cfft_sR_f32_len256; 54 | extern const arm_cfft_instance_f32 arm_cfft_sR_f32_len512; 55 | extern const arm_cfft_instance_f32 arm_cfft_sR_f32_len1024; 56 | extern const arm_cfft_instance_f32 arm_cfft_sR_f32_len2048; 57 | extern const arm_cfft_instance_f32 arm_cfft_sR_f32_len4096; 58 | 59 | extern const arm_cfft_instance_q31 arm_cfft_sR_q31_len16; 60 | extern const arm_cfft_instance_q31 arm_cfft_sR_q31_len32; 61 | extern const arm_cfft_instance_q31 arm_cfft_sR_q31_len64; 62 | extern const arm_cfft_instance_q31 arm_cfft_sR_q31_len128; 63 | extern const arm_cfft_instance_q31 arm_cfft_sR_q31_len256; 64 | extern const arm_cfft_instance_q31 arm_cfft_sR_q31_len512; 65 | extern const arm_cfft_instance_q31 arm_cfft_sR_q31_len1024; 66 | extern const arm_cfft_instance_q31 arm_cfft_sR_q31_len2048; 67 | extern const arm_cfft_instance_q31 arm_cfft_sR_q31_len4096; 68 | 69 | extern const arm_cfft_instance_q15 arm_cfft_sR_q15_len16; 70 | extern const arm_cfft_instance_q15 arm_cfft_sR_q15_len32; 71 | extern const arm_cfft_instance_q15 arm_cfft_sR_q15_len64; 72 | extern const arm_cfft_instance_q15 arm_cfft_sR_q15_len128; 73 | extern const arm_cfft_instance_q15 arm_cfft_sR_q15_len256; 74 | extern const arm_cfft_instance_q15 arm_cfft_sR_q15_len512; 75 | extern const arm_cfft_instance_q15 arm_cfft_sR_q15_len1024; 76 | extern const arm_cfft_instance_q15 arm_cfft_sR_q15_len2048; 77 | extern const arm_cfft_instance_q15 arm_cfft_sR_q15_len4096; 78 | 79 | #endif 80 | -------------------------------------------------------------------------------- /stm32/adc.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | // Copyright (C) 2014-2016 IBM Corporation. All rights reserved. 3 | // 4 | // This file is subject to the terms and conditions defined in file 'LICENSE', 5 | // which is part of this source code package. 6 | 7 | #include "lmic.h" 8 | #include "peripherals.h" 9 | 10 | static void adc_on (void) { 11 | #if defined(STM32L0) 12 | RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; // enable peripheral clock 13 | ADC1->CR |= ADC_CR_ADCAL; // start calibration 14 | while( (ADC1->CR & ADC_CR_ADCAL) != 0 ); // wait for it 15 | ADC1->ISR = ADC_ISR_ADRDY; // clear ready bit (rc_w1) 16 | ADC1->CR |= ADC_CR_ADEN; // switch on the ADC 17 | while( (ADC1->ISR & ADC_ISR_ADRDY) == 0 ); // wait until ADC is ready 18 | #elif defined(STM32L1) 19 | RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; // make sure the ADC is clocked 20 | ADC1->CR2 |= ADC_CR2_ADON; // switch on the ADC 21 | #endif 22 | } 23 | 24 | static void adc_off (void) { 25 | #if defined(STM32L0) 26 | ADC1->CR |= ADC_CR_ADDIS; // switch off the ADC 27 | while( (ADC1->CR & ADC_CR_ADEN) != 0 ); // wait for it 28 | ADC1->CR &= ~ADC_CR_ADVREGEN; // switch off regulator 29 | #elif defined(STM32L1) 30 | #warning "adc_off() not yet implemented for STM32L0" 31 | // TODO - implement 32 | #endif 33 | } 34 | 35 | unsigned int adc_read (unsigned int chnl, unsigned int rate) { 36 | adc_on(); 37 | #if defined(STM32L0) 38 | if( chnl == VREFINT_ADC_CH ) { 39 | ADC->CCR |= ADC_CCR_VREFEN; // internal voltage reference on channel 17 40 | } else if( chnl == TEMPINT_ADC_CH ) { 41 | ADC->CCR |= ADC_CCR_TSEN; // internal temperature on channel 18 42 | } 43 | ADC1->CHSELR = (1 << chnl); // select channel 44 | ADC1->SMPR = rate & 0x7; // sample rate 45 | ADC1->CR |= ADC_CR_ADSTART; // start conversion 46 | while( (ADC1->ISR & ADC_ISR_EOC) == 0 ); // wait for it 47 | if( chnl == VREFINT_ADC_CH ) { 48 | ADC->CCR &= ~ADC_CCR_VREFEN; 49 | } else if( chnl == TEMPINT_ADC_CH ) { 50 | ADC->CCR &= ~ADC_CCR_TSEN; 51 | } 52 | #elif defined(STM32L1) 53 | ADC1->SQR5 = chnl; // select the channel for the 1st conversion 54 | ADC1->CR2 |= ADC_CR2_SWSTART; // start the conversion 55 | while( (ADC1->SR & ADC_SR_EOC) == 0 ); // wait for it 56 | #endif 57 | u2_t v = ADC1->DR; 58 | adc_off(); 59 | return v; 60 | } 61 | -------------------------------------------------------------------------------- /stm32/board.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2020 Michael Kuyper. All rights reserved. 2 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 3 | // 4 | // This file is subject to the terms and conditions defined in file 'LICENSE', 5 | // which is part of this source code package. 6 | 7 | #ifndef _board_h_ 8 | #define _board_h_ 9 | 10 | // GPIO definitions 11 | // 33222222 22221111 11111100 00000000 12 | // 10987654 32109876 54321098 76543210 13 | // ________ _____fff ccccaaaa PPPPpppp 14 | 15 | #define BRD_GPIO(port,pin) (((port) << 4) | (pin)) 16 | #define BRD_GPIO_EX(port,pin,ex) (((port) << 4) | (pin) | (ex)) 17 | #define BRD_GPIO_AF(port,pin,af) (((af) << 8) | ((port) << 4) | (pin)) 18 | #define BRD_GPIO_AF_EX(port,pin,af,ex) (((af) << 8) | ((port) << 4) | (pin) | (ex)) 19 | #define BRD_PIN(gpio) ((gpio) & 0x0f) 20 | #define BRD_PORT(gpio) (((gpio) >> 4) & 0x0f) 21 | #define BRD_AF(gpio) (((gpio) >> 8) & 0x0f) 22 | 23 | // alternate function configuratons (c) 24 | #define BRD_GPIO_CHAN(ch) ((ch) << 12) 25 | #define BRD_GPIO_GET_CHAN(gpio) (((gpio) >> 12) & 0x07) 26 | 27 | // flags (f) 28 | #define BRD_GPIO_EXT_PULLUP (1 << 16) 29 | #define BRD_GPIO_EXT_PULLDN (1 << 17) 30 | #define BRD_GPIO_ACTIVE_LOW (1 << 18) 31 | 32 | // GPIO ports 33 | #define PORT_A 0 34 | #define PORT_B 1 35 | #define PORT_C 2 36 | 37 | // macros to define DMA channels 38 | #define BRD_DMA_CHAN(a) (a) 39 | #define BRD_DMA_CHANS(a,b) (((b) << 4) | (a)) 40 | #define BRD_DMA_CHAN_A(x) (((x) & 0xf) - 1) 41 | #define BRD_DMA_CHAN_B(x) ((((x) >> 4) & 0xf) - 1) 42 | 43 | // UART instances 44 | #define BRD_USART1 (1 << 0) 45 | #define BRD_USART2 (1 << 1) 46 | #define BRD_LPUART1 (1 << 2) 47 | 48 | // UART ports 49 | #define BRD_USART1_PORT usart_port_u1 50 | #define BRD_USART2_PORT usart_port_u2 51 | #define BRD_LPUART1_PORT usart_port_lpu1 52 | 53 | #define BRD_USART_EN(m) (((BRD_USART) & (m)) != 0) 54 | 55 | // Timer instances 56 | #define BRD_TIM2 (1 << 0) 57 | #define BRD_TIM3 (1 << 1) 58 | 59 | // Timer peripherals 60 | #define BRD_TIM2_PERIPH tmr_t2 61 | #define BRD_TIM3_PERIPH tmr_t3 62 | 63 | #define BRD_TMR_EN(m) (((BRD_TMR) & (m)) != 0) 64 | 65 | 66 | #ifdef BRD_IMPL_INC 67 | #include BRD_IMPL_INC 68 | #else 69 | #error "Missing board implementation include file" 70 | #endif 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /stm32/brd_devboards.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2020 Michael Kuyper. All rights reserved. 2 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 3 | // 4 | // This file is subject to the terms and conditions defined in file 'LICENSE', 5 | // which is part of this source code package. 6 | 7 | // to be included from board.h 8 | 9 | // ------------------------------------------- 10 | #if defined(CFG_nucleo_board) 11 | 12 | #define GPIO_RST BRD_GPIO(PORT_A, 0) 13 | 14 | #if defined(CFG_sx1272mbed) 15 | #define BRD_sx1272_radio 16 | #define GPIO_DIO0 BRD_GPIO(PORT_A, 10) 17 | #define GPIO_DIO1 BRD_GPIO(PORT_B, 3) 18 | #define GPIO_DIO2 BRD_GPIO(PORT_B, 5) 19 | #define GPIO_NSS BRD_GPIO(PORT_B, 6) 20 | 21 | #elif defined(CFG_sx1276mb1mas) || defined(CFG_sx1276mb1las) 22 | 23 | #define GPIO_DIO0 BRD_GPIO(PORT_A, 10) 24 | #define GPIO_DIO1 BRD_GPIO(PORT_B, 3) 25 | #define GPIO_DIO2 BRD_GPIO(PORT_B, 5) 26 | #define GPIO_NSS BRD_GPIO(PORT_B, 6) 27 | #define GPIO_TX BRD_GPIO(PORT_C, 1) 28 | 29 | #define BRD_sx1276_radio 30 | #if defined(CFG_sx1276mb1las) 31 | #define BRD_PABOOSTSEL(f,p) true 32 | #else 33 | #define BRD_PABOOSTSEL(f,p) false 34 | #endif 35 | 36 | #elif defined(CFG_sx1261mbed) || defined(CFG_sx1262mbed) 37 | 38 | #if defined(CFG_sx1261mbed) 39 | #define BRD_sx1261_radio 40 | #elif defined(CFG_sx1262mbed) 41 | #define BRD_sx1262_radio 42 | #endif 43 | #define GPIO_DIO1 BRD_GPIO_AF_EX(PORT_B, 4, 4, BRD_GPIO_CHAN(1)) 44 | #define GPIO_BUSY BRD_GPIO(PORT_B, 3) 45 | #define GPIO_NSS BRD_GPIO(PORT_A, 8) 46 | #define GPIO_TXRX_EN BRD_GPIO(PORT_A, 9) 47 | 48 | #else 49 | #error "Missing radio configuration" 50 | #endif 51 | 52 | #define BRD_RADIO_SPI 1 53 | #define GPIO_SCK BRD_GPIO_AF(PORT_A, 5, 0) 54 | #define GPIO_MISO BRD_GPIO_AF(PORT_A, 6, 0) 55 | #define GPIO_MOSI BRD_GPIO_AF(PORT_A, 7, 0) 56 | 57 | // Enabled USART peripherals 58 | #define BRD_USART (BRD_LPUART1 | BRD_USART2) 59 | 60 | // LPUART1 61 | #define BRD_LPUART1_DMA BRD_DMA_CHANS(2,3) 62 | #define GPIO_LPUART1_TX BRD_GPIO_AF(PORT_C, 4, 2) 63 | #define GPIO_LPUART1_RX BRD_GPIO_AF(PORT_C, 5, 2) 64 | 65 | // USART2 66 | #define BRD_USART2_DMA BRD_DMA_CHANS(4,5) 67 | #define GPIO_USART2_TX BRD_GPIO_AF(PORT_A, 2, 4) 68 | #define GPIO_USART2_RX BRD_GPIO_AF(PORT_A, 3, 4) 69 | 70 | // Debug LED / USART 71 | //#define GPIO_DBG_LED BRD_GPIO(PORT_A, 5) // -- LED is shared with SCK!! 72 | #define BRD_DBG_UART BRD_USART2_PORT 73 | 74 | // User button 75 | #define GPIO_BUTTON BRD_GPIO_EX(PORT_C, 13, BRD_GPIO_ACTIVE_LOW) 76 | 77 | // Personalization UART 78 | #define BRD_PERSO_UART BRD_USART2_PORT 79 | #define GPIO_PERSO_DET GPIO_BUTTON 80 | 81 | // power consumption 82 | 83 | #ifndef BRD_PWR_RUN_UA 84 | #define BRD_PWR_RUN_UA 6000 85 | #endif 86 | 87 | #ifndef BRD_PWR_S0_UA 88 | #define BRD_PWR_S0_UA 2000 89 | #endif 90 | 91 | #ifndef BRD_PWR_S1_UA 92 | #define BRD_PWR_S1_UA 12 93 | #endif 94 | 95 | #ifndef BRD_PWR_S2_UA 96 | #define BRD_PWR_S2_UA 5 97 | #endif 98 | 99 | 100 | // ------------------------------------------- 101 | #elif defined(CFG_b_l072Z_lrwan1_board) 102 | 103 | #define GPIO_RST BRD_GPIO(PORT_C, 0) 104 | #define GPIO_DIO0 BRD_GPIO_AF_EX(PORT_B, 4, 4, BRD_GPIO_CHAN(1)) 105 | #define GPIO_DIO1 BRD_GPIO(PORT_B, 1) 106 | #define GPIO_DIO2 BRD_GPIO(PORT_B, 0) 107 | #define GPIO_DIO3 BRD_GPIO(PORT_C, 13) 108 | #define GPIO_DIO4 BRD_GPIO(PORT_A, 5) 109 | #define GPIO_DIO5 BRD_GPIO(PORT_A, 4) 110 | 111 | #define GPIO_TCXO_PWR BRD_GPIO(PORT_A, 12) 112 | #define GPIO_RX BRD_GPIO(PORT_A, 1) // PA_RFI 113 | #define GPIO_TX BRD_GPIO(PORT_C, 1) // PA_BOOST 114 | #define GPIO_TX2 BRD_GPIO(PORT_C, 2) // PA_RFO 115 | 116 | #define GPIO_LED1 BRD_GPIO(PORT_B, 5) // grn 117 | #define GPIO_LED2 BRD_GPIO(PORT_A, 5) // red -- used by bootloader 118 | #define GPIO_LED3 BRD_GPIO(PORT_B, 6) // blu 119 | #define GPIO_LED4 BRD_GPIO(PORT_B, 7) // red 120 | 121 | #define GPIO_BUTTON BRD_GPIO_EX(PORT_B, 2, BRD_GPIO_ACTIVE_LOW) 122 | 123 | // button PB2 124 | 125 | #define BRD_sx1276_radio 126 | #define BRD_PABOOSTSEL(f,p) ((p) > 15) 127 | #define BRD_TXANTSWSEL(f,p) ((BRD_PABOOSTSEL(f,p)) ? HAL_ANTSW_TX : HAL_ANTSW_TX2) 128 | 129 | #define BRD_RADIO_SPI 1 130 | #define GPIO_NSS BRD_GPIO(PORT_A, 15) 131 | #define GPIO_SCK BRD_GPIO_AF(PORT_B, 3, 0) 132 | #define GPIO_MISO BRD_GPIO_AF(PORT_A, 6, 0) 133 | #define GPIO_MOSI BRD_GPIO_AF(PORT_A, 7, 0) 134 | 135 | // Enabled USART peripherals 136 | #define BRD_USART (BRD_USART1 | BRD_USART2) 137 | 138 | // USART1 139 | #define BRD_USART1_DMA BRD_DMA_CHANS(2,3) 140 | #define GPIO_USART1_TX BRD_GPIO_AF(PORT_A, 9, 4) 141 | #define GPIO_USART1_RX BRD_GPIO_AF(PORT_A, 10, 4) 142 | 143 | // USART2 144 | #define BRD_USART2_DMA BRD_DMA_CHANS(4,5) 145 | #define GPIO_USART2_TX BRD_GPIO_AF(PORT_A, 2, 4) 146 | #define GPIO_USART2_RX BRD_GPIO_AF(PORT_A, 3, 4) 147 | 148 | // Debug LED / USART 149 | #define GPIO_DBG_LED GPIO_LED4 150 | #define BRD_DBG_UART BRD_USART2_PORT 151 | 152 | // Personalization UART 153 | #define BRD_PERSO_UART BRD_USART2_PORT 154 | #define GPIO_PERSO_DET GPIO_BUTTON 155 | 156 | // power consumption 157 | 158 | #ifndef BRD_PWR_RUN_UA 159 | #define BRD_PWR_RUN_UA 6000 160 | #endif 161 | 162 | #ifndef BRD_PWR_S0_UA 163 | #define BRD_PWR_S0_UA 2000 164 | #endif 165 | 166 | #ifndef BRD_PWR_S1_UA 167 | #define BRD_PWR_S1_UA 12 168 | #endif 169 | 170 | #ifndef BRD_PWR_S2_UA 171 | #define BRD_PWR_S2_UA 5 172 | #endif 173 | 174 | // brown-out 175 | #define BRD_borlevel 9 // RM0376, pg 116: BOR level 2, around 2.0 V 176 | 177 | #endif 178 | -------------------------------------------------------------------------------- /stm32/dma.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2020 Michael Kuyper. All rights reserved. 2 | // 3 | // This file is subject to the terms and conditions defined in file 'LICENSE', 4 | // which is part of this source code package. 5 | 6 | #include "hw.h" 7 | 8 | #ifdef HW_DMA 9 | 10 | static struct { 11 | unsigned int active; 12 | struct { 13 | dma_cb callback; 14 | void* arg; 15 | } chan[7]; 16 | } dma; 17 | 18 | // NOTE: We use a 0-based index for DMA channels, while the STM32 numbers them 19 | // starting at 1. Thus, ch=0 refers to DMA channel 1, etc. 20 | 21 | #define MASK_BIT(ch) (1 << ((ch)-1)) 22 | #define MASK_CH1 MASK_BIT(1) 23 | #define MASK_CH23 (MASK_BIT(2) | MASK_BIT(3)) 24 | #define MASK_CH4567 (MASK_BIT(4) | MASK_BIT(5) | MASK_BIT(6) | MASK_BIT(7)) 25 | 26 | static void ch_mask_irqn (unsigned int ch, unsigned int* mask, int* irqn) { 27 | if( ch == 0 ) { 28 | *mask = MASK_CH1; 29 | *irqn = DMA1_Channel1_IRQn; 30 | } else if( ch < 3 ) { 31 | *mask = MASK_CH23; 32 | *irqn = DMA1_Channel2_3_IRQn; 33 | } else { 34 | *mask = MASK_CH4567; 35 | *irqn = DMA1_Channel4_5_6_7_IRQn; 36 | } 37 | } 38 | 39 | static void irq_on (unsigned int ch) { 40 | unsigned int mask; 41 | int irqn; 42 | ch_mask_irqn(ch, &mask, &irqn); 43 | // enable IRQ if no channel is active yet in block 44 | if( (dma.active & mask) == 0 ) { 45 | NVIC_EnableIRQ(irqn); 46 | } 47 | } 48 | 49 | static void irq_off (unsigned int ch) { 50 | unsigned int mask; 51 | int irqn; 52 | ch_mask_irqn(ch, &mask, &irqn); 53 | // disable IRQ if no channel is active anymore in block 54 | if( (dma.active & mask) == 0 ) { 55 | NVIC_DisableIRQ(irqn); 56 | } 57 | } 58 | 59 | static void dma_on (unsigned int ch) { 60 | hal_disableIRQs(); 61 | if( dma.active == 0 ) { 62 | RCC->AHBENR |= RCC_AHBENR_DMA1EN; 63 | } 64 | irq_on(ch); 65 | dma.active |= (1 << ch); 66 | hal_enableIRQs(); 67 | } 68 | 69 | static void dma_off (unsigned int ch) { 70 | hal_disableIRQs(); 71 | dma.active &= ~(1 << ch); 72 | irq_off(ch); 73 | if( dma.active == 0 ) { 74 | RCC->AHBENR &= ~RCC_AHBENR_DMA1EN; 75 | } 76 | hal_enableIRQs(); 77 | } 78 | 79 | #define DMACHAN(ch) ((DMA_Channel_TypeDef*)(DMA1_Channel1_BASE + (ch) * (DMA1_Channel2_BASE-DMA1_Channel1_BASE))) 80 | 81 | void dma_config (unsigned int ch, unsigned int peripheral, unsigned int ccr, unsigned int flags, dma_cb callback, void* arg) { 82 | dma.chan[ch].callback = callback; 83 | dma.chan[ch].arg = arg; 84 | dma_on(ch); 85 | DMACHAN(ch)->CCR = ccr; 86 | DMA1_CSELR->CSELR = (DMA1_CSELR->CSELR & ~(0xf << (ch<<2))) | (peripheral << (ch<<2)); 87 | DMA1->IFCR = 0xf << (ch<<2); 88 | if( (flags & DMA_CB_COMPLETE) ) { 89 | DMACHAN(ch)->CCR |= DMA_CCR_TCIE; 90 | } 91 | if( (flags & DMA_CB_HALF) ) { 92 | DMACHAN(ch)->CCR |= DMA_CCR_HTIE; 93 | } 94 | } 95 | 96 | int dma_deconfig (unsigned int ch) { 97 | int n = DMACHAN(ch)->CNDTR; 98 | DMACHAN(ch)->CCR = 0; 99 | dma_off(ch); 100 | // return remaning bytes 101 | return n; 102 | } 103 | 104 | int dma_remaining (unsigned int ch) { 105 | return DMACHAN(ch)->CNDTR; 106 | } 107 | 108 | void dma_transfer (unsigned int ch, volatile void* paddr, void* maddr, int n) { 109 | DMACHAN(ch)->CPAR = (uint32_t) paddr; 110 | DMACHAN(ch)->CMAR = (uint32_t) maddr; 111 | DMACHAN(ch)->CNDTR = n; 112 | DMACHAN(ch)->CCR |= DMA_CCR_EN; 113 | } 114 | 115 | void dma_irq (void) { 116 | unsigned int isr = DMA1->ISR; 117 | for( int ch = 0; ch < 7; ch++ ) { 118 | unsigned int ccr = DMACHAN(ch)->CCR; 119 | if( (ccr & DMA_CCR_TCIE) && (isr & (DMA_ISR_TCIF1 << (ch<<2))) ) { 120 | DMA1->IFCR = DMA_IFCR_CTCIF1 << (ch<<2); 121 | dma.chan[ch].callback(DMA_CB_COMPLETE, dma.chan[ch].arg); 122 | } 123 | if( (ccr & DMA_CCR_HTIE) && (isr & (DMA_ISR_HTIF1 << (ch<<2))) ) { 124 | DMA1->IFCR = DMA_IFCR_CHTIF1 << (ch<<2); 125 | dma.chan[ch].callback(DMA_CB_HALF, dma.chan[ch].arg); 126 | } 127 | } 128 | } 129 | 130 | #endif 131 | -------------------------------------------------------------------------------- /stm32/eeprom.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | // Copyright (C) 2014-2016 IBM Corporation. All rights reserved. 3 | // 4 | // This file is subject to the terms and conditions defined in file 'LICENSE', 5 | // which is part of this source code package. 6 | 7 | #include "peripherals.h" 8 | 9 | // write 32-bit word to EEPROM memory 10 | void eeprom_write (void* dest, unsigned int val) { 11 | u4_t* addr = dest; 12 | // check previous value 13 | if( *addr != val ) { 14 | // unlock data eeprom memory and registers 15 | FLASH->PEKEYR = 0x89ABCDEF; // FLASH_PEKEY1 16 | FLASH->PEKEYR = 0x02030405; // FLASH_PEKEY2 17 | 18 | // only auto-erase if neccessary (when content is non-zero) 19 | #if defined(STM32L0) 20 | FLASH->PECR &= ~FLASH_PECR_FIX; // clear FIX 21 | #elif defined(STM32L1) 22 | FLASH->PECR &= ~FLASH_PECR_FTDW; // clear FTDW 23 | #endif 24 | 25 | // write value 26 | *addr = val; 27 | 28 | // check for end of programming 29 | while( FLASH->SR & FLASH_SR_BSY ); // loop while busy 30 | 31 | // lock data eeprom memory and registers 32 | FLASH->PECR |= FLASH_PECR_PELOCK; 33 | 34 | // verify value 35 | ASSERT( *((volatile u4_t*) addr) == val ); 36 | } 37 | } 38 | 39 | void eeprom_copy (void* dest, const void* src, int len) { 40 | ASSERT( (((u4_t) dest | (u4_t) src | len) & 3) == 0 ); 41 | u4_t* d = (u4_t*) dest; 42 | u4_t* s = (u4_t*) src; 43 | len >>= 2; 44 | 45 | while( len-- ) { 46 | eeprom_write(d++, *s++); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /stm32/fw.ld: -------------------------------------------------------------------------------- 1 | SECTIONS { 2 | .text : { 3 | . = ALIGN(4); 4 | KEEP(*(.fwhdr)) 5 | . = ALIGN(4); 6 | *(.text) 7 | *(.text*) 8 | . = ALIGN(4); 9 | } >FWFLASH 10 | 11 | .data : { 12 | . = ALIGN(4); 13 | _sdata = .; 14 | *(.fastcode) 15 | *(.fastcode*) 16 | . = ALIGN(4); 17 | *(.data) 18 | *(.data*) 19 | . = ALIGN(4); 20 | _edata = .; 21 | } >RAM AT>FWFLASH 22 | 23 | _sidata = LOADADDR(.data); 24 | 25 | .bss : { 26 | . = ALIGN(4); 27 | _sbss = .; 28 | *(.bss) 29 | *(.bss*) 30 | *(COMMON) 31 | . = ALIGN(4); 32 | _ebss = .; 33 | } >RAM 34 | 35 | .rodata : { 36 | . = ALIGN(4); 37 | *(.rodata) 38 | *(.rodata*) 39 | . = ALIGN(4); 40 | 41 | /* make sure flash image is a multiple of page size */ 42 | FILL(0x00000000) 43 | . = ALIGN(128); 44 | __fw_end__ = .; 45 | } >FWFLASH 46 | 47 | /DISCARD/ : { 48 | *(.ARM) 49 | *(.ARM*) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /stm32/gpio.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2020 Michael Kuyper. All rights reserved. 2 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 3 | // 4 | // This file is subject to the terms and conditions defined in file 'LICENSE', 5 | // which is part of this source code package. 6 | 7 | #include "lmic.h" 8 | 9 | static unsigned int gpio_on[3]; 10 | 11 | static void gpio_begin (int port) { 12 | if( gpio_on[port] == 0 ) { 13 | GPIO_RCC_ENR |= GPIO_EN(port); 14 | // dummy read as per errata 15 | (void) GPIOx(port)->IDR; 16 | } 17 | gpio_on[port] += 1; 18 | } 19 | 20 | static void gpio_end (int port) { 21 | gpio_on[port] -= 1; 22 | if( gpio_on[port] == 0 ) { 23 | GPIO_RCC_ENR &= ~GPIO_EN(port); 24 | } 25 | } 26 | 27 | void gpio_cfg_pin (int port, int pin, int gpiocfg) { 28 | gpio_begin(port); 29 | HW_CFG_PIN(GPIOx(port), pin, gpiocfg); 30 | gpio_end(port); 31 | } 32 | 33 | void gpio_set_pin (int port, int pin, int state) { 34 | gpio_begin(port); 35 | HW_SET_PIN(GPIOx(port), pin, state); 36 | gpio_end(port); 37 | } 38 | 39 | void gpio_cfg_set_pin (int port, int pin, int gpiocfg, int state) { 40 | gpio_begin(port); 41 | HW_SET_PIN(GPIOx(port), pin, state); 42 | HW_CFG_PIN(GPIOx(port), pin, gpiocfg); 43 | gpio_end(port); 44 | } 45 | 46 | int gpio_get_pin (int port, int pin) { 47 | int val; 48 | gpio_begin(port); 49 | val = HW_GET_PIN(GPIOx(port), pin); 50 | gpio_end(port); 51 | return val; 52 | } 53 | 54 | int gpio_transition (int port, int pin, int type, int duration, unsigned int config) { 55 | int val; 56 | gpio_begin(port); 57 | val = HW_GET_PIN(GPIOx(port), pin); 58 | HW_SET_PIN(GPIOx(port), pin, type); 59 | HW_CFG_PIN(GPIOx(port), pin, GPIOCFG_MODE_OUT | GPIOCFG_OSPEED_400kHz | GPIOCFG_OTYPE_PUPD | GPIOCFG_PUPD_NONE); 60 | for (int i = 0; i < duration; i++) __NOP(); 61 | HW_SET_PIN(GPIOx(port), pin, type ^ 1); 62 | for (int i = 0; i < duration; i++) __NOP(); 63 | HW_CFG_PIN(GPIOx(port), pin, config); 64 | gpio_end(port); 65 | return val; 66 | } 67 | 68 | void gpio_cfg_extirq_ex (int port, int pin, bool rising, bool falling) { 69 | RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN; // enable system configuration controller 70 | 71 | // configure external interrupt (every irq line 0-15 can be configured with a 4-bit port index A-G) 72 | u4_t tmp1 = (pin & 0x3) << 2; 73 | u4_t tmp2 = 0x0F << tmp1; 74 | SYSCFG->EXTICR[pin >> 2] = (SYSCFG->EXTICR[pin >> 2] & ~tmp2) | (port << tmp1); 75 | 76 | RCC->APB2ENR &= ~RCC_APB2ENR_SYSCFGEN; // disable system configuration controller 77 | 78 | // configure trigger and enable irq 79 | u4_t mask = 1 << pin; 80 | EXTI->RTSR &= ~mask; // clear trigger 81 | EXTI->FTSR &= ~mask; // clear trigger 82 | if( rising ) { 83 | EXTI->RTSR |= mask; 84 | } 85 | if( falling ) { 86 | EXTI->FTSR |= mask; 87 | } 88 | 89 | // configure the NVIC 90 | #if defined(STM32L0) 91 | u1_t channel = (pin < 2) ? EXTI0_1_IRQn : (pin < 4) ? EXTI2_3_IRQn : EXTI4_15_IRQn; 92 | #elif defined(STM32L1) 93 | u1_t channel = (pin < 5) ? (EXTI0_IRQn + pin) : ((pin < 10) ? EXTI9_5_IRQn : EXTI15_10_IRQn); 94 | #endif 95 | NVIC->IP[channel] = 0x70; // interrupt priority 96 | NVIC->ISER[channel>>5] = 1 << (channel & 0x1F); // set enable IRQ 97 | } 98 | 99 | void gpio_cfg_extirq (int port, int pin, int irqcfg) { 100 | gpio_cfg_extirq_ex(port, pin, 101 | (irqcfg == GPIO_IRQ_CHANGE || irqcfg == GPIO_IRQ_RISING), 102 | (irqcfg == GPIO_IRQ_CHANGE || irqcfg == GPIO_IRQ_FALLING)); 103 | } 104 | 105 | void gpio_set_extirq (int pin, int on) { 106 | if (on) { 107 | EXTI->PR = (1 << pin); 108 | EXTI->IMR |= (1 << pin); 109 | } else { 110 | EXTI->IMR &= ~(1 << pin); 111 | } 112 | } 113 | 114 | void pio_set (unsigned int pin, int value) { 115 | if( value >= 0 ) { 116 | gpio_cfg_set_pin(BRD_PORT(pin), BRD_PIN(pin), 117 | GPIOCFG_MODE_OUT | GPIOCFG_OSPEED_40MHz | GPIOCFG_OTYPE_PUPD | GPIOCFG_PUPD_NONE, 118 | value); 119 | } else { 120 | int gpiocfg = 0; 121 | if( value == PIO_INP_PUP ) { 122 | gpiocfg = GPIOCFG_PUPD_PUP; 123 | } else if( value == PIO_INP_PDN ) { 124 | gpiocfg = GPIOCFG_PUPD_PDN; 125 | } else if( value == PIO_INP_PAU ) { 126 | if( pin & BRD_GPIO_ACTIVE_LOW ) { 127 | gpiocfg = GPIOCFG_PUPD_PUP; 128 | } else { 129 | gpiocfg = GPIOCFG_PUPD_PDN; 130 | } 131 | } else if( value == PIO_INP_ANA ) { 132 | gpiocfg = GPIOCFG_MODE_ANA; 133 | } 134 | gpio_cfg_pin(BRD_PORT(pin), BRD_PIN(pin), gpiocfg); 135 | } 136 | } 137 | 138 | void pio_direct_start (unsigned int pin, pio_direct* dpio) { 139 | int port = BRD_PORT(pin); 140 | dpio->gpio = GPIOx(port); 141 | dpio->mask = 1 << BRD_PIN(pin); 142 | dpio->m_out = 0x1 << (BRD_PIN(pin) << 1); 143 | dpio->m_inp = ~(0x3 << (BRD_PIN(pin) << 1)); 144 | dpio->port = port; 145 | gpio_begin(port); 146 | } 147 | 148 | void pio_direct_stop (pio_direct* dpio) { 149 | gpio_end(dpio->port); 150 | } 151 | 152 | void pio_direct_inp (pio_direct* dpio) { 153 | uint32_t r = dpio->gpio->MODER; 154 | r &= dpio->m_inp; // clear bits 155 | dpio->gpio->MODER = r; 156 | } 157 | 158 | void pio_direct_out (pio_direct* dpio) { 159 | uint32_t r = dpio->gpio->MODER; 160 | r &= dpio->m_inp; // clear bits 161 | r |= dpio->m_out; // set bits 162 | dpio->gpio->MODER = r; 163 | } 164 | 165 | void pio_direct_set (pio_direct* dpio, int value) { 166 | if( value ) { 167 | pio_direct_set1(dpio); 168 | } else { 169 | pio_direct_set0(dpio); 170 | } 171 | } 172 | 173 | void pio_direct_set1 (pio_direct* dpio) { 174 | dpio->gpio->BSRR = dpio->mask; 175 | } 176 | 177 | void pio_direct_set0 (pio_direct* dpio) { 178 | dpio->gpio->BRR = dpio->mask; 179 | } 180 | 181 | unsigned int pio_direct_get (pio_direct* dpio) { 182 | return dpio->gpio->IDR & dpio->mask; 183 | } 184 | 185 | void pio_activate (unsigned int pin, bool active) { 186 | pio_set(pin, (pin & BRD_GPIO_ACTIVE_LOW) ? !active : active); 187 | } 188 | 189 | int pio_get (unsigned int pin) { 190 | return gpio_get_pin(BRD_PORT(pin), BRD_PIN(pin)); 191 | } 192 | 193 | bool pio_active (unsigned int pin) { 194 | bool v = pio_get(pin); 195 | if( (pin & BRD_GPIO_ACTIVE_LOW) ) { 196 | v = !v; 197 | } 198 | return v; 199 | } 200 | 201 | void pio_default (unsigned int pin) { 202 | pio_set(pin, PIO_INP_ANA); 203 | } 204 | -------------------------------------------------------------------------------- /stm32/hal_stm32.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2020 Michael Kuyper. All rights reserved. 2 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 3 | // 4 | // This file is subject to the terms and conditions defined in file 'LICENSE', 5 | // which is part of this source code package. 6 | 7 | #ifndef _hal_stm32_h_ 8 | #define _hal_stm32_h_ 9 | 10 | #include "hw.h" 11 | #include "boottab.h" 12 | 13 | // Get current PC 14 | __attribute__((always_inline)) static inline uint32_t hal_getpc (void) { 15 | uint32_t addr; 16 | __asm__ volatile ("mov %[addr], pc" : [addr]"=r" (addr) : : ); 17 | return addr; 18 | } 19 | 20 | // Macro to place code in RAM 21 | #define __fastcode __attribute__((noinline,section(".fastcode"))) 22 | 23 | // Sleep modes 24 | enum { 25 | HAL_SLEEP_S0, // sleep, full speed clock 26 | HAL_SLEEP_S1, // sleep, reduced speed clock 27 | HAL_SLEEP_S2, // stop mode 28 | 29 | HAL_SLEEP_CNT // number of sleep states 30 | }; 31 | 32 | void hal_setMaxSleep (unsigned int level); 33 | void hal_clearMaxSleep (unsigned int level); 34 | 35 | #ifdef CFG_rtstats 36 | typedef struct { 37 | uint32_t run_ticks; 38 | uint32_t sleep_ticks[HAL_SLEEP_CNT]; 39 | } hal_rtstats; 40 | 41 | void hal_rtstats_collect (hal_rtstats* stats); 42 | #endif 43 | 44 | 45 | // NVIC interrupt definition 46 | typedef struct { 47 | uint32_t num; // NVIC interrupt number 48 | void* handler; // Pointer to handler function 49 | } irqdef; 50 | 51 | extern const irqdef HAL_irqdefs[]; 52 | 53 | // Firmware header -- do not modify (append only) 54 | typedef struct { 55 | boot_fwhdr boot; 56 | 57 | uint32_t version; 58 | } hal_fwhdr; 59 | 60 | // Personalization data 61 | #define HAL_PERSODATA_BASE PERSODATA_BASE 62 | 63 | void i2c_irq (void); 64 | 65 | #if defined(SVC_fuota) 66 | // Glue for FUOTA (fountain code) service 67 | 68 | #include "peripherals.h" 69 | 70 | #define fuota_flash_pagesz FLASH_PAGE_SZ 71 | #define fuota_flash_bitdefault 0 72 | 73 | #define fuota_flash_write(dst,src,nwords,erase) \ 74 | flash_write((uint32_t*) (dst), (uint32_t*) (src), nwords, erase) 75 | 76 | #define fuota_flash_read(dst,src,nwords) \ 77 | memcpy(dst, src, (nwords) << 2) 78 | 79 | #define fuota_flash_rd_u4(addr) \ 80 | (*((uint32_t*) (addr))) 81 | 82 | #define fuota_flash_rd_ptr(addr) \ 83 | (*((void**) (addr))) 84 | 85 | #endif 86 | 87 | #endif 88 | -------------------------------------------------------------------------------- /stm32/i2c.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | // 3 | // This file is subject to the terms and conditions defined in file 'LICENSE', 4 | // which is part of this source code package. 5 | 6 | #include "peripherals.h" 7 | 8 | #ifdef BRD_I2C 9 | 10 | #if BRD_I2C == 1 11 | #define I2Cx I2C1 12 | #define I2Cx_enable() do { RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; } while (0) 13 | #define I2Cx_disable() do { RCC->APB1ENR &= ~RCC_APB1ENR_I2C1EN; } while (0) 14 | #define I2Cx_IRQn I2C1_IRQn 15 | #else 16 | #error "Unsupported I2C peripheral" 17 | #endif 18 | 19 | static struct { 20 | unsigned int wlen; 21 | unsigned char* wptr; 22 | unsigned int rlen; 23 | unsigned char* rptr; 24 | osjob_t* job; 25 | osjobcb_t cb; 26 | int* pstatus; 27 | } xfr; 28 | 29 | static struct { 30 | int status; 31 | osjob_t job; 32 | i2c_cb cb; 33 | } xfr2; 34 | 35 | static void i2c_stop (int status) { 36 | // generate stop condition 37 | I2Cx->CR2 |= I2C_CR2_STOP; 38 | // disable interrupts in NVIC 39 | NVIC_DisableIRQ(I2Cx_IRQn); 40 | // disable interrupts/peripheral 41 | I2Cx->CR1 = 0; 42 | // reconfigure GPIOs 43 | CFG_PIN_DEFAULT(GPIO_I2C_SCL); 44 | CFG_PIN_DEFAULT(GPIO_I2C_SDA); 45 | // disable peripheral clock 46 | I2Cx_disable(); 47 | // schedule callback 48 | *(xfr.pstatus) = status; 49 | if (xfr.job != NULL) { 50 | os_setCallback(xfr.job, xfr.cb); 51 | } else { 52 | xfr.cb(NULL); 53 | } 54 | // re-enable sleep 55 | hal_clearMaxSleep(HAL_SLEEP_S0); 56 | } 57 | 58 | static void i2c_start (int addr) { 59 | // enable peripheral clock 60 | I2Cx_enable(); 61 | // set timing 62 | I2Cx->TIMINGR = 0x40101A22; // from CubeMX tool; t_rise=t_fall=50ns, 100kHz 63 | // start I2C 64 | I2Cx->CR1 |= I2C_CR1_PE; 65 | // setup slave address 66 | I2Cx->CR2 = (I2Cx->CR2 & ~I2C_CR2_SADD) | (addr & I2C_CR2_SADD); 67 | // setup GPIOs 68 | CFG_PIN_AF(GPIO_I2C_SCL, GPIOCFG_OSPEED_40MHz | GPIOCFG_OTYPE_OPEN | GPIOCFG_PUPD_NONE); 69 | CFG_PIN_AF(GPIO_I2C_SDA, GPIOCFG_OSPEED_40MHz | GPIOCFG_OTYPE_OPEN | GPIOCFG_PUPD_NONE); 70 | // disable sleep (keep clock at full speed during transfer 71 | hal_setMaxSleep(HAL_SLEEP_S0); 72 | // enable interrupts in NVIC 73 | NVIC_EnableIRQ(I2Cx_IRQn); 74 | } 75 | 76 | static void i2c_cont (void) { 77 | if (xfr.wlen) { 78 | // calculate length; TODO: handle >255 79 | int n = xfr.wlen & 0xff; 80 | xfr.wlen -= n; 81 | // set direction & number of bytes 82 | I2Cx->CR2 = (I2Cx->CR2 & ~(I2C_CR2_RD_WRN | I2C_CR2_NBYTES)) | (n << 16); 83 | // enable interrupts 84 | I2Cx->CR1 = (I2Cx->CR1 & ~0xfe) | I2C_CR1_TXIE | I2C_CR1_TCIE | I2C_CR1_NACKIE | I2C_CR1_ERRIE; 85 | // start TX 86 | I2Cx->CR2 |= I2C_CR2_START; 87 | } else if (xfr.rlen) { 88 | // calculate length; TODO: handle >255 89 | int n = xfr.rlen & 0xff; 90 | xfr.rlen -= n; 91 | // set direction & number of bytes 92 | I2Cx->CR2 = (I2Cx->CR2 & ~(I2C_CR2_RD_WRN | I2C_CR2_NBYTES)) | I2C_CR2_RD_WRN | (n << 16); 93 | // enable interrupts 94 | I2Cx->CR1 = (I2Cx->CR1 & ~0xfe) | I2C_CR1_RXIE | I2C_CR1_TCIE | I2C_CR1_NACKIE | I2C_CR1_ERRIE; 95 | // start RX 96 | I2Cx->CR2 |= I2C_CR2_START; 97 | } else { 98 | // done 99 | i2c_stop(I2C_OK); 100 | } 101 | } 102 | 103 | void i2c_irq (void) { 104 | unsigned int isr = I2Cx->ISR; 105 | if (isr & I2C_ISR_NACKF) { 106 | // NACK detected, transfer failed! 107 | i2c_stop(I2C_NAK); 108 | } else if (isr & I2C_ISR_TC) { 109 | // transfer complete, move on 110 | i2c_cont(); 111 | } else if (isr & I2C_ISR_TXIS) { 112 | // write next byte 113 | I2Cx->TXDR = *xfr.wptr++; 114 | } else if (isr & I2C_ISR_RXNE) { 115 | // next byte received 116 | *xfr.rptr++ = I2Cx->RXDR; 117 | } else { 118 | hal_failed(); // XXX 119 | } 120 | } 121 | 122 | static void i2c_timeout (osjob_t* job) { 123 | i2c_abort(); 124 | } 125 | 126 | void i2c_xfer_ex (unsigned int addr, unsigned char* buf, unsigned int wlen, unsigned int rlen, ostime_t timeout, 127 | osjob_t* job, osjobcb_t cb, int* pstatus) { 128 | // setup xfr structure 129 | xfr.wlen = wlen; 130 | xfr.rlen = rlen; 131 | xfr.wptr = xfr.rptr = buf; 132 | xfr.job = job; 133 | xfr.cb = cb; 134 | xfr.pstatus = pstatus; 135 | *xfr.pstatus = I2C_BUSY; 136 | // set timeout 137 | if (timeout) { 138 | os_setTimedCallback(job, os_getTime() + timeout, i2c_timeout); 139 | } 140 | // prepare peripheral 141 | i2c_start(addr); 142 | // start actual transfer 143 | i2c_cont(); 144 | } 145 | 146 | static void i2cfunc (osjob_t* j) { 147 | xfr2.cb(xfr2.status); 148 | } 149 | 150 | void i2c_xfer (unsigned int addr, unsigned char* buf, unsigned int wlen, unsigned int rlen, i2c_cb cb, ostime_t timeout) { 151 | xfr2.cb = cb; 152 | i2c_xfer_ex(addr, buf, wlen, rlen, timeout, &xfr2.job, i2cfunc, &xfr2.status); 153 | } 154 | 155 | void i2c_abort (void) { 156 | hal_disableIRQs(); 157 | i2c_stop(I2C_ABORT); 158 | hal_enableIRQs(); 159 | } 160 | 161 | #endif 162 | -------------------------------------------------------------------------------- /stm32/leds.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | // 3 | // This file is subject to the terms and conditions defined in file 'LICENSE', 4 | // which is part of this source code package. 5 | 6 | #include "hw.h" 7 | 8 | #if defined(BRD_LED_TIM) 9 | 10 | #if BRD_LED_TIM == 2 11 | #define TIMx TIM2 12 | #define TIMx_enable() do { RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; } while (0) 13 | #define TIMx_disable() do { RCC->APB1ENR &= ~RCC_APB1ENR_TIM2EN; } while (0) 14 | #define TIMx_IRQn TIM2_IRQn 15 | #else 16 | #error "Unsupported timer" 17 | #endif 18 | 19 | static struct { 20 | unsigned int state; 21 | struct { 22 | int step; 23 | unsigned int n; 24 | unsigned int delay; 25 | unsigned int min; 26 | unsigned int max; 27 | } pulse[4]; 28 | } pwm; 29 | 30 | #endif 31 | 32 | void leds_init (void) { 33 | #if defined(BRD_LED_TIM) 34 | TIMx_enable(); 35 | TIMx->PSC = 4; 36 | TIMx->ARR = 0xffff; 37 | TIMx->CCMR1 = 38 | TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1PE | 39 | TIM_CCMR1_OC2M_2 | TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2PE; 40 | TIMx->CCMR2 = 41 | TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3PE | 42 | TIM_CCMR2_OC4M_2 | TIM_CCMR2_OC4M_1 | TIM_CCMR2_OC4PE; 43 | TIMx_disable(); 44 | #endif 45 | } 46 | 47 | #if defined(BRD_LED_TIM) 48 | static void pwm_set_gpio (unsigned int gpio, bool enable, bool pulse, unsigned int ccr) { 49 | unsigned int ch = BRD_GPIO_GET_CHAN(gpio) - 1; 50 | ASSERT(ch < 4); 51 | 52 | unsigned int state0 = pwm.state; 53 | unsigned int state1 = state0; 54 | 55 | if (enable) { 56 | state1 |= (0x01 << ch); 57 | if (pulse) { 58 | state1 |= (0x10 << ch); 59 | } 60 | } else { 61 | state1 &= ~(0x11 << ch); 62 | } 63 | 64 | if (state0 == state1) { 65 | return; 66 | } 67 | 68 | hal_disableIRQs(); 69 | 70 | if (state1) { 71 | if (state0 == 0) { 72 | TIMx_enable(); // enable peripheral clock 73 | hal_setMaxSleep(HAL_SLEEP_S0); // disable sleep (keep clock at full speed) 74 | TIMx->CR1 |= TIM_CR1_CEN; // enable timer peripheral 75 | TIMx->EGR |= TIM_EGR_UG; // start pwm 76 | } 77 | if (state1 & 0xf0) { 78 | if ((state0 & 0xf0) == 0) { 79 | TIMx->DIER |= TIM_DIER_UIE; // enable update interrupt 80 | NVIC_EnableIRQ(TIMx_IRQn); // enable interrupt in NVIC 81 | } 82 | } else { 83 | if ((state0 & 0xf0) == 0) { 84 | TIMx->DIER &= ~TIM_DIER_UIE; // disable update interrupt 85 | NVIC_DisableIRQ(TIMx_IRQn); // disable interrupt in NVIC 86 | } 87 | } 88 | } else if (state0) { 89 | TIMx->CR1 &= ~TIM_CR1_CEN; // disable timer 90 | TIMx->DIER &= ~TIM_DIER_UIE; // disable update interrupt 91 | TIMx_disable(); // disable peripheral clock 92 | hal_clearMaxSleep(HAL_SLEEP_S0); // re-enable sleep 93 | NVIC_DisableIRQ(TIMx_IRQn); // disable interrupt in NVIC 94 | } 95 | 96 | if (enable) { 97 | *((&(TIMx->CCR1)) + ch) = ccr; // set initial CCR value 98 | if ((state0 & (1 << ch)) == 0) { 99 | unsigned int ccer = TIM_CCER_CC1E; 100 | if (gpio & BRD_GPIO_ACTIVE_LOW) { 101 | ccer |= TIM_CCER_CC1P; 102 | } 103 | TIMx->CCER |= (ccer << (4 * ch)); // enable channel 104 | CFG_PIN_AF(gpio, GPIOCFG_OSPEED_40MHz | GPIOCFG_OTYPE_PUPD | GPIOCFG_PUPD_NONE); 105 | } 106 | } 107 | pwm.state = state1; 108 | 109 | hal_enableIRQs(); 110 | } 111 | #endif 112 | 113 | void leds_pwm (unsigned int gpio, int dc) { 114 | #if defined(BRD_LED_TIM) 115 | pwm_set_gpio(gpio, true, false, dc); 116 | #endif 117 | } 118 | 119 | void leds_pulse (unsigned int gpio, unsigned int min, unsigned int max, int step, unsigned int delay) { 120 | #if defined(BRD_LED_TIM) 121 | unsigned int ch = BRD_GPIO_GET_CHAN(gpio) - 1; 122 | ASSERT(ch < 4); 123 | pwm.pulse[ch].n = 0; 124 | pwm.pulse[ch].min = min; 125 | pwm.pulse[ch].max = max; 126 | pwm.pulse[ch].step = step; 127 | pwm.pulse[ch].delay = delay; 128 | pwm_set_gpio(gpio, true, true, (step < 0) ? max : min); 129 | #endif 130 | } 131 | 132 | void leds_set (unsigned int gpio, int state) { 133 | if (state) { 134 | if (gpio & BRD_GPIO_ACTIVE_LOW) { 135 | SET_PIN(gpio, 0); 136 | } else { 137 | SET_PIN(gpio, 1); 138 | } 139 | CFG_PIN(gpio, GPIOCFG_MODE_OUT | GPIOCFG_OSPEED_400kHz | GPIOCFG_OTYPE_PUPD | GPIOCFG_PUPD_NONE); 140 | } else { 141 | CFG_PIN(gpio, GPIOCFG_MODE_ANA | GPIOCFG_OSPEED_400kHz | GPIOCFG_OTYPE_OPEN | GPIOCFG_PUPD_NONE); 142 | } 143 | #if defined(BRD_LED_TIM) 144 | pwm_set_gpio(gpio, false, false, 0); 145 | #endif 146 | } 147 | 148 | #if defined(BRD_LED_TIM) 149 | void leds_pwm_irq (void) { 150 | if (TIMx->SR & TIM_SR_UIF) { // update event 151 | TIMx->SR = ~TIM_SR_UIF; // clear flag 152 | unsigned int ps = pwm.state & 0x0f; 153 | while (ps) { 154 | unsigned int ch = __builtin_ctz(ps); 155 | if (pwm.pulse[ch].step) { 156 | if (pwm.pulse[ch].n < pwm.pulse[ch].delay) { 157 | pwm.pulse[ch].n += 1; 158 | } else { 159 | pwm.pulse[ch].n = 0; 160 | int ccr = *((&(TIMx->CCR1)) + ch); 161 | ccr += pwm.pulse[ch].step; 162 | if (ccr <= pwm.pulse[ch].min) { 163 | ccr = pwm.pulse[ch].min; 164 | pwm.pulse[ch].step = -pwm.pulse[ch].step; 165 | } else if (ccr >= pwm.pulse[ch].max) { 166 | ccr = pwm.pulse[ch].max; 167 | pwm.pulse[ch].step = -pwm.pulse[ch].step; 168 | } 169 | *((&(TIMx->CCR1)) + ch) = ccr; 170 | } 171 | } 172 | ps &= ~(1 << ch); 173 | } 174 | } 175 | } 176 | #endif 177 | -------------------------------------------------------------------------------- /stm32/sleep.S: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | // 3 | // This file is subject to the terms and conditions defined in file 'LICENSE', 4 | // which is part of this source code package. 5 | 6 | //#include "bootloader.h" 7 | 8 | #define LPTIM_ISR 0x00 9 | #define LPTIM_ICR 0x04 10 | 11 | #define LPTIM_ARRM_BIT 1 12 | 13 | #define LPTIM1_IRQn_BIT 13 14 | 15 | #define IWDG_KR 0 16 | 17 | #define MASK(n) (1 << (n ## _BIT)) // bit mask 18 | 19 | 20 | // -------------------------------------------- 21 | // assembler settings 22 | .syntax unified 23 | .thumb 24 | 25 | 26 | // -------------------------------------------- 27 | // u4_t sleep_htt (u4_t hticks, u4_t htt); 28 | // 29 | //.section .fastcode.tim22_sync,"ax",%progbits 30 | .section .fastcode.sleep_htt,"ax",%progbits 31 | .thumb_func 32 | sleep_htt: 33 | #if CFG_watchdog 34 | #define REGLIST r4-r7 35 | #else 36 | #define REGLIST r4-r6 37 | #endif 38 | push {REGLIST,lr} 39 | 40 | // r0 - hticks 41 | // r1 - htt 42 | // r2 - LPTIM1 base 43 | ldr r2, .L_LPTIM1_BASE 44 | // r3 - ARRM mask 45 | movs r3, #MASK(LPTIM_ARRM) 46 | // r4 - NVIC ICPR 47 | ldr r4, .L_NVIC_ICPR 48 | // r5 - LPTIM1_IRQn mask 49 | movs r5, #1 50 | lsls r5, #LPTIM1_IRQn_BIT 51 | // r6 - scratch 52 | #if CFG_watchdog 53 | // r7 - IWDG base 54 | ldr r7, .L_IWDG_BASE 55 | #endif 56 | 57 | b 3f 58 | 59 | // zzz 60 | 1: wfi 61 | 62 | // read and check ISR 63 | ldr r6, [r2, #LPTIM_ISR] 64 | tst r6, r3 65 | beq 4f 66 | 67 | // clear ISR 68 | str r3, [r2, #LPTIM_ICR] 69 | 2: ldr r6, [r2, #LPTIM_ISR] 70 | tst r6, r3 71 | bne 2b 72 | 73 | // clear NVIC 74 | str r5, [r4, #0] 75 | 76 | // increment hticks 77 | adds r0, #1 78 | 79 | #if CFG_watchdog 80 | // refresh watchdog 81 | ldr r6, .L_IWDG_REFRESH 82 | str r6, [r7, #IWDG_KR] 83 | #endif 84 | 85 | // compare hticks to htt 86 | 3: cmp r0, r1 87 | bmi 1b 88 | 89 | 4: pop {REGLIST,pc} 90 | 91 | .p2align(2) 92 | .L_LPTIM1_BASE: 93 | .word 0x40007c00 94 | .L_NVIC_ICPR: 95 | .word 0xE000E100+0x180 96 | #if CFG_watchdog 97 | .L_IWDG_BASE: 98 | .word 0x40003000 99 | .L_IWDG_REFRESH: 100 | .word 0xAAAA 101 | #endif 102 | #undef REGLIST 103 | 104 | .size sleep_htt, .-sleep_htt 105 | .global sleep_htt 106 | -------------------------------------------------------------------------------- /stm32/startup.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | // 3 | // This file is subject to the terms and conditions defined in file 'LICENSE', 4 | // which is part of this source code package. 5 | 6 | #include "lmic.h" 7 | #include "hw.h" 8 | 9 | #if defined(STM32L0) 10 | #define MAX_IRQn 32 /* see PM0223, 2.3.4, pg. 29 */ 11 | #elif defined(STM32L1) 12 | #define MAX_IRQn 68 /* see PM0056, 2.3.4, pg. 36 */ 13 | #else 14 | #error "Unsupported MCU" 15 | #endif 16 | 17 | __attribute__((aligned(512))) 18 | static uint32_t irqvector[16 + MAX_IRQn]; 19 | 20 | void _start (boot_boottab* boottab) { 21 | // symbols provided by linker script 22 | extern uint32_t _sidata, _sdata, _edata, _sbss, _ebss; 23 | 24 | // initialize data 25 | uint32_t* src = &_sidata; 26 | uint32_t* dst = &_sdata; 27 | while( dst < &_edata ) { 28 | *dst++ = *src++; 29 | } 30 | 31 | // initialize bss 32 | dst = &_sbss; 33 | while( dst < &_ebss ) { 34 | *dst++ = 0; 35 | } 36 | 37 | // copy current Cortex M IRQ + NVIC vector to RAM 38 | src = (uint32_t*) 0; 39 | dst = irqvector; 40 | for( int i = 0; i < (16 + MAX_IRQn); i++ ) { 41 | *dst++ = *src++; 42 | } 43 | // fix-up NVIC vector with handlers from firmware 44 | for( const irqdef* id = HAL_irqdefs; id->handler; id++ ) { 45 | irqvector[16 + id->num] = (uint32_t) id->handler; 46 | } 47 | // re-map interrupt vector 48 | SCB->VTOR = (uint32_t) irqvector; 49 | 50 | // call main function 51 | extern void main (boot_boottab* boottab); 52 | main(boottab); 53 | } 54 | 55 | // Firmware header 56 | __attribute__((section(".fwhdr"))) 57 | const volatile hal_fwhdr fwhdr = { 58 | // CRC and size will be patched by external tool 59 | .boot.crc = 0, 60 | .boot.size = BOOT_MAGIC_SIZE, 61 | .boot.entrypoint = (uint32_t) _start, 62 | 63 | .version = 0, 64 | }; 65 | -------------------------------------------------------------------------------- /stm32/timer.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2020 Michael Kuyper. All rights reserved. 2 | // 3 | // This file is subject to the terms and conditions defined in file 'LICENSE', 4 | // which is part of this source code package. 5 | 6 | #include "peripherals.h" 7 | 8 | #ifdef BRD_TMR 9 | 10 | enum { 11 | F_ON = 1 << 0, 12 | }; 13 | 14 | typedef struct { 15 | uint32_t flags; // flags 16 | tmr_cb cb; // callback 17 | } tmr_state; 18 | 19 | typedef struct { 20 | TIM_TypeDef* timer; // port 21 | volatile uint32_t* enr; // peripheral clock enable register 22 | uint32_t enb; // peripheral clock enable bit 23 | uint32_t irqn; // IRQ number 24 | tmr_state* state; // pointer to state (in RAM) 25 | } tmr_p; 26 | 27 | static void tmr_irq (const tmr_p* tmr); 28 | 29 | #if BRD_TMR_EN(BRD_TIM2) 30 | static tmr_state state_t2; 31 | static const tmr_p p_t2 = { 32 | .timer = TIM2, 33 | .enr = &RCC->APB1ENR, 34 | .enb = RCC_APB1ENR_TIM2EN, 35 | .irqn = TIM2_IRQn, 36 | .state = &state_t2 37 | }; 38 | const void* const tmr_t2 = &p_t2; 39 | void tmr_t2_irq (void) { 40 | tmr_irq(tmr_t2); 41 | } 42 | #endif 43 | #if BRD_TMR_EN(BRD_TIM3) 44 | static tmr_state state_t3; 45 | static const tmr_p p_t3 = { 46 | .timer = TIM3, 47 | .enr = &RCC->APB1ENR, 48 | .enb = RCC_APB1ENR_TIM3EN, 49 | .irqn = TIM3_IRQn, 50 | .state = &state_t3 51 | }; 52 | const void* const tmr_t3 = &p_t3; 53 | void tmr_t3_irq (void) { 54 | tmr_irq(tmr_t3); 55 | } 56 | #endif 57 | 58 | void tmr_start (const void* p, uint32_t psc) { 59 | const tmr_p* tmr = p; 60 | 61 | if( (tmr->state->flags & F_ON) == 0 ) { 62 | tmr->state->flags |= F_ON; 63 | hal_setMaxSleep(HAL_SLEEP_S0); 64 | *tmr->enr |= tmr->enb; // enable peripheral clock 65 | } 66 | tmr->timer->PSC = psc; // set prescaler 67 | } 68 | 69 | void tmr_stop (const void* p) { 70 | const tmr_p* tmr = p; 71 | 72 | if( (tmr->state->flags & F_ON) != 0 ) { 73 | tmr->state->flags &= ~F_ON; 74 | tmr->timer->CR1 = 0; // halt timer 75 | tmr->timer->DIER = 0; // disable all interrupts 76 | NVIC_DisableIRQ(tmr->irqn); // disable interrupt in NVIC 77 | *tmr->enr &= ~tmr->enb; // stop peripheral clock 78 | hal_clearMaxSleep(HAL_SLEEP_S0); 79 | } 80 | } 81 | 82 | void tmr_run (const void* p, uint32_t count, tmr_cb cb, bool once) { 83 | const tmr_p* tmr = p; 84 | 85 | ASSERT((tmr->state->flags & F_ON) != 0); 86 | 87 | tmr->state->cb = cb; 88 | 89 | tmr->timer->CNT = 0; // reset counter 90 | tmr->timer->ARR = count; // set auto-reload register 91 | tmr->timer->EGR = TIM_EGR_UG; // refresh registers 92 | 93 | tmr->timer->SR = 0; // clear interrupt flags 94 | tmr->timer->DIER = cb ? TIM_DIER_UIE : 0; // enable update interrupt 95 | NVIC_EnableIRQ(tmr->irqn); // enable interrupt in NVIC 96 | 97 | tmr->timer->CR1 = TIM_CR1_CEN 98 | | (once ? TIM_CR1_OPM : 0 ); // enable timer 99 | } 100 | 101 | void tmr_halt (const void* p) { 102 | const tmr_p* tmr = p; 103 | 104 | ASSERT((tmr->state->flags & F_ON) != 0); 105 | 106 | tmr->timer->CR1 = 0; // halt timer 107 | } 108 | 109 | uint32_t tmr_get (const void* p) { 110 | const tmr_p* tmr = p; 111 | return tmr->timer->CNT; 112 | } 113 | 114 | static void tmr_irq (const tmr_p* tmr) { 115 | if( tmr->state->cb ) { 116 | tmr->state->cb(); 117 | } 118 | } 119 | 120 | #endif 121 | -------------------------------------------------------------------------------- /stm32/trng.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | // 3 | // This file is subject to the terms and conditions defined in file 'LICENSE', 4 | // which is part of this source code package. 5 | 6 | #include "peripherals.h" 7 | 8 | #if defined(STM32L072xx) 9 | 10 | void trng_next (uint32_t* dest, int count) { 11 | RCC->AHBENR |= RCC_AHBENR_RNGEN; 12 | RNG->CR |= RNG_CR_RNGEN; 13 | while( count-- > 0 ) { 14 | while( (RNG->SR & RNG_SR_DRDY) == 0 ); 15 | *dest++ = RNG->DR; 16 | } 17 | RNG->CR &= ~RNG_CR_RNGEN; 18 | RCC->AHBENR &= ~RCC_AHBENR_RNGEN; 19 | } 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /tools/arch.gmk: -------------------------------------------------------------------------------- 1 | ARCH := $(shell gcc -dumpmachine) 2 | 3 | ifeq (,$(ARCH)) 4 | $(error Could not determine build platform architecture) 5 | endif 6 | 7 | 8 | # vim: filetype=make 9 | -------------------------------------------------------------------------------- /tools/openocd/flash.cfg: -------------------------------------------------------------------------------- 1 | proc flash_binary { filename } { 2 | init 3 | reset init 4 | halt 5 | flash write_image erase unlock $filename 0x08000000 6 | sleep 100 7 | reset run 8 | shutdown 9 | } 10 | 11 | proc flash_fw { firmware } { 12 | init 13 | reset init 14 | halt 15 | flash write_image erase unlock $firmware 0x08003000 16 | sleep 100 17 | reset run 18 | shutdown 19 | } 20 | 21 | proc flash_up { update } { 22 | set size [file size $update] 23 | set addr [expr [expr 0x08020000 - $size] & 0xFFFFFF80] 24 | init 25 | reset init 26 | halt 27 | flash write_image erase unlock $update $addr 28 | puts "update start address: 0x[format %08X $addr]" 29 | puts "gdb: call set_update(0x[format %08X $addr])" 30 | sleep 100 31 | reset run 32 | shutdown 33 | } 34 | 35 | proc flash_ihex { ihex } { 36 | init 37 | reset init 38 | halt 39 | flash write_image erase unlock $ihex 40 | sleep 100 41 | reset run 42 | shutdown 43 | } 44 | 45 | proc flash_info { } { 46 | init 47 | reset init 48 | halt 49 | flash info 0 50 | shutdown 51 | } 52 | 53 | proc restart_target { } { 54 | init 55 | reset init 56 | halt 57 | sleep 10 58 | reset run 59 | shutdown 60 | } 61 | 62 | proc memdump { addr len } { 63 | init 64 | reset init 65 | halt 66 | mdb $addr $len 67 | #mem2array m 8 $addr $len 68 | shutdown 69 | } 70 | 71 | proc option_bytes { } { 72 | init 73 | reset init 74 | halt 75 | read_option_bytes 0 76 | shutdown 77 | } 78 | 79 | proc flash_lock { } { 80 | init 81 | reset init 82 | halt 83 | stm32lx lock 0 84 | shutdown 85 | } 86 | 87 | proc flash_unlock { } { 88 | init 89 | reset init 90 | halt 91 | stm32lx unlock 0 92 | shutdown 93 | } 94 | -------------------------------------------------------------------------------- /tools/openocd/nucleo-l0.cfg: -------------------------------------------------------------------------------- 1 | source [find interface/stlink-v2-1.cfg] 2 | transport select hla_swd 3 | source [find target/stm32l0.cfg] 4 | reset_config srst_only srst_nogate connect_assert_srst 5 | gdb_memory_map disable 6 | -------------------------------------------------------------------------------- /tools/openocd/stlink-rules.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkuyper/basicmac/ad231427c4f7bc19cb88821fddeb83ba130fb065/tools/openocd/stlink-rules.tgz -------------------------------------------------------------------------------- /tools/svctool/cc.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | # 3 | # This file is subject to the terms and conditions defined in file 'LICENSE', 4 | # which is part of this source code package. 5 | 6 | import argparse 7 | 8 | from typing import Any,Callable,List,Optional,Union 9 | from argparse import Namespace as NS, ArgumentParser as AP # type aliases 10 | 11 | class CommandCollection: 12 | parser = argparse.ArgumentParser() 13 | subs = parser.add_subparsers(dest='command') 14 | subs.required = True # type: ignore 15 | sub = None 16 | 17 | @staticmethod 18 | def cmd(name:Optional[str]=None, **kwargs:Any) -> Callable: 19 | def cmd_decorator(f:Callable) -> Callable: 20 | p = CommandCollection.subs.add_parser(name if name else f.__name__, **kwargs) 21 | def cmd_do(obj:Any, args:NS) -> None: 22 | CommandCollection.sub = p 23 | f(obj, args) 24 | p.set_defaults(func=cmd_do) 25 | cmd_do.parser = p # type: ignore 26 | return cmd_do 27 | return cmd_decorator 28 | 29 | @staticmethod 30 | def arg(*args:Any, **kwargs:Any) -> Callable: 31 | def arg_decorator(f:Callable) -> Callable: 32 | p:AP = f.parser # type: ignore 33 | p.add_argument(*args, **kwargs) 34 | return f 35 | return arg_decorator 36 | 37 | @staticmethod 38 | def argf(af:Callable, **kwargs:Any) -> Callable: 39 | def arg_decorator(f:Callable) -> Callable: 40 | p:AP = f.parser # type: ignore 41 | af(p, **kwargs) 42 | return f 43 | return arg_decorator 44 | 45 | @staticmethod 46 | def run(obj:Any) -> None: 47 | args = CommandCollection.parser.parse_args() 48 | args.func(obj, args) 49 | 50 | @staticmethod 51 | def error(message:str) -> None: 52 | (CommandCollection.sub or CommandCollection.parser).error(message) 53 | -------------------------------------------------------------------------------- /unicorn/board.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2021 Michael Kuyper. All rights reserved. 2 | // 3 | // This file is subject to the terms and conditions defined in file 'LICENSE', 4 | // which is part of this source code package. 5 | 6 | #ifndef _board_h_ 7 | #define _board_h_ 8 | 9 | // GPIO definitions 10 | // 3 2 1 0 11 | // 10987654 32109876 54321098 76543210 12 | // ________ ________ _______f ___ppppp 13 | 14 | #define BRD_PIN(pin) ((pin) & 0x1f) 15 | 16 | #define BRD_GPIO_ACTIVE_LOW (1 << 8) 17 | 18 | 19 | #ifdef BRD_IMPL_INC 20 | #include BRD_IMPL_INC 21 | #endif 22 | 23 | 24 | // Personalization 25 | #ifndef GPIO_PERSO_DET 26 | #define GPIO_PERSO_DET 24 27 | #endif 28 | 29 | #ifndef BRD_PERSO_UART 30 | #define BRD_PERSO_UART USART_FUART1 31 | #endif 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /unicorn/fw.ld: -------------------------------------------------------------------------------- 1 | SECTIONS { 2 | .text : { 3 | . = ALIGN(4); 4 | KEEP(*(.fwhdr)) 5 | . = ALIGN(4); 6 | *(.text) 7 | *(.text*) 8 | . = ALIGN(4); 9 | } >FWFLASH 10 | 11 | .data : { 12 | . = ALIGN(4); 13 | _sdata = .; 14 | *(.data) 15 | *(.data*) 16 | . = ALIGN(4); 17 | _edata = .; 18 | } >RAM AT>FWFLASH 19 | 20 | _sidata = LOADADDR(.data); 21 | 22 | .bss : { 23 | . = ALIGN(4); 24 | _sbss = .; 25 | *(.bss) 26 | *(.bss*) 27 | *(COMMON) 28 | . = ALIGN(4); 29 | _ebss = .; 30 | } >RAM 31 | 32 | .rodata : { 33 | . = ALIGN(4); 34 | *(.rodata) 35 | *(.rodata*) 36 | . = ALIGN(4); 37 | 38 | /* make sure flash image is a multiple of page size */ 39 | FILL(0x00000000) 40 | . = ALIGN(128); 41 | __fw_end__ = .; 42 | } >FWFLASH 43 | 44 | /DISCARD/ : { 45 | *(.ARM) 46 | *(.ARM*) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /unicorn/hal.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2020 Michael Kuyper. All rights reserved. 2 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 3 | // 4 | // This file is subject to the terms and conditions defined in file 'LICENSE', 5 | // which is part of this source code package. 6 | 7 | #include "lmic.h" 8 | #include "peripherals.h" 9 | #include "boottab.h" 10 | 11 | #if defined(SVC_eefs) 12 | #include "eefs/eefs.h" 13 | #endif 14 | 15 | #if defined(SVC_frag) 16 | #include "fuota/frag.h" 17 | #endif 18 | 19 | static struct { 20 | boot_boottab* boottab; 21 | unsigned int irqlevel; 22 | } sim; 23 | 24 | void* HAL_svc; 25 | 26 | static inline void wfi (void) { 27 | ((void (*) (uint32_t)) HAL_svc)(SVC_WFI); 28 | } 29 | 30 | static inline void irq (void) { 31 | ((void (*) (uint32_t)) HAL_svc)(SVC_IRQ); 32 | } 33 | 34 | static inline void reset (void) { 35 | ((void (*) (uint32_t)) HAL_svc)(SVC_RESET); 36 | } 37 | 38 | void hal_init (void* bootarg) { 39 | sim.boottab = bootarg; 40 | ASSERT(sim.boottab->version >= 0x105); // require bootloader v261 41 | 42 | HAL_svc = sim.boottab->svc; 43 | 44 | // peripherals 45 | nvic_init(); 46 | dbg_init(); 47 | timer_init(); 48 | gpio_init(); 49 | fuart_init(); 50 | radio_halinit(); 51 | 52 | // TODO: RNG 53 | 54 | hal_pd_init(); 55 | 56 | #if defined(SVC_frag) 57 | { 58 | extern volatile boot_fwhdr fwhdr; 59 | void* beg[1] = { (void*) (((uintptr_t) &fwhdr + fwhdr.size 60 | + (FLASH_PAGE_SZ - 1)) & ~(FLASH_PAGE_SZ - 1)) }; 61 | void* end[1] = { (void*) FLASH_END }; 62 | _frag_init(1, beg, end); 63 | } 64 | #endif 65 | 66 | #if defined(SVC_eefs) 67 | eefs_init((void*) APPDATA_BASE, APPDATA_SZ); 68 | #endif 69 | } 70 | 71 | void hal_watchcount (int cnt) { 72 | // not implemented 73 | } 74 | 75 | void hal_disableIRQs (void) { 76 | if( sim.irqlevel++ == 0 ) { 77 | asm volatile ("cpsid i" : : : "memory"); 78 | } 79 | } 80 | 81 | void hal_enableIRQs (void) { 82 | ASSERT(sim.irqlevel); 83 | if( --sim.irqlevel == 0 ) { 84 | asm volatile ("cpsie i" : : : "memory"); 85 | irq(); 86 | } 87 | } 88 | 89 | void hal_sleep (u1_t type, u4_t targettime) { 90 | timer_set(timer_extend(targettime)); 91 | wfi(); 92 | } 93 | 94 | u4_t hal_ticks (void) { 95 | return hal_xticks(); 96 | } 97 | 98 | u8_t hal_xticks (void) { 99 | return timer_ticks(); 100 | } 101 | 102 | void hal_waitUntil (u4_t time) { 103 | // be very strict about how long we can busy wait 104 | ASSERT(((s4_t) time - (s4_t) hal_ticks()) < ms2osticks(100)); 105 | while( 1 ) { 106 | u4_t now = hal_ticks(); 107 | if( ((s4_t) (time - now)) <= 0 ) { 108 | return; 109 | } 110 | hal_sleep(0, time); 111 | } 112 | } 113 | 114 | u1_t hal_getBattLevel (void) { 115 | return 0; 116 | } 117 | 118 | void hal_setBattLevel (u1_t level) { 119 | } 120 | 121 | __attribute__((noreturn, naked)) 122 | void hal_failed (void) { 123 | // get return address 124 | uint32_t addr; 125 | __asm__("mov %[addr], lr" : [addr]"=r" (addr) : : ); 126 | // in thumb mode the linked address is the address of the calling instruction plus 4 bytes 127 | addr -= 4; 128 | 129 | #ifdef CFG_backtrace 130 | // log address of assertion 131 | backtrace_addr(__LINE__, addr); 132 | // save trace to EEPROM 133 | backtrace_save(); 134 | #endif 135 | 136 | // call panic function 137 | sim.boottab->panic(0, addr); 138 | // not reached 139 | } 140 | 141 | void hal_ant_switch (u1_t val) { 142 | } 143 | bool hal_pin_tcxo (u1_t val) { 144 | return false; 145 | } 146 | void hal_irqmask_set (int mask) { 147 | } 148 | 149 | #ifdef CFG_powerstats 150 | 151 | void hal_stats_get (hal_statistics* stats) { 152 | } 153 | void hal_stats_consume (hal_statistics* stats) { 154 | } 155 | 156 | #endif 157 | 158 | 159 | void hal_fwinfo (hal_fwi* fwi) { 160 | fwi->blversion = sim.boottab->version; 161 | 162 | extern volatile boot_fwhdr fwhdr; 163 | fwi->version = 0; // XXX no longer in fwhdr 164 | fwi->crc = fwhdr.crc; 165 | fwi->flashsz = 128*1024; 166 | } 167 | 168 | u4_t hal_unique (void) { 169 | return 0xdeadbeef; 170 | } 171 | 172 | 173 | // ------------------------------------------------ 174 | // EEPROM 175 | 176 | void eeprom_write (void* dest, unsigned int val) { 177 | ASSERT(((uintptr_t) dest & 3) == 0 178 | && (uintptr_t) dest >= EEPROM_BASE 179 | && (uintptr_t) dest < EEPROM_END); 180 | *((uint32_t*) dest) = val; 181 | } 182 | 183 | void eeprom_copy (void* dest, const void* src, int len) { 184 | ASSERT(((uintptr_t) src & 3) == 0 && (len & 3) == 0); 185 | uint32_t* p = dest; 186 | const uint32_t* s = src; 187 | len >>= 2; 188 | while( len-- > 0 ) { 189 | eeprom_write(p++, *s++); 190 | } 191 | } 192 | 193 | 194 | // ------------------------------------------------ 195 | // CRC engine (32bit aligned words only) 196 | 197 | unsigned int crc32 (void* ptr, int nwords) { 198 | return sim.boottab->crc32(ptr, nwords); 199 | } 200 | 201 | 202 | // ------------------------------------------------ 203 | // SHA-256 engine 204 | 205 | void sha256 (uint32_t* hash, const uint8_t* msg, uint32_t len) { 206 | sim.boottab->sha256(hash, msg, len); 207 | } 208 | 209 | void hal_reboot (void) { 210 | reset(); 211 | // not reached 212 | hal_failed(); 213 | } 214 | 215 | typedef struct { 216 | uint32_t dnonce; // dev nonce 217 | } pdata; 218 | 219 | u4_t hal_dnonce_next (void) { 220 | pdata* p = (pdata*) STACKDATA_BASE; 221 | return p->dnonce++; 222 | } 223 | 224 | void hal_dnonce_clear (void) { 225 | pdata* p = (pdata*) STACKDATA_BASE; 226 | p->dnonce = 0; 227 | } 228 | 229 | bool hal_set_update (void* ptr) { 230 | return sim.boottab->update(ptr, NULL) == BOOT_OK; 231 | } 232 | 233 | void flash_write (void* dst, const void* src, unsigned int nwords, bool erase) { 234 | sim.boottab->wr_flash(dst, src, nwords, erase); 235 | } 236 | 237 | void hal_logEv (uint8_t evcat, uint8_t evid, uint32_t evparam) { 238 | // TODO - implement? 239 | } 240 | -------------------------------------------------------------------------------- /unicorn/hal_unicorn.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2020 Michael Kuyper. All rights reserved. 2 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 3 | // 4 | // This file is subject to the terms and conditions defined in file 'LICENSE', 5 | // which is part of this source code package. 6 | 7 | #ifndef _hal_unicorn_h_ 8 | #define _hal_unicorn_h_ 9 | 10 | #include "hw.h" 11 | #include "boottab.h" 12 | 13 | extern void* HAL_svc; 14 | 15 | // peripherals 16 | enum { 17 | HAL_PID_NVIC, 18 | HAL_PID_DEBUG, 19 | HAL_PID_TIMER, 20 | HAL_PID_GPIO, 21 | HAL_PID_FUART, 22 | HAL_PID_RADIO, 23 | 24 | HAL_PID_COUNT 25 | }; 26 | 27 | void nvic_init (void); 28 | void nvic_sethandler (uint32_t pid, void* handler); 29 | 30 | void dbg_init (void); 31 | 32 | void timer_init (void); 33 | uint64_t timer_ticks (void); 34 | uint64_t timer_extend (uint32_t ticks); 35 | void timer_set (uint64_t target); 36 | 37 | void radio_halinit (void); 38 | 39 | void gpio_init (void); 40 | 41 | void fuart_init (void); 42 | void fuart_tx (unsigned char* buf, int n); 43 | void fuart_rx_start (void); 44 | void fuart_rx_cb (unsigned char* buf, int n); 45 | void fuart_rx_stop (void); 46 | 47 | 48 | // Personalization data 49 | #define HAL_PERSODATA_BASE PERSODATA_BASE 50 | 51 | #if defined(SVC_fuota) 52 | // Glue for FUOTA (fountain code) service 53 | 54 | #include "peripherals.h" 55 | 56 | #define fuota_flash_pagesz FLASH_PAGE_SZ 57 | #define fuota_flash_bitdefault 0 58 | 59 | #define fuota_flash_write(dst,src,nwords,erase) \ 60 | flash_write((uint32_t*) (dst), (uint32_t*) (src), nwords, erase) 61 | 62 | #define fuota_flash_read(dst,src,nwords) \ 63 | memcpy(dst, src, (nwords) << 2) 64 | 65 | #define fuota_flash_rd_u4(addr) \ 66 | (*((uint32_t*) (addr))) 67 | 68 | #define fuota_flash_rd_ptr(addr) \ 69 | (*((void**) (addr))) 70 | 71 | #endif 72 | 73 | // Firmware header -- do not modify (append only) 74 | typedef struct { 75 | boot_fwhdr boot; 76 | 77 | uint32_t version; 78 | } hal_fwhdr; 79 | 80 | #endif 81 | -------------------------------------------------------------------------------- /unicorn/hw.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2021 Michael Kuyper. All rights reserved. 2 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 3 | // 4 | // This file is subject to the terms and conditions defined in file 'LICENSE', 5 | // which is part of this source code package. 6 | 7 | #ifndef _hw_h_ 8 | #define _hw_h_ 9 | 10 | #include 11 | 12 | #define PERIPH_EEPROM 13 | 14 | #define EEPROM_BASE 0x30000000 15 | #define EEPROM_SZ (8 * 1024) 16 | #define EEPROM_END (EEPROM_BASE + EEPROM_SZ) 17 | 18 | // 0x0000-0x003f 64 B : reserved for bootloader 19 | // 0x0040-0x005f 32 B : reserved for persistent stack data 20 | // 0x0060-0x00ff 160 B : reserved for personalization data 21 | // 0x0100-...... : reserved for application 22 | 23 | #define STACKDATA_BASE (EEPROM_BASE + 0x0040) 24 | #define PERSODATA_BASE (EEPROM_BASE + 0x0060) 25 | #define APPDATA_BASE (EEPROM_BASE + 0x0100) 26 | 27 | #define STACKDATA_SZ (PERSODATA_BASE - STACKDATA_BASE) 28 | #define PERSODATA_SZ (APPDATA_BASE - PERSODATA_BASE) 29 | #define APPDATA_SZ (EEPROM_END - APPDATA_BASE) 30 | 31 | #define PERIPH_FLASH 32 | #define FLASH_BASE 0x20000000 33 | #define FLASH_SZ (128 * 1024) 34 | #define FLASH_END (FLASH_BASE + FLASH_SZ) 35 | #define FLASH_PAGE_SZ 128 36 | #define FLASH_PAGE_NW (FLASH_PAGE_SZ >> 2) 37 | 38 | #define PERIPH_USART 39 | #define USART_FUART1 ((void*) 1) 40 | 41 | typedef struct { 42 | void* reg; 43 | uint32_t mask; 44 | } pio_direct; 45 | #define PERIPH_PIO 46 | #define PERIPH_PIO_DIRECT 47 | #define PIO_IRQ_LINE(gpio) (gpio) 48 | 49 | #define PERIPH_CRC 50 | #define PERIPH_SHA256 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /unicorn/simul/eventhub.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020-2020 Michael Kuyper. All rights reserved. 2 | # 3 | # This file is subject to the terms and conditions defined in file 'LICENSE', 4 | # which is part of this source code package. 5 | 6 | from typing import Any 7 | 8 | class EventHub: 9 | LOG = 0 10 | LORA = 1 11 | 12 | def event(self, type:int, **kwargs:Any) -> None: 13 | raise NotImplementedError 14 | 15 | def log(self, src:Any, msg:str) -> None: 16 | self.event(EventHub.LOG, src=src, msg=msg) 17 | -------------------------------------------------------------------------------- /unicorn/simul/lwtest.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020-2020 Michael Kuyper. All rights reserved. 2 | # Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 3 | # 4 | # This file is subject to the terms and conditions defined in file 'LICENSE', 5 | # which is part of this source code package. 6 | 7 | from typing import cast, Any, Dict, Generator, List, Optional, Set, Tuple 8 | 9 | import contextlib 10 | import struct 11 | 12 | from dataclasses import dataclass 13 | 14 | import loramsg as lm 15 | import loraopts as lo 16 | 17 | from devtest import explain, DeviceTest 18 | from lorawan import LNS, LoraWanMsg, Session 19 | 20 | from ward import expect 21 | 22 | @dataclass 23 | class PowerStats: 24 | accu:float = 0.0 25 | count:int = 0 26 | 27 | def avg(self) -> float: 28 | return (self.accu / self.count) if self.count else 0 29 | 30 | def reset(self) -> None: 31 | self.accu = 0.0 32 | self.count = 0 33 | 34 | 35 | class LWTest(DeviceTest): 36 | def request_test(self, uplwm:LoraWanMsg, **kwargs:Any) -> None: 37 | self.dndf(uplwm, port=224, payload=b'\1\1\1\1', **kwargs) 38 | 39 | def request_echo(self, uplwm:LoraWanMsg, echo:bytes, **kwargs:Any) -> None: 40 | self.dndf(uplwm, port=224, payload=b'\x04' + echo, **kwargs) 41 | 42 | def request_mode(self, uplwm:LoraWanMsg, mode_conf:bool, **kwargs:Any) -> None: 43 | self.dndf(uplwm, port=224, payload=b'\x02' if mode_conf else b'\x03', **kwargs) 44 | 45 | def request_rejoin(self, uplwm:LoraWanMsg, **kwargs:Any) -> None: 46 | self.dndf(uplwm, port=224, payload=b'\x06', **kwargs) 47 | 48 | @staticmethod 49 | def unpack_dnctr(lwm:LoraWanMsg, *, expected:Optional[int]=None, **kwargs:Any) -> int: 50 | assert lwm.rtm is not None 51 | payload = lwm.rtm['FRMPayload']; 52 | try: 53 | dnctr, = cast(Tuple[int], struct.unpack('>H', payload)) 54 | except struct.error as e: 55 | raise ValueError(f'invalid payload: {payload.hex()}') from e 56 | if expected is not None: 57 | expect.assert_equal(expected, dnctr, explain('Unexpected downlink counter', **kwargs)) 58 | return dnctr 59 | 60 | @staticmethod 61 | def unpack_echo(lwm:LoraWanMsg, *, orig:Optional[bytes]=None, **kwargs:Any) -> bytes: 62 | assert lwm.rtm is not None 63 | payload:bytes = lwm.rtm['FRMPayload']; 64 | expect.assert_equal(0x04, payload[0], explain('Invalid echo packet', **kwargs)) 65 | echo = payload[1:] 66 | if orig is not None: 67 | expected = bytes((x + 1) & 0xff for x in orig) 68 | expect.assert_equal(expected, echo, explain('Unexpected echo response', **kwargs)) 69 | return echo 70 | 71 | async def test_updf(self, **kwargs:Any) -> LoraWanMsg: 72 | kwargs.setdefault('timeout', 60) 73 | return await self.updf(port=224, **kwargs) 74 | 75 | 76 | # join network, start test mode, return first test upmsg 77 | async def start_testmode(self, **kwargs:Any) -> LoraWanMsg: 78 | kwargs.setdefault('timeout', 60) 79 | await self.join(**kwargs) 80 | 81 | m = await self.updf(**kwargs) 82 | self.request_test(m) 83 | 84 | return await self.test_updf(**kwargs) 85 | 86 | async def echo(self, uplwm:LoraWanMsg, echo:bytes, **kwargs:Any) -> LoraWanMsg: 87 | self.request_echo(uplwm, echo, **kwargs) 88 | 89 | m = await self.updf() 90 | self.unpack_echo(m, orig=echo, **kwargs) 91 | 92 | return m 93 | 94 | async def upstats(self, m:LoraWanMsg, count:int, *, 95 | fstats:Optional[Dict[int,int]]=None, 96 | pstats:Optional[PowerStats]=None) -> LoraWanMsg: 97 | for _ in range(count): 98 | if fstats is not None: 99 | f = m.msg.freq 100 | fstats[f] = fstats.get(f, 0) + 1 101 | if pstats is not None: 102 | rssi = m.msg.rssi 103 | assert rssi is not None 104 | pstats.accu += rssi 105 | pstats.count += 1 106 | m = await self.test_updf() 107 | return m 108 | 109 | # check NewChannelAns 110 | @staticmethod 111 | def check_ncr_o(o:lo.Opt, ChnlAck:Optional[int]=1, DRAck:Optional[int]=1, **kwargs:Any) -> None: 112 | expect.assert_equal(type(o), lo.NewChannelAns, explain('Unexpected MAC command', **kwargs)) 113 | o = cast(lo.NewChannelAns, o) 114 | if ChnlAck is not None: 115 | expect.assert_equal(o.ChnlAck.value, ChnlAck, explain('Unexpected ChnlAck value', **kwargs)) # type: ignore 116 | if DRAck is not None: 117 | expect.assert_equal(o.DRAck.value, DRAck, explain('Unexpected DRAck value', **kwargs)) # type: ignore 118 | 119 | # check LinkADRAns 120 | @staticmethod 121 | def check_laa_o(o:lo.Opt, ChAck:Optional[int]=1, DRAck:Optional[int]=1, TXPowAck:Optional[int]=1, **kwargs:Any) -> None: 122 | expect.assert_equal(type(o), lo.LinkADRAns, explain('Unexpected MAC command', **kwargs)) 123 | o = cast(lo.LinkADRAns, o) 124 | if ChAck is not None: 125 | expect.assert_equal(o.ChAck.value, ChAck, explain('Unexpected ChAck', **kwargs)) # type: ignore 126 | if DRAck is not None: 127 | expect.assert_equal(o.DRAck.value, DRAck, explain('Unexpected DRAck', **kwargs)) # type: ignore 128 | if TXPowAck is not None: 129 | expect.assert_equal(o.TXPowAck.value, TXPowAck, explain('Unexpected TXPowAck', **kwargs)) # type: ignore 130 | 131 | # check frequency usage 132 | async def check_freqs(self, m:LoraWanMsg, freqs:Set[int], count:Optional[int]=None, **kwargs:Any) -> LoraWanMsg: 133 | if count is None: 134 | count = 16 * len(freqs) 135 | fstats:Dict[int,int] = {} 136 | m = await self.upstats(m, count, fstats=fstats) 137 | expect.assert_equal(fstats.keys(), freqs, explain('Unexpected channel usage', **kwargs)) 138 | return m 139 | 140 | def rps2dr(self, m:LoraWanMsg) -> int: 141 | assert self.session is not None 142 | return LNS.rps2dr(self.session['region'], m.msg.rps) 143 | 144 | @contextlib.contextmanager 145 | def modified_session(self, **kwargs:Any) -> Generator[None,None,None]: 146 | session = self.session 147 | assert session is not None 148 | changed:Dict[str,Any] = {} 149 | added:List[str] = [] 150 | for key in kwargs: 151 | if key in session: 152 | changed[key] = session[key] 153 | else: 154 | added.append(key) 155 | session[key] = kwargs[key] 156 | yield 157 | session.update(changed) 158 | for key in added: 159 | del session[key] 160 | -------------------------------------------------------------------------------- /unicorn/simul/runtime.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020-2020 Michael Kuyper. All rights reserved. 2 | # 3 | # This file is subject to the terms and conditions defined in file 'LICENSE', 4 | # which is part of this source code package. 5 | 6 | from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union 7 | 8 | import asyncio 9 | import heapq 10 | 11 | # ----------------------------------------------------------------------------- 12 | # Runtime for Serialized Callbacks 13 | # 14 | # --> asyncio.loop.call_at does not preserve order for jobs in the near past 15 | # when trying to catch up to real-time... 16 | 17 | class Clock: 18 | def time(self, *, update:bool=False) -> float: 19 | return 0 20 | 21 | def ticks(self, *, update:bool=False) -> int: 22 | return 0 23 | 24 | def ticks2time(self, ticks:int) -> float: 25 | return 0 26 | 27 | def time2ticks(self, time:float) -> int: 28 | return 0 29 | 30 | def sec2ticks(self, sec:float) -> int: 31 | return 0 32 | 33 | class Job: 34 | def _prepare(self, ticks:int) -> None: 35 | self._ticks = ticks 36 | self._cancelled = False 37 | 38 | def __lt__(self, other:Any) -> bool: 39 | if isinstance(other, Job): 40 | return self._ticks < other._ticks 41 | return NotImplemented 42 | 43 | def cancel(self) -> None: 44 | self._cancelled = True 45 | 46 | def run(self) -> None: 47 | pass 48 | 49 | class CallbacksJob(Job): 50 | def __init__(self) -> None: 51 | self.callbacks:List[Tuple[Callable[...,Any],Dict[str,Any]]] = list() 52 | 53 | def add(self, callback:Callable[...,Any], **kwargs:Any) -> None: 54 | self.callbacks.append((callback, kwargs)) 55 | 56 | def run(self) -> None: 57 | for callback, kwargs in self.callbacks: 58 | callback(**kwargs) 59 | 60 | class Runtime(): 61 | dummyclock = Clock() 62 | def __init__(self) -> None: 63 | self.clock = Runtime.dummyclock 64 | self.jobs:List[Job] = list() 65 | self.handle:Optional[asyncio.Handle] = None 66 | self.stepping = False 67 | 68 | def reset(self) -> None: 69 | self.clock = Runtime.dummyclock 70 | self.jobs.clear() 71 | if self.handle: 72 | self.handle.cancel() 73 | self.handle = None 74 | 75 | def setclock(self, clock:Optional[Clock]) -> None: 76 | if clock is None: 77 | clock = Runtime.dummyclock 78 | self.clock = clock 79 | 80 | def schedule(self, t:Union[int,float], job:Job) -> None: 81 | if isinstance(t, float): 82 | t = self.clock.time2ticks(t) 83 | job._prepare(t) 84 | heapq.heappush(self.jobs, job) 85 | self.rewind() 86 | 87 | def prune(self) -> None: 88 | while self.jobs and self.jobs[0]._cancelled: 89 | heapq.heappop(self.jobs) 90 | 91 | def step(self) -> None: 92 | now = self.clock.ticks(update=True) 93 | self.stepping = True 94 | while self.jobs and (j := self.jobs[0])._ticks <= now: 95 | heapq.heappop(self.jobs) 96 | if not j._cancelled: 97 | j.run() 98 | self.stepping = False 99 | self.handle = None 100 | self.rewind() 101 | 102 | def rewind(self) -> None: 103 | if self.stepping: 104 | return 105 | if self.handle: 106 | self.handle.cancel() 107 | self.handle = None 108 | self.prune() 109 | if self.jobs: 110 | t = self.clock.ticks2time(self.jobs[0]._ticks) 111 | self.handle = asyncio.get_running_loop().call_at(t, self.step) 112 | 113 | class JobGroup: 114 | def __init__(self, runtime:Runtime) -> None: 115 | self.runtime = runtime 116 | self.job2name:Dict[Job,Optional[str]] = dict() 117 | self.name2job:Dict[str,Job] = dict() 118 | 119 | def _add(self, job:Job, name:Optional[str]) -> None: 120 | if name: 121 | assert name not in self.name2job 122 | self.name2job[name] = job 123 | self.job2name[job] = name 124 | 125 | def _remove(self, job:Job) -> None: 126 | name = self.job2name.pop(job) 127 | if name: 128 | self.name2job.pop(name) 129 | 130 | def prerun_hook(self, job:Job) -> None: 131 | self._remove(job) 132 | 133 | def schedule(self, name:Optional[str], t:Union[int,float], callback:Callable[...,Any], **kwargs:Any) -> None: 134 | job = CallbacksJob() 135 | job.add(self.prerun_hook, job=job) 136 | job.add(callback, **kwargs) 137 | self._add(job, name) 138 | self.runtime.schedule(t, job) 139 | 140 | def cancel(self, name:str) -> bool: 141 | job = self.name2job.get(name, None) 142 | if job: 143 | job.cancel() 144 | self._remove(job) 145 | return True 146 | else: 147 | return False 148 | 149 | def cancel_all(self) -> None: 150 | for job in self.job2name: 151 | job.cancel() 152 | self.job2name.clear() 153 | self.name2job.clear() 154 | -------------------------------------------------------------------------------- /unicorn/simul/uuidgen.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | id = uuid.uuid1() 4 | print(f'// {id}') 5 | print('{ ' + ', '.join(map('0x{:02x}'.format, id.bytes)) + ' }') 6 | -------------------------------------------------------------------------------- /unicorn/simul/vtimeloop.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020-2020 Michael Kuyper. All rights reserved. 2 | # Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 3 | # 4 | # This file is subject to the terms and conditions defined in file 'LICENSE', 5 | # which is part of this source code package. 6 | 7 | from typing import Any, Awaitable, Callable, Dict, Generator, List, Optional, TypeVar, Union 8 | from typing import cast 9 | 10 | import asyncio 11 | import heapq 12 | 13 | from contextvars import Context 14 | 15 | T = TypeVar('T') 16 | 17 | class VirtualTimeLoop(asyncio.AbstractEventLoop): 18 | def __init__(self) -> None: 19 | self._time:float = 0 20 | self._tasks:List[asyncio.TimerHandle] = list() 21 | self._ex:Optional[BaseException] = None 22 | 23 | def get_debug(self) -> bool: 24 | return False 25 | 26 | def time(self) -> float: 27 | return self._time 28 | 29 | def call_exception_handler(self, context:Dict[str,Any]) -> None: 30 | self._ex = context.get('exception', None) 31 | 32 | def _run(self, future:Optional['asyncio.Future[Any]']) -> None: 33 | try: 34 | asyncio.events._set_running_loop(self) 35 | while len(self._tasks) and (future is None or not future.done()): 36 | th = heapq.heappop(self._tasks) 37 | self._time = th.when() 38 | if not th.cancelled(): 39 | th._run() 40 | if self._ex is not None: 41 | raise self._ex 42 | finally: 43 | self._ex = None 44 | asyncio.events._set_running_loop(None) 45 | 46 | def run_until_complete(self, future:Union[Generator[Any,None,T],Awaitable[T]]) -> T: 47 | f = asyncio.ensure_future(future, loop=self) 48 | self._run(f) 49 | return f.result() 50 | 51 | def create_task(self, coro): # type: ignore 52 | return asyncio.Task(coro, loop=self) 53 | 54 | def create_future(self) -> 'asyncio.Future[Any]': 55 | return asyncio.Future(loop=self) 56 | 57 | def call_at(self, when:float, callback:Callable[...,Any], *args:Any, context:Optional[Context]=None) -> asyncio.TimerHandle: 58 | th = asyncio.TimerHandle(when, callback, list(args), self, context) # type:ignore 59 | heapq.heappush(self._tasks, th) 60 | return th 61 | 62 | def call_later(self, delay:float, callback:Callable[...,Any], *args:Any, context:Optional[Context]=None) -> asyncio.TimerHandle: 63 | return self.call_at(self._time + delay, callback, *args, context=context) 64 | 65 | def call_soon(self, callback:Callable[...,Any], *args:Any, context:Optional[Context]=None) -> asyncio.TimerHandle: 66 | return self.call_later(0, callback, *args, context=context) 67 | 68 | def _timer_handle_cancelled(self, handle:asyncio.TimerHandle) -> None: 69 | pass 70 | -------------------------------------------------------------------------------- /unicorn/startup.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2016-2019 Semtech (International) AG. All rights reserved. 2 | // 3 | // This file is subject to the terms and conditions defined in file 'LICENSE', 4 | // which is part of this source code package. 5 | 6 | #include "lmic.h" 7 | 8 | #include "boottab.h" 9 | 10 | void _start (boot_boottab* boottab) { 11 | // symbols provided by linker script 12 | extern uint32_t _sidata, _sdata, _edata, _sbss, _ebss; 13 | 14 | // initialize data 15 | uint32_t* src = &_sidata; 16 | uint32_t* dst = &_sdata; 17 | while( dst < &_edata ) { 18 | *dst++ = *src++; 19 | } 20 | 21 | // initialize bss 22 | dst = &_sbss; 23 | while( dst < &_ebss ) { 24 | *dst++ = 0; 25 | } 26 | 27 | // call main function 28 | extern void main (boot_boottab* boottab); 29 | main(boottab); 30 | } 31 | 32 | // Firmware header 33 | __attribute__((section(".fwhdr"))) 34 | const volatile hal_fwhdr fwhdr = { 35 | // CRC and size will be patched by external tool 36 | .boot.crc = 0, 37 | .boot.size = BOOT_MAGIC_SIZE, 38 | .boot.entrypoint = (uint32_t) _start, 39 | 40 | .version = 0, 41 | }; 42 | -------------------------------------------------------------------------------- /unicorn/usart.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2020 Michael Kuyper. All rights reserved. 2 | // 3 | // This file is subject to the terms and conditions defined in file 'LICENSE', 4 | // which is part of this source code package. 5 | 6 | #include "lmic.h" 7 | #include "peripherals.h" 8 | 9 | 10 | // ----------------------------------------------------------------------------- 11 | // Fast UART implementation 12 | // - This UART has an infinite baud rate; transfers happen immediately 13 | 14 | static struct { 15 | struct { 16 | void* buf; 17 | int* pn; 18 | osjob_t* job; 19 | osjobcb_t cb; 20 | } rx; 21 | } fuart; 22 | 23 | void fuart_rx_cb (unsigned char* buf, int n) { 24 | if( n > *fuart.rx.pn ) { 25 | n = *fuart.rx.pn; 26 | } else { 27 | *fuart.rx.pn = n; 28 | } 29 | memcpy(fuart.rx.buf, buf, n); 30 | os_setCallback(fuart.rx.job, fuart.rx.cb); 31 | } 32 | 33 | static void fuart_send (void* src, int n, osjob_t* job, osjobcb_t cb) { 34 | fuart_tx(src, n); 35 | os_setCallback(job, cb); 36 | } 37 | 38 | static void fuart_abort_recv (void) { 39 | fuart_rx_stop(); 40 | *fuart.rx.pn = 0; 41 | os_setCallback(fuart.rx.job, fuart.rx.cb); 42 | } 43 | 44 | static void fuart_timeout (osjob_t* job) { 45 | fuart_abort_recv(); 46 | } 47 | 48 | static void fuart_recv (void* dst, int* n, ostime_t timeout, osjob_t* job, osjobcb_t cb) { 49 | fuart.rx.buf = dst; 50 | fuart.rx.pn = n; 51 | fuart.rx.job = job; 52 | fuart.rx.cb = cb; 53 | os_setTimedCallback(job, os_getTime() + timeout, fuart_timeout); 54 | fuart_rx_start(); 55 | } 56 | 57 | 58 | // ----------------------------------------------------------------------------- 59 | 60 | void usart_start (const void* port, unsigned int br) { } 61 | void usart_stop (const void* port) { } 62 | 63 | void usart_send (const void* port, void* src, int n, osjob_t* job, osjobcb_t cb) { 64 | if( port == USART_FUART1 ) { 65 | fuart_send(src, n, job, cb); 66 | } else { 67 | ASSERT(0); 68 | } 69 | } 70 | void usart_recv (const void* port, void* dst, int* n, ostime_t timeout, ostime_t idle_timeout, osjob_t* job, osjobcb_t cb) { 71 | if( port == USART_FUART1 ) { 72 | fuart_recv(dst, n, timeout, job, cb); 73 | } else { 74 | ASSERT(0); 75 | } 76 | } 77 | void usart_abort_recv (const void* port) { 78 | if( port == USART_FUART1 ) { 79 | fuart_abort_recv(); 80 | } else { 81 | ASSERT(0); 82 | } 83 | } 84 | 85 | void usart_str (const void* port, const char* str) { 86 | ASSERT(0); 87 | } 88 | --------------------------------------------------------------------------------