Опубликован: 24.04.2009 | Доступ: свободный | Студентов: 1180 / 358 | Оценка: 4.39 / 4.28 | Длительность: 18:45:00
Специальности: Программист
Лекция 9:

Введение в драйверы устройств ввода/вывода

< Лекция 8 || Лекция 9: 1234 || Лекция 10 >

Обработка ISR и IST для драйверов устройств

Большинство периферийных устройств могут генерировать прерывания, чтобы получить обслуживание от операционной системы. Некоторыми примерами устройств, которые могут использовать прерывания, являются платы ПК, встроенные таймеры, устройства аудио-ввода, клавиатуры, сенсорные экраны, последовательные порты, и устройства указатели. Почти любой тип периферийного устройства может использовать прерывания в качестве основного метода инициирования действий ОС по обслуживанию.

Так как эти периферийные устройства могут вызывать или сигнализировать о прерываниях, их драйверы устройств должны обрабатывать прерывания, чтобы обслужить свои устройства. Физические прерывания (IRQ) являются аппаратными линиями связи, по которым устройства могут посылать сигналы прерываний в микропроцессор. Логические прерывания (SYSINTR) являются отображением IRQ, которое определяет OAL.

Когда обрабатывается прерывание, имеет место определенная последовательность событий. Необходимо написать для драйвера устройства запрос на обслуживание прерывания (ISR) и поток обслуживания прерывания (IST), помня о следующей последовательности событий.

  1. Когда происходит прерывание, микропроцессор переходит к обработчику исключительных ситуаций ядра.
  2. Обработчик исключительных ситуаций отключает все прерывания равного или более низкого приоритета микропроцессора, и затем вызывает соответствующий ISR для запроса физического прерывания (IRQ).
  3. ISR возвращает логическое прерывание в форме идентификатора прерывания обработчику прерываний и, обычно, маскирует прерывание устройства уровня платы.
  4. Обработчик прерываний снова включает все прерывания в микропроцессоре, за исключением текущего прерывания, которое остается замаскированным на плате, и затем сигнализирует о соответствующем событии IST.
  5. IST планируется, обрабатывает оборудование, и затем заканчивает обработку прерывания.
  6. IST вызывает функцию InterruptDone, которая в свою очередь вызывает в OAL функцию OEMInterruptDone.

Функция OEMInterruptDone снова включает текущее прерывание. Драйвер устройства должен выполнить при загрузке следующие действия:

  • Регистрирует свой ISR в ядре. Драйвер должен зарегистрировать свой ISR в ядре, если драйвер не использует общую функцию OAL ISR для обработки своего прерывания. Драйвер должен зарегистрировать свой ISR в ядре, чтобы ядро вызывало ISR, когда происходит соответствующее физическое прерывание. Отобразить IRQ в SYSINTR в функции OEMInit из OAL. -или- Драйвер шины, который загружает драйвер, должен отобразить IRQ в SYSINTR, что делается в случае драйвера шины PCI.
  • Если драйвер не устанавливает ISR, любые прерывания, сгенерированные устройством, обрабатываются используемым по умолчанию ISR, который устанавливается OAL в OEMInit.

Процесс регистрации обработчика прерываний регистрирует событие, которое ассоциируется с системным прерыванием SYSINTR. После загрузки драйвера устройства драйвер создает поток службы прерываний (IST), а затем вызывает InterruptInitialize для регистрации события. IST может затем использовать функцию WaitForSingleObject для ожидания этого события и регистрации его в обработчике прерываний. IST регистрируется для одного или нескольких логических прерываний ( SYSINTR ).

Если для определенного драйвера используется реализация компании Microsoft модельного драйвера устройства (MDD), вам не нужно будет писать код для регистрации прерывания. Слой MDD драйвера регистрирует драйвер для прерываний. Если создается монолитный драйвер, нужно реализовать код для регистрации IST драйвера в обработчике прерываний. Для этого используется функция CreateEvent для создания события и функция InterruptInitialize для соединения события с SYSINTR.

Если драйвер устройства должен остановить обработку прерывания, то драйвер может использовать функцию InterruptDisable. Когда драйвер вызывает эту функцию, обработчик прерываний удаляет ассоциацию между IST и указанным логическим прерыванием. Обработчик прерываний выполняет это, вызывая функцию OEMInterruptDisable для отключения прерывания. Драйвер, если понадобиться, может позже снова зарегистрироваться для прерывания.

Могут возникать ситуации, когда требуется передать информацию между процедурой службы прерываний (ISR) и потоком службы прерываний (IST). Например, потому что вызов IST всякий раз, когда приходит запрос прерывания (IRQ), является длительным процессом, можно спроектировать ISR с буферизацией данных IRQ перед вызовом IST.

ISR будет возвращать SYSINTR_NOP, пока буфер не будет заполнен, а затем вернет соответствующий идентификатор SYSINTR, когда ISR будет готова для выполнения IST. Во время выполнения IST она может выбирать данные, которые буферизировала ISR.

Для передачи данных между ISR и IST:

  1. Зарезервируйте физическую память для ISR в файле Config.bib. Config.bib содержит несколько примеров резервирования физической памяти для последовательного и отладочного драйверов.
  2. Используйте зарезервированную память в вызове ISR. Так как ISR выполняется в режиме ядра, ISR может обращаться к зарезервированной памяти для буферизации данных.

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

Функция MMMapIoSpace вызывает функции VirtualAlloc и VirtualCopy для отображения физической памяти в адреса виртуальной памяти, к которой может обращаться IST.

Можно также вызывать функции VirtualAlloc и VirtualCopy непосредственно. Например, можно выделить память вне пространства виртуальной памяти процесса, вызывая VirtualAlloc с параметрами, заданными следующим образом:

  • dwSize >= 2 MB
  • flAllocationType задается как MEM_RESERVE
  • flProtect задается как PAGE_NOACCESS

В Windows Embedded CE устанавливаемая ISR может легко использовать данные совместно с IST, так как память может распределяться динамически, вместо резервирования в файле Config.bib. Поток службы прерываний (IST) является потоком, который выполняет большую часть обработки прерываний. ОС пробуждает IST, когда ОС получает прерывание для обработки. Иначе IST находится в спящем режиме. IST для клавиатуры, сенсорного экрана, мыши, и драйвера дисплея должны вызывать функцию SetKMode(TRUE), чтобы позволить GWES писать в общую кучу памяти.

Чтобы ОС пробудила IST, IST должна ассоциировать объект события с идентификатором прерывания. Используйте функцию CreateEvent для создания объекта события. После обработки прерывания IST должна ждать следующий сигнал прерывания. Этот вызов обычно делается в цикле.

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

Обычно потоки IST выполняются с приоритетом выше нормального. Они могут задать для себя более высокий приоритет перед регистрацией своего события в ядре, вызывая функцию CeSetThreadPriority. Таблица 9.3 показывает функции, которые обычно использует IST.

Таблица 9.3. Функции, используемые в IST
Функция Описание
InterruptInitialize Соединяет событие с идентификатором прерывания ISR.
WaitForSingleObject Возвращает управление, когда указанный объект находится в сигнальном состоянии или когда истекает интервал перерыва.
InterruptDone Инструктирует ядро о повторном включении аппаратного прерывания, связанного с этим потоком.

Следующий список содержит примеры того, что IST может сделать при запуске:

  • Создать структуру для хранения значений прерываний.
  • Использовать CreateEvent в качестве триггера IST.
  • Прочитать значения IRQ и SysIntr из реестра и позволить OAL отобразить IRQ в значение SYSINTR перед загрузкой драйвера.
  • Сохранить указатель для созданного потока.

Следующий пример кода драйвера устройства клавиатуры PS/2 из ..\WINCE600\Public\Common\OAK\Drivers\Keybd\Ps2_8042\Ps2keybd.cpp показывает типичную IST.

