Готовый перевод Linux Device Drivers 3 / Драйверы устройств Линукс (третье издание): 18.2. tty_driver Function Pointers
18.2. Указатели на функции tty-драйвера
В конце, драйвер tiny_tty декларирует четыре указателя на
функции.
18.2.1. Функции оpen и close
Функция open вызывается, когда происходит открытие устройства
пользователем. Ядро tty-устройств вызывает метод open, передавая ему
указатель на структуру tty_struct, назначенную данному устройству, и
file-указатель. Драйвер должен корректно инициализировать поле open,
иначе при открытии пользователь получит ошибку -ENODEV.
При вызывове функции open ожидается, что tty-драйвер сохранит в
переменную tty_struct, или в статичесий массив, информацию о
младшем номере. Это необходимо, чтобы драйвер знал на какие
устройства можно ссылаться при вызове функций закрытия, чтения,
записи и других.
В приведенном коде, драйвер 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 переменная open_count
инициализируется нулем. Это типичный счетчик ссылок, который
необходим т.к. open и close могут неоднократно вызываться для одного и
того же устройства в условиях многозадачности при чтении и записи
данных. Для корректной работы необходимо вести подсчет, сколько раз
порт открывался и закрывался. Драйвер инкрементирует и
декрементирует счетчик в процессе использования порта. При первом
открытии порта инициализируется аппаратное обеспечение, выделяется
память. При последнем закрытии происходит выключение аппаратного
обеспечения и освобождение памяти.
Оставшаяся часть функции tiny_open учитывает, сколько раз
устройство было открыто:
++tiny->open_count;
if (tiny->open_count = = 1) {
/* это первое открытие устройства*/
/* выполняется инициализация аппаратного обеспечения */
Функция open должна возвращать отрицательное число при
возникновении ошибки, или ноль в случае успешного завершения.
Функция close вызывается ядром tty-устройств, когда пользователь
вызывает метод close для дескриптора открытого устройства. Поскольку
устройство может быть открыто несколько раз, то и функция close так
же вызывается несколько раз. Функция ведет учет того, сколько раз
производилось закрытие, и при необходимости выключает аппаратное
обеспечение. Вот производящий это код:
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 не имеет возвращаемого
значения.
18.2.2. Поток данных
Функция write вызывается когда пользователь посылает данные в
устройство. С начала вызывается ядро tty-устройств, затем оно передает
управление функции tty-драйвера write, сообщая размер посылаемых
данных.
Иногда, из-за ограничения скорости передачи и ёмкости буфера
устройства, не все данные могут быть посланы сразу при вызове функции
write. Функция write возвращает количество символов отосланных
устройством (или поставленных в очередь отправки), таким образом,
программа пользователя может проверить все, ли данные были
действительно посланы. Такой подход делает проверку отправки в
пространстве пользователя менее надежной, чем в драйвере ядра,
который может дождаться отправки всех данных. Если, при вызове write,
возникает ошибка, функция должна вернуть отрицательное значение,
вместо количества переданных символов.
Функция write может вызываться как из контекста прерывания, так
и из контекста пользователя. Это важно учитывать при вызове других
функций, которые могут уснуть в контексте прерывания. Некоторые
функции, такие как copy_from_user, kmalloc и printk, могут вызывать
schedule. Если вам нужно уйти в сон, то для начала сделайте проверку, не
находитесь ли вы в контексте прерывания. Для этой проверки
используется функция in_interrupt.
Наш пример tty-драйвера не работает с реальным устройством,
поэтому функция записи просто пишет в отладочный log ядра, что данные
успешно переданы. (1)
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-
драйвер не реализует функцию put_char в структуре tty_struct. В этом
случае ядро tty-устройств использует метод write для записи данных
размера 1 символ. Обычно это происходит, когда ядру tty-устройств
требуется конвертировать символ новой строки в символы протяжки
бумаги плюс символ новой строки. Если в этом месте функция драйвера
write вернет «0», могут возникнуть серьезные проблемы. В данном
случае драйвер обязательно должен записать байт данных в устройство,
т.к. ядро tty-устройств передает ему не просто обычный буфер данных,
который можно послать и позже. Функция write не может определить
используется ли она в качестве put_char, поэтому постарайтесь
реализовать функцию так, что если посылается только один байт, то
возврат происходит только после того, как этот байт отправлен.
Некоторые современные tty-драйверы преобразователя USB-
последовательный порт не следуют этому правилу, по этой причине
некоторые терминалы не работают должным образом.
Функция write_room вызывается, когда ядру tty-устройств требуется
узнать, сколько пространства доступно в буфере записи устройства. С
течением времени это значение изменяется, т.к. символы из буфера
отсылаются, и, при вызове 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;
}
18.2.3. Другие функции работы с буфером
Функция chars_in_buffer не обязательно должна определятся в
структуре tty_driver, но желательна. Эта функция вызывается, когда
ядру tty-устройств требуется узнать, сколько еще символов из буфера
осталось послать. Если драйвер может сохранять символы в буфере перед
их аппаратной передачей, ему следует реализовать эту функцию, чтобы
ядро могло определить, факт отправки всех данных.
Еще три функции в структуре tty_driver, могут использоваться для
очистки данных находящихся в буфере драйвера. Их реализация не
обязательна, но рекомендуются в том случае, если tty-драйвер может
буферизировать посылаемые данные. Первые две функции flush_chars и
wait_until_sent вызываются, когда ядру требуется послать некоторое
количество символов функцией put_char. Функция flush_chars
заканчивает работу перед тем как все данные будут переданы. Функция
wait_until_sent работает аналогично, но возвращается после передачи
всех данных, или после истечения интервала времени timeout, который
передается в эту функцию. Tty-дравер, ожидая завершения, может спать в
этих функциях. Если передаваемое значение timeout равно «0», то
функция ждет завершения операций.
Третья функция flush_buffer. Она вызывается ядром tty-устройств,
когда нужно очистить буфер записи в драйвере. Данные, находящиеся в
буфере – теряются и не посылаются в устройство.
18.2.4. Нет функции read?
Это все функции, которые могут быть зарегистрированы: открытие
node устройства, запись данных в устройство, закрытие node устройства,
отмена регистрации драйвера, и выгрузка из ядра. Но ядро tty-устройств
и структура tty_driver не предоставляют функции чтения. Иными
словами не существует функции чтения из устройства.
Вместо привычной функции чтения, tty-драйвер посылает ядру
данные, принятые от устройства. Ядро tty-устройств накапливает данные
в буфере, пока их не запросит пользователь. Поскольку ядро tty-устройств
предоставляет логику работы с буфером, каждому драйверу ненужно ее
реализовывать. Ядро извещает драйвер, когда пользователь хочет
остановить и запустить передачу данных, но если внутренний буфер
заполнен, извещения не происходит.
Буфер принимаемых данных называется struct tty_flip_buffer.
Переключающийся (flip) буфер является структурой содержащий два
главных массива. Принятые данные хранятся в первом массиве, когда
массив заполняется, он становится доступным для пользователя,
ожидающего данных. Пока пользователь читает данные из первого
массива, входящие данные пишутся во второй массив. Когда работа с
этим массивом закончена, данные снова передаются пользователю, и
драйвер вновь работает с первым массивом.
По существу происходит переключение с одного буфера на другой, в
надежде что не произойдет переполнение сразу двух буферов. Чтобы
попытаться предотвратить потерю данных, tty-драйвер может
отслеживать размер входящего массива данных, и если он заполняет
буфер, драйвер говорит о необходимости переключить буфер, вместо
ожидания его освобождения.
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_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 происходит вызов планировщика, и
данные перемещаются в выходной буфер несколько позже.
1 Прим. переводчика: файл <linux></linux>tty_driver.h> ядра 2.6.5 декларирует функцию write
несколько иначе:
int (*write)(struct tty_struct * tty, int from_user, const unsigned char *buf, int count);
Внимание! Этот перевод, возможно, ещё не готов.
Его статус: перевод редактируется
Переведено на Нотабеноиде
http://notabenoid.org/book/11832/38400
Переводчики: hardorsoft
Настройки
Готово:
