- SerenityOS मुख्यतः QEMU में अच्छी तरह चलता था, लेकिन असली लैपटॉप पर बूट, डिबगिंग और स्टोरेज एक्सेस से लेकर हर चरण पर रुकावटें आईं, जिससे हार्डवेयर सपोर्ट की कमियाँ सामने आईं
- प्रयोग के लिए Intel Celeron N4020, 4GB DDR4, 32GB eMMC और 1366×768 TN डिस्प्ले वाला Dell 3100 Chromebook चुना गया, लेकिन जिस Cr50-आधारित closed-case debugging की उम्मीद थी, वह इस बोर्ड पर काम नहीं कर सकी
- Cr50 रास्ता बंद होने के बाद RP2040-आधारित Pi Pico को अंदर फिट करके UART और SPI फ्लैश से सीधे जोड़ा गया, और CircuitPython व serprog की मदद से PicoCCD नाम का अस्थायी debugging और flashing डिवाइस बनाया गया
- शुरुआती बूट लॉग सीधे PCI के पीछे मौजूद MMIO 16550 UART से निकालना मुश्किल था, इसलिए ChromeOS EC द्वारा रिकॉर्ड किए जाने वाले IO port 0x80 को एक धीमे अस्थायी output channel की तरह इस्तेमाल किया गया
- eMMC सपोर्ट SD/MMC initialization के अंतर, SDHCI power control की कमी, और SD-only commands को disable करने जैसी समस्याओं से गुजरते हुए कुछ हद तक ग्राफिकल सेशन तक पहुँचा, लेकिन performance, stability और patches को साफ़-सुथरा करना अभी बाकी है
असली हार्डवेयर के रूप में चुना गया Dell 3100 Chromebook
- SerenityOS में और गहराई से योगदान देने की कोशिश में सबसे पहले जो कमजोरी साफ़ दिखी, वह यह थी कि QEMU में चलने के बावजूद असली हार्डवेयर सपोर्ट कमज़ोर था
- UEFI सपोर्ट पर spholz पहले से काम कर रहे थे, इसलिए उसमें हस्तक्षेप नहीं किया गया; इस काम के लिए इतना काफ़ी था कि master branch kernel TianoCore UEFI runtime से GRUB के जरिए बूट हो जाए
- अपनी मुख्य development machine जैसे सिस्टम पर OS को डिबग करने से बचना था, इसलिए अपेक्षाकृत नया ऐसा हार्डवेयर चुना गया जिसे रोज़मर्रा में भी इस्तेमाल किया जा सके
- Allegro पर सस्ता Chromebook ढूँढते हुए Dell 3100 95PLN, लगभग 25EUR में खरीदा गया
- Intel Celeron N4020, 2 core, Hyper-Threading नहीं
- 4GB DDR4
- 32GB onboard eMMC
- UHD600 IGP द्वारा चलाया जाने वाला 1366×768 TN डिस्प्ले
- 2 USB-A, 2 USB-C, 3.5mm jack
- Dell के ऊँचे बिज़नेस लैपटॉप्स से भी बेहतर लगा keyboard
- इस डिवाइस को आगे octopus hostname से संदर्भित किया गया
Cr50-आधारित debugging की उम्मीद और विफलता
- Chromebook चुनने की एक बड़ी वजह यह थी कि उसका Cr50 security chip और embedded controller, closed-case debugging के लिए उपयोगी फीचर्स देते हैं
- 2018 के बाद के लगभग सभी Chromebook में SuzyQ cable के जरिए एक USB-C port को debugging के लिए इस्तेमाल किया जा सकता है, और Cr50 आमतौर पर तीन ttyUSB devices expose करता है
- अंदरूनी Cr50 console
- AP console, यानी Chromebook का serial port
- cros_ec console, यानी embedded controller
- लक्ष्य था कि लैपटॉप खोले बिना और बाहर तार लटकाए बिना serial console तक पहुँचा जाए; cros_ec के key input emulation और power state control को देखते हुए एक साधारण KVM जैसा सेटअप भी संभव लग रहा था
- लेकिन असली octopus पर Cr50 CCD काम नहीं कर रहा था
- नया SuzyQ cable बनाया गया और दूसरे Chromebook पर soldering की जाँच भी की गई, फिर भी सफलता नहीं मिली
- octopus उन गिने-चुने लैपटॉप्स में से निकला जिनमें Dell ने बोर्ड के कुछ resistors नहीं लगाए थे, इसलिए CCD काम नहीं करता था
- कुछ लोगों ने खास port orientation और charger connection की स्थिति में सीमित सफलता की बात कही थी, लेकिन इस डिवाइस पर वह भी बिल्कुल काम नहीं किया
- बाद में मिली जानकारी के अनुसार, गायब resistor का असर शायद USB bridge पर नहीं बल्कि केवल SPI flashing पर पड़ना चाहिए था, इसलिए Cr50 debugging के पूरी तरह विफल होने की सही वजह अब भी स्पष्ट नहीं है
Pi Pico से बना PicoCCD
- Cr50 वाला रास्ता बंद होने के बाद यह देखा गया कि क्या डिवाइस के अंदर की खाली जगह में एक सामान्य Pi Pico board फिट हो सकता है, और वह आराम से फिट हो गया
- मिलते-जुलते लैपटॉप्स की schematics देखी गईं, लेकिन octopus से पूरी तरह मेल खाने वाली schematic नहीं मिली
- बोर्ड का एक बड़ा debug port Intel-संबंधित JTAG और test points के लिए था, जो इस काम के लिए उपयुक्त नहीं था
- दूसरा Google Servo के लिए था, लेकिन Google ने कई debug probes को Servo नाम से जारी किया था और documentation भी सीमित थी, इसलिए सही जानकारी खोजना कठिन था
- संदर्भ के लिए Servo documentation देखी गई
- Glasgow के UART applet और frequency detection को चालू करके, Linux के
/dev/ttyS1पर लगातार output देने के दौरान संदिग्ध UART TX pads को सीधे probe किया गया- TX pad कुछ ही मिनटों में मिल गया
- RX अधिक कठिन था क्योंकि उसके लिए active transmission चाहिए था, और गलत लाइन छूने पर board reset हो सकता था; वास्तव में दो बार reset हुआ भी
- बाद में EC के RX/TX pins भी लगभग 10 मिनट में मिल गए
- solder की गई wires को UV-curing epoxy से फिक्स किया गया, और 6 महीने तक connection में कोई समस्या नहीं आई
- RP2040 के SPI peripheral का इस्तेमाल करते हुए flash chip पर 6 wires और solder की गईं, और write-protect pin तक जाने वाले trace को काटकर GND से जोड़ दिया गया ताकि Cr50 अनुमति के बिना write access मिल सके
- software के लिए CircuitPython चुना गया
- क्योंकि USB mass storage के जरिए scripts और data चढ़ाना आसान था
- UART को
cdc_acmUSB device से bridge करना सरल था - और चूँकि SPI flash भी जुड़ी हुई थी, flashing feature की भी ज़रूरत थी
- सामान्य open source EEPROM और SPI flashing tool के रूप में flashrom इस्तेमाल किया गया, और SPI को UART पर proxy करने वाला serprog इस काम के लिए उपयुक्त था
- stacksmashing का pico-serprog C implementation मौजूद था, लेकिन हर बार BIOS flash करने पर Pico को दोबारा flash करना पड़ता, जो अनुकूल नहीं था
- इसके बजाय CircuitPython में serprog implement किया गया, और Glasgow serprog applet से काफी मदद ली गई
- तैयार कोड को जल्दी में बनाए गए closed-case debugging solution PicoCCD के रूप में व्यवस्थित किया गया, और उसका repository Forgejo पर PicoCCD में है
- WeirdTreeThing ने भी इसी उद्देश्य से RP2040 C code लिखा था, और उनका PicoCCD version भी उपलब्ध है
SerenityOS बूट लॉग हासिल करना
- debugging के लिए Alpine Linux install किया गया और बाहर से build किए गए SerenityOS kernel को लाने वाली बुनियादी utilities तैयार की गईं
- बाद में यह सेटअप बढ़ते-बढ़ते ऐसा बन गया जिसमें build machine से artifacts अपने-आप download होते थे, kernel decompress होकर overwrite होता था, और user space
.tarको unpack करने वाला GRUB entry भी था - बदलाव करने के बाद test तक पहुँचने का iteration time लिखे जाने के समय लगभग 20 सेकंड था, जो bare metal hacking के हिसाब से काफी अच्छा माना गया
- पहला GRUB boot entry लगभग
multiboot /Kernel serial_debugही था, लेकिन न स्क्रीन पर और न serial port पर कोई output दिखाई दिया - स्क्रीन output समस्या के लिए GRUB boot entry में
insmod all_videoजोड़ने का उपाय मिला; इससे समस्या पूरी तरह हल नहीं हुई, लेकिन दिशा सही थी - serial output का न होना उससे भी बड़ी समस्या थी
- coreboot logs कुछ सेकंड पहले तक आ रहे थे
- इस डिवाइस का UART पारंपरिक port-mapped 16550 नहीं, बल्कि MMIO-आधारित 16550A था
- Linux logs में
ttyS0औरttyS1MMIO addresses पर मौजूद 16550A के रूप में दिखे lspciमें Intel Celeron/Pentium Silver Processor Serial IO UART Host Controller एक PCI device के रूप में दिखाई दिया
16550 UART और port 0x80 का workaround
- परंपरागत रूप से IBM PC के बाहरी devices x86 CPU के port I/O में map होते थे, और
outbवinbजैसे instructions से access किए जाते थे - बाद में कई devices MMIO पर चले गए, लेकिन serial ports में high-speed competition महत्वपूर्ण न होने के कारण पुराना तरीका लंबे समय तक बना रहा और debug port के रूप में उपयोगी रहा
- सामान्य माहौल में
outb 0x3f8, 0x41जैसी write करने पर दूसरी तरफAमिल सकता है, और initialization भी छोटा होता है, इसलिए छोटे projects में इसे implement करना आसान है - octopus का UART PCI के पीछे स्थित MMIO device था, और SerenityOS के शुरुआती boot phase में PCI initialization तक पहुँचना मुश्किल था
- SerenityOS में PCI bus implementation मौजूद है, लेकिन boot stage बहुत शुरुआती थी
- मौजूदा
PCISerialDeviceका MMIO context में पहले कभी उपयोग नहीं हुआ था - debug output के बिना इस driver को बनाना आदर्श नहीं था
- ChromeOS devices का embedded controller IO port 0x80 पर होने वाली सभी writes को log करता है
- इस port का उपयोग परंपरागत रूप से POST status reporting के लिए होता है
- motherboard के 7-segment boot code displays इसी port 80 decoding पद्धति पर चलते हैं
- Linux में
/dev/portपर byte लिखने वाली script से इस hypothesis को test किया गया, और cros_ec console में वही bytes पढ़े जा सके - SerenityOS के entry point
Kernel/Arch/init.cppके आसपासIO::out8(0x80, 1);जैसा code डालकर execution progress ट्रैक किया गया, और crash point कोMemory::MemoryManager::initialize(0);तक सीमित कर दिया गया - इसके बाद serial write routine का address 0x3f8 की जगह 0x80 करने का प्रयोग किया गया
- शुरुआत में बहुत सारे bytes आए, लेकिन cros_ec उन्हें भरोसेमंद तरीके से relay नहीं कर पाया और overflow होने लगा
- आगे चलकर cros_ec log output खुद भी corrupt हो गया
- वजह यह थी कि असली serial chip की तरह इसमें बड़ा buffer नहीं था
- workaround के तौर पर हर write के बीच बहुत से
nop-आधारित wait states डाले गए- इससे बूट messages पूरे दिखाने में कुछ सेकंड की जगह कई मिनट लगने लगे
- फिर भी bare metal debugging के लिए यह कीमत स्वीकार्य लगी
- cros_ec log lines को अपने-आप parse करके ASCII में decode करने के लिए
picocom,watch,grep,sed,cut,xxdको मिलाकर Bash one-liner इस्तेमाल किया गया
framebuffer और पहला graphical output
- बूट लॉग मिलने के बाद कुछ दिनों तक codebase पढ़कर समस्या को खुद समझने की कोशिश की गई, लेकिन अंततः community से मदद माँगनी पड़ी
- spholz ने उस समय खुला हुआ SerenityOS PR #24435 बताया, और उस branch से build करते ही generic framebuffer काम करने लगा
- स्क्रीन पर ऐसा परिणाम दिखाई दिया जो असफल होते हुए भी किसी हद तक सफल जैसा लग रहा था, और उसके बाद storage समस्याएँ गंभीर रूप से सामने आने लगीं
eMMC और SD/MMC initialization की समस्या
- मौजूदा StorageManagement crash, SD Host Controller initialization failure के बाद controller list खाली होने वाली assertion तक पहुँच रहा था
- logs में
PCI: Failed to initialize SD Host ControllerऔरASSERTION FAILED: !m_controllers.is_empty()दिखा - परिणामस्वरूप
StorageManagement::enumerate_storage_devices()में kernel panic हुआ
- logs में
- octopus में 32GB eMMC chip थी, और SerenityOS में पहले से कुछ SD drivers थे, इसलिए लगा कि MMC support जोड़ने से काम बन सकता है
- SD/MMC card इस्तेमाल करने के लिए मोटे तौर पर तीन चीज़ें चाहिए होती हैं
- Host Controller: आधुनिक hardware में सामान्यतः SD Association द्वारा परिभाषित SDHCI
- Host Controller से जुड़ी bus: इस मामले में PCI
- Host और card के बीच communication protocol का implementation
- crash logs के आधार पर SerenityOS में पहली दो चीज़ें मौजूद थीं; बची हुई समस्या protocol वाली थी
- SD protocol की public specification उपलब्ध है, लेकिन MMC 2007 में JEDEC standard बनने के बाद उसके आधिकारिक access के लिए शुल्क लगने लगा
- SD और MMC की initialization sequence अलग होती है
- SerenityOS की शुरुआत CMD0 भेजकर response का इंतज़ार करने से होती थी, जो SD और MMC दोनों पर काम करना चाहिए
- उसके बाद voltage setup के लिए CMD8 भेजा जाता था, लेकिन MMC इसे support नहीं करता, इसलिए error आना चाहिए
- कुछ स्रोत इसके बाद card को reset करके उसे MMC मानने का सुझाव देते हैं
- दूसरे स्रोत CMD8 और CMD58 के results के संयोजन से SD version और capacity type तक पहचानने वाला अधिक व्यापक flow बताते हैं
- पूरी advanced compatibility checking लागू करने के बजाय केवल बुनियादी checks ही implement किए गए
power control register की कमी और समाधान
- MMC initialization flow का सार इस प्रकार था
- reset के बाद clock को 400KHz पर सेट करना
- 1ms इंतज़ार करना, फिर 74 clocks और इंतज़ार करना
- CMD0 भेजना और response का इंतज़ार करना
- response का 31वाँ bit 1 होने तक CMD1 दोहराना
- loop खत्म होने पर value को Operating Conditions register में सहेजना
- SD-only registers की query छोड़कर SD initialization algorithm के बाकी हिस्से जारी रखना
- वैकल्पिक रूप से High-Speed compatibility detect करके कई HS modes में से एक activate करना
- code चौथे चरण तक पहुँच गया था, लेकिन उसके बाद eMMC किसी भी request का जवाब नहीं दे रही थी
- कई दिनों तक कारण न मिलने के बाद controller reset से जुड़ा code हटाया गया, तब eMMC ने response देना शुरू किया, और समस्या
reset_host_controller()function तक सीमित हो गई - इस function की अजीब बात यह थी कि standard में
host_configurationनाम का register मिला ही नहीं- पुराने code में कई registers को मनमाने ढंग से दो
host_configurationgroups में बाँट दिया गया था - initialization भी पूरी तरह समाप्त नहीं हुई थी, और पहले group को बस 0 पर सेट किया जा रहा था
- पुराने code में कई registers को मनमाने ढंग से दो
- पहले group में card power regulator नियंत्रित करने वाला Power Control register शामिल था
- कुछ hardware, जिनमें eMMC इस्तेमाल करने वाले सभी implementations शामिल हैं, में card को power देने के लिए यह register आवश्यक होता है
- दूसरी designs में power rail सीधे slot से जुड़ी होने के कारण इस setting को नज़रअंदाज़ किया जा सकता है
- अस्थायी समाधान के रूप में
host_configuration_0में मूल रूप से मौजूद value को वापस लेकर इस्तेमाल किया गया - मूल समस्या यह थी कि card को power दिए बिना उससे communication करने की कोशिश हो रही थी
- उसके बाद SD card पर ही मान्य कुछ खास commands को disable करने में कुछ और घंटे लगे, और controller से अधिक अर्थपूर्ण debug output मिलने लगा, जिससे बाकी काम अपेक्षाकृत सामान्य ढंग से आगे बढ़ा
मौजूदा स्थिति और बाकी काम
- अंततः SerenityOS बहुत धीमी गति से, कुछ हद तक खराब graphical session दिखाने में सफल हुआ, लेकिन जल्द ही freeze हो गया
- इस graphical session की समस्या और framebuffer को फिर से ठीक करने की प्रक्रिया पर अगली पोस्ट में चर्चा की जाएगी
- पूरा काम लगभग 6 महीने की learning process था, और इस दौरान दूसरे काम भी साथ चलते रहे
- patches को साफ़-सुथरा करके इस साल के भीतर upstream में भेजना अगला लक्ष्य है
1 टिप्पणियां
Hacker News की राय
मैंने कहीं पढ़ा था कि NetBSD के drivers को custom kernel के हिसाब से ढालना अपेक्षाकृत आसान है, तो लगता है Serenity भी शायद ऐसा रास्ता अपना सकता है
नए OS के लिए device drivers एक बड़ी बाधा होते हैं
hardware configuration लगभग fixed होती है, इसलिए drivers बनाना या लाना और system test करना आसान हो जाता है
drivers को न्यूनतम base support के साथ user space में चलाया जा सकता है
https://en.wikipedia.org/wiki/Rump_kernel
हालांकि drivers के बारे में मुझे अच्छी तरह नहीं पता
Apple ने शुरुआत से काफी हद तक यही तरीका अपनाया है, और consumer Unix-like systems में यह इकलौता बड़ा successful example है
System76 भी लगभग ऐसा ही example है, और Frame.work भी मिलता-जुलता है, लेकिन वह OS खुद पर कम focus करता है
सभी conditions unfavorable होने वाली machine पर इसे चलाना वाकई कमाल की hacking work है, और यह talented लोगों की जबरदस्त मेहनत का नतीजा लगता है
ऐसे लेख पढ़कर सोचता हूं कि drivers और OS की दुनिया में entry कैसे करनी चाहिए
यह इतना complex लगता है कि समझ नहीं आता शुरुआत कहां से करूं
इसे memory-mapped input/output (MMIO) कहा जाता है। normal applications में kernel hardware memory तक direct access रोक देता है, इसलिए वे ऐसा नहीं कर सकतीं
शुरुआत करने के लिए Rust/C++/C/Zig जैसी language चाहिए जो target CPU के लिए machine code निकाल सके, और बेहतर है कि उसमें runtime या GC न हो। अगर low-level language पहली बार सीख रहे हैं, तो examples ज्यादा होने की वजह से C recommend करूंगा
target CPU की basic assembly भी सीखनी होगी, और कुछ instructions high-level language के built-in functions में उपलब्ध नहीं हो सकते
फिर hello world kernel लिखते हुए आप सीखेंगे कि CPU kernel को कैसे start करता है, execution modes और privilege levels कैसे बंटे होते हैं
इसके बाद x86 में 64-bit instructions इस्तेमाल करने के लिए long mode में switch करने जैसे steps से CPU को अपनी जरूरत के हिसाब से setup करते हैं, और आम तौर पर इसी stage पर virtual memory भी setup करते हैं
यहां तक पहुंचने पर आपको अंदाजा हो जाएगा कि CPU OS के साथ कैसे जुड़ता है, available devices enumerate कैसे किए जाते हैं और memory locations कैसे खोजी जाती हैं; इसके बाद file system, scheduler जैसे काफी काम बाकी रहते हैं
OS पर चलने वाले software और OS kernel के बीच फर्क आखिरकार उस code को चला रहे CPU mode का है, और highest privilege में वे instructions इस्तेमाल किए जा सकते हैं जिन्हें normal apps नहीं इस्तेमाल कर सकतीं
बाद में मुझे FreeBSD docs के अंदर छिपे treasure जैसे resources मिले, जिनमें FreeBSD Architecture Handbook और FreeBSD Developers' Handbook खास तौर पर मददगार हो सकते हैं
https://lwn.net/Kernel/LDD3/
https://docs.freebsd.org/en/books/
Minix बहुत साफ-सुथरे तरीके से लिखा गया है, इसका kernel भी करीब 5 हजार lines का है, और कई textbooks में इसे cover किया गया है
मैंने एक simple server implement किया और kernel hacking भी की; Minix microkernel है, इसलिए ज्यादातर drivers उसी तरह operate करते हैं
मैंने course material पहले से पढ़ लिया था और lectures में लगभग नहीं गया, फिर भी 10 में से 8 marks मिले
NetBSD और SerenityOS के बारे में भी बहुत अच्छी बातें सुनी हैं, और Andreas ने काफी development live streaming के जरिए किया था
कहां से शुरू करना है, यह पता हो तो असल में आसान हो जाता है
उदाहरण के लिए, device driver का काम ऐसा interface expose करना है जिससे computer पर चलने वाले दूसरे programs किसी device को access और control कर सकें
https://m.youtube.com/watch?v=juGNPLdjLH4 एक ठीक-ठाक crash course है
Arduino जैसी चीज़ से PC के साथ information exchange करने वाला simple USB device भी बनाया जा सकता है। उदाहरण: https://m.youtube.com/watch?v=yTc2GLXfCOY
इसके बाद समझें कि जिस subsystem में आपकी दिलचस्पी है वह क्या करता है, उसे कैसे चलाना है, और फिर code लिखें। storage devices, graphics devices आदि इसमें आते हैं
Raspberry Pi भी ऐसे experiments के लिए अच्छा starting point हो सकता है। उदाहरण: Writing a bare metal operating system for the raspberry pi https://github.com/babbleberry/rpi4-osdev
https://wiki.osdev.org/Bare_Bones
मुझे SerenityOS का concept और Ladybird browser पसंद है, इसलिए ऐसी progress देखकर खुशी है
Ladybird न सिर्फ independent project बन गया है, बल्कि अब SerenityOS को target platform भी नहीं मानता
Ladybird अपनी Serenity layer को धीरे-धीरे घटाकर अधिक mainstream alternatives से replace कर रहा है
मुख्य रूप से Linux इस्तेमाल करने वाले के तौर पर मुझे उम्मीद है कि Ladybird Linux पर सचमुच का alternative बनता जा रहा है
लेकिन SerenityOS fan के तौर पर दुख है कि Ladybird में जाने वाली energy और innovation SerenityOS से बाहर निकल रही है
Chromebook hacking में मदद चाहिए हो तो chromium-os-dev mailing list पर पूछ सकते हैं
लगता है कोई CCD को काम कराने में मदद कर सकता है
https://groups.google.com/a/chromium.org/g/chromium-os-dev?p...
Depthcharge bootloader भी TFTP के जरिए network booting support करता है
इसे खुद build करके SPI में flash करना पड़ता है, लेकिन kernel को iteratively develop करते समय यह बहुत अच्छी feature है
https://chromium.googlesource.com/chromiumos/platform/depthc...
मुझे लगा था SerenityOS पहले से real hardware पर चल चुका है; क्या अभी भी सब कुछ QEMU के अंदर ही चल रहा है?
लेकिन बताने लायक drivers लगभग नहीं थे, इसलिए यह सिर्फ सबसे basic sense में काम करता था, वह भी specific hardware पर, और शायद बहुत अच्छी तरह नहीं चलता था
यह attempt कम से कम एक real hardware platform पर इसे भरोसेमंद तरीके से काम कराने का है
Serenity लगातार impressive है, भले ही कभी-कभी उसके implementation approach से सहमत न हो पाऊं
मैं ऐसी ही डरावनी चीज़ देखने आया था
doas dd seek=$((0x$1)) bs=1 count=1 of=/dev/port < <(xxd -p -r <<< "$2")