написание драйверов для linux

Драйверы устройств в Linux

Часть 2: Пишем в классе наш первый драйвер для Linux

Оригинал: «Device Drivers, Part 2: Writing Your First Linux Driver in the Classroom»
Автор: Anil Kumar Pugalia
Дата публикации: December 1, 2010
Перевод: Н.Ромоданов
Дата перевода: июнь 2012 г.

Светлана и Пагс добрались в свой класс с опозданием и увидели, что их профессор уже начал читать лекцию. Светлана робко попросила разрешения войти. Раздраженный профессор Гопи ответил: «Входите! Вы, друзья, опять сегодня опоздали, и по какой причине»?

Пагс поспешно ответил, что они обсуждали именно ту тему, которую сегодня изучают в классе — драйверы устройств в Linux. Пагс был более, чем счастлив, когда профессор сказал: «Хорошо! Тогда что-нибудь скажите о динамической загрузке в Linux. Если вы справитесь, то я прощу вас обоих!». Пагс знал, что один из способов сделать профессора счастливым, это — покритиковать Windows.

Он объяснил: «Как известно, при обычной установке драйверов в Windows для того, чтобы их активировать, необходимо перезагрузить систему. А если это, предположим, действительно неприемлемо в случае, если это нужно делать на сервере? Вот где выигрывает Linux. В Linux можно загружать и выгружать драйверы на лету, и это активно используется сразу после загрузки системы. Кроме того, драйвер мгновенно отключается после его выгрузки. Это называется динамической загрузкой и выгрузкой драйверов в Linux «.

Это впечатлило профессора. «Хорошо! Идите на свои места, но больше не опаздывайте». Профессор продолжил лекцию: «Теперь, когда вы уже знаете, что такое динамическая загрузка и выгрузка драйверов, я, прежде, чем мы перейдем к написанию нашего первого драйверов, покажу вам, как загружать и выгружать драйвера».

Динамическая загрузка драйверов

Рис.1: Предварительно собранные модули Linux

Чтобы динамически загружать и выгружать драйверы, воспользуйтесь следующими командами, которые находятся в директории /sbin и должны выполняться с привилегиями пользователя root:

Рис.2: Операции с модулями Linux

Наш первый драйвер для Linux

Сборка нашего первого драйвера

Подведем итог

Команда lsmod должна вам сообщить о том, что драйвер ofd загружен.

Источник

Как написать свой первый Linux device driver

Здравствуйте, дорогие хабрачитатели.

Цель данной статьи — показать принцип реализации драйверов устройств в системе Linux, на примере простого символьного драйвера.

Для меня же, главной целью является подвести итог и сформировать базовые знания для написания будущих модулей ядра, а также получить опыт изложения технической литературы для публики, т.к. через полгода я буду выступать со своим дипломным проектом (да я студент).

Это моя первая статья, пожалуйста не судите строго!

Получилось слишком много букв, поэтому я принял решение разделить статью на три части:

Часть 1 — Введение, инициализация и очистка модуля ядра.
Часть 2 — Функции open, read, write и trim.
Часть 3 — Пишем Makefile и тестируем устройство.

Перед вступлением, хочу сказать, что здесь будут изложены базовые вещи, более подробная информация будет изложена во второй и последней части данной статьи.

Подготовительные работы

Спасибо Kolyuchkin за уточнения.

Символьный драйвер (Char driver) — это, драйвер, который работает с символьными устройствами.
Символьные устройства — это устройства, к которым можно обращаться как к потоку байтов.
Пример символьного устройства — /dev/ttyS0, /dev/tty1.

К вопросу про проверсию ядра:

Драйвер представляет каждое символьное устройство структурой scull_dev, а также предостовляет интерфейс cdev к ядру.

Устройство будет представлять связный список указателей, каждый из которых указывает на структуру scull_qset.

Для наглядности посмотрите на картинку.

Для регистрации устройства, нужно задать специальные номера, а именно:

MAJOR — старший номер (является уникальным в системе).
MINOR — младший номер (не является уникальным в системе).

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

