Замените аппаратное прерывание в режиме плоской памяти на DOS32/A.

У меня вопрос о том, как заменить аппаратное прерывание в режиме плоской памяти...

  1. about my application...
    • created by combining Watcom C and DOS32/A.
    • написано для работы в режиме DOS (не в режиме ОС)
    • с DOS32/A теперь я могу получить доступ к >1M памяти и выделить большой объем памяти для использования... (работает в режиме плоской памяти !!!)
  2. current issue...
    • I want to write an ISR(interrupt service routine) for one PCI card. Thus I need to "replace" the HW interrupt.
    • Бывший. линия прерывания карты PCI = 0xE в DOS. Это означает, что это устройство вызовет прерывание через IRQ 14 8259.

Но я не понял, как достичь своей цели заменить это прерывание в плоском режиме?

@ ресурс, который я нашел... - в библиотеке watcom C есть один пример использования _dos_getvect, _dos_setvect и _chain_intr для перехвата INT 0x1C... Я протестировал этот код и нашел его в порядке. Но когда я применяю это к моему случаю: INT76 (где IRQ 14 - это "INT 0x76" ‹- (14-8) + 0x70), то ничего не происходит...

  • Я проверил, что аппаратное прерывание генерируется, но мой собственный ISR не вызывается...

Я что-то теряю? или есть ли какие-либо функции, которые я могу использовать для достижения своей цели?

===============================================================

[20120809] Я попытался использовать вызовы DPMI 0x204 и 0x205 и обнаружил, что MyISR() по-прежнему не вызывается. Я описал, что я сделал, как показано ниже, и, возможно, вы все можете дать мне несколько советов!

1) Используйте встроенную сборку для реализации вызовов DPMI 0x204 и 0x205 и проверьте OK...

Бывший. Используйте DPMI 0x204, чтобы показать векторы прерываний 16 IRQ, и я получаю (selector:offset) следующие результаты: 8:1540(INT8), 8:1544(INT9),.....,8:1560(INT70),8:1564(INT71),.. .,8:157C(INT77)

Бывший. Используйте DPMI 0x205, чтобы установить вектор прерывания для IRQ14 (INT76) и вернуть CF=0, что указывает на успешное завершение.

2) Создайте свой собственный ISR MyISR() следующим образом:

volatile int tick=0;  // global and volatile...
void MyISR(void)
{
  tick = 5;  // simple code to change the value of tick...
}

3) Установите новый вектор прерывания с помощью вызова DPMI 0x205:

selector = FP_SEG(MyISR);  // selector = 0x838 here
offset = FP_OFF(MyISR);    // offset   = 0x30100963 here
sts = DPMI_SetIntVector(0x76, selector, offset, &out_ax);

Тогда sts = 0 (CF = 0), что указывает на успех!

  • Вот одна странная вещь: мое приложение работает в плоской модели памяти, и я думаю, что селектор должен быть равен 0 для MyISR()... Но если селектор = 0 для вызова DPMI 0x205, тогда я получаю CF=1 и AX = 0x8022, что указывает на «неверный селектор»!

4) Пусть сгенерировано аппаратное прерывание и доказательства:

  • Регистр конфигурации устройства PCI 0x5, бит 2 (прерывание отключено) = 0
  • Регистр конфигурации устройства PCI 0x6, бит 3 (состояние прерывания) = 1
  • Регистр конфигурации устройства PCI 0x3C/0x3D (линия прерывания) = 0xE/0x2
  • В DOS режим прерывания - это режим PIC (режим 8259) и основанный на выводе (MSIE = 0).

5) Показать значение тика и найти, что оно по-прежнему "0"...

Таким образом, я думаю, что MyISR() вызывается неправильно...


person liaoo    schedule 24.07.2012    source источник


Ответы (2)


Попробуйте использовать функцию DPMI 0204h и 0205h вместо '_dos_getvect' и '_dos_setvect' соответственно.

