Home

Published

- 12 min read

UEFI Overview 101

img of UEFI Overview 101

บทนำ

นับตั้งแต่วินาทีที่เรากดปุ่มเปิดเครื่องไปจนถึงตอนที่เข้าสู่ระบบปฏิบัติการ สำหรับผู้ใช้แล้วมันก็เป็นเพียงแค่ช่วงเวลาของการรอคอย แต่สำหรับคอมพิวเตอร์แล้วนี่คือกระบวนการที่ซับซ้อนอย่างมาก ในยุคของ BIOS กระบวนการนี้เกิดขึ้นซ้ำเดิมปีแล้วปีเล่า ในขณะที่ระบบปฏิบัติการได้พัฒนาจากหน้าจอ text น่าเบื่อ ๆ ไปสู่ graphical interface ที่สีสันสดใส แต่ BIOS กลับยังคงทำงานแบบเดิม ๆ โดยหน้าตั้งค่าของ BIOS ก็ยังคงอยู่ในรูปแบบพื้นหลังสีน้ำเงินตัวอักษรสีขาวที่จำเจ

สาเหตุที่ BIOS อยู่ยงคงกระพันได้มาจากสองเหตุผล เหตุผลภายนอกคือ BIOS สามารถตอบสนองความต้องการของตลาดได้ในระดับหนึ่ง ส่วนเหตุผลภายในคือ การออกแบบของ BIOS ทำให้การ upgrade และการขยายความสามารถนั้นทำได้ยากมาก เมื่อ 64-bit CPU ค่อย ๆ เข้ามาแทนที่ 32-bit CPU ตัว BIOS ก็เริ่มไม่สามารถตอบสนองความต้องการของตลาดได้อีกต่อไป นี่จึงเปิดทางให้ UEFI ในฐานะผู้สืบทอดของ BIOS ค่อย ๆ เริ่มเข้ามาแทนที่

1.1 อดีตและปัจจุบันของ BIOS

BIOS ถือกำเนิดขึ้นในปี 1975 บนเครื่องคอมพิวเตอร์ CP/M ในตอนที่มันถูกสร้างขึ้นมา มันถือเป็นเทคโนโลยีที่ล้ำสมัยและเป็นส่วนสำคัญอย่างยิ่งของระบบ เมื่อ IBM PC compatibles เริ่มแพร่หลาย BIOS ก็ค่อย ๆ พัฒนาตามไปด้วย มัน “ครองบัลลังก์” ระบบคอมพิวเตอร์มานานกว่า 20 ปี ในช่วงเวลานี้ประสิทธิภาพของ CPU เพิ่มขึ้นเป็นสองเท่าทุก ๆ 18 เดือน ฮาร์ดแวร์และซอฟต์แวร์ของคอมพิวเตอร์ผ่านการเปลี่ยนแปลงมานับไม่ถ้วน ทั้ง 8-bit CPU และระบบ DOS ที่เคยอยู่คู่กับการกำเนิดของ BIOS ต่างก็เลือนหายไปจากหน้าประวัติศาสตร์ แต่ BIOS กลับยังคงฝังตัวอยู่ในคอมพิวเตอร์อย่างเหนียวแน่น

1.1.1 บทบาทของ BIOS ในระบบคอมพิวเตอร์

BIOS ย่อมาจาก “Basic Input/Output System” มันคือชุดของ program code ที่ถูกเก็บไว้ใน ROM บน motherboard โดย code เหล่านี้ประกอบไปด้วย

  • โปรแกรม Power-On Self-Test (POST) ใช้สำหรับตรวจสอบฮาร์ดแวร์ขณะเปิดเครื่อง
  • โค้ดสำหรับ initialize ระบบ รวมถึงการ initialize อุปกรณ์ฮาร์ดแวร์ การสร้าง BIOS interrupt vector เป็นต้น
  • โค้ด subroutine สำหรับการประมวลผล I/O พื้นฐานของอุปกรณ์ต่อพ่วง
  • โปรแกรมตั้งค่า CMOS

โปรแกรม BIOS ทำงานในโหมด 16-bit real mode ซึ่งช่วง addressing สูงสุดใน real mode คือ 1MB โดยมีช่วง 0x0C0000 ~ 0x0FFFFF ถูกสงวนไว้สำหรับ BIOS หลังจากเปิดเครื่อง CPU จะกระโดดไปที่ 0xFFFF0 เพื่อเริ่มทำงาน โดยทั่วไปตรงนี้จะมีคำสั่ง jump ที่ชี้ไปยังจุด entry point จริงของ BIOS เพื่อเริ่มประมวลผล

สิ่งแรกที่ BIOS code ทำคือ “Power On Self Test” (POST) ซึ่งจะตรวจสอบเป็นหลักว่าอุปกรณ์ฮาร์ดแวร์ทำงานปกติหรือไม่ และการตั้งค่าของอุปกรณ์ตรงกับที่เก็บไว้ใน CMOS หรือไม่ หากพบ hardware error จะมีการส่งสัญญาณเตือนผ่านลำโพง หลังจากผ่าน POST แล้ว มันจะ initialize อุปกรณ์แสดงผลและแสดงข้อมูลการ์ดจอ จากนั้นจึงดำเนินการ initialize อุปกรณ์อื่น ๆ ต่อไป เมื่อ initialize อุปกรณ์เสร็จสิ้น มันจะเริ่มตรวจสอบ CPU และ memory พร้อมแสดงผลการทดสอบ หลังจากผ่านการทดสอบ memory แล้ว ก็จะเริ่มทดสอบอุปกรณ์มาตรฐานต่าง ๆ เช่น hard drive, optical drive, serial port, parallel port เป็นต้น จากนั้นจะตรวจหา plug-and-play device และจัดสรรทรัพยากรอย่าง interrupt number, I/O port และ DMA channel ให้กับอุปกรณ์เหล่านั้น หากการตั้งค่าฮาร์ดแวร์มีการเปลี่ยนแปลง ค่าที่อัปเดตเหล่านี้จะถูกบันทึกลง CMOS

