Драйвер принтера 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, то выполняем возврат. В противном случае ожидаем, пока не произойдёт прерывание на порту (или не будет достигнут таймаут).