Средой выполнения вашей программы является DOS32A или сервер/хост DPMI. Поэтому используйте API, который они предоставили, вместо использования средств DOS int21h. Но DOS32A перехватывает прерывания int21h, так что ваш код должен работать нормально, насколько это касается реального режима.

На самом деле вы установили только обработчик прерываний реального режима для IRQ14 с помощью функций '_dos_getvect' и '_dos_setvect'.

Вместо этого, используя функции DPMI, вы устанавливаете обработчик прерывания защищенного режима для IRQ14, и DOS32a будет автоматически передавать прерывание IRQ14 этому обработчику защищенного режима.

Напомним: расширитель dos/сервер DPMI может находиться в защищенном режиме или в реальном режиме, пока установлено IRQ.

Это связано с тем, что ваше приложение использует какой-либо API-интерфейс DOS или BIOS, поэтому расширителю необходимо переключиться в реальный режим, чтобы выполнить их, и вернуться обратно в защищенный режим, чтобы передать управление вашему приложению защищенного режима.

DOS32a делает это, выделяя обратный вызов реального режима (по крайней мере, для аппаратных прерываний), который вызывает ваш обработчик защищенного режима, если установлено IRQ14, когда расширитель находится в реальном режиме.

Если расширитель находится в защищенном режиме, а IRQ14 установлен, он автоматически передаст управление вашему обработчику IRQ14.

Но если вы не установили обработчик защищенного режима для своего IRQ, то DOS32a не будет выделять никаких обратных вызовов реального режима, и ваш обработчик IRQ реального режима может не получить управление. Но он должен получить контроль AFAIK.

В любом случае попробуйте две вышеупомянутые функции. И сделайте цепочку к предыдущему обработчику прерывания int76h, как сказал Шон.

Короче:

В случае DOS32a вам не нужно использовать функции _dos_getvect и _dos_setvect. Вместо этого используйте функции DPMI 0204h и 0205h для установки обработчика IRQ защищенного режима.

Совет: в вашем обработчике прерываний первым шагом должна быть проверка действительно ли ваше устройство генерирует прерывание или это какое-то другое устройство, разделяющее этот irq (IRQ14 в вашем случае). Вы можете сделать это, проверив 'бит ожидания прерывания' на вашем устройстве, если он установлен, выполните обслуживание вашего устройства и цепочку к следующему обработчику. Если он не установлен в 1, просто цепочка к следующему обработчику.

ОТРЕДАКТИРОВАНО: Используйте последнюю версию DOS32a вместо одной это идет с OW.

Обновление от 14 августа 2012 г.:

Да, вы можете использовать макросы FP_SEG и FP_OFF ​​для получения селектора и смещения соответственно, точно так же, как вы использовали бы эти макросы в реальных режимах для получения сегмента и смещения.

Вы также можете использовать макрос MK_FP для создания дальних указателей из селектора и смещения. например. MK_FP(селектор, смещение).

Вы должны объявить свой обработчик прерываний с помощью ключевого слова '__interrupt' при написании обработчиков на C.

Вот фрагмент:

             #include <i86.h>  /* for FP_OFF, FP_SEG, and MK_FP in OW */

             /* C  Prototype for your IRQ handler */
             void   __interrupt    __far irqHandler(void);
                        .
                        . 
                        .
          irq_selector = (unsigned short)FP_SEG( &irqHandler );
          irq_offset = (unsigned long)FP_OFF( &irqHandler );

          __dpmi_SetVect( intNum, irq_selector, irq_offset );
                        .
                        . 
                        .

