Пишем драйверы OpenSolaris
Автор: James Liu, июль 2008 http://www.sun.com/bigadmin/features/articles/write_dev_driver.jsp
Перевод: Яна Ситникова, июнь 2010 http://solaris11.ru/lib/opensolaris_device_drivers
Эта статья посвящена драйверам устройств для операционной системы Solaris. Сначала даётся обзорная информация об архитектуре ядра Solaris в целом и драйверов в частности. В конце создаётся простейший драйвер.
Прим. пер.: Так как операционная система Solaris стала платной, вы можете без ущерба опробовать описанное ниже в бесплатной операционной системе OpenSolaris.
Знакомство с драйверами Solaris
Когда несколько специалистов собираются вместе и начинают обсуждать ядра различных операционных систем, велика вероятность, что будет затронута тема поддержки устройств. Хотя именно ядро является основным связующим звеном между аппаратной и программной частями компьютера, именно драйверы периферийных устройств позволяют пользователю взаимодействовать с компьютером.
Доступ к устройствам осуществляется при помощи чётко определённого набора интересов. Операционная система Solaris была создана в 1992, поэтому она поддерживает множество интерфейсов для большинства современных устройств. Разработчики могли бы начать писать собственные драйвера для графических и сетевых карт под Solaris гораздо раньше. Однако до недавнего времени число опытных разработчиков драйверов Solaris было весьма невелико. В качестве основной причины многие эксперты указывают на закрытость исходного кода. Но с момента открытия кода ОС Solaris в июле 2005 и формирования проекта OpenSolaris число программистов, участвующих в разработке драйверов, заметно возросло.
В этой статье мы познакомимся с драйверами устройств Solaris. Сначала мы рассмотрим драйвера с точки зрения системного администратора, желающего установить и настроить бинарный драйвер для конкретного устройства, а затем с точки зрения разработчика, портирующего существующий драйвер в Solaris или желающего написать собственный. Мы надеемся развеять мистику, окружающую драйверы Solaris, и сделать разработку драйверов такой же понятной задачей, как и разработку приложений.
Модули ядра
Для непосвящённых проблема написания драйвера устройства может оказаться пугающей. Вам придётся слишком много изучить, чтобы написать свой первый драйвер. Одним из препятствий является то, что драйверы взаимодействуют одновременно с ядром и с железом. Стандарты качества здесь намного выше, чем при создании приложения, так как плохо написанный модуль может погубить ядро и привести к панике всю операционную систему. В противоположность драйверам, обычные приложения как в Solaris, так и в других продвинутых системах отделены от ресурсов ядра. Плохо написанная программа может привести к необоснованному потреблению системных ресурсов или неправильному поведению, но маловероятно, чтобы это привело к сбою ядра или других независимо запущенных программ. Если не учитывать факта, что код драйверов обычно исполняется в ядре, есть много сходства между написанием драйвера устройства и обычной программы. Как и приложения, драйверы представляют собой бинарный код, чаще написанный на Си, который компилируется и связывается с библиотеками.
Дерево устройств
В Солярисе драйверы организованы в иерархию устройств, известную как дерево устройств. Можно провести аналогию между деревом устройств и структурой файловой системы. Так же как файловая иерархия состоит из директорий, поддиректорий и файлов, каждый узел дерева может являться ветвью, подветвью или конечным узлом. В файловой системе devfs, отвечающую за хранение информации об устройствах, родительские узлы соответствуют шинам и контроллерам мостов (bridge controller), а конечные узлы другим устройствам и контроллерам. Драйверы для шин и контроллеров называются nexus drivers (прим. пер.: не существует устоявшегося термина на русском). Остальные называются leaf node drivers, они контролируют устройства, имеющие автономную функциональность. Файловая система устройств дальше рассмотрена подробнее.

Типы драйверов
Драйвера типа nexus контролируют микрочипы на материнской плате и обычно поставляются вместе с ОС Solaris. Эти драйверы редко реализуются сторонними разработчиками. Операционные системы Solaris и OpenSolaris включают драйверы nexus для разных шин и контроллеров, таких как:
Шины PCI и PCI-express
USB nexus
Традиционные ISA контроллеры для клавиатур, мышей и последовательных портов
Шины SCSI
IDE nexus
Устройства LAN
Так как Solaris включает в себя все драйверы nexus, большинство разработчиков заняты созданием драйверов типа leaf node, которые бывают двух типов: символьные и блочные. Символьные драйверы устройств используются для доступа к последовательным портам и специальным платам ввода-вывода. Символьные драйверы могут быть реализованы как STREAMS или не-STREAMS драйверы. Драйверы блочных устройств используются для доступа к дискам и массивам хранения данных.
Важно понимать, что драйверы устройств позволяют ядру Solaris получить доступ к железу на низком уровне. На более высоком уровне, ядро предоставляет приложениям набор стандартных интерфейсов для контроля над устройствами. Для того, чтобы ядро могло использовать устройство от конкретного производителя, ему требуется код, реализующий низкоуровневую функциональность. Эту работу и выполняет драйвер устройства.
Следующая схема показывает архитектуру операционной системы Солярис. Приложения находятся на пользовательском уровне, а драйверы в ядре. Железо представлено процессорами, дисками, сетевыми картами...

Solaris DDI/DKI
Возможно, наибольшая разница между программированием приложений и драйверов заключается в свободе реализации. Реализация приложения ограничена минимальным набором правил, которые определяют взаимодействие приложения с пользователем через текстовый или графический интерфейс. Реализация может быть очень гибкой и не зависеть от конкретной операционной системы. Например, мы можем написать программу с собственными функциями и обязательную main(), которая, как мы знаем, должна быть реализована в любой программе. При этом не нужно заботиться о распределении задач, потоках, доступу к памяти, выделении ресурсов и прямом доступе к устройствам ввода-вывода - все эти задачи выполняет за нас операционная система.
В программировании драйверов намного меньше свободы и больше опасностей. Мы уже не пишем автономный код. Вместо этого, мы пишем расширения, при помощи которых ядро Solaris получает доступ к устройству. Таким образом, мы должны предоставить ядру модуль драйвера, который реализует набор заранее определённых процедур, при помощи которых ядро будет предсказуемо контролировать устройство. Эти процедуры должны следовать жёстким ограничениям по именованию и кодированию. Без этих ограничений ядро должны было бы реализовывать все возможные процедуры для каждого драйвера, что было бы крайне непрактично. На сегодняшний день все операционные системы определяют стандартизированные интерфейсы драйверов устройств (device driver interface, DDI) и интерфейсы между устройством и ядром (device-kernel interface, DKI).
Solaris DDI/DKI состоит из двух частей. Одна предоставляет стабильный набор вызываемых функций, которые облегчают разработку. Эти функции легко переносимы, поэтому написанный код можно скомпилировать и использовать на платформах SPARC/x86/x64 без или почти без изменений исходного кода. Стандартная библиотека Си остаётся доступна разработчикам драйверов, однако при доступе к устройствам ввода-вывода и манипулировании с битами неиспользование стандартных функций DDI/DKI может привести к большим проблемам с размерами, порядком байт и переносимостью между платформами. Следующая диаграмма показывает типичный модуль драйвера с использованием Solaris DDI/DKI.

