linux/arch/i386/boot/setup.S

setup.S sistem verilerinin BIOS'dan alınmasından ve onların uygun bir bellek bölgesine yerleştirilmesinden sorumludur.

GNU GRUB ve LILO gibi diğer önyükleyiciler de bzImage'ı yükleyebilirler. Bu tür önyükleyiciler bzImage'ı belleğe yüklemeli ve "gerçek-biçim çekirdek başlıkları"nı kurmalıdırlar, özellikle type_of_loader; sonra denetimi bsetup'a devreder. setup.S şunları varsayar:

  • bsetup veya setup SETUPSEG:0'a yüklenmemiş olabilir, örn. denetim setup.S'ye geçtiğinde CS SETUPSEG'e eşit olmayabilir;

  • setup'ın ilk 4 sektörü bootsect'den hemen sonra yüklenir. Sıfırlama vmlinux'den önce gelecek şekilde SYSSEG:0'da yüklü olabilir, bu varsayım bsetup'ta etkili olmaz.

Başlık

/* LILO'nun doğru yüklendiğinden emin olmak için imza kelimeleri */
#define SIG1    0xAA55
#define SIG2    0x5A5A

INITSEG  = DEF_INITSEG         # 0x9000, önyüklemeyi buraya taşır, yolun dışına
SYSSEG   = DEF_SYSSEG          # 0x1000, sistem 0x10000'a yüklenir (65536).
SETUPSEG = DEF_SETUPSEG        # 0x9020, bu geçerli bölüttür
                               # ... ve CS'nin önceki içeriği

DELTA_INITSEG = SETUPSEG - INITSEG      # 0x0020

.code16
.text

///////////////////////////////////////////////////////////////////////////////
start:
{
  goto trampoline();              // sıradaki başlığı atla
}

# Bu setup başlığıdır ve %cs:2 (old 0x9020:2) adresinden başlamalıdır
                .ascii  "HdrS"          # başlık imzası
                .word   0x0203          # başlık sürüm numarası (>= 0x0105)
                                        # yoksa eski loadlin-1.5 başarısız olur
realmode_swtch: .word   0, 0            # default_switch, SETUPSEG
start_sys_seg:  .word   SYSSEG
                .word   kernel_version  # çekirdek sürüm dizgesini göstererek
                                        # yukarıdaki başlık bölümü
                                        # loadlin-1.5 (başlık v1.5) ile uyumlu.
                                        # Değiştirmeyin
// kernel_version defined below
type_of_loader: .byte   0               # = 0, eski (LILO, Loadlin,
                                        #      Bootlin, SYSLX, bootsect...)
                                        # atanmış id değerleri için
                                        # bakınız Documentation/i386/boot.txt
# bayraklar, kullanılmayan bitler 0 olmalı, (RFU) loadflags arasındaki bitler
loadflags:
LOADED_HIGH     = 1               # Sıfır değilse, çekirdek yüksek yüklenmiştir
CAN_USE_HEAP    = 0x80            # Sıfır değilse, yükleyici ayrıca setup.S'nin
                                  # gerisinde ne kadar alanın yığın (heap)
                                  # olarak kullanılacağını tutmak için
                                  # heap_end_ptr'ye sahiptir.
                                  # Neyin boş olduğunu sadece yükleyici bilir
#ifndef __BIG_KERNEL__
                .byte   0
#else
                .byte   LOADED_HIGH
#endif
setup_move_size: .word  0x8000    # kurulum 0x90000'ye yüklenmediğinde
                                  # taşıma boyutu. Çekirdeğe sıçramadan
                                  # hemen önce kurulumu 0x90000'a taşıyacağız.


                                  # Bununla birlikte geride bıraktığımız
                                  # ne kadar yüklenmesi gereken veri
                                  # bulunduğunu sadece yükleyici bilir.
                                  # burada yükleyiciler 32-bit kod
code32_start:                     # için farklı başlangıç
                                  # adresleri koyarlar.
#ifndef __BIG_KERNEL__
                .long   0x1000    #   0x1000 = zImage için varsayılan
#else
                .long   0x100000  # 0x100000 = büyük çekirdek için öntanımlı
#endif
ramdisk_image:  .long   0         # yüklü ramdisk görüntüsünün adresi
                                  # Burada yükleyici görüntüyü yüklediği
                                  # 32-bit adresi koyar. Bu sadece
                                  # çekirdek tarafından okunacaktır.
ramdisk_size:   .long   0         # boyutu bayt cinsindendir
bootsect_kludge:
                .word  bootsect_helper, SETUPSEG
