воскресенье, 28 апреля 2019 г.

Подсистема параллельного порта Linux 2.4 - часть 3, parport - справочник программного интерфейса драйвера параллельного порта Linux

Перевод: The Linux 2.4 Parallel Port Subsystem
Автор: Тим Во (Tim Waugh)
Предыдущая часть: Подсистема параллельного порта Linux 2.4 - часть 2, ppdev - драйверы устройств, работающие в пространстве пользователя

Справочник программного интерфейса драйвера параллельного порта Linux

parport_device_num

Название
parport_device_num -- преобразовать координаты устройства
Обзор
int parport_device_num(int parport, int mux, int daisy);
Аргументы
parport

номер параллельного порта

mux

номер порта мультиплексора (-1, если мультиплексора нет)

daisy

адрес в цепочке устройств (-1, если нет адреса в цепочке)

Описание
Пытается найти устройство на указанном параллельном порту, порту мультиплексора и адресе в цепочке, и возвращает номер устройства или -NXIO, если устройство с такими координатами не существует.

parport_device_coords

Название
parport_device_coords -- преобразовать канонический номера устройства
Обзор
int parport_device_coords(int devnum, int * parport, int * mux, int * daisy);
Аргументы
devnum

номер устройства

parport

указатель на область для сохранения номера параллельного порта

mux

указатель на область для сохранения номера порта мультиплексора

daisy

указатель на область для сохранения адреса в цепочке устройств

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

Вызывающая сторона должна выделить области для сохранения parport, mux и daisy.

Если устройство с указанным номером отсутствует, то возвращается -ENXIO. В противном случае области, на которые указывают parport, mux и daisy, заполняются координатами устройства. Если координата не определена, то используется значение -1.

На самом деле функция не очень полезна, но этот интерфейс был предложен в IEEE 1284.3.

parport_find_device

Название
parport_find_device -- найти указанное устройство
Обзор
int parport_find_device(const char * mfg, const char * mdl, int from);
Аргументы
mfg

строка, содержащая название требуемого производителя

mdl

строка, содержащая название требуемой модели устройства

from

номер предыдущего устройства, найденного в процессе поиска, или NULL, если это новый поиск

Описание
Проходит по списку устройств на параллельных портах в поисках устройства, у которого строки 'MFG' и 'MDL' в идентификаторе устройства IEEE 1284 будут совпадать с mfg и mdl.

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

Новый поиск начинается с передачи значения NULL в аргументе from. Если from - не NULL, то поиск продолжается с указанного устройства.

parport_find_class

Название
parport_find_class -- найти устройство в указанном классе
Обзор
int parport_find_class(parport_device_class cls, int from);
Аргументы
cls

требуемый класс

from

номер устройства, найденного во время предыдущего поиска, или NULL, если это новый поиск

Описание
Проходит по списку устройств на параллельных портах в поисках устройства, у которого строка 'CLS' в идентификаторе устройства IEEE 1284 совпадает с cls.

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

Новый поиск начинается с передачи значения NULL в аргументе from. Если from - не NULL, то поиск продолжается с указанного устройства.

parport_register_driver

Название
parport_register_driver -- зарегистрировать драйвер устройства на параллельном порту
Обзор
int parport_register_driver(struct parport_driver * drv);
Аргументы
drv

структура, описывающая драйвер

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

Место для хранения структуры drv выделяется вызывающей стороной и не должно освобождаться вплоть до вызова parport_unregister_driver.

Функция attach драйвера может блокироваться. Порт, который передан в attach, действителен только на время вызова, но если драйверу нужно взять копию указателя, то он может воспользоваться parport_get_port. Вызов parport_register_device с этим портом сделает это автоматически.

Функция detach драйвера не может блокироваться. Порт, который передан в detach, действителен только на время вызова, но если драйверу нужно взять копию указателя, то для этого он должен воспользоваться parport_get_port.

В случае успеха возвращает 0. В настоящее время всегда завершается успешно.

parport_unregister_driver

Название
parport_unregister_driver -- отменить регистрацию драйвера устройства на параллельном порту
Обзор
void parport_unregister_driver(struct parport_driver * arg);
Аргументы
arg

структура, описывающая драйвер, которая была передана в parport_register_driver

Описание
Должна вызываться при выгрузке драйвера устройства на параллельном порту, который зарегистрировался при помощи parport_register_driver.

После возврата подпрограмма attach драйвера больше не будет вызываться, а для каждого из портов, для которого был вызван attach, будет вызвана подпрограмма detach.

Если функция attach вызывающего может блокироваться, то драйвер сам должен дождаться её завершения перед выгрузкой.

Перед возвратом из этой функции все вызовы detach драйвера будут гарантированно завершены.

В вызове detach драйвера не разрешается блокироваться.

parport_get_port

Название
parport_get_port -- увеличить счётчик ссылок на порт
Обзор
struct parport * parport_get_port(struct parport * port);
Аргументы
port

порт

Описание
Позволяет убедиться, что указатель на структуру parport останется доступным вплоть до соответствующего вызова parport_put_port.

parport_put_port

Название
parport_put_port -- уменьшить счётчик ссылок на порт
Обзор
void parport_put_port(struct parport * port);
Аргументы
port

порт

Описание
Должна вызываться для каждого из вызовов parport_get_port, когда порт больше не требуется.

parport_find_number

Название
parport_find_number -- найти параллельный порт по номеру
Обзор
struct parport * parport_find_number(int number);
Аргументы
number

номер параллельного порта

Описание
Возвращает параллельный порт с указанным номером или NULL, если его нет.

Выполняет неявный вызов parport_get_port. Чтобы отбросить ссылку на порт, которая была получена через parport_find_number, воспользуйтесь parport_put_port.

parport_find_base

Название
parport_find_base -- найти параллельный порт по базовому адресу
Обзор
struct parport * parport_find_base(unsigned long base);
Аргументы
base

базовый адрес ввода-вывода

Описание
Возвращает параллельный порт с указанным базовым адресом или NULL, если его нет.

Выполняет неявный вызов parport_get_port. Чтобы отбросить ссылку на порт, которая была получена через parport_find_base, воспользуйтесь parport_put_port.

parport_register_device

Название
parport_register_device -- зарегистрировать устройство на параллельном порту
Обзор
struct pardevice * parport_register_device(struct parport * port, const char * name, int (*pf) (void *), void (*kf) (void *), void (*irq_func) (int, void *, struct pt_regs *), int flags, void * handle);
Аргументы
port

порт, к которому присоединено устройство

name

имя для обращения к устройству

pf

обработчик вежливой просьбы освободить порт

kf

обработчик сообщения о свободном порте

irq_func

обработчик прерывания

flags

флаги регистрации

handle

данные для функций-обработчиков

Описание
Функция, вызываемая драйверами устройств на параллельном порту, объявляет, что это устройство подключено к порту и сообщает системе всё, что ей нужно знать.

Область для хранения name выделяется вызывающей стороной и не должна освобождаться вплоть до того, как вызывающая сторона не вызовет parport_unregister_device для этого устройства.

pf - обработчик вежливой просьбы освободить порт. Выполняется, когда драйвер владеет доступом к порту, но к этому порту хочет получить доступ драйвер другого устройства. Если драйвер желает освободить порт, то он должен вернуть ноль и тогда порт будет им освобождён. Если драйвер желает сохранить доступ к порту, он должен вернуть ненулевое значение и никаких действий не будет выполнено. Вежливый драйвер постарается отпустить порт как можно раньше, при первой возможности после вежливой просьбы, после того как первая вежливая просьба была отклонена. Отметим, что если вежливый обработчик будет рад выполнить просьбу, то освобождать порт не требуется - это будет выполнено автоматически. Эта функция не может блокироваться, поскольку она может быть вызвана из контекста прерывания. Если драйвер устройства не поддерживает обработку вежливых просьб, pf может принимать значение NULL.

kf - обработчик сообщения о свободном порте. Выполняется, когда порт можно затребовать в исключительный доступ. Если драйвер хочет затребовать доступ к порту, то вызов parport_claim гарантированно завершится удачно внутри обработчика сообщения о свободном порте. Если драйвер хочет затребовать доступ к порту, он должен сделать это. В противном случае никаких действий предпринимать не требуется. Эта функция не может блокироваться, поскольку она может быть вызвана из контекста прерывания. Если драйвер устройства не желает получать явные приглашения затребовать порт таким образом, kf может принимать значение NULL.

irq_func - обработчик прерывания. Он вызывается, когда происходит прерывание от параллельного порта. Отметим, что если драйвер устройства желает использовать прерывания, он должен использовать parport_enable_irq, а также может отметить поле irq в структуре parport, содержащей информацию о порте.

Низкоуровневый драйвер параллельного порта - один из тех, кто вызывается из request_irq и чей обработчик прерывания выполняется первым. Этот обработчик делает всё необходимое, чтобы оборудование получило подтверждение прерывания (для портов типа PC ничего специального не делается). Затем он сообщает о прерывании коду IEEE 1284, который реагирует на событие IEEE 1284 в соответствии с текущей фазой IEEE 1284. И только после этого, он вызовет irq_func. Не стоит говорить, что irq_func будет вызываться из контекста прерывания и не может блокироваться.

Флаг PARPORT_DEV_EXCL предотвращает совместное использование порта и поэтому должен использоваться только в тех случаях, если совместное использование порта с другими устройствами не возможно и может привести к некорректному поведению. Старайтесь избегать! Обычно параметр flags равен нулю.

Функция возвращает указатель на структуру, представляющую устройство на порту, или NULL, если не хватило памяти, чтобы выделить место для этой структуры.

parport_unregister_device

Название
parport_unregister_device -- отменить регистрацию устройства на параллельном порту
Обзор
void parport_unregister_device(struct pardevice * dev);
Аргументы
dev

указатель на структуру, представляющую устройство

Описание
Отменяет действие parport_register_device.

parport_open

Название
parport_open -- найти устройство по каноническому номеру устройства
Обзор
struct pardevice * parport_open(int devnum, const char * name, int (*pf) (void *), void (*kf) (void *), void (*irqf) (int, void *, struct pt_regs *), int flags, void * handle);
Аргументы
devnum

канонический номер устройства

name

имя для обращения к устройству

pf

обработчик вежливой просьбы освободить порт

kf

