Windows - статьи

       

Управление работой устройства, определение его


Управление работой устройства, определение его состояния, поддержка режимов и т.д. осуществляется с помощью запросов IRP_MJ_DEVICE_CONTROL и IRP_MJ_INTERNAL_DEVICE_CONTROL. В режиме пользователя поддерживается только запрос первого типа - он формируется с помощью функции DeviceIoControl. В качестве одного из параметров этой функции фигурирует код операции (IOCTL), который определяет действие, выполняемое драйвером. Для некоторых устройств (например, COM-порта) в API Win32 определены функции управления, которые служат оболочкой для DeviceIoControl при определенном значении IOCTL. Это делает программу более наглядной и читаемой. Для параллельного порта я такой возможности не обнаружил, хотя это не вызывает трудностей, поскольку никто не мешает описать такие функции самому.
Тело такой функции будет сводится к вызову DeviceIoControl с определенным набором параметров. Гораздо более неприятный момент заключается в том, что для LPTx невозможно определить коммуникационное событие, чтобы затем обработать его в отдельном потоке как это можно сделать, например, для последовательного порта с помощью вызова SetCommEvents. Поэтому о готовности Вашего устройства к каким-либо операциям, оно может сообщить, только выставив флаг в некотором своем регистре, который Вы будете регулярно опрашивать (например, по таймеру), что конечно не очень удобно и эффективно. Вообще говоря, в режиме EPP параллельного порта определено внешнее прерывание по положительному переходу на линии 10 (nACK), и драйвер содержит его обработчик. Доступ к обработчику можно получить с помощью IRP_MJ_INTERNAL_DEVICE_CONTROL, но только в режиме ядра. Еще раз хочу подчеркнуть, что все изложенное - результат моих собственных поисков, поэтому не исключено, что какое-то решение от Microsoft для обработки событий параллельного порта в режиме пользователя существует. Если кто-нибудь знает, поделитесь. В конце статьи я укажу E-mail для связи.
Рассмотрим по порядку все коды операций доступные в качестве аргумента в вызове DeviceIoControl при работе с LPTx.


Код представляет собой 32-разрядное слово и строится по определенному правилу, которое учитывает тип устройства, вид и метод доступа. Не вдаваясь в подробности, перечислим значения кодов для управления параллельным портом и их идентификаторы, принятые в Microsoft.

Идентификатор кода IOCTL Значение кода

IOCTL_IEEE1284_GET_MODE $160014
IOCTL_IEEE1284_NEGOTIATE $160018
IOCTL_PAR_GET_DEFAULT_MODES $160028
IOCTL_PAR_GET_DEVICE_CAPS $160024
IOCTL_PAR_IS_PORT_FREE $160054
IOCTL_PAR_QUERY_DEVICE_ID $16000C
IOCTL_PAR_QUERY_DEVICE_ID_SIZE $160010
IOCTL_PAR_QUERY_INFORMATION $160004
IOCTL_PAR_QUERY_LOCATION $160058
IOCTL_PAR_QUERY_RAW_DEVICE_ID $160030
IOCTL_PAR_SET_INFORMATION $160008
IOCTL_PAR_SET_READ_ADDRESS $160020
IOCTL_PAR_SET_WRITE_ADDRESS $16001C
IOCTL_SERIAL_GET_TIMEOUTS $1B001C
IOCTL_SERIAL_SET_TIMEOUTS $1B0020

Обращаю внимание читателя, что статья приготовлена для сайта Delphi, поэтому все примеры кода и т.п. приводятся на Паскале. Коды в таблице указаны в шестнадцатеричном формате (т.е. 0х160014 и т.д.).
Код IOCTL_IEEE1284_GET_MODE предназначен для формирования запроса текущего режима порта:
const IOCTL_IEEE1284_GET_MODE = $160014;
type PARCLASS_NEGOTIATION_MASK = record usReadMask: word; usWriteMask: word; end; PPARCLASS_NEGOTIATION_MASK = ^PARCLASS_NEGOTIATION_MASK;
var Mode: PARCLASS_NEGOTIATION_MASK; lpOverlapped: POverlapped; ret: DWORD;
DeviceIoControl(hLpt, IOCTL_IEEE1284_GET_MODE, nil, 0, @Mode, sizeof(PARCLASS_NEGOTIATION_MASK), ret, lpOverlapped);
Если запрос синхронный, то он выглядит так:
DeviceIoControl(hLpt, IOCTL_IEEE1284_GET_MODE, nil, 0, @Mode, sizeof(PARCLASS_NEGOTIATION_MASK), ret, nil);
@Mode - указатель на буфер, в котором драйвер возвращает текущий режим. Следующий параметр сообщает драйверу размер буфера, а параметр ret возвращает размер структуры, через которую передаются данные. Структура PARCLASS_NEGOTIATION_MASK имеет два поля: usReadMask - определяет режим работы порта при чтении, а usWriteMask - при записи.


