Тестируем поддержку USB

Drivers for periphery equipment
  • У меня другой подход. Не вызывать таймерную функцию, а активировать сигнализирующее событие.
    Собственно событие

    Code: Select all

    typedef struct
    {
        u32_t  code;
        u32_t  data[5];
    }kevent_t;
    
    С точки зрения ядра событие - объект ядра и принадлежит создавшему его потоку.

    Code: Select all

    struc EVENT
    {
       .magic       dd ?   ;'EVNT'
       .destroy     dd ?   ;internal destructor
       .fd          dd ?   ;next object in list
       .bk          dd ?   ;prev object in list
       .pid         dd ?   ;owner id
    
       .id          dd ?   ;event uid
       .state       dd ?   ;internal flags
       .code        dd ?
                    rd 5
       .size     =  $ - .magic
       .codesize =  $ - .code
    }

    Немного про события ядра и не только
    Код находится в gui/event.inc
    Основные функции: create_event() raise_event() wait_event() get_event_ex()
    Используется в звуковой подсистеме (RaiseEvent в mixer.asm и WaitEvent в infinity.asm) видеодрайвере (radeon_fence.c) и в драйвере drivers/usb/uhci (GetEvent в usb.c RaiseEvent и WaitEvent в hcd.inc)
    Пример работы в Infinity.
    Драйвер создаёт событие для каждого звукового буфера. Функции refill() и refill_ring(), вызываемые из обработчика прерываний, активируют события когда необходимо пополнить звуковой буфер. Приложение ожидает событие через вызов 68.14 get_event_ex() или это делает wave_out() в режиме ядра через вызов WaitEvent.
    В драйвере usb.
    Для каждого запроса request_t создаётся событие. Обработчик прерываний hc_interrupt() проходит по списку запросов и для каждого завершённого запроса активирует событие (там нет обработки ошибок и всё очень примитивно сделано) Поток драйвера в бесконечном цикле выбирает события GetEvent(), получает указатель на запрос и вызывает его обработчик.
  • В принципе, события можно активировать аппаратно, с помощью механизма MSI.

    При этом не требуется ни отдельный IRQ-канал, ни промежуточная обработка прерывания в драйвере.
  • Это подойдёт только для простых флагов. В данном случае надо перекидывать объект из одного списка в другой (у меня была паранойя в связи с возможной утечкой памяти в ядре). Плюс надо сделать правильную блокировку, как в мьютексах. Последнее и не подходит для аппаратного решения. Или monitor/mwait нам в помощь ?
  • CleverMouse
    Приношу 1000 извинений за очередной оффтопик в Вашей теме.

    Serge
    Я всегда думал, что системные события и должны быть по сути простыми флагами.
    Перекидыванием объектов должны заниматься соответствующие драйверы и/или системные службы.

    Насчет мьютексов -- в них нет никакой необходимости: можно явно размещать мессиджи только в некэшируемых страницах.

    А можно и не размещать: MSI-пакет всегда передается со сброшенным атрибутом NoSnoop, так что после передачи кэш будет автоматически объявлен недостоверным.
  • art_zh
    Хозяйки нет, гуляем :D
    Насчет мьютексов -- в них нет никакой необходимости: можно явно размещать мессиджи только в некэшируемых страницах.
    Как синхронизировать доступ к ресурсам без мьютексов ?
    Некешируемая страница - замечательный тормоз. А если там ещё опрос в цикле, так вообще ядро встанет. В некоторых ситуациях простота приводит к воровству. Взять наш старый мьютекс
    align 4
    wait_mutex:
    ;;Maxis use atomic bts for mutex 4.4.2009
    push eax
    push ebx
    .do_wait:
    bts dword [ebx],0
    jnc .locked
    call change_task
    jmp .do_wait
    .locked:
    pop ebx
    pop eax
    ret
    Всё просто и замечательно работает, если не считать того, что поток получает управление независимо от состояния блокировки. Подумаешь, два лишних переключения контекста. Но каждое переключение контекста это сброс TLB. А сброс TLB даёт пенальти равноценное копированию одной страницы (Это не я придумал, программисты QNX RIM. Если в ядре начнут одновременно крутиться десятки таких блокировок, потеря тактов станет заметна.

    Кстати, описанный тобой способ доставки сообщения уже используется в Колибри. Правда без MSI.
  • Serge
    Ты не понял (или это я за 10 лет разучился внятно излагать простые вещи по-русски), я вовсе не против мьютексов, и тем более не против твоего оптимизированного варианта мьютексов.

    Я лишь уточнил, что для хардверно-генерируемых системных событий мьютекс не нужен вообще.
    Когерентность аппаратной записи в список событий гарантируется всегда - даже если сам список событий находится в кэшируемой области памяти.

    Если состояние события не изменилось - структура EVENT будет выбрана из кэша мгновенно.
    Если же в результате MSI-цикла что-то (допустим, EVENT.state) в этой структуре было перезаписано по инициативе внешнего устройства - тогда соответствюущий кэш-блок будет объявлен недостоверным, и контроллер кэша сразу же перезагрузит его. Такой механизм называется Cache Snooping и стандартизирован для всех платформ с PCI 2.2+ и PCI Express.

    В самом худшем случае - при следующем опросе процессору придется подождать пару наносекунд, пока не перезагрузится этот кэш-блок.
    Но скорее всего - не придется, к этому моменту новое состояние уже будет сидеть в кэше.
    Кстати, описанный тобой способ доставки сообщения уже используется в Колибри. Правда без MSI.
    ???
    Железо пишет прямо в список событий, без активации IRQ ?
  • art_zh
    Теперь понял. К сожалению при текущей реализации EVENT не взлетит. Там надо не только флаги установить, но ещё и перекинуть из одного списка в другой.
  • Serge
    Теперь и до меня дошло :)
    Жаль, было бы красиво.
    И с APIC не пришлось бы заморачиваться, и механизм был бы независимый от конкретного чипсета.
    Правда, только для новых платформ.
  • Железо пишет прямо в список событий, без активации IRQ ?
    Ага, заинтриговал :D Не совсем, или совсем нет :D Писать в список событий не получается, почему я уже указывал. У gpu есть блок специальных scratch регистров. Для них установливается базовый адрес и запись в этот регистр приводит к записи в память CPU. Если не ошибаюсь scrath -> scratch_base+i*8. В драйвере механизм используется для подтверждения выполнения командных буферов. Каждому буферу присваивается свой ID (простой монотонный счетчик) и после команд на исполнение буфера идёт команда записи ID в scratch регистр

    Code: Select all

    Псевдокод командного процессора
    execute buffer_1
    wait idle
    write scratch[], 1
    generate interrupt ; совсем не обязательно, но тогда придётся крутиться в режиме опроса. 
    
    execute buffer_2
    wait idle
    write scratch[], 2
    generate interrupt
    ...
    execute buffer N
    wait idle[
    write scratch[], N 
    generate interrupt
    ...
    
    Драйвер читает значение id из памяти, проходит по списку запросов, и завершает все запросы у которых id меньше. Они уже выполнены. Конечно таким образом в EVENT не запишешь, но простой мьютекс разблокировать можно. Хотя это будет изврат.
    И с APIC не пришлось бы заморачиваться, и механизм был бы независимый от конкретного чипсета.
    Без прерываний и настоящей блокировки ожидающих потоков будут очень большие накладные расходы на пустой опрос. Для системы с одним - двумя потоками нормально. Но при большем числе потоков будет страшное бутылочное горлышко. Заткнутое пробкой. Несколько лет назад попались тесты разных вариантов мьютексов. В случае опроса с переключением задач маштабируемость системы падает очень быстро.
  • Serge wrote:Писать в список событий не получается, почему я уже указывал.
    ....
    К сожалению при текущей реализации EVENT не взлетит. Там надо не только флаги установить, но ещё и перекинуть из одного списка в другой
    Это в текущей реализации. Любую реализацию можно при необходимости изменить. Или дополнить.
    Например, если приложение ожидает событий с номером... ну, скажем, 16-31 :wink: , тогда ничего никуда перекидывать не нужно, достаточно просто проверить состояние таких аппаратно-устанавливаемых флажков, и если там не ноль - передать их приложению.
    А можно и что-нибудь поумнее придумать, тут широкий простор для полета фантазии.
    Serge wrote:У gpu есть блок специальных scratch регистров. Для них установливается базовый адрес и запись в этот регистр приводит к записи в память CPU. Если не ошибаюсь scrath -> scratch_base+i*8.
    ...
    Конечно таким образом в EVENT не запишешь, но простой мьютекс разблокировать можно. Хотя это будет изврат.

    У меня контроллер камеры может ввинтить каждый 100-й кадр прямо во фреймбуфер Колибри, в правый верхний угол.
    Подобным образом можно и в EVENT записать, и перекинуть его в другой список.
    Так вообще можно все ядро на уши поставить,- ты ведь говоришь про режим, в котором Bus Master в системе - царь и бог. Но что это за scratch-регистры и какой беспредел они могут замутить - зависит не столько от драйвера, сколько от конкретного железа.

    А функционал MSI-передач на 100% на 90% определяется именно драйвером. Потому что формат MSI-запроса строго стандартизирован, а его регистры (и адресный, и регистр данных) расположены в конфигспейсе и могут единообразно контролироваться системой. И зная этот формат, можно переадресовать MSI из APIC туда, где его обработка будет более эффективной.

    Без прерываний и настоящей блокировки ожидающих потоков будут очень большие накладные расходы на пустой опрос. Для системы с одним - двумя потоками нормально. Но при большем числе потоков будет страшное бутылочное горлышко. Заткнутое пробкой. Несколько лет назад попались тесты разных вариантов мьютексов. В случае опроса с переключением задач маштабируемость системы падает очень быстро.
    На каких задачах тестировались эти "разные варианты" мьютексов?
    Ведь есть же совершенно разные классы событий - для событий со среднебыстрым временем ожидания (LPT, HDD, USB-диски, видео, звук, сеть) имеет смысл задействовать IRQ, это очевидно.
    Зато устройства с длительным временем ожидания отклика (HID, USB-камеры, низкободный RS232), так же как и RT-устройства (которых по-уму в системе должно быть не более одной штуки) только выиграют от замены прерывания на опрос аппаратно-устанавливаемого флага.

    Я говорил не про абстрактный "случай опроса с переключением задач", а про конкретный вариант опроса перед переключением в ожидающую задачу.
    Нет события - не будет и переключения. Случилось "редкое" событие (нажали кнопку) - лишнее прерывание не генерится, TSS не перегружается, процессор вообще ничего не заметит. Всего лишь установится флажок, который будет опрошен при следующем прерывании таймера. Вторую кнопку за это время нажать невозможно.
  • Serge, и какое отношение всё это имеет к задачам "каждые 50мс генерировать нажатие на клавишу" и "каждую секунду опрашивать контроллер, не извлёк ли пользователь болванку"?
    Сделаем мир лучше!
  • CleverMouse
    Такое, что даже простая задача "каждую секунду опрашивать контроллер" решается разными способами.
    Можно в цикле опрашивать таймер, проверять контроллер раз в секунду, а остальное время переключать задачу.
    Или делегировать опрос таймера osloop. Всё равно ему нечего делать :roll:
    Или отсортировать таймеры в списке по времени срабатывания и проверять только когда подойдёт время.
    Наконец, проверять таймеры когда меняется счётчик тиков. То есть в обработчике прерываний от таймера и доставлять сообщение в поток через систему событий.

    У меня вот вопрос, каким образом поток usb получает сообщение о прерывании от обработчика прерываний ? Через опрос глобальной переменной ?
  • Возмущение по поводу core/timers.inc предполагает, что текущее решение этой задачи тебя чем-то не устраивает. Текущее решение - делегировать опрос osloop, точнее, через косвенный вызов функции таймера, при этом нет лишних переключений задач. Вещи типа сортировки таймеров по времени срабатывания или создания специальной таблицы таймеров, адресуемых несколькими младшими битами текущего времени, - это оптимизации, которые не затрагивают ни общей схемы работы, ни внешнего API и потому могут быть выполнены в любой момент; прямо сейчас, когда число таймеров мало, вряд ли имеет смысл ими заниматься. Если сигналить событие из обработчика таймера, мы возвращаемся к вопросу, какой поток должен это обрабатывать, - если главный, то непонятно, в чём преимущество перед текущей схемой, если специальный, то в CPU это будет уже третий поток по имени "OS/IDLE".

    Поток usb спит на событии - одном, выделенном при создании потока, чтобы не задаваться вопросами "а что будет, если обработчик прерывания не сможет выделить новое событие" и "не требует ли выделение события захвата какой-нибудь блокировки, которую мог захватить поток как раз в тот момент, когда пришло прерывание" - с таймаутом. Таймаут обычно равен бесконечности в OHCI и интервалу опроса событий подключения/отключения в UHCI, иногда меняясь на 100мс и 10мс.
    Сделаем мир лучше!
  • CleverMouse
    Мне не нравится нагрузка таймерами osloop. Первоначально цикл использовался для обработки пользовательского ввода. Потом к нему добавили сеть. Теперь его можно загрузить вообще чем угодно.
    Раз поток usb спит на событии то он может активироваться любым событием, в том числе и от таймера (через get_event_ex. Конечно не помешает некоторая доработка).
    Моя позиция такая: если драйверу требуется периодически выполнять некоторые действия он должен создавать для этого отдельный поток. Часто таким драйверам уже необходима работа в собственном потоке, поэтому добавление туда обработки таймера не создаёт проблем.
  • Who is online

    Users browsing this forum: No registered users and 0 guests