Mungkin sedikit orang IT yang perlu menjelaskan apa itu Memtest86 + - mungkin sudah menjadi kurang lebih standar dalam pengujian RAM pada PC. Ketika di salah satu bagian sebelumnya saya menemukan bilah memori yang rusak yang dibundel dengan papan, itu (bersama dengan netbook yang diaktifkan DDR2) tampak solusi yang jelas. Pertanyaan lain adalah bahwa di sana, pada prinsipnya, operasi sistem yang tidak stabil terlihat dengan mata telanjang. Dalam kasus yang lebih rumit, saya mendengar bahwa selain "penyadapan" dangkal sel memori hingga tak terbatas, alat ini menggunakan beberapa pola data khusus di mana kesalahan DDR lebih mungkin terdeteksi. Secara umum, hal yang luar biasa, sangat disayangkan bahkan dalam namanya tertulis: 86 - "Hanya untuk sistem yang kompatibel dengan x86." Atau tidak?
Di bawah potongan Anda akan melihat upaya saya untuk port MemTest86 + v5.1 ke RISC-V dan subtotal. Spoiler: bergerak!
PENOLAKAN: proyek yang dihasilkan minimal diuji secara khusus oleh saya pada perakitan RocketChip tertentu pada papan tertentu. Keakuratan dan keamanan (terutama pada sistem lain) tidak dijamin. Gunakan dengan risiko Anda sendiri. Secara khusus, area memori yang dipesan saat ini tidak diproses dengan cara apa pun jika masuk dalam kisaran RAM.
Seperti yang sudah saya katakan, belum lama ini saya membeli motherboard dengan Cyclone IV di AliExpress, tetapi memori di dalamnya bermasalah. Untungnya, salah satu fitur penting dari board ini adalah penggunaan modul DDR2 SO-DIMM konvensional - sama seperti pada netbook lama saya. Namun demikian, itu akan menarik untuk mendapatkan, sehingga untuk berbicara, solusi self-host untuk menguji modul memori (dan, pada kenyataannya, juga pengontrol). Prospek kesalahan saya debugging dalam kondisi memori buruk entah bagaimana sama sekali tidak menyenangkan. Terutama tidak berharap untuk solusi cepat dan secara mental bersiap untuk menunda penulisan ulang lengkap di assembler lain untuk waktu yang lama, saya membuka artikel Wikipedia tentang Memtest86 + dan tiba-tiba melihat "Ditulis dalam: C dan perakitan" di kartu. Hmm, yaitu, dia, meskipun "... 86", tetapi tidak sepenuhnya ditulis assembler? Ini menggembirakan. Tetap hanya untuk memahami hubungan.
Jadi, buka memtest.org dan unduh versi 5.01 di bawah GPL2. Untuk kemudahan pengembangan, saya memuatnya kembali di GitHub. Untungnya, tepat di arsip sumber, kami disambut oleh file README.background , berhak
Anatomi & Fisiologi Memtest86-SMP
Ini menjelaskan secara rinci (dan bahkan dengan gambar dalam bentuk ASCII-art) operasi kode tingkat tinggi. Di awal dokumen, kita melihat tata letak Biner , yang terdiri dari bootsect.o
, setup.o
, head.o
dan beberapa memtest_shared
. Sangat mudah untuk melihat bahwa ketiga file objek ini diperoleh dari sumber assembler yang sesuai. Sepintas, semua yang lain ditulis dalam C! Tidak buruk, tidak buruk ...
Akibatnya, saya menyalin Makefile
ke Makefile.arch
dan mulai menulis ulang semuanya, dan mencoba untuk membuang yang tidak sesuai. Pertama-tama, tentu saja, saya membutuhkan toolchain untuk RISC-V, yang, untungnya, masih bersama saya sejak percobaan sebelumnya. Pada awalnya saya berpikir untuk membuat port untuk arsitektur 32-bit, tetapi kemudian saya ingat bahwa prosesor 64-bit diunggah ke papan, dan saya memiliki riscv64-
dengan riscv64-
awalan.
Penyimpangan liris: tentu saja, hal pertama adalah mempelajari masalah kompatibilitas kode 32-dan 64-bit. Sebagai hasilnya, spesifikasi untuk bagian yang tidak terjangkau dari ISA (Instruction Set Architecture) ditemukan dalam paragraf 1.3 RISC-V ISA Overview
pernyataan 1.3 RISC-V ISA Overview
:
Keuntungan utama dari memisahkan ISA basis secara eksplisit adalah bahwa setiap ISA basis dapat dioptimalkan untuk kebutuhannya tanpa perlu mendukung semua operasi yang diperlukan untuk ISA basis lainnya. Misalnya, RV64I dapat menghilangkan instruksi dan CSR yang hanya diperlukan untuk mengatasi register yang lebih sempit di RV32I. Opsi RV32I dapat menggunakan ruang enkode yang disediakan untuk instruksi hanya diperlukan oleh varian ruang alamat yang lebih luas.
Saya juga ingin mencatat bahwa toolchain dengan riscv64-
awalan cenderung dengan mudah mengumpulkan kode 32-bit jika arsitektur target dipilih dengan benar - lebih lanjut tentang itu nanti.
Dalam proses porting, membuat dokumen-dokumen ini berguna:
Penyiapan bangun
Mari kita mulai dengan menyetujui: Saya ingin mendapatkan port yang cocok untuk porting lebih lanjut ke arsitektur selain x86 dan RISC-V. Saya juga mengusulkan untuk membuang disket boot dan spesifik x86 lainnya dari cross-platform build.
Apa yang akhirnya kita miliki: ada tiga file assembler: bootsect.S
, setup.S
dan head.S
Dua yang pertama diperlukan hanya pada saat startup, dan yang ketiga diperlukan nanti ketika pindah ke area memori lain. Faktanya adalah bahwa untuk menguji memori "di bawah diri sendiri", kode pengujian pertama-tama harus pindah ke tempat baru. File Sich dikumpulkan dalam ELF, dari mana bagian kode, data, dll. Kemudian diambil. Selain itu, dikumpulkan dalam bentuk PIC (Position Independent Code) - pada awalnya saya bahkan terkejut: meskipun kode tersebut berdiri bebas (yaitu, tanpa kernel, libc, dll.), Ia menggunakan fitur-fitur canggih seperti itu.
Selanjutnya, parameter yang mendefinisikan arsitektur secara berkala menemukan di Makefile: -march=i486
, -m32
dan sejenisnya. Saya perlu menulis sesuatu seperti itu, dan kemudian seperti pengisap . Dengan indikasi arsitektur, situasi dengan RISC-V adalah sesuatu seperti ini: ada opsi untuk rv32
dan rv64
(seperti, masih ada embedded paling terpotong dan dicadangkan untuk rv128
masa depan, tetapi kami tidak terlalu tertarik pada mereka), dan nama ISA dibentuk dengan memberikan huruf pada awalan ini. ekstensi: i
- kumpulan integer dasar instruksi, perkalian dan pembagian m
- integer, ... Tentu saja, saya ingin melakukan rv64i
, tetapi Memtest86 tidak akan dengan mudah dipindahkan ke arsitektur tanpa perkalian. Benar, tampaknya kompiler hanya akan menghasilkan panggilan fungsi daripada instruksi "bermasalah", tetapi ada risiko yang tersisa dengan kinerja sangat berkurang (belum lagi bahwa fungsi-fungsi ini perlu ditulis atau dibawa ke suatu tempat).
Anda juga akan membutuhkan garis ABI. Pada prinsipnya, dasar-dasar konvensi pemanggilan sudah dijelaskan dalam Volume I
ditentukan dalam "Buku Pegangan Programmer Perakitan RISC-V", jadi saya akan melakukan sesuatu seperti:
$ riscv64-linux-gnu-gcc-9 -mabi=help riscv64-linux-gnu-gcc-9: error: unrecognized argument in option '-mabi=help' riscv64-linux-gnu-gcc-9: note: valid arguments to '-mabi=' are: ilp32 ilp32d ilp32e ilp32f lp64 lp64d lp64f riscv64-linux-gnu-gcc-9: fatal error: no input files compilation terminated.
Dan tanpa berpikir lp64
, saya akan mengambil lp64
. Ke depan, saya akan mengatakan bahwa dengan ABI ini, file header dari perpustakaan standar tidak berfungsi, jadi saya mengambil lp64f
, dan ARCH "ditingkatkan" ke rv64imf
. Tanpa panik, saya tidak berencana untuk benar-benar menggunakan floating point di port saya.
Karena saya entah bagaimana tidak ingin menulis skrip linker lintas platform - dan karenanya saya tidak dapat segera menemukan kunci untuk menemukan , saya memutuskan untuk bertahan dengan file assembler head.S
., Berpegang teguh pada sisa fungsi menggunakan memtest_shared.arch.lds
. Saya membuang indikasi format output dan arsitektur darinya (setelah semua, lebih mudah untuk mengubahnya dari variabel di Makefile), dan juga untuk sementara mengomentari DISCARD
pada akhirnya, tidak dapat mengetahui bagian tertentu dari informasi debug yang saya butuhkan. (Melihat ke depan: informasi debug yang bagus, tetapi .rela
harus ditambahkan) Secara umum, versi x86 menekankan perlunya memuat dalam 64k - Saya harap ini hanya terkait dengan fitur mode nyata dan tidak menjadi perhatian kami pada RISC-V . Akibatnya, objek bersama dengan PIC akan dikumpulkan, seperti dalam aslinya, kode dan data yang akan dimuat ke dalam memori akan digigit keluar dari itu.
Kami mengumpulkan ... dan kompilasi jatuh pada file reloc.c
pertama - itu, tampaknya, diambil dari beberapa ld-linux.so
dan bertanggung jawab untuk mendukung Global Offset Table, dll. sesuai dengan konvensi pemanggilan untuk x86. Ternyata diperlukan kerja langsung dengan register menggunakan insert assembler. Tapi kami berada di RISC-V - ini awalnya dibuat untuk mendukung PIC secara reloc.c
, jadi silakan melempar reloc.c
. Selanjutnya, masih ada sisipan, terkadang cukup panjang. Untungnya, mereka berada dalam kode tes segera setelah kode C yang dikomentari, yang mereka optimalkan (dari mereka saya kembali membuat potongan kode yang diubah oleh arahan preprocessor) atau sesuatu yang bergantung pada platform, yang tanpanya, dalam kasus ekstrim, saya dapat (mungkin) lakukan (seperti menyalakan / mematikan cache, mengurangi CPUID, dll.). Akhirnya, ada beberapa hal seperti panggilan rdtsc
, yang saya rdtsc
, tanpa masalah besar dimasukkan ke header platform-dependent dan mengimplementasikannya sesuai dengan dokumentasi pada RISC-V.
Hasilnya, kami mendapatkan direktori arch/i386
, tempat sejumlah besar kode dukungan PCI dipindahkan, membaca informasi dari chipset, definisi platform-spesifik dari alamat yang dipetakan dengan memori, dll. Juga, awal dari fungsi test_start
, yang merupakan titik masuk dari setup.S
ke kode C. Berapa lama, pendek, tetapi mengomentari segala sesuatu yang mungkin dan menyadari segala sesuatu yang tidak dapat dikomentari di bawah RISC-V (seperti setup.S
dan kode untuk bekerja dengan port serial dalam implementasi SiFive), saya mendapat arch/riscv
, yang semuanya dikompilasi.
Di sini saya dipaksa untuk mengklarifikasi bahwa percobaan itu sendiri sebagian dilakukan sebelum penulisan artikel, sehingga urutan tindakan tertentu mungkin mengandung sejumlah "fiksi artistik". Namun, saya mencoba untuk setidaknya melakukan presentasi sedemikian rupa sehingga dalam hal apa pun itu merupakan salah satu jalur yang mungkin (saya seorang programmer, saya ingat itu) . Jadi mari kita lihat bagaimana memulai semuanya.
Berlari di atas besi
Sejak percobaan sebelumnya, saya masih memiliki "dudukan" berdebu dari Raspberry Pi, yang disambungkan ke papan debug. Kabel menyediakan UART, JTAG dan adaptor dengan kartu SD. Prosesor RV64 tertentu dengan pengontrol DDR2 dijahit ke dalam memori konfigurasi. Seperti pada waktu sebelumnya, saya menyalakan "raspberry", buka dua sesi SSH sebelumnya, salah satunya meneruskan 3333 port TCP untuk menghubungkan gdb ke OpenOCD. Dalam salah satu sesi, saya memulai minicom untuk menonton UART, di sesi lain - openocd untuk debug dari host melalui JTAG. Saya menyalakan daya papan - dan pesan di konsol tentang bagaimana memuat data dari SD berlari.
Sekarang Anda dapat menjalankan perintah:
riscv64-unknown-elf-gdb \ -ex 'target remote 127.0.0.1:3333' \ -ex 'restore /path/to/memtest_shared.bin binary 0x80010000' \ -ex 'add-symbol-file /path/to/memtest_shared 0x80010000' -ex 'set $pc=0x80010000'
opsi -ex
gdb untuk berpura-pura bahwa pengguna telah memasukkan perintah-perintah ini dari konsol:
- yang pertama membuat koneksi dengan OpenOCD
- yang kedua menyalin isi dari file host yang ditentukan ke alamat yang ditentukan
- yang ketiga menjelaskan kepada gdb bahwa informasi tentang kode sumber harus diambil dari file ini , dengan mempertimbangkan fakta bahwa kode itu diunduh di alamat ini (dan bukan apa yang ditunjukkan di dalamnya)
- catatan: kita mengambil karakter dari file ELF, dan memuat biner "mentah"
- akhirnya, yang keempat secara paksa menerjemahkan pointer perintah saat ini ke kode kami
Sayangnya, tidak semuanya berjalan lancar, dan meskipun baris kode di debugger ditampilkan dengan benar, tetapi di semua variabel global - nol. Bahkan, jika kita menjalankan perintah form p &global_var
di gdb, sayangnya, kita lihat alamatnya sesuai dengan alamat unduhan awal (saya punya 0x0
), yang tidak ditentukan menggunakan add-symbol-file
. Sebagai penopang, tetapi solusi yang sangat sederhana, saya hanya menambahkan 0x80010000
ke alamat yang ditentukan secara manual dan melihat isi memori melalui x/x 0xADDR
. Bahkan, mungkin untuk sementara menunjukkan alamat awal yang benar dalam skrip tautan, yang saat ini akan bertepatan dengan alamat unduhan dalam konfigurasi pengujian ini .
Fitur relokasi pada arsitektur modern
Nah, bagaimana cara mengunduh kodenya, entah bagaimana caranya - kita memulainya. Tidak bekerja Langkah-demi-langkah debugging menunjukkan bahwa kita jatuh selama operasi fungsi switch_to_main_stack
- tampaknya masih mencoba untuk menggunakan nilai yang tidak terkait dari alamat simbol yang sesuai dengan tumpukan kerja.
Semua sama, volume pertama dari dokumentasi memberi tahu kita tentang berbagai instruksi semu dan kerja mereka dengan PIC hidup dan mati:

