Возможно вы искали: 'Reel Deal Slots Nickel...'

May 15 2025 19:15:34
  • Как сделать 8Gamers.Ru домашней страницей?
  • Игры
    • База данных по играх
    • Игровые новости
    • Игровая индустрия
    • Обзоры на игры
    • Прохождения игр
    • Гайды к играм
    • Превью о играх
    • Игровые тизеры
    • Игровые арты
    • Игровые обои
    • Игровые скриншоты
    • Игровые обложки
    • Игровые трейлеры
    • Игровое видео
    • Вышедшие игры
    • Ближайшие релизы игр
  • Кино и ТВ
    • База данных по кино
    • Статьи о кино
    • Постеры
    • Кадры из кино
    • Кино трейлеры
    • Сегодня в кино
    • Скоро в кино
  • Комиксы и манга
    • Манга по алфавиту
    • База данных по комиксах
    • Читать онлайн комиксы
    • Читать онлайн манга
    • База персонажей
  • Читы и коды
    • Чит-коды для PC игр
    • Чит-коды для консольных игр
    • Трейнеры
    • Коды Game Genie
  • Моддинг
    • Модификации
    • Карты к играм
    • Программы для моддинга
    • Статьи о моддинге
  • Геймдев
    • Всё о создании игр
    • Список движков
    • Утилиты в помощь игроделу
    • Конструкторы игр
    • Игровые движки
    • Библиотеки разработки
    • 3D-модели
    • Спрайты и тайлы
    • Музыка и звуки
    • Текстуры и фоны
  • Рецензии
    • Игры
    • Кино
    • Аниме
    • Комиксы
    • Мангу
    • Саундтреки
  • Саундтреки
    • Лирика
  • Файлы
    • Патчи к играм
    • Русификаторы к играм
    • Сохранения к играм
    • Субтитры к кино
  • Медиа
    • Видео
    • Фото
    • Аудио
    • Фан-арты
    • Косплей
    • Фото с виставок
    • Девушки из игр
    • Рисунки
    • Рисуем онлайн
    • Фотохостинг
  • Юмор
    • Анекдоты
    • Афоризмы
    • Истории
    • Стишки и эпиграммы
    • Тосты
    • Цитаты
  • Флеш
    • Азартные
    • Аркады
    • Бродилки
    • Гонки
    • Для девочек
    • Для мальчиков
    • Драки
    • Квесты
    • Леталки
    • Логические
    • Мультфильмы
    • Открытки
    • Приколы
    • Разное
    • Спорт
    • Стратегии
    • Стрелялки
Статистика

Статей: 87772
Просмотров: 96111483
Игры
Injustice:  Gods Among Us
Injustice: Gods Among Us
...
Dark Souls 2
Dark Souls 2
Dark Souls II - вторая часть самой хардкорной ролевой игры 2011-2012 года, с новым героем, сюжето...
Battlefield 4
Battlefield 4
Battlefield 4 - продолжение венценосного мультиплеер-ориентированного шутера от первого ли...
Кино
Steins;Gate
Steins;Gate
Любители японской анимации уже давно поняли ,что аниме сериалы могут дать порой гораздо больше пи...
Ку! Кин-дза-дза
Ку! Кин-дза-дза
Начинающий диджей Толик и всемирно известный виолончелист Владимир Чижов встречают на шумной моск...
Обзоры на игры
• Обзор Ibara [PCB/PS2] 18357
• Обзор The Walking ... 18801
• Обзор DMC: Devil M... 19879
• Обзор на игру Valk... 15877
• Обзор на игру Stars! 17764
• Обзор на Far Cry 3 17948
• Обзор на Resident ... 16024
• Обзор на Chivalry:... 17508
• Обзор на игру Kerb... 17981
• Обзор игры 007: Fr... 16619
Превью о играх
• Превью к игре Comp... 17960
• Превью о игре Mage... 14464
• Превью Incredible ... 14721
• Превью Firefall 13479
• Превью Dead Space 3 16334
• Превью о игре SimC... 14730
• Превью к игре Fuse 15442
• Превью Red Orche... 15542
• Превью Gothic 3 16343
• Превью Black & W... 17354
Главная » Статьи » Разное » Разработка драйверов для USB-устройств под Linux (gcc driver linux usb)

