From: Olivier Matz Date: Wed, 28 Aug 2019 21:51:31 +0000 (+0200) Subject: add embedded code X-Git-Url: http://git.droids-corp.org/?a=commitdiff_plain;h=3593abcb0112ca77965370b4ceca4c4e94703e98;p=red-alert.git add embedded code --- diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d37d4f5 --- /dev/null +++ b/Makefile @@ -0,0 +1,84 @@ +# +# SPDX-License-Identifier: BSD-3-Clause +# Copyright 2019, Olivier MATZ +# + +ROOTDIR := $(abspath .) +ARCH := avr +CROSS := avr- + +include $(ROOTDIR)/mk/pre.mk + +MCU = atmega328p +F_CPU = 8000000UL + +#AVRDUDE_OPTS = -P /dev/ttyUSB0 -c arduino -b 57600 +#AVRDUDE_OPTS = -P usb -c dragon_isp -B 100 -v -v -v -v -i 100 +#AVRDUDE_OPTS = -P usb -c avrispmkii -B 100 -v -v -v -v -i 100 +#AVRDUDE_OPTS = -P usb -c avrispmkii +AVRDUDE_OPTS = -P usb -c usbasp-clone -B 100 -i 100 + +O ?= $(CURDIR)/build +PROG = $(O)/red-alert + +CFLAGS := -g -Werror -Isrc +CFLAGS += -Wall -W +CFLAGS += -Os +CFLAGS += -ffunction-sections +CFLAGS += -DF_CPU=$(F_CPU) +CFLAGS += -mmcu=$(MCU) + +LDFLAGS := -Wl,--gc-sections +LDFLAGS += -mmcu=$(MCU) +LDFLAGS += -lm + +exe-y-$(PROG) += src/cirbuf.c +exe-y-$(PROG) += src/uart.c +exe-y-$(PROG) += src/callout.c +exe-y-$(PROG) += src/led.c +exe-y-$(PROG) += src/rtc.c +exe-y-$(PROG) += src/commands.c +exe-y-$(PROG) += src/buzzer.c +exe-y-$(PROG) += src/main.c + +ldflags-$(PROG) += -Wl,-Map=$(PROG).map,--cref + +objcopy-hex-y-$(PROG).hex := $(PROG) +objcopy-bin-y-$(PROG).bin := $(PROG) + +BOOTLOADER = $(O)/bootloader +exe-y-$(BOOTLOADER) += bootloader/bootloader.c + +# 0x7800 bytes == 0x3c00 words -> BOOTSZ1=0, BOOTSZ0=1 +ldflags-$(BOOTLOADER) += -Wl,--section-start=.text=7800 +ldflags-$(BOOTLOADER) += -Wl,-Map=$(BOOTLOADER).map,--cref + +objcopy-hex-y-$(BOOTLOADER).hex := $(BOOTLOADER) +objcopy-bin-y-$(BOOTLOADER).bin := $(BOOTLOADER) + + +.PHONY: all +all: + $(CROSS)size $(PROG) + $(CROSS)size $(BOOTLOADER) + +include $(ROOTDIR)/mk/post.mk + +all: $(all-targets) + +.PHONY: clean +clean: __clean + +.PHONY: program +program: all +# avrdude -e -p $(MCU) $(AVRDUDE_OPTS) -U flash:w:$(PROG):e + avrdude -e -p $(MCU) $(AVRDUDE_OPTS) -U flash:w:$(BOOTLOADER):e + +.PHONY: reset +reset: + avrdude -p $(MCU) $(AVRDUDE_OPTS) + +# defaults + CKDIV8=1, BOOTSZ0=1, BOOTRST=0 +.PHONY: fuse +fuse: + avrdude -p $(MCU) $(AVRDUDE_OPTS) -U lfuse:w:0xE2:m -U hfuse:w:0xDA:m -U efuse:w:0xFF:m diff --git a/mk/ar-rules.mk b/mk/ar-rules.mk new file mode 100644 index 0000000..4ad30cb --- /dev/null +++ b/mk/ar-rules.mk @@ -0,0 +1,55 @@ +# +# Copyright 2015, Olivier MATZ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the University of California, Berkeley nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +# dump some infos if debug is enabled +ifeq ($(D),1) +$(call disp_list,------ all-ar,$(all-ar)) +$(foreach ar,$(all-ar),\ + $(info,out-$(ar): $(out-$(ar))) \ + $(call disp_list,pre-$(ar),$(pre-$(ar))) \ +) +endif + +# include dependencies and commands files if they exist +$(foreach ar,$(all-ar),\ + $(eval -include $(call depfile,$(ar))) \ + $(eval -include $(call cmdfile,$(ar))) \ +) + +# remove duplicates +filtered-all-ar := $(sort $(all-ar)) + +# link several objects files into one shared object +$(filtered-all-ar): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE + @[ -d $(dir $@) ] || mkdir -p $(dir $@) + @$(call display_deps,$(pre-$(@)),$@,\ + $(call ar_cmd,$(pre-$(@)),$@),$?) + @$(if $(call check_deps,$@,$(call ar_cmd,$(pre-$(@)),$@),$?),\ + $(call ar_print_cmd,$(pre-$(@)),$@) && \ + $(call ar_cmd,$(pre-$(@)),$@) && \ + $(call save_cmd,$(call ar_cmd,$(pre-$(@)),$@),$@) && \ + $(call create_empty_depfile,$@)) diff --git a/mk/ar-vars.mk b/mk/ar-vars.mk new file mode 100644 index 0000000..7d2fab7 --- /dev/null +++ b/mk/ar-vars.mk @@ -0,0 +1,75 @@ +# +# Copyright 2015, Olivier MATZ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the University of California, Berkeley nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +# ar-y-$(ar) is provided by the user +# $(ar) is the path of the static library, and the variable contains +# the list of sources. Several ar-y-$(ar) can be present. + +# list all ar builds requested by user +all-ar := $(patsubst ar-y-%,%,$(filter ar-y-%,$(.VARIABLES))) + +# add them to the list of targets +all-targets += $(all-ar) + +# for each ar, create the following variables: +# out-$(ar) = output path of the arcutable +# pre-$(ar) = list of prerequisites for this arcutable +# Some source files need intermediate objects, we define these variables +# for them too, and add them in a list: $(all-iobj). +# Last, we add the generated files in $(all-clean-file). +$(foreach ar,$(all-ar),\ + $(eval out-$(ar) := $(dir $(ar))) \ + $(eval pre-$(ar) := ) \ + $(foreach src,$(ar-y-$(ar)), \ + $(if $(call is_cc_source,$(src)), \ + $(eval iobj := $(call src2iobj,$(src),$(out-$(ar)))) \ + $(eval pre-$(iobj) := $(src)) \ + $(eval all-iobj += $(iobj)) \ + $(eval all-clean-file += $(iobj)) \ + $(eval pre-$(ar) += $(iobj)) \ + , \ + $(if $(call is_obj_source,$(src)),\ + $(eval pre-$(ar) += $(src)) \ + , \ + $(error "unsupported source format: $(src)"))) \ + )\ + $(eval all-clean-file += $(ar)) \ +) + +# link several *.o files into a static libary +# $1: sources (*.o) +# $2: dst (xyz.a) +ar_cmd = ar crsD $(2) $(1) + +# print line used to ar object files +ifeq ($(V),1) +ar_print_cmd = echo $(call protect_quote,$(call ar_cmd,$1,$2)) +else +ar_print_cmd = echo " AR $(2)" +endif + +all-clean-file += $(all-ar) diff --git a/mk/arch-avr.mk b/mk/arch-avr.mk new file mode 100644 index 0000000..3c04e58 --- /dev/null +++ b/mk/arch-avr.mk @@ -0,0 +1,34 @@ +# +# Copyright 2015, Olivier MATZ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the University of California, Berkeley nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +ARCH_CFLAGS += -Wall -W +ARCH_CFLAGS += -Os +ARCH_CFLAGS += -ffunction-sections + +ARCH_LDFLAGS += -Wl,--gc-sections + +ARCH_CROSS := avr- diff --git a/mk/clean-rules.mk b/mk/clean-rules.mk new file mode 100644 index 0000000..5e6bd49 --- /dev/null +++ b/mk/clean-rules.mk @@ -0,0 +1,33 @@ +# +# Copyright 2015, Olivier MATZ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the University of California, Berkeley nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +.PHONY: __clean +__clean: $(all-clean-target) FORCE + @$(call clean_print_cmd,$(all-clean-file) $(call depfile,$(all-clean-file)) \ + $(call cmdfile,$(all-clean-file))) && \ + $(call clean_cmd,$(all-clean-file) $(call depfile,$(all-clean-file)) \ + $(call cmdfile,$(all-clean-file))) diff --git a/mk/clean-vars.mk b/mk/clean-vars.mk new file mode 100644 index 0000000..53f732e --- /dev/null +++ b/mk/clean-vars.mk @@ -0,0 +1,37 @@ +# +# Copyright 2015, Olivier MATZ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the University of California, Berkeley nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +# remove files +# $1: files +clean_cmd = rm -rf $(1) + +# print line used to clean files +ifeq ($(V),1) +clean_print_cmd = echo $(call protect_quote,$(call clean_cmd,$1)) +else +clean_print_cmd = echo " CLEAN $(CURDIR)" +endif diff --git a/mk/copy-rules.mk b/mk/copy-rules.mk new file mode 100644 index 0000000..6602a9e --- /dev/null +++ b/mk/copy-rules.mk @@ -0,0 +1,55 @@ +# +# Copyright 2015, Olivier MATZ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the University of California, Berkeley nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +# dump some infos if debug is enabled +ifeq ($(D),1) +$(call disp_list,------ all-copy,$(all-copy)) +$(foreach copy,$(all-copy),\ + $(info,out-$(copy): $(out-$(copy))) \ + $(call disp_list,pre-$(copy),$(pre-$(copy))) \ +) +endif + +# include dependencies and commands files if they exist +$(foreach copy,$(all-copy),\ + $(eval -include $(call depfile,$(copy))) \ + $(eval -include $(call cmdfile,$(copy))) \ +) + +# remove duplicates +filtered-all-copy := $(sort $(all-copy)) + +# convert format of executable +$(filtered-all-copy): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE + @[ -d $(dir $@) ] || mkdir -p $(dir $@) + @$(call display_deps,$(pre-$(@)),$@,\ + $(call copy_cmd,$(pre-$(@)),$@),$?) + @$(if $(call check_deps,$@,$(call copy_cmd,$(pre-$(@)),$@),$?),\ + $(call copy_print_cmd,$(pre-$(@)),$@) && \ + $(call copy_cmd,$(pre-$(@)),$@) && \ + $(call save_cmd,$(call copy_cmd,$(pre-$(@)),$@),$@) && \ + $(call create_empty_depfile,$@)) diff --git a/mk/copy-vars.mk b/mk/copy-vars.mk new file mode 100644 index 0000000..677274e --- /dev/null +++ b/mk/copy-vars.mk @@ -0,0 +1,74 @@ +# +# Copyright 2015, Olivier MATZ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the University of California, Berkeley nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +# copy a file +# copy-y-$(copy) is provided by the user +# $(copy) is the path of the directory containing the destination +# files, and the variable contains the path of the files to copy. Several +# copy-y-$(copy) can be present. + +# list all path requested by user +_all-copy := $(patsubst copy-y-%,%,$(filter copy-y-%,$(.VARIABLES))) +all-copy := + +# for each copy, create the following variables: +# out-$(copy) = output path of the executable +# pre-$(copy) = list of prerequisites for this executable +# We also add the files in $(all-copy). +$(foreach copy,$(_all-copy),\ + $(if $(notdir $(copy)), \ + $(if $(call compare,$(words $(copy-y-$(copy))),1), \ + $(error "only one source file is allowed in copy-y-$(copy)")) \ + $(eval dst := $(dir $(copy))$(notdir $(copy-y-$(copy)))) \ + $(eval out-$(copy) := $(dir $(copy))) \ + $(eval pre-$(copy) := $(copy-y-$(copy))) \ + $(eval all-copy += $(dst)) \ + , \ + $(foreach src,$(copy-y-$(copy)),\ + $(eval dst := $(copy)$(notdir $(src))) \ + $(eval out-$(copy) := $(copy)) \ + $(eval pre-$(dst) := $(src)) \ + $(eval all-copy += $(dst)) \ + ) \ + ) \ +) + +# add them to the list of targets and clean +all-targets += $(all-copy) +all-clean-file += $(all-copy) + +# convert format of executable from elf to ihex +# $1: source executable (elf) +# $2: destination file +copy_cmd = $(CP) $(1) $(2) + +# print line used to convert executable format +ifeq ($(V),1) +copy_print_cmd = echo $(call protect_quote,$(call copy_cmd,$1,$2)) +else +copy_print_cmd = echo " COPY $(2)" +endif diff --git a/mk/exe-rules.mk b/mk/exe-rules.mk new file mode 100644 index 0000000..a1a5c42 --- /dev/null +++ b/mk/exe-rules.mk @@ -0,0 +1,55 @@ +# +# Copyright 2015, Olivier MATZ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the University of California, Berkeley nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +# dump some infos if debug is enabled +ifeq ($(D),1) +$(call disp_list,------ all-exe,$(all-exe)) +$(foreach exe,$(all-exe),\ + $(info,out-$(exe): $(out-$(exe))) \ + $(call disp_list,pre-$(exe),$(pre-$(exe))) \ +) +endif + +# include dependencies and commands files if they exist +$(foreach exe,$(all-exe),\ + $(eval -include $(call depfile,$(exe))) \ + $(eval -include $(call cmdfile,$(exe))) \ +) + +# remove duplicates +filtered-all-exe := $(sort $(all-exe)) + +# link several objects files into one executable +$(filtered-all-exe): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE + @[ -d $(dir $@) ] || mkdir -p $(dir $@) + @$(call display_deps,$(pre-$(@)),$@,\ + $(call link_cmd,$(pre-$(@)),$@),$?) + @$(if $(call check_deps,$@,$(call link_cmd,$(pre-$(@)),$@),$?),\ + $(call link_print_cmd,$(pre-$(@)),$@) && \ + $(call link_cmd,$(pre-$(@)),$@) && \ + $(call save_cmd,$(call link_cmd,$(pre-$(@)),$@),$@) && \ + $(call create_empty_depfile,$@)) diff --git a/mk/exe-vars.mk b/mk/exe-vars.mk new file mode 100644 index 0000000..f980be4 --- /dev/null +++ b/mk/exe-vars.mk @@ -0,0 +1,79 @@ +# +# Copyright 2015, Olivier MATZ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the University of California, Berkeley nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +# exe-y-$(exe) is provided by the user +# $(exe) is the path of the binary, and the variable contains +# the list of sources. Several exe-y-$(exe) can be present. + +# list all exe builds requested by user +all-exe := $(patsubst exe-y-%,%,$(filter exe-y-%,$(.VARIABLES))) + +# add them to the list of targets +all-targets += $(all-exe) + +# for each exe, create the following variables: +# out-$(exe) = output path of the executable +# pre-$(exe) = list of prerequisites for this executable +# Some source files need intermediate objects, we define these variables +# for them too, and add them in a list: $(all-iobj). +# Last, we add the generated files in $(all-clean-file). +$(foreach exe,$(all-exe),\ + $(eval out-$(exe) := $(dir $(exe))) \ + $(eval pre-$(exe) := ) \ + $(foreach src,$(exe-y-$(exe)), \ + $(if $(call is_cc_source,$(src)), \ + $(eval iobj := $(call src2iobj,$(src),$(out-$(exe)))) \ + $(eval pre-$(iobj) := $(src)) \ + $(eval all-iobj += $(iobj)) \ + $(eval all-clean-file += $(iobj)) \ + $(eval pre-$(exe) += $(iobj)) \ + , \ + $(if $(call is_obj_source,$(src)),\ + $(eval pre-$(exe) += $(src)) \ + , \ + $(if $(call is_alib_source,$(src)),\ + $(eval pre-$(exe) += $(src)) \ + , \ + $(error "unsupported source format: $(src)")))) \ + )\ + $(eval all-clean-file += $(exe)) \ +) + +# link several *.o files into a exeary +# $1: sources (*.o) (*.a) +# $2: dst (xyz.o too) +link_cmd = $(CC) $(LDFLAGS) $(ldflags-$(2)) -o $(2) $(filter %.o,$(1)) \ + $(filter %.a,$(1)) $(LDLIBS) $(ldlibs-$(2)) + +# print line used to link object files +ifeq ($(V),1) +link_print_cmd = echo $(call protect_quote,$(call link_cmd,$1,$2)) +else +link_print_cmd = echo " EXE $(2)" +endif + +all-clean-file += $(all-exe) diff --git a/mk/obj-rules.mk b/mk/obj-rules.mk new file mode 100644 index 0000000..af208e4 --- /dev/null +++ b/mk/obj-rules.mk @@ -0,0 +1,83 @@ +# +# Copyright 2015, Olivier MATZ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the University of California, Berkeley nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +# dump some infos if debug is enabled +ifeq ($(D),1) +$(call disp_list,------ all-obj,$(all-obj)) +$(foreach obj,$(all-obj),\ + $(info,out-$(obj): $(out-$(obj))) \ + $(call disp_list,pre-$(obj),$(pre-$(obj))) \ +) +$(call disp_list,------ all-iobj,$(all-iobj)) +$(foreach iobj,$(all-iobj),\ + $(call disp_list,pre-$(iobj),$(pre-$(iobj))) \ +) +endif + +# if a generated file has the same name than a user target, +# generate an error +conflicts := $(filter $(all-iobj),$(all-targets)) +$(if $(conflicts), \ + $(error Intermediate file has the same names than user targets:\ + $(conflicts))) + +# include dependencies and commands files if they exist +$(foreach obj,$(all-obj),\ + $(eval -include $(call depfile,$(obj))) \ + $(eval -include $(call cmdfile,$(obj))) \ +) +$(foreach iobj,$(all-iobj),\ + $(eval -include $(call depfile,$(iobj))) \ + $(eval -include $(call cmdfile,$(iobj))) \ +) + +# remove duplicates +filtered-all-iobj := $(sort $(all-iobj)) + +# convert source files to intermediate object file +$(filtered-all-iobj): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE + @[ -d $(dir $@) ] || mkdir -p $(dir $@) + @$(call display_deps,$(pre-$(@)),$@,$(call compile_cmd,$(pre-$(@)),$@),$?) + @$(if $(call check_deps,$@,$(call compile_cmd,$(pre-$(@)),$@),$?),\ + $(call compile_print_cmd,$(pre-$(@)),$@) && \ + $(call compile_cmd,$(pre-$(@)),$@) && \ + $(call save_cmd,$(call compile_cmd,$(pre-$(@)),$@),$@) && \ + $(call obj-fixdep,$@)) + +# remove duplicates +filtered-all-obj := $(sort $(all-obj)) + +# combine several objects files to one +$(filtered-all-obj): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE + @[ -d $(dir $@) ] || mkdir -p $(dir $@) + @$(call display_deps,$(pre-$(@)),$@,\ + $(call combine_cmd,$(pre-$(@)),$@),$?) + @$(if $(call check_deps,$@,$(call combine_cmd,$(pre-$(@)),$@),$?),\ + $(call combine_print_cmd,$(pre-$(@)),$@) && \ + $(call combine_cmd,$(pre-$(@)),$@) && \ + $(call save_cmd,$(call combine_cmd,$(pre-$(@)),$@),$@) && \ + $(call create_empty_depfile,$@)) diff --git a/mk/obj-vars.mk b/mk/obj-vars.mk new file mode 100644 index 0000000..8bd9101 --- /dev/null +++ b/mk/obj-vars.mk @@ -0,0 +1,124 @@ +# +# Copyright 2015, Olivier MATZ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the University of California, Berkeley nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +# obj-y-$(obj) is provided by the user +# $(obj) is the path of the object, and the variable contains +# the list of sources. Several obj-y-$(obj) can be present. + +# list all object builds requested by user +all-obj := $(patsubst obj-y-%,%,$(filter obj-y-%,$(.VARIABLES))) + +# add them to the list of targets +all-targets += $(all-obj) + +# convert source path to intermediate object path, and filter +# objects from sources +# $1: list of source paths +# $2: output directory (including trailing slash) +# return: list of intermediate object paths +src2iobj = $(addprefix $(filter-out ./,$(2)),$(notdir $(strip \ + $(patsubst %.c,%.o,\ + $(patsubst %.s,%.o,\ + $(filter-out %.o,$(1))))))) + +# return the file if it matches a extension that is built with cc +# $1: source file +is_cc_source = $(filter %.c %.s %S,$(1)) + +# return the file if it's already an object file: in this case no +# intermediate object is needed +# $1: source file +is_obj_source = $(filter %.o,$(1)) + +# return the file if it's a static library +# $1: source file +is_alib_source = $(filter %.a,$(1)) + +# for each obj, create the following variables: +# out-$(obj) = output path of the object +# pre-$(obj) = list of prerequisites for this object +# Some source files need intermediate objects, we define these variables +# for them too, and add them in a list: $(all-iobj). +# Last, we add the generated files in $(all-clean-file). +$(foreach obj,$(all-obj),\ + $(eval out-$(obj) := $(dir $(obj))) \ + $(eval pre-$(obj) := ) \ + $(foreach src,$(obj-y-$(obj)), \ + $(if $(call is_cc_source,$(src)), \ + $(eval iobj := $(call src2iobj,$(src),$(out-$(obj)))) \ + $(eval pre-$(iobj) := $(src)) \ + $(eval all-iobj += $(iobj)) \ + $(eval all-clean-file += $(iobj)) \ + $(eval pre-$(obj) += $(iobj)) \ + , \ + $(if $(call is_obj_source,$(src)),\ + $(eval pre-$(obj) += $(src)) \ + , \ + $(error "unsupported source format: $(src)"))) \ + )\ + $(eval all-clean-file += $(obj)) \ +) + +# fix the format of .o.d.tmp (generated by gcc) to a .o.d that defines +# dependencies as makefile variables +# $1: object file (.o) +obj-fixdep = if [ -f $(call file2tmpdep,$(1)) ]; then\ + echo -n "dep-$(1) = " > $(call depfile,$(1)) && \ + sed 's,^[^ ][^:]*: ,,' $(call file2tmpdep,$(1)) >> $(call depfile,$(1)) && \ + rm -f $(call file2tmpdep,$(1)); \ + else \ + $(call create_empty_depfile,$(1)); \ + fi + +# compile a file +# $1: sources +# $2: dst +compile_cmd = $(CC) -Wp,-MD,$(call file2tmpdep,$(2)) \ + $(CPPFLAGS) $(cppflags-$(2)) \ + $(CFLAGS) $(cflags-$(2)) \ + -c -o $2 $1 + +# print line used to compile a file +ifeq ($(V),1) +compile_print_cmd = echo $(call protect_quote,$(call compile_cmd,$1,$2)) +else +compile_print_cmd = echo " CC $(2)" +endif + +# combine several *.o files into one +# $1: sources (*.o) +# $2: dst (xyz.o too) +combine_cmd = $(LD) -r $(1) -o $(2) + +# print line used to combine object files +ifeq ($(V),1) +combine_print_cmd = echo $(call protect_quote,$(call combine_cmd,$1,$2)) +else +combine_print_cmd = echo " LD $(2)" +endif + +all-clean-file += $(all-obj) diff --git a/mk/objcopy-rules.mk b/mk/objcopy-rules.mk new file mode 100644 index 0000000..86a7cb6 --- /dev/null +++ b/mk/objcopy-rules.mk @@ -0,0 +1,74 @@ +# +# Copyright 2015, Olivier MATZ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the University of California, Berkeley nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +# dump some infos if debug is enabled +ifeq ($(D),1) +$(call disp_list,------ all-objcopy-hex,$(all-objcopy-hex)) +$(foreach objcopy,$(all-objcopy-hex),\ + $(info,out-$(objcopy): $(out-$(objcopy))) \ + $(call disp_list,pre-$(objcopy),$(pre-$(objcopy))) \ +) +$(call disp_list,------ all-objcopy-bin,$(all-objcopy-bin)) +$(foreach objcopy,$(all-objcopy-bin),\ + $(info,out-$(objcopy): $(out-$(objcopy))) \ + $(call disp_list,pre-$(objcopy),$(pre-$(objcopy))) \ +) +endif + +# include dependencies and commands files if they exist +$(foreach objcopy,$(all-objcopy-hex) $(all-objcopy-bin),\ + $(eval -include $(call depfile,$(objcopy))) \ + $(eval -include $(call cmdfile,$(objcopy))) \ +) + +# remove duplicates +filtered-all-objcopy-hex := $(sort $(all-objcopy-hex)) + +# convert format of executable +$(filtered-all-objcopy-hex): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE + @[ -d $(dir $@) ] || mkdir -p $(dir $@) + @$(call display_deps,$(pre-$(@)),$@,\ + $(call objcopy_hex_cmd,$(pre-$(@)),$@),$?) + @$(if $(call check_deps,$@,$(call objcopy_hex_cmd,$(pre-$(@)),$@),$?),\ + $(call objcopy_print_cmd,$(pre-$(@)),$@) && \ + $(call objcopy_hex_cmd,$(pre-$(@)),$@) && \ + $(call save_cmd,$(call objcopy_hex_cmd,$(pre-$(@)),$@),$@) && \ + $(call create_empty_depfile,$@)) + +# remove duplicates +filtered-all-objcopy-bin := $(sort $(all-objcopy-bin)) + +# convert format of executable +$(filtered-all-objcopy-bin): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE + @[ -d $(dir $@) ] || mkdir -p $(dir $@) + @$(call display_deps,$(pre-$(@)),$@,\ + $(call objcopy_bin_cmd,$(pre-$(@)),$@),$?) + @$(if $(call check_deps,$@,$(call objcopy_bin_cmd,$(pre-$(@)),$@),$?),\ + $(call objcopy_print_cmd,$(pre-$(@)),$@) && \ + $(call objcopy_bin_cmd,$(pre-$(@)),$@) && \ + $(call save_cmd,$(call objcopy_bin_cmd,$(pre-$(@)),$@),$@) && \ + $(call create_empty_depfile,$@)) diff --git a/mk/objcopy-vars.mk b/mk/objcopy-vars.mk new file mode 100644 index 0000000..d515d02 --- /dev/null +++ b/mk/objcopy-vars.mk @@ -0,0 +1,89 @@ +# +# Copyright 2015, Olivier MATZ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the University of California, Berkeley nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +# objcopy changes the format of a binary +# objcopy-hex-y-$(objcopy), objcopy-bin-y-$(objcopy) are provided by the user +# $(objcopy) is the path of the binary, and the variable contains +# the path to the elf. Several objcopy-y-$(objcopy) can be present. + +# list all executable builds requested by user +all-objcopy-hex := $(patsubst objcopy-hex-y-%,%,$(filter objcopy-hex-y-%,$(.VARIABLES))) +all-objcopy-bin := $(patsubst objcopy-bin-y-%,%,$(filter objcopy-bin-y-%,$(.VARIABLES))) + +# add them to the list of targets +all-targets += $(all-objcopy-hex) $(all-objcopy-bin) + +# for each objcopy, create the following variables: +# out-$(objcopy) = output path of the executable +# pre-$(objcopy) = list of prerequisites for this executable +# We also add the generated files in $(all-clean-file). +$(foreach objcopy,$(all-objcopy-hex),\ + $(if $(call compare,$(words $(objcopy-hex-y-$(objcopy))),1),\ + $(error "only one source file is allowed in objcopy-hex-y-$(objcopy)")) \ + $(eval out-$(objcopy) := $(dir $(objcopy))) \ + $(eval pre-$(objcopy) := $(objcopy-hex-y-$(objcopy))) \ + $(eval all-clean-file += $(objcopy)) \ +) + +# for each objcopy, create the following variables: +# out-$(objcopy) = output path of the executable +# pre-$(objcopy) = list of prerequisites for this executable +# We also add the generated files in $(all-clean-file). +$(foreach objcopy,$(all-objcopy-bin),\ + $(if $(call compare,$(words $(objcopy-bin-y-$(objcopy))),1),\ + $(error "only one source file is allowed in objcopy-bin-y-$(objcopy)")) \ + $(eval out-$(objcopy) := $(dir $(objcopy))) \ + $(eval pre-$(objcopy) := $(objcopy-bin-y-$(objcopy))) \ + $(eval all-clean-file += $(objcopy)) \ +) + +# convert format of executable from elf to ihex +# $1: source executable (elf) +# $2: destination file +objcopy_hex_cmd = $(OBJCOPY) -O ihex $(1) $(2) + +# print line used to convert executable format +ifeq ($(V),1) +objcopy_print_cmd = echo $(call protect_quote,$(call objcopy_hex_cmd,$1,$2)) +else +objcopy_print_cmd = echo " OBJCOPY $(2)" +endif + +# convert format of executable from elf to binary +# $1: source executable (elf) +# $2: destination file +objcopy_bin_cmd = $(OBJCOPY) -O binary $(1) $(2) + +# print line used to convert executable format +ifeq ($(V),1) +objcopy_print_cmd = echo $(call protect_quote,$(call objcopy_bin_cmd,$1,$2)) +else +objcopy_print_cmd = echo " OBJCOPY $(2)" +endif + +# XXX dup ? +all-clean-file += $(all-objcopy-hex) $(all-objcopy-bin) diff --git a/mk/post.mk b/mk/post.mk new file mode 100644 index 0000000..2707162 --- /dev/null +++ b/mk/post.mk @@ -0,0 +1,108 @@ +# +# Copyright 2015, Olivier MATZ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the University of California, Berkeley nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +# ---- variables that must be defined: +# +# ROOTDIR: path to project root directory +# +# ---- variable that can be defined anywhere +# +# CROSS: prefix of the toolchain +# CP, LN, GAWK, GREP: coreutils tools +# CC, CPP, AR, LD, OBJCOPY, OBJDUMP, STRIP: compilers/binutils +# +# ---- variable that can be defined by Makefile: +# +# obj-y-$(path) +# exe-y-$(path) +# ar-y-$(path) +# shlib-y-$(path) +# copy-y-$(path) +# slink-y-$(path) +# objcopy-y-$(path) +# subdir-y +# +# CPPFLAGS, CFLAGS, LDFLAGS, LDLIBS: global flags +# cflags-$(path), cppflags-$(path), ldflags-$(path), ldlibs-$(path): per +# file flags +# mkflags-$(path): flags for subdirectories +# +# ---- variables that can be defined on the command line: +# +# EXTRA_CPPFLAGS, EXTRA_CFLAGS, EXTRA_LDFLAGS, EXTRA_LDLIBS: global +# extra flags +# extra-cflags-$(path), extra-cppflags-$(path): per object extra flags + +ifeq ($(ROOTDIR),) +$(error ROOTDIR environment variable is not defined) +endif + +# list of targets asked by user +all-targets := +# list of files generated +all-clean-file := + +# usual internal variables: +# out-$(file) = output path of a generated file +# pre-$(file) = list of files needed to generate $(file) +# all-type = list of targets for this type + +include $(ROOTDIR)/mk/obj-vars.mk +include $(ROOTDIR)/mk/exe-vars.mk +include $(ROOTDIR)/mk/ar-vars.mk +include $(ROOTDIR)/mk/shlib-vars.mk +include $(ROOTDIR)/mk/copy-vars.mk +include $(ROOTDIR)/mk/slink-vars.mk +include $(ROOTDIR)/mk/objcopy-vars.mk +include $(ROOTDIR)/mk/subdir-vars.mk +# must stay at the end +include $(ROOTDIR)/mk/clean-vars.mk + +# dump the list of targets +ifeq ($(D),1) +$(call disp_list,------ all-targets,$(all-targets)) +endif + +# first rule (default) +.PHONY: __all +__all: $(all-targets) + +# the includes below require second expansion +.SECONDEXPANSION: + +include $(ROOTDIR)/mk/obj-rules.mk +include $(ROOTDIR)/mk/exe-rules.mk +include $(ROOTDIR)/mk/ar-rules.mk +include $(ROOTDIR)/mk/shlib-rules.mk +include $(ROOTDIR)/mk/copy-rules.mk +include $(ROOTDIR)/mk/slink-rules.mk +include $(ROOTDIR)/mk/objcopy-rules.mk +include $(ROOTDIR)/mk/subdir-rules.mk +include $(ROOTDIR)/mk/clean-rules.mk + +.PHONY: FORCE +FORCE: diff --git a/mk/pre.mk b/mk/pre.mk new file mode 100644 index 0000000..8280cc8 --- /dev/null +++ b/mk/pre.mk @@ -0,0 +1,48 @@ +# +# Copyright 2015, Olivier MATZ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the University of California, Berkeley nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +# ---- variables that must be defined: +# +# ROOTDIR: path to project root dir +# ARCH: architecture (ex: avr, stm32, ...) +# + +ifeq ($(ROOTDIR),) +$(error ROOTDIR environment variable is not defined) +endif + +ifeq ($(ARCH),) +$(error ARCH environment variable is not defined) +endif + +MAKEFLAGS += --no-print-directory + +include $(ROOTDIR)/mk/tools.mk + +include $(ROOTDIR)/mk/arch-$(ARCH).mk + +include $(ROOTDIR)/mk/vars.mk diff --git a/mk/shlib-rules.mk b/mk/shlib-rules.mk new file mode 100644 index 0000000..1a131cc --- /dev/null +++ b/mk/shlib-rules.mk @@ -0,0 +1,55 @@ +# +# Copyright 2015, Olivier MATZ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the University of California, Berkeley nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +# dump some infos if debug is enabled +ifeq ($(D),1) +$(call disp_list,------ all-shlib,$(all-shlib)) +$(foreach shlib,$(all-shlib),\ + $(info,out-$(shlib): $(out-$(shlib))) \ + $(call disp_list,pre-$(shlib),$(pre-$(shlib))) \ +) +endif + +# include dependencies and commands files if they exist +$(foreach shlib,$(all-shlib),\ + $(eval -include $(call depfile,$(shlib))) \ + $(eval -include $(call cmdfile,$(shlib))) \ +) + +# remove duplicates +filtered-all-shlib := $(sort $(all-shlib)) + +# link several objects files into one shared object +$(filtered-all-shlib): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE + @[ -d $(dir $@) ] || mkdir -p $(dir $@) + @$(call display_deps,$(pre-$(@)),$@,\ + $(call shlib_cmd,$(pre-$(@)),$@),$?) + @$(if $(call check_deps,$@,$(call shlib_cmd,$(pre-$(@)),$@),$?),\ + $(call shlib_print_cmd,$(pre-$(@)),$@) && \ + $(call shlib_cmd,$(pre-$(@)),$@) && \ + $(call save_cmd,$(call shlib_cmd,$(pre-$(@)),$@),$@) && \ + $(call create_empty_depfile,$@)) diff --git a/mk/shlib-vars.mk b/mk/shlib-vars.mk new file mode 100644 index 0000000..7906510 --- /dev/null +++ b/mk/shlib-vars.mk @@ -0,0 +1,76 @@ +# +# Copyright 2015, Olivier MATZ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the University of California, Berkeley nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +# shlib-y-$(shlib) is provided by the user +# $(shlib) is the path of the shared library, and the variable +# contains the list of sources. Several shlib-y-$(shlib) can be +# present. + +# list all shlib builds requested by user +all-shlib := $(patsubst shlib-y-%,%,$(filter shlib-y-%,$(.VARIABLES))) + +# add them to the list of targets +all-targets += $(all-shlib) + +# for each shlib, create the following variables: +# out-$(shlib) = output path of the shlibcutable +# pre-$(shlib) = list of prerequisites for this shlibcutable +# Some source files need intermediate objects, we define these variables +# for them too, and add them in a list: $(all-iobj). +# Last, we add the generated files in $(all-clean-file). +$(foreach shlib,$(all-shlib),\ + $(eval out-$(shlib) := $(dir $(shlib))) \ + $(eval pre-$(shlib) := ) \ + $(foreach src,$(shlib-y-$(shlib)), \ + $(if $(call is_cc_source,$(src)), \ + $(eval iobj := $(call src2iobj,$(src),$(out-$(shlib)))) \ + $(eval pre-$(iobj) := $(src)) \ + $(eval all-iobj += $(iobj)) \ + $(eval all-clean-file += $(iobj)) \ + $(eval pre-$(shlib) += $(iobj)) \ + , \ + $(if $(call is_obj_source,$(src)),\ + $(eval pre-$(shlib) += $(src)) \ + , \ + $(error "unsupported source format: $(src)"))) \ + )\ + $(eval all-clean-file += $(shlib)) \ +) + +# link several *.o files into a shared libary +# $1: sources (*.o) +# $2: dst (xyz.so) +shlib_cmd = $(CC) $(LDFLAGS) $(ldflags-$(2)) -shared -o $(2) $(1) + +# print line used to shlib object files +ifeq ($(V),1) +shlib_print_cmd = echo $(call protect_quote,$(call shlib_cmd,$1,$2)) +else +shlib_print_cmd = echo " SHLIB $(2)" +endif + +all-clean-file += $(all-shlib) diff --git a/mk/slink-rules.mk b/mk/slink-rules.mk new file mode 100644 index 0000000..bea3aad --- /dev/null +++ b/mk/slink-rules.mk @@ -0,0 +1,55 @@ +# +# Copyright 2015, Olivier MATZ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the University of California, Berkeley nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +# dump some infos if debug is enabled +ifeq ($(D),1) +$(call disp_list,------ all-slink,$(all-slink)) +$(foreach slink,$(all-slink),\ + $(info,out-$(slink): $(out-$(slink))) \ + $(call disp_list,pre-$(slink),$(pre-$(slink))) \ +) +endif + +# include dependencies and commands files if they exist +$(foreach slink,$(all-slink),\ + $(eval -include $(call depfile,$(slink))) \ + $(eval -include $(call cmdfile,$(slink))) \ +) + +# remove duplicates +filtered-all-slink := $(sort $(all-slink)) + +# convert format of executable +$(filtered-all-slink): $$(pre-$$@) $$(wildcard $$(dep-$$@)) FORCE + @[ -d $(dir $@) ] || mkdir -p $(dir $@) + @$(call display_deps,$(pre-$(@)),$@,\ + $(call slink_cmd,$(pre-$(@)),$@),$?) + @$(if $(call check_deps,$@,$(call slink_cmd,$(pre-$(@)),$@),$?),\ + $(call slink_print_cmd,$(pre-$(@)),$@) && \ + $(call slink_cmd,$(pre-$(@)),$@) && \ + $(call save_cmd,$(call slink_cmd,$(pre-$(@)),$@),$@) && \ + $(call create_empty_depfile,$@)) diff --git a/mk/slink-vars.mk b/mk/slink-vars.mk new file mode 100644 index 0000000..428cb25 --- /dev/null +++ b/mk/slink-vars.mk @@ -0,0 +1,74 @@ +# +# Copyright 2015, Olivier MATZ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the University of California, Berkeley nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +# create a symbolic link of a file +# slink-y-$(slink) is provided by the user +# $(slink) is the path of the directory containing the destination +# files, and the variable contains the path of the files to linked. Several +# slink-y-$(slink) can be present. + +# list all path requested by user +_all-slink := $(patsubst slink-y-%,%,$(filter slink-y-%,$(.VARIABLES))) +all-slink := + +# for each slink, create the following variables: +# out-$(slink) = output path of the executable +# pre-$(slink) = list of prerequisites for this executable +# We also add the files in $(all-slink). +$(foreach slink,$(_all-slink),\ + $(if $(notdir $(slink)), \ + $(if $(call compare,$(words $(slink-y-$(slink))),1), \ + $(error "only one source file is allowed in slink-y-$(slink)")) \ + $(eval dst := $(dir $(slink))$(notdir $(slink-y-$(slink)))) \ + $(eval out-$(slink) := $(dir $(slink))) \ + $(eval pre-$(slink) := $(slink-y-$(slink))) \ + $(eval all-slink += $(dst)) \ + , \ + $(foreach src,$(slink-y-$(slink)),\ + $(eval dst := $(slink)$(notdir $(src))) \ + $(eval out-$(slink) := $(slink)) \ + $(eval pre-$(dst) := $(src)) \ + $(eval all-slink += $(dst)) \ + ) \ + ) \ +) + +# add them to the list of targets and clean +all-targets += $(all-slink) +all-clean-file += $(all-slink) + +# convert format of executable from elf to ihex +# $1: source executable (elf) +# $2: destination file +slink_cmd = $(LN) -nsf $(abspath $(1)) $(2) + +# print line used to convert executable format +ifeq ($(V),1) +slink_print_cmd = echo $(call protect_quote,$(call slink_cmd,$1,$2)) +else +slink_print_cmd = echo " SLINK $(2)" +endif diff --git a/mk/subdir-rules.mk b/mk/subdir-rules.mk new file mode 100644 index 0000000..aae82db --- /dev/null +++ b/mk/subdir-rules.mk @@ -0,0 +1,30 @@ +# +# Copyright 2015, Olivier MATZ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the University of California, Berkeley nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +.PHONY: $(subdir-y) +$(subdir-y): FORCE + $(Q)$(MAKE) -C $(@) $(mkflags-$(@)) $(MAKECMDGOALS) diff --git a/mk/subdir-vars.mk b/mk/subdir-vars.mk new file mode 100644 index 0000000..14e4ee1 --- /dev/null +++ b/mk/subdir-vars.mk @@ -0,0 +1,33 @@ +# +# Copyright 2015, Olivier MATZ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the University of California, Berkeley nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +# subdir-y is provided by the user +# it contains the list of directory to build + +# add them to the list of targets +all-targets += $(subdir-y) +all-clean-target += $(subdir-y) diff --git a/mk/tools.mk b/mk/tools.mk new file mode 100644 index 0000000..484262b --- /dev/null +++ b/mk/tools.mk @@ -0,0 +1,159 @@ +# +# Copyright 2015, Olivier MATZ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the University of California, Berkeley nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +empty:= +space:= $(empty) $(empty) +indent:= $(space)$(space) + +# define a newline char, useful for debugging with $(info) +define newline + + +endef + +# $(prefix shell commands with $(Q) to silent them, except if V=1 +Q=@ +ifeq ("$(V)-$(origin V)", "1-command line") +Q= +endif + +# set variable $1 to $2 if the variable has an implicit value or +# is not defined +# $1 variable name +# $2 new variable content +set_default = $(if \ + $(call not,$(or \ + $(compare $(origin $(1)),default), \ + $(compare $(origin $(1)),undefined) \ + )),\ + $(eval $(1) = $(2)) \ +) + +# display a list +# $1 title +# $2 list +disp_list = $(info $(1)$(newline)\ + $(addsuffix $(newline),$(addprefix $(space),$(2)))) + +# add a dot in front of the file name +# $1 list of paths +# return: full paths with files prefixed by a dot +dotfile = $(strip $(foreach f,$(1),\ + $(join $(dir $f),.$(notdir $f)))) + +# convert source/obj files into dot-dep filename +# $1 list of paths +# return: full paths with files prefixed by a dot and suffixed with .d +depfile = $(strip $(call dotfile,$(addsuffix .d,$(1)))) + +# convert source/obj files into dot-dep filename +# $1 list of paths +# return: full paths with files prefixed by a dot and suffixed with .d.tmp +file2tmpdep = $(strip $(call dotfile,$(addsuffix .d.tmp,$(1)))) + +# convert source/obj files into dot-cmd filename +# $1 list of paths +# return: full paths with files prefixed by a dot and suffixed with .cmd +cmdfile = $(strip $(call dotfile,$(addsuffix .cmd,$(1)))) + +# add a \ before each quote +protect_quote = $(subst ','\'',$(1)) +#'# editor syntax highlight fix + +# return an non-empty string if $1 is empty, and vice versa +# $1 a string +not = $(if $1,,true) + +# return 1 if parameter is a non-empty string, else 0 +boolean = $(if $1,1,0) + +# return an empty string if string are equal +compare = $(strip $(subst $(1),,$(2)) $(subst $(2),,$(1))) + +# return a non-empty string if a file does not exist +# $1: file +file_missing = $(call compare,$(wildcard $1),$1) + +# return a non-empty string if cmdline changed +# $1: file to be built +# $2: the command to build it +cmdline_changed = $(call compare,$(strip $(cmd-$(1))),$(strip $(2))) + +# return an non-empty string if the .d file does not exist +# $1: the dep file (.d) +depfile_missing = $(call compare,$(wildcard $(1)),$(1)) + +# return a non-empty string if, according to dep-xyz variable, a file +# needed to build $1 does not exist. In this case we need to rebuild +# the file and the .d file. +# $1: file to be built +dep-missing = $(call compare,$(wildcard $(dep-$(1))),$(dep-$(1))) + +# return an empty string if no prereq is newer than target +# $1: list of prerequisites newer than target ($?) +dep-newer = $(strip $(filter-out FORCE,$(1))) + +# display why a file should be re-built +# $1: source files +# $2: dst file +# $3: build command +# $4: all prerequisites newer than target ($?) +ifeq ($(D),1) +display_deps = \ + echo -n "$1 -> $2 " ; \ + echo -n "file_missing=$(call boolean,$(call file_missing,$(2))) " ; \ + echo -n "cmdline_changed=$(call boolean,$(call cmdline_changed,$(2),$(3))) " ; \ + echo -n "depfile_missing=$(call boolean,$(call depfile_missing,$(call depfile,$(2)))) " ; \ + echo -n "dep-missing=$(call boolean,$(call dep-missing,$(2))) " ; \ + echo "dep-newer=$(call boolean,$(call dep-newer,$(4)))" +else +display_deps= +endif + +# return an empty string if a file should be rebuilt +# $1: dst file +# $2: build command +# $3: all prerequisites newer than target ($?) +check_deps = \ + $(or $(call file_missing,$(1)),\ + $(call cmdline_changed,$(1),$(2)),\ + $(call depfile_missing,$(call depfile,$(1))),\ + $(call dep-missing,$(1)),\ + $(call dep-newer,$(3))) + +# create a depfile (.d) with no additional deps +# $1: object file (.o) +create_empty_depfile = echo "dep-$(1) =" > $(call depfile,$(1)) + +# save a command in a file +# $1: command to build the file +# $2: name of the file +save_cmd = echo "cmd-$(2) = $(call protect_quote,$(1))" > $(call cmdfile,$(2)) + +# remove the FORCE target from the list of all prerequisites $+ +# no arguments, use $+ +prereq = $(filter-out FORCE,$(+)) diff --git a/mk/vars.mk b/mk/vars.mk new file mode 100644 index 0000000..c07764e --- /dev/null +++ b/mk/vars.mk @@ -0,0 +1,49 @@ +# +# Copyright 2015, Olivier MATZ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the University of California, Berkeley nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +CROSS ?= $(ARCH_CROSS) + +# core tools +CP ?= cp +LN ?= ln +GAWK ?= gawk +GREP ?= grep +# compiler and binutils, set_default overrides mk implicit value +# but not command line or standard variables +$(call set_default,CC,$(CROSS)gcc) +$(call set_default,CPP,$(CROSS)cpp) +$(call set_default,AR,$(CROSS)ar) +$(call set_default,LD,$(CROSS)ld) +$(call set_default,OBJCOPY,$(CROSS)objcopy) +$(call set_default,OBJDUMP,$(CROSS)objdump) +$(call set_default,STRIP,$(CROSS)strip) +HOSTCC ?= cc + +CFLAGS += $(EXTRA_CFLAGS) +CPPFLAGS += $(EXTRA_CPPFLAGS) +LDFLAGS += $(EXTRA_LDFLAGS) +LDLIBS += $(EXTRA_LDLIBS) diff --git a/src/buzzer.c b/src/buzzer.c new file mode 100644 index 0000000..1844e85 --- /dev/null +++ b/src/buzzer.c @@ -0,0 +1,158 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2019, Olivier MATZ + */ + +#include + +#include +#include +#include +#include + +#include "callout.h" +#include "main.h" +#include "buzzer.h" + +#define BUZZER_RESOLUTION_MS 8 + +struct buzzer_state { + uint16_t ms; /* current time in ms */ + uint16_t duration_ms; + + uint16_t vibrato_ms; /* time in ms, modulo vibrato period */ + uint16_t vibrato_period_ms; + uint8_t type; /* saw, sin, cos, ... */ + + float p1; /* start tone period */ + float k; /* coef to scale to end tone period */ +}; + +static struct buzzer_state buzzer_state; +static struct callout buzzer_timer; + +/* reminder: period_us is approximately between 100 and 10000. + * period_us -> freq + * 100 us -> 10000Hz + * 1000 us -> 1000Hz + * 10000 us -> 100Hz + */ +static void buzzer_set(uint16_t period_us) +{ + BUILD_BUG_ON(F_CPU != 8000000UL); + TCCR1B |= (1 << CS10); /* count at F_CPU */ + + /* duty cycle is 50%, but we could control it in the future */ + OCR1B = period_us * 2; + OCR1A = period_us * 4; +} + +void buzzer_stop(void) +{ + /* just stop timer that controls the PWM */ + TCCR1B &= ~(1 << CS10); +} + +static void buzzer_cb(struct callout_mgr *cm, + struct callout *tim, void *arg) +{ + struct buzzer_state *st = &buzzer_state; + float t, x, period_us; + + (void)arg; + + st->ms += BUZZER_RESOLUTION_MS; + if (st->duration_ms != 0 && st->ms >= st->duration_ms) { + buzzer_stop(); + return; + } + + if (st->type == VIBRATO_TYPE_NONE) + goto reschedule; + + st->vibrato_ms += BUZZER_RESOLUTION_MS; + if (st->vibrato_ms >= st->vibrato_period_ms) + st->vibrato_ms -= st->vibrato_period_ms; + + /* t is the time, normalized to be between 0 and 1 */ + t = (float)st->vibrato_ms / (float)st->vibrato_period_ms; + + /* x determines the normalized vibrato level, between 0 and 1 */ + switch (st->type) { + case VIBRATO_TYPE_SIN: + x = (sin(2 * M_PI * t) + 1) / 2.; + break; + case VIBRATO_TYPE_COS: + x = (-cos(2 * M_PI * t) + 1) / 2.; + break; + case VIBRATO_TYPE_TRIANGLE: + if (t < 0.5) + x = t * 2; + else + x = 2 - (t * 2); + break; + case VIBRATO_TYPE_SQUARE: + if (t < 0.5) + x = 0; + else + x = 1; + break; + case VIBRATO_TYPE_SAW: + default: + x = t; + break; + } + + /* scale x between period1 and period2 */ + period_us = st->p1 * exp(st->k * x); + buzzer_set(period_us); + +reschedule: + callout_reschedule(cm, tim, BUZZER_RESOLUTION_MS); +} + +void buzzer_init(void) +{ + DDRB |= (1 << 2); /* buzzer pin as output */ + + /* Fast PWM mode: OCR1A for TOP, OCR1B for half period */ + TCCR1A = (1 << WGM11) | (1 << WGM10) | (1 << COM1B1); + TCCR1B = (1 << WGM13) | (1 << WGM12); + + callout_init(&buzzer_timer, buzzer_cb, NULL, 128); +} + +void buzzer(uint16_t period_us, uint16_t duration_ms) +{ + callout_stop(&callout_mgr, &buzzer_timer); + buzzer_stop(); + + if (period_us == 0) + return; + + memset(&buzzer_state, 0, sizeof(buzzer_state)); + buzzer_state.type = VIBRATO_TYPE_NONE; + buzzer_state.duration_ms = duration_ms; + buzzer_set(period_us); + + callout_schedule(&callout_mgr, &buzzer_timer, 0); +} + +void buzzer_vibrato(uint16_t period1_us, uint16_t period2_us, + uint16_t duration_ms, uint16_t vibrato_period_ms, + uint8_t vibrato_type) +{ + callout_stop(&callout_mgr, &buzzer_timer); + buzzer_stop(); + + if (vibrato_period_ms == 0 || period1_us == 0 || period2_us == 0) + return; + + memset(&buzzer_state, 0, sizeof(buzzer_state)); + buzzer_state.duration_ms = duration_ms; + buzzer_state.vibrato_period_ms = vibrato_period_ms; + buzzer_state.type = vibrato_type; + buzzer_state.p1 = (float)period1_us; + buzzer_state.k = log((float)period2_us / (float)period1_us); + + callout_schedule(&callout_mgr, &buzzer_timer, 0); +} diff --git a/src/buzzer.h b/src/buzzer.h new file mode 100644 index 0000000..cf1b52a --- /dev/null +++ b/src/buzzer.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2019, Olivier MATZ + */ + +#ifndef BUZZER_H_ +#define BUZZER_H_ + +/* initialize timer1 for buzzer */ +void buzzer_init(void); + +/* stop buzzer */ +void buzzer_stop(void); + +/* buzz with specified period during specified time + * period_us is between 100 and 10000, duration_ms max is 32767 */ +void buzzer(uint16_t period_us, uint16_t duration_ms); + +#define VIBRATO_TYPE_NONE 0 +#define VIBRATO_TYPE_SAW 1 +#define VIBRATO_TYPE_TRIANGLE 2 +#define VIBRATO_TYPE_SIN 3 +#define VIBRATO_TYPE_COS 4 +#define VIBRATO_TYPE_SQUARE 5 + +/* buzz with a vibrato effect */ +void buzzer_vibrato(uint16_t period1_us, uint16_t period2_us, + uint16_t duration_ms, uint16_t vibrato_period_ms, + uint8_t vibrato_type); + +#endif diff --git a/src/callout.c b/src/callout.c new file mode 100644 index 0000000..9f56fbe --- /dev/null +++ b/src/callout.c @@ -0,0 +1,359 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2014-2019, Olivier MATZ + * Copyright 2010 Intel Corporation + * (Inspired by Intel DPDK rte_timer library) + */ + +#include + +#include +#include +#include +#include +#include + +#include +#include + +/* allow to browse a list while modifying the current element */ +#define _LIST_FOREACH_SAFE(cur, next, head, field) \ + for ((cur) = LIST_FIRST((head)), \ + (next) = ((cur) ? LIST_NEXT((cur), field) : NULL); \ + (cur); \ + (cur) = (next), \ + (next) = ((cur) ? LIST_NEXT((cur), field) : NULL)) + +#ifdef CALLOUT_STATS +/* called with irq locked */ +#define CALLOUT_STAT_ADD(cm, field, x) do { \ + cm->stats.field += x; \ + } while(0) +#else +#define CALLOUT_STAT_ADD(cm, field, x) do { } while(0) +#endif + +#ifdef CALLOUT_DEBUG +#define callout_dprintf(fmt, ...) \ + printf_P(PSTR("%s(): " fmt), __FUNCTION__, __VA_ARGS__) +#else +#define callout_dprintf(...) do { } while (0) +#endif + +/* Initialize a callout manager */ +void +callout_mgr_init(struct callout_mgr *cm, + callout_get_time_t *get_time) +{ + memset(cm, 0, sizeof(*cm)); + cm->get_time = get_time; + LIST_INIT(&cm->sched_list); +} + +/* Initialize the timer handle tim for use */ +void +callout_init(struct callout *tim, callout_cb_t f, void *arg, + uint8_t priority) +{ + memset(tim, 0, sizeof(*tim)); + tim->f = f; + tim->arg = arg; + tim->priority = priority; +} + +/* + * Add a timer in the scheduled list (timer must not already be in a list). The + * timers are sorted in the list according the expire time (the closer timers + * first). + * + * called with irq locked + */ +static void +callout_add_in_sched_list(struct callout_mgr *cm, struct callout *tim) +{ + struct callout *t, *prev_t; + + callout_dprintf("cm=%p tim=%p\r\n", cm, tim); + tim->state = CALLOUT_STATE_SCHEDULED; + + /* list is empty */ + if (LIST_EMPTY(&cm->sched_list)) { + LIST_INSERT_HEAD(&cm->sched_list, tim, next); + return; + } + + /* 'tim' expires before first entry */ + t = LIST_FIRST(&cm->sched_list); + if ((int16_t)(tim->expire - t->expire) <= 0) { + LIST_INSERT_HEAD(&cm->sched_list, tim, next); + return; + } + + /* find an element that will expire after 'tim' */ + LIST_FOREACH(t, &cm->sched_list, next) { + if ((int16_t)(tim->expire - t->expire) <= 0) { + LIST_INSERT_BEFORE(t, tim, next); + return; + } + prev_t = t; + } + + /* not found, insert at the end of the list */ + LIST_INSERT_AFTER(prev_t, tim, next); +} + +/* + * Add a timer in the local expired list (timer must not already be in a + * list). The timers are sorted in the list according to the priority (high + * priority first). + * + * called with irq locked + */ +static void +callout_add_in_expired_list(struct callout_mgr *cm, + struct callout_list *expired_list, struct callout *tim) +{ + struct callout *t, *prev_t; + + (void)cm; /* avoid warning if debug is disabled */ + + callout_dprintf("cm=%p tim=%p\r\n", cm, tim); + tim->state = CALLOUT_STATE_EXPIRED; + + /* list is empty */ + if (LIST_EMPTY(expired_list)) { + LIST_INSERT_HEAD(expired_list, tim, next); + return; + } + + /* 'tim' has a higher prio */ + t = LIST_FIRST(expired_list); + if (tim->priority >= t->priority) { + LIST_INSERT_HEAD(expired_list, tim, next); + return; + } + + /* find an element that will expire after 'tim' */ + LIST_FOREACH(t, expired_list, next) { + if (tim->priority >= t->priority) { + LIST_INSERT_BEFORE(t, tim, next); + return; + } + prev_t = t; + } + + /* not found, insert at the end of the list */ + LIST_INSERT_AFTER(prev_t, tim, next); +} + +/* + * del from list (timer must be in a list) + */ +static void +callout_del(struct callout_mgr *cm, struct callout *tim) +{ + (void)cm; /* avoid warning if debug is disabled */ + callout_dprintf("cm=%p tim=%p\r\n", cm, tim); + LIST_REMOVE(tim, next); +} + +/* Reset and start the timer associated with the timer handle tim */ +static int +__callout_schedule(struct callout_mgr *cm, struct callout *tim, + uint16_t expire) +{ + irqflags_t flags; + + callout_dprintf("cm=%p tim=%p expire=%d\r\n", + cm, tim, expire); + + flags = irq_lock_save(); + CALLOUT_STAT_ADD(cm, schedule, 1); + + /* remove it from list */ + if (tim->state != CALLOUT_STATE_STOPPED) { + /* stats */ + if (tim->state == CALLOUT_STATE_SCHEDULED) + CALLOUT_STAT_ADD(cm, cur_scheduled, -1); + else if (tim->state == CALLOUT_STATE_EXPIRED) + CALLOUT_STAT_ADD(cm, cur_expired, -1); + if (tim->state == CALLOUT_STATE_RUNNING) + CALLOUT_STAT_ADD(cm, cur_running, -1); + + callout_del(cm, tim); + } + + tim->expire = expire; + CALLOUT_STAT_ADD(cm, cur_scheduled, 1); + callout_add_in_sched_list(cm, tim); + irq_unlock_restore(flags); + + return 0; +} + +/* Reset and start the timer associated with the timer handle tim */ +int +callout_schedule(struct callout_mgr *cm, struct callout *tim, + uint16_t ticks) +{ + return __callout_schedule(cm, tim, cm->get_time() + ticks); +} + +/* Reset and start the timer associated with the timer handle tim */ +int +callout_reschedule(struct callout_mgr *cm, struct callout *tim, + uint16_t ticks) +{ + return __callout_schedule(cm, tim, tim->expire + ticks); +} + +/* Stop the timer associated with the timer handle tim */ +void +callout_stop(struct callout_mgr *cm, struct callout *tim) +{ + irqflags_t flags; + + callout_dprintf("cm=%p tim=%p\r\n", cm, tim); + + flags = irq_lock_save(); + if (tim->state != CALLOUT_STATE_STOPPED) { + + /* stats */ + if (tim->state == CALLOUT_STATE_SCHEDULED) + CALLOUT_STAT_ADD(cm, cur_scheduled, -1); + else if (tim->state == CALLOUT_STATE_EXPIRED) + CALLOUT_STAT_ADD(cm, cur_expired, -1); + if (tim->state == CALLOUT_STATE_RUNNING) + CALLOUT_STAT_ADD(cm, cur_running, -1); + CALLOUT_STAT_ADD(cm, stop, 1); + + /* remove it from list */ + callout_del(cm, tim); + tim->state = CALLOUT_STATE_STOPPED; + } + irq_unlock_restore(flags); +} + +/* must be called periodically, run all timer that expired */ +void callout_manage(struct callout_mgr *cm) +{ + struct callout_list expired_list; + struct callout_list reschedule_list; + struct callout *tim, *tim_next; + uint16_t cur_time; + uint8_t old_prio; + int16_t diff; + + CALLOUT_STAT_ADD(cm, manage, 1); + callout_dprintf("cm=%p\r\n", cm); + + /* maximize the number of self-recursions */ + if (cm->nb_recursion >= CALLOUT_MAX_RECURSION) { + CALLOUT_STAT_ADD(cm, max_recursion, 1); + return; + } + + irq_lock(); + cm->nb_recursion++; + LIST_INIT(&expired_list); + LIST_INIT(&reschedule_list); + cur_time = cm->get_time(); + old_prio = cm->cur_priority; + + /* move all expired timers in a local list */ + _LIST_FOREACH_SAFE(tim, tim_next, &cm->sched_list, next) { + + diff = cur_time - tim->expire; + + /* check the expiration time (tasks are sorted) */ + if (diff < 0) + break; + + callout_dprintf("cm=%p diff=%d\r\n", cm, diff); + + /* check the priority, if it's too low, inc stats */ + if (tim->priority <= cm->cur_priority) { + if (diff < 16484) + CALLOUT_STAT_ADD(cm, delayed, 1); + else { + /* reschedule to avoid an overflow */ + CALLOUT_STAT_ADD(cm, hard_delayed, 1); + LIST_REMOVE(tim, next); + tim->expire = cur_time; + LIST_INSERT_HEAD(&reschedule_list, tim, next); + } + continue; + } + + LIST_REMOVE(tim, next); + callout_add_in_expired_list(cm, &expired_list, tim); + CALLOUT_STAT_ADD(cm, cur_scheduled, -1); + CALLOUT_STAT_ADD(cm, cur_expired, 1); + } + + /* reschedule hard_delayed timers, this does not happen usually */ + while (!LIST_EMPTY(&reschedule_list)) { + tim = LIST_FIRST(&reschedule_list); + LIST_REMOVE(tim, next); + callout_add_in_sched_list(cm, tim); + } + + /* for each timer of 'expired' list, execute callback */ + while (!LIST_EMPTY(&expired_list)) { + tim = LIST_FIRST(&expired_list); + LIST_REMOVE(tim, next); + + /* execute callback function */ + CALLOUT_STAT_ADD(cm, cur_expired, -1); + CALLOUT_STAT_ADD(cm, cur_running, 1); + tim->state = CALLOUT_STATE_RUNNING; + cm->cur_priority = tim->priority; + irq_unlock(); + tim->f(cm, tim, tim->arg); + irq_lock(); + } + + cm->cur_priority = old_prio; + cm->nb_recursion--; + irq_unlock(); +} + +/* set the current priority level */ +uint8_t callout_mgr_set_prio(struct callout_mgr *cm, uint8_t new_prio) +{ + uint8_t old_prio; + + old_prio = cm->cur_priority; + if (new_prio <= old_prio) + return old_prio; + + cm->cur_priority = new_prio; + return old_prio; +} + +/* restore the current priority level */ +void callout_mgr_restore_prio(struct callout_mgr *cm, uint8_t old_prio) +{ + cm->cur_priority = old_prio; +} + +/* dump statistics about timers */ +void callout_dump_stats(struct callout_mgr *cm) +{ +#ifdef CALLOUT_STATS + printf_P(PSTR("Timer statistics:\r\n")); + printf_P(PSTR(" schedule = %"PRIu32"\r\n"), cm->stats.schedule); + printf_P(PSTR(" stop = %"PRIu32"\r\n"), cm->stats.stop); + printf_P(PSTR(" manage = %"PRIu32"\r\n"), cm->stats.manage); + printf_P(PSTR(" max_recursion = %"PRIu32"\r\n"), + cm->stats.max_recursion); + printf_P(PSTR(" delayed = %"PRIu32"\r\n"), cm->stats.delayed); + printf_P(PSTR(" hard_delayed = %"PRIu32"\r\n"), + cm->stats.hard_delayed); + + printf_P(PSTR(" cur_scheduled = %u\r\n"), cm->stats.cur_scheduled); + printf_P(PSTR(" cur_expired = %u\r\n"), cm->stats.cur_expired); + printf_P(PSTR(" cur_running = %u\r\n"), cm->stats.cur_running); +#else + printf_P(PSTR("No timer statistics, CALLOUT_STATS is disabled\r\n")); +#endif +} diff --git a/src/callout.h b/src/callout.h new file mode 100644 index 0000000..de0eadc --- /dev/null +++ b/src/callout.h @@ -0,0 +1,304 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2014-2015, Olivier MATZ + * Copyright 2010 Intel Corporation + * (Inspired by Intel DPDK rte_timer library) + */ + +#ifndef CALLOUT_H_ +#define CALLOUT_H_ + +#include + +#define CALLOUT_STATS +/* #define CALLOUT_DEBUG */ + +/** + * This module provides a timer service. The manager function + * callout_manage() can be called from an interrupt or from a + * standard function (usually a main-loop). In the latter case, no + * preemption is possible. + * + * Each timer has a priority: the timers with higher priorities are + * scheduled before the others. This feature is mostly useful when the + * manager is called from an interrupt. Indeed, the callback function of + * a timer with a high priority cannot be preempted by a timer with a + * lower priority. + * + * The module locks interrupts when doing critical operations, ensuring that + * critical data are accessed atomically. + * + * State of timers: + * - stopped: initial state after callout_init() + * - scheduled: after a call to callout_schedule(), the timer is in the + * scheduled list of the callout manager + * - expired: after a call to callout_manage(), if the expire time of a + * timer is reached, it is moved in a local list and its state is + * changed to "expired". + * - before starting the callback, the timer goes in state "running". + * + * Once running, the associated timer is not touched anymore by + * callout_manage(). As a result, the timer MUST be either reloaded + * or stopped (and potentially freed). + */ + +/** + * Maximum number of nested preemptions. + */ +#define CALLOUT_MAX_RECURSION 5 + +#ifdef CALLOUT_STATS +/** + * The structure that stores the timer statistics, mostly useful for debug + * purposes. + */ +struct callout_debug_stats { + uint32_t schedule; /**< nb of calls to callout_(re)schedule() */ + uint32_t stop; /**< nb of calls to callout_stop() */ + uint32_t manage; /**< nb of calls to callout_manage() */ + uint32_t max_recursion; /** manage() skipped due to max recursion */ + uint32_t delayed; /** task delayed a bit due to low prio */ + uint32_t hard_delayed; /** task recheduled later due to low priority */ + + uint8_t cur_scheduled; /**< current number of scheduled timers */ + uint8_t cur_expired; /**< current number of expired timers */ + uint8_t cur_running; /**< current number of running timers */ +}; +#endif + +struct callout; +struct callout_mgr; + +/** + * The type of a callout callback function. + */ +typedef void (callout_cb_t)(struct callout_mgr *cm, + struct callout *tim, void *arg); + +/** + * A callout structure, storing all data associated to a timer. + */ +struct callout { + LIST_ENTRY(callout) next; /**< next/prev in list */ + +#define CALLOUT_STATE_STOPPED 0 /**< not scheduled */ +#define CALLOUT_STATE_SCHEDULED 1 /**< in the scheduled list */ +#define CALLOUT_STATE_EXPIRED 2 /**< expired, will be executed soon */ +#define CALLOUT_STATE_RUNNING 3 /**< being executed */ + uint8_t state; /**< stopped, scheduled, expired */ + uint8_t priority; /**< the priority of the timer */ + uint16_t expire; /**< time when timer should expire */ + + callout_cb_t *f; /**< callback function pointer */ + void *arg; /**< argument given to the cb function. */ +}; + +/* define the callout list */ +LIST_HEAD(callout_list, callout); + +/* static initializer for a timer structure */ +#define CALLOUT_INITIALIZER { } + +/** + * Type of the function used by a callout manager to get a time reference + */ +typedef uint16_t (callout_get_time_t)(void); + +/** + * An instance of callout manager. It is possible to have several + * managers. A callout is attached to one manager at a time. + */ +struct callout_mgr { + callout_get_time_t *get_time; /**< func to get the time reference */ + uint16_t prev_time; /**< time of previous call */ + uint8_t cur_priority; /** priority of running event */ + uint8_t nb_recursion; /** number of recursion */ + struct callout_list sched_list; /**< list of scheduled timers */ + +#ifdef CALLOUT_STATS + struct callout_debug_stats stats; /**< stats */ +#endif +}; + +/** + * Initialize a callout manager + * + * The callout manager must be initialized before callout_add() or + * callout_manage() can be called. + * + * @param cm + * Pointer to the uninitialized callout manager structure. + * @param get_time + * Pointer to a function that returns a time reference (unsigned 16 bits). + */ +void callout_mgr_init(struct callout_mgr *cm, + callout_get_time_t *get_time); + +/** + * Initialize a callout structure and set callback function + * + * Before doing any operation on the callout structure, it has to be + * initialized with this function. It is possible to reinitialize a + * timer that has been previously scheduled, but it must be stopped. + * + * @param tim + * The timer to initialize. + * @param priority + * The priority of the callout (high value means higher priority) + * @param f + * The callback function of the timer. + * @param arg + * The user argument of the callback function. + */ +void callout_init(struct callout *tim, callout_cb_t f, void *arg, + uint8_t priority); + +/** + * Schedule a callout + * + * The callout_schedule() function adds the timer in the scheduled + * list. After the specified amount of ticks are elapsed, the callback + * function of the timer previously given to callout_init() will be + * invoked with its argument. + * + * The given "tick" value is relative to the current time, and is 16 bits + * wide. As it internally uses signed 16 bits comparison, the max value for + * ticks is 32767. + * + * @param cm + * The callout manager where the timer should be scheduled + * @param tim + * The timer handle + * @param ticks + * The number of ticks before the callback function is called, relative to now + * (the reference is given by the get_time() function of the callout manager). + * @return + * 0 on success, negative on error + */ +int callout_schedule(struct callout_mgr *cm, struct callout *tim, + uint16_t ticks); + +/** + * Reschedule a callout + * + * This function does exactly the same than callout_schedule() + * except that the given time "ticks" is not relative to the current + * time but to the "expire" field of the timer. + * + * Using this function is advised to avoid drift if you want to have periodic + * timers. + * + * This function should preferably be called from the callback function of + * the timer. Indeed, if the "expire" field should be a known value or it + * can result in an undefined behavior + * + * The given "tick" value is relative to the "expire" field of the + * timer, and is 16 bits wide. As it internally uses signed 16 bits + * comparison, the max value for ticks is 32767. + * + * @param cm + * The callout manager where the timer should be scheduled + * @param tim + * The timer handle + * @param ticks + * The number of ticks before the callback function is called, relative to + * the "expire" value of the timer + * @return + * 0 on success, negative on error + */ +int callout_reschedule(struct callout_mgr *cm, struct callout *tim, + uint16_t ticks); + +/** + * Stop a timer. + * + * The callout_stop() function stops a timer associated with the + * timer handle tim. + * + * If the timer is scheduled or expired, it is removed from the list: + * the callback function won't be invoked. If the timer is stopped or + * running the function does nothing. + * + * If a timer structure is dynamically allocated, invoking + * callout_stop() is needed before freeing the structure, even if + * the freeing occurs in a callback. Indeed, this function can be called + * safely from a timer callback. If it succeeds, the timer is not + * referenced anymore by the callout manager. + * + * @param cm + * The callout manager where the timer is or was scheduled + * @param tim + * The timer + * @return + * 0 on success, negative on error + */ +void callout_stop(struct callout_mgr *cm, struct callout *tim); + +/** + * Return the state of a timer + * + * @param tim + * The timer + * @return + * - CALLOUT_STATE_STOPPED: the timer is stopped + * - CALLOUT_STATE_SCHEDULED: the timer is scheduled + * - CALLOUT_STATE_EXPIRED: the timer was moved in a local list before + * execution + */ +static inline uint8_t callout_state(struct callout *tim) +{ + return tim->state; +} + +/** + * Manage the timer list and execute callback functions. + * + * This function must be called periodically, either from a loop of from + * an interrupt. It browses the list of scheduled timers and runs all + * timers that are expired. + * + * This function must be called at least every 16384 reference ticks of + * cm->get_time(), but calling it more often is recommanded to avoid + * delaying task abusively. + * + * The function must be called with IRQ allowed. + */ +void callout_manage(struct callout_mgr *cm); + +/** + * Dump statistics about timers. + */ +void callout_dump_stats(struct callout_mgr *cm); + +/** + * Set the current priority level + * + * Prevent callout with a priority lower than "new_prio" to be executed. + * If the current priority of the callout manager is already lower higher + * than "new_prio", the function won't change the running priority. + * + * The returned value should be stored by the caller and restored with + * callout_mgr_restore_prio(), preferably in the same function. + * + * @param cm + * The callout manager + * @param new_prio + * The new running priority + * + * @return + * The value of the running priority before the call og this function + */ +uint8_t callout_mgr_set_prio(struct callout_mgr *cm, uint8_t new_prio); + +/** + * Restore the current priority level + * + * Used after a call to callout_mgr_set_prio(). + * + * @param cm + * The callout manager + * @param old_prio + * The old running priority + */ +void callout_mgr_restore_prio(struct callout_mgr *cm, uint8_t old_prio); + +#endif /* CALLOUT_H_ */ diff --git a/src/cirbuf.c b/src/cirbuf.c new file mode 100644 index 0000000..0ab9465 --- /dev/null +++ b/src/cirbuf.c @@ -0,0 +1,374 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2009-2015, Olivier MATZ + */ + +#include +#include + +#include + +/* init a circular buffer */ +void +cirbuf_init(struct cirbuf *cbuf, char *buf, unsigned start, + unsigned maxlen) +{ + cbuf->maxlen = maxlen; + cbuf->len = 0; + cbuf->start = start; + cbuf->buf = buf; +} + +/* return the index of the tail (contains an empty element) */ +static unsigned +cirbuf_get_end(const struct cirbuf *cbuf) +{ + unsigned end; + + end = cbuf->start + cbuf->len; + if (end >= cbuf->maxlen) + end -= cbuf->maxlen; + + return end; +} + +/* multiple add at head */ +int +cirbuf_add_buf_head(struct cirbuf *cbuf, const char *buf, unsigned n) +{ + unsigned remain = n; + int copy_start; + unsigned copy_len; + + if (remain == 0 || remain > cirbuf_get_freelen(cbuf)) + return -EINVAL; + + copy_start = cbuf->start - remain; + if (copy_start < 0) + copy_start += cbuf->maxlen; + cbuf->start = copy_start; + cbuf->len += remain; + copy_len = remain; + if ((unsigned)copy_start + copy_len >= cbuf->maxlen) + copy_len = cbuf->maxlen - copy_start; + if (copy_len > 0) { + memcpy(cbuf->buf + copy_start, buf, copy_len); + buf += copy_len; + remain -= copy_len; + } + if (remain == 0) + return n; + + copy_len = remain; + memcpy(cbuf->buf, buf, copy_len); + + return n; +} + +/* multiple add at tail */ +int +cirbuf_add_buf_tail(struct cirbuf *cbuf, const char *buf, unsigned n) +{ + unsigned remain = n; + unsigned copy_start, copy_len; + + if (remain == 0 || remain > cirbuf_get_freelen(cbuf)) + return -EINVAL; + + copy_start = cirbuf_get_end(cbuf); + cbuf->len += remain; + copy_len = remain; + if (copy_start + copy_len >= cbuf->maxlen) + copy_len = cbuf->maxlen - copy_start; + if (copy_len > 0) { + memcpy(cbuf->buf + copy_start, buf, copy_len); + buf += copy_len; + remain -= copy_len; + } + if (remain == 0) + return n; + + copy_len = remain; + memcpy(cbuf->buf, buf, copy_len); + + return n; +} + +/* single add at head */ +static inline void +__cirbuf_add_head(struct cirbuf *cbuf, char c) +{ + unsigned start = cbuf->start; + + if (start == 0) + start = cbuf->maxlen - 1; + else + start = start - 1; + cbuf->buf[start] = c; + cbuf->start = start; + cbuf->len++; +} + +/* single add at head, checking if full first */ +int +cirbuf_add_head_safe(struct cirbuf *cbuf, char c) +{ + if (!cirbuf_is_full(cbuf)) { + __cirbuf_add_head(cbuf, c); + return 0; + } + return -EINVAL; +} + +/* single add at head */ +void +cirbuf_add_head(struct cirbuf *cbuf, char c) +{ + __cirbuf_add_head(cbuf, c); +} + +/* single add at tail */ +static inline void +__cirbuf_add_tail(struct cirbuf *cbuf, char c) +{ + unsigned end = cirbuf_get_end(cbuf); + + cbuf->buf[end] = c; + cbuf->len++; +} + +/* single add at tail, checking if full first */ +int +cirbuf_add_tail_safe(struct cirbuf *cbuf, char c) +{ + if (!cirbuf_is_full(cbuf)) { + __cirbuf_add_tail(cbuf, c); + return 0; + } + return -EINVAL; +} + +/* single add at tail */ +void +cirbuf_add_tail(struct cirbuf *cbuf, char c) +{ + __cirbuf_add_tail(cbuf, c); +} + +/* multiple delete at head */ +int +cirbuf_del_buf_head(struct cirbuf *cbuf, unsigned size) +{ + if (size == 0 || size > cirbuf_get_len(cbuf)) + return -EINVAL; + + cbuf->len -= size; + if (cirbuf_is_empty(cbuf)) { + cbuf->start += size - 1; + cbuf->start %= cbuf->maxlen; + } + else { + cbuf->start += size; + cbuf->start %= cbuf->maxlen; + } + return 0; +} + +/* multiple delete at tail */ +int +cirbuf_del_buf_tail(struct cirbuf *cbuf, unsigned size) +{ + if (size == 0 || size > cirbuf_get_len(cbuf)) + return -EINVAL; + + cbuf->len -= size; + return 0; +} + +/* single del at head */ +static inline void +__cirbuf_del_head(struct cirbuf *cbuf) +{ + cbuf->len --; + if (!cirbuf_is_empty(cbuf)) { + cbuf->start ++; + cbuf->start %= cbuf->maxlen; + } +} + +/* single del at head, checking if empty first */ +int +cirbuf_del_head_safe(struct cirbuf *cbuf) +{ + if (cbuf && !cirbuf_is_empty(cbuf)) { + __cirbuf_del_head(cbuf); + return 0; + } + return -EINVAL; +} + +/* single del at head */ +void +cirbuf_del_head(struct cirbuf *cbuf) +{ + __cirbuf_del_head(cbuf); +} + +/* single del at tail */ +static inline void +__cirbuf_del_tail(struct cirbuf *cbuf) +{ + cbuf->len--; +} + +/* single del at tail, checking if empty first */ +int +cirbuf_del_tail_safe(struct cirbuf *cbuf) +{ + if (cbuf && !cirbuf_is_empty(cbuf)) { + __cirbuf_del_tail(cbuf); + return 0; + } + return -EINVAL; +} + +/* single del at tail */ +void +cirbuf_del_tail(struct cirbuf *cbuf) +{ + __cirbuf_del_tail(cbuf); +} + +/* convert to buffer */ +int +cirbuf_get_buf_head(const struct cirbuf *cbuf, char *buf, unsigned n) +{ + unsigned remain = n; + unsigned cirbuf_len, copy_start, copy_len; + + cirbuf_len = cirbuf_get_len(cbuf); + if (remain >= cirbuf_len) + remain = cirbuf_len; + + if (remain == 0) + return 0; + + copy_start = cbuf->start; + copy_len = remain; + if (copy_start + copy_len >= cbuf->maxlen) + copy_len = cbuf->maxlen - copy_start; + if (copy_len > 0) { + memcpy(buf, cbuf->buf + copy_start, copy_len); + buf += copy_len; + remain -= copy_len; + } + if (remain == 0) + return n; + + copy_len = remain; + memcpy(buf, cbuf->buf, copy_len); + + return n; +} + +/* convert to buffer */ +int +cirbuf_get_buf_tail(const struct cirbuf *cbuf, char *buf, unsigned n) +{ + unsigned remain = n; + int copy_start; + unsigned cirbuf_len, copy_len; + + cirbuf_len = cirbuf_get_len(cbuf); + if (remain >= cirbuf_len) + remain = cirbuf_len; + + if (remain == 0) + return 0; + + copy_start = cirbuf_get_end(cbuf) - remain; + if (copy_start < 0) + copy_start += cbuf->maxlen; + copy_len = remain; + if ((unsigned)copy_start + copy_len >= cbuf->maxlen) + copy_len = cbuf->maxlen - copy_start; + if (copy_len > 0) { + memcpy(buf, cbuf->buf + copy_start, copy_len); + buf += copy_len; + remain -= copy_len; + } + if (remain == 0) + return n; + + copy_len = remain; + memcpy(buf, cbuf->buf, copy_len); + + return n; +} + +/* get head */ +char +cirbuf_get_head(const struct cirbuf *cbuf) +{ + return cbuf->buf[cbuf->start]; +} + +/* get tail */ +char +cirbuf_get_tail(const struct cirbuf *cbuf) +{ + unsigned end; + + /* should not happen */ + if (cbuf->len == 0) + return 0; + + end = cbuf->start + cbuf->len - 1; + if (end >= cbuf->maxlen) + end -= cbuf->maxlen; + return cbuf->buf[end]; +} + +static void +__cirbuf_shift(struct cirbuf *cbuf, unsigned n) +{ + char tmp, tmp2; + unsigned start, cur, min; + + start = 0; + cur = 0; + tmp = cbuf->buf[0]; + min = cbuf->maxlen; + + while (1) { + cur = cur + n; + if (cur >= cbuf->maxlen) + cur -= cbuf->maxlen; + tmp2 = cbuf->buf[cur]; + cbuf->buf[cur] = tmp; + tmp = tmp2; + if (cur == start) { + if ((cur + 1) == min) + break; + cur++; + tmp = cbuf->buf[cur]; + start = cur; + } else if (cur < min) { + min = cur; + } + } + + cbuf->start += n; + if (cbuf->start >= cbuf->maxlen) + cbuf->start -= cbuf->maxlen; +} + +void cirbuf_align_left(struct cirbuf *cbuf) +{ + __cirbuf_shift(cbuf, cbuf->maxlen - cbuf->start); +} + +void cirbuf_align_right(struct cirbuf *cbuf) +{ + unsigned end = cirbuf_get_end(cbuf); + __cirbuf_shift(cbuf, cbuf->maxlen - end); +} diff --git a/src/cirbuf.h b/src/cirbuf.h new file mode 100644 index 0000000..ab05ee5 --- /dev/null +++ b/src/cirbuf.h @@ -0,0 +1,355 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2009-2015, Olivier MATZ + */ + +#ifndef CIRBUF_H_ +#define CIRBUF_H_ + +#include + +/** + * A circular buffer. + */ +struct cirbuf { + unsigned maxlen; /**< Total length of the fifo (number of elements). */ + unsigned start; /**< Index of the first element. */ + unsigned len; /**< Current len of fifo. */ + char *buf; /**< Pointer to the data buffer. */ +}; + +/** + * Initialize a circular buffer. + * + * @param cbuf + * A pointer to an uninitialized circular buffer structure. + * @param buf + * The buffer used to store the data. + * @param start + * The index of head at initialization. + * @param maxlen + * The size of the buffer. + */ +void cirbuf_init(struct cirbuf *cbuf, char *buf, unsigned start, + unsigned maxlen); + +/** + * Check if the circular buffer is full. + * + * @param cbuf + * The circular buffer pointer. + * @return + * 1 if the circular buffer is full, else 0. + */ +static inline int cirbuf_is_full(const struct cirbuf *cbuf) +{ + return cbuf->len == cbuf->maxlen; +} + +/** + * Check if the circular buffer is empty. + * + * @param cbuf + * The circular buffer pointer. + * @return + * 1 if the circular buffer is empty, else 0. + */ +static inline int cirbuf_is_empty(const struct cirbuf *cbuf) +{ + return cbuf->len == 0; +} + +/** + * Get the length of data in the circular buffer. + * + * @param cbuf + * The circular buffer pointer. + * @return + * The current length of data in the circular buffer. + */ +static inline unsigned cirbuf_get_len(const struct cirbuf *cbuf) +{ + return cbuf->len; +} + +/** + * Get the size of the circular buffer. + * + * @param cbuf + * The circular buffer pointer. + * @return + * Return the maximum size of the circular buffer (used + free elements) + */ +static inline unsigned cirbuf_get_maxlen(const struct cirbuf *cbuf) +{ + return cbuf->maxlen; +} + +/** + * Get the lenght of free space in the circular buffer. + * + * @param cbuf + * The circular buffer pointer. + * @return + * Return the length of free space. + */ +static inline unsigned cirbuf_get_freelen(const struct cirbuf *cbuf) +{ + return cbuf->maxlen - cbuf->len; +} + +/** + * Iterator for a circular buffer + * + * cirbuf: struct cirbuf pointer + * i: an integer internally used in the macro + * elt: char that takes the value for each iteration + */ +#define CIRBUF_FOREACH(cirbuf, i, elt) \ + for (i = 0, elt = (cirbuf)->buf[(cirbuf)->start]; \ + i < ((cirbuf)->len); \ + i ++, elt = (cirbuf)->buf[((cirbuf)->start + i) % \ + ((cirbuf)->maxlen)]) + +/** + * Add a character at the head of the circular buffer. + * + * @param cbuf + * The circular buffer pointer. + * @param c + * The character to add. + * @return + * Return 0 on success, or a negative value on error. + */ +int cirbuf_add_head_safe(struct cirbuf *cbuf, char c); + +/** + * Add a character at the head of the circular buffer. + * + * The function does not check that there is enough free space + * in the buffer, so it has to be done by the caller. If it's + * not the case, undefined behavior will occur. + * + * @param cbuf + * The circular buffer pointer. + * @param c + * The character to add. + */ +void cirbuf_add_head(struct cirbuf *cbuf, char c); + +/** + * Add a character at the tail of the circular buffer. + * + * @param cbuf + * The circular buffer pointer. + * @param c + * The character to add. + * @return + * Return 0 on success, or a negative value on error. + */ +int cirbuf_add_tail_safe(struct cirbuf *cbuf, char c); + +/** + * Add a character at the tail of the circular buffer. + * + * The function does not check that there is enough free space + * in the buffer, so it has to be done by the caller. If it's + * not the case, undefined behavior will occur. + * + * @param cbuf + * The circular buffer pointer. + * @param c + * The character to add. + */ +void cirbuf_add_tail(struct cirbuf *cbuf, char c); + +/** + * Remove a char at the head of the circular buffer. + * + * @param cbuf + * The circular buffer pointer. + * @return + * Return 0 on success, or a negative value on error. + */ +int cirbuf_del_head_safe(struct cirbuf *cbuf); + +/** + * Remove a char at the head of the circular buffer. + * + * The function does not check that there is enough elements + * in the buffer, so it has to be done by the caller. If it's + * not the case, undefined behavior will occur. + * + * @param cbuf + * The circular buffer pointer. + */ +void cirbuf_del_head(struct cirbuf *cbuf); + +/** + * Remove a char at the tail of the circular buffer. + * + * @param cbuf + * The circular buffer pointer. + * @return + * Return 0 on success, or a negative value on error. + */ +int cirbuf_del_tail_safe(struct cirbuf *cbuf); + +/** + * Remove a char at the tail of the circular buffer. + * + * The function does not check that there is enough elements + * in the buffer, so it has to be done by the caller. If it's + * not the case, undefined behavior will occur. + * + * @param cbuf + * The circular buffer pointer. + */ +void cirbuf_del_tail(struct cirbuf *cbuf); + +/** + * Return the element at the tail of the circular buffer. + * + * The circular buffer must not be empty or an undefined character + * will be returned. + * + * @param cbuf + * The circular buffer pointer. + * @return + * The character at the tail of the circular buffer. + */ +char cirbuf_get_head(const struct cirbuf *cbuf); + +/** + * Return the element at the tail of the circular buffer. + * + * The circular buffer must not be empty or an undefined character + * will be returned. + * + * @param cbuf + * The circular buffer pointer. + * @return + * The character at the tail of the circular buffer. + */ +char cirbuf_get_tail(const struct cirbuf *cbuf); + +/** + * Add a buffer at the head of the circular buffer. + * + * Add 'n' bytes of buffer pointed by 'buf' ad the head of th + * circular buffer. + * + * @param cbuf + * The circular buffer pointer. + * @param buf + * The pointer to the buffer. + * @param n + * Number of bytes to add. + * @return + * Return 0 on success, or a negative value on error. + */ +int cirbuf_add_buf_head(struct cirbuf *cbuf, const char *buf, + unsigned n); + +/** + * Add a buffer at the tail of the circular buffer. + * + * Add 'n' bytes of buffer pointed by 'buf' ad the tail of th + * circular buffer. + * + * @param cbuf + * The circular buffer pointer. + * @param buf + * The pointer to the buffer. + * @param n + * Number of bytes to add. + * @return + * Return 0 on success, or a negative value on error. + */ +int cirbuf_add_buf_tail(struct cirbuf *cbuf, const char *buf, + unsigned n); + +/** + * Remove chars at the head of the circular buffer. + * + * @param cbuf + * The circular buffer pointer. + * @param n + * Number of bytes to remove. + * @return + * Return 0 on success, or a negative value on error. + */ +int cirbuf_del_buf_head(struct cirbuf *cbuf, unsigned n); + +/** + * Remove chars at the tail of the circular buffer. + * + * @param cbuf + * The circular buffer pointer. + * @param n + * Number of bytes to remove. + * @return + * Return 0 on success, or a negative value on error. + */ +int cirbuf_del_buf_tail(struct cirbuf *cbuf, unsigned n); + +/** + * Copy multiple bytes from the head of the circular buffer. + * + * Copy a maximum of 'n' characters from the head of the circular buffer + * into a flat buffer pointed by 'buf'. If the circular buffer is + * smaller than n, less bytes are copied. + * + * @param cbuf + * The circular buffer pointer. + * @param buf + * The pointer to the buffer. + * @param n + * Maximum number of bytes to copy. + * @return + * Return the number of copied chars. + */ +int cirbuf_get_buf_head(const struct cirbuf *cbuf, char *buf, + unsigned n); + +/** + * Copy multiple bytes from the tail of the circular buffer. + * + * Copy a maximum of 'n' characters from the tail of the circular buffer + * into a flat buffer pointed by 'buf'. If the circular buffer is + * smaller than n, less bytes are copied. + * + * @param cbuf + * The circular buffer pointer. + * @param buf + * The pointer to the buffer. + * @param n + * Maximum number of bytes to copy. + * @return + * Return the number of copied chars. + */ +int cirbuf_get_buf_tail(const struct cirbuf *cbuf, char *buf, + unsigned n); + +/** + * Set the start of the data to the index 0 of the internal buffer. + * + * After a call to this function, it is possible for the caller to + * use cbuf->buf as a linear (read-only) buffer. + * + * @param cbuf + * The circular buffer pointer. + */ +void cirbuf_align_left(struct cirbuf *cbuf); + +/** + * Set the end of the data to the last index of the internal buffer. + * + * After a call to this function, it is possible for the caller to + * use cbuf->buf as a linear (read-only) buffer. + * + * @param cbuf + * The circular buffer pointer. + */ +void cirbuf_align_right(struct cirbuf *cbuf); + +#endif /* CIRBUF_H_ */ diff --git a/src/commands.c b/src/commands.c new file mode 100644 index 0000000..54bd7a3 --- /dev/null +++ b/src/commands.c @@ -0,0 +1,210 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2019, Olivier MATZ + */ + +#include +#include + +#include "irq.h" +#include "uart.h" +#include "led.h" +#include "buzzer.h" +#include "main.h" +#include "commands.h" + +#define ntohs __builtin_bswap16 +#define htons __builtin_bswap16 + +#define CMD_LED_DEBUG 0x00 +struct cmd_led_debug { + uint8_t on; +}; + +#define CMD_BUZZER 0x01 +struct cmd_buzzer { + uint16_t period_us; + uint16_t duration_ms; +}; + +#define CMD_LED_GYRO_ALL 0x02 +struct cmd_led_gyro_all { + uint16_t intensity; + uint16_t blink_on_ms; + uint16_t blink_off_ms; + uint16_t stop_ms; +}; + +#define CMD_LED_GYRO_RAY 0x03 +struct cmd_led_gyro_ray { + uint16_t angle; + uint16_t intensity; + uint16_t width; + int16_t rot_speed; + uint16_t stop_ms; +}; + +#define CMD_LOG_DEBUG 0x04 +struct cmd_log_debug { + uint8_t mask; +}; + +#define CMD_RESET 0x05 + +#define CMD_BUZZER_VIBRATO 0x06 +struct cmd_buzzer_vibrato { + uint16_t period1_us; + uint16_t period2_us; + uint16_t duration_ms; + uint16_t vibrato_period_ms; + uint8_t vibrato_type; +}; + +#define CMD_MAX 0x07 + +#define CMD_INVALID 0xFF + +struct cmd_buf { + uint8_t cmd; + uint8_t len; + union { + struct cmd_led_debug led_debug; + struct cmd_buzzer buzzer; + struct cmd_led_gyro_all led_gyro_all; + struct cmd_led_gyro_ray led_gyro_ray; + struct cmd_log_debug log_debug; + struct cmd_buzzer_vibrato buzzer_vibrato; + }; + uint8_t csum; +}; + +/* current command buffer */ +static struct cmd_buf cmd_buf; +static uint8_t cmd_len; +static uint8_t cmd_csum; + +static void clear_cmd(void) +{ + cmd_len = 0; + cmd_csum = 0; +} + +static uint8_t csum_add(uint8_t x, uint8_t y) +{ + uint16_t res = (uint16_t)x + (uint16_t)y; + res = (res >> 8) + (res & 0xff); + return res; +} + +static void cmd_led_debug(void) +{ + if (cmd_buf.len != sizeof(struct cmd_led_debug) + 3) + return; + if (cmd_buf.led_debug.on) + led_debug_on(); + else + led_debug_off(); +} + +static void cmd_buzzer(void) +{ + if (cmd_buf.len != sizeof(struct cmd_buzzer) + 3) + return; + buzzer(ntohs(cmd_buf.buzzer.period_us), + ntohs(cmd_buf.buzzer.duration_ms)); +} + +static void cmd_led_gyro_all(void) +{ + if (cmd_buf.len != sizeof(struct cmd_led_gyro_all) + 3) + return; + led_gyro_all(ntohs(cmd_buf.led_gyro_all.intensity), + ntohs(cmd_buf.led_gyro_all.blink_on_ms), + ntohs(cmd_buf.led_gyro_all.blink_off_ms), + ntohs(cmd_buf.led_gyro_all.stop_ms)); +} + +static void cmd_led_gyro_ray(void) +{ + if (cmd_buf.len != sizeof(struct cmd_led_gyro_ray) + 3) + return; + led_gyro_ray( + ntohs(cmd_buf.led_gyro_ray.angle), + ntohs(cmd_buf.led_gyro_ray.intensity), + ntohs(cmd_buf.led_gyro_ray.width), + ntohs(cmd_buf.led_gyro_ray.rot_speed), + ntohs(cmd_buf.led_gyro_ray.stop_ms)); +} + +static void cmd_log_debug(void) +{ + if (cmd_buf.len != sizeof(struct cmd_log_debug) + 3) + return; + log_debug = 1; +} + +static void cmd_reset(void) +{ + wdt_enable(WDTO_15MS); + while(1); +} + +static void cmd_buzzer_vibrato(void) +{ + if (cmd_buf.len != sizeof(struct cmd_buzzer_vibrato) + 3) + return; + buzzer_vibrato(ntohs(cmd_buf.buzzer_vibrato.period1_us), + ntohs(cmd_buf.buzzer_vibrato.period2_us), + ntohs(cmd_buf.buzzer_vibrato.duration_ms), + ntohs(cmd_buf.buzzer_vibrato.vibrato_period_ms), + cmd_buf.buzzer_vibrato.vibrato_type); +} + +static void cmd_exec(void) +{ + if (cmd_csum != 0xff) { + clear_cmd(); + return; + } + + if (log_debug) + printf_P(PSTR("command %d received\n"), cmd_buf.cmd); + + switch (cmd_buf.cmd) { + case CMD_LED_DEBUG: cmd_led_debug(); break; + case CMD_BUZZER: cmd_buzzer(); break; + case CMD_LED_GYRO_ALL: cmd_led_gyro_all(); break; + case CMD_LED_GYRO_RAY: cmd_led_gyro_ray(); break; + case CMD_LOG_DEBUG: cmd_log_debug(); break; + case CMD_RESET: cmd_reset(); break; + case CMD_BUZZER_VIBRATO: cmd_buzzer_vibrato(); break; + default: break; + } + +} + +void cmd_input(uint8_t c) +{ + if (cmd_len >= sizeof(cmd_buf)) + clear_cmd(); + + if (cmd_len == 0) { + if (c >= CMD_MAX || c == CMD_INVALID) { + clear_cmd(); + return; + } + } else if (cmd_len == 1) { + if (c < 3 || c > sizeof(cmd_buf)) { + clear_cmd(); + return; + } + } + + ((uint8_t *)&cmd_buf)[cmd_len] = c; + cmd_len++; + cmd_csum = csum_add(cmd_csum, c); + + if (cmd_len >= 3 && cmd_len == cmd_buf.len) { + cmd_exec(); + clear_cmd(); + } +} diff --git a/src/commands.h b/src/commands.h new file mode 100644 index 0000000..b4d8b38 --- /dev/null +++ b/src/commands.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2019, Olivier MATZ + */ + +#ifndef _COMMANDS_H +#define _COMMANDS_H + +#include + +void cmd_input(uint8_t c); + +#endif diff --git a/src/errno.h b/src/errno.h new file mode 100644 index 0000000..5d77341 --- /dev/null +++ b/src/errno.h @@ -0,0 +1,147 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright (c) 1982, 1986, 1989, 1993 + * The Regents of the University of California. All rights reserved. + * (c) UNIX System Laboratories, Inc. + */ + +#ifndef ERRNO_H_ +#define ERRNO_H_ + +#define EPERM 1 /* Operation not permitted */ +#define ENOENT 2 /* No such file or directory */ +#define ESRCH 3 /* No such process */ +#define EINTR 4 /* Interrupted system call */ +#define EIO 5 /* Input/output error */ +#define ENXIO 6 /* Device not configured */ +#define E2BIG 7 /* Argument list too long */ +#define ENOEXEC 8 /* Exec format error */ +#define EBADF 9 /* Bad file descriptor */ +#define ECHILD 10 /* No child processes */ +#define EDEADLK 11 /* Resource deadlock avoided */ + /* 11 was EAGAIN */ +#define ENOMEM 12 /* Cannot allocate memory */ +#define EACCES 13 /* Permission denied */ +#define EFAULT 14 /* Bad address */ +#define ENOTBLK 15 /* Block device required */ +#define EBUSY 16 /* Device busy */ +#define EEXIST 17 /* File exists */ +#define EXDEV 18 /* Cross-device link */ +#define ENODEV 19 /* Operation not supported by device */ +#define ENOTDIR 20 /* Not a directory */ +#define EISDIR 21 /* Is a directory */ +#define EINVAL 22 /* Invalid argument */ +#define ENFILE 23 /* Too many open files in system */ +#define EMFILE 24 /* Too many open files */ +#define ENOTTY 25 /* Inappropriate ioctl for device */ +#define ETXTBSY 26 /* Text file busy */ +#define EFBIG 27 /* File too large */ +#define ENOSPC 28 /* No space left on device */ +#define ESPIPE 29 /* Illegal seek */ +#define EROFS 30 /* Read-only file system */ +#define EMLINK 31 /* Too many links */ +#define EPIPE 32 /* Broken pipe */ + +/* math software */ +#define EDOM 33 /* Numerical argument out of domain */ +#define ERANGE 34 /* Result too large or too small */ + +/* non-blocking and interrupt i/o */ +#define EAGAIN 35 /* Resource temporarily unavailable */ +#define EWOULDBLOCK EAGAIN /* Operation would block */ +#define EINPROGRESS 36 /* Operation now in progress */ +#define EALREADY 37 /* Operation already in progress */ + +/* ipc/network software -- argument errors */ +#define ENOTSOCK 38 /* Socket operation on non-socket */ +#define EDESTADDRREQ 39 /* Destination address required */ +#define EMSGSIZE 40 /* Message too long */ +#define EPROTOTYPE 41 /* Protocol wrong type for socket */ +#define ENOPROTOOPT 42 /* Protocol option not available */ +#define EPROTONOSUPPORT 43 /* Protocol not supported */ +#define ESOCKTNOSUPPORT 44 /* Socket type not supported */ +#define EOPNOTSUPP 45 /* Operation not supported */ +#define EPFNOSUPPORT 46 /* Protocol family not supported */ +#define EAFNOSUPPORT 47 /* Address family not supported by protocol family */ +#define EADDRINUSE 48 /* Address already in use */ +#define EADDRNOTAVAIL 49 /* Can't assign requested address */ + +/* ipc/network software -- operational errors */ +#define ENETDOWN 50 /* Network is down */ +#define ENETUNREACH 51 /* Network is unreachable */ +#define ENETRESET 52 /* Network dropped connection on reset */ +#define ECONNABORTED 53 /* Software caused connection abort */ +#define ECONNRESET 54 /* Connection reset by peer */ +#define ENOBUFS 55 /* No buffer space available */ +#define EISCONN 56 /* Socket is already connected */ +#define ENOTCONN 57 /* Socket is not connected */ +#define ESHUTDOWN 58 /* Can't send after socket shutdown */ +#define ETOOMANYREFS 59 /* Too many references: can't splice */ +#define ETIMEDOUT 60 /* Operation timed out */ +#define ECONNREFUSED 61 /* Connection refused */ + +#define ELOOP 62 /* Too many levels of symbolic links */ +#define ENAMETOOLONG 63 /* File name too long */ + +/* should be rearranged */ +#define EHOSTDOWN 64 /* Host is down */ +#define EHOSTUNREACH 65 /* No route to host */ +#define ENOTEMPTY 66 /* Directory not empty */ + +/* quotas & mush */ +#define EPROCLIM 67 /* Too many processes */ +#define EUSERS 68 /* Too many users */ +#define EDQUOT 69 /* Disc quota exceeded */ + +/* Network File System */ +#define ESTALE 70 /* Stale NFS file handle */ +#define EREMOTE 71 /* Too many levels of remote in path */ +#define EBADRPC 72 /* RPC struct is bad */ +#define ERPCMISMATCH 73 /* RPC version wrong */ +#define EPROGUNAVAIL 74 /* RPC prog. not avail */ +#define EPROGMISMATCH 75 /* Program version wrong */ +#define EPROCUNAVAIL 76 /* Bad procedure for program */ + +#define ENOLCK 77 /* No locks available */ +#define ENOSYS 78 /* Function not implemented */ + +#define EFTYPE 79 /* Inappropriate file type or format */ +#define EAUTH 80 /* Authentication error */ +#define ENEEDAUTH 81 /* Need authenticator */ + +/* SystemV IPC */ +#define EIDRM 82 /* Identifier removed */ +#define ENOMSG 83 /* No message of desired type */ +#define EOVERFLOW 84 /* Value too large to be stored in data type */ + +/* Wide/multibyte-character handling, ISO/IEC 9899/AMD1:1995 */ +#define EILSEQ 85 /* Illegal byte sequence */ + +/* From IEEE Std 1003.1-2001 */ +/* Base, Realtime, Threads or Thread Priority Scheduling option errors */ +#define ENOTSUP 86 /* Not supported */ + +/* Realtime option errors */ +#define ECANCELED 87 /* Operation canceled */ + +/* Realtime, XSI STREAMS option errors */ +#define EBADMSG 88 /* Bad or Corrupt message */ + +/* XSI STREAMS option errors */ +#define ENODATA 89 /* No message available */ +#define ENOSR 90 /* No STREAM resources */ +#define ENOSTR 91 /* Not a STREAM */ +#define ETIME 92 /* STREAM ioctl timeout */ + +/* File system extended attribute errors */ +#define ENOATTR 93 /* Attribute not found */ + +/* Realtime, XSI STREAMS option errors */ +#define EMULTIHOP 94 /* Multihop attempted */ +#define ENOLINK 95 /* Link has been severed */ +#define EPROTO 96 /* Protocol error */ + +#define ELAST 96 /* Must equal largest errno */ + +#include_next + +#endif /* !ERRNO_H_ */ diff --git a/src/irq.h b/src/irq.h new file mode 100644 index 0000000..72b2944 --- /dev/null +++ b/src/irq.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2015, Olivier MATZ + */ + +#ifndef IRQ_H_ +#define IRQ_H_ + +#include + +typedef uint8_t irqflags_t; + +static inline void irq_lock(void) +{ + cli(); +} + +static inline void irq_unlock(void) +{ + sei(); +} + +static inline irqflags_t irq_lock_save(void) +{ + irqflags_t flags; + + flags = SREG; + cli(); + return flags; +} + +static inline void irq_unlock_restore(irqflags_t flags) +{ + SREG = flags; +} + +static inline int irq_locked(void) +{ + return !(bit_is_set(SREG,7)); +} + +#endif /* IRQ_H_ */ diff --git a/src/led.c b/src/led.c new file mode 100644 index 0000000..85a79d5 --- /dev/null +++ b/src/led.c @@ -0,0 +1,339 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2019, Olivier MATZ + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "main.h" +#include "callout.h" +#include "led.h" + +#define GYRO_UPDATE_MS 10 +#define GYRO_N_LEDS 12 + +#define GYRO_TYPE_STATIC 0 +#define GYRO_TYPE_BLINK 1 +#define GYRO_TYPE_ROTATE 2 + +#define LED1642_PORT PORTD +#define LED1642_PIN PIND +#define LED1642_DDR DDRD +#define SDI_BIT 6 +#define SDO_BIT 2 +#define LE_BIT 3 +#define CLK_BIT 4 +#define PWCLK_BIT 5 + +#define LED1642_CMD_DATA_SWITCH 1 +#define LED1642_CMD_DATA_LATCH 3 +#define LED1642_CMD_GLOBAL_LATCH 5 +#define LED1642_CMD_WRITE_CONFIG 7 +#define LED1642_CMD_READ_CONFIG 8 + + +#define LED1642_CFG_CURRENT 0 /* 6 bits */ +#define LED1642_CFG_CURRENT_RANGE 6 +#define LED1642_CFG_ERROR_DETECT 7 +#define LED1642_CFG_SHORTED_LEDS 8 /* 2 bits */ +#define LED1642_CFG_AUTO_OFF 10 +#define LED1642_CFG_TURN_ON_TIME 11 /* 2 bits */ +#define LED1642_CFG_SDO_DELAY 13 +#define LED1642_CFG_OUT_DELAY 14 +#define LED1642_CFG_PWM12 15 + +struct gyro_state { + uint8_t type; /* none, blink, rotate */ + uint16_t ms; + uint16_t stop_ms; + + union { + struct { + uint16_t period_ms; + uint16_t intensity; + uint16_t on_ms; + uint16_t off_ms; + } blink; + struct { + uint16_t angle; + uint16_t intensity; + uint16_t width; + int16_t speed; + } rot; + }; +}; + +/* LEDs status. Intensity is stored within 12 bits. */ +static uint16_t gyro_leds[GYRO_N_LEDS]; +static struct gyro_state gyro_state; +static struct callout gyro_timer; + +void __led_gyro_ray(uint16_t angle, uint16_t intensity, uint16_t width) +{ +#define LED_WIDTH ((uint16_t)(65536 / GYRO_N_LEDS)) +#define LED_HALF ((uint16_t)(65536 / (GYRO_N_LEDS * 2))) + + uint32_t ray_half; + uint16_t led_angle; + int16_t diff; + uint8_t i; + + ray_half = width / 2; + + for (i = 0; i < GYRO_N_LEDS; i++) { + gyro_leds[i] = 0; + + led_angle = (uint16_t)(((uint32_t)i * 65536) / GYRO_N_LEDS); + + /* get angle diff */ + diff = led_angle - angle; /* modulo 16 bits */ + if (diff == -32768) + diff = 32767; + else if (diff < 0) + diff = -diff; + + /* calculate led intensity */ + if ((uint16_t)diff >= ray_half + LED_HALF) + gyro_leds[i] = 0; + else if (diff <= (int32_t)(ray_half - LED_HALF)) + gyro_leds[i] = intensity; + else + gyro_leds[i] = ((int32_t)(ray_half + LED_HALF - diff) * + intensity) / LED_WIDTH; + } +} + +static void +led1642_write(uint8_t cmd, uint16_t data) +{ + uint8_t i; + + LED1642_PORT = 0; + for (i = 0; i < 16; i++, data <<= 1) { + if (data & 0x8000) + LED1642_PORT |= (1 << SDI_BIT); + else + LED1642_PORT &= ~(1 << SDI_BIT); + if (i + cmd >= 16) + LED1642_PORT |= (1 << LE_BIT); + + LED1642_PORT |= (1 << CLK_BIT); + LED1642_PORT &= ~(1 << CLK_BIT); + } + LED1642_PORT = 0; +} + +static uint16_t +led1642_read(uint8_t cmd) +{ + uint8_t i; + uint16_t data = 0; + + led1642_write(cmd, 0); + for (i = 0; i < 16; i++) { + LED1642_PORT |= (1 << CLK_BIT); + LED1642_PORT &= ~(1 << CLK_BIT); + data <<= 1; + if (LED1642_PIN & (1 << SDO_BIT)) + data |= 1; + } + LED1642_PORT = 0; + return data; +} + +/* send gyro_leds[] to led1642 */ +static void +gyro_update_leds(void) +{ + uint16_t data, cmd; + uint8_t i; + + for (i = 0; i < 16; i++) { + if (i >= 4) + data = (gyro_leds[i - 4]) >> 4; + else + data = 0; + if (i != 15) + cmd = LED1642_CMD_DATA_LATCH; + else + cmd = LED1642_CMD_GLOBAL_LATCH; + + led1642_write(cmd, data); + } +} + +void led_gyro_off(void) +{ + callout_stop(&callout_mgr, &gyro_timer); + memset(gyro_leds, 0, sizeof(gyro_leds)); + gyro_update_leds(); +} + +static void gyro_update_cb(struct callout_mgr *cm, + struct callout *tim, void *arg) +{ + uint16_t intensity; + uint8_t i, update = 0; + + (void)arg; + + gyro_state.ms += GYRO_UPDATE_MS; + if (gyro_state.ms >= gyro_state.stop_ms) { + led_gyro_off(); + return; + } + + switch (gyro_state.type) { + case GYRO_TYPE_STATIC: + break; + case GYRO_TYPE_BLINK: + gyro_state.blink.period_ms += GYRO_UPDATE_MS; + if (gyro_state.blink.period_ms > gyro_state.blink.on_ms + + gyro_state.blink.off_ms) { + intensity = gyro_state.blink.intensity; + gyro_state.blink.period_ms -= gyro_state.blink.on_ms + + gyro_state.blink.off_ms; + update = 1; + } else if (gyro_state.blink.period_ms > gyro_state.blink.on_ms) { + intensity = 0; + update = 1; + } + if (update) { + for (i = 0; i < GYRO_N_LEDS; i++) + gyro_leds[i] = intensity; + } + break; + case GYRO_TYPE_ROTATE: + gyro_state.rot.angle += gyro_state.rot.speed; + __led_gyro_ray(gyro_state.rot.angle, gyro_state.rot.intensity, + gyro_state.rot.width); + update = 1; + break; + default: + break; + } + + if (update) + gyro_update_leds(); + + callout_reschedule(cm, tim, GYRO_UPDATE_MS); +} + +void led_debug_init(void) +{ + DDRB |= (1 << 6); /* debug led as output */ +} + +void led_debug_on(void) +{ + PORTB |= (1 << 6); +} + +void led_debug_off(void) +{ + PORTB &= (~(1 << 6)); +} + +void led_debug_toggle(void) +{ + PORTB ^= (1 << 6); +} + +void led_gyro_all(uint16_t intensity, uint16_t blink_on_ms, + uint16_t blink_off_ms, uint16_t stop_ms) +{ + uint8_t i; + + callout_stop(&callout_mgr, &gyro_timer); + printf_P(PSTR("%u %u %u %u\n"), + intensity, blink_on_ms, blink_off_ms, stop_ms); + for (i = 0; i < GYRO_N_LEDS; i++) + gyro_leds[i] = intensity; + gyro_update_leds(); + + if (!blink_on_ms || !blink_off_ms) { + gyro_state.type = GYRO_TYPE_STATIC; + return; + } + + gyro_state.type = GYRO_TYPE_BLINK; + gyro_state.ms = 0; + gyro_state.stop_ms = stop_ms; + gyro_state.blink.on_ms = blink_on_ms; + gyro_state.blink.off_ms = blink_off_ms; + gyro_state.blink.intensity = intensity; + gyro_state.blink.period_ms = 0; + callout_schedule(&callout_mgr, &gyro_timer, GYRO_UPDATE_MS); +} + +void led_gyro_ray(uint16_t angle, uint16_t intensity, uint16_t width, + int16_t rot_speed, uint16_t stop_ms) +{ + callout_stop(&callout_mgr, &gyro_timer); + + printf_P(PSTR("led_gyro_ray %u %u %u %u %u\n"), + angle, intensity, width, rot_speed, stop_ms); + __led_gyro_ray(angle, intensity, width); + gyro_update_leds(); + + gyro_state.ms = 0; + gyro_state.stop_ms = stop_ms; + if (rot_speed == 0) { + gyro_state.type = GYRO_TYPE_STATIC; + } else { + gyro_state.type = GYRO_TYPE_ROTATE; + gyro_state.rot.angle = angle; + gyro_state.rot.speed = rot_speed; + gyro_state.rot.intensity = intensity; + gyro_state.rot.width = width; + } + callout_schedule(&callout_mgr, &gyro_timer, GYRO_UPDATE_MS); +} + +void +led1642_init(void) +{ + /* 0 = 9.8mA, 31 = 39.3mA (with current range = 1)*/ + const uint16_t current = 0; + const uint16_t config = ( + (current << LED1642_CFG_CURRENT) | + (1U << LED1642_CFG_CURRENT_RANGE) | + (1U << LED1642_CFG_OUT_DELAY) | + (1U << LED1642_CFG_PWM12)); + uint16_t data; + + LED1642_DDR |= (1 << SDI_BIT) | (1 << LE_BIT) | (1 << CLK_BIT) | + (1 << PWCLK_BIT); + + /* Fast PWM, reset timer at OCRA, set OC0B at bottom, clear + * OC0B at OCR0B */ + TCCR0A = (1 << COM0B1) | (1 << WGM01) | (1 << WGM00); + TCCR0B = (1 << WGM02) | (1 << CS00); /* div = 1 */ + OCR0B = 2; + OCR0A = 4; + + LED1642_PORT = 0; + + led1642_write(LED1642_CMD_DATA_SWITCH, 0); + + led1642_write(LED1642_CMD_WRITE_CONFIG, config); + data = led1642_read(LED1642_CMD_READ_CONFIG); + if (data != config) + printf_P(PSTR("data (0x%x) != config (0x%x)\n"), + data, config); + + led1642_write(LED1642_CMD_DATA_SWITCH, 0xffff); + + _delay_ms(1000); + + led_gyro_off(); + + /* timer that updates gyro LEDs */ + callout_init(&gyro_timer, gyro_update_cb, NULL, 128); +} diff --git a/src/led.h b/src/led.h new file mode 100644 index 0000000..764a902 --- /dev/null +++ b/src/led.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2019, Olivier MATZ + */ + +#ifndef _LED_H +#define _LED_H + +void led_debug_init(void); +void led1642_init(void); + +void led_debug_on(void); +void led_debug_off(void); +void led_debug_toggle(void); + +/** + * Control all leds at the same time with specified intensity. + * + * @param intensity + * Led intensity between 0 and 4095 + * @param blink_on_ms + * Time during which leds are on when blinking. If 0, don't blink. + * @param blink_off_ms + * Time during which leds are off when blinking. If 0, don't blink. + * Note that on + off times must be < 65536. + * @param stop_ms + * Time after which all the leds are switched off. If 0, never stop. + */ +void led_gyro_all(uint16_t intensity, uint16_t blink_on_ms, + uint16_t blink_off_ms, uint16_t stop_ms); + +/** + * Switch on the leds at specified intensity in one direction. + * + * @param angle + * Between 0 and 65535. + * @param intensity + * Led intensity, between 0 and 4095. + * @param width + * The width represents the angle of the enabled zone, between 0 + * and 65535 (one led = 6553). + * @param rot_speed + * The rotation speed is angle per 10ms (so 655 is ~1 round/sec). + * @param stop_ms + * Time after which all the leds are switched off. If 0, never stop. + */ +void led_gyro_ray(uint16_t angle, uint16_t intensity, uint16_t width, + int16_t rot_speed, uint16_t stop_ms); + +#endif diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..0012835 --- /dev/null +++ b/src/main.c @@ -0,0 +1,83 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2019, Olivier MATZ + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include "irq.h" +#include "uart.h" +#include "callout.h" +#include "commands.h" +#include "led.h" +#include "rtc.h" +#include "buzzer.h" + +struct callout_mgr callout_mgr; +uint8_t log_debug = 0; + +/* disable watchdog early at init */ +__attribute__((naked)) __attribute__((section(".init3"))) +void wdt_init(void) +{ + MCUSR = 0; + wdt_disable(); +} + +/* called every ms to manage callouts */ +static void callout_cb(void) +{ + callout_manage(&callout_mgr); +} + +int main(void) +{ + uint8_t i; + char c; + + /* LEDs as output pin */ + led_debug_init(); + + /* toggle the pin */ + for (i = 0; i < 3; i++) { + led_debug_on(); + _delay_ms(100); + led_debug_off(); + _delay_ms(100); + } + + /* uart and stdout initialization */ + uart_init(); + + /* timer interrupt and callout manager */ + callout_mgr_init(&callout_mgr, rtc_get_ms_u16); + rtc_init(&callout_cb); + + /* LEDs as output pin */ + led1642_init(); + + /* timer 1 and buzzer */ + buzzer_init(); + + /* enable interrupts globally */ + irq_unlock(); + + //buzzer(100, 100); + //buzzer_vibrato(800, 600, 10000, 800, VIBRATO_TYPE_TRIANGLE); + + printf_P(PSTR("ready\r\n")); + + /* mainloop */ + while (1) { + if (uart_recv(&c) == 0) + cmd_input(c); + } + + return 0; +} diff --git a/src/main.h b/src/main.h new file mode 100644 index 0000000..4ffb195 --- /dev/null +++ b/src/main.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2019, Olivier MATZ + */ + +#ifndef MAIN_H_ +#define MAIN_H_ + +#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)])) + +extern struct callout_mgr callout_mgr; +extern uint8_t log_debug; + +#endif diff --git a/src/queue.h b/src/queue.h new file mode 100644 index 0000000..00dab04 --- /dev/null +++ b/src/queue.h @@ -0,0 +1,844 @@ +/* + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)queue.h 8.5 (Berkeley) 8/20/94 + */ + +#ifndef _SYS_QUEUE_H_ +#define _SYS_QUEUE_H_ + +/* + * This file defines five types of data structures: singly-linked lists, + * lists, simple queues, tail queues, and circular queues. + * + * A singly-linked list is headed by a single forward pointer. The + * elements are singly linked for minimum space and pointer manipulation + * overhead at the expense of O(n) removal for arbitrary elements. New + * elements can be added to the list after an existing element or at the + * head of the list. Elements being removed from the head of the list + * should use the explicit macro for this purpose for optimum + * efficiency. A singly-linked list may only be traversed in the forward + * direction. Singly-linked lists are ideal for applications with large + * datasets and few or no removals or for implementing a LIFO queue. + * + * A list is headed by a single forward pointer (or an array of forward + * pointers for a hash table header). The elements are doubly linked + * so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before + * or after an existing element or at the head of the list. A list + * may only be traversed in the forward direction. + * + * A simple queue is headed by a pair of pointers, one the head of the + * list and the other to the tail of the list. The elements are singly + * linked to save space, so elements can only be removed from the + * head of the list. New elements can be added to the list after + * an existing element, at the head of the list, or at the end of the + * list. A simple queue may only be traversed in the forward direction. + * + * A tail queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or + * after an existing element, at the head of the list, or at the end of + * the list. A tail queue may be traversed in either direction. + * + * A circle queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or after + * an existing element, at the head of the list, or at the end of the list. + * A circle queue may be traversed in either direction, but has a more + * complex end of list detection. + * + * For details on the use of these macros, see the queue(3) manual page. + */ + +/* + * Include the definition of NULL only on NetBSD because sys/null.h + * is not available elsewhere. This conditional makes the header + * portable and it can simply be dropped verbatim into any system. + * The caveat is that on other systems some other header + * must provide NULL before the macros can be used. + */ +#ifdef __NetBSD__ +#include +#endif + +#if defined(QUEUEDEBUG) +# if defined(_KERNEL) +# define QUEUEDEBUG_ABORT(...) panic(__VA_ARGS__) +# else +# include +# define QUEUEDEBUG_ABORT(...) err(1, __VA_ARGS__) +# endif +#endif + +/* + * Singly-linked List definitions. + */ +#define SLIST_HEAD(name, type) \ +struct name { \ + struct type *slh_first; /* first element */ \ +} + +#define SLIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define SLIST_ENTRY(type) \ +struct { \ + struct type *sle_next; /* next element */ \ +} + +/* + * Singly-linked List access methods. + */ +#define SLIST_FIRST(head) ((head)->slh_first) +#define SLIST_END(head) NULL +#define SLIST_EMPTY(head) ((head)->slh_first == NULL) +#define SLIST_NEXT(elm, field) ((elm)->field.sle_next) + +#define SLIST_FOREACH(var, head, field) \ + for((var) = (head)->slh_first; \ + (var) != SLIST_END(head); \ + (var) = (var)->field.sle_next) + +#define SLIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = SLIST_FIRST((head)); \ + (var) != SLIST_END(head) && \ + ((tvar) = SLIST_NEXT((var), field), 1); \ + (var) = (tvar)) + +/* + * Singly-linked List functions. + */ +#define SLIST_INIT(head) do { \ + (head)->slh_first = SLIST_END(head); \ +} while (/*CONSTCOND*/0) + +#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ + (elm)->field.sle_next = (slistelm)->field.sle_next; \ + (slistelm)->field.sle_next = (elm); \ +} while (/*CONSTCOND*/0) + +#define SLIST_INSERT_HEAD(head, elm, field) do { \ + (elm)->field.sle_next = (head)->slh_first; \ + (head)->slh_first = (elm); \ +} while (/*CONSTCOND*/0) + +#define SLIST_REMOVE_AFTER(slistelm, field) do { \ + (slistelm)->field.sle_next = \ + SLIST_NEXT(SLIST_NEXT((slistelm), field), field); \ +} while (/*CONSTCOND*/0) + +#define SLIST_REMOVE_HEAD(head, field) do { \ + (head)->slh_first = (head)->slh_first->field.sle_next; \ +} while (/*CONSTCOND*/0) + +#define SLIST_REMOVE(head, elm, type, field) do { \ + if ((head)->slh_first == (elm)) { \ + SLIST_REMOVE_HEAD((head), field); \ + } \ + else { \ + struct type *curelm = (head)->slh_first; \ + while(curelm->field.sle_next != (elm)) \ + curelm = curelm->field.sle_next; \ + curelm->field.sle_next = \ + curelm->field.sle_next->field.sle_next; \ + } \ +} while (/*CONSTCOND*/0) + + +/* + * List definitions. + */ +#define LIST_HEAD(name, type) \ +struct name { \ + struct type *lh_first; /* first element */ \ +} + +#define LIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define LIST_ENTRY(type) \ +struct { \ + struct type *le_next; /* next element */ \ + struct type **le_prev; /* address of previous next element */ \ +} + +/* + * List access methods. + */ +#define LIST_FIRST(head) ((head)->lh_first) +#define LIST_END(head) NULL +#define LIST_EMPTY(head) ((head)->lh_first == LIST_END(head)) +#define LIST_NEXT(elm, field) ((elm)->field.le_next) + +#define LIST_FOREACH(var, head, field) \ + for ((var) = ((head)->lh_first); \ + (var) != LIST_END(head); \ + (var) = ((var)->field.le_next)) + +#define LIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = LIST_FIRST((head)); \ + (var) != LIST_END(head) && \ + ((tvar) = LIST_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define LIST_MOVE(head1, head2) do { \ + LIST_INIT((head2)); \ + if (!LIST_EMPTY((head1))) { \ + (head2)->lh_first = (head1)->lh_first; \ + LIST_INIT((head1)); \ + } \ +} while (/*CONSTCOND*/0) + +/* + * List functions. + */ +#if defined(QUEUEDEBUG) +#define QUEUEDEBUG_LIST_INSERT_HEAD(head, elm, field) \ + if ((head)->lh_first && \ + (head)->lh_first->field.le_prev != &(head)->lh_first) \ + QUEUEDEBUG_ABORT("LIST_INSERT_HEAD %p %s:%d", (head), \ + __FILE__, __LINE__); +#define QUEUEDEBUG_LIST_OP(elm, field) \ + if ((elm)->field.le_next && \ + (elm)->field.le_next->field.le_prev != \ + &(elm)->field.le_next) \ + QUEUEDEBUG_ABORT("LIST_* forw %p %s:%d", (elm), \ + __FILE__, __LINE__); \ + if (*(elm)->field.le_prev != (elm)) \ + QUEUEDEBUG_ABORT("LIST_* back %p %s:%d", (elm), \ + __FILE__, __LINE__); +#define QUEUEDEBUG_LIST_POSTREMOVE(elm, field) \ + (elm)->field.le_next = (void *)1L; \ + (elm)->field.le_prev = (void *)1L; +#else +#define QUEUEDEBUG_LIST_INSERT_HEAD(head, elm, field) +#define QUEUEDEBUG_LIST_OP(elm, field) +#define QUEUEDEBUG_LIST_POSTREMOVE(elm, field) +#endif + +#define LIST_INIT(head) do { \ + (head)->lh_first = LIST_END(head); \ +} while (/*CONSTCOND*/0) + +#define LIST_INSERT_AFTER(listelm, elm, field) do { \ + QUEUEDEBUG_LIST_OP((listelm), field) \ + if (((elm)->field.le_next = (listelm)->field.le_next) != \ + LIST_END(head)) \ + (listelm)->field.le_next->field.le_prev = \ + &(elm)->field.le_next; \ + (listelm)->field.le_next = (elm); \ + (elm)->field.le_prev = &(listelm)->field.le_next; \ +} while (/*CONSTCOND*/0) + +#define LIST_INSERT_BEFORE(listelm, elm, field) do { \ + QUEUEDEBUG_LIST_OP((listelm), field) \ + (elm)->field.le_prev = (listelm)->field.le_prev; \ + (elm)->field.le_next = (listelm); \ + *(listelm)->field.le_prev = (elm); \ + (listelm)->field.le_prev = &(elm)->field.le_next; \ +} while (/*CONSTCOND*/0) + +#define LIST_INSERT_HEAD(head, elm, field) do { \ + QUEUEDEBUG_LIST_INSERT_HEAD((head), (elm), field) \ + if (((elm)->field.le_next = (head)->lh_first) != LIST_END(head))\ + (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ + (head)->lh_first = (elm); \ + (elm)->field.le_prev = &(head)->lh_first; \ +} while (/*CONSTCOND*/0) + +#define LIST_REMOVE(elm, field) do { \ + QUEUEDEBUG_LIST_OP((elm), field) \ + if ((elm)->field.le_next != NULL) \ + (elm)->field.le_next->field.le_prev = \ + (elm)->field.le_prev; \ + *(elm)->field.le_prev = (elm)->field.le_next; \ + QUEUEDEBUG_LIST_POSTREMOVE((elm), field) \ +} while (/*CONSTCOND*/0) + +#define LIST_REPLACE(elm, elm2, field) do { \ + if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \ + (elm2)->field.le_next->field.le_prev = \ + &(elm2)->field.le_next; \ + (elm2)->field.le_prev = (elm)->field.le_prev; \ + *(elm2)->field.le_prev = (elm2); \ + QUEUEDEBUG_LIST_POSTREMOVE((elm), field) \ +} while (/*CONSTCOND*/0) + +/* + * Simple queue definitions. + */ +#define SIMPLEQ_HEAD(name, type) \ +struct name { \ + struct type *sqh_first; /* first element */ \ + struct type **sqh_last; /* addr of last next element */ \ +} + +#define SIMPLEQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).sqh_first } + +#define SIMPLEQ_ENTRY(type) \ +struct { \ + struct type *sqe_next; /* next element */ \ +} + +/* + * Simple queue access methods. + */ +#define SIMPLEQ_FIRST(head) ((head)->sqh_first) +#define SIMPLEQ_END(head) NULL +#define SIMPLEQ_EMPTY(head) ((head)->sqh_first == SIMPLEQ_END(head)) +#define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next) + +#define SIMPLEQ_FOREACH(var, head, field) \ + for ((var) = ((head)->sqh_first); \ + (var) != SIMPLEQ_END(head); \ + (var) = ((var)->field.sqe_next)) + +#define SIMPLEQ_FOREACH_SAFE(var, head, field, next) \ + for ((var) = ((head)->sqh_first); \ + (var) != SIMPLEQ_END(head) && \ + ((next = ((var)->field.sqe_next)), 1); \ + (var) = (next)) + +/* + * Simple queue functions. + */ +#define SIMPLEQ_INIT(head) do { \ + (head)->sqh_first = NULL; \ + (head)->sqh_last = &(head)->sqh_first; \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (head)->sqh_first = (elm); \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.sqe_next = NULL; \ + *(head)->sqh_last = (elm); \ + (head)->sqh_last = &(elm)->field.sqe_next; \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (listelm)->field.sqe_next = (elm); \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_REMOVE_HEAD(head, field) do { \ + if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \ + (head)->sqh_last = &(head)->sqh_first; \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_REMOVE_AFTER(head, elm, field) do { \ + if (((elm)->field.sqe_next = (elm)->field.sqe_next->field.sqe_next) \ + == NULL) \ + (head)->sqh_last = &(elm)->field.sqe_next; \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_REMOVE(head, elm, type, field) do { \ + if ((head)->sqh_first == (elm)) { \ + SIMPLEQ_REMOVE_HEAD((head), field); \ + } else { \ + struct type *curelm = (head)->sqh_first; \ + while (curelm->field.sqe_next != (elm)) \ + curelm = curelm->field.sqe_next; \ + if ((curelm->field.sqe_next = \ + curelm->field.sqe_next->field.sqe_next) == NULL) \ + (head)->sqh_last = &(curelm)->field.sqe_next; \ + } \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_CONCAT(head1, head2) do { \ + if (!SIMPLEQ_EMPTY((head2))) { \ + *(head1)->sqh_last = (head2)->sqh_first; \ + (head1)->sqh_last = (head2)->sqh_last; \ + SIMPLEQ_INIT((head2)); \ + } \ +} while (/*CONSTCOND*/0) + +#define SIMPLEQ_LAST(head, type, field) \ + (SIMPLEQ_EMPTY((head)) ? \ + NULL : \ + ((struct type *)(void *) \ + ((char *)((head)->sqh_last) - offsetof(struct type, field)))) + +/* + * Tail queue definitions. + */ +#define _TAILQ_HEAD(name, type, qual) \ +struct name { \ + qual type *tqh_first; /* first element */ \ + qual type *qual *tqh_last; /* addr of last next element */ \ +} +#define TAILQ_HEAD(name, type) _TAILQ_HEAD(name, struct type,) + +#define TAILQ_HEAD_INITIALIZER(head) \ + { TAILQ_END(head), &(head).tqh_first } + +#define _TAILQ_ENTRY(type, qual) \ +struct { \ + qual type *tqe_next; /* next element */ \ + qual type *qual *tqe_prev; /* address of previous next element */\ +} +#define TAILQ_ENTRY(type) _TAILQ_ENTRY(struct type,) + +/* + * Tail queue access methods. + */ +#define TAILQ_FIRST(head) ((head)->tqh_first) +#define TAILQ_END(head) (NULL) +#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) +#define TAILQ_LAST(head, headname) \ + (*(((struct headname *)((head)->tqh_last))->tqh_last)) +#define TAILQ_PREV(elm, headname, field) \ + (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) +#define TAILQ_EMPTY(head) (TAILQ_FIRST(head) == TAILQ_END(head)) + + +#define TAILQ_FOREACH(var, head, field) \ + for ((var) = ((head)->tqh_first); \ + (var) != TAILQ_END(head); \ + (var) = ((var)->field.tqe_next)) + +#define TAILQ_FOREACH_SAFE(var, head, field, next) \ + for ((var) = ((head)->tqh_first); \ + (var) != TAILQ_END(head) && \ + ((next) = TAILQ_NEXT(var, field), 1); (var) = (next)) + +#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ + for ((var) = (*(((struct headname *)((head)->tqh_last))->tqh_last));\ + (var) != TAILQ_END(head); \ + (var) = (*(((struct headname *)((var)->field.tqe_prev))->tqh_last))) + +#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, prev) \ + for ((var) = TAILQ_LAST((head), headname); \ + (var) != TAILQ_END(head) && \ + ((prev) = TAILQ_PREV((var), headname, field), 1); (var) = (prev)) + +/* + * Tail queue functions. + */ +#if defined(QUEUEDEBUG) +#define QUEUEDEBUG_TAILQ_INSERT_HEAD(head, elm, field) \ + if ((head)->tqh_first && \ + (head)->tqh_first->field.tqe_prev != &(head)->tqh_first) \ + QUEUEDEBUG_ABORT("TAILQ_INSERT_HEAD %p %s:%d", (head), \ + __FILE__, __LINE__); +#define QUEUEDEBUG_TAILQ_INSERT_TAIL(head, elm, field) \ + if (*(head)->tqh_last != NULL) \ + QUEUEDEBUG_ABORT("TAILQ_INSERT_TAIL %p %s:%d", (head), \ + __FILE__, __LINE__); +#define QUEUEDEBUG_TAILQ_OP(elm, field) \ + if ((elm)->field.tqe_next && \ + (elm)->field.tqe_next->field.tqe_prev != \ + &(elm)->field.tqe_next) \ + QUEUEDEBUG_ABORT("TAILQ_* forw %p %s:%d", (elm), \ + __FILE__, __LINE__); \ + if (*(elm)->field.tqe_prev != (elm)) \ + QUEUEDEBUG_ABORT("TAILQ_* back %p %s:%d", (elm), \ + __FILE__, __LINE__); +#define QUEUEDEBUG_TAILQ_PREREMOVE(head, elm, field) \ + if ((elm)->field.tqe_next == NULL && \ + (head)->tqh_last != &(elm)->field.tqe_next) \ + QUEUEDEBUG_ABORT("TAILQ_PREREMOVE head %p elm %p %s:%d",\ + (head), (elm), __FILE__, __LINE__); +#define QUEUEDEBUG_TAILQ_POSTREMOVE(elm, field) \ + (elm)->field.tqe_next = (void *)1L; \ + (elm)->field.tqe_prev = (void *)1L; +#else +#define QUEUEDEBUG_TAILQ_INSERT_HEAD(head, elm, field) +#define QUEUEDEBUG_TAILQ_INSERT_TAIL(head, elm, field) +#define QUEUEDEBUG_TAILQ_OP(elm, field) +#define QUEUEDEBUG_TAILQ_PREREMOVE(head, elm, field) +#define QUEUEDEBUG_TAILQ_POSTREMOVE(elm, field) +#endif + +#define TAILQ_INIT(head) do { \ + (head)->tqh_first = TAILQ_END(head); \ + (head)->tqh_last = &(head)->tqh_first; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_INSERT_HEAD(head, elm, field) do { \ + QUEUEDEBUG_TAILQ_INSERT_HEAD((head), (elm), field) \ + if (((elm)->field.tqe_next = (head)->tqh_first) != TAILQ_END(head))\ + (head)->tqh_first->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (head)->tqh_first = (elm); \ + (elm)->field.tqe_prev = &(head)->tqh_first; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_INSERT_TAIL(head, elm, field) do { \ + QUEUEDEBUG_TAILQ_INSERT_TAIL((head), (elm), field) \ + (elm)->field.tqe_next = TAILQ_END(head); \ + (elm)->field.tqe_prev = (head)->tqh_last; \ + *(head)->tqh_last = (elm); \ + (head)->tqh_last = &(elm)->field.tqe_next; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ + QUEUEDEBUG_TAILQ_OP((listelm), field) \ + if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != \ + TAILQ_END(head)) \ + (elm)->field.tqe_next->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (listelm)->field.tqe_next = (elm); \ + (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ + QUEUEDEBUG_TAILQ_OP((listelm), field) \ + (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ + (elm)->field.tqe_next = (listelm); \ + *(listelm)->field.tqe_prev = (elm); \ + (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ +} while (/*CONSTCOND*/0) + +#define TAILQ_REMOVE(head, elm, field) do { \ + QUEUEDEBUG_TAILQ_PREREMOVE((head), (elm), field) \ + QUEUEDEBUG_TAILQ_OP((elm), field) \ + if (((elm)->field.tqe_next) != TAILQ_END(head)) \ + (elm)->field.tqe_next->field.tqe_prev = \ + (elm)->field.tqe_prev; \ + else \ + (head)->tqh_last = (elm)->field.tqe_prev; \ + *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ + QUEUEDEBUG_TAILQ_POSTREMOVE((elm), field); \ +} while (/*CONSTCOND*/0) + +#define TAILQ_REPLACE(head, elm, elm2, field) do { \ + if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != \ + TAILQ_END(head)) \ + (elm2)->field.tqe_next->field.tqe_prev = \ + &(elm2)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm2)->field.tqe_next; \ + (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \ + *(elm2)->field.tqe_prev = (elm2); \ + QUEUEDEBUG_TAILQ_POSTREMOVE((elm), field); \ +} while (/*CONSTCOND*/0) + +#define TAILQ_CONCAT(head1, head2, field) do { \ + if (!TAILQ_EMPTY(head2)) { \ + *(head1)->tqh_last = (head2)->tqh_first; \ + (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \ + (head1)->tqh_last = (head2)->tqh_last; \ + TAILQ_INIT((head2)); \ + } \ +} while (/*CONSTCOND*/0) + +/* + * Singly-linked Tail queue declarations. + */ +#define STAILQ_HEAD(name, type) \ +struct name { \ + struct type *stqh_first; /* first element */ \ + struct type **stqh_last; /* addr of last next element */ \ +} + +#define STAILQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).stqh_first } + +#define STAILQ_ENTRY(type) \ +struct { \ + struct type *stqe_next; /* next element */ \ +} + +/* + * Singly-linked Tail queue access methods. + */ +#define STAILQ_FIRST(head) ((head)->stqh_first) +#define STAILQ_END(head) NULL +#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next) +#define STAILQ_EMPTY(head) (STAILQ_FIRST(head) == STAILQ_END(head)) + +/* + * Singly-linked Tail queue functions. + */ +#define STAILQ_INIT(head) do { \ + (head)->stqh_first = NULL; \ + (head)->stqh_last = &(head)->stqh_first; \ +} while (/*CONSTCOND*/0) + +#define STAILQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.stqe_next = (head)->stqh_first) == NULL) \ + (head)->stqh_last = &(elm)->field.stqe_next; \ + (head)->stqh_first = (elm); \ +} while (/*CONSTCOND*/0) + +#define STAILQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.stqe_next = NULL; \ + *(head)->stqh_last = (elm); \ + (head)->stqh_last = &(elm)->field.stqe_next; \ +} while (/*CONSTCOND*/0) + +#define STAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.stqe_next = (listelm)->field.stqe_next) == NULL)\ + (head)->stqh_last = &(elm)->field.stqe_next; \ + (listelm)->field.stqe_next = (elm); \ +} while (/*CONSTCOND*/0) + +#define STAILQ_REMOVE_HEAD(head, field) do { \ + if (((head)->stqh_first = (head)->stqh_first->field.stqe_next) == NULL) \ + (head)->stqh_last = &(head)->stqh_first; \ +} while (/*CONSTCOND*/0) + +#define STAILQ_REMOVE(head, elm, type, field) do { \ + if ((head)->stqh_first == (elm)) { \ + STAILQ_REMOVE_HEAD((head), field); \ + } else { \ + struct type *curelm = (head)->stqh_first; \ + while (curelm->field.stqe_next != (elm)) \ + curelm = curelm->field.stqe_next; \ + if ((curelm->field.stqe_next = \ + curelm->field.stqe_next->field.stqe_next) == NULL) \ + (head)->stqh_last = &(curelm)->field.stqe_next; \ + } \ +} while (/*CONSTCOND*/0) + +#define STAILQ_FOREACH(var, head, field) \ + for ((var) = ((head)->stqh_first); \ + (var); \ + (var) = ((var)->field.stqe_next)) + +#define STAILQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = STAILQ_FIRST((head)); \ + (var) && ((tvar) = STAILQ_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define STAILQ_CONCAT(head1, head2) do { \ + if (!STAILQ_EMPTY((head2))) { \ + *(head1)->stqh_last = (head2)->stqh_first; \ + (head1)->stqh_last = (head2)->stqh_last; \ + STAILQ_INIT((head2)); \ + } \ +} while (/*CONSTCOND*/0) + +#define STAILQ_LAST(head, type, field) \ + (STAILQ_EMPTY((head)) ? \ + NULL : \ + ((struct type *)(void *) \ + ((char *)((head)->stqh_last) - offsetof(struct type, field)))) + + +#ifndef _KERNEL +/* + * Circular queue definitions. Do not use. We still keep the macros + * for compatibility but because of pointer aliasing issues their use + * is discouraged! + */ + +/* + * __launder_type(): We use this ugly hack to work around the the compiler + * noticing that two types may not alias each other and elide tests in code. + * We hit this in the CIRCLEQ macros when comparing 'struct name *' and + * 'struct type *' (see CIRCLEQ_HEAD()). Modern compilers (such as GCC + * 4.8) declare these comparisons as always false, causing the code to + * not run as designed. + * + * This hack is only to be used for comparisons and thus can be fully const. + * Do not use for assignment. + * + * If we ever choose to change the ABI of the CIRCLEQ macros, we could fix + * this by changing the head/tail sentinal values, but see the note above + * this one. + */ +static __inline const void * __launder_type(const void *); +static __inline const void * +__launder_type(const void *__x) +{ + __asm __volatile("" : "+r" (__x)); + return __x; +} + +#if defined(QUEUEDEBUG) +#define QUEUEDEBUG_CIRCLEQ_HEAD(head, field) \ + if ((head)->cqh_first != CIRCLEQ_ENDC(head) && \ + (head)->cqh_first->field.cqe_prev != CIRCLEQ_ENDC(head)) \ + QUEUEDEBUG_ABORT("CIRCLEQ head forw %p %s:%d", (head), \ + __FILE__, __LINE__); \ + if ((head)->cqh_last != CIRCLEQ_ENDC(head) && \ + (head)->cqh_last->field.cqe_next != CIRCLEQ_ENDC(head)) \ + QUEUEDEBUG_ABORT("CIRCLEQ head back %p %s:%d", (head), \ + __FILE__, __LINE__); +#define QUEUEDEBUG_CIRCLEQ_ELM(head, elm, field) \ + if ((elm)->field.cqe_next == CIRCLEQ_ENDC(head)) { \ + if ((head)->cqh_last != (elm)) \ + QUEUEDEBUG_ABORT("CIRCLEQ elm last %p %s:%d", \ + (elm), __FILE__, __LINE__); \ + } else { \ + if ((elm)->field.cqe_next->field.cqe_prev != (elm)) \ + QUEUEDEBUG_ABORT("CIRCLEQ elm forw %p %s:%d", \ + (elm), __FILE__, __LINE__); \ + } \ + if ((elm)->field.cqe_prev == CIRCLEQ_ENDC(head)) { \ + if ((head)->cqh_first != (elm)) \ + QUEUEDEBUG_ABORT("CIRCLEQ elm first %p %s:%d", \ + (elm), __FILE__, __LINE__); \ + } else { \ + if ((elm)->field.cqe_prev->field.cqe_next != (elm)) \ + QUEUEDEBUG_ABORT("CIRCLEQ elm prev %p %s:%d", \ + (elm), __FILE__, __LINE__); \ + } +#define QUEUEDEBUG_CIRCLEQ_POSTREMOVE(elm, field) \ + (elm)->field.cqe_next = (void *)1L; \ + (elm)->field.cqe_prev = (void *)1L; +#else +#define QUEUEDEBUG_CIRCLEQ_HEAD(head, field) +#define QUEUEDEBUG_CIRCLEQ_ELM(head, elm, field) +#define QUEUEDEBUG_CIRCLEQ_POSTREMOVE(elm, field) +#endif + +#define CIRCLEQ_HEAD(name, type) \ +struct name { \ + struct type *cqh_first; /* first element */ \ + struct type *cqh_last; /* last element */ \ +} + +#define CIRCLEQ_HEAD_INITIALIZER(head) \ + { CIRCLEQ_END(&head), CIRCLEQ_END(&head) } + +#define CIRCLEQ_ENTRY(type) \ +struct { \ + struct type *cqe_next; /* next element */ \ + struct type *cqe_prev; /* previous element */ \ +} + +/* + * Circular queue functions. + */ +#define CIRCLEQ_INIT(head) do { \ + (head)->cqh_first = CIRCLEQ_END(head); \ + (head)->cqh_last = CIRCLEQ_END(head); \ +} while (/*CONSTCOND*/0) + +#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + QUEUEDEBUG_CIRCLEQ_HEAD((head), field) \ + QUEUEDEBUG_CIRCLEQ_ELM((head), (listelm), field) \ + (elm)->field.cqe_next = (listelm)->field.cqe_next; \ + (elm)->field.cqe_prev = (listelm); \ + if ((listelm)->field.cqe_next == CIRCLEQ_ENDC(head)) \ + (head)->cqh_last = (elm); \ + else \ + (listelm)->field.cqe_next->field.cqe_prev = (elm); \ + (listelm)->field.cqe_next = (elm); \ +} while (/*CONSTCOND*/0) + +#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \ + QUEUEDEBUG_CIRCLEQ_HEAD((head), field) \ + QUEUEDEBUG_CIRCLEQ_ELM((head), (listelm), field) \ + (elm)->field.cqe_next = (listelm); \ + (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \ + if ((listelm)->field.cqe_prev == CIRCLEQ_ENDC(head)) \ + (head)->cqh_first = (elm); \ + else \ + (listelm)->field.cqe_prev->field.cqe_next = (elm); \ + (listelm)->field.cqe_prev = (elm); \ +} while (/*CONSTCOND*/0) + +#define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \ + QUEUEDEBUG_CIRCLEQ_HEAD((head), field) \ + (elm)->field.cqe_next = (head)->cqh_first; \ + (elm)->field.cqe_prev = CIRCLEQ_END(head); \ + if ((head)->cqh_last == CIRCLEQ_ENDC(head)) \ + (head)->cqh_last = (elm); \ + else \ + (head)->cqh_first->field.cqe_prev = (elm); \ + (head)->cqh_first = (elm); \ +} while (/*CONSTCOND*/0) + +#define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \ + QUEUEDEBUG_CIRCLEQ_HEAD((head), field) \ + (elm)->field.cqe_next = CIRCLEQ_END(head); \ + (elm)->field.cqe_prev = (head)->cqh_last; \ + if ((head)->cqh_first == CIRCLEQ_ENDC(head)) \ + (head)->cqh_first = (elm); \ + else \ + (head)->cqh_last->field.cqe_next = (elm); \ + (head)->cqh_last = (elm); \ +} while (/*CONSTCOND*/0) + +#define CIRCLEQ_REMOVE(head, elm, field) do { \ + QUEUEDEBUG_CIRCLEQ_HEAD((head), field) \ + QUEUEDEBUG_CIRCLEQ_ELM((head), (elm), field) \ + if ((elm)->field.cqe_next == CIRCLEQ_ENDC(head)) \ + (head)->cqh_last = (elm)->field.cqe_prev; \ + else \ + (elm)->field.cqe_next->field.cqe_prev = \ + (elm)->field.cqe_prev; \ + if ((elm)->field.cqe_prev == CIRCLEQ_ENDC(head)) \ + (head)->cqh_first = (elm)->field.cqe_next; \ + else \ + (elm)->field.cqe_prev->field.cqe_next = \ + (elm)->field.cqe_next; \ + QUEUEDEBUG_CIRCLEQ_POSTREMOVE((elm), field) \ +} while (/*CONSTCOND*/0) + +#define CIRCLEQ_FOREACH(var, head, field) \ + for ((var) = ((head)->cqh_first); \ + (var) != CIRCLEQ_ENDC(head); \ + (var) = ((var)->field.cqe_next)) + +#define CIRCLEQ_FOREACH_REVERSE(var, head, field) \ + for ((var) = ((head)->cqh_last); \ + (var) != CIRCLEQ_ENDC(head); \ + (var) = ((var)->field.cqe_prev)) + +/* + * Circular queue access methods. + */ +#define CIRCLEQ_FIRST(head) ((head)->cqh_first) +#define CIRCLEQ_LAST(head) ((head)->cqh_last) +/* For comparisons */ +#define CIRCLEQ_ENDC(head) (__launder_type(head)) +/* For assignments */ +#define CIRCLEQ_END(head) ((void *)(head)) +#define CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next) +#define CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev) +#define CIRCLEQ_EMPTY(head) \ + (CIRCLEQ_FIRST(head) == CIRCLEQ_ENDC(head)) + +#define CIRCLEQ_LOOP_NEXT(head, elm, field) \ + (((elm)->field.cqe_next == CIRCLEQ_ENDC(head)) \ + ? ((head)->cqh_first) \ + : (elm->field.cqe_next)) +#define CIRCLEQ_LOOP_PREV(head, elm, field) \ + (((elm)->field.cqe_prev == CIRCLEQ_ENDC(head)) \ + ? ((head)->cqh_last) \ + : (elm->field.cqe_prev)) +#endif /* !_KERNEL */ + +#endif /* !_SYS_QUEUE_H_ */ diff --git a/src/rtc.c b/src/rtc.c new file mode 100644 index 0000000..dd72a9f --- /dev/null +++ b/src/rtc.c @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2019, Olivier MATZ + */ + +#include + +#include + +#include "irq.h" +#include "main.h" +#include "callout.h" + +static volatile uint32_t global_ms; +static void (*rtc_cb)(void); + +void rtc_init(void (*cb)(void)) +{ + /* timer2 interrupt */ + BUILD_BUG_ON(F_CPU != 8000000UL); + TCCR2B = (1 << CS21); /* div = 8 */ + TIMSK2 |= (1 << TOIE2); /* enable intr */ + rtc_cb = cb; +} + +/* interrupt every 256us */ +SIGNAL(TIMER2_OVF_vect) +{ + static uint16_t i = 0; + + i++; + if ((i & 0x3) != 0) + return; + + global_ms++; + if (rtc_cb != NULL) + rtc_cb(); +} + +/* return the absolute time in ms */ +uint32_t rtc_get_ms_u32(void) +{ + irqflags_t flags; + uint32_t ms; + + flags = irq_lock_save(); + ms = global_ms; + irq_unlock_restore(flags); + return ms; +} + +uint16_t rtc_get_ms_u16(void) +{ + return (uint16_t)rtc_get_ms_u32(); +} diff --git a/src/rtc.h b/src/rtc.h new file mode 100644 index 0000000..ef9dbd2 --- /dev/null +++ b/src/rtc.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2019, Olivier MATZ + */ + +#ifndef _RTC_H +#define _RTC_H + +/* Rtc initialization. The cb is executed every ms. */ +void rtc_init(void (*cb)(void)); + +/* return the absolute time in ms */ +uint16_t rtc_get_ms_u16(void); + +/* return the absolute time in ms */ +uint32_t rtc_get_ms_u32(void); + +#endif diff --git a/src/uart.c b/src/uart.c new file mode 100644 index 0000000..86c15ef --- /dev/null +++ b/src/uart.c @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2019, Olivier MATZ + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "cirbuf.h" +#include "main.h" +#include "uart.h" + +#define UART_RXQ_LEN 16 +struct cirbuf uart_rxq; +static char uart_buf[UART_RXQ_LEN]; + +SIGNAL(USART_RX_vect) +{ + char c = UDR0; + cirbuf_add_tail_safe(&uart_rxq, c); +} + +void uart_send(char c) +{ + while (!(UCSR0A & (1 << UDRE0))) + ; + UDR0 = c; +} + +/* used by printf */ +static int stdout_send(char c, FILE *f) +{ + (void)f; + uart_send(c); + return 0; +} + +int8_t uart_recv(char *c) +{ + int8_t ret = -1; + irqflags_t flags; + + flags = irq_lock_save(); + if (!cirbuf_is_empty(&uart_rxq)) { + *c = cirbuf_get_head(&uart_rxq); + cirbuf_del_head(&uart_rxq); + ret = 0; + } + irq_unlock_restore(flags); + + return ret; +} + +void uart_init(void) +{ + cirbuf_init(&uart_rxq, uart_buf, 0, UART_RXQ_LEN); + + /* 57600 bauds @ 8Mhz */ + BUILD_BUG_ON(F_CPU != 8000000UL); + UBRR0H = 0; + UBRR0L = 16; + UCSR0A = (1 << U2X0); + UCSR0C = (1 << UCSZ01) | (1 << UCSZ00); + UCSR0B = (1 << RXEN0) | (1 << TXEN0) | (1 << RXCIE0); + + fdevopen(stdout_send, NULL); +} diff --git a/src/uart.h b/src/uart.h new file mode 100644 index 0000000..c4039f4 --- /dev/null +++ b/src/uart.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright 2019, Olivier MATZ + */ + +#ifndef UART_H_ +#define UART_H_ + +#include + +#include "cirbuf.h" + +extern struct cirbuf uart_rxq; + +/* Initialise uart hardware. */ +void uart_init(void); + +/* Synchronous uart send. */ +void uart_send(char c); + +/* Receive a character from the rxq. + * Return 0 on success, -1 if queue is empty. */ +int8_t uart_recv(char *c); + +#endif