Seperti yang Anda lihat, prinsip umum adalah bahwa alamat dalam memori dihitung dari instruksi saat ini, dengan yang pertama menambahkan bagian atas offset, dan selanjutnya add
memoles bit urutan rendah. Hampir tidak membantu untuk mendeklarasikan variabel global like
struct vars * const v = &variables;
Oleh karena itu, kami mengambil dokumentasi RISC-V ELF psABI dengan deskripsi jenis relokasi dan menulis bagian platform khusus untuk reloc.c
. Di sini harus dicatat bahwa file asli, tampaknya, diambil dari kode lintas platform. Di sana, bahkan alih-alih menentukan kedalaman bit tertentu, makro jenis ElfW(Addr)
, Elf32_Addr
ke Elf32_Addr
atau Elf64_Addr
. Tidak di mana-mana, bagaimanapun, itu sebabnya kami menambahkan mereka di mana mereka tidak berada dalam kode umum (juga dalam kode arch/riscv/reloc.inc.c
- lagipula, untuk RISC-V tidak ada rasa khusus untuk terikat pada kedalaman bit tertentu, di mana tidak ada wajib).
Akibatnya, switch_to_main_stack
mulai lulus (bukan tanpa instruksi assembler yang tergantung platform, tentu saja). Debugger menunjukkan variabel global masih bengkok. Baiklah, oke :(
Definisi Perangkat Keras
Tentu saja, untuk pengujian akan mungkin untuk menggunakan konstanta hard-coded bukan kode definisi peralatan yang dibuang, tetapi untuk setiap perakitan prosesor tertentu, membangun kembali memtest bahkan terlalu mahal menurut standar aplikasi saya. Karena itu, kita akan bertindak "sebagai orang dewasa yang serius." Untungnya, pada RISC-V (dan mungkin pada kebanyakan arsitektur modern), sudah biasa bagi bootloader untuk meneruskan kode ke Device Tree Blob , yang merupakan versi kompilasi dari deskripsi DTS seperti ini:
zeowaa-1gb.dts /dts-v1/; / { #address-cells = ^_^lt gt^_^; #size-cells = ^_^lt gt^_^; compatible = "freechips,rocketchip-unknown-dev"; model = "freechips,rocketchip-unknown"; chosen { bootargs = "console=ttySIF0,125200 debug loglevel=7"; }; firmware { sifive,uboot = "YYYY-MM-DD"; }; L16: aliases { serial0 = &L8; }; L15: cpus { #address-cells = ^_^lt gt^_^; #size-cells = ^_^lt�gt^_^; timebase-frequency = ^_^ltgt^_^; L5: cpu@0 { device_type = "cpu"; clock-frequency = ^_^lt�gt^_^; compatible = "sifive,rocket0", "riscv"; d-cache-block-size = ^_^lt gt^_^; d-cache-sets = ^_^lt@gt^_^; d-cache-size = ^_^ltကgt^_^; d-tlb-sets = ^_^lt gt^_^; d-tlb-size = ^_^lt gt^_^; i-cache-block-size = ^_^lt gt^_^; i-cache-sets = ^_^lt@gt^_^; i-cache-size = ^_^ltကgt^_^; i-tlb-sets = ^_^lt gt^_^; i-tlb-size = ^_^lt gt^_^; mmu-type = "riscv,sv39"; next-level-cache = <&L10>; reg = <0x0>; riscv,isa = "rv64imafdc"; status = "okay"; timebase-frequency = ^_^ltgt^_^; tlb-split; L3: interrupt-controller { #interrupt-cells = ^_^lt gt^_^; compatible = "riscv,cpu-intc"; interrupt-controller; }; }; }; L10: ram@80000000 { device_type = "memory"; reg = <0x0 0x80000000 0x0 0x40000000>; reg-names = "mem"; }; L14: soc { #address-cells = ^_^lt gt^_^; #size-cells = ^_^lt gt^_^; compatible = "freechips,rocketchip-unknown-soc", "simple-bus"; ranges; L1: clint@2000000 { compatible = "riscv,clint0"; interrupts-extended = <&L3 3 &L3 7>; reg = <0x2000000 0x10000>; reg-names = "control"; }; L2: debug-controller@0 { compatible = "sifive,debug-013", "riscv,debug-013"; interrupts-extended = <&L3 65535>; reg = <0x0 0x1000>; reg-names = "control"; }; L9: gpio@64002000 { #gpio-cells = ^_^lt gt^_^; #interrupt-cells = ^_^lt gt^_^; compatible = "sifive,gpio0"; gpio-controller; interrupt-controller; interrupt-parent = <&L0>; interrupts = <3 4 5 6 7 8>; reg = <0x64002000 0x1000>; reg-names = "control"; }; L0: interrupt-controller@c000000 { #interrupt-cells = ^_^lt gt^_^; compatible = "riscv,plic0"; interrupt-controller; interrupts-extended = <&L3 11 &L3 9>; reg = <0xc000000 0x4000000>; reg-names = "control"; riscv,max-priority = ^_^lt gt^_^; riscv,ndev = ^_^lt gt^_^; }; L6: rom@10000 { compatible = "sifive,maskrom0"; reg = <0x10000 0x2000>; reg-names = "mem"; }; L8: serial@64000000 { compatible = "sifive,uart0"; interrupt-parent = <&L0>; clocks = <&tlclk>; interrupts = ^_^lt gt^_^; reg = <0x64000000 0x1000>; reg-names = "control"; }; L7: spi@64001000 { #address-cells = ^_^lt gt^_^; #size-cells = ^_^lt�gt^_^; compatible = "sifive,spi0"; interrupt-parent = <&L0>; interrupts = ^_^lt gt^_^; reg = <0x64001000 0x1000>; clocks = <&tlclk>; reg-names = "control"; L12: mmc@0 { compatible = "mmc-spi-slot"; disable-wp; reg = <0x0>; spi-max-frequency = ^_^lt gt^_^; voltage-ranges = <3300 3300>; }; }; tlclk: tlclk { #clock-cells = ^_^lt�gt^_^; clock-frequency = ^_^lt gt^_^; clock-output-names = "tlclk"; compatible = "fixed-clock"; }; }; };
Saya dulu mem-parsing file ELF, tapi sekarang saya kembali yakin dengan FDT (tree device datar): spesifikasi semacam ini ditulis oleh orang yang peduli (Namun, mereka sendiri kemudian menguraikannya!) dan parsing file seperti itu (setidaknya sampai Anda perlu memproses input yang tidak dipercaya) tidak menimbulkan masalah tertentu. Jadi di sini: di awal file ada struktur header sederhana yang berisi angka ajaib 0xd00dfeed
dan beberapa bidang lagi. Kami tertarik dengan offset "flat tree" off_dt_struct
dan tabel baris off_dt_strings
. Sebenarnya, Anda juga perlu memproses off_mem_rsvmap
, yang menyebutkan area memori yang sebaiknya dihindari. Saya masih mengabaikan mereka (mereka tidak di papan saya), tetapi jangan ulangi ini di rumah .
Pada prinsipnya, pemrosesan tidak terlalu sulit: Anda hanya perlu berjalan di atas pohon datar sesuai dengan token. Ada tiga token utama :
Nah, secara umum, itu saja: kita melewati bagian ini, tidak lupa untuk mengamati perataan dengan 4 byte. Oh ya, lalat di salep: angka dalam FDT dalam format big endian, jadi kami membuat fungsi sederhana
static inline uint32_t be32(uint32_t x) { return (x << 24) | (x >> 24) | ((x & 0xff0000) >> 8) | ((x & 0xff00) << 8); }
Akibatnya, di riscv_entry
hal pertama yang harus dilakukan adalah mengurai FDT, dan bagian head.S
Yang bertanggung jawab untuk mentransfer kontrol ke riscv_entry
terlihat seperti ini
.globl startup_32 # -- ... startup_32: lla sp, boot_stack_top mv s0, a0 # s0, s1 -- callee-saved mv s1, a1 # ... .bss # jal _dl_start # mv a0, s0 mv a1, s1 j riscv_entry
Dalam register a0
kita diberi id hart (hart adalah sesuatu seperti aliran perangkat keras dalam terminologi RISC-V) - Saya belum menggunakannya, saya harus mencari tahu dalam case single-threaded. Dalam a1
bootloader menempatkan pointer ke FDT. Kami meneruskannya ke fungsi void riscv_entry(ulong hartid, uint8_t *fdt_address)
.
Sekarang, dengan munculnya parsilka FDT dalam kode saya, urutan pemuatan papan menjadi seperti ini:
Dan dari sisi gdb, perintah ditambahkan
Ternyata, selain insert assembler, ada juga alamat memori yang dikenal. Misalnya SCREEN_ADR
(persis seperti itu, dengan satu D
), yang menunjuk ke area yang sesuai dengan apa yang ditampilkan di layar. Ketika saya menemukan ini, saya hanya menempatkan dengan gerakan lebar segala sesuatu yang merujuknya di bawah #if HAS_SCREEN
, dan kemudian debugged secara membabi buta untuk waktu yang lama. Saya pikir sudah secara manual sekali dalam beberapa waktu untuk membuang ini semua ke konsol, tetapi kemudian saya perhatikan bahwa kode yang sama menyakitkan banyak keluaran urutan keluaran ke port serial. Ternyata semuanya sudah ditulis sebelum kita, Anda hanya perlu menempatkan definisi lebih akurat - dan ini dia, antarmuka yang akrab (walaupun hitam dan putih) di jendela minicom! (Saat ini, HAS_SCREEN tidak digunakan sama sekali - saya baru saja memulai array dummy_con
untuk mengubah kode asli minimum.)
Debug pada QEMU
Jadi saya men-debug semua yang ada di papan nyata, dan untuk beberapa waktu sekarang - bahkan tanpa membabi buta. Tapi semuanya melambat di JTAG - horor! Yah, pada akhirnya, semuanya harus bekerja pada perangkat keras nyata, tapi alangkah baiknya untuk debug pada QEMU. Setelah sejumlah percobaan, sesuatu ternyata menjadi penopang, tetapi sangat mirip dengan bekerja dengan papan:
$ qemu-system-riscv64 -M help Supported machines are: none empty machine sifive_e RISC-V Board compatible with SiFive E SDK sifive_u RISC-V Board compatible with SiFive U SDK spike_v1.10 RISC-V Spike Board (Privileged ISA v1.10) (default) spike_v1.9.1 RISC-V Spike Board (Privileged ISA v1.9.1) virt RISC-V VirtIO Board (Privileged ISA v1.10)
Kami melihat papan mana yang QEMU siap tiru. Saya tertarik pada perangkat keras yang kompatibel dengan sifive_u
.
$ qemu-system-riscv64 -M sifive_u,dumpdtb -m 1g # - QEMU on -- strace $ ls -l on -rw-rw-r-- 1 trosinenko trosinenko 1923 19 20:14 on $ dtc -I dtb < on > on.dts # $ vim on.dts # bootargs $ dtc < on.dts > on.dtb <stdout>: Warning (clocks_property): /soc/ethernet@100900fc:clocks: cell 0 is not a phandle reference <stdout>: Warning (clocks_property): /soc/ethernet@100900fc:clocks: cell 1 is not a phandle reference <stdout>: Warning (clocks_property): /soc/ethernet@100900fc:clocks: cell 2 is not a phandle reference <stdout>: Warning (interrupts_extended_property): /soc/interrupt-controller@c000000:interrupts-extended: cell 0 is not a phandle reference <stdout>: Warning (interrupts_extended_property): /soc/interrupt-controller@c000000:interrupts-extended: cell 2 is not a phandle reference <stdout>: Warning (interrupts_extended_property): /soc/clint@2000000:interrupts-extended: cell 0 is not a phandle reference <stdout>: Warning (interrupts_extended_property): /soc/clint@2000000:interrupts-extended: cell 2 is not a phandle reference
Sekarang kita memiliki gumpalan pohon perangkat "tetap". Tanpa mengubah konfigurasi VM (kruk!), Jalankan:
qemu-system-riscv64 \ -M sifive_u -m 1g \ -serial stdio \ -s -S
-serial stdio
mengarahkan port serial ke konsol, karena escape sequence akan digunakan secara aktif. Opsi -s -S
meningkatkan gdbserver dan membuat VM untuk jeda, masing-masing. Anda dapat mengunduh kode menggunakan loader
, tetapi Anda harus memulai ulang QEMU setiap saat.
Anda dapat terhubung menggunakan
riscv64-unknown-elf-gdb \ -ex 'target remote 127.0.0.1:1234' \ -ex 'restore /path/to/on.dtb binary 0x80100000' \ -ex 'restore /path/to/memtest_shared.bin binary 0x80020000' \ -ex 'add-symbol-file memtest_shared 0x80100000' \ -ex 'set $a1=0x80020000' \ -ex 'set $pc=0x80100000'
Hasilnya, semuanya bekerja lebih dari sekadar cerdas!
Prinsip kerja umum
, , , Memtest86+ btrace
, , ( , QEMU):

, , memtest . , (, trap): , , QEMU - ! «» Illegal instruction
, . mcause
(?), — mepc
(?), — mtval
( ?), .

, :
head.S:
# # = 0 --- , # , , ... lla t1, _trap_entry csrw mtvec, t1 # ... _trap_entry: csrr a0, mcause csrr a1, mepc csrr a2, mtval jal riscv_trap_entry
, calling convention, . memtest, HiFive_U-Boot, Volume II
:
arch.c:
static const char *errors[] = { "Instruction address misaligned", "Instruction access fault", "Illegal instruction", "Breakpoint", "Load address misaligned", "Load access fault", "Store/AMO address misaligned", "Store/AMO access fault", ^_^quot quot^_^, ^_^quot quot^_^, ^_^quot quot^_^, ^_^quot quot^_^, "Instruction page fault", "Load page fault", ^_^quot quot^_^, "Store/AMO page fault", }; void riscv_trap_entry(ulong cause, ulong epc, ulong tval) { char buf[32]; cprint(12, 0, "EXCP: "); if (cause < sizeof(errors) / sizeof(errors[0])) { cprint(12, 8, errors[cause]); } else { itoa(buf, cause); cprint(12, 8, buf); } cprint(13, 0, "PC: "); hprint3(13, 8, epc, 8); cprint(14, 0, "Addr: "); hprint3(14, 8, tval, 8); HALT(); }
— « » . , «» , , , .
: . , memtest : : « , , . ». : do_test
main.c
2, ( ), — «» , memtest. , run_at
, memtest _start
_end
( «» ), - spinlock' goto *addr;
. , , «» , «».
, bss
— _dl_start
, riscv_entry
, trap entry. , : L1I-, . , fence.i
.
, Memtest86+ — , barrier_s
. , . , , .
, : . : . : , - (Own Address, ) . , , . . - . , x86 , , uint64_t
0x80000002
. , : , load/store x86 , — . , QEMU , « , ».
, , — unaligned access ..
, , RocketChip, — QEMU, , , RocketChip — unaligned access trap, QEMU « ».
«misaligned» ,
Changed description of misaligned load and store behavior. The specification now allows visible misaligned address traps in execution environment interfaces, rather than just mandating invisible handling of misaligned loads and stores in user mode. Also, now allows access exceptions to be reported for misaligned accesses (including atomics) that should not be emulated.
, , — , user-mode code , . . , , . , — - machine mode . , rdtsc
(x86) rdtime
(rv64), trap, . , , memory-mapped .
: , low_test_addr
( ), , fdt . , , low_test_addr
, , 2 high_test_adr
… , — : head.S
initial_load_addr
, riscv_entry
move_to_correct_addr
:
static void move_to_correct_addr(void) { uintptr_t cur_start = (uintptr_t)&_start; uintptr_t cur_end = (uintptr_t)&_end; if (cur_start == low_test_addr || cur_start == high_test_adr) {
, — , memtest , RAM - . RISC-V , v->plim_lower
.
, «» , -, — test.c
ulong
( unsigneg long
), 32- x86 uint32_t
, « 64 » uint64_t
. «!!! Good: ffffffff Real: ffffffff Bad bits: 00000000». ? - -1, 32 1. , , 0… , : , ulong
( uint32_t
), ( uintptr_t
). , . , uint64_t
4. RISC-V , C, , — UB. memtest UBSan. , , UBSan trap-on-error JTAG.
, memtest - , , U-Boot.
: mkimage
U-Boot Linux :
mkimage -A riscv -O linux -T kernel -C none \ -a 0x80000000 -e 0x80000000 \ -n memtest -d memtest.bin memtest.uboot
SD-
run mmcsetup; run fdtsetup; fdt set /chosen bootargs "console=ttyS0"; fatload mmc 0:1 82000000 memtest.uboot; bootm fdt; bootm 82000000 - ${fdtaddr}
( , run
— ).
: FDT: 0xbffb7c80
. , : ffffffff
, . , ( ), : HiFive_U-Boot :
theKernel(machid, (unsigned long)images->ft_addr);
,
void (*theKernel)(int arch, uint params);
, , , , 32 , head.S
:
li t0, 0xffffffffL and a1, a1, t0
, , - , , , :
- x86. — review
- SMP RISC-V
arch/
-test.c
RISC-V ( -O0
!)