Разработка драйверов для USB-устройств под Linux (gcc driver linux usb)

Ключевые слова: gcc, driver, linux, usb, (найти похожие документы)

From: Павел Курочкин <http://b4open.spb.ru/>
Date: Sun, 19 Nov 2006 17:02:14 +0000 (UTC)
Subject: Разработка драйверов для USB-устройств под Linux

Оригинал: http://b4open.spb.ru/bin/view/B4/UsbDriversInLinuxKernelArticle


Предисловие
-----------

Недавно мне довелось написать драйверы для специализированного
USB-сканера. Этот сканер работает в режиме непрерывной развертки с
частотой 10 кадров в секунду и обеспечивает скорость потока на чтение
3,2 МБайт/сек.

Драйверы сначала были разработаны под ядро Linux версии 2.6.15, а
затем, по требованию заказчика, адаптированы под ядро версии 2.4.26. К
моему удивлению, в обоих упомянутых версиях ядра написать драйвер не
составило особого труда. Для этого лишь необходимо четко представлять,
как работает USB и какими функциями обеспечивается взаимодействие
драйвера с USB-устройством. Все остальное - уже давно реализовано в
ядре linux.

При написании этой статьи я вижу перед собой следующие задачи:
1. рассказать об основных свойствах обмена устройств по USB
2. описать программный интерфейс взаимодействия драйвера с
USB-устройством
3. привести примеры использования функций ядра

Я надеюсь, что данная информация будет интересна программистам
системного уровня.


Введение в USB

Наверное, нет смысла говорить о таких очевидных вещах, как широкое
распространение USB, высокая скорость обмена по USB, возможность
горячего подключения устройств. Сейчас уже каждый пользователь ПК так
или иначе оценил преимущества и плюсы USB.

Поэтому сразу перейдем к менее очевидным вещам - к тому, как USB
устроен внутри.


Кто главный?

Одна из главных концепций USB заключается в том, что в USB-системе
может быть только один мастер. Им является host-компьютер. USB -
устройства всегда отвечают на запросы host-компьютера - они никогда не
могу посылать информацию самостоятельно.

Есть только одно исключение: после того, как хост перевел устройство в
suspend-режим, устройство может посылать запрос remote wakeup. Во всех
остальных случаях хост формирует запросы, а устройства отвечают на
них.


Направление

Хост всегда является мастером, а обмен данными должен осуществляться в
обоих направлениях:

* OUT - отсылая пакет с флагом OUT, хост отсылает данные устройству


* IN - отсылая пакет с флагом IN, хост отправляет запрос на прием
данных из устройства.


К этому нужно просто привыкнуть. Чтобы принять данные из устройства,
хост отсылат пакет с флагом IN. smile


Классификация пакетов в USB

По USB может передаваться несколько типов пакетов:
1. Token - запрос, содержит управляющую информацию: направление
операции (IN, OUT), номер endpoint
2. Data - пакет данных
3. Handshake - служебные пакеты, могут содержать подтверждение (ACK),
сообщение об ошибке, отказ (NACK)
4. Special - служебные пакеты, такие как PING

Более подробную информацию о пакетах, предусмотренных в спецификации
USB, можно прочитать в [2,3]. Рассмотрим несколько примеров
использования этих пакетов.

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

Чтобы отослать данные устройству, хост посылает пакет Token "OUT",
затем пакет Data. Если устройство готово обработать принятые данные,
оно отсылает пакет Handshake "ACK", подтверждающий транзакцию. Если
оно занято, оно отсылает отказ - Handshake "NACK". Если произошла
какая-то ошибка, то устройство может не отсылать Handshake.

Пример: отсылка данных хосту

Как уже говорилось, устройство самостоятельно никогда не отсылает
данные. Только по запросу. Чтобы принять данные, хост посылает пакет
Handshake "IN". Устройство по запросу может отослать пакет Data, а
затем Handshake "ACK". Либо может отослать Handshake "NACK", не
посылая Data.


Типы передачи данных

