Консольные драйверы

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

Консоль является устройством, которое показывает сообщения printk(), генерируемые ядром. Если посмотреть на Рисунок 12.5, то можно увидеть, что консольные драйверы лежат в двух ярусах: верхний уровень составляют такие драйверы, как драйвер виртуального терминала, консольный драйвер печати и, например, драйвер консоли USB_UART (обсуждается в ближайшее время), а низкий уровень  - драйверы, которые несут ответственность за дополнительные операции. Следовательно, есть две основных определённых структуры интерфейса, используемые консольными драйверами. Высокоуровневые консольные драйверы вращаются вокруг структуры console, которая определяет такие основные операции, как setup() и write(). Низкоуровневые драйверы сконцентрированы на структуре consw, которая определяет дополнительные операции, такие, как установка свойств курсора, переключение консоли, гашение, изменение размеров и установка информации палитры. Эти структуры определяются в include/linux/console.h следующим образом:

 

1.struct console {
   char name[8];
   void (*write)(struct console *, const char *, unsigned);
   int (*read)(struct console *, char *, unsigned);
   /* ... */
   void (*unblank)(void);
   int (*setup)(struct console *, char *);
   /* ... */
};
 

2.struct consw {
   struct module *owner;
   const char *(*con_startup)(void);
   void (*con_init)(struct vc_data *, int);
   void (*con_deinit)(struct vc_data *);
   void (*con_clear)(struct vc_data *, int, int, int, int);
   void (*con_putc)(struct vc_data *, int, int, int);
   void (*con_putcs)(struct vc_data *,
   const unsigned short *, int, int, int);
   void (*con_cursor)(struct vc_data *, int);
   int (*con_scroll)(struct vc_data *, int, int, int, int);
   /* ... */
};

 

Как вы уже могли догадаться, глядя на Рисунок 12.5, большинство консольных устройств требуют драйверов обоих уровней, работающих в тандеме. Во многих ситуациях драйвером консоли верхнего уровня является vt. В ПК-совместимых системах, консольный драйвер VGA (vgacon) является обычно низкоуровневым консольным драйвером, тогда как на встроенных устройствах низкоуровневым драйвером часто является консольный драйвер кадрового буфер (fbcon). Из-за косвенности, предлагаемой абстракцией кадрового буфера, fbcon, в отличие от других низкоуровневых консольных драйверов, является аппаратно-независимым.

 

Давайте кратко рассмотрим архитектуру обоих уровней консольных драйверов:

 

Драйвер верхнего уровня заполняет структуру console заданными точками входа и регистрирует её в ядре с помощью register_console(). Отмена регистрации осуществляется с использованием unregister_console(). Это драйвер, который взаимодействует с printk(). Входные точки, принадлежащие этому драйверу, вызываются службы связанного низкоуровневого драйвера консоли.
 

Низкоуровневый драйвер консоли заполняет структуру consw заданными точками входа и регистрирует её в ядре с помощью register_con_driver(). Отмена регистрации осуществляется с помощью unregister_con_driver(). Когда система поддерживает несколько драйверов консоли, чтобы зарегистрировать себя, драйвер может вместо этого вызвать take_over_console() и взять на себя обслуживание существующей консоли. give_up_console() выполняет обратное. Для обычных дисплеев низкоуровневые драйверы взаимодействуют с консольным драйвером верхнего уровня vt и символьным (текстовым) драйвером vc_screen, который позволяет получить доступ к памяти консоли.
 

Некоторым простым консолям, таким, построчно-печатающие устройства и USB_UART, обсуждаемые далее, необходим только высокоуровневый драйвер консоли.

 

Драйвер fbcon в ядре 2.6 также поддерживает поворот консоли. Дисплейные панели на КПК и мобильных телефонах обычно монтируются в портретной ориентации, в то время как автомобильные панели и IP телефоны являются примерами систем, где дисплейная панель находится в альбомном режиме. Иногда из-за экономических или других факторов встроенные устройства могут потребовать альбомный LCD, смонтированный в режиме портрета, или наоборот. В таких ситуациях удобна поддержка поворота консоли. Поскольку fbcon аппаратно-независим, реализация поворота консоли также универсальна. Для включения поворота консоли  включите при конфигурации ядра CONFIG_FRAMEBUFFER_CONSOLE_ROTATION и добавьте в командную строчку ядра fbcon=rotate:X, где X равно 0 для нормальной ориентации, 1 для поворота на 90 градусов, 2 для поворота на 180 градусов, и 3 для поворота на 270 градусов.