После того как мы определили номера для нашего устройства, мы должны установить связь между этими номерами и операциями драйвера. Это можно сделать используя структуру file_operations.

В ядре есть специальные макросы module_init/module_exit, которые указывают путь к функциям инициализации/удаления модуля. Без этих определений функции инициализации/удаления никогда не будут вызваны.

Здесь будем хранить базовую информацию об устройстве.

Последним этапом подготовительной работы будет подключение заголовочных файлов.
Краткое описание приведено ниже, но если вы хотите копнуть поглубже, то добро пожаловать на прекрасный сайт: lxr

Инициализация

Теперь давайте посмотрим на функцию инициализации устройства.

Первым делом, вызывая alloc_chrdev_region мы регистрируем диапазон символьных номеров устройств и указываем имя устройства. После вызовом MAJOR(dev) мы получаем старший номер.
Далее проверяется вернувшееся значение, если оно является кодом ошибки, то выходим из функции. Стоит отметить, что при разработке реального драйвера устройства следует всегда проверять возвращаемые значения, а также указатели на любые элементы (NULL?).

Если вернувшееся значение не является кодом ошибки, продолжаем выполнять инициализацию.

Выделяем память, делая вызов функции kmalloc и обязательно проверяем указатель на NULL.

Стоит упомянуть, что вместо вызова двух функций kmalloc и memset, можно использовать один вызов kzalloc, который выделят область памяти и инициализирует ее нулями.

Продолжаем инициализацию. Главная здесь функция — это scull_setup_cdev, о ней мы поговорим чуть ниже. MKDEV служит для хранения старший и младших номеров устройств.

Возвращаем значение или обрабатываем ошибку и удаляем устройство.

Выше были представлены структуры scull_dev и cdev, которые реализуют интерфейс между нашим устройством и ядром. Функция scull_setup_cdev выполняет инициализацию и добавление структуры в систему.

Удаление

Функция scull_cleanup_module вызывается при удалении модуля устройства из ядра.
Обратный процесс инициализации, удаляем структуры устройств, освобождаем память и удаляем выделенные ядром младшие и старшие номера.

С удовольствием выслушаю конструктивную критику и буду ждать feedback’a.

Если вы нашли ошибки или я не правильно изложил материал, пожалуйста, укажите мне на это.
Для более быстрой реакции пишите в ЛС.

Источник

Пишем свой драйвер под Linux

Хочу признаться сразу, что я вас отчасти обманул, ибо драйвер, если верить википедии — это компьютерная программа, с помощью которой другая программа (обычно операционная система) получает доступ к аппаратному обеспечению некоторого устройства. А сегодня мы создадим некую заготовку для драйвера, т.к. на самом деле ни с каким железом мы работать не будем. Эту полезную функциональность вы сможете добавить сами, если пожелаете.

То, что мы сегодня создадим, корректнее будет назвать LKM (Linux Kernel Module или загрузочный модуль ядра). Стоит сказать, что драйвер – это одна из разновидностей LKM.

Писать модуль мы будем под ядра линейки 2.6. LKM для 2.6 отличается от 2.4. Я не буду останавливаться на различиях, ибо это не входит в рамки поста.

Мы создадим символьное устройство /dev/test, которое будет обрабатываться нашим модулем. Хочу сразу оговориться, что размещать символьное устройство не обязательно в каталоге /dev, просто это является частью «древнего магического ритуала».

Немного теории

Если кратко, то LKM – это объект, который содержит код для расширения возможностей уже запущенного ядра Linux. Т.е. работает он в пространстве ядра, а не пользователя. Так что не стоит экспериментировать на рабочем сервере. В случае ошибки, закравшейся в модуль, получите kernel panic. Будем считать, что я вас предупредил.

Модуль ядра должен иметь как минимум 2 функции: функцию инициализации и функцию выхода. Первая вызывается во время загрузки модуля в пространство ядра, а вторая, соответственно, при выгрузке его. Эти функции задаются с помощью макроопределений: module_init и module_exit.

