1
1
Fork 0

Драйвер devnp-e100.so для ЗОСРВ Нейтрино редакции 2020

This commit is contained in:
commit b31d5858eb
54 changed files with 5696 additions and 0 deletions

4
Makefile Normal file
View File

@ -0,0 +1,4 @@
LIST=ALL
EARLY_DIRS=lib
include recurse.mk

472
README.md Normal file
View File

@ -0,0 +1,472 @@
## Общая структура сетевого стека
```
┌─────────────────────────────┐
│ │
│ Сетевой контроллер │
│ │
└──────────────▴──────────────┘
┌──────────────┴──────────────┐
│ │
│ Сетевой драйвер (devnp-*) │
│ │
└──────────────▴──────────────┘
*
┌──────────────┴──────────────┐ ┌───────────────────────────┐
│ │ │ │
│ Сетевой менеджер (io-pkt-*) ◂─── * ───┤ Клиентское приложение │
│ │ │ │
└─────────────────────────────┘ ▲ └───────────────────────────┘
Интерфейс libsocket ───────┘
```
## Дерево исходных кодов
```
|- hardware/devnp/
| |- e100/ - Исходный код драйвера Fast Ethernet контроллеров Intel 8255x
| |- sample/ - Исходный код примера сетевого драйвера (поясняет изложенное в данном readme)
| `- Makefile - Правила сборки дерева исходников
|
|- netdrivers.mk - Параметры сборки драйверов
`- Makefile - Правила сборки дерева исходников
```
## Сборка драйвера
- Установить и настроить [комплект разработчика](https://help.kpda.ru/help/topic/ru.kpda.doc.dev_tools_ru/html/devkit/devkit.html) для [ЗОСРВ "Нейтрино" редакции 2020](https://help.kpda.ru/help/index.jsp).
- Выполнить команду:
```
make
```
## Запуск драйвера
Общая схема запуска драйвера:
```
io-pkt-* -d e100 [опция[,опция ...]] ...
```
или
```
io-pkt-*
...
mount -T io-pkt -o [опция[,опция ...]] devnp-e100.so
```
## Разработка сетевого драйвера
Сетевой драйвер выступает прослойкой между оборудованием и сетевым стеком. Он включает низкоуровневые обращения к конкретному оборудованию и высокоуровневые интерфейсы взаимодействия с _io-pkt_. Первая часть уникальна для каждого конкретного устройства и в данном материале не рассматривается.
При изучении основ разработки сетевых драйверов следует ориентироваться на драйвер [sample](hardware/devnp/sample), поскольку он свободен от аппаратно-зависимого кода и может расмативаться в качестве каркаса (шаблона) при разработке нового драйвера.
Далее будут рассматриваться следующие особенности драйверов:
- Инициализация
- Обработка прерываний и получение пакетов
- Отправка пакетов
- Периодические таймеры
- Состояние линка
- Контроль и управление
- Завершение драйвера
- Краткосрочные ожидания в драйвере
- Многопоточность
- Функция отсоединения
**Инициализация**
Код инициализации является самой сложной частью драйвера. Он разбит на две части: однократную и периодическую, причем, последняя требует более тщательной отладки.
Инициализация начинается с регистрации точки входа:
```
struct nw_dll_syms sam_syms[] = {
{ "iopkt_drvr_entry", &IOPKT_DRVR_ENTRY_SYM( sam ) },
{ NULL, NULL }
};
```
Это определение требует от сетевого стека в момент инициализации вызвать для данного драйвера функцию *sam_entry()*. Задача этой фунции в конечном счете вызвать *dev_attach()* для каждого экземпляра поддерживаемого оборудования, обнаруженного драйвером. Очевидно, экземпляров может быть один или несколько.
Прототип функции *dev_attach()*:
```
int dev_attach( char *drvr,
char *options,
struct cfattach *ca,
void *cfat_arg,
int *single,
struct device **devp,
int (*print)( void *, const char * ) );
```
Ее аргументы:
- **drvr**. Строка, используемая в качестве префикса имени интерфейса. В нашем примере оно указано как "sam", и по умолчанию создается интерфейс "sam0".
- **options**. Строка параметров, переданая драйверу. Она анализируется *dev_attach()* в поисках параметров *name*, *lan* и *unit*, которые переопределяют имя интерфейса по умолчанию. Параметры *lan* и *unit* идентичны по смыслу - они переопределяют число, добавляемое к имени интерфейса. Опция *name* переопределяет аргумент *drvr*.
- **ca**. Указатель на структуру *cfattach*, которая определяет размер структуры устройства, а также detach/attach-функции драйвера. Для инициализации экземпляра этой структуры используется макрос *CFATTACH_DECL()*.
- **cfat_arg**. Аргумент, который передается attach-функции драйвера в качестве третьего аргумента.
- **single**. Если параметр *lan* или *unit* находится в строке параметров, то целое число, на которое указывает *single*, устанавливается равным 1.
- **devp**. Указатель наструктуру `struct device`:
- Если не равно `NULL`, параметр определяет родительское устройство. При завершении драйвера проверяется, что удаляемое устройство не является родителем других устройств. Большинство драйверов устанавливают *devp* в `NULL`.
- Функция *dev_attach()* через *devp* преедает указатель на структуру, созданную функцией для нового устройства. Этот указатель также передается в качестве второго аргумента attach-функции.
- **print**. `NULL` или указатель на функцию отладки. *dev_attach()* будет вызывать ее следующим способом:
```
if ( print != NULL )
(*print)( cfat_arg, NULL );
```
Если возникает ошибка, *dev_attach()* возвращает значение *errno*. В противном случае вызывается attach-функция драйвера, которая должна вернуть `EOK` в случае успеха. При этом *dev_attach()* возвращает значение, переданное attach-функцией.
Функция *dev_attach()* через препроцессор с помощью параметра *&sam_ca* получает указатель на:
```
CFATTACH_DECL( sam, sizeof( struct sam_dev ), NULL, sam_attach, sam_detach, NULL );
```
В конечном счете для каждого инициализируемого экземпляра оборудования **ровно один раз** быдет вызвана функция *sam_attach()*. В общем случае, она решает две задачи: выделение ресурсов, требуемых драйверу и оборудованию, а также регистрация оборудования в стеке.
Функция *sam_attach()* подключается к стеку двумя способами:
- Настройка callout-фукнций в структуре *ifp*. Например, когда стек хочет отправить пакет в сеть, вызывается указатель на callout-фукнцию *ifp->if_start()*. Обратите внимание, что он не имеет никакого отношения к инициализации. В шаблоне драйвера в функции *sam_attach()* указатель *ifp->if_start* устанавливается на адрес функции *sam_start()*.
- Настройка обработки прерываний. Вызвав функцию *interrupt_entry_init()*, стеку передается в качестве параметра указатель на структуру *sc_inter*. Эта структура должна быть размещена в пределах уникального для экземпляра оборудования дескриптора устройства, обслуживаемого драйвером. Структура *sc_inter* включает указатели на функции *sam_process_interrupt()* и *sam_enable_interrupt()*, а также указатель на уникальный дескриптор устройства (*sam*, от названия драйвера - sample).
Обратите внимание, что в драйвере нигде не вызывается *pthread_create()*. Это является важной особенностью сетевого стека. Все потоки в нем порождает лишь сам стек. Драйверный код также должен работать под управлением стека, отсюда явный запрет на создание собственных потоков.
На данном этапе **однократная инициализация** драйвера считается завершенной. При этом, сетевое оборудование еще не является проинициализированным и не готово к приему-передаче пакетов. Для это требуется хотя бы однократный вызов кода, отвечающего за **периодическую инициализацию**. Он вызывается при запуске утилиты *ifconfig*. Также он может быть однократно вызван самим драйвером из функции *sam_attach()* в заключительной ее части. Пример:
```
ifconfig sam0 10.42.107.238
```
В этот момент стек вызовет callout-функцию *ifp->if_init()* (которая указывает на функцию *sam_init()*) для включения аппаратного обеспечения.
Имейте в виду, callout-функция *ifp->if_init()* будет вызываться стеком регулярно при практически каждом вызове утилиты *ifconfig*. Например, при вызове:
```
ifconfig sam0 mtu 8100
```
Таким образом, данная функция должна обеспечить завершение инициализации оборудования. Можно увидеть, что было бы ошибкой устанавливать MTU в функции *sam_attach()*. Функция периодической инициализации *sam_init()* должна постоянно проверять текущую конфигурацию оборудования и корректировать ее в соответствии с требованиями пользователя. При этом было бы ошибкой отключать аппаратуру и инициализировать ее заново, так как даже небольшое изменение конфигурации будет прерывать любые текущие потоки трафика.
Резюмируя сказанное: функция *sam_attach()* вызывается однократно для выделения ресурсов и подключения к сетевому стеку, а *sam_init()* вызывается многократно для настройки и включения оборудования.
Если разрабатывается драйвер для сетевого контроллера на шине PCI, драйверу придется озаботиться вопросом идентификации оборудования посредством VID:DID идентификаторов. В рассматриваемом примере драйвера такого кода по понятным причинам нет. Аналогичные задачи должны быть решены и при обращении к шине USB.
**Обработка прерываний и получение пакетов**
В шаблоне есть две функции *sam_isr()*. Одна использует для работы с прерываниями функцию *InterruptMask()*, а вторая маскирует их через регистры. Вторая может быть немного быстрее. В любом случае *sam_isr()* должна замаскировать прерывание и поставить в очередь сетевого стека фактический обработчик прерывания, вызвав *interrupt_queue()*.
После завершения ISR возвращаемое *interrupt_queue()* значение пробуждает сетевой стек и вызывает callout-функцию драйвера *sam_process_interrupt()*, определенную через указатель *sam->sc_inter.func()*. Функция *sam_process_interrupt()* и является фактическим обработчиком прерывания, который должен обслуживать запросы оборудования: выполнять работу с регистрами, обрабатывать ошибки и т.д. Она также может обслуживать TX-логику оборудования (обычно это не рекомендуется из-за негативного влияния на производительность).
При этом, ISR должен обслуживать RX-логику оборудования. Любой заполненный контроллером входящий пакет должен удаляться из оборудования, а новые пустые пакеты должны возвращаться в оборудование. Заполненные полученные пакеты передаются в стек с помощью callout-функции *ifp->if_input()*.
Возвращаемое ISR значение 0 означает, что функция завершилась не окончив всю работу. Это позволит другим сетевым интерфейсам выполнять обработку своих ISR, помещая *sam_process_interrupt()* в конец очереди выполнения. Возвращение 1 сигнализирует о том, что драйвер завершил обработку прерываний. В этом случае стеком будет вызвана функция *sam_enable_interrupt()*, которая должна размаскировать (включить) прерывания и обратить действия, выполненные *sam_isr()*.
**Отправка пакетов**
Когда сетевой стек хочет передать пакет, он вызывает callout-функцию *ifp->if_start()* драйвера, которая была задана в *dev_attach()* и соответствует *sam_start()*.
В первую очередь стоит убедиться, что имеются аппаратные ресурсы (дескрипторы, буферы и т.д.), доступные для передачи пакетов. Если ресурсы отсутствуют, драйвер должен вернуться из функции *ifp->if_start()*, установив статус `IFF_OACTIVE`:
```
ifp->if_flags_tx |= IFF_OACTIVE;
```
Также необходимо освободить мьютекс передачи, о котором будет сказано ниже.
Если данный флаг установлен стек не будет пытаться вызывать функцию *ifp->if_start()* при добавлении пакета в выходную очередь интерфейса. На этом этапе драйвер определить момент, когда ресурсов станет достаточно (с помощью периодического опроса, или в момент бработки TX-прерывания). После этого драйвер должен захватить мьютекс передачи и снова вызвать функцию начала передачи для отправки данных в очередь вывода.
Большинство драйверов, не возвращают управление из *ifp->if_start()*, пока не закончатся поступающие от стека пакеты или не закончатся аппаратные ресурсы.
Есть несколько удобных макросов, которые можно использовать при этом:
- Макрос *IFQ_POLL()* позволяет определить имеются ли у стека другие доступные для отправки пакеты. Если их нет, обработку исходящих пакетов можно завершить.
- Макрос *IFQ_DEQUEUE()* извлекает из очереди стека первый пакет, готовый к отправке. Некоторые драйверы не используют первый макрос, ожидая, что стек повторно вызовет *ifp->if_start()* при наличии других готовых исходящих пакетов. При извлечении пакета из очереди, его обязательно передать в сеть.
Прежде чем вернуться из этой callout-функции, следует освободить мьютекс передачи:
```
NW_SIGUNLOCK_P( &ifp->if_snd_ex, iopkt_selfp, wtp );
```
Обратите внимание, что в рассматриваемом драйвере функция *ifp->if_start()* вызывает *m_free( m )* для освобождения переданного пакета. Это осуществляется для избежания утечки памяти. Но если сетевая карта функционирует на основе дескрипторов, выполнять это в общем случае не требуется, так как дескрипторы обычно циклически переиспользуются.
Если сетевая карта требует копирования передаваемого пакета в буфер, вызвать *m_free( m )* скорее всего придется. Она сообщит стеку, что буфер доступен для повторного использования и в него можно производить запись новых данных.
Для сетевых карт, ориентированных на дескрипторы, передаваемый пакет не копируется: аппаратное обеспечение выполняет DMA-операцию и вы буфер пакета будет освобожден только после того, как операция завершится. Это позвлит избежать перезаписи данных пакета до его передачи в сеть. В исходном коде таких драйверов можно обнаружить функцию "harvest" или "reap", которая будет проверять переданные дескрипторы и попутно освобождать их буферы.
Это требует, чтобы где-то хранился указатель на переданный пакет (*mbuf*). Часто аппаратное обеспечение имеет несколько свободных байтов в дескрипторе для этой цели. В противном случае следует создать и обслуживать соответствующий массив *mbuf*, который будет индексироваться при освобождении дескрипторов.
Обычно пакеты поступают от стека в виде нескольких буферов. Для TCP-пакетов их количество равно 3, где первый из которых содержит заголовки, второй содержит остатки предыдущего mbuf, а третий содержит начало следующего mbuf. Плохо фрагментированные пакеты могут потребовать копирования в новый непрерывный буфер в зависимости от возможностей оборудования и степени фрагментации буфера. Очевидно, что это негативно влияет на производительность и следует всячески избегать таких ситуаций.
**Периодические таймеры**
Сетевым драйверам часто требуются периодические таймеры для выполнения вспомогательных функций. Например, для обслуживания канала и освобождения TX-дескрипторов. При этом, они не должны создавать собственный поток или асинхронные таймеры средствами ОС. Корректный способ установки периодического таймера в callout-функции *ifp->if_init()* следующий:
```
callout_msec( &dev->mii_callout, 2 * 1000, dev_monitor, dev );
```
Это приведет к вызову функцию *dev_monitor()* потоком сетевого стека по истечении двух секунд. Если требуется периодический таймер, при завершении *dev_monitor()* должна перезапустить таймер. Таким образом, данный таймер не является периодическим. Вероятно, потребуется добавить переменную *run_timer* и очистить ее при остановке таймера, а также вызвать *callout_stop()* и вызывать *callout_msec()* только в конце функции *dev_monitor()*, если эта переменная не установлена. Это позволит исключить состояния гонки, когда *dev_monitor()* не завершилась, когда другой поток выполняет *callout_stop()*, а затем по завершении *dev_monitor()* снова вызывается функция callout_msec(), перезапуская таймер, который предполагается остановить.
Создавать таймеры следует только один раз вызовом *callout_init()*:
```
callout_init( &dev->mii_callout );
```
*callout_msec()* может быть вызван несколько раз. Этот вызов запускает остановленный таймер или сбрасывает работающий. Вызов *callout_stop()* для остановленного таймера не имеет негативных последствий, но вызов *callout_init()* более одного раза приводит к неустранимой ошибке. *callout_init()* обычно используется в callout-функции *ifp->if_attach()*, которая вызывается единожды для каждого устройства. *callout_msec()* используется в *ifp->if_init()*, а также в самом callback-вызове. Поскольку он сбрасывает работающий таймер и запускает остановленный, дальнейшая блокировка не требуется. callout обычно останавливается вызовом *callout_stop()* из функции *ifp->if_stop()*.
Если TX-код вызывается для освобождения дескрипторов, следует заблокировать мьютекс передачи с помощью макроса *NW_SIGLOCK()*. Это позволит избежать повреждения данных и регистров.
**Состояние линка**
Пользователи должны быть уведомлены об изменениях состояния линка. Это осуществляется следующим образом:
```
if_link_state_change( ifp, LINK_STATE_UP );
if_link_state_change( ifp, LINK_STATE_DOWN );
```
**Контроль и управление**
Управление драйвером осуществляется с помощью callout-функции *ifp->if_ioctl()*, соответствующей *sam_ioctl()*.
Данная функция может быть пустой или довольно сложной, в зависимости от перечня поддерживаемых функций. Для совместимости с утилитой *nicinfo* (например, для успешного выполнения *nicinfo sam0*) следует добавить поддержку команд `SIOCGDRVCOM` `DRVCOM_CONFIG`/`DRVCOM_STATS`. Для поддержки аппаратного рассчета контрольных сумм следует поддержать команду `SIOCSIFCAP`.
Для отображения скорость канала (режима работы линка) и дуплекс с помощью:
```
ifconfig -v
```
следует поддержать команды `SIOCGIFMEDIA` и `SIOCSIFMEDIA`. Они также могут использоваться для установки данных параметров. Драйверы, поддерживающие настройку параметров среды передачи, имеют в своем составе файл с именем *bsd_media.c*. Для вывода перечня поддерживаемых режимов используется вызов:
```
ifconfig -m
```
Кроме того, *ioctl()* позволяет многоадресную передачу. В *sam.c* имеется пример работы с такими адресами (см. использование макросов *ETHER_FIRST_MULTI()* и *ETHER_NEXT_MULTI()*).
**Завершение драйвера**
Возможны следующие сценарии завершения драйвера:
- **Команда *ifconfig sam0 down*, приводящая к вызову callout-функции *sam_stop()***. Сценарий должен остановить все операции приема и передачи, а также очистить все используемые буферы (чтобы данные в них не могли появиться при следующем включении интерфейса). Обратите внимание, что кроме буферов и Tx/Rx передач, остальные структуры драйвера и аппаратные ресурсы освобождать не следует. Следующее обращение к драйверу, скорее всего, будет вызвано командой *ifconfig up*, что приведет к исполнению *sam_init()* и повторной инициализации оборудования.
- **Команда *ifconfig sam0 destroy*, приводящая к вызову callout-функции *sam_detach()***. Сценарий должен сбросить оборудование и освободить все ресурсы. Ожидается, что драйвер в скором времени будет выгружен из сетевого стека, но сам стек продолжит работать. Типичный тест корректности обработки данного сценария включает: в цикле осуществлять монтирование драйвера, вызов *ifconfig* с указанием адреса, выполнить несколько операций передачи трафика и выполнение *ifconfig ... destroy*. Корректно функционирующий драйвер не должен приводить к падению стека, утечкам памяти и невозможности передачи трафика.
- **Завершение сетевого стека или аварийное завершение с вызовом callout-функции *sam_shutdown()***. Сценарий должен сбросить оборудование, чтобы остановить любые операции (включая DMA). При этом следует избегать любого освобождения драйверных ресурсов, поскольку это может привести к рекурсивному вызову callout-а (маскируя первоначальную причину в дампе аварийного завершения процесса).
Функция *sam_detach()* для драйвера определяется уже известным образом:
```
CFATTACH_DECL( sam, sizeof( struct sam_dev ), NULL, sam_attach, sam_detach, NULL );
```
А вот аварийный callout-вызов *sam_shutdown()* определяется чуть сложнее:
```
sam->sc_sdhook = shutdownhook_establish( sam_shutdown, sam );
```
Важно не забыть установить этот хук в callout-функции *sam_attach()*, а также удалить его в *sam_detach()* с помощью:
```
shutdownhook_disestablish( sam->sc_sdhook );
```
**Краткосрочные ожидания в драйвере**
При обслуживании оборудования драйверу может регулярно требоваться выполнить непольшое ожидание. Как было сказано ранее, в сетевом драйвере все функции вызываются из специализированных потоков сетевого стека и они не подлежат управлению со стороны драйвера. При обслуживании драйвером нескольких интерфейсов это может стать причиной проблем, когда ожидание на одном интерфейсе влияет на потоки данных на другом.
Сетевой стек использует асинхронные псевдо-потоки, чтобы избежать излишних блокировок. Единственный сценарий, в котором задержка невозможна — это периодические таймеры (рассмотрены ранее). Единственным способом задержки в этих условиях является установка нового таймера. При запуске сетевого стека этот механизм еще не запущен и допустимо использовать стандартный механизм задержки.
Пример задержки на 0.5 секунд:
```
if ( !ISSTART && ISSTACK )
{
/*
* Called from an io-pkt thread and not at startup so can't use normal delay,
* work out what type of delay to use.
*/
if ( curproc == stk_ctl.proc0 )
{
/*
* Called from a callout, can only do another callout. If ltsleep is tried
* it returns success without actually sleeping.
*/
callout_msec( &dev->delay_callout, 500, next_part, dev );
return;
}
/* Normal io-pkt thread case. Use ltsleep to avoid blocking other interfaces */
timo = hz / 2;
ltsleep( &wait, 0, "delay", timo, NULL );
} else {
/*
* Either io-pkt is starting up or called from a different
* thread so will not block other interfaces. Just use delay.
*/
delay(500);
}
```
**Многопоточность**
Драйвер не должен создавать собственные потоки и должен работать под правлением потоков сетевого стека. Однако, бывают ситуации, когда драйверу действительно требуется отдельный поток (например, для обслуживания взаимодействия по USB или SDIO). Создавать стандартные потоки с помощью pthread_create() не рекомендуется, поскольку они не будут связаны с обработкой `mbufs`. В случае **острой необходимости** потоки обработки `mbuf` должны быть созданы с помощью *nw_pthread_create()* и ни в коем случае через *pthread_create()*:
```
nw_pthread_create( &tid, NULL, thread_fn, dev, 0, thread_init_fn, dev );
```
При этом, потоковая функция должна установить отдельное имя потоку, чтобы отличить его от стандартных потоков стека. Кроме того, следует также установить callout-обработчик *wtp->quiesce_callout()*. Допустимо выполнение и других инициализаций:
```
static int thread_init_fn( void *arg )
{
struct nw_work_thread *wtp;
dev_handle_t *dev = (dev_handle_t *)arg;
pthread_setname_np( 0, "My driver thread" );
wtp = WTP;
wtp->quiesce_callout = thread_quiesce;
wtp->quiesce_arg = dev;
return (EOK);
}
```
Имеет смысл назначать имя потока исходя из принадлежности конкретному драйверу и контуру обработки. Например:
```
# pidin -p io-pkt-v4 thread
pid name thread name STATE Blocked
4100 sbin/io-pkt-v4 io-pkt main SIGWAITINFO
4100 sbin/io-pkt-v4 io-pkt#0x00 RECEIVE 1
4100 sbin/io-pkt-v4 asixx Rx RECEIVE 22
```
В примере есть следующие потоки:
- **io-pkt main**. Используется для обработчика сигналов и обработки блокирующих запросов.
- **io-pkt#0x00**. Поток для выполнения основной работы сетевого стека. Другие нумерованные потоки создаются стеком для работы на отдельных процессорных ядрах и при дополнительных вызовах *interrupt_entry_init()*.
- **asixx Rx**. Драйверный поток, ассоциированный с *devnp-asixx.so* и его Rx-контуром. Его задачей является обработка специальных пакетов с малой задержкой, когда стек занят обслуживанием других запросов. Отсутствие у потока имени приведет к тому, что он автоматически получит нумерование и не будет отличим от потоков второго типа.
Сценарии вызова callout-обработчика *wtp->quiesce_callout()*:
- Стеку необходимо изменить некоторые структуры (например, при других вызовах *nw_pthread_create()*), что требует, чтобы все остальные потоки были заблокированы, пока обновление не завершится.
- При терминировании потоков, например, во время завершения сетевого стека.
Параметр *die* используется для определения одного из этих сценариев. Обратите внимание, что сама callout-функция вызывается из потока стека и должна уведомить драйверный поток о вызове *quiesce_block()* через глобальные переменные или сообщение-импульс. Синтетический пример, использующий глобальные переменные:
```
static int quiescing = 0;
static int quiesce_die = 0;
static void thread_quiesce( void *arg, int die )
{
dev_handle_t *dev = (dev_handle_t *)arg;
quiescing = 1;
quiesce_die = die;
}
static void *thread_fn( void *arg )
{
while ( 1 )
{
if ( quiescing )
{
if ( quiesce_die )
{
/* Thread will terminate on calling quiesce_block(), clean up here if required. */
}
quiesce_block( quiesce_die );
quiescing = 0;
}
/* Do normal thread work */
}
}
```
Если вызывается detach-функция драйвера, функция *quiesce_all()* вызывается стеком. Это может вызвать проблемы в других драйверах, если detach-функция выполняется длительное времени (например, много вызовов *nic_delay()*). В этом случае драйвер должен самостоятельно приостанавливаться (вызывать quiesce-функции), чтобы свести к минимуму влияние, которое он может оказать на другие сетевые драйверы. Если драйвер будет это выполнять, необходимо установить соответствующий флаг в attach-функции:
```
sam->dev.dv_flags |= DVF_QUIESCESELF;
```
Затем он может вызывать функции приостановки в detach-функции:
```
/* self quiesce */
quiesce_all();
ether_ifdetach( ifp );
if_detach( ifp );
unquiesce_all();
```
**Функция отсоединения (detach-функции)**
Одной из обязанностей detach-функции драйвера является определение необходимости отмонтирования драйвера. Она вызывается для каждого устройства, обслуживаемого драйвером. Таким образом, отмонтирование должно происходить при обработке последнего из устройств. Драйвер сам определяет способ отслеживания доступных устройств.
Если драйвер определяет, что отмонтирование преждевременно, следует выполнить следующее:
```
sam->dev.dv_dll_hdl = NULL;
```
В этой callout-функции часто необходимо использовать *nic_delay()* или другой вызов, который может передать контекст стека другому потоку. Контекст стека не должен быть передан после того, как драйвер внутренне пометил устройство как удаленное (уменьшен счетчик наличия устройств или устройство удалено из списка устройств). Если контекст стека передается detach-функции другого устройства, драйвер может быть выгружен, пока первое устройство еще завершает отсоединение. Это приведет к сбою. Аналогичная проблема может возникнуть, если attach-функция драйвера передаст контекст стека перед маркировкой устройства в качестве доступного.

3
hardware/Makefile Normal file
View File

@ -0,0 +1,3 @@
LIST=HARDWARE
EARLY_DIRS=devn
include recurse.mk

2
hardware/devnp/Makefile Normal file
View File

@ -0,0 +1,2 @@
LIST=DEVNP
include recurse.mk

View File

@ -0,0 +1,2 @@
LIST=CPU
include recurse.mk

View File

@ -0,0 +1,2 @@
LIST=VARIANT
include recurse.mk

View File

@ -0,0 +1 @@
include ../../common.mk

View File

@ -0,0 +1 @@
include ../../common.mk

View File

@ -0,0 +1,245 @@
/*
* (c) 2016-2017, SWD Embedded Systems Limited, http://www.kpda.ru
*/
/*****************************************************************************
* *
* Driver for the Intel 8255x 10/100 Mbps Ethernet Controller Family *
* Network controller bsd media routines *
* *
*****************************************************************************/
#include "e100.h"
//
// this is a callback, made by the bsd media code. We passed
// a pointer to this function during the ifmedia_init() call
// in bsd_mii_initmedia()
//
void bsd_mii_mediastatus(struct ifnet *ifp, struct ifmediareq *ifmr)
{
e100_dev_t *e100 = ifp->if_softc;
e100->bsd_mii.mii_media_active = IFM_ETHER;
e100->bsd_mii.mii_media_status = IFM_AVALID;
if (e100->force_advertise != -1) { // link is forced
if (e100->cfg.flags & NIC_FLAG_LINK_DOWN) {
e100->bsd_mii.mii_media_active |= IFM_NONE;
e100->bsd_mii.mii_media_status = 0;
} else { // link is up
e100->bsd_mii.mii_media_status |= IFM_ACTIVE;
switch(e100->cfg.media_rate) {
case 0:
e100->bsd_mii.mii_media_active |= IFM_NONE;
break;
case 1000*10:
e100->bsd_mii.mii_media_active |= IFM_10_T;
break;
case 1000*100:
e100->bsd_mii.mii_media_active |= IFM_100_TX;
break;
#if 0
case 1000*1000:
e100->bsd_mii.mii_media_active |= IFM_1000_T;
break;
#endif
default: // this shouldnt really happen, but ...
e100->bsd_mii.mii_media_active |= IFM_NONE;
break;
}
if (e100->cfg.duplex) {
e100->bsd_mii.mii_media_active |= IFM_FDX;
}
}
} else if (!(e100->cfg.flags & NIC_FLAG_LINK_DOWN)) { // link is auto-detect and up
e100->bsd_mii.mii_media_status |= IFM_ACTIVE;
switch(e100->cfg.media_rate) {
case 1000*10:
e100->bsd_mii.mii_media_active |= IFM_10_T;
break;
case 1000*100:
e100->bsd_mii.mii_media_active |= IFM_100_TX;
break;
#if 0
case 1000*1000:
e100->bsd_mii.mii_media_active |= IFM_1000_T;
break;
#endif
default: // this shouldnt really happen, but ...
e100->bsd_mii.mii_media_active |= IFM_NONE;
break;
}
if (e100->cfg.duplex) {
e100->bsd_mii.mii_media_active |= IFM_FDX;
}
// could move this to mii.c so there was no lag
ifmedia_set(&e100->bsd_mii.mii_media, IFM_ETHER|IFM_AUTO);
} else { // link is auto-detect and down
e100->bsd_mii.mii_media_active |= IFM_NONE;
e100->bsd_mii.mii_media_status = 0;
// could move this to mii.c so there was no lag
ifmedia_set(&e100->bsd_mii.mii_media, IFM_ETHER|IFM_NONE);
}
// stuff parameter values with hoked-up bsd values
ifmr->ifm_status = e100->bsd_mii.mii_media_status;
ifmr->ifm_active = e100->bsd_mii.mii_media_active;
}
//
// this is a callback, made by the bsd media code. We passed
// a pointer to this function during the ifmedia_init() call
// in bsd_mii_initmedia(). This function is called when
// someone makes an ioctl into us, we call into the generic
// ifmedia source, and it make this callback to actually
// force the speed and duplex, just as if the user had
// set the cmd line options
//
int bsd_mii_mediachange(struct ifnet *ifp)
{
e100_dev_t *e100 = ifp->if_softc;
int old_media_rate = e100->cfg.media_rate;
int old_duplex = e100->cfg.duplex;
struct ifmedia *ifm = &e100->bsd_mii.mii_media;
int user_duplex = ifm->ifm_media & IFM_FDX ? 1 : 0;
int user_media = ifm->ifm_media & IFM_TMASK;
if (!(ifp->if_flags & IFF_UP)) {
if (e100->cfg.verbose)
slogf(_SLOGC_NETWORK, _SLOG_WARNING, "%s(): isn't up, ioctl ignored", __devname__);
return 0;
}
if (!(ifm->ifm_media & IFM_ETHER)) {
if (e100->cfg.verbose)
slogf(_SLOGC_NETWORK, _SLOG_WARNING, "%s(): interface - bad media: 0x%X",
__devname__, ifm->ifm_media);
return 0; // should never happen
}
switch (user_media) {
case IFM_AUTO: // auto-select media
e100->force_advertise = -1;
e100->cfg.media_rate = -1;
e100->cfg.duplex = -1;
ifmedia_set(&e100->bsd_mii.mii_media, IFM_ETHER|IFM_AUTO);
break;
case IFM_NONE: // disable media
//
// forcing the link with a speed of zero means to disable the link
//
e100->force_advertise = 0;
e100->cfg.media_rate = 0;
e100->cfg.duplex = 0;
ifmedia_set(&e100->bsd_mii.mii_media, IFM_ETHER|IFM_NONE);
break;
case IFM_10_T: // force 10baseT
e100->force_advertise = user_duplex ? MDI_10bTFD : MDI_10bT;
e100->cfg.media_rate = 10 * 1000;
e100->cfg.duplex = user_duplex;
ifmedia_set(&e100->bsd_mii.mii_media,
user_duplex ? IFM_ETHER|IFM_10_T|IFM_FDX : IFM_ETHER|IFM_10_T);
break;
case IFM_100_TX: // force 100baseTX
e100->force_advertise = user_duplex ? MDI_100bTFD : MDI_100bT;
e100->cfg.media_rate = 100 * 1000;
e100->cfg.duplex = user_duplex;
ifmedia_set(&e100->bsd_mii.mii_media,
user_duplex ? IFM_ETHER|IFM_100_TX|IFM_FDX : IFM_ETHER|IFM_100_TX);
break;
#if 0
case IFM_1000_T: // force 1000baseT
//
// N.B. I have not had good luck, trying to get gige to work half
// duplex. Even with different gige switches, I can only force full duplex
//
e100->force_advertise = user_duplex ? MDI_1000bTFD : MDI_1000bT;
e100->cfg.media_rate = 1000 * 1000;
e100->cfg.duplex = user_duplex;
ifmedia_set(&e100->bsd_mii.mii_media,
user_duplex ? IFM_ETHER|IFM_1000_T|IFM_FDX : IFM_ETHER|IFM_1000_T);
break;
#endif
default: // should never happen
if (e100->cfg.verbose)
slogf(_SLOGC_NETWORK, _SLOG_WARNING, "%s(): - unknown media: 0x%X", __devname__, user_media);
return 0;
break;
}
// does the user want something different than it already is?
if ((e100->cfg.media_rate != old_media_rate) ||
(e100->cfg.duplex != old_duplex) ||
(e100->cfg.flags & NIC_FLAG_LINK_DOWN) ) {
// re-initialize hardware with new parameters
ifp->if_init(ifp);
}
return 0;
}
void bsd_mii_initmedia(e100_dev_t *e100)
{
e100->bsd_mii.mii_ifp = &e100->ecom.ec_if;
ifmedia_init(&e100->bsd_mii.mii_media, IFM_IMASK, bsd_mii_mediachange,
bsd_mii_mediastatus);
// we do NOT call mii_attach() - we do our own link management
//
// must create these entries to make ifconfig media work
// see net/if_media.h for defines
//
// ifconfig fxp0 media none (x22)
ifmedia_add(&e100->bsd_mii.mii_media, IFM_ETHER|IFM_NONE, 0, NULL);
// ifconfig fxp0 media auto (x20)
ifmedia_add(&e100->bsd_mii.mii_media, IFM_ETHER|IFM_AUTO, 0, NULL);
// ifconfig fxp0 media 10baseT (x23 - half duplex)
ifmedia_add(&e100->bsd_mii.mii_media, IFM_ETHER|IFM_10_T, 0, NULL);
// ifconfig fxp0 media 10baseT-FDX (x100023)
ifmedia_add(&e100->bsd_mii.mii_media, IFM_ETHER|IFM_10_T|IFM_FDX, 0, NULL);
// ifconfig fxp0 media 100baseTX (x26 - half duplex)
ifmedia_add(&e100->bsd_mii.mii_media, IFM_ETHER|IFM_100_TX, 0, NULL);
// ifconfig fxp0 media 100baseTX-FDX (x100026 - full duplex)
ifmedia_add(&e100->bsd_mii.mii_media, IFM_ETHER|IFM_100_TX|IFM_FDX, 0, NULL);
#if 0
// ifconfig fxp0 media 1000baseT (x30 - half duplex)
ifmedia_add(&e100->bsd_mii.mii_media, IFM_ETHER|IFM_1000_T, 0, NULL);
// ifconfig fxp0 media 1000baseT mediaopt fdx (x100030 - full duplex)
ifmedia_add(&e100->bsd_mii.mii_media, IFM_ETHER|IFM_1000_T|IFM_FDX, 0, NULL);
#endif
// add more entries to support flow control via ifconfig media
// link is initially down
ifmedia_set(&e100->bsd_mii.mii_media, IFM_ETHER|IFM_NONE);
}

View File

@ -0,0 +1,39 @@
#
# (c) 2017-2019, SWD Embedded Systems Limited, http://www.kpda.ru
#
ifndef QCONFIG
QCONFIG=qconfig.mk
endif
include $(QCONFIG)
ISKERNEL := 1
include ../../../../../netdrivers.mk
LIBS = drvrS cacheS
LIBS += $(foreach libpath,$(LIBVPATH), $(if $(wildcard $(libpath)/libnetdrvrS.a), netdrvrS) )
#CCFLAGS + -Wfatal-errors
CCFLAGS += -Werror
# CCFLAGS += -DE100_DEBUG_LEVEL=1
CCFLAGS += -DRFD_ALIGNMENT_FUDGE=4
FW_BLOB_SRCS = fw_blob.s
FW_BLOB_OBJS = fw_blob.o
EXTRA_OBJS += $(FW_BLOB_OBJS)
EXTRA_CLEAN += $(FW_BLOB_SRCS) firmwares.h
NAME = devnp-$(PROJECT)
USEFILE=$(PROJECT_ROOT)/$(NAME).use
define PINFO
PINFO DESCRIPTION=Driver for the Intel 8255x 10/100 Mbps Ethernet Controller Family
endef
include $(MKFILES_ROOT)/qtargets.mk
$(PROJECT_ROOT)/e100_fw.c: $(FW_BLOB_SRCS)
$(FW_BLOB_SRCS): $(PROJECT_ROOT)/firmware/mkblob.sh
$(PROJECT_ROOT)/firmware/mkblob.sh $@ firmwares.h $(wildcard $(PROJECT_ROOT)/firmware/*.bin)

View File

@ -0,0 +1,79 @@
/*
* (c) 2016-2017, SWD Embedded Systems Limited, http://www.kpda.ru
*/
/*****************************************************************************
* *
* Driver for the Intel 8255x 10/100 Mbps Ethernet Controller Family *
* Network controller DEVCTL routines *
* *
*****************************************************************************/
#include "e100.h"
int e100_ioctl(struct ifnet * ifp, unsigned long cmd, caddr_t data)
{
int error = 0;
e100_dev_t *e100 = ifp->if_softc;
struct drvcom_config *dcfgp;
struct drvcom_stats *dstp;
struct ifdrv_com *ifdc;
switch (cmd) {
case SIOCGDRVCOM:
ifdc = (struct ifdrv_com *)data;
switch (ifdc->ifdc_cmd) {
case DRVCOM_CONFIG:
dcfgp = (struct drvcom_config *)ifdc;
if (ifdc->ifdc_len != sizeof(nic_config_t)) {
error = EINVAL;
break;
}
memcpy(&dcfgp->dcom_config, &e100->cfg, sizeof(e100->cfg));
break;
case DRVCOM_STATS:
dstp = (struct drvcom_stats *)ifdc;
if (ifdc->ifdc_len != sizeof(nic_stats_t)) {
error = EINVAL;
break;
}
memcpy(&dstp->dcom_stats, &e100->stats, sizeof(e100->stats));
if (e100->cfg.verbose > 1) {
e100_hw_dump_registers(e100, 0);
}
break;
default:
error = ENOTTY;
}
break;
case SIOCSIFMEDIA:
case SIOCGIFMEDIA: {
struct ifreq *ifr = (struct ifreq *)data;
error = ifmedia_ioctl(ifp, ifr, &e100->bsd_mii.mii_media, cmd);
break;
}
default:
error = ether_ioctl(ifp, cmd, data);
if (error == ENETRESET) {
/*
* Multicast list has changed; set the
* hardware filter accordingly.
*/
if (ifp->if_init != NULL)
ifp->if_init(ifp);
error = 0;
}
break;
}
return error;
}

View File

@ -0,0 +1,32 @@
%C Driver for the Intel 8255x 10/100 Mbps Ethernet Controller Family
Syntax:
io-pkt-v4 -d e100 [option[,option ...]] ... &
Options (to override autodetected defaults):
duplex=0|1 Half (0) or full (1) duplex mode. Default autodetect.
did=0xXXXX PCI device ID.
irq=num IRQ of the interface. Default autodetect.
kermask=0|1 (1) Use kernel interrupt masking methodology
(0) manually mask the nic in the interrupt handler
mac=XXXXXXXXXXXX Interface address of the controller. Default eeprom.
use_io Use IO mapped registers (default: memory mapped)
pci=0xXXXX PCI index of the controller.
phy=num Address of connected PHY device. Default autodetect.
receive=X Set the number of rx descriptors
(min 16, default 256, max 2048).
speed=10|100 Media data rate in Megabits/Second. Default autodetect.
transmit=X Set the number of tx descriptors
(min 64, default 1024, max 2048).
verbose=N Set verbosity level. (default 0)
vid=0xXXXX PCI vendor ID. Default 0x8086.
typed_mem=tmem Use typed memory area
Note: if the duplex is specified via the "duplex" option, you should
also specify the speed using the "speed" option.
Examples:
# Start v4 TCP/IP io-pkt using the e100 driver:
io-pkt-v4 -d e100
ifconfig fxp0 10.184

695
hardware/devnp/e100/e100.h Normal file
View File

@ -0,0 +1,695 @@
/*
* (c) 2017-2019, SWD Embedded Systems Limited, http://www.kpda.ru
*/
/*****************************************************************************
* *
* Driver for the Intel 8255x 10/100 Mbps Ethernet Controller Family *
* Network controller main header *
* *
*****************************************************************************/
#ifndef _E100_H
#define _E100_H
#include <io-pkt/iopkt_driver.h>
#include <stdio.h>
#include <errno.h>
#include <atomic.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/siginfo.h>
#include <sys/syspage.h>
#include <sys/neutrino.h>
#include <sys/mbuf.h>
#include <sys/syslog.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <net/if_ether.h>
#include <net/if_media.h>
#include <sys/io-pkt.h>
#include <sys/cache.h>
#include <sys/callout.h>
#include <hw/inout.h>
#if _KPDA_VERSION > 20180500
#include <netdrvr/mdi.h>
#include <netdrvr/eth.h>
#include <netdrvr/nicsupport.h>
#else
#include <drvr/mdi.h>
#include <drvr/eth.h>
#include <drvr/nicsupport.h>
#endif
#include <hw/nicinfo.h>
#include <hw/pci.h>
#include <nw_pci.h>
#include <hw/pci_devices.h>
#include <sys/device.h>
#include <quiesce.h>
#include <siglock.h>
#include <dev/mii/miivar.h>
#include <sys/slog.h>
#include <sys/slogcodes.h>
#include "bpfilter.h"
#if NBPFILTER > 0
#include <net/bpf.h>
#include <net/bpfdesc.h>
#endif
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <netinet/tcp.h>
#include <net/if_vlanvar.h>
#include <net/ifdrvcom.h>
#include <sys/sockio.h>
#include <sys/malloc.h>
#include <device_qnx.h>
#if E100_DEBUG_LEVEL > 0
# define E100_DEBUG(x) if ( e100->cfg.verbose >= E100_DEBUG_LEVEL ) { x; }
# if E100_DEBUG_LEVEL > 1
# define E100_DEBUG2(x) x
# else
# define E100_DEBUG2(x)
# endif
# define RXID(__rx) (((void *)(__rx) - (void *)e100->rxs) / sizeof(struct rx))
#else
# define E100_DEBUG(x)
# define E100_DEBUG2(x)
#endif
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef u16 __le16;
typedef u32 __le32;
typedef u32 dma_addr_t;
#ifndef false
typedef int bool;
#define true (1==1)
#define false (1==0)
#endif
#define FULL 1
#define LITE 0
#define udelay(__usec) nanospin_ns((__usec)*1000);
#define msleep(__msec) delay((__msec))
#define msleep_interruptible msleep
#define cpu_to_le16(__val) ENDIAN_LE16((u16)(__val))
#define cpu_to_le32(__val) ENDIAN_LE32((u32)(__val))
#define le16_to_cpu cpu_to_le16
#define le32_to_cpu cpu_to_le32
#ifdef __X86__
#define iowrite8(__val, __port) ( \
!e100->use_io ? (*(volatile uint8_t *)(__port) = (__val)) : \
out8((uintptr_t)(__port), (__val)) \
)
#define iowrite16(__val, __port) ( \
!e100->use_io ? (*(volatile uint16_t *)(__port) = cpu_to_le16(__val)): \
out16((uintptr_t)(__port), cpu_to_le16(__val)) \
)
#define iowrite32(__val, __port) ( \
!e100->use_io ? (*(volatile uint32_t *)(__port) = cpu_to_le32(__val)): \
out32((uintptr_t)(__port), cpu_to_le32(__val)) \
)
#define ioread8(__port) ( \
!e100->use_io ? *(volatile uint8_t *)(__port) : \
in8((uintptr_t)(__port)) \
)
#define ioread16(__port) ( \
!e100->use_io ? le16_to_cpu(*(volatile uint16_t *)(__port)) : \
le16_to_cpu(in16((uintptr_t)(__port))) \
)
#define ioread32(__port) ( \
!e100->use_io ? le32_to_cpu(*(volatile uint32_t *)(__port)) : \
le32_to_cpu(in32((uintptr_t)(__port))) \
)
#else
#define iowrite8(__val, __port) (out8((uintptr_t)(__port), (__val)))
#define iowrite16(__val, __port) (out16((uintptr_t)(__port), cpu_to_le16(__val)))
#define iowrite32(__val, __port) (out32((uintptr_t)(__port), cpu_to_le32(__val)))
#define ioread8(__port) (in8((uintptr_t)(__port)))
#define ioread16(__port) (le16_to_cpu(in16((uintptr_t)(__port))))
#define ioread32(__port) (le32_to_cpu(in32((uintptr_t)(__port))))
#endif
#define dma_wmb() __cpu_membarrier()
#define dma_rmb() __cpu_membarrier()
#define FROM_POINTER_TO_INT(p, type) ( (type)(((uintptr_t)(p)) & ((1ULL << (sizeof(type) * 8)) - 1)) )
struct pci_device_id {
u32 vendor, device; /* Vendor and device ID or PCI_ANY_ID*/
u32 driver_data; /* Data private to the driver */
};
struct firmware_list {
uint32_t start;
// uint32_t end;
size_t size;
char *name;
};
struct firmware {
size_t size;
const u8 *data;
};
enum mac {
mac_82557_D100_A = 0,
mac_82557_D100_B = 1,
mac_82557_D100_C = 2,
mac_82558_D101_A4 = 4,
mac_82558_D101_B0 = 5,
mac_82559_D101M = 8,
mac_82559_D101S = 9,
mac_82550_D102 = 12,
mac_82550_D102_C = 13,
mac_82551_E = 14,
mac_82551_F = 15,
mac_82551_10 = 16,
mac_unknown = 0xFF,
};
enum phy {
phy_100a = 0x000003E0,
phy_100c = 0x035002A8,
phy_82555_tx = 0x015002A8,
phy_nsc_tx = 0x5C002000,
phy_82562_et = 0x033002A8,
phy_82562_em = 0x032002A8,
phy_82562_ek = 0x031002A8,
phy_82562_eh = 0x017002A8,
phy_82552_v = 0xd061004d,
phy_unknown = 0xFFFFFFFF,
};
/* CSR (Control/Status Registers) */
struct csr {
struct {
u8 status;
u8 stat_ack;
u8 cmd_lo;
u8 cmd_hi;
u32 gen_ptr;
} scb;
u32 port;
u16 flash_ctrl;
u8 eeprom_ctrl_lo;
u8 eeprom_ctrl_hi;
u32 mdi_ctrl;
u32 rx_dma_count;
};
enum scb_status {
rus_no_res = 0x08,
rus_ready = 0x10,
rus_mask = 0x3C,
};
enum ru_state {
RU_SUSPENDED = 0,
RU_RUNNING = 1,
RU_UNINITIALIZED = -1,
};
enum scb_stat_ack {
stat_ack_not_ours = 0x00,
stat_ack_sw_gen = 0x04,
stat_ack_mdi_done = 0x08,
stat_ack_rnr = 0x10,
stat_ack_cu_idle = 0x20,
stat_ack_frame_rx = 0x40,
stat_ack_cu_cmd_done = 0x80,
stat_ack_not_present = 0xFF,
stat_ack_rx = (stat_ack_sw_gen | stat_ack_rnr | stat_ack_frame_rx),
stat_ack_tx = (stat_ack_cu_idle | stat_ack_cu_cmd_done),
};
enum scb_cmd_hi {
irq_mask_none = 0x00,
irq_mask_all = 0x01,
irq_sw_gen = 0x02,
};
enum scb_cmd_lo {
cuc_nop = 0x00,
ruc_start = 0x01,
ruc_resume = 0x02,
ruc_abort = 0x04,
ruc_load_base = 0x06,
cuc_start = 0x10,
cuc_resume = 0x20,
cuc_dump_addr = 0x40,
cuc_dump_stats = 0x50,
cuc_load_base = 0x60,
cuc_dump_reset = 0x70,
};
enum cuc_dump {
cuc_dump_complete = 0x0000A005,
cuc_dump_reset_complete = 0x0000A007,
};
enum port {
software_reset = 0x0000,
selftest = 0x0001,
selective_reset = 0x0002,
};
enum eeprom_ctrl_lo {
eesk = 0x01,
eecs = 0x02,
eedi = 0x04,
eedo = 0x08,
};
enum mdi_ctrl {
mdi_write = 0x04000000,
mdi_read = 0x08000000,
mdi_ready = 0x10000000,
};
enum eeprom_op {
op_write = 0x05,
op_read = 0x06,
op_ewds = 0x10,
op_ewen = 0x13,
};
enum eeprom_offsets {
eeprom_cnfg_mdix = 0x03,
eeprom_map_cntr = 0x05,
eeprom_phy_iface = 0x06,
eeprom_id = 0x0A,
eeprom_config_asf = 0x0D,
eeprom_smbus_addr = 0x90,
};
enum eeprom_cnfg_mdix {
eeprom_mdix_enabled = 0x0080,
};
enum eeprom_phy_iface {
NoSuchPhy = 0,
I82553AB,
I82553C,
I82503,
DP83840,
S80C240,
S80C24,
II82555,
DP83840A = 10,
};
enum eeprom_id {
eeprom_id_wol = 0x0020,
};
enum eeprom_config_asf {
eeprom_asf = 0x8000,
eeprom_gcl = 0x4000,
};
enum cb_status {
cb_complete = 0x8000,
cb_ok = 0x2000,
};
/**
* cb_command - Command Block flags
* @cb_tx_nc: 0: controller does CRC (normal), 1: CRC from skb memory
*/
enum cb_command {
cb_nop = 0x0000,
cb_iaaddr = 0x0001,
cb_config = 0x0002,
cb_multi = 0x0003,
cb_tx = 0x0004,
cb_ucode = 0x0005,
cb_dump = 0x0006,
cb_tx_sf = 0x0008,
cb_tx_nc = 0x0010,
cb_cid = 0x1f00,
cb_i = 0x2000,
cb_s = 0x4000,
cb_el = 0x8000,
};
struct rfd {
__le16 status;
__le16 command;
__le32 link;
__le32 rbd;
__le16 actual_size;
__le16 size;
};
struct rx {
struct rx *next, *prev;
struct mbuf *m;
dma_addr_t dma_addr;
};
enum destroy_variants {
DESTROY_ALL = -1,
ATTACH_DONE,
PCI_ENABLE_DONE,
EVCNT_DONE,
CACHE_INIT_DONE,
MMAP_CSR_DONE,
PCI_MASTER_DONE,
MMAP_E100_DONE,
ALLOC_CBS_DONE,
ALLOC_RXS_DONE,
INTER_ENTRY_DONE,
IF_SETUP_DONE
};
#if defined(__BIGENDIAN__)
#define X(a,b) b,a
#else
#define X(a,b) a,b
#endif
struct config {
/*0*/ u8 X(byte_count:6, pad0:2);
/*1*/ u8 X(X(rx_fifo_limit:4, tx_fifo_limit:3), pad1:1);
/*2*/ u8 adaptive_ifs;
/*3*/ u8 X(X(X(X(mwi_enable:1, type_enable:1), read_align_enable:1),
term_write_cache_line:1), pad3:4);
/*4*/ u8 X(rx_dma_max_count:7, pad4:1);
/*5*/ u8 X(tx_dma_max_count:7, dma_max_count_enable:1);
/*6*/ u8 X(X(X(X(X(X(X(late_scb_update:1, direct_rx_dma:1),
tno_intr:1), cna_intr:1), standard_tcb:1), standard_stat_counter:1),
rx_save_overruns : 1), rx_save_bad_frames : 1);
/*7*/ u8 X(X(X(X(X(rx_discard_short_frames:1, tx_underrun_retry:2),
pad7:2), rx_extended_rfd:1), tx_two_frames_in_fifo:1),
tx_dynamic_tbd:1);
/*8*/ u8 X(X(mii_mode:1, pad8:6), csma_disabled:1);
/*9*/ u8 X(X(X(X(X(rx_tcpudp_checksum:1, pad9:3), vlan_arp_tco:1),
link_status_wake:1), arp_wake:1), mcmatch_wake:1);
/*10*/ u8 X(X(X(pad10:3, no_source_addr_insertion:1), preamble_length:2),
loopback:2);
/*11*/ u8 X(linear_priority:3, pad11:5);
/*12*/ u8 X(X(linear_priority_mode:1, pad12:3), ifs:4);
/*13*/ u8 ip_addr_lo;
/*14*/ u8 ip_addr_hi;
/*15*/ u8 X(X(X(X(X(X(X(promiscuous_mode:1, broadcast_disabled:1),
wait_after_win:1), pad15_1:1), ignore_ul_bit:1), crc_16_bit:1),
pad15_2:1), crs_or_cdt:1);
/*16*/ u8 fc_delay_lo;
/*17*/ u8 fc_delay_hi;
/*18*/ u8 X(X(X(X(X(rx_stripping:1, tx_padding:1), rx_crc_transfer:1),
rx_long_ok:1), fc_priority_threshold:3), pad18:1);
/*19*/ u8 X(X(X(X(X(X(X(addr_wake:1, magic_packet_disable:1),
fc_disable:1), fc_restop:1), fc_restart:1), fc_reject:1),
full_duplex_force:1), full_duplex_pin:1);
/*20*/ u8 X(X(X(pad20_1:5, fc_priority_location:1), multi_ia:1), pad20_2:1);
/*21*/ u8 X(X(pad21_1:3, multicast_all:1), pad21_2:4);
/*22*/ u8 X(X(rx_d102_mode:1, rx_vlan_drop:1), pad22:6);
u8 pad_d102[9];
};
#define E100_MAX_MULTICAST_ADDRS 64
struct multi {
__le16 count;
u8 addr[E100_MAX_MULTICAST_ADDRS * ETH_MAC_LEN + 2/*pad*/];
};
/* Important: keep total struct u32-aligned */
#define UCODE_SIZE 134
#define E100_TX_FRAGMENTS 8
// #define E100_TX_FRAGMENTS 1
struct cb {
__le16 status;
__le16 command;
__le32 link;
union {
u8 iaaddr[ETH_MAC_LEN];
__le32 ucode[UCODE_SIZE];
struct config config;
struct multi multi;
struct {
u32 tbd_array;
u16 tcb_byte_count;
u8 threshold;
u8 tbd_count;
struct {
__le32 buf_addr;
__le16 size;
u16 eol;
} tbd[E100_TX_FRAGMENTS];
} tcb;
__le32 dump_buffer_addr;
} u;
struct cb *next, *prev;
dma_addr_t dma_addr;
void *data;
};
enum loopback {
lb_none = 0, lb_mac = 1, lb_phy = 3,
};
struct stats {
__le32 tx_good_frames, tx_max_collisions, tx_late_collisions,
tx_underruns, tx_lost_crs, tx_deferred, tx_single_collisions,
tx_multiple_collisions, tx_total_collisions;
__le32 rx_good_frames, rx_crc_errors, rx_alignment_errors,
rx_resource_errors, rx_overrun_errors, rx_cdt_errors,
rx_short_frame_errors;
__le32 fc_xmt_pause, fc_rcv_pause, fc_rcv_unsupported;
__le16 xmt_tco_frames, rcv_tco_frames;
__le32 complete;
};
struct mem {
struct {
u32 signature;
u32 result;
} selftest;
struct stats stats;
u8 dump_buf[596];
};
struct param_range {
u32 min;
u32 max;
u32 count;
u32 reap;
};
struct params {
struct param_range rfds;
struct param_range cbs;
};
#define ____cacheline_aligned __attribute__((__aligned__(NET_CACHELINE_SIZE)))
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
typedef struct _nic_nto_ext {
struct ethercom ecom; /* common device */
struct callout hk_callout; /* house keeping */
struct callout mii_callout; /* link up/down */
nic_config_t cfg;
nic_stats_t stats;
struct _iopkt_self *iopkt;
struct cache_ctrl cachectl;
void *pci_dev_hdl;
struct pci_dev_info pci_info;
unsigned short pci_command;
unsigned short pci_status;
u8 stat_ack;
int prot_flags;
int map_flags;
int dying;
volatile unsigned stopping;
const struct sigevent * (*isrp)(void *, int); ____cacheline_aligned
u16 (*mdio_ctrl)(struct _nic_nto_ext *e100, u32 addr, u32 dir, u32 reg, u16 data);
void *sd_hook;
int kermask;
int use_io;
int eeprom_bad_csum_allow;
int force_rx_bug;
int extra_rnr;
#define E100_DEFAULT_HK_TIMEOUT 5
unsigned hk_timeout;
__le16 tx_command;
struct rx *rxs ____cacheline_aligned;
struct rx *rx_to_use;
struct rx *rx_to_clean;
struct rfd blank_rfd;
enum ru_state ru_running;
// intrspin_t cb_lock ____cacheline_aligned;
// intrspin_t cmd_lock;
struct csr *csr ____cacheline_aligned;
enum scb_cmd_lo cuc_cmd;
unsigned int cbs_avail;
struct cb *cbs;
struct cb *cb_to_use;
struct cb *cb_to_send;
struct cb *cb_to_clean;
enum {
ich = (1 << 0),
promiscuous = (1 << 1),
multicast_all = (1 << 2),
wol_magic = (1 << 3),
ich_10h_workaround = (1 << 4),
} flags ____cacheline_aligned;
enum mac mac;
enum phy phy;
struct params params;
int num_receive;
int num_transmit;
enum loopback loopback;
struct mem *mem;
dma_addr_t dma_addr;
dma_addr_t cbs_dma_addr;
volatile unsigned irq_sw_gen;
/* TX descriptor and buffer tracking */
struct evcnt ev_txdrop ____cacheline_aligned;
int start_running; // tx in progress
mdi_t *mdi;
int pkts_received; // optimization to not probe phy
int force_advertise;
int tx_reap;
int bmstr; /* Cpu to Pci physical memory translation */
/* RX descriptor and buffer tracking */
struct _iopkt_inter inter ____cacheline_aligned;
int iid ____cacheline_aligned;
struct mii_data bsd_mii; // for media devctls
int tmem_fd;
char* tmem; // typed memory area
u8 adaptive_ifs;
u8 tx_threshold;
u32 tx_frames;
u32 tx_collisions;
u32 tx_fc_pause;
u32 tx_tco_frames;
u32 rx_fc_pause;
u32 rx_fc_unsupported;
u32 rx_tco_frames;
u32 rx_over_length_errors;
u16 eeprom_wc;
__le16 eeprom[256];
struct firmware *fw;
} e100_dev_t;
struct e100_dev {
#define STR_HELPER(x) #x
#define STR(x) STR_HELPER(x)
#define __devname__ (e100 && e100->ecom.ec_if.if_xname[0] ? e100->ecom.ec_if.if_xname : "fxp*")
struct device sc_dev; /* common device */
e100_dev_t *sc_e100;
char filler[sizeof(e100_dev_t) + NET_CACHELINE_SIZE];
};
// main
int e100_init(struct ifnet *ifp);
void e100_stop(struct ifnet *ifp, int disable);
// mii
int e100_phy_supports_mii(e100_dev_t *e100);
int e100_findphy(e100_dev_t *e100);
int nic_force_advertise(e100_dev_t *e100);
u16 mdio_ctrl_hw(e100_dev_t *e100, u32 addr, u32 dir, u32 reg, u16 data);
void e100_mii_callout(void *arg);