Драйверы устройств ввода

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

Давайте обратим наше внимание на драйверы для распространённых устройств ввода, таких как клавиатуры, мыши и сенсорные экраны. Но сначала давайте кратко рассмотрим готовый сервис для доступа к оборудованию, доступный для драйверов ввода.

Serio

Уровень serio предлагает библиотечные подпрограммы для доступа к устаревшему оборудованию ввода, такому как i8042-совместимые контроллеры клавиатуры и последовательный порт. Клавиатуры PS/2 и мыши подключаются к первому, а сенсорные контроллеры с последовательным интерфейсом подключаются к последнему. Для взаимодействия с оборудованием, обслуживаемым serio, например, для передачи команды для PS/2 мыши, предписанные процедуры обратного вызова serio регистрируются с помощью serio_register_driver().

 

Чтобы добавить новый драйвер как часть serio, с помощью serio_register_port() регистрируются точки входа open()/close()/start()/stop()/write(). Для примера посмотрите drivers/input/serio/serport.c.

 

Как можно увидеть на Рисунке 7.1, serio - это только один из маршрутов доступа к низкоуровневому оборудованию. Некоторые драйверы устройств ввода вместо него полагаются на низкоуровневую поддержку от шинных уровней, таких как USB или SPI.

Клавиатуры

Клавиатуры бывают на любой вкус - устаревшие PS/2, USB, Bluetooth, ИК, и так далее. Каждый тип имеет специальный драйвер устройства ввода, но все используют один и тот же драйвер событий клавиатуры, обеспечивая тем самым единый интерфейс для пользователей. Драйвер событий клавиатуры, однако, имеет отличительную особенность по сравнению с другими драйверами событий: он передаёт данные другой подсистеме ядра (уровню tty), а не в пользовательское пространстве с помощью узлов /dev.

Клавиатуры ПК

Клавиатуры ПК (также называемые клавиатурами PS/2  или AT клавиатурами) взаимодействует с процессором через i8042-совместимый контроллер клавиатуры. ПК обычно имеют специальный контроллер клавиатуры, но на ноутбуках взаимодействие с клавиатурой является одной из обязанностей встроенного контроллера общего назначения  (смотрите раздел "Встроенные контроллеры" в Главе 20, "Дополнительные устройства и драйверы"). Когда вы нажимаете клавишу на клавиатуре компьютера, это происходит по такому пути:

 

1.Контроллер клавиатуры (или встроенный контроллер) сканирует и декодирует клавиатурную матрицу и заботится о нюансах, таких как устранение дребезга контактов.
 

2.Клавиатурный драйвер устройства с помощью serio для каждого нажатия и отпускания клавиши читает с контроллера клавиатуры сырые коды сканирования. Разницей между нажатием и отпусканием является самый старший бит, который для последнего случая установлен. Например, нажатие на кнопку "a" даёт пару скан-кодов, 0x1e и 0x9e. Специальные кнопки экранируются с  помощью 0xE0, так что нажатие кнопки со стрелкой вправо производит последовательность (0xE0 0x4D 0xE0 0xCD). Для наблюдения выходящих из контроллера скан-кодов вы можете использовать утилиту showkey (после символа → идут пояснения):
 
bash> showkey -s
кл-ра была в режиме UNICODE
[ если вы пробуете это под X, это может не работать, так как
X сервер также читает /dev/console ]
 
нажмите любую кнопку (программа завершится спустя 10с после
последнего нажатия на кнопку)...
...
0x1e 0x9e → Нажатие кнопки "a"
 

3.Клавиатурный драйвер устройства преобразует полученные скан-коды в коды клавиш, основываясь на режиме ввода. Чтобы увидеть код клавиши, соответствующий кнопке "a":
 
bash> showkey
...
код кнопки 30 нажатие → Нажатие на кнопку "a"
код кнопки 30 отпускание → Отпускание кнопки "a"
 
Чтобы сообщить эти коды клавиш дальше вверх, драйвер генерирует событие ввода, которое передаёт управление драйверу событий клавиатуры.
 

