Основы портирования драйвера
В этой главе описываются некоторые принципиальные различия между традиционными драйверами стиля UNIX (включая BSD и Linux) и драйверами Набора I/O OS X. Как с любым предметом настолько же широко как портирование драйвера, существует много подробных данных, которые являются определенными для рассматриваемой технологической области. Эта глава не пытается охватить такие проблемы. Вместо этого это уделяет основное внимание более общим аспектам, оставляя подробные данные реализации как осуществление для читателя.
Ваш драйвер принадлежит ядра?
Этот документ прежде всего применяется к драйверам устройств в ядре. Не все драйверы устройств в OS X находятся в ядре. Например, устройствами наиболее интерфейса пользователя (клавиатуры, мыши, и т.д.) и графические и устройства отображения (принтеры, сканеры, и подобный) управляют драйверы пространства пользователя.
В целом Ваш драйвер должен быть в ядре, если это используется одновременно через многократные приложения или если его основной клиент находится в ядре. Примеры включают видеодрайверы, драйверы дискового или дискового контроллера, и т.д.
Существует несколько случаев, где драйвер должен находиться в ядре даже при том, что аналогичные устройства могут находиться в пространстве пользователя. В частности любое устройство на PCI или шина AGP должны быть в ядре, потому что те шины не экспортируют интерфейсы в пространство пользователя для соображений безопасности.
По большей части устройства PCI являются вещами, которые должны быть в ядре так или иначе. Существует несколько исключений, однако, таких как драйверы MIDI-устройства. В то время как драйверы MIDI-устройства, живые в пространстве пользователя, драйверы устройств PCI должны находиться в ядре. Таким образом платы PCI с MIDI-интерфейсами требуют двух драйверов: фактический драйвер Набора I/O для устройства (разделенный на подклассы от семейства драйвера PCI) и CFPlugIn в пространстве пользователя для обеспечения драйвера для CoreMIDI. Взаимодействие через интерфейс с драйверами через границы пользовательского ядра выходит за рамки этой книги. Для драйверов создания справки, охватывающих границы пользовательского ядра, необходимо связаться с Технической поддержкой Разработчика Apple.
Каков мой класс?
Прежде чем Вы фактически начнете портировать код, необходимо создать тупиковый драйвер с помощью корректного базового класса для типа устройства. Ссылка Платформы Ядра обеспечивает документацию для каждого из этих базовых классов.
В целом необходимо будет использовать два класса в драйверах: базовый класс (чье имя обычно заканчивается «драйвером») и класс куска (чье имя обычно заканчивается «устройством»).
Экземпляр базового класса будет представлять сам драйвер. Это должно тогда инстанцировать экземпляра класса куска для каждого устройства, которым это управляет. Эта абстракция куска обеспечивает механизм для других частей системы для фактического использования драйвера для передачи с устройством. Механизм для инстанцирования этого куска варьируется от класса до класса.
Для получения дальнейшей информации на этом процессе, необходимо считать Основные принципы IOKit и следовать Привет учебное руководство по Набору I/O.
Workloops по сравнению с обработчиками прерываний
Драйверы в OS X разработаны вокруг понятия workloop. (Много драйверов на других основанных на UNIX платформах записаны подобным способом, но не как часть основной модели драйвера.)
Идея workloop проста. Это - в основном просто поток помощника. Когда прерывание происходит вместо подпрограммы обработчика, выполняемой сразу, наборы ядра, распараллеливающие к “готовому выполниться”. Таким образом обработка прерываний происходит в приоритете потока ядра вместо в приоритете прерывания. Это гарантирует, чтобы прерывания не терялись, в то время как обработка подпрограмм имеет выключенные прерывания. Это также означает, что подпрограммы, работающие в контексте потока службы прерывания, не должны соблюдать те же правила как фактический обработчик прерываний; они могут блокировать, вызвать IOLog, и т.д.
Создание workloop относительно просто. Следующий код является примером регистрации для получения двух прерываний с помощью workloop:
/* class variables */ |
IOWorkLoop *myWorkLoop; |
IOInterruptEventSource *interruptSource; |
IOInterruptEventSource *DMAInterruptSource; |
/* code in start() */ |
myWorkLoop = IOWorkLoop::workLoop(); |
if( myWorkLoop == NULL) { IOLog( "org_mklinux_iokit_swim3_driver::start: Couldn't allocate ” |
“workLoop event source\n" ); |
return false; |
} |
interruptSource = IOInterruptEventSource::interruptEventSource( |
(OSObject*)this, |
(IOInterruptEventAction)&org_mklinux_iokit_swim3_driver::handleInterrupt, |
(Filter)&org_mklinux_iokit_swim3_driver::interruptPending, |
(IOService*)provider, |
(int)0 ); |
if ( interruptSource == NULL ) { |
IOLog( "org_mklinux_iokit_swim3_driver::start: Couldn't allocate “ |
“Interrupt event source\n" ); |
return false; |
} |
if ( myWorkLoop->addEventSource( interruptSource ) != kIOReturnSuccess ) { |
IOLog( "org_mklinux_iokit_swim3_driver::start - Couldn't add Interrupt” |
“event source\n" ); return false;} |
DMAInterruptSource = IOInterruptEventSource::interruptEventSource( |
(OSObject*)this, |
(IOInterruptEventAction)&org_mklinux_iokit_swim3_driver:: |
handleDMAInterrupt, (IOService*)provider, (int)1 ); |
if ( DMAInterruptSource == NULL ) { |
IOLog( "org_mklinux_iokit_swim3_driver::start: Couldn't allocate “ |
“Interrupt event source\n" ); |
return false; |
} |
if ( myWorkLoop->addEventSource( DMAInterruptSource ) != kIOReturnSuccess ) |
{ |
IOLog( "org_mklinux_iokit_swim3_driver::start - Couldn't add “ |
“Interrupt event source\n" ); |
return false; |
} |
myWorkLoop->enableAllInterrupts(); |
Методы handleInterrupt
и handleDMAInterrupt
будет теперь потребовал первые и вторые прерывания устройства (смещает 0 и 1), соответственно.
По большей части этот обработчик прерываний будет вести себя во многом как любой обработчик прерываний, был бы в любом другом OS (за исключением того, что можно вызвать printf
и IOLog
безопасно). Однако существуют некоторые исключения, особенно в области приоритета прерывания.
Необходимо отметить, что этот исходный пример использует IOFilterInterruptEventSource
вместо IOInterruptEventSource
. Это строго рекомендуется по двум причинам. Во-первых, если Вы запишете драйвер для многофункциональной платы PCI), то это избежит иметь OS вызов все драйверы для всех устройств на той карте. Во-вторых, если Ваша карта будет установлена в среду, где линия прерывания совместно используется, то это значительно улучшит производительность.
В отличие от этого IOInterruptEventSource
, когда Вы создаете IOFilterInterruptEventSource
объект, Вы передаете в функции фильтра в дополнение к обработчику.
В функции фильтра (org_mklinux_iokit_swim3_driver::interruptPending
в этом примере), необходимо протестировать, чтобы видеть, генерировало ли устройство прерывание, и возвратиться true
если Ваш драйвер должен обработать прерывание или false
если это принадлежит другому устройству на карте. Это обычно вовлекает опрос регистра в Ваше устройство для наблюдения, какие флаги прерывания установлены, затем храня значение для более позднего использования. Обратите внимание на то, что, потому что фильтр функционирует выполнения в основном контексте аппаратного прерывания, необходимо обработать его как любое другое прямое аппаратное прерывание — не блокируют, не вызывать IOLog
, не копируйте данные вокруг или выделяйте память и т.д.
Для получения дополнительной информации читайте Основные принципы IOKit и Руководство по программированию Ядра.
IOMemoryDescriptor по сравнению с kvtophys
Для любого данного расположения в памяти компьютера архитектуры ЭВМ определяют (по крайней мере) три отличных адреса: физический адрес (строка исполнительного адреса оценивает, как замечено процессором), логический / виртуальный адрес (адрес, как замечено программным обеспечением), и адрес шины (адрес, как замечено произвольным устройством ввода-вывода).
На большинстве 32-разрядных аппаратных средств физический адрес совпадает с адресом шины. Таким образом большинство людей игнорирует различие. В OS X необходимо обработать их как две разных вещи, особенно на 64-разрядных аппаратных средствах. Поэтому использование физических адресов, полученных с kvtophys, небезопасно, и функция kvtophys был фактически удален от доступности до KEXTs для предотвращения его неправильного использования.
Вместо этого необходимо использовать IOMemoryDescriptor
для Вашего выделения памяти так, чтобы можно было получить адрес шины, связанный с блоком памяти, и вручить ту информацию периферийному устройству.
Чтобы сделать это, Вы используете метод IOMemoryDescriptor::getPhysicalSegment
. Первым параметром является смещение с начала дескриптора. Вторым является адрес, где должна быть сохранена длина сегмента. Если длина сегмента возвратилась, меньше, чем общая длина дескриптора (который можно получить использование метода IOMemoryDescriptor::getLength
), тогда необходимо получить физический адрес следующего сегмента, и т.д., пока Вы не имели дело со всем дескриптором.
Для получения дополнительной информации о IOMemoryDescriptor
класс, см. справочную документацию Набора I/O, доступную от веб-сайта документации разработчика Apple.
Модель драйвера C++
Драйверы Набора I/O записаны в C++. Большинство драйверов устройств в других операционных системах записано в C. Это часто излагает интересные проблемы, прежде всего связанные со способом, которым хранятся специфичные для драйвера данные. Это также может привести ко многим другим неожиданностям. Они описаны далее в Соображениях Языка C++.
Типы данных
Типы данных имеют тенденцию иметь различные размеры в различных моделях драйвера. для предотвращения любых противных неожиданностей необходимо всегда проверять разрядную ширину различных типов данных в исходном OS, тогда явно использовать типы данных той же ширины. Например, если тип int
на Linux 32 бита, для максимальной долговечности, необходимо использовать тип uint32_t
в OS X.
Предложенные типы:
uint8_t
— 8-разрядное целое число без знакаuint16_t
— 16-разрядное целое число без знакаuint32_t
— 32-разрядное целое число без знакаuint64_t
— 64-разрядное целое число без знакаint8_t
— 8-разрядное целое число со знакомint16_t
— 16-разрядное целое число со знакомint32_t
— 32-разрядное целое число со знакомint64_t
— 64-разрядное целое число со знаком
Определение для этих типов может быть включено и в пространство пользователя и в код пространства ядра со следующим:
#include <stdint.h> |
Можно также использовать эквивалентные специфичные для Mac типы, такой как UInt32
, который может быть включен со следующим:
#include <libkern/OSTypes.h> |
Предупреждение: логические переменные могут быть проблематичными, поскольку нет никакого стандартного правила об их размере. Вместо того, чтобы использовать встроенные булевы типы, необходимо обычно придерживаться явного целого числа по Вашему выбору размеров для предотвращения непреднамеренных различий в выравнивании между C и кодом C++.
Обработка ioctl
OS X не использует ioctl
поддержка на уровне драйвера. Вместо этого высокоуровневый дескриптор семейств драйвера ioctl
вызовы и превращают их в вызовы явной функции. Подробные данные этих вызовов зависят от типа рассматриваемого драйвера.
Если семейство драйвера, которое Вы используете не обеспечивает ioctl
то, что Вам нужно, можно или зарегистрировать ошибку, запрашивающую что ioctl
будьте добавлены или можно предоставить аналогичное решение с помощью пользовательской пары интерфейса клиента/устройства. Они описаны более подробно в документе Руководство по проектированию Драйвера устройства IOKit, доступное от Apple Технический веб-сайт Публикаций.
Если ни одна из этих опций не приемлема, можно также иногда разделять пользовательский клиент BSD на подклассы для определенного класса устройств (такой как IOMediaBSDClient
) и добавьте дополнительный ioctl
поддержка. Например, для добавления ioctl для определенного типа носителей необходимо было бы переопределить следующие методы:
start
— определите, имеют ли носители желаемый типioctl
— обработайте ioctl
Краткий отрезанный код следует:
bool FloppyMediaBSDClient::start(IOService *provider) |
{ |
IOMedia * media = (IOMedia *) provider; |
u_int64_t size; |
IOStorage *storage; |
/* Make sure our provider’s start routine succeeds */ |
if (super::start(provider) == false) |
return false; |
/* Make sure our provider is a block storage device */ |
media = getProvider(); |
storage = media->getProvider(); |
this->driver = OSDynamicCast(IOBlockStorageDriver, storage); |
if (!this->driver) |
return false; |
/* |
* Determine if this is really the type of media we’re looking for, |
* in this case by its size. This could also be done using a more |
* specific OSDynamicCast if we are looking for a particular driver |
* to be upstream. |
*/ |
size = media->getSize() / (u_int64_t) 512; |
switch (size) { |
case 0: // unformatted |
case 720: // 360k |
case 800: // 400k |
case 1440: // 720k |
case 1600: // 800k |
case 2880: // 1440k |
case 2881: // 1440k also |
case 3360: // 1680k |
case 5760: // 2880k |
dIOLog("Floppy Disk Media Detected.\n"); |
break; |
default: |
dIOLog("Non-floppy disk media detected: %ld\n", (unsigned long)size); |
return false; |
} |
return true; |
} |
int FloppyMediaBSDClient::ioctl ( |
dev_t dev, |
u_long cmd, |
caddr_t data, |
int flags, |
struct proc * proc ) |
{ |
// |
// Process a Floppy-specific ioctl. |
// |
int *buffer; |
int error = 0; |
IOReturn status = kIOReturnSuccess; |
int formatflags; |
switch(cmd) { |
case FD_VERIFY: |
buffer = NULL; |
break; |
case FD_FORMAT: |
buffer = (int *)data; |
if (!buffer) { |
dIOLog("ioctl (floppy): null buffer!\n"); |
error=EINVAL; |
break; |
} |
} |
switch (cmd) |
{ |
case FD_VERIFY: |
{ |
IOLog("Got FD_VERIFY -- not supported yet.\n"); |
error = ENOTTY; |
break; |
} |
case FD_FORMAT: |
{ |
formatflags = *buffer; |
IOLog("Got FD_FORMAT -- not supported yet (flags = 0x%x).\n", |
formatflags); |
error = ENOTTY; |
break; |
} |
default: |
{ |
// |
// A foreign ioctl was received. Ask our superclass' opinion. |
// |
IOLog("fd: unknown ioctl, calling parent.\n"); |
error = super::ioctl(dev, cmd, data, flags, proc); |
break; |
} |
} |
return error; // (return error status) |
} |
sysctl и Обработка syscall
Во многом как ioctl
поддержка, драйверы OS X обычно не используют sysctl
или syscall
интерфейсы кроме предоставленных их соответствующими семьями. Вместо этого пользовательские пары интерфейса клиента/устройства используются. Они описаны более подробно в документе Руководство по проектированию Драйвера устройства IOKit, доступное от Apple Технический веб-сайт Публикаций.
Однако OS X действительно обеспечивает способы добавить дополнительный sysctl
и syscall
поддержка. Это описано подробно в Руководстве по программированию Ядра документа.
Уровни приоритета прерываний (IPL/SPL) по сравнению с Блокировками IOLock
Много основанных на UNIX моделей драйвера используют уровни приоритета прерываний в качестве средние значения защиты критических разделов в драйверах с помощью функций как вещи как splhigh
, splbio
, и splx
. Используя приоритет прерывания защитить критические разделы не работает особенно хорошо над системами SMP, и таким образом большинство операционных систем переезжает от этого проекта. Однако эти функции все еще широко используются.
OS X не поддерживает использование уровней приоритета прерываний для отключения прерываний для критической защиты раздела. Вместо этого необходимо использовать блокировки, семафоры или другие механизмы синхронизации. При компиляции кода с помощью этих функций код может скомпилировать, но не будет или работать должным образом (потому что рассматриваемая функция не), или приведет к панике ядра, в зависимости от используемых функций.
Вместо того, чтобы использовать эти функции, необходимо обычно использовать IOLock
взаимоисключающая блокировка. Они описаны в Ссылке Платформы Ядра и кратко получены в итоге ниже:
/* Allocate an I/O Lock */ |
IOLock *IOLockAlloc( void ); |
/* Free an I/O Lock */ |
void IOLockFree( IOLock * lock); |
/* Lock an I/O Lock */ |
static __inline__ void IOLockLock( IOLock * lock); |
/* Lock an I/O Lock if it doing so would not block. Returns true if |
lock was obtained. */ |
static __inline__boolean_t IOLockTryLock( IOLock * lock); |
/* Unlock an I/O Lock */ |
void IOLockUnlock( IOLock * lock); |
/* Wait on condition specified by event (where event is usually |
generated by taking the address of a variable, much like |
timeout/untimeout in BSD */ |
static __inline__ int IOLockSleep( |
IOLock * lock, |
void *event, |
UInt32 interType); |
/* Similar to IOLockSleep, only with a timeout specified */ |
static __inline__ int IOLockSleepDeadline( |
IOLock * lock, |
void *event, |
AbsoluteTime deadline, |
UInt32 interType); |
/* Wake up an event waiting on the condition specified by event |
The boolean oneThread specifies whether to signal on the condition |
(wake the first waiting thread) or broadcast (wake all threads |
that are waiting). |
*/ |
static __inline__ void IOLockWakeup(IOLock * lock, |
void *event, |
bool oneThread); |
Если Вы находите, что не можете жить без семафорной реализации, можно или реализовать тот с помощью IOLock
или используйте семафоры Маха, описанные в Руководстве по программированию Ядра. Прежнему строго рекомендуют, поскольку совместимость на уровне двоичных кодов не гарантируется для KEXTs то использование Маха непосредственно.