หลังจากนั้นมันจะ boot จากอุปกรณ์ตามลำดับ boot order ที่ตั้งค่าไว้ อ่าน boot code ของ Master Boot Record (MBR) ของอุปกรณ์ boot เข้าสู่ memory ผ่าน BIOS interrupt แล้วจึงส่งมอบการควบคุมให้กับ bootloader และเข้าสู่ระบบปฏิบัติการในที่สุด

1.1.2 ข้อด้อยของ BIOS

เมื่อ CPU และอุปกรณ์ฮาร์ดแวร์อื่น ๆ มีการพัฒนาอย่างต่อเนื่อง BIOS ก็ค่อย ๆ กลายเป็นคอขวด (bottleneck) ในการพัฒนาระบบคอมพิวเตอร์ ซึ่งสะท้อนออกมาในประเด็นต่าง ๆ ดังนี้

  1. ประสิทธิภาพในการพัฒนาต่ำ: โค้ดของ BIOS ส่วนใหญ่พัฒนาด้วยภาษา assembly ทำให้ประสิทธิภาพในการพัฒนาต่ำอย่างเห็นได้ชัด ข้อเสียอีกอย่างของการพัฒนาด้วย assembly คือมันผูกโค้ดเข้ากับอุปกรณ์อย่างแน่นหนา (heavily coupled) ทำให้โค้ดได้รับผลกระทบจากการเปลี่ยนแปลงของฮาร์ดแวร์ได้ง่ายมาก
  2. ประสิทธิภาพการทำงานแย่: บริการ input/output พื้นฐานของ BIOS ต้องทำผ่าน interrupt ซึ่งมี overhead สูง อีกทั้ง BIOS ก็ไม่รองรับการทำงานแบบ asynchronous ทำให้เสียเวลาไปกับการรอคอยเป็นจำนวนมาก
  3. ความสามารถในการขยาย (Scalability) แย่และ upgrade ช้า: โค้ดของ BIOS ใช้ static linking เมื่อต้องการเพิ่มฟังก์ชันฮาร์ดแวร์ จะต้องวาง 16-bit code ไว้ในช่วง 0x0C0000 ~ 0x0DFFFF และตั้งให้เป็น interrupt handler ตามที่ตกลงกันไว้ในขั้นตอน initialize นอกจากนี้ BIOS ยังไม่มีโซลูชันสำหรับการโหลด device driver แบบ dynamic
  4. ความปลอดภัย: ตลอดการทำงานของ BIOS ไม่มีการพิจารณาเรื่องความปลอดภัยสำหรับ executable code เลย
  5. ไม่รองรับการ boot จาก hard drive ที่ใหญ่กว่า 2TB: เนื่องจากข้อจำกัดของวิธี addressing ของ BIOS ซึ่งใช้ address แบบ 32-bit ทำให้ logical block address สูงสุดของ boot sector คือ 2^32 (เมื่อแปลงเป็น byte address จะได้ 2^32 × 512 = 2TB)

1.2 มาทำความรู้จักกับ UEFI

UEFI (Unified Extensible Firmware Interface) เป็นตัวกำหนด interface ระหว่างระบบปฏิบัติการกับ platform firmware มันเป็นมาตรฐานที่เผยแพร่โดย UEFI Forum โดยเป็นเพียง “มาตรฐาน” เท่านั้น ไม่ได้มีการให้ implementation มาด้วย

ส่วน implementation จริง ๆ นั้นจะถูกจัดทำโดยบริษัทอื่น ๆ หรือองค์กร open-source เช่น TianoCore ซึ่งเป็น UEFI implementation แบบ open-source ของ Intel และ SecureCore Tiano ของ Phoenix โดยทั่วไป UEFI implementation สามารถแบ่งออกได้เป็นสองส่วน

  • Platform Initialization (เป็นไปตามมาตรฐาน Platform Initialization ซึ่งเผยแพร่โดย UEFI Forum เช่นกัน)
  • Interface ระหว่าง Firmware กับ Operating System

UEFI มีจุดเริ่มต้นมาจากระบบ Itanium ในช่วงกลางทศวรรษ 1990 เมื่อเทียบกับระบบ 32-bit IA32 ที่นิยมในยุคนั้น Itanium ถือเป็นระบบ 64-bit ที่ใหม่หมดจด และข้อจำกัดของ BIOS ก็กลายเป็นสิ่งที่ยอมรับไม่ได้สำหรับระบบ 64-bit เช่นนี้ (BIOS ก็ค่อย ๆ ออกจากตลาดไปเมื่อระบบ 32-bit ถูกแทนที่ด้วยระบบ 64-bit)

เนื่องจากข้อจำกัดของ BIOS บนระบบ 64-bit ทาง Intel จึงริเริ่มโปรเจกต์ “Intel Boot Initiative” ขึ้นในปี 1998 ซึ่งภายหลังถูกเปลี่ยนชื่อเป็น EFI (Extensible Firmware Interface) ต่อมาในปี 2003 แผนการของ Itanium CPU ของ Intel กลับเจอแรงต้านอย่างรุนแรงจาก x86_64 CPU ของ AMD เมื่อยุคของ x86_64 CPU มาถึง ตลาดก็พิสูจน์ให้เห็นว่าเต็มใจที่จะยอมรับการเปลี่ยนแปลงแบบค่อยเป็นค่อยไปมากกว่า ทำให้ Intel ก็เริ่มออก x86_64 CPU ที่ compatible กับระบบ 32-bit เช่นกัน