4.Драйвер событий клавиатуры берёт на себя работу по преобразованию кода клавиши в зависимости от загруженной карты кодов клавиш. (Смотрите страницы справки loadkeys и map-файлы в /lib/kbd/keymaps.) Он проверяет, является ли преобразованный  код клавиш такими действиями, как переключение виртуальной консоли или перезагрузка системы. Чтобы вместо перезагрузки системы в ответ на нажатие Ctrl+Alt+Del зажглись светодиоды CAPSLOCK и NUMLOCK, добавьте в обработчик Ctrl+Alt+Del драйвера событий клавиатуры, drivers/char/keyboard.c, следующее:
 
static void fn_boot_it(struct vc_data *vc, struct pt_regs *regs)
{
+    set_vc_kbd_led(kbd, VC_CAPSLOCK);
+    set_vc_kbd_led(kbd, VC_NUMLOCK);
-    ctrl_alt_del();
}
 

5.Для обычных клавиш преобразованный код нажатия отправляется ассоциированному виртуальному терминалу и дисциплине линии N_TTY. (Мы обсуждали виртуальные терминалы и дисциплины линий в Главе 6, "Драйверы последовательных портов.") drivers/char/keyboard.c делает это следующим образом:
 
/* Добавляем код клавиши в переключаемый буфер */
tty_insert_flip_char(tty, keycode, 0);
/* Планируем */
con_schedule_flip(tty);

 

Дисциплина линии N_TTY обрабатывает ввод таким образом, что полученные с помощью клавиатуры данные отображаются на виртуальной консоли и позволяет приложениям пользовательского пространства читать символы из узла /dev/ttyX, подключённого к виртуальному терминалу.

 

На Рисунке 7.3 показано движение данных от момента нажатия клавиши на клавиатуре, до момента его появления на виртуальной консоли. Левая половина рисунка является зависимой от оборудования, а правая половина носит общий характер. В соответствии с целью разработки подсистемы ввода, нижележащий аппаратный интерфейс является прозрачным для драйвера событий клавиатуры и  уровня tty. Таким образом, ядро ввода и чётко определённые интерфейсы событий ограждают пользователей ввода от нюансов оборудования.

 

Рисунок 7.3. Поток данных от PS/2-совместимой клавиатуры.

Рисунок 7.3. Поток данных от PS/2-совместимой клавиатуры.

USB и Bluetooth клавиатуры

Спецификациями USB, связанными с устройствами взаимодействия с человеком (HID), предусмотрен протокол, по которому для взаимодействия используются USB клавиатуры, мыши, наборы кнопок и другие периферийные устройства ввода. На Linux это осуществляется через клиентский драйвер USB usbhid, который отвечает за класс USB HID (0x03). Usbhid регистрирует себя в качестве драйвера устройства ввода. Он соответствует API ввода и сообщает о соответствующих событиях ввода подключенных HID.

 

Для того, чтобы понять путь кода для USB клавиатуры, вернёмся к Рисунку 7.3 и изменим аппаратно-зависимую левую половину. Заменим контроллер клавиатуры в квадратике "Оборудование ввода" на контроллер USB, serio на уровень ядра USB и квадратик "Драйвер устройства ввода" на драйвер usbhid.

 

Для Bluetooth клавиатуры заменим на Рисунке 7.3 контроллер клавиатуры на набор микросхем Bluetooth, serio на уровень ядра Bluetooth и квадратик "Драйвер устройства ввода" на драйвер Bluetooth hidp.

 

USB и Bluetooth подробно рассматриваются в Главе 11, "Универсальная последовательная шина" и в Главе 16, "Linux без проводов", соответственно.

Мыши

Мыши, как и клавиатуры, бывают с разными возможностями и имеют различные варианты взаимодействия. Давайте посмотрим на обычно использующиеся.

PS/2 мышь

Мыши генерируют относительные передвижения по осям X и Y. Кроме того, они имеют одну или несколько кнопок. Некоторые из них также имеют колёсико прокрутки. Драйвер устройства ввода для устаревшей PS/2-совместимой мыши для взаимодействия с контроллером основывается на уровне serio. Драйвер событий ввода для мышей, называемый mousedev, сообщает события мыши  пользовательским приложениям с помощью /dev/input/mice.

Пример драйвера: Мышь-колёсико

