summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjaseg <git@jaseg.de>2021-03-02 19:27:52 +0100
committerjaseg <git@jaseg.de>2021-03-02 19:27:52 +0100
commitb328ef60595523e3922aae994d7bbe06c7c3fa56 (patch)
treebdbbd1a4a0528d6f479f903fbd1f1c04476ec5fe
parent6eddc61626d470363ba464c57a5fc5ec7e8ce329 (diff)
parent5b94dee9cfb1eaaf28510b843c60355663b660ea (diff)
downloadsecure-hid-b328ef60595523e3922aae994d7bbe06c7c3fa56.tar.gz
secure-hid-b328ef60595523e3922aae994d7bbe06c7c3fa56.tar.bz2
secure-hid-b328ef60595523e3922aae994d7bbe06c7c3fa56.zip
Add 'fw/' from commit '5b94dee9cfb1eaaf28510b843c60355663b660ea'
git-subtree-dir: fw git-subtree-mainline: 6eddc61626d470363ba464c57a5fc5ec7e8ce329 git-subtree-split: 5b94dee9cfb1eaaf28510b843c60355663b660ea
-rw-r--r--fw/.gitignore15
-rw-r--r--fw/.gitmodules3
-rw-r--r--fw/.travis.yml17
-rw-r--r--fw/CMakeLists.txt108
-rw-r--r--fw/COPYING.GPL3674
-rw-r--r--fw/COPYING.LGPL3165
-rw-r--r--fw/Doxyfile2406
-rw-r--r--fw/README.md105
-rw-r--r--fw/USB_icon.svg88
-rw-r--r--fw/adjectives.py34
-rw-r--r--fw/build/.gitignore1
-rw-r--r--fw/cmake/doc.cmake6
-rw-r--r--fw/cmake/openocd_flash.cmake18
-rw-r--r--fw/cmake/toolchain.cmake15
-rwxr-xr-xfw/hexcom.py54
-rw-r--r--fw/hexdump.py27
-rwxr-xr-xfw/hexnoise.py384
-rw-r--r--fw/include/cobs.h27
-rw-r--r--fw/include/driver/usbh_device_driver.h326
-rw-r--r--fw/include/usbh_config.h62
-rw-r--r--fw/include/usbh_core.h65
-rw-r--r--fw/include/usbh_driver_ac_midi.h65
-rw-r--r--fw/include/usbh_driver_gp_xbox.h78
-rw-r--r--fw/include/usbh_driver_hid.h85
-rw-r--r--fw/include/usbh_driver_hub.h39
-rw-r--r--fw/include/usbh_lld_stm32f4.h44
-rwxr-xr-xfw/initRepo.sh4
-rw-r--r--fw/keymap.py0
m---------fw/libopencm30
-rw-r--r--fw/libusbhost_stm32f4.ld52
-rw-r--r--fw/nouns.py34
-rw-r--r--fw/openocd.cfg16
-rwxr-xr-xfw/pairing.py164
-rw-r--r--fw/secureusb_icon.pngbin0 -> 13260 bytes
-rw-r--r--fw/src/CMakeLists.txt69
-rw-r--r--fw/src/cobs.c390
-rw-r--r--fw/src/crypto/CMakeLists.txt93
-rw-r--r--fw/src/demo.c684
-rw-r--r--fw/src/frama_c_cmdline1
-rw-r--r--fw/src/hid_keycodes.c48
-rw-r--r--fw/src/hid_keycodes.h218
-rw-r--r--fw/src/noise.c445
-rw-r--r--fw/src/noise.h56
-rw-r--r--fw/src/packet_interface.c98
-rw-r--r--fw/src/packet_interface.h58
-rw-r--r--fw/src/pgp_wordlist257
-rw-r--r--fw/src/rand_stm32.c135
-rw-r--r--fw/src/rand_stm32.h11
-rw-r--r--fw/src/tinyprintf.c523
-rw-r--r--fw/src/tinyprintf.h186
-rw-r--r--fw/src/tracing.h25
-rw-r--r--fw/src/usart_helpers.c149
-rw-r--r--fw/src/usart_helpers.h88
-rw-r--r--fw/src/usbh_core.c726
-rw-r--r--fw/src/usbh_driver_ac_midi.c364
-rw-r--r--fw/src/usbh_driver_ac_midi_private.h52
-rw-r--r--fw/src/usbh_driver_gp_xbox.c368
-rw-r--r--fw/src/usbh_driver_hid.c412
-rw-r--r--fw/src/usbh_driver_hub.c675
-rw-r--r--fw/src/usbh_driver_hub_private.h123
-rw-r--r--fw/src/usbh_lld_stm32f4.c1041
-rw-r--r--fw/src/words.c521
-rw-r--r--fw/src/words.h7
63 files changed, 13004 insertions, 0 deletions
diff --git a/fw/.gitignore b/fw/.gitignore
new file mode 100644
index 0000000..9c68981
--- /dev/null
+++ b/fw/.gitignore
@@ -0,0 +1,15 @@
+*.o
+*.d
+*.elf
+*.map
+*.bin
+*.hex
+*.cproject
+*.project
+*.a
+doc
+Makefile
+CMakeCache.txt
+cmake_install.cmake
+CMakeFiles
+src/demo
diff --git a/fw/.gitmodules b/fw/.gitmodules
new file mode 100644
index 0000000..2822909
--- /dev/null
+++ b/fw/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "libopencm3"]
+ path = libopencm3
+ url = https://amirhammad@github.com/amirhammad/libopencm3
diff --git a/fw/.travis.yml b/fw/.travis.yml
new file mode 100644
index 0000000..f453c85
--- /dev/null
+++ b/fw/.travis.yml
@@ -0,0 +1,17 @@
+language: c
+compiler:
+ - gcc
+before_install:
+ - sudo add-apt-repository -y ppa:terry.guo/gcc-arm-embedded
+ - sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-key 6D1D8367A3421AFB
+ - sudo apt-get update -o Dir::Etc::sourcelist="sources.list.d/terry_guo-gcc-arm-embedded-precise.list" -o Dir::Etc::sourceparts="-" -o APT::Get::List-Cleanup="0"
+ - sudo apt-get install gcc-arm-none-eabi
+script:
+ - cd build && cmake $FLAGS_matrix .. && make
+
+env:
+ matrix:
+ - FLAGS_matrix="-DUSE_USART_DEBUG=FALSE -DUSE_STM32F4_HS=TRUE -DUSE_STM32F4_FS=TRUE"
+ - FLAGS_matrix="-DUSE_USART_DEBUG=TRUE -DUSE_STM32F4_HS=TRUE -DUSE_STM32F4_FS=TRUE"
+ - FLAGS_matrix="-DUSE_USART_DEBUG=TRUE -DUSE_STM32F4_HS=FALSE -DUSE_STM32F4_FS=TRUE"
+ - FLAGS_matrix="-DUSE_USART_DEBUG=TRUE -DUSE_STM32F4_HS=TRUE -DUSE_STM32F4_FS=FALSE"
diff --git a/fw/CMakeLists.txt b/fw/CMakeLists.txt
new file mode 100644
index 0000000..53724b5
--- /dev/null
+++ b/fw/CMakeLists.txt
@@ -0,0 +1,108 @@
+cmake_minimum_required (VERSION 2.6)
+
+# initialize compiler
+include (cmake/toolchain.cmake)
+
+# initialize flashing
+include (cmake/openocd_flash.cmake)
+
+# initilize doc
+include (cmake/doc.cmake)
+
+project (libusbhost C)
+
+# Declare cached variables
+
+set (USE_STM32F4_FS TRUE CACHE BOOL "Use USB full speed (FS) host periphery")
+set (USE_STM32F4_HS TRUE CACHE BOOL "Use USB high speed (HS) host periphery")
+set (USE_USART_DEBUG TRUE CACHE BOOL "Enable human-readable serial debug output")
+set (DEBUG_USART USART1 CACHE STRING "USART to use for debug output")
+set (DEBUG_USART_BAUDRATE 1000000 CACHE STRING "Baud rate to use for debug USART")
+set (DEBUG_USART_DMA_NUM 2 CACHE STRING "DMA controller number to use for debug usart")
+set (DEBUG_USART_DMA_STREAM_NUM 7 CACHE STRING "DMA stream number to use for debug usart. This must be the stream mapped to the [DEBUG_USART]_TX channel")
+set (DEBUG_USART_DMA_CHANNEL_NUM 4 CACHE STRING "DMA channel number to use for debug usart. This must be the channel mapped to the [DEBUG_USART]_TX channel")
+
+# Set compiler and linker flags
+
+set (FP_FLAGS
+ "-mfloat-abi=hard -mfpu=fpv4-sp-d16 -mfp16-format=alternative"
+)
+
+set (ARCH_FLAGS
+ "-mthumb -mcpu=cortex-m4 ${FP_FLAGS}"
+)
+set (COMMON_FLAGS
+ "-O2 -g -Wextra -Wshadow -Wredundant-decls -fno-common -ffunction-sections -fdata-sections --specs=nosys.specs"
+)
+
+set (CMAKE_C_FLAGS
+ "${COMMON_FLAGS} ${ARCH_FLAGS} -Wstrict-prototypes -Wmissing-prototypes -Wimplicit-function-declaration"
+)
+
+set (CMAKE_CXX_FLAGS
+ "${COMMON_FLAGS} ${ARCH_FLAGS} -Weffc++"
+)
+
+# C preprocessor flags
+set (CPP_FLAGS
+ " -MD -Wall -Wundef"
+)
+
+add_definitions (${CPP_FLAGS})
+
+# set platform
+add_definitions (-DSTM32F4)
+
+set (CMAKE_EXE_LINKER_FLAGS
+ "--static -nostartfiles -T${CMAKE_SOURCE_DIR}/libusbhost_stm32f4.ld -Wl,-Map=FIXME_ONE.map -Wl,--gc-sections -Wl,--start-group -lc -lgcc -lnosys -Wl,--end-group"
+)
+
+include_directories (
+ ${CMAKE_SOURCE_DIR}/include
+ src/crypto/noise-c/include
+)
+
+function (init_libopencm3)
+ include_directories (${CMAKE_SOURCE_DIR}/libopencm3/include)
+ link_directories (${CMAKE_SOURCE_DIR}/libopencm3/lib)
+ set (LIBOPENCM3_LIB opencm3_stm32f4 PARENT_SCOPE)
+ execute_process (
+ COMMAND sh "${CMAKE_SOURCE_DIR}/initRepo.sh"
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+ OUTPUT_QUIET
+ )
+endfunction (init_libopencm3)
+
+message (STATUS "Initializing repository")
+init_libopencm3 ()
+message (STATUS "Repository initialized")
+
+# Process cached varibles
+message (STATUS "Setuping build")
+if (USE_STM32F4_FS)
+ message (STATUS "... Using USB full speed (FS) host periphery")
+ add_definitions (-DUSE_STM32F4_USBH_DRIVER_FS)
+endif (USE_STM32F4_FS)
+
+if (USE_STM32F4_HS)
+ message (STATUS "... Using USB high speed (HS) host periphery")
+ add_definitions (-DUSE_STM32F4_USBH_DRIVER_HS)
+endif (USE_STM32F4_HS)
+
+if (USE_USART_DEBUG)
+ message (STATUS "... Using debug uart output")
+ add_definitions (-DUSART_DEBUG)
+endif (USE_USART_DEBUG)
+message (STATUS "Setup done")
+
+add_definitions (-DDEBUG_USART=${DEBUG_USART})
+add_definitions (-DDEBUG_USART_BAUDRATE=${DEBUG_USART_BAUDRATE})
+add_definitions (-DDEBUG_USART_DMA_NUM=${DEBUG_USART_DMA_NUM})
+add_definitions (-DDEBUG_USART_DMA_STREAM_NUM=${DEBUG_USART_DMA_STREAM_NUM})
+add_definitions (-DDEBUG_USART_DMA_CHANNEL_NUM=${DEBUG_USART_DMA_CHANNEL_NUM})
+
+add_custom_target (README.md
+ SOURCES README.md
+)
+
+add_subdirectory (src)
diff --git a/fw/COPYING.GPL3 b/fw/COPYING.GPL3
new file mode 100644
index 0000000..94a9ed0
--- /dev/null
+++ b/fw/COPYING.GPL3
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/fw/COPYING.LGPL3 b/fw/COPYING.LGPL3
new file mode 100644
index 0000000..65c5ca8
--- /dev/null
+++ b/fw/COPYING.LGPL3
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/fw/Doxyfile b/fw/Doxyfile
new file mode 100644
index 0000000..99f8f1f
--- /dev/null
+++ b/fw/Doxyfile
@@ -0,0 +1,2406 @@
+# Doxyfile 1.8.11
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all text
+# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
+# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv
+# for the list of possible encodings.
+# The default value is: UTF-8.
+
+DOXYFILE_ENCODING = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME = "libusbhost"
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER =
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF =
+
+# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
+# in the documentation. The maximum height of the logo should not exceed 55
+# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
+# the logo to the output directory.
+
+PROJECT_LOGO =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
+OUTPUT_DIRECTORY = doc
+
+# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
+# directories (in 2 levels) under the output directory of each output format and
+# will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system.
+# The default value is: NO.
+
+CREATE_SUBDIRS = NO
+
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
+ALLOW_UNICODE_NAMES = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
+# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
+# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
+# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
+# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
+# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
+# Ukrainian and Vietnamese.
+# The default value is: English.
+
+OUTPUT_LANGUAGE = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+# The default value is: YES.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
+
+ABBREVIATE_BRIEF =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# doxygen will generate a detailed section even if there is only a brief
+# description.
+# The default value is: NO.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+# The default value is: NO.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
+
+FULL_PATH_NAMES = YES
+
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+
+STRIP_FROM_PATH =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
+
+STRIP_FROM_INC_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
+
+JAVADOC_AUTOBRIEF = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
+
+QT_AUTOBRIEF = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
+
+INHERIT_DOCS = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
+# page for each member. If set to NO, the documentation of a member will be part
+# of the file/class/namespace that contains it.
+# The default value is: NO.
+
+SEPARATE_MEMBER_PAGES = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
+
+TAB_SIZE = 4
+
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:\n"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". You can put \n's in the value part of an alias to insert
+# newlines.
+
+ALIASES =
+
+# This tag can be used to specify a number of word-keyword mappings (TCL only).
+# A mapping has the form "name=value". For example adding "class=itcl::class"
+# will allow you to use the command class in the itcl::class meaning.
+
+TCL_SUBST =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_FOR_C = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
+
+OPTIMIZE_FOR_FORTRAN = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_VHDL = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
+# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:
+# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:
+# Fortran. In the later case the parser tries to guess whether the code is fixed
+# or free formatted code, this is the default for Fortran type files), VHDL. For
+# instance to make doxygen treat .inc files as Fortran files (default is PHP),
+# and .f files as C (default is Fortran), use: inc=Fortran f=C.
+#
+# Note: For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen.
+
+EXTENSION_MAPPING =
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See http://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+MARKDOWN_SUPPORT = YES
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by putting a % sign in front of the word or
+# globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+AUTOLINK_SUPPORT = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
+
+BUILTIN_STL_SUPPORT = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+# The default value is: NO.
+
+CPP_CLI_SUPPORT = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
+
+SIP_SUPPORT = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
+
+IDL_PROPERTY_SUPPORT = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+# The default value is: NO.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# If one adds a struct or class to a group and this option is enabled, then also
+# any nested class or struct is added to the same group. By default this option
+# is disabled and one has to add nested compounds explicitly via \ingroup.
+# The default value is: NO.
+
+GROUP_NESTED_COMPOUNDS = NO
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
+
+SUBGROUPING = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS = NO
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
+
+TYPEDEF_HIDES_STRUCT = NO
+
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
+
+EXTRACT_ALL = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIVATE = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PACKAGE = NO
+
+# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
+
+EXTRACT_STATIC = YES
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO,
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
+
+EXTRACT_LOCAL_CLASSES = YES
+
+# This flag is only useful for Objective-C code. If set to YES, local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO, only methods in the interface are
+# included.
+# The default value is: NO.
+
+EXTRACT_LOCAL_METHODS = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
+
+EXTRACT_ANON_NSPACES = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_MEMBERS = YES
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO, these classes will be included in the various overviews. This option
+# has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_CLASSES = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# (class|struct|union) declarations. If set to NO, these declarations will be
+# included in the documentation.
+# The default value is: NO.
+
+HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO, these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
+
+INTERNAL_DOCS = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
+# names in lower-case letters. If set to YES, upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+# The default value is: system dependent.
+
+CASE_SENSE_NAMES = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES, the
+# scope will be hidden.
+# The default value is: NO.
+
+HIDE_SCOPE_NAMES = NO
+
+# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
+# append additional text to a page's title, such as Class Reference. If set to
+# YES the compound reference will be hidden.
+# The default value is: NO.
+
+HIDE_COMPOUND_REFERENCE= NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
+
+SHOW_INCLUDE_FILES = YES
+
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
+SHOW_GROUPED_MEMB_INC = NO
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
+
+FORCE_LOCAL_INCLUDES = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order.
+# The default value is: YES.
+
+SORT_MEMBER_DOCS = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
+
+SORT_BRIEF_DOCS = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
+
+SORT_GROUP_NAMES = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
+
+SORT_BY_SCOPE_NAME = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
+# list. This list is created by putting \todo commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
+# list. This list is created by putting \test commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
+
+GENERATE_BUGLIST = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES, the
+# list will mention the files that were used to generate the documentation.
+# The default value is: YES.
+
+SHOW_USED_FILES = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
+
+SHOW_FILES = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
+
+SHOW_NAMESPACES = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
+
+FILE_VERSION_FILTER =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
+
+LAYOUT_FILE =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. See also \cite for info how to create references.
+
+CITE_BIB_FILES =
+
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
+
+QUIET = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
+
+WARNINGS = YES
+
+# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
+
+WARN_IF_UNDOCUMENTED = YES
+
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some parameters
+# in a documented function, or documenting parameters that don't exist or using
+# markup commands wrongly.
+# The default value is: YES.
+
+WARN_IF_DOC_ERROR = YES
+
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO, doxygen will only warn about wrong or incomplete
+# parameter documentation, but not about the absence of documentation.
+# The default value is: NO.
+
+WARN_NO_PARAMDOC = NO
+
+# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
+# a warning is encountered.
+# The default value is: NO.
+
+WARN_AS_ERROR = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# The default value is: $file:$line: $text.
+
+WARN_FORMAT = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr).
+
+WARN_LOGFILE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
+# Note: If this tag is empty the current directory is searched.
+
+INPUT = include include/driver
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see: http://www.gnu.org/software/libiconv) for the list of
+# possible encodings.
+# The default value is: UTF-8.
+
+INPUT_ENCODING = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# read by doxygen.
+#
+# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
+# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
+# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
+# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f, *.for, *.tcl,
+# *.vhd, *.vhdl, *.ucf, *.qsf, *.as and *.js.
+
+FILE_PATTERNS = *.c *.h
+
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
+
+RECURSIVE = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE =
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+# The default value is: NO.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
+
+EXCLUDE_PATTERNS =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories use the pattern */test/*
+
+EXCLUDE_SYMBOLS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
+
+EXAMPLE_PATH =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
+
+EXAMPLE_PATTERNS =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
+
+IMAGE_PATH =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
+
+FILTER_SOURCE_FILES = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
+
+SOURCE_BROWSER = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
+
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
+
+STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# function all documented functions referencing it will be listed.
+# The default value is: NO.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
+
+REFERENCES_RELATION = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see http://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+USE_HTAGS = NO
+
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
+
+VERBATIM_HEADERS = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
+
+ALPHABETICAL_INDEX = YES
+
+# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
+# which the alphabetical index list will be split.
+# Minimum value: 1, maximum value: 20, default value: 5.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+COLS_IN_ALPHA_INDEX = 5
+
+# In case all classes in a project start with a common prefix, all classes will
+# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
+# can be used to specify a prefix (or a list of prefixes) that should be ignored
+# while generating the index headers.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+IGNORE_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
+# The default value is: YES.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_STYLESHEET =
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefore more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list). For an example see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the style sheet and background images according to
+# this color. Hue is specified as an angle on a colorwheel, see
+# http://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_HUE = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use grayscales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_SAT = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_GAMMA = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting this
+# to YES can help to show when doxygen was last run and thus if the
+# documentation is up to date.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_TIMESTAMP = NO
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_SECTIONS = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see: http://developer.apple.com/tools/xcode/), introduced with
+# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a
+# Makefile in the HTML output directory. Running make will produce the docset in
+# that directory and running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_DOCSET = NO
+
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDNAME = "Doxygen generated docs"
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_BUNDLE_ID = org.doxygen.Project
+
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_ID = org.doxygen.Publisher
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_NAME = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
+# Windows.
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_HTMLHELP = NO
+
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
+# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_FILE =
+
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler (hhc.exe). If non-empty,
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+HHC_LOCATION =
+
+# The GENERATE_CHI flag controls if a separate .chi index file is generated
+# (YES) or that it should be included in the master .chm file (NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+GENERATE_CHI = NO
+
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_INDEX_ENCODING =
+
+# The BINARY_TOC flag controls whether a binary table of contents is generated
+# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
+# enables the Previous and Next buttons.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+TOC_EXPAND = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_QHP = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QCH_FILE =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_NAMESPACE = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-
+# folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_VIRTUAL_FOLDER = doc
+
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_NAME =
+
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_SECT_FILTER_ATTRS =
+
+# The QHG_LOCATION tag can be used to specify the location of Qt's
+# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
+# generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_ECLIPSEHELP = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
+ECLIPSE_DOC_ID = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+DISABLE_INDEX = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine-tune the look of the index. As an example, the default style
+# sheet generated by doxygen has an example that shows how to put an image at
+# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
+# the same information as the tab index, you could consider setting
+# DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_TREEVIEW = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+ENUM_VALUES_PER_LINE = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+TREEVIEW_WIDTH = 250
+
+# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+EXT_LINKS_IN_WINDOW = NO
+
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_FONTSIZE = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are not
+# supported properly for IE 6.0, but are supported on all modern browsers.
+#
+# Note that when changing this option you need to delete any form_*.png files in
+# the HTML output directory before the changes have effect.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_TRANSPARENT = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# http://www.mathjax.org) which uses client side Javascript for the rendering
+# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+USE_MATHJAX = NO
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. See the MathJax site (see:
+# http://docs.mathjax.org/en/latest/output.html) for more details.
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility), NativeMML (i.e. MathML) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from http://www.mathjax.org before deployment.
+# The default value is: http://cdn.mathjax.org/mathjax/latest.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS =
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SEARCHENGINE = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using Javascript. There
+# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
+# setting. When disabled, doxygen will generate a PHP script for searching and
+# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SERVER_BASED_SEARCH = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/). See the section "External Indexing and
+# Searching" for details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHDATA_FILE = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
+# The default value is: YES.
+
+GENERATE_LATEX = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when enabling USE_PDFLATEX this option is only used for generating
+# bitmaps for formulas in the HTML output, but not in the Makefile that is
+# written to the output directory.
+# The default file is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PAPER_TYPE = a4
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. The package can be specified just
+# by its name or with the correct syntax as to be used with the LaTeX
+# \usepackage command. To get the times font for instance you can specify :
+# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
+# To use the option intlimits with the amsmath package you can specify:
+# EXTRA_PACKAGES=[intlimits]{amsmath}
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
+# generated LaTeX document. The header should contain everything until the first
+# chapter. If it is left blank doxygen will generate a standard header. See
+# section "Doxygen usage" for information on how to let doxygen write the
+# default header to a separate file.
+#
+# Note: Only use a user-defined header if you know what you are doing! The
+# following commands have a special meaning inside the header: $title,
+# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
+# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
+# string, for the replacement values of the other commands the user is referred
+# to HTML_HEADER.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HEADER =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
+# generated LaTeX document. The footer should contain everything after the last
+# chapter. If it is left blank doxygen will generate a standard footer. See
+# LATEX_HEADER for more information on how to generate a default footer and what
+# special commands can be used inside the footer.
+#
+# Note: Only use a user-defined footer if you know what you are doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_FOOTER =
+
+# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# LaTeX style sheets that are included after the standard style sheets created
+# by doxygen. Using this option one can overrule certain style aspects. Doxygen
+# will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list).
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_STYLESHEET =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PDF_HYPERLINKS = YES
+
+# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
+# the PDF file directly from the LaTeX files. Set this option to YES, to get a
+# higher quality PDF documentation.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+USE_PDFLATEX = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
+# command to the generated LaTeX files. This will instruct LaTeX to keep running
+# if errors occur, instead of asking the user for help. This option is also used
+# when generating formulas in HTML.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BATCHMODE = NO
+
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HIDE_INDICES = NO
+
+# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
+# code with syntax highlighting in the LaTeX output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_SOURCE_CODE = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# http://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BIB_STYLE = plain
+
+# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_TIMESTAMP = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's config
+# file, i.e. a series of assignments. You only have to provide replacements,
+# missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's config file. A template extensions file can be generated
+# using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_EXTENSIONS_FILE =
+
+# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code
+# with syntax highlighting in the RTF output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_SOURCE_CODE = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
+
+GENERATE_MAN = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_EXTENSION = .3
+
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_SUBDIR =
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_OUTPUT = xml
+
+# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_PROGRAMLISTING = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+GENERATE_DOCBOOK = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT = docbook
+
+# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the
+# program listings (including syntax highlighting and cross-referencing
+# information) to the DOCBOOK output. Note that enabling this will significantly
+# increase the size of the DOCBOOK output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_PROGRAMLISTING = NO
+
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
+# AutoGen Definitions (see http://autogen.sf.net) file that captures the
+# structure of the code including all documentation. Note that this feature is
+# still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO, the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
+# in the source code. If set to NO, only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+MACRO_EXPANSION = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_ONLY_PREDEF = NO
+
+# If the SEARCH_INCLUDES tag is set to YES, the include files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
+
+INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+PREDEFINED =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all references to function-like macros that are alone on a line, have
+# an all uppercase name, and do not end with a semicolon. Such function macros
+# are typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have a unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
+
+TAGFILES =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
+GENERATE_TAGFILE =
+
+# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
+# the class index. If set to NO, only the inherited external classes will be
+# listed.
+# The default value is: NO.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
+
+EXTERNAL_GROUPS = YES
+
+# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+EXTERNAL_PAGES = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of 'which perl').
+# The default file (with absolute path) is: /usr/bin/perl.
+
+PERL_PATH = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
+# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
+# NO turns the diagrams off. Note that this option also works with HAVE_DOT
+# disabled, but it is recommended to install and use dot, since it yields more
+# powerful graphs.
+# The default value is: YES.
+
+CLASS_DIAGRAMS = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see:
+# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH =
+
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+# If left empty dia is assumed to be found in the default search path.
+
+DIA_PATH =
+
+# If set to YES the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz (see:
+# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: NO.
+
+HAVE_DOT = YES
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NUM_THREADS = 0
+
+# When you want a differently looking font in the dot files that doxygen
+# generates you can specify the font name using DOT_FONTNAME. You need to make
+# sure dot is able to find the font, which can be done by putting it in a
+# standard location or by setting the DOTFONTPATH environment variable or by
+# setting DOT_FONTPATH to the directory containing the font.
+# The default value is: Helvetica.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTNAME = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
+# dot graphs.
+# Minimum value: 4, maximum value: 24, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTSIZE = 10
+
+# By default doxygen will tell dot to use the default font as specified with
+# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
+# the path where dot can find it using this tag.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTPATH =
+
+# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
+# each documented class showing the direct and indirect inheritance relations.
+# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CLASS_GRAPH = YES
+
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+COLLABORATION_GRAPH = YES
+
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GROUP_GRAPHS = YES
+
+# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LOOK = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LIMIT_NUM_FIELDS = 10
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+TEMPLATE_RELATIONS = NO
+
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDE_GRAPH = YES
+
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command. Disabling a call graph can be
+# accomplished by means of the command \hidecallgraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALL_GRAPH = NO
+
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command. Disabling a caller graph can be
+# accomplished by means of the command \hidecallergraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALLER_GRAPH = NO
+
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GRAPHICAL_HIERARCHY = YES
+
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DIRECTORY_GRAPH = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. For an explanation of the image formats see the section
+# output formats in the documentation of the dot tool (Graphviz (see:
+# http://www.graphviz.org/)).
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,
+# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
+# png:gdiplus:gdiplus.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_IMAGE_FORMAT = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INTERACTIVE_SVG = NO
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOTFILE_DIRS =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS =
+
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+DIAFILE_DIRS =
+
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file. If left blank, it is assumed
+# PlantUML is not used or called during a preprocessing step. Doxygen will
+# generate a warning when it encounters a \startuml command in this case and
+# will not generate output for the diagram.
+
+PLANTUML_JAR_PATH =
+
+# When using plantuml, the specified paths are searched for files specified by
+# the !include statement in a plantuml block.
+
+PLANTUML_INCLUDE_PATH =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_GRAPH_MAX_NODES = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+MAX_DOT_GRAPH_DEPTH = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not seem
+# to support this out of the box.
+#
+# Warning: Depending on the platform used, enabling this option may lead to
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
+# read).
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_TRANSPARENT = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_MULTI_TARGETS = NO
+
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot
+# files that are used to generate the various graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_CLEANUP = YES
diff --git a/fw/README.md b/fw/README.md
new file mode 100644
index 0000000..4608e7e
--- /dev/null
+++ b/fw/README.md
@@ -0,0 +1,105 @@
+[![Build Status](https://travis-ci.org/libusbhost/libusbhost.svg?branch=master)](https://travis-ci.org/libusbhost/libusbhost)
+##General Information
+
+[Link to the official repository](http://github.com/libusbhost/libusbhost)
+
+###Objectives
+
+- provide open-source(Lesser GPL3) usb host library for embedded devices
+- execution speed. This library doesn't use blocking sleep,
+making low overhead on runtime performance
+- use static allocation for all of its buffers.
+This means no allocation and reallocation is affecting performance
+(possibility of memory fragmentation. execution time indeterminism). No malloc(), realloc(), free()
+- do not depend on any operating system
+
+### Supported hardware
+
+- stm32f4discovery
+
+### Supported device drivers
+
+- HUB
+- Gamepad - XBox compatible Controller
+- Generic Human Interface driver: mouse, keyboard (raw data)
+- USB MIDI devices (raw data + note on/off)
+
+## Steps to compile library and demo
+### Prerequisities
+Make sure the following prerequisities are installed to be able to compile this library
+- **git** for libopencm3 submodule fetch
+- **gcc-arm-none-eabi** toolchain for cross compilation
+- **cmake**
+- **ccmake** (optional)
+- **openocd** (optional)
+
+### Basic setup
+1. go to build directory located in the root of the project
+> cd build
+
+2. compile demo and the library with the default options set
+> cmake .. && make
+
+Executable demo is placed into `build/demo.hex`.
+Library is placed into `build/src/libusbhost.a`.
+
+### Advanced setup
+*cmake* initial cache variables
+<table>
+<tr>
+ <th>Cache variable</th><th>Value</th><th>Description</th>
+</tr>
+<tr>
+ <td>USE_STM32F4_FS</td><td>TRUE</td><td>Enable STM32F4 Full Speed USB host peripheral</td>
+</tr>
+<tr>
+ <td>USE_STM32F4_HS</td><td>TRUE</td><td>Enable STM32F4 High Speed USB host peripheral</td>
+</tr>
+<tr>
+ <td>USE_USART_DEBUG</td><td>TRUE</td><td>Enable writing of the debug information to USART6</td>
+</tr>
+<tr>
+ <td>OOCD_INTERFACE</td><td>"stlink-v2"</td><td>Interface configuration file used by the openocd</td>
+</tr>
+<tr>
+ <td>OOCD_BOARD</td><td>"stm32f4discovery"</td><td>Board configuration file used by the openocd</td>
+</tr>
+</table>
+You can alter these by issuing the following commands in the build directory
+
+- Graphical user interface
+> ccmake ..
+
+- Command line interface
+> cmake .. -D{VARIABLE}={VALUE}
+
+### Flashing
+If the *openocd* is installed, `make flash` executed in the build directory
+flashes the `build/demo.hex` to the stm32f4discovery board.
+
+### Reading debug output
+The following table represents the configuration of the debug output
+<table>
+<tr>
+ <th>GPIO</th><td>GPIOC6</td>
+</tr>
+<tr>
+ <th>USART periphery</th><td>USART6</td>
+</tr>
+<tr>
+ <th>Function</th><td>UART TX</td>
+</tr>
+<tr>
+ <th>Baud rate</th><td>921600</td>
+</tr>
+<tr>
+ <th>Uart mode</th><td>8N1</td>
+</tr>
+</table>
+
+## License
+
+The libusbhost code is released under the terms of the GNU Lesser General
+Public License (LGPL), version 3 or later.
+
+See COPYING.GPL3 and COPYING.LGPL3 for details.
diff --git a/fw/USB_icon.svg b/fw/USB_icon.svg
new file mode 100644
index 0000000..aeaefff
--- /dev/null
+++ b/fw/USB_icon.svg
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.0"
+ width="24"
+ height="24"
+ viewBox="0 0 24.000001 24"
+ id="Layer_1"
+ xml:space="preserve"
+ sodipodi:docname="USB_icon.svg"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)"
+ inkscape:export-filename="/home/user/ref/libusbhost/secureusb_icon.png"
+ inkscape:export-xdpi="1280"
+ inkscape:export-ydpi="1280"><metadata
+ id="metadata7"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="1"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1030"
+ id="namedview5"
+ showgrid="false"
+ units="px"
+ inkscape:zoom="22.627417"
+ inkscape:cx="6.8983878"
+ inkscape:cy="13.626763"
+ inkscape:window-x="0"
+ inkscape:window-y="50"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="Layer_1" /><defs
+ id="defs1337" />
+<path
+ d="M 9.1909916,1.0365848 6.7543191,5.2559831 H 8.4925226 V 15.009993 L 4.0559868,10.810749 C 3.7695331,10.453379 3.568585,9.9857932 3.5574545,9.5048004 c 0,-1.9458109 -5.065e-4,-1.7888256 -0.00136,-2.2141627 0.8214146,-0.2883098 1.414308,-1.0627543 1.414308,-1.983421 0,-1.1647037 -0.9451206,-2.1099103 -2.1102466,-2.1099103 -1.1656325,0 -2.11041486,0.9451205 -2.11041486,2.1099103 0,0.9206667 0.59255516,1.6951112 1.41329516,1.983421 l -5.905e-4,2.1728424 c 0,0.9446159 0.5182659,1.9344279 1.1258318,2.5644249 -0.018039,-0.01719 -0.037271,-0.03508 3.365e-4,10e-4 0.014994,0.01332 4.7065449,4.455256 4.7065449,4.455256 0.2860334,0.356613 0.4857161,0.823945 0.4971849,1.304601 v 8.752206 c -1.6117146,0.323388 -2.8260028,1.746635 -2.8260028,3.45389 0,1.946487 1.5778153,3.524302 3.5237111,3.524302 1.9464859,0 3.5243859,-1.577815 3.5243859,-3.524302 0,-1.707592 -1.2153,-3.130838 -2.8283611,-3.454226 v -8.709455 c 0,-0.0061 3.365e-4,-0.01224 0,-0.01856 v -5.301631 c 0.012235,-0.479729 0.2121621,-0.94647 0.4985321,-1.302745 0,0 4.691197,-4.4409224 4.706376,-4.4546676 0.03771,-0.035583 0.01805,-0.017872 3.38e-4,-3.365e-4 0.60748,-0.6299968 1.125408,-1.6202312 1.125408,-2.565013 L 16.215975,3.454345 h 1.414729 V -0.76611257 H 13.410639 V 3.4543809 h 1.412874 c 0,0 -0.0015,-1.7403408 -0.0015,0.7750052 -0.01102,0.4810766 -0.211742,0.9491696 -0.498196,1.306372 L 9.8863699,9.7359362 V 5.2559831 h 1.7409841 z"
+ id="path1334"
+ inkscape:connector-curvature="0"
+ style="stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none"
+ inkscape:transform-center-x="3.8484292"
+ inkscape:transform-center-y="16.5482"
+ sodipodi:nodetypes="cccccccssscccccccssscscccccccccccccccccc" />
+<g
+ id="g4661"
+ transform="matrix(0.95637878,0,0,0.95637878,49.375726,8.0349764)"
+ inkscape:export-xdpi="1280"
+ inkscape:export-ydpi="1280"><rect
+ ry="1.6849748"
+ y="6.75"
+ x="-39.125"
+ height="9.8125"
+ width="12.375"
+ id="rect4608"
+ style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.8904658;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /><rect
+ ry="3.9323201"
+ y="0.68743151"
+ x="-36.86982"
+ height="9.4375687"
+ width="7.8646402"
+ id="rect4610"
+ style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#000000;stroke-width:2.69644809;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /><g
+ transform="matrix(0.93546514,0,0,0.93546514,-1.0854549,1.4271834)"
+ id="g4654"><circle
+ style="opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.72307682;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="path4627"
+ cx="-34.051041"
+ cy="8.8928328"
+ r="1.84375" /><path
+ style="opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="m -34.754167,8.8303324 h 1.40625 l 1.140625,4.8258756 c 0.125,0.616281 -0.508719,1.140625 -1.140625,1.140625 h -1.40625 c -0.631906,0 -1.25,-0.508719 -1.140625,-1.140625 z"
+ id="rect4629"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccsscc" /></g></g><path
+ style="opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="M -21.203125 -21.580078 L -21.203125 45.580078 L 45.203125 45.580078 L 45.203125 -21.580078 L -21.203125 -21.580078 z M 0 0 L 24 0 L 24 24 L 0 24 L 0 0 z "
+ id="rect816" /></svg> \ No newline at end of file
diff --git a/fw/adjectives.py b/fw/adjectives.py
new file mode 100644
index 0000000..6d7deb0
--- /dev/null
+++ b/fw/adjectives.py
@@ -0,0 +1,34 @@
+ADJECTIVES = '''
+ wrathful worthy weird warm volatile veiled vacuous useless
+ upset unsoiled unsightly unpronounceable unfriendly unfree unfit unfaithful
+ unchaste unbroken unbound unblessed unbefitting unaltered unabused unable
+ ugly tongued thorny thirsty thick terminal ten-sided teeming
+ tangerine taken substantial stupefying stringy strange stillborn sticky
+ stagnant spongy sour soul-destroying smoldering smitten slain six-sided
+ shifting shadowy severed seven-sided serene salty rust-red royal
+ rotten riddled resentful regrettable reeking rare rank rancid
+ quiescent putrid putrid putrescent prehistoric predatory predaceous porous
+ poisonous pierced phlegmatic petrifying pessimal pathetic odorless oddish
+ obsessed obscene numb nine-sided nasty mysterious mute musky
+ morose moribund moldy miasmic material many-lobed malodorous malign
+ maimed luminescent low-cut lousy live limp lifeless leering
+ leaky layered latent lackluster jagged irregular iridescent intangible
+ infinite inept incomprehensible in-between improper idle hunted hideous
+ heavy hairy guilty grotesque grey greedy gory gorgeous
+ gooey golden-brown golden ghastly frostbitten fresh-cut freakish frantic
+ fossilized formless formidable floccose five-lobed firstborn filthy fickle
+ fetid fertile fearful fatal familiar fallen fallacious faint
+ faceless extinct esoteric errant emergent elastic eight-sided eerie
+ ebon dysphoric dying dumb dull-purple dull dull dull
+ dormant doomed disfigured dirty defenseless deep-pink deep deconsecrated
+ deathlike deadly dead dark-blue dark curly curious cured
+ cunning crystalline cryptic crying crumbly crimson crested creepy
+ crazy corrupt corporeal contemptible contained concrete cloudy chopped
+ chained caustic catholic cathartic captive cancerous cabalistic burnt
+ buoyant bronze-red bronze broken bright-red breathless bound bound
+ bottomless bony bodiless blue-lilac blue bloody bloodthirsty bloodsucking
+ bloodstained bloodcurdling blonde blistered blank bitter bilgy bewitched
+ befouled beardless bastardly barbed baleful balding awkward awful
+ atrocious arcane appalling antic anonymous angry ample ambiguous
+ amber-green amber aghast activated acidic abused abstruse abject
+ '''.split()
diff --git a/fw/build/.gitignore b/fw/build/.gitignore
new file mode 100644
index 0000000..72e8ffc
--- /dev/null
+++ b/fw/build/.gitignore
@@ -0,0 +1 @@
+*
diff --git a/fw/cmake/doc.cmake b/fw/cmake/doc.cmake
new file mode 100644
index 0000000..76336a8
--- /dev/null
+++ b/fw/cmake/doc.cmake
@@ -0,0 +1,6 @@
+add_custom_target (doc
+ COMMAND doxygen
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+ COMMENT "output is generted in the ${CMAKE_SOURCE_DIR}/doc"
+ SOURCES Doxyfile
+)
diff --git a/fw/cmake/openocd_flash.cmake b/fw/cmake/openocd_flash.cmake
new file mode 100644
index 0000000..5c92c49
--- /dev/null
+++ b/fw/cmake/openocd_flash.cmake
@@ -0,0 +1,18 @@
+find_program (OOCD openocd DOC "openocd executable")
+
+set (OOCD_INTERFACE stlink-v2 CACHE STRING "interface config file used for openocd flashing")
+set (OOCD_BOARD stm32f4discovery CACHE STRING "board config file used for openocd flashing")
+if (OOCD)
+ message (STATUS "OpenOCD found: ${OOCD}")
+ message (STATUS "... interface: ${OOCD_INTERFACE}")
+ message (STATUS "... board: ${OOCD_BOARD}")
+ add_custom_target (flash
+ COMMAND sh -c '${OOCD} -f interface/${OOCD_INTERFACE}.cfg
+ -f board/${OOCD_BOARD}.cfg
+ -c "init" -c "reset init"
+ -c "flash write_image erase $<TARGET_FILE:demo>"
+ -c "reset"
+ -c "shutdown" '
+ DEPENDS demo
+ )
+endif (OOCD)
diff --git a/fw/cmake/toolchain.cmake b/fw/cmake/toolchain.cmake
new file mode 100644
index 0000000..8522d7a
--- /dev/null
+++ b/fw/cmake/toolchain.cmake
@@ -0,0 +1,15 @@
+set (_CMAKE_TOOLCHAIN_PREFIX "arm-none-eabi-" CACHE STRING "toolchain prefix")
+set (_CMAKE_TOOLCHAIN_LOCATION "" CACHE STRING "toolchain location hint")
+
+set (CMAKE_SYSTEM_NAME Generic)
+set (CMAKE_C_COMPILER_WORKS 1)
+set (CMAKE_CXX_COMPILER_WORKS 1)
+set (CMAKE_C_FLAGS "")
+set (CMAKE_CXX_FLAGS "")
+set (BUILD_SHARED_LIBS OFF)
+find_program (CMAKE_C_COMPILER NAMES ${_CMAKE_TOOLCHAIN_PREFIX}gcc HINTS ${_CMAKE_TOOLCHAIN_LOCATION})
+find_program (CMAKE_CXX_COMPILER NAMES ${_CMAKE_TOOLCHAIN_PREFIX}g++ HINTS ${_CMAKE_TOOLCHAIN_LOCATION})
+find_program (CMAKE_OBJCOPY NAMES ${_CMAKE_TOOLCHAIN_PREFIX}objcopy HINTS ${_CMAKE_TOOLCHAIN_LOCATION})
+find_program (CMAKE_SIZE NAMES ${_CMAKE_TOOLCHAIN_PREFIX}size HINTS ${_CMAKE_TOOLCHAIN_LOCATION})
+
+
diff --git a/fw/hexcom.py b/fw/hexcom.py
new file mode 100755
index 0000000..b575cc9
--- /dev/null
+++ b/fw/hexcom.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python3
+
+import time
+import string
+
+def _print_line(write, ts, line, width=16):
+ h,m,s,ms = int(ts//3600), int((ts//60)%60), int(ts%60), int((ts%1.0) * 1000)
+ timestamp = f'{h: 3d}:{m:02d}:{s:02d}:{ms:03d}'
+ line = list(line) + [None]*(width-len(line))
+ hexcol = '\033[94m'
+ col = lambda b, s: s if b != 0 else f'\033[91m{s}{hexcol}'
+ hexfmt = ' '.join(
+ ' '.join(col(b, f'{b:02x}') if b is not None else ' ' for b in line[i*8:i*8+8])
+ for i in range(1 + (len(line)-1)//8))
+ asciifmt = ''.join(chr(c) if c is not None and chr(c) in string.printable else '.' for c in line)
+ write(f'\033[38;5;244m{timestamp} {hexcol}{hexfmt} \033[38;5;244m|\033[92m{asciifmt}\033[38;5;244m|\033[0m', flush=True, end='')
+
+def hexcom(write, ser, width=16, split=False):
+ current_line = b''
+ start = time.time()
+ while ser.is_open:
+ data = ser.read_all() # non-blocking, flushes buffer
+ if not data:
+ data = ser.read(1) # blocking
+ ts = time.time()
+
+ write('\033[2K\r', end='')
+ current_line += data
+ foo = current_line.split(b'\0') if split else [current_line]
+ for i, packet in enumerate(foo):
+ if len(foo) > 1 and i < len(foo)-1:
+ packet += b'\0'
+ while len(packet) > width:
+ chunk, packet = packet[:width], packet[width:]
+ _print_line(write, ts-start, chunk, width=width)
+ write()
+ _print_line(write, ts-start, packet, width=width)
+ if i < len(foo)-1:
+ write()
+ current_line = packet
+
+if __name__ == '__main__':
+ import argparse
+ import serial
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument('serial')
+ parser.add_argument('baudrate')
+ parser.add_argument('-w', '--width', type=int, default=16, help='Number of bytes to display in one line')
+ parser.add_argument('-s', '--split', action='store_true', help='Split output on null bytes')
+ args = parser.parse_args()
+
+ ser = serial.Serial(args.serial, args.baudrate)
+ hexcom(print, ser, width=args.width, split=args.split)
diff --git a/fw/hexdump.py b/fw/hexdump.py
new file mode 100644
index 0000000..943d8e4
--- /dev/null
+++ b/fw/hexdump.py
@@ -0,0 +1,27 @@
+
+import string
+import time
+
+startup = time.time()
+
+def _print_line(write, ts, line, width=16):
+ h,m,s,ms = int(ts//3600), int((ts//60)%60), int(ts%60), int((ts%1.0) * 1000)
+ timestamp = f'{h: 3d}:{m:02d}:{s:02d}:{ms:03d}'
+ line = list(line) + [None]*(width-len(line))
+ hexcol = '\033[0m'
+ col = lambda b, s: s if b != 0 else f'\033[91m{s}{hexcol}'
+ hexfmt = ' '.join(
+ ' '.join(col(b, f'{b:02x}') if b is not None else ' ' for b in line[i*8:i*8+8])
+ for i in range(1 + (len(line)-1)//8))
+ asciifmt = ''.join(chr(c) if c is not None and chr(c) in string.printable and c>=0x20 else '.' for c in line)
+ write(f'\033[38;5;244m{timestamp} {hexcol}{hexfmt} \033[38;5;244m|\033[92m{asciifmt}\033[38;5;244m|\033[0m', flush=True, end='')
+
+def hexdump(write, packet, width=16):
+ ts = time.time()
+ while len(packet) > width:
+ chunk, packet = packet[:width], packet[width:]
+ _print_line(write, ts-startup, chunk, width=width)
+ write()
+ _print_line(write, ts-startup, packet, width=width)
+ write()
+
diff --git a/fw/hexnoise.py b/fw/hexnoise.py
new file mode 100755
index 0000000..c7acd65
--- /dev/null
+++ b/fw/hexnoise.py
@@ -0,0 +1,384 @@
+#!/usr/bin/env python3
+
+import time
+import enum
+import sys
+from contextlib import contextmanager, suppress, wraps
+import hashlib
+import secrets
+
+import serial
+from cobs import cobs
+import uinput
+from noise.connection import NoiseConnection, Keypair
+from noise.exceptions import NoiseInvalidMessage
+
+import keymap
+from hexdump import hexdump
+
+class PacketType(enum.Enum):
+ _RESERVED = 0
+ INITIATE_HANDSHAKE = 1
+ HANDSHAKE = 2
+ DATA = 3
+ COMM_ERROR = 4
+ CRYPTO_ERROR = 5
+ TOO_MANY_FAILS = 6
+
+class ReportType(enum.Enum):
+ _RESERVED = 0
+ KEYBOARD = 1
+ MOUSE = 2
+ PAIRING_INPUT = 3
+ PAIRING_SUCCESS = 4
+ PAIRING_ERROR = 5
+ PAIRING_START = 6
+
+class ProtocolError(Exception):
+ pass
+
+class Packetizer:
+ def __init__(self, serial, debug=False, width=16):
+ self.ser, self.debug, self.width = serial, debug, width
+ self.ser.write(b'\0') # COBS synchronization
+
+ def send_packet(self, pkt_type, data):
+ if self.debug:
+ print(f'\033[93mSending {len(data)} bytes, packet type {pkt_type.name} ({pkt_type.value})\033[0m')
+ hexdump(print, data, self.width)
+ data = bytes([pkt_type.value]) + data
+ encoded = cobs.encode(data) + b'\0'
+ self.ser.write(encoded)
+ self.ser.flushOutput()
+
+ def receive_packet(self):
+ packet = self.ser.read_until(b'\0')
+ data = cobs.decode(packet[:-1])
+
+ if self.debug:
+ print(f'\033[93mReceived {len(data)} bytes\033[0m')
+ hexdump(print, data, self.width)
+
+ pkt_type, data = PacketType(data[0]), data[1:]
+ if pkt_type is PacketType.COMM_ERROR:
+ raise ProtocolError('Device-side serial communication error')
+ elif pkt_type is PacketType.CRYPTO_ERROR:
+ raise ProtocolError('Device-side cryptographic error')
+ elif pkt_type is PacketType.TOO_MANY_FAILS:
+ raise ProtocolError('Device reports too many failed handshake attempts')
+ else:
+ return pkt_type, data
+
+class KeyMapper:
+ Keycode = enum.Enum('Keycode', start=0, names='''
+ KEY_NONE _RESERVED_0x01 _RESERVED_0x02 _RESERVED_0x03 KEY_A KEY_B KEY_C KEY_D
+ KEY_E KEY_F KEY_G KEY_H KEY_I KEY_J KEY_K KEY_L
+ KEY_M KEY_N KEY_O KEY_P KEY_Q KEY_R KEY_S KEY_T
+ KEY_U KEY_V KEY_W KEY_X KEY_Y KEY_Z KEY_1 KEY_2
+ KEY_3 KEY_4 KEY_5 KEY_6 KEY_7 KEY_8 KEY_9 KEY_0
+ KEY_ENTER KEY_ESC KEY_BACKSPACE KEY_TAB KEY_SPACE KEY_MINUS KEY_EQUAL KEY_LEFTBRACE
+ KEY_RIGHTBRACE KEY_BACKSLASH KEY_HASH KEY_SEMICOLON KEY_APOSTROPHE KEY_GRAVE KEY_COMMA KEY_DOT
+ KEY_SLASH KEY_CAPSLOCK KEY_F1 KEY_F2 KEY_F3 KEY_F4 KEY_F5 KEY_F6
+ KEY_F7 KEY_F8 KEY_F9 KEY_F10 KEY_F11 KEY_F12 KEY_SYSRQ KEY_SCROLLLOCK
+ KEY_PAUSE KEY_INSERT KEY_HOME KEY_PAGEUP KEY_DELETE KEY_END KEY_PAGEDOWN KEY_RIGHT
+ KEY_LEFT KEY_DOWN KEY_UP KEY_NUMLOCK KEY_KPSLASH KEY_KPASTERISK KEY_KPMINUS KEY_KPPLUS
+ KEY_KPENTER KEY_KP1 KEY_KP2 KEY_KP3 KEY_KP4 KEY_KP5 KEY_KP6 KEY_KP7
+ KEY_KP8 KEY_KP9 KEY_KP0 KEY_KPDOT KEY_102ND KEY_COMPOSE KEY_POWER KEY_KPEQUAL
+ KEY_F13 KEY_F14 KEY_F15 KEY_F16 KEY_F17 KEY_F18 KEY_F19 KEY_F20
+ KEY_F21 KEY_F22 KEY_F23 KEY_F24 KEY_OPEN KEY_HELP KEY_PROPS KEY_FRONT
+ KEY_STOP KEY_AGAIN KEY_UNDO KEY_CUT KEY_COPY KEY_PASTE KEY_FIND KEY_MUTE
+ KEY_VOLUMEUP KEY_VOLUMEDOWN _RESERVED_0x82 _RESERVED_0x83 _RESERVED_0x84 KEY_KPCOMMA _RESERVED_0x86 KEY_RO
+ KEY_KATAKANAHIRAGANA KEY_YEN KEY_HENKAN KEY_MUHENKAN KEY_KPJPCOMMA _RESERVED_0x8D _RESERVED_0x8E _RESERVED_0x8F
+ KEY_HANGEUL KEY_HANJA KEY_KATAKANA KEY_HIRAGANA KEY_ZENKAKUHANKAKU _RESERVED_0x95 _RESERVED_0x96 _RESERVED_0x97
+ _RESERVED_0x98 _RESERVED_0x99 _RESERVED_0x9A _RESERVED_0x9B _RESERVED_0x9C _RESERVED_0x9D _RESERVED_0x9E _RESERVED_0x9F
+ _RESERVED_0xA0 _RESERVED_0xA1 _RESERVED_0xA2 _RESERVED_0xA3 _RESERVED_0xA4 _RESERVED_0xA5 _RESERVED_0xA6 _RESERVED_0xA7
+ _RESERVED_0xA8 _RESERVED_0xA9 _RESERVED_0xAA _RESERVED_0xAB _RESERVED_0xAC _RESERVED_0xAD _RESERVED_0xAE _RESERVED_0xAF
+ _RESERVED_0xB0 _RESERVED_0xB1 _RESERVED_0xB2 _RESERVED_0xB3 _RESERVED_0xB4 _RESERVED_0xB5 KEY_KPLEFTPAREN KEY_KPRIGHTPAREN
+ _RESERVED_0xB8 _RESERVED_0xB9 _RESERVED_0xBA _RESERVED_0xBB _RESERVED_0xBC _RESERVED_0xBD _RESERVED_0xBE _RESERVED_0xBF
+ _RESERVED_0xC0 _RESERVED_0xC1 _RESERVED_0xC2 _RESERVED_0xC3 _RESERVED_0xC4 _RESERVED_0xC5 _RESERVED_0xC6 _RESERVED_0xC7
+ _RESERVED_0xC8 _RESERVED_0xC9 _RESERVED_0xCA _RESERVED_0xCB _RESERVED_0xCC _RESERVED_0xCD _RESERVED_0xCE _RESERVED_0xCF
+ _RESERVED_0xD0 _RESERVED_0xD1 _RESERVED_0xD2 _RESERVED_0xD3 _RESERVED_0xD4 _RESERVED_0xD5 _RESERVED_0xD6 _RESERVED_0xD7
+ _RESERVED_0xD8 _RESERVED_0xD9 _RESERVED_0xDA _RESERVED_0xDB _RESERVED_0xDC _RESERVED_0xDD _RESERVED_0xDE _RESERVED_0xDF
+ _RESERVED_0xE0 _RESERVED_0xE1 _RESERVED_0xE2 _RESERVED_0xE3 _RESERVED_0xE4 _RESERVED_0xE5 _RESERVED_0xE6 _RESERVED_0xE7
+ _RESERVED_0xE8 _RESERVED_0xE9 _RESERVED_0xEA _RESERVED_0xEB _RESERVED_0xEC _RESERVED_0xED _RESERVED_0xEE _RESERVED_0xEF
+ _RESERVED_0xF0 _RESERVED_0xF1 _RESERVED_0xF2 _RESERVED_0xF3 _RESERVED_0xF4 _RESERVED_0xF5 _RESERVED_0xF6 _RESERVED_0xF7
+ _RESERVED_0xF8 _RESERVED_0xF9 _RESERVED_0xFA _RESERVED_0xFB _RESERVED_0xFC _RESERVED_0xFD _RESERVED_0xFE _RESERVED_0xFF
+ ''')
+
+ MODIFIERS = [ uinput.ev.KEY_LEFTCTRL, uinput.ev.KEY_LEFTSHIFT, uinput.ev.KEY_LEFTALT, uinput.ev.KEY_LEFTMETA,
+ uinput.ev.KEY_RIGHTCTRL, uinput.ev.KEY_RIGHTSHIFT, uinput.ev.KEY_RIGHTALT, uinput.ev.KEY_RIGHTMETA ]
+
+ ALL_KEYS = [ v for k, v in uinput.ev.__dict__.items() if k.startswith('KEY_') ]
+ REGULAR_MAP = { kc.value: getattr(uinput.ev, kc.name) for kc in Keycode if hasattr(uinput.ev, kc.name) }
+
+ @classmethod
+ def map_modifiers(kls, val):
+ return [ mod for i, mod in enumerate(kls.MODIFIERS) if val & (1<<i) ]
+
+ @classmethod
+ def map_regulars(kls, keycodes):
+ return [ kls.REGULAR_MAP[kc] for kc in keycodes if kc != 0 and kc in kls.REGULAR_MAP ]
+
+class Magic:
+ @classmethod
+ def map_bytes_to_incantation(kls, data):
+ elems = [ f'{kls.ADJECTIVES[a]} {kls.NOUNS[b]}' for a, b in zip(data[0::2], data[1::2]) ]
+ nfirst = ", ".join(elems[:-1])
+ return f'{nfirst} and {elems[-1]}'
+
+ EVEN = '''
+ aardvark absurd accrue acme adrift adult afflict ahead
+ aimless Algol allow alone ammo ancient apple artist
+ assume Athens atlas Aztec baboon backfield backward banjo
+ beaming bedlamp beehive beeswax befriend Belfast berserk billiard
+ bison blackjack blockade blowtorch bluebird bombast bookshelf brackish
+ breadline breakup brickyard briefcase Burbank button buzzard cement
+ chairlift chatter checkup chisel choking chopper Christmas clamshell
+ classic classroom cleanup clockwork cobra commence concert cowbell
+ crackdown cranky crowfoot crucial crumpled crusade cubic dashboard
+ deadbolt deckhand dogsled dragnet drainage dreadful drifter dropper
+ drumbeat drunken Dupont dwelling eating edict egghead eightball
+ endorse endow enlist erase escape exceed eyeglass eyetooth
+ facial fallout flagpole flatfoot flytrap fracture framework freedom
+ frighten gazelle Geiger glitter glucose goggles goldfish gremlin
+ guidance hamlet highchair hockey indoors indulge inverse involve
+ island jawbone keyboard kickoff kiwi klaxon locale lockup
+ merit minnow miser Mohawk mural music necklace Neptune
+ newborn nightbird Oakland obtuse offload optic orca payday
+ peachy pheasant physique playhouse Pluto preclude prefer preshrunk
+ printer prowler pupil puppy python quadrant quiver quota
+ ragtime ratchet rebirth reform regain reindeer rematch repay
+ retouch revenge reward rhythm ribcage ringbolt robust rocker
+ ruffled sailboat sawdust scallion scenic scorecard Scotland seabird
+ select sentence shadow shamrock showgirl skullcap skydive slingshot
+ slowdown snapline snapshot snowcap snowslide solo southward soybean
+ spaniel spearhead spellbind spheroid spigot spindle spyglass stagehand
+ stagnate stairway standard stapler steamship sterling stockman stopwatch
+ stormy sugar surmount suspense sweatband swelter tactics talon
+ tapeworm tempest tiger tissue tonic topmost tracker transit
+ trauma treadmill Trojan trouble tumor tunnel tycoon uncut
+ unearth unwind uproot upset upshot vapor village virus
+ Vulcan waffle wallet watchword wayside willow woodlark Zulu
+ '''.split()
+
+ ODD = '''
+ adroitness adviser aftermath aggregate alkali almighty amulet amusement
+ antenna applicant Apollo armistice article asteroid Atlantic atmosphere
+ autopsy Babylon backwater barbecue belowground bifocals bodyguard bookseller
+ borderline bottomless Bradbury bravado Brazilian breakaway Burlington businessman
+ butterfat Camelot candidate cannonball Capricorn caravan caretaker celebrate
+ cellulose certify chambermaid Cherokee Chicago clergyman coherence combustion
+ commando company component concurrent confidence conformist congregate consensus
+ consulting corporate corrosion councilman crossover crucifix cumbersome customer
+ Dakota decadence December decimal designing detector detergent determine
+ dictator dinosaur direction disable disbelief disruptive distortion document
+ embezzle enchanting enrollment enterprise equation equipment escapade Eskimo
+ everyday examine existence exodus fascinate filament finicky forever
+ fortitude frequency gadgetry Galveston getaway glossary gossamer graduate
+ gravity guitarist hamburger Hamilton handiwork hazardous headwaters hemisphere
+ hesitate hideaway holiness hurricane hydraulic impartial impetus inception
+ indigo inertia infancy inferno informant insincere insurgent integrate
+ intention inventive Istanbul Jamaica Jupiter leprosy letterhead liberty
+ maritime matchmaker maverick Medusa megaton microscope microwave midsummer
+ millionaire miracle misnomer molasses molecule Montana monument mosquito
+ narrative nebula newsletter Norwegian October Ohio onlooker opulent
+ Orlando outfielder Pacific pandemic Pandora paperweight paragon paragraph
+ paramount passenger pedigree Pegasus penetrate perceptive performance pharmacy
+ phonetic photograph pioneer pocketful politeness positive potato processor
+ provincial proximate puberty publisher pyramid quantity racketeer rebellion
+ recipe recover repellent replica reproduce resistor responsive retraction
+ retrieval retrospect revenue revival revolver sandalwood sardonic Saturday
+ savagery scavenger sensation sociable souvenir specialist speculate stethoscope
+ stupendous supportive surrender suspicious sympathy tambourine telephone therapist
+ tobacco tolerance tomorrow torpedo tradition travesty trombonist truncated
+ typewriter ultimate undaunted underfoot unicorn unify universe unravel
+ upcoming vacancy vagabond vertigo Virginia visitor vocalist voyager
+ warranty Waterloo whimsical Wichita Wilmington Wyoming yesteryear Yucatan
+ '''.split()
+
+class NoiseEngine:
+ def __init__(self, host_key, packetizer, debug=False):
+ self.debug = debug
+ self.packetizer = packetizer
+ self.static_local = host_key
+ self.proto = NoiseConnection.from_name(b'Noise_XX_25519_ChaChaPoly_BLAKE2s')
+ self.proto.set_as_initiator()
+ self.proto.set_keypair_from_private_bytes(Keypair.STATIC, self.static_local)
+ self.proto.start_handshake()
+ self.handshake = self.proto.noise_protocol.handshake_state # save for later because someone didn't think
+ self.paired = False
+ self.connected = False
+
+ @property
+ def remote_fingerprint(self):
+ ''' Return the SHA-256 hash of the remote static key (rs). This can be used to fingerprint the remote party. '''
+ return hashlib.sha256(self.handshake.rs.public_bytes).hexdigest()
+
+ @classmethod
+ def generate_private_key_x25519(kls):
+ # This is taken from noise-c's reference implementation. This would not be needed had not cryptography/hazmat
+ # decided noone would ever need serialized x25519 private keys and noiseprotocol stopped just short of implementing
+ # key generation (who'd need that anyway, amiright?) -.-
+ key = list(secrets.token_bytes(32))
+ key[0] &= 0xF8
+ key[31] = (key[31] & 0x7F) | 0x40
+ return bytes(key)
+
+ @wraps(print)
+ def debug_print(self, *args, **kwargs):
+ if self.debug:
+ print(*args, **kwargs)
+
+ def perform_handshake(self):
+ self.packetizer.send_packet(PacketType.INITIATE_HANDSHAKE, b'')
+ self.debug_print('Handshake started')
+
+ while True:
+ if self.proto.handshake_finished:
+ break
+ self.packetizer.send_packet(PacketType.HANDSHAKE, self.proto.write_message())
+
+ if self.proto.handshake_finished:
+ break
+ pkt_type, payload = self.packetizer.receive_packet()
+ if pkt_type is PacketType.HANDSHAKE:
+ self.proto.read_message(payload)
+ else:
+ raise ProtocolError(f'Incorrect packet type {pkt_type}. Ignoring since this is only test code.')
+
+ msg_type, payload = self.packetizer.receive_packet()
+ rtype, data = self._decrypt(payload)
+ if rtype is ReportType.PAIRING_SUCCESS:
+ self.connected, self.paired = True, True
+ elif rtype is ReportType.PAIRING_START:
+ self.connected, self.paired = True, False
+ else:
+ self.connected, self.paired = True, False
+ raise UserWarning(f'Unexpected record type {rtype} in {msg_type} packet. Ignoring.')
+
+ if self.debug:
+ print('Handshake finished, handshake hash:')
+ hexdump(print, self.proto.get_handshake_hash())
+
+ def channel_binding_incantation(self):
+ hhash = self.proto.get_handshake_hash()
+ return '\n'.join(Magic.map_bytes_to_incantation(hhash[i:i+8]) for i in range(0, 16, 8))
+
+ def receive_loop(self):
+ while True:
+ try:
+ pkt_type, received = self.packetizer.receive_packet()
+ except Exception as e:
+ self.debug_print('Invalid framing:', e)
+
+ if pkt_type is not PacketType.DATA:
+ raise UserWarning(f'Unexpected packet type {pkt_type}. Ignoring.')
+ continue
+
+ rtype, data = self._decrypt(received)
+ if self.debug:
+ print(f'Decrypted packet {rtype} ({rtype.value}):')
+ hexdump(print, data)
+ yield rtype, data
+
+ def _decrypt(self, received):
+ try:
+ data = self.proto.decrypt(received)
+ return ReportType(data[0]), data[1:]
+
+ except NoiseInvalidMessage as e:
+ self.debug_print('Invalid noise message', e)
+ for i in range(3):
+ with self._nonce_lookahead() as set_nonce:
+ set_nonce(i)
+ data = self.proto.decrypt(received)
+ return ReportType(data[0]), data[1:]
+ else:
+ self.debug_print(' Unrecoverable.')
+ raise e
+ self.debug_print(f' Recovered. n={n}')
+
+ @contextmanager
+ def _nonce_lookahead(self):
+ nold = self.proto.noise_protocol.cipher_state_decrypt.n
+ def setter(n):
+ self.proto.noise_protocol.cipher_state_decrypt.n = nold + n
+
+ with suppress(NoiseInvalidMessage):
+ yield setter
+
+ self.proto.noise_protocol.cipher_state_decrypt.n = nold
+
+ def pairing_messages(self):
+ user_input = ''
+ for msg_type, payload in self.receive_loop():
+ if msg_type is ReportType.PAIRING_INPUT:
+ ch = chr(payload[0])
+ if ch == '\b':
+ user_input = user_input[:-1]
+ else:
+ user_input += ch
+ yield user_input
+
+ elif msg_type is ReportType.PAIRING_SUCCESS:
+ self.paired = True
+ break
+
+ elif msg_type is ReportType.PAIRING_ERROR:
+ raise ProtocolError('Device-side pairing error') # FIXME find better exception subclass here
+
+ else:
+ raise ProtocolError('Invalid report type')
+
+ def uinput_passthrough(self):
+ with uinput.Device(KeyMapper.ALL_KEYS) as ui:
+ old_kcs = set()
+ for msg_type, payload in self.receive_loop():
+ report_len, *report = payload
+ if report_len != 8:
+ raise ValueError('Unsupported report length', report_len)
+
+ if msg_type is ReportType.KEYBOARD:
+ modbyte, _reserved, *keycodes = report
+ import binascii
+ keys = { *KeyMapper.map_modifiers(modbyte), *KeyMapper.map_regulars(keycodes) }
+ if self.debug:
+ print('Emitting:', keys)
+
+ for key in keys - old_kcs:
+ ui.emit(key, 1, syn=False)
+ for key in old_kcs - keys:
+ ui.emit(key, 0, syn=False)
+ ui.syn()
+ old_kcs = keys
+
+ elif msg_type is ReportType.MOUSE:
+ # FIXME unhandled
+ pass
+
+if __name__ == '__main__':
+ import argparse
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument('serial')
+ parser.add_argument('baudrate')
+ parser.add_argument('-w', '--width', type=int, default=16, help='Number of bytes to display in one line')
+ parser.add_argument('-d', '--debug', action='store_true')
+ args = parser.parse_args()
+
+ ser = serial.Serial(args.serial, args.baudrate)
+ packetizer = Packetizer(ser, debug=args.debug, width=args.width)
+ noise = NoiseEngine(packetizer, debug=args.debug)
+ noise.perform_handshake()
+
+ print('Handshake channel binding incantation:')
+ print(noise.channel_binding_incantation())
+
+ for user_input in noise.pairing_messages():
+ if not args.debug:
+ print('\033[2K\r', end='')
+ print('Pairing input:', user_input, end='' if not args.debug else '\n', flush=True)
+ print()
+ print('Pairing success')
+
+ noise.uinput_passthrough()
diff --git a/fw/include/cobs.h b/fw/include/cobs.h
new file mode 100644
index 0000000..24e16fb
--- /dev/null
+++ b/fw/include/cobs.h
@@ -0,0 +1,27 @@
+#ifndef __COBS_H__
+#define __COBS_H__
+
+#include <stdint.h>
+#include <unistd.h>
+#include <string.h>
+
+
+struct cobs_decode_state {
+ size_t p;
+ size_t c;
+};
+
+
+ssize_t cobs_encode(char *dst, size_t dstlen, char *src, size_t srclen);
+ssize_t cobs_decode(char *dst, size_t dstlen, char *src, size_t srclen);
+
+int cobs_encode_incremental(void *f, int (*output)(void *f, unsigned char c), unsigned char *src, size_t srclen);
+
+/*@ requires \valid(state);
+ ensures state->p == 0 && state->c == 0;
+ assigns *state;
+ @*/
+void cobs_decode_incremental_initialize(struct cobs_decode_state *state);
+int cobs_decode_incremental(struct cobs_decode_state *state, unsigned char *dst, size_t dstlen, unsigned char src);
+
+#endif//__COBS_H__
diff --git a/fw/include/driver/usbh_device_driver.h b/fw/include/driver/usbh_device_driver.h
new file mode 100644
index 0000000..e846fd2
--- /dev/null
+++ b/fw/include/driver/usbh_device_driver.h
@@ -0,0 +1,326 @@
+/*
+ * This file is part of the libusbhost library
+ * hosted at http://github.com/libusbhost/libusbhost
+ *
+ * Copyright (C) 2015 Amir Hammad <amir.hammad@hotmail.com>
+ *
+ *
+ * libusbhost is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef USBH_DEVICE_DRIVER_
+#define USBH_DEVICE_DRIVER_
+
+#include "usbh_config.h"
+#include "usbh_core.h"
+
+#include <libopencm3/usb/usbstd.h>
+#include <stdint.h>
+
+BEGIN_DECLS
+
+enum USBH_ENDPOINT_TYPE {
+ USBH_ENDPOINT_TYPE_CONTROL = 0,
+ USBH_ENDPOINT_TYPE_ISOCHRONOUS = 1,
+ USBH_ENDPOINT_TYPE_BULK = 2,
+ USBH_ENDPOINT_TYPE_INTERRUPT = 3,
+};
+
+enum USBH_SPEED {
+ USBH_SPEED_FULL = 0,
+ USBH_SPEED_LOW = 1,
+ USBH_SPEED_HIGH = 2,
+};
+
+enum USBH_PACKET_CALLBACK_STATUS {
+ USBH_PACKET_CALLBACK_STATUS_OK = 0,
+ USBH_PACKET_CALLBACK_STATUS_ERRSIZ = 1,
+ USBH_PACKET_CALLBACK_STATUS_EAGAIN = 2, // -- TODO: automatic handling of transmit errors 3xTXERR->FATAL
+ USBH_PACKET_CALLBACK_STATUS_EFATAL = 3
+};
+
+enum USBH_POLL_STATUS {
+ USBH_POLL_STATUS_NONE,
+ USBH_POLL_STATUS_DEVICE_CONNECTED,
+ USBH_POLL_STATUS_DEVICE_DISCONNECTED
+};
+
+enum USBH_CONTROL_TYPE {
+ USBH_CONTROL_TYPE_SETUP,
+ USBH_CONTROL_TYPE_DATA
+};
+
+enum USBH_ENUM_STATE {
+ USBH_ENUM_STATE_SET_ADDRESS,
+ USBH_ENUM_STATE_FIRST = USBH_ENUM_STATE_SET_ADDRESS,
+ USBH_ENUM_STATE_DEVICE_DT_READ_SETUP,
+ USBH_ENUM_STATE_DEVICE_DT_READ_COMPLETE,
+ USBH_ENUM_STATE_CONFIGURATION_DT_HEADER_READ_SETUP,
+ USBH_ENUM_STATE_CONFIGURATION_DT_HEADER_READ,
+ USBH_ENUM_STATE_CONFIGURATION_DT_HEADER_READ_COMPLETE,
+ USBH_ENUM_STATE_CONFIGURATION_DT_READ_SETUP,
+ USBH_ENUM_STATE_CONFIGURATION_DT_READ,
+ USBH_ENUM_STATE_CONFIGURATION_DT_READ_COMPLETE,
+ USBH_ENUM_STATE_SET_CONFIGURATION_SETUP,
+ USBH_ENUM_STATE_SET_CONFIGURATION_COMPLETE,
+ USBH_ENUM_STATE_FIND_DRIVER,
+};
+
+enum USBH_CONTROL_STATE {
+ USBH_CONTROL_STATE_NONE,
+ USBH_CONTROL_STATE_SETUP,
+ USBH_CONTROL_STATE_DATA,
+ USBH_CONTROL_STATE_STATUS,
+};
+
+typedef struct _usbh_device usbh_device_t;
+
+struct _usbh_packet_callback_data {
+ /// status - it is used for reporting of the errors
+ enum USBH_PACKET_CALLBACK_STATUS status;
+
+ /// count of bytes that has been actually transferred
+ uint32_t transferred_length;
+};
+typedef struct _usbh_packet_callback_data usbh_packet_callback_data_t;
+
+typedef void (*usbh_packet_callback_t)(usbh_device_t *dev, usbh_packet_callback_data_t status);
+
+struct _usbh_control {
+ enum USBH_CONTROL_STATE state;
+ usbh_packet_callback_t callback;
+ union {
+ const void *out;
+ void *in;
+ } data;
+ uint16_t data_length;
+ struct usb_setup_data setup_data;
+};
+typedef struct _usbh_control usbh_control_t;
+
+/**
+ * @brief The _usbh_device struct
+ *
+ * This represents exactly one connected device.
+ */
+struct _usbh_device {
+ /// max packet size for control endpoint(0)
+ uint16_t packet_size_max0;
+
+ /// Device's address
+ int8_t address;
+
+ /// @see USBH_SPEED
+ enum USBH_SPEED speed;
+
+ /// state used for enumeration purposes
+ enum USBH_ENUM_STATE state;
+ usbh_control_t control;
+
+ /// toggle bit
+ uint8_t toggle0;
+
+ /**
+ * @brief drv - device driver used for this connected device
+ */
+
+ const usbh_dev_driver_t *drv;
+ /**
+ * @brief drvdata - device driver's private data
+ */
+ void *drvdata;
+
+ /**
+ * @brief lld - pointer to a low-level driver's instance
+ */
+ const void *lld;
+};
+typedef struct _usbh_device usbh_device_t;
+
+struct _usbh_packet {
+ /// pointer to data
+ union {
+ const void *out;
+ void *in;
+ } data;
+
+ /// length of the data (up to 1023)
+ uint16_t datalen;
+
+ /// Device's address
+ int8_t address;
+
+ /**
+ * @brief Endpoint type
+ * @see USBH_ENDPOINT_TYPE
+ */
+ enum USBH_ENDPOINT_TYPE endpoint_type;
+
+ enum USBH_CONTROL_TYPE control_type;
+
+ /// Endpoint number 0..15
+ uint8_t endpoint_address;
+
+ /// Max packet size for an endpoint
+ uint16_t endpoint_size_max;
+
+ /// @see USBH_SPEED
+ enum USBH_SPEED speed;
+ uint8_t *toggle;
+
+ /**
+ * @brief callback this will be called when the packet is finished - either successfuly or not.
+ */
+ usbh_packet_callback_t callback;
+
+ /**
+ * @brief callback_arg argument passed into callback
+ *
+ * Low level driver is not allowed to alter the data pointed by callback_arg
+ */
+ void *callback_arg;
+};
+typedef struct _usbh_packet usbh_packet_t;
+
+struct _usbh_low_level_driver {
+ /**
+ * @brief init initialization routine of the low-level driver
+ *
+ *
+ * This function is called during the initialization of the library
+ *
+ * @see usbh_init()
+ */
+ void (*init)(void *drvdata);
+
+ /**
+ * write - perform a write to a device
+ * @see usbh_packet_t
+ */
+ void (*write)(void *drvdata, const usbh_packet_t *packet);
+
+ /**
+ * @brief read - perform a read from a device
+ * @see usbh_packet_t
+ */
+ void (*read)(void *drvdata, usbh_packet_t *packet);
+
+ /**
+ * @brief this is called as a part of @ref usbh_poll() routine
+ */
+ enum USBH_POLL_STATUS (*poll)(void *drvdata, uint32_t time_curr_us);
+
+ /**
+ * @brief speed of the low-level bus
+ */
+ enum USBH_SPEED (*root_speed)(void *drvdata);
+
+ /**
+ * @brief Pointer to Low-level driver data
+ *
+ * Data pointed by this pointer should not be altered by the logic other from low-level driver's logic
+ */
+ void *driver_data;
+};
+typedef struct _usbh_low_level_driver usbh_low_level_driver_t;
+
+struct _usbh_generic_data {
+ usbh_device_t usbh_device[USBH_MAX_DEVICES];
+ uint8_t usbh_buffer[BUFFER_ONE_BYTES];
+};
+typedef struct _usbh_generic_data usbh_generic_data_t;
+
+
+/// set to -1 for unused items ("don't care" functionality) @see find_driver()
+struct _usbh_dev_driver_info {
+ int32_t deviceClass;
+ int32_t deviceSubClass;
+ int32_t deviceProtocol;
+ int32_t idVendor;
+ int32_t idProduct;
+ int32_t ifaceClass;
+ int32_t ifaceSubClass;
+ int32_t ifaceProtocol;
+};
+typedef struct _usbh_dev_driver_info usbh_dev_driver_info_t;
+
+struct _usbh_dev_driver {
+ /**
+ * @brief init is initialization routine of the device driver
+ *
+ * This function is called during the initialization of the device driver
+ */
+ void *(*init)(usbh_device_t *usbh_dev);
+
+ /**
+ * @brief analyze descriptor
+ * @param[in/out] drvdata is the device driver's private data
+ * @param[in] descriptor is the pointer to the descriptor that should
+ * be parsed in order to prepare driver to be loaded
+ *
+ * @retval true when the enumeration is complete and the driver is ready to be used
+ * @retval false when the device driver is not ready to be used
+ *
+ * This should be used for getting correct endpoint numbers, getting maximum sizes of endpoints.
+ * Should return true, when no more data is needed.
+ *
+ */
+ bool (*analyze_descriptor)(void *drvdata, void *descriptor);
+
+ /**
+ * @brief poll method is called periodically by the library core
+ * @param[in/out] drvdata is the device driver's private data
+ * @param[in] time_curr_us current timestamp in microseconds
+ * @see usbh_poll()
+ */
+ void (*poll)(void *drvdata, uint32_t time_curr_us);
+
+ /**
+ * @brief unloads the device driver
+ * @param[in/out] drvdata is the device driver's private data
+ *
+ * This should free any data associated with this device
+ */
+ void (*remove)(void *drvdata);
+
+ /**
+ * @brief info - compatibility information about the driver. It is used by the core during device enumeration
+ * @see find_driver()
+ */
+ const usbh_dev_driver_info_t * const info;
+};
+typedef struct _usbh_dev_driver usbh_dev_driver_t;
+
+#define ERROR(arg) LOG_PRINTF("UNHANDLED_ERROR %d: file: %s, line: %d",\
+ arg, __FILE__, __LINE__)
+
+
+/* Hub related functions */
+
+usbh_device_t *usbh_get_free_device(const usbh_device_t *dev);
+bool usbh_enum_available(void);
+void device_enumeration_start(usbh_device_t *dev);
+
+/* All devices functions */
+void usbh_read(usbh_device_t *dev, usbh_packet_t *packet);
+void usbh_write(usbh_device_t *dev, const usbh_packet_t *packet);
+
+/* Helper functions used by device drivers */
+void device_control(usbh_device_t *dev, usbh_packet_callback_t callback, const struct usb_setup_data *setup_data, void *data);
+void device_remove(usbh_device_t *dev);
+
+END_DECLS
+
+#endif
diff --git a/fw/include/usbh_config.h b/fw/include/usbh_config.h
new file mode 100644
index 0000000..8146ffb
--- /dev/null
+++ b/fw/include/usbh_config.h
@@ -0,0 +1,62 @@
+/*
+ * This file is part of the libusbhost library
+ * hosted at http://github.com/libusbhost/libusbhost
+ *
+ * Copyright (C) 2015 Amir Hammad <amir.hammad@hotmail.com>
+ *
+ *
+ * libusbhost is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef USBH_CONFIG_
+#define USBH_CONFIG_
+
+
+
+// Max devices per hub
+#define USBH_HUB_MAX_DEVICES (8)
+
+// Max number of hub instancies
+#define USBH_MAX_HUBS (2)
+
+// Max devices
+#define USBH_MAX_DEVICES (15)
+
+// Min: 128
+// Set this wisely
+#define BUFFER_ONE_BYTES (2048)
+
+// HID class devices
+#define USBH_HID_MAX_DEVICES (2)
+#define USBH_HID_BUFFER (256)
+#define USBH_HID_REPORT_BUFFER (4)
+
+// MIDI
+// Maximal number of midi devices connected to whatever hub
+#define USBH_AC_MIDI_MAX_DEVICES (4)
+
+#define USBH_AC_MIDI_BUFFER (64)
+
+// Gamepad XBOX
+#define USBH_GP_XBOX_MAX_DEVICES (2)
+
+#define USBH_GP_XBOX_BUFFER (32)
+
+/* Sanity checks */
+#if (USBH_MAX_DEVICES > 127)
+#error USBH_MAX_DEVICES > 127
+#endif
+
+#endif
diff --git a/fw/include/usbh_core.h b/fw/include/usbh_core.h
new file mode 100644
index 0000000..04dbd29
--- /dev/null
+++ b/fw/include/usbh_core.h
@@ -0,0 +1,65 @@
+/*
+ * This file is part of the libusbhost library
+ * hosted at http://github.com/libusbhost/libusbhost
+ *
+ * Copyright (C) 2015 Amir Hammad <amir.hammad@hotmail.com>
+ *
+ *
+ * libusbhost is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef USBH_CORE_
+#define USBH_CORE_
+
+#include "usbh_config.h"
+
+#include <stdint.h>
+#include <stdbool.h>
+
+/* This must be placed around external function declaration for C++
+ * support. */
+#ifdef __cplusplus
+# define BEGIN_DECLS extern "C" {
+# define END_DECLS }
+#else
+# define BEGIN_DECLS
+# define END_DECLS
+#endif
+
+BEGIN_DECLS
+
+typedef struct _usbh_dev_driver usbh_dev_driver_t;
+typedef struct _usbh_low_level_driver usbh_low_level_driver_t;
+
+/**
+ * @brief usbh_init
+ * @param low_level_drivers list of the low level drivers to be used by this library
+ * @param device_drivers list of the device drivers that could be used with attached devices
+ */
+void usbh_init(const usbh_low_level_driver_t * const low_level_drivers[], const usbh_dev_driver_t * const device_drivers[]);
+
+/**
+ * @brief usbh_poll
+ * @param time_curr_us - use monotically rising time
+ *
+ * time_curr_us:
+ * * can overflow, in time of this writing, after 1s
+ * * unit is microseconds
+ */
+void usbh_poll(uint32_t time_curr_us);
+
+END_DECLS
+
+#endif // USBH_CORE_
diff --git a/fw/include/usbh_driver_ac_midi.h b/fw/include/usbh_driver_ac_midi.h
new file mode 100644
index 0000000..9bab100
--- /dev/null
+++ b/fw/include/usbh_driver_ac_midi.h
@@ -0,0 +1,65 @@
+/*
+ * This file is part of the libusbhost library
+ * hosted at http://github.com/libusbhost/libusbhost
+ *
+ * Copyright (C) 2016 Amir Hammad <amir.hammad@hotmail.com>
+ *
+ *
+ * libusbhost is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef USBH_DRIVER_AC_MIDI_
+#define USBH_DRIVER_AC_MIDI_
+
+#include "usbh_core.h"
+
+#include <stdint.h>
+
+BEGIN_DECLS
+
+struct _midi_config {
+ void (*read_callback)(int device_id, uint8_t *data);
+ void (*notify_connected)(int device_id);
+ void (*notify_disconnected)(int device_id);
+};
+typedef struct _midi_config midi_config_t;
+
+/**
+ * @param bytes_written count of bytes that were actually written
+ */
+typedef void (*midi_write_callback_t)(uint8_t bytes_written);
+
+/**
+ * @brief midi_driver_init initialization routine - this will initialize internal structures of this device driver
+ * @param config
+ *
+ * @see midi_config_t
+ */
+void midi_driver_init(const midi_config_t *config);
+
+/**
+ * @brief usbh_midi_write
+ * @param device_id
+ * @param data
+ * @param length
+ * @param callback this is called when the write call finishes
+ */
+void usbh_midi_write(uint8_t device_id, const void *data, uint32_t length, midi_write_callback_t callback);
+
+extern const usbh_dev_driver_t usbh_midi_driver;
+
+END_DECLS
+
+#endif
diff --git a/fw/include/usbh_driver_gp_xbox.h b/fw/include/usbh_driver_gp_xbox.h
new file mode 100644
index 0000000..d648b90
--- /dev/null
+++ b/fw/include/usbh_driver_gp_xbox.h
@@ -0,0 +1,78 @@
+/*
+ * This file is part of the libusbhost library
+ * hosted at http://github.com/libusbhost/libusbhost
+ *
+ * Copyright (C) 2015 Amir Hammad <amir.hammad@hotmail.com>
+ *
+ *
+ * libusbhost is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef USBH_DRIVER_GP_XBOX_
+#define USBH_DRIVER_GP_XBOX_
+
+#include "usbh_core.h"
+
+#include <stdint.h>
+
+BEGIN_DECLS
+
+#define GP_XBOX_DPAD_TOP (1 << 0)
+#define GP_XBOX_DPAD_LEFT (1 << 1)
+#define GP_XBOX_DPAD_BOTTOM (1 << 2)
+#define GP_XBOX_DPAD_RIGHT (1 << 3)
+#define GP_XBOX_BUTTON_X (1 << 4)
+#define GP_XBOX_BUTTON_Y (1 << 5)
+#define GP_XBOX_BUTTON_A (1 << 6)
+#define GP_XBOX_BUTTON_B (1 << 7)
+#define GP_XBOX_BUTTON_SELECT (1 << 8)
+#define GP_XBOX_BUTTON_START (1 << 9)
+#define GP_XBOX_BUTTON_LT (1 << 10)
+#define GP_XBOX_BUTTON_RT (1 << 11)
+#define GP_XBOX_BUTTON_XBOX (1 << 12)
+#define GP_XBOX_BUTTON_AXIS_LEFT (1 << 13)
+#define GP_XBOX_BUTTON_AXIS_RIGHT (1 << 14)
+
+struct _gp_xbox_packet {
+ uint32_t buttons;
+ int16_t axis_left_x;
+ int16_t axis_left_y;
+ int16_t axis_right_x;
+ int16_t axis_right_y;
+ uint8_t axis_rear_left;
+ uint8_t axis_rear_right;
+};
+typedef struct _gp_xbox_packet gp_xbox_packet_t;
+
+
+struct _gp_xbox_config {
+ void (*update)(uint8_t device_id, gp_xbox_packet_t data);
+ void (*notify_connected)(uint8_t device_id);
+ void (*notify_disconnected)(uint8_t device_id);
+};
+typedef struct _gp_xbox_config gp_xbox_config_t;
+
+
+/**
+ * @brief gp_xbox_driver_init initialization routine - this will initialize internal structures of this device driver
+ * @see gp_xbox_config_t
+ */
+void gp_xbox_driver_init(const gp_xbox_config_t *config);
+
+extern const usbh_dev_driver_t usbh_gp_xbox_driver;
+
+END_DECLS
+
+#endif
diff --git a/fw/include/usbh_driver_hid.h b/fw/include/usbh_driver_hid.h
new file mode 100644
index 0000000..8155d82
--- /dev/null
+++ b/fw/include/usbh_driver_hid.h
@@ -0,0 +1,85 @@
+/*
+ * This file is part of the libusbhost library
+ * hosted at http://github.com/libusbhost/libusbhost
+ *
+ * Copyright (C) 2016 Amir Hammad <amir.hammad@hotmail.com>
+ *
+ *
+ * libusbhost is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef USBH_DRIVER_HID_
+#define USBH_DRIVER_HID_
+
+#include "usbh_core.h"
+
+#include <stdint.h>
+
+BEGIN_DECLS
+
+struct _hid_mouse_config {
+ /**
+ * @brief this is called when some data is read when polling the device
+ * @param device_id handle of HID device
+ * @param data pointer to the data
+ * @param length count of bytes in the data
+ *
+ * TODO: make better interface that provides data contained in the report descriptor
+ *
+ */
+ void (*hid_in_message_handler)(uint8_t device_id, const uint8_t *data, uint32_t length);
+};
+typedef struct _hid_mouse_config hid_config_t;
+
+/**
+ * @brief hid_mouse_driver_init initialization routine - this will initialize internal structures of this device driver
+ * @param config
+ * @see hid_mouse_config_t
+ */
+void hid_driver_init(const hid_config_t *config);
+
+/**
+ * @brief hid_set_report
+ * @param device_id handle of HID device
+ * @returns true on success, false otherwise
+ */
+bool hid_set_report(uint8_t device_id, uint8_t val);
+
+enum HID_TYPE {
+ HID_TYPE_NONE,
+ HID_TYPE_MOUSE,
+ HID_TYPE_KEYBOARD,
+};
+
+/**
+ * @brief hid_get_type
+ * @param device_id handle of HID device
+ * @return type of attached HID
+ * @see enum HID_TYPE
+ */
+enum HID_TYPE hid_get_type(uint8_t device_id);
+
+/**
+ * @brief hid_is_connected
+ * @param device_id handle of HID device
+ * @return true if the device with device_id is connected
+ */
+bool hid_is_connected(uint8_t device_id);
+
+extern const usbh_dev_driver_t usbh_hid_driver;
+
+END_DECLS
+
+#endif
diff --git a/fw/include/usbh_driver_hub.h b/fw/include/usbh_driver_hub.h
new file mode 100644
index 0000000..1066074
--- /dev/null
+++ b/fw/include/usbh_driver_hub.h
@@ -0,0 +1,39 @@
+/*
+ * This file is part of the libusbhost library
+ * hosted at http://github.com/libusbhost/libusbhost
+ *
+ * Copyright (C) 2015 Amir Hammad <amir.hammad@hotmail.com>
+ *
+ *
+ * libusbhost is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef USBH_DRIVER_HUB_
+#define USBH_DRIVER_HUB_
+
+#include "usbh_core.h"
+
+BEGIN_DECLS
+
+/**
+ * @brief hub_driver_init initialization routine - this will initialize internal structures of this device driver
+ */
+void hub_driver_init(void);
+
+extern const usbh_dev_driver_t usbh_hub_driver;
+
+END_DECLS
+
+#endif
diff --git a/fw/include/usbh_lld_stm32f4.h b/fw/include/usbh_lld_stm32f4.h
new file mode 100644
index 0000000..33be145
--- /dev/null
+++ b/fw/include/usbh_lld_stm32f4.h
@@ -0,0 +1,44 @@
+/*
+ * This file is part of the libusbhost library
+ * hosted at http://github.com/libusbhost/libusbhost
+ *
+ * Copyright (C) 2015 Amir Hammad <amir.hammad@hotmail.com>
+ *
+ *
+ * libusbhost is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef USBH_LLD_STM32F4_H_
+#define USBH_LLD_STM32F4_H_
+
+#include "usbh_core.h"
+
+#include <stdint.h>
+
+BEGIN_DECLS
+
+// pass this to usbh init
+extern const usbh_low_level_driver_t usbh_lld_stm32f4_driver_fs;
+extern const usbh_low_level_driver_t usbh_lld_stm32f4_driver_hs;
+
+#ifdef USART_DEBUG
+void print_channels(const void *drvdata);
+#else
+#define print_channels(arg) ((void)arg)
+#endif
+
+END_DECLS
+
+#endif
diff --git a/fw/initRepo.sh b/fw/initRepo.sh
new file mode 100755
index 0000000..10ff5cb
--- /dev/null
+++ b/fw/initRepo.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+git submodule init
+git submodule update
+make -C libopencm3 -j3 lib/stm32/f4
diff --git a/fw/keymap.py b/fw/keymap.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/fw/keymap.py
diff --git a/fw/libopencm3 b/fw/libopencm3
new file mode 160000
+Subproject 798c1edf4d11e8a40a7263dc465fa225a63fa7e
diff --git a/fw/libusbhost_stm32f4.ld b/fw/libusbhost_stm32f4.ld
new file mode 100644
index 0000000..a1cf6d9
--- /dev/null
+++ b/fw/libusbhost_stm32f4.ld
@@ -0,0 +1,52 @@
+/*
+ * This file is part of the libopencm3 project.
+ *
+ * Copyright (C) 2009 Uwe Hermann <uwe@hermann-uwe.de>
+ * Copyright (C) 2011 Stephen Caudle <scaudle@doceme.com>
+ *
+ * This library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Minimal linker script for chinese STM32F4 board (STM32F407VE, 512K flash, 128K RAM). */
+
+/* Define memory regions. */
+MEMORY
+{
+ rom (rx) : ORIGIN = 0x08000000, LENGTH = 512K
+ ram (rwx) : ORIGIN = 0x20000000, LENGTH = 112K /* Main SRAM */
+ sram2 (rwx) : ORIGIN = 0x2001c000, LENGTH = 16K /* SRAM2 with some security features (?) */
+ ccm (rwx) : ORIGIN = 0x10000000, LENGTH = 64K /* Fast core-coupled memory */
+ backup (rwx) : ORIGIN = 0x40024000, LENGTH = 4K /* Battery-backed backup SRAM */
+}
+
+/* Include the common ld script. */
+INCLUDE libopencm3_stm32f4.ld
+
+/* Extra stuff */
+SECTIONS
+{
+ .backup_sram : {
+ . = ALIGN(4);
+ __backup_sram_start = .;
+ *(.backup_sram)
+ __backup_sram_end = .;
+ } >backup
+}
+
+PROVIDE(_ram_start = ORIGIN(ram));
+PROVIDE(_ram_end = ORIGIN(ram) + LENGTH(ram));
+PROVIDE(_rom_start = ORIGIN(rom));
+PROVIDE(_rom_end = ORIGIN(rom) + LENGTH(rom));
+
+
diff --git a/fw/nouns.py b/fw/nouns.py
new file mode 100644
index 0000000..602e93c
--- /dev/null
+++ b/fw/nouns.py
@@ -0,0 +1,34 @@
+NOUNS = '''
+ yolk writing wrath wound worm wings whistle watchdog
+ waste vomit vermin variation underachievement tusk troll trick
+ transplant transgression tooth tongue tickle tick thorn thistle
+ thing terror tentacle tease surrender surge sucker substance
+ storm stone stew stalk squid sprout sponge spill
+ spider sphere spectacle speck spawn soul solution snout
+ snake smell sloth slime slice sleeper slave sinew
+ shell shape seizure seed schism scam scale sainthood
+ root robe roach rinse remains relay rejuvenation realization
+ reaction ransom pupa pride prey predator potion pornography
+ polyp plum pleasure pitch pigeon phenomenon pest periwinkle
+ percolation parasite pair oyster orphan orgasm organism orchid
+ object nail mushroom murder mucus movement mother mold
+ mist mildew metal mesh meddling mayhem masterpiece masonry
+ mask manhood maggot lust loop living_thing liquor liquid
+ lining laceration knife kitten kiss jumper jest instrument
+ injustice injury influence indulgence incursion impulse imago hound
+ horn hook hoof heirloom heart hawk hare hair
+ gulp guardian grass goat gnat gluttony glowworm gasp
+ game fusion fungus frustration frog foul foot food
+ fog foal fluke fluff flower flicker flea flattery
+ flask flare firefly finger filtration female feeder feather
+ fart fang failure face fabrication extract exodus evil
+ envy enema embryo egress echo eater ear dwarf
+ dust drop draft domestication distortion dew depravity deity
+ death daughter dash dagger culture crutch crow critter
+ creeper creation crab corruption cocoon claw chip child
+ cell catch carving carrot carnival cancer butterfly burn
+ buildup brush brew bottle boot book bone blunder
+ blot blood blink bite bird benthos beak basket
+ bark ball baby axolotl ashes artifact arson armor
+ apparition antenna alms alienation advent adornment abomination abandonment
+ '''.split()
diff --git a/fw/openocd.cfg b/fw/openocd.cfg
new file mode 100644
index 0000000..95410da
--- /dev/null
+++ b/fw/openocd.cfg
@@ -0,0 +1,16 @@
+telnet_port 4444
+gdb_port 3333
+
+source [find interface/stlink-v2.cfg]
+#interface jlink
+#interface stlink-v2
+#adapter_khz 10000
+#transport select swd
+
+#source /usr/share/openocd/scripts/target/stm32f0x.cfg
+source [find target/stm32f4x_stlink.cfg]
+
+init
+arm semihosting enable
+
+#flash bank sysflash.alias stm32f0x 0x00000000 0 0 0 $_TARGETNAME
diff --git a/fw/pairing.py b/fw/pairing.py
new file mode 100755
index 0000000..ce50081
--- /dev/null
+++ b/fw/pairing.py
@@ -0,0 +1,164 @@
+#!/usr/bin/env python3
+import threading
+import binascii
+import re
+import os
+import time
+
+import serial
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk, Gdk, Pango, GLib
+
+import hexnoise
+
+class PairingWindow(Gtk.Window):
+ def __init__(self, noise, debug=False):
+ Gtk.Window.__init__(self, title='SecureHID pairing')
+ self.noise = noise
+ self.debug = debug
+ self.trusted = False
+
+ self.set_border_width(10)
+ self.set_default_size(600, 200)
+
+ self.vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
+
+ self.label = Gtk.Label()
+ self.label.set_line_wrap(True)
+ self.label.set_justify(Gtk.Justification.CENTER)
+ self.label.set_markup('<b>Step 1</b>\n\nContacting device...')
+ self.vbox.pack_start(self.label, True, True, 0)
+
+ self.entry = Gtk.Entry()
+ self.entry.set_editable(False)
+ self.vbox.pack_start(self.entry, True, True, 0)
+
+ self.confirm_button = Gtk.Button(label='Trust this device')
+ self.confirm_button.connect('clicked', self.confirm_trust)
+ self.confirm_button.set_sensitive(False)
+ self.abort_button = Gtk.Button(label='Abort')
+ self.abort_button.connect('clicked', lambda _foo: self.destroy())
+ self.bbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
+ self.bbox.pack_start(self.confirm_button, True, True, 0)
+ self.bbox.pack_start(self.abort_button, True, True, 0)
+ self.vbox.pack_start(self.bbox, True, True, 0)
+
+ self.add(self.vbox)
+
+ self.handshaker = threading.Thread(target=self.pair, daemon=True)
+ self.handshaker.start()
+
+ def pair(self):
+ for i in range(10):
+ try:
+ self.run_handshake()
+ break
+ except hexnoise.ProtocolError as e:
+ print(e)
+
+ def run_handshake(self):
+ binding_incantation = self.noise.channel_binding_incantation()
+ GLib.idle_add(self.label.set_markup,
+ f'<b>Step 2</b>\n\nPerform channel binding ritual.\n'
+ f'Enter the following incantation, then press enter.\n'
+ f'<b>{binding_incantation}</b>')
+
+ def update_text(text):
+ self.entry.set_text(text)
+ self.entry.set_position(len(text))
+
+ clean = lambda s: re.sub('[^a-z0-9-]', '', s.lower())
+ if clean(binding_incantation).startswith(clean(text)):
+ color = 0.9, 1.0, 0.9 # light red
+ else:
+ color = 1.0, 0.9, 0.9 # light green
+ self.entry.override_background_color(Gtk.StateType.NORMAL, Gdk.RGBA(*color, 1.0))
+
+ try:
+ for user_input in self.noise.pairing_messages():
+ GLib.idle_add(update_text, user_input)
+
+ GLib.idle_add(self.finish_pairing)
+ except hexnoise.ProtocolError as e:
+ GLib.idle_add(self.label.set_markup, f'<b>Error: {e}</b>')
+
+ def finish_pairing(self):
+ self.label.set_markup(f'<b>Step 3</b>\n\nConfirm pairing.\n'
+ f'In case the device did not sound an alarm just now, confirm pairing now using the button below.')
+ self.confirm_button.set_sensitive(True)
+
+ def confirm_trust(self, _foo):
+ self.trusted = True
+ self.destroy()
+
+
+class StatusIcon(Gtk.StatusIcon):
+ def __init__(self):
+ Gtk.StatusIcon.__init__(self)
+ self.set_tooltip_text('SecureHID connected')
+ self.set_from_file('secureusb_icon.png')
+
+def run_pairing_gui(port, baudrate, debug=False):
+ XDG_CONFIG_HOME = os.environ.get('XDG_CONFIG_HOME') or os.path.join(os.path.expandvars('$HOME'), '.config', 'secure_hid')
+ if not os.path.isdir(XDG_CONFIG_HOME):
+ os.mkdir(XDG_CONFIG_HOME)
+
+ private_key_file = os.path.join(XDG_CONFIG_HOME, 'host_key.pem')
+ if not os.path.isfile(private_key_file):
+ with open(private_key_file, 'w') as f:
+ f.write(binascii.hexlify(hexnoise.NoiseEngine.generate_private_key_x25519()).decode())
+
+ known_devices_file = os.path.join(XDG_CONFIG_HOME, 'known_devices')
+ if not os.path.isfile(known_devices_file):
+ with open(known_devices_file, 'w') as f:
+ f.write('# This file contains the hex-encoded SHA-256 fingerprints of the X25519 keys of all trusted SecureHID devices\n')
+
+ with open(private_key_file) as f:
+ host_key_private = binascii.unhexlify(f.read())
+
+ ser = serial.Serial(port, baudrate)
+ packetizer = hexnoise.Packetizer(ser, debug=debug)
+ noise = hexnoise.NoiseEngine(host_key_private, packetizer, debug=debug)
+ noise.perform_handshake()
+ print('Connected.')
+ print('Device fingerprint:', noise.remote_fingerprint)
+
+ if not noise.paired:
+ window = PairingWindow(noise, debug=debug)
+ window.connect('destroy', Gtk.main_quit)
+ window.show_all()
+ Gtk.main()
+
+ if not window.trusted:
+ raise SystemError('User abort')
+
+ if not noise.paired:
+ raise SystemError('Unknown noise error')
+
+ with open(known_devices_file, 'a') as f:
+ f.write(f'{noise.remote_fingerprint} # added {time.ctime()}\n')
+
+ else:
+ with open(known_devices_file) as f:
+ known_devices = [ l.strip().partition('#')[0].strip() for l in f.readlines() if not l[0] == '#' ]
+
+ if noise.remote_fingerprint not in known_devices:
+ raise ValueError('Remote host is untrusted but seems to trust us.')
+
+ input_runner = threading.Thread(target=noise.uinput_passthrough, daemon=True)
+ input_runner.start()
+
+ status_icon = StatusIcon()
+ Gtk.main()
+
+if __name__ == '__main__':
+ import argparse
+ parser = argparse.ArgumentParser()
+ parser.add_argument('serial')
+ parser.add_argument('baudrate')
+ parser.add_argument('-d', '--debug', action='store_true')
+ args = parser.parse_args()
+
+ run_pairing_gui(args.serial, args.baudrate, args.debug)
+
diff --git a/fw/secureusb_icon.png b/fw/secureusb_icon.png
new file mode 100644
index 0000000..97a68cf
--- /dev/null
+++ b/fw/secureusb_icon.png
Binary files differ
diff --git a/fw/src/CMakeLists.txt b/fw/src/CMakeLists.txt
new file mode 100644
index 0000000..f2ae83f
--- /dev/null
+++ b/fw/src/CMakeLists.txt
@@ -0,0 +1,69 @@
+set (inc ${CMAKE_SOURCE_DIR}/include)
+
+add_library (usbhost
+ ${USART_HELPERS}
+ ${inc}/usbh_core.h
+ ${inc}/usbh_driver_ac_midi.h
+ ${inc}/usbh_driver_gp_xbox.h
+ ${inc}/usbh_driver_hid.h
+ ${inc}/usbh_driver_hub.h
+ ${inc}/usbh_lld_stm32f4.h
+ ${inc}/driver/usbh_device_driver.h
+ ${inc}/usbh_config.h
+
+ usbh_core.c
+ usbh_driver_ac_midi.c
+ usbh_driver_ac_midi_private.h
+ usbh_driver_gp_xbox.c
+ usbh_driver_hid.c
+ usbh_driver_hub.c
+ usbh_driver_hub_private.h
+ usbh_lld_stm32f4.c
+ usart_helpers.c
+ tinyprintf.c
+ cobs.c
+ noise.c
+ packet_interface.c
+ words.c
+ hid_keycodes.c
+)
+
+add_subdirectory (crypto)
+
+add_definitions (
+ -DBLAKE2S_USE_VECTOR_MATH=0
+)
+
+target_link_libraries (usbhost
+ noise
+ ${LIBOPENCM3_LIB}
+)
+
+add_executable (demo
+ rand_stm32.c
+ demo.c
+)
+
+target_link_libraries (demo
+ usbhost
+ noise
+)
+
+add_custom_command (TARGET demo
+ POST_BUILD
+ COMMAND ${CMAKE_OBJCOPY} -Oihex $<TARGET_FILE:demo> ${CMAKE_BINARY_DIR}/demo.hex
+ COMMENT "Generating output files: ${CMAKE_BINARY_DIR}/demo.hex"
+)
+
+add_custom_command (TARGET demo
+ POST_BUILD
+ COMMAND ${CMAKE_SIZE} $<TARGET_FILE:demo>
+ COMMENT "Calculating size of the binary"
+)
+
+add_custom_command (TARGET usbhost
+ POST_BUILD
+ COMMENT "Calculating size of the library"
+ COMMAND ${CMAKE_SIZE} $<TARGET_FILE:usbhost>
+)
+
diff --git a/fw/src/cobs.c b/fw/src/cobs.c
new file mode 100644
index 0000000..19dcad2
--- /dev/null
+++ b/fw/src/cobs.c
@@ -0,0 +1,390 @@
+
+#include <cobs.h>
+
+/*@ requires \valid(dst + (0..dstlen-1));
+ @ requires \valid_read(src + (0..srclen-1));
+ @ requires \separated(dst + (0..dstlen-1), src + (0..srclen-1));
+ @
+ @ behavior valid:
+ @ assumes 0 <= srclen <= 254;
+ @ assumes 0 <= dstlen <= 65535;
+ @ assumes dstlen >= srclen+2;
+ @ assigns dst[0..srclen+1];
+ @ ensures \forall integer i; (0 <= i < srclen && \old(src[i]) != 0) ==> dst[i+1] == src[i];
+ @ ensures \result == srclen+2;
+ @ ensures \forall integer i; 0 <= i <= srclen ==> dst[i] != 0;
+ @ ensures dst[srclen+1] == 0;
+ @
+ @ behavior invalid:
+ @ assumes srclen < 0 || srclen > 254
+ @ || dstlen < 0 || dstlen > 65535
+ @ || dstlen < srclen+2;
+ @ assigns \nothing;
+ @ ensures \result == -1;
+ @
+ @ complete behaviors;
+ @ disjoint behaviors;
+ @*/
+ssize_t cobs_encode(char *dst, size_t dstlen, char *src, size_t srclen) {
+ if (dstlen > 65535 || srclen > 254)
+ return -1;
+ //@ assert 0 <= dstlen <= 65535 && 0 <= srclen <= 254;
+
+ if (dstlen < srclen+2)
+ return -1;
+ //@ assert 0 <= srclen < srclen+2 <= dstlen;
+
+ size_t p = 0;
+ /*@ loop invariant 0 <= p <= srclen+1;
+ @ loop invariant \forall integer i; 0 <= i < p ==> dst[i] != 0;
+ @ loop invariant \forall integer i; 0 < i < p ==> (src[i-1] != 0 ==> dst[i] == src[i-1]);
+ @ loop assigns p, dst[0..srclen+1];
+ @ loop variant srclen-p+1;
+ @*/
+ while (p <= srclen) {
+
+ char val;
+ if (p != 0 && src[p-1] != 0) {
+ val = src[p-1];
+
+ } else {
+ size_t q = p;
+ /*@ loop invariant 0 <= p <= q <= srclen;
+ @ loop invariant \forall integer i; p <= i < q ==> src[i] != 0;
+ @ loop assigns q;
+ @ loop variant srclen-q;
+ @*/
+ while (q < srclen && src[q] != 0)
+ q++;
+ //@ assert q == srclen || src[q] == 0;
+ //@ assert q <= srclen <= 254;
+ val = (char)q-p+1;
+ //@ assert val != 0;
+ }
+
+ dst[p] = val;
+ p++;
+ }
+
+ dst[p] = 0;
+ //@ assert p == srclen+1;
+
+ return srclen+2;
+}
+
+/*@ requires \valid_read(src + (0..srclen-1));
+ @*/
+#ifndef VERIFICATION
+int cobs_encode_incremental(void *f, int (*output)(void *f, unsigned char c), unsigned char *src, size_t srclen) {
+#else
+int fc_verification_cobs_encode_incremental(unsigned char *src, size_t srclen) {
+#endif
+ //@ ghost unsigned char output_buf[1000000];
+ if (srclen > 254)
+ return -1;
+ //@ assert 0 <= srclen <= 254;
+
+ size_t p = 0;
+ /*@ loop invariant 0 <= p <= srclen+1;
+ @ loop invariant \forall integer i; 0 <= i < p ==> output_buf[i] != 0;
+ @ loop assigns p, output_buf[0..srclen+1];
+ @ loop variant srclen-p+1;
+ @*/
+ while (p <= srclen) {
+
+ unsigned char val;
+ if (p != 0 && src[p-1] != 0) {
+ val = src[p-1];
+
+ } else {
+ size_t q = p;
+ /*@ loop invariant 0 <= p <= q <= srclen;
+ @ loop invariant \forall integer i; p <= i < q ==> src[i] != 0;
+ @ loop assigns q;
+ @ loop variant srclen-q;
+ @*/
+ while (q < srclen && src[q] != 0)
+ q++;
+ val = (unsigned char)q-p+1;
+ }
+
+ //@ ghost output_buf[p] = val;
+#ifndef VERIFICATION
+ int rv = output(f, val);
+ if (rv)
+ return rv;
+#endif
+ p++;
+ }
+
+ //@ assert frame_size: p == srclen+1;
+ //@ assert synchronization_robustness: \forall integer i; 0 <= i < p ==> output_buf[i] != 0;
+#ifndef VERIFICATION
+ int rv = output(f, 0);
+ if (rv)
+ return rv;
+#endif
+
+ return 0;
+}
+
+/*@ requires \valid(dst + (0..dstlen-1));
+ @ requires \valid_read(src + (0..srclen-1));
+ @ requires \separated(dst + (0..dstlen-1), src + (0..srclen-1));
+ @
+ @ behavior maybe_valid_frame:
+ @ assumes 1 <= srclen <= dstlen <= 65535;
+ @ assumes \exists integer j; j > 0 && \forall integer i; 0 <= i < j ==> src[i] != 0;
+ @ assumes \exists integer i; 0 <= i < srclen && src[i] == 0;
+ @ assigns dst[0..dstlen-1];
+ @ ensures \result >= 0 || \result == -3;
+ @ ensures \result >= 0 ==> src[\result+1] == 0;
+ @ ensures \result >= 0 ==> (\forall integer i; 0 <= i < \result ==> src[i] != 0);
+ @
+ @ behavior invalid_frame:
+ @ assumes 1 <= srclen <= dstlen <= 65535;
+ @ assumes src[0] == 0 || \forall integer i; 0 <= i < srclen ==> src[i] != 0;
+ @ assigns dst[0..dstlen-1];
+ @ ensures \result == -2;
+ @
+ @ behavior invalid_buffers:
+ @ assumes dstlen < 0 || dstlen > 65535
+ @ || srclen < 1 || srclen > 65535
+ @ || dstlen < srclen;
+ @ assigns \nothing;
+ @ ensures \result == -1;
+ @
+ @ complete behaviors;
+ @ disjoint behaviors;
+ @*/
+ssize_t cobs_decode(char *dst, size_t dstlen, char *src, size_t srclen) {
+ if (dstlen > 65535 || srclen > 65535)
+ return -1;
+
+ if (srclen < 1)
+ return -1;
+
+ if (dstlen < srclen)
+ return -1;
+
+ size_t p = 1;
+ size_t c = (unsigned char)src[0];
+ //@ assert 0 <= c < 256;
+ //@ assert 0 <= c;
+ //@ assert c < 256;
+ if (c == 0)
+ return -2; /* invalid framing. An empty frame would be [...] 00 01 00, not [...] 00 00 */
+ //@ assert c >= 0;
+ //@ assert c != 0;
+ //@ assert c <= 257;
+ //@ assert c > 0;
+ //@ assert c >= 0 && c != 0 ==> c > 0;
+
+ /*@ //loop invariant \forall integer i; 0 <= i <= p ==> (i == srclen || src[i] != 0);
+ @ loop invariant \forall integer i; 1 <= i < p ==> src[i] != 0;
+ @ loop invariant c > 0;
+ @ loop invariant 1 <= p <= srclen <= dstlen <= 65535;
+ @ loop invariant \separated(dst + (0..dstlen-1), src + (0..srclen-1));
+ @ loop invariant \valid_read(src + (0..srclen-1));
+ @ loop invariant \forall integer i; 1 <= i <= srclen ==> \valid(dst + i - 1);
+ @ loop assigns dst[0..dstlen-1], p, c;
+ @ loop variant srclen-p;
+ @*/
+ while (p < srclen && src[p]) {
+ char val;
+ c--;
+
+ //@ assert src[p] != 0;
+ if (c == 0) {
+ c = (unsigned char)src[p];
+ val = 0;
+ } else {
+ val = src[p];
+ }
+
+ //@ assert 0 <= p-1 <= dstlen-1;
+ dst[p-1] = val;
+ p++;
+ }
+
+ if (p == srclen)
+ return -2; /* Invalid framing. The terminating null byte should always be present in the input buffer. */
+
+ if (c != 1)
+ return -3; /* Invalid framing. The skip counter does not hit the end of the frame. */
+
+ //@ assert 0 < p <= srclen <= 65535;
+ //@ assert src[p] == 0;
+ //@ assert \forall integer i; 1 <= i < p ==> src[i] != 0;
+ return p-1;
+}
+
+void cobs_decode_incremental_initialize(struct cobs_decode_state *state) {
+ state->p = 0;
+ state->c = 0;
+}
+
+/*@ requires separation: \separated(state, dst + (0..dstlen-1));
+ requires state_valid: \valid(state);
+ requires dst_valid: \valid(dst + (0..dstlen-1));
+ requires dstlen_bounds: 0 < dstlen <= INT_MAX;
+ requires p_valid: 0 <= state->p <= dstlen+1;
+ requires c_valid: state->p != 0 ==> 1 <= state->c <= 255;
+
+ // Sanity properties
+ ensures c_valid: state->p != 0 ==> 1 <= state->c <= 255;
+ ensures buffer_bounds: 0 <= state->p <= dstlen+1;
+ ensures return_value: 0 <= \result <= dstlen || \result \in {-1, -2, -3, -4};
+ ensures reset_on_error: \result < -1 ==> state->p == 0 && state->c == 0;
+
+ // Synchronization properties
+ ensures self_synchronization: src == 0 ==> \result >= 0 || \result \in {-2, -3};
+ ensures synchronization_robustness: src != 0 ==> \result \in {-1, -4};
+
+ // Basic functional sanity
+ ensures legal_advance: \result != -1 <==> state->p \in {0, \old(state->p)};
+ ensures incremental_advance: \result == -1 <==> state->p == \old(state->p) + 1;
+ ensures nonzero_unmodified: \result == -1 && state->p > 1 ==> dst[state->p-2] \in {0, src};
+
+ assigns *state, dst[0..dstlen-1];
+
+ behavior eof_bad_empty_frame:
+ assumes state->p == 0 && src == 0;
+ ensures \result == -3;
+ assigns *state;
+
+ behavior eof_maybe_good_frame:
+ assumes state->p != 0 && src == 0;
+ ensures \result >= 0 || \result == -2;
+ assigns *state;
+
+ behavior decoding_no_overflow:
+ assumes src != 0 && state->p <= dstlen;
+ ensures \result == -1;
+ assigns *state, dst[0..dstlen-1];
+
+ behavior decoding_overflow:
+ assumes src != 0 && state->p > dstlen;
+ ensures \result == -4;
+ assigns *state;
+
+ complete behaviors;
+ disjoint behaviors;
+ @*/
+int cobs_decode_incremental(struct cobs_decode_state *state, unsigned char *dst, size_t dstlen, unsigned char src) {
+ if (state->p == 0) {
+ if (src == 0) {
+ /* invalid framing. An empty frame would be [...] 00 01 00, not [...] 00 00 */
+ cobs_decode_incremental_initialize(state);
+ return -3;
+ }
+ state->c = src;
+ state->p++;
+ return -1;
+ }
+
+ if (!src) {
+ if (state->c != 1) {
+ /* invalid framing. The skip counter does not hit the end of the frame. */
+ cobs_decode_incremental_initialize(state);
+ return -2;
+ }
+ size_t rv = state->p-1;
+ cobs_decode_incremental_initialize(state);
+ return rv;
+ }
+
+ unsigned char val;
+ state->c--;
+
+ if (state->c == 0) {
+ state->c = src;
+ val = 0;
+ } else {
+ val = src;
+ }
+
+ size_t pos = state->p-1;
+ if (pos >= dstlen) {
+ cobs_decode_incremental_initialize(state);
+ return -4; /* output buffer too small */
+ }
+ dst[pos] = val;
+ state->p++;
+ return -1;
+}
+
+/*@ requires \valid_read(buf + (0..len-1));
+ assigns \nothing;
+ @*/
+void handle_packet(unsigned char *buf, size_t len);
+
+/*@ requires \valid(dst + (0..dstlen-1));
+ requires \valid_read(src + (0..srclen-1));
+ requires 0 < dstlen <= 65535;
+ assigns dst[0..dstlen-1];
+ @*/
+void cobs_decode_test(unsigned char *src, size_t srclen, unsigned char *dst, size_t dstlen) {
+ struct cobs_decode_state state;
+ cobs_decode_incremental_initialize(&state);
+ //@ assert state.p == 0;
+ //@ assert state.c == 0;
+ //@ assert state.p != 0 ==> 1 <= state.c <= 255;
+ /*@ loop invariant \separated(&state, dst + (0..dstlen-1), &i);
+ loop invariant \valid(&state);
+ loop invariant \valid(dst + (0..dstlen-1));
+ loop invariant 0 < dstlen <= 65535;
+ loop invariant 0 <= state.p <= dstlen+1;
+ loop invariant state.p != 0 ==> 1 <= state.c <= 255;
+ loop assigns dst[0..dstlen-1], state, i;
+ loop variant srclen-i;
+ @*/
+ for (size_t i=0; i<srclen; i++) {
+ int rv = cobs_decode_incremental(&state, dst, dstlen, src[i]);
+ if (rv >= 0)
+ handle_packet(dst, rv);
+ if (rv == -1)
+ continue;
+ if (rv < -1)
+ continue;
+ }
+}
+
+#ifdef VALIDATION
+/*@
+ @ requires 0 <= d < 256;
+ @ assigns \nothing;
+ @*/
+size_t test(char foo, unsigned int d) {
+ unsigned int c = (unsigned char)foo;
+ if (c != 0) {
+ //@ assert c < 256;
+ //@ assert c >= 0;
+ //@ assert c != 0;
+ //@ assert c > 0;
+ }
+ if (d != 0) {
+ //@ assert d >= 0;
+ //@ assert d != 0;
+ //@ assert d > 0;
+ }
+ return c + d;
+}
+
+#include <__fc_builtin.h>
+
+void main(void) {
+ char inbuf[254];
+ char cobsbuf[256];
+ char outbuf[256];
+
+ size_t range = Frama_C_interval(0, sizeof(inbuf));
+ Frama_C_make_unknown((char *)inbuf, range);
+
+ cobs_encode(cobsbuf, sizeof(cobsbuf), inbuf, sizeof(inbuf));
+ cobs_decode(outbuf, sizeof(outbuf), cobsbuf, sizeof(cobsbuf));
+
+ //@ assert \forall integer i; 0 <= i < sizeof(inbuf) ==> outbuf[i] == inbuf[i];
+}
+#endif//VALIDATION
+
diff --git a/fw/src/crypto/CMakeLists.txt b/fw/src/crypto/CMakeLists.txt
new file mode 100644
index 0000000..b21dc2b
--- /dev/null
+++ b/fw/src/crypto/CMakeLists.txt
@@ -0,0 +1,93 @@
+
+include_directories (
+ noise-c/include
+ noise-c/include/noise/keys
+ noise-c/src
+ noise-c/src/crypto/goldilocks/include
+ noise-c/src/crypto/goldilocks/src/include
+ noise-c/src/crypto/goldilocks/src/p448/arch_arm_32
+ noise-c/src/crypto/goldilocks/src/p448
+ noise-c/src/protocol
+)
+
+add_library (noise
+ noise-c/src/protocol/util.c
+ noise-c/src/protocol/patterns.c
+ noise-c/src/protocol/signstate.c
+ noise-c/src/protocol/randstate.c
+ noise-c/src/protocol/symmetricstate.c
+ noise-c/src/protocol/internal.c
+ noise-c/src/protocol/names.c
+ noise-c/src/protocol/hashstate.c
+ noise-c/src/protocol/errors.c
+ noise-c/src/protocol/cipherstate.c
+ noise-c/src/protocol/handshakestate.c
+ noise-c/src/protocol/dhstate.c
+ noise-c/src/keys/certificate.c
+ noise-c/src/keys/loader.c
+ noise-c/src/crypto/sha2/sha256.c
+ noise-c/src/crypto/sha2/sha512.c
+ noise-c/src/crypto/ghash/ghash.c
+ noise-c/src/crypto/ed25519/ed25519.c
+ noise-c/src/crypto/blake2/blake2s.c
+ noise-c/src/crypto/blake2/blake2b.c
+ noise-c/src/crypto/chacha/chacha.c
+ noise-c/src/crypto/goldilocks/src/ec_point.c
+ noise-c/src/crypto/goldilocks/src/sha512.c
+ noise-c/src/crypto/goldilocks/src/p448/arch_32/p448.c
+ noise-c/src/crypto/goldilocks/src/p448/f_arithmetic.c
+ noise-c/src/crypto/goldilocks/src/p448/arch_arm_32/p448.c
+ noise-c/src/crypto/goldilocks/src/p448/magic.c
+ noise-c/src/crypto/goldilocks/src/barrett_field.c
+ noise-c/src/crypto/goldilocks/src/goldilocks.c
+ noise-c/src/crypto/goldilocks/src/arithmetic.c
+ noise-c/src/crypto/goldilocks/src/crandom.c
+ noise-c/src/crypto/goldilocks/src/scalarmul.c
+ noise-c/src/crypto/newhope/poly.c
+ noise-c/src/crypto/newhope/randombytes.c
+ noise-c/src/crypto/newhope/reduce.c
+ noise-c/src/crypto/newhope/ntt.c
+ noise-c/src/crypto/newhope/crypto_stream_chacha20.c
+ noise-c/src/crypto/newhope/error_correction.c
+ noise-c/src/crypto/newhope/batcher.c
+ noise-c/src/crypto/newhope/fips202.c
+ noise-c/src/crypto/newhope/newhope.c
+ noise-c/src/crypto/newhope/precomp.c
+ noise-c/src/crypto/aes/rijndael-alg-fst.c
+ noise-c/src/crypto/curve448/curve448.c
+ noise-c/src/crypto/donna/poly1305-donna.c
+ noise-c/src/crypto/donna/curve25519-donna.c
+ noise-c/src/protobufs/protobufs.c
+ noise-c/src/backend/ref/sign-ed25519.c
+ noise-c/src/backend/ref/hash-blake2b.c
+ noise-c/src/backend/ref/hash-sha512.c
+ noise-c/src/backend/ref/hash-sha256.c
+ noise-c/src/backend/ref/cipher-aesgcm.c
+ noise-c/src/backend/ref/cipher-chachapoly.c
+ noise-c/src/backend/ref/dh-curve25519.c
+ noise-c/src/backend/ref/dh-newhope.c
+ noise-c/src/backend/ref/dh-curve448.c
+ noise-c/src/backend/ref/hash-blake2s.c
+)
+
+add_definitions (
+ -DUSE_LIBSODIUM=0
+ -DUSE_SODIUM=0
+ -DHAVE_PTHREAD=0
+ -DUSE_OPENSSL=0
+ -D__WORDSIZE=32
+ -D__BIG_ENDIAN=4321
+ -D__LITTLE_ENDIAN=1234
+ -D__BYTE_ORDER=__LITTLE_ENDIAN
+ -DED25519_CUSTOMRANDOM=1
+ -DED25519_CUSTOMHASH=1
+ -DED25519_REFHASH=1
+ -DBLAKE2S_USE_VECTOR_MATH=0
+ -DEXPERIMENT_CRANDOM_CUTOFF_BYTES=0
+ -D__clang__=0
+)
+
+set (CMAKE_C_FLAGS
+ "${CMAKE_C_FLAGS} -Wno-implicit-fallthrough -Wno-shadow -Wno-unused-parameter"
+)
+
diff --git a/fw/src/demo.c b/fw/src/demo.c
new file mode 100644
index 0000000..4d6a040
--- /dev/null
+++ b/fw/src/demo.c
@@ -0,0 +1,684 @@
+/*
+ * This file is part of the libusbhost library
+ * hosted at http://github.com/libusbhost/libusbhost
+ *
+ * Copyright (C) 2015 Amir Hammad <amir.hammad@hotmail.com>
+ *
+ *
+ * libusbhost is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <libopencm3/stm32/rcc.h>
+#include <libopencm3/stm32/gpio.h>
+#include <libopencm3/stm32/usart.h>
+#include <libopencm3/stm32/timer.h>
+#include <libopencm3/stm32/otg_hs.h>
+#include <libopencm3/stm32/otg_fs.h>
+#include <libopencm3/stm32/pwr.h>
+#include <libopencm3/stm32/dma.h>
+#include <libopencm3/cm3/nvic.h>
+#include <libopencmsis/core_cm3.h>
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "usart_helpers.h"
+#include "usbh_core.h"
+#include "usbh_lld_stm32f4.h"
+#include "usbh_driver_hid.h"
+#include "usbh_driver_hub.h"
+#include "rand_stm32.h"
+#include "packet_interface.h"
+#include "noise.h"
+#include "hid_keycodes.h"
+#include "words.h"
+#include "tracing.h"
+
+#include "crypto/noise-c/src/protocol/internal.h"
+
+#ifndef USE_STM32F4_USBH_DRIVER_FS
+#error The full-speed USB driver must be enabled with USE_STM32F4_USBH_DRIVER_FS in usbh_config.h!
+#endif
+
+#ifndef MAX_FAILED_HANDSHAKES
+#define MAX_FAILED_HANDSHAKES 5
+#endif
+
+static struct NoiseState noise_state;
+static struct {
+ union {
+ struct {
+ uint8_t local_key[CURVE25519_KEY_LEN];
+ uint8_t remote_key_reference[BLAKE2S_HASH_SIZE];
+ };
+ uint32_t all_keys[0];
+ } keys;
+ struct {
+ uint8_t identity_key_valid;
+ uint8_t scrub_backup;
+ uint8_t scrubber_armed;
+ uint32_t old_scrub_pattern;
+ uint32_t new_scrub_pattern;
+ int scrub_idx_read;
+ int scrub_idx_done;
+ } mgmt __attribute__((aligned(4)));
+} keystore __attribute__((section(".backup_sram"))) = {0};
+
+
+void _fini(void);
+
+static inline void delay(uint32_t n) {
+ for (volatile uint32_t i = 0; i < 1490*n; i++);
+}
+
+
+/* Set STM32 to 168 MHz. */
+static void clock_setup(void) {
+ rcc_clock_setup_hse_3v3(&hse_8mhz_3v3[CLOCK_3V3_168MHZ]);
+
+ rcc_periph_clock_enable(RCC_GPIOA);
+ rcc_periph_clock_enable(RCC_GPIOB);
+ rcc_periph_clock_enable(RCC_GPIOD);
+ rcc_periph_clock_enable(RCC_GPIOE);
+
+ rcc_periph_clock_enable(RCC_USART1);
+ rcc_periph_clock_enable(RCC_USART2);
+ rcc_periph_clock_enable(RCC_OTGFS);
+ rcc_periph_clock_enable(RCC_TIM6);
+ rcc_periph_clock_enable(RCC_DMA2);
+ rcc_periph_clock_enable(RCC_DMA1);
+
+ rcc_periph_clock_enable(RCC_PWR);
+ rcc_periph_clock_enable(RCC_BKPSRAM);
+
+ rcc_periph_clock_enable(RCC_RNG);
+}
+
+void arm_key_scrubber() {
+ keystore.mgmt.scrubber_armed = 1;
+}
+
+static void finish_scrub(int start_index, uint32_t pattern);
+static void finish_interrupted_scrub(void);
+
+void disarm_key_scrubber() {
+ keystore.mgmt.scrubber_armed = 0;
+ keystore.mgmt.old_scrub_pattern = keystore.mgmt.new_scrub_pattern;
+ keystore.mgmt.new_scrub_pattern = 0x00000000;
+ finish_scrub(0, keystore.mgmt.old_scrub_pattern);
+}
+
+static void finish_scrub(int start_index, uint32_t pattern) {
+ for (size_t i=start_index; i<sizeof(keystore.keys)/sizeof(keystore.keys.all_keys[0]); i++) {
+ keystore.mgmt.scrub_backup = keystore.keys.all_keys[i];
+ keystore.mgmt.scrub_idx_read = i;
+ keystore.keys.all_keys[i] ^= pattern;
+ keystore.mgmt.scrub_idx_done = i;
+ }
+}
+
+static void finish_interrupted_scrub(void) {
+ if (keystore.mgmt.scrub_idx_read != keystore.mgmt.scrub_idx_done)
+ keystore.keys.all_keys[keystore.mgmt.scrub_idx_read] = keystore.mgmt.scrub_backup;
+
+ finish_scrub(keystore.mgmt.scrub_idx_done, keystore.mgmt.old_scrub_pattern ^ keystore.mgmt.new_scrub_pattern);
+}
+
+/* setup 10kHz timer */
+static void tim6_setup(void) {
+ timer_reset(TIM6);
+ timer_set_prescaler(TIM6, 8400 - 1); // 84Mhz/10kHz - 1
+ timer_set_period(TIM6, 65535); // Overflow in ~6.5 seconds
+ timer_enable_irq(TIM6, TIM_DIER_UIE);
+ nvic_enable_irq(NVIC_TIM6_DAC_IRQ);
+ nvic_set_priority(NVIC_TIM6_DAC_IRQ, 15<<4); /* really low priority */
+ timer_enable_counter(TIM6);
+}
+
+void tim6_dac_isr(void) {
+ /* Runs every ~6.5s on timer overrun */
+ timer_clear_flag(TIM6, TIM_SR_UIF);
+
+ if (!keystore.mgmt.scrubber_armed)
+ return;
+
+ keystore.mgmt.old_scrub_pattern = keystore.mgmt.new_scrub_pattern;
+ noise_rand_bytes(&keystore.mgmt.new_scrub_pattern, sizeof(keystore.mgmt.new_scrub_pattern));
+ LOG_PRINTF("Scrubbing keys using pattern %08x\n", keystore.mgmt.new_scrub_pattern);
+ finish_scrub(0, keystore.mgmt.old_scrub_pattern ^ keystore.mgmt.new_scrub_pattern);
+}
+
+static uint32_t tim6_get_time_us(void)
+{
+ uint32_t cnt = timer_get_counter(TIM6);
+
+ // convert to 1MHz less precise timer value -> units: microseconds
+ uint32_t time_us = cnt * 100;
+
+ return time_us;
+}
+
+static void gpio_setup(void)
+{
+ /* Tracing */
+ gpio_mode_setup(GPIOD, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, 0xffff);
+
+ /* D2, D3 LEDs */
+ //gpio_mode_setup(GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO6 | GPIO7);
+ //gpio_set(GPIOA, GPIO6 | GPIO7);
+
+ /* Status LEDs (PE4-15) */
+ gpio_mode_setup(GPIOE, GPIO_MODE_INPUT, GPIO_PUPD_NONE, 0xfff0);
+
+ /* Alarm LEDs (PA6,7) */
+ gpio_mode_setup(GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO6 | GPIO7);
+ gpio_set(GPIOA, GPIO6 | GPIO7);
+
+ /* Speaker */
+ gpio_mode_setup(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO10);
+ gpio_set(GPIOB, GPIO10);
+
+ /* USB OTG FS phy outputs */
+ gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO11 | GPIO12);
+ gpio_set_af(GPIOA, GPIO_AF10, GPIO11 | GPIO12);
+
+ /* USART1 (debug) */
+ gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO9 | GPIO10);
+ gpio_set_af(GPIOA, GPIO_AF7, GPIO9 | GPIO10);
+
+ /* USART2 (host link) */
+ gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO2 | GPIO3);
+ gpio_set_af(GPIOA, GPIO_AF7, GPIO2 | GPIO3);
+
+ /* K0 (PE4)/K1 (PE3) buttons */
+ //gpio_mode_setup(GPIOE, GPIO_MODE_INPUT, GPIO_PUPD_PULLUP, GPIO3 | GPIO4);
+}
+
+struct hid_report {
+ uint8_t modifiers;
+ uint8_t _reserved;
+ uint8_t keycodes[6];
+} __attribute__((__packed__));
+
+static char pairing_buf[512];
+static size_t pairing_buf_pos = 0;
+
+int pairing_check(struct NoiseState *st, const char *buf);
+void pairing_input(uint8_t modbyte, uint8_t keycode);
+void pairing_parse_report(struct hid_report *buf, uint8_t len);
+
+/* Minimum number of bytes of handshake hash to confirm during pairing */
+#define MIN_PAIRING_SEQUENCE_LENGTH 8
+
+int pairing_check(struct NoiseState *st, const char *buf) {
+ //LOG_PRINTF("Checking pairing\n");
+ const char *p = buf;
+ int idx = 0;
+ do {
+ /* Skip over most special chars */
+ while (*p) {
+ char c = *p;
+ if ('0' <= c && c <= '9') break;
+ if ('a' <= c && c <= 'z') break;
+ if ('A' <= c && c <= 'Z') break;
+ if (c == '-') break;
+ p++;
+ }
+
+ const char *found = strchr(p, ' ');
+ size_t plen = found ? (size_t)(found - p) : strlen(p); /* p >= found */
+
+ while (plen > 0) {
+ char c = p[plen];
+ if ('0' <= c && c <= '9') break;
+ if ('a' <= c && c <= 'z') break;
+ if ('A' <= c && c <= 'Z') break;
+ if (c == '-') break;
+ plen--;
+ }
+ plen++;
+ //LOG_PRINTF("matching: \"%s\" - \"%s\" %d\n", p, p+plen, plen);
+
+ if (strncasecmp(p, "and", plen)) { /* ignore "and" */
+ int num = -1;
+ for (int i=0; i<256; i++) {
+ if ((!strncasecmp(p, even[i], plen) && plen == strlen(even[i]))
+ || (!strncasecmp(p, odd[i], plen) && plen == strlen(odd[i] ))) {
+ //LOG_PRINTF(" idx=%02d h=%02x i=%02x adj=%s n=%s plen=%d s=%s\n", idx, st->handshake_hash[idx], i, adjectives[i], nouns[i], plen, p);
+ num = i;
+ break;
+ }
+ }
+ if (num == -1) {
+ LOG_PRINTF("Pairing word \"%s\" not found in dictionary\n", p);
+ return -1;
+ }
+ if (st->handshake_hash[idx] != num) {
+ LOG_PRINTF("Pairing data does not match hash\n");
+ return -1;
+ }
+ idx++;
+ }
+
+ p = strchr(p, ' ');
+ if (!p)
+ break; /* end of string */
+ p++; /* skip space */
+ } while (idx < BLAKE2S_HASH_SIZE);
+
+ if (idx < MIN_PAIRING_SEQUENCE_LENGTH) {
+ LOG_PRINTF("Pairing sequence too short, only %d bytes of hash checked\n", idx);
+ return -1;
+ }
+
+ LOG_PRINTF("Pairing sequence match\n");
+ return 0;
+}
+
+void pairing_input(uint8_t modbyte, uint8_t keycode) {
+ char ch = 0;
+ uint8_t level = modbyte & MOD_XSHIFT ? LEVEL_SHIFT : LEVEL_NONE;
+ switch (keycode) {
+ case KEY_ENTER:
+ pairing_buf[pairing_buf_pos++] = '\0';
+ if (!pairing_check(&noise_state, pairing_buf)) {
+ persist_remote_key(&noise_state);
+ /* FIXME write key to backup memory */
+
+ uint8_t response = REPORT_PAIRING_SUCCESS;
+ if (send_encrypted_message(&noise_state, &response, sizeof(response)))
+ LOG_PRINTF("Error sending pairing response packet\n");
+
+ } else {
+ /* FIXME sound alarm */
+
+ pairing_buf_pos = 0; /* Reset input buffer */
+ uint8_t response = REPORT_PAIRING_ERROR;
+ if (send_encrypted_message(&noise_state, &response, sizeof(response)))
+ LOG_PRINTF("Error sending pairing response packet\n");
+ }
+ break;
+
+ case KEY_BACKSPACE:
+ if (pairing_buf_pos > 0)
+ pairing_buf_pos--;
+ pairing_buf[pairing_buf_pos] = '\0';
+ ch = '\b';
+ break;
+
+ default:
+ for (size_t i=0; keycode_mapping[i].kc != KEY_NONE; i++) {
+ if (keycode_mapping[i].kc == keycode) {
+ ch = keycode_mapping[i].ch[level];
+ if (!ch)
+ break;
+
+ if (pairing_buf_pos < sizeof(pairing_buf)-1) /* allow for terminating null byte */ {
+ pairing_buf[pairing_buf_pos++] = ch;
+ pairing_buf[pairing_buf_pos] = '\0';
+ } else {
+ LOG_PRINTF("Pairing confirmation user input buffer full\n");
+
+ uint8_t response = REPORT_PAIRING_ERROR;
+ if (send_encrypted_message(&noise_state, &response, sizeof(response)))
+ LOG_PRINTF("Error sending pairing response packet\n");
+ }
+ break;
+ }
+ }
+ break;
+ }
+
+ if (ch) {
+ //LOG_PRINTF("Input: %s\n", pairing_buf);
+ struct hid_report_packet pkt = {
+ .type = REPORT_PAIRING_INPUT,
+ .pairing_input = { .c = ch }
+ };
+ if (send_encrypted_message(&noise_state, (uint8_t *)&pkt, sizeof(pkt))) {
+ LOG_PRINTF("Error sending pairing input packet\n");
+ return;
+ }
+ }
+}
+
+void pairing_parse_report(struct hid_report *buf, uint8_t len) {
+ static uint8_t old_keycodes[6] = {0};
+
+ for (int i=0; i<len-2; i++) {
+ if (!buf->keycodes[i])
+ break; /* keycodes are always populated from low to high */
+
+ int found = 0;
+ for (int j=0; j<6; j++) {
+ if (old_keycodes[j] == buf->keycodes[i]) {
+ found = 1;
+ break;
+ }
+ }
+ if (!found) /* key pressed */
+ pairing_input(buf->modifiers, buf->keycodes[i]);
+ }
+
+ memcpy(old_keycodes, buf->keycodes, 6);
+}
+
+static void hid_in_message_handler(uint8_t device_id, const uint8_t *data, uint32_t length) {
+ TRACING_SET(TR_HID_MESSAGE_HANDLER);
+ if (length < 4 || length > 8) {
+ LOG_PRINTF("HID report length must be 4 < len < 8, is %d bytes\n", length);
+ TRACING_CLEAR(TR_HID_MESSAGE_HANDLER);
+ return;
+ }
+
+ //LOG_PRINTF("Sending event %02X %02X %02X %02X\n", data[0], data[1], data[2], data[3]);
+ int type = hid_get_type(device_id);
+ if (type != HID_TYPE_KEYBOARD && type != HID_TYPE_MOUSE) {
+ LOG_PRINTF("Unsupported HID report type %x\n", type);
+ TRACING_CLEAR(TR_HID_MESSAGE_HANDLER);
+ return;
+ }
+
+ if (noise_state.handshake_state == HANDSHAKE_DONE_UNKNOWN_HOST) {
+ if (type == HID_TYPE_KEYBOARD)
+ pairing_parse_report((struct hid_report *)data, length);
+ else
+ LOG_PRINTF("Not sending HID mouse report during pairing\n");
+ TRACING_CLEAR(TR_HID_MESSAGE_HANDLER);
+ return;
+ }
+
+ struct hid_report_packet pkt = {
+ .type = type == HID_TYPE_KEYBOARD ? REPORT_KEYBOARD : REPORT_MOUSE,
+ .report = {
+ .len = length,
+ .report = {0}
+ }
+ };
+ memcpy(pkt.report.report, data, length);
+
+ if (send_encrypted_message(&noise_state, (uint8_t *)&pkt, sizeof(pkt))) {
+ LOG_PRINTF("Error sending HID report packet\n");
+ TRACING_CLEAR(TR_HID_MESSAGE_HANDLER);
+ return;
+ }
+ TRACING_CLEAR(TR_HID_MESSAGE_HANDLER);
+}
+
+volatile struct {
+ struct dma_buf dma;
+ uint8_t data[256];
+} debug_buf = { .dma = { .len = sizeof(debug_buf.data) } };
+
+struct dma_usart_file debug_out_s = {
+ .usart = DEBUG_USART,
+ .baudrate = DEBUG_USART_BAUDRATE,
+ .dma = DMA(DEBUG_USART_DMA_NUM),
+ .stream = DEBUG_USART_DMA_STREAM_NUM,
+ .channel = DEBUG_USART_DMA_CHANNEL_NUM,
+ .irqn = NVIC_DMA_IRQ(DEBUG_USART_DMA_NUM, DEBUG_USART_DMA_STREAM_NUM),
+ .buf = &debug_buf.dma
+};
+struct dma_usart_file *debug_out = &debug_out_s;
+
+/* FIXME start unsafe debug code */
+void usart1_isr(void) {
+ if (USART1_SR & USART_SR_ORE) { /* Overrun handling */
+ LOG_PRINTF("USART1 data register overrun\n");
+ /* Clear interrupt flag */
+ int dummy = USART1_DR;
+ return;
+ }
+
+ uint8_t data = USART1_DR; /* This automatically acknowledges the IRQ */
+ for (size_t i=0; keycode_mapping[i].kc != KEY_NONE; i++) {
+ struct hid_report report = {0};
+ if (keycode_mapping[i].ch[0] == data)
+ report.modifiers = 0;
+ else if (keycode_mapping[i].ch[1] == data)
+ report.modifiers = MOD_LSHIFT;
+ else continue;
+
+ report.keycodes[0] = keycode_mapping[i].kc;
+ pairing_parse_report(&report, 8);
+ break;
+ }
+ LOG_PRINTF(" %02x ", data);
+ if (data == 0x7f) {
+ struct hid_report report = {.modifiers=0, .keycodes={KEY_BACKSPACE, 0}};
+ pairing_parse_report(&report, 8);
+ } else if (data == '\r') {
+ struct hid_report report = {.modifiers=0, .keycodes={KEY_ENTER, 0}};
+ pairing_parse_report(&report, 8);
+ LOG_PRINTF("\n");
+ }
+
+ struct hid_report report = {0};
+ pairing_parse_report(&report, 8);
+}
+/* end unsafe debug code */
+
+void DMA_ISR(DEBUG_USART_DMA_NUM, DEBUG_USART_DMA_STREAM_NUM)(void) {
+ TRACING_SET(TR_DEBUG_OUT_DMA_IRQ);
+ if (dma_get_interrupt_flag(debug_out->dma, debug_out->stream, DMA_FEIF)) {
+ /* Ignore FIFO errors as they're 100% non-critical for UART applications */
+ dma_clear_interrupt_flags(debug_out->dma, debug_out->stream, DMA_FEIF);
+ TRACING_CLEAR(TR_DEBUG_OUT_DMA_IRQ);
+ return;
+ }
+
+ /* Transfer complete */
+ dma_clear_interrupt_flags(debug_out->dma, debug_out->stream, DMA_TCIF);
+
+ if (debug_out->buf->wr_pos != debug_out->buf->xfr_end) /* buffer not empty */
+ schedule_dma(debug_out);
+ TRACING_CLEAR(TR_DEBUG_OUT_DMA_IRQ);
+}
+
+/*@ requires \valid_read(&pkt->type) && \valid_read(pkt->payload + (0..payload_length-1));
+ requires \valid(st);
+ requires \valid(st->handshake);
+ requires \separated(st, st->rx_cipher, st->tx_cipher, st->handshake, (uint8_t *)pkt->payload, &usart2_out, &st->handshake_hash);
+ requires \valid(usart2_out);
+
+ assigns pairing_buf_pos, *usart2_out, *st;
+
+ assigns st->handshake, st->handshake_state, st->rx_cipher, st->tx_cipher;
+ @*/
+void handle_host_packet(struct NoiseState *st, const struct control_packet *pkt, size_t payload_length) {
+ TRACING_SET(TR_HOST_PKT_HANDLER);
+ if (pkt->type == HOST_INITIATE_HANDSHAKE) {
+ /* It is important that we acknowledge this command right away. Starting the handshake involves key
+ * generation which takes a few milliseconds. If we'd acknowledge this later, we might run into an
+ * overrun here since we would be blocking the buffer during key generation. */
+
+ if (payload_length > 0) {
+ LOG_PRINTF("Extraneous data in INITIATE_HANDSHAKE message\n");
+ } else if (st->failed_handshakes < MAX_FAILED_HANDSHAKES) {
+ LOG_PRINTF("Starting noise protocol handshake...\n");
+ if (reset_protocol_handshake(st))
+ LOG_PRINTF("Error starting protocol handshake.\n");
+ pairing_buf_pos = 0; /* Reset channel binding keyboard input buffer */
+ } else {
+ LOG_PRINTF("Too many failed handshake attempts, not starting another one\n");
+ struct control_packet out = { .type=HOST_TOO_MANY_FAILS };
+ send_packet(usart2_out, (uint8_t *)&out, sizeof(out));
+ }
+
+ } else if (pkt->type == HOST_HANDSHAKE) {
+ LOG_PRINTF("Handling handshake packet of length %d\n", payload_length);
+ TRACING_SET(TR_NOISE_HANDSHAKE);
+ if (try_continue_noise_handshake(st, pkt->payload, payload_length)) {
+ TRACING_CLEAR(TR_NOISE_HANDSHAKE);
+ LOG_PRINTF("Reporting handshake error to host\n");
+ struct control_packet out = { .type=HOST_CRYPTO_ERROR };
+ send_packet(usart2_out, (uint8_t *)&out, sizeof(out));
+ } else TRACING_CLEAR(TR_NOISE_HANDSHAKE);
+
+ } else {
+ LOG_PRINTF("Unhandled packet of type %d\n", pkt->type);
+ }
+ TRACING_CLEAR(TR_HOST_PKT_HANDLER);
+}
+
+
+int main(void)
+{
+ clock_setup();
+ gpio_setup();
+ pwr_disable_backup_domain_write_protect();
+ PWR_CSR |= PWR_CSR_BRE; /* Enable backup SRAM battery power regulator */
+
+ finish_interrupted_scrub();
+ disarm_key_scrubber();
+ tim6_setup();
+
+#ifdef USART_DEBUG
+ usart_dma_init(debug_out);
+ /* FIXME start unsafe debug code */
+ usart_enable_rx_interrupt(debug_out->usart);
+ nvic_enable_irq(NVIC_USART1_IRQ);
+ nvic_set_priority(NVIC_USART1_IRQ, 3<<4);
+ /* end unsafe debug code */
+#endif
+
+ usart_dma_init(usart2_out);
+ usart_enable_rx_interrupt(USART2);
+ nvic_enable_irq(NVIC_USART2_IRQ);
+ nvic_set_priority(NVIC_USART2_IRQ, 3<<4);
+ nvic_set_priority(debug_out_s.irqn, 1<<4);
+
+ LOG_PRINTF("\n==================================\n");
+ LOG_PRINTF("SecureHID device side initializing\n");
+ LOG_PRINTF("==================================\n");
+
+ LOG_PRINTF("Initializing USB...\n");
+ const hid_config_t hid_config = { .hid_in_message_handler = &hid_in_message_handler };
+ hid_driver_init(&hid_config);
+ hub_driver_init();
+ const usbh_dev_driver_t *device_drivers[] = { &usbh_hub_driver, &usbh_hid_driver, NULL };
+ const usbh_low_level_driver_t * const lld_drivers[] = { &usbh_lld_stm32f4_driver_fs, NULL };
+ usbh_init(lld_drivers, device_drivers);
+
+ LOG_PRINTF("Initializing RNG...\n");
+ rand_init();
+
+ //@ assert \valid(&noise_state);
+ //@ assert \valid((uint8_t *)keystore.keys.remote_key_reference + (0..31)) && \valid((uint8_t *)keystore.keys.local_key + (0..31));
+ noise_state_init(&noise_state, keystore.keys.remote_key_reference, keystore.keys.local_key);
+
+ //@ assert \valid(noise_state.local_key + (0..31));
+ /* FIXME load remote key from backup memory */
+ /* FIXME only run this on first boot and persist key in backup sram. Allow reset via jumper-triggered factory reset function. */
+ if (!keystore.mgmt.identity_key_valid) {
+ LOG_PRINTF("Generating identity key...\n");
+ if (generate_identity_key(&noise_state)) {
+ LOG_PRINTF("Error generating identiy key\n");
+ } else {
+ keystore.mgmt.identity_key_valid = 1;
+ }
+ }
+
+ int poll_ctr = 0;
+ int led_ctr = 0;
+ int led_idx = 0;
+ int spk_ctr = 0;
+ int spk_ctr2 = 0;
+ int spk_adv = 0;
+ int spk_inc = 1;
+ gpio_clear(GPIOA, GPIO6);
+ gpio_clear(GPIOA, GPIO7);
+ gpio_clear(GPIOB, GPIO10);
+ while (23) {
+ delay(1);
+
+ led_ctr++;
+ if (led_ctr == 10) {
+ gpio_clear(GPIOA, GPIO6);
+ gpio_clear(GPIOA, GPIO7);
+ } else if (led_ctr == 300) {
+ gpio_mode_setup(GPIOE, GPIO_MODE_INPUT, GPIO_PUPD_NONE, 0xfff0);
+ } else if (led_ctr == 400) {
+ if (++led_idx == 12)
+ led_idx = 0;
+ gpio_mode_setup(GPIOE, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, 1<<(4+led_idx));
+ gpio_clear(GPIOE, 0xfff0);
+ if (led_idx & 1)
+ gpio_set(GPIOA, GPIO6);
+ else
+ gpio_set(GPIOA, GPIO7);
+ led_ctr = 0;
+ }
+
+ spk_ctr++;
+ spk_ctr2++;
+ if (spk_ctr2 == 100) {
+ spk_adv += spk_inc;
+ if (spk_adv > 31)
+ spk_inc = -3;
+ if (spk_adv < 1)
+ spk_inc = 1;
+ spk_ctr2 = 0;
+ }
+ if (spk_ctr%spk_adv == 0) {
+ gpio_set(GPIOB, GPIO10);
+ } else {
+ gpio_clear(GPIOB, GPIO10);
+ }
+ continue;
+
+ if (++poll_ctr == 10) {
+ poll_ctr = 0;
+ TRACING_SET(TR_USBH_POLL);
+ usbh_poll(tim6_get_time_us());
+ TRACING_CLEAR(TR_USBH_POLL);
+ }
+
+ if (host_packet_length > 0) {
+ handle_host_packet(&noise_state, (struct control_packet *)host_packet_buf, host_packet_length - 1);
+ host_packet_length = 0; /* Acknowledge to USART ISR the buffer has been handled */
+
+ } else if (host_packet_length < 0) { /* USART error */
+ host_packet_length = 0; /* Acknowledge to USART ISR the error has been handled */
+ if (noise_state.handshake_state < HANDSHAKE_DONE_UNKNOWN_HOST) {
+ LOG_PRINTF("USART error, aborting handshake\n");
+
+ struct control_packet pkt = { .type=HOST_COMM_ERROR };
+ send_packet(usart2_out, (uint8_t *)&pkt, sizeof(pkt));
+
+ if (reset_protocol_handshake(&noise_state))
+ LOG_PRINTF("Error starting protocol handshake.\n");
+
+ pairing_buf_pos = 0; /* Reset channel binding keyboard input buffer */
+ }
+ }
+
+ if (noise_state.handshake_state == HANDSHAKE_IN_PROGRESS) {
+ TRACING_SET(TR_NOISE_HANDSHAKE);
+ if (try_continue_noise_handshake(&noise_state, NULL, 0)) { /* handle outgoing messages */
+ TRACING_CLEAR(TR_NOISE_HANDSHAKE);
+ LOG_PRINTF("Reporting handshake error to host\n");
+ struct control_packet pkt = { .type=HOST_CRYPTO_ERROR };
+ send_packet(usart2_out, (uint8_t *)&pkt, sizeof(pkt));
+ } else TRACING_CLEAR(TR_NOISE_HANDSHAKE);
+ }
+ }
+}
+
+void _fini() {
+ while (1);
+}
+
diff --git a/fw/src/frama_c_cmdline b/fw/src/frama_c_cmdline
new file mode 100644
index 0000000..d6c6adc
--- /dev/null
+++ b/fw/src/frama_c_cmdline
@@ -0,0 +1 @@
+frama-c-gui -c11 -cpp-extra-args="-DVERIFICATION -DSTM32F4 -DUSE_STM32F4_USBH_DRIVER_FS -DDEBUG_USART=USART1 -DDEBUG_USART_BAUDRATE=115200 -DDEBUG_USART_DMA_CHANNEL_NUM=4 -DDEBUG_USART_DMA_NUM=2 -DDEBUG_USART_DMA_STREAM_NUM=7 -I. -I"(frama-c -print-path)"/libc -I crypto/noise-c/include -I../include -I../libopencm3/include" -wp -wp-rte -wp-model +cint -wp-verbose 2 -slevel 8 -wp-out goals noise.c demo.c -wp-fct try_continue_noise_handshake
diff --git a/fw/src/hid_keycodes.c b/fw/src/hid_keycodes.c
new file mode 100644
index 0000000..ac3899a
--- /dev/null
+++ b/fw/src/hid_keycodes.c
@@ -0,0 +1,48 @@
+
+#include "hid_keycodes.h"
+
+struct keymap_entry keycode_mapping[] = {
+ { KEY_A, {'a', 'A'}},
+ { KEY_B, {'b', 'B'}},
+ { KEY_C, {'c', 'C'}},
+ { KEY_D, {'d', 'D'}},
+ { KEY_E, {'e', 'E'}},
+ { KEY_F, {'f', 'F'}},
+ { KEY_G, {'g', 'G'}},
+ { KEY_H, {'h', 'H'}},
+ { KEY_I, {'i', 'I'}},
+ { KEY_J, {'j', 'J'}},
+ { KEY_K, {'k', 'K'}},
+ { KEY_L, {'l', 'L'}},
+ { KEY_M, {'m', 'M'}},
+ { KEY_N, {'n', 'N'}},
+ { KEY_O, {'o', 'O'}},
+ { KEY_P, {'p', 'P'}},
+ { KEY_Q, {'q', 'Q'}},
+ { KEY_R, {'r', 'R'}},
+ { KEY_S, {'s', 'S'}},
+ { KEY_T, {'t', 'T'}},
+ { KEY_U, {'u', 'U'}},
+ { KEY_V, {'v', 'V'}},
+ { KEY_W, {'w', 'W'}},
+ { KEY_X, {'x', 'X'}},
+ { KEY_Y, {'y', 'Y'}},
+ { KEY_Z, {'z', 'Z'}},
+ { KEY_1, {'1', '!'}},
+ { KEY_2, {'2', '@'}},
+ { KEY_3, {'3', '#'}},
+ { KEY_4, {'4', '$'}},
+ { KEY_5, {'5', '%'}},
+ { KEY_6, {'6', '^'}},
+ { KEY_7, {'7', '&'}},
+ { KEY_8, {'8', '*'}},
+ { KEY_9, {'9', '('}},
+ { KEY_0, {'0', ')'}},
+ { KEY_MINUS, {'-', '_'}},
+ { KEY_SPACE, {' ', ' '}},
+ { KEY_COMMA, {',', '<'}},
+ { KEY_DOT, {'.', '>'}},
+ { KEY_SEMICOLON, {';', ':'}},
+ { KEY_NONE, {0, 0}}, /* end marker */
+};
+
diff --git a/fw/src/hid_keycodes.h b/fw/src/hid_keycodes.h
new file mode 100644
index 0000000..f269e5b
--- /dev/null
+++ b/fw/src/hid_keycodes.h
@@ -0,0 +1,218 @@
+#ifndef __HID_KEYCODES_H__
+#define __HID_KEYCODES_H__
+
+enum mod_levels {
+ LEVEL_NONE,
+ LEVEL_SHIFT,
+ LEVEL_NLEVELS
+};
+
+enum mod_bits {
+ MOD_LCTRL,
+ MOD_LSHIFT,
+ MOD_LALT,
+ MOD_LMETA,
+ MOD_RCTRL,
+ MOD_RSHIFT,
+ MOD_RALT,
+ MOD_RMETA,
+};
+
+enum mod_bitmaps {
+ MOD_XCTRL = MOD_LCTRL | MOD_RCTRL,
+ MOD_XSHIFT = MOD_LSHIFT | MOD_RSHIFT,
+ MOD_XALT = MOD_LALT | MOD_RALT,
+ MOD_XMETA = MOD_LMETA | MOD_RMETA,
+};
+
+struct keymap_entry {
+ unsigned char kc;
+ char ch[LEVEL_NLEVELS];
+};
+
+extern struct keymap_entry keycode_mapping[];
+
+enum hid_keycode {
+ KEY_NONE = 0x00, // No key pressed
+ KEY_ERR_OVF = 0x01, // Keyboard Error Roll Over - used for all slots if too many keys are pressed ("Phantom key")
+ KEY_A = 0x04, // Keyboard a and A
+ KEY_B = 0x05, // Keyboard b and B
+ KEY_C = 0x06, // Keyboard c and C
+ KEY_D = 0x07, // Keyboard d and D
+ KEY_E = 0x08, // Keyboard e and E
+ KEY_F = 0x09, // Keyboard f and F
+ KEY_G = 0x0a, // Keyboard g and G
+ KEY_H = 0x0b, // Keyboard h and H
+ KEY_I = 0x0c, // Keyboard i and I
+ KEY_J = 0x0d, // Keyboard j and J
+ KEY_K = 0x0e, // Keyboard k and K
+ KEY_L = 0x0f, // Keyboard l and L
+ KEY_M = 0x10, // Keyboard m and M
+ KEY_N = 0x11, // Keyboard n and N
+ KEY_O = 0x12, // Keyboard o and O
+ KEY_P = 0x13, // Keyboard p and P
+ KEY_Q = 0x14, // Keyboard q and Q
+ KEY_R = 0x15, // Keyboard r and R
+ KEY_S = 0x16, // Keyboard s and S
+ KEY_T = 0x17, // Keyboard t and T
+ KEY_U = 0x18, // Keyboard u and U
+ KEY_V = 0x19, // Keyboard v and V
+ KEY_W = 0x1a, // Keyboard w and W
+ KEY_X = 0x1b, // Keyboard x and X
+ KEY_Y = 0x1c, // Keyboard y and Y
+ KEY_Z = 0x1d, // Keyboard z and Z
+
+ KEY_1 = 0x1e, // Keyboard 1 and !
+ KEY_2 = 0x1f, // Keyboard 2 and @
+ KEY_3 = 0x20, // Keyboard 3 and #
+ KEY_4 = 0x21, // Keyboard 4 and $
+ KEY_5 = 0x22, // Keyboard 5 and %
+ KEY_6 = 0x23, // Keyboard 6 and ^
+ KEY_7 = 0x24, // Keyboard 7 and &
+ KEY_8 = 0x25, // Keyboard 8 and *
+ KEY_9 = 0x26, // Keyboard 9 and (
+ KEY_0 = 0x27, // Keyboard 0 and )
+
+ KEY_ENTER = 0x28, // Keyboard Return (ENTER)
+ KEY_ESC = 0x29, // Keyboard ESCAPE
+ KEY_BACKSPACE = 0x2a, // Keyboard DELETE (Backspace)
+ KEY_TAB = 0x2b, // Keyboard Tab
+ KEY_SPACE = 0x2c, // Keyboard Spacebar
+ KEY_MINUS = 0x2d, // Keyboard - and _
+ KEY_EQUAL = 0x2e, // Keyboard = and +
+ KEY_LEFTBRACE = 0x2f, // Keyboard [ and {
+ KEY_RIGHTBRACE = 0x30, // Keyboard ] and }
+ KEY_BACKSLASH = 0x31, // Keyboard \ and |
+ KEY_HASHTILDE = 0x32, // Keyboard Non-US # and ~
+ KEY_SEMICOLON = 0x33, // Keyboard ; and :
+ KEY_APOSTROPHE = 0x34, // Keyboard ' and "
+ KEY_GRAVE = 0x35, // Keyboard ` and ~
+ KEY_COMMA = 0x36, // Keyboard , and <
+ KEY_DOT = 0x37, // Keyboard . and >
+ KEY_SLASH = 0x38, // Keyboard / and ?
+ KEY_CAPSLOCK = 0x39, // Keyboard Caps Lock
+
+ KEY_F1 = 0x3a, // Keyboard F1
+ KEY_F2 = 0x3b, // Keyboard F2
+ KEY_F3 = 0x3c, // Keyboard F3
+ KEY_F4 = 0x3d, // Keyboard F4
+ KEY_F5 = 0x3e, // Keyboard F5
+ KEY_F6 = 0x3f, // Keyboard F6
+ KEY_F7 = 0x40, // Keyboard F7
+ KEY_F8 = 0x41, // Keyboard F8
+ KEY_F9 = 0x42, // Keyboard F9
+ KEY_F10 = 0x43, // Keyboard F10
+ KEY_F11 = 0x44, // Keyboard F11
+ KEY_F12 = 0x45, // Keyboard F12
+
+ KEY_SYSRQ = 0x46, // Keyboard Print Screen
+ KEY_SCROLLLOCK = 0x47, // Keyboard Scroll Lock
+ KEY_PAUSE = 0x48, // Keyboard Pause
+ KEY_INSERT = 0x49, // Keyboard Insert
+ KEY_HOME = 0x4a, // Keyboard Home
+ KEY_PAGEUP = 0x4b, // Keyboard Page Up
+ KEY_DELETE = 0x4c, // Keyboard Delete Forward
+ KEY_END = 0x4d, // Keyboard End
+ KEY_PAGEDOWN = 0x4e, // Keyboard Page Down
+ KEY_RIGHT = 0x4f, // Keyboard Right Arrow
+ KEY_LEFT = 0x50, // Keyboard Left Arrow
+ KEY_DOWN = 0x51, // Keyboard Down Arrow
+ KEY_UP = 0x52, // Keyboard Up Arrow
+
+ KEY_NUMLOCK = 0x53, // Keyboard Num Lock and Clear
+ KEY_KPSLASH = 0x54, // Keypad /
+ KEY_KPASTERISK = 0x55, // Keypad *
+ KEY_KPMINUS = 0x56, // Keypad -
+ KEY_KPPLUS = 0x57, // Keypad +
+ KEY_KPENTER = 0x58, // Keypad ENTER
+ KEY_KP1 = 0x59, // Keypad 1 and End
+ KEY_KP2 = 0x5a, // Keypad 2 and Down Arrow
+ KEY_KP3 = 0x5b, // Keypad 3 and PageDn
+ KEY_KP4 = 0x5c, // Keypad 4 and Left Arrow
+ KEY_KP5 = 0x5d, // Keypad 5
+ KEY_KP6 = 0x5e, // Keypad 6 and Right Arrow
+ KEY_KP7 = 0x5f, // Keypad 7 and Home
+ KEY_KP8 = 0x60, // Keypad 8 and Up Arrow
+ KEY_KP9 = 0x61, // Keypad 9 and Page Up
+ KEY_KP0 = 0x62, // Keypad 0 and Insert
+ KEY_KPDOT = 0x63, // Keypad . and Delete
+
+ KEY_102ND = 0x64, // Keyboard Non-US \ and |
+ KEY_COMPOSE = 0x65, // Keyboard Application
+ KEY_POWER = 0x66, // Keyboard Power
+ KEY_KPEQUAL = 0x67, // Keypad =
+
+ KEY_F13 = 0x68, // Keyboard F13
+ KEY_F14 = 0x69, // Keyboard F14
+ KEY_F15 = 0x6a, // Keyboard F15
+ KEY_F16 = 0x6b, // Keyboard F16
+ KEY_F17 = 0x6c, // Keyboard F17
+ KEY_F18 = 0x6d, // Keyboard F18
+ KEY_F19 = 0x6e, // Keyboard F19
+ KEY_F20 = 0x6f, // Keyboard F20
+ KEY_F21 = 0x70, // Keyboard F21
+ KEY_F22 = 0x71, // Keyboard F22
+ KEY_F23 = 0x72, // Keyboard F23
+ KEY_F24 = 0x73, // Keyboard F24
+
+ KEY_OPEN = 0x74, // Keyboard Execute
+ KEY_HELP = 0x75, // Keyboard Help
+ KEY_PROPS = 0x76, // Keyboard Menu
+ KEY_FRONT = 0x77, // Keyboard Select
+ KEY_STOP = 0x78, // Keyboard Stop
+ KEY_AGAIN = 0x79, // Keyboard Again
+ KEY_UNDO = 0x7a, // Keyboard Undo
+ KEY_CUT = 0x7b, // Keyboard Cut
+ KEY_COPY = 0x7c, // Keyboard Copy
+ KEY_PASTE = 0x7d, // Keyboard Paste
+ KEY_FIND = 0x7e, // Keyboard Find
+ KEY_MUTE = 0x7f, // Keyboard Mute
+ KEY_VOLUMEUP = 0x80, // Keyboard Volume Up
+ KEY_VOLUMEDOWN = 0x81, // Keyboard Volume Down
+ KEY_KPCOMMA = 0x85, // Keypad Comma
+ KEY_RO = 0x87, // Keyboard International1
+ KEY_KATAKANAHIRAGANA = 0x88, // Keyboard International2
+ KEY_YEN = 0x89, // Keyboard International3
+ KEY_HENKAN = 0x8a, // Keyboard International4
+ KEY_MUHENKAN = 0x8b, // Keyboard International5
+ KEY_KPJPCOMMA = 0x8c, // Keyboard International6
+ KEY_HANGEUL = 0x90, // Keyboard LANG1
+ KEY_HANJA = 0x91, // Keyboard LANG2
+ KEY_KATAKANA = 0x92, // Keyboard LANG3
+ KEY_HIRAGANA = 0x93, // Keyboard LANG4
+ KEY_ZENKAKUHANKAKU = 0x94, // Keyboard LANG5
+ KEY_KPLEFTPAREN = 0xb6, // Keypad (
+ KEY_KPRIGHTPAREN = 0xb7, // Keypad )
+
+ KEY_LEFTCTRL = 0xe0, // Keyboard Left Control
+ KEY_LEFTSHIFT = 0xe1, // Keyboard Left Shift
+ KEY_LEFTALT = 0xe2, // Keyboard Left Alt
+ KEY_LEFTMETA = 0xe3, // Keyboard Left GUI
+ KEY_RIGHTCTRL = 0xe4, // Keyboard Right Control
+ KEY_RIGHTSHIFT = 0xe5, // Keyboard Right Shift
+ KEY_RIGHTALT = 0xe6, // Keyboard Right Alt
+ KEY_RIGHTMETA = 0xe7, // Keyboard Right GUI
+
+ KEY_MEDIA_PLAYPAUSE = 0xe8,
+ KEY_MEDIA_STOPCD = 0xe9,
+ KEY_MEDIA_PREVIOUSSONG = 0xea,
+ KEY_MEDIA_NEXTSONG = 0xeb,
+ KEY_MEDIA_EJECTCD = 0xec,
+ KEY_MEDIA_VOLUMEUP = 0xed,
+ KEY_MEDIA_VOLUMEDOWN = 0xee,
+ KEY_MEDIA_MUTE = 0xef,
+ KEY_MEDIA_WWW = 0xf0,
+ KEY_MEDIA_BACK = 0xf1,
+ KEY_MEDIA_FORWARD = 0xf2,
+ KEY_MEDIA_STOP = 0xf3,
+ KEY_MEDIA_FIND = 0xf4,
+ KEY_MEDIA_SCROLLUP = 0xf5,
+ KEY_MEDIA_SCROLLDOWN = 0xf6,
+ KEY_MEDIA_EDIT = 0xf7,
+ KEY_MEDIA_SLEEP = 0xf8,
+ KEY_MEDIA_COFFEE = 0xf9,
+ KEY_MEDIA_REFRESH = 0xfa,
+ KEY_MEDIA_CALC = 0xfb,
+};
+
+#endif
diff --git a/fw/src/noise.c b/fw/src/noise.c
new file mode 100644
index 0000000..bd0974c
--- /dev/null
+++ b/fw/src/noise.c
@@ -0,0 +1,445 @@
+
+#include <string.h>
+
+#include "noise.h"
+#include "packet_interface.h"
+#include "rand_stm32.h"
+
+#include "crypto/noise-c/src/crypto/blake2/blake2s.h"
+
+
+#ifdef VERIFICATION
+#define HANDLE_NOISE_ERROR(x, msg) if (x) { goto errout; }
+#else
+#define HANDLE_NOISE_ERROR(x, msg) do { \
+ err = x; \
+ if (err != NOISE_ERROR_NONE) { \
+ char errbuf[256]; \
+ noise_strerror(err, errbuf, sizeof(errbuf)); \
+ LOG_PRINTF("Error " msg ": %s\n", errbuf); \
+ goto errout; \
+ } \
+ } while(0);
+#endif
+
+#ifdef VERIFICATION
+
+/*@ requires \valid(s + (0..n-1));
+ @ assigns s[0..n-1] \from c;
+ @ assigns \result \from s;
+ @ ensures result_ptr: \result == s;
+ @*/
+uint8_t *fc_memset_uint8(uint8_t *s, int c, size_t n);
+
+/*@ requires \valid(dest + (0..n-1));
+ @ requires \valid_read(src + (0..n-1));
+ @ assigns dest[0..n-1] \from src[0..n-1];
+ @ assigns \result \from dest;
+ @ ensures result_ptr: \result == dest;
+ @ ensures equals: \forall integer i; 0 <= i <= n-1 ==> dest[i] == src[i];
+ @*/
+uint8_t *fc_memcpy_uint8(uint8_t *dest, const uint8_t *src, size_t n);
+
+/*@ requires valid_s1: \valid_read(s1 + (0..n-1));
+ @ requires valid_s2: \valid_read(s2 + (0..n-1));
+ @ assigns \result \from
+ @ indirect:s1[0.. n-1], indirect:s2[0.. n-1];
+ @ ensures logic_spec: \result == 0 <==> (
+ \forall integer i; 0 <= i <= n-1 ==> s1[i] == s2[i]);
+ @*/
+int fc_memcmp_uint8(const uint8_t *s1, const uint8_t *s2, size_t n);
+
+#else
+#define fc_memset_uint8 memset
+#define fc_memcpy_uint8 memcpy
+#define fc_memcmp_uint8 memcmp
+#endif
+
+
+volatile uint8_t host_packet_buf[MAX_HOST_PACKET_SIZE];
+volatile int host_packet_length = 0;
+
+
+/*@
+ requires validity: \valid(st);
+
+ ensures equal: st->remote_key_reference == remote_key_reference && st->local_key == local_key;
+ ensures equal: st->handshake_state == HANDSHAKE_UNINITIALIZED;
+ ensures equal: st->failed_handshakes == 0;
+ ensures equal: st->tx_cipher == NULL && st->rx_cipher == NULL && st->handshake == NULL;
+
+ assigns *st;
+ */
+void noise_state_init(struct NoiseState *st, uint8_t *remote_key_reference, uint8_t *local_key) {
+ st->handshake_state = HANDSHAKE_UNINITIALIZED;
+ st->handshake = NULL;
+ st->tx_cipher = NULL;
+ st->rx_cipher = NULL;
+ fc_memset_uint8(st->handshake_hash, 0, sizeof(st->handshake_hash));
+ st->remote_key_reference = remote_key_reference;
+ st->local_key = local_key;
+ st->failed_handshakes = 0;
+}
+
+/*@
+ requires validity: \valid(st) && \valid(st->handshake_hash + (0..31)) && \valid_read(st->local_key + (0..31));
+ requires separation: \separated(st, st->rx_cipher, st->tx_cipher, st->handshake);
+
+ ensures result: \result \in {0, -1};
+ ensures success: \result == 0 ==> (
+ \valid(st->handshake) &&
+ (st->handshake_state == HANDSHAKE_PHASE1));
+ ensures failure: \result != 0 ==> (
+ (st->handshake == NULL) &&
+ (st->handshake_state == HANDSHAKE_UNINITIALIZED));
+ ensures unmodified: \old(st->failed_handshakes) == st->failed_handshakes;
+
+ assigns *st, *st->rx_cipher, *st->tx_cipher;
+ */
+int reset_protocol_handshake(struct NoiseState *st) {
+ uninit_handshake(st, HANDSHAKE_UNINITIALIZED);
+ disarm_key_scrubber();
+ noise_cipherstate_free(st->tx_cipher);
+ noise_cipherstate_free(st->rx_cipher);
+ st->tx_cipher = NULL;
+ st->rx_cipher = NULL;
+ st->handshake = NULL;
+ fc_memset_uint8(st->handshake_hash, 0, sizeof(st->handshake_hash));
+ return start_protocol_handshake(st);
+}
+
+/*@ requires validity: \valid(st) && \valid_read(st->local_key + (0..31));
+
+ ensures result: \result \in {0, -1};
+ ensures success: \result == 0 ==> (
+ \valid(st->handshake) &&
+ st->handshake_state == HANDSHAKE_PHASE1);
+ ensures failure: \result != 0 ==> (
+ st->handshake == \old(st->handshake) &&
+ st->handshake_state == \old(st->handshake_state));
+
+ assigns st->handshake, st->handshake_state;
+ */
+int start_protocol_handshake(struct NoiseState *st) {
+ /* TODO Noise-C is nice for prototyping, but we should really get rid of it for mostly three reasons:
+ * * We don't need cipher/protocol agility, and by baking the final protocol into the firmware we can save a lot
+ * of flash space by not including all the primitives we don't need as well as noise's dynamic protocol
+ * abstraction layer.
+ * * Noise-c is not very embedded-friendly, in particular it uses malloc and free. We should be able to run
+ * everything with statically allocated buffers instead.
+ * * Parts of it are not written that well
+ */
+ NoiseHandshakeState *handshake;
+ int err;
+
+ HANDLE_NOISE_ERROR(noise_init(), "initializing noise");
+
+ HANDLE_NOISE_ERROR(noise_handshakestate_new_by_name(&handshake, "Noise_XX_25519_ChaChaPoly_BLAKE2s", NOISE_ROLE_RESPONDER), "instantiating handshake pattern");
+
+ NoiseDHState *dh = noise_handshakestate_get_local_keypair_dh(handshake);
+ HANDLE_NOISE_ERROR(noise_dhstate_set_keypair_private(dh, st->local_key, CURVE25519_KEY_LEN), "loading local private keys");
+
+ HANDLE_NOISE_ERROR(noise_handshakestate_start(handshake), "starting handshake");
+
+ st->handshake = handshake;
+ st->handshake_state = HANDSHAKE_PHASE1;
+ return 0;
+
+errout:
+ noise_handshakestate_free(handshake);
+ return -1;
+}
+
+/*@ requires validity: \valid(st) && \valid(st->local_key + (0..31));
+
+ assigns st->local_key[0..31];
+ */
+int generate_identity_key(struct NoiseState *st) {
+ NoiseDHState *dh;
+ int err;
+
+ HANDLE_NOISE_ERROR(noise_dhstate_new_by_name(&dh, "25519"), "creating dhstate for key generation");
+ HANDLE_NOISE_ERROR(noise_dhstate_generate_keypair(dh), "generating key pair");
+
+ uint8_t unused[CURVE25519_KEY_LEN]; /* the noise api is a bit bad here. */
+ fc_memset_uint8(st->local_key, 0, CURVE25519_KEY_LEN);
+
+ HANDLE_NOISE_ERROR(noise_dhstate_get_keypair(dh, st->local_key, CURVE25519_KEY_LEN, unused, sizeof(unused)), "saving key pair");
+
+ noise_dhstate_free(dh);
+ return 0;
+errout:
+ if (dh)
+ noise_dhstate_free(dh);
+ return -1;
+}
+
+/*@requires validity: \valid(st);
+ requires state_valid: new_state \in
+ {HANDSHAKE_UNINITIALIZED, HANDSHAKE_DONE_UNKNOWN_HOST, HANDSHAKE_DONE_KNOWN_HOST};
+
+ ensures state: st->handshake_state == new_state;
+ ensures handshake: st->handshake == NULL;
+
+ assigns st->handshake, st->handshake_state;
+ @*/
+void uninit_handshake(struct NoiseState *st, enum handshake_state new_state) {
+ if (st->handshake)
+ noise_handshakestate_free(st->handshake);
+ st->handshake_state = new_state;
+ st->handshake = NULL;
+ arm_key_scrubber();
+}
+
+/*@
+ requires validity: \valid(st) && \valid(usart2_out) && \valid(st->handshake);
+ requires validity: \valid(st->remote_key + (0..sizeof(st->remote_key)-1));
+ requires validity: \valid(st->handshake_hash + (0..sizeof(st->handshake_hash)-1));
+
+ requires separation: \separated(usart2_out, st, buf, st->handshake);
+
+ ensures \result \in {-1, 0};
+
+ assigns *usart2_out, *st->handshake;
+ @*/
+int handshake_phase1(struct NoiseState * const st, uint8_t *buf, size_t len) {
+ int err;
+ struct {
+ struct control_packet header;
+ uint8_t payload[MAX_HOST_PACKET_SIZE];
+ } pkt;
+ NoiseBuffer noise_msg;
+
+ /* Read the next handshake message and discard the payload */
+ noise_buffer_set_input(noise_msg, buf, len);
+ HANDLE_NOISE_ERROR(noise_handshakestate_read_message(st->handshake, &noise_msg, NULL), "reading handshake message");
+
+ /* Write the next handshake message with a zero-length noise payload */
+ pkt.header.type = HOST_HANDSHAKE;
+ noise_buffer_set_output(noise_msg, &pkt.payload, sizeof(pkt.payload));
+ HANDLE_NOISE_ERROR(noise_handshakestate_write_message(st->handshake, &noise_msg, NULL), "writing handshake message");
+ send_packet(usart2_out, (uint8_t *)&pkt, noise_msg.size + sizeof(pkt.header));
+
+ return 0;
+errout: /* for HANDLE_NOISE_ERROR macro */
+ return -1;
+}
+
+//@ ghost int key_checked_trace;
+//@ ghost int key_match_trace;
+/*@
+ requires validity: \valid(st) && \valid(usart2_out) && \valid(st->handshake);
+ requires validity: \valid(st->remote_key + (0..sizeof(st->remote_key)-1));
+ requires validity: \valid_read(st->remote_key_reference + (0..sizeof(st->remote_key)-1));
+ requires validity: \valid(st->handshake_hash + (0..sizeof(st->handshake_hash)-1));
+ requires separation: \separated(usart2_out, st);
+ requires sanity: 0 <= st->failed_handshakes < 100;
+
+ requires separation: \separated(&usart2_out, st, buf, st->handshake);
+
+ ensures result: \result \in {-1, 0, 1};
+ ensures sanity: 0 <= st->failed_handshakes <= 102;
+ ensures sanity: \result != 1 ==> st->failed_handshakes >= \old(st->failed_handshakes);
+
+ ensures permission_valid: \result != -1 ==> key_checked_trace == 1;
+ ensures permission_valid: \result == 1 ==> key_match_trace == 1;
+ //
+ assigns *usart2_out, *st, *st->handshake, key_checked_trace, key_match_trace;
+ @*/
+int handshake_phase2(struct NoiseState * const st, uint8_t *buf, size_t len) {
+ //@ ghost int old_failed_handshakes = st->failed_handshakes;
+ int err;
+ struct {
+ struct control_packet header;
+ uint8_t payload[MAX_HOST_PACKET_SIZE];
+ } pkt;
+ NoiseBuffer noise_msg;
+ //@ ghost key_checked_trace = 0;
+ // ghost key_match_trace = 0;
+
+ /* Read the next handshake message and discard the payload */
+ noise_buffer_set_input(noise_msg, buf, len);
+ HANDLE_NOISE_ERROR(noise_handshakestate_read_message(st->handshake, &noise_msg, NULL), "reading handshake message");
+
+ HANDLE_NOISE_ERROR(noise_handshakestate_split(st->handshake, &st->tx_cipher, &st->rx_cipher), "splitting handshake state");
+ LOG_PRINTF("Noise protocol handshake completed successfully, handshake hash:\n");
+
+ if (noise_handshakestate_get_handshake_hash(st->handshake, st->handshake_hash, sizeof(st->handshake_hash)) != NOISE_ERROR_NONE) {
+ LOG_PRINTF("Error fetching noise handshake state\n");
+ goto errout;
+ } else {
+ LOG_PRINTF(" ");
+ /*@ loop assigns i; @*/
+ for (size_t i=0; i<sizeof(st->handshake_hash); i++)
+ LOG_PRINTF("%02x ", st->handshake_hash[i]);
+ LOG_PRINTF("\n");
+ }
+
+
+ NoiseDHState *remote_dh = noise_handshakestate_get_remote_public_key_dh(st->handshake);
+ if (!remote_dh) {
+ LOG_PRINTF("Error: Host has not identified itself\n");
+ goto errout;
+ }
+
+ HANDLE_NOISE_ERROR(noise_dhstate_get_public_key(remote_dh, st->remote_key, sizeof(st->remote_key)), "getting remote pubkey");
+
+ /* TODO support list of known remote hosts here instead of just one */
+ uint8_t remote_fp[BLAKE2S_HASH_SIZE];
+ BLAKE2s_context_t bc;
+ BLAKE2s_reset(&bc);
+ fc_BLAKE2s_update_uint8(&bc, st->remote_key, sizeof(st->remote_key));
+ BLAKE2s_finish(&bc, remote_fp);
+
+ //@ ghost key_checked_trace = 1;
+ if (!fc_memcmp_uint8(remote_fp, st->remote_key_reference, sizeof(remote_fp))) { /* keys match */
+ //@ ghost key_match_trace = 1;
+ uint8_t response = REPORT_PAIRING_SUCCESS;
+ if (send_encrypted_message(st, &response, sizeof(response)))
+ LOG_PRINTF("Error sending pairing response packet\n");
+
+ st->failed_handshakes = 0;
+ //@ assert 0 <= st->failed_handshakes <= 102;
+ return 1;
+
+ } else { /* keys don't match */
+ uint8_t response = REPORT_PAIRING_START;
+ if (send_encrypted_message(st, &response, sizeof(response)))
+ LOG_PRINTF("Error sending pairing response packet\n");
+
+ st->failed_handshakes++;
+ //@ assert 0 <= st->failed_handshakes <= 102;
+ //@ assert st->failed_handshakes >= old_failed_handshakes;
+ return 0;
+ }
+
+errout:
+ //@ assert 0 <= st->failed_handshakes <= 102;
+ //@ assert st->failed_handshakes >= old_failed_handshakes;
+ return -1;
+}
+/*@
+ requires validity: \valid(st) && \valid(usart2_out) && \valid(st->handshake);
+ requires validity: \valid(st->remote_key + (0..sizeof(st->remote_key)-1));
+ requires validity: \valid(st->remote_key_reference + (0..sizeof(st->remote_key)-1));
+ requires validity: \valid(st->handshake_hash + (0..sizeof(st->handshake_hash)-1));
+ requires sanity: 0 <= st->failed_handshakes < 100;
+
+ requires separation: \separated(usart2_out, st, buf, st->handshake);
+
+ ensures result: \result \in {0, -1};
+
+ ensures state_legal: st->handshake_state \in
+ {HANDSHAKE_UNINITIALIZED, HANDSHAKE_PHASE1, HANDSHAKE_PHASE2,
+ HANDSHAKE_DONE_KNOWN_HOST, HANDSHAKE_DONE_UNKNOWN_HOST};
+ ensures transition_legal:
+ \result != -1 ==> \old(st->handshake_state) \in {HANDSHAKE_PHASE1, HANDSHAKE_PHASE2};
+ ensures transition_legal: (\old(st->handshake_state) == HANDSHAKE_PHASE1)
+ ==> st->handshake_state \in {HANDSHAKE_PHASE2, HANDSHAKE_UNINITIALIZED};
+ ensures transition_legal: (\old(st->handshake_state) == HANDSHAKE_PHASE2)
+ ==> st->handshake_state \in {HANDSHAKE_DONE_KNOWN_HOST, HANDSHAKE_DONE_UNKNOWN_HOST, HANDSHAKE_UNINITIALIZED};
+ ensures failure_handling: \result == -1 <==> st->handshake_state == HANDSHAKE_UNINITIALIZED;
+ ensures failure_handling: \result == -1 ==> st->failed_handshakes > \old(st->failed_handshakes);
+
+ ensures state_advance_condition: (st->handshake_state == HANDSHAKE_DONE_KNOWN_HOST) ==> key_match_trace == 1;
+
+ //assigns *usart2_out, *st, *st->rx_cipher, *st->tx_cipher, *st->handshake;
+ //assigns key_checked_trace, key_match_trace;
+ @*/
+int try_continue_noise_handshake(struct NoiseState * const st, uint8_t *buf, size_t len) {
+ //@ ghost key_checked_trace = 0;
+ //@ ghost key_match_trace = 0;
+ int rc;
+
+ if (!st->handshake) {
+ LOG_PRINTF("Error: Invalid handshake state\n");
+ goto errout;
+ }
+
+ /* Run the protocol handshake */
+ switch (st->handshake_state) {
+ case HANDSHAKE_PHASE1:
+ if (handshake_phase1(st, buf, len))
+ goto errout;
+
+ st->handshake_state = HANDSHAKE_PHASE2;
+ return 0;
+
+ case HANDSHAKE_PHASE2:
+ rc = handshake_phase2(st, buf, len);
+ if (rc < 0)
+ goto errout;
+
+ if (rc == 1)
+ uninit_handshake(st, HANDSHAKE_DONE_KNOWN_HOST);
+ else
+ uninit_handshake(st, HANDSHAKE_DONE_UNKNOWN_HOST);
+ break;
+
+ default:
+ LOG_PRINTF("Invalid handshake state\n");
+ goto errout;
+ }
+
+ return 0;
+errout:
+ uninit_handshake(st, HANDSHAKE_UNINITIALIZED);
+ st->failed_handshakes++;
+ LOG_PRINTF("Noise protocol handshake failed, %d failed attempts\n", st->failed_handshakes);
+ return -1;
+}
+
+/*@
+ requires validity: \valid(st);
+ requires validity: \valid_read(st->remote_key + (0..sizeof(st->remote_key)-1));
+ requires validity: \valid(st->remote_key_reference + (0..31));
+
+ ensures state: st->handshake_state == HANDSHAKE_DONE_KNOWN_HOST;
+ assigns st->remote_key_reference[0..31], st->handshake_state;
+ */
+void persist_remote_key(struct NoiseState *st) {
+ BLAKE2s_context_t bc;
+ BLAKE2s_reset(&bc);
+ fc_BLAKE2s_update_uint8(&bc, st->remote_key, sizeof(st->remote_key));
+ BLAKE2s_finish(&bc, st->remote_key_reference);
+ st->handshake_state = HANDSHAKE_DONE_KNOWN_HOST;
+}
+
+/*@
+ requires validity: \valid(st) && \valid(usart2_out) && \valid(st->tx_cipher) && \valid_read(msg + (0..len-1));
+ requires separation: \separated(usart2_out, st);
+
+ ensures length: !(0 <= len <= MAX_HOST_PACKET_SIZE) <==> \result == -3;
+ ensures \result \in {0, -1, -2, -3};
+ assigns *st->tx_cipher, *usart2_out;
+ */
+int send_encrypted_message(struct NoiseState *st, const uint8_t *msg, size_t len) {
+ int err;
+ NoiseBuffer noise_buf;
+ struct {
+ struct control_packet header;
+ uint8_t payload[MAX_HOST_PACKET_SIZE];
+ } pkt;
+
+ if (len > sizeof(pkt.payload)) {
+ LOG_PRINTF("Packet too long\n");
+ return -3;
+ }
+
+ if (!st->tx_cipher) {
+ LOG_PRINTF("Cannot send encrypted packet: Data ciphers not yet initialized\n");
+ return -1;
+ }
+
+ pkt.header.type = HOST_DATA;
+ fc_memcpy_uint8(pkt.payload, msg, len); /* This is necessary because noises API doesn't support separate in and out buffers. D'oh! */
+ noise_buffer_set_inout(noise_buf, pkt.payload, len, sizeof(pkt.payload));
+
+ HANDLE_NOISE_ERROR(noise_cipherstate_encrypt(st->tx_cipher, &noise_buf), "encrypting data");
+ send_packet(usart2_out, (uint8_t *)&pkt, noise_buf.size + sizeof(pkt.header));
+
+ return 0;
+errout:
+ return -2;
+}
+
diff --git a/fw/src/noise.h b/fw/src/noise.h
new file mode 100644
index 0000000..1969945
--- /dev/null
+++ b/fw/src/noise.h
@@ -0,0 +1,56 @@
+#ifndef __NOISE_H__
+#define __NOISE_H__
+
+#include <stdint.h>
+
+#include <noise/protocol.h>
+
+#include "usart_helpers.h"
+#include "rand_stm32.h"
+
+
+#define CURVE25519_KEY_LEN 32
+#define MAX_HOST_PACKET_SIZE 128
+
+
+extern volatile uint8_t host_packet_buf[MAX_HOST_PACKET_SIZE];
+extern volatile int host_packet_length;
+
+enum handshake_state {
+ HANDSHAKE_UNINITIALIZED,
+ HANDSHAKE_PHASE1,
+ HANDSHAKE_PHASE2,
+ HANDSHAKE_DONE_UNKNOWN_HOST,
+ HANDSHAKE_DONE_KNOWN_HOST,
+};
+
+extern volatile enum handshake_state handshake_state;
+
+struct NoiseState {
+ NoiseHandshakeState *handshake;
+ enum handshake_state handshake_state;
+ NoiseCipherState *tx_cipher, *rx_cipher;
+ uint8_t *local_key;
+ uint8_t remote_key[CURVE25519_KEY_LEN];
+ uint8_t *remote_key_reference;
+ uint8_t handshake_hash[BLAKE2S_HASH_SIZE];
+ int failed_handshakes;
+};
+
+
+void uninit_handshake(struct NoiseState *st, enum handshake_state new_state);
+void noise_state_init(struct NoiseState *st, uint8_t *remote_key_reference, uint8_t *local_key);
+void persist_remote_key(struct NoiseState *st);
+int start_protocol_handshake(struct NoiseState *st);
+int reset_protocol_handshake(struct NoiseState *st);
+int generate_identity_key(struct NoiseState *st);
+int try_continue_noise_handshake(struct NoiseState * const st, uint8_t *buf, size_t len);
+int send_encrypted_message(struct NoiseState *st, const uint8_t *msg, size_t len);
+
+/*@ assigns \nothing; */
+void arm_key_scrubber(void);
+
+/*@ assigns \nothing; */
+void disarm_key_scrubber(void);
+
+#endif
diff --git a/fw/src/packet_interface.c b/fw/src/packet_interface.c
new file mode 100644
index 0000000..98a1ef2
--- /dev/null
+++ b/fw/src/packet_interface.c
@@ -0,0 +1,98 @@
+
+#include "packet_interface.h"
+#include "noise.h"
+#include "cobs.h"
+#include "tracing.h"
+
+#include <libopencm3/stm32/usart.h>
+#include <libopencm3/stm32/dma.h>
+#include <libopencm3/cm3/nvic.h>
+#include <libopencmsis/core_cm3.h>
+
+volatile struct {
+ struct dma_buf dma;
+ uint8_t data[256];
+} usart2_buf = { .dma = { .len = sizeof(usart2_buf.data) } };
+
+struct dma_usart_file usart2_out_s = {
+ .usart = USART2,
+ .baudrate = 115200,
+ .dma = DMA1,
+ .stream = 6,
+ .channel = 4,
+ .irqn = NVIC_DMA_IRQ(1, 6),
+ .buf = &usart2_buf.dma
+};
+struct dma_usart_file *usart2_out = &usart2_out_s;
+
+void dma1_stream6_isr(void) {
+ TRACING_SET(TR_HOST_IF_DMA_IRQ);
+ static unsigned int fifo_errors = 0; /* debug */
+ if (dma_get_interrupt_flag(usart2_out->dma, usart2_out->stream, DMA_FEIF)) {
+ /* Ignore FIFO errors as they're 100% non-critical for UART applications */
+ dma_clear_interrupt_flags(usart2_out->dma, usart2_out->stream, DMA_FEIF);
+ fifo_errors++;
+ TRACING_CLEAR(TR_HOST_IF_DMA_IRQ);
+ return;
+ }
+
+ /* Transfer complete interrupt */
+ dma_clear_interrupt_flags(usart2_out->dma, usart2_out->stream, DMA_TCIF);
+
+ if (usart2_out->buf->wr_pos != usart2_out->buf->xfr_end) /* buffer not empty */
+ schedule_dma(usart2_out);
+ TRACING_CLEAR(TR_HOST_IF_DMA_IRQ);
+}
+
+void usart2_isr(void) {
+ TRACING_SET(TR_HOST_IF_USART_IRQ);
+ static struct cobs_decode_state host_cobs_state = {0};
+ if (USART2_SR & USART_SR_ORE) { /* Overrun handling */
+ LOG_PRINTF("USART2 data register overrun\n");
+ /* Clear interrupt flag */
+ (void)USART2_DR; /* FIXME make sure this read is not optimized out */
+ host_packet_length = -1;
+ TRACING_CLEAR(TR_HOST_IF_USART_IRQ);
+ return;
+ }
+
+ uint8_t data = USART2_DR; /* This automatically acknowledges the IRQ */
+
+ if (host_packet_length) {
+ LOG_PRINTF("USART2 COBS buffer overrun\n");
+ host_packet_length = -1;
+ TRACING_CLEAR(TR_HOST_IF_USART_IRQ);
+ return;
+ }
+
+ ssize_t rv = cobs_decode_incremental(&host_cobs_state, (char *)host_packet_buf, sizeof(host_packet_buf), data);
+ if (rv == 0) {
+ /* good, empty frame */
+ LOG_PRINTF("Got empty frame from host\n");
+ host_packet_length = -1;
+ } else if (rv == -1) {
+ /* Decoding frame, wait for next byte */
+ } else if (rv == -2) {
+ LOG_PRINTF("Host interface COBS framing error\n");
+ host_packet_length = -1;
+ } else if (rv == -3) {
+ /* invalid empty frame */
+ LOG_PRINTF("Got double null byte from host\n");
+ host_packet_length = -1;
+ } else if (rv == -4) {
+ /* frame too large */
+ LOG_PRINTF("Got too large frame from host\n");
+ host_packet_length = -1;
+ } else if (rv > 0) {
+ /* Good, non-empty frame */
+ host_packet_length = rv;
+ }
+ TRACING_CLEAR(TR_HOST_IF_USART_IRQ);
+}
+
+void send_packet(struct dma_usart_file *f, const uint8_t *data, size_t len) {
+ /* ignore return value as putf is blocking and always succeeds */
+ (void)cobs_encode_incremental(f, putf, (char *)data, len);
+ flush(f);
+}
+
diff --git a/fw/src/packet_interface.h b/fw/src/packet_interface.h
new file mode 100644
index 0000000..35e9758
--- /dev/null
+++ b/fw/src/packet_interface.h
@@ -0,0 +1,58 @@
+#ifndef __PACKET_INTERFACE_H__
+#define __PACKET_INTERFACE_H__
+
+#include "usart_helpers.h"
+
+
+extern struct dma_usart_file *usart2_out;
+
+enum control_packet_types {
+ _HOST_RESERVED = 0,
+ HOST_INITIATE_HANDSHAKE = 1,
+ HOST_HANDSHAKE = 2,
+ HOST_DATA = 3,
+ HOST_COMM_ERROR = 4,
+ HOST_CRYPTO_ERROR = 5,
+ HOST_TOO_MANY_FAILS = 6,
+};
+
+enum packet_types {
+ _REPORT_RESERVED = 0,
+ REPORT_KEYBOARD= 1,
+ REPORT_MOUSE= 2,
+ REPORT_PAIRING_INPUT = 3,
+ REPORT_PAIRING_SUCCESS = 4,
+ REPORT_PAIRING_ERROR = 5,
+ REPORT_PAIRING_START = 6,
+};
+
+struct hid_report_packet {
+ uint8_t type;
+ union {
+ struct {
+ uint8_t len;
+ uint8_t report[8];
+ } report;
+ struct {
+ char c;
+ } pairing_input;
+ };
+} __attribute__((__packed__));
+
+
+struct control_packet {
+ uint8_t type;
+ uint8_t payload[0];
+} __attribute__((__packed__));
+
+
+/*@
+ requires \valid(f);
+ requires \valid_read(data + (0..len-1));
+ requires len > 0;
+
+ assigns *f;
+ */
+void send_packet(struct dma_usart_file *f, const uint8_t *data, size_t len);
+
+#endif
diff --git a/fw/src/pgp_wordlist b/fw/src/pgp_wordlist
new file mode 100644
index 0000000..fcc3508
--- /dev/null
+++ b/fw/src/pgp_wordlist
@@ -0,0 +1,257 @@
+Hex Even Word Odd Word
+00 aardvark adroitness
+01 absurd adviser
+02 accrue aftermath
+03 acme aggregate
+04 adrift alkali
+05 adult almighty
+06 afflict amulet
+07 ahead amusement
+08 aimless antenna
+09 Algol applicant
+0A allow Apollo
+0B alone armistice
+0C ammo article
+0D ancient asteroid
+0E apple Atlantic
+0F artist atmosphere
+10 assume autopsy
+11 Athens Babylon
+12 atlas backwater
+13 Aztec barbecue
+14 baboon belowground
+15 backfield bifocals
+16 backward bodyguard
+17 banjo bookseller
+18 beaming borderline
+19 bedlamp bottomless
+1A beehive Bradbury
+1B beeswax bravado
+1C befriend Brazilian
+1D Belfast breakaway
+1E berserk Burlington
+1F billiard businessman
+20 bison butterfat
+21 blackjack Camelot
+22 blockade candidate
+23 blowtorch cannonball
+24 bluebird Capricorn
+25 bombast caravan
+26 bookshelf caretaker
+27 brackish celebrate
+28 breadline cellulose
+29 breakup certify
+2A brickyard chambermaid
+2B briefcase Cherokee
+2C Burbank Chicago
+2D button clergyman
+2E buzzard coherence
+2F cement combustion
+30 chairlift commando
+31 chatter company
+32 checkup component
+33 chisel concurrent
+34 choking confidence
+35 chopper conformist
+36 Christmas congregate
+37 clamshell consensus
+38 classic consulting
+39 classroom corporate
+3A cleanup corrosion
+3B clockwork councilman
+3C cobra crossover
+3D commence crucifix
+3E concert cumbersome
+3F cowbell customer
+40 crackdown Dakota
+41 cranky decadence
+42 crowfoot December
+43 crucial decimal
+44 crumpled designing
+45 crusade detector
+46 cubic detergent
+47 dashboard determine
+48 deadbolt dictator
+49 deckhand dinosaur
+4A dogsled direction
+4B dragnet disable
+4C drainage disbelief
+4D dreadful disruptive
+4E drifter distortion
+4F dropper document
+50 drumbeat embezzle
+51 drunken enchanting
+52 Dupont enrollment
+53 dwelling enterprise
+54 eating equation
+55 edict equipment
+56 egghead escapade
+57 eightball Eskimo
+58 endorse everyday
+59 endow examine
+5A enlist existence
+5B erase exodus
+5C escape fascinate
+5D exceed filament
+5E eyeglass finicky
+5F eyetooth forever
+60 facial fortitude
+61 fallout frequency
+62 flagpole gadgetry
+63 flatfoot Galveston
+64 flytrap getaway
+65 fracture glossary
+66 framework gossamer
+67 freedom graduate
+68 frighten gravity
+69 gazelle guitarist
+6A Geiger hamburger
+6B glitter Hamilton
+6C glucose handiwork
+6D goggles hazardous
+6E goldfish headwaters
+6F gremlin hemisphere
+70 guidance hesitate
+71 hamlet hideaway
+72 highchair holiness
+73 hockey hurricane
+74 indoors hydraulic
+75 indulge impartial
+76 inverse impetus
+77 involve inception
+78 island indigo
+79 jawbone inertia
+7A keyboard infancy
+7B kickoff inferno
+7C kiwi informant
+7D klaxon insincere
+7E locale insurgent
+7F lockup integrate
+80 merit intention
+81 minnow inventive
+82 miser Istanbul
+83 Mohawk Jamaica
+84 mural Jupiter
+85 music leprosy
+86 necklace letterhead
+87 Neptune liberty
+88 newborn maritime
+89 nightbird matchmaker
+8A Oakland maverick
+8B obtuse Medusa
+8C offload megaton
+8D optic microscope
+8E orca microwave
+8F payday midsummer
+90 peachy millionaire
+91 pheasant miracle
+92 physique misnomer
+93 playhouse molasses
+94 Pluto molecule
+95 preclude Montana
+96 prefer monument
+97 preshrunk mosquito
+98 printer narrative
+99 prowler nebula
+9A pupil newsletter
+9B puppy Norwegian
+9C python October
+9D quadrant Ohio
+9E quiver onlooker
+9F quota opulent
+A0 ragtime Orlando
+A1 ratchet outfielder
+A2 rebirth Pacific
+A3 reform pandemic
+A4 regain Pandora
+A5 reindeer paperweight
+A6 rematch paragon
+A7 repay paragraph
+A8 retouch paramount
+A9 revenge passenger
+AA reward pedigree
+AB rhythm Pegasus
+AC ribcage penetrate
+AD ringbolt perceptive
+AE robust performance
+AF rocker pharmacy
+B0 ruffled phonetic
+B1 sailboat photograph
+B2 sawdust pioneer
+B3 scallion pocketful
+B4 scenic politeness
+B5 scorecard positive
+B6 Scotland potato
+B7 seabird processor
+B8 select provincial
+B9 sentence proximate
+BA shadow puberty
+BB shamrock publisher
+BC showgirl pyramid
+BD skullcap quantity
+BE skydive racketeer
+BF slingshot rebellion
+C0 slowdown recipe
+C1 snapline recover
+C2 snapshot repellent
+C3 snowcap replica
+C4 snowslide reproduce
+C5 solo resistor
+C6 southward responsive
+C7 soybean retraction
+C8 spaniel retrieval
+C9 spearhead retrospect
+CA spellbind revenue
+CB spheroid revival
+CC spigot revolver
+CD spindle sandalwood
+CE spyglass sardonic
+CF stagehand Saturday
+D0 stagnate savagery
+D1 stairway scavenger
+D2 standard sensation
+D3 stapler sociable
+D4 steamship souvenir
+D5 sterling specialist
+D6 stockman speculate
+D7 stopwatch stethoscope
+D8 stormy stupendous
+D9 sugar supportive
+DA surmount surrender
+DB suspense suspicious
+DC sweatband sympathy
+DD swelter tambourine
+DE tactics telephone
+DF talon therapist
+E0 tapeworm tobacco
+E1 tempest tolerance
+E2 tiger tomorrow
+E3 tissue torpedo
+E4 tonic tradition
+E5 topmost travesty
+E6 tracker trombonist
+E7 transit truncated
+E8 trauma typewriter
+E9 treadmill ultimate
+EA Trojan undaunted
+EB trouble underfoot
+EC tumor unicorn
+ED tunnel unify
+EE tycoon universe
+EF uncut unravel
+F0 unearth upcoming
+F1 unwind vacancy
+F2 uproot vagabond
+F3 upset vertigo
+F4 upshot Virginia
+F5 vapor visitor
+F6 village vocalist
+F7 virus voyager
+F8 Vulcan warranty
+F9 waffle Waterloo
+FA wallet whimsical
+FB watchword Wichita
+FC wayside Wilmington
+FD willow Wyoming
+FE woodlark yesteryear
+FF Zulu Yucatan
diff --git a/fw/src/rand_stm32.c b/fw/src/rand_stm32.c
new file mode 100644
index 0000000..87bea8f
--- /dev/null
+++ b/fw/src/rand_stm32.c
@@ -0,0 +1,135 @@
+/* Quick-and-dirty cryptographic RNG based on BLAKE2s
+ *
+ * This system uses a 32-byte BLAKE2s hash as internal state seeded by somewhat random post-powerup SRAM content, the
+ * unique device ID and the program flash contents. This seed state is mixed with values from the hardware RNG for each
+ * 32-byte block of output data.
+ *
+ * The RNG's chaining looks like the following, with H(...) being the BLAKE2s hash function, | being binary
+ * concatenation and hw_rng(...) being the hardware RNG. c and e are the fixed extraction and chain string constants
+ * defined below.
+ *
+ * Seed: state = H(SRAM | FLASH | hw_rng(64 byte))
+ *
+ * Extract: state = H(state | c | hw_rng(64 byte)) block[0] = H(state | e)
+ * state = H(state | c | hw_rng(64 byte)) block[1] = H(state | e)
+ * [...]
+ * state = H(state | c | hw_rng(64 byte)) block[n] = H(state | e)
+ * state = H(state | c | hw_rng(64 byte))
+ *
+ *
+ * Graphically, with C = H( state | c | rng ) being the chaining function
+ * and X = H( state | e ) being the extraction function
+ * this becomes:
+ *
+ * rng rng rng rng
+ * | | | |
+ * v v v v
+ * state ---> [C] ---> [C] -- . . . --> [C] ---> [C] ---> new state
+ * | | |
+ * v v v
+ * [X] [X] [X]
+ * | | |
+ * v v v
+ * out[0] out[1] . . . out[n]
+ *
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <libopencm3/stm32/f4/rng.h>
+
+#include "usart_helpers.h"
+#include "rand_stm32.h"
+#include "tracing.h"
+
+#include "crypto/noise-c/src/protocol/internal.h"
+#include "crypto/noise-c/src/crypto/blake2/blake2s.h"
+
+/* FIXME persist state in backup sram */
+extern unsigned _ram_start, _ram_end, _rom_start, _rom_end;
+static uint8_t global_stm_rand_state[BLAKE2S_HASH_SIZE];
+
+static uint32_t stm32_read_rng_raw(void) {
+ if ((RNG_SR & (RNG_SR_SEIS | RNG_SR_CEIS)) || !(RNG_CR & RNG_CR_RNGEN)) {
+ LOG_PRINTF("RNG error detected, bailing out.\n");
+ exit(1);
+ }
+
+ while (!(RNG_SR & RNG_SR_DRDY))
+ ;
+
+ return RNG_DR;
+}
+
+static void rng_seed_blake(BLAKE2s_context_t *bc) {
+ /* This pulls out 64 bytes. Even though the resulting BLAKE2s hash only is 32 bytes large, the internal state of
+ * BLAKE2s is larger. Also I don't quite trust the STM32F4's hardware RNG. */
+ for (int i=0; i<16; i++) {
+ uint32_t val = stm32_read_rng_raw();
+ BLAKE2s_update(bc, &val, sizeof(val));
+ }
+}
+
+void rand_init() {
+ RNG_CR |= RNG_CR_RNGEN;
+ BLAKE2s_context_t bc;
+ BLAKE2s_reset(&bc);
+
+ /* Seed with entire SRAM area */
+ BLAKE2s_update(&bc, &_ram_start, &_ram_end - &_ram_start);
+ /* Seed with entire flash area. This includes the device unique ID if it has not been overwritten. */
+ BLAKE2s_update(&bc, &_rom_start, &_rom_end - &_rom_start);
+ /* Seed with 64 bytes of handware RNG input */
+ rng_seed_blake(&bc);
+ /* FIXME use ADC to seeed */
+
+ BLAKE2s_finish(&bc, global_stm_rand_state);
+ /* FIXME make sure this is not optimized out */
+ memset(&bc, 0, sizeof(bc));
+}
+
+const char *extraction_constant = "Blake2 RNG extraction constant";
+const char *chain_constant = "Blake2 RNG chaining constant";
+
+void noise_rand_bytes(void *bytes, size_t size) {
+ TRACING_SET(TR_RNG);
+ BLAKE2s_context_t out_ctx, chain_ctx;
+ uint8_t *out = (uint8_t *)bytes;
+ uint8_t hash_buf[BLAKE2S_HASH_SIZE];
+
+ for (size_t wr_pos = 0; wr_pos<size; wr_pos += BLAKE2S_HASH_SIZE) {
+ BLAKE2s_reset(&chain_ctx);
+ BLAKE2s_update(&chain_ctx, global_stm_rand_state, sizeof(global_stm_rand_state));
+ BLAKE2s_update(&chain_ctx, chain_constant, strlen(chain_constant));
+ rng_seed_blake(&chain_ctx);
+ BLAKE2s_finish(&chain_ctx, global_stm_rand_state);
+
+ BLAKE2s_reset(&out_ctx);
+ BLAKE2s_update(&out_ctx, global_stm_rand_state, sizeof(global_stm_rand_state));
+ BLAKE2s_update(&out_ctx, extraction_constant, strlen(extraction_constant));
+ BLAKE2s_finish(&out_ctx, hash_buf);
+
+ size_t rem = size-wr_pos;
+ memcpy(&out[wr_pos], hash_buf, rem < BLAKE2S_HASH_SIZE ? rem : BLAKE2S_HASH_SIZE);
+ }
+
+ BLAKE2s_reset(&chain_ctx);
+ BLAKE2s_update(&chain_ctx, global_stm_rand_state, sizeof(global_stm_rand_state));
+ BLAKE2s_update(&chain_ctx, chain_constant, strlen(chain_constant));
+ rng_seed_blake(&chain_ctx);
+ BLAKE2s_finish(&chain_ctx, global_stm_rand_state);
+
+ /* FIXME make sure this is not optimized out */
+ memset(&out_ctx, 0, sizeof(out_ctx));
+ memset(&chain_ctx, 0, sizeof(chain_ctx));
+ memset(hash_buf, 0, sizeof(hash_buf));
+ TRACING_CLEAR(TR_RNG);
+}
+
+#ifdef ED25519_CUSTOMRANDOM /* We are building against ed25519-donna, which needs a random function */
+void ed25519_randombytes_unsafe(void *p, size_t len) {
+ noise_rand_bytes(p, len);
+}
+#endif
diff --git a/fw/src/rand_stm32.h b/fw/src/rand_stm32.h
new file mode 100644
index 0000000..3e89ec3
--- /dev/null
+++ b/fw/src/rand_stm32.h
@@ -0,0 +1,11 @@
+#ifndef __RAND_STM32_H__
+#define __RAND_STM32_H__
+
+#include <stdint.h>
+#include <unistd.h>
+
+#define BLAKE2S_HASH_SIZE 32
+
+void rand_init(void);
+
+#endif
diff --git a/fw/src/tinyprintf.c b/fw/src/tinyprintf.c
new file mode 100644
index 0000000..0f9ec4e
--- /dev/null
+++ b/fw/src/tinyprintf.c
@@ -0,0 +1,523 @@
+/*
+File: tinyprintf.c
+
+Copyright (C) 2004 Kustaa Nyholm
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+*/
+
+#include "tinyprintf.h"
+
+
+/*
+ * Configuration
+ */
+
+/* Enable long int support */
+#define PRINTF_LONG_SUPPORT
+
+/* Enable long long int support (implies long int support) */
+#define PRINTF_LONG_LONG_SUPPORT
+
+/* Enable %z (size_t) support */
+#define PRINTF_SIZE_T_SUPPORT
+
+/*
+ * Configuration adjustments
+ */
+#ifdef PRINTF_SIZE_T_SUPPORT
+#include <sys/types.h>
+#endif
+
+#ifdef PRINTF_LONG_LONG_SUPPORT
+# define PRINTF_LONG_SUPPORT
+#endif
+
+/* __SIZEOF_<type>__ defined at least by gcc */
+#ifdef __SIZEOF_POINTER__
+# define SIZEOF_POINTER __SIZEOF_POINTER__
+#endif
+#ifdef __SIZEOF_LONG_LONG__
+# define SIZEOF_LONG_LONG __SIZEOF_LONG_LONG__
+#endif
+#ifdef __SIZEOF_LONG__
+# define SIZEOF_LONG __SIZEOF_LONG__
+#endif
+#ifdef __SIZEOF_INT__
+# define SIZEOF_INT __SIZEOF_INT__
+#endif
+
+#ifdef __GNUC__
+# define _TFP_GCC_NO_INLINE_ __attribute__ ((noinline))
+#else
+# define _TFP_GCC_NO_INLINE_
+#endif
+
+/*
+ * Implementation
+ */
+struct param {
+ char lz:1; /**< Leading zeros */
+ char alt:1; /**< alternate form */
+ char uc:1; /**< Upper case (for base16 only) */
+ char align_left:1; /**< 0 == align right (default), 1 == align left */
+ unsigned int width; /**< field width */
+ char sign; /**< The sign to display (if any) */
+ unsigned int base; /**< number base (e.g.: 8, 10, 16) */
+ char *bf; /**< Buffer to output */
+};
+
+
+#ifdef PRINTF_LONG_LONG_SUPPORT
+static void _TFP_GCC_NO_INLINE_ ulli2a(
+ unsigned long long int num, struct param *p)
+{
+ int n = 0;
+ unsigned long long int d = 1;
+ char *bf = p->bf;
+ while (num / d >= p->base)
+ d *= p->base;
+ while (d != 0) {
+ int dgt = num / d;
+ num %= d;
+ d /= p->base;
+ if (n || dgt > 0 || d == 0) {
+ *bf++ = dgt + (dgt < 10 ? '0' : (p->uc ? 'A' : 'a') - 10);
+ ++n;
+ }
+ }
+ *bf = 0;
+}
+
+static void lli2a(long long int num, struct param *p)
+{
+ if (num < 0) {
+ num = -num;
+ p->sign = '-';
+ }
+ ulli2a(num, p);
+}
+#endif
+
+#ifdef PRINTF_LONG_SUPPORT
+static void uli2a(unsigned long int num, struct param *p)
+{
+ int n = 0;
+ unsigned long int d = 1;
+ char *bf = p->bf;
+ while (num / d >= p->base)
+ d *= p->base;
+ while (d != 0) {
+ int dgt = num / d;
+ num %= d;
+ d /= p->base;
+ if (n || dgt > 0 || d == 0) {
+ *bf++ = dgt + (dgt < 10 ? '0' : (p->uc ? 'A' : 'a') - 10);
+ ++n;
+ }
+ }
+ *bf = 0;
+}
+
+static void li2a(long num, struct param *p)
+{
+ if (num < 0) {
+ num = -num;
+ p->sign = '-';
+ }
+ uli2a(num, p);
+}
+#endif
+
+static void ui2a(unsigned int num, struct param *p)
+{
+ int n = 0;
+ unsigned int d = 1;
+ char *bf = p->bf;
+ while (num / d >= p->base)
+ d *= p->base;
+ while (d != 0) {
+ int dgt = num / d;
+ num %= d;
+ d /= p->base;
+ if (n || dgt > 0 || d == 0) {
+ *bf++ = dgt + (dgt < 10 ? '0' : (p->uc ? 'A' : 'a') - 10);
+ ++n;
+ }
+ }
+ *bf = 0;
+}
+
+static void i2a(int num, struct param *p)
+{
+ if (num < 0) {
+ num = -num;
+ p->sign = '-';
+ }
+ ui2a(num, p);
+}
+
+static int a2d(char ch)
+{
+ if (ch >= '0' && ch <= '9')
+ return ch - '0';
+ else if (ch >= 'a' && ch <= 'f')
+ return ch - 'a' + 10;
+ else if (ch >= 'A' && ch <= 'F')
+ return ch - 'A' + 10;
+ else
+ return -1;
+}
+
+static char a2u(char ch, const char **src, int base, unsigned int *nump)
+{
+ const char *p = *src;
+ unsigned int num = 0;
+ int digit;
+ while ((digit = a2d(ch)) >= 0) {
+ if (digit > base)
+ break;
+ num = num * base + digit;
+ ch = *p++;
+ }
+ *src = p;
+ *nump = num;
+ return ch;
+}
+
+static void putchw(void *putp, putcf putf, struct param *p)
+{
+ char ch;
+ int n = p->width;
+ char *bf = p->bf;
+
+ /* Number of filling characters */
+ while (*bf++ && n > 0)
+ n--;
+ if (p->sign)
+ n--;
+ if (p->alt && p->base == 16)
+ n -= 2;
+ else if (p->alt && p->base == 8)
+ n--;
+
+ /* Fill with space to align to the right, before alternate or sign */
+ if (!p->lz && !p->align_left) {
+ while (n-- > 0)
+ putf(putp, ' ');
+ }
+
+ /* print sign */
+ if (p->sign)
+ putf(putp, p->sign);
+
+ /* Alternate */
+ if (p->alt && p->base == 16) {
+ putf(putp, '0');
+ putf(putp, (p->uc ? 'X' : 'x'));
+ } else if (p->alt && p->base == 8) {
+ putf(putp, '0');
+ }
+
+ /* Fill with zeros, after alternate or sign */
+ if (p->lz) {
+ while (n-- > 0)
+ putf(putp, '0');
+ }
+
+ /* Put actual buffer */
+ bf = p->bf;
+ while ((ch = *bf++))
+ putf(putp, ch);
+
+ /* Fill with space to align to the left, after string */
+ if (!p->lz && p->align_left) {
+ while (n-- > 0)
+ putf(putp, ' ');
+ }
+}
+
+void tfp_format(void *putp, putcf putf, const char *fmt, va_list va)
+{
+ struct param p;
+#ifdef PRINTF_LONG_SUPPORT
+ char bf[23]; /* long = 64b on some architectures */
+#else
+ char bf[12]; /* int = 32b on some architectures */
+#endif
+ char ch;
+ p.bf = bf;
+
+ while ((ch = *(fmt++))) {
+ if (ch != '%') {
+ putf(putp, ch);
+ } else {
+#ifdef PRINTF_LONG_SUPPORT
+ char lng = 0; /* 1 for long, 2 for long long */
+#endif
+ /* Init parameter struct */
+ p.lz = 0;
+ p.alt = 0;
+ p.width = 0;
+ p.align_left = 0;
+ p.sign = 0;
+
+ /* Flags */
+ while ((ch = *(fmt++))) {
+ switch (ch) {
+ case '-':
+ p.align_left = 1;
+ continue;
+ case '0':
+ p.lz = 1;
+ continue;
+ case '#':
+ p.alt = 1;
+ continue;
+ default:
+ break;
+ }
+ break;
+ }
+
+ /* Width */
+ if (ch >= '0' && ch <= '9') {
+ ch = a2u(ch, &fmt, 10, &(p.width));
+ }
+
+ /* We accept 'x.y' format but don't support it completely:
+ * we ignore the 'y' digit => this ignores 0-fill
+ * size and makes it == width (ie. 'x') */
+ if (ch == '.') {
+ p.lz = 1; /* zero-padding */
+ /* ignore actual 0-fill size: */
+ do {
+ ch = *(fmt++);
+ } while ((ch >= '0') && (ch <= '9'));
+ }
+
+#ifdef PRINTF_SIZE_T_SUPPORT
+# ifdef PRINTF_LONG_SUPPORT
+ if (ch == 'z') {
+ ch = *(fmt++);
+ if (sizeof(size_t) == sizeof(unsigned long int))
+ lng = 1;
+# ifdef PRINTF_LONG_LONG_SUPPORT
+ else if (sizeof(size_t) == sizeof(unsigned long long int))
+ lng = 2;
+# endif
+ } else
+# endif
+#endif
+
+#ifdef PRINTF_LONG_SUPPORT
+ if (ch == 'l') {
+ ch = *(fmt++);
+ lng = 1;
+#ifdef PRINTF_LONG_LONG_SUPPORT
+ if (ch == 'l') {
+ ch = *(fmt++);
+ lng = 2;
+ }
+#endif
+ }
+#endif
+ switch (ch) {
+ case 0:
+ goto abort;
+ case 'u':
+ p.base = 10;
+#ifdef PRINTF_LONG_SUPPORT
+#ifdef PRINTF_LONG_LONG_SUPPORT
+ if (2 == lng)
+ ulli2a(va_arg(va, unsigned long long int), &p);
+ else
+#endif
+ if (1 == lng)
+ uli2a(va_arg(va, unsigned long int), &p);
+ else
+#endif
+ ui2a(va_arg(va, unsigned int), &p);
+ putchw(putp, putf, &p);
+ break;
+ case 'd':
+ case 'i':
+ p.base = 10;
+#ifdef PRINTF_LONG_SUPPORT
+#ifdef PRINTF_LONG_LONG_SUPPORT
+ if (2 == lng)
+ lli2a(va_arg(va, long long int), &p);
+ else
+#endif
+ if (1 == lng)
+ li2a(va_arg(va, long int), &p);
+ else
+#endif
+ i2a(va_arg(va, int), &p);
+ putchw(putp, putf, &p);
+ break;
+#ifdef SIZEOF_POINTER
+ case 'p':
+ p.alt = 1;
+# if defined(SIZEOF_INT) && SIZEOF_POINTER <= SIZEOF_INT
+ lng = 0;
+# elif defined(SIZEOF_LONG) && SIZEOF_POINTER <= SIZEOF_LONG
+ lng = 1;
+# elif defined(SIZEOF_LONG_LONG) && SIZEOF_POINTER <= SIZEOF_LONG_LONG
+ lng = 2;
+# endif
+#endif
+ __attribute__((fallthrough));
+ case 'x':
+ __attribute__((fallthrough));
+ case 'X':
+ p.base = 16;
+ p.uc = (ch == 'X')?1:0;
+#ifdef PRINTF_LONG_SUPPORT
+#ifdef PRINTF_LONG_LONG_SUPPORT
+ if (2 == lng)
+ ulli2a(va_arg(va, unsigned long long int), &p);
+ else
+#endif
+ if (1 == lng)
+ uli2a(va_arg(va, unsigned long int), &p);
+ else
+#endif
+ ui2a(va_arg(va, unsigned int), &p);
+ putchw(putp, putf, &p);
+ break;
+ case 'o':
+ p.base = 8;
+ ui2a(va_arg(va, unsigned int), &p);
+ putchw(putp, putf, &p);
+ break;
+ case 'c':
+ putf(putp, (char)(va_arg(va, int)));
+ break;
+ case 's':
+ p.bf = va_arg(va, char *);
+ putchw(putp, putf, &p);
+ p.bf = bf;
+ break;
+ case '%':
+ putf(putp, ch);
+ default:
+ break;
+ }
+ }
+ }
+ abort:;
+}
+
+#if TINYPRINTF_DEFINE_TFP_PRINTF
+static putcf stdout_putf;
+static void *stdout_putp;
+
+void init_printf(void *putp, putcf putf)
+{
+ stdout_putf = putf;
+ stdout_putp = putp;
+}
+
+void tfp_printf(char *fmt, ...)
+{
+ va_list va;
+ va_start(va, fmt);
+ tfp_format(stdout_putp, stdout_putf, fmt, va);
+ va_end(va);
+}
+#endif
+
+#if TINYPRINTF_DEFINE_TFP_SPRINTF
+struct _vsnprintf_putcf_data
+{
+ size_t dest_capacity;
+ char *dest;
+ size_t num_chars;
+};
+
+static void _vsnprintf_putcf(void *p, char c)
+{
+ struct _vsnprintf_putcf_data *data = (struct _vsnprintf_putcf_data*)p;
+ if (data->num_chars < data->dest_capacity)
+ data->dest[data->num_chars] = c;
+ data->num_chars ++;
+}
+
+int tfp_vsnprintf(char *str, size_t size, const char *format, va_list ap)
+{
+ struct _vsnprintf_putcf_data data;
+
+ if (size < 1)
+ return 0;
+
+ data.dest = str;
+ data.dest_capacity = size-1;
+ data.num_chars = 0;
+ tfp_format(&data, _vsnprintf_putcf, format, ap);
+
+ if (data.num_chars < data.dest_capacity)
+ data.dest[data.num_chars] = '\0';
+ else
+ data.dest[data.dest_capacity] = '\0';
+
+ return data.num_chars;
+}
+
+int tfp_snprintf(char *str, size_t size, const char *format, ...)
+{
+ va_list ap;
+ int retval;
+
+ va_start(ap, format);
+ retval = tfp_vsnprintf(str, size, format, ap);
+ va_end(ap);
+ return retval;
+}
+
+struct _vsprintf_putcf_data
+{
+ char *dest;
+ size_t num_chars;
+};
+
+static void _vsprintf_putcf(void *p, char c)
+{
+ struct _vsprintf_putcf_data *data = (struct _vsprintf_putcf_data*)p;
+ data->dest[data->num_chars++] = c;
+}
+
+int tfp_vsprintf(char *str, const char *format, va_list ap)
+{
+ struct _vsprintf_putcf_data data;
+ data.dest = str;
+ data.num_chars = 0;
+ tfp_format(&data, _vsprintf_putcf, format, ap);
+ data.dest[data.num_chars] = '\0';
+ return data.num_chars;
+}
+
+int tfp_sprintf(char *str, const char *format, ...)
+{
+ va_list ap;
+ int retval;
+
+ va_start(ap, format);
+ retval = tfp_vsprintf(str, format, ap);
+ va_end(ap);
+ return retval;
+}
+#endif
diff --git a/fw/src/tinyprintf.h b/fw/src/tinyprintf.h
new file mode 100644
index 0000000..a769f4a
--- /dev/null
+++ b/fw/src/tinyprintf.h
@@ -0,0 +1,186 @@
+/*
+File: tinyprintf.h
+
+Copyright (C) 2004 Kustaa Nyholm
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+This library is really just two files: 'tinyprintf.h' and 'tinyprintf.c'.
+
+They provide a simple and small (+400 loc) printf functionality to
+be used in embedded systems.
+
+I've found them so useful in debugging that I do not bother with a
+debugger at all.
+
+They are distributed in source form, so to use them, just compile them
+into your project.
+
+Two printf variants are provided: printf and the 'sprintf' family of
+functions ('snprintf', 'sprintf', 'vsnprintf', 'vsprintf').
+
+The formats supported by this implementation are:
+'c' 'd' 'i' 'o' 'p' 'u' 's' 'x' 'X'.
+
+Zero padding and field width are also supported.
+
+If the library is compiled with 'PRINTF_SUPPORT_LONG' defined, then
+the long specifier is also supported. Note that this will pull in some
+long math routines (pun intended!) and thus make your executable
+noticeably longer. Likewise with 'PRINTF_LONG_LONG_SUPPORT' for the
+long long specifier, and with 'PRINTF_SIZE_T_SUPPORT' for the size_t
+specifier.
+
+The memory footprint of course depends on the target CPU, compiler and
+compiler options, but a rough guesstimate (based on a H8S target) is about
+1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space.
+Not too bad. Your mileage may vary. By hacking the source code you can
+get rid of some hundred bytes, I'm sure, but personally I feel the balance of
+functionality and flexibility versus code size is close to optimal for
+many embedded systems.
+
+To use the printf, you need to supply your own character output function,
+something like :
+
+void putc ( void* p, char c)
+{
+ while (!SERIAL_PORT_EMPTY) ;
+ SERIAL_PORT_TX_REGISTER = c;
+}
+
+Before you can call printf, you need to initialize it to use your
+character output function with something like:
+
+init_printf(NULL,putc);
+
+Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc',
+the NULL (or any pointer) you pass into the 'init_printf' will eventually be
+passed to your 'putc' routine. This allows you to pass some storage space (or
+anything really) to the character output function, if necessary.
+This is not often needed but it was implemented like that because it made
+implementing the sprintf function so neat (look at the source code).
+
+The code is re-entrant, except for the 'init_printf' function, so it is safe
+to call it from interrupts too, although this may result in mixed output.
+If you rely on re-entrancy, take care that your 'putc' function is re-entrant!
+
+The printf and sprintf functions are actually macros that translate to
+'tfp_printf' and 'tfp_sprintf' when 'TINYPRINTF_OVERRIDE_LIBC' is set
+(default). Setting it to 0 makes it possible to use them along with
+'stdio.h' printf's in a single source file. When
+'TINYPRINTF_OVERRIDE_LIBC' is set, please note that printf/sprintf are
+not function-like macros, so if you have variables or struct members
+with these names, things will explode in your face. Without variadic
+macros this is the best we can do to wrap these function. If it is a
+problem, just give up the macros and use the functions directly, or
+rename them.
+
+It is also possible to avoid defining tfp_printf and/or tfp_sprintf by
+clearing 'TINYPRINTF_DEFINE_TFP_PRINTF' and/or
+'TINYPRINTF_DEFINE_TFP_SPRINTF' to 0. This allows for example to
+export only tfp_format, which is at the core of all the other
+functions.
+
+For further details see source code.
+
+regs Kusti, 23.10.2004
+*/
+
+#ifndef __TFP_PRINTF__
+#define __TFP_PRINTF__
+
+#include <stdarg.h>
+
+/* Global configuration */
+
+/* Set this to 0 if you do not want to provide tfp_printf */
+#ifndef TINYPRINTF_DEFINE_TFP_PRINTF
+# define TINYPRINTF_DEFINE_TFP_PRINTF 1
+#endif
+
+/* Set this to 0 if you do not want to provide
+ tfp_sprintf/snprintf/vsprintf/vsnprintf */
+#ifndef TINYPRINTF_DEFINE_TFP_SPRINTF
+# define TINYPRINTF_DEFINE_TFP_SPRINTF 1
+#endif
+
+/* Set this to 0 if you do not want tfp_printf and
+ tfp_{vsn,sn,vs,s}printf to be also available as
+ printf/{vsn,sn,vs,s}printf */
+#ifndef TINYPRINTF_OVERRIDE_LIBC
+# define TINYPRINTF_OVERRIDE_LIBC 1
+#endif
+
+/* Optional external types dependencies */
+
+#if TINYPRINTF_DEFINE_TFP_SPRINTF
+# include <sys/types.h> /* size_t */
+#endif
+
+/* Declarations */
+
+#ifdef __GNUC__
+# define _TFP_SPECIFY_PRINTF_FMT(fmt_idx,arg1_idx) \
+ __attribute__((format (printf, fmt_idx, arg1_idx)))
+#else
+# define _TFP_SPECIFY_PRINTF_FMT(fmt_idx,arg1_idx)
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef void (*putcf) (void *, char);
+
+/*
+ 'tfp_format' really is the central function for all tinyprintf. For
+ each output character after formatting, the 'putf' callback is
+ called with 2 args:
+ - an arbitrary void* 'putp' param defined by the user and
+ passed unmodified from 'tfp_format',
+ - the character.
+ The 'tfp_printf' and 'tfp_sprintf' functions simply define their own
+ callback and pass to it the right 'putp' it is expecting.
+*/
+void tfp_format(void *putp, putcf putf, const char *fmt, va_list va);
+
+#if TINYPRINTF_DEFINE_TFP_SPRINTF
+int tfp_vsnprintf(char *str, size_t size, const char *fmt, va_list ap);
+int tfp_snprintf(char *str, size_t size, const char *fmt, ...) \
+ _TFP_SPECIFY_PRINTF_FMT(3, 4);
+int tfp_vsprintf(char *str, const char *fmt, va_list ap);
+int tfp_sprintf(char *str, const char *fmt, ...) \
+ _TFP_SPECIFY_PRINTF_FMT(2, 3);
+# if TINYPRINTF_OVERRIDE_LIBC
+# define vsnprintf tfp_vsnprintf
+# define snprintf tfp_snprintf
+# define vsprintf tfp_vsprintf
+# define sprintf tfp_sprintf
+# endif
+#endif
+
+#if TINYPRINTF_DEFINE_TFP_PRINTF
+void init_printf(void *putp, putcf putf);
+void tfp_printf(char *fmt, ...) _TFP_SPECIFY_PRINTF_FMT(1, 2);
+# if TINYPRINTF_OVERRIDE_LIBC
+# define printf tfp_printf
+# endif
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/fw/src/tracing.h b/fw/src/tracing.h
new file mode 100644
index 0000000..1970556
--- /dev/null
+++ b/fw/src/tracing.h
@@ -0,0 +1,25 @@
+#ifndef __TRACING_H__
+#define __TRACING_H__
+
+#include <libopencm3/stm32/gpio.h>
+
+#ifndef VERIFICATION
+#define TRACING_SET(i) gpio_set(GPIOD, (1<<i))
+#define TRACING_CLEAR(i) gpio_clear(GPIOD, (1<<i))
+#else
+#define TRACING_SET(i) ((void)0)
+#define TRACING_CLEAR(i) ((void)0)
+#endif
+
+enum tracing_channels {
+ TR_HID_MESSAGE_HANDLER = 0,
+ TR_DEBUG_OUT_DMA_IRQ = 1,
+ TR_HOST_IF_DMA_IRQ = 2,
+ TR_HOST_IF_USART_IRQ = 3,
+ TR_USBH_POLL = 4,
+ TR_HOST_PKT_HANDLER = 5,
+ TR_NOISE_HANDSHAKE = 6,
+ TR_RNG = 7,
+};
+
+#endif
diff --git a/fw/src/usart_helpers.c b/fw/src/usart_helpers.c
new file mode 100644
index 0000000..0cfc2d5
--- /dev/null
+++ b/fw/src/usart_helpers.c
@@ -0,0 +1,149 @@
+/*
+ * This file is part of the libusbhost library
+ * hosted at http://github.com/libusbhost/libusbhost
+ *
+ * Copyright (C) 2015 Amir Hammad <amir.hammad@hotmail.com>
+ *
+ *
+ * libusbhost is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include "usart_helpers.h"
+#define TINYPRINTF_OVERRIDE_LIBC 0
+#define TINYPRINTF_DEFINE_TFP_SPRINTF 0
+#include "tinyprintf.h"
+#include "cobs.h"
+
+#include <string.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <libopencm3/stm32/usart.h>
+#include <libopencm3/stm32/dma.h>
+#include <libopencm3/cm3/nvic.h>
+#include <libopencmsis/core_cm3.h>
+
+
+void usart_fprintf(struct dma_usart_file *f, const char *str, ...) {
+ va_list va;
+ va_start(va, str);
+ tfp_format(f, (void (*)(void *, char c))putf, str, va);
+ va_end(va);
+ flush(f);
+}
+
+void usart_init(uint32_t arg_usart, uint32_t baudrate) {
+ usart_set_baudrate(arg_usart, baudrate);
+ usart_set_databits(arg_usart, 8);
+ usart_set_flow_control(arg_usart, USART_FLOWCONTROL_NONE);
+ usart_set_mode(arg_usart, USART_MODE_TX | USART_MODE_RX);
+ usart_set_parity(arg_usart, USART_PARITY_NONE);
+ usart_set_stopbits(arg_usart, USART_STOPBITS_1);
+ usart_enable(arg_usart);
+}
+
+void usart_dma_init(struct dma_usart_file *f) {
+ usart_init(f->usart, f->baudrate);
+
+ f->buf->xfr_start = -1,
+ f->buf->xfr_end = 0,
+ f->buf->wr_pos = 0,
+
+ dma_stream_reset(f->dma, f->stream);
+ dma_channel_select(f->dma, f->stream, DMA_SxCR_CHSEL(f->channel));
+ dma_set_peripheral_address(f->dma, f->stream, (uint32_t)&USART_DR(f->usart));
+ dma_set_transfer_mode(f->dma, f->stream, DMA_SxCR_DIR_MEM_TO_PERIPHERAL);
+ dma_enable_memory_increment_mode(f->dma, f->stream);
+ dma_set_peripheral_size(f->dma, f->stream, DMA_SxCR_PSIZE_8BIT);
+ dma_set_memory_size(f->dma, f->stream, DMA_SxCR_MSIZE_8BIT);
+ dma_set_priority(f->dma, f->stream, DMA_SxCR_PL_VERY_HIGH);
+ dma_enable_transfer_complete_interrupt(f->dma, f->stream);
+ dma_enable_fifo_error_interrupt(f->dma, f->stream);
+ usart_enable_tx_dma(f->usart);
+}
+
+void usart_kickoff_dma(uint32_t dma, uint8_t stream, volatile uint8_t *buf, size_t len) {
+ /* initiate transmission of new buffer */
+ dma_set_memory_address(dma, stream, (uint32_t)buf); /* select active buffer address */
+ dma_set_number_of_data(dma, stream, len);
+ dma_enable_stream(dma, stream);
+}
+
+void schedule_dma(volatile struct dma_usart_file *f) {
+ volatile struct dma_buf *buf = f->buf;
+
+ uint32_t xfr_len, xfr_start = buf->xfr_end;
+ if (buf->wr_pos > xfr_start) /* no wraparound */
+ xfr_len = buf->wr_pos - xfr_start;
+ else /* wraparound */
+ xfr_len = buf->len - xfr_start; /* schedule transfer until end of buffer */
+
+ buf->xfr_start = xfr_start;
+ buf->xfr_end = (xfr_start + xfr_len) % buf->len; /* handle wraparound */
+ usart_kickoff_dma(f->dma, f->stream, buf->data + xfr_start, xfr_len);
+}
+
+int dma_fifo_push(volatile struct dma_buf *buf, char c) {
+ if (buf->wr_pos == buf->xfr_start)
+ return -EBUSY;
+
+ buf->data[buf->wr_pos] = c;
+ buf->wr_pos = (buf->wr_pos + 1) % buf->len;
+ return 0;
+}
+
+int putf(void *file, char c) {
+ volatile struct dma_usart_file *f = (struct dma_usart_file *)file;
+
+ nvic_disable_irq(f->irqn);
+ /* push char to fifo, busy-loop if stalled to wait for USART to empty fifo via DMA */
+ while (dma_fifo_push(f->buf, c) == -EBUSY) {
+ nvic_enable_irq(f->irqn);
+ flush(f);
+ nvic_disable_irq(f->irqn);
+ }
+ nvic_enable_irq(f->irqn);
+ return 0;
+}
+
+int putb(void *file, const uint8_t *buf, size_t len) {
+ volatile struct dma_usart_file *f = (struct dma_usart_file *)file;
+
+ nvic_disable_irq(f->irqn);
+ for (size_t i=0; i<len; i++) {
+ /* push char to fifo, busy-loop if stalled to wait for USART to empty fifo via DMA */
+ while (dma_fifo_push(f->buf, buf[i]) == -EBUSY) {
+ nvic_enable_irq(f->irqn);
+ nvic_disable_irq(f->irqn);
+ }
+ }
+ nvic_enable_irq(f->irqn);
+ return 0;
+}
+
+void flush(void *file) {
+ volatile struct dma_usart_file *f = (struct dma_usart_file *)file;
+
+ nvic_disable_irq(f->irqn);
+ /* If the DMA stream is idle right now, schedule a transfer */
+ if (!(DMA_SCR(f->dma, f->stream) & DMA_SxCR_EN)) { /* DMA is not running */
+ //&& !dma_get_interrupt_flag(f->dma, f->stream, DMA_TCIF)/* DMA interrupt is clear */) {
+ dma_clear_interrupt_flags(f->dma, f->stream, DMA_TCIF);
+ schedule_dma(f);
+ }
+ nvic_enable_irq(f->irqn);
+}
+
diff --git a/fw/src/usart_helpers.h b/fw/src/usart_helpers.h
new file mode 100644
index 0000000..531652a
--- /dev/null
+++ b/fw/src/usart_helpers.h
@@ -0,0 +1,88 @@
+/*
+ * This file is part of the libusbhost library
+ * hosted at http://github.com/libusbhost/libusbhost
+ *
+ * Copyright (C) 2015 Amir Hammad <amir.hammad@hotmail.com>
+ *
+ *
+ * libusbhost is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef USBH_USART_HELPERS_H
+#define USBH_USART_HELPERS_H
+
+#include "usbh_core.h"
+#include <stdint.h>
+#include <stdarg.h>
+#include <errno.h>
+
+BEGIN_DECLS
+
+struct dma_buf {
+ uint32_t xfr_start; /* Start index of running DMA transfer */
+ uint32_t xfr_end; /* End index of running DMA transfer plus one */
+ uint32_t wr_pos; /* Next index to be written */
+ uint32_t len;
+ uint8_t data[0];
+};
+
+struct dma_usart_file {
+ uint32_t usart;
+ uint32_t baudrate;
+ uint32_t dma;
+ uint8_t stream;
+ uint8_t channel;
+ uint8_t irqn;
+ volatile struct dma_buf *buf;
+};
+
+
+extern struct dma_usart_file *debug_out;
+
+
+void usart_init(uint32_t usart, uint32_t baudrate);
+void usart_fprintf(struct dma_usart_file *f, const char *str, ...);
+void usart_fifo_push(uint8_t c);
+
+void usart_dma_init(struct dma_usart_file *f);
+void usart_kickoff_dma(uint32_t dma, uint8_t stream, volatile uint8_t *buf, size_t len);
+void schedule_dma(volatile struct dma_usart_file *f);
+int dma_fifo_push(volatile struct dma_buf *buf, char c);
+int putf(void *file, char c);
+int putb(void *file, const uint8_t *buf, size_t len);
+void flush(void *file);
+
+/* This macro abomination templates a bunch of dma-specific register/constant names from preprocessor macros passed in
+ * from cmake. */
+#define DMA_PASTE(num) DMA ## num
+#define DMA(num) DMA_PASTE(num)
+
+#define NVIC_DMA_IRQ_PASTE(dma, stream) NVIC_ ## DMA ## dma ## _ ## STREAM ## stream ## _IRQ
+#define NVIC_DMA_IRQ(dma, stream) NVIC_DMA_IRQ_PASTE(dma, stream)
+
+#define DMA_ISR_PASTE(dma, stream) DMA ## dma ## _ ## STREAM ## stream ## _IRQHandler
+#define DMA_ISR(dma, stream) DMA_ISR_PASTE(dma, stream)
+
+#ifdef USART_DEBUG
+#define LOG_PRINTF(format, ...) usart_fprintf(debug_out, format, ##__VA_ARGS__);
+#else
+#define LOG_PRINTF(dummy, ...) ((void)dummy)
+#endif
+
+#define UNUSED(var) ((void)var)
+
+END_DECLS
+
+#endif
diff --git a/fw/src/usbh_core.c b/fw/src/usbh_core.c
new file mode 100644
index 0000000..1a60285
--- /dev/null
+++ b/fw/src/usbh_core.c
@@ -0,0 +1,726 @@
+/*
+ * This file is part of the libusbhost library
+ * hosted at http://github.com/libusbhost/libusbhost
+ *
+ * Copyright (C) 2015 Amir Hammad <amir.hammad@hotmail.com>
+ *
+ *
+ * libusbhost is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "usbh_config.h"
+#include "usbh_lld_stm32f4.h"
+#include "driver/usbh_device_driver.h"
+#include "usart_helpers.h"
+
+#include <libopencm3/stm32/gpio.h>
+#include <libopencm3/usb/usbstd.h>
+
+#include <stddef.h>
+
+static struct {
+ bool enumeration_run;
+ const usbh_low_level_driver_t * const *lld_drivers;
+ const usbh_dev_driver_t * const *dev_drivers;
+ int8_t address_temporary;
+} usbh_data = {};
+
+static void set_enumeration(void)
+{
+ usbh_data.enumeration_run = true;
+}
+
+static void reset_enumeration(void)
+{
+ usbh_data.enumeration_run = false;
+}
+
+static bool enumeration(void)
+{
+ return usbh_data.enumeration_run;
+}
+
+void device_remove(usbh_device_t *dev)
+{
+ if (dev->drv && dev->drvdata) {
+ dev->drv->remove(dev->drvdata);
+ }
+ dev->address = -1;
+ dev->drv = NULL;
+ dev->drvdata = NULL;
+}
+
+/**
+ *
+ */
+static bool find_driver(usbh_device_t *dev, const usbh_dev_driver_info_t * device_info)
+{
+
+#define CHECK_PARTIAL_COMPATIBILITY(what) \
+ if (usbh_data.dev_drivers[i]->info->what != -1\
+ && device_info->what != usbh_data.dev_drivers[i]->info->what) {\
+ i++;\
+ continue;\
+ }
+
+ int i = 0;
+
+ while (usbh_data.dev_drivers[i]) {
+
+ CHECK_PARTIAL_COMPATIBILITY(ifaceClass);
+ CHECK_PARTIAL_COMPATIBILITY(ifaceSubClass);
+ CHECK_PARTIAL_COMPATIBILITY(ifaceProtocol);
+ CHECK_PARTIAL_COMPATIBILITY(deviceClass);
+ CHECK_PARTIAL_COMPATIBILITY(deviceSubClass);
+ CHECK_PARTIAL_COMPATIBILITY(deviceProtocol);
+ CHECK_PARTIAL_COMPATIBILITY(idVendor);
+ CHECK_PARTIAL_COMPATIBILITY(idProduct);
+
+ dev->drv = usbh_data.dev_drivers[i];
+ dev->drvdata = dev->drv->init(dev);
+ if (!dev->drvdata) {
+ LOG_PRINTF("Unable to initialize device driver at index %d\n", i);
+ i++;
+ continue;
+ }
+ return true;
+ }
+ return false;
+#undef CHECK_PARTIAL_COMPATIBILITY
+}
+
+
+static void device_register(void *descriptors, uint16_t descriptors_len, usbh_device_t *dev)
+{
+ uint32_t i = 0;
+ uint8_t *buf = (uint8_t *)descriptors;
+
+ dev->drv = NULL;
+ dev->drvdata = NULL;
+
+ uint8_t desc_len = buf[i];
+ uint8_t desc_type = buf[i + 1];
+
+ usbh_dev_driver_info_t device_info;
+ if (desc_type == USB_DT_DEVICE) {
+ struct usb_device_descriptor *device_desc = (void*)&buf[i];
+ LOG_PRINTF("DEVICE DESCRIPTOR\n");
+ device_info.deviceClass = device_desc->bDeviceClass;
+ device_info.deviceSubClass = device_desc->bDeviceSubClass;
+ device_info.deviceProtocol = device_desc->bDeviceProtocol;
+ device_info.idVendor = device_desc->idVendor;
+ device_info.idProduct = device_desc->idProduct;
+ } else {
+ LOG_PRINTF("INVALID descriptors pointer - fatal error");
+ return;
+ }
+
+
+ while (i < descriptors_len) {
+ desc_len = buf[i];
+ desc_type = buf[i + 1];
+ switch (desc_type) {
+ case USB_DT_INTERFACE:
+ {
+ LOG_PRINTF("INTERFACE_DESCRIPTOR\n");
+ struct usb_interface_descriptor *iface = (void*)&buf[i];
+ device_info.ifaceClass = iface->bInterfaceClass;
+ device_info.ifaceSubClass = iface->bInterfaceSubClass;
+ device_info.ifaceProtocol = iface->bInterfaceProtocol;
+ if (find_driver(dev, &device_info)) {
+ int k = 0;
+ while (k < descriptors_len) {
+ desc_len = buf[k];
+ void *drvdata = dev->drvdata;
+ LOG_PRINTF("[%d]", buf[k+1]);
+ if (dev->drv->analyze_descriptor(drvdata, &buf[k])) {
+ LOG_PRINTF("Device Initialized\n");
+ return;
+ }
+
+ if (desc_len == 0) {
+ LOG_PRINTF("Problem occured while parsing complete configuration descriptor");
+ return;
+ }
+ k += desc_len;
+ }
+ LOG_PRINTF("Device driver isn't compatible with this device\n");
+ device_remove(dev);
+ } else {
+ LOG_PRINTF("No compatible driver has been found for interface #%d\n", iface->bInterfaceNumber);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (desc_len == 0) {
+ LOG_PRINTF("PROBLEM WITH PARSE %d\n",i);
+ return;
+ }
+ i += desc_len;
+ }
+ LOG_PRINTF("Device NOT Initialized\n");
+}
+
+void usbh_init(const usbh_low_level_driver_t * const low_level_drivers[], const usbh_dev_driver_t * const device_drivers[])
+{
+ if (!low_level_drivers) {
+ return;
+ }
+
+ usbh_data.lld_drivers = (const usbh_low_level_driver_t **)low_level_drivers;
+ usbh_data.dev_drivers = device_drivers;
+
+ uint32_t k = 0;
+ while (usbh_data.lld_drivers[k]) {
+ LOG_PRINTF("Initialization low-level driver with index=%d\n", k);
+
+ usbh_device_t * usbh_device =
+ ((usbh_generic_data_t *)(usbh_data.lld_drivers[k])->driver_data)->usbh_device;
+ uint32_t i;
+ for (i = 0; i < USBH_MAX_DEVICES; i++) {
+ //~ LOG_PRINTF("%p ", &usbh_device[i]);
+ usbh_device[i].address = -1;
+ usbh_device[i].drv = 0;
+ usbh_device[i].drvdata = 0;
+ }
+ usbh_data.lld_drivers[k]->init(usbh_data.lld_drivers[k]->driver_data);
+
+ k++;
+ }
+
+}
+
+static void device_xfer_control_write_setup(const void *data, uint16_t datalen, usbh_packet_callback_t callback, usbh_device_t *dev)
+{
+ usbh_packet_t packet;
+
+ packet.data.out = data;
+ packet.datalen = datalen;
+ packet.address = dev->address;
+ packet.endpoint_address = 0;
+ packet.endpoint_size_max = dev->packet_size_max0;
+ packet.endpoint_type = USBH_ENDPOINT_TYPE_CONTROL;
+ packet.control_type = USBH_CONTROL_TYPE_SETUP;
+ packet.speed = dev->speed;
+ packet.callback = callback;
+ packet.callback_arg = dev;
+ packet.toggle = &dev->toggle0;
+
+ usbh_write(dev, &packet);
+ LOG_PRINTF("WR-setup@device...%d \n", dev->address);
+}
+
+static void device_xfer_control_write_data(const void *data, uint16_t datalen, usbh_packet_callback_t callback, usbh_device_t *dev)
+{
+ usbh_packet_t packet;
+
+ packet.data.out = data;
+ packet.datalen = datalen;
+ packet.address = dev->address;
+ packet.endpoint_address = 0;
+ packet.endpoint_size_max = dev->packet_size_max0;
+ packet.endpoint_type = USBH_ENDPOINT_TYPE_CONTROL;
+ packet.control_type = USBH_CONTROL_TYPE_DATA;
+ packet.speed = dev->speed;
+ packet.callback = callback;
+ packet.callback_arg = dev;
+ packet.toggle = &dev->toggle0;
+
+ usbh_write(dev, &packet);
+ LOG_PRINTF("WR-data@device...%d \n", dev->address);
+}
+
+static void device_xfer_control_read(void *data, uint16_t datalen, usbh_packet_callback_t callback, usbh_device_t *dev)
+{
+ usbh_packet_t packet;
+
+ packet.data.in = data;
+ packet.datalen = datalen;
+ packet.address = dev->address;
+ packet.endpoint_address = 0;
+ packet.endpoint_size_max = dev->packet_size_max0;
+ packet.endpoint_type = USBH_ENDPOINT_TYPE_CONTROL;
+ packet.speed = dev->speed;
+ packet.callback = callback;
+ packet.callback_arg = dev;
+ packet.toggle = &dev->toggle0;
+
+ usbh_read(dev, &packet);
+ LOG_PRINTF("RD@device...%d | \n", dev->address);
+}
+
+
+static void control_state_machine(usbh_device_t *dev, usbh_packet_callback_data_t cb_data)
+{
+ switch (dev->control.state) {
+ case USBH_CONTROL_STATE_SETUP:
+ if (cb_data.status != USBH_PACKET_CALLBACK_STATUS_OK) {
+ dev->control.state = USBH_CONTROL_STATE_NONE;
+ // Unable to deliver setup control packet - this is a fatal error
+ usbh_packet_callback_data_t ret_data;
+ ret_data.status = USBH_PACKET_CALLBACK_STATUS_EFATAL;
+ ret_data.transferred_length = 0;
+ dev->control.callback(dev, ret_data);
+ break;
+ }
+ if (dev->control.setup_data.bmRequestType & USB_REQ_TYPE_IN) {
+ dev->control.state = USBH_CONTROL_STATE_DATA;
+ device_xfer_control_read(dev->control.data.in, dev->control.data_length, control_state_machine, dev);
+ } else {
+ if (dev->control.data_length == 0) {
+ dev->control.state = USBH_CONTROL_STATE_STATUS;
+ device_xfer_control_read(NULL, 0, control_state_machine, dev);
+ } else {
+ dev->control.state = USBH_CONTROL_STATE_DATA;
+ device_xfer_control_write_data(dev->control.data.out, dev->control.data_length, control_state_machine, dev);
+ }
+ }
+ break;
+
+ case USBH_CONTROL_STATE_DATA:
+ if (dev->control.setup_data.bmRequestType & USB_REQ_TYPE_IN) {
+ dev->control.state = USBH_CONTROL_STATE_NONE;
+ dev->control.callback(dev, cb_data);
+ } else {
+ if (cb_data.status != USBH_PACKET_CALLBACK_STATUS_OK) {
+ dev->control.state = USBH_CONTROL_STATE_NONE;
+ // Unable to deliver data control packet - this is a fatal error
+ usbh_packet_callback_data_t ret_data;
+ ret_data.status = USBH_PACKET_CALLBACK_STATUS_EFATAL;
+ ret_data.transferred_length = 0;
+ dev->control.callback(dev, ret_data);
+ break;
+ }
+
+ if (dev->control.data_length == 0) {
+ // we should be in status state when the length of data is zero
+ LOG_PRINTF("Control logic error\n");
+ dev->control.state = USBH_CONTROL_STATE_NONE;
+ dev->control.callback(dev, cb_data);
+ } else {
+ dev->control.state = USBH_CONTROL_STATE_STATUS;
+ device_xfer_control_read(NULL, 0, control_state_machine, dev);
+ }
+ }
+ break;
+
+ case USBH_CONTROL_STATE_STATUS:
+ dev->control.state = USBH_CONTROL_STATE_NONE;
+ dev->control.callback(dev, cb_data);
+ break;
+
+ default:
+ break;
+ }
+}
+
+void device_control(usbh_device_t *dev, usbh_packet_callback_t callback, const struct usb_setup_data *setup_data, void *data)
+{
+ if (dev->control.state != USBH_CONTROL_STATE_NONE) {
+ LOG_PRINTF("ERROR: Use of control state machine while not idle\n");
+ return;
+ }
+
+ dev->control.state = USBH_CONTROL_STATE_SETUP;
+ dev->control.callback = callback;
+ dev->control.data.out = data;
+ dev->control.data_length = setup_data->wLength;
+ dev->control.setup_data = *setup_data;
+ device_xfer_control_write_setup(&dev->control.setup_data, sizeof(dev->control.setup_data), control_state_machine, dev);
+}
+
+
+bool usbh_enum_available(void)
+{
+ return !enumeration();
+}
+
+/**
+ * Returns 0 on error
+ * device otherwise
+ */
+usbh_device_t *usbh_get_free_device(const usbh_device_t *dev)
+{
+ const usbh_low_level_driver_t *lld = dev->lld;
+ usbh_generic_data_t *lld_data = lld->driver_data;
+ usbh_device_t *usbh_device = lld_data->usbh_device;
+
+ uint8_t i;
+ LOG_PRINTF("DEV ADDRESS%d\n", dev->address);
+ for (i = 0; i < USBH_MAX_DEVICES; i++) {
+ if (usbh_device[i].address < 0) {
+ LOG_PRINTF("\t\t\t\t\tFOUND: %d", i);
+ usbh_device[i].address = i+1;
+ return &usbh_device[i];
+ } else {
+ LOG_PRINTF("address: %d\n\n\n", usbh_device[i].address);
+ }
+ }
+
+ return 0;
+}
+
+static void device_enumeration_finish(usbh_device_t *dev)
+{
+ reset_enumeration();
+ dev->state = USBH_ENUM_STATE_FIRST;
+}
+
+static void device_enumeration_terminate(usbh_device_t *dev)
+{
+ dev->address = -1;
+ device_enumeration_finish(dev);
+}
+
+#define CONTINUE_WITH(en) \
+ dev->state = en;\
+ device_enumerate(dev, cb_data);
+
+static void device_enumerate(usbh_device_t *dev, usbh_packet_callback_data_t cb_data)
+{
+ const usbh_low_level_driver_t *lld = dev->lld;
+ usbh_generic_data_t *lld_data = lld->driver_data;
+ uint8_t *usbh_buffer = lld_data->usbh_buffer;
+// LOG_PRINTF("\nSTATE: %d\n", state);
+ switch (dev->state) {
+ case USBH_ENUM_STATE_SET_ADDRESS:
+ switch (cb_data.status) {
+ case USBH_PACKET_CALLBACK_STATUS_OK:
+ if (dev->address == 0) {
+ dev->address = usbh_data.address_temporary;
+ LOG_PRINTF("Assigned address: %d\n", dev->address);
+ }
+ CONTINUE_WITH(USBH_ENUM_STATE_DEVICE_DT_READ_SETUP);
+ break;
+
+ default:
+ device_enumeration_terminate(dev);
+ ERROR(cb_data.status);
+ break;
+ }
+ break;
+
+ case USBH_ENUM_STATE_DEVICE_DT_READ_SETUP:
+ {
+ struct usb_setup_data setup_data;
+
+ setup_data.bmRequestType = USB_REQ_TYPE_IN | USB_REQ_TYPE_DEVICE;
+ setup_data.bRequest = USB_REQ_GET_DESCRIPTOR;
+ setup_data.wValue = USB_DT_DEVICE << 8;
+ setup_data.wIndex = 0;
+ setup_data.wLength = USB_DT_DEVICE_SIZE;
+
+ dev->state = USBH_ENUM_STATE_DEVICE_DT_READ_COMPLETE;
+ device_control(dev, device_enumerate, &setup_data, &usbh_buffer[0]);
+ }
+ break;
+
+ case USBH_ENUM_STATE_DEVICE_DT_READ_COMPLETE:
+ {
+ switch (cb_data.status) {
+ case USBH_PACKET_CALLBACK_STATUS_OK:
+ {
+ struct usb_device_descriptor *ddt =
+ (struct usb_device_descriptor *)&usbh_buffer[0];
+ dev->packet_size_max0 = ddt->bMaxPacketSize0;
+ LOG_PRINTF("Found device with vid=0x%04x pid=0x%04x\n", ddt->idVendor, ddt->idProduct);
+ LOG_PRINTF("class=0x%02x subclass=0x%02x protocol=0x%02x\n", ddt->bDeviceClass, ddt->bDeviceSubClass, ddt->bDeviceProtocol);
+ CONTINUE_WITH(USBH_ENUM_STATE_CONFIGURATION_DT_HEADER_READ_SETUP)
+ }
+ break;
+
+ case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+ if (cb_data.transferred_length >= 8) {
+ struct usb_device_descriptor *ddt =
+ (struct usb_device_descriptor *)&usbh_buffer[0];
+ dev->packet_size_max0 = ddt->bMaxPacketSize0;
+ CONTINUE_WITH(USBH_ENUM_STATE_DEVICE_DT_READ_SETUP);
+ } else {
+ device_enumeration_terminate(dev);
+ ERROR(cb_data.status);
+ }
+ break;
+
+ default:
+ device_enumeration_terminate(dev);
+ ERROR(cb_data.status);
+ break;
+ }
+ }
+ break;
+
+ case USBH_ENUM_STATE_CONFIGURATION_DT_HEADER_READ_SETUP:
+ {
+ struct usb_setup_data setup_data;
+
+ setup_data.bmRequestType = USB_REQ_TYPE_IN | USB_REQ_TYPE_DEVICE;
+ setup_data.bRequest = USB_REQ_GET_DESCRIPTOR;
+ setup_data.wValue = USB_DT_CONFIGURATION << 8;
+ setup_data.wIndex = 0;
+ setup_data.wLength = dev->packet_size_max0;
+
+ dev->state = USBH_ENUM_STATE_CONFIGURATION_DT_HEADER_READ;
+ device_xfer_control_write_setup(&setup_data, sizeof(setup_data),
+ device_enumerate, dev);
+ }
+ break;
+
+ case USBH_ENUM_STATE_CONFIGURATION_DT_HEADER_READ:
+ {
+ switch (cb_data.status) {
+ case USBH_PACKET_CALLBACK_STATUS_OK:
+ dev->state = USBH_ENUM_STATE_CONFIGURATION_DT_HEADER_READ_COMPLETE;
+ device_xfer_control_read(&usbh_buffer[USB_DT_DEVICE_SIZE],
+ dev->packet_size_max0, device_enumerate, dev);
+ break;
+
+ default:
+ device_enumeration_terminate(dev);
+ ERROR(cb_data.status);
+ break;
+ }
+ }
+ break;
+
+ case USBH_ENUM_STATE_CONFIGURATION_DT_HEADER_READ_COMPLETE:
+ {
+ switch (cb_data.status) {
+ case USBH_PACKET_CALLBACK_STATUS_OK:
+ CONTINUE_WITH(USBH_ENUM_STATE_CONFIGURATION_DT_READ_SETUP);
+ break;
+
+ case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+ if (cb_data.transferred_length >= USB_DT_CONFIGURATION_SIZE) {
+ struct usb_config_descriptor *cdt =
+ (struct usb_config_descriptor *)&usbh_buffer[USB_DT_DEVICE_SIZE];
+ if (cb_data.transferred_length == cdt->wTotalLength) {
+ LOG_PRINTF("Configuration descriptor read complete. length: %d\n", cdt->wTotalLength);
+ CONTINUE_WITH(USBH_ENUM_STATE_SET_CONFIGURATION_SETUP);
+ }
+ }
+ break;
+
+ default:
+ device_enumeration_terminate(dev);
+ ERROR(cb_data.status);
+ break;
+ }
+ }
+ break;
+
+ case USBH_ENUM_STATE_CONFIGURATION_DT_READ_SETUP:
+ {
+ struct usb_config_descriptor *cdt =
+ (struct usb_config_descriptor *)&usbh_buffer[USB_DT_DEVICE_SIZE];
+ struct usb_setup_data setup_data;
+ LOG_PRINTF("Getting complete configuration descriptor of length: %d bytes\n", cdt->wTotalLength);
+ setup_data.bmRequestType = USB_REQ_TYPE_IN | USB_REQ_TYPE_DEVICE;
+ setup_data.bRequest = USB_REQ_GET_DESCRIPTOR;
+ setup_data.wValue = USB_DT_CONFIGURATION << 8;
+ setup_data.wIndex = 0;
+ setup_data.wLength = cdt->wTotalLength;
+
+ dev->state = USBH_ENUM_STATE_CONFIGURATION_DT_READ;
+ device_xfer_control_write_setup(&setup_data, sizeof(setup_data),
+ device_enumerate, dev);
+ }
+ break;
+
+ case USBH_ENUM_STATE_CONFIGURATION_DT_READ:
+ {
+ switch (cb_data.status) {
+ case USBH_PACKET_CALLBACK_STATUS_OK:
+ {
+ struct usb_config_descriptor *cdt =
+ (struct usb_config_descriptor *)&usbh_buffer[USB_DT_DEVICE_SIZE];
+ dev->state = USBH_ENUM_STATE_CONFIGURATION_DT_READ_COMPLETE;
+ device_xfer_control_read(&usbh_buffer[USB_DT_DEVICE_SIZE],
+ cdt->wTotalLength, device_enumerate, dev);
+ }
+ break;
+
+ default:
+ device_enumeration_terminate(dev);
+ ERROR(cb_data.status);
+ break;
+ }
+ }
+ break;
+
+ case USBH_ENUM_STATE_CONFIGURATION_DT_READ_COMPLETE:
+ {
+ switch (cb_data.status) {
+ case USBH_PACKET_CALLBACK_STATUS_OK:
+ {
+ struct usb_config_descriptor *cdt =
+ (struct usb_config_descriptor *)&usbh_buffer[USB_DT_DEVICE_SIZE];
+ LOG_PRINTF("Configuration descriptor read complete. length: %d\n", cdt->wTotalLength);
+ CONTINUE_WITH(USBH_ENUM_STATE_SET_CONFIGURATION_SETUP);
+
+ }
+ break;
+
+ default:
+ device_enumeration_terminate(dev);
+ ERROR(cb_data.status);
+ break;
+ }
+
+ }
+ break;
+
+ case USBH_ENUM_STATE_SET_CONFIGURATION_SETUP:
+ {
+ struct usb_config_descriptor *cdt =
+ (struct usb_config_descriptor *)&usbh_buffer[USB_DT_DEVICE_SIZE];
+
+ struct usb_setup_data setup_data;
+
+ setup_data.bmRequestType = USB_REQ_TYPE_STANDARD | USB_REQ_TYPE_DEVICE;
+ setup_data.bRequest = USB_REQ_SET_CONFIGURATION;
+ setup_data.wValue = cdt->bConfigurationValue;
+ setup_data.wIndex = 0;
+ setup_data.wLength = 0;
+
+ dev->state = USBH_ENUM_STATE_SET_CONFIGURATION_COMPLETE;
+ device_control(dev, device_enumerate, &setup_data, 0);
+ }
+ break;
+
+ case USBH_ENUM_STATE_SET_CONFIGURATION_COMPLETE:
+ {
+ switch (cb_data.status) {
+ case USBH_PACKET_CALLBACK_STATUS_OK:
+ CONTINUE_WITH(USBH_ENUM_STATE_FIND_DRIVER);
+ break;
+
+ default:
+ device_enumeration_terminate(dev);
+ ERROR(cb_data.status);
+ break;
+ }
+ }
+ break;
+
+ case USBH_ENUM_STATE_FIND_DRIVER:
+ {
+ struct usb_config_descriptor *cdt =
+ (struct usb_config_descriptor *)&usbh_buffer[USB_DT_DEVICE_SIZE];
+ device_register(usbh_buffer, cdt->wTotalLength + USB_DT_DEVICE_SIZE, dev);
+
+ device_enumeration_finish(dev);
+ }
+ break;
+
+ default:
+ LOG_PRINTF("Error: Unknown state "__FILE__"/%d\n", __LINE__);
+ break;
+ }
+}
+
+void device_enumeration_start(usbh_device_t *dev)
+{
+ set_enumeration();
+
+ // save address
+ uint8_t address = dev->address;
+ dev->address = 0;
+
+ if (dev->speed == USBH_SPEED_LOW) {
+ dev->packet_size_max0 = 8;
+ } else {
+ dev->packet_size_max0 = 64;
+ }
+
+ usbh_data.address_temporary = address;
+
+ LOG_PRINTF("\n\n\n ENUMERATION OF DEVICE@%d STARTED \n\n", address);
+
+ dev->state = USBH_ENUM_STATE_SET_ADDRESS;
+ struct usb_setup_data setup_data;
+
+ setup_data.bmRequestType = USB_REQ_TYPE_STANDARD | USB_REQ_TYPE_DEVICE;
+ setup_data.bRequest = USB_REQ_SET_ADDRESS;
+ setup_data.wValue = address;
+ setup_data.wIndex = 0;
+ setup_data.wLength = 0;
+
+ device_control(dev, device_enumerate, &setup_data, 0);
+}
+
+/**
+ * Should be called with at least 1kHz frequency
+ *
+ */
+void usbh_poll(uint32_t time_curr_us)
+{
+ uint32_t k = 0;
+ while (usbh_data.lld_drivers[k]) {
+ usbh_device_t *usbh_device =
+ ((usbh_generic_data_t *)(usbh_data.lld_drivers[k]->driver_data))->usbh_device;
+ usbh_generic_data_t *lld_data = usbh_data.lld_drivers[k]->driver_data;
+
+ enum USBH_POLL_STATUS poll_status =
+ usbh_data.lld_drivers[k]->poll(lld_data, time_curr_us);
+
+ switch (poll_status) {
+ case USBH_POLL_STATUS_DEVICE_CONNECTED:
+ // New device found
+ LOG_PRINTF("\nDEVICE FOUND\n");
+ usbh_device[0].lld = usbh_data.lld_drivers[k];
+ usbh_device[0].speed = usbh_data.lld_drivers[k]->root_speed(lld_data);
+ usbh_device[0].address = 1;
+ usbh_device[0].control.state = USBH_CONTROL_STATE_NONE;
+
+ device_enumeration_start(&usbh_device[0]);
+ break;
+
+ case USBH_POLL_STATUS_DEVICE_DISCONNECTED:
+ {
+ usbh_device[0].control.state = USBH_CONTROL_STATE_NONE;
+ uint32_t i;
+ for (i = 0; i < USBH_MAX_DEVICES; i++) {
+ device_remove(&usbh_device[i]);
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (lld_data->usbh_device[0].drv && usbh_device[0].drvdata) {
+ usbh_device[0].drv->poll(usbh_device[0].drvdata, time_curr_us);
+ }
+
+ k++;
+ }
+}
+
+void usbh_read(usbh_device_t *dev, usbh_packet_t *packet)
+{
+ const usbh_low_level_driver_t *lld = dev->lld;
+ lld->read(lld->driver_data, packet);
+}
+
+void usbh_write(usbh_device_t *dev, const usbh_packet_t *packet)
+{
+ const usbh_low_level_driver_t *lld = dev->lld;
+ lld->write(lld->driver_data, packet);
+}
+
diff --git a/fw/src/usbh_driver_ac_midi.c b/fw/src/usbh_driver_ac_midi.c
new file mode 100644
index 0000000..96cf383
--- /dev/null
+++ b/fw/src/usbh_driver_ac_midi.c
@@ -0,0 +1,364 @@
+/*
+ * This file is part of the libusbhost library
+ * hosted at http://github.com/libusbhost/libusbhost
+ *
+ * Copyright (C) 2016 Amir Hammad <amir.hammad@hotmail.com>
+ *
+ *
+ * libusbhost is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "driver/usbh_device_driver.h"
+#include "usbh_driver_ac_midi_private.h"
+#include "usart_helpers.h"
+
+#include <stddef.h>
+
+#include <libopencm3/usb/midi.h>
+#include <libopencm3/usb/audio.h>
+#include <libopencm3/usb/usbstd.h>
+
+static midi_device_t midi_device[USBH_AC_MIDI_MAX_DEVICES];
+static const midi_config_t *midi_config = NULL;
+static bool initialized = false;
+
+void midi_driver_init(const midi_config_t *config)
+{
+ uint32_t i;
+ midi_config = config;
+ for (i = 0; i < USBH_AC_MIDI_MAX_DEVICES; i++) {
+ midi_device[i].state = 0;
+ }
+ initialized = true;
+}
+/**
+ *
+ *
+ */
+static void *init(usbh_device_t *usbh_dev)
+{
+ if (!midi_config || !initialized) {
+ LOG_PRINTF("\n%s/%d : driver not initialized\n", __FILE__, __LINE__);
+ return 0;
+ }
+ uint32_t i;
+ midi_device_t *drvdata = NULL;
+
+ // find free data space for midi device
+ for (i = 0; i < USBH_AC_MIDI_MAX_DEVICES; i++) {
+ if (midi_device[i].state == 0) {
+ drvdata = &midi_device[i];
+ drvdata->device_id = i;
+ drvdata->endpoint_in_address = 0;
+ drvdata->endpoint_out_address = 0;
+ drvdata->endpoint_in_toggle = 0;
+ drvdata->endpoint_out_toggle = 0;
+ drvdata->usbh_device = usbh_dev;
+ drvdata->write_callback_user = NULL;
+ drvdata->sending = false;
+ break;
+ }
+ }
+
+ return drvdata;
+}
+
+/**
+ * Returns true if all needed data are parsed
+ */
+static bool analyze_descriptor(void *drvdata, void *descriptor)
+{
+ midi_device_t *midi = drvdata;
+ uint8_t desc_type = ((uint8_t *)descriptor)[1];
+ switch (desc_type) {
+ case USB_DT_CONFIGURATION:
+ {
+ struct usb_config_descriptor *cfg =
+ (struct usb_config_descriptor*)descriptor;
+ midi->buffer[0] = cfg->bConfigurationValue;
+ }
+ break;
+ case USB_DT_DEVICE:
+ break;
+ case USB_DT_INTERFACE:
+ break;
+ case USB_DT_ENDPOINT:
+ {
+ struct usb_endpoint_descriptor *ep =
+ (struct usb_endpoint_descriptor*)descriptor;
+ if ((ep->bmAttributes&0x03) == USB_ENDPOINT_ATTR_BULK) {
+ uint8_t epaddr = ep->bEndpointAddress;
+ if (epaddr & (1<<7)) {
+ midi->endpoint_in_address = epaddr&0x7f;
+ if (ep->wMaxPacketSize < USBH_AC_MIDI_BUFFER) {
+ midi->endpoint_in_maxpacketsize = ep->wMaxPacketSize;
+ } else {
+ midi->endpoint_in_maxpacketsize = USBH_AC_MIDI_BUFFER;
+ }
+ } else {
+ midi->endpoint_out_address = epaddr;
+ midi->endpoint_out_maxpacketsize = ep->wMaxPacketSize;
+ }
+
+ if (midi->endpoint_in_address && midi->endpoint_out_address) {
+ midi->state = 1;
+ return true;
+ }
+ }
+ }
+ break;
+
+ case USB_AUDIO_DT_CS_ENDPOINT:
+ {
+ struct usb_midi_in_jack_descriptor *midi_in_jack_desc =
+ (struct usb_midi_in_jack_descriptor *) descriptor;
+ (void)midi_in_jack_desc;
+ }
+ break;
+ // TODO Class Specific descriptors
+ default:
+ break;
+ }
+ return false;
+}
+
+static void midi_in_message(midi_device_t *midi, const uint8_t datalen)
+{
+ uint8_t i = 0;
+ if (midi_config->read_callback) {
+ for (i = 0; i < datalen; i += 4) {
+
+// uint8_t cable_number = (midi->buffer[i] & 0xf0) >> 4;
+ uint8_t code_id = midi->buffer[i]&0xf;
+
+ uint8_t *ptrdata = &midi->buffer[i];
+ if (code_id < 2) {
+ continue;
+ }
+ midi_config->read_callback(midi->device_id, ptrdata);
+ }
+ }
+}
+
+static void event(usbh_device_t *dev, usbh_packet_callback_data_t status)
+{
+ midi_device_t *midi = (midi_device_t *)dev->drvdata;
+ switch (midi->state) {
+ case 26:
+ {
+ switch (status.status) {
+ case USBH_PACKET_CALLBACK_STATUS_OK:
+ midi_in_message(midi, midi->endpoint_in_maxpacketsize);
+ midi->state = 25;
+ break;
+
+ case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+ midi_in_message(midi, status.transferred_length);
+ midi->state = 25;
+ break;
+
+ default:
+ LOG_PRINTF("FATAL ERROR, MIDI DRIVER DEAD \n");
+ //~ dev->drv->remove();
+ midi->state = 0;
+ break;
+ }
+ }
+ break;
+
+ case 102:
+ {
+ midi->state = 101;
+ LOG_PRINTF("\n CAN'T TOUCH THIS... ignoring data\n");
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+static void read_midi_in(void *drvdata, const uint8_t nextstate)
+{
+ midi_device_t *midi = drvdata;
+ usbh_packet_t packet;
+
+ packet.address = midi->usbh_device->address;
+ packet.data.in = &midi->buffer[0];
+ packet.datalen = midi->endpoint_in_maxpacketsize;
+ packet.endpoint_address = midi->endpoint_in_address;
+ packet.endpoint_size_max = midi->endpoint_in_maxpacketsize;
+ packet.endpoint_type = USBH_ENDPOINT_TYPE_BULK;
+ packet.speed = midi->usbh_device->speed;
+ packet.callback = event;
+ packet.callback_arg = midi->usbh_device;
+ packet.toggle = &midi->endpoint_in_toggle;
+
+ midi->state = nextstate;
+ usbh_read(midi->usbh_device,&packet);
+}
+
+/**
+ *
+ * @param t_us global time us
+ */
+static void poll(void *drvdata, uint32_t t_us)
+{
+ (void)drvdata;
+
+ midi_device_t *midi = drvdata;
+ switch (midi->state) {
+
+ /// Upon configuration, some controllers send additional error data
+ /// case 100, 101, 102 cares for ignoring those data
+ case 100:
+ {
+ midi->time_us_config = t_us;
+ midi->state = 101;
+ }
+ break;
+
+ case 101:
+ {
+ read_midi_in(drvdata, 102);
+ }
+ break;
+
+ case 102:
+ {
+ // if elapsed MIDI initial delay microseconds
+ if (t_us - midi->time_us_config > MIDI_INITIAL_DELAY) {
+ midi->state = 26;
+ }
+ }
+ break;
+
+ case 25:
+ {
+ read_midi_in(drvdata, 26);
+ }
+ break;
+
+ case 1:
+ {
+ midi->state = 100;
+
+ midi->endpoint_in_toggle = 0;
+ LOG_PRINTF("\nMIDI CONFIGURED\n");
+
+ // Notify user
+ if (midi_config->notify_connected) {
+ midi_config->notify_connected(midi->device_id);
+ }
+ }
+ break;
+ }
+}
+
+// don't call directly
+static void write_callback(usbh_device_t *dev, usbh_packet_callback_data_t status)
+{
+ (void)status;
+ midi_device_t *midi = (midi_device_t *)dev->drvdata;
+
+ if (midi->sending) {
+ midi->sending = false;
+ const midi_write_callback_t callback = midi->write_callback_user;
+ if (!callback) {
+ return;
+ }
+
+ if (status.status & USBH_PACKET_CALLBACK_STATUS_OK) {
+ callback(midi->write_packet.datalen);
+ } else {
+ if (status.status & USBH_PACKET_CALLBACK_STATUS_ERRSIZ) {
+ const uint32_t length = status.transferred_length;
+ callback(length);
+ } else {
+ callback(0);
+ }
+ }
+ }
+}
+
+void usbh_midi_write(uint8_t device_id, const void *data, uint32_t length, midi_write_callback_t callback)
+{
+ // bad device_id handling
+ if (device_id >= USBH_AC_MIDI_MAX_DEVICES) {
+ return;
+ }
+
+ midi_device_t *midi = &midi_device[device_id];
+
+ // device with provided device_id is not alive
+ if (midi->state == 0) {
+ return;
+ }
+
+ usbh_device_t *dev = midi->usbh_device;
+ if (midi->endpoint_out_address == 0) {
+ return;
+ }
+
+ midi->sending = true;
+ midi->write_callback_user = callback;
+
+ midi->write_packet.data.out = data;
+ midi->write_packet.datalen = length;
+ midi->write_packet.address = dev->address;
+ midi->write_packet.endpoint_address = midi->endpoint_out_address;
+ midi->write_packet.endpoint_size_max = midi->endpoint_out_maxpacketsize;
+ midi->write_packet.endpoint_type = USBH_ENDPOINT_TYPE_BULK;
+ midi->write_packet.speed = dev->speed;
+ midi->write_packet.callback = write_callback;
+ midi->write_packet.callback_arg = midi->usbh_device;
+ midi->write_packet.toggle = &midi->endpoint_out_toggle;
+
+
+ usbh_write(dev, &midi->write_packet);
+}
+
+static void remove(void *drvdata)
+{
+ midi_device_t *midi = drvdata;
+
+ if (midi_config->notify_disconnected) {
+ midi_config->notify_disconnected(midi->device_id);
+ }
+
+ midi->state = 0;
+ midi->endpoint_in_address = 0;
+ midi->endpoint_out_address = 0;
+}
+
+static const usbh_dev_driver_info_t usbh_midi_driver_info = {
+ .deviceClass = -1,
+ .deviceSubClass = -1,
+ .deviceProtocol = -1,
+ .idVendor = -1,
+ .idProduct = -1,
+ .ifaceClass = 0x01,
+ .ifaceSubClass = 0x03,
+ .ifaceProtocol = -1,
+};
+
+const usbh_dev_driver_t usbh_midi_driver = {
+ .init = init,
+ .analyze_descriptor = analyze_descriptor,
+ .poll = poll,
+ .remove = remove,
+ .info = &usbh_midi_driver_info
+};
diff --git a/fw/src/usbh_driver_ac_midi_private.h b/fw/src/usbh_driver_ac_midi_private.h
new file mode 100644
index 0000000..b92ee97
--- /dev/null
+++ b/fw/src/usbh_driver_ac_midi_private.h
@@ -0,0 +1,52 @@
+/*
+ * This file is part of the libusbhost library
+ * hosted at http://github.com/libusbhost/libusbhost
+ *
+ * Copyright (C) 2016 Amir Hammad <amir.hammad@hotmail.com>
+ *
+ *
+ * libusbhost is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef USBH_DRIVER_AC_MIDI_PRIVATE_
+#define USBH_DRIVER_AC_MIDI_PRIVATE_
+
+#include "driver/usbh_device_driver.h"
+#include "usbh_driver_ac_midi.h"
+
+#include <stdint.h>
+
+
+#define MIDI_INITIAL_DELAY (100000)
+
+struct _midi_device {
+ usbh_device_t *usbh_device;
+ uint8_t buffer[USBH_AC_MIDI_BUFFER];
+ uint16_t endpoint_in_maxpacketsize;
+ uint16_t endpoint_out_maxpacketsize;
+ uint8_t endpoint_in_address;
+ uint8_t endpoint_out_address;
+ uint8_t state;
+ uint8_t endpoint_in_toggle;
+ uint8_t endpoint_out_toggle;
+ uint8_t device_id;
+ bool sending;
+ midi_write_callback_t write_callback_user;
+ usbh_packet_t write_packet;
+ // Timestamp at sending config command
+ uint32_t time_us_config;
+};
+typedef struct _midi_device midi_device_t;
+#endif
diff --git a/fw/src/usbh_driver_gp_xbox.c b/fw/src/usbh_driver_gp_xbox.c
new file mode 100644
index 0000000..957cb3e
--- /dev/null
+++ b/fw/src/usbh_driver_gp_xbox.c
@@ -0,0 +1,368 @@
+/*
+ * This file is part of the libusbhost library
+ * hosted at http://github.com/libusbhost/libusbhost
+ *
+ * Copyright (C) 2015 Amir Hammad <amir.hammad@hotmail.com>
+ *
+ *
+ * libusbhost is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include "usart_helpers.h"
+#include "usbh_driver_gp_xbox.h"
+#include "driver/usbh_device_driver.h"
+
+#include <stdint.h>
+#include <libopencm3/usb/usbstd.h>
+
+enum STATES {
+ STATE_INACTIVE,
+ STATE_INITIAL,
+ STATE_READING_REQUEST,
+ STATE_READING_COMPLETE,
+};
+
+#define GP_XBOX_CORRECT_TRANSFERRED_LENGTH 20
+
+struct _gp_xbox_device {
+ usbh_device_t *usbh_device;
+ uint8_t buffer[USBH_GP_XBOX_BUFFER];
+ uint16_t endpoint_in_maxpacketsize;
+ uint8_t endpoint_in_address;
+ enum STATES state_next;
+ uint8_t endpoint_in_toggle;
+ uint8_t device_id;
+ uint8_t configuration_value;
+};
+typedef struct _gp_xbox_device gp_xbox_device_t;
+
+static gp_xbox_device_t gp_xbox_device[USBH_GP_XBOX_MAX_DEVICES];
+static const gp_xbox_config_t *gp_xbox_config;
+
+static bool initialized = false;
+static void read_gp_xbox_in(gp_xbox_device_t *gp_xbox);
+
+void gp_xbox_driver_init(const gp_xbox_config_t *config)
+{
+ if (!config) {
+ return;
+ }
+ initialized = true;
+ uint32_t i;
+ gp_xbox_config = config;
+ for (i = 0; i < USBH_GP_XBOX_MAX_DEVICES; i++) {
+ gp_xbox_device[i].state_next = STATE_INACTIVE;
+ }
+}
+
+/**
+ *
+ *
+ */
+static void *init(usbh_device_t *usbh_dev)
+{
+ if (!initialized) {
+ LOG_PRINTF("\n%s/%d : driver not initialized\n", __FILE__, __LINE__);
+ return 0;
+ }
+
+ uint32_t i;
+ gp_xbox_device_t *drvdata = 0;
+
+ // find free data space for gp_xbox device
+ for (i = 0; i < USBH_GP_XBOX_MAX_DEVICES; i++) {
+ if (gp_xbox_device[i].state_next == STATE_INACTIVE) {
+ drvdata = &gp_xbox_device[i];
+ drvdata->device_id = i;
+ drvdata->endpoint_in_address = 0;
+ drvdata->endpoint_in_toggle = 0;
+ drvdata->usbh_device = usbh_dev;
+ break;
+ }
+ }
+
+ return drvdata;
+}
+
+/**
+ * Returns true if all needed data are parsed
+ */
+static bool analyze_descriptor(void *drvdata, void *descriptor)
+{
+ gp_xbox_device_t *gp_xbox = (gp_xbox_device_t *)drvdata;
+ uint8_t desc_type = ((uint8_t *)descriptor)[1];
+ switch (desc_type) {
+ case USB_DT_CONFIGURATION:
+ {
+ struct usb_config_descriptor *cfg = (struct usb_config_descriptor*)descriptor;
+ gp_xbox->configuration_value = cfg->bConfigurationValue;
+ }
+ break;
+ case USB_DT_DEVICE:
+ break;
+ case USB_DT_INTERFACE:
+ break;
+ case USB_DT_ENDPOINT:
+ {
+ struct usb_endpoint_descriptor *ep = (struct usb_endpoint_descriptor*)descriptor;
+ if ((ep->bmAttributes&0x03) == USB_ENDPOINT_ATTR_INTERRUPT) {
+ uint8_t epaddr = ep->bEndpointAddress;
+ if (epaddr & (1<<7)) {
+ gp_xbox->endpoint_in_address = epaddr&0x7f;
+ if (ep->wMaxPacketSize < USBH_GP_XBOX_BUFFER) {
+ gp_xbox->endpoint_in_maxpacketsize = ep->wMaxPacketSize;
+ } else {
+ gp_xbox->endpoint_in_maxpacketsize = USBH_GP_XBOX_BUFFER;
+ }
+ }
+
+ if (gp_xbox->endpoint_in_address) {
+ gp_xbox->state_next = STATE_INITIAL;
+ return true;
+ }
+ }
+ }
+ break;
+ // TODO Class Specific descriptors
+ default:
+ break;
+ }
+ return false;
+}
+
+static void parse_data(usbh_device_t *dev)
+{
+ gp_xbox_device_t *gp_xbox = (gp_xbox_device_t *)dev->drvdata;
+
+ uint8_t *packet = gp_xbox->buffer;
+
+ gp_xbox_packet_t gp_xbox_packet;
+ gp_xbox_packet.buttons = 0;
+
+ // DPAD
+ const uint8_t data1 = packet[2];
+ const uint8_t data2 = packet[3];
+ if (data1 & (1 << 0)) {
+ gp_xbox_packet.buttons |= GP_XBOX_DPAD_TOP;
+ }
+
+ if (data1 & (1 << 1)) {
+ gp_xbox_packet.buttons |= GP_XBOX_DPAD_BOTTOM;
+ }
+
+ if (data1 & (1 << 2)) {
+ gp_xbox_packet.buttons |= GP_XBOX_DPAD_LEFT;
+ }
+
+ if (data1 & (1 << 3)) {
+ gp_xbox_packet.buttons |= GP_XBOX_DPAD_RIGHT;
+ }
+
+ // Start + select
+
+ if (data1 & (1 << 4)) {
+ gp_xbox_packet.buttons |= GP_XBOX_BUTTON_START;
+ }
+
+ if (data1 & (1 << 5)) {
+ gp_xbox_packet.buttons |= GP_XBOX_BUTTON_SELECT;
+ }
+
+ // axis buttons
+
+ if (data1 & (1 << 6)) {
+ gp_xbox_packet.buttons |= GP_XBOX_BUTTON_AXIS_LEFT;
+ }
+
+ if (data1 & (1 << 7)) {
+ gp_xbox_packet.buttons |= GP_XBOX_BUTTON_AXIS_RIGHT;
+ }
+
+ // buttons ABXY
+
+ if (data2 & (1 << 4)) {
+ gp_xbox_packet.buttons |= GP_XBOX_BUTTON_A;
+ }
+
+ if (data2 & (1 << 5)) {
+ gp_xbox_packet.buttons |= GP_XBOX_BUTTON_B;
+ }
+
+ if (data2 & (1 << 6)) {
+ gp_xbox_packet.buttons |= GP_XBOX_BUTTON_X;
+ }
+
+ if (data2 & (1 << 7)) {
+ gp_xbox_packet.buttons |= GP_XBOX_BUTTON_Y;
+ }
+
+ // buttons rear
+
+ if (data2 & (1 << 0)) {
+ gp_xbox_packet.buttons |= GP_XBOX_BUTTON_LT;
+ }
+
+ if (data2 & (1 << 1)) {
+ gp_xbox_packet.buttons |= GP_XBOX_BUTTON_RT;
+ }
+
+ if (data2 & (1 << 2)) {
+ gp_xbox_packet.buttons |= GP_XBOX_BUTTON_XBOX;
+ }
+
+ // rear levers
+
+ gp_xbox_packet.axis_rear_left = packet[4];
+ gp_xbox_packet.axis_rear_right = packet[5];
+ gp_xbox_packet.axis_left_x = packet[7]*256 + packet[6];
+ gp_xbox_packet.axis_left_y = packet[9]*256 + packet[8];
+ gp_xbox_packet.axis_right_x = packet[11]*256 + packet[10];
+ gp_xbox_packet.axis_right_y = packet[13]*256 + packet[12];
+
+ // call update callback
+ if (gp_xbox_config->update) {
+ gp_xbox_config->update(gp_xbox->device_id, gp_xbox_packet);
+ }
+}
+
+static void event(usbh_device_t *dev, usbh_packet_callback_data_t cb_data)
+{
+ gp_xbox_device_t *gp_xbox = (gp_xbox_device_t *)dev->drvdata;
+ switch (gp_xbox->state_next) {
+ case STATE_READING_COMPLETE:
+ {
+ switch (cb_data.status) {
+ case USBH_PACKET_CALLBACK_STATUS_OK:
+ parse_data(dev);
+ gp_xbox->state_next = STATE_READING_REQUEST;
+ break;
+
+ case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+ if (cb_data.transferred_length == GP_XBOX_CORRECT_TRANSFERRED_LENGTH) {
+ parse_data(dev);
+ }
+ gp_xbox->state_next = STATE_READING_REQUEST;
+ break;
+
+ default:
+ ERROR(cb_data.status);
+ gp_xbox->state_next = STATE_INACTIVE;
+ break;
+ }
+ }
+ break;
+
+ case STATE_INACTIVE:
+ {
+ LOG_PRINTF("XBOX inactive");
+ }
+ break;
+ default:
+ {
+ LOG_PRINTF("Unknown state\n");
+ }
+ break;
+ }
+}
+
+
+static void read_gp_xbox_in(gp_xbox_device_t *gp_xbox)
+{
+ usbh_packet_t packet;
+
+ packet.address = gp_xbox->usbh_device->address;
+ packet.data.in = &gp_xbox->buffer[0];
+ packet.datalen = gp_xbox->endpoint_in_maxpacketsize;
+ packet.endpoint_address = gp_xbox->endpoint_in_address;
+ packet.endpoint_size_max = gp_xbox->endpoint_in_maxpacketsize;
+ packet.endpoint_type = USBH_ENDPOINT_TYPE_INTERRUPT;
+ packet.speed = gp_xbox->usbh_device->speed;
+ packet.callback = event;
+ packet.callback_arg = gp_xbox->usbh_device;
+ packet.toggle = &gp_xbox->endpoint_in_toggle;
+
+ gp_xbox->state_next = STATE_READING_COMPLETE;
+ usbh_read(gp_xbox->usbh_device, &packet);
+
+ // LOG_PRINTF("@gp_xbox EP1 | \n");
+}
+
+/**
+ * \param time_curr_us - monotically rising time (see usbh_hubbed.h)
+ * unit is microseconds
+ */
+static void poll(void *drvdata, uint32_t time_curr_us)
+{
+ (void)time_curr_us;
+
+ gp_xbox_device_t *gp_xbox = (gp_xbox_device_t *)drvdata;
+
+ switch (gp_xbox->state_next) {
+ case STATE_READING_REQUEST:
+ {
+ read_gp_xbox_in(gp_xbox);
+ }
+ break;
+
+ case STATE_INITIAL:
+ {
+ gp_xbox->state_next = STATE_READING_REQUEST;
+ gp_xbox->endpoint_in_toggle = 0;
+ LOG_PRINTF("\ngp_xbox CONFIGURED\n");
+ if (gp_xbox_config->notify_connected) {
+ gp_xbox_config->notify_connected(gp_xbox->device_id);
+ }
+ }
+ break;
+
+ default:
+ {
+ // do nothing - probably transfer is in progress
+ }
+ break;
+ }
+}
+
+static void remove(void *drvdata)
+{
+ LOG_PRINTF("Removing xbox\n");
+
+ gp_xbox_device_t *gp_xbox = (gp_xbox_device_t *)drvdata;
+ if (gp_xbox_config->notify_disconnected) {
+ gp_xbox_config->notify_disconnected(gp_xbox->device_id);
+ }
+ gp_xbox->state_next = STATE_INACTIVE;
+ gp_xbox->endpoint_in_address = 0;
+}
+
+static const usbh_dev_driver_info_t driver_info = {
+ .deviceClass = 0xff,
+ .deviceSubClass = 0xff,
+ .deviceProtocol = 0xff,
+ .idVendor = 0x045e,
+ .idProduct = 0x028e,
+ .ifaceClass = 0xff,
+ .ifaceSubClass = 93,
+ .ifaceProtocol = 0x01
+};
+
+const usbh_dev_driver_t usbh_gp_xbox_driver = {
+ .init = init,
+ .analyze_descriptor = analyze_descriptor,
+ .poll = poll,
+ .remove = remove,
+ .info = &driver_info
+};
diff --git a/fw/src/usbh_driver_hid.c b/fw/src/usbh_driver_hid.c
new file mode 100644
index 0000000..4893354
--- /dev/null
+++ b/fw/src/usbh_driver_hid.c
@@ -0,0 +1,412 @@
+/*
+ * This file is part of the libusbhost library
+ * hosted at http://github.com/libusbhost/libusbhost
+ *
+ * Copyright (C) 2016 Amir Hammad <amir.hammad@hotmail.com>
+ *
+ *
+ * libusbhost is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "usbh_core.h"
+#include "driver/usbh_device_driver.h"
+#include "usbh_driver_hid.h"
+#include "usart_helpers.h"
+
+#include <libopencm3/usb/usbstd.h>
+#include <libopencm3/usb/hid.h>
+#include <stdint.h>
+#include <stddef.h>
+
+#define USB_HID_SET_REPORT 0x09
+#define USB_HID_SET_IDLE 0x0A
+
+enum STATES {
+ STATE_INACTIVE,
+ STATE_READING_REQUEST,
+ STATE_READING_COMPLETE_AND_CHECK_REPORT,
+ STATE_SET_REPORT_EMPTY_READ,
+ STATE_GET_REPORT_DESCRIPTOR_READ_SETUP,// configuration is complete at this point. We write request
+ STATE_GET_REPORT_DESCRIPTOR_READ_COMPLETE,// after the read finishes, we parse that descriptor
+ STATE_SET_IDLE,
+ STATE_SET_IDLE_COMPLETE,
+};
+
+enum REPORT_STATE {
+ REPORT_STATE_NULL,
+ REPORT_STATE_READY,
+ REPORT_STATE_PENDING,
+};
+
+struct _hid_device {
+ usbh_device_t *usbh_device;
+ uint8_t buffer[USBH_HID_BUFFER];
+ uint16_t endpoint_in_maxpacketsize;
+ uint8_t endpoint_in_address;
+ enum STATES state_next;
+ uint8_t endpoint_in_toggle;
+ uint8_t device_id;
+ uint8_t configuration_value;
+ uint16_t report0_length;
+ enum REPORT_STATE report_state;
+ uint8_t report_data[USBH_HID_REPORT_BUFFER];
+ uint8_t report_data_length;
+ enum HID_TYPE hid_type;
+ uint8_t interface_number;
+};
+typedef struct _hid_device hid_device_t;
+
+struct hid_report_decriptor {
+ struct usb_hid_descriptor header;
+ struct _report_descriptor_info {
+ uint8_t bDescriptorType;
+ uint16_t wDescriptorLength;
+ } __attribute__((packed)) report_descriptors_info[];
+} __attribute__((packed));
+
+static hid_device_t hid_device[USBH_HID_MAX_DEVICES];
+static hid_config_t hid_config;
+
+static bool initialized = false;
+
+void hid_driver_init(const hid_config_t *config)
+{
+ uint32_t i;
+
+ initialized = true;
+
+ hid_config = *config;
+ for (i = 0; i < USBH_HID_MAX_DEVICES; i++) {
+ hid_device[i].state_next = STATE_INACTIVE;
+ }
+}
+
+static void *init(usbh_device_t *usbh_dev)
+{
+ if (!initialized) {
+ LOG_PRINTF("\n%s/%d : driver not initialized\n", __FILE__, __LINE__);
+ return 0;
+ }
+
+ uint32_t i;
+ hid_device_t *drvdata = NULL;
+
+ // find free data space for HID device
+ for (i = 0; i < USBH_HID_MAX_DEVICES; i++) {
+ if (hid_device[i].state_next == STATE_INACTIVE) {
+ drvdata = &hid_device[i];
+ drvdata->device_id = i;
+ drvdata->endpoint_in_address = 0;
+ drvdata->endpoint_in_toggle = 0;
+ drvdata->report0_length = 0;
+ drvdata->usbh_device = usbh_dev;
+ drvdata->report_state = REPORT_STATE_NULL;
+ drvdata->hid_type = HID_TYPE_NONE;
+ break;
+ }
+ }
+
+ return drvdata;
+}
+
+static void parse_report_descriptor(hid_device_t *hid, const uint8_t *buffer, uint32_t length)
+{
+ // TODO
+ // Do some parsing!
+ // add some checks
+ hid->report_state = REPORT_STATE_READY;
+
+ // TODO: parse this from buffer!
+ hid->report_data_length = 1;
+ (void)buffer;
+ (void)length;
+}
+
+/**
+ * Returns true if all needed data are parsed
+ */
+static bool analyze_descriptor(void *drvdata, void *descriptor)
+{
+ hid_device_t *hid = (hid_device_t *)drvdata;
+ uint8_t desc_type = ((uint8_t *)descriptor)[1];
+ switch (desc_type) {
+ case USB_DT_CONFIGURATION:
+ {
+ const struct usb_config_descriptor * cfg = (const struct usb_config_descriptor*)descriptor;
+ hid->configuration_value = cfg->bConfigurationValue;
+ }
+ break;
+
+ case USB_DT_DEVICE:
+ {
+ const struct usb_device_descriptor *devDesc = (const struct usb_device_descriptor *)descriptor;
+ (void)devDesc;
+ }
+ break;
+
+ case USB_DT_INTERFACE:
+ {
+ const struct usb_interface_descriptor *ifDesc = (const struct usb_interface_descriptor *)descriptor;
+ if (ifDesc->bInterfaceProtocol == 0x01) {
+ hid->hid_type = HID_TYPE_KEYBOARD;
+ hid->interface_number = ifDesc->bInterfaceNumber;
+ } else if (ifDesc->bInterfaceProtocol == 0x02) {
+ hid->hid_type = HID_TYPE_MOUSE;
+ hid->interface_number = ifDesc->bInterfaceNumber;
+ }
+ }
+ break;
+
+ case USB_DT_ENDPOINT:
+ {
+ const struct usb_endpoint_descriptor *ep = (const struct usb_endpoint_descriptor *)descriptor;
+ if ((ep->bmAttributes&0x03) == USB_ENDPOINT_ATTR_INTERRUPT) {
+ uint8_t epaddr = ep->bEndpointAddress;
+ if (epaddr & (1<<7)) {
+ hid->endpoint_in_address = epaddr&0x7f;
+ if (ep->wMaxPacketSize < USBH_HID_BUFFER) {
+ hid->endpoint_in_maxpacketsize = ep->wMaxPacketSize;
+ } else {
+ hid->endpoint_in_maxpacketsize = USBH_HID_BUFFER;
+ }
+ }
+ }
+ }
+ break;
+
+ case USB_DT_HID:
+ {
+ const struct hid_report_decriptor *desc = (const struct hid_report_decriptor *)descriptor;
+ if (desc->header.bNumDescriptors > 0 && desc->report_descriptors_info[0].bDescriptorType == USB_DT_REPORT) {
+ hid->report0_length = desc->report_descriptors_info[0].wDescriptorLength;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (hid->endpoint_in_address && hid->report0_length) {
+ hid->state_next = STATE_GET_REPORT_DESCRIPTOR_READ_SETUP;
+ return true;
+ }
+
+ return false;
+}
+
+static void report_event(usbh_device_t *dev, usbh_packet_callback_data_t cb_data)
+{
+ (void)cb_data;// UNUSED
+
+ hid_device_t *hid = (hid_device_t *)dev->drvdata;
+ hid->report_state = REPORT_STATE_READY;
+}
+
+static void event(usbh_device_t *dev, usbh_packet_callback_data_t cb_data)
+{
+ hid_device_t *hid = (hid_device_t *)dev->drvdata;
+
+ switch (hid->state_next) {
+ case STATE_READING_COMPLETE_AND_CHECK_REPORT:
+ {
+ switch (cb_data.status) {
+ case USBH_PACKET_CALLBACK_STATUS_OK:
+ case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+ if (hid_config.hid_in_message_handler) {
+ hid_config.hid_in_message_handler(hid->device_id, hid->buffer, cb_data.transferred_length);
+ }
+ hid->state_next = STATE_READING_REQUEST;
+ break;
+
+ default:
+ ERROR(cb_data.status);
+ hid->state_next = STATE_INACTIVE;
+ break;
+ }
+ }
+ break;
+
+ case STATE_GET_REPORT_DESCRIPTOR_READ_COMPLETE: // read complete, SET_IDLE to 0
+ {
+ switch (cb_data.status) {
+ case USBH_PACKET_CALLBACK_STATUS_OK:
+ LOG_PRINTF("READ REPORT COMPLETE \n");
+ hid->state_next = STATE_READING_REQUEST;
+ hid->endpoint_in_toggle = 0;
+
+ parse_report_descriptor(hid, hid->buffer, cb_data.transferred_length);
+ break;
+
+ default:
+ ERROR(cb_data.status);
+ hid->state_next = STATE_INACTIVE;
+ break;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+
+static void read_hid_in_endpoint(void *drvdata)
+{
+ hid_device_t *hid = (hid_device_t *)drvdata;
+ usbh_packet_t packet;
+
+ packet.address = hid->usbh_device->address;
+ packet.data.in = &hid->buffer[0];
+ packet.datalen = hid->endpoint_in_maxpacketsize;
+ packet.endpoint_address = hid->endpoint_in_address;
+ packet.endpoint_size_max = hid->endpoint_in_maxpacketsize;
+ packet.endpoint_type = USBH_ENDPOINT_TYPE_INTERRUPT;
+ packet.speed = hid->usbh_device->speed;
+ packet.callback = event;
+ packet.callback_arg = hid->usbh_device;
+ packet.toggle = &hid->endpoint_in_toggle;
+
+ hid->state_next = STATE_READING_COMPLETE_AND_CHECK_REPORT;
+ usbh_read(hid->usbh_device, &packet);
+}
+
+/**
+ * @param time_curr_us - monotically rising time
+ * unit is microseconds
+ * @see usbh_poll()
+ */
+static void poll(void *drvdata, uint32_t time_curr_us)
+{
+ (void)time_curr_us;
+
+ hid_device_t *hid = (hid_device_t *)drvdata;
+ usbh_device_t *dev = hid->usbh_device;
+ switch (hid->state_next) {
+ case STATE_READING_REQUEST:
+ {
+ read_hid_in_endpoint(drvdata);
+ }
+ break;
+
+ case STATE_GET_REPORT_DESCRIPTOR_READ_SETUP:
+ {
+ hid->endpoint_in_toggle = 0;
+ // We support only the first report descriptor with index 0
+
+ // limit the size of the report descriptor!
+ if (hid->report0_length > USBH_HID_BUFFER) {
+ hid->report0_length = USBH_HID_BUFFER;
+ }
+
+ struct usb_setup_data setup_data;
+
+ setup_data.bmRequestType = USB_REQ_TYPE_IN | USB_REQ_TYPE_INTERFACE;
+ setup_data.bRequest = USB_REQ_GET_DESCRIPTOR;
+ setup_data.wValue = USB_DT_REPORT << 8;
+ setup_data.wIndex = 0;
+ setup_data.wLength = hid->report0_length;
+
+ hid->state_next = STATE_GET_REPORT_DESCRIPTOR_READ_COMPLETE;
+ device_control(dev, event, &setup_data, hid->buffer);
+ }
+ break;
+
+ default:
+ // do nothing - probably transfer is in progress
+ break;
+ }
+}
+
+static void remove(void *drvdata)
+{
+ hid_device_t *hid = (hid_device_t *)drvdata;
+ hid->state_next = STATE_INACTIVE;
+ hid->endpoint_in_address = 0;
+}
+
+bool hid_set_report(uint8_t device_id, uint8_t val)
+{
+ if (device_id >= USBH_HID_MAX_DEVICES) {
+ LOG_PRINTF("invalid device id\n");
+ return false;
+ }
+
+ hid_device_t *hid = &hid_device[device_id];
+ if (hid->report_state != REPORT_STATE_READY) {
+ LOG_PRINTF("reporting is not ready\n");
+ // store and update afterwards
+ return false;
+ }
+
+ if (hid->report_data_length == 0) {
+ LOG_PRINTF("reporting is not available (report len=0)\n");
+ return false;
+ }
+
+ struct usb_setup_data setup_data;
+ setup_data.bmRequestType = USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE;
+ setup_data.bRequest = USB_HID_SET_REPORT;
+ setup_data.wValue = 0x02 << 8;
+ setup_data.wIndex = hid->interface_number;
+ setup_data.wLength = hid->report_data_length;
+
+ hid->report_data[0] = val;
+
+ hid->report_state = REPORT_STATE_PENDING;
+ device_control(hid->usbh_device, report_event, &setup_data, &hid->report_data);
+ return true;
+}
+
+bool hid_is_connected(uint8_t device_id)
+{
+ if (device_id >= USBH_HID_MAX_DEVICES) {
+ LOG_PRINTF("is connected: invalid device id\n");
+ return false;
+ }
+ return hid_device[device_id].state_next == STATE_INACTIVE;
+}
+
+
+enum HID_TYPE hid_get_type(uint8_t device_id)
+{
+ if (hid_is_connected(device_id)) {
+ return HID_TYPE_NONE;
+ }
+ return hid_device[device_id].hid_type;
+}
+
+static const usbh_dev_driver_info_t driver_info = {
+ .deviceClass = -1,
+ .deviceSubClass = -1,
+ .deviceProtocol = -1,
+ .idVendor = -1,
+ .idProduct = -1,
+ .ifaceClass = 0x03, // HID class
+ .ifaceSubClass = -1,
+ .ifaceProtocol = -1, // Do not care
+};
+
+const usbh_dev_driver_t usbh_hid_driver = {
+ .init = init,
+ .analyze_descriptor = analyze_descriptor,
+ .poll = poll,
+ .remove = remove,
+ .info = &driver_info
+};
+
+
+
diff --git a/fw/src/usbh_driver_hub.c b/fw/src/usbh_driver_hub.c
new file mode 100644
index 0000000..3f51c68
--- /dev/null
+++ b/fw/src/usbh_driver_hub.c
@@ -0,0 +1,675 @@
+/*
+ * This file is part of the libusbhost library
+ * hosted at http://github.com/libusbhost/libusbhost
+ *
+ * Copyright (C) 2015 Amir Hammad <amir.hammad@hotmail.com>
+ *
+ *
+ * libusbhost is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "usbh_driver_hub_private.h"
+#include "driver/usbh_device_driver.h"
+#include "usart_helpers.h"
+#include "usbh_config.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+static hub_device_t hub_device[USBH_MAX_HUBS];
+
+static bool initialized = false;
+
+void hub_driver_init(void)
+{
+ uint32_t i;
+
+ initialized = true;
+
+ for (i = 0; i < USBH_MAX_HUBS; i++) {
+ hub_device[i].device[0] = 0;
+ hub_device[i].ports_num = 0;
+ hub_device[i].current_port = CURRENT_PORT_NONE;
+ }
+}
+
+static void *init(usbh_device_t *usbh_dev)
+{
+ if (!initialized) {
+ LOG_PRINTF("\n%s/%d : driver not initialized\n", __FILE__, __LINE__);
+ return 0;
+ }
+
+ uint32_t i;
+ hub_device_t *drvdata = NULL;
+ // find free data space for hub device
+ for (i = 0; i < USBH_MAX_HUBS; i++) {
+ if (hub_device[i].device[0] == 0) {
+ break;
+ }
+ }
+ LOG_PRINTF("{%d}",i);
+ if (i == USBH_MAX_HUBS) {
+ LOG_PRINTF("Unable to initialize hub driver");
+ return 0;
+ }
+
+ drvdata = &hub_device[i];
+ drvdata->state = EVENT_STATE_NONE;
+ drvdata->ports_num = 0;
+ drvdata->device[0] = usbh_dev;
+ drvdata->busy = 0;
+ drvdata->endpoint_in_address = 0;
+ drvdata->endpoint_in_maxpacketsize = 0;
+
+ return drvdata;
+}
+
+/**
+ * @returns true if all needed data are parsed
+ */
+static bool analyze_descriptor(void *drvdata, void *descriptor)
+{
+ hub_device_t *hub = (hub_device_t *)drvdata;
+ uint8_t desc_type = ((uint8_t *)descriptor)[1];
+ switch (desc_type) {
+ case USB_DT_ENDPOINT:
+ {
+ struct usb_endpoint_descriptor *ep = (struct usb_endpoint_descriptor *)descriptor;
+ if ((ep->bmAttributes&0x03) == USB_ENDPOINT_ATTR_INTERRUPT) {
+ uint8_t epaddr = ep->bEndpointAddress;
+ if (epaddr & (1<<7)) {
+ hub->endpoint_in_address = epaddr&0x7f;
+ hub->endpoint_in_maxpacketsize = ep->wMaxPacketSize;
+ }
+ }
+ LOG_PRINTF("ENDPOINT DESCRIPTOR FOUND\n");
+ }
+ break;
+
+ case USB_DT_HUB:
+ {
+ struct usb_hub_descriptor *desc = (struct usb_hub_descriptor *)descriptor;
+ if ( desc->head.bNbrPorts <= USBH_HUB_MAX_DEVICES) {
+ hub->ports_num = desc->head.bNbrPorts;
+ } else {
+ LOG_PRINTF("INCREASE NUMBER OF ENABLED PORTS\n");
+ hub->ports_num = USBH_HUB_MAX_DEVICES;
+ }
+ LOG_PRINTF("HUB DESCRIPTOR FOUND \n");
+ }
+ break;
+
+ default:
+ LOG_PRINTF("TYPE: %02X \n",desc_type);
+ break;
+ }
+
+ if (hub->endpoint_in_address) {
+ hub->state = EVENT_STATE_INITIAL;
+ LOG_PRINTF("end enum");
+ return true;
+ }
+ return false;
+}
+
+// Enumerate
+static void event(usbh_device_t *dev, usbh_packet_callback_data_t cb_data)
+{
+ hub_device_t *hub = (hub_device_t *)dev->drvdata;
+
+ LOG_PRINTF("\nHUB->STATE = %d\n", hub->state);
+ switch (hub->state) {
+ case EVENT_STATE_POLL:
+ switch (cb_data.status) {
+ case USBH_PACKET_CALLBACK_STATUS_OK:
+ {
+ uint8_t i;
+ uint8_t *buf = hub->buffer;
+ uint32_t psc = 0; // Limit: up to 4 bytes...
+ for (i = 0; i < cb_data.transferred_length; i++) {
+ psc += buf[i] << (i*8);
+ }
+ int8_t port = 0;
+
+ LOG_PRINTF("psc:%d\n",psc);
+ // Driver error... port not found
+ if (!psc) {
+ // Continue reading status change endpoint
+ hub->state = EVENT_STATE_POLL_REQ;
+ break;
+ }
+
+ for (i = 0; i <= hub->ports_num; i++) {
+ if (psc & (1<<i)) {
+ port = i;
+ psc = 0;
+ break;
+ }
+ }
+
+ if (hub->current_port >= 1) {
+ if (hub->current_port != port) {
+ LOG_PRINTF("X");
+ hub->state = EVENT_STATE_POLL_REQ;
+ break;
+ }
+ }
+ struct usb_setup_data setup_data;
+ // If regular port event, else hub event
+ if (port) {
+ setup_data.bmRequestType = USB_REQ_TYPE_IN | USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE | USB_REQ_TYPE_ENDPOINT;
+ } else {
+ setup_data.bmRequestType = USB_REQ_TYPE_IN | USB_REQ_TYPE_CLASS | USB_REQ_TYPE_DEVICE;
+ }
+
+ setup_data.bRequest = USB_REQ_GET_STATUS;
+ setup_data.wValue = 0;
+ setup_data.wIndex = port;
+ setup_data.wLength = 4;
+ hub->state = EVENT_STATE_GET_STATUS_COMPLETE;
+
+ hub->current_port = port;
+ LOG_PRINTF("\n\nPORT FOUND: %d\n", port);
+ device_control(dev, event, &setup_data, &hub->hub_and_port_status[port]);
+ }
+ break;
+
+ default:
+ ERROR(cb_data.status);
+ hub->state = EVENT_STATE_NONE;
+ break;
+
+ case USBH_PACKET_CALLBACK_STATUS_EAGAIN:
+
+ // In case of EAGAIN error, retry read on status endpoint
+ hub->state = EVENT_STATE_POLL_REQ;
+ LOG_PRINTF("HUB: Retrying...\n");
+ break;
+ }
+ break;
+
+ case EVENT_STATE_READ_HUB_DESCRIPTOR_COMPLETE:// Hub descriptor found
+ {
+ switch (cb_data.status) {
+ case USBH_PACKET_CALLBACK_STATUS_OK:
+ {
+ struct usb_hub_descriptor *hub_descriptor =
+ (struct usb_hub_descriptor *)hub->buffer;
+
+ // Check size
+ if (hub_descriptor->head.bDescLength > hub->desc_len) {
+ struct usb_setup_data setup_data;
+ hub->desc_len = hub_descriptor->head.bDescLength;
+
+ setup_data.bmRequestType = USB_REQ_TYPE_IN | USB_REQ_TYPE_CLASS | USB_REQ_TYPE_DEVICE;
+ setup_data.bRequest = USB_REQ_GET_DESCRIPTOR;
+ setup_data.wValue = 0x29<<8;
+ setup_data.wIndex = 0;
+ setup_data.wLength = hub->desc_len;
+
+ hub->state = EVENT_STATE_READ_HUB_DESCRIPTOR_COMPLETE;
+ device_control(dev, event, &setup_data, hub->buffer);
+ break;
+ } else if (hub_descriptor->head.bDescLength == hub->desc_len) {
+ hub->ports_num = hub_descriptor->head.bNbrPorts;
+
+ hub->state = EVENT_STATE_ENABLE_PORTS;
+ hub->index = 0;
+ cb_data.status = USBH_PACKET_CALLBACK_STATUS_OK;
+ event(dev, cb_data);
+ } else {
+ //try again
+ }
+ }
+ break;
+
+ case USBH_PACKET_CALLBACK_STATUS_ERRSIZ:
+ {
+ LOG_PRINTF("->\t\t\t\t\t ERRSIZ: deschub\n");
+ struct usb_hub_descriptor*hub_descriptor =
+ (struct usb_hub_descriptor *)hub->buffer;
+
+ if (cb_data.transferred_length >= sizeof(struct usb_hub_descriptor_head)) {
+ if (cb_data.transferred_length == hub_descriptor->head.bDescLength) {
+ // Process HUB descriptor
+ if ( hub_descriptor->head.bNbrPorts <= USBH_HUB_MAX_DEVICES) {
+ hub->ports_num = hub_descriptor->head.bNbrPorts;
+ } else {
+ LOG_PRINTF("INCREASE NUMBER OF ENABLED PORTS\n");
+ hub->ports_num = USBH_HUB_MAX_DEVICES;
+ }
+ hub->state = EVENT_STATE_ENABLE_PORTS;
+ hub->index = 0;
+
+ cb_data.status = USBH_PACKET_CALLBACK_STATUS_OK;
+ event(dev, cb_data);
+ }
+ }
+ }
+ break;
+
+ default:
+ ERROR(cb_data.status);
+ break;
+ }
+ }
+ break;
+
+ case EVENT_STATE_ENABLE_PORTS:// enable ports
+ {
+ switch (cb_data.status) {
+ case USBH_PACKET_CALLBACK_STATUS_OK:
+ if (hub->index < hub->ports_num) {
+ hub->index++;
+ struct usb_setup_data setup_data;
+
+ LOG_PRINTF("[!%d!]",hub->index);
+ setup_data.bmRequestType = USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE | USB_REQ_TYPE_ENDPOINT;
+ setup_data.bRequest = HUB_REQ_SET_FEATURE;
+ setup_data.wValue = HUB_FEATURE_PORT_POWER;
+ setup_data.wIndex = hub->index;
+ setup_data.wLength = 0;
+
+ device_control(dev, event, &setup_data, 0);
+ } else {
+ // TODO:
+ // Delay Based on hub descriptor field bPwr2PwrGood
+ // delay_ms_busy_loop(200);
+
+ LOG_PRINTF("\nHUB CONFIGURED & PORTS POWERED\n");
+
+ // get device status
+ struct usb_setup_data setup_data;
+
+ setup_data.bmRequestType = USB_REQ_TYPE_IN | USB_REQ_TYPE_CLASS | USB_REQ_TYPE_DEVICE;
+ setup_data.bRequest = USB_REQ_GET_STATUS;
+ setup_data.wValue = 0;
+ setup_data.wIndex = 0;
+ setup_data.wLength = 4;
+
+ hub->state = EVENT_STATE_GET_PORT_STATUS;
+ hub->index = 0;
+ device_control(dev, event, &setup_data, hub->buffer);
+ }
+ break;
+
+ default:
+ ERROR(cb_data.status);
+ break;
+ }
+ }
+ break;
+
+ case EVENT_STATE_GET_PORT_STATUS:
+ {
+ switch (cb_data.status) {
+ case USBH_PACKET_CALLBACK_STATUS_OK:
+ {
+ if (hub->index < hub->ports_num) {
+ //TODO: process data contained in hub->buffer
+
+ struct usb_setup_data setup_data;
+
+ setup_data.bmRequestType = USB_REQ_TYPE_IN | USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE | USB_REQ_TYPE_ENDPOINT;
+ setup_data.bRequest = USB_REQ_GET_STATUS;
+ setup_data.wValue = 0;
+ setup_data.wIndex = ++hub->index;
+ setup_data.wLength = 4;
+
+ hub->state = EVENT_STATE_GET_PORT_STATUS;
+ device_control(dev, event, &setup_data, hub->buffer);
+ } else {
+ hub->busy = 0;
+ hub->state = EVENT_STATE_POLL_REQ;
+ }
+
+ }
+ break;
+
+ default:
+ ERROR(cb_data.status);
+ break;
+ }
+ }
+ break;
+
+ case EVENT_STATE_GET_STATUS_COMPLETE:
+ {
+ switch (cb_data.status) {
+ case USBH_PACKET_CALLBACK_STATUS_OK:
+ {
+ int8_t port = hub->current_port;
+ LOG_PRINTF("|%d",port);
+
+
+ // Get Port status, else Get Hub status
+ if (port) {
+ uint16_t stc = hub->hub_and_port_status[port].stc;
+
+ // Connection status changed
+ if (stc & (1<<HUB_FEATURE_PORT_CONNECTION)) {
+
+ // Check, whether device is in connected state
+ if (!hub->device[port]) {
+ if (!usbh_enum_available() || hub->busy) {
+ LOG_PRINTF("\n\t\t\tCannot enumerate %d %d\n", !usbh_enum_available(), hub->busy);
+ hub->state = EVENT_STATE_POLL_REQ;
+ break;
+ }
+ }
+
+ // clear feature C_PORT_CONNECTION
+ struct usb_setup_data setup_data;
+
+ setup_data.bmRequestType = USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE | USB_REQ_TYPE_ENDPOINT;
+ setup_data.bRequest = HUB_REQ_CLEAR_FEATURE;
+ setup_data.wValue = HUB_FEATURE_C_PORT_CONNECTION;
+ setup_data.wIndex = port;
+ setup_data.wLength = 0;
+
+ hub->state = EVENT_STATE_PORT_RESET_REQ;
+ device_control(dev, event, &setup_data, 0);
+
+ } else if(stc & (1<<HUB_FEATURE_PORT_RESET)) {
+ // clear feature C_PORT_RESET
+ // Reset processing is complete, enumerate device
+ struct usb_setup_data setup_data;
+
+ setup_data.bmRequestType = USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE | USB_REQ_TYPE_ENDPOINT;
+ setup_data.bRequest = HUB_REQ_CLEAR_FEATURE;
+ setup_data.wValue = HUB_FEATURE_C_PORT_RESET;
+ setup_data.wIndex = port;
+ setup_data.wLength = 0;
+
+ hub->state = EVENT_STATE_PORT_RESET_COMPLETE;
+
+ LOG_PRINTF("RESET");
+ device_control(dev, event, &setup_data, 0);
+ } else {
+ LOG_PRINTF("another STC %d\n", stc);
+ }
+ } else {
+ hub->state = EVENT_STATE_POLL_REQ;
+ LOG_PRINTF("HUB status change\n");
+ }
+ }
+ break;
+
+ default:
+ ERROR(cb_data.status);
+ // continue
+ hub->state = EVENT_STATE_POLL_REQ;
+ break;
+ }
+ }
+ break;
+ case EVENT_STATE_PORT_RESET_REQ:
+ {
+ switch (cb_data.status) {
+ case USBH_PACKET_CALLBACK_STATUS_OK:
+ {
+ int8_t port = hub->current_port;
+ uint16_t stc = hub->hub_and_port_status[port].stc;
+ if (!hub->device[port]) {
+ if ((stc) & (1<<HUB_FEATURE_PORT_CONNECTION)) {
+ struct usb_setup_data setup_data;
+
+ setup_data.bmRequestType = USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE | USB_REQ_TYPE_ENDPOINT;
+ setup_data.bRequest = HUB_REQ_SET_FEATURE;
+ setup_data.wValue = HUB_FEATURE_PORT_RESET;
+ setup_data.wIndex = port;
+ setup_data.wLength = 0;
+
+ hub->state = EVENT_STATE_GET_PORT_STATUS;
+
+ LOG_PRINTF("CONN");
+
+ hub->busy = 1;
+ device_control(dev, event, &setup_data, 0);
+ }
+ } else {
+ LOG_PRINTF("\t\t\t\tDISCONNECT EVENT\n");
+ device_remove(hub->device[port]);
+
+ hub->device[port] = 0;
+ hub->current_port = CURRENT_PORT_NONE;
+ hub->state = EVENT_STATE_POLL_REQ;
+ hub->busy = 0;
+ }
+ }
+ break;
+
+ default:
+ ERROR(cb_data.status);
+ // continue
+ hub->state = EVENT_STATE_POLL_REQ;
+ break;
+ }
+ }
+ break;
+ case EVENT_STATE_PORT_RESET_COMPLETE: // RESET COMPLETE, start enumeration
+ {
+ switch (cb_data.status) {
+ case USBH_PACKET_CALLBACK_STATUS_OK:
+ {
+ LOG_PRINTF("\nPOLL\n");
+ int8_t port = hub->current_port;
+ uint16_t sts = hub->hub_and_port_status[port].sts;
+
+
+ if (sts & (1<<HUB_FEATURE_PORT_ENABLE)) {
+ hub->device[port] = usbh_get_free_device(dev);
+
+ if (!hub->device[port]) {
+ LOG_PRINTF("\nFATAL ERROR\n");
+ return;// DEAD END
+ }
+ if ((sts & (1<<(HUB_FEATURE_PORT_LOWSPEED))) &&
+ !(sts & (1<<(HUB_FEATURE_PORT_HIGHSPEED)))) {
+#define DISABLE_LOW_SPEED
+#ifdef DISABLE_LOW_SPEED
+ LOG_PRINTF("Low speed device");
+
+ // Disable Low speed device immediately
+ struct usb_setup_data setup_data;
+
+ setup_data.bmRequestType = USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE | USB_REQ_TYPE_ENDPOINT;
+ setup_data.bRequest = HUB_REQ_CLEAR_FEATURE;
+ setup_data.wValue = HUB_FEATURE_PORT_ENABLE;
+ setup_data.wIndex = port;
+ setup_data.wLength = 0;
+
+ // After write process another devices, poll for events
+ //Expecting all ports are powered (constant/non-changeable after init)
+ hub->state = EVENT_STATE_GET_PORT_STATUS;
+
+ hub->current_port = CURRENT_PORT_NONE;
+ device_control(dev, event, &setup_data, 0);
+#else
+ hub->device[port]->speed = USBH_SPEED_LOW;
+ LOG_PRINTF("Low speed device");
+ hub->timestamp_us = hub->time_curr_us;
+ hub->state = EVENT_STATE_SLEEP_500_MS; // schedule wait for 500ms
+#endif
+ } else if (!(sts & (1<<(HUB_FEATURE_PORT_LOWSPEED))) &&
+ !(sts & (1<<(HUB_FEATURE_PORT_HIGHSPEED)))) {
+ hub->device[port]->speed = USBH_SPEED_FULL;
+ LOG_PRINTF("Full speed device");
+ hub->timestamp_us = hub->time_curr_us;
+ hub->state = EVENT_STATE_SLEEP_500_MS; // schedule wait for 500ms
+ }
+
+
+ } else {
+ LOG_PRINTF("%s:%d Do not know what to do, when device is disabled after reset\n", __FILE__, __LINE__);
+ hub->state = EVENT_STATE_POLL_REQ;
+ return;
+ }
+ }
+ break;
+
+ default:
+ ERROR(cb_data.status);
+ // continue
+ hub->state = EVENT_STATE_POLL_REQ;
+ break;
+ }
+ }
+ break;
+ default:
+ LOG_PRINTF("UNHANDLED EVENT %d\n",hub->state);
+ break;
+ }
+}
+
+static void read_ep1(void *drvdata)
+{
+ hub_device_t *hub = (hub_device_t *)drvdata;
+ usbh_packet_t packet;
+
+ packet.address = hub->device[0]->address;
+ packet.data.in = hub->buffer;
+ packet.datalen = hub->endpoint_in_maxpacketsize;
+ packet.endpoint_address = hub->endpoint_in_address;
+ packet.endpoint_size_max = hub->endpoint_in_maxpacketsize;
+ packet.endpoint_type = USBH_ENDPOINT_TYPE_INTERRUPT;
+ packet.speed = hub->device[0]->speed;
+ packet.callback = event;
+ packet.callback_arg = hub->device[0];
+ packet.toggle = &hub->endpoint_in_toggle;
+
+ hub->state = EVENT_STATE_POLL;
+ usbh_read(hub->device[0], &packet);
+ LOG_PRINTF("@hub %d/EP1 | \n", hub->device[0]->address);
+
+}
+
+/**
+ * @param time_curr_us - monotically rising time
+ * unit is microseconds
+ * @see usbh_poll()
+ */
+static void poll(void *drvdata, uint32_t time_curr_us)
+{
+ hub_device_t *hub = (hub_device_t *)drvdata;
+ usbh_device_t *dev = hub->device[0];
+
+ hub->time_curr_us = time_curr_us;
+
+ switch (hub->state) {
+ case EVENT_STATE_POLL_REQ:
+ {
+ if (usbh_enum_available()) {
+ read_ep1(hub);
+ } else {
+ LOG_PRINTF("enum not available\n");
+ }
+ }
+ break;
+
+ case EVENT_STATE_INITIAL:
+ {
+ if (hub->ports_num) {
+ hub->index = 0;
+ hub->state = EVENT_STATE_ENABLE_PORTS;
+ LOG_PRINTF("No need to get HUB DESC\n");
+ event(dev, (usbh_packet_callback_data_t){0, 0});
+ } else {
+ hub->endpoint_in_toggle = 0;
+
+ struct usb_setup_data setup_data;
+ hub->desc_len = hub->device[0]->packet_size_max0;
+
+ setup_data.bmRequestType = USB_REQ_TYPE_IN | USB_REQ_TYPE_CLASS | USB_REQ_TYPE_DEVICE;
+ setup_data.bRequest = USB_REQ_GET_DESCRIPTOR;
+ setup_data.wValue = 0x29 << 8;
+ setup_data.wIndex = 0;
+ setup_data.wLength = hub->desc_len;
+
+ hub->state = EVENT_STATE_READ_HUB_DESCRIPTOR_COMPLETE;
+ device_control(dev, event, &setup_data, hub->buffer);
+ LOG_PRINTF("DO Need to get HUB DESC\n");
+ }
+ }
+ break;
+ case EVENT_STATE_SLEEP_500_MS:
+ if (hub->time_curr_us - hub->timestamp_us > 500000) {
+ int8_t port = hub->current_port;
+ LOG_PRINTF("PORT: %d\n", port);
+ LOG_PRINTF("NEW device at address: %d\n", hub->device[port]->address);
+ hub->device[port]->lld = hub->device[0]->lld;
+
+ device_enumeration_start(hub->device[port]);
+ hub->current_port = CURRENT_PORT_NONE;
+
+ // Maybe error, when assigning address is taking too long
+ //
+ // Detail:
+ // USB hub cannot enable another port while the device
+ // the current one is also in address state (has address==0)
+ // Only one device on bus can have address==0
+ hub->busy = 0;
+
+ hub->state = EVENT_STATE_POLL_REQ;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (usbh_enum_available()) {
+ uint32_t i;
+ for (i = 1; i < USBH_HUB_MAX_DEVICES + 1; i++) {
+ if (hub->device[i]) {
+ if (hub->device[i]->drv && hub->device[i]->drvdata) {
+ hub->device[i]->drv->poll(hub->device[i]->drvdata, time_curr_us);
+ }
+ }
+ }
+ }
+}
+static void remove(void *drvdata)
+{
+ hub_device_t *hub = (hub_device_t *)drvdata;
+ uint8_t i;
+
+ hub->state = EVENT_STATE_NONE;
+ hub->endpoint_in_address = 0;
+ hub->busy = 0;
+ for (i = 0; i < USBH_HUB_MAX_DEVICES + 1; i++) {
+ hub->device[i] = 0;
+
+ }
+}
+
+static const usbh_dev_driver_info_t driver_info = {
+ .deviceClass = 0x09,
+ .deviceSubClass = -1,
+ .deviceProtocol = -1,
+ .idVendor = -1,
+ .idProduct = -1,
+ .ifaceClass = 0x09,
+ .ifaceSubClass = -1,
+ .ifaceProtocol = -1
+};
+
+const usbh_dev_driver_t usbh_hub_driver = {
+ .init = init,
+ .analyze_descriptor = analyze_descriptor,
+ .poll = poll,
+ .remove = remove,
+ .info = &driver_info
+};
diff --git a/fw/src/usbh_driver_hub_private.h b/fw/src/usbh_driver_hub_private.h
new file mode 100644
index 0000000..8c9bc44
--- /dev/null
+++ b/fw/src/usbh_driver_hub_private.h
@@ -0,0 +1,123 @@
+/*
+ * This file is part of the libusbhost library
+ * hosted at http://github.com/libusbhost/libusbhost
+ *
+ * Copyright (C) 2015 Amir Hammad <amir.hammad@hotmail.com>
+ *
+ *
+ * libusbhost is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef USBH_DRIVER_HUB_PRIVATE_
+#define USBH_DRIVER_HUB_PRIVATE_
+
+#include "usbh_config.h"
+#include "driver/usbh_device_driver.h"
+#include "usbh_driver_hub.h"
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <libopencm3/usb/usbstd.h>
+
+
+// # HUB DEFINITIONS
+#define HUB_FEATURE_PORT_CONNECTION 0
+#define HUB_FEATURE_PORT_ENABLE 1
+#define HUB_FEATURE_PORT_SUSPEND 2
+#define HUB_FEATURE_PORT_OVERCURRENT 3
+#define HUB_FEATURE_PORT_RESET 4
+#define HUB_FEATURE_PORT_POWER 8
+#define HUB_FEATURE_PORT_LOWSPEED 9
+#define HUB_FEATURE_PORT_HIGHSPEED 10
+
+#define HUB_FEATURE_C_PORT_CONNECTION 16
+#define HUB_FEATURE_C_PORT_ENABLE 17
+#define HUB_FEATURE_C_PORT_SUSPEND 18
+#define HUB_FEATURE_C_PORT_OVERCURRENT 19
+#define HUB_FEATURE_C_PORT_RESET 20
+
+#define HUB_REQ_GET_STATUS 0
+#define HUB_REQ_CLEAR_FEATURE 1
+#define HUB_REQ_SET_FEATURE 3
+#define HUB_REQ_GET_DESCRIPTOR 6
+
+#define USB_DT_HUB (41)
+#define USB_DT_HUB_SIZE (9)
+// Hub buffer: must be larger than hub descriptor
+#define USBH_HUB_BUFFER_SIZE (USB_DT_HUB_SIZE)
+
+
+#define CURRENT_PORT_NONE -1
+
+enum EVENT_STATE {
+ EVENT_STATE_NONE,
+ EVENT_STATE_INITIAL,
+ EVENT_STATE_POLL_REQ,
+ EVENT_STATE_POLL,
+ EVENT_STATE_READ_HUB_DESCRIPTOR_COMPLETE,
+ EVENT_STATE_ENABLE_PORTS,
+ EVENT_STATE_GET_PORT_STATUS,
+ EVENT_STATE_PORT_RESET_REQ,
+ EVENT_STATE_PORT_RESET_COMPLETE,
+ EVENT_STATE_SLEEP_500_MS,
+ EVENT_STATE_GET_STATUS_COMPLETE,
+};
+
+struct _hub_device {
+ usbh_device_t *device[USBH_HUB_MAX_DEVICES + 1];
+ uint8_t buffer[USBH_HUB_BUFFER_SIZE];
+ uint16_t endpoint_in_maxpacketsize;
+ uint8_t endpoint_in_address;
+ uint8_t endpoint_in_toggle;
+ enum EVENT_STATE state;
+
+ uint8_t desc_len;
+ uint16_t ports_num;
+ int8_t index;
+ int8_t current_port;
+
+ struct {
+ uint16_t sts;
+ uint16_t stc;
+ } hub_and_port_status[USBH_HUB_MAX_DEVICES + 1];
+
+ bool busy;
+
+ uint32_t time_curr_us;
+ uint32_t timestamp_us;
+};
+
+typedef struct _hub_device hub_device_t;
+
+struct usb_hub_descriptor_head {
+ uint8_t bDescLength;
+ uint8_t bDescriptorType;
+ uint8_t bNbrPorts;
+ uint16_t wHubCharacteristics;
+ uint8_t bPwrOn2PwrGood;
+ uint8_t bHubContrCurrent;
+} __attribute__((packed));
+struct usb_hub_descriptor_body {
+ uint8_t bDeviceRemovable;
+ uint8_t PortPwrCtrlMask;
+} __attribute__((packed));
+
+// for hubs with up to 7 ports on hub
+struct usb_hub_descriptor {
+ struct usb_hub_descriptor_head head;
+ struct usb_hub_descriptor_body body[1];
+} __attribute__((packed));
+
+#endif
diff --git a/fw/src/usbh_lld_stm32f4.c b/fw/src/usbh_lld_stm32f4.c
new file mode 100644
index 0000000..3fcd51a
--- /dev/null
+++ b/fw/src/usbh_lld_stm32f4.c
@@ -0,0 +1,1041 @@
+/*
+ * This file is part of the libusbhost library
+ * hosted at http://github.com/libusbhost/libusbhost
+ *
+ * Copyright (C) 2015 Amir Hammad <amir.hammad@hotmail.com>
+ *
+ *
+ * libusbhost is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "driver/usbh_device_driver.h"
+#include "usbh_lld_stm32f4.h"
+#include "usart_helpers.h"
+
+#include <string.h>
+#include <stdint.h>
+#include <libopencm3/stm32/otg_hs.h>
+#include <libopencm3/stm32/otg_fs.h>
+
+
+
+/* Receive FIFO size in 32-bit words. */
+#define RX_FIFO_SIZE (64)
+/* Transmit NON-periodic FIFO size in 32-bit words. */
+#define TX_NP_FIFO_SIZE (64)
+/* Transmit periodic FIFO size in 32-bit words. */
+#define TX_P_FIFO_SIZE (64)
+
+enum CHANNEL_STATE {
+ CHANNEL_STATE_FREE = 0,
+ CHANNEL_STATE_WORK = 1
+};
+
+struct _channel {
+ enum CHANNEL_STATE state;
+ usbh_packet_t packet;
+ uint32_t data_index; //used in receive function
+};
+typedef struct _channel channel_t;
+
+enum DEVICE_STATE {
+ DEVICE_STATE_INIT = 0,
+ DEVICE_STATE_RUN = 1,
+ DEVICE_STATE_RESET = 2
+};
+
+enum DEVICE_POLL_STATE {
+ DEVICE_POLL_STATE_DISCONN = 0,
+ DEVICE_POLL_STATE_DEVCONN = 1,
+ DEVICE_POLL_STATE_DEVRST = 2,
+ DEVICE_POLL_STATE_RUN = 3
+};
+
+struct _usbh_lld_stm32f4_driver_data {
+ usbh_generic_data_t generic;
+ const uint32_t base;
+ channel_t *channels;
+ const uint8_t num_channels;
+
+ uint32_t poll_sequence;
+ enum DEVICE_POLL_STATE dpstate;
+ enum DEVICE_STATE state;
+ uint32_t state_prev;//for reset only
+ uint32_t time_curr_us;
+ uint32_t timestamp_us;
+};
+typedef struct _usbh_lld_stm32f4_driver_data usbh_lld_stm32f4_driver_data_t;
+
+
+
+/*
+ * Define correct REBASE. If only one driver is enabled use directly OTG base
+ *
+ */
+#if defined(USE_STM32F4_USBH_DRIVER_FS) || \
+ defined(USE_STM32F4_USBH_DRIVER_HS)
+
+#if defined(USE_STM32F4_USBH_DRIVER_FS) && \
+ defined(USE_STM32F4_USBH_DRIVER_HS)
+#define REBASE(reg) MMIO32(dev->base + reg)
+#define REBASE_CH(reg, x) MMIO32(dev->base + reg(x))
+#elif defined(USE_STM32F4_USBH_DRIVER_FS)
+#define REBASE(reg) MMIO32(USB_OTG_FS_BASE + reg)
+#define REBASE_CH(reg, x) MMIO32(USB_OTG_FS_BASE + reg(x))
+#elif defined(USE_STM32F4_USBH_DRIVER_HS)
+#define REBASE(reg) MMIO32(USB_OTG_HS_BASE + reg)
+#define REBASE_CH(reg, x) MMIO32(USB_OTG_HS_BASE + reg(x))
+#endif
+
+static int8_t get_free_channel(void *drvdata);
+static void channels_init(void *drvdata);
+static void rxflvl_handle(void *drvdata);
+static void free_channel(void *drvdata, uint8_t channel);
+
+
+
+
+
+static inline void reset_start(usbh_lld_stm32f4_driver_data_t *dev)
+{
+
+ // apply reset condition on port
+ REBASE(OTG_HPRT) |= OTG_HPRT_PRST;
+
+ // push current state to stack
+ dev->state_prev = dev->state;
+
+ // move to new state
+ dev->state = DEVICE_STATE_RESET;
+
+ // schedule disable reset condition after ~10ms
+ dev->timestamp_us = dev->time_curr_us;
+}
+
+/**
+ * Should be nonblocking
+ *
+ */
+static void init(void *drvdata)
+{
+ usbh_lld_stm32f4_driver_data_t *dev = drvdata;
+ dev->state = DEVICE_STATE_INIT;
+ dev->poll_sequence = 0;
+ dev->timestamp_us = dev->time_curr_us;
+
+ //Disable interrupts first
+ REBASE(OTG_GAHBCFG) &= ~OTG_GAHBCFG_GINT;
+
+ // Select full speed phy
+ REBASE(OTG_GUSBCFG) |= OTG_GUSBCFG_PHYSEL;
+}
+
+static uint32_t usbh_to_stm32_endpoint_type(enum USBH_ENDPOINT_TYPE usbh_eptyp)
+{
+ switch (usbh_eptyp) {
+ case USBH_ENDPOINT_TYPE_CONTROL: return OTG_HCCHAR_EPTYP_CONTROL;
+ case USBH_ENDPOINT_TYPE_BULK: return OTG_HCCHAR_EPTYP_BULK;
+
+ // Use bulk transfer also for interrupt, since no difference is on protocol layer
+ // Except different behaviour of the core
+ case USBH_ENDPOINT_TYPE_INTERRUPT: return OTG_HCCHAR_EPTYP_BULK;
+ case USBH_ENDPOINT_TYPE_ISOCHRONOUS: return OTG_HCCHAR_EPTYP_ISOCHRONOUS;
+ default:
+ LOG_PRINTF("\n\n\n\nWRONG EP TYPE\n\n\n\n\n");
+ return OTG_HCCHAR_EPTYP_CONTROL;
+ }
+}
+
+static void stm32f4_usbh_port_channel_setup(
+ void *drvdata, uint32_t channel, uint32_t epdir)
+{
+ usbh_lld_stm32f4_driver_data_t *dev = drvdata;
+ channel_t *channels = dev->channels;
+ uint32_t max_packet_size = channels[channel].packet.endpoint_size_max;
+ uint32_t address = channels[channel].packet.address;
+ uint32_t epnum = channels[channel].packet.endpoint_address;
+ uint32_t eptyp = usbh_to_stm32_endpoint_type(channels[channel].packet.endpoint_type);
+
+ uint32_t speed = 0;
+ if (channels[channel].packet.speed == USBH_SPEED_LOW) {
+ speed = OTG_HCCHAR_LSDEV;
+ }
+
+ REBASE_CH(OTG_HCCHAR, channel) = OTG_HCCHAR_CHENA |
+ (OTG_HCCHAR_DAD_MASK & (address << 22)) |
+ OTG_HCCHAR_MCNT_1 |
+ (OTG_HCCHAR_EPTYP_MASK & (eptyp)) |
+ (speed) |
+ (OTG_HCCHAR_EPDIR_MASK & epdir) |
+ (OTG_HCCHAR_EPNUM_MASK & (epnum << 11)) |
+ (OTG_HCCHAR_MPSIZ_MASK & max_packet_size);
+
+}
+
+
+/**
+ * TODO: Check for maximum datalength
+ */
+static void read(void *drvdata, usbh_packet_t *packet)
+{
+ usbh_lld_stm32f4_driver_data_t *dev = drvdata;
+ channel_t *channels = dev->channels;
+
+ int8_t channel = get_free_channel(dev);
+ if (channel == -1) {
+ // BIG PROBLEM
+ LOG_PRINTF("FATAL ERROR IN, NO CHANNEL LEFT \n");
+ usbh_packet_callback_data_t cb_data;
+ cb_data.status = USBH_PACKET_CALLBACK_STATUS_EFATAL;
+ cb_data.transferred_length = 0;
+ packet->callback(packet->callback_arg, cb_data);
+ return;
+ }
+
+ channels[channel].data_index = 0;
+ channels[channel].packet = *packet;
+
+ uint32_t dpid;
+ if (packet->toggle[0]) {
+ dpid = OTG_HCTSIZ_DPID_DATA1;
+ } else {
+ dpid = OTG_HCTSIZ_DPID_DATA0;
+ }
+
+ uint32_t num_packets;
+ if (packet->datalen) {
+ num_packets = ((packet->datalen - 1) / packet->endpoint_size_max) + 1;
+ } else {
+ num_packets = 0;
+ }
+
+ REBASE_CH(OTG_HCTSIZ, channel) = dpid | (num_packets << 19) | packet->datalen;
+
+ stm32f4_usbh_port_channel_setup(dev, channel, OTG_HCCHAR_EPDIR_IN);
+}
+
+/**
+ *
+ * Bug: datalen > max_packet_size ...
+ */
+static void write(void *drvdata, const usbh_packet_t *packet)
+{
+ usbh_lld_stm32f4_driver_data_t *dev = drvdata;
+ channel_t *channels = dev->channels;
+
+ int8_t channel = get_free_channel(dev);
+
+ if (channel == -1) {
+ // BIG PROBLEM
+ LOG_PRINTF("FATAL ERROR OUT, NO CHANNEL LEFT \n");
+ usbh_packet_callback_data_t cb_data;
+ cb_data.status = USBH_PACKET_CALLBACK_STATUS_EFATAL;
+ cb_data.transferred_length = 0;
+ packet->callback(packet->callback_arg, cb_data);
+ return;
+ }
+
+ channels[channel].data_index = 0;
+ channels[channel].packet = *packet;
+
+ uint32_t dpid;
+ if (packet->endpoint_type == USBH_ENDPOINT_TYPE_CONTROL) {
+ if (packet->control_type == USBH_CONTROL_TYPE_DATA) {
+ dpid = packet->toggle[0] ? OTG_HCTSIZ_DPID_DATA1 : OTG_HCTSIZ_DPID_DATA0;
+ } else {
+ dpid = OTG_HCTSIZ_DPID_MDATA;
+ packet->toggle[0] = 0;
+ }
+ } else if(packet->endpoint_type == USBH_ENDPOINT_TYPE_INTERRUPT) {
+ dpid = packet->toggle[0] ? OTG_HCTSIZ_DPID_DATA1 : OTG_HCTSIZ_DPID_DATA0;
+ } else if (packet->endpoint_type == USBH_ENDPOINT_TYPE_BULK) {
+ dpid = packet->toggle[0] ? OTG_HCTSIZ_DPID_DATA1 : OTG_HCTSIZ_DPID_DATA0;
+ } else {
+ dpid = OTG_HCTSIZ_DPID_DATA0; // ! TODO: BUG
+ ERROR("");
+ }
+
+ uint32_t num_packets;
+ if (packet->datalen) {
+ num_packets = ((packet->datalen - 1) / packet->endpoint_size_max) + 1;
+ } else {
+ num_packets = 1;
+ }
+ REBASE_CH(OTG_HCTSIZ, channel) = dpid | (num_packets << 19) | packet->datalen;
+
+ stm32f4_usbh_port_channel_setup(dev, channel, OTG_HCCHAR_EPDIR_OUT);
+
+ if (packet->endpoint_type == USBH_ENDPOINT_TYPE_CONTROL ||
+ packet->endpoint_type == USBH_ENDPOINT_TYPE_BULK) {
+
+ volatile uint32_t *fifo = &REBASE_CH(OTG_FIFO, channel) + RX_FIFO_SIZE;
+ const uint32_t * buf32 = packet->data.out;
+ int i;
+ LOG_PRINTF("\nSending[%d]: ", packet->datalen);
+ for(i = packet->datalen; i >= 4; i-=4) {
+ const uint8_t *buf8 = (const uint8_t *)buf32;
+ LOG_PRINTF("%02X %02X %02X %02X, ", buf8[0], buf8[1], buf8[2], buf8[3]);
+ *fifo++ = *buf32++;
+
+ }
+
+ if (i > 0) {
+ *fifo = *buf32&((1 << (8*i)) - 1);
+ uint8_t *buf8 = (uint8_t *)buf32;
+ while (i--) {
+ LOG_PRINTF("%02X ", *buf8++);
+ }
+ }
+ LOG_PRINTF("\n");
+
+ } else {
+ volatile uint32_t *fifo = &REBASE_CH(OTG_FIFO, channel) +
+ RX_FIFO_SIZE + TX_NP_FIFO_SIZE;
+ const uint32_t * buf32 = packet->data.out;
+ int i;
+ for(i = packet->datalen; i > 0; i-=4) {
+ *fifo++ = *buf32++;
+ }
+ }
+ LOG_PRINTF("->WRITE %08X\n", REBASE_CH(OTG_HCCHAR, channel));
+}
+
+static void rxflvl_handle(void *drvdata)
+{
+ usbh_lld_stm32f4_driver_data_t *dev = drvdata;
+ channel_t *channels = dev->channels;
+ uint32_t rxstsp = REBASE(OTG_GRXSTSP);
+ uint8_t channel = rxstsp&0xf;
+ uint32_t len = (rxstsp>>4) & 0x1ff;
+ if ((rxstsp&OTG_GRXSTSP_PKTSTS_MASK) == OTG_GRXSTSP_PKTSTS_IN) {
+ uint8_t *data = channels[channel].packet.data.in;
+ uint32_t *buf32 = (uint32_t *)&data[channels[channel].data_index];
+
+ int32_t i;
+ uint32_t extra;
+ if (!len) {
+ return;
+ }
+ // Receive data from fifo
+ volatile uint32_t *fifo = &REBASE_CH(OTG_FIFO, channel);
+ for (i = len; i > 4; i -= 4) {
+ *buf32++ = *fifo++;
+ }
+ extra = *fifo;
+
+ memcpy(buf32, &extra, i);
+ channels[channel].data_index += len;
+
+ // If transfer not complete, Enable channel to continue
+ if ( channels[channel].data_index < channels[channel].packet.datalen) {
+ if (len == channels[channel].packet.endpoint_size_max) {
+ REBASE_CH(OTG_HCCHAR, channel) |= OTG_HCCHAR_CHENA;
+ //LOG_PRINTF("CHENA[%d/%d] ", channels[channel].data_index, channels[channel].packet.datalen);
+ }
+
+ }
+
+ } else if ((rxstsp&OTG_GRXSTSP_PKTSTS_MASK) == OTG_GRXSTSP_PKTSTS_IN_COMP) {
+/*
+#ifdef USART_DEBUG
+ uint32_t i;
+ LOG_PRINTF("\nDATA: ");
+ for (i = 0; i < channels[channel].data_index; i++) {
+ uint8_t *data = channels[channel].packet.data.in;
+ LOG_PRINTF("%02X ", data[i]);
+ }
+#endif
+*/
+ } else if ((rxstsp&OTG_GRXSTSP_PKTSTS_MASK) == OTG_GRXSTSP_PKTSTS_CHH) {
+
+ } else {
+
+ }
+}
+
+
+static enum USBH_POLL_STATUS poll_run(usbh_lld_stm32f4_driver_data_t *dev)
+{
+ channel_t *channels = dev->channels;
+
+ if (dev->dpstate == DEVICE_POLL_STATE_DISCONN) {
+ REBASE(OTG_GINTSTS) = REBASE(OTG_GINTSTS);
+ // Check for connection of device
+ if ((REBASE(OTG_HPRT) & OTG_HPRT_PCDET) &&
+ (REBASE(OTG_HPRT) & OTG_HPRT_PCSTS) ) {
+
+ dev->dpstate = DEVICE_POLL_STATE_DEVCONN;
+ dev->timestamp_us = dev->time_curr_us;
+ return USBH_POLL_STATUS_NONE;
+ }
+ }
+
+ if (dev->dpstate == DEVICE_POLL_STATE_DEVCONN) {
+ // May be other condition, e.g. Debounce done,
+ // using 0.5s wait by default
+ if (dev->time_curr_us - dev->timestamp_us < 500000) {
+ return USBH_POLL_STATUS_NONE;
+ }
+
+ if ((REBASE(OTG_HPRT) & OTG_HPRT_PCDET) &&
+ (REBASE(OTG_HPRT) & OTG_HPRT_PCSTS) ) {
+ if ((REBASE(OTG_HPRT) & OTG_HPRT_PSPD_MASK) == OTG_HPRT_PSPD_FULL) {
+ REBASE(OTG_HFIR) = (REBASE(OTG_HFIR) & ~OTG_HFIR_FRIVL_MASK) | 48000;
+ if ((REBASE(OTG_HCFG) & OTG_HCFG_FSLSPCS_MASK) != OTG_HCFG_FSLSPCS_48MHz) {
+ REBASE(OTG_HCFG) = (REBASE(OTG_HCFG) & ~OTG_HCFG_FSLSPCS_MASK) | OTG_HCFG_FSLSPCS_48MHz;
+ LOG_PRINTF("\n Reset Full-Speed \n");
+ }
+ channels_init(dev);
+ dev->dpstate = DEVICE_POLL_STATE_DEVRST;
+ reset_start(dev);
+
+ } else if ((REBASE(OTG_HPRT) & OTG_HPRT_PSPD_MASK) == OTG_HPRT_PSPD_LOW) {
+ REBASE(OTG_HFIR) = (REBASE(OTG_HFIR) & ~OTG_HFIR_FRIVL_MASK) | 6000;
+ if ((REBASE(OTG_HCFG) & OTG_HCFG_FSLSPCS_MASK) != OTG_HCFG_FSLSPCS_6MHz) {
+ REBASE(OTG_HCFG) = (REBASE(OTG_HCFG) & ~OTG_HCFG_FSLSPCS_MASK) | OTG_HCFG_FSLSPCS_6MHz;
+ LOG_PRINTF("\n Reset Low-Speed \n");
+ }
+
+ channels_init(dev);
+ dev->dpstate = DEVICE_POLL_STATE_DEVRST;
+ reset_start(dev);
+ }
+ return USBH_POLL_STATUS_NONE;
+ }
+ }
+
+ if (dev->dpstate == DEVICE_POLL_STATE_DEVRST) {
+ if (dev->time_curr_us - dev->timestamp_us < 210000) {
+ return USBH_POLL_STATUS_NONE;
+ } else {
+ dev->dpstate = DEVICE_POLL_STATE_RUN;
+ }
+ }
+
+ // ELSE RUN
+
+ if (REBASE(OTG_GINTSTS) & OTG_GINTSTS_SOF) {
+ REBASE(OTG_GINTSTS) = OTG_GINTSTS_SOF;
+ }
+
+ while (REBASE(OTG_GINTSTS) & OTG_GINTSTS_RXFLVL) {
+ //receive data
+ rxflvl_handle(dev);
+ }
+
+ if (REBASE(OTG_GINTSTS) & OTG_GINTSTS_HPRTINT) {
+ if (REBASE(OTG_HPRT) & OTG_HPRT_PENCHNG) {
+ uint32_t hprt = REBASE(OTG_HPRT);
+ // Clear Interrupt
+ // HARDWARE BUG - not mentioned in errata
+ // To clear interrupt write 0 to PENA
+ // To disable port write 1 to PENCHNG
+ REBASE(OTG_HPRT) &= ~OTG_HPRT_PENA;
+ //LOG_PRINTF("PENCHNG");
+ if ((hprt & OTG_HPRT_PENA)) {
+ return USBH_POLL_STATUS_DEVICE_CONNECTED;
+ }
+
+ }
+
+ if (REBASE(OTG_HPRT) & OTG_HPRT_POCCHNG) {
+ // TODO: Check for functionality
+ REBASE(OTG_HPRT) |= OTG_HPRT_POCCHNG;
+ //LOG_PRINTF("POCCHNG");
+ }
+ }
+
+ if (REBASE(OTG_GINTSTS) & OTG_GINTSTS_DISCINT) {
+ REBASE(OTG_GINTSTS) = OTG_GINTSTS_DISCINT;
+ //LOG_PRINTF("DISCINT");
+
+ /*
+ * When the voltage drops, DISCINT interrupt is generated although
+ * Device is connected, so there is no need to reinitialize channels.
+ * Often, DISCINT is bad interpreted upon insertion of device
+ */
+ if (!(REBASE(OTG_HPRT) & OTG_HPRT_PCSTS)) {
+ //LOG_PRINTF("discint processsing...");
+ channels_init(dev);
+ }
+ REBASE(OTG_GINTSTS) = REBASE(OTG_GINTSTS);
+ dev->dpstate = DEVICE_POLL_STATE_DISCONN;
+ return USBH_POLL_STATUS_DEVICE_DISCONNECTED;
+ }
+
+ if (REBASE(OTG_GINTSTS) & OTG_GINTSTS_HCINT) {
+ uint32_t channel;
+
+ for(channel = 0; channel < dev->num_channels; channel++)
+ {
+ if (channels[channel].state != CHANNEL_STATE_WORK ||
+ !(REBASE(OTG_HAINT)&(1<<channel))) {
+ continue;
+ }
+ uint32_t hcint = REBASE_CH(OTG_HCINT, channel);
+ uint8_t eptyp = channels[channel].packet.endpoint_type;
+
+ // Write
+ if (!(REBASE_CH(OTG_HCCHAR, channel)&OTG_HCCHAR_EPDIR_IN)) {
+
+ if (hcint & OTG_HCINT_NAK) {
+ REBASE_CH(OTG_HCINT, channel) = OTG_HCINT_NAK;
+ //LOG_PRINTF("NAK\n");
+
+ free_channel(dev, channel);
+
+ usbh_packet_callback_data_t cb_data;
+ cb_data.status = USBH_PACKET_CALLBACK_STATUS_EAGAIN;
+ cb_data.transferred_length = channels[channel].data_index;
+
+ channels[channel].packet.callback(
+ channels[channel].packet.callback_arg,
+ cb_data);
+
+ }
+
+ if (hcint & OTG_HCINT_ACK) {
+ REBASE_CH(OTG_HCINT, channel) = OTG_HCINT_ACK;
+ //LOG_PRINTF("ACK");
+ if (eptyp == USBH_ENDPOINT_TYPE_CONTROL) {
+ channels[channel].packet.toggle[0] = 1;
+ } else {
+ channels[channel].packet.toggle[0] ^= 1;
+ }
+ }
+
+ if (hcint & OTG_HCINT_XFRC) {
+ REBASE_CH(OTG_HCINT, channel) = OTG_HCINT_XFRC;
+ //LOG_PRINTF("XFRC\n");
+
+ free_channel(dev, channel);
+
+ usbh_packet_callback_data_t cb_data;
+ cb_data.status = USBH_PACKET_CALLBACK_STATUS_OK;
+ cb_data.transferred_length = channels[channel].data_index;
+
+ channels[channel].packet.callback(
+ channels[channel].packet.callback_arg,
+ cb_data);
+ continue;
+ }
+
+ if (hcint & OTG_HCINT_FRMOR) {
+ REBASE_CH(OTG_HCINT, channel) = OTG_HCINT_FRMOR;
+ //LOG_PRINTF("FRMOR");
+
+ free_channel(dev, channel);
+
+ usbh_packet_callback_data_t cb_data;
+ cb_data.status = USBH_PACKET_CALLBACK_STATUS_EFATAL;
+ cb_data.transferred_length = 0;
+
+ channels[channel].packet.callback(
+ channels[channel].packet.callback_arg,
+ cb_data);
+ }
+
+ if (hcint & OTG_HCINT_TXERR) {
+ REBASE_CH(OTG_HCINT, channel) = OTG_HCINT_TXERR;
+ //LOG_PRINTF("TXERR");
+
+ free_channel(dev, channel);
+
+ usbh_packet_callback_data_t cb_data;
+ cb_data.status = USBH_PACKET_CALLBACK_STATUS_EAGAIN;
+ cb_data.transferred_length = 0;
+
+ channels[channel].packet.callback(
+ channels[channel].packet.callback_arg,
+ cb_data);
+
+
+ }
+
+ if (hcint & OTG_HCINT_STALL) {
+ REBASE_CH(OTG_HCINT, channel) = OTG_HCINT_STALL;
+ //LOG_PRINTF("STALL");
+
+ free_channel(dev, channel);
+
+ usbh_packet_callback_data_t cb_data;
+ cb_data.status = USBH_PACKET_CALLBACK_STATUS_EFATAL;
+ cb_data.transferred_length = 0;
+
+
+ channels[channel].packet.callback(
+ channels[channel].packet.callback_arg,
+ cb_data);
+ }
+
+ if (hcint & OTG_HCINT_CHH) {
+ REBASE_CH(OTG_HCINT, channel) = OTG_HCINT_CHH;
+ //LOG_PRINTF("CHH");
+
+ free_channel(dev, channel);
+ }
+ } else { // Read
+
+ if (hcint & OTG_HCINT_NAK) {
+ REBASE_CH(OTG_HCINT, channel) = OTG_HCINT_NAK;
+ if (eptyp == USBH_ENDPOINT_TYPE_CONTROL) {
+ //LOG_PRINTF("NAK");
+ }
+
+ REBASE_CH(OTG_HCCHAR, channel) |= OTG_HCCHAR_CHENA;
+
+ }
+
+ if (hcint & OTG_HCINT_DTERR) {
+ REBASE_CH(OTG_HCINT, channel) = OTG_HCINT_DTERR;
+ //LOG_PRINTF("DTERR");
+ }
+
+ if (hcint & OTG_HCINT_ACK) {
+ REBASE_CH(OTG_HCINT, channel) = OTG_HCINT_ACK;
+ //LOG_PRINTF("ACK");
+
+ channels[channel].packet.toggle[0] ^= 1;
+
+ }
+
+
+
+ if (hcint & OTG_HCINT_XFRC) {
+ REBASE_CH(OTG_HCINT, channel) = OTG_HCINT_XFRC;
+ //LOG_PRINTF("XFRC\n");
+
+ free_channel(dev, channel);
+ usbh_packet_callback_data_t cb_data;
+ if (channels[channel].data_index == channels[channel].packet.datalen) {
+ cb_data.status = USBH_PACKET_CALLBACK_STATUS_OK;
+ } else {
+ cb_data.status = USBH_PACKET_CALLBACK_STATUS_ERRSIZ;
+ }
+ cb_data.transferred_length = channels[channel].data_index;
+
+ channels[channel].packet.callback(
+ channels[channel].packet.callback_arg,
+ cb_data);
+
+ continue;
+ }
+
+ if (hcint & OTG_HCINT_BBERR) {
+ REBASE_CH(OTG_HCINT, channel) = OTG_HCINT_BBERR;
+ //LOG_PRINTF("BBERR");
+ free_channel(dev, channel);
+
+ usbh_packet_callback_data_t cb_data;
+ cb_data.status = USBH_PACKET_CALLBACK_STATUS_EFATAL;
+ cb_data.transferred_length = 0;
+
+ channels[channel].packet.callback(
+ channels[channel].packet.callback_arg,
+ cb_data);
+ }
+
+ if (hcint & OTG_HCINT_FRMOR) {
+ REBASE_CH(OTG_HCINT, channel) = OTG_HCINT_FRMOR;
+ //LOG_PRINTF("FRMOR");
+
+ }
+
+ if (hcint & OTG_HCINT_TXERR) {
+ REBASE_CH(OTG_HCINT, channel) = OTG_HCINT_TXERR;
+ //LOG_PRINTF("TXERR");
+
+ free_channel(dev, channel);
+
+ usbh_packet_callback_data_t cb_data;
+ cb_data.status = USBH_PACKET_CALLBACK_STATUS_EFATAL;
+ cb_data.transferred_length = 0;
+
+ channels[channel].packet.callback(
+ channels[channel].packet.callback_arg,
+ cb_data);
+
+ }
+
+ if (hcint & OTG_HCINT_STALL) {
+ REBASE_CH(OTG_HCINT, channel) = OTG_HCINT_STALL;
+ //LOG_PRINTF("STALL");
+
+ free_channel(dev, channel);
+
+ usbh_packet_callback_data_t cb_data;
+ cb_data.status = USBH_PACKET_CALLBACK_STATUS_EFATAL;
+ cb_data.transferred_length = 0;
+
+ channels[channel].packet.callback(
+ channels[channel].packet.callback_arg,
+ cb_data);
+
+ }
+ if (hcint & OTG_HCINT_CHH) {
+ REBASE_CH(OTG_HCINT, channel) = OTG_HCINT_CHH;
+ //LOG_PRINTF("CHH");
+ free_channel(dev, channel);
+ }
+
+ }
+ }
+ }
+
+ if (REBASE(OTG_GINTSTS) & OTG_GINTSTS_MMIS) {
+ REBASE(OTG_GINTSTS) = OTG_GINTSTS_MMIS;
+ //LOG_PRINTF("Mode mismatch");
+ }
+
+ if (REBASE(OTG_GINTSTS) & OTG_GINTSTS_IPXFR) {
+ REBASE(OTG_GINTSTS) = OTG_GINTSTS_IPXFR;
+ //LOG_PRINTF("IPXFR");
+ }
+
+ return USBH_POLL_STATUS_NONE;
+}
+
+/*
+ * Sequence numbers are hardcoded, since it is used
+ * locally in poll_init() function.
+ * If value of poll_sequence is needed elsewhere, enum must be defined.
+ *
+ */
+static void poll_init(usbh_lld_stm32f4_driver_data_t *dev)
+{
+ //=======================================
+
+ int done = 0;
+ /* Wait for AHB idle. */
+ switch (dev->poll_sequence) {
+ case 0:// wait until AHBIDL is set
+ if (REBASE(OTG_GRSTCTL) & OTG_GRSTCTL_AHBIDL) {
+ done = 1;
+ }
+ break;
+
+ case 1:// wait 1ms and issue core soft reset
+
+ // needs delay to not hang?? Do not know why.
+ // Maybe after AHBIDL is set, it needs to set up some things
+ if (dev->time_curr_us - dev->timestamp_us > 1000) {
+ REBASE(OTG_GRSTCTL) |= OTG_GRSTCTL_CSRST;
+ done = 1;
+ }
+ break;
+
+ case 2:// wait until core soft reset processing is done
+ if (!(REBASE(OTG_GRSTCTL) & OTG_GRSTCTL_CSRST)) {
+ done = 1;
+ }
+ break;
+
+ case 3:// wait for 50ms
+ if (dev->time_curr_us - dev->timestamp_us > 50000) {
+ done = 1;
+ }
+ break;
+
+ case 4:// wait until AHBIDL is set and power up the USB
+ if (REBASE(OTG_GRSTCTL) & OTG_GRSTCTL_AHBIDL) {
+ REBASE(OTG_GCCFG) = OTG_GCCFG_VBUSASEN | OTG_GCCFG_VBUSBSEN |
+ OTG_GCCFG_NOVBUSSENS | OTG_GCCFG_PWRDWN;
+ done = 1;
+ }
+ break;
+
+ case 5:// wait for 50ms and force host only mode
+ if (dev->time_curr_us - dev->timestamp_us > 50000) {
+
+ // Core initialized
+ // Force host only mode.
+ REBASE(OTG_GUSBCFG) |= OTG_GUSBCFG_FHMOD;
+ done = 1;
+ }
+ break;
+
+ case 6:// wait for 200ms and reset PHY clock start reset processing
+ if (dev->time_curr_us - dev->timestamp_us > 200000) {
+ /* Restart the PHY clock. */
+ REBASE(OTG_PCGCCTL) = 0;
+
+ REBASE(OTG_HCFG) = (REBASE(OTG_HCFG) & ~OTG_HCFG_FSLSPCS_MASK) |
+ OTG_HCFG_FSLSPCS_48MHz;
+
+ // Start reset processing
+ REBASE(OTG_HPRT) |= OTG_HPRT_PRST;
+
+ done = 1;
+
+ }
+ break;
+
+ case 7:// wait for reset processing to be done(12ms), disable PRST
+ if (dev->time_curr_us - dev->timestamp_us > 12000) {
+
+ REBASE(OTG_HPRT) &= ~OTG_HPRT_PRST;
+ done = 1;
+ }
+ break;
+
+ case 8:// wait 12ms after PRST was disabled, configure fifo
+ if (dev->time_curr_us - dev->timestamp_us > 12000) {
+
+ REBASE(OTG_HCFG) &= ~OTG_HCFG_FSLSS;
+
+ REBASE(OTG_GRXFSIZ) = RX_FIFO_SIZE;
+ REBASE(OTG_GNPTXFSIZ) = (TX_NP_FIFO_SIZE << 16) |
+ RX_FIFO_SIZE;
+ REBASE(OTG_HPTXFSIZ) = (TX_P_FIFO_SIZE << 16) |
+ (RX_FIFO_SIZE + TX_NP_FIFO_SIZE);
+
+ // FLUSH RX FIFO
+ REBASE(OTG_GRSTCTL) |= OTG_GRSTCTL_RXFFLSH;
+
+ done = 1;
+ }
+ break;
+
+ case 9: // wait to RX FIFO become flushed, flush TX
+ if (!(REBASE(OTG_GRSTCTL) & OTG_GRSTCTL_RXFFLSH)) {
+ REBASE(OTG_GRSTCTL) |= OTG_GRSTCTL_TXFFLSH | (0x10 << 6);
+
+ done = 1;
+ }
+ break;
+
+ case 10: // wait to TX FIFO become flushed
+ if (!(REBASE(OTG_GRSTCTL) & OTG_GRSTCTL_TXFFLSH)) {
+
+ channels_init(dev);
+
+ REBASE(OTG_GOTGINT) |= 1 << 19;
+ REBASE(OTG_GINTMSK) = 0;
+ REBASE(OTG_GINTSTS) = ~0;
+ REBASE(OTG_HPRT) |= OTG_HPRT_PPWR;
+
+ done = 1;
+ }
+ break;
+
+ case 11: // wait 200ms
+ if (dev->time_curr_us - dev->timestamp_us > 200000) {
+
+ // Uncomment to enable Interrupt generation
+ REBASE(OTG_GAHBCFG) |= OTG_GAHBCFG_GINT;
+
+ LOG_PRINTF("INIT COMPLETE\n");
+
+ // Finish
+ dev->state = DEVICE_STATE_RUN;
+ dev->dpstate = DEVICE_POLL_STATE_DISCONN;
+
+ done = 1;
+ }
+ }
+
+ if (done) {
+ dev->poll_sequence++;
+ dev->timestamp_us = dev->time_curr_us;
+ //LOG_PRINTF("\t\t POLL SEQUENCE %d\n", dev->poll_sequence);
+ }
+
+}
+
+static void poll_reset(usbh_lld_stm32f4_driver_data_t *dev)
+{
+ if (dev->time_curr_us - dev->timestamp_us > 10000) {
+ REBASE(OTG_HPRT) &= ~OTG_HPRT_PRST;
+ dev->state = dev->state_prev;
+ dev->state_prev = DEVICE_STATE_RESET;
+
+ //LOG_PRINTF("RESET");
+ } else {
+ LOG_PRINTF("waiting %d < %d\n",dev->time_curr_us, dev->timestamp_us);
+ }
+}
+
+static enum USBH_POLL_STATUS poll(void *drvdata, uint32_t time_curr_us)
+{
+ (void)time_curr_us;
+
+ usbh_lld_stm32f4_driver_data_t *dev = drvdata;
+ enum USBH_POLL_STATUS ret = USBH_POLL_STATUS_NONE;
+
+ dev->time_curr_us = time_curr_us;
+
+ switch (dev->state) {
+ case DEVICE_STATE_RUN:
+ ret = poll_run(dev);
+ break;
+
+ case DEVICE_STATE_INIT:
+ poll_init(dev);
+ break;
+
+ case DEVICE_STATE_RESET:
+ poll_reset(dev);
+ break;
+
+ default:
+ break;
+ }
+
+ return ret;
+
+}
+
+
+/**
+ *
+ * Returns positive free channel id
+ * otherwise -1 for error
+ */
+static int8_t get_free_channel(void *drvdata)
+{
+ usbh_lld_stm32f4_driver_data_t *dev = drvdata;
+ channel_t *channels = dev->channels;
+ uint32_t i = 0;
+ for (i = 0; i < dev->num_channels; i++) {
+ if (dev->channels[i].state == CHANNEL_STATE_FREE &&
+ !(REBASE_CH(OTG_HCCHAR, i) & OTG_HCCHAR_CHENA)) {
+ channels[i].state = CHANNEL_STATE_WORK;
+ REBASE_CH(OTG_HCINT, i) = ~0;
+ REBASE_CH(OTG_HCINTMSK, i) |= OTG_HCINTMSK_ACKM | OTG_HCINTMSK_NAKM |
+ OTG_HCINTMSK_TXERRM | OTG_HCINTMSK_XFRCM |
+ OTG_HCINTMSK_DTERRM | OTG_HCINTMSK_BBERRM |
+ OTG_HCINTMSK_CHHM | OTG_HCINTMSK_STALLM |
+ OTG_HCINTMSK_FRMORM;
+ REBASE(OTG_HAINTMSK) |= (1 << i);
+ return i;
+ }
+ }
+ return -1;
+}
+
+/*
+ * Do not clear callback and callback data, so channel can be freed even before callback is called
+ * This saves number of active channels: When one transfer ends, in callback driver can write/read to this channel again (indirectly)
+ */
+static void free_channel(void *drvdata, uint8_t channel)
+{
+ usbh_lld_stm32f4_driver_data_t *dev = drvdata;
+ channel_t *channels = dev->channels;
+
+ if (REBASE_CH(OTG_HCCHAR, channel) & OTG_HCCHAR_CHENA) {
+ REBASE_CH(OTG_HCCHAR, channel) |= OTG_HCCHAR_CHDIS;
+ REBASE_CH(OTG_HCINT, channel) = ~0;
+ LOG_PRINTF("\nDisabling channel %d\n", channel);
+ } else {
+ channels[channel].state = CHANNEL_STATE_FREE;
+ }
+}
+/**
+ * Init channels
+ */
+static void channels_init(void *drvdata)
+{
+ usbh_lld_stm32f4_driver_data_t *dev = drvdata;
+
+ uint32_t i = 0;
+ for (i = 0; i < dev->num_channels; i++) {
+ REBASE_CH(OTG_HCINT, i) = ~0;
+ REBASE_CH(OTG_HCINTMSK, i) = 0x7ff;
+ free_channel(dev, i);
+ }
+
+ // Enable interrupt mask bits for all channels
+ REBASE(OTG_HAINTMSK) = (1 << dev->num_channels) - 1;
+}
+
+/**
+ * Get speed of connected device
+ *
+ */
+static enum USBH_SPEED root_speed(void *drvdata)
+{
+ usbh_lld_stm32f4_driver_data_t *dev = drvdata;
+ (void)dev;
+ uint32_t hprt_speed = REBASE(OTG_HPRT) & OTG_HPRT_PSPD_MASK;
+ if (hprt_speed == OTG_HPRT_PSPD_LOW) {
+ return USBH_SPEED_LOW;
+ } else if(hprt_speed == OTG_HPRT_PSPD_FULL) {
+ return USBH_SPEED_FULL;
+ } else if(hprt_speed == OTG_HPRT_PSPD_HIGH) {
+ return USBH_SPEED_HIGH;
+ } else {
+ // Should not happen(let the compiler be happy)
+ return USBH_SPEED_FULL;
+ }
+}
+#endif // if defined otg_hs or otg_fs
+
+
+#ifdef USART_DEBUG
+
+/**
+ * Just for debug
+ */
+void print_channels(const void *lld)
+{
+ usbh_lld_stm32f4_driver_data_t *dev = ((usbh_low_level_driver_t *)lld)->driver_data;
+ channel_t *channels = dev->channels;
+ int32_t i;
+ LOG_PRINTF("\nCHANNELS: \n");
+ for (i = 0;i < dev->num_channels;i++) {
+ LOG_PRINTF("%4d %4d %4d %08X\n", channels[i].state, channels[i].packet.address, channels[i].packet.datalen, MMIO32(dev->base + OTG_HCINT(i)));
+ }
+}
+#endif
+
+// USB Full Speed - OTG_FS
+#if defined(USE_STM32F4_USBH_DRIVER_FS)
+#define NUM_CHANNELS_FS (8)
+static channel_t channels_fs[NUM_CHANNELS_FS];
+static usbh_lld_stm32f4_driver_data_t driver_data_fs = {
+ .base = USB_OTG_FS_BASE,
+ .channels = channels_fs,
+ .num_channels = NUM_CHANNELS_FS
+};
+const usbh_low_level_driver_t usbh_lld_stm32f4_driver_fs = {
+ .init = init,
+ .poll = poll,
+ .read = read,
+ .write = write,
+ .root_speed = root_speed,
+ .driver_data = &driver_data_fs
+};
+#endif
+
+// USB High Speed - OTG_HS
+#if defined(USE_STM32F4_USBH_DRIVER_HS)
+#define NUM_CHANNELS_HS (12)
+static channel_t channels_hs[NUM_CHANNELS_HS];
+static usbh_lld_stm32f4_driver_data_t driver_data_hs = {
+ .base = USB_OTG_HS_BASE,
+ .channels = channels_hs,
+ .num_channels = NUM_CHANNELS_HS
+};
+
+const usbh_low_level_driver_t usbh_lld_stm32f4_driver_hs = {
+ .init = init,
+ .poll = poll,
+ .read = read,
+ .write = write,
+ .root_speed = root_speed,
+ .driver_data = &driver_data_hs
+};
+
+#endif
diff --git a/fw/src/words.c b/fw/src/words.c
new file mode 100644
index 0000000..387d23b
--- /dev/null
+++ b/fw/src/words.c
@@ -0,0 +1,521 @@
+
+#include "words.h"
+
+const char * const even[256] = {
+ "aardvark", /* 00 */
+ "absurd", /* 01 */
+ "accrue", /* 02 */
+ "acme", /* 03 */
+ "adrift", /* 04 */
+ "adult", /* 05 */
+ "afflict", /* 06 */
+ "ahead", /* 07 */
+ "aimless", /* 08 */
+ "Algol", /* 09 */
+ "allow", /* 0A */
+ "alone", /* 0B */
+ "ammo", /* 0C */
+ "ancient", /* 0D */
+ "apple", /* 0E */
+ "artist", /* 0F */
+ "assume", /* 10 */
+ "Athens", /* 11 */
+ "atlas", /* 12 */
+ "Aztec", /* 13 */
+ "baboon", /* 14 */
+ "backfield", /* 15 */
+ "backward", /* 16 */
+ "banjo", /* 17 */
+ "beaming", /* 18 */
+ "bedlamp", /* 19 */
+ "beehive", /* 1A */
+ "beeswax", /* 1B */
+ "befriend", /* 1C */
+ "Belfast", /* 1D */
+ "berserk", /* 1E */
+ "billiard", /* 1F */
+ "bison", /* 20 */
+ "blackjack", /* 21 */
+ "blockade", /* 22 */
+ "blowtorch", /* 23 */
+ "bluebird", /* 24 */
+ "bombast", /* 25 */
+ "bookshelf", /* 26 */
+ "brackish", /* 27 */
+ "breadline", /* 28 */
+ "breakup", /* 29 */
+ "brickyard", /* 2A */
+ "briefcase", /* 2B */
+ "Burbank", /* 2C */
+ "button", /* 2D */
+ "buzzard", /* 2E */
+ "cement", /* 2F */
+ "chairlift", /* 30 */
+ "chatter", /* 31 */
+ "checkup", /* 32 */
+ "chisel", /* 33 */
+ "choking", /* 34 */
+ "chopper", /* 35 */
+ "Christmas", /* 36 */
+ "clamshell", /* 37 */
+ "classic", /* 38 */
+ "classroom", /* 39 */
+ "cleanup", /* 3A */
+ "clockwork", /* 3B */
+ "cobra", /* 3C */
+ "commence", /* 3D */
+ "concert", /* 3E */
+ "cowbell", /* 3F */
+ "crackdown", /* 40 */
+ "cranky", /* 41 */
+ "crowfoot", /* 42 */
+ "crucial", /* 43 */
+ "crumpled", /* 44 */
+ "crusade", /* 45 */
+ "cubic", /* 46 */
+ "dashboard", /* 47 */
+ "deadbolt", /* 48 */
+ "deckhand", /* 49 */
+ "dogsled", /* 4A */
+ "dragnet", /* 4B */
+ "drainage", /* 4C */
+ "dreadful", /* 4D */
+ "drifter", /* 4E */
+ "dropper", /* 4F */
+ "drumbeat", /* 50 */
+ "drunken", /* 51 */
+ "Dupont", /* 52 */
+ "dwelling", /* 53 */
+ "eating", /* 54 */
+ "edict", /* 55 */
+ "egghead", /* 56 */
+ "eightball", /* 57 */
+ "endorse", /* 58 */
+ "endow", /* 59 */
+ "enlist", /* 5A */
+ "erase", /* 5B */
+ "escape", /* 5C */
+ "exceed", /* 5D */
+ "eyeglass", /* 5E */
+ "eyetooth", /* 5F */
+ "facial", /* 60 */
+ "fallout", /* 61 */
+ "flagpole", /* 62 */
+ "flatfoot", /* 63 */
+ "flytrap", /* 64 */
+ "fracture", /* 65 */
+ "framework", /* 66 */
+ "freedom", /* 67 */
+ "frighten", /* 68 */
+ "gazelle", /* 69 */
+ "Geiger", /* 6A */
+ "glitter", /* 6B */
+ "glucose", /* 6C */
+ "goggles", /* 6D */
+ "goldfish", /* 6E */
+ "gremlin", /* 6F */
+ "guidance", /* 70 */
+ "hamlet", /* 71 */
+ "highchair", /* 72 */
+ "hockey", /* 73 */
+ "indoors", /* 74 */
+ "indulge", /* 75 */
+ "inverse", /* 76 */
+ "involve", /* 77 */
+ "island", /* 78 */
+ "jawbone", /* 79 */
+ "keyboard", /* 7A */
+ "kickoff", /* 7B */
+ "kiwi", /* 7C */
+ "klaxon", /* 7D */
+ "locale", /* 7E */
+ "lockup", /* 7F */
+ "merit", /* 80 */
+ "minnow", /* 81 */
+ "miser", /* 82 */
+ "Mohawk", /* 83 */
+ "mural", /* 84 */
+ "music", /* 85 */
+ "necklace", /* 86 */
+ "Neptune", /* 87 */
+ "newborn", /* 88 */
+ "nightbird", /* 89 */
+ "Oakland", /* 8A */
+ "obtuse", /* 8B */
+ "offload", /* 8C */
+ "optic", /* 8D */
+ "orca", /* 8E */
+ "payday", /* 8F */
+ "peachy", /* 90 */
+ "pheasant", /* 91 */
+ "physique", /* 92 */
+ "playhouse", /* 93 */
+ "Pluto", /* 94 */
+ "preclude", /* 95 */
+ "prefer", /* 96 */
+ "preshrunk", /* 97 */
+ "printer", /* 98 */
+ "prowler", /* 99 */
+ "pupil", /* 9A */
+ "puppy", /* 9B */
+ "python", /* 9C */
+ "quadrant", /* 9D */
+ "quiver", /* 9E */
+ "quota", /* 9F */
+ "ragtime", /* A0 */
+ "ratchet", /* A1 */
+ "rebirth", /* A2 */
+ "reform", /* A3 */
+ "regain", /* A4 */
+ "reindeer", /* A5 */
+ "rematch", /* A6 */
+ "repay", /* A7 */
+ "retouch", /* A8 */
+ "revenge", /* A9 */
+ "reward", /* AA */
+ "rhythm", /* AB */
+ "ribcage", /* AC */
+ "ringbolt", /* AD */
+ "robust", /* AE */
+ "rocker", /* AF */
+ "ruffled", /* B0 */
+ "sailboat", /* B1 */
+ "sawdust", /* B2 */
+ "scallion", /* B3 */
+ "scenic", /* B4 */
+ "scorecard", /* B5 */
+ "Scotland", /* B6 */
+ "seabird", /* B7 */
+ "select", /* B8 */
+ "sentence", /* B9 */
+ "shadow", /* BA */
+ "shamrock", /* BB */
+ "showgirl", /* BC */
+ "skullcap", /* BD */
+ "skydive", /* BE */
+ "slingshot", /* BF */
+ "slowdown", /* C0 */
+ "snapline", /* C1 */
+ "snapshot", /* C2 */
+ "snowcap", /* C3 */
+ "snowslide", /* C4 */
+ "solo", /* C5 */
+ "southward", /* C6 */
+ "soybean", /* C7 */
+ "spaniel", /* C8 */
+ "spearhead", /* C9 */
+ "spellbind", /* CA */
+ "spheroid", /* CB */
+ "spigot", /* CC */
+ "spindle", /* CD */
+ "spyglass", /* CE */
+ "stagehand", /* CF */
+ "stagnate", /* D0 */
+ "stairway", /* D1 */
+ "standard", /* D2 */
+ "stapler", /* D3 */
+ "steamship", /* D4 */
+ "sterling", /* D5 */
+ "stockman", /* D6 */
+ "stopwatch", /* D7 */
+ "stormy", /* D8 */
+ "sugar", /* D9 */
+ "surmount", /* DA */
+ "suspense", /* DB */
+ "sweatband", /* DC */
+ "swelter", /* DD */
+ "tactics", /* DE */
+ "talon", /* DF */
+ "tapeworm", /* E0 */
+ "tempest", /* E1 */
+ "tiger", /* E2 */
+ "tissue", /* E3 */
+ "tonic", /* E4 */
+ "topmost", /* E5 */
+ "tracker", /* E6 */
+ "transit", /* E7 */
+ "trauma", /* E8 */
+ "treadmill", /* E9 */
+ "Trojan", /* EA */
+ "trouble", /* EB */
+ "tumor", /* EC */
+ "tunnel", /* ED */
+ "tycoon", /* EE */
+ "uncut", /* EF */
+ "unearth", /* F0 */
+ "unwind", /* F1 */
+ "uproot", /* F2 */
+ "upset", /* F3 */
+ "upshot", /* F4 */
+ "vapor", /* F5 */
+ "village", /* F6 */
+ "virus", /* F7 */
+ "Vulcan", /* F8 */
+ "waffle", /* F9 */
+ "wallet", /* FA */
+ "watchword", /* FB */
+ "wayside", /* FC */
+ "willow", /* FD */
+ "woodlark", /* FE */
+ "Zulu" /* FF */
+};
+
+const char * const odd[256] = {
+ "aardvark", /* 00 */
+ "absurd", /* 01 */
+ "accrue", /* 02 */
+ "acme", /* 03 */
+ "adrift", /* 04 */
+ "adult", /* 05 */
+ "afflict", /* 06 */
+ "ahead", /* 07 */
+ "aimless", /* 08 */
+ "Algol", /* 09 */
+ "allow", /* 0A */
+ "alone", /* 0B */
+ "ammo", /* 0C */
+ "ancient", /* 0D */
+ "apple", /* 0E */
+ "artist", /* 0F */
+ "assume", /* 10 */
+ "Athens", /* 11 */
+ "atlas", /* 12 */
+ "Aztec", /* 13 */
+ "baboon", /* 14 */
+ "backfield", /* 15 */
+ "backward", /* 16 */
+ "banjo", /* 17 */
+ "beaming", /* 18 */
+ "bedlamp", /* 19 */
+ "beehive", /* 1A */
+ "beeswax", /* 1B */
+ "befriend", /* 1C */
+ "Belfast", /* 1D */
+ "berserk", /* 1E */
+ "billiard", /* 1F */
+ "bison", /* 20 */
+ "blackjack", /* 21 */
+ "blockade", /* 22 */
+ "blowtorch", /* 23 */
+ "bluebird", /* 24 */
+ "bombast", /* 25 */
+ "bookshelf", /* 26 */
+ "brackish", /* 27 */
+ "breadline", /* 28 */
+ "breakup", /* 29 */
+ "brickyard", /* 2A */
+ "briefcase", /* 2B */
+ "Burbank", /* 2C */
+ "button", /* 2D */
+ "buzzard", /* 2E */
+ "cement", /* 2F */
+ "chairlift", /* 30 */
+ "chatter", /* 31 */
+ "checkup", /* 32 */
+ "chisel", /* 33 */
+ "choking", /* 34 */
+ "chopper", /* 35 */
+ "Christmas", /* 36 */
+ "clamshell", /* 37 */
+ "classic", /* 38 */
+ "classroom", /* 39 */
+ "cleanup", /* 3A */
+ "clockwork", /* 3B */
+ "cobra", /* 3C */
+ "commence", /* 3D */
+ "concert", /* 3E */
+ "cowbell", /* 3F */
+ "crackdown", /* 40 */
+ "cranky", /* 41 */
+ "crowfoot", /* 42 */
+ "crucial", /* 43 */
+ "crumpled", /* 44 */
+ "crusade", /* 45 */
+ "cubic", /* 46 */
+ "dashboard", /* 47 */
+ "deadbolt", /* 48 */
+ "deckhand", /* 49 */
+ "dogsled", /* 4A */
+ "dragnet", /* 4B */
+ "drainage", /* 4C */
+ "dreadful", /* 4D */
+ "drifter", /* 4E */
+ "dropper", /* 4F */
+ "drumbeat", /* 50 */
+ "drunken", /* 51 */
+ "Dupont", /* 52 */
+ "dwelling", /* 53 */
+ "eating", /* 54 */
+ "edict", /* 55 */
+ "egghead", /* 56 */
+ "eightball", /* 57 */
+ "endorse", /* 58 */
+ "endow", /* 59 */
+ "enlist", /* 5A */
+ "erase", /* 5B */
+ "escape", /* 5C */
+ "exceed", /* 5D */
+ "eyeglass", /* 5E */
+ "eyetooth", /* 5F */
+ "facial", /* 60 */
+ "fallout", /* 61 */
+ "flagpole", /* 62 */
+ "flatfoot", /* 63 */
+ "flytrap", /* 64 */
+ "fracture", /* 65 */
+ "framework", /* 66 */
+ "freedom", /* 67 */
+ "frighten", /* 68 */
+ "gazelle", /* 69 */
+ "Geiger", /* 6A */
+ "glitter", /* 6B */
+ "glucose", /* 6C */
+ "goggles", /* 6D */
+ "goldfish", /* 6E */
+ "gremlin", /* 6F */
+ "guidance", /* 70 */
+ "hamlet", /* 71 */
+ "highchair", /* 72 */
+ "hockey", /* 73 */
+ "indoors", /* 74 */
+ "indulge", /* 75 */
+ "inverse", /* 76 */
+ "involve", /* 77 */
+ "island", /* 78 */
+ "jawbone", /* 79 */
+ "keyboard", /* 7A */
+ "kickoff", /* 7B */
+ "kiwi", /* 7C */
+ "klaxon", /* 7D */
+ "locale", /* 7E */
+ "lockup", /* 7F */
+ "merit", /* 80 */
+ "minnow", /* 81 */
+ "miser", /* 82 */
+ "Mohawk", /* 83 */
+ "mural", /* 84 */
+ "music", /* 85 */
+ "necklace", /* 86 */
+ "Neptune", /* 87 */
+ "newborn", /* 88 */
+ "nightbird", /* 89 */
+ "Oakland", /* 8A */
+ "obtuse", /* 8B */
+ "offload", /* 8C */
+ "optic", /* 8D */
+ "orca", /* 8E */
+ "payday", /* 8F */
+ "peachy", /* 90 */
+ "pheasant", /* 91 */
+ "physique", /* 92 */
+ "playhouse", /* 93 */
+ "Pluto", /* 94 */
+ "preclude", /* 95 */
+ "prefer", /* 96 */
+ "preshrunk", /* 97 */
+ "printer", /* 98 */
+ "prowler", /* 99 */
+ "pupil", /* 9A */
+ "puppy", /* 9B */
+ "python", /* 9C */
+ "quadrant", /* 9D */
+ "quiver", /* 9E */
+ "quota", /* 9F */
+ "ragtime", /* A0 */
+ "ratchet", /* A1 */
+ "rebirth", /* A2 */
+ "reform", /* A3 */
+ "regain", /* A4 */
+ "reindeer", /* A5 */
+ "rematch", /* A6 */
+ "repay", /* A7 */
+ "retouch", /* A8 */
+ "revenge", /* A9 */
+ "reward", /* AA */
+ "rhythm", /* AB */
+ "ribcage", /* AC */
+ "ringbolt", /* AD */
+ "robust", /* AE */
+ "rocker", /* AF */
+ "ruffled", /* B0 */
+ "sailboat", /* B1 */
+ "sawdust", /* B2 */
+ "scallion", /* B3 */
+ "scenic", /* B4 */
+ "scorecard", /* B5 */
+ "Scotland", /* B6 */
+ "seabird", /* B7 */
+ "select", /* B8 */
+ "sentence", /* B9 */
+ "shadow", /* BA */
+ "shamrock", /* BB */
+ "showgirl", /* BC */
+ "skullcap", /* BD */
+ "skydive", /* BE */
+ "slingshot", /* BF */
+ "slowdown", /* C0 */
+ "snapline", /* C1 */
+ "snapshot", /* C2 */
+ "snowcap", /* C3 */
+ "snowslide", /* C4 */
+ "solo", /* C5 */
+ "southward", /* C6 */
+ "soybean", /* C7 */
+ "spaniel", /* C8 */
+ "spearhead", /* C9 */
+ "spellbind", /* CA */
+ "spheroid", /* CB */
+ "spigot", /* CC */
+ "spindle", /* CD */
+ "spyglass", /* CE */
+ "stagehand", /* CF */
+ "stagnate", /* D0 */
+ "stairway", /* D1 */
+ "standard", /* D2 */
+ "stapler", /* D3 */
+ "steamship", /* D4 */
+ "sterling", /* D5 */
+ "stockman", /* D6 */
+ "stopwatch", /* D7 */
+ "stormy", /* D8 */
+ "sugar", /* D9 */
+ "surmount", /* DA */
+ "suspense", /* DB */
+ "sweatband", /* DC */
+ "swelter", /* DD */
+ "tactics", /* DE */
+ "talon", /* DF */
+ "tapeworm", /* E0 */
+ "tempest", /* E1 */
+ "tiger", /* E2 */
+ "tissue", /* E3 */
+ "tonic", /* E4 */
+ "topmost", /* E5 */
+ "tracker", /* E6 */
+ "transit", /* E7 */
+ "trauma", /* E8 */
+ "treadmill", /* E9 */
+ "Trojan", /* EA */
+ "trouble", /* EB */
+ "tumor", /* EC */
+ "tunnel", /* ED */
+ "tycoon", /* EE */
+ "uncut", /* EF */
+ "unearth", /* F0 */
+ "unwind", /* F1 */
+ "uproot", /* F2 */
+ "upset", /* F3 */
+ "upshot", /* F4 */
+ "vapor", /* F5 */
+ "village", /* F6 */
+ "virus", /* F7 */
+ "Vulcan", /* F8 */
+ "waffle", /* F9 */
+ "wallet", /* FA */
+ "watchword", /* FB */
+ "wayside", /* FC */
+ "willow", /* FD */
+ "woodlark", /* FE */
+ "Zulu", /* FF */
+};
+
diff --git a/fw/src/words.h b/fw/src/words.h
new file mode 100644
index 0000000..0bbd3c4
--- /dev/null
+++ b/fw/src/words.h
@@ -0,0 +1,7 @@
+#ifndef __ADJECTIVES_H__
+#define __ADJECTIVES_H__
+
+extern const char * const even[256];
+extern const char * const odd[256];
+
+#endif