แม้ Itanium จะไม่สามารถผูกขาดตลาดได้ตามที่คาดหวัง แต่ EFI ก็ได้แสดงคุณค่าของมันออกมา ในปี 2005 Intel ร่วมกับอีก 11 บริษัทรวมถึง Microsoft, AMD และ Lenovo ได้ก่อตั้ง Unified EFI Forum ขึ้นเพื่อรับผิดชอบในการกำหนดมาตรฐาน EFI ที่เป็นหนึ่งเดียว มาตรฐาน UEFI ฉบับแรก — UEFI 2.0 — ถูกเผยแพร่ในเดือนมกราคม ปี 2006 ส่วนมาตรฐาน UEFI ที่ใหม่ที่สุดในขณะนั้นคือ UEFI 2.4 ซึ่งเผยแพร่ในปี 2013

1.2.1 องค์ประกอบของระบบ UEFI

Interface ที่ UEFI มอบให้กับระบบปฏิบัติการประกอบไปด้วย Boot Services (BS) และ Runtime Services (RT) รวมถึงชุด Protocol อันหลากหลายที่ซ่อนอยู่เบื้องหลัง BS โดย BS และ RT มีอยู่ในรูปแบบของ table (หรือ struct ในภาษา C) ส่วน driver และ service ของ UEFI จะถูกมอบให้กับระบบปฏิบัติการผ่าน BS ในรูปแบบของ Protocol

รูปที่ 1-1 แสดงให้เห็นองค์ประกอบของระบบคอมพิวเตอร์ที่ใช้ EFI

คำแปลข้อความในแผนภาพ:
  • กล่องบนสุด: Operating System
  • กล่องกลาง (บน): EFI OS Loader
  • กล่องกลาง (ล่าง): EFI Boot Services | EFI Runtime Services
  • กล่องล่างสุด: Computer Hardware Devices
  • คำบรรยายภาพ: รูปที่ 1-1 องค์ประกอบของระบบ EFI

ช่วงเวลาตั้งแต่ที่ operating system loader (OS Loader) ถูกโหลดเข้ามา ไปจนถึงตอนที่ OS Loader เรียก ExitBootServices() คือกระบวนการเปลี่ยนผ่านจากสภาพแวดล้อมของ UEFI ไปสู่ระบบปฏิบัติการ ในระหว่างกระบวนการนี้ OS Loader สามารถใช้บริการที่ UEFI มอบให้ผ่าน BS และ RT เพื่อค่อย ๆ โอนการควบคุมทรัพยากรของระบบคอมพิวเตอร์มาอยู่ในมือของตัวเอง กระบวนการนี้เรียกว่า TSL (Transient System Load)

เมื่อ OS Loader เข้าควบคุมทรัพยากรของระบบคอมพิวเตอร์ได้อย่างเต็มที่แล้ว BS ก็ถือว่าเสร็จสิ้นภารกิจ OS Loader จะเรียก ExitBootServices() เพื่อยุติการทำงานของ BS และเรียกคืนทรัพยากรที่มันเคยใช้ หลังจากนั้นระบบคอมพิวเตอร์จะเข้าสู่ช่วง UEFI Runtime

ในช่วง Runtime นั้น จะมีเพียง runtime service เท่านั้นที่ยังคงให้บริการแก่ OS ต่อไป ส่วน BS ได้ถูกทำลายไปจากระบบคอมพิวเตอร์เรียบร้อยแล้ว

ในช่วง TSL ทรัพยากรของระบบจะถูกจัดการโดย BS โดยบริการที่ BS มอบให้มีดังนี้

  1. Event Services: Event เป็นพื้นฐานของการทำงานแบบ asynchronous การทำงานแบบ concurrent ภายในระบบ UEFI จะเกิดขึ้นได้ก็ต่อเมื่อมี event รองรับเท่านั้น
  2. Memory Management: ให้บริการหลักในการจัดสรร (allocate) และคืน (deallocate) หน่วยความจำ รวมถึงการจัดการ system memory mapping
  3. Protocol Management: ให้บริการในการติดตั้ง (install) และถอนการติดตั้ง (uninstall) Protocol รวมถึงบริการในการลงทะเบียน Protocol notification function (ซึ่งจะถูกเรียกเมื่อมีการ install Protocol)
  4. Protocol Usage Services: รวมถึงการเปิด/ปิด Protocol และการค้นหา controller ที่รองรับ Protocol นั้น ๆ ตัวอย่างเช่น หากต้องการอ่าน/เขียน register ของอุปกรณ์ PCI ตัวหนึ่ง คุณสามารถใช้บริการ OpenProtocol เพื่อเปิด PciIo Protocol บนอุปกรณ์นี้ แล้วใช้บริการ PciIo->Io.Read() เพื่ออ่าน register บนอุปกรณ์
  5. Driver Management: รวมถึงบริการ connect สำหรับติดตั้ง driver ลงบน controller และบริการ disconnect สำหรับถอน driver ออกจาก controller เช่น ระหว่าง boot หากเราต้องการการรองรับ network เราสามารถใช้ loadImage เพื่อโหลด driver เข้าสู่ memory แล้วใช้บริการ connect เพื่อติดตั้ง driver ลงบนอุปกรณ์
  6. Image Management: บริการในกลุ่มนี้ครอบคลุมการโหลด (load), ถอน (unload), เริ่ม (start) และออก (exit) จาก UEFI application หรือ driver
  7. ExitBootServices: ใช้สำหรับยุติการทำงานของ boot services

ส่วนบริการที่ RT มอบให้นั้น ประกอบไปด้วยด้านต่าง ๆ ดังต่อไปนี้เป็นหลัก

  1. Time Services: อ่าน/ตั้งค่าเวลาของระบบ และอ่าน/ตั้งค่าเวลาที่ระบบจะตื่นจากโหมด sleep
  2. Read/Write UEFI System Variables: อ่าน/ตั้งค่า system variable เช่น BootOrder ที่ใช้ระบุลำดับของอุปกรณ์ boot การตั้งค่าระบบสามารถบันทึกผ่าน system variable เหล่านี้ได้
  3. Virtual Memory Services: แปลง physical address ให้เป็น virtual address
  4. Other Services: รวมถึง ResetSystem สำหรับ reboot ระบบ และการขอค่า monotonically increasing ตัวถัดไปที่ระบบมอบให้ เป็นต้น