обработчик сообщения о свободном порте

irqf

обработчик прерываний

flags

флаги регистрации

handle

данные драйвера

Описание
Функция аналогична parport_register_device, за исключением того, что находит устройство по его номеру, а не по порту, к которому оно подключено. См. parport_find_device и parport_find_class.

Все параметры, за исключением devnum, такие же как и у parport_register_device. Возвращаемое значение такое же, как и у parport_register_device.

parport_close

Название
parport_close -- закрыть устройство, открытое при помощи parport_open
Обзор
void parport_close(struct pardevice * dev);
Аргументы
dev

закрываемое устройство

Описание
То же самое по отношению к parport_open, как parport_unregister_device по отношению к parport_register_device.

parport_claim

Название
parport_claim -- затребовать доступ к устройству на параллельном порту
Обзор
int parport_claim(struct pardevice * dev);
Аргументы
dev

указатель на структуру данных, представляющую устройство на порту

Описание
Эта функция не блокируется и поэтому может использоваться в контексте прерывания. Если parport_claim получила доступ к порту, то она вернёт ноль и порт будет доступен для использования. Она также может завершиться неудачно (вернув ненулевое значение), если порт используется другим драйвером и этот драйвер не хочет отказываться от управления портом.

parport_claim_or_block

Название
parport_claim_or_block -- затребовать доступ к устройству на параллельном порту
Обзор
int parport_claim_or_block(struct pardevice * dev);
Аргументы
dev

указатель на структуру, представляющую устройство на порту

Описание
Ведёт себя как parport_claim, но заблокируется, если необходимо подождать, когда освободится порт. Возвращаемое значение 1 указывает, что функция спала, а 0 означает, что функция сразу выполнилась успешно. Отрицательный код ошибки свидетельствует о неудаче.

parport_release

Название
parport_release -- отказаться от доступа к устройству на параллельном порту
Обзор
void parport_release(struct pardevice * dev);
Аргументы
dev

указатель на структуру, представляющую устройство на параллельном порту

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

parport_yield

Название
parport_yield -- уступить доступ к параллельному порту
Обзор
int parport_yield(struct pardevice * dev);
Аргументы
dev

устройство на параллельном порту

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

Функции parport_yield и parport_yield_blocking предназначены для отметки мест в драйвере, где другие драйверы могут затребовать доступ к порту и использовать его для связи со своими устройствами. Уступка порта аналогична его освобождению и повторному затребованию, но более эффективна, т.к. если нет других драйверов, которым требуется доступ к порту, то никаких действий не предпринимается. На деле ничего не делается даже тогда, когда есть другие устройства, ожидающие доступа, но текущее устройство находится внутри своего кванта времени. По умолчанию квант времени - полсекунды, но эта длительность может настраиваться через интерфейс /proc.

parport_yield_blocking

Название
parport_yield_blocking -- уступить доступ к параллельному порту
Обзор
int parport_yield_blocking(struct pardevice * dev);
Аргументы
dev

устройство на параллельном порту

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

parport_negotiate

Название
parport_negotiate -- согласовать режим IEEE 1284
Обзор
int parport_negotiate(struct parport * port, int mode);
Аргументы
port

используемый порт

mode

согласуемый режим

Описание
Используйте для согласования определённого режима передачи IEEE 1284. Параметр mode должен быть одной из констант, определённых в файле parport.h, имя которой начинается с IEEE1284_MODE_xxx.

Возвращает значение 0, если периферийное устройство согласовало указанный режим. -1 возвращается, если периферийное устройство не совместимо со стандартом IEEE 1284 (или отсутствует). 1 возвращается, если периферийное устройство не согласовало указанный режим.

parport_write

Название
parport_write -- записать блок данных в параллельный порт
Обзор
ssize_t parport_write(struct parport * port, const void * buffer, size_t len);
Аргументы
port

порт для записи

buffer

буфер данных (в пространстве ядра)

len

количество передаваемых байт данных

Описание
Пишет до len байт из буфера в указанный порт, используя последний из согласованных (при помощи parport_negotiate) режимов передачи IEEE 1284, если этот режим поддерживает прямую передачу (от компьютера к периферийному устройству).

За действительность первых len байт буфера отвечает вызывающая сторона.

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

parport_read

Название
parport_read -- прочитать блок данных из параллельного порта
Обзор
ssize_t parport_read(struct parport * port, void * buffer, size_t len);
Аргументы
port

порт для чтения

buffer

буфер данных (в пространстве ядра)

len

количество принимаемых байт данных

Описание
Читает до len байт данных из указанного порта в буфер, используя последний из согласованных (при помощи parport_negotiate) режимов передачи IEEE 1284, если этот режим поддерживает обратную передачу (от периферийного устройства на компьютер).

За доступность для записи первых len байт буфера отвечает вызывающая сторона.

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

parport_set_timeout

Название
parport_set_timeout -- задать таймаут бездействия устройства
Обзор
long parport_set_timeout(struct pardevice * dev, long inactivity);
Аргументы
dev

устройство на порту

inactivity

время бездействия (в тактах)

Описание
Задаёт таймаут бездействия указанного устройства на порту. Действует на функции, подобные parport_wait_peripheral. Специальное значение 0 означает - не использовать расписание при работе с этим устройством.

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

Все обратившиеся к parport_wait_event для этого устройства будут разбужены.

Подсистема параллельного порта Linux 2.2

Хотя описанный в этом документе интерфейс был значительно обновлён в ядре 2.4, в ядре 2.2 используется аналогичный механизм совместного доступа. В 2.2 доступны следующие функции:
  • parport_register_device
  • parport_unregister_device
  • parport_claim
  • parport_claim_or_block
  • parport_release
  • parport_yield
  • parport_yield_blocking
А также поддерживается согласование полубайтового режима:
int parport_ieee1284_nibble_mode_ok(struct parport *port, unsigned char mode);
Единственными действующими значениями mode являются 0 (для полубайтового режима) и 4 (для чтения идентификатора устройства в полубайтовом режиме).

Эта функция устарела и в Linux 2.4 была заменена на parport_negotiate.

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

воскресенье, 21 апреля 2019 г.

Подсистема параллельного порта Linux 2.4 - часть 2, ppdev - драйверы устройств, работающие в пространстве пользователя

Перевод: The Linux 2.4 Parallel Port Subsystem
Автор: Тим Во (Tim Waugh)
Предыдущая часть: Подсистема параллельного порта Linux 2.4 - часть 1, обзор

Драйверы устройств, работающие в пространстве пользователя

Введение в ppdev

Принтер доступен через /dev/lp0, а параллельный порт доступен через /dev/parport0. Разница заключается в уровне управления, который можно осуществлять по проводам в кабеле параллельного порта.

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

В отличие от драйвера принтера, драйвер ppdev (доступный через /dev/parport0) позволяет:
  • проверять линии статуса,
  • задавать линии управления,
  • задавать/проверять линии данных (и управлять направлением линий данных),
  • ожидать прерывания (срабатывает при изменении одной из линий статуса),
  • узнавать, сколько произошло новых прерываний,
  • задавать ответ на прерывание,
  • использовать согласование IEEE 1284 (чтобы сообщить периферийному устройству, какой режим передачи использовать),
  • передавать данные с использованием указанного режима IEEE 1284.

Драйвер в пространстве ядра или в пространстве пользователя?

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

Другой критерий - это лёгкость разработки. В большинстве случаев проще написать драйвер, который будет работать в пространстве пользователя, потому что (а) одно неверное движение не приведёт к аварии всего компьютера, (б) имеется доступ к пользовательским библиотекам (таким как библиотека C), и (в) его проще отлаживать.

Интерфейс программирования

Интерфейс ppdev во многом совпадает с интерфейсом символьных специальных устройств, т.к. он тоже поддерживает функции open, close, read, write и ioctl. Константы для команд ioctl имеются в файле include/linux/ppdev.h.
Начало и завершение: open и close
Файл устройства /dev/parport0 представляет любое устройство, подключенное к parport0 - первому параллельному порту в системе. Каждый раз при открытии файла устройства, он представляет (для процесса, выполняющего открытие) другое устройство. Он также может быть открыт более одного раза, но в любой момент только один экземпляр действительно будет управлять параллельным портом. Процесс, который открыл /dev/parport0, работает с параллельным портом через механизм совместного доступа таким же образом, как и любой другой драйвер устройства. Драйвер в пространстве пользователя может работать с параллельным портом совместно как с драйверами устройств пространства ядра, так и с драйверами пространства пользователя.
Управление: ioctl
Большая часть управления выполняется через вызовы ioctl. При помощи ioctl драйвер пространства пользователя может управлять как драйвером ppdev в ядре, так и самим физическим параллельным портом. Вызов ioctl принимает в качестве параметров дескриптор файла (который был получен при открытии файла устройства), команду, и (не обязательный) указатель на некоторые данные.
PPCLAIM



Затребовать доступ к порту. Необходимо сделать это перед тем, как приступить к работе с параллельным портом. Отметим, что некоторые операции действуют только на драйвер ppdev, но не на порт - например, PPSETMODE. Они могут осуществляться только в тот момент, когда доступ к порту не затребован.

PPEXCL



Указывает драйверу ядра запретить любое совместное использование с другими драйверами, то есть запрашивается исключительный доступ. Команда PPEXCL действует только пока доступ к порту ещё никем не затребован. Последующий запрос ioctl PPCLAIM может завершится неудачно, т.к. к этому моменту доступ к порту может быть предоставлен какому-то другому драйверу.

Большинству драйверов устройств не требуется эксклюзивный доступ к порту. Такой доступ предоставляется в случае, если он действительно нужен. Например, это могут быть устройства, которым требуется доступ на продолжительное время (многие секунды).

Отметим, что ioctl PPEXCL на самом деле не запрашивает доступ к порту - действие откладывается до тех пор, пока не будет выполнена команда ioctl PPCLAIM.

PPRELEASE



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

PPYIELD



Уступить порт другому драйверу. Этот ioctl является сокращенной записью освобождения доступа к порту и немедленного его повторного затребования. Это позволяет другим драйверам получить возможность пообщаться с их устройствами, но после этого доступ к порту вернётся обратно. Пример использования можно найти в драйвере принтера, работающем в пространстве пользователя: как только были записаны несколько символов, можно на некоторое время передать порт драйверу другого устройства, но если символы для отправки на принтер ещё есть, то драйвер принтера желает вернуть доступ к порту как можно раньше.

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

