ไคลเอนต์ IRC ที่ฝังอยู่ในเมนบอร์ด
(axleos.com)- UEFIRC คือกราฟิกไคลเอนต์ IRC ที่ทำงานใน สภาพแวดล้อมก่อนบูตของ UEFI ภายในเฟิร์มแวร์ของเมนบอร์ดก่อนที่ระบบปฏิบัติการจะเริ่มทำงาน แสดงให้เห็นว่าแม้ในสภาพแวดล้อมสำหรับบูตโหลดเดอร์ก็ยังสามารถสร้าง UI และความสามารถด้านเครือข่ายที่ใกล้เคียงแอปทั่วไปได้
- การพัฒนานี้อาศัย ไดรเวอร์ NIC และสแตก TCP ที่ UEFI มีไว้สำหรับการบูตผ่านเครือข่าย และมี network backend
vmnetสำหรับ QEMU ที่ทำให้การพัฒนาเป็นไปได้ - จุดที่ยากที่สุดคือการจัดการโปรโตคอล TCP ของ UEFI ใน Rust โดยมีทั้งสถานะส่วนกลาง, callback แบบ reentrant, scatter-gather buffer และความสัมพันธ์ซับซ้อนระหว่าง event, token, handle และ protocol
- GUI เป็นการนำ GUI toolkit ของ axle ที่เขียนด้วย Rust พร้อม TrueType renderer มาปรับให้ทำงานบน UEFI และยังมีการ ปรับปรุง libgui เพื่อรองรับอินพุตเมาส์, แถบเลื่อน และการเรนเดอร์ข้อความใน scroll view
- ผลลัพธ์สุดท้ายใกล้เคียงกับโปรเจกต์มุกที่ทำอย่างประณีตมากกว่าจะเป็นไคลเอนต์ IRC สำหรับใช้งานจริง แต่ก็กลายเป็นเครื่องมือสำหรับบ่นเรื่อง UEFI TCP/IP stack ผ่าน IRC จากภายใน UEFI ได้
UEFIRC ทำอะไรได้บ้าง
- UEFIRC คือ กราฟิกไคลเอนต์ IRC ที่รันบน UEFI
- เขียนด้วย Rust และใช้ GUI toolkit กับ TrueType renderer ที่สร้างขึ้นสำหรับ axle user space
- สามารถเชื่อมต่อกับ IRC server เพื่อแชตและอ่านข้อความได้
- ในการพัฒนาใช้
vmnetnetwork backend สำหรับ QEMU
UEFI ในฐานะเวทีการทำงาน
- บูตโหลดเดอร์ของระบบปฏิบัติการถูกโหลดขึ้นมาด้วยความช่วยเหลือจากเฟิร์มแวร์ที่เก็บอยู่ใน ROM ของเมนบอร์ด
- ในอดีต BIOS มีข้อจำกัดหลายอย่าง จึงมีการสร้างมาตรฐาน UEFI ขึ้นมาเพื่อทดแทน
- BIOS กำหนดให้บูตโหลดเดอร์ต้องเริ่มในโหมด 16-bit
- ยังมีข้อกำหนดให้โหลดเดอร์ขั้นแรกต้องมีขนาดไม่เกิน 512 ไบต์
- UEFI ทำให้บูตโหลดเดอร์เริ่มต้นในสภาพแวดล้อม 64-bit ตั้งแต่แรก พร้อมมี API สำหรับสลับความละเอียดจอแบบ VESA, จัดสรรหน่วยความจำ และเข้าถึง EFI filesystem
- แม้จะก้าวหน้ากว่า BIOS มาก แต่ก็มีเสียงวิจารณ์ว่าออกแบบมาซับซ้อนเกินไป
นำความสามารถการบูตผ่านเครือข่ายมาใช้กับ IRC
- บูตโหลดเดอร์บางตัวสามารถโหลด ระบบปฏิบัติการผ่านเครือข่าย แทนการใช้ local block device ได้
- การรองรับกรณีใช้งานนี้ทำให้เฟิร์มแวร์ UEFI ต้องมี network stack อยู่ภายใน
- ไดรเวอร์ NIC
- implementation ของ TCP
- API ให้แอปพลิเคชันที่ทำงานในสภาพแวดล้อมก่อนบูตเข้าถึงสแตกนี้ได้
- และเพราะบูตโหลดเดอร์ไม่ได้จำเป็นต้องใช้เพื่อโหลดระบบปฏิบัติการเท่านั้น จึงสามารถรันไคลเอนต์ IRC ในสภาพแวดล้อมเดียวกันได้ด้วย
ความยากของการจัดการ UEFI TCP ใน Rust
- ส่วนที่ยากที่สุดของโปรเจกต์คือการทำ UEFI TCP protocol client ใน Rust
- โปรโตคอล TCP ของ UEFI ต้องการอายุข้อมูลและรูปแบบปฏิสัมพันธ์ที่อธิบายกับ Rust ได้ยาก
- สถานะส่วนกลาง
- callback แบบ reentrant
- scatter-gather buffer
- event, token, handle, protocol
- มีการทดสอบโค้ด Rust อยู่หลายวันเพื่อกำจัดทั้ง memory leak และปัญหา use-after-free ใน TCP receive buffer
ความสับสนของ NOTIFY_SIGNAL และ NOTIFY_WAIT
- API event ของ UEFI เป็นสิ่งที่ดูจากชื่อแล้วคาดเดาพฤติกรรมได้ยาก
- หากกำหนด
NOTIFY_SIGNALcallback จะถูกเรียกเมื่อเกิด event และการใช้wait()จะกลายเป็นข้อผิดพลาด - หากกำหนด
NOTIFY_WAITแล้วเรียกwait()UEFI อาจเรียก callback หลายครั้งก่อน event จะเกิดขึ้น และเมื่อ event เกิดwait()จึงจะถูกปลดล็อก - ทั้งสองโหมดทำให้ callback ตัวเดียวกันมี ความหมายที่ต่างกันโดยสิ้นเชิง
NOTIFY_SIGNAL: event เกิดขึ้นแล้ว ถึงเวลาทำงานถัดไปNOTIFY_WAIT: event ยังไม่เกิด ถึงเวลาช่วยผลักดันให้กระบวนการเดินหน้าต่อ
- สุดท้ายมีการใช้ลูป
NOTIFY_WAITร่วมกับ timer timeout สั้น ๆ เพื่อทำ asynchronous buffering ของข้อมูลแพ็กเก็ตรับเข้า
การรองรับเมาส์และเคอร์เซอร์
- เมาส์ไม่ใช่สิ่งจำเป็นสำหรับไคลเอนต์ IRC แต่ช่วยให้แอปรู้สึกโต้ตอบได้มากขึ้น
- ใช้ Simple Pointer Protocol ของ UEFI เพื่ออ่านการเคลื่อนที่และการกดปุ่มเมาส์ แล้วส่งข้อมูลตำแหน่งเคอร์เซอร์กลับไปยัง GUI
- Simple Pointer Protocol ไม่รองรับ scroll wheel
- ใน UEFIRC จึงต้องใช้ปุ่มลูกศรหรือใช้เคอร์เซอร์ลากแถบเลื่อนแทน
- เนื่องจากเฟิร์มแวร์ UEFI มาตรฐานของ OVMF ไม่สามารถรับ event จากเมาส์ได้ จึงต้อง build เฟิร์มแวร์ UEFI แบบคัสตอมที่รวมไดรเวอร์และโปรโตคอลที่จำเป็น เช่น
UsbMouseDxe - เพื่อให้ทดลอง UEFIRC บน QEMU ได้ จึงมีการ อัปโหลดเฟิร์มแวร์ UEFI ดังกล่าวไว้ในหน้า release ด้วย
การสเกลการเคลื่อนที่ของเมาส์
- ไดรเวอร์เมาส์รายงาน การเปลี่ยนแปลงตำแหน่ง ไม่ใช่ตำแหน่งแบบสัมบูรณ์
- ถ้าเพียงนำ
delta_x,delta_yมาบวกตรง ๆ ด้วยการสเกลแบบเชิงเส้น จะให้ความรู้สึกหน่วง - ระบบปฏิบัติการมักใช้การสเกลที่ช่วยให้ทั้งการเคลื่อนที่เร็วและการปรับละเอียดทำได้พร้อมกัน
- ในตัวอย่างการใช้งาน มีการนำผลรวมค่าสัมบูรณ์ของระยะเคลื่อนที่ไปคูณกับค่าที่ได้จาก
log2()เพื่อเพิ่มระยะการเลื่อนของเคอร์เซอร์ - เคอร์เซอร์ที่เคลื่อนที่แบบเชิงเส้นล้วน ๆ มักทำให้ทั้งสภาพแวดล้อมดูช้าและตอบสนองไม่ดี
การโมเดลข้อความ IRC
- การโมเดลข้อความ IRC ค่อนข้างง่ายและทำได้อย่างสบาย
- IRC ใช้ ฟอร์แมตบรรทัดแบบข้อความล้วน จึงแยกวิเคราะห์ได้ง่าย
- แต่การขยายต่อเนื่องตลอดหลายสิบปีก็ทำให้ยังมีภาระจากสิ่งที่ถูกทำให้เป็นมาตรฐานเพียงบางส่วน
การใช้ libgui บน UEFI
- GUI toolkit ของ axle ที่เขียนด้วย Rust ถูกเตรียมไว้พอสมควรแล้วสำหรับการใช้งานนอกบริบทของ axle จึงไม่ยากมากที่จะนำมารันบน UEFI
- งานหลักคือการสร้าง implementation ของ
AwmWindowที่ใช้ได้ภายใน UEFI - จากนั้นก็สามารถใช้ความสามารถหลายอย่างของ libgui ได้ตามเดิม
- การจัดการ event
- การเรนเดอร์ฟอนต์
- การ compositing เลเยอร์
- การตกแต่ง view
- คอมโพเนนต์ที่ซับซ้อนอย่าง scroll view
แถบเลื่อนและการเรนเดอร์ข้อความใน scroll view
- libgui แบบ C ของ axle มีความสามารถเรื่อง scrollbar อยู่แล้ว แต่เวอร์ชัน Rust ยังขาดบางส่วน
- เนื่องจากปฏิสัมพันธ์หลักของ UEFIRC เกิดขึ้นใน scroll view ที่เต็มไปด้วยข้อความ จึงมีการนำ ความสามารถของ scrollbar กลับมา implement ใน Rust libgui
- scroll view มีต้นทุนการเรนเดอร์พิกเซลสูงกว่า view ขนาดคงที่
- view ขนาดคงที่คิดง่าย ๆ เป็น RGB buffer ขนาด
width * height - ส่วน scroll view ต้องจัดการกับแคนวาสที่ขยายได้ไม่สิ้นสุด
- view ขนาดคงที่คิดง่าย ๆ เป็น RGB buffer ขนาด
- GUI toolkit Rust ของ axle จัดการ scroll view แบบ tile-based
- แต่ละ tile คือบัฟเฟอร์พิกเซลสี่เหลี่ยมจัตุรัสกว้างหลายร้อยพิกเซล
- จะจัดสรรเฉพาะ tile ที่จำเป็นสำหรับบริเวณที่มีการเรนเดอร์เนื้อหาจริง
- จากนั้นคำนวณ tile ที่มองเห็นและนำมาต่อกันเป็นภาพสุดท้าย
- หาก TrueType renderer เรียก
putpixel()สำหรับทุกพิกเซลของ glyph, scroll view จะไม่รู้ล่วงหน้าถึงพื้นที่เรนเดอร์ทั้งหมด ทำให้ไม่มีประสิทธิภาพ - เพื่อแก้ปัญหานี้ จึงเพิ่ม polygon stack เข้าไปในหน่วยการวาดพื้นฐานอย่างเส้น, วงกลม และสี่เหลี่ยม
- ทำให้ scroll view รู้ล่วงหน้าได้ว่ากำลังวาด polygon ขนาดใหญ่ และสามารถจัดสรร tile ที่จำเป็นไว้ก่อน
- แม้จะไม่ชอบนักที่ต้องใช้การเติม arbitrary polygon เป็น primitive พื้นฐาน แต่ในทางปฏิบัติก็ทำงานได้ดี
libgui ที่ถูกปรับปรุงระหว่างสร้าง UEFIRC
- ทุกครั้งที่สร้างกราฟิกแอปใหม่ มักจะเจอข้อจำกัดและความไม่สะดวกเล็ก ๆ ของ GUI toolkit, IPC, อินเทอร์เฟซไดรเวอร์ และความสามารถของเคอร์เนล
- ระหว่างการพัฒนา UEFIRC จึงมีการแก้ไข libgui หลายจุด
- เพิ่ม event สำหรับการปล่อยคลิกซ้าย
- รองรับการเปลี่ยนขนาดแบบไดนามิกของ Label
- ให้ Label เรนเดอร์ฟอนต์ TrueType แทน bitmap font เดิม
- ให้ Button เรนเดอร์ฟอนต์ TrueType แทน bitmap font เดิม
- ให้ TrueType renderer ให้ข้อมูล layout เพิ่มขึ้นระหว่างเรนเดอร์ glyph
- ให้ TrueType parser โมเดลและเปิดเผย font metric ได้มากขึ้น
- แก้บั๊กเรื่อง layout และระยะห่างเล็ก ๆ ใน TrueType renderer
- ให้ TextInputView รองรับ callback คีย์อินพุตที่ผู้ใช้กำหนดเอง
ผลลัพธ์ที่ไม่จำเป็นอย่างสิ้นเชิง
- ตัวไคลเอนต์ IRC เองเป็นโปรเจกต์มุกที่ทำอย่างประณีต จึงไม่ได้มีประโยชน์เชิงใช้งานจริงมากนัก
- แต่เมื่อรู้สึกหงุดหงิดกับ UEFI TCP/IP stack ก็สามารถใช้มันเป็นเครื่องมือระบายความไม่พอใจได้
- และท้ายที่สุดก็สามารถเข้าไปทักทายในช่อง IRC
#edk2สำหรับนักพัฒนา UEFI ภายใน UEFI ได้เลย
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
ผมทำไคลเอนต์ IRC แบบกราฟิกที่รันได้เฉพาะใน สภาพแวดล้อมก่อนบูตของ UEFI เล่น ๆ และยังใส่ฟีเจอร์เกินจำเป็นอย่างฟอนต์ TrueType, เคอร์เซอร์ และองค์ประกอบตกแต่ง GUI เข้าไปด้วย
เดิมทีเป็นโปรเจกต์ที่อยากทำอะไรเร็วและเบา ๆ หลังจากเหนื่อยกับการทำตัวรับสัญญาณ GPS ตั้งแต่ต้น แต่ก็เหมือนทุกครั้ง มันใช้เวลานานกว่าที่คิดไว้มาก
ผมใช้เวลากับภาพประกอบในบทความที่แสดงวิธีโมเดล scroll view แล้วเรนเดอร์ลงใน viewport แบบคงที่อยู่ไม่น้อยเหมือนกัน หวังว่าจะดูแล้วสนุก
ตอนแรกคิดว่า “ลองยัดสิ่งที่ไม่ควรอยู่ใน UEFI เข้าไปกันเถอะ” แล้วนึกถึงไคลเอนต์ Twitter แต่มีคนทำไว้ดีแล้วด้วยโปรโตคอล HTTP ของ UEFI เลยตัดสินใจเลี่ยง HTTP
ดังนั้นจึงเลือก IRC ซึ่งทำงานบน TCP และยังให้ความรู้สึกแบบโซเชียลมีเดียที่ไม่เข้ากับสภาพแวดล้อมก่อนบูตเลย
แน่นอนว่าพูดเล่นนะ ก็ในระดับหนึ่ง
ผมเป็นสายมินิมัลลิสต์จนไม่ต้องการ GUI หรือเมาส์ด้วยซ้ำ และ UEFI เองก็ดูเหมือนจะมีมากกว่าที่ผมจำเป็นต้องใช้แล้ว
ไคลเอนต์ Twitter ที่พูดถึงอยู่ที่นี่: https://github.com/arata-nvm/mitnal
ดูเหมือนอาจเป็นวิธีที่ค่อนข้างดี และอาจอนุญาตได้อย่างปลอดภัย เพื่อกู้คืนระบบระยะไกลโดยอัตโนมัติเมื่อการติดตั้งพังจนบูตตามปกติไม่ได้
ดีมาก ๆ และยังแสดงให้เห็นชัดว่าใต้ระบบที่คนส่วนใหญ่คิดถึงนั้น มีซอฟต์แวร์ที่ซับซ้อนและทรงพลังกว่าที่คิดซ่อนอยู่
คนมักเข้าใจผิดว่าระบบปฏิบัติการคือ “ชั้นล่างสุด” ของ software stack แต่จริง ๆ แล้วยังมี โค้ดลักษณะเฟิร์มแวร์ ที่เป็นเจ้าของระบบอย่างแท้จริงอยู่
บางครั้งมันทำงานเสร็จแล้วก็หายไป และบางครั้งก็อยู่ตลอดเวลาที่ระบบเปิดอยู่ในสภาพที่แม้แต่ระบบปฏิบัติการยังรู้สึกเหมือนมองไม่เห็นมัน
มีทัศนคติว่า “ก็แค่โค้ดระดับต่ำสำหรับขับอุปกรณ์เท่านั้น ไม่มีอะไรเลวร้ายเกิดขึ้นจากตรงนั้นหรอก” แต่ถ้าถึงขั้นใส่ไคลเอนต์ IRC ลงไปใต้ชั้นนั้นได้ เรื่องประสงค์ร้ายอื่น ๆ ก็จินตนาการได้ไม่ยาก
ถามว่า “ทำไม?” นี่ “ทำไม” เป็นคำถามอะไรกันแน่? ผมมา HN ก็เพื่อดูจิตวิญญาณแบบนี้แหละ
“ความตระหนักที่น่ากลัวที่สุดมาเยือนผม สิ่งที่ผมทำไปไม่มีเหตุผลใด ๆ เลย ผมรู้ว่าทำไมผมถึงทำ ก็แค่เพราะคิดว่าน่าจะสนุก แต่พวกเขาจะถามว่า ‘ทำบ้าอะไรแบบนี้ไปทำไม’ และถ้าไม่มีเหตุผลที่ฟังขึ้นพอ ก็ดูเหมือนพวกเขาจะจับผมยัดเข้าโรงพยาบาลจิตเวช” — Boyd Rice
ไม่ต้องดูถูกตัวเองก็ได้ ที่นี่มีโปรเจกต์ ไคลเอนต์สั่งการและควบคุมบอตเน็ต อยู่
UI นี่ตลกอยู่หน่อย ๆ
เจ๋งมาก ไม่รู้มาก่อนว่า UEFI API เข้าถึงได้ง่ายและมีเอกสารดีขนาดนี้
อยากรู้ว่าวงจรการพัฒนาเป็นยังไง น่าจะรันใน VM แต่สงสัยว่าทุกครั้งที่รันไคลเอนต์ต้อง “บูต” ใหม่ทุกครั้งหรือเปล่า
สคริปต์หลักสำหรับรันจะสร้าง EFI filesystem ใหม่ที่มี build ใหม่ของ UEFIRC แล้วส่งต่อให้ QEMU
แต่ตอนทำ GUI overhead นี้ค่อนข้างน่ารำคาญ เลยตั้งค่าให้แอป build ได้ทั้งเป้าหมายเป็น UEFI แท้ ๆ และ host environment ที่รันบน Mac
พอเปลี่ยน build flag แล้ว GUI toolkit จะวาดลง framebuffer ที่ UEFI ให้มาโดยตรง หรือเชื่อมกับ window system ของ Mac เพื่อรับส่ง event
overhead ของแนวทางแบบ สองเป้าหมาย นี้เห็นได้จาก entry point ด้วย: https://github.com/codyd51/uefirc/blob/main/src/main.rs
ส่วนการ parse ข้อความ IRC ไม่ต้องมีของตกแต่งพิเศษ จึงพัฒนาเป็นชุด unit test ที่รันบน Mac ได้โดยตรง และบางส่วนอยู่ที่นี่: https://github.com/codyd51/uefirc/blob/main/src/irc/response...
สักวันหนึ่งอยากทำ ระบบปฏิบัติการ สำหรับบอต IRC ของผมที่ยังรันอยู่ให้เสร็จ
อาจเป็นสิ่งที่ไร้ประโยชน์ที่สุดที่จะพูดก็ได้ แต่การเคลื่อนเมาส์แบบไม่เชิงเส้น หรือก็คือ acceleration เป็นการตั้งค่าแรกที่ผมปิดเมื่อบูตระบบปฏิบัติการใหม่ขึ้นมา แปลกดีที่มือผมเจ็บจริง ๆ
เช่น บน Mac มี linearmouse ฟรี ส่วนบน Windows ก็แค่ปิด acceleration ได้เลย บน Linux ก็แน่นอนว่าง่าย
ถ้าใช้ mouse acceleration จะฝึกความรู้สึกในการ mapping ระหว่างระยะที่เมาส์เคลื่อนกับระยะที่เคลื่อนบนหน้าจอได้ยาก และในระยะยาวผมมองว่าใช้แบบไม่มี acceleration มีประสิทธิภาพกว่า
เป็นวิธีที่เรียนรู้มาจากเกมเมอร์ และคิดว่าเกมเมอร์ยังทำแบบนั้นด้วยเหตุผลที่สมควรอยู่
ไม่ว่าจะเป็นแบบไหน ความรู้สึกก็น่าจะคุ้นชินได้ เหมือนคันเร่งรถยนต์ที่ปกติก็ไม่ได้ map ตรงกับความเร็วโดยตรง
ถ้าถามว่า “ทำไม?” ก็เพราะตอน UEFI ถูกแนะนำครั้งแรก มันสัญญาไว้ถึงแอประดับต่ำแบบนี้
ฝั่งที่สร้าง UEFI ยังเคยฝันว่าจะใช้มันแทนระบบปฏิบัติการมินิสำหรับอินเทอร์เน็ตโดยเฉพาะที่อิง Linux ซึ่งบาง vendor ให้กดปุ่มเฉพาะระหว่างบูตเพื่อเข้าใช้งานได้ด้วย จำชื่อไม่ได้แล้ว
ผมเคยดูวิดีโอ YouTube ที่เจาะลึกเรื่องนี้ จำได้ว่าตอนแรกเป็น Linux แบบย่อส่วนหรือระบบปฏิบัติการปรับแต่งอื่น ๆ ต่อมาย้ายไปเป็นแอป UEFI แล้วสุดท้ายกระแสก็จางไป
บทความดี ทำให้นึกถึงมุก April Fools ของ bootloader barebox เมื่อ 2 ปีก่อน เป็นฟีเจอร์ที่ถ้าเป้าหมายบูตอื่น ๆ ล้มเหลวหมด ก็จะเชื่อมต่อไปที่ #barebox[1]
ฝั่งนั้นโฟกัสที่การเพิ่มการรองรับ TCP ให้ barebox และไม่มีองค์ประกอบ GUI เท่ ๆ แบบที่นี่
อินเทอร์เฟซมีแค่ command line และถ้า build barebox เป็น EFI payload ก็สามารถวาดบน EFI GOP ได้
[1]: https://lore.barebox.org/barebox/20220401145902.GF4351@telli...
นึกถึงวิดีโอล่าสุดของ Cathode Ray Dude ทันที ที่พูดถึง QuickLook ของ HP ซึ่งเป็น “ไคลเอนต์อีเมล” หรือจริง ๆ แล้วคือปลั๊กอิน Outlook และเป็นผลิตภัณฑ์ที่ implement แล้ววางขายด้วยวิธีประมาณนี้: https://www.youtube.com/watch?v=ssob-7sGVWs
ในวิดีโอยังมีเรื่องแปลก ๆ กว่านี้ที่ HP เคยทำด้วย แต่โปรเจกต์นี้ทำได้ถึงส่วนยากที่ QuickLook เลี่ยงไป นั่นคือ networking
ภาพประกอบ ในบทความยอดเยี่ยมและน่าประทับใจอย่างน่าทึ่ง