Спецификация USB определяет 4 типа потоков данных:
1. bulk transfer - предназначен для пакетной передачи данных с
размером пакетов 8, 16, 32, 64 для USB 1.1 и 512 для USB 2.0.
Используется алгоритм перепосылки (в случае возникновения ошибок),
а управление потоком осуществляется с использованием handshake
пакетов, поэтому данный тип является достоверным. Поддерживаются
оба направления - IN и OUT.
2. control transfer - предназначен для конфигурирования и управления
устройством. Также, как и в bulk, используются алгоритмы
подтверждения и перепосылки, поэтому этот тип обеспечивает
гарантированный обмен данными. Направления - IN (status) и
OUT(setup, control).
3. interrupt transfer - похож на bulk. Размер пакета - от 1 до 64
байт для USB 1.1 и до 1024 байт для USB 2.0. Этот тип гарантирует,
что устройство будет опрашиваться (то есть хост будет отсылать ему
token "IN") хостом с заданным интервалом. Направление - IN.
4. isochronous transfer - предназначен для передачи данных без
управления потоком (без подтверждений). Область применения -
аудио-потоки, видео-потоки. Размер пакета - до 1023 байт для USB
1.1 и до 1024 байт для USB 2.0. Предусмотрен контроль ошибок (на
приемной стороне) по CRC16. Направления - IN и OUT.


Endpoint - источник/приемник данных

Спецификация USB определеят endpoint (EP), как источник или приемник
данных. Устройство может иметь до 32 EP: 16 на прием и 16 на передачу.
Обращение к тому или иному endpoint'у происходит по его адресу.

