Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
iakovlev.org

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
008
019
02A
03B
04C
05D
06E
07F
1870
1971
1A72
1B73
1C74
1D75
1E76
1F77

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