Пример устройства: Снова сотовый телефон

Чтобы узнать, как писать консольные драйверы, давайте вновь обратимся к сотовому телефону с Linux, который мы использовали в Главе 6. Наша задача в этом разделе заключается в разработке драйвера консоли, который работает в мобильном телефоне поверх USB_UART. Для удобства на Рисунке 12.7 воспроизводится сотовый телефон из Рисунка 6.5 Главы 6. Давайте напишем консольный драйвер, который выдаёт сообщения printk() наружу через USB_UART. Сообщения забираются ПК и отображаются для пользователя с помощью сессии, эмулирующей терминал.

 

Рисунок 12.7. Консоль через USB_UART.

Рисунок 12.7. Консоль через USB_UART.

 

Распечатка 12.3 содержит реализацию консольного драйвера, который работает через USB_UART-ы. Структура usb_uart_port[] и некоторые определения, используемые драйвером USB_UART в Главе 6, также включены в эту распечатку, чтобы создать полноценный драйвер. Действия драйвера объясняют комментарии распечатки.

 

Рисунок 12.5 показывает положение нашего примера консольного драйвера USB_UART в подсистеме видео в Linux. Как вы можете видеть, USB_UART - это простое устройство, которому необходим только высокоуровневый консольный драйвер.

 

Распечатка 12.3. Консоль через USB_UART

 

Код:

#include <linux/console.h>

#include <linux/serial_core.h>

#include <asm/io.h>

 

#define USB_UART_PORTS          2 /* Этот сотовый телефон имеет 2 порта USB_UART */

/* Каждый USB_UART имеет 3-х байтовый набор регистров, состоящий из

   UU_STATUS_REGISTER по смещению 0, UU_READ_DATA_REGISTER по

   смещению 1, и UU_WRITE_DATA_REGISTER по смещению 2, как показано

   Таблице 1 Главы 6, "Драйверы последовательный устройств" */

#define USB_UART1_BASE          0xe8000000 /* Базовый адрес памяти для USB_UART1 */

#define USB_UART2_BASE          0xe9000000 /* Базовый адрес памяти для USB_UART2 */

#define USB_UART_REGISTER_SPACE 0x3

/* Назначение битов регистра состояния */

#define USB_UART_TX_FULL        0x20

#define USB_UART_RX_EMPTY       0x10

#define USB_UART_STATUS         0x0F

 

#define USB_UART1_IRQ           3

#define USB_UART2_IRQ           4

#define USB_UART_CLK_FREQ       16000000

#define USB_UART_FIFO_SIZE      32

 

/* Параметры каждого из поддерживаемых портов USB_UART */

static struct uart_port usb_uart_port[] = {

    {

        .mapbase  = (unsigned int)USB_UART1_BASE,

        .iotype   = UPIO_MEM,           /* Отображённая память */

        .irq      = USB_UART1_IRQ,      /* IRQ */

        .uartclk  = USB_UART_CLK_FREQ,  /* Частота в Гц */

        .fifosize = USB_UART_FIFO_SIZE, /* Размер FIFO */

        .flags    = UPF_BOOT_AUTOCONF,  /* Флаг порта UART */

        .line     = 0,                  /* Номер линии UART */

    },

    {

        .mapbase  = (unsigned int)USB_UART2_BASE,

        .iotype   = UPIO_MEM,           /* Отображённая память */

        .irq      = USB_UART2_IRQ,      /* IRQ */

        .uartclk  = USB_UART_CLK_FREQ,  /* Частота в Гц */

        .fifosize = USB_UART_FIFO_SIZE, /* Размер FIFO */

        .flags    = UPF_BOOT_AUTOCONF,  /* Флаг порта UART */

        .line     = 1,                  /* Номер линии UART */

    }

};

 

/* Запись символа в порт USB_UART */

static void

usb_uart_putc(struct uart_port *port, unsigned char c)

{

    /* Ждём, пока появится место в TX FIFO этого USB_UART.

       Проверяем это опрашивая бит USB_UART_TX_FULL

       регистра состояния */

    while (__raw_readb(port->membase) & USB_UART_TX_FULL);

 

    /* Записываем символ в порт данных */

    __raw_writeb(c, (port->membase+1));

}

 

/* Консольная запись */

static void

