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

<<< Назад Начало Далее >>>
Интерфейс программирования Наверх Справочник программного интерфейса драйвера параллельного порта Linux