1.2.2 ข้อดีของ UEFI

UEFI สามารถเข้ามาแทนที่ BIOS ได้อย่างรวดเร็วก็เพราะมันมีข้อได้เปรียบหลายประการเหนือ BIOS

(1) ประสิทธิภาพในการพัฒนาของ UEFI การพัฒนา BIOS มักใช้ภาษา assembly โดยโค้ดส่วนใหญ่จะผูกติดกับฮาร์ดแวร์ แต่ใน UEFI โค้ดส่วนใหญ่เขียนด้วยภาษา C และ UEFI application กับ driver สามารถเขียนด้วย C++ ได้ด้วยซ้ำ ด้วยการมี interface ระหว่าง firmware กับ operating system (บริการ BS และ RT) UEFI จึงช่วยซ่อนรายละเอียดฮาร์ดแวร์ระดับล่างจาก OS และ OS loader ทำให้ UEFI application ระดับบนสามารถนำกลับมาใช้ซ้ำ (reuse) ได้อย่างสะดวก

(2) ความสามารถในการขยาย (Extensibility) ของระบบ UEFI ความสามารถในการขยายของระบบ UEFI สะท้อนออกมาในสองด้าน ด้านแรกคือการออกแบบ driver แบบ modular ด้านที่สองคือความ compatible ในการ upgrade ทั้งซอฟต์แวร์และฮาร์ดแวร์

การ initialize ฮาร์ดแวร์ส่วนใหญ่ทำผ่าน UEFI driver แต่ละ driver เป็น module อิสระ ซึ่งสามารถรวมอยู่ใน firmware หรือวางไว้บนอุปกรณ์ และสามารถถูกโหลดแบบ dynamic ขณะ runtime ได้ตามต้องการ ทุก table และทุก Protocol (รวมถึง driver) ใน UEFI ล้วนมี version number ทำให้การ upgrade ระบบแบบไร้รอยต่อเป็นเรื่องง่าย

(3) ประสิทธิภาพการทำงานของระบบ UEFI เมื่อเทียบกับ BIOS แล้ว UEFI มีประสิทธิภาพที่ดีขึ้นอย่างมาก โดยช่วยลดเวลาจากการ boot ไปจนถึงการเข้าสู่ระบบปฏิบัติการลงได้มาก การเพิ่มประสิทธิภาพนี้มาจากด้านต่าง ๆ ดังนี้

  1. UEFI รองรับการทำงานแบบ asynchronous การทำงานแบบ asynchronous ที่อิงกับ event ช่วยเพิ่มอัตราการใช้งาน CPU และลดเวลารอคอยรวม
  2. UEFI เลิกใช้วิธีการที่ใช้ interrupt ในการสั่งงานอุปกรณ์ภายนอกซึ่งกินเวลามาก โดยคงไว้เพียง timer interrupt เท่านั้น การสั่งงานอุปกรณ์ภายนอกจะทำผ่าน “event + asynchronous operation”
  3. วิธีการ traverse อุปกรณ์ที่ scalable ได้ ช่วยให้สามารถ traverse เฉพาะอุปกรณ์ที่จำเป็นต่อการ boot ในระหว่างเริ่มต้นระบบ จึงเร่งความเร็วในการ boot ได้

(4) ความปลอดภัยของระบบ UEFI ความก้าวหน้าที่สำคัญอย่างหนึ่งของ UEFI คือการคำนึงถึงความปลอดภัย เมื่อเปิดใช้งานฟังก์ชัน Secure Boot ของระบบ UEFI จะตรวจสอบ certificate ของ application และ driver ก่อนที่จะ execute มัน และจะ execute เฉพาะ application หรือ driver ที่ certificate น่าเชื่อถือเท่านั้น UEFI application และ driver ใช้รูปแบบ PE/COFF โดย signature ของมันจะถูกวางไว้ใน signature block

1.2.3 กระบวนการ Boot ของระบบ UEFI

การ boot ของระบบ UEFI เป็นไปตามมาตรฐาน UEFI Platform Initialization (PI) ระบบ UEFI ตั้งแต่เปิดเครื่องจนถึงปิดเครื่องสามารถแบ่งออกได้เป็น 7 ช่วง (phase) ดังนี้

SEC (Security) → PEI (Pre-EFI Initialization) → DXE (Driver Execution Environment) → BDS (Boot Device Selection) → TSL (Transient System Load) → RT (Run Time) → AL (After Life)

รูปที่ 1-2 แสดงทั้ง 7 ช่วงของระบบ UEFI ตั้งแต่เปิดเครื่องจนถึงปิดเครื่อง (แบ่งด้วยเส้นแนวตั้งในรูป) สามช่วงแรกคือช่วง initialize ของ UEFI และสภาพแวดล้อม UEFI จะพร้อมใช้งานหลังจากช่วง DXE สิ้นสุดลง ส่วน BDS และ TSL คือช่วงที่ OS loader ทำงานในฐานะ UEFI application หลังจาก OS loader เรียกบริการ ExitBootServices() แล้ว ก็จะเข้าสู่ช่วง RT โดยช่วง RT ครอบคลุมทั้งช่วงท้ายของ OS loader และช่วง runtime ของ OS

เมื่อฮาร์ดแวร์ของระบบหรือระบบปฏิบัติการเกิด error ร้ายแรงจนไม่สามารถทำงานต่อได้ตามปกติ firmware จะพยายามซ่อมแซม error นั้น ซึ่งจุดนี้ระบบจะเข้าสู่ช่วง AL อย่างไรก็ตามทั้ง PI specification และ UEFI specification ต่างก็ไม่ได้นิยามพฤติกรรมของช่วง AL ไว้ เครื่องหมาย ”?” ใช้แสดงว่าพฤติกรรมของมันถูกกำหนดโดยผู้ผลิตระบบ

1. ช่วง SEC