heap_end_ptr:   .word   modelist+1024  # (Başlık sürümü 0x0201 veya daha büyük)
                                       # buradan sonra kurulum sonuna kadar
                                  # boşluklar (özel) kurulum tarafından
                                  # yerel yığın amaçları için kullanılabilir.
// modelist .text bölümünün sonundadır
pad1:           .word   0
cmd_line_ptr:   .long 0           # (Başlık sürümü 0x0202 veya daha büyük)
                                  # Sıfır değilse, çekirdek komut
                                  # satırına 32-bit bir gösterici.
                                  # Komut satırı kurulumun başı ile
                                  # alçak bellek arasına (0xa0000)
                                  # yerleştirilmeli veya okunmadan önce
                                  # üzerine yazılmalı. Eğer bu alan
                                  # kullanılırsa, 0x90000 bölütüyle ilgili
                                  # sihirli birşey kalmaz; kurulum
                                  # alçak bellekte 0x10000 veya
                                  #  daha yüksek herhangi bir
                                  # yere yerleştirilebilir.
ramdisk_max:    .long __MAXMEM-1  # (Başlık sürümü 0x0203 veya daha büyük)
                                  # initrd içeriği için en
                                  # yüksek güvenli adres

__MAXMEM tanımlaması linux/asm-i386/page.h içindedir:

/*
 * 0xC0000000'ın bir __PAGE_OFFSET değeri çekirdeğin bir gigabayt
 * sanal adres boşluğuna sahip olduğu anlamına gelir ki bu da
 * kullanabileceğiniz fiziksel bellek miktarını 950MB'a sınırlar
 */
#define __PAGE_OFFSET           (0xC0000000)

/*
 * Bu kadar adres boşluğu vmalloc() ve iomap() olduğu kadar
 * "fixmap" eşleştirmeleri (mappings) için de tahsis edilir.
 */
#define __VMALLOC_RESERVE       (128 << 20)

#define __MAXMEM                (-__PAGE_OFFSET-__VMALLOC_RESERVE)

__MAXMEM = 1G - 128M değerini verir.

Bu başlık bazı plan örneklerini takip etmelidir. linux/Documentation/i386/boot.txt belgesine başvurun:

Konum   Proto   İsim            Anlamı
/Boyut
0200/2  2.00+   jump            Sıçrama komutu
0202/4  2.00+   header          Sihirli imza "HdrS"
0206/2  2.00+   version         Desteklene önyükleme protokolü sürümü
0208/4  2.00+   realmode_swtch  Önyükleme yükleyici çengeli (hook)
020C/2  2.00+   start_sys       load-low bölütü (0x1000) (modası geçmiş)
020E/2  2.00+   kernel_version  Çekirdek sürüm dizgesi göstericisi
0210/1  2.00+   type_of_loader  Önyükleme yükleyici belirteci
0211/1  2.00+   loadflags       Önyükleme protokolü seçenek bayrakları
0212/2  2.00+   setup_move_size Yüksek bellek boyutuna taşı
                                (çengellerle kullanılır)
0214/4  2.00+   code32_start    Önyükleyici çengeli
0218/4  2.00+   ramdisk_image   initrd yükleme adresi
                                (önyükleyici tarafından atanır)
021C/4  2.00+   ramdisk_size    initrd boyutu (önyükleyici tarafından atanır)
0220/4  2.00+   bootsect_kludge KULLANMAYIN - sadece bootsect.S kullanımı için
0224/2  2.01+   heap_end_ptr    kurulum bittikten sonra boş bellek
0226/2  N/A     pad1            Kullanılmaz
0228/4  2.02+   cmd_line_ptr    çekirdek komut satırına 32-bit gösterici
022C/4  2.03+   initrd_addr_max En yüksek yasal initrd adresi

Kod Bütünlüğünü Kontrol Et

setup kodu sürekli olmayabileceği için önce kod bütünlüğünü kontrol etmeliyiz.

///////////////////////////////////////////////////////////////////////////////
trampoline()
{
  start_of_setup();       // asla dönmez
  .space 1024;
}

///////////////////////////////////////////////////////////////////////////////
// tüm kodun yüklenip yüklenmediğini görmek için imzayı kontrol et
start_of_setup()
{
  // Bootlin bunun daha önce yapılmasına bağlıdır, bakınız bootlin:technic.doc
  int13/AH=15h(AL=0, DL=0x81);
  // int13/AH=15h: DİSK - DİSK TÜRÜNÜ AL

#ifdef SAFE_RESET_DISK_CONTROLLER
  int13/AH=0(AL=0, DL=0x80);
  // int13/AH=00h: DİSK - DİSK SİSTEMİNİ RESETLE
#endif

  DS = CS;
  // kurulum sonunda imzayı kontrol et
  if (setup_sig1!=SIG1 || setup_sig2!=SIG2) {
    goto bad_sig;
  }
  goto goodsig1;
}

