Zynq 7000. Прикручиваем Wi-Fi модуль RTL8822CS с использованием SDIO через EMIO

Наконец-то пришла пора продолжить изучение возможностей платы Zynq QMTech и SoC XC7Z020. Следующая интересная задача, которую я для себя придумал в качестве обучающей — оснастить плату Wi-Fi модулем Realtek RTL8822CS и, если Wi-Fi модуль будет не нужен, а нужна будет ещё одна флешка — вторым портом для SD-карточки. Если интересны подробности того, как я это всё реализовал — добро пожаловать под кат. 

1f40b1842a5c4beba0adc49d0afcdc35.png

Важно! Перед началом повествования, хотелось бы заранее оговориться, что основная цель, которую я преследую при написании этой статьи — рассказать о своем опыте, с чего можно начать, при изучении отладочных плат на базе Zynq. Я не являюсь профессиональным разработчиком под ПЛИС и SoC Zynq, не являюсь системным программистом под Linux и могу допускать какие-либо ошибки в использовании терминологии, использовать не самые оптимальные пути решения задач, etc. Но отмечу, что любая конструктивная и аргументированная критика только приветствуется. Что ж, поехали…

Сформулируем цель и задачи

Итак, у нас есть плата, описанная в этой (https://habr.com/ru/post/559946/) статье и есть желание прикрутить к ней Wi-Fi модуль Realtek RTL8822CS. Модуль, разумеется, должен корректно инициализироваться в ОС Linux и обеспечить пропускную способность до 150 Мбит\с (с чем связано такое ограничение  и именно этой цифрой — ниже). В качестве альтернативного варианта для использования выведенного SDIO — необходимо организовать второй порт для подключения SD-карт.

Исходя из этого можно сформировать несколько задач, решив которые мы можем достигнуть цели:

  1. Проверить возможность выноса линий SDIO через EMIO (т.е. через пины PL-части Zynq) и подготовить bitstream-файл;

  2. Проверить работоспособность SDIO через EMIO в приложении baremetal с использованием microSD-карточки;

  3. Изготовить мезонинные платы для Wi-Fi модуля и смонтировать на них компоненты;

  4. Подготовить First Stage Bootloader, Device Tree Source, U-Boot, Linux Kernel в нужной конфигурации для запуска Wi-Fi модуля;

  5. Подготовить модуль ядра для работы Wi-Fi модуля, а именно модуль cfg80211, который является линуксовым API для 802.11 устройств;

  6. Подготовить модуль ядра 88×2cs, который является драйвером для модуля Wi-Fi RTL8822CS который работает поверх SDIO-шины;

  7. Проверить работоспособность сети, сделать сканы эфира, посмотреть рабочие уровни, сделать замеры скорости downstream/upstream-трафиком через iPerf;

Итак. Перейдем к реализации намеченного, в целом, не выглядит сильно сложно =)

Настройка проекта в Vivado и конфигурация PL

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

Открываем Vivado и создаем проект через кнопку Create Project. Откроется мастер настройки нового проекта, быстро пройдемся по шагам. 

  1. Даём имя проекту, например SDIO_EMIO и указываем папку сохранения;

  2. Указываем, что это RTL Project, оставляем включенной галочку Do not specify sources at this time;

  3. Находим модель SoC использованного на плате. В моем случае это xc7z020clg400–1;

  4. Нажимаем Finish.

В левом меню IP Integrator нажимаем Create Block Design и создаем новый дизайн. Назовём его для простоты block_design.

Добавляем примитив процессорной системы Zynq на диаграмму через нажатие правой кнопкой Add IP — ZYNQ7 Processing System. Сразу же выполним предложенные действия от автоматизатора, через команду на зеленом поле Run Block Automation и откроем настройки процессорной системы. 