ช่วง SEC (Security Phase) เป็นช่วงแรกของการ initialize platform โดยระบบคอมพิวเตอร์จะเข้าสู่ช่วงนี้ทันทีหลังเปิดเครื่อง

(1) หน้าที่ของช่วง SEC

เมื่อระบบ UEFI เปิดเครื่องหรือ restart มันจะเข้าสู่ช่วง SEC ในเชิงหน้าที่แล้ว มันทำงาน 4 อย่างดังต่อไปนี้

  1. รับและจัดการสัญญาณ startup และ restart ของระบบ: สัญญาณเปิดเครื่อง, สัญญาณ restart ระบบ และสัญญาณ exception ร้ายแรงระหว่างที่ระบบทำงาน
  2. Initialize พื้นที่จัดเก็บข้อมูลชั่วคราว (temporary storage area): เมื่อระบบทำงานอยู่ในช่วง SEC จะมีเพียง CPU และทรัพยากรภายในของ CPU เท่านั้นที่ถูก initialize ส่วนอุปกรณ์ภายนอกและ memory ต่าง ๆ ยังไม่ถูก initialize ดังนั้นระบบจึงต้องการพื้นที่ temporary RAM สำหรับการเข้าถึง code และ data ซึ่งเราเรียกว่า temporary RAM เพื่อแยกออกจาก main memory โดย temporary RAM นี้สามารถอยู่ได้เฉพาะภายใน CPU เท่านั้น temporary RAM ที่นิยมใช้กันมากที่สุดคือ Cache เมื่อ Cache ถูกตั้งค่าให้อยู่ในโหมด no-eviction มันจะสามารถใช้เป็น memory ได้ คือ เมื่อ read hit จะคืนค่า data ที่อยู่ใน Cache และเมื่อ read miss จะไม่ส่ง miss event ไปยัง main memory ส่วนเมื่อ write hit จะเขียน data ลงใน Cache และเมื่อ write miss จะไม่ส่ง miss event ไปยัง main memory เทคนิคนี้เรียกว่า CAR (Cache As Ram)
  3. ทำหน้าที่เป็นรากฐานของ trusted system (root of trust): ในฐานะส่วนแรกที่เข้าควบคุมระบบ ช่วง SEC คือรากฐานของ trusted system ทั้งหมด SEC สามารถถูกระบบไว้วางใจได้ ซึ่งเป็นพื้นฐานสำหรับการไว้วางใจในแต่ละช่วงถัด ๆ ไป โดยปกติแล้วก่อนที่จะโอนการควบคุมไปยัง PEI ตัว SEC สามารถ verify PEI ได้
  4. ส่งต่อ system parameter ไปยังช่วงถัดไป (คือ PEI): งานทั้งหมดในช่วง SEC ล้วนเป็นการเตรียมพร้อมสำหรับช่วง PEI ในที่สุด SEC จะโอนการควบคุมไปยัง PEI และรายงานผลลัพธ์ของช่วงปัจจุบันไปยัง PEI โดยวิธีการรายงานคือการส่งข้อมูลต่อไปนี้เป็น parameter ไปยัง entry function ของ PEI
  • สถานะปัจจุบันของระบบ ซึ่ง PEI สามารถใช้ตัดสินความสมบูรณ์ของระบบได้
  • Address และขนาดของ Boot Firmware Volume
  • Address และขนาดของพื้นที่ temporary RAM
  • Address และขนาดของ stack

(2) ลำดับการทำงานของช่วง SEC

ข้างต้นได้อธิบายหน้าที่ของ SEC ไปแล้ว ทีนี้มาดูลำดับการทำงานของ SEC กันบ้าง ตามที่แสดงในรูปที่ 1-3

รูปที่ 1-3 ลำดับการทำงานของช่วง SEC

โดยใช้การ initialize temporary RAM เป็นเส้นแบ่ง การทำงานของ SEC สามารถแบ่งออกได้เป็นสองส่วนใหญ่ ๆ ส่วนก่อนที่ temporary RAM จะมีผลเรียกว่าช่วง Reset Vector และหลังจากที่ temporary RAM มีผลแล้ว ก็จะมีการเรียก SEC entry function เพื่อเข้าสู่พื้นที่ฟังก์ชันของ SEC

ลำดับการทำงานของ Reset Vector มีดังนี้

  1. เข้าสู่ firmware entry
  2. สลับจาก real mode ไปยัง 32-bit flat mode (inclusive mode)
  3. ระบุตำแหน่ง BFV (Boot Firmware Volume) ภายใน firmware
  4. ระบุตำแหน่ง SEC image ภายใน BFV
  5. หากเป็นระบบ 64-bit ให้สลับจาก 32-bit mode ไปยัง 64-bit mode
  6. เรียก SEC entry function

โค้ดต่อไปนี้อธิบายกระบวนการทำงานตั้งแต่ firmware entry (Reset Vector) ไปจนถึง SEC entry function

   ; file: UefiCpuPkg/ResetVector/Vtf0/Ia16/ResetVectorVtf0.asm
resetVector:
jmp     short EarlyBspInitReal16
   ; file: UefiCpuPkg/ResetVector/Vtf0/Ia16/Init16.asm
EarlyBspInitReal16:
mov     di, 'BP'
jmp     short Main16
   ; file: UefiCpuPkg/ResetVector/Vtf0/Main.asm
Main16:
OneTimeCall EarlyInit16
OneTimeCall TransitionFromReal16To32BitFlat;    ; สลับจาก real mode ไปยัง 32-bit flat mode
OneTimeCall Flat32SearchForBfvBase;             ; ระบุตำแหน่ง BFV ภายใน firmware
   OneTimeCall Flat32SearchForSecEntryPoint;       ; ระบุตำแหน่ง SEC image ภายใน BFV