Например, допустим, что хост хочет прочитать пакет данных из EP4 (4го
endpoint'a) устройства, пользуясь типом "bulk transfer". Он отсылает
пакет token "in", в котором указывает адрес endpoint'a.
Соответствующий источник в устройстве отсылает пакет данных хосту.
Аналогично происходит передача пакета данных.


Endpoint No.0

EP0 имеет особое значение для USB. Это Control EP. Он должен быть в
каждом USB-устройстве. Этот EP использует token "setup", чтобы
сигнализировать, что данные, отправляемые после него, предназначены
для управления устройством.

Используя этот EP0, хост может передавать setup-пакет длиной 8 байт и
данные, которые следуют за этим пакетом. Во многих случаях может
хватать передачи только setup-пакета. Однако устройство может
использовать и передачу данных по EP0, например для смены прошивок
компонентов устройства, или получения расширенной информации об
устройстве.

Рассмотрим немного подробнее setup-пакет.


EP0: setup-пакет

Содержимое setup-пакета представлено в таблице

Байт (No.) Имя Назначение

0 bmRequestType Поле для указания типа запроса, направления,
получателя
1 bRequest идентификатор запроса
2 wValueL 16-битное значение wValue, зависит от запроса.
3 wValueH
4 wIndexL 16-битное значение wIndex, зависит от запроса.
5 wIndexH
6 wLengthL количество байт, отсылаемых после setup-пакета.
7 wLengthH

Как видно из таблицы, setup-пакет содержит 5 полей. bmRequestType и
bRequest определяют запрос, а wValue, wIndex и wLength - его свойства.

Спецификация USB резервирует диапазон значений bRequest под
стандартные запросы. Каждое устройство обязано отвечать на все
стандартные запросы. В следующей таблице приведены только несколько
стандартных запросов, с которыми мы будем сталкиваться дальше.

bRequest Имя Описание

0x05 Set Address установка уникального адреса устройства в системе
0x06 Get Descriptor получение информации об устройстве. Тип информации
зависит от поля wValue.

Остальной диапазон устройство может использовать по своему усмотрению.
Более расширенную информацию можно получить в [2,3].


Распознавание устройства

Как происходит распознавание устройства, только что подключившегося к
системе? Уже упоминалось, что каждое устройство обязано обеспечить
доступ к EP0. Но кроме этого, оно еще должно отвечать на запросы,
указанные в спецификации USB для EP0. Пользуясь этими запросами и
происходит распознавание устройства в системе.

Алгоритм детектирования нового устройства следующий:
1. хост отсылает setup-пакет "Get Descriptor" (wValue = "device").
2. хост получает идентифицирующую информацию об устройстве
3. хост отсылает setup-пакет "Set address", после чего устройство
получает уникальный адрес в системе
4. хост отсылает остальные setup-пакеты "Get Descriptor" и получает
дополнительную информацию об устройстве: количество EP, требования
к питанию, и т.п.


Поддержка USB в ядре Linux

Программный интерфейс для взаимодействия с USB устройствами в ядре
Linux очень прост. За простым интерфейсом скрываются все алгоритмы
отсылки запросов, отслеживания подтверждений, контроля ошибок и т.п.
Все тонкости, описанные в предыдущей главе, уже реализованы в ядре
Linux.

В ядре файлы программ располагаются в drivers/usb/, а заголовочные
файлы - в include/linux/. Информации, представленной в этих
директориях, достаточно, чтобы самостоятельно написать драйвер для
любого USB-устройства.

Драйвер, взаимодействующий с USB-устройством(-ами), как правило,
выполняет следующие действия:
1. регистрация/выгрузка драйвера
2. регистрация/удаление устройства
3. обмен данными: управляющий и информационный.

Рассмотрим их по порядку более подробно, применительно к реализации
под ядро 2.6.15. Для большей ясности необходимо понимать основные
принципы UDM [4, 5], появившиеся в ядре 2.6.


Регистрация/выгрузка драйвера.

Регистрация USB-драйвера подразумевает:
1. заполнение структуры usb_driver
2. регистрацию структуры в системе

Структура usb_driver описана в include/linux/usb.h Рассмотрим наиболее
важные поля этой структуры.

struct usb_driver {
// ...
const char *name;
int (*probe) (struct usb_interface *intf,
const struct usb_device_id *id);
void (*disconnect) (struct usb_interface *intf);
const struct usb_device_id *id_table;
struct device_driver driver;
// ...
};


Очевидно, что name - это имя драйвера. id_table - это массив структур
usb_device_id. Этот список предназначен для определения cоответствия
подключаемого устройства определенным параметрам. Только те
устройства, которые соответствуют перечисленным параметрам, могут быть
подключены к драйверу. Если массив пуст, система будет пытаться
подключить каждое устройство к драйверу.

Поле driver говорит о том, что usb_driver унаследован от device_driver.

В самом простом случае каждый элемент id_table[i] содержит пару
идентификаторов:

* идентификатор производителя (Vendor ID)
* идентификатор устройства (Device ID).


Определение структуры usb_device_id можно видеть в
include/linux/mod_devicetable.h

probe и disconnect - это callback-функции, вызываемые системой при
подключении и отключении USB-устройства. probe будет вызыван для
каждого устройства, если список id_table пуст, или только для тех
устройств, которые соответствуют параметрам, перечисленным в списке.

Рассмотрим пример.

#include <linux/usb.h>

#define MY_DEV_NAME "my_usb_device"
#define PRODUCT_ID 0x1
#define VENDOR_ID 0x1234

static struct usb_device_id my_table [] = {
{ USB_DEVICE(VENDOR_ID, PRODUCT_ID) },
{ } // терминирующий элемент списка
};

static struct usb_driver my_driver = {
.name = MY_DEV_NAME,
.probe = my_probe,
.disconnect = my_disconnect,
.id_table = my_table,
};

static int __init my_module_init(void)
{
// регистрируем драйвер
return usb_register(&my_driver);
}

static void __exit my_module_exit(void)
{
// выгружаем драйвер
usb_deregister(&my_driver);
}


В этом примере регистрируется драйвер USB-устройства, которое имеет
значения полей PRODUCT_ID = 0x1, VENDOR_ID = 0x1234. Только для
устройства с такими параметрами будет вызвана функция my_probe.

Вызов my_probe фактически означает регистрацию устройства в драйвере
my_driver, а вызов my_disconnect - удаление устройства. Поэтому
перейдем к следующему этапу - регистрация/удаление устройства.


Регистрация устройства

Один зарегистрированный драйвер может "подключать" несколько
устройств. Для подключения устройства к драйверу система вызывает
функцию драйвера probe, которой передает 2 параметра:

static int my_probe(struct usb_interface *interface,
const struct usb_device_id *id)
{
// ...
}


interface - это интерфейс USB-устройства. Обычно USB-драйвер
взаимодействует не с устройством напрямую, а с его интерфейсом. id -
содержит информацию об устройстве. Если функция возвращает 0, то
устройство успешно зарегистрировано, иначе - система попытается
"привязать" устройство к какому-нибудь другому драйверу.

Для отключения устройства от драйвера система вызывает функцию
disconnect, которой передается один параметр - интерфейс:

static void my_disconnect(struct usb_interface *interface)
{
// ...
}


В общем случае, в функции probe для каждого подключаемого устройства
выделяется структура в памяти, заполняется, затем регистрируется,
например, символьное устройство, и проводится регистрация устройства в
sysfs. Также в функции probe может выполняться проверка на наличие у
устройства необходимых EP.

В качестве примера регистрации/удаления устройства лучше обратиться к
примеру из ядра linux, который находится в drivers/usb/usb-skeleton.c.


Использование USB Major

Остановимся более подробно на регистрации символьного устройства. Как
известно, для регистрации символьного устройства необходимо получить
число major - либо статически (обратиться к maintainer'у ядра и
занести его в include/linux/major.h), либо динамически (вызвать
register_chrdev с параметром major = 0). Когда символьное устройство
зарегистрировано, необходимо создать файл в директории /dev. Для этого
можно воспользоваться либо командой mknod (из user-space) или
функциями devfs, если данное ядро поддерживает devfs.

В ядре версии 2.6 вышеописанная процедура сильно упрощена благодаря
появлению udev и sysfs. В системе работает программа-daemon udevd,
которая отслеживает появление файлов в sysfs (/sys/class/). На
основании информации, читаемой из этих файлов, она автоматически,
пользуясь правилами udev для данного устройства, создает необходимые
файлы в dev.

В программном интерфейсе USB для этих целей есть функция
usb_register_dev, которая выполняет все необходимое, чтобы udev
выполнил вышеописанные процедуры:

extern int usb_register_dev(struct usb_interface *intf,
struct usb_class_driver *class_driver);
extern void usb_deregister_dev(struct usb_interface *intf,
struct usb_class_driver *class_driver);

usb_register_dev принимает на вход interface и class_driver. Структура
usb_class_driver выглядит следующим образом:

struct usb_class_driver {
char *name;
struct file_operations *fops;
int minor_base;
};

name - это имя устройства. Директория с этим именем появится в sysfs.
fops - файловые операции символьного устройства. minor_base - базовый
minor номер.


Функция usb_register_dev выполняет следующие действия:

* регистрирует символьное устройство с major номером 180 (см
include/linux/major.h) и резервирует диапазон из 16 minor номеров.
Поэтому minor_base должен иметь младший полубайт = 0.


* в зарезервированном диапазоне minor номеров выделяет один номер
для данного устройства. Этот номер записывает в interface->minor.


* создает все необходимые файлы в sysfs: после этого udev создает
файлы в /dev/


Вызов usb_deregister_dev выполняет обратные процедуры, поэтому должен
вызываться в функции disconnect.


Обмен данными с устройством

Рассмотрим обмен данными для наиболее часто используемых типов:
control и bulk. Соответствующие функции определены в
drivers/usb/core/message.c.

control transfer

Для отсылки/приема данных в 0й EP используется функция
usb_control_msg:

extern int usb_control_msg(struct usb_device *dev, unsigned int pipe,
__u8 request, __u8 requesttype, __u16 value, __u16 index,
void *data, __u16 size, int timeout);


dev - указатель на usb_device. Этот указатель может быть получен с
помощью вызова функции interface_to_usbdev(interface). pipe - EP pipe.
Этот параметр хранит в себе: тип передачи данных (bulk, control, ...),
направление, номер EP. Для задания pipe в include/linux/usb.h
определены макросы. Приведем только некоторые:

#define usb_sndctrlpipe(dev,endpoint)
((PIPE_CONTROL << 30) | __create_pipe(dev,endpoint))
#define usb_rcvctrlpipe(dev,endpoint)
((PIPE_CONTROL << 30) | __create_pipe(dev,endpoint) | USB_DIR_IN)

#define usb_sndbulkpipe(dev,endpoint)
((PIPE_BULK << 30) | __create_pipe(dev,endpoint))
#define usb_rcvbulkpipe(dev,endpoint)
((PIPE_BULK << 30) | __create_pipe(dev,endpoint) | USB_DIR_IN)


Параметры request, request_type, value, index - это поля setup-пакета.
Их содержимое зависит от приложения. data, size - это массив для
отсылки/приема. См выше и [2,3].

timeout - параметр, задающий в tick'ах, сколько времени дается на
отсылку/прием.

Необходимо помнить, что функция usb_control_msg вернет значение только
после того, как setup-пакет и данные будут доставлены, или, если
произойдет ошибка или тайм-аут. Эта функция использует вызов
interruptible_sleep_on, поэтому эту функцию нельзя вызывать в
контексте прерывания.

Пример. Отсылка setup-пакета с заданными bRequest и wValue может
выглядеть следующим образом:

// ...
int ret;
struct usb_device* udev = interface_to_usbdev(interface);

ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
bRequest, USB_TYPE_VENDOR,
wValue, 0, udev, 0, HZ);
// ...