или попробуйте это:

          extern void sendEOItoMaster(void);  
          # pragma aux sendEOItoMaster = \                         
                "mov  al,    0x20"  \       
                "out  0x20,  al"    \       
                modify [eax] ;



          extern void sendEOItoSlave(void);  
          # pragma aux sendEOItoSlave = \                           
              "mov  al,    0x20"  \       
              "out  0xA0,  al"    \       
              modify [eax] ;


      unsigned int   old76_selector, new76_selector;
      unsigned long  old76_offset, new76_offset;


      volatile int chain = 1; /* Chain to the old handler */
      volatile int tick=0;  // global and volatile...


     void (__interrupt __far *old76Handler)(void) = NULL;      // function pointer declaration

     void __interrupt __far new76Handler(void) {


            tick = 5;  // simple code to change the value of tick...

               .
               .
               .

           if( chain ){

                 // disable irqs if enabled above.

                 _chain_intr( old76Handler );  // 'jumping' to the old handler

                //  ( *old76Handler )();          // 'calling' the old handler 

           }else{

               sendEOItoMaster();
               sendEOItoSlave();
           }

      }


      __dpmi_GetVect( 0x76, &old76_selector, &old76_offset );

      old76Handler = ( void (__interrupt __far *)(void) ) MK_FP (old76_selector, old76_offset)

      new76_selector = (unsigned int)FP_SEG( &new76Handler );
      new76_offset = (unsigned long)FP_OFF( &new76Handler );

      __dpmi_SetVect( 0x76, new76_selector, new76_offset );

                    .
                    .

ЗАМЕТКА:

Вы должны сначала дважды проверить, что IRQ#, который вы перехватываете, действительно назначен/сопоставлен с выводом прерывания вашего соответствующего устройства PCI. IOW, сначала прочитайте 'Регистр строки прерывания' (НЕ регистр вывода прерывания) из пространства конфигурации PCI и перехватите только этот irq#. Допустимые значения для этого регистра в вашем случае: от 0x00 до 0x0F включительно, где 0x00 означает IRQ0, а 0x01 означает IRQ1 и так далее.

Код POST / BIOS записывает значение в «Регистр линии прерывания» во время загрузки, и вы НЕ ДОЛЖНЫ изменять этот регистр любой ценой (конечно, если вы не имеете дело с проблемами маршрутизации прерываний, с которыми будет иметь дело писатель ОС)

Вы также должны получить и сохранить селектор и смещение старого обработчика, используя вызов DPMI 0204h, на случай, если вы привязываетесь к старому обработчику. Если нет, не забудьте отправить EOI (End-of-interrupt) ОБЕЕМ главному и подчиненному PIC в случае, если вы подключили IRQ, принадлежащий подчиненному PIC (т.е. INT 70h-77h, включая INT 0Ah), и ТОЛЬКО главному PIC в случае, если вы перехватили IRQ, принадлежащий мастеру PIC.

В плоской модели адрес BASE равен 0, а предел равен 0xFFFFF, а бит G (т. е. бит гранулярности) = 1.

База и ограничение (вместе с битами атрибутов (например, бит G) сегмента) находятся в дескрипторе, соответствующем конкретному сегменту. Сам дескриптор находится в таблице дескрипторов.

Таблицы дескрипторов представляют собой массив, каждая запись которого имеет размер 8 байт.

Селектор — это просто указатель (или индекс) на запись 8-байтового дескриптора в таблице дескрипторов (либо GDT, либо LDT). Таким образом, селектор НЕ МОЖЕТ быть 0.

Обратите внимание, что младшие 3 бита 16-битного селектора имеют особое значение, и только старшие 13 бит используются для индексации записи дескриптора из таблицы дескрипторов.

GDT = глобальная таблица дескрипторов

LDT = локальная таблица дескрипторов

В системе может быть только один GDT, но много LDT.

Поскольку номер записи 0 в GDT зарезервирован и не может использоваться. Насколько я знаю, DOS32A не создает никаких LDT для своих приложений, вместо этого он просто выделяет и инициализирует записи дескриптора, соответствующие приложению, в самом GDT.

Селектор НЕ ДОЛЖЕН быть равен 0, так как архитектура x86 считает селектор 0 недопустимым, когда вы пытаетесь получить доступ к памяти с помощью этого селектора; хотя вы можете успешно поместить 0 в любой регистр сегмента, только когда вы пытаетесь получить доступ (чтение/запись/выполнение) к этому сегменту, процессор генерирует исключение.