PPNEGOT



Производит согласование одного из режимов IEEE 1284. Согласование - это метод, по которому компьютер и периферийное устройство принимают решение об используемом протоколе передачи данных.

Устройство, совместимое с IEEE 1284, начинает работу в совместимом режиме, а затем компьютер может согласовать другой режим (например, ECP).

Параметр ioctl должен быть указателем на int. В файле incluce/linux/parport.h определены следующие значения:
  • IEEE1284_MODE_COMPAT
  • IEEE1284_MODE_NIBBLE
  • IEEE1284_MODE_BYTE
  • IEEE1284_MODE_EPP
  • IEEE1284_MODE_ECP
ioctl PPNEGOT на самом деле выполняет два действия: производит согласование режима и настраивает поведение последующих вызовов read/write, которые будут работать в этом режиме (но см. также PPSETMODE).

PPSETMODE



Задаёт режим передачи IEEE 1284, который будет использоваться вызовами read и write.

Параметр ioctl должен быть указателем на int.

PPGETMODE



Возвращает текущий режим IEEE 1284, используемый read и write.

PPGETTIME



Возвращает значение таймаута. Вызовы read и write завершаются, если периферийное устройство не ответит достаточно быстро. ioctl PPGETTIME возвращает время, в течение которого периферийное устройство считается доступным, даже если оно не отвечает.

Параметр ioctl должен быть указателем на структуру timeval.

PPSETTIME



Задаёт таймаут. Параметр ioctl должен быть указателем на структуру timeval.

PPGETMODES



Запрашивает возможности оборудования (то есть поле modes из структуры parport).

PPSETFLAGS



Задаёт флаги устройства ppdev, которые могут влиять на последующие операции ввода-вывода. Доступны следующие флаги:
  • PP_FASTWRITE
  • PP_FASTREAD
  • PP_W91284PIC
  • PPWCONTROL
Задаёт линии управления. Параметр ioctl - это указатель на unsigned char, содержащий результат битового ИЛИ над определениями из include/linux/parport.h, соответствующих управляющих линий.

PPRCONTROL



Возвращает последнее значение, записанное в регистр управления, в виде unsigned char: каждый бит соответствует управляющий линии (некоторые из них не используются). Параметр ioctl должен быть указателем на unsigned char.

При этом оборудование фактически не затрагивается, т.к. последнее записанное значение запоминается программно. Так сделано, поскольку некоторое аппаратное обеспечение параллельных портов не предоставляет доступ к регистру управления.

Биты управляющих линий определены в include/linux/parport.h:
  • PARPORT_CONTROL_STROBE
  • PARPORT_CONTROL_AUTOFD
  • PARPORT_CONTROL_SELECT
  • PARPORT_CONTROL_INIT

PPFCONTROL



Переключить управляющие линии. Поскольку часто бывает нужно изменить один из управляющих сигналов, не трогая остальные, было бы не очень эффективно для драйвера в пространстве пользователя сначала воспользоваться PPRCONTROL, внести изменения и затем воспользоваться PPWCONTROL. Конечно, каждому драйверу придётся запоминать состояние управляющих линий (они не могут быть изменены из другого места), но для предоставления PPRCONTROL самому ppdev тоже приходится запоминать состояние управляющих линий.

ioctl PPFCONTROL для переключения управляющих линий аналогичен PPWCONTROL, но воздействует лишь на ограниченный набор управляющих линий. Параметр ioctl - это указатель на структуру ppdev_frob_struct:
struct ppdev_frob_struct {
        unsigned char mask;
        unsigned char val;
};
Поля mask и val - это битовое ИЛИ над именами управляющих линий (таких же, как в PPWCONTROL). PPFCONTROL выполняет следующую операцию:
new_ctr = (old_ctr & ~mask) | val;
Другими словами, сигналы, указанные в mask, примут значения, указанные в val.

PPRSTATUS



Возвращает unsigned char, содержащий биты для каждой из активных линий состояния (например, PARPORT_STATUS_BUSY). Параметр ioctl должен быть указателем на unsigned char.

PPDATADIR



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

Этот вызов бывает нужен только в сочетании с PPWDATA или PPRDATA.

Параметр ioctl - это указатель на int. Если int - ноль, управление линиями данных включается (прямое направление). Если же int - не ноль, то управление линиями данных отключается (обратное направление).

PPWDATA



Задаёт линии данных (если они находится в режиме прямого направления). Параметр ioctl - указатель на unsigned char.

PPRDATA



Читает линии данных (если они находится в режиме обратного направления). Параметр ioctl - указатель на unsigned char.

PPCLRIRQ



Очищает счётчик прерываний. Драйвер ppdev хранит счётчик произошедших прерываний. PPCLRIRQ сохранит этот счётчик в int, указатель на который передан в качестве параметра ioctl.

После этого счётчик прерываний сбрасывается в ноль.

PPWCTLONIRQ



Задаёт признак ответа. После того, как произойдёт прерывание, обработчик должен выставить линии управления в соответствии с запросом. Параметр ioctl - указатель на unsigned char, который интерпретируется так же, как в PPWCONTROL.

Этот ioctl введён по соображениям скорости. Без этого ioctl соответствующее прерывание начнёт обрабатываться в обработчике прерываний, переключится через poll или select на драйвер в пользовательском пространстве, а затем переключится обратно в ядро, чтобы обработать PPWCONTROL. Выполнение всех процедур в обработчике прерывания значительно быстрее.

Передача данных: read и write
Передача данных при помощи read и write осуществляется очевидным образом. Данные передаются с использованием текущего режима IEEE 1284 (см. ioctl PPSETMODE). В режимах, которые позволяют передавать данные только в одном направлении, будет работать только соответствующая функция.
Ожидание событий: poll и select
Драйвер ppdev позволяет драйверам устройств, работающим в пространстве пользователя, ожидать прерываний при помощи poll (и select, который реализуется средствами poll).

Когда драйвер устройства, работающий в пространстве пользователя, захочет подождать прерывания, он засыпает, выполняя poll. Когда поступает прерывание, ppdev будит его (событием «read», хотя, строго говоря, читать на самом деле нечего).

Примеры

Имеющиеся здесь два примера описывают процесс написания простого драйвера принтера для ppdev. В первом примере используется функция write, а во втором примере - непосредственная манипуляция линиями данных и управления.

