From b57ceb6f5bea98059bfb83c4a8d44721689fd461 Mon Sep 17 00:00:00 2001 From: "Kate J. Temkin" Date: Fri, 22 Feb 2019 16:09:33 -0700 Subject: [PATCH 01/23] pre-mega-merge: abstract some cmake files from greatfet --- firmware/cmake/dfu.cmake | 42 +++++++++++++++++++++++++++++ firmware/cmake/m0_coprocessor.cmake | 25 +++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 firmware/cmake/dfu.cmake create mode 100644 firmware/cmake/m0_coprocessor.cmake diff --git a/firmware/cmake/dfu.cmake b/firmware/cmake/dfu.cmake new file mode 100644 index 0000000..6b1153e --- /dev/null +++ b/firmware/cmake/dfu.cmake @@ -0,0 +1,42 @@ +# +# This file is part of libgreat +# +# Extensions for adding an lpc43xx DFU target to a libgreat target. + + +# Adds a DFU executable that corresponds to a given +function(add_dfu_executable NEW_EXECUTABLE BASE_EXECUTABLE BASE_LINKER_SCRIPT DFU_LINKER_SCRIPT) + + # Read out the properties of the relevant target; so we can use them to create our own DFU version. + get_target_property(SOURCES ${BASE_EXECUTABLE}.elf SOURCES) + get_target_property(LINK_OPTIONS ${BASE_EXECUTABLE}.elf LINK_OPTIONS) + get_target_property(LINK_DIRECTORIES ${BASE_EXECUTABLE}.elf LINK_DIRECTORIES) + get_target_property(LINK_LIBRARIES ${BASE_EXECUTABLE}.elf LINK_LIBRARIES) + + # Replace the base linker script with the target linker script. + string(REPLACE "${BASE_LINKER_SCRIPT}" "${DFU_LINKER_SCRIPT}" DFU_LINK_OPTIONS "${LINK_OPTIONS}") + + # Create a new executable that will create the elf representation of the given target. + add_executable(${NEW_EXECUTABLE}.elf) + set_target_properties(${NEW_EXECUTABLE}.elf PROPERTIES + SOURCES "${SOURCES}" + LINK_OPTIONS "${DFU_LINK_OPTIONS}" + LINK_DIRECTORIES "${LINK_DIRECTORIES}" + LINK_LIBRARIES "${LINK_LIBRARIES}" + ) + + # Add a custom target that converts the generated ELF file to a binary. + add_custom_target(${NEW_EXECUTABLE}.bin ALL DEPENDS ${NEW_EXECUTABLE}.elf COMMAND ${CMAKE_OBJCOPY} -Obinary ${NEW_EXECUTABLE}.elf ${NEW_EXECUTABLE}.bin) + + # Add a custom target that converts the genrated binary to a DFU file. + # FIXME: rewrite this to be a single command? (e.g. rewrite the python) + add_custom_target(${NEW_EXECUTABLE} ${DFU_ALL} DEPENDS ${NEW_EXECUTABLE}.bin + COMMAND rm -f _tmp.dfu _header.bin + COMMAND cp ${NEW_EXECUTABLE}.bin _tmp.dfu + COMMAND ${DFU_COMMAND} + COMMAND python ${PATH_GREATFET_FIRMWARE}/dfu.py ${NEW_EXECUTABLE}.bin + COMMAND cat _header.bin _tmp.dfu >${NEW_EXECUTABLE} + ) + +endfunction(add_dfu_executable) + diff --git a/firmware/cmake/m0_coprocessor.cmake b/firmware/cmake/m0_coprocessor.cmake new file mode 100644 index 0000000..ec46df6 --- /dev/null +++ b/firmware/cmake/m0_coprocessor.cmake @@ -0,0 +1,25 @@ +# +# This file is part of GreatFET +# +# Support for loading a secondary program to the m0 coprocessor. +# + + +set(SOURCE_M0 ${PATH_GREATFET_FIRMWARE_COMMON}/m0_sleep.c) + +configure_file(${PATH_GREATFET_FIRMWARE}/cmake/m0_bin.s.cmake m0_bin.s) + +add_executable(greatfet_usb_m0.elf ${SOURCE_M0}) + +target_compile_options(greatfet_usb_m0.elf PRIVATE ${FLAGS_COMPILE_COMMON} ${FLAGS_CPU_COMMON} ${FLAGS_CPU_M0}) +target_compile_definitions(greatfet_usb_m0.elf PRIVATE ${DEFINES_COMMON}) +target_link_options(greatfet_usb_m0.elf PRIVATE ${FLAGS_CPU_COMMON} ${FLAGS_CPU_M0} ${FLAGS_LINK_COMMON} ${FLAGS_LINK_M0}) + +target_link_directories(greatfet_usb_m0.elf PRIVATE ${PATH_LIBOPENCM3}/lib ${PATH_LIBOPENCM3}/lib/lpc43xx ${PATH_LIBGREAT}/firmware/platform/lpc43xx/linker) + +target_link_libraries(greatfet_usb_m0.elf PRIVATE c nosys opencm3_lpc43xx_m0) + +# ELF -> bin +add_custom_target(greatfet_usb_m0.bin ALL DEPENDS greatfet_usb_m0.elf COMMAND ${CMAKE_OBJCOPY} -Obinary greatfet_usb_m0.elf greatfet_usb_m0.bin) + +# this should add a depdency to the parent target From 9e0361caa7cf7d621b1edafa998b22985527b368 Mon Sep 17 00:00:00 2001 From: "Kate J. Temkin" Date: Mon, 25 Feb 2019 14:06:36 -0700 Subject: [PATCH 02/23] pre-mega-merge: cmake cleanup to ready for future changes --- CMakeLists.txt | 50 ---------------------------- firmware/CMakeLists.txt | 58 +++++++++++++++++++++++++++++++++ firmware/cmake/libopencm3.cmake | 4 +-- 3 files changed, 60 insertions(+), 52 deletions(-) delete mode 100644 CMakeLists.txt create mode 100644 firmware/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 6150deb..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,50 +0,0 @@ -# -# This file is part of GreatFET -# - -cmake_minimum_required(VERSION 3.1.3) - -#set(CMAKE_TOOLCHAIN_FILE ../firmware/cmake/toolchain-arm-cortex-m.cmake) - -project(libgreat C CXX ASM) - -# Horrible hack: use libopencm3, for now. -include ("${CMAKE_CURRENT_SOURCE_DIR}/firmware/cmake/libopencm3.cmake") - -set(LD_SCRIPT "-T${CMAKE_CURRENT_SOURCE_DIR}/../firmware/common/LPC43xx_M4_memory.ld -Tlibopencm3_lpc43xx_rom_to_ram.ld -T${CMAKE_CURRENT_SOURCE_DIR}/..firmware/common/LPC43xx_M4_M0_image_from_text.ld") - -set(LD_SCRIPT_DFU "-T${CMAKE_CURRENT_SOURCE_DIR}/../firmware/common/LPC4330_M4.memory.ld -Tlibopencm3_lpc43xx.ld -T${CMAKE_CURRENT_SOURCE_DIR}/../firmware/common/LPC43xx_M4_M0_image_from_text.ld") - -message(STATUS "Finding classses in ${PATH_LIBGREAT}/classes") -aux_source_directory(${PATH_LIBGREAT}/classes CLASSES_LIBGREAT_COMMON_SOURCES) - -message(STATUS "Using classes: " ${CLASSES_LIBGREAT_COMMON_SOURCES}) - -add_library(libgreat OBJECT - ${CMAKE_CURRENT_SOURCE_DIR}/firmware/platform/lpc43xx/crt0.c - ${CMAKE_CURRENT_SOURCE_DIR}/firmware/platform/lpc43xx/sync.c - ${CMAKE_CURRENT_SOURCE_DIR}/firmware/platform/lpc43xx/sync.S - ${CMAKE_CURRENT_SOURCE_DIR}/firmware/drivers/memory/allocator.c - ${CMAKE_CURRENT_SOURCE_DIR}/firmware/drivers/memory/allocator/umm_malloc.c - ${CMAKE_CURRENT_SOURCE_DIR}/firmware/drivers/usb/lpc43xx/usb.c - ${CMAKE_CURRENT_SOURCE_DIR}/firmware/drivers/usb/lpc43xx/usb_host.c - ${CMAKE_CURRENT_SOURCE_DIR}/firmware/drivers/usb/lpc43xx/usb_request.c - ${CMAKE_CURRENT_SOURCE_DIR}/firmware/drivers/usb/lpc43xx/usb_standard_request.c - ${CMAKE_CURRENT_SOURCE_DIR}/firmware/drivers/usb/lpc43xx/usb_queue.c - ${CMAKE_CURRENT_SOURCE_DIR}/firmware/drivers/usb/lpc43xx/usb_queue_host.c - ${CMAKE_CURRENT_SOURCE_DIR}/firmware/drivers/usb/comms_backend.c - ${CMAKE_CURRENT_SOURCE_DIR}/firmware/drivers/gpio/lpc43xx/gpio.c - ${CMAKE_CURRENT_SOURCE_DIR}/firmware/drivers/comms/comms_class.c - ${CMAKE_CURRENT_SOURCE_DIR}/firmware/drivers/comms/utils.c - ${CMAKE_CURRENT_SOURCE_DIR}/firmware/classes/core.c - ${CMAKE_CURRENT_SOURCE_DIR}/firmware/classes/firmware.c - ${CLASSES_LIBGREAT_COMMON_SOURCES} - ) - -target_compile_definitions(libgreat PUBLIC ${DEFINES_COMMON}) -add_dependencies(libgreat libopencm3_${PROJECT_NAME}) - -target_include_directories(libgreat SYSTEM PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../firmware/libopencm3/include) -target_include_directories(libgreat PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/firmware/include ${CMAKE_CURRENT_SOURCE_DIR}/firmware/include/platform/lpc43xx ${CMAKE_CURRENT_SOURCE_DIR}/../firmware/common) - -target_compile_options(libgreat PRIVATE ${FLAGS_COMPILE_COMMON} ${FLAGS_CPU_COMMON} ${FLAGS_CPU_M4}) diff --git a/firmware/CMakeLists.txt b/firmware/CMakeLists.txt new file mode 100644 index 0000000..08d1db3 --- /dev/null +++ b/firmware/CMakeLists.txt @@ -0,0 +1,58 @@ +# +# This file is part of GreatFET +# + +cmake_minimum_required(VERSION 3.1.3) + +#set(CMAKE_TOOLCHAIN_FILE ../firmware/cmake/toolchain-arm-cortex-m.cmake) + +project(libgreat C CXX ASM) + +# Horrible hack: use libopencm3, for now. +include ("${CMAKE_CURRENT_SOURCE_DIR}/cmake/libopencm3.cmake") + +set(LD_SCRIPT "-T${CMAKE_CURRENT_SOURCE_DIR}/../firmware/common/LPC43xx_M4_memory.ld -Tlibopencm3_lpc43xx_rom_to_ram.ld -T${CMAKE_CURRENT_SOURCE_DIR}/..firmware/common/LPC43xx_M4_M0_image_from_text.ld") +set(LD_SCRIPT_DFU "-T${CMAKE_CURRENT_SOURCE_DIR}/../firmware/common/LPC4330_M4.memory.ld -Tlibopencm3_lpc43xx.ld -T${CMAKE_CURRENT_SOURCE_DIR}/../firmware/common/LPC43xx_M4_M0_image_from_text.ld") + +file(GLOB_RECURSE CLASSES_LIBGREAT_COMMON_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/classes/*.c") +message(STATUS "Using classes: " ${CLASSES_LIBGREAT_COMMON_SOURCES}) + +add_library(libgreat OBJECT + ${CMAKE_CURRENT_SOURCE_DIR}/platform/lpc43xx/crt0.c + ${CMAKE_CURRENT_SOURCE_DIR}/platform/lpc43xx/sync.c + ${CMAKE_CURRENT_SOURCE_DIR}/platform/lpc43xx/sync.S + ${CMAKE_CURRENT_SOURCE_DIR}/drivers/memory/allocator.c + ${CMAKE_CURRENT_SOURCE_DIR}/drivers/memory/allocator/umm_malloc.c + ${CMAKE_CURRENT_SOURCE_DIR}/drivers/usb/lpc43xx/usb.c + ${CMAKE_CURRENT_SOURCE_DIR}/drivers/usb/lpc43xx/usb_host.c + ${CMAKE_CURRENT_SOURCE_DIR}/drivers/usb/lpc43xx/usb_request.c + ${CMAKE_CURRENT_SOURCE_DIR}/drivers/usb/lpc43xx/usb_standard_request.c + ${CMAKE_CURRENT_SOURCE_DIR}/drivers/usb/lpc43xx/usb_queue.c + ${CMAKE_CURRENT_SOURCE_DIR}/drivers/usb/lpc43xx/usb_queue_host.c + ${CMAKE_CURRENT_SOURCE_DIR}/drivers/usb/comms_backend.c + ${CMAKE_CURRENT_SOURCE_DIR}/drivers/gpio/lpc43xx/gpio.c + ${CMAKE_CURRENT_SOURCE_DIR}/drivers/comms/comms_class.c + ${CMAKE_CURRENT_SOURCE_DIR}/drivers/comms/utils.c + ${CMAKE_CURRENT_SOURCE_DIR}/classes/core.c + ${CMAKE_CURRENT_SOURCE_DIR}/classes/firmware.c + ${CLASSES_LIBGREAT_COMMON_SOURCES} + ) + +target_compile_definitions(libgreat PUBLIC ${DEFINES_COMMON} ${DEFINES_BOARD}) + +# FIXME: get rid of this! +add_dependencies(libgreat libopencm3) + +# XXX fix this +target_include_directories(libgreat SYSTEM PRIVATE ${PATH_LIBOPENCM3}/include) +target_include_directories(libgreat PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/include/platform/lpc43xx + + # XXX get rid of this + ${CMAKE_CURRENT_SOURCE_DIR}/../../firmware/common + ${BOARD_INCLUDE_DIRECTORIES} + ${BUILD_INCLUDE_DIRECTORIES} +) + +target_compile_options(libgreat PRIVATE ${FLAGS_COMPILE_COMMON} ${FLAGS_BOARD} ${FLAGS_MAIN_CPU}) diff --git a/firmware/cmake/libopencm3.cmake b/firmware/cmake/libopencm3.cmake index 76cb9de..cacaa83 100644 --- a/firmware/cmake/libopencm3.cmake +++ b/firmware/cmake/libopencm3.cmake @@ -2,6 +2,7 @@ # This file is part of GreatFET. # Legacy support stub for libopencm3. It will be removed as soon as we can. :) # +include_guard(GLOBAL) include(ExternalProject) @@ -10,8 +11,7 @@ include(ExternalProject) set(PATH_LIBOPENCM3 ${PATH_GREATFET_FIRMWARE}/libopencm3) # Actually build libopencm3. -message(STATUS "Ensuring libopencm3 has been built.") -ExternalProject_Add(libopencm3_${PROJECT_NAME} +ExternalProject_Add(libopencm3 SOURCE_DIR "${PATH_LIBOPENCM3}" BUILD_IN_SOURCE true DOWNLOAD_COMMAND "" From b65c7f04029087abbd96f3ac09b89f5959b88be4 Mon Sep 17 00:00:00 2001 From: "Kate J. Temkin" Date: Mon, 25 Feb 2019 19:21:37 -0700 Subject: [PATCH 03/23] build: implement module system to make building cleaner/easier --- firmware/CMakeLists.txt | 62 +++----- firmware/cmake/libgreat.cmake | 168 ++++++++++++++++++++ firmware/cmake/libgreat_prelude.cmake | 24 +++ firmware/cmake/libopencm3.cmake | 25 ++- firmware/cmake/platform/lpc4330.cmake | 14 ++ firmware/cmake/platform/lpc43xx.cmake | 29 ++++ firmware/cmake/toolchain/arm-cortex-m.cmake | 2 + firmware/drivers/usb/lpc43xx/usb.c | 3 + firmware/platform/lpc43xx/CMakeLists.txt | 37 +++++ 9 files changed, 309 insertions(+), 55 deletions(-) create mode 100644 firmware/cmake/libgreat.cmake create mode 100644 firmware/cmake/libgreat_prelude.cmake create mode 100644 firmware/cmake/platform/lpc4330.cmake create mode 100644 firmware/cmake/platform/lpc43xx.cmake create mode 100644 firmware/platform/lpc43xx/CMakeLists.txt diff --git a/firmware/CMakeLists.txt b/firmware/CMakeLists.txt index 08d1db3..f1809bd 100644 --- a/firmware/CMakeLists.txt +++ b/firmware/CMakeLists.txt @@ -1,58 +1,36 @@ # # This file is part of GreatFET # - cmake_minimum_required(VERSION 3.1.3) -#set(CMAKE_TOOLCHAIN_FILE ../firmware/cmake/toolchain-arm-cortex-m.cmake) - +include(cmake/libgreat_prelude.cmake) project(libgreat C CXX ASM) +include(cmake/libgreat.cmake) -# Horrible hack: use libopencm3, for now. -include ("${CMAKE_CURRENT_SOURCE_DIR}/cmake/libopencm3.cmake") +# Include any platform-specific targets desired. +add_subdirectory(${PATH_LIBGREAT_FIRMWARE_PLATFORM}) -set(LD_SCRIPT "-T${CMAKE_CURRENT_SOURCE_DIR}/../firmware/common/LPC43xx_M4_memory.ld -Tlibopencm3_lpc43xx_rom_to_ram.ld -T${CMAKE_CURRENT_SOURCE_DIR}/..firmware/common/LPC43xx_M4_M0_image_from_text.ld") -set(LD_SCRIPT_DFU "-T${CMAKE_CURRENT_SOURCE_DIR}/../firmware/common/LPC4330_M4.memory.ld -Tlibopencm3_lpc43xx.ld -T${CMAKE_CURRENT_SOURCE_DIR}/../firmware/common/LPC43xx_M4_M0_image_from_text.ld") +# Include the core libgreat library. +add_libgreat_library_if_necessary(libgreat OBJECT + ${PATH_LIBGREAT_FIRMWARE_PLATFORM}/crt0.c + ${PATH_LIBGREAT_FIRMWARE_PLATFORM}/sync.c + ${PATH_LIBGREAT_FIRMWARE_PLATFORM}/sync.S +) -file(GLOB_RECURSE CLASSES_LIBGREAT_COMMON_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/classes/*.c") -message(STATUS "Using classes: " ${CLASSES_LIBGREAT_COMMON_SOURCES}) +# Provide a basic allocator. +define_libgreat_module(allocator + ${PATH_LIBGREAT_FIRMWARE_DRIVERS}/memory/allocator.c + ${PATH_LIBGREAT_FIRMWARE_DRIVERS}/memory/allocator/umm_malloc.c +) -add_library(libgreat OBJECT - ${CMAKE_CURRENT_SOURCE_DIR}/platform/lpc43xx/crt0.c - ${CMAKE_CURRENT_SOURCE_DIR}/platform/lpc43xx/sync.c - ${CMAKE_CURRENT_SOURCE_DIR}/platform/lpc43xx/sync.S - ${CMAKE_CURRENT_SOURCE_DIR}/drivers/memory/allocator.c - ${CMAKE_CURRENT_SOURCE_DIR}/drivers/memory/allocator/umm_malloc.c - ${CMAKE_CURRENT_SOURCE_DIR}/drivers/usb/lpc43xx/usb.c - ${CMAKE_CURRENT_SOURCE_DIR}/drivers/usb/lpc43xx/usb_host.c - ${CMAKE_CURRENT_SOURCE_DIR}/drivers/usb/lpc43xx/usb_request.c - ${CMAKE_CURRENT_SOURCE_DIR}/drivers/usb/lpc43xx/usb_standard_request.c - ${CMAKE_CURRENT_SOURCE_DIR}/drivers/usb/lpc43xx/usb_queue.c - ${CMAKE_CURRENT_SOURCE_DIR}/drivers/usb/lpc43xx/usb_queue_host.c - ${CMAKE_CURRENT_SOURCE_DIR}/drivers/usb/comms_backend.c - ${CMAKE_CURRENT_SOURCE_DIR}/drivers/gpio/lpc43xx/gpio.c - ${CMAKE_CURRENT_SOURCE_DIR}/drivers/comms/comms_class.c +# Provide the core communications protocol. +define_libgreat_module(comms ${CMAKE_CURRENT_SOURCE_DIR}/drivers/comms/utils.c + ${CMAKE_CURRENT_SOURCE_DIR}/drivers/comms/comms_class.c ${CMAKE_CURRENT_SOURCE_DIR}/classes/core.c ${CMAKE_CURRENT_SOURCE_DIR}/classes/firmware.c - ${CLASSES_LIBGREAT_COMMON_SOURCES} - ) +) -target_compile_definitions(libgreat PUBLIC ${DEFINES_COMMON} ${DEFINES_BOARD}) -# FIXME: get rid of this! +# FIXME: get rid of this add_dependencies(libgreat libopencm3) - -# XXX fix this -target_include_directories(libgreat SYSTEM PRIVATE ${PATH_LIBOPENCM3}/include) -target_include_directories(libgreat PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/include - ${CMAKE_CURRENT_SOURCE_DIR}/include/platform/lpc43xx - - # XXX get rid of this - ${CMAKE_CURRENT_SOURCE_DIR}/../../firmware/common - ${BOARD_INCLUDE_DIRECTORIES} - ${BUILD_INCLUDE_DIRECTORIES} -) - -target_compile_options(libgreat PRIVATE ${FLAGS_COMPILE_COMMON} ${FLAGS_BOARD} ${FLAGS_MAIN_CPU}) diff --git a/firmware/cmake/libgreat.cmake b/firmware/cmake/libgreat.cmake new file mode 100644 index 0000000..72d4fa9 --- /dev/null +++ b/firmware/cmake/libgreat.cmake @@ -0,0 +1,168 @@ +# +# This file is part of libgreat. +# Basic configuration and helper defines for using libgreat. +# +include_guard() + +# Horrible hack: use libopencm3, for now. +include ("${PATH_LIBGREAT_FIRMWARE}/cmake/libopencm3.cmake") + +# +# Function that prepends a given prefix to each menber of a list. +# +function(generate_linker_script_arguments OUTPUT_VARIABLE) + set(ARGUMENTS "") + + # Append a -T flag for each linker script passed in. + foreach (SCRIPT ${ARGN}) + list(APPEND ARGUMENTS "-T${SCRIPT}") + endforeach(SCRIPT) + + # Set the relevant output variable. + set("${OUTPUT_VARIABLE}" "${ARGUMENTS}" PARENT_SCOPE) +endfunction() + +# +# Function that creates a new GreatFET library / source archive. +# Arguments: [library_name] [sources...] +# +function(add_libgreat_library LIBRARY_NAME) + + # Create the relevant library. + add_library(${LIBRARY_NAME} ${ARGN}) + + # And set its default properties. + target_include_directories(${LIBRARY_NAME} PRIVATE + ${BOARD_INCLUDE_DIRECTORIES} + ${BUILD_INCLUDE_DIRECTORIES} + ${PATH_LIBOPENCM3}/include + ${PATH_GREATFET_FIRMWARE_COMMON} + ${PATH_LIBGREAT}/firmware/include + ${PATH_LIBGREAT}/firmware/include/platform/${LIBGREAT_PLATFORM} + ) + target_compile_options(${LIBRARY_NAME} PRIVATE ${FLAGS_COMPILE_COMMON} ${FLAGS_ARCHITECTURE} ${FLAGS_MAIN_CPU}) + target_compile_definitions(${LIBRARY_NAME} PRIVATE ${DEFINES_COMMON} ${DEFINES_BOARD}) + +endfunction(add_libgreat_library) + + +# +# Function that creates a new GreatFET library / source archive iff the relevant library does not exist. +# Arguments: [library_name] [sources...] +# +function(add_libgreat_library_if_necessary LIBRARY_NAME) + + # If the target doesn't already exist, create it. + if (NOT TARGET ${LIBRARY_NAME}) + add_libgreat_library(${LIBRARY_NAME} ${ARGN}) + endif() + +endfunction(add_libgreat_library_if_necessary) + + +# +# Function that adds a flash "executable" target to the currrent build. +# Arguments: [binary_name] [sources...] +# +function(add_flash_executable EXECUTABLE_NAME) + + # Add a target for the ELF form of the executable. + add_executable(${EXECUTABLE_NAME}.elf ${ARGN}) + + # ... and set its default properties. + generate_linker_script_arguments(FLAGS_LINKER_SCRIPTS ${LINKER_SCRIPTS_MAIN_CPU} ${LINKER_SCRIPT_FLASH}) + target_link_options(${EXECUTABLE_NAME}.elf PRIVATE + ${FLAGS_ARCHITECTURE} + ${FLAGS_MAIN_CPU} + ${FLAGS_LINK_BOARD} + ${FLAGS_LINKER_SCRIPTS} + ${FLAGS_LINK_MAIN_CPU} + ) + target_link_directories(${EXECUTABLE_NAME}.elf PRIVATE + ${PATH_LIBOPENCM3}/lib + ${PATH_LIBOPENCM3}/lib/lpc43xx + ${PATH_LIBGREAT}/firmware/platform/${LIBGREAT_PLATFORM}/linker + ) + + target_link_libraries(${EXECUTABLE_NAME}.elf c nosys ${LINK_LIBRARIES_BOARD} m) + + # Add a target that creates the final binary. + add_custom_target(${EXECUTABLE_NAME} ALL DEPENDS ${EXECUTABLE_NAME}.elf COMMAND ${CMAKE_OBJCOPY} -Obinary ${EXECUTABLE_NAME}.elf ${EXECUTABLE_NAME}) + +endfunction(add_flash_executable) + +# +# Function that creates a new libgreat library / source collection. +# Arguments: [sources...] +# +function(add_libgreat_library LIBRARY_NAME) + + # Create the relevant library. + add_library(${LIBRARY_NAME} OBJECT ${ARGN}) + + # And set its default properties. + target_include_directories(${LIBRARY_NAME} PRIVATE + ${BOARD_INCLUDE_DIRECTORIES} + ${BUILD_INCLUDE_DIRECTORIES} + ${PATH_LIBOPENCM3}/include + ${PATH_GREATFET_FIRMWARE_COMMON} #XXX: remove this! + ${PATH_LIBGREAT_FIRMWARE}/include + ${PATH_LIBGREAT}/firmware/include/platform/${LIBGREAT_PLATFORM} + ) + target_compile_options(${LIBRARY_NAME} PRIVATE ${FLAGS_COMPILE_COMMON} ${FLAGS_ARCHITECTURE} ${FLAGS_MAIN_CPU}) + target_compile_definitions(${LIBRARY_NAME} PRIVATE ${DEFINES_COMMON} ${DEFINES_BOARD}) + +endfunction(add_libgreat_library) + + +# +# Function that creates a new libgreat library / source collection iff the relevant library does not exist. +# Arguments: [sources...] +# +function(add_libgreat_library_if_necessary LIBRARY_NAME) + + # If the target doesn't already exist, create it. + if (NOT TARGET ${LIBRARY_NAME}) + add_libgreat_library(${LIBRARY_NAME} ${ARGN}) + endif() + +endfunction(add_libgreat_library_if_necessary) + + +# +# Function that provides a configurable libgreat feature, which can be optionally included in a given consume. +# Arguments: [sources...] +# +function(define_libgreat_module MODULE_NAME) + add_libgreat_library_if_necessary(libgreat_module_${MODULE_NAME} ${ARGN}) +endfunction(define_libgreat_module) + + +# +# Function that adds a libgreat module to a given target. +# Arguments: [additional_modules...] +# +function(use_libgreat_modules TARGET_NAME) + + # Iterate over each of the provided modules. + foreach (MODULE ${ARGN}) + + # Compute the module's internal object name. + set (MODULE_OBJECT libgreat_module_${MODULE}) + + # Ensure that the relevant module exists. + if (NOT TARGET libgreat_module_${MODULE}) + message(FATAL_ERROR "Cannot find the required libgreat module '${MODULE}'-- there's likely something wrong" + " with the build configuration; or this module isn't supported on your platform.") + endif() + + # Ensure that the relevant target depends on the given module... + add_dependencies(${TARGET_NAME} libgreat_module_${MODULE}) + + # ... and include the module's sources in the target. + get_target_property(MODULE_SOURCES libgreat_module_${MODULE} SOURCES) + target_sources(${TARGET_NAME} PRIVATE ${MODULE_SOURCES}) + + endforeach(MODULE) + +endfunction(use_libgreat_modules) diff --git a/firmware/cmake/libgreat_prelude.cmake b/firmware/cmake/libgreat_prelude.cmake new file mode 100644 index 0000000..8ba0b27 --- /dev/null +++ b/firmware/cmake/libgreat_prelude.cmake @@ -0,0 +1,24 @@ +# +# This file is part of libgreat. +# Early configuration and helper defines for using libgreat. +# +# Should be included before the main project() call. +# +include_guard() + + +# If we don't have a platform configuration, this can't be right. +if (NOT LIBGREAT_PLATFORM) + message(FATAL_ERROR "libgreat firmware cannot be built without a configured platform! Check the including code.") +endif() + +if (NOT CMAKE_TOOLCHAIN_FILE) + message(FATAL_ERROR "libgreat firmware cannot be built without a cross-compile toolchain! Check the including code.") +endif() + +# Libgreat paths. +# TODO: move most of these to a libgreat.cmake? +set(PATH_LIBGREAT_FIRMWARE ${PATH_LIBGREAT}/firmware) +set(PATH_LIBGREAT_FIRMWARE_CMAKE ${PATH_LIBGREAT_FIRMWARE}/cmake) +set(PATH_LIBGREAT_FIRMWARE_DRIVERS ${PATH_LIBGREAT_FIRMWARE}/drivers) +set(PATH_LIBGREAT_FIRMWARE_PLATFORM ${PATH_LIBGREAT}/firmware/platform/${LIBGREAT_PLATFORM}) diff --git a/firmware/cmake/libopencm3.cmake b/firmware/cmake/libopencm3.cmake index cacaa83..4e90a5e 100644 --- a/firmware/cmake/libopencm3.cmake +++ b/firmware/cmake/libopencm3.cmake @@ -2,22 +2,21 @@ # This file is part of GreatFET. # Legacy support stub for libopencm3. It will be removed as soon as we can. :) # -include_guard(GLOBAL) +include_guard() include(ExternalProject) # XXX Don't require libopencm3 from another place. Ick! -# XXX Don't require libopencm3 at all. >.> +# FIXME: Don't require libopencm3 at all. >.> set(PATH_LIBOPENCM3 ${PATH_GREATFET_FIRMWARE}/libopencm3) -# Actually build libopencm3. -ExternalProject_Add(libopencm3 - SOURCE_DIR "${PATH_LIBOPENCM3}" - BUILD_IN_SOURCE true - DOWNLOAD_COMMAND "" - CONFIGURE_COMMAND "" - INSTALL_COMMAND "" -) - -# BUILD_COMMAND "make ${LIBOPENCM3_MAKE_FLAGS}" - +# Specify how we build libopencm3. +if (NOT TARGET libopencm3) + ExternalProject_Add(libopencm3 + SOURCE_DIR "${PATH_LIBOPENCM3}" + BUILD_IN_SOURCE true + DOWNLOAD_COMMAND "" + CONFIGURE_COMMAND "" + INSTALL_COMMAND "" + ) +endif() diff --git a/firmware/cmake/platform/lpc4330.cmake b/firmware/cmake/platform/lpc4330.cmake new file mode 100644 index 0000000..575dd88 --- /dev/null +++ b/firmware/cmake/platform/lpc4330.cmake @@ -0,0 +1,14 @@ +# +# This file is part of greatfet. +# Common board configuration for LPC4330-based boards. +# +include_guard() + +# Derive our configuration from the LPC43xx platform code. +include(${PATH_LIBGREAT_FIRMWARE_CMAKE}/platform/lpc43xx.cmake) + +# Specify the individual part number we're using. +set(LIBGREAT_PART lpc4330) + +# Add on the LPC4330 linker script for the main CPU. +set(LINKER_SCRIPTS_MAIN_CPU ${LINKER_SCRIPTS_MAIN_CPU} LPC4330_M4_memory.ld) diff --git a/firmware/cmake/platform/lpc43xx.cmake b/firmware/cmake/platform/lpc43xx.cmake new file mode 100644 index 0000000..9a2e28a --- /dev/null +++ b/firmware/cmake/platform/lpc43xx.cmake @@ -0,0 +1,29 @@ +# +# This file is part of greatfet. +# Board support for LPC43xx-based boards. +# +include_guard() + +# Define that this is an LPC43xx platform. +set(LIBGREAT_PLATFORM lpc43xx) + +# TODO: do we want to use the hard-float ABI, or do we want to make these inter-linkable? +set(FLAGS_MAIN_CPU -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16) +set(FLAGS_SECONDARY_CPU -mcpu=cortex-m0 -mfloat-abi=soft) + +set(FLAGS_LINK_BOARD -nostartfiles -Wl,--gc-sections) +set(FLAGS_LINK_MAIN_CPU -Xlinker -Map=m4.map) +set(FLAGS_LINK_SECONDARY_CPU -Xlinker -Map=m0.map) + +# FIXME: don't include libopencm3! +set(LINK_LIBRARIES_BOARD opencm3_lpc43xx) +set(DEFINES_BOARD ${DEFINES_BOARD} LPC43XX_M4) + +# TODO: make these just the linker scripts, and not the flags +set(LINKER_SCRIPT_FLASH libgreat_lpc43xx_rom_to_ram.ld) +set(LINKER_SCRIPT_DFU libgreat_lpc43xx.ld) +set(LINKER_SCRIPTS_SECONDARY_CPU LPC43xx_M0_memory.ld libopencm3_lpc43xx_m0.ld) + +# Use our Cortex-M toolchain. +set(CMAKE_TOOLCHAIN_FILE "${PATH_LIBGREAT}/firmware/cmake/toolchain/arm-cortex-m.cmake") +mark_as_advanced(CMAKE_TOOLCHAIN_FILE) diff --git a/firmware/cmake/toolchain/arm-cortex-m.cmake b/firmware/cmake/toolchain/arm-cortex-m.cmake index 71a78fa..511276e 100644 --- a/firmware/cmake/toolchain/arm-cortex-m.cmake +++ b/firmware/cmake/toolchain/arm-cortex-m.cmake @@ -35,3 +35,5 @@ set(CMAKE_FIND_ROOT_PATH ${CMAKE_INSTALL_PREFIX}) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + +set(FLAGS_ARCHITECTURE -mthumb) diff --git a/firmware/drivers/usb/lpc43xx/usb.c b/firmware/drivers/usb/lpc43xx/usb.c index ad74353..f54a204 100644 --- a/firmware/drivers/usb/lpc43xx/usb.c +++ b/firmware/drivers/usb/lpc43xx/usb.c @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -21,6 +22,8 @@ // FIXME: Clean me up to use the USB_REG macro from usb_registers.h to reduce duplication! +usb_peripheral_t WEAK usb_peripherals[] = {{ .controller = 0, }, { .controller = 1, }}; + #define USB_QH_INDEX(endpoint_address) (((endpoint_address & 0xF) * 2) + ((endpoint_address >> 7) & 1)) usb_queue_head_t* usb_queue_head( diff --git a/firmware/platform/lpc43xx/CMakeLists.txt b/firmware/platform/lpc43xx/CMakeLists.txt new file mode 100644 index 0000000..4a422c6 --- /dev/null +++ b/firmware/platform/lpc43xx/CMakeLists.txt @@ -0,0 +1,37 @@ +# +# This file is part of GreatFET +# +cmake_minimum_required(VERSION 3.1.3) + +include(../../cmake/libgreat_prelude.cmake) +project(libgreat_platform_lpc43xx C ASM) +include(../../cmake/libgreat.cmake) + +# +# FIXME: fix the path structure -- these all should be relative to the current source dir +# and should be simply paths like -- /drivers/usb/usb.c. The restructure for this is already done +# in other branches, but given how we've chosen to rebase we won't get this for a few more commits. +# + +# Provide a USB driver stack. +# FIXME: move these into a seperate cmake for the platform? +define_libgreat_module(usb + ${PATH_LIBGREAT_FIRMWARE}/drivers/usb/${LIBGREAT_PLATFORM}/usb.c + ${PATH_LIBGREAT_FIRMWARE}/drivers/usb/${LIBGREAT_PLATFORM}/usb_host.c + ${PATH_LIBGREAT_FIRMWARE}/drivers/usb/${LIBGREAT_PLATFORM}/usb_request.c + ${PATH_LIBGREAT_FIRMWARE}/drivers/usb/${LIBGREAT_PLATFORM}/usb_standard_request.c + ${PATH_LIBGREAT_FIRMWARE}/drivers/usb/${LIBGREAT_PLATFORM}/usb_queue.c + ${PATH_LIBGREAT_FIRMWARE}/drivers/usb/${LIBGREAT_PLATFORM}/usb_queue_host.c +) + +# Allow modules to communicate via the comms protocol and USB. +# TODO: automatically handle dependency management, here? +define_libgreat_module(usb_comms + ${PATH_LIBGREAT_FIRMWARE}/drivers/usb/comms_backend.c +) + +# GPIO module. +# TODO: move to a platform module collection? +define_libgreat_module(gpio + ${PATH_LIBGREAT_FIRMWARE}/drivers/gpio/lpc43xx/gpio.c +) From 2ceb693f733d812ab37e3e3d223b50915a66182f Mon Sep 17 00:00:00 2001 From: "Kate J. Temkin" Date: Mon, 25 Feb 2019 19:45:33 -0700 Subject: [PATCH 04/23] libgreat: temporarily have all modules depend on libopencm3 :( --- firmware/cmake/libgreat.cmake | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/firmware/cmake/libgreat.cmake b/firmware/cmake/libgreat.cmake index 72d4fa9..44a0212 100644 --- a/firmware/cmake/libgreat.cmake +++ b/firmware/cmake/libgreat.cmake @@ -117,6 +117,7 @@ endfunction(add_libgreat_library) # # Function that creates a new libgreat library / source collection iff the relevant library does not exist. + # Arguments: [sources...] # function(add_libgreat_library_if_necessary LIBRARY_NAME) @@ -134,7 +135,11 @@ endfunction(add_libgreat_library_if_necessary) # Arguments: [sources...] # function(define_libgreat_module MODULE_NAME) - add_libgreat_library_if_necessary(libgreat_module_${MODULE_NAME} ${ARGN}) + add_libgreat_library_if_necessary(libgreat_module_${MODULE_NAME} ${ARGN}) + + # FIXME: don't have everything depend on libopencm3 + add_dependencies(libgreat_module_${MODULE_NAME} libopencm3) + endfunction(define_libgreat_module) From ae7fb10bbba6e1913c21351187a4fe9e3483568c Mon Sep 17 00:00:00 2001 From: "Kate J. Temkin" Date: Fri, 1 Mar 2019 19:33:25 -0700 Subject: [PATCH 05/23] cmake: allow compatibility with CMake 3.2+ --- firmware/cmake/compatibility.cmake | 11 +++++++++++ firmware/cmake/libgreat.cmake | 2 +- firmware/cmake/libgreat_prelude.cmake | 3 +++ 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 firmware/cmake/compatibility.cmake diff --git a/firmware/cmake/compatibility.cmake b/firmware/cmake/compatibility.cmake new file mode 100644 index 0000000..dd5cad1 --- /dev/null +++ b/firmware/cmake/compatibility.cmake @@ -0,0 +1,11 @@ +# +# This file is part of libgreat. +# Compatibilty stub that enables us to work down to CMake 3.0. +# + +# Compatibility with older CMake versions. +if (NOT COMMAND target_link_options) + function(target_link_options) + target_link_libraries(${ARGN}) + endfunction() +endif() diff --git a/firmware/cmake/libgreat.cmake b/firmware/cmake/libgreat.cmake index 44a0212..a4bef75 100644 --- a/firmware/cmake/libgreat.cmake +++ b/firmware/cmake/libgreat.cmake @@ -84,7 +84,7 @@ function(add_flash_executable EXECUTABLE_NAME) ${PATH_LIBGREAT}/firmware/platform/${LIBGREAT_PLATFORM}/linker ) - target_link_libraries(${EXECUTABLE_NAME}.elf c nosys ${LINK_LIBRARIES_BOARD} m) + target_link_libraries(${EXECUTABLE_NAME}.elf PRIVATE c nosys ${LINK_LIBRARIES_BOARD} m) # Add a target that creates the final binary. add_custom_target(${EXECUTABLE_NAME} ALL DEPENDS ${EXECUTABLE_NAME}.elf COMMAND ${CMAKE_OBJCOPY} -Obinary ${EXECUTABLE_NAME}.elf ${EXECUTABLE_NAME}) diff --git a/firmware/cmake/libgreat_prelude.cmake b/firmware/cmake/libgreat_prelude.cmake index 8ba0b27..bb10e88 100644 --- a/firmware/cmake/libgreat_prelude.cmake +++ b/firmware/cmake/libgreat_prelude.cmake @@ -22,3 +22,6 @@ set(PATH_LIBGREAT_FIRMWARE ${PATH_LIBGREAT}/firmware) set(PATH_LIBGREAT_FIRMWARE_CMAKE ${PATH_LIBGREAT_FIRMWARE}/cmake) set(PATH_LIBGREAT_FIRMWARE_DRIVERS ${PATH_LIBGREAT_FIRMWARE}/drivers) set(PATH_LIBGREAT_FIRMWARE_PLATFORM ${PATH_LIBGREAT}/firmware/platform/${LIBGREAT_PLATFORM}) + +# CMake compatibility for older CMake versions. +include(${PATH_LIBGREAT_FIRMWARE_CMAKE}/compatibility.cmake) From 7af7c9cf0b67b133ee6db33258a061a31c2631c3 Mon Sep 17 00:00:00 2001 From: "Kate J. Temkin" Date: Fri, 1 Mar 2019 21:39:02 -0700 Subject: [PATCH 06/23] cmake: add more compatibility shims for older cmake versions --- firmware/cmake/compatibility.cmake | 51 +++++++++++++++++++++++++-- firmware/cmake/dfu.cmake | 3 +- firmware/cmake/libgreat_prelude.cmake | 2 -- 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/firmware/cmake/compatibility.cmake b/firmware/cmake/compatibility.cmake index dd5cad1..d64b45b 100644 --- a/firmware/cmake/compatibility.cmake +++ b/firmware/cmake/compatibility.cmake @@ -3,9 +3,56 @@ # Compatibilty stub that enables us to work down to CMake 3.0. # -# Compatibility with older CMake versions. +# Compatibility with older CMake versions: + +# Target link options are called using target_link_libraries in older versions of cmake. if (NOT COMMAND target_link_options) function(target_link_options) target_link_libraries(${ARGN}) - endfunction() + endfunction(target_link_options) +endif() + +# We can't use target_link_directories in older cmake; so we'll manually generate the flags for these directories. +if (NOT COMMAND target_link_directories) + function(target_link_directories TARGET SCOPE) + + # Add each directory to the search path. + foreach (DIRECTORY ${ARGN}) + target_link_options(${TARGET} ${SCOPE} "-L${DIRECTORY}") + endforeach(DIRECTORY) + + endfunction(target_link_directories) +endif() + +# include_guard() is too new; so we'll emulate it for older verseions of cmake. +if (NOT COMMAND include_guard) + macro(include_guard) + + # Detemrine which scope we should use. + if(${ARGN}) + if(${ARGN} STREQUAL "GLOBAL") + set(SCOPE "GLOBAL") + else() + set(SCOPE "DIRECTORY") + endif() + else() + set(SCOPE "VARIABLE") + endif() + + # Check to see if a relevant variable has ever been included in this scope. + set(__filename "${CMAKE_CURRENT_LIST_FILE}") + get_property(already_included ${SCOPE} PROPERTY "pr_${__filename}") + + # If we have been included, abort. + if(already_included) + return() + endif() + + # Set a properly-scoped variable that will indicate that this has already been included. + if("${SCOPE}" STREQUAL "VARIABLE") + set("pr_${__filename}" TRUE) + else() + set_property("${SCOPE}" PROPERTY "pr_${__filename}") + endif() + endmacro(include_guard) endif() diff --git a/firmware/cmake/dfu.cmake b/firmware/cmake/dfu.cmake index 6b1153e..1280aee 100644 --- a/firmware/cmake/dfu.cmake +++ b/firmware/cmake/dfu.cmake @@ -17,9 +17,8 @@ function(add_dfu_executable NEW_EXECUTABLE BASE_EXECUTABLE BASE_LINKER_SCRIPT DF string(REPLACE "${BASE_LINKER_SCRIPT}" "${DFU_LINKER_SCRIPT}" DFU_LINK_OPTIONS "${LINK_OPTIONS}") # Create a new executable that will create the elf representation of the given target. - add_executable(${NEW_EXECUTABLE}.elf) + add_executable(${NEW_EXECUTABLE}.elf ${SOURCES}) set_target_properties(${NEW_EXECUTABLE}.elf PROPERTIES - SOURCES "${SOURCES}" LINK_OPTIONS "${DFU_LINK_OPTIONS}" LINK_DIRECTORIES "${LINK_DIRECTORIES}" LINK_LIBRARIES "${LINK_LIBRARIES}" diff --git a/firmware/cmake/libgreat_prelude.cmake b/firmware/cmake/libgreat_prelude.cmake index bb10e88..6dea26a 100644 --- a/firmware/cmake/libgreat_prelude.cmake +++ b/firmware/cmake/libgreat_prelude.cmake @@ -4,8 +4,6 @@ # # Should be included before the main project() call. # -include_guard() - # If we don't have a platform configuration, this can't be right. if (NOT LIBGREAT_PLATFORM) From 9a494e16713b3f4d6f2ae983d1de77d547b61d39 Mon Sep 17 00:00:00 2001 From: "Kate J. Temkin" Date: Tue, 5 Mar 2019 12:57:47 -0700 Subject: [PATCH 07/23] firmware: merge in libgreat working state ('mega-merge') --- .gitignore | 40 + firmware/CMakeLists.txt | 8 +- firmware/classes/core.c | 28 +- firmware/classes/firmware.c | 10 +- firmware/cmake/libgreat.cmake | 46 +- firmware/cmake/libgreat_prelude.cmake | 9 +- firmware/drivers/ethernet.c | 20 + firmware/drivers/reset.c | 117 + firmware/drivers/timer.c | 133 + firmware/include/drivers/comms_backend.h | 6 +- firmware/include/drivers/ethernet.h | 37 + firmware/include/drivers/reset.h | 52 + firmware/include/drivers/timer.h | 72 + firmware/include/toolchain.h | 64 +- firmware/include/toolchain_clang.h | 15 + firmware/include/toolchain_gcc.h | 12 + firmware/platform/lpc43xx/CMakeLists.txt | 39 +- firmware/platform/lpc43xx/crt0.c | 114 +- .../lpc43xx/drivers/arm_system_control.c | 29 + firmware/platform/lpc43xx/drivers/ethernet.c | 179 ++ .../lpc43xx/drivers}/gpio.c | 0 .../platform/lpc43xx/drivers/platform_clock.c | 2132 +++++++++++++++++ .../lpc43xx/drivers/platform_config.c | 52 + .../platform/lpc43xx/drivers/platform_reset.c | 119 + .../platform/lpc43xx/drivers/platform_timer.c | 161 ++ firmware/platform/lpc43xx/drivers/reset.c | 5 + .../lpc43xx}/drivers/usb/comms_backend.c | 34 +- .../lpc43xx/drivers/usb}/usb.c | 23 +- .../lpc43xx/drivers/usb}/usb_host.c | 14 +- .../lpc43xx/drivers/usb}/usb_host_stack.c | 0 .../lpc43xx/drivers/usb}/usb_queue.c | 4 +- .../lpc43xx/drivers/usb}/usb_queue_host.c | 10 +- .../lpc43xx/drivers/usb}/usb_request.c | 20 +- .../drivers/usb}/usb_standard_request.c | 44 +- .../include/drivers/arm_system_control.h | 117 + .../include/drivers/ethernet/platform.h | 226 ++ .../lpc43xx/include/drivers/platform_clock.h | 596 +++++ .../lpc43xx/include/drivers/platform_config.h | 96 + .../lpc43xx/include}/drivers/platform_gpio.h | 0 .../lpc43xx/include/drivers/platform_reset.h | 168 ++ .../lpc43xx/include/drivers/platform_timer.h | 159 ++ .../include/drivers/usb/comms_backend.h | 0 .../lpc43xx/include/drivers/usb}/usb.h | 0 .../lpc43xx/include/drivers/usb}/usb_host.h | 0 .../include/drivers/usb}/usb_host_stack.h | 0 .../lpc43xx/include/drivers/usb}/usb_queue.h | 0 .../include/drivers/usb}/usb_queue_host.h | 0 .../include/drivers/usb}/usb_registers.h | 0 .../include/drivers/usb}/usb_request.h | 0 .../drivers/usb}/usb_standard_request.h | 0 .../lpc43xx/include/drivers/usb}/usb_type.h | 6 +- .../lpc43xx/include}/platform_sync.h | 0 .../lpc43xx/linker/libgreat_lpc43xx.ld | 16 +- .../linker/libgreat_lpc43xx_rom_to_ram.ld | 11 +- host/pygreat/board.py | 28 +- host/pygreat/comms.py | 11 +- host/pygreat/comms_backends/usb.py | 6 +- 57 files changed, 4873 insertions(+), 215 deletions(-) create mode 100644 .gitignore create mode 100644 firmware/drivers/ethernet.c create mode 100644 firmware/drivers/reset.c create mode 100644 firmware/drivers/timer.c create mode 100644 firmware/include/drivers/ethernet.h create mode 100644 firmware/include/drivers/reset.h create mode 100644 firmware/include/drivers/timer.h create mode 100644 firmware/include/toolchain_clang.h create mode 100644 firmware/include/toolchain_gcc.h create mode 100644 firmware/platform/lpc43xx/drivers/arm_system_control.c create mode 100644 firmware/platform/lpc43xx/drivers/ethernet.c rename firmware/{drivers/gpio/lpc43xx => platform/lpc43xx/drivers}/gpio.c (100%) create mode 100644 firmware/platform/lpc43xx/drivers/platform_clock.c create mode 100644 firmware/platform/lpc43xx/drivers/platform_config.c create mode 100644 firmware/platform/lpc43xx/drivers/platform_reset.c create mode 100644 firmware/platform/lpc43xx/drivers/platform_timer.c create mode 100644 firmware/platform/lpc43xx/drivers/reset.c rename firmware/{ => platform/lpc43xx}/drivers/usb/comms_backend.c (91%) rename firmware/{drivers/usb/lpc43xx => platform/lpc43xx/drivers/usb}/usb.c (98%) rename firmware/{drivers/usb/lpc43xx => platform/lpc43xx/drivers/usb}/usb_host.c (97%) rename firmware/{drivers/usb/lpc43xx => platform/lpc43xx/drivers/usb}/usb_host_stack.c (100%) rename firmware/{drivers/usb/lpc43xx => platform/lpc43xx/drivers/usb}/usb_queue.c (99%) rename firmware/{drivers/usb/lpc43xx => platform/lpc43xx/drivers/usb}/usb_queue_host.c (98%) rename firmware/{drivers/usb/lpc43xx => platform/lpc43xx/drivers/usb}/usb_request.c (92%) rename firmware/{drivers/usb/lpc43xx => platform/lpc43xx/drivers/usb}/usb_standard_request.c (97%) create mode 100644 firmware/platform/lpc43xx/include/drivers/arm_system_control.h create mode 100644 firmware/platform/lpc43xx/include/drivers/ethernet/platform.h create mode 100644 firmware/platform/lpc43xx/include/drivers/platform_clock.h create mode 100644 firmware/platform/lpc43xx/include/drivers/platform_config.h rename firmware/{include/platform/lpc43xx => platform/lpc43xx/include}/drivers/platform_gpio.h (100%) create mode 100644 firmware/platform/lpc43xx/include/drivers/platform_reset.h create mode 100644 firmware/platform/lpc43xx/include/drivers/platform_timer.h rename firmware/{ => platform/lpc43xx}/include/drivers/usb/comms_backend.h (100%) rename firmware/{include/drivers/usb/lpc43xx => platform/lpc43xx/include/drivers/usb}/usb.h (100%) rename firmware/{include/drivers/usb/lpc43xx => platform/lpc43xx/include/drivers/usb}/usb_host.h (100%) rename firmware/{include/drivers/usb/lpc43xx => platform/lpc43xx/include/drivers/usb}/usb_host_stack.h (100%) rename firmware/{include/drivers/usb/lpc43xx => platform/lpc43xx/include/drivers/usb}/usb_queue.h (100%) rename firmware/{include/drivers/usb/lpc43xx => platform/lpc43xx/include/drivers/usb}/usb_queue_host.h (100%) rename firmware/{include/drivers/usb/lpc43xx => platform/lpc43xx/include/drivers/usb}/usb_registers.h (100%) rename firmware/{include/drivers/usb/lpc43xx => platform/lpc43xx/include/drivers/usb}/usb_request.h (100%) rename firmware/{include/drivers/usb/lpc43xx => platform/lpc43xx/include/drivers/usb}/usb_standard_request.h (100%) rename firmware/{include/drivers/usb/lpc43xx => platform/lpc43xx/include/drivers/usb}/usb_type.h (99%) rename firmware/{include/platform/lpc43xx => platform/lpc43xx/include}/platform_sync.h (100%) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8850440 --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +# Compiled output +*.bin +*.d +*.elf +*.hex +*.srec +*.dfu +host/build/ + +# CMakeInternals +CMakeFiles +CMakeCache.txt + +# Compiled python files +__pycache__/ +*.py[cod] +*$py.class +host/dist +*.egg-info + +# Operating system spew +.DS_Store +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Editor junk +*.swp +*.sublime-project +*.sublime-workspace +.vscode +.ccls-cache +compile_commands.json + + +# ctags output +tags + +firmware/*/build diff --git a/firmware/CMakeLists.txt b/firmware/CMakeLists.txt index f1809bd..b10ea2f 100644 --- a/firmware/CMakeLists.txt +++ b/firmware/CMakeLists.txt @@ -12,10 +12,15 @@ add_subdirectory(${PATH_LIBGREAT_FIRMWARE_PLATFORM}) # Include the core libgreat library. add_libgreat_library_if_necessary(libgreat OBJECT - ${PATH_LIBGREAT_FIRMWARE_PLATFORM}/crt0.c + + # Archictecture support. + # TODO: move me to an architecture support package? ${PATH_LIBGREAT_FIRMWARE_PLATFORM}/sync.c ${PATH_LIBGREAT_FIRMWARE_PLATFORM}/sync.S + + ${PATH_LIBGREAT_FIRMWARE_PLATFORM_DRIVERS}/arm_system_control.c ) +use_libgreat_modules(libgreat bsp) # Always include the board support package for the current board. # Provide a basic allocator. define_libgreat_module(allocator @@ -31,6 +36,5 @@ define_libgreat_module(comms ${CMAKE_CURRENT_SOURCE_DIR}/classes/firmware.c ) - # FIXME: get rid of this add_dependencies(libgreat libopencm3) diff --git a/firmware/classes/core.c b/firmware/classes/core.c index d8c5c6d..4858fbd 100644 --- a/firmware/classes/core.c +++ b/firmware/classes/core.c @@ -1,7 +1,7 @@ /* * This file is part of libgreat * - * Core communications class -- provides + * Core communications class -- provides */ #include @@ -17,28 +17,28 @@ extern struct comms_class *class_head; -WEAK int core_verb_read_board_id(struct command_transaction *trans) +ATTR_WEAK int core_verb_read_board_id(struct command_transaction *trans) { (void)trans; return ENOSYS; } -WEAK int core_verb_read_version_string(struct command_transaction *trans) +ATTR_WEAK int core_verb_read_version_string(struct command_transaction *trans) { (void)trans; return ENOSYS; } -WEAK int core_verb_read_part_id(struct command_transaction *trans) +ATTR_WEAK int core_verb_read_part_id(struct command_transaction *trans) { (void)trans; return ENOSYS; } -WEAK int core_verb_read_serial_number(struct command_transaction *trans) +ATTR_WEAK int core_verb_read_serial_number(struct command_transaction *trans) { (void)trans; return ENOSYS; @@ -47,7 +47,7 @@ WEAK int core_verb_read_serial_number(struct command_transaction *trans) /** * TODO: get me out of here! */ -WEAK int core_verb_request_reset(struct command_transaction *trans) +ATTR_WEAK int core_verb_request_reset(struct command_transaction *trans) { (void)trans; return ENOSYS; @@ -56,14 +56,14 @@ WEAK int core_verb_request_reset(struct command_transaction *trans) /** * Internal introspection command that returns the list of supported classes. - */ + */ static int verb_get_available_classes(struct command_transaction *trans) { struct comms_class *cls; // Add each class number to the list. for (cls = class_head; cls; cls = cls->next) { - comms_response_add_uint32_t(trans, cls->class_number); + comms_response_add_uint32_t(trans, cls->class_number); } return 0; @@ -72,7 +72,7 @@ static int verb_get_available_classes(struct command_transaction *trans) /** * Internal introspection command that returns the list of supported classes. - */ + */ static int verb_get_verb_name(struct command_transaction *trans) { uint32_t class_number = comms_argument_parse_uint32_t(trans); @@ -110,7 +110,7 @@ enum verb_descriptor_request { /** * Internal introspection command that returns information about a verb. - */ + */ static int verb_get_verb_descriptor(struct command_transaction *trans) { uint32_t class_number = comms_argument_parse_uint32_t(trans); @@ -155,7 +155,7 @@ static int verb_get_verb_descriptor(struct command_transaction *trans) /** * Internal introspection command that returns the list of verbs for a given class. - */ + */ static int verb_get_available_verbs(struct command_transaction *trans) { uint32_t class_number = comms_argument_parse_uint32_t(trans); @@ -171,7 +171,7 @@ static int verb_get_available_verbs(struct command_transaction *trans) // Iterate through the array of command verbs, adding them to our response. for (verb = relevant_class->command_verbs; verb->handler; ++verb) { - comms_response_add_uint32_t(trans, verb->verb_number); + comms_response_add_uint32_t(trans, verb->verb_number); } return 0; @@ -181,7 +181,7 @@ static int verb_get_available_verbs(struct command_transaction *trans) /** * Internal introspection command that returns the name for a given class. - */ + */ static int verb_get_class_name(struct command_transaction *trans) { uint32_t class_number = comms_argument_parse_uint32_t(trans); @@ -203,7 +203,7 @@ static int verb_get_class_name(struct command_transaction *trans) /** * Internal introspection command that returns the documentation for a given class. - */ + */ static int verb_get_class_docs(struct command_transaction *trans) { uint32_t class_number = comms_argument_parse_uint32_t(trans); diff --git a/firmware/classes/firmware.c b/firmware/classes/firmware.c index bce8316..93b90ce 100644 --- a/firmware/classes/firmware.c +++ b/firmware/classes/firmware.c @@ -28,7 +28,7 @@ * - a uint32_t that indicates the device's page size in bytes. * - a uint32_t that indicates the device's total size, in bytes */ -WEAK int firmware_verb_initialize(struct command_transaction *trans) +ATTR_WEAK int firmware_verb_initialize(struct command_transaction *trans) { (void)trans; return ENOSYS; @@ -40,7 +40,7 @@ WEAK int firmware_verb_initialize(struct command_transaction *trans) * * Accepts no arguments. */ -WEAK int firmware_verb_full_erase(struct command_transaction *trans) +ATTR_WEAK int firmware_verb_full_erase(struct command_transaction *trans) { (void)trans; return ENOSYS; @@ -51,7 +51,7 @@ WEAK int firmware_verb_full_erase(struct command_transaction *trans) * * Accepts a uint32_t that indicates the address. */ -WEAK int firmware_verb_erase_page(struct command_transaction *trans) +ATTR_WEAK int firmware_verb_erase_page(struct command_transaction *trans) { (void)trans; return ENOSYS; @@ -63,7 +63,7 @@ WEAK int firmware_verb_erase_page(struct command_transaction *trans) * * Accepts a uint32_t that is the address; followed by a single page of data. */ -WEAK int firmware_verb_write_page(struct command_transaction *trans) +ATTR_WEAK int firmware_verb_write_page(struct command_transaction *trans) { (void)trans; return ENOSYS; @@ -76,7 +76,7 @@ WEAK int firmware_verb_write_page(struct command_transaction *trans) * Accepts a uint32_t that is the address. * Returns the relevant page. */ -WEAK int firmware_verb_read_page(struct command_transaction *trans) +ATTR_WEAK int firmware_verb_read_page(struct command_transaction *trans) { (void)trans; return ENOSYS; diff --git a/firmware/cmake/libgreat.cmake b/firmware/cmake/libgreat.cmake index a4bef75..551574b 100644 --- a/firmware/cmake/libgreat.cmake +++ b/firmware/cmake/libgreat.cmake @@ -22,43 +22,6 @@ function(generate_linker_script_arguments OUTPUT_VARIABLE) set("${OUTPUT_VARIABLE}" "${ARGUMENTS}" PARENT_SCOPE) endfunction() -# -# Function that creates a new GreatFET library / source archive. -# Arguments: [library_name] [sources...] -# -function(add_libgreat_library LIBRARY_NAME) - - # Create the relevant library. - add_library(${LIBRARY_NAME} ${ARGN}) - - # And set its default properties. - target_include_directories(${LIBRARY_NAME} PRIVATE - ${BOARD_INCLUDE_DIRECTORIES} - ${BUILD_INCLUDE_DIRECTORIES} - ${PATH_LIBOPENCM3}/include - ${PATH_GREATFET_FIRMWARE_COMMON} - ${PATH_LIBGREAT}/firmware/include - ${PATH_LIBGREAT}/firmware/include/platform/${LIBGREAT_PLATFORM} - ) - target_compile_options(${LIBRARY_NAME} PRIVATE ${FLAGS_COMPILE_COMMON} ${FLAGS_ARCHITECTURE} ${FLAGS_MAIN_CPU}) - target_compile_definitions(${LIBRARY_NAME} PRIVATE ${DEFINES_COMMON} ${DEFINES_BOARD}) - -endfunction(add_libgreat_library) - - -# -# Function that creates a new GreatFET library / source archive iff the relevant library does not exist. -# Arguments: [library_name] [sources...] -# -function(add_libgreat_library_if_necessary LIBRARY_NAME) - - # If the target doesn't already exist, create it. - if (NOT TARGET ${LIBRARY_NAME}) - add_libgreat_library(${LIBRARY_NAME} ${ARGN}) - endif() - -endfunction(add_libgreat_library_if_necessary) - # # Function that adds a flash "executable" target to the currrent build. @@ -107,18 +70,16 @@ function(add_libgreat_library LIBRARY_NAME) ${PATH_LIBOPENCM3}/include ${PATH_GREATFET_FIRMWARE_COMMON} #XXX: remove this! ${PATH_LIBGREAT_FIRMWARE}/include - ${PATH_LIBGREAT}/firmware/include/platform/${LIBGREAT_PLATFORM} + ${PATH_LIBGREAT_FIRMWARE_PLATFORM}/include ) target_compile_options(${LIBRARY_NAME} PRIVATE ${FLAGS_COMPILE_COMMON} ${FLAGS_ARCHITECTURE} ${FLAGS_MAIN_CPU}) target_compile_definitions(${LIBRARY_NAME} PRIVATE ${DEFINES_COMMON} ${DEFINES_BOARD}) endfunction(add_libgreat_library) - # -# Function that creates a new libgreat library / source collection iff the relevant library does not exist. - -# Arguments: [sources...] +# Function that creates a new GreatFET library / source archive iff the relevant library does not exist. +# Arguments: [library_name] [sources...] # function(add_libgreat_library_if_necessary LIBRARY_NAME) @@ -130,6 +91,7 @@ function(add_libgreat_library_if_necessary LIBRARY_NAME) endfunction(add_libgreat_library_if_necessary) + # # Function that provides a configurable libgreat feature, which can be optionally included in a given consume. # Arguments: [sources...] diff --git a/firmware/cmake/libgreat_prelude.cmake b/firmware/cmake/libgreat_prelude.cmake index 6dea26a..02d841d 100644 --- a/firmware/cmake/libgreat_prelude.cmake +++ b/firmware/cmake/libgreat_prelude.cmake @@ -16,10 +16,11 @@ endif() # Libgreat paths. # TODO: move most of these to a libgreat.cmake? -set(PATH_LIBGREAT_FIRMWARE ${PATH_LIBGREAT}/firmware) -set(PATH_LIBGREAT_FIRMWARE_CMAKE ${PATH_LIBGREAT_FIRMWARE}/cmake) -set(PATH_LIBGREAT_FIRMWARE_DRIVERS ${PATH_LIBGREAT_FIRMWARE}/drivers) -set(PATH_LIBGREAT_FIRMWARE_PLATFORM ${PATH_LIBGREAT}/firmware/platform/${LIBGREAT_PLATFORM}) +set(PATH_LIBGREAT_FIRMWARE ${PATH_LIBGREAT}/firmware) +set(PATH_LIBGREAT_FIRMWARE_CMAKE ${PATH_LIBGREAT_FIRMWARE}/cmake) +set(PATH_LIBGREAT_FIRMWARE_DRIVERS ${PATH_LIBGREAT_FIRMWARE}/drivers) +set(PATH_LIBGREAT_FIRMWARE_PLATFORM ${PATH_LIBGREAT_FIRMWARE}/platform/${LIBGREAT_PLATFORM}) +set(PATH_LIBGREAT_FIRMWARE_PLATFORM_DRIVERS ${PATH_LIBGREAT_FIRMWARE_PLATFORM}/drivers) # CMake compatibility for older CMake versions. include(${PATH_LIBGREAT_FIRMWARE_CMAKE}/compatibility.cmake) diff --git a/firmware/drivers/ethernet.c b/firmware/drivers/ethernet.c new file mode 100644 index 0000000..a046d8b --- /dev/null +++ b/firmware/drivers/ethernet.c @@ -0,0 +1,20 @@ +/* + * This file is part of libgreat + * + * General Ethernet Complex drivers + */ + +#include + + +/** + * Initialies a new ethernet controller object, and readies it (and the appropriate) + * hardware for use. + * + * @param An unpopulated ethernet device structure to be readied for use. + */ +void ethernet_init(ethernet_controller_t *device) +{ + // Perform the core low-level initialization for the ethernet controller. + platform_ethernet_init(device); +} diff --git a/firmware/drivers/reset.c b/firmware/drivers/reset.c new file mode 100644 index 0000000..1da980e --- /dev/null +++ b/firmware/drivers/reset.c @@ -0,0 +1,117 @@ +/* + * This file is part of libgreat. + * + * System reset driver functionality. + */ + +#include +#include +#include + +/* This special variable is preserved across soft resets by a little bit of + * reset handler magic. It allows us to pass a Reason across resets. */ +static volatile uint32_t reset_reason ATTR_PERSISTENT; + +/* Reset reason from the last iteration. */ +static uint32_t last_reset_reason = 0; + + +/** + * Set up our system reset driver. + */ +void reset_driver_initialize(void) +{ + // Store the reset reason we gathered from the last iteration. + last_reset_reason = reset_reason; + reset_reason = RESET_REASON_UNKNOWN; + + // If all memory seems to be cleared out / corrupted, this was likely a power cycle; + // set the last reset reason to "power cycle". + if (!system_persistent_memory_likely_intact()) { + last_reset_reason = RESET_REASON_POWER_CYCLE; + } + + // Let the platform handle any initialization it needs to + platform_initialize_reset_driver(); +} +CALL_ON_PREINIT(reset_driver_initialize); + + +/** + * @return true iff the system's memory seems likely ot have preserved its value since a prior operation + */ +bool system_persistent_memory_likely_intact(void) +{ + // If we've already overwritten the reset reason, use our stored one; otherewise use our persistent value directly. + // This ensures that we return a reasonable value no matter if we're called before or after our driver is initialized. + uint32_t reset_reason_to_use = (reset_reason == RESET_REASON_UNKNOWN) ? last_reset_reason : reset_reason; + + // Check if the semaphore bits in the reset reason were set to our "known valid" mask -- this gives us a high + // probaility that we explicility set this in a previous iteration. + return (reset_reason_to_use & RESET_MEMORY_LIKELY_VALID_MASK) == RESET_MEMORY_LIKELY_VALID_MASK; +} + + +/** + * @return a string describing the reason for the system's reset + */ +const char *system_get_reset_reason_string(void) +{ + // If we've already overwritten the reset reason, use our stored one; otherewise use our persistent value directly. + // This ensures that we return a reasonable value no matter if we're called before or after our driver is initialized. + uint32_t reset_reason_to_use = (reset_reason == RESET_REASON_UNKNOWN) ? last_reset_reason : reset_reason; + + switch (reset_reason_to_use) { + case RESET_REASON_POWER_CYCLE: + return "power cycle"; + case RESET_REASON_SOFT_RESET: + return "software reset"; + case RESET_REASON_USE_EXTCLOCK: + return "reset to switch to external clock"; + case RESET_REASON_FAULT: + return "fault-induced reset"; + case RESET_REASON_WATCHDOG_TIMEOUT: + return "watchdog timeout"; + case RESET_REASON_NEW_FIRMWARE: + return "firmware re-flash."; + default: + if (system_persistent_memory_likely_intact()) { + return "unknown (non-power-cycle) reset"; + } else { + return "hard reset / power cycle"; + } + } +} + + + +/** + * @return a constant indicating the reason for the last reset, if known + */ +reset_reason_t system_reset_reason(void) +{ + // If our persistent memory is likely intact, we can use its reset reason. + if (system_persistent_memory_likely_intact()) { + return last_reset_reason; + } + + // Otherewise, we don't know why the system was reset; indicate so. + return RESET_REASON_UNKNOWN; +} + + +/** + * Resets the entire system. + * + * @param reason The reset reason to report. + * @param include_always_on_domain True iff the always-on power domain should be included in the given reset. + */ +ATTR_NORETURN void system_reset(reset_reason_t reason, bool include_always_on_domain) +{ + reset_reason = reason; + platform_software_reset(include_always_on_domain); + + while(1); +} + + diff --git a/firmware/drivers/timer.c b/firmware/drivers/timer.c new file mode 100644 index 0000000..1ded0c7 --- /dev/null +++ b/firmware/drivers/timer.c @@ -0,0 +1,133 @@ +/* + * This file is part of libgreat + * + * Core system timer drivers + */ + +// Temporary debug. +#define LOCAL_FILE_OVERRIDE_LOGLEVEL + +#include +#include + + +/** + * Initializes a timer peripheral. + * + * @param timer The timer object to be initialized. + * @param index The number of the timer to be set up. + */ +void timer_initialize(timer_t *timer, timer_index_t index) +{ + timer->number = index; + + // Perform platform-specific timer initialization. + platform_timer_initialize(timer, index); +} + + +/** + * Enables the given timer and sets it to tick at a given frequency. + */ +void timer_enable(timer_t *timer, uint32_t tick_frequency) +{ + // Store the timer's frequency, for later use. + timer->frequency = tick_frequency; + + // Set the timer up, and enable it. + platform_timer_set_frequency(timer, tick_frequency); + platform_timer_enable(timer); +} + + +/** + * @returns the current counter value of the given timer + */ +uint32_t timer_get_value(timer_t *timer) +{ + return platform_timer_get_value(timer); +} + + +/** + * Function that must be called whenever the clock driving the given timer experiences a change in frequency. + * This allows the timer to automatically recompute its period. There may be some loss of ticks during the clock + * frequency change. + */ +void timer_handle_clock_frequency_change(timer_t * timer) +{ + // Update the timer's internal frequency. + platform_timer_set_frequency(timer, timer->frequency); +} + + +/** + * Initialization function for the platform microsecond timer, which is used + * to track runtime microseconds. + */ +void set_up_platform_timers(void) +{ + timer_t *timer = platform_set_up_platform_timer(); + + // Enable the timer, with a frequency of a millisecond. + timer_enable(timer, 1000000UL); +} + + +/** + * @returns the total number of microseconds since this timer was initialized. + * + * Overflows roughly once per hour. For tracking longer spans; use the RTC + * functions, which are currently not synchronized to this one. + */ +uint32_t get_time(void) +{ + // Return the value on the platform timer. + timer_t *timer = platform_get_platform_timer(); + return timer_get_value(timer); +} + + +/** + * @returns The total number of microseconds that have passed since a reference call to get_time(). + * Useful for computing timeouts. + */ +uint32_t get_time_since(uint32_t base) +{ + return get_time() - base; +} + + +/** + * Function that should be called whenever the platform timer's basis changes. + * // FIXME: remove this! + */ +void handle_platform_timer_frequency_change(void) +{ + timer_t *platform_timer = platform_get_platform_timer(); + + if (!platform_timer) { + return; + } + + timer_handle_clock_frequency_change(platform_timer); +} + + +/** + * Blocks execution for the provided number of microseconds. + */ +void delay_us(uint32_t duration) +{ + if (!platform_get_platform_timer()) + { + pr_critical("critical: tried to get the platform timer before it was up!\n"); + while(1); + } + + uint32_t time_base = get_time(); + while(get_time_since(time_base) < duration); +} + + + diff --git a/firmware/include/drivers/comms_backend.h b/firmware/include/drivers/comms_backend.h index 16d3bc2..bd2ec62 100644 --- a/firmware/include/drivers/comms_backend.h +++ b/firmware/include/drivers/comms_backend.h @@ -5,8 +5,8 @@ * the standard communications protocol. */ -#include -#include +#include +#include #include /** @@ -35,7 +35,7 @@ struct ATTR_PACKED libgreat_command_prelude { * @param trans An object representing the command to be submitted, and its * response. */ -int comms_backend_submit_command(struct comm_backend_driver *backend, +int comms_backend_submit_command(struct comm_backend_driver *backend, struct command_transaction *trans); diff --git a/firmware/include/drivers/ethernet.h b/firmware/include/drivers/ethernet.h new file mode 100644 index 0000000..ccee530 --- /dev/null +++ b/firmware/include/drivers/ethernet.h @@ -0,0 +1,37 @@ +/* + * This file is part of libgreat + * + * Generic Ethernet drivers + */ + +#ifndef __LIBGREAT_ETHERNET_H__ +#define __LIBGREAT_ETHERNET_H__ + +#include +#include + +/** + * Data structure storing state for an ethernet controller. + */ +typedef struct ethernet_controller { + + // Reference to the (platform-specific) register block that controls the + // system's ethernet controller. + ethernet_register_block_t *reg; + + // Platform-specific data. + ethernet_platform_data_t platform; + + +} ethernet_controller_t; + + +/** + * Initialies a new ethernet controller, readying it for use. + */ +void ethernet_init(ethernet_controller_t *device); + + + + +#endif diff --git a/firmware/include/drivers/reset.h b/firmware/include/drivers/reset.h new file mode 100644 index 0000000..e4491ed --- /dev/null +++ b/firmware/include/drivers/reset.h @@ -0,0 +1,52 @@ +/* + * This file is part of libgreat. + * + * System reset driver functionality. + */ + +#ifndef __LIBGREAT_RESET_H__ +#define __LIBGREAT_RESET_H__ + +#include + +typedef enum { + // Keep these unique, so the RAM is unlikely to settle into these on first + // boot. + RESET_REASON_UNKNOWN = 0xAA55FF00, + RESET_REASON_SOFT_RESET = 0xAA55FF01, + RESET_REASON_FAULT = 0xAA55FF02, + RESET_REASON_POWER_CYCLE = 0xAA55FF03, + RESET_REASON_WATCHDOG_TIMEOUT = 0xAA55FF04, + RESET_REASON_NEW_FIRMWARE = 0xAA55FF05, + RESET_REASON_USE_EXTCLOCK = 0xAA55CCDD, + + RESET_REASON_LIKELY_VALID_MASK = 0xAA550000, + RESET_MEMORY_LIKELY_VALID_MASK = 0xAA550000, +} reset_reason_t; + + +/** + * @return true iff the system's memory seems likely ot have preserved its value since a prior operation + */ +bool system_persistent_memory_likely_intact(void); + + +/** + * @return a constant indicating the reason for the last reset, if known + */ +reset_reason_t system_reset_reason(void); + +/** + * Resets the entire system. + * + * @param reason The reset reason to report. + * @param include_always_on_domain True iff the always-on power domain should be included in the given reset. + */ +void system_reset(reset_reason_t reason, bool include_always_on_domain); + +/** + * @return a string describing the reason for the system's reset + */ +const char *system_get_reset_reason_string(void); + +#endif diff --git a/firmware/include/drivers/timer.h b/firmware/include/drivers/timer.h new file mode 100644 index 0000000..7f8a404 --- /dev/null +++ b/firmware/include/drivers/timer.h @@ -0,0 +1,72 @@ +/* + * This file is part of libgreat + * + * Core system timer drivers + */ + +#ifndef __LIBGREAT_TIMER_H__ +#define __LIBGREAT_TIMER_H__ + +#include + +/** + * Struct representing a timer peripheral. + */ +typedef struct timer { + + // The timer number for the given timer. + timer_index_t number; + + // Reference to the timer control register bank. + platform_timer_registers_t *reg; + + // Timer's frequency, in Hz. + // This is the frequency at which the timer counts. + uint32_t frequency; + +} timer_t; + + +/** + * Initializes a timer peripheral. Does not configure or enable the timer. + * + * @param timer The timer object to be initialized. + * @param index The number of the timer to be set up. Platform-specific, but usually a 0-indexed integer. + */ +void timer_initialize(timer_t *timer, timer_index_t index); + +/** + * Initialization function for the platform microsecond timer, which is used + * to track runtime microseconds. + */ +void set_up_platform_timers(void); + + +/** + * Blocks execution for the provided number of microseconds. + */ +void delay_us(uint32_t duration); + +/** + * @returns the total number of microseconds since this timer was initialized. + * + * Overflows roughly once per hour. For tracking longer spans; use the RTC + * functions, which are currently not synchronized to this one. + */ +uint32_t get_time(void); + + +/** + * @returns The total number of microseconds that have passed since a reference call to get_time(). + * Useful for computing timeouts. + */ +uint32_t get_time_since(uint32_t base); + + +/** + * Function that should be called whenever the platform timer's basis changes. + * FIXME: remove this! + */ +void handle_platform_timer_frequency_change(void); + +#endif diff --git a/firmware/include/toolchain.h b/firmware/include/toolchain.h index 4b877a7..9b8837f 100644 --- a/firmware/include/toolchain.h +++ b/firmware/include/toolchain.h @@ -4,14 +4,31 @@ * Toolchain helper functions. */ +#ifndef __LIBGREAT_TOOLCHAIN_H__ +#define __LIBGREAT_TOOLCHAIN_H__ + +#include +#include +#include +#include /** * Generic attribute wrappers. */ -#define ATTR_PACKED __attribute__((packed)) -#define ATTR_ALIGNED(x) __attribute__ ((aligned(x))) -#define ATTR_SECTION(x) __attribute__ ((section(x))) -#define WEAK __attribute__((weak)) +#define ATTR_PACKED __attribute__((packed)) +#define ATTR_ALIGNED(x) __attribute__((aligned(x))) //FIXME: use alignas? +#define ATTR_SECTION(x) __attribute__((section(x))) +#define ATTR_WEAK __attribute__((weak)) +#define ATTR_NORETURN __attribute__((noreturn)) +#define ATTR_PRINTF __attribute__((format (printf, 1, 2))) +#define ATTR_PRINTF_N(n) __attribute__((format (printf, n, n + 1))) + + +/** + * Attribute helpers for variables that should persist across a reset. + */ +#define ATTR_PERSISTENT ATTR_SECTION(".bss.persistent") + /** * Macros for populating the preinit_array and init_array @@ -21,3 +38,42 @@ __attribute__((section(".preinit_array"), used)) static typeof(preinit) *preinit##_initcall_p = preinit; #define CALL_ON_INIT(init) \ __attribute__((section(".init_array"), used)) static typeof(init) *init##_initcall_p = init; + +/* Macros for populating our finializers. */ +#define CALL_BEFORE_RESET(fini) \ + __attribute__((section(".fini_array"), used)) static typeof(init) *fini##_finalizer_p = fini; + + +/** + * Compile-time error detection. + */ + +// Compatibility stub for things that don't support static_assert. +// Ignore these assertions entirely. +#ifndef static_assert +#define static_assert(condition, msg) +#endif + +// Helpers for validating struct member alignment. +#define ASSERT_OFFSET(structure, member, offset) \ + static_assert(offsetof(structure, member) == offset, #member " is not at offset " #offset " in struct " #structure) + + +#define _CONCAT_TOKENS(a, b) a##b +#define _CONCAT(a, b) _CONCAT_TOKENS(a, b) + +/** + * Helpers for defining structs that match hardware register layouts. + */ +#define RESERVED_BYTES(n) uint8_t _CONCAT(reserved, __LINE__) [n] +#define RESERVED_WORDS(n) uint32_t _CONCAT(reserved, __LINE__)[n] + +#ifdef __clang__ +#include +#endif + +#ifdef __GNUC__ +#include +#endif + +#endif diff --git a/firmware/include/toolchain_clang.h b/firmware/include/toolchain_clang.h new file mode 100644 index 0000000..57904c7 --- /dev/null +++ b/firmware/include/toolchain_clang.h @@ -0,0 +1,15 @@ +/* + * This file is part of libgreat + * + * Toolchain helper functions. + */ + +#ifndef __LIBGREAT_TOOLCHAIN_COMPILER_H__ +#define __LIBGREAT_TOOLCHAIN_COMPILER_H__ + +#ifndef PRIu32 +#define PRIu32 "u" +#define PRIx32 "x" +#endif + +#endif diff --git a/firmware/include/toolchain_gcc.h b/firmware/include/toolchain_gcc.h new file mode 100644 index 0000000..3462dba --- /dev/null +++ b/firmware/include/toolchain_gcc.h @@ -0,0 +1,12 @@ +/* + * This file is part of libgreat + * + * Toolchain helper functions. + */ + +#ifndef __LIBGREAT_TOOLCHAIN_COMPILER_H__ +#define __LIBGREAT_TOOLCHAIN_COMPILER_H__ + +#include + +#endif diff --git a/firmware/platform/lpc43xx/CMakeLists.txt b/firmware/platform/lpc43xx/CMakeLists.txt index 4a422c6..bf2208d 100644 --- a/firmware/platform/lpc43xx/CMakeLists.txt +++ b/firmware/platform/lpc43xx/CMakeLists.txt @@ -13,25 +13,46 @@ include(../../cmake/libgreat.cmake) # in other branches, but given how we've chosen to rebase we won't get this for a few more commits. # +# Core board support. +define_libgreat_module(bsp + + # Start of day code. + ${PATH_LIBGREAT_FIRMWARE_PLATFORM}/crt0.c + + # Timers. + ${PATH_LIBGREAT_FIRMWARE_DRIVERS}/timer.c + ${PATH_LIBGREAT_FIRMWARE_PLATFORM_DRIVERS}/platform_timer.c + + # Platform configuration. + ${PATH_LIBGREAT_FIRMWARE_PLATFORM_DRIVERS}/platform_config.c + + # Reset control. + ${PATH_LIBGREAT_FIRMWARE_DRIVERS}/reset.c + ${PATH_LIBGREAT_FIRMWARE_PLATFORM_DRIVERS}/platform_reset.c + + # Clock control / generation. + ${PATH_LIBGREAT_FIRMWARE_PLATFORM_DRIVERS}/platform_clock.c +) + + # Provide a USB driver stack. -# FIXME: move these into a seperate cmake for the platform? define_libgreat_module(usb - ${PATH_LIBGREAT_FIRMWARE}/drivers/usb/${LIBGREAT_PLATFORM}/usb.c - ${PATH_LIBGREAT_FIRMWARE}/drivers/usb/${LIBGREAT_PLATFORM}/usb_host.c - ${PATH_LIBGREAT_FIRMWARE}/drivers/usb/${LIBGREAT_PLATFORM}/usb_request.c - ${PATH_LIBGREAT_FIRMWARE}/drivers/usb/${LIBGREAT_PLATFORM}/usb_standard_request.c - ${PATH_LIBGREAT_FIRMWARE}/drivers/usb/${LIBGREAT_PLATFORM}/usb_queue.c - ${PATH_LIBGREAT_FIRMWARE}/drivers/usb/${LIBGREAT_PLATFORM}/usb_queue_host.c + ${PATH_LIBGREAT_FIRMWARE_PLATFORM_DRIVERS}/usb/usb.c + ${PATH_LIBGREAT_FIRMWARE_PLATFORM_DRIVERS}/usb/usb_host.c + ${PATH_LIBGREAT_FIRMWARE_PLATFORM_DRIVERS}/usb/usb_request.c + ${PATH_LIBGREAT_FIRMWARE_PLATFORM_DRIVERS}/usb/usb_standard_request.c + ${PATH_LIBGREAT_FIRMWARE_PLATFORM_DRIVERS}/usb/usb_queue.c + ${PATH_LIBGREAT_FIRMWARE_PLATFORM_DRIVERS}/usb/usb_queue_host.c ) # Allow modules to communicate via the comms protocol and USB. # TODO: automatically handle dependency management, here? define_libgreat_module(usb_comms - ${PATH_LIBGREAT_FIRMWARE}/drivers/usb/comms_backend.c + ${PATH_LIBGREAT_FIRMWARE_PLATFORM_DRIVERS}/usb/comms_backend.c ) # GPIO module. # TODO: move to a platform module collection? define_libgreat_module(gpio - ${PATH_LIBGREAT_FIRMWARE}/drivers/gpio/lpc43xx/gpio.c + ${PATH_LIBGREAT_FIRMWARE_PLATFORM_DRIVERS}/gpio.c ) diff --git a/firmware/platform/lpc43xx/crt0.c b/firmware/platform/lpc43xx/crt0.c index 9e11a4c..81795f3 100644 --- a/firmware/platform/lpc43xx/crt0.c +++ b/firmware/platform/lpc43xx/crt0.c @@ -4,27 +4,25 @@ * C Runtime 0: start of day code for the LPC4330 */ - -#include -#include -#include -#include -#include -#include -#include - +// FIXME: get rid of all of these #include +#include +#include + +#include /* This special variable is preserved across soft resets by a little bit of * reset handler magic. It allows us to pass a Reason across resets. */ /* FIXME: use sections to do this instead of the below */ /* FIXME: make this static and provide an accessor, somewhere? */ /* FIXME: move this out of the crt0 and to its own driver? */ -volatile uint32_t reset_reason ATTR_SECTION(".bss.persistent"); +volatile uint32_t reset_reason ATTR_PERSISTENT; - -void main(void); +/** + * Define the main function, which allows us to refer to the user program. + */ +int main(void); /** * Section start and end markers for the constructor and @@ -40,83 +38,115 @@ extern funcp_t __fini_array_start, __fini_array_end; * Provided by the linker. */ extern unsigned _data_loadaddr, _data, _edata, _bss, _ebss, _stack; -extern unsigned _etext_ram, _text_ram, _etext_rom; +extern unsigned _text_segment_ram, _text_segment_rom; +extern unsigned _text_segment_end, _text_segment_rom_end, _text_segment_ram_end; /** * Function to be called before main, but after an initializers. */ -static void _relocate_to_ram(void) +static void relocate_to_ram(void) { - volatile unsigned *src, *dest; + volatile unsigned *load_source, *load_destination; + volatile unsigned *load_target = &_text_segment_ram; + + /* If we need to relocate, relocate. */ + if (&_text_segment_ram != &_text_segment_rom) { + + // Figure out the location that we're relocating from. + load_source = &_text_segment_rom_end - (&_text_segment_ram_end - load_target); - /* Copy the code from ROM to Real RAM (if enabled) */ - if ((&_etext_ram-&_text_ram) > 0) { - src = &_etext_rom-(&_etext_ram-&_text_ram); /* Change Shadow memory to ROM (for Debug Purpose in case Boot * has not set correctly the M4MEMMAP because of debug) */ - CREG_M4MEMMAP = (unsigned long)src; + platform_remap_address_zero(load_source); - for (dest = &_text_ram; dest < &_etext_ram; ) { - *dest++ = *src++; + // FIXME: make this a memcpy + for (load_destination = load_target; load_destination < &_text_segment_ram_end;) { + *load_destination = *load_source; + + load_source++; + load_destination++; } /* Change Shadow memory to Real RAM */ - CREG_M4MEMMAP = (unsigned long)&_text_ram; + platform_remap_address_zero(&_text_segment_ram); /* Continue Execution in RAM */ } - - /* Enable access to Floating-Point coprocessor. */ - SCB_CPACR |= SCB_CPACR_FULL * (SCB_CPACR_CP10 | SCB_CPACR_CP11); } extern unsigned int debug_read_index; extern unsigned int debug_write_index; + +/** + * Prepare the system's CPU for use. + */ +void set_up_cpu(void) +{ + // Enable access to the system's FPUs. + arch_enable_fpu(true); + + // Enable the early clocks necessary for basic functionality. + platform_initialize_early_clocks(); +} + + /** * Startup code for the processor and general initialization. */ -void __attribute__ ((naked)) reset_handler(void) +void ATTR_NORETURN reset_handler(void) { volatile unsigned *src, *dest; funcp_t *fp; - for (src = &_data_loadaddr, dest = &_data; - dest < &_edata; - src++, dest++) { + // Initialize the systems's data segment. + for (src = &_data_loadaddr, dest = &_data; dest < &_edata; src++, dest++) { *dest = *src; } + // Clear the system's BSS. for (dest = &_bss; dest < &_ebss; ) { *dest++ = 0; } - /* - * Begin executing the program from RAM, instead of - * ROM, if desired. This improvides performance, as we - * don't have to keep fetching over spifi. - * - * TODO: provide an XIP mechanism that allows us to skip - * this if we do want to run from spifi - */ - _relocate_to_ram(); - - /* Constructors. */ + // Configure the CPU into its full running state. + set_up_cpu(); + + // Begin executing the program from RAM, instead of + // ROM, if desired. This improvides performance, as we + // don't have to keep fetching over SPIFI. + relocate_to_ram(); + + // Initilize the bare-bones early clocks. + platform_initialize_early_clocks(); + + // Extremely early pre-init. This section is for initializer that + // should run very early -- before we've even fully brought up the CPU clocking scheme. + // This is for the very basics of getting our platform up and running. for (fp = &__preinit_array_start; fp < &__preinit_array_end; fp++) { (*fp)(); } + + // With the pre-init complete, we're ready to begin platform initialization. + // First, we'll perform the few steps that pivot us from early startup to + // fully capable of using the hardware. + platform_initialize_clocks(); + + // Run each of the initializers. for (fp = &__init_array_start; fp < &__init_array_end; fp++) { (*fp)(); } - /* Call the application's entry point. */ + // Call the application's entry point. main(); - /* Destructors. */ + // Run any destructors on the platform. for (fp = &__fini_array_start; fp < &__fini_array_end; fp++) { (*fp)(); } + // TODO: trigger a system reset. + while (1); } diff --git a/firmware/platform/lpc43xx/drivers/arm_system_control.c b/firmware/platform/lpc43xx/drivers/arm_system_control.c new file mode 100644 index 0000000..f939501 --- /dev/null +++ b/firmware/platform/lpc43xx/drivers/arm_system_control.c @@ -0,0 +1,29 @@ +/* + * This file is part of libgreat. + * + * ARM system control drivers. + */ + +#include + +/** + * @return a reference to the ARM SCB. + */ +arm_system_control_register_block_t *arch_get_system_control_registers(void) +{ + return (arm_system_control_register_block_t *)0xE000ED00; +} + + +/** + * Enables access to the system's FPU. + * + * @param allow_unprivileged_access True iff user-mode should be able to use the FPU. + */ +void arch_enable_fpu(bool allow_unprivileged_access) +{ + arm_system_control_register_block_t *scb = arch_get_system_control_registers(); + fpu_access_rights_t access = allow_unprivileged_access ? FPU_FULL_ACCESS : FPU_PRIVILEGED_ONLY; + + scb->cpacr.fpu_access = access; +} diff --git a/firmware/platform/lpc43xx/drivers/ethernet.c b/firmware/platform/lpc43xx/drivers/ethernet.c new file mode 100644 index 0000000..b08c02e --- /dev/null +++ b/firmware/platform/lpc43xx/drivers/ethernet.c @@ -0,0 +1,179 @@ +/* + * This file is part of libgreat + * + * LPC43xx Ethernet Complex drivers + */ + + +#include + +#include +#include + + +/** + * @return a reference to the LPC43xx's ethernet registers + */ +static ethernet_register_block_t *get_ethernet_register_block(void) +{ + return (ethernet_register_block_t *)0x40010000; +} + + +/** + * Reset the platform's ethernet controller. + * Should only be called once the ethernet controller is clocked. + */ +static void ethernet_reset_peripheral(void) +{ + platform_reset_register_block_t *reset = get_platform_reset_registers(); + + // Issue our reset, and then wait for it to complete. + reset->ethernet_reset = 1; + while (reset->ethernet_reset); +} + + + +/** + * Initialies a new ethernet controller object, and readies it (and the appropriate) + * hardware for use. + * + * @param An unpopulated ethernet device structure to be readied for use. + */ +void platform_ethernet_init(ethernet_controller_t *device) +{ + platform_clock_control_register_block_t *ccu = get_platform_clock_control_registers(); + + // Populate the device object with platform-specific knowledge; + // in this case how to poke ethernet registers. + device->reg = get_ethernet_register_block(); + device->platform.creg = get_platform_configuration_registers(); + device->platform.clock = &ccu->m4.ethernet; + + // Enable clock. + platform_enable_clock(device->platform.clock, false); + + // Reset the ethernet controller. + ethernet_reset_peripheral(); + + // Figure out PHY clock routing if needed? + + // Switch to RMII. + // TODO: make this configurable? + device->platform.creg->ethmode = ETHMODE_RMII; + + // Set up things via the MII link? + // Set up DMA and MAC modes? +} + + +void platform_ethernet_configure_phy(ethernet_controller_t *device, uint16_t clock_divider, uint16_t phy_address) +{ + +} + + + +/** + * Queue a non-blocking MII transaction, which communicates with the PHY. + * + * @param Should be set to true iff the given operation is to be a write. + * @param register_index The PHY register address. + * @param value The value to be written to the given PHY register, or 0 for a read operation. + */ +static void platform_ethernet_mii_start_transaction(ethernet_controller_t *device, + bool is_write, uint8_t register_index, uint16_t value) +{ + // Get a reference to the MII control register. + platform_ethernet_mii_address_register_t *addr = &device->reg->mac.mii_addr; + + // Wait for the MII bus to be ready. + while (addr->comms_in_progress); + + // Populate the index of the target MII register. + addr->register_index = register_index; + + // If this is a write, mark it as a write, and queue the data to be transmitted. + if (is_write) { + device->reg->mac.mii_data = value; + addr->write = 1; + } else { + addr->write = 0; + } + + // Finally, trigger the transaction. + addr->comms_in_progress = 1; +} + + +/** + * Queue a non-blocking MII write, which communicates with the PHY over the + * management interface. To emulate a blocking write, follow this up by calling + * for platform_ethernet_mii_complete_transaction(). + * + * @param register_index The PHY register address. + * @param value The value to be written to the given PHY register. + */ +void platform_ethernet_mii_write(ethernet_controller_t *device, uint8_t register_index, uint16_t value) +{ + platform_ethernet_mii_start_transaction(device, true, register_index, value); +} + + +/** + * @return true iff a management read or write is currently in progress + */ +bool platform_ethernet_mii_write_in_progress(ethernet_controller_t *device) +{ + return device->reg->mac.mii_addr.comms_in_progress; +} + + +/** + * Blocks until the active management transaction completes. + * + * @returns The relevant MII data; mostly useful to retrieve the result + * of a completed read. + */ +uint16_t platform_ethernet_mii_complete_transaction(ethernet_controller_t *device) +{ + // Wait for the transaction to complete. + while (platform_ethernet_mii_write_in_progress(device)); + + // ... and return the last bit of data. + return device->reg->mac.mii_data; +} + + +/** + * Queue a non-blocking MII read, which communicates with the PHY over the + * management interface. The result of this read can be read by calling + * platform_ethernet_mii_complete_transcation(); its readiness can be checked using + * platform_ethernet_mii_write_in_progress. + * + * @param register_index The PHY register address. + * @param value The value to be written to the given PHY register. + */ +void platform_ethernet_mii_start_read(ethernet_controller_t *device, uint8_t register_index) +{ + // Start an MII read transaction... + platform_ethernet_mii_start_transaction(device, false, register_index, 0); +} + + + +/** + * Blocking read from the PHY over the management interface. + * + * @param register_index The register to read from. + * @return The result of the read operation. + */ +uint16_t platform_ethernet_mii_read(ethernet_controller_t *device, uint8_t register_index) +{ + // Start an MII read transaction... + platform_ethernet_mii_start_read(device, register_index); + + // ... and wait for it to complete, and then return the result. + return platform_ethernet_mii_complete_transaction(device); +} diff --git a/firmware/drivers/gpio/lpc43xx/gpio.c b/firmware/platform/lpc43xx/drivers/gpio.c similarity index 100% rename from firmware/drivers/gpio/lpc43xx/gpio.c rename to firmware/platform/lpc43xx/drivers/gpio.c diff --git a/firmware/platform/lpc43xx/drivers/platform_clock.c b/firmware/platform/lpc43xx/drivers/platform_clock.c new file mode 100644 index 0000000..00d95cc --- /dev/null +++ b/firmware/platform/lpc43xx/drivers/platform_clock.c @@ -0,0 +1,2132 @@ +/* + * This file is part of libgreat + * + * LPC43xx clock setup / control drivers + */ + +#include + +#include +#include +#include + +#include + + +// Don't try to bring up any clock more than five times. +static const uint32_t platform_clock_max_bringup_attempts = 5; + +/** + * Base address for the LPC43xx Clock Generation Address. + */ +#define CGU_BASE_ADDRESS (0x40050000UL) + +/** + * Base address for the LPC43xx Clock Generation Address. + */ +#define CCU_BASE_ADDRESS (0x40051000UL) + + +/** + * Constants to make configuration easier. + */ +#define HZ (1UL) +#define KHZ (1000UL) +#define MHZ (1000000UL) + + +/** + * Helper macros for getting quick references ot the system base / branch clocks. + * Intended only for use in this driver; external sources should manually grab the offset in the CCU/CGU. + */ +#define BASE_CLOCK(name) (platform_base_clock_t *)(CGU_BASE_ADDRESS + CGU_OFFSET(name)) +#define BRANCH_CLOCK(name) (platform_branch_clock_t *)(CCU_BASE_ADDRESS + CCU_OFFSET(name)) + + +// Forward declarations. +static int platform_handle_dependencies_for_clock_source(clock_source_t source); +static void platform_handle_clock_source_frequency_change(clock_source_t source); +static uint32_t platform_get_clock_source_frequency(clock_source_t source); +void platform_handle_base_clock_frequency_change(platform_base_clock_t *clock); +clock_source_t platform_get_physical_clock_source(clock_source_t source); +static int platform_ensure_main_xtal_is_up(void); +static void platform_soft_start_cpu_clock(void); +static int platform_bring_up_main_pll(uint32_t frequency); +static char *platform_get_base_clock_name(platform_base_clock_t *base); +static platform_base_clock_t *platform_base_clock_for_divider(clock_source_t source); +static int platform_bring_up_audio_pll(void); +static int platform_bring_up_usb_pll(void); + +// Set to true once we're finished with early initialization. +// (We can't do some things until early init completes.) +static bool platform_early_init_complete; + + +/** + * Data structure represneting the configuration for each active clock source. + */ +typedef struct { + + // True iff the clock source is currently enabled. + bool enabled; + + // The expected frequency of the clock source, in Hz. + // If this is set to 0, any input frequency will be acceptable. + uint32_t frequency; + + // The actual / measured frequency of the clock source, in Hz. + uint32_t frequency_actual; + + // If this is a generated clock source, this field indicates the source for the generated clock. + // Otherwise, this field is meaningless. + clock_source_t source; + + // Set to true once the given clock has been brought up, so we can avoid setting it up again. + bool up_and_okay; + + // Counts the number of total failures to bring this clock up. + bool failure_count; + +} platform_clock_source_configuration_t; + + +/** + * Active configurations for each of the system's clock sources. + */ +ATTR_WEAK platform_clock_source_configuration_t platform_clock_source_configurations[CLOCK_SOURCE_COUNT] = { + + // Slow oscillators, both external (RTC) and internal (IRC). + [CLOCK_SOURCE_32KHZ_OSCILLATOR] = { .frequency = 32 * KHZ }, + [CLOCK_SOURCE_INTERNAL_OSCILLATOR] = { .frequency = 12 * MHZ, .frequency_actual = 12 * MHZ, .up_and_okay = true}, + + // Clock inputs -- these accept clocks directly on a GPIO pin. + [CLOCK_SOURCE_ENET_RX_CLOCK] = { .frequency = 50 * MHZ }, + [CLOCK_SOURCE_ENET_TX_CLOCK] = { .frequency = 50 * MHZ }, + [CLOCK_SOURCE_GP_CLOCK_INPUT] = {}, + + // Main clock oscillator. + [CLOCK_SOURCE_XTAL_OSCILLATOR] = { .frequency = 12 * MHZ, .frequency_actual = 12 * MHZ }, + + // Derived clocks -- including PLLs and dividiers. + [CLOCK_SOURCE_PLL0_USB] = { .frequency = 480 * MHZ, .source = CLOCK_SOURCE_PRIMARY_INPUT }, + [CLOCK_SOURCE_PLL0_AUDIO] = {}, + [CLOCK_SOURCE_PLL1] = { .frequency = 204 * MHZ, .source = CLOCK_SOURCE_PRIMARY_INPUT }, + [CLOCK_SOURCE_DIVIDER_A_OUT] = {}, + [CLOCK_SOURCE_DIVIDER_B_OUT] = {}, + [CLOCK_SOURCE_DIVIDER_C_OUT] = {}, + [CLOCK_SOURCE_DIVIDER_D_OUT] = {}, + [CLOCK_SOURCE_DIVIDER_E_OUT] = {} +}; + + +/** + * Data structure describing the relationship between a base clock and its + * subordinate "branch" clocks, as well as configuration defaults for each clock. + */ +typedef struct { + + char *name; + + // The location of the relevant base clock in the CGU; + // represented as an offset into the CGU block. + uintptr_t cgu_offset; + + // The location of the relevant block of CCU registers. + uintptr_t ccu_region_offset; + + // The span of the CCU base. For convenience, a value of 0 is + // equivalent to a minimum span of 0x100. + uintptr_t ccu_region_span; + + // Initial CGU clock settings. + clock_source_t source; + uint8_t divisor; + + // Freuqency, in Hz. + uint32_t frequency; + + // Indicates that a given clock is unused, and thus should never be brought up. + bool unused; + + // Indicates that a given clock has no possible configuration. + bool cannot_be_configured; + + // Indicates that the provided clock should not attempt to fall back to the internal oscillator. + bool no_fallback; + + +} platform_base_clock_configuration_t; + + +/** + * Platform configuration for each of the local clocks. + * These specify the relationships between the various generated and derivative clocks. + */ +ATTR_WEAK platform_base_clock_configuration_t clock_configs[] = { + { .name = "idiva", .cgu_offset = CGU_OFFSET(idiva), .source = CLOCK_SOURCE_PLL0_USB, .divisor = 4 }, + { .name = "idivb", .cgu_offset = CGU_OFFSET(idivb), .source = CLOCK_SOURCE_DIVIDER_A_OUT, .divisor = 2 }, + { .name = "idivc", .cgu_offset = CGU_OFFSET(idivc) }, + { .name = "idivd", .cgu_offset = CGU_OFFSET(idivd) }, + { .name = "idive", .cgu_offset = CGU_OFFSET(idive) }, + { .name = "safe", .cgu_offset = CGU_OFFSET(safe), .cannot_be_configured = true }, + { .name = "usb0", .cgu_offset = CGU_OFFSET(usb0), .ccu_region_offset = CCU_OFFSET(usb0), + .source = CLOCK_SOURCE_PLL0_USB, .no_fallback = true }, + { .name = "periph", .cgu_offset = CGU_OFFSET(periph), .ccu_region_offset = CCU_OFFSET(periph), + .source = CLOCK_SOURCE_PRIMARY }, + { .name = "usb1", .cgu_offset = CGU_OFFSET(usb1), .ccu_region_offset = CCU_OFFSET(usb1), + .source = CLOCK_SOURCE_DIVIDER_B_OUT }, + { .name = "m4", .cgu_offset = CGU_OFFSET(m4), .ccu_region_offset = CCU_OFFSET(m4), + .ccu_region_span = 0x300, .source = CLOCK_SOURCE_PRIMARY, }, + { .name = "spifi", .cgu_offset = CGU_OFFSET(spifi), .ccu_region_offset = CCU_OFFSET(spifi), + .source = CLOCK_SOURCE_PRIMARY }, + { .name = "spi", .cgu_offset = CGU_OFFSET(spi), .ccu_region_offset = CCU_OFFSET(spi), + .source = CLOCK_SOURCE_PRIMARY }, + { .name = "phy_rx", .cgu_offset = CGU_OFFSET(phy_rx) }, + { .name = "phy_tx", .cgu_offset = CGU_OFFSET(phy_tx) }, + { .name = "apb1", .cgu_offset = CGU_OFFSET(apb1), .ccu_region_offset = CCU_OFFSET(apb1), + .source = CLOCK_SOURCE_PRIMARY }, + { .name = "apb3", .cgu_offset = CGU_OFFSET(apb3), .ccu_region_offset = CCU_OFFSET(apb3), + .source = CLOCK_SOURCE_PRIMARY }, + { .name = "lcd", .cgu_offset = CGU_OFFSET(lcd) }, + { .name = "adchs", .cgu_offset = CGU_OFFSET(adchs), .ccu_region_offset = CCU_OFFSET(adchs), + .source = CLOCK_SOURCE_DIVIDER_B_OUT }, + { .name = "sdio", .cgu_offset = CGU_OFFSET(sdio), .ccu_region_offset = CCU_OFFSET(sdio), + .source = CLOCK_SOURCE_PRIMARY }, + { .name = "ssp0", .cgu_offset = CGU_OFFSET(ssp0), .ccu_region_offset = CCU_OFFSET(ssp0), + .source = CLOCK_SOURCE_PRIMARY }, + { .name = "ssp1", .cgu_offset = CGU_OFFSET(ssp1), .ccu_region_offset = CCU_OFFSET(ssp1), + .source = CLOCK_SOURCE_PRIMARY }, + { .name = "uart0", .cgu_offset = CGU_OFFSET(uart0), .ccu_region_offset = CCU_OFFSET(usart0), + .source = CLOCK_SOURCE_PRIMARY }, + { .name = "uart1", .cgu_offset = CGU_OFFSET(uart1), .ccu_region_offset = CCU_OFFSET(uart1), + .source = CLOCK_SOURCE_PRIMARY }, + { .name = "uart2", .cgu_offset = CGU_OFFSET(uart2), .ccu_region_offset = CCU_OFFSET(usart2), + .source = CLOCK_SOURCE_PRIMARY }, + { .name = "uart3", .cgu_offset = CGU_OFFSET(uart3), .ccu_region_offset = CCU_OFFSET(usart3), + .source = CLOCK_SOURCE_PRIMARY }, + { .name = "out", .cgu_offset = CGU_OFFSET(out) }, + { .name = "out0", .cgu_offset = CGU_OFFSET(out0) }, + { .name = "out1", .cgu_offset = CGU_OFFSET(out1) }, + { .name = "audio", .cgu_offset = CGU_OFFSET(audio), .ccu_region_offset = CCU_OFFSET(audio), + .source = CLOCK_SOURCE_PRIMARY_INPUT }, + + // Sentinel; indicates the end of our collection. + {} + +}; + +/** + * Full collection of branch clocks. Allows us to iterate over each branch clock to perform e.g. maintenance tasks. + */ +static platform_branch_clock_t *all_branch_clocks[] = { + BRANCH_CLOCK(apb3.bus), BRANCH_CLOCK(apb3.i2c1), BRANCH_CLOCK(apb3.dac), BRANCH_CLOCK(apb3.adc0), + BRANCH_CLOCK(apb3.adc1), BRANCH_CLOCK(apb3.can0), BRANCH_CLOCK(apb1.bus), BRANCH_CLOCK(apb1.motocon_pwm), + BRANCH_CLOCK(apb1.i2c0), BRANCH_CLOCK(apb1.i2s), BRANCH_CLOCK(apb1.can1), BRANCH_CLOCK(spifi), + BRANCH_CLOCK(m4.bus), BRANCH_CLOCK(m4.spifi), BRANCH_CLOCK(m4.gpio), BRANCH_CLOCK(m4.lcd), + BRANCH_CLOCK(m4.ethernet), BRANCH_CLOCK(m4.usb0), BRANCH_CLOCK(m4.emc), BRANCH_CLOCK(m4.sdio), BRANCH_CLOCK(m4.dma), + BRANCH_CLOCK(m4.core), BRANCH_CLOCK(m4.sct), BRANCH_CLOCK(m4.usb1), BRANCH_CLOCK(m4.emcdiv), + BRANCH_CLOCK(m4.flasha), BRANCH_CLOCK(m4.flashb), BRANCH_CLOCK(m4.m0app), BRANCH_CLOCK(m4.adchs), + BRANCH_CLOCK(m4.eeprom), BRANCH_CLOCK(m4.wwdt), BRANCH_CLOCK(m4.usart0), BRANCH_CLOCK(m4.uart1), + BRANCH_CLOCK(m4.ssp0), BRANCH_CLOCK(m4.timer0), BRANCH_CLOCK(m4.timer1), BRANCH_CLOCK(m4.scu), + BRANCH_CLOCK(m4.creg), BRANCH_CLOCK(m4.ritimer), BRANCH_CLOCK(m4.usart2), BRANCH_CLOCK(m4.usart3), + BRANCH_CLOCK(m4.timer2), BRANCH_CLOCK(m4.timer3), BRANCH_CLOCK(m4.ssp1), BRANCH_CLOCK(m4.qei), + BRANCH_CLOCK(periph.bus), BRANCH_CLOCK(periph.core), BRANCH_CLOCK(periph.sgpio), BRANCH_CLOCK(usb0), + BRANCH_CLOCK(usb1), BRANCH_CLOCK(spi), BRANCH_CLOCK(adchs), BRANCH_CLOCK(audio), BRANCH_CLOCK(usart3), + BRANCH_CLOCK(usart2), BRANCH_CLOCK(uart1), BRANCH_CLOCK(usart0), BRANCH_CLOCK(ssp1), BRANCH_CLOCK(ssp0), + BRANCH_CLOCK(sdio) +}; + +/** + * Full collection of base clocks. Allows us to iterate over each base clock to perform e.g. maintenance tasks. + */ + static platform_base_clock_t *all_base_clocks[] = { + BASE_CLOCK(idiva), BASE_CLOCK(idivb), BASE_CLOCK(idivc), BASE_CLOCK(idivd), BASE_CLOCK(idive), BASE_CLOCK(safe), + BASE_CLOCK(usb0), BASE_CLOCK(periph), BASE_CLOCK(usb1), BASE_CLOCK(m4), BASE_CLOCK(spifi), BASE_CLOCK(spi), + BASE_CLOCK(phy_rx), BASE_CLOCK(phy_tx), BASE_CLOCK(apb1), BASE_CLOCK(apb3), BASE_CLOCK(lcd), BASE_CLOCK(adchs), + BASE_CLOCK(sdio), BASE_CLOCK(ssp0), BASE_CLOCK(ssp1), BASE_CLOCK(uart0), BASE_CLOCK(uart1), BASE_CLOCK(uart2), + BASE_CLOCK(uart3), BASE_CLOCK(out), BASE_CLOCK(audio), BASE_CLOCK(out0), BASE_CLOCK(out1) + }; + +/** + * Names for each of the branch clocks. Indexes are the same as all_branch_clock indexes. + */ +static const char *branch_clock_names[] = { + "apb3.bus", "apb3.i2c1", "apb3.dac", "apb3.adc0", "apb3.adc1", "apb3.can0", "apb1.bus", "apb1.motocon_pwm", + "apb1.i2c0", "apb1.i2s", "apb1.can1", "spifi", "m4.bus", "m4.spifi", "m4.gpio", "m4.lcd", "m4.ethernet", "m4.usb0", + "m4.emc", "m4.sdio", "m4.dma", "m4.core", "m4.sct", "m4.usb1", "m4.emcdiv", "m4.flasha", "m4.flashb", "m4.m0app", + "m4.adchs", "m4.eeprom", "m4.wwdt", "m4.usart0", "m4.uart1", "m4.ssp0", "m4.timer0", "m4.timer1", "m4.scu", + "m4.creg", "m4.ritimer", "m4.usart2", "m4.usart3", "m4.timer2", "m4.timer3", "m4.ssp1", "m4.qei", "periph.bus", + "periph.core", "periph.sgpio", "usb0", "usb1", "spi", "adchs", "audio", "usart3", "usart2", "uart1", "usart0", + "ssp1", "ssp0", "sdio" +}; + + +/** + * Return a reference to the LPC43xx's CCU block. + */ +platform_clock_control_register_block_t *get_platform_clock_control_registers(void) +{ + return (platform_clock_control_register_block_t *)CCU_BASE_ADDRESS; +} + + +/** + * Return a reference to the LPC43xx's CGU block. + */ +platform_clock_generation_register_block_t *get_platform_clock_generation_registers(void) +{ + return (platform_clock_generation_register_block_t *)CGU_BASE_ADDRESS; +} + + +/** + * Convert an offset into the CGU register block into a base-clock object. + * + * @param cgu_offset The offset into the CGU register bank. + */ +static platform_base_clock_t *platform_get_base_clock_from_cgu_offset(uintptr_t cgu_offset) +{ + if (!cgu_offset) { + return NULL; + } + + return (platform_base_clock_register_t *)(CGU_BASE_ADDRESS + cgu_offset); +} + + +/** + * Convert an offset into the CGU register block into a branch-clock object. + * + * @param cgu_offset The offset into the CCU register bank. + */ +static platform_branch_clock_t *platform_get_branch_clock_from_ccu_offset(uintptr_t ccu_offset) +{ + if (!ccu_offset) { + return NULL; + } + + return (platform_branch_clock_register_t *)(CCU_BASE_ADDRESS + ccu_offset); +} + + +/** + * Fetches the register that controls the parent clock for the given peripheral clock. + */ +static const platform_base_clock_configuration_t *platform_find_config_for_branch_clock(platform_branch_clock_t *clock) +{ + // Iterator. + const platform_base_clock_configuration_t *config; + + // Figure out the byte offset of the given clock entity. + uintptr_t ccu_offset = (uintptr_t)clock - CCU_BASE_ADDRESS; + + // Search through our possible base clocks until we hit a sentinel, or find our result. + for (config = clock_configs; config->name; ++config) { + uintptr_t ccu_region_span = config->ccu_region_span ? config->ccu_region_span : 0x100; + uintptr_t ccu_region_max = config->ccu_region_offset + ccu_region_span; + + // If this entry doesn't have a CCU offset, it doesn't have an associated branch clock, + // and thus isn't of interest of us. + if (!config->ccu_region_offset) { + continue; + } + + // If the relevant clock's offset is in the relevant region, return the corresponding base clock. + if ((ccu_offset >= config->ccu_region_offset) && (ccu_offset < ccu_region_max)) { + return config; + } + } + + // If we didn't find a base, return NULL. + return NULL; +} + +/** + * Fetches the register that controls the parent clock for the given peripheral clock. + */ +static platform_base_clock_configuration_t *platform_find_config_for_base_clock(platform_base_clock_t *clock) +{ + // Iterator. + platform_base_clock_configuration_t *config; + + // Figure out the byte offset of the given clock entity. + uintptr_t cgu_offset = (uintptr_t)clock - CGU_BASE_ADDRESS; + + // Search through our possible base clocks until we hit a sentinel, or find our result. + for (config = clock_configs; config->name; ++config) { + + // If the relevant clock's offset is in the relevant region, return the corresponding base clock. + if (cgu_offset == config->cgu_offset) { + return config; + } + } + + // If we didn't find a config, return NULL. + return NULL; +} + + +/** + * Fetches the clock that controls the bus the given peripheral is on. + */ +static platform_branch_clock_t *platform_get_bus_clock(platform_branch_clock_register_t *clock) +{ + platform_branch_clock_t *bus_clock; + + // Find the configuration entry that covers the given branch clock. + const platform_base_clock_configuration_t *config = platform_find_config_for_branch_clock(clock); + + // If we didn't find a configuration entry, return NULL. + if (!config) { + return NULL; + } + + // Otherwise, get a reference to the bus clock. + bus_clock = platform_get_branch_clock_from_ccu_offset(config->ccu_region_offset); + + // Special cases: some perpiherals are connected directly, and thus don't have a bus clock. + // We internally represent these clocks as being their own bus clock; but it doesn't make sense + // to return that here -- we don't e.g. want to turn on a peripheral in order to turn on itself. + if (bus_clock == clock) { + return NULL; + } + + return bus_clock; +} + + +/** + * @return a string containing the given clock source's name + */ +const char *platform_get_clock_source_name(clock_source_t source) +{ + switch(source) { + case CLOCK_SOURCE_32KHZ_OSCILLATOR: return "32kHz oscillator"; + case CLOCK_SOURCE_INTERNAL_OSCILLATOR: return "internal oscillator"; + case CLOCK_SOURCE_ENET_RX_CLOCK: return "ethernet rx clock"; + case CLOCK_SOURCE_ENET_TX_CLOCK: return "ethernet tx clock"; + case CLOCK_SOURCE_GP_CLOCK_INPUT: return "clock input"; + case CLOCK_SOURCE_XTAL_OSCILLATOR: return "external crystal oscillator"; + case CLOCK_SOURCE_PLL0_USB: return "USB PLL"; + case CLOCK_SOURCE_PLL0_AUDIO: return "audio PLL"; + case CLOCK_SOURCE_PLL1: return "core PLL"; + case CLOCK_SOURCE_DIVIDER_A_OUT: return "divider-A"; + case CLOCK_SOURCE_DIVIDER_B_OUT: return "divider-B"; + case CLOCK_SOURCE_DIVIDER_C_OUT: return "divider-C"; + case CLOCK_SOURCE_DIVIDER_D_OUT: return "divider-D"; + case CLOCK_SOURCE_DIVIDER_E_OUT: return "divider-E"; + case CLOCK_SOURCE_PRIMARY: return "primary clock"; + case CLOCK_SOURCE_PRIMARY_INPUT: return "primary input clock"; + default: return "unknown source"; + } +} + + +/** + * Ensures the provided clock is active and can be used. + */ +int platform_enable_base_clock(platform_base_clock_register_t *base) +{ + int rc; + platform_base_clock_t value; + + // Identify the relevant configuration for the given base clock. + platform_base_clock_configuration_t *config = platform_find_config_for_base_clock(base); + + // If this clock cannot be configured, we'll assume it's already okay. + // TODO: validate the configuration settings? + if (config->cannot_be_configured) { + return 0; + } + + // Switch the base clock to its relevant clock source. + rc = platform_handle_dependencies_for_clock_source(config->source); + if (rc && !config->no_fallback) { + pr_warning("failed to bring up source %s for base clock %s; falling back to internal oscillator!\n", + platform_get_clock_source_name(config->source), platform_get_base_clock_name(base)); + config->source = CLOCK_SOURCE_INTERNAL_OSCILLATOR; + } + else if (rc) { + pr_warning("failed to bring up source %s for base clock %s; trying to continue anyway.\n", + platform_get_clock_source_name(config->source), platform_get_base_clock_name(base)); + } + + // Finally, ensure the clock is powered up. + value.power_down = 0; + value.block_during_changes = 0; + value.source = config->source; + value.divisor = 0; + base->all_bits = value.all_bits; + + return 0; +} + + +void platform_disable_base_clock(platform_base_clock_register_t *base) +{ + const platform_base_clock_configuration_t *config = platform_find_config_for_base_clock(base); + + if (config->cannot_be_configured) { + return; + } + + // TODO: provide an complement to platform_handle_dependencies_for_clock_source + // that allows us to disable any dependencies for this clock source if necessary. + + // Power down the base clock. + base->power_down = true; +} + + +/** + * Fetches the base clock for the given branch clock. + */ +static platform_base_clock_register_t *platform_get_clock_base(platform_branch_clock_register_t *clock) +{ + // Iterator. + const platform_base_clock_configuration_t *config = platform_find_config_for_branch_clock(clock); + + // If we didn't find a configuration entry, return NULL. + if (!config) { + return NULL; + } + + // Otherwise, return the base clock. + return platform_get_base_clock_from_cgu_offset(config->cgu_offset); +} + + +/** + * @return a string describing the given base clock + */ +static char *platform_get_base_clock_name(platform_base_clock_t *base) +{ + // Find the base clock configuration, which also has the base clock's name. + const platform_base_clock_configuration_t *config = platform_find_config_for_base_clock(base); + + // If we found a config, return its name; otherwise return a default string. + if (config && config->name) { + return config->name; + } else { + return "unknown clock"; + } +} + + +/** + * Returns true iff the given base clock is in use. + * + * Searches manually for branches that depend on the given base clock, + * so this can be used even for generated clocks. + */ +bool platform_clock_source_in_use(clock_source_t source) +{ + platform_clock_generation_register_block_t *cgu = get_platform_clock_generation_registers(); + + // Search all of the branch clocks for any clocks that depend on us. + for (unsigned i = 0; i < ARRAY_SIZE(all_branch_clocks); ++i) { + platform_branch_clock_t *branch = all_branch_clocks[i]; + platform_base_clock_t *base = platform_get_clock_base(branch); + + // If we can't find a base clock for this branch, it doesn't depend on us. + if (!base) { + continue; + } + + // If this clock or its source are off, it doesn't depend on us. + if (base->power_down || branch->current.disabled) { + continue; + } + + // If this clock is derived from a source other than us, it doesn't depend on us. + if (base->source != source) { + continue; + } + + return true; + } + + // Search all of the base clocks for any _base_ clocks that depend on us. + for (unsigned i = 0; i < ARRAY_SIZE(all_base_clocks); ++i) { + platform_base_clock_t *base = all_base_clocks[i]; + + // If this clock isn't based on us, it doesn't depened on us. + if (base->source != source) { + continue; + } + + // If this clock is off, it doesn't depend on us. + if (base->power_down) { + continue; + } + + return true; + } + + // Check our PLLs to see if any of them depend on us. + if (!cgu->pll1.power_down && (cgu->pll1.source == source)) { + return true; + } + if (!cgu->pll_usb.powered_down && (cgu->pll_usb.source == source)) { + return true; + } + if (!cgu->pll_audio.core.powered_down && (cgu->pll_audio.core.source == source)) { + return true; + } + + // If none of the conditions above were met, we're not in use! + return false; +} + + +/** + * @returns true iff the given base clock is in use + */ +static bool platform_base_clock_in_use(platform_base_clock_register_t *base) +{ + platform_clock_control_register_block_t *ccu = get_platform_clock_control_registers(); + + // Find the CGU offset, which we'll use to look up the appropriate register. + uintptr_t cgu_offset = (uintptr_t)base - CGU_BASE_ADDRESS; + + switch(cgu_offset) { + case CGU_OFFSET(idiva): return platform_clock_source_in_use(CLOCK_SOURCE_DIVIDER_A_OUT); + case CGU_OFFSET(idivb): return platform_clock_source_in_use(CLOCK_SOURCE_DIVIDER_B_OUT); + case CGU_OFFSET(idivc): return platform_clock_source_in_use(CLOCK_SOURCE_DIVIDER_C_OUT); + case CGU_OFFSET(idivd): return platform_clock_source_in_use(CLOCK_SOURCE_DIVIDER_D_OUT); + case CGU_OFFSET(idive): return platform_clock_source_in_use(CLOCK_SOURCE_DIVIDER_E_OUT); + + // The "safe" clock is always available, intentionally. + case CGU_OFFSET(safe): + return true; + + // For most other clocks, return whether the hardware reports them as driving a branch clock. + case CGU_OFFSET(usb0): return ccu->usb0_needed; + case CGU_OFFSET(periph): return ccu->periph_needed; + case CGU_OFFSET(usb1): return ccu->usb1_needed; + case CGU_OFFSET(m4): return ccu->m4_needed; + case CGU_OFFSET(spifi): return ccu->spifi_needed; + case CGU_OFFSET(spi): return ccu->spi_needed; + case CGU_OFFSET(apb1): return ccu->apb1_needed; + case CGU_OFFSET(apb3): return ccu->apb3_needed; + case CGU_OFFSET(ssp0): return ccu->ssp0_needed; + case CGU_OFFSET(ssp1): return ccu->ssp1_needed; + case CGU_OFFSET(uart0): return ccu->uart0_needed; + case CGU_OFFSET(uart1): return ccu->uart1_needed; + case CGU_OFFSET(uart2): return ccu->uart2_needed; + case CGU_OFFSET(uart3): return ccu->uart3_needed; + + // FIXME: For now, assume that output clocks are always on. + // We can probably develop a better logic for this later. + case CGU_OFFSET(audio): return true; + case CGU_OFFSET(out): return true; + case CGU_OFFSET(out0): return true; + case CGU_OFFSET(out1): return true; + + // FIXME: these should return whether m4.ethernet is enabled + case CGU_OFFSET(phy_rx): return true; + case CGU_OFFSET(phy_tx): return true; + + // FIXME: this should return whether their relevant branch clocks are enabled. + // These usually bave both their own branch clocks and a clock on the m4. + case CGU_OFFSET(lcd): return true; + case CGU_OFFSET(adchs): return true; + case CGU_OFFSET(sdio): return true; + } + + // If we didn't find a relevant clock, assume the clock is necessary. + return true; +} + + +/** + * @return a string containing the given clock source's name + */ +static const char *platform_get_branch_clock_name(platform_branch_clock_t *clock) +{ + for (unsigned i = 0; i < ARRAY_SIZE(all_branch_clocks); ++i) { + if (all_branch_clocks[i] == clock) + return branch_clock_names[i]; + } + + return "unknown branch clock"; +} + + +/** + * Disables the given base clock iff it's no longer used; e.g. if it no longer + * drives any active branch clocks. + */ +void platform_disable_base_clock_if_unused(platform_base_clock_t *base) +{ + // Check to see if the base clock is in use. + // If it is, we don't need to disable it; bail out. + if (platform_base_clock_in_use(base)) { + return; + } + + pr_debug("clock: base clock %s no longer in use; disabling.\n", platform_get_base_clock_name(base)); + + // Otherwise, disable the base clock. + platform_disable_base_clock(base); +} + + +/** + * Updates our internal notion of the IRC frequency -- usually as a result of measuring a more + * accurate clock source, such as the system's external crystal oscillator. + */ +static void platform_calibrate_irc_frequency(uint32_t frequency) +{ + platform_clock_source_configurations[CLOCK_SOURCE_INTERNAL_OSCILLATOR].frequency_actual = frequency; + platform_handle_clock_source_frequency_change(CLOCK_SOURCE_INTERNAL_OSCILLATOR); +} + + +/** + * @returns The system's internal oscillator frequency; to the best of our knowledge. + */ +static uint32_t platform_get_irc_frequency(void) +{ + return platform_clock_source_configurations[CLOCK_SOURCE_INTERNAL_OSCILLATOR].frequency_actual; +} + + +/** + * @returns True iff the given clock is ticking. + */ +static bool validate_clock_source_is_ticking(clock_source_t source) +{ + const uint32_t timeout = 1000; + + platform_clock_generation_register_block_t *cgu = get_platform_clock_generation_registers(); + uint32_t time_base = get_time(); + + // Create a measurement of the given clock source that should complete very quickly IFF the given + // clock is up. + cgu->frequency_monitor.source_to_measure = source; + cgu->frequency_monitor.reference_ticks_remaining = 1; + + // Trigger our measurement, and wait for it to complete. + cgu->frequency_monitor.measurement_active = 1; + while (cgu->frequency_monitor.measurement_active) { + + // If we exceed our timeout, cancel the measurement, and abort. + if (get_time_since(time_base) > timeout) { + cgu->frequency_monitor.measurement_active = 0; + return false; + } + } + + // If the measurement completed, it must be ticking; continue. + return true; +} + + + +/** + * Performs a single iteration of frequency measurement using the frequency-monitor hardware. + * Mostly used by this API's measurement function, platform_detect_clock_source_frequency; + * this algorithm mostly makes sense in its context. + * + * @param observed_ticks_max The maximum number of observed-clock ticks that should be able to + * occur before the counters are halted. + * @param observed_ticks_max The maximum number of reference-clock ticks that should be able to + * occur before the counters are halted. + * + * @param use_reference_timeframe If true, the time spent counting will be returned as a number of reference + * clock ticks; if false, it will be returened as a number of observed-clock ticks. + */ +static uint32_t platform_run_frequency_measurement_iteration(uint32_t observed_ticks_max, + uint32_t measurement_period_max, bool use_reference_timeframe) +{ + platform_clock_generation_register_block_t *cgu = get_platform_clock_generation_registers(); + + const uint32_t observed_tick_register_saturation_point = 0x3FFF; + + // Normally, the observed ticks only stop the measurement if the counter saturates -- so, to impose our maximum + // we'll need to initialize the counter with a value such that it saturates after `observed_ticks_max` ticks. + // So, we'll figure out how many ticks we want to happen _until_ the saturation point. + uint32_t initial_observed_ticks = observed_tick_register_saturation_point - observed_ticks_max; + + // Set the reference clock value to their maximum values. + // The reference clock counts down, while the selected clock counts up, + // so the extremes are the highest and lowest posisble values, respectively. + cgu->frequency_monitor.reference_ticks_remaining = measurement_period_max; + cgu->frequency_monitor.observed_clock_ticks = initial_observed_ticks; + + // Trigger our measurement, and wait for it to complete. + cgu->frequency_monitor.measurement_active = 1; + while (cgu->frequency_monitor.measurement_active); + + // Return the value we managed to count to with our selected clock. + if (use_reference_timeframe) { + // It's possible we terminated early by hitting our maximu observed ticks; + // so compensate by subtracting the number of ticks remaining. + return measurement_period_max - cgu->frequency_monitor.reference_ticks_remaining; + } else { + // If we added an initial value to the observred counter to reduce the ticks until + // the counter saturates, we'll need to remove them before we return the total ticks that occurred. + return cgu->frequency_monitor.observed_clock_ticks - initial_observed_ticks; + } + +} + + + +/** + * @return true iff the last frequency measurement run completed a full measurement period; + * this will return false if it aborted early due to hitting its maxium number of observed-clock-ticks + */ +uint32_t platform_last_frequency_measurement_period_ticks_left_over(void) +{ + platform_clock_generation_register_block_t *cgu = get_platform_clock_generation_registers(); + return cgu->frequency_monitor.reference_ticks_remaining; +} + + +/** + * @return true iff the last frequency measurement run completed a full measurement period; + * this will return false if it aborted early due to hitting its maxium number of observed-clock-ticks + */ +bool platform_last_frequency_measurement_period_completed(void) +{ + return platform_last_frequency_measurement_period_ticks_left_over() == 0; +} + + +/** + * Uses the LPC43xx's internal frequency monitor to detect the frequency of the given clock source. + * If trying to determine the internal clock frequency, the external oscillator must be up, as it will + * be used as the refernece clock. + * + * This version will never use an integer divider; so it will be inaccurate for higher-frequency clocks. + * + * @param source The source to be meausred. + * @return The relevant frequency, in Hz, or 1 if the given clock is too low to measure (a stopped clock + * will correctly return 0 Hz). + */ +uint32_t platform_detect_clock_source_frequency_directly(clock_source_t clock_to_detect) +{ + double resultant_frequency, resultant_ratio; + + // Maximum values for our counters -- determined by the bit size of the counters in the frequency_monitor + // registers. + const uint32_t observed_ticks_max = 0x3FFF; + const uint32_t measurement_period_max = 0x1FF; + + volatile uint32_t observed_ticks, measurement_period; + + platform_clock_generation_register_block_t *cgu = get_platform_clock_generation_registers(); + clock_source_t clock_to_measure = clock_to_detect; + + // We can't calibrate the internal oscillator (IRC) against itself; + // so we'll compare against the external XTAL, and use that to detect the IRC frequency. + if (clock_to_detect == CLOCK_SOURCE_INTERNAL_OSCILLATOR) { + clock_to_measure = CLOCK_SOURCE_XTAL_OSCILLATOR; + } + // Otherwise, calibrate the internal frequency against the XTAL first. + // (The XTAL is more accurate; this will help to null out any drift due to e.g. temperature.) + else { + uint32_t measured = platform_detect_clock_source_frequency_directly(CLOCK_SOURCE_INTERNAL_OSCILLATOR); + + // If we managed a calibration, continue. + if (measured) { + platform_calibrate_irc_frequency(measured); + } + } + + // Special case: if the given clock source isn't ticking, bail out immediately with a frequency of 0 Hz. + if (!validate_clock_source_is_ticking(clock_to_measure)) { + return 0; + } + + // Set the frequency monitor to measure the appropriate clock. + cgu->frequency_monitor.source_to_measure = clock_to_measure; + + // This monitor works by measuring the number of clock ticks that occur on the "observed clock" + // over a defined "measurement period", which is measured by allowing a number of _reference clock_ cycles to pass. + // We'll start off with the longest posisble period, and decrease the measurement period _iff_ it allows + // us to get more accuracy (see below). + measurement_period = measurement_period_max; + + // Try a first iteration of our measurement, allowing the measurement to go on for as long as possible. + observed_ticks = platform_run_frequency_measurement_iteration(observed_ticks_max, measurement_period_max, false); + + // If we made it through the measurement period without seeing even a single tick on the clock we're observing, + // the clock is too slow for us to measure. We can't measure clocks slower than ~24kHz, as we don't have enough + // bits in our period timer to make the measurement period any longer. :( + if (observed_ticks == 0) { + + // Indicate that this clock is too slow to be measured. + return 0; + } + + // We now have an initial reading, but it's possible we can improve on this reading's accuracy. The observed + // clock and reference clock are effecively racing until either the measurment clock reached its maximum value + // or the measurement period elapses. + // + // If we stopped at the end of the measurement period, then we have a potential source of noise: we don't know for + // sure that we measured _an integer number_ of measurement clock periods, as our measurement period could have + // ended anywhere in our observed clock's cycle. + if (platform_last_frequency_measurement_period_completed()) + { + // Luckily, we can fix this: we can decrease the measurement period until we see fewer ticks. + while(platform_run_frequency_measurement_iteration(observed_ticks, measurement_period--, false) == observed_ticks) + + // We'll count our measurement period to be equal to the _last_ period at which we saw the same amount of ticks + // -- this is the shortest amount of time in which we can see the observed amount of ticks -- and thus as close + // as we can measure to a span that contians an integer number of observed-clock periods. + + // Since we stopped decreasing the measurement period length _just after_ the number of ticks changed, we'll + // go back by one to find the last value before the change. + measurement_period++; + } + + // We also have another source of error: if we stopped due to reaching the most observed ticks we can count, + // then we likely stopped before the full measurement period has elapsed -- so this measurement doesn't correspond + // to the full span of time. + else { + if (!observed_ticks) { + pr_error("error: internally inconsistent frequency readings; the source seems unstable or too fast!\n"); + return 0; + } + + // Since we stopped decreasing the total number of observed-ticks _just after_ they affected our measurement + // period, we'll go back by one to find the actual amount of ticks that occur in our measurement period. + observed_ticks++; + } + + // We now have an as-accurate-as-possible ratio of (observed-ticks)-to-(measurement-period) -- which is effectively + // the ratio of our observed and reference clock frequencies. We can use that to compute the relevant clock + // frequency. + if (clock_to_detect != clock_to_measure) { + resultant_ratio = (double)measurement_period / (double)observed_ticks; + resultant_frequency = platform_clock_source_configurations[clock_to_measure].frequency * resultant_ratio; + } else { + resultant_ratio = (double)observed_ticks / (double)measurement_period; + resultant_frequency = platform_get_irc_frequency() * resultant_ratio; + } + + return (uint32_t)resultant_frequency; +} + +/** + * Attempts to find an integer divider that's not in use. + * @returns the given divider output, or CLOCK_SOURCE_NONE if none is availble. + */ +clock_source_t platform_find_free_integer_divider(void) +{ + // Prefer later-numbered clock dividers first; they're less liekly to be used. + clock_source_t integer_dividers[] = + { CLOCK_SOURCE_DIVIDER_E_OUT, CLOCK_SOURCE_DIVIDER_D_OUT, CLOCK_SOURCE_DIVIDER_C_OUT, + CLOCK_SOURCE_DIVIDER_B_OUT, CLOCK_SOURCE_DIVIDER_A_OUT}; + + // Search each of our integer dividers for one that's not in use. + for (unsigned i = 0; i < ARRAY_SIZE(integer_dividers); ++i) { + clock_source_t candidate_source = integer_dividers[i]; + + // If the clock source is currently unused, return it. + if (!platform_clock_source_in_use(candidate_source)) { + return candidate_source; + } + } + + // If we didn't have one, abort. + return CLOCK_SOURCE_NONE; +} + +/** + * Attempts to measure the frequency of the USB PLL, in Hz. + * @returns the frequency in Hz, or 0 if the frequency could not be measured. + */ +static uint32_t platform_detect_usb_pll_frequency(void) +{ + // Only divisor A can be based off of the USB PLL. We'll need to check to see if we can use it. + platform_clock_generation_register_block_t *cgu = get_platform_clock_generation_registers(); + platform_base_clock_t *divider = &cgu->idiva; + uint32_t divided_frequency; + + // If the divider isn't set up to divide the USB PLL, return a low-precision measurement. + //platform_handle_dependencies_for_clock_source(CLOCK_SOURCE_DIVIDER_A_OUT); + if ((divider->source != CLOCK_SOURCE_PLL0_USB) || divider->power_down) { + return platform_detect_clock_source_frequency_directly(CLOCK_SOURCE_PLL0_USB); + } + + // If it is set up, measure its output, and then multiply away the divider. + divided_frequency = platform_detect_clock_source_frequency_directly(CLOCK_SOURCE_DIVIDER_A_OUT); + return divided_frequency * (divider->divisor + 1); +} + + + +/** + * Uses the LPC43xx's internal frequency monitor to detect the frequency of the given clock source. + * This version is allowed to consume an integer divider in order to more accurately measure higher-frequency clocks, + * and will do so if necessary. + * + * @param source The source to be meausred. + * @param source The integer divider to be consumed; or CLOCK_SOURCE_NONE to automatically detect one, if possible. + * @return The relevant frequency, in Hz, or 1 if the given clock is too low to measure (a stopped clock + * will correctly return 0 Hz). + */ +uint32_t platform_detect_clock_source_frequency_via_divider(clock_source_t clock_to_detect, clock_source_t divider) +{ + platform_base_clock_t *divider_clock; + platform_base_clock_t original_state; + + // If the clock is above 240 MHz, it'll be divided by four before our final measurement. + const uint32_t divider_cutoff = 240 * MHZ; + const uint32_t scale_factor = 4; + + // Get an initial measurement, which will determine if we need to re-compute using our divider. + uint32_t frequency = platform_detect_clock_source_frequency_directly(clock_to_detect); + + // If this frequency is below our divider cutoff, we don't need to harness a divider. + // Return it immediately. + if (frequency < divider_cutoff) { + return frequency; + } + + // Sanity check to make sure we didn't make it here with our slow clock, whcih should be guaranteed to be < 120 MHz. + if (clock_to_detect == CLOCK_SOURCE_INTERNAL_OSCILLATOR) { + pr_error("error: measured the internal oscillator at %" PRIu32 " Hz; that makes no sense!\n", frequency); + return 0; + } + + // If this is the USB PLL, this can only drive divider A. We'll need to take special steps. + if (clock_to_detect == CLOCK_SOURCE_PLL0_USB) { +; return platform_detect_usb_pll_frequency(); + } + + // If the dividier is CLOCK_SOURCE_NONE + if (divider == CLOCK_SOURCE_NONE) { + divider = platform_find_free_integer_divider(); + } + + // Get the base clock object that corresponds to the given divider. + divider_clock = platform_base_clock_for_divider(divider); + + // If we don't have a divider to use, return the frequency directly. + if (!divider_clock) { + pr_warning("warning: trying to meausre a high-frequency clock, but all integer dividers are in use!\n"); + pr_warning(" The accuracy of the relevant measurement will be reduced.\n"); + + return frequency; + } + + // Store the state of the divider we're going to use, so we can restore it. + original_state = *divider_clock; + + // Otherwise, enable the given divider, with our relevant scale factor. + divider_clock->power_down = false; + divider_clock->source = clock_to_detect; + divider_clock->block_during_changes = 1; + divider_clock->divisor = scale_factor - 1; + + // Measure the _divided_ version of our clock, and multiply our result by the amount we divided by. + frequency = platform_detect_clock_source_frequency_directly(divider) * scale_factor; + + // Restore the state of the relevant divider. + *divider_clock = original_state; + return frequency; +} + + +/** + * Uses the LPC43xx's internal frequency monitor to detect the frequency of the given clock source. + * If trying to determine the internal clock frequency, the external oscillator must be up, as it will + * be used as the refernece clock. + * + * @param source The source to be meausred. + * @return The relevant frequency, in Hz, or 1 if the given clock is too low to measure (a stopped clock + * will correctly return 0 Hz). + */ +uint32_t platform_detect_clock_source_frequency(clock_source_t clock_to_detect) +{ + return platform_detect_clock_source_frequency_via_divider(clock_to_detect, CLOCK_SOURCE_NONE); +} + + +/** + * Verifies the frequency of a given clock source; this also sets our + * known actual frequency for the given source. + * + * @return 0 on success, or an error code on failure + */ +static int platform_verify_source_frequency(clock_source_t source) +{ + // Get a reference to the configuration for the given source. + platform_clock_source_configuration_t *config = &platform_clock_source_configurations[source]; + + // Measure the clock's actual frequency. + config->frequency_actual = platform_detect_clock_source_frequency(source); + pr_debug("clock: clock %s measured at %" PRIu32 " Hz\n", platform_get_clock_source_name(source), config->frequency_actual); + + // If the given source is 0 Hz and it's not supposed to be, return an error. + // TODO: valdiate that this is close enough to the specified frequency, instead + if (config->frequency && !config->frequency_actual) { + pr_error("error: clock: clock %s (%d) did not come up correctly! (actual frequency %" PRIu32 + " Hz vs expected %" PRIu32 " Hz)\n", + platform_get_clock_source_name(source), source, config->frequency_actual, config->frequency); + config->up_and_okay = false; + return EIO; + } + + // TODO: if this is the XTAL oscillator, should we just modify the actual to be the specified, assuming we're + // in the span? it's much more accurate than our IRC, and we should be calibrating accordingly + config->up_and_okay = true; + return 0; +} + +/** + * @return true iff the given clock source is already up, running, and configured + */ +static bool platform_clock_source_is_configured(clock_source_t source) +{ + return platform_clock_source_configurations[source].up_and_okay; +} + + +/** + * @return true iff the given clock source is already up, running, and configured + */ +static bool platform_clock_source_is_configured_at_frequency(clock_source_t source, uint32_t frequency) +{ + // If this clock isn't configured for the appropriate frequency, this can't be right. Return false. + if (platform_clock_source_configurations[source].frequency != frequency) { + return false; + } + return platform_clock_source_is_configured(source); +} + + + +/** + * Ensures that the system's primary clock oscillator is up, enabling us to switch + * to the more accurate crystal oscillator. + */ +static int platform_ensure_main_xtal_is_up(void) +{ + platform_clock_generation_register_block_t *cgu = get_platform_clock_generation_registers(); + + // If the XTAL is already configured, we're done! + if (platform_clock_source_is_configured(CLOCK_SOURCE_XTAL_OSCILLATOR)) { + return 0; + } + + // Ensure we're not in bypass. + cgu->xtal_control.bypass = 0; + + // Per the datasheet, the bypass and enable executions must not be modified + // in the same write -- so we use a barrier to ensure the writes stay separate. + __sync_synchronize(); + + // Enable the crystal oscillator. + cgu->xtal_control.disabled = 0; + + // Wait 250us. + delay_us(250UL); + + // XXX + delay_us(250UL * 10); + + // Success! + return platform_verify_source_frequency(CLOCK_SOURCE_XTAL_OSCILLATOR); +} + + +static int platform_ensure_rtc_xtal_is_up(void) +{ + // FIXME: Implement bringing up the RTC clock input. + return ENOSYS; +} + + + +static int platform_route_clock_input(clock_source_t source) +{ + // FIXME: Implement bringing external clocks to the relevant sources (GP_CLKIN and RTC, when bypassed). + (void) source; + return ENOSYS; +} + +/** + * @returns the base clock associated with the given integer clock divider + */ +static platform_base_clock_t *platform_base_clock_for_divider(clock_source_t source) +{ + platform_clock_generation_register_block_t *cgu = get_platform_clock_generation_registers(); + + switch (source) { + case CLOCK_SOURCE_DIVIDER_A_OUT: return &cgu->idiva; + case CLOCK_SOURCE_DIVIDER_B_OUT: return &cgu->idivb; + case CLOCK_SOURCE_DIVIDER_C_OUT: return &cgu->idivc; + case CLOCK_SOURCE_DIVIDER_D_OUT: return &cgu->idivd; + case CLOCK_SOURCE_DIVIDER_E_OUT: return &cgu->idive; + default: return NULL; + } +} + + +/** + * Brings up the clock divider that drives a given clock source. + * + * @source The clock source corresponding to the relevant clock divider. + * @return 0 on success, or an error number on failure + */ +static int platform_bring_up_clock_divider(clock_source_t source) +{ + int rc; + + // Find the base clock that coresponds tro the given divider. + platform_base_clock_t *clock = platform_base_clock_for_divider(source); + platform_base_clock_t value; + + // If the given divider has already been configured, this is a trivial success. + if (platform_clock_source_is_configured(source)) { + return 0; + } + + // Apply the relevant divisor to the base clock. + const platform_base_clock_configuration_t *config = platform_find_config_for_base_clock(clock); + + // ... and finally, enable the given clock. + rc = platform_handle_dependencies_for_clock_source(config->source); + if (rc) { + return rc; + } + + // Build the value to apply, and then apply it all at once, so we don't leave the write mid-configuration. + value.power_down = 0; + value.block_during_changes = 0; + value.source = config->source; + value.divisor = config->divisor - 1; + clock->all_bits = value.all_bits; + + return 0; +} + +/** + * Configures and brings up the source necessary to use the generated clock, per our local configuration array. + * + * @param source The clock generator to be configured. + */ +static uint32_t set_source_for_generated_clock(clock_source_t source) +{ + int rc; + + platform_clock_generation_register_block_t *cgu = get_platform_clock_generation_registers(); + platform_clock_source_configuration_t *config = &platform_clock_source_configurations[source]; + platform_clock_source_configuration_t *parent_config; + + clock_source_t parent_clock = platform_get_physical_clock_source(config->source); + parent_config = &platform_clock_source_configurations[parent_clock]; + + // Ensure that the parent clock is up. + rc = platform_handle_dependencies_for_clock_source(parent_clock); + if (rc) { + pr_critical("critical: failed to bring up source %s for the main PLL; falling back to internal oscillator", + platform_get_clock_source_name(parent_clock)); + parent_clock = config->source = CLOCK_SOURCE_INTERNAL_OSCILLATOR; + } + + // Set the actual source itself. + switch (source) { + + // Main PLLs. + case CLOCK_SOURCE_PLL1: + cgu->pll1.source = parent_clock; + break; + + default: + pr_warning("warning: cannot set source for clock %s (%d) as we don't know how!\n", + platform_get_clock_source_name(source), source); + return 0; + } + + // Return the clock frequency for the source clock, or 0 if an error occurred. + if (parent_config->frequency) { + return parent_config->frequency; + } else { + return platform_get_clock_source_frequency(parent_clock); + } +} + + +/** + * + */ +static int platform_configure_main_pll_parameters(uint32_t target_frequency, uint32_t input_frequency) +{ + platform_clock_generation_register_block_t *cgu = get_platform_clock_generation_registers(); + + const uint32_t input_divisor_max = 3; + const uint32_t input_high_bound = 25 * MHZ; + const uint32_t cco_low_bound = 156 * MHZ; + + uint32_t input_divisor = 1; + uint32_t output_divisor = 0; + uint32_t multiplier, rounding_offset; + + // If the input frequency is too high, try to divide it down to something acceptable. + while (input_frequency > input_high_bound) { + input_divisor++; + input_frequency /= 2; + } + + // If the necessary divider to reach an acceptable input freuqency is more than we can handle, + // fail out. TODO: someday, it might be nice to automatically drive PLL1 off an integer divider? + if (input_divisor > input_divisor_max) { + pr_error("error: cannot drive PLL1 from a %" PRIu32 " Hz clock, which is too fast!\n", input_frequency); + pr_error(" (you may want to drive PLL1 from an integer divider)\n"); + return EIO; + } + + // If the target frequency is too low for our PLL to synthesize using its CCO, increase our target frequency, + // but compensate by increasing our output divider. + while (target_frequency < cco_low_bound) { + pr_info("pll1: target frequency %" PRIu32 " Hz < CCO_min; doubling to %" PRIu32 " Hz and compensating with post-divider\n", + target_frequency, target_frequency * 2); + output_divisor++; + target_frequency *= 2; + } + + // We can configure the PLL in either integer or non-integer mode by determining whether we use the output + // or oscillator clock to drive the PLL feedback. Using the output clock ("integer mode") gives us a more stable + // (lower jitter) clock, but using the output clock ("non-integer") gives us more granularity in frequency + // selection. + // + // For now, we'll allow non-integr modes, but we'll want to reconsider this to save power? TODO: do so! + cgu->pll1.use_pll_feedback = 0; + + // Determine the multiplier for the PLL. + // We offset the target frequency first by half of the input frequency to round nicely. + rounding_offset = (input_frequency / 2); + multiplier = (target_frequency + rounding_offset) / input_frequency; + + if (output_divisor) { + pr_debug("pll1: computed integer-mode parameters: N: %" PRIu32 " M: %" PRIu32 " P: %" PRIu32 " for an input clock of %" PRIu32 " Hz\n", + input_divisor - 1 , multiplier - 1, output_divisor - 1, input_frequency); + } else { + pr_debug("pll1: computed direct-mode: N: %" PRIu32 " M: %" PRIu32 " for an input clock of %" PRIu32 " Hz\n", + input_divisor - 1, multiplier - 1, input_frequency); + } + + // Program the PLL's various dividers, including the M-divider, which divides the PLL feedback path. + // (Dividing the feedback path means the PLL will need to push the CCO higher to compensate, so it effectively + // acts as a multiplier. See the LPC datasheet and any PLL documentation for theory info. W2AEW has a nice video.) + cgu->pll1.feedback_divisor_M = multiplier - 1; + cgu->pll1.input_divisor_N = input_divisor - 1; + + // If we have an output divisor, use and program the output divisor. + if (output_divisor) { + cgu->pll1.output_divisor_P = output_divisor - 1; + cgu->pll1.bypass_output_divider = 0; + } + // Otherwise, bypass the output divisor and output the CCO frequency directly. + else { + cgu->pll1.bypass_output_divider = 1; + } + + return 0; +} + +/** + */ +static void platform_soft_start_cpu_clock(void) +{ + int rc; + + // Per the user manual, we need to soft start if the relevant frequency is >= 110 MHz [13.2.1.1]. + // This means holding the relevant base clock at half-frequency for 50uS. + const uint32_t soft_start_cutoff = 110 * MHZ; + const uint32_t soft_start_duration = 50; + + platform_clock_generation_register_block_t *cgu = get_platform_clock_generation_registers(); + + // Identify the clock source for the CPU, which will determine if we have to soft-start. + const platform_base_clock_configuration_t *config = platform_find_config_for_base_clock(&cgu->m4); + clock_source_t parent_clock = platform_get_physical_clock_source(config->source); + + // And read the source clock's target frequency. + uint32_t source_frequency = platform_clock_source_configurations[parent_clock].frequency; + + // If this clock is going to run at a frequency low enough that we don't have to soft-start it, + // we'll abort here and let the normal configuration bring the clock up. + if (source_frequency < soft_start_cutoff) { + return; + } + + // For now, we only support soft-starting off of PLL1. + // TODO: support soft-starting via other clocks, perhaps using an integer divider? + if (parent_clock != CLOCK_SOURCE_PLL1) { + pr_warning("warning: not able to soft-switch the CPU to source %s (%d); system may be unstable.\n", + platform_get_clock_source_name(parent_clock), parent_clock); + return; + } + + pr_debug("clock: soft-switching the main CPU clock to %" PRIu32 " Hz\n", source_frequency); + + // First, ensure the main CPU complex is running our safe, slow internal oscillator. + cgu->m4.source = CLOCK_SOURCE_INTERNAL_OSCILLATOR; + + // Configure the main PLL to produce the target frequency -- this is essentially the mode we _want_ to run in. + // This configures the core PLL to come up in the state we want. + rc = platform_bring_up_main_pll(source_frequency); + if (rc) { + return; + } + + // If we're currently bypassing the output divider, turning the divider + // on (and to its least setting) achieves a trivial divide-by-two. + if (cgu->pll1.bypass_output_divider) { + cgu->pll1.output_divisor_P = 0; + cgu->pll1.bypass_output_divider = 0; + } else { + cgu->pll1.output_divisor_P++; + } + while (!cgu->pll1.locked); + + // Set the main CPU clock to our halved PLL... + cgu->m4.source = parent_clock; + platform_handle_base_clock_frequency_change(&cgu->m4); + + // ... and hold it there for our soft-start period. + pr_debug("clock: CPU is now running from %s\n", platform_get_clock_source_name(parent_clock)); + delay_us(soft_start_duration); + + // Undo our changes, bringing the PLL output back up to its full speed. + if (cgu->pll1.output_divisor_P == 0) { + cgu->pll1.bypass_output_divider = 1; + } else { + cgu->pll1.output_divisor_P--; + } + while (!cgu->pll1.locked); + + platform_handle_base_clock_frequency_change(&cgu->m4); + pr_debug("clock: CPU is now running at our target speed of %" PRIu32 "\n", source_frequency); +} + + +/** + * Bring up the system's main PLL at the desired frequency. + * + * @param frequency The desired frequency; max of 204 MHz. + */ +static int platform_bring_up_main_pll(uint32_t frequency) +{ + const uint32_t pll_lock_timeout = 1000000; // 1 second; this should probably be made tweakable + + // Store the bounds for the PLL's internal programmable current-controlled oscillator (CCO). + const uint32_t input_low_bound = 10 * MHZ; + const uint32_t output_low_bound = 9750 * KHZ; + const uint32_t cco_high_bound = 320 * MHZ; + + platform_clock_generation_register_block_t *cgu = get_platform_clock_generation_registers(); + platform_clock_source_configuration_t *config = &platform_clock_source_configurations[CLOCK_SOURCE_PLL1]; + + uint32_t input_frequency, time_base; + int rc; + + // If the PLL is already configured, we're done! + if (platform_clock_source_is_configured_at_frequency(CLOCK_SOURCE_PLL1, frequency)) { + return 0; + } + + if (config->failure_count > platform_clock_max_bringup_attempts) + { + pr_error("error: not trying to bring up main PLL; too many failures\n"); + return ETIMEDOUT; + } + + // Update the clock configuration to match the provided frequency. + config->up_and_okay = false; + config->frequency = frequency; + pr_debug("clock: configuring main PLL to run at %" PRIu32 " Hz.\n", frequency); + + // Validate our freuqency bounds. + if (frequency > cco_high_bound) { + pr_error("error: cannot program PLL1 to frequency %" PRIu32 "; this frequency is higher than is possible\n", frequency); + pr_error(" (you may want to derive your clock from PLL0, which can generate higher frequencies)\n"); + return EINVAL; + } + if (frequency < output_low_bound) { + pr_error("error: cannot program PLL1 to frequency %" PRIu32 "; this frequency is lower than is possible\n", frequency); + pr_error(" (you may want to derive your clock from an integer divider based off of a PLL)\n"); + return EINVAL; + } + + // Decouple ourselves from configuration of the clock, so we can adjust it without worrying about it blocking. + cgu->pll1.block_during_frequency_changes = 0; + + // Set the source for the relevant PLL. + input_frequency = set_source_for_generated_clock(CLOCK_SOURCE_PLL1); + + // Check to make sure the input frequency isn't too low. + if (input_frequency < input_low_bound) { + pr_error("error: cannot drive PLL1 from a %" PRIu32 " Hz clock; must be at least %" PRIu32 " Hz\n", + input_frequency, input_low_bound); + return EIO; + } + + // Configure the PLL itself. + rc = platform_configure_main_pll_parameters(frequency, input_frequency); + if (rc) { + return rc; + } + + // Wait for a lock to occur. + time_base = get_time(); + while (!cgu->pll1.locked) { + if (get_time_since(time_base) > pll_lock_timeout) { + pr_error("error: PLL lock timed out (attempt %d)!\n", config->failure_count); + config->failure_count += 1; + return ETIMEDOUT; + } + } + + // Verify that we produced an appropriate source frequency for the device. + rc = platform_verify_source_frequency(CLOCK_SOURCE_PLL1); + if (rc) { + return rc; + } + + platform_handle_clock_source_frequency_change(CLOCK_SOURCE_PLL1); + return 0; +} + +/** + * @return an integer representing the likely-intended clock frequency for the primary input source, in MHz. + */ +static unsigned platform_identify_clock_frequency_mhz(clock_source_t source) +{ + const uint32_t rounding_factor = MHZ /2; + + // Get the input frequency of our main clock input. + clock_source_t physical_source = platform_get_physical_clock_source(source); + uint32_t frequency = platform_clock_source_configurations[physical_source].frequency; + + // Round the frequency to the nearest MHz and return. + return (frequency + rounding_factor) / MHZ; +} + +/** + * Conigure the USB PLL to produce the freuqencies necessary to drive the platform's + * various USB controllers. + */ +static int platform_bring_up_audio_pll(void) +{ + // TODO: Implement support for the audio PL. + pr_error("error: clock: audio PLL support not yet implemeneted!"); + return ENOSYS; +} + +/** + * Conigure the USB PLL to produce the freuqencies necessary to drive the platform's + * various USB controllers. + */ +static int platform_bring_up_usb_pll(void) +{ + const uint32_t usb_pll_target = 480 * MHZ; + unsigned source_frequency; + + // Pre-computed and encoded multiplier/divider constants for the USB PLL. + // From datasheet table 152 (section 13.8.3). + // TODO: replace this LUT with a computation, probably? + const uint32_t m_divider_constants[] = { + 0x00000000, 0x073e56c9, 0x073e2dad, 0x0b3e34b1, // 0, 1, 2, 3 MHz + 0x0e3e7777, 0x0d326667, 0x0b2a2a66, 0x00000000, // 4, 5, 6, 7 + 0x08206aaa, 0x00000000, 0x071a7faa, 0x00000000, // 8, 9, 10, 11, + 0x06167ffa, 0x00000000, 0x00000000, 0x05123fff, // 12, 13, 14, 15, + 0x04101fff, 0x00000000, 0x00000000, 0x00000000, // 16, 17, 18, 19 + 0x040e03ff, 0x00000000, 0x00000000, 0x00000000, // 20, 21, 22, 23, + 0x030c00ff // 24 + }; + const uint32_t np_divider_constant = 0x00302062; + + // Time to wait for the USB PLL to lock up. + const uint32_t pll_lock_timeout = 1000000; // 1 second; this should probably be made tweakable + + platform_clock_generation_register_block_t *cgu = get_platform_clock_generation_registers(); + uint32_t time_base; + + // Get the clock that should be the basis for our frequency. + platform_clock_source_configuration_t *config = &platform_clock_source_configurations[CLOCK_SOURCE_PLL0_USB]; + + // Ensure the relevant clock is up. + int rc = platform_handle_dependencies_for_clock_source(config->source); + if (rc) { + pr_warning("critical: failed to bring up source %s for USB PLL; falling back to internal oscillator!\n", + platform_get_clock_source_name(config->source)); + config->source = CLOCK_SOURCE_INTERNAL_OSCILLATOR; + } + + // TODO: support frequencies that aren't simple integers. + source_frequency = platform_identify_clock_frequency_mhz(config->source); + + // If the relevant clock is already up and okay, we're done! + if(platform_clock_source_is_configured(CLOCK_SOURCE_PLL0_USB)) { + return 0; + } + + // For now, ensure that we're trying to program a supported PLL frequency. + if (config->frequency != usb_pll_target) { + pr_error("error: cannot currently configure USB PLLs to frequencies other than %" PRIu32, usb_pll_target); + return EINVAL; + } + + // Check to ensure we can produce the relevant clock. + if ((source_frequency > 24) || (m_divider_constants[source_frequency] == 0)) { + pr_error("error: pll0-usb: cannot currently generate a USB clock from %s running at %" PRIu32 "\n", + platform_get_clock_source_name(config->source), platform_get_clock_source_frequency(config->source)); + } + + // Power off the PLL for configuration. + cgu->pll_usb.powered_down = 1; + cgu->pll_usb.block_during_frequency_changes = 0; + + // Configure the PLL's source. + cgu->pll_usb.source = platform_get_physical_clock_source(config->source); + + // And apply the relevant PLL settings. + cgu->pll_usb.m_divider_encoded = m_divider_constants[source_frequency]; + cgu->pll_usb.np_divider_encoded = np_divider_constant; + + // Set the PLL to simple direct-mode. + cgu->pll_usb.direct_input = 1; + cgu->pll_usb.direct_output = 1; + cgu->pll_usb.clock_enable = 1; + cgu->pll_usb.set_free_running = 0; + + // Turn the PLL on... + cgu->pll_usb.powered_down = 0; + + // ... and wait for it to lock. + time_base = get_time(); + while (!cgu->pll_usb.locked) { + if (get_time_since(time_base) > pll_lock_timeout) { + + pr_error("error: PLL lock timed out (attempt %d)!\n", config->failure_count); + config->failure_count += 1; + + return ETIMEDOUT; + } + } + + // If we got here, we should be live! + cgu->pll_usb.bypassed = false; + return platform_verify_source_frequency(CLOCK_SOURCE_PLL0_USB); +} + + +/** + * Ensures that all hardware dependencies are met to use the provided clock source, + * bringing up any dependencies as needed. + * + * + * + * @param source The source for which dependencies should be identified. + * @return 0 on success, or an error code on failure. + */ +static int platform_handle_dependencies_for_clock_source(clock_source_t source) +{ + source = platform_get_physical_clock_source(source); + + switch(source) { + + // If the requisite source is an xtal, start it. + case CLOCK_SOURCE_XTAL_OSCILLATOR: + return platform_ensure_main_xtal_is_up(); + case CLOCK_SOURCE_32KHZ_OSCILLATOR: + return platform_ensure_rtc_xtal_is_up(); + + // If the source is a direct clock input, ensure we can access it. + case CLOCK_SOURCE_ENET_RX_CLOCK: + case CLOCK_SOURCE_ENET_TX_CLOCK: + case CLOCK_SOURCE_GP_CLOCK_INPUT: + return platform_route_clock_input(source); + + // If the source is one of our dividers, start it. + case CLOCK_SOURCE_DIVIDER_A_OUT: + case CLOCK_SOURCE_DIVIDER_B_OUT: + case CLOCK_SOURCE_DIVIDER_C_OUT: + case CLOCK_SOURCE_DIVIDER_D_OUT: + case CLOCK_SOURCE_DIVIDER_E_OUT: + return platform_bring_up_clock_divider(source); + + // If the clock source is based on the main PLL, bring it up. + case CLOCK_SOURCE_PLL1: + return platform_bring_up_main_pll(platform_clock_source_configurations[source].frequency); + + // If the clock source is based on a fast PLL, bring it up. + case CLOCK_SOURCE_PLL0_USB: + return platform_bring_up_usb_pll(); + case CLOCK_SOURCE_PLL0_AUDIO: + return platform_bring_up_audio_pll(); + + // The internal oscillator is always up, so we don't need to handle any + // dependencies for it. + case CLOCK_SOURCE_INTERNAL_OSCILLATOR: + return 0; + + // If we've received another clock source, something's gone wrong. + // fail out. + default: + pr_error("clock: clould not bring up clock #%d (%s) as we don't know how!\n", + source, platform_get_clock_source_name(source)); + return ENODEV; + } +} + + +/** + * Handles any changes to a given clock source. + */ +static void platform_handle_clock_source_frequency_change(clock_source_t source) +{ + platform_clock_generation_register_block_t *cgu = get_platform_clock_generation_registers(); + const clock_source_t dividers[] = + { CLOCK_SOURCE_DIVIDER_A_OUT, CLOCK_SOURCE_DIVIDER_B_OUT, CLOCK_SOURCE_DIVIDER_C_OUT, + CLOCK_SOURCE_DIVIDER_D_OUT, CLOCK_SOURCE_DIVIDER_E_OUT }; + + // Notify any base clocks that depend on us of the change. + for(unsigned i = 0; i < ARRAY_SIZE(all_base_clocks); ++i) { + platform_base_clock_t *base = all_base_clocks[i]; + + if (!base->power_down && (base->source == source)) { + platform_handle_base_clock_frequency_change(base); + } + } + + // Notify the descendents of any sources that depend on us. + if (!cgu->pll1.power_down && (cgu->pll1.source == source)) { + platform_handle_clock_source_frequency_change(CLOCK_SOURCE_PLL1); + } + if (!cgu->pll_usb.powered_down && (cgu->pll_usb.source == source)) { + platform_handle_clock_source_frequency_change(CLOCK_SOURCE_PLL0_USB); + } + if (!cgu->pll_audio.core.powered_down && (cgu->pll_audio.core.source == source)) { + platform_handle_clock_source_frequency_change(CLOCK_SOURCE_PLL0_AUDIO); + } + + // Notify any descendents of any dividers that depend on us. + for(unsigned i = 0; i < ARRAY_SIZE(dividers); ++i) { + platform_base_clock_t *base = platform_base_clock_for_divider(dividers[i]); + + if (!base->power_down && (base->source == source)) { + platform_handle_base_clock_frequency_change(base); + } + } + + // TODO: allow downstream components to register monitors for clock sources + // which should be notified, here! +} + +/** + * Handles any changes to a provided clock. + */ +void platform_handle_branch_clock_frequency_change(platform_branch_clock_t *clock) +{ + platform_clock_control_register_block_t *ccu = get_platform_clock_control_registers(); + + // TODO: allow downstream components to register monitors for base clock changes + // which should be notified, here! + + // FIXME: Don't hardcode this! This is just a shim until we have a proper callback system. + if (clock == &ccu->m4.timer3) { + handle_platform_timer_frequency_change(); + } + +} + +/** + * Handles any changes to a provided clock. + */ +void platform_handle_base_clock_frequency_change(platform_base_clock_t *clock) +{ + + // Notify any branch clocks that depend on us. + for (unsigned i = 0; i < ARRAY_SIZE(all_branch_clocks); ++i) { + platform_branch_clock_t *branch = all_branch_clocks[i]; + platform_base_clock_t *base = platform_get_clock_base(branch);\ + + // FIXME: should this not notify base objects that are disabled? + if (!base->power_down && (base == clock)) { + platform_handle_branch_clock_frequency_change(branch); + } + } + + // TODO: allow downstream components to register monitors for base clock changes + // which should be notified, here! +} + + + +/** + * Translates a given clock source into the correct physical clock source-- + * handling virtual clock sources such as CLOCK_SOURCE_PRIMARY. + */ +clock_source_t platform_get_physical_clock_source(clock_source_t source) +{ + + if (source == CLOCK_SOURCE_PRIMARY) { + if (platform_early_init_complete) { + source = platform_determine_primary_clock_source(); + } else { + source = CLOCK_SOURCE_INTERNAL_OSCILLATOR; + } + } + if (source == CLOCK_SOURCE_PRIMARY_INPUT) { + source = platform_determine_primary_clock_input(); + } + + return source; +} + + +/** + * Set up the source for a provided generic base clock. + * + * @param clock The base clock to be configured. + * @param source The clock source for the given clock. + */ +int platform_select_base_clock_source(platform_base_clock_t *clock, clock_source_t source) +{ + int rc = 0; + + // Special case: if we have a virtual source, replace the variable with the real clock source + // behind it. see platform_determine_primary_clock_source() / platform_determine_primary_clock_input() + // for information on how downstream software can influence the primary source selection. + source = platform_get_physical_clock_source(source); + + // Before we can switch to the given source, ensure that we can use it. + rc = platform_handle_dependencies_for_clock_source(source); + if (rc) { + pr_critical("critical: failed to bring up clock source %s (%d)! Falling back to internal oscillator.\n", + platform_get_clock_source_name(source), rc); + source = CLOCK_SOURCE_INTERNAL_OSCILLATOR; + } + + clock->block_during_changes = 1; + clock->source = source; + + // Notify any consumers of the change. + platform_handle_base_clock_frequency_change(clock); + return rc; +} + +/** + * @returns true iff the given branch clock is divideable + */ +static bool platform_branch_clock_is_divideable(platform_branch_clock_t *clock) +{ + const platform_branch_clock_t *divideable_clocks[] = { + BRANCH_CLOCK(m4.emcdiv), BRANCH_CLOCK(m4.flasha), BRANCH_CLOCK(m4.flashb), + BRANCH_CLOCK(m4.m0app), BRANCH_CLOCK(m4.adchs), BRANCH_CLOCK(m4.eeprom) + }; + + // Check to see if the clock is in our list of divideable clocks. + for (unsigned i = 0; i < ARRAY_SIZE(divideable_clocks); ++i) { + if (divideable_clocks[i] == clock) { + return true; + } + } + + // If it's not, then it's not divideable. + return false; +} + + +/** + * Turns on the clock for a given peripheral. + * (clocks for this function are found in the clock control register block.) + * + * @param clock The clock to enable. + */ +void platform_enable_branch_clock(platform_branch_clock_register_t *clock, bool divide_by_two) +{ + int rc; + + // Try to find each of the clocks that this perpiheral might depend on. + platform_base_clock_register_t *base = platform_get_clock_base(clock); + platform_branch_clock_register_t *bus = platform_get_bus_clock(clock); + + // If we've found either of the clocks we depend on, enable them. + if (base) { + rc = platform_enable_base_clock(base); + if (rc) { + pr_warning("warning: failed to set up base clock for branch %s\n", platform_get_branch_clock_name(clock)); + } + } + if (bus) { + platform_enable_branch_clock(bus, false); + } + + // Zero out the advanced clock configuration options. + clock->control.disable_when_bus_transactions_complete = 0; + clock->control.wake_after_powerdown = 0; + + // If we're dividing by two, mark the divisor. + if (platform_branch_clock_is_divideable(clock)) { + clock->control.divisor = divide_by_two ? 1 : 0; + } + + // Finally, enable the given clock. + clock->control.enable = 1; +} + + +/** + * @returns true iff the provided branch clock is critical and must remain on. + */ +bool platform_branch_clock_must_remain_on(platform_branch_clock_register_t *clock) +{ + // List of critical clocks we must never turn off. + platform_branch_clock_register_t *critical_clocks[] = { BRANCH_CLOCK(m4.bus), BRANCH_CLOCK(m4.core) }; + + // Check to see if the given clock is in our list of critical clocks. + for (unsigned i = 0; i < ARRAY_SIZE(critical_clocks); ++i) { + if (clock == critical_clocks[i]) { + return true; + } + } + + // If it wasn't, then allow it to be disabled. + return false; +} + + +/** + * Turns off the clock for a given peripheral. + * (clocks for this function are found in the clock control register block.) + * + * @param clock The clock to disable. + */ +void platform_disable_branch_clock(platform_branch_clock_register_t *clock) +{ + // Try to find the base_clock that owns the given clock. + // this is the internal source that drives the relevant peripheral's clock. + platform_base_clock_register_t *base = platform_get_clock_base(clock); + + // If this clock must remain on, never disable it. + if (platform_branch_clock_must_remain_on(clock)) { + return; + } + + pr_debug("clock: disabling branch clock %s (%p)\n", platform_get_branch_clock_name(clock), clock); + + // Per the datasheet, disabling the clock should happen as two steps: + // - we should set auto-disable-when-not-clocked, and then + // - as a separate write, we should clear the enable bit. + // we use a full barrier to ensure these writes aren't merged. + clock->control.disable_when_bus_transactions_complete = 1; + clock->control.wake_after_powerdown = 1; + __sync_synchronize(); + clock->control.enable = 0; + + // If this branch clock has a parent clock, we'll disable it iff it's no longer + // used. This allows us to save power. + if (base) { + platform_disable_base_clock_if_unused(base); + } +} + + +/** + * Default function that determines the primary clock source, which will drive + * most of the major clocking sections of the device. + */ +ATTR_WEAK clock_source_t platform_determine_primary_clock_source(void) +{ + return CLOCK_SOURCE_PLL1; +} + + +/** + * Function that determines the primary clock input, which determines which + * root clock (i.e. which oscillator) is accepted to drive the primary clock source. + */ +ATTR_WEAK clock_source_t platform_determine_primary_clock_input(void) +{ + return CLOCK_SOURCE_XTAL_OSCILLATOR; +} + + + +/** + * @returns the frequency of the given clock source, in Hz. + */ +static uint32_t platform_get_clock_source_frequency(clock_source_t source) +{ + // Get a quick reference to the clock source's current state. + platform_clock_source_configuration_t *config; + + // Get the configuration for the relevant clock source, ensuring we're always + // dealing with a physical clock source. + source = platform_get_physical_clock_source(source); + config = &platform_clock_source_configurations[source]; + + // If we don't have an actual frequency, attempt to measure one. + if (config->frequency_actual == 0) { + + // If we can use our measurement hardware, do so. + if (platform_early_init_complete) { + pr_debug("clock: unknown frequency for source %s (%d); attempting to measure\n", + platform_get_clock_source_name(source), source); + + platform_verify_source_frequency(source); + pr_debug("clock: frequency meausred at %" PRIu32 " Hz\n", config->frequency_actual); + } + + // Otherwise, just assume the relevant frequency. + else { + return config->frequency; + } + } + + // Return the final actual frequency for the given source. + return config->frequency_actual; +} + + + +/** + * Returns the divider for the given base clock. For most of the base clocks, + * there's no divider, and the divider is thus always 1. For the integer divider + * clocks, this can be a dynamic number.. + */ +static uint32_t platform_base_clock_get_divisor(platform_base_clock_t *clock) +{ + // List of clocks that have a divisor. + platform_base_clock_t *dividable_clocks[] = { + BASE_CLOCK(idiva), + BASE_CLOCK(idivb), + BASE_CLOCK(idivc), + BASE_CLOCK(idivd), + BASE_CLOCK(idive) + }; + + // Check to see if the given clock is in our list of clocks with dividers... + for (unsigned i = 0; i < ARRAY_SIZE(dividable_clocks); ++i) { + + // .. if it is, grab its divisor. + if (clock == dividable_clocks[i]) { + return clock->divisor + 1; + } + + } + + // If it wasn't, then its divisor is always '1'. + return 1; +} + + +/** + * @returns the frequency of the provided base clock, in Hz. + */ +uint32_t platform_get_base_clock_frequency(platform_base_clock_t *clock) +{ + // Find the frequency of the clock source, and our local divisor. + uint32_t source_frequency = platform_get_clock_source_frequency(clock->source); + uint32_t divisor = platform_base_clock_get_divisor(clock); + + // Return the relevant clock frequency. + return source_frequency / divisor; +} + +/** + * @returns the frequency of the provided branch clock, in Hz. + */ +uint32_t platform_get_branch_clock_frequency(platform_branch_clock_t *clock) +{ + uint32_t base_frequency; + uint32_t divisor = 1; + + // Find the base clock off of which the given clock is based. + platform_base_clock_register_t *base = platform_get_clock_base(clock); + + // If we couldn't find one, we can't figure out this clock's frequency. Abort. + if (!base) { + return 0; + } + + if (platform_branch_clock_is_divideable(clock)) { + divisor = clock->control.current_divisor + 1; + } + + // Find the frequency of our base clock. + base_frequency = platform_get_base_clock_frequency(base); + + // Finally, return our base frequency, factoring in our clock's divisor. + return base_frequency / divisor; +} + + +/** + * @returns the clock source that drives the given branch clock + */ +clock_source_t platform_get_branch_clock_source(platform_branch_clock_t *clock) +{ + // Find the base clock off of which the given clock is based. + platform_base_clock_register_t *base = platform_get_clock_base(clock); + + // If we couldn't find one, we can't figure out this clock's frequency. Abort. + if (!base) { + return platform_get_physical_clock_source(CLOCK_SOURCE_PRIMARY); + } + + // Return the base clock's source. + return base->source; +} + + +/** + * @return the configured parent source for the given clock, or 0 if the clock doesn't appear to have one + */ +clock_source_t platform_get_parent_clock_source(clock_source_t source) +{ + return platform_clock_source_configurations[source].source; +} + + + +/** + * Initialize any clocks that need to be brought up at the very beginning + * of system initialization. + */ +void platform_initialize_early_clocks(void) +{ + platform_clock_generation_register_block_t *cgu = get_platform_clock_generation_registers(); + platform_early_init_complete = false; + + // Switch the system clock onto the 12MHz internal oscillator for early initialization. + // This gives us a stable clock to work with to set up everything else. + platform_select_base_clock_source(&cgu->m4, CLOCK_SOURCE_INTERNAL_OSCILLATOR); + + // Set up our microsecond timer, which we'll need to handle clock bringup, + // as several of our clocks require us to wait a controlled time. + set_up_platform_timers(); + + // Mark our early-init as complete. + platform_early_init_complete = true; +} + + +/** + * Initialize all of the system's clocks -- called by the crt0 as part of the platform setup. + */ +void platform_initialize_clocks(void) +{ + // Soft start the CPU clock. + platform_soft_start_cpu_clock(); + + // For now, enable all branch clocks. This also inherently configures the hardware necessary + // to generate the relevant clock. TODO: disable branch clocks here, instead, and let the downstream + // library users + for (unsigned i = 0; i < ARRAY_SIZE(all_branch_clocks); ++i) { + platform_enable_branch_clock(all_branch_clocks[i], false); + } + + pr_info("System clock bringup complete.\n"); +} diff --git a/firmware/platform/lpc43xx/drivers/platform_config.c b/firmware/platform/lpc43xx/drivers/platform_config.c new file mode 100644 index 0000000..7aedf56 --- /dev/null +++ b/firmware/platform/lpc43xx/drivers/platform_config.c @@ -0,0 +1,52 @@ +/* + * This file is part of libgreat + * + * LPC43xx misc configuration register control + */ + +#include + +/** + * Return a reference to the LPC43xx's CREG block. + */ +platform_configuration_registers_t *get_platform_configuration_registers(void) +{ + return (platform_configuration_registers_t *)0x40043000; +} + + +/** + * Remaps the M4 core's address zero to exist in the given region. + * + * @param base_addr A pointer to the region to be mapped in. + */ +void platform_remap_address_zero(volatile void *base_addr) +{ + get_platform_configuration_registers()->m4memmap = (uint32_t)base_addr; +} + + +/** + * @return returns true iff the calling thread is running on the M4 + */ +bool platform_running_on_m4(void) +{ +#ifdef LPC43XX_M4 + return true; +#else + return false; +#endif +} + + +/** + * @return returns true iff the calling thread is running on the M0 + */ +bool platform_running_on_m0(void) +{ +#ifdef LPC43XX_M0 + return true; +#else + return false; +#endif +} diff --git a/firmware/platform/lpc43xx/drivers/platform_reset.c b/firmware/platform/lpc43xx/drivers/platform_reset.c new file mode 100644 index 0000000..a1021f3 --- /dev/null +++ b/firmware/platform/lpc43xx/drivers/platform_reset.c @@ -0,0 +1,119 @@ +/** + * This file is part of libgreat + * + * LPC43xx reset generation/control driver + */ + +#include +#include + +/** + * Return a reference to the LPC43xx's RGU block. + */ +platform_reset_register_block_t *get_platform_reset_registers(void) +{ + return (platform_reset_register_block_t *)0x40053000; +} + + +/** + * Returns a reference to the LPC43xx's watchdog timer block. + */ +platform_watchdog_register_block_t *get_platform_watchdog_registers() +{ + return (platform_watchdog_register_block_t *)0x40080000; +} + + +/** + * Reset everything except for the always-on / RTC power domain. + */ +static void platform_core_reset(void) +{ + platform_reset_register_block_t *rgu = get_platform_reset_registers(); + rgu->core_reset = 1; +} + + +/** + * Feed the platform's watchdog timer, noting that the system is still alive. + */ +void platform_watchdog_feed(void) +{ + platform_watchdog_register_block_t *wwdt = get_platform_watchdog_registers(); + + // Issue the write sequence that should feed the watchdog. + wwdt->feed = 0xAA; + wwdt->feed = 0x55; +} + + +/** + * Reset everything including the always-on / RTC power domain. + */ +static void platform_watchdog_reset(void) +{ + const uint32_t default_watchdog_timeout = 100000; + + platform_watchdog_register_block_t *wwdt = get_platform_watchdog_registers(); + + wwdt->enable = 1; + wwdt->reset_enable = 1; + wwdt->timeout = default_watchdog_timeout; + + platform_watchdog_feed(); +} + + +/** + * Software reset the entire system. + * + * @param true iff the always-on power domain should be included + */ +void platform_software_reset(bool include_always_on_domain) +{ + if (include_always_on_domain) { + platform_watchdog_reset(); + } else { + platform_core_reset(); + } +} + + +/** + * @return true iff the system reset was an unintentional watchdog reset + * tries to ignore cases where a soft-reset used the watchdog to implement the reset itself + */ +bool platform_reset_was_watchdog_timeout(void) +{ + platform_watchdog_register_block_t *wwdt = get_platform_watchdog_registers(); + reset_reason_t reported_reset_reason = system_reset_reason(); + + // If the watchdog didn't time out in the previous iteration, this can't be a watchdog timeout. + if (!wwdt->timed_out) { + return false; + } + + // If the watchdog did time out, this wasn't necessarily a true watchdog timeout, as sometimes + // we use the a watchdog reset to trigger a full system reset from software. + switch (reported_reset_reason) { + + // Filter out faults and soft-resets, as those both can trigger the WWDT, + // and are not direct watchdog timer resets. + case RESET_REASON_FAULT: + case RESET_REASON_SOFT_RESET: + return false; + + default: + return true; + } +} + +/** + * Clears any system state necessary to track the system's state across resets. + */ +void platform_initialize_reset_driver(void) +{ + platform_watchdog_register_block_t *wwdt = get_platform_watchdog_registers(); + wwdt->timed_out = 0; +} diff --git a/firmware/platform/lpc43xx/drivers/platform_timer.c b/firmware/platform/lpc43xx/drivers/platform_timer.c new file mode 100644 index 0000000..2211d50 --- /dev/null +++ b/firmware/platform/lpc43xx/drivers/platform_timer.c @@ -0,0 +1,161 @@ +/* + * This file is part of libgreat + * + * LPC43xx timer drivers + */ + +// Temporary debug. +#define LOCAL_FILE_OVERRIDE_LOGLEVEL + + +#include +#include + +#include + +/** + * Timer object for the timer reserved for system use. + */ +static timer_t platform_timer = { .reg = NULL, .number = TIMER3 }; + + +/** + * @returns a reference to the register bank for the given timer index. + */ +static platform_timer_registers_t *platform_get_timer_registers(timer_index_t index) +{ + switch(index) { + case TIMER0: return (platform_timer_registers_t *)0x40084000; + case TIMER1: return (platform_timer_registers_t *)0x40085000; + case TIMER2: return (platform_timer_registers_t *)0x400C3000; + case TIMER3: return (platform_timer_registers_t *)0x400C4000; + } + + return NULL; +} + + +/** + * @returns A reference to the clock that controls the given timer. + */ +static platform_branch_clock_t *platform_get_timer_clock(timer_index_t index) +{ + platform_clock_control_register_block_t *ccu = get_platform_clock_control_registers(); + + switch(index) { + case TIMER0: return &ccu->m4.timer0; + case TIMER1: return &ccu->m4.timer1; + case TIMER2: return &ccu->m4.timer2; + case TIMER3: return &ccu->m4.timer3; + } + + return NULL; +} + + + +/** + * Perform platform-specific initialization for an LPC43xx timer peripheral. + * + * @param timer The timer object to be initialized. + * @param index The number of the timer to be set up. + */ +void platform_timer_initialize(timer_t *timer, timer_index_t index) +{ + // Figure out the clock that drives the given timer, and the register bank that controls it. + platform_timer_registers_t *reg = platform_get_timer_registers(index); + platform_branch_clock_t *clock = platform_get_timer_clock(index); + + // Store a reference to the timer registers... + timer->reg = reg; + + // ... and ensure the relevant clock is enabled. + platform_enable_branch_clock(clock, false); +} + + +/** + * Sets the frequency of the given timer. For the LPC43xx, this recomputes the timer's divider. + * + * @param timer The timer to be configured. + * @param tick_freuqency The timer's tick frequency, in Hz. + */ +void platform_timer_set_frequency(timer_t *timer, uint32_t tick_frequency) +{ + platform_branch_clock_t *clock = platform_get_timer_clock(timer->number); + + // Identify the frequency of the timer's parent clock, and identify a divisor accordingly. + uint32_t base_frequency = platform_get_branch_clock_frequency(clock); + uint32_t target_divider = (double)base_frequency / (double)tick_frequency; + + pr_debug("timer%d: parent clock frequency identified as to %" PRIu32 " Hz\n", timer->number, base_frequency); + pr_debug("timer%d: divisor identified as %" PRIu32 "\n", timer->number, target_divider); + + // Apply our divisor in order to achieve as close as we can to our target output frequency. + timer->reg->prescaler = target_divider - 1; +} + + +/** + * Enables the given timer. Typically, you want to configure the timer + * beforehand with calls to e.g. platform_timer_set_frequency. + */ +void platform_timer_enable(timer_t *timer) +{ + timer->reg->enabled = 1; +} + + +/** + * Disables the given timer. + */ +void platform_timer_disable(timer_t *timer) +{ + timer->reg->enabled = 0; +} + + +/** + * @returns True iff the given timer is enabled. + */ +bool platform_timer_enabled(timer_t *timer) +{ + return timer->reg->enabled; +} + + + +/** + * @returns the current counter value of the given timer + */ +uint32_t platform_timer_get_value(timer_t *timer) +{ + return timer->reg->value; +} + + + +/** + * Sets up the system's platform timer. + * + * @returns A reference to the system's platform timer. + */ +timer_t *platform_set_up_platform_timer(void) +{ + timer_initialize(&platform_timer, platform_timer.number); + return &platform_timer; +} + + +/** + * @returns A reference to the system's platform timer, or NULL if it has not yet been set up. + */ +timer_t *platform_get_platform_timer(void) +{ + // If the platform timer hasn't been set up yet, enable it. + if (!platform_timer.reg || !platform_timer_enabled(&platform_timer)) { + return NULL; + } + + return &platform_timer; +} diff --git a/firmware/platform/lpc43xx/drivers/reset.c b/firmware/platform/lpc43xx/drivers/reset.c new file mode 100644 index 0000000..85ffeb0 --- /dev/null +++ b/firmware/platform/lpc43xx/drivers/reset.c @@ -0,0 +1,5 @@ +/* + * This file is part of libgreat + * + * LPC43xx reset generation/control driver + */ diff --git a/firmware/drivers/usb/comms_backend.c b/firmware/platform/lpc43xx/drivers/usb/comms_backend.c similarity index 91% rename from firmware/drivers/usb/comms_backend.c rename to firmware/platform/lpc43xx/drivers/usb/comms_backend.c index 52a1fc6..710dd61 100644 --- a/firmware/drivers/usb/comms_backend.c +++ b/firmware/platform/lpc43xx/drivers/usb/comms_backend.c @@ -12,8 +12,8 @@ #include #include -#include -#include +#include +#include #define LIBGREAT_REQUEST_CANCEL_VALUE (0xDEAD) @@ -131,7 +131,10 @@ static usb_request_status_t libgreat_comms_vendor_request_out_handler( // Otherwise, ACK the transcation. else { rc = usb_transfer_schedule_ack(endpoint->in); - return rc ? USB_REQUEST_STATUS_STALL : USB_REQUEST_STATUS_OK; + if(rc) { + pr_warning("warning: comms: could not ACK the start of a USB comms request (%d)\n", rc); + return USB_REQUEST_STATUS_STALL; + } } } @@ -184,10 +187,13 @@ static usb_request_status_t libgreat_comms_vendor_request_in_handler( if (sizeof(usb_data_out_buffer) < data_length) { data_length = sizeof(usb_data_out_buffer); } + // Schedule the transfer itself. - rc = usb_transfer_schedule_block(endpoint->in, usb_data_out_buffer, - data_length, NULL, NULL); - return rc ? USB_REQUEST_STATUS_STALL : USB_REQUEST_STATUS_OK; + rc = usb_transfer_schedule_block(endpoint->in, usb_data_out_buffer, data_length, NULL, NULL); + if (rc) { + pr_warning("warning: comms: could not respond to a USB comms request (%d) \n", rc); + return USB_REQUEST_STATUS_STALL; + } } // If this is the end of the DATA stage, queue an ACK for the status stage. @@ -195,7 +201,10 @@ static usb_request_status_t libgreat_comms_vendor_request_in_handler( if (stage == USB_TRANSFER_STAGE_DATA) { transaction_underway = false; rc = usb_transfer_schedule_ack(endpoint->out); - return rc ? USB_REQUEST_STATUS_STALL : USB_REQUEST_STATUS_OK; + if(rc) { + pr_warning("warning: comms: could not ACK the response to a USB comms request (%d)\n", rc); + return USB_REQUEST_STATUS_STALL; + } } return USB_REQUEST_STATUS_OK; @@ -227,16 +236,21 @@ static usb_request_status_t libgreat_comms_vendor_request_cancel_handler( // Schedule a respone rc = usb_transfer_schedule_block(endpoint->in, &last_errno, sizeof(last_errno), NULL, NULL); - return rc ? USB_REQUEST_STATUS_STALL : USB_REQUEST_STATUS_OK; + if(rc) { + pr_warning("warning: comms: could not send the response to a USB comms request (%d)\n", rc); + return USB_REQUEST_STATUS_STALL; + } } // If this is the data stage, ACk our transaction and complete. if (stage == USB_TRANSFER_STAGE_DATA) { rc = usb_transfer_schedule_ack(endpoint->out); - return rc ? USB_REQUEST_STATUS_STALL : USB_REQUEST_STATUS_OK; + if(rc) { + pr_warning("warning: comms: could not ACK the cancellation of a USB comms request (%d)\n", rc); + return USB_REQUEST_STATUS_STALL; + } } - return USB_REQUEST_STATUS_OK; } diff --git a/firmware/drivers/usb/lpc43xx/usb.c b/firmware/platform/lpc43xx/drivers/usb/usb.c similarity index 98% rename from firmware/drivers/usb/lpc43xx/usb.c rename to firmware/platform/lpc43xx/drivers/usb/usb.c index f54a204..a6ce74d 100644 --- a/firmware/drivers/usb/lpc43xx/usb.c +++ b/firmware/platform/lpc43xx/drivers/usb/usb.c @@ -2,16 +2,19 @@ * This file is part of GreatFET */ + +#include + #include #include #include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include "greatfet_core.h" #include @@ -20,6 +23,8 @@ #include #include +#include + // FIXME: Clean me up to use the USB_REG macro from usb_registers.h to reduce duplication! usb_peripheral_t WEAK usb_peripherals[] = {{ .controller = 0, }, { .controller = 1, }}; @@ -722,6 +727,7 @@ static void usb_interrupt_enable( } } + void usb_device_init( usb_peripheral_t* const device ) { @@ -732,6 +738,11 @@ void usb_device_init( usb_controller_reset(device); usb_controller_set_device_mode(device); + // Temporary: if we're in emergency mode, prevent high speed . + if (platform_get_parent_clock_source(CLOCK_SOURCE_PLL0_USB) == CLOCK_SOURCE_INTERNAL_OSCILLATOR) { + pr_warning("In emergency mode; disabling high speed USB.\n"); + USB0_PORTSC1_D |= USB0_PORTSC1_D_PFSC; + } // Set interrupt threshold interval to 0 USB0_USBCMD_D &= ~USB0_USBCMD_D_ITC_MASK; diff --git a/firmware/drivers/usb/lpc43xx/usb_host.c b/firmware/platform/lpc43xx/drivers/usb/usb_host.c similarity index 97% rename from firmware/drivers/usb/lpc43xx/usb_host.c rename to firmware/platform/lpc43xx/drivers/usb/usb_host.c index c6ea91a..394ad34 100644 --- a/firmware/drivers/usb/lpc43xx/usb_host.c +++ b/firmware/platform/lpc43xx/drivers/usb/usb_host.c @@ -6,13 +6,13 @@ #include #include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include "greatfet_core.h" #include "glitchkit.h" diff --git a/firmware/drivers/usb/lpc43xx/usb_host_stack.c b/firmware/platform/lpc43xx/drivers/usb/usb_host_stack.c similarity index 100% rename from firmware/drivers/usb/lpc43xx/usb_host_stack.c rename to firmware/platform/lpc43xx/drivers/usb/usb_host_stack.c diff --git a/firmware/drivers/usb/lpc43xx/usb_queue.c b/firmware/platform/lpc43xx/drivers/usb/usb_queue.c similarity index 99% rename from firmware/drivers/usb/lpc43xx/usb_queue.c rename to firmware/platform/lpc43xx/drivers/usb/usb_queue.c index e703193..7a329a7 100644 --- a/firmware/drivers/usb/lpc43xx/usb_queue.c +++ b/firmware/platform/lpc43xx/drivers/usb/usb_queue.c @@ -15,8 +15,8 @@ #include #include -#include -#include +#include +#include // FIXME abstract: #define USB_ALLOC_TIMEOUT_DEFAULT_US (1000000UL) diff --git a/firmware/drivers/usb/lpc43xx/usb_queue_host.c b/firmware/platform/lpc43xx/drivers/usb/usb_queue_host.c similarity index 98% rename from firmware/drivers/usb/lpc43xx/usb_queue_host.c rename to firmware/platform/lpc43xx/drivers/usb/usb_queue_host.c index 754b2d7..2d4c2a1 100644 --- a/firmware/drivers/usb/lpc43xx/usb_queue_host.c +++ b/firmware/platform/lpc43xx/drivers/usb/usb_queue_host.c @@ -10,11 +10,11 @@ #include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include "greatfet_core.h" #include "glitchkit.h" diff --git a/firmware/drivers/usb/lpc43xx/usb_request.c b/firmware/platform/lpc43xx/drivers/usb/usb_request.c similarity index 92% rename from firmware/drivers/usb/lpc43xx/usb_request.c rename to firmware/platform/lpc43xx/drivers/usb/usb_request.c index 9fef81c..2b065d8 100644 --- a/firmware/drivers/usb/lpc43xx/usb_request.c +++ b/firmware/platform/lpc43xx/drivers/usb/usb_request.c @@ -2,10 +2,10 @@ * This file is part of GreatFET */ -#include -#include -#include -#include +#include +#include +#include +#include #include #include @@ -37,28 +37,28 @@ static void usb_request( if(endpoint->device->controller == 1) { usb_request_handlers = &usb1_request_handlers; } - + usb_request_status_t status = USB_REQUEST_STATUS_STALL; usb_request_handler_fn handler = 0; - + switch( endpoint->setup.request_type & USB_SETUP_REQUEST_TYPE_mask ) { case USB_SETUP_REQUEST_TYPE_STANDARD: handler = usb_request_handlers->standard; break; - + case USB_SETUP_REQUEST_TYPE_CLASS: handler = usb_request_handlers->class; break; - + case USB_SETUP_REQUEST_TYPE_VENDOR: handler = usb_request_handlers->vendor; break; - + case USB_SETUP_REQUEST_TYPE_RESERVED: handler = usb_request_handlers->reserved; break; } - + if( handler ) { status = handler(endpoint, stage); } diff --git a/firmware/drivers/usb/lpc43xx/usb_standard_request.c b/firmware/platform/lpc43xx/drivers/usb/usb_standard_request.c similarity index 97% rename from firmware/drivers/usb/lpc43xx/usb_standard_request.c rename to firmware/platform/lpc43xx/drivers/usb/usb_standard_request.c index 65db1dc..61cc87f 100644 --- a/firmware/drivers/usb/lpc43xx/usb_standard_request.c +++ b/firmware/platform/lpc43xx/drivers/usb/usb_standard_request.c @@ -5,11 +5,11 @@ #include #include -#include +#include -#include -#include -#include +#include +#include +#include const uint8_t* usb_endpoint_descriptor( const usb_endpoint_t* const endpoint @@ -26,10 +26,10 @@ const uint8_t* usb_endpoint_descriptor( descriptor += descriptor[0]; } } - + return 0; } - + uint_fast16_t usb_endpoint_descriptor_max_packet_size( const uint8_t* const endpoint_descriptor ) { @@ -57,7 +57,7 @@ bool usb_set_configuration( const usb_configuration_t* new_configuration = 0; if( configuration_number != 0 ) { - + // Locate requested configuration. if( device->configurations ) { usb_configuration_t** configurations = *(device->configurations); @@ -78,7 +78,7 @@ bool usb_set_configuration( return false; } } - + if( new_configuration != device->configuration ) { // Configuration changed. device->configuration = new_configuration; @@ -89,7 +89,7 @@ bool usb_set_configuration( return true; } - + static usb_request_status_t usb_send_descriptor( usb_endpoint_t* const endpoint, const uint8_t* const descriptor_data @@ -148,7 +148,7 @@ static usb_request_status_t usb_standard_request_get_descriptor_setup( switch( endpoint->setup.value_h ) { case USB_DESCRIPTOR_TYPE_DEVICE: return usb_send_descriptor(endpoint, endpoint->device->descriptor); - + case USB_DESCRIPTOR_TYPE_CONFIGURATION: // TODO: Duplicated code. Refactor. if( usb_speed(endpoint->device) == USB_SPEED_HIGH ) { @@ -156,7 +156,7 @@ static usb_request_status_t usb_standard_request_get_descriptor_setup( } else { return usb_send_descriptor_config(endpoint, USB_SPEED_FULL, endpoint->setup.value_l); } - + case USB_DESCRIPTOR_TYPE_DEVICE_QUALIFIER: return usb_send_descriptor(endpoint, endpoint->device->qualifier_descriptor); @@ -167,10 +167,10 @@ static usb_request_status_t usb_standard_request_get_descriptor_setup( } else { return usb_send_descriptor_config(endpoint, USB_SPEED_HIGH, endpoint->setup.value_l); } - + case USB_DESCRIPTOR_TYPE_STRING: return usb_send_descriptor_string(endpoint); - + case USB_DESCRIPTOR_TYPE_INTERFACE: case USB_DESCRIPTOR_TYPE_ENDPOINT: default: @@ -185,7 +185,7 @@ static usb_request_status_t usb_standard_request_get_descriptor( switch( stage ) { case USB_TRANSFER_STAGE_SETUP: return usb_standard_request_get_descriptor_setup(endpoint); - + case USB_TRANSFER_STAGE_DATA: case USB_TRANSFER_STAGE_STATUS: return USB_REQUEST_STATUS_OK; @@ -212,7 +212,7 @@ static usb_request_status_t usb_standard_request_set_address( switch( stage ) { case USB_TRANSFER_STAGE_SETUP: return usb_standard_request_set_address_setup(endpoint); - + case USB_TRANSFER_STAGE_DATA: case USB_TRANSFER_STAGE_STATUS: /* NOTE: Not necessary to set address here, as DEVICEADR.USBADRA bit @@ -220,7 +220,7 @@ static usb_request_status_t usb_standard_request_set_address( * operation on IN ACK. */ return USB_REQUEST_STATUS_OK; - + default: return USB_REQUEST_STATUS_STALL; } @@ -251,11 +251,11 @@ static usb_request_status_t usb_standard_request_set_configuration( switch( stage ) { case USB_TRANSFER_STAGE_SETUP: return usb_standard_request_set_configuration_setup(endpoint); - + case USB_TRANSFER_STAGE_DATA: case USB_TRANSFER_STAGE_STATUS: return USB_REQUEST_STATUS_OK; - + default: return USB_REQUEST_STATUS_STALL; } @@ -286,7 +286,7 @@ static usb_request_status_t usb_standard_request_get_configuration( switch( stage ) { case USB_TRANSFER_STAGE_SETUP: return usb_standard_request_get_configuration_setup(endpoint); - + case USB_TRANSFER_STAGE_DATA: case USB_TRANSFER_STAGE_STATUS: return USB_REQUEST_STATUS_OK; @@ -305,13 +305,13 @@ usb_request_status_t usb_standard_request( switch( endpoint->setup.request ) { case USB_STANDARD_REQUEST_GET_DESCRIPTOR: return usb_standard_request_get_descriptor(endpoint, stage); - + case USB_STANDARD_REQUEST_SET_ADDRESS: return usb_standard_request_set_address(endpoint, stage); - + case USB_STANDARD_REQUEST_SET_CONFIGURATION: return usb_standard_request_set_configuration(endpoint, stage); - + case USB_STANDARD_REQUEST_GET_CONFIGURATION: return usb_standard_request_get_configuration(endpoint, stage); diff --git a/firmware/platform/lpc43xx/include/drivers/arm_system_control.h b/firmware/platform/lpc43xx/include/drivers/arm_system_control.h new file mode 100644 index 0000000..b770b99 --- /dev/null +++ b/firmware/platform/lpc43xx/include/drivers/arm_system_control.h @@ -0,0 +1,117 @@ +/* + * This file is part of libgreat. + * + * ARM system control drivers. + */ + +#ifndef __ARM_SYSTEM_CONTROL__ +#define __ARM_SYSTEM_CONTROL__ + +#include + +typedef volatile struct { + + const uint32_t cpuid; + + // Interrupt control and status register + uint32_t icsr; + + // Vector table offset register + uint32_t vtor; + + // Application interrupt and reset control register + uint32_t aircr; + + // System control register + uint32_t scr; + + // Configuration control register + uint32_t ccr; + + // System Handler Priority registers + uint32_t shpr[3]; + + // System Handler control/status registers + uint32_t shcsr; + + // Configurable fault status register + union { + uint32_t cfsr; + struct { + uint16_t ufsr; + uint8_t bfsr; + uint8_t mmfsr; + } __attribute__((packed)); + }; + + // Hard fault status register. + uint32_t hfsr; + + // Debug fault status register. + uint32_t dfsr; + + // Memory Management fault address register + uint32_t mmfar; + + // Bus Fault address register + uint32_t bfar; + + // Aux Fault status register + uint32_t afsr; + + // Processor feature register + const uint32_t pfr[1]; + + // Debug feature register. + const uint32_t dfr; + + // Aux feature register + const uint32_t afr; + + // Memory Model feature registert + const uint32_t mmfr[4]; + + // Instruction set attributes register. + const uint32_t isar[5]; + + RESERVED_WORDS(5); + + // Coprocessor access control register. + // Note that the FPU is actually split between two "co-processors", + // so there's actually two two-bit registers that must be set identically., + // We treat these as a single four-bit register. + struct { + uint32_t : 20; + uint32_t fpu_access : 4; + uint32_t : 8; + } cpacr; + +} ATTR_PACKED arm_system_control_register_block_t; + +ASSERT_OFFSET(arm_system_control_register_block_t, afsr, 0x3c); + + +/** + * Constants for the CPACR fpu_access bits. + */ +typedef enum { + FPU_DISABLED = 0b0000, + FPU_PRIVILEGED_ONLY = 0b0101, + FPU_FULL_ACCESS = 0b1111, +} fpu_access_rights_t; + + +/** + * @return a reference to the ARM SCB. + */ +arm_system_control_register_block_t *arch_get_system_control_registers(void); + + +/** + * Enables access to the system's FPU. + * + * @param allow_unprivileged_access True iff user-mode should be able to use the FPU. + */ +void arch_enable_fpu(bool allow_unprivileged_access); + +#endif diff --git a/firmware/platform/lpc43xx/include/drivers/ethernet/platform.h b/firmware/platform/lpc43xx/include/drivers/ethernet/platform.h new file mode 100644 index 0000000..1630fbb --- /dev/null +++ b/firmware/platform/lpc43xx/include/drivers/ethernet/platform.h @@ -0,0 +1,226 @@ +/* + * This file is part of libgreat + * + * LPC43xx Ethernet Complex data structures + */ + +#ifndef __LIBGREAT_ETHERNET_PLATFORM_H__ +#define __LIBGREAT_ETHERNET_PLATFORM_H__ + +#include +#include + +#include +#include +#include +#include +#include + + +// Opaque references to ethernet controller objects. +typedef struct ethernet_controller ethernet_controller_t; + + +/** + * Ethernet clock divider frequencies. + */ +enum { + CSR_DIV_BY_42 = 0, + CSR_DIV_BY_62 = 1, + CSR_DIV_BY_16 = 2, + CSR_DIV_BY_26 = 3, + CSR_DIV_BY_102 = 4, + CSR_DIV_BY_124 = 5, +}; + + + +/** + * Structure representing a MII configuration register. + */ +typedef volatile struct ATTR_PACKED { + uint32_t comms_in_progress : 1; + uint32_t write : 1; + uint32_t csr_clock_range : 4; + uint32_t register_index : 5; + uint32_t phy_address : 5; + uint32_t : 16; +} platform_ethernet_mii_address_register_t; + + +/** + * Structure representing the LPC43xx MAC configuration registers. + */ +typedef struct ATTR_PACKED { + uint32_t config; + uint32_t frame_filter; + uint64_t hashtable; + + /** + * Structure describing how management data should be addressed. + */ + platform_ethernet_mii_address_register_t mii_addr; + struct { + uint32_t mii_data : 16; + uint32_t : 16; + }; + + uint32_t flow_ctrl; + uint32_t vlan_tag; + uint32_t _reserved0; + uint32_t debug; + uint32_t rwake_frflt; + uint32_t pmt_ctrl_stat; + + RESERVED_WORDS(2); + + uint32_t intr; + uint32_t intr_mask; + uint64_t addr0; +} ethernet_mac_register_block_t; + +ASSERT_OFFSET(ethernet_mac_register_block_t, intr, 0x38); + +/** + * Structure representing the LPC43xx DMA configuration registers. + */ +typedef struct ATTR_PACKED { + uint32_t bus_mode; + uint32_t trans_poll_demand; + uint32_t rec_poll_demand; + uint32_t rec_des_addr; + uint32_t trans_des_addr; + uint32_t stat; + uint32_t op_mode; + uint32_t int_en; + uint32_t mfrm_bufof; + uint32_t rec_int_wdt; + + RESERVED_WORDS(8); + + uint32_t curhost_trans_des; + uint32_t curhost_rec_des; + uint32_t curhost_trans_buf; + uint32_t curhost_rec_buf; + +} ethernet_dma_register_block_t; + +ASSERT_OFFSET(ethernet_dma_register_block_t, curhost_trans_des, 0x48); + + +/** + * Structure representing the LPC43xx ethernet register block. + */ +typedef volatile struct ATTR_PACKED { + + // MAC control registers. + ethernet_mac_register_block_t mac; + + RESERVED_WORDS(431); + + // Time registers. + uint32_t subsecond_incr; + uint32_t seconds; + uint32_t nanoseconds; + uint32_t secondsupdate; + uint32_t nanosecondsupdate; + uint32_t addend; + uint32_t targetseconds; + uint32_t targetnanoseconds; + uint32_t highword; + uint32_t timestampstat; + + RESERVED_WORDS(565); + + // DMA configuration registers. + ethernet_dma_register_block_t dma; + +} ethernet_register_block_t; + + +ASSERT_OFFSET(ethernet_register_block_t, subsecond_incr, 0x0704); +ASSERT_OFFSET(ethernet_register_block_t, dma, 0x1000); + + +/** + * Platform-specific data for ethernet drivers. + */ +typedef struct { + + // Reference to the system's platform configuration registers. + platform_configuration_registers_t *creg; + + // Pointer to the clock that's used for the current controller. + platform_branch_clock_register_t *clock; + +} ethernet_platform_data_t; + + +/** + * Initialies a new ethernet controller object, and readies it (and the appropriate) + * hardware for use. + * + * @param An unpopulated ethernet device structure to be readied for use. + */ +void platform_ethernet_init(ethernet_controller_t *device); + + + +/** + * Queue a non-blocking MII transaction, which communicates with the PHY. + * + * @param Should be set to true iff the given operation is to be a write. + * @param register_index The PHY register address. + * @param value The value to be written to the given PHY register, or 0 for a read operation. + */ +static void platform_ethernet_mii_start_transaction(ethernet_controller_t *device, + bool is_write, uint8_t register_index, uint16_t value); + + +/** + * Queue a non-blocking MII write, which communicates with the PHY over the + * management interface. To emulate a blocking write, follow this up by calling + * for platform_ethernet_mii_complete_transaction(). + * + * @param register_index The PHY register address. + * @param value The value to be written to the given PHY register. + */ +void platform_ethernet_mii_write(ethernet_controller_t *device, uint8_t register_index, uint16_t value); + + +/** + * @return true iff a management read or write is currently in progress + */ +bool platform_ethernet_mii_write_in_progress(ethernet_controller_t *device); + +/** + * Blocks until the active management transaction completes. + * + * @returns The relevant MII data; mostly useful to retrieve the result + * of a completed read. + */ +uint16_t platform_ethernet_mii_complete_transaction(ethernet_controller_t *device); + + +/** + * Queue a non-blocking MII read, which communicates with the PHY over the + * management interface. The result of this read can be read by calling + * platform_ethernet_mii_complete_transcation(); its readiness can be checked using + * platform_ethernet_mii_write_in_progress. + * + * @param register_index The PHY register address. + * @param value The value to be written to the given PHY register. + */ +void platform_ethernet_mii_start_read(ethernet_controller_t *device, uint8_t register_index); + + +/** + * Blocking read from the PHY over the management interface. + * + * @param register_index The register to read from. + * @return The result of the read operation. + */ +uint16_t platform_ethernet_mii_read(ethernet_controller_t *device, uint8_t register_index); + + +#endif diff --git a/firmware/platform/lpc43xx/include/drivers/platform_clock.h b/firmware/platform/lpc43xx/include/drivers/platform_clock.h new file mode 100644 index 0000000..46023f1 --- /dev/null +++ b/firmware/platform/lpc43xx/include/drivers/platform_clock.h @@ -0,0 +1,596 @@ +/* + * This file is part of libgreat + * + * LPC43xx clock generation/control driver + */ + + +#ifndef __LIBGREAT_PLATFORM_CLOCK_H__ +#define __LIBGREAT_PLATFORM_CLOCK_H__ + +#include +#include + +/** + * Register field that describes a base clock in the Clock Generation Unit. + */ +typedef volatile union { + struct ATTR_PACKED{ + uint32_t power_down : 1; + uint32_t : 1; + uint32_t divisor : 8; + uint32_t : 1; + uint32_t block_during_changes : 1; + uint32_t : 12; + uint32_t source : 5; + uint32_t : 3; + }; + uint32_t all_bits; +} platform_base_clock_register_t; + +/** + * For now, assume that base clock objects are equivalent to references + * to their registers. + */ +typedef platform_base_clock_register_t platform_base_clock_t; + + +/** + * Various different sources that can drive various clock units. + */ +typedef enum { + + // Slow oscillators, both external (RTC) and internal (IRC). + CLOCK_SOURCE_32KHZ_OSCILLATOR = 0x00, + CLOCK_SOURCE_INTERNAL_OSCILLATOR = 0x01, + + // Clock inputs -- these accept clocks directly on a GPIO pin. + CLOCK_SOURCE_ENET_RX_CLOCK = 0x02, + CLOCK_SOURCE_ENET_TX_CLOCK = 0x03, + CLOCK_SOURCE_GP_CLOCK_INPUT = 0x04, + + // Main clock oscillator. + CLOCK_SOURCE_XTAL_OSCILLATOR = 0x06, + + // Derived clocks -- including PLLs and dividiers. + CLOCK_SOURCE_PLL0_USB = 0x07, + CLOCK_SOURCE_PLL0_AUDIO = 0x08, + CLOCK_SOURCE_PLL1 = 0x09, + CLOCK_SOURCE_DIVIDER_A_OUT = 0x0c, + CLOCK_SOURCE_DIVIDER_B_OUT = 0x0d, + CLOCK_SOURCE_DIVIDER_C_OUT = 0x0e, + CLOCK_SOURCE_DIVIDER_D_OUT = 0x0f, + CLOCK_SOURCE_DIVIDER_E_OUT = 0x10, + + // Total number of actual clock sources. + CLOCK_SOURCE_COUNT = 0x11, + + // Special value -- used to represent an unused or invalid clock. + CLOCK_SOURCE_NONE = 0x1D, + + // Special value -- the primary clock input to the system, which is usually the external oscillator. + // This is the clock that's used to drive e.g. the internal PLL that drives the main system clock. + // The downstream software can override this by defining platform_determine_primary_clock_input(). + CLOCK_SOURCE_PRIMARY_INPUT = 0x1E, + + // Special value -- use the system's "primary" clock source, which is usually an internal PLL. + // The downstream software can override this by defining platform_determine_primary_clock_source(). + CLOCK_SOURCE_PRIMARY = 0x1F, + +} clock_source_t; + + +/** + * Register pair that provides control and status for each downstream clock. + */ +typedef volatile struct ATTR_PACKED { + + /** + * Values to be applied to the given clock; these may not + * take effect immediately. Check the current register for the + * relevant value. + */ + struct { + uint32_t enable : 1; + uint32_t disable_when_bus_transactions_complete : 1; + uint32_t wake_after_powerdown : 1; + uint32_t : 2; + uint32_t divisor : 3; + uint32_t : 19; + uint32_t current_divisor : 3; + uint32_t : 2; + } control; + + /** + * Values currently being used by hardware for each clock. + */ + struct { + uint32_t enabled : 1; + uint32_t disable_when_bus_transactions_complete : 1; + uint32_t wake_after_powerdown : 1; + uint32_t : 2; + uint32_t disabled : 1; + uint32_t : 26; + } current; + +} platform_branch_clock_register_t; + +ASSERT_OFFSET(platform_branch_clock_register_t, current, 0x4); + + +/** + * For now, consider branch clocks and their registers the same thing. + */ +typedef platform_branch_clock_register_t platform_branch_clock_t; + + +/** + * Structure representing the clock control registers. + */ +typedef volatile struct ATTR_PACKED { + + // Power management register. + struct { + // Disable wakeable clocks. + // Powers down all clocks that can be automatically resumed after a power-down. + uint32_t power_down : 1; + uint32_t : 31; + } ccu1; + + // Base clock status for CCU1. + struct { + uint32_t apb3_needed : 1; + uint32_t apb1_needed : 1; + uint32_t spifi_needed : 1; + uint32_t m4_needed : 1; + uint32_t : 2; + uint32_t periph_needed : 1; + uint32_t usb0_needed : 1; + uint32_t usb1_needed : 1; + uint32_t spi_needed : 1; + uint32_t : 22; + }; + + RESERVED_WORDS(62); + + // APB3 Clock register pairs. + struct { + platform_branch_clock_register_t bus; + platform_branch_clock_register_t i2c1; + platform_branch_clock_register_t dac; + platform_branch_clock_register_t adc0; + platform_branch_clock_register_t adc1; + platform_branch_clock_register_t can0; + } apb3; + + RESERVED_WORDS(52); + + // APB1 Clock register pairs. + struct { + platform_branch_clock_register_t bus; + platform_branch_clock_register_t motocon_pwm; + platform_branch_clock_register_t i2c0; + platform_branch_clock_register_t i2s; + platform_branch_clock_register_t can1; + } apb1; + + RESERVED_WORDS(54); + + platform_branch_clock_register_t spifi; + + RESERVED_WORDS(62); + + // M4 core related clcoks. + struct { + platform_branch_clock_register_t bus; + platform_branch_clock_register_t spifi; + platform_branch_clock_register_t gpio; + platform_branch_clock_register_t lcd; + platform_branch_clock_register_t ethernet; + platform_branch_clock_register_t usb0; + platform_branch_clock_register_t emc; + platform_branch_clock_register_t sdio; + platform_branch_clock_register_t dma; + platform_branch_clock_register_t core; + RESERVED_WORDS(6); + platform_branch_clock_register_t sct; + platform_branch_clock_register_t usb1; + platform_branch_clock_register_t emcdiv; + platform_branch_clock_register_t flasha; + platform_branch_clock_register_t flashb; + platform_branch_clock_register_t m0app; + platform_branch_clock_register_t adchs; + platform_branch_clock_register_t eeprom; + RESERVED_WORDS(22); + platform_branch_clock_register_t wwdt; + platform_branch_clock_register_t usart0; + platform_branch_clock_register_t uart1; + platform_branch_clock_register_t ssp0; + platform_branch_clock_register_t timer0; + platform_branch_clock_register_t timer1; + platform_branch_clock_register_t scu; + platform_branch_clock_register_t creg; + RESERVED_WORDS(48); + platform_branch_clock_register_t ritimer; + platform_branch_clock_register_t usart2; + platform_branch_clock_register_t usart3; + platform_branch_clock_register_t timer2; + platform_branch_clock_register_t timer3; + platform_branch_clock_register_t ssp1; + platform_branch_clock_register_t qei; + } m4; + + RESERVED_WORDS(50); + + // Peripheral bus. + struct { + platform_branch_clock_register_t bus; + platform_branch_clock_register_t core; + platform_branch_clock_register_t sgpio; + } periph; + + RESERVED_WORDS(58); + + platform_branch_clock_register_t usb0; + + RESERVED_WORDS(62); + + platform_branch_clock_register_t usb1; + + RESERVED_WORDS(62); + + platform_branch_clock_register_t spi; + + RESERVED_WORDS(62); + + platform_branch_clock_register_t adchs; + + // Space until CGU2 + RESERVED_WORDS(318); + + // Power management register. + struct { + // Disable wakeable clocks. + // Powers down all clocks that can be automatically resumed after a power-down. + uint32_t power_down : 1; + uint32_t : 31; + } ccu2; + + // Base clock status. + struct { + uint32_t : 1; + uint32_t uart3_needed : 1; + uint32_t uart2_needed : 1; + uint32_t uart1_needed : 1; + uint32_t uart0_needed : 1; + uint32_t ssp1_needed : 1; + uint32_t ssp0_needed : 1; + uint32_t : 25; + }; + RESERVED_WORDS(62); + + platform_branch_clock_register_t audio; + RESERVED_WORDS(62); + + platform_branch_clock_register_t usart3; + RESERVED_WORDS(62); + + platform_branch_clock_register_t usart2; + RESERVED_WORDS(62); + + platform_branch_clock_register_t uart1; + RESERVED_WORDS(62); + + platform_branch_clock_register_t usart0; + RESERVED_WORDS(62); + + platform_branch_clock_register_t ssp1; + RESERVED_WORDS(62); + + platform_branch_clock_register_t ssp0; + RESERVED_WORDS(62); + + platform_branch_clock_register_t sdio; + RESERVED_WORDS(62); + + +} platform_clock_control_register_block_t; + + +ASSERT_OFFSET(platform_clock_control_register_block_t, apb3, 0x0100); +ASSERT_OFFSET(platform_clock_control_register_block_t, apb1, 0x0200); +ASSERT_OFFSET(platform_clock_control_register_block_t, spifi, 0x0300); +ASSERT_OFFSET(platform_clock_control_register_block_t, m4, 0x0400); +ASSERT_OFFSET(platform_clock_control_register_block_t, m4.core, 0x0448); +ASSERT_OFFSET(platform_clock_control_register_block_t, m4.sct, 0x0468); +ASSERT_OFFSET(platform_clock_control_register_block_t, m4.wwdt, 0x0500); +ASSERT_OFFSET(platform_clock_control_register_block_t, m4.ritimer, 0x0600); +ASSERT_OFFSET(platform_clock_control_register_block_t, periph, 0x0700); +ASSERT_OFFSET(platform_clock_control_register_block_t, usb0, 0x0800); +ASSERT_OFFSET(platform_clock_control_register_block_t, usb1, 0x0900); +ASSERT_OFFSET(platform_clock_control_register_block_t, spi, 0x0A00); +ASSERT_OFFSET(platform_clock_control_register_block_t, ccu2, 0x1000); + + +typedef volatile struct ATTR_PACKED { + + // Status register. + struct { + uint32_t locked : 1; + uint32_t is_free_running : 1; + uint32_t : 30; + }; + + // Control register. + struct { + uint32_t powered_down : 1; + uint32_t bypassed : 1; + uint32_t direct_input : 1; + uint32_t direct_output : 1; + uint32_t clock_enable : 1; + uint32_t : 1; + uint32_t set_free_running : 1; + uint32_t : 4; + uint32_t block_during_frequency_changes : 1; + uint32_t : 12; + uint32_t source : 5; + uint32_t : 3; + }; + + // M-divider register. + union { + + // Provide the individual parts... + struct { + uint32_t m_divider_coefficient : 17; + uint32_t bandwidth_p : 5; + uint32_t bandwidth_i : 6; + uint32_t bandwidth_r : 4; + }; + + // ... and the whole register. + uint32_t m_divider_encoded; + }; + + // NP-divider. + union { + + // Provide the individual parts... + struct { + uint32_t p_divider_coefficient : 7; + uint32_t : 5; + uint32_t n_divider_coefficient : 10; + uint32_t : 10; + }; + + // ... and the whole register. + uint32_t np_divider_encoded; + }; + +} platform_peripheral_pll_t; + + +/** + * Structure representing the clock generation registers. + */ +typedef volatile struct ATTR_PACKED { + RESERVED_WORDS(5); + + // Frequency monitor + struct { + uint32_t reference_ticks_remaining : 9; + uint32_t observed_clock_ticks : 14; + uint32_t measurement_active : 1; + uint32_t source_to_measure : 5; + uint32_t : 3; + } frequency_monitor; + + // XTAL oscillator control + struct { + uint32_t disabled : 1; + uint32_t bypass : 1; + uint32_t is_high_frequency : 1; + uint32_t : 29; + } xtal_control; + + // USB high-speed PLL. + platform_peripheral_pll_t pll_usb; + + // The PLL audio is a peripheral PLL with another register added for the fractional divider. + struct { + + // The core of the register is the main PLL itself... + platform_peripheral_pll_t core; + + // ... and an add-on represents our fractional divider. + struct { + uint32_t fractional_divider : 22; + uint32_t : 10; + }; + + } pll_audio; + + // Main PLL1. + struct { + + /* Status register. */ + struct { + uint32_t locked : 1; + uint32_t : 31; + }; + + /* Control register. */ + struct { + uint32_t power_down : 1; + uint32_t bypass_pll_entirely : 1; + uint32_t : 4; + uint32_t use_pll_feedback : 1; + uint32_t bypass_output_divider : 1; + uint32_t output_divisor_P : 2; + uint32_t : 1; + uint32_t block_during_frequency_changes : 1; + uint32_t input_divisor_N : 2; + uint32_t : 2; + uint32_t feedback_divisor_M : 8; + uint32_t source : 5; + uint32_t : 3; + }; + + } pll1; + + + // Integer divisor devices. + platform_base_clock_register_t idiva; + platform_base_clock_register_t idivb; + platform_base_clock_register_t idivc; + platform_base_clock_register_t idivd; + platform_base_clock_register_t idive; + + // Core base clocks. + platform_base_clock_register_t safe; + platform_base_clock_register_t usb0; + platform_base_clock_register_t periph; + platform_base_clock_register_t usb1; + platform_base_clock_register_t m4; + platform_base_clock_register_t spifi; + platform_base_clock_register_t spi; + platform_base_clock_register_t phy_rx; + platform_base_clock_register_t phy_tx; + platform_base_clock_register_t apb1; + platform_base_clock_register_t apb3; + platform_base_clock_register_t lcd; + platform_base_clock_register_t adchs; + platform_base_clock_register_t sdio; + platform_base_clock_register_t ssp0; + platform_base_clock_register_t ssp1; + platform_base_clock_register_t uart0; + platform_base_clock_register_t uart1; + platform_base_clock_register_t uart2; + platform_base_clock_register_t uart3; + platform_base_clock_register_t out; + RESERVED_WORDS(4); + platform_base_clock_register_t audio; + platform_base_clock_register_t out0; + platform_base_clock_register_t out1; +} platform_clock_generation_register_block_t; + +ASSERT_OFFSET(platform_clock_generation_register_block_t, frequency_monitor, 0x14); +ASSERT_OFFSET(platform_clock_generation_register_block_t, xtal_control, 0x18); +ASSERT_OFFSET(platform_clock_generation_register_block_t, pll_usb, 0x1c); +ASSERT_OFFSET(platform_clock_generation_register_block_t, pll_audio, 0x2c); +ASSERT_OFFSET(platform_clock_generation_register_block_t, pll1, 0x40); +ASSERT_OFFSET(platform_clock_generation_register_block_t, idiva, 0x48); +ASSERT_OFFSET(platform_clock_generation_register_block_t, audio, 0xc0); + + + +/** + * Helpful initialization macros. + */ +#define CGU_OFFSET(name) offsetof(platform_clock_generation_register_block_t, name) +#define CCU_OFFSET(name) offsetof(platform_clock_control_register_block_t, name) + + + +/** + * Return a reference to the LPC43xx's CCU block. + */ +platform_clock_control_register_block_t *get_platform_clock_control_registers(void); + + +/** + * Return a reference to the LPC43xx's CGU block. + */ +platform_clock_generation_register_block_t *get_platform_clock_generation_registers(void); + + +/** + * Turns on the clock for a given peripheral. + * (Clocks for this function are found in the clock control register block.) + * + * @param clock The clock to enable. + */ +void platform_enable_branch_clock(platform_branch_clock_register_t *clock, bool divide_by_two); + + +/** + * Turns off the clock for a given peripheral (not branch clocks). + * (Clocks for this function are found in the clock control register block.) + * + * @param clock The clock to enable. + */ +void platform_disable_clock(platform_branch_clock_register_t *clock); + + +/** + * Set up the source for a provided generic base clock. + * + * @param clock The base clock to be configured. + * @param source The clock source for the given clock. + * + * @return 0 on success, or an error number on failure + */ +int platform_select_base_clock_source(platform_base_clock_register_t *clock, clock_source_t source); + + +/** + * Function that determines the primary clock input, which determines which + * root clock (i.e. which oscillator) is accepted to drive the primary clock source. + * + * A default implementation is provided, but end software can override this + * to select a different alternate soruce e.g. programmatically. + */ +clock_source_t platform_determine_primary_clock_input(void); + + +/** + * Function that determines the primary clock source, which will drive + * most of the major clocking sections of the device. + * + * A default implementation is provided, but end software can override this + * to select a different alternate soruce e.g. programmatically. + */ +clock_source_t platform_determine_primary_clock_source(void); + + +/** + * Initialize all of the system's clocks -- typically called by the crt0 as part of the platform setup. + */ +void platform_initialize_clocks(void); + + +/** + * Initialize any clocks that need to be brought up at the very beginning + * of system initialization. Typically called by the crt0 as part of the platform setup. + */ +void platform_initialize_early_clocks(void); + + +/** + * Uses the LPC43xx's internal frequency monitor to detect the frequency of the given clock source. + * If trying to determine the internal clock frequency, the external oscillator must be up, as it will + * be used as the refernece clock. + * + * @param source The source to be meausred. + * @return The relevant frequency, in Hz, or 0 if the given clock is too low to measure. + */ +uint32_t platform_detect_clock_source_frequency(clock_source_t clock_to_detect); + + +/** + * @return a string containing the given clock source's name + */ +const char *platform_get_clock_source_name(clock_source_t source); + + +/** + * @returns the frequency of the provided branch clock, in Hz. + */ +uint32_t platform_get_branch_clock_frequency(platform_branch_clock_t *clock); + +/** + * @return the configured parent source for the given clock, or 0 if the clock doesn't appear to have one + */ +clock_source_t platform_get_parent_clock_source(clock_source_t source); + +#endif diff --git a/firmware/platform/lpc43xx/include/drivers/platform_config.h b/firmware/platform/lpc43xx/include/drivers/platform_config.h new file mode 100644 index 0000000..f6615e3 --- /dev/null +++ b/firmware/platform/lpc43xx/include/drivers/platform_config.h @@ -0,0 +1,96 @@ +/* + * This file is part of libgreat + * + * LPC43xx misc configuration registers + */ + +#ifndef __LPC43XX_PLATFORM_CONFIG__ +#define __LPC43XX_PLATFORM_CONFIG__ + +#include +#include +#include + +#include + +/** + * LPC43xx misc. configuration registers + */ +typedef volatile struct ATTR_PACKED { + RESERVED_WORDS(1); + + uint32_t creg0; + uint32_t creg1; + + RESERVED_WORDS(61); + + uint32_t m4memmap; + + RESERVED_WORDS(5); + + uint32_t creg5; + uint32_t dmamux; + uint32_t flashcfga; + uint32_t flashcfgb; + uint32_t etbcfg; + + // CREG6 + struct { + uint32_t ethmode : 3; + uint32_t : 1; + uint32_t ctoutctrl : 1; + uint32_t : 7; + uint32_t i2s0_tx_sck_in_sel : 1; + uint32_t i2s0_rx_sck_in_sel : 1; + uint32_t i2s1_tx_sck_in_sel : 1; + uint32_t i2s1_rx_sck_in_sel : 1; + uint32_t emc_clk_sel : 1; + uint32_t : 15; + }; + + uint32_t m4txevent; + + // TODO: implement the rest of this + +} platform_configuration_registers_t; + +ASSERT_OFFSET(platform_configuration_registers_t, creg0, 0x004); +ASSERT_OFFSET(platform_configuration_registers_t, m4memmap, 0x100); +ASSERT_OFFSET(platform_configuration_registers_t, etbcfg, 0x128); + +/** + * ETHMODE constants. + */ +enum { + ETHMODE_MII = 0, + ETHMODE_RMII = 4, +}; + + +/** + * Return a reference to the LPC43xx's CREG block. + */ +platform_configuration_registers_t *get_platform_configuration_registers(void); + + +/** + * Remaps the M4 core's address zero to exist in the given region. + * + * @param base_addr A pointer to the region to be mapped in. + */ +void platform_remap_address_zero(volatile void *base_addr); + + +/** + * @return returns true iff the calling thread is running on the M4 + */ +bool platform_running_on_m4(void); + + +/** + * @return returns true iff the calling thread is running on the M0 + */ +bool platform_running_on_m0(void); + + +#endif diff --git a/firmware/include/platform/lpc43xx/drivers/platform_gpio.h b/firmware/platform/lpc43xx/include/drivers/platform_gpio.h similarity index 100% rename from firmware/include/platform/lpc43xx/drivers/platform_gpio.h rename to firmware/platform/lpc43xx/include/drivers/platform_gpio.h diff --git a/firmware/platform/lpc43xx/include/drivers/platform_reset.h b/firmware/platform/lpc43xx/include/drivers/platform_reset.h new file mode 100644 index 0000000..d1c191e --- /dev/null +++ b/firmware/platform/lpc43xx/include/drivers/platform_reset.h @@ -0,0 +1,168 @@ +/** + * This file is part of libgreat + * + * LPC43xx reset generation/control driver + */ + + +#ifndef __LIBGREAT_PLATFORM_RESET_H__ +#define __LIBGREAT_PLATFORM_RESET_H__ + +#include +#include + +/** + * Structure representing the reset control registers. + */ +typedef volatile struct ATTR_PACKED { + + // Reset control register 0. + struct { + uint32_t core_reset : 1; + uint32_t perpiheral_reset : 1; + uint32_t master_reset : 1; + uint32_t : 1; + uint32_t watchdog_reset : 1; + uint32_t creg_reset : 1; + uint32_t : 1; + uint32_t : 1; + uint32_t bus_reset : 1; + uint32_t scu_reset : 1; + uint32_t : 2; + uint32_t m0_core_reset : 1; + uint32_t m4_core_reset : 1; + uint32_t : 2; + uint32_t lcd_reset : 1; + uint32_t usb0_reset : 1; + uint32_t usb1_reset : 1; + uint32_t dma_reset : 1; + uint32_t sdio_reset : 1; + uint32_t emc_reset : 1; + uint32_t ethernet_reset : 1; + uint32_t : 2; + uint32_t flash_a_reset : 1; + uint32_t : 1; + uint32_t eeprom_reset : 1; + uint32_t gpio_reset : 1; + uint32_t flash_b_reset : 1; + uint32_t : 2; + }; + + // Reset control register 1. + struct { + uint32_t timer0_reset : 1; + uint32_t timer1_reset : 1; + uint32_t timer2_reset : 1; + uint32_t timer3_reset : 1; + uint32_t rtimer_reset : 1; + uint32_t sct_reset : 1; + uint32_t motoconpwm_reset : 1; + uint32_t qei_reset : 1; + uint32_t adc0_reset : 1; + uint32_t adc1_reset : 1; + uint32_t dac_reset : 1; + uint32_t : 1; + uint32_t uart0_reset : 1; + uint32_t uart1_reset : 1; + uint32_t uart2_reset : 1; + uint32_t uart3_reset : 1; + uint32_t i2c0_reset : 1; + uint32_t i2c1_reset : 1; + uint32_t ssp0_reset : 1; + uint32_t ssp1_reset : 1; + uint32_t i2s_reset : 1; + uint32_t spifi_reset : 1; + uint32_t can1_reset : 1; + uint32_t can0_reset : 1; + uint32_t m0app_reset : 1; + uint32_t sgpio_reset : 1; + uint32_t spi_reset : 1; + uint32_t : 1; + uint32_t adchs_reset : 1; + uint32_t : 3; + }; + + RESERVED_WORDS(2); + + + // TODO: fill in the bits for these registers! + uint32_t reset_status[4]; + uint32_t reset_active_status[2]; + uint32_t reset_ext_stat[64]; + +} platform_reset_register_block_t; + + +ASSERT_OFFSET(platform_reset_register_block_t, reset_status, 0x10); + +/** + * Watchdog controller drivers. + */ +typedef volatile struct ATTR_PACKED { + + struct { + uint8_t enable : 1; + uint8_t reset_enable : 1; + uint8_t timed_out : 1; + uint8_t interrupt_on_timeout : 1; + uint8_t limit_update_interval : 1; + uint8_t : 3; + }; + + RESERVED_BYTES(3); + + struct { + uint32_t timeout : 24; + uint32_t : 8; + }; + + // Watchdog feed register. + uint8_t feed; + RESERVED_BYTES(3); + + struct { + uint32_t timer_value : 24; + uint32_t : 8; + }; + + + RESERVED_WORDS(1); + + struct { + uint32_t warning_threshold : 10; + uint32_t : 22; + }; + struct { + uint32_t valid_feed_threshold : 24; + uint32_t : 8; + }; + +} platform_watchdog_register_block_t; + + +/** + * Return a reference to the LPC43xx's RGU block. + */ +platform_reset_register_block_t *get_platform_reset_registers(void); + + +/** + * Software reset the entire system. + * + * @param true iff the always-on power domain should be included + */ +void platform_software_reset(bool include_always_on_domain); + + +/** + * @return true iff the system reset was an unintentional watchdog reset + * tries to ignore cases where a soft-reset used the watchdog to implement the reset itself + */ +bool platform_reset_was_watchdog_timeout(void); + +/** + * Clears any system state necessary to track the system's state across resets. + */ +void platform_initialize_reset_driver(void); + +#endif diff --git a/firmware/platform/lpc43xx/include/drivers/platform_timer.h b/firmware/platform/lpc43xx/include/drivers/platform_timer.h new file mode 100644 index 0000000..86ac254 --- /dev/null +++ b/firmware/platform/lpc43xx/include/drivers/platform_timer.h @@ -0,0 +1,159 @@ +/* + * This file is part of libgreat + * + * LPC43xx timer drivers + */ + +#ifndef __LIBGREAT_PLATFORM_TIMERS_H__ +#define __LIBGREAT_PLATFORM_TIMERS_H__ + +#include + +typedef struct timer timer_t; + + +/** + * Timer numbers for each of the LPC43xx timer perpiherals. + */ +typedef enum { + TIMER0 = 0, + TIMER1 = 1, + TIMER2 = 2, + TIMER3 = 3, +} timer_index_t; + + +/** + * Register layout for LPC43xx timers. + */ +typedef volatile struct ATTR_PACKED { + + // Interrupt register. + struct { + // Match channels. + uint32_t match0 : 1; + uint32_t match1 : 1; + uint32_t match2 : 1; + uint32_t match3 : 1; + + // Capture channels. + uint32_t capture0 : 1; + uint32_t capture1 : 1; + uint32_t capture2 : 1; + uint32_t capture3 : 1; + + uint32_t : 24; + } interrupt_pending; + + // Timer control. + struct { + uint32_t enabled : 1; + uint32_t reset : 1; + uint32_t : 30; + }; + + // Current counter value ("counter"). + uint32_t value; + + // Prescale registers. + uint32_t prescaler; + uint32_t prescale_counter; + + // Control registers for matching hardware. + uint32_t match_control; + uint32_t match_value[4]; + + // Control registers for the capture hardware. + uint32_t capture_control; + uint32_t captured_value[4]; + + // Control registers for matching against external pins. + uint32_t external_match_register; + + RESERVED_WORDS(12); + + // Counter control: controls using the timer/counter to count events, + // rather than continuously counting. + struct { + + // Determines the core behavior of the timer: should it count unconditionally (timer mode), + // or should it count events? (Takes a value from platform_timer_counter_mode_t.) + uint32_t counter_mode : 2; + + // The input that will drive counter events. + // Selected from the capture inputs. + uint32_t counter_input : 2; + + uint32_t : 28; + + } count_control_register; + +} platform_timer_registers_t; + + +ASSERT_OFFSET(platform_timer_registers_t, value, 0x08); +ASSERT_OFFSET(platform_timer_registers_t, match_control, 0x14); +ASSERT_OFFSET(platform_timer_registers_t, capture_control, 0x28); +ASSERT_OFFSET(platform_timer_registers_t, external_match_register, 0x3c); +ASSERT_OFFSET(platform_timer_registers_t, count_control_register, 0x70); + + +/** + * Counter mode for the LPC43xx counter peripherals. + * + * These will be used by the downstream Counter driver for the LPC43xx, + * when it's completed. We'll link that here, then. + */ +typedef enum { + TIMER_COUNT_PRESCALER_PERIODS = 0, + TIMER_COUNT_EVENT_RISING_EDGES = 1, + TIMER_COUNT_EVENT_FALLING_EDGES = 2, + TIMER_COUNT_EVENT_EDGES = 3, +} timer_counter_mode_t; + + +/** + * Perform platform-specific initialization for an LPC43xx timer peripheral. + * + * @param timer The timer object to be initialized. + * @param index The number of the timer to be set up. + */ +void platform_timer_initialize(timer_t *timer, timer_index_t index); + + +/** + * Sets the frequency of the given timer. For the LPC43xx, this recomputes the timer's divider. + * + * @param timer The timer to be configured. + * @param tick_freuqency The timer's tick frequency, in Hz. + */ +void platform_timer_set_frequency(timer_t *timer, uint32_t tick_frequency); + + +/** + * Enables the given timer. Typically, you want to configure the timer + * beforehand with calls to e.g. platform_timer_set_frequency. + */ +void platform_timer_enable(timer_t *timer); + + +/** + * @returns the current counter value of the given timer + */ +uint32_t platform_timer_get_value(timer_t *timer); + + +/** + * @returns A reference to the system's platform timer -- initializing the relevant timer, if needed. + */ +timer_t *platform_get_platform_timer(void); + + +/** + * Sets up the system's platform timer. + * + * @returns A reference to the system's platform timer. + */ +timer_t *platform_set_up_platform_timer(void); + +#endif diff --git a/firmware/include/drivers/usb/comms_backend.h b/firmware/platform/lpc43xx/include/drivers/usb/comms_backend.h similarity index 100% rename from firmware/include/drivers/usb/comms_backend.h rename to firmware/platform/lpc43xx/include/drivers/usb/comms_backend.h diff --git a/firmware/include/drivers/usb/lpc43xx/usb.h b/firmware/platform/lpc43xx/include/drivers/usb/usb.h similarity index 100% rename from firmware/include/drivers/usb/lpc43xx/usb.h rename to firmware/platform/lpc43xx/include/drivers/usb/usb.h diff --git a/firmware/include/drivers/usb/lpc43xx/usb_host.h b/firmware/platform/lpc43xx/include/drivers/usb/usb_host.h similarity index 100% rename from firmware/include/drivers/usb/lpc43xx/usb_host.h rename to firmware/platform/lpc43xx/include/drivers/usb/usb_host.h diff --git a/firmware/include/drivers/usb/lpc43xx/usb_host_stack.h b/firmware/platform/lpc43xx/include/drivers/usb/usb_host_stack.h similarity index 100% rename from firmware/include/drivers/usb/lpc43xx/usb_host_stack.h rename to firmware/platform/lpc43xx/include/drivers/usb/usb_host_stack.h diff --git a/firmware/include/drivers/usb/lpc43xx/usb_queue.h b/firmware/platform/lpc43xx/include/drivers/usb/usb_queue.h similarity index 100% rename from firmware/include/drivers/usb/lpc43xx/usb_queue.h rename to firmware/platform/lpc43xx/include/drivers/usb/usb_queue.h diff --git a/firmware/include/drivers/usb/lpc43xx/usb_queue_host.h b/firmware/platform/lpc43xx/include/drivers/usb/usb_queue_host.h similarity index 100% rename from firmware/include/drivers/usb/lpc43xx/usb_queue_host.h rename to firmware/platform/lpc43xx/include/drivers/usb/usb_queue_host.h diff --git a/firmware/include/drivers/usb/lpc43xx/usb_registers.h b/firmware/platform/lpc43xx/include/drivers/usb/usb_registers.h similarity index 100% rename from firmware/include/drivers/usb/lpc43xx/usb_registers.h rename to firmware/platform/lpc43xx/include/drivers/usb/usb_registers.h diff --git a/firmware/include/drivers/usb/lpc43xx/usb_request.h b/firmware/platform/lpc43xx/include/drivers/usb/usb_request.h similarity index 100% rename from firmware/include/drivers/usb/lpc43xx/usb_request.h rename to firmware/platform/lpc43xx/include/drivers/usb/usb_request.h diff --git a/firmware/include/drivers/usb/lpc43xx/usb_standard_request.h b/firmware/platform/lpc43xx/include/drivers/usb/usb_standard_request.h similarity index 100% rename from firmware/include/drivers/usb/lpc43xx/usb_standard_request.h rename to firmware/platform/lpc43xx/include/drivers/usb/usb_standard_request.h diff --git a/firmware/include/drivers/usb/lpc43xx/usb_type.h b/firmware/platform/lpc43xx/include/drivers/usb/usb_type.h similarity index 99% rename from firmware/include/drivers/usb/lpc43xx/usb_type.h rename to firmware/platform/lpc43xx/include/drivers/usb/usb_type.h index 390af53..2f98809 100644 --- a/firmware/include/drivers/usb/lpc43xx/usb_type.h +++ b/firmware/platform/lpc43xx/include/drivers/usb/usb_type.h @@ -67,12 +67,12 @@ typedef enum { USB_SETUP_REQUEST_TYPE_shift = 5, USB_SETUP_REQUEST_TYPE_mask = 3 << USB_SETUP_REQUEST_TYPE_shift, - + USB_SETUP_REQUEST_TYPE_STANDARD = 0 << USB_SETUP_REQUEST_TYPE_shift, USB_SETUP_REQUEST_TYPE_CLASS = 1 << USB_SETUP_REQUEST_TYPE_shift, USB_SETUP_REQUEST_TYPE_VENDOR = 2 << USB_SETUP_REQUEST_TYPE_shift, USB_SETUP_REQUEST_TYPE_RESERVED = 3 << USB_SETUP_REQUEST_TYPE_shift, - + USB_SETUP_REQUEST_TYPE_DATA_TRANSFER_DIRECTION_shift = 7, USB_SETUP_REQUEST_TYPE_DATA_TRANSFER_DIRECTION_mask = 1 << USB_SETUP_REQUEST_TYPE_DATA_TRANSFER_DIRECTION_shift, USB_SETUP_REQUEST_TYPE_DATA_TRANSFER_DIRECTION_HOST_TO_DEVICE = 0 << USB_SETUP_REQUEST_TYPE_DATA_TRANSFER_DIRECTION_shift, @@ -83,7 +83,7 @@ typedef enum { USB_TRANSFER_DIRECTION_OUT = 0, USB_TRANSFER_DIRECTION_IN = 1, } usb_transfer_direction_t; - + typedef enum { USB_DESCRIPTOR_TYPE_DEVICE = 1, USB_DESCRIPTOR_TYPE_CONFIGURATION = 2, diff --git a/firmware/include/platform/lpc43xx/platform_sync.h b/firmware/platform/lpc43xx/include/platform_sync.h similarity index 100% rename from firmware/include/platform/lpc43xx/platform_sync.h rename to firmware/platform/lpc43xx/include/platform_sync.h diff --git a/firmware/platform/lpc43xx/linker/libgreat_lpc43xx.ld b/firmware/platform/lpc43xx/linker/libgreat_lpc43xx.ld index 6ccc247..22e4fcb 100644 --- a/firmware/platform/lpc43xx/linker/libgreat_lpc43xx.ld +++ b/firmware/platform/lpc43xx/linker/libgreat_lpc43xx.ld @@ -35,7 +35,11 @@ SECTIONS { .text : { . = ALIGN(0x400); - _text_ram = 0; /* Start of Code in RAM NULL because Copy of Code from ROM to RAM disabled */ + + /* Don't provide relocation targets, as we're not performing relocation on direct-to-RAM and XIP loads. */ + _text_segment_rom = 0; + _text_segment_ram = 0; + *(.vectors) /* Vector table */ *(.text*) /* Program code */ . = ALIGN(4); @@ -73,7 +77,7 @@ SECTIONS .ARM.extab : { *(.ARM.extab*) } >rom - + /* exception index - required due to libgcc.a issuing /0 exceptions */ .ARM.exidx : { __exidx_start = .; @@ -82,9 +86,11 @@ SECTIONS } >rom . = ALIGN(4); - _etext = .; - _etext_ram = 0; /* Start of Code in RAM NULL because Copy of Code from ROM to RAM disabled */ - _etext_rom = 0; /* Start of Code in RAM NULL because Copy of Code from ROM to RAM disabled */ + _text_segment_end = .; + + /* Don't provide relocation targets, as we're not performing relocation on direct-to-RAM and XIP loads. */ + _text_segment_rom_end = 0; + _text_segment_ram_end = 0; . = ORIGIN(ram_local2); diff --git a/firmware/platform/lpc43xx/linker/libgreat_lpc43xx_rom_to_ram.ld b/firmware/platform/lpc43xx/linker/libgreat_lpc43xx_rom_to_ram.ld index 1f7dcd8..b2bdaa9 100644 --- a/firmware/platform/lpc43xx/linker/libgreat_lpc43xx_rom_to_ram.ld +++ b/firmware/platform/lpc43xx/linker/libgreat_lpc43xx_rom_to_ram.ld @@ -35,7 +35,8 @@ SECTIONS { .text : { . = ALIGN(0x400); - _text_ram = (. - ORIGIN(rom)) + ORIGIN(ram_local1); /* Start of Code in RAM */ + _text_segment_rom = .; + _text_segment_ram = (. - ORIGIN(rom)) + ORIGIN(ram_local1); /* Start of Code in RAM */ *(.vectors) /* Vector table */ *(.text*) /* Program code */ @@ -74,7 +75,7 @@ SECTIONS .ARM.extab : { *(.ARM.extab*) } >rom - + /* exception index - required due to libgcc.a issuing /0 exceptions */ .ARM.exidx : { __exidx_start = .; @@ -83,9 +84,9 @@ SECTIONS } >rom . = ALIGN(4); - _etext = .; - _etext_ram = (. - ORIGIN(rom)) + ORIGIN(ram_local1); - _etext_rom = (. - ORIGIN(rom)) + ORIGIN(rom_flash); + _text_segment_end = .; + _text_segment_ram_end = (. - ORIGIN(rom)) + ORIGIN(ram_local1); + _text_segment_rom_end = (. - ORIGIN(rom)) + ORIGIN(rom_flash); . = ORIGIN(ram_local2); diff --git a/host/pygreat/board.py b/host/pygreat/board.py index c46aea1..a6df9a6 100644 --- a/host/pygreat/board.py +++ b/host/pygreat/board.py @@ -46,6 +46,12 @@ class GreatBoard(object): BOARD_VENDOR_ID = 0 BOARD_PRODUCT_ID = 0 + # Commands for resetting a libgreat board. + RESET_REQUEST_NORMAL = 0 + RESET_REQUEST_SWITCH_TO_EXTCLOCK = 1 + RESET_REQUEST_MAINTAIN_ALWAYS_ON_DOMAIN = 2 + RESET_REQUEST_POST_FIRMWARE_FLASH = 3 + @classmethod def autodetect(cls, **device_identifiers): """ @@ -306,8 +312,8 @@ def try_reconnect(self): self.__init__(**self.identifiers) self.initialize_apis() - - def reset(self, reconnect=True, switch_to_external_clock=False): + def reset(self, reconnect=True, switch_to_external_clock=False, + is_post_firmware_flash=False, maintain_always_on_domain=False): """ Reset the device. @@ -316,18 +322,24 @@ def reset(self, reconnect=True, switch_to_external_clock=False): finish the reset and then attempt to reconnect. switch_to_external_clock -- If true, the device will accept a 12MHz clock signal on P4_7 (J2_P11 on the GreatFET one) after the reset. + is_post_firmware_flash -- If true, the device will be notified that this immediately + follows a firmware flash, and can adjust its internal messages accordingly. """ + reset_command = self.RESET_REQUEST_NORMAL - # FIXME: abstract - reset_type = 1 if switch_to_external_clock else 0 + if switch_to_external_clock: + reset_command = self.RESET_REQUEST_SWITCH_TO_EXTCLOCK + elif maintain_always_on_domain: + reset_command = self.RESET_REQUEST_MAINTAIN_ALWAYS_ON_DOMAIN + elif is_post_firmware_flash: + reset_command = self.RESET_REQUEST_POST_FIRMWARE_FLASH try: - self.apis.core.request_reset(reset_type) - except usb.core.USBError as e: + self.apis.core.request_reset(reset_command) + except usb.core.USBError: pass # If we're to attempt a reconnect, do so. - connected = False if reconnect: time.sleep(RECONNECT_DELAY) self.try_reconnect() @@ -342,8 +354,6 @@ def switch_to_external_clock(self): """ self.reset(switch_to_external_clock=True) - - def close(self): """ Dispose resources allocated by this connection. This connection diff --git a/host/pygreat/comms.py b/host/pygreat/comms.py index abf4d14..f37ad6f 100644 --- a/host/pygreat/comms.py +++ b/host/pygreat/comms.py @@ -788,7 +788,7 @@ def execute_command(self, class_number, verb, in_format, out_format, for index, value in enumerate(result): if isinstance(value, bytes): - result[index] = value.decode(encoding) + result[index] = value.decode(encoding, errors='ignore') result = tuple(result) @@ -942,6 +942,11 @@ class CommsApiCollection(object): def __init__(self, wrapped_dict): self.__dict__ = wrapped_dict + def __getattr__(self, name): + """ Trivial definition of __getattr_. Makes linters happy. """ + + return self.__dict__[name] + def _generate_command_in_signature(in_format, in_names): """ Generates in-signature documentation for a given RPC. @@ -1080,7 +1085,7 @@ def method(self, *arguments, **kwargs): max_response_length = kwargs.pop('max_response_length', 4096) return self.execute_command(verb_number, in_format, out_format, name=name, class_name=class_name, - timeout=timeout, max_response_length=max_response_length, *arguments) + timeout=timeout, max_response_length=max_response_length, encoding=encoding, *arguments) # Apply our known documentation to the given command. method.__name__ = future_utils.native_str(name) @@ -1129,7 +1134,7 @@ def c_string_return(raw_bytes, encoding='UTF-8'): # If we have an encoding argument, decode it. if encoding: - raw_strings = [string.decode(encoding) for string in raw_strings] + raw_strings = [string.decode(encoding, errors='ignore') for string in raw_strings] return tuple(raw_strings) diff --git a/host/pygreat/comms_backends/usb.py b/host/pygreat/comms_backends/usb.py index a076961..1f7a1ee 100644 --- a/host/pygreat/comms_backends/usb.py +++ b/host/pygreat/comms_backends/usb.py @@ -156,7 +156,7 @@ def _vendor_request_in_string(self, request, length=255, value=0, index=0, timeo """ raw = self._vendor_request(usb.ENDPOINT_IN, request, length_or_data=length, value=value, index=index, timeout=timeout) - return raw.tostring().decode(encoding) + return raw.tobytes().ecode(encoding, errors='ignore') def _vendor_request_out(self, request, value=0, index=0, data=None, timeout=1000): @@ -282,10 +282,10 @@ def execute_raw_command(self, class_number, verb, data=None, timeout=1000, encod # If we were passed an encoding, attempt to decode the response data. if encoding and response: - response = response.tostring().decode(encoding) + response = response.tobytes().decode(encoding, errors='ignore') # Return the device's response. - return response.tostring() + return response.tobytes() except Exception as e: From 3f35e546fafff12b7abb32c1b627073b26e132be Mon Sep 17 00:00:00 2001 From: "Kate J. Temkin" Date: Tue, 5 Mar 2019 20:58:36 -0700 Subject: [PATCH 08/23] firmware: add start of AD970x driver (supporting Gladiolus) --- firmware/CMakeLists.txt | 13 +- firmware/drivers/dac/ad970x.c | 273 ++++++++++++++++++++++++++ firmware/include/drivers/dac/ad970x.h | 66 +++++++ 3 files changed, 348 insertions(+), 4 deletions(-) create mode 100644 firmware/drivers/dac/ad970x.c create mode 100644 firmware/include/drivers/dac/ad970x.h diff --git a/firmware/CMakeLists.txt b/firmware/CMakeLists.txt index b10ea2f..770a7a4 100644 --- a/firmware/CMakeLists.txt +++ b/firmware/CMakeLists.txt @@ -30,10 +30,15 @@ define_libgreat_module(allocator # Provide the core communications protocol. define_libgreat_module(comms - ${CMAKE_CURRENT_SOURCE_DIR}/drivers/comms/utils.c - ${CMAKE_CURRENT_SOURCE_DIR}/drivers/comms/comms_class.c - ${CMAKE_CURRENT_SOURCE_DIR}/classes/core.c - ${CMAKE_CURRENT_SOURCE_DIR}/classes/firmware.c + ${PATH_LIBGREAT_FIRMWARE_DRIVERS}/comms/utils.c + ${PATH_LIBGREAT_FIRMWARE_DRIVERS}/comms/comms_class.c + ${PATH_LIBGREAT_FIRMWARE}/classes/core.c + ${PATH_LIBGREAT_FIRMWARE}/classes/firmware.c +) + +# DAC drivers. +define_libgreat_module(ad970x + ${PATH_LIBGREAT_FIRMWARE_DRIVERS}/dac/ad970x.c ) # FIXME: get rid of this diff --git a/firmware/drivers/dac/ad970x.c b/firmware/drivers/dac/ad970x.c new file mode 100644 index 0000000..3dbf9d6 --- /dev/null +++ b/firmware/drivers/dac/ad970x.c @@ -0,0 +1,273 @@ +/* + * This file is part of GreatFET + * + * Code for controlling an AD970x DAC. + */ + +#include + +#include +#include + +/** + * Configuration constants for the AD970x protocol. + */ +enum { + + // DAC command -- direction bit. + // Identifies if the command to follow is a READ or a WRITE. + DAC_DIRECTION_READ = (1 << 7), + DAC_DIRECTION_WRITE = (0 << 7), + + // DAC command -- length. + // Identifies the length of the data stage of this command. + DAC_WIDTH_BYTE = (0 << 5) + +}; + + +/** + * Sets up a new connection to an AD970x DAC. + * + * @param dac The DAC object to be initialized. Must already have its gpio_port_* and gpio_pin_* properties initialized + * to the correct GPIO ports/pins for the DAC. + * @param clock_period The (approximate) clock period, in microseconds. Must be divisible by two. Can be set to 0 to + iterate as fast as our bit-banging allows. + * + * @return 0 on success, or an error code on failure + */ +int ad970x_initialize(ad970x_t *dac, uint32_t clock_period) +{ + // Store our half period. + dac->config_half_period = clock_period / 2; + + // Validate our period. + if (clock_period && !dac->config_half_period) { + pr_error("error: tried to configure DAC for a currently-impossible period < 2uS!\n"); + return EINVAL; + } + + // Set the SCK and CS pins to output only, as we'll maintain control over these. + gpio_set_pin_direction(dac->gpio_port_cs, dac->gpio_pin_cs, true); + gpio_set_pin_direction(dac->gpio_port_sck, dac->gpio_pin_sck, true); + + // We'll start by driving the data line; and release it when we need to. + gpio_set_pin_direction(dac->gpio_port_data, dac->gpio_pin_data, true); + + // Keep the DAC in SPI mode, for now. + gpio_set_pin_direction(dac->gpio_port_mode, dac->gpio_pin_mode, true); + gpio_clear_pin(dac->gpio_port_mode, dac->gpio_pin_mode); + + return 0; +} + +/** + * Convenience function to set SCK. + */ +static void dac_set_sck_high(ad970x_t *dac) +{ + gpio_set_pin(dac->gpio_port_sck, dac->gpio_pin_sck); +} + +/** + * Convenience function to clear SCK. + */ +static void dac_set_sck_low(ad970x_t *dac) +{ + gpio_clear_pin(dac->gpio_port_sck, dac->gpio_pin_sck); +} + +/** + * Convenience function to read the DATA line. + */ +static uint8_t dac_read_data_state(ad970x_t *dac) +{ + return gpio_get_pin_value(dac->gpio_port_data, dac->gpio_pin_data); +} + +/** + * Convenience function to read the DATA line. + */ +static void dac_set_data_state(ad970x_t *dac, uint8_t value) +{ + gpio_set_pin_value(dac->gpio_port_data, dac->gpio_pin_data, value); +} + +/** + * Convenience function that waits for a half of the DAC configuraiton period. + */ +static void dac_wait_for_half_period(ad970x_t *dac) +{ + if (dac->config_half_period) { + delay_us(dac->config_half_period); + } +} + +/** + * Begins driving the DAC configuration data line, taking control of the bus. + */ +static void dac_drive_data_line(ad970x_t *dac) +{ + gpio_set_pin_direction(dac->gpio_port_data, dac->gpio_pin_data, true); + dac_wait_for_half_period(dac); +} + + +/** + * Cease driving the DAC configuration data line, allowing the DAC to respond. + */ +static void dac_release_data_line(ad970x_t *dac) +{ + gpio_set_pin_direction(dac->gpio_port_data, dac->gpio_pin_data, false); + dac_wait_for_half_period(dac); +} + + + +/** + * Reads a single bit from the AD970x configuration bus. + * Synchronously blocks for the bit period. + */ +static uint8_t dac_receive_bit(ad970x_t *dac) +{ + uint8_t bit; + + // Drive the clock low for half of a period. + dac_set_sck_low(dac); + dac_wait_for_half_period(dac); + + // Sample the DATA pin's value _just before_ we'll go high. + // This gives the value the most time to settle, as it's set by the DAC on the falling edge. + bit = dac_read_data_state(dac); + + // Drive the clock high for the remainder of the period. + dac_set_sck_high(dac); + dac_wait_for_half_period(dac); + + // Return the value we observed. + return bit; +} + +/** + * Writes a single bit to the AD970x configuration bus. + * Synchronously blocks for the bit period. + */ +static void dac_send_bit(ad970x_t *dac, uint8_t value) +{ + // Drive DATA to a given value... + dac_set_data_state(dac, value); + + //... and then re-use our "read bit" code to iterate through the cycle. + dac_receive_bit(dac); +} + +/** + * Starts a DAC configuration transaction. + */ +static void dac_start_config_transaction(ad970x_t *dac) +{ + // Clear CS, and wait a bit to meet timing requirements. + gpio_clear_pin(dac->gpio_port_cs, dac->gpio_pin_cs); + dac_wait_for_half_period(dac); +} + + +/** + * Termiantes a configuration transaction, placing the configruation bus back into its idle state. + */ +static void dac_end_config_transaction(ad970x_t *dac) +{ + // Put the system back into its idle state (CS high, SCK low). + gpio_set_pin(dac->gpio_port_cs, dac->gpio_pin_cs); + dac_set_sck_low(dac); + + // Block for half a period to meet timing requirements. + dac_wait_for_half_period(dac); +} + + +/** + * Writes a single byte onto the DAC configuration interface. + + * @param value The value to write. + */ +static void dac_send_byte(ad970x_t *dac, uint8_t value) +{ + // Set the SDIO port to output mode, so we can issue our write. + dac_drive_data_line(dac); + + // Transmit each of the bits. + for (int i = 7; i >= 0; --i) { + + // Determine the bit to be scanned out. + uint8_t bit = value & (1 << i); + + // ... and scan it out. + dac_send_bit(dac, bit); + } +} + +/** + * Reads a single byte from the DAC configuration interface. + * + * @return the byte read from the interface + */ +static uint8_t dac_receive_byte(ad970x_t *dac) +{ + uint8_t byte = 0; + + // Set the SDIO port to input mode, so we can get the response. + dac_release_data_line(dac); + + // ... and read each of the bits. + for (int i = 0; i < 8; ++i) { + + // Read the current bit... + uint8_t bit = dac_receive_bit(dac); + + // .. and melt it into our running shift. + byte = (byte << 1) | bit; + } + + return byte; +} + +/** + * Reads a DAC configuration register. + * + * @param address The register address to touch. + * @return The raw value read. + */ +uint8_t ad970x_register_read(ad970x_t *dac, uint8_t address) +{ + uint8_t command = DAC_DIRECTION_READ | DAC_WIDTH_BYTE | address; + uint8_t response; + + dac_start_config_transaction(dac); + + // Scan out the command, and then read back the response. + dac_send_byte(dac, command); + response = dac_receive_byte(dac); + + dac_end_config_transaction(dac); + return response; +} + +/** + * Writes a DAC configuration register. + * + * @param address The register address to touch. + * @param value The raw value to be written. + */ +void ad970x_register_write(ad970x_t *dac, uint8_t address, uint8_t value) +{ + uint8_t command = DAC_DIRECTION_WRITE | DAC_WIDTH_BYTE | address; + + dac_start_config_transaction(dac); + + // Scan out the command, and then scan out the argument. + dac_send_byte(dac, command); + dac_send_byte(dac, value); + + dac_end_config_transaction(dac); +} diff --git a/firmware/include/drivers/dac/ad970x.h b/firmware/include/drivers/dac/ad970x.h new file mode 100644 index 0000000..4e7e243 --- /dev/null +++ b/firmware/include/drivers/dac/ad970x.h @@ -0,0 +1,66 @@ +/* + * This file is part of GreatFET + * + * Code for controlling an AD970x DAC. + */ + +#ifndef __LIBGREAT_AD970X_H__ +#define __LIBGREAT_AD970X_H__ + +#include +#include + +/** + * Structure representing an AD970X DAC. + */ +typedef struct { + + // GPIO port locations for each of the given GPIO pins. + uint8_t gpio_port_cs; + uint8_t gpio_port_sck; + uint8_t gpio_port_data; + uint8_t gpio_port_mode; + + // GPIO pin locations for each of the given GPIO pins. + uint8_t gpio_pin_cs; + uint8_t gpio_pin_sck; + uint8_t gpio_pin_data; + uint8_t gpio_pin_mode; + + // Internal fields: + + // The length of a half-period of the DAC configuration clock. + uint32_t config_half_period; + +} ad970x_t; + + +/** + * Sets up a new connection to an AD970x DAC. + * + * @param dac The DAC object to be initialized. Must already have its gpio_port_* and gpio_pin_* properties initialized + * to the correct GPIO ports/pins for the DAC. + * @param clock_period The (very approximate) clock period, in microseconds. Must be divisible by two. + * + * @return 0 on success, or an error code on failure + */ +int ad970x_initialize(ad970x_t *dac, uint32_t clock_period); + + +/** + * Reads a DAC configuration register. + * + * @param address The register address to touch. + * @return The raw value read. + */ +uint8_t ad970x_register_read(ad970x_t *dac, uint8_t address); + +/** + * Writes a DAC configuration register. + * + * @param address The register address to touch. + * @param value The raw value to be written. + */ +void ad970x_register_write(ad970x_t *dac, uint8_t address, uint8_t value); + +#endif From 3353728bf8febc3781aca6f449e238e9ac2b99bf Mon Sep 17 00:00:00 2001 From: "Kate J. Temkin" Date: Thu, 7 Mar 2019 20:04:35 -0700 Subject: [PATCH 09/23] pygreat: handle versioning correctly, in preparation for GreatFET release --- .gitignore | 3 +++ host/setup.py | 18 ++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 8850440..9f64ada 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,6 @@ compile_commands.json tags firmware/*/build + +# release files +VERSION diff --git a/host/setup.py b/host/setup.py index e09198c..a3f3edf 100644 --- a/host/setup.py +++ b/host/setup.py @@ -6,16 +6,30 @@ def read(fname): with open(filename, 'r') as f: return f.read() + +setup_req = [] +version_format = None +version = None + +# Deduce version, if possible. +if os.path.isfile('../VERSION'): + version = read('../VERSION') +else: + version_format = '{tag}.dev{commitcount}+git.{gitsha}' + setup_req.append('setuptools-git-version') + setup( name='pygreat', - version='0.0', #TODO: Derive this from the main module. + version=version, + version_format=version_format, + setup_requires=setup_req, url='https://greatscottgadgets.com/greatfet/', license='BSD', entry_points={ 'console_scripts': [], }, author='Katherine J. Temkin', - author_email='ktemkin@insomniasec.io', + author_email='ktemkin@greatscottgadgets.com', tests_require=[''], install_requires=['pyusb', 'future', 'backports.functools_lru_cache'], description='Python library for talking with libGreat devices', From 5b81c39b11f61721de46d7ded51d0fe90048e9c7 Mon Sep 17 00:00:00 2001 From: "Kate J. Temkin" Date: Sun, 10 Mar 2019 23:14:35 -0600 Subject: [PATCH 10/23] lpc43xx: clock driver: fix a clock source selection issue Fixes an issue where virtual clock sources (PRIMARY_CLOCK) were accidentally run off of the fallback internal oscillator, rather than the system's actual primary clock. --- firmware/platform/lpc43xx/drivers/platform_clock.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/firmware/platform/lpc43xx/drivers/platform_clock.c b/firmware/platform/lpc43xx/drivers/platform_clock.c index 00d95cc..14de1ff 100644 --- a/firmware/platform/lpc43xx/drivers/platform_clock.c +++ b/firmware/platform/lpc43xx/drivers/platform_clock.c @@ -429,6 +429,7 @@ int platform_enable_base_clock(platform_base_clock_register_t *base) { int rc; platform_base_clock_t value; + clock_source_t source; // Identify the relevant configuration for the given base clock. platform_base_clock_configuration_t *config = platform_find_config_for_base_clock(base); @@ -439,22 +440,25 @@ int platform_enable_base_clock(platform_base_clock_register_t *base) return 0; } + // Translate the clock source into a physical clock source. + source = platform_get_physical_clock_source(config->source); + // Switch the base clock to its relevant clock source. - rc = platform_handle_dependencies_for_clock_source(config->source); + rc = platform_handle_dependencies_for_clock_source(source); if (rc && !config->no_fallback) { pr_warning("failed to bring up source %s for base clock %s; falling back to internal oscillator!\n", - platform_get_clock_source_name(config->source), platform_get_base_clock_name(base)); + platform_get_clock_source_name(source), platform_get_base_clock_name(base)); config->source = CLOCK_SOURCE_INTERNAL_OSCILLATOR; } else if (rc) { pr_warning("failed to bring up source %s for base clock %s; trying to continue anyway.\n", - platform_get_clock_source_name(config->source), platform_get_base_clock_name(base)); + platform_get_clock_source_name(source), platform_get_base_clock_name(base)); } // Finally, ensure the clock is powered up. value.power_down = 0; value.block_during_changes = 0; - value.source = config->source; + value.source = source; value.divisor = 0; base->all_bits = value.all_bits; From 34fde42fe2b312cbf09555542b7f8dc050f88c9e Mon Sep 17 00:00:00 2001 From: "Kate J. Temkin" Date: Mon, 25 Mar 2019 14:58:30 -0600 Subject: [PATCH 11/23] firmware: add support for simple "scheduling" --- firmware/CMakeLists.txt | 5 +++ firmware/drivers/scheduler.c | 38 +++++++++++++++++++ firmware/include/scheduler.h | 25 ++++++++++++ firmware/include/toolchain.h | 7 ++++ .../lpc43xx/linker/libgreat_lpc43xx.ld | 11 ++++++ .../linker/libgreat_lpc43xx_rom_to_ram.ld | 14 +++++++ 6 files changed, 100 insertions(+) create mode 100644 firmware/drivers/scheduler.c create mode 100644 firmware/include/scheduler.h diff --git a/firmware/CMakeLists.txt b/firmware/CMakeLists.txt index 770a7a4..80ad29b 100644 --- a/firmware/CMakeLists.txt +++ b/firmware/CMakeLists.txt @@ -41,5 +41,10 @@ define_libgreat_module(ad970x ${PATH_LIBGREAT_FIRMWARE_DRIVERS}/dac/ad970x.c ) +# Scheduler. +define_libgreat_module(scheduler + ${PATH_LIBGREAT_FIRMWARE_DRIVERS}/scheduler.c +) + # FIXME: get rid of this add_dependencies(libgreat libopencm3) diff --git a/firmware/drivers/scheduler.c b/firmware/drivers/scheduler.c new file mode 100644 index 0000000..d2c2619 --- /dev/null +++ b/firmware/drivers/scheduler.c @@ -0,0 +1,38 @@ +/** + * Simple co-operative round-robin scheduler functionality for GreatFET. + * This file is part of libgreat + */ + +#include + +// TODO: implement task state, yielding, and magic? + +// Definitions that let us get at our list of tasks. +typedef void (*task_implementation_t) (void); +extern task_implementation_t __task_array_start, __task_array_end; + + + +/** + * Runs a single iteration of each defined task (a single scheduler "round") + * For an variant that runs indefinitely, use scheduler_run(). + */ +void scheduler_run_tasks(void) +{ + task_implementation_t *task; + + // Execute each task in our list, once. + for (task = &__task_array_start; task < &__task_array_end; task++) { + (*task)(); + } +} + +/** + * Runs our round-robin scheduler for as long as the device is alive; never returns. + */ +ATTR_NORETURN void scheduler_run(void) +{ + while(1) { + scheduler_run_tasks(); + } +} diff --git a/firmware/include/scheduler.h b/firmware/include/scheduler.h new file mode 100644 index 0000000..f63180e --- /dev/null +++ b/firmware/include/scheduler.h @@ -0,0 +1,25 @@ +/** + * Simple co-operative round-robin scheduler functionality for GreatFET. + * This file is part of libgreat. + */ + + +#include + +#ifndef __LIBGREAT_SCHEDULER_H__ +#define __LIBGREAT_SCHEDULER_H__ + + + +/** + * Runs a single iteration of each defined task (a single scheduler "round") + * For an variant that runs indefinitely, use scheduler_run(). + */ +void scheduler_run_tasks(void); + +/** + * Runs our round-robin scheduler for as long as the device is alive; never returns. + */ +ATTR_NORETURN void scheduler_run(void); + +#endif diff --git a/firmware/include/toolchain.h b/firmware/include/toolchain.h index 9b8837f..5e0a688 100644 --- a/firmware/include/toolchain.h +++ b/firmware/include/toolchain.h @@ -43,6 +43,13 @@ #define CALL_BEFORE_RESET(fini) \ __attribute__((section(".fini_array"), used)) static typeof(init) *fini##_finalizer_p = fini; +/** + * Macros for simple scheduler support. This uses the same type of linker magic as .init/.fini to populate + * a simple array of pointers that we can easily iterate over. + */ +#define DEFINE_TASK(task) \ + __attribute__((section(".task_array"), used)) static typeof(task) *task##_implementation_p = task; + /** * Compile-time error detection. diff --git a/firmware/platform/lpc43xx/linker/libgreat_lpc43xx.ld b/firmware/platform/lpc43xx/linker/libgreat_lpc43xx.ld index 22e4fcb..e9da3d2 100644 --- a/firmware/platform/lpc43xx/linker/libgreat_lpc43xx.ld +++ b/firmware/platform/lpc43xx/linker/libgreat_lpc43xx.ld @@ -69,6 +69,17 @@ SECTIONS KEEP (*(SORT(.fini_array.*))) __fini_array_end = .; } >rom + /** + * Array of task pointers that allow us to schedule things without explicitly adding them to our main routine. + * Supports our simple round-robin scheduler. + */ + .task_array : { + . = ALIGN(4); + __task_array_start = .; + KEEP (*(.task_array)) + KEEP (*(SORT(.task_array.*))) + __task_array_end = .; + } >rom /* * Another section used by C++ stuff, appears when using newlib with diff --git a/firmware/platform/lpc43xx/linker/libgreat_lpc43xx_rom_to_ram.ld b/firmware/platform/lpc43xx/linker/libgreat_lpc43xx_rom_to_ram.ld index b2bdaa9..29c56dc 100644 --- a/firmware/platform/lpc43xx/linker/libgreat_lpc43xx_rom_to_ram.ld +++ b/firmware/platform/lpc43xx/linker/libgreat_lpc43xx_rom_to_ram.ld @@ -45,6 +45,8 @@ SECTIONS . = ALIGN(4); } >rom + /* FIXME: abstract these away into an include file! */ + /* C++ Static constructors/destructors, also used for __attribute__ * ((constructor)) and the likes */ .preinit_array : { @@ -68,6 +70,18 @@ SECTIONS __fini_array_end = .; } >rom + /** + * Array of task pointers that allow us to schedule things without explicitly adding them to our main routine. + * Supports our simple round-robin scheduler. + */ + .task_array : { + . = ALIGN(4); + __task_array_start = .; + KEEP (*(.task_array)) + KEEP (*(SORT(.task_array.*))) + __task_array_end = .; + } >rom + /* * Another section used by C++ stuff, appears when using newlib with * 64bit (long long) printf support From 4529b04523bc77e960cdd1112dd80d5e099d1a65 Mon Sep 17 00:00:00 2001 From: "Kate J. Temkin" Date: Tue, 26 Mar 2019 21:29:11 -0600 Subject: [PATCH 12/23] lpc43xx clock: change the way we detect USB PLL, and improve stability --- .../platform/lpc43xx/drivers/platform_clock.c | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/firmware/platform/lpc43xx/drivers/platform_clock.c b/firmware/platform/lpc43xx/drivers/platform_clock.c index 14de1ff..cfb5c5e 100644 --- a/firmware/platform/lpc43xx/drivers/platform_clock.c +++ b/firmware/platform/lpc43xx/drivers/platform_clock.c @@ -12,7 +12,6 @@ #include - // Don't try to bring up any clock more than five times. static const uint32_t platform_clock_max_bringup_attempts = 5; @@ -56,6 +55,8 @@ static char *platform_get_base_clock_name(platform_base_clock_t *base); static platform_base_clock_t *platform_base_clock_for_divider(clock_source_t source); static int platform_bring_up_audio_pll(void); static int platform_bring_up_usb_pll(void); +static int platform_bring_up_clock_divider(clock_source_t source, bool handle_dependencies); + // Set to true once we're finished with early initialization. // (We can't do some things until early init completes.) @@ -457,7 +458,7 @@ int platform_enable_base_clock(platform_base_clock_register_t *base) // Finally, ensure the clock is powered up. value.power_down = 0; - value.block_during_changes = 0; + value.block_during_changes = 1; value.source = source; value.divisor = 0; base->all_bits = value.all_bits; @@ -951,8 +952,9 @@ static uint32_t platform_detect_usb_pll_frequency(void) platform_base_clock_t *divider = &cgu->idiva; uint32_t divided_frequency; + platform_bring_up_clock_divider(CLOCK_SOURCE_DIVIDER_A_OUT, false); + // If the divider isn't set up to divide the USB PLL, return a low-precision measurement. - //platform_handle_dependencies_for_clock_source(CLOCK_SOURCE_DIVIDER_A_OUT); if ((divider->source != CLOCK_SOURCE_PLL0_USB) || divider->power_down) { return platform_detect_clock_source_frequency_directly(CLOCK_SOURCE_PLL0_USB); } @@ -983,6 +985,11 @@ uint32_t platform_detect_clock_source_frequency_via_divider(clock_source_t clock const uint32_t divider_cutoff = 240 * MHZ; const uint32_t scale_factor = 4; + // If this is the USB PLL, this can only drive divider A. We'll need to take special steps. + if (clock_to_detect == CLOCK_SOURCE_PLL0_USB) { + return platform_detect_usb_pll_frequency(); + } + // Get an initial measurement, which will determine if we need to re-compute using our divider. uint32_t frequency = platform_detect_clock_source_frequency_directly(clock_to_detect); @@ -998,10 +1005,6 @@ uint32_t platform_detect_clock_source_frequency_via_divider(clock_source_t clock return 0; } - // If this is the USB PLL, this can only drive divider A. We'll need to take special steps. - if (clock_to_detect == CLOCK_SOURCE_PLL0_USB) { -; return platform_detect_usb_pll_frequency(); - } // If the dividier is CLOCK_SOURCE_NONE if (divider == CLOCK_SOURCE_NONE) { @@ -1179,11 +1182,11 @@ static platform_base_clock_t *platform_base_clock_for_divider(clock_source_t sou * @source The clock source corresponding to the relevant clock divider. * @return 0 on success, or an error number on failure */ -static int platform_bring_up_clock_divider(clock_source_t source) +static int platform_bring_up_clock_divider(clock_source_t source, bool handle_dependencies) { int rc; - // Find the base clock that coresponds tro the given divider. + // Find the base clock that coresponds to the given divider. platform_base_clock_t *clock = platform_base_clock_for_divider(source); platform_base_clock_t value; @@ -1196,18 +1199,22 @@ static int platform_bring_up_clock_divider(clock_source_t source) const platform_base_clock_configuration_t *config = platform_find_config_for_base_clock(clock); // ... and finally, enable the given clock. - rc = platform_handle_dependencies_for_clock_source(config->source); - if (rc) { - return rc; + if (handle_dependencies) { + rc = platform_handle_dependencies_for_clock_source(config->source); + if (rc) { + return rc; + } } // Build the value to apply, and then apply it all at once, so we don't leave the write mid-configuration. value.power_down = 0; - value.block_during_changes = 0; + value.block_during_changes = 1; value.source = config->source; value.divisor = config->divisor - 1; clock->all_bits = value.all_bits; + // TODO: possibly validate the clock frequency? + return 0; } @@ -1382,6 +1389,10 @@ static void platform_soft_start_cpu_clock(void) return; } + // Configure the system bus to block during all future frequency changes, to make sure we never accidentally + // clock-glitch the CPU. + cgu->pll1.block_during_frequency_changes = 1; + // If we're currently bypassing the output divider, turning the divider // on (and to its least setting) achieves a trivial divide-by-two. if (cgu->pll1.bypass_output_divider) { @@ -1588,7 +1599,7 @@ static int platform_bring_up_usb_pll(void) // Power off the PLL for configuration. cgu->pll_usb.powered_down = 1; - cgu->pll_usb.block_during_frequency_changes = 0; + cgu->pll_usb.block_during_frequency_changes = 1; // Configure the PLL's source. cgu->pll_usb.source = platform_get_physical_clock_source(config->source); @@ -1657,7 +1668,7 @@ static int platform_handle_dependencies_for_clock_source(clock_source_t source) case CLOCK_SOURCE_DIVIDER_C_OUT: case CLOCK_SOURCE_DIVIDER_D_OUT: case CLOCK_SOURCE_DIVIDER_E_OUT: - return platform_bring_up_clock_divider(source); + return platform_bring_up_clock_divider(source, true); // If the clock source is based on the main PLL, bring it up. case CLOCK_SOURCE_PLL1: From 7456a69c4706c2563a63e51615a500d272a161c4 Mon Sep 17 00:00:00 2001 From: "Kate J. Temkin" Date: Thu, 4 Apr 2019 15:11:48 -0600 Subject: [PATCH 13/23] firmware: cmake: provide the capability to rely on per-module extra includes --- firmware/cmake/libgreat.cmake | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/firmware/cmake/libgreat.cmake b/firmware/cmake/libgreat.cmake index 551574b..fa0d25c 100644 --- a/firmware/cmake/libgreat.cmake +++ b/firmware/cmake/libgreat.cmake @@ -35,6 +35,7 @@ function(add_flash_executable EXECUTABLE_NAME) # ... and set its default properties. generate_linker_script_arguments(FLAGS_LINKER_SCRIPTS ${LINKER_SCRIPTS_MAIN_CPU} ${LINKER_SCRIPT_FLASH}) target_link_options(${EXECUTABLE_NAME}.elf PRIVATE + ${FLAGS_PLATFORM} ${FLAGS_ARCHITECTURE} ${FLAGS_MAIN_CPU} ${FLAGS_LINK_BOARD} @@ -72,7 +73,7 @@ function(add_libgreat_library LIBRARY_NAME) ${PATH_LIBGREAT_FIRMWARE}/include ${PATH_LIBGREAT_FIRMWARE_PLATFORM}/include ) - target_compile_options(${LIBRARY_NAME} PRIVATE ${FLAGS_COMPILE_COMMON} ${FLAGS_ARCHITECTURE} ${FLAGS_MAIN_CPU}) + target_compile_options(${LIBRARY_NAME} PRIVATE ${FLAGS_COMPILE_COMMON} ${FLAGS_PLATFORM} ${FLAGS_ARCHITECTURE} ${FLAGS_MAIN_CPU}) target_compile_definitions(${LIBRARY_NAME} PRIVATE ${DEFINES_COMMON} ${DEFINES_BOARD}) endfunction(add_libgreat_library) @@ -105,6 +106,22 @@ function(define_libgreat_module MODULE_NAME) endfunction(define_libgreat_module) +# +# Provides the include paths necessary to use a given libgreat module. Will automatically be added to the include path +# for the relevant libgreat module. +# +# Arguments: [include_directories...] +# +function(libgreat_module_include_directories MODULE_NAME) + + # Adds the relevant include direcotires to the given libgreat module. These will also be added to the include path + # of anything that uses the relevant module. + target_include_directories(libgreat_module_${MODULE_NAME} PUBLIC ${ARGN}) + +endfunction(libgreat_module_include_directories) + + + # # Function that adds a libgreat module to a given target. # Arguments: [additional_modules...] @@ -130,6 +147,12 @@ function(use_libgreat_modules TARGET_NAME) get_target_property(MODULE_SOURCES libgreat_module_${MODULE} SOURCES) target_sources(${TARGET_NAME} PRIVATE ${MODULE_SOURCES}) + # Include any includes specified for interfacing with the given target in the downstream target. + get_target_property(ADDITIONAL_INCLUDES libgreat_module_${MODULE} INTERFACE_INCLUDE_DIRECTORIES) + if (NOT ${ADDITIONAL_INCLUDES} MATCHES "-NOTFOUND$") + target_include_directories(${TARGET_NAME} PRIVATE ${ADDITIONAL_INCLUDES}) + endif() + endforeach(MODULE) endfunction(use_libgreat_modules) From eb9809c67501898965aaf54c7eb3b95e492448f1 Mon Sep 17 00:00:00 2001 From: "Kate J. Temkin" Date: Thu, 4 Apr 2019 20:13:33 -0600 Subject: [PATCH 14/23] firmware: cmake: provide functionality to facilitate build configuration --- firmware/cmake/libgreat.cmake | 54 +++++++++++++++++++++++++++++++++++ host/pygreat/comms.py | 7 ++++- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/firmware/cmake/libgreat.cmake b/firmware/cmake/libgreat.cmake index fa0d25c..5f5e0c1 100644 --- a/firmware/cmake/libgreat.cmake +++ b/firmware/cmake/libgreat.cmake @@ -4,6 +4,8 @@ # include_guard() +include(CMakeDependentOption) + # Horrible hack: use libopencm3, for now. include ("${PATH_LIBGREAT_FIRMWARE}/cmake/libopencm3.cmake") @@ -41,6 +43,7 @@ function(add_flash_executable EXECUTABLE_NAME) ${FLAGS_LINK_BOARD} ${FLAGS_LINKER_SCRIPTS} ${FLAGS_LINK_MAIN_CPU} + ${FLAGS_LINK_COMMON} ) target_link_directories(${EXECUTABLE_NAME}.elf PRIVATE ${PATH_LIBOPENCM3}/lib @@ -147,6 +150,11 @@ function(use_libgreat_modules TARGET_NAME) get_target_property(MODULE_SOURCES libgreat_module_${MODULE} SOURCES) target_sources(${TARGET_NAME} PRIVATE ${MODULE_SOURCES}) + # Set a definition that marks that we have the relevant module. + string(TOUPPER "${MODULE}" MODULE_UPPERCASE) + string(REPLACE "-" "_" MODULE_MACRO "${MODULE_UPPERCASE}") + target_compile_definitions(${TARGET_NAME} PUBLIC HAS_LIBGREAT_MODULE_${MODULE_MACRO}) + # Include any includes specified for interfacing with the given target in the downstream target. get_target_property(ADDITIONAL_INCLUDES libgreat_module_${MODULE} INTERFACE_INCLUDE_DIRECTORIES) if (NOT ${ADDITIONAL_INCLUDES} MATCHES "-NOTFOUND$") @@ -156,3 +164,49 @@ function(use_libgreat_modules TARGET_NAME) endforeach(MODULE) endfunction(use_libgreat_modules) + +# +# Function that generates a configuration entry in a build configuration file for a libgreat feature. +# Generates a CONFIG_ENABLE_ entry from a FEATURE_ENABLE_ variable, which contains "#define CONFIG_ENABLE_" +# where appropriate, or an appropriate C++-style comment otherwise. +# +function(libgreat_configuration_for_feature FEATURE_NAME) + + if (FEATURE_ENABLE_${FEATURE_NAME}) + set(CONFIG_ENABLE_${FEATURE_NAME} "#define CONFIG_ENABLE_${FEATURE_NAME}" PARENT_SCOPE) + else() + set(CONFIG_ENABLE_${FEATURE_NAME} "//#define CONFIG_ENABLE_${FEATURE_NAME}" PARENT_SCOPE) + endif() + +endfunction(libgreat_configuration_for_feature) + + +# +# Macro that assists in generation of a quick feature options that make their way into config.h. +# Takes thes same arguments as option(), but generates a CONFIG_ENABLE_ variable that contains +# either a C define with the same name, or a commented-version of that define for disabled features. +# +macro(configuration_feature FEATURE_NAME DESCRIPTION DEFAULT) + option(FEATURE_ENABLE_${FEATURE_NAME} "${DESCRIPTION}" ${DEFAULT}) + libgreat_configuration_for_feature(${FEATURE_NAME}) +endmacro(configuration_feature) + +# +# Special version of configuration_feature that allow for depenedent options +# +macro(dependent_configuration_feature FEATURE_NAME DEPENDS_ON DESCRIPTION DEFAULT) + # TODO: iterate over this and prepend each with their relevant option + cmake_dependent_option(FEATURE_ENABLE_${FEATURE_NAME} "${DESCRIPTION}" ${DEFAULT} FEATURE_ENABLE_${DEPENDS_ON} OFF) + libgreat_configuration_for_feature(${FEATURE_NAME}) +endmacro(dependent_configuration_feature) + +# +# Mark a non-feature as depending on a feature. Used to mask off configuration options that don't apply when a feature is off. +# +macro(configuration_depends_on_feature CONFIG_NAME FEATURE) + if (FEATURE_ENABLE_${FEATURE}) + mark_as_advanced(CLEAR ${CONFIG_NAME}) + else() + mark_as_advanced(FORCED ${CONFIG_NAME}) + endif() +endmacro(configuration_depends_on_feature) diff --git a/host/pygreat/comms.py b/host/pygreat/comms.py index f37ad6f..c78b464 100644 --- a/host/pygreat/comms.py +++ b/host/pygreat/comms.py @@ -1209,7 +1209,12 @@ def __init__(self, comms_backend): # Ensure that the class is defined enough. if self.CLASS_NUMBER is None: - raise ArgumentError("This class is implemented improperly -- it must define CLASS_NUMBER! Object: {}".format(self)) + raise ValueError("This class is implemented improperly -- it must define CLASS_NUMBER! Object: {}".format(self)) + + + def supports_verb(self, verb_name): + """ Returns true iff the given comms class supports a verb with the given name. """ + return hasattr(self, verb_name) def execute_command(self, verb, in_format, out_format, *arguments, **kwargs): From aecadb29eb47ebb3bdea1d32a7c5c79db244d752 Mon Sep 17 00:00:00 2001 From: "Kate J. Temkin" Date: Thu, 4 Apr 2019 20:18:11 -0600 Subject: [PATCH 15/23] firmware: add support for advanced debug logging / backtracing --- firmware/cmake/platform/lpc43xx.cmake | 14 + firmware/include/toolchain.h | 1 + firmware/platform/lpc43xx/CMakeLists.txt | 4 + .../platform/lpc43xx/drivers/platform_clock.c | 23 +- .../lpc43xx/include/drivers/platform_clock.h | 12 + .../lpc43xx/linker/libgreat_lpc43xx.ld | 5 - firmware/third-party/backtrace/LICENSE | 373 +++++++++++++++++ firmware/third-party/backtrace/backtrace.c | 387 ++++++++++++++++++ firmware/third-party/backtrace/backtrace.h | 67 +++ 9 files changed, 880 insertions(+), 6 deletions(-) create mode 100644 firmware/third-party/backtrace/LICENSE create mode 100644 firmware/third-party/backtrace/backtrace.c create mode 100644 firmware/third-party/backtrace/backtrace.h diff --git a/firmware/cmake/platform/lpc43xx.cmake b/firmware/cmake/platform/lpc43xx.cmake index 9a2e28a..9d240bf 100644 --- a/firmware/cmake/platform/lpc43xx.cmake +++ b/firmware/cmake/platform/lpc43xx.cmake @@ -6,11 +6,25 @@ include_guard() # Define that this is an LPC43xx platform. set(LIBGREAT_PLATFORM lpc43xx) +set(LIBGREAT_ARCHITECTURE arm-v7m) # TODO: do we want to use the hard-float ABI, or do we want to make these inter-linkable? +set(FLAGS_PLATFORM -ffunction-sections -fdata-sections) set(FLAGS_MAIN_CPU -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16) set(FLAGS_SECONDARY_CPU -mcpu=cortex-m0 -mfloat-abi=soft) +# FIXME: move some of this to an arm-v7m archictecture file +# TODO: decide if we want this off unless debugging is on +option(FEATURE_ENABLE_BACKTRACE "Enables rich debug back-tracking at a cost of a slightly increased build size." ON) +if (FEATURE_ENABLE_BACKTRACE) + set(FLAGS_PLATFORM ${FLAGS_PLATFORM} -funwind-tables -fno-omit-frame-pointer) +endif() + +option(FEATURE_ENABLE_FUNCTION_NAMES "Enables function names in output files, which improves debug messages but significantly increases file size." ${DEFAULT_DEBUG_ONLY}) +if (FEATURE_ENABLE_FUNCTION_NAMES) + set(FLAGS_PLATFORM ${FLAGS_PLATFORM} -mpoke-function-name) +endif() + set(FLAGS_LINK_BOARD -nostartfiles -Wl,--gc-sections) set(FLAGS_LINK_MAIN_CPU -Xlinker -Map=m4.map) set(FLAGS_LINK_SECONDARY_CPU -Xlinker -Map=m0.map) diff --git a/firmware/include/toolchain.h b/firmware/include/toolchain.h index 5e0a688..26971a2 100644 --- a/firmware/include/toolchain.h +++ b/firmware/include/toolchain.h @@ -20,6 +20,7 @@ #define ATTR_SECTION(x) __attribute__((section(x))) #define ATTR_WEAK __attribute__((weak)) #define ATTR_NORETURN __attribute__((noreturn)) +#define ATTR_NAKED __attribute__((naked)) #define ATTR_PRINTF __attribute__((format (printf, 1, 2))) #define ATTR_PRINTF_N(n) __attribute__((format (printf, n, n + 1))) diff --git a/firmware/platform/lpc43xx/CMakeLists.txt b/firmware/platform/lpc43xx/CMakeLists.txt index bf2208d..1090956 100644 --- a/firmware/platform/lpc43xx/CMakeLists.txt +++ b/firmware/platform/lpc43xx/CMakeLists.txt @@ -56,3 +56,7 @@ define_libgreat_module(usb_comms define_libgreat_module(gpio ${PATH_LIBGREAT_FIRMWARE_PLATFORM_DRIVERS}/gpio.c ) + +# Backtrace support. +define_libgreat_module(debug-backtrace ${PATH_LIBGREAT_FIRMWARE}/third-party/backtrace/backtrace.c) +libgreat_module_include_directories(debug-backtrace ${PATH_LIBGREAT_FIRMWARE}/third-party/backtrace/) diff --git a/firmware/platform/lpc43xx/drivers/platform_clock.c b/firmware/platform/lpc43xx/drivers/platform_clock.c index cfb5c5e..f72743e 100644 --- a/firmware/platform/lpc43xx/drivers/platform_clock.c +++ b/firmware/platform/lpc43xx/drivers/platform_clock.c @@ -1297,7 +1297,7 @@ static int platform_configure_main_pll_parameters(uint32_t target_frequency, uin // If the target frequency is too low for our PLL to synthesize using its CCO, increase our target frequency, // but compensate by increasing our output divider. while (target_frequency < cco_low_bound) { - pr_info("pll1: target frequency %" PRIu32 " Hz < CCO_min; doubling to %" PRIu32 " Hz and compensating with post-divider\n", + pr_debug("pll1: target frequency %" PRIu32 " Hz < CCO_min; doubling to %" PRIu32 " Hz and compensating with post-divider\n", target_frequency, target_frequency * 2); output_divisor++; target_frequency *= 2; @@ -2145,3 +2145,24 @@ void platform_initialize_clocks(void) pr_info("System clock bringup complete.\n"); } + +/** + * Returns the name of the clock source currently driving the CPU, as a string. + * Intended for debugging, only. + */ +const char *platform_get_cpu_clock_source_name(void) +{ + platform_base_clock_t *m4 = BASE_CLOCK(m4); + return platform_get_clock_source_name(m4->source); +} + + +/** + * Returns the frequency of the clock source currently driving the CPU. + * Intended for debugging, only. + */ +uint32_t platform_get_cpu_clock_source_frequency(void) +{ + platform_base_clock_t *m4 = BASE_CLOCK(m4); + return platform_get_base_clock_frequency(m4); +} diff --git a/firmware/platform/lpc43xx/include/drivers/platform_clock.h b/firmware/platform/lpc43xx/include/drivers/platform_clock.h index 46023f1..7a5f854 100644 --- a/firmware/platform/lpc43xx/include/drivers/platform_clock.h +++ b/firmware/platform/lpc43xx/include/drivers/platform_clock.h @@ -593,4 +593,16 @@ uint32_t platform_get_branch_clock_frequency(platform_branch_clock_t *clock); */ clock_source_t platform_get_parent_clock_source(clock_source_t source); +/** + * Returns the name of the clock source currently driving the CPU, as a string. + * Intended for debugging, only. + */ +const char *platform_get_cpu_clock_source_name(void); + +/** + * Returns the frequency of the clock source currently driving the CPU. + * Intended for debugging, only. + */ +uint32_t platform_get_cpu_clock_source_frequency(void); + #endif diff --git a/firmware/platform/lpc43xx/linker/libgreat_lpc43xx.ld b/firmware/platform/lpc43xx/linker/libgreat_lpc43xx.ld index e9da3d2..d2c8e48 100644 --- a/firmware/platform/lpc43xx/linker/libgreat_lpc43xx.ld +++ b/firmware/platform/lpc43xx/linker/libgreat_lpc43xx.ld @@ -143,11 +143,6 @@ SECTIONS _ebss = .; } >ram_local2 - /* exception unwind data - required due to libgcc.a issuing /0 exceptions */ - .ARM.extab : { - *(.ARM.extab*) - } >ram_local2 - /* * The .eh_frame section appears to be used for C++ exception handling. * You may need to fix this if you're using C++. diff --git a/firmware/third-party/backtrace/LICENSE b/firmware/third-party/backtrace/LICENSE new file mode 100644 index 0000000..a612ad9 --- /dev/null +++ b/firmware/third-party/backtrace/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/firmware/third-party/backtrace/backtrace.c b/firmware/third-party/backtrace/backtrace.c new file mode 100644 index 0000000..99c8cb4 --- /dev/null +++ b/firmware/third-party/backtrace/backtrace.c @@ -0,0 +1,387 @@ +/* + * Copyright 2015 Stephen Street + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include +#include +#include + +/* This prevents the linking of libgcc unwinder code */ +void __aeabi_unwind_cpp_pr0(void); +void __aeabi_unwind_cpp_pr1(void); +void __aeabi_unwind_cpp_pr2(void); + +void __aeabi_unwind_cpp_pr0(void) +{ +}; + +void __aeabi_unwind_cpp_pr1(void) +{ +}; + +void __aeabi_unwind_cpp_pr2(void) +{ +}; + +static inline __attribute__((always_inline)) uint32_t prel31_to_addr(const uint32_t *prel31) +{ + int32_t offset = (((int32_t)(*prel31)) << 1) >> 1; + return ((uint32_t)prel31 + offset) & 0x7fffffff; +} + +static const struct unwind_index *unwind_search_index(const unwind_index_t *start, const unwind_index_t *end, uint32_t ip) +{ + const struct unwind_index *middle; + + /* Perform a binary search of the unwind index */ + while (start < end - 1) { + middle = start + ((end - start + 1) >> 1); + if (ip < prel31_to_addr(&middle->addr_offset)) + end = middle; + else + start = middle; + } + return start; +} + +static const char *unwind_get_function_name(void *address) +{ + uint32_t flag_word = *(uint32_t *)(address - 4); + if ((flag_word & 0xff000000) == 0xff000000) { + return (const char *)(address - 4 - (flag_word & 0x00ffffff)); + } + return "unknown"; +} + +static int unwind_get_next_byte(unwind_control_block_t *ucb) +{ + int instruction; + + /* Are there more instructions */ + if (ucb->remaining == 0) + return -1; + + /* Extract the current instruction */ + instruction = ((*ucb->current) >> (ucb->byte << 3)) & 0xff; + + /* Move the next byte */ + --ucb->byte; + if (ucb->byte < 0) { + ++ucb->current; + ucb->byte = 3; + } + --ucb->remaining; + + return instruction; +} + +static int unwind_control_block_init(unwind_control_block_t *ucb, const uint32_t *instructions, const backtrace_frame_t *frame) +{ + /* Initialize control block */ + memset(ucb, 0, sizeof(unwind_control_block_t)); + ucb->current = instructions; + + /* Is the a short unwind description */ + if ((*instructions & 0xff000000) == 0x80000000) { + ucb->remaining = 3; + ucb->byte = 2; + /* Is the a long unwind description */ + } else if ((*instructions & 0xff000000) == 0x81000000) { + ucb->remaining = ((*instructions & 0x00ff0000) >> 14) + 2; + ucb->byte = 1; + } else + return -1; + + /* Initialize the virtual register set */ + if (frame) { + ucb->vrs[7] = frame->fp; + ucb->vrs[13] = frame->sp; + ucb->vrs[14] = frame->lr; + ucb->vrs[15] = 0; + } + + /* All good */ + return 0; +} + +static int unwind_execute_instruction(unwind_control_block_t *ucb) +{ + int instruction; + uint32_t mask; + uint32_t reg; + uint32_t *vsp; + + /* Consume all instruction byte */ + while ((instruction = unwind_get_next_byte(ucb)) != -1) { + + if ((instruction & 0xc0) == 0x00) { + /* vsp = vsp + (xxxxxx << 2) + 4 */ + ucb->vrs[13] += ((instruction & 0x3f) << 2) + 4; + + } else if ((instruction & 0xc0) == 0x40) { + /* vsp = vsp - (xxxxxx << 2) - 4 */ + ucb->vrs[13] -= ((instruction & 0x3f) << 2) - 4; + + } else if ((instruction & 0xf0) == 0x80) { + /* pop under mask {r15-r12},{r11-r4} or refuse to unwind */ + instruction = instruction << 8 | unwind_get_next_byte(ucb); + + /* Check for refuse to unwind */ + if (instruction == 0x8000) + return 0; + + /* Pop registers using mask */ + vsp = (uint32_t *)ucb->vrs[13]; + mask = instruction & 0xfff; + + reg = 4; + while (mask != 0) { + if ((mask & 0x001) != 0) + ucb->vrs[reg] = *vsp++; + mask = mask >> 1; + ++reg; + } + + /* Patch up the vrs sp if it was in the mask */ + if ((mask & (1 << (13 - 4))) != 0) + ucb->vrs[13] = (uint32_t)vsp; + + } else if ((instruction & 0xf0) == 0x90 && instruction != 0x9d && instruction != 0x9f) { + /* vsp = r[nnnn] */ + ucb->vrs[13] = ucb->vrs[instruction & 0x0f]; + + } else if ((instruction & 0xf0) == 0xa0) { + /* pop r4-r[4+nnn] or pop r4-r[4+nnn], r14*/ + vsp = (uint32_t *)ucb->vrs[13]; + + for (reg = 4; reg <= (instruction & 0x07) + 4; ++reg) + ucb->vrs[reg] = *vsp++; + + if (instruction & 0x80) + ucb->vrs[14] = *vsp++; + + ucb->vrs[13] = (uint32_t)vsp; + + } else if (instruction == 0xb0) { + /* finished */ + if (ucb->vrs[15] == 0) + ucb->vrs[15] = ucb->vrs[14]; + + /* All done unwinding */ + return 0; + + } else if (instruction == 0xb1) { + /* pop register under mask {r3,r2,r1,r0} */ + vsp = (uint32_t *)ucb->vrs[13]; + mask = unwind_get_next_byte(ucb); + + reg = 0; + while (mask != 0) { + if ((mask & 0x01) != 0) + ucb->vrs[reg] = *vsp++; + mask = mask >> 1; + ++reg; + } + ucb->vrs[13] = (uint32_t)vsp; + + } else if (instruction == 0xb2) { + /* vps = vsp + 0x204 + (uleb128 << 2) */ + ucb->vrs[13] += 0x204 + (unwind_get_next_byte(ucb) << 2); + + } else if (instruction == 0xb3 || instruction == 0xc8 || instruction == 0xc9) { + /* pop VFP double-precision registers */ + vsp = (uint32_t *)ucb->vrs[13]; + + /* D[ssss]-D[ssss+cccc] */ + ucb->vrs[14] = *vsp++; + + if (instruction == 0xc8) { + /* D[16+sssss]-D[16+ssss+cccc] */ + ucb->vrs[14] |= 1 << 16; + } + + if (instruction != 0xb3) { + /* D[sssss]-D[ssss+cccc] */ + ucb->vrs[14] |= 1 << 17; + } + + ucb->vrs[13] = (uint32_t)vsp; + + } else if ((instruction & 0xf8) == 0xb8 || (instruction & 0xf8) == 0xd0) { + /* Pop VFP double precision registers D[8]-D[8+nnn] */ + ucb->vrs[14] = 0x80 | (instruction & 0x07); + + if ((instruction & 0xf8) == 0xd0) { + ucb->vrs[14] = 1 << 17; + } + + } else + return -1; + } + + return instruction != -1; +} + +static inline __attribute__((always_inline)) uint32_t *read_psp(void) +{ + /* Read the current PSP and return its value as a pointer */ + uint32_t psp; + + __asm volatile ( + " mrs %0, psp \n" + : "=r" (psp) : : + ); + + return (uint32_t*)psp; +} + +/* TODO How do I range check the stack pointer */ +static int unwind_frame(backtrace_frame_t *frame) +{ + unwind_control_block_t ucb; + const unwind_index_t *index; + const uint32_t *instructions; + int execution_result; + + /* Search the unwind index for the matching unwind table */ + index = unwind_search_index(__exidx_start, __exidx_end, frame->pc); + if (index == NULL) + return -1; + + /* Make sure we can unwind this frame */ + if (index->insn == 0x00000001) + return 0; + + /* Get the pointer to the first unwind instruction */ + if (index->insn & 0x80000000) + instructions = &index->insn; + else + instructions = (uint32_t *)prel31_to_addr(&index->insn); + + /* Initialize the unwind control block */ + if (unwind_control_block_init(&ucb, instructions, frame) < 0) + return -1; + + /* Execute the unwind instructions TODO range check the stack pointer */ + while ((execution_result = unwind_execute_instruction(&ucb)) > 0); + if (execution_result == -1) + return -1; + + /* Set the virtual pc to the virtual lr if this is the first unwind */ + if (ucb.vrs[15] == 0) + ucb.vrs[15] = ucb.vrs[14]; + + /* Check for exception return */ + /* TODO Test with other ARM processors to verify this method. */ + if ((ucb.vrs[15] & 0xf0000000) == 0xf0000000) { + /* According to the Cortex Programming Manual (p.44), the stack address is always 8-byte aligned (Cortex-M7). + Depending on where the exception came from (MSP or PSP), we need the right SP value to work with. + + ucb.vrs[7] contains the right value, so take it and align it by 8 bytes, store it as the current + SP to work with (ucb.vrs[13]) which is then saved as the current (virtual) frame's SP. + */ + uint32_t *stack; + ucb.vrs[13] = (ucb.vrs[7] & ~7); + + /* If we need to start from the MSP, we need to go down X words to find the PC, where: + X=2 if it was a non-floating-point exception + X=20 if it was a floating-point (VFP) exception + + If we need to start from the PSP, we need to go up exactly 6 words to find the PC. + See the ARMv7-M Architecture Reference Manual p.594 and Cortex-M7 Processor Programming Manual p.44/p.45 for details. + */ + if ((ucb.vrs[15] & 0xc) == 0) { + /* Return to Handler Mode: MSP (0xffffff-1) */ + stack = (uint32_t*)(ucb.vrs[13]); + + /* The PC is always 2 words down from the MSP, if it was a non-floating-point exception */ + stack -= 2; + + /* If there was a VFP exception (0xffffffe1), the PC is located another 18 words down */ + if ((ucb.vrs[15] & 0xf0) == 0xe0) + { + stack -= 18; + } + } + else { + /* Return to Thread Mode: PSP (0xffffff-d) */ + stack = read_psp(); + + /* The PC is always 6 words up from the PSP */ + stack += 6; + } + + /* Store the PC */ + ucb.vrs[15] = *stack--; + + /* Store the LR */ + ucb.vrs[14] = *stack--; + } + + /* We are done if current frame pc is equal to the virtual pc, prevent infinite loop */ + if (frame->pc == ucb.vrs[15]) + return 0; + + /* Update the frame */ + frame->fp = ucb.vrs[7]; + frame->sp = ucb.vrs[13]; + frame->lr = ucb.vrs[14]; + frame->pc = ucb.vrs[15]; + + /* All good */ + return 1; +} + +int _backtrace_unwind(backtrace_t *buffer, int size, backtrace_frame_t *frame) +{ + int count = 0; + + /* Initialize the backtrace frame buffer */ + memset(buffer, 0, sizeof(backtrace_t) * size); + + /* Unwind all frames */ + do { + if (frame->pc == 0) { + /* Reached __exidx_end. */ + buffer[count++].name = ""; + break; + } + + if (frame->pc == 0x00000001) { + /* Reached .cantunwind instruction. */ + buffer[count++].name = ""; + break; + } + + /* Find the unwind index of the current frame pc */ + const unwind_index_t *index = unwind_search_index(__exidx_start, __exidx_end, frame->pc); + + /* Clear last bit (Thumb indicator) */ + frame->pc &= 0xfffffffeU; + + /* Generate the backtrace information */ + buffer[count].address = (void *)frame->pc; + buffer[count].function = (void *)prel31_to_addr(&index->addr_offset); + buffer[count].name = unwind_get_function_name(buffer[count].function); + + /* Next backtrace frame */ + ++count; + + } while (unwind_frame(frame) && count < size); + + /* All done */ + return count; +} + +const char *backtrace_function_name(uint32_t pc) +{ + const unwind_index_t *index = unwind_search_index(__exidx_start, __exidx_end, pc); + if (!index) + return 0; + + return unwind_get_function_name((void *)prel31_to_addr(&index->addr_offset)); +} diff --git a/firmware/third-party/backtrace/backtrace.h b/firmware/third-party/backtrace/backtrace.h new file mode 100644 index 0000000..10d60f8 --- /dev/null +++ b/firmware/third-party/backtrace/backtrace.h @@ -0,0 +1,67 @@ +/* + * Copyright 2015 Stephen Street + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef BACKTRACE_H_ +#define BACKTRACE_H_ + +#include + +typedef struct backtrace_frame +{ + uint32_t fp; + uint32_t sp; + uint32_t lr; + uint32_t pc; +} backtrace_frame_t; + +typedef struct backtrace +{ + void *function; + void *address; + const char *name; +} backtrace_t; + +typedef struct unwind_control_block +{ + uint32_t vrs[16]; + const uint32_t *current; + int remaining; + int byte; +} unwind_control_block_t; + +typedef struct unwind_index +{ + uint32_t addr_offset; + uint32_t insn; +} unwind_index_t; + +/* These symbols point to the unwind index and should be provide by the linker script */ +extern const unwind_index_t __exidx_start[]; +extern const unwind_index_t __exidx_end[]; + +int _backtrace_unwind(backtrace_t *buffer, int size, backtrace_frame_t *frame); +const char *backtrace_function_name(uint32_t pc); + +static inline int __attribute__((always_inline)) backtrace_unwind(backtrace_t *buffer, int size) +{ + /* Get the current pc */ + register uint32_t pc; + __asm__ volatile("mov %0, pc" : "=r"(pc)); + + /* Initialize the stack frame */ + backtrace_frame_t frame; + frame.sp = (uint32_t)__builtin_frame_address(0); + frame.fp = (uint32_t)__builtin_frame_address(0); + frame.lr = (uint32_t)__builtin_return_address(0); + frame.pc = pc; + + /* Let it rip */ + return _backtrace_unwind(buffer, size, &frame); +} + +#endif /* BACKTRACE_H_ */ From d581746ac8c7bdb9e08bb9910d2e3a4742110e3c Mon Sep 17 00:00:00 2001 From: "Kate J. Temkin" Date: Thu, 4 Apr 2019 20:23:12 -0600 Subject: [PATCH 16/23] firmware: toolchain: add USED attribute --- firmware/include/toolchain.h | 1 + 1 file changed, 1 insertion(+) diff --git a/firmware/include/toolchain.h b/firmware/include/toolchain.h index 26971a2..d7eb8d1 100644 --- a/firmware/include/toolchain.h +++ b/firmware/include/toolchain.h @@ -23,6 +23,7 @@ #define ATTR_NAKED __attribute__((naked)) #define ATTR_PRINTF __attribute__((format (printf, 1, 2))) #define ATTR_PRINTF_N(n) __attribute__((format (printf, n, n + 1))) +#define ATTR_USED __attribute__((used)) /** From 22ee0ace8702311e42df4edcbc80f3e5a49317b2 Mon Sep 17 00:00:00 2001 From: "Kate J. Temkin" Date: Thu, 4 Apr 2019 21:16:36 -0600 Subject: [PATCH 17/23] firmware: cmake: improve dependent-feature hiding logic --- firmware/cmake/libgreat.cmake | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/firmware/cmake/libgreat.cmake b/firmware/cmake/libgreat.cmake index 5f5e0c1..e80028e 100644 --- a/firmware/cmake/libgreat.cmake +++ b/firmware/cmake/libgreat.cmake @@ -203,10 +203,24 @@ endmacro(dependent_configuration_feature) # # Mark a non-feature as depending on a feature. Used to mask off configuration options that don't apply when a feature is off. # -macro(configuration_depends_on_feature CONFIG_NAME FEATURE) - if (FEATURE_ENABLE_${FEATURE}) - mark_as_advanced(CLEAR ${CONFIG_NAME}) +function(configuration_depends_on_features CONFIG_NAME) + set(ALL_ENABLED ON) + + # Check to see if all of our dependencies are met. + foreach (FEATURE ${ARGN}) + if (NOT FEATURE_ENABLE_${FEATURE}) + message(STATUS "not enabling ${CONFIG_NAME} as ${FEATURE} isn't on") + set(ALL_ENABLED OFF) + endif() + endforeach() + + # If they are, show the option... + if (ALL_ENABLED) + mark_as_advanced(CLEAR ${CONFIG_NAME}) + + # ... otherwise, hide it. else() mark_as_advanced(FORCED ${CONFIG_NAME}) - endif() -endmacro(configuration_depends_on_feature) + endif() + +endfunction(configuration_depends_on_features) From 897e30d43639b452460a9a1451f4de7a223badaa Mon Sep 17 00:00:00 2001 From: "Kate J. Temkin" Date: Thu, 4 Apr 2019 21:18:46 -0600 Subject: [PATCH 18/23] firmware: linker: don't include a redundnant and incorrect extab location --- firmware/platform/lpc43xx/linker/LPC43xx_l0adable.ld | 6 +----- .../platform/lpc43xx/linker/libgreat_lpc43xx_rom_to_ram.ld | 5 ----- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/firmware/platform/lpc43xx/linker/LPC43xx_l0adable.ld b/firmware/platform/lpc43xx/linker/LPC43xx_l0adable.ld index 718be64..62167e9 100644 --- a/firmware/platform/lpc43xx/linker/LPC43xx_l0adable.ld +++ b/firmware/platform/lpc43xx/linker/LPC43xx_l0adable.ld @@ -81,7 +81,7 @@ SECTIONS .ARM.extab : { *(.ARM.extab*) } >ram_local1 - + /* exception index - required due to libgcc.a issuing /0 exceptions */ .ARM.exidx : { __exidx_start = .; @@ -115,10 +115,6 @@ SECTIONS _ebss = .; } >ram_local2 - /* exception unwind data - required due to libgcc.a issuing /0 exceptions */ - .ARM.extab : { - *(.ARM.extab*) - } >ram_local2 /* * The .eh_frame section appears to be used for C++ exception handling. diff --git a/firmware/platform/lpc43xx/linker/libgreat_lpc43xx_rom_to_ram.ld b/firmware/platform/lpc43xx/linker/libgreat_lpc43xx_rom_to_ram.ld index 29c56dc..ea3b13a 100644 --- a/firmware/platform/lpc43xx/linker/libgreat_lpc43xx_rom_to_ram.ld +++ b/firmware/platform/lpc43xx/linker/libgreat_lpc43xx_rom_to_ram.ld @@ -143,11 +143,6 @@ SECTIONS } >ram_local2 - /* exception unwind data - required due to libgcc.a issuing /0 exceptions */ - .ARM.extab : { - *(.ARM.extab*) - } >ram_local2 - /* * The .eh_frame section appears to be used for C++ exception handling. * You may need to fix this if you're using C++. From e00a9a862bb06f1ea9383055241e0ac6bd92fbb2 Mon Sep 17 00:00:00 2001 From: "Kate J. Temkin" Date: Thu, 4 Apr 2019 21:20:11 -0600 Subject: [PATCH 19/23] firmware: cmake: silence an errant status message --- firmware/cmake/libgreat.cmake | 1 - 1 file changed, 1 deletion(-) diff --git a/firmware/cmake/libgreat.cmake b/firmware/cmake/libgreat.cmake index e80028e..e6529b5 100644 --- a/firmware/cmake/libgreat.cmake +++ b/firmware/cmake/libgreat.cmake @@ -209,7 +209,6 @@ function(configuration_depends_on_features CONFIG_NAME) # Check to see if all of our dependencies are met. foreach (FEATURE ${ARGN}) if (NOT FEATURE_ENABLE_${FEATURE}) - message(STATUS "not enabling ${CONFIG_NAME} as ${FEATURE} isn't on") set(ALL_ENABLED OFF) endif() endforeach() From 14c00b7c8f036f4d467e4b1a324ffa3566b126fa Mon Sep 17 00:00:00 2001 From: "Kate J. Temkin" Date: Wed, 24 Apr 2019 16:09:21 -0600 Subject: [PATCH 20/23] release-prep: improve autoversioning scheme --- .gitignore | 1 + host/setup.py | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 9f64ada..4ed7db6 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ firmware/*/build # release files VERSION +.eggs diff --git a/host/setup.py b/host/setup.py index a3f3edf..9d8f5be 100644 --- a/host/setup.py +++ b/host/setup.py @@ -8,20 +8,21 @@ def read(fname): setup_req = [] -version_format = None -version = None +setup_options = {} # Deduce version, if possible. if os.path.isfile('../VERSION'): - version = read('../VERSION') + setup_options['version'] = read('../VERSION').strip() else: - version_format = '{tag}.dev{commitcount}+git.{gitsha}' - setup_req.append('setuptools-git-version') + setup_options['version_config'] = { + "version_format": '{tag}.dev{commitcount}+git.{gitsha}', + "starting_version": "2019.05.01" + } + setup_req.append('better-setuptools-git-version') + setup( name='pygreat', - version=version, - version_format=version_format, setup_requires=setup_req, url='https://greatscottgadgets.com/greatfet/', license='BSD', @@ -50,5 +51,6 @@ def read(fname): 'Topic :: Scientific/Engineering', 'Topic :: Security', ], - extras_require={} + extras_require={}, + **setup_options ) From 424fa14429a1cde45713928a0af90c05c10f0271 Mon Sep 17 00:00:00 2001 From: "Kate J. Temkin" Date: Sat, 4 May 2019 00:28:39 -0600 Subject: [PATCH 21/23] host: fix our versioning scheme --- host/setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/host/setup.py b/host/setup.py index 9d8f5be..0973b3f 100644 --- a/host/setup.py +++ b/host/setup.py @@ -10,17 +10,17 @@ def read(fname): setup_req = [] setup_options = {} + # Deduce version, if possible. if os.path.isfile('../VERSION'): setup_options['version'] = read('../VERSION').strip() else: setup_options['version_config'] = { - "version_format": '{tag}.dev{commitcount}+git.{gitsha}', + "version_format": '{tag}.dev+git.{sha}', "starting_version": "2019.05.01" } setup_req.append('better-setuptools-git-version') - setup( name='pygreat', setup_requires=setup_req, From dfcd6a4ae3217d0cfbbceb929b2f5ab77c43f26c Mon Sep 17 00:00:00 2001 From: "Kate J. Temkin" Date: Sat, 4 May 2019 00:29:25 -0600 Subject: [PATCH 22/23] pygreat: restore python2 compatibility --- host/pygreat/comms_backends/usb.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/host/pygreat/comms_backends/usb.py b/host/pygreat/comms_backends/usb.py index 1f7a1ee..4bc6437 100644 --- a/host/pygreat/comms_backends/usb.py +++ b/host/pygreat/comms_backends/usb.py @@ -156,7 +156,7 @@ def _vendor_request_in_string(self, request, length=255, value=0, index=0, timeo """ raw = self._vendor_request(usb.ENDPOINT_IN, request, length_or_data=length, value=value, index=index, timeout=timeout) - return raw.tobytes().ecode(encoding, errors='ignore') + return raw.tostring().encode(encoding, errors='ignore') def _vendor_request_out(self, request, value=0, index=0, data=None, timeout=1000): @@ -282,10 +282,10 @@ def execute_raw_command(self, class_number, verb, data=None, timeout=1000, encod # If we were passed an encoding, attempt to decode the response data. if encoding and response: - response = response.tobytes().decode(encoding, errors='ignore') + response = response.tostring().decode(encoding, errors='ignore') # Return the device's response. - return response.tobytes() + return response.tostring() except Exception as e: From 1578468b53542b0e13f453046d168388babb9070 Mon Sep 17 00:00:00 2001 From: "Kate J. Temkin" Date: Tue, 21 May 2019 13:55:21 -0600 Subject: [PATCH 23/23] windows-compat: allow cases where libusb reports no error code --- host/pygreat/board.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/host/pygreat/board.py b/host/pygreat/board.py index a6df9a6..14b711c 100644 --- a/host/pygreat/board.py +++ b/host/pygreat/board.py @@ -173,7 +173,7 @@ def accepts_connected_device(cls, **device_identifiers): # A pipe error here likely means the device didn't support a start-up # command, and STALLED. # We'll interpret that as a "we don't accept this device" by default. - if e.errno == LIBUSB_PIPE_ERROR: + if e.errno == LIBUSB_PIPE_ERROR or e.errno == None: return False else: raise e