Подсистема параллельного порта Linux 2.4
<<< Назад Далее >>>

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

Этот раздел написан с точки зрения программиста, который собирается написать драйвер принтера, сканера или какого-то другого устройства, подключаемого к параллельному порту. В разделе объясняется, как использовать интерфейс 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).


<<< Назад Начало Далее >>>
Программный интерфейс IEEE 1284.3   Драйверы порта