Содержание

Errata_01: Энумерация PCI и размер BAR0

Когда БИОС сканирует устройства подключенные по шинам PCI Express, то он читает их конфигурационное пространство. Это пространство состоит из 256 байт, его структуру можно посмотреть в WIKI. В этом пространстве есть поля PID и DID идентифицирующие производителя и изделие, а так-же регистры BAR (Base Address Registers), которые указывают, какое адресное пространство необходимо выделить устройству для работы.

Конфигурационное пространство может быть двух типов Type0 и Type1. RootComplex (RC) устройства обычно используют Type1 формат, а EndPoint (EP) используют Type0 формат. Конкретный формат определен в поле HDR_TYPE регистра BIST_HEADER, который присутствует в обоих вариантах конфигурации. RootComplex проводит энумерацию подключенных к нему EndPoint-ов.

Конфигурация Type0 содержит 6 регистров BAR. Каждый регистр по отдельности позволяет запросить 32-разрядное окно в адресном пространстве PCI. Для того чтобы запросить 64-х разрядное окно необходимо использовать по два BAR регистра. Конфигурация Type1 содержит только 2 регистра BAR. В отличие от Type0, устройства с Type1 имеют некоторые отличия в фильтрации TLP (Transaction layer packet)

Процесс определения окон наглядно описан здесь - PCIe obtains BAR space length.

Общий алгоритм, по которому БИОС определяет размер запрошенной памяти такой:

  1. При подаче питания, EndPoint устройство конфигурирует свои регистры BAR_MASK. 1-цы в таком регистре делают ReadOnly нулевыми биты в соответствующем регистре BAR. Т.е. если в BAR0_MASK записать 0x0000_FFFF, то регистр BAR0 всегда будет читаться с нулевыми младшими битами - 0xXXXX_0000. Старшие же биты останутся доступны для записи мастеру шины, проводящим энумерацию.
  2. RootComplex при энумерации записывает 0xFFFF_FFFF в регистры BAR и читает их. Позиция старшего нуля в считанном слове, является степенью двойки того размера адресного пространства, которое EndPoint запрашивает через данный BAR. Если BAR читается нулем, то данный BAR не используется, т.е. выключен в EndPoint. В младших 4-х битах BAR находятся флаги для данного окна:
    • память (MEM) или область ввода-вывода (IO),
    • разрешена ли предвыборка (prefetch)
    • разрядность адресации 32/64 бит
  3. RootComplex выделяет стартовый адрес для запрошенного окна и записывает его в регистр BAR. Теперь EndPoint знает стартовый и конечный адрес выделенного ему пространства PCI.

Как задать размер окна, проще понять на примере. Пусть нам необходимо выделить PCI mem окно для внутренних адресов с 0xC000_0000 по 0xC097_0000. Т.е. окно необходимо размером 0x0097_0000, но ближайшая степень двойки дает длину 0x0100_0000. Соответственно, в BAR_MASK необходимо записать 0x00FF_FFFF.

Так может быть настроен любой из BAR0-BAR5 на запрос 32-х разрядного адресного окна для работы PCI устройства. Можно запросить несколько окон, если есть такая необходимость. Со времен 32-разрядных РС, для которых поддерживается совместимость и в 64-битных, размер окна PCI ограничен 1ГБ. Соответственно 32-х разрядному процессу остается 3ГБ. Необходимость во всех 6-ти BAR встречается редко, поэтому если необходимости в окне нет, то регистр BAR_MASK надо прописать 0xFFFF_FFFF, чтобы регистр BAR был 0. Тогда энумератор поймет, что BAR не используется.

В качестве примера, можно посмотреть спецификацию от TI - PCI Express.

Реализация в 1923КХ028 и ошибка ERRATA_01

В 1923КХ028 региcтры BAR и BAR_MASKED имеют один и тот же адрес. Например, BAR0 имеет адрес 0x10 и у BAR_MASKED адрес 0x10. Только BAR_MASKED является теневым регистром и для доступа к нему в адресе необходимо выставить бит CS2. Кроме этого регистр BAR_MASKED доступен только для записи, считать его нельзя.