Стоит сказать несколько слов о функции printk(). Основное назначение этой функции — реализация механизма регистрации событий и предупреждений. Иными словами эта функция для записи в лог ядра некой информации.

Т.к. драйвер работает в пространстве ядра, то он отграничен от адресного пространства пользователя. А нам хотелось бы иметь возможность вернуть некий результат. Для этого используется функция put_user(). Она как раз и занимается тем, что перекидывает данные из пространства ядра в пользовательское.

Хочу ещё сказать пару слов о символьных устройствах.

Между словом «disk» и датой есть два числа разделённых запятой. Первое число называют старшим номером устройства. Старший номер указывает на то, какой драйвер используется для обслуживания данного устройства. Каждый драйвер имеет свой уникальный старший номер.

Я не буду сильно углубляться в теорию, т.к. кому интересно – тот сможет сам почитать про это подробнее. Я дам ссылку в конце.

Прежде чем начать

Для компиляции модуля нам потребуются заголовки текущего ядра.

В debian/ubutnu их можно легко поставить так (к примеру для 2.6.26-2-686):
apt-get install linux-headers-2.6.26-2-686
Либо собрать пакет для вашего текущего ядра самим: fakeroot make-kpkg kernel_headers

Исходник

// Ниже мы задаём информацию о модуле, которую можно будет увидеть с помощью Modinfo
MODULE_LICENSE( «GPL» );
MODULE_AUTHOR( «Alex Petrov

» );
MODULE_DESCRIPTION( «My nice module» );
MODULE_SUPPORTED_DEVICE( «test» ); /* /dev/testdevice */

#define SUCCESS 0
#define DEVICE_NAME «test» /* Имя нашего устройства */

// Поддерживаемые нашим устройством операции
static int device_open( struct inode *, struct file * );
static int device_release( struct inode *, struct file * );
static ssize_t device_read( struct file *, char *, size_t, loff_t * );
static ssize_t device_write( struct file *, const char *, size_t, loff_t * );

// Прописываем обработчики операций на устройством
static struct file_operations fops =
<
.read = device_read,
.write = device_write,
.open = device_open,
.release = device_release
>;

// Функция загрузки модуля. Входная точка. Можем считать что это наш main()
static int __init test_init( void )
<
printk( KERN_ALERT «TEST driver loaded!\n» );

// Регистрируем устройсво и получаем старший номер устройства
major_number = register_chrdev( 0, DEVICE_NAME, &fops );

// Сообщаем присвоенный нам старший номер устройства
printk( «Test module is loaded!\n» );

// Функция выгрузки модуля
static void __exit test_exit( void )
<
// Освобождаем устройство
unregister_chrdev( major_number, DEVICE_NAME );

printk( KERN_ALERT «Test module is unloaded!\n» );
>

// Указываем наши функции загрузки и выгрузки
module_init( test_init );
module_exit( test_exit );

static int device_open( struct inode *inode, struct file *file )
<
text_ptr = text;

static int device_release( struct inode *inode, struct file *file )
<
is_device_open—;
return SUCCESS;
>

static ssize_t device_read( struct file *filp, /* include/linux/fs.h */
char *buffer, /* buffer */
size_t length, /* buffer length */
loff_t * offset )
<
int byte_read = 0;

if ( *text_ptr == 0 )
return 0;

Сборка модуля

Ну а теперь можем написать небольшой Makefile:

И проверить его работоспособность:

Посмотрим что у нас получилось:

Теперь посмотрим информацию о только что скомпилированном модуле:

root@joker:/tmp/test# modinfo test.ko
filename: test.ko
description: My nice module
author: Alex Petrov
license: GPL
depends:
vermagic: 2.6.26-2-openvz-amd64 SMP mod_unload modversions

Ну и наконец установим модуль в ядро:

root@joker:/tmp/test# insmod test.ko

Посмотрим есть ли наш модуль с списке:

root@joker:/tmp/test# lsmod | grep test

И что попало в логи:

root@joker:/tmp/test# dmesg | tail

[829528.598922] Test module is loaded!
[829528.598926] Please, create a dev file with ‘mknod /dev/test c 249 0’.

Наш модуль подсказываем нам что нужно сделать.

Последуем его совету:

root@joker:/tmp/test# mknod /dev/test c 249 0

Ну и наконец проверим работает ли наш модуль:

root@joker:/tmp/test# cat /dev/test

Наш модуль не поддерживает приём данных со стороны пользователя:

root@joker:/tmp/test# echo 1 > /dev/test

bash: echo: ошибка записи: Недопустимый аргумент

Посмотрим что что скажет модуль на наши действия:

root@joker:/tmp/test# dmesg | tail

[829528.598922] Test module is loaded!
[829528.598926] Please, create a dev file with ‘mknod /dev/test c 249 0’.
[829747.462715] Sorry, this operation isn’t supported.

root@joker:/tmp/test# rmmod test

И посмотрим что он нам скажет на прощание:

root@joker:/tmp/test# dmesg | tail

[829528.598922] Test module is loaded!
[829528.598926] Please, create a dev file with ‘mknod /dev/test c 249 0’.
[829747.462715] Sorry, this operation isn’t supported.
[829893.681197] Test module is unloaded!

Удалим файл устройства, что бы он нас не смущал:

root@joker:/tmp/test# rm /dev/test

Заключение

Дальнейшее развитие этой «заготовки» зависит только от вас. Можно превратить её в настоящий драйвер, который будет предоставлять интерфейс к вашему девайсу, либо использовать для дальнейшего изучения ядра Linux.

Только что в голову пришла совершенно безумная идея сделать sudo через файл устройства. Т.е. посылаем в /dev/test команду и она выполняется от имени root.

Литература

И под конец дам ссылку на книгу заклинаний LKMPG (Linux Kernel Module Programming Guide)

UPD2:
Поправил ошибки в исходнике.
Парсер глючит и сохраняет ‘MODULE_DEscriptION( «My nice module» );’. Естественно в module_description все буквы заглавные.

UPD3:
segoon прислал несколько поправок к посту:

1) В функции device_open() находится race condition:

static int device_open( struct inode *inode, struct file *file )
<
text_ptr = text;

Источник

Пишем драйвер сетевого устройства для Linux

Введение

Настоящая статья была написана для тех, кто только начинает изучение ядра Linux и хочет знать о драйверах сетевых устройств. Предполагается, что читатель в должной мере знаком с языком C и средой Linux.

В настоящей статье описывается сетевой драйвер для сетевой платы RealTek 8139. Я выбрал чипсет RealTek по следующим двум причинам: Во-первых, компания RealTek бесплатно предоставляет технические спецификации на этот чипсет (спасибо, RealTek!). Во-вторых, он сравнительно дешев. В Индии его можно приобрести менее, чем за 300 рупий (приблизительно 7 долларов США).

Драйвер, представленный в статье, имеет минимум функций; он просто посылает и принимает пакеты и собирает некоторую статистику. Для ознакомления с полноценным драйвером, написанном на профессиональном уровне, пожалуйста, смотрите исходные тексты Linux.

Подготовка к разработке драйвера

Прежде, чем начать разработку драйвера, нам нужно для этого подготовить систему. Настоящая статья была написана и проверена для ядра Linux 2.4.18, в котором содержится исходный код драйвера чипсета RealTek8139. Может быть в ядре, с которым Вы работаете, драйвер включен в состав ядра, либо скомпилирован как модуль. Для того, чтобы избавиться от каких-либо сюрпризов, желательно собрать ядро, в котором не будет драйвера RealTek8139 ни в каком из вариантов. Если Вы не знаете, как откомпилировать ядро, я рекомендую обратиться по следующей ссылке.

Настоятельно рекомендуется иметь книгу Rubini Linux Device Drivers в качестве справочника по API. В настоящий момент это лучший известный мне источник сведений для разработки драйверов устройств под Linux.

