Странное поведение сброса UC3 после использования NVRAM страницы пользователя

Недавно мне нужно было использовать в сборке NVRAM/EEPROM AT32UC3L0256 для хранения некоторых данных конфигурации. Мне наконец удалось использовать пользовательскую страницу NVRAM MCU (после нескольких дней проб и ошибок и проклятия на игнорирование GCCnoinit директивы, исправления и обхода ошибок в ASF, как обычно) примерно так:

typedef struct
    {
    int writes;                 // write cycles counter
    int irc_pot;                // IRC_POT_IN position state
    } _cfg;
volatile static int *nvram_adr=(int*)(void*)0x80800000;     // user page NVRAM
volatile static _cfg    ram_cfg;                            // RAM copy of cfg

void cfg_load() // nvram_cfg -> ram_cfg
    {
    ram_cfg.writes =nvram_adr[8];
    ram_cfg.irc_pot=nvram_adr[9];
    }
void cfg_save() // nvram_cfg <- ram_cfg
    {
    int i;
    U32 buf[128];
    // blank
    for (i=0;i<128;i++) buf[i]=0xFFFFFFFF;
    // cfg
    buf[8]=ram_cfg.writes;
    buf[9]=ram_cfg.irc_pot;
    // Bootloader default cfg
    buf[126]=0x929E0B79;
    buf[127]=0xE11EFFD7;
    flashcdw_memcpy(nvram_adr   ,buf   ,256,true);  // write data -> nvram_cfg with erase
    flashcdw_memcpy(nvram_adr+64,buf+64,256,false); // write data -> nvram_cfg without erase (fucking ASF cant write more than 256Bytes at once but erases whole page !!!)
    }

Мне пришлось обновить flashcdw.c,flashcdw.h от ASF 3.48.0.98чтобы иметь возможность записать полные 512 байт, поскольку старый ASF запрограммировал всего до 256 Байт, но стирает всю страницу, создавая беспорядок. Мне также пришлось сохранить всю страницу (вместо 8 байтов из-за стирания), и, как обычно, из-за ошибок ASF мне нужно было сделать это как есть, а не просто вызыватьflashcdw_memcpy только раз...

Сейчас он работает, но я обнаружил, что некоторые адреса вызывают странное поведение. когда0xFFне находится на каком-либо адресе, устройство больше не будет сбрасывать в обычном режиме (но все равно, пока сброс не запустится). При СБРОСЕ без загрузчика запускается код прошивки, но через несколько[ms]он снова сбрасывается, и это продолжается вечно. Для ясности, в этой части кода (в моем случае) происходит СБРОС:

for (U8 i=0;i<4;i++)
    {
    gpio_tgl_gpio_pin(_LED);
    wait_ms(200);
    }

его простое мигание светодиода после настройки системы (частота процессора PLL, настроенные таймеры и ISR, но прерывания все еще отключены). Светодиод мигает как следует несколько раз (ФАПЧ работает с правильной скоростью), но до завершения цикла происходит сброс. Ждать просто:

//------------------------------------------------------------------------------------------------
#define clk_cpu 50000000
#define RDTSC_mask 0x0FFFFFFF
void wait_ms(U32 dt)
    {
    U32 t0,t1;
    t0=Get_system_register(AVR32_COUNT);
    static const U32 ms=(clk_cpu+999)/1000;
    t0&=RDTSC_mask;
    for (;dt>0;)
        {
        t1=Get_system_register(AVR32_COUNT);
        t1&=RDTSC_mask;
        if (t0>t1)  t1+=RDTSC_mask+1;
        if ((t1-t0)>=ms)
            {
            dt--;
            t0+=ms;
            t0&=RDTSC_mask;
            continue;
            }
        }
    }
//------------------------------------------------------------------------------------------------

Еще более странно то, что если я загружаюсь в загрузчик, а затем снова перезагружаюсь, устройство сбрасывается правильно, и прошивка снова работает (без стирания / программирования), однако, если я снова перезагружаюсь в обычном режиме, цикл сброса повторяется...

Если я перепрограммирую .userpage NVRAM возвращается в исходное состояние с помощью BatchISP (перевернуть) чип снова работает нормально.