; esi register เก็บ address ของ SEC entry, ebp register เก็บ address เริ่มต้นของ BFV
%ifdef ARCH_IA32
mov eax, esp
jmp esi;                                        ; กระโดดไปยัง SEC entry
%else
OneTimeCall Transition32FlatTo64Flat;           ; สลับจาก 32-bit mode ไปยัง 64-bit mode
...
jmp rsi;                                         ; กระโดดไปยัง SEC entry
%endif

เมื่อสร้างใหม่เป็น flow จะได้ดังนี้

ในส่วนของ Reset Vector เนื่องจากระบบยังไม่มี RAM จึงไม่สามารถใช้การออกแบบโปรแกรมที่อิงกับ stack ได้ ทำให้ทุกการ function call ต้องถูกจำลองด้วยคำสั่ง jmp โดย OneTimeCall คือ macro ที่ใช้จำลองคำสั่ง call ตัวอย่างเช่น การเรียก macro OneTimeCall EarlyInit16 ตามที่แสดงในรูปที่ 1-4

รูปที่ 1-4 ตัวอย่าง OneTimeCall

หลังจากเข้าสู่พื้นที่ฟังก์ชันของ SEC แล้ว ขั้นแรกมันจะใช้เทคนิค CAR เพื่อ initialize stack, initialize IDT, initialize EFI_SEC_PEI_HAND_OFF, โอนการควบคุมไปยัง PEI และส่ง EFI_SEC_PEI_HAND_OFF ไปยัง PEI

แพลตฟอร์มฮาร์ดแวร์ที่แตกต่างกันจะมี implementation ของ SEC code ที่แตกต่างกัน แต่กระบวนการทำงานโดยรวมจะคล้ายกัน ด้านล่างนี้จะใช้ OVMF เป็นตัวอย่างในการอธิบายกระบวนการทำงานของพื้นที่ฟังก์ชัน SEC

   # file: OvmfPkg/Sec/X64/SecEntry.S
ASM_PFX(_ModuleEntryPoint):
# temporary RAM ถูก initialize แล้ว จึงตั้งค่า address ของ stack ได้ PcdOvmfSecPeiTempRamBase และ PcdOvmfSecPeiTempRamSize
#   ถูกนิยามไว้ใน OvmfPkgIa32X64.fdf
# โดยมีค่าเป็น 0x010000 และ 0x008000 ตามลำดับ
.set SEC_TOP_OF_STACK, FixedPcdGet32 (PcdOvmfSecPeiTempRamBase) +
FixedPcdGet32 (PcdOvmfSecPeiTempRamSize)
movq $SEC_TOP_OF_STACK, %rsp
movq %rbp, %rcx        #rcx: address เริ่มต้นของ BFV, rbp เป็น parameter ที่ส่งเข้ามา
movq %rsp, %rdx        #rdx: address เริ่มต้นของ stack
subq $0x20, %rsp
call ASM_PFX(SecCoreStartupWithStack)   # ตอนนี้ stack พร้อมใช้งานแล้ว จึงใช้คำสั่ง call ได้
   # file: OvmfPkg/Sec/X64/SecEntry.S
VOID EFI_API SecCoreStartupWithStack(
IN EFI_FIRMWARE_VOLUME_HEADER *BootFv,
IN VOID *TopOfCurrentStack
){
  EFI_SEC_PEI_HAND_OFF SecCoreData;
  // initialize floating-point register
  // initialize IDT
  // initialize SecCoreData โดยกำหนด address ของ temporary RAM, stack และ BFV ให้กับ SecCoreData
  SecStartupPhase2(&SecCoreData);
}
   # file: OvmfPkg/Sec/X64/SecEntry.S
VOID EFI_API SecStartupPhase2(IN VOID *Context)
{
  EFI_SEC_PEI_HAND_OFF        SecCoreData;
  EFI_PEI_CORE_ENTRY_POINT    PeiCoreEntryPoint;
  SecCoreData = (EFI_SEC_PEI_HAND_OFF *) Context;
  // ค้นหา entry function ของ PEI จาก BFV
  FindAndReportEntryPoints (&SecCoreData->BootFirmwareVolumeBase,
  &PeiCoreEntryPoint);
  // เรียก entry function ของ PEI, SecCoreData บรรจุ address และขนาดของ temporary RAM, stack และ BFV,
  // mPrivateDispatchTable บรรจุ EFI_PEI_TEMPORARY_RAM_SUPPORT_PPI
  (*PeiCoreEntryPoint) (SecCoreData,
  (EFI_PEI_PPI_DESCRIPTOR *) &mPrivateDispatchTable);
}

เมื่อสร้างใหม่เป็น flow จะได้ดังนี้

2. ช่วง PEI

ทรัพยากรในช่วง PEI (Pre-EFI Initialization) ยังคงมีจำกัดมาก โดย memory จะถูก initialize ในช่วงท้าย ๆ ของ PEI เท่านั้น หน้าที่หลักของมันคือการเตรียมสภาพแวดล้อมการทำงานให้กับ DXE, รวบรวมข้อมูลที่ต้องส่งต่อไปยัง DXE เข้าเป็น HOB (Handoff Block) list และสุดท้ายโอนการควบคุมไปยัง DXE ลำดับการทำงานของ PEI แสดงในรูปที่ 1-5

ในเชิงหน้าที่ PEI สามารถแบ่งออกได้เป็นสองส่วนดังนี้

  • PEI Foundation: รับผิดชอบบริการและกระบวนการพื้นฐานของ PEI
  • PEIM (PEI Module) Dispatcher: หน้าที่หลักคือการค้นหา PEIM ทั้งหมดในระบบและ execute มันตามลำดับโดยอิงจาก dependency ระหว่าง PEIM การ initialize ระบบในช่วง PEI ส่วนใหญ่ทำสำเร็จได้ด้วย PEIM

แต่ละ PEIM เป็น module อิสระ โดยนิยาม type ของ entry point function ของ module ไว้ดังนี้

   typedef EFI_STATUS (EFIAPI *EFI_PEIM_ENTRY_POINT2) (
    IN EFI_PEI_FILE_HANDLE FileHandle, IN CONST EFI_PEI_SERVICES **PeiServices
);