Начинаем разработку драйвера

Рассмотрим разработку драйвера поэтапно по следующим пунктам:

Обнаружение устройства

Каждый разработчик имеет уникальный, назначенный только ему идентификатор ID и назначает уникальный идентификатор ID каждому конкретному виду устройств. Макросы REALTEK_VENDER_ID и REALTEK_DEVICE_ID определяют эти идентификаторы ID. Вы можете найти эти значения в «PCI Configuration Space Table» в спецификациях RealTek8139.

Включение устройства

Давайте на время приостановим процесс изучения кода драйвера; вместо этого мы рассмотрим несколько важных тем, чтобы понять, чем с точки зрения Linux является сетевое устройство. Мы рассмотрим сетевые устройства и разницу между вводом-выводом с отображением в память (memory-mapped I/O), вводом-выводом с отображением по портам (port-mapped I/O) и конфигурационным адресным пространством PCI (PCI configuration space).

Что такое сетевые устройства

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

Хотя мы не упомянули все поля структуры net_device, пожалуйста, обратите внимание на то, что нет никаких ссылок на функцию, принимающую пакеты. Это делается обработчиком прерываний устройства, что мы также рассмотрим далее в настоящей статье.

Доступ к устройству не через шину

Замечание: Этот раздел был взят из книги Алана Кокса (Alan Cox) «Bus-Independent Device Accesses», которая доступна по ссылке http://tali.admingilde.org/linux-docbook/deviceiobook.pdf

Ввод-вывод с отображением в память (Memory-Mapped I/O)

Наиболее широко используемый способ ввода/вывода – ввод/вывод с отображением в память (memory-mapped I/O). Т.е. часть адресного пространства CPU интерпретируется не как адреса памяти, а используется для доступа к устройству. В некоторых системах с определенной архитектурой требуется, чтобы устройства имели фиксированные адреса, но в большинстве систем имеется некоторый способ обнаружения устройств. Хорошим примером такой схемы является обход шины PCI. В настоящей статье не рассматривается, как получить такой адрес, но предполагается, что изначально он у вас есть.

Физический адрес является беззнаковым числом типа long. Эти адреса не используются напрямую. Вместо этого для того, чтобы получить адрес, который можно было передать в функцию так, как это описано ниже, вам следует вызвать ioremap. В ответ Вы получите адрес, который можно использовать для доступа к устройству.

После того, как Вы закончите использовать устройство (скажем, в вашей подпрограмме выхода из модуля), вызовите iounmap для того, чтобы вернуть ядру адресное пространство. Архитектура большинства систем позволяет выделять новое адресное пространство каждый раз, когда Вы вызываете ioremap, и использовать его до тех пор, пока Вы не вызовете iounmap.

Доступ к устройству

Для некоторых устройств (работающих, например, с буферами кадров) было бы удобнее за один раз передавать блоки, значительно большие чем 8 байтов. Для этих устройств предлагается использовать функции memcpy_toio, memcpy_fromio и memset_io. Не используйте memset или memcpy для работы с адресами ввода/вывода; они не гарантируют копирование данных в правильном порядке.

Работа функций чтения и записи должна происходить в определенном порядке. Т.е. компилятору не разрешается выполнять переупорядочивание последовательностей ввода-вывода. Если компилятору разрешается оптимизировать порядок, то Вы можете использовать функцию __readb и ей подобные с тем, чтобы не требовать строгого сохранения порядка выполнения операций. Пользуйтесь этим с осторожностью. Операция rmb блокирует чтение памяти. Операция wmb блокирует запись в память.

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

Доступ к пространству портов

В отличие от ввода-вывода с отображением в память, для доступа к пространству портов подготовка не требуется.

Доступ к пространству портов или устройства с отображением ввода-вывода

Эти функции имеют несколько вариантов. Для некоторых устройств требуется, чтобы доступ к их портам происходил со сниженной скоростью. Эта функциональность обеспечивается при помощи добавления _p в конце команды. Имеются также эквиваленты команды memcpy. Функции ins и out копируют байты, слова и длинные слова в заданный порт и из него.

