Подсистема параллельного порта 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); } |