ioctl-ы

Предыдущая  Содержание  Следующая V*D*V

Функция обратного вызова ioctl в struct tty_driver вызывается ядром tty, когда для узла устройства вызывается ioctl(2). Если tty драйвер не знает, как обработать переданное ему значение ioctl, он должен вернуть -ENOIOCTLCMD, чтобы попытаться дать ядру tty реализовать универсальную версию вызова.

 

Ядро версии 2.6 определяет около 70 различных tty ioctl-ов, которые могут быть отправлены в tty драйвер. Большинство tty драйверов не обрабатывают их все, а только небольшую часть более общих команд. Вот список наиболее популярных tty ioctl-ов, что они означают и как их реализовать:

 

TIOCSERGETLSR

Получает значение регистра статуса строки (LSR) этого tty устройства.

 

TIOCGSERIAL

Получает информацию последовательной линии. Вызывающий с помощью этого вызова может потенциально за раз получить много информации о последовательной линии tty устройства. Некоторые программы (такие, как setserial и dip) вызывают эту функцию, чтобы убедиться, что скорость передачи была установлена правильно и получить общую информацию о том, каким типом устройства управляет tty драйвер. Вызывающий передаёт указатель на большую структуру типа serial_struct, которую tty драйвер должен заполнить соответствующими значениями.

 

Вот пример того, как это может быть реализовано:

 

static int tiny_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg)

{

    struct tiny_serial *tiny = tty->driver_data;

    if (cmd == TIOCGSERIAL) {

        struct serial_struct tmp;

        if (!arg)

            return -EFAULT;

        memset(&tmp, 0, sizeof(tmp));

        tmp.type     = tiny->serial.type;

        tmp.line     = tiny->serial.line;

        tmp.port     = tiny->serial.port;

        tmp.irq      = tiny->serial.irq;

        tmp.flags    = ASYNC_SKIP_TEST | ASYNC_AUTO_IRQ;

        tmp.xmit_fifo_size = tiny->serial.xmit_fifo_size;

        tmp.baud_base      = tiny->serial.baud_base;

        tmp.close_delay    = 5*HZ;

        tmp.closing_wait   = 30*HZ;

        tmp.custom_divisor = tiny->serial.custom_divisor;

        tmp.hub6     = tiny->serial.hub6;

        tmp.io_type  = tiny->serial.io_type;

        if (copy_to_user((void __user *)arg, &tmp, sizeof(tmp)))

            return -EFAULT;

        return 0;

    }

    return -ENOIOCTLCMD;

}

 

TIOCSSERIAL

Устанавливает информацию последовательной линии. Это является противоположностью TIOCGSERIAL и позволяет пользователю за раз установить состояние последовательной линии tty устройства. Указатель на struct serial_struct, передаваемый в этом вызове, заполнен данными, которые tty устройство должно сейчас установить. Если tty драйвер не реализует этот вызов, большинство программ по-прежнему работает правильно.

 

TIOCMIWAIT

Ожидание изменения МСР. Пользователь запрашивает этот ioctl в необычных обстоятельствах, при которых он хочет заснуть в ядре, пока что-то не случится в регистре MSR tty устройства. Параметр arg содержит тип события, которое ждёт пользователь. Это обычно используется для ожидания изменений линии состояния, сигнализирующих, что к отправке на это устройство готовы дополнительные данные.

Будьте осторожны при реализации этого ioctl и не используйте вызов interruptible_sleep_on, так как это небезопасно (есть много неприятных состояний гонок, создаваемых им). Вместо этого, чтобы избежать таких проблем, следует использовать wait_queue. Вот пример того, как реализовать этот ioctl:

 

static int tiny_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg)

{

    struct tiny_serial *tiny = tty->driver_data;

    if (cmd == TIOCMIWAIT) {

        DECLARE_WAITQUEUE(wait, current);

        struct async_icount cnow;

        struct async_icount cprev;

        cprev = tiny->icount;

        while (1) {

            add_wait_queue(&tiny->wait, &wait);

            set_current_state(TASK_INTERRUPTIBLE);

            schedule( );

            remove_wait_queue(&tiny->wait, &wait);

            /* смотрим, не разбудил ли нас сигнал */

            if (signal_pending(current))

                return -ERESTARTSYS;

            cnow = tiny->icount;

            if (cnow.rng == cprev.rng && cnow.dsr == cprev.dsr &&

                cnow.dcd == cprev.dcd && cnow.cts == cprev.cts)

                return -EIO; /* нет изменения => ошибка */

            if (((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) ||

                ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) ||

                ((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd)) ||

                ((arg & TIOCM_CTS) && (cnow.cts != cprev.cts)) ) {

                return 0;

            }

            cprev = cnow;

        }

    }

    return -ENOIOCTLCMD;

}

 

Где-то в коде tty драйвера, который распознаёт изменения регистра MSR, для правильной работы этого кода должна быть вызвана следующая строка:

 

wake_up_interruptible(&tp->wait);

 

TIOCGICOUNT

Получает число прерываний. Это вызывается, когда пользователь хочет узнать, сколько прерываний случилось на последовательной линии. Если драйвер имеет обработчик прерываний, он должен определить внутреннюю структуру счетчиков, чтобы отслеживать такую статистику и увеличивать соответствующий счётчик каждый раз, когда функция выполняется ядром.

Этот вызов ioctl передаёт ядру указатель на структуру serial_icounter_struct, которая должна быть заполнена tty драйвером. Этот вызов часто выполняется в сочетании с предыдущим вызовом ioctl TIOCMIWAIT. Если tty драйвер во время работы драйвера сохраняет число всех этих прерываний, код для реализации этого вызова может быть очень простым:

 

static int tiny_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg)

{

    struct tiny_serial *tiny = tty->driver_data;

    if (cmd == TIOCGICOUNT) {

        struct async_icount cnow = tiny->icount;

        struct serial_icounter_struct icount;

        icount.cts = cnow.cts;

        icount.dsr = cnow.dsr;

        icount.rng = cnow.rng;

        icount.dcd = cnow.dcd;

        icount.rx  = cnow.rx;

        icount.tx  = cnow.tx;

        icount.frame   = cnow.frame;

        icount.overrun = cnow.overrun;

        icount.parity  = cnow.parity;

        icount.brk = cnow.brk;

        icount.buf_overrun = cnow.buf_overrun;

        if (copy_to_user((void __user *)arg, &icount, sizeof(icount)))

            return -EFAULT;

        return 0;

    }

    return -ENOIOCTLCMD;

}

Предыдущая  Содержание  Следующая