В этом примере отсылается только setup-пакет. Поэтому data = udev,
size = 0.

bulk transfer

Для использования bulk transfer используется функция usb_bulk_msg:

extern int usb_bulk_msg(struct usb_device *dev, unsigned int pipe,
void *data, int size, int *actual_size,
int timeout);


Параметры dev, pipe, data, size, timeout имеют тот же смысл, что и в
usb_control_msg. actual_size - количество переданных байт в
действительности. actual_size <= size.

usb_bulk_msg имеет то же свойство, что и usb_control_msg - ее нельзя
вызывать в контексте прерываний (см выше).

Пример. Прием 100 байт из 5го EP может выглядеть следующим образом:

// ...
struct usb_device* udev = interface_to_usbdev(interface);
u8 buf[[ ;]]
int ret, actual_size;
ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, 5), buf, 100, &actual_size, HZ)
;
// ...


Заключение

Спецификация USB определяет большое количество типов передачи данных,
пакетов, алгоритмов. Все алгоритмы USB реализованы в ядре linux, а
программисту драйверов предоставляется удобный и простой интерфейс в
виде набора функций, макросов, структур. В ядре linux 2.6 подсистема
USB вписывается в модель UDM [3,4], что делает ее очень гибкой для
взаимодействия с udev, sysfs.

