Указатели на функции в tty_driver

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

Наконец, драйвер tiny_tty декларирует четыре указателя на функции.

open и close

Функция open вызывается ядром tty, когда пользователь вызывает open для назначенного tty драйверу узла устройства. Ядро tty вызывает её с указателем на структуру tty_struct, присвоенной этому устройству, и указателем на файл. Для корректной работы tty драйвером должно быть установлено поле open; в противном случае, при вызове открытия пользователю возвращается -ENODEV.

 

Когда вызывается эта функция open, ожидается, что tty драйвер либо сохраняет некоторые данные в переменной tty_struct, которая передаётся ему, либо сохраняет данные в статическом массиве, на который можно ссылаться на основе младшего номера порта. Это необходимо, чтобы tty драйвер знал, на какое устройство в настоящее время ссылаются, когда впоследствии будут вызваны close, write и другие функции.

 

Драйвер tiny_tty сохраняет указатель в структуре tty, как можно видеть в следующем коде:

 

static int tiny_open(struct tty_struct *tty, struct file *file)

{

    struct tiny_serial *tiny;

    struct timer_list *timer;

    int index;

 

    /* проинициализируем указатель на случай, если что-то пойдёт не так */

    tty->driver_data = NULL;

 

    /* получить последовательный объект, связанный с этим указателем tty */

    index = tty->index;

    tiny = tiny_table[index];

    if (tiny == NULL) {

        /* к этому устройству обращение делается в первый раз, давайте его создадим */

        tiny = kmalloc(sizeof(*tiny), GFP_KERNEL);

        if (!tiny)

            return -ENOMEM;

 

        init_MUTEX(&tiny->sem);

        tiny->open_count = 0;

        tiny->timer = NULL;

 

        tiny_table[index] = tiny;

    }

 

    down(&tiny->sem);

 

    /* сохраним нашу структуру в структуре tty */

    tty->driver_data = tiny;

    tiny->tty = tty;

 

В этом коде структура tiny_serial сохраняется в структуре tty. Это позволяет функциям tiny_write, tiny_write_room и tiny_close получать структуру tiny_serial и манипулировать ею должным образом.

 

Структура tiny_serial определена как:

 

struct tiny_serial {

    struct tty_struct *tty;  /* указатель на tty для этого устройства */

    int         open_count;  /* сколько раз был открыт этот порт */

    struct semaphore   sem;  /* блокировка этой структуры */

    struct timer_list *timer;

};

 

Как мы уже видели, переменная open_count инициализируется в 0 во время вызова open при первом открытии порта. Это типичный счётчик ссылок, необходимый потому, что функции open и close драйвера tty могут быть вызваны для одного устройства множество раз, чтобы позволить нескольким процессам читать и записывать данные. Чтобы всё обработать правильно, должен храниться счётчик, сколько раз порт был открыт или закрыт; при использовании порта драйвер увеличивает и уменьшает этот счётчик. Когда порт открыт в первый раз, может быть выполнена любая необходимая инициализация оборудования и выделение памяти. Когда порт закрывается в последний раз, может быть выполнена всё необходимое для выключения оборудования и очистке памяти.

 

Остальная часть функции tiny_open показывает, как отслеживать число открытий устройства:

 

++tiny->open_count;

if (tiny->open_count == 1) {

    /* здесь порт открыт первый раз */

    /* выполняем здесь любую необходимую инициализацию оборудования */

 

Функция open должна возвращать либо отрицательный номер ошибки, если что-то случилось, чтобы предотвратить успешное открытие, или 0, что означает успех.

 

Указатель на функцию close вызывается ядром tty, когда пользователь вызвал close для дескриптора файла, который ранее был создан вызовом open. Это означает, что в это время устройство должно быть закрыто. Однако, поскольку функция open может быть вызвана более одного раза, функция close также может быть вызвана более одного раза. Поэтому эта функция должна отслеживать, сколько раз она была вызвана, чтобы определить, действительно ли в это время должно быть закрыто оборудование. Драйвер tiny_tty делает это с помощью следующего кода:

 

static void do_close(struct tiny_serial *tiny)

{

    down(&tiny->sem);

 

    if (!tiny->open_count) {

        /* порт никогда не открывался */

        goto exit;

    }

 

    --tiny->open_count;

    if (tiny->open_count <= 0) {

        /* Этот порт был закрыт последним пользователем. */

        /* Выполняем здесь любые относящиеся к оборудованию действия */

 

        /* выключить наш таймер */

        del_timer(tiny->timer);

    }

exit:

    up(&tiny->sem);

}

 

static void tiny_close(struct tty_struct *tty, struct file *file)

{

    struct tiny_serial *tiny = tty->driver_data;

    if (tiny)

        do_close(tiny);

}

 

Чтобы сделать настоящую работу по закрытию устройства, функция tiny_close просто вызывает функцию do_close. Это делается для того, чтобы не дублировать логику закрытия здесь и при выгрузке драйверов и при открытии порта. Функция close не имеет возвращаемого значения, так как провал не предполагается.

Движение данных

Функция write вызывается пользователем, когда есть данные для отправки в оборудование. Сначала ядро tty получает вызов, а затем передаёт эти данные в функцию tty драйвера write. Ядро tty также сообщает tty драйверу размер отправляемых данных.

 

Иногда из-за скорости и ёмкости буфера оборудования tty не все символы, запрошенные записывающей программой, могут быть отправлены в момент, когда вызвана функция write. Функция write должна возвращать количество символов, которое смогла отправить в оборудование (или поместить в очередь, чтобы отправить позднее), так что пользовательская программа может проверить, все ли данные были записаны на самом деле. Гораздо проще выполнить такую проверку в пользовательском пространстве, чем для драйвера ядра стоять и спать, пока все запрошенные данные смогут быть отправлены. Если во время вызова write произошла ошибка, должно быть возвращено отрицательное значение ошибки, а не число записанных символов.

 

Функция write может быть вызвана и из контекста прерывания и из пользовательского контекста. Это важно знать, так как tty драйверы не должны вызывать каких-либо функций, которые могли бы заснуть, когда они находятся в контексте прерывания. К ним относятся любые функции, которые могли бы быть вызвать schedule, такие как обычные функции copy_from_user, kmalloc и printk. Если вы действительно хотите поместить драйвер в сон, сначала убедитесь, находится ли драйвер в контексте прерывания, вызвав in_interrupt.

 

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

 

static int tiny_write(struct tty_struct *tty, const unsigned char *buffer, int count)

{

    struct tiny_serial *tiny = tty->driver_data;

    int i;

    int retval = -EINVAL;

 

    if (!tiny)

        return -ENODEV;

 

    down(&tiny->sem);

    if (!tiny->open_count)

    /* порт не был открыт */

        goto exit;

 

    /* подделываем отправку данных в аппаратный порт

     * записывая их в журнал отладки ядра.

     */

    printk(KERN_DEBUG "%s - ", __FUNCTION__);

    for (i = 0; i < count; ++i)

        printk("%02x ", buffer[i]);

    printk("\n");

 

exit:

    up(&tiny->sem);

    return retval;

}

 

Функция write может быть вызвана, когда tty подсистема сама нуждается отправить какие-то данные в tty устройство. Это может произойти, если tty драйвер не реализует функцию put_char в tty_struct. В этом случае ядро tty использует обратный вызов функции write с размером данных 1. Это обычно происходит, когда ядро tty хочет преобразовать символ новой строки в перевод строки плюс символ новой строки. Самая большая проблема , которая может здесь возникнуть в том, что функция tty драйвера write не должна возвращать 0 для вызова такого вида. Это означает, что драйвер должен записать такой байт данных в устройство, так как вызывающий (ядро tty) не буферирует данные и пробует ещё раз позже. Так как функция write не может определить, вызвана ли она  вместо put_char, даже если в настоящее время отправляется только один байт данных, попытаемся реализовать функцию write так, чтобы перед возвращением она всегда писала по крайней мере один байт. Ряд текущих tty драйверов USB-последовательный порт не следуют этому правилу и в силу этого при подключении к ним некоторые типы терминалов не работают должным образом.

 

Когда ядро tty хочет узнать, как много места доступно в буфере записи tty драйвера, вызывается функция write_room. Это число со временем изменяется, так как символы уходят наружу, освобождая буфер записи, и происходят вызовы функции write, добавляя символы в буфер.

 

static int tiny_write_room(struct tty_struct *tty)

{

    struct tiny_serial *tiny = tty->driver_data;

    int room = -EINVAL;

 

    if (!tiny)

        return -ENODEV;

 

    down(&tiny->sem);

 

    if (!tiny->open_count) {

        /* порт не был открыт */

        goto exit;

    }

    /* вычисляем, как много места осталось в устройстве */

    room = 255;

 

exit:

    up(&tiny->sem);

    return room;

}

Другие функции буферизации

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

 

Для сброса в поток всех оставшихся данных, удерживаемых драйвером, могут быть использованы три функции обратного вызова в структуре tty_driver. Реализация их не требуется, но рекомендуется, если tty драйвер может буферизировать данные перед их отправкой в оборудование. Первые две функции обратного вызова называются flush_chars и wait_until_sent. Эти функции вызываются, когда ядро tty отправило в tty драйвер несколько символов с помощью функции обратного вызова put_char. Функция обратного вызова flush_chars вызывается, когда ядро tty хочет, чтобы tty драйвер начал отправку этих символов в оборудование, если он ещё не начал это делать. Этой функции разрешено вернуться до того, как все данные отправлены в оборудование. Функция обратного вызова wait_until_sent работает во многом аналогично; но она должна подождать отправки всех символов до возвращения в ядро tty или истечения переданного ей времени ожидания, в зависимости от того, что произойдёт в первую очередь. В течение этой функции, для её завершения, tty драйверу разрешается спать. Если значение времени ожидания, переданное в функцию обратного вызова wait_until_sent, установлено в 0, функция должна ждать, пока эта операция не завершится.

 

Оставшейся функцией обратного вызова для сброса данных является flush_buffer. Она вызывается ядром tty, когда tty драйвер  должен сбросить из памяти все данные, всё ещё хранящиеся в его буферах записи. Любые данные, остающиеся в буфере, теряются и не передаются на устройство. (видимо, не остающиеся в буфере, а данные, которые до этого не поступили в буфер.)

Нет функции read?

Только с этими функциями драйвер tiny_tty может быть зарегистрирован, узел устройства открыт, данные записаны в устройство, узел устройства закрыт и отменена регистрация драйвера и он выгружен из ядра. Но ядро tty и структура tty_driver не предоставляет функцию чтения; в других словах, не существует функции обратного вызова для получения данных из драйвера ядром tty.

 

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

 

Ядро tty буферизует данные, полученные tty драйверами в структуре, названной struct tty_flip_buffer. Переключаемый буфер является структурой, которая содержит два основных массива данных. Данные, полученные от tty устройства хранятся в первом массиве. Когда этот массив полон, любой пользователь, ожидающий данные, уведомляется, что данные доступны для чтения. Хотя пользователь читает данные из этого массива, любые новые входящие данные хранятся во втором массиве. Когда этот массив заполнен, данные вновь сбрасываются пользователю, а драйвер начинает заполнять первый массив. По сути, принятые данные "переключаются" с одного буфера в другой, в надежде не переполнить их обоих. Чтобы попытаться предотвратить потерю данных, tty драйвер может контролировать, насколько велик входной массив, и если он заполнится, приказать tty драйверу очистить переключаемый буфер в этот момент времени, а не ожидать следующего шанса.

 

Детали структуры struct tty_flip_buffer не имеют значения для tty драйвера за с одним исключением, переменной count. Эта переменная содержит сколько байт в настоящее время остаётся в буфере, который используется для приёма данных. Если это значение равно значению TTY_FLIPBUF_SIZE, буфер необходимо сбросить пользователю вызовом tty_flip_buffer_push. Это показано следующим фрагментом кода:

 

for (i = 0; i < data_size; ++i) {

    if (tty->flip.count >= TTY_FLIPBUF_SIZE)

        tty_flip_buffer_push(tty);

    tty_insert_flip_char(tty, data[i], TTY_NORMAL);

}

tty_flip_buffer_push(tty);

 

Символы, полученные от tty драйвера для отправки пользователю, добавляются в переключаемый буфер вызовом tty_insert_flip_char. Первым параметром этой функции является struct tty_struct, где должны быть сохранены данные, вторым параметром является символ для сохранения и третьим параметром являются любые флаги, которые должны быть установлены для этого символа. Значение флагов должно быть установлено в TTY_NORMAL, если получен обычный символ. Если это особый тип символа, указывающий на ошибку передачу данных, оно должно быть установлено на TTY_BREAK, TTY_FRAME, TTY_PARITY или TTY_OVERRUN, в зависимости от ошибки.

 

Для того, чтобы "протолкнуть" данные пользователю, выполняется вызов tty_flip_buffer_push. Этот функция должна быть также вызвана, если переключаемый буфера близок к переполнению, как показано в этом примере. Поэтому, когда данные добавляются в переключаемый буфер, или когда переключаемый буфер заполнен, tty драйвер должен вызвать tty_flip_buffer_push. Если tty драйвер может принимать данные на очень высоких скоростях, должен быть установлен флаг tty->low_latency, что приводит при вызове к немедленному вызову tty_flip_buffer_push. В противном случае, вызов tty_flip_buffer_push планирует себя для выталкивания данных из буфера когда-то позднее в ближайшем будущем.

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