В этой статье мы рассмотрим как устроен драйвер сетевого адаптера для Linux. Cтатью разделим на две части. В первой части рассмотрим общую структуру сетевого адаптера, узнаем какие компоненты входят в его состав, что такое MAC и PHY, разберемся как подготовить адаптер к работе, сконфигурировать, и как в итоге получать сетевые пакеты. Хотя при разработке драйверов необходимо использовать стандартные ядерные фреймворки, такие как clock, reset, libphy и пр., поначалу мы будем работать с адаптером напрямую через регистры состояния и управления. Это позволит нам детально разобраться с аппаратной частью. Во второй части статьи приведем драйвер к нормальному виду, с использованием стандартных фреймворков и описанием того, что надо указать в Device Tree, и рассмотрим как передавать сетевые пакеты. Нам нужна макетная плата с сетевым адаптером, на которую можно поставить Linux. Возьмем Orange Pi Zero на платформе Allwinner H2+. В состав платформы входят четыре процессорных ядра Cortex-A7, поддерживается ОЗУ стандартов LPDDR2, LPDDR3, DDR3, широкий спектр соединений и интерфейсов, в том числе сетевой адаптер, для которого мы будем разрабатывать драйвер. Подробное описание платформы тут, документация на платформу Allwinner H3 Datasheet. Также нам понадобится переходник USB-to-COM для получения отладочного вывода платы. В качестве консольной программы можно использовать minicom. В первой части статьи рассмотрим следующие вопросы: запуск Linux на макетной плате общая структура Ethernet сетевого адаптера начнем разработку драйвера – рассмотрим порядок включения и настройки для приема пакетов 1. Запуск Linux на макетной плате  Для запуска Linux на плате необходимо: подготовить средства разработки подготовить корневую файловую систему (rootfs) подготовить ядро Linux и дерево устройств для данной платформы (device tree) подготовить загрузчик u-boot сформировать загрузочную sd-карту и загрузиться с нее 1.1 Средства разработки  Хост-система, на которой ведем разработку - Debian 10. Для разработки установим следующие пакеты: Целевая платформа у нас ARM, укажем ее и кросс-компилятор: 1.2. Корневая файловая система (roosfs)  Есть два варианта подготовки корневой файловой системы – взять готовую или сделать самим. Выберем второй, соберем rootfs из busybox. Скачиваем его: Запускаем конфигуратор: и включаем статическую линковку: Собираем и устанавливаем. Для установки надо в CONFIG_PREFIX указать путь куда ставить: Допустим, мы установили busybox в каталог /tmp/build/bb/ramdisk. Если посмотреть на его содержание, увидим такую картину: Создадим точки монтирования для procfs и sysfs, каталог etc для конфигурационных файлов и dev для файлов устройств: В busybox присутствует mdev - аналог подсистемы udev. Его задача - динамически создавать и удалять файлы устройств в каталоге /dev. Подробнее о нем в документации https://git.busybox.net/busybox/plain/docs/mdev.txt. В каталоге etc создадим конфигурационные файлы group, hosts, inittab, passwd: Также в etc создаем каталог init.d и в нем исполняемый файл rc.S: Этот rc.S будет выполнен при старте системы. В результате будут смонтированы procfs в /proc, sysfs в /sys, tmpfs в /dev, и mdev создаст в /dev файлы устройств, которые он найдет в sysfs.   В rootfs необходимо в каталог /lib/modules добавить модули ядра, которые предварительно надо собрать. Об этом будет следующий пункт. По ссылке https://github.com/ubob74/net/raw/p1/files/ramdisk.tgz можно скачать готовый rootfs.1.3. ЯдроПереходим к сборке ядра. Клонируем репозиторий и становимся на нужный комит: Формируем конфигурационный файл для нашей платформы: Заходим в режим конфигурации и отключаем сборку сетевого драйвера, т.к. у нас будет свой: Собираем ядро и модули: Формируем Device Tree: По итогу сборки получим сжатый бинарный образ ядра arch/arm/boot/zImage и дерево устройств (device tree blob) arch/arm/boot/dts/sun8i-h2-plus-orangepi-zero.dtb. Устанавливаем собранные модули на rootfs: После установки модулей пакуем rootfs и получаем сжатый образ initramfs.img: 1.4. ЗагрузчикКлонируем репозиторий: Формируем конфигурационный файл и собираем: В итоге должны получить бинарный файл u-boot-sunxi-with-spl.bin.1.5. Загрузочная sd картаС помощью fdisk и mkfs формируем на sd карте раздел FAT32. Стартовый сектор раздела ставим 8192, т.к. перед разделом надо расположить загрузчик u-boot.Создаем на sd карте каталог /boot и копируем туда собранные компоненты - бинарный образ ядра arch/arm/boot/zImage, дерево устройств arch/arm/boot/dts/sun8i-h2-plus-orangepi-zero.dtb и сжатый образ rootfs initramfs.img. Далее формируем файл сценария boot.cmd для u-boot: В этом файле мы указываем откуда, что и куда должен вгрузить u-boot и командную строку ядра. Корневая файловая система будет расположена на ramdisk. Теперь из boot.cmd формируем сценарий boot.src и копируем его на на sd карту: В итоге на sd карте у нас должны находиться следующие файлы:- boot.scr в корневом каталоге- zImage, sun8i-h2-plus-orangepi-zero.dtb, initramfs.img в каталоге /bootНапример, sd карта смонтирована как устройство /dev/sdc1 в каталог /mnt/sdc: Последний этап подготовки sd карты - запись загрузчика. Тут надо действовать внимательно. Необходимо указать правильный файл устройства загрузочной sd карты, чтобы ничего не повредить. В нашем случае /dev/sdc. Переходим в каталог, в котором собирали u-boot, и выполняем команду: Все готово, загрузчик записан. Загружаем систему - вставляем sd карту в слот платы и подаем питание, процесс загрузки отслеживаем через отладочную консоль. Можно приступать к разработке драйвера. Но вначале ознакомимся, что из себя представляет сетевой адаптер Ethernet. 2. Общая структура Ethernet сетевого адаптера В состав Ethernet адаптера входят: блок управления доступом к среде MAC (Media Access Controller) блок DMA приемный и передающий FIFO буферы приемопередатчик PHY Задача MAC - формирование кадров для передачи, инкапсуляция в них данных (payload), прием кадров, обработка адресной информации, фильтрация кадров, детектирование ошибок в данных, обнаружение коллизий (алгоритм CSMA/CD). Задача DMA - обмен кадрами между системной памятью и MAC. DMA взаимодействует с MAC с помощью двух FIFO буферов - передающий (TX) и приемный (RX). Через TX FIFO передаваемый кадр считывается из системной памяти в MAC, а через RX FIFO наоборот - принятый кадр загружается из MAC в системную память. Задача PHY - преобразование бинарного представления кадра в вид, пригодный для передачи по физическому носителю, т.е. по Ethernet кабелю, и наоборот - принятый по физическому носителю сигнал преобразовать в бинарный вид. MAC соединяется с PHY с помощью двух интерфейсов - MII (Media Independent Interface) и SMI (Station Management Interface). К одному MAC можно подключить до 32-х PHY. Каждый PHY адресуется порядковым номером, размер адреса – 5 бит. Через MII выполняется обмен данными между MAC и PHY, перечень и описание сигналов смотрите по ссылке. SMI служит для управления PHY. Это двухпроводный интерфейс, состоящий из линии данных (Management Data Input/Output, MDIO) и линии тактирования (MDC). Через SMI можно установить режим работы PHY (дуплекс - полудуплекс), скорость обмена, проверить подключен или нет сетевой кабель и сделать другие полезные вещи. Выполняется все это с помощью регистров управления, входящих в состав PHY. Перечень регистров PHY и управляющие биты с функциональным назначением каждого можно посмотреть в заголовочном файле ядра <linux/mii.h>. Для доступа к регистру указывается его адрес и адрес PHY. Размер адреса - 5 бит. Регистр PHY адресуется указанием его порядкового номер, также как и сам PHY. Адресная информация и данные передаются по линии MDIO. Формат MDIO кадра для режимов чтения и записи показан на рис. 5. MAC также как и PHY содержит набор регистров. Их число и назначение определяет производитель. Рассматриваемый нами Ethernet MAC контроллер (далее EMAC) содержит набор регистров для управления устройством, полный перечень в п.8.9.3 "EMAC Core Register List" спецификации на плату. Каждый регистр адресуется указанием смещения относительно базового адреса EMAC. Также для управления устройством потребуются блоки System Control и CCU. CCU - это Clock Control Unit, блок управления тактовыми генераторами. Он выполняет генерацию, распределение, синхронизацию и отключение сигналов тактовой частоты для всех компонентов платы. 3. Драйвер В этой части разработка драйвера будет включать следующие шаги:- подготовка к работе PHY- подготовка к работе EMAC- прием пакетов- отображение результатов 3.1. Подготовка PHY Подготовка PHY включает следующие шаги:- подача тактовой частоты- выполнить аппаратный сброс (reset)- определить функции для работы с PHY Подача частоты выполняется установкой бита 0 в регистре Bus Clock Gating Register 4, смещение 0x0070 относительно базового адреса CCU (п. 4.3.5.18 спецификации): Аппаратный сброс выполняется сбросом, а затем установкой бита 2 в регистре Bus Software Reset Register 2, смещение 0x2C8 (п. 4.3.5.72): Для обмена данными с PHY используются два регистра - регистр команд MII Command Register (MII_CMD) и регистр данных MII Data Register (MII_DATA). Оба входят в состав EMAC Core Register List. Смещение MII_CMD - 0x48, MII_DATA - 0x4C. В MII_CMD указываем адрес PHY, номер регистра PHY, значение делителя частоты для MDC и направление обмена - чтение или запись. В режиме записи в MII_DATA записываем передаваемые данные, в режиме чтения - считываем полученные от PHY данные. Перед каждой новой операцией ждем завершения предыдущей проверкой бита MII_BUSY - устанавливаем мы его сами, а сбрасывается он автоматически (аппаратно): Разберемся с phy_mdc_div_ratio, какое значение принимает этот параметр. Мы записываем его в 3-х битовое поле MDC_DIV_RATIO_M регистра MII_CMD. Оно задает значение делителя частоты MDC, и может принимать следующие значения: Значение поля Номинал делителя 000 16 001 32 010 64 011 128 Т.к. сигнал MDC формируется из частоты шины AHB1 (п. 8.9.4.15), то конкретное значение делителя зависит от номинала частоты AHB1 шины. Согласно стандарта 802.3 минимальный период тактового сигнала на линии MDC должен быть равен 400 нс, т.е. максимальная частота MDC равна 2.5 МГц. Значит должно выполнятся условие:  Согласно схеме Bus Clock Tree (рис. 4-2 спецификации) формирователем частоты AHB1 является PLL_PERIPH0, и тогда номинал AHB1 вычисляется как: Значения делителей AHB1_PRE_DIV и AHB1_CLK_DIV_RATIO определяет CCU регистр AHB1_APB1_CFG_REG, а регистр PLL_PERIPH0_CTRL_REG - номинал частоты PLL_PERIPH0: Значения N и K определены в полях PLL_FACTOR_N и PLL_FACTOR_K регистра PLL_PERIPH0_CTRL_REG. Для окончательного подсчета сдампим значения регистров AHB1_APB1_CFG_REG и PLL_PERIPH0_CTRL_REG: Биты 13 и 12 в AHB1_APB1_CFG_REG установлены, значит частота AHB1 формируется из PLL_PERIPH0/AHB1_PRE_DIV, мы изначально исходили из этого условия. Получаем значения делителей, устанавливаемых в AHB1_APB1_CFG_REG: Значения PLL_FACTOR_N и PLL_FACTOR_K: Частота PLL: 600 MHz - такое же значение заявлено и в спецификации, мы это просто перепроверили. Теперь можно посчитать частоту AHB1: Вычисляем значение делителя MDC: Какое значение выбрать? Обратимся за советом к FreeBSD драйверу, посмотрим как сделано там: Последнее условие нам подходит, поэтому выбираем номинал делителя 128, и в поле MDC_DIV_RATIO_M регистра MII_CMD записываем значение 3. Перед началом работы выполняем программный сброс (soft-reset) PHY установкой бита BMCR_RESET в PHY регистре MII_BMCR (Basic mode control register). Установив этот бит, мы должны дождаться его сброса, опрашивая регистр MII_BMCR: Теперь рассмотрим такой важный вопрос как согласования скорости и режима работы адаптера и порта роутера, к которому он подключается. Тут можно применить два метода. Первый, самый простой - установить фиксированное значение скорости обмена и режима, такую же, как у роутера. Метод простой, но не удобный по очевидным причинам - нам надо заранее знать настройки порта роутера и при переключении на другой порт корректировать настройки адаптера. Поэтому применим метод автосогласования скоростей (auto-negotiation). Суть метода заключается в том, что адаптер и роутер обмениваются информацией о поддерживаемых режимах работы и выбирают оптимальный. Подробно вдаваться в детали процесса не будем, об этом можно почитать тут, а рассмотрим каким образом инициировать и выполнить процедуру автосогласования в драйвере. PHY содержит регистры MII_BMSR и MII_ADVERTISE. MII_BMSR - это базовый регистр статуса (Basic mode status register), он содержит, помимо всего прочего, поддерживаемые режимы работы и скорости обмена, например: Это неполный перечень режимов, подробнее в <linux/mii.h>. MII_ADVERTISE - это Advertisement control register, через этот регистр адаптер может сообщить противоположной стороне о своих возможностях: Записав нужные значения режимов в регистр MII_ADVERTISE, надо в регистре MII_BMCR установить флаги BMCR_ANRESTART и BMCR_ANENABLE для активизации режима автосогласования и ждать завершения процесса, проверяя флаг BMSR_ANEGCOMPLETE в регистре MII_BMSR: После установки флага читаем регистр MII_LPA Link - Partner Ability register. В нем будут установлены поддерживаемые режимы передачи и скорости, согласованные со второй стороной, например: Оформим весь вышеописанный процесс в виде функций: PHY готов к работе. Переходим к настройке EMAC. 3.2. Подготовка к работе EMAC Подготовка EMAC включает в себя следующие шаги: получение ресурсов адаптера - адрес ввода-вывода и номер прерывания включение и аппаратный сброс конфигурирование PHY установка аппаратного адреса (MAC адрес) формирование очереди для приема сетевых пакетов настройка фильтрации пакетов включение приемного тракта и разрешение прерываний 3.2.1. Получение ресурсов Для работы с EMAC требуется получить номер прерывания и отобразить диапазон адресов ввода-вывода, начиная с базового адрес EMAC, на виртуальные адреса ядра, аналогично как было сделано для CCU и System Control: Необходимые ресурсы определены в Device Tree (полное описание см. в arch/arm/boot/dts/sunxi-h3-h5.dtsi): 3.2.2. Включение и аппаратный сбросПодготовка к работе EMAC такая же как и для PHY - необходимо включить тактовую частоту и выполнить аппаратный сброс. Включаем тактовую частоту EMAС установкой бита 17 в CCU регистре Bus Clock Gating Register0, смещение 0x60 (п. 4.3.5.14): Выполняем reset сбросом, а затем установкой бита 17 в регистре Bus Sotfware Reset Register0, смещение 0x2C0 (п. 4.3.5.70):  3.2.3. Конфигурирование PHY Теперь конфигурируем PHY - надо указать его адрес, тип, режим, частоту обмена при передаче данных и подать питание. Режим работы PHY - Normal, частота тактирования - 25 MHz, тип – внутренний (Internal), адрес - порядковый номер PHY, т.к. он единственный, то адрес равен 1. Эти параметры выставляем в регистре EMAC-EPHY Clock Register из System Control, смещение регистра 0x30 (п. 4.5.3.2): Режим и частоту не выставляли явно, используем значения по умолчанию.3.2.4. Установка аппаратного адреса (MAC адрес) Аппаратный адрес хранится в регистрах MAC Address 0 High Register (смещение 0x50) и MAC Address 0 Low Register (offset 0x54). В первый регистр записываем старшие 16 бит адреса, во второй - младшие 32 бита. Теперь готовим адаптер к приему пакетов. Но вначале немного о DMA операциях. EMAC умеет выполнять DMA операции. Поступивший из PHY пакет блок DMA EMAC может самостоятельно положить в системную память и сообщить через прерывание о наличии новых данных. И наоборот - может забрать пакет из системной памяти и отправить его в PHY для дальнейшей передачи. Блок DMA EMAC использует физические адреса для хранения пакета. Для доступа драйвера к данным необходимо отобразить физический адрес на виртуальный ядерный. Для DMA операций ядро Linux поддерживает два типа отображения - постоянное (Consistent DMA mappings) и потоковое (Streaming DMA mappings). Постоянное отображение используется для данных, которые должны находиться в памяти на протяжении всего времени работы драйвера. Это отображение гарантирует одновременный доступ к данным как устройству, так и драйверу. Любое изменение данных видно без каких-либо дополнительных операций программной синхронизации (сброс буферов, кешей etc). Такое отображение еще называют синхронным. Потоковое отображение создается для одной короткой транзакции, например прием сетевого пакета (наш случай). Для такого вида отображения есть одна характерная особенность - кто владеет данными. Когда отображение создано, данными владеет устройство. Драйвер явно должен запросить доступ к данным и вернуть его после обработки. Для этого есть спецальные API ядра, мы ниже рассмотрим как ими пользоваться. Дополнительно о DMA можно прочитать в официальной документации ядра и тут.3.2.5. Формирование очереди для приема сетевых пакетов Чтобы EMAC умел выполнять DMA операции, его надо соответствующим образом настроить. Ему надо знать откуда брать и куда сохранять сетевые пакеты. Для хранения пакетов выделим буфер памяти. Адрес буфера сохраним в специальном дескрипторе. Буферов может быть несколько, соответственно, для каждого буфера будет свой дескриптор. Все дескрипторы формируют связаный список. Адрес начала списка хранится в регистре Receive DMA Descriptor List Address, смещение 0x34 относительно базового адреса EMAC (п. 8.9.4.11). Когда приходит пакет, DMA блок получает адрес списка дескрипторов из этого регистра, находит в списке свободный буфер и копирует в него пакет. Дескриптор состоит из четырех 32-х разрядных полей. Первое поле содержит управляющие и контрольные флаги, второе - размер буфера для принятого пакета, третье - адрес буфера, четвертое - адрес следующего дескриптора в списке. Представить дескриптор можно в виде структуры: Получается такая картина: В поле status важным является бит #31, RX_DESC_CTL. Этот бит определяет, может ли данный дескриптор быть использован блоком DMA, т.е. может ли DMA скопировать принятый пакет по адресу, на который указывает третье поле дескриптора. Если бит установлен, то дескриптор поступает в распоряжение DMA блока. При инициализации дескрипторов этот бит устанавливается. Скопировав принятый пакет в буфер, DMA блок сбрасывает этот бит и генерирует прерывание. Обработчик прерывания драйвера найдет дескриптор с новым пакетом, ориентируясь на сброшенный бит. Обработав пакет, драйвер вернет дескриптор DMA блоку установкой бита RX_DESC_CTL. Хранить принятый пакет будем в буфере сокета, который определен структурой sk_buff. Это основная структура в сетевом стеке ядра Linux, она содержит все данные о принимаемом или передаваемом пакете, включая заголовки всех уровней и другую информацию. Структура sk_buff определена в <linux/skbuff.h>, о ней можно прочитать тут и тут. Более подробнее работу с этой структурой мы рассмотрим в следующей части, коснемся, в частности, такого вопроса как линеаризация, а пока нам интересно только поле data. Это поле - адрес буфера для принятого пакета. Так как буферов может быть несколько (RX_SIZE), то понадобится массив для хранения указателей на них: А чтобы DMA блок имел доступ к буферу пакета, надо для каждого sk_buff[i]->data получить аппаратный адрес с помощью dma_map_single: Тут мы используем потоковое DMA отображение, о котором говорили выше. Теперь нам нужна память для хранения списка дескрипторов: Здесь используем постоянное отображение, т.к. список дескрипторов нужен на все время работы драйвера. dma_rx_phy - аппаратный адрес списка дескрипторов, который мы должны записать в регистр Receive DMA Descriptor List Address. Инициализируем список дескрипторов. В каждом дескрипторе надо установить флаг RX_DESC_CTL, указать размер буфера, его адрес (аппаратный), и ссылку на следующий дескриптор в списке, последний элемент замыкаем на первый: Передаем адаптеру адрес списка дескрипторов: Все готово к приему пакетов.3.2.6. Структура net_device Поговорим немного о структуре net_device. Хотя в этой части мы ее особо трогать не будем, но упомянуть надо, т.к. в дальнейшем она нам понадобится. Эта структура является ключевой структурой драйвера сетевого адаптера. Она содержит кучу разной информации, такую как имя сетевого интерфейса, адреса ввода-вывода и номер прерывания, которые использует устройство, статистическая информация, функции обратного вызова (callbacks) для взаимодействия с сетевым стеком ядра и многое другое. Подробно с содержанием это стурктуры можно ознакомиться в заголовочном файле <linux/netdevice.h>. Память под эту структуру выделяется на этапе инициализации драйвера вызовом alloc_etherdev. Параметр вызова - размер частных (private) данных. Частные данные – это, как правило, структура, которая содержит данные, необходимые для работы конкретного драйвера. В нашем примере частные данные это структура вида struct rx_queue - эта структура описывает очередь приема сетевых пакетов: Поля этой структуры нам знакомы, о них мы говорили выше, когда формировали очередь для приема пакетов, а cur_rx - это номер текущего дескриптора. При выделении памяти функция alloc_etherdev выделяет sizeof(struct net_device) + private size байт, с учетом выравнивания, и частные данные располагаются сразу за структурой net_device. Для получения ссылки на эти данные используется макрос netdev_priv: 3.2.7. Прием пакетов При приеме пакетов мы исходим из того, что устройство будет генерировать прерывание при поступлении пакета. Чтобы устройство могло сгенерировать прерывание, когда блок DMA поместит принятый пакет в выделенный буфер, в регистре разрешения прерываний (Interrupt Enable Register, п.8.9.4.4) установим бит RX_INT_EN. В результате при возникновении прерывания в регистре состояния прерываний (Interrupt Status Register, п.8.9.4.3) будет установлен бит RX_INT. Обработчик прерывания проверит этот бит и вызовет функцию-обработчик принятого пакета. Устанавливаем обработчик прерывания, последний параметр - стуруктура net_device: Функция-обработчик прерывания: Здесь мы проверяем установку флага RX_INT и вызываем функцию-обработчик принятого пакета emac_rx: Функция принимает указатель на частные данные адаптера, получает указатель на очередь приема пакетов и номер текущего дескриптора. Далее адресуем текущий дескриптор из массива, и проверям, не занят ли он DMA блоком. Для этого смотрим бит RX_DESC_CTL в поле status. Если занят, то не трогаем его и выходим: Определяем размер принятого пакета, это биты 29:16 поля status. Размер пакета не должен превышать 1536 байт (размер Ethernet кадра): Если все хорошо, то получаем данные пакета. Данные будут в буфере sk_buff[entry]->data. Это виртуальный адрес, который отображен на физический sk_buff_dma[entry]. Отображение потоковое (см. выше), надо явно запрашивать доступ к данным: Выводим в лог первые 30 байт принятых данных: После возвращаем доступ устройству: Вычисляем следующий дескриптор и возвращаем текущий DMA блоку, установив бит 31 в поле status: 3.2.8. Фильтрация пакетов В составе EMAC есть регистр Receive Frame Filter Regsiter (смещение 0x38, п.8.9.4.12), который управляет режимом фильтрации принимаемых пакетов. Опций фильтрации достаточно много, но нас пока интересует только прием широковещательных (broadcast) пакетов, для этого бит #17 надо сбросить в 0. По умолчанию значение регистра 0x0, поэтому оставим его в исходном состоянии. 3.2.9. Включение приемного тракта и разрешение прерываний. Последний этап настройки. Активируем прием пакетов, установив бит #31 (RX_EN, Enable receiver) в регистре Receive Control 0 Register (п.8.9.4.9) и биты #30 и #1 в регистре Receive Control 1 Register (п.8.9.4.10). Бит #30 включает RX DMA, бит #1 устанавливает размер данных, копируемых из RX FIFO в системную память. Если бит установлен, то блок DMA скопирует данные их RX FIFO в память после получения целого пакета. Если бит сброшен, то DMA блок скопирует данные из FIFO в память при достижении установленного порога. Значение порога определяет поле RX_TH (threshold value of RX DMA FIFO). Принимать мы будем целый пакет, значение порога не используем. Прерывание разрешаем установкой бита RX_INT_EN (бит 8) в регистре Interrupt Enable Register (п. 8.9.4.4): 4. Сборка и загрузка драйвера Для сборки драйвера достаточно ввести команду make, предварительно определив переменную окружения KSRC, в которой указать путь к исходным текстам ядра. После сборки у нас появится файл net.ko. Его скопируем на загрузочную SD карту – ее можно извлечь после загрузки платы, т.к. rootfs расположена на ram-диске. Копируем net.ko на карту, вставляем ее обратно в слот и монтируем: Теперь загрузим драйвер и в логе ядра увидим примерное такую картину: /mnt # insmod net.ko[   42.956146] net: loading out-of-tree module taints kernel.[   42.962760] net-test 1c30000.ethernet: CCU setup..[   42.967580] ccu_base_addr=d0890000 clk gating reg4: 00000000 clk gating reg0: 33800140 rst reg2: 00000004 rst reg0: 33824140 [   42.978946] net-test 1c30000.ethernet: Syscon setup..[   42.984248] ccu_base_addr=d0890000 clk gating reg4: 00000000 clk gating reg0: 33800140 rst reg2: 00000004 rst reg0: 33824140 [   42.996757] net-test 1c30000.ethernet: PHY setup..[   43.101561] net-test 1c30000.ethernet: syscon=d0892000 val=148000[   43.107664] net-test 1c30000.ethernet: syscon val=148000[   43.112971] ccu_base_addr=d0890000 clk gating reg4: 00000001 clk gating reg0: 33800040 rst reg2: 00000004 rst reg0: 33824040 [   43.124290] net-test 1c30000.ethernet: EMAC hardware setup..[   43.129996] net-test 1c30000.ethernet: emac_irq=43[   43.134797] net-test 1c30000.ethernet: emac_base_addr=d08a0000[   43.140640] ccu_base_addr=d0890000 clk gating reg4: 00000001 clk gating reg0: 33820040 rst reg2: 00000004 rst reg0: 33824040 [   43.247669] net-test 1c30000.ethernet: PHY reset done[   43.253515] net-test 1c30000.ethernet: 0: physid1=00000044 physid2=00001400 phyid=00441400[   43.264049] net-test 1c30000.ethernet: 1: physid1=00000044 physid2=00001400 phyid=00441400[   44.807690] ccu_base_addr=d0890000 clk gating reg4: 00000001 clk gating reg0: 33820040 rst reg2: 00000004 rst reg0: 33824040 [   44.819059] net-test 1c30000.ethernet: EMAC reset done[   44.824524] net-test 1c30000.ethernet: rx_q: sk_buff=c1ee5300 sk_buff_dma=c1ee5340[   44.832143] net-test 1c30000.ethernet: rx_q: dma_rx=cf050000 dma_rx_phy=4f050000[   44.839594] net-test 1c30000.ethernet: 0: p=cf050000 skb=c1e48000 skb_dma=41f27700[   44.847174] net-test 1c30000.ethernet: 1: p=cf051000 skb=c1e480c0 skb_dma=41f26dc0[   44.854770] net-test 1c30000.ethernet: 2: p=cf052000 skb=c1e48180 skb_dma=41f26480[   44.862363] net-test 1c30000.ethernet: 3: p=cf053000 skb=c1e48240 skb_dma=41f25b40[   44.869962] 0: desc=cf050000 status=80000000 st=000007ff buf_addr=41f27700 next=4f051000[   44.878070] 1: desc=cf051000 status=80000000 st=000007ff buf_addr=41f26dc0 next=4f052000[   44.886158] 2: desc=cf052000 status=80000000 st=000007ff buf_addr=41f26480 next=4f053000[   44.894263] 3: desc=cf053000 status=80000000 st=000007ff buf_addr=41f25b40 next=4f050000[   44.902920] net-test 1c30000.ethernet: 02:42:8d:01:14:9b[   44.909078] phy_adjust_link: status=79ed advertise=de1[   44.914440] phy_adjust_link: advertise=de1[   46.467891] phy_aneg_complete: auto-neg complete, lpa=4de1[   46.473389] net-test 1c30000.ethernet: duplex full, speed 100Mb/s[   46.479516] net-test 1c30000.ethernet: EMAC_CTL0=0000000d[   46.484918] net-test 1c30000.ethernet: Frame control: v=00000000 Все готово к тестированию. 5. Тестирование. Для тестирования напишем приложение, которое будет формировать широковещательные пакеты и передавать их в сеть. Из широковещательного в этих пакетах только адрес назначения, остальное место заполним простым паттерном, чтобы было видно что это наш пакет. Собрав и запустив тестовую утилиту (запускать с правами root), увидим как драйвер принимает наши пакеты: На этом первую часть статьи завершим. Мы рассмотрели все ранее запланированные вопросы, разобрали структуру сетевого адаптера, узнали как подготовить его к работе и принимать сетевые пакеты. Во второй части продолжим изучение драйвера, перейдем к использованию стандартных ядерных фреймворков clock, reset и пр., и разберем как передавать сетевые пакеты. Материалы к статье: исходные тексты драйвера из первой части git clone –b p1 https://github.com/ubob74/net.git корневая файловая система https://github.com/ubob74/net/raw/p1/files/ramdisk.tgz загрузочный образ SD карты https://github.com/ubob74/net/raw/p1/files/image.gz