Замените прерывание HW в режиме плоской памяти на DOS32/A
У меня есть вопрос о том, как заменить прерывание HW в режиме плоской памяти...
- о моем заявлении...
- создан путем объединения Watcom C и DOS32/A.
- написано для работы в режиме DOS (не в режиме ОС)
- с DOS32/A теперь я могу получить доступ к>1М памяти и выделить большую память для использования... (работает в режиме плоской памяти!!!)
- текущий номер...
- Я хочу написать ISR(подпрограмму обработки прерываний) для одной карты PCI. Таким образом мне нужно "заменить" прерывание HW.
- Ex. линия прерывания карты PCI = 0xE в DOS. Это означает, что это устройство будет выдавать прерывание через IRQ 14 8259.
Но я не сделал, как достичь своей цели, чтобы заменить это прерывание в плоском режиме?
@ ресурс, который я нашел... - в библиотеке watcom C есть один пример, использующий _dos_getvect, _dos_setvect и _chain_intr для перехвата INT 0x1C... Я проверил этот код и нашел ОК. Но когда я применил это к моему случаю: INT76 (где IRQ 14 - "INT 0x76" <- (14-8) + 0x70), тогда ничего не произошло...
- Я проверил, что прерывание HW генерируется, но мой собственный ISR не вызывался...
Я что-то теряю? или есть какие-то функции, которые я могу использовать для достижения своей цели?
================================================== =============
[20120809] Я попытался использовать вызовы DPMI 0x204 и 0x205 и обнаружил, что MyISR() по-прежнему не вызывается. Я описал то, что я сделал, как показано ниже, и, возможно, вы все можете дать мне несколько советов!
1) Используйте встроенную сборку для реализации вызовов DPMI 0x204 и 0x205 и проверки OK...
Ex. Используйте DPMI 0x204, чтобы показать векторы прерываний 16 IRQ, и я получаю (селектор: смещение) следующие результаты: 8: 1540(INT8),8: 1544(INT9),.....,8: 1560(INT70),8: 1564(INT71),...,8: 157C(INT77)
Ex. Используйте 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) Пусть HW прерывание будет сгенерировано и доказательства:
- Регистр конфигурации устройства PCI 0x5 бит2(прерывание отключено) = 0
- Регистр конфигурации устройства PCI 0x6 бит3(статус прерывания) = 1
- Регистр конфигурации устройства PCI 0x3C/0x3D (линия прерывания) = 0xE/0x2
- В DOS режимом прерывания является режим PIC (режим 8259) и основанный на выводе (MSIE = 0)
5) Показать значение тика и обнаружил, что он по-прежнему "0"...
Таким образом, я думаю, что MyISR() не вызывается правильно...
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.
Это потому, что ваше приложение использует некоторые DOS или BIOS API, поэтому расширителю необходимо переключиться в реальный режим, чтобы выполнить их, и вернуться обратно в защищенный режим, чтобы передать управление приложению в защищенном режиме.
DOS32a делает это, выделяя обратный вызов в реальном режиме (по крайней мере, для аппаратных прерываний), который вызывает ваш обработчик защищенного режима, если установлен IRQ14, когда расширитель находится в реальном режиме.
Если экстендер находится в защищенном режиме, а IRQ14 установлен, он автоматически передаст управление вашему обработчику IRQ14.
Но если вы не установили обработчик защищенного режима для своего IRQ, то DOS32a не будет выделять какой-либо обратный вызов реального режима, и ваш обработчик реального режима irq может не получить контроль. Но это должно получить контроль AFAIK.
В любом случае, попробуйте две вышеупомянутые функции. И сделайте цепочку к предыдущему обработчику прерываний int76h, как сказал Шон.
Короче:
В случае DOS32a вам не нужно использовать функции _dos_getvect и _dos_setvect. Вместо этого используйте функции DPMI 0204h и 0205h для установки обработчика IRQ защищенного режима.
Совет: в вашем обработчике прерываний первым шагом должна быть проверка, действительно ли ваше устройство сгенерировало прерывание, или это какое-то другое устройство использует этот irq(IRQ14 в вашем случае). Вы можете сделать это, проверив "бит ожидания прерывания" на вашем устройстве, если оно установлено, обслужите ваше устройство и подключитесь к следующему обработчику. Если он не установлен в 1, просто цепочка к следующему обработчику.
РЕДАКТИРОВАНИЕ: Используйте последнюю версию DOS32a, а не ту, которая поставляется с OW.
Обновление 2012-08-14:
Да, вы можете использовать макросы 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-устройства. IOWs, сначала прочитайте 'Регистр линии прерывания' (НЕ регистр Pin прерывания) из пространства конфигурации PCI и подключите только этот irq#. Допустимые значения для этого регистра, в вашем случае: от 0x00 до 0x0F включительно, с 0x00 означает IRQ0, а 0x01 означает IRQ1 и так далее.
Код POST/BIOS во время загрузки записывает значение в "регистр строки прерывания", и вы НЕ ДОЛЖНЫ изменять этот регистр любой ценой (конечно, если вы не имеете дело с проблемами маршрутизации прерываний, с которыми будет сталкиваться писатель ОС)
Вы также должны получить и сохранить селектор и смещение старого обработчика, используя вызов DPMI 0204h, на случай, если вы подключаетесь к старому обработчику. Если нет, не забудьте отправить EOI(конец прерывания) на оба 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, зарезервирована и не может быть использована. AFAIK, 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 методов или процедуры BIOS BIOS.
НОТА:
Многие контроллеры дисков PCI имеют бит, называемый "Включение / отключение прерывания". Регистр, который содержит этот бит, обычно находится в пространстве конфигурации PCI, и его можно найти в таблице данных.
Фактически, этот параметр предназначен для "пересылки" прерывания, генерируемого устройством, подключенным к контроллеру PCI, на шину PCI.
Если прерывания отключены с помощью этого бита, то даже если ваше устройство (подключенное к контроллеру PCI) генерирует прерывание, прерывание НЕ будет перенаправлено на шину PCI (и, следовательно, процессор никогда не узнает, произошло ли прерывание), но прерывание Бит (этот бит отличается от бита "Разрешить / отключить прерывание") в контроллере PCI по-прежнему установлен для уведомления о том, что устройство (подключенное к контроллеру PCI, например, к жесткому диску) сгенерировало прерывание, так что программа может прочитать этот бит и принять соответствующие меры. Это похоже на опрос с точки зрения программирования.
Обычно это относится только к основным передачам, не связанным с автобусами.
Но, похоже, что вы используете главные передачи шины (например, DMA), поэтому это не должно применяться в вашем случае.
Но в любом случае, я бы посоветовал вам внимательно прочитать таблицу данных контроллера PCI, особенно в поисках битов / регистров, связанных с обработкой прерываний.
Редакция:
Что касается программирования на уровне приложений, вам не нужно сталкиваться с / использовать указатели _far, так как ваша программа не получит доступа к чему-либо, кроме вашего кода.
Но это не совсем так, когда вы переходите к программированию на системном уровне, вам необходимо получить доступ к регистрам отображенного в памяти устройства, внешнему ПЗУ или реализации обработчиков прерываний и т. Д.
История меняется здесь. Создание сегмента, т. Е. Выделение дескриптора и получение связанного с ним селектора, гарантирует, что даже в случае ошибки в коде он не будет раздражающим образом изменять что-то внешнее по отношению к тому конкретному сегменту, из которого выполняется текущий код. Если он попытается это сделать, процессор выдаст ошибку. Таким образом, при доступе к внешним устройствам (особенно к регистрам отображаемого в памяти устройства) или к некоторым данным, например, BIOS и т. Д., Неплохо было бы выделить дескриптор и установить пределы базы и сегмента в соответствии с областью, которую необходимо выполнить / читать / писать и продолжить. Но вы не обязаны это делать.
Некоторые внешние коды, находящиеся, например, в rom, предполагают, что они будут вызываться с помощью дальнего вызова.
Как я уже говорил ранее, в архитектуре x86 на каком-то уровне (ниже) вы должны иметь дело с сегментацией, поскольку невозможно полностью отключить ее. Но в плоской модели сегментация, как я уже говорил выше, помогает программисту при доступе к внешним (по отношению к вашей программе) вещам. Но вам не нужно использовать, если вы не хотите делать это.
When an interrupt handler is invoked, it doesn't know the base and limits of program that was interrupted. It doesn't know the segment attributes, limits etc. of the interrupted program, we say except CS and EIP all registers are in undefined state wrt interrupt handler. So it is needed to be declared as far function to indicate that it resides somewhere external to currently executing program.
Прошло много времени с тех пор, как я возился с прерываниями, но таблица - это указатель, указывающий, куда процессор должен обращаться для обработки прерывания. Я могу дать вам процесс, но не код, поскольку я когда-либо использовал только код 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