Содержание


Обслуживание периферии в коде модулей ядра: Часть 58. Создание "верхней половины" обработчика прерываний

Comments

В этой и последующей статьях мы подробно рассмотрим написание функции обработчика аппаратных прерываний (ISR) и некоторые «подводные камни», порою возникающие при написании такого кода.

Верхняя половина обработчика прерываний

В прошлой статье мы изучили как установить обработчик прерывания, так что осталось написать саму функцию обработчика. Прототип функции обработчика прерывания уже показывался выше:

   typedef irqreturn_t (*irq_handler_t)( int irq, void *dev );

где:

  • irq— линия IRQ;
  • dev— уникальный указатель экземпляра обработчика (который передаётся в последнем параметре вызова request_irq() при регистрации обработчика).

Это именно та функция, которая в первую очередь будет вызываться (верхняя половина) при каждом возникновении аппаратного прерывания. Но это вовсе не означает, что при возврате из этой функции работа по обработке текущего прерывания будет завершена (хотя и такой вариант вполне допустим). Из-за этой «неполноты» такой обработчик и получил название верхней половины обработчика прерывания. Дальнейшие действия по обработке могут быть запланированы эти обработчиком на более позднее время, используя несколько различных механизмов, обобщённо называемых нижняя половина.

Важно то, что код обработчика верхней половины выполняется при запрещённых последующих прерываниях по этой же линии irq для задействованного локального процессора. Но после возврата из этой функции локальные прерывания снова разрешаются.

Верхняя половина обработчика прерываний может вернуть одно из значений, объявленных в файле <linux/irqreturn.h>:

   typedef int irqreturn_t;
   #define IRQ_NONE        (0)
   #define IRQ_HANDLED     (1)
   #define IRQ_RETVAL(x)   ((x) != 0)

Значение IRQ_HANDLED означает, что устройство прерывания распознано как обслуживаемое обработчиком, и прерывание успешно обработано.

Значение IRQ_NONE говорит о том, устройство не является источником прерывания для данного обработчика, и прерывание должно быть передано другим обработчикам, зарегистрированным на данной линии IRQ.

Типичная схема обработчика при этом будет выглядеть как показано ниже:

   static irqreturn_t intr_handler ( int irq, void *dev ) {
        if( ! /* проверка того, что обслуживаемое устройство запросило прерывание*/ )
           return IRQ_NONE;
        /* код обслуживания устройства */
        return IRQ_HANDLED;
   }

Пока мы не углубились в дальнейшую обработку, производимую в нижней половине, хотелось бы отметить, что в ряде случаев оказывается достаточно простого обработчика верхней половины, и нет необходимости внедрять сложные механизмами отложенной обработки. Это утверждение будет верным при крайне простой обработке и отсутствии возможности очень быстрого возникновения повторных прерываний.

Стоит также упомянуть самую большую сложность в программировании функции обработчика. Код функции ни непосредственно, ни косвенно не имеет права вызывать функции API ядра, которые вызовут переход выполняющегося кода в блокированное состояние ожидания (msleep() и др.) или даже только предполагают возможность такого перехода при некоторых условиях (kmalloc() и др.)! Это связано с тем, что функция обработчика верхней половины выполняется в контексте прерывания, и после завершения блокированного ожидания будет просто некуда возвратиться для продолжения её выполнения. Результатом такого ошибочного вызова скорее всего станет крах всей операционной системы.

Управление линиями прерываний

Под управлением линиями прерываний мы будем понимать запрет-разрешение прерываний по одной или нескольким линиям IRQ. Раньше существовала возможность вообще запретить прерывания на определённое время. Но сейчас («заточенный» под SMP) набор API для этих целей позволяет вам выбрать один из двух сценариев поведения:

  • запретить прерывания по всем линиям IRQ, но только для локального процессора;
  • запретить прерывания на всех процессорах, но только для одной линии IRQ.

Макросы управления линиями прерываний определены в файле <linux/irqflags.h>. Для управления запретом и разрешением прерываний на локальном процессоре используются следующие методы:

  • local_irq_disable()— запретить прерывания на локальном CPU;
  • local_irq_enable()— разрешить прерывания на локальном CPU;
  • int irqs_disabled()— возвратить ненулевое значение, если запрещены прерывания на локальном CPU, в противном случае возвращается нуль.

