Как реализовать таблицу компиляции [отправка] для AVR?

У меня есть те же предварительные условия, что и Дейв Дурбин в Как я могу реализовать таблицу динамической рассылки в C... кроме моей цели является AVR. Вот мои ограничения:

  • Модули
  • должны быть выбраны в списке, подобно Linux-компилированным модулям ядра
  • количество модулей C (может быть С++) известно во время компиляции
  • модули должны быть статически связаны (очевидно)
  • Я хочу, чтобы таблица в программной памяти не была в SRAM.

Обычно таблица должна содержать элементы этого типа:

typedef struct jump_item {
    uint16_t function_id;
    void (*callback)(void);
} jump_item_t;

Я попытался использовать пользовательские разделы, как было предложено в ответе, но затем компоновщик выдает ошибку для неизвестного символа __start_myownsection (любое имя раздела, которое я использую). Конечно, поскольку код нацелен на Linux/GCC. Но я думаю, что я близок, потому что avr-gcc действительно может использовать sections, просто я еще не смог выяснить как складывать символы в пользовательский раздел и фактически указывать на начало таблицы, а также определять длину таблицы во время выполнения.

Как Ответ искусства" был адаптирован к AVR?


* РЕДАКТИРОВАТЬ *

Я вижу по крайней мере два способа достижения того, что я хочу, используя разделы, либо с функциями, "прикрепленными" к определенному пользователем разделу, либо таблицам структур (как определено выше), которые все будут складываться в пользовательском разделе, Мои текущие проблемы:

  • неиспользуемые переменные оптимизируются во время компиляции!
  • неиспользуемые функции оптимизируются во время связи из-за аргумента linker -gc-sections, который мне нужно очистить неиспользуемые функции.

Я предпочитаю второй вариант, что-то похожее на это:

module1.c:

const jump_item_t module1_table[] __attribute__((__progmem__, section("tbl_dispatch"))) =
{
    { 0x02, func11 },
    { 0x03, func12 },
    ...
};

module2.c:

const jump_item_t module2_table[] __attribute__((__progmem__, section("tbl_dispatch"))) =
{
    { 0x12, func21 },
    { 0x13, func22 },
    ...
};

Примечание: индексы не должны считаться релевантными.

Когда все модули определяют такие переменные, они оптимизируются, как нигде никакой ссылки на них. Они должны складываться в разделе tbl_dispatch. Поэтому мой вопрос возвращается к:

Как я могу сказать компилятору об удалении переменных, которые он "думает" не используется, но только с определенными модулями C/С++?

Глобальная командная строка, которую я использую до сих пор, выглядит следующим образом:

avr-gcc -g -Wall -mcall-prologues -fshort-enums -Os \
    -DF_CPU=8000000 -Wl,-relax -mmcu=... \
    *.cpp *.c -o main

* РЕДАКТИРОВАТЬ *

К моему разочарованию, PROGMEM и пользовательские разделы не совпадают. Я попытался объединить их, но я получаю распространенные таблицы прыжка в памяти программ... когда я вообще их включаю. Факт, что даже не все таблицы отображаются в памяти программы.

Отказаться.

Любая идея приветствуется.

Ответ 1

Вы можете определенно создать модульную систему, если напишите свой собственный компоновщик script и скопируйте то, что было сделано для конструкторов и деструкторов (ctors и dtors). Компилятор script ниже был основан на avr5.x от AVR GCC, но я добавил к нему материал отправки.

Если вы посмотрите на вывод сборки script в сеансе оболочки ниже, вы увидите, что таблица отправки настроена правильно и имеет символы, указывающие на начало и конец. Сеанс оболочки включает все сценарии исходного кода и сборки, которые я использовал для компиляции этого примера.

$ ls
avr5-x-modules.ld  build.sh  kernel.c  kernel.h  module_foo.c

$ cat avr5-x-modules.ld
/* Default linker script, for normal executables */
/* Copyright (C) 2014 Free Software Foundation, Inc.
   Copying and distribution of this script, with or without modification,
   are permitted in any medium without royalty provided the copyright
   notice and this notice are preserved.  */
