Прежде, чем объяснять код поддержки хост-контроллеров, необходимо рассказать о некоторых принципах работы железа, а также об используемых структурах данных. Как я выяснила при написании текста, одна статья обо всём уровне поддержки хост-контроллеров получилась бы слишком большой, поэтому вторая часть цикла — которую вы сейчас читаете — рассказывает о том, что необходимо знать для понимания кода, а описание действий, происходящие в коде, я отложу до следующей части.
Прерывания и потоки
Хост-контроллеры оповещают софт о происходящих событиях, генерируя прерывания. Прерывание может прийти и оторвать процессор от текущей задачи в любой момент времени; это накладывает жёсткие требования на обработчик прерывания. Обработчик прерывания не может захватывать никакие блокировки — ведь вполне возможно, что прерванный код как раз завладел блокировкой и уже не сможет её освободить. Единственным исключением является вариант спинлока, запрещающий прерывания на время блокировки, но из-за глобальности эффекта спинлок стоит применять пореже и для очень коротких участков кода. На однопроцессорных конфигурациях такой вариант вырождается в пару cli
/sti
без собственно спинлока, на многопроцессорных внутри cli
/sti
остаётся обычный спинлок. Кроме того, контроллер прерываний во время обработки одного прерывания блокирует остальные с тем же или более низким приоритетом.
По этим двум причинам в KolibriOS обработчики прерываний от хост-контроллеров USB передают основную часть работы в выделенный под USB поток ядра, а сами ограничиваются сообщением хост-контроллеру «спасибо, сигнал принят». Сам USB-поток имеет наивысший приоритет, чтобы задумавшиеся пользовательские приложения не мешали обработке. Все функции вышележащих уровней, которые вызываются из уровня поддержки хост-контроллера, работают в контексте потока USB и, как следствие, вполне могут использовать примитивы синхронизации. Приятным побочным эффектом является автоматическая сериализация вызовов: ни обработчик завершения второй передачи из очереди канала, ни функция DeviceDisconnected не будут вызваны, пока не закончит работу обработчик завершения первой передачи из очереди канала, что есть логичное требование к API.
Поток USB также иногда просыпается для обработки событий, отложенных по времени. Пример, о котором я позже расскажу подробнее: после события подключения устройства нужно выждать 100 миллисекунд перед дальнейшей обработкой. В этом случае поток проснётся при обнаружении подключения устройства и запланирует следующее пробуждение через 100 миллисекунд, уже не связанное с пробуждением из-за прерывания.
Читать полностью »