diff --git a/firmware/CMakeLists.txt b/firmware/CMakeLists.txt index 28758308..763ceec4 100644 --- a/firmware/CMakeLists.txt +++ b/firmware/CMakeLists.txt @@ -28,6 +28,7 @@ set(CHIBIOS_PORTAPACK ${PROJECT_SOURCE_DIR}/chibios-portapack) set(EXTRACT_CPLD_DATA ${PROJECT_SOURCE_DIR}/tools/extract_cpld_data.py) set(MAKE_SPI_IMAGE ${PROJECT_SOURCE_DIR}/tools/make_spi_image.py) set(MAKE_IMAGE_CHUNK ${PROJECT_SOURCE_DIR}/tools/make_image_chunk.py) +set(LZ4 lz4) set(FIRMWARE_NAME portapack-h1_h2-mayhem) set(FIRMWARE_FILENAME ${FIRMWARE_NAME}.bin) diff --git a/firmware/application/CMakeLists.txt b/firmware/application/CMakeLists.txt index 760c8d94..a222d61b 100644 --- a/firmware/application/CMakeLists.txt +++ b/firmware/application/CMakeLists.txt @@ -317,7 +317,10 @@ set(TCSRC) set(TCPPSRC) # List ASM source files here -set(ASMSRC ${PORTASM}) +set(ASMSRC + ${PORTASM} + lz4.S +) set(INCDIR ${CMAKE_CURRENT_BINARY_DIR} ${COMMON} ${PORTINC} ${KERNINC} ${TESTINC} ${HALINC} ${PLATFORMINC} ${BOARDINC} diff --git a/firmware/application/core_control.cpp b/firmware/application/core_control.cpp index ab653bc2..75cc2afb 100644 --- a/firmware/application/core_control.cpp +++ b/firmware/application/core_control.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2023 Bernd Herzog * * This file is part of PortaPack. * @@ -28,22 +29,20 @@ using namespace lpc43xx; #include "message.hpp" #include "baseband_api.hpp" +#include "lz4.h" #include -/* TODO: OK, this is cool, but how do I put the M4 to sleep so I can switch to - * a different image? Other than asking the old image to sleep while the M0 - * makes changes? - * - * I suppose I could force M4MEMMAP to an invalid memory reason which would - * cause an exception and effectively halt the M4. But that feels gross. - */ void m4_init(const portapack::spi_flash::image_tag_t image_tag, const portapack::memory::region_t to, const bool full_reset) { const portapack::spi_flash::chunk_t* chunk = reinterpret_cast(portapack::spi_flash::images.base()); while(chunk->tag) { - if( chunk->tag == image_tag ) { - /* Initialize M4 code RAM */ - std::memcpy(reinterpret_cast(to.base()), &chunk->data[0], chunk->length); + if(chunk->tag == image_tag) { + + const void *src = &chunk->data[0]; + void *dst = reinterpret_cast(to.base()); + + /* extract and initialize M4 code RAM */ + unlz4_len(src, dst, chunk->compressed_data_size); /* M4 core is assumed to be sleeping with interrupts off, so we can mess * with its address space and RAM without concern. diff --git a/firmware/application/lz4.S b/firmware/application/lz4.S new file mode 100644 index 00000000..4db7545c --- /dev/null +++ b/firmware/application/lz4.S @@ -0,0 +1,70 @@ +/* source: https://community.arm.com/arm-community-blogs/b/architectures-and-processors-blog/posts/lz4-decompression-routine-for-cortex-m0-and-later */ + + .syntax unified + .cpu cortex-m0 + .thumb + +/* License: Public Domain - I cannot be held responsible for what it does or does not do if you use it, whether it's modified or not. */ +/* Entry point = unlz4. On entry: r0 = source, r1 = destination. The first two bytes of the source must contain the length of the compressed data. */ + + .func unlz4 + .global unlz4,unlz4_len + .type unlz4,%function + .type unlz4_len,%function + + .thumb_func +unlz4: ldrh r2,[r0] /* get length of compressed data */ + adds r0,r0,#2 /* advance source pointer */ + + .thumb_func +unlz4_len: push {r4-r6,lr} /* save r4, r5, r6 and return-address */ + adds r5,r2,r0 /* point r5 to end of compressed data */ + +getToken: ldrb r6,[r0] /* get token */ + adds r0,r0,#1 /* advance source pointer */ + lsrs r4,r6,#4 /* get literal length, keep token in r6 */ + beq getOffset /* jump forward if there are no literals */ + bl getLength /* get length of literals */ + movs r2,r0 /* point r2 to literals */ + bl copyData /* copy literals (r2=src, r1=dst, r4=len) */ + movs r0,r2 /* update source pointer */ + +getOffset: ldrb r3,[r0,#0] /* get match offset's low byte */ + subs r2,r1,r3 /* subtract from destination; this will become the match position */ + ldrb r3,[r0,#1] /* get match offset's high byte */ + lsls r3,r3,#8 /* shift to high byte */ + subs r2,r2,r3 /* subtract from match position */ + adds r0,r0,#2 /* advance source pointer */ + lsls r4,r6,#28 /* get rid of token's high 28 bits */ + lsrs r4,r4,#28 /* move the 4 low bits back where they were */ + bl getLength /* get length of match data */ + adds r4,r4,#4 /* minimum match length is 4 bytes */ + bl copyData /* copy match data (r2=src, r1=dst, r4=len) */ + cmp r0,r5 /* check if we've reached the end of the compressed data */ + blt getToken /* if not, go get the next token */ + pop {r4-r6,pc} /* restore r4, r5 and r6, then return */ + + .thumb_func +getLength: cmp r4,#0x0f /* if length is 15, then more length info follows */ + bne gotLength /* jump forward if we have the complete length */ + +getLengthLoop: ldrb r3,[r0] /* read another byte */ + adds r0,r0,#1 /* advance source pointer */ + adds r4,r4,r3 /* add byte to length */ + cmp r3,#0xff /* check if end reached */ + beq getLengthLoop /* if not, go round loop */ +gotLength: bx lr /* return */ + + .thumb_func +copyData: rsbs r4,r4,#0 /* index = -length */ + subs r2,r2,r4 /* point to end of source */ + subs r1,r1,r4 /* point to end of destination */ + +copyDataLoop: ldrb r3,[r2,r4] /* read byte from source_end[-index] */ + strb r3,[r1,r4] /* store byte in destination_end[-index] */ + adds r4,r4,#1 /* increment index */ + bne copyDataLoop /* keep going until index wraps to 0 */ + bx lr /* return */ + .size unlz4,.-unlz4 + .endfunc +/* 42 narrow instructions = 84 bytes */ \ No newline at end of file diff --git a/firmware/application/lz4.h b/firmware/application/lz4.h new file mode 100644 index 00000000..6453afc4 --- /dev/null +++ b/firmware/application/lz4.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2023 Bernd Herzog + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __LZ4_H__ +#define __LZ4_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + extern void unlz4_len(const void *aSource, void *aDestination, uint32_t aLength); +#ifdef __cplusplus +} +#endif + +#endif /*__LZ4_H__*/ \ No newline at end of file diff --git a/firmware/baseband/CMakeLists.txt b/firmware/baseband/CMakeLists.txt index 7d9739a6..acd258a2 100644 --- a/firmware/baseband/CMakeLists.txt +++ b/firmware/baseband/CMakeLists.txt @@ -273,7 +273,8 @@ macro(DeclareTargets chunk_tag name) add_custom_command( OUTPUT ${PROJECT_NAME}.bin ${PROJECT_NAME}.img COMMAND ${CMAKE_OBJCOPY} -O binary ${PROJECT_NAME}.elf ${PROJECT_NAME}.bin - COMMAND ${MAKE_IMAGE_CHUNK} ${PROJECT_NAME}.bin ${chunk_tag} ${PROJECT_NAME}.img + COMMAND ${LZ4} -f -5 --no-frame-crc ${PROJECT_NAME}.bin ${PROJECT_NAME}.lz4 + COMMAND ${MAKE_IMAGE_CHUNK} ${PROJECT_NAME}.lz4 ${chunk_tag} ${PROJECT_NAME}.img DEPENDS ${PROJECT_NAME}.elf ${MAKE_IMAGE_CHUNK} VERBATIM ) @@ -508,7 +509,8 @@ DeclareTargets(PWFM wfm_audio) add_custom_command( OUTPUT hackrf.img - COMMAND ${MAKE_IMAGE_CHUNK} ${HACKRF_FIRMWARE_BIN_IMAGE} HRF1 hackrf.img 98304 + COMMAND ${LZ4} -f -9 --no-frame-crc ${HACKRF_FIRMWARE_BIN_IMAGE} hackrf.lz4 + COMMAND ${MAKE_IMAGE_CHUNK} hackrf.lz4 HRF1 hackrf.img DEPENDS ${HACKRF_FIRMWARE_BIN_FILENAME} ${MAKE_IMAGE_CHUNK} VERBATIM ) diff --git a/firmware/common/spi_image.hpp b/firmware/common/spi_image.hpp index 758429c8..ce2ab1c8 100644 --- a/firmware/common/spi_image.hpp +++ b/firmware/common/spi_image.hpp @@ -114,6 +114,7 @@ constexpr image_tag_t image_tag_hackrf { 'H', 'R', 'F', '1' }; struct chunk_t { const image_tag_t tag; const uint32_t length; + const uint32_t compressed_data_size; const uint8_t data[]; const chunk_t* next() const { diff --git a/firmware/tools/make_image_chunk.py b/firmware/tools/make_image_chunk.py index 4ad18192..222d2103 100755 --- a/firmware/tools/make_image_chunk.py +++ b/firmware/tools/make_image_chunk.py @@ -41,13 +41,20 @@ def write_image(data, path): f.write(data) f.close() -input_image_max_length = 32768 -if len(sys.argv) in (4, 5): +compressed_data_size = 0 + +if len(sys.argv) == 4: input_image = read_image(sys.argv[1]) tag = tuple(map(ord, sys.argv[2])) output_path = sys.argv[3] - if len(sys.argv) == 5: - input_image_max_length = int(sys.argv[4]) + + if input_image[5] & 4 == 4: + input_image = input_image[19:] + else: + input_image = input_image[11:] + + compressed_data_size = len(input_image) + elif len(sys.argv) == 2: input_image = bytearray() tag = (0, 0, 0, 0) @@ -60,13 +67,15 @@ if len(tag) != 4: print(usage_message) sys.exit(-2) -if len(input_image) > input_image_max_length: - raise RuntimeError('image size of %d exceeds device size of %d bytes' % (len(input_image), input_image_max_length)) if (len(input_image) & 3) != 0: - raise RuntimeError('image size of %d is not multiple of four' % (len(input_image,))) + input_image = bytearray(input_image) + + for i in range(4 - (len(input_image) & 3)): + input_image.append(0) output_image = bytearray() -output_image += struct.pack('<4BI', tag[0], tag[1], tag[2], tag[3], len(input_image)) +output_image += struct.pack('<4BII', tag[0], tag[1], tag[2], tag[3], len(input_image), compressed_data_size) + output_image += input_image write_image(output_image, output_path)