Zynq 7000. Прикручиваем Wi-Fi модуль RTL8822CS с использованием SDIO через EMIO
Наконец-то пришла пора продолжить изучение возможностей платы Zynq QMTech и SoC XC7Z020. Следующая интересная задача, которую я для себя придумал в качестве обучающей — оснастить плату Wi-Fi модулем Realtek RTL8822CS и, если Wi-Fi модуль будет не нужен, а нужна будет ещё одна флешка — вторым портом для SD-карточки. Если интересны подробности того, как я это всё реализовал — добро пожаловать под кат.
Важно! Перед началом повествования, хотелось бы заранее оговориться, что основная цель, которую я преследую при написании этой статьи — рассказать о своем опыте, с чего можно начать, при изучении отладочных плат на базе Zynq. Я не являюсь профессиональным разработчиком под ПЛИС и SoC Zynq, не являюсь системным программистом под Linux и могу допускать какие-либо ошибки в использовании терминологии, использовать не самые оптимальные пути решения задач, etc. Но отмечу, что любая конструктивная и аргументированная критика только приветствуется. Что ж, поехали…
Сформулируем цель и задачи
Итак, у нас есть плата, описанная в этой (https://habr.com/ru/post/559946/) статье и есть желание прикрутить к ней Wi-Fi модуль Realtek RTL8822CS. Модуль, разумеется, должен корректно инициализироваться в ОС Linux и обеспечить пропускную способность до 150 Мбит\с (с чем связано такое ограничение и именно этой цифрой — ниже). В качестве альтернативного варианта для использования выведенного SDIO — необходимо организовать второй порт для подключения SD-карт.
Исходя из этого можно сформировать несколько задач, решив которые мы можем достигнуть цели:
Проверить возможность выноса линий SDIO через EMIO (т.е. через пины PL-части Zynq) и подготовить bitstream-файл;
Проверить работоспособность SDIO через EMIO в приложении baremetal с использованием microSD-карточки;
Изготовить мезонинные платы для Wi-Fi модуля и смонтировать на них компоненты;
Подготовить First Stage Bootloader, Device Tree Source, U-Boot, Linux Kernel в нужной конфигурации для запуска Wi-Fi модуля;
Подготовить модуль ядра для работы Wi-Fi модуля, а именно модуль cfg80211, который является линуксовым API для 802.11 устройств;
Подготовить модуль ядра 88×2cs, который является драйвером для модуля Wi-Fi RTL8822CS который работает поверх SDIO-шины;
Проверить работоспособность сети, сделать сканы эфира, посмотреть рабочие уровни, сделать замеры скорости downstream/upstream-трафиком через iPerf;
Итак. Перейдем к реализации намеченного, в целом, не выглядит сильно сложно =)
Настройка проекта в Vivado и конфигурация PL
В этой главе я кратко (без скриншотов) опишу процедуру создания нового проекта с акцентами на важных моментах. Если необходима инструкция с картинками — можно обратиться к моим предыдущим статьям.
Открываем Vivado и создаем проект через кнопку Create Project. Откроется мастер настройки нового проекта, быстро пройдемся по шагам.
Даём имя проекту, например SDIO_EMIO и указываем папку сохранения;
Указываем, что это RTL Project, оставляем включенной галочку Do not specify sources at this time;
Находим модель SoC использованного на плате. В моем случае это xc7z020clg400–1;
Нажимаем 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:
Остальные настройки я оставил без изменений, в т.ч. частоты тактирования SDIO, PL. Получаем следующую картину (если развернуть группу сигнальных линий SDIO_1):
В данном случае нас интересуют линии CLK, CMD и 4 линии DATA, что в общем-то является стандартным набором для SDIO-шины. Но вот незадача, каждая из линий организована тремя отдельными сигналами. Для решения этой задачи нам необходимо руками создать буфер IOBUF, который будет управляться через активный пин T с тремя состояниями и разрулит Input, Output сигналы как нам нужно для использования его с двунаправленным сигналом I/O. Выглядит он следующим образом:
Т.к. в стандартной библиотеке графических примитивов он отсутствует — нужно будет создать свой 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. Соединяем сигналы с нужными портами и получится следующее:
Хотелось бы обратить внимание на две важные вещи:
На порт iobuf_t модуля для сигнала CLK я добавил Constant с шириной в 1 бит и со значением 0, т.к. управляющего T-пина для CLK не предусмотрено;
Сделал порты iobuf_io как External и дал им соответствующие названия;
Соединил порты Input с портами Output;
На текущем этапе можно запустить синтез и назначить выходы портов на реальные физические порты. Для этого нужно:
Нажимаем на меню block_design правой кнопкой и выбираем команду Create HDL Wrapper.
В меню IP Integrator выбираем Generate Block Design;
Выбираем Synthesis Option — Global и нажимаем Apply, затем Generate.
Дожидаемся окончания генерации;
Выбираем Run Synthesis для старта синтеза нашего дизайна.
Дожидаемся окончания синтеза;
После окончания синтеза переходим в меню Open Synthesized Design и откроется меню Package с нижним меню I/O Ports. Далее необходимо:
Отметить подходящие физические I/O пины
Убедиться, что включена подтяжка PULLUP на всех пинах кроме CLK;
В моем случае получается следующее:
Сохраняем настройки, будет предложено создать файл с constraints. Называем его как удобно и нажимаем Ok. После нажимаем Generate bitstream, соглашаемся с тем, что синтез устарел и ждем окончания всех необходимых процедур до получения сообщения:
После этого можем экспортировать BSP в SDK и запустить проверку работоспособности SDIO в baremetal. Делается это через меню File — Export — Export Hardware, ставим Include bitstream. Нажимаем Ok и запускаем SDK через команду File — Launch SDK.
Проверка SDIO через microSD-карту в Baremetal
Прежде чем приступить к проверке необходимо подключить microSD-карту к пинам, которые мы размечали в следующем разделе. Смотрим в первую очередь на распиновку microSD-карты:
С пинами VDD (3.3V) и VSS (GND) всё в целом понятно. С другими пинами есть нюанс, для пина CMD и шины DATA требуется подтяжка к высокому уровню через 10 кОм резисторы. В моем случае, чтобы не городить навесным монтажом резисторы, я взял одну из плат, заготовленных для Wi-Fi модуля и запаял на него все что нужно:
Если посмотреть на принципиальную схему этой платы — все выглядит вот таким образом:
Питание подключено напрямую в соответствии с тем, как оно выведено на гребенку. Для удобства отладки логическим анализатором — я подпаивал МГТФ-проводки.
После того, как все необходимые линии подключены — можно вернуться в SDK к созданию проекта. После запуска SDK обзываем проект SDIO_EMIO и нажимаем Next, выбираем шаблон проекта Hello World.
Открываем файл 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). В выводе в консоль мы должны увидеть следующее:
Что означают эти сообщения? Это значит, что драйвер успешно «достучался» до карточки и может спокойно начать с ней обмен. Значит наш 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 мм. — было решено сделать такой же разъем и на мезонинной плате.
Распиновка со стороны платы:
Распиновка со стороны модуля:
Решено было вывести в т.ч. и Bluetooth для дальнейших экспериментов. В итоге общая обвязка модуля выглядит следующим образом:
Всё в целом достаточно примитивно и в соответствии с референсной схемой и небольшими изменениями, такими как пин включения\выключения питания модуля и прочих мелких доработок. Антенный тракт было решено вывести на u.FL-разъем, для удобства и возможности подключения любых подходящих для 2.4/5ГГц антенн.
Получившиеся платы достаточно просты в трассировке и не представляют собой никакого технического ноу-хау, но линии данных нужно выровнять т.к. SDIO-шина тут достаточно скоростная, чтобы пренебречь таким важным аспектом трассировки.
Результат трассировки в 2D:
Результат трассировки в 3D:
Вид сверху:
Вид снизу:
Вид платы после монтажа компонентов:
Т.к. плата изначально разрабатывалась не только для этой версии Wi-Fi модуля — остались не установленными некоторые компоненты.
В целом можно переходить к подготовке всего необходимого для работы с радиомодулем с точки зрения основных управляющих сигналов и подготовки необходимых файлов для работы радиомодуля в Linux. Об этом в следующих главах.
Небольшое дополнение проекта в Vivado
Для того, чтобы была возможность запустить радиомодуль необходимо соблюсти два условия:
Подать низкое напряжение на затвор P-канального ключа VT1, т.е. на линию WL_BT_ON, чтобы подать питание на радиомодуль;
Подать высокое напряжение на ножку WL_REG_ON радиомодуля, чтобы запустить его;
Поэтому на этом этапе нам будет нужно слегка модернизировать проект и добавить 2 сигнала с AXI GPIO-контроллера и завести их на соответствующие ножки:
Для транзисторного ключа это ножка 11 на общей гребенке, что соответствует физическому пину W20;
Для сигнала WL_REG_ON это 25-я ножка на той же гребенке, что соответствует в моем случае пину V17;
Как работать с AXI GPIO Я рассказывал в этой статье (https://habr.com/ru/post/565368/) и на деталях повторяться не буду. В нашем случае нужно выполнить лишь несколько шагов:
Добавляем в дизайн IP-модуль AXI GPIO;
Выполняем предложенные шаги автоматизации предложенные Vivado, отметив все галочки;
Появится блок AXI GPIO, необходимо зайти в его опции и поставить галочку All Outputs, поставить ширину шины GPIO Width в значение 2 (нужно 2 сигнала) и в Default Output вписать 0×00000002, чтобы по умолчанию после загрузки bitstream-файла оба пина приняли необходимое значение;
Называем External Interface от AXI GPIO как MGMT_GPIO и сохраняемся. Должно получиться следующее:
Синтезируем дизайн, проверяем, что всё прошло успешно;
Переходим в меню Open Synthesized Design и назначаем MGMT_GPIO[0] пин W20, а MGMT_GPIO[1] пин V17;
Изменяем у этих двух пинов I/O Std на LVCMOS3.3 и сохраняем;
Запускаем генерацию bitstream-файла через Generate bitstream;
Экспортируем сгенерированный bitstream и всё что необходимо для дальнейших шагов через команду File — Export — Export Hardware;
Все изменения, необходимые на этом этапе мы внесли. Переходим к подготовке всего необходимого для работы с радиомодулем в Linux;
Подготовка FSBL, Device Tree Sources, U-Boot
На этом этапе можно переходить к созданию загрузчиков, файлу описания железа и компиляции ядра Linux. Многие моменты и детали Я целенаправленно опущу т.к. уже рассказывал в предыдущей статье (https://habr.com/ru/post/565368/), а отличающимся моментам посвящу основную часть повествования.
Первым этапом надо подготовить FSBL. Для этого нужно:
В меню Xilinx SDK переходим в меню File — New — Application Project;
Именуем его как FSBL;
Нажимаем клавишу Next;
Из заготовок выбираем Zynq FSBL;
Дожидаемся окончания компиляции и проверяем, что появился файл FSBL.elf
Вторым этапом необходимо подготовить Device Tree Source/Blob файл который будет использован для сборки U-Boot. Для этого сделаем следующее:
Добавляем BSP Repository из репозитория device-tree-xlnx. Для этого нужно выбрать меню Xilinx — Repositories и указать папку с device-tree-xlnx в секцию Global Repositories.
Добавляем BSP Project через меню File — New — Board Support Package, даём ему имя и выбираем внизу меню device_tree и в следующем меню нажимаем Ok, если не требуется внесения каких-либо изменений.
Выполним редактирование файла zynq-7000.dtsiи находим секцию описывающую SDIO1 в строке sdhci1: mmc@e0101000;
Дополняем секцию следующим содержанием (файл можно взять тут — https://github.com/megalloid/zynq_qmtech/blob/main/zynq-7000.dtsi) :
Переходим в папку с сгенерированными 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 для нашей платы. Нужно выполнить несколько простых шагов:
Перейти в папку с U-Boot: cd u-boot-xlnx
Задать переменные для кросс-компиляции: export CROSS_COMPILE=arm-linux-gnueabihf-; export ARCH=arm
Очистить сырцы от предыдущих результатов компиляции и применить конфиг для Zynq по умолчанию: make distclean; make xilinx_zynq_virt_defconfig
Положить в папку с U-Boot полученный в предыдущем шаге system.dtb в папку arch/arm/dts/ c именем zynq-qmtech.dtb
Указываем какой Device Tree файл использовать: export DEVICE_TREE=«zynq-qmtech»
Далее необходимо зайти в меню конфигуратора и изменить ряд опций: make menuconfig
Опция для того, чтобы активировать поддержку AXI GPIO, которая используется для включения питания модуля и подачи сигнала WL_REG_ON на радиомодуле: Device Drivers — GPIO Support — Xilinx GPIO Driver
Сохраняем конфиг (можно взять тут) в файл .config
Запускаем сборку: make -j$(nproc) и дожидаемся окончания компиляции;
После этого можно скомпоновать FSBL, Bitstream-файл и U-Boot в один загрузочный файл BOOT.BIN. Подробное описание компоновки можно найти в моей прошлой статье (https://habr.com/ru/post/565368/). В целом надо выполнить несколько простых шагов:
Найти файл в каталоге с проектом First stage bootloader. По умолчанию он находится в SDIO_EMIO/SDIO_EMIO.sdk/FSBL/Debug/FSBL.elf
Найти Bitstream-файл для программируемой логики. Найти его можно в каталоге SDIO_EMIO/SDIO_EMIO.sdk/block_design_wrapper_hw_platform_0/block_design_wrapper.bit
Найти бинарный файл U-Boot. Данный файл после компиляции лежит в папке с U-Boot: u-boot-xlnx/u-boot.elf
Открываем в Xilinx SDK пункт меню Xilinx — Create Boot Image;
Выбираем куда сохранить новый bif-файл, я его сохраняю в корневом каталоге проекта;
Добавляем поочередно файлы FSBL.elf, block_design_wrapper.bit, u-boot.elf в секцию Boot Image Partitions;
Нажимаем Create Image и видим как в каталоге с bif-файлом появляется BOOT.BIN;
Теперь необходимо залить этот файл на загрузочную SD-карту и можно попробовать загрузиться. Если мы видим приглашение от U-Boot и что светится светодиод FPGA DONE — значит мы всё выполнили правильно.
Можно перейти к проверке того, проинициализирован ли AXI GPIO в U-Boot и проверим их. Для этого сначала выведем список устройств которые проинициализированы. Это можно сделать через команду dm tree.
Отлично. Видно, что у нас на борту есть два SDIO-контроллера, и два GPIO-контроллера. Попробуем изменить состояние ножки транзистора. Для этого нужно:
Посмотреть какие адреса в GPIO доступны и какие отвечают за те, или иные ножки: gpio status -a
В конце вывода видно, что есть Bank GPIO с двумя ножками, которые соответствуют значению по умолчанию:
Проверки ради можно изменить состояние ножки 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. Перейдем к выполнению шагов, необходимых для сборки ядра с необходимыми модулями и драйверами. Для этого нужно:
Перейти в папку с клонированным репозиторием linux-xlnx;
Обновить его (при необходимости): cd linux-xlnx; git fetch -p; git checkout master; git pull;
Экспортируем также переменные для кросс-компиляции: export CROSS_COMPILE=arm-linux-gnueabihf-; export ARCH=arm
Делаем очистку от результатов предыдущих сборок: make distclean
Загружаем дефолтный конфиг: make ARCH=arm xilinx_zynq_defconfig;
Изменим дефолтный конфиг загрузив меню конфигурации: make ARCH=arm menuconfig
Изменим опции конфигурации (можно взять тут https://github.com/megalloid/zynq_qmtech/blob/main/kernel.config) , устанавливаем опцию (проследим, что там вместо [M] в поле выбора стоит символ звездочки [*] : Networking Support — Wireless — cfg80211
Сохраняем конфиг в файл .config и выходим из меню конфигурации;
Запускаем процесс компиляции: make ARCH=arm UIMAGE_LOADADDR=0×8000 uImage -j8
Компилируем модули ядра: make ARCH=arm -j8 modules
Копируем uImage из каталога linux-xlnx/arch/arm/boot/ на загрузочную SD-карту;
Далее необходимо подготовить образ RootFS с всеми необходимым userspace-утилитами, типа wpa_supplicant, wpa_cli, etc. Собирать RootFS будем с помощью системы сборки buildroot, статью о которой Я писал до этого (https://habr.com/ru/post/567408/). Итак, перейдем к сборке, для этого нужно:
Заходим в папку с buildroot: cd buildroot
Обновляем его до последней версии: git fetch -p; git checkout master; git pull;
Делаем очистку от результатов предыдущих сборок: make distclean
Запускаем меню конфигурации: make -C /home/megalloid/buildroot O=$PWD ARCH=arm nconfig
Изменяем опции конфигурации по списку ниже;
Опция 1: Target options — Target Architecture — ARM (little endian)
Опция 2: Target options — Target Architecture Variant — cortex-A9
Опция 3: Target options — Enable NEON SIMD extension support — [*]
Опция 4: Target options — Enable VFP extension support — [*]
Опция 5: Target options — Target ABI — EABIhf
Опция 6: Target options — Floating point strategy — NEON
Опция 7: Build options — Number of jobs to run simultaneously — 8 (по количеству ядер CPU)
Опция 8: Toolchain — C library — glibc
Опция 9: Toolchain — Kernel headers — Custom Git Repository
Опция 10: Toolchain — URL of custom repository —
Опция 11: Toolchain — Custom repository version — <хэш-сумма от git show-ref | grep master из папки linux-xlnx>
Опция 12: Toolchain — Custom kernel headers series — 5.15.x
Опция 13: Toolchain — Install glibc utilities — [*]
Опция 14: Toolchain — GCC compiler Version — gcc 9.x
Опция 15: Toolchain — Enable C++ support — [*]
Опция 16: Toolchain — Enable compiler link-time-optimization support — [*]
Опция 17: Toolchain — Enable compiler OpenMP support — [*]
Опция 18: Target packages — Miscellaneous — haveged
Опция 19: Target packages — Networking applications — dhcpd
Опция 20: Target packages — Networking applications — dropbear
Опция 21: Target packages — Networking applications — ifupdown scripts
Опция 22: Target packages — Networking applications — hostapd
Опция 23: Target packages — Networking applications — iperf
Опция 24: Target packages — Networking applications — iperf3
Опция 25: Target packages — Networking applications — fping
Опция 26: Target packages — Networking applications — iw
Опция 27: Target packages — Networking applications — iwd
Опция 28: Target packages — Networking applications — wpa_supplicant
Опция 28: Target packages — Networking applications — wireless tools
Опция 29: Filesystem images — cpio the root filesystem — [*]
Опция 30: Filesystem images — Compression method — lzma
Опция 31: Host Utilites — host dosfstools
Опция 32: Host Utilites — host genimage
Опция 33: Host Utilites — host mtools
После этой длительной конфигурации выбираем Save и сохраняем конфиг (можно взять тут и подложить к себе https://github.com/megalloid/zynq_qmtech/blob/main/buildroot.config);
Нажимаем F9 и запускаем процесс сборки: make -C /home/megalloid/buildroot O=» class=«formula inline»>PWD ARCH=arm BR2_JLEVEL=»(nproc) — 1))»
Ждём когда закончится компиляция, самое время заварить и выпить кофе =)
Теперь когда образ RootFS собрался надо его подписать и привести к виду, в котором его сможет корректно переварить U-Boot:
Переходим в папку с готовыми образами: cd buildroot/images
Производим подпись: mkimage -A arm -T ramdisk -C gzip -d rootfs.cpio uramdisk.image.gz
Складываем uramdisk.image.gz на загрузочную SD-карту;
Настраиваем загрузку образов в U-Boot:
Надо загрузиться в консоль 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;»
Сохраняем эту переменную через saveenv и перезагружаемся;
Дожидаемся загрузки ОС;
Теперь можно переходить к компиляции драйвера и непосредственно настройке Wi-Fi модуля.
Компиляция модуля ядра 88×2cs для Wi-Fi модуля
Переходим к последнему элементу, необходимому для работы Wi-Fi модуля — драйверу нашего Wi-Fi модуля. На просторах GITHUB я нашел вполне себе собираемый и рабочий вариант драйвера. Чтобы собрать драйвер out-of-tree нужно сделать следующее:
Склонировать исходные коды себе: git clone https://github.com/megalloid/rtl88×2cs
Переключим ветку на версию драйвера совместимую с 5.15: git checkout tune_for_jethub_5_14
Запускаем компиляцию: make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KSRC=»/home/megalloid/linux-xlnx» (где KSRC — путь до папки с ядром LInux из предыдущего шага);
На выходе мы получим в этой папке файл 88×2cs.ko, его нужно положить туда же на загрузочную SD-карту;
Проверяем, загрузится ли модуль ядра и появится ли wlan-интерфейс:
Монтируем SD-карту в файловую систему: mount /dev/mmcblk0p1 /mnt
Переходим в каталог SD-карты: cd /mnt/
Выполняем insmod 88×2cs.ko для загрузки модуля ядра;
Запускаем программу ifconfig — и видим, что появился wlan0 интерфейс:
Это говорит о том, что модуль ядра успешно загрузился и готов к работе. В следующей главе перейдем к настройке wlan0 интерфейса и проверке работоспособности Wi-Fi, прогону скорости через iPerf;
Про модули ядра можно почитать тут: https://habr.com/ru/post/117654/
Настройка Wi-Fi интерфейса и измерение скорости с использованием iPerf
Для настройки Wi-Fi необходимо первым делом создать конфиг, удобнее всего в этом случае его создать на SD-карте в виде файла wpa_supplicant.conf.
Выполним в консоли Zynq следующие команды:
Создадим файл конфигурации: touch /mnt/wpa_supplicant.conf
Открываем его на редактирование: vi /mnt/wpa_supplicant.conf
Записываем в него следующее содержимое и сохраняем:
network={
ssid="ASUS"
scan_ssid=1
key_mgmt=WPA-PSK
psk="megapassword"
}
Запускаем wpa_supplicant: wpa_supplicant -B -Dnl80211 -iwlan0 -c/mnt/wpa_supplicant.conf -P/var/run/wpa_supplicant.pid -dd -f/mnt/wpa_supplicant.log
Через некоторое время появится IP-адрес на интерфейсе wlan0 и будет установлена связь с роутером;
Теперь можно прогнать трафик с компьютера (который подключен к роутеру кабелем) на Zynq и обратно. Для этого нужно:
Запустить на компьютере: iperf3 -s
Запустить на Zynq: iperf3 -c
-N -i1 -t60 -u Чтобы пустить трафик в обратном направлении на 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