OUTPUT_FORMAT("elf32-avr","elf32-avr","elf32-avr")
OUTPUT_ARCH(avr:5)
MEMORY
{
  text   (rx)   : ORIGIN = 0, LENGTH = 128K
  data   (rw!x) : ORIGIN = 0x800060, LENGTH = 0xffa0
  eeprom (rw!x) : ORIGIN = 0x810000, LENGTH = 64K
  fuse      (rw!x) : ORIGIN = 0x820000, LENGTH = 1K
  lock      (rw!x) : ORIGIN = 0x830000, LENGTH = 1K
  signature (rw!x) : ORIGIN = 0x840000, LENGTH = 1K
  user_signatures (rw!x) : ORIGIN = 0x850000, LENGTH = 1K
}
SECTIONS
{
  /* Read-only sections, merged into text segment: */
  .hash          : { *(.hash)           }
  .dynsym        : { *(.dynsym)         }
  .dynstr        : { *(.dynstr)         }
  .gnu.version   : { *(.gnu.version)    }
  .gnu.version_d   : { *(.gnu.version_d)        }
  .gnu.version_r   : { *(.gnu.version_r)        }
  .rel.init      : { *(.rel.init)               }
  .rela.init     : { *(.rela.init)      }
  .rel.text      :
    {
      *(.rel.text)
      *(.rel.text.*)
      *(.rel.gnu.linkonce.t*)
    }
  .rela.text     :
    {
      *(.rela.text)
      *(.rela.text.*)
      *(.rela.gnu.linkonce.t*)
    }
  .rel.fini      : { *(.rel.fini)               }
  .rela.fini     : { *(.rela.fini)      }
  .rel.rodata    :
    {
      *(.rel.rodata)
      *(.rel.rodata.*)
      *(.rel.gnu.linkonce.r*)
    }
  .rela.rodata   :
    {
      *(.rela.rodata)
      *(.rela.rodata.*)
      *(.rela.gnu.linkonce.r*)
    }
  .rel.data      :
    {
      *(.rel.data)
      *(.rel.data.*)
      *(.rel.gnu.linkonce.d*)
    }
  .rela.data     :
    {
      *(.rela.data)
      *(.rela.data.*)
      *(.rela.gnu.linkonce.d*)
    }
  .rel.ctors     : { *(.rel.ctors)      }
  .rela.ctors    : { *(.rela.ctors)     }
  .rel.dtors     : { *(.rel.dtors)      }
  .rela.dtors    : { *(.rela.dtors)     }
  .rel.got       : { *(.rel.got)                }
  .rela.got      : { *(.rela.got)               }
  .rel.bss       : { *(.rel.bss)                }
  .rela.bss      : { *(.rela.bss)               }
  .rel.plt       : { *(.rel.plt)                }
  .rela.plt      : { *(.rela.plt)               }
  /* Internal text space or external memory.  */
  .text   :
  {
    *(.vectors)
    KEEP(*(.vectors))
    /* For data that needs to reside in the lower 64k of progmem.  */
     *(.progmem.gcc*)
    /* PR 13812: Placing the trampolines here gives a better chance
       that they will be in range of the code that uses them.  */
    . = ALIGN(2);
     __trampolines_start = . ;
    /* The jump trampolines for the 16-bit limited relocs will reside here.  */
    *(.trampolines)
     *(.trampolines*)
     __trampolines_end = . ;
     *(.progmem*)
    . = ALIGN(2);
    /* For future tablejump instruction arrays for 3 byte pc devices.
       We don't relax jump/call instructions within these sections.  */
    *(.jumptables)
     *(.jumptables*)
    /* For code that needs to reside in the lower 128k progmem.  */
    *(.lowtext)
     *(.lowtext*)
     __ctors_start = . ;
     *(.ctors)
     __ctors_end = . ;
     __dtors_start = . ;
     *(.dtors)
     __dtors_end = . ;
    KEEP(SORT(*)(.ctors))
    KEEP(SORT(*)(.dtors))
    __dispatch_start = . ;
    *(.dispatch)
    __dispatch_end = . ;
    KEEP(SORT(*)(.dispatch))
    /* From this point on, we don't bother about wether the insns are
       below or above the 16 bits boundary.  */
    *(.init0)  /* Start here after reset.  */
    KEEP (*(.init0))
    *(.init1)
    KEEP (*(.init1))
    *(.init2)  /* Clear __zero_reg__, set up stack pointer.  */
    KEEP (*(.init2))
    *(.init3)
    KEEP (*(.init3))
    *(.init4)  /* Initialize data and BSS.  */
    KEEP (*(.init4))
    *(.init5)
    KEEP (*(.init5))
    *(.init6)  /* C++ constructors.  */
    KEEP (*(.init6))
    *(.init7)
    KEEP (*(.init7))
    *(.init8)
    KEEP (*(.init8))
    *(.init9)  /* Call main().  */
    KEEP (*(.init9))
    *(.text)
    . = ALIGN(2);
     *(.text.*)
    . = ALIGN(2);
    *(.fini9)  /* _exit() starts here.  */
    KEEP (*(.fini9))
    *(.fini8)
    KEEP (*(.fini8))
    *(.fini7)
    KEEP (*(.fini7))
    *(.fini6)  /* C++ destructors.  */
    KEEP (*(.fini6))
    *(.fini5)
    KEEP (*(.fini5))
    *(.fini4)
    KEEP (*(.fini4))
    *(.fini3)
    KEEP (*(.fini3))
    *(.fini2)
    KEEP (*(.fini2))
    *(.fini1)
    KEEP (*(.fini1))
    *(.fini0)  /* Infinite loop after program termination.  */
    KEEP (*(.fini0))
     _etext = . ;
  }  > text
  .data          :
  {
     PROVIDE (__data_start = .) ;
    *(.data)
     *(.data*)
    *(.rodata)  /* We need to include .rodata here if gcc is used */
     *(.rodata*) /* with -fdata-sections.  */
    *(.gnu.linkonce.d*)
    . = ALIGN(2);
     _edata = . ;
     PROVIDE (__data_end = .) ;
  }  > data AT> text
  .bss  ADDR(.data) + SIZEOF (.data)   : AT (ADDR (.bss))
  {
     PROVIDE (__bss_start = .) ;
    *(.bss)
     *(.bss*)
    *(COMMON)
     PROVIDE (__bss_end = .) ;
  }  > data
   __data_load_start = LOADADDR(.data);
   __data_load_end = __data_load_start + SIZEOF(.data);
  /* Global data not cleared after reset.  */
  .noinit  ADDR(.bss) + SIZEOF (.bss)  :  AT (ADDR (.noinit))
  {
     PROVIDE (__noinit_start = .) ;
    *(.noinit*)
     PROVIDE (__noinit_end = .) ;
     _end = . ;
     PROVIDE (__heap_start = .) ;
  }  > data
  .eeprom  :
  {
    /* See .data above...  */
    KEEP(*(.eeprom*))
     __eeprom_end = . ;
  }  > eeprom
  .fuse  :
  {
    KEEP(*(.fuse))
    KEEP(*(.lfuse))
    KEEP(*(.hfuse))
    KEEP(*(.efuse))
  }  > fuse
  .lock  :
  {
    KEEP(*(.lock*))
  }  > lock
  .signature  :
  {
    KEEP(*(.signature*))
  }  > signature
  .user_signatures  :
  {
    KEEP(*(.user_signatures*))
  }  > user_signatures
  /* Stabs debugging sections.  */
  .stab 0 : { *(.stab) }
  .stabstr 0 : { *(.stabstr) }
  .stab.excl 0 : { *(.stab.excl) }
  .stab.exclstr 0 : { *(.stab.exclstr) }
  .stab.index 0 : { *(.stab.index) }
  .stab.indexstr 0 : { *(.stab.indexstr) }
  .comment 0 : { *(.comment) }
  .note.gnu.build-id : { *(.note.gnu.build-id) }
  /* DWARF debug sections.
     Symbols in the DWARF debugging sections are relative to the beginning
     of the section so we begin them at 0.  */
  /* DWARF 1 */
  .debug          0 : { *(.debug) }
  .line           0 : { *(.line) }
  /* GNU DWARF 1 extensions */
  .debug_srcinfo  0 : { *(.debug_srcinfo) }
  .debug_sfnames  0 : { *(.debug_sfnames) }
  /* DWARF 1.1 and DWARF 2 */
  .debug_aranges  0 : { *(.debug_aranges) }
  .debug_pubnames 0 : { *(.debug_pubnames) }
  /* DWARF 2 */
  .debug_info     0 : { *(.debug_info .gnu.linkonce.wi.*) }
  .debug_abbrev   0 : { *(.debug_abbrev) }
  .debug_line     0 : { *(.debug_line .debug_line.* .debug_line_end ) }
  .debug_frame    0 : { *(.debug_frame) }
  .debug_str      0 : { *(.debug_str) }
  .debug_loc      0 : { *(.debug_loc) }
  .debug_macinfo  0 : { *(.debug_macinfo) }
  /* SGI/MIPS DWARF 2 extensions */
  .debug_weaknames 0 : { *(.debug_weaknames) }
  .debug_funcnames 0 : { *(.debug_funcnames) }
  .debug_typenames 0 : { *(.debug_typenames) }
  .debug_varnames  0 : { *(.debug_varnames) }
  /* DWARF 3 */
  .debug_pubtypes 0 : { *(.debug_pubtypes) }
  .debug_ranges   0 : { *(.debug_ranges) }
  /* DWARF Extension.  */
  .debug_macro    0 : { *(.debug_macro) }
}

