Установка обработчика прерывания
Если вы действительно хотите "видеть" сгенерированные прерывания, подключения к аппаратному устройству не достаточно; в системе должен быть настроен программный обработчик. Если ядру Linux не было сказано ожидать вашего прерывания, оно просто получит и проигнорирует его.
Линии прерываний являются ценным и часто ограниченным ресурсом, особенно когда есть только 15 или 16 из них. Ядро ведёт реестр линий прерываний, похожий на реестр портов ввода/вывода. Как ожидается, модуль запрашивает канал прерывания (или IRQ, для запроса прерывания), прежде чем использовать его и освобождает его, когда заканчивает работу. Как мы увидим, во многих ситуациях также ожидается, что модули будут способны делить линии прерывания с другими драйверами. Следующие функции, объявленные в , реализуют интерфейс регистрации прерывания:
int request_irq(unsigned int irq,
irqreturn_t (*handler)(int, void *, struct pt_regs *),
unsigned long flags,
const char *dev_name,
void *dev_id);
void free_irq(unsigned int irq, void *dev_id);
Значение, возвращаемое request_irq запрашивающей функции, либо 0, что означает успешность, либо отрицательный код ошибки, как обычно. Не редки случаи, когда функция возвращает -EBUSY, чтобы просигнализировать, что другой драйвер уже использует запрошенную линию прерывания. Аргументами функции являются:
unsigned int irq
Номер запрашиваемого прерывания.
irqreturn_t (*handler)(int, void *, struct pt_regs *)
Указатель на установленную функцию-обработчик. Мы обсудим аргументы этой функции и её возвращаемое значение далее в этой главе.
unsigned long flags
Как и следовало ожидать, битовая маска опций (описываемая далее), связанная с управлением прерыванием.
const char *dev_name
Строка, передаваемая в request_irq, используется в /proc/interrupts, чтобы показать владельца прерывания (смотрите следующий раздел).
void *dev_id
Указатель, используемый для общих линий прерываний. Это уникальный идентификатор, который используется, когда линия прерывания освобождена, и может также быть использован драйвером для указания на свою собственную область данных (с целью определения, какое устройство создало прерывание). Если прерывание не является общим, dev_id может быть установлен в NULL, но так или иначе, хорошая идея использовать этот элемент для указания на структуру устройства. Мы увидим практическое применение dev_id в разделе "Реализация обработчика".
Битами, которые могут быть установлены в flags, являются:
SA_INTERRUPT
Если установлен, указывает на "быстрый" обработчик прерывания. Быстрые обработчики выполняются при запрещённых прерываниях на текущем процессоре (тема рассматривается в разделе "Быстрые и медленные обработчики").
SA_SHIRQ
Этот бит сигнализирует, что прерывание может быть разделено между устройствами. Концепция общего использования изложена в разделе "Разделяемые прерывания".
SA_SAMPLE_RANDOM
Этот бит показывает, что сгенерированные прерывания могут внести свой вклад в пул энтропии, используемый /dev/random и dev/urandom. Эти устройства возвращают при чтении действительно случайные числа и предназначены для оказания помощи прикладному программному обеспечению выбирать безопасные ключи для шифрования. Такие случайные числа извлекаются из пула энтропии, в который вносят вклад различные случайные события. Если ваше устройство генерирует прерывания в действительно случайные моменты времени, вам следует установить этот флаг. Если, с другой стороны, ваши прерывания предсказуемые (например, вертикальная развертка от захвата кадров), флаг не стоит устанавливать - это не будет способствовать каким-либо образом энтропии системы. Устройствам, которые могут быть под влиянием атакующих, не следует устанавливать этот флаг; например, сетевые драйверы могут быть зависимыми от предсказуемых периодичных пакетов извне и не должны вносить свой вклад в пул энтропии. Для дополнительной информации смотрите комментарии в drivers/char/random.c.
Обработчик прерывания может быть установлен либо при инициализации драйвера, либо при первом открытии устройства. Хотя установка обработчика прерывания в функции инициализации модуля может звучать как хорошая идея, часто это не так, особенно если устройство не разделяет прерывания. Из-за ограниченного числа линий прерывания вы не хотите их растрачивать. Вы можете легко выключить больше устройств на вашем компьютер, чем существует прерываний. Если модуль запрашивает прерывание при инициализации, он запрещает любым другим драйверам использовать прерывание, даже если удерживающее его устройство никогда не используется. С другой стороны, запрос прерывания при открытии устройства позволяет некоторое совместное использование ресурсов.
Можно, например, запускать захват кадров на том же прерывании, как и модем до тех пор, пока вы не пользуетесь двумя устройствами одновременно. Весьма распространённая практика для пользователей - загрузить модуль для специального устройства при загрузке системы, даже если устройство используется редко. Приспособление сбора данных может использовать те же прерывания, как второй последовательный порт. Хотя не слишком трудно избежать подключения к поставщику услуг Интернета (ISP) во время сбора данных, необходимость выгрузить модуль, чтобы использовать модем, очень неприятна.
Правильным местом для вызова request_irq является первое открытие устройства перед поручением оборудованию генерировать прерывания. Местом для вызова free_irq является закрытие устройства последний раз, после указания оборудованию больше не прерывать процессор. Недостатком этого метода является то, что вам нужно сохранять счётчик открытий каждого устройства, чтобы знать, когда прерывания могут быть отключены.
Несмотря на это обсуждение, short запрашивает свою линию прерывания во время загрузки. Этот было сделано так, чтобы вы могли запускать тестовые программы без запуска дополнительных процессов, держащих устройство открытым. short, таким образом, запрашивает прерывание внутри своей функции инициализации (short_init), а не делает это в short_open, как бы сделал реальный драйвер устройства.
Прерыванием, запрошенным следующим кодом, является short_irq. Реальное получение переменной (то есть определение, какое прерывание использовать) показано позже, поскольку это не относится к данному обсуждению. short_base является базовым адресом ввода/вывода используемого параллельного интерфейса; для разрешения подтверждающих прерываний делается запись в регистр 2.
if (short_irq >= 0) {
result = request_irq(short_irq, short_interrupt, SA_INTERRUPT, "short", NULL);
if (result) {
printk(KERN_INFO "short: can't get assigned irq %i\n", short_irq);
short_irq = -1;
}
else { /* фактическое разрешение -- подразумевается, что это *является* параллельным портом */
outb(0x10,short_base+2);
}
}
Код показывает, что устанавливаемый обработчик является быстрым обработчиком (SA_INTERRUPT), не поддерживает совместное использование прерывания (SA_SHIRQ отсутствует) и не способствует энтропии системы (SA_SAMPLE_RANDOM тоже отсутствует). Следующий затем вызов outb разрешает подтверждающие прерывания для параллельного порта.
Не знаем насколько это важно, для архитектур i386 и x86_64 определена функция для запроса наличия линии прерывания:
int can_request_irq(unsigned int irq, unsigned long flags);
Эта функция возвращает ненулевое значение, если попытка получить данное прерывание успешна. Однако, следует отметить, что между вызовами can_request_irq и request_irq всё может в любой момент измениться.
Всякий раз, когда аппаратное прерывание достигает процессора, увеличивается внутренний счётчик, предоставляя возможность проверить, работает ли устройство так, как ожидалось.
Полученные прерывания показываются в /proc/interrupts. Следующий снимок был получен на двухпроцессорной системе Pentium:
root@montalcino:/bike/corbet/write/ldd3/src/short# m /proc/interrupts
CPU0 CPU1
0: 4848108 34 IO-APIC-edge timer
2: 0 0 XT-PIC cascade
8: 3 1 IO-APIC-edge rtc
10: 4335 1 IO-APIC-level aic7xxx
11: 8903 0 IO-APIC-level uhci_hcd
12: 49 1 IO-APIC-edge i8042
NMI: 0 0
LOC: 4848187 4848186
ERR: 0
MIS: 0
Первая колонка - это число прерываний. Вы можете видеть, что судя по отсутствующим прерываниям, этот файл показывает только прерывания, соответствующие установленным обработчикам. Например, первый последовательный порт (который использует прерывание номер 4) не показан, это свидетельствует, что модем не используется. В самом деле, даже если модем был использован ранее, но не использовался во время снимка, он не будет отображаться в файле; последовательные порты хорошо себя ведут и отключают свои обработчики прерываний при закрытом устройстве.
Распечатка /proc/interrupts показывает, как много прерываний было доставлено каждому процессору в системе. Как можно видеть из вывода, ядро Linux обычно обрабатывает прерывания на первом процессоре, как способ улучшения использования кэша. (* Хотя некоторые крупные системы явно используют схемы балансирования прерываний для распределения нагрузки по обработке прерываний в системе). Последние два столбца предоставляют информацию о том программируемом контроллере прерываний, который обрабатывает прерывание (и о котором автору драйвера беспокоиться нет необходимости) и имя (имена) устройств(а), которые имеют зарегистрированные обработчики прерывания (указанные в аргументе dev_name для request_irq).
Дерево /proc содержит другой относящийся к прерываниям файл, /proc/stat; иногда вы будете находить один файл более полезными, иногда вы предпочтёте другой. /proc/stat ведёт запись некоторой низкоуровневой статистики о системной активности, включая (но не ограничиваясь ими) число прерываний, полученных после загрузки системы. Каждая строка stat начинается с текстовой строки, которая является ключом к строке; метка intr - то, что мы ищем. Следующий (усечённый) снимок был сделан вскоре после предыдущего:
intr 5167833 5154006 2 0 2 4907 0 2 68 4 0 4406 9291 50 0 0
Первое число - это общая сумма всех прерываний, а каждое из остальных представляет одну линию прерывания, начиная с прерывания 0. Все счётчики суммируются по всем процессорам в системе. Это снимок показывает, что прерывание номер 4 было использовано 4907 раз, хотя обработчик и не установлен. Если тестируемый вами драйвер получает и освобождает прерывания в каждом цикле открытия и закрытия, вы можете найти /proc/stat более полезным, чем /proc/interrupts.
Ещё одно различие между этими двумя файлами в том, что interrupts является архитектурно-независимым (за исключением, быть может, нескольких строчек в конце), а stat является; число полей зависит от оборудования, на котором работает ядро. Количество доступных прерываний варьируется от небольшого, как 15 на SPARC, до значительного, как 256 на IA-64 и нескольких других системах. Интересно отметить, что число прерываний, определённых на x86, в настоящее время 224, а не 16, как могло ожидаться; это, как описано в include/asm-i386/irq.h, зависит от использования Linux архитектурного предела, а не предела определённого реализацией (такого, как 16 источников прерываний на старомодном контроллере прерываний ПК).
Следующий снимок /proc/interrupts сделан на системе IA-64. Как вы можете видеть, помимо другой аппаратной маршрутизации обычных источников прерываний, вывод очень похож на показанный ранее из 32-х разрядной системы.
CPU0 CPU1
27: 1705 34141 IO-SAPIC-level qla1280
40: 0 0 SAPIC perfmon
43: 913 6960 IO-SAPIC-level eth0
47: 26722 146 IO-SAPIC-level usb-uhci
64: 3 6 IO-SAPIC-edge ide0
80: 4 2 IO-SAPIC-edge keyboard
89: 0 0 IO-SAPIC-edge PS/2 Mouse
239: 5606341 5606052 SAPIC timer
254: 67575 52815 SAPIC IPI
NMI: 0 0
ERR: 0
Одной из наиболее сложных проблем для драйвера во время инициализации может быть как определить, какая линия прерывания будет использоваться устройством. Драйверу необходима информация, чтобы правильно установить обработчик. Хотя программист даже может потребовать от пользователя указать номер прерывания во время загрузки, это плохая практика, поскольку в большинстве случаев пользователь не знает номера, либо потому, что он не настроил перемычки, либо потому, что устройство их не имеет. Большинство пользователей хотят, чтобы их оборудование "просто работало" и не интересуются такими вопросами, как номера прерывания. Так что автоопределение номера прерывания является основным требованием для удобства использования драйвера.
Иногда автоопределение зависит от знаний, которые некоторые устройства имеют по умолчанию, которые редко, если когда-либо вообще, изменяются. В этом случае драйвер может считать, что применяются значения по умолчанию. Именно так по умолчанию ведёт себя с параллельным портом short. Реализация проста, как показано в short:
if (short_irq < 0) /* not yet specified: force the default on */
switch(short_base) {
case 0x378: short_irq = 7; break;
case 0x278: short_irq = 2; break;
case 0x3bc: short_irq = 5; break;
}
Код присваивает номер прерывания в соответствии с выбранным базовым адресом ввода/вывода, позволяя пользователю переопределить значение по умолчанию во время загрузки с чем-то вроде:
insmod ./short.ko irq=x
short_base умолчанию 0x378, так что short_irq по умолчанию 7.
Некоторые устройства являются более передовыми в области проектирования и просто "сообщают", какое прерывание они собираются использовать. В этом случае драйвер получает номер прерывания, читая байт состояния одного из портов ввода/вывода устройства или конфигурационного пространства PCI. Когда целевое устройство обладает способностью сказать драйверу, какое прерывание собирается использовать, автоопределение номера прерывания просто означает зондирование прибора без дополнительной работы, требуемой для проверки прерывания. К счастью, большинство современного оборудования работает таким образом, например, стандарт PCI решает данную проблему, требуя периферийные устройства заявлять, какую линию (линии) прерывания они собираются использовать. Стандарт PCI обсуждается в Главе 12.
К сожалению, не все устройства дружественны к программисту и автоопределение может потребовать некоторых проверок. Техника очень проста: драйвер просит устройство генерировать прерывания и наблюдает, что происходит. Если всё идёт хорошо, активируется только одна линия прерывания.
Хотя зондирование просто в теории, фактическая реализация может быть неясной. Мы рассматриваем два варианта выполнения задачи: вызывая определённые в ядре вспомогательные функции и реализацию нашей собственной версии.
Ядро Linux предоставляет низкоуровневые средства проверки номера прерывания. Это работает только для неразделяемых прерываний, но большинство оборудования, способного работать в режиме разделяемого прерывания, в любом случае предлагает лучшие способы нахождения настроенного номера прерывания. Средство состоит из двух функций, объявленных в (который также описывает механизм зондирования):
unsigned long probe_irq_on(void);
Эта функция возвращает битовую маску неназначенных прерываний. Драйвер должен сохранить возвращённую битовую маску и передать её позже в probe_irq_off. После этого вызова драйвер должен обеспечить, чтобы его устройство сгенерировало по крайней мере одно прерывание.
int probe_irq_off(unsigned long);
После запроса прерывания устройством драйвер вызывает эту функцию, передавая в качестве аргумента битовую маску, которую перед этим вернула probe_irq_on. probe_irq_off возвращает номер прерывания, которое было вызвано после "probe_on". Если прерывание не произошло, возвращается 0 (следовательно, IRQ 0 не может быть прозондировано, но в любом случае нет пользовательских устройств, которые могли бы использовать его на любой из поддерживаемых архитектур). Если произошло более чем одно прерывание (неоднозначное обнаружение), probe_irq_off возвращает отрицательное значение.
Программист должен быть осторожным и разрешить прерывания на устройстве после вызова probe_irq_on и отключить их перед вызовом probe_irq_off. Кроме того, необходимо помнить, что служба ожидает прерывания в вашем устройстве после probe_irq_off.
Модуль short демонстрирует использование такой проверки. Если вы загружаете модуль с probe=1, чтобы обнаружить вашу линию прерывания, выполняется следующий код, при условии, что контакты 9 и 10 разъёма параллельного порта соединены вместе:
int count = 0;
do {
unsigned long mask;
mask = probe_irq_on( );
outb_p(0x10,short_base+2); /* разрешение подтверждений */
outb_p(0x00,short_base); /* очистить бит */
outb_p(0xFF,short_base); /* установить бит: прерывание! */
outb_p(0x00,short_base+2); /* запретить подтверждение */
udelay(5); /* подождать некоторое время */
short_irq = probe_irq_off(mask);
if (short_irq == 0) { /* ничего нет? */
printk(KERN_INFO "short: no irq reported by probe\n");
short_irq = -1;
}
/*
* Если более чем одна линия была активирована, результат
* отрицателен. Следует обслужить это прерывание (не требуется для lpt порта)
* и продолжить цикл. Посмотреть максимум пять раз, затем закончить
*/
} while (short_irq < 0 && count++ < 5);
if (short_irq < 0)
printk("short: probe failed %i times, giving up\n", count);
Обратите внимание на использование udelay перед вызовом probe_irq_off. В зависимости от скорости вашего процессора, возможно, придётся подождать в течение короткого периода, чтобы дать время прерыванию произойти на самом деле.
Проверка может быть длительной задачей. Хотя для short это и не так, проверка устройства захвата кадров, например, требует задержку, по меньшей мере, 20 мс (которая велика для процессора), а другие устройства могут занять ещё больше времени. Поэтому лучше всего проверить линию прерывания только один раз, при инициализации модуля, независимо от того, устанавливаете ли вы обработчик при открытии устройства (как следует) или в функции инициализации (что не рекомендуется).
Интересно отметить, что на некоторых платформах (PowerPC, M68k, большинство реализаций MIPS и обе версии SPARC) зондирование является ненужным и, следовательно, предыдущие функции просто пустые заполнители, иногда называемые "бесполезной чепухой ISA". На других платформах зондирование осуществляется только для устройств ISA. В любом случае большинство архитектур определяют эти функции (даже если они пустые) для облегчения переносимости существующих драйверов устройств.
Зондирование также может быть реализовано без особых проблем самостоятельно в самом драйвере. Драйверы, которые должны осуществлять своё собственное зондирование, редки, но просмотр их работы даёт некоторое понимание данного процесса. Для этого модуль short выполняет самостоятельное обнаружение линии прерывания, если он загружен с probe=2.
Механизм аналогичен описанному выше: разрешить все неиспользуемые прерывания, затем подождать и посмотреть, что происходит. Тем не менее, мы можем использовать наши знания об устройстве. Часто устройство может быть настроено на использование одного номера прерывания из набора из трёх или четырёх; проверка только этих прерываний позволяет определить то единственно правильное, не проверяя все возможные прерывания.
Реализация short предполагает, что 3, 5, 7 и 9 являются единственно возможными значениями прерываний. Эти цифры на самом деле являются теми значениями, которые позволяют вам выбрать некоторые параллельные устройства.
Следующий код зондирует, проверяя все "возможные" прерывания и глядя на то, что происходит. trials - массив списка прерываний для проверки и имеет 0 в качестве маркера конца; массив tried используется для отслеживания обработчиков, которые действительно были зарегистрированы этим драйвером.
int trials[ ] = {3, 5, 7, 9, 0};
int tried[ ] = {0, 0, 0, 0, 0};
int i, count = 0;
/*
* устанавливаем обработчик зондирования на все возможные линии. Запоминаем
* результат (0 - успешно, или -EBUSY), чтобы освободить только
* запрошенные
*/
for (i = 0; trials[i]; i++)
tried[i] = request_irq(trials[i], short_probing, SA_INTERRUPT, "short probe", NULL);
do {
short_irq = 0; /* ещё ничего нет */
outb_p(0x10,short_base+2); /* разрешить */
outb_p(0x00,short_base);
outb_p(0xFF,short_base); /* переключить бит */
outb_p(0x00,short_base+2); /* запретить */
udelay(5); /* подождать какое-то время */
/* это значение было установлено обработчиком */
if (short_irq == 0) { /* ничего нет? */
printk(KERN_INFO "short: no irq reported by probe\n");
}
/*
* Если более, чем одна линия была активирована, результат
* отрицательный. Нам следует обработать прерывание (но для lpt порта
* это не требуется) и повторить цикл. Делаем это максимум 5 раз
*/
} while (short_irq <=0 && count++ < 5);
/* конец цикла, выгрузить обработчик */
for (i = 0; trials[i]; i++)
if (tried[i] == 0)
free_irq(trials[i], NULL);
if (short_irq < 0)
printk("short: probe failed %i times, giving up\n", count);
Можно не знать заранее "возможные" значения прерываний. В таком случае, необходимо исследовать все свободные прерывания, а не ограничиваться несколькими trials[ ]. Для зондирования всех прерываний необходимо проверить прерывания с 0 по NR_IRQS-1, где NR_IRQS определён в и зависит от платформы.
Теперь мы пропустили только свой обработчик зондирования. Ролью обработчика является обновление short_irq в соответствии с фактически полученным прерыванием. Значение 0 в short_irq означает "ещё ничего", а отрицательное значение означает "неоднозначно". Эти значения выбраны для соответствия с probe_irq_off и чтобы позволить тому же коду в short.c вызывать оба вида зондирования.
irqreturn_t short_probing(int irq, void *dev_id, struct pt_regs *regs)
{
if (short_irq = = 0) short_irq = irq; /* найдено */
if (short_irq != irq) short_irq = -irq; /* неоднозначно */
return IRQ_HANDLED;
}
Аргументы для обработчика описаны ниже. Знания, что irq является обрабатываемым прерыванием должно быть достаточным для понимания только что показанной функции.
Старые версии ядра Linux предпринимали большие усилия, чтобы различать "быстрые" и "медленные" прерывания. Быстрыми прерываниями были те, которые могли быть обработаны очень быстро, в то время как обработка медленных прерываний длилась значительно дольше. Медленные прерывания могли быть достаточно загружающими процессор и было целесообразно снова разрешать прерывания во время их обработки. В противном случае, задачи, требующие быстрого внимания, могли быть отложены на слишком долгий срок.
В современных ядрах большинство различий между быстрыми и медленными прерывания исчезли. Остаётся только одно: быстрые прерывания (те, которые были запрошены флагом SA_INTERRUPT) выполняются с запретом всех других прерываний на текущем процессоре. Обратите внимание, что другие процессоры могут всё ещё обрабатывать прерывания, хотя вы никогда не увидите двух процессоров, обрабатывающих одно и то же прерывание в одно и то же время. Итак, прерывание какого типа должен использовать ваш драйвер? В современных системах SA_INTERRUPT предназначен для использования только в нескольких особых ситуациях, таких как таймерные прерывания. Если у вас нет веских оснований для работы вашего обработчика прерываний при отключенных других прерываниях, не следует использовать SA_INTERRUPT.
Это описание должно удовлетворить большинство читателей, хотя кто-то со вкусом к оборудованию и некоторым опытом работы со своим компьютером может быть заинтересован в углублении. Если вы не заботитесь о внутренних деталях, вы можете перейти к следующему разделу.
Это описание о том, как это выглядит в ядрах версии 2.6, было экстраполировано из arch/i386/kernel/irq.c, arch/i386/kernel/apic.c, arch/i386/kernel/entry.S, arch/i386/kernel/i8259.c и include/asm-i386/hw_irq.h; хотя общая концепция остается той же, аппаратные подробности отличаться на других платформах.
Самый низкий уровень обработки прерываний можно найти в entry.S, ассемблерном файле, который выполняет большую часть работы машинного уровня. Путём небольших ассемблерных трюков и некоторых макросов, каждому возможному прерыванию присвоен небольшой код. В каждом случае код помещает номер прерывания в стек и переходит к общему сегменту, который вызывает do_IRQ, определённую в irq.c.
Первое, что делает do_IRQ - подтверждает прерывание, чтобы контроллер прерываний мог переходить к другим вещам. Затем она получает спин-блокировку для данного номера прерывания, предотвращая таким образом обработку этого прерывания любым другим процессором. Она очищает несколько статусных битов (в том числе один, называемый IRQ_WAITING, который мы рассмотрим в ближайшее время) и затем ищет обработчик(и) для данного прерывания. Если обработчика нет, делать нечего; спин-блокировка освобождается, все ожидающие программные прерывания обработаны и do_IRQ возвращается.
Однако, обычно, если устройство создаёт прерывания, также есть по крайней мере один зарегистрированный обработчик для прерывания. Для реального вызова обработчиков вызывается функция handle_IRQ_event. Если обработчик является медленной разновидностью (SA_INTERRUPT не установлен), прерывания в оборудовании снова разрешаются и вызывается обработчик. Затем, только для чистоты, выполняются программные прерывания и происходит возврат к обычной работе. "Обычная работа" может также измениться в результате прерывания (обработчик мог wake_up (пробудить) процесс, например), поэтому последней вещью, которая происходит по возвращении из прерывания, является возможное перепланирование процессора.
Зондирование прерываний осуществляется установкой для каждого IRQ, для которого в настоящее время отсутствует обработчик, статусного бита IRQ_WAITING. Когда происходит прерывание, do_IRQ очищает этот бит и затем возвращается, потому что обработчик не зарегистрирован. probe_irq_off, вызванной драйвером, необходимо только поискать прерывание, которое больше не имеет установленного IRQ_WAITING.
Комментариев нет:
Отправить комментарий
Примечание. Отправлять комментарии могут только участники этого блога.