Я заранее сохранил шаблон настроек PS, чтобы в каждом проекте не прописывать всё заново. Взять его можно тут (https://github.com/megalloid/zynq_qmtech/blob/main/ZynqQMTech.tcl). Через меню Presets — Apply Configuration применяем его и сразу переходим в меню Peripherial I/O Pins.

Ставим галочку возле меню SD 1 и проверяем, что выбран вариант подключения через EMIO:

5224e625a4ecf1a10e2699c08bd368cc.png

Остальные настройки я оставил без изменений, в т.ч. частоты тактирования SDIO, PL. Получаем следующую картину (если развернуть группу сигнальных линий SDIO_1):

1b4f6f3853c024147049ddb371be1fa0.png

В данном случае нас интересуют линии CLK, CMD и 4 линии DATA, что в общем-то является стандартным набором для SDIO-шины. Но вот незадача, каждая из линий организована тремя отдельными сигналами. Для решения этой задачи нам необходимо руками создать буфер IOBUF, который будет управляться через активный пин T с тремя состояниями и разрулит Input, Output сигналы как нам нужно для использования его с двунаправленным сигналом I/O. Выглядит он следующим образом:

6c4d7658be9b0cf6c1c2d277866bb42e.png

Т.к. в стандартной библиотеке графических примитивов он отсутствует — нужно будет создать свой IP-модуль и добавить его на схему. Для каждого из сигнала он будет свой.  

Через меню Sources — Add Sources (Alt + A) добавляем Design Source — Create File, выбираем тип файла Verilog и назовём его iobuf_cmd.

Запишем следующее содержимое:

module iobuf_cmd	
(
    input iobuf_i,     // Вход буфера
    input iobuf_t,     // Входной сигнал с тремя состояниями, high=input, low=output
    output iobuf_o,    // Выход буфера
    inout  iobuf_io     	// Двунаправленный порт (подключается напрямую к top-level порту)
);

IOBUF #(
    .DRIVE(12),                 // Указывается Drive Strength для сигналов
    .IBUF_LOW_PWR("FALSE"),     // Low Power - "TRUE", High Perforrmance = "FALSE"
    .IOSTANDARD("LVCMOS33"),    // Указывается стандарт сигнала
    .SLEW("FAST")               // Указывается slew rate
) IOBUF_inst (
    .O(iobuf_o),      // Выход буфера
    .IO(iobuf_io),    // Двунаправленный порт
    .I(iobuf_i),      // Вход буфера
    .T(iobuf_t)       // Входной сигнал с тремя состояниями
);
endmodule 

То же самое сделаем для сигнала CLK, только файл назовём iobuf_clk. Запишем в него следующее содержание:

module iobuf_clk	
(
    input iobuf_i,     // Вход буфера
    input iobuf_t,     // Входной сигнал с тремя состояниями, high=input, low=output
    output iobuf_o,    // Выход буфера
    inout  iobuf_io    // Двунаправленный порт (подключается напрямую к top-level порту)
);

IOBUF #(
    .DRIVE(12),                 // Указывается Drive Strength для сигналов
    .IBUF_LOW_PWR("FALSE"),     // Low Power - "TRUE", High Perforrmance = "FALSE"
    .IOSTANDARD("LVCMOS33"),    // Указывается стандарт сигнала
    .SLEW("FAST")               // Указывается slew rate
) IOBUF_inst (
    .O(iobuf_o),      // Выход буфера
    .IO(iobuf_io),    // Двунаправленный порт
    .I(iobuf_i),      // Вход буфера
    .T(iobuf_t)       // Входной сигнал с тремя состояниями
);
endmodule 

С SDIO DATA немного сложнее т.к. она состоит из нескольких линий. Так же создадим файл iobuf_data и запишем в него следующее содержимое:

module iobuf_data
(
    input [3:0] iobuf_i,    // Вход буфера
    input iobuf_t,          // Входной сигнал с тремя состояниями, high=input, low=output
    output [3:0]iobuf_o,    // Выход буфера
    inout [3:0] iobuf_io    // Двунаправленный порт (подключается напрямую к top-level порту)
);

    generate  genvar i ; for (i=0; i<4; i=i+1) 
    begin 

    IOBUF #(
    .DRIVE(12),                 // Указывается Drive Strength для сигналов
    .IBUF_LOW_PWR("FALSE"),     // Low Power - "TRUE", High Perforrmance = "FALSE"
    .IOSTANDARD("LVCMOS33"),    // Указывается стандарт сигнала
    .SLEW("FAST")               // Указывается slew rate
)
     IOBUF_inst (
                .O  (iobuf_o[i]),  // Выход буфера
                .IO (iobuf_io[i]), // Двунаправленный порт
                .I  (iobuf_i[i]),  // Вход буфера
                .T  (iobuf_t)      // Входной сигнал с тремя состояниями
             );
    end
    endgenerate
endmodule

Для каждой из линии создается свой экземпляр примитива IOBUF.

Поочередно размещаем добавленные модули на диаграмму через клик правой мышкой по Diagram — Add Module. Соединяем сигналы с нужными портами и получится следующее:

60c884937e087e0faa2b3445b80bfb74.png

Хотелось бы обратить внимание на две важные вещи:

  1. На порт iobuf_t модуля для сигнала CLK я добавил Constant с шириной в 1 бит и со значением 0, т.к. управляющего T-пина для CLK не предусмотрено;

  2. Сделал порты iobuf_io как External и дал им соответствующие названия;

  3. Соединил порты Input с портами Output;

На текущем этапе можно запустить синтез и назначить выходы портов на реальные физические порты. Для этого нужно:

  1. Нажимаем на меню block_design правой кнопкой и выбираем команду Create HDL Wrapper

  2. В меню IP Integrator выбираем Generate Block Design;  

  3. Выбираем Synthesis Option — Global и нажимаем Apply, затем Generate

  4. Дожидаемся окончания генерации;

  5. Выбираем Run Synthesis для старта синтеза нашего дизайна.

  6. Дожидаемся окончания синтеза;