///////////////////////////////////////////////////////////////////////////////
// bazı küçük işlevler
prtstr();  /* DS:SI'teki ascii'leri yaz */
prtsp2();  /* çift boşluk yaz */
prtspc();  /* tek boşluk yaz  */
prtchr();  /* AL'deki ascii'leri yaz */
beep();    /* CTRL-G yaz, örn. bip */

Kod bütünlüğünü doğrulamak için imza kontrol edilir.

İmza bulunmazsa kalan setup kodu SYSSEG:0'da vmlinux öncesinde yer alır.

no_sig_mess: .string "No setup signature found ..."

goodsig1:
  goto goodsig;                           // yakın sıçrama yap

///////////////////////////////////////////////////////////////////////////////
// kalan setup kodunu SYSSEG:0'dan CS:0800'e taşı
bad_sig()
  DELTA_INITSEG = 0x0020 (= SETUPSEG - INITSEG)
  SYSSEG = 0x1000
  word start_sys_seg = SYSSEG;            // kurulum başlığında tanımlı
{
  DS = CS - DELTA_INITSEG;                // INITSEG olarak da bilinir
  BX = (byte)(DS:[497]);                  // örn. setup_sects

  // ilk 4 sekctör zaten yüklü
  CX = (BX - 4) << 8;           // kelime cinsinden kalan kod (2-bayt)
  start_sys_seg = (CX >> 3) + SYSSEG;     // gerçek sistem kodu başlangıcı
  move SYSSEG:0 to CS:0800 (CX*2 bytes);

  if (setup_sig1!=SIG1 || setup_sig2!=SIG2) {
no_sig:
    prtstr("No setup signature found ...");
no_sig_loop:
    hlt;
    goto no_sig_loop;
  }
}

hlt komutu komut çalıştırmayı durdurur ve işlemciyi halt durumuna getirir. İşlemci halt kipine girildiğini belirtecek şekilde özel bir taşıt dizisi üretir. Etkin bir kesme (NMI dahil) oluştuğunda, işlemci çalıştırmayı hlt komutundan sonra devam ettirir ve komut göstericisi, hlt'ı takip eden komutu göstererek, kesme yöneticisi çağırılmadan önce yığıta kaydedilecektir. Böylece işlemciyi tekrar halt durumuna koymak için hlt'tan sonra jmp komutuna ihtiyaç duyarız.

setup kodu doğru yere taşınmıştır. start_sys_seg değişkeni gerçek sistem kodunun başladığı yeri gösterir. Eğer bad_sig olmazsa start_sys_seg, SYSSEG olarak kalır.

Yükleyici Türünü Kontrol Et

Yükleyicinin imgeyle uyumluluğunu olduğunu kontrol et.

///////////////////////////////////////////////////////////////////////////////
good_sig()
  char loadflags;                 // setup başlığı içinde
  char type_of_loader;            // setup başlığı içinde
  LOADHIGH = 1
{
  DS = CS - DELTA_INITSEG;        // INITSEG olarak da bilinir
  if ( (loadflags & LOADHIGH) && !type_of_loader ) {
    // Hata, eski yükleyiciler büyük-çekirdek yüklemeye çalışırlar
    prtstr("Wrong loader, giving up...");
    goto no_sig_loop;             // yukarıda bad_sig()'de tanımlı
  }
}

loader_panic_mess: .string "Wrong loader, giving up..."

bootsect_helper() tarafından bvmlinux yüklendiğinde type_of_loader'un 0x20 olarak değiştiğine dikkat edin.

Bellek Boyutunu

kB cinsinden genişletilmiş bellek boyutunu (1M üzeri) elde etmek için üç değişik bellek saptama şeması dene.

İlk olarak bir bellek haritası oluşturmamızı sağlayan e820h'yi dene; sonra 32 bitlik bellek boyutu döndüren e801h'yi ve son olarak 0-64M döndüren 88h'yi dene.

