Чтобы сделать устройство доступным для системы и пользовательских программ, нужно его зарегистрировать. В заголовочном файле fs.h
определены функции для регистрации символьных и блочных устройств. Все они начинаются с префикса register. Наше устройство будет символьным, поэтому для его регистрации мы будем использовать функцию register_chrdev. Вот ее прототип:
extern int register_chrdev(unsigned int, const char *,
struct file_operations *);
Первый аргумент — это старший номер (
Чтобы понять, для чего нужен старший номер, вспомним каталог /dev, содержащий файлы устройств. Файл /dev/tty1
— это терминал с номером 1. Старший номер определяет тип устройства — терминал (tty), а младший — его номер в системе (1). Человеку проще работать не с номерами. а с символьными именами устройств, поэтому каждому старшему номеру соответствует символьное обозначение.
При регистрации устройства нужно указать его тип — старший номер устройства. Но для этого нужно знать, какие номера свободны. Проще всего указать первым аргументом 0 — тогда функция возвратит первый свободный старший номер символьного устройства для вашей системы. Если старший номер указать явно, может возникнуть конфликт номеров, и наше устройство не будет зарегистрировано.
Второй параметр определяет имя устройства («device»). Последний параметр очень важен. Это структура указателей на функции для работы с нашим устройством. Наш модуль содержит таблицу доступных функций, а операционная система вызывает нужную функцию, когда пользовательской программе нужно выполнить операцию с файлом устройства (открытие/закрытие, чтение/запись). Таблица функций символьного устройства хранится в структуре file_operations, которая передается при регистрации устройства.
После регистрации драйвера устройства происходит поиск устройств данного типа. Причем этот поиск должен произвести сам модуль. Для простоты будем считать, что у нас всего два устройства. Нам нужно создать эти два устройства, предварительно вычислив старший номер устройства. Напишем модуль, который помимо регистрации устройства выводил бы его старший номер — потом мы его будем использовать при создании устройства.
Листинг 28.6. Драйвер устройства /dev/device (без структуры file_operations)
#define MODULE
#define __KERNEL__
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h> // регистрация устройств
#include <linux/ioport.h> // работа с портами ввода/вывода
#include <linux/sched.h> // резервирование прерывания
// Имя нашего устройства
#define DEV_NAME 'device'
// Порты ввода-вывода нашего устройства
#define PORT_START 0x2000 #define PORT_QTY 10
// Память нашего устройства
#define MEM_START 0x20000000
#define MEM_QTY 0x20
// Номер прерывания для нашего устройства
#define IRQ_NUM 9
MODULE_AUTHOR('Denis Kolisnichenko [email protected]');
MODULE_DESCRIPTION('Linux kernel module');
// Старший номер файла устройства
static int Major;
// Структура file_operations - пока пустая,
//но вскоре мы ее напишем
struct file_operations FO;
// Обработчик прерывания
void irq_handler(int irq, void *dev_id,
struct pt_regs *regs) {
return;
}
int init_module() {
// Регистрируем устройство
printk('My module: starting...
');
Major = register_chrdev(0, DEV_NAME, &F0);
if (Major < 0) {
// Устройство не зарегистрировано
printk('My module: registration failed
');
return Major;
}
printk('My module: device registered, major number = %d
',
Major);
// Резервирование портов ввода-вывода
printk('My module: allocating io ports
');
if (check_region(PORT_START, PORT_QTY)) {
printk('My module; allocation io ports failed
');
return -EBUSY;
}
request_region(PORT_START, PORT_QTY, DEV_NAME);
printk('My module: io ports allocated
');
// Резервирование памяти
if (check_mem_region(MEM_START, MEM_QTY)) {
printk('My module: memory allocation failed
');
release_region(PORT_START, PORT_QTY);
return -EBUSY;
}
request_mem_region(MEM_START, MEM_QTY, DEV_NAME);
printk('My module: memory allocated
');
// Резервирование прерывания
if (request_irq(IRQ_NUM, irq_handler, 0, DEV_NAME, NULL)) {
printk('My module: IRQ allocation failed
');