Чтобы получить настоящий драйвер устройства мыши, давайте преобразуем вращающееся колёсико, рассматриваемое в Главе 4, "Создание основы", в вариант обычной PS/2 мыши. "Мышь-колёсико" создаёт одномерное движение по оси Y. Повороты колёсика по часовой стрелке и против часовой стрелки создают положительные и отрицательные относительные Y координаты соответственно (как колесо прокрутки на мыши), а нажатие на колёсико приводит к событию нажатия на левую кнопку мыши. Мышь-колёсико идеально подходит для навигации по меню в таких устройствах, как смартфоны, КПК и музыкальные плееры.

 

Драйвер устройства мыши-колёсика, реализованный в Распечатке 7.3, работает с оконными системами, такими как X Windows. Чтобы увидеть, как драйвер заявляет о своих похожих на мышь возможностях, посмотрите на roller_mouse_init(). В отличие от  драйвера вращающегося колёсика в Распечатке 4.1 Главы 4, драйверу мыши-колёсика не нужны методы read() или poll(), так как о событиях сообщается с использованием API ввода. Обработчик прерывания от колёсика roller_isr() также соответственно изменяется. Убираем служебные действия, выполняемые в обработчике прерывания, использующие очередь ожидания, спин-блокировку и процедуру store_movement() для поддержки read() и poll().

 

В Распечатке 7.3, + и - в начале строк обозначают отличия от драйвера вращающего колёсика, реализованного в Распечатке 4.1 Главы 4.

 

Распечатка 7.3. Драйвер мыши-колёсика

 

Код:

+ #include <linux/input.h>

+ #include <linux/interrupt.h>

+ /* Device structure */

+ struct {

+     /* ... */

+     struct input_dev dev;

+ } roller_mouse;

+ static int __init

+ roller_mouse_init(void)

+ {

+     /* Выделяем память для структуры устройства ввода */

+     roller_mouse->dev = input_allocate_device();

+

+     /* Можем генерировать щелчок и относительное передвижение */

+     roller_mouse->dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REL);

+     /* Можеи двигаться только по оси Y */

+     roller_mouse->dev->relbit[0] = BIT(REL_Y);

+

+     /* Клик должен восприниматься как щелчок левой кнопки мыши */

+     roller_mouse->dev->keybit[LONG(BTN_MOUSE)] = BIT(BTN_LEFT);

+     roller_mouse->dev->name = "roll";

+

+     /* Для записи в /sys/class/input/inputX/id/ */

+     roller_mouse->dev->id.bustype = ROLLER_BUS;

+     roller_mouse->dev->id.vendor = ROLLER_VENDOR;

+     roller_mouse->dev->id.product = ROLLER_PROD;

+     roller_mouse->dev->id.version = ROLLER_VER;

+     /* Регистрируемся в подсистеме ввода */

+     input_register_device(roller_mouse->dev);

+}

 

/* Глобальные переменные */

- spinlock_t roller_lock = SPIN_LOCK_UNLOCKED;

- static DECLARE_WAIT_QUEUE_HEAD(roller_poll);

 

/* Обработчик прерывания колёсика */

static irqreturn_t

roller_interrupt(int irq, void *dev_id)

{

    int i, PA_t, PA_delta_t, movement = 0;

 

    /* Получить входные сигналы с битов 0, 1 и 2

       порта D, как показано на Рисунке 7.1 */

    PA_t = PA_delta_t = PORTD & 0x07;

 

    /* Ждём, пока состояние порта меняется.

      (Добавляем небольшое время ожидания в цикле) */

    for (i=0; (PA_t==PA_delta_t); i++){

        PA_delta_t = PORTD & 0x07;

    }

 

    movement = determine_movement(PA_t, PA_delta_t);

-   spin_lock(&roller_lock);

-

-   /* Сохраняем движение колёсика в буфере для

-      дальнейшего доступа через входные точки read()/poll() */

-   store_movements(movement);

-

-   spin_unlock(&roller_lock);

-

-   /* Будим входную точку опроса, которая должна бы

-      спать, ожидая вращение колёсика */

-   wake_up_interruptible(&roller_poll);

-

+   if (movement == CLOCKWISE) {

      input_report_rel(roller_mouse->dev, REL_Y, 1);

+   } else if (movement == ANTICLOCKWISE) {

      input_report_rel(roller_mouse->dev, REL_Y, -1);

+   } else if (movement == KEYPRESSED) {

      input_report_key(roller_mouse->dev, BTN_LEFT, 1);

+   }

  input_sync(roller_mouse->dev);

    return IRQ_HANDLED;

}

 