///////////////////////////////////////////////////////////////////////////////
// bellek boyunu al
loader_ok()
  E820NR  = 0x1E8
  E820MAP = 0x2D0
{
  // bu işleve girerken, DS = CS-DELTA_INITSEG; INITSEG olarak da bilinir
  (long)DS:[0x1E0] = 0;

#ifndef STANDARD_MEMORY_BIOS_CALL
  (byte)DS:[0x1E8] = 0;                   // E820NR

  /* method E820H: bakınız ACPI spec
    * bellek haritası (from hell).  e820h belleği farklı türlerden
    * bir bütün deste olarak sınıflandırılmış şekilde döndürür, ve
    * bellek deliklerine ve herşeye izin verir. Biz bu bellek
    * haritasını tararız ve ilk 32 bellek alanının listesini oluştururuz,
    * [E820MAP]'den döneriz. */
meme820:
  EBX = 0;
  DI = 0x02D0;                            // E820MAP
  do {
jmpe820:
    int15/EAX=E820h(EDX='SMAP', EBX, ECX=20, ES:DI=DS:DI);
    // int15/AX=E820h: GET SYSTEM MEMORY MAP
    if (failed || 'SMAP'!=EAX) break;
    // if (1!=DS:[DI+16]) continue; // kullanışsız
good820:
    if (DS:[1E8]>=32) break;        // entry# > E820MAX
    DS:[0x1E8]++;                   // entry# ++;
    DI += 20;                       // tamponu sonraki için ayarla
again820:
  } while (!EBX)                          // bitmedi
bail820:

  /* method E801H:
    * 1k parça boyutuyla bellek boyutu, loadlin karıştırmamak için.
    * 0xe801 bellek boyutunu tamamen farklı bir yerde tutarız
    * çünkü muhtemelen 16 bitten daha uzun olacaktır
    * (1e0 kullanınız çünkü bu Larry Augustine'in alternatif bellek
    * tespit şemasını kullanma yöntemidir ve bu yöntem
    * herşeyi aynı yere yazma konusunda hassastır.) */
meme801:
  stc;            // hatalı BIOSlar için uğraş
  CX = DX = 0;
  int15/AX=E801h;
  /* int15/AX=E801h: >64M YAPILANDIRMALAR İÇİN BELLEK BOYUTUNU AL
    *   AX = K cinsinden 1M ve 16M arasında bellek boyutu (en çok 3C00 = 15MB)
    *   BX = genişletilmiş bellek, 16M üzeri, 64K bloklar halinde
    *   CX = K cinsinden 1M'dan 16M'a yapılandırılmış bellek
    *   DX = 16M üzeri yapılandırılmış bellek, 64K bloklar halinde */
  if (failed) goto mem88;
  if (!CX && !DX) {
    CX = AX;
    DX = BX;
  }
e801usecxdx:
  (long)DS:[0x1E0] = ((EDX & 0xFFFF) << 6) + (ECX & 0xFFFF);      // in K
#endif

mem88:  // eski geleneksel yöntem
  int15/AH=88h;
  /* int15/AH=88h: SİSTEM - GENİŞLETİLMİŞ BELLEK BOYUTU
   *   AX = mutlak 100000h adresinden başlayan sürekli kB'ların sayısı */
  DS:[2] = AX;
}

Donanım Desteği

Klavye, ekran kartı, harddisk, MCA bus'ı ve işaretleme cihazı gibi donanımların desteğini kontrol et.

{
  // klavye tekrarlama oranını en çoğa ayarla
  int16/AX=0305h(BX=0);
  // int16/AH=03h: KEYBOARD - SET TYPEMATIC RATE AND DELAY

  /* Ekran kiplerini kullanıcıya göstermek için
    *   ekran kartını ve parametrelerini kontrol et. */
  video();                        // see video.S

  // hd0 ve hd1 verisini al
  hd0 verisini (*int41)'dan CS-DELTA_INITSEG:0080'ya (16 bytes) kopyala;
  // int41: SYSTEM DATA - HARD DISK 0 PARAMETRE TABLO ADRESİ
  hd1 verisini (*int46)'dan CS-DELTA_INITSEG:0090'ya (16 bytes) kopyala;
  // int46: SYSTEM DATA - HARD DISK 1 PARAMETRE TABLE ADRESİ
  // hd1 var mı kontrol et
  int13/AH=15h(AL=0, DL=0x81);
  // int13/AH=15h: DISK - DİSK TÜRÜNÜ AL
  if (failed || AH!=03h) {        // AH==03h eğer harddisk ise
no_disk1:
    temizle CS-DELTA_INITSEG:0090 (16 bytes);
  }
is_disk1:

  // Mikro Kanal veriyolu (Micro Channel-MCA bus) için kontrol et
  CS-DELTA_INITSEG:[0xA0] = 0;    // tablo uzunluğunu 0'a ayarla
  int15/AH=C0h;
  /* int15/AH=C0h: SİSTEM - YAPILANDIRMAYI AL
    *   ES:BX = ROM yapılandırma tablosu */
  if (failed) goto no_mca;
    ROM yapılandırma tablosunu (ES:BX)'den CS-DELTA_INITSEG:00A0 adresine taşı;
  // CX = (table length<14)? CX:16;    sadece ilk 16 bayt
no_mca:

  // PS/2 noktalama cihazlarını (pointing device) kontrol et
  CS-DELTA_INITSEG:[0x1FF] = 0;   // varsayılan noktalama cihazı 0
  int11h();
  // int11h: BIOS - EKİPMAN LİSTESİNİ AL
  if (AL & 0x04) {                // fare kuruldu
          DS:[0x1FF] = 0xAA;
  }
}

APM Desteği

BIOS APM desteğini kontrol et.

#if defined(CONFIG_APM) || defined(CONFIG_APM_MODULE)
{
  DS:[0x40] = 0;                  // sürüm = 0 APM BIOS olmadığı anlamına gelir
  int15/AX=5300h(BX=0);
  // int15/AX=5300h: Advanced Power Management v1.0+ - INSTALLATION CHECK
  if (failed || 'PM'!=BX || !(CX & 0x02)) goto done_apm_bios;
  // (CX & 0x02) 32 bit desteklendiği anlamına gelir
  int15/AX=5304h(BX=0);
  // int15/AX=5304h: Advanced Power Management v1.0+ - DISCONNECT INTERFACE
  EBX = CX = DX = ESI = DI = 0;
  int15/AX=5303h(BX=0);
  /* int15/AX=5303h: Advanced Power Management v1.0+
    *   - CONNECT 32-BIT PROTMODE INTERFACE */
  if (failed) {
no_32_apm_bios:                     // no_32_apm_bios etiketini buraya taşıdım
    DS:[0x4C] &= ~0x0002;   // 32 bit destekleme bitini kaldır
    goto done_apm_bios;
  }
  DS:[0x42] = AX, 32-bit kod bölütü temel adresi;
  DS:[0x44] = EBX, giriş noktası konumu;
  DS:[0x48] = CX, 16-bit kod bölütü temel adresi;
  DS:[0x4A] = DX, 16-bit veri bölütü temel adresi;
  DS:[0x4E] = ESI, APM BIOS kod bölütü uzunluğu;
  DS:[0x52] = DI, APM BIOS veri bölütü uzunluğu;
  int15/AX=5300h(BX=0);     // tekrar kontrol et
  // int15/AX=5300h: Advanced Power Management v1.0+ - INSTALLATION CHECK
  if (success &&  'PM'==BX) {
          DS:[0x40] = AX, APM version;
          DS:[0x4C] = CX, APM flags;
  } else {
apm_disconnect:
    int15/AX=5304h(BX=0);
    /* int15/AX=5304h: Advanced Power Management v1.0+
     * - DISCONNECT INTERFACE */
  }
done_apm_bios:
}
#endif

Korumalı Kip için Hazırlık

// kip seçiciyi çağır
{
  if (realmode_swtch) {
    realmode_swtch();               // kip seçme çengeli
  } else {
rmodeswtch_normal:
    default_switch() {
      cli;                    // hiçbir kesmeye izin yok
      outb(0x80, 0x70);       // NMI etkinleştirmeyi kaldır
    }
  }
rmodeswtch_end:
}

// gerekliyse kodu tekrar konumla
{
  (long)code32 = code32_start;
  if (!(loadflags & LOADED_HIGH)) {       // düşük yüklü zImage
    // 0x0100 <= start_sys_seg < CS-DELTA_INITSEG
do_move0:
    AX = 0x100;
    BP = CS - DELTA_INITSEG;        // INITSEG olarak da bilinir
    BX = start_sys_seg;
do_move:
    sistem imgesini (start_sys_seg:0 .. CS-DELTA_INITSEG:0)'dan
      0100:0'a taşı;                // her seferinde 0x1000 bayt taşı
  }
end_move:

code32_start'ın zImage için 0x1000 adresi ile bzImage için ise 0x100000 adresi ile ilklendirildiğine dikkat edin. code32 değeri denetimin Korumalı Kipe Geç içindekilinux/arch/i386/boot/compressed/head.S'e geçirilmesi için kullanılacaktır. zImage önyüklemesi yapılırsa vmlinux'u 0100:0'a taşır; bzImage önyüklemesi yapılırsa bvmlinux start_sys_seg:0'da kalır. Taşıma adresi linux/arch/i386/boot/compressed/Makefile içindeki -Ttext seçeneğiyle uyuşmalıdır. Bakınız linux/arch/i386/boot/compressed/Makefile.

Sonra, eğer gerekliyse, kod CS-DELTA_INITSEG:0'dan (bbootsect ve bsetup) INITSEG:0'a taşınır.

  DS = CS;                // SETUPSEG olarak da bilinir
  // sürüm <=201 ile geriye dönük uyumluluğa ihtiyacımız olup olmadığını kontrol et
  if (!cmd_line_ptr && 0x20!=type_of_loader && SETUPSEG!=CS) {
    cli;            // taşınırken kesme olarak yığıtı kullanabilir
    // store new SS in DX
    AX = CS - DELTA_INITSEG;
    DX = SS;
    if (DX>=AX) {   // yığıt çerçevesi birlikte taşınacak
      DX = DX + INITSEG - AX; // i.e. SS-CS+SETUPSEG
    }
move_self_1:
    /* CS-DELTA_INITSEG:0'dan INITSEG:0'ya taşı (setup_move_size bayt)
      *  CS:IP üzerindeki kodun üzerine yazmamak için iki adımda
      * (src < dest) taşı fakat aşağıya doğru ("std") */
    move CS-DELTA_INITSEG:move_self_here+0x200
      to INITSEG:move_self_here+0x200,
      setup_move_size-(move_self_here+0x200) bytes;
    // INITSEG:move_self_here+0x200 == SETUPSEG:move_self_here
    goto SETUPSEG:move_self_here;   // artık CS=SETUPSEG
move_self_here:
    move CS-DELTA_INITSEG:0 to INITSEG:0,
      move_self_here+0x200 bytes;   // goto'dan önce eski CS anlamında
    DS = SETUPSEG;
    SS = DX;
  }
end_move_self:
}

Tekrar dikkat edin, bvmlinux'u yüklediğinde bootsect_helper() tarafından type_of_loader'un değeri 0x20 olarak değiştirilmektedir.

A20'yi Etkinleştir

A20 sorunu ve çözümü için A20 - a pain from the past'e başvurun.

  A20_TEST_LOOPS          =  32   # Bekleme başına adım sayısı
  A20_ENABLE_LOOPS        = 255   # deneme için toplam döngü
{
#if defined(CONFIG_MELAN)
  // Enable A20. AMD Elan bug fix.
  outb(0x02, 0x92);               // outb(val, port)
a20_elan_wait:
  while (!a20_test());            // testi geçemedi
  goto a20_done;
#endif

a20_try_loop:
  // Önce, A20 kapısı olmayan bir sistemde olup olmadığımıza bak.
a20_none:
  if (a20_test()) goto a20_done;  // testi geçti

  // Sonra, BIOS'u (INT 0x15, AX=0x2401) dene
a20_bios:
  int15/AX=2401h;
  // Int15/AX=2401h: SYSTEM - later PS/2s - ENABLE A20 GATE
  if (a20_test()) goto a20_done;  // testi geçti

  // Klavye denetleyici üzerinden A20'yi etkinleştirmeye çalış
a20_kbc:
  empty_8042();
  if (a20_test()) goto a20_done;  // BIOS gecikmesi durumunda testi tekrarla
  outb(0xD1, 0x64);               // komut yaz
  empty_8042();
  outb(0xDF, 0x60);               // A20 etkin
  empty_8042();
  // a20 gerçekten etkin olana kadar bekle
a20_kbc_wait:
  CX = 0;
a20_kbc_wait_loop:
  do {
          if (a20_test()) goto a20_done;  // testi geçti
  } while (--CX)

  // Son girişim: "yapılandırma portu A"'yı kullan
  outb((inb(0x92) | 0x02) & 0xFE, 0x92);
  // yapılandırma portu A etkilenene kadar bekle
a20_fast_wait:
  CX = 0;
a20_fast_wait_loop:
  do {
          if (a20_test()) goto a20_done;  // testi geçti
  } while (--CX)

  // A20 hala cevap vermiyor. Tekrar ayarlamayı dene.
  if (--a20_tries) goto a20_try_loop;
  prtstr("linux: fatal error: A20 gate not responding!");
a20_die:
  hlt;
  goto a20_die;
}

a20_tries:
  .byte   A20_ENABLE_LOOPS                // i.e. 255
a20_err_msg:
  .ascii  "linux: fatal error: A20 gate not responding!"
  .byte   13, 10, 0

I/O port işlemleri için, Kaynakça içindeki ilgili başvuru materyallerine bakın.

Korumalı Kipe Geç