$ cat build.sh
CFLAGS="-std=gnu11 -mmcu=atmega328p"
set -uex
avr-gcc $CFLAGS -c module_foo.c -o module_foo.o
avr-gcc $CFLAGS -c kernel.c -o kernel.o
avr-gcc -T avr5-x-modules.ld kernel.o module_foo.o \
        -o program.elf -Wl,-Map=program.map
grep dispatch program.map

$ cat kernel.c
#include "kernel.h"
#include <avr/pgmspace.h>

extern dispatch_item * __dispatch_start;
extern dispatch_item * __dispatch_end;

int main()
{
    while (1)
    {
        for (dispatch_item * item = __dispatch_start; item < __dispatch_end; item++)
        {
            // TODO: Insert code here for reading the contents of the
            // dispatch item from program space and using it.  You
            // probably have to use pgm_read_word avr avr/pgmspace.h,
            // but with GCC 5 you could probably use the new named
            // memory space feature to just access the dispatch item
            // the same way you would access any other struct:
            // https://gcc.gnu.org/onlinedocs/gcc/Named-Address-Spaces.html
        }
    }
}

$ cat kernel.h
#pragma once

#include <stdint.h>

typedef struct dispatch_item {
    uint16_t func_id;
    void (*func)(void);
} dispatch_item;