Павел Курочкин, НТЦ Метротек

Литература

1. http://www.kernel.org
2. EZ-USB FX2 Technical Reference Manual (ver 2.2), Cypress
Semiconductor, 2003
3. http://www.usb.org
4. Использование и расширение Linux Unified Device Model (UDM),
Курочкин Павел, Metrotek, 2006
5. Linux 2.6 Kernel: первые шаги к sysfs, Курочкин Павел,
Metrotek, 2006
1720 Прочтений •  [Разработка драйверов для USB-устройств под Linux (gcc driver linux usb)] [08.05.2012] [Комментариев: 0]
Добавил: Ukraine Vova
Ссылки
HTML: 
[BB Url]: 
Похожие статьи
Название Добавил Добавлено
• Разработка драйверов для USB-устрой... Ukraine Vova 08.05.2012
Ни одного комментария? Будешь первым :).
Пожалуйста, авторизуйтесь для добавления комментария.

Проект входит в сеть сайтов «8Gamers Network»

Все права сохранены. 8Gamers.NET © 2011 - 2025

Статьи
Рецензия на Pressure
Рецензия на Pressure
Чтобы обратить на себя внимание, начинающие маленькие разработчики, как правило, уходят в жанры, ...
Рецензия на Lost Chronicles of Zerzura
Рецензия на Lost Chron...
Игры, сделанные без любви и старания, похожи на воздушный шар – оболочка есть, а внутри пусто. Lo...
Рецензия на The Bridge
Рецензия на The Bridge
«Верх» и «низ» в The Bridge — понятия относительные. Прогуливаясь под аркой, можно запросто перей...
Рецензия на SimCity
Рецензия на SimCity
Когда месяц назад состоялся релиз SimCity, по Сети прокатилось цунами народного гнева – глупые ош...
Рецензия на Strategy & Tactics: World War 2
Рецензия на Strategy &...
Название Strategy & Tactics: World War II вряд ли кому-то знакомо. Зато одного взгляда на ее скри...
Рецензия на игру Scribblenauts Unlimited
Рецензия на игру Scrib...
По сложившейся традиции в информационной карточке игры мы приводим в пример несколько похожих игр...
Рецензия на игру Walking Dead: Survival Instinct, The
Рецензия на игру Walki...
Зомби и продукция-по-лицензии — которые и сами по себе не лучшие представители игровой биосферы —...
Обратная связь | RSS | Донейт | Статистика | Команда | Техническая поддержка