Что такое конфигурационноеадресное пространство PCI (PCI Configuration Space)

В этом разделе мы рассмотрим конфигурационное адресное пространство PCI. Устройства PCI имеют 256 байтное адресное пространство. Первые 64 байта используются стандартным образом, тогда как использование оставшихся байтов зависит от устройства. На рис.1. показано стандартное конфигурационное адресное пространство PCI

Рис.1: Конфигурационное адресное пространство

Инициализация net_device

Таблица 4: Структура rtl8139_private

Таблица 5: Инициализация net_device

Теперь давайте объясним, что мы сделали в таблице 5. Функцию probe_for_realtek8139 мы уже рассматривали. Функция rtl8139_init распределяет память для локального указателя rtl8139_dev, который мы должны использовать как net_device. Вдобавок эта функция заполняет компоненту pci_dev of rtl8139_private для обнаруженного устройства.

В оставшейся части кода, приведенного в таблице 5, выполняется обычная инициализация структуры net_device. Заметьте, что теперь мы читаем аппаратный адрес из устройства и записываем его в dev_addr. Если Вы смотрели раздел «Описания регистров» в спецификации RealTek8139, то знаете, что первые 6 байтов являются аппаратным адресом устройства. Также мы должны иметь проинициализированные компоненты указателя на функцию, но не должны определять какую-либо соответствующую функцию. Теперь для компиляции модуля мы добавим фиктивные функции.

Таблица 6: Фиктивные функции

Обратите внимание, что в init_module пропущена часть, обрабатывающая ошибки. Вы можете написать эту обработку, заглянув для этого в модуль cleanup_module, приведенный ниже:

Таблица 7: Функция cleanup_module

Таблица 8: Компиляция драйвера

Снова остановим разработку драйвера и попытаемся лучше понять, какой механизм передачи и приема данных используется в устройстве.

Какой механизм передачи используется в RealTek 8139

В этом разделе описывается механизм передачи данных в устройстве RealTek8139; однако рекомендуется загрузить руководство по программированию «RTL8139 (A/B) Programming Guide», в котором приведены все подробности. В RealTek8139 имеется 4 дескриптора передачи, каждый дескриптор имеет фиксированное смещение адреса ввода-вывода. Четыре дескриптора используются циклически. Это означает, что для передачи четырех пакетов драйвер будет использовать в циклическом порядке дескриптор 0, дескриптор 1, дескриптор 2 и дескриптор 3. Для передачи следующего пакета драйвер снова будет использовать дескриптор 0 (при условии, что он свободен). В спецификациях RealTek8139 в разделе «Описание регистров» указывается, что регистры TSAD0, TSAD1, TSAD2 и TSAD3 имеют смещения 0x20, 0x24, 0x28, 0x2C, соответственно. В этих регистрах хранится «начальный адрес дескрипторов передачи «, т.е. в них хранится стартовый адрес (в памяти) пакетов, которые должны быть переданы. Позже устройство считает содержимое пакетов из этих адресов DMA, перепишет их в свой собственный стек FIFO, а затем выполнит передачу данных в сеть.

Мы скоро увидим, что этот драйвер выделяет память прямого доступа (доступ DMA), где будут храниться пакеты, и записывает адрес этой памяти в регистры TSAD.

Какой механизм приема используется в RealTek 8139

Приемная часть RTL8139 спроектирована как кольцевой буфер (линейная память, управление которой осуществляется как кольцевой памятью). Всякий раз, когда устройство принимает пакет, содержимое пакета запоминается в память кольцевого буфера и изменяется место, куда будет записываться содержимое следующего пакета (начальный адрес первого пакета + длина первого пакета). Устройство продолжает так запоминать пакеты до тех пор, пока не исчерпается линейная память. В этом случае устройство снова начинает запись с начального адреса линейной памяти, реализуя, таким образом, кольцевой буфер.

Делаем устройство готовым к передаче пакетов