ผ่าน PeiServices ตัว PEIM สามารถใช้ system service ที่ช่วง PEI มอบให้ได้ และผ่าน system service เหล่านี้ PEIM ก็สามารถเข้าถึง PEI Foundation ได้ ส่วนการสื่อสารระหว่าง PEIM จะทำผ่าน PPI (PEIM-to-PEIM Interfaces)

รูปที่ 1-5 ลำดับการทำงานของช่วง PEI

PPI มีลักษณะคล้ายกับ Protocol ในช่วง DXE โดยแต่ละ PPI เป็น struct ที่บรรจุ function pointer และ variable ตัวอย่างเช่น

   struct _EFI_PEI_DECOMPRESS_PPI {
  EFI_PEI_DECOMPRESS_DECOMPRESS Decompress;
}
extern EFI_GUID    gEfiPeiDecompressPpiGuid;

ทุก PPI จะมี GUID เป็นของตัวเอง โดยอิงจาก GUID เราสามารถใช้บริการ LocatePpi ของ PeiServices เพื่อขอ instance ของ PPI ที่ตรงกับ GUID นั้นได้

คุณลักษณะสำคัญอย่างหนึ่งของ UEFI คือการออกแบบแบบ modular หลังจาก module ถูกโหลดเข้าสู่ memory มันจะสร้าง Image ขึ้นมา โดย entry function ของ Image คือ _ModuleEntryPoint ตัว PEI เองก็เป็น module เช่นกัน entry function ของ PEI Image คือ _ModuleEntryPoint ซึ่งอยู่ใน MdePkg/Library/PeimEntryPoint/PeimEntryPoint.c สุดท้าย _ModuleEntryPoint จะเรียก entry function ของ PEI module คือ PeiCore ซึ่งอยู่ใน MdeModulePkg/Core/Pei/PeiMain/PeiMain.c

หลังจากเข้าสู่ PeiCore แล้ว ขั้นแรกมันจะตั้งค่า Pei Core Services โดยอิงจากข้อมูลที่ส่งเข้ามาจากช่วง SEC จากนั้นจึงเรียก PeiDispatcher เพื่อ execute PEIM ในระบบ หลังจาก memory ถูก initialize ระบบจะทำการ stack switch และเข้าสู่ PeiCore อีกครั้ง หลังจากเข้า PeiCore ใหม่ memory ที่ใช้จะเป็น memory ปกติที่เราคุ้นเคย เมื่อ PEIM ทั้งหมด execute เสร็จสิ้น มันจะเรียกบริการ LocatePpi ของ PeiServices เพื่อขอ DXE IPL PPI และเรียกบริการ Entry ของ DXE IPL PPI ซึ่งบริการ Entry นี้แท้จริงแล้วก็คือ DxeLoadCore ที่จะค้นหา entry function ของ DXE Image, execute มัน และส่ง HOB list ต่อไปยัง DXE

3. ช่วง DXE

ช่วง DXE (Driver Execution Environment) ทำงาน initialize ระบบเป็นส่วนใหญ่ เมื่อเข้าสู่ช่วงนี้ memory พร้อมใช้งานได้อย่างเต็มที่แล้ว จึงสามารถทำงานที่ซับซ้อนได้เป็นจำนวนมาก ในแง่ของการออกแบบโปรแกรม ช่วง DXE มีความคล้ายกับช่วง PEI ลำดับการทำงานของมันแสดงในรูปที่ 1-6

รูปที่ 1-6 ลำดับการทำงานของช่วง DXE

เช่นเดียวกับ PEI ในเชิงหน้าที่ DXE สามารถแบ่งออกได้เป็นสองส่วนดังนี้

  • DXE kernel: รับผิดชอบบริการพื้นฐานและลำดับการทำงานของ DXE
  • DXE dispatcher: รับผิดชอบการ dispatch และ execute DXE driver รวมถึงการ initialize อุปกรณ์ของระบบ

บริการพื้นฐานที่ DXE มอบให้ ได้แก่ System Table, Boot Services และ Runtime Services

แต่ละ DXE driver เป็น module อิสระ โดยนิยาม type ของ entry function ของ module ไว้ดังนี้

   typedef EFI_STATUS(EFIAPI *EFI_IMAGE_ENTRY_POINT)(
  IN  EFI_HANDLE ImageHandle,
  IN  EFI_SYSTEM_TABLE *SystemTable
);

DXE driver สื่อสารกันผ่าน Protocol โดย Protocol เป็น struct พิเศษ และแต่ละ Protocol จะตรงกับ GUID หนึ่งตัว ด้วยการใช้ OpenProtocol ของ BootServices ของระบบ และอิงจาก GUID มันจะเปิด Protocol ที่ตรงกัน แล้วจึงใช้บริการที่ Protocol นั้นมอบให้

หลังจาก driver ทั้งหมด execute เสร็จสิ้น ระบบก็จะ initialize เสร็จสมบูรณ์ DXE จะระบุตำแหน่ง BDS ผ่าน EFI_BDS_ARCH_PROTOCOL และเรียก entry function ของ BDS เพื่อเข้าสู่ช่วง BDS โดยแท้จริงแล้ว BDS ก็คือ application ในช่วง DXE ชนิดพิเศษนั่นเอง

4. ช่วง BDS

หน้าที่หลักของ BDS (Boot Device Selection) คือการ execute boot policy โดยมีหน้าที่หลักดังนี้

  • Initialize console device
  • โหลด device driver ที่จำเป็น
  • โหลดและ execute boot option ตามการตั้งค่าของระบบ

หากการโหลด boot option ล้มเหลว ระบบจะ re-run DXE dispatcher เพื่อโหลด driver เพิ่มเติม แล้วลองโหลด boot option ใหม่อีกครั้ง

