| Подсистема параллельного порта Linux 2.4 | ||
|---|---|---|
| <<< Назад | Драйверы устройств, работающие в пространстве пользователя | Далее >>> |
Имеющиеся здесь два примера описывают процесс написания простого драйвера принтера для 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);
}
|