У регистров BAR0_MASK, BAR2_MASK, BAR4_MASK нулевой бит является Enable для основного регистра BAR. Если бит равен нулю, то соответствующий BAR выключен и будет читаться нулевым. Остальные биты 1-31 являются масками ReadOnly для битов соответствующего BAR.

У регистров BAR1_MASK, BAR3_MASK, BAR5_MASK нет бита enable. Все биты 0-31 являются маской ReadOnly. Данные регистры предназначены для расширения соответствующих регистров BAR до 64-х разрядной адресации, на случай необходимости.

Для проверки того, как работает BAR0 использует следующий код:

#define KX028_PCIE_CS2_NORMAL_Msk  0x000000UL
#define KX028_PCIE_CS2_HIDDEN_Msk  0x100000UL

// BAR0 и маска BAR
#define KX028_PCIE_BAR0_REG                   0x10

//#define MDR_KX028_BAR0_MASK   0x0000FFFFUL
#define MDR_KX028_BAR0_MASK   0xFFFF0000UL
#define MDR_KX028_BAR0_ENA             1UL

// Переменные для наблюдения значений регистра BAR:
volatile uint32_t rdStart, rdMasked;

void MDR_KX028_M0_Test_Bar0(void)
{  
  // Начальное значение - флаги и начальный адрес окна
  rdStart = MDR_KX028_ReadAXI(KX028_PCIE_BAR0_REG);
  
  // Пытаемся записать маску BAR0_MASK
  MDR_KX028_WriteAXI(KX028_PCIE_CS2_HIDDEN_Msk | KX028_PCIE_BAR0_REG, MDR_KX028_BAR0_MASK | MDR_KX028_BAR0_ENA);
  
  // Читаем реакцию на запись 0xFFFFFFFF - размер окна
  MDR_KX028_WriteAXI(KX028_PCIE_BAR0_REG, 0xFFFFFFFF);
  rdMasked = MDR_KX028_ReadAXI(KX028_PCIE_BAR0_REG);
   
  //  Восстанавливаем исходное значение
  MDR_KX028_WriteAXI(KX028_PCIE_BAR0_REG, rdStart);
}

Исполнение функции показывает, что регистр BAR0_MASK нельзя записать вручную. Значение rdMasked получается равно 0x8000_000F, а не 0xFFFF0000. В значении 0x8000_000F ведущий 0 находится в 15-м бите - т.е. размер запрашиваемого окна составляет 2ГБ, что является недопустимым. Об этом и говорит ошибка в Errata за номером 0001.

Со слов разработчика, во второй ревизии 1923КХ028 значение регистра BAR0_MASK будет исправлено и доступ ко всему адресному пространству AXI будет доступно через BAR0. Пока же приходится использовать окна BAR2 и BAR4, но эти окна в сумме не дают доступ ко всем адресам на AXI шине. BAR2 обеспечивает доступ к блокам BMU1(0x100000) .. EGPI8(0x7F0000), а BAR4 к блокам EGPI9(0x800000) .. ETGPI8(0x8F0000). Блоки, начиная с ETGPI9(0x90000) уже не доступны для драйвера, т.к. к ним нет доступа через выделенные окна памяти BAR2 и BAR4.

Окна запрашиваемые через регистры BAR2 и BAR4 можно посмотреть аналогичной функцией. Итоговые значения приведены в таблице:

Значение BAR0 BAR2 BAR4
rdStart 0x0000_0000 0xDF00_0000 0xDF80_0000
rdMased 0x8000_000F 0xFF80_000F 0xFFF0_000F
"подадреса" 0x7FFF_FFFF 0x007F_FFFF 0x000F_FFFF
размер окна 2ГБ 8МБ 1МБ