Напротив, для управления (запретом и разрешением) одной выбранной линии IRQ, но уже для всех процессоров в системе, используют следующие методы:

  • void disable_irq( unsigned int irq ) и void disable_irq_nosync( unsigned int irq )— обе эти функции запрещают прерывания с линии IRQ на контроллере (для всех CPU), причём disable_irq() не возвращается до тех пор, пока все выполняемые в данный момент обработчики прерываний не закончат работу;
  • void enable_irq( unsigned int irq )— разрешаются прерывания с линии IRQ на контроллере (для всех CPU);
  • void synchronize_irq( unsigned int irq )— ожидает пока завершится выполняемый обработчик прерывания от линии IRQ, (хорошим правилом является вызов этой функции перед выгрузкой модуля, использующего эту линию IRQ).

Вызовы функций disable_irq*() и enable_irq() должны обязательно быть парными— каждому вызову функции запрещения линии должен соответствовать вызов функции разрешения. Только после последнего вызова функции enable_irq() линия запроса на прерывание будет снова разрешена.

Пример обработчика прерываний

Довольно сложно продемонстрировать работающий код обработчика прерываний, потому что такой код должен быть связан с реальным аппаратным расширением, и, поэтому будет перенасыщен специфическими деталями, затемняющими суть происходящего. Но удачный оригинальный пример приведён в уже упоминавшейся ранее книге "Writing Linux Device Drivers" Джерри Купперстайна (Jerry Cooperstein). Также его можно найти в архиве IRQ.tgz в разделе "Материалы для скачивания".

Листинг 1. Простейший обработчик прерываний.
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>

#define SHARED_IRQ 17

static int irq = SHARED_IRQ, my_dev_id, irq_counter = 0;
module_param( irq, int, S_IRUGO );

static irqreturn_t my_interrupt( int irq, void *dev_id ) {
   irq_counter++;
   printk( KERN_INFO "In the ISR: counter = %d\n", irq_counter );
   /* IRQ_NONE возвращается так фактически мы только наблюдаем за процессом */
   return IRQ_NONE;
}

static int __init my_init( void ) {
   if ( request_irq( irq, my_interrupt, IRQF_SHARED, "my_interrupt", &my_dev_id ) )
      return -1;
   printk( KERN_INFO "Successfully loading ISR handler on IRQ %d\n", irq );
   return 0;
}

static void __exit my_exit( void ) {
   synchronize_irq( irq );
   free_irq( irq, &my_dev_id );
   printk( KERN_INFO "Successfully unloading, irq_counter = %d\n", irq_counter );
}

module_init( my_init );
module_exit( my_exit );
MODULE_AUTHOR( "Jerry Cooperstein" );
MODULE_DESCRIPTION( "LDD:1.0 s_08/lab1_interrupt.c" );
MODULE_LICENSE( "GPL v2" );

Логика этого примера состоит в том, что обработчик включается в цепочку с другим обработчиком прерываний, уже существующим в системе. Новый обработчик не нарушает работу существующего и фактически ничего не делает, только подсчитывает число обработанных прерываний. В оригинале предлагается опробовать его с установкой на IRQ сетевой платы, но более наглядный результат будет получен при установке на IRQ клавиатуры (IRQ 1) или мыши (IRQ 12) на интерфейсе PS/2.

 $ cat /proc/interrupts
           CPU0
  0:   20329441          XT-PIC  timer
  1:        423          XT-PIC  i8042
...
$ sudo /sbin/insmod lab1_interrupt.ko irq=1
$ cat /proc/interrupts
           CPU0
  0:   20527017          XT-PIC  timer
  1:        572          XT-PIC  i8042, my_interrupt
...
$ sudo /sbin/rmmod lab1_interrupt
$ dmesg | tail -n5
In the ISR: counter = 33
In the ISR: counter = 34
In the ISR: counter = 35
In the ISR: counter = 36
Successfully unloading, irq_counter = 36
$ cat /proc/interrupts
           CPU0
  0:   20568216          XT-PIC  timer
  1:        622          XT-PIC  i8042
...

Польза этого примера заключается ещё и в том, что на его основе можно начать разработку кода модуля для любого реального устройства, даже не имея самого устройства, но используя один из штатных существующих источников прерываний компьютера в качестве имитатора прерываний от отрабатываемого устройства. А позже с минимальными потерями весь наработанный код переключить на работу с реальной линией прерывания, используемой проектируемым устройством.

Заключение

В данной статье была изложена техника написания "верхней половины" функции обработчика аппаратных прерываний. В следующей статье мы рассмотрим "нижнюю половину" функции обработчика аппаратных прерываний, т.е. методы организации отложенной обработки прерываний.


Ресурсы для скачивания


Похожие темы


Комментарии

Войдите или зарегистрируйтесь для того чтобы оставлять комментарии или подписаться на них.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=932638
ArticleTitle=Обслуживание периферии в коде модулей ядра: Часть 58. Создание "верхней половины" обработчика прерываний
publish-date=06042013