В случае обработчиков прерываний базовый адрес не обязательно должен быть равен 0, даже в случае плоского режима. Для этого у среды DPMI должны быть веские причины. В конце концов, вам все еще нужно заняться сегментацией на каком-то уровне в архитектуре x86.

                  PCI device config register 0x5 bit2(Interrupt Disabled) = 0

                  PCI device config register 0x6 bit3(Interrupt status) = 1  

Я думаю, вы имеете в виду регистры команд и состояния Bus master соответственно. На самом деле они находятся либо в пространстве ввода-вывода, либо в пространстве памяти, но НЕ в пространстве конфигурации PCI. Таким образом, вы можете читать/записывать их напрямую через инструкции IN/OUT или MOV.

Для чтения/записи регистров конфигурации PCI вы должны использовать методы конфигурации red/write или подпрограммы PCI BIOS.

ЗАМЕТКА:

Многие контроллеры дисков PCI имеют бит, называемый битом «Включение/выключение прерывания». Регистр, содержащий этот бит, обычно находится в пространстве конфигурации PCI, и его можно найти в таблице данных.

Собственно, этот параметр предназначен для «переадресации» прерывания, генерируемого устройством, подключенным к контроллеру PCI, на шину PCI.

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

Обычно это применимо только к нешинным мастер-передачам.

Но похоже, что вы используете главную передачу шины (то есть DMA), поэтому в вашем случае это не должно применяться.

Но в любом случае, я бы посоветовал вам внимательно прочитать техническое описание контроллера PCI, особенно в поисках битов/регистров, связанных с обработкой прерываний.

ОТРЕДАКТИРОВАНО:

Ну, что касается программирования на уровне приложений, вам не нужно сталкиваться/использовать указатели _far, поскольку ваша программа не будет обращаться ни к чему вне вашего кода.

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

История меняется здесь. Создание сегмента, т. е. выделение дескриптора и получение связанного с ним селектора, гарантирует, что даже если в коде есть ошибка, он не будет раздражающе изменять что-либо внешнее по отношению к тому конкретному сегменту, из которого выполняется текущий код. Если он попытается это сделать, процессор выдаст ошибку. Таким образом, при доступе к внешним устройствам (особенно к регистрам устройства с отображением памяти) или доступе к некоторым данным ПЗУ, например, BIOS и т. д., рекомендуется выделить дескриптор и установить базовые и сегментные ограничения в соответствии с областью, которую вам нужно выполнить. прочитать/записать и продолжить. Но вы не обязаны это делать.

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

Как я уже говорил ранее, в архитектуре x86 на каком-то уровне (чем ниже вы идете) вам нужно иметь дело с сегментацией, поскольку полностью отключить ее невозможно. Но в плоской модели сегментация присутствует как помощь программисту, как я сказал выше, при доступе к внешним (по отношению к вашей программе) вещам. Но вам не нужно использовать, если вы не хотите этого делать.

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