BOOL
Ps2Keybd::
IsrThreadProc(
  void
  )
{
  DWORD dwTransferred = 0;
  HKEY hk;
  DWORD dwStatus, dwSize, dwValue, dwType;
  int iPriority = 145;

  // ищем свой приоритет в реестре 
  dwStatus = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("HARDWARE\\DEVICEMAP\\KEYBD"), 0, 0, &hk);
  if(dwStatus == ERROR_SUCCESS) {
  // Смотрим, можем ли мы включить прерывание, чтобы пробудить 
  // систему из приостановленного состояния.
    dwSize = sizeof(dwValue);
    dwStatus = RegQueryValueEx(hk, _T("EnableWake"), NULL, &dwType, (LPBYTE) &dwValue, &dwSize);
    if(dwStatus == ERROR_SUCCESS && dwType == REG_DWORD) {
      if (dwValue != 0) {
        m_pp2p->SetWake(TRUE);
      }
    }

    // получаем приоритет потока прерывания 
    dwSize = sizeof(dwValue);
    dwStatus = RegQueryValueEx(hk, _T("Priority256"), NULL, &dwType, (LPBYTE) &dwValue, &dwSize);
    if(dwStatus == ERROR_SUCCESS && dwType == REG_DWORD) {
      iPriority = (int) dwValue;
    }

    // считываем sysintr
    dwSize = sizeof(dwValue);
    dwStatus = RegQueryValueEx(hk, _T("SysIntr"), NULL, &dwType, (LPBYTE) &dwValue, &dwSize);
    if(dwStatus == ERROR_SUCCESS) {
      if(dwType == REG_DWORD) {
        dwSysIntr_Keybd = dwValue;
      } else {
      dwStatus = ERROR_INVALID_PARAMETER;
      }
    }
    RegCloseKey(hk);
  }

  if(dwStatus != ERROR_SUCCESS) {
    goto leave;
  }

  // задаем приоритет потока 
  CeSetThreadPriority(GetCurrentThread(), iPriority);

  m_hevInterrupt = CreateEvent(NULL, FALSE, FALSE, NULL);
  if ( m_hevInterrupt == NULL)
  {
    goto leave;
  }

  if ( !InterruptInitialize(dwSysIntr_Keybd, m_hevInterrupt, NULL, 0) )
  {
    goto leave;
  }

  if (m_pp2p->WillWake()) {
// Запрашиваем OAL включить прерывание, чтобы пробудить систему из приостановленного состояния.
    DEBUGMSG(ZONE_INIT, (TEXT("Keyboard: Enabling wake from suspend\r\n")));
    BOOL fErr = KernelIoControl(IOCTL_HAL_ENABLE_WAKE, &dwSysIntr_Keybd,  
    sizeof(dwSysIntr_Keybd), NULL, 0, &dwTransferred);
    DEBUGCHK(fErr); // KernelIoControl всегда должна выполняться успешно.

  } 

  m_pp2p -> KeybdInterruptEnable();

  extern UINT v_uiPddId;
  extern PFN_KEYBD_EVENT v_pfnKeybdEvent;

  KEYBD_IST keybdIst;
  keybdIst.hevInterrupt = m_hevInterrupt;
  keybdIst.dwSysIntr_Keybd = dwSysIntr_Keybd;
  keybdIst.uiPddId = v_uiPddId;
  keybdIst.pfnGetKeybdEvent = KeybdPdd_GetEventEx2;
  keybdIst.pfnKeybdEvent = v_pfnKeybdEvent;

    KeybdIstLoop(&keybdIst);

leave:
    return 0;
}
9.1.

Следующий образец кода показывает реализацию функции KeybdIstLoop.

BOOL
 KeybdIstLoop (
    PKEYBD_IST pKeybdIst
    )
{
    SETFNAME(_T("KeybdIstLoop"));

    UINT32  rguiScanCode[16];
    BOOL    rgfKeyUp[16];
    UINT    cEvents;

    DEBUGCHK(pKeybdIst->hevInterrupt != NULL);
    DEBUGCHK(pKeybdIst->pfnGetKeybdEvent != NULL);
    DEBUGCHK(pKeybdIst->pfnKeybdEvent != NULL);

    SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);

wait_for_keybd_interrupt:
    if (WaitForSingleObject(pKeybdIst->hevInterrupt, INFINITE) == WAIT_OBJECT_0)
    {
        cEvents = (*pKeybdIst->pfnGetKeybdEvent)
            (pKeybdIst->uiPddId, rguiScanCode, rgfKeyUp);
            
        for (UINT iEvent = 0; iEvent < cEvents; ++iEvent) {
            (*pKeybdIst->pfnKeybdEvent)(pKeybdIst->uiPddId, 
                rguiScanCode[iEvent], rgfKeyUp[iEvent]);
        }
   // cEvents может быть 0, если это был частичный скан-код, такой как 0xE0.

        InterruptDone(pKeybdIst->dwSysIntr_Keybd);
    }

    goto wait_for_keybd_interrupt;

    ERRORMSG(1, (TEXT("KeybdIstLoop: Keyboard driver thread terminating.\r\n")));
    return TRUE;
}
< Лекция 8 || Лекция 9: 1234 || Лекция 10 >