Итак, наконец, вопросы:

  1. Какие адреса на странице пользователя NVRAM вызывают это или их следует зарезервировать / избегать изменения?

    Я знаю, что последние 8 байтов - это конфигурация загрузчика. Я подозреваю, что проблемные адреса - первые 16 байт. В.userpage должен быть для пользовательских данных и не содержать предохранителей.

  2. Что случилось?

    это какой-то сторожевой пес что ли? Я думал, это внутренние предохранители, которые хранятся в другом месте. Я ничего не вижу в даташите.

Здесь шестигранник оригинала .userpage:

:020000048080FA
:10000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00
:10001000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0
:10002000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE0
:10003000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD0
:10004000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC0
:10005000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB0
:10006000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA0
:10007000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF90
:10008000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF80
:10009000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF70
:1000A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF60
:1000B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF50
:1000C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF40
:1000D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF30
:1000E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF20
:1000F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF10
:10010000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
:10011000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEF
:10012000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDF
:10013000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCF
:10014000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBF
:10015000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAF
:10016000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9F
:10017000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8F
:10018000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F
:10019000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6F
:1001A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5F
:1001B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4F
:1001C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3F
:1001D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2F
:1001E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1F
:1001F000FFFFFFFFFFFFFFFF929E0B79E11EFFD77E
:00000001FF

Я использую эти (команды переворота), чтобы получить и восстановить его:

Batchisp -device AT32UC3L0256 -hardware RS232 -port COM1 -baudrate 115200 -operation memory user read savebuffer cfg_userpage.hex hex386 start reset 0
Batchisp -device AT32UC3L0256 -hardware RS232 -port COM1 -baudrate 115200 -operation onfail abort memory user loadbuffer cfg_userpage.hex program start reset 0

Рассматриваемый загрузчик - это версия USART: 1.0.2. И микропрограмма с таким поведением использует модули PLL,TC,GPIO,PWMA,ADC, однако сброс происходит до использования ISR и / или ADC,PWMA,TC.

[Edit1] Watchdog

в соответствии с этим первое слово в.userpage NVRAM - это предохранитель для сторожевого пса, который объясняет сброс после нескольких ms после восстановления данных до исходных значений и отключения WDTсброс останавливается. Однако теперь вместо загрузки программы запускается загрузчик, так что все еще есть что-то подозрительное. Выбран PIN-код загрузчика в последних 8 байтах

Также я заглянул в источник USART Bootloader ver: 1.0.2 и обнаружил, что они используют FLASHC вместо того FLASHCDW и принудительная загрузка с помощью сторожевого таймера (который может сбросить его состояние и позволить моей программе каким-то образом снова запуститься).

[Edit2] ошибка изолирована

Я наконец обнаружил, что проблема вызвана записью в последнее 32-битное слово из 512 байт..userpage:

U32 btldr[2]={0x929E0B79,0xE11EFFD7};
flashcdw_memcpy(&nvram_adr[127],(U32*)&btldr[1]       ,4,false);

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

U32 btldr[2]={0x929E0B79,0xE11EFFD7};
flashcdw_memcpy(&nvram_adr[126],(U32*)&btldr[0]       ,4,false);
flashcdw_memcpy(&nvram_adr[127],(U32*)&btldr[1]       ,4,false);

Мне нужно найти обходной путь, как вернуть чип в рабочее состояние. Возможно, продублируйте сброс сторожевого таймера из загрузчика (но это было бы очень проблематично и даже рискованно в моем приложении), поскольку он восстанавливает чип даже без перепрошивки...

так что карта сейчас:

:020000048080FA
:10000000---WDT--FFFFFFFFFFFFFFFFFFFFFFFF00
:10001000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0
:10002000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE0
:10003000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD0
:10004000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC0
:10005000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB0
:10006000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA0
:10007000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF90
:10008000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF80
:10009000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF70
:1000A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF60
:1000B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF50
:1000C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF40
:1000D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF30
:1000E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF20
:1000F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF10
:10010000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
:10011000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEF
:10012000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDF
:10013000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCF
:10014000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBF
:10015000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAF
:10016000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9F
:10017000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8F
:10018000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F
:10019000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6F
:1001A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5F
:1001B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4F
:1001C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3F
:1001D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2F
:1001E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1F
:1001F000FFFFFFFFFFFFFFFF------BTLDR-----7E
:00000001FF