person jacks    schedule 28.07.2012
comment
Я выполнил вызовы DPMI 0x204 и 0x205 для получения и установки вектора прерывания с использованием встроенного ассемблера. Но теперь у меня есть 2 вопроса: (1) Как определить селектор и адрес смещения при использовании DPMI 0x205 (Set Vector)? (Есть ли какой-либо API для этой цели, например FP_SEG() и FP_OFF()?) (2) Нужно ли мне использовать ключевое слово _interrupt при объявлении моей собственной процедуры обслуживания? Заранее спасибо ! - person liaoo; 08.08.2012
comment
@liaoo- см. раздел Обновление от 14 августа 2012 г. в моем ответе. - person jacks; 14.08.2012
comment
Дорогие джеки, спасибо за вашу ценную и полезную информацию, и я попробую это как можно скорее. Но у меня все еще есть 2 вопроса о вашей информации: (1) Вы сказали MK_FP(), FP_SEG() и FP_OFF( ) по-прежнему можно использовать в плоском режиме, верно? Но я нашел следующие слова в здесь ... вам никогда не придется иметь дело с ограничениями сегмента 64K или странными ключевыми словами, такими как FAR, NEAR или MK_FP... что это значит? (2) Я могу использовать DPMI 204 для получения селектора и смещения OldISR, но как его вызвать, если мне нужно связать его внутри НовыйISR ? - person liaoo; 14.08.2012
comment
@liaoo- см. раздел кода в ответе. Я изменил его соответственно. Также обратите внимание на раздел отредактировано. - person jacks; 14.08.2012
comment
Уважаемые jacks, моя собственная процедура обслуживания успешно запущена на основе вашей информации/кода... Большое спасибо за вашу помощь! - person liaoo; 15.08.2012
comment
Уважаемые jacks, у меня есть еще 2 вопроса о процедуре обслуживания прерываний... возможно, вы поможете мне их прояснить! (1) должен ли я использовать iret или iretd в конце MyISR() ? (то есть, EOI master и slave затем iret...) (2) Могу ли я всегда использовать chain oldISR() ? (предположим, что старый ISR работает, и он будет различать, а затем делать правильные вещи) - person liaoo; 15.08.2012
comment
@liaoo- рад узнать, что все получилось! :) Ну, если вы пишете обработчик прерываний на ассемблере, то вы ДОЛЖНЫ использовать iretd. Но в вашем случае, поскольку вы пишете на C и используете ключевое слово ' __interrupt ', вам НЕ нужно помещать отдельный ired через встроенную сборку. Компилятор позаботится об этом (ТОЛЬКО если вы пишете ISR с ключевым словом _interrupt, иначе компилятор не сможет отличить функцию от ISR). Компилятор C поставит соответствующие инструкции, когда обнаружит '}', т.е. заключит фигурную скобку вашего ISR во время компиляции. Если ты ... - person jacks; 15.08.2012
comment
... НЕ отправляют EOI в вашем коде ISR, тогда вы ДОЛЖНЫ привязываться к старому ISR, в противном случае вам не нужно связываться, если вы сами обрабатываете EOI. Но обратите внимание, что если вы обрабатываете EOI в своем коде, то вы НЕ ДОЛЖНЫ привязываться к старому ISR, иначе он может запутаться. Кроме того, ISR по умолчанию устанавливаются с помощью POST-кода во время загрузки, поэтому вы можете быть уверены, что они будут последними в цепочке и сами отправят EOI. Также обратите внимание, что pci-устройство, которое сгенерировало прерывание, «очистит бит прерывания», как только ваш ISR прочитает это, чтобы подтвердить, что ваше устройство действительно подтвердило... - person jacks; 15.08.2012
comment
... irq, поэтому в таком случае вы должны сами отправлять EOI и не привязываться к старому обработчику. Потому что, если теперь (бит прерывания очищен) вы привязываетесь к старому обработчику, обработчик будет думать, что устройство не генерировало прерывание. Короче говоря, если вы уверены, что ваше устройство подтвердило прерывание, то полностью обработайте его и отправьте EOI последним, а не цепочку. Но если вы уверены, что какое-то другое устройство выставило этот irq (напомним: прерывания PCI являются общими), тогда вы должны просто привязать к старому обработчику, не выполняя никакой дальнейшей обработки. - person jacks; 15.08.2012
comment
jacks, что означает ...устройство pci, сгенерировавшее прерывание, 'очистит бит прерывания', как только ваш ISR прочитает это, чтобы подтвердить, что ваше устройство действительно выставило irq...? (Я предполагаю: если устройство PCI генерирует прерывание, то его бит IS (состояние прерывания) = 1; тогда в нашем ISR мы должны проверить, равен ли бит IS = 1, прочитав его... Тогда само устройство PCI будет очистить бит IS?) я прав? (здесь бит IS = смещение пространства конфигурации PCI 0x6 бит3...) - person liaoo; 16.08.2012
comment
jacks, я думаю, что могу всегда связать старый isr независимо от того, является ли это прерывание общим или нет, потому что: (1) если прерывание не является общим тогда старый ISR будет фиктивным (в котором отправляется EOI), или (2), если прерывание является общим, цепочка прерываний будет выглядеть так: myISR-›oldISR-›Dummy. В этом случае после выполнения моего обработчика и передачи управления на oldISR, но ни одно устройство не генерирует прерывание, происходит передача управления на фиктивный ISR и отправляется EOI... В обоих случаях myISR выполняется и EOI отправляется корректно так что все в порядке... я прав? - person liaoo; 16.08.2012
comment
@liaoo- я думаю, что это проблема терминологии. Хорошо, во-первых, регистр конфигурации устройства PCI 0x6 бит 3 (статус прерывания) и регистр конфигурации устройства PCI 0x5 бит 2 (прерывание отключено), биты НЕ являются стандартными для каждого устройства PCI (даже если устройство генерирует прерывание через вывод INT # x). Например, вы можете обратиться к любому техническому описанию концентратора ввода-вывода Intel и найти конфигурацию PCI контроллера IDE. космические регистры. В таких устройствах PCI эти биты (IS и IE) находятся где-то еще в пространстве конфигурации. Какой тип контроллера AHCI вы используете - карта расширения или встроенная в материнскую плату? - person jacks; 18.08.2012
comment
.. . В случае контроллеров AHCI эти два зарезервированных бита могут использоваться для упомянутых вами целей, НО это НЕ является стандартным для других устройств. Таким образом, вы ДОЛЖНЫ проверить в таблице данных фактическое значение этих 2 бит и очистит ли само устройство PCI бит IS. Биты, о которых я говорил, то есть ID (прерывание отключено) и IS, служат следующим целям: ID в контроллере AHCI, если сброс будет перенаправлять прерывание, сгенерированное устройством SATA в вашем случае, на шину PCI и, таким образом, выдавать IRQ всякий раз, когда устройство SATA генерирует прерывание. ИС будет установлена... - person jacks; 18.08.2012
comment
... всякий раз, когда устройство SATA генерирует прерывание. независимо от того, установлен ли идентификатор или сброшен. Прерывание НЕ будет перенаправлено на шину PCI (т.е. IRQ не будет установлено), если установлен ID. И всякий раз, когда вы читаете бит IS, когда он установлен, устройство PCI обычно сбрасывает его автоматически. Это просто так. Итак, как я уже говорил ранее, вам нужно пересмотреть таблицу данных, чтобы узнать точное значение и действие битов, о которых вы говорите, то есть ID и IS. Во-вторых, что касается привязки к старому ISR, да, вы можете всегда привязываться к старому ISR, НО вы НЕ должны отправлять EOI в свой ISR, если вы привязываетесь. - person jacks; 18.08.2012

Прошло некоторое время с тех пор, как я возился с прерываниями, но таблица — это указатель, указывающий, куда процессор должен перейти для обработки прерывания. Я могу дать вам процесс, но не код, так как я когда-либо использовал только код 8086.

Псевдокод:

Initialize:
    Get current vector - store value
    Set vector to point to the entry point of your routine

следующий:

Process Interrupt:
    Your code decides what to do with data
    If it's your data:
        process it, and return
    If not:
        jump to the stored vector that we got during initialize, 
        and let the chain of interrupts continue as they normally would

наконец-то:

Program End:
    check to see if interrupt still points to your code
        if yes, set vector back to the saved value
        if no, set beginning of your code to long jump to vector address you saved, 
            or set a flag that lets your program not process anything
person SeanC    schedule 24.07.2012
comment
Хотя это не то, что я хочу сейчас, но все же спасибо за вашу подробную информацию о процессе, потому что то, что вы сказали, является вторым шагом для меня. Теперь моя подпрограмма НЕ может быть вызвана, поэтому я не могу продолжать ^_^ И я точно не знал о части завершения программы, не могли бы вы объяснить подробнее? Бывший. зачем нужно видеть, указывает ли прерывание на мой код? - person liaoo; 25.07.2012
comment
когда вашей программе больше не нужно обрабатывать прерывания, она должна выгрузить себя. Если что-то еще зацепило прерывания, то вы не можете выгрузить сбросом вектора (поскольку ломаете новую программу), и должны выдавать сообщение о выгрузке программ в порядке, обратном их загрузке - person SeanC; 25.07.2012