- SerenityOS chủ yếu chạy tốt trên QEMU, nhưng trên laptop thật, các vấn đề lần lượt xuất hiện ngay từ khâu khởi động, debug và truy cập storage, làm lộ ra những khoảng trống trong hỗ trợ phần cứng
- Thiết bị thử nghiệm là Dell 3100 Chromebook với Intel Celeron N4020, 4GB DDR4, eMMC 32GB và màn hình TN 1366×768; cơ chế debug closed-case dựa trên Cr50 như kỳ vọng đã thất bại trên bo mạch này
- Khi đường Cr50 bị chặn, tác giả đặt một Pi Pico dùng RP2040 vào bên trong, nối trực tiếp tới UART và SPI flash, rồi dùng CircuitPython cùng serprog để tạo một thiết bị debug và flash tạm thời tên PicoCCD
- Log khởi động ban đầu được thu thập bằng cách tận dụng IO port 0x80 mà ChromeOS EC ghi lại như một kênh xuất tạm thời chậm, vì khó dùng ngay UART MMIO 16550 nằm sau PCI
- Hỗ trợ eMMC đã vượt qua các khác biệt trong khởi tạo SD/MMC, thiếu sót điều khiển nguồn SDHCI và vấn đề vô hiệu hóa lệnh chỉ dành cho SD để đi tới được một phần phiên đồ họa, nhưng hiệu năng, độ ổn định và việc dọn dẹp patch vẫn còn phía trước
Dell 3100 Chromebook được chọn làm mục tiêu phần cứng thật
- Trong quá trình muốn tham gia sâu hơn vào SerenityOS, điểm yếu đầu tiên đập vào mắt là nó chạy được trên QEMU nhưng hỗ trợ phần cứng thật còn thiếu
- Hỗ trợ UEFI đã được spholz triển khai, nên tác giả không động vào; với công việc này, chỉ cần kernel nhánh master khởi động bằng GRUB trong runtime TianoCore UEFI là đủ
- Tác giả muốn tránh cách debug OS trên cùng thiết bị với máy phát triển chính, và chọn phần cứng tương đối mới đến mức có thể dùng hằng ngày trong thực tế
- Khi tìm một Chromebook giá rẻ trên Allegro, tác giả mua được Dell 3100 với giá 95PLN, khoảng 25EUR
- Intel Celeron N4020, 2 nhân, không có Hyper-Threading
- 4GB DDR4
- eMMC onboard 32GB
- Màn hình TN 1366×768 do IGP UHD600 điều khiển
- 2 cổng USB-A, 2 cổng USB-C, jack 3,5mm
- Bàn phím được cảm nhận là tốt hơn các laptop doanh nghiệp cao cấp của Dell
- Thiết bị này về sau được gọi bằng hostname octopus
Kỳ vọng debug dựa trên Cr50 và thất bại
- Một lý do lớn để chọn Chromebook là chip bảo mật kiêm embedded controller Cr50 cung cấp các tính năng hữu ích cho debug closed-case
- Gần như mọi Chromebook từ năm 2018 trở đi đều có thể dùng một cổng USB-C để debug bằng cáp SuzyQ, và Cr50 thường phơi ra ba thiết bị ttyUSB
- Console nội bộ của Cr50
- Console AP, tức cổng serial của Chromebook
- Console cros_ec, tức embedded controller
- Mục tiêu là truy cập serial console mà không phải mở laptop rồi để dây lòng thòng; nếu tính cả giả lập nhập phím và điều khiển trạng thái nguồn của cros_ec, một dạng KVM đơn giản cũng có vẻ khả thi
- Trên octopus thực tế, Cr50 CCD không hoạt động
- Tác giả làm một cáp SuzyQ mới và kiểm chứng mối hàn bằng Chromebook khác nhưng vẫn thất bại
- octopus là một trong số ít laptop mà CCD không hoạt động vì Dell không gắn một số điện trở trên bo mạch
- Một số người nói họ thành công hạn chế trong điều kiện hướng cắm cổng cụ thể và có cắm sạc, nhưng trên thiết bị này thì hoàn toàn không hoạt động
- Theo thông tin kiểm tra sau đó, điện trở bị thiếu lẽ ra chỉ ảnh hưởng đến SPI flashing chứ không phải bản thân USB bridge, nên lý do chính xác khiến debug Cr50 hoàn toàn không hoạt động vẫn chưa rõ
PicoCCD được làm bằng Pi Pico
- Sau khi đường Cr50 bị chặn, tác giả kiểm tra xem có thể đặt một bo Pi Pico thông thường vào khoảng trống bên trong thiết bị hay không, và thấy là vừa đủ
- Tác giả tham khảo sơ đồ mạch của các laptop tương tự, nhưng không có sơ đồ nào khớp chính xác với octopus
- Một cổng debug lớn trên bo là các test point và JTAG liên quan đến Intel, không phù hợp với mục đích
- Cổng còn lại là Google Servo, nhưng Google đã phát hành nhiều debug probe dưới tên Servo và tài liệu cũng hạn chế nên khó tra cứu
- Tác giả tham khảo tài liệu Servo
- Tác giả bật applet UART và phát hiện tần số của Glasgow, rồi trực tiếp dò các pad UART TX nghi vấn trong lúc Linux liên tục xuất ra
/dev/ttyS1- Pad TX được tìm thấy trong vài phút
- RX khó hơn vì cần truyền chủ động, và chạm nhầm đường có thể làm bo mạch reset; thực tế đã xảy ra reset hai lần
- Sau đó, các chân RX/TX cho EC cũng được tìm thấy trong khoảng 10 phút
- Các dây đã hàn được cố định bằng epoxy đóng rắn bằng UV, và trong 6 tháng không gặp vấn đề kết nối
- Tác giả cũng tận dụng ngoại vi SPI của RP2040 để hàn 6 dây vào chip flash, cắt trace đi tới chân write-protect rồi nối xuống GND để có quyền ghi mà không cần Cr50 cho phép
- Về phần mềm, tác giả chọn CircuitPython
- Vì có thể tải script và dữ liệu lên dưới dạng USB mass storage
- Việc bridge UART thành thiết bị USB
cdc_acmkhá đơn giản - Vì đã nối cả SPI flash nên cũng cần chức năng flashing
- Tác giả dùng flashrom làm công cụ flashing EEPROM/SPI mã nguồn mở phổ biến, và serprog, vốn proxy SPI qua UART, phù hợp với mục đích
- Đã có bản triển khai C pico-serprog của stacksmashing, nhưng việc phải flash lại Pico mỗi lần flash BIOS không phù hợp
- Thay vào đó, tác giả triển khai serprog bằng CircuitPython và tham khảo nhiều từ applet Glasgow serprog
- Mã kết quả được sắp xếp thành giải pháp debug closed-case làm nhanh tên PicoCCD, và repository nằm tại PicoCCD trên Forgejo
- WeirdTreeThing cũng viết mã RP2040 C với mục đích tương tự, và cũng có phiên bản PicoCCD của anh ấy
Thu thập log khởi động SerenityOS
- Tác giả cài Alpine Linux để debug và cấu hình các tiện ích cơ bản để đưa kernel SerenityOS được build bên ngoài vào máy
- Sau đó, cấu trúc này lớn dần thành hệ thống tự động tải artifact từ máy build, giải nén và ghi đè kernel, cũng như có entry GRUB để giải nén
.tarcủa user space - Thời gian lặp từ lúc thay đổi đến lúc test, tại thời điểm viết, khoảng 20 giây, khá ổn đối với việc hack bare-metal
- Entry khởi động GRUB đầu tiên thực chất là
multiboot /Kernel serial_debug, nhưng cả màn hình lẫn cổng serial đều không có đầu ra nào - Với vấn đề xuất màn hình, tác giả tìm ra cách thêm
insmod all_videovào entry khởi động GRUB; riêng việc đó chưa giải quyết được, nhưng là hướng đúng - Việc không có đầu ra serial là vấn đề lớn hơn
- Log coreboot vẫn còn vào được cho đến vài giây trước đó
- UART của thiết bị này không phải 16550 kiểu port-mapped truyền thống mà là 16550A dựa trên MMIO
- Log Linux hiển thị
ttyS0vàttyS1là 16550A nằm tại địa chỉ MMIO - Trong
lspci, Intel Celeron/Pentium Silver Processor Serial IO UART Host Controller xuất hiện như một thiết bị PCI
UART 16550 và đường vòng port 0x80
- Theo truyền thống, thiết bị ngoại vi của IBM PC được map vào port I/O của CPU x86 và được truy cập bằng các lệnh như
outbvàinb - Nhiều thiết bị về sau chuyển sang MMIO, nhưng cổng serial vẫn giữ cách cũ vì cạnh tranh tốc độ cao không quan trọng, nên hữu ích làm cổng debug
- Trong môi trường thông thường, viết kiểu
outb 0x3f8, 0x41là phía bên kia có thể nhận đượcA, và quá trình khởi tạo cũng ngắn nên dễ triển khai trong các dự án nhỏ - UART của octopus là thiết bị MMIO nằm sau PCI, và ở giai đoạn rất sớm khi SerenityOS khởi động thì khó kỳ vọng PCI đã được khởi tạo
- SerenityOS có triển khai bus PCI, nhưng thời điểm khởi động còn quá sớm
PCISerialDevicehiện có cũng chưa từng được dùng trong ngữ cảnh MMIO- Việc viết driver này mà không có đầu ra debug là không lý tưởng
- Embedded controller của thiết bị ChromeOS ghi log mọi lệnh ghi tới IO port 0x80
- Port này theo truyền thống được dùng để báo trạng thái POST
- Bộ hiển thị mã khởi động 7 đoạn trên bo mạch chủ hoạt động bằng cách decode port 80
- Tác giả kiểm thử giả thuyết bằng một script ghi byte vào
/dev/porttrên Linux, và có thể đọc được byte đó trên console cros_ec - Tác giả chèn mã như
IO::out8(0x80, 1);quanh điểm bắt đầu của SerenityOS làKernel/Arch/init.cppđể theo dõi vị trí thực thi, rồi thu hẹp được điểm crash tớiMemory::MemoryManager::initialize(0); - Sau đó, tác giả thử đổi địa chỉ của routine ghi serial từ 0x3f8 sang 0x80
- Ban đầu có nhiều byte xuất ra, nhưng cros_ec không relay ổn định được nên xảy ra overflow
- Sau đó nữa, chính đầu ra log cros_ec cũng bị hỏng
- Lý do là nó không có buffer lớn như chip serial thật
- Tác giả đi vòng bằng cách chèn nhiều wait state dựa trên
nopgiữa mỗi lần ghi- Nếu in hết thông điệp khởi động, quá trình boot từ vài giây tăng lên vài phút
- Dù vậy, tác giả xem đây là cái giá chấp nhận được cho debug bare-metal
- Để tự động parse dòng log cros_ec và decode thành ASCII, tác giả dùng một lệnh Bash một dòng kết hợp
picocom,watch,grep,sed,cut,xxd
Framebuffer và đầu ra đồ họa đầu tiên
- Sau khi có log khởi động, tác giả dành vài ngày đọc trực tiếp codebase để hiểu vấn đề, nhưng cuối cùng nhờ cộng đồng trợ giúp
- spholz chỉ ra SerenityOS PR #24435 đang mở lúc đó; khi build từ nhánh này, generic framebuffer hoạt động
- Trên màn hình xuất hiện kết quả có trạng thái như thể việc thất bại đã thành công, và sau đó vấn đề storage bắt đầu lộ rõ
eMMC và vấn đề khởi tạo SD/MMC
- Lỗi crash StorageManagement hiện có dẫn tới một assertion rằng danh sách controller rỗng sau khi khởi tạo SD Host Controller thất bại
- Log cho thấy
PCI: Failed to initialize SD Host ControllervàASSERTION FAILED: !m_controllers.is_empty() - Kết quả là kernel panic xảy ra trong
StorageManagement::enumerate_storage_devices()
- Log cho thấy
- octopus có chip eMMC 32GB, và SerenityOS đã có một phần driver SD nên có vẻ chỉ cần thêm hỗ trợ MMC
- Để dùng thẻ SD/MMC, về cơ bản cần ba yếu tố
- Host Controller: trên thiết bị hiện đại thường là SDHCI do SD Association đặc tả
- Bus kết nối với Host Controller: trong trường hợp này là PCI
- Triển khai protocol để host và card giao tiếp
- Theo log crash, SerenityOS đã có hai yếu tố đầu, vấn đề còn lại nằm ở phần protocol
- SD protocol có đặc tả công khai, nhưng MMC sau khi trở thành tiêu chuẩn JEDEC năm 2007 thì cần trả phí để truy cập chính thức
- SD và MMC có trình tự khởi tạo khác nhau
- SerenityOS bắt đầu bằng cách gửi CMD0 và chờ phản hồi, điều này lẽ ra phải qua được với cả SD và MMC
- Tiếp theo, nó gửi CMD8 để thiết lập điện áp, nhưng MMC không hỗ trợ lệnh này nên phải báo lỗi
- Một số tài liệu đề xuất sau đó reset card và xem nó là MMC
- Tài liệu khác đưa ra luồng toàn diện hơn, phân loại cả phiên bản SD và loại dung lượng bằng tổ hợp kết quả CMD8 và CMD58
- Tác giả không triển khai toàn bộ kiểm tra tương thích nâng cao mà chỉ triển khai kiểm tra cơ bản
Thiếu thanh ghi điều khiển nguồn và cách giải quyết
- Luồng khởi tạo MMC có thể tóm tắt như các bước sau
- Sau reset, đặt clock thành 400KHz
- Chờ 1ms rồi chờ thêm 74 clock
- Gửi CMD0 và chờ phản hồi
- Lặp CMD1 cho tới khi bit thứ 31 của phản hồi bằng 1
- Khi vòng lặp kết thúc, lưu giá trị làm thanh ghi Operating Conditions
- Tiếp tục thuật toán khởi tạo SD, trừ việc truy vấn các thanh ghi chỉ dành cho SD
- Tùy chọn phát hiện tương thích High-Speed và bật một trong nhiều chế độ HS
- Code đi được tới khoảng bước 4, nhưng sau đó eMMC không phản hồi bất kỳ yêu cầu nào
- Sau vài ngày không tìm ra nguyên nhân, tác giả gỡ mã liên quan đến reset controller thì eMMC bắt đầu phản hồi, và vấn đề được thu hẹp vào hàm
reset_host_controller() - Điểm lạ của hàm này là không thể tìm thấy thanh ghi
host_configurationtrong tiêu chuẩn- Code trước đó đã tùy ý gom nhiều thanh ghi thành hai nhóm
host_configuration - Quá trình khởi tạo cũng chưa hoàn tất, và nhóm đầu tiên chỉ đơn giản bị đặt về 0
- Code trước đó đã tùy ý gom nhiều thanh ghi thành hai nhóm
- Nhóm đầu tiên bao gồm thanh ghi Power Control điều khiển bộ điều áp nguồn của card
- Trên một số phần cứng, gồm mọi triển khai dùng eMMC, thanh ghi này cần thiết để bật chính card
- Ở các thiết kế khác nối trực tiếp rail nguồn vào slot, thiết lập này có thể bị bỏ qua
- Là giải pháp tạm thời, tác giả lấy và dùng giá trị vốn có trong
host_configuration_0 - Vấn đề cốt lõi là đã cố giao tiếp với card khi chưa bật nguồn cho nó
- Sau đó, tác giả mất thêm vài giờ để tìm và vô hiệu hóa một số lệnh chỉ hợp lệ với thẻ SD; khi controller bắt đầu xuất debug có ý nghĩa hơn, phần việc còn lại diễn ra tương đối bình thường
Trạng thái hiện tại và việc còn lại
- Cuối cùng, SerenityOS đã thành công mở một phiên đồ họa rất chậm và bị hỏng một phần, nhưng nhanh chóng bị treo
- Vấn đề phiên đồ họa này và quá trình đưa framebuffer trở lại bình thường sẽ được nói trong bài tiếp theo
- Toàn bộ công việc là một quá trình học tập kéo dài khoảng 6 tháng, trong thời gian đó tác giả cũng làm những việc khác
- Mục tiêu tiếp theo là dọn dẹp các patch và đưa lên upstream trong năm nay
1 bình luận
Ý kiến trên Hacker News
Tôi từng đọc rằng việc điều chỉnh driver của NetBSD cho phù hợp với kernel tùy chỉnh tương đối dễ, nên tự hỏi liệu phía Serenity cũng có thể đi theo hướng đó không
Với một OS mới, driver thiết bị là một rào cản lớn
Vì cấu hình phần cứng gần như cố định, việc viết hoặc lấy driver và kiểm thử hệ thống sẽ dễ hơn
Có thể chạy driver trong user space chỉ với phần hỗ trợ nền tảng tối thiểu
https://en.wikipedia.org/wiki/Rump_kernel
Tuy nhiên về phần driver thì tôi không rõ lắm
Tôi thấy Apple về cơ bản đã làm như vậy ngay từ đầu, và đó là trường hợp duy nhất thành công lớn trong dòng Unix dành cho người tiêu dùng
System76 cũng gần như là một ví dụ như thế, Frame.work cũng tương tự nhưng ít tập trung vào bản thân OS hơn
Việc khiến nó chạy được trên một máy có mọi điều kiện đều bất lợi thật sự là một màn hack rất ấn tượng, có vẻ là kết quả của nỗ lực khổng lồ từ những người tài năng
Đọc những bài như thế này khiến tôi tự hỏi nên bắt đầu bước vào thế giới driver và OS như thế nào
Trông quá phức tạp nên tôi không rõ nên bắt đầu từ đâu
Việc này được gọi là vào/ra ánh xạ bộ nhớ (MMIO). Trong ứng dụng thông thường thì không làm được vì kernel chặn truy cập trực tiếp vào bộ nhớ phần cứng
Để bắt đầu, bạn cần một ngôn ngữ có thể sinh mã máy cho CPU mục tiêu, như Rust/C++/C/Zig, và tốt nhất là không có runtime hay GC. Nếu đây là lần đầu với ngôn ngữ cấp thấp, tôi khuyên dùng C vì có nhiều ví dụ
Bạn cũng cần học assembly cơ bản của CPU mục tiêu, vì một số lệnh có thể không được cung cấp dưới dạng hàm dựng sẵn trong ngôn ngữ cấp cao
Sau đó, trong khi viết kernel hello world, bạn sẽ học CPU khởi động kernel như thế nào, các chế độ thực thi và mức đặc quyền được phân chia ra sao
Tiếp theo, trên x86, bạn cấu hình CPU theo cách mình muốn, chẳng hạn chuyển sang long mode để dùng lệnh 64-bit; thường ở giai đoạn này cũng sẽ thiết lập bộ nhớ ảo
Đến đây bạn sẽ bắt đầu nắm được CPU khớp với OS ra sao, cách liệt kê các thiết bị khả dụng và tìm vị trí bộ nhớ; sau đó vẫn còn nhiều việc như file system, scheduler
Khác biệt giữa phần mềm chạy trên OS và kernel của OS rốt cuộc là chế độ CPU hiện đang chạy đoạn mã đó, và ở mức đặc quyền cao nhất có thể dùng các lệnh mà ứng dụng thông thường không dùng được
Về sau tôi tìm thấy những tài liệu như kho báu ẩn trong tài liệu FreeBSD, trong đó FreeBSD Architecture Handbook và FreeBSD Developers' Handbook có thể đặc biệt hữu ích
https://lwn.net/Kernel/LDD3/
https://docs.freebsd.org/en/books/
Minix được viết rất sạch, kernel cũng chỉ khoảng 5 nghìn dòng và được trình bày trong nhiều giáo trình
Tôi đã triển khai một server đơn giản và cũng hack kernel; vì Minix là microkernel nên hầu hết driver hoạt động theo kiểu đó
Tôi đọc trước tài liệu môn học và gần như không nghe giảng mà vẫn được 8/10 điểm
Tôi cũng nghe nhiều điều tốt về NetBSD và SerenityOS, còn Andreas đã phát triển rất nhiều qua livestream
Khi biết bắt đầu từ đâu thì thực ra mọi thứ trở nên dễ hơn
Ví dụ, driver thiết bị có nhiệm vụ cung cấp một giao diện để các chương trình khác chạy trên máy tính có thể truy cập và điều khiển một thiết bị nào đó
https://m.youtube.com/watch?v=juGNPLdjLH4 là một bài giảng cấp tốc khá ổn
Bạn cũng có thể dùng thứ như Arduino để tạo một thiết bị USB đơn giản trao đổi thông tin với PC. Ví dụ: https://m.youtube.com/watch?v=yTc2GLXfCOY
Sau đó, chỉ cần hiểu subsystem mà bạn quan tâm làm gì, vận hành nó ra sao, rồi viết code. Thiết bị lưu trữ, thiết bị đồ họa, v.v. thuộc nhóm này
Raspberry Pi cũng có thể là điểm khởi đầu tốt cho các thử nghiệm như vậy. Ví dụ: Writing a bare metal operating system for the raspberry pi https://github.com/babbleberry/rpi4-osdev
https://wiki.osdev.org/Bare_Bones
Tôi thích concept của SerenityOS và trình duyệt Ladybird, nên rất vui khi thấy tiến triển như thế này
Ladybird không chỉ trở thành một dự án độc lập mà còn không còn xem SerenityOS là nền tảng mục tiêu nữa
Ladybird đang dần loại bỏ lớp Serenity riêng của mình và thay bằng các lựa chọn chính thống hơn
Với tư cách một người chủ yếu dùng Linux, tôi rất mong Ladybird trở thành một lựa chọn thay thế thực sự trên Linux
Nhưng với tư cách fan SerenityOS, tôi tiếc khi năng lượng và đổi mới từng đổ vào Ladybird đang rời khỏi SerenityOS
Nếu cần trợ giúp về hack Chromebook, hãy hỏi trên mailing list chromium-os-dev
Có lẽ sẽ có người giúp làm cho CCD hoạt động
https://groups.google.com/a/chromium.org/g/chromium-os-dev?p...
Bootloader Depthcharge cũng hỗ trợ boot qua mạng bằng TFTP
Bạn phải tự build và flash vào SPI, nhưng đây là tính năng rất tốt khi phát triển kernel lặp đi lặp lại
https://chromium.googlesource.com/chromiumos/platform/depthc...
Tôi cứ tưởng SerenityOS đã chạy trên phần cứng thật rồi, chẳng lẽ đến giờ vẫn chỉ chạy hoàn toàn trong QEMU sao?
Nhưng gần như không có driver đáng kể nào, nên nó chỉ hoạt động theo nghĩa cơ bản nhất, chỉ trên một số phần cứng cụ thể, và có lẽ cũng không chạy tốt lắm
Nỗ lực lần này là để làm cho nó hoạt động đáng tin cậy trên ít nhất một nền tảng phần cứng thật
Serenity vẫn luôn gây ấn tượng, dù có những lúc tôi không đồng ý với cách triển khai của họ
Tôi đến đây để xem những thứ đáng sợ như thế này
doas dd seek=$((0x$1)) bs=1 count=1 of=/dev/port < <(xxd -p -r <<< "$2")