Вот возможные значения этих переменных:
const NONE = $0000;
// SPP modes CENTRONICS = $0001; // Только для записи IEEE_COMPATIBILITY = $0002; // Только для записи NIBBLE = $0004; // Только для чтения CHANNEL_NIBBLE = $0008; // Только для чтения BYTE_BIDIR = $0010; // Только для чтения
// EPP modes EPP_HW = $0020; // Аппаратный EPP EPP_SW = $0040; // Программный EPP
// ECP modes BOUNDED_ECP = $0080; // Упрощенный ECP ECP_HW_NOIRQ = $0100; // Аппаратный ECP без IRQ ECP_HW_IRQ = $0200; // Аппаратный ECP с IRQ ECP_SW = $0400; // Программный ECP
Сразу после того, как порт открыт, он устанавливается в режим CENTRONICS по записи и NIBBLE по чтению.
Код операции IOCTL_IEEE1284_NEGOTIATE предназначен для согласования режима работы порта и устройства:
const IOCTL_IEEE1284_NEGOTIATE = $160018;
var ReqMode, LptMode: PARCLASS_NEGOTIATION_MASK;
ReqMode. usReadMask:= $7FF; // Тестировать все режимы для чтения ReqMode. usWriteMask:= $7FF; // Тестировать все режимы для записи DeviceIoControl(hLpt, IOCTL_IEEE1284_NEGOTIATE, @ReqMode, sizeof(PARCLASS_NEGOTIATION_MASK), @LptMode, sizeof(PARCLASS_NEGOTIATION_MASK), ret, lpOverlapped);
@ReqMode указывает на структуру PARCLASS_NEGOTIATION_MASK, которая содержит маску тестирования режимов для работы с устройством. Маска представляет собой произвольную сумму, указанных выше констант.
Драйвер выполняет с устройством для каждого указанного в маске режима в соответствии со спецификацией IEEE 1284. Если соответствующий бит в маске выставлен в 1, то данный режим проверяется на возможность его применения при работе с подключенным устройством, а затем выбирается режим с максимальной пропускной способностью. Этот режим устанавливается в качестве текущего, а соответствующие ему значения драйвер возвращает в структуре LptMode. Кроме указанных выше констант, однозначно обозначающих режимы, Windows определяет еще две маски для запроса:
EPP_ANY = $0060; // любой из EPP ECP_ANY = $0780; // любой из ECP
Чтобы запросить проверку всех режимов, нужно указать $7FF.
После согласования режима драйвер не переводит выходные линии порта в состояние, соответствующее выбранному режиму! Он оставляет их в состоянии инициализации:



Сигнал Конт. Состояние
nSelectIn(1284 Active) 17 низкий
nAutoFeed 4 высокий
nStrobe 1 высокий
Initialize 16 высокий