#define DISPATCH_ITEM dispatch_item const __attribute__((section (".dispatch")))


$ cat module_foo.c
#include "kernel.h"
#include <avr/io.h>

// This gets called before main.
void __attribute__((constructor)) foo_init()
{
    PINB = 0;
}

// There is a pointer to this in the dispatch table.
void foo()
{
    PINB = 1;
}

// DISPATHCH_TABLE_ENTRY(0x12, &foo);

DISPATCH_ITEM foo_dispatch = { 0x12, &foo };

DISPATCH_ITEM foo_dispatch2 = { 0x13, &foo };

$ ./build.sh
++ avr-gcc -std=gnu11 -mmcu=atmega328p -c module_foo.c -o module_foo.o
++ avr-gcc -std=gnu11 -mmcu=atmega328p -c kernel.c -o kernel.o
++ avr-gcc -T avr5-x-modules.ld kernel.o module_foo.o -o program.elf -Wl,-Map=program.map
++ grep dispatch program.map
                0x00000002                __dispatch_start = .
 *(.dispatch)
 .dispatch      0x00000002        0x8 module_foo.o
                0x00000002                foo_dispatch
                0x00000006                foo_dispatch2
                0x0000000a                __dispatch_end = .
 SORT(*)(.dispatch)

Ответ 2

Единственный практический способ, о котором я могу думать, поскольку все мои попытки потерпели неудачу до сих пор, - это скрипты и меню makefile, похожие на создание модулей ядра Linux: вы выбираете серию модулей для компиляции и make script создает файлы заголовка/источника с таблицей отправки.

Сгенерированный исходный файл построен таким образом, чтобы включать ссылки на все необходимые функции и переменные, не позволяя сборщику мусора отрывать их во время соединения. У меня нет деталей реализации, это всего лишь намек на то, что я мог бы следовать, но не самую простую форму.

Ответ 3

Пользовательский раздел будет работать, но не используйте PROGMEM. С avr-gcc PROGMEM добавляет атрибут раздела. Добавление другого приведет к проблемам. Если вы не будете работать над этим, новый раздел войдет в память программы. Вам не нужно заменять компоновщик по умолчанию script, но вам нужно добавить его, чтобы получить начало и размер нового раздела. В руководстве ld см. 3.10.9 Встроенные функции ADDR и SIZE, 3.11. Неявные скрипты компоновщика, 3.5.4 Ссылка на исходный код.