После окончания синтеза переходим в меню Open Synthesized Design и откроется меню Package с нижним меню I/O Ports. Далее необходимо:

  1. Отметить подходящие физические I/O пины

  2. Убедиться, что включена подтяжка PULLUP на всех пинах кроме CLK;

В моем случае получается следующее:

e3896e4c823181edc37e95018b770af4.png

Сохраняем настройки, будет предложено создать файл с constraints. Называем его как удобно и нажимаем Ok. После нажимаем Generate bitstream, соглашаемся с тем, что синтез устарел и ждем окончания всех необходимых процедур до получения сообщения:

36a53db4a28eae08bea232d61d4c997b.png

После этого можем экспортировать BSP в SDK и запустить проверку работоспособности SDIO в baremetal. Делается это через меню File — Export — Export Hardware, ставим Include bitstream. Нажимаем Ok и запускаем SDK через команду File — Launch SDK.

Проверка SDIO через microSD-карту в Baremetal

Прежде чем приступить к проверке необходимо подключить microSD-карту к пинам, которые мы размечали в следующем разделе. Смотрим в первую очередь на распиновку microSD-карты:

594d64fd794748b2345a683a28f90da6.png

С пинами VDD (3.3V) и VSS (GND) всё в целом понятно. С другими пинами есть нюанс, для пина CMD и шины DATA требуется подтяжка к высокому уровню через 10 кОм резисторы. В моем случае, чтобы не городить навесным монтажом резисторы, я взял одну из плат, заготовленных для Wi-Fi модуля и запаял на него все что нужно:

3a079740ff6982ba2b59b4f298ac483f.png

Если посмотреть на принципиальную схему этой платы — все выглядит вот таким образом:

76eab6fef6c96a4b520b67d4177ca83e.png

Питание подключено напрямую в соответствии с тем, как оно выведено на гребенку. Для удобства отладки логическим анализатором — я подпаивал МГТФ-проводки. 

После того, как все необходимые линии подключены — можно вернуться в SDK к созданию проекта. После запуска SDK обзываем проект SDIO_EMIO и нажимаем Next, выбираем шаблон проекта Hello World.

e68cae1f9e9d65ce3c694c6b7130053c.png

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

#include 
#include "platform.h"
#include "xil_printf.h"
#include "xsdps.h"
#include "xparameters.h"
#include "xil_types.h"

int main()
{
		int Status;

		static XSdPs SdInstance;
		XSdPs_Config *SdConfig;

    init_platform();

    xil_printf("\r\nSD Raw Read/ Write Test\r\n");

		SdConfig = XSdPs_LookupConfig(XPAR_XSDPS_1_DEVICE_ID); 	
    if (NULL == SdConfig)
    {
      xil_printf("XSdPs_LookupConfig failed\r\n");
      return XST_FAILURE;
    }
    else
    {
      xil_printf("XSdPs_LookupConfig ok \r\n");
    }

    Status = XSdPs_CfgInitialize(&SdInstance, SdConfig, SdConfig->BaseAddress);
    if (Status != XST_SUCCESS)
    {
      xil_printf("XSdPs_CfgInitialize failed \r\n");
      return XST_FAILURE;
    }
    else
    {
      xil_printf("XSdPs_CfgInitialize ok \r\n");
    }

    Status = XSdPs_CardInitialize(&SdInstance);
    xil_printf("XSdPs_CardInitialize... \r\n");
    if (Status != XST_SUCCESS)
    {
      xil_printf("XSdPs_CardInitialize failed \r\n");
    }
    else
    {
      xil_printf("XSdPs_CardInitialize ok \r\n");
    }
}

Кликаем правой кнопкой по проекту SDIO_EMIO и выбираем Build Project. Смотрим, что проект собирается без проблем. Важный момент — выбрать правильный SDIO-контроллер в строке SdConfig = XSdPs_LookupConfig (XPAR_XSDPS_1_DEVICE_ID);

Подключаем JTAG-отладчик и после сборки проекта заливаем bitstream-файл выполнив команду Xilinx — Program FPGA и дожидаемся сигнала FPGA DONE на плате. 

Открываем Serial Port, который принадлежит нашей плате. Например: minicom -D /dev/ttyUSB0. Узнать какой порт принадлежит плате можно запустив команду udevadm monitor | grep ttyUSB и выткнуть-воткнуть miniUSB кабель из платы. Там можно увидеть сообщения по которым можно понять какой у нас порт. Например:

megalloid@megalloid-lenovo:~$ udevadm monitor | grep ttyUSB
KERNEL[191576.781021] remove   /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0/tty/ttyUSB0 (tty)
KERNEL[191576.781124] unbind   /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0 (usb-serial)
KERNEL[191576.781170] remove   /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0 (usb-serial)
UDEV  [191576.796568] remove   /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0/tty/ttyUSB0 (tty)
UDEV  [191576.804931] unbind   /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0 (usb-serial)
UDEV  [191576.811644] remove   /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0 (usb-serial)
KERNEL[191579.176521] add      /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0 (usb-serial)
KERNEL[191579.177593] add      /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0/tty/ttyUSB0 (tty)
KERNEL[191579.177706] bind     /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0 (usb-serial)
UDEV  [191579.217863] add      /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0 (usb-serial)
UDEV  [191579.225195] add      /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0/tty/ttyUSB0 (tty)
UDEV  [191579.229537] bind     /devices/pci0000:00/0000:00:1d.0/0000:05:00.0/0000:06:01.0/0000:08:00.0/0000:09:02.0/0000:0a:00.0/usb3/3-2/3-2.1/3-2.1.3/3-2.1.3.2/3-2.1.3.2.4/3-2.1.3.2.4:1.0/ttyUSB0 (usb-serial)

После запускаем проект также кликнув по корневому значку проекта выбрав меню Run As — Launch on hardware (System Debugger). В выводе в консоль мы должны увидеть следующее:

509c9fe1c167cc3f9ae22df02ed60e50.png

Что означают эти сообщения? Это значит, что драйвер успешно «достучался» до карточки и может спокойно начать с ней обмен. Значит наш SDIO-контроллер работает. Идём дальше.

Подробнее о standalone-драйвере можно почитать тут: https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18841881/Zynq+SD+Standalone+driver

Трассировка и изготовление мезонинных плат для Wi-Fi модуля

Следующим шагом нужно подготовить платы, на которые я запаяю радиомодуль, со всей необходимой обвязкой. В качестве первого радиомодуля был взят модуль от FN-Link с Realtek RTL8822CS чипом, модель модуля 6222B-SRC. Подробнее можно посмотреть тут: https://wikidevi.wi-cat.ru/Fn-Link_6222B-SRC, Datasheet: https://www.signal.com.tr/pdf/cat/6222B-SRC_03.pdf

Кратко скажу, что это простой MIMO 2×2 Dual Band модуль Wi-Fi с поддержкой 802.11ac и Bluetooth 4.2 или 5.0 в зависимости от модели. 

Так как модуль мы будем подключать к гребенке PLS на плате с шагом в 2.54 мм. — было решено сделать такой же разъем и на мезонинной плате. 

Распиновка со стороны платы:  

fd6beaba330e9c276e89c1c70635dc0e.png

Распиновка со стороны модуля:

c08551665f95f38e517da467eb9b9225.png

Решено было вывести в т.ч. и Bluetooth для дальнейших экспериментов. В итоге общая обвязка модуля выглядит следующим образом:

6477985104680c2496ae428262f1ef6d.png

Всё в целом достаточно примитивно и в соответствии с референсной схемой и небольшими изменениями, такими как пин включения\выключения питания модуля и прочих мелких доработок. Антенный тракт было решено вывести на u.FL-разъем, для удобства и возможности подключения любых подходящих для 2.4/5ГГц антенн. 

Получившиеся платы достаточно просты в трассировке и не представляют собой никакого технического ноу-хау, но линии данных нужно выровнять т.к. SDIO-шина тут достаточно скоростная, чтобы пренебречь таким важным аспектом трассировки. 

Результат трассировки в 2D:

e6a0888e65459464618817f0dd7e2089.png

Результат трассировки в 3D:

52150ff46241c57edceb55ef6fe13b8d.png

Вид сверху:

2ca5cce635682082f3f3895c559998d7.png

Вид снизу:

d51b5848e2903bdc36415732f10b45e7.png

Вид платы после монтажа компонентов:

381d11d093d12b6ab6a05cfd90a13316.png

Т.к. плата изначально разрабатывалась не только для этой версии Wi-Fi модуля — остались не установленными некоторые компоненты. 

В целом можно переходить к подготовке всего необходимого для работы с радиомодулем с точки зрения основных управляющих сигналов и подготовки необходимых файлов для работы радиомодуля в Linux. Об этом в следующих главах.

Небольшое дополнение проекта в Vivado

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

  1. Подать низкое напряжение на затвор P-канального ключа VT1, т.е. на линию WL_BT_ON, чтобы подать питание на радиомодуль;

  2. Подать высокое напряжение на ножку WL_REG_ON радиомодуля, чтобы запустить его;

Поэтому на этом этапе нам будет нужно слегка модернизировать проект и добавить 2 сигнала с AXI GPIO-контроллера и завести их на соответствующие ножки:  

  1. Для транзисторного ключа это ножка 11 на общей гребенке, что соответствует физическому пину W20;

  2. Для сигнала WL_REG_ON это 25-я ножка на той же гребенке, что соответствует в моем случае пину V17;

