У меня есть процессорный модуль NVIDIA Tegra TK1 на несущей плате с подключенным к нему слотом PCI-e. В этом слоте PCIe находится плата FPGA, которая предоставляет некоторые регистры и область памяти 64 КБ через PCIe.
На процессоре ARM платы Tegra выполняется минимальная установка Linux.
Я использую / dev / mem и функцию mmap для получения указателей пользовательского пространства на структуры регистров и область памяти 64K.
Отдельные файлы регистров и блок памяти являются назначенными адресами, которые выровнены и не перекрываются по отношению к страницам памяти 4 КБ.
Я явно отображаю целые страницы с помощью mmap, используя результат getpagesize (), который также равен 4096.
Я могу читать / писать из / в эти открытые регистры просто отлично.
Я могу читать из области памяти (64 КБ), выполняя пословное чтение uint32 в цикле for, просто отлично. То есть прочитайте содержание правильно.
Но если я использую std :: memcpy в одном и том же диапазоне адресов, процессор Tegra всегда зависает. Я не вижу сообщения об ошибке, если GDB подключен, я также не вижу ничего в Eclipse, когда пытаюсь перешагнуть через строку memcpy, она просто останавливается. И я должен сбросить процессор, используя кнопку аппаратного сброса, так как удаленная консоль зависла.
Это отладочная сборка без оптимизации (-O0) с использованием gcc-linaro-6.3.1-2017.05-i686-mingw32_arm-linux-gnueabihf. Мне сказали, что область 64K доступна побайтно, я не пробовал это явно.
Есть ли реальная (потенциальная) проблема, о которой мне нужно беспокоиться, или есть конкретная причина, по которой memcpy не работает и, возможно, не следует использовать в первую очередь в этом сценарии — и я могу просто продолжать использовать цикл for и не думать об этом?
РЕДАКТИРОВАТЬ: Наблюдается еще один эффект: в исходном фрагменте кода отсутствовал «жизненно важный» printf в цикле копирования for, который был до чтения из памяти. Это удалено, я не вернул действительные данные. Теперь я обновил фрагмент кода, чтобы иметь дополнительное чтение с того же адреса вместо printf, что также дает правильные данные. Путаница усиливается.
Здесь (я думаю) важные выдержки из того, что происходит. С небольшими изменениями, чтобы иметь смысл, как показано, в этой «распушенной» форме.
// void* physicalAddr: PCIe "BAR0" address as reported by dmesg, added to the physical address offset of FPGA memory region
// long size: size of the physical region to be mapped
//--------------------------------
// doing the memory mapping
//
const uint32_t pageSize = getpagesize();
assert( IsPowerOfTwo( pageSize ) );
const uint32_t physAddrNum = (uint32_t) physicalAddr;
const uint32_t offsetInPage = physAddrNum & (pageSize - 1);
const uint32_t firstMappedPageIdx = physAddrNum / pageSize;
const uint32_t lastMappedPageIdx = (physAddrNum + size - 1) / pageSize;
const uint32_t mappedPagesCount = 1 + lastMappedPageIdx - firstMappedPageIdx;
const uint32_t mappedSize = mappedPagesCount * pageSize;
const off_t targetOffset = physAddrNum & ~(off_t)(pageSize - 1);
m_fileID = open( "/dev/mem", O_RDWR | O_SYNC );
// addr passed as null means: we supply pages to map. Supplying non-null addr would mean, Linux takes it as a "hint" where to place.
void* mapAtPageStart = mmap( 0, mappedSize, PROT_READ | PROT_WRITE, MAP_SHARED, m_fileID, targetOffset );
if (MAP_FAILED != mapAtPageStart)
{
m_userSpaceMappedAddr = (volatile void*) ( uint32_t(mapAtPageStart) + offsetInPage );
}
//--------------------------------
// Accessing the mapped memory
//
//void* m_rawData: <== m_userSpaceMappedAddr
//uint32_t* destination: points to a stack object
//int length: size in 32bit words of the stack object (a struct with only U32's in it)
// this crashes:
std::memcpy( destination, m_rawData, length * sizeof(uint32_t) );
// this does not, AND does yield correct memory contents - but only with a preceding extra read
for (int i=0; i<length; ++i)
{
// This extra read makes the data gotten in the 2nd read below valid.
// Commented out, the data read into destination will not be valid.
uint32_t tmp = ((const volatile uint32_t*)m_rawData)[i];
(void)tmp; //pacify compiler
destination[i] = ((const volatile uint32_t*)m_rawData)[i];
}
Исходя из описания, похоже, что ваш код FPGA неправильно реагирует на загрузку инструкций, которые считываются из мест на вашей FPGA, и вызывает блокировку процессора. Это не сбой, это навсегда застопорилось, отсюда и необходимость полного сброса. У меня была эта проблема также при отладке логики PCIE на FPGA.
Еще одним признаком того, что ваша логика не отвечает правильно, является то, что вам нужно дополнительное чтение, чтобы получить правильные ответы.
Ваш цикл выполняет 32-битные загрузки, но memcpy выполняет как минимум 64-битные загрузки, что меняет реакцию вашей логики. Например, потребуется использовать два TLP с 32-битным откликом, если первые 128 битов завершения и следующие 32 бита во втором 128-битном TLP завершения.
Я обнаружил, что очень полезно добавить логику для регистрации всех транзакций PCIE в SRAM и иметь возможность выгрузить SRAM, чтобы посмотреть, как логика ведет себя или ведет себя неправильно. У нас есть отличная утилита, pcieflat, это печатает один TLP PCIE на строку. Это даже имеет документация.
Когда интерфейс PCIE работает недостаточно хорошо, я передаю журнал в UART в шестнадцатеричном формате, который может быть декодирован программой pcieflat.
Этот инструмент также полезен для устранения проблем с производительностью — вы можете посмотреть, насколько хорошо ваши чтения и записи DMA передаются по конвейеру.
В качестве альтернативы, если у вас есть встроенный логический анализатор или аналогичный в ПЛИС, вы можете отслеживать действия таким образом. Но лучше анализировать TLP в соответствии с протоколом PCIE.
Других решений пока нет …