usb_uart_console_write(struct console *co, const char *s, u_int count)

{

    int i;

 

    /* Пишем каждый символ */

    for (i = 0; i < count; i++, s++) {

        usb_uart_putc(&usb_uart_port[co->index], *s);

    }

}

 

/* Получение параметров коммуникации */

static void __init

usb_uart_console_get_options(struct uart_port *port,

                    int *baud, int *parity, int *bits)

{

    /* Читаем текущие настройки (возможно, установленные через bootloader)

       или возвращаем настройки по умолчанию для чётности, числа бит данных

       и скорости передачи */

    *parity = 'n';

    *bits = 8;

    *baud = 115200;

}

 

/* Установка параметров консоли для коммуникации */

static int __init

usb_uart_console_setup(struct console *co, char *options)

{

    struct uart_port *port;

    int baud, bits, parity, flow;

 

    /* Проверяем номер порта и получаем индекс

       соответствующей структуры */

    if (co->index == -1 || co->index >= USB_UART_PORTS) {

        co->index = 0;

    }

    port = &usb_uart_port[co->index];

 

    /* Используем функции, предоставляемые уровнем serial для обработки параметров */

    if (options) {

        uart_parse_options(options, &baud, &parity, &bits, &flow);

    } else {

        usb_uart_console_get_options(port, &baud, &parity, &bits);

    }

    return uart_set_options(port, co, baud, parity, bits, flow);

}

 

/* Заполняем структуру консоли */

static struct console usb_uart_console = {

    .name   = "ttyUU",                /* Имя консоли */

    .write  = usb_uart_console_write, /* Как делать printk в консоли */

    .device = uart_console_device,    /* Предоставлена ядром serial */

    .setup  = usb_uart_console_setup, /* Как настроить консоль */

    .flags  = CON_PRINTBUFFER,        /* Флаг по умолчанию */

    .index  = -1,                     /* Инициализация в неправильное значение */

};

 

/* Инициализация консоли */

static int __init

usb_uart_console_init(void)

{

/* ... */

/* Регистрация этой консоли */

register_console(&usb_uart_console);

return 0;

}

 

console_initcall(usb_uart_console_init); /* Метка инициализации консоли */

 

После того, как драйвер будет собран как часть ядра, вы сможете активировать его с помощью добавления в командную строку ядра console=ttyUUX (где X равен 0 или 1).

Логотип при загрузке

Популярной функцией, предлагаемой подсистемой кадрового буфера является отображение при загрузке логотипа. Для отображения логотипа при конфигурации ядра включите CONFIG_LOGO и выберите доступный логотип. Вы также можете добавить своё изображение логотипа в каталог drivers/video/logo/.

 

Широко используемым форматом изображения для логотипа загрузки является CLUT224, который поддерживает 224 цветов. Работа в таком формате похожа на псевдо-палитры, описанные в разделе "Цветовые режимы". Изображение в CLUT224 является файлом языка Си, содержащим две структуры:

 

CLUT (Color Look Up Table, Справочная таблица цветов), которая является символьным массивом из 224 наборов RGB (тем самым имеет размер 224*3 байт). Каждый 3-х байтовый элемент CLUT представляет собой комбинацию красного, зеленого и синего цветов.
 

Массив данных, каждый байт которого является индексом в CLUT. Индексы имеют значения от 32 до 255 (поддерживая тем самым 224 цветов). Индекс 32 относится к первому элементу в CLUT. Код работы с логотипом (в drivers/video/fbmem.c) создаёт пиксельные данные кадрового буфера из наборов CLUT, соответствующих каждому индексу в массиве данных. Визуальное отображение информации осуществляется с использованием метода низкоуровневого драйвера кадрового буфера fb_imageblit(), как рассказывалось в разделе "Методы ускорения".

 

Другими поддерживаемыми форматами логотипа являются 16-ти цветный vga16 и черно-белый mono. Скрипты для конвертации стандартных файлов в Переносимую пиксельную карту, Portable Pixel Map (PPM)  доступны на верхнем уровне каталога scripts/.

 

Если устройство кадрового буфера также является консолью, под логотипом прокручиваются сообщения при загрузке. Вы можете предпочесть отключить консольные сообщения на системе, готовой к поставке (добавив в командную строку ядра console=/dev/null), и показывать в качестве загрузочного логотипа собственно подготовленное изображение "заставка" в формата CLUT224.

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