Как работать с AXI GPIO Я рассказывал в этой статье (https://habr.com/ru/post/565368/) и на деталях повторяться не буду. В нашем случае нужно выполнить лишь несколько шагов:

  1. Добавляем в дизайн IP-модуль AXI GPIO;

  2. Выполняем предложенные шаги автоматизации предложенные Vivado, отметив все галочки;

  3. Появится блок AXI GPIO, необходимо зайти в его опции и поставить галочку All Outputs, поставить ширину шины GPIO Width в значение 2 (нужно 2 сигнала) и в Default Output вписать 0×00000002, чтобы по умолчанию после загрузки bitstream-файла оба пина приняли необходимое значение;

  4. Называем External Interface от AXI GPIO как MGMT_GPIO и сохраняемся. Должно получиться следующее:

23498f1072db7330570a736b1a6085e3.png

  1. Синтезируем дизайн, проверяем, что всё прошло успешно;

  2. Переходим в меню Open Synthesized Design и назначаем MGMT_GPIO[0] пин W20, а MGMT_GPIO[1] пин V17;

  3. Изменяем у этих двух пинов I/O Std на LVCMOS3.3 и сохраняем;

  4. Запускаем генерацию bitstream-файла через Generate bitstream;

  5. Экспортируем сгенерированный bitstream и всё что необходимо для дальнейших шагов через команду File — Export — Export Hardware;  

Все изменения, необходимые на этом этапе мы внесли. Переходим к подготовке всего необходимого для работы с радиомодулем в Linux;

Подготовка FSBL, Device Tree Sources, U-Boot

На этом этапе можно переходить к созданию загрузчиков, файлу описания железа и компиляции ядра Linux. Многие моменты и детали Я целенаправленно опущу т.к. уже рассказывал в предыдущей статье (https://habr.com/ru/post/565368/), а отличающимся моментам посвящу основную часть повествования. 

Первым этапом надо подготовить FSBL. Для этого нужно:

  1. В меню Xilinx SDK переходим в меню File — New — Application Project;

  2. Именуем его как FSBL;

  3. Нажимаем клавишу Next;

  4. Из заготовок выбираем Zynq FSBL;

  5. Дожидаемся окончания компиляции и проверяем, что появился файл FSBL.elf

Вторым этапом необходимо подготовить Device Tree Source/Blob файл который будет использован для сборки U-Boot. Для этого сделаем следующее:

  1. Добавляем BSP Repository из репозитория device-tree-xlnx. Для этого нужно выбрать меню Xilinx — Repositories и указать папку с device-tree-xlnx в секцию Global Repositories

  2. Добавляем BSP Project через меню File — New — Board Support Package, даём ему имя и выбираем внизу меню device_tree и в следующем меню нажимаем Ok, если не требуется внесения каких-либо изменений. 

  3. Выполним редактирование файла zynq-7000.dtsiи находим секцию описывающую SDIO1 в строке sdhci1: mmc@e0101000;

Дополняем секцию следующим содержанием (файл можно взять тут — https://github.com/megalloid/zynq_qmtech/blob/main/zynq-7000.dtsi) :

68afa89dac5d3025b33798e58a3833c8.png

Переходим в папку с сгенерированными dts-файлами в системной консоли:

megalloid@megalloid-lenovo:~$ cd Zynq/Projects/SDIO_EMIO/SDIO_EMIO.sdk/device_tree/

Компонуем исходники:

gcc -I my_dts -E -nostdinc -undef -D__DTS__ -x assembler-with-cpp -o system.dts system-top.dts

Компилируем в blob:

dtc -I dts -O dtb -o system.dtb system.dts

Всё. Device Tree готов.

Следующим этапом необходимо подготовить U-Boot для нашей платы. Нужно выполнить несколько простых шагов:

  1. Перейти в папку с U-Boot: cd u-boot-xlnx

  2. Задать переменные для кросс-компиляции: export CROSS_COMPILE=arm-linux-gnueabihf-; export ARCH=arm

  3. Очистить сырцы от предыдущих результатов компиляции и применить конфиг для Zynq по умолчанию: make distclean; make xilinx_zynq_virt_defconfig

  4. Положить в папку с U-Boot полученный в предыдущем шаге system.dtb в папку arch/arm/dts/ c именем zynq-qmtech.dtb

  5. Указываем какой Device Tree файл использовать: export DEVICE_TREE=«zynq-qmtech»

  6. Далее необходимо зайти в меню конфигуратора и изменить ряд опций: make menuconfig 

  7. Опция для того, чтобы активировать поддержку AXI GPIO, которая используется для включения питания модуля и подачи сигнала WL_REG_ON на радиомодуле: Device Drivers — GPIO Support — Xilinx GPIO Driver 

  8. Сохраняем конфиг (можно взять тут) в файл .config

  9. Запускаем сборку: make -j$(nproc) и дожидаемся окончания компиляции;

После этого можно скомпоновать FSBL, Bitstream-файл и U-Boot в один загрузочный файл BOOT.BIN. Подробное описание компоновки можно найти в моей прошлой статье (https://habr.com/ru/post/565368/). В целом надо выполнить несколько простых шагов:

  1. Найти файл в каталоге с проектом First stage bootloader. По умолчанию он находится в SDIO_EMIO/SDIO_EMIO.sdk/FSBL/Debug/FSBL.elf

  2. Найти Bitstream-файл для программируемой логики. Найти его можно в каталоге SDIO_EMIO/SDIO_EMIO.sdk/block_design_wrapper_hw_platform_0/block_design_wrapper.bit

  3. Найти бинарный файл U-Boot. Данный файл после компиляции лежит в папке с U-Boot: u-boot-xlnx/u-boot.elf

  4. Открываем в Xilinx SDK пункт меню Xilinx — Create Boot Image;

  5. Выбираем куда сохранить новый bif-файл, я его сохраняю в корневом каталоге проекта;

  6. Добавляем поочередно файлы FSBL.elf, block_design_wrapper.bit, u-boot.elf в секцию Boot Image Partitions;

  7. Нажимаем Create Image и видим как в каталоге с bif-файлом появляется BOOT.BIN;

Теперь необходимо залить этот файл на загрузочную SD-карту и можно попробовать загрузиться. Если мы видим приглашение от U-Boot и что светится светодиод FPGA DONE — значит мы всё выполнили правильно. 

d5746a95acb71fa17314158be3af4802.png

Можно перейти к проверке того, проинициализирован ли AXI GPIO в U-Boot и проверим их. Для этого сначала выведем список устройств которые проинициализированы. Это можно сделать через команду dm tree.

a9ad52c2f7ca103c3f6b91c3bf6ba6c3.png

Отлично. Видно, что у нас на борту есть два SDIO-контроллера, и два GPIO-контроллера. Попробуем изменить состояние ножки транзистора. Для этого нужно:

  1. Посмотреть какие адреса в GPIO доступны и какие отвечают за те, или иные ножки:   gpio status -a

  2. В конце вывода видно, что есть Bank GPIO с двумя ножками, которые соответствуют значению по умолчанию:

    1. Проверки ради можно изменить состояние ножки gpio@412000000 через команду: gpio toogle gpio@412000000 или gpio set gpio@412000000 / gpio clear gpio@412000000 для установки логической 1 или 0 соответственно

Так. Загрузчик подготовлен. Теперь можно переходить к подготовке ядра Linux и RootFS со всем необходимым для нас содержимым

Компиляция ядра Linux и подготовка RootFS с использованием Buildroot

Так же как и прошлых главах, для исключения необходимости подробного объяснения каждого шага, сошлюсь на предыдущую статью (https://habr.com/ru/post/565368/) как скомпилировать ядро Linux. Перейдем к выполнению шагов, необходимых для сборки ядра с необходимыми модулями и драйверами. Для этого нужно:

  1. Перейти в папку с клонированным репозиторием linux-xlnx;

  2. Обновить его (при необходимости): cd linux-xlnx; git fetch -p; git checkout master; git pull;  

  3. Экспортируем также переменные для кросс-компиляции: export CROSS_COMPILE=arm-linux-gnueabihf-; export ARCH=arm

  4. Делаем очистку от результатов предыдущих сборок: make distclean

  5. Загружаем дефолтный конфиг: make ARCH=arm xilinx_zynq_defconfig;

  6. Изменим дефолтный конфиг загрузив меню конфигурации: make ARCH=arm menuconfig

  7. Изменим опции конфигурации (можно взять тут https://github.com/megalloid/zynq_qmtech/blob/main/kernel.config) , устанавливаем опцию (проследим, что там вместо [M] в поле выбора стоит символ звездочки [*] : Networking Support — Wireless — cfg80211 

  8. Сохраняем конфиг в файл .config и выходим из меню конфигурации;

  9. Запускаем процесс компиляции: make ARCH=arm UIMAGE_LOADADDR=0×8000 uImage -j8

  10. Компилируем модули ядра: make ARCH=arm -j8 modules

  11. Копируем uImage из каталога linux-xlnx/arch/arm/boot/ на загрузочную SD-карту;

Далее необходимо подготовить образ RootFS с всеми необходимым userspace-утилитами, типа wpa_supplicant, wpa_cli, etc. Собирать RootFS будем с помощью системы сборки buildroot, статью о которой Я писал до этого (https://habr.com/ru/post/567408/). Итак, перейдем к сборке, для этого нужно:

  1. Заходим в папку с buildroot: cd buildroot

  2. Обновляем его до последней версии: git fetch -p; git checkout master; git pull;

  3. Делаем очистку от результатов предыдущих сборок: make distclean

  4. Запускаем меню конфигурации: make -C /home/megalloid/buildroot O=$PWD ARCH=arm nconfig

    Изменяем опции конфигурации по списку ниже;

  5. Опция 1: Target options — Target Architecture — ARM (little endian)

  6. Опция 2: Target options — Target Architecture Variant — cortex-A9

  7. Опция 3: Target options — Enable NEON SIMD extension support — [*]

  8. Опция 4: Target options — Enable VFP extension support — [*]

  9. Опция 5: Target options — Target ABI — EABIhf

  10. Опция 6: Target options — Floating point strategy — NEON 

  11. Опция 7: Build options — Number of jobs to run simultaneously — 8 (по количеству ядер CPU)

  12. Опция 8: Toolchain — C library — glibc

  13. Опция 9: Toolchain — Kernel headers — Custom Git Repository 

  14. Опция 10: Toolchain — URL of custom repository — 

  15. Опция 11: Toolchain — Custom repository version — <хэш-сумма от git show-ref | grep master из папки linux-xlnx>

  16. Опция 12: Toolchain — Custom kernel headers series — 5.15.x

  17. Опция 13: Toolchain — Install glibc utilities — [*]

  18. Опция 14: Toolchain — GCC compiler Version — gcc 9.x

  19. Опция 15: Toolchain — Enable C++ support — [*]

  20. Опция 16: Toolchain — Enable compiler link-time-optimization support — [*]

  21. Опция 17: Toolchain — Enable compiler OpenMP support — [*]

  22. Опция 18: Target packages — Miscellaneous — haveged

  23. Опция 19: Target packages — Networking applications — dhcpd

  24. Опция 20: Target packages — Networking applications — dropbear

  25. Опция 21: Target packages — Networking applications — ifupdown scripts

  26. Опция 22: Target packages — Networking applications — hostapd

  27. Опция 23: Target packages — Networking applications — iperf

  28. Опция 24: Target packages — Networking applications — iperf3

  29. Опция 25: Target packages — Networking applications — fping

  30. Опция 26: Target packages — Networking applications — iw

  31. Опция 27: Target packages — Networking applications — iwd

  32. Опция 28: Target packages — Networking applications — wpa_supplicant

  33. Опция 28: Target packages — Networking applications — wireless tools

  34. Опция 29: Filesystem images — cpio the root filesystem — [*]

  35. Опция 30:   Filesystem images — Compression method — lzma

  36. Опция 31: Host Utilites — host dosfstools

  37. Опция 32: Host Utilites — host genimage

  38. Опция 33: Host Utilites — host mtools

  39. После этой длительной конфигурации выбираем Save и сохраняем конфиг (можно взять тут и подложить к себе https://github.com/megalloid/zynq_qmtech/blob/main/buildroot.config);

  40. Нажимаем F9 и запускаем процесс сборки: make -C /home/megalloid/buildroot O=» class=«formula inline»>PWD ARCH=arm BR2_JLEVEL=»(((nproc) — 1))»

  41. Ждём когда закончится компиляция, самое время заварить и выпить кофе =)

Теперь когда образ RootFS собрался надо его подписать и привести к виду, в котором его сможет корректно переварить U-Boot:

  1. Переходим в папку с готовыми образами: cd buildroot/images

  2. Производим подпись: mkimage -A arm -T ramdisk -C gzip -d rootfs.cpio uramdisk.image.gz

  3. Складываем uramdisk.image.gz на загрузочную SD-карту;

Настраиваем загрузку образов в U-Boot:

  1. Надо загрузиться в консоль U-Boot и записать туда: setenv mmc_boot «fatload mmc 0 0×2000000 uramdisk.image.gz; fatload mmc 0 0×4000000 uImage; fatload mmc 0 0×4500000 system.dtb; bootm 0×4000000 0×2000000 0×4500000;»

  2. Сохраняем эту переменную через saveenv и перезагружаемся;

  3. Дожидаемся загрузки ОС;

Теперь можно переходить к компиляции драйвера и непосредственно настройке Wi-Fi модуля.

Компиляция модуля ядра 88×2cs для Wi-Fi модуля

Переходим к последнему элементу, необходимому для работы Wi-Fi модуля — драйверу нашего Wi-Fi модуля. На просторах GITHUB я нашел вполне себе собираемый и рабочий вариант драйвера. Чтобы собрать драйвер out-of-tree нужно сделать следующее:

  1. Склонировать исходные коды себе: git clone https://github.com/megalloid/rtl88×2cs

  2. Переключим ветку на версию драйвера совместимую с 5.15: git checkout tune_for_jethub_5_14

  3. Запускаем компиляцию: make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KSRC=»/home/megalloid/linux-xlnx» (где KSRC — путь до папки с ядром LInux из предыдущего шага);

  4. На выходе мы получим в этой папке файл 88×2cs.ko, его нужно положить туда же на загрузочную SD-карту;

Проверяем, загрузится ли модуль ядра и появится ли wlan-интерфейс:

  1. Монтируем SD-карту в файловую систему: mount /dev/mmcblk0p1 /mnt

  2. Переходим в каталог SD-карты: cd /mnt/

  3. Выполняем insmod 88×2cs.ko для загрузки модуля ядра;

Запускаем программу ifconfig — и видим, что появился wlan0 интерфейс:

71fefcc9af149185e1a6ac8b0e0f437b.png

Это говорит о том, что модуль ядра успешно загрузился и готов к работе. В следующей главе перейдем к настройке wlan0 интерфейса и проверке работоспособности Wi-Fi, прогону скорости через iPerf;

Про модули ядра можно почитать тут: https://habr.com/ru/post/117654/

Настройка Wi-Fi интерфейса и измерение скорости с использованием iPerf

Для настройки Wi-Fi необходимо первым делом создать конфиг, удобнее всего в этом случае его создать на SD-карте в виде файла wpa_supplicant.conf

Выполним в консоли Zynq следующие команды:

  1. Создадим файл конфигурации: touch /mnt/wpa_supplicant.conf

  2. Открываем его на редактирование: vi /mnt/wpa_supplicant.conf

  3. Записываем в него следующее содержимое и сохраняем:  

network={
	ssid="ASUS"
	scan_ssid=1
	key_mgmt=WPA-PSK
	psk="megapassword"
}
  1. Запускаем wpa_supplicant: wpa_supplicant -B -Dnl80211 -iwlan0 -c/mnt/wpa_supplicant.conf -P/var/run/wpa_supplicant.pid -dd -f/mnt/wpa_supplicant.log

  2. Через некоторое время появится IP-адрес на интерфейсе wlan0 и будет установлена связь с роутером;

Теперь можно прогнать трафик с компьютера (который подключен к роутеру кабелем) на Zynq и обратно. Для этого нужно:

  1. Запустить на компьютере: iperf3 -s

  2. Запустить на Zynq: iperf3 -c -N -i1 -t60 -u 

  3. Чтобы пустить трафик в обратном направлении на Zynq нужно выполнить: iperf3 -c -N -i1 -t60 -R -u

Наблюдаем результат:

# iperf3 -c 192.168.2.3 -N -i1 -u -b300M
Connecting to host 192.168.2.3, port 5201
[  5] local 192.168.2.233 port 55279 connected to 192.168.2.3 port 5201
[ ID] Interval           Transfer     Bitrate         Total Datagrams
[  5]   0.00-1.00   sec  15.9 MBytes   133 Mbits/sec  11481  
[  5]   1.00-2.00   sec  16.8 MBytes   141 Mbits/sec  12154  
[  5]   2.00-3.00   sec  17.0 MBytes   143 Mbits/sec  12336  
[  5]   3.00-4.00   sec  17.0 MBytes   142 Mbits/sec  12275  
[  5]   4.00-5.00   sec  16.3 MBytes   137 Mbits/sec  11812  
[  5]   5.00-6.00   sec  16.3 MBytes   137 Mbits/sec  11831  
[  5]   6.00-7.00   sec  17.0 MBytes   143 Mbits/sec  12332  
[  5]   7.00-8.00   sec  10.4 MBytes  87.4 Mbits/sec  7545  
[  5]   8.00-9.00   sec  14.8 MBytes   124 Mbits/sec  10736  
[  5]   9.00-10.00  sec  15.3 MBytes   128 Mbits/sec  11050  

- - - - - - - - - - - - - - - - - - - - - - - - -

[ ID] Interval           Transfer     Bitrate         Jitter    Lost/Total Datagrams
[  5]   0.00-10.00  sec   157 MBytes   132 Mbits/sec  0.000 ms  0/113552 (0%)  sender
[  5]   0.00-10.03  sec   157 MBytes   131 Mbits/sec  0.066 ms  0/113551 (0%)  receiver
iperf Done.

# iperf3 -c 192.168.2.3 -N -i1 -u -b300M -R
Connecting to host 192.168.2.3, port 5201
Reverse mode, remote host 192.168.2.3 is sending
[  5] local 192.168.2.233 port 50266 connected to 192.168.2.3 port 5201
[ ID] Interval           Transfer     Bitrate         Jitter    Lost/Total Datagrams
[  5]   0.00-1.00   sec  12.0 MBytes   101 Mbits/sec  0.043 ms  0/8693 (0%)  
[  5]   1.00-2.00   sec  12.2 MBytes   102 Mbits/sec  0.072 ms  0/8842 (0%)  
[  5]   2.00-3.00   sec  12.6 MBytes   105 Mbits/sec  0.161 ms  16279/25380 (64%)  
[  5]   3.00-4.00   sec  12.1 MBytes   101 Mbits/sec  0.032 ms  16435/25185 (65%)  
[  5]   4.00-5.00   sec  12.5 MBytes   105 Mbits/sec  0.076 ms  17924/27002 (66%)  
[  5]   5.00-6.00   sec  12.4 MBytes   104 Mbits/sec  0.320 ms  16380/25350 (65%)  
[  5]   6.00-7.00   sec  12.6 MBytes   105 Mbits/sec  0.055 ms  16807/25910 (65%)  
[  5]   7.00-8.00   sec  12.5 MBytes   105 Mbits/sec  0.196 ms  17230/26287 (66%)  
[  5]   8.0
    
            

© Habrahabr.ru