Tüm 32 bitlik IA-32 işlemcileri ile kod uyumluluğunu sağlamak için, aşağıdaki korumalı kipe geçmeyi sağlayacak adımları yerine getirin:

  1. İlk GDT girdisinde boş tanımlayıcılı GDT'yi hazırlayın, bir kod bölütü tanımlayıcı ve bir veri bölütü tanımlayıcı;
  2. Maskelenebilir donanım kesmeleri ve NMI dahil kesmeleri etkisiz kılın;
  3. lgdt asm komutunu kullanarak GDTR yazmacına taban adresini ve GDT sınırını yükleyin;
  4. mov cr0 (Intel 386 ve üstü) veya lmsw komutu (Intel 286 ile uyumluluk için) kullanarak CR0 yazmacındaki PE bayrağını etkinleştirin;
  5. Ardından bir uzak jmp veya bir uzak call komutu çalıştırın.

Yığıt normal oku/yaz veri bölütüne yerleştirilebilir, böylece adanmış tanımlayıcıya gerek kalmaz.

a20_done:
{
  lidt    idt_48;         // load idt with 0, 0;

  // DS:gdt'yi doğrusal göstericiye dönüştür
  *(long*)(gdt_48+2) = DS << 4 + &gdt;
  lgdt    gdt_48;

  // yardımcı işlemciyi sıfırla
  outb(0, 0xF0);
  delay();
  outb(0, 0xF1);
  delay();

  // kesmeleri yeniden programla
  outb(0xFF, 0xA1);       // tüm kesmeleri maskele
  delay();
  outb(0xFB, 0x21);       // irq2 dışında tüm irq'ları maskele

  // korumalı kip!
  AX = 1;
  lmsw ax;                // makina durumu kelimesi (word), CR0'ın 0'dan 15'e
                          // kadar bitleri sadece PE, MP, EM ve TS bayraklarını
                          // etkiler
  goto flush_instr;

flush_instr:
  BX = 0;                                 // bir önyükleme gösteren bayrak
  ESI = (CS - DELTA_INITSEG) << 4;        // gerçek kip kod göstericisi
  /* NOT: Yüksek yüklenen büyük çekirdekler için
    * jmpi    0x100000,__KERNEL_CS'ye gereksinim duyarız
    *
    * fakat henüz CS yazmacını yüklemedik,
    * bu yüzden hedef konumun varsayılan boyutu hala 16 bit.
    * Bununla birlikte, bir terim öneki (0x66) kullanarak,
    * CPU bizim 48 bit uzak göstericimizi uygun bir şekilde alır.
    * Bakınız (INTeL 80386 Programmer's Reference Manual,
    * Mixing 16-bit and 32-bit code, page 16-6) */

  // __KERNEL_CS:[(uint32*)code32]'e git;
  .byte   0x66, 0xea
code32: .long   0x1000          //  Korumalı Kip için Hazırlık içinde üstüne yazılır
  .word   __KERNEL_CS     // bölüt 0x10
  // bakınız linux/arch/i386/boot/compressed/head.S:startup_32
}

Uzak jmp komutu (0xea) CS kaydedicisini günceller. kalan bölüt kaydedicileri (DS, SS, ES, FS ve GS) içeriği daha sonra yeniden yüklenmelidir. terim boyutu öneki (0x66) jmp'ı 32 bitlik terim code32'ye kadar çalıştırılmasına zorlamak için kullanılır. Terim boyutu öneki ayrıntıları için bakınız: IA-32 Manual (Vol.1. Ch.3.6. Operand-size and Address-size Attributes, and Vol.3. Ch.17. Mixing 16-bit and 32-bit Code).

Denetim linux/arch/i386/boot/compressed/head.S:startup_32'ye geçirilir. zImage için 0x1000 adresinde; bzImage için 0x100000 adresinde. Bakınız: linux/arch/i386/boot/compressed/head.S.

ESI toplanan sistem verilerinin bellek alanını gösterir. 16 bitlik gerçek kip çekirdek kodundan 32 bitlik kısma parametre geçirmek için kullanılır. Ayrıntılar için linux/Documentation/i386/zero-page.txt dosyasına bakınız.

Daha fazla anahtarlama ayrıntısı için IA-32 Manual Vol.3. (Ch.9.8. Software Initialization for Protected-Mode Operation, Ch.9.9.1. Switching to Protected Mode, and Ch.17.4. Transferring Control Among Mixed-Size Code Segments) belgesine başvurun.

Muhtelif

Kalanlar destek işlevleri ve değişkenleridir.

/* linux/Makefile hedefleri tarafından oluşturulan makrolar:
 *   include/linux/compile.h ve include/linux/version.h */
kernel_version: .ascii  UTS_RELEASE
                .ascii  " ("
                .ascii  LINUX_COMPILE_BY
                .ascii  "@"
                .ascii  LINUX_COMPILE_HOST
                .ascii  ") "
                .ascii  UTS_VERSION
                .byte   0

