Защищенный режим процессоров Intel 80286/80386/80486© Александр Фролов, Григорий ФроловТом 4, М.: Диалог-МИФИ, 1993, 234 стр. 3.3. Программа, которая работает с прерываниямиСледующая программа, которую мы вам представим, выполняет все необходимые действия, связанные с обработкой прерываний и исключений в защищённом режиме. Наша первая программа (листинг 1) работала в защищённом режиме с запрещёнными прерываниями. Она как бы пролетала через неизведанное с завязанными глазами и сразу же возвращалась в хорошо освоенный реальный режим. Теперь же программа имеет возможность "осмотреться" и может реагировать на такие события, как прерывания от клавиатуры и таймера. Кроме того, в случае возникновения исключений вам будет выдана некоторая диагностика, включающая код исключения, код ошибки и содержимое регистров процессора на момент возникновения исключения. Таблица IDT содержит дескрипторы для обработчиков исключений и аппаратных прерываний. Вентили исключений содержат ссылки на программы с именами exc_00...exc_1F. Вслед за вентилями исключений в таблице IDT следуют вентили аппаратных прерываний. Из них задействованы только IRQ0 и IRQ1 - прерывания от таймера и клавиатуры. Те аппаратные прерывания, которые нас не интересуют, приводят к выдаче в контроллеры прерывания команды конца прерывания. Для таких прерываний предусмотрены заглушки - процедуры с именами dummy_iret0 и dummy_iret1. Первая заглушка относится к первому контроллеру прерывания, вторая - ко второму. Вслед за вентилями аппаратных прерываний мы поместили в таблицу IDT вентиль программного прерывания int 30h, предназначенного для организации взаимодействия с клавиатурой на манер прерывания BIOS INT 16h. При выдаче прерывания int 30h вызывается процедура с именем Int_30h_Entry. После запуска программа переходит в защищённый режим и размаскирует прерывания от таймера и клавиатуры. Далее она вызывает в цикле прерывание int 30h (ввод символа с клавиатуры), и выводит на экран скан-код нажатой клавиши и состояние переключающих клавиш (таких, как CapsLock, Ins, и т.д.). Если окажется нажатой клавиша ESC, программа выходит из цикла. Далее в тексте программы следует ряд команд, закрытых символом комментария. Эти команды приводят к исключению. Вы можете попробовать их работу, удалив соответствующий символ комментария. При возникновении исключения на экран будет выведена диагностика, о чём позаботится обработчик исключений. Перед завершением работы программа устанавливает реальный режим процессора и стирает экран. Обработчик аппаратного прерывания клавиатуры - процедура с именем Keyb_int. После прихода прерывания она выдаёт короткий звуковой сигнал, считывает и анализирует скан-код клавиши, вызвавшей прерывание. Скан-коды классифицируются на обычные и расширенные (для 101-клавишной клавиатуры). В отличие от прерывания BIOS INT 16h, мы для простоты не стали реализовывать очередь, а ограничились записью полученного скан-кода в глобальную ячейку памяти key_code. Причём прерывания, возникающие при отпускании клавиш игнорируются. Запись скан-кода в ячейку key_code выполняет процедура Keyb_PutQ. После записи эта процедура устанавливает признак того, что была нажата клавиша - записывает значение 0FFh в глобальную переменную key_flag. Программное прерывание int 30h опрашивает состояние key_flag. Если этот флаг оказывается установленным, он сбрасывается, вслед за чем обработчик int 30h записывает в регистр AX скан-код нажатой клавиши, в регистр BX - состояние переключающих клавиш на момент нажатия клавиши, код которой передан в регистре AX. Для того чтобы не загромождать программу второстепенными деталями, мы не стали перекодировать скан-код в код ASCII, вы сможете при необходимости сделать это сами. Обработчик прерываний таймера - процедура с именем Timer_int. Эта процедура примерно раз в секунду выдаёт звуковой сигнал, чем её действия и ограничиваются. Процедура rdump выводит на экран содержимое регистров процессора и может быть использована для отладки программы. Обратите внимание на использованный способ возврата процессора в реальный режим: DATASEG ; Пустой дескриптор для выполнения возврата ; процессора в реальный режим через перевод ; его в состояние отключения. null_idt idt_struc <> CODESEG PROC set_rmode NEAR mov [real_sp],sp ; Переводим процессор в состояние отключения, ; это эквивалентно аппаратному сбросу, но ; выполняется быстрее. ; Сначала мы загружаем IDTR нулями, затем ; выдаём команду прерывания. lidt [FWORD null_idt] int 3 rwait: hlt jmp rwait LABEL shutdown_return FAR Регистр IDTR загружается нулями, следовательно, предел дескрипторной таблицы прерываний равен нулю. После этого мы выдаём команду программного прерывания. При обработке прерывания возникает исключение, так как регистр IDTR инициализирован неправильно. Но это исключение не может быть обработано по той же причине, что вызывает новое исключение. Теперь процессор переходит уже в состояние отключения и выполняет рестарт в реальном режиме. Что нам и требовалось получить! Для удобства мы вынесли в отдельный файл tiny-os.inc все необходимые определения структур данных и констант (листинг 2). Файл tiny-os.asm содержит саму программу, текст которой приведён в листинге 3. Листинг 2. Структуры данных и константы. ----------------------------------------------------------- ; ------------------------------------------------------------ ; Определения структур данных и констант ; ------------------------------------------------------------ STRUC desc_struc ; структура дескриптора limit dw 0 ; предел base_l dw 0 ; мл. слово физического адреса base_h db 0 ; ст. байт физического адреса access db 0 ; байт доступа rsrv dw 0 ; зарезервировано ENDS desc_struc STRUC idt_struc ; вентиль прерывания destoff dw 0 ; смещение обработчика destsel dw 0 ; селектор обработчика nparams db 0 ; кол-во параметров assess db 0 ; байт доступа rsrv dw 0 ; зарезервировано ENDS idt_struc STRUC idtr_struc ; регистр IDTR idt_lim dw 0 ; предел IDT idt_l dw 0 ; мл. слово физического адреса idt_h db 0 ; ст. байт физического адреса rsrv db 0 ; зарезервировано ENDS idtr_struc ; --------------------------------------------------------------- ; Биты байта доступа ACC_PRESENT EQU 10000000b ; сегмент есть в памяти ACC_CSEG EQU 00011000b ; сегмент кода ACC_DSEG EQU 00010000b ; сегмент данных ACC_EXPDOWN EQU 00000100b ; сегмент расширяется вниз ACC_CONFORM EQU 00000100b ; согласованный сегмент ACC_DATAWR EQU 00000010b ; разрешена запись ACC_INT_GATE EQU 00000110b ; вентиль прерывания ACC_TRAP_GATE EQU 00000111b ; вентиль исключения ; ------------------------------------------------------------ ; Типы сегментов ; сегмент данных DATA_ACC = ACC_PRESENT OR ACC_DSEG OR ACC_DATAWR ; сегмент кода CODE_ACC = ACC_PRESENT OR ACC_CSEG OR ACC_CONFORM ; сегмент стека STACK_ACC = ACC_PRESENT OR ACC_DSEG OR ACC_DATAWR OR ACC_EXPDOWN ; байт доступа сегмента таблицы IDT IDT_ACC = DATA_ACC ; байт доступа вентиля прерывания INT_ACC = ACC_PRESENT OR ACC_INT_GATE ; байт доступа вентиля исключения TRAP_ACC = ACC_PRESENT OR ACC_TRAP_GATE ; ------------------------------------------------------------ ; Константы STACK_SIZE EQU 0400 ; размер стека B_DATA_SIZE EQU 0300 ; размер области данных BIOS B_DATA_ADDR EQU 0400 ; адрес области данных BIOS MONO_SEG EQU 0b000 ; сегмент видеопамяти ; монохромного видеоадаптера COLOR_SEG EQU 0b800 ; сегмент видеопамяти ; цветного видеоадаптера CRT_SIZE EQU 4000 ; размер сегмента видеопамяти ; цветного видеоадаптера MONO_SIZE EQU 1000 ; размер сегмента видеопамяти ; монохромного видеоадаптера CRT_LOW EQU 8000 ; мл. байт физического адреса ; сегмента видеопамяти ; цветного видеоадаптера MONO_LOW EQU 0000 ; мл. байт физического адреса ; сегмента видеопамяти ; монохромного видеоадаптера CRT_SEG EQU 0Bh ; ст. байт физического адреса ; сегмента видеопамяти CMOS_PORT EQU 70h ; порт для доступа к CMOS-памяти PORT_6845 EQU 0063h ; адрес области данных BIOS, ; где записано значение адреса ; порта контроллера 6845 COLOR_PORT EQU 03d4h ; порт цветного видеоконтроллера MONO_PORT EQU 03b4h ; порт монохромного видеоконтроллера STATUS_PORT EQU 64h ; порт состояния клавиатуры SHUT_DOWN EQU 0feh ; команда сброса процессора VIRTUAL_MODE EQU 0001h ; бит перехода в защищённый режим A20_PORT EQU 0d1h ; команда управления линией A20 A20_ON EQU 0dfh ; открыть A20 A20_OFF EQU 0ddh ; закрыть A20 KBD_PORT_A EQU 60h ; адреса клавиатурных KBD_PORT_B EQU 61h ; портов INT_MASK_PORT EQU 21h ; порт для маскирования прерываний EOI EQU 20 ; команда конца прерывания MASTER8259A EQU 20 ; первый контроллер прерываний SLAVE8259A EQU 0a0 ; второй контроллер прерываний ; ------------------------------------------------------------ ; Селекторы, определённые в таблице GDT DS_DESCR = (gdt_ds - gdt_0) CS_DESCR = (gdt_cs - gdt_0) SS_DESCR = (gdt_ss - gdt_0) BIOS_DESCR = (gdt_bio - gdt_0) CRT_DESCR = (gdt_crt - gdt_0) MDA_DESCR = (gdt_mda - gdt_0) ; ------------------------------------------------------------ ; Маски и инверсные маски для клавиш L_SHIFT equ 0000000000000001b NL_SHIFT equ 1111111111111110b R_SHIFT equ 0000000000000010b NR_SHIFT equ 1111111111111101b L_CTRL equ 0000000000000100b NL_CTRL equ 1111111111111011b R_CTRL equ 0000000000001000b NR_CTRL equ 1111111111110111b L_ALT equ 0000000000010000b NL_ALT equ 1111111111101111b R_ALT equ 0000000000100000b NR_ALT equ 1111111111011111b CAPS_LOCK equ 0000000001000000b SCR_LOCK equ 0000000010000000b NUM_LOCK equ 0000000100000000b INSERT equ 0000001000000000b Листинг 3. Демонстрация обработки прерываний и исключений в защищённом режиме для процессора 80286 ----------------------------------------------------------- IDEAL RADIX 16 P286 MODEL LARGE include 'tiny-os.inc' STACK STACK_SIZE DATASEG DSEG_BEG = THIS WORD real_ss dw ? real_sp dw ? real_es dw ? GDT_BEG = $ LABEL gdtr WORD gdt_0 desc_struc <0,0,0,0,0> gdt_gdt desc_struc <GDT_SIZE-1,,,DATA_ACC,0> gdt_idt desc_struc <IDT_SIZE-1,,,IDT_ACC,0> gdt_ds desc_struc <DSEG_SIZE-1,,,DATA_ACC,0> gdt_cs desc_struc <CSEG_SIZE-1,,,CODE_ACC,0> gdt_ss desc_struc <STACK_SIZE-1,,,DATA_ACC,0> gdt_bio desc_struc <B_DATA_SIZE-1,B_DATA_ADDR,0,DATA_ACC,0> gdt_crt desc_struc <CRT_SIZE-1,CRT_LOW,CRT_SEG,DATA_ACC,0> gdt_mda desc_struc <MONO_SIZE-1,MONO_LOW,CRT_SEG,DATA_ACC,0> GDT_SIZE = ($ - GDT_BEG) ; Область памяти для загрузки регистра IDTR idtr idtr_struc <IDT_SIZE,,,0> ; Таблица дескрипторов прерываний IDT_BEG = $ ; ---------------------- Вентили исключений -------------------- idt idt_struc <OFFSET exc_00,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_01,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_02,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_03,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_04,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_05,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_06,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_07,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_08,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_09,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_0A,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_0B,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_0C,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_0D,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_0E,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_0F,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_10,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_11,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_12,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_13,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_14,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_15,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_16,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_17,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_18,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_19,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_1A,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_1B,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_1C,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_1D,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_1E,CS_DESCR,0,TRAP_ACC,0> idt_struc <OFFSET exc_1F,CS_DESCR,0,TRAP_ACC,0> ; --------------- Вентили аппаратных прерываний --------------- ; int 20h-IRQ0 idt_struc <OFFSET Timer_int,CS_DESCR,0,INT_ACC,0> ; int 21h-IRQ1 idt_struc <OFFSET Keyb_int,CS_DESCR,0,INT_ACC,0> ; int 22h, 23h, 24h, 25h, 26h, 27h-IRQ2-IRQ7 idt_struc 6 dup (<OFFSET dummy_iret0,CS_DESCR,0,INT_ACC,0>) ; int 28h, 29h, 2ah, 2bh, 2ch, 2dh, 2eh, 2fh-IRQ8-IRQ15 idt_struc 8 dup (<OFFSET dummy_iret1,CS_DESCR,0,INT_ACC,0>) ; -------------------- Вентиль прерывания -------------------- ; int 30h idt_struc <OFFSET Int_30h_Entry,CS_DESCR,0,INT_ACC,0> IDT_SIZE = ($ - IDT_BEG) CODESEG PROC start mov ax,DGROUP mov ds,ax call set_crt_base mov bh, 77h call clrscr ; Устанавливаем защищённый режим call set_pmode call write_hello_msg ; Размаскируем прерывания от таймера и клавиатуры in al,INT_MASK_PORT and al,0fch out INT_MASK_PORT,al ; Ожидаем нажатия на клавишу <ESC> charin: int 30h ; ожидаем нажатия на клавишу ; AX - скан-код клавиши, ; BX - состояние переключающих клавиш cmp al, 1 ; если <ESC> - выход из цикла jz continue push bx ; выводим скан-код на экран mov bx, 0301h ; координаты вывода call Print_Word pop bx mov ax, bx ; выводим состояние push bx ; переключающих клавиш mov bx, 0306h call Print_Word pop bx jmp charin ; Следующий байт находится в сегменте кода. ; Он используется нами для демонстрации возникновения ; исключения при попытке записи в сегмент кода. wrong1 db ? continue: ; После нажатия на клавишу <ESC> выходим в это место ; программы. Следующие несколько строк демонстрируют ; команды, которые вызывают исключение. Вы можете ; попробовать их, если уберёте символ комментария ; из соответствующей строки. ; Попытка записи за конец сегмента данных. Метка wrong ; находится в самом конце программы. mov [wrong], al ; Попытка записи в сегмент кода. ; mov [wrong1], al ; Попытка извлечения из пустого стека. ; pop ax ; Загрузка в сегментный регистр неправильного селектора. ; mov ax, 1280h ; mov ds, ax ; Прямой вызов исключения при помощи команды прерывания. ; int 1 call set_rmode ; установка реального режима mov bh, 07h ; стираем экран и call clrscr ; выходим в DOS mov ah,4c int 21h ENDP start MACRO setgdtentry mov [(desc_struc bx).base_l],ax mov [(desc_struc bx).base_h],dl ENDM ; ------------------------------------------- ; Установка защищённого режима ; ------------------------------------------- PROC set_pmode NEAR mov ax,DGROUP mov dl,ah shr dl,4 shl ax,4 mov si,ax mov di,dx add ax,OFFSET gdtr adc dl,0 mov bx,OFFSET gdt_gdt setgdtentry ; Заполняем дескриптор в GDT, указывающий на ; дескрипторную таблицу прерываний mov ax,si ; загружаем 24-битовый адрес сегмента mov dx,di ; данных add ax,OFFSET idt ; адрес дескриптора для IDT adc dl,0 mov bx,OFFSET gdt_idt setgdtentry ; Заполняем структуру для загрузки регистра IDTR mov bx,OFFSET idtr mov [(idtr_struc bx).idt_l],ax mov [(idtr_struc bx).idt_h],dl mov bx,OFFSET gdt_ds mov ax,si mov dx,di setgdtentry mov bx,OFFSET gdt_cs mov ax,cs mov dl,ah shr dl,4 shl ax,4 setgdtentry mov bx,OFFSET gdt_ss mov ax,ss mov dl,ah shr dl,4 shl ax,4 setgdtentry ; готовим возврат в реальный режим push ds mov ax,40 mov ds,ax mov [WORD 67],OFFSET shutdown_return mov [WORD 69],cs pop ds cli mov al,8f out CMOS_PORT,al jmp del1 del1: mov al,5 out CMOS_PORT+1,al mov ax,[rl_crt] ; сегмент видеопамяти mov es,ax call enable_a20 ; открываем линию A20 mov [real_ss],ss ; сохраняем сегментные mov [real_es],es ; регистры ; -------- Перепрограммируем контроллер прерываний -------- ; Устанавливаем для IRQ0-IRQ7 номера прерываний 20h-27h mov dx,MASTER8259A mov ah,20h call set_int_ctrlr ; Устанавливаем для IRQ8-IRQ15 номера прерываний 28h-2Fh mov dx,SLAVE8259A mov ah,28h call set_int_ctrlr ; Загружаем регистры IDTR и GDTR lidt [FWORD idtr] lgdt [QWORD gdt_gdt] ; Переключаемся в защищённый режим mov ax,VIRTUAL_MODE lmsw ax ; jmp far flush db 0ea dw OFFSET flush dw CS_DESCR LABEL flush FAR ; Загружаем селекторы в сегментные регистры mov ax,SS_DESCR mov ss,ax mov ax,DS_DESCR mov ds,ax ; Разрешаем прерывания sti ret ENDP set_pmode ; -------------------------------- ; Возврат в реальный режим ; -------------------------------- DATASEG ; Пустой дескриптор для выполнения возврата ; процессора в реальный режим через перевод ; его в состояние отключения. null_idt idt_struc <> CODESEG PROC set_rmode NEAR mov [real_sp],sp ; Переводим процессор в состояние отключения, ; это эквивалентно аппаратному сбросу, но ; выполняется быстрее. ; Сначала мы загружаем IDTR нулями, затем ; выдаём команду прерывания. lidt [FWORD null_idt] int 3 ; Это старый способ сброса процессора через ; контроллер клавиатуры. ; mov al,SHUT_DOWN ; out STATUS_PORT,al rwait: hlt jmp rwait LABEL shutdown_return FAR in al,INT_MASK_PORT and al,0 out INT_MASK_PORT,al mov ax,DGROUP mov ds,ax assume ds:DGROUP cli mov ss,[real_ss] mov sp,[real_sp] mov ax,000dh out CMOS_PORT,al sti mov es,[real_es] call disable_a20 ret ENDP set_rmode ; ------------------------------------------------- ; Обработка исключений ; ------------------------------------------------- ; Обработчики исключений. Записываем в AX номер ; исключения и передаём управление процедуре ; shutdown LABEL exc_00 WORD mov ax,0 jmp shutdown LABEL exc_01 WORD mov ax,1 jmp shutdown LABEL exc_02 WORD mov ax,2 jmp shutdown LABEL exc_03 WORD mov ax,3 jmp shutdown LABEL exc_04 WORD mov ax,4 jmp shutdown LABEL exc_05 WORD mov ax,5 jmp shutdown LABEL exc_06 WORD mov ax,6 jmp shutdown LABEL exc_07 WORD mov ax,7 jmp shutdown LABEL exc_08 WORD mov ax,8 jmp shutdown LABEL exc_09 WORD mov ax,9 jmp shutdown LABEL exc_0A WORD mov ax,0ah jmp shutdown LABEL exc_0B WORD mov ax,0bh jmp shutdown LABEL exc_0C WORD mov ax,0ch jmp shutdown LABEL exc_0D WORD mov ax,0dh jmp shutdown LABEL exc_0E WORD mov ax,0eh jmp shutdown LABEL exc_0F WORD mov ax,0fh jmp shutdown LABEL exc_10 WORD mov ax,10h jmp shutdown LABEL exc_11 WORD mov ax,11h jmp shutdown LABEL exc_12 WORD mov ax,12h jmp shutdown LABEL exc_13 WORD mov ax,13h jmp shutdown LABEL exc_14 WORD mov ax,14h jmp shutdown LABEL exc_15 WORD mov ax,15h jmp shutdown LABEL exc_16 WORD mov ax,16h jmp shutdown LABEL exc_17 WORD mov ax,17h jmp shutdown LABEL exc_18 WORD mov ax,18h jmp shutdown LABEL exc_19 WORD mov ax,19h jmp shutdown LABEL exc_1A WORD mov ax,1ah jmp shutdown LABEL exc_1B WORD mov ax,1bh jmp shutdown LABEL exc_1C WORD mov ax,1ch jmp shutdown LABEL exc_1D WORD mov ax,1dh jmp shutdown LABEL exc_1E WORD mov ax,1eh jmp shutdown LABEL exc_1F WORD mov ax,1fh jmp shutdown DATASEG exc_msg db "Exception ...., ....:.... code ..... Press any key... " CODESEG ; ----------------------------------------------- ; Вывод на экран номера исключения, кода ошибки, ; дампа регистров и возврат в реальный режим. ; ----------------------------------------------- PROC shutdown NEAR call rdump ; дамп регистров процессора push ax call beep ; звуковой сигнал ; Выводим сообщение об исключении mov ax,[vir_crt] mov es,ax mov bx,1d mov ax,4 mov si,OFFSET exc_msg mov dh,74h mov cx, SIZE exc_msg call writexy pop ax mov bx, 040bh ; номер исключения call Print_Word pop ax mov bx, 0420h ; код ошибки call Print_Word pop ax mov bx, 0416h ; смещение call Print_Word pop ax mov bx, 0411h ; селектор call Print_Word call set_rmode ; возвращаемся в реальный режим mov ax, 0 ; ожидаем нажатия на клавишу int 16h mov bh, 07h call clrscr mov ah,4Ch int 21h ENDP shutdown ; ------------------------------------------------- ; Перепрограммирование контроллера прерываний ; На входе: DX - порт контроллера прерывания ; AH - начальный номер прерывания ; ------------------------------------------------- PROC set_int_ctrlr NEAR mov al,11 out dx,al jmp SHORT $+2 mov al,ah inc dx out dx,al jmp SHORT $+2 mov al,4 out dx,al jmp SHORT $+2 mov al,1 out dx,al jmp SHORT $+2 mov al,0ff out dx,al dec dx ret ENDP set_int_ctrlr ; ------------------------------- ; Разрешение линии A20 ; ------------------------------- PROC enable_a20 NEAR mov al,A20_PORT out STATUS_PORT,al mov al,A20_ON out KBD_PORT_A,al ret ENDP enable_a20 ; ------------------------------- ; Запрещение линии A20 ; ------------------------------- PROC disable_a20 NEAR mov al,A20_PORT out STATUS_PORT,al mov al,A20_OFF out KBD_PORT_A,al ret ENDP disable_a20 ; ---------- Обработчик аппаратных прерываний IRQ2-IRQ7 PROC dummy_iret0 NEAR push ax ; Посылаем сигнал конца прерывания в первый контроллер 8259A mov al,EOI out MASTER8259A,al pop ax iret ENDP dummy_iret0 ; ---------- Обработчик аппаратных прерываний IRQ8-IRQ15 PROC dummy_iret1 NEAR push ax ; Посылаем сигнал конца прерывания в первый ; и второй контроллеры 8259A mov al,EOI out MASTER8259A,al out SLAVE8259A,al pop ax iret ENDP dummy_iret1 ; ------------------------------------------ ; Процедура выдаёт короткий звуковой сигнал ; ------------------------------------------ PROC beep NEAR push ax bx cx in al,KBD_PORT_B push ax mov cx,80 beep0: push cx and al,11111100b out KBD_PORT_B,al mov cx,60 idle1: loop idle1 or al,00000010b out KBD_PORT_B,al mov cx,60 idle2: loop idle2 pop cx loop beep0 pop ax out KBD_PORT_B,al pop cx bx ax ret ENDP beep ; ------------------------------------------------ ; Процедура задерживает выполнение программы ; на некоторое время, зависящее от быстродействия ; процессора. ; ------------------------------------------------ PROC pause NEAR push cx mov cx,10 ploop0: push cx xor cx,cx ploop1: loop ploop1 pop cx loop ploop0 pop cx ret ENDP pause ; ------------------------------------------ ; Процедуры для работы с клавиатурой ; ------------------------------------------ DATASEG key_flag db 0 key_code dw 0 ext_scan db 0 keyb_status dw 0 CODESEG ; ---------------------------------------------- ; Обработчик аппаратного прерывания клавиатуры ; ---------------------------------------------- PROC Keyb_int NEAR call beep ; выдаём звуковой сигнал push ax mov al, [ext_scan] ; расширенный скан-код cmp al, 0 ; или обычный ? jz normal_scan1 ; --------- обработка расширенного скан-кода ------------- cmp al, 0e1h ; это клавиша <Pause>? jz pause_key in al, 60h ; вводим скан-код cmp al, 2ah ; игнорируем префикс 2Ah jz intkeyb_exit_1 cmp al, 0aah ; игнорируем отпускание jz intkeyb_exit_1 ; клавиш mov ah, [ext_scan] ; записываем скан-код и call Keyb_PutQ ; расширенный скан-код ; в "очередь", состоящую ; из одного слова mov al, 0 ; сбрасываем признак mov [ext_scan], al ; получения расширенного jmp intkeyb_exit ; скан-кода pause_key: ; обработка клавиши <Pause> in al, 60h ; вводим скан-код cmp al, 0c5h ; если это код <Pause>, jz pause_key1 ; записываем его в очередь, cmp al, 45h ; иначе игнорируем jz pause_key1 jmp intkeyb_exit pause_key1: mov ah, [ext_scan] ; запись в очередь call Keyb_PutQ ; кода клавиши <Pause> mov al, 0 ; сбрасываем признак mov [ext_scan], al ; получения расширенного jmp intkeyb_exit ; скан-кода ; --------- обработка обычного скан-кода ------------- normal_scan1: in al, 60h ; вводим скан-код cmp al, 0feh ; игнорируем FEh jz intkeyb_exit cmp al, 0e1h ; расширенный скан-код? jz ext_key ; если да, то на обработку ; расширенного скан-кода cmp al, 0e0h jnz normal_scan ext_key: mov [ext_scan], al ; устанавливаем признак jmp intkeyb_exit ; расширенного скан-кода ; Сброс признака расширенного скан-кода и выход intkeyb_exit_1: mov al, 0 mov [ext_scan], al jmp intkeyb_exit ; Запись нормального скан-кода в очередь и выход normal_scan: mov ah, 0 call Keyb_PutQ intkeyb_exit: in al, 61h ; разблокируем клавиатуру mov ah, al or al, 80h out 61h, al xchg ah, al out 61h, al mov al,EOI ; посылаем сигнал конца out MASTER8259A,al ; прерывания pop ax sti iret ENDP Keyb_int ; --------------------------------------------------- ; Запись скан-кода и расширенного скан-кода в ; "буфер", состоящий из одного слова. ; --------------------------------------------------- PROC Keyb_PutQ NEAR push ax mov [key_code], ax ; записываемый код ; ------- Обрабатываем переключающие клавиши --------- cmp ax, 002ah ; L_SHIFT down jnz @@kb1 mov ax, [keyb_status] or ax, L_SHIFT mov [keyb_status], ax jmp keyb_putq_exit @@kb1: cmp ax, 00aah ; L_SHIFT up jnz @@kb2 mov ax, [keyb_status] and ax, NL_SHIFT mov [keyb_status], ax jmp keyb_putq_exit @@kb2: cmp ax, 0036h ; R_SHIFT down jnz @@kb3 mov ax, [keyb_status] or ax, R_SHIFT mov [keyb_status], ax jmp keyb_putq_exit @@kb3: cmp ax, 00b6h ; R_SHIFT up jnz @@kb4 mov ax, [keyb_status] and ax, NR_SHIFT mov [keyb_status], ax jmp keyb_putq_exit @@kb4: cmp ax, 001dh ; L_CTRL down jnz @@kb5 mov ax, [keyb_status] or ax, L_CTRL mov [keyb_status], ax jmp keyb_putq_exit @@kb5: cmp ax, 009dh ; L_CTRL up jnz @@kb6 mov ax, [keyb_status] and ax, NL_CTRL mov [keyb_status], ax jmp keyb_putq_exit @@kb6: cmp ax, 0e01dh ; R_CTRL down jnz @@kb7 mov ax, [keyb_status] or ax, R_CTRL mov [keyb_status], ax jmp keyb_putq_exit @@kb7: cmp ax, 0e09dh ; R_CTRL up jnz @@kb8 mov ax, [keyb_status] and ax, NR_CTRL mov [keyb_status], ax jmp keyb_putq_exit @@kb8: cmp ax, 0038h ; L_ALT down jnz @@kb9 mov ax, [keyb_status] or ax, L_ALT mov [keyb_status], ax jmp keyb_putq_exit @@kb9: cmp ax, 00b8h ; L_ALT up jnz @@kb10 mov ax, [keyb_status] and ax, NL_ALT mov [keyb_status], ax jmp keyb_putq_exit @@kb10: cmp ax, 0e038h ; R_ALT down jnz @@kb11 mov ax, [keyb_status] or ax, R_ALT mov [keyb_status], ax jmp keyb_putq_exit @@kb11: cmp ax, 0e0b8h ; R_ALT up jnz @@kb12 mov ax, [keyb_status] and ax, NR_ALT mov [keyb_status], ax jmp keyb_putq_exit @@kb12: cmp ax, 003ah ; CAPS_LOCK up jnz @@kb13 mov ax, [keyb_status] xor ax, CAPS_LOCK mov [keyb_status], ax jmp keyb_putq_exit @@kb13: cmp ax, 00bah ; CAPS_LOCK down jnz @@kb14 jmp keyb_putq_exit @@kb14: cmp ax, 0046h ; SCR_LOCK up jnz @@kb15 mov ax, [keyb_status] xor ax, SCR_LOCK mov [keyb_status], ax jmp keyb_putq_exit @@kb15: cmp ax, 00c6h ; SCR_LOCK down jnz @@kb16 jmp keyb_putq_exit @@kb16: cmp ax, 0045h ; NUM_LOCK up jnz @@kb17 mov ax, [keyb_status] xor ax, NUM_LOCK mov [keyb_status], ax jmp keyb_putq_exit @@kb17: cmp ax, 00c5h ; NUM_LOCK down jnz @@kb18 jmp keyb_putq_exit @@kb18: cmp ax, 0e052h ; INSERT up jnz @@kb19 mov ax, [keyb_status] xor ax, INSERT mov [keyb_status], ax jmp keyb_putq_exit @@kb19: cmp ax, 0e0d2h ; INSERT down jnz @@kb20 jmp keyb_putq_exit @@kb20: test ax, 0080h ; фильтруем отжатия клавиш jnz keyb_putq_exit mov al, 0ffh ; устанавиваем признак mov [key_flag], al ; готовности для чтения ; символа из "буфера" keyb_putq_exit: pop ax ret ENDP Keyb_PutQ ; ----------------------------------------------------- ; Программное прерывание, предназначенное для чтения ; символа из буфера клавиатуры. По своим функциям ; напоминает прерывание INT 16h реального режима. ; В AX возвращается скан-код нажатой клавиши, ; в BX - состояние переключающих клавиш. ; ----------------------------------------------------- PROC Int_30h_Entry NEAR push dx ; запрещаем прерывания и cli ; сбрасываем признак mov al, 0 ; готовности скан-кода mov [key_flag], al ; в буфере клавиатуры ; Ожидаем прихода прерывания от клавиатуры. ; Процедура клавиатурного прерывания установит ; признак в переменной key_flag. keyb_int_wait: sti ; разрешаем прерывания nop ; ждём прерывание nop cli ; запрещаем прерывания mov al, [key_flag] ; и опрашиваем флаг cmp al, 0 ; готовности скан-кода jz keyb_int_wait mov al, 0 ; сбрасываем флаг mov [key_flag], al ; готовности mov ax, [key_code] ; записываем скан-код mov bx, [keyb_status] ; и состояние переключающих ; клавиш sti ; разрешаем прерывания pop dx iret ENDP Int_30h_Entry ; ------------------------------------------- ; TIMER section ; ------------------------------------------- DATASEG timer_cnt dw 0 CODESEG PROC Timer_int NEAR cli push ax ; Увеличиваем содержимое счётчика времени mov ax, [timer_cnt] inc ax mov [timer_cnt], ax ; Примерно раз в секунду выдаём звуковой сигнал test ax, 0fh jnz timer_exit call beep timer_exit: ; Посылаем команду конца прерывания mov al,EOI out MASTER8259A,al pop ax sti iret ENDP Timer_int ; -------------------------------------------------- ; Процедуры обслуживания видеоконтроллера ; -------------------------------------------------- DATASEG columns db 80d rows db 25d rl_crt dw COLOR_SEG vir_crt dw CRT_DESCR curr_line dw 0d text_buf db " " CODESEG ; ----------------------------------------- ; Определение адреса видеопамяти ; ----------------------------------------- PROC set_crt_base NEAR mov ax,40 mov es,ax mov bx,[WORD es:4a] mov [columns],bl mov bl,[BYTE es:84] inc bl mov [rows],bl mov bx,[WORD es:PORT_6845] cmp bx,COLOR_PORT je color_crt mov [rl_crt],MONO_SEG mov [vir_crt],MDA_DESCR color_crt: ret ENDP set_crt_base ; ------------------------------------- ; Запись строки в видеопамять ; ------------------------------------- PROC writexy NEAR push si push di mov dl,[columns] mul dl add ax,bx shl ax,1 mov di,ax mov ah,dh write_loop: lodsb stosw loop write_loop pop di pop si ret ENDP writexy ; --------------------------------------- ; Стирание экрана (в реальном режиме) ; --------------------------------------- PROC clrscr NEAR xor cx,cx mov dl,[columns] mov dh,[rows] mov ax,0600 int 10 ret ENDP clrscr DATASEG hello_msg db " Protected mode monitor *TINY/OS*, v.1.1 for CPU 80286 ¦ © Frolov A.V., 1992 " CODESEG ; ------------------------------------ ; Вывод начального сообщения ; в защищённом режиме ; ------------------------------------ PROC write_hello_msg NEAR mov ax,[vir_crt] mov es,ax mov si,OFFSET hello_msg mov bx,0 mov ax,[curr_line] inc [curr_line] mov cx,SIZE hello_msg mov dh,30h call writexy call beep ret ENDP write_hello_msg ; ---------------------------------------------- ; Процедура выводит на экран содержимое AX ; (x,y) = (bh, bl) ; ---------------------------------------------- PROC Print_Word near push ax push bx push dx push ax mov cl,8 rol ax,cl call Byte_to_hex mov [text_buf], dh mov [text_buf+1], dl pop ax call Byte_to_hex mov [text_buf+2], dh mov [text_buf+3], dl mov si, OFFSET text_buf mov dh, 70h mov cx, 4 mov al, bh mov ah, 0 mov bh, 0 call writexy pop dx pop bx pop ax ret ENDP Print_Word DATASEG tabl db '0123456789ABCDEF' CODESEG ; ----------------------------------------- ; Преобразование байта в шестнадцатеричный ; символьный формат ; al - входной байт ; dx - выходное слово ; ----------------------------------------- PROC Byte_to_hex near push cx push bx mov bx, OFFSET tabl push ax and al,0fh xlat mov dl,al pop ax mov cl,4 shr al,cl xlat mov dh,al pop bx pop cx ret ENDP Byte_to_hex DATASEG reg_title db " CS IP AX BX CX DX SP BP SI DI " ; .... .... .... .... .... .... .... .... sreg_title db " DS ES SS FLAGS " ; .... .... .... .... .... .... .... .... CODESEG ; ------------------------------------------------ ; Вывод на экран содержимого регистров процессора ; ------------------------------------------------ PROC rdump NEAR pushf pusha mov di, es mov ax,[vir_crt] mov es,ax mov si,OFFSET reg_title mov bx,1 ; (X,Y) = (AX,BX) mov ax,6 mov cx,SIZE reg_title mov dh,1fh ; чёрный на голубом фоне call writexy ; Выводим содержимое всех регистров mov ax,cs ; cs mov bx, 0702h call Print_Word mov bp, sp mov ax, [bp+18d] ; ip mov bx, 0708h call Print_Word mov bx, 070eh mov ax,[bp+14d] ; ax call Print_Word mov bx, 0714h mov ax,[bp+8d] ; bx call Print_Word mov bx, 071ah mov ax,[bp+12d] ; cx call Print_Word mov bx, 0720h mov ax,[bp+10d] ; dx call Print_Word mov ax,bp add ax,20d ; sp mov bx, 0726h call Print_Word mov ax,[bp+4d] ; bp mov bx, 072ch call Print_Word mov bx, 0732h mov ax,[bp+2] ; si call Print_Word mov bx, 0738h mov ax, [bp] ; di call Print_Word mov si,OFFSET sreg_title mov bx,1 mov ax,8 mov cx,SIZE sreg_title mov dh,1fh call writexy mov bx, 0902h mov ax, ds ; ds call Print_Word mov bx, 0908h mov ax, di ; es call Print_Word mov bx, 090eh mov ax,ss ; ss call Print_Word mov bx, 0914h mov ax, [bp+16d] ; flags call Print_Word ; Восстанавливаем содержимое регистров popa popf ret ENDP rdump CSEG_SIZE = ($ - start) DATASEG DSEG_SIZE = ($ - DSEG_BEG) wrong db ? END start |