[Edit3] обходной путь

Мне удалось успешно записать / прочитать FLASH памяти программ (проверено с помощью BatchISP и MCU), он также загружается нормально.

Вот код, с которым я это тестирую:

// **** test ****
volatile static U32 *flash_adr=(U32*)(void*)0x00000000;
const U32 buf[4]=
    {
    0xDEADBEEF,0x00112233,
    0xDEADBEEF,0x44556677,
    };
volatile static U32 *adr=(U32*)(void*)0x80030000;
flashcdw_memcpy(&adr[0],(U32*)buf,4*4,true ); // erase

flash_adr=(U32*)(void*)0x80000000;
for (U32 i=0;i<0x08000000;i++,flash_adr++)
    {
    if (flash_adr!=buf)
     if (flash_adr[0]==buf[0])
      if (flash_adr[1]==buf[1])
       if (flash_adr[2]==buf[2])
        if (flash_adr[3]==buf[3])
         { break; }
    if ((i&0xFFF)==0) gpio_tgl_gpio_pin(_LED);
    }

где flash_adr найденный адрес, содержание которого соответствует buf[]подпись... (я распечатываю ее на ЖК-дисплее, чтобы посмотреть, соответствует ли она тому, что я ожидал), и она наконец соответствует:). Поэтому я буду использовать это вместо.userpage.

Тем не менее .userpage Исправление проблемы с загрузкой все еще под вопросом

2 ответа

Решение

Хорошо, вот официальное разрешение от Atmel/Microchip:

Предлагаемое решение:
похоже, что восстанавливаемое слово DFU не имеет флага загрузки + изменение CRC. Для клиентского приложения, где данные пользовательской страницы непостоянны, для энергонезависимого хранилища предпочтительнее использовать флэш-память.

Итак, я понимаю, что это ошибка HW (на новых микросхемах, таких как AT32UC3L0256), и ее нельзя обойти ... кроме использования другой энергонезависимой памяти, такой как Flash для программы (точно так же, как я закончил это в первую очередь).

Отключить wdt в начале основной функции

wdt_disable();

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

unsigned char buf[AVR32_FLASHCDW_PAGE_SIZE];
void* flash_addr = AVR32_FLASHCDW_USER_PAGE;
memcpy(buf, flash_addr, 512);
// modify data in buf
flashcdw_memcpy(flash_addr, buf,AVR32_FLASHCDW_PAGE_SIZE,TRUE);

или просто используйте

int mydata = 123;
int *nvmydata=AVR32_FLASHCDW_USER_PAGE + 16 // offset
flashcdw_memcyp(nvmydata,&mydata,sizeof(mydata),TRUE);