Выводы из проверки записи в BAR регистры: Значения регистров BAR_MASKED заданы по умолчанию и поменять их нельзя. Они ReadOnly, за исключением вероятно младших 4-х бит. Следовательно, поменять запрашиваемый размер окна памяти программно нельзя.

Выделенные адреса окон доступа можно посмотреть, например, при загрузке драйвера. Отрывок из логов:

... DBGP_FEAT_HIF: dev->base_addr: 0xdf000000

Окна памяти PCI можно получить и так:

static bool kx028_pci_check_bar_resources(struct pci_dev *pdev, UINT bar, resource_size_t *start, resource_size_t *end)
{
  *start = pci_resource_start(pdev, bar);
  *end = pci_resource_end(pdev, bar);
  FP_DEBUG(DBGP_FEAT_HIF, FP_LOG_INFO, "PCI resource BAR%d mem start address: 0x%pa\n", bar, start);
  FP_DEBUG(DBGP_FEAT_HIF, FP_LOG_INFO, "PCI resource BAR%d mem end address: 0x%pa\n", bar, end);
  if (pci_resource_flags(pdev, bar) & IORESOURCE_MEM)
    return true;
  else	
  {
    FP_DEBUG(DBGP_FEAT_ERR, FP_LOG_EMERG, "Can't read PCI resource flags of BAR%d\n", bar);
    return false;
  }
}

ПОЛУЧАЕМ:
... DBGP_FEAT_HIF: PCI resource BAR2 mem start address: 0x0x00000000df000000
... DBGP_FEAT_HIF: PCI resource BAR2 mem end address: 0x0x00000000df7fffff
... DBGP_FEAT_HIF: PCI resource BAR4 mem start address: 0x0x00000000df800000
... DBGP_FEAT_HIF: PCI resource BAR4 mem end address: 0x0x00000000df8fffff

Трансляция адресов PCI в AXI

Итак, PCI адреса, выделенные при энумерации устройства с 1923КХ028 при помощи BAR2 и BAR4, дают нам два окна в PCI адресном пространстве:

  1. BAR2: 0xDF00_0000 .. 0xDFFF_FFFF
  2. BAR4: 0xDFD0_0000 .. 0xDFDF_FFFF

Обращаться к ресурсам 1923КХ028 из драйвера PCI можно только через эти два окна. Но шина AXI, на которой расположены структурные блоки 1923КХ028 наинается с адреса 0хС000_0000. В частности первый блок на шине BMU1 имеет адрес 0xС010_0000. Необходимо преобразование адресов, грубо говоря из 0xDF00_0000 в 0xС000_0000. Для этих целей используется блок iATU, который транслирует адреса PCI в AXI.

Для каждого BAR необходимо настроить свой регион iATU. В прошивке для управляющего микроконтроллера на плате это делается так:

BAR iATU регион IN_TARGET_ADDR IN_CTRL1 IN_CTRL2
BAR2 0 0xC000_0000 0x0 0xC000_0200
BAR4 1 0xC080_0000 0x0 0xC000_0400

В регистре IN_CTRL2 включается:

Соответственно, когда от PCI придет обращение в адрес BAR2(0xDF00_0000..0xDFFF_FFFF), то iATU переведет это в адрес региона, связанного с BAR2. Т.е. в 0xC000_0000 на шине AXI. Аналогично с BAR4.

В приведенной уже ранее ссылке "TI - PCI Express" есть еще один способ настройки трансляции адреся для Inbound TLP. Кроме этого там указано, что BAR0 не требует блока трансляции адреса. Адреса PCI, соответствующие окну BAR0, автоматически транслируются в адреса локальной шины AXI. Это сделано для того, чтобы драйвер изначально имел доступ ко всем регистрам устройства без необходимости предварительной настройки. Во второй ревизии 1923КХ028 ожидается что доступ через BAR0 будет работать так-же.

Рекомендую прочитать главу "2.7 Address Translation" в "TI - PCI Express" для более детального понимания вопросов связанных с BAR и трансляцией адресов PCI.