BDS policy ถูกตั้งค่าผ่าน global NVRAM variable โดย variable เหล่านี้สามารถอ่านได้ผ่าน runtime service GetVariable() และตั้งค่าผ่าน SetVariable() ตัวอย่างเช่น variable BootOrder ที่นิยามลำดับการ boot และ variable Boot#### ที่นิยาม boot option แต่ละตัว (โดย #### คืออักขระ hexadecimal ตัวพิมพ์ใหญ่ 4 ตัว)

หลังจากผู้ใช้เลือก boot option (หรือระบบเข้าสู่ boot option ค่าเริ่มต้น) ตัว OS Loader จะเริ่มทำงาน และระบบจะเข้าสู่ช่วง TSL

5. ช่วง TSL

TSL (Transient System Load) คือช่วงแรกที่ operating system loader (OS Loader) execute ในช่วงนี้ OS Loader ทำงานในฐานะ UEFI application และทรัพยากรของระบบยังคงถูกควบคุมโดย UEFI kernel หลังจากบริการ ExitBootServices() ของ Boot Services ถูกเรียก ระบบก็จะเข้าสู่ช่วง Run Time

เหตุผลที่ช่วง TSL ถูกเรียกว่าระบบชั่วคราว (transient system) ก็เพราะวัตถุประสงค์ของการมีอยู่ของมันคือการเตรียมสภาพแวดล้อมการทำงานให้กับ operating system loader ถึงแม้จะเป็นระบบชั่วคราว แต่ความสามารถของมันก็ค่อนข้างทรงพลังแล้ว และมีลักษณะเค้าโครงของระบบปฏิบัติการอยู่แล้ว โดย UEFI Shell คือ human-machine interface ของระบบชั่วคราวนี้ ในสถานการณ์ปกติ ระบบจะไม่เข้าสู่ UEFI Shell แต่จะ execute operating system loader โดยตรง มันจะเข้าสู่ UEFI Shell ก็ต่อเมื่อมีการแทรกแซงจากผู้ใช้ หรือเมื่อ operating system loader เจอ error ร้ายแรงเท่านั้น

6. ช่วง RT

หลังจากระบบเข้าสู่ช่วง RT (Run Time) การควบคุมระบบจะถูกโอนจาก UEFI kernel ไปยัง OS Loader ทรัพยากรต่าง ๆ ที่ UEFI เคยใช้จะถูกเรียกคืนโดย OS Loader โดยจะเหลือเพียง UEFI runtime service เท่านั้นที่ถูกสงวนไว้ให้ OS Loader และ OS ใช้งาน เมื่อ OS Loader ทำงานต่อไป สุดท้าย OS ก็จะเข้าควบคุมระบบได้

7. ช่วง AL

ในช่วง RT หากระบบ (ทั้งฮาร์ดแวร์หรือซอฟต์แวร์) เจอ error ระดับหายนะ (catastrophic error) ตัว system firmware จะต้องจัดเตรียมกลไกการจัดการ error และการกู้คืนจากภัยพิบัติ (disaster recovery) กลไกเหล่านี้ทำงานอยู่ในช่วง AL (After Life) ทั้งมาตรฐาน UEFI และ UEFI PI ต่างก็ไม่ได้นิยามพฤติกรรมหรือข้อกำหนดของช่วงนี้ไว้

1.3 สรุปท้ายบท

เมื่อเทียบกับ BIOS แล้ว UEFI มีความสามารถในการเขียนโปรแกรม (programmability) ที่ดีกว่า, ความสามารถในการขยาย (extensibility) ที่ทรงพลัง, ความปลอดภัยที่ยอดเยี่ยม และการออกแบบของมันยังสามารถปรับตัวเข้ากับแพลตฟอร์ม 64-bit ได้ดีกว่าด้วย ข้อได้เปรียบเหล่านี้ทำให้ UEFI สามารถเข้ามาแทนที่ BIOS ได้อย่างรวดเร็ว

(ครอบคลุม: https://github.com/tianocore/tianocore.github.io/wiki/PI-Boot-Flow)

UEFI นิยาม interface ระหว่างระบบปฏิบัติการกับ platform firmware โดย interface ของ UEFI สามารถแบ่งออกได้เป็นสองส่วนดังนี้

  1. Boot Services: เป้าหมายหลักของการให้บริการของ Boot Services คือ operating system loader และ UEFI application กับ UEFI driver อื่น ๆ โดย operating system loader จะค่อย ๆ เข้าควบคุมทรัพยากรทั้งหมดของระบบคอมพิวเตอร์ผ่าน Boot Services หลังจาก loader ควบคุมทรัพยากรฮาร์ดแวร์และซอฟต์แวร์ของคอมพิวเตอร์ได้อย่างสมบูรณ์แล้ว ระบบจะยุติ Boot Services และเข้าสู่ Run Time โดย Boot Services ประกอบไปด้วย event services, memory management, Protocol management, Protocol-usage services, driver management, Image management และ ExitBootServices service เป็นหลัก
  2. Runtime Services: เป้าหมายหลักของการให้บริการของ Runtime Services คือ operating system, operating system loader รวมถึง UEFI application และ UEFI driver โดย Runtime Services ประกอบไปด้วย time services, บริการอ่าน/เขียน UEFI system variable, virtual memory services และ system-restart service เป็นหลัก

ระบบคอมพิวเตอร์ที่ใช้ UEFI ตั้งแต่เปิดเครื่องจนถึงปิดเครื่องสามารถแบ่งออกได้เป็น 7 ช่วง บทความนี้ได้แนะนำหน้าที่และลำดับการทำงานของแต่ละช่วงทั้ง 7 ช่วงไปแล้วอย่างคร่าว ๆ โดย Boot Services และ Runtime Services จะถูกสร้างขึ้นหลังจากระบบเข้าสู่ช่วง DXE เท่านั้น และ UEFI application กับ UEFI driver ที่บทความนี้เน้นก็ทำงานอยู่ในช่วงนี้

Happy Hacking :)