Устройства позиционирования

Устройство позиционирования (Trackpoint) является указывающим устройством, поставляемым на некоторых ноутбуках интегрированным с клавиатурой типа PS/2. Это устройство включает в себя джойстик, расположенный между клавишами, и кнопки мыши, расположенные под кнопкой пробела. Оно в основном функционирует как мышь, так что вы можете работать с помощью драйвера мыши PS/2.

 

В отличие от обычной мыши, устройство позиционирования предлагает большее управление движением. Вы можете скомандовать контроллеру устройства изменить свойства, такие как чувствительность и инерция. Для создания и управления связанными узлами sysfs ядро имеет специальный драйвер, drivers/input/mouse/trackpoint.c. Для полного набора конфигураций параметров устройства позиционирования смотрите в /sys/devices/platform/i8042/serioX/serioY/.

Сенсорные панели

Сенсорная панель является мыше-подобным указывающим устройством, обычно встречающимся на ноутбуках. В отличие от обычной мыши, сенсорная панель не имеет движущихся частей. Она может создавать совместимые с мышью относительные координаты, но обычно используется операционными системами в более мощном режиме, который даёт абсолютные координаты. Протокол связи, используемый в абсолютном режиме, похож на протокол мыши PS/2, но не совместим с ним.

 

Основной драйвер PS/2 мыши способен поддерживать устройства, которые отвечают разным вариантам простого протокола PS/2 мыши. Можно добавить в базовый драйвер поддержку нового протокола мыши, поставляя драйвер протокола через структуру psmouse. Например, если ваш ноутбук использует сенсорную панель Synaptics в абсолютном режиме, базовый драйвер PS/2 мыши для интерпретации потока данных пользуется услугами драйвера протокола Synaptics. Для полного понимания того, как протокол Synaptics работает в тандеме с базовым драйвером PS/2, посмотрим на следующие четыре куска кода, собранные в Распечатке 7.4:

 

Драйвер PS/2 мыши, drivers/input/mouse/psmouse-base.c, создаёт экземпляр структуры psmouse_protocol с информацией о поддерживаемых протоколах мыши (в том числе о протоколе сенсорной панели Synaptics).
 

Структура psmouse, определённая в drivers/input/mouse/psmouse.h, связывает вместе различные протоколы PS/2. synaptics_init() заполняет структуру psmouse адресами соответствующих функций протокола.
 

Функция обработчика протокола synaptics_process_byte(), установленная в synaptics_init(), вызывается из контекста прерывания, когда serio чувствует движение мыши. Если открыть synaptics_process_byte(), то можно увидеть, как движения по сенсорной панели сообщаются пользовательским приложениям через mousedev.

 

Распечатка 7.4. Драйвер протокола PS/2 мыши для сенсорной панели Synaptics

 

Код:

drivers/input/mouse/psmouse-base.c:

/* List of supported PS/2 mouse protocols */

static struct psmouse_protocol psmouse_protocols[] = {

{

    .type = PSMOUSE_PS2,        /* обычный обработчик PS/2 */

    .name = "PS/2",

    .alias = "bare",

    .maxproto = 1,

    .detect = ps2bare_detect,

},

/* ... */

{

    .type = PSMOUSE_SYNAPTICS,  /* Протокол сенсорной панели Synaptics */

    .name = "SynPS/2",

    .alias = "synaptics",

    .detect = synaptics_detect, /* Протокол обнаружен? */

    .init = synaptics_init,     /* Инициализация обработчика протокола */

},

/* ... */

}

 

drivers/input/mouse/psmouse.h:

/* Структура, которая объединяет вместе разные протоколы мыши */

struct psmouse {

    struct input_dev *dev; /* Устройство ввода */

    /* ... */

 

    /* Методы протокола */

    psmouse_ret_t (*protocol_handler)

            (struct psmouse *psmouse, struct pt_regs *regs);

    void (*set_rate)(struct psmouse *psmouse, unsigned int rate);

    void (*set_resolution)

            (struct psmouse *psmouse, unsigned int resolution);

    int (*reconnect)(struct psmouse *psmouse);

    void (*disconnect)(struct psmouse *psmouse);

    /* ... */

};

 