Сначала нужно открыть устройство.
int drive_printer (const char *name)
{
    int fd;
    int mode; /* Потребуется позже. */

    fd = open (name, O_RDWR);
    if (fd == -1) {
        perror ("open");
        return 1;
    }
Параметр name из вышеприведённого фрагмента должен быть строкой, содержащей имя файла устройства параллельного порта, например "/dev/parport0". (Если файлов /dev/parport нет, то их можно создать при помощи mknod. Это файлы специальных символьных устройств со старшим номером 99.)

Прежде чем работать с портом, нужно получить к нему доступ.
if (ioctl (fd, PPCLAIM)) {
        perror ("PPCLAIM");
        close (fd);
        return 1;
    }
Наш драйвер принтера будет просто копировать свой ввод (со стандартного потока ввода) на принтер. Сделать это можно одним из двух способов. Первый способ - передать всё драйверу, работающему в ядре, зная что принтер работает по протоколу, который в IEEE 1284 называется режимом совместимости.
/* Переключимся в совместимый режим. (Фактически этого делать 
     * не нужно, поскольку в начале всегда используется совместимый режим,
     * но здесь демонстрируется использование PPNEGOT.) */
    mode = IEEE1284_MODE_COMPAT;
    if (ioctl (fd, PPNEGOT, &mode)) {
        perror ("PPNEGOT");
        close (fd);
        return 1;
    }

    for (;;) {
        char buffer[1000];
        char *ptr = buffer;
        size_t got;

        got = read (0 /* стандартный поток ввода */, buffer, 1000);
        if (got < 0) {
            perror ("read");
            close (fd);
            return 1;
        }

        if (got == 0)
            /* Конец ввода */
            break;

        while (got > 0) {
            int written = write_printer (fd, ptr, got);

            if (written < 0) {
                perror ("write");
                close (fd);
                return 1;
            }

            ptr += written;
            got -= written;
        }
    }
Определение функция write_printer в фрагменте выше не показано. Это сделано специально, поскольку приведённый в фрагменте главный цикл может использоваться с обоими рассматриваемыми методами управления принтером. Вот первая реализация write_printer:
ssize_t write_printer (int fd, const void *ptr, size_t count)
{
    return write (fd, ptr, count);
}
При помощи функции write данные передаются драйверу, работающему в пространстве ядра. Дальше он обрабатывает их по протоколу принтера.

Теперь давайте попробуем пойти более сложным путём! В рассматриваемом примере нет никаких причин, чтобы делать что-либо кроме вызова write, потому что принтер работает по протоколу IEEE 1284. С другой стороны, этот пример не требует наличия драйвера в пространстве пользователя, потому что уже есть один, который работает в пространстве ядра. В целях иллюстрации, попробуем представить, что принтер работает по протоколу, который в Linux ещё не реализован.

Получим альтернативную реализацию write_printer (для краткости обработка ошибок не выполняется):
ssize_t write_printer (int fd, const void *ptr, size_t count)
{
    ssize_t wrote = 0;

    while (wrote < count) {
        unsigned char status, control, data;
        unsigned char mask = (PARPORT_STATUS_ERROR
                              | PARPORT_STATUS_BUSY);
        unsigned char val = (PARPORT_STATUS_ERROR
                              | PARPORT_STATUS_BUSY);
        struct ppdev_frob_struct frob;
        struct timespec ts;

        /* Подождём готовности принтера */
        for (;;) {
            ioctl (fd, PPRSTATUS, &status);

            if ((status & mask) == val)
                break;

            ioctl (fd, PPRELEASE);
            sleep (1);
            ioctl (fd, PPCLAIM);
        }

        /* Задаём линии данных */
        data = * ((char *) ptr)++;
        ioctl (fd, PPWDATA, &data);

        /* Немного подождём */
        ts.tv_sec = 0;
        ts.tv_nsec = 1000;
        nanosleep (&ts, NULL);

        /* Стробирующий импульс */
        frob.mask = PARPORT_CONTROL_STROBE;
        frob.val = PARPORT_CONTROL_STROBE;
        ioctl (fd, PPFCONTROL, &frob);
        nanosleep (&ts, NULL);

        /* Конец импульса */
        frob.val = 0;
        ioctl (fd, PPFCONTROL, &frob);
        nanosleep (&ts, NULL);

        wrote++;
    }

    return wrote;
}
Чтобы продемонстрировать интерфейс ppdev слегка подробнее, приведём небольшой фрагмент кода, который предназначен для имитации протокола принтера со стороны принтера.
for (;;)
    {
      int irqc;
      int busy = nAck | nFault;
      int acking = nFault;
      int ready = Busy | nAck | nFault;
      char ch;

      /* Задаём управляющие линии на случай прерывания */
      ioctl (fd, PPWCTLONIRQ, &busy);

      /* Теперь мы готовы */
      ioctl (fd, PPWCONTROL, &ready);

      /* Ждём прерывания */
      {
        fd_set rfds;
        FD_ZERO (&rfds);
        FD_SET (fd, &rfds);
        if (!select (fd + 1, &rfds, NULL, NULL, NULL))
          /* Сигнал получен? */
          continue;
      }

      /* На линиях управления выставляется сигнал "занято" */

      /* Читаем данные */
      ioctl (fd, PPRDATA, &ch);

      /* Очищаем прерывание */
      ioctl (fd, PPCLRIRQ, &irqc);
      if (irqc > 1)
        fprintf (stderr, "Аххх! Потеряно %d прерываний!\n",
         irqc - 1);

      /* Подтверждаем его */
      ioctl (fd, PPWCONTROL, &acking);
      usleep (2);
      ioctl (fd, PPWCONTROL, &busy);

      putchar (ch);
    }
А вот пример (тоже без обработки ошибок), который демонстрирует, как читать данные из порта в режиме ECP, с необязательным начальным согласованием режима ECP.
{
      int fd, mode;
      fd = open ("/dev/parport0", O_RDONLY | O_NOCTTY);
      ioctl (fd, PPCLAIM);
      mode = IEEE1284_MODE_ECP;
      if (negotiate_first) {
        ioctl (fd, PPNEGOT, &mode);
        /* PPSETMODE не требуется */
      } else {
        ioctl (fd, PPSETMODE, &mode);
      }

      /* Теперь делаем с fd всё, что нужно */
      close (0);
      dup2 (fd, 0);
      if (!fork()) {
        /* Потомок */
        execlp ("cat", "cat", NULL);
        exit (1);
      } else {
        /* Родитель */
        wait (NULL);
      }

      /* Ну вот и закончили */
      ioctl (fd, PPRELEASE);
      close (fd);
    }

воскресенье, 14 апреля 2019 г.

Подсистема параллельного порта Linux 2.4 - часть 1, обзор

Перевод: The Linux 2.4 Parallel Port Subsystem
Автор: Тим Во (Tim Waugh)

Цели проекта

Задачи

Поддержка параллельного порта в Linux впервые появилась вместе с драйвером строчного принтера - lp. Драйвер принтера - это специальное символьное устройство, которое (в Linux 2.0) поддерживает запись через write, а также просмотр настроек и статистики через ioctl.

Драйвер принтера может использоваться на любом компьютере, который оснащён параллельным портом, совместимым с IBM PC. Поскольку некоторые архитектуры оснащаются параллельными портами, которые не совместимы с портами PC, для поддержки параллельных портов Amiga и Atari были написаны другие варианты драйвера принтера.

Когда был выпущен привод Iomega Zip и для него был написан драйвер, появилась очевидная проблема. Привод Zip - это устройство для параллельного порта, которое оснащено своим собственным параллельным портом. Он был спроектирован так, чтобы находиться между компьютером и принтером: принтер подключался к приводу Zip, а привод Zip подключался к компьютеру.

Проблема в том, что хотя принтер и привод Zip поддерживаются, в любой момент времени определённый порт может использоваться только одним из них. Одновременно присутствовать в ядре может только один из двух драйверов. Так происходит потому, что оба драйвера пытаются управлять одним и тем же оборудованием - параллельным портом. Когда драйвер принтера инициализируется, он должен вызвать функцию check_region, чтобы убедиться, что область ввода-вывода, соответствующая параллельному порту, свободна. Затем драйвер должен вызвать функцию request_region, чтобы получить доступ к этой области. Привод Zip использует тот же механизм. Какой бы из драйверов ни был инициализирован первым, он получит к параллельному порту исключительный доступ.

Единственный способ обойти эту проблему - убедиться что оба драйвера доступны в виде загружаемых модулей ядра. Чтобы использовать принтер, нужно загрузить модуль с драйвером принтера. А затем, чтобы воспользоваться приводом Zip, нужно выгрузить модуль с драйвером принтера и загрузить модуль с драйвером Zip.

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

Приводы Zip - это не единственные устройства, из-за которых в Linux возникают подобные проблемы. Существуют и другие устройства со сквозными портам. Например, приводы CD-ROM, подключаемые к параллельному порту. Также существуют принтеры, которые докладывают о собственном состоянии по линиям данных, а не при помощи выделенных для этих целей линий. У таких принтеров могут быть команды, которые просят принтер вернуть отчёт о полном количестве напечатанных им страниц, или об объёме свободной памяти, или сообщить, закончился ли тонер и т.п. Драйвер принтера изначально не предоставлял никаких возможностей для чтения этой информации (однако Карстен Гросс (Carsten Gross) добавил в ядро 2.2 поддержку режима чтения полубайтов).

IEEE выпустил стандарт под названием IEEE 1284, в котором описана существующая практика взаимодействия с параллельным портом в различных режимах. Это такие режимы как: режим совместимости, полубайтовый режим, байтовый режим, ECP и EPP. Новые устройства часто используют более продвинутые режимы передачи (ECP и EPP). В Linux 2.0 драйвер принтера поддерживал только режим совместимости (то есть обычный протокол принтера) и полубайтовый режим.

Решения

Код parport в Linux 2.2 был спроектирован так, чтобы решить поставленные задачи: учесть архитектурные различия между параллельными портами, сделать возможным совместный доступ к порту для устройств со сквозными портами и реализовать полную поддержку режимов передачи IEEE 1284.

В подсистеме parport имеется два слоя, только один из которых напрямую взаимодействует с оборудованием. Другой слой реализует совместный доступ и режимы передачи IEEE 1284. Таким образом, поддержка параллельных портов архитектурно реализуется в виде модуля, который регистрируется в обобщённом слое совместного доступа.

Модель совместного доступа, предоставляемая подсистемой parport, является разновидностью исключительного доступа. Драйвер устройства, например - принтера, сначала должен запросить слой parport разрешить доступ к порту, и только после получения доступа он сможет использовать порт. Когда драйвер завершит «транзакцию», он может сообщить слою parport, что желает освободить порт для использования драйвером другого устройства.

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

Эта техника опирается на соответствующие «специальные сигналы», невидимые для устройств, которые не наблюдают за ними. Чаще всего это смена сигналов данных без изменения управляющих сигналов. В IEEE 1284.3 описан стандартный протокол для формирования цепочек устройств на параллельных портах.

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

Стандартные режимы передачи

«Стандартные» режимы передачи, используемые на параллельном порту, «определены» в документе IEEE 1284. На самом деле стандарт просто описывает сложившуюся практику и документирует протоколы (и варианты протоколов), которые были общепринятыми в течение некоторого времени.

Оригинальные определения назначения линий были сформированы Centronics Data Computer Corporation, но описан был только интерфейс со стороны принтера.

В начале 1980-х наиболее широко распространилась реализация интерфейса со стороны компьютера от IBM. Вновь появлявшиеся принтеры заявляли о совместимости с Centronics, но их совместимость с Centronics взаимно отличалась несколькими способами.

В результате, когда в 1994 году был опубликован IEEE 1284, всё что фактически можно было сделать - это задокументировать различные протоколы, которые использовались принтерами (около шести разных вариантов).

В дополнение к протоколу, который используется для общения с Centronics-совместимыми принтерами, IEEE 1284 определяет другие протоколы, которые используются для однонаправленной передачи данных от периферийного устройства к компьютеру (полубайтовый и байтовый) и для быстрой двунаправленной передачи данных (ECP и EPP).

Структура


Подсистема совместного доступа

В подсистеме parport находится механизм совместного доступа (см. drivers/parport/share.c). Модуль parport отвечает за отслеживание: портов, имеющихся в системе, драйверов устройств, заинтересованных в информации о новых портах, доступа устройств к портам (какие порты доступны для использования, а какие порты в настоящее время используются определёнными драйверами).

parport и его переопределение

Обобщённый код совместного доступа parport не взаимодействует с оборудованием параллельного порта напрямую. Вместо него это делают «низкоуровневые» драйверы parport. Функция низкоуровневого драйвера parport: обнаружить параллельные порты, зарегистрировать их в коде совместного доступа и предоставить список доступных функций для каждого из портов.

Самые базовые функции, которые должны быть предоставлены - это функции чтения линий состояния, задания управляющих линий и задания линий данных. Также могут предоставляться функции для настройки направления линий данных. Обычно линии данных находятся в «прямом» направлении (так что ими управляет компьютер), но некоторые порты позволяют переключить линии данных в «обратный» режим (чтобы ими управляло периферийное устройство). И наконец, могут предоставляться функции для чтения линий данных, находящихся в обратном режиме.

Режимы передачи IEEE 1284

Функции передачи данных располагаются поверх механизма совместного доступа, но тоже являются частью модуля parport. Эти функции предоставляются для драйверов устройств, чтобы они пользовались ими как библиотечными подпрограммами. Поскольку эти функции передачи предоставляются обобщённой подсистемой parport, они должны использовать «наименьший общий знаменатель» из функций доступа: они могут задавать управляющие линии, проверять линии состояния и использовать линии данных. У некоторых параллельных портов линии данных могут быть только заданы и невозможно прочитать их текущее состояние, а у других портов доступ к регистрам данных приводит к изменению состояния управляющих линий. В таких случаях функции передачи IEEE 1284 делают всё возможное для правильной работы. В некоторых случаях оказывается физически невозможно использовать отдельные из режимов передачи IEEE 1284.

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

pardevice и parport_driver

Когда инициализируется драйвер параллельного порта (такой как lp), он сообщает слою совместного доступа о себе при помощи parport_register_driver. Информация помещается в структуру parport_driver, которая размещается в связанном списке. Информация в структуре parport_driver представляет собой совокупность указателей на функции, которые может вызывать драйвер устройства на параллельном порту.

Во время инициализации, низкоуровневый драйвер порта при помощи функции parport_register_port сообщает слою совместного доступа обо всех портах, которые он нашёл, а слой совместного доступа создаёт структуру parport для каждого из них. Каждая структура parport содержит (среди прочего) указатель на структуру parport_operations, которая содержит список указателей на функции для различных операций, которые могут быть произведены над портом. Если вам привычнее мыслить в объектно-ориентированной парадигме, вы можете думать о структуре parport как об объекте параллельного порта. Структуры parport связаны друг с другом через связанный список, головой которого является portlist (в drivers/parport/share.c).

Как только порт зарегистрируется, низкоуровневый драйвер анонсирует его. Функция parport_announce_port проходится по списку драйверов устройств на параллельных портах (структура parport_drivers), вызывая функцию attach каждого из них (которая может блокироваться).

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

Драйверы устройств также могут отменить последствия собственной регистрации при помощи функции parport_unregister_driver.

Программный интерфейс IEEE 1284.3

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

Во время написания, когда IEEE 1284.3 ещё не был опубликован, в черновике уже был описан протокол для формирования цепочек устройств и мультиплексирования, а также был предложен программный интерфейс для их использования. Этот интерфейс (или большая его часть) был реализован в коде parport в Linux.

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

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

Сочетая возможность формирования цепочек пяти устройств с возможностью мультиплексирования одного параллельного порта между четырьмя портами, получаем потенциальную возможность иметь до двадцати периферийных устройств, подключенных к одному параллельному порту!

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

Каждое устройство в системе получает номер в процессе инициализации (их назначает функция parport_daisy_init). Преобразовать номер устройства в его координаты и наоборот можно при помощи функций parport_device_num и parport_device_coords.
#include <parport.h>h;
    
int parport_device_num(int parport, int mux, int daisy);

int parport_device_coords(int devnum, int *parport, int *mux, int *daisy);
Любое периферийное устройство на параллельном порту может быть подключено напрямую или опосредованно к параллельному порту системы, но не может иметь адрес в цепочке, если оно не знает о цепочке, и не может быть подключено сквозь мультиплексор, если мультиплексора нет. В этих случаях используется специальное значение координаты -1.

Для поиска устройств по их идентификатору IEEE 1284 - Device ID, предоставляются две функции: parport_find_device и parport_find_class.
#include <parport.h>
    
int parport_find_device(const char *mfg, const char *mdl, int from);

int parport_find_class(parport_device_class cls, int from);
Эти функции принимают критерии поиска и номер устройства, и возвращают номер другого устройства. Они проходятся по списку обнаруженных устройств, пока не найдут подходящее требованиям и возвращают номер устройства (или -1, если таких устройств больше нет). Они начинают свой поиск с устройства в списке, следующим за указанным номером (другими словами - с from+1).

Обзор драйверов устройств

Этот раздел написан с точки зрения программиста, который собирается написать драйвер принтера, сканера или какого-то другого устройства, подключаемого к параллельному порту. В разделе объясняется, как использовать интерфейс parport для поиска параллельных портов, их использования и совместного доступа с драйверами других устройств.

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

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

Итак, первая задача драйвера устройства - сообщить parport о том, что он хочет узнать о параллельных портах, имеющихся в системе. Чтобы сделать это, драйвер использует функцию parport_register_device:
#include <parport.h>

struct parport_driver {
        const char *name;
        void (*attach) (struct parport *);
        void (*detach) (struct parport *);
        struct parport_driver *next;
};
   
int parport_register_driver(struct parport_driver *driver);
Другими словами, драйвер устройства передаёт в parport указатели на несколько функций, а parport вызывает attach для каждого обнаруженного порта (и detach для каждого порта, который пропал - да, такое тоже может случиться).

Далее драйвер устройства может сообщить parport, что предполагает, что к порту подключено устройство, которым он сможет управлять. Обычно это происходит в функции драйвера attach и это делается при помощи функции parport_register_device:
#include <parport.h>
   
struct pardevice *parport_register_device(struct parport *port, const char *name, int (*pf) (void *), void (*kf) (void *), void (*irq_func) (int, void *, struct pt_regs *), int flags, void *handle);
Драйвер получает порт либо через параметр функции attach, либо вызвав функцию parport_enumerate (в настоящее время устарела), которая ищет порт в списке обнаруженных параллельных портов. Для этого лучше воспользоваться функциями parport_find_number и parport_find_base, которые находят порты соответственно по номеру и базовому адресу ввода-вывода.
#include <parport.h>
   
struct parport *parport_find_number(int number);

#include <parport.h>
   
struct parport *parport_find_base(unsigned long base);
Следующие три параметра - pf, kf и irq_func - являются указателями на дополнительные функции. Это функции, которые будут вызываться в различных обстоятельствах и они всегда принимают дескриптор в качестве одного из своих параметров.

pf - обработчик вежливой просьбы освободить порт. Выполняется, когда драйвер владеет доступом к порту, но к этому порту хочет получить доступ драйвер другого устройства. Если драйвер желает освободить порт, то он должен вернуть ноль и тогда порт будет им освобождён. В таком случае не требуется выполнять вызов parport_release. Если pf будет вызван в неподходящее для освобождения порта время, то драйвер должен вернуть ненулевое значение и никаких действий не будет выполнено. Вежливый драйвер постарается отпустить порт как можно раньше, при первой возможности после вежливой просьбы.

kf - обработчик сообщения о свободном порте. Выполняется, когда порт можно затребовать в исключительный доступ. Если драйвер хочет затребовать доступ к порту, то вызов parport_claim гарантированно завершится удачно внутри обработчика сообщения о свободном порте. Если драйвер хочет затребовать доступ к порту, он должен сделать это. В противном случае никаких действий предпринимать не требуется.

Вызов irq_func выполняется, как и следует из его названия, когда на параллельном порту произошло прерывание. Но это не единственный код, который обрабатывает прерывание. Последовательность обработки прерывания начинается с вызова request_irq, который обрабатывает низкоуровневый драйвер. Сначала он выполняет действия, которые необходимо сделать конкретно для данного типа аппаратного обеспечения параллельного порта (для портов типа PC ничего особого делать не требуется). Затем он сообщает о прерывании коду IEEE 1284, который реагирует на событие IEEE 1284 в соответствии с текущей фазой IEEE 1284. И наконец, вызывается функция irq_func.

Ни одна из этих функций не должна блокироваться.

Флаги flags сообщают parport полезные требования или подсказки. Одно из полезных значений (отличное от нуля, которое обычно используется) - это PARPORT_DEV_EXCL. Смысл этого флага в том, чтобы запросить исключительный доступ на всё время - как только драйвер однажды успешно выполнил parport_register_device с этим флагом, драйвер ни одного другого устройства не сможет зарегистрировать устройство на этом порту (конечно, до тех пор, пока первый драйвер не отменит регистрацию своего устройства).

Флаг PARPORT_DEV_EXCL предотвращает совместное использование порта и должен вызываться только если совместное использование порта драйвером другого устройства невозможно и может привести к некорректному поведению. Старайтесь избегать!

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

Функция parport_open аналогична parport_register_device, а parport_close аналогична parport_unregister_device. Разница в том, что parport_open принимает номер устройства, а не указатель на структуру parport.
#include <parport.h>
   
struct pardevice *parport_open(int devnum, const char *name, int (*pf) (void *), int (*kf) (void *), int (*irqf) (int, void *, struct pt_regs *), int flags, void *handle);

void parport_close(struct pardevice *dev);

struct pardevice *parport_register_device(struct parport *port, const char *name, int (*pf) (void *), int (*kf) (void *), int (*irqf) (int, void *, struct pt_regs *), int flags, void *handle);

void parport_unregister_device(struct pardevice *dev);
Предполагается, что эти функции используются в процессе инициализации драйвера, когда драйвер ищет поддерживаемые им устройства, как показано в следующем фрагменте кода:
int devnum = -1;
while ((devnum = parport_find_class (PARPORT_CLASS_DIGCAM,
                                     devnum)) != -1) {
    struct pardevice *dev = parport_open (devnum, ...);
    ...
}
Как только драйвер устройства зарегистрировал своё устройство и предоставил указатель на структуру pardevice, скорее всего он попытается связаться с предполагаемым устройством. Чтобы сделать это, нужно затребовать доступ к порту.
#include <parport.h>
   
int parport_claim(struct pardevice *dev);

int parport_claim_or_block(struct pardevice *dev);

void parport_release(struct pardevice *dev);
Чтобы затребовать доступ к порту, воспользуйтесь parport_claim или parport_claim_or_block. Первая функция не блокируется, поэтому может использоваться в контексте прерывания. Если parport_claim завершилась успешно, то она вернёт ноль и порт будет доступен для использования. Она может завершиться ошибкой (вернёт не ноль), если порт используется другим драйвером устройства и этот драйвер не хочет отказываться от управления портом.

Другая функция, parport_claim_or_block, заблокируется, если необходимо подождать освобождения порта. Если она засыпала, то вернёт 1. Если же засыпать не понадобилось, то она вернёт 0. Если она завершится ошибкой, она вернёт отрицательный код ошибки.

Когда общение с устройством будет завершено, можно освободить порт, чтобы другие драйверы смогли связаться через порт со своими устройствами. Функция parport_release не может завершиться ошибкой, но она не должна вызываться, если доступ к порту не был затребован. Аналогично, не нужно пытаться затребовать доступ к порту, если доступ уже был получен.

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

Функции parport_yield и parport_yield_blocking предназначены для отметки мест драйвера, в которых другие драйверы могут затребовать доступ к порту для связи со своими устройствами. Уступание доступа к порту аналогично его освобождению и повторному затребованию, но оно более эффективно, потому что ничего не происходит, если порт не нужен другим устройствам. На деле ничего не происходит даже тогда, когда другие устройства ожидают получения доступа, но текущее устройство ещё находится внутри кванта времени. По умолчанию квант времени составляет полсекунды, но он может быть изменён через файловую систему /proc.
#include <parport.h>
   
int parport_yield(struct pardevice *dev);

int parport_yield_blocking(struct pardevice *dev);
Первая из функций, parport_yield, не блокируется, но может завершиться ошибкой. Возвращаемое значение у parport_yield точно такое же, как и у parport_claim. Вариант с блокировкой, parport_yield_blocking, возвращает такое же значение, как и parport_claim_or_block.

После получения доступа к порту, драйвер устройства может использовать функции из структуры parport_operations, указатель на которую содержится в структуре parport. Например:
port->ops->write_data (port, d);
Некоторые из этих операций являются «сокращениями». Например, parport_write_data является аналогом указанной выше строчки, но может оказаться чуть быстрее (это макрос, который в некоторых случаях может избегать косвенных обращений через port и ops).

Драйверы порта

Если подытожить, то:
  • Драйвер устройства регистрируется в parport.
  • Низкоуровневый драйвер находит параллельный порт и регистрирует его в parport (два этих события происходят в произвольном порядке). Эта регистрация создаёт структуру parport, которая связывается со списком известных портов.
  • parport вызывает функцию attach каждого из зарегистрированных драйверов устройств, передавая указатель на новую структуру parport.
  • Драйвер устройства получает дескриптор из parport, который использует в parport_claim/release. Этот дескриптор имеет вид указателя на структуру pardevice, представляющую конкретное устройство на параллельном порту, и его можно получить при помощи parport_register_device.
  • Драйвер устройства требует доступ к порту при помощи функции parport_claim (или parport_claim_or_block).
  • Впоследствии драйвер использует порт. Когда драйвер завершает использование порта, он его освобождает.
В свою очередь, назначение низкоуровневых драйверов заключается в том, чтобы обнаружить порты и предоставить методы для работы с ним (то есть предоставить реализации операций в структуре parport_operations).

Более полное описание назначения каждой из операций доступно в файле Documentation/parport-lowlevel.txt.

Драйвер принтера

Драйвер принтера lp - это специальное символьное устройство и клиент parport. Как драйвер специального символьного устройства, он при помощи register_chrdev регистрирует структуру file_operations с заполненными указателями write, ioctl, open и release. Как клиент parport, он регистрирует структуру parport_driver при помощи parport_register_driver, так что parport узнаёт, что нужно вызвать lp_attach при обнаружении нового параллельного порта (и lp_detach при его пропадании).

Функциональность консоли параллельного порта также реализована в drivers/char/lp.c, но он здесь рассматриваться не будет (потому что он очень прост).

Процесс инициализации драйвера прост для понимания (см. lp_init). lp_table - это массив структур, который содержит информацию об определённом устройстве (с ним, например, связана структура pardevice). Прежде всего этот массив инициализируется осмысленными значениями.

Затем драйвер принтера вызывает register_chrdev, передавая указатель на lp_fops, который содержит указатели на функции open, write и т.п., реализующие драйвер принтера. В этом отношении он похож на любой драйвер специального символьного устройства.

После успешной регистрации себя в качестве драйвера специального символьного устройства, драйвер принтера при помощи функции parport_register_driver регистрируется как клиент parport. Он передаёт указатель на следующую структуру:
static struct parport_driver lp_driver = {
        "lp",
        lp_attach,
        lp_detach,
        NULL
};
Функция lp_detach не очень интересна (она ничего не делает). Немного интересна функция lp_attach. Происходящее в ней зависит от того, какие параметры указал пользователь. Если параметры не указаны, то драйвер принтера использует каждый обнаруженный порт. Если пользователь указал параметр «auto», то будут использоваться только те порты, на которых обнаружена строка, идентифицирующая принтер. Если же пользователь указал список пробуемых номеров параллельных портов, то использоваться будут только они.

Для каждого порта, который драйвер принтера желает использовать (см. lp_register), он вызывает parport_register_device и сохраняет указатель на результирующую структуру pardevice в lp_table. Если пользователь попросил сбросить принтер, тогда выполняется сброс.

Другой интересной частью драйвера принтера, с точки зрения parport, является lp_write. С помощью этой функции процесс, работающий в пространстве пользователя, передаёт в драйвер принтера данные, которые он хочет напечатать. А драйвер передаёт их в соответствующий код parport.

Функции parport, которые он использует, мы ещё не видели - это функции parport_negotiate, parport_set_timeout и parport_write. Эти функции являются частью реализации IEEE 1284.

В фазе согласования протокол IEEE 1284 работает следующим образом: компьютер сообщает периферийному устройству режим передачи, который хочет использовать, а периферийное устройство либо соглашается, либо отказывается. Если режим отклонён, то компьютер пытается предложить другой режим. Как только периферийное устройство принимает один из режимов передачи, можно начинать передачу данных в этом режиме.

Драйвер принтера желает использовать режим передачи, который в IEEE 1284 носит название «совместимого». Функция для запроса определённого режима называется parport_negotiate.
#include <parport.h>
   
int parport_negotiate(struct parport *port, int mode);
Параметр mode - это именованная константа, которая соответствует режиму IEEE 1284. В данном случае это константа IEEE1284_MODE_COMPAT. (Совместимый режим немного отличается от других режимов: пока не запрошен какой-то определённый режим, этот режим выбран по умолчанию.)

Теперь вернёмся к lp_write. Прежде всего, драйвер требует доступ к параллельному порту при помощи parport_claim_or_block. В этот момент драйвер может уснуть, ожидая когда другой драйвер (например - драйвер привода Zip) освободит порт. Затем драйвер переключается в совместимый режим при помощи parport_negotiate.

Основная работа выполняется в цикле записи. В частности, данные в порт передаются такой строчкой:
written = parport_write (port, kbuf, copy_size);
Функция parport_write пишет данные в периферийное устройство с использованием текущего выбранного режима (в данном случае используется совместимый режим). Функция возвращает количество успешно записанных байтов:
#include <parport.h>
   
ssize_t parport_write(struct parport *port, const void *buf, size_t len);

ssize_t parport_read(struct parport *port, void *buf, size_t len);
(parport_read читает данные из параллельного порта, но работает только в режимах, в которых возможна обратная передача. И конечно, parport_write тоже работает только в режимах, в которых возможна прямая передача.)

Указатель buf должен находиться памяти ядра, а параметр len, очевидно, содержит количество передаваемых данных.

На деле parport_write вызывает соответствующую функцию передачи блока из структуры parport_operations:
struct parport_operations {
        [...]

        /* Чтение/запись блока */
        size_t (*epp_write_data) (struct parport *port,
                                  const void *buf,
                                  size_t len, int flags);
        size_t (*epp_read_data) (struct parport *port,
                                 void *buf, size_t len,
                                 int flags);
        size_t (*epp_write_addr) (struct parport *port,
                                  const void *buf,
                                  size_t len, int flags);
        size_t (*epp_read_addr) (struct parport *port,
                                 void *buf, size_t len,
                                 int flags);

        size_t (*ecp_write_data) (struct parport *port,
                                  const void *buf,
                                  size_t len, int flags);
        size_t (*ecp_read_data) (struct parport *port,
                                 void *buf, size_t len,
                                 int flags);
        size_t (*ecp_write_addr) (struct parport *port,
                                  const void *buf,
                                  size_t len, int flags);

        size_t (*compat_write_data) (struct parport *port,
                                     const void *buf,
                                     size_t len, int flags);
        size_t (*nibble_read_data) (struct parport *port,
                                    void *buf, size_t len,
                                    int flags);
        size_t (*byte_read_data) (struct parport *port,
                                  void *buf, size_t len,
                                  int flags);
};
Код передачи в параллельный порт будет ожидать передачи данных лишь в течение определённого времени, которое можно задать при помощи функции parport_set_timeout, которая возвращает предыдущий таймаут:
#include <parport.h>
   
long parport_set_timeout(struct pardevice *dev, long inactivity);
Этот таймаут относится к определённому устройству и он восстанавливается при вызове parport_claim.

Следующая рассматриваемая функция относится к тем, которые позволяют выполнять чтение из /dev/lp0: lp_read. Она короткая, как и lp_write.

Семантика чтения из устройства строчного принтера такова:
  • Переключиться в полубайтовый режим.
  • Пытаться читать данные из периферийного устройства с использованием полубайтового режима до тех пор, пока либо не заполнится предоставленный пользователем буфер, либо периферийное устройство не сообщит об окончании данных.
  • Если имеются данные, то остановиться и вернуть их.
  • В противном случае произошла попытка прочитать данные, но их не оказалось. Если пользователь открыл устройство с флагом O_NONBLOCK, то выполняем возврат. В противном случае ожидаем, пока не произойдёт прерывание на порту (или не будет достигнут таймаут).

воскресенье, 7 апреля 2019 г.

Параллельный порт

Источник: Parallel port
Автор: Маури Марковиц (Maury Markowitz) и другие
Автор перевода на русский язык: Владимир Ступин
Лицензия: Creative Commons Attribution-ShareAlike License

Параллельный порт - это разновидность компьютерного интерфейса для подключения периферийных устройств. Название указывает на способ передачи данных. Параллельные порты отправляют несколько бит данных за раз, параллельно, в отличие от последовательных интерфейсов, которые отправляют по одному биту за раз. Для этого в кабеле и разъёме параллельного порта имеется несколько линий данных - больше чем в последовательных портах, в которых имеется только одна линия данных.

Существует множество типов параллельных портов, но понятие чаще всего ассоциируется в портом принтера или портом Centronics, который имелся на большинстве персональных компьютеров с 1970-х до 2000-х годов. Он был фактическим промышленным стандартом в течение многих лет и впоследствии был стандартизован как IEEE 1284 в конце 1990-х, где были определены двунаправленные версии порта EPP (Enhanced Parallel Port - улучшенный параллельный порт) и ECP (Extended Capability Port - расширенный параллельный порт). В наши дни интерфейс параллельного порта практически не используется из-за распространения устройств USB (Universal Serial Bus - универсальная последовательная шина), а также сетевой печати на принтеры, подключенные через Ethernet и Wi-Fi.

Изначально на компьютерах, совместимых с IBM PC, интерфейс параллельного порта был известен как Parallel Printer Adapter - адаптер параллельного принтера. Он был спроектирован для работы с принтерами, использующими расширенный IBM 8-битный набор символов ASCII для печати текстов, но также мог использоваться для подключения других периферийных устройств, таких как, например, графические принтеры.

История

Centronics

Доктор Эн Вонг (Dr. An Wang), Роберт Ховард (Robert Howard) и Прентис Робинсон (Prentice Robinson) начали разработку недорогого принтера в Centronics - дочерней компании Wang Laboratories, которая производила специализированные компьютерные терминалы. Принтер использовал принцип матричной печати, был оснащён печатающей головкой, содержащий семь иголок, выстроенных в вертикальный ряд и соединённых с соленоидами. Когда ток протекал через соленоиды, иголки выдвигались вперёд, ударяя по бумаге и оставляя точку. Для создания полного изображения символа печатающая головка подавала ток на указанные иголки для создания одного вертикального шаблона, затем печатающая головка сдвигалась немного правее и процесс повторялся. В первоначальной модели изображение символа обычно печаталось в виде матрицы размерами семь точек в высоту и пять точек в ширину. В модели "A" использовалась печатающая головка с девятью иголками, которая формировала изображения символов размерами 9 на 7.

Оставалась задача отправить на принтер данные в кодировке ASCII. Хотя последовательный порт требовал минимального количества выводов и проводов, он требовал, чтобы устройство помещало поступающие данные в буфер бит за битом и превращало их обратно в многобитные значения. Параллельный порт упрощал эту задачу, так как значение в кодировке ASCII целиком умещалось на выводах. В дополнение к семи выводам для данных, системе также требовалось несколько выводов для управления и общий провод. К счастью, у Вонга имелся запас из 20000 36-контактных микроразъёмов ленточного типа, которые первоначально должны были использоваться в одной из их ранних моделей калькуляторов. Для интерфейса требовался только 21 из этих контактов, а оставшиеся были подсоединены к общему проводу или вовсе не использовались. Разъём стал так часто ассоциироваться с Centronics, что теперь общеизвестен как "разъём Centronics".

Принтер Centronics Model 101, оснащённый таким разъёмом, был выпущен в 1970 году. Компьютер отправлял символы в кодировке ASCII на принтер через 7 или 8 выводов для данных, подтяжка которых к +5 Вольтам соответствовала 1. Когда данные были готовы, компьютер подтягивал вывод STROBE к напряжению 0 Вольт. Принтер отвечал высоким уровнем напряжения на линии BUSY, печатая символ, а затем возвращал BUSY снова к низкому уровню напряжения. После этого компьютер мог отправить следующий символ. Другие действия выполнялись при получении по линиям данных управляющих символов, таких как CR или EOF. Компьютер также мог заставить автоматически начинать печать с новой строки, выставляя высокий уровень напряжения на линии AUTOFEED, и поддерживая его на этом уровне. Компьютер должен был внимательно следить за линией BUSY, чтобы не подавать данные на принтер слишком быстро, особенно во время длительных операций, таких как подача бумаги.

Интерфейс со стороны принтера вскоре превратился в промышленный стандарт де факто, но со стороны компьютера производители использовали различные разъёмы, поэтому требовались различные кабели. Например, в продукции NCR использовался 36-контактный микроразъём ленточного типа с обеих сторон, на ранних системах VAX использовался разъём DC-37, в продукции Texas Instruments использовался 25-контактный разъём на краю печатной платы, а Data General использовали 50-контактный микроразъём ленточного типа. Когда IBM реализовали параллельный интерфейс в IBM PC, они использовали разъём DB-25F со стороны компьютера, создав таким образом ныне известный параллельный кабель с разъёмом DB-25M на одном конце и 36-контактным микроразъёмом ленточного типа с другой стороны.

Теоретически, порт Centronics мог передавать данные со скоростью 75000 символов в секунду. Это было намного быстрее, чем скорость принтера, который в среднем мог печатать около 160 символов в секунду, из-за чего большую часть времени порт простаивал. Производительность определялась тем, насколько быстро компьютер ответит на сигнал BUSY от принтера, который с помощью этого сигнала запрашивает новые данные. Для улучшения производительности в принтер стали встраивать буферы, чтобы компьютер мог отправлять данные быстрее - очередями. Это не только снизило (или вовсе устранило) задержки из-за ожидания следующего символа от компьютера, но также освободило компьютер для других операций, без снижения производительности. Производительность в последующем улучшалась использованием буферов для хранения нескольких строк и печатью в обоих направлениях, чтобы устранить задержки, требовавшиеся для перемещения печатающей головки к левому краю страницы. В случае с принтерами Centronics моделей 102 и 308, эти изменения позволили поднять производительность более чем в два раза.

IBM

В 1981 году IBM выпустила IBM Personal Computer, который содержал разновидность интерфейса Centronics. С этим компьютером можно было использовать только принтеры с логотипом IBM (это были принтеры Epson, выпускавшиеся под брендом IBM). IBM приняла в качестве стандарта параллельный кабель с разъёмом DB25F со стороны компьютера и с 36-контактным разъёмом Centronics со стороны принтера. Вскоре производители стали выпускать принтеры, которые были совместимы с обеими реализациями стандарта - от Centronics и от IBM.

Оригинальный адаптер параллельного порта IBM PC был спроектирован в 1981 году и поддерживал двунаправленную передачу 8 бит данных. Это позволяло использовать порт для других целей, не только для вывода на принтер. Поскольку устройство с каждой стороны могло осуществлять запись на линии данных, требовалось чтобы и порт со стороны компьютера был двунаправленным. Эта функция использовалась редко и была удалена в последующих выпусках аппаратного обеспечения. Годами позже, в 1987 году, IBM вновь внедрила двунаправленный интерфейс в компьютерах серии IBM PS/2, где функция могла быть включена или выключена, чтобы достичь совместимости с приложениями, не ожидавшими, что порт принтера может быть двунаправленным.

Bi-Tronics

По мере роста рынка принтеров появлялись новые типы механизмов печати. Они часто поддерживали новые функции и статусы ошибок, для которых не было достаточно существующих выводов состояния. Хотя оборудование IBM позволяло передавать данные в обе стороны, воспользоваться этой возможностью было не просто, из-за чего поначалу она не использовалась. По этим причинам в 1992 HP внедрила в свой принтер LaserJet 4 систему Bi-Tronics. В ней использовались существующие контакты статуса ERROR, SELECT, PE и BUSY, которые образовывали полубайт, так что 8-битное значение можно было передать в два этапа. Режим Bi-Tronics, ныне известный как полубайтовый режим, выбирался выставлением высокого уровня напряжения на SELECT, а данные передавались когда компьютер переключал AUTOFEED на низкий уровень напряжения. Другие изменения в процедуре согласования увеличили производительность, подняв её до 400000 символов в секунду при передаче с компьютера на принтер и примерно до 50000 символов в секунду при передаче в обратном направлении. Важным преимуществом системы Bi-Tronics было то, что её целиком можно было реализовать программно и использовать неизменное аппаратное обеспечение - для передачи данных от принтера к компьютеру использовались те же контакты и линии, что и раньше.

EPP и ECP

С появлением новых устройств, таких как сканеры и многофункциональные устройства, требовалась более высокая производительность, которую не могли обеспечить ни Bi-Tronics, ни протокол IBM. Вместо них популярность приобрели два других стандарта. EPP (Enhanced Parallel Port - улучшенный параллельный порт) изначально был определён Zenith Electronics и напоминал концепцию байтового режима от IBM, но отличался в процедуре согласования, позволяя достичь скорости в 2 мегабайта в секунду. ECP (Extended Capability Port - расширенный параллельный порт) фактически является совершенно новым портом в том же физическом корпусе, с поддержкой прямого доступа в память, основанным на шине ISA, и кодированием повторяющихся символов для сжатия данных, что особенно полезно для передачи простых изображений, таких как факсы или чёрно-белые отсканированные изображения. ECP предоставляет производительность до 2,5 мегабайт в секунду в обоих направлениях.

Все эти улучшения стали частью стандарта IEEE 1284. Первое издание от 1994 года включало исходный режим Centronics ("совместимый режим"), полубайтовый и байтовый режимы, а также изменения процедур согласования, которые уже широко использовались. Исходная реализация Centronics требовала переключения BUSY при каждом изменении на любой из линий данных, в то время как IEEE 1284 требует переключения BUSY с каждым принятым символом. Это уменьшило количество переключений BUSY и прерываний обеих сторон. Обновление от 1997 года стандартизировало коды статуса принтера. В 2000 году в стандарт попали режимы EPP и ECP, несколько разъёмов, типов кабелей и метод создания цепочек до восьми устройств на одном порту.

Некоторые компьютеры и принт-серверы могут использовать сигнал STROBE с относительно низким уровнем напряжения или быстрым переключением. Любая из этих проблем может повлечь отсутствие печати, прерывистую печать, пропуск, повтор символов или печать мусорных данных. Некоторые модели принтеров оснащены переключателем или настройкой для установки сигнала BUSY для каждого символа, а другим может потребоваться согласующий адаптер.

Dataproducts

Dataproducts внедряла совершенно другую реализацию параллельного интерфейса для их принтеров. Они использовали разъём DC-37 со стороны компьютера и 50-контактный разъём со стороны принтера - либо DD-50 (иногда ошибочно называемый "DB50") или прямоугольный разъём M-50; M-50 также часто называют Winchester. Параллельные порты Dataproducts существовали в коротком варианте с кабелем длиной до 15 метров и в длинном варианте - с кабелем диной до 150 метров, который использовал разностную сигнализацию. Интерфейс Dataproducts можно было встретить на многих мейнфреймах вплоть до 1990-х годов, а многие производители принтеров в качестве опции предлагали интерфейс Dataproducts.

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

Историческое использование

До появления USB параллельный интерфейс был приспособлен работы с большим количеством различных периферийных устройств, помимо принтеров. Возможно одними из самых ранних устройств, предназначенными для использования с параллельным портом, были аппаратные ключи для защиты от копирования программ. Zip-дисководы и сканеры стали первопроходцами, за которыми последовали внешние модемы, звуковые карты, веб-камеры, геймпады, джойстики, внешние жёсткие диски и приводы CD-ROM. Некоторые из самых ранних мобильных MP3-плееров для записи музыки на устройство требовали подключения к параллельному порту. Существовали адаптеры для работы со SCSI-устройствами через параллельный порт. Другими устройствами, подключаемыми к параллельному порту, были программаторы EPROM и контроллеры оборудования.

Интерфейсы

Большинство компьютеров, совместимых с PC, в 1980-х и 1990-х имело один из трёх портов с интерфейсами, определёнными ниже:
  • Логический параллельный порт 1: порт ввода-вывода 0x3BC, IRQ 7 (обычно в монохромных графических адаптерах)
  • Логический параллельный порт 2: порт ввода-вывода 0x378, IRQ 7 (выделенная карта ввода-вывода или контроллер, встроенный в материнскую плату)
  • Логический параллельный порт 3: порт ввода-вывода 0x278, IRQ 5 (выделенная карта ввода-вывода или контроллер, встроенный в материнскую плату)
Если по адресу 0x3BC не было порта принтера, то для BIOS первым логическим параллельным портом становился второй порт ввода-вывода из списка - 0x378, а вторым логическим параллельным портом становился следующий по списку порт - 0x278 . Иногда порты принтера настраиваются переключателями для совместного использования прерывания несмотря на то, что каждый из них обладает собственным адресом ввода-вывода (то есть в любой момент времени только один из них может управляться прерываниями). В некоторых случаях BIOS поддерживает четвёртый порт, но его базовый адрес у разных производителей значительно отличается. Поскольку зарезервированная запись для четвёртого логического порта принтера в BDA (BIOS Data Area - области данных BIOS) на компьютерах PS/2 и на компьютерах с S3-совместимыми графическими картами используется совместно с другими устройствами, для большинства таких систем требуется специальный драйвер. В DR-DOS 7.02 привязка портов BIOS может изменяться при помощи директив LPT1, LPT2, LPT3 (и не обязательной директивы LPT4) в файле CONFIG.SYS.

Доступ

В системах на основе DOS логические параллельные порты, обнаруживаемые BIOS, доступны под именами LPT1, LPT2 или LPT3 (соответствуют логическим параллельным портам 1, 2 и 3, соответственно). Эти имена произошли от словосочетаний Line Print Terminal - строчный печатающий терминал, Local Print Terminal - локальный печатающий терминал или Line PrinTer - строчный принтер. Подобное соглашение об именовании использовалось в системах ITS, DEC, а также в CP/M и 86-DOS (LST).

В DOS доступ к принтерам на параллельных портах можно получить прямо из командной строки. Например, команда "TYPE C:\AUTOEXEC.BAT > LPT1:" перенаправит содержимое файла AUTOEXEC.BAT в порт принтера. Устройство LPT1 также доступно под псевдонимом PRN. Некоторые операционные системы (например, многопользовательские DOS) позволяли различным образом изменять это фиксированное соответствие. Некоторые версии DOS использовали резидентные драйверы-расширения, предоставляемые командой MODE. В других (в DR-DOS версии 7.02 и выше) пользователи могли изменить соответствие директивой PRN=n в файле CONFIG.SYS. DR-DOS 7.02 также предоставлял встроенную поддержку порт LPT4, если его поддерживал нижележащий BIOS.

В DOS и Windows, даже в Windows XP, в качестве имён файлов нельзя использовать имена PRN, CON, AUX и несколько других имён. В Windows 95 и 98 существовала даже уязвимость, вызывавшая крах компьютера, если пользователь вводил в адресной строке Windows Explorer "C:\CON\CON", "C:\PRN\PRN" или "C:\AUX\AUX". В Microsoft выпустили исправление, но только что установленные операционные системы Windows 95 и 98 по-прежнему содержат эту ошибку.

Специальная команда "PRINT" приводит к тому же результату. Во многих случаях Microsoft Windows по-прежнему ссылается на порты по этим именам, хотя часто это бывает скрыто.

В SCO UNIX и Linux первый параллельный порт доступен через файловую систему как /dev/lp0. Устройства IDE в Linux могут использовать драйвер paride (parallel port IDE - IDE через параллельный порт).

Известные устройства

Использование в настоящее время

Параллельный порт был заменён USB и компьютерными сетями, позволяющими соединяться как с принтерами, так и с другими устройствами.

Многие производители персональных компьютеров и ноутбуков считают параллельный порт устаревшим и больше не добавляют его. Компьютеры меньших размеров не имеют достаточно места для разъёма параллельного порта. Для использования принтеров, поддерживающих только параллельный порт, на компьютерах, содержащих только USB-порты, существуют адаптеры USB-параллельный порт. Это карты PCI (и PCI-express), содержащие параллельные порты. Существуют также принт-серверы, позволяющие получить доступ к интерфейсу параллельного порта через сеть. Микросхемы USB-в-EPP также позволяют использовать устройства, не являющиеся принтерами, в современных компьютерах без параллельного порта.

Для радиолюбителей параллельный порт всё ещё является простейшим способом подключения внешних схем. Он быстрее, чем другие устаревшие порты (последовательный порт), не требует преобразования из последовательного кода в параллельный, и требует меньше интерфейсной логики и программного обеспечения, по сравнению с интерфейсом USB. Однако, операционные системы Microsoft Windows после 95/98 запрещают пользовательским программам писать напрямую в порт или читать из него без дополнительного программного обеспечения (расширений ядра). В настоящее время параллельный порт также часто используется на фрезерных станках с ЧПУ для прямого управления двигателями и приспособлениями.

Реализация IBM PC

Адреса портов

Традиционно в системах IBM PC для первых трёх параллельных портов выделена конфигурация, приведённая в таблице ниже (если есть все три порта).
Номер портаНомер прерыванияНачальный адресКонечный адрес
#1IRQ 70x3BC0x3BF
#2IRQ 70x3780x37F
#3IRQ 50x2780x27F
Если имеется неиспользуемый слот, порты с остальными адресами передвигаются вверх. (Например, если нет порта 0x3BC, то порт 0x378 становится первым логическим портом.) Базовый адрес 0x3BC обычно используется параллельными портам на видеоадаптерах MDA и Hercules, а параллельные порты материнской платы или карт расширения иногда поддерживают настройку базового адреса. Поэтому, при отсутствии монохромного видеоадаптера, обычно первому логическому порту (а также драйверу устройства LPT1 в DOS) соответствует адрес 0x378, хотя по умолчанию им по-прежнему является адрес 0x3BC (он будет выбран BIOS, если параллельный порт будет обнаружен на этом адресе). Линии IRQ обычно соответствующим образом настраиваются аппаратно. Следует избегать назначать одно и то же прерывание нескольким параллельным портам, т.к. это может привести к тому, что порты будут работать только в режиме опроса. Адреса порта, соответствующие слоту, могут быть определены чтением BDA (BIOS Data Area - области данных BIOS) по адресам 0000h:0408h.

Соответствие бита выводам SPP (Standard Parallel Port - стандартного параллельного порта):
Адрес Старшие битыМладшие биты
 Бит:76543210
База (порт данных)Вывод:98765432
База+1 (порт состояния)Вывод:~1110121315   
База+2 (порт управления)Вывод:    ~1716~14~1
~ указывает на аппаратную инверсию бита.

Программный интерфейс

В версиях Windows, не использующих ядро Windows NT (как в DOS и некоторых других операционных системах), программы могут обращаться к параллельному порту через простые вызовы подпрограмм outportb() и inportb(). В операционных системах вроде Windows NT и Unix (NetBSD, FreeBSD, Solaris, 386BSD и т.п.) микропроцессор работает в различных кольцах защиты и доступ к параллельному порту ограничен, если не использовать необходимый драйвер. Это увеличивает безопасность и защищает от одновременного доступа к устройству. В Linux inb() и outb() могут использоваться, если процесс запущен от имени пользователя root и вызвал подпрограмму ioperm() для получения доступа к базовому адресу. Другим способом доступа к параллельному порту является использование драйвера ppdev, который поддерживает совместное использование порта и, при настройке соответствующих разрешений, может использоваться из пространства пользователя.

Во многих дистрибутивах Linux также доступна кроссплатформенная библиотека libieee1284, которая предоставляет абстрактный интерфейс к параллельным портам системы. Доступ обрабатывается в последовательности открыть-затребовать-освободить-закрыть - open-claim-release-close, что делает возможным одновременный доступ в пространстве пользователя.

Расположение выводов

У старых параллельных портов имеется 8-битная шина данных, четыре управляющих вывода (Strobe, Linefeed, Initialize, Select In) и пять выводов для контроля состояния (ACK, Busy, Select, Error и Paper Out). Его скорость передачи данных - 150 Кбит/с.

Новые EPP (Enhanced Parallel Port - улучшенные параллельные порты) имеют 8-битную шину данных и такое же количество управляющих выводов, как и у обычного параллельного порта. Новые порты достигают скорости до 2 Мбайт/с.

Расположение выводов разъёма параллельного порта таково:

Номер вывода (DB25)Номер вывода (36-контактный разъём)Название сигналаНаправлениеРегистр-битИнвертирован
11StrobeВвод/выводControl-0Да
22Data0ВыводData-0Нет
33Data1ВыводData-1Нет
44Data2ВыводData-2Нет
55Data3ВыводData-3Нет
66Data4ВыводData-4Нет
77Data5ВыводData-5Нет
88Data6ВыводData-6Нет
99Data7ВыводData-7Нет
1010AckВводStatus-6Нет
1111BusyВводStatus-7Да
1212Paper-OutВводStatus-5Нет
1313SelectВводStatus-4Нет
1414LinefeedВвод/ВыводControl-1Да
1532ErrorВводStatus-3Нет
1631ResetВвод/ВыводControl-2Нет
1736Select-PrinterВвод/ВыводControl-3Да
18-2519-30,33,17,16Ground---
Инвертированные выводы истинны при низком логическом уровне. Если они не инвертированы, то истине соответствует высокий логический уровень.

Контакт 25 на разъёме DB25 современных компьютеров может быть не подключен к общему проводу.

Смотрите также

Интегральные микросхемы:
  • Для компьютера - см. суперконтроллер ввода-вывода
  • Для периферийного оборудования - микросхемы параллельного порта: PPC34C60 (SMSC) и W91284PIC (Warp Nine)
  • Для переходников USB-параллельный порт, например, USB-микросхемы PL-2305 (Prolific) и CH341 (QinHeng)