///////////////////////////////////////////////////////////////////////////////
default_switch() { cli; outb(0x80, 0x70); } /* Kesmeleri ve NMI'yi iptal et */
bootsect_helper(ES:BX); /* bkz.  Bootsect Yardımcısı */

///////////////////////////////////////////////////////////////////////////////
a20_test()
{
  FS = 0;
  GS = 0xFFFF;
  CX = A20_TEST_LOOPS;                    // i.e. 32
  AX = FS:[0x200];
  do {
a20_test_wait:
    FS:[0x200] = ++AX;
    delay();
  } while (AX==GS:[0x210] && --CX);
  return (AX!=GS[0x210]);
  // ZF==0 (i.e. NZ/NE, a20_test!=0) means test passed
}

///////////////////////////////////////////////////////////////////////////////
// klavye komut kuyruğu boş mu, bak
empty_8042()
{
  int timeout = 100000;

  for (;;) {
empty_8042_loop:
          if (!--timeout) return;
          delay();
          inb(0x64, &AL);                 // 8042 durum portu
          if (AL & 1) {                   // çıktı
            delay();
            inb(0x60, &AL);               // oku
no_output:} else if (!(AL & 2)) return;   // girdi yok
  }
}

///////////////////////////////////////////////////////////////////////////////
// CMOS saatini oku, AL'den saniyeyi döndür, video.S'de kullanılır
gettime()
{
  int1A/AH=02h();
  /* int1A/AH=02h: SAAT - GERÇEK SAAT ZAMANINI AL
    * DH = BCD gösterimli saniye*/
  AL = DH & 0x0F;
  AH = DH >> 4;
  aad;
}

///////////////////////////////////////////////////////////////////////////////
delay() { outb(AL, 0x80); }                     // I/O yaptıktan sonra gerekli

// Tanımlayıcı tablo
gdt:
  .word   0, 0, 0, 0                      # dummy
  .word   0, 0, 0, 0                      # kullanılmadı
  // bölüt 0x10, __KERNEL_CS
  .word   0xFFFF                          # 4Gb - (0x100000*0x1000 = 4Gb)
  .word   0                               # taban adres = 0
  .word   0x9A00                          # kodu oku/çalıştır
  .word   0x00CF                          # tanelilik (granularity) = 4096, 386
                                          #  (sınırın 5. yarım baytı)
  // bölüt 0x18, __KERNEL_DS
  .word   0xFFFF                          # 4Gb - (0x100000*0x1000 = 4Gb)
  .word   0                               # taban adres = 0
  .word   0x9200                          # veri oku/yaz
  .word   0x00CF                          # tanelilik = 4096, 386
                                          #  (sınırın 5. yarım baytı)
idt_48:
  .word   0                               # idt sınırı = 0
  .word   0, 0                            # idt tabanı = 0L
/* [gdt_48] komutla eşleşmesi için 0x0800 (2048) olmalı,
 *   Linux 2.2.22'nin yaptığı gibi. */
gdt_48:
  .word   0x8000                          # gdt sınırı=2048,
                                          #  256 GDT girdisi
  .word   0, 0                            # gdt tabanı (daha sonra doldurulur)

#include "video.S"

// setup.S'nin sonundaki imza:
{
setup_sig1:     .word   SIG1                    // 0xAA55
setup_sig2:     .word   SIG2                    // 0x5A5A
modelist:
}

video.S içindeki video ayar ve algılama kodu:

ASK_VGA = 0xFFFD  // defined in linux/include/asm-i386/boot.h
///////////////////////////////////////////////////////////////////////////////
video()
{
        pushw DS;               // farklı bölütler kullan
        FS = DS;
        DS = ES = CS;
        GS = 0;
        cld;
        basic_detect();         // temel kart türü testi (EGA/VGA/MDA/CGA)
#ifdef CONFIG_VIDEO_SELECT
  if (FS:[0x01FA]!=ASK_VGA) {     // kullanıcı seçimli video kipi
    mode_set();
    if (failed) {
      prtstr("You passed an undefined mode number.\n");
      mode_menu();
    }
  } else {
vid2:
    mode_menu();
  }
vid1:
#ifdef CONFIG_VIDEO_RETAIN
  restore_screen();               // ekran içeriğini geri yükle
#endif /* CONFIG_VIDEO_RETAIN */
#endif /* CONFIG_VIDEO_SELECT */
    mode_params();                  // kip parametrelerini sakla
    popw ds;                        // orjinal DS'yi geri yükle
}

/* YAPILACAKLAR: video() ayrıntıları */

Kaynakça