Creating your own Operating System
by Dark Fiber
A20
Сначала были AT-шки , у которых был доступ к 16 метрам памяти .
Потом появились IBM-XT, и для совместимости с AT шину данных A20 при загрузке XT вырубили ,
и все были счастливы . Шина A20 почему-то контролируется контролером клавиатуры .
За его работу отвечает контролер 8042 .
Как получить доступ ко всей памяти ?
Парадокс A20 заключается в том , что поскольку A20 выключена при загрузке ,
вы получаете доступ всего к 1 метру памяти , независимо от того , сколько у вас памяти .
Для включения A20 нужно использовать hardware IO с использованием 8042 chip.
При этом последовательность действий может быть например такой :
1. Выключаем прерывания
2. Ждем когда очистится буфер клавиатуры
3. Посылаем команду для выключения клавиатуры
4. Ждем когда очистится буфер клавиатуры
5. Читаем порт output
6. Ждем когда очистится буфер клавиатуры
7. Сохраняем байт от input-порта
8. Ждем когда очистится буфер клавиатуры
9. Пишем в порт output
10. Ждем когда очистится буфер клавиатуры
11. Делаем над сохраненным байтом операцию OR 2 и посылаем его (GATE A20 to ON)
12. Ждем когда очистится буфер клавиатуры
13. Включаем клавиатуру
14. Включаем прерывания
C-код :
/* Init the A20 by Dark Fiber */
void init_A20(void)
{
UCHAR a;
disable_ints();
kyb_wait_until_done();
kyb_send_command(0xAD); // disable keyboard
kyb_wait_until_done();
kyb_send_command(0xD0); // Read from input
kyb_wait_until_done();
a=kyb_get_data();
kyb_wait_until_done();
kyb_send_command(0xD1); // Write to output
kyb_wait_until_done();
kyb_send_data(a|2);
kyb_wait_until_done();
kyb_send_command(0xAE); // enable keyboard
enable_ints();
}
ASM-вариант :
;;
;; NASM 32bit assembler
;;
[bits 32]
[section .text]
enable_A20:
cli
call a20wait
mov al,0xAD
out 0x64,al
call a20wait
mov al,0xD0
out 0x64,al
call a20wait2
in al,0x60
push eax
call a20wait
mov al,0xD1
out 0x64,al
call a20wait
pop eax
or al,2
out 0x60,al
call a20wait
mov al,0xAE
out 0x64,al
call a20wait
ret
a20wait:
.l0: mov ecx,65536
.l1: in al,0x64
test al,2
jz .l2
loop .l1
jmp .l0
.l2: ret
a20wait2:
.l0: mov ecx,65536
.l1: in al,0x64
test al,1
jnz .l2
loop .l1
jmp .l0
.l2: ret
Как определить размер памяти ?
Когда компьютеры были старые и памяти было не более 64 метров , ее размер можно было
легко проконтролировать с помощью CMOS . Ну а куда деваться , если ее гигабайт ?
Для контроля памяти в биосе есть ряд функций . Некоторые из них на разных версиях
биоса могут не поддерживаться .
С помощью биоса это можно делать несколькими способами : одни из них имеются во всех биосах ,
другие - только в новейших .
(from Ralf Brown's Interrupt Listing)
--------B-1588-------------------------------
INT 15 - SYSTEM - GET EXTENDED MEMORY SIZE (286+)
AH = 88h
Return: CF clear if successful
AX = number of contiguous KB starting at absolute address 100000h
CF set on error
AH = status
80h invalid command (PC,PCjr)
86h unsupported function (XT,PS30)
TSR использует это прерывание для выделения памяти в пределах 640 метров .
Следующий метод пробы :
/*
* void count_memory (void)
*
* probes memory above 1mb
*
* last mod : 05sep98 - stuart george
* 08dec98 - "" ""
* 21feb99 - removed dummy calls
*
*/
void count_memory(void)
{
register ULONG *mem;
ULONG mem_count, a;
USHORT memkb;
UCHAR irq1, irq2;
ULONG cr0;
/* save IRQ's */
irq1=inb(0x21);
irq2=inb(0xA1);
/* kill all irq's */
outb(0x21, 0xFF);
outb(0xA1, 0xFF);
mem_count=0;
memkb=0;
// store a copy of CR0
__asm__ __volatile("movl %%cr0, %%eax":"=a"(cr0))::"eax");
// invalidate the cache
// write-back and invalidate the cache
__asm__ __volatile__ ("wbinvd");
// plug cr0 with just PE/CD/NW
// cache disable(486+), no-writeback(486+), 32bit mode(386+)
__asm__ __volatile__("movl %%eax, %%cr0",
:: "a" (cr0 | 0x00000001 | 0x40000000 | 0x20000000) : "eax");
do
{
memkb++;
mem_count+=1024*1024;
mem=(ULONG*)mem_count;
a=*mem;
*mem=0x55AA55AA;
// the empty asm calls tell gcc not to rely on whats in its registers
// as saved variables (this gets us around GCC optimisations)
asm("":::"memory");
if(*mem!=0x55AA55AA)
mem_count=0;
else
{
*mem=0xAA55AA55;
asm("":::"memory");
if(*mem!=0xAA55AA55)
mem_count=0;
}
asm("":::"memory");
*mem=a;
}while(memkb<4096 && mem_count!=0);
__asm__ __volatile__("movl %%eax, %%cr0", :: "a" (cr0) : "eax");
mem_end=memkb<<20;
mem=(ULONG*)0x413;
bse_end=((*mem)&0xFFFF)<<6;
outb(0x21, irq1);
outb(0xA1, irq2);
}
Что такое PIC ?
PIC - это программируемый контроллер прерываний - "Programmable Interrupt Controler" -
один из важнейших чипов , без которого архитектура прерываний x86 просто немыслима.
Информацию о событиях на периферии нужно как-то обрабатывать .
Если бы не было PIC , пришлось бы заводить отдельный список всех устройств и постоянно
его контролировать .
В начале в эпоху XT был всего один PIC с 8 прерываниями .
В современном 8259A PIC их целый каскад . Один PIC дает нам 15 прерываний , 16-й используется
для коммутации .
PIC имеет возможность перепрограммироваться на другой набор прерываний .
Дефолтная таблица значений PIC :
PIC | | IRQ | | INT |
0 | | 0 | | 8 |
0 | | 1 | | 9 |
0 | | 2 | | A |
0 | | 3 | | B |
0 | | 4 | | C |
0 | | 5 | | D |
0 | | 6 | | E |
0 | | 7 | | F |
1 | | 8 | | 70 |
1 | | 9 | | 71 |
1 | | A | | 72 |
1 | | B | | 73 |
1 | | C | | 74 |
1 | | D | | 75 |
1 | | E | | 76 |
1 | | F | | 77 |
PIC - программируемое устройство , его можно перепрограммировать ,
с условием , что каждый PIC-вектор должен быть кратен 8 .
Для защищенного режима первые 32 прерывания зарезервированы .
Следующий код перепрограммирует 16 прерываний с 32 по 48 :
#define PIC1 0x20
#define PIC2 0xA0
#define PIC1_COMMAND PIC1
#define PIC1_DATA (PIC1+1)
#define PIC2_COMMAND PIC2
#define PIC2_DATA (PIC2+1)
#define PIC_EOI 0x20
#define ICW1_ICW4 0x01 /* ICW4 (not) needed */
#define ICW1_SINGLE 0x02 /* Single (cascade) mode */
#define ICW1_INTERVAL4 0x04 /* Call address interval 4 (8) */
#define ICW1_LEVEL 0x08 /* Level triggered (edge) mode */
#define ICW1_INIT 0x10 /* Initialization - required! */
#define ICW4_8086 0x01 /* 8086/88 (MCS-80/85) mode */
#define ICW4_AUTO 0x02 /* Auto (normal) EOI */
#define ICW4_BUF_SLAVE 0x08 /* Buffered mode/slave */
#define ICW4_BUF_MASTER 0x0C /* Buffered mode/master */
#define ICW4_SFNM 0x10 /* Special fully nested (not) */
void remap_pics(int pic1, int pic2)
{
UCHAR a1, a2;
a1=inb(PIC1_DATA);
a2=inb(PIC2_DATA);
outb(PIC1_COMMAND, ICW1_INIT+ICW1_ICW4);
io_wait();
outb(PIC2_COMMAND, ICW1_INIT+ICW1_ICW4);
io_wait();
outb(PIC1_DATA, pic1);
io_wait();
outb(PIC2_DATA, pic2);
io_wait();
outb(PIC1_DATA, 4);
io_wait();
outb(PIC2_DATA, 2);
io_wait();
outb(PIC1_DATA, ICW4_8086);
io_wait();
outb(PIC2_DATA, ICW4_8086);
io_wait();
outb(PIC1_DATA, a1);
outb(PIC2_DATA, a2);
}
В основе архитектуры x86 лежит понятие прерывания .
Внешнее событие вызывает процедуру прерывания - interrupt service routines - ISR ,
которые управляются из глобальной таблицы IDT .
Это внешнее событие может быть двояким - либо оно вызывается железом ,
либо сгенерировано программно . Примером первого может быть нажатие клавиши .
Примером второго может служить INT 21h.
Между ISR и любой другой процедурой , написанной программистом , существует принципиальная
разница . У ISR кодом возврата является команда IRET , в то время как обычная
оканчивается на RET.
Для того , чтобы из си вызвать ISR , используя GCC , нужно написать на ассемблере
примерно следующее :
.globl _clock_isr
.align 4
_clock_isr:
/* save some registers */
pushl %eax
pushl %ds
pushl %es
pushl %fs
pushl %gs
/* set our descriptors up to a DATA selector */
movl $0x10, %eax
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
/* call our clock routine */
call _ISR_clock
/* clear the PIC (clock is a PIC interrupt) */
movl $0x20, %eax
outb %al, $0x20
/* restor our regs */
popl %gs
popl %fs
popl %es
popl %ds
popl %eax
iret
/* filename : clock.c */
/* clock routine */
/* tell our linker clock_isr is not in this file */
extern void clock_isr(void);
__volatile__ unsigned long clock_count=0L;
void ISR_clock(void)
{
clock_count++;
}
|
Николай | Спасибо за сайт, всё очень интересно и полезно.
Но АТ продвинутей ХТ и создан был позже.
http:ru.wikipedia.orgwikiIBM_PC
Извините если я понял как-то не так аббревиатуру АТ.
У ХТ были процессоры 8086 и позже 8088 (для бедных),
для АТ использовались 80286. 2012-06-13 10:08:30 | Яковлев Сергей | IBM Personal Computer XT был реализован в 1983 году.
IBM АТ появился на год позже.
Все правильно - у меня в начале статьи опечатка
2012-06-13 20:53:45 | |
|