Таблица 10: Определения регистров RTL 8139

Таблица 11: Пишем функцию открытия устройства

Таблица 12: Пишем функцию start_xmit

Функция rtl8139_start_xmit, показанная в таблице 12, исключительно тривиальная. Сначала она ищет имеющийся дескриптор передачи, а затем проверяет, чтобы размер пакета был, по меньшей мере, 60 байтов (поскольку размер пакетов Ethernet не может быть меньше 60 байтов). Как только это будет сделано, будет вызвана функция skb_copy_and_csum_dev, которая скопирует содержимое пакетов в имеющуюся память DMA. Следующей функцией writel мы информируем устройство о длине пакета. После этого пакеты передаются в сеть. Затем мы определяем имеющиеся в наличии следующие дескрипторы передачи и, если так случится, что он будет равен дескриптору, для которого передача еще не завершена, то мы остановим устройство; в противном случае мы просто выйдем из функции.

Теперь наше устройство готово отсылать пакеты (помните, что мы еще не можем принимать пакеты). Откомпилируйте драйвер и попытайтесь послать пакеты ping. На другом конце Вы должны увидеть несколько пакетов ARP. Даже если удаленные хосты будут отвечать на пакеты ARP, они для нас будут бесполезными, поскольку мы не готовы их принимать.

Делаем устройство готовым к приему пакетов

Таблица 13: Расширяем структуру rtl8139_private

Компонента stats должна хранить статистику с устройства (большая часть статистики, выдаваемой ifconfig, берется из этой структуры). Следующая компонента, rx_ring, является адресом памяти в ядре, где запоминаются принятые пакеты, а rx_ring_dma – физический адрес этой памяти. Как мы скоро увидим, компонент cur_rx используется для отслеживания места, куда будет записываться следующий пакет.

Таблица 14: Расширяем функцию rtl8139_open

Код в таблице 14 вычисляет размер памяти, нужный для кольцевого буфера. Вычисление RX_BUF_TOT_LEN зависит от некоторых конфигурационных параметров устройства. Как мы скоро увидим, в функции rtl8139_hw_start устанавливаются значения битов 12 – 11 регистра RCR в 10, что задает длину приемного буфера равной 32K+16. Таким образом, мы выделяем этот объем памяти для приемного буфера. Также мы устанавливаем значение бита с 7 в 1, что означает, что RTL8139 переместит остальные пакетные данные в память, которая идет сразу после конца приемного буфера. Поэтому мы дополнительно выделяем 2048 байтов с тем, чтобы справиться с такими ситуациями.

Таблица 15: Расширяем функцию rtl8139_hw_start

Следующее крупное изменение – конфигурирование регистра RBSTART. В этом регистре содержится стартовый адрес приемного буфера. Далее мы обнуляем регистр MPC (Missed Packet Counter – счетчик ошибочных пакетов) и конфигурируем устройство так, чтобы не генерировались ранние прерывания.

Последняя большая функция, которую мы хотим обсудить – обработчик прерываний устройства. Этот обработчик прерываний ответственен за прием пакетов, а также за обновление необходимой статистики. Ниже приведен исходный код обработчика прерываний.

Таблица 16: Обработчик прерываний

Таблица 17: Функция rtl8139_get_stats

Этим завершается разработка нашего драйвера. Откомпилируйте его, снова вставьте его в ядро (Вы должны с помощью команды rmmod выгрузить предыдущий модуль) и пропингуйте другой хост. Вы должны получить ответ на пинги.

Хотя драйвер профессионального уровня содержит гораздо больше возможностей, чем имеется в нашем драйвере, последний даст вам хорошее представление о сетевых драйверах и поможет понять процесс разработки драйверов.

Источник

Понравилась статья? Поделиться с друзьями:

Не пропустите наши новые статьи:

  • написание демона linux c
  • написание shell скриптов linux
  • написание bat файлов для windows 10
  • написание bat скриптов для windows
  • Нанокад что за программа

  • Операционные системы и программное обеспечение
    0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest
    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии