Я не буду описывать здесь то, как следует писать системные драйверы (Исчерпывающую информацию по написанию таких драйверов читатель может найти в книге "Написание драйверов для MS-DOS" Р.Лея и "Уэйт Груп", Москва "Мир", 1995), тем более что существует достаточное их количество в депозитариях общего доступа (Например, анонимное FTP по адресам ftp.funet.fi, ftp.switch.ch или oak.oakland.edu, депозитарий SimTel ). Приведенное выше описание регистров интерфейса не является единственно возможным (см. также руководство по сетевому контроллеру 8390 и файл NE2.ASM из ссылки ftp.funet.fi. Структура драйверов варьируется для разных операционных систем. Для системных программистов полезно иметь возможность настраивать драйвер или непосредственно интерфейс на определенный режим, например, на прием всех пакетов, проходящих по кабельному сегменту. Последнее может представлять интерес в диагностических целях, так как вслед за пакетным драйвером загружается Etherdrv, Winsock или winpkt и т.д., блокирующие режим приема всех пакетов (mode=6). Ниже приведен пример описания основных параметров драйвера:
BLUEBOOK
equ
1
IEEE8023
equ
11
ADDR_LEN
equ
6
; размер Ethernet-адреса
MAX_M_CAST
equ
8
; максимальное число мультикаст-адресов.
Public
int_no,
io_addr
int_no
db
2,0,0,0
; должно иметь 4 байта для get_number.
io_addr
dw
0300h,0
; I/O адрес карты (переключатели)
public
driver_class
driver_type,
driver_name,
driver_function,
parameter_list
driver_class
db
BLUEBOOK, IEEE8023, 0
; из спецификации интерфейса
driver_type
dw
54
; из спецификации интерфейса
driver_name
db
'NE2000',0
; имя драйвера.
driver_function
db
2
parameter_list
label
byte
db
1
;
db
9
;
db
14
; длина списка параметров в байтах
db
ADDR_LEN
; длина адреса MAC-уровня в байтах
dw
GIANT
; MTU, включая MAC-заголовок
dw
MAX_M_CAST * ADDR_LEN
<
/p>
; размер буфера для мультикаст-адресов
dw
0
;(# принимаемых подряд пакетов с; размером MTU) - 1
dw
0
; (# посылаемых подряд пакетов) - 1
int_num
dw
0
; Номер прерывания
Работа с пакетным драйвером в MS-DOS
Существует множество пакетных драйверов. Можно обнаружить несколько модификаций для одного и того же типа интерфейса. Эти драйверы могут быть ориентированы на работу в разных программных средах (Novell, UNIX, MS-DOS и т.д.) и иметь разные возможности. Для MS-DOS сложился неофициальный стандарт, который позволяет использовать драйвер для самых разных приложений. Драйвер может использовать минимум возможностей интерфейса (базовый уровень), реализовать более широкий набор функций (мультикастинг, сбор статистики и т.д.) или поддерживать практически все, на что способен данный прибор. В последнем случае он занимает больше места в памяти. Описания операций с пакетными драйверами, приведенные ниже, выполнены в нотации ассемблера IBM/PC. При написании программы следует помнить, что порядок байтов в Ethernet противоположен тому, который используется в вашей IBM/PC.
Пакетные драйверы используют программные прерывания в интервале 0x60 - 0x80. Следует сразу заметить, что не все прерывания из этого списка свободны и при конфигурировании системы следует проявлять осмотрительность. Для того чтобы избежать конфликтов с другими внешними устройствами, предусматривается возможность реконфигурации прерываний. Предполагается, что программа обработки прерываний начинается с команды безусловной передачи управления (JMP), за которой следует текстовая строка "PKT DRVR". Именно эта строка служит указателем при поиске адреса пакетного прерывания. Практически все драйверы могут работать с различными протоколами (TCP/IP, OSI и др.). Решить задачу мультиплексирования на связном уровне помогает процедура access_type, которая обеспечивает доступ для пакетов определенного типа.
Все функции реализуются с помощью обращения к драйверу с набором определенных параметров.
При этом значение регистра AH определяет тип запроса. Каждому типу используемого сетевого протокола, с которым работает интерфейс, ставится в соответствие целочисленный указатель (handle), получаемый с помощью процедуры access_type. Выполнимость драйвером тех или иных операций может быть выяснена с помощью запроса driver_info.
При работе с драйвером следует проявлять осторожность и спасать нужные вам регистры. Следует также помнить, что порядок байтов в PC и в некоторых сетях, включая Ethernet, не совпадает. Описание основных запросов, посылаемых пакетному драйверу:
1. Получение информации о типе и функциональных возможностях драйвера
driver_info AH == 1,
AL == 255 (код запроса)
public
_driver_info
_driver_info
proc near
mov AX, 1FFH
; ah=1, al=255
call int_pkt
; обращение к драйверу
jnc lv
mov AX, seg _PARAM.ER_CODE
mov DS, AX
mov _PARAM.ER_CODE, 272
; Устанавливаем код "Нет инф. о драйвере"
lv:
ret
_driver_info
endp
int_pkt:
; Подпрограмма обращения к драйверу
push ds
push es
pushf
cli
call _param.Handler
; адрес _param.Handler должен быть определен раньше
pop es
pop ds
ret
Целочисленный указатель (handle) должен быть занесен в регистр BX (для старых драйверов). В случае ошибки устанавливается флаг carry, а код ошибки заносится в регистр DH. Сообщение BAD_HANDLE (неверный указатель) возможно только для старых драйверов. При благополучном исполнении флаг carry равен нулю, а в регистры будет занесены следующие параметры:
BX
версия;
CH
класс;
CL
номер;
DX
тип;
DS:SI
указывают на строку имени драйвера;
AL
функциональные возможности.
AL = 1
гарантируется выполнение базовых функций;
= 2
обеспечено выполнение базового и расширенного набора функций;
= 5
выполняется базовый и экстра-набор функций;
= 6
выполним полный набор функций;
= 255
драйвер не установлен.
<
/p>
Ниже приведен пример программы, реализующей некоторые из описанных запросов.
.MODEL
small
PUBLIC
_INFACE
VERSION
EQU
1
EXTRN
_PARAM:BYTE
EXTRN
_Q:BYTE
.DATA
INCLUDE
DEF.ASM
; Определения некоторых констант
P_LIST
STRUC
LINTN
DB
32 dup(0)
; Список активных номеров прерываний
HANDLES
DW
?
HANDLEP
DW
?
ER_CODE
DW
?
ERNUM
DW
?
; Код ошибки
HANDLER
DD
?
MODE
DW
?
; Текущий режим приема пакетов
MLIST
DB
0,0,0,0,0,0
; Список допустимых режимов; 1 => имеется
PKT_IN
DW
?,?
; Диагностический массив
pkt_out
DW
?,?
byte_in
DW
?,?
byt_out
DW
?,?
err_in
DW
?,?
err_out
DW
?,?
pk_drop
DW
?,?
L1
DW
0
; Версия драйвера
L2
DW
0
; класс/номер
L3
DW
0
; Тип
L4
DW
0
; Функция
_NAME
DB
0,0,0,0,0,0,0,0,0,0
; Имя интерфейса
ETHER_ADR
DB
ADDR_LEN dup(-1)
; Ethernet-адрес
S_ADR
DB
EADDR_LEN+5 dup(-1)
; Ethernet-адрес получателя
D_ADR
DB
EADDR_LEN+5 dup(-1)
; Ethernet-адрес отправителя
P_LIST
ENDS
QUEUE
STRUC
Leng
DW
15000,?
; Длина очереди
Tail
DW
?
; Смещение последнего элемента очереди
Head
DW
?
; Смещение первого элемента очереди
_end
DW
?
; Указатель на конец очереди
p_len
DW
?
; Длина пакета
P_start
DW
?
; Указатель на текущий пакет = Q_head - Q_begin +2
NEW
DB
0
; Флаг нового пакета
Line
DB
?
; Строка экрана
Npacks
DD
0
; Счетчик принятых пакетов
B
DW
?
; смещение Q_beg
Point
DW
380 dup(?)
Beg
DB
31000 dup(?)
; Пакетный буфер
QUEUE
ENDS
ether_bdcst
DB
EADDR_LEN dup(-1)
; Широковещательный адрес Ethernet, заполненный -1.
ether_addr
DB
EADDR_LEN dup(-1)
bogus_type
DB
0,0;
signature
DB
'PKT DRVR',0
; Сигнатура пакетного драйвера
signature_len
equ
$-signature
SAFE
DW
?
DFLAG
DB
0
.CODE
PUBLIC
_INFACE
_INFACE
PROC
NEAR
<
/p>
CLD
MOV DFLAG, 0
; Очистка флага драйвера
MOV _PARAM.ER_CODE, 0
; Очистка флага ошибки
PUSH BP
; Спасение регистров
MOV BP, SP
PUSH SI
PUSH DI
PUSH ES
PUSH DS
MOV CX, 32
MOV AL, 60H
; Установка начального номера прерывания
LEA SI, _PARAM.LINTN
; Формирование указателя на список номеров прерывания
CHECK:
PUSH AX
PUSH CX
PUSH SI
CALL CHK_INT
POP SI
POP CX
MOV byte ptr [SI], 0 ;
JNE NO_SIGNATURE
INC DFLAG
; Установка флага <Это драйвер>
MOV BYTE PTR [SI], 1
; Установка флага наличия
NO_SIGNATURE:
POP AX
INC AL
; Следующий номер прерывания
INC SI
; Актуализация указателя
LOOP CHECK
CMP DFLAG, 0
; Драйвер присутствует?
JNE HAVE_SIGNATURE
MOV _PARAM.ER_CODE, 271
; Установка флага <No signature>
JMP OKAY
INT_PKT:
PUSH ES
pushf
cli
call _PARAM.HANDLER
POP ES
RET
CHK_INT:
PUSH ES
; AL = номер прерывания
PUSH DI
MOV AH, 35H
; Получение вектора прерывания
INT 21H
; ES:BX=seg:offs драйвера
MOV _PARAM.HANDLER.OFFS,BX
; Записываем адрес драйвера
MOV _PARAM.HANDLER.SEGM, ES
LEA DI, 3[BX]
; Устанавливаем смещение сигнатуры драйвера
MOV SI, OFFSET SIGNATURE
; Проверка сигнатуры драйвера
MOV CX, SIGNATURE_LEN
; Присутствует ли здесь драйвер?
REPE CMPSB ; DS:[SI] - ES:[DI]
POP DI
POP ES
RET
HAVE_SIGNATURE:
MOV CX, 32
; Установка начального значения счетчика
LEA SI, _PARAM.LINTN
; Устанавливаем указатель списка
MOV AL, 60H
; Задаем начальный номер прерывания
CHOICE:
CMP BYTE PTR [SI], 0
JNE SETDRV
INC AL
LOOP CHOICE
<
/p>
SETDRV:
MOV AH, 35H
INT 21H
MOV _PARAM.HANDLER.OFFS,BX
; Определяем адрес драйвера
MOV _PARAM.HANDLER.SEGM, ES
PUSH DS
POP ES
MOV CX, EADDR_LEN
MOV SI, OFFSET ETHER_ADDR
MOV DI, OFFSET ETHER_BDCST
REPE CMPSB
JE GET_MODE
; Адрес не определен
MOV AH, 25
; Записываем ethernet-адрес
MOV DI, offset ETHER_ADDR
MOV CX, EADDR_LEN
call int_pkt
MOV _PARAM.ER_CODE, DX
; Устанавливаем код ошибки
JMP OKAY
GET_MODE:
MOV SAFE, DS
; Спасаем DS
PUSH DS
MOV AH, 2
; Открываем доступ пакетам
MOV AL, 1
; Класс интерфейса
MOV BX, -1
; Тип интерфейса
MOV DL, 0
; Номер интерфейса
MOV CX, 2
; Используем длину type = 2
MOV SI, OFFSET BOGUS_TYPE
PUSH CS
; ES:DI -> Receiver.
POP ES
MOV DI, OFFSET RECEIVER
call INT_PKT
JNC $_$
MOV _PARAM.ER_CODE, DX
; Устанавливаем код ошибки
$_$:
MOV _PARAM.HANDLES, AX
; Записываем указатель-Handle
MOV AH, 6
; Определяем ethernet-адрес интерфейса
PUSH DS
POP ES
MOV DI, offset _PARAM.ETHER_ADR
MOV CX, EADDR_LEN
MOV BX, _PARAM.HANDLES
call int_pkt
JNC NOBAD
MOV _PARAM.ER_CODE, 273
; Ошибка при определении Ethernet-адреса
POP DS
JMP OKAY
NOBAD:
MOV AX, 1FFH
; Запрашиваем информацию о драйвере
MOV BX, _PARAM.HANDLES
; Устанавливаем указатель
call INT_PKT
JNC N_BAD
MOV _PARAM.ER_CODE, 272
; Ошибка при получении информации о драйвере
POP DS
JMP OKAY
<
/p>
N_BAD:
PUSH DS
PUSH SS
&nsp;
POP DS
MOV ES, SAFE
MOV _PARAM.L1, BX
; Версия драйвера
MOV _PARAM.L2, CX
; номер/класс
MOV _PARAM.L3, DX
; Тип
MOV _PARAM.L4, AX
; Функциональность
LEA BX, _PARAM._NAME
POP DS
MOV CX, 8
ZFIND:
CMP byte ptr [SI], 0
MOV AL, byte ptr [SI]
MOV byte ptr ES:[BX], AL
JE ZERO_
INC SI
INC BX
LOOP ZFIND
ZERO_:
POP DS
MOV AH, 21
; Запрашиваем код режима приема пакетов
MOV BX, _PARAM.HANDLES
call INT_PKT
MOV _PARAM.MODE, AX
; Записываем код режима
.........................
OKAY:
POP DS
POP ES
POP DI
POP SI
MOV SP, BP
POP BP
RET
RECEIVER:
; Подпрограмма RECEIVER, вызываемая при получении пакета
Запрос access_type инициализирует доступ для пакетов определенного типа (type). Аргумент typelen – длина спецификации типа в байтах, для PC/TCP равна 5 (наименьшее значение - 2, для IP и ARP). Аргумент receiver является указателем на подпрограмму, которая вызывается при приеме пакета. Получая пакет, драйвер дважды обращается к этой программе. Первый раз (при AX==0) это делается с целью получения адреса буфера, куда должен быть положен пакет. Прикладная программа в этом случае должна выдать указатель буфера в регистры ES:DI. Если прикладной процесс не имеет свободного буфера,то возвращается значение 0:0. Пакет выбрасывается и повторное обращение к программе receiver отменяется.
Форма реализации запроса аналогична приведенному для driver_info:
Int
if_class; AL
; класс интерфейса
Int
if_type; BX
; тип интерфейса
Int
if_number; DL
; номер интерфейса
Char
far *type; DS:SI
Unsigned
typelen; CX
Int
(far *receiver); ES:DI
access:
mov ah, 2
mov al, ch
; установка класса; здесь предполагается, что содержимое регистров соответствует тому, что получено в результате обращения к driver_info
mov bx, dx
; устанавливаем параметр type
mov dl, cl
; устанавливаем параметр number, при одном интерфейсе number=0
xor cx, cx
; длина type равна нулю
push cs
; устанавливаем сегментный регистр receiver
pop es
mov di, offset RECEIVER
; вызов подпрограммы receiver
call int_pkt
; обращение к пакетному драйверу
В случае ошибки флаг carry=1, а в регистр DH заносится код ошибки. Возможные ошибки:
2
NO_CLASS не найдено интерфейса указанного класса;
3
NO_TYPE не найдено интерфейса указанного типа;
4
NO_NUMBER не найдено интерфейса с указанным номером;
5
BAD_TYPE специфицирован неправильный тип пакета;
9
NO_SPACE недостаточно места в памяти;
10
TYPE_INUSE было обращение к данному типу и он пока занят.
При успешном выполнении запроса флаг carry=0, а в регистр AX занесен указатель (handle).
Обращение к приемнику (receiver):
(*receiver)(handle, flag, len [, buffer])
int handle;
BX
; указатель
int flag;
AX
; флаг вызова(0/1)
unsigned len;
CX
; целое без знака - длина пакета
if AX == 1,
char far *buffer;
DS:SI
; адрес буфера
Если параметр typelen равен нулю, прикладной процесс готов получать все пакеты. Очень важно, чтобы при первом обращении к receiver (AX==0) CX (длина пакета) была указана правильно, что позволит выделить нужное место в памяти. CX должна включать в себя длину MAC-заголовка и размер самого сообщения без контрольной суммы (CRC). Повторный вызов (AX==1) программы receiver указывает на то,что пакет записан в буфер и прикладная программа может с ним работать.
Адрес буфера будет указан в регистрах DS:SI.
3. Завершение доступа пакетов данного типа release_type
int release_type(handle) AH == 3;
код запроса int handle;
BX ; указатель определяет тип пакетов
_release_type proc near
push bp
; спасение регистров
push ds
push es
mov ah, 3
; задаем код запроса
mov bx, _param.handle
; заносим указатель
pushf
cli
call _param.handler
; обращение к драйверу
mov _param.er_CODE, dx
; занесение кода ошибки
pop es
; восстановление регистров
pop ds
pop bp
ret
_release_type
endp
В случае ошибки флаг carry=1, а в регистр DH заносится код ошибки. Возможная ошибка: BAD_HANDLE (не верный указатель). При успешном выполнении запроса флаг carry=0. Эта операция прерывает доступ пакетов, соответствующих указателю, полученному с помощью запроса access_type. Старый указатель после выполнения этого запроса не действителен.
В случае ошибки флаг carry=1, а в регистр DH заносится код ошибки 12 CANT_SEND. send_packet отправляет пакет с числом байт, равным CX. Пакет должен в исходный момент лежать, начиная с адреса DS:SI. Прикладная программа должна сформировать все необходимые заголовки. Информация, нужная для осуществления демультиплексирования пакетов (MAC или LLC), также должна быть записана в пакет, так как при этом запросе не сообщается значение указателя (handle).
5. Завершение работы драйвера terminate(handle)
AH == 5 (код запроса)
int handle; BX (указатель)
В случае ошибки флаг carry=1, а в регистр DH заносится код ошибки. Возможные ошибки:
1 BAD_HANDLE;
7 CANT_TERMINATE.
Завершает работу драйвера, соответствующего указателю, который приведен в качестве параметра запроса.
Если возможно, драйвер будет выгружен и занимаемая им память освобождена.
В случае ошибки флаг carry=1, а в регистр DH заносится код ошибки. Возможные ошибки:
1 BAD_HANDLE;
9 NO_SPACE. При успешном выполнении запроса флаг carry=0, а в регистр CX занесена длина адреса.
Копирует текущее значение сетевого (физического) адреса интерфейса в буфер. Если получено сообщение NO_SPACE, это означает, что выделенного места (len=CX) для копирования адреса не хватило.
7. Возвращение интерфейса в исходное состояние reset_interface(handle)
AH == 7 (код запроса)
int handle; BX (указатель)
В случае ошибки флаг carry=1, а в регистр DH заносится код ошибки. Возможные ошибки:
1 BAD_HANDLE;
15 CANT_RESET.
Возвращает интерфейс в исходное состояние, прерывая все процессы. Местное значение физического сетевого адреса, если оно было изменено, восстанавливается из ROM, прием переключается в режим 3, а список мультикастинг-адресов обнуляется. При работе с несколькими указателями (handle) возможны серьезные неприятности, по этому выполнение запроса блокируется и присылается сообщение CANT_RESET.
8. Запрос установки режима приема пакетов set_rcv_mode(handle,mode)
AH == 20 (код запроса) int handle;
BX (входные параметры - указатель) int mode;
CX (код режима приема пакетов)
В случае ошибки флаг carry=1, а в регистр DH заносится код ошибки. Возможные ошибки:
1 BAD_HANDLE;
8 BAD_MODE.
Устанавливает режим приема пакетов. Режим 3 используется по умолчанию. Возможны (но не для всех интерфейсов) следующие режимы:
Режим
Значение
1
выключение приема пакетов;
2
прием пакетов, адресованных только данному интерфейсу;
3
режим 2 плюс бродкастинг-пакеты;
4
режим 3 плюс некоторые мультикастинг-пакеты;
5
режим 3 плюс все мультикастинг-пакеты;
6
все пакеты.
9. Считывание действующего режима приема пакетов get_rcv_mode(handle)
AH == 21 (код запроса)
int handle; BX (входной параметр - указатель)
В случае ошибки флаг carry=1, а в регистр DH заносится код ошибки 1 BAD_HANDLE. При успешном выполнении запроса флаг carry=0, а в регистр AX заносится код режима приема пакетов.
10. Занесение списка мультикастинг-адресов в интерфейс set_multicast_list(addrlst,len)
AH == 22 (код запроса)
char far *addrlst; ES:DI (адрес буфера, где лежат адреса)
int len; CX (длина списка адресов)
В случае ошибки флаг carry=1, а в регистр DH заносится код ошибки. Возможные ошибки:
6 NO_MULTICAST;
9 NO_SPACE;
14 BAD_ADDRESS.
Список адресов представляет собой счетную последовательность, начинающуюся с байта числа адресов в списке. На список адресов указывает комбинация регистров ES:DI. Сообщение NO_SPACE присылается, если указатель адреса отсутствует, или число адресов превосходит аппаратные возможности интерфейса. Прежде чем заносить список, полезно сначала ознакомиться с имеющимся уже списком, выполнив запрос get_multicast_list. При получении сообщения NO_SPACE рекомендуется попытаться установить режим приема 3 с помощью запроса set_rcv_mode.
11. Получение рабочего списка мультикастинг-адресов
get_multicast_list
AH == 23 (код запроса)
В случае ошибки флаг carry=1, а в регистр DH заносится код ошибки. Возможные ошибки:
6 NO_MULTICAST;
9 NO_SPACE.
При успешном выпонении запроса флаг carry=0, в регистр CX заносится длина списка адресов, а регистры ES:DI указывают на начало счетной оследовательности, где запрошенный список лежит. Прикладная программа не должна модифицировать этот список.
12. Получение статистических данных об ошибках и трафике через данный интерфейсget_statistics(handle)
AH == 24 (код запроса)
int handle; BX (указатель)
char far *statistics; DS:SI (адрес буфера, куда записываются статистические данные)
В случае ошибки флаг carry=1, а в регистр DH заносится код ошибки 1 BAD_HANDLE. При успешном выполнении запроса флаг carry=0, а в массиве, начиная с адреса DS:SI, лежит запрошенная информация.
struct statistics {
unsigned long packets_in;
( Число принятых пакетов для всех указателей)
unsigned long packets_out;
( Число посланных пакетов)
unsigned long bytes_in;
( Число принятых байтов, включая MAC заголовки)
unsigned long bytes_out;
( Число посланных байтов)
unsigned long errors_in;
( Полное число ошибок при приеме)
unsigned long errors_out;
( Число ошибок при посылке пакетов)
unsigned long packets_lost;
( Число потерянных пакетов из-за отсутствия свободного буфера или других ресурсов)
};
Статистические данные имеют вид целых 32-разрядных чисел в формате IBM/PC.
13. Смена физического адреса интерфейса
set_address(addr, len) AH == 25
char far *addr; ES:DI (адрес буфера, где лежит новое значение адреса)
int len; CX (длина адреса в байтах)
В случае ошибки флаг carry=1, а в регистр DH заносится код ошибки. Возможные ошибки:
13 CANT_SET;
14 BAD_ADDRESS.
При благоприятном выполнении запроса флаг carry=0, а значение регистра CX сохраняется.
Запрос используется в случае, когда необходим специфический физический адрес интерфейса (например, в случае DECNET). При наличии более одного указателя (handle) драйвер откажется исполнить данный запрос и пришлет сообщение CANT_SET.
Этим не исчерпывается перечень возможных запросов, существует некоторое количество операций, относящихся к экстра-набору функций (код функциональности 5 или 6, смотри описание запроса driver_info).