В добавление к вызываемым функциям, другая часть DDI/DKI определяет интерфейсы обратных вызовов, которые разработчик должен реализовать в драйвере. Ядро предполагает наличие реализации этих интерфейсов в каждом драйвере и вызывает функции обратного вызова на известной стадии жизненного цикла драйвера. Обратные вызовы включают интерфейсы, необходимые для управлением операциями загрузки, инициализации, присоединения или отсоединения, обработки прерываний и стандартного контроля ввода-вывода конкретного физического устройства. Наконец, разработчик должен реализовать дополнительные интерфейсы, которые потребуются для работы с конкретным контроллером, например, SCSI, SATA, USB или LAN.
Возникает естественный вопрос: как и где происходит контроль за состоянием драйвера? За это в Солярисе отвечают три структуры данных. На верхнем уровне находятся модульные операции, которые представлены структурой mod_ops. Эта структура отвечает за состояние модуля в Solaris. Структура mod_ops передаётся системе во время инициализации и завершении работы. Одна из ссылок к структуре mod_ops указывает на вторую структуру под названием dev_ops. Данная структура несёт информацию о состоянии устройства и хранит указатели на обратные вызовы attach(), detach(), getinfo() и probe(). Структура dev_ops также содержит ссылку на структуру cb_ops, которая включает список ссылок на специфичные функции DDI, реализованные в драйвере.
Жизненный цикл драйвера
Давайте исследуем жизненный цикл драйвера более детально. Когда ядро загружает драйвер, оно ожидает от драйвера реализации определённых процедур и передаёт ссылку на структуру mod_ops данного модуля. Необходимые процедуры, определённые в DDI/DKI, должны как минимум реализовывать обратные вызовы для _info() и _init(). И наоборот, при выгрузке драйвера ядро вызывает функцию _fini(). В итоге, мы имеем три обязательных обратных вызова, которые должны реализовать все драйверы:
_info(9E)
_init(9E)
_fini(9E)
После загрузки модуля (а также перед выгрузкой модуля) ядро ищет системные вызовы для инициализации и деинициализации устройства. Эти вызовы аналогичны системным вызовам для модулей, но предназначены для управления железом. Вот эти два вызова:
attach(9E)
detach(9E)
Все пять системных вызовов должны быть реализованы во всех драйверах. Также DDI/DKI определяет стандартные функции для доступа к устройству и совершения операций ввода-вывода:
open(9E)
close(9E)
read(9E)
write(9E)
Все приведённые системные вызовы являются функциями с определёнными аргументами, необходимыми ядру для взаимодействия с драйвером.
Используйте следующую команду для вывода сигнатуры функций и аргументов, а также примеров использования:
% man -s 9E function_name
Системное администрирование
Обзор 32-битных и 64-битных драйверов в ядре
На платформах x86, операционная система Solaris может работать как в 32-, так и в 64-битном режиме в зависимости от архитектуры процессора. По умолчанию, на 64-битной платформе Solaris загружается в 64-битном режиме, при этом драйверы должны быть также 64-битными модулями. Это происходит из-за того, что модули ядра разделяют то же адресное пространство, что и остальное ядро. Если платформа 32-битная, то Solaris загружается в 32-битном режиме вместе с 32-битными драйверами. Согласно общему правилу, бинарные файлы драйверов должны соответсвовать ядру по битовости. Однако обычные приложения не работают внутри ядра, поэтому 64-битное ядро может исполнять 32-битные и 64-битные приложения. 32-битное ядро может исполнять только 32-битные приложения.
Существует возможность насильно загрузить Solaris в 32-битном режиме на 64-битной системе путём редактирования загрузочного меню grub. Для этого либо отредактируйте файл /boot/grub/menu.lst и перезагрузитесь, либо перезагрузитесь и введите e на стадии появления меню начального загрузчика. Обычно требуется заменить строку kernel/amd64/unix или kernel/$ISADIR/unix на kernel/unix. Эту конфигурацию можно записать в файл /boot/grub/menu.lst, чтобы не вводить каждый раз. Нет никакого смысла загружаться в 32-битном режиме на 64-битной машине с точки зрения производительности. Однако мы делаем это, чтобы удостовериться в том, что драйвер работает одинаково хорошо в обоих режимах.
Установка модуля драйвера
Когда вы скомпилируете драйвер и захотите проверить его работоспособность, вам понадобится знать, где Solaris хранит модули ядра. Для платформ x86/x64 модули драйвера должны хранится в следующих директориях:
/kernel/drv
32-битные драйверы и файл driver.conf
/kernel/drv/amd64
64-битные драйверы
Прим. пер.: На платформе SPARC модули драйвера хранятся в каталоге /kernel/drv/sparcv9 (64-битные). Так как 32-битных машин SPARC почти не осталось, то для этой платформы можно писать только 64-битные драйверы.
Мы могли бы использовать и другие директории для хранения модулей, но рекомендуется использовать вышеуказанные директории. Обычно, когда мы собираем модуль драйвера, мы создаём 32-битные и 64-битные бинарные файлы и размещаем их в указанных директориях, даже если планируем работать только в одном из режимов. Это делает возможным использовать драйвер после переноса на другую платформу, либо после перезагрузки в другом режиме, как объяснялось выше.
Взаимодействие драйверов и системы. Дерево устройств.
Выше мы упомянули, что Дерево Устройств в ОС Solaris представлено файловой системой, известной как devfs. Эта файловая система управляет пространством имён всех физических устройств, известных операционной системе и смонтированных во время загрузки в директории /devices. Это не обычная файловая система, и к ней невозможно применять стандартные команды файловых систем. Операционная система самостоятельно управляет ей. Узлы создаются, когда модуль драйвера подключается к устройству во время инициализации (см. цикл жизни драйвера). Каждому узлу приписывается основной (major) и дополнительный (minor) номера, которые выводятся в названиях файлов. Основной номер обозначает конкретный модуль, который был загружен ядром для взаимодействия с устройством, а дополнительный номер отражает экземпляр устройства. По умолчанию, первый экземпляр устройства имеет номер 0. Если присутствуют дополнительные сходные устройства, которые используют тот же модуль драйвера, им приписываются номера 1, 2 и так далее.
В то время как файловая система /devices отображает все физические устройства, файловая система /dev содержит логические имена всех физических устройств. Другими словами, логические устройства в /dev являются символическими ссылками на настоящие узлы в /devices. Файловая система /dev является обыкновенной файловой системой, и ей можно управлять вручную, хотя и не рекомендуется.
Причина наличия двух отдельных директорий для файлов устройств заключается в том, что логические устройства могут быть использованы системой и приложениями. На другой платформе тем же логическим устройствам могут отвечать совершенно иные устройства от другого производителя, использующие другие драйверы. Рассмотрим для примера аудио-устройство. Если аудио работает, то в системе будут присутствовать логические устройства /dev/audio и /dev/audioctl. Так будет на любой платформе. Однако /dev/audio может указывать на аудио-устройство AC'97 на одной платформе и HD на другой платформе, и каждому из этих устройств требуется свой собственный драйвер. То же справедливо для устройств хранения данных. ОС Solaris обозначает диски SCSI как /dev/rdsk/cXtYdZsN, где X, Y, Z и N означают соответственно номер контроллера, целевой SCSI-номер, номер диска и номер раздела. Но этот файл может ссылаться на разные реальные устройства.
Заметьте, что хотя невозможно манипулировать содержанием каталога /devices, можно выполнять в нём команду ls(1). Таким образом, для проверки подсоединения драйвера к устройству можно вывести содержимое /devices и проверить в нём наличие интересующего нас устройства.
Конфигурационные файлы драйверов
Дойдя до этого места, некоторые читатели, возможно, ещё не поняли, каким образом ОС Solaris узнаёт, какой модуль необходимо загрузить. Как и в других системах, существует файл, в котором модули драйверов приписаны физическим устройствам. Этот файл:
/etc/driver_aliases
Другой текстовый файл приписывает имя физического устройства (обычно путь в /devices) номеру экземпляра драйвера:
/etc/path_to_inst
Каждому имени драйвера приписывается универсальный номер в файле:
/etc/name_to_major
Система хранит записи о правах доступа для каждого экземпляра драйвера в файле:
/etc/minor_perms
Хотя файлы driver_aliases, path_to_inst(4), name_to_major, и minor_perms можно править вручную, рекомендуется вносить любые изменения только при помощи команд, которые обсуждаются в следующем параграфе.
Команды для управления драйверами
Ниже приведён список команд, позволяющих манипулировать драйверами. Предпочтительнее использовать эти команды, чем вручную править конфигурационные файлы.
add_drv
Команда add_drv(1M) добавляет новый драйвер. Например, для добавления драйвера foobar, который будет управлять устройством PCI с полномочиями root, мы можем выполнить следующую команду:
# /usr/sbin/add_drv -i '"pci0909,5c"' \
-m '* 0600 root sys' foobar
Заметьте, что в некоторых случаях драйвер должен реализовывать или принадлежать к определённому классу устройств. Например, Host Bus Adapter (HBA) реализует интерфейс SCSI, хотя мы можем подключить к нему IDE, SATA или SAS. Чтобы указать системе, что такой драйвер экспортирует интерфейс SCSI, мы просто добавляем опцию -c scsi к команде add_drv(1M).
rem_drv
Команда rem_drv(1M) удаляет существующий драйвер. Она удаляет записи о драйвере из файла /etc/driver_aliases, но может не удалить драйвер из /kernel/drv до перезагрузки, в случае если модуль используется.
# /usr/sbin/rem_drv foobar
update_drv
Команда update_drv(1M) обновляет уже известные системе сведения о драйвере. Она часто используется при добавлении нового устройства PCI, если известно, что оно использует существующий драйвер. Эта команда использует тот же формат, что и add_drv. Например, если мы хотим, чтобы новое устройство PCI поддерживалось установленным драйвером foobar, мы можем выполнить следующую команду:
# /usr/sbin/update_drv -i '"pci0909,8f"' \
-m '* 0600 root sys' foobar
modinfo
Команда modinfo(1M) отображает информацию о загруженных модулях. Это хороший способ узнать, загружен ли определённый модуль и каков его размер.
modload
Команда modload(1M) служит для ручной загрузки модуля.
modunload
Команда modunload(1M) пытается выгрузить модуль. Не даёт эффекта, если модуль используется.
devfsadm
Команда devfsadm(1M) поддерживает соответствие между ссылками в /dev и ссылками в /etc/path_to_inst.
Сборка драйвера
Процесс сборки состоит из двух шагов: создания бинарного файла и связывания.
Шаг 1: Компилирование
Для создания бинарного файла мы можем выбрать компилятор Sun Studio (прим. пер.: новое название Oracle Solaris Studio) или GNU C compiler (gcc). Некоторые операционные системы (такие как Linux) принуждают разработчиков использовать конкретную версию компилятора для конкретной версии ядра, с тем чтобы новые модули ядра были бинарно совместимы с остальным ядром. В отличие от Linux, в ОС Solaris все бинарные интерфейсы строго контролируются и не меняются от версии к версии, поэтому можно спокойно использовать любую версию компиляторов Sun Studio или gcc.
Хотя это официально не поддерживается, бинарные файлы драйверов обладают прямой совместимостью в рамках одной версии Solaris. Другими словами, драйвер для Solaris 10 1/06 будет работать в Solaris 10 5/08 без изменений, если он использует только интерфейсы, определённые DDI/DKI. То же относится к драйверам для Solaris Express и OpenSolaris, скомпилированных для более ранних версий. Помните, что обратное неверно: драйвер, скомпилированный для новой версии может не работать в более старой версии.
Ниже мы приведём команды компилятору с несколькими примерами. Более подробно об этом вы можете прочесть в документации.
Если вы компилируете для 64-битной архитектуры x86 с помощью Sun Studio 10 или Sun Studio 11, используйте опции -xarch=amd64 и -xmodel=kernel:
# cc -D_KERNEL -xarch=amd64 -xmodel=kernel -c foobar.c
Мы должны использовать флаг -D_KERNEL, чтобы показать, что мы компилируем модуль ядра. В случае 64-битной платформы мы должны указать архитектуру (-xarch=amd64) и -xmodel для создания 64-битного объекта. В Sun Studio 12 и далее, опция -xarch=amd64 заменена на -m64, также как в gcc. Также вы можете скомпилировать 64-битный объект в 32-битной операционной системе. Однако по умолчанию создаётся 32-битный бинарный файл как в 32-битных, так и в 64-битных системах, если специально не задан флаг.
Если вы используете GNU C compiler на 64-битной архитектуре, выполните следующую команду:
# gcc -D_KERNEL -ffreestanding -m64 -c foobar.c
Прим. пер.: Не рекомендуется использовать gcc на платформе SPARC из-за низкой оптимизирующей способности. Используйте Oracle Solaris Studio или GCCFSS (GCC For Sun Systems).
При компилировании с помощью Sun Studio для 32-битной архитектуры:
# cc -D_KERNEL -c foobar.c
При компилировании с помощью GNU C compiler для 32-битной архитектуры:
# gcc -D_KERNEL -ffreestanding -c foobar.c
Шаг 2: Связывание
Все бинарные файлы должны быть связаны в единый бинарный объект. Для обычных модулей, которые не зависят от других драйверов или модулей ядра, просто выполните:
# ld -r -o foobar foobar.o
Во многих случаях, модуль драйвера имеет зависимости в виде других модулей, либо должен быть динамически связан с другими библиотеками ядра. Ниже мы приведём два примера.
Этот пример показывает, как связать объект аудио драйвера, который использует поддержку аудио из ядра и звуковой микшер. Чтобы динамически связать модуль с библиотеками ядра, используют совместно флаги -dy и -N.
# ld -r -dy -N misc/audiosup -N misc/mixer \
-N misc/amsrc -o audiodrv audiodrv.o
Следующий пример показывает, как связать типичный сетевой драйвер со слоем LAN (generic LAN driver (GLD)).
# ld -r -dy -N misc/gld -o nicdrv nicdrv.o
Используйте команду file(1) применительно к бинарному объекту, чтобы узнать, является ли данный модуль 32-битным или 64-битным. Наконец, мы рекомендуем поместить все вышеуказанные команды в Makefile для облегчения процедуры сборки.
Пример драйвера
Здесь мы покажем пример драйвера, который реализует минимальный набор интерфейсов. Позже будут приведены заголовочный и конфигурационный файлы для этого драйвера.
Простейший драйвер
Обычно драйвер состоит из одного или более файла на Си, которые реализуют функции, используемые ядром для доступа к драйверу. Принято декларировать структуры в каждом файле Си, где они используются, а не в заголовочном файле. Принято использовать имя драйвера в качестве префикса к названию функций и глобальных переменных, относящихся к данному драйверу. Например, процедура attach() реализуется как foobar_attach().
/*
* foobar.c - example skeleton driver
*/
#include <sys/errno.h>
#include <sys/conf.h>
#include <sys/cmn_err.h>
#include <sys/modctl.h>
#include <sys/sunddi.h>
#include <sys/stat.h>
#include "foobar.h"
static int foobar_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
static int foobar_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
static struct cb_ops foobar_cb_ops = {
nodev, /* no open */
nodev, /* no close */
nodev, /* no strategy (only for block drivers) */
nodev, /* no print */
nodev, /* no dump (only for block drivers) */
nodev, /* no read */
nodev, /* no write */
nodev, /* no ioctl */
nodev, /* no devmap */
nodev, /* no mmap */
nodev, /* no segmap */
nochpoll, /* no chpoll entry point */
ddi_prop_op, /* Use system-supplied prop_op entry point */
NULL,
D_NEW | D_MP
};
static struct dev_ops foobar_ops = {
DEVO_REV,
0,/* reference count: always 0 initially */
nulldev, /* No getinfo entry point */
nulldev, /* DEPRECATED: identify entry point */
nulldev, /* no probe entry point */
foobar_attach,
foobar_detach,
nodev, /* no reset entry point */
&foobar_cb_ops, /* Reference the cb_ops defined above */
(struct bus_ops *)NULL /* Not a nexus driver, so no bus_ops */
};
extern struct mod_ops mod_driverops;
static struct modldrv Modldrv = {
&mod_driverops, /* Use system-supplied mod_driverops */
"foobar driver v" FOOBAR_VERSION, /* Module Name/Version */
&foobar_ops,
};
static struct modlinkage Modlinkage = {
MODREV_1,
&Modldrv,
NULL
};
/*
* This bit of static data is used by the DDI to
* keep track of the per-instance driver "soft state"
*/
static void *soft_statep;
int
_init(void)
{
/*
* Initialize the soft state APIs so we can
* allocate soft state in foobar_attach()
*/
if (ddi_soft_state_init(&soft_statep,
sizeof (struct foobar_state), 1)
!= DDI_SUCCESS)
return (DDI_FAILURE);
if (mod_install(&Modlinkage) != 0) {
ddi_soft_state_fini(&soft_statep);
return (-1);
}
return (0);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&Modlinkage, modinfop));
}
int
_fini(void)
{
ddi_soft_state_fini(&soft_statep);
return (mod_remove(&Modlinkage));
}
static int
foobar_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
/* Use the instance number as the minor number */
instance = ddi_get_instance(dip);
if (ddi_soft_state_zalloc(soft_statep, instance)
== DDI_FAILURE)
return (DDI_FAILURE);
softp = ddi_get_soft_state(soft_statep, instance);
ASSERT(softp != NULL);
if (ddi_create_minor_node(dip, FOOBAR_MINOR_NAME,
S_IFCHR, instance, DDI_PSEUDO, 0)
!= DDI_SUCCESS) {
cmn_err(CE_WARN, "Minor creation failed!");
return (DDI_FAILURE);
}
softp->init_state |= FOOBAR_INIT_MINOR;
softp->dip = dip;
mutex_init(&softp->mutex, NULL, MUTEX_DRIVER, 0);
softp->buffer = (char *)kmem_alloc(FOOBAR_BUFLEN,
KM_SLEEP);
ddi_report_dev(dip); /* Announce we've attached! */
return (DDI_SUCCESS);
}
static int
foobar_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
int instance;
struct foobar_state *softp;
if (cmd != DDI_DETACH)
return (DDI_FAILURE);
/* Use the instance number as the minor number */
instance = ddi_get_instance(dip);
softp = ddi_get_soft_state(soft_statep, instance);
ASSERT(softp != NULL);
if (softp->init_state & FOOBAR_INIT_MINOR) {
/* Remove minor nodes associated with dip */
ddi_remove_minor_node(dip, NULL);
}
ASSERT(softp->buffer != NULL);
kmem_free(softp->buffer, FOOBAR_BUFLEN);
ddi_soft_state_free(soft_statep, instance);
return (DDI_SUCCESS);
}
Заголовочный файл
В заголовочном файле содержатся оставшиеся объявления.
/*
* foobar.h - example skeleton driver header
*/
#ifndef _FOOBAR_H
#define _FOOBAR_H
#define FOOBAR_INIT_MINOR 0x00000001
#define FOOBAR_VERSION "1.0"
#define FOOBAR_BUFLEN 1024
#define FOOBAR_MINOR_NAME "xyzzy"
struct foobar_state {
dev_info_t *dip; /* Opaque dev_info pointer */
int init_state; /* See FOOBAR_INIT_* */
kmutex_t mutex; /* driver lock */
char *buffer; /* message buffer */
};
#endif /* #ifdef _FOOBAR_H */
Конфигурационный файл
В нашем случае конфигурационный файл будет носить имя foobar.conf. Этот файл используется для переопределения внутренних настроек драйвера и считывается при инициализации драйвера. Большинство современных драйверов не используют файл .conf.
#
# foobar.conf - example skeleton driver conf file
#
name="foobar" parent="pseudo" instance=0;
Помните, что файл .conf должен помещаться в директории /kernel/drv, даже если система 64-битная.
Полезные ссылки
Writing Device Drivers, http://docs.sun.com/app/docs/doc/819-3196, Sun Microsystems, Inc., 2008.
Device Driver Tutorial, http://docs.sun.com/app/docs/doc/819-3159, Sun Microsystems, Inc., 2008.
OpenSolaris Device Drivers Community, http://hub.opensolaris.org/bin/view/Community+Group+device_drivers/WebHome