drivers/input/mouse/synaptics.c:

/* Метод init() протокола Synaptics */

int synaptics_init(struct psmouse *psmouse)

{

    struct synaptics_data *priv;

    psmouse->private = priv = kmalloc(sizeof(struct synaptics_data),

            GFP_KERNEL);

    /* ... */

 

    /* Это вызывается в контексте прерывания, когда ощущается движение мыши */

    psmouse->protocol_handler = synaptics_process_byte;

 

    /* Другие методы протокола */

    psmouse->set_rate = synaptics_set_rate;

    psmouse->disconnect = synaptics_disconnect;

    psmouse->reconnect = synaptics_reconnect;

 

    /* ... */

}

 

drivers/input/mouse/synaptics.c:

/* Если открыть synaptics_process_byte() и посмотреть в

   synaptics_process_packet(), можно увидеть сообщения ввода,

   сообщаемые пользовательским приложениям через mousedev */

static void synaptics_process_packet(struct psmouse *psmouse)

{

    /* ... */

    if (hw.z > 0) {

        /* Абсолютная X координата */

        input_report_abs(dev, ABS_X, hw.x);

        /* Абсолютная Y координата */

        input_report_abs(dev, ABS_Y,

        YMAX_NOMINAL + YMIN_NOMINAL - hw.y);

    }

    /* Абсолютная Z координата */

    input_report_abs(dev, ABS_PRESSURE, hw.z);

    /* ... */

    /* Левая кнопка TouchPad */

    input_report_key(dev, BTN_LEFT, hw.left);

    /* Правая кнопка TouchPad */

    input_report_key(dev, BTN_RIGHT, hw.right);

    /* ... */

}

 

USB и Bluetooth мыши

USB мышь обрабатываются тем же драйвером ввода (usbhid), который поддерживает USB клавиатуры. Аналогично, драйвер hidp, который реализует поддержку Bluetooth клавиатур, также заботится о Bluetooth мышах. Как и следовало ожидать, драйверы USB и Bluetooth мыши отправляют данные устройства через mousedev.

Сенсорные контроллеры

В Главе 6 мы реализовали драйвер устройства для последовательного сенсорного контроллера в виде дисциплины линии, называемой N_TCH. Подсистема ввода предлагает лучший и более простой способ для реализации такого драйвера. Переделаем конечный автомат в N_TCH в драйвер устройства ввода с помощью следующих изменений:

 

1.Для доступа к устройствам, подключенным к последовательному порту, serio предоставляет дисциплину линии, называемую serport. Воспользуемся услугами serport для общения с сенсорным контроллером.
 

2.Вместо передачи координатной информации на уровень tty, cгенерируем отчёты о вводе с помощью evdev, как это сделано для виртуальной мыши в Распечатке 7.2.

 

При этом сенсорный экран доступен для пользовательского пространства через /dev/input/eventX. Фактическая реализация драйвера остаётся в качестве упражнения.

 

Примером сенсорного контроллера, который не подключается через последовательный порт, является микросхема Analog Devices ADS7846, который взаимодействует через последовательный интерфейс периферийных устройств (Serial Peripheral Interface, SPI). Драйвер для этого устройства пользуется услугами ядра SPI, а не serio. SPI рассматривается в разделе "Шина последовательного интерфейса периферийных устройств" в Главе 8, "Протокол связи между микросхемами". Как и большинство драйверов сенсорных устройств, драйвер ADS7846 использует для отправки информации пользовательским приложениям интерфейс evdev.

 

Некоторые сенсорные контроллеры подключаются через USB. Примером может служить сенсорный USB контроллер 3M, поддерживаемый drivers/input/touchscreen/usbtouchscreen.c.

 

 

Многие КПК имеют 4-проводные резистивные сенсорные панели, наложенные на их ЖК дисплеи. X и Y пластины панели (два провода для каждой оси) подключаются к аналого-цифровому преобразователю (АЦП), который обеспечивает цифровое считывание аналоговой разности потенциалов, возникающей от прикасания к экрану. Драйвер ввода забирает координаты от АЦП и отправляет их в пользовательское пространство.

 

 

Различные экземпляры одной сенсорной панели могут давать несколько различные диапазоны координат (максимальные значения в направлениях X и Y) в связи с нюансами производственных процессов. Чтобы изолировать приложения от такого изменения, сенсорные экраны перед использованием калибруются. Калибровка, как правило, выполняется через GUI путём показа крестиков на границах экрана и других точках, с просьбой к пользователю прикоснуться к этим точкам. Сгенерированные координаты программируются в сенсорный контроллер с помощью соответствующих команд, если он поддерживает самокалибровку, или используются для масштабирования потока координат в программном обеспечение в противном случае.

 

Подсистема ввода также содержит драйвер событий, называемый tsdev, который генерирует координатную информацию в соответствии с протоколом сенсорного экрана Compaq. Если ваша система сообщает о событиях прикосновений через tsdev, приложения, которые понимают этот протокол, могут извлекать данные сенсорного ввод из /dev/input/tsX. Однако, этот драйвер запланирован на удаление из основной ветки ядра в пользу библиотеки пользовательского пространства tslib.  То, что уйдёт из дерева исходных кодов ядра, перечисляет Documentation/feature-removalschedule.txt.

Акселерометры

Акселерометр измеряет ускорение. Некоторые ноутбуки IBM/Lenovo имеют акселерометр, который определяет внезапное движение. Сгенерированная информация используется для защиты от повреждений жёсткого диска с использованием механизма, называемого Активная система защиты жёсткого диска (Hard Drive Active Protection System, HDAPS), аналогично тому, как автомобильные подушки безопасности защищают от травм пассажира. Драйвер HDAPS реализован как драйвер платформы, который регистрируется в подсистеме ввода. Для передачи X и Y компонентов обнаруженного ускорения он использует evdev. Для выявления таких условий, как удар и вибрация, и выполнения защитных действий, таких как парковка головки жёсткого диска, приложения могут читать события ускорений через /dev/input/eventX. Следующая команда выплёскивает вывод, если сдвинуть ноутбук (предположим, что для HDASP определён event3):

 

bash> od –x /dev/input/event3

0000000 a94d 4599 1f19 0007 0003 0000 ffed ffff

...

 

Акселерометр также предоставляет такую информацию, как температура, активность клавиатуры и мыши, всё, что можно извлечь с помощью файлов в /sys/devices/platform/hdaps/. Из-за этого драйвер HDAPS является частью подсистемы ядра аппаратного мониторинга (hwmon). Мы поговорим о мониторинге оборудования в разделе "Мониторинг оборудования с помощью LM-Sensors" следующей главы.

События вывода

Некоторые драйверы устройств ввода также обрабатывают события вывода. Например, драйвер клавиатуры может зажечь индикатор CAPSLOCK, а драйвер динамика ПК может издать гудок. Давайте последнее рассмотрим поближе. Во время инициализации драйвер динамика заявляет о своей возможности вывода путём установки соответствующих evbits и регистрации процедуры обратного вызова для обработки события вывода:

 

Код:

drivers/input/misc/pcspkr.c:

static int __devinit pcspkr_probe(struct platform_device *dev)

{

    /* ... */

 

    /* Биты возможностей */

    pcspkr_dev->evbit[0] = BIT(EV_SND);

    pcspkr_dev->sndbit[0] = BIT(SND_BELL) | BIT(SND_TONE);

 

    /* Процедура обратного вызова */

    pcspkr_dev->event = pcspkr_event;

 

    err = input_register_device(pcspkr_dev);

    /* ... */

}

/* Процедура обратного вызова */

static int pcspkr_event(struct input_dev *dev, unsigned int type,

                        unsigned int code, int value)

{

    /* ... */

 

    /* Программирование ввода/вывода для издавания гудка */

 

    outb_p(inb_p(0x61) | 3, 0x61);

    /* дать команду для счётчика 2, записать 2 байта */

    outb_p(0xB6, 0x43);

    /* выбрать требуемую частоту */

    outb_p(count & 0xff, 0x42);

    outb((count >> 8) & 0xff, 0x42);

 

    /* ... */

}

 

Чтобы издать звук, драйвер событий клавиатуры генерирует звуковое событие (EV_SND) следующим образом:

 

input_event(handle->dev, EV_SND,   /* Тип */

                         SND_TONE, /* Код */

                         hz        /* Значение */);

 

Это вызывает выполнение процедуры обратного вызова, pcspkr_event(), и вы слышите звуковой сигнал.

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