В таком состоянии выводы находятся сразу после открытия порта, или после сброса драйвера (см. далее). При первом обращении к порту на предмет ввода-вывода драйвер сначала сообщит устройству о начале работы повторением последовательности согласования (на этот раз только для выбранного режима), а затем осуществит обмен данными. После этого состояние линий будет соответствовать выбранному режиму, и уже каждый последующий цикл ввода-вывода будет происходить по обычной схеме.
Код IOCTL_PAR_GET_DEFAULT_MODES применяется для запроса режима драйвера по умолчанию: CENTRONICS для записи и NONE для чтения. Может быть возможны и другие варианты, поэтому приведу команду целиком.
const IOCTL_PAR_GET_DEFAULT_MODES = $160028;
var Mode: PARCLASS_NEGOTIATION_MASK;
DeviceIoControl(hLpt, IOCTL_PAR_GET_DEFAULT_MODES, nil, 0, @Mode, sizeof(PARCLASS_NEGOTIATION_MASK), ret, lpOverlapped);
В структуре Mode возвращается режим по умолчанию в той же манере, что и для запроса текущего режима IOCTL_IEEE1284_GET_MODE.
Код IOCTL_PAR_GET_DEVICE_CAPS формирует запрос поддержки устройством различных режимов параллельного порта. Майкрософт заявляет о поддержке их драйвером спецификации IEEE1284, хотя есть немало существенных исключений, если доверять сайту экспертов этого протокола . К сожалению, у меня нет на руках самого документа, потому что IEEE просит за него около $100, поэтому дать 100% гарантию, что Майкрософт не прав не могу. Если у кого-то есть pdf оригинала, поделитесь с народом. Я говорю про IEEE1284.3 от 1994 года. А то без исходников происходит вечная путаница. Спецификация определяет пять основных режимов:
  • совместимости (Centronics);

  • полубайтный (Nibble);

  • байтный (Byte_Bidir);

  • EPP;

  • ECP.

  • Майкрософт добавляет несколько подрежимов:
  • адресуемый полубайтный (CHANNEL_NIBBLE);

  • программный EPP (EPP_SW);

  • программный ECP (ECP_SW);

  • упрощенный ECP (BOUNDED_ECP).



  • Запрос с кодом IOCTL_PAR_GET_DEVICE_CAPS похож на IOCTL_IEEE1284_NEGOTIATE с той разницей, что он не меняет режим драйвера, а только проводит циклы согласования с устройством для всех(!!!) режимов, чтобы выяснить какие из них поддерживаются. Затем результат проверки возвращается в уже известной структуре PARCLASS_NEGOTIATION_MASK.
    const IOCTL_PAR_GET_DEVICE_CAPS = $160024;
    var LptMode: PARCLASS_NEGOTIATION_MASK;
    DeviceIoControl(hLpt, IOCTL_PAR_GET_DEVICE_CAPS, nil, 0, @LptMode, sizeof(PARCLASS_NEGOTIATION_MASK), ret, lpOverlapped);
    Если выдать этот запрос на пустой порт (без устройства), то будет отмечена поддержка только CENTRONICS и IEEE_COMPATIBILITY. Соответственно и включить никакие другие режимы будет невозможно. Исключение составляет только NIBBLE, который согласно спецификации должен поддерживаться всеми IEEE1284-устройствами. Чтобы использовать другой режим, например EPP, необходимо подключить устройство, поддерживающее для этого режима. В ходе цикла согласования хост выставляет на шину данных байт совместимости, который уникален для каждого режима:
    EPP 01000000 ECP 00x10000 byte mode 00000001
    Затем по отрицательному переходу сигнала Paper End (конт. 12) хост проверяет уровень на линии Select (конт. 13). Если устройство подтверждает режим, то на эту линию оно должно выставить 1, в противном случае - 0. Байт совместимости имеет очень удобную структуру для его аппаратной обработки: за каждым режимом закреплен определенный бит, который выставляется в единицу только для этого режима. Поэтому, скажем для EPP, достаточно защелкнуть 6 бит (считая от 0) по сигналу nStrobe (1 конт.) и вернуть его по линии Select. Если сигнал Select просто навсегда установить в 1 (подтянуть к +5В), то устройство будет подтверждать все режимы.
    Что касается других сигналов, то для согласования работы с драйвером параллельного порта достаточно на линях nError (конт. 15) и Paper End выставить лог. 1, а для формирования сигнала завершения цикла согласования можно по линии nACK (конт. 10) вернуть в хост уровень nAutoFeed (конт. 14).


    Так как оборванный вход LPT- порта воспринимается как 1, то для реализации этой последовательности достаточно просто воткнуть в разъем перемычку на контакты 10 и 14. Такое "устройство" драйвер распознает как поддерживающее следующие режимы: CENTRONICS, IEEE_COMPATIBILITY, NIBBLE, CHANNEL_NIBBLE, BYTE_BIDIR, EPP_SW, - т.е. все режимы, кроме аппаратного EPP и всех разновидностей ECP. Дело в том, что согласование ECP (который, кстати, придуман Microsoft в сотрудничестве с HP) требует более сложной последовательности согласования, и простой аппаратурой здесь не обойтись. В интернете свободно распространяется файл ecp_reg.pdf от Microsoft с подробным описанием режима.
    Ситуация с аппаратным EPP еще более оригинальная: при запросе этого режима на разъеме порта……..ничего не происходит!!! А драйвер сообщает, что режим не поддерживается. Отсутствие даже намека на попытку согласования EPP_HW позволяет сделать вывод о том, что именно сам драйвер не поддерживает этот режим,- возможно из-за каких-то трений между Microsoft и Intel - автором EPP, может из-за технических нестыковок. Ниже сопоставляются сигналы в режимах аппаратного и программного EPP, а также применение соответствующих линий в цикле согласования.

    № конт. Centronics Аппарат. EPP Прогр. EPP Согласование
    1 nStrobe nWrite nWrite nStrobe
    10 nAck Interrupt ? nAck
    11 Busy nWait nWait -
    12 Paper End - - PE
    13 Select - - Select
    14 nAutoFeed Data Strobe Data Strobe nAutoFeed
    15 nError - - nError
    16 nInitialize nReset Address Strobe -
    17 nSelectPrinter Address Strobe 1284 Active 1284 Active

    Здесь надо обратить внимание на две вещи. Во-первых, в программной реализации EPP Майкрософт случайно или намеренно перепутал сигналы местами: строб адреса теперь на контакте 16 разъема, т.е. он соответствует сигналу nInitialize. На контакте 17 остается признак интерфейса 1284, как и во всех остальных режимах, включая последовательность согласования. Этот сигнал можно использовать как ChipSelect (активный высокий).


    Сигнала сброса nReset как такового нет, но есть последовательность сброса, которую выполняет драйвер по соответствующему запросу (см. далее). Во-вторых, из-за ограниченности набора сигналов одни и те же линии используются как в циклах квитирования режимов, так и в цикле согласования (например, Data Strobe - AutoFeed, Write - Strobe). Поэтому если не принять определенных мер, то во время последовательности согласования будет сымитирован цикл записи, и в регистр устройства будет записан байт совместимости, что не всегда допустимо. Мало того, непосредственно перед этим имитируется чтение данных из устройства, несмотря на наличие байта на выходе LPT. Работа двух устройств на выход одновременно не сулит ничего хорошего. В моем случае внешнее устройство "перетягивало" порт, изменяя байт совместимости. В худшем случае что-нибудь может выгореть.
    Последовательность согласования предусматривает возможность запроса идентификатора устройства, который представляет собой нуль-терминальную строку. Эту строку Windows выдает на экран при обнаружении нового устройства. Чтобы сделать запрос ID, придется воспользоваться кодами IOCTL_PAR_QUERY_DEVICE_ID, IOCTL_PAR_QUERY_DEVICE_ID_SIZE, IOCTL_PAR_QUERY_RAW_DEVICE_ID.
    С помощью кода IOCTL_PAR_QUERY_INFORMATION у драйвера можно запросить информацию об его состоянии.
    const IOCTL_PAR_QUERY_INFORMATION = $160004;
    type PAR_QUERY_INFORMATION = record Status: byte; end; PPAR_QUERY_INFORMATION = ^PAR_QUERY_INFORMATION;
    var ParInfo: PAR_QUERY_INFORMATION;
    DeviceIoControl(hLpt, IOCTL_PAR_QUERY_INFORMATION, nil, 0, @ParInfo, sizeof(PAR_QUERY_INFORMATION), ret, lpOverlapped);
    В поле ParInfo.Status драйвер возвращает байт, который может представлять собой комбинацию следующих чисел
    const PARALLEL_INIT = $01; PARALLEL_AUTOFEED = $02; PARALLEL_PAPER_EMPTY = $04; PARALLEL_OFF_LINE = $08; PARALLEL_POWER_OFF = $10; PARALLEL_NOT_CONNECTED = $20; PARALLEL_BUSY = $40; PARALLEL_SELECTED = $80;
    Прямого соответствия между битами ParInfo.Status и физическими регистрами параллельного порта нет, но какая-то нетривиальная взаимосвязь существует: определенные комбинации входных сигналов на разъеме порождают различное состояние поля статуса драйвера.


    Код IOCTL_PAR_SET_INFORMATION применяется для сброса драйвера.
    const IOCTL_PAR_SET_INFORMATION = $160008;
    type PAR_SET_INFORMATION = record Init: byte; end; PPAR_SET_INFORMATION = ^PAR_SET_INFORMATION;
    var ParControl: PAR_SET_INFORMATION;
    ParControl.Init := PARALLEL_INIT; DeviceIoControl(hLpt, IOCTL_PAR_SET_INFORMATION, @ParControl, sizeof(PAR_SET_INFORMATION), nil, 0, ret, lpOverlapped);
    Обратите внимание на значение, которое присваивается переменной ParControl.Init. Других вариантов не существует. При таком запросе драйвер сбрасывает nSelectIn (1284 Active) в ноль, а на линию nInitialize (Address Strobe) выдает отрицательный строб. При этом сигнал nStrobe (Write) сохраняет высокий уровень. Такая последовательность чем-то похожа на цикл чтения адреса EPP, которая на практике редко используется. Поэтому по условию
  • nStrobe (конт. 1) - высокий;

  • nInitialize (конт. 16) - низкий;

  • nSelectIn (конт. 17) - низкий;

  • можно сформировать сигнал сброса устройства.
    Запрос IOCTL_SERIAL_SET_TIMEOUTS устанавливает значение таймаута, которое драйвер использует в операциях записи в режимах SPP и ECP_SW. Прочитать установленное значение можно с помощью IOCTL_SERIAL_GET_TIMEOUTS.

    Содержание раздела