flashcdw_memcpy

 volatile void* flashcdw_memcpy(volatile void* dst, const void* src, size_t nbytes, Bool erase)
{
  // Use aggregated pointers to have several alignments available for a same address.
  UnionCVPtr flash_array_end;
  UnionVPtr dest;
  UnionCPtr source;
  StructCVPtr dest_end;
  UnionCVPtr flash_page_source_end;
  Bool incomplete_flash_page_end;
  Union64 flash_dword;
  Bool flash_dword_pending = FALSE;
  UnionVPtr tmp;
  unsigned int error_status = 0;
  unsigned int i, j;

  // Reformat arguments.
  flash_array_end.u8ptr = AVR32_FLASH + flashcdw_get_flash_size();
  dest.u8ptr = dst;
  source.u8ptr = src;
  dest_end.u8ptr = dest.u8ptr + nbytes;

  // If destination is outside flash, go to next flash page if any.
  if (dest.u8ptr < AVR32_FLASH)
  {
    source.u8ptr += AVR32_FLASH - dest.u8ptr;
    dest.u8ptr = AVR32_FLASH;
  }
  else if (flash_array_end.u8ptr <= dest.u8ptr && dest.u8ptr < AVR32_FLASHCDW_USER_PAGE)
  {
    source.u8ptr += AVR32_FLASHCDW_USER_PAGE - dest.u8ptr;
    dest.u8ptr = AVR32_FLASHCDW_USER_PAGE;
  }

  // If end of destination is outside flash, move it to the end of the previous flash page if any.
  if (dest_end.u8ptr > AVR32_FLASHCDW_USER_PAGE + AVR32_FLASHCDW_USER_PAGE_SIZE)
  {
    dest_end.u8ptr = AVR32_FLASHCDW_USER_PAGE + AVR32_FLASHCDW_USER_PAGE_SIZE;
  }
  else if (AVR32_FLASHCDW_USER_PAGE >= dest_end.u8ptr && dest_end.u8ptr > flash_array_end.u8ptr)
  {
    dest_end.u8ptr = flash_array_end.u8ptr;
  }

  // Align each end of destination pointer with its natural boundary.
  dest_end.u16ptr = (U16*)Align_down((U32)dest_end.u8ptr, sizeof(U16));
  dest_end.u32ptr = (U32*)Align_down((U32)dest_end.u16ptr, sizeof(U32));
  dest_end.u64ptr = (U64*)Align_down((U32)dest_end.u32ptr, sizeof(U64));

  // While end of destination is not reached...
  while (dest.u8ptr < dest_end.u8ptr)
  {
    // Clear the page buffer in order to prepare data for a flash page write.
    flashcdw_clear_page_buffer();
    error_status |= flashcdw_error_status;

    // Determine where the source data will end in the current flash page.
    flash_page_source_end.u64ptr =
      (U64*)min((U32)dest_end.u64ptr,
                 Align_down((U32)dest.u8ptr, AVR32_FLASHCDW_PAGE_SIZE) + AVR32_FLASHCDW_PAGE_SIZE);

    // Determine if the current destination page has an incomplete end.
    incomplete_flash_page_end = (Align_down((U32)dest.u8ptr, AVR32_FLASHCDW_PAGE_SIZE) >=
                                 Align_down((U32)dest_end.u8ptr, AVR32_FLASHCDW_PAGE_SIZE));

    // If destination does not point to the beginning of the current flash page...
    if (!Test_align((U32)dest.u8ptr, AVR32_FLASHCDW_PAGE_SIZE))
    {
      // Fill the beginning of the page buffer with the current flash page data.
      // This is required by the hardware, even if page erase is not requested,
      // in order to be able to write successfully to erased parts of flash
      // pages that have already been written to.
      for (tmp.u8ptr = (U8*)Align_down((U32)dest.u8ptr, AVR32_FLASHCDW_PAGE_SIZE);
           tmp.u64ptr < (U64*)Align_down((U32)dest.u8ptr, sizeof(U64));
           tmp.u64ptr++)
      {
        * tmp.u32ptr = *tmp.u32ptr;
        * (tmp.u32ptr + 1) = *(tmp.u32ptr + 1);
      }

      // If destination is not 64-bit aligned...
      if (!Test_align((U32)dest.u8ptr, sizeof(U64)))
      {
        // Fill the beginning of the flash double-word buffer with the current
        // flash page data.
        // This is required by the hardware, even if page erase is not
        // requested, in order to be able to write successfully to erased parts
        // of flash pages that have already been written to.
        for (i = 0; i < Get_align((U32)dest.u8ptr, sizeof(U64)); i++)
          flash_dword.u8[i] = *tmp.u8ptr++;

        // Fill the end of the flash double-word buffer with the source data.
        for (; i < sizeof(U64); i++)
          flash_dword.u8[i] = *source.u8ptr++;

        // Align the destination pointer with its 64-bit boundary.
        dest.u64ptr = (U64*)Align_down((U32)dest.u8ptr, sizeof(U64));

        // If the current destination double-word is not the last one...
        if (dest.u64ptr < dest_end.u64ptr)
        {
          // Write the flash double-word buffer to the page buffer.
            *dest.u32ptr++ = flash_dword.u32[0];
            *dest.u32ptr++ = flash_dword.u32[1];
        }
        // If the current destination double-word is the last one, the flash
        // double-word buffer must be kept for later.
        else flash_dword_pending = TRUE;
      }
    }

    // Read the source data with the maximal possible alignment and write it to
    // the page buffer with 64-bit alignment.
    switch (Get_align((U32)source.u8ptr, sizeof(U32)))
    {
    case 0:
      for (i = flash_page_source_end.u64ptr - dest.u64ptr; i; i--)
      {
        *dest.u32ptr++ = *source.u32ptr++;
        *dest.u32ptr++ = *source.u32ptr++;
      }
      break;

    case sizeof(U16) :
      for (i = flash_page_source_end.u64ptr - dest.u64ptr; i; i--)
      {
        for (j = 0; j < sizeof(U64) / sizeof(U16); j++) flash_dword.u16[j] = *source.u16ptr++;
* dest.u32ptr++ = flash_dword.u32[0];
* dest.u32ptr++ = flash_dword.u32[1];
      }
      break;

    default:
      for (i = flash_page_source_end.u64ptr - dest.u64ptr; i; i--)
      {
        for (j = 0; j < sizeof(U64); j++) flash_dword.u8[j] = *source.u8ptr++;
 dest.u32ptr++ = flash_dword.u32[0];
 dest.u32ptr++ = flash_dword.u32[1];
      }
    }

    // If the current destination page has an incomplete end...
    if (incomplete_flash_page_end)
    {
      // If the flash double-word buffer is in use, do not initialize it.
      if (flash_dword_pending) i = Get_align((U32)dest_end.u8ptr, sizeof(U64));
      // If the flash double-word buffer is free...
      else
      {
        // Fill the beginning of the flash double-word buffer with the source data.
        for (i = 0; i < Get_align((U32)dest_end.u8ptr, sizeof(U64)); i++)
          flash_dword.u8[i] = *source.u8ptr++;
      }

      // This is required by the hardware, even if page erase is not requested,
      // in order to be able to write successfully to erased parts of flash
      // pages that have already been written to.
      {
        tmp.u8ptr = (volatile U8*)dest_end.u8ptr;

        // If end of destination is not 64-bit aligned...
        if (!Test_align((U32)dest_end.u8ptr, sizeof(U64)))
        {
          // Fill the end of the flash double-word buffer with the current flash page data.
          for (; i < sizeof(U64); i++)
            flash_dword.u8[i] = *tmp.u8ptr++;

          // Write the flash double-word buffer to the page buffer.
* dest.u32ptr++ = flash_dword.u32[0];
* dest.u32ptr++ = flash_dword.u32[1];
        }

        // Fill the end of the page buffer with the current flash page data.
        for (; !Test_align((U32)tmp.u64ptr, AVR32_FLASHCDW_PAGE_SIZE); tmp.u64ptr++)
        {
 tmp.u32ptr = *tmp.u32ptr;
* (tmp.u32ptr + 1) = *(tmp.u32ptr + 1);
        }
      }
    }

    // If the current flash page is in the flash array...
    if (dest.u8ptr <= AVR32_FLASHCDW_USER_PAGE)
    {
      // Erase the current page if requested and write it from the page buffer.
      if (erase)
      {
        flashcdw_erase_page(-1, FALSE);
        error_status |= flashcdw_error_status;
      }
      flashcdw_write_page(-1);
      error_status |= flashcdw_error_status;

      // If the end of the flash array is reached, go to the User page.
      if (dest.u8ptr >= flash_array_end.u8ptr)
      {
        source.u8ptr += AVR32_FLASHCDW_USER_PAGE - dest.u8ptr;
        dest.u8ptr = AVR32_FLASHCDW_USER_PAGE;
      }
    }
    // If the current flash page is the User page...
    else
    {
      // Erase the User page if requested and write it from the page buffer.
      if (erase)
      {
        flashcdw_erase_user_page(FALSE);
        error_status |= flashcdw_error_status;
      }
      flashcdw_write_user_page();
      error_status |= flashcdw_error_status;
    }
  }

  // Update the FLASHC error status.
  flashcdw_error_status = error_status;

  // Return the initial destination pointer as the standard memcpy function does.
  return dst;
}
Другие вопросы по тегам