Repository: s-matyukevich/raspberry-pi-os Branch: master Commit: 31fc1481f529 Files: 2526 Total size: 2.7 MB Directory structure: gitextract_cgeajq_d/ ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── _config.yml ├── _layouts/ │ └── default.html ├── assets/ │ └── css/ │ └── style.scss ├── docs/ │ ├── Contributions.md │ ├── Introduction.md │ ├── Prerequisites.md │ ├── lesson01/ │ │ ├── exercises.md │ │ ├── linux/ │ │ │ ├── build-system.md │ │ │ ├── kernel-startup.md │ │ │ └── project-structure.md │ │ └── rpi-os.md │ ├── lesson02/ │ │ ├── exercises.md │ │ ├── linux.md │ │ └── rpi-os.md │ ├── lesson03/ │ │ ├── exercises.md │ │ ├── linux/ │ │ │ ├── interrupt_controllers.md │ │ │ ├── low_level-exception_handling.md │ │ │ └── timer.md │ │ └── rpi-os.md │ ├── lesson04/ │ │ ├── exercises.md │ │ ├── linux/ │ │ │ ├── basic_structures.md │ │ │ ├── fork.md │ │ │ └── scheduler.md │ │ └── rpi-os.md │ ├── lesson05/ │ │ ├── exercises.md │ │ ├── linux.md │ │ └── rpi-os.md │ └── lesson06/ │ ├── exercises.md │ └── rpi-os.md ├── exercises/ │ ├── lesson01/ │ │ ├── 1/ │ │ │ ├── H-4ND-H/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── mini_uart.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ └── utils.S │ │ │ ├── a-v-v/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── mini_uart.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ └── utils.S │ │ │ ├── adkaster/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── mini_uart.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ └── utils.S │ │ │ ├── avenito/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── mini_uart.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ └── utils.S │ │ │ ├── bl4ckout31/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── mini_uart.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ └── utils.S │ │ │ ├── evopen/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.sh │ │ │ │ ├── config.txt │ │ │ │ ├── include/ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── mini_uart.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── kernel_main.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ └── utils.S │ │ │ ├── gcrisis/ │ │ │ │ ├── Makefile │ │ │ │ ├── include/ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── mini_uart.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ └── utils.S │ │ │ ├── rs/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── mini_uart.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ └── utils.S │ │ │ ├── stefanji/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── mini_uart.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ └── utils.S │ │ │ ├── xdfm1/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── mini_uart.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ └── utils.S │ │ │ └── zjd0112/ │ │ │ ├── Makefile │ │ │ ├── build.bat │ │ │ ├── build.sh │ │ │ ├── include/ │ │ │ │ ├── mini_uart.h │ │ │ │ ├── mm.h │ │ │ │ ├── peripherals/ │ │ │ │ │ ├── base.h │ │ │ │ │ ├── gpio.h │ │ │ │ │ └── mini_uart.h │ │ │ │ └── utils.h │ │ │ └── src/ │ │ │ ├── boot.S │ │ │ ├── config.txt │ │ │ ├── kernel.c │ │ │ ├── linker.ld │ │ │ ├── mini_uart.c │ │ │ ├── mm.S │ │ │ └── utils.S │ │ ├── 2/ │ │ │ ├── H-4ND-H/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── uart_pl011.h │ │ │ │ │ ├── uart_pl011.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mm.S │ │ │ │ ├── uart_pl011.c │ │ │ │ └── utils.S │ │ │ ├── a-v-v/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── uart.h │ │ │ │ │ ├── uart.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mm.S │ │ │ │ ├── uart.c │ │ │ │ └── utils.S │ │ │ ├── adkaster/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ │ └── uart.h │ │ │ │ │ ├── uart.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ ├── uart.c │ │ │ │ └── utils.S │ │ │ ├── avenito/ │ │ │ │ ├── Makefile │ │ │ │ ├── README.md │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── uart.h │ │ │ │ │ ├── uart.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mm.S │ │ │ │ ├── uart.c │ │ │ │ └── utils.S │ │ │ ├── bl4ckout31/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── uart.h │ │ │ │ │ ├── uart.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mm.S │ │ │ │ ├── uart.c │ │ │ │ └── utils.S │ │ │ ├── evopen/ │ │ │ │ ├── .gitignore │ │ │ │ ├── Makefile │ │ │ │ ├── build.sh │ │ │ │ ├── config.txt │ │ │ │ ├── include/ │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ │ └── uart.h │ │ │ │ │ ├── uart.h │ │ │ │ │ └── utils.h │ │ │ │ ├── src/ │ │ │ │ │ ├── boot.S │ │ │ │ │ ├── kernel.c │ │ │ │ │ ├── linker-qemu.ld │ │ │ │ │ ├── linker.ld │ │ │ │ │ ├── mm.S │ │ │ │ │ ├── uart.c │ │ │ │ │ └── utils.S │ │ │ │ └── start.sh │ │ │ ├── gcrisis/ │ │ │ │ ├── Makefile │ │ │ │ ├── include/ │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── uart.h │ │ │ │ │ ├── uart.h │ │ │ │ │ └── utils.h │ │ │ │ ├── src/ │ │ │ │ │ ├── boot.S │ │ │ │ │ ├── config.txt │ │ │ │ │ ├── kernel.c │ │ │ │ │ ├── linker.ld │ │ │ │ │ ├── mm.S │ │ │ │ │ ├── uart.c │ │ │ │ │ └── utils.S │ │ │ │ └── uart-boot/ │ │ │ │ ├── Makefile │ │ │ │ ├── downloader/ │ │ │ │ │ └── raspiloader.c │ │ │ │ ├── include/ │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── uart.h │ │ │ │ │ ├── uart.h │ │ │ │ │ └── utils.h │ │ │ │ ├── readme │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── linker.ld │ │ │ │ ├── main.c │ │ │ │ ├── mm.S │ │ │ │ ├── uart.c │ │ │ │ └── utils.S │ │ │ ├── rs/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── uart.h │ │ │ │ │ ├── uart.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mm.S │ │ │ │ ├── uart.c │ │ │ │ └── utils.S │ │ │ ├── stefanji/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── uart_pl011.h │ │ │ │ │ ├── pl011_uart.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mm.S │ │ │ │ ├── pl011_uart.c │ │ │ │ └── utils.S │ │ │ ├── xdfm1/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── uart.h │ │ │ │ │ ├── uart.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mm.S │ │ │ │ ├── uart.c │ │ │ │ └── utils.S │ │ │ └── zjd0112/ │ │ │ ├── Makefile │ │ │ ├── build.bat │ │ │ ├── build.sh │ │ │ ├── include/ │ │ │ │ ├── mini_uart.h │ │ │ │ ├── mm.h │ │ │ │ ├── peripherals/ │ │ │ │ │ ├── base.h │ │ │ │ │ ├── gpio.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ └── pl011_uart.h │ │ │ │ ├── pl011_uart.h │ │ │ │ └── utils.h │ │ │ └── src/ │ │ │ ├── boot.S │ │ │ ├── config.txt │ │ │ ├── kernel.c │ │ │ ├── linker.ld │ │ │ ├── mini_uart.c │ │ │ ├── mm.S │ │ │ ├── pl011_uart.c │ │ │ └── utils.S │ │ ├── 3/ │ │ │ ├── a-v-v/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── mini_uart.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ └── utils.S │ │ │ ├── adkaster/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── mini_uart.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ └── utils.S │ │ │ ├── avenito/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── mini_uart.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ └── utils.S │ │ │ ├── bl4ckout31/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── mini_uart.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ └── utils.S │ │ │ ├── evopen/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.sh │ │ │ │ ├── config.txt │ │ │ │ ├── include/ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── mini_uart.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ └── utils.S │ │ │ ├── gcrisis/ │ │ │ │ ├── Makefile │ │ │ │ ├── include/ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── mini_uart.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ └── utils.S │ │ │ ├── rs/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── mini_uart.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ └── utils.S │ │ │ ├── stefanji/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── mini_uart.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ └── utils.S │ │ │ ├── szediwy/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── mini_uart.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ └── utils.S │ │ │ └── zjd0112/ │ │ │ ├── Makefile │ │ │ ├── build.bat │ │ │ ├── build.sh │ │ │ ├── include/ │ │ │ │ ├── mini_uart.h │ │ │ │ ├── mm.h │ │ │ │ ├── peripherals/ │ │ │ │ │ ├── base.h │ │ │ │ │ ├── gpio.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ └── pl011_uart.h │ │ │ │ ├── pl011_uart.h │ │ │ │ └── utils.h │ │ │ └── src/ │ │ │ ├── boot.S │ │ │ ├── config.txt │ │ │ ├── kernel.c │ │ │ ├── linker.ld │ │ │ ├── mini_uart.c │ │ │ ├── mm.S │ │ │ ├── pl011_uart.c │ │ │ └── utils.S │ │ └── 4/ │ │ ├── H-4ND-H/ │ │ │ ├── Makefile │ │ │ ├── build.bat │ │ │ ├── build.sh │ │ │ ├── include/ │ │ │ │ ├── mini_uart.h │ │ │ │ ├── mm.h │ │ │ │ ├── peripherals/ │ │ │ │ │ ├── base.h │ │ │ │ │ ├── gpio.h │ │ │ │ │ └── mini_uart.h │ │ │ │ └── utils.h │ │ │ └── src/ │ │ │ ├── boot.S │ │ │ ├── config.txt │ │ │ ├── kernel.c │ │ │ ├── linker.ld │ │ │ ├── mini_uart.c │ │ │ ├── mm.S │ │ │ └── utils.S │ │ ├── a-v-v/ │ │ │ ├── Makefile │ │ │ ├── build.bat │ │ │ ├── build.sh │ │ │ ├── include/ │ │ │ │ ├── mini_uart.h │ │ │ │ ├── mm.h │ │ │ │ ├── peripherals/ │ │ │ │ │ ├── base.h │ │ │ │ │ ├── gpio.h │ │ │ │ │ └── mini_uart.h │ │ │ │ └── utils.h │ │ │ ├── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ └── utils.S │ │ │ └── start_qemu.sh │ │ ├── avenito/ │ │ │ ├── Makefile │ │ │ ├── build.bat │ │ │ ├── build.sh │ │ │ ├── include/ │ │ │ │ ├── mm.h │ │ │ │ ├── peripherals/ │ │ │ │ │ ├── base.h │ │ │ │ │ ├── gpio.h │ │ │ │ │ └── uart.h │ │ │ │ ├── uart.h │ │ │ │ └── utils.h │ │ │ └── src/ │ │ │ ├── boot.S │ │ │ ├── config.txt │ │ │ ├── kernel.c │ │ │ ├── linker.ld │ │ │ ├── mm.S │ │ │ ├── uart.c │ │ │ └── utils.S │ │ ├── bl4ckout31/ │ │ │ ├── Makefile │ │ │ ├── build.bat │ │ │ ├── build.sh │ │ │ ├── include/ │ │ │ │ ├── mm.h │ │ │ │ ├── peripherals/ │ │ │ │ │ ├── base.h │ │ │ │ │ ├── gpio.h │ │ │ │ │ └── uart.h │ │ │ │ ├── uart.h │ │ │ │ └── utils.h │ │ │ ├── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mm.S │ │ │ │ ├── uart.c │ │ │ │ └── utils.S │ │ │ └── start.sh │ │ ├── evopen/ │ │ │ ├── .gitignore │ │ │ ├── Makefile │ │ │ ├── build.sh │ │ │ ├── config.txt │ │ │ ├── include/ │ │ │ │ ├── mm.h │ │ │ │ ├── peripherals/ │ │ │ │ │ ├── base.h │ │ │ │ │ ├── gpio.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ └── uart.h │ │ │ │ ├── uart.h │ │ │ │ └── utils.h │ │ │ ├── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── kernel.c │ │ │ │ ├── linker-qemu.ld │ │ │ │ ├── linker.ld │ │ │ │ ├── mm.S │ │ │ │ ├── uart.c │ │ │ │ └── utils.S │ │ │ └── start.sh │ │ ├── rs/ │ │ │ ├── Makefile │ │ │ ├── build.bat │ │ │ ├── build.sh │ │ │ ├── include/ │ │ │ │ ├── mini_uart.h │ │ │ │ ├── mm.h │ │ │ │ ├── peripherals/ │ │ │ │ │ ├── base.h │ │ │ │ │ ├── gpio.h │ │ │ │ │ └── mini_uart.h │ │ │ │ └── utils.h │ │ │ ├── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ └── utils.S │ │ │ └── start.sh │ │ └── stefanji/ │ │ └── run_on_qemu.sh │ ├── lesson02/ │ │ ├── 1/ │ │ │ ├── H-4ND-H/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── arm/ │ │ │ │ │ │ └── sysregs.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── mini_uart.h │ │ │ │ │ ├── printf.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ ├── printf.c │ │ │ │ └── utils.S │ │ │ ├── avenito/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── arm/ │ │ │ │ │ │ └── sysregs.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── mini_uart.h │ │ │ │ │ ├── printf.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ ├── printf.c │ │ │ │ └── utils.S │ │ │ ├── bl4ckout31/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── arm/ │ │ │ │ │ │ └── sysregs.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── mini_uart.h │ │ │ │ │ ├── printf.h │ │ │ │ │ └── utils.h │ │ │ │ ├── src/ │ │ │ │ │ ├── boot.S │ │ │ │ │ ├── config.txt │ │ │ │ │ ├── kernel.c │ │ │ │ │ ├── linker.ld │ │ │ │ │ ├── mini_uart.c │ │ │ │ │ ├── mm.S │ │ │ │ │ ├── printf.c │ │ │ │ │ └── utils.S │ │ │ │ └── start.sh │ │ │ ├── evopen/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── config.txt │ │ │ │ ├── include/ │ │ │ │ │ ├── arm/ │ │ │ │ │ │ └── sysregs.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── mini_uart.h │ │ │ │ │ ├── printf.h │ │ │ │ │ └── utils.h │ │ │ │ ├── src/ │ │ │ │ │ ├── boot.S │ │ │ │ │ ├── config.txt │ │ │ │ │ ├── kernel.c │ │ │ │ │ ├── linker.ld │ │ │ │ │ ├── mini_uart.c │ │ │ │ │ ├── mm.S │ │ │ │ │ ├── printf.c │ │ │ │ │ └── utils.S │ │ │ │ └── start.sh │ │ │ ├── gcrisis/ │ │ │ │ ├── Makefile │ │ │ │ ├── include/ │ │ │ │ │ ├── arm/ │ │ │ │ │ │ └── sysregs.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── mini_uart.h │ │ │ │ │ ├── printf.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ ├── printf.c │ │ │ │ └── utils.S │ │ │ ├── rs/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── arm/ │ │ │ │ │ │ └── sysregs.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── mini_uart.h │ │ │ │ │ ├── printf.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ ├── printf.c │ │ │ │ └── utils.S │ │ │ └── zjd0112/ │ │ │ ├── Makefile │ │ │ ├── build.bat │ │ │ ├── build.sh │ │ │ ├── include/ │ │ │ │ ├── arm/ │ │ │ │ │ └── sysregs.h │ │ │ │ ├── mini_uart.h │ │ │ │ ├── mm.h │ │ │ │ ├── peripherals/ │ │ │ │ │ ├── base.h │ │ │ │ │ ├── gpio.h │ │ │ │ │ └── mini_uart.h │ │ │ │ ├── printf.h │ │ │ │ └── utils.h │ │ │ └── src/ │ │ │ ├── boot.S │ │ │ ├── config.txt │ │ │ ├── kernel.c │ │ │ ├── linker.ld │ │ │ ├── mini_uart.c │ │ │ ├── mm.S │ │ │ ├── printf.c │ │ │ └── utils.S │ │ ├── 2/ │ │ │ ├── H-4ND-H/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── arm/ │ │ │ │ │ │ └── sysregs.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── mini_uart.h │ │ │ │ │ ├── printf.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ ├── printf.c │ │ │ │ └── utils.S │ │ │ ├── avenito/ │ │ │ │ ├── Makefile │ │ │ │ ├── README.md │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── dis_with.txt │ │ │ │ ├── dis_without.txt │ │ │ │ ├── include/ │ │ │ │ │ ├── arm/ │ │ │ │ │ │ └── sysregs.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── mini_uart.h │ │ │ │ │ ├── printf.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ ├── printf.c │ │ │ │ └── utils.S │ │ │ ├── bl4ckout31/ │ │ │ │ ├── Makefile │ │ │ │ ├── avec │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── arm/ │ │ │ │ │ │ └── sysregs.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── mini_uart.h │ │ │ │ │ ├── printf.h │ │ │ │ │ └── utils.h │ │ │ │ ├── sans │ │ │ │ ├── src/ │ │ │ │ │ ├── boot.S │ │ │ │ │ ├── config.txt │ │ │ │ │ ├── kernel.c │ │ │ │ │ ├── linker.ld │ │ │ │ │ ├── mini_uart.c │ │ │ │ │ ├── mm.S │ │ │ │ │ ├── printf.c │ │ │ │ │ └── utils.S │ │ │ │ └── start.sh │ │ │ ├── evopen/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── config.txt │ │ │ │ ├── include/ │ │ │ │ │ ├── arm/ │ │ │ │ │ │ └── sysregs.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── mini_uart.h │ │ │ │ │ ├── printf.h │ │ │ │ │ └── utils.h │ │ │ │ ├── src/ │ │ │ │ │ ├── boot.S │ │ │ │ │ ├── config.txt │ │ │ │ │ ├── kernel.c │ │ │ │ │ ├── linker.ld │ │ │ │ │ ├── mini_uart.c │ │ │ │ │ ├── mm.S │ │ │ │ │ ├── printf.c │ │ │ │ │ └── utils.S │ │ │ │ └── start.sh │ │ │ ├── gcrisis/ │ │ │ │ ├── Makefile │ │ │ │ ├── include/ │ │ │ │ │ ├── arm/ │ │ │ │ │ │ └── sysregs.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── mini_uart.h │ │ │ │ │ ├── printf.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ ├── printf.c │ │ │ │ └── utils.S │ │ │ ├── rs/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── arm/ │ │ │ │ │ │ └── sysregs.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ └── mini_uart.h │ │ │ │ │ ├── printf.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ ├── printf.c │ │ │ │ └── utils.S │ │ │ ├── szediwy/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── custom_printf.h │ │ │ │ │ ├── entry.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ ├── irq.h │ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ │ └── timer.h │ │ │ │ │ ├── sysregs.h │ │ │ │ │ ├── timer.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── custom_printf.c │ │ │ │ ├── entry.S │ │ │ │ ├── irq.S │ │ │ │ ├── irq.c │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ ├── timer.c │ │ │ │ └── utils.S │ │ │ └── zjd0112/ │ │ │ ├── Makefile │ │ │ ├── build.bat │ │ │ ├── build.sh │ │ │ ├── include/ │ │ │ │ ├── arm/ │ │ │ │ │ └── sysregs.h │ │ │ │ ├── mini_uart.h │ │ │ │ ├── mm.h │ │ │ │ ├── peripherals/ │ │ │ │ │ ├── base.h │ │ │ │ │ ├── gpio.h │ │ │ │ │ └── mini_uart.h │ │ │ │ ├── printf.h │ │ │ │ └── utils.h │ │ │ ├── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ ├── printf.c │ │ │ │ └── utils.S │ │ │ ├── test1.txt │ │ │ └── test2.txt │ │ └── 3/ │ │ ├── H-4ND-H/ │ │ │ ├── Makefile │ │ │ ├── build.bat │ │ │ ├── build.sh │ │ │ ├── include/ │ │ │ │ ├── arm/ │ │ │ │ │ └── sysregs.h │ │ │ │ ├── mini_uart.h │ │ │ │ ├── mm.h │ │ │ │ ├── peripherals/ │ │ │ │ │ ├── base.h │ │ │ │ │ ├── gpio.h │ │ │ │ │ └── mini_uart.h │ │ │ │ ├── printf.h │ │ │ │ └── utils.h │ │ │ └── src/ │ │ │ ├── boot.S │ │ │ ├── config.txt │ │ │ ├── kernel.c │ │ │ ├── linker.ld │ │ │ ├── mini_uart.c │ │ │ ├── mm.S │ │ │ ├── printf.c │ │ │ └── utils.S │ │ ├── avenito/ │ │ │ ├── Makefile │ │ │ ├── README.md │ │ │ ├── build.bat │ │ │ ├── build.sh │ │ │ ├── include/ │ │ │ │ ├── arm/ │ │ │ │ │ └── sysregs.h │ │ │ │ ├── mm.h │ │ │ │ ├── peripherals/ │ │ │ │ │ ├── base.h │ │ │ │ │ ├── gpio.h │ │ │ │ │ └── uart.h │ │ │ │ ├── printf.h │ │ │ │ ├── uart.h │ │ │ │ └── utils.h │ │ │ └── src/ │ │ │ ├── boot.S │ │ │ ├── config.txt │ │ │ ├── kernel.c │ │ │ ├── linker.ld │ │ │ ├── mm.S │ │ │ ├── printf.c │ │ │ ├── uart.c │ │ │ └── utils.S │ │ ├── bl4ckout31/ │ │ │ ├── Makefile │ │ │ ├── build.bat │ │ │ ├── build.sh │ │ │ ├── include/ │ │ │ │ ├── arm/ │ │ │ │ │ └── sysregs.h │ │ │ │ ├── mini_uart.h │ │ │ │ ├── mm.h │ │ │ │ ├── peripherals/ │ │ │ │ │ ├── base.h │ │ │ │ │ ├── gpio.h │ │ │ │ │ └── mini_uart.h │ │ │ │ ├── printf.h │ │ │ │ └── utils.h │ │ │ ├── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ ├── printf.c │ │ │ │ └── utils.S │ │ │ └── start.sh │ │ ├── evopen/ │ │ │ ├── Makefile │ │ │ ├── build.sh │ │ │ ├── config.txt │ │ │ ├── include/ │ │ │ │ ├── arm/ │ │ │ │ │ └── sysregs.h │ │ │ │ ├── mm.h │ │ │ │ ├── peripherals/ │ │ │ │ │ ├── base.h │ │ │ │ │ ├── gpio.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ └── uart.h │ │ │ │ ├── printf.h │ │ │ │ ├── uart.h │ │ │ │ └── utils.h │ │ │ ├── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── kernel.c │ │ │ │ ├── linker-qemu.ld │ │ │ │ ├── linker.ld │ │ │ │ ├── mm.S │ │ │ │ ├── printf.c │ │ │ │ ├── uart.c │ │ │ │ └── utils.S │ │ │ └── start.sh │ │ └── rs/ │ │ ├── Makefile │ │ ├── build.bat │ │ ├── build.sh │ │ ├── include/ │ │ │ ├── arm/ │ │ │ │ └── sysregs.h │ │ │ ├── mini_uart.h │ │ │ ├── mm.h │ │ │ ├── peripherals/ │ │ │ │ ├── base.h │ │ │ │ ├── gpio.h │ │ │ │ └── mini_uart.h │ │ │ ├── printf.h │ │ │ └── utils.h │ │ ├── src/ │ │ │ ├── boot.S │ │ │ ├── config.txt │ │ │ ├── kernel.c │ │ │ ├── linker.ld │ │ │ ├── mini_uart.c │ │ │ ├── mm.S │ │ │ ├── printf.c │ │ │ └── utils.S │ │ └── start.sh │ ├── lesson03/ │ │ ├── 1/ │ │ │ ├── H-4ND-H/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── arm/ │ │ │ │ │ │ └── sysregs.h │ │ │ │ │ ├── entry.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── local_timer.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ ├── irq.h │ │ │ │ │ │ ├── local_timer.h │ │ │ │ │ │ └── mini_uart.h │ │ │ │ │ ├── printf.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── entry.S │ │ │ │ ├── irq.S │ │ │ │ ├── irq.c │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── local_timer.c │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ ├── printf.c │ │ │ │ └── utils.S │ │ │ ├── avenito/ │ │ │ │ ├── Makefile │ │ │ │ ├── README.md │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── arm/ │ │ │ │ │ │ └── sysregs.h │ │ │ │ │ ├── entry.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ ├── irq.h │ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ │ └── timer.h │ │ │ │ │ ├── printf.h │ │ │ │ │ ├── timer.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── entry.S │ │ │ │ ├── irq.S │ │ │ │ ├── irq.c │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ ├── printf.c │ │ │ │ ├── timer.c │ │ │ │ └── utils.S │ │ │ ├── bl4ckout31/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── arm/ │ │ │ │ │ │ └── sysregs.h │ │ │ │ │ ├── entry.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ ├── irq.h │ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ │ └── timer.h │ │ │ │ │ ├── printf.h │ │ │ │ │ ├── timer.h │ │ │ │ │ └── utils.h │ │ │ │ ├── src/ │ │ │ │ │ ├── boot.S │ │ │ │ │ ├── config.txt │ │ │ │ │ ├── entry.S │ │ │ │ │ ├── irq.S │ │ │ │ │ ├── irq.c │ │ │ │ │ ├── kernel.c │ │ │ │ │ ├── linker.ld │ │ │ │ │ ├── mini_uart.c │ │ │ │ │ ├── mm.S │ │ │ │ │ ├── printf.c │ │ │ │ │ ├── timer.c │ │ │ │ │ └── utils.S │ │ │ │ └── start.sh │ │ │ ├── rs/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── arm/ │ │ │ │ │ │ └── sysregs.h │ │ │ │ │ ├── entry.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ ├── irq.h │ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ │ └── timer.h │ │ │ │ │ ├── printf.h │ │ │ │ │ ├── timer.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── entry.S │ │ │ │ ├── irq.S │ │ │ │ ├── irq.c │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ ├── printf.c │ │ │ │ ├── timer.c │ │ │ │ └── utils.S │ │ │ ├── szediwy/ │ │ │ │ ├── Makefile │ │ │ │ ├── README.md │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── custom_printf.h │ │ │ │ │ ├── entry.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ ├── irq.h │ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ │ └── timer.h │ │ │ │ │ ├── sysregs.h │ │ │ │ │ ├── timer.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── custom_printf.c │ │ │ │ ├── debug.c │ │ │ │ ├── entry.S │ │ │ │ ├── irq.S │ │ │ │ ├── irq.c │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ ├── timer.c │ │ │ │ └── utils.S │ │ │ └── xdfm1/ │ │ │ ├── Makefile │ │ │ ├── build.bat │ │ │ ├── build.sh │ │ │ ├── include/ │ │ │ │ ├── arm/ │ │ │ │ │ └── sysregs.h │ │ │ │ ├── arm_timer.h │ │ │ │ ├── entry.h │ │ │ │ ├── irq.h │ │ │ │ ├── mini_uart.h │ │ │ │ ├── mm.h │ │ │ │ ├── peripherals/ │ │ │ │ │ ├── arm_timer.h │ │ │ │ │ ├── base.h │ │ │ │ │ ├── gpio.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ └── timer.h │ │ │ │ ├── printf.h │ │ │ │ └── utils.h │ │ │ └── src/ │ │ │ ├── arm_timer.c │ │ │ ├── boot.S │ │ │ ├── config.txt │ │ │ ├── entry.S │ │ │ ├── irq.S │ │ │ ├── irq.c │ │ │ ├── kernel.c │ │ │ ├── linker.ld │ │ │ ├── mini_uart.c │ │ │ ├── mm.S │ │ │ ├── printf.c │ │ │ └── utils.S │ │ ├── 2/ │ │ │ ├── H-4ND-H/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── arm/ │ │ │ │ │ │ └── sysregs.h │ │ │ │ │ ├── entry.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ ├── irq.h │ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ │ └── timer.h │ │ │ │ │ ├── printf.h │ │ │ │ │ ├── timer.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── entry.S │ │ │ │ ├── irq.S │ │ │ │ ├── irq.c │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ ├── printf.c │ │ │ │ ├── timer.c │ │ │ │ └── utils.S │ │ │ ├── avenito/ │ │ │ │ ├── Makefile │ │ │ │ ├── README.md │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── arm/ │ │ │ │ │ │ └── sysregs.h │ │ │ │ │ ├── entry.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ ├── irq.h │ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ │ └── timer.h │ │ │ │ │ ├── printf.h │ │ │ │ │ ├── timer.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── entry.S │ │ │ │ ├── irq.S │ │ │ │ ├── irq.c │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ ├── printf.c │ │ │ │ ├── timer.c │ │ │ │ └── utils.S │ │ │ ├── bl4ckout31/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── arm/ │ │ │ │ │ │ └── sysregs.h │ │ │ │ │ ├── entry.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ ├── irq.h │ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ │ └── timer.h │ │ │ │ │ ├── printf.h │ │ │ │ │ ├── timer.h │ │ │ │ │ └── utils.h │ │ │ │ ├── src/ │ │ │ │ │ ├── boot.S │ │ │ │ │ ├── config.txt │ │ │ │ │ ├── entry.S │ │ │ │ │ ├── irq.S │ │ │ │ │ ├── irq.c │ │ │ │ │ ├── kernel.c │ │ │ │ │ ├── linker.ld │ │ │ │ │ ├── mini_uart.c │ │ │ │ │ ├── mm.S │ │ │ │ │ ├── printf.c │ │ │ │ │ ├── timer.c │ │ │ │ │ └── utils.S │ │ │ │ └── start.sh │ │ │ ├── rs/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── arm/ │ │ │ │ │ │ └── sysregs.h │ │ │ │ │ ├── entry.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ ├── irq.h │ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ │ └── timer.h │ │ │ │ │ ├── printf.h │ │ │ │ │ ├── timer.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── entry.S │ │ │ │ ├── irq.S │ │ │ │ ├── irq.c │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ ├── printf.c │ │ │ │ ├── timer.c │ │ │ │ └── utils.S │ │ │ └── szediwy/ │ │ │ ├── Makefile │ │ │ ├── README.md │ │ │ ├── build.bat │ │ │ ├── build.sh │ │ │ ├── include/ │ │ │ │ ├── custom_printf.h │ │ │ │ ├── entry.h │ │ │ │ ├── irq.h │ │ │ │ ├── mini_uart.h │ │ │ │ ├── mm.h │ │ │ │ ├── peripherals/ │ │ │ │ │ ├── base.h │ │ │ │ │ ├── gpio.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ └── timer.h │ │ │ │ ├── sysregs.h │ │ │ │ ├── timer.h │ │ │ │ └── utils.h │ │ │ └── src/ │ │ │ ├── boot.S │ │ │ ├── config.txt │ │ │ ├── custom_printf.c │ │ │ ├── debug.c │ │ │ ├── entry.S │ │ │ ├── irq.S │ │ │ ├── irq.c │ │ │ ├── kernel.c │ │ │ ├── linker.ld │ │ │ ├── mini_uart.c │ │ │ ├── mm.S │ │ │ ├── timer.c │ │ │ └── utils.S │ │ └── 3/ │ │ ├── H-4ND-H/ │ │ │ ├── Makefile │ │ │ ├── build.bat │ │ │ ├── build.sh │ │ │ ├── include/ │ │ │ │ ├── arm/ │ │ │ │ │ └── sysregs.h │ │ │ │ ├── entry.h │ │ │ │ ├── irq.h │ │ │ │ ├── mini_uart.h │ │ │ │ ├── mm.h │ │ │ │ ├── peripherals/ │ │ │ │ │ ├── base.h │ │ │ │ │ ├── gpio.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ └── timer.h │ │ │ │ ├── printf.h │ │ │ │ ├── timer.h │ │ │ │ └── utils.h │ │ │ └── src/ │ │ │ ├── boot.S │ │ │ ├── config.txt │ │ │ ├── entry.S │ │ │ ├── irq.S │ │ │ ├── irq.c │ │ │ ├── kernel.c │ │ │ ├── linker.ld │ │ │ ├── mini_uart.c │ │ │ ├── mm.S │ │ │ ├── printf.c │ │ │ ├── timer.c │ │ │ └── utils.S │ │ ├── avenito/ │ │ │ ├── Makefile │ │ │ ├── build.bat │ │ │ ├── build.sh │ │ │ ├── include/ │ │ │ │ ├── arm/ │ │ │ │ │ └── sysregs.h │ │ │ │ ├── entry.h │ │ │ │ ├── irq.h │ │ │ │ ├── mm.h │ │ │ │ ├── peripherals/ │ │ │ │ │ ├── base.h │ │ │ │ │ ├── gpio.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── timer.h │ │ │ │ │ └── uart.h │ │ │ │ ├── printf.h │ │ │ │ ├── timer.h │ │ │ │ ├── uart.h │ │ │ │ └── utils.h │ │ │ └── src/ │ │ │ ├── boot.S │ │ │ ├── config.txt │ │ │ ├── entry.S │ │ │ ├── irq.S │ │ │ ├── irq.c │ │ │ ├── kernel.c │ │ │ ├── linker.ld │ │ │ ├── mm.S │ │ │ ├── printf.c │ │ │ ├── timer.c │ │ │ ├── uart.c │ │ │ └── utils.S │ │ ├── bl4ckout31/ │ │ │ ├── Makefile │ │ │ ├── build.bat │ │ │ ├── build.sh │ │ │ ├── include/ │ │ │ │ ├── arm/ │ │ │ │ │ └── sysregs.h │ │ │ │ ├── entry.h │ │ │ │ ├── irq.h │ │ │ │ ├── mini_uart.h │ │ │ │ ├── mm.h │ │ │ │ ├── peripherals/ │ │ │ │ │ ├── base.h │ │ │ │ │ ├── gpio.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ └── timer.h │ │ │ │ ├── printf.h │ │ │ │ ├── timer.h │ │ │ │ └── utils.h │ │ │ ├── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── entry.S │ │ │ │ ├── irq.S │ │ │ │ ├── irq.c │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ ├── printf.c │ │ │ │ ├── timer.S │ │ │ │ ├── timer.c │ │ │ │ └── utils.S │ │ │ └── start.sh │ │ └── rs/ │ │ ├── Makefile │ │ ├── build.bat │ │ ├── build.sh │ │ ├── include/ │ │ │ ├── arm/ │ │ │ │ └── sysregs.h │ │ │ ├── entry.h │ │ │ ├── irq.h │ │ │ ├── mini_uart.h │ │ │ ├── mm.h │ │ │ ├── peripherals/ │ │ │ │ ├── base.h │ │ │ │ ├── gpio.h │ │ │ │ ├── irq.h │ │ │ │ ├── mini_uart.h │ │ │ │ └── timer.h │ │ │ ├── printf.h │ │ │ ├── timer.h │ │ │ └── utils.h │ │ ├── src/ │ │ │ ├── boot.S │ │ │ ├── config.txt │ │ │ ├── entry.S │ │ │ ├── irq.S │ │ │ ├── irq.c │ │ │ ├── kernel.c │ │ │ ├── linker.ld │ │ │ ├── mini_uart.c │ │ │ ├── mm.S │ │ │ ├── printf.c │ │ │ ├── timer.c │ │ │ └── utils.S │ │ └── start.sh │ ├── lesson04/ │ │ ├── 1/ │ │ │ ├── avenito/ │ │ │ │ ├── Makefile │ │ │ │ ├── README.md │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── arm/ │ │ │ │ │ │ └── sysregs.h │ │ │ │ │ ├── entry.h │ │ │ │ │ ├── fork.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ ├── irq.h │ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ │ ├── timer.h │ │ │ │ │ │ └── uart.h │ │ │ │ │ ├── printf.h │ │ │ │ │ ├── sched.h │ │ │ │ │ ├── timer.h │ │ │ │ │ └── utils.h │ │ │ │ ├── saida.txt │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── entry.S │ │ │ │ ├── fork.c │ │ │ │ ├── irq.S │ │ │ │ ├── irq.c │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ ├── mm.c │ │ │ │ ├── printf.c │ │ │ │ ├── sched.S │ │ │ │ ├── sched.c │ │ │ │ ├── timer.c │ │ │ │ └── utils.S │ │ │ ├── bl4ckout31/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── arm/ │ │ │ │ │ │ └── sysregs.h │ │ │ │ │ ├── entry.h │ │ │ │ │ ├── fork.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ ├── irq.h │ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ │ └── timer.h │ │ │ │ │ ├── printf.h │ │ │ │ │ ├── sched.h │ │ │ │ │ ├── timer.h │ │ │ │ │ └── utils.h │ │ │ │ ├── src/ │ │ │ │ │ ├── boot.S │ │ │ │ │ ├── config.txt │ │ │ │ │ ├── entry.S │ │ │ │ │ ├── fork.c │ │ │ │ │ ├── irq.S │ │ │ │ │ ├── irq.c │ │ │ │ │ ├── kernel.c │ │ │ │ │ ├── linker.ld │ │ │ │ │ ├── mini_uart.c │ │ │ │ │ ├── mm.S │ │ │ │ │ ├── mm.c │ │ │ │ │ ├── printf.c │ │ │ │ │ ├── sched.S │ │ │ │ │ ├── sched.c │ │ │ │ │ ├── timer.c │ │ │ │ │ └── utils.S │ │ │ │ └── start.sh │ │ │ └── rs/ │ │ │ ├── Makefile │ │ │ ├── build.bat │ │ │ ├── build.sh │ │ │ ├── include/ │ │ │ │ ├── arm/ │ │ │ │ │ └── sysregs.h │ │ │ │ ├── entry.h │ │ │ │ ├── fork.h │ │ │ │ ├── irq.h │ │ │ │ ├── mini_uart.h │ │ │ │ ├── mm.h │ │ │ │ ├── peripherals/ │ │ │ │ │ ├── base.h │ │ │ │ │ ├── gpio.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ └── timer.h │ │ │ │ ├── printf.h │ │ │ │ ├── sched.h │ │ │ │ ├── timer.h │ │ │ │ └── utils.h │ │ │ ├── output.txt │ │ │ └── src/ │ │ │ ├── boot.S │ │ │ ├── config.txt │ │ │ ├── entry.S │ │ │ ├── fork.c │ │ │ ├── irq.S │ │ │ ├── irq.c │ │ │ ├── kernel.c │ │ │ ├── linker.ld │ │ │ ├── mini_uart.c │ │ │ ├── mm.S │ │ │ ├── mm.c │ │ │ ├── printf.c │ │ │ ├── sched.S │ │ │ ├── sched.c │ │ │ ├── timer.c │ │ │ └── utils.S │ │ ├── 2/ │ │ │ ├── avenito/ │ │ │ │ ├── Makefile │ │ │ │ ├── README.md │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── arm/ │ │ │ │ │ │ └── sysregs.h │ │ │ │ │ ├── entry.h │ │ │ │ │ ├── fork.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ ├── irq.h │ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ │ └── timer.h │ │ │ │ │ ├── printf.h │ │ │ │ │ ├── sched.h │ │ │ │ │ ├── timer.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── entry.S │ │ │ │ ├── fork.c │ │ │ │ ├── irq.S │ │ │ │ ├── irq.c │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ ├── mm.c │ │ │ │ ├── printf.c │ │ │ │ ├── sched.S │ │ │ │ ├── sched.c │ │ │ │ ├── timer.c │ │ │ │ └── utils.S │ │ │ ├── bl4ckout31/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── arm/ │ │ │ │ │ │ └── sysregs.h │ │ │ │ │ ├── entry.h │ │ │ │ │ ├── fork.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ ├── irq.h │ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ │ └── timer.h │ │ │ │ │ ├── printf.h │ │ │ │ │ ├── sched.h │ │ │ │ │ ├── timer.h │ │ │ │ │ └── utils.h │ │ │ │ ├── src/ │ │ │ │ │ ├── boot.S │ │ │ │ │ ├── config.txt │ │ │ │ │ ├── entry.S │ │ │ │ │ ├── fork.c │ │ │ │ │ ├── irq.S │ │ │ │ │ ├── irq.c │ │ │ │ │ ├── kernel.c │ │ │ │ │ ├── linker.ld │ │ │ │ │ ├── mini_uart.c │ │ │ │ │ ├── mm.S │ │ │ │ │ ├── mm.c │ │ │ │ │ ├── printf.c │ │ │ │ │ ├── sched.S │ │ │ │ │ ├── sched.c │ │ │ │ │ ├── timer.c │ │ │ │ │ └── utils.S │ │ │ │ └── start.sh │ │ │ └── rs/ │ │ │ ├── Makefile │ │ │ ├── build.bat │ │ │ ├── build.sh │ │ │ ├── include/ │ │ │ │ ├── arm/ │ │ │ │ │ └── sysregs.h │ │ │ │ ├── entry.h │ │ │ │ ├── fork.h │ │ │ │ ├── irq.h │ │ │ │ ├── mini_uart.h │ │ │ │ ├── mm.h │ │ │ │ ├── peripherals/ │ │ │ │ │ ├── base.h │ │ │ │ │ ├── gpio.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ └── timer.h │ │ │ │ ├── printf.h │ │ │ │ ├── sched.h │ │ │ │ ├── timer.h │ │ │ │ └── utils.h │ │ │ ├── output.txt │ │ │ └── src/ │ │ │ ├── boot.S │ │ │ ├── config.txt │ │ │ ├── entry.S │ │ │ ├── fork.c │ │ │ ├── irq.S │ │ │ ├── irq.c │ │ │ ├── kernel.c │ │ │ ├── linker.ld │ │ │ ├── mini_uart.c │ │ │ ├── mm.S │ │ │ ├── mm.c │ │ │ ├── printf.c │ │ │ ├── sched.S │ │ │ ├── sched.c │ │ │ ├── timer.c │ │ │ └── utils.S │ │ ├── 3/ │ │ │ ├── avenito/ │ │ │ │ ├── Makefile │ │ │ │ ├── README.md │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── file.txt │ │ │ │ ├── include/ │ │ │ │ │ ├── arm/ │ │ │ │ │ │ └── sysregs.h │ │ │ │ │ ├── entry.h │ │ │ │ │ ├── fork.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ ├── irq.h │ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ │ └── timer.h │ │ │ │ │ ├── printf.h │ │ │ │ │ ├── sched.h │ │ │ │ │ ├── timer.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── entry.S │ │ │ │ ├── fork.c │ │ │ │ ├── irq.S │ │ │ │ ├── irq.c │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ ├── mm.c │ │ │ │ ├── printf.c │ │ │ │ ├── sched.S │ │ │ │ ├── sched.c │ │ │ │ ├── timer.c │ │ │ │ └── utils.S │ │ │ └── bl4ckout31/ │ │ │ ├── Makefile │ │ │ ├── build.bat │ │ │ ├── build.sh │ │ │ ├── include/ │ │ │ │ ├── arm/ │ │ │ │ │ └── sysregs.h │ │ │ │ ├── entry.h │ │ │ │ ├── fork.h │ │ │ │ ├── irq.h │ │ │ │ ├── mini_uart.h │ │ │ │ ├── mm.h │ │ │ │ ├── peripherals/ │ │ │ │ │ ├── base.h │ │ │ │ │ ├── gpio.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ └── timer.h │ │ │ │ ├── printf.h │ │ │ │ ├── sched.h │ │ │ │ ├── timer.h │ │ │ │ └── utils.h │ │ │ ├── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── entry.S │ │ │ │ ├── fork.c │ │ │ │ ├── irq.S │ │ │ │ ├── irq.c │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ ├── mm.c │ │ │ │ ├── printf.c │ │ │ │ ├── sched.S │ │ │ │ ├── sched.c │ │ │ │ ├── timer.c │ │ │ │ └── utils.S │ │ │ └── start.sh │ │ ├── 4/ │ │ │ ├── avenito/ │ │ │ │ ├── Makefile │ │ │ │ ├── README.md │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── arm/ │ │ │ │ │ │ └── sysregs.h │ │ │ │ │ ├── entry.h │ │ │ │ │ ├── fork.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ ├── irq.h │ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ │ └── timer.h │ │ │ │ │ ├── printf.h │ │ │ │ │ ├── sched.h │ │ │ │ │ ├── timer.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── entry.S │ │ │ │ ├── fork.c │ │ │ │ ├── irq.S │ │ │ │ ├── irq.c │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ ├── mm.c │ │ │ │ ├── printf.c │ │ │ │ ├── sched.S │ │ │ │ ├── sched.c │ │ │ │ ├── timer.c │ │ │ │ └── utils.S │ │ │ ├── bl4ckout31/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── arm/ │ │ │ │ │ │ └── sysregs.h │ │ │ │ │ ├── entry.h │ │ │ │ │ ├── fork.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ ├── irq.h │ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ │ └── timer.h │ │ │ │ │ ├── printf.h │ │ │ │ │ ├── sched.h │ │ │ │ │ ├── timer.h │ │ │ │ │ └── utils.h │ │ │ │ ├── src/ │ │ │ │ │ ├── boot.S │ │ │ │ │ ├── config.txt │ │ │ │ │ ├── entry.S │ │ │ │ │ ├── fork.c │ │ │ │ │ ├── irq.S │ │ │ │ │ ├── irq.c │ │ │ │ │ ├── kernel.c │ │ │ │ │ ├── linker.ld │ │ │ │ │ ├── mini_uart.c │ │ │ │ │ ├── mm.S │ │ │ │ │ ├── mm.c │ │ │ │ │ ├── printf.c │ │ │ │ │ ├── sched.S │ │ │ │ │ ├── sched.c │ │ │ │ │ ├── timer.c │ │ │ │ │ └── utils.S │ │ │ │ └── start.sh │ │ │ └── rs/ │ │ │ ├── Makefile │ │ │ ├── build.bat │ │ │ ├── build.sh │ │ │ ├── include/ │ │ │ │ ├── arm/ │ │ │ │ │ └── sysregs.h │ │ │ │ ├── entry.h │ │ │ │ ├── fork.h │ │ │ │ ├── irq.h │ │ │ │ ├── mini_uart.h │ │ │ │ ├── mm.h │ │ │ │ ├── peripherals/ │ │ │ │ │ ├── base.h │ │ │ │ │ ├── gpio.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ └── timer.h │ │ │ │ ├── printf.h │ │ │ │ ├── sched.h │ │ │ │ ├── timer.h │ │ │ │ └── utils.h │ │ │ ├── output.txt │ │ │ └── src/ │ │ │ ├── boot.S │ │ │ ├── config.txt │ │ │ ├── entry.S │ │ │ ├── fork.c │ │ │ ├── irq.S │ │ │ ├── irq.c │ │ │ ├── kernel.c │ │ │ ├── linker.ld │ │ │ ├── mini_uart.c │ │ │ ├── mm.S │ │ │ ├── mm.c │ │ │ ├── printf.c │ │ │ ├── sched.S │ │ │ ├── sched.c │ │ │ ├── timer.c │ │ │ └── utils.S │ │ └── 5/ │ │ ├── avenito/ │ │ │ ├── Makefile │ │ │ ├── README.md │ │ │ ├── build.bat │ │ │ ├── build.sh │ │ │ ├── include/ │ │ │ │ ├── arm/ │ │ │ │ │ └── sysregs.h │ │ │ │ ├── entry.h │ │ │ │ ├── fork.h │ │ │ │ ├── irq.h │ │ │ │ ├── mm.h │ │ │ │ ├── peripherals/ │ │ │ │ │ ├── base.h │ │ │ │ │ ├── gpio.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── timer.h │ │ │ │ │ └── uart.h │ │ │ │ ├── printf.h │ │ │ │ ├── sched.h │ │ │ │ ├── timer.h │ │ │ │ ├── uart.h │ │ │ │ └── utils.h │ │ │ ├── saida.txt │ │ │ └── src/ │ │ │ ├── boot.S │ │ │ ├── config.txt │ │ │ ├── entry.S │ │ │ ├── fork.c │ │ │ ├── irq.S │ │ │ ├── irq.c │ │ │ ├── kernel.c │ │ │ ├── linker.ld │ │ │ ├── mm.S │ │ │ ├── mm.c │ │ │ ├── printf.c │ │ │ ├── sched.S │ │ │ ├── sched.c │ │ │ ├── timer.c │ │ │ ├── uart.c │ │ │ └── utils.S │ │ └── bl4ckout31/ │ │ ├── Makefile │ │ ├── build.bat │ │ ├── build.sh │ │ ├── include/ │ │ │ ├── arm/ │ │ │ │ └── sysregs.h │ │ │ ├── entry.h │ │ │ ├── fork.h │ │ │ ├── irq.h │ │ │ ├── mini_uart.h │ │ │ ├── mm.h │ │ │ ├── peripherals/ │ │ │ │ ├── base.h │ │ │ │ ├── gpio.h │ │ │ │ ├── irq.h │ │ │ │ ├── mini_uart.h │ │ │ │ └── timer.h │ │ │ ├── printf.h │ │ │ ├── sched.h │ │ │ ├── timer.h │ │ │ └── utils.h │ │ ├── src/ │ │ │ ├── boot.S │ │ │ ├── config.txt │ │ │ ├── entry.S │ │ │ ├── fork.c │ │ │ ├── irq.S │ │ │ ├── irq.c │ │ │ ├── kernel.c │ │ │ ├── linker.ld │ │ │ ├── mini_uart.c │ │ │ ├── mm.S │ │ │ ├── mm.c │ │ │ ├── printf.c │ │ │ ├── sched.S │ │ │ ├── sched.c │ │ │ ├── timer.S │ │ │ ├── timer.c │ │ │ └── utils.S │ │ └── start.sh │ ├── lesson05/ │ │ ├── 1/ │ │ │ ├── avenito/ │ │ │ │ ├── Makefile │ │ │ │ ├── README.md │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── arm/ │ │ │ │ │ │ └── sysregs.h │ │ │ │ │ ├── entry.h │ │ │ │ │ ├── fork.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ ├── irq.h │ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ │ └── timer.h │ │ │ │ │ ├── printf.h │ │ │ │ │ ├── sched.h │ │ │ │ │ ├── sys.h │ │ │ │ │ ├── timer.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── entry.S │ │ │ │ ├── fork.c │ │ │ │ ├── irq.S │ │ │ │ ├── irq.c │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ ├── mm.c │ │ │ │ ├── printf.c │ │ │ │ ├── sched.S │ │ │ │ ├── sched.c │ │ │ │ ├── sys.S │ │ │ │ ├── sys.c │ │ │ │ ├── timer.c │ │ │ │ └── utils.S │ │ │ ├── bl4ckout31/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── arm/ │ │ │ │ │ │ └── sysregs.h │ │ │ │ │ ├── entry.h │ │ │ │ │ ├── fork.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ ├── irq.h │ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ │ └── timer.h │ │ │ │ │ ├── printf.h │ │ │ │ │ ├── sched.h │ │ │ │ │ ├── sys.h │ │ │ │ │ ├── timer.h │ │ │ │ │ └── utils.h │ │ │ │ ├── src/ │ │ │ │ │ ├── boot.S │ │ │ │ │ ├── config.txt │ │ │ │ │ ├── entry.S │ │ │ │ │ ├── fork.c │ │ │ │ │ ├── irq.S │ │ │ │ │ ├── irq.c │ │ │ │ │ ├── kernel.c │ │ │ │ │ ├── linker.ld │ │ │ │ │ ├── mini_uart.c │ │ │ │ │ ├── mm.S │ │ │ │ │ ├── mm.c │ │ │ │ │ ├── printf.c │ │ │ │ │ ├── sched.S │ │ │ │ │ ├── sched.c │ │ │ │ │ ├── sys.S │ │ │ │ │ ├── sys.c │ │ │ │ │ ├── timer.c │ │ │ │ │ └── utils.S │ │ │ │ └── start.sh │ │ │ └── xdfm1/ │ │ │ ├── Makefile │ │ │ ├── build.bat │ │ │ ├── build.sh │ │ │ ├── include/ │ │ │ │ ├── arm/ │ │ │ │ │ └── sysregs.h │ │ │ │ ├── entry.h │ │ │ │ ├── fork.h │ │ │ │ ├── irq.h │ │ │ │ ├── mini_uart.h │ │ │ │ ├── mm.h │ │ │ │ ├── peripherals/ │ │ │ │ │ ├── base.h │ │ │ │ │ ├── gpio.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ └── timer.h │ │ │ │ ├── printf.h │ │ │ │ ├── sched.h │ │ │ │ ├── sys.h │ │ │ │ ├── timer.h │ │ │ │ └── utils.h │ │ │ └── src/ │ │ │ ├── boot.S │ │ │ ├── config.txt │ │ │ ├── entry.S │ │ │ ├── fork.c │ │ │ ├── irq.S │ │ │ ├── irq.c │ │ │ ├── kernel.c │ │ │ ├── linker.ld │ │ │ ├── mini_uart.c │ │ │ ├── mm.S │ │ │ ├── mm.c │ │ │ ├── printf.c │ │ │ ├── sched.S │ │ │ ├── sched.c │ │ │ ├── sys.S │ │ │ ├── sys.c │ │ │ ├── timer.c │ │ │ └── utils.S │ │ ├── 2/ │ │ │ ├── avenito/ │ │ │ │ ├── Makefile │ │ │ │ ├── README.md │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── arm/ │ │ │ │ │ │ └── sysregs.h │ │ │ │ │ ├── entry.h │ │ │ │ │ ├── fork.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ ├── irq.h │ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ │ └── timer.h │ │ │ │ │ ├── printf.h │ │ │ │ │ ├── sched.h │ │ │ │ │ ├── sys.h │ │ │ │ │ ├── timer.h │ │ │ │ │ └── utils.h │ │ │ │ └── src/ │ │ │ │ ├── boot.S │ │ │ │ ├── config.txt │ │ │ │ ├── entry.S │ │ │ │ ├── fork.c │ │ │ │ ├── irq.S │ │ │ │ ├── irq.c │ │ │ │ ├── kernel.c │ │ │ │ ├── linker.ld │ │ │ │ ├── mini_uart.c │ │ │ │ ├── mm.S │ │ │ │ ├── mm.c │ │ │ │ ├── printf.c │ │ │ │ ├── sched.S │ │ │ │ ├── sched.c │ │ │ │ ├── sys.S │ │ │ │ ├── sys.c │ │ │ │ ├── timer.c │ │ │ │ └── utils.S │ │ │ ├── bl4ckout31/ │ │ │ │ ├── Makefile │ │ │ │ ├── build.bat │ │ │ │ ├── build.sh │ │ │ │ ├── include/ │ │ │ │ │ ├── arm/ │ │ │ │ │ │ └── sysregs.h │ │ │ │ │ ├── entry.h │ │ │ │ │ ├── fork.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ ├── mm.h │ │ │ │ │ ├── peripherals/ │ │ │ │ │ │ ├── base.h │ │ │ │ │ │ ├── gpio.h │ │ │ │ │ │ ├── irq.h │ │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ │ └── timer.h │ │ │ │ │ ├── printf.h │ │ │ │ │ ├── sched.h │ │ │ │ │ ├── sys.h │ │ │ │ │ ├── timer.h │ │ │ │ │ └── utils.h │ │ │ │ ├── src/ │ │ │ │ │ ├── boot.S │ │ │ │ │ ├── config.txt │ │ │ │ │ ├── entry.S │ │ │ │ │ ├── fork.c │ │ │ │ │ ├── irq.S │ │ │ │ │ ├── irq.c │ │ │ │ │ ├── kernel.c │ │ │ │ │ ├── linker.ld │ │ │ │ │ ├── mini_uart.c │ │ │ │ │ ├── mm.S │ │ │ │ │ ├── mm.c │ │ │ │ │ ├── printf.c │ │ │ │ │ ├── sched.S │ │ │ │ │ ├── sched.c │ │ │ │ │ ├── sys.S │ │ │ │ │ ├── sys.c │ │ │ │ │ ├── timer.c │ │ │ │ │ └── utils.S │ │ │ │ └── start.sh │ │ │ └── xdfm1/ │ │ │ ├── Makefile │ │ │ ├── build.bat │ │ │ ├── build.sh │ │ │ ├── include/ │ │ │ │ ├── arm/ │ │ │ │ │ └── sysregs.h │ │ │ │ ├── entry.h │ │ │ │ ├── fork.h │ │ │ │ ├── irq.h │ │ │ │ ├── mini_uart.h │ │ │ │ ├── mm.h │ │ │ │ ├── peripherals/ │ │ │ │ │ ├── base.h │ │ │ │ │ ├── gpio.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ └── timer.h │ │ │ │ ├── printf.h │ │ │ │ ├── sched.h │ │ │ │ ├── sys.h │ │ │ │ ├── timer.h │ │ │ │ └── utils.h │ │ │ └── src/ │ │ │ ├── boot.S │ │ │ ├── config.txt │ │ │ ├── entry.S │ │ │ ├── fork.c │ │ │ ├── irq.S │ │ │ ├── irq.c │ │ │ ├── kernel.c │ │ │ ├── linker.ld │ │ │ ├── mini_uart.c │ │ │ ├── mm.S │ │ │ ├── mm.c │ │ │ ├── printf.c │ │ │ ├── sched.S │ │ │ ├── sched.c │ │ │ ├── sys.S │ │ │ ├── sys.c │ │ │ ├── timer.c │ │ │ └── utils.S │ │ └── 3/ │ │ ├── avenito/ │ │ │ ├── Makefile │ │ │ ├── README.md │ │ │ ├── build.bat │ │ │ ├── build.sh │ │ │ ├── include/ │ │ │ │ ├── arm/ │ │ │ │ │ └── sysregs.h │ │ │ │ ├── entry.h │ │ │ │ ├── fork.h │ │ │ │ ├── irq.h │ │ │ │ ├── mini_uart.h │ │ │ │ ├── mm.h │ │ │ │ ├── peripherals/ │ │ │ │ │ ├── base.h │ │ │ │ │ ├── gpio.h │ │ │ │ │ ├── irq.h │ │ │ │ │ ├── mini_uart.h │ │ │ │ │ └── timer.h │ │ │ │ ├── printf.h │ │ │ │ ├── sched.h │ │ │ │ ├── sys.h │ │ │ │ ├── timer.h │ │ │ │ └── utils.h │ │ │ └── src/ │ │ │ ├── boot.S │ │ │ ├── config.txt │ │ │ ├── entry.S │ │ │ ├── fork.c │ │ │ ├── irq.S │ │ │ ├── irq.c │ │ │ ├── kernel.c │ │ │ ├── linker.ld │ │ │ ├── mini_uart.c │ │ │ ├── mm.S │ │ │ ├── mm.c │ │ │ ├── printf.c │ │ │ ├── sched.S │ │ │ ├── sched.c │ │ │ ├── sys.S │ │ │ ├── sys.c │ │ │ ├── timer.c │ │ │ └── utils.S │ │ └── bl4ckout31/ │ │ ├── Makefile │ │ ├── build.bat │ │ ├── build.sh │ │ ├── include/ │ │ │ ├── arm/ │ │ │ │ └── sysregs.h │ │ │ ├── entry.h │ │ │ ├── fork.h │ │ │ ├── irq.h │ │ │ ├── mini_uart.h │ │ │ ├── mm.h │ │ │ ├── peripherals/ │ │ │ │ ├── base.h │ │ │ │ ├── gpio.h │ │ │ │ ├── irq.h │ │ │ │ ├── mini_uart.h │ │ │ │ └── timer.h │ │ │ ├── printf.h │ │ │ ├── sched.h │ │ │ ├── sys.h │ │ │ ├── timer.h │ │ │ └── utils.h │ │ ├── src/ │ │ │ ├── boot.S │ │ │ ├── config.txt │ │ │ ├── entry.S │ │ │ ├── fork.c │ │ │ ├── irq.S │ │ │ ├── irq.c │ │ │ ├── kernel.c │ │ │ ├── linker.ld │ │ │ ├── mini_uart.c │ │ │ ├── mm.S │ │ │ ├── mm.c │ │ │ ├── printf.c │ │ │ ├── sched.S │ │ │ ├── sched.c │ │ │ ├── sys.S │ │ │ ├── sys.c │ │ │ ├── timer.S │ │ │ ├── timer.c │ │ │ └── utils.S │ │ └── start.sh │ └── lesson06/ │ └── 1/ │ └── xdfm1/ │ ├── Makefile │ ├── build.bat │ ├── build.sh │ ├── gdb.cmd │ ├── include/ │ │ ├── arm/ │ │ │ ├── mmu.h │ │ │ └── sysregs.h │ │ ├── entry.h │ │ ├── fork.h │ │ ├── irq.h │ │ ├── mini_uart.h │ │ ├── mm.h │ │ ├── peripherals/ │ │ │ ├── base.h │ │ │ ├── gpio.h │ │ │ ├── irq.h │ │ │ ├── mini_uart.h │ │ │ └── timer.h │ │ ├── printf.h │ │ ├── sched.h │ │ ├── sys.h │ │ ├── timer.h │ │ ├── user.h │ │ ├── user_sys.h │ │ └── utils.h │ ├── src/ │ │ ├── boot.S │ │ ├── config.txt │ │ ├── entry.S │ │ ├── fork.c │ │ ├── irq.S │ │ ├── irq.c │ │ ├── kernel.c │ │ ├── linker.ld │ │ ├── mini_uart.c │ │ ├── mm.S │ │ ├── mm.c │ │ ├── printf.c │ │ ├── sched.S │ │ ├── sched.c │ │ ├── sys.c │ │ ├── timer.c │ │ ├── user.c │ │ ├── user_sys.S │ │ └── utils.S │ └── start.sh ├── src/ │ ├── lesson01/ │ │ ├── Makefile │ │ ├── build.bat │ │ ├── build.sh │ │ ├── include/ │ │ │ ├── mini_uart.h │ │ │ ├── mm.h │ │ │ ├── peripherals/ │ │ │ │ ├── base.h │ │ │ │ ├── gpio.h │ │ │ │ └── mini_uart.h │ │ │ └── utils.h │ │ └── src/ │ │ ├── boot.S │ │ ├── config.txt │ │ ├── kernel.c │ │ ├── linker.ld │ │ ├── mini_uart.c │ │ ├── mm.S │ │ └── utils.S │ ├── lesson02/ │ │ ├── Makefile │ │ ├── build.bat │ │ ├── build.sh │ │ ├── include/ │ │ │ ├── arm/ │ │ │ │ └── sysregs.h │ │ │ ├── mini_uart.h │ │ │ ├── mm.h │ │ │ ├── peripherals/ │ │ │ │ ├── base.h │ │ │ │ ├── gpio.h │ │ │ │ └── mini_uart.h │ │ │ ├── printf.h │ │ │ └── utils.h │ │ └── src/ │ │ ├── boot.S │ │ ├── config.txt │ │ ├── kernel.c │ │ ├── linker.ld │ │ ├── mini_uart.c │ │ ├── mm.S │ │ ├── printf.c │ │ └── utils.S │ ├── lesson03/ │ │ ├── Makefile │ │ ├── build.bat │ │ ├── build.sh │ │ ├── include/ │ │ │ ├── arm/ │ │ │ │ └── sysregs.h │ │ │ ├── entry.h │ │ │ ├── irq.h │ │ │ ├── mini_uart.h │ │ │ ├── mm.h │ │ │ ├── peripherals/ │ │ │ │ ├── base.h │ │ │ │ ├── gpio.h │ │ │ │ ├── irq.h │ │ │ │ ├── mini_uart.h │ │ │ │ └── timer.h │ │ │ ├── printf.h │ │ │ ├── timer.h │ │ │ └── utils.h │ │ └── src/ │ │ ├── boot.S │ │ ├── config.txt │ │ ├── entry.S │ │ ├── irq.S │ │ ├── irq.c │ │ ├── kernel.c │ │ ├── linker.ld │ │ ├── mini_uart.c │ │ ├── mm.S │ │ ├── printf.c │ │ ├── timer.c │ │ └── utils.S │ ├── lesson04/ │ │ ├── Makefile │ │ ├── build.bat │ │ ├── build.sh │ │ ├── include/ │ │ │ ├── arm/ │ │ │ │ └── sysregs.h │ │ │ ├── entry.h │ │ │ ├── fork.h │ │ │ ├── irq.h │ │ │ ├── mini_uart.h │ │ │ ├── mm.h │ │ │ ├── peripherals/ │ │ │ │ ├── base.h │ │ │ │ ├── gpio.h │ │ │ │ ├── irq.h │ │ │ │ ├── mini_uart.h │ │ │ │ └── timer.h │ │ │ ├── printf.h │ │ │ ├── sched.h │ │ │ ├── timer.h │ │ │ └── utils.h │ │ └── src/ │ │ ├── boot.S │ │ ├── config.txt │ │ ├── entry.S │ │ ├── fork.c │ │ ├── irq.S │ │ ├── irq.c │ │ ├── kernel.c │ │ ├── linker.ld │ │ ├── mini_uart.c │ │ ├── mm.S │ │ ├── mm.c │ │ ├── printf.c │ │ ├── sched.S │ │ ├── sched.c │ │ ├── timer.c │ │ └── utils.S │ ├── lesson05/ │ │ ├── Makefile │ │ ├── build.bat │ │ ├── build.sh │ │ ├── include/ │ │ │ ├── arm/ │ │ │ │ └── sysregs.h │ │ │ ├── entry.h │ │ │ ├── fork.h │ │ │ ├── irq.h │ │ │ ├── mini_uart.h │ │ │ ├── mm.h │ │ │ ├── peripherals/ │ │ │ │ ├── base.h │ │ │ │ ├── gpio.h │ │ │ │ ├── irq.h │ │ │ │ ├── mini_uart.h │ │ │ │ └── timer.h │ │ │ ├── printf.h │ │ │ ├── sched.h │ │ │ ├── sys.h │ │ │ ├── timer.h │ │ │ └── utils.h │ │ └── src/ │ │ ├── boot.S │ │ ├── config.txt │ │ ├── entry.S │ │ ├── fork.c │ │ ├── irq.S │ │ ├── irq.c │ │ ├── kernel.c │ │ ├── linker.ld │ │ ├── mini_uart.c │ │ ├── mm.S │ │ ├── mm.c │ │ ├── printf.c │ │ ├── sched.S │ │ ├── sched.c │ │ ├── sys.S │ │ ├── sys.c │ │ ├── timer.c │ │ └── utils.S │ └── lesson06/ │ ├── Makefile │ ├── build.bat │ ├── build.sh │ ├── include/ │ │ ├── arm/ │ │ │ ├── mmu.h │ │ │ └── sysregs.h │ │ ├── entry.h │ │ ├── fork.h │ │ ├── irq.h │ │ ├── mini_uart.h │ │ ├── mm.h │ │ ├── peripherals/ │ │ │ ├── base.h │ │ │ ├── gpio.h │ │ │ ├── irq.h │ │ │ ├── mini_uart.h │ │ │ └── timer.h │ │ ├── printf.h │ │ ├── sched.h │ │ ├── sys.h │ │ ├── timer.h │ │ ├── user.h │ │ ├── user_sys.h │ │ └── utils.h │ └── src/ │ ├── boot.S │ ├── config.txt │ ├── entry.S │ ├── fork.c │ ├── irq.S │ ├── irq.c │ ├── kernel.c │ ├── linker.ld │ ├── mini_uart.c │ ├── mm.S │ ├── mm.c │ ├── printf.c │ ├── sched.S │ ├── sched.c │ ├── sys.c │ ├── timer.c │ ├── user.c │ ├── user_sys.S │ └── utils.S └── translations/ ├── ko/ │ ├── Introduction.md │ ├── Prerequisites.md │ ├── lesson01/ │ │ ├── exercises.md │ │ ├── linux/ │ │ │ ├── build-system.md │ │ │ ├── kernel-startup.md │ │ │ └── project-structure.md │ │ └── rpi-os.md │ ├── lesson02/ │ │ ├── exercises.md │ │ ├── linux.md │ │ └── rpi-os.md │ ├── lesson03/ │ │ ├── exercises.md │ │ ├── linux/ │ │ │ ├── interrupt_controllers.md │ │ │ ├── low_level-exception_handling.md │ │ │ └── timer.md │ │ └── rpi-os.md │ └── lesson05/ │ ├── exercises.md │ ├── linux.md │ └── rpi-os.md └── zh-cn/ ├── Prerequisites.md ├── lesson01/ │ ├── exercises.md │ ├── linux/ │ │ ├── build-system.md │ │ ├── kernel-startup.md │ │ └── project-structure.md │ └── rpi-os.md ├── lesson02/ │ ├── exercises.md │ ├── linux.md │ └── rpi-os.md ├── lesson03/ │ ├── exercises.md │ ├── linux/ │ │ ├── interrupt_controllers.md │ │ ├── low_level-exception_handling.md │ │ └── timer.md │ └── rpi-os.md ├── lesson04/ │ ├── exercises.md │ ├── linux/ │ │ ├── basic_structures.md │ │ ├── fork.md │ │ └── scheduler.md │ └── rpi-os.md ├── lesson05/ │ ├── exercises.md │ ├── linux.md │ └── rpi-os.md └── lesson06/ ├── exercises.md └── rpi-os.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ backup/* src/*/build **/*.o **/*.d **/*.swp **/*.img **/*.elf # gnu global files GPATH GRTAGS GSYMS GTAGS ================================================ FILE: Dockerfile ================================================ FROM ubuntu:16.04 MAINTAINER Sergey Matyukevich RUN apt-get update && apt-get install -y gcc-aarch64-linux-gnu build-essential ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2018 Sergey Matyukevich Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Learning operating system development using Linux kernel and Raspberry Pi This repository contains a step-by-step guide that teaches how to create a simple operating system (OS) kernel from scratch. I call this OS Raspberry Pi OS or just RPi OS. The RPi OS source code is largely based on [Linux kernel](https://github.com/torvalds/linux), but the OS has very limited functionality and supports only [Raspberry PI 3](https://www.raspberrypi.org/products/raspberry-pi-3-model-b/). Each lesson is designed in such a way that it first explains how some kernel feature is implemented in the RPi OS, and then it tries to demonstrate how the same functionality works in the Linux kernel. Each lesson has a corresponding folder in the [src](https://github.com/s-matyukevich/raspberry-pi-os/tree/master/src) directory, which contains a snapshot of the OS source code at the time when the lesson had just been completed. This allows the introduction of new concepts gracefully and helps readers to follow the evolution of the RPi OS. Understanding this guide doesn't require any specific OS development skills. For more information about project goals and history, please read the [Introduction](docs/Introduction.md). The project is still under active development, if you are willing to participate - please read the [Contribution guide](docs/Contributions.md).

Follow @RPi_OS on twitter Follow Raspberry Pi OS on facebook Join Raspberry Pi OS in slack Subscribe for updates

## Table of Contents * **[Introduction](docs/Introduction.md)** * **[Contribution guide](docs/Contributions.md)** * **[Prerequisites](docs/Prerequisites.md)** * **Lesson 1: Kernel Initialization** * 1.1 [Introducing RPi OS, or bare metal "Hello, world!"](docs/lesson01/rpi-os.md) * Linux * 1.2 [Project structure](docs/lesson01/linux/project-structure.md) * 1.3 [Kernel build system](docs/lesson01/linux/build-system.md) * 1.4 [Startup sequence](docs/lesson01/linux/kernel-startup.md) * 1.5 [Exercises](docs/lesson01/exercises.md) * **Lesson 2: Processor initialization** * 2.1 [RPi OS](docs/lesson02/rpi-os.md) * 2.2 [Linux](docs/lesson02/linux.md) * 2.3 [Exercises](docs/lesson02/exercises.md) * **Lesson 3: Interrupt handling** * 3.1 [RPi OS](docs/lesson03/rpi-os.md) * Linux * 3.2 [Low level exception handling](docs/lesson03/linux/low_level-exception_handling.md) * 3.3 [Interrupt controllers](docs/lesson03/linux/interrupt_controllers.md) * 3.4 [Timers](docs/lesson03/linux/timer.md) * 3.5 [Exercises](docs/lesson03/exercises.md) * **Lesson 4: Process scheduler** * 4.1 [RPi OS](docs/lesson04/rpi-os.md) * Linux * 4.2 [Scheduler basic structures](docs/lesson04/linux/basic_structures.md) * 4.3 [Forking a task](docs/lesson04/linux/fork.md) * 4.4 [Scheduler](docs/lesson04/linux/scheduler.md) * 4.5 [Exercises](docs/lesson04/exercises.md) * **Lesson 5: User processes and system calls** * 5.1 [RPi OS](docs/lesson05/rpi-os.md) * 5.2 [Linux](docs/lesson05/linux.md) * 5.3 [Exercises](docs/lesson05/exercises.md) * **Lesson 6: Virtual memory management** * 6.1 [RPi OS](docs/lesson06/rpi-os.md) * 6.2 Linux (In progress) * 6.3 [Exercises](docs/lesson06/exercises.md) * **Lesson 7: Signals and interrupt waiting** (To be done) * **Lesson 8: File systems** (To be done) * **Lesson 9: Executable files (ELF)** (To be done) * **Lesson 10: Drivers** (To be done) * **Lesson 11: Networking** (To be done) ================================================ FILE: _config.yml ================================================ theme: jekyll-theme-hacker ================================================ FILE: _layouts/default.html ================================================ {% seo %}

{{ site.description | default: site.github.project_tagline }}

{% if site.show_downloads %} Download as .zip Download as .tar.gz {% endif %} View on GitHub
{{ content }}
{% if site.google_analytics %} {% endif %} ================================================ FILE: assets/css/style.scss ================================================ --- --- @import "{{ site.theme }}"; #main_content a[target] { text-decoration: none; } header a.logo { text-decoration: none; } ================================================ FILE: docs/Contributions.md ================================================ ## Contributing to the Raspberry PI OS The general workflow is the following: 1. Find an [issue](https://github.com/s-matyukevich/raspberry-pi-os/issues) you want to start working on or create a new one. 1. In comments claim that you want to take over the issue. The first person who does this will be assigned to the issue. 1. [Fork the repository](https://help.github.com/articles/fork-a-repo/). 1. Make all necessary changes. 1. [Send a pull request](https://help.github.com/articles/about-pull-requests/). 1. After a review, your changes will be merged. The following types of contributions are particularly useful for the project. 1. Validating the source code and text of the lessons, fixing bugs and errors. 1. Help in making lessons content more accurate and easier to understand. 1. Working on the source code and content for new lessons. 1. Sharing source code of completed exercises. 1. Suggesting new exercises. 1. Providing feedback, requesting new features and content. 1. Anything else that can help the project. ### Contributing completed exercises If you successfully completed any of the exercises you can share your source code. The code should be placed in the following folder `/exercises/lessonXX///` Such folder structure allows to accept pull requests for the same exercise from different people. ##### Previous Page [Introduction](../docs/Introduction.md) ##### Next Page [Prerequisites](../docs/Prerequisites.md) ================================================ FILE: docs/Introduction.md ================================================ ## Raspberry Pi OS project introduction or how to efficiently learn operating system development? A few years ago, I opened the source code of the Linux kernel for the first time. At that time, I considered myself more or less a skillful software developer: I knew a little bit of assembler and C programming, and had a high-level understanding of major operating system concepts, such as process scheduling and virtual memory management. However, my first attempt was a complete failure - I understood almost nothing. For other software projects that I have to deal with, I have a simple approach that usually works very well: I find the entry point of the program and then start reading the source code, going as deep as necessary to understand all the details that I am interested in. This approach works well, but not for something as sophisticated as an operating system. It was not just that it took me more than a week just to find an entry point - the main problem was that I quickly found myself in a situation where I was looking at a few lines of code, and I had no idea how to find any clues about what those lines were doing. This was especially true for the low-level assembler source code, but it worked no better for any other part of the system that I tried to investigate. I don't like the idea of dismissing a problem just because it looks complex from the beginning. Furthermore, I believe that there are no complex problems. Instead, there are a lot of problems we simply don't know how to address efficiently, so I started to look for an effective way to learn OS development in general and Linux in particular. ### Challenges in learning OS development I know that there are tons of books and documentation written about Linux kernel development, but neither of them provides me with the learning experience that I want. Half of the material are so superficial that I already know it. With the other half I have a very similar problem that I have with exploring the kernel source code: as soon as a book goes deep enough, 90% of the details appear to be irrelevant to the core concepts, but related to some security, performance or legacy considerations as well as to millions of features that the Linux kernel supports. As a result, instead of learning core operating system concepts, you always end up digging into the implementation details of those features. You may be wondering why I need to learn operating system development in the first place. For me, the main reason is that I was always interested in how things work under the hood. It is not just curiosity: the more difficult the task you are working on, frequently things begin to trace down to the operating system level. You just can't make fully informed technical decisions if you don't understand how everything works at a lower level. Another thing is that if you really like a technical challenge, working with OS development can be an exciting task for you. The next question you may ask is, why Linux? Other operating systems would probably be easier to approach. The answer is that I want my knowledge to be, at least in some way, relevant to what I am currently doing and to something I expect to be doing in the future. Linux is perfect in this regard because nowadays everything from small IoT devices to large servers tend to run Linux. When I said that most of the books about Linux kernel development didn't work well for me - I wasn't being quite honest. There was one book that explained some essential concepts using the actual source code that I was capable of fully understanding even though I am a novice in OS development. This book is "Linux Device Drivers", and it's no wonder that it is one of the most famous technical books about the Linux kernel. It starts by introducing source code of a simple driver that you can compile and play around with. Then it begins to introduce new driver related concepts one by one and explains how to modify the source code of the driver to use these new concepts. That is exactly what I refer to as a "good learning experience". The only problem with this book is that it focuses explicitly on driver development and says very little about core kernel implementation details. But why has nobody created a similar book for kernel developers? I think this is because if you use the current Linux kernel source code as a base for your book, then it's just not possible. There is no function, structure, or module that can be used as a simple starting point because there is nothing simple about the Linux source. You also can't introduce new concepts one at a time because in the source code, everything is very closely related to one another. After I realized this, an idea came to me: if the Linux kernel is too vast and too complicated to be used as a starting point for learning OS development, why don't I implement my own OS that will be explicitly designed for learning purposes? In this way, I can make the OS simple enough to provide a good learning experience. Also, if this OS will be implemented mostly by copying and simplifying different parts of the Linux kernel source, it would be straightforward to use it as a starting point to learn the Linux kernel as well. In addition to the OS, I decided to write a series of lectures that teaches major OS development concepts and fully explains the OS source code. ### OS requirements I started working on the project, which later became the [RPi OS](https://github.com/s-matyukevich/raspberry-pi-os). The first thing I had to do was to determine what parts of kernel development I considered to be "basic", and what components I considered to be not so essential and can be skipped (at least in the beginning). In my understanding, each operating system has 2 fundamental goals: 1. Run user processes in isolation. 1. Provide each user process with a unified view of the machine hardware. To satisfy the first requirement, the RPi OS needs to have its own scheduler. If I want to implement a scheduler, I also have to handle timer interrupts. The second requirement implies that the OS should support some drivers and provide system calls to expose them to user applications. Since this is for beginners, I don't want to work with complicated hardware, so the only drivers I care about are drivers that can write something to screen and read user input from a keyboard. Also, the OS needs to be able to load and execute user programs, so naturally it needs to support some sort of file system and be capable of understanding some sort of executable file format. It would be nice if the OS can support basic networking, but I don't want to focus on that in a text for beginners. So those are basically the things that I can identify as "core concepts of any operating system". Now let's take a look at the things that I want to ignore: 1. **Performance** I don't want to use any sophisticated algorithms in the OS. I am also going to disable all caches and other performance optimization techniques for simplicity. 1. **Security** It is true that the RPi OS has at least one security feature: virtual memory. Everything else can be safely ignored. 1. **Multiprocessing and synchronization** I am quite happy with my OS being executed on a single processor core. In particular, this allows me to get rid of a vast source of complexity - synchronization. 1. **Support for multiple architectures and different types of devices** More on this in the next section. 1. **Millions of other features that any production-ready operating system supports** ### How Raspberry Pi comes into play I already mentioned that I don't want the RPi OS to support multiple computer architectures or a lot of different devices. I felt even stronger about this after I dug into the Linux kernel driver model. It appears that even devices with similar purposes can largely vary in implementation details. This makes it very difficult to come up with simple abstractions around different driver types, and to reuse driver source code. To me, this seems like one of the primary sources of complexity in the Linux kernel, and I definitely want to avoid it in the RPi OS. Ok, but what kind of computer should I use then? I clearly don't want to test my bare metal programs using my working laptop, because I'm honestly not sure that it is going to survive. More importantly, I don't want people to buy an expensive laptop just to follow my OS development exercises (I don't think anybody would do this anyway). Emulators look like more or less a good choice, but I want to work with a real device because it gives me the feeling that I am doing something real rather than playing with bare metal programming. I ended up using the Raspberry Pi, in particular, the [Raspberry Pi 3 Model B](https://www.raspberrypi.org/products/raspberry-pi-3-model-b/). Using this device looks like the ideal choice for a number of reasons: 1. It costs something around $35. I think that should be an affordable price. 1. This device is specially designed for learning. Its inner architecture is as simple as possible, and that perfectly suits my needs. 1. This device uses ARM v8 architecture. This is a simple RISC architecture, is very well adapted to OS authors' needs, and doesn't have so many legacy requirements as, for example, the popular x86 architecture. If you don't believe me, you can compare the amount of source code in the `/arch/arm64` and `/arch/x86` folders in the Linux kernel. The OS is not compatible with the older versions of the Raspberry Pi, because neither of them support 64 bit ARM v8 architecture, though I think that support for all future devices should be trivial. ### Working with community One major drawback of any technical book is that very soon after release each book becomes obsolete. Technology nowadays is evolving so fast that it is almost impossible for book writers to keep up with it. That's why I like the idea of an "open source book" - a book that is freely available on the internet and encourages its readers to participate in content creation and validation. If the book content is available on Github, it is very easy for any reader to fix and develop new code samples, update the book content, and participate in writing new chapters. I understand that right now the project is not perfect, and at the time of writing it is even not finished. But I still want to publish it now, because I hope that with the help of the community I will be able to not only complete the project faster but also to make it much better and much more useful than it was in the beginning. ##### Previous Page [Main Page](https://github.com/s-matyukevich/raspberry-pi-os#learning-operating-system-development-using-linux-kernel-and-raspberry-pi) ##### Next Page [Contributing to the Raspberry PI OS](../docs/Contributions.md) ================================================ FILE: docs/Prerequisites.md ================================================ ## Prerequisites ### 1. [Raspberry Pi 3 Model B](https://www.raspberrypi.org/products/raspberry-pi-3-model-b/) Older versions of Raspberry Pi are not going to work with this tutorial because all lessons are designed to use a 64-bit processor that supports ARMv8 architecture, and such processor is only available in the Raspberry Pi 3. Newer versions, including [Raspberry Pi 3 Model B+](https://www.raspberrypi.org/products/raspberry-pi-3-model-b-plus/) should work fine, though I haven't tested it yet. ### 2. [USB to TTL serial cable](https://www.amazon.com/s/ref=nb_sb_noss_2?url=search-alias%3Daps&field-keywords=usb+to+ttl+serial+cable&rh=i%3Aaps%2Ck%3Ausb+to+ttl+serial+cable) After you get a serial cable, you need to test your connection. If you never did this before I recommend you to follow [this guide](https://cdn-learn.adafruit.com/downloads/pdf/adafruits-raspberry-pi-lesson-5-using-a-console-cable.pdf) It describes the process of connecting your Raspberry PI via a serial cable in great details. The guide also describes how to power your Raspberry Pi using a serial cable. RPi OS works fine with such kind of setup, however, in this case you need to run your terminal emulator right after you plug in the cable. Check [this](https://github.com/s-matyukevich/raspberry-pi-os/issues/2) issue for details.. ### 3. [SD card](https://www.raspberrypi.org/documentation/installation/sd-cards.md) with installed [Raspbian OS](https://www.raspberrypi.org/downloads/raspbian/) We need Raspbian to test USB to TTL cable connectivity initially. Another reason is that after installation it leaves the SD card formatted in the right way. ### 4. Docker Strictly speaking, Docker is not a required dependency. It is just convenient to use Docker to build source code of the lessons, especially for Mac and Windows users. Each lesson has `build.sh` script (or `build.bat` for windows users) This script uses Docker to build source code of the lesson. Instructions how to install docker for your platform can be found on the [official docker website](https://docs.docker.com/engine/installation/) If for some reasons you want to avoid using Docker, you can install the [make utility](http://www.math.tau.ac.il/~danha/courses/software1/make-intro.html) as well as `aarch64-linux-gnu` toolchain. If you are using Ubuntu you just need to install `gcc-aarch64-linux-gnu` and `build-essential` packages. ##### Previous Page [Contribution guide](../docs/Contributions.md) ##### Next Page 1.1 [Kernel Initialization: Introducing RPi OS, or bare metal "Hello, world!"](../docs/lesson01/rpi-os.md) ================================================ FILE: docs/lesson01/exercises.md ================================================ ## 1.5: Exercises. Exercises are optional, though I strongly recommend you to experiment with the source code a little bit. If you were able to complete any of the exercises - please share your source code with others. For details see the [contribution guide](../Contributions.md). 1. Introduce a constant `baud_rate`, calculate necessary Mini UART register values using this constant. Make sure that the program can work using baud rates other than 115200. 1. Change the OS code to use UART device instead of Mini UART. Use `BCM2837 ARM Peripherals` and [ARM PrimeCell UART (PL011)](http://infocenter.arm.com/help/topic/com.arm.doc.ddi0183g/DDI0183G_uart_pl011_r1p5_trm.pdf) manuals to figure out how to access UART registers and how to configure GPIO pins. The UART device uses the 48MHz clock as a base. 1. Try to use all 4 processor cores. The OS should print `Hello, from processor ` for all of the cores. Don't forget to set up a separate stack for each core and make sure that Mini UART is initialized only once. You can use a combination of global variables and `delay` function for synchronization. 1. Adapt lesson 01 to run on qemu. Check [this](https://github.com/s-matyukevich/raspberry-pi-os/issues/8) issue for reference. ##### Previous Page 1.4 [Kernel Initialization: Linux startup sequence](../../docs/lesson01/linux/kernel-startup.md) ##### Next Page 2.1 [Processor initialization: RPi OS](../../docs/lesson02/rpi-os.md) ================================================ FILE: docs/lesson01/linux/build-system.md ================================================ ## 1.3: Kernel build system After we examined Linux kernel structure, it worth spending some time investigating how we can build and run it. Linux also uses `make` utility to build the kernel, though Linux makefile is much more complicated. Before we will take a look at the makefile, let's learn some important concepts about Linux build system, which is called "kbuild". ### A few essential kbuild concepts * Build process can be customized by using kbuild variables. Those variables are defined in `Kconfig` files. Here you can define the variables themselves and their default values. Variables can have different types, including string, boolean and integer. In a Kconfig file you can also define dependencies between variables (for example, you can say that if variable X is selected then variable Y will be selected implicitly). As an example, you can take a look at [arm64 Kconfig file](https://github.com/torvalds/linux/tree/v4.14/arch/arm64/Kconfig). This file defines all variables, specific for `arm64` architecture. `Kconfig` functionality is not part of the standard `make` and is implemented in the Linux makefile. Variables, defined in `Kconfig` are exposed to the kernel source code as well as to the nested makefiles. Variable values can be set during kernel configuration step (for example, if you type `make menuconfig` a console GUI will be shown. It allows you to customize values for all kernel variables and stores the values in `.config`. Use `make help` command to view all possible options to configure the kernel) * Linux uses recursive build. This means that each subfolder of the Linux kernel can define it's own `Makefile` and `Kconfig`. Most of the nested Makefiles are very simple and just define what object files need to be compiled. Usually, such definitions have the following format. ``` obj-$(SOME_CONFIG_VARIABLE) += some_file.o ``` This definition means that `some_file.c` will be compiled and linked to the kernel only if `SOME_CONFIG_VARIABLE` is set. If you want to compile and link a file unconditionally, you need to change the previous definition to look like this. ``` obj-y += some_file.o ``` An example of the nested Makefile can be found [here](https://github.com/torvalds/linux/tree/v4.14/kernel/Makefile). * Before we move forward, you need to understand the structure of a basic make rule and be comfortable with make terminology. Common rule structure is illustrated in the following diagram. ``` targets : prerequisites recipe … ``` * `targets` are file names, separated by spaces. Targets are generated after the rule is executed. Usually, there is only one target per rule. * `prerequisites` are files that `make` trackes to see whether it needs to update the targets. * `recipe` is a bash script. Make calls it when some of the prerequisites have been updated. The recipe is responsible for generating the targets. * Both targets and prerequisites can include wildcards (`%`). When wildcards are used the recipe is executed for each of the matched prerequisites separately. In this case, you can use `$<` and `$@` variables to refer to the prerequisite and the target inside the recipe. We already did it in the [RPi OS makefile](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson01/Makefile#L14). For additional information about make rules, please refer to the [official documentation](https://www.gnu.org/software/make/manual/html_node/Rule-Syntax.html#Rule-Syntax). * `make` is very good in detecting whether any of the prerequisites have been changed and updating only targets that need to be rebuilt. However, if a recipe is dynamically updated, `make` is unable to detect this change. How can this happen? Very easily. One good example is when you change some configuration variable, which results in appending an additional option to the recipe. By default, in this case, `make` will not recompile previously generated object files, because their prerequisites haven't been changed, only the recipe have been updated. To overcome this behavior Linux introduces [if_changed](https://github.com/torvalds/linux/blob/v4.14/scripts/Kbuild.include#L264) function. To see how it works let's consider the following example. ``` cmd_compile = gcc $(flags) -o $@ $< %.o: %.c FORCE $(call if_changed,compile) ``` Here for each `.c` file we build corresponding `.o` file by calling `if_changed` function with the argument `compile`. `if_changed` then looks for `cmd_compile` variable (it adds `cmd_` prefix to the first argument) and checks whether this variable has been updated since the last execution, or any of the prerequisites has been changed. If yes - `cmd_compile` command is executed and object file is regenerated. Our sample rule has 2 prerequisites: source `.c` file and `FORCE`. `FORCE` is a special prerequisite that forces the recipe to be called each time when `make` command is called. Without it, the recipe would be called only if `.c` file was changed. You can read more about `FORCE` target [here](https://www.gnu.org/software/make/manual/html_node/Force-Targets.html). ### Building the kernel Now, that we learned some important concepts about the Linux build system, let's try to figure out what exactly is going on after you type `make` command. This process is very complicated and includes a lot of details, most of which we will skip. Our goal will be to answer 2 questions. 1. How exactly are source files compiled into object files? 1. How are object files linked into the OS image? We are going to tackle the second question first. #### Link stage * As you might see from the output of `make help` command, the default target, which is responsible for building the kernel, is called `vmlinux`. * `vmlinux` target definition can be found [here](https://github.com/torvalds/linux/blob/v4.14/Makefile#L1004) and it looks like this. ``` cmd_link-vmlinux = \ $(CONFIG_SHELL) $< $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux) ; \ $(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true) vmlinux: scripts/link-vmlinux.sh vmlinux_prereq $(vmlinux-deps) FORCE +$(call if_changed,link-vmlinux) ``` This target uses already familiar to us `if_changed` function. Whenever some of the prerequsities are updated `cmd_link-vmlinux` command is executed. This command executes [scripts/link-vmlinux.sh](https://github.com/torvalds/linux/blob/v4.14/scripts/link-vmlinux.sh) script (Note usage of [$<](https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html) automatic variable in the `cmd_link-vmlinux` command). It also executes architecture specific [postlink script](https://github.com/torvalds/linux/blob/v4.14/Documentation/kbuild/makefiles.txt#L1229), but we are not very interested in it. * When [scripts/link-vmlinux.sh](https://github.com/torvalds/linux/blob/v4.14/scripts/link-vmlinux.sh) is executed it assumes that all required object files are already built and their locations are stored in 3 variables: `KBUILD_VMLINUX_INIT`, `KBUILD_VMLINUX_MAIN`, `KBUILD_VMLINUX_LIBS`. * `link-vmlinux.sh` script first creates `thin archive` from all available object files. `thin archive` is a special object that contains references to a set of object files as well as their combined symbol table. This is done inside [archive_builtin](https://github.com/torvalds/linux/blob/v4.14/scripts/link-vmlinux.sh#L56) function. In order to create `thin archive` this function uses [ar](https://sourceware.org/binutils/docs/binutils/ar.html) utility. Generated `thin archive` is stored as `built-in.o` file and has the format that is understandable by the linker, so it can be used as any other normal object file. * Next [modpost_link](https://github.com/torvalds/linux/blob/v4.14/scripts/link-vmlinux.sh#L69) is called. This function calls linker and generates `vmlinux.o` object file. We need this object file to perform [Section mismatch analysis](https://github.com/torvalds/linux/blob/v4.14/lib/Kconfig.debug#L308). This analysis is performed by the [modpost](https://github.com/torvalds/linux/tree/v4.14/scripts/mod) program and is triggered at [this](https://github.com/torvalds/linux/blob/v4.14/scripts/link-vmlinux.sh#L260) line. * Next kernel symbol table is generated. It contains information about all functions and global variables as well as their location in the `vmlinux` binary. The main work is done inside [kallsyms](https://github.com/torvalds/linux/blob/v4.14/scripts/link-vmlinux.sh#L146) function. This function first uses [nm](https://sourceware.org/binutils/docs/binutils/nm.html) to extract all symbols from `vmlinux` binary. Then it uses [scripts/kallsyms](https://github.com/torvalds/linux/blob/v4.14/scripts/kallsyms.c) utility to generate a special assembler file containing all symbols in a special format, understandable by the Linux kernel. Next, this assembler file is compiled and linked together with the original binary. This process is repeated several times because after the final link addresses of some symbols can be changed. Information from the kernel symbol table is used to generate '/proc/kallsyms' file at runtime. * Finally `vmlinux` binary is ready and `System.map` is build. `System.map` contains the same information as `/proc/kallsyms` but this is static file and unlike `/proc/kallsyms` it is not generated at runtime. `System.map` is mostly used to resolve addresses to symbol names during [kernel oops](https://en.wikipedia.org/wiki/Linux_kernel_oops). The same `nm` utility is used to build `System.map`. This is done [here](https://github.com/torvalds/linux/blob/v4.14/scripts/mksysmap#L44). #### Build stage * Now let's take one step backward and examine how source code files are compiled into object files. As you might remember one of the prerequisites of the `vmlinux` target is `$(vmlinux-deps)` variable. Let me now copy a few relevant lines from the main Linux makefile to demonstrate how this variable is built. ``` init-y := init/ drivers-y := drivers/ sound/ firmware/ net-y := net/ libs-y := lib/ core-y := usr/ core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/ init-y := $(patsubst %/, %/built-in.o, $(init-y)) core-y := $(patsubst %/, %/built-in.o, $(core-y)) drivers-y := $(patsubst %/, %/built-in.o, $(drivers-y)) net-y := $(patsubst %/, %/built-in.o, $(net-y)) export KBUILD_VMLINUX_INIT := $(head-y) $(init-y) export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y2) $(drivers-y) $(net-y) $(virt-y) export KBUILD_VMLINUX_LIBS := $(libs-y1) export KBUILD_LDS := arch/$(SRCARCH)/kernel/vmlinux.lds vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN) $(KBUILD_VMLINUX_LIBS) ``` It all starts with variables like `init-y`, `core-y`, etc., which combined contains all subfolders of the Linux kernel that contains buildable source code. Then `built-in.o` is appended to all the subfolder names, so, for example, `drivers/` becomes `drivers/built-in.o`. `vmlinux-deps` then just aggregates all resulting values. This explains how `vmlinux` eventually becomes dependent on all `built-in.o` files. * Next question is how all `built-in.o` objects are created? Once again, let me copy all relevant lines and explain how it all works. ``` $(sort $(vmlinux-deps)): $(vmlinux-dirs) ; vmlinux-dirs := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \ $(core-y) $(core-m) $(drivers-y) $(drivers-m) \ $(net-y) $(net-m) $(libs-y) $(libs-m) $(virt-y))) build := -f $(srctree)/scripts/Makefile.build obj #Copied from `scripts/Kbuild.include` $(vmlinux-dirs): prepare scripts $(Q)$(MAKE) $(build)=$@ ``` The first line tells us that `vmlinux-deps` depends on `vmlinux-dirs`. Next, we can see that `vmlinux-dirs` is a variable that contains all direct root subfolders without `/` character at the end. And the most important line here is the recipe to build `$(vmlinux-dirs)` target. After substitution of all variables, this recipe will look like the following (we use `drivers` folder as an example, but this rule will be executed for all root subfolders) ``` make -f scripts/Makefile.build obj=drivers ``` This line just calls another makefile ([scripts/Makefile.build](https://github.com/torvalds/linux/blob/v4.14/scripts/Makefile.build)) and passes `obj` variable, which contains a folder to be compiled. * Next logical step is to take a look at [scripts/Makefile.build](https://github.com/torvalds/linux/blob/v4.14/scripts/Makefile.build). The first important thing that happens after it is executed is that all variables from `Makefile` or `Kbuild` files, defined in the current directory, are included. By current directory I mean the directory referenced by the `obj` variable. The inclusion is done in the [following 3 lines](https://github.com/torvalds/linux/blob/v4.14/scripts/Makefile.build#L43-L45). ``` kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src)) kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile) include $(kbuild-file) ``` Nested makefiles are mostly responsible for initializing variables like `obj-y`. As a quick reminder: `obj-y` variable should contain list of all source code files, located in the current directory. Another important variable that is initialized by the nested makefiles is `subdir-y`. This variable contains a list of all subfolders that need to be visited before the source code in the curent directory can be built. `subdir-y` is used to implement recursive descending into subfolders. * When `make` is called without specifying the target (as it is in the case when `scripts/Makefile.build` is executed) it uses the first target. The first target for `scripts/Makefile.build` is called `__build` and it can be found [here](https://github.com/torvalds/linux/blob/v4.14/scripts/Makefile.build#L96) Let's take a look at it. ``` __build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \ $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \ $(subdir-ym) $(always) @: ``` As you can see `__build` target doesn't have a receipt, but it depends on a bunch of other targets. We are only interested in `$(builtin-target)` - it is responsible for creating `built-in.o` file, and `$(subdir-ym)` - it is responsible for descending into nested directories. * Let's take a look at `subdir-ym`. This variable is initialized [here](https://github.com/torvalds/linux/blob/v4.14/scripts/Makefile.lib#L48) and it is just a concatenation of `subdir-y` and `subdir-m` variables. (`subdir-m` variable is similar to `subdir-y`, but it defines subfolders need to be included in a separate [kernel module](https://en.wikipedia.org/wiki/Loadable_kernel_module). We skip the discussion of modules, for now, to keep focused.) * `subdir-ym` target is defined [here](https://github.com/torvalds/linux/blob/v4.14/scripts/Makefile.build#L572) and should look familiar to you. ``` $(subdir-ym): $(Q)$(MAKE) $(build)=$@ ``` This target just triggers execution of the `scripts/Makefile.build` in one of the nested subfolders. * Now it is time to examine the [builtin-target](https://github.com/torvalds/linux/blob/v4.14/scripts/Makefile.build#L467) target. Once again I am copying only relevant lines here. ``` cmd_make_builtin = rm -f $@; $(AR) rcSTP$(KBUILD_ARFLAGS) cmd_make_empty_builtin = rm -f $@; $(AR) rcSTP$(KBUILD_ARFLAGS) cmd_link_o_target = $(if $(strip $(obj-y)),\ $(cmd_make_builtin) $@ $(filter $(obj-y), $^) \ $(cmd_secanalysis),\ $(cmd_make_empty_builtin) $@) $(builtin-target): $(obj-y) FORCE $(call if_changed,link_o_target) ``` This target depends on `$(obj-y)` target and `obj-y` is a list of all object files that need to be built in the current folder. After those files become ready `cmd_link_o_target` command is executed. In case if `obj-y` variable is empty `cmd_make_empty_builtin` is called, which just creates an empty `built-in.o`. Otherwise, `cmd_make_builtin` command is executed; it uses familiar to us `ar` tool to create `built-in.o` thin archive. * Finally we got to the point where we need to compile something. You remember that our last unexplored dependency is `$(obj-y)` and `obj-y` is just a list of object files. The target that compiles all object files from corresponding `.c` files is defined [here](https://github.com/torvalds/linux/blob/v4.14/scripts/Makefile.build#L313). Let's examine all lines, needed to understand this target. ``` cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $< define rule_cc_o_c $(call echo-cmd,checksrc) $(cmd_checksrc) \ $(call cmd_and_fixdep,cc_o_c) \ $(cmd_modversions_c) \ $(call echo-cmd,objtool) $(cmd_objtool) \ $(call echo-cmd,record_mcount) $(cmd_record_mcount) endef $(obj)/%.o: $(src)/%.c $(recordmcount_source) $(objtool_dep) FORCE $(call cmd,force_checksrc) $(call if_changed_rule,cc_o_c) ``` Inside it's recipe this target calls `rule_cc_o_c`. This rule is responsible for a lot of things, like checking the source code for some common errors (`cmd_checksrc`), enabling versioning for exported module symbols (`cmd_modversions_c`), using [objtool](https://github.com/torvalds/linux/tree/v4.14/tools/objtool) to validate some aspects of generated object files and constructing a list of calls to `mcount` function so that [ftrace](https://github.com/torvalds/linux/blob/v4.14/Documentation/trace/ftrace.txt) can find them quickly. But most importantly it calls `cmd_cc_o_c` command that actually compiles all `.c` files to object files. ### Conclusion Wow, it was a long journey inside kernel build system internals! Still, we skipped a lot of details and, for those who want to learn more about the subject, I can recommend to read the following [document](https://github.com/torvalds/linux/blob/v4.14/Documentation/kbuild/makefiles.txt) and continue reading Makefiles source code. Let me now emphasize the important points, that you should take as a take-home message from this chapter. 1. How `.c` files are compiled into object files. 1. How object files are combined into `built-in.o` files. 1. How recursive build pick up all child `built-in.o` files and combines them into a single one. 1. How `vmlinux` is linked from all top-level `built-in.o` files. My main goal was that after reading this chapter you will gain a general understanding of all above points. ##### Previous Page 1.2 [Kernel Initialization: Linux project structure](../../../docs/lesson01/linux/project-structure.md) ##### Next Page 1.4 [Kernel Initialization: Linux startup sequence](../../../docs/lesson01/linux/kernel-startup.md) ================================================ FILE: docs/lesson01/linux/kernel-startup.md ================================================ ## 1.4: Linux startup sequence ### Searching for the entry point After taking a look at the Linux project structure and examining how it can be built, next logical step is to find the program entry point. This step might be trivial for a lot of programs, but not for the Linux kernel. The first thing we are going to do is to take a look at [arm64 linker script](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/vmlinux.lds.S). We have already seen how the linker script [is used in the main makefile](https://github.com/torvalds/linux/blob/v4.14/Makefile#L970). From this line, we can easily infer, where the linker script for a particular architecture can be found. It should be mentioned that the file we are going to examine is not an actual linker script - it is a template, from which the actual linker script is built by substituting some macros with their actual values. But precisely because this file consists mostly of macros it becomes much easier to read and to port between different architectures. The first section that we can find in the linker script is called [.head.text](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/vmlinux.lds.S#L96). This is very important for us because the entry point should be defined in this section. If you think a little about it, it makes a perfect sense: after the kernel is loaded, the content of the binary image is copied into some memory area and execution is started from the beginning of that area. This means that just by searching who is using `.head.text` section we should be able to find the entry point. And indeed, `arm64` architecture has a single file that uses [__HEAD](https://github.com/torvalds/linux/blob/v4.14/include/linux/init.h#L90) macro, which is expanded to `.section ".head.text","ax"` - this file is [head.S](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/head.S). The first executable line, that we can find in `head.S` file is [this one](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/head.S#L85). Here we use arm assembler `b` of `branch` instruction to jump to the [stext](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/head.S#L116) function. And this is the first function that is executed after you boot the kernel. Next logical step is to explore what is going on inside the `stext` function - but we are not ready yet. First, we have to implement similar functionality in the RPi OS, and that is something we will cover in the next few lessons. What we are going to do right now is to examine a few critical concepts, related to kernel boot. ### Linux bootloader and boot protocol When linux kernel boots it assumes that the machine hardware is prepared in some "known state". The set of rules that defines this state is called "boot protocol", and for `arm64` architecture it is documented [here](https://github.com/torvalds/linux/blob/v4.14/Documentation/arm64/booting.txt). Among other things, it defines, for example, that the execution must start only on primary CPU, Memory Mapping Unit must be turned off and all interrupts must be disabled. Ok, but who is responsible for bringing a machine into that known state? Usually, there is a special program that runs before the kernel and performs all initializations. This program is called `bootloader`. Bootloader code may be very machine specific, and this is the case, with Raspberry PI. Raspberry PI has a bootloader that is is built into the board. We can only use [config.txt](https://www.raspberrypi.org/documentation/configuration/config-txt/) file to customize its behavior. ### UEFI boot However, there is one boot loader that can be built into the kernel image itself. This bootloader can be used only on the platforms that support [Unified Extensible Firmware Interface (UEFI)](https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface). Devices that support UEFI provide a set of standardized services to the running software and those services can be used to figure out all necessary information about the machine itself and its capabilities. UEFI also requires that computer firmware should be capable of running executable files in [Portable Executable (PE)](https://en.wikipedia.org/wiki/Portable_Executable) format. Linux kernel UEFI bootloader makes use of this feature: it injects `PE` header at the beginning of the Linux kernel image so that computer firmware think that the image is a normal `PE` file. This is done in [efi-header.S](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/efi-header.S) file. This file defines [__EFI_PE_HEADER](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/efi-header.S#L13) macro, which is used inside [head.S](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/head.S#L98). One important property that is defined inside `__EFI_PE_HEADER` is the one that tells about the [location of the UEFI entry point](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/efi-header.S#L33) and the entry point itself can be found in [efi-entry.S](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/efi-entry.S#L32). Starting from this location, you can follow the source code and examine what exactly UEFI bootloader is doing (the source code itself is more or less straightforward). But we are going to stop here because the purpose of this section is not to examine UEFI bootloader in details, but instead, give you a general idea what UEFI is and how Linux kernel uses it. ### Device Tree When I started to examine the startup code of the Linux kernel, I found a lot of mentions of `Device Trees`. It appears to be an essential concept, and I consider it necessary to discuss it. When we were working on `Raspberry PI OS` kernel, we used [BCM2837 ARM Peripherals manual](https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf) to figure out what is the exact offset at which a particular memory mapped register is located. This information obviously is different for each board, and we are lucky that we have to support only one of them. But what if we need to support hundreds of different boards? It would be a total mess if we try to hardcode information about each board in the kernel code. And even if we manage to do so, how would we figure out what board we are using right now? `BCM2837`, for example, doesn't provide any means of communicating such information to the running kernel. Device tree provides us with the solution to the problem, mentioned above. It is a special format that can be used to describe computer hardware. Device tree specification can be found [here](https://www.devicetree.org/). Before the kernel is executed, bootloader selects proper device tree file and passes it as an argument to the kernel. If you take a look at the files in the boot partition on a Raspberry PI SD card, you can find a lot of `.dtb` files here. `.dtb` are compiled device tree files. You can select some of them in the `config.txt` to enable or disable some Raspberry PI hardware. This process is described in more details in the [Raspberry PI official documentation](https://www.raspberrypi.org/documentation/configuration/device-tree.md). Ok, now it is time to take a look at how an actual device tree looks like. As a quick exercise, let's try to find a device tree for [Raspberry PI 3 Model B](https://www.raspberrypi.org/products/raspberry-pi-3-model-b/). From the [documentation](https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2837/README.md) we can figure out that `Raspberry PI 3 Model B` uses a chip that is called `BCM2837`. If you search for this name you can find [/arch/arm64/boot/dts/broadcom/bcm2837-rpi-3-b.dts](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/boot/dts/broadcom/bcm2837-rpi-3-b.dts) file. As you might see it just includes the same file from `arm` architecture. This makes a perfect sense because `ARM.v8` processor supports 32-bit mode as well. Next, we can find [bcm2837-rpi-3-b.dts](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837-rpi-3-b.dts) belonging to the [arm](https://github.com/torvalds/linux/tree/v4.14/arch/arm) architecture. We already saw that device tree files could include on another. This is the case with the `bcm2837-rpi-3-b.dts` - it only contains those definitions, that are specific for `BCM2837` and reuses everything else. For example, `bcm2837-rpi-3-b.dts` specifies that [the device now have 1GB of memory](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837-rpi-3-b.dts#L18). As I mentioned previously, `BCM2837` and `BCM2835` have an identical peripheral hardware, and, if you follow the chain of includes, you can find [bcm283x.dtsi](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm283x.dtsi) that actually defines most of this hardware. A device tree definition consists of the blocks nested one in another. At the top level we usually can find such blocks as [cpus](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837.dtsi#L30) or [memory](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837-rpi-3-b.dts#L17) The meaning of those blocks should be quite self-explanatory. Another interesting top-level element that we can find in the `bcm283x.dtsi` is [SoC](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm283x.dtsi#L52) that means [System on a chip](https://en.wikipedia.org/wiki/System_on_a_chip) It tells us that all peripheral devices are directly mapped to some memory area via memory mapped registers. `soc` element serves as a parent element for all peripheral devices. One of its children is [gpio](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm283x.dtsi#L147) element. This element defines [reg = <0x7e200000 0xb4>](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm283x.dtsi#L149) property that tells us that GPIO memory mapped registers are located in the `[0x7e200000 : 0x7e2000b4]` region. One of the childern of `gpio` element has the [following definition](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm283x.dtsi#L474) ``` uart1_gpio14: uart1_gpio14 { brcm,pins = <14 15>; brcm,function = ; }; ``` This definition tells us that if alternative function 5 is selected for pins 14 and 15 those pins will be connection to `uart1` device. You can easily gues that `uart1` device is the Mini UART that we have used already. One important thing that you need to know about device trees is that the format is extendable. Each device can define its own properties and nested blocks. Those properties are transparently passed to the device driver, and it is driver responsibility to interpret them. But how can the kernel figure out the correspondence between a block in a device tree and the right driver? It uses `compatible` property to do this. For example, for `uart1` device `compatible` property is specified like this ``` compatible = "brcm,bcm2835-aux-uart"; ``` And indeed, if you search for `bcm2835-aux-uart` in the Linux source code, you can find a matching driver, it is defined in [8250_bcm2835aux.c](https://github.com/torvalds/linux/blob/v4.14/drivers/tty/serial/8250/8250_bcm2835aux.c) ### Conclusion You can think about this chapter as a preparation for reading `arm64` boot code - without understanding the concepts that we've just explored you would have a hard time learning it. In the next lesson, we will go back to the `stext` function and examine in details how it works. ##### Previous Page 1.3 [Kernel Initialization: Kernel build system](../../../docs/lesson01/linux/build-system.md) ##### Next Page 1.5 [Kernel Initialization: Exercises](../../../docs/lesson01/exercises.md) ================================================ FILE: docs/lesson01/linux/project-structure.md ================================================ ## 1.2: Linux project structure This is the first time we are going to talk about Linux. The idea is first to complete some small step in writing our own kernel, and then take a look at how the same things work in Linux. So far we have done very little: we just implemented our first bare metal hello world program, Still, we will be able to find some similarities between the RPi OS and Linux. And now we are going to explore some of them. ### Project structure Whenever you start investigating any large software project, it worth taking a quick look at the project structure. This is very important because it allows you to understand what modules compose the project and what is the high-level architecture. Let's try to explore project structure of the Linux kernel. First of all, you need to clone the Linux repository. ``` git clone -b v4.14 --depth 1 https://github.com/torvalds/linux.git ``` We are using `v4.14` version because this was the latest version at the time of writing. All references to the Linux source code will be made using this specific version. Next, let's take a look at the folders that we can find inside the Linux repository. We are not going to look at all of them, but only at those that I consider the most important to start with. * [arch](https://github.com/torvalds/linux/tree/v4.14/arch) This folder contains subfolders, each for a specific processor architecture. Mostly we are going to work with [arm64](https://github.com/torvalds/linux/tree/v4.14/arch/arm64) - this is the one that is compatible with ARM.v8 processors. * [init](https://github.com/torvalds/linux/tree/v4.14/init) Kernel is always booted by architecture specific code. But then execution is passed to the [start_kernel](https://github.com/torvalds/linux/blob/v4.14/init/main.c#L509) function that is responsible for common kernel initialization and is an architecture independent kernel starting point. `start_kernel` function, together with some other initialization functions, is defined in the `init` folder. * [kernel](https://github.com/torvalds/linux/tree/v4.14/kernel) This is the core of the Linux kernel. Almost all major kernel subsystems are implemented there. * [mm](https://github.com/torvalds/linux/tree/v4.14/mm) All data structures and methods related to memory management are defined there. * [drivers](https://github.com/torvalds/linux/tree/v4.14/drivers) This is the largest folder in the Linux kernel. It contains implementations of all device drivers. * [fs](https://github.com/torvalds/linux/tree/v4.14/fs) You can look here to find different filesystem implementations. This explanation is very high-level, but this is enough for now. In the next chapter, we will try to examine Linux build system in some details. ##### Previous Page 1.1 [Kernel Initialization: Introducing RPi OS, or bare metal "Hello, world!"](../../../docs/lesson01/rpi-os.md) ##### Next Page 1.3 [Kernel Initialization: Kernel build system](../../../docs/lesson01/linux/build-system.md) ================================================ FILE: docs/lesson01/rpi-os.md ================================================ ## 1.1: Introducing RPi OS, or bare-metal "Hello, World!" We are going to start our journey in OS development by writing a small, bare-metal "Hello, World" application. I assume that you have gone through the [Prerequisites](../Prerequisites.md) and have everything ready. If not, now is the time to do this. Before we move forward, I want to establish a simple naming convention. From the README file you can see that the whole tutorial is divided into lessons. Each lesson consists of individual files that I call "chapters" (right now, you are reading lesson 1, chapter 1.1). A chapter is further divided into "sections" with headings. This naming convention allows me to make references to different parts of the material. Another thing I want you to pay attention to is that the tutorial contains a lot of source code samples. I'll usually start the explanation by providing the complete code block, and then describe it line by line. ### Project structure The source code of each lesson has the same structure. You can find this lesson's source code [here](https://github.com/s-matyukevich/raspberry-pi-os/tree/master/src/lesson01). Let's briefly describe the main components of this folder: 1. **Makefile** We will use the [make](http://www.math.tau.ac.il/~danha/courses/software1/make-intro.html) utility to build the kernel. `make`'s behavior is configured by a Makefile, which contains instructions on how to compile and link the source code. 1. **build.sh or build.bat** You'll need these files if you want to build the kernel using Docker. You won't need to have the make utility or the compiler toolchain installed on your laptop. 1. **src** This folder contains all of the source code. 1. **include** All of the header files are placed here. ### Makefile Now let's take a closer look at the project Makefile. The primary purpose of the make utility is to automatically determine what pieces of a program need to be recompiled, and to issue commands to recompile them. If you are not familiar with make and Makefiles, I recommend that you read [this](http://opensourceforu.com/2012/06/gnu-make-in-detail-for-beginners/) article. The Makefile used in the first lesson can be found [here](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson01/Makefile). The whole Makefile is listed below: ``` ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ``` Now, let's inspect this file in detail: ``` ARMGNU ?= aarch64-linux-gnu ``` The Makefile starts with a variable definition. `ARMGNU` is a cross-compiler prefix. We need to use a [cross-compiler](https://en.wikipedia.org/wiki/Cross_compiler) because we are compiling the source code for the `arm64` architecture on an `x86` machine. So instead of `gcc`, we will use `aarch64-linux-gnu-gcc`. ``` COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude ``` `COPS` and `ASMOPS` are options that we pass to the compiler when compiling C and assembler code, respectively. These options require a short explanation: * **-Wall** Show all warnings. * **-nostdlib** Don't use the C standard library. Most of the calls in the C standard library eventually interact with the operating system. We are writing a bare-metal program, and we don't have any underlying operating system, so the C standard library is not going to work for us anyway. * **-nostartfiles** Don't use standard startup files. Startup files are responsible for setting an initial stack pointer, initializing static data, and jumping to the main entry point. We are going to do all of this by ourselves. * **-ffreestanding** A freestanding environment is an environment in which the standard library may not exist, and program startup may not necessarily be at main. The option `-ffreestanding` directs the compiler to not assume that standard functions have their usual definition. * **-Iinclude** Search for header files in the `include` folder. * **-mgeneral-regs-only**. Use only general-purpose registers. ARM processors also have [NEON](https://developer.arm.com/technologies/neon) registers. We don't want the compiler to use them because they add additional complexity (since, for example, we will need to store the registers during a context switch). ``` BUILD_DIR = build SRC_DIR = src ``` `SRC_DIR` and `BUILD_DIR` are directories that contain source code and compiled object files, respectively. ``` all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img ``` Next, we define make targets. The first two targets are pretty simple: the `all` target is the default one, and it is executed whenever you type `make` without any arguments (`make` always uses the first target as the default). This target just redirects all work to a different target, `kernel8.img`. The `clean` target is responsible for deleting all compilation artifacts and the compiled kernel image. ``` $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ ``` The next two targets are responsible for compiling C and assembler files. If, for example, in the `src` directory we have `foo.c` and `foo.S` files, they will be compiled into `build/foo_c.o` and `build/foo_s.o`, respectively. `$<` and `$@` are substituted at runtime with the input and output filenames (`foo.c` and `foo_c.o`). Before compiling C files, we also create a `build` directory in case it doesn't exist yet. ``` C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) ``` Here we are building an array of all object files (`OBJ_FILES`) created from the concatenation of both C and assembler source files (see [Substitution References](https://www.gnu.org/software/make/manual/html_node/Substitution-Refs.html)). ``` DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) ``` The next two lines are a little bit tricky. If you take a look at how we defined our compilation targets for both C and assembler source files, you will notice that we used the `-MMD` parameter. This parameter instructs the `gcc` compiler to create a dependency file for each generated object file. A dependency file defines all of the dependencies for a particular source file. These dependencies usually contain a list of all included headers. We need to include all of the generated dependency files so that make knows what exactly to recompile in case a header changes. ``` $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o kernel8.elf $(OBJ_FILES) ``` We use the `OBJ_FILES` array to build the `kernel8.elf` file. We use the linker script `src/linker.ld` to define the basic layout of the resulting executable image (we will discuss the linker script in the next section). ``` $(ARMGNU)-objcopy kernel8.elf -O binary kernel8.img ``` `kernel8.elf` is in the [ELF](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format) format. The problem is that ELF files are designed to be executed by an operating system. To write a bare-metal program, we need to extract all executable and data sections from the ELF file and put them into the `kernel8.img` image. The trailing `8` denotes ARMv8 which is a 64-bit architecture. This filename tells the firmware to boot the processor into 64-bit mode. You can also boot the CPU in the 64-bit mode by using `arm_control=0x200` flag in the `config.txt` file. The RPi OS previously used this method, and you can still find it in some of the exercise answers. However, `arm_control` flag is undocumented and it is preferable to use `kernel8.img` naming convention instead. ### The linker script The primary purpose of the linker script is to describe how the sections in the input object files (`_c.o` and `_s.o`) should be mapped into the output file (`.elf`). More information about linker scripts can be found [here](https://sourceware.org/binutils/docs/ld/Scripts.html#Scripts). Now let's take a look at the RPi OS linker script: ``` SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ``` After startup, the Raspberry Pi loads `kernel8.img` into memory and starts execution from the beginning of the file. That's why the `.text.boot` section must be first; we are going to put the OS startup code inside this section. The `.text`, `.rodata`, and `.data` sections contain kernel-compiled instructions, read-only data, and normal data––there is nothing special to add about them. The `.bss` section contains data that should be initialized to 0. By putting such data in a separate section, the compiler can save some space in the ELF binary––only the section size is stored in the ELF header, but the section itself is omitted. After loading the image into memory, we must initialize the `.bss` section to 0; that's why we need to record the start and end of the section (hence the `bss_begin` and `bss_end` symbols) and align the section so that it starts at an address that is a multiple of 8. If the section is not aligned, it would be more difficult to use the `str` instruction to store 0 at the beginning of the `bss` section because the `str` instruction can be used only with 8-byte-aligned addresses. ### Booting the kernel Now it is time to take a look at the [boot.S](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson01/src/boot.S) file. This file contains the kernel startup code: ``` #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main ``` Let's review this file in detail: ``` .section ".text.boot" ``` First, we specify that everything defined in `boot.S` should go in the `.text.boot` section. Previously, we saw that this section is placed at the beginning of the kernel image by the linker script. So when the kernel is started, execution begins at the `start` function: ``` .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang ``` The first thing this function does is check the processor ID. The Raspberry Pi 3 has four core processors, and after the device is powered on, each core begins to execute the same code. However, we don't want to work with four cores; we want to work only with the first one and put all of the other cores in an endless loop. This is exactly what the `_start` function is responsible for. It gets the processor ID from the [mpidr_el1](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0500g/BABHBJCI.html) system register. If the current process ID is 0, then execution is transferred to the `master` function: ``` master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero ``` Here, we clean the `.bss` section by calling `memzero`. We will define this function later. In ARMv8 architecture, by convention, the first seven arguments are passed to the called function via registers x0–x6. The `memzero` function accepts only two arguments: the start address (`bss_begin`) and the size of the section needed to be cleaned (`bss_end - bss_begin`). ``` mov sp, #LOW_MEMORY bl kernel_main ``` After cleaning the `.bss` section, we initialize the stack pointer and pass execution to the `kernel_main` function. The Raspberry Pi loads the kernel at address 0; that's why the initial stack pointer can be set to any location high enough so that stack will not override the kernel image when it grows sufficiently large. `LOW_MEMORY` is defined in [mm.h](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson01/include/mm.h) and is equal to 4MB. Our kernel's stack won't grow very large and the image itself is tiny, so `4MB` is more than enough for us. For those of you who are not familiar with ARM assembler syntax, let me quickly summarize the instructions that we have used: * [**mrs**](http://www.keil.com/support/man/docs/armasm/armasm_dom1361289881374.htm) Load value from a system register to one of the general purpose registers (x0–x30) * [**and**](http://www.keil.com/support/man/docs/armasm/armasm_dom1361289863017.htm) Perform the logical AND operation. We use this command to strip the last byte from the value we obtain from the `mpidr_el1` register. * [**cbz**](http://www.keil.com/support/man/docs/armasm/armasm_dom1361289867296.htm) Compare the result of the previously executed operation to 0 and jump (or `branch` in ARM terminology) to the provided label if the comparison yields true. * [**b**](http://www.keil.com/support/man/docs/armasm/armasm_dom1361289863797.htm) Perform an unconditional branch to some label. * [**adr**](http://www.keil.com/support/man/docs/armasm/armasm_dom1361289862147.htm) Load a label's relative address into the target register. In this case, we want pointers to the start and end of the `.bss` region. * [**sub**](http://www.keil.com/support/man/docs/armasm/armasm_dom1361289908389.htm) Subtract values from two registers. * [**bl**](http://www.keil.com/support/man/docs/armasm/armasm_dom1361289865686.htm) "Branch with a link": perform an unconditional branch and store the return address in x30 (the link register). When the subroutine is finished, use the `ret` instruction to jump back to the return address. * [**mov**](http://www.keil.com/support/man/docs/armasm/armasm_dom1361289878994.htm) Move a value between registers or from a constant to a register. [Here](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0024a/index.html) is the ARMv8-A developer's guide. It's a good resource if the ARM ISA is unfamiliar to you. [This page](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0024a/ch09s01s01.html) specifically outlines the register usage convention in the ABI. ### The `kernel_main` function We have seen that the boot code eventually passes control to the `kernel_main` function. Let's take a look at it: ``` #include "mini_uart.h" void kernel_main(void) { uart_init(); uart_send_string("Hello, world!\r\n"); while (1) { uart_send(uart_recv()); } } ``` This function is one of the simplest in the kernel. It works with the `Mini UART` device to print to screen and read user input. The kernel just prints `Hello, world!` and then enters an infinite loop that reads characters from the user and sends them back to the screen. ### Raspberry Pi devices Now we are going to dig into something specific to the Raspberry Pi. Before we begin, I recommend that you download the [BCM2837 ARM Peripherals manual](https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf). BCM2837 is a board that is used by the Raspberry Pi 3 Models B, and B+. Sometime in our discussion, I will also mention BCM2835 and BCM2836 - those are names of the board used in older versions of the Raspberry Pi. Before we proceed to the implementation details, I want to share some basic concepts on how to work with memory-mapped devices. BCM2837 is a simple [SOC (System on a chip)](https://en.wikipedia.org/wiki/System_on_a_chip) board. In such a board, access to all devices is performed via memory-mapped registers. The Raspberry Pi 3 reserves the memory above address `0x3F000000` for devices. To activate or configure a particular device, you need to write some data in one of the device's registers. A device register is just a 32-bit region of memory. The meaning of each bit in each device register is described in the `BCM2837 ARM Peripherals` manual. Take a look at section 1.2.3 ARM physical addresses in the manual and the surrounding documentation for more context on why we use `0x3F000000` as a base address (even though `0x7E000000` is used throughout the manual). From the `kernel_main` function, you can guess that we are going to work with a Mini UART device. UART stands for [Universal asynchronous receiver-transmitter](https://en.wikipedia.org/wiki/Universal_asynchronous_receiver-transmitter). This device is capable of converting values stored in one of its memory mapped registers to a sequence of high and low voltages. This sequence is passed to your computer via the `TTL-to-serial cable` and is interpreted by your terminal emulator. We are going to use the Mini UART to facilitate communication with our Raspberry Pi. If you want to see the specification of the Mini UART registers, please go to page 8 of the `BCM2837 ARM Peripherals` manual. A Raspberry Pi has two UARTs: Mini UART and PL011 UART. In this tutorial, we are going to work only with the first one, because it is simpler. There is, however, an optional [exercise](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/docs/lesson01/exercises.md) that shows how to work with PL011 UART. You can refer to the [official documentation](https://www.raspberrypi.org/documentation/configuration/uart.md) if you want to find out more about Raspberry Pi UARTs and learn the difference between them. Another device that you need to familiarize yourself with is the GPIO [General-purpose input/output](https://en.wikipedia.org/wiki/General-purpose_input/output). GPIOs are responsible for controlling `GPIO pins`. You should be able to easily recognize them in the image below: ![Raspberry Pi GPIO pins](../../images/gpio-pins.jpg) The GPIO can be used to configure the behavior of different GPIO pins. For example, to be able to use the Mini UART, we need to activate pins 14 and 15 and set them up to use this device. The image below illustrates how numbers are assigned to the GPIO pins: ![Raspberry Pi GPIO pin numbers](../../images/gpio-numbers.png) ### Mini UART initialization Now let's take a look at how mini UART is initialized. This code is defined in [mini_uart.c](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson01/src/mini_uart.c): ``` void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio 15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } ``` Here, we use the two functions `put32` and `get32`. Those functions are very simple; they allow us to read and write some data to and from a 32-bit register. You can take a look at how they are implemented in [utils.S](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson01/src/utils.S). `uart_init` is one of the most complex and important functions in this lesson, and we will continue to examine it in the next three sections. #### GPIO alternative function selection First, we need to activate the GPIO pins. Most of the pins can be used with different devices, so before using a particular pin, we need to select the pin's `alternative function`. An `alternative function` is just a number from 0 to 5 that can be set for each pin and configures which device is connected to the pin. You can see the list of all available GPIO alternative functions in the image below (the image is taken from page 102 of `BCM2837 ARM Peripherals` manual): ![Raspberry Pi GPIO alternative functions](../../images/alt.png?raw=true) Here you can see that pins 14 and 15 have the TXD1 and RXD1 alternative functions available. This means that if we select alternative function number 5 for pins 14 and 15, they will be used as a Mini UART Transmit Data pin and Mini UART Receive Data pin, respectively. The `GPFSEL1` register is used to control alternative functions for pins 10-19. The meaning of all the bits in those registers is shown in the following table (page 92 of `BCM2837 ARM Peripherals` manual): ![Raspberry Pi GPIO function selector](../../images/gpfsel1.png?raw=true) So now you know everything you need to understand the following lines of code that are used to configure GPIO pins 14 and 15 to work with the Mini UART device: ``` unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio 15 put32(GPFSEL1,selector); ``` #### GPIO pull-up/down When you work with Raspberry Pi GPIO pins, you will often encounter terms such as pull-up/pull-down. These concepts are explained in great detail in [this](https://grantwinney.com/using-pullup-and-pulldown-resistors-on-the-raspberry-pi/) article. For those who are too lazy to read the whole article, I will briefly explain the pull-up/pull-down concept. If you use a particular pin as input and don't connect anything to this pin, you will not be able to identify whether the value of the pin is 1 or 0. In fact, the device will report random values. The pull-up/pull-down mechanism allows you to overcome this issue. If you set the pin to the pull-up state and nothing is connected to it, it will report `1` all the time (for the pull-down state, the value will always be 0). In our case, we need neither the pull-up nor the pull-down state, because both the 14 and 15 pins are going to be connected all the time. The pin state is preserved even after a reboot, so before using any pin, we always have to initialize its state. There are three available states: pull-up, pull-down, and neither (to remove the current pull-up or pull-down state), and we need the third one. Switching between pin states is not a very simple procedure because it requires physically toggling a switch on the electric circuit. This process involves the `GPPUD` and `GPPUDCLK` registers and is described on page 101 of the `BCM2837 ARM Peripherals` manual. I copied the description here: ``` The GPIO Pull-up/down Clock Registers control the actuation of internal pull-downs on the respective GPIO pins. These registers must be used in conjunction with the GPPUD register to effect GPIO Pull-up/down changes. The following sequence of events is required: 1. Write to GPPUD to set the required control signal (i.e. Pull-up or Pull-Down or neither to remove the current Pull-up/down) 2. Wait 150 cycles – this provides the required set-up time for the control signal 3. Write to GPPUDCLK0/1 to clock the control signal into the GPIO pads you wish to modify – NOTE only the pads which receive a clock will be modified, all others will retain their previous state. 4. Wait 150 cycles – this provides the required hold time for the control signal 5. Write to GPPUD to remove the control signal 6. Write to GPPUDCLK0/1 to remove the clock ``` This procedure describes how we can remove both the pull-up and pull-down states from a pin, which is what we are doing for pins 14 and 15 in the following code: ``` put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); ``` #### Initializing the Mini UART Now our Mini UART is connected to the GPIO pins, and the pins are configured. The rest of the `uart_init` function is dedicated to Mini UART initialization. ``` put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver ``` Let's examine this code snippet line by line. ``` put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) ``` This line enables the Mini UART. We must do this in the beginning, because this also enables access to all the other Mini UART registers. ``` put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) ``` Here we disable the receiver and transmitter before the configuration is finished. We also permanently disable auto-flow control because it requires us to use additional GPIO pins, and the TTL-to-serial cable doesn't support it. For more information about auto-flow control, you can refer to [this](http://www.deater.net/weave/vmwprod/hardware/pi-rts/) article. ``` put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts ``` It is possible to configure the Mini UART to generate a processor interrupt each time new data is available. We are going to start working with interrupts in lesson 3, so for now, we will just disable this feature. ``` put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode ``` Mini UART can support either 7- or 8-bit operations. This is because an ASCII character is 7 bits for the standard set and 8 bits for the extended. We are going to use 8-bit mode. ``` put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high ``` The RTS line is used in the flow control and we don't need it. Set it to be high all the time. ``` put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 ``` The baud rate is the rate at which information is transferred in a communication channel. “115200 baud” means that the serial port is capable of transferring a maximum of 115200 bits per second. The baud rate of your Raspberry Pi mini UART device should be the same as the baud rate in your terminal emulator. The Mini UART calculates baud rate according to the following equation: ``` baudrate = system_clock_freq / (8 * ( baudrate_reg + 1 )) ``` The `system_clock_freq` is 250 MHz, so we can easily calculate the value of `baudrate_reg` as 270. ``` put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver ``` After this line is executed, the Mini UART is ready for work! ### Sending data using the Mini UART After the Mini UART is ready, we can try to use it to send and receive some data. To do this, we can use the following two functions: ``` void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } ``` Both of the functions start with an infinite loop, the purpose of which is to verify whether the device is ready to transmit or receive data. We are using the `AUX_MU_LSR_REG` register to do this. Bit zero, if set to 1, indicates that the data is ready; this means that we can read from the UART. Bit five, if set to 1, tells us that the transmitter is empty, meaning that we can write to the UART. Next, we use `AUX_MU_IO_REG` to either store the value of the transmitted character or read the value of the received character. We also have a very simple function that is capable of sending strings instead of characters: ``` void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } ``` This function just iterates over all characters in a string and sends them one by one. ### Raspberry Pi config The Raspberry Pi startup sequence is the following (simplified): 1. The device is powered on. 1. The GPU starts up and reads the `config.txt` file from the boot partition. This file contains some configuration parameters that the GPU uses to further adjust the startup sequence. 1. `kernel8.img` is loaded into memory and executed. To be able to run our simple OS, the `config.txt` file should be the following: ``` kernel_old=1 disable_commandline_tags=1 ``` * `kernel_old=1` specifies that the kernel image should be loaded at address 0. * `disable_commandline_tags` instructs the GPU to not pass any command line arguments to the booted image. ### Testing the kernel Now that we have gone through all of the source code, it is time to see it work. To build and test the kernel you need to do the following: 1. Execute `./build.sh` or `./build.bat` from [src/lesson01](https://github.com/s-matyukevich/raspberry-pi-os/tree/master/src/lesson01) in order to build the kernel. 1. Copy the generated `kernel8.img` file to the `boot` partition of your Raspberry Pi flash card and delete `kernel7.img` as well as any other `kernel*.img` files that may be present on your SD card. Make sure you left all other files in the boot partition untouched (see [43](https://github.com/s-matyukevich/raspberry-pi-os/issues/43) and [158](https://github.com/s-matyukevich/raspberry-pi-os/issues/158) issues for details) 1. Modify the `config.txt` file as described in the previous section. 1. Connect the USB-to-TTL serial cable as described in the [Prerequisites](../Prerequisites.md). 1. Power on your Raspberry Pi. 1. Open your terminal emulator. You should be able to see the `Hello, world!` message there. Note that the sequence of steps described above asumes that you have Raspbian installed on your SD card. It is also posible to run the RPi OS using an empty SD card. 1. Prepare your SD card: * Use an MBR partition table * Format the boot partition as FAT32 > The card should be formatted exactly in the same way as it is required to install Raspbian. Check `HOW TO FORMAT AN SD CARD AS FAT` section in the [official documentation](https://www.raspberrypi.org/documentation/installation/noobs.md) for more information. 1. Copy the following files to the card: * [bootcode.bin](https://github.com/raspberrypi/firmware/blob/master/boot/bootcode.bin) This is the GPU bootloader, it contains the GPU code to start the GPU and load the GPU firmware. * [start.elf](https://github.com/raspberrypi/firmware/blob/master/boot/start.elf) This is the GPU firmware. It reads `config.txt` and enables the GPU to load and run ARM specific user code from `kernel8.img` 1. Copy `kernel8.img` and `config.txt` files. 1. Connect the USB-to-TTL serial cable. 1. Power on your Raspberry Pi. 1. Use your terminal emulator to connect to the RPi OS. Unfortunately, all Raspberry Pi firmware files are closed-sourced and undocumented. For more information about the Raspberry Pi startup sequence, you can refer to some unofficial sources, like [this](https://raspberrypi.stackexchange.com/questions/10442/what-is-the-boot-sequence) StackExchange question or [this](https://github.com/DieterReuter/workshop-raspberrypi-64bit-os/blob/master/part1-bootloader.md) Github repository. ##### Previous Page [Prerequisites](../../docs/Prerequisites.md) ##### Next Page 1.2 [Kernel Initialization: Linux project structure](../../docs/lesson01/linux/project-structure.md) ================================================ FILE: docs/lesson02/exercises.md ================================================ ## 2.3: Exercises 1. Instead of jumping directly from EL3 to EL1, try to get to EL2 first and only then switch to EL1. 1. One issue that I ran into when working on this lesson was that if FP/SIMD registers are used then everything works well at EL3, but as soon as you get to EL1 print function stops working. This was the reason why I've added [-mgeneral-regs-only](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/Makefile#L3) parameter to the compiler options. Now I want you to remove this parameter and reproduce this behavior. Next, you can use `objdump` tool to see how exactly gcc make use of FP/SIMD registers in the absence of `-mgeneral-regs-only` flag. Finally, I want you to use 'cpacr_el1' to allow using FP/SIMD registers. 1. Adapt lesson 02 to run on qemu. Check [this](https://github.com/s-matyukevich/raspberry-pi-os/issues/8) issue for reference. ##### Previous Page 2.2 [Processor initialization: Linux](../../docs/lesson02/linux.md) ##### Next Page 3.1 [Interrupt handling: RPi OS](../../docs/lesson03/rpi-os.md) ================================================ FILE: docs/lesson02/linux.md ================================================ ## 2.2: Processor initialization (Linux) We stopped our exploration of the Linux kernel at [stext](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/head.S#L116) function, which is the entry point of `arm64` architecture. This time we are going to go a little bit deeper and find some similarities with the code that we have already implemented in this and previous lessons. You may find this chapter a little bit boring because it mostly discusses different ARM system registers and how they are used in the Linux kernel. But I still consider it very important for the following reasons: 1. It is necessary to understand the interface that the hardware provides to the software. Just by knowing this interface you will be able, in many cases, to deconstruct how a particular kernel feature is implemented and how software and hardware collaborate to implement this feature. 1. Different options in the system register are usually related to enabling/disabling various hardware features. If you learn what different system registers an ARM processor have you will already have an idea what kind of functionality it supports. Ok, now let's resume our investigation of the `stext` function. ``` ENTRY(stext) bl preserve_boot_args bl el2_setup // Drop to EL1, w0=cpu_boot_mode adrp x23, __PHYS_OFFSET and x23, x23, MIN_KIMG_ALIGN - 1 // KASLR offset, defaults to 0 bl set_cpu_boot_mode_flag bl __create_page_tables /* * The following calls CPU setup code, see arch/arm64/mm/proc.S for * details. * On return, the CPU will be ready for the MMU to be turned on and * the TCR will have been set. */ bl __cpu_setup // initialise processor b __primary_switch ENDPROC(stext) ``` ### preserve_boot_args [preserve_boot_args](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/head.S#L136) function is responsible for saving parameters, passed to the kernel by the bootloader. ``` preserve_boot_args: mov x21, x0 // x21=FDT adr_l x0, boot_args // record the contents of stp x21, x1, [x0] // x0 .. x3 at kernel entry stp x2, x3, [x0, #16] dmb sy // needed before dc ivac with // MMU off mov x1, #0x20 // 4 x 8 bytes b __inval_dcache_area // tail call ENDPROC(preserve_boot_args) ``` Accordingly to the [kernel boot protocol](https://github.com/torvalds/linux/blob/v4.14/Documentation/arm64/booting.txt#L150), parameters are passed to the kernel in registers `x0 - x3`. `x0` contains the physical address of device tree blob (`.dtb`) in system RAM. `x1 - x3` are reserved for future usage. What this function is doing is copying the content of `x0 - x3` registers to the [boot_args](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/setup.c#L93) array and then [invalidate](https://developer.arm.com/docs/den0024/latest/caches/cache-maintenance) the corresponding cache line from the data cache. Cache maintenance in a multiprocessor system is a large topic on its own, and we are going to skip it for now. For those who are interested in this subject, I can recommend reading [Caches](https://developer.arm.com/docs/den0024/latest/caches) and [Multi-core processors](https://developer.arm.com/docs/den0024/latest/multi-core-processors) chapters of the `ARM Programmer’s Guide`. ### el2_setup Accordingly to the [arm64boot protocol](https://github.com/torvalds/linux/blob/v4.14/Documentation/arm64/booting.txt#L159), the kernel can be booted in either EL1 or EL2. In the second case, the kernel has access to the virtualization extensions and is able to act as a host operating system. If we are lucky enough to be booted in EL2, [el2_setup](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/head.S#L386) function is called. It is responsible for configuring different parameters, accessible only at EL2, and dropping to EL1. Now I am going to split this function into small parts and explain each piece one by one. ``` msr SPsel, #1 // We want to use SP_EL{1,2} ``` Dedicated stack pointer will be used for both EL1 and EL2. Another option is to reuse stack pointer from EL0. ``` mrs x0, CurrentEL cmp x0, #CurrentEL_EL2 b.eq 1f ``` Only if current EL is EL2 branch to label `1`, otherwise we can't do EL2 setup and not much is left to be done in this function. ``` mrs x0, sctlr_el1 CPU_BE( orr x0, x0, #(3 << 24) ) // Set the EE and E0E bits for EL1 CPU_LE( bic x0, x0, #(3 << 24) ) // Clear the EE and E0E bits for EL1 msr sctlr_el1, x0 mov w0, #BOOT_CPU_MODE_EL1 // This cpu booted in EL1 isb ret ``` If it happens that we execute at EL1, `sctlr_el1` register is updated so that CPU works in either `big-endian` of `little-endian` mode depending on the value of [CPU_BIG_ENDIAN](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/Kconfig#L612) config setting. Then we just exit from the `el2_setup` function and return [BOOT_CPU_MODE_EL1](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/virt.h#L55) constant. Accordingly to [ARM64 Function Calling Conventions](http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf) return value should be placed in `x0` register (or `w0` in our case. You can think about `w0` register as the first 32 bit of `x0`.). ``` 1: mrs x0, sctlr_el2 CPU_BE( orr x0, x0, #(1 << 25) ) // Set the EE bit for EL2 CPU_LE( bic x0, x0, #(1 << 25) ) // Clear the EE bit for EL2 msr sctlr_el2, x0 ``` If it appears that we are booted in EL2 we are doing the same kind of setup for EL2 (note that this time `sctlr_el2` register is used instead of `sctlr_el1`.). ``` #ifdef CONFIG_ARM64_VHE /* * Check for VHE being present. For the rest of the EL2 setup, * x2 being non-zero indicates that we do have VHE, and that the * kernel is intended to run at EL2. */ mrs x2, id_aa64mmfr1_el1 ubfx x2, x2, #8, #4 #else mov x2, xzr #endif ``` If [Virtualization Host Extensions (VHE)](https://developer.arm.com/products/architecture/a-profile/docs/100942/latest/aarch64-virtualization) is enabled via [ARM64_VHE](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/Kconfig#L926) config variable and the host machine supports them, `x2` then is updated with non zero value. `x2` will be used to check whether `VHE` is enabled later in the same function. ``` mov x0, #HCR_RW // 64-bit EL1 cbz x2, set_hcr orr x0, x0, #HCR_TGE // Enable Host Extensions orr x0, x0, #HCR_E2H set_hcr: msr hcr_el2, x0 isb ``` Here we set `hcr_el2` register. We used the same register to set 64-bit execution mode for EL1 in the RPi OS. This is exactly what is done in the first line of the provided code sample. Also if `x2 != 0`, which means that VHE is available and the kernel is configured to use it, `hcr_el2` is also used to enable VHE. ``` /* * Allow Non-secure EL1 and EL0 to access physical timer and counter. * This is not necessary for VHE, since the host kernel runs in EL2, * and EL0 accesses are configured in the later stage of boot process. * Note that when HCR_EL2.E2H == 1, CNTHCTL_EL2 has the same bit layout * as CNTKCTL_EL1, and CNTKCTL_EL1 accessing instructions are redefined * to access CNTHCTL_EL2. This allows the kernel designed to run at EL1 * to transparently mess with the EL0 bits via CNTKCTL_EL1 access in * EL2. */ cbnz x2, 1f mrs x0, cnthctl_el2 orr x0, x0, #3 // Enable EL1 physical timers msr cnthctl_el2, x0 1: msr cntvoff_el2, xzr // Clear virtual offset ``` Next piece of code is well explained in the comment above it. I have nothing to add. ``` #ifdef CONFIG_ARM_GIC_V3 /* GICv3 system register access */ mrs x0, id_aa64pfr0_el1 ubfx x0, x0, #24, #4 cmp x0, #1 b.ne 3f mrs_s x0, SYS_ICC_SRE_EL2 orr x0, x0, #ICC_SRE_EL2_SRE // Set ICC_SRE_EL2.SRE==1 orr x0, x0, #ICC_SRE_EL2_ENABLE // Set ICC_SRE_EL2.Enable==1 msr_s SYS_ICC_SRE_EL2, x0 isb // Make sure SRE is now set mrs_s x0, SYS_ICC_SRE_EL2 // Read SRE back, tbz x0, #0, 3f // and check that it sticks msr_s SYS_ICH_HCR_EL2, xzr // Reset ICC_HCR_EL2 to defaults 3: #endif ``` Next code snippet is executed only if GICv3 is available and enabled. GIC stands for Generic Interrupt Controller. v3 version of the GIC specification adds a few features, that are particularly useful in virtualization context. For example, with GICv3 it becomes possible to have LPIs (Locality-specific Peripheral Interrupt). Such interrupts are routed via message bus and their configuration is held in special tables in memory. The provided code is responsible for enabling SRE (System Register Interface) This step must be done before we will be able to use `ICC_*_ELn` registers and take advantages of GICv3 features. ``` /* Populate ID registers. */ mrs x0, midr_el1 mrs x1, mpidr_el1 msr vpidr_el2, x0 msr vmpidr_el2, x1 ``` `midr_el1` and `mpidr_el1` are read-only registers from the Identification registers group. They provide various information about processor manufacturer, processor architecture name, number of cores and some other info. It is possible to change this information for all readers that try to access it from EL1. Here we populate `vpidr_el2` and ` vmpidr_el2` with the values taken from `midr_el1` and `mpidr_el1`, so this information is the same whether you try to access it from EL1 or higer exception levels. ``` #ifdef CONFIG_COMPAT msr hstr_el2, xzr // Disable CP15 traps to EL2 #endif ``` When the processor is executing in 32-bit execution mode, there is a concept of "coprocessor". The coprocessor can be used to access information, that in 64-bit execution mode is typically accessed via system registers. You can read about what exactly is accessible via coprocessor [in the official documentation](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0311d/I1014521.html). `msr hstr_el2, xzr` instruction allows using coprocessor from lower exception levels. This makes sense to do only when compatibility mode is enabled (in this mode kernel can run 32-bit user applications on top of 64-bit kernel.). ``` /* EL2 debug */ mrs x1, id_aa64dfr0_el1 // Check ID_AA64DFR0_EL1 PMUVer sbfx x0, x1, #8, #4 cmp x0, #1 b.lt 4f // Skip if no PMU present mrs x0, pmcr_el0 // Disable debug access traps ubfx x0, x0, #11, #5 // to EL2 and allow access to 4: csel x3, xzr, x0, lt // all PMU counters from EL1 /* Statistical profiling */ ubfx x0, x1, #32, #4 // Check ID_AA64DFR0_EL1 PMSVer cbz x0, 6f // Skip if SPE not present cbnz x2, 5f // VHE? mov x1, #(MDCR_EL2_E2PB_MASK << MDCR_EL2_E2PB_SHIFT) orr x3, x3, x1 // If we don't have VHE, then b 6f // use EL1&0 translation. 5: // For VHE, use EL2 translation orr x3, x3, #MDCR_EL2_TPMS // and disable access from EL1 6: msr mdcr_el2, x3 // Configure debug traps ``` This piece of code is responsible for configuring `mdcr_el2` (Monitor Debug Configuration Register (EL2)). This register is responsible for setting different debug traps, related to the virtualization extension. I am going to leave the details of this code block unexplained because debug and tracing are a little bit out of scope for our discussion. If you are interested in details, I can recommend you to read the description of `mdcr_el2` register on page `2810` of the [AArch64-Reference-Manual](https://developer.arm.com/docs/ddi0487/ca/arm-architecture-reference-manual-armv8-for-armv8-a-architecture-profile). ``` /* Stage-2 translation */ msr vttbr_el2, xzr ``` When your OS is used as a hypervisor it should provide complete memory isolation for its guest OSes. Stage 2 virtual memory translation is used precisely for this purpose: each guest OS thinks that it owns all system memory, though in reality each memory access is mapped to the physical memory by stage 2 translation. `vttbr_el2` holds the base address of the translation table for the stage 2 translation. At this point, stage 2 translation is disabled, and `vttbr_el2` should be set to 0. ``` cbz x2, install_el2_stub mov w0, #BOOT_CPU_MODE_EL2 // This CPU booted in EL2 isb ret ``` First `x2` is compared to `0` to check whether VHE is enabled. If yes - jump to `install_el2_stub` label, otherwise record that CPU is booted in EL2 mode and exit from `el2_setup` function. In the latter case, the processor continues to operate in EL2 mode and EL1 will not be used at all. ``` install_el2_stub: /* sctlr_el1 */ mov x0, #0x0800 // Set/clear RES{1,0} bits CPU_BE( movk x0, #0x33d0, lsl #16 ) // Set EE and E0E on BE systems CPU_LE( movk x0, #0x30d0, lsl #16 ) // Clear EE and E0E on LE systems msr sctlr_el1, x0 ``` If we reach this point it means that we don't need VHE and are going to switch to EL1 soon, so early EL1 initialization needs to be done here. The copied code snippet is responsible for `sctlr_el1` (System Control Register) initialization. We already did the same job [here](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/src/boot.S#L18) for the RPi OS. ``` /* Coprocessor traps. */ mov x0, #0x33ff msr cptr_el2, x0 // Disable copro. traps to EL2 ``` This code allows EL1 to access `cpacr_el1` register and, as a result, to control access to Trace, Floating-point, and Advanced SIMD functionality. ``` /* Hypervisor stub */ adr_l x0, __hyp_stub_vectors msr vbar_el2, x0 ``` We don't plan to use EL2 now, though some functionality requires it. We need it, for example, to implement [kexec](https://linux.die.net/man/8/kexec) system call that enables you to load and boot into another kernel from the currently running kernel. [_hyp_stub_vectors](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/hyp-stub.S#L33) holds the addresses of all exception handlers for EL2. We are going to implement exception handling functionality for EL1 in the next lesson, after we talk about interrupts and exception handling in details. ``` /* spsr */ mov x0, #(PSR_F_BIT | PSR_I_BIT | PSR_A_BIT | PSR_D_BIT |\ PSR_MODE_EL1h) msr spsr_el2, x0 msr elr_el2, lr mov w0, #BOOT_CPU_MODE_EL2 // This CPU booted in EL2 eret ``` Finally, we need to initialize processor state at EL1 and switch exception levels. We already did it for the [RPi OS](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/src/boot.S#L27-L33) so I am not going to explain the details of this code. The only new thing here is the way how `elr_el2` is initialized. `lr` or Link Register is an alias for `x30`. Whenever you execute `bl` (Branch Link) instruction `x30` is automatically populated with the address of the current instruction. This fact is usually used by `ret` instruction, so it knows where exactly to return. In our case, `lr` points [here](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/head.S#L119) and, because of the way how we initialized `elr_el2`, this is also the place from which the execution is going to be resumed after switching to EL1. ### Processor initialization at EL1 Now we are back to the `stext` function. Next few lines are not very important for us, but I want to explain them for the sake of completeness. ``` adrp x23, __PHYS_OFFSET and x23, x23, MIN_KIMG_ALIGN - 1 // KASLR offset, defaults to 0 ``` [KASLR](https://lwn.net/Articles/569635/), or Kernel address space layout randomization, is a technique that allows to place the kernel at a random address in the memory. This is required only for security reasons. For more information, you can read the link above. ``` bl set_cpu_boot_mode_flag ``` Here CPU boot mode is saved into [__boot_cpu_mode](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/virt.h#L74) variable. The code that does this is very similar to `preserve_boot_args` function that we explored previously. ``` bl __create_page_tables bl __cpu_setup // initialise processor b __primary_switch ``` The last 3 functions are very important, but they all are related to virtual memory management, so we are going to postpone their detailed exploration until the lesson 6. For now, I just want to brefely describe there meanings. * `__create_page_tables` As its name stands this one is responsible for creating Page Tables. * `__cpu_setup` Initialize various processor settings, mostly specific for virtual memory management. * `__primary_switch` Enable MMU and jump to [start_kernel](https://github.com/torvalds/linux/blob/v4.14/init/main.c#L509) function, which is architecture independent starting point. ### Conclusion In this chapter, we briefly discussed how a processor is initialized when the Linux kernel is booted. In the next lesson, we will continue to closely work with the ARM processor and investigate a vital topic for any OS: interrupt handling. ##### Previous Page 2.1 [Processor initialization: RPi OS](../../docs/lesson02/rpi-os.md) ##### Next Page 2.3 [Processor initialization: Exercises](../../docs/lesson02/exercises.md) ================================================ FILE: docs/lesson02/rpi-os.md ================================================ ## 2.1: Processor initialization In this lesson, we are going to work more closely with the ARM processor. It has some essential features that can be utilized by the OS. The first such feature is called "Exception levels". ### Exception levels Each ARM processor that supports ARM.v8 architecture has 4 exception levels. You can think about an exception level (or `EL` for short) as a processor execution mode in which only a subset of all operations and registers is available. The least privileged exception level is level 0. When processor operates at this level, it mostly uses only general purpose registers (X0 - X30) and stack pointer register (SP). EL0 also allows using `STR` and `LDR` commands to load and store data to and from memory and a few other instructions commonly used by a user program. An operating system should deal with exception levels because it needs to implement process isolation. A user process should not be able to access other process's data. To achieve such behavior, an operating system always runs each user process at EL0. Operating at this exception level a process can only use it's own virtual memory and can't access any instructions that change virtual memory settings. So, to ensure process isolation, an OS need to prepare separate virtual memory mapping for each process and put the processor into EL0 before transferring execution to a user process. An operating system itself usually works at EL1. While running at this exception level processor gets access to the registers that allows configuring virtual memory settings as well as to some system registers. Raspberry Pi OS also will be using EL1. We are not going to use exceptions levels 2 and 3 a lot, but I just want to briefly describe them so you can get an idea why they are needed. EL2 is used in a scenario when we are using a hypervisor. In this case host operating system runs at EL2 and guest operating systems can only use EL 1. This allows host OS to isolate guest OSes in a similar way how OS isolates user processes. EL3 is used for transitions from ARM "Secure World" to "Insecure world". This abstraction exist to provide full hardware isolation between the software running in two different "worlds". Application from an "Insecure world" can in no way access or modify information (both instruction and data) that belongs to "Secure world", and this restriction is enforced at the hardware level. ### Debugging the kernel Next thing that I want to do is to figure out which Exception level we are currently using. But when I tried to do this, I realized that the kernel could only print some constant string on a screen, but what I need is some analog of [printf](https://en.wikipedia.org/wiki/Printf_format_string) function. With `printf` I can easily display values of different registers and variables. Such functionality is essential for the kernel development because you don't have any other debugger support and `printf` becomes the only mean by which you can figure out what is going on inside your program. For the RPi OS I decided not to reinvent the wheel and use one of [existing printf implementations](http://www.sparetimelabs.com/tinyprintf/tinyprintf.php) This function consists mostly from string manipulations and is not very interesting from a kernel developer point of view. The implementation that I used is very small and don't have external dependencies, that allows it to be easily integrated into the kernel. The only thing that I have to do is to define `putc` function that can send a single character to the screen. This function is defined [here](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/src/mini_uart.c#L59) and it just uses already existing `uart_send` function. Also, we need to initialize the `printf` library and specify the location of the `putc` function. This is done in a single [line of code](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/src/kernel.c#L8). ### Finding current Exception level Now, when we are equipped with the `printf` function, we can complete our original task: figure out at which exception level the OS is booted. A small function that can answer this question is defined [here](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/src/utils.S#L1) and looks like this. ``` .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret ``` Here we use `mrs` instruction to read the value from `CurrentEL` system register into `x0` register. Then we shift this value 2 bits to the right (we need to do this because first 2 bits in the `CurrentEL` register are reserved and always have value 0) And finally in the register `x0` we have an integer number indicating current exception level. Now the only thing that is left is to display this value, like [this](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/src/kernel.c#L10). ``` int el = get_el(); printf("Exception level: %d \r\n", el); ``` If you reproduce this experiment, you should see `Exception level: 3` on the screen. ### Changing current exception level In ARM architecture there is no way how a program can increase its own exception level without the participation of the software that already runs on a higher level. This makes a perfect sense: otherwise, any program would be able to escape its assigned EL and access other programs data. Current EL can be changed only if an exception is generated. This can happen if a program executes some illegal instruction (for example, tries to access memory location at a nonexisting address, or tries to divide by 0) Also an application can run `svc` instruction to generate an exception on purpose. Hardware generated interrupts are also handled as a special type of exceptions. Whenever an exception is generated the following sequence of steps takes place (In the description I am assuming that the exception is handled at EL `n`, were `n` could be 1, 2 or 3). 1. Address of the current instruction is saved in the `ELR_ELn` register. (It is called `Exception link register`) 1. Current processor state is stored in `SPSR_ELn` register (`Saved Program Status Register`) 1. An exception handler is executed and does whatever job it needs to do. 1. Exception handler calls `eret` instruction. This instruction restores processor state from `SPSR_ELn` and resumes execution starting from the address, stored in the `ELR_ELn` register. In practice the process is a little more complicated because exception handler also needs to store the state of all general purpose registers and restore it back afterwards, but we will discuss this process in details in the next lesson. For now, we need just to understand the process in general and remember the meaning of the `ELR_ELn` and `SPSR_ELn` registers. An important thing to know is that exception handler is not obliged to return to the same location from which the exception originates. Both `ELR_ELn` and `SPSR_ELn` are writable and exception handler can modify them if it wants to. We are going to use this technique to our advantage when we try to switch from EL3 to EL1 in our code. ### Switching to EL1 Strictly speaking, our operating system is not obliged to switch to EL1, but EL1 is a natural choice for us because this level has just the right set of privileges to implement all common OS tasks. It also will be an interesting exercise to see how switching exceptions levels works in action. Let's take a look at the [source code that does this](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/src/boot.S#L17). ``` master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret ``` As you can see the code consists mostly of configuring a few system registers. Now we are going to examine those registers one by one. In order to do this we first need to download [AArch64-Reference-Manual](https://developer.arm.com/docs/ddi0487/ca/arm-architecture-reference-manual-armv8-for-armv8-a-architecture-profile). This document contains the detailed specification of the `ARM.v8` architecture. #### SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. ``` ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ``` Here we set the value of the `sctlr_el1` system register. `sctlr_el1` is responsible for configuring different parameters of the processor, when it operates at EL1. For example, it controls whether the cache is enabled and, what is most important for us, whether the MMU (Memory Management Unit) is turned on. `sctlr_el1` is accessible from all exception levels higher or equal than EL1 (you can infer this from `_el1` postfix) `SCTLR_VALUE_MMU_DISABLED` constant is defined [here](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/include/arm/sysregs.h#L16) Individual bits of this value are defined like this: * `#define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11)` Some bits in the description of `sctlr_el1` register are marked as `RES1`. Those bits are reserved for future usage and should be initialized with `1`. * `#define SCTLR_EE_LITTLE_ENDIAN (0 << 25)` Exception [Endianness](https://en.wikipedia.org/wiki/Endianness). This field controls endianess of explicit data access at EL1. We are going to configure the processor to work only with `little-endian` format. * `#define SCTLR_EOE_LITTLE_ENDIAN (0 << 24)` Similar to previous field but this one controls endianess of explicit data access at EL0, instead of EL1. * `#define SCTLR_I_CACHE_DISABLED (0 << 12)` Disable instruction cache. We are going to disable all caches for simplicity. You can find more information about data and instruction caches [here](https://stackoverflow.com/questions/22394750/what-is-meant-by-data-cache-and-instruction-cache). * `#define SCTLR_D_CACHE_DISABLED (0 << 2)` Disable data cache. * `#define SCTLR_MMU_DISABLED (0 << 0)` Disable MMU. MMU must be disabled until the lesson 6, where we are going to prepare page tables and start working with virtual memory. #### HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. ``` ldr x0, =HCR_VALUE msr hcr_el2, x0 ``` We are not going to implement our own [hypervisor](https://en.wikipedia.org/wiki/Hypervisor). Stil we need to use this register because, among other settings, it controls the execution state at EL1. Execution state must be `AArch64` and not `AArch32`. This is configured [here](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/include/arm/sysregs.h#L22). #### SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. ``` ldr x0, =SCR_VALUE msr scr_el3, x0 ``` This register is responsible for configuring security settings. For example, it controls whether all lower levels are executed in "secure" or "nonsecure" state. It also controls execution state at EL2. [here](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/include/arm/sysregs.h#L26) we set that EL2 will execute at `AArch64` state, and all lower exception levels will be "non secure". #### SPSR_EL3, Saved Program Status Register (EL3), Page 389 of AArch64-Reference-Manual. ``` ldr x0, =SPSR_VALUE msr spsr_el3, x0 ``` This register should be already familiar to you - we mentioned it when discussed the process of changing exception levels. `spsr_el3` contains processor state, that will be restored after we execute `eret` instruction. It is worth saying a few words explaining what processor state is. Processor state includes the following information: * **Condition Flags** Those flags contains information about previously executed operation: whether the result was negative (N flag), zero (A flag), has unsigned overflow (C flag) or has signed overflow (V flag). Values of those flags can be used in conditional branch instructions. For example, `b.eq` instruction will jump to the provided label only if the result of the last comparison operation is equal to 0. The processor checks this by testing whether Z flag is set to 1. * **Interrupt disable bits** Those bits allows to enable/disable different types of interrupts. * Some other information, required to fully restore the processor execution state after an exception is handled. Usually `spsr_el3` is saved automatically when an exception is taken to EL3. However this register is writable, so we take advantage of this fact and manually prepare processor state. `SPSR_VALUE` is prepared [here](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/include/arm/sysregs.h#L35) and we initialize the following fields: * `#define SPSR_MASK_ALL (7 << 6)` After we change EL to EL1 all types of interrupts will be masked (or disabled, which is the same). * `#define SPSR_EL1h (5 << 0)` At EL1 we can either use our own dedicated stack pointer or use EL0 stack pointer. `EL1h` mode means that we are using EL1 dedicated stack pointer. #### ELR_EL3, Exception Link Register (EL3), Page 351 of AArch64-Reference-Manual. ``` adr x0, el1_entry msr elr_el3, x0 eret ``` `elr_el3` holds the address, to which we are going to return after `eret` instruction will be executed. Here we set this address to the location of `el1_entry` label. ### Conclusion That is pretty much it: when we enter `el1_entry` function the execution should be already at EL1 mode. Go ahead and try it out! ##### Previous Page 1.5 [Kernel Initialization: Exercises](../../docs/lesson01/exercises.md) ##### Next Page 2.2 [Processor initialization: Linux](../../docs/lesson02/linux.md) ================================================ FILE: docs/lesson03/exercises.md ================================================ ## 3.5: Exercises 1. Use local timer instead of the system timer to generate processor interrupts. See [this](https://github.com/s-matyukevich/raspberry-pi-os/issues/70) issue for details. 1. Handle MiniUART interrupts. Replace the final loop in the `kernel_main` function with a loop that does nothing. Setup MiniUART device to generate an interrupt as soon as the user types a new character. Implement an interrupt handler that will be responsible for printing each newly arrived character on the screen. 1. Adapt lesson 03 to run on qemu. Check [this](https://github.com/s-matyukevich/raspberry-pi-os/issues/8) issue for reference. ##### Previous Page 3.4 [Interrupt handling: Timers](../../docs/lesson03/linux/timer.md) ##### Next Page 4.1 [Process scheduler: RPi OS Scheduler](../../docs/lesson04/rpi-os.md) ================================================ FILE: docs/lesson03/linux/interrupt_controllers.md ================================================ ## 3.3: Interrupt controllers In this chapter, we are going to talk a lot about Linux drivers and how they handle interrupts. We will start with driver initialization code and then take a look at how interrupts are processed after [handle_arch_irq](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/irq.c#L44) function. ### Using device tree to find out needed devices and drivers When implementing interrupts in the RPi OS we have been working with 2 devices: system timer and interrupt controller. Now our goal will be to understand how the same devices work in Linux. The first thing we need to do is to find drivers that are responsible for working with mentioned devices. And in order to find needed drivers we can use [bcm2837-rpi-3-b.dts](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837-rpi-3-b.dts) device tree file. This is the top level device tree file that is specific for Raspberry Pi 3 Model B, it includes other more common device tree files, that are shared between different versions of Raspberry Pi. If you follow the chain of includes and search for `timer` and `interrupt-controller` you can find 4 devices. 1. [Local interrupt controller](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837.dtsi#L11) 1. [Local timer](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837.dtsi#L20) 1. Global interrupt controller. It is defined [here](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm283x.dtsi#L109) and modified [here](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837.dtsi#L72). 1. [System timer](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm283x.dtsi#L57) Stop, but why do we have 4 devices instead of 2? This requires some explanation, and we will tackle this question in the next section. ### Local vs global interrupt controllers When you think about interrupt handling in multiprocessor systems, one question you should ask yourself is which core should be responsible for processing a particular interrupt? When an interrupt occurs, are all 4 cores interrupted, or only a single one? Is it possible to route a particular interrupt to a specific core? Another question you may wonder is how one processor can notify another processor if he needs to pass some information to it? The local interrupt controller is a device that can help you in answering all those questions. It is responsible for the following tasks. * Configuring which core should receive a specific interrupt. * Sending interrupts between cores. Such interrupts are called "mailboxes" and allow cores to communicate one with each other. * Handling interrupts from local timer and performance monitors interrupts (PMU). The behavior of a local interrupt controller as well as a local timer is documented in [BCM2836 ARM-local peripherals](https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2836/QA7_rev3.4.pdf) manual. I already mentioned local timer several times. Now you probably wonder why do we need two independent timers in the system? I guess that the primary use-case for using the local timer is when you want to configure all 4 cores to receive timer interrupts simultaneously. If you use system timer you can only route interrupts to a single core. When working with the RPi OS we didn't work with either local interrupt controller or local timer. That is because by default local interrupt controller is configured in such a way that all external interrupts are sent to the first core, which is exactly what we need. We haven't used local timer because we use system timer instead. ### Local interrupt controller Accordingly to the [bcm2837.dtsi](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837.dtsi#L75) the global interrupt controller is a child of the local one. Thus it makes sense to start our exploration with the local controller. If we need to find a driver that works with a particular device, we should use [compatible](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837.dtsi#L12) property. Searching for the value of this property you can easily find that there is a single driver that is compatible with RPi local interrupt controller - here is the corresponding [definition](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2836.c#L315). ``` IRQCHIP_DECLARE(bcm2836_arm_irqchip_l1_intc, "brcm,bcm2836-l1-intc", bcm2836_arm_irqchip_l1_intc_of_init); ``` Now you can probably guess what is the procedure of a driver initialization: the kernel walks through all device definitions in the device tree and for each definition it looks for a matching driver using "compatible" property. If the driver is found, then its initialization function is called. Initialization function is provided during device registration, and in our case this function is [bcm2836_arm_irqchip_l1_intc_of_init](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2836.c#L280). ``` static int __init bcm2836_arm_irqchip_l1_intc_of_init(struct device_node *node, struct device_node *parent) { intc.base = of_iomap(node, 0); if (!intc.base) { panic("%pOF: unable to map local interrupt registers\n", node); } bcm2835_init_local_timer_frequency(); intc.domain = irq_domain_add_linear(node, LAST_IRQ + 1, &bcm2836_arm_irqchip_intc_ops, NULL); if (!intc.domain) panic("%pOF: unable to create IRQ domain\n", node); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_CNTPSIRQ, &bcm2836_arm_irqchip_timer); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_CNTPNSIRQ, &bcm2836_arm_irqchip_timer); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_CNTHPIRQ, &bcm2836_arm_irqchip_timer); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_CNTVIRQ, &bcm2836_arm_irqchip_timer); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_GPU_FAST, &bcm2836_arm_irqchip_gpu); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_PMU_FAST, &bcm2836_arm_irqchip_pmu); bcm2836_arm_irqchip_smp_init(); set_handle_irq(bcm2836_arm_irqchip_handle_irq); return 0; } ``` The initialization function takes 2 parameters: 'node' and 'parent', both of them are of the type [struct device_node](https://github.com/torvalds/linux/blob/v4.14/include/linux/of.h#L49). `node` represents the current node in the device tree, and in our case it points [here](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837.dtsi#L11) `parent` is a parent node in the device tree hierarchy, and for the local interrupt controller it points to `soc` element (`soc` stands for "system on chip" and it is the simplest possible bus which maps all device registers directly to main memory.). `node` can be used to read various properties from the current device tree node. For example, the first line of the `bcm2836_arm_irqchip_l1_intc_of_init` function reads the device base address from [reg](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837.dtsi#L13) property. However, the process is more complicated than that, because when this function is executed MMU is already enabled, and before we will be able to access some region of physical memory we must map this region to some virtual address. This is exactly what [of_iomap](https://github.com/torvalds/linux/blob/v4.14/drivers/of/address.c#L759) function is doing: it reads `reg` property of the provided node and maps the whole memory region, described by `reg` property, to some virtual memory region. Next local timer frequency is initialized in [bcm2835_init_local_timer_frequency](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2836.c#L264) function. There is nothing specific about this function: it just uses some of the registers, described in [BCM2836 ARM-local peripherals](https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2836/QA7_rev3.4.pdf) manual, to initialize local timer. Next line requires some explanations. ``` intc.domain = irq_domain_add_linear(node, LAST_IRQ + 1, &bcm2836_arm_irqchip_intc_ops, NULL); ``` Linux assigns a unique integer number to each interrupt, you can think about this number as a unique interrupt ID. This ID is used each time you want to do something with an interrupt (for example, assign a handler, or assign which CPU should handle it). Each interrupt also has a hardware interrupt number. This is usually a number that tells which interrupt line was triggered. `BCM2837 ARM Peripherals manual` has the peripheral interrupt table at page 113 - you can think about an index in this table as a hardware interrupt number. So obviously we need some mechanism to map Linux irq numbers to hardware irq number and vice versa. If there is only one interrupt controller it would be possible to use one to one mapping but in general case a more sophisticated mechanism need to be used. In Linux [struct irq_domain](https://github.com/torvalds/linux/blob/v4.14/include/linux/irqdomain.h#L152) implements such mapping. Each interrupt controller driver should create its own irq domain and register all interrupts that it can handle with this domain. Registration function returns Linux irq number that later is used to work with the interrupt. Next 6 lines are responsible for registering each supported interrupt with the irq domain. ``` bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_CNTPSIRQ, &bcm2836_arm_irqchip_timer); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_CNTPNSIRQ, &bcm2836_arm_irqchip_timer); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_CNTHPIRQ, &bcm2836_arm_irqchip_timer); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_CNTVIRQ, &bcm2836_arm_irqchip_timer); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_GPU_FAST, &bcm2836_arm_irqchip_gpu); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_PMU_FAST, &bcm2836_arm_irqchip_pmu); ``` Accordingly to [BCM2836 ARM-local peripherals](https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2836/QA7_rev3.4.pdf) manual local interrupt controller handles 10 different interrupts: 0 - 3 are interrupts from local timer, 4 - 7 are mailbox interrupts, which are used in interprocess communication, 8 corresponds to all interrupts generated by the global interrupt controller and interrupt 9 is a performance monitor interrupt. [Here](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2836.c#L67) you can see that the driver defines a set of constants that holds hardware irq number per each interrupt. The registration code above registers all interrupts, except mailbox interrupts, which are registered separately. In order to understand the registration code better lets examine [bcm2836_arm_irqchip_register_irq](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2836.c#L154) function. ``` static void bcm2836_arm_irqchip_register_irq(int hwirq, struct irq_chip *chip) { int irq = irq_create_mapping(intc.domain, hwirq); irq_set_percpu_devid(irq); irq_set_chip_and_handler(irq, chip, handle_percpu_devid_irq); irq_set_status_flags(irq, IRQ_NOAUTOEN); } ``` The first line here performs actual interrupt registration. [irq_create_mapping](https://github.com/torvalds/linux/blob/v4.14/kernel/irq/irqdomain.c#L632) takes hardware interrupt number as an input and returns Linux irq number. [irq_set_percpu_devid](https://github.com/torvalds/linux/blob/v4.14/kernel/irq/irqdesc.c#L849) configures interrupt as "per CPU", so that it will be handled only on the current CPU. This makes perfect sense because all interrupts that we are discussing now are local and they all can be handled only on the current CPU. [irq_set_chip_and_handler](https://github.com/torvalds/linux/blob/v4.14/include/linux/irq.h#L608), as its name suggest, sets irq chip and irq handler. Irq chip is a special struct, which needs to be created by the driver, that has methods for masking and unmasking a particular interrupt. The driver that we are examining right now defines 3 different irq chips: [timer](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2836.c#L118) chip, [PMU](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2836.c#L134) chip and [GPU](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2836.c#L148) chip, which controls all interrupts generated by the external peripheral devices. Handler is a function that is responsible for processing an interrupt. In this case, the handler is set to generic [handle_percpu_devid_irq](https://github.com/torvalds/linux/blob/v4.14/kernel/irq/chip.c#L859) function. This handler later will be rewritten by the global interrupt controller driver. [irq_set_status_flags](https://github.com/torvalds/linux/blob/v4.14/include/linux/irq.h#L652) in this particular case sets a flag, indicating that the current interrupt should be enabled manually and should not be enabled by default. Going back to the `bcm2836_arm_irqchip_l1_intc_of_init` function, there are only 2 calls left. The first one is [bcm2836_arm_irqchip_smp_init](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2836.c#L243). Here mailbox interrupts are enabled, allowing processors cores to communicate with each other. The last function call is extremely important - this is the place where low-level exception handling code is connected to the driver. ``` set_handle_irq(bcm2836_arm_irqchip_handle_irq); ``` [set_handle_irq](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/irq.c#L46) is defined in architecture specific code and we already encountered this function. From the line above we can understand that [bcm2836_arm_irqchip_handle_irq](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2836.c#L164) will be called by the low-level exception code. The function itself is listed below. ``` static void __exception_irq_entry bcm2836_arm_irqchip_handle_irq(struct pt_regs *regs) { int cpu = smp_processor_id(); u32 stat; stat = readl_relaxed(intc.base + LOCAL_IRQ_PENDING0 + 4 * cpu); if (stat & BIT(LOCAL_IRQ_MAILBOX0)) { #ifdef CONFIG_SMP void __iomem *mailbox0 = (intc.base + LOCAL_MAILBOX0_CLR0 + 16 * cpu); u32 mbox_val = readl(mailbox0); u32 ipi = ffs(mbox_val) - 1; writel(1 << ipi, mailbox0); handle_IPI(ipi, regs); #endif } else if (stat) { u32 hwirq = ffs(stat) - 1; handle_domain_irq(intc.domain, hwirq, regs); } } ``` This function reads `LOCAL_IRQ_PENDING` register to figure out what interrupts are currently pending. There are 4 `LOCAL_IRQ_PENDING` registers, each corresponding to its own processor core, that's why current processor index is used to select the right one. Mailbox interrupts and all other interrupts are processed in 2 different clauses of an if statement. The interaction between different cores of a multiprocessor system is out of scope for our current discussion, so we are going to skip mailbox interrupt handling part. Now we have only the following 2 lines left unexplained. ``` u32 hwirq = ffs(stat) - 1; handle_domain_irq(intc.domain, hwirq, regs); ``` This is were interrupt is passed to the next handler. First of all hardware irq number is calculated. [ffs](https://github.com/torvalds/linux/blob/v4.14/include/asm-generic/bitops/ffs.h#L13) (Find first bit) function is used to do this. After hardware irq number is calculated [handle_domain_irq](https://github.com/torvalds/linux/blob/v4.14/kernel/irq/irqdesc.c#L622) function is called. This function uses irq domain to translate hardware irq number to Linux irq number, then checks irq configuration (it is stored in [irq_desc](https://github.com/torvalds/linux/blob/v4.14/include/linux/irqdesc.h#L55) struct) and calls an interrupt handler. We've seen that the handler was set to [handle_percpu_devid_irq](https://github.com/torvalds/linux/blob/v4.14/kernel/irq/chip.c#L859). However, this handler will be overwritten by the child interrupt controller later. Now, let's examine how this happens. ### Generic interrupt controller We have already seen how to use device tree and `compatible` property to find the driver corresponding to some device, so I am going to skip this part and jump straight to the generic interrupt controller driver source code. You can find it in [irq-bcm2835.c](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2835.c) file. As usual, we are going to start our exploration with the initialization function. It is called [armctrl_of_init](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2835.c#L141). ``` static int __init armctrl_of_init(struct device_node *node, struct device_node *parent, bool is_2836) { void __iomem *base; int irq, b, i; base = of_iomap(node, 0); if (!base) panic("%pOF: unable to map IC registers\n", node); intc.domain = irq_domain_add_linear(node, MAKE_HWIRQ(NR_BANKS, 0), &armctrl_ops, NULL); if (!intc.domain) panic("%pOF: unable to create IRQ domain\n", node); for (b = 0; b < NR_BANKS; b++) { intc.pending[b] = base + reg_pending[b]; intc.enable[b] = base + reg_enable[b]; intc.disable[b] = base + reg_disable[b]; for (i = 0; i < bank_irqs[b]; i++) { irq = irq_create_mapping(intc.domain, MAKE_HWIRQ(b, i)); BUG_ON(irq <= 0); irq_set_chip_and_handler(irq, &armctrl_chip, handle_level_irq); irq_set_probe(irq); } } if (is_2836) { int parent_irq = irq_of_parse_and_map(node, 0); if (!parent_irq) { panic("%pOF: unable to get parent interrupt.\n", node); } irq_set_chained_handler(parent_irq, bcm2836_chained_handle_irq); } else { set_handle_irq(bcm2835_handle_irq); } return 0; } ``` Now, let's investigate this function in more details. ``` void __iomem *base; int irq, b, i; base = of_iomap(node, 0); if (!base) panic("%pOF: unable to map IC registers\n", node); intc.domain = irq_domain_add_linear(node, MAKE_HWIRQ(NR_BANKS, 0), &armctrl_ops, NULL); if (!intc.domain) panic("%pOF: unable to create IRQ domain\n", node); ``` The function starts with the code that reads device base address from the device tree and initializes the irq domain. This part should be already familiar to you because we have seen similar code in the local irq controller driver. ``` for (b = 0; b < NR_BANKS; b++) { intc.pending[b] = base + reg_pending[b]; intc.enable[b] = base + reg_enable[b]; intc.disable[b] = base + reg_disable[b]; ``` Next, there is a loop that iterates over all irq banks. We already briefly touched irq banks in the first chapter of this lesson. The interrupt controller has 3 irq banks, which are controlled by `ENABLE_IRQS_1`, `ENABLE_IRQS_2` and `ENABLE_BASIC_IRQS` registers. Each of the banks has its own enable, disable and pending registers. Enable and disable registers can be used to either enable or disable individual interrupts that belong to a particular bank. Pending register is used to determine what interrupts are waiting to be processed. ``` for (i = 0; i < bank_irqs[b]; i++) { irq = irq_create_mapping(intc.domain, MAKE_HWIRQ(b, i)); BUG_ON(irq <= 0); irq_set_chip_and_handler(irq, &armctrl_chip, handle_level_irq); irq_set_probe(irq); } ``` Next, there is a nested loop that is responsible for registering each supported interrupt and setting irq chip and handler. We already saw how the same functions are used in the local interrupt controller driver. However, I would like to highlight a few important things. * [MAKE_HWIRQ](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2835.c#L57) macro is used to calculate hardware irq number. It is calculated based on bank index and irq index inside the bank. * [handle_level_irq](https://github.com/torvalds/linux/blob/v4.14/kernel/irq/chip.c#L603) is a common handler that is used for interrupts of the level type. Interrupts of such type keep interrupt line set to "high" until the interrupt is acknowledged. There are also edge type interrupts that works in a different way. * [irq_set_probe](https://github.com/torvalds/linux/blob/v4.14/include/linux/irq.h#L667) function just unsets [IRQ_NOPROBE](https://github.com/torvalds/linux/blob/v4.14/include/linux/irq.h#L64) interrupt flag, effectively disabling interrupt auto-probing. Interrupt auto-probing is a process that allows different drivers to discover which interrupt line their devices are connected to. This is not needed for Raspberry Pi, because this information is encoded in the device tree, however, for some devices, this might be useful. Please, refer to [this](https://github.com/torvalds/linux/blob/v4.14/include/linux/interrupt.h#L662) comment to understand how auto-probing works in the Linux kernel. Next piece of code is different for BCM2836 and BCM2835 interrupt controllers (the first one corresponds to the RPi models 2 and 3, and the second one to RPi Model 1). If we are dealing with BCM2836 the following code is executed. ``` int parent_irq = irq_of_parse_and_map(node, 0); if (!parent_irq) { panic("%pOF: unable to get parent interrupt.\n", node); } irq_set_chained_handler(parent_irq, bcm2836_chained_handle_irq); ``` Device tree [indicates](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837.dtsi#L75) that local interrupt controller is a parent of the global interrupt controller. Another device tree [property](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837.dtsi#L76) tells us that global interrupt controller is connected to the interupt line number 8 of the local controller, this means that our parent irq is the one with hardware irq number 8. Those 2 properties allow Linux kernel to find out parent interrupt number (this is Linux interrupt number, not hardware number). Finally [irq_set_chained_handler](https://github.com/torvalds/linux/blob/v4.14/include/linux/irq.h#L636) function replaces the handler of the parent irq with [bcm2836_chained_handle_irq](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2835.c#L246) function. [bcm2836_chained_handle_irq](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2835.c#L246) is very simple. Its code is listed below. ``` static void bcm2836_chained_handle_irq(struct irq_desc *desc) { u32 hwirq; while ((hwirq = get_next_armctrl_hwirq()) != ~0) generic_handle_irq(irq_linear_revmap(intc.domain, hwirq)); } ``` You can think about this code as an advanced version of what we did [here](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/irq.c#L39) for the RPi OS. [get_next_armctrl_hwirq](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2835.c#L217) uses all 3 pending registers to figure out which interrupt was fired. [irq_linear_revmap](https://github.com/torvalds/linux/blob/v4.14/include/linux/irqdomain.h#L377) uses irq domain to translate hardware irq number into Linux irq number and [generic_handle_irq](https://github.com/torvalds/linux/blob/v4.14/include/linux/irqdesc.h#L156) just executes irq handler. Irq handler was set in the initialization function and it points to [handle_level_irq](https://github.com/torvalds/linux/blob/v4.14/kernel/irq/chip.c#L603) that eventually executes all irq actions associated with the interrupt (this is actually done [here](https://github.com/torvalds/linux/blob/v4.14/kernel/irq/handle.c#L135).). For now, the list of irq actions is empty for all supported interrupts - a driver that is interested in handling some interrupt should add an action to the appropriate list. In the next chapter, we are going to see how this is done using system timer as an example. ##### Previous Page 3.2 [Interrupt handling: Low-level exception handling in Linux](../../../docs/lesson03/linux/low_level-exception_handling.md) ##### Next Page 3.4 [Interrupt handling: Timers](../../../docs/lesson03/linux/timer.md) ================================================ FILE: docs/lesson03/linux/low_level-exception_handling.md ================================================ ## 3.2: Low-level exception handling in Linux Given huge Linux kernel source code, what is a good way to find the code that is responsible for interrupt handling? I can suggest one idea. Vector table base address should be stored in the 'vbar_el1' register, so, if you search for `vbar_el1`, you should be able to figure out where exactly the vector table is initialized. Indeed, the search gives us just a few usages, one of which belongs to already familiar to us [head.S](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/head.S). This code is inside [__primary_switched](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/head.S#L323) function. This function is executed after the MMU is switched on. The code looks like the following. ``` adr_l x8, vectors // load VBAR_EL1 with virtual msr vbar_el1, x8 // vector table address ``` From this code, we can infer that the vector table is called `vectors` and you should be able to easily find [its definition](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L367). ``` /* * Exception vectors. */ .pushsection ".entry.text", "ax" .align 11 ENTRY(vectors) kernel_ventry el1_sync_invalid // Synchronous EL1t kernel_ventry el1_irq_invalid // IRQ EL1t kernel_ventry el1_fiq_invalid // FIQ EL1t kernel_ventry el1_error_invalid // Error EL1t kernel_ventry el1_sync // Synchronous EL1h kernel_ventry el1_irq // IRQ EL1h kernel_ventry el1_fiq_invalid // FIQ EL1h kernel_ventry el1_error_invalid // Error EL1h kernel_ventry el0_sync // Synchronous 64-bit EL0 kernel_ventry el0_irq // IRQ 64-bit EL0 kernel_ventry el0_fiq_invalid // FIQ 64-bit EL0 kernel_ventry el0_error_invalid // Error 64-bit EL0 #ifdef CONFIG_COMPAT kernel_ventry el0_sync_compat // Synchronous 32-bit EL0 kernel_ventry el0_irq_compat // IRQ 32-bit EL0 kernel_ventry el0_fiq_invalid_compat // FIQ 32-bit EL0 kernel_ventry el0_error_invalid_compat // Error 32-bit EL0 #else kernel_ventry el0_sync_invalid // Synchronous 32-bit EL0 kernel_ventry el0_irq_invalid // IRQ 32-bit EL0 kernel_ventry el0_fiq_invalid // FIQ 32-bit EL0 kernel_ventry el0_error_invalid // Error 32-bit EL0 #endif END(vectors) ``` Looks familiar, isn't it? And indeed, I've copied most of this code and just simplified it a little bit. [kernel_ventry](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L72) macro is almost the same as [ventry](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/entry.S#L12), defined in the RPi OS. One difference, though, is that `kernel_ventry` also is responsible for checking whether a kernel stack overflow has occurred. This functionality is enabled if `CONFIG_VMAP_STACK` is set and it is a part of the kernel feature that is called `Virtually mapped kernel stacks`. I'm not going to explain it in details here, however, if you are interested, I can recommend you to read [this](https://lwn.net/Articles/692208/) article. ### kernel_entry [kernel_entry](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L120) macro should also be familiar to you. It is used exactly in the same way as the [corresonding macro](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/entry.S#L17) in the RPI OS. Original (Linux) version, however, is a lot more complicated. The code is listed below. ``` .macro kernel_entry, el, regsize = 64 .if \regsize == 32 mov w0, w0 // zero upper 32 bits of x0 .endif stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] .if \el == 0 mrs x21, sp_el0 ldr_this_cpu tsk, __entry_task, x20 // Ensure MDSCR_EL1.SS is clear, ldr x19, [tsk, #TSK_TI_FLAGS] // since we can unmask debug disable_step_tsk x19, x20 // exceptions when scheduling. mov x29, xzr // fp pointed to user-space .else add x21, sp, #S_FRAME_SIZE get_thread_info tsk /* Save the task's original addr_limit and set USER_DS (TASK_SIZE_64) */ ldr x20, [tsk, #TSK_TI_ADDR_LIMIT] str x20, [sp, #S_ORIG_ADDR_LIMIT] mov x20, #TASK_SIZE_64 str x20, [tsk, #TSK_TI_ADDR_LIMIT] /* No need to reset PSTATE.UAO, hardware's already set it to 0 for us */ .endif /* \el == 0 */ mrs x22, elr_el1 mrs x23, spsr_el1 stp lr, x21, [sp, #S_LR] /* * In order to be able to dump the contents of struct pt_regs at the * time the exception was taken (in case we attempt to walk the call * stack later), chain it together with the stack frames. */ .if \el == 0 stp xzr, xzr, [sp, #S_STACKFRAME] .else stp x29, x22, [sp, #S_STACKFRAME] .endif add x29, sp, #S_STACKFRAME #ifdef CONFIG_ARM64_SW_TTBR0_PAN /* * Set the TTBR0 PAN bit in SPSR. When the exception is taken from * EL0, there is no need to check the state of TTBR0_EL1 since * accesses are always enabled. * Note that the meaning of this bit differs from the ARMv8.1 PAN * feature as all TTBR0_EL1 accesses are disabled, not just those to * user mappings. */ alternative_if ARM64_HAS_PAN b 1f // skip TTBR0 PAN alternative_else_nop_endif .if \el != 0 mrs x21, ttbr0_el1 tst x21, #0xffff << 48 // Check for the reserved ASID orr x23, x23, #PSR_PAN_BIT // Set the emulated PAN in the saved SPSR b.eq 1f // TTBR0 access already disabled and x23, x23, #~PSR_PAN_BIT // Clear the emulated PAN in the saved SPSR .endif __uaccess_ttbr0_disable x21 1: #endif stp x22, x23, [sp, #S_PC] /* Not in a syscall by default (el0_svc overwrites for real syscall) */ .if \el == 0 mov w21, #NO_SYSCALL str w21, [sp, #S_SYSCALLNO] .endif /* * Set sp_el0 to current thread_info. */ .if \el == 0 msr sp_el0, tsk .endif /* * Registers that may be useful after this macro is invoked: * * x21 - aborted SP * x22 - aborted PC * x23 - aborted PSTATE */ .endm ``` Now we are going to explore the `kernel_entry` macro in details. ``` .macro kernel_entry, el, regsize = 64 ``` The macro accepts 2 parameters: `el` and `regsize`. `el` can be either `0` or `1` depending on whether an exception was generated at EL0 or EL1. `regsize` is 32 if we came from 32-bit EL0 or 64 otherwise. ``` .if \regsize == 32 mov w0, w0 // zero upper 32 bits of x0 .endif ``` In 32-bit mode, we use 32-bit general purpose registers (`w0` instead of `x0`). `w0` is architecturally mapped to the lower part of `x0`. The provided code snippet zeroes upper 32 bits of the `x0` register by writing `w0` to itself. ``` stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] ``` This part saves all general purpose registers on the stack. Note, that stack pointer was already adjusted in the [kernel_ventry](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L74) to fit everything that needs to be stored. The order in which we save registers matters because in Linux there is a special structure [pt_regs](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/ptrace.h#L119) that is used to access saved registers later inside an exception handler. As you might see this structure contains not only general purpose registers but also some other information, which is mostly populated later in the `kernel_entry` macro. I recommend you to remember `pt_regs` struct because we are going to implement and use a similar one in the next few lessons. ``` .if \el == 0 mrs x21, sp_el0 ``` `x21` now contains aborted stack pointer. Note, that a task in Linux uses 2 different stacks for user and kernel mode. In case of user mode, we can use `sp_el0` register to figure out the stack pointer value at the moment when the exception was generated. This line is very important because we need to swap stack pointers during the context switch. We will talk about it in details in the next lesson. ``` ldr_this_cpu tsk, __entry_task, x20 // Ensure MDSCR_EL1.SS is clear, ldr x19, [tsk, #TSK_TI_FLAGS] // since we can unmask debug disable_step_tsk x19, x20 // exceptions when scheduling. ``` `MDSCR_EL1.SS` bit is responsible for enabling "Software Step exceptions". If this bit is set and debug exceptions are unmasked, an exception is generated after any instruction has been executed. This is commonly used by debuggers. When taking exception from user mode, we need to check first whether [TIF_SINGLESTEP](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/thread_info.h#L93) flag is set for the current task. If yes, this indicates that the task is executing under a debugger and we must unset `MDSCR_EL1.SS` bit. The important thing to understand in this code is how information about the current task is obtained. In Linux, each process or thread (later I will reference any of them as just "task") has a [task_struct](https://github.com/torvalds/linux/blob/v4.14/include/linux/sched.h#L519) associated with it. This struct contains all metadata information about a task. On `arm64` architecture `task_struct` embeds another structure that is called [thread_info](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/thread_info.h#L39) so that a pointer to `task_struct` can always be used as a pointer to `thread_info`. `thread_info` is the place were flags are stored along with some other low-level values that `entry.S` need direct access to. ``` mov x29, xzr // fp pointed to user-space ``` Though `x29` is a general purpose register it usually has a special meaning. It is used as a "Frame pointer". Now I want to spend some time to explain its purpose. When a function is compiled, the first couple of instructions are usually responsible for storing old frame pointer and link register values on the stack. (Just a quick reminder: `x30` is called link register and it holds a "return address" that is used by the `ret` instruction) Then a new stack frame is allocated, so that it can contain all local variables of the function, and frame pointer register is set to point to the bottom of the frame. Whenever the function needs to access some local variable it simply adds hardcoded offset to the frame pointer. Imagine now that an error has occurred and we need to generate a stack trace. We can use current frame pointer to find all local variables in the stack, and the link register can be used used to figure out the precise location of the caller. Next, we take advantage of the fact that old frame pointer and link register values are always saved at the beginning of the stack frame, and we just read them from there. After we get caller's frame pointer we can now access all its local variables as well. This process is repeated recursively until we reach the top of the stack and is called "stack unwinding". A similar algorithm is used by [ptrace](http://man7.org/linux/man-pages/man2/ptrace.2.html) system call. Now, going back to the `kernel_entry` macro, it should be clear why do we need to clear `x29` register after taking an exception from EL0. That is because in Linux each task uses a different stack for user and kernel mode, and therefore it doesn't make sense to have common stack traces. ``` .else add x21, sp, #S_FRAME_SIZE ``` Now we are inside else clause, which mean that this code is relevant only if we are handling an exception taken from EL1. In this case, we are reusing old stack and the provided code snippet just saves original `sp` value in the `x21` register for later usage. ``` /* Save the task's original addr_limit and set USER_DS (TASK_SIZE_64) */ ldr x20, [tsk, #TSK_TI_ADDR_LIMIT] str x20, [sp, #S_ORIG_ADDR_LIMIT] mov x20, #TASK_SIZE_64 str x20, [tsk, #TSK_TI_ADDR_LIMIT] ``` Task address limit specifies the largest virtual address that can be used. When user process operates in 32-bit mode this limit is `2^32`. For 64 bit kernel it can be larger and usually is `2^48`. If it happens that an exception is taken from 32-bit EL1, task address limit need to be changed to [TASK_SIZE_64](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/memory.h#L80). Also, it is required to save the original address limit because it needs to be restored before the execution will be returned to user mode. ``` mrs x22, elr_el1 mrs x23, spsr_el1 ``` `elr_el1` and `spsr_el1` must be saved on the stack before we start handling an exception. We haven't done it yet in the RPI OS, because for now we always return to the same location from which an exception was taken. But what if we need to do a context switch while handling an exception? We will discuss this scenario in details in the next lesson. ``` stp lr, x21, [sp, #S_LR] ``` Link register and frame pointer registers are saved on the stack. We already saw that frame pointer is calculated differently depending on whether an exception was taken from EL0 or EL1 and the result of this calculation was already stored in `x21` register. ``` /* * In order to be able to dump the contents of struct pt_regs at the * time the exception was taken (in case we attempt to walk the call * stack later), chain it together with the stack frames. */ .if \el == 0 stp xzr, xzr, [sp, #S_STACKFRAME] .else stp x29, x22, [sp, #S_STACKFRAME] .endif add x29, sp, #S_STACKFRAME ``` Here [stackframe](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/ptrace.h#L140) property of the `pt_regs` struct is filled. This property also contains link register and frame pointer, though this time the value of `elr_el1` (which is now in `x22`) is used instead of `lr`. `stackframe` is used solely for stack unwinding. ``` #ifdef CONFIG_ARM64_SW_TTBR0_PAN alternative_if ARM64_HAS_PAN b 1f // skip TTBR0 PAN alternative_else_nop_endif .if \el != 0 mrs x21, ttbr0_el1 tst x21, #0xffff << 48 // Check for the reserved ASID orr x23, x23, #PSR_PAN_BIT // Set the emulated PAN in the saved SPSR b.eq 1f // TTBR0 access already disabled and x23, x23, #~PSR_PAN_BIT // Clear the emulated PAN in the saved SPSR .endif __uaccess_ttbr0_disable x21 1: #endif ``` `CONFIG_ARM64_SW_TTBR0_PAN` parameter prevents the kernel from accessing user-space memory directly. If you are wondering when this might be useful you can read [this](https://kernsec.org/wiki/index.php/Exploit_Methods/Userspace_data_usage) article. For now, I will also skip the detailed explanation of how this works, because such security features are too out of scope for our discussion. ``` stp x22, x23, [sp, #S_PC] ``` Here `elr_el1` and `spsr_el1` are saved on the stack. ``` /* Not in a syscall by default (el0_svc overwrites for real syscall) */ .if \el == 0 mov w21, #NO_SYSCALL str w21, [sp, #S_SYSCALLNO] .endif ``` `pt_regs` struct has a [field](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/ptrace.h#L132) indicating whether the current exception is a system call or not. By default, we assume that it isn't. Wait till lecture 5 for the detailed explanation how syscalls work. ``` /* * Set sp_el0 to current thread_info. */ .if \el == 0 msr sp_el0, tsk .endif ``` When a task is executed in kernel mode, `sp_el0` is not needed. Its value was previously saved on the stack so it can be easily restored in `kernel_exit` macro. Starting from this point `sp_el0` will be used to hold a pointer to current [task_struct](https://github.com/torvalds/linux/blob/v4.14/include/linux/sched.h#L519) for quick access. ### el1_irq Next thing we are going to explore is the handler that is responsible for processing IRQs taken from EL1. From the [vector table](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L374) we can easily find out that the handler is called `el1_irq` and is defined [here](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L562). Let's take a look on the code now and examine it line by line. ``` el1_irq: kernel_entry 1 enable_dbg #ifdef CONFIG_TRACE_IRQFLAGS bl trace_hardirqs_off #endif irq_handler #ifdef CONFIG_PREEMPT ldr w24, [tsk, #TSK_TI_PREEMPT] // get preempt count cbnz w24, 1f // preempt count != 0 ldr x0, [tsk, #TSK_TI_FLAGS] // get flags tbz x0, #TIF_NEED_RESCHED, 1f // needs rescheduling? bl el1_preempt 1: #endif #ifdef CONFIG_TRACE_IRQFLAGS bl trace_hardirqs_on #endif kernel_exit 1 ENDPROC(el1_irq) ``` The following is done inside this function. * `kernel_entry` and `kernel_exit` macros are called to save and restore processor state. The first parameter indicates that the exception is taken from EL1. * Debug interrupts are unmasked by calling `enable_dbg` macro. At this point, it is safe to do so, because the processor state is already saved and, even if debug exception occurred in the middle of the interrupt handler, it will be processed correctly. If you wonder why is it necessary to unmask debug exceptions during an interrupt processing in the first place - read [this](https://github.com/torvalds/linux/commit/2a2830703a2371b47f7b50b1d35cb15dc0e2b717) commit message. * Code inside `#ifdef CONFIG_TRACE_IRQFLAGS` block is responsible for tracing interrupts. It records 2 events: interrupt start and end. * Code inside `#ifdef CONFIG_PREEMPT` block access current task flags to check whether we need to call the scheduler. This code will be examined details in the next lesson. * `irq_handler` - this is the place were actual interrupt handling is performed. [irq_handler](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L351) is a macro and it is defined as the follows. ``` .macro irq_handler ldr_l x1, handle_arch_irq mov x0, sp irq_stack_entry blr x1 irq_stack_exit .endm ``` As you might see from the code, `irq_handler` executes [handle_arch_irq](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/irq.c#L44) function. This function is executed with special stack, that is called "irq stack". Why is it necessary to switch to a different stack? In RPI OS, for example, we didn't do this. Well, I guess it is not necessary, but without it, an interrupt will be handled using task stack, and we can never be sure how much of it is still left for the interrupt handler. Next, we need to look at [handle_arch_irq](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/irq.c#L44). It appears that it is not a function, but a variable. It is set inside [set_handle_irq](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/irq.c#L46) function. But who sets it, and what is the fade of an interrupt after it reaches this point? We will figure out the answer in the next chapter of this lesson. ### Conclusion As a conclusion, I can say that we've already explored the low-level interrupt handling code and trace the path of an interrupt from the vector table all the way to the `handle_arch_irq`. This is the point were an interrupt leaves architecture specific code and started to be handled by a driver code. Our goal in the next chapter will be to trace the path of a timer interrupt through the driver source code. ##### Previous Page 3.1 [Interrupt handling: RPi OS](../../../docs/lesson03/rpi-os.md) ##### Next Page 3.3 [Interrupt handling: Interrupt controllers](../../../docs/lesson03/linux/interrupt_controllers.md) ================================================ FILE: docs/lesson03/linux/timer.md ================================================ ## 3.4: Timers We finished the last chapter by examining global interrupt controller. We were able to trace the path of a timer interrupt all the way up to the [bcm2836_chained_handle_irq](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2835.c#L246) function. Next logical step is to see how the timer driver handles this interrupt. However, before we can do this, you need to familiarize yourself with a few important concepts related to timer functionality. All of them are explained in the [official kernel documentation](https://github.com/torvalds/linux/blob/v4.14/Documentation/timers/timekeeping.txt), and I strongly advise you to read this document. But for those who are too busy to read it, I can provide my own brief explanation of the mentioned concepts. 1. **Clock sources** Each time you need to find out exactly what time it is now you are using clock source framework. Typically the clock source is implemented as a monotonic, atomic n-bit counter, which counts from 0 to 2^(n-1) and then wraps around to 0 and starts over. The clock source also provides means to translate the counter into a nanosecond value. 1. **Clock events** This abstraction is introduced to allow anybody to subscribe on timer interrupts. Clock events framework takes designed time of the next event as an input and, based on it, calculates appropriate values of the timer hardware registers. 1. **sched_clock()** This function returns the number of nanoseconds since the system was started. It usually does so by directly reading timer registers. This function is called very frequently and should be optimized for performance. In the next section, we are going to see how system timer is used to implement clock sources, clock events and sched_clock functionality. ### BCM2835 System Timer. As usual, we start the exploration of a particular device with finding its location in the device tree. System timer node is defined [here](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm283x.dtsi#L57). You can keep this definition open for a while because we are going to reference it several times. Next, we need to use `compatible` property to figure out the location of the corresponding driver. The driver can be found [here](https://github.com/torvalds/linux/blob/v4.14/drivers/clocksource/bcm2835_timer.c). The first thing we are going to look at is [bcm2835_timer](https://github.com/torvalds/linux/blob/v4.14/drivers/clocksource/bcm2835_timer.c#L42) structure. ``` struct bcm2835_timer { void __iomem *control; void __iomem *compare; int match_mask; struct clock_event_device evt; struct irqaction act; }; ``` This structure contains all state needed for the driver to function. `control` and `compare` fields holds the addresses of the corresponding memory mapped registers, `match_mask` is used to determine which of the 4 available timer interrupts we are going to use, `evt` field contains a structure that is passed to clock events framework and `act` is an irq action that is used to connect the current driver with the interrupt controller. Next we are going to look at [bcm2835_timer_init](https://github.com/torvalds/linux/blob/v4.14/drivers/clocksource/bcm2835_timer.c#L83) which is the driver initialization function. It is large, but not as difficult as you might think from the beginning. ``` static int __init bcm2835_timer_init(struct device_node *node) { void __iomem *base; u32 freq; int irq, ret; struct bcm2835_timer *timer; base = of_iomap(node, 0); if (!base) { pr_err("Can't remap registers\n"); return -ENXIO; } ret = of_property_read_u32(node, "clock-frequency", &freq); if (ret) { pr_err("Can't read clock-frequency\n"); goto err_iounmap; } system_clock = base + REG_COUNTER_LO; sched_clock_register(bcm2835_sched_read, 32, freq); clocksource_mmio_init(base + REG_COUNTER_LO, node->name, freq, 300, 32, clocksource_mmio_readl_up); irq = irq_of_parse_and_map(node, DEFAULT_TIMER); if (irq <= 0) { pr_err("Can't parse IRQ\n"); ret = -EINVAL; goto err_iounmap; } timer = kzalloc(sizeof(*timer), GFP_KERNEL); if (!timer) { ret = -ENOMEM; goto err_iounmap; } timer->control = base + REG_CONTROL; timer->compare = base + REG_COMPARE(DEFAULT_TIMER); timer->match_mask = BIT(DEFAULT_TIMER); timer->evt.name = node->name; timer->evt.rating = 300; timer->evt.features = CLOCK_EVT_FEAT_ONESHOT; timer->evt.set_next_event = bcm2835_time_set_next_event; timer->evt.cpumask = cpumask_of(0); timer->act.name = node->name; timer->act.flags = IRQF_TIMER | IRQF_SHARED; timer->act.dev_id = timer; timer->act.handler = bcm2835_time_interrupt; ret = setup_irq(irq, &timer->act); if (ret) { pr_err("Can't set up timer IRQ\n"); goto err_iounmap; } clockevents_config_and_register(&timer->evt, freq, 0xf, 0xffffffff); pr_info("bcm2835: system timer (irq = %d)\n", irq); return 0; err_iounmap: iounmap(base); return ret; } ``` Now let's take a closer look at this function. ``` base = of_iomap(node, 0); if (!base) { pr_err("Can't remap registers\n"); return -ENXIO; } ``` It starts with mapping memory registers and obtaining register base address. You should be already familiar with this part. ``` ret = of_property_read_u32(node, "clock-frequency", &freq); if (ret) { pr_err("Can't read clock-frequency\n"); goto err_iounmap; } system_clock = base + REG_COUNTER_LO; sched_clock_register(bcm2835_sched_read, 32, freq); ``` Next, `sched_clock` subsystem is initialized. `sched_clock` need to access timer counter registers each time it is executed and [bcm2835_sched_read](https://github.com/torvalds/linux/blob/v4.14/drivers/clocksource/bcm2835_timer.c#L52) is passed as the first argument to assist with this task. The second argument corresponds to the number of bits that the timer counter has (in our case it is 32). the number of bits is used to calculate how soon the counter is going to wrap to 0. The last argument specifies timer frequency - it is used to convert values of the timer counter to nanoseconds. Timer frequency is defined in the device tree at [this](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm283x.dtsi#L65) line. ``` clocksource_mmio_init(base + REG_COUNTER_LO, node->name, freq, 300, 32, clocksource_mmio_readl_up); ``` Next line initializes clock source framework. [clocksource_mmio_init](https://github.com/torvalds/linux/blob/v4.14/drivers/clocksource/mmio.c#L52) initializes a simple clock source based on memory mapped registers. The clock source framework, in some aspects, duplicates the functionality of `sched_clock` and it needs access to the same 3 basic parameters. * The location of the timer counter register. * The number of valid bits in the counter. * Timer frequency. Another 3 parameters include the name of the clock source, its rating, which is used to rate clock source devices, and a function that can read timer counter register. ``` irq = irq_of_parse_and_map(node, DEFAULT_TIMER); if (irq <= 0) { pr_err("Can't parse IRQ\n"); ret = -EINVAL; goto err_iounmap; } ``` This code snippet is used to find Linux irq number, corresponding to the third timer interrupt (Number 3 is hardcoded as [DEFAULT_TIMER](https://github.com/torvalds/linux/blob/v4.14/drivers/clocksource/bcm2835_timer.c#L108) constant). Just a quick reminder: Raspberry Pi system timer has 4 independent set of timer registers, and here the third one is used. If you go back to the device tree, you can find [interrupts](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm283x.dtsi#L60) property. This property describes all interrupts, supported by a device, and how those interrupts are mapped to interrupt controller lines. It is an array, where each item represents one interrupt. The format of the items is specific to the interrupt controller. In our case, each item consists of 2 numbers: the first one specifies an interrupt bank and the second - interrupt number inside the bank. [irq_of_parse_and_map](https://github.com/torvalds/linux/blob/v4.14/drivers/of/irq.c#L41) reads the value of `interrupts` property, then it uses the second argument to find which of the supported interrupts we are interested in and returns Linux irq number for the requested interrupt. ``` timer = kzalloc(sizeof(*timer), GFP_KERNEL); if (!timer) { ret = -ENOMEM; goto err_iounmap; } ``` Here memory for `bcm2835_timer` structure is allocated. ``` timer->control = base + REG_CONTROL; timer->compare = base + REG_COMPARE(DEFAULT_TIMER); timer->match_mask = BIT(DEFAULT_TIMER); ``` Next, the addresses of the control and compare registers are calculated and `match_mask` is set to the `DEFAULT_TIMER` constant. ``` timer->evt.name = node->name; timer->evt.rating = 300; timer->evt.features = CLOCK_EVT_FEAT_ONESHOT; timer->evt.set_next_event = bcm2835_time_set_next_event; timer->evt.cpumask = cpumask_of(0); ``` In this code snippet [clock_event_device](https://github.com/torvalds/linux/blob/v4.14/include/linux/clockchips.h#L100) struct is initialized. The most important property here is `set_next_event` which points to [bcm2835_time_set_next_event](https://github.com/torvalds/linux/blob/v4.14/drivers/clocksource/bcm2835_timer.c#L57) function. This function is called by the clock events framework to schedule next interrupt. `bcm2835_time_set_next_event` is very simple - it updates compare register so that interrupt will be scheduled after a desied interval. This is analogaus to what we did [here](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/timer.c#L17) for the RPi OS. ``` timer->act.flags = IRQF_TIMER | IRQF_SHARED; timer->act.dev_id = timer; timer->act.handler = bcm2835_time_interrupt; ``` Next, irq action is initialized. The most important property here is `handler`, which points to [bcm2835_time_interrupt](https://github.com/torvalds/linux/blob/v4.14/drivers/clocksource/bcm2835_timer.c#L67) - this is the function that is called after an interrupt is fired. If you take a look at it, you will see that it redirects all work to the event handler, registered by the clock events framework. We will examine this event handler in a while. ``` ret = setup_irq(irq, &timer->act); if (ret) { pr_err("Can't set up timer IRQ\n"); goto err_iounmap; } ``` After the irq action is configured, it is added to the list of irq actions of the timer interrupt. ``` clockevents_config_and_register(&timer->evt, freq, 0xf, 0xffffffff); ``` And finally clock events framework is initialized by calling [clockevents_config_and_register](https://github.com/torvalds/linux/blob/v4.14/kernel/time/clockevents.c#L504). `evt` structure and timer frequency are passed as first 2 arguments. Last 2 arguments are used only in "one-shot" timer mode and are not relevant to our current discussion. Now, we have traced the path of a timer interrupt all the way up to the `bcm2835_time_interrupt` function, but we still didn't find the place were the actual work is done. In the next section, we are going to dig even deeper and find out how an interrupt is processed when it enters the clock events framework. ### How an interrupt is processed in the clock events framework In the previous section, we have seen that the real work of handling a timer interrupt is outsourced to the clock events framework. This is done in the [following](https://github.com/torvalds/linux/blob/v4.14/drivers/clocksource/bcm2835_timer.c#L74) few lines. ``` event_handler = ACCESS_ONCE(timer->evt.event_handler); if (event_handler) event_handler(&timer->evt); ``` Now our goal will be to figure out were exactly `event_handler` is set and what happens after it is called. [clockevents_config_and_register](https://github.com/torvalds/linux/blob/v4.14/kernel/time/clockevents.c#L504) function is a good place to start the exploration because this is the place where clock events framework is configured and, if we follow the logic of this function, eventually we should find how `event_handler` is set. Now let me show you the chain of function calls that leads us to the place we need. 1. [clockevents_config_and_register](https://github.com/torvalds/linux/blob/v4.14/kernel/time/clockevents.c#L504) This is the top level initialization function. 1. [clockevents_register_device](https://github.com/torvalds/linux/blob/v4.14/kernel/time/clockevents.c#L449) In this function the timer is added to the global list of clock event devices. 1. [tick_check_new_device](https://github.com/torvalds/linux/blob/v4.14/kernel/time/tick-common.c#L300) This function checks whether the current device is a good candidate to be used as a "tick device". If yes, such device will be used to generate periodic ticks that the rest of the kernel will use to do all work that needs to be done on a regular basis. 1. [tick_setup_device](https://github.com/torvalds/linux/blob/v4.14/kernel/time/tick-common.c#L177) This function starts device configuration. 1. [tick_setup_periodic](https://github.com/torvalds/linux/blob/v4.14/kernel/time/tick-common.c#L144) This is the place were device is configured for periodic tics. 1. [tick_set_periodic_handler](https://github.com/torvalds/linux/blob/v4.14/kernel/time/tick-broadcast.c#L432) Finally we reached the place where the handler is assigned! If you take a look at the last function in the call chain, you will see that Linux uses different handlers depending on whether broadcast is enabled or not. Tick broadcast is used to awake idle CPUs, you can read more about it [here](https://lwn.net/Articles/574962/). But we are going to ignore it and concentrate on a more general tick handler instead. In general case [tick_handle_periodic](https://github.com/torvalds/linux/blob/v4.14/kernel/time/tick-common.c#L99) and then [tick_periodic](https://github.com/torvalds/linux/blob/v4.14/kernel/time/tick-common.c#L79) functions are called. The later one is exactly the function that we are interested in. Let me copy its content here. ``` /* * Periodic tick */ static void tick_periodic(int cpu) { if (tick_do_timer_cpu == cpu) { write_seqlock(&jiffies_lock); /* Keep track of the next tick event */ tick_next_period = ktime_add(tick_next_period, tick_period); do_timer(1); write_sequnlock(&jiffies_lock); update_wall_time(); } update_process_times(user_mode(get_irq_regs())); profile_tick(CPU_PROFILING); } ``` A few important things are done in this function: 1. `tick_next_period` is calculated so that next tick event can be scheduled. 1. [do_timer](https://github.com/torvalds/linux/blob/v4.14/kernel/time/timekeeping.c#L2200) is called, which is responsible for setting 'jiffies'. `jiffies` is a number of ticks since the last system reboot. `jiffies` can be used in the same way as `sched_clock` function, in cases when you don't need nanosecond precision. 1. [update_process_times](https://github.com/torvalds/linux/blob/v4.14/kernel/time/timer.c#L1583) is called. This is the place where currently executing process is given a chance to do all work that needed to be done periodically. This work includes, for example, running local process timers, or, most importantly, notifying the scheduler about the tick event. ### Conclusion Now you see how long is the way of an ordinary timer interrupt, but we followed it from the beginning to the very end. One of the things that are the most important, is that we finally reached the place where the scheduler is called. The scheduler is one of the most critical parts of any operating system and it relies heavily on timer interrupts. So now, when we've seen where the scheduler functionality is triggered, its time to discuss its implementation - that is something we are going to do in the next lesson. ##### Previous Page 3.3 [Interrupt handling: Interrupt controllers](../../../docs/lesson03/linux/interrupt_controllers.md) ##### Next Page 3.5 [Interrupt handling: Exercises](../../../docs/lesson03/exercises.md) ================================================ FILE: docs/lesson03/rpi-os.md ================================================ ## 3.1: Interrupts From the lesson 1, we already know how to communicate with hardware. However, most of the time the pattern of communication is not that simple. Usually, this pattern is asynchronous: we send some command to a device, but it doesn't respond immediately. Instead, it notifies us when the work is completed. Such asynchronous notifications are called "interrupts" because they interrupt normal execution flow and force the processor to execute an "interrupt handler". There is one device that is particularly useful in operating system development: system timer. It is a device that can be configured to periodically interrupt a processor with some predefined frequency. One particular application of the timer that it is used in the process scheduling. A scheduler needs to measure for how long each process has been executed and use this information to select the next process to run. This measurement is based on timer interrupts. We are going to talk about process scheduling in details in the next lesson, but for now, our task will be to initialize system timer and implement a timer interrupt handler. ### Interrupts vs exceptions In ARM.v8 architecture, interrupts are part of a more general term: exceptions. There are 4 types of exceptions * **Synchronous exception** Exceptions of this type are always caused by the currently executed instruction. For example, you can use `str` instruction to store some data at an unexistent memory location. In this case, a synchronous exception is generated. Synchronous exceptions also can be used to generate a "software interrupt". Software interrupt is a synchronous exception that is generated on purpose by `svc` instruction. We will use this technique in lesson 5 to implement system calls. * **IRQ (Interrupt Request)** Those are normal interrupts. They are always asynchronous, which means that they have nothing to do with the currently executed instruction. In contrast to synchronous exceptions, they are always not generated by the processor itself, but by external hardware. * **FIQ (Fast Interrupt Request)** This type of exception is called "fast interrupts" and exist solely for the purpose of prioritizing exceptions. It is possible to configure some interrupts as "normal" and other as "fast". Fast interrupts will be signaled first and will be handled by a separate exception handler. Linux doesn't use fast interrupts and we also are not going to do so. * **SError (System Error)** Like `IRQ` and `FIQ`, `SError` exceptions are asynchronous and are generated by external hardware. Unlike `IRQ` and `FIQ`, `SError` always indicates some error condition. [Here](https://community.arm.com/processors/f/discussions/3205/re-what-is-serror-detailed-explanation-is-required) you can find an example explaining when `SError` can be generated. ### Exception vectors Each exception type needs its own handler. Also, separate handlers should be defined for each different execution state, in which exception is generated. There are 4 execution states that are interesting from the exception handling standpoint. If we are working at EL1 those states can be defined as follows: 1. **EL1t** Exception is taken from EL1 while stack pointer was shared with EL0. This happens when `SPSel` register holds the value `0`. 1. **EL1h** Exception is taken from EL1 at the time when dedicated stack pointer was allocated for EL1. This means that `SPSel` holds the value `1` and this is the mode that we are currently using. 1. **EL0_64** Exception is taken from EL0 executing in 64-bit mode. 1. **EL0_32** Exception is taken from EL0 executing in 32-bit mode. In total, we need to define 16 exception handlers (4 exception levels multiplied by 4 execution states) A special structure that holds addresses of all exception handlers is called *exception vector table* or just *vector table*. The structure of a vector table is defined in `Table D1-7 Vector offsets from vector table base address` at page 1876 of the [AArch64-Reference-Manual](https://developer.arm.com/docs/ddi0487/ca/arm-architecture-reference-manual-armv8-for-armv8-a-architecture-profile). You can think of a vector table as an array of exception vectors, where each exception vector (or handler) is a continuous sequence of instructions responsible for handling a particular exception. Accordingly, to `Table D1-7` from `AArch64-Reference-Manual`, each exception vector can ocupy `0x80` bytes maximum. This is not much, but nobody prevents us from jumping to some other memory location from an exception vector. I think all of this will be much clearer with an example, so now it is time to see how exception vectors are implemented in the RPI-OS. Everything related to exception handling is defined in [entry.S](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/entry.S) and we are going to start examining it right now. The first useful macro is called [ventry](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/entry.S#L12) and it is used to create entries in the vector table. ``` .macro ventry label .align 7 b \label .endm ``` As you might infer from this definition, we are not going to handle exceptions right inside the exception vector, but instead, we jump to a label that is provided for the macro as `label` argument. We need `.align 7` instruction because all exception vectors should be located at offset `0x80` bytes one from another. Vector table is defined [here](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/entry.S#L64) and it consists of 16 `ventry` definitions. For now we are only interested in handling `IRQ` from `EL1h` but we still need to define all 16 handlers. This is not because of some hardware requirement, but rather because we want to see a meaningful error message in case something goes wrong. All handlers that should never be executed in normal flow have `invalid` postfix and uses [handle_invalid_entry](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/entry.S#L3) macro. Let's take a look at how this macro is defined. ``` .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm ``` In the first line, you can see that another macro is used: `kernel_entry`. We will discuss it shortly. Then we call [show_invalid_entry_message](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/irq.c#L34) and prepare 3 arguments for it. The first argument is exception type that can take one of [these](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/include/entry.h#L6) values. It tells us exactly which exception handler has been executed. The second parameter is the most important one, it is called `ESR` which stands for Exception Syndrome Register. This argument is taken from `esr_el1` register, which is described on page 2431 of `AArch64-Reference-Manual`. This register contains detailed information about what causes an exception. The third argument is important mostly in case of synchronous exceptions. Its value is taken from already familiar to us `elr_el1` register, which contains the address of the instruction that had been executed when the exception was generated. For synchronous exceptions, this is also the instruction that causes the exception. After `show_invalid_entry_message` function prints all this information to the screen we put the processor in an infinite loop because there is not much else we can do. ### Saving register state After an exception handler finishes execution, we want all general purpose registers to have the same values they had before the exception was generated. If we don't implement such functionality, an interrupt that has nothing to do with currently executing code, can influence the behavior of this code unpredictably. That's why the first thing we must do after an exception is generated is to save the processor state. This is done in the [kernel_entry](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/entry.S) macro. This macro is very simple: it just stores registers `x0 - x30` to the stack. There is also a corresponding macro [kernel_exit](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/entry.S#L37), which is called after an exception handler finishes execution. `kernel_exit` restores processor state by copying back the values of `x0 - x30` registers. It also executes `eret` instruction, which returns us back to normal execution flow. By the way, general purpose registers are not the only thing that needs to be saved before executing an exception handler, but it is enough for our simple kernel for now. In later lessons, we will add more functionality to the `kernel_entry` and `kernel_exit` macros. ### Setting the vector table Ok, now we have prepared the vector table, but the processor doesn't know where it is located and therefore can't use it. In order for the exception handling to work, we must set `vbar_el1` (Vector Base Address Register) to the vector table address. This is done [here](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/irq.S#L2). ``` .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret ``` ### Masking/unmasking interrupts Another thing that we need to do is to unmask all types of interrupts. Let me explain what I mean by "unmasking" an interrupt. Sometimes there is a need to tell that a particular piece of code must never be intercepted by an asynchronous interrupt. Imagine, for example, what happens if an interrupt occurs right in the middle of `kernel_entry` macro? In this case, processor state would be overwritten and lost. That's why whenever an exception handler is executed, the processor automatically disables all types of interrupts. This is called "masking", and this also can be done manually if we need to do so. Many people mistakenly think that interrupts must be masked for the whole duration of the exception handler. This isn't true - it is perfectly legal to unmask interrupts after you saved processor state and therefore it is also legal to have nested interrupts. We are not going to do this right now, but this is important information to keep in mind. The [following two functions](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/irq.S#L7-L15) are responsible for masking and unmasking interrupts. ``` .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ``` ARM processor state has 4 bits that are responsible for holding mask status for different types of interrupts. Those bits are defined as following. * **D** Masks debug exceptions. These are a special type of synchronous exceptions. For obvious reasons, it is not possible to mask all synchronous exceptions, but it is convenient to have a separate flag that can mask debug exceptions. * **A** Masks `SErrors`. It is called `A` because `SErrors` sometimes are called asynchronous aborts. * **I** Masks `IRQs` * **F** Masks `FIQs` Now you can probably guess why registers that are responsible for changing interrupt mask status are called `daifclr` and `daifset`. Those registers set and clear interrupt mask status bits in the processor state. The last thing you may wonder about is why do we use constant value `2` in both of the functions? This is because we only want to set and clear second (`I`) bit. ### Configuring interrupt controller Devices usually don't interrupt processor directly: instead, they rely on interrupt controller to do the job. Interrupt controller can be used to enable/disable interrupts sent by the hardware. We can also use interrupt controller to figure out which device generates an interrupt. Raspberry PI has its own interrupt controller that is described on page 109 of [BCM2837 ARM Peripherals manual](https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf). Raspberry Pi interrupt controller has 3 registers that hold enabled/disabled status for all types of interrupts. For now, we are only interested in timer interrupts, and those interrupts can be enabled using [ENABLE_IRQS_1](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/include/peripherals/irq.h#L10) register, which is described at page 116 of `BCM2837 ARM Peripherals manual`. According to the documentation, interrupts are divided into 2 banks. The first bank consists of interrupts `0 - 31`, each of these interrupts can be enabled or disabled by setting different bits of `ENABLE_IRQS_1` register. There is also a corresponding register for the last 32 interrupts - `ENABLE_IRQS_2` and a register that controls some common interrupts together with ARM local interrupts - `ENABLE_BASIC_IRQS` (We will talk about ARM local interrupts in the next chapter of this lesson). The Peripherals manual, however, has a lot of mistakes and one of those is directly relevant to our discussion. Peripheral interrupt table (which is described at page 113 of the manual) should contain 4 interrupts from system timer at lines `0 - 3`. From reverse engineering Linux source code and reading [some other sources](http://embedded-xinu.readthedocs.io/en/latest/arm/rpi/BCM2835-System-Timer.html) I was able to figure out that timer interrupts 0 and 2 are reserved and used by GPU and interrupts 1 and 3 can be used for any other purposes. So here is the [function](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/irq.c#L29) that enables system timer IRQ number 1. ``` void enable_interrupt_controller() { put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1); } ``` ### Generic IRQ handler From our previous discussion, you should remember that we have a single exception handler that is responsible for handling all `IRQs`. This handler is defined [here](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/irq.c#L39). ``` void handle_irq(void) { unsigned int irq = get32(IRQ_PENDING_1); switch (irq) { case (SYSTEM_TIMER_IRQ_1): handle_timer_irq(); break; default: printf("Unknown pending irq: %x\r\n", irq); } } ``` In the handler, we need a way to figure out what device was responsible for generating an interrupt. Interrupt controller can help us with this job: it has `IRQ_PENDING_1` register that holds interrupt status for interrupts `0 - 31`. Using this register we can check whether the current interrupt was generated by the timer or by some other device and call device specific interrupt handler. Note, that multiple interrupts can be pending at the same time. That's why each device specific interrupt handler must acknowledge that it completed handling the interrupt and only after that interrupt pending bit in `IRQ_PENDING_1` will be cleared. Because of the same reason, for a production ready OS you would probably want to wrap switch construct in the interrupt handler in a loop: in this way, you will be able to handle multiple interrupts during a single handler execution. ### Timer initialization Raspberry Pi system timer is a very simple device. It has a counter that increases its value by 1 after each clock tick. It also has 4 interrupt lines that connect to the interrupt controller(so it can generate 4 different interrupts) and 4 corresponding compare registers. When the value of the counter becomes equal to the value stored in one of the compare registers the corresponding interrupt is fired. That's why, before we will be able to use system timer interrupts, we need to initialize one of the compare registers with a non-zero value, the larger the value is - the later an interrupt will be generated. This is done in [timer_init](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/timer.c#L8) function. ``` const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } ``` The first line reads current counter value, the second line increases it and the third line sets the value of the compare register for the interrupt number 1. By manipulating `interval` value you can adjust how soon the first timer interrupt will be generated. ### Handing timer interrupts Finally, we got to the timer interrupt handler. It is actually very simple. ``` void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); printf("Timer interrupt received\n\r"); } ``` Here we first update compare register so that that next interrupt will be generated after the same time interval. Next, we acknowledge the interrupt by writing 1 to the `TIMER_CS` register. In the documentation `TIMER_CS` is called "Timer Control/Status" register. Bits [0:3] of this register can be used to acknowledge interrupts coming from one of the 4 available interrupt lines. ### Conclusion The last thing that you might want to take a look at is the [kernel_main](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/kernel.c#L7) function where all previously discussed functionality is orchestrated. After you compile and run the sample it should print "Timer interrupt received" message after an interrupt is taken. Please, try to do it by yourself and don't forget to carefully examine the code and experiment with it. ##### Previous Page 2.3 [Processor initialization: Exercises](../../docs/lesson02/exercises.md) ##### Next Page 3.2 [Interrupt handling: Low-level exception handling in Linux](../../docs/lesson03/linux/low_level-exception_handling.md) ================================================ FILE: docs/lesson04/exercises.md ================================================ ## 4.5: Exercises 1. Add `printf` to all main kernel functions to output information about the curent memory and processor state. Make sure that the state diagrams, that I've added to the end of the RPi OS part of this lesson, are correct. (You do not necessarily need to output all state each time, but as soon as some major event happens you can output current stack pointer, or address of the object that has just been allocated, or whatever you consider necessary. Think about some mechanism to prevent information overflow) 1. Introduce a way to assign priority to the tasks. Make sure that a task with higher priority gets more processor time that the one with lower priority. 1. Allow user processes to use FP/SIMD registers. Those registers should be saved in the task context and swapped during the context switch. 1. Allow the kernel to have an unlimited number of tasks (right now the limit is 64). 1. Adopt lesson 04 to run on qemu. Check [this](https://github.com/s-matyukevich/raspberry-pi-os/issues/8) issue for reference. ##### Previous Page 4.4 [Process scheduler: Scheduler](../../docs/lesson04/linux/scheduler.md) ##### Next Page 5.1 [User processes and system calls: RPi OS](../../docs/lesson05/rpi-os.md) ================================================ FILE: docs/lesson04/linux/basic_structures.md ================================================ ## 4.2: Scheduler basic structures In all previous lessons we have been working mostly with either architecture specific code or driver code, and now it is the first time we will dig deep into the core of the Linux kernel. This task isn't simple, and it requires some preparations: before you will be able to understand the Linux scheduler source code, you need to become familiar with a few major concepts that the scheduler is based on. ### [task_struct](https://github.com/torvalds/linux/blob/v4.14/include/linux/sched.h#L519) This is one of the most critical structures in the whole kernel — it contains all information about a running task. We already briefly touched `task_struct` in lesson 2 and we even have implemented our own `task_struct` for the RPi OS, so I assume that by this time you should already have a basic understanding how it is used. Now I want to highlight a few important fields of this struct that are relevant to our discussion. * [thread_info](https://github.com/torvalds/linux/blob/v4.14/include/linux/sched.h#L525) This is the first field of the `task_struct` and it contains all fields that must be accessed by the low-level architecture code. We have already seen how this happens in lesson 2 and will encounter a few other examples later. [thread_info](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/thread_info.h#L39) is architecture specific. In `arm64` case, it is a simple structure with a few fields. ``` struct thread_info { unsigned long flags; /* low level flags */ mm_segment_t addr_limit; /* address limit */ #ifdef CONFIG_ARM64_SW_TTBR0_PAN u64 ttbr0; /* saved TTBR0_EL1 */ #endif int preempt_count; /* 0 => preemptable, <0 => bug */ }; ``` `flags` field is used very frequently — it contains information about the current task state (whether it is under a trace, whether a signal is pending, etc.). All possible flags values can be found [here](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/thread_info.h#L79). * [state](https://github.com/torvalds/linux/blob/v4.14/include/linux/sched.h#L528) Task current state (whether it is currently running, waiting for an interrupt, exited etc.). All possible task states are described [here](https://github.com/torvalds/linux/blob/v4.14/include/linux/sched.h#L69). * [stack](https://github.com/torvalds/linux/blob/v4.14/include/linux/sched.h#L536) When working on the RPi OS, we have seen that `task_struct` is always kept at the bottom of the task stack, so we can use a pointer to `task_struct` as a pointer to the stack. Kernel stacks have constant size, so finding stack end is also an easy task. I think that the same approach was used in the early versions of the Linux kernel, but right now, after the introduction of the [vitually mapped stacks](https://lwn.net/Articles/692208/), `stack` field is used to store a pointer to the kernel stack. * [thread](https://github.com/torvalds/linux/blob/v4.14/include/linux/sched.h#L1108) Another important architecture specific structure is [thread_struct](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/processor.h#L81). It contains all information (such as [cpu_context](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/processor.h#L65)) that is used during a context switch. In fact, the RPi OS implements its own `cpu_context` that is used exactly in the same way as the original one. * [sched_class and sched_entity](https://github.com/torvalds/linux/blob/v4.14/include/linux/sched.h#L562-L563) Those fields are used in schedule algorithm - more on them follows. ### Scheduler class In Linux, there is an extendable mechanism that allows each task to use its own scheduling algorithm. This mechanism uses a structure [sched_class](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/sched.h#L1400). You can think about this structure as an interface that defines all methods that a schedules class have to implement. Let's see what kind of methods are defined in the `sched_class` interface. (Not all of the methods are shown, but only those that I consider the most important for us) * [enqueue_task](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/sched.h#L1403) is executed each time a new task is added to a scheduler class. * [dequeue_task](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/sched.h#L1404) is called when a task can be removed from the scheduler. * [pick_next_task](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/sched.h#L1418) Core scheduler code calls this method when it needs to decide which task to run next. * [task_tick](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/sched.h#L1437) is called on each timer tick and gives the scheduler class an opportunity to measure how long the current task is being executed and notify core scheduler code if it needs to be preempted. There are a few `sched_class` implementations. The most commonly used one, which is typically used for all user tasks, is called "Completely Fair Scheduler (CFS)" ### Completely Fair Scheduler (CFS) The principals behind CFS algorithm are very simple: 1. For each task in the system, CFS measures how much CPU time has been already allocated to it (The value is stored in [sum_exec_runtime](https://github.com/torvalds/linux/blob/v4.14/include/linux/sched.h#L385) field of the `sched_entity` structure) 1. `sum_exec_runtime` is adjusted accordingly to task priority and saved in [vruntime](https://github.com/torvalds/linux/blob/v4.14/include/linux/sched.h#L386) field. 1. When CFS need to pick a new task to execute, the one with the smallest `vruntime` is selected. Linux scheduler uses another important data structure that is called "runqueue" and is described by the [rq](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/sched.h#L667) struct. There is a single instance of a runqueue per CPU. When a new task needs to be selected for execution, the selection is made only from the local runqueue. But if there is a need, tasks can be balanced between different `rq` structures. Runqueues are used by all scheduler classes, not only by CFS. All CFS specific information is kept in [cfs_rq](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/sched.h#L420) struct, which is embedded in the `rq` struct. One important field of the `cfs_rq` struct is called [min_vruntime](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/sched.h#L425) — this is the lowest `vruntime` from all tasks, assigned to a runqueue. `min_vruntime` is assigned to a newly forked task — this ensures that the task will be selected next, because CFS always ensures that a task with the smallest `vruntime` is picked. This approach also ensures that the new task will not be running for an unreasonably long time before it will be preempted. All tasks, assigned to a particular runqueue and tracked by CFS are kept in [tasks_timeline](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/sched.h#L430) field of the `cfs_rq` struct. `tasks_timeline` represents a [Red–black tree](https://en.wikipedia.org/wiki/Red%E2%80%93black_tree), which can be used to pick tasks ordered by their `vruntime` value. Red-black trees have an important property: all operations on it (search, insert, delete) can be done in [O(log n)](https://en.wikipedia.org/wiki/Big_O_notation) time. This means that even if we have thousands of concurrent tasks in the system all scheduler methods still executes very quickly. Another important property of a red-black tree is that for any node in the tree its right child will always have larger `vruntime` value than the parent, and left child's `vruntime` will be always less or equal then the parent's `vruntime`. This has an important implication: the leftmost node is always the one with the smallest `vruntime`. ### [struct pt_regs](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/ptrace.h#L119) I have already shown you how all general purpose registers are saved on the stack when an interrupt is taken — we explored how this process works in Linux kernel and implemented a similar one for the RPi OS. What I haven't mentioned yet, is that it is entirely legal to manipulate those registers while processing an interrupt. It is also legal to manually prepare a set of registers and put them on the stack — this is done when a kernel thread is moved to a user mode, and we are going to implement the same functionality in the next lesson. For now, you just need to remember that `pt_regs` structure is used to describe saved registers and it must have its fields ordered in the same order in with register are saved in the `kernel_entry` macro. In this lesson, we are going to see a few examples of how this structure is used. ### Conclusion There are far more important structures, algorithms and concepts related to scheduling, but what we've just explored is a minimal set, required to understand the next two chapters. Now it's time to see how all of this actually works. ##### Previous Page 4.1 [Process scheduler: RPi OS Scheduler](../../../docs/lesson04/rpi-os.md) ##### Next Page 4.3 [Process scheduler: Forking a task](../../../docs/lesson04/linux/fork.md) ================================================ FILE: docs/lesson04/linux/fork.md ================================================ ## 4.3: Forking a task Scheduling is all about selecting a proper task to run from the list of available tasks. But before the scheduler will be able to do its job we need to somehow fill this list. The way in which new tasks can be created is the main topic of this chapter. For now, we want to focus only on kernel threads and postpone the discussion of user-mode functionality till the next lesson. However, not everywhere it will be possible, so be prepared to learn a little bit about executing tasks in user mode as well. ### Init task When the kernel is started there is a single task running: init task. The corresponding `task_struct` is defined [here](https://github.com/torvalds/linux/blob/v4.14/init/init_task.c#L20) and is initialized by [INIT_TASK](https://github.com/torvalds/linux/blob/v4.14/include/linux/init_task.h#L226) macro. This task is critical for the system because all other tasks in the system are derived from it. ### Creating new tasks In Linux it is not possible to create a new task from scratch - instead, all tasks are forked from a currently running task. Now, as we've seen from were the initial task came from, we can try to explore how new tasks can be created from it. There are 4 ways in which a new task can be created. 1. [fork](http://man7.org/linux/man-pages/man2/fork.2.html) system call creates a full copy of the current process, including its virtual memory and it is used to create new processes (not threads). This syscall is defined [here](https://github.com/torvalds/linux/blob/v4.14/kernel/fork.c#L2116). 1. [vfork](http://man7.org/linux/man-pages/man2/vfork.2.html) system call is similar to `fork` but it differs in that the child reuses parent virtual memory as well as stack, and the parent is blocked until the child finished execution. The definition of this syscall can be found [here](https://github.com/torvalds/linux/blob/v4.14/kernel/fork.c#L2128). 1. [clone](http://man7.org/linux/man-pages/man2/clone.2.html) system call is the most flexible one - it also copies the current task but it allows to customize the process using `flags` parameter and allows to configure the entry point for the child task. In the next lesson, we will see how `glibc` clone wrapper function is implemented - this wrapper allows to use `clone` syscall to create new threads. 1. Finally, [kernel_thread](https://github.com/torvalds/linux/blob/v4.14/kernel/fork.c#L2109) function can be used to create new kernel threads. All of the above functions calls [_do_fork](https://github.com/torvalds/linux/blob/v4.14/kernel/fork.c#L2020), which accept the following arguments. * `clone_flags` Flags are used to configure fork behavior. The complete list of the flags can be found [here](https://github.com/torvalds/linux/blob/v4.14/include/uapi/linux/sched.h#L8). * `stack_start` In case of `clone` syscall this parameter indicates the location of the user stack for the new task. If 'kernel_thread' calls `_do_fork` this parameter points to the function that needs to be executed in a kernel thread. * `stack_size` In `arm64` architecture this parameter is only used in the case when `_do_fork` is called by `kernel_thread. It is a pointer to the argument that needs to be passed to the kernel thread function. (And yes, I also find the naming of the last two parameters misleading) * `parent_tidptr` `child_tidptr` Those 2 parameters are used only in `clone` syscall. Fork will store the child thread ID at the location `parent_tidptr` in the parent's memory, or it can store parent's ID at `child_tidptr`location. * `tls` [Thread Local Storage](https://en.wikipedia.org/wiki/Thread-local_storage) ### Fork procedure Next, I want to highlight the most important events that take place during `_do_fork` execution, preserving their order. 1. [_do_fork](https://github.com/torvalds/linux/blob/v4.14/kernel/fork.c#L2020) calls [copy_process](https://github.com/torvalds/linux/blob/v4.14/kernel/fork.c#L1539) `copy_process` is responsible for configuring new `task_struct`. 1. `copy_process` calls [dup_task_struct](https://github.com/torvalds/linux/blob/v4.14/kernel/fork.c#L512), which allocates new `task_struct` and copies all fields from the original one. Actual copying takes place in the architecture-specific [arch_dup_task_struct](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/process.c#L244) 1. New kernel stack is allocated. If `CONFIG_VMAP_STACK` is enabled the kernel uses [virtually mapped stacks](https://lwn.net/Articles/692208/) to protect against kernel stack overflow. [link](https://github.com/torvalds/linux/blob/v4.14/kernel/fork.c#L525) 1. Task's credentials are copied. [link](https://github.com/torvalds/linux/blob/v4.14/kernel/fork.c#L1628) 1. The scheduler is notified that a new task is forked. [link](https://github.com/torvalds/linux/blob/v4.14/kernel/fork.c#L1727) 1. [task_fork_fair](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/fair.c#L9063) method of the CFS scheduler class is called. This method updates `vruntime` value for the currently running task (this is done inside [update_curr](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/fair.c#L827) function) and updates `min_vruntime` value for the current runqueue (inside [update_min_vruntime](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/fair.c#L514)). Then `min_vruntime` value is assigned to the forked task - this ensures that this task will be picked up next. Note, that at this point of time new task still hasn't been added to the `task_timeline`. 1. A lot of different properties, such as information about filesystems, open files, virtual memory, signals, namespaces, are either reused or copied from the current task. The decision whether to copy something or reuse current property is usually made based on the `clone_flags` parameter. [link](https://github.com/torvalds/linux/blob/v4.14/kernel/fork.c#L1731-L1765) 1. [copy_thread_tls](https://github.com/torvalds/linux/blob/v4.14/kernel/fork.c#L1766) is called which in turn calls architecture specific [copy_thread](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/process.c#L254) function. This function deserves a special attention because it works as a prototype for the [copy_process](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/fork.c#L5) function in the RPi OS, and I want to investigate it deeper. ### copy_thread The whole function is listed below. ``` int copy_thread(unsigned long clone_flags, unsigned long stack_start, unsigned long stk_sz, struct task_struct *p) { struct pt_regs *childregs = task_pt_regs(p); memset(&p->thread.cpu_context, 0, sizeof(struct cpu_context)); if (likely(!(p->flags & PF_KTHREAD))) { *childregs = *current_pt_regs(); childregs->regs[0] = 0; /* * Read the current TLS pointer from tpidr_el0 as it may be * out-of-sync with the saved value. */ *task_user_tls(p) = read_sysreg(tpidr_el0); if (stack_start) { if (is_compat_thread(task_thread_info(p))) childregs->compat_sp = stack_start; else childregs->sp = stack_start; } /* * If a TLS pointer was passed to clone (4th argument), use it * for the new thread. */ if (clone_flags & CLONE_SETTLS) p->thread.tp_value = childregs->regs[3]; } else { memset(childregs, 0, sizeof(struct pt_regs)); childregs->pstate = PSR_MODE_EL1h; if (IS_ENABLED(CONFIG_ARM64_UAO) && cpus_have_const_cap(ARM64_HAS_UAO)) childregs->pstate |= PSR_UAO_BIT; p->thread.cpu_context.x19 = stack_start; p->thread.cpu_context.x20 = stk_sz; } p->thread.cpu_context.pc = (unsigned long)ret_from_fork; p->thread.cpu_context.sp = (unsigned long)childregs; ptrace_hw_copy_thread(p); return 0; } ``` Some of this code can be already a little bit familiar to you. Let's dig dipper into it. ``` struct pt_regs *childregs = task_pt_regs(p); ``` The function starts with allocating new [pt_regs](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/ptrace.h#L119) struct. This struct is used to provide access to the registers, saved during `kernel_entry`. `childregs` variable then can be used to prepare whatever state we need for the newly created task. If the task then decides to move to user mode the state will be restored by the `kernel_exit` macro. An important thing to understand here is that [task_pt_regs](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/processor.h#L161) macro doesn't allocate anything - it just calculate the position on the kernel stack, were `kernel_entry` stores registers, and for the newly created task, this position will always be at the top of the kernel stack. ``` memset(&p->thread.cpu_context, 0, sizeof(struct cpu_context)); ``` Next, forked task `cpu_context` is cleared. ``` if (likely(!(p->flags & PF_KTHREAD))) { ``` Then a check is made to determine whether we are creating a kernel or a user thread. For now, we are interested only in kernel thread case and we will discuss the second option in the next lesson. ``` memset(childregs, 0, sizeof(struct pt_regs)); childregs->pstate = PSR_MODE_EL1h; if (IS_ENABLED(CONFIG_ARM64_UAO) && cpus_have_const_cap(ARM64_HAS_UAO)) childregs->pstate |= PSR_UAO_BIT; p->thread.cpu_context.x19 = stack_start; p->thread.cpu_context.x20 = stk_sz; ``` If we are creating a kernel thread `x19` and `x20` registers of the `cpu_context` are set to point to the function that needs to be executed (`stack_start`) and its argument (`stk_sz`). After CPU will be switched to the forked task, [ret_from_fork](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L942) will use those registers to jump to the needed function. (I don't quite understand why do we also need to set `childregs->pstate` here. `ret_from_fork` will not call `kernel_exit` before jumping to the function stored in `x19`, and even if the kernel thread decides to move to the user mode `childregs` will be overwritten anyway. Any ideas?) ``` p->thread.cpu_context.pc = (unsigned long)ret_from_fork; p->thread.cpu_context.sp = (unsigned long)childregs; ``` Next `cpu_context.pc` is set to `ret_from_fork` pointer - this ensures that we return to the `ret_from_fork` after the first context switch. `cpu_context.sp` is set to the location just below the `childregs`. We still need `childregs` at the top of the stack because after the kernel thread finishes its execution the task will be moved to user mode and `childregs` structure will be used. In the next lesson, we will discuss in details how this happens. That's it about `copy_thread` function. Now let's return to the place in the fork procedure from where we left. ### Fork procedure (continued) 1. After `copy_process` succsesfully prepares `task_struct` for the forked task `_do_fork` can now run it by calling [wake_up_new_task](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/core.c#L2438). This is done [here](https://github.com/torvalds/linux/blob/v4.14/kernel/fork.c#L2074). Then task state is changed to `TASK_RUNNING` and [enqueue_task_fair](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/fair.c#L4879) CFS method is called, wich triggers execution of the [__enqueue_entity](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/fair.c#L549) that actually adds task to the `task_timeline` red-black tree. 1. At [this](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/core.c#L2463) line, [check_preempt_curr](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/core.c#L871) is called, which in turn calls [check_preempt_wakeup](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/fair.c#L6167) CFS method. This method is responsible for checking whether the current task should be preempted by some other task. That is exactly what is going to happen because we have just put a new task on the timeline that has minimal possible `vruntime`. So [resched_curr](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/core.c#L479) function is triggered, which sets `TIF_NEED_RESCHED` flag for the current task. 1. `TIF_NEED_RESCHED` is checked just before the current task exit from an exception handler (`fork`, `vfork` and `clone` are all system call, and each system call is a special type of exception.). The check is made [here](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L801). Note that [_TIF_WORK_MASK](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/thread_info.h#L109) includes `_TIF_NEED_RESCHED`. It is also important to understand that in case of a kernel thread creation, the new thread will not be started until the next timer tick or until the parent task volatirely calls `schedule()`. 1. If the current task needs to be rescheduled, [do_notify_resume](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/signal.c#L744) is triggered, which in turn calls [schedule](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/core.c#L3418). Finally we reached the point where task scheduling is triggered, and we are going to stop at this point. ### Conclusion Now that you understand how new tasks are created and added to the scheduler, it is time to take a look on how the scheduler itself works and how context switch is implemented. That is something we are going to explore in the next chapter. ##### Previous Page 4.2 [Process scheduler: Scheduler basic structures](../../../docs/lesson04/linux/basic_structures.md) ##### Next Page 4.4 [Process scheduler: Scheduler](../../../docs/lesson04/linux/scheduler.md) ================================================ FILE: docs/lesson04/linux/scheduler.md ================================================ ## 4.4: Scheduler We have already learned a lot of details about the Linux scheduler inner workings, so there is not so much left for us. To make the whole picture complete in this chapter we will take a look at 2 important scheduler entry points: 1. [scheduler_tick()](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/core.c#L3003) function, which is called at each timer interrupt. 1. [schedule()](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/core.c#L3418) function, which is called each time when the current task needs to be rescheduled. The third major thing that we are going to investigate in this chapter is the concept of context switch. A context switch is the process that suspends the current task and runs another task instead - this process is highly architecture specific and closely correlates with what we have been doing when working with RPi OS. ### scheduler_tick This function is important for 2 reasons: 1. It provides a way for the scheduler to update time statistics and runtime information for the current task. 1. Runtime information then is used to determine whether the current task needs to be preempted, and if so `schedule()` function is called. As well as most of the previously explored functions, `scheduler_tick` is too complex to be fully explained - instead, as usual, I will just highlight the most important parts. 1. The main work is done inside CFS method [task_tick_fair](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/fair.c#L9044). This method calls [entity_tick](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/fair.c#L3990) for the `sched_entity` corresponding to the current task. When looking at the source code, you may be wondering why instead of just calling `entry_tick` for the current `sched_entry`, `for_each_sched_entity` macro is used instead? `for_each_sched_entity` doesn't iterate over all `sched_entry` in the system. Instead, it only traverses the `sched_entry` inheritance tree up to the root. This is useful when tasks are grouped - after updating runtime information for a particular task, `sched_entry` corresponding to the whole group is also updated. 1. [entity_tick](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/fair.c#L3990) does 2 main things: * Calls [update_curr](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/fair.c#L827), which is responsible for updating task's `vruntime` as well as runqueue's `min_vruntime`. An important thing to remember here is that `vruntime` is always based on 2 things: how long task has actually been executed and tasks priority. * Calls [check_preempt_tick](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/fair.c#L3834), which checks whether the current task needs to be preempted. Preemption happens in 2 cases: 1. If the current task has been running for too long (the comparison is made using normal time, not `vruntime`). [link](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/fair.c#L3842) 1. If there is a task with smaller `vruntime` and the difference between `vruntime` values is greater than some threshold. [link](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/fair.c#L3866) In both cases the current task is marked for preemption by calling [resched_curr](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/core.c#L479) function. We have already seen in the previous chapter how calling `resched_curr` leads to `TIF_NEED_RESCHED` flag being set for the current task and eventually `schedule` being called. That's it about `schedule_tick` now we are finally ready to take a look at the `schedule` function. ### schedule We have already seen so many examples were `schedule` is used, so now you are probably anxious to see how this function actually works. You will be surprised to know that the internals of this function are rather simple. 1. The main work is done inside [__schedule](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/core.c#L3278) function. 1. `__schedule` calls [pick_next_task](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/core.c#L3199) which redirect most of the work to the [pick_next_task_fair](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/fair.c#L6251) method of the CFS scheduler. 1. As you might expect `pick_next_task_fair` in a normal case just selects the leftmost element from the red-black tree and returns it. It happens [here](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/fair.c#L3915). 1. `__schedule` calls [context_switch](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/core.c#L2750), which does some preparation work and calls architecture specific [__switch_to](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/process.c#L348) function, where low-level arch specific task parameters are prepared to the switch. 1. `__switch_to` first switches some additional task components, like, for example, TLS (Thread-local Store) and saved floating point and NEON registers. 1. Actual switch takes place in the assembler [cpu_switch_to](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L914) function. This function should be already familiar to you because I copied it almost without any changes to the RPi OS. As you might remember, this function switches callee-saved registers and task stack. After it returns, the new task will be running using its own kernel stack. ### Conclusion Now we are done with the Linux scheduler. The good thing is that it appears to be not so difficult if you focus only on the very basic workflow. After you understand the basic workflow you probably might want to to make another path through the schedule code and pay more attention to the details, because there are so many of them. But for now, we are happy with our current understanding and ready to move to the following lesson, which describes user processes and system calls. ##### Previous Page 4.3 [Process scheduler: Forking a task](../../../docs/lesson04/linux/fork.md) ##### Next Page 4.5 [Process scheduler: Exercises](../../../docs/lesson04/exercises.md) ================================================ FILE: docs/lesson04/rpi-os.md ================================================ ## 4.1: Scheduler By now, the PRi OS is already a fairly complicated bare metal program, but to be honest, we still can't call it an operating system. The reason is that it can't do any of the core tasks that any OS should do. One of such core tasks is called process scheduling. By scheduling I mean that an operating system should be able to share CPU time between different processes. The hard part of it is that a process should be unaware of the scheduling happening: it should view itself as the only one occupying the CPU. In this lesson, we are going to add this functionality to the RPi OS. ### task_struct If we want to manage processes, the first thing we should do is to create a struct that describes a process. Linux has such a struct and it is called `task_struct` (in Linux both thread and processes are just different types of tasks). As we are mostly mimicking Linux implementation, we are going to do the same. RPi OS [task_struct](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/include/sched.h#L36) looks like the following. ``` struct cpu_context { unsigned long x19; unsigned long x20; unsigned long x21; unsigned long x22; unsigned long x23; unsigned long x24; unsigned long x25; unsigned long x26; unsigned long x27; unsigned long x28; unsigned long fp; unsigned long sp; unsigned long pc; }; struct task_struct { struct cpu_context cpu_context; long state; long counter; long priority; long preempt_count; }; ``` This struct has the following members: * `cpu_context` This is an embedded structure that contains values of all registers that might be different between the tasks, that are being switched. A reasonable question to ask is why do we save not all registers, but only registers `x19 - x30` and `sp`? (`fp` is `x29` and `pc` is `x30`) The answer is that actual context switch happens only when a task calls [cpu_switch_to](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/sched.S#L4) function. So, from the point of view of the task that is being switched, it just calls `cpu_switch_to` function and it returns after some (potentially long) time. The task doesn't notice that another task happens to runs during this period. Accordingly to ARM calling conventions registers `x0 - x18` can be overwritten by the called function, so the caller must not assume that the values of those registers will survive after a function call. That's why it doesn't make sense to save `x0 - x18` registers. * `state` This is the state of the currently running task. For tasks that are just doing some work on the CPU the state will always be [TASK_RUNNING](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/include/sched.h#L15). Actually, this is the only state that the RPi OS is going to support for now. However, later we will have to add a few additional states. For example, a task that is waiting for an interrupt should be moved to a different state, because it doesn't make sense to awake the task while the required interrupt hasn't yet happened. * `counter` This field is used to determine how long the current task has been running. `counter` decreases by 1 each timer tick and when it reaches 0 another task is scheduled. * `priority` When a new task is scheduled its `priority` is copied to `counter`. By setting tasks priority, we can regulate the amount of processor time that the task gets relative to other tasks. * `preempt_count` If this field has a non-zero value it is an indicator that right now the current task is executing some critical function that must not be interrupted (for example, it runs the scheduling function.). If timer tick occurs at such time it is ignored and rescheduling is not triggered. After the kernel startup, there is only one task running: the one that runs [kernel_main](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/kernel.c#L19) function. It is called "init task". Before the scheduler functionality is enabled, we must fill `task_struct` corresponding to the init task. This is done [here](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/include/sched.h#L53). All tasks are stored in [task](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/sched.c#L7) array. This array has only 64 slots - that is the maximum number of simultaneous tasks that we can have in the RPi OS. It is definitely not the best solution for the production-ready OS, but it is ok for our goals. There is also a very important variable called [current](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/sched.c#L6) that always points to the currently executing task. Both `current` and `task` array are initially set to hold a pointer to the init task. There is also a global variable called [nr_tasks](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/sched.c#L8) - it contains the number of currently running tasks in the system. Those are all structures and global variables that we are going to use to implement the scheduler functionality. In the description of the `task_struct` I already briefly mentioned some aspects of how scheduling works, because it is impossible to understand the meaning of a particular `task_struct` field without understanding how this field is used. Now we are going to examine the scheduling algorithm in much more details and we will start with the `kernel_main` function. ### `kernel_main` function Before we dig into the scheduler implementation, I want to quickly show you how we are going to prove that the scheduler actually works. To understand it, you need to take a look at the [kernel.c](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/kernel.c) file. Let me copy the relevant content here. ``` void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); int res = copy_process((unsigned long)&process, (unsigned long)"12345"); if (res != 0) { printf("error while starting process 1"); return; } res = copy_process((unsigned long)&process, (unsigned long)"abcde"); if (res != 0) { printf("error while starting process 2"); return; } while (1){ schedule(); } } ``` There are a few important things about this code. 1. New function [copy_process](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/fork.c#L5) is introduced. `copy_process` takes 2 arguments: a function to execute in a new thread and an argument that need to be passed to this function. `copy_process` allocates a new `task_struct` and makes it available for the scheduler. 1. Another new function for us is called [schedule](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/sched.c#L21). This is the core scheduler function: it checks whether there is a new task that needs to preempt the current one. A task can voluntarily call `schedule` if it doesn't have any work to do at the moment. `schedule` is also called from the timer interrupt handler. We are calling `copy_process` 2 times, each time passing a pointer to the [process](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/kernel.c#L9) function as the first argument. `process` function is very simple. ``` void process(char *array) { while (1){ for (int i = 0; i < 5; i++){ uart_send(array[i]); delay(100000); } } } ``` It just keeps printing on the screen characters from the array, that is passed as an argument The first time it is called with the argument "12345" and second time the argument is "abcde". If our scheduler implementation is correct, we should see on the screen mixed output from both threads. ### Memory allocation Each task in the system should have its dedicated stack. That's why when creating a new task we must have a way to allocate memory. For now, our memory allocator is extremely primitive. (The implementation can be found in [mm.c](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/mm.c) file) ``` static unsigned short mem_map [ PAGING_PAGES ] = {0,}; unsigned long get_free_page() { for (int i = 0; i < PAGING_PAGES; i++){ if (mem_map[i] == 0){ mem_map[i] = 1; return LOW_MEMORY + i*PAGE_SIZE; } } return 0; } void free_page(unsigned long p){ mem_map[(p - LOW_MEMORY) / PAGE_SIZE] = 0; } ``` The allocator can work only with memory pages (each page is 4 KB in size). There is an array called `mem_map` that for each page in the system holds its status: whether it is allocated or free. Whenever we need to allocate a new page, we just loop through this array and return the first free page. This implementation is based on 2 assumptions: 1. We know the total amount of memory in the system. It is `1 GB - 1 MB` (the last megabyte of memory is reserved for device registers.). This value is stored in the [HIGH_MEMORY](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/include/mm.h#L14) constant. 1. First 4 MB of memory are reserved for the kernel image and init task stack. This value is stored in [LOW_MEMORY](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/include/mm.h#L13) constant. All memory allocations start right after this point. ### Creating a new task New task allocation is implemented in [copy_process](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/fork.c#L5) function. ``` int copy_process(unsigned long fn, unsigned long arg) { preempt_disable(); struct task_struct *p; p = (struct task_struct *) get_free_page(); if (!p) return 1; p->priority = current->priority; p->state = TASK_RUNNING; p->counter = p->priority; p->preempt_count = 1; //disable preemtion until schedule_tail p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)p + THREAD_SIZE; int pid = nr_tasks++; task[pid] = p; preempt_enable(); return 0; } ``` Now, we are going to examine it in details. ``` preempt_disable(); struct task_struct *p; ``` The function starts with disabling preemption and allocating a pointer for the new task. Preemption is disabled because we don't want to be rescheduled to a different task in the middle of the `copy_process` function. ``` p = (struct task_struct *) get_free_page(); if (!p) return 1; ``` Next, a new page is allocated. At the bottom of this page, we are putting the `task_struct` for the newly created task. The rest of this page will be used as the task stack. ``` p->priority = current->priority; p->state = TASK_RUNNING; p->counter = p->priority; p->preempt_count = 1; //disable preemtion until schedule_tail ``` After the `task_struct` is allocated, we can initialize its properties. Priority and initial counters are set based on the current task priority. The state is set to `TASK_RUNNING`, indicating that the new task is ready to be started. `preempt_count` is set to 1, meaning that after the task is executed it should not be rescheduled until it completes some initialization work. ``` p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)p + THREAD_SIZE; ``` This is the most important part of the function. Here `cpu_context` is initialized. The stack pointer is set to the top of the newly allocated memory page. `pc` is set to the [ret_from_fork](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/entry.S#L146) function, and we need to look at this function now in order to understand why the rest of the `cpu_context` registers are initialized in the way they are. ``` .globl ret_from_fork ret_from_fork: bl schedule_tail mov x0, x20 blr x19 //should never return ``` As you can see `ret_from_fork` first call [schedule_tail](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/sched.c#L65), which just enabled preemption, and then it calls the function stored in `x19` register with the argument stored in `x20`. `x19` and `x20` are restored from the `cpu_context` just before `ret_from_fork` function is called. Now, let's go back to `copy_process`. ``` int pid = nr_tasks++; task[pid] = p; preempt_enable(); return 0; ``` Finally, `copy_process` adds the newly created task to the `task` array and enables preemption for the current task. An important thing to understand about the `copy_process` function is that after it finishes execution, no context switch happens. The function only prepares new `task_struct` and adds it to the `task` array — this task will be executed only after `schedule` function is called. ### Who calls `schedule`? Before we get into the details of the `schedule` function, lets first figure out how `schedule` is called. There are 2 scenarios. 1. When one task doesn't have anything to do right now, but it nevertheless can't be terminated, it can call `schedule` voluntarily. That is something `kernel_main` function does. 1. `schedule` is also called on a regular basis from the [timer interrupt handler](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/timer.c#L21). Now let's take a look at [timer_tick](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/sched.c#L70) function, which is called from the timer interrupt. ``` void timer_tick() { --current->counter; if (current->counter>0 || current->preempt_count >0) { return; } current->counter=0; enable_irq(); _schedule(); disable_irq(); ``` First of all, it decreases current task's counter. If the counter is greater then 0 or preemption is currently disabled the function returns, but otherwise`schedule` is called with interrupts enabled. (We are inside an interrupt handler, and interrupts are disabled by default) We will see why interrupts must be enabled during scheduler execution in the next section. ### Scheduling algorithm Finally, we are ready to look at the scheduler algorithm. I almost precisely copied this algorithm from the first release of the Linux kernel. You can find the original version [here](https://github.com/zavg/linux-0.01/blob/master/kernel/sched.c#L68). ``` void _schedule(void) { preempt_disable(); int next,c; struct task_struct * p; while (1) { c = -1; next = 0; for (int i = 0; i < NR_TASKS; i++){ p = task[i]; if (p && p->state == TASK_RUNNING && p->counter > c) { c = p->counter; next = i; } } if (c) { break; } for (int i = 0; i < NR_TASKS; i++) { p = task[i]; if (p) { p->counter = (p->counter >> 1) + p->priority; } } } switch_to(task[next]); preempt_enable(); } ``` The algorithm works like the following: * The first inner `for` loop iterates over all tasks and tries to find a task in `TASK_RUNNING` state with the maximum counter. If such task is found and its counter is greater then 0, we immediately break from the outer `while` loop and switch to this task. If no such task is found this means that no tasks in `TASK_RUNNING` state currently exist or all such tasks have 0 counters. In a real OS, the first case might happen, for example, when all tasks are waiting for an interrupt. In this case, the second nested `for` loop is executed. For each task (no matter what state it is in) this loop increases its counter. The counter increase is done in a very smart way: 1. The more iterations of the second `for` loop a task passes, the more its counter will be increased. 2. A task counter can never get larger than `2 * priority`. * Then the process is repeated. If there are at least one task in `TASK_RUNNIG` state, the second iteration of the outer `while` loop will be the last one because after the first iteration all counters are already non-zero. However, if no `TASK_RUNNING` tasks are there, the process is repeated over and over again until some of the tasks will move to `TASK_RUNNING` state. But if we are running on a single CPU, how then a task state can change while this loop is running? The answer is that if some task is waiting for an interrupt, this interrupt can happen while `schedule` function is executed and interrupt handler can change the state of the task. This actually explains why interrupts must be enabled during `schedule` execution. This also demonstrates an important distinction between disabling interrupts and disabling preemption. `schedule` disables preemption for the duration of the whole function. This ensures that nested `schedule` will not be called while we are in the middle of the original function execution. However, interrupts can legally happen during `schedule` function execution. I paid a lot of attention to the situation where some task is waiting for an interrupt, though this functionality isn't implemented in the RPi OS yet. But I still consider it necessary to understand this case because it is a part of the core scheduler algorithm and similar functionality will be added later. ### Switching tasks After the task in `TASK_RUNNING` state with non-zero counter is found, [switch_to](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/sched.c#L56) function is called. It looks like this. ``` void switch_to(struct task_struct * next) { if (current == next) return; struct task_struct * prev = current; current = next; cpu_switch_to(prev, next); } ``` Here we check that next process is not the same as the current, and if not, `current` variable is updated. The actual work is redirected to [cpu_switch_to](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/sched.S) function. ``` .globl cpu_switch_to cpu_switch_to: mov x10, #THREAD_CPU_CONTEXT add x8, x0, x10 mov x9, sp stp x19, x20, [x8], #16 // store callee-saved registers stp x21, x22, [x8], #16 stp x23, x24, [x8], #16 stp x25, x26, [x8], #16 stp x27, x28, [x8], #16 stp x29, x9, [x8], #16 str x30, [x8] add x8, x1, x10 ldp x19, x20, [x8], #16 // restore callee-saved registers ldp x21, x22, [x8], #16 ldp x23, x24, [x8], #16 ldp x25, x26, [x8], #16 ldp x27, x28, [x8], #16 ldp x29, x9, [x8], #16 ldr x30, [x8] mov sp, x9 ret ``` This is the place where the real context switch happens. Let's examine it line by line. ``` mov x10, #THREAD_CPU_CONTEXT add x8, x0, x10 ``` `THREAD_CPU_CONTEXT` constant contains offset of the `cpu_context` structure in the `task_struct`. `x0` contains a pointer to the first argument, which is the current `task_struct` (by current here I mean the one we are switching from). After the copied 2 lines are executed, `x8` will contain a pointer to the current `cpu_context`. ``` mov x9, sp stp x19, x20, [x8], #16 // store callee-saved registers stp x21, x22, [x8], #16 stp x23, x24, [x8], #16 stp x25, x26, [x8], #16 stp x27, x28, [x8], #16 stp x29, x9, [x8], #16 str x30, [x8] ``` Next all calle-saved registers are stored in the order, in which they are defined in `cpu_context` structure. `x30`, which is the link register and contains function return address, is stored as `pc`, current stack pointer is saved as `sp` and `x29` is saved as `fp` (frame pointer). ``` add x8, x1, x10 ``` Now `x10` contains an offset of the `cpu_context` structure inside `task_struct`, `x1` is a pointer to the next `task_struct`, so `x8` will contain a pointer to the next `cpu_context`. ``` ldp x19, x20, [x8], #16 // restore callee-saved registers ldp x21, x22, [x8], #16 ldp x23, x24, [x8], #16 ldp x25, x26, [x8], #16 ldp x27, x28, [x8], #16 ldp x29, x9, [x8], #16 ldr x30, [x8] mov sp, x9 ``` Callee saved registers are restored from the next `cpu_context`. ``` ret ``` Function returns to the location pointed to by the link register (`x30`) If we are switching to some task for the first time, this will be [ret_from_fork](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/entry.S#L148) function. In all other cases this will be the location, previously saved in the `cpu_context` by the `cpu_switch_to` function. ### How scheduling works with exception entry/exit? In the previous lesson, we have seen how [kernel_entry](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/entry.S#L17) and [kernel_exit](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/entry.S#L4) macros are used to save and restore the processor state. After the scheduler has been introduced, a new problem arrives: now it becomes fully legal to enter an interrupt as one task and leave it as a different one. This is a problem, because `eret` instruction, which we are using to return from an interrupts, relies on the fact that return address should be stored in `elr_el1` and processor state in `spsr_el1` registers. So, if we want to switch tasks while processing an interrupt, we must save and restore those 2 registers alongside with all other general purpose registers. The code that does this is very straightforward, you can find the save part [here](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/entry.S#L35) and restore [here](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/entry.S#L46). ### Tracking system state during a context switch We have already examined all source code related to the context switch. However, that code contains a lot of asynchronous interactions that make it difficult to fully understand how the state of the whole system changes over time. In this section I want to make this task easier for you: I want to describe the sequence of events that happen from system startup to the time of the second context switch. For each such event, I will also include a diagram representing the state of the memory at the time of the event. I hope that such representation will help you to get a deep understanding of how the scheduler works. So, let's start! 1. The kernel is initialized and [kernel_main](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/kernel.c#L19) function is executed. The initial stack is configured to start at [LOW_MEMORY](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/include/mm.h#L13), which is 4 MB. ``` 0 +------------------+ | kernel image | |------------------| | | |------------------| | init task stack | 0x00400000 +------------------+ | | | | 0x3F000000 +------------------+ | device registers | 0x40000000 +------------------+ ``` 1. `kernel_main` calls [copy_process](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/fork.c#L5) for the first time. New 4 KB memory page is allocated, and `task_struct` is placed at the bottom of this page. (Later I will refer to the task created at this point as "task 1") ``` 0 +------------------+ | kernel image | |------------------| | | |------------------| | init task stack | 0x00400000 +------------------+ | task_struct 1 | |------------------| | | 0x00401000 +------------------+ | | | | 0x3F000000 +------------------+ | device registers | 0x40000000 +------------------+ ``` 1. `kernel_main` calls `copy_process` for the second time and the same process repeats. Task 2 is created and added to the task list. ``` 0 +------------------+ | kernel image | |------------------| | | |------------------| | init task stack | 0x00400000 +------------------+ | task_struct 1 | |------------------| | | 0x00401000 +------------------+ | task_struct 2 | |------------------| | | 0x00402000 +------------------+ | | | | 0x3F000000 +------------------+ | device registers | 0x40000000 +------------------+ ``` 1. `kernel_main` voluntarily calls [schedule](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/sched.c#L21) function and it decides to run task 1. 1. [cpu_switch_to](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/sched.S#L4) saves calee-saved registers in the init task `cpu_context`, which is located inside the kernel image. 1. `cpu_switch_to` restores calee-saved registers from task 1 `cpu_context`. `sp` now points to `0x00401000`, link register to [ret_from_fork](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/entry.S#L146) function, `x19` contains a pointer to [process](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/kernel.c#L9) function and `x20` a pointer to string "12345", which is located somewhere in the kernel image. 1. `cpu_switch_to` calls `ret` instruction, which jumps to the `ret_from_fork` function. 1. `ret_from_fork` reads `x19` and `x20` registers and calls `process` function with the argument "12345". After `process` function starts to execute its stack begins to grow. ``` 0 +------------------+ | kernel image | |------------------| | | |------------------| | init task stack | 0x00400000 +------------------+ | task_struct 1 | |------------------| | | |------------------| | task 1 stack | 0x00401000 +------------------+ | task_struct 2 | |------------------| | | 0x00402000 +------------------+ | | | | 0x3F000000 +------------------+ | device registers | 0x40000000 +------------------+ ``` 1. A timer interrupt occured. [kernel_entry](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/entry.S#L17) macro saves all general purpose registers + `elr_el1` and `spsr_el1` to the bottom of task 1 stack. ``` 0 +------------------------+ | kernel image | |------------------------| | | |------------------------| | init task stack | 0x00400000 +------------------------+ | task_struct 1 | |------------------------| | | |------------------------| | task 1 saved registers | |------------------------| | task 1 stack | 0x00401000 +------------------------+ | task_struct 2 | |------------------------| | | 0x00402000 +------------------------+ | | | | 0x3F000000 +------------------------+ | device registers | 0x40000000 +------------------------+ ``` 1. `schedule` is called and it decides to run task 2. But we still run task 1 and its stack continues to grow below task 1 saved registers region. On the diagram, this part of the stack is marked as (int), which means "interrupt stack" ``` 0 +------------------------+ | kernel image | |------------------------| | | |------------------------| | init task stack | 0x00400000 +------------------------+ | task_struct 1 | |------------------------| | | |------------------------| | task 1 stack (int) | |------------------------| | task 1 saved registers | |------------------------| | task 1 stack | 0x00401000 +------------------------+ | task_struct 2 | |------------------------| | | 0x00402000 +------------------------+ | | | | 0x3F000000 +------------------------+ | device registers | 0x40000000 +------------------------+ ``` 1. `cpu_switch_to` runs task 2. In order to do this, it executes exactly the same sequence of steps that it does for task 1. Task 2 started to execute and it stack grows. Note, that we didn't return from an interrupt at this point, but this is ok because interrupts now are enabled (interrupts have been enabled previously in [timer_tick](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/sched.c#L70) before `schedule` was called) ``` 0 +------------------------+ | kernel image | |------------------------| | | |------------------------| | init task stack | 0x00400000 +------------------------+ | task_struct 1 | |------------------------| | | |------------------------| | task 1 stack (int) | |------------------------| | task 1 saved registers | |------------------------| | task 1 stack | 0x00401000 +------------------------+ | task_struct 2 | |------------------------| | | |------------------------| | task 2 stack | 0x00402000 +------------------------+ | | | | 0x3F000000 +------------------------+ | device registers | 0x40000000 +------------------------+ ``` 1. Another timer interrupt happens and `kernel_entry` saves all general purpose registers + `elr_el1` and `spsr_el1` at the bottom of task 2 stack. Task 2 interrupt stack begins to grow. ``` 0 +------------------------+ | kernel image | |------------------------| | | |------------------------| | init task stack | 0x00400000 +------------------------+ | task_struct 1 | |------------------------| | | |------------------------| | task 1 stack (int) | |------------------------| | task 1 saved registers | |------------------------| | task 1 stack | 0x00401000 +------------------------+ | task_struct 2 | |------------------------| | | |------------------------| | task 2 stack (int) | |------------------------| | task 2 saved registers | |------------------------| | task 2 stack | 0x00402000 +------------------------+ | | | | 0x3F000000 +------------------------+ | device registers | 0x40000000 +------------------------+ ``` 1. `schedule` is called. It observes that all tasks have their counters set to 0 and set counters to their tasks priorities. 1. `schedule` selects init task to run. (This is because all tasks now have their counters set to 1 and init task is the first in the list). But actually, it would be fully legal for `schedule` to select task 1 or task 2 at this point, because their counters has equal values. We are more interested in the case when task 1 is selected so let's now assume that this is what had happened. 1. `cpu_switch_to` is called and it restores previously saved callee-saved registers from task 1 `cpu_context`. Link register now points [here](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/sched.c#L63) because this is the place from which `cpu_switch_to` was called last time when task 1 was executed. `sp` points to the bottom of task 1 interrupt stack. 1. `timer_tick` function resumes execution, starting from [this](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/sched.c#L79) line. It disables interrupts and finally [kernel_exit](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/sched.c#L79) is executed. By the time `kernel_exit` is called, task 1 interrupt stack is collapsed to 0. ``` 0 +------------------------+ | kernel image | |------------------------| | | |------------------------| | init task stack | 0x00400000 +------------------------+ | task_struct 1 | |------------------------| | | |------------------------| | task 1 saved registers | |------------------------| | task 1 stack | 0x00401000 +------------------------+ | task_struct 2 | |------------------------| | | |------------------------| | task 2 stack (int) | |------------------------| | task 2 saved registers | |------------------------| | task 2 stack | 0x00402000 +------------------------+ | | | | 0x3F000000 +------------------------+ | device registers | 0x40000000 +------------------------+ ``` 1. `kernel_exit` restores all general purpose registers as well as `elr_el1` and `spsr_el1`. `elr_el1` now points somewhere in the middle of the `process` function. `sp` points to the bottom of task 1 stack. ``` 0 +------------------------+ | kernel image | |------------------------| | | |------------------------| | init task stack | 0x00400000 +------------------------+ | task_struct 1 | |------------------------| | | |------------------------| | task 1 stack | 0x00401000 +------------------------+ | task_struct 2 | |------------------------| | | |------------------------| | task 2 stack (int) | |------------------------| | task 2 saved registers | |------------------------| | task 2 stack | 0x00402000 +------------------------+ | | | | 0x3F000000 +------------------------+ | device registers | 0x40000000 +------------------------+ ``` 1. `kernel_exit` executes `eret` instruction which uses `elr_el1` register to jump back to `process` function. Task 1 resumes it normal execution. The described above sequence of steps is very important — I personally consider it one of the most important things in the whole tutorial. If you have difficulties with understanding it, I can advise you to work on the exercise number 1 from this lesson. ### Conclusion We are done with scheduling, but right now our kernel can manage only kernel threads: they are executed at EL1 and can directly access any kernel functions or data. In the next 2 lessons we are going fix this and introduce system calls and virtual memory. ##### Previous Page 3.5 [Interrupt handling: Exercises](../../docs/lesson03/exercises.md) ##### Next Page 4.2 [Process scheduler: Scheduler basic structures](../../docs/lesson04/linux/basic_structures.md) ================================================ FILE: docs/lesson05/exercises.md ================================================ ## 5.3: Exercises 1. When a task is executed in user mode, try to access some of the system registers. Make sure that a synchronous exception is generated in this case. Handle this exception, use `esr_el1` register to distinguish it from a system call. 1. Implement a new system call that can be used to set current task priority. Demonstrate how priority changes are dynamically applied while the task is running. 1. Adapt lesson 05 to run on qemu. Check [this](https://github.com/s-matyukevich/raspberry-pi-os/issues/8) issue for reference. ##### Previous Page 5.2 [User processes and system calls: Linux](../../docs/lesson05/linux.md) ##### Next Page 6.1 [Virtual memory management: RPi OS](../../docs/lesson06/rpi-os.md) ================================================ FILE: docs/lesson05/linux.md ================================================ ## 5.2: User processes and system calls This chapter is going to be a short one. The reason is that I copied syscall implementation from Linux to RPi OS almost precisely, therefore it doesn't require a lot of explanations. But still I want to guide you through the Linux source code so that you can see where and how a particular syscall functionality is implemented. ### Creating first user process The first question we are going to tackle is how the first user process is created. A good place to start looking for the answer is [start_kernel](https://github.com/torvalds/linux/blob/v4.14/init/main.c#L509) function - as we've seen previously, this is the first architecture independent function that is called early in the kernel boot process. This is where the kernel initialization begins, and it would make sense to run the first user process during kernel initialization. An indeed, if you follow the logic of `start_kernel` you will soon discover [kernel_init](https://github.com/torvalds/linux/blob/v4.14/init/main.c#L989) function that has the following code. ``` if (!try_to_run_init_process("/sbin/init") || !try_to_run_init_process("/etc/init") || !try_to_run_init_process("/bin/init") || !try_to_run_init_process("/bin/sh")) return 0; ``` It looks like this is precisely what we are looking for. From this code we can also infer where exactly and in which order Linux kernel looks for the `init` user program. `try_to_run_init_process` then executes [do_execve](https://github.com/torvalds/linux/blob/v4.14/fs/exec.c#L1837) function, which is also responsible for handling [execve](http://man7.org/linux/man-pages/man2/execve.2.html) system call. This system call reads a binary executable file and runs it inside the current process. The logic behind the `execve` system call will be explored in details in the lesson 9, for now, it will be enough to mention that the most important work that this syscall does is parsing executable file and loading its content into memory, and it is done inside [load_elf_binary](https://github.com/torvalds/linux/blob/v4.14/fs/binfmt_elf.c#L679) function. (Here we assume that the executable file is in [ELF](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format) format - it is the most popular, but not the only choice) At the end of `load_elf_binary` method (actually [here](https://github.com/torvalds/linux/blob/v4.14/fs/binfmt_elf.c#L1149)) there is a call to architecture specific [start_thread](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/processor.h#L119) function. I used it as a prototype for the RPi OS `move_to_user_mode` routine, and this is the code that we mostly care about. Here it is. ``` static inline void start_thread_common(struct pt_regs *regs, unsigned long pc) { memset(regs, 0, sizeof(*regs)); forget_syscall(regs); regs->pc = pc; } static inline void start_thread(struct pt_regs *regs, unsigned long pc, unsigned long sp) { start_thread_common(regs, pc); regs->pstate = PSR_MODE_EL0t; regs->sp = sp; } ``` By the time `start_thread` is executed, the current process operates in the kernel mode. `start_thread` is given access to the current `pt_regs` struct, which is used to set saved `pstate`, `sp` and `pc` fields. The logic here is exactly the same as in the RPi OS `move_to_user_mode` function, so I don't want to repeat it one more time. An important thing to remember is that `start_thread` prepares saved processor state in such a way that `kernel_exit` macro will eventually move the process to user mode. ### Linux syscalls It would be no surprise to you that the primary syscall mechanism is exactly the same in Linux and RPi OS. Now we are going to use already familiar [clone](http://man7.org/linux/man-pages/man2/clone.2.html) syscall to understand the details of this mechanism. It would make sense to start our exploration with the [glibc clone wrapper function](https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/aarch64/clone.S;h=e0653048259dd9a3d3eb3103ec2ae86acb43ef48;hb=HEAD#l35). It works exactly the same as [call_sys_clone](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/sys.S#L22) function in the RPi OS, with the exception that the former function performers arguments sanity check and handles exceptions properly. An important thing to remember and understand is that in both cases we are using `svc` instruction to generate a synchronous exception, syscall number is passed using `x8` register and all arguments are passed in registers `x0` - `x7`. Next, let's take a look at how `clone` syscall is defined. The definition can be found [here](https://github.com/torvalds/linux/blob/v4.14/kernel/fork.c#L2153) and looks like this. ``` SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp, int __user *, parent_tidptr, int __user *, child_tidptr, unsigned long, tls) { return _do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr, tls); } ``` [SYSCALL_DEFINE5](https://github.com/torvalds/linux/blob/v4.14/include/trace/syscall.h#L25) macro has number 5 in its name indicating that we are defining a syscall with 5 parameters. The macro allocates and populates new [syscall_metadata](https://github.com/torvalds/linux/blob/v4.14/include/trace/syscall.h#L25) struct and creates `sys_` function. For example, in case of the `clone` syscall `sys_clone` function will be defined - this is the actuall syscall handler that will be called from the low-level architecture code. When a syscall is executed, the kernel needs a way to find syscall handler by the syscall number. The easiest way to achieve this is to create an array of pointers to syscall handlers and use syscall number as an index in this array. This is the approach we used in the RPi OS and exactly the same approach is used in the Linux kernel. The array of pointers to syscall handlers is called [sys_call_table](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/sys.c#L62) and is defined like this. ``` void * const sys_call_table[__NR_syscalls] __aligned(4096) = { [0 ... __NR_syscalls - 1] = sys_ni_syscall, #include }; ``` All syscalls are initially allocated to point to `sys_ni_syscall` function ("ni" here means "non-existent"). Syscall with number `0` and all syscalls that aren't defined for the current architecture will keep this handler. All other syscall handlers in the `sys_call_table` array are rewritten in the [asm/unistd.h](https://github.com/torvalds/linux/blob/v4.14/include/uapi/asm-generic/unistd.h) header file. As you might see, this file simply provides a mapping between syscall number and syscall handler function. ### Low-level syscall handling code Ok, we've seen how `sys_call_table` is created and populated, now it is time to investigate how it is used by the low-level syscall handling code. And once again the basic mechanism here will be almost exactly the same as in the RPi OS. We know that any syscall is a synchronous exception and all exception handlers are defined in the exception vector table (it is our favorite [vectors](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L367) array). The handler that we are interested in should be the one that handles synchronous exceptions generated at EL0. All of this makes it trivial to find the right handler, it is called [el0_sync](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L598) and looks like the following. ``` el0_sync: kernel_entry 0 mrs x25, esr_el1 // read the syndrome register lsr x24, x25, #ESR_ELx_EC_SHIFT // exception class cmp x24, #ESR_ELx_EC_SVC64 // SVC in 64-bit state b.eq el0_svc cmp x24, #ESR_ELx_EC_DABT_LOW // data abort in EL0 b.eq el0_da cmp x24, #ESR_ELx_EC_IABT_LOW // instruction abort in EL0 b.eq el0_ia cmp x24, #ESR_ELx_EC_FP_ASIMD // FP/ASIMD access b.eq el0_fpsimd_acc cmp x24, #ESR_ELx_EC_FP_EXC64 // FP/ASIMD exception b.eq el0_fpsimd_exc cmp x24, #ESR_ELx_EC_SYS64 // configurable trap b.eq el0_sys cmp x24, #ESR_ELx_EC_SP_ALIGN // stack alignment exception b.eq el0_sp_pc cmp x24, #ESR_ELx_EC_PC_ALIGN // pc alignment exception b.eq el0_sp_pc cmp x24, #ESR_ELx_EC_UNKNOWN // unknown exception in EL0 b.eq el0_undef cmp x24, #ESR_ELx_EC_BREAKPT_LOW // debug exception in EL0 b.ge el0_dbg b el0_inv ``` Here `esr_el1` (exception syndrome register) is used to figure out whether the current exception is a system call. If this is the case [el0_svc](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L837) function is called. This function is listed below. ``` el0_svc: adrp stbl, sys_call_table // load syscall table pointer mov wscno, w8 // syscall number in w8 mov wsc_nr, #__NR_syscalls el0_svc_naked: // compat entry point stp x0, xscno, [sp, #S_ORIG_X0] // save the original x0 and syscall number enable_dbg_and_irq ct_user_exit 1 ldr x16, [tsk, #TSK_TI_FLAGS] // check for syscall hooks tst x16, #_TIF_SYSCALL_WORK b.ne __sys_trace cmp wscno, wsc_nr // check upper syscall limit b.hs ni_sys ldr x16, [stbl, xscno, lsl #3] // address in the syscall table blr x16 // call sys_* routine b ret_fast_syscall ni_sys: mov x0, sp bl do_ni_syscall b ret_fast_syscall ENDPROC(el0_svc) ``` Now, let's examine it line by line. ``` el0_svc: adrp stbl, sys_call_table // load syscall table pointer mov wscno, w8 // syscall number in w8 mov wsc_nr, #__NR_syscalls ``` The first 3 lines initialize `stbl`, `wscno` and `wsc_nr` variables that are just aliases for some registers. `stbl` holds the address of the syscall table, `wsc_nr` contains the total number of system calls and `wscno` is the current syscall number from `w8` register. ``` stp x0, xscno, [sp, #S_ORIG_X0] // save the original x0 and syscall number ``` As you might remember from our RPi OS syscall discussion, `x0` is overwritten in the `pt_regs` area after a syscall finishes. In case when original value of the `x0` register might be needed, it is saved in the separate field of the `pt_regs` struct. Similarly syscall number is also saved in the `pt_regs`. ``` enable_dbg_and_irq ``` Interrupts and debug exceptions are enabled. ``` ct_user_exit 1 ``` The event of switching from the user mode to the kernel mode is recorded. ``` ldr x16, [tsk, #TSK_TI_FLAGS] // check for syscall hooks tst x16, #_TIF_SYSCALL_WORK b.ne __sys_trace ``` In case the current task is executed under a syscall tracer `_TIF_SYSCALL_WORK` flag should be set. In this case, `__sys_trace` function will be called. As our discussion is only focused on the general case, we are going to skip this function. ``` cmp wscno, wsc_nr // check upper syscall limit b.hs ni_sys ``` If current syscall number is greater then total syscall count an error is returned to the user. ``` ldr x16, [stbl, xscno, lsl #3] // address in the syscall table blr x16 // call sys_* routine b ret_fast_syscall ``` Syscall number is used as an index in the syscall table array to find the handler. Then handler address is loaded into `x16` register and it is executed. Finally `ret_fast_syscall` function is called. ``` ret_fast_syscall: disable_irq // disable interrupts str x0, [sp, #S_X0] // returned x0 ldr x1, [tsk, #TSK_TI_FLAGS] // re-check for syscall tracing and x2, x1, #_TIF_SYSCALL_WORK cbnz x2, ret_fast_syscall_trace and x2, x1, #_TIF_WORK_MASK cbnz x2, work_pending enable_step_tsk x1, x2 kernel_exit 0 ``` The important things here are the first line, were interrupts are disabled, and the last line, were `kernel_exit` is called - everything else is related to special case processing. So as you might guess this is the place where a system call actually finishes and execution is transfered to user process. ### Conclusion We've now gone through the process of generating and processing a system call. This process is relatively simple, but it is vital for the OS, because it allows the kernel to set up an API and make sure that this API is the only mean of communication between a user program and the kernel. ##### Previous Page 5.1 [User processes and system calls: RPi OS](../../docs/lesson05/rpi-os.md) ##### Next Page 5.3 [User processes and system calls: Exercises](../../docs/lesson05/exercises.md) ================================================ FILE: docs/lesson05/rpi-os.md ================================================ ## 5.1: User processes and system calls We have already added a lot of features to the RPi OS that makes it looks like an actual operating system instead of just a bare metal program. The RPi OS can now manage processes, but there is still a major drawback in this functionality: there is no process isolation at all. In this lesson, we are going to fix this issue. First of all, we will move all user processes to EL0, which restricts their access to privileged processor operations. Without this step any other isolation techniques don't make sense, because any user program will be able to rewrite our security settings, thus breaking from isolation. If we restrict user programs from direct access to kernel functions, this brings us a different problem. What if a user program needs, for example, to print something to a user? We definitely don't want it to work with the UART device directly. Instead, it would be nice if the OS provides each program with a set of API methods. Such API can't be implemented as a simple set of methods, because each time a user program wants to call one of the API methods current exception level should be raised to EL1. Individual methods in such API are called "system calls", and in this lesson, we will add a set of system calls to the RPi OS. There is also a third aspect of process isolation: each process should have its own independent view of memory — we are going to tackle this issue in the lesson 6. ### System calls implementation The main idea behind system calls (syscalls for short) is very simple: each system call is actually a synchronous exception. If a user program need to execute a syscall, it first has to to prepare all necessary arguments, and then run `svc` instruction. This instruction generates a synchronous exception. Such exceptions are handled at EL1 by the operating system. The OS then validates all arguments, performs the requested action and execute normal exception return, which ensures that the execution will resume at EL0 right after the `svc` instruction. The RPi OS defines 4 simple syscalls: 1. `write` This syscall outputs something on the screen using UART device. It accepts a buffer with the text to be printed as the first argument. 1. `clone` This syscall creates a new user thread. The location of the stack for the newly created thread is passed as the first argument. 1. `malloc` This system call allocates a memory page for a user process. There is no analog of this syscall in Linux (and I think in any other OS as well.) The only reason why we need it is that RPi OS doesn't implement virtual memory yet, and all user processes work with physical memory. That's why each process needs a way to figure out which memory page isn't occupied and can be used. `malloc` syscall return pointer to the newly allocated page or -1 in case of an error. 1. `exit` Each process must call this syscall after it finishes execution. It will do all necessary cleanup. All syscalls are defined in the [sys.c](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/sys.c) file. There is also an array [sys_call_table](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/sys.c) that contains pointers to all syscall handlers. Each syscall has a "syscall number" — this is just an index in the `sys_call_table` array. All syscall numbers are defined [here](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/include/sys.h#L6) — they are used by the assembler code to specify which syscall we are interested in. Let's use `write` syscall as an example and take a look at the syscall wrapper function. You can find it [here](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/sys.S#L4). ``` .globl call_sys_write call_sys_write: mov w8, #SYS_WRITE_NUMBER svc #0 ret ``` The function is very simple: it just stores syscall number in the `w8` register and generates a synchronous exception by executing `svc` instruction. `w8` is used for the syscall number by convention: registers `x0` — `x7`are used for syscall arguments and `x8` is used to store syscall number, this allows a syscall to have up to 8 arguments. Such wrapper functions are usually not included in the kernel itself — you are more likely to find them in the different language's standard libraries, such as [glibc](https://www.gnu.org/software/libc/). ### Handling synchronous exceptions After a synchronous exception is generated, the [handler](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/entry.S#L98), which is registered in the exception table, is called. The handler itself can be found [here](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/entry.S#L157) and it starts with the following code. ``` el0_sync: kernel_entry 0 mrs x25, esr_el1 // read the syndrome register lsr x24, x25, #ESR_ELx_EC_SHIFT // exception class cmp x24, #ESR_ELx_EC_SVC64 // SVC in 64-bit state b.eq el0_svc handle_invalid_entry 0, SYNC_ERROR ``` First of all, as for all exception handlers, `kernel_entry` macro is called. Then `esr_el1` (Exception Syndrome Register) is checked. This register contains "exception class" field at offset [ESR_ELx_EC_SHIFT](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/include/arm/sysregs.h#L46). If exception class is equal to [ESR_ELx_EC_SVC64](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/include/arm/sysregs.h#L47) this means that the current exception is caused by the `svc` instruction and it is a system call. In this case, we jump to `el0_svc` label and show an error message otherwise. ``` sc_nr .req x25 // number of system calls scno .req x26 // syscall number stbl .req x27 // syscall table pointer el0_svc: adr stbl, sys_call_table // load syscall table pointer uxtw scno, w8 // syscall number in w8 mov sc_nr, #__NR_syscalls bl enable_irq cmp scno, sc_nr // check upper syscall limit b.hs ni_sys ldr x16, [stbl, scno, lsl #3] // address in the syscall table blr x16 // call sys_* routine b ret_from_syscall ni_sys: handle_invalid_entry 0, SYSCALL_ERROR ``` `el0_svc` first loads the address of the syscall table in the `stbl` (it is just an alias to the `x27` register.) and syscall number in the `scno` variable. Then interrupts are enabled and syscall number is compared to the total number of syscalls in the system — if it is greater or equal an error message is shown. If syscall number falls within the required range, it is used as an index in the syscall table array to obtain a pointer to the syscall handler. Next, the handler is executed and after it finishes `ret_from_syscall` is called. Note, that we don't touch here registers `x0` – `x7` — they are transparently passed to the handler. ``` ret_from_syscall: bl disable_irq str x0, [sp, #S_X0] // returned x0 kernel_exit 0 ``` `ret_from_syscall` first disables interrupts. Then it saves the value of `x0` register on the stack. This is required because `kernel_exit` will restore all general purpose registers from their saved values, but `x0` now contains return value of the syscall handler and we want this value to be passed to the user code. Finally `kernel_exit` is called, which returns to the user code. ### Switching between EL0 and EL1 If you read previous lessons carefully you might notice a change in the `kernel_entry` and `kernel_exit` macros: now both of them accepts an additional argument. This argument indicates which exception level an exception is taken from. The information about the originating exception level is required to properly save/restore stack pointer. Here are the two relevant parts from the `kernel_entry` and `kernel_exit` macros. ``` .if \el == 0 mrs x21, sp_el0 .else add x21, sp, #S_FRAME_SIZE .endif /* \el == 0 */ ``` ``` .if \el == 0 msr sp_el0, x21 .endif /* \el == 0 */ ``` We are using 2 distinct stack pointers for EL0 and EL1, that's why right after an exception is taken from EL0 the stack pointer is overwritten. The original stack pointer can be found in the `sp_el0` register. The value of this register must be stored and restored before and after taking an exception, even if we don't touch `sp_el0` in the exception handler. If you don't do this you will end up having wrong value in the `sp` register after a context switch. You may also ask why don't we restore the value of the `sp` register in the case when an exception was taken from EL1? That is because we are reusing the same kernel stack for the exception handler. Even if a context switch happens during an exception processing, by the time of `kernel_exit`, `sp` will be already switched by the `cpu_switch_to` function. (By the way, in Linux the behavior is different because Linux uses a different stack for interrupt handlers.) It is also worth noticing that we don't need to explicitly specify to which exception level we need to return before the `eret` instruction. This is because this information is encoded in the `spsr_el1` register, so we always return to the level from which the exception was taken. ### Moving a task to user mode Before any syscall can take place, we obviously need to have a task running in user mode. There are 2 possibilities how new user tasks can be created: either a kernel thread will be moved to user mode, or a user task can fork itself to create a new user task. In this section, we will explore the first possibility. The function that actually does the job is called [move_to_user_mode](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/fork.c#Li47), but before we will look into it, let's first examine how this function is used. In order to do so, you need to first open [kernel.c](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/kernel.c) file. Let me copy the relevant lines here. ``` int res = copy_process(PF_KTHREAD, (unsigned long)&kernel_process, 0, 0); if (res < 0) { printf("error while starting kernel process"); return; } ``` First, in the `kernel_main` function we create a new kernel thread. We do this in the same way as we did it in the previous lesson. After the scheduler runs the newly created task, `kernel_process` function will be executed in kernel mode. ``` void kernel_process(){ printf("Kernel process started. EL %d\r\n", get_el()); int err = move_to_user_mode((unsigned long)&user_process); if (err < 0){ printf("Error while moving process to user mode\n\r"); } } ``` `kernel_process` then prints status message and calls `move_to_user_mode`, passing a pointer to the `user_process` as the first argument. Now let's see what `move_to_user_mode` function is doing. ``` int move_to_user_mode(unsigned long pc) { struct pt_regs *regs = task_pt_regs(current); memzero((unsigned long)regs, sizeof(*regs)); regs->pc = pc; regs->pstate = PSR_MODE_EL0t; unsigned long stack = get_free_page(); //allocate new user stack if (!stack) { return -1; } regs->sp = stack + PAGE_SIZE; current->stack = stack; return 0; } ``` Right now we are in the middle of execution of a kernel thread that was created by forking from the init task. In the previous lesson we've discussed the forking process, and we've seen that a small area (`pt_regs` area) was reserved at the top of the stack of the newly created task. This is the first time we are going to use this area: we will save manually prepared processor state there. This state will have exactly the same format as `kernel_exit` macro expects and its structure is described by the [pt_regs](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/include/fork.h#L21) struct. The following fields of the `pt_regs` struct are initialized in the `move_to_user_mode` function. * `pc` It now points to the function that needs to be executed in the user mode. `kernel_exit` will copy `pc` to the `elr_el1` register, thus making sure that we will return to the `pc` address after performing exception return. * `pstate` This field will be copied to `spsr_el1` by the `kernel_exit` and becomes the processor state after exception return is completed. [PSR_MODE_EL0t](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/include/fork.h#L9) constant, which is copied to the `pstate` field, is prepared in such a way that exception return will be made to EL0 level. We already did the same trick in the lesson 2 when switching from EL3 to EL1. * `stack` `move_to_user_mode` allocates a new page for the user stack and sets `sp` field to point to the top of this page. `task_pt_regs` function is used to calculate the location of the `pt_regs` area. Because of the way we initialized the current kernel thread, we are sure that after it finished `sp` will point right before the `pt_regs` area. This happens in the middle of the [ret_from_fork](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/entry.S#L188) function. ``` .globl ret_from_fork ret_from_fork: bl schedule_tail cbz x19, ret_to_user // not a kernel thread mov x0, x20 blr x19 ret_to_user: bl disable_irq kernel_exit 0 ``` As you might notice `ret_from_fork` has been updated. Now, after a kernel thread finishes, the execution goes to the `ret_to_user` label, here we disable interrupts and perform normal exception return, using previously prepared processor state. ### Forking user processes Now let's go back to the `kernel.c` file. As we've seen in the previous section, after `kernel_process` finishes, [user_process](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/kernel.c#L22) function will be executed in the user mode. This function calls `clone` system call 2 times in order to execute [user_process1](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/kernel.c#L10) function in 2 parallel threads. `clone` system call requires that the location of a new user stack will be passed to it, we also need to call `malloc` syscall in order to allocate 2 new memory pages. Let's now take a look at what the `clone` syscall wrapping function looks like. You can find it [here](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/sys.S#L22). ``` .globl call_sys_clone call_sys_clone: /* Save args for the child. */ mov x10, x0 /*fn*/ mov x11, x1 /*arg*/ mov x12, x2 /*stack*/ /* Do the system call. */ mov x0, x2 /* stack */ mov x8, #SYS_CLONE_NUMBER svc 0x0 cmp x0, #0 beq thread_start ret thread_start: mov x29, 0 /* Pick the function arg and execute. */ mov x0, x11 blr x10 /* We are done, pass the return value through x0. */ mov x8, #SYS_EXIT_NUMBER svc 0x0 ``` In the design of the `clone` syscall wrapping function, I tried to emulate the behavior of the [coresponding function](https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/aarch64/clone.S;h=e0653048259dd9a3d3eb3103ec2ae86acb43ef48;hb=HEAD#l35) from the `glibc` library. This function does the following. 1. Saves registers `x0` – `x3`, those registers contain parameters of the syscall and later will be overwritten by the syscall handler. 1. Calls syscall handler. 1. Checks return value of the syscall handler: if it is `0`, we are executing inside of the newly created thread. In this case, execution goes to `thread_start` label. 1. If the return value is non-zero, then it is the PID of the new task. This means that we return here right after the syscall finishes and we are executing inside the original thread — just return to the caller in this case. 1. The function, originally passed as the first argument, is called in a new thread. 1. After the function finishes, `exit` syscall is performed — it never returns. As you can see, the semantics of the clone wrapper function and clone syscall differ: The former accepts a pointer to the function to be executed as an argument and the later return to the caller twice: first time in the original task and second time in the cloned task. Clone syscall handler can be found [here](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/sys.c#L11). It is very simple and it just calls already familiar `copy_process` function. This function, however, has been modified since the last lesson — now it supports cloning user threads as well as kernel threads. The source of the function is listed below. ``` int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg, unsigned long stack) { preempt_disable(); struct task_struct *p; p = (struct task_struct *) get_free_page(); if (!p) { return -1; } struct pt_regs *childregs = task_pt_regs(p); memzero((unsigned long)childregs, sizeof(struct pt_regs)); memzero((unsigned long)&p->cpu_context, sizeof(struct cpu_context)); if (clone_flags & PF_KTHREAD) { p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; } else { struct pt_regs * cur_regs = task_pt_regs(current); *childregs = *cur_regs; childregs->regs[0] = 0; childregs->sp = stack + PAGE_SIZE; p->stack = stack; } p->flags = clone_flags; p->priority = current->priority; p->state = TASK_RUNNING; p->counter = p->priority; p->preempt_count = 1; //disable preemtion until schedule_tail p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)childregs; int pid = nr_tasks++; task[pid] = p; preempt_enable(); return pid; } ``` In case, when we are creating a new kernel thread, the function behaves exactly the same, as was described in the previous lesson. In the other case, when we are cloning a user thread, this part of the code is executed. ``` struct pt_regs * cur_regs = task_pt_regs(current); *childregs = *cur_regs; childregs->regs[0] = 0; childregs->sp = stack + PAGE_SIZE; p->stack = stack; ``` The first thing that we are doing here is getting access to the processor state, saved by the `kernel_entry` macro. It is not obvious, however, why we can use the same `task_pt_regs` function, which just returns `pt_regs` area at the top of the kernel stack. Why isn't it possible that `pt_regs` will be stored somewhere else on the stack? The answer is that this code can be executed only after `clone` syscall was called. At the time when syscall was triggered the current kernel stack was empty (we left it empty after moving to the user mode). That's why `pt_regs` will always be stored at the top of the kernel stack. This rule will be kept for all subsequent syscalls because each of them will leave kernel stack empty before returning to the user mode. In the second line current processor state is copied to the new task's state. `x0` in the new state is set to `0`, because `x0` will be interpreted by the caller as a return value of the syscall. We've just seen how clone wrapper function uses this value to determine whether we are still executing as a part of the original thread or a new one. Next `sp` for the new task is set to point to the top of the new user stack page. We also save the pointer to the stack page in order to do a cleanup after the task finishes. ### Exiting a task After each user tasks finishes it should call `exit` syscall (In the current implementation `exit` is called implicitly by the `clone` wrapper function.). `exit` syscall then calls [exit_process](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/sched.c) function, which is responsible for deactivating a task. The function is listed below. ``` void exit_process(){ preempt_disable(); for (int i = 0; i < NR_TASKS; i++){ if (task[i] == current) { task[i]->state = TASK_ZOMBIE; break; } } if (current->stack) { free_page(current->stack); } preempt_enable(); schedule(); } ``` Following Linux convention, we are not deleting the task at once but set its state to `TASK_ZOMBIE` instead. This prevents the task from being selected and executed by the scheduler. In Linux such approach is used to allow parent process to query information about the child even after it finishes. `exit_process` also deletes now unnecessary user stack and calls `schedule`. After `schedule` is called new task will be selected, that's why this system call never returns. ### Conclusion Now that the RPi OS can manage user tasks, we become much closer to the full process isolation. But one important step is still missing: all user tasks share the same physical memory and can easily read one another's data. In the next lesson, we are going to introduce virtual memory and fix this issue. ##### Previous Page 4.5 [Process scheduler: Exercises](../../docs/lesson04/exercises.md) ##### Next Page 5.2 [User processes and system calls: Linux](../../docs/lesson05/linux.md) ================================================ FILE: docs/lesson06/exercises.md ================================================ ## 6.3 Exercises 1. Adapt lesson 06 to run on qemu. You have to implement [identity mapping](https://wiki.osdev.org/Identity_Paging) in order to do this. Check [this](https://github.com/s-matyukevich/raspberry-pi-os/issues/8) issue for reference. ##### Previous Page 6.2 Virtual memory management: Linux (in progress) 6.1 jump backward to [Virtual memory management: RPi OS](../../docs/lesson06/rpi-os.md) ##### Next Page 7.1 Signals and interrupt waiting: RPi OS (to be done) ================================================ FILE: docs/lesson06/rpi-os.md ================================================ ## 6.1: Virtual memory management The RPi OS now can run and schedule user processes, but the isolation between them is not complete - all processes and the kernel itself share the same memory. This allows any process to easily access somebody else's data and even kernel data. And even if we assume that all our processes are not malicious, there is another drawback: before allocating memory each process need to know which memory regions are already occupied - this makes memory allocation for a process more complicated. ### Translation process In this lesson, we are going to fix all issues mentioned above by introducing virtual memory. Virtual memory provides each process with an abstraction that makes it think that it occupies all available memory. Each time a process needs to access some memory location it uses virtual address, which is translated into a physical address. The process of translation is done completely transparent for the process and is performed by a special device: MMU (Memory Mapping Unit). The MMU uses translation tables in order to translate a virtual address into a physical address. The process of translation is described in the following diagram. ``` Virtual address Physical Memory +-----------------------------------------------------------------------+ +------------------+ | | PGD Index | PUD Index | PMD Index | PTE Index | Page offset | | | +-----------------------------------------------------------------------+ | | 63 47 | 38 | 29 | 20 | 11 | 0 | Page N | | | | | +--------------------+ +---->+------------------+ | | | +---------------------+ | | | | +------+ | | | | | | | | | +----------+ | | | |------------------| +------+ | PGD | | | +---------------->| Physical address | | ttbr |---->+-------------+ | PUD | | | |------------------| +------+ | | | | +->+-------------+ | PMD | | | | | +-------------+ | | | | | +->+-------------+ | PTE | +------------------+ +->| PUD address |----+ +-------------+ | | | | | +->+--------------+ | | | +-------------+ +--->| PMD address |----+ +-------------+ | | | | | | | | | +-------------+ +--->| PTE address |----+ +-------------_+ | | | +-------------+ | | +-------------+ +--->| Page address |----+ | | +-------------+ | | +--------------+ | | +-------------+ | | | | +--------------+ +------------------+ ``` The following facts are critical to understand this diagram and memory translation process in general. * Memory for a process is always allocated in pages. A page is a contignious memory region 4KB in size (ARM processors support larger pages, but 4KB is the most common case and we are going to limit our discussion only to this page size). * Page tables have a hierarchical structure. An item in any of the tables contains an address of the next table in the hierarchy. * There are 4 levels in the table hierarchy: PGD (Page Global Directory), PUD (Page Upper Directory), PMD (Page Middle Directory), PTE (Page Table Entry). PTE is the last table in the hierarchy and it points to the actual page in the physical memory. * Memory translation process starts by locating the address of PGD (Page Global Directory) table. The address of this table is stored in the `ttbr0_el1` register. Each process has its own copy of all page tables, including PGD, and therefore each process must keep its PGD address. During a context switch, PGD address of the next process is loaded into the `ttbr0_el1` register. * Next, MMU uses PGD pointer and virtual address to calculate the corresponding physical address. All virtual addresses use only 48 out of 64 available bits. When doing a translation, MMU splits an address into 4 parts: * Bits [39 - 47] contain an index in the PGD table. MMU uses this index to find the location of the PUD. * Bits [30 - 38] contain an index in the PUD table. MMU uses this index to find the location of the PMD. * Bits [21 - 29] contain an index in the PMD table. MMU uses this index to find the location of the PTE. * Bits [12 - 20] contain an index in the PTE table. MMU uses this index to find a page in the physical memory. * Bits [0 - 11] contain an offset in the physical page. MMU uses this offset to determine the exact position in the previously found page that corresponds to the original virtual address. Now, let's make a small exercise and calculate the size of a page table. From the diagram above we know that index in a page table occupies 9 bits (this is true for all page table levels). This means that each page table contains `2^9 = 512` items. Each item in a page table is an address of either the next page table in the hierarchy or a physical page in case of PTE. As we are using a 64-bit processor, each address must be 64 bit or 8 bytes in size. Putting all of this together we can calculate that the size of a page table must be `512 * 8 = 4096` bytes or 4 KB. But this is exactly the size of a page! This might give you an intuition why MMU designers chose such numbers. ### Section mapping There is one more thing that I want to discuss before we start looking at the source code: section mapping. Sometimes there is the need to map large parts of continuous physical memory. In this case, instead of 4 KB pages, we can directly map 2 MB blocks that are called sections. This allows to eliminate 1 level of translation. The translation diagram, in this case, looks like the following. ``` Virtual address Physical Memory +-----------------------------------------------------------------------+ +------------------+ | | PGD Index | PUD Index | PMD Index | Section offset | | | +-----------------------------------------------------------------------+ | | 63 47 | 38 | 29 | 20 | 0 | Section N | | | | | +---->+------------------+ | | | | | | | +------+ | | | | | | | | +----------+ | | |------------------| +------+ | PGD | | +------------------------->| Physical address | | ttbr |---->+-------------+ | PUD | | |------------------| +------+ | | | | +->+-------------+ | PMD | | | | +-------------+ | | | | | +->+-----------------+ | +------------------+ +->| PUD address |----+ +-------------+ | | | | | | | +-------------+ +--->| PMD address |----+ +-----------------+ | | | | | +-------------+ +--->| Section address |-----+ | | +-------------+ | | +-----------------+ | | +-------------+ | | | | +-----------------+ | | +------------------+ ``` As you can see the difference here is that now PMD contains a pointer to the physical section. Also, the offset occupies 21 bits instead of 12 bits (this is because we need 21 bits to encode a 2MB range) ### Page descriptor format You may ask how does the MMU know whether PMD item points to a PTE or a physical 2 MB section? In order to answer this question we need to take a closer look at the structure of a page table item. Now I can confess that I wasn't quite accurate when claiming that an item in a page table always contains an address of either next page table or a physical page: each such item includes some other information as well. An item in a page table is called "descriptor". A description has a special format, which is described below. ``` Descriptor format `+------------------------------------------------------------------------------------------+ | Upper attributes | Address (bits 47:12) | Lower attributes | Block/table bit | Valid bit | +------------------------------------------------------------------------------------------+ 63 47 11 2 1 0 ``` The key thing to understand here is that each descriptor always points to something that is page aligned (either a physical page, a section or the next page table in the hierarchy). This means that last 12 bits of the address, stored in a descriptor, will always be 0. This also means that MUU can use those bits to store something more useful - and that is exactly what it does. Now let me explain the meaning of all bits in a descriptor. * **Bit 0** This bit must be set to 1 for all valid descriptors. If MMU encounter non-valid descriptor during translation process a synchronous exception is generated. The kernel then should handle this exception, allocate a new page and prepare a correct descriptor (We will look in details on how this works a little bit later) * **Bit 1** This bit indicates whether the current descriptor points to a next page table in the hierarchy (we call such descriptor a "table descriptor") or it points instead to a physical page or a section (such descriptors are called "block descriptors"). * **Bits [11:2]** Those bits are ignored for table descriptors. For block descriptors they contain some attributes that control, for example, whether the mapped page is cachable, executable, etc. * **Bits [47:12]**. This is the place where the address that a descriptor points to is stored. As I mentioned previously, only bits [47:12] of the address need to be stored, because all other bits are always 0. * **Bits [63:48]** Another set of attributes. ### Configuring page attributes As I mentioned in the previous section, each block descriptor contains a set of attributes that controls various virtual page parameters. However, the attributes that are most important for our discussion are not configured directly in the descriptor. Instead, ARM processors implement a trick, which allows them to save some space in the descriptor attributes section. ARM.v8 architecture introduces `mair_el1` register. This register consists of 8 sections, each being 8 bits long. Each such section configures a common set of attributes. A descriptor then specifies just an index of the `mair` section, instead of specifying all attributes directly. This allows using only 3 bits in the descriptor to reference a `mair` section. The meaning of each bit in the `mair` section is described on the page 2609 of the `AArch64-Reference-Manual`. In the RPi OS we are using only a few of available attribute options. [Here](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/include/arm/mmu.h#L11) is the code that prepares values for the `mair` register. ``` /* * Memory region attributes: * * n = AttrIndx[2:0] * n MAIR * DEVICE_nGnRnE 000 00000000 * NORMAL_NC 001 01000100 */ #define MT_DEVICE_nGnRnE 0x0 #define MT_NORMAL_NC 0x1 #define MT_DEVICE_nGnRnE_FLAGS 0x00 #define MT_NORMAL_NC_FLAGS 0x44 #define MAIR_VALUE (MT_DEVICE_nGnRnE_FLAGS << (8 * MT_DEVICE_nGnRnE)) | (MT_NORMAL_NC_FLAGS << (8 * MT_NORMAL_NC)) ``` Here we are using only 2 out of 8 available slots in the `mair` registers. The first one corresponds to device memory and second to normal non-cachable memory. `MT_DEVICE_nGnRnE` and `MT_NORMAL_NC` are indexes that we are going to use in block descriptors, `MT_DEVICE_nGnRnE_FLAGS` and `MT_NORMAL_NC_FLAGS` are values that we are storing in the first 2 slots of the `mair_el1` register. ### Kernel vs user virtual memory After the MMU is switched on, each memory access must use virtual memory instead of physical memory. One consequence of this fact is that the kernel itself must be prepared to use virtual memory and maintain its own set of page tables. One possible solution could be to reload `pgd` register each time we switch from user to kernel mode. The problem is that switching `pgd` is very expensive operation because it requires the invalidation of all caches. Having in mind how often we need to switch from user mode to kernel mode, this solution would make caching completely useless and therefore this solution is never used in OS development. What operating system are doing instead is splitting address space into 2 parts: user space and kernel space. 32-bit architectures usually allocate first 3 GB of the address space for user programs and reserve last 1 GB for the kernel. 64-bit architectures are much more favorable in this regard because of their huge address space. And even more: ARM.v8 architecture comes with a native feature that can be used to easily implement user/kernel address split. There are 2 registers that can hold the address of the PGD: `ttbr0_el1` and `ttbr1_el1`. As you might remember we are using only 48 bits in the addresses out of 64 available, so the upper 16 bits can be used to distinguish between `ttbr0` and `ttbr1` translation processes. If upper 16 bits are all equal to 0 then PGD address stored in `ttbr0_el1` is used, and if the address starts with `0xffff`(first 16 bit are all equal to 1) then PGD address stored in the `ttbr1_el1` is selected. The architecture also ensures that a process running at EL0 can never access virtual addresses started with `0xffff` without generating a synchronous exception. From this description, you can easily infer that a pointer to the kernel PGD is stored in the `ttbr1_el1` and is kept there throughout the life of the kernel, and `ttbr0_el1` is used to store current user process PGD. One implication of this approach is that all absolute kernel addresses must start with `0xffff`. There are 2 places in the RPi OS source code, were we handle this. In the [linker script](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/linker.ld#L3) we specify base address of the image as `0xffff000000000000`. This will make the compiler think that our image is going to be loaded at `0xffff000000000000` address, and therefore whenever it needs to generate an absolute address it will make it right. (There are a few more changes to the linker script, but we will discuss them later.) There is one more place were we hardcode absolute kernel base addresses: in the [header](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/include/peripherals/base.h#L7) where we define device base address. Now we will access all device memory starting from `0xffff00003F000000` Certainly, in order for this to work, we need first to map all memory, which kernel needs to access. In the next section we will explore in detail the code that creates this mapping. ### Initializing kernel page tables The process of creating kernel page tables is something that we need to handle very early in the boot process. It starts in the [boot.S](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/boot.S#L42) file. Right after we switch to EL1 and clear the BSS [__create_page_tables](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/boot.S#L92) function is called. Let's examine it line by line. ``` __create_page_tables: mov x29, x30 // save return address ``` First, the function saves `x30` (link register). As we are going to call other functions from `__create_page_tables`, `x30` will be overwritten. Usually `x30` is saved on the stack but, as we know that we are not going to use recursion and nobody else will use `x29` during `__create_page_tables` execution, this simple method of preserving link register also works fine. ``` adrp x0, pg_dir mov x1, #PG_DIR_SIZE bl memzero ``` Next, we clear the initial page tables area. An important thing to understand here is where this area is located and how do we know its size? Initial page tables area is defined in the [linker script](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/linker.ld#L20) - this means that we are allocating the spot for this area in the kernel image itself. Calculating the size of this area is a little bit trickier. First, we need to understand the structure of the initial kernel page tables. We know that all our mappings are all inside 1 GB region (this is the size of RPi memory). One PGD descriptor can cover `2^39 = 512 GB` and one PUD descriptor can cover `2^30 = 1 GB` of continuous virtual mapping area. (Those values are calculated based on the PGD and PUD indexes location in the virtual address.) This means that we need just one PGD and one PUD to map the whole RPi memory, and even more - both PGD and PUD will contain a single descriptor. If we have a single PUD entry there also must be a single PMD table, to which this entry will point. (Single PMD entry covers 2 MB, there are 512 items in a PMD, so in total the whole PMD table covers the same 1 GB of memory that is covered by a single PUD descriptor.) Next, we know that we need to map 1 GB region of memory, which is a multiple of 2 MB - so we can use section mapping. This means that we don't need PTE at all. So in total, we need 3 pages: one for PGD, PUD and PMD - this is precisely the size of the initial page table area. Now we are going to step outside `__create_page_tables` function and take a look on 2 essential macros: [create_table_entry](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/boot.S#L68) and [create_block_map](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/boot.S#L77). `create_table_entry` is responsible for allocating a new page table (In our case either PGD or PUD) The source code is listed below. ``` .macro create_table_entry, tbl, virt, shift, tmp1, tmp2 lsr \tmp1, \virt, #\shift and \tmp1, \tmp1, #PTRS_PER_TABLE - 1 // table index add \tmp2, \tbl, #PAGE_SIZE orr \tmp2, \tmp2, #MM_TYPE_PAGE_TABLE str \tmp2, [\tbl, \tmp1, lsl #3] add \tbl, \tbl, #PAGE_SIZE // next level table page .endm ``` This macro accepts the following arguments. * `tbl` - a pointer to a memory region were new table has to be allocated. * `virt` - virtual address that we are currently mapping. * `shift` - shift that we need to apply to the virtual address in order to extract current table index. (39 in case of PGD and 30 in case of PUD) * `tmp1`, `tmp2` - temporary registers. This macro is very important, so we are going to spend some time understanding it. ``` lsr \tmp1, \virt, #\shift and \tmp1, \tmp1, #PTRS_PER_TABLE - 1 // table index ``` The first two lines of the macro are responsible for extracting table index from the virtual address. We are applying right shift first to strip everything to the right of the index and then using `and` operation to strip everything to the left. ``` add \tmp2, \tbl, #PAGE_SIZE ``` Then the address of the next page table is calculated. Here we are using the convention that all our initial page tables are located in one continuous memory region. We simply assume that the next page table in the hierarchy will be adjacent to the current page table. ``` orr \tmp2, \tmp2, #MM_TYPE_PAGE_TABLE ``` Next, a pointer to the next page table in the hierarchy is converted to a table descriptor. (A descriptor must have 2 lower bits set to `1`) ``` str \tmp2, [\tbl, \tmp1, lsl #3] ``` Then the descriptor is stored in the current page table. We use previously calculated index to find the right spot in the table. ``` add \tbl, \tbl, #PAGE_SIZE // next level table page ``` Finally, we change `tbl` parameter to point to the next page table in the hierarchy. This is convenient because now we can call `create_table_entry` one more time for the next table in the hierarchy without making any adjustments to the `tbl` parameter. This is precisely what we are doing in the [create_pgd_entry](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/boot.S#L63) macro, which is just a wrapper that allocates both PGD and PUD. Next important macro is`create_block_map` As you might guess this macro is responsible for populating entries of the PMD table. It looks like the following. ``` .macro create_block_map, tbl, phys, start, end, flags, tmp1 lsr \start, \start, #SECTION_SHIFT and \start, \start, #PTRS_PER_TABLE - 1 // table index lsr \end, \end, #SECTION_SHIFT and \end, \end, #PTRS_PER_TABLE - 1 // table end index lsr \phys, \phys, #SECTION_SHIFT mov \tmp1, #\flags orr \phys, \tmp1, \phys, lsl #SECTION_SHIFT // table entry 9999: str \phys, [\tbl, \start, lsl #3] // store the entry add \start, \start, #1 // next entry add \phys, \phys, #SECTION_SIZE // next block cmp \start, \end b.ls 9999b .endm ``` Parameters here are a little bit different. * `tbl` - a pointer to the PMD table. * `phys` - the start of the physical region to be mapped. * `start` - virtual address of the first section to be mapped. * `end` - virtual address of the last section to be mapped. * `flags` - flags that need to be copied into lower attributes of the block descriptor. * `tmp1` - temporary register. Now, let's examine the source. ``` lsr \start, \start, #SECTION_SHIFT and \start, \start, #PTRS_PER_TABLE - 1 // table index ``` Those 2 lines extract the table index from `start` virtual address. This is done exactly in the same way as we did it before in the `create_table_entry` macro. ``` lsr \end, \end, #SECTION_SHIFT and \end, \end, #PTRS_PER_TABLE - 1 // table end index ``` The same thing is repeated for the `end` address. Now both `start` and `end` contains not virtual addresses, but indexes in the PMD table, corresponding to the original addresses. ``` lsr \phys, \phys, #SECTION_SHIFT mov \tmp1, #\flags orr \phys, \tmp1, \phys, lsl #SECTION_SHIFT // table entry ``` Next, block descriptor is prepared and stored in the `tmp1` variable. In order to prepare the descriptor `phys` parameter is first shifted to right then shifted back and merged with the `flags` parameter using `orr` instruction. If you wonder why do we have to shift the address back and forth - the answer is that this clears first 21 bit in the `phys` address and makes our macro universal, allowing it to be used with any address, not just the first address of the section. ``` 9999: str \phys, [\tbl, \start, lsl #3] // store the entry add \start, \start, #1 // next entry add \phys, \phys, #SECTION_SIZE // next block cmp \start, \end b.ls 9999b ``` The final part of the function is executed inside a loop. Here we first store current descriptor at the right index in the PMD table. Next, we increase current index by 1 and update the descriptor to point to the next section. We repeat the same process until current index becomes equal to the last index. Now, when you understand how `create_table_entry` and `create_block_map` macros work, it will be straightforward to understand the rest of the `__create_page_tables` function. ``` adrp x0, pg_dir mov x1, #VA_START create_pgd_entry x0, x1, x2, x3 ``` Here we create both PGD and PUD. We configure them to start mapping from [VA_START](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/include/mm.h#L6) virtual address. Because of the semantics of the `create_table_entry` macro, after `create_pgd_entry` finishes `x0` will contain the address of the next table in the hierarchy - namely PMD. ``` /* Mapping kernel and init stack*/ mov x1, xzr // start mapping from physical offset 0 mov x2, #VA_START // first virtual address ldr x3, =(VA_START + DEVICE_BASE - SECTION_SIZE) // last virtual address create_block_map x0, x1, x2, x3, MMU_FLAGS, x4 ``` Next, we create virtual mapping of the whole memory, excluding device registers region. We use [MMU_FLAGS](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/include/arm/mmu.h#L24) constant as `flags` parameter - this marks all sections to be mapped as normal noncacheable memory. (Note, that `MM_ACCESS` flag is also specified as part of `MMU_FLAGS` constant. Without this flag each memory access will generate a synchronous exception.) ``` /* Mapping device memory*/ mov x1, #DEVICE_BASE // start mapping from device base address ldr x2, =(VA_START + DEVICE_BASE) // first virtual address ldr x3, =(VA_START + PHYS_MEMORY_SIZE - SECTION_SIZE) // last virtual address create_block_map x0, x1, x2, x3, MMU_DEVICE_FLAGS, x4 ``` Then device registers region is mapped. This is done exactly in the same way as in the previous code sample, with the exception that we are now using different start and end addresses and different flags. ``` mov x30, x29 // restore return address ret ``` Finally, the function restored link register and returns to the caller. ### Configuring page translation Now page tables are created and we are back to the `el1_entry` function. But there is still some work to be done before we can switch on the MMU. Here is what happens. ``` mov x0, #VA_START add sp, x0, #LOW_MEMORY ``` We are updating init task stack pointer. Now it uses a virtual address, instead of a physical one. (Therefore it could be used only after MMU is on.) ``` adrp x0, pg_dir msr ttbr1_el1, x0 ``` `ttbr1_el1` is updated to point to the previously populated PGD table. ``` ldr x0, =(TCR_VALUE) msr tcr_el1, x0 ``` `tcr_el1` of Translation Control Register is responsible for configuring some general parameters of the MMU. (For example, here we configure that both kernel and user page tables should use 4 KB pages.) ``` ldr x0, =(MAIR_VALUE) msr mair_el1, x0 ``` We already discussed `mair` register in the "Configuring page attributes" section. Here we just set its value. ``` ldr x2, =kernel_main mov x0, #SCTLR_MMU_ENABLED msr sctlr_el1, x0 br x2 ``` `msr sctlr_el1, x0` is the line where MMU is actually enabled. Now we can jump to the `kernel_main` function. An interesting question is why can't we just execute `br kernel_main` instruction? Indeed, we can't. Before the MMU was enabled we have been working with physical memory, the kernel is loaded at a physical offset 0 - this means that current program counter is very close to 0. Switching on the MMU doesn't update the program counter. If we now execute `br kernel_main` instruction, this instruction will use offset relative to the current program counter and jumps to the place were `kernel_main` would have been if we don't turn on the MMU. `ldr x2, =kernel_main` on the other hand loads `x2` with the absolute address of the `kernel_main` function. Because of the fact that we set the image base address to `0xffff000000000000` in the linker script, the absolute address of the `kernel_main` function will be calculated as an offset from the beginning of the image plus `0xffff000000000000` - which is exactly what we need. Another important thing that you need to understand is why `ldr x2, =kernel_main` instruction must be executed before we turn on the MMU. The reason is that `ldr` also uses `pc` relative offset, so if we try to execute this instruction after MMU is on but before we jump to the image base address, the instruction will generate a page fault. ### Allocating user processes If you work with a real OS you would probably expect it to be capable of reading your program from the file system and executing it. This is different for the RPi OS - it doesn't have file system support yet. We were not bothered by this fact in the previous lessons, because user processes shared the same address space with the kernel. Now things have changed and each process should have its own address space, so we need to figure out how to store the user program so we can later load it into the newly created process. The trick that I end up implementing is to store the user program in a separate section of the kernel image. Here is the relevant section of the linker script that is responsible for doing this. ``` . = ALIGN(0x00001000); user_begin = .; .text.user : { build/user* (.text) } .rodata.user : { build/user* (.rodata) } .data.user : { build/user* (.data) } .bss.user : { build/user* (.bss) } user_end = .; ``` I made a convention that user level source code should be defined in the files with `user` prefix. The linker script then can isolate all user related code in a continuous region and define `user_begin` and `user_end` variables, which mark the beginning and end of this region. In this way we can simply copy everything between `user_begin` and `user_end` to the newly allocated process address space, thus simulating loading a user program. This is simple enough and works well for our current purpose, but after we implement file system support and will be able to load ELF files we will get rid of this hack. Right now there are 2 files that are compiled in the user region. * [user_sys.S](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/user_sys.S) This file contains definitions of the syscall wrapper functions. The RPi OS still supports the same syscalls as in the previous lesson, with the exception that now instead of `clone` syscall we are going to use `fork` syscall. The difference is that `fork` copies process virtual memory, and that is something we want to try doing. * [user.c](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/user.c) User program source code. Almost the same as we've used in the previous lesson. ### Creating first user process As it was the case in the previous lesson, [move_to_user_mode](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/fork.c#L44) function is responsible for creating the first user process. We call this function from a kernel thread. Here is how we do this. ``` void kernel_process(){ printf("Kernel process started. EL %d\r\n", get_el()); unsigned long begin = (unsigned long)&user_begin; unsigned long end = (unsigned long)&user_end; unsigned long process = (unsigned long)&user_process; int err = move_to_user_mode(begin, end - begin, process - begin); if (err < 0){ printf("Error while moving process to user mode\n\r"); } } ``` Now we need 3 arguments to call `move_to_user_mode`: a pointer to the beginning of the user code area, size of the area and offset of the startup function inside it. This information is calculated based on the previously discussed `user_begin` and `user_end` variables. `move_to_user_mode` function is listed below. ``` int move_to_user_mode(unsigned long start, unsigned long size, unsigned long pc) { struct pt_regs *regs = task_pt_regs(current); regs->pstate = PSR_MODE_EL0t; regs->pc = pc; regs->sp = 2 * PAGE_SIZE; unsigned long code_page = allocate_user_page(current, 0); if (code_page == 0) { return -1; } memcpy(code_page, start, size); set_pgd(current->mm.pgd); return 0; } ``` Now let's try to inspect in details what is going on here. ``` struct pt_regs *regs = task_pt_regs(current); ``` As it was the case in the previous lesson, we obtain a pointer to `pt_regs` area and set `pstate`, so that after `kernel_exit` we will end up in EL0. ``` regs->pc = pc; ``` `pc` now points to the offset of the startup function in the user region. ``` regs->sp = 2 * PAGE_SIZE; ``` We made a simple convention that our user program will not exceed 1 page in size. We allocate the second page to the stack. ``` unsigned long code_page = allocate_user_page(current, 0); if (code_page == 0) { return -1; } ``` `allocate_user_page` reserves 1 memory page and maps it to the virtual address, provided as a second argument. In the process of mapping it populates page tables, associated with the current process. We will investigate in details how this function works later in this chapter. ``` memcpy(code_page, start, size); ``` Next, we are going to copy the whole user region to the new address space (in the page that we have just mapped), starting from offset 0, so the offset in the user region will become an actual virtual address of the starting point. ``` set_pgd(current->mm.pgd); ``` Finally, we call [set_pgd](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/utils.S#L24), which updates `ttbr0_el1` register and thus activate current process translation tables. ### TLB (Translation lookaside buffer) If you take a look at the `set_pgd` function you will see that after it sets `ttbr0_el1` it also clears [TLB](https://en.wikipedia.org/wiki/Translation_lookaside_buffer) (Translation lookaside buffer). TLB is a cache that is designed specifically to store the mapping between physical and virtual pages. The first time some virtual address is mapped into a physical one this mapping is stored in TLB. Next time we need to access the same page we no longer need to perform full page table walk. Therefore it makes perfect sense that we invalidate TLB after updating page tables - otherwise our change will not be applied for the pages already stored in the TLB. Usually, we try to avoid using all caches for simplicity, but without TLB any memory access would become extremely inefficient, and I don't think that it is even possible to completely disable TLB. Besides, TLB doesn't add any other complexity to the OS, in spite of the fact that we must clean it after switching `ttbr0_el1`. ### Mapping a virtual page We have seen previously how [allocate_user_page](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/mm.c#L14) function is used - now it is time to see what is inside it. ``` unsigned long allocate_user_page(struct task_struct *task, unsigned long va) { unsigned long page = get_free_page(); if (page == 0) { return 0; } map_page(task, va, page); return page + VA_START; } ``` This function allocates a new page, maps it to the provided virtual address and returns a pointer to the page. When we say "a pointer" now we need to distinguish between 3 things: a pointer to a physical page, a pointer inside kernel address space and a pointer inside user address space - all these 3 different pointers can lead to the same location in memory. In our case `page` variable is a physical pointer and the return value is a pointer inside kernel address space. This pointer can be easily calculated because we linearly map the whole physical memory starting at `VA_START` virtual address. We also don't need to worry about allocating new kernel page table because all of the memory is already mapped in `boot.S`. User mapping is still required to be created and this happens in the [map_page](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/mm.c#L62) function, which we will explore next. ``` void map_page(struct task_struct *task, unsigned long va, unsigned long page){ unsigned long pgd; if (!task->mm.pgd) { task->mm.pgd = get_free_page(); task->mm.kernel_pages[++task->mm.kernel_pages_count] = task->mm.pgd; } pgd = task->mm.pgd; int new_table; unsigned long pud = map_table((unsigned long *)(pgd + VA_START), PGD_SHIFT, va, &new_table); if (new_table) { task->mm.kernel_pages[++task->mm.kernel_pages_count] = pud; } unsigned long pmd = map_table((unsigned long *)(pud + VA_START) , PUD_SHIFT, va, &new_table); if (new_table) { task->mm.kernel_pages[++task->mm.kernel_pages_count] = pmd; } unsigned long pte = map_table((unsigned long *)(pmd + VA_START), PMD_SHIFT, va, &new_table); if (new_table) { task->mm.kernel_pages[++task->mm.kernel_pages_count] = pte; } map_table_entry((unsigned long *)(pte + VA_START), va, page); struct user_page p = {page, va}; task->mm.user_pages[task->mm.user_pages_count++] = p; } ``` `map_page` in some way duplicates what we've been doing in the `__create_page_tables` function: it allocates and populates a page table hierarchy. There are 3 important difference, however: now we are doing this in C, instead of assembler. `map_page` maps a single page, instead of the whole memory, and use normal page mapping, instead of section mapping. There are 2 important functions involved in the process: [map_table](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/mm.c#L47) and [map_table_entry](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/mm.c#L40). `map_table` is listed below. ``` unsigned long map_table(unsigned long *table, unsigned long shift, unsigned long va, int* new_table) { unsigned long index = va >> shift; index = index & (PTRS_PER_TABLE - 1); if (!table[index]){ *new_table = 1; unsigned long next_level_table = get_free_page(); unsigned long entry = next_level_table | MM_TYPE_PAGE_TABLE; table[index] = entry; return next_level_table; } else { *new_table = 0; } return table[index] & PAGE_MASK; } ``` This function has the following arguments. * `table` This is a pointer to the parent page table. This page table is assumed to be already allocated, but might be empty. * `shift` This argument is used to extract table index from the provided virtual address. * `va` Virtual address itself. * `new_table` This is an output parameter. It is set to 1 if a new child table has been allocated and left 0 otherwise. You can think of this function as an analog of the `create_table_entry` macro. It extracts table index from the virtual address and prepares a descriptor in the parent table that points to the child table. Unlike `create_table_entry` macro we don't assume that the child table should be adjacent into memory with the parent table - instead, we rely on `get_free_table` function to return whatever page is available. It also might be the case that child table was already allocated (This might happen if child page table covers the region where another page has been allocated previously.). In this case we set `new_table` to 0 and read child page table address from the parent table. `map_page` calls `map_table` 3 times: once for PGD, PUD and PMD. The last call allocates PTE and sets a descriptor in the PMD. Next, `map_table_entry` is called. You can see this function below. ``` void map_table_entry(unsigned long *pte, unsigned long va, unsigned long pa) { unsigned long index = va >> PAGE_SHIFT; index = index & (PTRS_PER_TABLE - 1); unsigned long entry = pa | MMU_PTE_FLAGS; pte[index] = entry; } ``` `map_table_entry` extracts PTE index from the virtual address and then prepares and sets PTE descriptor. It is similar to what we've been doing in the `create_block_map` macro. That's it about user page tables allocation, but `map_page` is responsible for one more important role: it keeps track of the pages that have been allocated during the process of virtual address mapping. All such pages are stored in the [kernel_pages](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/include/sched.h#L53) array. We need this array to be able to clean up allocated pages after a task exits. There is also [user_pages](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/include/sched.h#L51) array, which is also populated by the `map_page` function. This array store information about the correspondence between process virtual pages any physical pages. We need this information in order to be able to copy process virtual memory during `fork` (More on this later). ### Forking a process Before we move forward let me summarize where we are so far: we've seen how first user process is created, its page tables populated, source code copied to the proper location and stack initialized. After all of this preparation, the process is ready to run. The code that is executed inside user process is listed below. ``` void loop(char* str) { char buf[2] = {""}; while (1){ for (int i = 0; i < 5; i++){ buf[0] = str[i]; call_sys_write(buf); user_delay(1000000); } } } void user_process() { call_sys_write("User process\n\r"); int pid = call_sys_fork(); if (pid < 0) { call_sys_write("Error during fork\n\r"); call_sys_exit(); return; } if (pid == 0){ loop("abcde"); } else { loop("12345"); } } ``` The code itself is very simple. The only tricky part is the semantics of the `fork` system call. Unlike `clone`, when doing `fork` we don't need to provide the function that needs to be executed in a new process. Also, the [fork wrapper function](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/user_sys.S#L26) is much easier than the `clone` one. All of this is possible because of the fact that `fork` make a full copy of the process virtual address space, so the fork wrapper function return twice: one time in the original process and one time in the new one. At this point, we have two identical processes, with identical stacks and `pc` positions. The only difference is the return value of the `fork` syscall: it returns child PID in the parent process and 0 in the child process. Starting from this point both processes begin completely independent life and can modify their stacks and write different things using same addresses in memory - all of this without affecting one another. Now let's see how `fork` system call is implemented. [copy_process](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/fork.c#L7) function does most of the job. ``` int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg) { preempt_disable(); struct task_struct *p; unsigned long page = allocate_kernel_page(); p = (struct task_struct *) page; struct pt_regs *childregs = task_pt_regs(p); if (!p) return -1; if (clone_flags & PF_KTHREAD) { p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; } else { struct pt_regs * cur_regs = task_pt_regs(current); *childregs = *cur_regs; childregs->regs[0] = 0; copy_virt_memory(p); } p->flags = clone_flags; p->priority = current->priority; p->state = TASK_RUNNING; p->counter = p->priority; p->preempt_count = 1; //disable preemtion until schedule_tail p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)childregs; int pid = nr_tasks++; task[pid] = p; preempt_enable(); return pid; } ``` This function looks almost exactly the same as in the previous lesson with one exception: when copying user processes, now, instead of modifying new process stack pointer and program counter, we instead call [copy_virt_memory](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/mm.c#L87). `copy_virt_memory` looks like this. ``` int copy_virt_memory(struct task_struct *dst) { struct task_struct* src = current; for (int i = 0; i < src->mm.user_pages_count; i++) { unsigned long kernel_va = allocate_user_page(dst, src->mm.user_pages[i].virt_addr); if( kernel_va == 0) { return -1; } memcpy(kernel_va, src->mm.user_pages[i].virt_addr, PAGE_SIZE); } return 0; } ``` It iterates over `user_pages` array, which contains all pages, allocated by the current process. Note, that in `user_pages` array we store only pages that are actually available to the process and contain its source code or data; we don't include here page table pages, which are stored in `kernel_pages` array. Next, for each page, we allocate another empty page and copy the original page content there. We also map the new page using the same virtual address, that is used by the original one. This is how we get the exact copy of the original process address space. All other details of the forking procedure work exactly in the same way, as they have been in the previous lesson. ### Allocating new pages on demand If you go back and take a look at the `move_to_user_mode` function, you may notice that we only map a single page, starting at offset 0. But we also assume that the second page will be used as a stack. Why don't we map the second page as well? If you think it is a bug, it is not - it is a feature! Stack page, as well as any other page that a process needs to access will be mapped as soon as it will be requested for the first time. Now we are going to explore the inner-workings of this mechanism. When a process tries to access some address which belongs to the page that is not yet mapped a synchronous exception is generated. This is the second type of synchronous exception that we are going to support (the first type is an exception generated by the `svc` instruction which is a system call). Synchronous exception handler now looks like the following. ``` el0_sync: kernel_entry 0 mrs x25, esr_el1 // read the syndrome register lsr x24, x25, #ESR_ELx_EC_SHIFT // exception class cmp x24, #ESR_ELx_EC_SVC64 // SVC in 64-bit state b.eq el0_svc cmp x24, #ESR_ELx_EC_DABT_LOW // data abort in EL0 b.eq el0_da handle_invalid_entry 0, SYNC_ERROR ``` Here we use `esr_el1` register to determine exception type. If it is a page fault exception (or, which is the same, data access exception) `el0_da` function is called. ``` el0_da: bl enable_irq mrs x0, far_el1 mrs x1, esr_el1 bl do_mem_abort cmp x0, 0 b.eq 1f handle_invalid_entry 0, DATA_ABORT_ERROR 1: bl disable_irq kernel_exit 0 ``` `el0_da` redirects the main work to the [do_mem_abort](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/mm.c#L101) function. This function takes two arguments 1. The memory address which we tried to access. This address is taken from `far_el1` register (Fault address register) 1. The content of the `esr_el1` (Exception syndrome register) `do_mem_abort` is listed below. ``` int do_mem_abort(unsigned long addr, unsigned long esr) { unsigned long dfs = (esr & 0b111111); if ((dfs & 0b111100) == 0b100) { unsigned long page = get_free_page(); if (page == 0) { return -1; } map_page(current, addr & PAGE_MASK, page); ind++; if (ind > 2){ return -1; } return 0; } return -1; } ``` In order to understand this function, you need to know a little bit about the specifics of that `esr_el1` register. Bits [32:26] of this register are called "Exception Class". We check those bits in the `el0_sync` handler to determine whether it is a syscall, or a data abort exception or potentially something else. Exception class determines the meaning of bits [24:0] - those bits are usually used to provide additional information about the exception. The meaning of [24:0] bits in case of the data abort exception is described on the page 2460 of the `AArch64-Reference-Manual`. In general, data abort exception can happen in many different scenarios (it could be a permission fault, or address size fault or a lot of other things). We are only interested in a translation fault which happens when some of the page tables for the current virtual address are not initialized. So in the first 2 lines of the `do_mem_abort` function, we check whether the current exception is actually a translation fault. If yes we allocate a new page and map it to the requested virtual address. All of this happens completely transparent for the user program - it doesn't notice that some of the memory accesses were interrupted and new page tables were allocated in the meantime. ### Conclusion This was a long and difficult chapter, but I hope it was useful as well. Virtual memory is really one of the most fundamental pieces of any operating system and I am glad we've passed through this chapter and, hopefully, started to understand how it works at the lowest level. With the introduction of virtual memory we now have full process isolation, but the RPi OS is still far from completion. It still doesn't support file systems, drivers, signals and interrupt waitlists, networking and a lot of other useful concepts, and we will continue to uncover them in the upcoming lessons. ##### Previous Page 5.3 [User processes and system calls: Exercises](../../docs/lesson05/exercises.md) ##### Next Page 6.2 Virtual memory management: Linux (in progress) 6.3 jump forward to [Virtual memory management: Exercises](../../docs/lesson06/exercises.md) ================================================ FILE: exercises/lesson01/1/H-4ND-H/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson01/1/H-4ND-H/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson01/1/H-4ND-H/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/1/H-4ND-H/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson01/1/H-4ND-H/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/1/H-4ND-H/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/1/H-4ND-H/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/1/H-4ND-H/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #define BAUD_RATE 9600 #define MHz *1000000 #define SYSTEM_FREQ 250 MHz #define BAUD_REG_VAL (unsigned int)(((SYSTEM_FREQ/BAUD_RATE)/8)-1) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson01/1/H-4ND-H/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/1/H-4ND-H/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson01/1/H-4ND-H/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/1/H-4ND-H/src/kernel.c ================================================ #include "mini_uart.h" void kernel_main(void) { uart_init(); uart_send_string("Hello, world!\r\n"); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson01/1/H-4ND-H/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/1/H-4ND-H/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,BAUD_REG_VAL); //Set baud rate to defined value in peripherals/mini_uart.h put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } ================================================ FILE: exercises/lesson01/1/H-4ND-H/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/1/H-4ND-H/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/1/a-v-v/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson01/1/a-v-v/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson01/1/a-v-v/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/1/a-v-v/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( unsigned int baudrate ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson01/1/a-v-v/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/1/a-v-v/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/1/a-v-v/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/1/a-v-v/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson01/1/a-v-v/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/1/a-v-v/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson01/1/a-v-v/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/1/a-v-v/src/kernel.c ================================================ #include "mini_uart.h" void kernel_main(void) { const unsigned int baud_rate = 460800; uart_init(baud_rate); uart_send_string("Hello, world!\r\n"); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson01/1/a-v-v/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/1/a-v-v/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( unsigned int baudrate ) { unsigned int selector; const unsigned int baudrate_reg = 31250000 / baudrate - 1; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,baudrate_reg); //Set baud rate put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } ================================================ FILE: exercises/lesson01/1/a-v-v/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/1/a-v-v/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/1/adkaster/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson01/1/adkaster/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson01/1/adkaster/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/1/adkaster/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); /* Support 1200, 2400, 4800, 9600, 19200, 38400, 57600, and 115200 Baud */ typedef enum { BAUD_1200 = 1200, BAUD_2400 = 2400, BAUD_4800 = 4800, BAUD_9600 = 9600, BAUD_19200 = 19200, BAUD_38400 = 38400, BAUD_57600 = 57600, BAUD_115200 = 115200 } miniuart_baud_t; void uart_init ( miniuart_baud_t baudrate ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson01/1/adkaster/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/1/adkaster/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/1/adkaster/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/1/adkaster/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson01/1/adkaster/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/1/adkaster/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson01/1/adkaster/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/1/adkaster/src/kernel.c ================================================ #include "mini_uart.h" void kernel_main(void) { uart_init(BAUD_9600); uart_send_string("Hello, world!\r\n"); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson01/1/adkaster/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/1/adkaster/src/mini_uart.c ================================================ #include "utils.h" #include "mini_uart.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" #include void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( miniuart_baud_t baud_rate ) { unsigned int selector; /* Final baudrate = system_clock_freq / (8 * ( baudrate_reg + 1 )) * From docs, system_clock_freq = 250MHz * Solve for baudrate_reg: * baudrate_reg = (system_clock_freq / (8*baudrate)) - 1 * baudrate_reg = 31.25MHz/baudrate - 1 * baudrate_reg = 31250000/baudrate -1 * baudrate_reg is a 32 bit register, but per BCM2835-ARM-Peripherals.pdf, only bottom 16 are read */ uint32_t baudrate_reg = 31250000/baud_rate - 1; baudrate_reg &= 0x0000FFFF; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,baudrate_reg); //Set baud rate based on input put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } ================================================ FILE: exercises/lesson01/1/adkaster/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/1/adkaster/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/1/avenito/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson01/1/avenito/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson01/1/avenito/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/1/avenito/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H // values according to N = (ClockFreq./(8*BAUD RATE))-1 #define BAUDRATE_9600 3254 #define BAUDRATE_19200 1626 #define BAUDRATE_38400 812 #define BAUDRATE_57600 541 #define BAUDRATE_115200 270 void uart_init ( int baudrate ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson01/1/avenito/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/1/avenito/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/1/avenito/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/1/avenito/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson01/1/avenito/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/1/avenito/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson01/1/avenito/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/1/avenito/src/kernel.c ================================================ #include "mini_uart.h" void kernel_main(void) { uart_init(BAUDRATE_19200); // the baud rate constants are definide in 'mini_uart.h' uart_send_string("Hello, world!\r\n"); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson01/1/avenito/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/1/avenito/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( int baudrate ) { unsigned int selector; if(baudrate == 0) baudrate = 270; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,baudrate); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } ================================================ FILE: exercises/lesson01/1/avenito/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/1/avenito/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/1/bl4ckout31/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel7.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard src/*.c) ASM_FILES = $(wildcard src/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel7.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T src/linker.ld -o $(BUILD_DIR)/kernel7.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel7.elf -O binary kernel7.img ================================================ FILE: exercises/lesson01/1/bl4ckout31/build.bat ================================================ docker run -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson01/1/bl4ckout31/build.sh ================================================ #!/bin/bash docker run -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/1/bl4ckout31/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson01/1/bl4ckout31/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/1/bl4ckout31/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/1/bl4ckout31/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #define BAUD_RATE 115200 #define SYSTEM_FREQ_CLOCK 250000000 #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/1/bl4ckout31/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson01/1/bl4ckout31/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/1/bl4ckout31/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson01/1/bl4ckout31/src/config.txt ================================================ arm_control=0x200 kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/1/bl4ckout31/src/kernel.c ================================================ #include "mini_uart.h" void kernel_main(void) { uart_init(); uart_send_string("Hello, world!\r\n"); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson01/1/bl4ckout31/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/1/bl4ckout31/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio 15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high int baudrate_reg = (int) ((SYSTEM_FREQ_CLOCK / (8 * BAUD_RATE)) - 1); put32(AUX_MU_BAUD_REG, baudrate_reg); //Set baund rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } ================================================ FILE: exercises/lesson01/1/bl4ckout31/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/1/bl4ckout31/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/1/evopen/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu C_FLAGS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASM_FLAGS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(C_FLAGS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASM_FLAGS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T src/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson01/1/evopen/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/1/evopen/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/1/evopen/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init(void); char uart_recv(void); void uart_send(char c); void uart_send_string(char* str); unsigned int get_reg_baud(unsigned int core_freq, unsigned int baud); #endif ================================================ FILE: exercises/lesson01/1/evopen/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/1/evopen/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/1/evopen/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE + 0x00200004) #define GPSET0 (PBASE + 0x0020001C) #define GPCLR0 (PBASE + 0x00200028) #define GPPUD (PBASE + 0x00200094) #define GPPUDCLK0 (PBASE + 0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/1/evopen/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE + 0x00215004) #define AUX_MU_IO_REG (PBASE + 0x00215040) #define AUX_MU_IER_REG (PBASE + 0x00215044) #define AUX_MU_IIR_REG (PBASE + 0x00215048) #define AUX_MU_LCR_REG (PBASE + 0x0021504C) #define AUX_MU_MCR_REG (PBASE + 0x00215050) #define AUX_MU_LSR_REG (PBASE + 0x00215054) #define AUX_MU_MSR_REG (PBASE + 0x00215058) #define AUX_MU_SCRATCH (PBASE + 0x0021505C) #define AUX_MU_CNTL_REG (PBASE + 0x00215060) #define AUX_MU_STAT_REG (PBASE + 0x00215064) #define AUX_MU_BAUD_REG (PBASE + 0x00215068) #define CORE_FREQ 250 #define BAUD_FREQ 1800 #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson01/1/evopen/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay(unsigned long); extern void put32(unsigned long, unsigned int); extern unsigned int get32(unsigned long); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/1/evopen/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF cbz x0, master b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main ================================================ FILE: exercises/lesson01/1/evopen/src/kernel_main.c ================================================ #include "mini_uart.h" int a; void kernel_main(void) { uart_init(); uart_send_string("Hello, world!\r\n"); while(1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson01/1/evopen/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/1/evopen/src/mini_uart.c ================================================ #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" #include "utils.h" unsigned int get_reg_baud(unsigned int core_freq, unsigned int baud) { return core_freq * 1000000 / (8 * baud) - 1; } void uart_init(void) { unsigned int selector; // 32 bits selector = get32(GPFSEL1); selector &= ~(7 << 12); selector |= 2 << 12; selector &= ~(7 << 15); selector |= 2 << 15; put32(GPFSEL1, selector); put32(GPPUD, 0); delay(150); put32(GPPUDCLK0, (1 << 14) | (1 << 15)); delay(150); put32(GPPUDCLK0, 0); put32(AUX_ENABLES, 1); // Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG, 0); // Disable auto flow control put32(AUX_MU_IER_REG, 0); // Disable receive and transmit interrupts put32(AUX_MU_LCR_REG, 3); // Enable 8 bit mode put32(AUX_MU_MCR_REG, 0); // Set RTS line to be always high put32(AUX_MU_BAUD_REG, get_reg_baud(CORE_FREQ, BAUD_FREQ)); // Set baud rate to 115200 put32(AUX_MU_IIR_REG, 6); // Clear FIFO put32(AUX_MU_CNTL_REG, 3); // Finally, enable transmitter and receiver } void uart_send(char c) { while (1) { if (get32(AUX_MU_LSR_REG) & 32) break; } put32(AUX_MU_IO_REG, c); } char uart_recv(void) { while (1) { if (get32(AUX_MU_LSR_REG) & 1) break; } return (get32(AUX_MU_IO_REG) & 0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i++) { uart_send((char)str[i]); } } ================================================ FILE: exercises/lesson01/1/evopen/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/1/evopen/src/utils.S ================================================ .globl put32 put32: str w1, [x0] ret .globl get32 get32: ldr w0, [x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/1/gcrisis/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean: rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson01/1/gcrisis/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init (int baudrate ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson01/1/gcrisis/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/1/gcrisis/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/1/gcrisis/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/1/gcrisis/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson01/1/gcrisis/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/1/gcrisis/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson01/1/gcrisis/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/1/gcrisis/src/kernel.c ================================================ #include "mini_uart.h" void kernel_main(void) { uart_init(9600); uart_send_string("Hello, world!\r\n"); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson01/1/gcrisis/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot)} .text :{ *(.text)} .rodata : {*(.rodata) } .data : {*(.data) } . = ALIGN(0x8); bss_begin =.; .bss : {*(.bss*) } bss_end =.; } ================================================ FILE: exercises/lesson01/1/gcrisis/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" #define BAUD_REG_VAL(baud_rate) ((250000000/(baud_rate)/8-1)&0x0000ffff) void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init (int baudrate ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,(int)BAUD_REG_VAL(baudrate)); //Set baud rate to baudrate put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } ================================================ FILE: exercises/lesson01/1/gcrisis/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/1/gcrisis/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/1/rs/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel7.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel7.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel7.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel7.elf -O binary kernel7.img ================================================ FILE: exercises/lesson01/1/rs/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson01/1/rs/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/1/rs/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init(void); char uart_recv(void); void uart_send(char c); void uart_send_string(char *str); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson01/1/rs/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/1/rs/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/1/rs/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE + 0x00200004) #define GPSET0 (PBASE + 0x0020001C) #define GPCLR0 (PBASE + 0x00200028) #define GPPUD (PBASE + 0x00200094) #define GPPUDCLK0 (PBASE + 0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/1/rs/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE + 0x00215004) #define AUX_MU_IO_REG (PBASE + 0x00215040) #define AUX_MU_IER_REG (PBASE + 0x00215044) #define AUX_MU_IIR_REG (PBASE + 0x00215048) #define AUX_MU_LCR_REG (PBASE + 0x0021504C) #define AUX_MU_MCR_REG (PBASE + 0x00215050) #define AUX_MU_LSR_REG (PBASE + 0x00215054) #define AUX_MU_MSR_REG (PBASE + 0x00215058) #define AUX_MU_SCRATCH (PBASE + 0x0021505C) #define AUX_MU_CNTL_REG (PBASE + 0x00215060) #define AUX_MU_STAT_REG (PBASE + 0x00215064) #define AUX_MU_BAUD_REG (PBASE + 0x00215068) #define SYS_CLOCK_FREQ 250000000 #define AUX_BAUD_RATE 115200 #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson01/1/rs/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay(unsigned long); extern void put32(unsigned long, unsigned int); extern unsigned int get32(unsigned long); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/1/rs/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0, #0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson01/1/rs/src/config.txt ================================================ arm_control=0x200 kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/1/rs/src/kernel.c ================================================ #include "mini_uart.h" void kernel_main(void) { uart_init(); uart_send_string("Hello, world!\r\n"); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson01/1/rs/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/1/rs/src/mini_uart.c ================================================ #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" #include "utils.h" void uart_send(char c) { while (1) { if (get32(AUX_MU_LSR_REG) & 0x20) break; } put32(AUX_MU_IO_REG, c); } char uart_recv(void) { while (1) { if (get32(AUX_MU_LSR_REG) & 0x01) break; } return (get32(AUX_MU_IO_REG) & 0xFF); } void uart_send_string(char *str) { for (int i = 0; str[i] != '\0'; i++) { uart_send((char)str[i]); } } void uart_init(void) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7 << 12); // clean gpio14 selector |= 2 << 12; // set alt5 for gpio14 selector &= ~(7 << 15); // clean gpio15 selector |= 2 << 15; // set alt5 for gpio15 put32(GPFSEL1, selector); put32(GPPUD, 0); delay(150); put32(GPPUDCLK0, (1 << 14) | (1 << 15)); delay(150); put32(GPPUDCLK0, 0); put32(AUX_ENABLES, 1); // Enable mini uart // (this also enables access to its registers) put32(AUX_MU_CNTL_REG, 0); // Disable auto flow control and disable receiver // and transmitter (for now) put32(AUX_MU_IER_REG, 0); // Disable receive and transmit interrupts put32(AUX_MU_LCR_REG, 3); // Enable 8 bit mode put32(AUX_MU_MCR_REG, 0); // Set RTS line to be always high put32(AUX_MU_BAUD_REG, SYS_CLOCK_FREQ / 8 / AUX_BAUD_RATE - 1); // Set baud rate put32(AUX_MU_CNTL_REG, 3); // Finally, enable transmitter and receiver } ================================================ FILE: exercises/lesson01/1/rs/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/1/rs/src/utils.S ================================================ .globl put32 put32: str w1, [x0] ret .globl get32 get32: ldr w0, [x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/1/stefanji/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson01/1/stefanji/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson01/1/stefanji/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/1/stefanji/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init (unsigned int); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson01/1/stefanji/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/1/stefanji/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/1/stefanji/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/1/stefanji/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #define SYSTEM_CLOCK_FREQ 250000000 #define BAUD_RATE_REG(target_baudrate) ((SYSTEM_CLOCK_FREQ/target_baudrate/8)-1) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson01/1/stefanji/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/1/stefanji/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson01/1/stefanji/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/1/stefanji/src/kernel.c ================================================ #include "mini_uart.h" void kernel_main(void) { uart_init(9600); uart_send_string("Hello, world!\r\n"); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson01/1/stefanji/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/1/stefanji/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init (unsigned int rate) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high int target = BAUD_RATE_REG(rate); put32(AUX_MU_BAUD_REG, target); //Set baud rate to target put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } ================================================ FILE: exercises/lesson01/1/stefanji/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/1/stefanji/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/1/xdfm1/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only -std=gnu99 ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel7.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard src/*.c) ASM_FILES = $(wildcard src/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel7.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T src/linker.ld -o $(BUILD_DIR)/kernel7.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel7.elf -O binary kernel7.img ================================================ FILE: exercises/lesson01/1/xdfm1/build.bat ================================================ docker run -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson01/1/xdfm1/build.sh ================================================ #!/bin/bash docker run -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/1/xdfm1/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); unsigned int uart_get_baudrate( void ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson01/1/xdfm1/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/1/xdfm1/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/1/xdfm1/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/1/xdfm1/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #define CLK_FREQ_MHZ 250 #define BAUD_RATE 9600 #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson01/1/xdfm1/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/1/xdfm1/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson01/1/xdfm1/src/config.txt ================================================ arm_control=0x200 kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/1/xdfm1/src/kernel.c ================================================ #include "mini_uart.h" void kernel_main(void) { uart_init(); uart_send_string("Hello, world!\r\n"); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson01/1/xdfm1/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/1/xdfm1/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } unsigned int uart_get_baudrate( void ) { return (CLK_FREQ_MHZ * 125000) / BAUD_RATE - 1; } void uart_init ( void ) { unsigned int selector; unsigned int baudrate; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio 15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high baudrate = uart_get_baudrate(); put32(AUX_MU_BAUD_REG,baudrate); //Set baund rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } ================================================ FILE: exercises/lesson01/1/xdfm1/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/1/xdfm1/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/1/zjd0112/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson01/1/zjd0112/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson01/1/zjd0112/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/1/zjd0112/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( unsigned int baud_rate ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson01/1/zjd0112/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/1/zjd0112/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/1/zjd0112/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/1/zjd0112/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #define SYSTEM_CLOCK_FREQ (250000000) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson01/1/zjd0112/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/1/zjd0112/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson01/1/zjd0112/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/1/zjd0112/src/kernel.c ================================================ #include "mini_uart.h" void kernel_main(void) { uart_init(9600); uart_send_string("Hello, world!\r\n"); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson01/1/zjd0112/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/1/zjd0112/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( unsigned int baud_rate ) { unsigned int selector; unsigned int baudReg_val = 0; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high // put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 baudReg_val = ((SYSTEM_CLOCK_FREQ / (baud_rate*8)) - 1) & 0xFFFF; put32(AUX_MU_BAUD_REG, baudReg_val); // Set baud rate to baud_rate put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } ================================================ FILE: exercises/lesson01/1/zjd0112/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/1/zjd0112/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/2/H-4ND-H/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson01/2/H-4ND-H/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson01/2/H-4ND-H/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/2/H-4ND-H/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/2/H-4ND-H/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/2/H-4ND-H/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/2/H-4ND-H/include/peripherals/uart_pl011.h ================================================ #ifndef _P_UART_PL011_H #define _P_UART_PL011_H #include "peripherals/base.h" #define UART_REG_BASE_ADDR (PBASE+0x00201000) //only used registers are defined #define UART_DATA_REG (UART_REG_BASE_ADDR+0x00) #define UART_FLAG_REG (UART_REG_BASE_ADDR+0x18) #define UART_IBRD_REG (UART_REG_BASE_ADDR+0x24) #define UART_FBRD_REG (UART_REG_BASE_ADDR+0x28) #define UART_LCRH_REG (UART_REG_BASE_ADDR+0x2C) #define UART_CTRL_REG (UART_REG_BASE_ADDR+0x30) //baudrate //for calculation hint see PrimeCell ® UART (PL011) Technical Reference Manual #define MHz *1000000 #define UART_CLK 48 MHz #define UART_BAUD_RATE 115200 #define UART_BAUD_DIV ((double)UART_CLK)/(16*UART_BAUD_RATE) #define UART_IBRD (unsigned int)(UART_BAUD_DIV) #define UART_FBRD (unsigned int)((UART_BAUD_DIV - UART_IBRD)*64 + .5) #endif /*_P_UART_PL011_H */ ================================================ FILE: exercises/lesson01/2/H-4ND-H/include/uart_pl011.h ================================================ #ifndef _UART_PL011_H #define _UART_PL011_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); #endif /*_UART_PL011_H */ ================================================ FILE: exercises/lesson01/2/H-4ND-H/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/2/H-4ND-H/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson01/2/H-4ND-H/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/2/H-4ND-H/src/kernel.c ================================================ #include "uart_pl011.h" #include "peripherals/uart_pl011.h" void kernel_main(void) { uart_init(); uart_send_string("Hello, world!\r\n"); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson01/2/H-4ND-H/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/2/H-4ND-H/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/2/H-4ND-H/src/uart_pl011.c ================================================ #include "utils.h" #include "peripherals/uart_pl011.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(!(get32(UART_FLAG_REG)&0x20)) break; } put32(UART_DATA_REG,c); } char uart_recv ( void ) { while(1) { if(!(get32(UART_FLAG_REG)&0x10)) break; } return(get32(UART_DATA_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 4<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 4<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); //At startup uart is disabled by default put32(UART_IBRD_REG, UART_IBRD); //Change baudrate in peripherals/uart_pl011.h put32(UART_FBRD_REG, UART_FBRD); put32(UART_LCRH_REG, 0x70); //Uart hardcoded for for 8 bit mode - no parity - fifo enable put32(UART_CTRL_REG, 0x301); //Enable uart, RX and TX } ================================================ FILE: exercises/lesson01/2/H-4ND-H/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/2/a-v-v/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson01/2/a-v-v/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson01/2/a-v-v/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/2/a-v-v/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/2/a-v-v/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/2/a-v-v/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/2/a-v-v/include/peripherals/uart.h ================================================ #ifndef _P_UART_H #define _P_UART_H #include "peripherals/base.h" #define UART_BASE (PBASE + 0x201000) #define UART_DR (UART_BASE + 0x0) #define UART_FR (UART_BASE + 0x18) #define UART_IBRD (UART_BASE + 0x24) #define UART_FBRD (UART_BASE + 0x28) #define UART_LCRH (UART_BASE + 0x2c) #define UART_CR (UART_BASE + 0x30) #define UART_CLK_HZ 48000000 #endif /*_P_UART_H */ ================================================ FILE: exercises/lesson01/2/a-v-v/include/uart.h ================================================ #ifndef _UART_H #define _UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); #endif /*_UART_H */ ================================================ FILE: exercises/lesson01/2/a-v-v/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/2/a-v-v/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson01/2/a-v-v/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/2/a-v-v/src/kernel.c ================================================ #include "uart.h" void kernel_main(void) { uart_init(); uart_send_string("Hello, world!\r\n"); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson01/2/a-v-v/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/2/a-v-v/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/2/a-v-v/src/uart.c ================================================ #include "utils.h" #include "peripherals/uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(get32(UART_FR) & (1u<<5)) // wait while TXFF is set ; put32(UART_DR,c); } char uart_recv ( void ) { while(get32(UART_FR) & (1u<<4)) // wait while RXFE is set ; return (get32(UART_DR)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 4<<12; // set alt0 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 4<<15; // set alt0 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); const unsigned int BAUDS = 115200; const unsigned int IBRD = 0xFFFF & (UART_CLK_HZ / (16 * BAUDS)); const unsigned int FBRD = 0x3f & (UART_CLK_HZ % (16 * BAUDS) * 64 / UART_CLK_HZ); put32(UART_IBRD, IBRD); put32(UART_FBRD, FBRD); put32(UART_LCRH, (3u << 5)); // set WLEN to 0b11 put32(UART_CR, (1u << 9) | (1u << 8) | 1u); // set RXE, TXE, UARTEN bits } ================================================ FILE: exercises/lesson01/2/a-v-v/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/2/adkaster/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson01/2/adkaster/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson01/2/adkaster/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/2/adkaster/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/2/adkaster/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/2/adkaster/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/2/adkaster/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson01/2/adkaster/include/peripherals/uart.h ================================================ #ifndef P_UART_H #define P_UART_H #include "peripherals/base.h" #define UART_OFFS 0x00201000 #define UART_DR (PBASE + UART_OFFS + 0x00) #define UART_RSRECR (PBASE + UART_OFFS + 0x04) #define UART_FR (PBASE + UART_OFFS + 0x18) #define UART_ILPR (PBASE + UART_OFFS + 0x20) #define UART_IBRD (PBASE + UART_OFFS + 0x24) #define UART_FBRD (PBASE + UART_OFFS + 0x28) #define UART_LCRH (PBASE + UART_OFFS + 0x2C) #define UART_CR (PBASE + UART_OFFS + 0x30) #define UART_IFLS (PBASE + UART_OFFS + 0x34) #define UART_IMSC (PBASE + UART_OFFS + 0x38) #define UART_RIS (PBASE + UART_OFFS + 0x3C) #define UART_MIS (PBASE + UART_OFFS + 0x40) #define UART_ICR (PBASE + UART_OFFS + 0x44) #define UART_DMACR (PBASE + UART_OFFS + 0x48) #define UART_ITCR (PBASE + UART_OFFS + 0x80) #define UART_ITIP (PBASE + UART_OFFS + 0x84) #define UART_ITOP (PBASE + UART_OFFS + 0x88) #define UART_TDR (PBASE + UART_OFFS + 0x8C) #endif /* P_UART_H */ ================================================ FILE: exercises/lesson01/2/adkaster/include/uart.h ================================================ #ifndef _UART_H #define _UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); #endif /*_UART_H */ ================================================ FILE: exercises/lesson01/2/adkaster/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/2/adkaster/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson01/2/adkaster/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/2/adkaster/src/kernel.c ================================================ #include "uart.h" void kernel_main(void) { uart_init(); uart_send_string("Hello, world!\r\n"); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson01/2/adkaster/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/2/adkaster/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void mini_uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char mini_uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void mini_uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { mini_uart_send((char)str[i]); } } void mini_uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } ================================================ FILE: exercises/lesson01/2/adkaster/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/2/adkaster/src/uart.c ================================================ #include "utils.h" #include "peripherals/uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { /* Wait for TX FIFO to not be Full. * This would work way better if we used the fifo level interrupts :) */ while(get32(UART_FR) & (1 << 5)) {} put32(UART_DR,c); } char uart_recv ( void ) { /* Block while RX FIFO empty */ while(get32(UART_FR) & (1 << 4)) { } return(get32(UART_DR)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 4<<12; // set alt0 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 4<<15; // set alt0 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(UART_CR, 0); // Disable UART while we mess around put32(UART_IMSC, 0); // Disable all interrupts // Assume 48MHz UART Reference Clock (Standard) // Calculate UART clock divider per datasheet // BAUDDIV = (FUARTCLK/(16 Baud rate)) // Note: We get 6 bits of fraction for the baud div // 48000000/(16 * 115200) = 3000000/115200 = 26.0416666... // Integer part = 26 :) // From http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0183g/I54603.html // we want floor(0.04166666.. * 64 + 0.5) = 3 put32(UART_IBRD, 26); put32(UART_FBRD, 3); // set to 8 bits, enable fifo put32(UART_LCRH, (3 << 5) | (1 << 4)); // Enable RX, TX, UART put32(UART_CR, (1 << 9) | (1 << 8) | (1 << 0)); } ================================================ FILE: exercises/lesson01/2/adkaster/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/2/avenito/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson01/2/avenito/README.md ================================================ # How to install qemu to emulate ARM v.8 I followed this site: https://raspberrypi.stackexchange.com/questions/34733/how-to-do-qemu-emulation-for-bare-metal-raspberry-pi-images/85135#85135 # How to run qemu-system-aarch64 -M raspi3 -kernel kernel8.img -serial stdio ================================================ FILE: exercises/lesson01/2/avenito/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson01/2/avenito/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/2/avenito/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/2/avenito/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 //215004 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/2/avenito/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/2/avenito/include/peripherals/uart.h ================================================ #ifndef _P_UART_H #define _P_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00201000) #define UART_DR (PBASE+0x00201000) #define UART_FR (PBASE+0x00201018) #define UART_CR (PBASE+0x00201030) #define UART_IBRD (PBASE+0x00201024) #define UART_FBRD (PBASE+0x00201028) #define UARTLCR_LCRH (PBASE+0x0020102C) #endif /*_P_UART_H */ ================================================ FILE: exercises/lesson01/2/avenito/include/uart.h ================================================ #ifndef _UART_H #define _UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); #endif /*_UART_H */ ================================================ FILE: exercises/lesson01/2/avenito/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/2/avenito/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson01/2/avenito/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/2/avenito/src/kernel.c ================================================ #include "uart.h" void kernel_main(void) { uart_init(); uart_send_string("Hello, world!\r\n"); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson01/2/avenito/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/2/avenito/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/2/avenito/src/uart.c ================================================ #include "utils.h" #include "peripherals/uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(get32(UART_FR)&0x20) {} // wait if TX is full put32(UART_DR,c); // when TX is empty, send next char } char uart_recv ( void ) { while(get32(UART_FR)&0x10) {} // wait if RX is empty return(get32(UART_DR)&0xFF); // get recived char } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 4<<12; // set alt0 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 4<<15; // set alt0 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(UART_CR,0); // disable RX and TX to configure put32(UART_IBRD,26); //PrimeCell UART (PL011) rev.r1p5 pag.3-9 BAUDDIV = (FUARTCLK/(16 Baud rate)) = 48MHz/(16*115200) = 26.041666 put32(UART_FBRD,3); put32(UARTLCR_LCRH,0x60); //Enable 8 bit mode put32(UART_CR,0x301); // enable UART, RX and TX } ================================================ FILE: exercises/lesson01/2/avenito/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/2/bl4ckout31/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel7.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard src/*.c) ASM_FILES = $(wildcard src/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel7.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T src/linker.ld -o $(BUILD_DIR)/kernel7.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel7.elf -O binary kernel7.img ================================================ FILE: exercises/lesson01/2/bl4ckout31/build.bat ================================================ docker run -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson01/2/bl4ckout31/build.sh ================================================ #!/bin/bash docker run -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/2/bl4ckout31/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/2/bl4ckout31/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/2/bl4ckout31/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/2/bl4ckout31/include/peripherals/uart.h ================================================ #ifndef _P_UART_H #define _P_UART_H #include "peripherals/base.h" #define U_BASE (PBASE+0x00201000) #define U_DATA_REG (U_BASE) #define U_FR_REG (U_BASE+0x18) #define U_IBRD_REG (U_BASE+0x24) #define U_FBRD_REG (U_BASE+0x28) #define U_LCRH_REG (U_BASE+0x2C) #define U_CR_REG (U_BASE+0x30) #define U_IMSC_REG (U_BASE+0x38) #endif /*_P_UART_H */ ================================================ FILE: exercises/lesson01/2/bl4ckout31/include/uart.h ================================================ #ifndef _UART_H #define _UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); #endif /*_UART_H */ ================================================ FILE: exercises/lesson01/2/bl4ckout31/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/2/bl4ckout31/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson01/2/bl4ckout31/src/config.txt ================================================ arm_control=0x200 kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/2/bl4ckout31/src/kernel.c ================================================ #include "uart.h" void kernel_main(void) { uart_init(); uart_send_string("Hello, world!\r\n"); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson01/2/bl4ckout31/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/2/bl4ckout31/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/2/bl4ckout31/src/uart.c ================================================ #include "utils.h" #include "peripherals/uart.h" #include "peripherals/gpio.h" void uart_send (char c) { // wait for transmit FIFO to have an available slot while(get32(U_FR_REG) & (1<<5)) { } put32(U_DATA_REG, c); } char uart_recv () { // wait for receive FIFO to have data to read while(get32(U_FR_REG) & (1<<4)) { } return(get32(U_DATA_REG) & 0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char) str[i]); } } void uart_init (void) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 4<<12; // set alt0 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 4<<15; // set alt0 for gpio 15 put32(GPFSEL1, selector); put32(GPPUD, 0); delay(150); put32(GPPUDCLK0, (1<<14) | (1<<15)); delay(150); put32(GPPUDCLK0, 0); put32(U_CR_REG, 0); // disable UART until configuration is done /* baud divisor = UARTCLK / (16 * baud_rate) = 48 * 10^6 / (16 * 115200) = 26.0416666667 integer part = 26 fractional part = (int) ((0.0416666667 * 64) + 0.5) = 3 generated baud rate divisor = 26 + (3 / 64) = 26.046875 generated baud rate = (48 * 10^6) / (16 * 26.046875) = 115177 error = |(115177 - 115200) / 115200 * 100| = 0.02% */ put32(U_IBRD_REG, 26); // baud rate divisor, integer part put32(U_FBRD_REG, 3); // baud rate divisor, fractional part put32(U_LCRH_REG, (1<<4) | (3<<5)); // enable FIFOs and 8 bits frames put32(U_IMSC_REG, 0); // mask interupts put32(U_CR_REG, 1 | (1<<8) | (1<<9)); // enable UART, receive and transmit } ================================================ FILE: exercises/lesson01/2/bl4ckout31/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/2/evopen/.gitignore ================================================ workspace/ ================================================ FILE: exercises/lesson01/2/evopen/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu C_FLAGS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASM_FLAGS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img kernel-qemu.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(C_FLAGS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASM_FLAGS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T src/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img kernel-qemu.img: $(SRC_DIR)/linker-qemu.ld $(OBJ_FILES) $(ARMGNU)-ld -T src/linker-qemu.ld -o $(BUILD_DIR)/kernel-qemu.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel-qemu.elf -O binary kernel-qemu.img ================================================ FILE: exercises/lesson01/2/evopen/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/2/evopen/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/2/evopen/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/2/evopen/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/2/evopen/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE + 0x00200004) #define GPSET0 (PBASE + 0x0020001C) #define GPCLR0 (PBASE + 0x00200028) #define GPPUD (PBASE + 0x00200094) #define GPPUDCLK0 (PBASE + 0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/2/evopen/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE + 0x00215004) #define AUX_MU_IO_REG (PBASE + 0x00215040) #define AUX_MU_IER_REG (PBASE + 0x00215044) #define AUX_MU_IIR_REG (PBASE + 0x00215048) #define AUX_MU_LCR_REG (PBASE + 0x0021504C) #define AUX_MU_MCR_REG (PBASE + 0x00215050) #define AUX_MU_LSR_REG (PBASE + 0x00215054) #define AUX_MU_MSR_REG (PBASE + 0x00215058) #define AUX_MU_SCRATCH (PBASE + 0x0021505C) #define AUX_MU_CNTL_REG (PBASE + 0x00215060) #define AUX_MU_STAT_REG (PBASE + 0x00215064) #define AUX_MU_BAUD_REG (PBASE + 0x00215068) #define CORE_FREQ 250 #define BAUD_FREQ 115200 #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson01/2/evopen/include/peripherals/uart.h ================================================ #ifndef _P_UART_H #define _P_UART_H #include "peripherals/base.h" #define UART_BASE (PBASE + 0x201000) #define UART_DR (UART_BASE) #define UART_FR (UART_BASE + 0x18) #define UART_IBRD (UART_BASE + 0x24) #define UART_FBRD (UART_BASE + 0x28) #define UART_CR (UART_BASE + 0x30) #define UART_LCRH (UART_BASE + 0x2c) #define UART_IMSC (UART_BASE + 0x18) #define CORE_FREQ 250 #define BAUD_FREQ 115200 #endif /*_P_UART_H */ ================================================ FILE: exercises/lesson01/2/evopen/include/uart.h ================================================ #ifndef _UART_H #define _UART_H void uart_init(void); char uart_recv(void); void uart_send(char c); void uart_send_string(char* str); unsigned int get_reg_baud(unsigned int core_freq, unsigned int baud); #endif ================================================ FILE: exercises/lesson01/2/evopen/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay(unsigned long); extern void put32(unsigned long, unsigned int); extern unsigned int get32(unsigned long); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/2/evopen/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF cbz x0, master b slave master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY mrs x0, mpidr_el1 and x0, x0,#0xFF bl kernel_main slave: mov sp, #LOW_MEMORY mrs x0, mpidr_el1 and x0, x0,#0xFF lsl x0, x0, #15 bl delay mrs x0, mpidr_el1 and x0, x0,#0xFF bl kernel_main ================================================ FILE: exercises/lesson01/2/evopen/src/kernel.c ================================================ #include "uart.h" void kernel_main(unsigned long id) { if(id == 0) { uart_init(); } uart_send_string("Hello from processor "); uart_send(id + 48); uart_send_string(".\r\n"); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson01/2/evopen/src/linker-qemu.ld ================================================ SECTIONS { . = 0x80000; .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/2/evopen/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/2/evopen/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/2/evopen/src/uart.c ================================================ #include "peripherals/gpio.h" #include "peripherals/uart.h" #include "utils.h" void uart_init(void) { unsigned int selector; // 32 bits selector = get32(GPFSEL1); selector &= ~(7 << 12); selector |= 4 << 12; selector &= ~(7 << 15); selector |= 4 << 15; put32(GPFSEL1, selector); put32(GPPUD, 0); delay(150); put32(GPPUDCLK0, (1 << 14) | (1 << 15)); delay(150); put32(GPPUDCLK0, 0); put32(UART_CR, 0); put32(UART_IBRD, 26); put32(UART_FBRD, 3); put32(UART_LCRH, (1 << 4) | (3 << 5)); // put32(UART_IMSC, 0); put32(UART_CR, 1 | (1 << 8) | (1 << 9)); } void uart_send(char c) { while (1) { if (!(get32(UART_FR) & (1 << 5))) break; } put32(UART_DR, c); } char uart_recv(void) { while (1) { if (!(get32(UART_FR) & (1 << 4))) break; } return (get32(UART_DR) & 0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i++) { uart_send((char)str[i]); } } ================================================ FILE: exercises/lesson01/2/evopen/src/utils.S ================================================ .globl put32 put32: str w1, [x0] ret .globl get32 get32: ldr w0, [x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/2/evopen/start.sh ================================================ #!/bin/bash ./build.sh if [ $1 == "pi" ]; then cp -f config.txt kernel8.img /Volumes/boot/ && diskutil umount /Volumes/boot elif [ $1 == "qemu" ]; then qemu-system-aarch64 -M raspi3 -smp 4 -nographic -kernel kernel-qemu.img fi ================================================ FILE: exercises/lesson01/2/gcrisis/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean: rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson01/2/gcrisis/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/2/gcrisis/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/2/gcrisis/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/2/gcrisis/include/peripherals/uart.h ================================================ #ifndef _P_UART_H #define _P_UART_H #include "peripherals/base.h" #define UART_DR (PBASE+0x00201000) #define UART_RSRECR (PBASE+0x00201004) #define UART_FR (PBASE+0x00201018) #define UART_IBRD (PBASE+0x00201024) #define UART_FBRD (PBASE+0x00201028) #define UART_LCRH (PBASE+0x0020102C) #define UART_CR (PBASE+0x00201030) #define UART_IFLS (PBASE+0x00201034) #define UART_IMSC (PBASE+0x00201038) #define UART_RIS (PBASE+0x0020103C) #define UART_MIS (PBASE+0x00201040) #define UART_ICR (PBASE+0x00201044) #define UART_DMACR (PBASE+0x00201048) #define UART_ITCR (PBASE+0x00201080) #define UART_ITIP (PBASE+0x00201084) #define UART_ITOP (PBASE+0x00201088) #define UART_TDR (PBASE+0x0020108C) #endif /*_P_UART_H */ ================================================ FILE: exercises/lesson01/2/gcrisis/include/uart.h ================================================ #ifndef _UART_H #define _UART_H void uart_init (int baudrate); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); #endif /*_UART_H */ ================================================ FILE: exercises/lesson01/2/gcrisis/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/2/gcrisis/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson01/2/gcrisis/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/2/gcrisis/src/kernel.c ================================================ #include "uart.h" void kernel_main(void) { uart_init(115200); uart_send_string("Hello, world!\r\n"); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson01/2/gcrisis/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot)} .text :{ *(.text)} .rodata : {*(.rodata) } .data : {*(.data) } . = ALIGN(0x8); bss_begin =.; .bss : {*(.bss*) } bss_end =.; } ================================================ FILE: exercises/lesson01/2/gcrisis/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/2/gcrisis/src/uart.c ================================================ #include "utils.h" #include "peripherals/uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(UART_FR) & (1<<7)) { break; } } put32(UART_DR,c); } char uart_recv ( void ) { while(1) { if(get32(UART_FR) & (1<<6)) { break; } } return(get32(UART_DR)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init (int baudrate) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 4<<12; // set alt0 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 4<<15; // set alt0 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(UART_CR,0); //need remove -mgeneral-regs-only in Makefile int ibrd = 3000000/baudrate; int fbrd = (int)((3000000.0f/baudrate -ibrd)*64.f + 0.5); //LCRH must be set behind IBRD and FBRD,please refer to page 3-14 in http://infocenter.arm.com/help/topic/com.arm.doc.ddi0183g/DDI0183G_uart_pl011_r1p5_trm.pdf put32(UART_IBRD,ibrd); //Set baud rate integer part put32(UART_FBRD,fbrd); //Set baud rate frational part put32(UART_LCRH,(3<<5)); //Enable 8 bit mode put32(UART_IMSC, 0); put32(UART_CR,(1<<8)|(1<<9)|1); //Finally, enable transmitter and receiver } ================================================ FILE: exercises/lesson01/2/gcrisis/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/2/gcrisis/uart-boot/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img raspiloader clean: rm -rf $(BUILD_DIR) *.img raspiloader $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img raspiloader: gcc downloader/*.c -o $@ ================================================ FILE: exercises/lesson01/2/gcrisis/uart-boot/downloader/raspiloader.c ================================================ /************************************************************************* > File Name: raspiloader.c > Author: gyx > Mail: 3224592879@qq.com > Created Time: 2019年04月28日 星期日 16时57分39秒 ************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #define BUF_SIZE 65536 void do_exit(int fd, int res) { // close FD if (fd != -1) close(fd); exit(res); } // open serial connection int open_serial(const char *dev) { struct termios termios; int fd = open(dev, O_RDWR | O_NOCTTY); printf("fd=%d\r\n",fd); if (fd == -1) { // failed to open return -1; } // must be a tty if (!isatty(fd)) { fprintf(stderr, "%s is not a tty\n", dev); do_exit(fd, EXIT_FAILURE); } // Get the attributes. if(tcgetattr(fd, &termios) == -1) { perror("Failed to get attributes of device"); do_exit(fd, EXIT_FAILURE); } // So, we poll. termios.c_cc[VTIME] = 0; termios.c_cc[VMIN] = 0; // 8N1 mode, no input/output/line processing masks. termios.c_iflag = 0; termios.c_oflag = 0; termios.c_cflag = CS8 | CREAD | CLOCAL; termios.c_lflag = 0; // Set the baud rate. if((cfsetispeed(&termios, B115200) < 0) || (cfsetospeed(&termios, B115200) < 0)) { perror("Failed to set baud-rate"); do_exit(fd, EXIT_FAILURE); } // Write the attributes. if (tcsetattr(fd, TCSAFLUSH, &termios) == -1) { perror("tcsetattr()"); do_exit(fd, EXIT_FAILURE); } return fd; } // send kernel to rpi void send_kernel(int fd, const char *file) { int file_fd; off_t off; uint32_t size; ssize_t pos; char *p; bool done = false; // Open file if ((file_fd = open(file, O_RDONLY)) == -1) { perror(file); do_exit(fd, EXIT_FAILURE); } // Get kernel size off = lseek(file_fd, 0L, SEEK_END); if (off > 0x200000) { fprintf(stderr, "kernel too big\n"); do_exit(fd, EXIT_FAILURE); } size = htole32(off); lseek(file_fd, 0L, SEEK_SET); printf("### sending kernel %s [%zu byte]\n", file, (size_t)off); // send kernel size to RPi p = (uint8_t*)&size; pos = 0; while(pos < 4) { ssize_t len = write(fd, &p[pos], 4 - pos); tcdrain(fd); if (len == -1) { perror("write()"); do_exit(fd, EXIT_FAILURE); } pos += len; } //check size uint8_t sizee[4]={0}; pos=0; while(pos<4){ int nn=read(fd, sizee, 4-pos); pos+=nn; } uint32_t size_check=sizee[0]<<24|sizee[1]<<16|sizee[2]<<8|sizee[3]; if(size_check!=size) { perror("size check error"); do_exit(fd, EXIT_FAILURE); } while(!done) { char buf[BUF_SIZE]; ssize_t pos = 0; ssize_t len = read(file_fd, buf, BUF_SIZE); switch(len) { case -1: perror("read()"); do_exit(fd, EXIT_FAILURE); case 0: done = true; } while(len > 0) { ssize_t len2 = write(fd, &buf[pos], len); tcdrain(fd); if (len2 == -1) { perror("write()"); do_exit(fd, EXIT_FAILURE); } len -= len2; pos += len2; } } fprintf(stderr, "### finished sending\n"); return; } int main(int argc, char *argv[]) { int fd; char buf[BUF_SIZE]; fd_set rfds, wfds, efds; printf("raspiloader v1.0\n"); if(argc != 3) { printf("USAGE: %s \n", argv[0]); printf("Example: %s /dev/ttyUSB0 kernel/kernel.img\n", argv[0]); exit(EXIT_FAILURE); } fd=open_serial(argv[1]); if(fd == -1) { perror(argv[1]); do_exit(fd, EXIT_FAILURE); } printf("Start...\n"); while(1) { char *head = "start"; while(1) { char buf2[BUF_SIZE]={0}; write(fd,head,5); tcdrain(fd); for(int i=0;i<5;i++) { usleep(1000); int n = read(fd,buf2,BUF_SIZE); if(n>0) break; } if(!strcmp(buf2,"OK")) { break; } } printf("Download Kernel\r\n"); send_kernel(fd, argv[2]); break; } struct termios old_tio, new_tio; tcgetattr(STDIN_FILENO, &old_tio); new_tio = old_tio; new_tio.c_lflag &= (~ICANON & ~ECHO); tcsetattr(STDIN_FILENO, TCSANOW, &new_tio); while(1) { FD_ZERO(&rfds); FD_SET(0, &rfds); FD_SET(fd, &rfds); int ret=select(fd+1, &rfds, &wfds, &efds, NULL); if(ret) { // input from the user, copy to RPi if (FD_ISSET(STDIN_FILENO, &rfds)) { ssize_t len = read(STDIN_FILENO, &buf[0], BUF_SIZE); ssize_t len2 = write(fd, buf, len); } if (FD_ISSET(fd, &rfds)) { char buf2[BUF_SIZE]; ssize_t len = read(fd, buf2, BUF_SIZE); ssize_t len2 = write(STDOUT_FILENO, buf2, len); } } } close(fd); return 0; } ================================================ FILE: exercises/lesson01/2/gcrisis/uart-boot/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/2/gcrisis/uart-boot/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/2/gcrisis/uart-boot/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/2/gcrisis/uart-boot/include/peripherals/uart.h ================================================ #ifndef _P_UART_H #define _P_UART_H #include "peripherals/base.h" #define UART_DR (PBASE+0x00201000) #define UART_RSRECR (PBASE+0x00201004) #define UART_FR (PBASE+0x00201018) #define UART_IBRD (PBASE+0x00201024) #define UART_FBRD (PBASE+0x00201028) #define UART_LCRH (PBASE+0x0020102C) #define UART_CR (PBASE+0x00201030) #define UART_IFLS (PBASE+0x00201034) #define UART_IMSC (PBASE+0x00201038) #define UART_RIS (PBASE+0x0020103C) #define UART_MIS (PBASE+0x00201040) #define UART_ICR (PBASE+0x00201044) #define UART_DMACR (PBASE+0x00201048) #define UART_ITCR (PBASE+0x00201080) #define UART_ITIP (PBASE+0x00201084) #define UART_ITOP (PBASE+0x00201088) #define UART_TDR (PBASE+0x0020108C) #endif /*_P_UART_H */ ================================================ FILE: exercises/lesson01/2/gcrisis/uart-boot/include/uart.h ================================================ #ifndef _UART_H #define _UART_H void uart_init (int baudrate); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); #endif /*_UART_H */ ================================================ FILE: exercises/lesson01/2/gcrisis/uart-boot/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/2/gcrisis/uart-boot/readme ================================================ This is a simple uart loader for raspberryPi,can download img file less than 2M When download complete ,it will be a simple minicom for debug,use ctrl+c exit. build: make use: ./raspiloader /dev/ttyUSBx kernel8.img ================================================ FILE: exercises/lesson01/2/gcrisis/uart-boot/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: b reset .space 0x200000-4,0 reset: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson01/2/gcrisis/uart-boot/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/2/gcrisis/uart-boot/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot)} .text :{ *(.text)} .rodata : {*(.rodata) } .data : {*(.data) } . = ALIGN(0x8); bss_begin =.; .bss : {*(.bss*) } bss_end =.; } ================================================ FILE: exercises/lesson01/2/gcrisis/uart-boot/src/main.c ================================================ #include "uart.h" #include extern void BRANCHTO(unsigned int); void check_head(char *h,int hlen) { uint8_t index=0; while(1) { char c=uart_recv(); if(c==h[index]) { index++; } else { if(c==h[0]) index=1; else index=0; } if(index==hlen) break; } uart_send_string("OK"); } void kernel_main(void) { uart_init(115200); while (1) { //check_begin char *starth="start"; check_head(starth,5); uint32_t size = uart_recv(); size|=uart_recv()<<8; size|=uart_recv()<<16; size|=uart_recv()<<24; uart_send((size>>24)&0xff); uart_send((size>>16)&0xff); uart_send((size>>8)&0xff); uart_send(size&0xff); uint8_t * kernel = (uint8_t *)0x0; while(size-- >0) { *kernel++ = uart_recv(); } BRANCHTO(0); } return ; } ================================================ FILE: exercises/lesson01/2/gcrisis/uart-boot/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/2/gcrisis/uart-boot/src/uart.c ================================================ #include "utils.h" #include "peripherals/uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(UART_FR) & (1<<7)) { break; } } put32(UART_DR,c); } char uart_recv ( void ) { while(1) { if(get32(UART_FR) & (1<<6)) { break; } } return(get32(UART_DR)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init (int baudrate) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 4<<12; // set alt0 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 4<<15; // set alt0 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(UART_CR,0); //need remove -mgeneral-regs-only in Makefile int ibrd = 3000000/baudrate; int fbrd = (int)((3000000.0f/baudrate -ibrd)*64.f + 0.5); //LCRH must be set behind IBRD and FBRD,please refer to page 3-14 in http://infocenter.arm.com/help/topic/com.arm.doc.ddi0183g/DDI0183G_uart_pl011_r1p5_trm.pdf put32(UART_IBRD,ibrd); //Set baud rate integer part put32(UART_FBRD,fbrd); //Set baud rate frational part put32(UART_LCRH,(3<<5)); //Enable 8 bit mode put32(UART_IMSC, 0); put32(UART_CR,(1<<8)|(1<<9)|1); //Finally, enable transmitter and receiver } ================================================ FILE: exercises/lesson01/2/gcrisis/uart-boot/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl put16 put16: strh w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret .globl BRANCHTO BRANCHTO: br x0 ================================================ FILE: exercises/lesson01/2/rs/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel7.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel7.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel7.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel7.elf -O binary kernel7.img ================================================ FILE: exercises/lesson01/2/rs/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson01/2/rs/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/2/rs/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/2/rs/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/2/rs/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE + 0x00200004) #define GPSET0 (PBASE + 0x0020001C) #define GPCLR0 (PBASE + 0x00200028) #define GPPUD (PBASE + 0x00200094) #define GPPUDCLK0 (PBASE + 0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/2/rs/include/peripherals/uart.h ================================================ #ifndef _P_UART_H #define _P_UART_H #include "peripherals/base.h" #define UART_BASE (PBASE + 0x201000) #define UART_DR_REG (UART_BASE + 0x0) // Data Register #define UART_FR_REG (UART_BASE + 0x18) // Flag register #define UART_IBRD_REG (UART_BASE + 0x24) // Integer Baud rate divisor #define UART_FBRD_REG (UART_BASE + 0x28) // Fractional Baud rate divisor #define UART_LCRH_REG (UART_BASE + 0x2c) // Line Control register #define UART_CR_REG (UART_BASE + 0x30) // Control register // UART_CR_REG fields #define CR_ENABLE 1 // UART enable #define CR_TXE (1 << 8) // Transmit enable #define CR_RXE (1 << 9) // Receive enable // UART_LCRH_REG fields #define LCRH_FEN (1 << 4) // Enable FIFOs #define LCRH_WLEN_8BITS (3 << 5) #define IBRD_BDIVINT 0xffff // Significant part of int. divisor value #define FBRD_BDIVFRAC 0x1f // Significant part of frac. divisor value #define UART_CLOCK 48000000 // Assuming 48Mhz (TODO: use mailbox to get this value) #define UART_BAUD_RATE 115200 #endif /*_P_UART_H */ ================================================ FILE: exercises/lesson01/2/rs/include/uart.h ================================================ #ifndef _UART_H #define _UART_H void uart_init(void); char uart_recv(void); void uart_send(char c); void uart_send_string(char *str); #endif /*_UART_H */ ================================================ FILE: exercises/lesson01/2/rs/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay(unsigned long); extern void put32(unsigned long, unsigned int); extern unsigned int get32(unsigned long); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/2/rs/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0, #0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson01/2/rs/src/config.txt ================================================ arm_control=0x200 kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/2/rs/src/kernel.c ================================================ #include "uart.h" void kernel_main(void) { uart_init(); uart_send_string("Hello, world!\r\n"); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson01/2/rs/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/2/rs/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/2/rs/src/uart.c ================================================ #include "peripherals/uart.h" #include "peripherals/gpio.h" #include "utils.h" void uart_send(char c) { // TXFF: Transmit FIFO full. while (get32(UART_FR_REG) & (1 << 5)) ; put32(UART_DR_REG, c); } char uart_recv(void) { // RXFE: Receive FIFO empty. while (get32(UART_FR_REG) & (1 << 4)) ; return (get32(UART_DR_REG) & 0xFF); } void uart_send_string(char *str) { for (int i = 0; str[i] != '\0'; i++) { uart_send((char)str[i]); } } void uart_init(void) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7 << 12); // clean gpio14 selector |= 4 << 12; // set alt0 for gpio14 selector &= ~(7 << 15); // clean gpio15 selector |= 4 << 15; // set alt0 for gpio15 put32(GPFSEL1, selector); put32(GPPUD, 0); delay(150); put32(GPPUDCLK0, (1 << 14) | (1 << 15)); delay(150); put32(GPPUDCLK0, 0); // Baud rate divisor BAUDDIV = (FUARTCLK/(16 Baud rate)) where FUARTCLK is the // UART reference clock frequency. The BAUDDIV is comprised of the integer // value IBRD and the fractional value FBRD. unsigned int baud = UART_CLOCK * 4 / UART_BAUD_RATE; put32(UART_IBRD_REG, ((unsigned int)(baud >> 6)) & IBRD_BDIVINT); put32(UART_FBRD_REG, (unsigned int)(baud & 0x3F) & FBRD_BDIVFRAC); // Enable FIFO and set word length to 8bits. put32(UART_LCRH_REG, LCRH_FEN | LCRH_WLEN_8BITS); // Enable UART. put32(UART_CR_REG, CR_ENABLE | CR_TXE | CR_RXE); } ================================================ FILE: exercises/lesson01/2/rs/src/utils.S ================================================ .globl put32 put32: str w1, [x0] ret .globl get32 get32: ldr w0, [x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/2/stefanji/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson01/2/stefanji/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson01/2/stefanji/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/2/stefanji/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/2/stefanji/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/2/stefanji/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/2/stefanji/include/peripherals/uart_pl011.h ================================================ #ifndef _P_UART_PL011_H #define _P_UART_PL011_H #include "base.h" #define PL011_BASE (PBASE+0x00201000) #define PL011_DR (PL011_BASE+0x00) #define PL011_RSRECR (PL011_BASE+0x04) #define PL011_FR (PL011_BASE+0x18) #define PL011_ILPR (PL011_BASE+0x20) #define PL011_IBRD (PL011_BASE+0x24) #define PL011_FBRD (PL011_BASE+0x28) #define PL011_LCRH (PL011_BASE+0x2c) #define PL011_CR (PL011_BASE+0x30) #define PL011_IFLS (PL011_BASE+0x34) #define PL011_IMSC (PL011_BASE+0x38) #define PL011_RIS (PL011_BASE+0x3c) #define PL011_MIS (PL011_BASE+0x40) #define PL011_ICR (PL011_BASE+0x44) #define PL011_DMACR (PL011_BASE+0x48) #define PL011_ITCR (PL011_BASE+0x80) #define PL011_ITIP (PL011_BASE+0x84) #define PL011_ITOP (PL011_BASE+0x88) #define PL011_TDR (PL011_BASE+0x8c) #endif ================================================ FILE: exercises/lesson01/2/stefanji/include/pl011_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson01/2/stefanji/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/2/stefanji/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson01/2/stefanji/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/2/stefanji/src/kernel.c ================================================ #include "pl011_uart.h" void kernel_main(void) { uart_init(); uart_send_string("Hello, world!\r\n"); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson01/2/stefanji/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/2/stefanji/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/2/stefanji/src/pl011_uart.c ================================================ #include "utils.h" #include "peripherals/gpio.h" #include "peripherals/uart_pl011.h" void uart_send ( char c ) { while(get32(PL011_FR) & (1<<5)) {} put32(PL011_DR,c); } char uart_recv ( void ) { while(get32(PL011_FR) & (1<<4)) {} return(get32(PL011_DR)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 4<<12; // set alt0 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 4<<15; // set alt0 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); //first disable uart put32(PL011_CR, 0); put32(PL011_IMSC, 0); //from ../adkaster/src/uart.c // Assume 48MHz UART Reference Clock (Standard) // Calculate UART clock divider per datasheet // BAUDDIV = (FUARTCLK/(16 Baud rate)) // Note: We get 6 bits of fraction for the baud div // 48000000/(16 * 115200) = 3000000/115200 = 26.0416666... // Integer part = 26 :) // From http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0183g/I54603.html // we want floor(0.04166666.. * 64 + 0.5) = 3 put32(PL011_IBRD, 26); put32(PL011_FBRD, 3); //little endian: 0111|0000 => 8 bits and enable fifos put32(PL011_LCRH,7<<4); //little endian: 0011|0000|0001 => enable: rx tx uart put32(PL011_CR, (1<<9)|(1<<8)|(1<<0)); } ================================================ FILE: exercises/lesson01/2/stefanji/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/2/xdfm1/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only -std=gnu99 ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel7.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard src/*.c) ASM_FILES = $(wildcard src/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel7.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T src/linker.ld -o $(BUILD_DIR)/kernel7.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel7.elf -O binary kernel7.img ================================================ FILE: exercises/lesson01/2/xdfm1/build.bat ================================================ docker run -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson01/2/xdfm1/build.sh ================================================ #!/bin/bash docker run -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/2/xdfm1/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/2/xdfm1/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/2/xdfm1/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/2/xdfm1/include/peripherals/uart.h ================================================ #ifndef _P_UART_H #define _P_UART_H #include "peripherals/base.h" #define UART_BASE (PBASE + 0x201000) #define UART_DR_REG (UART_BASE + 0x00) #define UART_FR_REG (UART_BASE + 0x18) #define UART_IBRD_REG (UART_BASE + 0x24) #define UART_FBRD_REG (UART_BASE + 0x28) #define UART_LCRH_REG (UART_BASE + 0x2c) #define UART_CR_REG (UART_BASE + 0x30) #define UART_IMSC_REG (UART_BASE + 0x38) #define UART_ICR_REG (UART_BASE + 0x44) #define UART_LCRH_FEN (1<<4) // enable FIFO buffer #define UART_LCRH_WLEN_8BIT (3<<5) // word length = 8 bits #define UART_CR_UARTEN 1 #define UART_CR_TXE (1<<8) #define UART_CR_RXE (1<<9) #define UARTCLK 48000000 #endif /*_P_UART_H */ ================================================ FILE: exercises/lesson01/2/xdfm1/include/uart.h ================================================ #ifndef _UART_H #define _UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); #endif /*_UART_H */ ================================================ FILE: exercises/lesson01/2/xdfm1/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/2/xdfm1/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson01/2/xdfm1/src/config.txt ================================================ arm_control=0x200 kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/2/xdfm1/src/kernel.c ================================================ #include "uart.h" void kernel_main(void) { uart_init(); uart_send_string("Hello, world!\r\n"); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson01/2/xdfm1/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/2/xdfm1/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/2/xdfm1/src/uart.c ================================================ #include "utils.h" #include "peripherals/uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { // wait if transmit buffer is full (TXFF) while(get32(UART_FR_REG) & (1<<5)) { } put32(UART_DR_REG,c); } char uart_recv ( void ) { // wait if recv buffer is empty (RXFE) while(get32(UART_FR_REG) & (1<<4)) { } return(get32(UART_DR_REG)); } void uart_send_string(char* str ) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 4<<12; // set alt0 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 4<<15; // set alt0 for gpio 15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(UART_ICR_REG, 0x7ff); // clear all interrupts /* divisor = UARTCLK / (16 * baudrate) */ /* fractional part register = (fractional part of divisor * 64) + 0.5 */ /* baudrate = 115200 */ put32(UART_IBRD_REG, 26); // integer part put32(UART_FBRD_REG, 3); // fractional part put32(UART_LCRH_REG, UART_LCRH_FEN | UART_LCRH_WLEN_8BIT); // FIFO buffer, 8bits data put32(UART_IMSC_REG, 0); // mask all interrupts put32(UART_CR_REG, UART_CR_UARTEN | UART_CR_TXE | UART_CR_RXE); // enable uaert, transmt and receive } ================================================ FILE: exercises/lesson01/2/xdfm1/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/2/zjd0112/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson01/2/zjd0112/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson01/2/zjd0112/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/2/zjd0112/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson01/2/zjd0112/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/2/zjd0112/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/2/zjd0112/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/2/zjd0112/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson01/2/zjd0112/include/peripherals/pl011_uart.h ================================================ #ifndef _P_PL011_UART_H #define _P_PL011_UART_H #include "peripherals/base.h" #define UART_FR (PBASE+0x201018) #define UART_DR (PBASE+0x201000) #define UART_CR (PBASE+0x201030) #define UART_LCRH (PBASE+0x20102C) #define UART_IBRD (PBASE+0x201024) #define UART_FBRD (PBASE+0x201028) #endif /*_P_PL011_UART_H*/ ================================================ FILE: exercises/lesson01/2/zjd0112/include/pl011_uart.h ================================================ #ifndef _PL011_UART_H #define _PL011_UART_H void pl011_uart_init ( void ); char pl011_uart_recv ( void ); void pl011_uart_send ( char ch ); void pl011_uart_send_string(char* str); #endif /*_PL011_UART_H */ ================================================ FILE: exercises/lesson01/2/zjd0112/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/2/zjd0112/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson01/2/zjd0112/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/2/zjd0112/src/kernel.c ================================================ #include "pl011_uart.h" void kernel_main(void) { pl011_uart_init(); pl011_uart_send_string("Hello, world!\r\n"); while (1) { pl011_uart_send(pl011_uart_recv()); } } ================================================ FILE: exercises/lesson01/2/zjd0112/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/2/zjd0112/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } ================================================ FILE: exercises/lesson01/2/zjd0112/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/2/zjd0112/src/pl011_uart.c ================================================ #include "utils.h" #include "peripherals/pl011_uart.h" #include "peripherals/gpio.h" void pl011_uart_send(char ch) { /*Transmit FIFO is full*/ while (get32(UART_FR) & (1<<5)) { ; } put32(UART_DR, ch); } char pl011_uart_recv(void) { /*Receive FIFO is empty*/ while (get32(UART_FR) & (1<<4)) { ; } return (get32(UART_DR) & 0xFF); } void pl011_uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i++) { pl011_uart_send((char)str[i]); } } void pl011_uart_init(void) { unsigned int selector; /*set gpio function*/ selector = get32(GPFSEL1); selector = selector & (~(7<<12)); // clean gpio14 selector = selector | (4<<12); // set alt1 for gpio14 selector = selector & (~(7<<15)); // clean gpio15 selector = selector | (4<<15); // set alt1 for gpio15 put32(GPFSEL1, selector); /*set gpio pull-up/down*/ put32(GPPUD, 0); delay(150); put32(GPPUDCLK0, (1<<14 | 1<<15)); delay(150); put32(GPPUDCLK0, 0); /*Initializing PL011 uart*/ put32(UART_CR, 0); // disable uart put32(UART_IBRD, 26); // set baudrate = 115200 put32(UART_FBRD, 3); put32(UART_LCRH, (1<<4) | (3<<5)); // 8bits and enable FIFO put32(UART_CR, (1 | (3<<8))); // enable uart, rx, tx } ================================================ FILE: exercises/lesson01/2/zjd0112/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/3/a-v-v/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson01/3/a-v-v/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson01/3/a-v-v/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/3/a-v-v/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson01/3/a-v-v/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/3/a-v-v/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/3/a-v-v/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/3/a-v-v/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson01/3/a-v-v/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/3/a-v-v/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0, #0x3 cbz x0, init_bss /* If processor id is not 0 then pending lock processor * (wait for `sev` instruction) */ wfe b master proc_hang: b proc_hang init_bss: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero sev b master master: mrs x0, mpidr_el1 and x0, x0, #0x3 mov x1, #SECTION_SIZE mul x1, x1, x0 add x1, x1, #LOW_MEMORY mov sp, x1 bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson01/3/a-v-v/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/3/a-v-v/src/kernel.c ================================================ #include "mini_uart.h" void kernel_main(unsigned long processor_index) { static unsigned int current_processor_index = 0; if (processor_index == 0) { uart_init(); } while (processor_index != current_processor_index) ; uart_send_string("Hello from processor "); uart_send(processor_index + '0'); uart_send_string("!\r\n"); current_processor_index++; if (processor_index == 0) { // if current_processor_index == 4 then all processors send message while (current_processor_index != 4) ; for (;;) { uart_send(uart_recv()); } } } ================================================ FILE: exercises/lesson01/3/a-v-v/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/3/a-v-v/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } ================================================ FILE: exercises/lesson01/3/a-v-v/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/3/a-v-v/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/3/adkaster/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson01/3/adkaster/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson01/3/adkaster/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/3/adkaster/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson01/3/adkaster/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/3/adkaster/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/3/adkaster/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/3/adkaster/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson01/3/adkaster/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/3/adkaster/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b setup_stack master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero setup_stack: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id mov x1, 0x4000 // 2Kb for stack mul x1, x1, x0 // multiply 2kb by processor id mov x2, #LOW_MEMORY // Start of stack stpace add x1, x1, x2 // add our offset mov sp, x1 bl kernel_main b proc_hang // should never come here proc_hang: b proc_hang ================================================ FILE: exercises/lesson01/3/adkaster/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/3/adkaster/src/kernel.c ================================================ #include "mini_uart.h" #include "utils.h" void kernel_main(int procid) { char procstr[2]; procstr[0] = procid + '0'; procstr[1] = 0; if(procid == 0) { uart_init(); } else { delay(100000 * procid); } uart_send_string("Hello from processor "); uart_send_string(procstr); uart_send_string("\r\n"); // Don't spin this with every processor or we'll have issues D: if(procid ==0) { while (1) { uart_send(uart_recv()); } } else { while(1) {} } } ================================================ FILE: exercises/lesson01/3/adkaster/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/3/adkaster/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } ================================================ FILE: exercises/lesson01/3/adkaster/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/3/adkaster/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/3/avenito/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson01/3/avenito/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson01/3/avenito/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/3/avenito/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson01/3/avenito/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/3/avenito/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/3/avenito/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/3/avenito/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson01/3/avenito/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/3/avenito/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // get processor id cbz x0, master b init_stack master: // only master needs to initialize de memory adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero init_stack: mrs x0, mpidr_el1 and x0, x0,#0xFF // get processor id again mov x1, x0 // store processor id in 'x1' to preserve 'x0' when call kernel_main mov x2, #0x1000 // define the stack offset in 4Kb (0x1000) mul x1, x1,x2 // multiply the stack offset by processor id add x1, x1,#LOW_MEMORY // add to base address (#LOW_MEMORY) mov sp, x1 bl kernel_main // call 'kernel_main' passing processor id through 'x0' proc_hang: b proc_hang ================================================ FILE: exercises/lesson01/3/avenito/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/3/avenito/src/kernel.c ================================================ #include "utils.h" #include "mm.h" #include "mini_uart.h" static unsigned int processor = 0; // global variable to sinc the processors void kernel_main(char proc_id) // get processor id as 'char' from 'x0' { while (processor != proc_id) {} // wait to execute if (proc_id == 0) { // only the master (processor id = 0) initialize uart uart_init(); } uart_send_string("Hello, world! I am the Core "); uart_send(proc_id + '0'); // add '0' (0x30) to converte to ascii uart_send_string("\r\n"); processor++; // increment 'processor' to enable the next core to execute if (proc_id == 0) { while (1) { uart_send(uart_recv()); // only the master echos the typed character through uart } } else { while (1) {} // all the other cores in loop here } } ================================================ FILE: exercises/lesson01/3/avenito/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/3/avenito/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } ================================================ FILE: exercises/lesson01/3/avenito/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/3/avenito/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/3/bl4ckout31/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel7.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard src/*.c) ASM_FILES = $(wildcard src/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel7.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T src/linker.ld -o $(BUILD_DIR)/kernel7.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel7.elf -O binary kernel7.img ================================================ FILE: exercises/lesson01/3/bl4ckout31/build.bat ================================================ docker run -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson01/3/bl4ckout31/build.sh ================================================ #!/bin/bash docker run -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/3/bl4ckout31/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson01/3/bl4ckout31/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/3/bl4ckout31/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/3/bl4ckout31/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/3/bl4ckout31/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson01/3/bl4ckout31/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern unsigned int proc_id ( void ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/3/bl4ckout31/src/boot.S ================================================ #include "mm.h" .data barrier: .byte 0 .align 2 .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, bss_init // CPU #0 do BSS initialization, others wait b wait bss_init: // BSS Initialization adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero // Tell other CPU that BSS is ready mov w0, #1 ldr x1, =barrier strb w0, [x1] b master wait: // Wait for BSS to bo initialized ldr x1, =barrier ldrb w0, [x1] cmp w0, #1 beq master b wait proc_hang: b proc_hang master: // Use the CPU ID to determine the stack location: // CPU #0: sp = 1 * #LOW_MEMORY, CPU #1: sp = 2 * #LOW_MEMORY, etc mov x0, #LOW_MEMORY mrs x1, mpidr_el1 and x1, x1, #0xFF add x1, x1, #1 mul x0, x0, x1 mov sp, x0 bl kernel_main b proc_hang ================================================ FILE: exercises/lesson01/3/bl4ckout31/src/config.txt ================================================ arm_control=0x200 kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/3/bl4ckout31/src/kernel.c ================================================ #include "mini_uart.h" #include "utils.h" static unsigned int semaphore = 0; void kernel_main(void) { const unsigned int id = proc_id(); // Only CPU #0 do the UART initialization if(id == 0) { uart_init(); } // Wait for previous CPU to finish printing while(id != semaphore) { } uart_send_string("Hello, from processor "); uart_send(id + '0'); uart_send_string("\r\n"); // Tells the next CPU to go ++semaphore; // Only CPU #0 do the echo if (id == 0) { // Wait for everyone else to finish while(semaphore != 4) { } while (1) { uart_send(uart_recv()); } } } ================================================ FILE: exercises/lesson01/3/bl4ckout31/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/3/bl4ckout31/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio 15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baund rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } ================================================ FILE: exercises/lesson01/3/bl4ckout31/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/3/bl4ckout31/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret .globl proc_id proc_id: mrs x0, mpidr_el1 and x0, x0, #0xFF ret ================================================ FILE: exercises/lesson01/3/evopen/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu C_FLAGS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASM_FLAGS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(C_FLAGS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASM_FLAGS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T src/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson01/3/evopen/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/3/evopen/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/3/evopen/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init(void); char uart_recv(void); void uart_send(char c); void uart_send_string(char* str); unsigned int get_reg_baud(unsigned int core_freq, unsigned int baud); #endif ================================================ FILE: exercises/lesson01/3/evopen/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/3/evopen/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/3/evopen/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE + 0x00200004) #define GPSET0 (PBASE + 0x0020001C) #define GPCLR0 (PBASE + 0x00200028) #define GPPUD (PBASE + 0x00200094) #define GPPUDCLK0 (PBASE + 0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/3/evopen/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE + 0x00215004) #define AUX_MU_IO_REG (PBASE + 0x00215040) #define AUX_MU_IER_REG (PBASE + 0x00215044) #define AUX_MU_IIR_REG (PBASE + 0x00215048) #define AUX_MU_LCR_REG (PBASE + 0x0021504C) #define AUX_MU_MCR_REG (PBASE + 0x00215050) #define AUX_MU_LSR_REG (PBASE + 0x00215054) #define AUX_MU_MSR_REG (PBASE + 0x00215058) #define AUX_MU_SCRATCH (PBASE + 0x0021505C) #define AUX_MU_CNTL_REG (PBASE + 0x00215060) #define AUX_MU_STAT_REG (PBASE + 0x00215064) #define AUX_MU_BAUD_REG (PBASE + 0x00215068) #define CORE_FREQ 250 #define BAUD_FREQ 115200 #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson01/3/evopen/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay(unsigned long); extern void put32(unsigned long, unsigned int); extern unsigned int get32(unsigned long); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/3/evopen/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF cbz x0, master b slave master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY mrs x0, mpidr_el1 and x0, x0,#0xFF bl kernel_main slave: mov sp, #LOW_MEMORY mrs x0, mpidr_el1 and x0, x0,#0xFF lsl x0, x0, #15 bl delay mrs x0, mpidr_el1 and x0, x0,#0xFF bl kernel_main ================================================ FILE: exercises/lesson01/3/evopen/src/kernel.c ================================================ #include "mini_uart.h" #include "utils.h" void kernel_main(unsigned long id) { if (id == 0) { uart_init(); } uart_send_string("Hello, from processor "); uart_send(id + 48); uart_send_string(".\r\n"); while (1) ; } ================================================ FILE: exercises/lesson01/3/evopen/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/3/evopen/src/mini_uart.c ================================================ #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" #include "utils.h" unsigned int get_reg_baud(unsigned int core_freq, unsigned int baud) { return core_freq * 1000000 / (8 * baud) - 1; } void uart_init(void) { unsigned int selector; // 32 bits selector = get32(GPFSEL1); selector &= ~(7 << 12); selector |= 2 << 12; selector &= ~(7 << 15); selector |= 2 << 15; put32(GPFSEL1, selector); put32(GPPUD, 0); delay(150); put32(GPPUDCLK0, (1 << 14) | (1 << 15)); delay(150); put32(GPPUDCLK0, 0); put32(AUX_ENABLES, 1); // Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG, 0); // Disable auto flow control put32(AUX_MU_IER_REG, 0); // Disable receive and transmit interrupts put32(AUX_MU_LCR_REG, 3); // Enable 8 bit mode put32(AUX_MU_MCR_REG, 0); // Set RTS line to be always high put32(AUX_MU_BAUD_REG, get_reg_baud(CORE_FREQ, BAUD_FREQ)); // Set baud rate to 115200 put32(AUX_MU_IIR_REG, 6); // Clear FIFO put32(AUX_MU_CNTL_REG, 3); // Finally, enable transmitter and receiver } void uart_send(char c) { while (1) { if (get32(AUX_MU_LSR_REG) & 32) break; } put32(AUX_MU_IO_REG, c); } char uart_recv(void) { while (1) { if (get32(AUX_MU_LSR_REG) & 1) break; } return (get32(AUX_MU_IO_REG) & 0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i++) { uart_send((char)str[i]); } } ================================================ FILE: exercises/lesson01/3/evopen/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/3/evopen/src/utils.S ================================================ .globl put32 put32: str w1, [x0] ret .globl get32 get32: ldr w0, [x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/3/gcrisis/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean: rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson01/3/gcrisis/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init (int baudrate ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson01/3/gcrisis/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/3/gcrisis/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/3/gcrisis/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/3/gcrisis/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson01/3/gcrisis/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern unsigned int get_core_num(void); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/3/gcrisis/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master b init_stack // Hang for all non-primary CPU proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero init_stack: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id mov x2, #0x1000 mul x1, x0,x2 //4kb for stack size add x1, x1,#LOW_MEMORY //offset for stack mov sp, x1 bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson01/3/gcrisis/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/3/gcrisis/src/kernel.c ================================================ #include "mini_uart.h" #include "utils.h" #include "peripherals/mini_uart.h" #include bool is_uart_inited=false; void kernel_main(int corenum) { if(0==corenum) { uart_init(115200); is_uart_inited = true; //output something to make sure only init uart once uart_send(corenum+'0'); uart_send_string("\r\n"); } while(!is_uart_inited); //wait uart init OK delay(corenum*200000); //I find the origin delay cannot use 0 as parameter, so I make some change to delay function uart_send_string("Hello, world! From processor <"); uart_send(corenum+'0'); uart_send_string(">\r\n"); if(0==corenum) { while (1) { uart_send(uart_recv()); } } else { while(1); } } ================================================ FILE: exercises/lesson01/3/gcrisis/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot)} .text :{ *(.text)} .rodata : {*(.rodata) } .data : {*(.data) } . = ALIGN(0x8); bss_begin =.; .bss : {*(.bss*) } bss_end =.; } ================================================ FILE: exercises/lesson01/3/gcrisis/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" #define BAUD_REG_VAL(baud_rate) ((250000000/(baud_rate)/8-1)&0x0000ffff) void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init (int baudrate ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,(int)BAUD_REG_VAL(baudrate)); //Set baud rate to baudrate put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } ================================================ FILE: exercises/lesson01/3/gcrisis/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/3/gcrisis/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: cmp x0,#0 bne do_sub ret do_sub: subs x0, x0, #1 bne do_sub ret ================================================ FILE: exercises/lesson01/3/rs/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel7.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel7.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel7.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel7.elf -O binary kernel7.img ================================================ FILE: exercises/lesson01/3/rs/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson01/3/rs/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/3/rs/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init(void); char uart_recv(void); void uart_send(char c); void uart_send_string(char *str); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson01/3/rs/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/3/rs/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/3/rs/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE + 0x00200004) #define GPSET0 (PBASE + 0x0020001C) #define GPCLR0 (PBASE + 0x00200028) #define GPPUD (PBASE + 0x00200094) #define GPPUDCLK0 (PBASE + 0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/3/rs/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE + 0x00215004) #define AUX_MU_IO_REG (PBASE + 0x00215040) #define AUX_MU_IER_REG (PBASE + 0x00215044) #define AUX_MU_IIR_REG (PBASE + 0x00215048) #define AUX_MU_LCR_REG (PBASE + 0x0021504C) #define AUX_MU_MCR_REG (PBASE + 0x00215050) #define AUX_MU_LSR_REG (PBASE + 0x00215054) #define AUX_MU_MSR_REG (PBASE + 0x00215058) #define AUX_MU_SCRATCH (PBASE + 0x0021505C) #define AUX_MU_CNTL_REG (PBASE + 0x00215060) #define AUX_MU_STAT_REG (PBASE + 0x00215064) #define AUX_MU_BAUD_REG (PBASE + 0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson01/3/rs/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay(unsigned long); extern void put32(unsigned long, unsigned int); extern unsigned int get32(unsigned long); extern unsigned long getmpid(); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/3/rs/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: // Only proc 0 init BSS. mrs x0, mpidr_el1 and x0, x0, #0xFF cbz x0, bss_init // Other procs are delayed to let proc 0 init BSS. mov x0, #100 bl delay b master bss_init: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero b master proc_hang: b proc_hang master: // Init stack with each core its own section. // First core starts at #LOW_MEMORY and other core at // #LOW_MEMORY + (core_id * #SECTION_SIZE). mrs x0, mpidr_el1 and x0, x0, #0xFF mov x1, #LOW_MEMORY mov x2, #SECTION_SIZE mul x2, x2, x0 add x1, x1, x2 mov sp, x1 bl kernel_main b proc_hang ================================================ FILE: exercises/lesson01/3/rs/src/config.txt ================================================ arm_control=0x200 kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/3/rs/src/kernel.c ================================================ #include "mini_uart.h" #include "utils.h" static unsigned int cur_proc = 0; void kernel_main(void) { unsigned long mpid = getmpid(); // Only init UART on proc #0 if (mpid == 0) { uart_init(); } // Wait for previous proc to finish printing their message. while (cur_proc != mpid) ; uart_send_string("Hello, from processor "); uart_send(mpid + '0'); uart_send_string("\r\n"); // Signal the next proc to go. ++cur_proc; // Only proc #0 handles echo if (mpid == 0) { while (cur_proc != 4) ; while (1) { uart_send(uart_recv()); } } } ================================================ FILE: exercises/lesson01/3/rs/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/3/rs/src/mini_uart.c ================================================ #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" #include "utils.h" void uart_send(char c) { while (1) { if (get32(AUX_MU_LSR_REG) & 0x20) break; } put32(AUX_MU_IO_REG, c); } char uart_recv(void) { while (1) { if (get32(AUX_MU_LSR_REG) & 0x01) break; } return (get32(AUX_MU_IO_REG) & 0xFF); } void uart_send_string(char *str) { for (int i = 0; str[i] != '\0'; i++) { uart_send((char)str[i]); } } void uart_init(void) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7 << 12); // clean gpio14 selector |= 2 << 12; // set alt5 for gpio14 selector &= ~(7 << 15); // clean gpio15 selector |= 2 << 15; // set alt5 for gpio15 put32(GPFSEL1, selector); put32(GPPUD, 0); delay(150); put32(GPPUDCLK0, (1 << 14) | (1 << 15)); delay(150); put32(GPPUDCLK0, 0); put32(AUX_ENABLES, 1); // Enable mini uart // (this also enables access to its registers) put32(AUX_MU_CNTL_REG, 0); // Disable auto flow control and disable receiver // and transmitter (for now) put32(AUX_MU_IER_REG, 0); // Disable receive and transmit interrupts put32(AUX_MU_LCR_REG, 3); // Enable 8 bit mode put32(AUX_MU_MCR_REG, 0); // Set RTS line to be always high put32(AUX_MU_BAUD_REG, 270); // Set baud rate to 115200 put32(AUX_MU_CNTL_REG, 3); // Finally, enable transmitter and receiver } ================================================ FILE: exercises/lesson01/3/rs/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/3/rs/src/utils.S ================================================ .globl put32 put32: str w1, [x0] ret .globl get32 get32: ldr w0, [x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret .globl getmpid getmpid: mrs x0, mpidr_el1 and x0, x0, #0xFF ret ================================================ FILE: exercises/lesson01/3/stefanji/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson01/3/stefanji/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson01/3/stefanji/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/3/stefanji/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson01/3/stefanji/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #define STACK_OFFSET 1 << 10 // 1KB #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); void init_memory(unsigned long begin, unsigned long end); void init_stack(unsigned int cort_id); int get_core_id(); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/3/stefanji/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/3/stefanji/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/3/stefanji/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson01/3/stefanji/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/3/stefanji/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: b master proc_hang: b proc_hang master: bl get_core_id cbz x0, init_memory bl get_core_id bl init_stack bl get_core_id bl kernel_main ================================================ FILE: exercises/lesson01/3/stefanji/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/3/stefanji/src/kernel.c ================================================ #include "mini_uart.h" #include "utils.h" void kernel_main(unsigned int core_id) { if(core_id == 0){ uart_init(); } else { delay(300000 * core_id); } //TODO find a better way uart_send_string("Hello Word From #"); uart_send(core_id + '0'); uart_send_string(" Processor Core.\r\n"); if(core_id == 0) while (1) { uart_send(uart_recv()); } else while(1) {}; } ================================================ FILE: exercises/lesson01/3/stefanji/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/3/stefanji/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } ================================================ FILE: exercises/lesson01/3/stefanji/src/mm.S ================================================ #include "mm.h" .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret .global init_memory init_memory: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 b memzero ret .global get_core_id get_core_id: mrs x0, mpidr_el1 and x0, x0, #0xFF ret .global init_stack init_stack: mov x1, #STACK_OFFSET mul x1, x1,x0 add x1, x1,#LOW_MEMORY mov sp, x1 ret ================================================ FILE: exercises/lesson01/3/stefanji/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/3/szediwy/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src .PHONY: clean all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img @echo "clean: [SUCCESS]" $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson01/3/szediwy/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson01/3/szediwy/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/3/szediwy/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson01/3/szediwy/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/3/szediwy/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H // #define PBASE 0x3F000000 // So a peripheral described in this document as being at legacy address 0x7Enn_nnnn is available in the 35-bit address // space at 0x4_7Enn_nnnn, and visible to the ARM at 0x0_FEnn_nnnn if Low Peripheral mode is enabled. // 0x7E000000 (legacy) -> 0x4_7E00_0000 (35-bit) -> 0x0_FE00_0000 (low peripheral) #define PBASE 0xFE000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/3/szediwy/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) // #define GPSET0 (PBASE+0x0020001C) // #define GPCLR0 (PBASE+0x00200028) // #define GPPUD (PBASE+0x00200094) // #define GPPUDCLK0 (PBASE+0x00200098) #define GPIO_PUP_PDN_CNTRL_REG0 (PBASE+0x002000E4) #define UART0_DR (PBASE+0x00201000) #define UART0_FR (PBASE+0x00201018) #define UART0_IBRD (PBASE+0x00201024) #define UART0_FBRD (PBASE+0x00201028) #define UART0_LCRH (PBASE+0x0020102C) #define UART0_CR (PBASE+0x00201030) #define UART0_IMSC (PBASE+0x00201038) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/3/szediwy/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson01/3/szediwy/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/3/szediwy/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0, #0x3 cbz x0, init_bss /* If processor id is not 0 then pending lock processor * (wait for `sev` instruction) */ wfe b master proc_hang: b proc_hang init_bss: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero sev /***********************************************************************/ /* Enable the other cores link: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/arm64/booting.rst?h=v5.3#n255 The boot loader is expected to enter the kernel on each CPU in the following manner: - The primary CPU must jump directly to the first instruction of the kernel image. The device tree blob passed by this CPU must contain an 'enable-method' property for each cpu node. The supported enable-methods are described below. It is expected that the bootloader will generate these device tree properties and insert them into the blob prior to kernel entry. - CPUs with a "spin-table" enable-method must have a 'cpu-release-addr' property in their cpu node. This property identifies a naturally-aligned 64-bit zero-initalised memory location. These CPUs should spin outside of the kernel in a reserved area of memory (communicated to the kernel by a /memreserve/ region in the device tree) polling their cpu-release-addr location, which must be contained in the reserved region. A wfe instruction may be inserted to reduce the overhead of the busy-loop and a sev will be issued by the primary CPU. When a read of the location pointed to by the cpu-release-addr returns a non-zero value, the CPU must jump to this value. The value will be written as a single 64-bit little-endian value, so CPUs must convert the read value to their native endianness before jumping to it. - CPUs with a "psci" enable method should remain outside of the kernel (i.e. outside of the regions of memory described to the kernel in the memory node, or in a reserved area of memory described to the kernel by a /memreserve/ region in the device tree). The kernel will issue CPU_ON calls as described in ARM document number ARM DEN 0022A ("Power State Coordination Interface System Software on ARM processors") to bring CPUs into the kernel. The device tree should contain a 'psci' node, as described in Documentation/devicetree/bindings/arm/psci.yaml. - Secondary CPU general-purpose register settings x0 = 0 (reserved for future use) x1 = 0 (reserved for future use) x2 = 0 (reserved for future use) x3 = 0 (reserved for future use) */ /* cpu0: cpu@0 { device_type = "cpu"; compatible = "arm,cortex-a72"; reg = <0>; enable-method = "spin-table"; cpu-release-addr = <0x0 0x000000d8>; }; cpu1: cpu@1 { device_type = "cpu"; compatible = "arm,cortex-a72"; reg = <1>; enable-method = "spin-table"; cpu-release-addr = <0x0 0x000000e0>; }; cpu2: cpu@2 { device_type = "cpu"; compatible = "arm,cortex-a72"; reg = <2>; enable-method = "spin-table"; cpu-release-addr = <0x0 0x000000e8>; }; cpu3: cpu@3 { device_type = "cpu"; compatible = "arm,cortex-a72"; reg = <3>; enable-method = "spin-table"; cpu-release-addr = <0x0 0x000000f0>; }; */ /****************************************************/ mov x0, #0 adr x0, master mov x1, #0xe0 str x0, [x1] mov x1, #0xe8 str x0, [x1] mov x1, #0xf0 str x0, [x1] master: mrs x0, mpidr_el1 and x0, x0, #0x3 mov x1, #SECTION_SIZE mul x1, x1, x0 add x1, x1, #LOW_MEMORY mov sp, x1 bl kernel_main b proc_hang ================================================ FILE: exercises/lesson01/3/szediwy/src/config.txt ================================================ arm_64bit=1 enable_uart=1 uart_2ndstage=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/3/szediwy/src/kernel.c ================================================ #include "mini_uart.h" void kernel_main(unsigned long processor_index) { static unsigned int current_processor_index = 0; if (processor_index == 0) { uart_init(); } while (processor_index != current_processor_index) ; uart_send_string("Hello from processor "); uart_send(processor_index + '0'); uart_send_string("!\r\n"); current_processor_index++; if (processor_index == 0) { // if current_processor_index == 4 then all processors send message while (current_processor_index != 4) ; for (;;) { uart_send(uart_recv()); } } } ================================================ FILE: exercises/lesson01/3/szediwy/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/3/szediwy/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send(char c) { while (get32(UART0_FR) & (1 << 5)) { } put32(UART0_DR, c); } char uart_recv(void) { while (get32(UART0_FR) & (1 << 4)) { } return (get32(UART0_DR) & 0xFF); } void uart_send_string(char *str) { for (int i = 0; str[i] != '\0'; i++) { uart_send((char)str[i]); } } void uart_init(void) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7 << 12); // clean gpio14 selector |= 4 << 12; // set alt5 for gpio14 selector &= ~(7 << 15); // clean gpio15 selector |= 4 << 15; // set alt5 for gpio15 put32(GPFSEL1, selector); unsigned int pullRegister; pullRegister = get32(GPIO_PUP_PDN_CNTRL_REG0); pullRegister &= ~(3 << 30); pullRegister &= ~(3 << 28); put32(GPIO_PUP_PDN_CNTRL_REG0, pullRegister); //first disable uart put32(UART0_CR, 0); put32(UART0_IMSC, 0); //from ../adkaster/src/uart.c // Assume 48MHz UART Reference Clock (Standard) // Calculate UART clock divider per datasheet // BAUDDIV = (FUARTCLK/(16 Baud rate)) // Note: We get 6 bits of fraction for the baud div // 48000000/(16 * 115200) = 3000000/115200 = 26.0416666... // Integer part = 26 :) // From http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0183g/I54603.html // we want floor(0.04166666.. * 64 + 0.5) = 3 put32(UART0_IBRD, 26); put32(UART0_FBRD, 3); //little endian: 0111|0000 => 8 bits and enable fifos put32(UART0_LCRH, 7 << 4); //little endian: 0011|0000|0001 => enable: rx tx uart put32(UART0_CR, (1 << 9) | (1 << 8) | (1 << 0)); } ================================================ FILE: exercises/lesson01/3/szediwy/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/3/szediwy/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/3/zjd0112/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson01/3/zjd0112/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson01/3/zjd0112/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/3/zjd0112/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson01/3/zjd0112/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #define STACK_SIZE (1 << 20) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/3/zjd0112/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/3/zjd0112/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/3/zjd0112/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson01/3/zjd0112/include/peripherals/pl011_uart.h ================================================ #ifndef _P_PL011_UART_H #define _P_PL011_UART_H #include "peripherals/base.h" #define UART_FR (PBASE+0x201018) #define UART_DR (PBASE+0x201000) #define UART_CR (PBASE+0x201030) #define UART_LCRH (PBASE+0x20102C) #define UART_IBRD (PBASE+0x201024) #define UART_FBRD (PBASE+0x201028) #endif /*_P_PL011_UART_H*/ ================================================ FILE: exercises/lesson01/3/zjd0112/include/pl011_uart.h ================================================ #ifndef _PL011_UART_H #define _PL011_UART_H void pl011_uart_init ( void ); char pl011_uart_recv ( void ); void pl011_uart_send ( char ch ); void pl011_uart_send_string(char* str); #endif /*_PL011_UART_H */ ================================================ FILE: exercises/lesson01/3/zjd0112/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/3/zjd0112/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, init_mem // Hang for all non-primary CPU mov x0, #100 bl delay b kernel_entr proc_hang: b proc_hang init_mem: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero kernel_entr: mrs x0, mpidr_el1 // get processor id again and x0, x0, #0xFF // check processor id mov x1, #STACK_SIZE // move STACK_SIZE to x1 mul x1, x0, x1 // processor_id * STACK_SIZE add x1, x1, #LOW_MEMORY mov sp, x1 bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson01/3/zjd0112/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/3/zjd0112/src/kernel.c ================================================ #include "pl011_uart.h" static int semaphore = 0; void kernel_main(unsigned int proc_id) { if (proc_id == 0) { pl011_uart_init(); } while (semaphore != proc_id) { ; } // semaphore = 1; pl011_uart_send_string("Hello, world from processor "); pl011_uart_send((char)(proc_id + '0')); pl011_uart_send_string("\r\n"); semaphore++; if (proc_id == 0) { while (1) { pl011_uart_send(pl011_uart_recv()); } } } ================================================ FILE: exercises/lesson01/3/zjd0112/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/3/zjd0112/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } ================================================ FILE: exercises/lesson01/3/zjd0112/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/3/zjd0112/src/pl011_uart.c ================================================ #include "utils.h" #include "peripherals/pl011_uart.h" #include "peripherals/gpio.h" void pl011_uart_send(char ch) { /*Transmit FIFO is full*/ while (get32(UART_FR) & (1<<5)) { ; } put32(UART_DR, ch); } char pl011_uart_recv(void) { /*Receive FIFO is empty*/ while (get32(UART_FR) & (1<<4)) { ; } return (get32(UART_DR) & 0xFF); } void pl011_uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i++) { pl011_uart_send((char)str[i]); } } void pl011_uart_init(void) { unsigned int selector; /*set gpio function*/ selector = get32(GPFSEL1); selector = selector & (~(7<<12)); // clean gpio14 selector = selector | (4<<12); // set alt1 for gpio14 selector = selector & (~(7<<15)); // clean gpio15 selector = selector | (4<<15); // set alt1 for gpio15 put32(GPFSEL1, selector); /*set gpio pull-up/down*/ put32(GPPUD, 0); delay(150); put32(GPPUDCLK0, (1<<14 | 1<<15)); delay(150); put32(GPPUDCLK0, 0); /*Initializing PL011 uart*/ put32(UART_CR, 0); // disable uart put32(UART_IBRD, 26); // set baudrate = 115200 put32(UART_FBRD, 3); put32(UART_LCRH, (1<<4) | (3<<5)); // 8bits and enable FIFO put32(UART_CR, (1 | (3<<8))); // enable uart, rx, tx } ================================================ FILE: exercises/lesson01/3/zjd0112/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/4/H-4ND-H/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson01/4/H-4ND-H/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson01/4/H-4ND-H/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/4/H-4ND-H/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson01/4/H-4ND-H/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/4/H-4ND-H/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/4/H-4ND-H/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/4/H-4ND-H/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson01/4/H-4ND-H/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/4/H-4ND-H/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson01/4/H-4ND-H/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/4/H-4ND-H/src/kernel.c ================================================ #include "mini_uart.h" //To run in qemu use "qemu-system-aarch64 -M raspi3 -serial null -serial mon:stdio -kernel " void kernel_main(void) { uart_init(); uart_send_string("Hello, world!\r\n"); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson01/4/H-4ND-H/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/4/H-4ND-H/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } ================================================ FILE: exercises/lesson01/4/H-4ND-H/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/4/H-4ND-H/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/4/a-v-v/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson01/4/a-v-v/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson01/4/a-v-v/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/4/a-v-v/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( unsigned int baudrate ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson01/4/a-v-v/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/4/a-v-v/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/4/a-v-v/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/4/a-v-v/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson01/4/a-v-v/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/4/a-v-v/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson01/4/a-v-v/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/4/a-v-v/src/kernel.c ================================================ #include "mini_uart.h" void kernel_main(void) { const unsigned int baud_rate = 460800; uart_init(baud_rate); uart_send_string("Hello, world!\r\n"); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson01/4/a-v-v/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/4/a-v-v/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( unsigned int baudrate ) { unsigned int selector; const unsigned int baudrate_reg = 31250000 / baudrate - 1; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,baudrate_reg); //Set baud rate put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } ================================================ FILE: exercises/lesson01/4/a-v-v/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/4/a-v-v/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/4/a-v-v/start_qemu.sh ================================================ #!/usr/bin/bash #https://github.com/s-matyukevich/raspberry-pi-os/issues/8#issuecomment-522339426 qemu-system-aarch64 -m 128 -M raspi3 -cpu cortex-a53 -kernel kernel8.img \ -nographic -serial null -chardev stdio,id=tty0 \ -serial chardev:tty0 -monitor none ================================================ FILE: exercises/lesson01/4/avenito/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson01/4/avenito/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson01/4/avenito/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/4/avenito/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/4/avenito/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 //215004 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/4/avenito/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/4/avenito/include/peripherals/uart.h ================================================ #ifndef _P_UART_H #define _P_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00201000) #define UART_DR (PBASE+0x00201000) #define UART_FR (PBASE+0x00201018) #define UART_CR (PBASE+0x00201030) #define UART_IBRD (PBASE+0x00201024) #define UART_FBRD (PBASE+0x00201028) #define UARTLCR_LCRH (PBASE+0x0020102C) #endif /*_P_UART_H */ ================================================ FILE: exercises/lesson01/4/avenito/include/uart.h ================================================ #ifndef _UART_H #define _UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); void putc ( void* p, char c ); #endif /*_UART_H */ ================================================ FILE: exercises/lesson01/4/avenito/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/4/avenito/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson01/4/avenito/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/4/avenito/src/kernel.c ================================================ #include "uart.h" void kernel_main(void) { uart_init(); uart_send_string("Hello, world!\r\n"); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson01/4/avenito/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/4/avenito/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/4/avenito/src/uart.c ================================================ #include "utils.h" #include "peripherals/uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(get32(UART_FR)&0x20) {} // wait if TX is full put32(UART_DR,c); // when TX is empty, send next char } char uart_recv ( void ) { while(get32(UART_FR)&0x10) {} // wait if RX is empty return(get32(UART_DR)&0xFF); // get recived char } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 4<<12; // set alt0 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 4<<15; // set alt0 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(UART_CR,0); // disable RX and TX to configure put32(UART_IBRD,26); //PrimeCell UART (PL011) rev.r1p5 pag.3-9 BAUDDIV = (FUARTCLK/(16 Baud rate)) = 48MHz/(16*115200) = 26.041666 put32(UART_FBRD,3); put32(UARTLCR_LCRH,0x60); //Enable 8 bit mode put32(UART_CR,0x301); // enable UART, RX and TX } ================================================ FILE: exercises/lesson01/4/avenito/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/4/bl4ckout31/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel7.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard src/*.c) ASM_FILES = $(wildcard src/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel7.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T src/linker.ld -o $(BUILD_DIR)/kernel7.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel7.elf -O binary kernel7.img ================================================ FILE: exercises/lesson01/4/bl4ckout31/build.bat ================================================ docker run -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson01/4/bl4ckout31/build.sh ================================================ #!/bin/bash docker run -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/4/bl4ckout31/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/4/bl4ckout31/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/4/bl4ckout31/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/4/bl4ckout31/include/peripherals/uart.h ================================================ #ifndef _P_UART_H #define _P_UART_H #include "peripherals/base.h" #define U_BASE (PBASE+0x00201000) #define U_DATA_REG (U_BASE) #define U_FR_REG (U_BASE+0x18) #define U_IBRD_REG (U_BASE+0x24) #define U_FBRD_REG (U_BASE+0x28) #define U_LCRH_REG (U_BASE+0x2C) #define U_CR_REG (U_BASE+0x30) #define U_IMSC_REG (U_BASE+0x38) #endif /*_P_UART_H */ ================================================ FILE: exercises/lesson01/4/bl4ckout31/include/uart.h ================================================ #ifndef _UART_H #define _UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); #endif /*_UART_H */ ================================================ FILE: exercises/lesson01/4/bl4ckout31/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/4/bl4ckout31/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson01/4/bl4ckout31/src/config.txt ================================================ arm_control=0x200 kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/4/bl4ckout31/src/kernel.c ================================================ #include "uart.h" void kernel_main(void) { uart_init(); uart_send_string("Hello, world!\r\n"); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson01/4/bl4ckout31/src/linker.ld ================================================ SECTIONS { . = 0x80000; .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/4/bl4ckout31/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/4/bl4ckout31/src/uart.c ================================================ #include "utils.h" #include "peripherals/uart.h" #include "peripherals/gpio.h" void uart_send (char c) { // wait for transmit FIFO to have an available slot while(get32(U_FR_REG) & (1<<5)) { } put32(U_DATA_REG, c); } char uart_recv () { // wait for receive FIFO to have data to read while(get32(U_FR_REG) & (1<<4)) { } return(get32(U_DATA_REG) & 0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char) str[i]); } } void uart_init (void) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 4<<12; // set alt0 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 4<<15; // set alt0 for gpio 15 put32(GPFSEL1, selector); put32(GPPUD, 0); delay(150); put32(GPPUDCLK0, (1<<14) | (1<<15)); delay(150); put32(GPPUDCLK0, 0); put32(U_CR_REG, 0); // disable UART until configuration is done /* baud divisor = UARTCLK / (16 * baud_rate) = 48 * 10^6 / (16 * 115200) = 26.0416666667 integer part = 26 fractional part = (int) ((0.0416666667 * 64) + 0.5) = 3 generated baud rate divisor = 26 + (3 / 64) = 26.046875 generated baud rate = (48 * 10^6) / (16 * 26.046875) = 115177 error = |(115177 - 115200) / 115200 * 100| = 0.02% */ put32(U_IBRD_REG, 26); // baud rate divisor, integer part put32(U_FBRD_REG, 3); // baud rate divisor, fractional part put32(U_LCRH_REG, (1<<4) | (3<<5)); // enable FIFOs and 8 bits frames put32(U_IMSC_REG, 0); // mask interupts put32(U_CR_REG, 1 | (1<<8) | (1<<9)); // enable UART, receive and transmit } ================================================ FILE: exercises/lesson01/4/bl4ckout31/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/4/bl4ckout31/start.sh ================================================ #!/bin/bash echo "###" echo "### Use C-a h for help" echo "###" echo "" qemu-system-aarch64 -machine raspi3 -nographic -kernel kernel7.img ================================================ FILE: exercises/lesson01/4/evopen/.gitignore ================================================ workspace/ ================================================ FILE: exercises/lesson01/4/evopen/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu C_FLAGS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASM_FLAGS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img kernel-qemu.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(C_FLAGS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASM_FLAGS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T src/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img kernel-qemu.img: $(SRC_DIR)/linker-qemu.ld $(OBJ_FILES) $(ARMGNU)-ld -T src/linker-qemu.ld -o $(BUILD_DIR)/kernel-qemu.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel-qemu.elf -O binary kernel-qemu.img ================================================ FILE: exercises/lesson01/4/evopen/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/4/evopen/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/4/evopen/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/4/evopen/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/4/evopen/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE + 0x00200004) #define GPSET0 (PBASE + 0x0020001C) #define GPCLR0 (PBASE + 0x00200028) #define GPPUD (PBASE + 0x00200094) #define GPPUDCLK0 (PBASE + 0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/4/evopen/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE + 0x00215004) #define AUX_MU_IO_REG (PBASE + 0x00215040) #define AUX_MU_IER_REG (PBASE + 0x00215044) #define AUX_MU_IIR_REG (PBASE + 0x00215048) #define AUX_MU_LCR_REG (PBASE + 0x0021504C) #define AUX_MU_MCR_REG (PBASE + 0x00215050) #define AUX_MU_LSR_REG (PBASE + 0x00215054) #define AUX_MU_MSR_REG (PBASE + 0x00215058) #define AUX_MU_SCRATCH (PBASE + 0x0021505C) #define AUX_MU_CNTL_REG (PBASE + 0x00215060) #define AUX_MU_STAT_REG (PBASE + 0x00215064) #define AUX_MU_BAUD_REG (PBASE + 0x00215068) #define CORE_FREQ 250 #define BAUD_FREQ 115200 #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson01/4/evopen/include/peripherals/uart.h ================================================ #ifndef _P_UART_H #define _P_UART_H #include "peripherals/base.h" #define UART_BASE (PBASE + 0x201000) #define UART_DR (UART_BASE) #define UART_FR (UART_BASE + 0x18) #define UART_IBRD (UART_BASE + 0x24) #define UART_FBRD (UART_BASE + 0x28) #define UART_CR (UART_BASE + 0x30) #define UART_LCRH (UART_BASE + 0x2c) #define UART_IMSC (UART_BASE + 0x18) #define CORE_FREQ 250 #define BAUD_FREQ 115200 #endif /*_P_UART_H */ ================================================ FILE: exercises/lesson01/4/evopen/include/uart.h ================================================ #ifndef _UART_H #define _UART_H void uart_init(void); char uart_recv(void); void uart_send(char c); void uart_send_string(char* str); unsigned int get_reg_baud(unsigned int core_freq, unsigned int baud); #endif ================================================ FILE: exercises/lesson01/4/evopen/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay(unsigned long); extern void put32(unsigned long, unsigned int); extern unsigned int get32(unsigned long); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/4/evopen/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF cbz x0, master b slave master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY mrs x0, mpidr_el1 and x0, x0,#0xFF bl kernel_main slave: mov sp, #LOW_MEMORY mrs x0, mpidr_el1 and x0, x0,#0xFF lsl x0, x0, #15 bl delay mrs x0, mpidr_el1 and x0, x0,#0xFF bl kernel_main ================================================ FILE: exercises/lesson01/4/evopen/src/kernel.c ================================================ #include "uart.h" void kernel_main(unsigned long id) { if(id == 0) { uart_init(); } uart_send_string("Hello from processor "); uart_send(id + 48); uart_send_string(".\r\n"); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson01/4/evopen/src/linker-qemu.ld ================================================ SECTIONS { . = 0x80000; .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/4/evopen/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/4/evopen/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/4/evopen/src/uart.c ================================================ #include "peripherals/gpio.h" #include "peripherals/uart.h" #include "utils.h" void uart_init(void) { unsigned int selector; // 32 bits selector = get32(GPFSEL1); selector &= ~(7 << 12); selector |= 4 << 12; selector &= ~(7 << 15); selector |= 4 << 15; put32(GPFSEL1, selector); put32(GPPUD, 0); delay(150); put32(GPPUDCLK0, (1 << 14) | (1 << 15)); delay(150); put32(GPPUDCLK0, 0); put32(UART_CR, 0); put32(UART_IBRD, 26); put32(UART_FBRD, 3); put32(UART_LCRH, (1 << 4) | (3 << 5)); // put32(UART_IMSC, 0); put32(UART_CR, 1 | (1 << 8) | (1 << 9)); } void uart_send(char c) { while (1) { if (!(get32(UART_FR) & (1 << 5))) break; } put32(UART_DR, c); } char uart_recv(void) { while (1) { if (!(get32(UART_FR) & (1 << 4))) break; } return (get32(UART_DR) & 0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i++) { uart_send((char)str[i]); } } ================================================ FILE: exercises/lesson01/4/evopen/src/utils.S ================================================ .globl put32 put32: str w1, [x0] ret .globl get32 get32: ldr w0, [x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/4/evopen/start.sh ================================================ #!/bin/bash ./build.sh if [ $1 == "pi" ]; then cp -f config.txt kernel8.img /Volumes/boot/ && diskutil umount /Volumes/boot elif [ $1 == "qemu" ]; then qemu-system-aarch64 -M raspi3 -smp 4 -nographic -kernel kernel-qemu.img fi ================================================ FILE: exercises/lesson01/4/rs/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel7.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel7.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel7.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel7.elf -O binary kernel7.img ================================================ FILE: exercises/lesson01/4/rs/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson01/4/rs/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson01/4/rs/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init(void); char uart_recv(void); void uart_send(char c); void uart_send_string(char *str); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson01/4/rs/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson01/4/rs/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson01/4/rs/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE + 0x00200004) #define GPSET0 (PBASE + 0x0020001C) #define GPCLR0 (PBASE + 0x00200028) #define GPPUD (PBASE + 0x00200094) #define GPPUDCLK0 (PBASE + 0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson01/4/rs/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE + 0x00215004) #define AUX_MU_IO_REG (PBASE + 0x00215040) #define AUX_MU_IER_REG (PBASE + 0x00215044) #define AUX_MU_IIR_REG (PBASE + 0x00215048) #define AUX_MU_LCR_REG (PBASE + 0x0021504C) #define AUX_MU_MCR_REG (PBASE + 0x00215050) #define AUX_MU_LSR_REG (PBASE + 0x00215054) #define AUX_MU_MSR_REG (PBASE + 0x00215058) #define AUX_MU_SCRATCH (PBASE + 0x0021505C) #define AUX_MU_CNTL_REG (PBASE + 0x00215060) #define AUX_MU_STAT_REG (PBASE + 0x00215064) #define AUX_MU_BAUD_REG (PBASE + 0x00215068) #define SYS_CLOCK_FREQ 250000000 #define AUX_BAUD_RATE 115200 #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson01/4/rs/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay(unsigned long); extern void put32(unsigned long, unsigned int); extern unsigned int get32(unsigned long); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson01/4/rs/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0, #0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson01/4/rs/src/config.txt ================================================ arm_control=0x200 kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson01/4/rs/src/kernel.c ================================================ #include "mini_uart.h" void kernel_main(void) { uart_init(); uart_send_string("Hello, world!\r\n"); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson01/4/rs/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson01/4/rs/src/mini_uart.c ================================================ #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" #include "utils.h" void uart_send(char c) { while (1) { if (get32(AUX_MU_LSR_REG) & 0x20) break; } put32(AUX_MU_IO_REG, c); } char uart_recv(void) { while (1) { if (get32(AUX_MU_LSR_REG) & 0x01) break; } return (get32(AUX_MU_IO_REG) & 0xFF); } void uart_send_string(char *str) { for (int i = 0; str[i] != '\0'; i++) { uart_send((char)str[i]); } } void uart_init(void) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7 << 12); // clean gpio14 selector |= 2 << 12; // set alt5 for gpio14 selector &= ~(7 << 15); // clean gpio15 selector |= 2 << 15; // set alt5 for gpio15 put32(GPFSEL1, selector); put32(GPPUD, 0); delay(150); put32(GPPUDCLK0, (1 << 14) | (1 << 15)); delay(150); put32(GPPUDCLK0, 0); put32(AUX_ENABLES, 1); // Enable mini uart // (this also enables access to its registers) put32(AUX_MU_CNTL_REG, 0); // Disable auto flow control and disable receiver // and transmitter (for now) put32(AUX_MU_IER_REG, 0); // Disable receive and transmit interrupts put32(AUX_MU_LCR_REG, 3); // Enable 8 bit mode put32(AUX_MU_MCR_REG, 0); // Set RTS line to be always high put32(AUX_MU_BAUD_REG, SYS_CLOCK_FREQ / 8 / AUX_BAUD_RATE - 1); // Set baud rate put32(AUX_MU_CNTL_REG, 3); // Finally, enable transmitter and receiver } ================================================ FILE: exercises/lesson01/4/rs/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson01/4/rs/src/utils.S ================================================ .globl put32 put32: str w1, [x0] ret .globl get32 get32: ldr w0, [x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson01/4/rs/start.sh ================================================ #!/bin/bash qemu-system-aarch64 -machine raspi3 -serial null -serial mon:stdio -nographic -kernel kernel7.img ================================================ FILE: exercises/lesson01/4/stefanji/run_on_qemu.sh ================================================ #! /usr/bin/env bash path='../../../../src/lesson01/' # build lesson01 echo "cd $path" cd $path ./build.sh $1 # run on qemu echo "Will running on qemu, ctl-c to exit qemu process" qemu-system-aarch64 -m 128 \ -M raspi3 \ -cpu cortex-a53 \ -kernel kernel8.img \ -nographic \ -serial null \ -chardev stdio,id=uart1 \ -serial chardev:uart1 \ -monitor none ================================================ FILE: exercises/lesson02/1/H-4ND-H/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson02/1/H-4ND-H/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson02/1/H-4ND-H/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson02/1/H-4ND-H/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_EL2h (9 << 0) #define SPSR_VALUE_EL1 (SPSR_MASK_ALL | SPSR_EL1h) #define SPSR_VALUE_EL2 (SPSR_MASK_ALL | SPSR_EL2h) #endif ================================================ FILE: exercises/lesson02/1/H-4ND-H/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson02/1/H-4ND-H/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson02/1/H-4ND-H/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson02/1/H-4ND-H/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson02/1/H-4ND-H/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson02/1/H-4ND-H/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson02/1/H-4ND-H/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson02/1/H-4ND-H/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_init //Uart will now work ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el2, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE_EL2 msr spsr_el3, x0 adr x0, el2_entry msr elr_el3, x0 eret el1_entry: mov sp, #LOW_MEMORY bl kernel_main b proc_hang //Shoule never come here el2_entry: mov sp, #LOW_MEMORY bl print_el ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =SPSR_VALUE_EL1 msr spsr_el2, x0 adr x0, el1_entry msr elr_el2, x0 eret ================================================ FILE: exercises/lesson02/1/H-4ND-H/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson02/1/H-4ND-H/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "mini_uart.h" void print_el() { int el = get_el(); printf("Exception level: %d \r\n", el); } void kernel_init() { uart_init(); init_printf(0, putc); print_el(); } void kernel_main(void) { print_el(); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson02/1/H-4ND-H/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson02/1/H-4ND-H/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson02/1/H-4ND-H/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson02/1/H-4ND-H/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson02/1/H-4ND-H/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson02/1/avenito/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson02/1/avenito/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson02/1/avenito/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson02/1/avenito/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_EL2h (9 << 0) // according to Page 393 of AArch64-Reference-Manual. #define SPSR_VALUE_EL1 (SPSR_MASK_ALL | SPSR_EL1h) #define SPSR_VALUE_EL2 (SPSR_MASK_ALL | SPSR_EL2h) #endif ================================================ FILE: exercises/lesson02/1/avenito/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson02/1/avenito/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson02/1/avenito/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson02/1/avenito/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson02/1/avenito/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson02/1/avenito/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson02/1/avenito/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson02/1/avenito/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 // goes to EL2 ldr x0, =SPSR_VALUE_EL2 // look at sysregs.h msr spsr_el3, x0 adr x0, el2_entry msr elr_el3, x0 eret el2_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main_first // goes to EL1 ldr x0, =SPSR_VALUE_EL1 msr spsr_el2, x0 adr x0, el1_entry msr elr_el2, x0 eret el1_entry: mov sp, #LOW_MEMORY bl kernel_main_second b proc_hang // should never come here ================================================ FILE: exercises/lesson02/1/avenito/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson02/1/avenito/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "mini_uart.h" void kernel_main_first(void) { uart_init(); init_printf(0, putc); int el = get_el(); printf("1.Exception level: %d \r\n", el); return; } void kernel_main_second(void) { int el = get_el(); printf("2.Exception level: %d \r\n", el); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson02/1/avenito/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson02/1/avenito/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson02/1/avenito/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson02/1/avenito/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson02/1/avenito/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson02/1/bl4ckout31/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson02/1/bl4ckout31/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson02/1/bl4ckout31/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson02/1/bl4ckout31/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR1_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR1_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR1_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR1_I_CACHE_DISABLED (0 << 12) #define SCTLR1_D_CACHE_DISABLED (0 << 2) #define SCTLR1_MMU_DISABLED (0 << 0) #define SCTLR1_MMU_ENABLED (1 << 0) #define SCTLR1_VALUE_MMU_DISABLED (SCTLR1_RESERVED | SCTLR1_EE_LITTLE_ENDIAN | SCTLR1_I_CACHE_DISABLED | SCTLR1_D_CACHE_DISABLED | SCTLR1_MMU_DISABLED) // *************************************** // SCTLR_EL2, System Control Register (EL2), Page 2665 of AArch64-Reference-Manual. // *************************************** #define SCTLR2_RESERVED (3 << 28) | (3 << 22) | (1 << 18) | (1 << 16) | (1 << 11) | (3 << 4) #define SCTLR2_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR2_I_CACHE_DISABLED (0 << 12) #define SCTLR2_D_CACHE_DISABLED (0 << 2) #define SCTLR2_MMU_DISABLED (0 << 0) #define SCTLR2_VALUE_MMU_DISABLED (SCTLR2_RESERVED | SCTLR2_EE_LITTLE_ENDIAN | SCTLR2_I_CACHE_DISABLED | SCTLR2_D_CACHE_DISABLED | SCTLR2_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR3_MASK_ALL (7 << 6) #define SPSR3_EL2h (9 << 0) #define SPSR3_VALUE (SPSR3_MASK_ALL | SPSR3_EL2h) // *************************************** // SPSR_EL2, Saved Program Status Register (EL2) Page 383 of AArch64-Reference-Manual. // *************************************** #define SPSR2_MASK_ALL (7 << 6) #define SPSR2_EL1h (5 << 0) #define SPSR2_VALUE (SPSR2_MASK_ALL | SPSR2_EL1h) #endif ================================================ FILE: exercises/lesson02/1/bl4ckout31/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson02/1/bl4ckout31/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson02/1/bl4ckout31/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson02/1/bl4ckout31/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson02/1/bl4ckout31/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson02/1/bl4ckout31/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson02/1/bl4ckout31/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson02/1/bl4ckout31/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR2_VALUE_MMU_DISABLED msr sctlr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR3_VALUE msr spsr_el3, x0 adr x0, el2_entry msr elr_el3, x0 eret el2_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main ldr x0, =SCTLR1_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SPSR2_VALUE msr spsr_el2, x0 adr x0, el1_entry msr elr_el2, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson02/1/bl4ckout31/src/config.txt ================================================ arm_control=0x200 kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson02/1/bl4ckout31/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "mini_uart.h" void kernel_main(void) { int el = get_el(); // UART init in EL2 only if (el == 2) { uart_init(); } init_printf(0, putc); printf("Exception level: %d \r\n", el); // Echo in EL1 only if (el == 1) { while (1) { uart_send(uart_recv()); } } } ================================================ FILE: exercises/lesson02/1/bl4ckout31/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson02/1/bl4ckout31/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson02/1/bl4ckout31/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson02/1/bl4ckout31/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson02/1/bl4ckout31/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson02/1/bl4ckout31/start.sh ================================================ #!/bin/bash if [ $# -ne 1 ]; then echo "usage: $0 kernel" exit 1 fi mount -L rpiboot /mnt cp "$1" /mnt umount /mnt ================================================ FILE: exercises/lesson02/1/evopen/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu C_FLAGS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASM_FLAGS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(C_FLAGS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASM_FLAGS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T src/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson02/1/evopen/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson02/1/evopen/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson02/1/evopen/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson02/1/evopen/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2025 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 1923 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2022 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 288 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (0b0101) #define SPSR_EL2h (0b1001) #define SPSR_EL1 (SPSR_MASK_ALL | SPSR_EL1h) #define SPSR_EL2 (SPSR_MASK_ALL | SPSR_EL2h) #endif ================================================ FILE: exercises/lesson02/1/evopen/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson02/1/evopen/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson02/1/evopen/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson02/1/evopen/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson02/1/evopen/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson02/1/evopen/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson02/1/evopen/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson02/1/evopen/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el2, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_EL2 msr spsr_el3, x0 adr x0, el2_entry msr elr_el3, x0 eret el2_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =SPSR_EL1 msr spsr_el2, x0 adr x0, el1_entry msr elr_el2, x0 eret el1_entry: mov sp, #LOW_MEMORY bl kernel_main b proc_hang ================================================ FILE: exercises/lesson02/1/evopen/src/config.txt ================================================ arm_control=0x200 kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson02/1/evopen/src/kernel.c ================================================ #include "mini_uart.h" #include "printf.h" #include "utils.h" void kernel_main(void) { uart_init(); init_printf(0, putc); int el = get_el(); printf("Exception level: %d \r\n", el); } ================================================ FILE: exercises/lesson02/1/evopen/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson02/1/evopen/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson02/1/evopen/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson02/1/evopen/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson02/1/evopen/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson02/1/evopen/start.sh ================================================ #!/bin/bash ./build.sh if [ $1 == "pi" ]; then cp -f config.txt kernel8.img /Volumes/boot/ && diskutil umount /Volumes/boot elif [ $1 == "qemu" ]; then qemu-system-aarch64 -M raspi3 -smp cores=4,sockets=1 -nographic -kernel kernel-qemu.img fi ================================================ FILE: exercises/lesson02/1/gcrisis/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean: rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson02/1/gcrisis/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR1_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // SCTLR_EL2, System Control Register (EL2), Page 2665 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR2_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL2h (9 << 0) #define SPSR_EL1h (5 << 0) #define SPSR_EL2_VALUE (SPSR_MASK_ALL | SPSR_EL2h) #define SPSR_EL1_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #endif ================================================ FILE: exercises/lesson02/1/gcrisis/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init (int baudrate ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); void putc(void* p,char c); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson02/1/gcrisis/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson02/1/gcrisis/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson02/1/gcrisis/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson02/1/gcrisis/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson02/1/gcrisis/include/printf.h ================================================ /* File: printf.h Copyright (c) 2004,2012 Kustaa Nyholm / SpareTimeLabs All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of the Kustaa Nyholm or SpareTimeLabs nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson02/1/gcrisis/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern unsigned int get_el(void); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson02/1/gcrisis/src/boot.S ================================================ #include "mm.h" #include "arm/sysregs.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0,=SCTLR2_VALUE_MMU_DISABLED msr sctlr_el2,x0 ldr x0,=HCR_VALUE msr hcr_el2 ,x0 ldr x0,=SCR_VALUE msr scr_el3, x0 ldr x0,=SPSR_EL2_VALUE msr spsr_el3,x0 adr x0,el2_entry msr elr_el3,x0 eret el2_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel2_main ldr x0,=SCTLR1_VALUE_MMU_DISABLED msr sctlr_el1,x0 ldr x0,=SPSR_EL1_VALUE msr spsr_el2,x0 adr x0,el1_entry msr elr_el2,x0 eret el1_entry: mov sp, #LOW_MEMORY bl kernel1_main b proc_hang // should never come here ================================================ FILE: exercises/lesson02/1/gcrisis/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson02/1/gcrisis/src/kernel.c ================================================ #include "mini_uart.h" #include "printf.h" #include "utils.h" void kernel1_main(void) { //init uart and printf uart_init(115200); init_printf(0,putc); while (1) { printf("current EL:%d\r\n",get_el()); uart_send(uart_recv()); } } void kernel2_main(void) { //init uart and printf uart_init(115200); init_printf(0,putc); printf("current EL:%d\r\n",get_el()); return; } ================================================ FILE: exercises/lesson02/1/gcrisis/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot)} .text :{ *(.text)} .rodata : {*(.rodata) } .data : {*(.data) } . = ALIGN(0x8); bss_begin =.; .bss : {*(.bss*) } bss_end =.; } ================================================ FILE: exercises/lesson02/1/gcrisis/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" #define BAUD_REG_VAL(baud_rate) ((250000000/(baud_rate)/8-1)&0x0000ffff) void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init (int baudrate ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,(int)BAUD_REG_VAL(baudrate)); //Set baud rate to baudrate put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson02/1/gcrisis/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson02/1/gcrisis/src/printf.c ================================================ /* * Copyright (c) 2004,2012 Kustaa Nyholm / SpareTimeLabs * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this list * of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, this * list of conditions and the following disclaimer in the documentation and/or other * materials provided with the distribution. * * Neither the name of the Kustaa Nyholm or SpareTimeLabs nor the names of its * contributors may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson02/1/gcrisis/src/utils.S ================================================ .globl get_el get_el: mrs x0,CurrentEL lsr x0,x0,#2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson02/1/rs/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel7.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel7.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel7.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel7.elf -O binary kernel7.img ================================================ FILE: exercises/lesson02/1/rs/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson02/1/rs/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson02/1/rs/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of // AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED \ (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | \ SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of // AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of // AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of // AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_EL2h (9 << 0) #define SPSR_EL1 (SPSR_MASK_ALL | SPSR_EL1h) #define SPSR_EL2 (SPSR_MASK_ALL | SPSR_EL2h) /* Current Exception Level values, as contained in CurrentEL */ #define CurrentEL_EL1 (1 << 2) #define CurrentEL_EL2 (2 << 2) #define CurrentEL_EL3 (3 << 2) #endif ================================================ FILE: exercises/lesson02/1/rs/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init(void); char uart_recv(void); void uart_send(char c); void putc(void *p, char c); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson02/1/rs/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson02/1/rs/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson02/1/rs/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE + 0x00200004) #define GPSET0 (PBASE + 0x0020001C) #define GPCLR0 (PBASE + 0x00200028) #define GPPUD (PBASE + 0x00200094) #define GPPUDCLK0 (PBASE + 0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson02/1/rs/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE + 0x00215004) #define AUX_MU_IO_REG (PBASE + 0x00215040) #define AUX_MU_IER_REG (PBASE + 0x00215044) #define AUX_MU_IIR_REG (PBASE + 0x00215048) #define AUX_MU_LCR_REG (PBASE + 0x0021504C) #define AUX_MU_MCR_REG (PBASE + 0x00215050) #define AUX_MU_LSR_REG (PBASE + 0x00215054) #define AUX_MU_MSR_REG (PBASE + 0x00215058) #define AUX_MU_SCRATCH (PBASE + 0x0021505C) #define AUX_MU_CNTL_REG (PBASE + 0x00215060) #define AUX_MU_STAT_REG (PBASE + 0x00215064) #define AUX_MU_BAUD_REG (PBASE + 0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson02/1/rs/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void *putp, void (*putf)(void *, char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char *s, char *fmt, ...); void tfp_format(void *putp, void (*putf)(void *, char), char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson02/1/rs/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay(unsigned long); extern void put32(unsigned long, unsigned int); extern unsigned int get32(unsigned long); extern int get_el(void); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson02/1/rs/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0, #0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: mrs x0, CurrentEL cmp x0, #CurrentEL_EL3 b.eq el3_entry b el2_entry el3_entry: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el2, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_EL2 msr spsr_el3, x0 adr x0, el2_entry msr elr_el3, x0 eret el2_entry: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =SPSR_EL1 msr spsr_el2, x0 adr x0, el1_entry msr elr_el2, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson02/1/rs/src/config.txt ================================================ arm_control=0x200 kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson02/1/rs/src/kernel.c ================================================ #include "mini_uart.h" #include "printf.h" #include "utils.h" void kernel_main(void) { uart_init(); init_printf(0, putc); int el = get_el(); printf("Exception level: %d \r\n", el); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson02/1/rs/src/linker.ld ================================================ SECTIONS { . = 0x80000; .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson02/1/rs/src/mini_uart.c ================================================ #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" #include "utils.h" void uart_send(char c) { while (1) { if (get32(AUX_MU_LSR_REG) & 0x20) break; } put32(AUX_MU_IO_REG, c); } char uart_recv(void) { while (1) { if (get32(AUX_MU_LSR_REG) & 0x01) break; } return (get32(AUX_MU_IO_REG) & 0xFF); } void uart_send_string(char *str) { for (int i = 0; str[i] != '\0'; i++) { uart_send((char)str[i]); } } void uart_init(void) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7 << 12); // clean gpio14 selector |= 2 << 12; // set alt5 for gpio14 selector &= ~(7 << 15); // clean gpio15 selector |= 2 << 15; // set alt5 for gpio15 put32(GPFSEL1, selector); put32(GPPUD, 0); delay(150); put32(GPPUDCLK0, (1 << 14) | (1 << 15)); delay(150); put32(GPPUDCLK0, 0); put32(AUX_ENABLES, 1); // Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG, 0); // Disable auto flow control and disable receiver // and transmitter (for now) put32(AUX_MU_IER_REG, 0); // Disable receive and transmit interrupts put32(AUX_MU_LCR_REG, 3); // Enable 8 bit mode put32(AUX_MU_MCR_REG, 0); // Set RTS line to be always high put32(AUX_MU_BAUD_REG, 270); // Set baud rate to 115200 put32(AUX_MU_CNTL_REG, 3); // Finally, enable transmitter and receiver } // This function is required by printf function void putc(void *p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson02/1/rs/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson02/1/rs/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf)(void *, char); static putcf stdout_putf; static void *stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc, char *bf) { int n = 0; unsigned int d = 1; while (num / d >= base) d *= base; while (d != 0) { int dgt = num / d; num %= d; d /= base; if (n || dgt > 0 || d == 0) { *bf++ = dgt + (dgt < 10 ? '0' : (uc ? 'A' : 'a') - 10); ++n; } } *bf = 0; } static void li2a(long num, char *bf) { if (num < 0) { num = -num; *bf++ = '-'; } uli2a(num, 10, 0, bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc, char *bf) { int n = 0; unsigned int d = 1; while (num / d >= base) d *= base; while (d != 0) { int dgt = num / d; num %= d; d /= base; if (n || dgt > 0 || d == 0) { *bf++ = dgt + (dgt < 10 ? '0' : (uc ? 'A' : 'a') - 10); ++n; } } *bf = 0; } static void i2a(int num, char *bf) { if (num < 0) { num = -num; *bf++ = '-'; } ui2a(num, 10, 0, bf); } static int a2d(char ch) { if (ch >= '0' && ch <= '9') return ch - '0'; else if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10; else if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10; else return -1; } static char a2i(char ch, char **src, int base, int *nump) { char *p = *src; int num = 0; int digit; while ((digit = a2d(ch)) >= 0) { if (digit > base) break; num = num * base + digit; ch = *p++; } *src = p; *nump = num; return ch; } static void putchw(void *putp, putcf putf, int n, char z, char *bf) { char fc = z ? '0' : ' '; char ch; char *p = bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp, fc); while ((ch = *bf++)) putf(putp, ch); } void tfp_format(void *putp, putcf putf, char *fmt, va_list va) { char bf[12]; char ch; while ((ch = *(fmt++))) { if (ch != '%') putf(putp, ch); else { char lz = 0; #ifdef PRINTF_LONG_SUPPORT char lng = 0; #endif int w = 0; ch = *(fmt++); if (ch == '0') { ch = *(fmt++); lz = 1; } if (ch >= '0' && ch <= '9') { ch = a2i(ch, &fmt, 10, &w); } #ifdef PRINTF_LONG_SUPPORT if (ch == 'l') { ch = *(fmt++); lng = 1; } #endif switch (ch) { case 0: goto abort; case 'u': { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int), 10, 0, bf); else #endif ui2a(va_arg(va, unsigned int), 10, 0, bf); putchw(putp, putf, w, lz, bf); break; } case 'd': { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int), bf); else #endif i2a(va_arg(va, int), bf); putchw(putp, putf, w, lz, bf); break; } case 'x': case 'X': #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int), 16, (ch == 'X'), bf); else #endif ui2a(va_arg(va, unsigned int), 16, (ch == 'X'), bf); putchw(putp, putf, w, lz, bf); break; case 'c': putf(putp, (char)(va_arg(va, int))); break; case 's': putchw(putp, putf, w, 0, va_arg(va, char *)); break; case '%': putf(putp, ch); default: break; } } } abort:; } void init_printf(void *putp, void (*putf)(void *, char)) { stdout_putf = putf; stdout_putp = putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va, fmt); tfp_format(stdout_putp, stdout_putf, fmt, va); va_end(va); } static void putcp(void *p, char c) { *(*((char **)p))++ = c; } void tfp_sprintf(char *s, char *fmt, ...) { va_list va; va_start(va, fmt); tfp_format(&s, putcp, fmt, va); putcp(&s, 0); va_end(va); } ================================================ FILE: exercises/lesson02/1/rs/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1, [x0] ret .globl get32 get32: ldr w0, [x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson02/1/zjd0112/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson02/1/zjd0112/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson02/1/zjd0112/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson02/1/zjd0112/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR1_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR1_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR1_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR1_I_CACHE_DISABLED (0 << 12) #define SCTLR1_D_CACHE_DISABLED (0 << 2) #define SCTLR1_MMU_DISABLED (0 << 0) #define SCTLR1_MMU_ENABLED (1 << 0) #define SCTLR1_VALUE_MMU_DISABLED (SCTLR1_RESERVED | SCTLR1_EE_LITTLE_ENDIAN | SCTLR1_I_CACHE_DISABLED | SCTLR1_D_CACHE_DISABLED | SCTLR1_MMU_DISABLED) // *************************************** // SCTLR_EL2, System Control Register (EL2), Page 2665 of AArch64-Reference-Manual. // *************************************** #define SCTLR2_RESERVED (3 << 28) | (3 << 22) | (1 << 18) | (1 << 16) | (1 << 11) | (1 << 4) #define SCTLR2_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR2_I_CACHE_DISABLED (0 << 12) #define SCTLR2_D_CACHE_DISABLED (0 << 2) #define SCTLR2_MMU_DISABLED (0 << 0) #define SCTLR2_MMU_ENABLED (1 << 0) #define SCTLR2_VALUE_MMU_DISABLED (SCTLR2_RESERVED | SCTLR2_EE_LITTLE_ENDIAN | SCTLR2_I_CACHE_DISABLED | SCTLR2_D_CACHE_DISABLED | SCTLR2_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL_EL3 (7 << 6) #define SPSR_EL2h (9 << 0) #define SPSR_VALUE_EL3 (SPSR_MASK_ALL_EL3 | SPSR_EL2h) // *************************************** // SPSR_EL2, Saved Program Status Register (EL2) Page 383 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL_EL2 (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE_EL2 (SPSR_MASK_ALL_EL2 | SPSR_EL1h) #endif ================================================ FILE: exercises/lesson02/1/zjd0112/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson02/1/zjd0112/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson02/1/zjd0112/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson02/1/zjd0112/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson02/1/zjd0112/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson02/1/zjd0112/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson02/1/zjd0112/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson02/1/zjd0112/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR2_VALUE_MMU_DISABLED msr sctlr_el2, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE_EL3 msr spsr_el3, x0 adr x0, el2_entry msr elr_el3, x0 eret el2_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_el2 ldr x0, =SCTLR1_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =SPSR_VALUE_EL2 msr spsr_el2, x0 adr x0, el1_entry msr elr_el2, x0 eret el1_entry: mov sp, #LOW_MEMORY bl kernel_el1 b proc_hang // should never come here ================================================ FILE: exercises/lesson02/1/zjd0112/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson02/1/zjd0112/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "mini_uart.h" void kernel_el2(void) { uart_init(); init_printf(0, putc); int el = get_el(); printf("Exception level: %d \r\n", el); } void kernel_el1(void) { int el = get_el(); printf("Exception level: %d \r\n", el); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson02/1/zjd0112/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson02/1/zjd0112/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson02/1/zjd0112/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson02/1/zjd0112/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson02/1/zjd0112/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson02/2/H-4ND-H/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson02/2/H-4ND-H/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson02/2/H-4ND-H/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson02/2/H-4ND-H/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) // *************************************** // CPACR_EL1, Architectural Feature Access Control Register (EL1) Page 2411 of AArch64-Reference-Manual. // *************************************** #define CPACR_FP_SIMD_NO_TRAP (3 << 20) #define CPACR_VALUE CPACR_FP_SIMD_NO_TRAP #endif ================================================ FILE: exercises/lesson02/2/H-4ND-H/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson02/2/H-4ND-H/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson02/2/H-4ND-H/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson02/2/H-4ND-H/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson02/2/H-4ND-H/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson02/2/H-4ND-H/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson02/2/H-4ND-H/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson02/2/H-4ND-H/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 ldr x0, =CPACR_VALUE msr cpacr_el1, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson02/2/H-4ND-H/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson02/2/H-4ND-H/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "mini_uart.h" void kernel_main(void) { uart_init(); init_printf(0, putc); int el = get_el(); printf("Exception level: %d \r\n", el); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson02/2/H-4ND-H/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson02/2/H-4ND-H/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson02/2/H-4ND-H/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson02/2/H-4ND-H/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson02/2/H-4ND-H/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson02/2/avenito/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson02/2/avenito/README.md ================================================ # Option "-mgeneral-regs-only" I couldn't get the difference between compiling with "-mgeneral-regs-only" and without it. The disassembled code with the parameter is in "dis_with.txt", and without "dis_without.txt". Used command to disassembly: "aarch64-linux-gnu-objdump -d build/*.o > file.txt". Used command to compare the two files: "diff -s dis_with.txt dis_without.txt", and output "Files dis_with.txt and dis_without.txt are identical". ================================================ FILE: exercises/lesson02/2/avenito/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson02/2/avenito/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson02/2/avenito/dis_with.txt ================================================ build/boot_s.o: file format elf64-littleaarch64 Disassembly of section .text.boot: 0000000000000000 <_start>: 0: d53800a0 mrs x0, mpidr_el1 4: 92401c00 and x0, x0, #0xff 8: b4000060 cbz x0, 14 c: 14000001 b 10 0000000000000010 : 10: 14000000 b 10 0000000000000014 : 14: 58000260 ldr x0, 60 18: d5181000 msr sctlr_el1, x0 1c: 58000260 ldr x0, 68 20: d51c1100 msr hcr_el2, x0 24: 58000260 ldr x0, 70 28: d51e1100 msr scr_el3, x0 2c: 58000260 ldr x0, 78 30: d51e4000 msr spsr_el3, x0 34: 10000060 adr x0, 40 38: d51e4020 msr elr_el3, x0 3c: d69f03e0 eret 0000000000000040 : 40: 10000000 adr x0, 0 44: 10000001 adr x1, 0 48: cb000021 sub x1, x1, x0 4c: 94000000 bl 0 50: b26a03ff mov sp, #0x400000 // #4194304 54: 94000000 bl 0 58: 17ffffee b 10 5c: 00000000 .word 0x00000000 60: 30d00800 .word 0x30d00800 64: 00000000 .word 0x00000000 68: 80000000 .word 0x80000000 6c: 00000000 .word 0x00000000 70: 00000431 .word 0x00000431 74: 00000000 .word 0x00000000 78: 000001c5 .word 0x000001c5 7c: 00000000 .word 0x00000000 build/kernel_c.o: file format elf64-littleaarch64 Disassembly of section .text: 0000000000000000 : 0: a9be7bfd stp x29, x30, [sp,#-32]! 4: 910003fd mov x29, sp 8: 94000000 bl 0 c: 90000000 adrp x0, 0 10: 91000000 add x0, x0, #0x0 14: aa0003e1 mov x1, x0 18: d2800000 mov x0, #0x0 // #0 1c: 94000000 bl 0 20: 94000000 bl 0 24: b9001fa0 str w0, [x29,#28] 28: 90000000 adrp x0, 0 2c: 91000000 add x0, x0, #0x0 30: b9401fa1 ldr w1, [x29,#28] 34: 94000000 bl 0 38: 94000000 bl 0 3c: 53001c00 uxtb w0, w0 40: 94000000 bl 0 44: 17fffffd b 38 build/mini_uart_c.o: file format elf64-littleaarch64 Disassembly of section .text: 0000000000000000 : 0: a9be7bfd stp x29, x30, [sp,#-32]! 4: 910003fd mov x29, sp 8: 39007fa0 strb w0, [x29,#31] c: d28a0a80 mov x0, #0x5054 // #20564 10: f2a7e420 movk x0, #0x3f21, lsl #16 14: 94000000 bl 0 18: 121b0000 and w0, w0, #0x20 1c: 7100001f cmp w0, #0x0 20: 54000041 b.ne 28 24: 17fffffa b c 28: d503201f nop 2c: 39407fa0 ldrb w0, [x29,#31] 30: 2a0003e1 mov w1, w0 34: d28a0800 mov x0, #0x5040 // #20544 38: f2a7e420 movk x0, #0x3f21, lsl #16 3c: 94000000 bl 0 40: d503201f nop 44: a8c27bfd ldp x29, x30, [sp],#32 48: d65f03c0 ret 000000000000004c : 4c: a9bf7bfd stp x29, x30, [sp,#-16]! 50: 910003fd mov x29, sp 54: d28a0a80 mov x0, #0x5054 // #20564 58: f2a7e420 movk x0, #0x3f21, lsl #16 5c: 94000000 bl 0 60: 12000000 and w0, w0, #0x1 64: 7100001f cmp w0, #0x0 68: 54000041 b.ne 70 6c: 17fffffa b 54 70: d503201f nop 74: d28a0800 mov x0, #0x5040 // #20544 78: f2a7e420 movk x0, #0x3f21, lsl #16 7c: 94000000 bl 0 80: 53001c00 uxtb w0, w0 84: a8c17bfd ldp x29, x30, [sp],#16 88: d65f03c0 ret 000000000000008c : 8c: a9bd7bfd stp x29, x30, [sp,#-48]! 90: 910003fd mov x29, sp 94: f9000fa0 str x0, [x29,#24] 98: b9002fbf str wzr, [x29,#44] 9c: 14000009 b c0 a0: b9802fa0 ldrsw x0, [x29,#44] a4: f9400fa1 ldr x1, [x29,#24] a8: 8b000020 add x0, x1, x0 ac: 39400000 ldrb w0, [x0] b0: 94000000 bl 0 b4: b9402fa0 ldr w0, [x29,#44] b8: 11000400 add w0, w0, #0x1 bc: b9002fa0 str w0, [x29,#44] c0: b9802fa0 ldrsw x0, [x29,#44] c4: f9400fa1 ldr x1, [x29,#24] c8: 8b000020 add x0, x1, x0 cc: 39400000 ldrb w0, [x0] d0: 7100001f cmp w0, #0x0 d4: 54fffe61 b.ne a0 d8: d503201f nop dc: a8c37bfd ldp x29, x30, [sp],#48 e0: d65f03c0 ret 00000000000000e4 : e4: a9be7bfd stp x29, x30, [sp,#-32]! e8: 910003fd mov x29, sp ec: d2800080 mov x0, #0x4 // #4 f0: f2a7e400 movk x0, #0x3f20, lsl #16 f4: 94000000 bl 0 f8: b9001fa0 str w0, [x29,#28] fc: b9401fa0 ldr w0, [x29,#28] 100: 12117000 and w0, w0, #0xffff8fff 104: b9001fa0 str w0, [x29,#28] 108: b9401fa0 ldr w0, [x29,#28] 10c: 32130000 orr w0, w0, #0x2000 110: b9001fa0 str w0, [x29,#28] 114: b9401fa0 ldr w0, [x29,#28] 118: 120e7000 and w0, w0, #0xfffc7fff 11c: b9001fa0 str w0, [x29,#28] 120: b9401fa0 ldr w0, [x29,#28] 124: 32100000 orr w0, w0, #0x10000 128: b9001fa0 str w0, [x29,#28] 12c: b9401fa1 ldr w1, [x29,#28] 130: d2800080 mov x0, #0x4 // #4 134: f2a7e400 movk x0, #0x3f20, lsl #16 138: 94000000 bl 0 13c: 52800001 mov w1, #0x0 // #0 140: d2801280 mov x0, #0x94 // #148 144: f2a7e400 movk x0, #0x3f20, lsl #16 148: 94000000 bl 0 14c: d28012c0 mov x0, #0x96 // #150 150: 94000000 bl 0 154: 52980001 mov w1, #0xc000 // #49152 158: d2801300 mov x0, #0x98 // #152 15c: f2a7e400 movk x0, #0x3f20, lsl #16 160: 94000000 bl 0 164: d28012c0 mov x0, #0x96 // #150 168: 94000000 bl 0 16c: 52800001 mov w1, #0x0 // #0 170: d2801300 mov x0, #0x98 // #152 174: f2a7e400 movk x0, #0x3f20, lsl #16 178: 94000000 bl 0 17c: 52800021 mov w1, #0x1 // #1 180: d28a0080 mov x0, #0x5004 // #20484 184: f2a7e420 movk x0, #0x3f21, lsl #16 188: 94000000 bl 0 18c: 52800001 mov w1, #0x0 // #0 190: d28a0c00 mov x0, #0x5060 // #20576 194: f2a7e420 movk x0, #0x3f21, lsl #16 198: 94000000 bl 0 19c: 52800001 mov w1, #0x0 // #0 1a0: d28a0880 mov x0, #0x5044 // #20548 1a4: f2a7e420 movk x0, #0x3f21, lsl #16 1a8: 94000000 bl 0 1ac: 52800061 mov w1, #0x3 // #3 1b0: d28a0980 mov x0, #0x504c // #20556 1b4: f2a7e420 movk x0, #0x3f21, lsl #16 1b8: 94000000 bl 0 1bc: 52800001 mov w1, #0x0 // #0 1c0: d28a0a00 mov x0, #0x5050 // #20560 1c4: f2a7e420 movk x0, #0x3f21, lsl #16 1c8: 94000000 bl 0 1cc: 528021c1 mov w1, #0x10e // #270 1d0: d28a0d00 mov x0, #0x5068 // #20584 1d4: f2a7e420 movk x0, #0x3f21, lsl #16 1d8: 94000000 bl 0 1dc: 52800061 mov w1, #0x3 // #3 1e0: d28a0c00 mov x0, #0x5060 // #20576 1e4: f2a7e420 movk x0, #0x3f21, lsl #16 1e8: 94000000 bl 0 1ec: d503201f nop 1f0: a8c27bfd ldp x29, x30, [sp],#32 1f4: d65f03c0 ret 00000000000001f8 : 1f8: a9be7bfd stp x29, x30, [sp,#-32]! 1fc: 910003fd mov x29, sp 200: f9000fa0 str x0, [x29,#24] 204: 39005fa1 strb w1, [x29,#23] 208: 39405fa0 ldrb w0, [x29,#23] 20c: 94000000 bl 0 210: d503201f nop 214: a8c27bfd ldp x29, x30, [sp],#32 218: d65f03c0 ret build/mm_s.o: file format elf64-littleaarch64 Disassembly of section .text: 0000000000000000 : 0: f800841f str xzr, [x0],#8 4: f1002021 subs x1, x1, #0x8 8: 5400000c b.gt 0 c: d65f03c0 ret build/printf_c.o: file format elf64-littleaarch64 Disassembly of section .text: 0000000000000000 : 0: d100c3ff sub sp, sp, #0x30 4: b9001fe0 str w0, [sp,#28] 8: b9001be1 str w1, [sp,#24] c: b90017e2 str w2, [sp,#20] 10: f90007e3 str x3, [sp,#8] 14: b9002fff str wzr, [sp,#44] 18: 52800020 mov w0, #0x1 // #1 1c: b9002be0 str w0, [sp,#40] 20: 14000005 b 34 24: b9402be1 ldr w1, [sp,#40] 28: b9401be0 ldr w0, [sp,#24] 2c: 1b007c20 mul w0, w1, w0 30: b9002be0 str w0, [sp,#40] 34: b9401fe1 ldr w1, [sp,#28] 38: b9402be0 ldr w0, [sp,#40] 3c: 1ac00821 udiv w1, w1, w0 40: b9401be0 ldr w0, [sp,#24] 44: 6b00003f cmp w1, w0 48: 54fffee2 b.cs 24 4c: 1400002f b 108 50: b9401fe1 ldr w1, [sp,#28] 54: b9402be0 ldr w0, [sp,#40] 58: 1ac00820 udiv w0, w1, w0 5c: b90027e0 str w0, [sp,#36] 60: b9401fe0 ldr w0, [sp,#28] 64: b9402be1 ldr w1, [sp,#40] 68: 1ac10802 udiv w2, w0, w1 6c: b9402be1 ldr w1, [sp,#40] 70: 1b017c41 mul w1, w2, w1 74: 4b010000 sub w0, w0, w1 78: b9001fe0 str w0, [sp,#28] 7c: b9402be1 ldr w1, [sp,#40] 80: b9401be0 ldr w0, [sp,#24] 84: 1ac00820 udiv w0, w1, w0 88: b9002be0 str w0, [sp,#40] 8c: b9402fe0 ldr w0, [sp,#44] 90: 7100001f cmp w0, #0x0 94: 540000e1 b.ne b0 98: b94027e0 ldr w0, [sp,#36] 9c: 7100001f cmp w0, #0x0 a0: 5400008c b.gt b0 a4: b9402be0 ldr w0, [sp,#40] a8: 7100001f cmp w0, #0x0 ac: 540002e1 b.ne 108 b0: f94007e1 ldr x1, [sp,#8] b4: 91000420 add x0, x1, #0x1 b8: f90007e0 str x0, [sp,#8] bc: b94027e0 ldr w0, [sp,#36] c0: 7100241f cmp w0, #0x9 c4: 5400010d b.le e4 c8: b94017e0 ldr w0, [sp,#20] cc: 7100001f cmp w0, #0x0 d0: 54000060 b.eq dc d4: 528006e0 mov w0, #0x37 // #55 d8: 14000004 b e8 dc: 52800ae0 mov w0, #0x57 // #87 e0: 14000002 b e8 e4: 52800600 mov w0, #0x30 // #48 e8: b94027e2 ldr w2, [sp,#36] ec: 53001c42 uxtb w2, w2 f0: 0b020000 add w0, w0, w2 f4: 53001c00 uxtb w0, w0 f8: 39000020 strb w0, [x1] fc: b9402fe0 ldr w0, [sp,#44] 100: 11000400 add w0, w0, #0x1 104: b9002fe0 str w0, [sp,#44] 108: b9402be0 ldr w0, [sp,#40] 10c: 7100001f cmp w0, #0x0 110: 54fffa01 b.ne 50 114: f94007e0 ldr x0, [sp,#8] 118: 3900001f strb wzr, [x0] 11c: d503201f nop 120: 9100c3ff add sp, sp, #0x30 124: d65f03c0 ret 0000000000000128 : 128: a9be7bfd stp x29, x30, [sp,#-32]! 12c: 910003fd mov x29, sp 130: b9001fa0 str w0, [x29,#28] 134: f9000ba1 str x1, [x29,#16] 138: b9401fa0 ldr w0, [x29,#28] 13c: 7100001f cmp w0, #0x0 140: 5400012a b.ge 164 144: b9401fa0 ldr w0, [x29,#28] 148: 4b0003e0 neg w0, w0 14c: b9001fa0 str w0, [x29,#28] 150: f9400ba0 ldr x0, [x29,#16] 154: 91000401 add x1, x0, #0x1 158: f9000ba1 str x1, [x29,#16] 15c: 528005a1 mov w1, #0x2d // #45 160: 39000001 strb w1, [x0] 164: b9401fa0 ldr w0, [x29,#28] 168: f9400ba3 ldr x3, [x29,#16] 16c: 52800002 mov w2, #0x0 // #0 170: 52800141 mov w1, #0xa // #10 174: 97ffffa3 bl 0 178: d503201f nop 17c: a8c27bfd ldp x29, x30, [sp],#32 180: d65f03c0 ret 0000000000000184 : 184: d10043ff sub sp, sp, #0x10 188: 39003fe0 strb w0, [sp,#15] 18c: 39403fe0 ldrb w0, [sp,#15] 190: 7100bc1f cmp w0, #0x2f 194: 540000e9 b.ls 1b0 198: 39403fe0 ldrb w0, [sp,#15] 19c: 7100e41f cmp w0, #0x39 1a0: 54000088 b.hi 1b0 1a4: 39403fe0 ldrb w0, [sp,#15] 1a8: 5100c000 sub w0, w0, #0x30 1ac: 14000014 b 1fc 1b0: 39403fe0 ldrb w0, [sp,#15] 1b4: 7101801f cmp w0, #0x60 1b8: 540000e9 b.ls 1d4 1bc: 39403fe0 ldrb w0, [sp,#15] 1c0: 7101981f cmp w0, #0x66 1c4: 54000088 b.hi 1d4 1c8: 39403fe0 ldrb w0, [sp,#15] 1cc: 51015c00 sub w0, w0, #0x57 1d0: 1400000b b 1fc 1d4: 39403fe0 ldrb w0, [sp,#15] 1d8: 7101001f cmp w0, #0x40 1dc: 540000e9 b.ls 1f8 1e0: 39403fe0 ldrb w0, [sp,#15] 1e4: 7101181f cmp w0, #0x46 1e8: 54000088 b.hi 1f8 1ec: 39403fe0 ldrb w0, [sp,#15] 1f0: 5100dc00 sub w0, w0, #0x37 1f4: 14000002 b 1fc 1f8: 12800000 mov w0, #0xffffffff // #-1 1fc: 910043ff add sp, sp, #0x10 200: d65f03c0 ret 0000000000000204 : 204: a9bc7bfd stp x29, x30, [sp,#-64]! 208: 910003fd mov x29, sp 20c: 3900bfa0 strb w0, [x29,#47] 210: f90013a1 str x1, [x29,#32] 214: b9002ba2 str w2, [x29,#40] 218: f9000fa3 str x3, [x29,#24] 21c: f94013a0 ldr x0, [x29,#32] 220: f9400000 ldr x0, [x0] 224: f9001fa0 str x0, [x29,#56] 228: b90037bf str wzr, [x29,#52] 22c: 14000010 b 26c 230: b94033a1 ldr w1, [x29,#48] 234: b9402ba0 ldr w0, [x29,#40] 238: 6b00003f cmp w1, w0 23c: 5400026c b.gt 288 240: b94037a1 ldr w1, [x29,#52] 244: b9402ba0 ldr w0, [x29,#40] 248: 1b007c21 mul w1, w1, w0 24c: b94033a0 ldr w0, [x29,#48] 250: 0b000020 add w0, w1, w0 254: b90037a0 str w0, [x29,#52] 258: f9401fa0 ldr x0, [x29,#56] 25c: 91000401 add x1, x0, #0x1 260: f9001fa1 str x1, [x29,#56] 264: 39400000 ldrb w0, [x0] 268: 3900bfa0 strb w0, [x29,#47] 26c: 3940bfa0 ldrb w0, [x29,#47] 270: 97ffffc5 bl 184 274: b90033a0 str w0, [x29,#48] 278: b94033a0 ldr w0, [x29,#48] 27c: 7100001f cmp w0, #0x0 280: 54fffd8a b.ge 230 284: 14000002 b 28c 288: d503201f nop 28c: f94013a0 ldr x0, [x29,#32] 290: f9401fa1 ldr x1, [x29,#56] 294: f9000001 str x1, [x0] 298: f9400fa0 ldr x0, [x29,#24] 29c: b94037a1 ldr w1, [x29,#52] 2a0: b9000001 str w1, [x0] 2a4: 3940bfa0 ldrb w0, [x29,#47] 2a8: a8c47bfd ldp x29, x30, [sp],#64 2ac: d65f03c0 ret 00000000000002b0 : 2b0: a9bc7bfd stp x29, x30, [sp,#-64]! 2b4: 910003fd mov x29, sp 2b8: f90017a0 str x0, [x29,#40] 2bc: f90013a1 str x1, [x29,#32] 2c0: b9001fa2 str w2, [x29,#28] 2c4: 39006fa3 strb w3, [x29,#27] 2c8: f9000ba4 str x4, [x29,#16] 2cc: 39406fa0 ldrb w0, [x29,#27] 2d0: 7100001f cmp w0, #0x0 2d4: 54000060 b.eq 2e0 2d8: 52800600 mov w0, #0x30 // #48 2dc: 14000002 b 2e4 2e0: 52800400 mov w0, #0x20 // #32 2e4: 3900dfa0 strb w0, [x29,#55] 2e8: f9400ba0 ldr x0, [x29,#16] 2ec: f9001fa0 str x0, [x29,#56] 2f0: 14000004 b 300 2f4: b9401fa0 ldr w0, [x29,#28] 2f8: 51000400 sub w0, w0, #0x1 2fc: b9001fa0 str w0, [x29,#28] 300: f9401fa0 ldr x0, [x29,#56] 304: 91000401 add x1, x0, #0x1 308: f9001fa1 str x1, [x29,#56] 30c: 39400000 ldrb w0, [x0] 310: 7100001f cmp w0, #0x0 314: 54000120 b.eq 338 318: b9401fa0 ldr w0, [x29,#28] 31c: 7100001f cmp w0, #0x0 320: 54fffeac b.gt 2f4 324: 14000005 b 338 328: f94013a2 ldr x2, [x29,#32] 32c: 3940dfa1 ldrb w1, [x29,#55] 330: f94017a0 ldr x0, [x29,#40] 334: d63f0040 blr x2 338: b9401fa0 ldr w0, [x29,#28] 33c: 51000401 sub w1, w0, #0x1 340: b9001fa1 str w1, [x29,#28] 344: 7100001f cmp w0, #0x0 348: 54ffff0c b.gt 328 34c: 14000005 b 360 350: f94013a2 ldr x2, [x29,#32] 354: 3940dba1 ldrb w1, [x29,#54] 358: f94017a0 ldr x0, [x29,#40] 35c: d63f0040 blr x2 360: f9400ba0 ldr x0, [x29,#16] 364: 91000401 add x1, x0, #0x1 368: f9000ba1 str x1, [x29,#16] 36c: 39400000 ldrb w0, [x0] 370: 3900dba0 strb w0, [x29,#54] 374: 3940dba0 ldrb w0, [x29,#54] 378: 7100001f cmp w0, #0x0 37c: 54fffea1 b.ne 350 380: d503201f nop 384: a8c47bfd ldp x29, x30, [sp],#64 388: d65f03c0 ret 000000000000038c : 38c: a9ba7bfd stp x29, x30, [sp,#-96]! 390: 910003fd mov x29, sp 394: f9000bf3 str x19, [sp,#16] 398: f9001fa0 str x0, [x29,#56] 39c: f9001ba1 str x1, [x29,#48] 3a0: f90017a2 str x2, [x29,#40] 3a4: aa0303f3 mov x19, x3 3a8: 140000fd b 79c 3ac: 39417fa0 ldrb w0, [x29,#95] 3b0: 7100941f cmp w0, #0x25 3b4: 540000c0 b.eq 3cc 3b8: f9401ba2 ldr x2, [x29,#48] 3bc: 39417fa1 ldrb w1, [x29,#95] 3c0: f9401fa0 ldr x0, [x29,#56] 3c4: d63f0040 blr x2 3c8: 140000f5 b 79c 3cc: 39017bbf strb wzr, [x29,#94] 3d0: b9004fbf str wzr, [x29,#76] 3d4: f94017a0 ldr x0, [x29,#40] 3d8: 91000401 add x1, x0, #0x1 3dc: f90017a1 str x1, [x29,#40] 3e0: 39400000 ldrb w0, [x0] 3e4: 39017fa0 strb w0, [x29,#95] 3e8: 39417fa0 ldrb w0, [x29,#95] 3ec: 7100c01f cmp w0, #0x30 3f0: 54000101 b.ne 410 3f4: f94017a0 ldr x0, [x29,#40] 3f8: 91000401 add x1, x0, #0x1 3fc: f90017a1 str x1, [x29,#40] 400: 39400000 ldrb w0, [x0] 404: 39017fa0 strb w0, [x29,#95] 408: 52800020 mov w0, #0x1 // #1 40c: 39017ba0 strb w0, [x29,#94] 410: 39417fa0 ldrb w0, [x29,#95] 414: 7100bc1f cmp w0, #0x2f 418: 54000189 b.ls 448 41c: 39417fa0 ldrb w0, [x29,#95] 420: 7100e41f cmp w0, #0x39 424: 54000128 b.hi 448 428: 910133a1 add x1, x29, #0x4c 42c: 9100a3a0 add x0, x29, #0x28 430: aa0103e3 mov x3, x1 434: 52800142 mov w2, #0xa // #10 438: aa0003e1 mov x1, x0 43c: 39417fa0 ldrb w0, [x29,#95] 440: 97ffff71 bl 204 444: 39017fa0 strb w0, [x29,#95] 448: 39417fa0 ldrb w0, [x29,#95] 44c: 71018c1f cmp w0, #0x63 450: 540011c0 b.eq 688 454: 71018c1f cmp w0, #0x63 458: 5400010c b.gt 478 45c: 7100941f cmp w0, #0x25 460: 54001940 b.eq 788 464: 7101601f cmp w0, #0x58 468: 54000b60 b.eq 5d4 46c: 7100001f cmp w0, #0x0 470: 54001a80 b.eq 7c0 474: 140000c9 b 798 478: 7101cc1f cmp w0, #0x73 47c: 54001440 b.eq 704 480: 7101cc1f cmp w0, #0x73 484: 5400008c b.gt 494 488: 7101901f cmp w0, #0x64 48c: 540005c0 b.eq 544 490: 140000c2 b 798 494: 7101d41f cmp w0, #0x75 498: 54000080 b.eq 4a8 49c: 7101e01f cmp w0, #0x78 4a0: 540009a0 b.eq 5d4 4a4: 140000bd b 798 4a8: b9401a60 ldr w0, [x19,#24] 4ac: f9400261 ldr x1, [x19] 4b0: 7100001f cmp w0, #0x0 4b4: 540000eb b.lt 4d0 4b8: aa0103e0 mov x0, x1 4bc: 91002c00 add x0, x0, #0xb 4c0: 927df000 and x0, x0, #0xfffffffffffffff8 4c4: f9000260 str x0, [x19] 4c8: aa0103e0 mov x0, x1 4cc: 1400000f b 508 4d0: 11002002 add w2, w0, #0x8 4d4: b9001a62 str w2, [x19,#24] 4d8: b9401a62 ldr w2, [x19,#24] 4dc: 7100005f cmp w2, #0x0 4e0: 540000ed b.le 4fc 4e4: aa0103e0 mov x0, x1 4e8: 91002c00 add x0, x0, #0xb 4ec: 927df000 and x0, x0, #0xfffffffffffffff8 4f0: f9000260 str x0, [x19] 4f4: aa0103e0 mov x0, x1 4f8: 14000004 b 508 4fc: f9400661 ldr x1, [x19,#8] 500: 93407c00 sxtw x0, w0 504: 8b000020 add x0, x1, x0 508: b9400000 ldr w0, [x0] 50c: 910143a1 add x1, x29, #0x50 510: aa0103e3 mov x3, x1 514: 52800002 mov w2, #0x0 // #0 518: 52800141 mov w1, #0xa // #10 51c: 97fffeb9 bl 0 520: b9404fa0 ldr w0, [x29,#76] 524: 910143a1 add x1, x29, #0x50 528: aa0103e4 mov x4, x1 52c: 39417ba3 ldrb w3, [x29,#94] 530: 2a0003e2 mov w2, w0 534: f9401ba1 ldr x1, [x29,#48] 538: f9401fa0 ldr x0, [x29,#56] 53c: 97ffff5d bl 2b0 540: 14000097 b 79c 544: b9401a60 ldr w0, [x19,#24] 548: f9400261 ldr x1, [x19] 54c: 7100001f cmp w0, #0x0 550: 540000eb b.lt 56c 554: aa0103e0 mov x0, x1 558: 91002c00 add x0, x0, #0xb 55c: 927df000 and x0, x0, #0xfffffffffffffff8 560: f9000260 str x0, [x19] 564: aa0103e0 mov x0, x1 568: 1400000f b 5a4 56c: 11002002 add w2, w0, #0x8 570: b9001a62 str w2, [x19,#24] 574: b9401a62 ldr w2, [x19,#24] 578: 7100005f cmp w2, #0x0 57c: 540000ed b.le 598 580: aa0103e0 mov x0, x1 584: 91002c00 add x0, x0, #0xb 588: 927df000 and x0, x0, #0xfffffffffffffff8 58c: f9000260 str x0, [x19] 590: aa0103e0 mov x0, x1 594: 14000004 b 5a4 598: f9400661 ldr x1, [x19,#8] 59c: 93407c00 sxtw x0, w0 5a0: 8b000020 add x0, x1, x0 5a4: b9400000 ldr w0, [x0] 5a8: 910143a1 add x1, x29, #0x50 5ac: 97fffedf bl 128 5b0: b9404fa0 ldr w0, [x29,#76] 5b4: 910143a1 add x1, x29, #0x50 5b8: aa0103e4 mov x4, x1 5bc: 39417ba3 ldrb w3, [x29,#94] 5c0: 2a0003e2 mov w2, w0 5c4: f9401ba1 ldr x1, [x29,#48] 5c8: f9401fa0 ldr x0, [x29,#56] 5cc: 97ffff39 bl 2b0 5d0: 14000073 b 79c 5d4: b9401a60 ldr w0, [x19,#24] 5d8: f9400261 ldr x1, [x19] 5dc: 7100001f cmp w0, #0x0 5e0: 540000eb b.lt 5fc 5e4: aa0103e0 mov x0, x1 5e8: 91002c00 add x0, x0, #0xb 5ec: 927df000 and x0, x0, #0xfffffffffffffff8 5f0: f9000260 str x0, [x19] 5f4: aa0103e0 mov x0, x1 5f8: 1400000f b 634 5fc: 11002002 add w2, w0, #0x8 600: b9001a62 str w2, [x19,#24] 604: b9401a62 ldr w2, [x19,#24] 608: 7100005f cmp w2, #0x0 60c: 540000ed b.le 628 610: aa0103e0 mov x0, x1 614: 91002c00 add x0, x0, #0xb 618: 927df000 and x0, x0, #0xfffffffffffffff8 61c: f9000260 str x0, [x19] 620: aa0103e0 mov x0, x1 624: 14000004 b 634 628: f9400661 ldr x1, [x19,#8] 62c: 93407c00 sxtw x0, w0 630: 8b000020 add x0, x1, x0 634: b9400004 ldr w4, [x0] 638: 39417fa0 ldrb w0, [x29,#95] 63c: 7101601f cmp w0, #0x58 640: 1a9f17e0 cset w0, eq 644: 53001c00 uxtb w0, w0 648: 2a0003e1 mov w1, w0 64c: 910143a0 add x0, x29, #0x50 650: aa0003e3 mov x3, x0 654: 2a0103e2 mov w2, w1 658: 52800201 mov w1, #0x10 // #16 65c: 2a0403e0 mov w0, w4 660: 97fffe68 bl 0 664: b9404fa0 ldr w0, [x29,#76] 668: 910143a1 add x1, x29, #0x50 66c: aa0103e4 mov x4, x1 670: 39417ba3 ldrb w3, [x29,#94] 674: 2a0003e2 mov w2, w0 678: f9401ba1 ldr x1, [x29,#48] 67c: f9401fa0 ldr x0, [x29,#56] 680: 97ffff0c bl 2b0 684: 14000046 b 79c 688: b9401a60 ldr w0, [x19,#24] 68c: f9400261 ldr x1, [x19] 690: 7100001f cmp w0, #0x0 694: 540000eb b.lt 6b0 698: aa0103e0 mov x0, x1 69c: 91002c00 add x0, x0, #0xb 6a0: 927df000 and x0, x0, #0xfffffffffffffff8 6a4: f9000260 str x0, [x19] 6a8: aa0103e0 mov x0, x1 6ac: 1400000f b 6e8 6b0: 11002002 add w2, w0, #0x8 6b4: b9001a62 str w2, [x19,#24] 6b8: b9401a62 ldr w2, [x19,#24] 6bc: 7100005f cmp w2, #0x0 6c0: 540000ed b.le 6dc 6c4: aa0103e0 mov x0, x1 6c8: 91002c00 add x0, x0, #0xb 6cc: 927df000 and x0, x0, #0xfffffffffffffff8 6d0: f9000260 str x0, [x19] 6d4: aa0103e0 mov x0, x1 6d8: 14000004 b 6e8 6dc: f9400661 ldr x1, [x19,#8] 6e0: 93407c00 sxtw x0, w0 6e4: 8b000020 add x0, x1, x0 6e8: b9400000 ldr w0, [x0] 6ec: 53001c00 uxtb w0, w0 6f0: f9401ba2 ldr x2, [x29,#48] 6f4: 2a0003e1 mov w1, w0 6f8: f9401fa0 ldr x0, [x29,#56] 6fc: d63f0040 blr x2 700: 14000027 b 79c 704: b9404fa5 ldr w5, [x29,#76] 708: b9401a60 ldr w0, [x19,#24] 70c: f9400261 ldr x1, [x19] 710: 7100001f cmp w0, #0x0 714: 540000eb b.lt 730 718: aa0103e0 mov x0, x1 71c: 91003c00 add x0, x0, #0xf 720: 927df000 and x0, x0, #0xfffffffffffffff8 724: f9000260 str x0, [x19] 728: aa0103e0 mov x0, x1 72c: 1400000f b 768 730: 11002002 add w2, w0, #0x8 734: b9001a62 str w2, [x19,#24] 738: b9401a62 ldr w2, [x19,#24] 73c: 7100005f cmp w2, #0x0 740: 540000ed b.le 75c 744: aa0103e0 mov x0, x1 748: 91003c00 add x0, x0, #0xf 74c: 927df000 and x0, x0, #0xfffffffffffffff8 750: f9000260 str x0, [x19] 754: aa0103e0 mov x0, x1 758: 14000004 b 768 75c: f9400661 ldr x1, [x19,#8] 760: 93407c00 sxtw x0, w0 764: 8b000020 add x0, x1, x0 768: f9400000 ldr x0, [x0] 76c: aa0003e4 mov x4, x0 770: 52800003 mov w3, #0x0 // #0 774: 2a0503e2 mov w2, w5 778: f9401ba1 ldr x1, [x29,#48] 77c: f9401fa0 ldr x0, [x29,#56] 780: 97fffecc bl 2b0 784: 14000006 b 79c 788: f9401ba2 ldr x2, [x29,#48] 78c: 39417fa1 ldrb w1, [x29,#95] 790: f9401fa0 ldr x0, [x29,#56] 794: d63f0040 blr x2 798: d503201f nop 79c: f94017a0 ldr x0, [x29,#40] 7a0: 91000401 add x1, x0, #0x1 7a4: f90017a1 str x1, [x29,#40] 7a8: 39400000 ldrb w0, [x0] 7ac: 39017fa0 strb w0, [x29,#95] 7b0: 39417fa0 ldrb w0, [x29,#95] 7b4: 7100001f cmp w0, #0x0 7b8: 54ffdfa1 b.ne 3ac 7bc: 14000002 b 7c4 7c0: d503201f nop 7c4: d503201f nop 7c8: f9400bf3 ldr x19, [sp,#16] 7cc: a8c67bfd ldp x29, x30, [sp],#96 7d0: d65f03c0 ret 00000000000007d4 : 7d4: d10043ff sub sp, sp, #0x10 7d8: f90007e0 str x0, [sp,#8] 7dc: f90003e1 str x1, [sp] 7e0: 90000000 adrp x0, 0 7e4: 91000000 add x0, x0, #0x0 7e8: f94003e1 ldr x1, [sp] 7ec: f9000001 str x1, [x0] 7f0: 90000000 adrp x0, 0 7f4: 91000000 add x0, x0, #0x0 7f8: f94007e1 ldr x1, [sp,#8] 7fc: f9000001 str x1, [x0] 800: d503201f nop 804: 910043ff add sp, sp, #0x10 808: d65f03c0 ret 000000000000080c : 80c: a9b67bfd stp x29, x30, [sp,#-160]! 810: 910003fd mov x29, sp 814: f9001fa0 str x0, [x29,#56] 818: f90037a1 str x1, [x29,#104] 81c: f9003ba2 str x2, [x29,#112] 820: f9003fa3 str x3, [x29,#120] 824: f90043a4 str x4, [x29,#128] 828: f90047a5 str x5, [x29,#136] 82c: f9004ba6 str x6, [x29,#144] 830: f9004fa7 str x7, [x29,#152] 834: 910283a0 add x0, x29, #0xa0 838: f90023a0 str x0, [x29,#64] 83c: 910283a0 add x0, x29, #0xa0 840: f90027a0 str x0, [x29,#72] 844: 910183a0 add x0, x29, #0x60 848: f9002ba0 str x0, [x29,#80] 84c: 128006e0 mov w0, #0xffffffc8 // #-56 850: b9005ba0 str w0, [x29,#88] 854: b9005fbf str wzr, [x29,#92] 858: 90000000 adrp x0, 0 85c: 91000000 add x0, x0, #0x0 860: f9400004 ldr x4, [x0] 864: 90000000 adrp x0, 0 868: 91000000 add x0, x0, #0x0 86c: f9400005 ldr x5, [x0] 870: 910043a2 add x2, x29, #0x10 874: 910103a3 add x3, x29, #0x40 878: a9400460 ldp x0, x1, [x3] 87c: a9000440 stp x0, x1, [x2] 880: a9410460 ldp x0, x1, [x3,#16] 884: a9010440 stp x0, x1, [x2,#16] 888: 910043a0 add x0, x29, #0x10 88c: aa0003e3 mov x3, x0 890: f9401fa2 ldr x2, [x29,#56] 894: aa0503e1 mov x1, x5 898: aa0403e0 mov x0, x4 89c: 94000000 bl 38c 8a0: d503201f nop 8a4: a8ca7bfd ldp x29, x30, [sp],#160 8a8: d65f03c0 ret 00000000000008ac : 8ac: d10043ff sub sp, sp, #0x10 8b0: f90007e0 str x0, [sp,#8] 8b4: 39001fe1 strb w1, [sp,#7] 8b8: f94007e0 ldr x0, [sp,#8] 8bc: f9400000 ldr x0, [x0] 8c0: 91000402 add x2, x0, #0x1 8c4: f94007e1 ldr x1, [sp,#8] 8c8: f9000022 str x2, [x1] 8cc: 39401fe1 ldrb w1, [sp,#7] 8d0: 39000001 strb w1, [x0] 8d4: d503201f nop 8d8: 910043ff add sp, sp, #0x10 8dc: d65f03c0 ret 00000000000008e0 : 8e0: a9b77bfd stp x29, x30, [sp,#-144]! 8e4: 910003fd mov x29, sp 8e8: f9001fa0 str x0, [x29,#56] 8ec: f9001ba1 str x1, [x29,#48] 8f0: f90033a2 str x2, [x29,#96] 8f4: f90037a3 str x3, [x29,#104] 8f8: f9003ba4 str x4, [x29,#112] 8fc: f9003fa5 str x5, [x29,#120] 900: f90043a6 str x6, [x29,#128] 904: f90047a7 str x7, [x29,#136] 908: 910243a0 add x0, x29, #0x90 90c: f90023a0 str x0, [x29,#64] 910: 910243a0 add x0, x29, #0x90 914: f90027a0 str x0, [x29,#72] 918: 910183a0 add x0, x29, #0x60 91c: f9002ba0 str x0, [x29,#80] 920: 128005e0 mov w0, #0xffffffd0 // #-48 924: b9005ba0 str w0, [x29,#88] 928: b9005fbf str wzr, [x29,#92] 92c: 910043a2 add x2, x29, #0x10 930: 910103a3 add x3, x29, #0x40 934: a9400460 ldp x0, x1, [x3] 938: a9000440 stp x0, x1, [x2] 93c: a9410460 ldp x0, x1, [x3,#16] 940: a9010440 stp x0, x1, [x2,#16] 944: 910043a2 add x2, x29, #0x10 948: 90000000 adrp x0, 0 94c: 91000001 add x1, x0, #0x0 950: 9100e3a0 add x0, x29, #0x38 954: aa0203e3 mov x3, x2 958: f9401ba2 ldr x2, [x29,#48] 95c: 94000000 bl 38c 960: 9100e3a0 add x0, x29, #0x38 964: 52800001 mov w1, #0x0 // #0 968: 97ffffd1 bl 8ac 96c: d503201f nop 970: a8c97bfd ldp x29, x30, [sp],#144 974: d65f03c0 ret build/uart_c.o: file format elf64-littleaarch64 Disassembly of section .text: 0000000000000000 : 0: a9be7bfd stp x29, x30, [sp,#-32]! 4: 910003fd mov x29, sp 8: 39007fa0 strb w0, [x29,#31] c: d503201f nop 10: d2820300 mov x0, #0x1018 // #4120 14: f2a7e400 movk x0, #0x3f20, lsl #16 18: 94000000 bl 0 1c: 121b0000 and w0, w0, #0x20 20: 7100001f cmp w0, #0x0 24: 54ffff61 b.ne 10 28: 39407fa0 ldrb w0, [x29,#31] 2c: 2a0003e1 mov w1, w0 30: d2820000 mov x0, #0x1000 // #4096 34: f2a7e400 movk x0, #0x3f20, lsl #16 38: 94000000 bl 0 3c: d503201f nop 40: a8c27bfd ldp x29, x30, [sp],#32 44: d65f03c0 ret 0000000000000048 : 48: a9bf7bfd stp x29, x30, [sp,#-16]! 4c: 910003fd mov x29, sp 50: d503201f nop 54: d2820300 mov x0, #0x1018 // #4120 58: f2a7e400 movk x0, #0x3f20, lsl #16 5c: 94000000 bl 0 60: 121c0000 and w0, w0, #0x10 64: 7100001f cmp w0, #0x0 68: 54ffff61 b.ne 54 6c: d2820000 mov x0, #0x1000 // #4096 70: f2a7e400 movk x0, #0x3f20, lsl #16 74: 94000000 bl 0 78: 53001c00 uxtb w0, w0 7c: a8c17bfd ldp x29, x30, [sp],#16 80: d65f03c0 ret 0000000000000084 : 84: a9bd7bfd stp x29, x30, [sp,#-48]! 88: 910003fd mov x29, sp 8c: f9000fa0 str x0, [x29,#24] 90: b9002fbf str wzr, [x29,#44] 94: 14000009 b b8 98: b9802fa0 ldrsw x0, [x29,#44] 9c: f9400fa1 ldr x1, [x29,#24] a0: 8b000020 add x0, x1, x0 a4: 39400000 ldrb w0, [x0] a8: 94000000 bl 0 ac: b9402fa0 ldr w0, [x29,#44] b0: 11000400 add w0, w0, #0x1 b4: b9002fa0 str w0, [x29,#44] b8: b9802fa0 ldrsw x0, [x29,#44] bc: f9400fa1 ldr x1, [x29,#24] c0: 8b000020 add x0, x1, x0 c4: 39400000 ldrb w0, [x0] c8: 7100001f cmp w0, #0x0 cc: 54fffe61 b.ne 98 d0: d503201f nop d4: a8c37bfd ldp x29, x30, [sp],#48 d8: d65f03c0 ret 00000000000000dc : dc: a9be7bfd stp x29, x30, [sp,#-32]! e0: 910003fd mov x29, sp e4: d2800080 mov x0, #0x4 // #4 e8: f2a7e400 movk x0, #0x3f20, lsl #16 ec: 94000000 bl 0 f0: b9001fa0 str w0, [x29,#28] f4: b9401fa0 ldr w0, [x29,#28] f8: 12117000 and w0, w0, #0xffff8fff fc: b9001fa0 str w0, [x29,#28] 100: b9401fa0 ldr w0, [x29,#28] 104: 32120000 orr w0, w0, #0x4000 108: b9001fa0 str w0, [x29,#28] 10c: b9401fa0 ldr w0, [x29,#28] 110: 120e7000 and w0, w0, #0xfffc7fff 114: b9001fa0 str w0, [x29,#28] 118: b9401fa0 ldr w0, [x29,#28] 11c: 320f0000 orr w0, w0, #0x20000 120: b9001fa0 str w0, [x29,#28] 124: b9401fa1 ldr w1, [x29,#28] 128: d2800080 mov x0, #0x4 // #4 12c: f2a7e400 movk x0, #0x3f20, lsl #16 130: 94000000 bl 0 134: 52800001 mov w1, #0x0 // #0 138: d2801280 mov x0, #0x94 // #148 13c: f2a7e400 movk x0, #0x3f20, lsl #16 140: 94000000 bl 0 144: d28012c0 mov x0, #0x96 // #150 148: 94000000 bl 0 14c: 52980001 mov w1, #0xc000 // #49152 150: d2801300 mov x0, #0x98 // #152 154: f2a7e400 movk x0, #0x3f20, lsl #16 158: 94000000 bl 0 15c: d28012c0 mov x0, #0x96 // #150 160: 94000000 bl 0 164: 52800001 mov w1, #0x0 // #0 168: d2801300 mov x0, #0x98 // #152 16c: f2a7e400 movk x0, #0x3f20, lsl #16 170: 94000000 bl 0 174: 52800001 mov w1, #0x0 // #0 178: d2820600 mov x0, #0x1030 // #4144 17c: f2a7e400 movk x0, #0x3f20, lsl #16 180: 94000000 bl 0 184: 52800341 mov w1, #0x1a // #26 188: d2820480 mov x0, #0x1024 // #4132 18c: f2a7e400 movk x0, #0x3f20, lsl #16 190: 94000000 bl 0 194: 52800061 mov w1, #0x3 // #3 198: d2820500 mov x0, #0x1028 // #4136 19c: f2a7e400 movk x0, #0x3f20, lsl #16 1a0: 94000000 bl 0 1a4: 52800c01 mov w1, #0x60 // #96 1a8: d2820580 mov x0, #0x102c // #4140 1ac: f2a7e400 movk x0, #0x3f20, lsl #16 1b0: 94000000 bl 0 1b4: 52806021 mov w1, #0x301 // #769 1b8: d2820600 mov x0, #0x1030 // #4144 1bc: f2a7e400 movk x0, #0x3f20, lsl #16 1c0: 94000000 bl 0 1c4: d503201f nop 1c8: a8c27bfd ldp x29, x30, [sp],#32 1cc: d65f03c0 ret build/utils_s.o: file format elf64-littleaarch64 Disassembly of section .text: 0000000000000000 : 0: d5384240 mrs x0, currentel 4: d342fc00 lsr x0, x0, #2 8: d65f03c0 ret 000000000000000c : c: b9000001 str w1, [x0] 10: d65f03c0 ret 0000000000000014 : 14: b9400000 ldr w0, [x0] 18: d65f03c0 ret 000000000000001c : 1c: f1000400 subs x0, x0, #0x1 20: 54000001 b.ne 1c 24: d65f03c0 ret ================================================ FILE: exercises/lesson02/2/avenito/dis_without.txt ================================================ build/boot_s.o: file format elf64-littleaarch64 Disassembly of section .text.boot: 0000000000000000 <_start>: 0: d53800a0 mrs x0, mpidr_el1 4: 92401c00 and x0, x0, #0xff 8: b4000060 cbz x0, 14 c: 14000001 b 10 0000000000000010 : 10: 14000000 b 10 0000000000000014 : 14: 58000260 ldr x0, 60 18: d5181000 msr sctlr_el1, x0 1c: 58000260 ldr x0, 68 20: d51c1100 msr hcr_el2, x0 24: 58000260 ldr x0, 70 28: d51e1100 msr scr_el3, x0 2c: 58000260 ldr x0, 78 30: d51e4000 msr spsr_el3, x0 34: 10000060 adr x0, 40 38: d51e4020 msr elr_el3, x0 3c: d69f03e0 eret 0000000000000040 : 40: 10000000 adr x0, 0 44: 10000001 adr x1, 0 48: cb000021 sub x1, x1, x0 4c: 94000000 bl 0 50: b26a03ff mov sp, #0x400000 // #4194304 54: 94000000 bl 0 58: 17ffffee b 10 5c: 00000000 .word 0x00000000 60: 30d00800 .word 0x30d00800 64: 00000000 .word 0x00000000 68: 80000000 .word 0x80000000 6c: 00000000 .word 0x00000000 70: 00000431 .word 0x00000431 74: 00000000 .word 0x00000000 78: 000001c5 .word 0x000001c5 7c: 00000000 .word 0x00000000 build/kernel_c.o: file format elf64-littleaarch64 Disassembly of section .text: 0000000000000000 : 0: a9be7bfd stp x29, x30, [sp,#-32]! 4: 910003fd mov x29, sp 8: 94000000 bl 0 c: 90000000 adrp x0, 0 10: 91000000 add x0, x0, #0x0 14: aa0003e1 mov x1, x0 18: d2800000 mov x0, #0x0 // #0 1c: 94000000 bl 0 20: 94000000 bl 0 24: b9001fa0 str w0, [x29,#28] 28: 90000000 adrp x0, 0 2c: 91000000 add x0, x0, #0x0 30: b9401fa1 ldr w1, [x29,#28] 34: 94000000 bl 0 38: 94000000 bl 0 3c: 53001c00 uxtb w0, w0 40: 94000000 bl 0 44: 17fffffd b 38 build/mini_uart_c.o: file format elf64-littleaarch64 Disassembly of section .text: 0000000000000000 : 0: a9be7bfd stp x29, x30, [sp,#-32]! 4: 910003fd mov x29, sp 8: 39007fa0 strb w0, [x29,#31] c: d28a0a80 mov x0, #0x5054 // #20564 10: f2a7e420 movk x0, #0x3f21, lsl #16 14: 94000000 bl 0 18: 121b0000 and w0, w0, #0x20 1c: 7100001f cmp w0, #0x0 20: 54000041 b.ne 28 24: 17fffffa b c 28: d503201f nop 2c: 39407fa0 ldrb w0, [x29,#31] 30: 2a0003e1 mov w1, w0 34: d28a0800 mov x0, #0x5040 // #20544 38: f2a7e420 movk x0, #0x3f21, lsl #16 3c: 94000000 bl 0 40: d503201f nop 44: a8c27bfd ldp x29, x30, [sp],#32 48: d65f03c0 ret 000000000000004c : 4c: a9bf7bfd stp x29, x30, [sp,#-16]! 50: 910003fd mov x29, sp 54: d28a0a80 mov x0, #0x5054 // #20564 58: f2a7e420 movk x0, #0x3f21, lsl #16 5c: 94000000 bl 0 60: 12000000 and w0, w0, #0x1 64: 7100001f cmp w0, #0x0 68: 54000041 b.ne 70 6c: 17fffffa b 54 70: d503201f nop 74: d28a0800 mov x0, #0x5040 // #20544 78: f2a7e420 movk x0, #0x3f21, lsl #16 7c: 94000000 bl 0 80: 53001c00 uxtb w0, w0 84: a8c17bfd ldp x29, x30, [sp],#16 88: d65f03c0 ret 000000000000008c : 8c: a9bd7bfd stp x29, x30, [sp,#-48]! 90: 910003fd mov x29, sp 94: f9000fa0 str x0, [x29,#24] 98: b9002fbf str wzr, [x29,#44] 9c: 14000009 b c0 a0: b9802fa0 ldrsw x0, [x29,#44] a4: f9400fa1 ldr x1, [x29,#24] a8: 8b000020 add x0, x1, x0 ac: 39400000 ldrb w0, [x0] b0: 94000000 bl 0 b4: b9402fa0 ldr w0, [x29,#44] b8: 11000400 add w0, w0, #0x1 bc: b9002fa0 str w0, [x29,#44] c0: b9802fa0 ldrsw x0, [x29,#44] c4: f9400fa1 ldr x1, [x29,#24] c8: 8b000020 add x0, x1, x0 cc: 39400000 ldrb w0, [x0] d0: 7100001f cmp w0, #0x0 d4: 54fffe61 b.ne a0 d8: d503201f nop dc: a8c37bfd ldp x29, x30, [sp],#48 e0: d65f03c0 ret 00000000000000e4 : e4: a9be7bfd stp x29, x30, [sp,#-32]! e8: 910003fd mov x29, sp ec: d2800080 mov x0, #0x4 // #4 f0: f2a7e400 movk x0, #0x3f20, lsl #16 f4: 94000000 bl 0 f8: b9001fa0 str w0, [x29,#28] fc: b9401fa0 ldr w0, [x29,#28] 100: 12117000 and w0, w0, #0xffff8fff 104: b9001fa0 str w0, [x29,#28] 108: b9401fa0 ldr w0, [x29,#28] 10c: 32130000 orr w0, w0, #0x2000 110: b9001fa0 str w0, [x29,#28] 114: b9401fa0 ldr w0, [x29,#28] 118: 120e7000 and w0, w0, #0xfffc7fff 11c: b9001fa0 str w0, [x29,#28] 120: b9401fa0 ldr w0, [x29,#28] 124: 32100000 orr w0, w0, #0x10000 128: b9001fa0 str w0, [x29,#28] 12c: b9401fa1 ldr w1, [x29,#28] 130: d2800080 mov x0, #0x4 // #4 134: f2a7e400 movk x0, #0x3f20, lsl #16 138: 94000000 bl 0 13c: 52800001 mov w1, #0x0 // #0 140: d2801280 mov x0, #0x94 // #148 144: f2a7e400 movk x0, #0x3f20, lsl #16 148: 94000000 bl 0 14c: d28012c0 mov x0, #0x96 // #150 150: 94000000 bl 0 154: 52980001 mov w1, #0xc000 // #49152 158: d2801300 mov x0, #0x98 // #152 15c: f2a7e400 movk x0, #0x3f20, lsl #16 160: 94000000 bl 0 164: d28012c0 mov x0, #0x96 // #150 168: 94000000 bl 0 16c: 52800001 mov w1, #0x0 // #0 170: d2801300 mov x0, #0x98 // #152 174: f2a7e400 movk x0, #0x3f20, lsl #16 178: 94000000 bl 0 17c: 52800021 mov w1, #0x1 // #1 180: d28a0080 mov x0, #0x5004 // #20484 184: f2a7e420 movk x0, #0x3f21, lsl #16 188: 94000000 bl 0 18c: 52800001 mov w1, #0x0 // #0 190: d28a0c00 mov x0, #0x5060 // #20576 194: f2a7e420 movk x0, #0x3f21, lsl #16 198: 94000000 bl 0 19c: 52800001 mov w1, #0x0 // #0 1a0: d28a0880 mov x0, #0x5044 // #20548 1a4: f2a7e420 movk x0, #0x3f21, lsl #16 1a8: 94000000 bl 0 1ac: 52800061 mov w1, #0x3 // #3 1b0: d28a0980 mov x0, #0x504c // #20556 1b4: f2a7e420 movk x0, #0x3f21, lsl #16 1b8: 94000000 bl 0 1bc: 52800001 mov w1, #0x0 // #0 1c0: d28a0a00 mov x0, #0x5050 // #20560 1c4: f2a7e420 movk x0, #0x3f21, lsl #16 1c8: 94000000 bl 0 1cc: 528021c1 mov w1, #0x10e // #270 1d0: d28a0d00 mov x0, #0x5068 // #20584 1d4: f2a7e420 movk x0, #0x3f21, lsl #16 1d8: 94000000 bl 0 1dc: 52800061 mov w1, #0x3 // #3 1e0: d28a0c00 mov x0, #0x5060 // #20576 1e4: f2a7e420 movk x0, #0x3f21, lsl #16 1e8: 94000000 bl 0 1ec: d503201f nop 1f0: a8c27bfd ldp x29, x30, [sp],#32 1f4: d65f03c0 ret 00000000000001f8 : 1f8: a9be7bfd stp x29, x30, [sp,#-32]! 1fc: 910003fd mov x29, sp 200: f9000fa0 str x0, [x29,#24] 204: 39005fa1 strb w1, [x29,#23] 208: 39405fa0 ldrb w0, [x29,#23] 20c: 94000000 bl 0 210: d503201f nop 214: a8c27bfd ldp x29, x30, [sp],#32 218: d65f03c0 ret build/mm_s.o: file format elf64-littleaarch64 Disassembly of section .text: 0000000000000000 : 0: f800841f str xzr, [x0],#8 4: f1002021 subs x1, x1, #0x8 8: 5400000c b.gt 0 c: d65f03c0 ret build/printf_c.o: file format elf64-littleaarch64 Disassembly of section .text: 0000000000000000 : 0: d100c3ff sub sp, sp, #0x30 4: b9001fe0 str w0, [sp,#28] 8: b9001be1 str w1, [sp,#24] c: b90017e2 str w2, [sp,#20] 10: f90007e3 str x3, [sp,#8] 14: b9002fff str wzr, [sp,#44] 18: 52800020 mov w0, #0x1 // #1 1c: b9002be0 str w0, [sp,#40] 20: 14000005 b 34 24: b9402be1 ldr w1, [sp,#40] 28: b9401be0 ldr w0, [sp,#24] 2c: 1b007c20 mul w0, w1, w0 30: b9002be0 str w0, [sp,#40] 34: b9401fe1 ldr w1, [sp,#28] 38: b9402be0 ldr w0, [sp,#40] 3c: 1ac00821 udiv w1, w1, w0 40: b9401be0 ldr w0, [sp,#24] 44: 6b00003f cmp w1, w0 48: 54fffee2 b.cs 24 4c: 1400002f b 108 50: b9401fe1 ldr w1, [sp,#28] 54: b9402be0 ldr w0, [sp,#40] 58: 1ac00820 udiv w0, w1, w0 5c: b90027e0 str w0, [sp,#36] 60: b9401fe0 ldr w0, [sp,#28] 64: b9402be1 ldr w1, [sp,#40] 68: 1ac10802 udiv w2, w0, w1 6c: b9402be1 ldr w1, [sp,#40] 70: 1b017c41 mul w1, w2, w1 74: 4b010000 sub w0, w0, w1 78: b9001fe0 str w0, [sp,#28] 7c: b9402be1 ldr w1, [sp,#40] 80: b9401be0 ldr w0, [sp,#24] 84: 1ac00820 udiv w0, w1, w0 88: b9002be0 str w0, [sp,#40] 8c: b9402fe0 ldr w0, [sp,#44] 90: 7100001f cmp w0, #0x0 94: 540000e1 b.ne b0 98: b94027e0 ldr w0, [sp,#36] 9c: 7100001f cmp w0, #0x0 a0: 5400008c b.gt b0 a4: b9402be0 ldr w0, [sp,#40] a8: 7100001f cmp w0, #0x0 ac: 540002e1 b.ne 108 b0: f94007e1 ldr x1, [sp,#8] b4: 91000420 add x0, x1, #0x1 b8: f90007e0 str x0, [sp,#8] bc: b94027e0 ldr w0, [sp,#36] c0: 7100241f cmp w0, #0x9 c4: 5400010d b.le e4 c8: b94017e0 ldr w0, [sp,#20] cc: 7100001f cmp w0, #0x0 d0: 54000060 b.eq dc d4: 528006e0 mov w0, #0x37 // #55 d8: 14000004 b e8 dc: 52800ae0 mov w0, #0x57 // #87 e0: 14000002 b e8 e4: 52800600 mov w0, #0x30 // #48 e8: b94027e2 ldr w2, [sp,#36] ec: 53001c42 uxtb w2, w2 f0: 0b020000 add w0, w0, w2 f4: 53001c00 uxtb w0, w0 f8: 39000020 strb w0, [x1] fc: b9402fe0 ldr w0, [sp,#44] 100: 11000400 add w0, w0, #0x1 104: b9002fe0 str w0, [sp,#44] 108: b9402be0 ldr w0, [sp,#40] 10c: 7100001f cmp w0, #0x0 110: 54fffa01 b.ne 50 114: f94007e0 ldr x0, [sp,#8] 118: 3900001f strb wzr, [x0] 11c: d503201f nop 120: 9100c3ff add sp, sp, #0x30 124: d65f03c0 ret 0000000000000128 : 128: a9be7bfd stp x29, x30, [sp,#-32]! 12c: 910003fd mov x29, sp 130: b9001fa0 str w0, [x29,#28] 134: f9000ba1 str x1, [x29,#16] 138: b9401fa0 ldr w0, [x29,#28] 13c: 7100001f cmp w0, #0x0 140: 5400012a b.ge 164 144: b9401fa0 ldr w0, [x29,#28] 148: 4b0003e0 neg w0, w0 14c: b9001fa0 str w0, [x29,#28] 150: f9400ba0 ldr x0, [x29,#16] 154: 91000401 add x1, x0, #0x1 158: f9000ba1 str x1, [x29,#16] 15c: 528005a1 mov w1, #0x2d // #45 160: 39000001 strb w1, [x0] 164: b9401fa0 ldr w0, [x29,#28] 168: f9400ba3 ldr x3, [x29,#16] 16c: 52800002 mov w2, #0x0 // #0 170: 52800141 mov w1, #0xa // #10 174: 97ffffa3 bl 0 178: d503201f nop 17c: a8c27bfd ldp x29, x30, [sp],#32 180: d65f03c0 ret 0000000000000184 : 184: d10043ff sub sp, sp, #0x10 188: 39003fe0 strb w0, [sp,#15] 18c: 39403fe0 ldrb w0, [sp,#15] 190: 7100bc1f cmp w0, #0x2f 194: 540000e9 b.ls 1b0 198: 39403fe0 ldrb w0, [sp,#15] 19c: 7100e41f cmp w0, #0x39 1a0: 54000088 b.hi 1b0 1a4: 39403fe0 ldrb w0, [sp,#15] 1a8: 5100c000 sub w0, w0, #0x30 1ac: 14000014 b 1fc 1b0: 39403fe0 ldrb w0, [sp,#15] 1b4: 7101801f cmp w0, #0x60 1b8: 540000e9 b.ls 1d4 1bc: 39403fe0 ldrb w0, [sp,#15] 1c0: 7101981f cmp w0, #0x66 1c4: 54000088 b.hi 1d4 1c8: 39403fe0 ldrb w0, [sp,#15] 1cc: 51015c00 sub w0, w0, #0x57 1d0: 1400000b b 1fc 1d4: 39403fe0 ldrb w0, [sp,#15] 1d8: 7101001f cmp w0, #0x40 1dc: 540000e9 b.ls 1f8 1e0: 39403fe0 ldrb w0, [sp,#15] 1e4: 7101181f cmp w0, #0x46 1e8: 54000088 b.hi 1f8 1ec: 39403fe0 ldrb w0, [sp,#15] 1f0: 5100dc00 sub w0, w0, #0x37 1f4: 14000002 b 1fc 1f8: 12800000 mov w0, #0xffffffff // #-1 1fc: 910043ff add sp, sp, #0x10 200: d65f03c0 ret 0000000000000204 : 204: a9bc7bfd stp x29, x30, [sp,#-64]! 208: 910003fd mov x29, sp 20c: 3900bfa0 strb w0, [x29,#47] 210: f90013a1 str x1, [x29,#32] 214: b9002ba2 str w2, [x29,#40] 218: f9000fa3 str x3, [x29,#24] 21c: f94013a0 ldr x0, [x29,#32] 220: f9400000 ldr x0, [x0] 224: f9001fa0 str x0, [x29,#56] 228: b90037bf str wzr, [x29,#52] 22c: 14000010 b 26c 230: b94033a1 ldr w1, [x29,#48] 234: b9402ba0 ldr w0, [x29,#40] 238: 6b00003f cmp w1, w0 23c: 5400026c b.gt 288 240: b94037a1 ldr w1, [x29,#52] 244: b9402ba0 ldr w0, [x29,#40] 248: 1b007c21 mul w1, w1, w0 24c: b94033a0 ldr w0, [x29,#48] 250: 0b000020 add w0, w1, w0 254: b90037a0 str w0, [x29,#52] 258: f9401fa0 ldr x0, [x29,#56] 25c: 91000401 add x1, x0, #0x1 260: f9001fa1 str x1, [x29,#56] 264: 39400000 ldrb w0, [x0] 268: 3900bfa0 strb w0, [x29,#47] 26c: 3940bfa0 ldrb w0, [x29,#47] 270: 97ffffc5 bl 184 274: b90033a0 str w0, [x29,#48] 278: b94033a0 ldr w0, [x29,#48] 27c: 7100001f cmp w0, #0x0 280: 54fffd8a b.ge 230 284: 14000002 b 28c 288: d503201f nop 28c: f94013a0 ldr x0, [x29,#32] 290: f9401fa1 ldr x1, [x29,#56] 294: f9000001 str x1, [x0] 298: f9400fa0 ldr x0, [x29,#24] 29c: b94037a1 ldr w1, [x29,#52] 2a0: b9000001 str w1, [x0] 2a4: 3940bfa0 ldrb w0, [x29,#47] 2a8: a8c47bfd ldp x29, x30, [sp],#64 2ac: d65f03c0 ret 00000000000002b0 : 2b0: a9bc7bfd stp x29, x30, [sp,#-64]! 2b4: 910003fd mov x29, sp 2b8: f90017a0 str x0, [x29,#40] 2bc: f90013a1 str x1, [x29,#32] 2c0: b9001fa2 str w2, [x29,#28] 2c4: 39006fa3 strb w3, [x29,#27] 2c8: f9000ba4 str x4, [x29,#16] 2cc: 39406fa0 ldrb w0, [x29,#27] 2d0: 7100001f cmp w0, #0x0 2d4: 54000060 b.eq 2e0 2d8: 52800600 mov w0, #0x30 // #48 2dc: 14000002 b 2e4 2e0: 52800400 mov w0, #0x20 // #32 2e4: 3900dfa0 strb w0, [x29,#55] 2e8: f9400ba0 ldr x0, [x29,#16] 2ec: f9001fa0 str x0, [x29,#56] 2f0: 14000004 b 300 2f4: b9401fa0 ldr w0, [x29,#28] 2f8: 51000400 sub w0, w0, #0x1 2fc: b9001fa0 str w0, [x29,#28] 300: f9401fa0 ldr x0, [x29,#56] 304: 91000401 add x1, x0, #0x1 308: f9001fa1 str x1, [x29,#56] 30c: 39400000 ldrb w0, [x0] 310: 7100001f cmp w0, #0x0 314: 54000120 b.eq 338 318: b9401fa0 ldr w0, [x29,#28] 31c: 7100001f cmp w0, #0x0 320: 54fffeac b.gt 2f4 324: 14000005 b 338 328: f94013a2 ldr x2, [x29,#32] 32c: 3940dfa1 ldrb w1, [x29,#55] 330: f94017a0 ldr x0, [x29,#40] 334: d63f0040 blr x2 338: b9401fa0 ldr w0, [x29,#28] 33c: 51000401 sub w1, w0, #0x1 340: b9001fa1 str w1, [x29,#28] 344: 7100001f cmp w0, #0x0 348: 54ffff0c b.gt 328 34c: 14000005 b 360 350: f94013a2 ldr x2, [x29,#32] 354: 3940dba1 ldrb w1, [x29,#54] 358: f94017a0 ldr x0, [x29,#40] 35c: d63f0040 blr x2 360: f9400ba0 ldr x0, [x29,#16] 364: 91000401 add x1, x0, #0x1 368: f9000ba1 str x1, [x29,#16] 36c: 39400000 ldrb w0, [x0] 370: 3900dba0 strb w0, [x29,#54] 374: 3940dba0 ldrb w0, [x29,#54] 378: 7100001f cmp w0, #0x0 37c: 54fffea1 b.ne 350 380: d503201f nop 384: a8c47bfd ldp x29, x30, [sp],#64 388: d65f03c0 ret 000000000000038c : 38c: a9ba7bfd stp x29, x30, [sp,#-96]! 390: 910003fd mov x29, sp 394: f9000bf3 str x19, [sp,#16] 398: f9001fa0 str x0, [x29,#56] 39c: f9001ba1 str x1, [x29,#48] 3a0: f90017a2 str x2, [x29,#40] 3a4: aa0303f3 mov x19, x3 3a8: 140000fd b 79c 3ac: 39417fa0 ldrb w0, [x29,#95] 3b0: 7100941f cmp w0, #0x25 3b4: 540000c0 b.eq 3cc 3b8: f9401ba2 ldr x2, [x29,#48] 3bc: 39417fa1 ldrb w1, [x29,#95] 3c0: f9401fa0 ldr x0, [x29,#56] 3c4: d63f0040 blr x2 3c8: 140000f5 b 79c 3cc: 39017bbf strb wzr, [x29,#94] 3d0: b9004fbf str wzr, [x29,#76] 3d4: f94017a0 ldr x0, [x29,#40] 3d8: 91000401 add x1, x0, #0x1 3dc: f90017a1 str x1, [x29,#40] 3e0: 39400000 ldrb w0, [x0] 3e4: 39017fa0 strb w0, [x29,#95] 3e8: 39417fa0 ldrb w0, [x29,#95] 3ec: 7100c01f cmp w0, #0x30 3f0: 54000101 b.ne 410 3f4: f94017a0 ldr x0, [x29,#40] 3f8: 91000401 add x1, x0, #0x1 3fc: f90017a1 str x1, [x29,#40] 400: 39400000 ldrb w0, [x0] 404: 39017fa0 strb w0, [x29,#95] 408: 52800020 mov w0, #0x1 // #1 40c: 39017ba0 strb w0, [x29,#94] 410: 39417fa0 ldrb w0, [x29,#95] 414: 7100bc1f cmp w0, #0x2f 418: 54000189 b.ls 448 41c: 39417fa0 ldrb w0, [x29,#95] 420: 7100e41f cmp w0, #0x39 424: 54000128 b.hi 448 428: 910133a1 add x1, x29, #0x4c 42c: 9100a3a0 add x0, x29, #0x28 430: aa0103e3 mov x3, x1 434: 52800142 mov w2, #0xa // #10 438: aa0003e1 mov x1, x0 43c: 39417fa0 ldrb w0, [x29,#95] 440: 97ffff71 bl 204 444: 39017fa0 strb w0, [x29,#95] 448: 39417fa0 ldrb w0, [x29,#95] 44c: 71018c1f cmp w0, #0x63 450: 540011c0 b.eq 688 454: 71018c1f cmp w0, #0x63 458: 5400010c b.gt 478 45c: 7100941f cmp w0, #0x25 460: 54001940 b.eq 788 464: 7101601f cmp w0, #0x58 468: 54000b60 b.eq 5d4 46c: 7100001f cmp w0, #0x0 470: 54001a80 b.eq 7c0 474: 140000c9 b 798 478: 7101cc1f cmp w0, #0x73 47c: 54001440 b.eq 704 480: 7101cc1f cmp w0, #0x73 484: 5400008c b.gt 494 488: 7101901f cmp w0, #0x64 48c: 540005c0 b.eq 544 490: 140000c2 b 798 494: 7101d41f cmp w0, #0x75 498: 54000080 b.eq 4a8 49c: 7101e01f cmp w0, #0x78 4a0: 540009a0 b.eq 5d4 4a4: 140000bd b 798 4a8: b9401a60 ldr w0, [x19,#24] 4ac: f9400261 ldr x1, [x19] 4b0: 7100001f cmp w0, #0x0 4b4: 540000eb b.lt 4d0 4b8: aa0103e0 mov x0, x1 4bc: 91002c00 add x0, x0, #0xb 4c0: 927df000 and x0, x0, #0xfffffffffffffff8 4c4: f9000260 str x0, [x19] 4c8: aa0103e0 mov x0, x1 4cc: 1400000f b 508 4d0: 11002002 add w2, w0, #0x8 4d4: b9001a62 str w2, [x19,#24] 4d8: b9401a62 ldr w2, [x19,#24] 4dc: 7100005f cmp w2, #0x0 4e0: 540000ed b.le 4fc 4e4: aa0103e0 mov x0, x1 4e8: 91002c00 add x0, x0, #0xb 4ec: 927df000 and x0, x0, #0xfffffffffffffff8 4f0: f9000260 str x0, [x19] 4f4: aa0103e0 mov x0, x1 4f8: 14000004 b 508 4fc: f9400661 ldr x1, [x19,#8] 500: 93407c00 sxtw x0, w0 504: 8b000020 add x0, x1, x0 508: b9400000 ldr w0, [x0] 50c: 910143a1 add x1, x29, #0x50 510: aa0103e3 mov x3, x1 514: 52800002 mov w2, #0x0 // #0 518: 52800141 mov w1, #0xa // #10 51c: 97fffeb9 bl 0 520: b9404fa0 ldr w0, [x29,#76] 524: 910143a1 add x1, x29, #0x50 528: aa0103e4 mov x4, x1 52c: 39417ba3 ldrb w3, [x29,#94] 530: 2a0003e2 mov w2, w0 534: f9401ba1 ldr x1, [x29,#48] 538: f9401fa0 ldr x0, [x29,#56] 53c: 97ffff5d bl 2b0 540: 14000097 b 79c 544: b9401a60 ldr w0, [x19,#24] 548: f9400261 ldr x1, [x19] 54c: 7100001f cmp w0, #0x0 550: 540000eb b.lt 56c 554: aa0103e0 mov x0, x1 558: 91002c00 add x0, x0, #0xb 55c: 927df000 and x0, x0, #0xfffffffffffffff8 560: f9000260 str x0, [x19] 564: aa0103e0 mov x0, x1 568: 1400000f b 5a4 56c: 11002002 add w2, w0, #0x8 570: b9001a62 str w2, [x19,#24] 574: b9401a62 ldr w2, [x19,#24] 578: 7100005f cmp w2, #0x0 57c: 540000ed b.le 598 580: aa0103e0 mov x0, x1 584: 91002c00 add x0, x0, #0xb 588: 927df000 and x0, x0, #0xfffffffffffffff8 58c: f9000260 str x0, [x19] 590: aa0103e0 mov x0, x1 594: 14000004 b 5a4 598: f9400661 ldr x1, [x19,#8] 59c: 93407c00 sxtw x0, w0 5a0: 8b000020 add x0, x1, x0 5a4: b9400000 ldr w0, [x0] 5a8: 910143a1 add x1, x29, #0x50 5ac: 97fffedf bl 128 5b0: b9404fa0 ldr w0, [x29,#76] 5b4: 910143a1 add x1, x29, #0x50 5b8: aa0103e4 mov x4, x1 5bc: 39417ba3 ldrb w3, [x29,#94] 5c0: 2a0003e2 mov w2, w0 5c4: f9401ba1 ldr x1, [x29,#48] 5c8: f9401fa0 ldr x0, [x29,#56] 5cc: 97ffff39 bl 2b0 5d0: 14000073 b 79c 5d4: b9401a60 ldr w0, [x19,#24] 5d8: f9400261 ldr x1, [x19] 5dc: 7100001f cmp w0, #0x0 5e0: 540000eb b.lt 5fc 5e4: aa0103e0 mov x0, x1 5e8: 91002c00 add x0, x0, #0xb 5ec: 927df000 and x0, x0, #0xfffffffffffffff8 5f0: f9000260 str x0, [x19] 5f4: aa0103e0 mov x0, x1 5f8: 1400000f b 634 5fc: 11002002 add w2, w0, #0x8 600: b9001a62 str w2, [x19,#24] 604: b9401a62 ldr w2, [x19,#24] 608: 7100005f cmp w2, #0x0 60c: 540000ed b.le 628 610: aa0103e0 mov x0, x1 614: 91002c00 add x0, x0, #0xb 618: 927df000 and x0, x0, #0xfffffffffffffff8 61c: f9000260 str x0, [x19] 620: aa0103e0 mov x0, x1 624: 14000004 b 634 628: f9400661 ldr x1, [x19,#8] 62c: 93407c00 sxtw x0, w0 630: 8b000020 add x0, x1, x0 634: b9400004 ldr w4, [x0] 638: 39417fa0 ldrb w0, [x29,#95] 63c: 7101601f cmp w0, #0x58 640: 1a9f17e0 cset w0, eq 644: 53001c00 uxtb w0, w0 648: 2a0003e1 mov w1, w0 64c: 910143a0 add x0, x29, #0x50 650: aa0003e3 mov x3, x0 654: 2a0103e2 mov w2, w1 658: 52800201 mov w1, #0x10 // #16 65c: 2a0403e0 mov w0, w4 660: 97fffe68 bl 0 664: b9404fa0 ldr w0, [x29,#76] 668: 910143a1 add x1, x29, #0x50 66c: aa0103e4 mov x4, x1 670: 39417ba3 ldrb w3, [x29,#94] 674: 2a0003e2 mov w2, w0 678: f9401ba1 ldr x1, [x29,#48] 67c: f9401fa0 ldr x0, [x29,#56] 680: 97ffff0c bl 2b0 684: 14000046 b 79c 688: b9401a60 ldr w0, [x19,#24] 68c: f9400261 ldr x1, [x19] 690: 7100001f cmp w0, #0x0 694: 540000eb b.lt 6b0 698: aa0103e0 mov x0, x1 69c: 91002c00 add x0, x0, #0xb 6a0: 927df000 and x0, x0, #0xfffffffffffffff8 6a4: f9000260 str x0, [x19] 6a8: aa0103e0 mov x0, x1 6ac: 1400000f b 6e8 6b0: 11002002 add w2, w0, #0x8 6b4: b9001a62 str w2, [x19,#24] 6b8: b9401a62 ldr w2, [x19,#24] 6bc: 7100005f cmp w2, #0x0 6c0: 540000ed b.le 6dc 6c4: aa0103e0 mov x0, x1 6c8: 91002c00 add x0, x0, #0xb 6cc: 927df000 and x0, x0, #0xfffffffffffffff8 6d0: f9000260 str x0, [x19] 6d4: aa0103e0 mov x0, x1 6d8: 14000004 b 6e8 6dc: f9400661 ldr x1, [x19,#8] 6e0: 93407c00 sxtw x0, w0 6e4: 8b000020 add x0, x1, x0 6e8: b9400000 ldr w0, [x0] 6ec: 53001c00 uxtb w0, w0 6f0: f9401ba2 ldr x2, [x29,#48] 6f4: 2a0003e1 mov w1, w0 6f8: f9401fa0 ldr x0, [x29,#56] 6fc: d63f0040 blr x2 700: 14000027 b 79c 704: b9404fa5 ldr w5, [x29,#76] 708: b9401a60 ldr w0, [x19,#24] 70c: f9400261 ldr x1, [x19] 710: 7100001f cmp w0, #0x0 714: 540000eb b.lt 730 718: aa0103e0 mov x0, x1 71c: 91003c00 add x0, x0, #0xf 720: 927df000 and x0, x0, #0xfffffffffffffff8 724: f9000260 str x0, [x19] 728: aa0103e0 mov x0, x1 72c: 1400000f b 768 730: 11002002 add w2, w0, #0x8 734: b9001a62 str w2, [x19,#24] 738: b9401a62 ldr w2, [x19,#24] 73c: 7100005f cmp w2, #0x0 740: 540000ed b.le 75c 744: aa0103e0 mov x0, x1 748: 91003c00 add x0, x0, #0xf 74c: 927df000 and x0, x0, #0xfffffffffffffff8 750: f9000260 str x0, [x19] 754: aa0103e0 mov x0, x1 758: 14000004 b 768 75c: f9400661 ldr x1, [x19,#8] 760: 93407c00 sxtw x0, w0 764: 8b000020 add x0, x1, x0 768: f9400000 ldr x0, [x0] 76c: aa0003e4 mov x4, x0 770: 52800003 mov w3, #0x0 // #0 774: 2a0503e2 mov w2, w5 778: f9401ba1 ldr x1, [x29,#48] 77c: f9401fa0 ldr x0, [x29,#56] 780: 97fffecc bl 2b0 784: 14000006 b 79c 788: f9401ba2 ldr x2, [x29,#48] 78c: 39417fa1 ldrb w1, [x29,#95] 790: f9401fa0 ldr x0, [x29,#56] 794: d63f0040 blr x2 798: d503201f nop 79c: f94017a0 ldr x0, [x29,#40] 7a0: 91000401 add x1, x0, #0x1 7a4: f90017a1 str x1, [x29,#40] 7a8: 39400000 ldrb w0, [x0] 7ac: 39017fa0 strb w0, [x29,#95] 7b0: 39417fa0 ldrb w0, [x29,#95] 7b4: 7100001f cmp w0, #0x0 7b8: 54ffdfa1 b.ne 3ac 7bc: 14000002 b 7c4 7c0: d503201f nop 7c4: d503201f nop 7c8: f9400bf3 ldr x19, [sp,#16] 7cc: a8c67bfd ldp x29, x30, [sp],#96 7d0: d65f03c0 ret 00000000000007d4 : 7d4: d10043ff sub sp, sp, #0x10 7d8: f90007e0 str x0, [sp,#8] 7dc: f90003e1 str x1, [sp] 7e0: 90000000 adrp x0, 0 7e4: 91000000 add x0, x0, #0x0 7e8: f94003e1 ldr x1, [sp] 7ec: f9000001 str x1, [x0] 7f0: 90000000 adrp x0, 0 7f4: 91000000 add x0, x0, #0x0 7f8: f94007e1 ldr x1, [sp,#8] 7fc: f9000001 str x1, [x0] 800: d503201f nop 804: 910043ff add sp, sp, #0x10 808: d65f03c0 ret 000000000000080c : 80c: a9b67bfd stp x29, x30, [sp,#-160]! 810: 910003fd mov x29, sp 814: f9001fa0 str x0, [x29,#56] 818: f90037a1 str x1, [x29,#104] 81c: f9003ba2 str x2, [x29,#112] 820: f9003fa3 str x3, [x29,#120] 824: f90043a4 str x4, [x29,#128] 828: f90047a5 str x5, [x29,#136] 82c: f9004ba6 str x6, [x29,#144] 830: f9004fa7 str x7, [x29,#152] 834: 910283a0 add x0, x29, #0xa0 838: f90023a0 str x0, [x29,#64] 83c: 910283a0 add x0, x29, #0xa0 840: f90027a0 str x0, [x29,#72] 844: 910183a0 add x0, x29, #0x60 848: f9002ba0 str x0, [x29,#80] 84c: 128006e0 mov w0, #0xffffffc8 // #-56 850: b9005ba0 str w0, [x29,#88] 854: b9005fbf str wzr, [x29,#92] 858: 90000000 adrp x0, 0 85c: 91000000 add x0, x0, #0x0 860: f9400004 ldr x4, [x0] 864: 90000000 adrp x0, 0 868: 91000000 add x0, x0, #0x0 86c: f9400005 ldr x5, [x0] 870: 910043a2 add x2, x29, #0x10 874: 910103a3 add x3, x29, #0x40 878: a9400460 ldp x0, x1, [x3] 87c: a9000440 stp x0, x1, [x2] 880: a9410460 ldp x0, x1, [x3,#16] 884: a9010440 stp x0, x1, [x2,#16] 888: 910043a0 add x0, x29, #0x10 88c: aa0003e3 mov x3, x0 890: f9401fa2 ldr x2, [x29,#56] 894: aa0503e1 mov x1, x5 898: aa0403e0 mov x0, x4 89c: 94000000 bl 38c 8a0: d503201f nop 8a4: a8ca7bfd ldp x29, x30, [sp],#160 8a8: d65f03c0 ret 00000000000008ac : 8ac: d10043ff sub sp, sp, #0x10 8b0: f90007e0 str x0, [sp,#8] 8b4: 39001fe1 strb w1, [sp,#7] 8b8: f94007e0 ldr x0, [sp,#8] 8bc: f9400000 ldr x0, [x0] 8c0: 91000402 add x2, x0, #0x1 8c4: f94007e1 ldr x1, [sp,#8] 8c8: f9000022 str x2, [x1] 8cc: 39401fe1 ldrb w1, [sp,#7] 8d0: 39000001 strb w1, [x0] 8d4: d503201f nop 8d8: 910043ff add sp, sp, #0x10 8dc: d65f03c0 ret 00000000000008e0 : 8e0: a9b77bfd stp x29, x30, [sp,#-144]! 8e4: 910003fd mov x29, sp 8e8: f9001fa0 str x0, [x29,#56] 8ec: f9001ba1 str x1, [x29,#48] 8f0: f90033a2 str x2, [x29,#96] 8f4: f90037a3 str x3, [x29,#104] 8f8: f9003ba4 str x4, [x29,#112] 8fc: f9003fa5 str x5, [x29,#120] 900: f90043a6 str x6, [x29,#128] 904: f90047a7 str x7, [x29,#136] 908: 910243a0 add x0, x29, #0x90 90c: f90023a0 str x0, [x29,#64] 910: 910243a0 add x0, x29, #0x90 914: f90027a0 str x0, [x29,#72] 918: 910183a0 add x0, x29, #0x60 91c: f9002ba0 str x0, [x29,#80] 920: 128005e0 mov w0, #0xffffffd0 // #-48 924: b9005ba0 str w0, [x29,#88] 928: b9005fbf str wzr, [x29,#92] 92c: 910043a2 add x2, x29, #0x10 930: 910103a3 add x3, x29, #0x40 934: a9400460 ldp x0, x1, [x3] 938: a9000440 stp x0, x1, [x2] 93c: a9410460 ldp x0, x1, [x3,#16] 940: a9010440 stp x0, x1, [x2,#16] 944: 910043a2 add x2, x29, #0x10 948: 90000000 adrp x0, 0 94c: 91000001 add x1, x0, #0x0 950: 9100e3a0 add x0, x29, #0x38 954: aa0203e3 mov x3, x2 958: f9401ba2 ldr x2, [x29,#48] 95c: 94000000 bl 38c 960: 9100e3a0 add x0, x29, #0x38 964: 52800001 mov w1, #0x0 // #0 968: 97ffffd1 bl 8ac 96c: d503201f nop 970: a8c97bfd ldp x29, x30, [sp],#144 974: d65f03c0 ret build/uart_c.o: file format elf64-littleaarch64 Disassembly of section .text: 0000000000000000 : 0: a9be7bfd stp x29, x30, [sp,#-32]! 4: 910003fd mov x29, sp 8: 39007fa0 strb w0, [x29,#31] c: d503201f nop 10: d2820300 mov x0, #0x1018 // #4120 14: f2a7e400 movk x0, #0x3f20, lsl #16 18: 94000000 bl 0 1c: 121b0000 and w0, w0, #0x20 20: 7100001f cmp w0, #0x0 24: 54ffff61 b.ne 10 28: 39407fa0 ldrb w0, [x29,#31] 2c: 2a0003e1 mov w1, w0 30: d2820000 mov x0, #0x1000 // #4096 34: f2a7e400 movk x0, #0x3f20, lsl #16 38: 94000000 bl 0 3c: d503201f nop 40: a8c27bfd ldp x29, x30, [sp],#32 44: d65f03c0 ret 0000000000000048 : 48: a9bf7bfd stp x29, x30, [sp,#-16]! 4c: 910003fd mov x29, sp 50: d503201f nop 54: d2820300 mov x0, #0x1018 // #4120 58: f2a7e400 movk x0, #0x3f20, lsl #16 5c: 94000000 bl 0 60: 121c0000 and w0, w0, #0x10 64: 7100001f cmp w0, #0x0 68: 54ffff61 b.ne 54 6c: d2820000 mov x0, #0x1000 // #4096 70: f2a7e400 movk x0, #0x3f20, lsl #16 74: 94000000 bl 0 78: 53001c00 uxtb w0, w0 7c: a8c17bfd ldp x29, x30, [sp],#16 80: d65f03c0 ret 0000000000000084 : 84: a9bd7bfd stp x29, x30, [sp,#-48]! 88: 910003fd mov x29, sp 8c: f9000fa0 str x0, [x29,#24] 90: b9002fbf str wzr, [x29,#44] 94: 14000009 b b8 98: b9802fa0 ldrsw x0, [x29,#44] 9c: f9400fa1 ldr x1, [x29,#24] a0: 8b000020 add x0, x1, x0 a4: 39400000 ldrb w0, [x0] a8: 94000000 bl 0 ac: b9402fa0 ldr w0, [x29,#44] b0: 11000400 add w0, w0, #0x1 b4: b9002fa0 str w0, [x29,#44] b8: b9802fa0 ldrsw x0, [x29,#44] bc: f9400fa1 ldr x1, [x29,#24] c0: 8b000020 add x0, x1, x0 c4: 39400000 ldrb w0, [x0] c8: 7100001f cmp w0, #0x0 cc: 54fffe61 b.ne 98 d0: d503201f nop d4: a8c37bfd ldp x29, x30, [sp],#48 d8: d65f03c0 ret 00000000000000dc : dc: a9be7bfd stp x29, x30, [sp,#-32]! e0: 910003fd mov x29, sp e4: d2800080 mov x0, #0x4 // #4 e8: f2a7e400 movk x0, #0x3f20, lsl #16 ec: 94000000 bl 0 f0: b9001fa0 str w0, [x29,#28] f4: b9401fa0 ldr w0, [x29,#28] f8: 12117000 and w0, w0, #0xffff8fff fc: b9001fa0 str w0, [x29,#28] 100: b9401fa0 ldr w0, [x29,#28] 104: 32120000 orr w0, w0, #0x4000 108: b9001fa0 str w0, [x29,#28] 10c: b9401fa0 ldr w0, [x29,#28] 110: 120e7000 and w0, w0, #0xfffc7fff 114: b9001fa0 str w0, [x29,#28] 118: b9401fa0 ldr w0, [x29,#28] 11c: 320f0000 orr w0, w0, #0x20000 120: b9001fa0 str w0, [x29,#28] 124: b9401fa1 ldr w1, [x29,#28] 128: d2800080 mov x0, #0x4 // #4 12c: f2a7e400 movk x0, #0x3f20, lsl #16 130: 94000000 bl 0 134: 52800001 mov w1, #0x0 // #0 138: d2801280 mov x0, #0x94 // #148 13c: f2a7e400 movk x0, #0x3f20, lsl #16 140: 94000000 bl 0 144: d28012c0 mov x0, #0x96 // #150 148: 94000000 bl 0 14c: 52980001 mov w1, #0xc000 // #49152 150: d2801300 mov x0, #0x98 // #152 154: f2a7e400 movk x0, #0x3f20, lsl #16 158: 94000000 bl 0 15c: d28012c0 mov x0, #0x96 // #150 160: 94000000 bl 0 164: 52800001 mov w1, #0x0 // #0 168: d2801300 mov x0, #0x98 // #152 16c: f2a7e400 movk x0, #0x3f20, lsl #16 170: 94000000 bl 0 174: 52800001 mov w1, #0x0 // #0 178: d2820600 mov x0, #0x1030 // #4144 17c: f2a7e400 movk x0, #0x3f20, lsl #16 180: 94000000 bl 0 184: 52800341 mov w1, #0x1a // #26 188: d2820480 mov x0, #0x1024 // #4132 18c: f2a7e400 movk x0, #0x3f20, lsl #16 190: 94000000 bl 0 194: 52800061 mov w1, #0x3 // #3 198: d2820500 mov x0, #0x1028 // #4136 19c: f2a7e400 movk x0, #0x3f20, lsl #16 1a0: 94000000 bl 0 1a4: 52800c01 mov w1, #0x60 // #96 1a8: d2820580 mov x0, #0x102c // #4140 1ac: f2a7e400 movk x0, #0x3f20, lsl #16 1b0: 94000000 bl 0 1b4: 52806021 mov w1, #0x301 // #769 1b8: d2820600 mov x0, #0x1030 // #4144 1bc: f2a7e400 movk x0, #0x3f20, lsl #16 1c0: 94000000 bl 0 1c4: d503201f nop 1c8: a8c27bfd ldp x29, x30, [sp],#32 1cc: d65f03c0 ret build/utils_s.o: file format elf64-littleaarch64 Disassembly of section .text: 0000000000000000 : 0: d5384240 mrs x0, currentel 4: d342fc00 lsr x0, x0, #2 8: d65f03c0 ret 000000000000000c : c: b9000001 str w1, [x0] 10: d65f03c0 ret 0000000000000014 : 14: b9400000 ldr w0, [x0] 18: d65f03c0 ret 000000000000001c : 1c: f1000400 subs x0, x0, #0x1 20: 54000001 b.ne 1c 24: d65f03c0 ret ================================================ FILE: exercises/lesson02/2/avenito/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) // *************************************** // CPACR_EL1, Architectural Feature Access Control Register (EL1) Page 2411 of AArch64-Reference-Manual. // *************************************** #define CPACR_FPEN (3 << 20) #define CPACR_VALUE (CPACR_FPEN) #endif ================================================ FILE: exercises/lesson02/2/avenito/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson02/2/avenito/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson02/2/avenito/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson02/2/avenito/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson02/2/avenito/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson02/2/avenito/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson02/2/avenito/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson02/2/avenito/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 ldr x0, =CPACR_VALUE // This control does not cause any instructions to be trapped msr cpacr_el1, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson02/2/avenito/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson02/2/avenito/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "mini_uart.h" void kernel_main(void) { uart_init(); init_printf(0, putc); int el = get_el(); printf("Exception level: %d \r\n", el); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson02/2/avenito/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson02/2/avenito/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson02/2/avenito/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson02/2/avenito/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson02/2/avenito/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson02/2/bl4ckout31/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson02/2/bl4ckout31/avec ================================================ build/kernel8.elf: file format elf64-littleaarch64 Disassembly of section .text.boot: 0000000000000000 <_start>: 0: d53800a0 mrs x0, mpidr_el1 4: 92401c00 and x0, x0, #0xff 8: b4000060 cbz x0, 14 c: 14000001 b 10 0000000000000010 : 10: 14000000 b 10 0000000000000014 : 14: 58000260 ldr x0, 60 18: d5181000 msr sctlr_el1, x0 1c: 58000260 ldr x0, 68 20: d51c1100 msr hcr_el2, x0 24: 58000260 ldr x0, 70 28: d51e1100 msr scr_el3, x0 2c: 58000260 ldr x0, 78 30: d51e4000 msr spsr_el3, x0 34: 10000060 adr x0, 40 38: d51e4020 msr elr_el3, x0 3c: d69f03e0 eret 0000000000000040 : 40: 10006380 adr x0, cb0 44: 100063e1 adr x1, cc0 48: cb000021 sub x1, x1, x0 4c: 94000304 bl c5c 50: b26a03ff mov sp, #0x400000 // #4194304 54: 940002f0 bl c14 58: 17ffffee b 10 5c: 00000000 .word 0x00000000 60: 30d00800 .word 0x30d00800 64: 00000000 .word 0x00000000 68: 80000000 .word 0x80000000 6c: 00000000 .word 0x00000000 70: 00000431 .word 0x00000431 74: 00000000 .word 0x00000000 78: 000001c5 .word 0x000001c5 7c: 00000000 .word 0x00000000 Disassembly of section .text: 0000000000000080 : 80: d100c3ff sub sp, sp, #0x30 84: b9001fe0 str w0, [sp,#28] 88: b9001be1 str w1, [sp,#24] 8c: b90017e2 str w2, [sp,#20] 90: f90007e3 str x3, [sp,#8] 94: b9002fff str wzr, [sp,#44] 98: 52800020 mov w0, #0x1 // #1 9c: b9002be0 str w0, [sp,#40] a0: 14000005 b b4 a4: b9402be1 ldr w1, [sp,#40] a8: b9401be0 ldr w0, [sp,#24] ac: 1b007c20 mul w0, w1, w0 b0: b9002be0 str w0, [sp,#40] b4: b9401fe1 ldr w1, [sp,#28] b8: b9402be0 ldr w0, [sp,#40] bc: 1ac00821 udiv w1, w1, w0 c0: b9401be0 ldr w0, [sp,#24] c4: 6b00003f cmp w1, w0 c8: 54fffee2 b.cs a4 cc: 1400002f b 188 d0: b9401fe1 ldr w1, [sp,#28] d4: b9402be0 ldr w0, [sp,#40] d8: 1ac00820 udiv w0, w1, w0 dc: b90027e0 str w0, [sp,#36] e0: b9401fe0 ldr w0, [sp,#28] e4: b9402be1 ldr w1, [sp,#40] e8: 1ac10802 udiv w2, w0, w1 ec: b9402be1 ldr w1, [sp,#40] f0: 1b017c41 mul w1, w2, w1 f4: 4b010000 sub w0, w0, w1 f8: b9001fe0 str w0, [sp,#28] fc: b9402be1 ldr w1, [sp,#40] 100: b9401be0 ldr w0, [sp,#24] 104: 1ac00820 udiv w0, w1, w0 108: b9002be0 str w0, [sp,#40] 10c: b9402fe0 ldr w0, [sp,#44] 110: 7100001f cmp w0, #0x0 114: 540000e1 b.ne 130 118: b94027e0 ldr w0, [sp,#36] 11c: 7100001f cmp w0, #0x0 120: 5400008c b.gt 130 124: b9402be0 ldr w0, [sp,#40] 128: 7100001f cmp w0, #0x0 12c: 540002e1 b.ne 188 130: f94007e1 ldr x1, [sp,#8] 134: 91000420 add x0, x1, #0x1 138: f90007e0 str x0, [sp,#8] 13c: b94027e0 ldr w0, [sp,#36] 140: 7100241f cmp w0, #0x9 144: 5400010d b.le 164 148: b94017e0 ldr w0, [sp,#20] 14c: 7100001f cmp w0, #0x0 150: 54000060 b.eq 15c 154: 528006e0 mov w0, #0x37 // #55 158: 14000004 b 168 15c: 52800ae0 mov w0, #0x57 // #87 160: 14000002 b 168 164: 52800600 mov w0, #0x30 // #48 168: b94027e2 ldr w2, [sp,#36] 16c: 53001c42 uxtb w2, w2 170: 0b020000 add w0, w0, w2 174: 53001c00 uxtb w0, w0 178: 39000020 strb w0, [x1] 17c: b9402fe0 ldr w0, [sp,#44] 180: 11000400 add w0, w0, #0x1 184: b9002fe0 str w0, [sp,#44] 188: b9402be0 ldr w0, [sp,#40] 18c: 7100001f cmp w0, #0x0 190: 54fffa01 b.ne d0 194: f94007e0 ldr x0, [sp,#8] 198: 3900001f strb wzr, [x0] 19c: d503201f nop 1a0: 9100c3ff add sp, sp, #0x30 1a4: d65f03c0 ret 00000000000001a8 : 1a8: a9be7bfd stp x29, x30, [sp,#-32]! 1ac: 910003fd mov x29, sp 1b0: b9001fa0 str w0, [x29,#28] 1b4: f9000ba1 str x1, [x29,#16] 1b8: b9401fa0 ldr w0, [x29,#28] 1bc: 7100001f cmp w0, #0x0 1c0: 5400012a b.ge 1e4 1c4: b9401fa0 ldr w0, [x29,#28] 1c8: 4b0003e0 neg w0, w0 1cc: b9001fa0 str w0, [x29,#28] 1d0: f9400ba0 ldr x0, [x29,#16] 1d4: 91000401 add x1, x0, #0x1 1d8: f9000ba1 str x1, [x29,#16] 1dc: 528005a1 mov w1, #0x2d // #45 1e0: 39000001 strb w1, [x0] 1e4: b9401fa0 ldr w0, [x29,#28] 1e8: f9400ba3 ldr x3, [x29,#16] 1ec: 52800002 mov w2, #0x0 // #0 1f0: 52800141 mov w1, #0xa // #10 1f4: 97ffffa3 bl 80 1f8: d503201f nop 1fc: a8c27bfd ldp x29, x30, [sp],#32 200: d65f03c0 ret 0000000000000204 : 204: d10043ff sub sp, sp, #0x10 208: 39003fe0 strb w0, [sp,#15] 20c: 39403fe0 ldrb w0, [sp,#15] 210: 7100bc1f cmp w0, #0x2f 214: 540000e9 b.ls 230 218: 39403fe0 ldrb w0, [sp,#15] 21c: 7100e41f cmp w0, #0x39 220: 54000088 b.hi 230 224: 39403fe0 ldrb w0, [sp,#15] 228: 5100c000 sub w0, w0, #0x30 22c: 14000014 b 27c 230: 39403fe0 ldrb w0, [sp,#15] 234: 7101801f cmp w0, #0x60 238: 540000e9 b.ls 254 23c: 39403fe0 ldrb w0, [sp,#15] 240: 7101981f cmp w0, #0x66 244: 54000088 b.hi 254 248: 39403fe0 ldrb w0, [sp,#15] 24c: 51015c00 sub w0, w0, #0x57 250: 1400000b b 27c 254: 39403fe0 ldrb w0, [sp,#15] 258: 7101001f cmp w0, #0x40 25c: 540000e9 b.ls 278 260: 39403fe0 ldrb w0, [sp,#15] 264: 7101181f cmp w0, #0x46 268: 54000088 b.hi 278 26c: 39403fe0 ldrb w0, [sp,#15] 270: 5100dc00 sub w0, w0, #0x37 274: 14000002 b 27c 278: 12800000 mov w0, #0xffffffff // #-1 27c: 910043ff add sp, sp, #0x10 280: d65f03c0 ret 0000000000000284 : 284: a9bc7bfd stp x29, x30, [sp,#-64]! 288: 910003fd mov x29, sp 28c: 3900bfa0 strb w0, [x29,#47] 290: f90013a1 str x1, [x29,#32] 294: b9002ba2 str w2, [x29,#40] 298: f9000fa3 str x3, [x29,#24] 29c: f94013a0 ldr x0, [x29,#32] 2a0: f9400000 ldr x0, [x0] 2a4: f9001fa0 str x0, [x29,#56] 2a8: b90037bf str wzr, [x29,#52] 2ac: 14000010 b 2ec 2b0: b94033a1 ldr w1, [x29,#48] 2b4: b9402ba0 ldr w0, [x29,#40] 2b8: 6b00003f cmp w1, w0 2bc: 5400026c b.gt 308 2c0: b94037a1 ldr w1, [x29,#52] 2c4: b9402ba0 ldr w0, [x29,#40] 2c8: 1b007c21 mul w1, w1, w0 2cc: b94033a0 ldr w0, [x29,#48] 2d0: 0b000020 add w0, w1, w0 2d4: b90037a0 str w0, [x29,#52] 2d8: f9401fa0 ldr x0, [x29,#56] 2dc: 91000401 add x1, x0, #0x1 2e0: f9001fa1 str x1, [x29,#56] 2e4: 39400000 ldrb w0, [x0] 2e8: 3900bfa0 strb w0, [x29,#47] 2ec: 3940bfa0 ldrb w0, [x29,#47] 2f0: 97ffffc5 bl 204 2f4: b90033a0 str w0, [x29,#48] 2f8: b94033a0 ldr w0, [x29,#48] 2fc: 7100001f cmp w0, #0x0 300: 54fffd8a b.ge 2b0 304: 14000002 b 30c 308: d503201f nop 30c: f94013a0 ldr x0, [x29,#32] 310: f9401fa1 ldr x1, [x29,#56] 314: f9000001 str x1, [x0] 318: f9400fa0 ldr x0, [x29,#24] 31c: b94037a1 ldr w1, [x29,#52] 320: b9000001 str w1, [x0] 324: 3940bfa0 ldrb w0, [x29,#47] 328: a8c47bfd ldp x29, x30, [sp],#64 32c: d65f03c0 ret 0000000000000330 : 330: a9bc7bfd stp x29, x30, [sp,#-64]! 334: 910003fd mov x29, sp 338: f90017a0 str x0, [x29,#40] 33c: f90013a1 str x1, [x29,#32] 340: b9001fa2 str w2, [x29,#28] 344: 39006fa3 strb w3, [x29,#27] 348: f9000ba4 str x4, [x29,#16] 34c: 39406fa0 ldrb w0, [x29,#27] 350: 7100001f cmp w0, #0x0 354: 54000060 b.eq 360 358: 52800600 mov w0, #0x30 // #48 35c: 14000002 b 364 360: 52800400 mov w0, #0x20 // #32 364: 3900dfa0 strb w0, [x29,#55] 368: f9400ba0 ldr x0, [x29,#16] 36c: f9001fa0 str x0, [x29,#56] 370: 14000004 b 380 374: b9401fa0 ldr w0, [x29,#28] 378: 51000400 sub w0, w0, #0x1 37c: b9001fa0 str w0, [x29,#28] 380: f9401fa0 ldr x0, [x29,#56] 384: 91000401 add x1, x0, #0x1 388: f9001fa1 str x1, [x29,#56] 38c: 39400000 ldrb w0, [x0] 390: 7100001f cmp w0, #0x0 394: 54000120 b.eq 3b8 398: b9401fa0 ldr w0, [x29,#28] 39c: 7100001f cmp w0, #0x0 3a0: 54fffeac b.gt 374 3a4: 14000005 b 3b8 3a8: f94013a2 ldr x2, [x29,#32] 3ac: 3940dfa1 ldrb w1, [x29,#55] 3b0: f94017a0 ldr x0, [x29,#40] 3b4: d63f0040 blr x2 3b8: b9401fa0 ldr w0, [x29,#28] 3bc: 51000401 sub w1, w0, #0x1 3c0: b9001fa1 str w1, [x29,#28] 3c4: 7100001f cmp w0, #0x0 3c8: 54ffff0c b.gt 3a8 3cc: 14000005 b 3e0 3d0: f94013a2 ldr x2, [x29,#32] 3d4: 3940dba1 ldrb w1, [x29,#54] 3d8: f94017a0 ldr x0, [x29,#40] 3dc: d63f0040 blr x2 3e0: f9400ba0 ldr x0, [x29,#16] 3e4: 91000401 add x1, x0, #0x1 3e8: f9000ba1 str x1, [x29,#16] 3ec: 39400000 ldrb w0, [x0] 3f0: 3900dba0 strb w0, [x29,#54] 3f4: 3940dba0 ldrb w0, [x29,#54] 3f8: 7100001f cmp w0, #0x0 3fc: 54fffea1 b.ne 3d0 400: d503201f nop 404: a8c47bfd ldp x29, x30, [sp],#64 408: d65f03c0 ret 000000000000040c : 40c: a9ba7bfd stp x29, x30, [sp,#-96]! 410: 910003fd mov x29, sp 414: f9000bf3 str x19, [sp,#16] 418: f9001fa0 str x0, [x29,#56] 41c: f9001ba1 str x1, [x29,#48] 420: f90017a2 str x2, [x29,#40] 424: aa0303f3 mov x19, x3 428: 140000fd b 81c 42c: 39417fa0 ldrb w0, [x29,#95] 430: 7100941f cmp w0, #0x25 434: 540000c0 b.eq 44c 438: f9401ba2 ldr x2, [x29,#48] 43c: 39417fa1 ldrb w1, [x29,#95] 440: f9401fa0 ldr x0, [x29,#56] 444: d63f0040 blr x2 448: 140000f5 b 81c 44c: 39017bbf strb wzr, [x29,#94] 450: b9004fbf str wzr, [x29,#76] 454: f94017a0 ldr x0, [x29,#40] 458: 91000401 add x1, x0, #0x1 45c: f90017a1 str x1, [x29,#40] 460: 39400000 ldrb w0, [x0] 464: 39017fa0 strb w0, [x29,#95] 468: 39417fa0 ldrb w0, [x29,#95] 46c: 7100c01f cmp w0, #0x30 470: 54000101 b.ne 490 474: f94017a0 ldr x0, [x29,#40] 478: 91000401 add x1, x0, #0x1 47c: f90017a1 str x1, [x29,#40] 480: 39400000 ldrb w0, [x0] 484: 39017fa0 strb w0, [x29,#95] 488: 52800020 mov w0, #0x1 // #1 48c: 39017ba0 strb w0, [x29,#94] 490: 39417fa0 ldrb w0, [x29,#95] 494: 7100bc1f cmp w0, #0x2f 498: 54000189 b.ls 4c8 49c: 39417fa0 ldrb w0, [x29,#95] 4a0: 7100e41f cmp w0, #0x39 4a4: 54000128 b.hi 4c8 4a8: 910133a1 add x1, x29, #0x4c 4ac: 9100a3a0 add x0, x29, #0x28 4b0: aa0103e3 mov x3, x1 4b4: 52800142 mov w2, #0xa // #10 4b8: aa0003e1 mov x1, x0 4bc: 39417fa0 ldrb w0, [x29,#95] 4c0: 97ffff71 bl 284 4c4: 39017fa0 strb w0, [x29,#95] 4c8: 39417fa0 ldrb w0, [x29,#95] 4cc: 71018c1f cmp w0, #0x63 4d0: 540011c0 b.eq 708 4d4: 71018c1f cmp w0, #0x63 4d8: 5400010c b.gt 4f8 4dc: 7100941f cmp w0, #0x25 4e0: 54001940 b.eq 808 4e4: 7101601f cmp w0, #0x58 4e8: 54000b60 b.eq 654 4ec: 7100001f cmp w0, #0x0 4f0: 54001a80 b.eq 840 4f4: 140000c9 b 818 4f8: 7101cc1f cmp w0, #0x73 4fc: 54001440 b.eq 784 500: 7101cc1f cmp w0, #0x73 504: 5400008c b.gt 514 508: 7101901f cmp w0, #0x64 50c: 540005c0 b.eq 5c4 510: 140000c2 b 818 514: 7101d41f cmp w0, #0x75 518: 54000080 b.eq 528 51c: 7101e01f cmp w0, #0x78 520: 540009a0 b.eq 654 524: 140000bd b 818 528: b9401a60 ldr w0, [x19,#24] 52c: f9400261 ldr x1, [x19] 530: 7100001f cmp w0, #0x0 534: 540000eb b.lt 550 538: aa0103e0 mov x0, x1 53c: 91002c00 add x0, x0, #0xb 540: 927df000 and x0, x0, #0xfffffffffffffff8 544: f9000260 str x0, [x19] 548: aa0103e0 mov x0, x1 54c: 1400000f b 588 550: 11002002 add w2, w0, #0x8 554: b9001a62 str w2, [x19,#24] 558: b9401a62 ldr w2, [x19,#24] 55c: 7100005f cmp w2, #0x0 560: 540000ed b.le 57c 564: aa0103e0 mov x0, x1 568: 91002c00 add x0, x0, #0xb 56c: 927df000 and x0, x0, #0xfffffffffffffff8 570: f9000260 str x0, [x19] 574: aa0103e0 mov x0, x1 578: 14000004 b 588 57c: f9400661 ldr x1, [x19,#8] 580: 93407c00 sxtw x0, w0 584: 8b000020 add x0, x1, x0 588: b9400000 ldr w0, [x0] 58c: 910143a1 add x1, x29, #0x50 590: aa0103e3 mov x3, x1 594: 52800002 mov w2, #0x0 // #0 598: 52800141 mov w1, #0xa // #10 59c: 97fffeb9 bl 80 5a0: b9404fa0 ldr w0, [x29,#76] 5a4: 910143a1 add x1, x29, #0x50 5a8: aa0103e4 mov x4, x1 5ac: 39417ba3 ldrb w3, [x29,#94] 5b0: 2a0003e2 mov w2, w0 5b4: f9401ba1 ldr x1, [x29,#48] 5b8: f9401fa0 ldr x0, [x29,#56] 5bc: 97ffff5d bl 330 5c0: 14000097 b 81c 5c4: b9401a60 ldr w0, [x19,#24] 5c8: f9400261 ldr x1, [x19] 5cc: 7100001f cmp w0, #0x0 5d0: 540000eb b.lt 5ec 5d4: aa0103e0 mov x0, x1 5d8: 91002c00 add x0, x0, #0xb 5dc: 927df000 and x0, x0, #0xfffffffffffffff8 5e0: f9000260 str x0, [x19] 5e4: aa0103e0 mov x0, x1 5e8: 1400000f b 624 5ec: 11002002 add w2, w0, #0x8 5f0: b9001a62 str w2, [x19,#24] 5f4: b9401a62 ldr w2, [x19,#24] 5f8: 7100005f cmp w2, #0x0 5fc: 540000ed b.le 618 600: aa0103e0 mov x0, x1 604: 91002c00 add x0, x0, #0xb 608: 927df000 and x0, x0, #0xfffffffffffffff8 60c: f9000260 str x0, [x19] 610: aa0103e0 mov x0, x1 614: 14000004 b 624 618: f9400661 ldr x1, [x19,#8] 61c: 93407c00 sxtw x0, w0 620: 8b000020 add x0, x1, x0 624: b9400000 ldr w0, [x0] 628: 910143a1 add x1, x29, #0x50 62c: 97fffedf bl 1a8 630: b9404fa0 ldr w0, [x29,#76] 634: 910143a1 add x1, x29, #0x50 638: aa0103e4 mov x4, x1 63c: 39417ba3 ldrb w3, [x29,#94] 640: 2a0003e2 mov w2, w0 644: f9401ba1 ldr x1, [x29,#48] 648: f9401fa0 ldr x0, [x29,#56] 64c: 97ffff39 bl 330 650: 14000073 b 81c 654: b9401a60 ldr w0, [x19,#24] 658: f9400261 ldr x1, [x19] 65c: 7100001f cmp w0, #0x0 660: 540000eb b.lt 67c 664: aa0103e0 mov x0, x1 668: 91002c00 add x0, x0, #0xb 66c: 927df000 and x0, x0, #0xfffffffffffffff8 670: f9000260 str x0, [x19] 674: aa0103e0 mov x0, x1 678: 1400000f b 6b4 67c: 11002002 add w2, w0, #0x8 680: b9001a62 str w2, [x19,#24] 684: b9401a62 ldr w2, [x19,#24] 688: 7100005f cmp w2, #0x0 68c: 540000ed b.le 6a8 690: aa0103e0 mov x0, x1 694: 91002c00 add x0, x0, #0xb 698: 927df000 and x0, x0, #0xfffffffffffffff8 69c: f9000260 str x0, [x19] 6a0: aa0103e0 mov x0, x1 6a4: 14000004 b 6b4 6a8: f9400661 ldr x1, [x19,#8] 6ac: 93407c00 sxtw x0, w0 6b0: 8b000020 add x0, x1, x0 6b4: b9400004 ldr w4, [x0] 6b8: 39417fa0 ldrb w0, [x29,#95] 6bc: 7101601f cmp w0, #0x58 6c0: 1a9f17e0 cset w0, eq 6c4: 53001c00 uxtb w0, w0 6c8: 2a0003e1 mov w1, w0 6cc: 910143a0 add x0, x29, #0x50 6d0: aa0003e3 mov x3, x0 6d4: 2a0103e2 mov w2, w1 6d8: 52800201 mov w1, #0x10 // #16 6dc: 2a0403e0 mov w0, w4 6e0: 97fffe68 bl 80 6e4: b9404fa0 ldr w0, [x29,#76] 6e8: 910143a1 add x1, x29, #0x50 6ec: aa0103e4 mov x4, x1 6f0: 39417ba3 ldrb w3, [x29,#94] 6f4: 2a0003e2 mov w2, w0 6f8: f9401ba1 ldr x1, [x29,#48] 6fc: f9401fa0 ldr x0, [x29,#56] 700: 97ffff0c bl 330 704: 14000046 b 81c 708: b9401a60 ldr w0, [x19,#24] 70c: f9400261 ldr x1, [x19] 710: 7100001f cmp w0, #0x0 714: 540000eb b.lt 730 718: aa0103e0 mov x0, x1 71c: 91002c00 add x0, x0, #0xb 720: 927df000 and x0, x0, #0xfffffffffffffff8 724: f9000260 str x0, [x19] 728: aa0103e0 mov x0, x1 72c: 1400000f b 768 730: 11002002 add w2, w0, #0x8 734: b9001a62 str w2, [x19,#24] 738: b9401a62 ldr w2, [x19,#24] 73c: 7100005f cmp w2, #0x0 740: 540000ed b.le 75c 744: aa0103e0 mov x0, x1 748: 91002c00 add x0, x0, #0xb 74c: 927df000 and x0, x0, #0xfffffffffffffff8 750: f9000260 str x0, [x19] 754: aa0103e0 mov x0, x1 758: 14000004 b 768 75c: f9400661 ldr x1, [x19,#8] 760: 93407c00 sxtw x0, w0 764: 8b000020 add x0, x1, x0 768: b9400000 ldr w0, [x0] 76c: 53001c00 uxtb w0, w0 770: f9401ba2 ldr x2, [x29,#48] 774: 2a0003e1 mov w1, w0 778: f9401fa0 ldr x0, [x29,#56] 77c: d63f0040 blr x2 780: 14000027 b 81c 784: b9404fa5 ldr w5, [x29,#76] 788: b9401a60 ldr w0, [x19,#24] 78c: f9400261 ldr x1, [x19] 790: 7100001f cmp w0, #0x0 794: 540000eb b.lt 7b0 798: aa0103e0 mov x0, x1 79c: 91003c00 add x0, x0, #0xf 7a0: 927df000 and x0, x0, #0xfffffffffffffff8 7a4: f9000260 str x0, [x19] 7a8: aa0103e0 mov x0, x1 7ac: 1400000f b 7e8 7b0: 11002002 add w2, w0, #0x8 7b4: b9001a62 str w2, [x19,#24] 7b8: b9401a62 ldr w2, [x19,#24] 7bc: 7100005f cmp w2, #0x0 7c0: 540000ed b.le 7dc 7c4: aa0103e0 mov x0, x1 7c8: 91003c00 add x0, x0, #0xf 7cc: 927df000 and x0, x0, #0xfffffffffffffff8 7d0: f9000260 str x0, [x19] 7d4: aa0103e0 mov x0, x1 7d8: 14000004 b 7e8 7dc: f9400661 ldr x1, [x19,#8] 7e0: 93407c00 sxtw x0, w0 7e4: 8b000020 add x0, x1, x0 7e8: f9400000 ldr x0, [x0] 7ec: aa0003e4 mov x4, x0 7f0: 52800003 mov w3, #0x0 // #0 7f4: 2a0503e2 mov w2, w5 7f8: f9401ba1 ldr x1, [x29,#48] 7fc: f9401fa0 ldr x0, [x29,#56] 800: 97fffecc bl 330 804: 14000006 b 81c 808: f9401ba2 ldr x2, [x29,#48] 80c: 39417fa1 ldrb w1, [x29,#95] 810: f9401fa0 ldr x0, [x29,#56] 814: d63f0040 blr x2 818: d503201f nop 81c: f94017a0 ldr x0, [x29,#40] 820: 91000401 add x1, x0, #0x1 824: f90017a1 str x1, [x29,#40] 828: 39400000 ldrb w0, [x0] 82c: 39017fa0 strb w0, [x29,#95] 830: 39417fa0 ldrb w0, [x29,#95] 834: 7100001f cmp w0, #0x0 838: 54ffdfa1 b.ne 42c 83c: 14000002 b 844 840: d503201f nop 844: d503201f nop 848: f9400bf3 ldr x19, [sp,#16] 84c: a8c67bfd ldp x29, x30, [sp],#96 850: d65f03c0 ret 0000000000000854 : 854: d10043ff sub sp, sp, #0x10 858: f90007e0 str x0, [sp,#8] 85c: f90003e1 str x1, [sp] 860: 90000000 adrp x0, 0 <_start> 864: 9132c000 add x0, x0, #0xcb0 868: f94003e1 ldr x1, [sp] 86c: f9000001 str x1, [x0] 870: 90000000 adrp x0, 0 <_start> 874: 9132e000 add x0, x0, #0xcb8 878: f94007e1 ldr x1, [sp,#8] 87c: f9000001 str x1, [x0] 880: d503201f nop 884: 910043ff add sp, sp, #0x10 888: d65f03c0 ret 000000000000088c : 88c: a9b67bfd stp x29, x30, [sp,#-160]! 890: 910003fd mov x29, sp 894: f9001fa0 str x0, [x29,#56] 898: f90037a1 str x1, [x29,#104] 89c: f9003ba2 str x2, [x29,#112] 8a0: f9003fa3 str x3, [x29,#120] 8a4: f90043a4 str x4, [x29,#128] 8a8: f90047a5 str x5, [x29,#136] 8ac: f9004ba6 str x6, [x29,#144] 8b0: f9004fa7 str x7, [x29,#152] 8b4: 910283a0 add x0, x29, #0xa0 8b8: f90023a0 str x0, [x29,#64] 8bc: 910283a0 add x0, x29, #0xa0 8c0: f90027a0 str x0, [x29,#72] 8c4: 910183a0 add x0, x29, #0x60 8c8: f9002ba0 str x0, [x29,#80] 8cc: 128006e0 mov w0, #0xffffffc8 // #-56 8d0: b9005ba0 str w0, [x29,#88] 8d4: b9005fbf str wzr, [x29,#92] 8d8: 90000000 adrp x0, 0 <_start> 8dc: 9132e000 add x0, x0, #0xcb8 8e0: f9400004 ldr x4, [x0] 8e4: 90000000 adrp x0, 0 <_start> 8e8: 9132c000 add x0, x0, #0xcb0 8ec: f9400005 ldr x5, [x0] 8f0: 910043a2 add x2, x29, #0x10 8f4: 910103a3 add x3, x29, #0x40 8f8: a9400460 ldp x0, x1, [x3] 8fc: a9000440 stp x0, x1, [x2] 900: a9410460 ldp x0, x1, [x3,#16] 904: a9010440 stp x0, x1, [x2,#16] 908: 910043a0 add x0, x29, #0x10 90c: aa0003e3 mov x3, x0 910: f9401fa2 ldr x2, [x29,#56] 914: aa0503e1 mov x1, x5 918: aa0403e0 mov x0, x4 91c: 97fffebc bl 40c 920: d503201f nop 924: a8ca7bfd ldp x29, x30, [sp],#160 928: d65f03c0 ret 000000000000092c : 92c: d10043ff sub sp, sp, #0x10 930: f90007e0 str x0, [sp,#8] 934: 39001fe1 strb w1, [sp,#7] 938: f94007e0 ldr x0, [sp,#8] 93c: f9400000 ldr x0, [x0] 940: 91000402 add x2, x0, #0x1 944: f94007e1 ldr x1, [sp,#8] 948: f9000022 str x2, [x1] 94c: 39401fe1 ldrb w1, [sp,#7] 950: 39000001 strb w1, [x0] 954: d503201f nop 958: 910043ff add sp, sp, #0x10 95c: d65f03c0 ret 0000000000000960 : 960: a9b77bfd stp x29, x30, [sp,#-144]! 964: 910003fd mov x29, sp 968: f9001fa0 str x0, [x29,#56] 96c: f9001ba1 str x1, [x29,#48] 970: f90033a2 str x2, [x29,#96] 974: f90037a3 str x3, [x29,#104] 978: f9003ba4 str x4, [x29,#112] 97c: f9003fa5 str x5, [x29,#120] 980: f90043a6 str x6, [x29,#128] 984: f90047a7 str x7, [x29,#136] 988: 910243a0 add x0, x29, #0x90 98c: f90023a0 str x0, [x29,#64] 990: 910243a0 add x0, x29, #0x90 994: f90027a0 str x0, [x29,#72] 998: 910183a0 add x0, x29, #0x60 99c: f9002ba0 str x0, [x29,#80] 9a0: 128005e0 mov w0, #0xffffffd0 // #-48 9a4: b9005ba0 str w0, [x29,#88] 9a8: b9005fbf str wzr, [x29,#92] 9ac: 910043a2 add x2, x29, #0x10 9b0: 910103a3 add x3, x29, #0x40 9b4: a9400460 ldp x0, x1, [x3] 9b8: a9000440 stp x0, x1, [x2] 9bc: a9410460 ldp x0, x1, [x3,#16] 9c0: a9010440 stp x0, x1, [x2,#16] 9c4: 910043a2 add x2, x29, #0x10 9c8: 90000000 adrp x0, 0 <_start> 9cc: 9124b001 add x1, x0, #0x92c 9d0: 9100e3a0 add x0, x29, #0x38 9d4: aa0203e3 mov x3, x2 9d8: f9401ba2 ldr x2, [x29,#48] 9dc: 97fffe8c bl 40c 9e0: 9100e3a0 add x0, x29, #0x38 9e4: 52800001 mov w1, #0x0 // #0 9e8: 97ffffd1 bl 92c 9ec: d503201f nop 9f0: a8c97bfd ldp x29, x30, [sp],#144 9f4: d65f03c0 ret 00000000000009f8 : 9f8: a9be7bfd stp x29, x30, [sp,#-32]! 9fc: 910003fd mov x29, sp a00: 39007fa0 strb w0, [x29,#31] a04: d28a0a80 mov x0, #0x5054 // #20564 a08: f2a7e420 movk x0, #0x3f21, lsl #16 a0c: 9400009d bl c80 a10: 121b0000 and w0, w0, #0x20 a14: 7100001f cmp w0, #0x0 a18: 54000041 b.ne a20 a1c: 17fffffa b a04 a20: d503201f nop a24: 39407fa0 ldrb w0, [x29,#31] a28: 2a0003e1 mov w1, w0 a2c: d28a0800 mov x0, #0x5040 // #20544 a30: f2a7e420 movk x0, #0x3f21, lsl #16 a34: 94000091 bl c78 a38: d503201f nop a3c: a8c27bfd ldp x29, x30, [sp],#32 a40: d65f03c0 ret 0000000000000a44 : a44: a9bf7bfd stp x29, x30, [sp,#-16]! a48: 910003fd mov x29, sp a4c: d28a0a80 mov x0, #0x5054 // #20564 a50: f2a7e420 movk x0, #0x3f21, lsl #16 a54: 9400008b bl c80 a58: 12000000 and w0, w0, #0x1 a5c: 7100001f cmp w0, #0x0 a60: 54000041 b.ne a68 a64: 17fffffa b a4c a68: d503201f nop a6c: d28a0800 mov x0, #0x5040 // #20544 a70: f2a7e420 movk x0, #0x3f21, lsl #16 a74: 94000083 bl c80 a78: 53001c00 uxtb w0, w0 a7c: a8c17bfd ldp x29, x30, [sp],#16 a80: d65f03c0 ret 0000000000000a84 : a84: a9bd7bfd stp x29, x30, [sp,#-48]! a88: 910003fd mov x29, sp a8c: f9000fa0 str x0, [x29,#24] a90: b9002fbf str wzr, [x29,#44] a94: 14000009 b ab8 a98: b9802fa0 ldrsw x0, [x29,#44] a9c: f9400fa1 ldr x1, [x29,#24] aa0: 8b000020 add x0, x1, x0 aa4: 39400000 ldrb w0, [x0] aa8: 97ffffd4 bl 9f8 aac: b9402fa0 ldr w0, [x29,#44] ab0: 11000400 add w0, w0, #0x1 ab4: b9002fa0 str w0, [x29,#44] ab8: b9802fa0 ldrsw x0, [x29,#44] abc: f9400fa1 ldr x1, [x29,#24] ac0: 8b000020 add x0, x1, x0 ac4: 39400000 ldrb w0, [x0] ac8: 7100001f cmp w0, #0x0 acc: 54fffe61 b.ne a98 ad0: d503201f nop ad4: a8c37bfd ldp x29, x30, [sp],#48 ad8: d65f03c0 ret 0000000000000adc : adc: a9be7bfd stp x29, x30, [sp,#-32]! ae0: 910003fd mov x29, sp ae4: d2800080 mov x0, #0x4 // #4 ae8: f2a7e400 movk x0, #0x3f20, lsl #16 aec: 94000065 bl c80 af0: b9001fa0 str w0, [x29,#28] af4: b9401fa0 ldr w0, [x29,#28] af8: 12117000 and w0, w0, #0xffff8fff afc: b9001fa0 str w0, [x29,#28] b00: b9401fa0 ldr w0, [x29,#28] b04: 32130000 orr w0, w0, #0x2000 b08: b9001fa0 str w0, [x29,#28] b0c: b9401fa0 ldr w0, [x29,#28] b10: 120e7000 and w0, w0, #0xfffc7fff b14: b9001fa0 str w0, [x29,#28] b18: b9401fa0 ldr w0, [x29,#28] b1c: 32100000 orr w0, w0, #0x10000 b20: b9001fa0 str w0, [x29,#28] b24: b9401fa1 ldr w1, [x29,#28] b28: d2800080 mov x0, #0x4 // #4 b2c: f2a7e400 movk x0, #0x3f20, lsl #16 b30: 94000052 bl c78 b34: 52800001 mov w1, #0x0 // #0 b38: d2801280 mov x0, #0x94 // #148 b3c: f2a7e400 movk x0, #0x3f20, lsl #16 b40: 9400004e bl c78 b44: d28012c0 mov x0, #0x96 // #150 b48: 94000050 bl c88 b4c: 52980001 mov w1, #0xc000 // #49152 b50: d2801300 mov x0, #0x98 // #152 b54: f2a7e400 movk x0, #0x3f20, lsl #16 b58: 94000048 bl c78 b5c: d28012c0 mov x0, #0x96 // #150 b60: 9400004a bl c88 b64: 52800001 mov w1, #0x0 // #0 b68: d2801300 mov x0, #0x98 // #152 b6c: f2a7e400 movk x0, #0x3f20, lsl #16 b70: 94000042 bl c78 b74: 52800021 mov w1, #0x1 // #1 b78: d28a0080 mov x0, #0x5004 // #20484 b7c: f2a7e420 movk x0, #0x3f21, lsl #16 b80: 9400003e bl c78 b84: 52800001 mov w1, #0x0 // #0 b88: d28a0c00 mov x0, #0x5060 // #20576 b8c: f2a7e420 movk x0, #0x3f21, lsl #16 b90: 9400003a bl c78 b94: 52800001 mov w1, #0x0 // #0 b98: d28a0880 mov x0, #0x5044 // #20548 b9c: f2a7e420 movk x0, #0x3f21, lsl #16 ba0: 94000036 bl c78 ba4: 52800061 mov w1, #0x3 // #3 ba8: d28a0980 mov x0, #0x504c // #20556 bac: f2a7e420 movk x0, #0x3f21, lsl #16 bb0: 94000032 bl c78 bb4: 52800001 mov w1, #0x0 // #0 bb8: d28a0a00 mov x0, #0x5050 // #20560 bbc: f2a7e420 movk x0, #0x3f21, lsl #16 bc0: 9400002e bl c78 bc4: 528021c1 mov w1, #0x10e // #270 bc8: d28a0d00 mov x0, #0x5068 // #20584 bcc: f2a7e420 movk x0, #0x3f21, lsl #16 bd0: 9400002a bl c78 bd4: 52800061 mov w1, #0x3 // #3 bd8: d28a0c00 mov x0, #0x5060 // #20576 bdc: f2a7e420 movk x0, #0x3f21, lsl #16 be0: 94000026 bl c78 be4: d503201f nop be8: a8c27bfd ldp x29, x30, [sp],#32 bec: d65f03c0 ret 0000000000000bf0 : bf0: a9be7bfd stp x29, x30, [sp,#-32]! bf4: 910003fd mov x29, sp bf8: f9000fa0 str x0, [x29,#24] bfc: 39005fa1 strb w1, [x29,#23] c00: 39405fa0 ldrb w0, [x29,#23] c04: 97ffff7d bl 9f8 c08: d503201f nop c0c: a8c27bfd ldp x29, x30, [sp],#32 c10: d65f03c0 ret 0000000000000c14 : c14: a9be7bfd stp x29, x30, [sp,#-32]! c18: 910003fd mov x29, sp c1c: 97ffffb0 bl adc c20: 90000000 adrp x0, 0 <_start> c24: 912fc000 add x0, x0, #0xbf0 c28: aa0003e1 mov x1, x0 c2c: d2800000 mov x0, #0x0 // #0 c30: 97ffff09 bl 854 c34: 9400000e bl c6c c38: b9001fa0 str w0, [x29,#28] c3c: 90000000 adrp x0, 0 <_start> c40: 91326000 add x0, x0, #0xc98 c44: b9401fa1 ldr w1, [x29,#28] c48: 97ffff11 bl 88c c4c: 97ffff7e bl a44 c50: 53001c00 uxtb w0, w0 c54: 97ffff69 bl 9f8 c58: 17fffffd b c4c 0000000000000c5c : c5c: f800841f str xzr, [x0],#8 c60: f1002021 subs x1, x1, #0x8 c64: 54ffffcc b.gt c5c c68: d65f03c0 ret 0000000000000c6c : c6c: d5384240 mrs x0, currentel c70: d342fc00 lsr x0, x0, #2 c74: d65f03c0 ret 0000000000000c78 : c78: b9000001 str w1, [x0] c7c: d65f03c0 ret 0000000000000c80 : c80: b9400000 ldr w0, [x0] c84: d65f03c0 ret 0000000000000c88 : c88: f1000400 subs x0, x0, #0x1 c8c: 54ffffe1 b.ne c88 c90: d65f03c0 ret ================================================ FILE: exercises/lesson02/2/bl4ckout31/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson02/2/bl4ckout31/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson02/2/bl4ckout31/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) // *************************************** // CPACR_EL1, Architectural Feature Access Control Register (EL1) Page 2411 of AArch64-Reference-Manual. // *************************************** #define CPACR_FPEN (3 << 20) #define CPACR_VALUE (CPACR_FPEN) #endif ================================================ FILE: exercises/lesson02/2/bl4ckout31/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson02/2/bl4ckout31/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson02/2/bl4ckout31/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson02/2/bl4ckout31/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson02/2/bl4ckout31/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson02/2/bl4ckout31/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson02/2/bl4ckout31/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson02/2/bl4ckout31/sans ================================================ build/kernel8.elf: file format elf64-littleaarch64 Disassembly of section .text.boot: 0000000000000000 <_start>: 0: d53800a0 mrs x0, mpidr_el1 4: 92401c00 and x0, x0, #0xff 8: b4000060 cbz x0, 14 c: 14000001 b 10 0000000000000010 : 10: 14000000 b 10 0000000000000014 : 14: 58000260 ldr x0, 60 18: d5181000 msr sctlr_el1, x0 1c: 58000260 ldr x0, 68 20: d51c1100 msr hcr_el2, x0 24: 58000260 ldr x0, 70 28: d51e1100 msr scr_el3, x0 2c: 58000260 ldr x0, 78 30: d51e4000 msr spsr_el3, x0 34: 10000060 adr x0, 40 38: d51e4020 msr elr_el3, x0 3c: d69f03e0 eret 0000000000000040 : 40: 100065c0 adr x0, cf8 44: 10006621 adr x1, d08 48: cb000021 sub x1, x1, x0 4c: 94000316 bl ca4 50: b26a03ff mov sp, #0x400000 // #4194304 54: 94000302 bl c5c 58: 17ffffee b 10 5c: 00000000 .word 0x00000000 60: 30d00800 .word 0x30d00800 64: 00000000 .word 0x00000000 68: 80000000 .word 0x80000000 6c: 00000000 .word 0x00000000 70: 00000431 .word 0x00000431 74: 00000000 .word 0x00000000 78: 000001c5 .word 0x000001c5 7c: 00000000 .word 0x00000000 Disassembly of section .text: 0000000000000080 : 80: d100c3ff sub sp, sp, #0x30 84: b9001fe0 str w0, [sp,#28] 88: b9001be1 str w1, [sp,#24] 8c: b90017e2 str w2, [sp,#20] 90: f90007e3 str x3, [sp,#8] 94: b9002fff str wzr, [sp,#44] 98: 52800020 mov w0, #0x1 // #1 9c: b9002be0 str w0, [sp,#40] a0: 14000005 b b4 a4: b9402be1 ldr w1, [sp,#40] a8: b9401be0 ldr w0, [sp,#24] ac: 1b007c20 mul w0, w1, w0 b0: b9002be0 str w0, [sp,#40] b4: b9401fe1 ldr w1, [sp,#28] b8: b9402be0 ldr w0, [sp,#40] bc: 1ac00821 udiv w1, w1, w0 c0: b9401be0 ldr w0, [sp,#24] c4: 6b00003f cmp w1, w0 c8: 54fffee2 b.cs a4 cc: 1400002f b 188 d0: b9401fe1 ldr w1, [sp,#28] d4: b9402be0 ldr w0, [sp,#40] d8: 1ac00820 udiv w0, w1, w0 dc: b90027e0 str w0, [sp,#36] e0: b9401fe0 ldr w0, [sp,#28] e4: b9402be1 ldr w1, [sp,#40] e8: 1ac10802 udiv w2, w0, w1 ec: b9402be1 ldr w1, [sp,#40] f0: 1b017c41 mul w1, w2, w1 f4: 4b010000 sub w0, w0, w1 f8: b9001fe0 str w0, [sp,#28] fc: b9402be1 ldr w1, [sp,#40] 100: b9401be0 ldr w0, [sp,#24] 104: 1ac00820 udiv w0, w1, w0 108: b9002be0 str w0, [sp,#40] 10c: b9402fe0 ldr w0, [sp,#44] 110: 7100001f cmp w0, #0x0 114: 540000e1 b.ne 130 118: b94027e0 ldr w0, [sp,#36] 11c: 7100001f cmp w0, #0x0 120: 5400008c b.gt 130 124: b9402be0 ldr w0, [sp,#40] 128: 7100001f cmp w0, #0x0 12c: 540002e1 b.ne 188 130: f94007e1 ldr x1, [sp,#8] 134: 91000420 add x0, x1, #0x1 138: f90007e0 str x0, [sp,#8] 13c: b94027e0 ldr w0, [sp,#36] 140: 7100241f cmp w0, #0x9 144: 5400010d b.le 164 148: b94017e0 ldr w0, [sp,#20] 14c: 7100001f cmp w0, #0x0 150: 54000060 b.eq 15c 154: 528006e0 mov w0, #0x37 // #55 158: 14000004 b 168 15c: 52800ae0 mov w0, #0x57 // #87 160: 14000002 b 168 164: 52800600 mov w0, #0x30 // #48 168: b94027e2 ldr w2, [sp,#36] 16c: 53001c42 uxtb w2, w2 170: 0b020000 add w0, w0, w2 174: 53001c00 uxtb w0, w0 178: 39000020 strb w0, [x1] 17c: b9402fe0 ldr w0, [sp,#44] 180: 11000400 add w0, w0, #0x1 184: b9002fe0 str w0, [sp,#44] 188: b9402be0 ldr w0, [sp,#40] 18c: 7100001f cmp w0, #0x0 190: 54fffa01 b.ne d0 194: f94007e0 ldr x0, [sp,#8] 198: 3900001f strb wzr, [x0] 19c: d503201f nop 1a0: 9100c3ff add sp, sp, #0x30 1a4: d65f03c0 ret 00000000000001a8 : 1a8: a9be7bfd stp x29, x30, [sp,#-32]! 1ac: 910003fd mov x29, sp 1b0: b9001fa0 str w0, [x29,#28] 1b4: f9000ba1 str x1, [x29,#16] 1b8: b9401fa0 ldr w0, [x29,#28] 1bc: 7100001f cmp w0, #0x0 1c0: 5400012a b.ge 1e4 1c4: b9401fa0 ldr w0, [x29,#28] 1c8: 4b0003e0 neg w0, w0 1cc: b9001fa0 str w0, [x29,#28] 1d0: f9400ba0 ldr x0, [x29,#16] 1d4: 91000401 add x1, x0, #0x1 1d8: f9000ba1 str x1, [x29,#16] 1dc: 528005a1 mov w1, #0x2d // #45 1e0: 39000001 strb w1, [x0] 1e4: b9401fa0 ldr w0, [x29,#28] 1e8: f9400ba3 ldr x3, [x29,#16] 1ec: 52800002 mov w2, #0x0 // #0 1f0: 52800141 mov w1, #0xa // #10 1f4: 97ffffa3 bl 80 1f8: d503201f nop 1fc: a8c27bfd ldp x29, x30, [sp],#32 200: d65f03c0 ret 0000000000000204 : 204: d10043ff sub sp, sp, #0x10 208: 39003fe0 strb w0, [sp,#15] 20c: 39403fe0 ldrb w0, [sp,#15] 210: 7100bc1f cmp w0, #0x2f 214: 540000e9 b.ls 230 218: 39403fe0 ldrb w0, [sp,#15] 21c: 7100e41f cmp w0, #0x39 220: 54000088 b.hi 230 224: 39403fe0 ldrb w0, [sp,#15] 228: 5100c000 sub w0, w0, #0x30 22c: 14000014 b 27c 230: 39403fe0 ldrb w0, [sp,#15] 234: 7101801f cmp w0, #0x60 238: 540000e9 b.ls 254 23c: 39403fe0 ldrb w0, [sp,#15] 240: 7101981f cmp w0, #0x66 244: 54000088 b.hi 254 248: 39403fe0 ldrb w0, [sp,#15] 24c: 51015c00 sub w0, w0, #0x57 250: 1400000b b 27c 254: 39403fe0 ldrb w0, [sp,#15] 258: 7101001f cmp w0, #0x40 25c: 540000e9 b.ls 278 260: 39403fe0 ldrb w0, [sp,#15] 264: 7101181f cmp w0, #0x46 268: 54000088 b.hi 278 26c: 39403fe0 ldrb w0, [sp,#15] 270: 5100dc00 sub w0, w0, #0x37 274: 14000002 b 27c 278: 12800000 mov w0, #0xffffffff // #-1 27c: 910043ff add sp, sp, #0x10 280: d65f03c0 ret 0000000000000284 : 284: a9bc7bfd stp x29, x30, [sp,#-64]! 288: 910003fd mov x29, sp 28c: 3900bfa0 strb w0, [x29,#47] 290: f90013a1 str x1, [x29,#32] 294: b9002ba2 str w2, [x29,#40] 298: f9000fa3 str x3, [x29,#24] 29c: f94013a0 ldr x0, [x29,#32] 2a0: f9400000 ldr x0, [x0] 2a4: f9001fa0 str x0, [x29,#56] 2a8: b90037bf str wzr, [x29,#52] 2ac: 14000010 b 2ec 2b0: b94033a1 ldr w1, [x29,#48] 2b4: b9402ba0 ldr w0, [x29,#40] 2b8: 6b00003f cmp w1, w0 2bc: 5400026c b.gt 308 2c0: b94037a1 ldr w1, [x29,#52] 2c4: b9402ba0 ldr w0, [x29,#40] 2c8: 1b007c21 mul w1, w1, w0 2cc: b94033a0 ldr w0, [x29,#48] 2d0: 0b000020 add w0, w1, w0 2d4: b90037a0 str w0, [x29,#52] 2d8: f9401fa0 ldr x0, [x29,#56] 2dc: 91000401 add x1, x0, #0x1 2e0: f9001fa1 str x1, [x29,#56] 2e4: 39400000 ldrb w0, [x0] 2e8: 3900bfa0 strb w0, [x29,#47] 2ec: 3940bfa0 ldrb w0, [x29,#47] 2f0: 97ffffc5 bl 204 2f4: b90033a0 str w0, [x29,#48] 2f8: b94033a0 ldr w0, [x29,#48] 2fc: 7100001f cmp w0, #0x0 300: 54fffd8a b.ge 2b0 304: 14000002 b 30c 308: d503201f nop 30c: f94013a0 ldr x0, [x29,#32] 310: f9401fa1 ldr x1, [x29,#56] 314: f9000001 str x1, [x0] 318: f9400fa0 ldr x0, [x29,#24] 31c: b94037a1 ldr w1, [x29,#52] 320: b9000001 str w1, [x0] 324: 3940bfa0 ldrb w0, [x29,#47] 328: a8c47bfd ldp x29, x30, [sp],#64 32c: d65f03c0 ret 0000000000000330 : 330: a9bc7bfd stp x29, x30, [sp,#-64]! 334: 910003fd mov x29, sp 338: f90017a0 str x0, [x29,#40] 33c: f90013a1 str x1, [x29,#32] 340: b9001fa2 str w2, [x29,#28] 344: 39006fa3 strb w3, [x29,#27] 348: f9000ba4 str x4, [x29,#16] 34c: 39406fa0 ldrb w0, [x29,#27] 350: 7100001f cmp w0, #0x0 354: 54000060 b.eq 360 358: 52800600 mov w0, #0x30 // #48 35c: 14000002 b 364 360: 52800400 mov w0, #0x20 // #32 364: 3900dfa0 strb w0, [x29,#55] 368: f9400ba0 ldr x0, [x29,#16] 36c: f9001fa0 str x0, [x29,#56] 370: 14000004 b 380 374: b9401fa0 ldr w0, [x29,#28] 378: 51000400 sub w0, w0, #0x1 37c: b9001fa0 str w0, [x29,#28] 380: f9401fa0 ldr x0, [x29,#56] 384: 91000401 add x1, x0, #0x1 388: f9001fa1 str x1, [x29,#56] 38c: 39400000 ldrb w0, [x0] 390: 7100001f cmp w0, #0x0 394: 54000120 b.eq 3b8 398: b9401fa0 ldr w0, [x29,#28] 39c: 7100001f cmp w0, #0x0 3a0: 54fffeac b.gt 374 3a4: 14000005 b 3b8 3a8: f94013a2 ldr x2, [x29,#32] 3ac: 3940dfa1 ldrb w1, [x29,#55] 3b0: f94017a0 ldr x0, [x29,#40] 3b4: d63f0040 blr x2 3b8: b9401fa0 ldr w0, [x29,#28] 3bc: 51000401 sub w1, w0, #0x1 3c0: b9001fa1 str w1, [x29,#28] 3c4: 7100001f cmp w0, #0x0 3c8: 54ffff0c b.gt 3a8 3cc: 14000005 b 3e0 3d0: f94013a2 ldr x2, [x29,#32] 3d4: 3940dba1 ldrb w1, [x29,#54] 3d8: f94017a0 ldr x0, [x29,#40] 3dc: d63f0040 blr x2 3e0: f9400ba0 ldr x0, [x29,#16] 3e4: 91000401 add x1, x0, #0x1 3e8: f9000ba1 str x1, [x29,#16] 3ec: 39400000 ldrb w0, [x0] 3f0: 3900dba0 strb w0, [x29,#54] 3f4: 3940dba0 ldrb w0, [x29,#54] 3f8: 7100001f cmp w0, #0x0 3fc: 54fffea1 b.ne 3d0 400: d503201f nop 404: a8c47bfd ldp x29, x30, [sp],#64 408: d65f03c0 ret 000000000000040c : 40c: a9ba7bfd stp x29, x30, [sp,#-96]! 410: 910003fd mov x29, sp 414: f9000bf3 str x19, [sp,#16] 418: f9001fa0 str x0, [x29,#56] 41c: f9001ba1 str x1, [x29,#48] 420: f90017a2 str x2, [x29,#40] 424: aa0303f3 mov x19, x3 428: 140000fd b 81c 42c: 39417fa0 ldrb w0, [x29,#95] 430: 7100941f cmp w0, #0x25 434: 540000c0 b.eq 44c 438: f9401ba2 ldr x2, [x29,#48] 43c: 39417fa1 ldrb w1, [x29,#95] 440: f9401fa0 ldr x0, [x29,#56] 444: d63f0040 blr x2 448: 140000f5 b 81c 44c: 39017bbf strb wzr, [x29,#94] 450: b9004fbf str wzr, [x29,#76] 454: f94017a0 ldr x0, [x29,#40] 458: 91000401 add x1, x0, #0x1 45c: f90017a1 str x1, [x29,#40] 460: 39400000 ldrb w0, [x0] 464: 39017fa0 strb w0, [x29,#95] 468: 39417fa0 ldrb w0, [x29,#95] 46c: 7100c01f cmp w0, #0x30 470: 54000101 b.ne 490 474: f94017a0 ldr x0, [x29,#40] 478: 91000401 add x1, x0, #0x1 47c: f90017a1 str x1, [x29,#40] 480: 39400000 ldrb w0, [x0] 484: 39017fa0 strb w0, [x29,#95] 488: 52800020 mov w0, #0x1 // #1 48c: 39017ba0 strb w0, [x29,#94] 490: 39417fa0 ldrb w0, [x29,#95] 494: 7100bc1f cmp w0, #0x2f 498: 54000189 b.ls 4c8 49c: 39417fa0 ldrb w0, [x29,#95] 4a0: 7100e41f cmp w0, #0x39 4a4: 54000128 b.hi 4c8 4a8: 910133a1 add x1, x29, #0x4c 4ac: 9100a3a0 add x0, x29, #0x28 4b0: aa0103e3 mov x3, x1 4b4: 52800142 mov w2, #0xa // #10 4b8: aa0003e1 mov x1, x0 4bc: 39417fa0 ldrb w0, [x29,#95] 4c0: 97ffff71 bl 284 4c4: 39017fa0 strb w0, [x29,#95] 4c8: 39417fa0 ldrb w0, [x29,#95] 4cc: 71018c1f cmp w0, #0x63 4d0: 540011c0 b.eq 708 4d4: 71018c1f cmp w0, #0x63 4d8: 5400010c b.gt 4f8 4dc: 7100941f cmp w0, #0x25 4e0: 54001940 b.eq 808 4e4: 7101601f cmp w0, #0x58 4e8: 54000b60 b.eq 654 4ec: 7100001f cmp w0, #0x0 4f0: 54001a80 b.eq 840 4f4: 140000c9 b 818 4f8: 7101cc1f cmp w0, #0x73 4fc: 54001440 b.eq 784 500: 7101cc1f cmp w0, #0x73 504: 5400008c b.gt 514 508: 7101901f cmp w0, #0x64 50c: 540005c0 b.eq 5c4 510: 140000c2 b 818 514: 7101d41f cmp w0, #0x75 518: 54000080 b.eq 528 51c: 7101e01f cmp w0, #0x78 520: 540009a0 b.eq 654 524: 140000bd b 818 528: b9401a60 ldr w0, [x19,#24] 52c: f9400261 ldr x1, [x19] 530: 7100001f cmp w0, #0x0 534: 540000eb b.lt 550 538: aa0103e0 mov x0, x1 53c: 91002c00 add x0, x0, #0xb 540: 927df000 and x0, x0, #0xfffffffffffffff8 544: f9000260 str x0, [x19] 548: aa0103e0 mov x0, x1 54c: 1400000f b 588 550: 11002002 add w2, w0, #0x8 554: b9001a62 str w2, [x19,#24] 558: b9401a62 ldr w2, [x19,#24] 55c: 7100005f cmp w2, #0x0 560: 540000ed b.le 57c 564: aa0103e0 mov x0, x1 568: 91002c00 add x0, x0, #0xb 56c: 927df000 and x0, x0, #0xfffffffffffffff8 570: f9000260 str x0, [x19] 574: aa0103e0 mov x0, x1 578: 14000004 b 588 57c: f9400661 ldr x1, [x19,#8] 580: 93407c00 sxtw x0, w0 584: 8b000020 add x0, x1, x0 588: b9400000 ldr w0, [x0] 58c: 910143a1 add x1, x29, #0x50 590: aa0103e3 mov x3, x1 594: 52800002 mov w2, #0x0 // #0 598: 52800141 mov w1, #0xa // #10 59c: 97fffeb9 bl 80 5a0: b9404fa0 ldr w0, [x29,#76] 5a4: 910143a1 add x1, x29, #0x50 5a8: aa0103e4 mov x4, x1 5ac: 39417ba3 ldrb w3, [x29,#94] 5b0: 2a0003e2 mov w2, w0 5b4: f9401ba1 ldr x1, [x29,#48] 5b8: f9401fa0 ldr x0, [x29,#56] 5bc: 97ffff5d bl 330 5c0: 14000097 b 81c 5c4: b9401a60 ldr w0, [x19,#24] 5c8: f9400261 ldr x1, [x19] 5cc: 7100001f cmp w0, #0x0 5d0: 540000eb b.lt 5ec 5d4: aa0103e0 mov x0, x1 5d8: 91002c00 add x0, x0, #0xb 5dc: 927df000 and x0, x0, #0xfffffffffffffff8 5e0: f9000260 str x0, [x19] 5e4: aa0103e0 mov x0, x1 5e8: 1400000f b 624 5ec: 11002002 add w2, w0, #0x8 5f0: b9001a62 str w2, [x19,#24] 5f4: b9401a62 ldr w2, [x19,#24] 5f8: 7100005f cmp w2, #0x0 5fc: 540000ed b.le 618 600: aa0103e0 mov x0, x1 604: 91002c00 add x0, x0, #0xb 608: 927df000 and x0, x0, #0xfffffffffffffff8 60c: f9000260 str x0, [x19] 610: aa0103e0 mov x0, x1 614: 14000004 b 624 618: f9400661 ldr x1, [x19,#8] 61c: 93407c00 sxtw x0, w0 620: 8b000020 add x0, x1, x0 624: b9400000 ldr w0, [x0] 628: 910143a1 add x1, x29, #0x50 62c: 97fffedf bl 1a8 630: b9404fa0 ldr w0, [x29,#76] 634: 910143a1 add x1, x29, #0x50 638: aa0103e4 mov x4, x1 63c: 39417ba3 ldrb w3, [x29,#94] 640: 2a0003e2 mov w2, w0 644: f9401ba1 ldr x1, [x29,#48] 648: f9401fa0 ldr x0, [x29,#56] 64c: 97ffff39 bl 330 650: 14000073 b 81c 654: b9401a60 ldr w0, [x19,#24] 658: f9400261 ldr x1, [x19] 65c: 7100001f cmp w0, #0x0 660: 540000eb b.lt 67c 664: aa0103e0 mov x0, x1 668: 91002c00 add x0, x0, #0xb 66c: 927df000 and x0, x0, #0xfffffffffffffff8 670: f9000260 str x0, [x19] 674: aa0103e0 mov x0, x1 678: 1400000f b 6b4 67c: 11002002 add w2, w0, #0x8 680: b9001a62 str w2, [x19,#24] 684: b9401a62 ldr w2, [x19,#24] 688: 7100005f cmp w2, #0x0 68c: 540000ed b.le 6a8 690: aa0103e0 mov x0, x1 694: 91002c00 add x0, x0, #0xb 698: 927df000 and x0, x0, #0xfffffffffffffff8 69c: f9000260 str x0, [x19] 6a0: aa0103e0 mov x0, x1 6a4: 14000004 b 6b4 6a8: f9400661 ldr x1, [x19,#8] 6ac: 93407c00 sxtw x0, w0 6b0: 8b000020 add x0, x1, x0 6b4: b9400004 ldr w4, [x0] 6b8: 39417fa0 ldrb w0, [x29,#95] 6bc: 7101601f cmp w0, #0x58 6c0: 1a9f17e0 cset w0, eq 6c4: 53001c00 uxtb w0, w0 6c8: 2a0003e1 mov w1, w0 6cc: 910143a0 add x0, x29, #0x50 6d0: aa0003e3 mov x3, x0 6d4: 2a0103e2 mov w2, w1 6d8: 52800201 mov w1, #0x10 // #16 6dc: 2a0403e0 mov w0, w4 6e0: 97fffe68 bl 80 6e4: b9404fa0 ldr w0, [x29,#76] 6e8: 910143a1 add x1, x29, #0x50 6ec: aa0103e4 mov x4, x1 6f0: 39417ba3 ldrb w3, [x29,#94] 6f4: 2a0003e2 mov w2, w0 6f8: f9401ba1 ldr x1, [x29,#48] 6fc: f9401fa0 ldr x0, [x29,#56] 700: 97ffff0c bl 330 704: 14000046 b 81c 708: b9401a60 ldr w0, [x19,#24] 70c: f9400261 ldr x1, [x19] 710: 7100001f cmp w0, #0x0 714: 540000eb b.lt 730 718: aa0103e0 mov x0, x1 71c: 91002c00 add x0, x0, #0xb 720: 927df000 and x0, x0, #0xfffffffffffffff8 724: f9000260 str x0, [x19] 728: aa0103e0 mov x0, x1 72c: 1400000f b 768 730: 11002002 add w2, w0, #0x8 734: b9001a62 str w2, [x19,#24] 738: b9401a62 ldr w2, [x19,#24] 73c: 7100005f cmp w2, #0x0 740: 540000ed b.le 75c 744: aa0103e0 mov x0, x1 748: 91002c00 add x0, x0, #0xb 74c: 927df000 and x0, x0, #0xfffffffffffffff8 750: f9000260 str x0, [x19] 754: aa0103e0 mov x0, x1 758: 14000004 b 768 75c: f9400661 ldr x1, [x19,#8] 760: 93407c00 sxtw x0, w0 764: 8b000020 add x0, x1, x0 768: b9400000 ldr w0, [x0] 76c: 53001c00 uxtb w0, w0 770: f9401ba2 ldr x2, [x29,#48] 774: 2a0003e1 mov w1, w0 778: f9401fa0 ldr x0, [x29,#56] 77c: d63f0040 blr x2 780: 14000027 b 81c 784: b9404fa5 ldr w5, [x29,#76] 788: b9401a60 ldr w0, [x19,#24] 78c: f9400261 ldr x1, [x19] 790: 7100001f cmp w0, #0x0 794: 540000eb b.lt 7b0 798: aa0103e0 mov x0, x1 79c: 91003c00 add x0, x0, #0xf 7a0: 927df000 and x0, x0, #0xfffffffffffffff8 7a4: f9000260 str x0, [x19] 7a8: aa0103e0 mov x0, x1 7ac: 1400000f b 7e8 7b0: 11002002 add w2, w0, #0x8 7b4: b9001a62 str w2, [x19,#24] 7b8: b9401a62 ldr w2, [x19,#24] 7bc: 7100005f cmp w2, #0x0 7c0: 540000ed b.le 7dc 7c4: aa0103e0 mov x0, x1 7c8: 91003c00 add x0, x0, #0xf 7cc: 927df000 and x0, x0, #0xfffffffffffffff8 7d0: f9000260 str x0, [x19] 7d4: aa0103e0 mov x0, x1 7d8: 14000004 b 7e8 7dc: f9400661 ldr x1, [x19,#8] 7e0: 93407c00 sxtw x0, w0 7e4: 8b000020 add x0, x1, x0 7e8: f9400000 ldr x0, [x0] 7ec: aa0003e4 mov x4, x0 7f0: 52800003 mov w3, #0x0 // #0 7f4: 2a0503e2 mov w2, w5 7f8: f9401ba1 ldr x1, [x29,#48] 7fc: f9401fa0 ldr x0, [x29,#56] 800: 97fffecc bl 330 804: 14000006 b 81c 808: f9401ba2 ldr x2, [x29,#48] 80c: 39417fa1 ldrb w1, [x29,#95] 810: f9401fa0 ldr x0, [x29,#56] 814: d63f0040 blr x2 818: d503201f nop 81c: f94017a0 ldr x0, [x29,#40] 820: 91000401 add x1, x0, #0x1 824: f90017a1 str x1, [x29,#40] 828: 39400000 ldrb w0, [x0] 82c: 39017fa0 strb w0, [x29,#95] 830: 39417fa0 ldrb w0, [x29,#95] 834: 7100001f cmp w0, #0x0 838: 54ffdfa1 b.ne 42c 83c: 14000002 b 844 840: d503201f nop 844: d503201f nop 848: f9400bf3 ldr x19, [sp,#16] 84c: a8c67bfd ldp x29, x30, [sp],#96 850: d65f03c0 ret 0000000000000854 : 854: d10043ff sub sp, sp, #0x10 858: f90007e0 str x0, [sp,#8] 85c: f90003e1 str x1, [sp] 860: 90000000 adrp x0, 0 <_start> 864: 9133e000 add x0, x0, #0xcf8 868: f94003e1 ldr x1, [sp] 86c: f9000001 str x1, [x0] 870: 90000000 adrp x0, 0 <_start> 874: 91340000 add x0, x0, #0xd00 878: f94007e1 ldr x1, [sp,#8] 87c: f9000001 str x1, [x0] 880: d503201f nop 884: 910043ff add sp, sp, #0x10 888: d65f03c0 ret 000000000000088c : 88c: a9ae7bfd stp x29, x30, [sp,#-288]! 890: 910003fd mov x29, sp 894: f9001fa0 str x0, [x29,#56] 898: f90077a1 str x1, [x29,#232] 89c: f9007ba2 str x2, [x29,#240] 8a0: f9007fa3 str x3, [x29,#248] 8a4: f90083a4 str x4, [x29,#256] 8a8: f90087a5 str x5, [x29,#264] 8ac: f9008ba6 str x6, [x29,#272] 8b0: f9008fa7 str x7, [x29,#280] 8b4: 3d801ba0 str q0, [x29,#96] 8b8: 3d801fa1 str q1, [x29,#112] 8bc: 3d8023a2 str q2, [x29,#128] 8c0: 3d8027a3 str q3, [x29,#144] 8c4: 3d802ba4 str q4, [x29,#160] 8c8: 3d802fa5 str q5, [x29,#176] 8cc: 3d8033a6 str q6, [x29,#192] 8d0: 3d8037a7 str q7, [x29,#208] 8d4: 910483a0 add x0, x29, #0x120 8d8: f90023a0 str x0, [x29,#64] 8dc: 910483a0 add x0, x29, #0x120 8e0: f90027a0 str x0, [x29,#72] 8e4: 910383a0 add x0, x29, #0xe0 8e8: f9002ba0 str x0, [x29,#80] 8ec: 128006e0 mov w0, #0xffffffc8 // #-56 8f0: b9005ba0 str w0, [x29,#88] 8f4: 12800fe0 mov w0, #0xffffff80 // #-128 8f8: b9005fa0 str w0, [x29,#92] 8fc: 90000000 adrp x0, 0 <_start> 900: 91340000 add x0, x0, #0xd00 904: f9400004 ldr x4, [x0] 908: 90000000 adrp x0, 0 <_start> 90c: 9133e000 add x0, x0, #0xcf8 910: f9400005 ldr x5, [x0] 914: 910043a2 add x2, x29, #0x10 918: 910103a3 add x3, x29, #0x40 91c: a9400460 ldp x0, x1, [x3] 920: a9000440 stp x0, x1, [x2] 924: a9410460 ldp x0, x1, [x3,#16] 928: a9010440 stp x0, x1, [x2,#16] 92c: 910043a0 add x0, x29, #0x10 930: aa0003e3 mov x3, x0 934: f9401fa2 ldr x2, [x29,#56] 938: aa0503e1 mov x1, x5 93c: aa0403e0 mov x0, x4 940: 97fffeb3 bl 40c 944: d503201f nop 948: a8d27bfd ldp x29, x30, [sp],#288 94c: d65f03c0 ret 0000000000000950 : 950: d10043ff sub sp, sp, #0x10 954: f90007e0 str x0, [sp,#8] 958: 39001fe1 strb w1, [sp,#7] 95c: f94007e0 ldr x0, [sp,#8] 960: f9400000 ldr x0, [x0] 964: 91000402 add x2, x0, #0x1 968: f94007e1 ldr x1, [sp,#8] 96c: f9000022 str x2, [x1] 970: 39401fe1 ldrb w1, [sp,#7] 974: 39000001 strb w1, [x0] 978: d503201f nop 97c: 910043ff add sp, sp, #0x10 980: d65f03c0 ret 0000000000000984 : 984: a9af7bfd stp x29, x30, [sp,#-272]! 988: 910003fd mov x29, sp 98c: f9001fa0 str x0, [x29,#56] 990: f9001ba1 str x1, [x29,#48] 994: f90073a2 str x2, [x29,#224] 998: f90077a3 str x3, [x29,#232] 99c: f9007ba4 str x4, [x29,#240] 9a0: f9007fa5 str x5, [x29,#248] 9a4: f90083a6 str x6, [x29,#256] 9a8: f90087a7 str x7, [x29,#264] 9ac: 3d801ba0 str q0, [x29,#96] 9b0: 3d801fa1 str q1, [x29,#112] 9b4: 3d8023a2 str q2, [x29,#128] 9b8: 3d8027a3 str q3, [x29,#144] 9bc: 3d802ba4 str q4, [x29,#160] 9c0: 3d802fa5 str q5, [x29,#176] 9c4: 3d8033a6 str q6, [x29,#192] 9c8: 3d8037a7 str q7, [x29,#208] 9cc: 910443a0 add x0, x29, #0x110 9d0: f90023a0 str x0, [x29,#64] 9d4: 910443a0 add x0, x29, #0x110 9d8: f90027a0 str x0, [x29,#72] 9dc: 910383a0 add x0, x29, #0xe0 9e0: f9002ba0 str x0, [x29,#80] 9e4: 128005e0 mov w0, #0xffffffd0 // #-48 9e8: b9005ba0 str w0, [x29,#88] 9ec: 12800fe0 mov w0, #0xffffff80 // #-128 9f0: b9005fa0 str w0, [x29,#92] 9f4: 910043a2 add x2, x29, #0x10 9f8: 910103a3 add x3, x29, #0x40 9fc: a9400460 ldp x0, x1, [x3] a00: a9000440 stp x0, x1, [x2] a04: a9410460 ldp x0, x1, [x3,#16] a08: a9010440 stp x0, x1, [x2,#16] a0c: 910043a2 add x2, x29, #0x10 a10: 90000000 adrp x0, 0 <_start> a14: 91254001 add x1, x0, #0x950 a18: 9100e3a0 add x0, x29, #0x38 a1c: aa0203e3 mov x3, x2 a20: f9401ba2 ldr x2, [x29,#48] a24: 97fffe7a bl 40c a28: 9100e3a0 add x0, x29, #0x38 a2c: 52800001 mov w1, #0x0 // #0 a30: 97ffffc8 bl 950 a34: d503201f nop a38: a8d17bfd ldp x29, x30, [sp],#272 a3c: d65f03c0 ret 0000000000000a40 : a40: a9be7bfd stp x29, x30, [sp,#-32]! a44: 910003fd mov x29, sp a48: 39007fa0 strb w0, [x29,#31] a4c: d28a0a80 mov x0, #0x5054 // #20564 a50: f2a7e420 movk x0, #0x3f21, lsl #16 a54: 9400009d bl cc8 a58: 121b0000 and w0, w0, #0x20 a5c: 7100001f cmp w0, #0x0 a60: 54000041 b.ne a68 a64: 17fffffa b a4c a68: d503201f nop a6c: 39407fa0 ldrb w0, [x29,#31] a70: 2a0003e1 mov w1, w0 a74: d28a0800 mov x0, #0x5040 // #20544 a78: f2a7e420 movk x0, #0x3f21, lsl #16 a7c: 94000091 bl cc0 a80: d503201f nop a84: a8c27bfd ldp x29, x30, [sp],#32 a88: d65f03c0 ret 0000000000000a8c : a8c: a9bf7bfd stp x29, x30, [sp,#-16]! a90: 910003fd mov x29, sp a94: d28a0a80 mov x0, #0x5054 // #20564 a98: f2a7e420 movk x0, #0x3f21, lsl #16 a9c: 9400008b bl cc8 aa0: 12000000 and w0, w0, #0x1 aa4: 7100001f cmp w0, #0x0 aa8: 54000041 b.ne ab0 aac: 17fffffa b a94 ab0: d503201f nop ab4: d28a0800 mov x0, #0x5040 // #20544 ab8: f2a7e420 movk x0, #0x3f21, lsl #16 abc: 94000083 bl cc8 ac0: 53001c00 uxtb w0, w0 ac4: a8c17bfd ldp x29, x30, [sp],#16 ac8: d65f03c0 ret 0000000000000acc : acc: a9bd7bfd stp x29, x30, [sp,#-48]! ad0: 910003fd mov x29, sp ad4: f9000fa0 str x0, [x29,#24] ad8: b9002fbf str wzr, [x29,#44] adc: 14000009 b b00 ae0: b9802fa0 ldrsw x0, [x29,#44] ae4: f9400fa1 ldr x1, [x29,#24] ae8: 8b000020 add x0, x1, x0 aec: 39400000 ldrb w0, [x0] af0: 97ffffd4 bl a40 af4: b9402fa0 ldr w0, [x29,#44] af8: 11000400 add w0, w0, #0x1 afc: b9002fa0 str w0, [x29,#44] b00: b9802fa0 ldrsw x0, [x29,#44] b04: f9400fa1 ldr x1, [x29,#24] b08: 8b000020 add x0, x1, x0 b0c: 39400000 ldrb w0, [x0] b10: 7100001f cmp w0, #0x0 b14: 54fffe61 b.ne ae0 b18: d503201f nop b1c: a8c37bfd ldp x29, x30, [sp],#48 b20: d65f03c0 ret 0000000000000b24 : b24: a9be7bfd stp x29, x30, [sp,#-32]! b28: 910003fd mov x29, sp b2c: d2800080 mov x0, #0x4 // #4 b30: f2a7e400 movk x0, #0x3f20, lsl #16 b34: 94000065 bl cc8 b38: b9001fa0 str w0, [x29,#28] b3c: b9401fa0 ldr w0, [x29,#28] b40: 12117000 and w0, w0, #0xffff8fff b44: b9001fa0 str w0, [x29,#28] b48: b9401fa0 ldr w0, [x29,#28] b4c: 32130000 orr w0, w0, #0x2000 b50: b9001fa0 str w0, [x29,#28] b54: b9401fa0 ldr w0, [x29,#28] b58: 120e7000 and w0, w0, #0xfffc7fff b5c: b9001fa0 str w0, [x29,#28] b60: b9401fa0 ldr w0, [x29,#28] b64: 32100000 orr w0, w0, #0x10000 b68: b9001fa0 str w0, [x29,#28] b6c: b9401fa1 ldr w1, [x29,#28] b70: d2800080 mov x0, #0x4 // #4 b74: f2a7e400 movk x0, #0x3f20, lsl #16 b78: 94000052 bl cc0 b7c: 52800001 mov w1, #0x0 // #0 b80: d2801280 mov x0, #0x94 // #148 b84: f2a7e400 movk x0, #0x3f20, lsl #16 b88: 9400004e bl cc0 b8c: d28012c0 mov x0, #0x96 // #150 b90: 94000050 bl cd0 b94: 52980001 mov w1, #0xc000 // #49152 b98: d2801300 mov x0, #0x98 // #152 b9c: f2a7e400 movk x0, #0x3f20, lsl #16 ba0: 94000048 bl cc0 ba4: d28012c0 mov x0, #0x96 // #150 ba8: 9400004a bl cd0 bac: 52800001 mov w1, #0x0 // #0 bb0: d2801300 mov x0, #0x98 // #152 bb4: f2a7e400 movk x0, #0x3f20, lsl #16 bb8: 94000042 bl cc0 bbc: 52800021 mov w1, #0x1 // #1 bc0: d28a0080 mov x0, #0x5004 // #20484 bc4: f2a7e420 movk x0, #0x3f21, lsl #16 bc8: 9400003e bl cc0 bcc: 52800001 mov w1, #0x0 // #0 bd0: d28a0c00 mov x0, #0x5060 // #20576 bd4: f2a7e420 movk x0, #0x3f21, lsl #16 bd8: 9400003a bl cc0 bdc: 52800001 mov w1, #0x0 // #0 be0: d28a0880 mov x0, #0x5044 // #20548 be4: f2a7e420 movk x0, #0x3f21, lsl #16 be8: 94000036 bl cc0 bec: 52800061 mov w1, #0x3 // #3 bf0: d28a0980 mov x0, #0x504c // #20556 bf4: f2a7e420 movk x0, #0x3f21, lsl #16 bf8: 94000032 bl cc0 bfc: 52800001 mov w1, #0x0 // #0 c00: d28a0a00 mov x0, #0x5050 // #20560 c04: f2a7e420 movk x0, #0x3f21, lsl #16 c08: 9400002e bl cc0 c0c: 528021c1 mov w1, #0x10e // #270 c10: d28a0d00 mov x0, #0x5068 // #20584 c14: f2a7e420 movk x0, #0x3f21, lsl #16 c18: 9400002a bl cc0 c1c: 52800061 mov w1, #0x3 // #3 c20: d28a0c00 mov x0, #0x5060 // #20576 c24: f2a7e420 movk x0, #0x3f21, lsl #16 c28: 94000026 bl cc0 c2c: d503201f nop c30: a8c27bfd ldp x29, x30, [sp],#32 c34: d65f03c0 ret 0000000000000c38 : c38: a9be7bfd stp x29, x30, [sp,#-32]! c3c: 910003fd mov x29, sp c40: f9000fa0 str x0, [x29,#24] c44: 39005fa1 strb w1, [x29,#23] c48: 39405fa0 ldrb w0, [x29,#23] c4c: 97ffff7d bl a40 c50: d503201f nop c54: a8c27bfd ldp x29, x30, [sp],#32 c58: d65f03c0 ret 0000000000000c5c : c5c: a9be7bfd stp x29, x30, [sp,#-32]! c60: 910003fd mov x29, sp c64: 97ffffb0 bl b24 c68: 90000000 adrp x0, 0 <_start> c6c: 9130e000 add x0, x0, #0xc38 c70: aa0003e1 mov x1, x0 c74: d2800000 mov x0, #0x0 // #0 c78: 97fffef7 bl 854 c7c: 9400000e bl cb4 c80: b9001fa0 str w0, [x29,#28] c84: 90000000 adrp x0, 0 <_start> c88: 91338000 add x0, x0, #0xce0 c8c: b9401fa1 ldr w1, [x29,#28] c90: 97fffeff bl 88c c94: 97ffff7e bl a8c c98: 53001c00 uxtb w0, w0 c9c: 97ffff69 bl a40 ca0: 17fffffd b c94 0000000000000ca4 : ca4: f800841f str xzr, [x0],#8 ca8: f1002021 subs x1, x1, #0x8 cac: 54ffffcc b.gt ca4 cb0: d65f03c0 ret 0000000000000cb4 : cb4: d5384240 mrs x0, currentel cb8: d342fc00 lsr x0, x0, #2 cbc: d65f03c0 ret 0000000000000cc0 : cc0: b9000001 str w1, [x0] cc4: d65f03c0 ret 0000000000000cc8 : cc8: b9400000 ldr w0, [x0] ccc: d65f03c0 ret 0000000000000cd0 : cd0: f1000400 subs x0, x0, #0x1 cd4: 54ffffe1 b.ne cd0 cd8: d65f03c0 ret ================================================ FILE: exercises/lesson02/2/bl4ckout31/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 ldr x0, =CPACR_VALUE msr cpacr_el1, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson02/2/bl4ckout31/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson02/2/bl4ckout31/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "mini_uart.h" void kernel_main(void) { uart_init(); init_printf(0, putc); int el = get_el(); printf("Exception level: %d \r\n", el); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson02/2/bl4ckout31/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson02/2/bl4ckout31/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson02/2/bl4ckout31/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson02/2/bl4ckout31/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson02/2/bl4ckout31/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson02/2/bl4ckout31/start.sh ================================================ #!/bin/bash if [ $# -ne 1 ]; then echo "usage: $0 kernel" exit 1 fi mount -L rpiboot /mnt cp "$1" /mnt umount /mnt ================================================ FILE: exercises/lesson02/2/evopen/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu C_FLAGS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude ASM_FLAGS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(C_FLAGS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASM_FLAGS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T src/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson02/2/evopen/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson02/2/evopen/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson02/2/evopen/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson02/2/evopen/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2025 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 1923 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2022 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 288 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #endif ================================================ FILE: exercises/lesson02/2/evopen/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson02/2/evopen/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson02/2/evopen/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson02/2/evopen/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson02/2/evopen/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson02/2/evopen/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson02/2/evopen/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson02/2/evopen/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =(1 << 20) // allow el1 to use SIMD registers msr CPACR_EL1, x0 // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0500e/CIHBGEAB.html ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson02/2/evopen/src/config.txt ================================================ arm_control=0x200 kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson02/2/evopen/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "mini_uart.h" void kernel_main(void) { uart_init(); init_printf(0, putc); int el = get_el(); printf("Exception level: %d \r\n", el); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson02/2/evopen/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson02/2/evopen/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson02/2/evopen/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson02/2/evopen/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson02/2/evopen/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson02/2/evopen/start.sh ================================================ #!/bin/bash ./build.sh if [ $1 == "pi" ]; then cp -f config.txt kernel8.img /Volumes/boot/ && diskutil umount /Volumes/boot elif [ $1 == "qemu" ]; then qemu-system-aarch64 -M raspi3 -smp cores=4,sockets=1 -nographic -kernel kernel-qemu.img fi ================================================ FILE: exercises/lesson02/2/gcrisis/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean: rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson02/2/gcrisis/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR1_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // SCTLR_EL2, System Control Register (EL2), Page 2665 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR2_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL2h (9 << 0) #define SPSR_EL1h (5 << 0) #define SPSR_EL2_VALUE (SPSR_MASK_ALL | SPSR_EL2h) #define SPSR_EL1_VALUE (SPSR_MASK_ALL | SPSR_EL1h) /* *CAPCR_EL1 Enable EL1 FP */ #define CPACR_EL1_FPEN (3<<20) #define CPACR_EL1_VALUE (CPACR_EL1_FPEN) #endif ================================================ FILE: exercises/lesson02/2/gcrisis/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init (int baudrate ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); void putc(void* p,char c); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson02/2/gcrisis/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson02/2/gcrisis/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson02/2/gcrisis/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson02/2/gcrisis/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson02/2/gcrisis/include/printf.h ================================================ /* File: printf.h Copyright (c) 2004,2012 Kustaa Nyholm / SpareTimeLabs All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of the Kustaa Nyholm or SpareTimeLabs nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson02/2/gcrisis/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern unsigned int get_el(void); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson02/2/gcrisis/src/boot.S ================================================ #include "mm.h" #include "arm/sysregs.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0,=SCTLR2_VALUE_MMU_DISABLED msr sctlr_el2,x0 ldr x0,=HCR_VALUE msr hcr_el2 ,x0 ldr x0,=SCR_VALUE msr scr_el3, x0 ldr x0,=CPACR_EL1_VALUE //allow el1 use FP/SIMD msr cpacr_el1,x0 ldr x0,=SPSR_EL2_VALUE msr spsr_el3,x0 adr x0,el2_entry msr elr_el3,x0 eret el2_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel2_main ldr x0,=SCTLR1_VALUE_MMU_DISABLED msr sctlr_el1,x0 ldr x0,=SPSR_EL1_VALUE msr spsr_el2,x0 adr x0,el1_entry msr elr_el2,x0 eret el1_entry: mov sp, #LOW_MEMORY bl kernel1_main b proc_hang // should never come here ================================================ FILE: exercises/lesson02/2/gcrisis/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson02/2/gcrisis/src/kernel.c ================================================ #include "mini_uart.h" #include "printf.h" #include "utils.h" void kernel1_main(void) { //init uart and printf uart_init(115200); init_printf(0,putc); while (1) { printf("current EL:%d\r\n",get_el()); uart_send(uart_recv()); } } void kernel2_main(void) { //init uart and printf uart_init(115200); init_printf(0,putc); printf("current EL:%d\r\n",get_el()); return; } ================================================ FILE: exercises/lesson02/2/gcrisis/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot)} .text :{ *(.text)} .rodata : {*(.rodata) } .data : {*(.data) } . = ALIGN(0x8); bss_begin =.; .bss : {*(.bss*) } bss_end =.; } ================================================ FILE: exercises/lesson02/2/gcrisis/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" #define BAUD_REG_VAL(baud_rate) ((250000000/(baud_rate)/8-1)&0x0000ffff) void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init (int baudrate ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,(int)BAUD_REG_VAL(baudrate)); //Set baud rate to baudrate put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson02/2/gcrisis/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson02/2/gcrisis/src/printf.c ================================================ /* * Copyright (c) 2004,2012 Kustaa Nyholm / SpareTimeLabs * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this list * of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, this * list of conditions and the following disclaimer in the documentation and/or other * materials provided with the distribution. * * Neither the name of the Kustaa Nyholm or SpareTimeLabs nor the names of its * contributors may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson02/2/gcrisis/src/utils.S ================================================ .globl get_el get_el: mrs x0,CurrentEL lsr x0,x0,#2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson02/2/rs/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel7.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel7.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel7.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel7.elf -O binary kernel7.img ================================================ FILE: exercises/lesson02/2/rs/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson02/2/rs/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson02/2/rs/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of // AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED \ (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | \ SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of // AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of // AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of // AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #define CAPCR_RESERVED (3 << 29) | (0x1f << 22) | 0xfffff #define CAPCR_FPEN_NOTRAP (3 << 20) #define CAPCR_VALUE CAPCR_RESERVED | CAPCR_FPEN_NOTRAP #endif ================================================ FILE: exercises/lesson02/2/rs/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init(void); char uart_recv(void); void uart_send(char c); void putc(void *p, char c); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson02/2/rs/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson02/2/rs/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson02/2/rs/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE + 0x00200004) #define GPSET0 (PBASE + 0x0020001C) #define GPCLR0 (PBASE + 0x00200028) #define GPPUD (PBASE + 0x00200094) #define GPPUDCLK0 (PBASE + 0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson02/2/rs/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE + 0x00215004) #define AUX_MU_IO_REG (PBASE + 0x00215040) #define AUX_MU_IER_REG (PBASE + 0x00215044) #define AUX_MU_IIR_REG (PBASE + 0x00215048) #define AUX_MU_LCR_REG (PBASE + 0x0021504C) #define AUX_MU_MCR_REG (PBASE + 0x00215050) #define AUX_MU_LSR_REG (PBASE + 0x00215054) #define AUX_MU_MSR_REG (PBASE + 0x00215058) #define AUX_MU_SCRATCH (PBASE + 0x0021505C) #define AUX_MU_CNTL_REG (PBASE + 0x00215060) #define AUX_MU_STAT_REG (PBASE + 0x00215064) #define AUX_MU_BAUD_REG (PBASE + 0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson02/2/rs/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void *putp, void (*putf)(void *, char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char *s, char *fmt, ...); void tfp_format(void *putp, void (*putf)(void *, char), char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson02/2/rs/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay(unsigned long); extern void put32(unsigned long, unsigned int); extern unsigned int get32(unsigned long); extern int get_el(void); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson02/2/rs/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0, #0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =CAPCR_VALUE msr cpacr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson02/2/rs/src/config.txt ================================================ arm_control=0x200 kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson02/2/rs/src/kernel.c ================================================ #include "mini_uart.h" #include "printf.h" #include "utils.h" void kernel_main(void) { uart_init(); init_printf(0, putc); int el = get_el(); printf("Exception level: %d \r\n", el); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson02/2/rs/src/linker.ld ================================================ SECTIONS { . = 0x80000; .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson02/2/rs/src/mini_uart.c ================================================ #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" #include "utils.h" void uart_send(char c) { while (1) { if (get32(AUX_MU_LSR_REG) & 0x20) break; } put32(AUX_MU_IO_REG, c); } char uart_recv(void) { while (1) { if (get32(AUX_MU_LSR_REG) & 0x01) break; } return (get32(AUX_MU_IO_REG) & 0xFF); } void uart_send_string(char *str) { for (int i = 0; str[i] != '\0'; i++) { uart_send((char)str[i]); } } void uart_init(void) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7 << 12); // clean gpio14 selector |= 2 << 12; // set alt5 for gpio14 selector &= ~(7 << 15); // clean gpio15 selector |= 2 << 15; // set alt5 for gpio15 put32(GPFSEL1, selector); put32(GPPUD, 0); delay(150); put32(GPPUDCLK0, (1 << 14) | (1 << 15)); delay(150); put32(GPPUDCLK0, 0); put32(AUX_ENABLES, 1); // Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG, 0); // Disable auto flow control and disable receiver // and transmitter (for now) put32(AUX_MU_IER_REG, 0); // Disable receive and transmit interrupts put32(AUX_MU_LCR_REG, 3); // Enable 8 bit mode put32(AUX_MU_MCR_REG, 0); // Set RTS line to be always high put32(AUX_MU_BAUD_REG, 270); // Set baud rate to 115200 put32(AUX_MU_CNTL_REG, 3); // Finally, enable transmitter and receiver } // This function is required by printf function void putc(void *p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson02/2/rs/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson02/2/rs/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf)(void *, char); static putcf stdout_putf; static void *stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc, char *bf) { int n = 0; unsigned int d = 1; while (num / d >= base) d *= base; while (d != 0) { int dgt = num / d; num %= d; d /= base; if (n || dgt > 0 || d == 0) { *bf++ = dgt + (dgt < 10 ? '0' : (uc ? 'A' : 'a') - 10); ++n; } } *bf = 0; } static void li2a(long num, char *bf) { if (num < 0) { num = -num; *bf++ = '-'; } uli2a(num, 10, 0, bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc, char *bf) { int n = 0; unsigned int d = 1; while (num / d >= base) d *= base; while (d != 0) { int dgt = num / d; num %= d; d /= base; if (n || dgt > 0 || d == 0) { *bf++ = dgt + (dgt < 10 ? '0' : (uc ? 'A' : 'a') - 10); ++n; } } *bf = 0; } static void i2a(int num, char *bf) { if (num < 0) { num = -num; *bf++ = '-'; } ui2a(num, 10, 0, bf); } static int a2d(char ch) { if (ch >= '0' && ch <= '9') return ch - '0'; else if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10; else if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10; else return -1; } static char a2i(char ch, char **src, int base, int *nump) { char *p = *src; int num = 0; int digit; while ((digit = a2d(ch)) >= 0) { if (digit > base) break; num = num * base + digit; ch = *p++; } *src = p; *nump = num; return ch; } static void putchw(void *putp, putcf putf, int n, char z, char *bf) { char fc = z ? '0' : ' '; char ch; char *p = bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp, fc); while ((ch = *bf++)) putf(putp, ch); } void tfp_format(void *putp, putcf putf, char *fmt, va_list va) { char bf[12]; char ch; while ((ch = *(fmt++))) { if (ch != '%') putf(putp, ch); else { char lz = 0; #ifdef PRINTF_LONG_SUPPORT char lng = 0; #endif int w = 0; ch = *(fmt++); if (ch == '0') { ch = *(fmt++); lz = 1; } if (ch >= '0' && ch <= '9') { ch = a2i(ch, &fmt, 10, &w); } #ifdef PRINTF_LONG_SUPPORT if (ch == 'l') { ch = *(fmt++); lng = 1; } #endif switch (ch) { case 0: goto abort; case 'u': { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int), 10, 0, bf); else #endif ui2a(va_arg(va, unsigned int), 10, 0, bf); putchw(putp, putf, w, lz, bf); break; } case 'd': { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int), bf); else #endif i2a(va_arg(va, int), bf); putchw(putp, putf, w, lz, bf); break; } case 'x': case 'X': #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int), 16, (ch == 'X'), bf); else #endif ui2a(va_arg(va, unsigned int), 16, (ch == 'X'), bf); putchw(putp, putf, w, lz, bf); break; case 'c': putf(putp, (char)(va_arg(va, int))); break; case 's': putchw(putp, putf, w, 0, va_arg(va, char *)); break; case '%': putf(putp, ch); default: break; } } } abort:; } void init_printf(void *putp, void (*putf)(void *, char)) { stdout_putf = putf; stdout_putp = putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va, fmt); tfp_format(stdout_putp, stdout_putf, fmt, va); va_end(va); } static void putcp(void *p, char c) { *(*((char **)p))++ = c; } void tfp_sprintf(char *s, char *fmt, ...) { va_list va; va_start(va, fmt); tfp_format(&s, putcp, fmt, va); putcp(&s, 0); va_end(va); } ================================================ FILE: exercises/lesson02/2/rs/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1, [x0] ret .globl get32 get32: ldr w0, [x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson02/2/szediwy/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src .PHONY: clean all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img @echo "clean: [SUCCESS]" $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson02/2/szediwy/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson02/2/szediwy/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson02/2/szediwy/include/custom_printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); void test (char c); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson02/2/szediwy/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 256 // size of all saved registers #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #endif ================================================ FILE: exercises/lesson02/2/szediwy/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson02/2/szediwy/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init(void); char uart_recv(void); void uart_send(char c); void uart_send_string(char *str); void putc(void *p, char c); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson02/2/szediwy/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson02/2/szediwy/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H // #define PBASE 0x3F000000 // So a peripheral described in this document as being at legacy address 0x7Enn_nnnn is available in the 35-bit address // space at 0x4_7Enn_nnnn, and visible to the ARM at 0x0_FEnn_nnnn if Low Peripheral mode is enabled. // 0x7E000000 (legacy) -> 0x4_7E00_0000 (35-bit) -> 0x0_FE00_0000 (low peripheral) #define PBASE 0xFE000000 // The base address of the GIC-400 is 0x4c0040000. Note that, unlike other peripheral addresses in this document, this is an // ARM-only address and not a legacy master address. If Low Peripheral mode is enabled this base address becomes // 0xff840000. // The GIC-400 is configured with "NUM_CPUS=4" and "NUM_SPIS=192". For full register details, please refer to the ARM // GIC-400 documentation on the ARM Developer website. #define GIC_BASE 0xFF840000 // The ARMC register base address is 0x7e00b000 -> 0x4_7E00B000 -> 0x0FE00B000 #define ARMC_BASE 0x0FE00B000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson02/2/szediwy/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) // #define GPSET0 (PBASE+0x0020001C) // #define GPCLR0 (PBASE+0x00200028) // #define GPPUD (PBASE+0x00200094) // #define GPPUDCLK0 (PBASE+0x00200098) #define GPIO_PUP_PDN_CNTRL_REG0 (PBASE+0x002000E4) #define UART0_DR (PBASE+0x00201000) #define UART0_FR (PBASE+0x00201018) #define UART0_IBRD (PBASE+0x00201024) #define UART0_FBRD (PBASE+0x00201028) #define UART0_LCRH (PBASE+0x0020102C) #define UART0_CR (PBASE+0x00201030) #define UART0_IMSC (PBASE+0x00201038) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson02/2/szediwy/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_PENDING_0_CORE_0 (PBASE+0x0000B200) #define IRQ_PENDING_1_CORE_0 (PBASE+0x0000B204) #define IRQ_PENDING_2_CORE_0 (PBASE+0x0000B208) //#define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_0_CORE_0 (PBASE+0x0000B210) #define ENABLE_IRQS_1_CORE_0 (PBASE+0x0000B214) #define ENABLE_IRQS_2_CORE_0 (PBASE+0x0000B218) #define DISABLE_IRQS_0_CORE_0 (PBASE+0x0000B220) #define DISABLE_IRQS_1_CORE_0 (PBASE+0x0000B224) #define DISABLE_IRQS_2_CORE_0 (PBASE+0x0000B228) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson02/2/szediwy/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson02/2/szediwy/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson02/2/szediwy/include/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #define BYTE_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c" #define BYTE_TO_BINARY(byte) \ (byte & 0x80 ? '1' : '0'), \ (byte & 0x40 ? '1' : '0'), \ (byte & 0x20 ? '1' : '0'), \ (byte & 0x10 ? '1' : '0'), \ (byte & 0x08 ? '1' : '0'), \ (byte & 0x04 ? '1' : '0'), \ (byte & 0x02 ? '1' : '0'), \ (byte & 0x01 ? '1' : '0') #define CPACR_EL1_MASK (1 << 28 | 3 << 20 | 3 << 16) #endif ================================================ FILE: exercises/lesson02/2/szediwy/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson02/2/szediwy/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay(unsigned long); extern void put32(unsigned long, unsigned int); extern unsigned int get32(unsigned long); extern unsigned int get_el(); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson02/2/szediwy/src/boot.S ================================================ #include "mm.h" #include "sysregs.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0, #0x3 cbz x0, init_bss /* If processor id is not 0 then pending lock processor * (wait for `sev` instruction) */ wfe b master proc_hang: b proc_hang init_bss: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero sev /***********************************************************************/ /* Enable the other cores link: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/arm64/booting.rst?h=v5.3#n255 The boot loader is expected to enter the kernel on each CPU in the following manner: - The primary CPU must jump directly to the first instruction of the kernel image. The device tree blob passed by this CPU must contain an 'enable-method' property for each cpu node. The supported enable-methods are described below. It is expected that the bootloader will generate these device tree properties and insert them into the blob prior to kernel entry. - CPUs with a "spin-table" enable-method must have a 'cpu-release-addr' property in their cpu node. This property identifies a naturally-aligned 64-bit zero-initalised memory location. These CPUs should spin outside of the kernel in a reserved area of memory (communicated to the kernel by a /memreserve/ region in the device tree) polling their cpu-release-addr location, which must be contained in the reserved region. A wfe instruction may be inserted to reduce the overhead of the busy-loop and a sev will be issued by the primary CPU. When a read of the location pointed to by the cpu-release-addr returns a non-zero value, the CPU must jump to this value. The value will be written as a single 64-bit little-endian value, so CPUs must convert the read value to their native endianness before jumping to it. - CPUs with a "psci" enable method should remain outside of the kernel (i.e. outside of the regions of memory described to the kernel in the memory node, or in a reserved area of memory described to the kernel by a /memreserve/ region in the device tree). The kernel will issue CPU_ON calls as described in ARM document number ARM DEN 0022A ("Power State Coordination Interface System Software on ARM processors") to bring CPUs into the kernel. The device tree should contain a 'psci' node, as described in Documentation/devicetree/bindings/arm/psci.yaml. - Secondary CPU general-purpose register settings x0 = 0 (reserved for future use) x1 = 0 (reserved for future use) x2 = 0 (reserved for future use) x3 = 0 (reserved for future use) */ /* cpu0: cpu@0 { device_type = "cpu"; compatible = "arm,cortex-a72"; reg = <0>; enable-method = "spin-table"; cpu-release-addr = <0x0 0x000000d8>; }; cpu1: cpu@1 { device_type = "cpu"; compatible = "arm,cortex-a72"; reg = <1>; enable-method = "spin-table"; cpu-release-addr = <0x0 0x000000e0>; }; cpu2: cpu@2 { device_type = "cpu"; compatible = "arm,cortex-a72"; reg = <2>; enable-method = "spin-table"; cpu-release-addr = <0x0 0x000000e8>; }; cpu3: cpu@3 { device_type = "cpu"; compatible = "arm,cortex-a72"; reg = <3>; enable-method = "spin-table"; cpu-release-addr = <0x0 0x000000f0>; }; */ /****************************************************/ mov x0, #0 adr x0, configure_el1 mov x1, #0xe0 str x0, [x1] mov x1, #0xe8 str x0, [x1] mov x1, #0xf0 str x0, [x1] configure_el1: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =CPACR_EL1_MASK msr cpacr_el1, x0 ldr x0, =SPSR_VALUE msr spsr_el2, x0 adr x0, master msr elr_el2, x0 eret master: mrs x0, mpidr_el1 and x0, x0, #0x3 mov x1, #SECTION_SIZE mul x1, x1, x0 add x1, x1, #LOW_MEMORY mov sp, x1 bl kernel_main b proc_hang ================================================ FILE: exercises/lesson02/2/szediwy/src/config.txt ================================================ arm_64bit=1 enable_uart=1 uart_2ndstage=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson02/2/szediwy/src/custom_printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "custom_printf.h" typedef void (*putcf)(void *, char); static putcf stdout_putf; static void *stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc, char *bf) { int n = 0; unsigned int d = 1; while (num / d >= base) d *= base; while (d != 0) { int dgt = num / d; num %= d; d /= base; if (n || dgt > 0 || d == 0) { *bf++ = dgt + (dgt < 10 ? '0' : (uc ? 'A' : 'a') - 10); ++n; } } *bf = 0; } static void li2a(long num, char *bf) { if (num < 0) { num = -num; *bf++ = '-'; } uli2a(num, 10, 0, bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc, char *bf) { int n = 0; unsigned int d = 1; while (num / d >= base) d *= base; while (d != 0) { int dgt = num / d; num %= d; d /= base; if (n || dgt > 0 || d == 0) { *bf++ = dgt + (dgt < 10 ? '0' : (uc ? 'A' : 'a') - 10); ++n; } } *bf = 0; } static void i2a(int num, char *bf) { if (num < 0) { num = -num; *bf++ = '-'; } ui2a(num, 10, 0, bf); } static int a2d(char ch) { if (ch >= '0' && ch <= '9') return ch - '0'; else if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10; else if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10; else return -1; } static char a2i(char ch, char **src, int base, int *nump) { char *p = *src; int num = 0; int digit; while ((digit = a2d(ch)) >= 0) { if (digit > base) break; num = num * base + digit; ch = *p++; } *src = p; *nump = num; return ch; } static void putchw(void *putp, putcf putf, int n, char z, char *bf) { char fc = z ? '0' : ' '; char ch; char *p = bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp, fc); while ((ch = *bf++)) putf(putp, ch); } void tfp_format(void *putp, putcf putf, char *fmt, va_list va) { char bf[12]; char ch; while ((ch = *(fmt++))) { if (ch != '%') { putf(putp, ch); } else { char lz = 0; #ifdef PRINTF_LONG_SUPPORT char lng = 0; #endif int w = 0; ch = *(fmt++); if (ch == '0') { ch = *(fmt++); lz = 1; } if (ch >= '0' && ch <= '9') { ch = a2i(ch, &fmt, 10, &w); } #ifdef PRINTF_LONG_SUPPORT if (ch == 'l') { ch = *(fmt++); lng = 1; } #endif switch (ch) { case 0: goto abort; case 'u': { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int), 10, 0, bf); else #endif ui2a(va_arg(va, unsigned int), 10, 0, bf); putchw(putp, putf, w, lz, bf); break; } case 'd': { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int), bf); else #endif i2a(va_arg(va, int), bf); putchw(putp, putf, w, lz, bf); break; } case 'x': case 'X': #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int), 16, (ch == 'X'), bf); else #endif ui2a(va_arg(va, unsigned int), 16, (ch == 'X'), bf); putchw(putp, putf, w, lz, bf); break; case 'c': putf(putp, (char)(va_arg(va, int))); break; case 's': putchw(putp, putf, w, 0, va_arg(va, char *)); break; case '%': putf(putp, ch); default: break; } } } abort:; } void init_printf(void *putp, void (*putf)(void *, char)) { stdout_putf = putf; stdout_putp = putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va, fmt); tfp_format(stdout_putp, stdout_putf, fmt, va); va_end(va); } static void putcp(void *p, char c) { *(*((char **)p))++ = c; } void tfp_sprintf(char *s, char *fmt, ...) { va_list va; va_start(va, fmt); tfp_format(&s, putcp, fmt, va); putcp(&s, 0); va_end(va); } ================================================ FILE: exercises/lesson02/2/szediwy/src/entry.S ================================================ #include "entry.h" .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] str x30, [sp, #16 * 15] .endm .macro kernel_exit ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] ldr x30, [sp, #16 * 15] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 ventry irq_invalid_el0_64 // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry ERROR_INVALID_EL1h sync_invalid_el0_64: handle_invalid_entry SYNC_INVALID_EL0_64 irq_invalid_el0_64: handle_invalid_entry IRQ_INVALID_EL0_64 fiq_invalid_el0_64: handle_invalid_entry FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry ERROR_INVALID_EL0_32 el1_irq: kernel_entry bl handle_irq kernel_exit .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson02/2/szediwy/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson02/2/szediwy/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "timer.h" #include "entry.h" #include "peripherals/irq.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32" }; void enable_interrupt_controller() { put32(ENABLE_IRQS_0_CORE_0, SYSTEM_TIMER_IRQ_1); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { unsigned int irq = get32(IRQ_PENDING_0_CORE_0); switch (irq) { case (SYSTEM_TIMER_IRQ_1): handle_timer_irq(); break; default: printf("Unknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson02/2/szediwy/src/kernel.c ================================================ #include "custom_printf.h" #include "timer.h" #include "utils.h" #include "mini_uart.h" #include "irq.h" void kernel_main(unsigned long processor_index) { static unsigned int current_processor_index = 0; if (processor_index == 0) { uart_init(); init_printf(0, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); } while (processor_index != current_processor_index) ; int exception_level = get_el(); printf("{CPU: %d, Exception level: %d} \r\n", processor_index, exception_level); current_processor_index++; if (processor_index == 0) { // if current_processor_index == 4 then all processors send message while (current_processor_index != 4) ; for (;;) { uart_send(uart_recv()); } } } ================================================ FILE: exercises/lesson02/2/szediwy/src/linker.ld ================================================ SECTIONS { . = 0x80000; .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson02/2/szediwy/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send(char c) { while (get32(UART0_FR) & (1 << 5)) { } put32(UART0_DR, c); } char uart_recv(void) { while (get32(UART0_FR) & (1 << 4)) { } return (get32(UART0_DR) & 0xFF); } void uart_send_string(char *str) { for (int i = 0; str[i] != '\0'; i++) { uart_send((char)str[i]); } } void uart_init(void) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7 << 12); // clean gpio14 selector |= 4 << 12; // set alt0 for gpio14 selector &= ~(7 << 15); // clean gpio15 selector |= 4 << 15; // set alt0 for gpio15 put32(GPFSEL1, selector); unsigned int pullRegister; pullRegister = get32(GPIO_PUP_PDN_CNTRL_REG0); pullRegister &= ~(3 << 30); pullRegister &= ~(3 << 28); put32(GPIO_PUP_PDN_CNTRL_REG0, pullRegister); //first disable uart put32(UART0_CR, 0); put32(UART0_IMSC, 0); //from ../adkaster/src/uart.c // Assume 48MHz UART Reference Clock (Standard) // Calculate UART clock divider per datasheet // BAUDDIV = (FUARTCLK/(16 Baud rate)) // Note: We get 6 bits of fraction for the baud div // 48000000/(16 * 115200) = 3000000/115200 = 26.0416666... // Integer part = 26 :) // From http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0183g/I54603.html // we want floor(0.04166666.. * 64 + 0.5) = 3 put32(UART0_IBRD, 26); put32(UART0_FBRD, 3); //little endian: 0111|0000 => 8 bits and enable fifos put32(UART0_LCRH, 7 << 4); //little endian: 0011|0000|0001 => enable: rx tx uart put32(UART0_CR, (1 << 9) | (1 << 8) | (1 << 0)); } // This function is required by printf function void putc(void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson02/2/szediwy/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson02/2/szediwy/src/timer.c ================================================ #include "utils.h" #include "custom_printf.h" #include "peripherals/timer.h" const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); printf("Timer interrupt received\n\r"); } ================================================ FILE: exercises/lesson02/2/szediwy/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret ================================================ FILE: exercises/lesson02/2/zjd0112/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson02/2/zjd0112/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson02/2/zjd0112/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson02/2/zjd0112/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR1_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR1_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR1_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR1_I_CACHE_DISABLED (0 << 12) #define SCTLR1_D_CACHE_DISABLED (0 << 2) #define SCTLR1_MMU_DISABLED (0 << 0) #define SCTLR1_MMU_ENABLED (1 << 0) #define SCTLR1_VALUE_MMU_DISABLED (SCTLR1_RESERVED | SCTLR1_EE_LITTLE_ENDIAN | SCTLR1_I_CACHE_DISABLED | SCTLR1_D_CACHE_DISABLED | SCTLR1_MMU_DISABLED) // *************************************** // SCTLR_EL2, System Control Register (EL2), Page 2665 of AArch64-Reference-Manual. // *************************************** #define SCTLR2_RESERVED (3 << 28) | (3 << 22) | (1 << 18) | (1 << 16) | (1 << 11) | (1 << 4) #define SCTLR2_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR2_I_CACHE_DISABLED (0 << 12) #define SCTLR2_D_CACHE_DISABLED (0 << 2) #define SCTLR2_MMU_DISABLED (0 << 0) #define SCTLR2_MMU_ENABLED (1 << 0) #define SCTLR2_VALUE_MMU_DISABLED (SCTLR2_RESERVED | SCTLR2_EE_LITTLE_ENDIAN | SCTLR2_I_CACHE_DISABLED | SCTLR2_D_CACHE_DISABLED | SCTLR2_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL_EL3 (7 << 6) #define SPSR_EL2h (9 << 0) #define SPSR_VALUE_EL3 (SPSR_MASK_ALL_EL3 | SPSR_EL2h) // *************************************** // SPSR_EL2, Saved Program Status Register (EL2) Page 383 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL_EL2 (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE_EL2 (SPSR_MASK_ALL_EL2 | SPSR_EL1h) // *************************************** // CPACR_EL1,Architectural Feature Access Control Register Page 2411 of AArch64-Reference-Manual. // *************************************** #define CPACR_VALUE_EL1 (3 << 20) #endif ================================================ FILE: exercises/lesson02/2/zjd0112/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson02/2/zjd0112/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson02/2/zjd0112/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson02/2/zjd0112/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson02/2/zjd0112/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson02/2/zjd0112/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson02/2/zjd0112/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson02/2/zjd0112/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR2_VALUE_MMU_DISABLED msr sctlr_el2, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE_EL3 msr spsr_el3, x0 ldr x0, =CPACR_VALUE_EL1 // This control does not cause any instructions to be trapped. msr cpacr_el1, x0 adr x0, el2_entry msr elr_el3, x0 eret el2_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_el2 ldr x0, =SCTLR1_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =SPSR_VALUE_EL2 msr spsr_el2, x0 adr x0, el1_entry msr elr_el2, x0 eret el1_entry: mov sp, #LOW_MEMORY bl kernel_el1 b proc_hang // should never come here ================================================ FILE: exercises/lesson02/2/zjd0112/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson02/2/zjd0112/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "mini_uart.h" void kernel_el2(void) { uart_init(); init_printf(0, putc); int el = get_el(); printf("Exception level: %d \r\n", el); } void kernel_el1(void) { int el = get_el(); printf("Exception level: %d \r\n", el); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson02/2/zjd0112/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson02/2/zjd0112/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson02/2/zjd0112/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson02/2/zjd0112/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson02/2/zjd0112/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson02/2/zjd0112/test1.txt ================================================ kernel8.elf: file format elf64-littleaarch64 Disassembly of section .text.boot: 0000000000000000 <_start>: 0: d53800a0 mrs x0, mpidr_el1 4: 92401c00 and x0, x0, #0xff 8: b4000060 cbz x0, 14 c: 14000001 b 10 0000000000000010 : 10: 14000000 b 10 0000000000000014 : 14: 58000360 ldr x0, 80 18: d51c1000 msr sctlr_el2, x0 1c: 58000360 ldr x0, 88 20: d51c1100 msr hcr_el2, x0 24: 58000360 ldr x0, 90 28: d51e1100 msr scr_el3, x0 2c: 58000360 ldr x0, 98 30: d51e4000 msr spsr_el3, x0 34: 10000060 adr x0, 40 38: d51e4020 msr elr_el3, x0 3c: d69f03e0 eret 0000000000000040 : 40: 10006780 adr x0, d30 44: 100067e1 adr x1, d40 48: cb000021 sub x1, x1, x0 4c: 94000325 bl ce0 50: b26a03ff mov sp, #0x400000 // #4194304 54: 940002fc bl c44 58: 58000240 ldr x0, a0 5c: d5181000 msr sctlr_el1, x0 60: 58000240 ldr x0, a8 64: d51c4000 msr spsr_el2, x0 68: 10000060 adr x0, 74 6c: d51c4020 msr elr_el2, x0 70: d69f03e0 eret 0000000000000074 : 74: b26a03ff mov sp, #0x400000 // #4194304 78: 94000304 bl c88 7c: 17ffffe5 b 10 80: 30c50810 .word 0x30c50810 84: 00000000 .word 0x00000000 88: 80000000 .word 0x80000000 8c: 00000000 .word 0x00000000 90: 00000431 .word 0x00000431 94: 00000000 .word 0x00000000 98: 000001c9 .word 0x000001c9 9c: 00000000 .word 0x00000000 a0: 30d00800 .word 0x30d00800 a4: 00000000 .word 0x00000000 a8: 000001c5 .word 0x000001c5 ac: 00000000 .word 0x00000000 Disassembly of section .text: 00000000000000b0 : b0: a9be7bfd stp x29, x30, [sp, #-32]! b4: 910003fd mov x29, sp b8: 39007fa0 strb w0, [x29, #31] bc: d28a0a80 mov x0, #0x5054 // #20564 c0: f2a7e420 movk x0, #0x3f21, lsl #16 c4: 94000302 bl ccc c8: 121b0000 and w0, w0, #0x20 cc: 7100001f cmp w0, #0x0 d0: 54000041 b.ne d8 // b.any d4: 17fffffa b bc d8: d503201f nop dc: 39407fa0 ldrb w0, [x29, #31] e0: 2a0003e1 mov w1, w0 e4: d28a0800 mov x0, #0x5040 // #20544 e8: f2a7e420 movk x0, #0x3f21, lsl #16 ec: 940002f6 bl cc4 f0: d503201f nop f4: a8c27bfd ldp x29, x30, [sp], #32 f8: d65f03c0 ret 00000000000000fc : fc: a9bf7bfd stp x29, x30, [sp, #-16]! 100: 910003fd mov x29, sp 104: d28a0a80 mov x0, #0x5054 // #20564 108: f2a7e420 movk x0, #0x3f21, lsl #16 10c: 940002f0 bl ccc 110: 12000000 and w0, w0, #0x1 114: 7100001f cmp w0, #0x0 118: 54000041 b.ne 120 // b.any 11c: 17fffffa b 104 120: d503201f nop 124: d28a0800 mov x0, #0x5040 // #20544 128: f2a7e420 movk x0, #0x3f21, lsl #16 12c: 940002e8 bl ccc 130: 12001c00 and w0, w0, #0xff 134: a8c17bfd ldp x29, x30, [sp], #16 138: d65f03c0 ret 000000000000013c : 13c: a9bd7bfd stp x29, x30, [sp, #-48]! 140: 910003fd mov x29, sp 144: f9000fa0 str x0, [x29, #24] 148: b9002fbf str wzr, [x29, #44] 14c: 14000009 b 170 150: b9802fa0 ldrsw x0, [x29, #44] 154: f9400fa1 ldr x1, [x29, #24] 158: 8b000020 add x0, x1, x0 15c: 39400000 ldrb w0, [x0] 160: 97ffffd4 bl b0 164: b9402fa0 ldr w0, [x29, #44] 168: 11000400 add w0, w0, #0x1 16c: b9002fa0 str w0, [x29, #44] 170: b9802fa0 ldrsw x0, [x29, #44] 174: f9400fa1 ldr x1, [x29, #24] 178: 8b000020 add x0, x1, x0 17c: 39400000 ldrb w0, [x0] 180: 7100001f cmp w0, #0x0 184: 54fffe61 b.ne 150 // b.any 188: d503201f nop 18c: a8c37bfd ldp x29, x30, [sp], #48 190: d65f03c0 ret 0000000000000194 : 194: a9be7bfd stp x29, x30, [sp, #-32]! 198: 910003fd mov x29, sp 19c: d2800080 mov x0, #0x4 // #4 1a0: f2a7e400 movk x0, #0x3f20, lsl #16 1a4: 940002ca bl ccc 1a8: b9001fa0 str w0, [x29, #28] 1ac: b9401fa0 ldr w0, [x29, #28] 1b0: 12117000 and w0, w0, #0xffff8fff 1b4: b9001fa0 str w0, [x29, #28] 1b8: b9401fa0 ldr w0, [x29, #28] 1bc: 32130000 orr w0, w0, #0x2000 1c0: b9001fa0 str w0, [x29, #28] 1c4: b9401fa0 ldr w0, [x29, #28] 1c8: 120e7000 and w0, w0, #0xfffc7fff 1cc: b9001fa0 str w0, [x29, #28] 1d0: b9401fa0 ldr w0, [x29, #28] 1d4: 32100000 orr w0, w0, #0x10000 1d8: b9001fa0 str w0, [x29, #28] 1dc: b9401fa1 ldr w1, [x29, #28] 1e0: d2800080 mov x0, #0x4 // #4 1e4: f2a7e400 movk x0, #0x3f20, lsl #16 1e8: 940002b7 bl cc4 1ec: 52800001 mov w1, #0x0 // #0 1f0: d2801280 mov x0, #0x94 // #148 1f4: f2a7e400 movk x0, #0x3f20, lsl #16 1f8: 940002b3 bl cc4 1fc: d28012c0 mov x0, #0x96 // #150 200: 940002b5 bl cd4 204: 52980001 mov w1, #0xc000 // #49152 208: d2801300 mov x0, #0x98 // #152 20c: f2a7e400 movk x0, #0x3f20, lsl #16 210: 940002ad bl cc4 214: d28012c0 mov x0, #0x96 // #150 218: 940002af bl cd4 21c: 52800001 mov w1, #0x0 // #0 220: d2801300 mov x0, #0x98 // #152 224: f2a7e400 movk x0, #0x3f20, lsl #16 228: 940002a7 bl cc4 22c: 52800021 mov w1, #0x1 // #1 230: d28a0080 mov x0, #0x5004 // #20484 234: f2a7e420 movk x0, #0x3f21, lsl #16 238: 940002a3 bl cc4 23c: 52800001 mov w1, #0x0 // #0 240: d28a0c00 mov x0, #0x5060 // #20576 244: f2a7e420 movk x0, #0x3f21, lsl #16 248: 9400029f bl cc4 24c: 52800001 mov w1, #0x0 // #0 250: d28a0880 mov x0, #0x5044 // #20548 254: f2a7e420 movk x0, #0x3f21, lsl #16 258: 9400029b bl cc4 25c: 52800061 mov w1, #0x3 // #3 260: d28a0980 mov x0, #0x504c // #20556 264: f2a7e420 movk x0, #0x3f21, lsl #16 268: 94000297 bl cc4 26c: 52800001 mov w1, #0x0 // #0 270: d28a0a00 mov x0, #0x5050 // #20560 274: f2a7e420 movk x0, #0x3f21, lsl #16 278: 94000293 bl cc4 27c: 528021c1 mov w1, #0x10e // #270 280: d28a0d00 mov x0, #0x5068 // #20584 284: f2a7e420 movk x0, #0x3f21, lsl #16 288: 9400028f bl cc4 28c: 52800061 mov w1, #0x3 // #3 290: d28a0c00 mov x0, #0x5060 // #20576 294: f2a7e420 movk x0, #0x3f21, lsl #16 298: 9400028b bl cc4 29c: d503201f nop 2a0: a8c27bfd ldp x29, x30, [sp], #32 2a4: d65f03c0 ret 00000000000002a8 : 2a8: a9be7bfd stp x29, x30, [sp, #-32]! 2ac: 910003fd mov x29, sp 2b0: f9000fa0 str x0, [x29, #24] 2b4: 39005fa1 strb w1, [x29, #23] 2b8: 39405fa0 ldrb w0, [x29, #23] 2bc: 97ffff7d bl b0 2c0: d503201f nop 2c4: a8c27bfd ldp x29, x30, [sp], #32 2c8: d65f03c0 ret 00000000000002cc : 2cc: d100c3ff sub sp, sp, #0x30 2d0: b9001fe0 str w0, [sp, #28] 2d4: b9001be1 str w1, [sp, #24] 2d8: b90017e2 str w2, [sp, #20] 2dc: f90007e3 str x3, [sp, #8] 2e0: b9002fff str wzr, [sp, #44] 2e4: 52800020 mov w0, #0x1 // #1 2e8: b9002be0 str w0, [sp, #40] 2ec: 14000005 b 300 2f0: b9402be1 ldr w1, [sp, #40] 2f4: b9401be0 ldr w0, [sp, #24] 2f8: 1b007c20 mul w0, w1, w0 2fc: b9002be0 str w0, [sp, #40] 300: b9401fe1 ldr w1, [sp, #28] 304: b9402be0 ldr w0, [sp, #40] 308: 1ac00820 udiv w0, w1, w0 30c: b9401be1 ldr w1, [sp, #24] 310: 6b00003f cmp w1, w0 314: 54fffee9 b.ls 2f0 // b.plast 318: 1400002f b 3d4 31c: b9401fe1 ldr w1, [sp, #28] 320: b9402be0 ldr w0, [sp, #40] 324: 1ac00820 udiv w0, w1, w0 328: b90027e0 str w0, [sp, #36] 32c: b9401fe0 ldr w0, [sp, #28] 330: b9402be1 ldr w1, [sp, #40] 334: 1ac10802 udiv w2, w0, w1 338: b9402be1 ldr w1, [sp, #40] 33c: 1b017c41 mul w1, w2, w1 340: 4b010000 sub w0, w0, w1 344: b9001fe0 str w0, [sp, #28] 348: b9402be1 ldr w1, [sp, #40] 34c: b9401be0 ldr w0, [sp, #24] 350: 1ac00820 udiv w0, w1, w0 354: b9002be0 str w0, [sp, #40] 358: b9402fe0 ldr w0, [sp, #44] 35c: 7100001f cmp w0, #0x0 360: 540000e1 b.ne 37c // b.any 364: b94027e0 ldr w0, [sp, #36] 368: 7100001f cmp w0, #0x0 36c: 5400008c b.gt 37c 370: b9402be0 ldr w0, [sp, #40] 374: 7100001f cmp w0, #0x0 378: 540002e1 b.ne 3d4 // b.any 37c: b94027e0 ldr w0, [sp, #36] 380: 7100241f cmp w0, #0x9 384: 5400010d b.le 3a4 388: b94017e0 ldr w0, [sp, #20] 38c: 7100001f cmp w0, #0x0 390: 54000060 b.eq 39c // b.none 394: 528006e0 mov w0, #0x37 // #55 398: 14000004 b 3a8 39c: 52800ae0 mov w0, #0x57 // #87 3a0: 14000002 b 3a8 3a4: 52800600 mov w0, #0x30 // #48 3a8: b94027e1 ldr w1, [sp, #36] 3ac: 12001c22 and w2, w1, #0xff 3b0: f94007e1 ldr x1, [sp, #8] 3b4: 91000423 add x3, x1, #0x1 3b8: f90007e3 str x3, [sp, #8] 3bc: 0b020000 add w0, w0, w2 3c0: 12001c00 and w0, w0, #0xff 3c4: 39000020 strb w0, [x1] 3c8: b9402fe0 ldr w0, [sp, #44] 3cc: 11000400 add w0, w0, #0x1 3d0: b9002fe0 str w0, [sp, #44] 3d4: b9402be0 ldr w0, [sp, #40] 3d8: 7100001f cmp w0, #0x0 3dc: 54fffa01 b.ne 31c // b.any 3e0: f94007e0 ldr x0, [sp, #8] 3e4: 3900001f strb wzr, [x0] 3e8: d503201f nop 3ec: 9100c3ff add sp, sp, #0x30 3f0: d65f03c0 ret 00000000000003f4 : 3f4: a9be7bfd stp x29, x30, [sp, #-32]! 3f8: 910003fd mov x29, sp 3fc: b9001fa0 str w0, [x29, #28] 400: f9000ba1 str x1, [x29, #16] 404: b9401fa0 ldr w0, [x29, #28] 408: 7100001f cmp w0, #0x0 40c: 5400012a b.ge 430 // b.tcont 410: b9401fa0 ldr w0, [x29, #28] 414: 4b0003e0 neg w0, w0 418: b9001fa0 str w0, [x29, #28] 41c: f9400ba0 ldr x0, [x29, #16] 420: 91000401 add x1, x0, #0x1 424: f9000ba1 str x1, [x29, #16] 428: 528005a1 mov w1, #0x2d // #45 42c: 39000001 strb w1, [x0] 430: b9401fa0 ldr w0, [x29, #28] 434: f9400ba3 ldr x3, [x29, #16] 438: 52800002 mov w2, #0x0 // #0 43c: 52800141 mov w1, #0xa // #10 440: 97ffffa3 bl 2cc 444: d503201f nop 448: a8c27bfd ldp x29, x30, [sp], #32 44c: d65f03c0 ret 0000000000000450 : 450: d10043ff sub sp, sp, #0x10 454: 39003fe0 strb w0, [sp, #15] 458: 39403fe0 ldrb w0, [sp, #15] 45c: 7100bc1f cmp w0, #0x2f 460: 540000e9 b.ls 47c // b.plast 464: 39403fe0 ldrb w0, [sp, #15] 468: 7100e41f cmp w0, #0x39 46c: 54000088 b.hi 47c // b.pmore 470: 39403fe0 ldrb w0, [sp, #15] 474: 5100c000 sub w0, w0, #0x30 478: 14000014 b 4c8 47c: 39403fe0 ldrb w0, [sp, #15] 480: 7101801f cmp w0, #0x60 484: 540000e9 b.ls 4a0 // b.plast 488: 39403fe0 ldrb w0, [sp, #15] 48c: 7101981f cmp w0, #0x66 490: 54000088 b.hi 4a0 // b.pmore 494: 39403fe0 ldrb w0, [sp, #15] 498: 51015c00 sub w0, w0, #0x57 49c: 1400000b b 4c8 4a0: 39403fe0 ldrb w0, [sp, #15] 4a4: 7101001f cmp w0, #0x40 4a8: 540000e9 b.ls 4c4 // b.plast 4ac: 39403fe0 ldrb w0, [sp, #15] 4b0: 7101181f cmp w0, #0x46 4b4: 54000088 b.hi 4c4 // b.pmore 4b8: 39403fe0 ldrb w0, [sp, #15] 4bc: 5100dc00 sub w0, w0, #0x37 4c0: 14000002 b 4c8 4c4: 12800000 mov w0, #0xffffffff // #-1 4c8: 910043ff add sp, sp, #0x10 4cc: d65f03c0 ret 00000000000004d0 : 4d0: a9bc7bfd stp x29, x30, [sp, #-64]! 4d4: 910003fd mov x29, sp 4d8: 3900bfa0 strb w0, [x29, #47] 4dc: f90013a1 str x1, [x29, #32] 4e0: b9002ba2 str w2, [x29, #40] 4e4: f9000fa3 str x3, [x29, #24] 4e8: f94013a0 ldr x0, [x29, #32] 4ec: f9400000 ldr x0, [x0] 4f0: f9001fa0 str x0, [x29, #56] 4f4: b90037bf str wzr, [x29, #52] 4f8: 14000010 b 538 4fc: b94033a1 ldr w1, [x29, #48] 500: b9402ba0 ldr w0, [x29, #40] 504: 6b00003f cmp w1, w0 508: 5400026c b.gt 554 50c: b94037a1 ldr w1, [x29, #52] 510: b9402ba0 ldr w0, [x29, #40] 514: 1b007c20 mul w0, w1, w0 518: b94033a1 ldr w1, [x29, #48] 51c: 0b000020 add w0, w1, w0 520: b90037a0 str w0, [x29, #52] 524: f9401fa0 ldr x0, [x29, #56] 528: 91000401 add x1, x0, #0x1 52c: f9001fa1 str x1, [x29, #56] 530: 39400000 ldrb w0, [x0] 534: 3900bfa0 strb w0, [x29, #47] 538: 3940bfa0 ldrb w0, [x29, #47] 53c: 97ffffc5 bl 450 540: b90033a0 str w0, [x29, #48] 544: b94033a0 ldr w0, [x29, #48] 548: 7100001f cmp w0, #0x0 54c: 54fffd8a b.ge 4fc // b.tcont 550: 14000002 b 558 554: d503201f nop 558: f94013a0 ldr x0, [x29, #32] 55c: f9401fa1 ldr x1, [x29, #56] 560: f9000001 str x1, [x0] 564: f9400fa0 ldr x0, [x29, #24] 568: b94037a1 ldr w1, [x29, #52] 56c: b9000001 str w1, [x0] 570: 3940bfa0 ldrb w0, [x29, #47] 574: a8c47bfd ldp x29, x30, [sp], #64 578: d65f03c0 ret 000000000000057c : 57c: a9bc7bfd stp x29, x30, [sp, #-64]! 580: 910003fd mov x29, sp 584: f90017a0 str x0, [x29, #40] 588: f90013a1 str x1, [x29, #32] 58c: b9001fa2 str w2, [x29, #28] 590: 39006fa3 strb w3, [x29, #27] 594: f9000ba4 str x4, [x29, #16] 598: 39406fa0 ldrb w0, [x29, #27] 59c: 7100001f cmp w0, #0x0 5a0: 54000060 b.eq 5ac // b.none 5a4: 52800600 mov w0, #0x30 // #48 5a8: 14000002 b 5b0 5ac: 52800400 mov w0, #0x20 // #32 5b0: 3900dfa0 strb w0, [x29, #55] 5b4: f9400ba0 ldr x0, [x29, #16] 5b8: f9001fa0 str x0, [x29, #56] 5bc: 14000004 b 5cc 5c0: b9401fa0 ldr w0, [x29, #28] 5c4: 51000400 sub w0, w0, #0x1 5c8: b9001fa0 str w0, [x29, #28] 5cc: f9401fa0 ldr x0, [x29, #56] 5d0: 91000401 add x1, x0, #0x1 5d4: f9001fa1 str x1, [x29, #56] 5d8: 39400000 ldrb w0, [x0] 5dc: 7100001f cmp w0, #0x0 5e0: 54000120 b.eq 604 // b.none 5e4: b9401fa0 ldr w0, [x29, #28] 5e8: 7100001f cmp w0, #0x0 5ec: 54fffeac b.gt 5c0 5f0: 14000005 b 604 5f4: f94013a2 ldr x2, [x29, #32] 5f8: 3940dfa1 ldrb w1, [x29, #55] 5fc: f94017a0 ldr x0, [x29, #40] 600: d63f0040 blr x2 604: b9401fa0 ldr w0, [x29, #28] 608: 51000401 sub w1, w0, #0x1 60c: b9001fa1 str w1, [x29, #28] 610: 7100001f cmp w0, #0x0 614: 54ffff0c b.gt 5f4 618: 14000005 b 62c 61c: f94013a2 ldr x2, [x29, #32] 620: 3940dba1 ldrb w1, [x29, #54] 624: f94017a0 ldr x0, [x29, #40] 628: d63f0040 blr x2 62c: f9400ba0 ldr x0, [x29, #16] 630: 91000401 add x1, x0, #0x1 634: f9000ba1 str x1, [x29, #16] 638: 39400000 ldrb w0, [x0] 63c: 3900dba0 strb w0, [x29, #54] 640: 3940dba0 ldrb w0, [x29, #54] 644: 7100001f cmp w0, #0x0 648: 54fffea1 b.ne 61c // b.any 64c: d503201f nop 650: a8c47bfd ldp x29, x30, [sp], #64 654: d65f03c0 ret 0000000000000658 : 658: a9ba7bfd stp x29, x30, [sp, #-96]! 65c: 910003fd mov x29, sp 660: f9000bf3 str x19, [sp, #16] 664: f9001fa0 str x0, [x29, #56] 668: f9001ba1 str x1, [x29, #48] 66c: f90017a2 str x2, [x29, #40] 670: aa0303f3 mov x19, x3 674: 140000fd b a68 678: 39417fa0 ldrb w0, [x29, #95] 67c: 7100941f cmp w0, #0x25 680: 540000c0 b.eq 698 // b.none 684: f9401ba2 ldr x2, [x29, #48] 688: 39417fa1 ldrb w1, [x29, #95] 68c: f9401fa0 ldr x0, [x29, #56] 690: d63f0040 blr x2 694: 140000f5 b a68 698: 39017bbf strb wzr, [x29, #94] 69c: b9004fbf str wzr, [x29, #76] 6a0: f94017a0 ldr x0, [x29, #40] 6a4: 91000401 add x1, x0, #0x1 6a8: f90017a1 str x1, [x29, #40] 6ac: 39400000 ldrb w0, [x0] 6b0: 39017fa0 strb w0, [x29, #95] 6b4: 39417fa0 ldrb w0, [x29, #95] 6b8: 7100c01f cmp w0, #0x30 6bc: 54000101 b.ne 6dc // b.any 6c0: f94017a0 ldr x0, [x29, #40] 6c4: 91000401 add x1, x0, #0x1 6c8: f90017a1 str x1, [x29, #40] 6cc: 39400000 ldrb w0, [x0] 6d0: 39017fa0 strb w0, [x29, #95] 6d4: 52800020 mov w0, #0x1 // #1 6d8: 39017ba0 strb w0, [x29, #94] 6dc: 39417fa0 ldrb w0, [x29, #95] 6e0: 7100bc1f cmp w0, #0x2f 6e4: 54000189 b.ls 714 // b.plast 6e8: 39417fa0 ldrb w0, [x29, #95] 6ec: 7100e41f cmp w0, #0x39 6f0: 54000128 b.hi 714 // b.pmore 6f4: 910133a1 add x1, x29, #0x4c 6f8: 9100a3a0 add x0, x29, #0x28 6fc: aa0103e3 mov x3, x1 700: 52800142 mov w2, #0xa // #10 704: aa0003e1 mov x1, x0 708: 39417fa0 ldrb w0, [x29, #95] 70c: 97ffff71 bl 4d0 710: 39017fa0 strb w0, [x29, #95] 714: 39417fa0 ldrb w0, [x29, #95] 718: 71018c1f cmp w0, #0x63 71c: 540011c0 b.eq 954 // b.none 720: 71018c1f cmp w0, #0x63 724: 5400010c b.gt 744 728: 7100941f cmp w0, #0x25 72c: 54001940 b.eq a54 // b.none 730: 7101601f cmp w0, #0x58 734: 54000b60 b.eq 8a0 // b.none 738: 7100001f cmp w0, #0x0 73c: 54001a80 b.eq a8c // b.none 740: 140000c9 b a64 744: 7101cc1f cmp w0, #0x73 748: 54001440 b.eq 9d0 // b.none 74c: 7101cc1f cmp w0, #0x73 750: 5400008c b.gt 760 754: 7101901f cmp w0, #0x64 758: 540005c0 b.eq 810 // b.none 75c: 140000c2 b a64 760: 7101d41f cmp w0, #0x75 764: 54000080 b.eq 774 // b.none 768: 7101e01f cmp w0, #0x78 76c: 540009a0 b.eq 8a0 // b.none 770: 140000bd b a64 774: b9401a60 ldr w0, [x19, #24] 778: f9400261 ldr x1, [x19] 77c: 7100001f cmp w0, #0x0 780: 540000eb b.lt 79c // b.tstop 784: aa0103e0 mov x0, x1 788: 91002c00 add x0, x0, #0xb 78c: 927df000 and x0, x0, #0xfffffffffffffff8 790: f9000260 str x0, [x19] 794: aa0103e0 mov x0, x1 798: 1400000f b 7d4 79c: 11002002 add w2, w0, #0x8 7a0: b9001a62 str w2, [x19, #24] 7a4: b9401a62 ldr w2, [x19, #24] 7a8: 7100005f cmp w2, #0x0 7ac: 540000ed b.le 7c8 7b0: aa0103e0 mov x0, x1 7b4: 91002c00 add x0, x0, #0xb 7b8: 927df000 and x0, x0, #0xfffffffffffffff8 7bc: f9000260 str x0, [x19] 7c0: aa0103e0 mov x0, x1 7c4: 14000004 b 7d4 7c8: f9400661 ldr x1, [x19, #8] 7cc: 93407c00 sxtw x0, w0 7d0: 8b000020 add x0, x1, x0 7d4: b9400000 ldr w0, [x0] 7d8: 910143a1 add x1, x29, #0x50 7dc: aa0103e3 mov x3, x1 7e0: 52800002 mov w2, #0x0 // #0 7e4: 52800141 mov w1, #0xa // #10 7e8: 97fffeb9 bl 2cc 7ec: b9404fa0 ldr w0, [x29, #76] 7f0: 910143a1 add x1, x29, #0x50 7f4: aa0103e4 mov x4, x1 7f8: 39417ba3 ldrb w3, [x29, #94] 7fc: 2a0003e2 mov w2, w0 800: f9401ba1 ldr x1, [x29, #48] 804: f9401fa0 ldr x0, [x29, #56] 808: 97ffff5d bl 57c 80c: 14000097 b a68 810: b9401a60 ldr w0, [x19, #24] 814: f9400261 ldr x1, [x19] 818: 7100001f cmp w0, #0x0 81c: 540000eb b.lt 838 // b.tstop 820: aa0103e0 mov x0, x1 824: 91002c00 add x0, x0, #0xb 828: 927df000 and x0, x0, #0xfffffffffffffff8 82c: f9000260 str x0, [x19] 830: aa0103e0 mov x0, x1 834: 1400000f b 870 838: 11002002 add w2, w0, #0x8 83c: b9001a62 str w2, [x19, #24] 840: b9401a62 ldr w2, [x19, #24] 844: 7100005f cmp w2, #0x0 848: 540000ed b.le 864 84c: aa0103e0 mov x0, x1 850: 91002c00 add x0, x0, #0xb 854: 927df000 and x0, x0, #0xfffffffffffffff8 858: f9000260 str x0, [x19] 85c: aa0103e0 mov x0, x1 860: 14000004 b 870 864: f9400661 ldr x1, [x19, #8] 868: 93407c00 sxtw x0, w0 86c: 8b000020 add x0, x1, x0 870: b9400000 ldr w0, [x0] 874: 910143a1 add x1, x29, #0x50 878: 97fffedf bl 3f4 87c: b9404fa0 ldr w0, [x29, #76] 880: 910143a1 add x1, x29, #0x50 884: aa0103e4 mov x4, x1 888: 39417ba3 ldrb w3, [x29, #94] 88c: 2a0003e2 mov w2, w0 890: f9401ba1 ldr x1, [x29, #48] 894: f9401fa0 ldr x0, [x29, #56] 898: 97ffff39 bl 57c 89c: 14000073 b a68 8a0: b9401a60 ldr w0, [x19, #24] 8a4: f9400261 ldr x1, [x19] 8a8: 7100001f cmp w0, #0x0 8ac: 540000eb b.lt 8c8 // b.tstop 8b0: aa0103e0 mov x0, x1 8b4: 91002c00 add x0, x0, #0xb 8b8: 927df000 and x0, x0, #0xfffffffffffffff8 8bc: f9000260 str x0, [x19] 8c0: aa0103e0 mov x0, x1 8c4: 1400000f b 900 8c8: 11002002 add w2, w0, #0x8 8cc: b9001a62 str w2, [x19, #24] 8d0: b9401a62 ldr w2, [x19, #24] 8d4: 7100005f cmp w2, #0x0 8d8: 540000ed b.le 8f4 8dc: aa0103e0 mov x0, x1 8e0: 91002c00 add x0, x0, #0xb 8e4: 927df000 and x0, x0, #0xfffffffffffffff8 8e8: f9000260 str x0, [x19] 8ec: aa0103e0 mov x0, x1 8f0: 14000004 b 900 8f4: f9400661 ldr x1, [x19, #8] 8f8: 93407c00 sxtw x0, w0 8fc: 8b000020 add x0, x1, x0 900: b9400004 ldr w4, [x0] 904: 39417fa0 ldrb w0, [x29, #95] 908: 7101601f cmp w0, #0x58 90c: 1a9f17e0 cset w0, eq // eq = none 910: 12001c00 and w0, w0, #0xff 914: 2a0003e1 mov w1, w0 918: 910143a0 add x0, x29, #0x50 91c: aa0003e3 mov x3, x0 920: 2a0103e2 mov w2, w1 924: 52800201 mov w1, #0x10 // #16 928: 2a0403e0 mov w0, w4 92c: 97fffe68 bl 2cc 930: b9404fa0 ldr w0, [x29, #76] 934: 910143a1 add x1, x29, #0x50 938: aa0103e4 mov x4, x1 93c: 39417ba3 ldrb w3, [x29, #94] 940: 2a0003e2 mov w2, w0 944: f9401ba1 ldr x1, [x29, #48] 948: f9401fa0 ldr x0, [x29, #56] 94c: 97ffff0c bl 57c 950: 14000046 b a68 954: b9401a60 ldr w0, [x19, #24] 958: f9400261 ldr x1, [x19] 95c: 7100001f cmp w0, #0x0 960: 540000eb b.lt 97c // b.tstop 964: aa0103e0 mov x0, x1 968: 91002c00 add x0, x0, #0xb 96c: 927df000 and x0, x0, #0xfffffffffffffff8 970: f9000260 str x0, [x19] 974: aa0103e0 mov x0, x1 978: 1400000f b 9b4 97c: 11002002 add w2, w0, #0x8 980: b9001a62 str w2, [x19, #24] 984: b9401a62 ldr w2, [x19, #24] 988: 7100005f cmp w2, #0x0 98c: 540000ed b.le 9a8 990: aa0103e0 mov x0, x1 994: 91002c00 add x0, x0, #0xb 998: 927df000 and x0, x0, #0xfffffffffffffff8 99c: f9000260 str x0, [x19] 9a0: aa0103e0 mov x0, x1 9a4: 14000004 b 9b4 9a8: f9400661 ldr x1, [x19, #8] 9ac: 93407c00 sxtw x0, w0 9b0: 8b000020 add x0, x1, x0 9b4: b9400000 ldr w0, [x0] 9b8: 12001c00 and w0, w0, #0xff 9bc: f9401ba2 ldr x2, [x29, #48] 9c0: 2a0003e1 mov w1, w0 9c4: f9401fa0 ldr x0, [x29, #56] 9c8: d63f0040 blr x2 9cc: 14000027 b a68 9d0: b9404fa5 ldr w5, [x29, #76] 9d4: b9401a60 ldr w0, [x19, #24] 9d8: f9400261 ldr x1, [x19] 9dc: 7100001f cmp w0, #0x0 9e0: 540000eb b.lt 9fc // b.tstop 9e4: aa0103e0 mov x0, x1 9e8: 91003c00 add x0, x0, #0xf 9ec: 927df000 and x0, x0, #0xfffffffffffffff8 9f0: f9000260 str x0, [x19] 9f4: aa0103e0 mov x0, x1 9f8: 1400000f b a34 9fc: 11002002 add w2, w0, #0x8 a00: b9001a62 str w2, [x19, #24] a04: b9401a62 ldr w2, [x19, #24] a08: 7100005f cmp w2, #0x0 a0c: 540000ed b.le a28 a10: aa0103e0 mov x0, x1 a14: 91003c00 add x0, x0, #0xf a18: 927df000 and x0, x0, #0xfffffffffffffff8 a1c: f9000260 str x0, [x19] a20: aa0103e0 mov x0, x1 a24: 14000004 b a34 a28: f9400661 ldr x1, [x19, #8] a2c: 93407c00 sxtw x0, w0 a30: 8b000020 add x0, x1, x0 a34: f9400000 ldr x0, [x0] a38: aa0003e4 mov x4, x0 a3c: 52800003 mov w3, #0x0 // #0 a40: 2a0503e2 mov w2, w5 a44: f9401ba1 ldr x1, [x29, #48] a48: f9401fa0 ldr x0, [x29, #56] a4c: 97fffecc bl 57c a50: 14000006 b a68 a54: f9401ba2 ldr x2, [x29, #48] a58: 39417fa1 ldrb w1, [x29, #95] a5c: f9401fa0 ldr x0, [x29, #56] a60: d63f0040 blr x2 a64: d503201f nop a68: f94017a0 ldr x0, [x29, #40] a6c: 91000401 add x1, x0, #0x1 a70: f90017a1 str x1, [x29, #40] a74: 39400000 ldrb w0, [x0] a78: 39017fa0 strb w0, [x29, #95] a7c: 39417fa0 ldrb w0, [x29, #95] a80: 7100001f cmp w0, #0x0 a84: 54ffdfa1 b.ne 678 // b.any a88: 14000002 b a90 a8c: d503201f nop a90: d503201f nop a94: f9400bf3 ldr x19, [sp, #16] a98: a8c67bfd ldp x29, x30, [sp], #96 a9c: d65f03c0 ret 0000000000000aa0 : aa0: d10043ff sub sp, sp, #0x10 aa4: f90007e0 str x0, [sp, #8] aa8: f90003e1 str x1, [sp] aac: 90000000 adrp x0, 0 <_start> ab0: 9134c000 add x0, x0, #0xd30 ab4: f94003e1 ldr x1, [sp] ab8: f9000001 str x1, [x0] abc: 90000000 adrp x0, 0 <_start> ac0: 9134e000 add x0, x0, #0xd38 ac4: f94007e1 ldr x1, [sp, #8] ac8: f9000001 str x1, [x0] acc: d503201f nop ad0: 910043ff add sp, sp, #0x10 ad4: d65f03c0 ret 0000000000000ad8 : ad8: a9b67bfd stp x29, x30, [sp, #-160]! adc: 910003fd mov x29, sp ae0: f9001fa0 str x0, [x29, #56] ae4: f90037a1 str x1, [x29, #104] ae8: f9003ba2 str x2, [x29, #112] aec: f9003fa3 str x3, [x29, #120] af0: f90043a4 str x4, [x29, #128] af4: f90047a5 str x5, [x29, #136] af8: f9004ba6 str x6, [x29, #144] afc: f9004fa7 str x7, [x29, #152] b00: 910283a0 add x0, x29, #0xa0 b04: f90023a0 str x0, [x29, #64] b08: 910283a0 add x0, x29, #0xa0 b0c: f90027a0 str x0, [x29, #72] b10: 910183a0 add x0, x29, #0x60 b14: f9002ba0 str x0, [x29, #80] b18: 128006e0 mov w0, #0xffffffc8 // #-56 b1c: b9005ba0 str w0, [x29, #88] b20: b9005fbf str wzr, [x29, #92] b24: 90000000 adrp x0, 0 <_start> b28: 9134e000 add x0, x0, #0xd38 b2c: f9400004 ldr x4, [x0] b30: 90000000 adrp x0, 0 <_start> b34: 9134c000 add x0, x0, #0xd30 b38: f9400005 ldr x5, [x0] b3c: 910043a2 add x2, x29, #0x10 b40: 910103a3 add x3, x29, #0x40 b44: a9400460 ldp x0, x1, [x3] b48: a9000440 stp x0, x1, [x2] b4c: a9410460 ldp x0, x1, [x3, #16] b50: a9010440 stp x0, x1, [x2, #16] b54: 910043a0 add x0, x29, #0x10 b58: aa0003e3 mov x3, x0 b5c: f9401fa2 ldr x2, [x29, #56] b60: aa0503e1 mov x1, x5 b64: aa0403e0 mov x0, x4 b68: 97fffebc bl 658 b6c: d503201f nop b70: a8ca7bfd ldp x29, x30, [sp], #160 b74: d65f03c0 ret 0000000000000b78 : b78: d10043ff sub sp, sp, #0x10 b7c: f90007e0 str x0, [sp, #8] b80: 39001fe1 strb w1, [sp, #7] b84: f94007e0 ldr x0, [sp, #8] b88: f9400000 ldr x0, [x0] b8c: 91000402 add x2, x0, #0x1 b90: f94007e1 ldr x1, [sp, #8] b94: f9000022 str x2, [x1] b98: 39401fe1 ldrb w1, [sp, #7] b9c: 39000001 strb w1, [x0] ba0: d503201f nop ba4: 910043ff add sp, sp, #0x10 ba8: d65f03c0 ret 0000000000000bac : bac: a9b77bfd stp x29, x30, [sp, #-144]! bb0: 910003fd mov x29, sp bb4: f9001fa0 str x0, [x29, #56] bb8: f9001ba1 str x1, [x29, #48] bbc: f90033a2 str x2, [x29, #96] bc0: f90037a3 str x3, [x29, #104] bc4: f9003ba4 str x4, [x29, #112] bc8: f9003fa5 str x5, [x29, #120] bcc: f90043a6 str x6, [x29, #128] bd0: f90047a7 str x7, [x29, #136] bd4: 910243a0 add x0, x29, #0x90 bd8: f90023a0 str x0, [x29, #64] bdc: 910243a0 add x0, x29, #0x90 be0: f90027a0 str x0, [x29, #72] be4: 910183a0 add x0, x29, #0x60 be8: f9002ba0 str x0, [x29, #80] bec: 128005e0 mov w0, #0xffffffd0 // #-48 bf0: b9005ba0 str w0, [x29, #88] bf4: b9005fbf str wzr, [x29, #92] bf8: 910043a2 add x2, x29, #0x10 bfc: 910103a3 add x3, x29, #0x40 c00: a9400460 ldp x0, x1, [x3] c04: a9000440 stp x0, x1, [x2] c08: a9410460 ldp x0, x1, [x3, #16] c0c: a9010440 stp x0, x1, [x2, #16] c10: 910043a2 add x2, x29, #0x10 c14: 90000000 adrp x0, 0 <_start> c18: 912de001 add x1, x0, #0xb78 c1c: 9100e3a0 add x0, x29, #0x38 c20: aa0203e3 mov x3, x2 c24: f9401ba2 ldr x2, [x29, #48] c28: 97fffe8c bl 658 c2c: 9100e3a0 add x0, x29, #0x38 c30: 52800001 mov w1, #0x0 // #0 c34: 97ffffd1 bl b78 c38: d503201f nop c3c: a8c97bfd ldp x29, x30, [sp], #144 c40: d65f03c0 ret 0000000000000c44 : c44: a9be7bfd stp x29, x30, [sp, #-32]! c48: 910003fd mov x29, sp c4c: 97fffd52 bl 194 c50: 90000000 adrp x0, 0 <_start> c54: f9468800 ldr x0, [x0, #3344] c58: aa0003e1 mov x1, x0 c5c: d2800000 mov x0, #0x0 // #0 c60: 97ffff90 bl aa0 c64: 94000015 bl cb8 c68: b9001fa0 str w0, [x29, #28] c6c: 90000000 adrp x0, 0 <_start> c70: 9133c000 add x0, x0, #0xcf0 c74: b9401fa1 ldr w1, [x29, #28] c78: 97ffff98 bl ad8 c7c: d503201f nop c80: a8c27bfd ldp x29, x30, [sp], #32 c84: d65f03c0 ret 0000000000000c88 : c88: a9be7bfd stp x29, x30, [sp, #-32]! c8c: 910003fd mov x29, sp c90: 9400000a bl cb8 c94: b9001fa0 str w0, [x29, #28] c98: 90000000 adrp x0, 0 <_start> c9c: 9133c000 add x0, x0, #0xcf0 ca0: b9401fa1 ldr w1, [x29, #28] ca4: 97ffff8d bl ad8 ca8: 97fffd15 bl fc cac: 12001c00 and w0, w0, #0xff cb0: 97fffd00 bl b0 cb4: 17fffffd b ca8 0000000000000cb8 : cb8: d5384240 mrs x0, currentel cbc: d342fc00 lsr x0, x0, #2 cc0: d65f03c0 ret 0000000000000cc4 : cc4: b9000001 str w1, [x0] cc8: d65f03c0 ret 0000000000000ccc : ccc: b9400000 ldr w0, [x0] cd0: d65f03c0 ret 0000000000000cd4 : cd4: f1000400 subs x0, x0, #0x1 cd8: 54ffffe1 b.ne cd4 // b.any cdc: d65f03c0 ret 0000000000000ce0 : ce0: f800841f str xzr, [x0], #8 ce4: f1002021 subs x1, x1, #0x8 ce8: 54ffffcc b.gt ce0 cec: d65f03c0 ret ================================================ FILE: exercises/lesson02/2/zjd0112/test2.txt ================================================ kernel8.elf: file format elf64-littleaarch64 Disassembly of section .text.boot: 0000000000000000 <_start>: 0: d53800a0 mrs x0, mpidr_el1 4: 92401c00 and x0, x0, #0xff 8: b4000060 cbz x0, 14 c: 14000001 b 10 0000000000000010 : 10: 14000000 b 10 0000000000000014 : 14: 58000360 ldr x0, 80 18: d51c1000 msr sctlr_el2, x0 1c: 58000360 ldr x0, 88 20: d51c1100 msr hcr_el2, x0 24: 58000360 ldr x0, 90 28: d51e1100 msr scr_el3, x0 2c: 58000360 ldr x0, 98 30: d51e4000 msr spsr_el3, x0 34: 10000060 adr x0, 40 38: d51e4020 msr elr_el3, x0 3c: d69f03e0 eret 0000000000000040 : 40: 100069c0 adr x0, d78 44: 10006a21 adr x1, d88 48: cb000021 sub x1, x1, x0 4c: 94000337 bl d28 50: b26a03ff mov sp, #0x400000 // #4194304 54: 9400030e bl c8c 58: 58000240 ldr x0, a0 5c: d5181000 msr sctlr_el1, x0 60: 58000240 ldr x0, a8 64: d51c4000 msr spsr_el2, x0 68: 10000060 adr x0, 74 6c: d51c4020 msr elr_el2, x0 70: d69f03e0 eret 0000000000000074 : 74: b26a03ff mov sp, #0x400000 // #4194304 78: 94000316 bl cd0 7c: 17ffffe5 b 10 80: 30c50810 .word 0x30c50810 84: 00000000 .word 0x00000000 88: 80000000 .word 0x80000000 8c: 00000000 .word 0x00000000 90: 00000431 .word 0x00000431 94: 00000000 .word 0x00000000 98: 000001c9 .word 0x000001c9 9c: 00000000 .word 0x00000000 a0: 30d00800 .word 0x30d00800 a4: 00000000 .word 0x00000000 a8: 000001c5 .word 0x000001c5 ac: 00000000 .word 0x00000000 Disassembly of section .text: 00000000000000b0 : b0: a9be7bfd stp x29, x30, [sp, #-32]! b4: 910003fd mov x29, sp b8: 39007fa0 strb w0, [x29, #31] bc: d28a0a80 mov x0, #0x5054 // #20564 c0: f2a7e420 movk x0, #0x3f21, lsl #16 c4: 94000314 bl d14 c8: 121b0000 and w0, w0, #0x20 cc: 7100001f cmp w0, #0x0 d0: 54000041 b.ne d8 // b.any d4: 17fffffa b bc d8: d503201f nop dc: 39407fa0 ldrb w0, [x29, #31] e0: 2a0003e1 mov w1, w0 e4: d28a0800 mov x0, #0x5040 // #20544 e8: f2a7e420 movk x0, #0x3f21, lsl #16 ec: 94000308 bl d0c f0: d503201f nop f4: a8c27bfd ldp x29, x30, [sp], #32 f8: d65f03c0 ret 00000000000000fc : fc: a9bf7bfd stp x29, x30, [sp, #-16]! 100: 910003fd mov x29, sp 104: d28a0a80 mov x0, #0x5054 // #20564 108: f2a7e420 movk x0, #0x3f21, lsl #16 10c: 94000302 bl d14 110: 12000000 and w0, w0, #0x1 114: 7100001f cmp w0, #0x0 118: 54000041 b.ne 120 // b.any 11c: 17fffffa b 104 120: d503201f nop 124: d28a0800 mov x0, #0x5040 // #20544 128: f2a7e420 movk x0, #0x3f21, lsl #16 12c: 940002fa bl d14 130: 12001c00 and w0, w0, #0xff 134: a8c17bfd ldp x29, x30, [sp], #16 138: d65f03c0 ret 000000000000013c : 13c: a9bd7bfd stp x29, x30, [sp, #-48]! 140: 910003fd mov x29, sp 144: f9000fa0 str x0, [x29, #24] 148: b9002fbf str wzr, [x29, #44] 14c: 14000009 b 170 150: b9802fa0 ldrsw x0, [x29, #44] 154: f9400fa1 ldr x1, [x29, #24] 158: 8b000020 add x0, x1, x0 15c: 39400000 ldrb w0, [x0] 160: 97ffffd4 bl b0 164: b9402fa0 ldr w0, [x29, #44] 168: 11000400 add w0, w0, #0x1 16c: b9002fa0 str w0, [x29, #44] 170: b9802fa0 ldrsw x0, [x29, #44] 174: f9400fa1 ldr x1, [x29, #24] 178: 8b000020 add x0, x1, x0 17c: 39400000 ldrb w0, [x0] 180: 7100001f cmp w0, #0x0 184: 54fffe61 b.ne 150 // b.any 188: d503201f nop 18c: a8c37bfd ldp x29, x30, [sp], #48 190: d65f03c0 ret 0000000000000194 : 194: a9be7bfd stp x29, x30, [sp, #-32]! 198: 910003fd mov x29, sp 19c: d2800080 mov x0, #0x4 // #4 1a0: f2a7e400 movk x0, #0x3f20, lsl #16 1a4: 940002dc bl d14 1a8: b9001fa0 str w0, [x29, #28] 1ac: b9401fa0 ldr w0, [x29, #28] 1b0: 12117000 and w0, w0, #0xffff8fff 1b4: b9001fa0 str w0, [x29, #28] 1b8: b9401fa0 ldr w0, [x29, #28] 1bc: 32130000 orr w0, w0, #0x2000 1c0: b9001fa0 str w0, [x29, #28] 1c4: b9401fa0 ldr w0, [x29, #28] 1c8: 120e7000 and w0, w0, #0xfffc7fff 1cc: b9001fa0 str w0, [x29, #28] 1d0: b9401fa0 ldr w0, [x29, #28] 1d4: 32100000 orr w0, w0, #0x10000 1d8: b9001fa0 str w0, [x29, #28] 1dc: b9401fa1 ldr w1, [x29, #28] 1e0: d2800080 mov x0, #0x4 // #4 1e4: f2a7e400 movk x0, #0x3f20, lsl #16 1e8: 940002c9 bl d0c 1ec: 52800001 mov w1, #0x0 // #0 1f0: d2801280 mov x0, #0x94 // #148 1f4: f2a7e400 movk x0, #0x3f20, lsl #16 1f8: 940002c5 bl d0c 1fc: d28012c0 mov x0, #0x96 // #150 200: 940002c7 bl d1c 204: 52980001 mov w1, #0xc000 // #49152 208: d2801300 mov x0, #0x98 // #152 20c: f2a7e400 movk x0, #0x3f20, lsl #16 210: 940002bf bl d0c 214: d28012c0 mov x0, #0x96 // #150 218: 940002c1 bl d1c 21c: 52800001 mov w1, #0x0 // #0 220: d2801300 mov x0, #0x98 // #152 224: f2a7e400 movk x0, #0x3f20, lsl #16 228: 940002b9 bl d0c 22c: 52800021 mov w1, #0x1 // #1 230: d28a0080 mov x0, #0x5004 // #20484 234: f2a7e420 movk x0, #0x3f21, lsl #16 238: 940002b5 bl d0c 23c: 52800001 mov w1, #0x0 // #0 240: d28a0c00 mov x0, #0x5060 // #20576 244: f2a7e420 movk x0, #0x3f21, lsl #16 248: 940002b1 bl d0c 24c: 52800001 mov w1, #0x0 // #0 250: d28a0880 mov x0, #0x5044 // #20548 254: f2a7e420 movk x0, #0x3f21, lsl #16 258: 940002ad bl d0c 25c: 52800061 mov w1, #0x3 // #3 260: d28a0980 mov x0, #0x504c // #20556 264: f2a7e420 movk x0, #0x3f21, lsl #16 268: 940002a9 bl d0c 26c: 52800001 mov w1, #0x0 // #0 270: d28a0a00 mov x0, #0x5050 // #20560 274: f2a7e420 movk x0, #0x3f21, lsl #16 278: 940002a5 bl d0c 27c: 528021c1 mov w1, #0x10e // #270 280: d28a0d00 mov x0, #0x5068 // #20584 284: f2a7e420 movk x0, #0x3f21, lsl #16 288: 940002a1 bl d0c 28c: 52800061 mov w1, #0x3 // #3 290: d28a0c00 mov x0, #0x5060 // #20576 294: f2a7e420 movk x0, #0x3f21, lsl #16 298: 9400029d bl d0c 29c: d503201f nop 2a0: a8c27bfd ldp x29, x30, [sp], #32 2a4: d65f03c0 ret 00000000000002a8 : 2a8: a9be7bfd stp x29, x30, [sp, #-32]! 2ac: 910003fd mov x29, sp 2b0: f9000fa0 str x0, [x29, #24] 2b4: 39005fa1 strb w1, [x29, #23] 2b8: 39405fa0 ldrb w0, [x29, #23] 2bc: 97ffff7d bl b0 2c0: d503201f nop 2c4: a8c27bfd ldp x29, x30, [sp], #32 2c8: d65f03c0 ret 00000000000002cc : 2cc: d100c3ff sub sp, sp, #0x30 2d0: b9001fe0 str w0, [sp, #28] 2d4: b9001be1 str w1, [sp, #24] 2d8: b90017e2 str w2, [sp, #20] 2dc: f90007e3 str x3, [sp, #8] 2e0: b9002fff str wzr, [sp, #44] 2e4: 52800020 mov w0, #0x1 // #1 2e8: b9002be0 str w0, [sp, #40] 2ec: 14000005 b 300 2f0: b9402be1 ldr w1, [sp, #40] 2f4: b9401be0 ldr w0, [sp, #24] 2f8: 1b007c20 mul w0, w1, w0 2fc: b9002be0 str w0, [sp, #40] 300: b9401fe1 ldr w1, [sp, #28] 304: b9402be0 ldr w0, [sp, #40] 308: 1ac00820 udiv w0, w1, w0 30c: b9401be1 ldr w1, [sp, #24] 310: 6b00003f cmp w1, w0 314: 54fffee9 b.ls 2f0 // b.plast 318: 1400002f b 3d4 31c: b9401fe1 ldr w1, [sp, #28] 320: b9402be0 ldr w0, [sp, #40] 324: 1ac00820 udiv w0, w1, w0 328: b90027e0 str w0, [sp, #36] 32c: b9401fe0 ldr w0, [sp, #28] 330: b9402be1 ldr w1, [sp, #40] 334: 1ac10802 udiv w2, w0, w1 338: b9402be1 ldr w1, [sp, #40] 33c: 1b017c41 mul w1, w2, w1 340: 4b010000 sub w0, w0, w1 344: b9001fe0 str w0, [sp, #28] 348: b9402be1 ldr w1, [sp, #40] 34c: b9401be0 ldr w0, [sp, #24] 350: 1ac00820 udiv w0, w1, w0 354: b9002be0 str w0, [sp, #40] 358: b9402fe0 ldr w0, [sp, #44] 35c: 7100001f cmp w0, #0x0 360: 540000e1 b.ne 37c // b.any 364: b94027e0 ldr w0, [sp, #36] 368: 7100001f cmp w0, #0x0 36c: 5400008c b.gt 37c 370: b9402be0 ldr w0, [sp, #40] 374: 7100001f cmp w0, #0x0 378: 540002e1 b.ne 3d4 // b.any 37c: b94027e0 ldr w0, [sp, #36] 380: 7100241f cmp w0, #0x9 384: 5400010d b.le 3a4 388: b94017e0 ldr w0, [sp, #20] 38c: 7100001f cmp w0, #0x0 390: 54000060 b.eq 39c // b.none 394: 528006e0 mov w0, #0x37 // #55 398: 14000004 b 3a8 39c: 52800ae0 mov w0, #0x57 // #87 3a0: 14000002 b 3a8 3a4: 52800600 mov w0, #0x30 // #48 3a8: b94027e1 ldr w1, [sp, #36] 3ac: 12001c22 and w2, w1, #0xff 3b0: f94007e1 ldr x1, [sp, #8] 3b4: 91000423 add x3, x1, #0x1 3b8: f90007e3 str x3, [sp, #8] 3bc: 0b020000 add w0, w0, w2 3c0: 12001c00 and w0, w0, #0xff 3c4: 39000020 strb w0, [x1] 3c8: b9402fe0 ldr w0, [sp, #44] 3cc: 11000400 add w0, w0, #0x1 3d0: b9002fe0 str w0, [sp, #44] 3d4: b9402be0 ldr w0, [sp, #40] 3d8: 7100001f cmp w0, #0x0 3dc: 54fffa01 b.ne 31c // b.any 3e0: f94007e0 ldr x0, [sp, #8] 3e4: 3900001f strb wzr, [x0] 3e8: d503201f nop 3ec: 9100c3ff add sp, sp, #0x30 3f0: d65f03c0 ret 00000000000003f4 : 3f4: a9be7bfd stp x29, x30, [sp, #-32]! 3f8: 910003fd mov x29, sp 3fc: b9001fa0 str w0, [x29, #28] 400: f9000ba1 str x1, [x29, #16] 404: b9401fa0 ldr w0, [x29, #28] 408: 7100001f cmp w0, #0x0 40c: 5400012a b.ge 430 // b.tcont 410: b9401fa0 ldr w0, [x29, #28] 414: 4b0003e0 neg w0, w0 418: b9001fa0 str w0, [x29, #28] 41c: f9400ba0 ldr x0, [x29, #16] 420: 91000401 add x1, x0, #0x1 424: f9000ba1 str x1, [x29, #16] 428: 528005a1 mov w1, #0x2d // #45 42c: 39000001 strb w1, [x0] 430: b9401fa0 ldr w0, [x29, #28] 434: f9400ba3 ldr x3, [x29, #16] 438: 52800002 mov w2, #0x0 // #0 43c: 52800141 mov w1, #0xa // #10 440: 97ffffa3 bl 2cc 444: d503201f nop 448: a8c27bfd ldp x29, x30, [sp], #32 44c: d65f03c0 ret 0000000000000450 : 450: d10043ff sub sp, sp, #0x10 454: 39003fe0 strb w0, [sp, #15] 458: 39403fe0 ldrb w0, [sp, #15] 45c: 7100bc1f cmp w0, #0x2f 460: 540000e9 b.ls 47c // b.plast 464: 39403fe0 ldrb w0, [sp, #15] 468: 7100e41f cmp w0, #0x39 46c: 54000088 b.hi 47c // b.pmore 470: 39403fe0 ldrb w0, [sp, #15] 474: 5100c000 sub w0, w0, #0x30 478: 14000014 b 4c8 47c: 39403fe0 ldrb w0, [sp, #15] 480: 7101801f cmp w0, #0x60 484: 540000e9 b.ls 4a0 // b.plast 488: 39403fe0 ldrb w0, [sp, #15] 48c: 7101981f cmp w0, #0x66 490: 54000088 b.hi 4a0 // b.pmore 494: 39403fe0 ldrb w0, [sp, #15] 498: 51015c00 sub w0, w0, #0x57 49c: 1400000b b 4c8 4a0: 39403fe0 ldrb w0, [sp, #15] 4a4: 7101001f cmp w0, #0x40 4a8: 540000e9 b.ls 4c4 // b.plast 4ac: 39403fe0 ldrb w0, [sp, #15] 4b0: 7101181f cmp w0, #0x46 4b4: 54000088 b.hi 4c4 // b.pmore 4b8: 39403fe0 ldrb w0, [sp, #15] 4bc: 5100dc00 sub w0, w0, #0x37 4c0: 14000002 b 4c8 4c4: 12800000 mov w0, #0xffffffff // #-1 4c8: 910043ff add sp, sp, #0x10 4cc: d65f03c0 ret 00000000000004d0 : 4d0: a9bc7bfd stp x29, x30, [sp, #-64]! 4d4: 910003fd mov x29, sp 4d8: 3900bfa0 strb w0, [x29, #47] 4dc: f90013a1 str x1, [x29, #32] 4e0: b9002ba2 str w2, [x29, #40] 4e4: f9000fa3 str x3, [x29, #24] 4e8: f94013a0 ldr x0, [x29, #32] 4ec: f9400000 ldr x0, [x0] 4f0: f9001fa0 str x0, [x29, #56] 4f4: b90037bf str wzr, [x29, #52] 4f8: 14000010 b 538 4fc: b94033a1 ldr w1, [x29, #48] 500: b9402ba0 ldr w0, [x29, #40] 504: 6b00003f cmp w1, w0 508: 5400026c b.gt 554 50c: b94037a1 ldr w1, [x29, #52] 510: b9402ba0 ldr w0, [x29, #40] 514: 1b007c20 mul w0, w1, w0 518: b94033a1 ldr w1, [x29, #48] 51c: 0b000020 add w0, w1, w0 520: b90037a0 str w0, [x29, #52] 524: f9401fa0 ldr x0, [x29, #56] 528: 91000401 add x1, x0, #0x1 52c: f9001fa1 str x1, [x29, #56] 530: 39400000 ldrb w0, [x0] 534: 3900bfa0 strb w0, [x29, #47] 538: 3940bfa0 ldrb w0, [x29, #47] 53c: 97ffffc5 bl 450 540: b90033a0 str w0, [x29, #48] 544: b94033a0 ldr w0, [x29, #48] 548: 7100001f cmp w0, #0x0 54c: 54fffd8a b.ge 4fc // b.tcont 550: 14000002 b 558 554: d503201f nop 558: f94013a0 ldr x0, [x29, #32] 55c: f9401fa1 ldr x1, [x29, #56] 560: f9000001 str x1, [x0] 564: f9400fa0 ldr x0, [x29, #24] 568: b94037a1 ldr w1, [x29, #52] 56c: b9000001 str w1, [x0] 570: 3940bfa0 ldrb w0, [x29, #47] 574: a8c47bfd ldp x29, x30, [sp], #64 578: d65f03c0 ret 000000000000057c : 57c: a9bc7bfd stp x29, x30, [sp, #-64]! 580: 910003fd mov x29, sp 584: f90017a0 str x0, [x29, #40] 588: f90013a1 str x1, [x29, #32] 58c: b9001fa2 str w2, [x29, #28] 590: 39006fa3 strb w3, [x29, #27] 594: f9000ba4 str x4, [x29, #16] 598: 39406fa0 ldrb w0, [x29, #27] 59c: 7100001f cmp w0, #0x0 5a0: 54000060 b.eq 5ac // b.none 5a4: 52800600 mov w0, #0x30 // #48 5a8: 14000002 b 5b0 5ac: 52800400 mov w0, #0x20 // #32 5b0: 3900dfa0 strb w0, [x29, #55] 5b4: f9400ba0 ldr x0, [x29, #16] 5b8: f9001fa0 str x0, [x29, #56] 5bc: 14000004 b 5cc 5c0: b9401fa0 ldr w0, [x29, #28] 5c4: 51000400 sub w0, w0, #0x1 5c8: b9001fa0 str w0, [x29, #28] 5cc: f9401fa0 ldr x0, [x29, #56] 5d0: 91000401 add x1, x0, #0x1 5d4: f9001fa1 str x1, [x29, #56] 5d8: 39400000 ldrb w0, [x0] 5dc: 7100001f cmp w0, #0x0 5e0: 54000120 b.eq 604 // b.none 5e4: b9401fa0 ldr w0, [x29, #28] 5e8: 7100001f cmp w0, #0x0 5ec: 54fffeac b.gt 5c0 5f0: 14000005 b 604 5f4: f94013a2 ldr x2, [x29, #32] 5f8: 3940dfa1 ldrb w1, [x29, #55] 5fc: f94017a0 ldr x0, [x29, #40] 600: d63f0040 blr x2 604: b9401fa0 ldr w0, [x29, #28] 608: 51000401 sub w1, w0, #0x1 60c: b9001fa1 str w1, [x29, #28] 610: 7100001f cmp w0, #0x0 614: 54ffff0c b.gt 5f4 618: 14000005 b 62c 61c: f94013a2 ldr x2, [x29, #32] 620: 3940dba1 ldrb w1, [x29, #54] 624: f94017a0 ldr x0, [x29, #40] 628: d63f0040 blr x2 62c: f9400ba0 ldr x0, [x29, #16] 630: 91000401 add x1, x0, #0x1 634: f9000ba1 str x1, [x29, #16] 638: 39400000 ldrb w0, [x0] 63c: 3900dba0 strb w0, [x29, #54] 640: 3940dba0 ldrb w0, [x29, #54] 644: 7100001f cmp w0, #0x0 648: 54fffea1 b.ne 61c // b.any 64c: d503201f nop 650: a8c47bfd ldp x29, x30, [sp], #64 654: d65f03c0 ret 0000000000000658 : 658: a9ba7bfd stp x29, x30, [sp, #-96]! 65c: 910003fd mov x29, sp 660: f9000bf3 str x19, [sp, #16] 664: f9001fa0 str x0, [x29, #56] 668: f9001ba1 str x1, [x29, #48] 66c: f90017a2 str x2, [x29, #40] 670: aa0303f3 mov x19, x3 674: 140000fd b a68 678: 39417fa0 ldrb w0, [x29, #95] 67c: 7100941f cmp w0, #0x25 680: 540000c0 b.eq 698 // b.none 684: f9401ba2 ldr x2, [x29, #48] 688: 39417fa1 ldrb w1, [x29, #95] 68c: f9401fa0 ldr x0, [x29, #56] 690: d63f0040 blr x2 694: 140000f5 b a68 698: 39017bbf strb wzr, [x29, #94] 69c: b9004fbf str wzr, [x29, #76] 6a0: f94017a0 ldr x0, [x29, #40] 6a4: 91000401 add x1, x0, #0x1 6a8: f90017a1 str x1, [x29, #40] 6ac: 39400000 ldrb w0, [x0] 6b0: 39017fa0 strb w0, [x29, #95] 6b4: 39417fa0 ldrb w0, [x29, #95] 6b8: 7100c01f cmp w0, #0x30 6bc: 54000101 b.ne 6dc // b.any 6c0: f94017a0 ldr x0, [x29, #40] 6c4: 91000401 add x1, x0, #0x1 6c8: f90017a1 str x1, [x29, #40] 6cc: 39400000 ldrb w0, [x0] 6d0: 39017fa0 strb w0, [x29, #95] 6d4: 52800020 mov w0, #0x1 // #1 6d8: 39017ba0 strb w0, [x29, #94] 6dc: 39417fa0 ldrb w0, [x29, #95] 6e0: 7100bc1f cmp w0, #0x2f 6e4: 54000189 b.ls 714 // b.plast 6e8: 39417fa0 ldrb w0, [x29, #95] 6ec: 7100e41f cmp w0, #0x39 6f0: 54000128 b.hi 714 // b.pmore 6f4: 910133a1 add x1, x29, #0x4c 6f8: 9100a3a0 add x0, x29, #0x28 6fc: aa0103e3 mov x3, x1 700: 52800142 mov w2, #0xa // #10 704: aa0003e1 mov x1, x0 708: 39417fa0 ldrb w0, [x29, #95] 70c: 97ffff71 bl 4d0 710: 39017fa0 strb w0, [x29, #95] 714: 39417fa0 ldrb w0, [x29, #95] 718: 71018c1f cmp w0, #0x63 71c: 540011c0 b.eq 954 // b.none 720: 71018c1f cmp w0, #0x63 724: 5400010c b.gt 744 728: 7100941f cmp w0, #0x25 72c: 54001940 b.eq a54 // b.none 730: 7101601f cmp w0, #0x58 734: 54000b60 b.eq 8a0 // b.none 738: 7100001f cmp w0, #0x0 73c: 54001a80 b.eq a8c // b.none 740: 140000c9 b a64 744: 7101cc1f cmp w0, #0x73 748: 54001440 b.eq 9d0 // b.none 74c: 7101cc1f cmp w0, #0x73 750: 5400008c b.gt 760 754: 7101901f cmp w0, #0x64 758: 540005c0 b.eq 810 // b.none 75c: 140000c2 b a64 760: 7101d41f cmp w0, #0x75 764: 54000080 b.eq 774 // b.none 768: 7101e01f cmp w0, #0x78 76c: 540009a0 b.eq 8a0 // b.none 770: 140000bd b a64 774: b9401a60 ldr w0, [x19, #24] 778: f9400261 ldr x1, [x19] 77c: 7100001f cmp w0, #0x0 780: 540000eb b.lt 79c // b.tstop 784: aa0103e0 mov x0, x1 788: 91002c00 add x0, x0, #0xb 78c: 927df000 and x0, x0, #0xfffffffffffffff8 790: f9000260 str x0, [x19] 794: aa0103e0 mov x0, x1 798: 1400000f b 7d4 79c: 11002002 add w2, w0, #0x8 7a0: b9001a62 str w2, [x19, #24] 7a4: b9401a62 ldr w2, [x19, #24] 7a8: 7100005f cmp w2, #0x0 7ac: 540000ed b.le 7c8 7b0: aa0103e0 mov x0, x1 7b4: 91002c00 add x0, x0, #0xb 7b8: 927df000 and x0, x0, #0xfffffffffffffff8 7bc: f9000260 str x0, [x19] 7c0: aa0103e0 mov x0, x1 7c4: 14000004 b 7d4 7c8: f9400661 ldr x1, [x19, #8] 7cc: 93407c00 sxtw x0, w0 7d0: 8b000020 add x0, x1, x0 7d4: b9400000 ldr w0, [x0] 7d8: 910143a1 add x1, x29, #0x50 7dc: aa0103e3 mov x3, x1 7e0: 52800002 mov w2, #0x0 // #0 7e4: 52800141 mov w1, #0xa // #10 7e8: 97fffeb9 bl 2cc 7ec: b9404fa0 ldr w0, [x29, #76] 7f0: 910143a1 add x1, x29, #0x50 7f4: aa0103e4 mov x4, x1 7f8: 39417ba3 ldrb w3, [x29, #94] 7fc: 2a0003e2 mov w2, w0 800: f9401ba1 ldr x1, [x29, #48] 804: f9401fa0 ldr x0, [x29, #56] 808: 97ffff5d bl 57c 80c: 14000097 b a68 810: b9401a60 ldr w0, [x19, #24] 814: f9400261 ldr x1, [x19] 818: 7100001f cmp w0, #0x0 81c: 540000eb b.lt 838 // b.tstop 820: aa0103e0 mov x0, x1 824: 91002c00 add x0, x0, #0xb 828: 927df000 and x0, x0, #0xfffffffffffffff8 82c: f9000260 str x0, [x19] 830: aa0103e0 mov x0, x1 834: 1400000f b 870 838: 11002002 add w2, w0, #0x8 83c: b9001a62 str w2, [x19, #24] 840: b9401a62 ldr w2, [x19, #24] 844: 7100005f cmp w2, #0x0 848: 540000ed b.le 864 84c: aa0103e0 mov x0, x1 850: 91002c00 add x0, x0, #0xb 854: 927df000 and x0, x0, #0xfffffffffffffff8 858: f9000260 str x0, [x19] 85c: aa0103e0 mov x0, x1 860: 14000004 b 870 864: f9400661 ldr x1, [x19, #8] 868: 93407c00 sxtw x0, w0 86c: 8b000020 add x0, x1, x0 870: b9400000 ldr w0, [x0] 874: 910143a1 add x1, x29, #0x50 878: 97fffedf bl 3f4 87c: b9404fa0 ldr w0, [x29, #76] 880: 910143a1 add x1, x29, #0x50 884: aa0103e4 mov x4, x1 888: 39417ba3 ldrb w3, [x29, #94] 88c: 2a0003e2 mov w2, w0 890: f9401ba1 ldr x1, [x29, #48] 894: f9401fa0 ldr x0, [x29, #56] 898: 97ffff39 bl 57c 89c: 14000073 b a68 8a0: b9401a60 ldr w0, [x19, #24] 8a4: f9400261 ldr x1, [x19] 8a8: 7100001f cmp w0, #0x0 8ac: 540000eb b.lt 8c8 // b.tstop 8b0: aa0103e0 mov x0, x1 8b4: 91002c00 add x0, x0, #0xb 8b8: 927df000 and x0, x0, #0xfffffffffffffff8 8bc: f9000260 str x0, [x19] 8c0: aa0103e0 mov x0, x1 8c4: 1400000f b 900 8c8: 11002002 add w2, w0, #0x8 8cc: b9001a62 str w2, [x19, #24] 8d0: b9401a62 ldr w2, [x19, #24] 8d4: 7100005f cmp w2, #0x0 8d8: 540000ed b.le 8f4 8dc: aa0103e0 mov x0, x1 8e0: 91002c00 add x0, x0, #0xb 8e4: 927df000 and x0, x0, #0xfffffffffffffff8 8e8: f9000260 str x0, [x19] 8ec: aa0103e0 mov x0, x1 8f0: 14000004 b 900 8f4: f9400661 ldr x1, [x19, #8] 8f8: 93407c00 sxtw x0, w0 8fc: 8b000020 add x0, x1, x0 900: b9400004 ldr w4, [x0] 904: 39417fa0 ldrb w0, [x29, #95] 908: 7101601f cmp w0, #0x58 90c: 1a9f17e0 cset w0, eq // eq = none 910: 12001c00 and w0, w0, #0xff 914: 2a0003e1 mov w1, w0 918: 910143a0 add x0, x29, #0x50 91c: aa0003e3 mov x3, x0 920: 2a0103e2 mov w2, w1 924: 52800201 mov w1, #0x10 // #16 928: 2a0403e0 mov w0, w4 92c: 97fffe68 bl 2cc 930: b9404fa0 ldr w0, [x29, #76] 934: 910143a1 add x1, x29, #0x50 938: aa0103e4 mov x4, x1 93c: 39417ba3 ldrb w3, [x29, #94] 940: 2a0003e2 mov w2, w0 944: f9401ba1 ldr x1, [x29, #48] 948: f9401fa0 ldr x0, [x29, #56] 94c: 97ffff0c bl 57c 950: 14000046 b a68 954: b9401a60 ldr w0, [x19, #24] 958: f9400261 ldr x1, [x19] 95c: 7100001f cmp w0, #0x0 960: 540000eb b.lt 97c // b.tstop 964: aa0103e0 mov x0, x1 968: 91002c00 add x0, x0, #0xb 96c: 927df000 and x0, x0, #0xfffffffffffffff8 970: f9000260 str x0, [x19] 974: aa0103e0 mov x0, x1 978: 1400000f b 9b4 97c: 11002002 add w2, w0, #0x8 980: b9001a62 str w2, [x19, #24] 984: b9401a62 ldr w2, [x19, #24] 988: 7100005f cmp w2, #0x0 98c: 540000ed b.le 9a8 990: aa0103e0 mov x0, x1 994: 91002c00 add x0, x0, #0xb 998: 927df000 and x0, x0, #0xfffffffffffffff8 99c: f9000260 str x0, [x19] 9a0: aa0103e0 mov x0, x1 9a4: 14000004 b 9b4 9a8: f9400661 ldr x1, [x19, #8] 9ac: 93407c00 sxtw x0, w0 9b0: 8b000020 add x0, x1, x0 9b4: b9400000 ldr w0, [x0] 9b8: 12001c00 and w0, w0, #0xff 9bc: f9401ba2 ldr x2, [x29, #48] 9c0: 2a0003e1 mov w1, w0 9c4: f9401fa0 ldr x0, [x29, #56] 9c8: d63f0040 blr x2 9cc: 14000027 b a68 9d0: b9404fa5 ldr w5, [x29, #76] 9d4: b9401a60 ldr w0, [x19, #24] 9d8: f9400261 ldr x1, [x19] 9dc: 7100001f cmp w0, #0x0 9e0: 540000eb b.lt 9fc // b.tstop 9e4: aa0103e0 mov x0, x1 9e8: 91003c00 add x0, x0, #0xf 9ec: 927df000 and x0, x0, #0xfffffffffffffff8 9f0: f9000260 str x0, [x19] 9f4: aa0103e0 mov x0, x1 9f8: 1400000f b a34 9fc: 11002002 add w2, w0, #0x8 a00: b9001a62 str w2, [x19, #24] a04: b9401a62 ldr w2, [x19, #24] a08: 7100005f cmp w2, #0x0 a0c: 540000ed b.le a28 a10: aa0103e0 mov x0, x1 a14: 91003c00 add x0, x0, #0xf a18: 927df000 and x0, x0, #0xfffffffffffffff8 a1c: f9000260 str x0, [x19] a20: aa0103e0 mov x0, x1 a24: 14000004 b a34 a28: f9400661 ldr x1, [x19, #8] a2c: 93407c00 sxtw x0, w0 a30: 8b000020 add x0, x1, x0 a34: f9400000 ldr x0, [x0] a38: aa0003e4 mov x4, x0 a3c: 52800003 mov w3, #0x0 // #0 a40: 2a0503e2 mov w2, w5 a44: f9401ba1 ldr x1, [x29, #48] a48: f9401fa0 ldr x0, [x29, #56] a4c: 97fffecc bl 57c a50: 14000006 b a68 a54: f9401ba2 ldr x2, [x29, #48] a58: 39417fa1 ldrb w1, [x29, #95] a5c: f9401fa0 ldr x0, [x29, #56] a60: d63f0040 blr x2 a64: d503201f nop a68: f94017a0 ldr x0, [x29, #40] a6c: 91000401 add x1, x0, #0x1 a70: f90017a1 str x1, [x29, #40] a74: 39400000 ldrb w0, [x0] a78: 39017fa0 strb w0, [x29, #95] a7c: 39417fa0 ldrb w0, [x29, #95] a80: 7100001f cmp w0, #0x0 a84: 54ffdfa1 b.ne 678 // b.any a88: 14000002 b a90 a8c: d503201f nop a90: d503201f nop a94: f9400bf3 ldr x19, [sp, #16] a98: a8c67bfd ldp x29, x30, [sp], #96 a9c: d65f03c0 ret 0000000000000aa0 : aa0: d10043ff sub sp, sp, #0x10 aa4: f90007e0 str x0, [sp, #8] aa8: f90003e1 str x1, [sp] aac: 90000000 adrp x0, 0 <_start> ab0: 9135e000 add x0, x0, #0xd78 ab4: f94003e1 ldr x1, [sp] ab8: f9000001 str x1, [x0] abc: 90000000 adrp x0, 0 <_start> ac0: 91360000 add x0, x0, #0xd80 ac4: f94007e1 ldr x1, [sp, #8] ac8: f9000001 str x1, [x0] acc: d503201f nop ad0: 910043ff add sp, sp, #0x10 ad4: d65f03c0 ret 0000000000000ad8 : ad8: a9ae7bfd stp x29, x30, [sp, #-288]! adc: 910003fd mov x29, sp ae0: f9001fa0 str x0, [x29, #56] ae4: f90077a1 str x1, [x29, #232] ae8: f9007ba2 str x2, [x29, #240] aec: f9007fa3 str x3, [x29, #248] af0: f90083a4 str x4, [x29, #256] af4: f90087a5 str x5, [x29, #264] af8: f9008ba6 str x6, [x29, #272] afc: f9008fa7 str x7, [x29, #280] b00: 3d801ba0 str q0, [x29, #96] b04: 3d801fa1 str q1, [x29, #112] b08: 3d8023a2 str q2, [x29, #128] b0c: 3d8027a3 str q3, [x29, #144] b10: 3d802ba4 str q4, [x29, #160] b14: 3d802fa5 str q5, [x29, #176] b18: 3d8033a6 str q6, [x29, #192] b1c: 3d8037a7 str q7, [x29, #208] b20: 910483a0 add x0, x29, #0x120 b24: f90023a0 str x0, [x29, #64] b28: 910483a0 add x0, x29, #0x120 b2c: f90027a0 str x0, [x29, #72] b30: 910383a0 add x0, x29, #0xe0 b34: f9002ba0 str x0, [x29, #80] b38: 128006e0 mov w0, #0xffffffc8 // #-56 b3c: b9005ba0 str w0, [x29, #88] b40: 12800fe0 mov w0, #0xffffff80 // #-128 b44: b9005fa0 str w0, [x29, #92] b48: 90000000 adrp x0, 0 <_start> b4c: 91360000 add x0, x0, #0xd80 b50: f9400004 ldr x4, [x0] b54: 90000000 adrp x0, 0 <_start> b58: 9135e000 add x0, x0, #0xd78 b5c: f9400005 ldr x5, [x0] b60: 910043a2 add x2, x29, #0x10 b64: 910103a3 add x3, x29, #0x40 b68: a9400460 ldp x0, x1, [x3] b6c: a9000440 stp x0, x1, [x2] b70: a9410460 ldp x0, x1, [x3, #16] b74: a9010440 stp x0, x1, [x2, #16] b78: 910043a0 add x0, x29, #0x10 b7c: aa0003e3 mov x3, x0 b80: f9401fa2 ldr x2, [x29, #56] b84: aa0503e1 mov x1, x5 b88: aa0403e0 mov x0, x4 b8c: 97fffeb3 bl 658 b90: d503201f nop b94: a8d27bfd ldp x29, x30, [sp], #288 b98: d65f03c0 ret 0000000000000b9c : b9c: d10043ff sub sp, sp, #0x10 ba0: f90007e0 str x0, [sp, #8] ba4: 39001fe1 strb w1, [sp, #7] ba8: f94007e0 ldr x0, [sp, #8] bac: f9400000 ldr x0, [x0] bb0: 91000402 add x2, x0, #0x1 bb4: f94007e1 ldr x1, [sp, #8] bb8: f9000022 str x2, [x1] bbc: 39401fe1 ldrb w1, [sp, #7] bc0: 39000001 strb w1, [x0] bc4: d503201f nop bc8: 910043ff add sp, sp, #0x10 bcc: d65f03c0 ret 0000000000000bd0 : bd0: a9af7bfd stp x29, x30, [sp, #-272]! bd4: 910003fd mov x29, sp bd8: f9001fa0 str x0, [x29, #56] bdc: f9001ba1 str x1, [x29, #48] be0: f90073a2 str x2, [x29, #224] be4: f90077a3 str x3, [x29, #232] be8: f9007ba4 str x4, [x29, #240] bec: f9007fa5 str x5, [x29, #248] bf0: f90083a6 str x6, [x29, #256] bf4: f90087a7 str x7, [x29, #264] bf8: 3d801ba0 str q0, [x29, #96] bfc: 3d801fa1 str q1, [x29, #112] c00: 3d8023a2 str q2, [x29, #128] c04: 3d8027a3 str q3, [x29, #144] c08: 3d802ba4 str q4, [x29, #160] c0c: 3d802fa5 str q5, [x29, #176] c10: 3d8033a6 str q6, [x29, #192] c14: 3d8037a7 str q7, [x29, #208] c18: 910443a0 add x0, x29, #0x110 c1c: f90023a0 str x0, [x29, #64] c20: 910443a0 add x0, x29, #0x110 c24: f90027a0 str x0, [x29, #72] c28: 910383a0 add x0, x29, #0xe0 c2c: f9002ba0 str x0, [x29, #80] c30: 128005e0 mov w0, #0xffffffd0 // #-48 c34: b9005ba0 str w0, [x29, #88] c38: 12800fe0 mov w0, #0xffffff80 // #-128 c3c: b9005fa0 str w0, [x29, #92] c40: 910043a2 add x2, x29, #0x10 c44: 910103a3 add x3, x29, #0x40 c48: a9400460 ldp x0, x1, [x3] c4c: a9000440 stp x0, x1, [x2] c50: a9410460 ldp x0, x1, [x3, #16] c54: a9010440 stp x0, x1, [x2, #16] c58: 910043a2 add x2, x29, #0x10 c5c: 90000000 adrp x0, 0 <_start> c60: 912e7001 add x1, x0, #0xb9c c64: 9100e3a0 add x0, x29, #0x38 c68: aa0203e3 mov x3, x2 c6c: f9401ba2 ldr x2, [x29, #48] c70: 97fffe7a bl 658 c74: 9100e3a0 add x0, x29, #0x38 c78: 52800001 mov w1, #0x0 // #0 c7c: 97ffffc8 bl b9c c80: d503201f nop c84: a8d17bfd ldp x29, x30, [sp], #272 c88: d65f03c0 ret 0000000000000c8c : c8c: a9be7bfd stp x29, x30, [sp, #-32]! c90: 910003fd mov x29, sp c94: 97fffd40 bl 194 c98: 90000000 adrp x0, 0 <_start> c9c: f946ac00 ldr x0, [x0, #3416] ca0: aa0003e1 mov x1, x0 ca4: d2800000 mov x0, #0x0 // #0 ca8: 97ffff7e bl aa0 cac: 94000015 bl d00 cb0: b9001fa0 str w0, [x29, #28] cb4: 90000000 adrp x0, 0 <_start> cb8: 9134e000 add x0, x0, #0xd38 cbc: b9401fa1 ldr w1, [x29, #28] cc0: 97ffff86 bl ad8 cc4: d503201f nop cc8: a8c27bfd ldp x29, x30, [sp], #32 ccc: d65f03c0 ret 0000000000000cd0 : cd0: a9be7bfd stp x29, x30, [sp, #-32]! cd4: 910003fd mov x29, sp cd8: 9400000a bl d00 cdc: b9001fa0 str w0, [x29, #28] ce0: 90000000 adrp x0, 0 <_start> ce4: 9134e000 add x0, x0, #0xd38 ce8: b9401fa1 ldr w1, [x29, #28] cec: 97ffff7b bl ad8 cf0: 97fffd03 bl fc cf4: 12001c00 and w0, w0, #0xff cf8: 97fffcee bl b0 cfc: 17fffffd b cf0 0000000000000d00 : d00: d5384240 mrs x0, currentel d04: d342fc00 lsr x0, x0, #2 d08: d65f03c0 ret 0000000000000d0c : d0c: b9000001 str w1, [x0] d10: d65f03c0 ret 0000000000000d14 : d14: b9400000 ldr w0, [x0] d18: d65f03c0 ret 0000000000000d1c : d1c: f1000400 subs x0, x0, #0x1 d20: 54ffffe1 b.ne d1c // b.any d24: d65f03c0 ret 0000000000000d28 : d28: f800841f str xzr, [x0], #8 d2c: f1002021 subs x1, x1, #0x8 d30: 54ffffcc b.gt d28 d34: d65f03c0 ret ================================================ FILE: exercises/lesson02/3/H-4ND-H/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson02/3/H-4ND-H/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson02/3/H-4ND-H/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson02/3/H-4ND-H/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #endif ================================================ FILE: exercises/lesson02/3/H-4ND-H/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson02/3/H-4ND-H/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson02/3/H-4ND-H/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson02/3/H-4ND-H/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson02/3/H-4ND-H/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson02/3/H-4ND-H/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson02/3/H-4ND-H/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson02/3/H-4ND-H/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SPSR_VALUE msr spsr_el2, x0 adr x0, el1_entry msr elr_el2, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson02/3/H-4ND-H/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson02/3/H-4ND-H/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "mini_uart.h" //to run in qemu use: qemu-system-aarch64 -M raspi3 -serial null -serial mon:stdio -kernel void kernel_main(void) { uart_init(); init_printf(0, putc); int el = get_el(); printf("Exception level: %d \r\n", el); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson02/3/H-4ND-H/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson02/3/H-4ND-H/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson02/3/H-4ND-H/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson02/3/H-4ND-H/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson02/3/H-4ND-H/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson02/3/avenito/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson02/3/avenito/README.md ================================================ # How I figured out that QEMU start at EL2 First I did the changes to use UART instead mini_UART, but the simulation didn't work. After a few tries, I removed the folowing code from boot.S: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el2, x0 adr x0, el1_entry msr elr_el2, x0 eret The simulation worked and the output was: "Exception level: 2". That means, the qemu starts at EL2 ================================================ FILE: exercises/lesson02/3/avenito/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson02/3/avenito/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson02/3/avenito/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #endif ================================================ FILE: exercises/lesson02/3/avenito/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson02/3/avenito/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson02/3/avenito/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson02/3/avenito/include/peripherals/uart.h ================================================ #ifndef _P_UART_H #define _P_UART_H #include "base.h" #define AUX_ENABLES (PBASE+0x00201000) #define UART_DR (PBASE+0x00201000) #define UART_FR (PBASE+0x00201018) #define UART_CR (PBASE+0x00201030) #define UART_IBRD (PBASE+0x00201024) #define UART_FBRD (PBASE+0x00201028) #define UARTLCR_LCRH (PBASE+0x0020102C) #endif /*_P_UART_H */ ================================================ FILE: exercises/lesson02/3/avenito/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson02/3/avenito/include/uart.h ================================================ #ifndef _UART_H #define _UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); void putc ( void* p, char c ); #endif /*_UART_H */ ================================================ FILE: exercises/lesson02/3/avenito/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson02/3/avenito/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 /* qemu starts at EL2, than is not possible to access any register of EL3 */ //ldr x0, =SCR_VALUE //msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el2, x0 // 'spsr_el3' changed to 'spsr_el2' adr x0, el1_entry msr elr_el2, x0 // 'elr_el3' changed to 'elr_el2' eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson02/3/avenito/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson02/3/avenito/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "uart.h" void kernel_main(void) { uart_init(); init_printf(0, putc); int el = get_el(); printf("Exception level: %d \r\n", el); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson02/3/avenito/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson02/3/avenito/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson02/3/avenito/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson02/3/avenito/src/uart.c ================================================ #include "utils.h" #include "peripherals/uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(get32(UART_FR)&0x20) {} // wait if TX is full put32(UART_DR,c); // when TX is empty, send next char } char uart_recv ( void ) { while(get32(UART_FR)&0x10) {} // wait if RX is empty return(get32(UART_DR)&0xFF); // get recived char } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 4<<12; // set alt0 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 4<<15; // set alt0 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(UART_CR,0); // disable RX and TX to configure put32(UART_IBRD,26); //PrimeCell UART (PL011) rev.r1p5 pag.3-9 BAUDDIV = (FUARTCLK/(16 Baud rate)) = 48MHz/(16*115200) = 26.041666 put32(UART_FBRD,3); put32(UARTLCR_LCRH,0x60); //Enable 8 bit mode put32(UART_CR,0x301); // enable UART, RX and TX } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson02/3/avenito/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson02/3/bl4ckout31/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson02/3/bl4ckout31/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson02/3/bl4ckout31/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson02/3/bl4ckout31/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL2, System Control Register (EL2), Page 2665 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 18) | (1 << 16) | (1 << 11) | (3 << 4) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL2, Saved Program Status Register (EL2) Page 383 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #endif ================================================ FILE: exercises/lesson02/3/bl4ckout31/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson02/3/bl4ckout31/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson02/3/bl4ckout31/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson02/3/bl4ckout31/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson02/3/bl4ckout31/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson02/3/bl4ckout31/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson02/3/bl4ckout31/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson02/3/bl4ckout31/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SPSR_VALUE msr spsr_el2, x0 adr x0, el1_entry msr elr_el2, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson02/3/bl4ckout31/src/config.txt ================================================ arm_control=0x200 kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson02/3/bl4ckout31/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "mini_uart.h" void kernel_main(void) { uart_init(); init_printf(0, putc); int el = get_el(); printf("Exception level: %d \r\n", el); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson02/3/bl4ckout31/src/linker.ld ================================================ SECTIONS { . = 0x80000; .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson02/3/bl4ckout31/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson02/3/bl4ckout31/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson02/3/bl4ckout31/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson02/3/bl4ckout31/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson02/3/bl4ckout31/start.sh ================================================ #!/bin/bash echo "###" echo "### Use C-a h for help" echo "###" echo "" qemu-system-aarch64 -machine raspi3 -serial null -serial mon:stdio -nographic -kernel kernel8.img ================================================ FILE: exercises/lesson02/3/evopen/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu C_FLAGS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASM_FLAGS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img kernel-qemu.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(C_FLAGS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASM_FLAGS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T src/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img kernel-qemu.img: $(SRC_DIR)/linker-qemu.ld $(OBJ_FILES) $(ARMGNU)-ld -T src/linker-qemu.ld -o $(BUILD_DIR)/kernel-qemu.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel-qemu.elf -O binary kernel-qemu.img ================================================ FILE: exercises/lesson02/3/evopen/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson02/3/evopen/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson02/3/evopen/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2025 of // AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED \ (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | \ SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 1923 of // AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2022 of // AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 288 of // AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #endif ================================================ FILE: exercises/lesson02/3/evopen/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson02/3/evopen/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson02/3/evopen/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE + 0x00200004) #define GPSET0 (PBASE + 0x0020001C) #define GPCLR0 (PBASE + 0x00200028) #define GPPUD (PBASE + 0x00200094) #define GPPUDCLK0 (PBASE + 0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson02/3/evopen/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE + 0x00215004) #define AUX_MU_IO_REG (PBASE + 0x00215040) #define AUX_MU_IER_REG (PBASE + 0x00215044) #define AUX_MU_IIR_REG (PBASE + 0x00215048) #define AUX_MU_LCR_REG (PBASE + 0x0021504C) #define AUX_MU_MCR_REG (PBASE + 0x00215050) #define AUX_MU_LSR_REG (PBASE + 0x00215054) #define AUX_MU_MSR_REG (PBASE + 0x00215058) #define AUX_MU_SCRATCH (PBASE + 0x0021505C) #define AUX_MU_CNTL_REG (PBASE + 0x00215060) #define AUX_MU_STAT_REG (PBASE + 0x00215064) #define AUX_MU_BAUD_REG (PBASE + 0x00215068) #define CORE_FREQ 250 #define BAUD_FREQ 115200 #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson02/3/evopen/include/peripherals/uart.h ================================================ #ifndef _P_UART_H #define _P_UART_H #include "peripherals/base.h" #define UART_BASE (PBASE + 0x201000) #define UART_DR (UART_BASE) #define UART_FR (UART_BASE + 0x18) #define UART_IBRD (UART_BASE + 0x24) #define UART_FBRD (UART_BASE + 0x28) #define UART_CR (UART_BASE + 0x30) #define UART_LCRH (UART_BASE + 0x2c) #define UART_IMSC (UART_BASE + 0x18) #define CORE_FREQ 250 #define BAUD_FREQ 115200 #endif /*_P_UART_H */ ================================================ FILE: exercises/lesson02/3/evopen/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp, void (*putf)(void*, char)); void tfp_printf(char* fmt, ...); void tfp_sprintf(char* s, char* fmt, ...); void tfp_format(void* putp, void (*putf)(void*, char), char* fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson02/3/evopen/include/uart.h ================================================ #ifndef _UART_H #define _UART_H void uart_init(void); char uart_recv(void); void uart_send(char c); void putc(void* p, char c); void uart_send_string(char* str); unsigned int get_reg_baud(unsigned int core_freq, unsigned int baud); #endif ================================================ FILE: exercises/lesson02/3/evopen/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay(unsigned long); extern void put32(unsigned long, unsigned int); extern int get_el(); extern unsigned int get32(unsigned long); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson02/3/evopen/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SPSR_VALUE // qemu starts at el2 msr spsr_el2, x0 adr x0, el1_entry msr elr_el2, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson02/3/evopen/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "uart.h" void kernel_main(void) { uart_init(); init_printf(0, putc); int el = get_el(); printf("Exception level: %d \r\n", el); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson02/3/evopen/src/linker-qemu.ld ================================================ SECTIONS { . = 0x80000; .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson02/3/evopen/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson02/3/evopen/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson02/3/evopen/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf)(void*, char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc, char* bf) { int n = 0; unsigned int d = 1; while (num / d >= base) d *= base; while (d != 0) { int dgt = num / d; num %= d; d /= base; if (n || dgt > 0 || d == 0) { *bf++ = dgt + (dgt < 10 ? '0' : (uc ? 'A' : 'a') - 10); ++n; } } *bf = 0; } static void li2a(long num, char* bf) { if (num < 0) { num = -num; *bf++ = '-'; } uli2a(num, 10, 0, bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc, char* bf) { int n = 0; unsigned int d = 1; while (num / d >= base) d *= base; while (d != 0) { int dgt = num / d; num %= d; d /= base; if (n || dgt > 0 || d == 0) { *bf++ = dgt + (dgt < 10 ? '0' : (uc ? 'A' : 'a') - 10); ++n; } } *bf = 0; } static void i2a(int num, char* bf) { if (num < 0) { num = -num; *bf++ = '-'; } ui2a(num, 10, 0, bf); } static int a2d(char ch) { if (ch >= '0' && ch <= '9') return ch - '0'; else if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10; else if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10; else return -1; } static char a2i(char ch, char** src, int base, int* nump) { char* p = *src; int num = 0; int digit; while ((digit = a2d(ch)) >= 0) { if (digit > base) break; num = num * base + digit; ch = *p++; } *src = p; *nump = num; return ch; } static void putchw(void* putp, putcf putf, int n, char z, char* bf) { char fc = z ? '0' : ' '; char ch; char* p = bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp, fc); while ((ch = *bf++)) putf(putp, ch); } void tfp_format(void* putp, putcf putf, char* fmt, va_list va) { char bf[12]; char ch; while ((ch = *(fmt++))) { if (ch != '%') putf(putp, ch); else { char lz = 0; #ifdef PRINTF_LONG_SUPPORT char lng = 0; #endif int w = 0; ch = *(fmt++); if (ch == '0') { ch = *(fmt++); lz = 1; } if (ch >= '0' && ch <= '9') { ch = a2i(ch, &fmt, 10, &w); } #ifdef PRINTF_LONG_SUPPORT if (ch == 'l') { ch = *(fmt++); lng = 1; } #endif switch (ch) { case 0: goto abort; case 'u': { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int), 10, 0, bf); else #endif ui2a(va_arg(va, unsigned int), 10, 0, bf); putchw(putp, putf, w, lz, bf); break; } case 'd': { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int), bf); else #endif i2a(va_arg(va, int), bf); putchw(putp, putf, w, lz, bf); break; } case 'x': case 'X': #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int), 16, (ch == 'X'), bf); else #endif ui2a(va_arg(va, unsigned int), 16, (ch == 'X'), bf); putchw(putp, putf, w, lz, bf); break; case 'c': putf(putp, (char)(va_arg(va, int))); break; case 's': putchw(putp, putf, w, 0, va_arg(va, char*)); break; case '%': putf(putp, ch); default: break; } } } abort:; } void init_printf(void* putp, void (*putf)(void*, char)) { stdout_putf = putf; stdout_putp = putp; } void tfp_printf(char* fmt, ...) { va_list va; va_start(va, fmt); tfp_format(stdout_putp, stdout_putf, fmt, va); va_end(va); } static void putcp(void* p, char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s, char* fmt, ...) { va_list va; va_start(va, fmt); tfp_format(&s, putcp, fmt, va); putcp(&s, 0); va_end(va); } ================================================ FILE: exercises/lesson02/3/evopen/src/uart.c ================================================ #include "peripherals/gpio.h" #include "peripherals/uart.h" #include "utils.h" void uart_init(void) { unsigned int selector; // 32 bits selector = get32(GPFSEL1); selector &= ~(7 << 12); selector |= 4 << 12; selector &= ~(7 << 15); selector |= 4 << 15; put32(GPFSEL1, selector); put32(GPPUD, 0); delay(150); put32(GPPUDCLK0, (1 << 14) | (1 << 15)); delay(150); put32(GPPUDCLK0, 0); put32(UART_CR, 0); put32(UART_IBRD, 26); put32(UART_FBRD, 3); put32(UART_LCRH, (1 << 4) | (3 << 5)); // put32(UART_IMSC, 0); put32(UART_CR, 1 | (1 << 8) | (1 << 9)); } void uart_send(char c) { while (1) { if (!(get32(UART_FR) & (1 << 5))) break; } put32(UART_DR, c); } char uart_recv(void) { while (1) { if (!(get32(UART_FR) & (1 << 4))) break; } return (get32(UART_DR) & 0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i++) { uart_send((char)str[i]); } } void putc(void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson02/3/evopen/src/utils.S ================================================ .globl put32 put32: str w1, [x0] ret .globl get32 get32: ldr w0, [x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret ================================================ FILE: exercises/lesson02/3/evopen/start.sh ================================================ #!/bin/bash ./build.sh if [ $1 == "pi" ]; then cp -f config.txt kernel8.img /Volumes/boot/ && diskutil umount /Volumes/boot elif [ $1 == "qemu" ]; then qemu-system-aarch64 -M raspi3 -smp cores=4,sockets=1 -nographic -kernel kernel-qemu.img fi ================================================ FILE: exercises/lesson02/3/rs/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel7.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel7.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel7.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel7.elf -O binary kernel7.img ================================================ FILE: exercises/lesson02/3/rs/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson02/3/rs/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson02/3/rs/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of // AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED \ (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | \ SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of // AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of // AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of // AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_EL2h (9 << 0) #define SPSR_EL1 (SPSR_MASK_ALL | SPSR_EL1h) #define SPSR_EL2 (SPSR_MASK_ALL | SPSR_EL2h) /* Current Exception Level values, as contained in CurrentEL */ #define CurrentEL_EL1 (1 << 2) #define CurrentEL_EL2 (2 << 2) #define CurrentEL_EL3 (3 << 2) #endif ================================================ FILE: exercises/lesson02/3/rs/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init(void); char uart_recv(void); void uart_send(char c); void putc(void *p, char c); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson02/3/rs/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson02/3/rs/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson02/3/rs/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE + 0x00200004) #define GPSET0 (PBASE + 0x0020001C) #define GPCLR0 (PBASE + 0x00200028) #define GPPUD (PBASE + 0x00200094) #define GPPUDCLK0 (PBASE + 0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson02/3/rs/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE + 0x00215004) #define AUX_MU_IO_REG (PBASE + 0x00215040) #define AUX_MU_IER_REG (PBASE + 0x00215044) #define AUX_MU_IIR_REG (PBASE + 0x00215048) #define AUX_MU_LCR_REG (PBASE + 0x0021504C) #define AUX_MU_MCR_REG (PBASE + 0x00215050) #define AUX_MU_LSR_REG (PBASE + 0x00215054) #define AUX_MU_MSR_REG (PBASE + 0x00215058) #define AUX_MU_SCRATCH (PBASE + 0x0021505C) #define AUX_MU_CNTL_REG (PBASE + 0x00215060) #define AUX_MU_STAT_REG (PBASE + 0x00215064) #define AUX_MU_BAUD_REG (PBASE + 0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson02/3/rs/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void *putp, void (*putf)(void *, char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char *s, char *fmt, ...); void tfp_format(void *putp, void (*putf)(void *, char), char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson02/3/rs/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay(unsigned long); extern void put32(unsigned long, unsigned int); extern unsigned int get32(unsigned long); extern int get_el(void); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson02/3/rs/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0, #0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: mrs x0, CurrentEL cmp x0, #CurrentEL_EL3 b.eq el3_entry b el2_entry el3_entry: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el2, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_EL2 msr spsr_el3, x0 adr x0, el2_entry msr elr_el3, x0 eret el2_entry: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =SPSR_EL1 msr spsr_el2, x0 adr x0, el1_entry msr elr_el2, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson02/3/rs/src/config.txt ================================================ arm_control=0x200 kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson02/3/rs/src/kernel.c ================================================ #include "mini_uart.h" #include "printf.h" #include "utils.h" void kernel_main(void) { uart_init(); init_printf(0, putc); int el = get_el(); printf("Exception level: %d \r\n", el); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson02/3/rs/src/linker.ld ================================================ SECTIONS { . = 0x80000; .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson02/3/rs/src/mini_uart.c ================================================ #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" #include "utils.h" void uart_send(char c) { while (1) { if (get32(AUX_MU_LSR_REG) & 0x20) break; } put32(AUX_MU_IO_REG, c); } char uart_recv(void) { while (1) { if (get32(AUX_MU_LSR_REG) & 0x01) break; } return (get32(AUX_MU_IO_REG) & 0xFF); } void uart_send_string(char *str) { for (int i = 0; str[i] != '\0'; i++) { uart_send((char)str[i]); } } void uart_init(void) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7 << 12); // clean gpio14 selector |= 2 << 12; // set alt5 for gpio14 selector &= ~(7 << 15); // clean gpio15 selector |= 2 << 15; // set alt5 for gpio15 put32(GPFSEL1, selector); put32(GPPUD, 0); delay(150); put32(GPPUDCLK0, (1 << 14) | (1 << 15)); delay(150); put32(GPPUDCLK0, 0); put32(AUX_ENABLES, 1); // Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG, 0); // Disable auto flow control and disable receiver // and transmitter (for now) put32(AUX_MU_IER_REG, 0); // Disable receive and transmit interrupts put32(AUX_MU_LCR_REG, 3); // Enable 8 bit mode put32(AUX_MU_MCR_REG, 0); // Set RTS line to be always high put32(AUX_MU_BAUD_REG, 270); // Set baud rate to 115200 put32(AUX_MU_CNTL_REG, 3); // Finally, enable transmitter and receiver } // This function is required by printf function void putc(void *p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson02/3/rs/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson02/3/rs/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf)(void *, char); static putcf stdout_putf; static void *stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc, char *bf) { int n = 0; unsigned int d = 1; while (num / d >= base) d *= base; while (d != 0) { int dgt = num / d; num %= d; d /= base; if (n || dgt > 0 || d == 0) { *bf++ = dgt + (dgt < 10 ? '0' : (uc ? 'A' : 'a') - 10); ++n; } } *bf = 0; } static void li2a(long num, char *bf) { if (num < 0) { num = -num; *bf++ = '-'; } uli2a(num, 10, 0, bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc, char *bf) { int n = 0; unsigned int d = 1; while (num / d >= base) d *= base; while (d != 0) { int dgt = num / d; num %= d; d /= base; if (n || dgt > 0 || d == 0) { *bf++ = dgt + (dgt < 10 ? '0' : (uc ? 'A' : 'a') - 10); ++n; } } *bf = 0; } static void i2a(int num, char *bf) { if (num < 0) { num = -num; *bf++ = '-'; } ui2a(num, 10, 0, bf); } static int a2d(char ch) { if (ch >= '0' && ch <= '9') return ch - '0'; else if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10; else if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10; else return -1; } static char a2i(char ch, char **src, int base, int *nump) { char *p = *src; int num = 0; int digit; while ((digit = a2d(ch)) >= 0) { if (digit > base) break; num = num * base + digit; ch = *p++; } *src = p; *nump = num; return ch; } static void putchw(void *putp, putcf putf, int n, char z, char *bf) { char fc = z ? '0' : ' '; char ch; char *p = bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp, fc); while ((ch = *bf++)) putf(putp, ch); } void tfp_format(void *putp, putcf putf, char *fmt, va_list va) { char bf[12]; char ch; while ((ch = *(fmt++))) { if (ch != '%') putf(putp, ch); else { char lz = 0; #ifdef PRINTF_LONG_SUPPORT char lng = 0; #endif int w = 0; ch = *(fmt++); if (ch == '0') { ch = *(fmt++); lz = 1; } if (ch >= '0' && ch <= '9') { ch = a2i(ch, &fmt, 10, &w); } #ifdef PRINTF_LONG_SUPPORT if (ch == 'l') { ch = *(fmt++); lng = 1; } #endif switch (ch) { case 0: goto abort; case 'u': { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int), 10, 0, bf); else #endif ui2a(va_arg(va, unsigned int), 10, 0, bf); putchw(putp, putf, w, lz, bf); break; } case 'd': { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int), bf); else #endif i2a(va_arg(va, int), bf); putchw(putp, putf, w, lz, bf); break; } case 'x': case 'X': #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int), 16, (ch == 'X'), bf); else #endif ui2a(va_arg(va, unsigned int), 16, (ch == 'X'), bf); putchw(putp, putf, w, lz, bf); break; case 'c': putf(putp, (char)(va_arg(va, int))); break; case 's': putchw(putp, putf, w, 0, va_arg(va, char *)); break; case '%': putf(putp, ch); default: break; } } } abort:; } void init_printf(void *putp, void (*putf)(void *, char)) { stdout_putf = putf; stdout_putp = putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va, fmt); tfp_format(stdout_putp, stdout_putf, fmt, va); va_end(va); } static void putcp(void *p, char c) { *(*((char **)p))++ = c; } void tfp_sprintf(char *s, char *fmt, ...) { va_list va; va_start(va, fmt); tfp_format(&s, putcp, fmt, va); putcp(&s, 0); va_end(va); } ================================================ FILE: exercises/lesson02/3/rs/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1, [x0] ret .globl get32 get32: ldr w0, [x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson02/3/rs/start.sh ================================================ #!/bin/bash qemu-system-aarch64 -machine raspi3 -serial null -serial mon:stdio -nographic -kernel kernel7.img ================================================ FILE: exercises/lesson03/1/H-4ND-H/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson03/1/H-4ND-H/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson03/1/H-4ND-H/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson03/1/H-4ND-H/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #endif ================================================ FILE: exercises/lesson03/1/H-4ND-H/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 256 // size of all saved registers #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #endif ================================================ FILE: exercises/lesson03/1/H-4ND-H/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson03/1/H-4ND-H/include/local_timer.h ================================================ #ifndef _LOCAL_TIMER_H #define _LOCAL_TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); #endif /*_LOCAL_TIMER_H */ ================================================ FILE: exercises/lesson03/1/H-4ND-H/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson03/1/H-4ND-H/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson03/1/H-4ND-H/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson03/1/H-4ND-H/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson03/1/H-4ND-H/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define CORE0_IRQ_SRC (0x40000060) #define LOCAL_TIMER_IRQ (1 << 11) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson03/1/H-4ND-H/include/peripherals/local_timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (0x40000034) #define TIMER_ICR (0x40000038) #define TIMER_LIR (0x40000024) #define TIMER_IRQ_CLR (1 << 31) #define TIMER_IRQ_CORE0 (0) #define TIMER_IRQ_EN (1 << 29) #define TIMER_EN (1 << 28) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson03/1/H-4ND-H/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson03/1/H-4ND-H/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson03/1/H-4ND-H/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson03/1/H-4ND-H/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson03/1/H-4ND-H/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson03/1/H-4ND-H/src/entry.S ================================================ #include "entry.h" .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] str x30, [sp, #16 * 15] .endm .macro kernel_exit ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] ldr x30, [sp, #16 * 15] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 ventry irq_invalid_el0_64 // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry ERROR_INVALID_EL1h sync_invalid_el0_64: handle_invalid_entry SYNC_INVALID_EL0_64 irq_invalid_el0_64: handle_invalid_entry IRQ_INVALID_EL0_64 fiq_invalid_el0_64: handle_invalid_entry FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry ERROR_INVALID_EL0_32 el1_irq: kernel_entry bl handle_irq kernel_exit .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson03/1/H-4ND-H/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson03/1/H-4ND-H/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "local_timer.h" #include "entry.h" #include "peripherals/irq.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32" }; void enable_interrupt_controller() { //local timer has no en/dis bit, it is enablen timer itself } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { unsigned int irq = get32(CORE0_IRQ_SRC); switch (irq) { case (LOCAL_TIMER_IRQ): handle_timer_irq(); break; default: printf("Unknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson03/1/H-4ND-H/src/kernel.c ================================================ #include "printf.h" #include "local_timer.h" #include "irq.h" #include "mini_uart.h" void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); while (1){ uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson03/1/H-4ND-H/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson03/1/H-4ND-H/src/local_timer.c ================================================ #include "utils.h" #include "printf.h" #include "peripherals/local_timer.h" #define RESET_VALUE 38461538 void timer_init ( void ) { put32(TIMER_CS, TIMER_IRQ_EN | TIMER_EN | RESET_VALUE); put32(TIMER_LIR, TIMER_IRQ_CORE0); } void handle_timer_irq( void ) { put32(TIMER_ICR, TIMER_IRQ_CLR); printf("Timer interrupt received\n\r"); } ================================================ FILE: exercises/lesson03/1/H-4ND-H/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson03/1/H-4ND-H/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson03/1/H-4ND-H/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson03/1/H-4ND-H/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson03/1/avenito/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson03/1/avenito/README.md ================================================ # Getting information about the hardware 1. Looking at the suggested issue [here](https://github.com/s-matyukevich/raspberry-pi-os/issues/70), we can find out by [BCM2836 ARM-local peripherals](https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2836/QA7_rev3.4.pdf) how to use the peripherals. 1. BCM2836 ARM-local peripherals, page 3, has the ARM address map and we can verify the base address of Local peripherals is 0x40000000. 1. On page 7 we find the register's addresses for "Local timer". 0x4000_0034 Local timer control & status 0x4000_0038 Local timer write flags 1. On page 17, bits of Local timer control & status, Page 18 bits of Local timer write flags. # Steps to setup the "Local Timer" Following the "kernel.c" ... 1. timer_init() - reload the time interval - Page 17 - Address: 0x4000_0034 Bits 0:27. According to the information, the value to generate an interruption each 1 sec can be calculated by the equation "Interval(Hz) = 38.4*10^6 / value", but it doesn't work for me, then, I simply got an "aleatory" value. Enable Timer (1<<28). 1. enable_interrupt_controller() - it isn't enabling interrupt controller, instead, it's enabling local timer interrupt. 1. enable_irq() - nothing to change. 1. handle_timer_irq() - clear the interrupt flag and reload timer at 0x4000_0038. ================================================ FILE: exercises/lesson03/1/avenito/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson03/1/avenito/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson03/1/avenito/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #endif ================================================ FILE: exercises/lesson03/1/avenito/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 256 // size of all saved registers #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #endif ================================================ FILE: exercises/lesson03/1/avenito/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson03/1/avenito/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson03/1/avenito/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson03/1/avenito/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #define PERIPHERAL_BASE 0x40000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson03/1/avenito/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson03/1/avenito/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #define CORE0_INT_SOURCE (PERIPHERAL_BASE+0x60) #define LOCAL_TIMER_INT (1 << 11) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson03/1/avenito/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson03/1/avenito/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) // Local timer #define TIMER_CTRL (PERIPHERAL_BASE+0x34) #define TIMER_FLAG (PERIPHERAL_BASE+0x38) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson03/1/avenito/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson03/1/avenito/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson03/1/avenito/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson03/1/avenito/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson03/1/avenito/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson03/1/avenito/src/entry.S ================================================ #include "entry.h" .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] str x30, [sp, #16 * 15] .endm .macro kernel_exit ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] ldr x30, [sp, #16 * 15] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 ventry irq_invalid_el0_64 // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry ERROR_INVALID_EL1h sync_invalid_el0_64: handle_invalid_entry SYNC_INVALID_EL0_64 irq_invalid_el0_64: handle_invalid_entry IRQ_INVALID_EL0_64 fiq_invalid_el0_64: handle_invalid_entry FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry ERROR_INVALID_EL0_32 el1_irq: kernel_entry bl handle_irq kernel_exit .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson03/1/avenito/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson03/1/avenito/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "timer.h" #include "entry.h" #include "peripherals/irq.h" #include "peripherals/timer.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32" }; void enable_interrupt_controller() { // it isn't the interrupt controller, but enable local timer interrupt unsigned int local_timer_ctrl = get32(TIMER_CTRL); put32(TIMER_CTRL, (local_timer_ctrl | (1 << 29))); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { unsigned int irq = get32(CORE0_INT_SOURCE); switch (irq) { case (LOCAL_TIMER_INT): handle_timer_irq(); break; default: printf("Unknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson03/1/avenito/src/kernel.c ================================================ #include "printf.h" #include "timer.h" #include "irq.h" #include "mini_uart.h" void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); while (1){ uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson03/1/avenito/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson03/1/avenito/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson03/1/avenito/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson03/1/avenito/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson03/1/avenito/src/timer.c ================================================ #include "utils.h" #include "printf.h" #include "peripherals/timer.h" const unsigned int interval = 20000000; unsigned int curVal = 0; void timer_init ( void ) { // Set value, enable Timer and Interrupt put32(TIMER_CTRL, ((1<<28) | interval)); } void handle_timer_irq( void ) { printf("Timer interrupt received, Local Timer\n\r"); put32(TIMER_FLAG, (3<<30)); // clear interrupt flag and reload timer } ================================================ FILE: exercises/lesson03/1/avenito/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson03/1/bl4ckout31/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson03/1/bl4ckout31/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson03/1/bl4ckout31/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson03/1/bl4ckout31/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #endif ================================================ FILE: exercises/lesson03/1/bl4ckout31/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 256 // size of all saved registers #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #endif ================================================ FILE: exercises/lesson03/1/bl4ckout31/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson03/1/bl4ckout31/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson03/1/bl4ckout31/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson03/1/bl4ckout31/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #define LPBASE 0x40000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson03/1/bl4ckout31/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson03/1/bl4ckout31/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) // See BCM2836 ARM-local peripherals at // https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2836/QA7_rev3.4.pdf #define CORE0_INTERRUPT_SOURCES (LPBASE+0x60) #define LTIMER_INTERRUPT (1 << 11) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson03/1/bl4ckout31/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson03/1/bl4ckout31/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) // See BCM2836 ARM-local peripherals at // https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2836/QA7_rev3.4.pdf #define LTIMER_CTRL (LPBASE+0x34) #define LTIMER_CLR (LPBASE+0x38) #define LTIMER_CTRL_EN (1 << 28) #define LTIMER_CTRL_INT_EN (1 << 29) #define LTIMER_CTRL_VALUE (LTIMER_CTRL_EN | LTIMER_CTRL_INT_EN) #define LTIMER_CLR_ACK (1 << 31) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson03/1/bl4ckout31/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson03/1/bl4ckout31/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); void local_timer_init ( void ); void handle_local_timer_irq ( void ); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson03/1/bl4ckout31/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson03/1/bl4ckout31/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson03/1/bl4ckout31/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson03/1/bl4ckout31/src/entry.S ================================================ #include "entry.h" .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] str x30, [sp, #16 * 15] .endm .macro kernel_exit ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] ldr x30, [sp, #16 * 15] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 ventry irq_invalid_el0_64 // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry ERROR_INVALID_EL1h sync_invalid_el0_64: handle_invalid_entry SYNC_INVALID_EL0_64 irq_invalid_el0_64: handle_invalid_entry IRQ_INVALID_EL0_64 fiq_invalid_el0_64: handle_invalid_entry FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry ERROR_INVALID_EL0_32 el1_irq: kernel_entry bl handle_irq kernel_exit .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson03/1/bl4ckout31/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson03/1/bl4ckout31/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "timer.h" #include "entry.h" #include "peripherals/irq.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32" }; void enable_interrupt_controller() { /** The local interrupt controller can't be enable or disable. * Instead, we are supposed to rely on the interrupt sources * registers to enable or disable their interrupts * * Neverless, we can still change wich core will receive the * interrupts. They go to Core 0 IRQ by default, which is what * we want. This function becomes effectively useless. */ } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { // Each Core has its own pending local intrrupts register unsigned int irq = get32(CORE0_INTERRUPT_SOURCES); switch (irq) { case (LTIMER_INTERRUPT): handle_local_timer_irq(); break; default: printf("Unknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson03/1/bl4ckout31/src/kernel.c ================================================ #include "printf.h" #include "timer.h" #include "irq.h" #include "mini_uart.h" void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); local_timer_init(); enable_interrupt_controller(); enable_irq(); while (1){ uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson03/1/bl4ckout31/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson03/1/bl4ckout31/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson03/1/bl4ckout31/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson03/1/bl4ckout31/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson03/1/bl4ckout31/src/timer.c ================================================ #include "utils.h" #include "printf.h" #include "peripherals/timer.h" const unsigned int interval = 9600000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); printf("Timer interrupt received\n\r"); } void local_timer_init ( void ) { put32(LTIMER_CTRL, (interval | LTIMER_CTRL_VALUE)); } void handle_local_timer_irq( void ) { printf("Timer interrupt received\n\r"); put32(LTIMER_CLR, LTIMER_CLR_ACK); } ================================================ FILE: exercises/lesson03/1/bl4ckout31/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson03/1/bl4ckout31/start.sh ================================================ #!/bin/bash if [ $# -ne 1 ]; then echo "usage: $0 kernel" exit 1 fi mount -L rpiboot /mnt cp "$1" /mnt umount /mnt ================================================ FILE: exercises/lesson03/1/rs/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel7.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel7.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel7.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel7.elf -O binary kernel7.img ================================================ FILE: exercises/lesson03/1/rs/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson03/1/rs/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson03/1/rs/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of // AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED \ (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | \ SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of // AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of // AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of // AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #endif ================================================ FILE: exercises/lesson03/1/rs/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 256 // size of all saved registers #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #endif ================================================ FILE: exercises/lesson03/1/rs/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller(void); void irq_vector_init(void); void enable_irq(void); void disable_irq(void); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson03/1/rs/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init(void); char uart_recv(void); void uart_send(char c); void putc(void *p, char c); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson03/1/rs/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson03/1/rs/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson03/1/rs/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE + 0x00200004) #define GPSET0 (PBASE + 0x0020001C) #define GPCLR0 (PBASE + 0x00200028) #define GPPUD (PBASE + 0x00200094) #define GPPUDCLK0 (PBASE + 0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson03/1/rs/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE + 0x0000B200) #define IRQ_PENDING_1 (PBASE + 0x0000B204) #define IRQ_PENDING_2 (PBASE + 0x0000B208) #define FIQ_CONTROL (PBASE + 0x0000B20C) #define ENABLE_IRQS_1 (PBASE + 0x0000B210) #define ENABLE_IRQS_2 (PBASE + 0x0000B214) #define ENABLE_BASIC_IRQS (PBASE + 0x0000B218) #define DISABLE_IRQS_1 (PBASE + 0x0000B21C) #define DISABLE_IRQS_2 (PBASE + 0x0000B220) #define DISABLE_BASIC_IRQS (PBASE + 0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #define ARM_TIMER_IRQ (1 << 0) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson03/1/rs/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE + 0x00215004) #define AUX_MU_IO_REG (PBASE + 0x00215040) #define AUX_MU_IER_REG (PBASE + 0x00215044) #define AUX_MU_IIR_REG (PBASE + 0x00215048) #define AUX_MU_LCR_REG (PBASE + 0x0021504C) #define AUX_MU_MCR_REG (PBASE + 0x00215050) #define AUX_MU_LSR_REG (PBASE + 0x00215054) #define AUX_MU_MSR_REG (PBASE + 0x00215058) #define AUX_MU_SCRATCH (PBASE + 0x0021505C) #define AUX_MU_CNTL_REG (PBASE + 0x00215060) #define AUX_MU_STAT_REG (PBASE + 0x00215064) #define AUX_MU_BAUD_REG (PBASE + 0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson03/1/rs/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE + 0x00003000) #define TIMER_CLO (PBASE + 0x00003004) #define TIMER_CHI (PBASE + 0x00003008) #define TIMER_C0 (PBASE + 0x0000300C) #define TIMER_C1 (PBASE + 0x00003010) #define TIMER_C2 (PBASE + 0x00003014) #define TIMER_C3 (PBASE + 0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) // Reference: 14.2 Timer Registers // https://web.stanford.edu/class/cs140e/docs/BCM2837-ARM-Peripherals.pdf #define ARM_TIMER_BASE (PBASE + 0xB000) #define ARM_TIMER_LOAD (ARM_TIMER_BASE + 0x400) #define ARM_TIMER_VALUE (ARM_TIMER_BASE + 0x404) #define ARM_TIMER_CTRL (ARM_TIMER_BASE + 0x408) #define ARM_TIMER_CLR (ARM_TIMER_BASE + 0x40c) #define CTRL_23BIT (1 << 1) // 23-bit counter #define CTRL_INT_ENABLE (1 << 5) // Timer interrupt enabled #define CTRL_ENABLE (1 << 7) // Timer enabled #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson03/1/rs/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void *putp, void (*putf)(void *, char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char *s, char *fmt, ...); void tfp_format(void *putp, void (*putf)(void *, char), char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson03/1/rs/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init(void); void handle_timer_irq(void); void arm_timer_init(void); void handle_arm_timer_irq(void); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson03/1/rs/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay(unsigned long); extern void put32(unsigned long, unsigned int); extern unsigned int get32(unsigned long); extern int get_el(void); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson03/1/rs/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0, #0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson03/1/rs/src/config.txt ================================================ arm_control=0x200 kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson03/1/rs/src/entry.S ================================================ #include "entry.h" .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] str x30, [sp, #16 * 15] .endm .macro kernel_exit ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] ldr x30, [sp, #16 * 15] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 ventry irq_invalid_el0_64 // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry ERROR_INVALID_EL1h sync_invalid_el0_64: handle_invalid_entry SYNC_INVALID_EL0_64 irq_invalid_el0_64: handle_invalid_entry IRQ_INVALID_EL0_64 fiq_invalid_el0_64: handle_invalid_entry FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry ERROR_INVALID_EL0_32 el1_irq: kernel_entry bl handle_irq kernel_exit .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson03/1/rs/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson03/1/rs/src/irq.c ================================================ #include "peripherals/irq.h" #include "entry.h" #include "printf.h" #include "timer.h" #include "utils.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32"}; void enable_interrupt_controller() { // put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1); put32(ENABLE_BASIC_IRQS, ARM_TIMER_IRQ); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { // unsigned int irq = get32(IRQ_PENDING_1); // switch (irq) { // case (SYSTEM_TIMER_IRQ_1): // handle_timer_irq(); // break; // default: // printf("Unknown pending irq: %x\r\n", irq); // } unsigned int irq = get32(IRQ_BASIC_PENDING); switch (irq) { case (ARM_TIMER_IRQ): handle_arm_timer_irq(); break; default: printf("Unknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson03/1/rs/src/kernel.c ================================================ #include "irq.h" #include "mini_uart.h" #include "printf.h" #include "timer.h" void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); arm_timer_init(); enable_interrupt_controller(); enable_irq(); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson03/1/rs/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson03/1/rs/src/mini_uart.c ================================================ #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" #include "utils.h" void uart_send(char c) { while (1) { if (get32(AUX_MU_LSR_REG) & 0x20) break; } put32(AUX_MU_IO_REG, c); } char uart_recv(void) { while (1) { if (get32(AUX_MU_LSR_REG) & 0x01) break; } return (get32(AUX_MU_IO_REG) & 0xFF); } void uart_send_string(char *str) { for (int i = 0; str[i] != '\0'; i++) { uart_send((char)str[i]); } } void uart_init(void) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7 << 12); // clean gpio14 selector |= 2 << 12; // set alt5 for gpio14 selector &= ~(7 << 15); // clean gpio15 selector |= 2 << 15; // set alt5 for gpio15 put32(GPFSEL1, selector); put32(GPPUD, 0); delay(150); put32(GPPUDCLK0, (1 << 14) | (1 << 15)); delay(150); put32(GPPUDCLK0, 0); put32(AUX_ENABLES, 1); // Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG, 0); // Disable auto flow control and disable receiver // and transmitter (for now) put32(AUX_MU_IER_REG, 0); // Disable receive and transmit interrupts put32(AUX_MU_LCR_REG, 3); // Enable 8 bit mode put32(AUX_MU_MCR_REG, 0); // Set RTS line to be always high put32(AUX_MU_BAUD_REG, 270); // Set baud rate to 115200 put32(AUX_MU_CNTL_REG, 3); // Finally, enable transmitter and receiver } // This function is required by printf function void putc(void *p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson03/1/rs/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson03/1/rs/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf)(void *, char); static putcf stdout_putf; static void *stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc, char *bf) { int n = 0; unsigned int d = 1; while (num / d >= base) d *= base; while (d != 0) { int dgt = num / d; num %= d; d /= base; if (n || dgt > 0 || d == 0) { *bf++ = dgt + (dgt < 10 ? '0' : (uc ? 'A' : 'a') - 10); ++n; } } *bf = 0; } static void li2a(long num, char *bf) { if (num < 0) { num = -num; *bf++ = '-'; } uli2a(num, 10, 0, bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc, char *bf) { int n = 0; unsigned int d = 1; while (num / d >= base) d *= base; while (d != 0) { int dgt = num / d; num %= d; d /= base; if (n || dgt > 0 || d == 0) { *bf++ = dgt + (dgt < 10 ? '0' : (uc ? 'A' : 'a') - 10); ++n; } } *bf = 0; } static void i2a(int num, char *bf) { if (num < 0) { num = -num; *bf++ = '-'; } ui2a(num, 10, 0, bf); } static int a2d(char ch) { if (ch >= '0' && ch <= '9') return ch - '0'; else if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10; else if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10; else return -1; } static char a2i(char ch, char **src, int base, int *nump) { char *p = *src; int num = 0; int digit; while ((digit = a2d(ch)) >= 0) { if (digit > base) break; num = num * base + digit; ch = *p++; } *src = p; *nump = num; return ch; } static void putchw(void *putp, putcf putf, int n, char z, char *bf) { char fc = z ? '0' : ' '; char ch; char *p = bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp, fc); while ((ch = *bf++)) putf(putp, ch); } void tfp_format(void *putp, putcf putf, char *fmt, va_list va) { char bf[12]; char ch; while ((ch = *(fmt++))) { if (ch != '%') putf(putp, ch); else { char lz = 0; #ifdef PRINTF_LONG_SUPPORT char lng = 0; #endif int w = 0; ch = *(fmt++); if (ch == '0') { ch = *(fmt++); lz = 1; } if (ch >= '0' && ch <= '9') { ch = a2i(ch, &fmt, 10, &w); } #ifdef PRINTF_LONG_SUPPORT if (ch == 'l') { ch = *(fmt++); lng = 1; } #endif switch (ch) { case 0: goto abort; case 'u': { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int), 10, 0, bf); else #endif ui2a(va_arg(va, unsigned int), 10, 0, bf); putchw(putp, putf, w, lz, bf); break; } case 'd': { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int), bf); else #endif i2a(va_arg(va, int), bf); putchw(putp, putf, w, lz, bf); break; } case 'x': case 'X': #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int), 16, (ch == 'X'), bf); else #endif ui2a(va_arg(va, unsigned int), 16, (ch == 'X'), bf); putchw(putp, putf, w, lz, bf); break; case 'c': putf(putp, (char)(va_arg(va, int))); break; case 's': putchw(putp, putf, w, 0, va_arg(va, char *)); break; case '%': putf(putp, ch); default: break; } } } abort:; } void init_printf(void *putp, void (*putf)(void *, char)) { stdout_putf = putf; stdout_putp = putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va, fmt); tfp_format(stdout_putp, stdout_putf, fmt, va); va_end(va); } static void putcp(void *p, char c) { *(*((char **)p))++ = c; } void tfp_sprintf(char *s, char *fmt, ...) { va_list va; va_start(va, fmt); tfp_format(&s, putcp, fmt, va); putcp(&s, 0); va_end(va); } ================================================ FILE: exercises/lesson03/1/rs/src/timer.c ================================================ #include "peripherals/timer.h" #include "printf.h" #include "utils.h" const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init(void) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq(void) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); printf("Timer interrupt received\n\r"); } void arm_timer_init(void) { // According to the timer pre-divider register documentation, the default // pre-devider is configured to run at apb_clock/126 which is 1Mhz like the // system timer. put32(ARM_TIMER_LOAD, interval); put32(ARM_TIMER_CTRL, CTRL_ENABLE | CTRL_INT_ENABLE | CTRL_23BIT); } void handle_arm_timer_irq(void) { put32(ARM_TIMER_CLR, 1); printf("Timer interrupt received\n\r"); } ================================================ FILE: exercises/lesson03/1/rs/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson03/1/szediwy/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src .PHONY: clean all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img @echo "clean: [SUCCESS]" $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson03/1/szediwy/README.md ================================================ # Readme Solved the issue for the Raspberry Pi 4B. The solution has both timer active. The system timer 1 and the local timer. ================================================ FILE: exercises/lesson03/1/szediwy/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson03/1/szediwy/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson03/1/szediwy/include/custom_printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); void test (char c); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson03/1/szediwy/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 256 // size of all saved registers #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #endif ================================================ FILE: exercises/lesson03/1/szediwy/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void enable_interrupt(unsigned int irq); void assign_target(unsigned int irq, unsigned int cpu); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson03/1/szediwy/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init(void); char uart_recv(void); void uart_send(char c); void uart_send_string(char *str); void putc(void *p, char c); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson03/1/szediwy/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson03/1/szediwy/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H // #define PBASE 0x3F000000 // So a peripheral described in this document as being at legacy address 0x7Enn_nnnn is available in the 35-bit address // space at 0x4_7Enn_nnnn, and visible to the ARM at 0x0_FEnn_nnnn if Low Peripheral mode is enabled. // 0x7E000000 (legacy) -> 0x4_7E00_0000 (35-bit) -> 0x0_FE00_0000 (low peripheral) #define PBASE 0xFE000000 // The base address of the GIC-400 is 0x4c0040000. Note that, unlike other peripheral addresses in this document, this is an // ARM-only address and not a legacy master address. If Low Peripheral mode is enabled this base address becomes // 0xff840000. // The GIC-400 is configured with "NUM_CPUS=4" and "NUM_SPIS=192". For full register details, please refer to the ARM // GIC-400 documentation on the ARM Developer website. #define GIC_BASE 0xFF840000 #define ARM_LOCAL_BASE 0xFF800000 // The ARMC register base address is 0x7e00b000 -> 0x4_7E00B000 -> 0x0FE00B000 #define ARMC_BASE 0x0FE00B000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson03/1/szediwy/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) // #define GPSET0 (PBASE+0x0020001C) // #define GPCLR0 (PBASE+0x00200028) // #define GPPUD (PBASE+0x00200094) // #define GPPUDCLK0 (PBASE+0x00200098) #define GPIO_PUP_PDN_CNTRL_REG0 (PBASE+0x002000E4) #define UART0_DR (PBASE+0x00201000) #define UART0_FR (PBASE+0x00201018) #define UART0_IBRD (PBASE+0x00201024) #define UART0_FBRD (PBASE+0x00201028) #define UART0_LCRH (PBASE+0x0020102C) #define UART0_CR (PBASE+0x00201030) #define UART0_IMSC (PBASE+0x00201038) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson03/1/szediwy/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define GIC_DIST_BASE (GIC_BASE+0x00001000) #define GIC_CPU_BASE (GIC_BASE+0x00002000) // For interrupt ID m, when DIV and MOD are the integer division and modulo operations: // • the corresponding GICD_ISENABLER number, n, is given by n = m DIV 32 // • the offset of the required GICD_ISENABLER is (0x100 + (4*n)) // • the bit number of the required Set-enable bit in this register is m MOD 32. // // VC Timer 1 = 1 => 97 / 32 = 3 => offset 0x10C // #define GIC_ENABLE_IRQ_BASE (GIC_DIST_BASE+0x00000100) #define GIC_ENABLE_IRQ_3 (GIC_DIST_BASE+0x0000010C) #define GICC_IAR (GIC_CPU_BASE+0x0000000C) #define GICC_EOIR (GIC_CPU_BASE+0x00000010) // For interrupt ID m, when DIV and MOD are the integer division and modulo operations: // • the corresponding GICD_ITARGETSRn number, n, is given by n = m DIV 4 // • the offset of the required GICD_ITARGETSR is (0x800 + (4*n)) // • the byte offset of the required Priority field in this register is m MOD 4, where: // — byte offset 0 refers to register bits [7:0] // — byte offset 1 refers to register bits [15:8] // — byte offset 2 refers to register bits [23:16] // — byte offset 3 refers to register bits [31:24]. #define GIC_IRQ_TARGET_BASE (GIC_DIST_BASE+0x00000800) // 97 / 4 => n = 24 // 0x800 + 4*24 // 97 % 4 => m = 1 // byte offset = 1<<8 #define GIC_IRQ_TARGET_24 (GIC_DIST_BASE+0x00000860) #define LOCAL_TIMER_IRQ (0x35) // Local timer IRQ #define SYSTEM_TIMER_IRQ_0 (0x60) // VC IRQ 0 #define SYSTEM_TIMER_IRQ_1 (0x61) // VC IRQ 1 #define SYSTEM_TIMER_IRQ_2 (0x62) // VC IRQ 2 #define SYSTEM_TIMER_IRQ_3 (0x63) // VC IRQ 3 #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson03/1/szediwy/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson03/1/szediwy/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) // Local Timer Configuration. // A free-running secondary timer is provided that can generate an interrupt each time it crosses zero. When it is // enabled, the timer is decremented on each edge (positive or negative) of the crystal reference clock. It is // automatically reloaded with the TIMER_TIMEOUT value when it reaches zero and then continues to decrement. // Routing of the timer interrupt is controlled by the PERI_IRQ_ROUTE0 register #define LOCAL_TIMER_CONTROL (ARM_LOCAL_BASE + 0x34) // Local Timer Interrupt Control #define LOCAL_TIMER_IRQ (ARM_LOCAL_BASE + 0x38) // 54 MHz #define LOCAL_TIMER_FREQ (54000000) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson03/1/szediwy/include/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #define BYTE_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c" #define BYTE_TO_BINARY(byte) \ (byte & 0x80 ? '1' : '0'), \ (byte & 0x40 ? '1' : '0'), \ (byte & 0x20 ? '1' : '0'), \ (byte & 0x10 ? '1' : '0'), \ (byte & 0x08 ? '1' : '0'), \ (byte & 0x04 ? '1' : '0'), \ (byte & 0x02 ? '1' : '0'), \ (byte & 0x01 ? '1' : '0') #define CPACR_EL1_MASK (0 << 28 | 3 << 20 | 3 << 16) #endif ================================================ FILE: exercises/lesson03/1/szediwy/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); void local_timer_init (void); void handle_local_timer_irq (void); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson03/1/szediwy/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay(unsigned long); extern void put32(unsigned long, unsigned int); extern unsigned int get32(unsigned long); extern unsigned int get_el(); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson03/1/szediwy/src/boot.S ================================================ #include "mm.h" #include "sysregs.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0, #0x3 cbz x0, init_bss /* If processor id is not 0 then pending lock processor * (wait for `sev` instruction) */ wfe b master proc_hang: b proc_hang init_bss: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero sev /***********************************************************************/ /* Enable the other cores link: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/arm64/booting.rst?h=v5.3#n255 The boot loader is expected to enter the kernel on each CPU in the following manner: - The primary CPU must jump directly to the first instruction of the kernel image. The device tree blob passed by this CPU must contain an 'enable-method' property for each cpu node. The supported enable-methods are described below. It is expected that the bootloader will generate these device tree properties and insert them into the blob prior to kernel entry. - CPUs with a "spin-table" enable-method must have a 'cpu-release-addr' property in their cpu node. This property identifies a naturally-aligned 64-bit zero-initalised memory location. These CPUs should spin outside of the kernel in a reserved area of memory (communicated to the kernel by a /memreserve/ region in the device tree) polling their cpu-release-addr location, which must be contained in the reserved region. A wfe instruction may be inserted to reduce the overhead of the busy-loop and a sev will be issued by the primary CPU. When a read of the location pointed to by the cpu-release-addr returns a non-zero value, the CPU must jump to this value. The value will be written as a single 64-bit little-endian value, so CPUs must convert the read value to their native endianness before jumping to it. - CPUs with a "psci" enable method should remain outside of the kernel (i.e. outside of the regions of memory described to the kernel in the memory node, or in a reserved area of memory described to the kernel by a /memreserve/ region in the device tree). The kernel will issue CPU_ON calls as described in ARM document number ARM DEN 0022A ("Power State Coordination Interface System Software on ARM processors") to bring CPUs into the kernel. The device tree should contain a 'psci' node, as described in Documentation/devicetree/bindings/arm/psci.yaml. - Secondary CPU general-purpose register settings x0 = 0 (reserved for future use) x1 = 0 (reserved for future use) x2 = 0 (reserved for future use) x3 = 0 (reserved for future use) */ /* cpu0: cpu@0 { device_type = "cpu"; compatible = "arm,cortex-a72"; reg = <0>; enable-method = "spin-table"; cpu-release-addr = <0x0 0x000000d8>; }; cpu1: cpu@1 { device_type = "cpu"; compatible = "arm,cortex-a72"; reg = <1>; enable-method = "spin-table"; cpu-release-addr = <0x0 0x000000e0>; }; cpu2: cpu@2 { device_type = "cpu"; compatible = "arm,cortex-a72"; reg = <2>; enable-method = "spin-table"; cpu-release-addr = <0x0 0x000000e8>; }; cpu3: cpu@3 { device_type = "cpu"; compatible = "arm,cortex-a72"; reg = <3>; enable-method = "spin-table"; cpu-release-addr = <0x0 0x000000f0>; }; */ /****************************************************/ mov x0, #0 adr x0, configure_el1 mov x1, #0xe0 str x0, [x1] mov x1, #0xe8 str x0, [x1] mov x1, #0xf0 str x0, [x1] configure_el1: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =CPACR_EL1_MASK msr cpacr_el1, x0 ldr x0, =SPSR_VALUE msr spsr_el2, x0 adr x0, master msr elr_el2, x0 eret master: mrs x0, mpidr_el1 and x0, x0, #0x3 mov x1, #SECTION_SIZE mul x1, x1, x0 add x1, x1, #LOW_MEMORY mov sp, x1 bl kernel_main b proc_hang ================================================ FILE: exercises/lesson03/1/szediwy/src/config.txt ================================================ arm_64bit=1 enable_uart=1 uart_2ndstage=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson03/1/szediwy/src/custom_printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "custom_printf.h" typedef void (*putcf)(void *, char); static putcf stdout_putf; static void *stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc, char *bf) { int n = 0; unsigned int d = 1; while (num / d >= base) d *= base; while (d != 0) { int dgt = num / d; num %= d; d /= base; if (n || dgt > 0 || d == 0) { *bf++ = dgt + (dgt < 10 ? '0' : (uc ? 'A' : 'a') - 10); ++n; } } *bf = 0; } static void li2a(long num, char *bf) { if (num < 0) { num = -num; *bf++ = '-'; } uli2a(num, 10, 0, bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc, char *bf) { int n = 0; unsigned int d = 1; while (num / d >= base) d *= base; while (d != 0) { int dgt = num / d; num %= d; d /= base; if (n || dgt > 0 || d == 0) { *bf++ = dgt + (dgt < 10 ? '0' : (uc ? 'A' : 'a') - 10); ++n; } } *bf = 0; } static void i2a(int num, char *bf) { if (num < 0) { num = -num; *bf++ = '-'; } ui2a(num, 10, 0, bf); } static int a2d(char ch) { if (ch >= '0' && ch <= '9') return ch - '0'; else if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10; else if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10; else return -1; } static char a2i(char ch, char **src, int base, int *nump) { char *p = *src; int num = 0; int digit; while ((digit = a2d(ch)) >= 0) { if (digit > base) break; num = num * base + digit; ch = *p++; } *src = p; *nump = num; return ch; } static void putchw(void *putp, putcf putf, int n, char z, char *bf) { char fc = z ? '0' : ' '; char ch; char *p = bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp, fc); while ((ch = *bf++)) putf(putp, ch); } void tfp_format(void *putp, putcf putf, char *fmt, va_list va) { char bf[12]; char ch; while ((ch = *(fmt++))) { if (ch != '%') { putf(putp, ch); } else { char lz = 0; #ifdef PRINTF_LONG_SUPPORT char lng = 0; #endif int w = 0; ch = *(fmt++); if (ch == '0') { ch = *(fmt++); lz = 1; } if (ch >= '0' && ch <= '9') { ch = a2i(ch, &fmt, 10, &w); } #ifdef PRINTF_LONG_SUPPORT if (ch == 'l') { ch = *(fmt++); lng = 1; } #endif switch (ch) { case 0: goto abort; case 'u': { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int), 10, 0, bf); else #endif ui2a(va_arg(va, unsigned int), 10, 0, bf); putchw(putp, putf, w, lz, bf); break; } case 'd': { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int), bf); else #endif i2a(va_arg(va, int), bf); putchw(putp, putf, w, lz, bf); break; } case 'x': case 'X': #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int), 16, (ch == 'X'), bf); else #endif ui2a(va_arg(va, unsigned int), 16, (ch == 'X'), bf); putchw(putp, putf, w, lz, bf); break; case 'c': putf(putp, (char)(va_arg(va, int))); break; case 's': putchw(putp, putf, w, 0, va_arg(va, char *)); break; case '%': putf(putp, ch); default: break; } } } abort:; } void init_printf(void *putp, void (*putf)(void *, char)) { stdout_putf = putf; stdout_putp = putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va, fmt); tfp_format(stdout_putp, stdout_putf, fmt, va); va_end(va); } static void putcp(void *p, char c) { *(*((char **)p))++ = c; } void tfp_sprintf(char *s, char *fmt, ...) { va_list va; va_start(va, fmt); tfp_format(&s, putcp, fmt, va); putcp(&s, 0); va_end(va); } ================================================ FILE: exercises/lesson03/1/szediwy/src/debug.c ================================================ #include "custom_printf.h" void d0(void) { printf("#0"); } void d1(void) { printf("#1"); } void d2(void) { printf("#2"); } void d3(void) { printf("#3"); } void d4(void) { printf("#4"); } ================================================ FILE: exercises/lesson03/1/szediwy/src/entry.S ================================================ #include "entry.h" .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] str x30, [sp, #16 * 15] .endm .macro kernel_exit ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] ldr x30, [sp, #16 * 15] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 ventry irq_invalid_el0_64 // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry ERROR_INVALID_EL1h sync_invalid_el0_64: handle_invalid_entry SYNC_INVALID_EL0_64 irq_invalid_el0_64: handle_invalid_entry IRQ_INVALID_EL0_64 fiq_invalid_el0_64: handle_invalid_entry FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry ERROR_INVALID_EL0_32 el1_irq: kernel_entry bl handle_irq kernel_exit .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson03/1/szediwy/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 ret .globl enable_irq enable_irq: msr daifclr, #0b0010 ret .globl disable_irq disable_irq: msr daifset, #0b0010 ret ================================================ FILE: exercises/lesson03/1/szediwy/src/irq.c ================================================ #include "utils.h" #include "custom_printf.h" #include "timer.h" #include "entry.h" #include "peripherals/irq.h" #include "sysregs.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32" }; void enable_interrupt(unsigned int irq) { // For interrupt ID m, when DIV and MOD are the integer division and modulo operations: // • the corresponding GICD_ISENABLER number, n, is given by n = m DIV 32 // • the offset of the required GICD_ISENABLER is (0x100 + (4*n)) // • the bit number of the required Set-enable bit in this register is m MOD 32. unsigned int n = irq / 32; unsigned int offset = irq % 32; unsigned int enableRegister = GIC_ENABLE_IRQ_BASE + (4*n); put32(enableRegister, 1 << offset); } void assign_target(unsigned int irq, unsigned int cpu) { // For interrupt ID m, when DIV and MOD are the integer division and modulo operations: // • the corresponding GICD_ITARGETSRn number, n, is given by n = m DIV 4 // • the offset of the required GICD_ITARGETSR is (0x800 + (4*n)) // • the byte offset of the required Priority field in this register is m MOD 4, where: // — byte offset 0 refers to register bits [7:0] // — byte offset 1 refers to register bits [15:8] // — byte offset 2 refers to register bits [23:16] // — byte offset 3 refers to register bits [31:24]. unsigned int n = irq / 4; unsigned int offset = irq % 4; unsigned int targetRegister = GIC_IRQ_TARGET_BASE + (4*n); // Currently we only enter the target CPU 0 put32(targetRegister, get32(targetRegister) | (1 << (offset*8))); } void enable_interrupt_controller() { assign_target(SYSTEM_TIMER_IRQ_1, 0); enable_interrupt(SYSTEM_TIMER_IRQ_1); assign_target(LOCAL_TIMER_IRQ, 0); enable_interrupt(LOCAL_TIMER_IRQ); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { // NOTE (ARM® Generic Interrupt Controller - Architecture version 2.0): // For compatibility with possible extensions to the GIC architecture specification, ARM recommends that software // preserves the entire register value read from the GICC_IAR when it acknowledges the interrupt, and uses that entire // value for its corresponding write to the GICC_EOIR. unsigned int irqAckRegister = get32(GICC_IAR); unsigned int irq = irqAckRegister & 0x2FF; // bit [9:0] -> interrupt ID switch (irq) { case (SYSTEM_TIMER_IRQ_1): handle_timer_irq(); put32(GICC_EOIR, irqAckRegister); break; case (LOCAL_TIMER_IRQ): handle_local_timer_irq(); put32(GICC_EOIR, irqAckRegister); break; default: printf("Unknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson03/1/szediwy/src/kernel.c ================================================ #include "custom_printf.h" #include "timer.h" #include "utils.h" #include "mini_uart.h" #include "irq.h" void kernel_main(unsigned long processor_index) { static unsigned int current_processor_index = 0; if (processor_index == 0) { uart_init(); init_printf(0, putc); printf("irq_vector_init\r\n"); irq_vector_init(); printf("timer_init\r\n"); timer_init(); printf("local_timer_init\r\n"); local_timer_init(); printf("enable_interrupt_controller\r\n"); enable_interrupt_controller(); printf("enable_irq\r\n"); enable_irq(); } while (processor_index != current_processor_index) ; int exception_level = get_el(); printf("{CPU: %d, Exception level: %d}\r\n", processor_index, exception_level); current_processor_index++; if (processor_index == 0) { // if current_processor_index == 4 then all processors send message while (current_processor_index != 4) ; for (;;) { uart_send(uart_recv()); } } } ================================================ FILE: exercises/lesson03/1/szediwy/src/linker.ld ================================================ SECTIONS { . = 0x80000; .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson03/1/szediwy/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send(char c) { while (get32(UART0_FR) & (1 << 5)) { } put32(UART0_DR, c); } char uart_recv(void) { while (get32(UART0_FR) & (1 << 4)) { } return (get32(UART0_DR) & 0xFF); } void uart_send_string(char *str) { for (int i = 0; str[i] != '\0'; i++) { uart_send((char)str[i]); } } void uart_init(void) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7 << 12); // clean gpio14 selector |= 4 << 12; // set alt0 for gpio14 selector &= ~(7 << 15); // clean gpio15 selector |= 4 << 15; // set alt0 for gpio15 put32(GPFSEL1, selector); unsigned int pullRegister; pullRegister = get32(GPIO_PUP_PDN_CNTRL_REG0); pullRegister &= ~(3 << 30); pullRegister &= ~(3 << 28); put32(GPIO_PUP_PDN_CNTRL_REG0, pullRegister); //first disable uart put32(UART0_CR, 0); put32(UART0_IMSC, 0); //from ../adkaster/src/uart.c // Assume 48MHz UART Reference Clock (Standard) // Calculate UART clock divider per datasheet // BAUDDIV = (FUARTCLK/(16 Baud rate)) // Note: We get 6 bits of fraction for the baud div // 48000000/(16 * 115200) = 3000000/115200 = 26.0416666... // Integer part = 26 :) // From http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0183g/I54603.html // we want floor(0.04166666.. * 64 + 0.5) = 3 put32(UART0_IBRD, 26); put32(UART0_FBRD, 3); //little endian: 0111|0000 => 8 bits and enable fifos put32(UART0_LCRH, 7 << 4); //little endian: 0011|0000|0001 => enable: rx tx uart put32(UART0_CR, (1 << 9) | (1 << 8) | (1 << 0)); } // This function is required by printf function void putc(void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson03/1/szediwy/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson03/1/szediwy/src/timer.c ================================================ #include "utils.h" #include "custom_printf.h" #include "peripherals/timer.h" const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); printf("Timer interrupt received\n\r"); } void handle_local_timer_irq( void ) { printf("LOCAL_TIMER_IRQ received\n\r"); put32(LOCAL_TIMER_IRQ, (3<<30)); } void local_timer_init (void) { // Enable the timer put32(LOCAL_TIMER_CONTROL, (3<<28) | (3*LOCAL_TIMER_FREQ)); } ================================================ FILE: exercises/lesson03/1/szediwy/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret ================================================ FILE: exercises/lesson03/1/xdfm1/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only -std=gnu99 ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel7.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel7.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel7.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel7.elf -O binary kernel7.img ================================================ FILE: exercises/lesson03/1/xdfm1/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson03/1/xdfm1/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson03/1/xdfm1/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #endif ================================================ FILE: exercises/lesson03/1/xdfm1/include/arm_timer.h ================================================ #ifndef _ARMTIMER_H #define _ARMTIMER_H void armtimer_init ( void ); void handle_armtimer_irq ( void ); #endif /*_ARMTIMER_H */ ================================================ FILE: exercises/lesson03/1/xdfm1/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 256 // size of all saved registers #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #endif ================================================ FILE: exercises/lesson03/1/xdfm1/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson03/1/xdfm1/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson03/1/xdfm1/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson03/1/xdfm1/include/peripherals/arm_timer.h ================================================ #ifndef _P_ARMTIMER_H #define _P_ARMTIMER_H #include "peripherals/base.h" // from section 14.2 of SoC doc #define ARMTIMER_BASE (PBASE+0x0000B000) #define ARMTIMER_LOAD (ARMTIMER_BASE+0x400) #define ARMTIMER_VAL (ARMTIMER_BASE+0x404) #define ARMTIMER_CTRL (ARMTIMER_BASE+0x408) #define ARMTIMER_CLR (ARMTIMER_BASE+0x40C) #define ARMTIMER_CTRL_23BIT (1 << 1) #define ARMTIMER_CTRL_PRESCALE256 (2 << 2) #define ARMTIMER_CTRL_ENABLE (1 << 7) #define ARMTIMER_CTRL_INT_ENABLE (1 << 5) #endif /*_P_ARMTIMER_H */ ================================================ FILE: exercises/lesson03/1/xdfm1/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson03/1/xdfm1/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson03/1/xdfm1/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #define ARMTIMER_IRQ (1 << 0) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson03/1/xdfm1/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson03/1/xdfm1/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson03/1/xdfm1/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson03/1/xdfm1/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson03/1/xdfm1/src/arm_timer.c ================================================ #include "utils.h" #include "printf.h" #include "peripherals/arm_timer.h" // counter value in load register const unsigned int interval = 0x800; void armtimer_init ( void ) { put32(ARMTIMER_LOAD, interval); put32(ARMTIMER_CTRL, ARMTIMER_CTRL_23BIT | ARMTIMER_CTRL_PRESCALE256 | //presclaer clk/256 ARMTIMER_CTRL_ENABLE | ARMTIMER_CTRL_INT_ENABLE); } void handle_armtimer_irq( void ) { put32(ARMTIMER_CLR, 1); printf("Timer iterrupt received\n\r"); } ================================================ FILE: exercises/lesson03/1/xdfm1/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson03/1/xdfm1/src/config.txt ================================================ arm_control=0x200 kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson03/1/xdfm1/src/entry.S ================================================ #include "entry.h" .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] str x30, [sp, #16 * 15] .endm .macro kernel_exit ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] ldr x30, [sp, #16 * 15] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 ventry irq_invalid_el0_64 // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry ERROR_INVALID_EL1h sync_invalid_el0_64: handle_invalid_entry SYNC_INVALID_EL0_64 irq_invalid_el0_64: handle_invalid_entry IRQ_INVALID_EL0_64 fiq_invalid_el0_64: handle_invalid_entry FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry ERROR_INVALID_EL0_32 el1_irq: kernel_entry bl handle_irq kernel_exit .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson03/1/xdfm1/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson03/1/xdfm1/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "arm_timer.h" #include "entry.h" #include "peripherals/irq.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32" }; void enable_interrupt_controller() { //put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1); put32(ENABLE_BASIC_IRQS, ARMTIMER_IRQ); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { unsigned int irq = get32(IRQ_BASIC_PENDING); switch (irq) { case (ARMTIMER_IRQ): handle_armtimer_irq(); break; default: printf("Inknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson03/1/xdfm1/src/kernel.c ================================================ #include "printf.h" #include "arm_timer.h" #include "irq.h" #include "mini_uart.h" void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); armtimer_init(); enable_interrupt_controller(); enable_irq(); while (1){ uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson03/1/xdfm1/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson03/1/xdfm1/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson03/1/xdfm1/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson03/1/xdfm1/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson03/1/xdfm1/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson03/2/H-4ND-H/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson03/2/H-4ND-H/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson03/2/H-4ND-H/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson03/2/H-4ND-H/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #endif ================================================ FILE: exercises/lesson03/2/H-4ND-H/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 256 // size of all saved registers #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #endif ================================================ FILE: exercises/lesson03/2/H-4ND-H/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson03/2/H-4ND-H/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); void handle_uart_irq( void ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson03/2/H-4ND-H/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson03/2/H-4ND-H/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson03/2/H-4ND-H/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson03/2/H-4ND-H/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #define AUX_IRQ (1 << 29) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson03/2/H-4ND-H/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson03/2/H-4ND-H/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson03/2/H-4ND-H/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson03/2/H-4ND-H/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson03/2/H-4ND-H/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson03/2/H-4ND-H/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson03/2/H-4ND-H/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson03/2/H-4ND-H/src/entry.S ================================================ #include "entry.h" .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] str x30, [sp, #16 * 15] .endm .macro kernel_exit ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] ldr x30, [sp, #16 * 15] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 ventry irq_invalid_el0_64 // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry ERROR_INVALID_EL1h sync_invalid_el0_64: handle_invalid_entry SYNC_INVALID_EL0_64 irq_invalid_el0_64: handle_invalid_entry IRQ_INVALID_EL0_64 fiq_invalid_el0_64: handle_invalid_entry FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry ERROR_INVALID_EL0_32 el1_irq: kernel_entry bl handle_irq kernel_exit .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson03/2/H-4ND-H/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson03/2/H-4ND-H/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "mini_uart.h" #include "timer.h" #include "entry.h" #include "peripherals/irq.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32" }; void enable_interrupt_controller() { put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1 | AUX_IRQ); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { unsigned int irq = get32(IRQ_PENDING_1); if(irq & AUX_IRQ) { handle_uart_irq(); irq &= ~AUX_IRQ; } if(irq & SYSTEM_TIMER_IRQ_1) { handle_timer_irq(); irq &= ~SYSTEM_TIMER_IRQ_1; } else if(irq) { printf("Unknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson03/2/H-4ND-H/src/kernel.c ================================================ #include "printf.h" #include "timer.h" #include "irq.h" #include "mini_uart.h" void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); while (1){ //uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson03/2/H-4ND-H/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson03/2/H-4ND-H/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" #define BUFFER_SIZE 32 static char buffer[BUFFER_SIZE]; static unsigned int wr_buffer_index = 0; static unsigned int rd_buffer_index = 0; void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,3); //Enable receive interrupt and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } void handle_uart_irq() { unsigned int id = get32(AUX_MU_IIR_REG); if((id & 0x06) == 0x04) { while(get32(AUX_MU_LSR_REG)&0x01) { buffer[wr_buffer_index++] = get32(AUX_MU_IO_REG)&0xFF; if(wr_buffer_index == BUFFER_SIZE) wr_buffer_index = 0; } } if((id & 0x06) == 0x02) { while(get32(AUX_MU_LSR_REG)&0x20) { if(rd_buffer_index == wr_buffer_index) break; char c = buffer[rd_buffer_index++]; put32(AUX_MU_IO_REG,c); if(rd_buffer_index == BUFFER_SIZE) rd_buffer_index = 0; } } } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson03/2/H-4ND-H/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson03/2/H-4ND-H/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson03/2/H-4ND-H/src/timer.c ================================================ #include "utils.h" #include "printf.h" #include "peripherals/timer.h" const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); printf("Timer interrupt received\n\r"); } ================================================ FILE: exercises/lesson03/2/H-4ND-H/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson03/2/avenito/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson03/2/avenito/README.md ================================================ # Getting information about the hardware 1. Looking at the [BCM2837 ARM Peripherals manual](https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf), we figure out how to use the Mini_UART interrupt. # Steps to setup the receive interrupts 1. Page 12. AUX_MU_IER_REG to enable receive interrupts. Changes in the "uart_init ( void )" 1. Enable uart interrupt on "enable_interrupt_controller()". 1. Page 9. Look at AUX_IRQ Bit 0 for mini UART interrupt pending in "handle_irq(void)". 1. Create interrupt handler: handle_uart_irq(). ================================================ FILE: exercises/lesson03/2/avenito/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson03/2/avenito/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson03/2/avenito/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #endif ================================================ FILE: exercises/lesson03/2/avenito/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 256 // size of all saved registers #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #endif ================================================ FILE: exercises/lesson03/2/avenito/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson03/2/avenito/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); void handle_uart_irq( void ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson03/2/avenito/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson03/2/avenito/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson03/2/avenito/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson03/2/avenito/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) // Page 9. Enable mini UART interrupt #define miniUART_IRQ (1 << 0) // Page 113. Enable AUx int #define en_AUX_INT (1 << 29) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson03/2/avenito/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) // Page 9 - BCM2837 ARM Peripherals manual #define AUX_IRQ (PBASE+0x00215000) // Page 12 - BCM2837 ARM Peripherals manual #define ENABLE_MU_REC_INT (0xFD) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson03/2/avenito/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson03/2/avenito/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson03/2/avenito/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson03/2/avenito/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson03/2/avenito/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson03/2/avenito/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson03/2/avenito/src/entry.S ================================================ #include "entry.h" .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] str x30, [sp, #16 * 15] .endm .macro kernel_exit ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] ldr x30, [sp, #16 * 15] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 ventry irq_invalid_el0_64 // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry ERROR_INVALID_EL1h sync_invalid_el0_64: handle_invalid_entry SYNC_INVALID_EL0_64 irq_invalid_el0_64: handle_invalid_entry IRQ_INVALID_EL0_64 fiq_invalid_el0_64: handle_invalid_entry FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry ERROR_INVALID_EL0_32 el1_irq: kernel_entry bl handle_irq kernel_exit .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson03/2/avenito/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson03/2/avenito/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "timer.h" #include "entry.h" #include "mini_uart.h" #include "peripherals/mini_uart.h" #include "peripherals/irq.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32" }; void enable_interrupt_controller() { put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1 | en_AUX_INT); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { unsigned int irq1 = get32(IRQ_PENDING_1); unsigned int irq2 = get32(AUX_IRQ); if (irq1 & SYSTEM_TIMER_IRQ_1) handle_timer_irq(); if (irq2 & miniUART_IRQ) handle_uart_irq(); } ================================================ FILE: exercises/lesson03/2/avenito/src/kernel.c ================================================ #include "printf.h" #include "timer.h" #include "irq.h" #include "mini_uart.h" void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); while (1){ } } ================================================ FILE: exercises/lesson03/2/avenito/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson03/2/avenito/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); // enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); // disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG, ENABLE_MU_REC_INT); // enable receive interrupts put32(AUX_MU_LCR_REG,3); // enable 8 bit mode put32(AUX_MU_MCR_REG,0); // set RTS line to be always high put32(AUX_MU_BAUD_REG,270); // set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); // finally, enable transmitter and receiver } void handle_uart_irq( void ) { char c = uart_recv(); uart_send_string("mini UART interrupt received. Received: "); uart_send(c); uart_send_string("\n\r"); } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson03/2/avenito/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson03/2/avenito/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson03/2/avenito/src/timer.c ================================================ #include "utils.h" #include "printf.h" #include "peripherals/timer.h" const unsigned int interval = 1600000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); printf("Timer interrupt received\n\r"); } ================================================ FILE: exercises/lesson03/2/avenito/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson03/2/bl4ckout31/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson03/2/bl4ckout31/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson03/2/bl4ckout31/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson03/2/bl4ckout31/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #endif ================================================ FILE: exercises/lesson03/2/bl4ckout31/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 256 // size of all saved registers #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #endif ================================================ FILE: exercises/lesson03/2/bl4ckout31/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson03/2/bl4ckout31/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); void handle_uart_irq ( void ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson03/2/bl4ckout31/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson03/2/bl4ckout31/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson03/2/bl4ckout31/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson03/2/bl4ckout31/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) // See BCM2537 ARM Peripherals p113 at // https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf #define AUX_IRQ (1 << 29) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson03/2/bl4ckout31/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_IRQ_REG (PBASE+0x00215000) #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) // See BCM2537 ARM Peripherals p12 at // https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf #define IER_REG_EN_REC_INT (1 << 0) #define IER_REG_INT (3 << 2) // Must be set to receive interrupts #define IER_REG_VALUE (IER_REG_EN_REC_INT | IER_REG_INT) #define IIR_REG_REC_NON_EMPTY (2 << 1) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson03/2/bl4ckout31/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson03/2/bl4ckout31/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson03/2/bl4ckout31/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson03/2/bl4ckout31/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson03/2/bl4ckout31/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson03/2/bl4ckout31/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson03/2/bl4ckout31/src/entry.S ================================================ #include "entry.h" .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] str x30, [sp, #16 * 15] .endm .macro kernel_exit ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] ldr x30, [sp, #16 * 15] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 ventry irq_invalid_el0_64 // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry ERROR_INVALID_EL1h sync_invalid_el0_64: handle_invalid_entry SYNC_INVALID_EL0_64 irq_invalid_el0_64: handle_invalid_entry IRQ_INVALID_EL0_64 fiq_invalid_el0_64: handle_invalid_entry FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry ERROR_INVALID_EL0_32 el1_irq: kernel_entry bl handle_irq kernel_exit .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson03/2/bl4ckout31/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson03/2/bl4ckout31/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "timer.h" #include "mini_uart.h" #include "entry.h" #include "peripherals/irq.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32" }; void enable_interrupt_controller() { put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1 | AUX_IRQ); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { unsigned int irq = get32(IRQ_PENDING_1); // loop in case multiple interrupts have been raised while (irq) { // each bitflag is only 1 bit so we do not // need to test equality against themself if (irq & SYSTEM_TIMER_IRQ_1) { handle_timer_irq(); irq &= ~SYSTEM_TIMER_IRQ_1; } else if (irq & AUX_IRQ) { handle_uart_irq(); irq &= ~AUX_IRQ; } else { printf("Unknown pending irq: %x\r\n", irq); irq = 0; } } } ================================================ FILE: exercises/lesson03/2/bl4ckout31/src/kernel.c ================================================ #include "printf.h" #include "timer.h" #include "irq.h" #include "mini_uart.h" void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); while (1){ //uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson03/2/bl4ckout31/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson03/2/bl4ckout31/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG, IER_REG_VALUE); //Enable receive interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } void handle_uart_irq( void ) { // There may be more than one byte in the FIFO. while((get32(AUX_MU_IIR_REG) & IIR_REG_REC_NON_EMPTY) == IIR_REG_REC_NON_EMPTY) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson03/2/bl4ckout31/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson03/2/bl4ckout31/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson03/2/bl4ckout31/src/timer.c ================================================ #include "utils.h" #include "printf.h" #include "peripherals/timer.h" const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); printf("Timer interrupt received\n\r"); } ================================================ FILE: exercises/lesson03/2/bl4ckout31/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson03/2/bl4ckout31/start.sh ================================================ #!/bin/bash if [ $# -ne 1 ]; then echo "usage: $0 kernel" exit 1 fi mount -L rpiboot /mnt cp "$1" /mnt umount /mnt ================================================ FILE: exercises/lesson03/2/rs/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel7.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel7.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel7.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel7.elf -O binary kernel7.img ================================================ FILE: exercises/lesson03/2/rs/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson03/2/rs/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson03/2/rs/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of // AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED \ (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | \ SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of // AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of // AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of // AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #endif ================================================ FILE: exercises/lesson03/2/rs/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 256 // size of all saved registers #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #endif ================================================ FILE: exercises/lesson03/2/rs/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller(void); void irq_vector_init(void); void enable_irq(void); void disable_irq(void); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson03/2/rs/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init(void); char uart_recv(void); void uart_send(char c); void putc(void *p, char c); void handle_uart_irq(void); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson03/2/rs/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson03/2/rs/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson03/2/rs/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE + 0x00200004) #define GPSET0 (PBASE + 0x0020001C) #define GPCLR0 (PBASE + 0x00200028) #define GPPUD (PBASE + 0x00200094) #define GPPUDCLK0 (PBASE + 0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson03/2/rs/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE + 0x0000B200) #define IRQ_PENDING_1 (PBASE + 0x0000B204) #define IRQ_PENDING_2 (PBASE + 0x0000B208) #define FIQ_CONTROL (PBASE + 0x0000B20C) #define ENABLE_IRQS_1 (PBASE + 0x0000B210) #define ENABLE_IRQS_2 (PBASE + 0x0000B214) #define ENABLE_BASIC_IRQS (PBASE + 0x0000B218) #define DISABLE_IRQS_1 (PBASE + 0x0000B21C) #define DISABLE_IRQS_2 (PBASE + 0x0000B220) #define DISABLE_BASIC_IRQS (PBASE + 0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #define AUX_IRQ (1 << 29) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson03/2/rs/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE + 0x00215004) #define AUX_MU_IO_REG (PBASE + 0x00215040) #define AUX_MU_IER_REG (PBASE + 0x00215044) #define AUX_MU_IIR_REG (PBASE + 0x00215048) #define AUX_MU_LCR_REG (PBASE + 0x0021504C) #define AUX_MU_MCR_REG (PBASE + 0x00215050) #define AUX_MU_LSR_REG (PBASE + 0x00215054) #define AUX_MU_MSR_REG (PBASE + 0x00215058) #define AUX_MU_SCRATCH (PBASE + 0x0021505C) #define AUX_MU_CNTL_REG (PBASE + 0x00215060) #define AUX_MU_STAT_REG (PBASE + 0x00215064) #define AUX_MU_BAUD_REG (PBASE + 0x00215068) #define MU_IER_RXEN (1 << 0) // Enable receive interrupts. #define MU_IER_TXEN (1 << 1) // Enable transmit interrupts. #define MU_IER_IGNORED (0x1f << 2) // Ignored but must be set to 1 #define MU_IIR_RX_READY (2 << 1) // Receiver holds valid byte #define MU_IIR_RX_CLR (1 << 1) // Clear receiver FIFO #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson03/2/rs/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE + 0x00003000) #define TIMER_CLO (PBASE + 0x00003004) #define TIMER_CHI (PBASE + 0x00003008) #define TIMER_C0 (PBASE + 0x0000300C) #define TIMER_C1 (PBASE + 0x00003010) #define TIMER_C2 (PBASE + 0x00003014) #define TIMER_C3 (PBASE + 0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson03/2/rs/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void *putp, void (*putf)(void *, char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char *s, char *fmt, ...); void tfp_format(void *putp, void (*putf)(void *, char), char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson03/2/rs/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init(void); void handle_timer_irq(void); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson03/2/rs/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay(unsigned long); extern void put32(unsigned long, unsigned int); extern unsigned int get32(unsigned long); extern int get_el(void); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson03/2/rs/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson03/2/rs/src/config.txt ================================================ arm_control=0x200 kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson03/2/rs/src/entry.S ================================================ #include "entry.h" .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] str x30, [sp, #16 * 15] .endm .macro kernel_exit ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] ldr x30, [sp, #16 * 15] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 ventry irq_invalid_el0_64 // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry ERROR_INVALID_EL1h sync_invalid_el0_64: handle_invalid_entry SYNC_INVALID_EL0_64 irq_invalid_el0_64: handle_invalid_entry IRQ_INVALID_EL0_64 fiq_invalid_el0_64: handle_invalid_entry FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry ERROR_INVALID_EL0_32 el1_irq: kernel_entry bl handle_irq kernel_exit .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson03/2/rs/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson03/2/rs/src/irq.c ================================================ #include "peripherals/irq.h" #include "entry.h" #include "printf.h" #include "timer.h" #include "utils.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32"}; void enable_interrupt_controller() { put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1); put32(ENABLE_IRQS_1, AUX_IRQ); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { unsigned int irq = get32(IRQ_PENDING_1); switch (irq) { case (SYSTEM_TIMER_IRQ_1): handle_timer_irq(); break; case (AUX_IRQ): handle_uart_irq(); break; default: printf("Unknown pending irq1: %x\r\n", irq); } } ================================================ FILE: exercises/lesson03/2/rs/src/kernel.c ================================================ #include "irq.h" #include "mini_uart.h" #include "printf.h" #include "timer.h" void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); while (1) { } } ================================================ FILE: exercises/lesson03/2/rs/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson03/2/rs/src/mini_uart.c ================================================ #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" #include "printf.h" #include "utils.h" void uart_send(char c) { while (1) { if (get32(AUX_MU_LSR_REG) & 0x20) break; } put32(AUX_MU_IO_REG, c); } char uart_recv(void) { while (1) { if (get32(AUX_MU_LSR_REG) & 0x01) break; } return (get32(AUX_MU_IO_REG) & 0xFF); } void uart_send_string(char *str) { for (int i = 0; str[i] != '\0'; i++) { uart_send((char)str[i]); } } void handle_uart_irq(void) { if (get32(AUX_MU_IIR_REG) & MU_IIR_RX_READY) { char c = get32(AUX_MU_IO_REG) & 0xFF; put32(AUX_MU_IIR_REG, MU_IIR_RX_CLR); uart_send(c); } } void uart_init(void) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7 << 12); // clean gpio14 selector |= 2 << 12; // set alt5 for gpio14 selector &= ~(7 << 15); // clean gpio15 selector |= 2 << 15; // set alt5 for gpio15 put32(GPFSEL1, selector); put32(GPPUD, 0); delay(150); put32(GPPUDCLK0, (1 << 14) | (1 << 15)); delay(150); put32(GPPUDCLK0, 0); put32(AUX_ENABLES, 1); // Enable mini uart // (this also enables access to its registers) put32(AUX_MU_CNTL_REG, 0); // Disable auto flow control and disable receiver // and transmitter (for now) put32(AUX_MU_IER_REG, MU_IER_RXEN); // Enable receive interrupts put32(AUX_MU_LCR_REG, 3); // Enable 8 bit mode put32(AUX_MU_MCR_REG, 0); // Set RTS line to be always high put32(AUX_MU_BAUD_REG, 270); // Set baud rate to 115200 put32(AUX_MU_CNTL_REG, 3); // Finally, enable transmitter and receiver } // This function is required by printf function void putc(void *p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson03/2/rs/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson03/2/rs/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf)(void *, char); static putcf stdout_putf; static void *stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc, char *bf) { int n = 0; unsigned int d = 1; while (num / d >= base) d *= base; while (d != 0) { int dgt = num / d; num %= d; d /= base; if (n || dgt > 0 || d == 0) { *bf++ = dgt + (dgt < 10 ? '0' : (uc ? 'A' : 'a') - 10); ++n; } } *bf = 0; } static void li2a(long num, char *bf) { if (num < 0) { num = -num; *bf++ = '-'; } uli2a(num, 10, 0, bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc, char *bf) { int n = 0; unsigned int d = 1; while (num / d >= base) d *= base; while (d != 0) { int dgt = num / d; num %= d; d /= base; if (n || dgt > 0 || d == 0) { *bf++ = dgt + (dgt < 10 ? '0' : (uc ? 'A' : 'a') - 10); ++n; } } *bf = 0; } static void i2a(int num, char *bf) { if (num < 0) { num = -num; *bf++ = '-'; } ui2a(num, 10, 0, bf); } static int a2d(char ch) { if (ch >= '0' && ch <= '9') return ch - '0'; else if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10; else if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10; else return -1; } static char a2i(char ch, char **src, int base, int *nump) { char *p = *src; int num = 0; int digit; while ((digit = a2d(ch)) >= 0) { if (digit > base) break; num = num * base + digit; ch = *p++; } *src = p; *nump = num; return ch; } static void putchw(void *putp, putcf putf, int n, char z, char *bf) { char fc = z ? '0' : ' '; char ch; char *p = bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp, fc); while ((ch = *bf++)) putf(putp, ch); } void tfp_format(void *putp, putcf putf, char *fmt, va_list va) { char bf[12]; char ch; while ((ch = *(fmt++))) { if (ch != '%') putf(putp, ch); else { char lz = 0; #ifdef PRINTF_LONG_SUPPORT char lng = 0; #endif int w = 0; ch = *(fmt++); if (ch == '0') { ch = *(fmt++); lz = 1; } if (ch >= '0' && ch <= '9') { ch = a2i(ch, &fmt, 10, &w); } #ifdef PRINTF_LONG_SUPPORT if (ch == 'l') { ch = *(fmt++); lng = 1; } #endif switch (ch) { case 0: goto abort; case 'u': { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int), 10, 0, bf); else #endif ui2a(va_arg(va, unsigned int), 10, 0, bf); putchw(putp, putf, w, lz, bf); break; } case 'd': { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int), bf); else #endif i2a(va_arg(va, int), bf); putchw(putp, putf, w, lz, bf); break; } case 'x': case 'X': #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int), 16, (ch == 'X'), bf); else #endif ui2a(va_arg(va, unsigned int), 16, (ch == 'X'), bf); putchw(putp, putf, w, lz, bf); break; case 'c': putf(putp, (char)(va_arg(va, int))); break; case 's': putchw(putp, putf, w, 0, va_arg(va, char *)); break; case '%': putf(putp, ch); default: break; } } } abort:; } void init_printf(void *putp, void (*putf)(void *, char)) { stdout_putf = putf; stdout_putp = putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va, fmt); tfp_format(stdout_putp, stdout_putf, fmt, va); va_end(va); } static void putcp(void *p, char c) { *(*((char **)p))++ = c; } void tfp_sprintf(char *s, char *fmt, ...) { va_list va; va_start(va, fmt); tfp_format(&s, putcp, fmt, va); putcp(&s, 0); va_end(va); } ================================================ FILE: exercises/lesson03/2/rs/src/timer.c ================================================ #include "peripherals/timer.h" #include "printf.h" #include "utils.h" const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init(void) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq(void) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); printf("Timer interrupt received\n\r"); } ================================================ FILE: exercises/lesson03/2/rs/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson03/2/szediwy/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src .PHONY: clean all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img @echo "clean: [SUCCESS]" $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson03/2/szediwy/README.md ================================================ # Readme Solved the issue for the Raspberry Pi 4B. The solution has both timer active. The system timer 1 and the local timer. ================================================ FILE: exercises/lesson03/2/szediwy/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson03/2/szediwy/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson03/2/szediwy/include/custom_printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); void test (char c); #define printf tfp_printf #define sprintf tfp_sprintf #define BYTE_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c" #define BYTE_TO_BINARY(byte) \ (byte & 0x80 ? '1' : '0'), \ (byte & 0x40 ? '1' : '0'), \ (byte & 0x20 ? '1' : '0'), \ (byte & 0x10 ? '1' : '0'), \ (byte & 0x08 ? '1' : '0'), \ (byte & 0x04 ? '1' : '0'), \ (byte & 0x02 ? '1' : '0'), \ (byte & 0x01 ? '1' : '0') #endif ================================================ FILE: exercises/lesson03/2/szediwy/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 256 // size of all saved registers #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #endif ================================================ FILE: exercises/lesson03/2/szediwy/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void enable_interrupt(unsigned int irq); void assign_target(unsigned int irq, unsigned int cpu); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson03/2/szediwy/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init(void); char uart_recv(void); void uart_send(char c); void uart_send_string(char *str); void putc(void *p, char c); void handle_uart_interrupt(void ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson03/2/szediwy/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson03/2/szediwy/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H // #define PBASE 0x3F000000 // So a peripheral described in this document as being at legacy address 0x7Enn_nnnn is available in the 35-bit address // space at 0x4_7Enn_nnnn, and visible to the ARM at 0x0_FEnn_nnnn if Low Peripheral mode is enabled. // 0x7E000000 (legacy) -> 0x4_7E00_0000 (35-bit) -> 0x0_FE00_0000 (low peripheral) #define PBASE 0xFE000000 // The base address of the GIC-400 is 0x4c0040000. Note that, unlike other peripheral addresses in this document, this is an // ARM-only address and not a legacy master address. If Low Peripheral mode is enabled this base address becomes // 0xff840000. // The GIC-400 is configured with "NUM_CPUS=4" and "NUM_SPIS=192". For full register details, please refer to the ARM // GIC-400 documentation on the ARM Developer website. #define GIC_BASE 0xFF840000 #define ARM_LOCAL_BASE 0xFF800000 // The ARMC register base address is 0x7e00b000 -> 0x4_7E00B000 -> 0x0FE00B000 #define ARMC_BASE 0x0FE00B000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson03/2/szediwy/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) // #define GPSET0 (PBASE+0x0020001C) // #define GPCLR0 (PBASE+0x00200028) // #define GPPUD (PBASE+0x00200094) // #define GPPUDCLK0 (PBASE+0x00200098) #define GPIO_PUP_PDN_CNTRL_REG0 (PBASE+0x002000E4) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson03/2/szediwy/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define GIC_DIST_BASE (GIC_BASE+0x00001000) #define GIC_CPU_BASE (GIC_BASE+0x00002000) // For interrupt ID m, when DIV and MOD are the integer division and modulo operations: // • the corresponding GICD_ISENABLER number, n, is given by n = m DIV 32 // • the offset of the required GICD_ISENABLER is (0x100 + (4*n)) // • the bit number of the required Set-enable bit in this register is m MOD 32. // // VC Timer 1 = 1 => 97 / 32 = 3 => offset 0x10C // #define GIC_ENABLE_IRQ_BASE (GIC_DIST_BASE+0x00000100) #define GIC_ENABLE_IRQ_3 (GIC_DIST_BASE+0x0000010C) #define GICC_IAR (GIC_CPU_BASE+0x0000000C) #define GICC_EOIR (GIC_CPU_BASE+0x00000010) // For interrupt ID m, when DIV and MOD are the integer division and modulo operations: // • the corresponding GICD_ITARGETSRn number, n, is given by n = m DIV 4 // • the offset of the required GICD_ITARGETSR is (0x800 + (4*n)) // • the byte offset of the required Priority field in this register is m MOD 4, where: // — byte offset 0 refers to register bits [7:0] // — byte offset 1 refers to register bits [15:8] // — byte offset 2 refers to register bits [23:16] // — byte offset 3 refers to register bits [31:24]. #define GIC_IRQ_TARGET_BASE (GIC_DIST_BASE+0x00000800) // 97 / 4 => n = 24 // 0x800 + 4*24 // 97 % 4 => m = 1 // byte offset = 1<<8 #define GIC_IRQ_TARGET_24 (GIC_DIST_BASE+0x00000860) #define LOCAL_TIMER_IRQ (0x35) // Local timer IRQ #define SYSTEM_TIMER_IRQ_0 (0x60) // VC IRQ 0 #define SYSTEM_TIMER_IRQ_1 (0x61) // VC IRQ 1 #define SYSTEM_TIMER_IRQ_2 (0x62) // VC IRQ 2 #define SYSTEM_TIMER_IRQ_3 (0x63) // VC IRQ 3 // #define UART_IRQ (0x99) // VC IRQ 57 #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson03/2/szediwy/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE + 0x00215004) #define AUX_MU_IO_REG (PBASE + 0x00215040) #define AUX_MU_IER_REG (PBASE + 0x00215044) #define AUX_MU_IIR_REG (PBASE + 0x00215048) #define AUX_MU_LCR_REG (PBASE + 0x0021504C) #define AUX_MU_MCR_REG (PBASE + 0x00215050) #define AUX_MU_LSR_REG (PBASE + 0x00215054) #define AUX_MU_MSR_REG (PBASE + 0x00215058) #define AUX_MU_SCRATCH (PBASE + 0x0021505C) #define AUX_MU_CNTL_REG (PBASE + 0x00215060) #define AUX_MU_STAT_REG (PBASE + 0x00215064) #define AUX_MU_BAUD_REG (PBASE + 0x00215068) #define PACTL_CS_REG (PBASE + 0x00204E00) #define UART0_BASE (PBASE + 0x00201000) #define UART2_BASE (PBASE + 0x00201400) #define UART3_BASE (PBASE + 0x00201600) #define UART4_BASE (PBASE + 0x00201800) #define UART5_BASE (PBASE + 0x00201a00) #define UART_DR_OFFSET 0x0 #define UART_FR_OFFSET 0x18 #define UART_IBRD_OFFSET 0x24 #define UART_FBRD_OFFSET 0x28 #define UART_LCRH_OFFSET 0x2C #define UART_CR_OFFSET 0x30 // The UART_IFLS Register is the interrupt FIFO level select register. // You can use this register to define the FIFO level that triggers the // assertion of the combined interrupt signal. // // The interrupts are generated based on a transition through a level rather // than being based on the level. That is, the interrupts are generated when // the fill level progresses through the trigger level. The bits are reset // so that the trigger level is when the FIFOs are at the half-way mark. #define UART_IFLS_OFFSET 0x34 // Setting the appropriate mask bit HIGH enables the interrupt. #define UART_IMSC_OFFSET 0x38 // Masked Interrupt Status Register // The UARTMIS Register is the masked interrupt status register. It is a read-only register. // This register returns the current masked status value of the corresponding interrupt. A // write has no effect. // All the bits except for the modem status interrupt bits (bits 3 to 0) are cleared to 0 when // reset. The modem status interrupt bits are undefined after reset. Table 3-16 lists the // register bit assignments. #define UART_MIS_OFFSET 0x40 // The UART_ICR Register is the interrupt clear register. #define UART_ICR_OFFSET 0x44 // The receive interrupt changes state when one of the following events occurs: // • If the FIFOs are enabled and the receive FIFO reaches the programmed trigger // level. When this happens, the receive interrupt is asserted HIGH. The receive // interrupt is cleared by reading data from the receive FIFO until it becomes less // than the trigger level, or by clearing the interrupt. // • If the FIFOs are disabled (have a depth of one location) and data is received // thereby filling the location, the receive interrupt is asserted HIGH. The receive // interrupt is cleared by performing a single read of the receive FIFO, or by clearing // the interrupt. #define UART_IRQ_RXIM_MASK (1 << 4) // The receive timeout interrupt is asserted when the receive FIFO is not empty, and no // more data is received during a 32-bit period. The receive timeout interrupt is cleared // either when the FIFO becomes empty through reading all the data (or by reading the // holding register), or when a 1 is written to the corresponding bit of the Interrupt Clear // Register, UARTICR on page 3-21. // TODO: 32-bit period - 32 cycles of UART clock? #define UART_IRQ_RTIM_MASK (1 << 6) #define UART_RX_MASK (1 << 4) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson03/2/szediwy/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) // Local Timer Configuration. // A free-running secondary timer is provided that can generate an interrupt each time it crosses zero. When it is // enabled, the timer is decremented on each edge (positive or negative) of the crystal reference clock. It is // automatically reloaded with the TIMER_TIMEOUT value when it reaches zero and then continues to decrement. // Routing of the timer interrupt is controlled by the PERI_IRQ_ROUTE0 register #define LOCAL_TIMER_CONTROL (ARM_LOCAL_BASE + 0x34) // Local Timer Interrupt Control #define LOCAL_TIMER_IRQ (ARM_LOCAL_BASE + 0x38) // 54 MHz #define LOCAL_TIMER_FREQ (54000000) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson03/2/szediwy/include/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #define CPACR_EL1_MASK (0 << 28 | 3 << 20 | 3 << 16) #endif ================================================ FILE: exercises/lesson03/2/szediwy/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); void local_timer_init (void); void handle_local_timer_irq (void); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson03/2/szediwy/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay(unsigned long); extern void put32(unsigned long, unsigned int); extern unsigned int get32(unsigned long); extern unsigned int get_el(); #endif /*_BOOT_H */ ================================================ FILE: exercises/lesson03/2/szediwy/src/boot.S ================================================ #include "mm.h" #include "sysregs.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0, #0x3 cbz x0, init_bss /* If processor id is not 0 then pending lock processor * (wait for `sev` instruction) */ wfe b master proc_hang: b proc_hang init_bss: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero sev /***********************************************************************/ /* Enable the other cores link: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/arm64/booting.rst?h=v5.3#n255 The boot loader is expected to enter the kernel on each CPU in the following manner: - The primary CPU must jump directly to the first instruction of the kernel image. The device tree blob passed by this CPU must contain an 'enable-method' property for each cpu node. The supported enable-methods are described below. It is expected that the bootloader will generate these device tree properties and insert them into the blob prior to kernel entry. - CPUs with a "spin-table" enable-method must have a 'cpu-release-addr' property in their cpu node. This property identifies a naturally-aligned 64-bit zero-initalised memory location. These CPUs should spin outside of the kernel in a reserved area of memory (communicated to the kernel by a /memreserve/ region in the device tree) polling their cpu-release-addr location, which must be contained in the reserved region. A wfe instruction may be inserted to reduce the overhead of the busy-loop and a sev will be issued by the primary CPU. When a read of the location pointed to by the cpu-release-addr returns a non-zero value, the CPU must jump to this value. The value will be written as a single 64-bit little-endian value, so CPUs must convert the read value to their native endianness before jumping to it. - CPUs with a "psci" enable method should remain outside of the kernel (i.e. outside of the regions of memory described to the kernel in the memory node, or in a reserved area of memory described to the kernel by a /memreserve/ region in the device tree). The kernel will issue CPU_ON calls as described in ARM document number ARM DEN 0022A ("Power State Coordination Interface System Software on ARM processors") to bring CPUs into the kernel. The device tree should contain a 'psci' node, as described in Documentation/devicetree/bindings/arm/psci.yaml. - Secondary CPU general-purpose register settings x0 = 0 (reserved for future use) x1 = 0 (reserved for future use) x2 = 0 (reserved for future use) x3 = 0 (reserved for future use) */ /* cpu0: cpu@0 { device_type = "cpu"; compatible = "arm,cortex-a72"; reg = <0>; enable-method = "spin-table"; cpu-release-addr = <0x0 0x000000d8>; }; cpu1: cpu@1 { device_type = "cpu"; compatible = "arm,cortex-a72"; reg = <1>; enable-method = "spin-table"; cpu-release-addr = <0x0 0x000000e0>; }; cpu2: cpu@2 { device_type = "cpu"; compatible = "arm,cortex-a72"; reg = <2>; enable-method = "spin-table"; cpu-release-addr = <0x0 0x000000e8>; }; cpu3: cpu@3 { device_type = "cpu"; compatible = "arm,cortex-a72"; reg = <3>; enable-method = "spin-table"; cpu-release-addr = <0x0 0x000000f0>; }; */ /****************************************************/ mov x0, #0 adr x0, configure_el1 mov x1, #0xe0 str x0, [x1] mov x1, #0xe8 str x0, [x1] mov x1, #0xf0 str x0, [x1] configure_el1: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =CPACR_EL1_MASK msr cpacr_el1, x0 ldr x0, =SPSR_VALUE msr spsr_el2, x0 adr x0, master msr elr_el2, x0 eret master: mrs x0, mpidr_el1 and x0, x0, #0x3 mov x1, #SECTION_SIZE mul x1, x1, x0 add x1, x1, #LOW_MEMORY mov sp, x1 bl kernel_main b proc_hang ================================================ FILE: exercises/lesson03/2/szediwy/src/config.txt ================================================ arm_64bit=1 enable_uart=1 uart_2ndstage=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson03/2/szediwy/src/custom_printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "custom_printf.h" typedef void (*putcf)(void *, char); static putcf stdout_putf; static void *stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc, char *bf) { int n = 0; unsigned int d = 1; while (num / d >= base) d *= base; while (d != 0) { int dgt = num / d; num %= d; d /= base; if (n || dgt > 0 || d == 0) { *bf++ = dgt + (dgt < 10 ? '0' : (uc ? 'A' : 'a') - 10); ++n; } } *bf = 0; } static void li2a(long num, char *bf) { if (num < 0) { num = -num; *bf++ = '-'; } uli2a(num, 10, 0, bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc, char *bf) { int n = 0; unsigned int d = 1; while (num / d >= base) d *= base; while (d != 0) { int dgt = num / d; num %= d; d /= base; if (n || dgt > 0 || d == 0) { *bf++ = dgt + (dgt < 10 ? '0' : (uc ? 'A' : 'a') - 10); ++n; } } *bf = 0; } static void i2a(int num, char *bf) { if (num < 0) { num = -num; *bf++ = '-'; } ui2a(num, 10, 0, bf); } static int a2d(char ch) { if (ch >= '0' && ch <= '9') return ch - '0'; else if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10; else if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10; else return -1; } static char a2i(char ch, char **src, int base, int *nump) { char *p = *src; int num = 0; int digit; while ((digit = a2d(ch)) >= 0) { if (digit > base) break; num = num * base + digit; ch = *p++; } *src = p; *nump = num; return ch; } static void putchw(void *putp, putcf putf, int n, char z, char *bf) { char fc = z ? '0' : ' '; char ch; char *p = bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp, fc); while ((ch = *bf++)) putf(putp, ch); } void tfp_format(void *putp, putcf putf, char *fmt, va_list va) { char bf[12]; char ch; while ((ch = *(fmt++))) { if (ch != '%') { putf(putp, ch); } else { char lz = 0; #ifdef PRINTF_LONG_SUPPORT char lng = 0; #endif int w = 0; ch = *(fmt++); if (ch == '0') { ch = *(fmt++); lz = 1; } if (ch >= '0' && ch <= '9') { ch = a2i(ch, &fmt, 10, &w); } #ifdef PRINTF_LONG_SUPPORT if (ch == 'l') { ch = *(fmt++); lng = 1; } #endif switch (ch) { case 0: goto abort; case 'u': { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int), 10, 0, bf); else #endif ui2a(va_arg(va, unsigned int), 10, 0, bf); putchw(putp, putf, w, lz, bf); break; } case 'd': { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int), bf); else #endif i2a(va_arg(va, int), bf); putchw(putp, putf, w, lz, bf); break; } case 'x': case 'X': #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int), 16, (ch == 'X'), bf); else #endif ui2a(va_arg(va, unsigned int), 16, (ch == 'X'), bf); putchw(putp, putf, w, lz, bf); break; case 'c': putf(putp, (char)(va_arg(va, int))); break; case 's': putchw(putp, putf, w, 0, va_arg(va, char *)); break; case '%': putf(putp, ch); default: break; } } } abort:; } void init_printf(void *putp, void (*putf)(void *, char)) { stdout_putf = putf; stdout_putp = putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va, fmt); tfp_format(stdout_putp, stdout_putf, fmt, va); va_end(va); } static void putcp(void *p, char c) { *(*((char **)p))++ = c; } void tfp_sprintf(char *s, char *fmt, ...) { va_list va; va_start(va, fmt); tfp_format(&s, putcp, fmt, va); putcp(&s, 0); va_end(va); } ================================================ FILE: exercises/lesson03/2/szediwy/src/debug.c ================================================ #include "custom_printf.h" void d0(void) { printf("#0"); } void d1(void) { printf("#1"); } void d2(void) { printf("#2"); } void d3(void) { printf("#3"); } void d4(void) { printf("#4"); } ================================================ FILE: exercises/lesson03/2/szediwy/src/entry.S ================================================ #include "entry.h" .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] str x30, [sp, #16 * 15] .endm .macro kernel_exit ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] ldr x30, [sp, #16 * 15] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 ventry irq_invalid_el0_64 // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry ERROR_INVALID_EL1h sync_invalid_el0_64: handle_invalid_entry SYNC_INVALID_EL0_64 irq_invalid_el0_64: handle_invalid_entry IRQ_INVALID_EL0_64 fiq_invalid_el0_64: handle_invalid_entry FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry ERROR_INVALID_EL0_32 el1_irq: kernel_entry bl handle_irq kernel_exit .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson03/2/szediwy/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 ret .globl enable_irq enable_irq: msr daifclr, #0b0010 ret .globl disable_irq disable_irq: msr daifset, #0b0010 ret ================================================ FILE: exercises/lesson03/2/szediwy/src/irq.c ================================================ #include "utils.h" #include "custom_printf.h" #include "timer.h" #include "entry.h" #include "peripherals/irq.h" #include "sysregs.h" #include "mini_uart.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32" }; void enable_interrupt(unsigned int irq) { // For interrupt ID m, when DIV and MOD are the integer division and modulo operations: // • the corresponding GICD_ISENABLER number, n, is given by n = m DIV 32 // • the offset of the required GICD_ISENABLER is (0x100 + (4*n)) // • the bit number of the required Set-enable bit in this register is m MOD 32. unsigned int n = irq / 32; unsigned int offset = irq % 32; unsigned int enableRegister = GIC_ENABLE_IRQ_BASE + (4*n); put32(enableRegister, 1 << offset); } void assign_target(unsigned int irq, unsigned int cpu) { // For interrupt ID m, when DIV and MOD are the integer division and modulo operations: // • the corresponding GICD_ITARGETSRn number, n, is given by n = m DIV 4 // • the offset of the required GICD_ITARGETSR is (0x800 + (4*n)) // • the byte offset of the required Priority field in this register is m MOD 4, where: // — byte offset 0 refers to register bits [7:0] // — byte offset 1 refers to register bits [15:8] // — byte offset 2 refers to register bits [23:16] // — byte offset 3 refers to register bits [31:24]. unsigned int n = irq / 4; unsigned int offset = irq % 4; unsigned int targetRegister = GIC_IRQ_TARGET_BASE + (4*n); // Currently we only enter the target CPU 0 put32(targetRegister, get32(targetRegister) | (1 << (offset*8))); } void enable_interrupt_controller() { // assign_target(SYSTEM_TIMER_IRQ_1, 0); // enable_interrupt(SYSTEM_TIMER_IRQ_1); // assign_target(LOCAL_TIMER_IRQ, 0); // enable_interrupt(LOCAL_TIMER_IRQ); assign_target(UART_IRQ, 0); enable_interrupt(UART_IRQ); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { // NOTE (ARM® Generic Interrupt Controller - Architecture version 2.0): // For compatibility with possible extensions to the GIC architecture specification, ARM recommends that software // preserves the entire register value read from the GICC_IAR when it acknowledges the interrupt, and uses that entire // value for its corresponding write to the GICC_EOIR. unsigned int irqAckRegister = get32(GICC_IAR); unsigned int irq = irqAckRegister & 0x2FF; // bit [9:0] -> interrupt ID switch (irq) { case (SYSTEM_TIMER_IRQ_1): handle_timer_irq(); put32(GICC_EOIR, irqAckRegister); break; case (LOCAL_TIMER_IRQ): handle_local_timer_irq(); put32(GICC_EOIR, irqAckRegister); break; case (UART_IRQ): handle_uart_interrupt(); put32(GICC_EOIR, irqAckRegister); break; default: printf("Unknown pending irq: 0x%x\r\n", irq); } } ================================================ FILE: exercises/lesson03/2/szediwy/src/kernel.c ================================================ #include "custom_printf.h" #include "timer.h" #include "utils.h" #include "mini_uart.h" #include "irq.h" void kernel_main(unsigned long processor_index) { static unsigned int current_processor_index = 0; if (processor_index == 0) { uart_init(); init_printf(0, putc); printf("irq_vector_init\r\n"); irq_vector_init(); // printf("timer_init\r\n"); // timer_init(); // printf("local_timer_init\r\n"); // local_timer_init(); printf("enable_interrupt_controller\r\n"); enable_interrupt_controller(); printf("enable_irq\r\n"); enable_irq(); } while (processor_index != current_processor_index) ; int exception_level = get_el(); printf("{CPU: %d, Exception level: %d}\r\n", processor_index, exception_level); current_processor_index++; if (processor_index == 0) { // if current_processor_index == 4 then all processors send message while (current_processor_index != 4) ; for (;;) { // uart_send(uart_recv()); } } } ================================================ FILE: exercises/lesson03/2/szediwy/src/linker.ld ================================================ SECTIONS { . = 0x80000; .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson03/2/szediwy/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" #include "custom_printf.h" void uart_send(char c) { while (get32(UART0_BASE + UART_FR_OFFSET) & (1 << 5)) { } put32(UART0_BASE + UART_DR_OFFSET, c); } char uart_recv(void) { while (get32(UART0_BASE + UART_FR_OFFSET) & UART_RX_MASK) { } return (get32(UART0_BASE + UART_DR_OFFSET) & 0xFF); } void uart_send_string(char *str) { for (int i = 0; str[i] != '\0'; i++) { uart_send((char)str[i]); } } void uart_init(void) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7 << 12); // clean gpio14 selector |= 4 << 12; // set alt0 for gpio14 selector &= ~(7 << 15); // clean gpio15 selector |= 4 << 15; // set alt0 for gpio15 put32(GPFSEL1, selector); unsigned int pullRegister; pullRegister = get32(GPIO_PUP_PDN_CNTRL_REG0); pullRegister &= ~(3 << 30); pullRegister &= ~(3 << 28); put32(GPIO_PUP_PDN_CNTRL_REG0, pullRegister); // first disable uart put32(UART0_BASE + UART_CR_OFFSET, 0); // unmask all interrupts -> disable them put32(UART0_BASE + UART_IMSC_OFFSET, 0); //from ../adkaster/src/uart.c // Assume 48MHz UART Reference Clock (Standard) // Calculate UART clock divider per datasheet // BAUDDIV = (FUARTCLK/(16 Baud rate)) // Note: We get 6 bits of fraction for the baud div // 48000000/(16 * 115200) = 3000000/115200 = 26.0416666... // Integer part = 26 :) // From http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0183g/I54603.html // we want floor(0.04166666.. * 64 + 0.5) = 3 put32(UART0_BASE + UART_IBRD_OFFSET, 26); put32(UART0_BASE + UART_FBRD_OFFSET, 3); //0111|0000 => 8 bits and enable fifos put32(UART0_BASE + UART_LCRH_OFFSET, 7 << 4); // if the fifo queue is enabled set the lowest levels to trigger an interrupt. put32(UART0_BASE + UART_IFLS_OFFSET, 0); // 0011|0000|0001 => enable: rx tx uart put32(UART0_BASE + UART_CR_OFFSET, (1 << 9) | (1 << 8) | (1 << 0)); // enable the RX and RX timeout interrupt put32(UART0_BASE + UART_IMSC_OFFSET, (UART_IRQ_RXIM_MASK | UART_IRQ_RTIM_MASK)); } // This function is required by printf function void putc(void *p, char c) { uart_send(c); } void handle_uart_interrupt(void) { // Which UART was the interrupt source // UART5 IRQ bit 16 // UART4 IRQ bit 17 // UART3 IRQ bit 18 // UART2 IRQ bit 19 // UART0 IRQ bit 20 unsigned int uart = get32(PACTL_CS_REG) & (0b11111 << 16); switch (uart) { case 1 << 20: uart = 0; break; case 1 << 19: uart = 2; break; case 1 << 18: uart = 3; break; case 1 << 17: uart = 4; break; case 1 << 16: uart = 5; break; default: uart = -1; } static unsigned int uart_bases[] = {UART0_BASE, -1, UART2_BASE, UART3_BASE, UART4_BASE, UART5_BASE}; unsigned int base = uart_bases[uart]; // Read the exact interrupt type from the masked interrupt status register // and check whether it is a RX unsigned int uart_irq_status = get32(base + UART_MIS_OFFSET); printf("Detected interrupt 0x%x for UART%d at 0x%x: ", uart_irq_status, uart, base); if (uart_irq_status & (UART_IRQ_RXIM_MASK | UART_IRQ_RTIM_MASK)) { uart_send(uart_recv()); } // Clear the interrupt put32(base + UART_ICR_OFFSET, uart_irq_status); } ================================================ FILE: exercises/lesson03/2/szediwy/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson03/2/szediwy/src/timer.c ================================================ #include "utils.h" #include "custom_printf.h" #include "peripherals/timer.h" const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); printf("Timer interrupt received\n\r"); } void handle_local_timer_irq( void ) { printf("LOCAL_TIMER_IRQ received\n\r"); put32(LOCAL_TIMER_IRQ, (3<<30)); } void local_timer_init (void) { // Enable the timer put32(LOCAL_TIMER_CONTROL, (3<<28) | (3*LOCAL_TIMER_FREQ)); } ================================================ FILE: exercises/lesson03/2/szediwy/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret ================================================ FILE: exercises/lesson03/3/H-4ND-H/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson03/3/H-4ND-H/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson03/3/H-4ND-H/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson03/3/H-4ND-H/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #endif ================================================ FILE: exercises/lesson03/3/H-4ND-H/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 256 // size of all saved registers #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #endif ================================================ FILE: exercises/lesson03/3/H-4ND-H/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson03/3/H-4ND-H/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson03/3/H-4ND-H/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson03/3/H-4ND-H/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson03/3/H-4ND-H/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson03/3/H-4ND-H/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define CORE0_TIMER_IRQ_CTRL (0x40000040) #define CORE0_IRQ_SOURCE (0x40000060) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #define NON_SECURE_TIMER_IRQ (1 << 1) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson03/3/H-4ND-H/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson03/3/H-4ND-H/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CTRL (0x04000000) #define TIMER_PS (0x04000008) #define TIMER_LS (0x0400001c) #define TIMER_MS (0x04000020) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson03/3/H-4ND-H/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson03/3/H-4ND-H/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson03/3/H-4ND-H/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson03/3/H-4ND-H/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SPSR_VALUE msr spsr_el2, x0 adr x0, el1_entry msr elr_el2, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson03/3/H-4ND-H/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson03/3/H-4ND-H/src/entry.S ================================================ #include "entry.h" .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] str x30, [sp, #16 * 15] .endm .macro kernel_exit ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] ldr x30, [sp, #16 * 15] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 ventry irq_invalid_el0_64 // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry ERROR_INVALID_EL1h sync_invalid_el0_64: handle_invalid_entry SYNC_INVALID_EL0_64 irq_invalid_el0_64: handle_invalid_entry IRQ_INVALID_EL0_64 fiq_invalid_el0_64: handle_invalid_entry FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry ERROR_INVALID_EL0_32 el1_irq: kernel_entry bl handle_irq kernel_exit .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson03/3/H-4ND-H/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson03/3/H-4ND-H/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "timer.h" #include "entry.h" #include "peripherals/irq.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32" }; void enable_interrupt_controller() { put32(CORE0_TIMER_IRQ_CTRL, NON_SECURE_TIMER_IRQ); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { unsigned int irq = get32(CORE0_IRQ_SOURCE); switch (irq) { case (NON_SECURE_TIMER_IRQ): handle_timer_irq(); break; default: printf("Unknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson03/3/H-4ND-H/src/kernel.c ================================================ #include "printf.h" #include "timer.h" #include "irq.h" #include "mini_uart.h" //to run in qemu use: qemu-system-aarch64 -M raspi3 -serial null -serial mon:stdio -kernel void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); while (1){ uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson03/3/H-4ND-H/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson03/3/H-4ND-H/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson03/3/H-4ND-H/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson03/3/H-4ND-H/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson03/3/H-4ND-H/src/timer.c ================================================ #include "utils.h" #include "printf.h" #include "peripherals/timer.h" const unsigned int interval = 38461538; void timer_init ( void ) { int enable = 1; asm("msr CNTP_CTL_EL0, %0" :: "r" (enable)); asm("msr CNTP_TVAL_EL0, %0" :: "r" (interval)); } void handle_timer_irq( void ) { asm("msr CNTP_TVAL_EL0, %0" :: "r" (interval)); printf("Timer interrupt received\n\r"); } ================================================ FILE: exercises/lesson03/3/H-4ND-H/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson03/3/avenito/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson03/3/avenito/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson03/3/avenito/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson03/3/avenito/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #endif ================================================ FILE: exercises/lesson03/3/avenito/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 256 // size of all saved registers #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #endif ================================================ FILE: exercises/lesson03/3/avenito/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson03/3/avenito/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson03/3/avenito/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #define PERIPHERAL_BASE 0x40000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson03/3/avenito/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson03/3/avenito/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #define CORE0_INT_CTR (PERIPHERAL_BASE+0x40) #define CORE0_INT_SOURCE (PERIPHERAL_BASE+0x60) #define LOCAL_TIMER_INT (1 << 11) #define CNTPNSIRQ_Int (1 << 1) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson03/3/avenito/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) // Local timer #define TIMER_CTRL (PERIPHERAL_BASE+0x34) #define TIMER_FLAG (PERIPHERAL_BASE+0x38) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson03/3/avenito/include/peripherals/uart.h ================================================ #ifndef _P_UART_H #define _P_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00201000) #define UART_DR (PBASE+0x00201000) #define UART_FR (PBASE+0x00201018) #define UART_CR (PBASE+0x00201030) #define UART_IBRD (PBASE+0x00201024) #define UART_FBRD (PBASE+0x00201028) #define UARTLCR_LCRH (PBASE+0x0020102C) #endif /*_P_UART_H */ ================================================ FILE: exercises/lesson03/3/avenito/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson03/3/avenito/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson03/3/avenito/include/uart.h ================================================ #ifndef _UART_H #define _UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); void putc ( void* p, char c ); #endif /*_UART_H */ ================================================ FILE: exercises/lesson03/3/avenito/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); extern void generic_timer_init ( void ); extern void generic_timer_reset ( void ); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson03/3/avenito/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 /* qemu starts at EL2, than is not possible to access any register of EL3 */ //ldr x0, =SCR_VALUE //msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el2, x0 // 'spsr_el3' changed to 'spsr_el2' adr x0, el1_entry msr elr_el2, x0 // 'elr_el3' changed to 'elr_el2' eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson03/3/avenito/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson03/3/avenito/src/entry.S ================================================ #include "entry.h" .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] str x30, [sp, #16 * 15] .endm .macro kernel_exit ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] ldr x30, [sp, #16 * 15] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 ventry irq_invalid_el0_64 // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry ERROR_INVALID_EL1h sync_invalid_el0_64: handle_invalid_entry SYNC_INVALID_EL0_64 irq_invalid_el0_64: handle_invalid_entry IRQ_INVALID_EL0_64 fiq_invalid_el0_64: handle_invalid_entry FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry ERROR_INVALID_EL0_32 el1_irq: kernel_entry bl handle_irq kernel_exit .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson03/3/avenito/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson03/3/avenito/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "timer.h" #include "entry.h" #include "peripherals/irq.h" #include "peripherals/timer.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32" }; void enable_interrupt_controller() { // Enable IRQ Core 0 - Pag. 13 BCM2836_ARM-local_peripherals put32(CORE0_INT_CTR, (1 << 1)); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { unsigned int irq = get32(CORE0_INT_SOURCE); switch (irq) { case (CNTPNSIRQ_Int): handle_timer_irq(); break; default: printf("Unknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson03/3/avenito/src/kernel.c ================================================ #include "printf.h" #include "timer.h" #include "irq.h" #include "uart.h" #include "entry.h" #include "utils.h" #include "peripherals/irq.h" #include "peripherals/timer.h" void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); generic_timer_init(); enable_interrupt_controller(); enable_irq(); while (1){ //uart_send(uart_recv()); uart_recv(); printf("IRQ_PENDING_1: %04x \n\r", get32(IRQ_PENDING_1)); } } ================================================ FILE: exercises/lesson03/3/avenito/src/linker.ld ================================================ SECTIONS { . = 0x80000; .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson03/3/avenito/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson03/3/avenito/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson03/3/avenito/src/timer.c ================================================ #include "utils.h" #include "printf.h" #include "peripherals/timer.h" const unsigned int interval = 20000000; unsigned int curVal = 0; void timer_init ( void ) { // Set value, enable Timer and Interrupt put32(TIMER_CTRL, ((1<<28) | interval)); } void handle_timer_irq( void ) { printf("Timer interrupt received, Generic Timer\n\r"); generic_timer_reset(); } ================================================ FILE: exercises/lesson03/3/avenito/src/uart.c ================================================ #include "utils.h" #include "peripherals/uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(get32(UART_FR)&0x20) {} // wait if TX is full put32(UART_DR,c); // when TX is empty, send next char } char uart_recv ( void ) { while(get32(UART_FR)&0x10) {} // wait if RX is empty return(get32(UART_DR)&0xFF); // get recived char } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 4<<12; // set alt0 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 4<<15; // set alt0 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(UART_CR,0); // disable RX and TX to configure put32(UART_IBRD,26); //PrimeCell UART (PL011) rev.r1p5 pag.3-9 BAUDDIV = (FUARTCLK/(16 Baud rate)) = 48MHz/(16*115200) = 26.041666 put32(UART_FBRD,3); put32(UARTLCR_LCRH,0x60); //Enable 8 bit mode put32(UART_CR,0x301); // enable UART, RX and TX } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson03/3/avenito/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl get_cnt get_cnt: mrs x0, CNTPCT_EL0 lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret /* Looking at See AArch64-referenc-manual p.5667 */ .globl generic_timer_init generic_timer_init: mov x0, #1 msr CNTP_CTL_EL0, x0 ret .globl generic_timer_reset generic_timer_reset: mov x0, #1 lsl x0, x0, #24 msr CNTP_TVAL_EL0, x0 ret ================================================ FILE: exercises/lesson03/3/bl4ckout31/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson03/3/bl4ckout31/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson03/3/bl4ckout31/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson03/3/bl4ckout31/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #endif ================================================ FILE: exercises/lesson03/3/bl4ckout31/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 256 // size of all saved registers #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #endif ================================================ FILE: exercises/lesson03/3/bl4ckout31/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson03/3/bl4ckout31/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson03/3/bl4ckout31/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson03/3/bl4ckout31/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #define LPBASE 0x40000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson03/3/bl4ckout31/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson03/3/bl4ckout31/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) // See BCM2836 ARM-local peripherals at // https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2836/QA7_rev3.4.pdf #define TIMER_INT_CTRL_0 (0x40000040) #define INT_SOURCE_0 (LPBASE+0x60) #define TIMER_INT_CTRL_0_VALUE (1 << 1) #define GENERIC_TIMER_INTERRUPT (1 << 1) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson03/3/bl4ckout31/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson03/3/bl4ckout31/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson03/3/bl4ckout31/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson03/3/bl4ckout31/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); void generic_timer_init ( void ); void handle_generic_timer_irq ( void ); extern void gen_timer_init(); extern void gen_timer_reset(); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson03/3/bl4ckout31/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson03/3/bl4ckout31/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SPSR_VALUE msr spsr_el2, x0 adr x0, el1_entry msr elr_el2, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson03/3/bl4ckout31/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson03/3/bl4ckout31/src/entry.S ================================================ #include "entry.h" .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] str x30, [sp, #16 * 15] .endm .macro kernel_exit ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] ldr x30, [sp, #16 * 15] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 ventry irq_invalid_el0_64 // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry ERROR_INVALID_EL1h sync_invalid_el0_64: handle_invalid_entry SYNC_INVALID_EL0_64 irq_invalid_el0_64: handle_invalid_entry IRQ_INVALID_EL0_64 fiq_invalid_el0_64: handle_invalid_entry FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry ERROR_INVALID_EL0_32 el1_irq: kernel_entry bl handle_irq kernel_exit .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson03/3/bl4ckout31/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson03/3/bl4ckout31/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "timer.h" #include "entry.h" #include "peripherals/irq.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32" }; void enable_interrupt_controller() { // Enables Core 0 Timers interrupt control for the generic timer put32(TIMER_INT_CTRL_0, TIMER_INT_CTRL_0_VALUE); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { // Each Core has its own pending local intrrupts register unsigned int irq = get32(INT_SOURCE_0); switch (irq) { case (GENERIC_TIMER_INTERRUPT): handle_generic_timer_irq(); break; default: printf("Unknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson03/3/bl4ckout31/src/kernel.c ================================================ #include "printf.h" #include "timer.h" #include "irq.h" #include "mini_uart.h" #include "utils.h" void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); generic_timer_init(); enable_interrupt_controller(); enable_irq(); while (1){ uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson03/3/bl4ckout31/src/linker.ld ================================================ SECTIONS { . = 0x80000; .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson03/3/bl4ckout31/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson03/3/bl4ckout31/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson03/3/bl4ckout31/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson03/3/bl4ckout31/src/timer.S ================================================ /** Here, the physical timer at EL1 is used with the TimerValue views. * Once the count-down has reach 0, the interrupt line is HIGH until * a new timer value > 0 is write into the CNTP_TVAL_EL0 system register. * * See AArch64-referenc-manual p.2326 at * https://developer.arm.com/docs/ddi0487/ca/arm-architecture-reference-manual-armv8-for-armv8-a-architecture-profile */ .globl gen_timer_init gen_timer_init: mov x0, #1 msr CNTP_CTL_EL0, x0 ret .globl gen_timer_reset gen_timer_reset: mov x0, #1 lsl x0, x0, #24 msr CNTP_TVAL_EL0, x0 ret ================================================ FILE: exercises/lesson03/3/bl4ckout31/src/timer.c ================================================ #include "utils.h" #include "printf.h" #include "peripherals/timer.h" #include "timer.h" const unsigned int interval = 9600000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); printf("Timer interrupt received\n\r"); } void generic_timer_init ( void ) { gen_timer_init(); gen_timer_reset(); } void handle_generic_timer_irq( void ) { printf("Timer interrupt received\n\r"); gen_timer_reset(); } ================================================ FILE: exercises/lesson03/3/bl4ckout31/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl get_cnt get_cnt: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson03/3/bl4ckout31/start.sh ================================================ #!/bin/bash echo "###" echo "### Use C-a h for help" echo "###" echo "" qemu-system-aarch64 -machine raspi3 -serial null -serial mon:stdio -nographic -kernel kernel8.img ================================================ FILE: exercises/lesson03/3/rs/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel7.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel7.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel7.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel7.elf -O binary kernel7.img ================================================ FILE: exercises/lesson03/3/rs/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson03/3/rs/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson03/3/rs/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of // AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED \ (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | \ SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of // AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of // AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of // AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_EL2h (9 << 0) #define SPSR_EL1 (SPSR_MASK_ALL | SPSR_EL1h) #define SPSR_EL2 (SPSR_MASK_ALL | SPSR_EL2h) /* Current Exception Level values, as contained in CurrentEL */ #define CurrentEL_EL1 (1 << 2) #define CurrentEL_EL2 (2 << 2) #define CurrentEL_EL3 (3 << 2) #endif ================================================ FILE: exercises/lesson03/3/rs/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 256 // size of all saved registers #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #endif ================================================ FILE: exercises/lesson03/3/rs/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller(void); void irq_vector_init(void); void enable_irq(void); void disable_irq(void); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson03/3/rs/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init(void); char uart_recv(void); void uart_send(char c); void putc(void *p, char c); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson03/3/rs/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson03/3/rs/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson03/3/rs/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE + 0x00200004) #define GPSET0 (PBASE + 0x0020001C) #define GPCLR0 (PBASE + 0x00200028) #define GPPUD (PBASE + 0x00200094) #define GPPUDCLK0 (PBASE + 0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson03/3/rs/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE + 0x0000B200) #define IRQ_PENDING_1 (PBASE + 0x0000B204) #define IRQ_PENDING_2 (PBASE + 0x0000B208) #define FIQ_CONTROL (PBASE + 0x0000B20C) #define ENABLE_IRQS_1 (PBASE + 0x0000B210) #define ENABLE_IRQS_2 (PBASE + 0x0000B214) #define ENABLE_BASIC_IRQS (PBASE + 0x0000B218) #define DISABLE_IRQS_1 (PBASE + 0x0000B21C) #define DISABLE_IRQS_2 (PBASE + 0x0000B220) #define DISABLE_BASIC_IRQS (PBASE + 0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #define ARM_TIMER_IRQ (1 << 0) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson03/3/rs/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE + 0x00215004) #define AUX_MU_IO_REG (PBASE + 0x00215040) #define AUX_MU_IER_REG (PBASE + 0x00215044) #define AUX_MU_IIR_REG (PBASE + 0x00215048) #define AUX_MU_LCR_REG (PBASE + 0x0021504C) #define AUX_MU_MCR_REG (PBASE + 0x00215050) #define AUX_MU_LSR_REG (PBASE + 0x00215054) #define AUX_MU_MSR_REG (PBASE + 0x00215058) #define AUX_MU_SCRATCH (PBASE + 0x0021505C) #define AUX_MU_CNTL_REG (PBASE + 0x00215060) #define AUX_MU_STAT_REG (PBASE + 0x00215064) #define AUX_MU_BAUD_REG (PBASE + 0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson03/3/rs/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE + 0x00003000) #define TIMER_CLO (PBASE + 0x00003004) #define TIMER_CHI (PBASE + 0x00003008) #define TIMER_C0 (PBASE + 0x0000300C) #define TIMER_C1 (PBASE + 0x00003010) #define TIMER_C2 (PBASE + 0x00003014) #define TIMER_C3 (PBASE + 0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) // Reference: 14.2 Timer Registers // https://web.stanford.edu/class/cs140e/docs/BCM2837-ARM-Peripherals.pdf #define ARM_TIMER_BASE (PBASE + 0xB000) #define ARM_TIMER_LOAD (ARM_TIMER_BASE + 0x400) #define ARM_TIMER_VALUE (ARM_TIMER_BASE + 0x404) #define ARM_TIMER_CTRL (ARM_TIMER_BASE + 0x408) #define ARM_TIMER_CLR (ARM_TIMER_BASE + 0x40c) #define CTRL_23BIT (1 << 1) // 23-bit counter #define CTRL_INT_ENABLE (1 << 5) // Timer interrupt enabled #define CTRL_ENABLE (1 << 7) // Timer enabled #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson03/3/rs/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void *putp, void (*putf)(void *, char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char *s, char *fmt, ...); void tfp_format(void *putp, void (*putf)(void *, char), char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson03/3/rs/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init(void); void handle_timer_irq(void); void arm_timer_init(void); void handle_arm_timer_irq(void); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson03/3/rs/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay(unsigned long); extern void put32(unsigned long, unsigned int); extern unsigned int get32(unsigned long); extern int get_el(void); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson03/3/rs/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0, #0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: mrs x0, CurrentEL cmp x0, #CurrentEL_EL3 b.eq el3_entry b el2_entry el3_entry: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el2, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_EL2 msr spsr_el3, x0 adr x0, el2_entry msr elr_el3, x0 eret el2_entry: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =SPSR_EL1 msr spsr_el2, x0 adr x0, el1_entry msr elr_el2, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson03/3/rs/src/config.txt ================================================ arm_control=0x200 kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson03/3/rs/src/entry.S ================================================ #include "entry.h" .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] str x30, [sp, #16 * 15] .endm .macro kernel_exit ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] ldr x30, [sp, #16 * 15] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 ventry irq_invalid_el0_64 // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry ERROR_INVALID_EL1h sync_invalid_el0_64: handle_invalid_entry SYNC_INVALID_EL0_64 irq_invalid_el0_64: handle_invalid_entry IRQ_INVALID_EL0_64 fiq_invalid_el0_64: handle_invalid_entry FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry ERROR_INVALID_EL0_32 el1_irq: kernel_entry bl handle_irq kernel_exit .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson03/3/rs/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson03/3/rs/src/irq.c ================================================ #include "peripherals/irq.h" #include "entry.h" #include "printf.h" #include "timer.h" #include "utils.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32"}; void enable_interrupt_controller() { // put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1); put32(ENABLE_BASIC_IRQS, ARM_TIMER_IRQ); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { // unsigned int irq = get32(IRQ_PENDING_1); // switch (irq) { // case (SYSTEM_TIMER_IRQ_1): // handle_timer_irq(); // break; // default: // printf("Unknown pending irq: %x\r\n", irq); // } unsigned int irq = get32(IRQ_BASIC_PENDING); switch (irq) { case (ARM_TIMER_IRQ): handle_arm_timer_irq(); break; default: printf("Unknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson03/3/rs/src/kernel.c ================================================ #include "irq.h" #include "mini_uart.h" #include "printf.h" #include "timer.h" void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); arm_timer_init(); enable_interrupt_controller(); enable_irq(); while (1) { uart_send(uart_recv()); } } ================================================ FILE: exercises/lesson03/3/rs/src/linker.ld ================================================ SECTIONS { . = 0x80000; .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson03/3/rs/src/mini_uart.c ================================================ #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" #include "utils.h" void uart_send(char c) { while (1) { if (get32(AUX_MU_LSR_REG) & 0x20) break; } put32(AUX_MU_IO_REG, c); } char uart_recv(void) { while (1) { if (get32(AUX_MU_LSR_REG) & 0x01) break; } return (get32(AUX_MU_IO_REG) & 0xFF); } void uart_send_string(char *str) { for (int i = 0; str[i] != '\0'; i++) { uart_send((char)str[i]); } } void uart_init(void) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7 << 12); // clean gpio14 selector |= 2 << 12; // set alt5 for gpio14 selector &= ~(7 << 15); // clean gpio15 selector |= 2 << 15; // set alt5 for gpio15 put32(GPFSEL1, selector); put32(GPPUD, 0); delay(150); put32(GPPUDCLK0, (1 << 14) | (1 << 15)); delay(150); put32(GPPUDCLK0, 0); put32(AUX_ENABLES, 1); // Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG, 0); // Disable auto flow control and disable receiver // and transmitter (for now) put32(AUX_MU_IER_REG, 0); // Disable receive and transmit interrupts put32(AUX_MU_LCR_REG, 3); // Enable 8 bit mode put32(AUX_MU_MCR_REG, 0); // Set RTS line to be always high put32(AUX_MU_BAUD_REG, 270); // Set baud rate to 115200 put32(AUX_MU_CNTL_REG, 3); // Finally, enable transmitter and receiver } // This function is required by printf function void putc(void *p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson03/3/rs/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson03/3/rs/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf)(void *, char); static putcf stdout_putf; static void *stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc, char *bf) { int n = 0; unsigned int d = 1; while (num / d >= base) d *= base; while (d != 0) { int dgt = num / d; num %= d; d /= base; if (n || dgt > 0 || d == 0) { *bf++ = dgt + (dgt < 10 ? '0' : (uc ? 'A' : 'a') - 10); ++n; } } *bf = 0; } static void li2a(long num, char *bf) { if (num < 0) { num = -num; *bf++ = '-'; } uli2a(num, 10, 0, bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc, char *bf) { int n = 0; unsigned int d = 1; while (num / d >= base) d *= base; while (d != 0) { int dgt = num / d; num %= d; d /= base; if (n || dgt > 0 || d == 0) { *bf++ = dgt + (dgt < 10 ? '0' : (uc ? 'A' : 'a') - 10); ++n; } } *bf = 0; } static void i2a(int num, char *bf) { if (num < 0) { num = -num; *bf++ = '-'; } ui2a(num, 10, 0, bf); } static int a2d(char ch) { if (ch >= '0' && ch <= '9') return ch - '0'; else if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10; else if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10; else return -1; } static char a2i(char ch, char **src, int base, int *nump) { char *p = *src; int num = 0; int digit; while ((digit = a2d(ch)) >= 0) { if (digit > base) break; num = num * base + digit; ch = *p++; } *src = p; *nump = num; return ch; } static void putchw(void *putp, putcf putf, int n, char z, char *bf) { char fc = z ? '0' : ' '; char ch; char *p = bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp, fc); while ((ch = *bf++)) putf(putp, ch); } void tfp_format(void *putp, putcf putf, char *fmt, va_list va) { char bf[12]; char ch; while ((ch = *(fmt++))) { if (ch != '%') putf(putp, ch); else { char lz = 0; #ifdef PRINTF_LONG_SUPPORT char lng = 0; #endif int w = 0; ch = *(fmt++); if (ch == '0') { ch = *(fmt++); lz = 1; } if (ch >= '0' && ch <= '9') { ch = a2i(ch, &fmt, 10, &w); } #ifdef PRINTF_LONG_SUPPORT if (ch == 'l') { ch = *(fmt++); lng = 1; } #endif switch (ch) { case 0: goto abort; case 'u': { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int), 10, 0, bf); else #endif ui2a(va_arg(va, unsigned int), 10, 0, bf); putchw(putp, putf, w, lz, bf); break; } case 'd': { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int), bf); else #endif i2a(va_arg(va, int), bf); putchw(putp, putf, w, lz, bf); break; } case 'x': case 'X': #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int), 16, (ch == 'X'), bf); else #endif ui2a(va_arg(va, unsigned int), 16, (ch == 'X'), bf); putchw(putp, putf, w, lz, bf); break; case 'c': putf(putp, (char)(va_arg(va, int))); break; case 's': putchw(putp, putf, w, 0, va_arg(va, char *)); break; case '%': putf(putp, ch); default: break; } } } abort:; } void init_printf(void *putp, void (*putf)(void *, char)) { stdout_putf = putf; stdout_putp = putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va, fmt); tfp_format(stdout_putp, stdout_putf, fmt, va); va_end(va); } static void putcp(void *p, char c) { *(*((char **)p))++ = c; } void tfp_sprintf(char *s, char *fmt, ...) { va_list va; va_start(va, fmt); tfp_format(&s, putcp, fmt, va); putcp(&s, 0); va_end(va); } ================================================ FILE: exercises/lesson03/3/rs/src/timer.c ================================================ #include "peripherals/timer.h" #include "printf.h" #include "utils.h" const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init(void) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq(void) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); printf("Timer iterrupt received\n\r"); } void arm_timer_init(void) { // According to the timer pre-divider register documentation, the default // pre-devider is configured to run at apb_clock/126 which is 1Mhz like the // system timer. put32(ARM_TIMER_LOAD, interval); put32(ARM_TIMER_CTRL, CTRL_ENABLE | CTRL_INT_ENABLE | CTRL_23BIT); } void handle_arm_timer_irq(void) { put32(ARM_TIMER_CLR, 1); printf("Timer iterrupt received\n\r"); } ================================================ FILE: exercises/lesson03/3/rs/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1, [x0] ret .globl get32 get32: ldr w0, [x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson03/3/rs/start.sh ================================================ #!/bin/bash qemu-system-aarch64 -machine raspi3 -serial null -serial mon:stdio -nographic -kernel kernel7.img ================================================ FILE: exercises/lesson04/1/avenito/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson04/1/avenito/README.md ================================================ # Exercise 1 I just added some 'printf' instructions into 'fork.c' and 'sched.c' files to print the status of the tasks. ================================================ FILE: exercises/lesson04/1/avenito/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson04/1/avenito/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson04/1/avenito/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #endif ================================================ FILE: exercises/lesson04/1/avenito/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 272 // size of all saved registers #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #ifndef __ASSEMBLER__ void ret_from_fork(void); #endif #endif ================================================ FILE: exercises/lesson04/1/avenito/include/fork.h ================================================ #ifndef _FORK_H #define _FORK_H int copy_process(unsigned long fn, unsigned long arg); #endif ================================================ FILE: exercises/lesson04/1/avenito/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson04/1/avenito/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson04/1/avenito/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #include "peripherals/base.h" #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #define HIGH_MEMORY PBASE #define PAGING_MEMORY (HIGH_MEMORY - LOW_MEMORY) #define PAGING_PAGES (PAGING_MEMORY/PAGE_SIZE) #ifndef __ASSEMBLER__ unsigned long get_free_page(); void free_page(unsigned long p); void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson04/1/avenito/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson04/1/avenito/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson04/1/avenito/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson04/1/avenito/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson04/1/avenito/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson04/1/avenito/include/peripherals/uart.h ================================================ #ifndef _P_UART_H #define _P_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00201000) #define UART_DR (PBASE+0x00201000) #define UART_FR (PBASE+0x00201018) #define UART_CR (PBASE+0x00201030) #define UART_IBRD (PBASE+0x00201024) #define UART_FBRD (PBASE+0x00201028) #define UARTLCR_LCRH (PBASE+0x0020102C) #endif /*_P_UART_H */ ================================================ FILE: exercises/lesson04/1/avenito/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson04/1/avenito/include/sched.h ================================================ #ifndef _SCHED_H #define _SCHED_H #define THREAD_CPU_CONTEXT 0 // offset of cpu_context in task_struct #ifndef __ASSEMBLER__ #define THREAD_SIZE 4096 #define NR_TASKS 3 // changed to 3 to be easier #define FIRST_TASK task[0] #define LAST_TASK task[NR_TASKS-1] #define TASK_RUNNING 0 extern struct task_struct *current; extern struct task_struct * task[NR_TASKS]; extern int nr_tasks; struct cpu_context { unsigned long x19; unsigned long x20; unsigned long x21; unsigned long x22; unsigned long x23; unsigned long x24; unsigned long x25; unsigned long x26; unsigned long x27; unsigned long x28; unsigned long fp; unsigned long sp; unsigned long pc; }; struct task_struct { struct cpu_context cpu_context; long state; long counter; long priority; long preempt_count; }; extern void sched_init(void); extern void schedule(void); extern void timer_tick(void); extern void preempt_disable(void); extern void preempt_enable(void); extern void switch_to(struct task_struct* next); extern void cpu_switch_to(struct task_struct* prev, struct task_struct* next); #define INIT_TASK \ /*cpu_context*/ { {0,0,0,0,0,0,0,0,0,0,0,0,0}, \ /* state etc */ 0,0,1, 0 \ } #endif #endif ================================================ FILE: exercises/lesson04/1/avenito/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson04/1/avenito/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson04/1/avenito/saida.txt ================================================ _shedule chamado NR_TASKS: 6 switch_to(task[0]) ; ================================================ FILE: exercises/lesson04/1/avenito/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #(2 * SECTION_SIZE) bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson04/1/avenito/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson04/1/avenito/src/entry.S ================================================ #include "entry.h" .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] mrs x22, elr_el1 mrs x23, spsr_el1 stp x30, x22, [sp, #16 * 15] str x23, [sp, #16 * 16] .endm .macro kernel_exit ldr x23, [sp, #16 * 16] ldp x30, x22, [sp, #16 * 15] msr elr_el1, x22 msr spsr_el1, x23 ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 ventry irq_invalid_el0_64 // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry ERROR_INVALID_EL1h sync_invalid_el0_64: handle_invalid_entry SYNC_INVALID_EL0_64 irq_invalid_el0_64: handle_invalid_entry IRQ_INVALID_EL0_64 fiq_invalid_el0_64: handle_invalid_entry FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry ERROR_INVALID_EL0_32 el1_irq: kernel_entry bl handle_irq kernel_exit .globl ret_from_fork ret_from_fork: bl schedule_tail mov x0, x20 blr x19 //should never return .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson04/1/avenito/src/fork.c ================================================ #include "printf.h" #include "mm.h" #include "sched.h" #include "entry.h" int copy_process(unsigned long fn, unsigned long arg) { preempt_disable(); struct task_struct *p; p = (struct task_struct *) get_free_page(); if (!p) return 1; p->priority = current->priority; p->state = TASK_RUNNING; p->counter = p->priority; p->preempt_count = 1; //disable preemtion until schedule_tail p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)p + THREAD_SIZE; int pid = nr_tasks++; task[pid] = p; printf("\n\r----------- Task[%d] created -----------\r\n", pid); printf("\n\rStruct task allocated at 0x%08x.\r\n", p); printf("p->cpu_context.x19 = 0x%08x. (fn)\r\n", p->cpu_context.x19); printf("p->cpu_context.x20 = 0x%08x. (arg)\r\n", p->cpu_context.x20); printf("p->cpu_context.pc = 0x%08x. (ret_from_fork)\r\n", p->cpu_context.pc); printf("p->cpu_context.sp = 0x%08x. (sp)\r\n", p->cpu_context.sp); preempt_enable(); return 0; } ================================================ FILE: exercises/lesson04/1/avenito/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson04/1/avenito/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "timer.h" #include "entry.h" #include "peripherals/irq.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32" }; void enable_interrupt_controller() { put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { unsigned int irq = get32(IRQ_PENDING_1); switch (irq) { case (SYSTEM_TIMER_IRQ_1): handle_timer_irq(); break; default: printf("Inknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson04/1/avenito/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "timer.h" #include "irq.h" #include "fork.h" #include "sched.h" #include "mini_uart.h" void process(char *array) { while (1){ for (int i = 0; i < 5; i++){ uart_send(array[i]); delay(100000); } } } void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); int res = copy_process((unsigned long)&process, (unsigned long)"12345"); if (res != 0) { printf("error while starting process 1"); return; } res = copy_process((unsigned long)&process, (unsigned long)"abcde"); if (res != 0) { printf("error while starting process 2"); return; } while (1){ schedule(); } } ================================================ FILE: exercises/lesson04/1/avenito/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson04/1/avenito/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson04/1/avenito/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson04/1/avenito/src/mm.c ================================================ #include "mm.h" static unsigned short mem_map [ PAGING_PAGES ] = {0,}; unsigned long get_free_page() { for (int i = 0; i < PAGING_PAGES; i++){ if (mem_map[i] == 0){ mem_map[i] = 1; return LOW_MEMORY + i*PAGE_SIZE; } } return 0; } void free_page(unsigned long p){ mem_map[(p - LOW_MEMORY) / PAGE_SIZE] = 0; } ================================================ FILE: exercises/lesson04/1/avenito/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson04/1/avenito/src/sched.S ================================================ #include "sched.h" .globl cpu_switch_to cpu_switch_to: mov x10, #THREAD_CPU_CONTEXT add x8, x0, x10 mov x9, sp stp x19, x20, [x8], #16 // store callee-saved registers stp x21, x22, [x8], #16 stp x23, x24, [x8], #16 stp x25, x26, [x8], #16 stp x27, x28, [x8], #16 stp x29, x9, [x8], #16 str x30, [x8] add x8, x1, x10 ldp x19, x20, [x8], #16 // restore callee-saved registers ldp x21, x22, [x8], #16 ldp x23, x24, [x8], #16 ldp x25, x26, [x8], #16 ldp x27, x28, [x8], #16 ldp x29, x9, [x8], #16 ldr x30, [x8] mov sp, x9 ret ================================================ FILE: exercises/lesson04/1/avenito/src/sched.c ================================================ #include "sched.h" #include "irq.h" #include "printf.h" static struct task_struct init_task = INIT_TASK; struct task_struct *current = &(init_task); struct task_struct * task[NR_TASKS] = {&(init_task), }; int nr_tasks = 1; void preempt_disable(void) { current->preempt_count++; } void preempt_enable(void) { current->preempt_count--; } void _schedule(void) { preempt_disable(); int next,c; struct task_struct * p; while (1) { c = -1; next = 0; for (int i = 0; i < NR_TASKS; i++){ p = task[i]; if (p && p->state == TASK_RUNNING && p->counter > c) { c = p->counter; next = i; } } if (c) { break; } for (int i = 0; i < NR_TASKS; i++) { p = task[i]; if (p) { p->counter = (p->counter >> 1) + p->priority; } } } switch_to(task[next]); preempt_enable(); } void schedule(void) { current->counter = 0; _schedule(); } void switch_to(struct task_struct * next) { struct task_struct * p; printf("\n\r\n\r----------- Task switch -----------\r\n"); for(int t=0; t < NR_TASKS; t++) { p = task[t]; printf("\n\rtask[%d] counter = %d\n\r", t, p->counter); printf("task[%d] priority = %d\n\r", t, p->priority); printf("task[%d] preempt_count = %d\n\r", t, p->preempt_count); printf("task[%d] sp = 0x%08x\n\r", t, p->cpu_context.sp); printf("\n\r------------------------------\r\n"); } printf("\n\rtask output: "); if (current == next) return; struct task_struct * prev = current; current = next; cpu_switch_to(prev, next); } void schedule_tail(void) { preempt_enable(); } void timer_tick() { --current->counter; if (current->counter>0 || current->preempt_count >0) { return; } current->counter=0; enable_irq(); _schedule(); disable_irq(); } ================================================ FILE: exercises/lesson04/1/avenito/src/timer.c ================================================ #include "utils.h" #include "printf.h" #include "sched.h" #include "peripherals/timer.h" const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); timer_tick(); } ================================================ FILE: exercises/lesson04/1/avenito/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson04/1/bl4ckout31/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only -DPRINTF_SUPPORT_LONG ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson04/1/bl4ckout31/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson04/1/bl4ckout31/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson04/1/bl4ckout31/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #endif ================================================ FILE: exercises/lesson04/1/bl4ckout31/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 272 // size of all saved registers #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #ifndef __ASSEMBLER__ void ret_from_fork(void); #endif #endif ================================================ FILE: exercises/lesson04/1/bl4ckout31/include/fork.h ================================================ #ifndef _FORK_H #define _FORK_H int copy_process(unsigned long fn, unsigned long arg); #endif ================================================ FILE: exercises/lesson04/1/bl4ckout31/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson04/1/bl4ckout31/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson04/1/bl4ckout31/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #include "peripherals/base.h" #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #define HIGH_MEMORY PBASE #define PAGING_MEMORY (HIGH_MEMORY - LOW_MEMORY) #define PAGING_PAGES (PAGING_MEMORY/PAGE_SIZE) #ifndef __ASSEMBLER__ unsigned long get_free_page(); void free_page(unsigned long p); void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson04/1/bl4ckout31/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson04/1/bl4ckout31/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson04/1/bl4ckout31/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson04/1/bl4ckout31/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson04/1/bl4ckout31/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson04/1/bl4ckout31/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson04/1/bl4ckout31/include/sched.h ================================================ #ifndef _SCHED_H #define _SCHED_H #define THREAD_CPU_CONTEXT 0 // offset of cpu_context in task_struct #ifndef __ASSEMBLER__ #define THREAD_SIZE 4096 #define NR_TASKS 64 #define FIRST_TASK task[0] #define LAST_TASK task[NR_TASKS-1] #define TASK_RUNNING 0 extern struct task_struct *current; extern struct task_struct * task[NR_TASKS]; extern int nr_tasks; struct cpu_context { unsigned long x19; unsigned long x20; unsigned long x21; unsigned long x22; unsigned long x23; unsigned long x24; unsigned long x25; unsigned long x26; unsigned long x27; unsigned long x28; unsigned long fp; unsigned long sp; unsigned long pc; }; struct task_struct { struct cpu_context cpu_context; long state; long counter; long priority; long preempt_count; }; extern void sched_init(void); extern void schedule(void); extern void timer_tick(void); extern void preempt_disable(void); extern void preempt_enable(void); extern void switch_to(struct task_struct* next); extern void cpu_switch_to(struct task_struct* prev, struct task_struct* next); void print_tasks(); #define INIT_TASK \ /*cpu_context*/ { {0,0,0,0,0,0,0,0,0,0,0,0,0}, \ /* state etc */ 0,0,1, 0 \ } #endif #endif ================================================ FILE: exercises/lesson04/1/bl4ckout31/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson04/1/bl4ckout31/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson04/1/bl4ckout31/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #(2 * SECTION_SIZE) bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson04/1/bl4ckout31/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson04/1/bl4ckout31/src/entry.S ================================================ #include "entry.h" .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] mrs x22, elr_el1 mrs x23, spsr_el1 stp x30, x22, [sp, #16 * 15] str x23, [sp, #16 * 16] .endm .macro kernel_exit ldr x23, [sp, #16 * 16] ldp x30, x22, [sp, #16 * 15] msr elr_el1, x22 msr spsr_el1, x23 ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 ventry irq_invalid_el0_64 // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry ERROR_INVALID_EL1h sync_invalid_el0_64: handle_invalid_entry SYNC_INVALID_EL0_64 irq_invalid_el0_64: handle_invalid_entry IRQ_INVALID_EL0_64 fiq_invalid_el0_64: handle_invalid_entry FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry ERROR_INVALID_EL0_32 el1_irq: kernel_entry bl handle_irq kernel_exit .globl ret_from_fork ret_from_fork: bl schedule_tail mov x0, x20 blr x19 //should never return .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson04/1/bl4ckout31/src/fork.c ================================================ #include "mm.h" #include "sched.h" #include "entry.h" #include "printf.h" int copy_process(unsigned long fn, unsigned long arg) { preempt_disable(); struct task_struct *p; p = (struct task_struct *) get_free_page(); if (!p) return 1; p->priority = current->priority; p->state = TASK_RUNNING; p->counter = p->priority; p->preempt_count = 1; //disable preemtion until schedule_tail p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)p + THREAD_SIZE; int pid = nr_tasks++; task[pid] = p; printf("Process created, pid: %d, sp: 0x%x\r\n", pid, p->cpu_context.sp); preempt_enable(); return 0; } ================================================ FILE: exercises/lesson04/1/bl4ckout31/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson04/1/bl4ckout31/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "timer.h" #include "entry.h" #include "peripherals/irq.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32" }; void enable_interrupt_controller() { put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { unsigned int irq = get32(IRQ_PENDING_1); switch (irq) { case (SYSTEM_TIMER_IRQ_1): handle_timer_irq(); break; default: printf("Inknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson04/1/bl4ckout31/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "timer.h" #include "irq.h" #include "fork.h" #include "sched.h" #include "mini_uart.h" void process(char *array) { while (1){ for (int i = 0; i < 5; i++){ //uart_send(array[i]); delay(1000000); } } } void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); int res = copy_process((unsigned long)&process, (unsigned long)"12345"); if (res != 0) { printf("error while starting process 1"); return; } res = copy_process((unsigned long)&process, (unsigned long)"abcde"); if (res != 0) { printf("error while starting process 2"); return; } while (1){ schedule(); } } ================================================ FILE: exercises/lesson04/1/bl4ckout31/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson04/1/bl4ckout31/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson04/1/bl4ckout31/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson04/1/bl4ckout31/src/mm.c ================================================ #include "mm.h" static unsigned short mem_map [ PAGING_PAGES ] = {0,}; unsigned long get_free_page() { for (int i = 0; i < PAGING_PAGES; i++){ if (mem_map[i] == 0){ mem_map[i] = 1; return LOW_MEMORY + i*PAGE_SIZE; } } return 0; } void free_page(unsigned long p){ mem_map[(p - LOW_MEMORY) / PAGE_SIZE] = 0; } ================================================ FILE: exercises/lesson04/1/bl4ckout31/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson04/1/bl4ckout31/src/sched.S ================================================ #include "sched.h" .globl cpu_switch_to cpu_switch_to: mov x10, #THREAD_CPU_CONTEXT add x8, x0, x10 mov x9, sp stp x19, x20, [x8], #16 // store callee-saved registers stp x21, x22, [x8], #16 stp x23, x24, [x8], #16 stp x25, x26, [x8], #16 stp x27, x28, [x8], #16 stp x29, x9, [x8], #16 str x30, [x8] add x8, x1, x10 ldp x19, x20, [x8], #16 // restore callee-saved registers ldp x21, x22, [x8], #16 ldp x23, x24, [x8], #16 ldp x25, x26, [x8], #16 ldp x27, x28, [x8], #16 ldp x29, x9, [x8], #16 ldr x30, [x8] mov sp, x9 ret ================================================ FILE: exercises/lesson04/1/bl4ckout31/src/sched.c ================================================ #include "sched.h" #include "irq.h" #include "printf.h" #include "utils.h" static struct task_struct init_task = INIT_TASK; struct task_struct *current = &(init_task); struct task_struct * task[NR_TASKS] = {&(init_task), }; int nr_tasks = 1; void preempt_disable(void) { current->preempt_count++; } void preempt_enable(void) { current->preempt_count--; } void _schedule(void) { preempt_disable(); int next,c; struct task_struct * p; while (1) { c = -1; next = 0; for (int i = 0; i < NR_TASKS; i++){ p = task[i]; if (p && p->state == TASK_RUNNING && p->counter > c) { c = p->counter; next = i; } } if (c) { break; } for (int i = 0; i < NR_TASKS; i++) { p = task[i]; if (p) { p->counter = (p->counter >> 1) + p->priority; } } } printf("\r\nswitch to task %d\r\n", next); printf("Tasks state:\r\n"); print_tasks(); switch_to(task[next]); preempt_enable(); } void schedule(void) { current->counter = 0; _schedule(); } void switch_to(struct task_struct * next) { if (current == next) return; struct task_struct * prev = current; current = next; cpu_switch_to(prev, next); } void schedule_tail(void) { preempt_enable(); } void timer_tick() { --current->counter; if (current->counter>0 || current->preempt_count >0) { return; } current->counter=0; enable_irq(); _schedule(); disable_irq(); } void print_tasks() { struct task_struct *t = task[0]; for (int i = 0; (i < NR_TASKS) && t; i++){ t = task[i]; if (t) { printf(" %d: sp: 0x%x\r\n", i, t->cpu_context.sp); } } } ================================================ FILE: exercises/lesson04/1/bl4ckout31/src/timer.c ================================================ #include "utils.h" #include "printf.h" #include "sched.h" #include "peripherals/timer.h" const unsigned int interval = 2000000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); timer_tick(); } ================================================ FILE: exercises/lesson04/1/bl4ckout31/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson04/1/bl4ckout31/start.sh ================================================ #!/bin/bash mount -L rpiboot /mnt cp "kernel8.img" /mnt umount /mnt ================================================ FILE: exercises/lesson04/1/rs/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel7.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel7.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel7.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel7.elf -O binary kernel7.img ================================================ FILE: exercises/lesson04/1/rs/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson04/1/rs/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson04/1/rs/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of // AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED \ (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | \ SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of // AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of // AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of // AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #endif ================================================ FILE: exercises/lesson04/1/rs/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 272 // size of all saved registers #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #ifndef __ASSEMBLER__ void ret_from_fork(void); #endif #endif ================================================ FILE: exercises/lesson04/1/rs/include/fork.h ================================================ #ifndef _FORK_H #define _FORK_H int copy_process(unsigned long fn, unsigned long arg); #endif ================================================ FILE: exercises/lesson04/1/rs/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller(void); void irq_vector_init(void); void enable_irq(void); void disable_irq(void); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson04/1/rs/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init(void); char uart_recv(void); void uart_send(char c); void putc(void *p, char c); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson04/1/rs/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #include "peripherals/base.h" #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #define HIGH_MEMORY PBASE #define PAGING_MEMORY (HIGH_MEMORY - LOW_MEMORY) #define PAGING_PAGES (PAGING_MEMORY / PAGE_SIZE) #ifndef __ASSEMBLER__ unsigned long get_free_page(); void free_page(unsigned long p); void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson04/1/rs/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson04/1/rs/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE + 0x00200004) #define GPSET0 (PBASE + 0x0020001C) #define GPCLR0 (PBASE + 0x00200028) #define GPPUD (PBASE + 0x00200094) #define GPPUDCLK0 (PBASE + 0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson04/1/rs/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE + 0x0000B200) #define IRQ_PENDING_1 (PBASE + 0x0000B204) #define IRQ_PENDING_2 (PBASE + 0x0000B208) #define FIQ_CONTROL (PBASE + 0x0000B20C) #define ENABLE_IRQS_1 (PBASE + 0x0000B210) #define ENABLE_IRQS_2 (PBASE + 0x0000B214) #define ENABLE_BASIC_IRQS (PBASE + 0x0000B218) #define DISABLE_IRQS_1 (PBASE + 0x0000B21C) #define DISABLE_IRQS_2 (PBASE + 0x0000B220) #define DISABLE_BASIC_IRQS (PBASE + 0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson04/1/rs/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE + 0x00215004) #define AUX_MU_IO_REG (PBASE + 0x00215040) #define AUX_MU_IER_REG (PBASE + 0x00215044) #define AUX_MU_IIR_REG (PBASE + 0x00215048) #define AUX_MU_LCR_REG (PBASE + 0x0021504C) #define AUX_MU_MCR_REG (PBASE + 0x00215050) #define AUX_MU_LSR_REG (PBASE + 0x00215054) #define AUX_MU_MSR_REG (PBASE + 0x00215058) #define AUX_MU_SCRATCH (PBASE + 0x0021505C) #define AUX_MU_CNTL_REG (PBASE + 0x00215060) #define AUX_MU_STAT_REG (PBASE + 0x00215064) #define AUX_MU_BAUD_REG (PBASE + 0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson04/1/rs/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE + 0x00003000) #define TIMER_CLO (PBASE + 0x00003004) #define TIMER_CHI (PBASE + 0x00003008) #define TIMER_C0 (PBASE + 0x0000300C) #define TIMER_C1 (PBASE + 0x00003010) #define TIMER_C2 (PBASE + 0x00003014) #define TIMER_C3 (PBASE + 0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson04/1/rs/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void *putp, void (*putf)(void *, char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char *s, char *fmt, ...); void tfp_format(void *putp, void (*putf)(void *, char), char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson04/1/rs/include/sched.h ================================================ #ifndef _SCHED_H #define _SCHED_H #define THREAD_CPU_CONTEXT 0 // offset of cpu_context in task_struct #ifndef __ASSEMBLER__ #define THREAD_SIZE 4096 #define NR_TASKS 64 #define FIRST_TASK task[0] #define LAST_TASK task[NR_TASKS - 1] #define TASK_RUNNING 0 extern struct task_struct *current; extern struct task_struct *task[NR_TASKS]; extern int nr_tasks; struct cpu_context { unsigned long x19; unsigned long x20; unsigned long x21; unsigned long x22; unsigned long x23; unsigned long x24; unsigned long x25; unsigned long x26; unsigned long x27; unsigned long x28; unsigned long fp; unsigned long sp; unsigned long pc; }; struct task_struct { struct cpu_context cpu_context; long state; long counter; long priority; long preempt_count; }; extern void sched_init(void); extern void schedule(void); extern void timer_tick(void); extern void preempt_disable(void); extern void preempt_enable(void); extern void switch_to(struct task_struct *next); extern void cpu_switch_to(struct task_struct *prev, struct task_struct *next); #define INIT_TASK \ /*cpu_context*/ { \ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, /* state etc */ 0, 0, 1, 0 \ } #endif #endif ================================================ FILE: exercises/lesson04/1/rs/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init(void); void handle_timer_irq(void); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson04/1/rs/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay(unsigned long); extern void put32(unsigned long, unsigned int); extern unsigned int get32(unsigned long); extern int get_el(void); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson04/1/rs/output.txt ================================================ copy_process: pid=1, sp=401000 copy_process: pid=2, sp=402000 schedule switch_to 1 tasks: [0] sp=0 [1] sp=401000 [2] sp=402000 schedule switch_to 2 tasks: [0] sp=3fff70 [1] sp=401000 [2] sp=402000 schedule switch_to 0 tasks: [0] sp=3fff70 [1] sp=400e20 [2] sp=402000 schedule switch_to 1 tasks: [0] sp=3fff70 [1] sp=400e20 [2] sp=401e20 schedule switch_to 2 tasks: [0] sp=3fff70 [1] sp=400e20 [2] sp=401e20 schedule switch_to 0 tasks: [0] sp=3fff70 [1] sp=400e20 [2] sp=401e20 schedule switch_to 1 tasks: [0] sp=3fff70 [1] sp=400e20 [2] sp=401e20 schedule switch_to 2 tasks: [0] sp=3fff70 [1] sp=400e20 [2] sp=401e20 ================================================ FILE: exercises/lesson04/1/rs/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #(2 * SECTION_SIZE) bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson04/1/rs/src/config.txt ================================================ arm_control=0x200 kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson04/1/rs/src/entry.S ================================================ #include "entry.h" .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] mrs x22, elr_el1 mrs x23, spsr_el1 stp x30, x22, [sp, #16 * 15] str x23, [sp, #16 * 16] .endm .macro kernel_exit ldr x23, [sp, #16 * 16] ldp x30, x22, [sp, #16 * 15] msr elr_el1, x22 msr spsr_el1, x23 ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 ventry irq_invalid_el0_64 // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry ERROR_INVALID_EL1h sync_invalid_el0_64: handle_invalid_entry SYNC_INVALID_EL0_64 irq_invalid_el0_64: handle_invalid_entry IRQ_INVALID_EL0_64 fiq_invalid_el0_64: handle_invalid_entry FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry ERROR_INVALID_EL0_32 el1_irq: kernel_entry bl handle_irq kernel_exit .globl ret_from_fork ret_from_fork: bl schedule_tail mov x0, x20 blr x19 //should never return .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson04/1/rs/src/fork.c ================================================ #include "entry.h" #include "mm.h" #include "printf.h" #include "sched.h" int copy_process(unsigned long fn, unsigned long arg) { preempt_disable(); struct task_struct *p; p = (struct task_struct *)get_free_page(); if (!p) return 1; p->priority = current->priority; p->state = TASK_RUNNING; p->counter = p->priority; p->preempt_count = 1; // disable preemtion untill schedule_tail p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)p + THREAD_SIZE; int pid = nr_tasks++; task[pid] = p; printf("copy_process: pid=%d, sp=%x\r\n", pid, p->cpu_context.sp); preempt_enable(); return 0; } ================================================ FILE: exercises/lesson04/1/rs/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson04/1/rs/src/irq.c ================================================ #include "peripherals/irq.h" #include "entry.h" #include "printf.h" #include "timer.h" #include "utils.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32"}; void enable_interrupt_controller() { put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { unsigned int irq = get32(IRQ_PENDING_1); switch (irq) { case (SYSTEM_TIMER_IRQ_1): handle_timer_irq(); break; default: printf("Inknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson04/1/rs/src/kernel.c ================================================ #include "fork.h" #include "irq.h" #include "mini_uart.h" #include "printf.h" #include "sched.h" #include "timer.h" #include "utils.h" void process(char *array) { while (1) { for (int i = 0; i < 5; i++) { // uart_send(array[i]); delay(100000); } } } void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); int res = copy_process((unsigned long)&process, (unsigned long)"12345"); if (res != 0) { printf("error while starting process 1"); return; } res = copy_process((unsigned long)&process, (unsigned long)"abcde"); if (res != 0) { printf("error while starting process 2"); return; } while (1) { schedule(); } } ================================================ FILE: exercises/lesson04/1/rs/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson04/1/rs/src/mini_uart.c ================================================ #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" #include "utils.h" void uart_send(char c) { while (1) { if (get32(AUX_MU_LSR_REG) & 0x20) break; } put32(AUX_MU_IO_REG, c); } char uart_recv(void) { while (1) { if (get32(AUX_MU_LSR_REG) & 0x01) break; } return (get32(AUX_MU_IO_REG) & 0xFF); } void uart_send_string(char *str) { for (int i = 0; str[i] != '\0'; i++) { uart_send((char)str[i]); } } void uart_init(void) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7 << 12); // clean gpio14 selector |= 2 << 12; // set alt5 for gpio14 selector &= ~(7 << 15); // clean gpio15 selector |= 2 << 15; // set alt5 for gpio15 put32(GPFSEL1, selector); put32(GPPUD, 0); delay(150); put32(GPPUDCLK0, (1 << 14) | (1 << 15)); delay(150); put32(GPPUDCLK0, 0); put32(AUX_ENABLES, 1); // Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG, 0); // Disable auto flow control and disable receiver // and transmitter (for now) put32(AUX_MU_IER_REG, 0); // Disable receive and transmit interrupts put32(AUX_MU_LCR_REG, 3); // Enable 8 bit mode put32(AUX_MU_MCR_REG, 0); // Set RTS line to be always high put32(AUX_MU_BAUD_REG, 270); // Set baud rate to 115200 put32(AUX_MU_CNTL_REG, 3); // Finally, enable transmitter and receiver } // This function is required by printf function void putc(void *p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson04/1/rs/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson04/1/rs/src/mm.c ================================================ #include "mm.h" static unsigned short mem_map[PAGING_PAGES] = { 0, }; unsigned long get_free_page() { for (int i = 0; i < PAGING_PAGES; i++) { if (mem_map[i] == 0) { mem_map[i] = 1; return LOW_MEMORY + i * PAGE_SIZE; } } return 0; } void free_page(unsigned long p) { mem_map[(p - LOW_MEMORY) / PAGE_SIZE] = 0; } ================================================ FILE: exercises/lesson04/1/rs/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf)(void *, char); static putcf stdout_putf; static void *stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc, char *bf) { int n = 0; unsigned int d = 1; while (num / d >= base) d *= base; while (d != 0) { int dgt = num / d; num %= d; d /= base; if (n || dgt > 0 || d == 0) { *bf++ = dgt + (dgt < 10 ? '0' : (uc ? 'A' : 'a') - 10); ++n; } } *bf = 0; } static void li2a(long num, char *bf) { if (num < 0) { num = -num; *bf++ = '-'; } uli2a(num, 10, 0, bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc, char *bf) { int n = 0; unsigned int d = 1; while (num / d >= base) d *= base; while (d != 0) { int dgt = num / d; num %= d; d /= base; if (n || dgt > 0 || d == 0) { *bf++ = dgt + (dgt < 10 ? '0' : (uc ? 'A' : 'a') - 10); ++n; } } *bf = 0; } static void i2a(int num, char *bf) { if (num < 0) { num = -num; *bf++ = '-'; } ui2a(num, 10, 0, bf); } static int a2d(char ch) { if (ch >= '0' && ch <= '9') return ch - '0'; else if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10; else if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10; else return -1; } static char a2i(char ch, char **src, int base, int *nump) { char *p = *src; int num = 0; int digit; while ((digit = a2d(ch)) >= 0) { if (digit > base) break; num = num * base + digit; ch = *p++; } *src = p; *nump = num; return ch; } static void putchw(void *putp, putcf putf, int n, char z, char *bf) { char fc = z ? '0' : ' '; char ch; char *p = bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp, fc); while ((ch = *bf++)) putf(putp, ch); } void tfp_format(void *putp, putcf putf, char *fmt, va_list va) { char bf[12]; char ch; while ((ch = *(fmt++))) { if (ch != '%') putf(putp, ch); else { char lz = 0; #ifdef PRINTF_LONG_SUPPORT char lng = 0; #endif int w = 0; ch = *(fmt++); if (ch == '0') { ch = *(fmt++); lz = 1; } if (ch >= '0' && ch <= '9') { ch = a2i(ch, &fmt, 10, &w); } #ifdef PRINTF_LONG_SUPPORT if (ch == 'l') { ch = *(fmt++); lng = 1; } #endif switch (ch) { case 0: goto abort; case 'u': { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int), 10, 0, bf); else #endif ui2a(va_arg(va, unsigned int), 10, 0, bf); putchw(putp, putf, w, lz, bf); break; } case 'd': { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int), bf); else #endif i2a(va_arg(va, int), bf); putchw(putp, putf, w, lz, bf); break; } case 'x': case 'X': #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int), 16, (ch == 'X'), bf); else #endif ui2a(va_arg(va, unsigned int), 16, (ch == 'X'), bf); putchw(putp, putf, w, lz, bf); break; case 'c': putf(putp, (char)(va_arg(va, int))); break; case 's': putchw(putp, putf, w, 0, va_arg(va, char *)); break; case '%': putf(putp, ch); default: break; } } } abort:; } void init_printf(void *putp, void (*putf)(void *, char)) { stdout_putf = putf; stdout_putp = putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va, fmt); tfp_format(stdout_putp, stdout_putf, fmt, va); va_end(va); } static void putcp(void *p, char c) { *(*((char **)p))++ = c; } void tfp_sprintf(char *s, char *fmt, ...) { va_list va; va_start(va, fmt); tfp_format(&s, putcp, fmt, va); putcp(&s, 0); va_end(va); } ================================================ FILE: exercises/lesson04/1/rs/src/sched.S ================================================ #include "sched.h" .globl cpu_switch_to cpu_switch_to: mov x10, #THREAD_CPU_CONTEXT add x8, x0, x10 mov x9, sp stp x19, x20, [x8], #16 // store callee-saved registers stp x21, x22, [x8], #16 stp x23, x24, [x8], #16 stp x25, x26, [x8], #16 stp x27, x28, [x8], #16 stp x29, x9, [x8], #16 str x30, [x8] add x8, x1, x10 ldp x19, x20, [x8], #16 // restore callee-saved registers ldp x21, x22, [x8], #16 ldp x23, x24, [x8], #16 ldp x25, x26, [x8], #16 ldp x27, x28, [x8], #16 ldp x29, x9, [x8], #16 ldr x30, [x8] mov sp, x9 ret ================================================ FILE: exercises/lesson04/1/rs/src/sched.c ================================================ #include "sched.h" #include "irq.h" #include "printf.h" static struct task_struct init_task = INIT_TASK; struct task_struct *current = &(init_task); struct task_struct *task[NR_TASKS] = { &(init_task), }; int nr_tasks = 1; void preempt_disable(void) { current->preempt_count++; } void preempt_enable(void) { current->preempt_count--; } void print_debug_tasks(void) { printf("tasks:\r\n"); struct task_struct *p; for (int i = 0; i < NR_TASKS; i++) { p = task[i]; if (!p) return; printf("[%d] sp=%x\r\n", i, p->cpu_context.sp); } } void _schedule(void) { preempt_disable(); int next, c; struct task_struct *p; while (1) { c = -1; next = 0; for (int i = 0; i < NR_TASKS; i++) { p = task[i]; if (p && p->state == TASK_RUNNING && p->counter > c) { c = p->counter; next = i; } } if (c) { break; } for (int i = 0; i < NR_TASKS; i++) { p = task[i]; if (p) { p->counter = (p->counter >> 1) + p->priority; } } } if (task[next] != current) { printf("schedule switch_to %d\r\n", next); print_debug_tasks(); } switch_to(task[next]); preempt_enable(); } void schedule(void) { current->counter = 0; _schedule(); } void switch_to(struct task_struct *next) { if (current == next) return; struct task_struct *prev = current; current = next; cpu_switch_to(prev, next); } void schedule_tail(void) { preempt_enable(); } void timer_tick() { --current->counter; if (current->counter > 0 || current->preempt_count > 0) { return; } current->counter = 0; enable_irq(); _schedule(); disable_irq(); } ================================================ FILE: exercises/lesson04/1/rs/src/timer.c ================================================ #include "peripherals/timer.h" #include "printf.h" #include "sched.h" #include "utils.h" const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init(void) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq(void) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); timer_tick(); } ================================================ FILE: exercises/lesson04/1/rs/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1, [x0] ret .globl get32 get32: ldr w0, [x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson04/2/avenito/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson04/2/avenito/README.md ================================================ # Task Priority To assign priority to the task, the new parameter 'pri' was added. int copy_process(unsigned long fn, unsigned long arg, unsigned int pri) # Results Assigning priority, is possible to check that one task print much more characters than the other. (Priority 3) task output: 345123451234512345123451234512 (Priority 1) task output: eabcde ================================================ FILE: exercises/lesson04/2/avenito/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson04/2/avenito/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson04/2/avenito/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #endif ================================================ FILE: exercises/lesson04/2/avenito/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 272 // size of all saved registers #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #ifndef __ASSEMBLER__ void ret_from_fork(void); #endif #endif ================================================ FILE: exercises/lesson04/2/avenito/include/fork.h ================================================ #ifndef _FORK_H #define _FORK_H // Parameter pri added to assign priority int copy_process(unsigned long fn, unsigned long arg, unsigned int pri); #endif ================================================ FILE: exercises/lesson04/2/avenito/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson04/2/avenito/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson04/2/avenito/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #include "peripherals/base.h" #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #define HIGH_MEMORY PBASE #define PAGING_MEMORY (HIGH_MEMORY - LOW_MEMORY) #define PAGING_PAGES (PAGING_MEMORY/PAGE_SIZE) #ifndef __ASSEMBLER__ unsigned long get_free_page(); void free_page(unsigned long p); void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson04/2/avenito/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson04/2/avenito/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson04/2/avenito/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson04/2/avenito/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson04/2/avenito/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson04/2/avenito/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson04/2/avenito/include/sched.h ================================================ #ifndef _SCHED_H #define _SCHED_H #define THREAD_CPU_CONTEXT 0 // offset of cpu_context in task_struct #ifndef __ASSEMBLER__ #define THREAD_SIZE 4096 #define NR_TASKS 3 // changed to 3 to be easier #define FIRST_TASK task[0] #define LAST_TASK task[NR_TASKS-1] #define TASK_RUNNING 0 extern struct task_struct *current; extern struct task_struct * task[NR_TASKS]; extern int nr_tasks; struct cpu_context { unsigned long x19; unsigned long x20; unsigned long x21; unsigned long x22; unsigned long x23; unsigned long x24; unsigned long x25; unsigned long x26; unsigned long x27; unsigned long x28; unsigned long fp; unsigned long sp; unsigned long pc; }; struct task_struct { struct cpu_context cpu_context; long state; long counter; long priority; long preempt_count; }; extern void sched_init(void); extern void schedule(void); extern void timer_tick(void); extern void preempt_disable(void); extern void preempt_enable(void); extern void switch_to(struct task_struct* next); extern void cpu_switch_to(struct task_struct* prev, struct task_struct* next); #define INIT_TASK \ /*cpu_context*/ { {0,0,0,0,0,0,0,0,0,0,0,0,0}, \ /* state etc */ 0,0,1, 0 \ } #endif #endif ================================================ FILE: exercises/lesson04/2/avenito/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson04/2/avenito/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson04/2/avenito/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #(2 * SECTION_SIZE) bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson04/2/avenito/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson04/2/avenito/src/entry.S ================================================ #include "entry.h" .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] mrs x22, elr_el1 mrs x23, spsr_el1 stp x30, x22, [sp, #16 * 15] str x23, [sp, #16 * 16] .endm .macro kernel_exit ldr x23, [sp, #16 * 16] ldp x30, x22, [sp, #16 * 15] msr elr_el1, x22 msr spsr_el1, x23 ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 ventry irq_invalid_el0_64 // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry ERROR_INVALID_EL1h sync_invalid_el0_64: handle_invalid_entry SYNC_INVALID_EL0_64 irq_invalid_el0_64: handle_invalid_entry IRQ_INVALID_EL0_64 fiq_invalid_el0_64: handle_invalid_entry FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry ERROR_INVALID_EL0_32 el1_irq: kernel_entry bl handle_irq kernel_exit .globl ret_from_fork ret_from_fork: bl schedule_tail mov x0, x20 blr x19 //should never return .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson04/2/avenito/src/fork.c ================================================ #include "printf.h" #include "mm.h" #include "sched.h" #include "entry.h" // Parameter pri added to assign priority int copy_process(unsigned long fn, unsigned long arg, unsigned int pri) { preempt_disable(); struct task_struct *p; p = (struct task_struct *) get_free_page(); if (!p) return 1; p->priority = pri; p->state = TASK_RUNNING; p->counter = pri; p->preempt_count = 1; //disable preemtion until schedule_tail p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)p + THREAD_SIZE; int pid = nr_tasks++; task[pid] = p; printf("\n\r----------- Task[%d] created -----------\r\n", pid); printf("\n\rStruct task allocated at 0x%08x.\r\n", p); printf("p->cpu_context.x19 = 0x%08x. (fn)\r\n", p->cpu_context.x19); printf("p->cpu_context.x20 = 0x%08x. (arg)\r\n", p->cpu_context.x20); printf("p->cpu_context.pc = 0x%08x. (ret_from_fork)\r\n", p->cpu_context.pc); printf("p->cpu_context.sp = 0x%08x. (sp)\r\n", p->cpu_context.sp); preempt_enable(); return 0; } ================================================ FILE: exercises/lesson04/2/avenito/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson04/2/avenito/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "timer.h" #include "entry.h" #include "peripherals/irq.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32" }; void enable_interrupt_controller() { put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { unsigned int irq = get32(IRQ_PENDING_1); switch (irq) { case (SYSTEM_TIMER_IRQ_1): handle_timer_irq(); break; default: printf("Inknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson04/2/avenito/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "timer.h" #include "irq.h" #include "fork.h" #include "sched.h" #include "mini_uart.h" void process(char *array) { while (1){ for (int i = 0; i < 5; i++){ uart_send(array[i]); delay(100000); } } } void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); int res = copy_process((unsigned long)&process, (unsigned long)"12345", (unsigned int) 3); if (res != 0) { printf("error while starting process 1"); return; } res = copy_process((unsigned long)&process, (unsigned long)"abcde", (unsigned int) 1); if (res != 0) { printf("error while starting process 2"); return; } while (1){ schedule(); } } ================================================ FILE: exercises/lesson04/2/avenito/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson04/2/avenito/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson04/2/avenito/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson04/2/avenito/src/mm.c ================================================ #include "mm.h" static unsigned short mem_map [ PAGING_PAGES ] = {0,}; unsigned long get_free_page() { for (int i = 0; i < PAGING_PAGES; i++){ if (mem_map[i] == 0){ mem_map[i] = 1; return LOW_MEMORY + i*PAGE_SIZE; } } return 0; } void free_page(unsigned long p){ mem_map[(p - LOW_MEMORY) / PAGE_SIZE] = 0; } ================================================ FILE: exercises/lesson04/2/avenito/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson04/2/avenito/src/sched.S ================================================ #include "sched.h" .globl cpu_switch_to cpu_switch_to: mov x10, #THREAD_CPU_CONTEXT add x8, x0, x10 mov x9, sp stp x19, x20, [x8], #16 // store callee-saved registers stp x21, x22, [x8], #16 stp x23, x24, [x8], #16 stp x25, x26, [x8], #16 stp x27, x28, [x8], #16 stp x29, x9, [x8], #16 str x30, [x8] add x8, x1, x10 ldp x19, x20, [x8], #16 // restore callee-saved registers ldp x21, x22, [x8], #16 ldp x23, x24, [x8], #16 ldp x25, x26, [x8], #16 ldp x27, x28, [x8], #16 ldp x29, x9, [x8], #16 ldr x30, [x8] mov sp, x9 ret ================================================ FILE: exercises/lesson04/2/avenito/src/sched.c ================================================ #include "sched.h" #include "irq.h" #include "printf.h" static struct task_struct init_task = INIT_TASK; struct task_struct *current = &(init_task); struct task_struct * task[NR_TASKS] = {&(init_task), }; int nr_tasks = 1; void preempt_disable(void) { current->preempt_count++; } void preempt_enable(void) { current->preempt_count--; } void _schedule(void) { preempt_disable(); int next,c; struct task_struct * p; while (1) { c = -1; next = 0; for (int i = 0; i < NR_TASKS; i++){ p = task[i]; if (p && p->state == TASK_RUNNING && p->counter > c) { c = p->counter; next = i; } } if (c) { break; } for (int i = 0; i < NR_TASKS; i++) { p = task[i]; if (p) { p->counter = (p->counter >> 1) + p->priority; } } } switch_to(task[next]); preempt_enable(); } void schedule(void) { current->counter = 0; _schedule(); } void switch_to(struct task_struct * next) { struct task_struct * p; printf("\n\r\n\r----------- Task switch -----------\r\n"); for(int t=0; t < NR_TASKS; t++) { p = task[t]; printf("\n\rtask[%d] counter = %d\n\r", t, p->counter); printf("task[%d] priority = %d\n\r", t, p->priority); printf("task[%d] preempt_count = %d\n\r", t, p->preempt_count); printf("task[%d] sp = 0x%08x\n\r", t, p->cpu_context.sp); printf("\n\r------------------------------\r\n"); } printf("\n\rtask output: "); if (current == next) return; struct task_struct * prev = current; current = next; cpu_switch_to(prev, next); } void schedule_tail(void) { preempt_enable(); } void timer_tick() { --current->counter; if (current->counter>0 || current->preempt_count >0) { return; } current->counter=0; enable_irq(); _schedule(); disable_irq(); } ================================================ FILE: exercises/lesson04/2/avenito/src/timer.c ================================================ #include "utils.h" #include "printf.h" #include "sched.h" #include "peripherals/timer.h" const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); timer_tick(); } ================================================ FILE: exercises/lesson04/2/avenito/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson04/2/bl4ckout31/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson04/2/bl4ckout31/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson04/2/bl4ckout31/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson04/2/bl4ckout31/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #endif ================================================ FILE: exercises/lesson04/2/bl4ckout31/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 272 // size of all saved registers #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #ifndef __ASSEMBLER__ void ret_from_fork(void); #endif #endif ================================================ FILE: exercises/lesson04/2/bl4ckout31/include/fork.h ================================================ #ifndef _FORK_H #define _FORK_H int copy_process(unsigned long fn, unsigned int priority, unsigned long arg); #endif ================================================ FILE: exercises/lesson04/2/bl4ckout31/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson04/2/bl4ckout31/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson04/2/bl4ckout31/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #include "peripherals/base.h" #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #define HIGH_MEMORY PBASE #define PAGING_MEMORY (HIGH_MEMORY - LOW_MEMORY) #define PAGING_PAGES (PAGING_MEMORY/PAGE_SIZE) #ifndef __ASSEMBLER__ unsigned long get_free_page(); void free_page(unsigned long p); void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson04/2/bl4ckout31/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson04/2/bl4ckout31/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson04/2/bl4ckout31/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson04/2/bl4ckout31/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson04/2/bl4ckout31/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson04/2/bl4ckout31/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson04/2/bl4ckout31/include/sched.h ================================================ #ifndef _SCHED_H #define _SCHED_H #define THREAD_CPU_CONTEXT 0 // offset of cpu_context in task_struct #ifndef __ASSEMBLER__ #define THREAD_SIZE 4096 #define NR_TASKS 64 #define FIRST_TASK task[0] #define LAST_TASK task[NR_TASKS-1] #define TASK_RUNNING 0 extern struct task_struct *current; extern struct task_struct * task[NR_TASKS]; extern int nr_tasks; struct cpu_context { unsigned long x19; unsigned long x20; unsigned long x21; unsigned long x22; unsigned long x23; unsigned long x24; unsigned long x25; unsigned long x26; unsigned long x27; unsigned long x28; unsigned long fp; unsigned long sp; unsigned long pc; }; struct task_struct { struct cpu_context cpu_context; long state; long counter; long priority; long preempt_count; }; extern void sched_init(void); extern void schedule(void); extern void timer_tick(void); extern void preempt_disable(void); extern void preempt_enable(void); extern void switch_to(struct task_struct* next); extern void cpu_switch_to(struct task_struct* prev, struct task_struct* next); #define INIT_TASK \ /*cpu_context*/ { {0,0,0,0,0,0,0,0,0,0,0,0,0}, \ /* state etc */ 0,0,1, 0 \ } #endif #endif ================================================ FILE: exercises/lesson04/2/bl4ckout31/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson04/2/bl4ckout31/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson04/2/bl4ckout31/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #(2 * SECTION_SIZE) bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson04/2/bl4ckout31/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson04/2/bl4ckout31/src/entry.S ================================================ #include "entry.h" .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] mrs x22, elr_el1 mrs x23, spsr_el1 stp x30, x22, [sp, #16 * 15] str x23, [sp, #16 * 16] .endm .macro kernel_exit ldr x23, [sp, #16 * 16] ldp x30, x22, [sp, #16 * 15] msr elr_el1, x22 msr spsr_el1, x23 ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 ventry irq_invalid_el0_64 // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry ERROR_INVALID_EL1h sync_invalid_el0_64: handle_invalid_entry SYNC_INVALID_EL0_64 irq_invalid_el0_64: handle_invalid_entry IRQ_INVALID_EL0_64 fiq_invalid_el0_64: handle_invalid_entry FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry ERROR_INVALID_EL0_32 el1_irq: kernel_entry bl handle_irq kernel_exit .globl ret_from_fork ret_from_fork: bl schedule_tail mov x0, x20 blr x19 //should never return .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson04/2/bl4ckout31/src/fork.c ================================================ #include "mm.h" #include "sched.h" #include "entry.h" int copy_process(unsigned long fn, unsigned int priority, unsigned long arg) { preempt_disable(); struct task_struct *p; p = (struct task_struct *) get_free_page(); if (!p) return 1; p->priority = priority; p->state = TASK_RUNNING; p->counter = p->priority; p->preempt_count = 1; //disable preemtion until schedule_tail p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)p + THREAD_SIZE; int pid = nr_tasks++; task[pid] = p; preempt_enable(); return 0; } ================================================ FILE: exercises/lesson04/2/bl4ckout31/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson04/2/bl4ckout31/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "timer.h" #include "entry.h" #include "peripherals/irq.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32" }; void enable_interrupt_controller() { put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { unsigned int irq = get32(IRQ_PENDING_1); switch (irq) { case (SYSTEM_TIMER_IRQ_1): handle_timer_irq(); break; default: printf("Inknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson04/2/bl4ckout31/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "timer.h" #include "irq.h" #include "fork.h" #include "sched.h" #include "mini_uart.h" void process(char *array) { while (1){ for (int i = 0; i < 5; i++){ uart_send(array[i]); delay(100000); } } } void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); int res = copy_process((unsigned long)&process, 1, (unsigned long)"12345"); if (res != 0) { printf("error while starting process 1"); return; } res = copy_process((unsigned long)&process, 4, (unsigned long)"abcde"); if (res != 0) { printf("error while starting process 2"); return; } while (1){ schedule(); } } ================================================ FILE: exercises/lesson04/2/bl4ckout31/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson04/2/bl4ckout31/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson04/2/bl4ckout31/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson04/2/bl4ckout31/src/mm.c ================================================ #include "mm.h" static unsigned short mem_map [ PAGING_PAGES ] = {0,}; unsigned long get_free_page() { for (int i = 0; i < PAGING_PAGES; i++){ if (mem_map[i] == 0){ mem_map[i] = 1; return LOW_MEMORY + i*PAGE_SIZE; } } return 0; } void free_page(unsigned long p){ mem_map[(p - LOW_MEMORY) / PAGE_SIZE] = 0; } ================================================ FILE: exercises/lesson04/2/bl4ckout31/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson04/2/bl4ckout31/src/sched.S ================================================ #include "sched.h" .globl cpu_switch_to cpu_switch_to: mov x10, #THREAD_CPU_CONTEXT add x8, x0, x10 mov x9, sp stp x19, x20, [x8], #16 // store callee-saved registers stp x21, x22, [x8], #16 stp x23, x24, [x8], #16 stp x25, x26, [x8], #16 stp x27, x28, [x8], #16 stp x29, x9, [x8], #16 str x30, [x8] add x8, x1, x10 ldp x19, x20, [x8], #16 // restore callee-saved registers ldp x21, x22, [x8], #16 ldp x23, x24, [x8], #16 ldp x25, x26, [x8], #16 ldp x27, x28, [x8], #16 ldp x29, x9, [x8], #16 ldr x30, [x8] mov sp, x9 ret ================================================ FILE: exercises/lesson04/2/bl4ckout31/src/sched.c ================================================ #include "sched.h" #include "irq.h" #include "printf.h" static struct task_struct init_task = INIT_TASK; struct task_struct *current = &(init_task); struct task_struct * task[NR_TASKS] = {&(init_task), }; int nr_tasks = 1; void preempt_disable(void) { current->preempt_count++; } void preempt_enable(void) { current->preempt_count--; } void _schedule(void) { preempt_disable(); int next,c; struct task_struct * p; while (1) { c = -1; next = 0; for (int i = 0; i < NR_TASKS; i++){ p = task[i]; if (p && p->state == TASK_RUNNING && p->counter > c) { c = p->counter; next = i; } } if (c) { break; } for (int i = 0; i < NR_TASKS; i++) { p = task[i]; if (p) { p->counter = (p->counter >> 1) + p->priority; } } } switch_to(task[next]); preempt_enable(); } void schedule(void) { current->counter = 0; _schedule(); } void switch_to(struct task_struct * next) { if (current == next) return; struct task_struct * prev = current; current = next; cpu_switch_to(prev, next); } void schedule_tail(void) { preempt_enable(); } void timer_tick() { --current->counter; if (current->counter>0 || current->preempt_count >0) { return; } current->counter=0; enable_irq(); _schedule(); disable_irq(); } ================================================ FILE: exercises/lesson04/2/bl4ckout31/src/timer.c ================================================ #include "utils.h" #include "printf.h" #include "sched.h" #include "peripherals/timer.h" const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); timer_tick(); } ================================================ FILE: exercises/lesson04/2/bl4ckout31/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson04/2/bl4ckout31/start.sh ================================================ #!/bin/bash mount -L rpiboot /mnt cp "kernel8.img" /mnt umount /mnt ================================================ FILE: exercises/lesson04/2/rs/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel7.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel7.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel7.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel7.elf -O binary kernel7.img ================================================ FILE: exercises/lesson04/2/rs/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson04/2/rs/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson04/2/rs/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of // AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED \ (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | \ SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of // AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of // AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of // AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #endif ================================================ FILE: exercises/lesson04/2/rs/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 272 // size of all saved registers #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #ifndef __ASSEMBLER__ void ret_from_fork(void); #endif #endif ================================================ FILE: exercises/lesson04/2/rs/include/fork.h ================================================ #ifndef _FORK_H #define _FORK_H int copy_process(unsigned long fn, unsigned long arg, long pri); #endif ================================================ FILE: exercises/lesson04/2/rs/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller(void); void irq_vector_init(void); void enable_irq(void); void disable_irq(void); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson04/2/rs/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init(void); char uart_recv(void); void uart_send(char c); void putc(void *p, char c); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson04/2/rs/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #include "peripherals/base.h" #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #define HIGH_MEMORY PBASE #define PAGING_MEMORY (HIGH_MEMORY - LOW_MEMORY) #define PAGING_PAGES (PAGING_MEMORY / PAGE_SIZE) #ifndef __ASSEMBLER__ unsigned long get_free_page(); void free_page(unsigned long p); void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson04/2/rs/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson04/2/rs/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE + 0x00200004) #define GPSET0 (PBASE + 0x0020001C) #define GPCLR0 (PBASE + 0x00200028) #define GPPUD (PBASE + 0x00200094) #define GPPUDCLK0 (PBASE + 0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson04/2/rs/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE + 0x0000B200) #define IRQ_PENDING_1 (PBASE + 0x0000B204) #define IRQ_PENDING_2 (PBASE + 0x0000B208) #define FIQ_CONTROL (PBASE + 0x0000B20C) #define ENABLE_IRQS_1 (PBASE + 0x0000B210) #define ENABLE_IRQS_2 (PBASE + 0x0000B214) #define ENABLE_BASIC_IRQS (PBASE + 0x0000B218) #define DISABLE_IRQS_1 (PBASE + 0x0000B21C) #define DISABLE_IRQS_2 (PBASE + 0x0000B220) #define DISABLE_BASIC_IRQS (PBASE + 0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson04/2/rs/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE + 0x00215004) #define AUX_MU_IO_REG (PBASE + 0x00215040) #define AUX_MU_IER_REG (PBASE + 0x00215044) #define AUX_MU_IIR_REG (PBASE + 0x00215048) #define AUX_MU_LCR_REG (PBASE + 0x0021504C) #define AUX_MU_MCR_REG (PBASE + 0x00215050) #define AUX_MU_LSR_REG (PBASE + 0x00215054) #define AUX_MU_MSR_REG (PBASE + 0x00215058) #define AUX_MU_SCRATCH (PBASE + 0x0021505C) #define AUX_MU_CNTL_REG (PBASE + 0x00215060) #define AUX_MU_STAT_REG (PBASE + 0x00215064) #define AUX_MU_BAUD_REG (PBASE + 0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson04/2/rs/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE + 0x00003000) #define TIMER_CLO (PBASE + 0x00003004) #define TIMER_CHI (PBASE + 0x00003008) #define TIMER_C0 (PBASE + 0x0000300C) #define TIMER_C1 (PBASE + 0x00003010) #define TIMER_C2 (PBASE + 0x00003014) #define TIMER_C3 (PBASE + 0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson04/2/rs/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void *putp, void (*putf)(void *, char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char *s, char *fmt, ...); void tfp_format(void *putp, void (*putf)(void *, char), char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson04/2/rs/include/sched.h ================================================ #ifndef _SCHED_H #define _SCHED_H #define THREAD_CPU_CONTEXT 0 // offset of cpu_context in task_struct #ifndef __ASSEMBLER__ #define THREAD_SIZE 4096 #define NR_TASKS 64 #define FIRST_TASK task[0] #define LAST_TASK task[NR_TASKS - 1] #define TASK_RUNNING 0 extern struct task_struct *current; extern struct task_struct *task[NR_TASKS]; extern int nr_tasks; struct cpu_context { unsigned long x19; unsigned long x20; unsigned long x21; unsigned long x22; unsigned long x23; unsigned long x24; unsigned long x25; unsigned long x26; unsigned long x27; unsigned long x28; unsigned long fp; unsigned long sp; unsigned long pc; }; struct task_struct { struct cpu_context cpu_context; long state; long counter; long priority; long preempt_count; }; extern void sched_init(void); extern void schedule(void); extern void timer_tick(void); extern void preempt_disable(void); extern void preempt_enable(void); extern void switch_to(struct task_struct *next); extern void cpu_switch_to(struct task_struct *prev, struct task_struct *next); #define INIT_TASK \ /*cpu_context*/ { \ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, /* state etc */ 0, 0, 1, 0 \ } #endif #endif ================================================ FILE: exercises/lesson04/2/rs/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init(void); void handle_timer_irq(void); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson04/2/rs/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay(unsigned long); extern void put32(unsigned long, unsigned int); extern unsigned int get32(unsigned long); extern int get_el(void); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson04/2/rs/output.txt ================================================ copy_process: pid=1, sp=401000 copy_process: pid=2, sp=402000 schedule switch_to 1 tasks: [0] sp=0 [1] sp=401000 [2] sp=402000 schedule switch_to 2 tasks: [0] sp=3fff70 [1] sp=401000 [2] sp=402000 schedule switch_to 0 tasks: [0] sp=3fff70 [1] sp=400e20 [2] sp=402000 schedule switch_to 1 tasks: [0] sp=3fff70 [1] sp=400e20 [2] sp=401e20 schedule switch_to 2 tasks: [0] sp=3fff70 [1] sp=400e20 [2] sp=401e20 schedule switch_to 0 tasks: [0] sp=3fff70 [1] sp=400e20 [2] sp=401e20 schedule switch_to 1 tasks: [0] sp=3fff70 [1] sp=400e20 [2] sp=401e20 schedule switch_to 2 tasks: [0] sp=3fff70 [1] sp=400e20 [2] sp=401e20 ================================================ FILE: exercises/lesson04/2/rs/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #(2 * SECTION_SIZE) bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson04/2/rs/src/config.txt ================================================ arm_control=0x200 kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson04/2/rs/src/entry.S ================================================ #include "entry.h" .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] mrs x22, elr_el1 mrs x23, spsr_el1 stp x30, x22, [sp, #16 * 15] str x23, [sp, #16 * 16] .endm .macro kernel_exit ldr x23, [sp, #16 * 16] ldp x30, x22, [sp, #16 * 15] msr elr_el1, x22 msr spsr_el1, x23 ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 ventry irq_invalid_el0_64 // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry ERROR_INVALID_EL1h sync_invalid_el0_64: handle_invalid_entry SYNC_INVALID_EL0_64 irq_invalid_el0_64: handle_invalid_entry IRQ_INVALID_EL0_64 fiq_invalid_el0_64: handle_invalid_entry FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry ERROR_INVALID_EL0_32 el1_irq: kernel_entry bl handle_irq kernel_exit .globl ret_from_fork ret_from_fork: bl schedule_tail mov x0, x20 blr x19 //should never return .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson04/2/rs/src/fork.c ================================================ #include "entry.h" #include "mm.h" #include "sched.h" int copy_process(unsigned long fn, unsigned long arg, long pri) { preempt_disable(); struct task_struct *p; p = (struct task_struct *)get_free_page(); if (!p) return 1; p->priority = pri; p->state = TASK_RUNNING; p->counter = p->priority; p->preempt_count = 1; // disable preemtion untill schedule_tail p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)p + THREAD_SIZE; int pid = nr_tasks++; task[pid] = p; preempt_enable(); return 0; } ================================================ FILE: exercises/lesson04/2/rs/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson04/2/rs/src/irq.c ================================================ #include "peripherals/irq.h" #include "entry.h" #include "printf.h" #include "timer.h" #include "utils.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32"}; void enable_interrupt_controller() { put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { unsigned int irq = get32(IRQ_PENDING_1); switch (irq) { case (SYSTEM_TIMER_IRQ_1): handle_timer_irq(); break; default: printf("Inknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson04/2/rs/src/kernel.c ================================================ #include "fork.h" #include "irq.h" #include "mini_uart.h" #include "printf.h" #include "sched.h" #include "timer.h" #include "utils.h" void process(char *array) { while (1) { for (int i = 0; i < 5; i++) { uart_send(array[i]); delay(100000); } } } void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); int res = copy_process((unsigned long)&process, (unsigned long)"12345", 1); if (res != 0) { printf("error while starting process 1"); return; } res = copy_process((unsigned long)&process, (unsigned long)"abcde", 2); if (res != 0) { printf("error while starting process 2"); return; } while (1) { schedule(); } } ================================================ FILE: exercises/lesson04/2/rs/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson04/2/rs/src/mini_uart.c ================================================ #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" #include "utils.h" void uart_send(char c) { while (1) { if (get32(AUX_MU_LSR_REG) & 0x20) break; } put32(AUX_MU_IO_REG, c); } char uart_recv(void) { while (1) { if (get32(AUX_MU_LSR_REG) & 0x01) break; } return (get32(AUX_MU_IO_REG) & 0xFF); } void uart_send_string(char *str) { for (int i = 0; str[i] != '\0'; i++) { uart_send((char)str[i]); } } void uart_init(void) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7 << 12); // clean gpio14 selector |= 2 << 12; // set alt5 for gpio14 selector &= ~(7 << 15); // clean gpio15 selector |= 2 << 15; // set alt5 for gpio15 put32(GPFSEL1, selector); put32(GPPUD, 0); delay(150); put32(GPPUDCLK0, (1 << 14) | (1 << 15)); delay(150); put32(GPPUDCLK0, 0); put32(AUX_ENABLES, 1); // Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG, 0); // Disable auto flow control and disable receiver // and transmitter (for now) put32(AUX_MU_IER_REG, 0); // Disable receive and transmit interrupts put32(AUX_MU_LCR_REG, 3); // Enable 8 bit mode put32(AUX_MU_MCR_REG, 0); // Set RTS line to be always high put32(AUX_MU_BAUD_REG, 270); // Set baud rate to 115200 put32(AUX_MU_CNTL_REG, 3); // Finally, enable transmitter and receiver } // This function is required by printf function void putc(void *p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson04/2/rs/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson04/2/rs/src/mm.c ================================================ #include "mm.h" static unsigned short mem_map[PAGING_PAGES] = { 0, }; unsigned long get_free_page() { for (int i = 0; i < PAGING_PAGES; i++) { if (mem_map[i] == 0) { mem_map[i] = 1; return LOW_MEMORY + i * PAGE_SIZE; } } return 0; } void free_page(unsigned long p) { mem_map[(p - LOW_MEMORY) / PAGE_SIZE] = 0; } ================================================ FILE: exercises/lesson04/2/rs/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf)(void *, char); static putcf stdout_putf; static void *stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc, char *bf) { int n = 0; unsigned int d = 1; while (num / d >= base) d *= base; while (d != 0) { int dgt = num / d; num %= d; d /= base; if (n || dgt > 0 || d == 0) { *bf++ = dgt + (dgt < 10 ? '0' : (uc ? 'A' : 'a') - 10); ++n; } } *bf = 0; } static void li2a(long num, char *bf) { if (num < 0) { num = -num; *bf++ = '-'; } uli2a(num, 10, 0, bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc, char *bf) { int n = 0; unsigned int d = 1; while (num / d >= base) d *= base; while (d != 0) { int dgt = num / d; num %= d; d /= base; if (n || dgt > 0 || d == 0) { *bf++ = dgt + (dgt < 10 ? '0' : (uc ? 'A' : 'a') - 10); ++n; } } *bf = 0; } static void i2a(int num, char *bf) { if (num < 0) { num = -num; *bf++ = '-'; } ui2a(num, 10, 0, bf); } static int a2d(char ch) { if (ch >= '0' && ch <= '9') return ch - '0'; else if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10; else if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10; else return -1; } static char a2i(char ch, char **src, int base, int *nump) { char *p = *src; int num = 0; int digit; while ((digit = a2d(ch)) >= 0) { if (digit > base) break; num = num * base + digit; ch = *p++; } *src = p; *nump = num; return ch; } static void putchw(void *putp, putcf putf, int n, char z, char *bf) { char fc = z ? '0' : ' '; char ch; char *p = bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp, fc); while ((ch = *bf++)) putf(putp, ch); } void tfp_format(void *putp, putcf putf, char *fmt, va_list va) { char bf[12]; char ch; while ((ch = *(fmt++))) { if (ch != '%') putf(putp, ch); else { char lz = 0; #ifdef PRINTF_LONG_SUPPORT char lng = 0; #endif int w = 0; ch = *(fmt++); if (ch == '0') { ch = *(fmt++); lz = 1; } if (ch >= '0' && ch <= '9') { ch = a2i(ch, &fmt, 10, &w); } #ifdef PRINTF_LONG_SUPPORT if (ch == 'l') { ch = *(fmt++); lng = 1; } #endif switch (ch) { case 0: goto abort; case 'u': { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int), 10, 0, bf); else #endif ui2a(va_arg(va, unsigned int), 10, 0, bf); putchw(putp, putf, w, lz, bf); break; } case 'd': { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int), bf); else #endif i2a(va_arg(va, int), bf); putchw(putp, putf, w, lz, bf); break; } case 'x': case 'X': #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int), 16, (ch == 'X'), bf); else #endif ui2a(va_arg(va, unsigned int), 16, (ch == 'X'), bf); putchw(putp, putf, w, lz, bf); break; case 'c': putf(putp, (char)(va_arg(va, int))); break; case 's': putchw(putp, putf, w, 0, va_arg(va, char *)); break; case '%': putf(putp, ch); default: break; } } } abort:; } void init_printf(void *putp, void (*putf)(void *, char)) { stdout_putf = putf; stdout_putp = putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va, fmt); tfp_format(stdout_putp, stdout_putf, fmt, va); va_end(va); } static void putcp(void *p, char c) { *(*((char **)p))++ = c; } void tfp_sprintf(char *s, char *fmt, ...) { va_list va; va_start(va, fmt); tfp_format(&s, putcp, fmt, va); putcp(&s, 0); va_end(va); } ================================================ FILE: exercises/lesson04/2/rs/src/sched.S ================================================ #include "sched.h" .globl cpu_switch_to cpu_switch_to: mov x10, #THREAD_CPU_CONTEXT add x8, x0, x10 mov x9, sp stp x19, x20, [x8], #16 // store callee-saved registers stp x21, x22, [x8], #16 stp x23, x24, [x8], #16 stp x25, x26, [x8], #16 stp x27, x28, [x8], #16 stp x29, x9, [x8], #16 str x30, [x8] add x8, x1, x10 ldp x19, x20, [x8], #16 // restore callee-saved registers ldp x21, x22, [x8], #16 ldp x23, x24, [x8], #16 ldp x25, x26, [x8], #16 ldp x27, x28, [x8], #16 ldp x29, x9, [x8], #16 ldr x30, [x8] mov sp, x9 ret ================================================ FILE: exercises/lesson04/2/rs/src/sched.c ================================================ #include "sched.h" #include "irq.h" #include "printf.h" static struct task_struct init_task = INIT_TASK; struct task_struct *current = &(init_task); struct task_struct *task[NR_TASKS] = { &(init_task), }; int nr_tasks = 1; void preempt_disable(void) { current->preempt_count++; } void preempt_enable(void) { current->preempt_count--; } void _schedule(void) { preempt_disable(); int next, c; struct task_struct *p; while (1) { c = -1; next = 0; for (int i = 0; i < NR_TASKS; i++) { p = task[i]; if (p && p->state == TASK_RUNNING && p->counter > c) { c = p->counter; next = i; } } if (c) { break; } for (int i = 0; i < NR_TASKS; i++) { p = task[i]; if (p) { p->counter = (p->counter >> 1) + p->priority; } } } switch_to(task[next]); preempt_enable(); } void schedule(void) { current->counter = 0; _schedule(); } void switch_to(struct task_struct *next) { if (current == next) return; struct task_struct *prev = current; current = next; cpu_switch_to(prev, next); } void schedule_tail(void) { preempt_enable(); } void timer_tick() { --current->counter; if (current->counter > 0 || current->preempt_count > 0) { return; } current->counter = 0; enable_irq(); _schedule(); disable_irq(); } ================================================ FILE: exercises/lesson04/2/rs/src/timer.c ================================================ #include "peripherals/timer.h" #include "printf.h" #include "sched.h" #include "utils.h" const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init(void) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq(void) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); timer_tick(); } ================================================ FILE: exercises/lesson04/2/rs/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1, [x0] ret .globl get32 get32: ldr w0, [x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson04/3/avenito/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson04/3/avenito/README.md ================================================ # To use FP/SIMD registers Remove the "-mgeneral-regs-only" option in Makefile. # FD/SIMD Registers To use these registers, first of all (as in the Exercise 2 - Lesson 2) we have to use the 'cpacr_el1'. See 'boot.S'. To switch the tasks we have to store and restore as exactly as we do to the CPU registers. ARM Architecture Reference Manual ARMv8, for ARMv8-A architecture profile, available [here](https://developer.arm.com/docs/ddi0487/latest/arm-architecture-reference-manual-armv8-for-armv8-a-architecture-profile), page 146 describes the 32 registers in the SIMD and floating-point register file, V0-V31. ARM Cortex-A53 MPCore Processor Advanced SIMD and Floating-point Extension, available [here](http://infocenter.arm.com/help/topic/com.arm.doc.ddi0502e/DDI0502E_cortex_a53_fpu_r0p3_trm.pdf), page 14 describes the processor Advanced SIMD and Floating-point system registers. # To save and restore the FP/SIMD registers 1. Add a struct to save the registers in 'sched.h': 32 x 128 bits to V0-V31 + Floating-point Control Register (FPCR) + Floating-point Status Register (FPSR). 1. Change also in INIT_TASK. 1. Add code to store and restore the FP/SIMD register in 'sched.S'. ================================================ FILE: exercises/lesson04/3/avenito/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson04/3/avenito/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson04/3/avenito/file.txt ================================================ build/boot_s.o: file format elf64-littleaarch64 Disassembly of section .text.boot: 0000000000000000 <_start>: 0: d53800a0 mrs x0, mpidr_el1 4: 92401c00 and x0, x0, #0xff 8: b4000060 cbz x0, 14 c: 14000001 b 10 0000000000000010 : 10: 14000000 b 10 0000000000000014 : 14: 58000260 ldr x0, 60 18: d5181000 msr sctlr_el1, x0 1c: 58000260 ldr x0, 68 20: d51c1100 msr hcr_el2, x0 24: 58000260 ldr x0, 70 28: d51e1100 msr scr_el3, x0 2c: 58000260 ldr x0, 78 30: d51e4000 msr spsr_el3, x0 34: 10000060 adr x0, 40 38: d51e4020 msr elr_el3, x0 3c: d69f03e0 eret 0000000000000040 : 40: 10000000 adr x0, 0 44: 10000001 adr x1, 0 48: cb000021 sub x1, x1, x0 4c: 94000000 bl 0 50: b26a03ff mov sp, #0x400000 // #4194304 54: 94000000 bl 0 58: 17ffffee b 10 5c: 00000000 .word 0x00000000 60: 30d00800 .word 0x30d00800 64: 00000000 .word 0x00000000 68: 80000000 .word 0x80000000 6c: 00000000 .word 0x00000000 70: 00000431 .word 0x00000431 74: 00000000 .word 0x00000000 78: 000001c5 .word 0x000001c5 7c: 00000000 .word 0x00000000 build/entry_s.o: file format elf64-littleaarch64 Disassembly of section .text: 0000000000000000 : 0: 140001e1 b 784 4: d503201f nop 8: d503201f nop c: d503201f nop 10: d503201f nop 14: d503201f nop 18: d503201f nop 1c: d503201f nop 20: d503201f nop 24: d503201f nop 28: d503201f nop 2c: d503201f nop 30: d503201f nop 34: d503201f nop 38: d503201f nop 3c: d503201f nop 40: d503201f nop 44: d503201f nop 48: d503201f nop 4c: d503201f nop 50: d503201f nop 54: d503201f nop 58: d503201f nop 5c: d503201f nop 60: d503201f nop 64: d503201f nop 68: d503201f nop 6c: d503201f nop 70: d503201f nop 74: d503201f nop 78: d503201f nop 7c: d503201f nop 80: 140001da b 7e8 84: d503201f nop 88: d503201f nop 8c: d503201f nop 90: d503201f nop 94: d503201f nop 98: d503201f nop 9c: d503201f nop a0: d503201f nop a4: d503201f nop a8: d503201f nop ac: d503201f nop b0: d503201f nop b4: d503201f nop b8: d503201f nop bc: d503201f nop c0: d503201f nop c4: d503201f nop c8: d503201f nop cc: d503201f nop d0: d503201f nop d4: d503201f nop d8: d503201f nop dc: d503201f nop e0: d503201f nop e4: d503201f nop e8: d503201f nop ec: d503201f nop f0: d503201f nop f4: d503201f nop f8: d503201f nop fc: d503201f nop 100: 140001d3 b 84c 104: d503201f nop 108: d503201f nop 10c: d503201f nop 110: d503201f nop 114: d503201f nop 118: d503201f nop 11c: d503201f nop 120: d503201f nop 124: d503201f nop 128: d503201f nop 12c: d503201f nop 130: d503201f nop 134: d503201f nop 138: d503201f nop 13c: d503201f nop 140: d503201f nop 144: d503201f nop 148: d503201f nop 14c: d503201f nop 150: d503201f nop 154: d503201f nop 158: d503201f nop 15c: d503201f nop 160: d503201f nop 164: d503201f nop 168: d503201f nop 16c: d503201f nop 170: d503201f nop 174: d503201f nop 178: d503201f nop 17c: d503201f nop 180: 140001cc b 8b0 184: d503201f nop 188: d503201f nop 18c: d503201f nop 190: d503201f nop 194: d503201f nop 198: d503201f nop 19c: d503201f nop 1a0: d503201f nop 1a4: d503201f nop 1a8: d503201f nop 1ac: d503201f nop 1b0: d503201f nop 1b4: d503201f nop 1b8: d503201f nop 1bc: d503201f nop 1c0: d503201f nop 1c4: d503201f nop 1c8: d503201f nop 1cc: d503201f nop 1d0: d503201f nop 1d4: d503201f nop 1d8: d503201f nop 1dc: d503201f nop 1e0: d503201f nop 1e4: d503201f nop 1e8: d503201f nop 1ec: d503201f nop 1f0: d503201f nop 1f4: d503201f nop 1f8: d503201f nop 1fc: d503201f nop 200: 140001c5 b 914 204: d503201f nop 208: d503201f nop 20c: d503201f nop 210: d503201f nop 214: d503201f nop 218: d503201f nop 21c: d503201f nop 220: d503201f nop 224: d503201f nop 228: d503201f nop 22c: d503201f nop 230: d503201f nop 234: d503201f nop 238: d503201f nop 23c: d503201f nop 240: d503201f nop 244: d503201f nop 248: d503201f nop 24c: d503201f nop 250: d503201f nop 254: d503201f nop 258: d503201f nop 25c: d503201f nop 260: d503201f nop 264: d503201f nop 268: d503201f nop 26c: d503201f nop 270: d503201f nop 274: d503201f nop 278: d503201f nop 27c: d503201f nop 280: 140002b8 b d60 284: d503201f nop 288: d503201f nop 28c: d503201f nop 290: d503201f nop 294: d503201f nop 298: d503201f nop 29c: d503201f nop 2a0: d503201f nop 2a4: d503201f nop 2a8: d503201f nop 2ac: d503201f nop 2b0: d503201f nop 2b4: d503201f nop 2b8: d503201f nop 2bc: d503201f nop 2c0: d503201f nop 2c4: d503201f nop 2c8: d503201f nop 2cc: d503201f nop 2d0: d503201f nop 2d4: d503201f nop 2d8: d503201f nop 2dc: d503201f nop 2e0: d503201f nop 2e4: d503201f nop 2e8: d503201f nop 2ec: d503201f nop 2f0: d503201f nop 2f4: d503201f nop 2f8: d503201f nop 2fc: d503201f nop 300: 1400019e b 978 304: d503201f nop 308: d503201f nop 30c: d503201f nop 310: d503201f nop 314: d503201f nop 318: d503201f nop 31c: d503201f nop 320: d503201f nop 324: d503201f nop 328: d503201f nop 32c: d503201f nop 330: d503201f nop 334: d503201f nop 338: d503201f nop 33c: d503201f nop 340: d503201f nop 344: d503201f nop 348: d503201f nop 34c: d503201f nop 350: d503201f nop 354: d503201f nop 358: d503201f nop 35c: d503201f nop 360: d503201f nop 364: d503201f nop 368: d503201f nop 36c: d503201f nop 370: d503201f nop 374: d503201f nop 378: d503201f nop 37c: d503201f nop 380: 14000197 b 9dc 384: d503201f nop 388: d503201f nop 38c: d503201f nop 390: d503201f nop 394: d503201f nop 398: d503201f nop 39c: d503201f nop 3a0: d503201f nop 3a4: d503201f nop 3a8: d503201f nop 3ac: d503201f nop 3b0: d503201f nop 3b4: d503201f nop 3b8: d503201f nop 3bc: d503201f nop 3c0: d503201f nop 3c4: d503201f nop 3c8: d503201f nop 3cc: d503201f nop 3d0: d503201f nop 3d4: d503201f nop 3d8: d503201f nop 3dc: d503201f nop 3e0: d503201f nop 3e4: d503201f nop 3e8: d503201f nop 3ec: d503201f nop 3f0: d503201f nop 3f4: d503201f nop 3f8: d503201f nop 3fc: d503201f nop 400: 14000190 b a40 404: d503201f nop 408: d503201f nop 40c: d503201f nop 410: d503201f nop 414: d503201f nop 418: d503201f nop 41c: d503201f nop 420: d503201f nop 424: d503201f nop 428: d503201f nop 42c: d503201f nop 430: d503201f nop 434: d503201f nop 438: d503201f nop 43c: d503201f nop 440: d503201f nop 444: d503201f nop 448: d503201f nop 44c: d503201f nop 450: d503201f nop 454: d503201f nop 458: d503201f nop 45c: d503201f nop 460: d503201f nop 464: d503201f nop 468: d503201f nop 46c: d503201f nop 470: d503201f nop 474: d503201f nop 478: d503201f nop 47c: d503201f nop 480: 14000189 b aa4 484: d503201f nop 488: d503201f nop 48c: d503201f nop 490: d503201f nop 494: d503201f nop 498: d503201f nop 49c: d503201f nop 4a0: d503201f nop 4a4: d503201f nop 4a8: d503201f nop 4ac: d503201f nop 4b0: d503201f nop 4b4: d503201f nop 4b8: d503201f nop 4bc: d503201f nop 4c0: d503201f nop 4c4: d503201f nop 4c8: d503201f nop 4cc: d503201f nop 4d0: d503201f nop 4d4: d503201f nop 4d8: d503201f nop 4dc: d503201f nop 4e0: d503201f nop 4e4: d503201f nop 4e8: d503201f nop 4ec: d503201f nop 4f0: d503201f nop 4f4: d503201f nop 4f8: d503201f nop 4fc: d503201f nop 500: 14000182 b b08 504: d503201f nop 508: d503201f nop 50c: d503201f nop 510: d503201f nop 514: d503201f nop 518: d503201f nop 51c: d503201f nop 520: d503201f nop 524: d503201f nop 528: d503201f nop 52c: d503201f nop 530: d503201f nop 534: d503201f nop 538: d503201f nop 53c: d503201f nop 540: d503201f nop 544: d503201f nop 548: d503201f nop 54c: d503201f nop 550: d503201f nop 554: d503201f nop 558: d503201f nop 55c: d503201f nop 560: d503201f nop 564: d503201f nop 568: d503201f nop 56c: d503201f nop 570: d503201f nop 574: d503201f nop 578: d503201f nop 57c: d503201f nop 580: 1400017b b b6c 584: d503201f nop 588: d503201f nop 58c: d503201f nop 590: d503201f nop 594: d503201f nop 598: d503201f nop 59c: d503201f nop 5a0: d503201f nop 5a4: d503201f nop 5a8: d503201f nop 5ac: d503201f nop 5b0: d503201f nop 5b4: d503201f nop 5b8: d503201f nop 5bc: d503201f nop 5c0: d503201f nop 5c4: d503201f nop 5c8: d503201f nop 5cc: d503201f nop 5d0: d503201f nop 5d4: d503201f nop 5d8: d503201f nop 5dc: d503201f nop 5e0: d503201f nop 5e4: d503201f nop 5e8: d503201f nop 5ec: d503201f nop 5f0: d503201f nop 5f4: d503201f nop 5f8: d503201f nop 5fc: d503201f nop 600: 14000174 b bd0 604: d503201f nop 608: d503201f nop 60c: d503201f nop 610: d503201f nop 614: d503201f nop 618: d503201f nop 61c: d503201f nop 620: d503201f nop 624: d503201f nop 628: d503201f nop 62c: d503201f nop 630: d503201f nop 634: d503201f nop 638: d503201f nop 63c: d503201f nop 640: d503201f nop 644: d503201f nop 648: d503201f nop 64c: d503201f nop 650: d503201f nop 654: d503201f nop 658: d503201f nop 65c: d503201f nop 660: d503201f nop 664: d503201f nop 668: d503201f nop 66c: d503201f nop 670: d503201f nop 674: d503201f nop 678: d503201f nop 67c: d503201f nop 680: 1400016d b c34 684: d503201f nop 688: d503201f nop 68c: d503201f nop 690: d503201f nop 694: d503201f nop 698: d503201f nop 69c: d503201f nop 6a0: d503201f nop 6a4: d503201f nop 6a8: d503201f nop 6ac: d503201f nop 6b0: d503201f nop 6b4: d503201f nop 6b8: d503201f nop 6bc: d503201f nop 6c0: d503201f nop 6c4: d503201f nop 6c8: d503201f nop 6cc: d503201f nop 6d0: d503201f nop 6d4: d503201f nop 6d8: d503201f nop 6dc: d503201f nop 6e0: d503201f nop 6e4: d503201f nop 6e8: d503201f nop 6ec: d503201f nop 6f0: d503201f nop 6f4: d503201f nop 6f8: d503201f nop 6fc: d503201f nop 700: 14000166 b c98 704: d503201f nop 708: d503201f nop 70c: d503201f nop 710: d503201f nop 714: d503201f nop 718: d503201f nop 71c: d503201f nop 720: d503201f nop 724: d503201f nop 728: d503201f nop 72c: d503201f nop 730: d503201f nop 734: d503201f nop 738: d503201f nop 73c: d503201f nop 740: d503201f nop 744: d503201f nop 748: d503201f nop 74c: d503201f nop 750: d503201f nop 754: d503201f nop 758: d503201f nop 75c: d503201f nop 760: d503201f nop 764: d503201f nop 768: d503201f nop 76c: d503201f nop 770: d503201f nop 774: d503201f nop 778: d503201f nop 77c: d503201f nop 780: 1400015f b cfc 0000000000000784 : 784: d10443ff sub sp, sp, #0x110 788: a90007e0 stp x0, x1, [sp] 78c: a9010fe2 stp x2, x3, [sp,#16] 790: a90217e4 stp x4, x5, [sp,#32] 794: a9031fe6 stp x6, x7, [sp,#48] 798: a90427e8 stp x8, x9, [sp,#64] 79c: a9052fea stp x10, x11, [sp,#80] 7a0: a90637ec stp x12, x13, [sp,#96] 7a4: a9073fee stp x14, x15, [sp,#112] 7a8: a90847f0 stp x16, x17, [sp,#128] 7ac: a9094ff2 stp x18, x19, [sp,#144] 7b0: a90a57f4 stp x20, x21, [sp,#160] 7b4: a90b5ff6 stp x22, x23, [sp,#176] 7b8: a90c67f8 stp x24, x25, [sp,#192] 7bc: a90d6ffa stp x26, x27, [sp,#208] 7c0: a90e77fc stp x28, x29, [sp,#224] 7c4: d5384036 mrs x22, elr_el1 7c8: d5384017 mrs x23, spsr_el1 7cc: a90f5bfe stp x30, x22, [sp,#240] 7d0: f90083f7 str x23, [sp,#256] 7d4: d2800000 mov x0, #0x0 // #0 7d8: d5385201 mrs x1, esr_el1 7dc: d5384022 mrs x2, elr_el1 7e0: 94000000 bl 0 7e4: 14000000 b e14 00000000000007e8 : 7e8: d10443ff sub sp, sp, #0x110 7ec: a90007e0 stp x0, x1, [sp] 7f0: a9010fe2 stp x2, x3, [sp,#16] 7f4: a90217e4 stp x4, x5, [sp,#32] 7f8: a9031fe6 stp x6, x7, [sp,#48] 7fc: a90427e8 stp x8, x9, [sp,#64] 800: a9052fea stp x10, x11, [sp,#80] 804: a90637ec stp x12, x13, [sp,#96] 808: a9073fee stp x14, x15, [sp,#112] 80c: a90847f0 stp x16, x17, [sp,#128] 810: a9094ff2 stp x18, x19, [sp,#144] 814: a90a57f4 stp x20, x21, [sp,#160] 818: a90b5ff6 stp x22, x23, [sp,#176] 81c: a90c67f8 stp x24, x25, [sp,#192] 820: a90d6ffa stp x26, x27, [sp,#208] 824: a90e77fc stp x28, x29, [sp,#224] 828: d5384036 mrs x22, elr_el1 82c: d5384017 mrs x23, spsr_el1 830: a90f5bfe stp x30, x22, [sp,#240] 834: f90083f7 str x23, [sp,#256] 838: d2800020 mov x0, #0x1 // #1 83c: d5385201 mrs x1, esr_el1 840: d5384022 mrs x2, elr_el1 844: 94000000 bl 0 848: 14000000 b e14 000000000000084c : 84c: d10443ff sub sp, sp, #0x110 850: a90007e0 stp x0, x1, [sp] 854: a9010fe2 stp x2, x3, [sp,#16] 858: a90217e4 stp x4, x5, [sp,#32] 85c: a9031fe6 stp x6, x7, [sp,#48] 860: a90427e8 stp x8, x9, [sp,#64] 864: a9052fea stp x10, x11, [sp,#80] 868: a90637ec stp x12, x13, [sp,#96] 86c: a9073fee stp x14, x15, [sp,#112] 870: a90847f0 stp x16, x17, [sp,#128] 874: a9094ff2 stp x18, x19, [sp,#144] 878: a90a57f4 stp x20, x21, [sp,#160] 87c: a90b5ff6 stp x22, x23, [sp,#176] 880: a90c67f8 stp x24, x25, [sp,#192] 884: a90d6ffa stp x26, x27, [sp,#208] 888: a90e77fc stp x28, x29, [sp,#224] 88c: d5384036 mrs x22, elr_el1 890: d5384017 mrs x23, spsr_el1 894: a90f5bfe stp x30, x22, [sp,#240] 898: f90083f7 str x23, [sp,#256] 89c: d2800040 mov x0, #0x2 // #2 8a0: d5385201 mrs x1, esr_el1 8a4: d5384022 mrs x2, elr_el1 8a8: 94000000 bl 0 8ac: 14000000 b e14 00000000000008b0 : 8b0: d10443ff sub sp, sp, #0x110 8b4: a90007e0 stp x0, x1, [sp] 8b8: a9010fe2 stp x2, x3, [sp,#16] 8bc: a90217e4 stp x4, x5, [sp,#32] 8c0: a9031fe6 stp x6, x7, [sp,#48] 8c4: a90427e8 stp x8, x9, [sp,#64] 8c8: a9052fea stp x10, x11, [sp,#80] 8cc: a90637ec stp x12, x13, [sp,#96] 8d0: a9073fee stp x14, x15, [sp,#112] 8d4: a90847f0 stp x16, x17, [sp,#128] 8d8: a9094ff2 stp x18, x19, [sp,#144] 8dc: a90a57f4 stp x20, x21, [sp,#160] 8e0: a90b5ff6 stp x22, x23, [sp,#176] 8e4: a90c67f8 stp x24, x25, [sp,#192] 8e8: a90d6ffa stp x26, x27, [sp,#208] 8ec: a90e77fc stp x28, x29, [sp,#224] 8f0: d5384036 mrs x22, elr_el1 8f4: d5384017 mrs x23, spsr_el1 8f8: a90f5bfe stp x30, x22, [sp,#240] 8fc: f90083f7 str x23, [sp,#256] 900: d2800060 mov x0, #0x3 // #3 904: d5385201 mrs x1, esr_el1 908: d5384022 mrs x2, elr_el1 90c: 94000000 bl 0 910: 14000000 b e14 0000000000000914 : 914: d10443ff sub sp, sp, #0x110 918: a90007e0 stp x0, x1, [sp] 91c: a9010fe2 stp x2, x3, [sp,#16] 920: a90217e4 stp x4, x5, [sp,#32] 924: a9031fe6 stp x6, x7, [sp,#48] 928: a90427e8 stp x8, x9, [sp,#64] 92c: a9052fea stp x10, x11, [sp,#80] 930: a90637ec stp x12, x13, [sp,#96] 934: a9073fee stp x14, x15, [sp,#112] 938: a90847f0 stp x16, x17, [sp,#128] 93c: a9094ff2 stp x18, x19, [sp,#144] 940: a90a57f4 stp x20, x21, [sp,#160] 944: a90b5ff6 stp x22, x23, [sp,#176] 948: a90c67f8 stp x24, x25, [sp,#192] 94c: a90d6ffa stp x26, x27, [sp,#208] 950: a90e77fc stp x28, x29, [sp,#224] 954: d5384036 mrs x22, elr_el1 958: d5384017 mrs x23, spsr_el1 95c: a90f5bfe stp x30, x22, [sp,#240] 960: f90083f7 str x23, [sp,#256] 964: d2800080 mov x0, #0x4 // #4 968: d5385201 mrs x1, esr_el1 96c: d5384022 mrs x2, elr_el1 970: 94000000 bl 0 974: 14000000 b e14 0000000000000978 : 978: d10443ff sub sp, sp, #0x110 97c: a90007e0 stp x0, x1, [sp] 980: a9010fe2 stp x2, x3, [sp,#16] 984: a90217e4 stp x4, x5, [sp,#32] 988: a9031fe6 stp x6, x7, [sp,#48] 98c: a90427e8 stp x8, x9, [sp,#64] 990: a9052fea stp x10, x11, [sp,#80] 994: a90637ec stp x12, x13, [sp,#96] 998: a9073fee stp x14, x15, [sp,#112] 99c: a90847f0 stp x16, x17, [sp,#128] 9a0: a9094ff2 stp x18, x19, [sp,#144] 9a4: a90a57f4 stp x20, x21, [sp,#160] 9a8: a90b5ff6 stp x22, x23, [sp,#176] 9ac: a90c67f8 stp x24, x25, [sp,#192] 9b0: a90d6ffa stp x26, x27, [sp,#208] 9b4: a90e77fc stp x28, x29, [sp,#224] 9b8: d5384036 mrs x22, elr_el1 9bc: d5384017 mrs x23, spsr_el1 9c0: a90f5bfe stp x30, x22, [sp,#240] 9c4: f90083f7 str x23, [sp,#256] 9c8: d28000c0 mov x0, #0x6 // #6 9cc: d5385201 mrs x1, esr_el1 9d0: d5384022 mrs x2, elr_el1 9d4: 94000000 bl 0 9d8: 14000000 b e14 00000000000009dc : 9dc: d10443ff sub sp, sp, #0x110 9e0: a90007e0 stp x0, x1, [sp] 9e4: a9010fe2 stp x2, x3, [sp,#16] 9e8: a90217e4 stp x4, x5, [sp,#32] 9ec: a9031fe6 stp x6, x7, [sp,#48] 9f0: a90427e8 stp x8, x9, [sp,#64] 9f4: a9052fea stp x10, x11, [sp,#80] 9f8: a90637ec stp x12, x13, [sp,#96] 9fc: a9073fee stp x14, x15, [sp,#112] a00: a90847f0 stp x16, x17, [sp,#128] a04: a9094ff2 stp x18, x19, [sp,#144] a08: a90a57f4 stp x20, x21, [sp,#160] a0c: a90b5ff6 stp x22, x23, [sp,#176] a10: a90c67f8 stp x24, x25, [sp,#192] a14: a90d6ffa stp x26, x27, [sp,#208] a18: a90e77fc stp x28, x29, [sp,#224] a1c: d5384036 mrs x22, elr_el1 a20: d5384017 mrs x23, spsr_el1 a24: a90f5bfe stp x30, x22, [sp,#240] a28: f90083f7 str x23, [sp,#256] a2c: d28000e0 mov x0, #0x7 // #7 a30: d5385201 mrs x1, esr_el1 a34: d5384022 mrs x2, elr_el1 a38: 94000000 bl 0 a3c: 14000000 b e14 0000000000000a40 : a40: d10443ff sub sp, sp, #0x110 a44: a90007e0 stp x0, x1, [sp] a48: a9010fe2 stp x2, x3, [sp,#16] a4c: a90217e4 stp x4, x5, [sp,#32] a50: a9031fe6 stp x6, x7, [sp,#48] a54: a90427e8 stp x8, x9, [sp,#64] a58: a9052fea stp x10, x11, [sp,#80] a5c: a90637ec stp x12, x13, [sp,#96] a60: a9073fee stp x14, x15, [sp,#112] a64: a90847f0 stp x16, x17, [sp,#128] a68: a9094ff2 stp x18, x19, [sp,#144] a6c: a90a57f4 stp x20, x21, [sp,#160] a70: a90b5ff6 stp x22, x23, [sp,#176] a74: a90c67f8 stp x24, x25, [sp,#192] a78: a90d6ffa stp x26, x27, [sp,#208] a7c: a90e77fc stp x28, x29, [sp,#224] a80: d5384036 mrs x22, elr_el1 a84: d5384017 mrs x23, spsr_el1 a88: a90f5bfe stp x30, x22, [sp,#240] a8c: f90083f7 str x23, [sp,#256] a90: d2800100 mov x0, #0x8 // #8 a94: d5385201 mrs x1, esr_el1 a98: d5384022 mrs x2, elr_el1 a9c: 94000000 bl 0 aa0: 14000000 b e14 0000000000000aa4 : aa4: d10443ff sub sp, sp, #0x110 aa8: a90007e0 stp x0, x1, [sp] aac: a9010fe2 stp x2, x3, [sp,#16] ab0: a90217e4 stp x4, x5, [sp,#32] ab4: a9031fe6 stp x6, x7, [sp,#48] ab8: a90427e8 stp x8, x9, [sp,#64] abc: a9052fea stp x10, x11, [sp,#80] ac0: a90637ec stp x12, x13, [sp,#96] ac4: a9073fee stp x14, x15, [sp,#112] ac8: a90847f0 stp x16, x17, [sp,#128] acc: a9094ff2 stp x18, x19, [sp,#144] ad0: a90a57f4 stp x20, x21, [sp,#160] ad4: a90b5ff6 stp x22, x23, [sp,#176] ad8: a90c67f8 stp x24, x25, [sp,#192] adc: a90d6ffa stp x26, x27, [sp,#208] ae0: a90e77fc stp x28, x29, [sp,#224] ae4: d5384036 mrs x22, elr_el1 ae8: d5384017 mrs x23, spsr_el1 aec: a90f5bfe stp x30, x22, [sp,#240] af0: f90083f7 str x23, [sp,#256] af4: d2800120 mov x0, #0x9 // #9 af8: d5385201 mrs x1, esr_el1 afc: d5384022 mrs x2, elr_el1 b00: 94000000 bl 0 b04: 14000000 b e14 0000000000000b08 : b08: d10443ff sub sp, sp, #0x110 b0c: a90007e0 stp x0, x1, [sp] b10: a9010fe2 stp x2, x3, [sp,#16] b14: a90217e4 stp x4, x5, [sp,#32] b18: a9031fe6 stp x6, x7, [sp,#48] b1c: a90427e8 stp x8, x9, [sp,#64] b20: a9052fea stp x10, x11, [sp,#80] b24: a90637ec stp x12, x13, [sp,#96] b28: a9073fee stp x14, x15, [sp,#112] b2c: a90847f0 stp x16, x17, [sp,#128] b30: a9094ff2 stp x18, x19, [sp,#144] b34: a90a57f4 stp x20, x21, [sp,#160] b38: a90b5ff6 stp x22, x23, [sp,#176] b3c: a90c67f8 stp x24, x25, [sp,#192] b40: a90d6ffa stp x26, x27, [sp,#208] b44: a90e77fc stp x28, x29, [sp,#224] b48: d5384036 mrs x22, elr_el1 b4c: d5384017 mrs x23, spsr_el1 b50: a90f5bfe stp x30, x22, [sp,#240] b54: f90083f7 str x23, [sp,#256] b58: d2800140 mov x0, #0xa // #10 b5c: d5385201 mrs x1, esr_el1 b60: d5384022 mrs x2, elr_el1 b64: 94000000 bl 0 b68: 14000000 b e14 0000000000000b6c : b6c: d10443ff sub sp, sp, #0x110 b70: a90007e0 stp x0, x1, [sp] b74: a9010fe2 stp x2, x3, [sp,#16] b78: a90217e4 stp x4, x5, [sp,#32] b7c: a9031fe6 stp x6, x7, [sp,#48] b80: a90427e8 stp x8, x9, [sp,#64] b84: a9052fea stp x10, x11, [sp,#80] b88: a90637ec stp x12, x13, [sp,#96] b8c: a9073fee stp x14, x15, [sp,#112] b90: a90847f0 stp x16, x17, [sp,#128] b94: a9094ff2 stp x18, x19, [sp,#144] b98: a90a57f4 stp x20, x21, [sp,#160] b9c: a90b5ff6 stp x22, x23, [sp,#176] ba0: a90c67f8 stp x24, x25, [sp,#192] ba4: a90d6ffa stp x26, x27, [sp,#208] ba8: a90e77fc stp x28, x29, [sp,#224] bac: d5384036 mrs x22, elr_el1 bb0: d5384017 mrs x23, spsr_el1 bb4: a90f5bfe stp x30, x22, [sp,#240] bb8: f90083f7 str x23, [sp,#256] bbc: d2800160 mov x0, #0xb // #11 bc0: d5385201 mrs x1, esr_el1 bc4: d5384022 mrs x2, elr_el1 bc8: 94000000 bl 0 bcc: 14000000 b e14 0000000000000bd0 : bd0: d10443ff sub sp, sp, #0x110 bd4: a90007e0 stp x0, x1, [sp] bd8: a9010fe2 stp x2, x3, [sp,#16] bdc: a90217e4 stp x4, x5, [sp,#32] be0: a9031fe6 stp x6, x7, [sp,#48] be4: a90427e8 stp x8, x9, [sp,#64] be8: a9052fea stp x10, x11, [sp,#80] bec: a90637ec stp x12, x13, [sp,#96] bf0: a9073fee stp x14, x15, [sp,#112] bf4: a90847f0 stp x16, x17, [sp,#128] bf8: a9094ff2 stp x18, x19, [sp,#144] bfc: a90a57f4 stp x20, x21, [sp,#160] c00: a90b5ff6 stp x22, x23, [sp,#176] c04: a90c67f8 stp x24, x25, [sp,#192] c08: a90d6ffa stp x26, x27, [sp,#208] c0c: a90e77fc stp x28, x29, [sp,#224] c10: d5384036 mrs x22, elr_el1 c14: d5384017 mrs x23, spsr_el1 c18: a90f5bfe stp x30, x22, [sp,#240] c1c: f90083f7 str x23, [sp,#256] c20: d2800180 mov x0, #0xc // #12 c24: d5385201 mrs x1, esr_el1 c28: d5384022 mrs x2, elr_el1 c2c: 94000000 bl 0 c30: 14000000 b e14 0000000000000c34 : c34: d10443ff sub sp, sp, #0x110 c38: a90007e0 stp x0, x1, [sp] c3c: a9010fe2 stp x2, x3, [sp,#16] c40: a90217e4 stp x4, x5, [sp,#32] c44: a9031fe6 stp x6, x7, [sp,#48] c48: a90427e8 stp x8, x9, [sp,#64] c4c: a9052fea stp x10, x11, [sp,#80] c50: a90637ec stp x12, x13, [sp,#96] c54: a9073fee stp x14, x15, [sp,#112] c58: a90847f0 stp x16, x17, [sp,#128] c5c: a9094ff2 stp x18, x19, [sp,#144] c60: a90a57f4 stp x20, x21, [sp,#160] c64: a90b5ff6 stp x22, x23, [sp,#176] c68: a90c67f8 stp x24, x25, [sp,#192] c6c: a90d6ffa stp x26, x27, [sp,#208] c70: a90e77fc stp x28, x29, [sp,#224] c74: d5384036 mrs x22, elr_el1 c78: d5384017 mrs x23, spsr_el1 c7c: a90f5bfe stp x30, x22, [sp,#240] c80: f90083f7 str x23, [sp,#256] c84: d28001a0 mov x0, #0xd // #13 c88: d5385201 mrs x1, esr_el1 c8c: d5384022 mrs x2, elr_el1 c90: 94000000 bl 0 c94: 14000000 b e14 0000000000000c98 : c98: d10443ff sub sp, sp, #0x110 c9c: a90007e0 stp x0, x1, [sp] ca0: a9010fe2 stp x2, x3, [sp,#16] ca4: a90217e4 stp x4, x5, [sp,#32] ca8: a9031fe6 stp x6, x7, [sp,#48] cac: a90427e8 stp x8, x9, [sp,#64] cb0: a9052fea stp x10, x11, [sp,#80] cb4: a90637ec stp x12, x13, [sp,#96] cb8: a9073fee stp x14, x15, [sp,#112] cbc: a90847f0 stp x16, x17, [sp,#128] cc0: a9094ff2 stp x18, x19, [sp,#144] cc4: a90a57f4 stp x20, x21, [sp,#160] cc8: a90b5ff6 stp x22, x23, [sp,#176] ccc: a90c67f8 stp x24, x25, [sp,#192] cd0: a90d6ffa stp x26, x27, [sp,#208] cd4: a90e77fc stp x28, x29, [sp,#224] cd8: d5384036 mrs x22, elr_el1 cdc: d5384017 mrs x23, spsr_el1 ce0: a90f5bfe stp x30, x22, [sp,#240] ce4: f90083f7 str x23, [sp,#256] ce8: d28001c0 mov x0, #0xe // #14 cec: d5385201 mrs x1, esr_el1 cf0: d5384022 mrs x2, elr_el1 cf4: 94000000 bl 0 cf8: 14000000 b e14 0000000000000cfc : cfc: d10443ff sub sp, sp, #0x110 d00: a90007e0 stp x0, x1, [sp] d04: a9010fe2 stp x2, x3, [sp,#16] d08: a90217e4 stp x4, x5, [sp,#32] d0c: a9031fe6 stp x6, x7, [sp,#48] d10: a90427e8 stp x8, x9, [sp,#64] d14: a9052fea stp x10, x11, [sp,#80] d18: a90637ec stp x12, x13, [sp,#96] d1c: a9073fee stp x14, x15, [sp,#112] d20: a90847f0 stp x16, x17, [sp,#128] d24: a9094ff2 stp x18, x19, [sp,#144] d28: a90a57f4 stp x20, x21, [sp,#160] d2c: a90b5ff6 stp x22, x23, [sp,#176] d30: a90c67f8 stp x24, x25, [sp,#192] d34: a90d6ffa stp x26, x27, [sp,#208] d38: a90e77fc stp x28, x29, [sp,#224] d3c: d5384036 mrs x22, elr_el1 d40: d5384017 mrs x23, spsr_el1 d44: a90f5bfe stp x30, x22, [sp,#240] d48: f90083f7 str x23, [sp,#256] d4c: d28001e0 mov x0, #0xf // #15 d50: d5385201 mrs x1, esr_el1 d54: d5384022 mrs x2, elr_el1 d58: 94000000 bl 0 d5c: 14000000 b e14 0000000000000d60 : d60: d10443ff sub sp, sp, #0x110 d64: a90007e0 stp x0, x1, [sp] d68: a9010fe2 stp x2, x3, [sp,#16] d6c: a90217e4 stp x4, x5, [sp,#32] d70: a9031fe6 stp x6, x7, [sp,#48] d74: a90427e8 stp x8, x9, [sp,#64] d78: a9052fea stp x10, x11, [sp,#80] d7c: a90637ec stp x12, x13, [sp,#96] d80: a9073fee stp x14, x15, [sp,#112] d84: a90847f0 stp x16, x17, [sp,#128] d88: a9094ff2 stp x18, x19, [sp,#144] d8c: a90a57f4 stp x20, x21, [sp,#160] d90: a90b5ff6 stp x22, x23, [sp,#176] d94: a90c67f8 stp x24, x25, [sp,#192] d98: a90d6ffa stp x26, x27, [sp,#208] d9c: a90e77fc stp x28, x29, [sp,#224] da0: d5384036 mrs x22, elr_el1 da4: d5384017 mrs x23, spsr_el1 da8: a90f5bfe stp x30, x22, [sp,#240] dac: f90083f7 str x23, [sp,#256] db0: 94000000 bl 0 db4: f94083f7 ldr x23, [sp,#256] db8: a94f5bfe ldp x30, x22, [sp,#240] dbc: d5184036 msr elr_el1, x22 dc0: d5184017 msr spsr_el1, x23 dc4: a94007e0 ldp x0, x1, [sp] dc8: a9410fe2 ldp x2, x3, [sp,#16] dcc: a94217e4 ldp x4, x5, [sp,#32] dd0: a9431fe6 ldp x6, x7, [sp,#48] dd4: a94427e8 ldp x8, x9, [sp,#64] dd8: a9452fea ldp x10, x11, [sp,#80] ddc: a94637ec ldp x12, x13, [sp,#96] de0: a9473fee ldp x14, x15, [sp,#112] de4: a94847f0 ldp x16, x17, [sp,#128] de8: a9494ff2 ldp x18, x19, [sp,#144] dec: a94a57f4 ldp x20, x21, [sp,#160] df0: a94b5ff6 ldp x22, x23, [sp,#176] df4: a94c67f8 ldp x24, x25, [sp,#192] df8: a94d6ffa ldp x26, x27, [sp,#208] dfc: a94e77fc ldp x28, x29, [sp,#224] e00: 910443ff add sp, sp, #0x110 e04: d69f03e0 eret 0000000000000e08 : e08: 94000000 bl 0 e0c: aa1403e0 mov x0, x20 e10: d63f0260 blr x19 0000000000000e14 : e14: 14000000 b e14 build/fork_c.o: file format elf64-littleaarch64 Disassembly of section .text: 0000000000000000 : 0: a9bd7bfd stp x29, x30, [sp,#-48]! 4: 910003fd mov x29, sp 8: f9000fa0 str x0, [x29,#24] c: f9000ba1 str x1, [x29,#16] 10: 94000000 bl 0 14: 94000000 bl 0 18: f90017a0 str x0, [x29,#40] 1c: f94017a0 ldr x0, [x29,#40] 20: f100001f cmp x0, #0x0 24: 54000061 b.ne 30 28: 52800020 mov w0, #0x1 // #1 2c: 14000049 b 150 30: 90000000 adrp x0, 0 34: 91000000 add x0, x0, #0x0 38: f9400000 ldr x0, [x0] 3c: f9414801 ldr x1, [x0,#656] 40: f94017a0 ldr x0, [x29,#40] 44: f9014801 str x1, [x0,#656] 48: f94017a0 ldr x0, [x29,#40] 4c: f901401f str xzr, [x0,#640] 50: f94017a0 ldr x0, [x29,#40] 54: f9414801 ldr x1, [x0,#656] 58: f94017a0 ldr x0, [x29,#40] 5c: f9014401 str x1, [x0,#648] 60: f94017a0 ldr x0, [x29,#40] 64: d2800021 mov x1, #0x1 // #1 68: f9014c01 str x1, [x0,#664] 6c: f94017a0 ldr x0, [x29,#40] 70: f9400fa1 ldr x1, [x29,#24] 74: f9000001 str x1, [x0] 78: f94017a0 ldr x0, [x29,#40] 7c: f9400ba1 ldr x1, [x29,#16] 80: f9000401 str x1, [x0,#8] 84: 90000000 adrp x0, 0 88: 91000001 add x1, x0, #0x0 8c: f94017a0 ldr x0, [x29,#40] 90: f9003001 str x1, [x0,#96] 94: f94017a0 ldr x0, [x29,#40] 98: 91400401 add x1, x0, #0x1, lsl #12 9c: f94017a0 ldr x0, [x29,#40] a0: f9002c01 str x1, [x0,#88] a4: 90000000 adrp x0, 0 a8: 91000000 add x0, x0, #0x0 ac: b9400000 ldr w0, [x0] b0: 11000402 add w2, w0, #0x1 b4: 90000001 adrp x1, 0 b8: 91000021 add x1, x1, #0x0 bc: b9000022 str w2, [x1] c0: b90027a0 str w0, [x29,#36] c4: 90000000 adrp x0, 0 c8: 91000000 add x0, x0, #0x0 cc: b98027a1 ldrsw x1, [x29,#36] d0: f94017a2 ldr x2, [x29,#40] d4: f8217802 str x2, [x0,x1,lsl #3] d8: 90000000 adrp x0, 0 dc: 91000000 add x0, x0, #0x0 e0: b94027a1 ldr w1, [x29,#36] e4: 94000000 bl 0 e8: 90000000 adrp x0, 0 ec: 91000000 add x0, x0, #0x0 f0: f94017a1 ldr x1, [x29,#40] f4: 94000000 bl 0 f8: f94017a0 ldr x0, [x29,#40] fc: f9400001 ldr x1, [x0] 100: 90000000 adrp x0, 0 104: 91000000 add x0, x0, #0x0 108: 94000000 bl 0 10c: f94017a0 ldr x0, [x29,#40] 110: f9400401 ldr x1, [x0,#8] 114: 90000000 adrp x0, 0 118: 91000000 add x0, x0, #0x0 11c: 94000000 bl 0 120: f94017a0 ldr x0, [x29,#40] 124: f9403001 ldr x1, [x0,#96] 128: 90000000 adrp x0, 0 12c: 91000000 add x0, x0, #0x0 130: 94000000 bl 0 134: f94017a0 ldr x0, [x29,#40] 138: f9402c01 ldr x1, [x0,#88] 13c: 90000000 adrp x0, 0 140: 91000000 add x0, x0, #0x0 144: 94000000 bl 0 148: 94000000 bl 0 14c: 52800000 mov w0, #0x0 // #0 150: a8c37bfd ldp x29, x30, [sp],#48 154: d65f03c0 ret build/irq_c.o: file format elf64-littleaarch64 Disassembly of section .text: 0000000000000000 : 0: a9bf7bfd stp x29, x30, [sp,#-16]! 4: 910003fd mov x29, sp 8: 52800041 mov w1, #0x2 // #2 c: d2964200 mov x0, #0xb210 // #45584 10: f2a7e000 movk x0, #0x3f00, lsl #16 14: 94000000 bl 0 18: d503201f nop 1c: a8c17bfd ldp x29, x30, [sp],#16 20: d65f03c0 ret 0000000000000024 : 24: a9bd7bfd stp x29, x30, [sp,#-48]! 28: 910003fd mov x29, sp 2c: b9002fa0 str w0, [x29,#44] 30: f90013a1 str x1, [x29,#32] 34: f9000fa2 str x2, [x29,#24] 38: 90000000 adrp x0, 0 3c: 91000000 add x0, x0, #0x0 40: b9802fa1 ldrsw x1, [x29,#44] 44: f8617801 ldr x1, [x0,x1,lsl #3] 48: 90000000 adrp x0, 0 4c: 91000000 add x0, x0, #0x0 50: f9400fa3 ldr x3, [x29,#24] 54: f94013a2 ldr x2, [x29,#32] 58: 94000000 bl 0 5c: d503201f nop 60: a8c37bfd ldp x29, x30, [sp],#48 64: d65f03c0 ret 0000000000000068 : 68: a9be7bfd stp x29, x30, [sp,#-32]! 6c: 910003fd mov x29, sp 70: d2964080 mov x0, #0xb204 // #45572 74: f2a7e000 movk x0, #0x3f00, lsl #16 78: 94000000 bl 0 7c: b9001fa0 str w0, [x29,#28] 80: b9401fa0 ldr w0, [x29,#28] 84: 7100081f cmp w0, #0x2 88: 54000061 b.ne 94 8c: 94000000 bl 0 90: 14000005 b a4 94: 90000000 adrp x0, 0 98: 91000000 add x0, x0, #0x0 9c: b9401fa1 ldr w1, [x29,#28] a0: 94000000 bl 0 a4: d503201f nop a8: a8c27bfd ldp x29, x30, [sp],#32 ac: d65f03c0 ret build/irq_s.o: file format elf64-littleaarch64 Disassembly of section .text: 0000000000000000 : 0: 10000000 adr x0, 0 4: d518c000 msr vbar_el1, x0 8: d65f03c0 ret 000000000000000c : c: d50342ff msr daifclr, #0x2 10: d65f03c0 ret 0000000000000014 : 14: d50342df msr daifset, #0x2 18: d65f03c0 ret build/kernel_c.o: file format elf64-littleaarch64 Disassembly of section .text: 0000000000000000 : 0: a9bd7bfd stp x29, x30, [sp,#-48]! 4: 910003fd mov x29, sp 8: f9000fa0 str x0, [x29,#24] c: b9002fbf str wzr, [x29,#44] 10: 1400000c b 40 14: b9802fa0 ldrsw x0, [x29,#44] 18: f9400fa1 ldr x1, [x29,#24] 1c: 8b000020 add x0, x1, x0 20: 39400000 ldrb w0, [x0] 24: 94000000 bl 0 28: d290d400 mov x0, #0x86a0 // #34464 2c: f2a00020 movk x0, #0x1, lsl #16 30: 94000000 bl 0 34: b9402fa0 ldr w0, [x29,#44] 38: 11000400 add w0, w0, #0x1 3c: b9002fa0 str w0, [x29,#44] 40: b9402fa0 ldr w0, [x29,#44] 44: 7100101f cmp w0, #0x4 48: 54fffe6d b.le 14 4c: 17fffff0 b c 0000000000000050 : 50: a9be7bfd stp x29, x30, [sp,#-32]! 54: 910003fd mov x29, sp 58: 94000000 bl 0 5c: 90000000 adrp x0, 0 60: 91000000 add x0, x0, #0x0 64: aa0003e1 mov x1, x0 68: d2800000 mov x0, #0x0 // #0 6c: 94000000 bl 0 70: 94000000 bl 0 74: 94000000 bl 0 78: 94000000 bl 0 7c: 94000000 bl 0 80: 90000000 adrp x0, 0 84: 91000002 add x2, x0, #0x0 88: 90000000 adrp x0, 0 8c: 91000000 add x0, x0, #0x0 90: aa0003e1 mov x1, x0 94: aa0203e0 mov x0, x2 98: 94000000 bl 0 9c: b9001fa0 str w0, [x29,#28] a0: b9401fa0 ldr w0, [x29,#28] a4: 7100001f cmp w0, #0x0 a8: 540000a0 b.eq bc ac: 90000000 adrp x0, 0 b0: 91000000 add x0, x0, #0x0 b4: 94000000 bl 0 b8: 14000012 b 100 bc: 90000000 adrp x0, 0 c0: 91000002 add x2, x0, #0x0 c4: 90000000 adrp x0, 0 c8: 91000000 add x0, x0, #0x0 cc: aa0003e1 mov x1, x0 d0: aa0203e0 mov x0, x2 d4: 94000000 bl 0 d8: b9001fa0 str w0, [x29,#28] dc: b9401fa0 ldr w0, [x29,#28] e0: 7100001f cmp w0, #0x0 e4: 540000a0 b.eq f8 e8: 90000000 adrp x0, 0 ec: 91000000 add x0, x0, #0x0 f0: 94000000 bl 0 f4: 14000003 b 100 f8: 94000000 bl 0 fc: 17ffffff b f8 100: a8c27bfd ldp x29, x30, [sp],#32 104: d65f03c0 ret build/mini_uart_c.o: file format elf64-littleaarch64 Disassembly of section .text: 0000000000000000 : 0: a9be7bfd stp x29, x30, [sp,#-32]! 4: 910003fd mov x29, sp 8: 39007fa0 strb w0, [x29,#31] c: d28a0a80 mov x0, #0x5054 // #20564 10: f2a7e420 movk x0, #0x3f21, lsl #16 14: 94000000 bl 0 18: 121b0000 and w0, w0, #0x20 1c: 7100001f cmp w0, #0x0 20: 54000041 b.ne 28 24: 17fffffa b c 28: d503201f nop 2c: 39407fa0 ldrb w0, [x29,#31] 30: 2a0003e1 mov w1, w0 34: d28a0800 mov x0, #0x5040 // #20544 38: f2a7e420 movk x0, #0x3f21, lsl #16 3c: 94000000 bl 0 40: d503201f nop 44: a8c27bfd ldp x29, x30, [sp],#32 48: d65f03c0 ret 000000000000004c : 4c: a9bf7bfd stp x29, x30, [sp,#-16]! 50: 910003fd mov x29, sp 54: d28a0a80 mov x0, #0x5054 // #20564 58: f2a7e420 movk x0, #0x3f21, lsl #16 5c: 94000000 bl 0 60: 12000000 and w0, w0, #0x1 64: 7100001f cmp w0, #0x0 68: 54000041 b.ne 70 6c: 17fffffa b 54 70: d503201f nop 74: d28a0800 mov x0, #0x5040 // #20544 78: f2a7e420 movk x0, #0x3f21, lsl #16 7c: 94000000 bl 0 80: 53001c00 uxtb w0, w0 84: a8c17bfd ldp x29, x30, [sp],#16 88: d65f03c0 ret 000000000000008c : 8c: a9bd7bfd stp x29, x30, [sp,#-48]! 90: 910003fd mov x29, sp 94: f9000fa0 str x0, [x29,#24] 98: b9002fbf str wzr, [x29,#44] 9c: 14000009 b c0 a0: b9802fa0 ldrsw x0, [x29,#44] a4: f9400fa1 ldr x1, [x29,#24] a8: 8b000020 add x0, x1, x0 ac: 39400000 ldrb w0, [x0] b0: 94000000 bl 0 b4: b9402fa0 ldr w0, [x29,#44] b8: 11000400 add w0, w0, #0x1 bc: b9002fa0 str w0, [x29,#44] c0: b9802fa0 ldrsw x0, [x29,#44] c4: f9400fa1 ldr x1, [x29,#24] c8: 8b000020 add x0, x1, x0 cc: 39400000 ldrb w0, [x0] d0: 7100001f cmp w0, #0x0 d4: 54fffe61 b.ne a0 d8: d503201f nop dc: a8c37bfd ldp x29, x30, [sp],#48 e0: d65f03c0 ret 00000000000000e4 : e4: a9be7bfd stp x29, x30, [sp,#-32]! e8: 910003fd mov x29, sp ec: d2800080 mov x0, #0x4 // #4 f0: f2a7e400 movk x0, #0x3f20, lsl #16 f4: 94000000 bl 0 f8: b9001fa0 str w0, [x29,#28] fc: b9401fa0 ldr w0, [x29,#28] 100: 12117000 and w0, w0, #0xffff8fff 104: b9001fa0 str w0, [x29,#28] 108: b9401fa0 ldr w0, [x29,#28] 10c: 32130000 orr w0, w0, #0x2000 110: b9001fa0 str w0, [x29,#28] 114: b9401fa0 ldr w0, [x29,#28] 118: 120e7000 and w0, w0, #0xfffc7fff 11c: b9001fa0 str w0, [x29,#28] 120: b9401fa0 ldr w0, [x29,#28] 124: 32100000 orr w0, w0, #0x10000 128: b9001fa0 str w0, [x29,#28] 12c: b9401fa1 ldr w1, [x29,#28] 130: d2800080 mov x0, #0x4 // #4 134: f2a7e400 movk x0, #0x3f20, lsl #16 138: 94000000 bl 0 13c: 52800001 mov w1, #0x0 // #0 140: d2801280 mov x0, #0x94 // #148 144: f2a7e400 movk x0, #0x3f20, lsl #16 148: 94000000 bl 0 14c: d28012c0 mov x0, #0x96 // #150 150: 94000000 bl 0 154: 52980001 mov w1, #0xc000 // #49152 158: d2801300 mov x0, #0x98 // #152 15c: f2a7e400 movk x0, #0x3f20, lsl #16 160: 94000000 bl 0 164: d28012c0 mov x0, #0x96 // #150 168: 94000000 bl 0 16c: 52800001 mov w1, #0x0 // #0 170: d2801300 mov x0, #0x98 // #152 174: f2a7e400 movk x0, #0x3f20, lsl #16 178: 94000000 bl 0 17c: 52800021 mov w1, #0x1 // #1 180: d28a0080 mov x0, #0x5004 // #20484 184: f2a7e420 movk x0, #0x3f21, lsl #16 188: 94000000 bl 0 18c: 52800001 mov w1, #0x0 // #0 190: d28a0c00 mov x0, #0x5060 // #20576 194: f2a7e420 movk x0, #0x3f21, lsl #16 198: 94000000 bl 0 19c: 52800001 mov w1, #0x0 // #0 1a0: d28a0880 mov x0, #0x5044 // #20548 1a4: f2a7e420 movk x0, #0x3f21, lsl #16 1a8: 94000000 bl 0 1ac: 52800061 mov w1, #0x3 // #3 1b0: d28a0980 mov x0, #0x504c // #20556 1b4: f2a7e420 movk x0, #0x3f21, lsl #16 1b8: 94000000 bl 0 1bc: 52800001 mov w1, #0x0 // #0 1c0: d28a0a00 mov x0, #0x5050 // #20560 1c4: f2a7e420 movk x0, #0x3f21, lsl #16 1c8: 94000000 bl 0 1cc: 528021c1 mov w1, #0x10e // #270 1d0: d28a0d00 mov x0, #0x5068 // #20584 1d4: f2a7e420 movk x0, #0x3f21, lsl #16 1d8: 94000000 bl 0 1dc: 52800061 mov w1, #0x3 // #3 1e0: d28a0c00 mov x0, #0x5060 // #20576 1e4: f2a7e420 movk x0, #0x3f21, lsl #16 1e8: 94000000 bl 0 1ec: d503201f nop 1f0: a8c27bfd ldp x29, x30, [sp],#32 1f4: d65f03c0 ret 00000000000001f8 : 1f8: a9be7bfd stp x29, x30, [sp,#-32]! 1fc: 910003fd mov x29, sp 200: f9000fa0 str x0, [x29,#24] 204: 39005fa1 strb w1, [x29,#23] 208: 39405fa0 ldrb w0, [x29,#23] 20c: 94000000 bl 0 210: d503201f nop 214: a8c27bfd ldp x29, x30, [sp],#32 218: d65f03c0 ret build/mm_c.o: file format elf64-littleaarch64 Disassembly of section .text: 0000000000000000 : 0: d10043ff sub sp, sp, #0x10 4: b9000fff str wzr, [sp,#12] 8: 14000014 b 58 c: 90000000 adrp x0, 0 10: 91000000 add x0, x0, #0x0 14: b9800fe1 ldrsw x1, [sp,#12] 18: 78617800 ldrh w0, [x0,x1,lsl #1] 1c: 7100001f cmp w0, #0x0 20: 54000161 b.ne 4c 24: 90000000 adrp x0, 0 28: 91000000 add x0, x0, #0x0 2c: b9800fe1 ldrsw x1, [sp,#12] 30: 52800022 mov w2, #0x1 // #1 34: 78217802 strh w2, [x0,x1,lsl #1] 38: b9400fe0 ldr w0, [sp,#12] 3c: 11100000 add w0, w0, #0x400 40: 53144c00 lsl w0, w0, #12 44: 93407c00 sxtw x0, w0 48: 1400000a b 70 4c: b9400fe0 ldr w0, [sp,#12] 50: 11000400 add w0, w0, #0x1 54: b9000fe0 str w0, [sp,#12] 58: b9400fe1 ldr w1, [sp,#12] 5c: 529d7fe0 mov w0, #0xebff // #60415 60: 72a00060 movk w0, #0x3, lsl #16 64: 6b00003f cmp w1, w0 68: 54fffd2d b.le c 6c: d2800000 mov x0, #0x0 // #0 70: 910043ff add sp, sp, #0x10 74: d65f03c0 ret 0000000000000078 : 78: d10043ff sub sp, sp, #0x10 7c: f90007e0 str x0, [sp,#8] 80: f94007e0 ldr x0, [sp,#8] 84: d1500000 sub x0, x0, #0x400, lsl #12 88: d34cfc01 lsr x1, x0, #12 8c: 90000000 adrp x0, 0 90: 91000000 add x0, x0, #0x0 94: 7821781f strh wzr, [x0,x1,lsl #1] 98: d503201f nop 9c: 910043ff add sp, sp, #0x10 a0: d65f03c0 ret build/mm_s.o: file format elf64-littleaarch64 Disassembly of section .text: 0000000000000000 : 0: f800841f str xzr, [x0],#8 4: f1002021 subs x1, x1, #0x8 8: 5400000c b.gt 0 c: d65f03c0 ret build/printf_c.o: file format elf64-littleaarch64 Disassembly of section .text: 0000000000000000 : 0: d100c3ff sub sp, sp, #0x30 4: b9001fe0 str w0, [sp,#28] 8: b9001be1 str w1, [sp,#24] c: b90017e2 str w2, [sp,#20] 10: f90007e3 str x3, [sp,#8] 14: b9002fff str wzr, [sp,#44] 18: 52800020 mov w0, #0x1 // #1 1c: b9002be0 str w0, [sp,#40] 20: 14000005 b 34 24: b9402be1 ldr w1, [sp,#40] 28: b9401be0 ldr w0, [sp,#24] 2c: 1b007c20 mul w0, w1, w0 30: b9002be0 str w0, [sp,#40] 34: b9401fe1 ldr w1, [sp,#28] 38: b9402be0 ldr w0, [sp,#40] 3c: 1ac00821 udiv w1, w1, w0 40: b9401be0 ldr w0, [sp,#24] 44: 6b00003f cmp w1, w0 48: 54fffee2 b.cs 24 4c: 1400002f b 108 50: b9401fe1 ldr w1, [sp,#28] 54: b9402be0 ldr w0, [sp,#40] 58: 1ac00820 udiv w0, w1, w0 5c: b90027e0 str w0, [sp,#36] 60: b9401fe0 ldr w0, [sp,#28] 64: b9402be1 ldr w1, [sp,#40] 68: 1ac10802 udiv w2, w0, w1 6c: b9402be1 ldr w1, [sp,#40] 70: 1b017c41 mul w1, w2, w1 74: 4b010000 sub w0, w0, w1 78: b9001fe0 str w0, [sp,#28] 7c: b9402be1 ldr w1, [sp,#40] 80: b9401be0 ldr w0, [sp,#24] 84: 1ac00820 udiv w0, w1, w0 88: b9002be0 str w0, [sp,#40] 8c: b9402fe0 ldr w0, [sp,#44] 90: 7100001f cmp w0, #0x0 94: 540000e1 b.ne b0 98: b94027e0 ldr w0, [sp,#36] 9c: 7100001f cmp w0, #0x0 a0: 5400008c b.gt b0 a4: b9402be0 ldr w0, [sp,#40] a8: 7100001f cmp w0, #0x0 ac: 540002e1 b.ne 108 b0: f94007e1 ldr x1, [sp,#8] b4: 91000420 add x0, x1, #0x1 b8: f90007e0 str x0, [sp,#8] bc: b94027e0 ldr w0, [sp,#36] c0: 7100241f cmp w0, #0x9 c4: 5400010d b.le e4 c8: b94017e0 ldr w0, [sp,#20] cc: 7100001f cmp w0, #0x0 d0: 54000060 b.eq dc d4: 528006e0 mov w0, #0x37 // #55 d8: 14000004 b e8 dc: 52800ae0 mov w0, #0x57 // #87 e0: 14000002 b e8 e4: 52800600 mov w0, #0x30 // #48 e8: b94027e2 ldr w2, [sp,#36] ec: 53001c42 uxtb w2, w2 f0: 0b020000 add w0, w0, w2 f4: 53001c00 uxtb w0, w0 f8: 39000020 strb w0, [x1] fc: b9402fe0 ldr w0, [sp,#44] 100: 11000400 add w0, w0, #0x1 104: b9002fe0 str w0, [sp,#44] 108: b9402be0 ldr w0, [sp,#40] 10c: 7100001f cmp w0, #0x0 110: 54fffa01 b.ne 50 114: f94007e0 ldr x0, [sp,#8] 118: 3900001f strb wzr, [x0] 11c: d503201f nop 120: 9100c3ff add sp, sp, #0x30 124: d65f03c0 ret 0000000000000128 : 128: a9be7bfd stp x29, x30, [sp,#-32]! 12c: 910003fd mov x29, sp 130: b9001fa0 str w0, [x29,#28] 134: f9000ba1 str x1, [x29,#16] 138: b9401fa0 ldr w0, [x29,#28] 13c: 7100001f cmp w0, #0x0 140: 5400012a b.ge 164 144: b9401fa0 ldr w0, [x29,#28] 148: 4b0003e0 neg w0, w0 14c: b9001fa0 str w0, [x29,#28] 150: f9400ba0 ldr x0, [x29,#16] 154: 91000401 add x1, x0, #0x1 158: f9000ba1 str x1, [x29,#16] 15c: 528005a1 mov w1, #0x2d // #45 160: 39000001 strb w1, [x0] 164: b9401fa0 ldr w0, [x29,#28] 168: f9400ba3 ldr x3, [x29,#16] 16c: 52800002 mov w2, #0x0 // #0 170: 52800141 mov w1, #0xa // #10 174: 97ffffa3 bl 0 178: d503201f nop 17c: a8c27bfd ldp x29, x30, [sp],#32 180: d65f03c0 ret 0000000000000184 : 184: d10043ff sub sp, sp, #0x10 188: 39003fe0 strb w0, [sp,#15] 18c: 39403fe0 ldrb w0, [sp,#15] 190: 7100bc1f cmp w0, #0x2f 194: 540000e9 b.ls 1b0 198: 39403fe0 ldrb w0, [sp,#15] 19c: 7100e41f cmp w0, #0x39 1a0: 54000088 b.hi 1b0 1a4: 39403fe0 ldrb w0, [sp,#15] 1a8: 5100c000 sub w0, w0, #0x30 1ac: 14000014 b 1fc 1b0: 39403fe0 ldrb w0, [sp,#15] 1b4: 7101801f cmp w0, #0x60 1b8: 540000e9 b.ls 1d4 1bc: 39403fe0 ldrb w0, [sp,#15] 1c0: 7101981f cmp w0, #0x66 1c4: 54000088 b.hi 1d4 1c8: 39403fe0 ldrb w0, [sp,#15] 1cc: 51015c00 sub w0, w0, #0x57 1d0: 1400000b b 1fc 1d4: 39403fe0 ldrb w0, [sp,#15] 1d8: 7101001f cmp w0, #0x40 1dc: 540000e9 b.ls 1f8 1e0: 39403fe0 ldrb w0, [sp,#15] 1e4: 7101181f cmp w0, #0x46 1e8: 54000088 b.hi 1f8 1ec: 39403fe0 ldrb w0, [sp,#15] 1f0: 5100dc00 sub w0, w0, #0x37 1f4: 14000002 b 1fc 1f8: 12800000 mov w0, #0xffffffff // #-1 1fc: 910043ff add sp, sp, #0x10 200: d65f03c0 ret 0000000000000204 : 204: a9bc7bfd stp x29, x30, [sp,#-64]! 208: 910003fd mov x29, sp 20c: 3900bfa0 strb w0, [x29,#47] 210: f90013a1 str x1, [x29,#32] 214: b9002ba2 str w2, [x29,#40] 218: f9000fa3 str x3, [x29,#24] 21c: f94013a0 ldr x0, [x29,#32] 220: f9400000 ldr x0, [x0] 224: f9001fa0 str x0, [x29,#56] 228: b90037bf str wzr, [x29,#52] 22c: 14000010 b 26c 230: b94033a1 ldr w1, [x29,#48] 234: b9402ba0 ldr w0, [x29,#40] 238: 6b00003f cmp w1, w0 23c: 5400026c b.gt 288 240: b94037a1 ldr w1, [x29,#52] 244: b9402ba0 ldr w0, [x29,#40] 248: 1b007c21 mul w1, w1, w0 24c: b94033a0 ldr w0, [x29,#48] 250: 0b000020 add w0, w1, w0 254: b90037a0 str w0, [x29,#52] 258: f9401fa0 ldr x0, [x29,#56] 25c: 91000401 add x1, x0, #0x1 260: f9001fa1 str x1, [x29,#56] 264: 39400000 ldrb w0, [x0] 268: 3900bfa0 strb w0, [x29,#47] 26c: 3940bfa0 ldrb w0, [x29,#47] 270: 97ffffc5 bl 184 274: b90033a0 str w0, [x29,#48] 278: b94033a0 ldr w0, [x29,#48] 27c: 7100001f cmp w0, #0x0 280: 54fffd8a b.ge 230 284: 14000002 b 28c 288: d503201f nop 28c: f94013a0 ldr x0, [x29,#32] 290: f9401fa1 ldr x1, [x29,#56] 294: f9000001 str x1, [x0] 298: f9400fa0 ldr x0, [x29,#24] 29c: b94037a1 ldr w1, [x29,#52] 2a0: b9000001 str w1, [x0] 2a4: 3940bfa0 ldrb w0, [x29,#47] 2a8: a8c47bfd ldp x29, x30, [sp],#64 2ac: d65f03c0 ret 00000000000002b0 : 2b0: a9bc7bfd stp x29, x30, [sp,#-64]! 2b4: 910003fd mov x29, sp 2b8: f90017a0 str x0, [x29,#40] 2bc: f90013a1 str x1, [x29,#32] 2c0: b9001fa2 str w2, [x29,#28] 2c4: 39006fa3 strb w3, [x29,#27] 2c8: f9000ba4 str x4, [x29,#16] 2cc: 39406fa0 ldrb w0, [x29,#27] 2d0: 7100001f cmp w0, #0x0 2d4: 54000060 b.eq 2e0 2d8: 52800600 mov w0, #0x30 // #48 2dc: 14000002 b 2e4 2e0: 52800400 mov w0, #0x20 // #32 2e4: 3900dfa0 strb w0, [x29,#55] 2e8: f9400ba0 ldr x0, [x29,#16] 2ec: f9001fa0 str x0, [x29,#56] 2f0: 14000004 b 300 2f4: b9401fa0 ldr w0, [x29,#28] 2f8: 51000400 sub w0, w0, #0x1 2fc: b9001fa0 str w0, [x29,#28] 300: f9401fa0 ldr x0, [x29,#56] 304: 91000401 add x1, x0, #0x1 308: f9001fa1 str x1, [x29,#56] 30c: 39400000 ldrb w0, [x0] 310: 7100001f cmp w0, #0x0 314: 54000120 b.eq 338 318: b9401fa0 ldr w0, [x29,#28] 31c: 7100001f cmp w0, #0x0 320: 54fffeac b.gt 2f4 324: 14000005 b 338 328: f94013a2 ldr x2, [x29,#32] 32c: 3940dfa1 ldrb w1, [x29,#55] 330: f94017a0 ldr x0, [x29,#40] 334: d63f0040 blr x2 338: b9401fa0 ldr w0, [x29,#28] 33c: 51000401 sub w1, w0, #0x1 340: b9001fa1 str w1, [x29,#28] 344: 7100001f cmp w0, #0x0 348: 54ffff0c b.gt 328 34c: 14000005 b 360 350: f94013a2 ldr x2, [x29,#32] 354: 3940dba1 ldrb w1, [x29,#54] 358: f94017a0 ldr x0, [x29,#40] 35c: d63f0040 blr x2 360: f9400ba0 ldr x0, [x29,#16] 364: 91000401 add x1, x0, #0x1 368: f9000ba1 str x1, [x29,#16] 36c: 39400000 ldrb w0, [x0] 370: 3900dba0 strb w0, [x29,#54] 374: 3940dba0 ldrb w0, [x29,#54] 378: 7100001f cmp w0, #0x0 37c: 54fffea1 b.ne 350 380: d503201f nop 384: a8c47bfd ldp x29, x30, [sp],#64 388: d65f03c0 ret 000000000000038c : 38c: a9ba7bfd stp x29, x30, [sp,#-96]! 390: 910003fd mov x29, sp 394: f9000bf3 str x19, [sp,#16] 398: f9001fa0 str x0, [x29,#56] 39c: f9001ba1 str x1, [x29,#48] 3a0: f90017a2 str x2, [x29,#40] 3a4: aa0303f3 mov x19, x3 3a8: 140000fd b 79c 3ac: 39417fa0 ldrb w0, [x29,#95] 3b0: 7100941f cmp w0, #0x25 3b4: 540000c0 b.eq 3cc 3b8: f9401ba2 ldr x2, [x29,#48] 3bc: 39417fa1 ldrb w1, [x29,#95] 3c0: f9401fa0 ldr x0, [x29,#56] 3c4: d63f0040 blr x2 3c8: 140000f5 b 79c 3cc: 39017bbf strb wzr, [x29,#94] 3d0: b9004fbf str wzr, [x29,#76] 3d4: f94017a0 ldr x0, [x29,#40] 3d8: 91000401 add x1, x0, #0x1 3dc: f90017a1 str x1, [x29,#40] 3e0: 39400000 ldrb w0, [x0] 3e4: 39017fa0 strb w0, [x29,#95] 3e8: 39417fa0 ldrb w0, [x29,#95] 3ec: 7100c01f cmp w0, #0x30 3f0: 54000101 b.ne 410 3f4: f94017a0 ldr x0, [x29,#40] 3f8: 91000401 add x1, x0, #0x1 3fc: f90017a1 str x1, [x29,#40] 400: 39400000 ldrb w0, [x0] 404: 39017fa0 strb w0, [x29,#95] 408: 52800020 mov w0, #0x1 // #1 40c: 39017ba0 strb w0, [x29,#94] 410: 39417fa0 ldrb w0, [x29,#95] 414: 7100bc1f cmp w0, #0x2f 418: 54000189 b.ls 448 41c: 39417fa0 ldrb w0, [x29,#95] 420: 7100e41f cmp w0, #0x39 424: 54000128 b.hi 448 428: 910133a1 add x1, x29, #0x4c 42c: 9100a3a0 add x0, x29, #0x28 430: aa0103e3 mov x3, x1 434: 52800142 mov w2, #0xa // #10 438: aa0003e1 mov x1, x0 43c: 39417fa0 ldrb w0, [x29,#95] 440: 97ffff71 bl 204 444: 39017fa0 strb w0, [x29,#95] 448: 39417fa0 ldrb w0, [x29,#95] 44c: 71018c1f cmp w0, #0x63 450: 540011c0 b.eq 688 454: 71018c1f cmp w0, #0x63 458: 5400010c b.gt 478 45c: 7100941f cmp w0, #0x25 460: 54001940 b.eq 788 464: 7101601f cmp w0, #0x58 468: 54000b60 b.eq 5d4 46c: 7100001f cmp w0, #0x0 470: 54001a80 b.eq 7c0 474: 140000c9 b 798 478: 7101cc1f cmp w0, #0x73 47c: 54001440 b.eq 704 480: 7101cc1f cmp w0, #0x73 484: 5400008c b.gt 494 488: 7101901f cmp w0, #0x64 48c: 540005c0 b.eq 544 490: 140000c2 b 798 494: 7101d41f cmp w0, #0x75 498: 54000080 b.eq 4a8 49c: 7101e01f cmp w0, #0x78 4a0: 540009a0 b.eq 5d4 4a4: 140000bd b 798 4a8: b9401a60 ldr w0, [x19,#24] 4ac: f9400261 ldr x1, [x19] 4b0: 7100001f cmp w0, #0x0 4b4: 540000eb b.lt 4d0 4b8: aa0103e0 mov x0, x1 4bc: 91002c00 add x0, x0, #0xb 4c0: 927df000 and x0, x0, #0xfffffffffffffff8 4c4: f9000260 str x0, [x19] 4c8: aa0103e0 mov x0, x1 4cc: 1400000f b 508 4d0: 11002002 add w2, w0, #0x8 4d4: b9001a62 str w2, [x19,#24] 4d8: b9401a62 ldr w2, [x19,#24] 4dc: 7100005f cmp w2, #0x0 4e0: 540000ed b.le 4fc 4e4: aa0103e0 mov x0, x1 4e8: 91002c00 add x0, x0, #0xb 4ec: 927df000 and x0, x0, #0xfffffffffffffff8 4f0: f9000260 str x0, [x19] 4f4: aa0103e0 mov x0, x1 4f8: 14000004 b 508 4fc: f9400661 ldr x1, [x19,#8] 500: 93407c00 sxtw x0, w0 504: 8b000020 add x0, x1, x0 508: b9400000 ldr w0, [x0] 50c: 910143a1 add x1, x29, #0x50 510: aa0103e3 mov x3, x1 514: 52800002 mov w2, #0x0 // #0 518: 52800141 mov w1, #0xa // #10 51c: 97fffeb9 bl 0 520: b9404fa0 ldr w0, [x29,#76] 524: 910143a1 add x1, x29, #0x50 528: aa0103e4 mov x4, x1 52c: 39417ba3 ldrb w3, [x29,#94] 530: 2a0003e2 mov w2, w0 534: f9401ba1 ldr x1, [x29,#48] 538: f9401fa0 ldr x0, [x29,#56] 53c: 97ffff5d bl 2b0 540: 14000097 b 79c 544: b9401a60 ldr w0, [x19,#24] 548: f9400261 ldr x1, [x19] 54c: 7100001f cmp w0, #0x0 550: 540000eb b.lt 56c 554: aa0103e0 mov x0, x1 558: 91002c00 add x0, x0, #0xb 55c: 927df000 and x0, x0, #0xfffffffffffffff8 560: f9000260 str x0, [x19] 564: aa0103e0 mov x0, x1 568: 1400000f b 5a4 56c: 11002002 add w2, w0, #0x8 570: b9001a62 str w2, [x19,#24] 574: b9401a62 ldr w2, [x19,#24] 578: 7100005f cmp w2, #0x0 57c: 540000ed b.le 598 580: aa0103e0 mov x0, x1 584: 91002c00 add x0, x0, #0xb 588: 927df000 and x0, x0, #0xfffffffffffffff8 58c: f9000260 str x0, [x19] 590: aa0103e0 mov x0, x1 594: 14000004 b 5a4 598: f9400661 ldr x1, [x19,#8] 59c: 93407c00 sxtw x0, w0 5a0: 8b000020 add x0, x1, x0 5a4: b9400000 ldr w0, [x0] 5a8: 910143a1 add x1, x29, #0x50 5ac: 97fffedf bl 128 5b0: b9404fa0 ldr w0, [x29,#76] 5b4: 910143a1 add x1, x29, #0x50 5b8: aa0103e4 mov x4, x1 5bc: 39417ba3 ldrb w3, [x29,#94] 5c0: 2a0003e2 mov w2, w0 5c4: f9401ba1 ldr x1, [x29,#48] 5c8: f9401fa0 ldr x0, [x29,#56] 5cc: 97ffff39 bl 2b0 5d0: 14000073 b 79c 5d4: b9401a60 ldr w0, [x19,#24] 5d8: f9400261 ldr x1, [x19] 5dc: 7100001f cmp w0, #0x0 5e0: 540000eb b.lt 5fc 5e4: aa0103e0 mov x0, x1 5e8: 91002c00 add x0, x0, #0xb 5ec: 927df000 and x0, x0, #0xfffffffffffffff8 5f0: f9000260 str x0, [x19] 5f4: aa0103e0 mov x0, x1 5f8: 1400000f b 634 5fc: 11002002 add w2, w0, #0x8 600: b9001a62 str w2, [x19,#24] 604: b9401a62 ldr w2, [x19,#24] 608: 7100005f cmp w2, #0x0 60c: 540000ed b.le 628 610: aa0103e0 mov x0, x1 614: 91002c00 add x0, x0, #0xb 618: 927df000 and x0, x0, #0xfffffffffffffff8 61c: f9000260 str x0, [x19] 620: aa0103e0 mov x0, x1 624: 14000004 b 634 628: f9400661 ldr x1, [x19,#8] 62c: 93407c00 sxtw x0, w0 630: 8b000020 add x0, x1, x0 634: b9400004 ldr w4, [x0] 638: 39417fa0 ldrb w0, [x29,#95] 63c: 7101601f cmp w0, #0x58 640: 1a9f17e0 cset w0, eq 644: 53001c00 uxtb w0, w0 648: 2a0003e1 mov w1, w0 64c: 910143a0 add x0, x29, #0x50 650: aa0003e3 mov x3, x0 654: 2a0103e2 mov w2, w1 658: 52800201 mov w1, #0x10 // #16 65c: 2a0403e0 mov w0, w4 660: 97fffe68 bl 0 664: b9404fa0 ldr w0, [x29,#76] 668: 910143a1 add x1, x29, #0x50 66c: aa0103e4 mov x4, x1 670: 39417ba3 ldrb w3, [x29,#94] 674: 2a0003e2 mov w2, w0 678: f9401ba1 ldr x1, [x29,#48] 67c: f9401fa0 ldr x0, [x29,#56] 680: 97ffff0c bl 2b0 684: 14000046 b 79c 688: b9401a60 ldr w0, [x19,#24] 68c: f9400261 ldr x1, [x19] 690: 7100001f cmp w0, #0x0 694: 540000eb b.lt 6b0 698: aa0103e0 mov x0, x1 69c: 91002c00 add x0, x0, #0xb 6a0: 927df000 and x0, x0, #0xfffffffffffffff8 6a4: f9000260 str x0, [x19] 6a8: aa0103e0 mov x0, x1 6ac: 1400000f b 6e8 6b0: 11002002 add w2, w0, #0x8 6b4: b9001a62 str w2, [x19,#24] 6b8: b9401a62 ldr w2, [x19,#24] 6bc: 7100005f cmp w2, #0x0 6c0: 540000ed b.le 6dc 6c4: aa0103e0 mov x0, x1 6c8: 91002c00 add x0, x0, #0xb 6cc: 927df000 and x0, x0, #0xfffffffffffffff8 6d0: f9000260 str x0, [x19] 6d4: aa0103e0 mov x0, x1 6d8: 14000004 b 6e8 6dc: f9400661 ldr x1, [x19,#8] 6e0: 93407c00 sxtw x0, w0 6e4: 8b000020 add x0, x1, x0 6e8: b9400000 ldr w0, [x0] 6ec: 53001c00 uxtb w0, w0 6f0: f9401ba2 ldr x2, [x29,#48] 6f4: 2a0003e1 mov w1, w0 6f8: f9401fa0 ldr x0, [x29,#56] 6fc: d63f0040 blr x2 700: 14000027 b 79c 704: b9404fa5 ldr w5, [x29,#76] 708: b9401a60 ldr w0, [x19,#24] 70c: f9400261 ldr x1, [x19] 710: 7100001f cmp w0, #0x0 714: 540000eb b.lt 730 718: aa0103e0 mov x0, x1 71c: 91003c00 add x0, x0, #0xf 720: 927df000 and x0, x0, #0xfffffffffffffff8 724: f9000260 str x0, [x19] 728: aa0103e0 mov x0, x1 72c: 1400000f b 768 730: 11002002 add w2, w0, #0x8 734: b9001a62 str w2, [x19,#24] 738: b9401a62 ldr w2, [x19,#24] 73c: 7100005f cmp w2, #0x0 740: 540000ed b.le 75c 744: aa0103e0 mov x0, x1 748: 91003c00 add x0, x0, #0xf 74c: 927df000 and x0, x0, #0xfffffffffffffff8 750: f9000260 str x0, [x19] 754: aa0103e0 mov x0, x1 758: 14000004 b 768 75c: f9400661 ldr x1, [x19,#8] 760: 93407c00 sxtw x0, w0 764: 8b000020 add x0, x1, x0 768: f9400000 ldr x0, [x0] 76c: aa0003e4 mov x4, x0 770: 52800003 mov w3, #0x0 // #0 774: 2a0503e2 mov w2, w5 778: f9401ba1 ldr x1, [x29,#48] 77c: f9401fa0 ldr x0, [x29,#56] 780: 97fffecc bl 2b0 784: 14000006 b 79c 788: f9401ba2 ldr x2, [x29,#48] 78c: 39417fa1 ldrb w1, [x29,#95] 790: f9401fa0 ldr x0, [x29,#56] 794: d63f0040 blr x2 798: d503201f nop 79c: f94017a0 ldr x0, [x29,#40] 7a0: 91000401 add x1, x0, #0x1 7a4: f90017a1 str x1, [x29,#40] 7a8: 39400000 ldrb w0, [x0] 7ac: 39017fa0 strb w0, [x29,#95] 7b0: 39417fa0 ldrb w0, [x29,#95] 7b4: 7100001f cmp w0, #0x0 7b8: 54ffdfa1 b.ne 3ac 7bc: 14000002 b 7c4 7c0: d503201f nop 7c4: d503201f nop 7c8: f9400bf3 ldr x19, [sp,#16] 7cc: a8c67bfd ldp x29, x30, [sp],#96 7d0: d65f03c0 ret 00000000000007d4 : 7d4: d10043ff sub sp, sp, #0x10 7d8: f90007e0 str x0, [sp,#8] 7dc: f90003e1 str x1, [sp] 7e0: 90000000 adrp x0, 0 7e4: 91000000 add x0, x0, #0x0 7e8: f94003e1 ldr x1, [sp] 7ec: f9000001 str x1, [x0] 7f0: 90000000 adrp x0, 0 7f4: 91000000 add x0, x0, #0x0 7f8: f94007e1 ldr x1, [sp,#8] 7fc: f9000001 str x1, [x0] 800: d503201f nop 804: 910043ff add sp, sp, #0x10 808: d65f03c0 ret 000000000000080c : 80c: a9b67bfd stp x29, x30, [sp,#-160]! 810: 910003fd mov x29, sp 814: f9001fa0 str x0, [x29,#56] 818: f90037a1 str x1, [x29,#104] 81c: f9003ba2 str x2, [x29,#112] 820: f9003fa3 str x3, [x29,#120] 824: f90043a4 str x4, [x29,#128] 828: f90047a5 str x5, [x29,#136] 82c: f9004ba6 str x6, [x29,#144] 830: f9004fa7 str x7, [x29,#152] 834: 910283a0 add x0, x29, #0xa0 838: f90023a0 str x0, [x29,#64] 83c: 910283a0 add x0, x29, #0xa0 840: f90027a0 str x0, [x29,#72] 844: 910183a0 add x0, x29, #0x60 848: f9002ba0 str x0, [x29,#80] 84c: 128006e0 mov w0, #0xffffffc8 // #-56 850: b9005ba0 str w0, [x29,#88] 854: b9005fbf str wzr, [x29,#92] 858: 90000000 adrp x0, 0 85c: 91000000 add x0, x0, #0x0 860: f9400004 ldr x4, [x0] 864: 90000000 adrp x0, 0 868: 91000000 add x0, x0, #0x0 86c: f9400005 ldr x5, [x0] 870: 910043a2 add x2, x29, #0x10 874: 910103a3 add x3, x29, #0x40 878: a9400460 ldp x0, x1, [x3] 87c: a9000440 stp x0, x1, [x2] 880: a9410460 ldp x0, x1, [x3,#16] 884: a9010440 stp x0, x1, [x2,#16] 888: 910043a0 add x0, x29, #0x10 88c: aa0003e3 mov x3, x0 890: f9401fa2 ldr x2, [x29,#56] 894: aa0503e1 mov x1, x5 898: aa0403e0 mov x0, x4 89c: 94000000 bl 38c 8a0: d503201f nop 8a4: a8ca7bfd ldp x29, x30, [sp],#160 8a8: d65f03c0 ret 00000000000008ac : 8ac: d10043ff sub sp, sp, #0x10 8b0: f90007e0 str x0, [sp,#8] 8b4: 39001fe1 strb w1, [sp,#7] 8b8: f94007e0 ldr x0, [sp,#8] 8bc: f9400000 ldr x0, [x0] 8c0: 91000402 add x2, x0, #0x1 8c4: f94007e1 ldr x1, [sp,#8] 8c8: f9000022 str x2, [x1] 8cc: 39401fe1 ldrb w1, [sp,#7] 8d0: 39000001 strb w1, [x0] 8d4: d503201f nop 8d8: 910043ff add sp, sp, #0x10 8dc: d65f03c0 ret 00000000000008e0 : 8e0: a9b77bfd stp x29, x30, [sp,#-144]! 8e4: 910003fd mov x29, sp 8e8: f9001fa0 str x0, [x29,#56] 8ec: f9001ba1 str x1, [x29,#48] 8f0: f90033a2 str x2, [x29,#96] 8f4: f90037a3 str x3, [x29,#104] 8f8: f9003ba4 str x4, [x29,#112] 8fc: f9003fa5 str x5, [x29,#120] 900: f90043a6 str x6, [x29,#128] 904: f90047a7 str x7, [x29,#136] 908: 910243a0 add x0, x29, #0x90 90c: f90023a0 str x0, [x29,#64] 910: 910243a0 add x0, x29, #0x90 914: f90027a0 str x0, [x29,#72] 918: 910183a0 add x0, x29, #0x60 91c: f9002ba0 str x0, [x29,#80] 920: 128005e0 mov w0, #0xffffffd0 // #-48 924: b9005ba0 str w0, [x29,#88] 928: b9005fbf str wzr, [x29,#92] 92c: 910043a2 add x2, x29, #0x10 930: 910103a3 add x3, x29, #0x40 934: a9400460 ldp x0, x1, [x3] 938: a9000440 stp x0, x1, [x2] 93c: a9410460 ldp x0, x1, [x3,#16] 940: a9010440 stp x0, x1, [x2,#16] 944: 910043a2 add x2, x29, #0x10 948: 90000000 adrp x0, 0 94c: 91000001 add x1, x0, #0x0 950: 9100e3a0 add x0, x29, #0x38 954: aa0203e3 mov x3, x2 958: f9401ba2 ldr x2, [x29,#48] 95c: 94000000 bl 38c 960: 9100e3a0 add x0, x29, #0x38 964: 52800001 mov w1, #0x0 // #0 968: 97ffffd1 bl 8ac 96c: d503201f nop 970: a8c97bfd ldp x29, x30, [sp],#144 974: d65f03c0 ret build/sched_c.o: file format elf64-littleaarch64 Disassembly of section .text: 0000000000000000 : 0: 90000000 adrp x0, 2a0 4: 91000000 add x0, x0, #0x0 8: f9400000 ldr x0, [x0] c: f9414c01 ldr x1, [x0,#664] 10: 91000421 add x1, x1, #0x1 14: f9014c01 str x1, [x0,#664] 18: d503201f nop 1c: d65f03c0 ret 0000000000000020 : 20: 90000000 adrp x0, 2a0 24: 91000000 add x0, x0, #0x0 28: f9400000 ldr x0, [x0] 2c: f9414c01 ldr x1, [x0,#664] 30: d1000421 sub x1, x1, #0x1 34: f9014c01 str x1, [x0,#664] 38: d503201f nop 3c: d65f03c0 ret 0000000000000040 <_schedule>: 40: a9bd7bfd stp x29, x30, [sp,#-48]! 44: 910003fd mov x29, sp 48: 94000000 bl 0 4c: 12800000 mov w0, #0xffffffff // #-1 50: b9002ba0 str w0, [x29,#40] 54: b9002fbf str wzr, [x29,#44] 58: b90027bf str wzr, [x29,#36] 5c: 1400001a b c4 <_schedule+0x84> 60: 90000000 adrp x0, 2a8 64: 91000000 add x0, x0, #0x0 68: b98027a1 ldrsw x1, [x29,#36] 6c: f8617800 ldr x0, [x0,x1,lsl #3] 70: f9000fa0 str x0, [x29,#24] 74: f9400fa0 ldr x0, [x29,#24] 78: f100001f cmp x0, #0x0 7c: 540001e0 b.eq b8 <_schedule+0x78> 80: f9400fa0 ldr x0, [x29,#24] 84: f9414000 ldr x0, [x0,#640] 88: f100001f cmp x0, #0x0 8c: 54000161 b.ne b8 <_schedule+0x78> 90: f9400fa0 ldr x0, [x29,#24] 94: f9414401 ldr x1, [x0,#648] 98: b9802ba0 ldrsw x0, [x29,#40] 9c: eb00003f cmp x1, x0 a0: 540000cd b.le b8 <_schedule+0x78> a4: f9400fa0 ldr x0, [x29,#24] a8: f9414400 ldr x0, [x0,#648] ac: b9002ba0 str w0, [x29,#40] b0: b94027a0 ldr w0, [x29,#36] b4: b9002fa0 str w0, [x29,#44] b8: b94027a0 ldr w0, [x29,#36] bc: 11000400 add w0, w0, #0x1 c0: b90027a0 str w0, [x29,#36] c4: b94027a0 ldr w0, [x29,#36] c8: 7100081f cmp w0, #0x2 cc: 54fffcad b.le 60 <_schedule+0x20> d0: b9402ba0 ldr w0, [x29,#40] d4: 7100001f cmp w0, #0x0 d8: 54000341 b.ne 140 <_schedule+0x100> dc: b90023bf str wzr, [x29,#32] e0: 14000014 b 130 <_schedule+0xf0> e4: 90000000 adrp x0, 2a8 e8: 91000000 add x0, x0, #0x0 ec: b98023a1 ldrsw x1, [x29,#32] f0: f8617800 ldr x0, [x0,x1,lsl #3] f4: f9000fa0 str x0, [x29,#24] f8: f9400fa0 ldr x0, [x29,#24] fc: f100001f cmp x0, #0x0 100: 54000120 b.eq 124 <_schedule+0xe4> 104: f9400fa0 ldr x0, [x29,#24] 108: f9414400 ldr x0, [x0,#648] 10c: 9341fc01 asr x1, x0, #1 110: f9400fa0 ldr x0, [x29,#24] 114: f9414800 ldr x0, [x0,#656] 118: 8b000021 add x1, x1, x0 11c: f9400fa0 ldr x0, [x29,#24] 120: f9014401 str x1, [x0,#648] 124: b94023a0 ldr w0, [x29,#32] 128: 11000400 add w0, w0, #0x1 12c: b90023a0 str w0, [x29,#32] 130: b94023a0 ldr w0, [x29,#32] 134: 7100081f cmp w0, #0x2 138: 54fffd6d b.le e4 <_schedule+0xa4> 13c: 17ffffc4 b 4c <_schedule+0xc> 140: d503201f nop 144: 90000000 adrp x0, 2a8 148: 91000000 add x0, x0, #0x0 14c: b9802fa1 ldrsw x1, [x29,#44] 150: f8617800 ldr x0, [x0,x1,lsl #3] 154: 94000000 bl 190 158: 94000000 bl 20 15c: d503201f nop 160: a8c37bfd ldp x29, x30, [sp],#48 164: d65f03c0 ret 0000000000000168 : 168: a9bf7bfd stp x29, x30, [sp,#-16]! 16c: 910003fd mov x29, sp 170: 90000000 adrp x0, 2a0 174: 91000000 add x0, x0, #0x0 178: f9400000 ldr x0, [x0] 17c: f901441f str xzr, [x0,#648] 180: 94000000 bl 40 <_schedule> 184: d503201f nop 188: a8c17bfd ldp x29, x30, [sp],#16 18c: d65f03c0 ret 0000000000000190 : 190: a9bc7bfd stp x29, x30, [sp,#-64]! 194: 910003fd mov x29, sp 198: f9000fa0 str x0, [x29,#24] 19c: 90000000 adrp x0, 0 1a0: 91000000 add x0, x0, #0x0 1a4: 94000000 bl 0 1a8: b9003fbf str wzr, [x29,#60] 1ac: 14000028 b 24c 1b0: 90000000 adrp x0, 2a8 1b4: 91000000 add x0, x0, #0x0 1b8: b9803fa1 ldrsw x1, [x29,#60] 1bc: f8617800 ldr x0, [x0,x1,lsl #3] 1c0: f9001ba0 str x0, [x29,#48] 1c4: f9401ba0 ldr x0, [x29,#48] 1c8: f9414401 ldr x1, [x0,#648] 1cc: 90000000 adrp x0, 0 1d0: 91000000 add x0, x0, #0x0 1d4: aa0103e2 mov x2, x1 1d8: b9403fa1 ldr w1, [x29,#60] 1dc: 94000000 bl 0 1e0: f9401ba0 ldr x0, [x29,#48] 1e4: f9414801 ldr x1, [x0,#656] 1e8: 90000000 adrp x0, 0 1ec: 91000000 add x0, x0, #0x0 1f0: aa0103e2 mov x2, x1 1f4: b9403fa1 ldr w1, [x29,#60] 1f8: 94000000 bl 0 1fc: f9401ba0 ldr x0, [x29,#48] 200: f9414c01 ldr x1, [x0,#664] 204: 90000000 adrp x0, 0 208: 91000000 add x0, x0, #0x0 20c: aa0103e2 mov x2, x1 210: b9403fa1 ldr w1, [x29,#60] 214: 94000000 bl 0 218: f9401ba0 ldr x0, [x29,#48] 21c: f9402c01 ldr x1, [x0,#88] 220: 90000000 adrp x0, 0 224: 91000000 add x0, x0, #0x0 228: aa0103e2 mov x2, x1 22c: b9403fa1 ldr w1, [x29,#60] 230: 94000000 bl 0 234: 90000000 adrp x0, 0 238: 91000000 add x0, x0, #0x0 23c: 94000000 bl 0 240: b9403fa0 ldr w0, [x29,#60] 244: 11000400 add w0, w0, #0x1 248: b9003fa0 str w0, [x29,#60] 24c: b9403fa0 ldr w0, [x29,#60] 250: 7100081f cmp w0, #0x2 254: 54fffaed b.le 1b0 258: 90000000 adrp x0, 0 25c: 91000000 add x0, x0, #0x0 260: 94000000 bl 0 264: 90000000 adrp x0, 2a0 268: 91000000 add x0, x0, #0x0 26c: f9400001 ldr x1, [x0] 270: f9400fa0 ldr x0, [x29,#24] 274: eb00003f cmp x1, x0 278: 540001a0 b.eq 2ac 27c: 90000000 adrp x0, 2a0 280: 91000000 add x0, x0, #0x0 284: f9400000 ldr x0, [x0] 288: f90017a0 str x0, [x29,#40] 28c: 90000000 adrp x0, 2a0 290: 91000000 add x0, x0, #0x0 294: f9400fa1 ldr x1, [x29,#24] 298: f9000001 str x1, [x0] 29c: f9400fa1 ldr x1, [x29,#24] 2a0: f94017a0 ldr x0, [x29,#40] 2a4: 94000000 bl 0 2a8: 14000002 b 2b0 2ac: d503201f nop 2b0: a8c47bfd ldp x29, x30, [sp],#64 2b4: d65f03c0 ret 00000000000002b8 : 2b8: a9bf7bfd stp x29, x30, [sp,#-16]! 2bc: 910003fd mov x29, sp 2c0: 94000000 bl 20 2c4: d503201f nop 2c8: a8c17bfd ldp x29, x30, [sp],#16 2cc: d65f03c0 ret 00000000000002d0 : 2d0: a9bf7bfd stp x29, x30, [sp,#-16]! 2d4: 910003fd mov x29, sp 2d8: 90000000 adrp x0, 2a0 2dc: 91000000 add x0, x0, #0x0 2e0: f9400000 ldr x0, [x0] 2e4: f9414401 ldr x1, [x0,#648] 2e8: d1000421 sub x1, x1, #0x1 2ec: f9014401 str x1, [x0,#648] 2f0: 90000000 adrp x0, 2a0 2f4: 91000000 add x0, x0, #0x0 2f8: f9400000 ldr x0, [x0] 2fc: f9414400 ldr x0, [x0,#648] 300: f100001f cmp x0, #0x0 304: 540001ec b.gt 340 308: 90000000 adrp x0, 2a0 30c: 91000000 add x0, x0, #0x0 310: f9400000 ldr x0, [x0] 314: f9414c00 ldr x0, [x0,#664] 318: f100001f cmp x0, #0x0 31c: 5400012c b.gt 340 320: 90000000 adrp x0, 2a0 324: 91000000 add x0, x0, #0x0 328: f9400000 ldr x0, [x0] 32c: f901441f str xzr, [x0,#648] 330: 94000000 bl 0 334: 94000000 bl 40 <_schedule> 338: 94000000 bl 0 33c: 14000002 b 344 340: d503201f nop 344: a8c17bfd ldp x29, x30, [sp],#16 348: d65f03c0 ret build/sched_s.o: file format elf64-littleaarch64 Disassembly of section .text: 0000000000000000 : 0: d280000a mov x10, #0x0 // #0 4: 8b0a0008 add x8, x0, x10 8: 910003e9 mov x9, sp c: a8815113 stp x19, x20, [x8],#16 10: a8815915 stp x21, x22, [x8],#16 14: a8816117 stp x23, x24, [x8],#16 18: a8816919 stp x25, x26, [x8],#16 1c: a881711b stp x27, x28, [x8],#16 20: a881251d stp x29, x9, [x8],#16 24: f900011e str x30, [x8] 28: 8b0a0028 add x8, x1, x10 2c: a8c15113 ldp x19, x20, [x8],#16 30: a8c15915 ldp x21, x22, [x8],#16 34: a8c16117 ldp x23, x24, [x8],#16 38: a8c16919 ldp x25, x26, [x8],#16 3c: a8c1711b ldp x27, x28, [x8],#16 40: a8c1251d ldp x29, x9, [x8],#16 44: f940011e ldr x30, [x8] 48: 9100013f mov sp, x9 4c: d2800e0a mov x10, #0x70 // #112 50: 8b0a0008 add x8, x0, x10 54: ac810500 stp q0, q1, [x8],#32 58: ac810d02 stp q2, q3, [x8],#32 5c: ac811504 stp q4, q5, [x8],#32 60: ac811d06 stp q6, q7, [x8],#32 64: ac812508 stp q8, q9, [x8],#32 68: ac812d0a stp q10, q11, [x8],#32 6c: ac81350c stp q12, q13, [x8],#32 70: ac813d0e stp q14, q15, [x8],#32 74: ac814510 stp q16, q17, [x8],#32 78: ac814d12 stp q18, q19, [x8],#32 7c: ac815514 stp q20, q21, [x8],#32 80: ac815d16 stp q22, q23, [x8],#32 84: ac816518 stp q24, q25, [x8],#32 88: ac816d1a stp q26, q27, [x8],#32 8c: ac81751c stp q28, q29, [x8],#32 90: ac817d1e stp q30, q31, [x8],#32 94: d53b442b mrs x11, fpsr 98: f800850b str x11, [x8],#8 9c: d53b440b mrs x11, fpcr a0: f900010b str x11, [x8] a4: 8b0a0028 add x8, x1, x10 a8: acc10500 ldp q0, q1, [x8],#32 ac: acc10d02 ldp q2, q3, [x8],#32 b0: acc11504 ldp q4, q5, [x8],#32 b4: acc11d06 ldp q6, q7, [x8],#32 b8: acc12508 ldp q8, q9, [x8],#32 bc: acc12d0a ldp q10, q11, [x8],#32 c0: acc1350c ldp q12, q13, [x8],#32 c4: acc13d0e ldp q14, q15, [x8],#32 c8: acc14510 ldp q16, q17, [x8],#32 cc: acc14d12 ldp q18, q19, [x8],#32 d0: acc15514 ldp q20, q21, [x8],#32 d4: acc15d16 ldp q22, q23, [x8],#32 d8: acc16518 ldp q24, q25, [x8],#32 dc: acc16d1a ldp q26, q27, [x8],#32 e0: acc1751c ldp q28, q29, [x8],#32 e4: acc17d1e ldp q30, q31, [x8],#32 e8: f840850b ldr x11, [x8],#8 ec: d51b442b msr fpsr, x11 f0: f940010b ldr x11, [x8] f4: d51b440b msr fpcr, x11 f8: d65f03c0 ret build/timer_c.o: file format elf64-littleaarch64 Disassembly of section .text: 0000000000000000 : 0: a9bf7bfd stp x29, x30, [sp,#-16]! 4: 910003fd mov x29, sp 8: d2860080 mov x0, #0x3004 // #12292 c: f2a7e000 movk x0, #0x3f00, lsl #16 10: 94000000 bl 0 14: 2a0003e1 mov w1, w0 18: 90000000 adrp x0, 0 1c: 91000000 add x0, x0, #0x0 20: b9000001 str w1, [x0] 24: 90000000 adrp x0, 0 28: 91000000 add x0, x0, #0x0 2c: b9400001 ldr w1, [x0] 30: 5281a800 mov w0, #0xd40 // #3392 34: 72a00060 movk w0, #0x3, lsl #16 38: 0b000021 add w1, w1, w0 3c: 90000000 adrp x0, 0 40: 91000000 add x0, x0, #0x0 44: b9000001 str w1, [x0] 48: 90000000 adrp x0, 0 4c: 91000000 add x0, x0, #0x0 50: b9400000 ldr w0, [x0] 54: 2a0003e1 mov w1, w0 58: d2860200 mov x0, #0x3010 // #12304 5c: f2a7e000 movk x0, #0x3f00, lsl #16 60: 94000000 bl 0 64: d503201f nop 68: a8c17bfd ldp x29, x30, [sp],#16 6c: d65f03c0 ret 0000000000000070 : 70: a9bf7bfd stp x29, x30, [sp,#-16]! 74: 910003fd mov x29, sp 78: 90000000 adrp x0, 0 7c: 91000000 add x0, x0, #0x0 80: b9400001 ldr w1, [x0] 84: 5281a800 mov w0, #0xd40 // #3392 88: 72a00060 movk w0, #0x3, lsl #16 8c: 0b000021 add w1, w1, w0 90: 90000000 adrp x0, 0 94: 91000000 add x0, x0, #0x0 98: b9000001 str w1, [x0] 9c: 90000000 adrp x0, 0 a0: 91000000 add x0, x0, #0x0 a4: b9400000 ldr w0, [x0] a8: 2a0003e1 mov w1, w0 ac: d2860200 mov x0, #0x3010 // #12304 b0: f2a7e000 movk x0, #0x3f00, lsl #16 b4: 94000000 bl 0 b8: 52800041 mov w1, #0x2 // #2 bc: d2860000 mov x0, #0x3000 // #12288 c0: f2a7e000 movk x0, #0x3f00, lsl #16 c4: 94000000 bl 0 c8: 94000000 bl 0 cc: d503201f nop d0: a8c17bfd ldp x29, x30, [sp],#16 d4: d65f03c0 ret build/utils_s.o: file format elf64-littleaarch64 Disassembly of section .text: 0000000000000000 : 0: d5384240 mrs x0, currentel 4: d342fc00 lsr x0, x0, #2 8: d65f03c0 ret 000000000000000c : c: b9000001 str w1, [x0] 10: d65f03c0 ret 0000000000000014 : 14: b9400000 ldr w0, [x0] 18: d65f03c0 ret 000000000000001c : 1c: f1000400 subs x0, x0, #0x1 20: 54000001 b.ne 1c 24: d65f03c0 ret ================================================ FILE: exercises/lesson04/3/avenito/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) // *************************************** // CPACR_EL1, Architectural Feature Access Control Register (EL1) Page 2411 of AArch64-Reference-Manual. // *************************************** #define CPACR_FPEN (3 << 20) #define CPACR_VALUE (CPACR_FPEN) #endif ================================================ FILE: exercises/lesson04/3/avenito/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 272 // size of all saved registers #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #ifndef __ASSEMBLER__ void ret_from_fork(void); #endif #endif ================================================ FILE: exercises/lesson04/3/avenito/include/fork.h ================================================ #ifndef _FORK_H #define _FORK_H int copy_process(unsigned long fn, unsigned long arg); #endif ================================================ FILE: exercises/lesson04/3/avenito/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson04/3/avenito/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson04/3/avenito/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #include "peripherals/base.h" #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #define HIGH_MEMORY PBASE #define PAGING_MEMORY (HIGH_MEMORY - LOW_MEMORY) #define PAGING_PAGES (PAGING_MEMORY/PAGE_SIZE) #ifndef __ASSEMBLER__ unsigned long get_free_page(); void free_page(unsigned long p); void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson04/3/avenito/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson04/3/avenito/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson04/3/avenito/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson04/3/avenito/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson04/3/avenito/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson04/3/avenito/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson04/3/avenito/include/sched.h ================================================ #ifndef _SCHED_H #define _SCHED_H #define THREAD_CPU_CONTEXT 0 // offset of cpu_context in task_struct #define THREAD_FPSIMD_CONTEXT (14*64/8) // 14 = 13 registers of cpu_context + 1 to point to the next free position // each register (Xn) 64 bits and 8 bits/byte => 14 * 64 / 8 = offset of struct #ifndef __ASSEMBLER__ #define THREAD_SIZE 4096 #define NR_TASKS 3 // changed to 3 to be easier #define FIRST_TASK task[0] #define LAST_TASK task[NR_TASKS-1] #define TASK_RUNNING 0 extern struct task_struct *current; extern struct task_struct * task[NR_TASKS]; extern int nr_tasks; // Add a struct to save the FP/SIMD registers struct fpsimd_context { __uint128_t v[32]; unsigned int fpsr; unsigned int fpcr; }; struct cpu_context { unsigned long x19; unsigned long x20; unsigned long x21; unsigned long x22; unsigned long x23; unsigned long x24; unsigned long x25; unsigned long x26; unsigned long x27; unsigned long x28; unsigned long fp; unsigned long sp; unsigned long pc; }; struct task_struct { struct cpu_context cpu_context; struct fpsimd_context fpsimd_context; long state; long counter; long priority; long preempt_count; }; extern void sched_init(void); extern void schedule(void); extern void timer_tick(void); extern void preempt_disable(void); extern void preempt_enable(void); extern void switch_to(struct task_struct* next); extern void cpu_switch_to(struct task_struct* prev, struct task_struct* next); // Change also in INIT_TASK #define INIT_TASK \ /*cpu_context*/ { {0,0,0,0,0,0,0,0,0,0,0,0,0}, \ /*fpsimd_context*/ { {0}, 0,0}, \ /* state etc */ 0,0,1,0} #endif #endif ================================================ FILE: exercises/lesson04/3/avenito/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson04/3/avenito/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson04/3/avenito/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 ldr x0, =CPACR_VALUE // This control does not cause any instructions to be trapped msr cpacr_el1, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #(2 * SECTION_SIZE) bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson04/3/avenito/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson04/3/avenito/src/entry.S ================================================ #include "entry.h" .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] mrs x22, elr_el1 mrs x23, spsr_el1 stp x30, x22, [sp, #16 * 15] str x23, [sp, #16 * 16] .endm .macro kernel_exit ldr x23, [sp, #16 * 16] ldp x30, x22, [sp, #16 * 15] msr elr_el1, x22 msr spsr_el1, x23 ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 ventry irq_invalid_el0_64 // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry ERROR_INVALID_EL1h sync_invalid_el0_64: handle_invalid_entry SYNC_INVALID_EL0_64 irq_invalid_el0_64: handle_invalid_entry IRQ_INVALID_EL0_64 fiq_invalid_el0_64: handle_invalid_entry FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry ERROR_INVALID_EL0_32 el1_irq: kernel_entry bl handle_irq kernel_exit .globl ret_from_fork ret_from_fork: bl schedule_tail mov x0, x20 blr x19 //should never return .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson04/3/avenito/src/fork.c ================================================ #include "printf.h" #include "mm.h" #include "sched.h" #include "entry.h" int copy_process(unsigned long fn, unsigned long arg) { preempt_disable(); struct task_struct *p; p = (struct task_struct *) get_free_page(); if (!p) return 1; p->priority = current->priority; p->state = TASK_RUNNING; p->counter = p->priority; p->preempt_count = 1; //disable preemtion until schedule_tail p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)p + THREAD_SIZE; int pid = nr_tasks++; task[pid] = p; printf("\n\r----------- Task[%d] created -----------\r\n", pid); printf("\n\rStruct task allocated at 0x%08x.\r\n", p); printf("p->cpu_context.x19 = 0x%08x. (fn)\r\n", p->cpu_context.x19); printf("p->cpu_context.x20 = 0x%08x. (arg)\r\n", p->cpu_context.x20); printf("p->cpu_context.pc = 0x%08x. (ret_from_fork)\r\n", p->cpu_context.pc); printf("p->cpu_context.sp = 0x%08x. (sp)\r\n", p->cpu_context.sp); preempt_enable(); return 0; } ================================================ FILE: exercises/lesson04/3/avenito/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson04/3/avenito/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "timer.h" #include "entry.h" #include "peripherals/irq.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32" }; void enable_interrupt_controller() { put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { unsigned int irq = get32(IRQ_PENDING_1); switch (irq) { case (SYSTEM_TIMER_IRQ_1): handle_timer_irq(); break; default: printf("Inknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson04/3/avenito/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "timer.h" #include "irq.h" #include "fork.h" #include "sched.h" #include "mini_uart.h" void process(char *array) { while (1){ for (int i = 0; i < 5; i++){ uart_send(array[i]); delay(100000); } } } void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); printf("Comecou ..."); int res = copy_process((unsigned long)&process, (unsigned long)"12345"); if (res != 0) { printf("error while starting process 1"); return; } res = copy_process((unsigned long)&process, (unsigned long)"abcde"); if (res != 0) { printf("error while starting process 2"); return; } while (1){ schedule(); } } ================================================ FILE: exercises/lesson04/3/avenito/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson04/3/avenito/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson04/3/avenito/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson04/3/avenito/src/mm.c ================================================ #include "mm.h" static unsigned short mem_map [ PAGING_PAGES ] = {0,}; unsigned long get_free_page() { for (int i = 0; i < PAGING_PAGES; i++){ if (mem_map[i] == 0){ mem_map[i] = 1; return LOW_MEMORY + i*PAGE_SIZE; } } return 0; } void free_page(unsigned long p){ mem_map[(p - LOW_MEMORY) / PAGE_SIZE] = 0; } ================================================ FILE: exercises/lesson04/3/avenito/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson04/3/avenito/src/sched.S ================================================ #include "sched.h" .globl cpu_switch_to cpu_switch_to: mov x10, #THREAD_CPU_CONTEXT add x8, x0, x10 mov x9, sp stp x19, x20, [x8], #16 // store callee-saved registers stp x21, x22, [x8], #16 stp x23, x24, [x8], #16 stp x25, x26, [x8], #16 stp x27, x28, [x8], #16 stp x29, x9, [x8], #16 str x30, [x8] add x8, x1, x10 ldp x19, x20, [x8], #16 // restore callee-saved registers ldp x21, x22, [x8], #16 ldp x23, x24, [x8], #16 ldp x25, x26, [x8], #16 ldp x27, x28, [x8], #16 ldp x29, x9, [x8], #16 ldr x30, [x8] mov sp, x9 /* 1. Point to fpsimd_context offset, 2. Store FP/SIMD registers adding 32 bytes (128 + 128 bits) each store cycle, 3. Load and store FP/SIMD control and status register. 4. Point to next task sp 5. Restore FP/SIMD registers adding 32 bytes (128 + 128 bits) each store cycle, 6. Restore FP/SIMD control and status register. */ mov x10, #THREAD_FPSIMD_CONTEXT // 1. add x8, x0, x10 // 2. stp q0, q1, [x8], #32 stp q2, q3, [x8], #32 stp q4, q5, [x8], #32 stp q6, q7, [x8], #32 stp q8, q9, [x8], #32 stp q10, q11, [x8], #32 stp q12, q13, [x8], #32 stp q14, q15, [x8], #32 stp q16, q17, [x8], #32 stp q18, q19, [x8], #32 stp q20, q21, [x8], #32 stp q22, q23, [x8], #32 stp q24, q25, [x8], #32 stp q26, q27, [x8], #32 stp q28, q29, [x8], #32 stp q30, q31, [x8], #32 mrs x11, fpsr // 3. str x11, [x8], #8 mrs x11, fpcr str x11, [x8] add x8, x1, x10 // 4. ldp q0, q1, [x8], #32 // 5. ldp q2, q3, [x8], #32 ldp q4, q5, [x8], #32 ldp q6, q7, [x8], #32 ldp q8, q9, [x8], #32 ldp q10, q11, [x8], #32 ldp q12, q13, [x8], #32 ldp q14, q15, [x8], #32 ldp q16, q17, [x8], #32 ldp q18, q19, [x8], #32 ldp q20, q21, [x8], #32 ldp q22, q23, [x8], #32 ldp q24, q25, [x8], #32 ldp q26, q27, [x8], #32 ldp q28, q29, [x8], #32 ldp q30, q31, [x8], #32 ldr x11, [x8], #8 // 6. msr fpsr, x11 ldr x11, [x8] msr fpcr, x11 ret ================================================ FILE: exercises/lesson04/3/avenito/src/sched.c ================================================ #include "sched.h" #include "irq.h" #include "printf.h" static struct task_struct init_task = INIT_TASK; struct task_struct *current = &(init_task); struct task_struct * task[NR_TASKS] = {&(init_task), }; int nr_tasks = 1; void preempt_disable(void) { current->preempt_count++; } void preempt_enable(void) { current->preempt_count--; } void _schedule(void) { preempt_disable(); int next,c; struct task_struct * p; while (1) { c = -1; next = 0; for (int i = 0; i < NR_TASKS; i++){ p = task[i]; if (p && p->state == TASK_RUNNING && p->counter > c) { c = p->counter; next = i; } } if (c) { break; } for (int i = 0; i < NR_TASKS; i++) { p = task[i]; if (p) { p->counter = (p->counter >> 1) + p->priority; } } } switch_to(task[next]); preempt_enable(); } void schedule(void) { current->counter = 0; _schedule(); } void switch_to(struct task_struct * next) { struct task_struct * p; printf("\n\r\n\r----------- Task switch -----------\r\n"); for(int t=0; t < NR_TASKS; t++) { p = task[t]; printf("\n\rtask[%d] counter = %d\n\r", t, p->counter); printf("task[%d] priority = %d\n\r", t, p->priority); printf("task[%d] preempt_count = %d\n\r", t, p->preempt_count); printf("task[%d] sp = 0x%08x\n\r", t, p->cpu_context.sp); printf("\n\r------------------------------\r\n"); } printf("\n\rtask output: "); if (current == next) return; struct task_struct * prev = current; current = next; cpu_switch_to(prev, next); } void schedule_tail(void) { preempt_enable(); } void timer_tick() { --current->counter; if (current->counter>0 || current->preempt_count >0) { return; } current->counter=0; enable_irq(); _schedule(); disable_irq(); } ================================================ FILE: exercises/lesson04/3/avenito/src/timer.c ================================================ #include "utils.h" #include "printf.h" #include "sched.h" #include "peripherals/timer.h" const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); timer_tick(); } ================================================ FILE: exercises/lesson04/3/avenito/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret .globl set_fpsimd_reg0 set_fpsimd_reg0: mov v0.D[0], x0 ret .globl set_fpsimd_reg2 set_fpsimd_reg2: mov v2.D[0], x0 ret .globl set_fpsimd_reg31 set_fpsimd_reg31: mov v31.D[0], x0 ret .globl get_fpsimd_reg0 get_fpsimd_reg0: mov x0, v0.D[0] ret .globl get_fpsimd_reg2 get_fpsimd_reg2: mov x0, v2.D[0] ret .globl get_fpsimd_reg31 get_fpsimd_reg31: mov x0, v31.D[0] ret ================================================ FILE: exercises/lesson04/3/bl4ckout31/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -DPRINTF_SUPPORT_LONG ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson04/3/bl4ckout31/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson04/3/bl4ckout31/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson04/3/bl4ckout31/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) // *************************************** // CPACR_EL1, Architectural Feature Access Control Register (EL1) Page 2411 of AArch64-Reference-Manual. // *************************************** #define CPACR_FPEN (3 << 20) #define CPACR_VALUE (CPACR_FPEN) #endif ================================================ FILE: exercises/lesson04/3/bl4ckout31/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 272 // size of all saved registers #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #ifndef __ASSEMBLER__ void ret_from_fork(void); #endif #endif ================================================ FILE: exercises/lesson04/3/bl4ckout31/include/fork.h ================================================ #ifndef _FORK_H #define _FORK_H int copy_process(unsigned long fn, unsigned long arg); #endif ================================================ FILE: exercises/lesson04/3/bl4ckout31/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson04/3/bl4ckout31/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson04/3/bl4ckout31/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #include "peripherals/base.h" #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #define HIGH_MEMORY PBASE #define PAGING_MEMORY (HIGH_MEMORY - LOW_MEMORY) #define PAGING_PAGES (PAGING_MEMORY/PAGE_SIZE) #ifndef __ASSEMBLER__ unsigned long get_free_page(); void free_page(unsigned long p); void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson04/3/bl4ckout31/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson04/3/bl4ckout31/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson04/3/bl4ckout31/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson04/3/bl4ckout31/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson04/3/bl4ckout31/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson04/3/bl4ckout31/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson04/3/bl4ckout31/include/sched.h ================================================ #ifndef _SCHED_H #define _SCHED_H #define THREAD_CPU_CONTEXT 0 // offset of cpu_context in task_struct #define THREAD_FPSIMD_CONTEXT 0x70 // offset of fpsimd_context in task_struct #ifndef __ASSEMBLER__ #define THREAD_SIZE 4096 #define NR_TASKS 64 #define FIRST_TASK task[0] #define LAST_TASK task[NR_TASKS-1] #define TASK_RUNNING 0 extern struct task_struct *current; extern struct task_struct * task[NR_TASKS]; extern int nr_tasks; struct cpu_context { unsigned long x19; unsigned long x20; unsigned long x21; unsigned long x22; unsigned long x23; unsigned long x24; unsigned long x25; unsigned long x26; unsigned long x27; unsigned long x28; unsigned long fp; unsigned long sp; unsigned long pc; }; struct fpsimd_context { __uint128_t vregs[32]; unsigned int fpsr; unsigned int fpcr; }; struct task_struct { struct cpu_context cpu_context; struct fpsimd_context fpsimd_context; long state; long counter; long priority; long preempt_count; }; extern void sched_init(void); extern void schedule(void); extern void timer_tick(void); extern void preempt_disable(void); extern void preempt_enable(void); extern void switch_to(struct task_struct* next); extern void cpu_switch_to(struct task_struct* prev, struct task_struct* next); #define INIT_TASK \ /*cpu_context*/ { {0}, \ /*fpsimd_context*/ { {0}, 0,0}, \ /* state etc */ 0,0,1,0 \ } #endif #endif ================================================ FILE: exercises/lesson04/3/bl4ckout31/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson04/3/bl4ckout31/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); extern void set_fpsimd_reg0(unsigned int); extern void set_fpsimd_reg2(unsigned int); extern void set_fpsimd_reg31(unsigned int); extern unsigned int get_fpsimd_reg0(void); extern unsigned int get_fpsimd_reg2(void); extern unsigned int get_fpsimd_reg31(void); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson04/3/bl4ckout31/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 ldr x0, =CPACR_VALUE msr cpacr_el1, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #(2 * SECTION_SIZE) bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson04/3/bl4ckout31/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson04/3/bl4ckout31/src/entry.S ================================================ #include "entry.h" .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] mrs x22, elr_el1 mrs x23, spsr_el1 stp x30, x22, [sp, #16 * 15] str x23, [sp, #16 * 16] .endm .macro kernel_exit ldr x23, [sp, #16 * 16] ldp x30, x22, [sp, #16 * 15] msr elr_el1, x22 msr spsr_el1, x23 ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 ventry irq_invalid_el0_64 // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry ERROR_INVALID_EL1h sync_invalid_el0_64: handle_invalid_entry SYNC_INVALID_EL0_64 irq_invalid_el0_64: handle_invalid_entry IRQ_INVALID_EL0_64 fiq_invalid_el0_64: handle_invalid_entry FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry ERROR_INVALID_EL0_32 el1_irq: kernel_entry bl handle_irq kernel_exit .globl ret_from_fork ret_from_fork: bl schedule_tail mov x0, x20 blr x19 //should never return .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson04/3/bl4ckout31/src/fork.c ================================================ #include "mm.h" #include "sched.h" #include "entry.h" int copy_process(unsigned long fn, unsigned long arg) { preempt_disable(); struct task_struct *p; p = (struct task_struct *) get_free_page(); if (!p) return 1; p->priority = current->priority; p->state = TASK_RUNNING; p->counter = p->priority; p->preempt_count = 1; //disable preemtion until schedule_tail p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)p + THREAD_SIZE; int pid = nr_tasks++; task[pid] = p; preempt_enable(); return 0; } ================================================ FILE: exercises/lesson04/3/bl4ckout31/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson04/3/bl4ckout31/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "timer.h" #include "entry.h" #include "peripherals/irq.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32" }; void enable_interrupt_controller() { put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { unsigned int irq = get32(IRQ_PENDING_1); switch (irq) { case (SYSTEM_TIMER_IRQ_1): handle_timer_irq(); break; default: printf("Inknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson04/3/bl4ckout31/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "timer.h" #include "irq.h" #include "fork.h" #include "sched.h" #include "mini_uart.h" #include "mm.h" void process(char *array) { if(array[0] == '1') { set_fpsimd_reg0(1); set_fpsimd_reg2(2); set_fpsimd_reg31(3); } else { set_fpsimd_reg0(5); set_fpsimd_reg2(6); set_fpsimd_reg31(7); } while (1) { for (int i = 0; i < 5; i++){ unsigned int a = get_fpsimd_reg0(); unsigned int b = get_fpsimd_reg2(); unsigned int c = get_fpsimd_reg31(); uart_send(a + '0'); delay(100000); uart_send(b + '0'); delay(100000); uart_send(c + '0'); delay(100000); } } } void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); int res = copy_process((unsigned long)&process, (unsigned long)"12345"); if (res != 0) { printf("error while starting process 1"); return; } res = copy_process((unsigned long)&process, (unsigned long)"abcde"); if (res != 0) { printf("error while starting process 2"); return; } while (1){ schedule(); } } ================================================ FILE: exercises/lesson04/3/bl4ckout31/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson04/3/bl4ckout31/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson04/3/bl4ckout31/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson04/3/bl4ckout31/src/mm.c ================================================ #include "mm.h" static unsigned short mem_map [ PAGING_PAGES ] = {0,}; unsigned long get_free_page() { for (int i = 0; i < PAGING_PAGES; i++){ if (mem_map[i] == 0){ mem_map[i] = 1; return LOW_MEMORY + i*PAGE_SIZE; } } return 0; } void free_page(unsigned long p){ mem_map[(p - LOW_MEMORY) / PAGE_SIZE] = 0; } ================================================ FILE: exercises/lesson04/3/bl4ckout31/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson04/3/bl4ckout31/src/sched.S ================================================ #include "sched.h" .globl cpu_switch_to cpu_switch_to: mov x10, #THREAD_CPU_CONTEXT add x8, x0, x10 mov x9, sp stp x19, x20, [x8], #16 // store callee-saved registers stp x21, x22, [x8], #16 stp x23, x24, [x8], #16 stp x25, x26, [x8], #16 stp x27, x28, [x8], #16 stp x29, x9, [x8], #16 str x30, [x8] add x8, x1, x10 ldp x19, x20, [x8], #16 // restore callee-saved registers ldp x21, x22, [x8], #16 ldp x23, x24, [x8], #16 ldp x25, x26, [x8], #16 ldp x27, x28, [x8], #16 ldp x29, x9, [x8], #16 ldr x30, [x8] mov sp, x9 mov x10, #THREAD_FPSIMD_CONTEXT add x8, x0, x10 stp q0, q1, [x8], #32 // store FP/SIMD registers stp q2, q3, [x8], #32 stp q4, q5, [x8], #32 stp q6, q7, [x8], #32 stp q8, q9, [x8], #32 stp q10, q11, [x8], #32 stp q12, q13, [x8], #32 stp q14, q15, [x8], #32 stp q16, q17, [x8], #32 stp q18, q19, [x8], #32 stp q20, q21, [x8], #32 stp q22, q23, [x8], #32 stp q24, q25, [x8], #32 stp q26, q27, [x8], #32 stp q28, q29, [x8], #32 stp q30, q31, [x8], #32 mrs x11, fpsr // store FP/SIMD control & status register str x11, [x8], #8 mrs x11, fpcr str x11, [x8] add x8, x1, x10 ldp q0, q1, [x8], #32 // restore FP/SIMD registers ldp q2, q3, [x8], #32 ldp q4, q5, [x8], #32 ldp q6, q7, [x8], #32 ldp q8, q9, [x8], #32 ldp q10, q11, [x8], #32 ldp q12, q13, [x8], #32 ldp q14, q15, [x8], #32 ldp q16, q17, [x8], #32 ldp q18, q19, [x8], #32 ldp q20, q21, [x8], #32 ldp q22, q23, [x8], #32 ldp q24, q25, [x8], #32 ldp q26, q27, [x8], #32 ldp q28, q29, [x8], #32 ldp q30, q31, [x8], #32 ldr x11, [x8], #8 // restore FP/SIMD control & status register msr fpsr, x11 ldr x11, [x8] msr fpcr, x11 ret ================================================ FILE: exercises/lesson04/3/bl4ckout31/src/sched.c ================================================ #include "sched.h" #include "irq.h" #include "printf.h" static struct task_struct init_task = INIT_TASK; struct task_struct *current = &(init_task); struct task_struct * task[NR_TASKS] = {&(init_task), }; int nr_tasks = 1; void preempt_disable(void) { current->preempt_count++; } void preempt_enable(void) { current->preempt_count--; } void _schedule(void) { preempt_disable(); int next,c; struct task_struct * p; while (1) { c = -1; next = 0; for (int i = 0; i < NR_TASKS; i++){ p = task[i]; if (p && p->state == TASK_RUNNING && p->counter > c) { c = p->counter; next = i; } } if (c) { break; } for (int i = 0; i < NR_TASKS; i++) { p = task[i]; if (p) { p->counter = (p->counter >> 1) + p->priority; } } } switch_to(task[next]); preempt_enable(); } void schedule(void) { current->counter = 0; _schedule(); } void switch_to(struct task_struct * next) { if (current == next) return; struct task_struct * prev = current; current = next; cpu_switch_to(prev, next); } void schedule_tail(void) { preempt_enable(); } void timer_tick() { --current->counter; if (current->counter>0 || current->preempt_count >0) { return; } current->counter=0; enable_irq(); _schedule(); disable_irq(); } ================================================ FILE: exercises/lesson04/3/bl4ckout31/src/timer.c ================================================ #include "utils.h" #include "printf.h" #include "sched.h" #include "peripherals/timer.h" const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); timer_tick(); } ================================================ FILE: exercises/lesson04/3/bl4ckout31/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret .globl set_fpsimd_reg0 set_fpsimd_reg0: mov v0.D[0], x0 ret .globl set_fpsimd_reg2 set_fpsimd_reg2: mov v2.D[0], x0 ret .globl set_fpsimd_reg31 set_fpsimd_reg31: mov v31.D[0], x0 ret .globl get_fpsimd_reg0 get_fpsimd_reg0: mov x0, v0.D[0] ret .globl get_fpsimd_reg2 get_fpsimd_reg2: mov x0, v2.D[0] ret .globl get_fpsimd_reg31 get_fpsimd_reg31: mov x0, v31.D[0] ret ================================================ FILE: exercises/lesson04/3/bl4ckout31/start.sh ================================================ #!/bin/bash mount -L rpiboot /mnt cp "kernel8.img" /mnt umount /mnt ================================================ FILE: exercises/lesson04/4/avenito/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson04/4/avenito/README.md ================================================ # How to implement an unlimited number of tasks To have an unlimited number of tasks we should have an unlimited amount of memory too, but the goal is to implement a way where we can run how many tasks how is possible. A possible solution is to use a data structure called Threaded Binary Trees. [Here](https://en.wikipedia.org/wiki/Threaded_binary_tree) a reference. 1. Change the task structure in 'sched.h'. We have to change the 'INIT_TASK' too. 1. Create an initial task 1. Change the code in 'sched.c' to start by initial task ================================================ FILE: exercises/lesson04/4/avenito/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson04/4/avenito/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson04/4/avenito/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #endif ================================================ FILE: exercises/lesson04/4/avenito/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 272 // size of all saved registers #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #ifndef __ASSEMBLER__ void ret_from_fork(void); #endif #endif ================================================ FILE: exercises/lesson04/4/avenito/include/fork.h ================================================ #ifndef _FORK_H #define _FORK_H int copy_process(unsigned long fn, unsigned long arg); #endif ================================================ FILE: exercises/lesson04/4/avenito/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson04/4/avenito/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson04/4/avenito/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #include "peripherals/base.h" #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #define HIGH_MEMORY PBASE #define PAGING_MEMORY (HIGH_MEMORY - LOW_MEMORY) #define PAGING_PAGES (PAGING_MEMORY/PAGE_SIZE) #ifndef __ASSEMBLER__ unsigned long get_free_page(); void free_page(unsigned long p); void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson04/4/avenito/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson04/4/avenito/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson04/4/avenito/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson04/4/avenito/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson04/4/avenito/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson04/4/avenito/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson04/4/avenito/include/sched.h ================================================ #ifndef _SCHED_H #define _SCHED_H #define THREAD_CPU_CONTEXT 0 // offset of cpu_context in task_struct #ifndef __ASSEMBLER__ #define THREAD_SIZE 4096 // #define NR_TASKS 3 // we don't need more // #define FIRST_TASK task[0] // we don't need more // #define LAST_TASK task[NR_TASKS-1] // we don't need more #define TASK_RUNNING 0 extern struct task_struct *current; extern struct task_struct *initial_task; // we declare here as extern to be accessible from other files //extern struct task_struct * task[NR_TASKS]; // we don't need more //extern int nr_tasks; // we don't need more struct cpu_context { unsigned long x19; unsigned long x20; unsigned long x21; unsigned long x22; unsigned long x23; unsigned long x24; unsigned long x25; unsigned long x26; unsigned long x27; unsigned long x28; unsigned long fp; unsigned long sp; unsigned long pc; }; // Change the structure task struct task_struct { struct cpu_context cpu_context; long state; long counter; long priority; long preempt_count; struct task_struct* next_task; // point to the next task }; extern void sched_init(void); extern void schedule(void); extern void timer_tick(void); extern void preempt_disable(void); extern void preempt_enable(void); extern void switch_to(struct task_struct* next); extern void cpu_switch_to(struct task_struct* prev, struct task_struct* next); // Change the INIT_TASK adding 0 #define INIT_TASK \ /*cpu_context*/ { {0,0,0,0,0,0,0,0,0,0,0,0,0}, \ /* state etc */ 0,0,1, 0, 0 \ } #endif #endif ================================================ FILE: exercises/lesson04/4/avenito/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson04/4/avenito/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson04/4/avenito/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #(2 * SECTION_SIZE) bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson04/4/avenito/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson04/4/avenito/src/entry.S ================================================ #include "entry.h" .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] mrs x22, elr_el1 mrs x23, spsr_el1 stp x30, x22, [sp, #16 * 15] str x23, [sp, #16 * 16] .endm .macro kernel_exit ldr x23, [sp, #16 * 16] ldp x30, x22, [sp, #16 * 15] msr elr_el1, x22 msr spsr_el1, x23 ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 ventry irq_invalid_el0_64 // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry ERROR_INVALID_EL1h sync_invalid_el0_64: handle_invalid_entry SYNC_INVALID_EL0_64 irq_invalid_el0_64: handle_invalid_entry IRQ_INVALID_EL0_64 fiq_invalid_el0_64: handle_invalid_entry FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry ERROR_INVALID_EL0_32 el1_irq: kernel_entry bl handle_irq kernel_exit .globl ret_from_fork ret_from_fork: bl schedule_tail mov x0, x20 blr x19 //should never return .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson04/4/avenito/src/fork.c ================================================ #include "printf.h" #include "mm.h" #include "sched.h" #include "entry.h" int copy_process(unsigned long fn, unsigned long arg) { preempt_disable(); struct task_struct *p, *previous_task; p = (struct task_struct *) get_free_page(); if (!p) return 1; p->priority = current->priority; p->state = TASK_RUNNING; p->counter = p->priority; p->preempt_count = 1; //disable preemtion until schedule_tail p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)p + THREAD_SIZE; p->next_task = 0; // the just created task shall point to 0, that means it's the last one created //int pid = nr_tasks++; // we don't need more //task[pid] = p; previous_task = initial_task; // point to the first task (initial task) // Find the last created task (it points to 0) // run while previous_task->next_task != 0 while(previous_task->next_task) previous_task = previous_task->next_task; previous_task->next_task = p; // point the last one to this just created one printf("\n\r----------- Task created -----------\r\n"); printf("\n\rStruct task allocated at 0x%08x.\r\n", p); printf("p->cpu_context.x19 = 0x%08x. (fn)\r\n", p->cpu_context.x19); printf("p->cpu_context.x20 = 0x%08x. (arg)\r\n", p->cpu_context.x20); printf("p->cpu_context.pc = 0x%08x. (ret_from_fork)\r\n", p->cpu_context.pc); printf("p->cpu_context.sp = 0x%08x. (sp)\r\n", p->cpu_context.sp); printf("\n\r----------- Previous task -----------\r\n"); printf("previous_task->next_task = 0x%08x.\r\n", previous_task->next_task); preempt_enable(); return 0; } ================================================ FILE: exercises/lesson04/4/avenito/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson04/4/avenito/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "timer.h" #include "entry.h" #include "peripherals/irq.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32" }; void enable_interrupt_controller() { put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { unsigned int irq = get32(IRQ_PENDING_1); switch (irq) { case (SYSTEM_TIMER_IRQ_1): handle_timer_irq(); break; default: printf("Inknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson04/4/avenito/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "timer.h" #include "irq.h" #include "fork.h" #include "sched.h" #include "mini_uart.h" void process(char *array) { while (1){ for (int i = 0; i < 5; i++){ uart_send(array[i]); delay(100000); } } } void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); int res = copy_process((unsigned long)&process, (unsigned long)"12345"); if (res != 0) { printf("error while starting process 1"); return; } res = copy_process((unsigned long)&process, (unsigned long)"abcde"); if (res != 0) { printf("error while starting process 2"); return; } while (1){ schedule(); } } ================================================ FILE: exercises/lesson04/4/avenito/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson04/4/avenito/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson04/4/avenito/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson04/4/avenito/src/mm.c ================================================ #include "mm.h" static unsigned short mem_map [ PAGING_PAGES ] = {0,}; unsigned long get_free_page() { for (int i = 0; i < PAGING_PAGES; i++){ if (mem_map[i] == 0){ mem_map[i] = 1; return LOW_MEMORY + i*PAGE_SIZE; } } return 0; } void free_page(unsigned long p){ mem_map[(p - LOW_MEMORY) / PAGE_SIZE] = 0; } ================================================ FILE: exercises/lesson04/4/avenito/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson04/4/avenito/src/sched.S ================================================ #include "sched.h" .globl cpu_switch_to cpu_switch_to: mov x10, #THREAD_CPU_CONTEXT add x8, x0, x10 mov x9, sp stp x19, x20, [x8], #16 // store callee-saved registers stp x21, x22, [x8], #16 stp x23, x24, [x8], #16 stp x25, x26, [x8], #16 stp x27, x28, [x8], #16 stp x29, x9, [x8], #16 str x30, [x8] add x8, x1, x10 ldp x19, x20, [x8], #16 // restore callee-saved registers ldp x21, x22, [x8], #16 ldp x23, x24, [x8], #16 ldp x25, x26, [x8], #16 ldp x27, x28, [x8], #16 ldp x29, x9, [x8], #16 ldr x30, [x8] mov sp, x9 ret ================================================ FILE: exercises/lesson04/4/avenito/src/sched.c ================================================ #include "sched.h" #include "irq.h" #include "printf.h" static struct task_struct init_task = INIT_TASK; struct task_struct *current = &(init_task); // struct task_struct * task[NR_TASKS] = {&(init_task), }; // we don't need create the tasks now, instead struct task_struct *initial_task = &(init_task); // we create a initial task (reference task) // int nr_tasks = 1; // we don't need more void preempt_disable(void) { current->preempt_count++; } void preempt_enable(void) { current->preempt_count--; } void _schedule(void) { preempt_disable(); int c; struct task_struct * p, * next_task; while (1) { c = -1; //next = 0; // Point to the first task (initial_task) and loop until p != 0 for (p = initial_task; p; p = p->next_task){ //p = task[i]; we don't need more if (p && p->state == TASK_RUNNING && p->counter > c) { c = p->counter; next_task = p; // point to next task } } if (c) { break; } // Same here for (p = initial_task; p; p = p->next_task) { //p = task[i]; we don't need more if (p) { p->counter = (p->counter >> 1) + p->priority; } } } switch_to(next_task); // next_task is a pointer to a task preempt_enable(); } void schedule(void) { current->counter = 0; _schedule(); } void switch_to(struct task_struct * next) { if (current == next) return; struct task_struct * prev = current; current = next; cpu_switch_to(prev, next); } void schedule_tail(void) { preempt_enable(); } void timer_tick() { --current->counter; if (current->counter>0 || current->preempt_count >0) { return; } current->counter=0; enable_irq(); _schedule(); disable_irq(); } ================================================ FILE: exercises/lesson04/4/avenito/src/timer.c ================================================ #include "utils.h" #include "printf.h" #include "sched.h" #include "peripherals/timer.h" const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); timer_tick(); } ================================================ FILE: exercises/lesson04/4/avenito/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson04/4/bl4ckout31/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson04/4/bl4ckout31/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson04/4/bl4ckout31/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson04/4/bl4ckout31/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #endif ================================================ FILE: exercises/lesson04/4/bl4ckout31/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 272 // size of all saved registers #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #ifndef __ASSEMBLER__ void ret_from_fork(void); #endif #endif ================================================ FILE: exercises/lesson04/4/bl4ckout31/include/fork.h ================================================ #ifndef _FORK_H #define _FORK_H int copy_process(unsigned long fn, unsigned long arg); #endif ================================================ FILE: exercises/lesson04/4/bl4ckout31/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson04/4/bl4ckout31/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson04/4/bl4ckout31/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #include "peripherals/base.h" #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #define HIGH_MEMORY PBASE #define PAGING_MEMORY (HIGH_MEMORY - LOW_MEMORY) #define PAGING_PAGES (PAGING_MEMORY/PAGE_SIZE) #ifndef __ASSEMBLER__ unsigned long get_free_page(); void free_page(unsigned long p); void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson04/4/bl4ckout31/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson04/4/bl4ckout31/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson04/4/bl4ckout31/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson04/4/bl4ckout31/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson04/4/bl4ckout31/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson04/4/bl4ckout31/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson04/4/bl4ckout31/include/sched.h ================================================ #ifndef _SCHED_H #define _SCHED_H #define THREAD_CPU_CONTEXT 0 // offset of cpu_context in task_struct #ifndef __ASSEMBLER__ #define THREAD_SIZE 4096 #define NR_TASKS 64 #define FIRST_TASK task[0] #define LAST_TASK task[NR_TASKS-1] #define TASK_RUNNING 0 extern struct task_struct *current; extern struct task_struct *kernel_task; extern int nr_tasks; struct cpu_context { unsigned long x19; unsigned long x20; unsigned long x21; unsigned long x22; unsigned long x23; unsigned long x24; unsigned long x25; unsigned long x26; unsigned long x27; unsigned long x28; unsigned long fp; unsigned long sp; unsigned long pc; }; struct task_struct { struct cpu_context cpu_context; long state; long counter; long priority; long preempt_count; struct task_struct *next; }; extern void sched_init(void); extern void schedule(void); extern void timer_tick(void); extern void preempt_disable(void); extern void preempt_enable(void); extern void switch_to(struct task_struct* next); extern void cpu_switch_to(struct task_struct* prev, struct task_struct* next); #define INIT_TASK \ /*cpu_context*/ { {0,0,0,0,0,0,0,0,0,0,0,0,0}, \ /* state etc */ 0,0,1, 0, \ /* next */ 0 \ } #endif #endif ================================================ FILE: exercises/lesson04/4/bl4ckout31/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson04/4/bl4ckout31/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson04/4/bl4ckout31/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #(2 * SECTION_SIZE) bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson04/4/bl4ckout31/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson04/4/bl4ckout31/src/entry.S ================================================ #include "entry.h" .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] mrs x22, elr_el1 mrs x23, spsr_el1 stp x30, x22, [sp, #16 * 15] str x23, [sp, #16 * 16] .endm .macro kernel_exit ldr x23, [sp, #16 * 16] ldp x30, x22, [sp, #16 * 15] msr elr_el1, x22 msr spsr_el1, x23 ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 ventry irq_invalid_el0_64 // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry ERROR_INVALID_EL1h sync_invalid_el0_64: handle_invalid_entry SYNC_INVALID_EL0_64 irq_invalid_el0_64: handle_invalid_entry IRQ_INVALID_EL0_64 fiq_invalid_el0_64: handle_invalid_entry FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry ERROR_INVALID_EL0_32 el1_irq: kernel_entry bl handle_irq kernel_exit .globl ret_from_fork ret_from_fork: bl schedule_tail mov x0, x20 blr x19 //should never return .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson04/4/bl4ckout31/src/fork.c ================================================ #include "mm.h" #include "sched.h" #include "entry.h" int copy_process(unsigned long fn, unsigned long arg) { preempt_disable(); struct task_struct *p, *prev; p = (struct task_struct *) get_free_page(); if (!p) return 1; p->priority = kernel_task->priority; p->state = TASK_RUNNING; p->counter = p->priority; p->preempt_count = 1; //disable preemtion until schedule_tail p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)p + THREAD_SIZE; p->next = 0; prev = kernel_task; while(prev->next) prev = prev->next; prev->next = p; preempt_enable(); return 0; } ================================================ FILE: exercises/lesson04/4/bl4ckout31/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson04/4/bl4ckout31/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "timer.h" #include "entry.h" #include "peripherals/irq.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32" }; void enable_interrupt_controller() { put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { unsigned int irq = get32(IRQ_PENDING_1); switch (irq) { case (SYSTEM_TIMER_IRQ_1): handle_timer_irq(); break; default: printf("Inknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson04/4/bl4ckout31/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "timer.h" #include "irq.h" #include "fork.h" #include "sched.h" #include "mini_uart.h" void process(char *array) { while (1){ for (int i = 0; i < 5; i++){ uart_send(array[i]); delay(100000); } } } void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); int res = copy_process((unsigned long)&process, (unsigned long)"12345"); if (res != 0) { printf("error while starting process 1"); return; } res = copy_process((unsigned long)&process, (unsigned long)"abcde"); if (res != 0) { printf("error while starting process 2"); return; } while (1){ schedule(); } } ================================================ FILE: exercises/lesson04/4/bl4ckout31/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson04/4/bl4ckout31/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson04/4/bl4ckout31/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson04/4/bl4ckout31/src/mm.c ================================================ #include "mm.h" static unsigned short mem_map [ PAGING_PAGES ] = {0,}; unsigned long get_free_page() { for (int i = 0; i < PAGING_PAGES; i++){ if (mem_map[i] == 0){ mem_map[i] = 1; return LOW_MEMORY + i*PAGE_SIZE; } } return 0; } void free_page(unsigned long p){ mem_map[(p - LOW_MEMORY) / PAGE_SIZE] = 0; } ================================================ FILE: exercises/lesson04/4/bl4ckout31/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson04/4/bl4ckout31/src/sched.S ================================================ #include "sched.h" .globl cpu_switch_to cpu_switch_to: mov x10, #THREAD_CPU_CONTEXT add x8, x0, x10 mov x9, sp stp x19, x20, [x8], #16 // store callee-saved registers stp x21, x22, [x8], #16 stp x23, x24, [x8], #16 stp x25, x26, [x8], #16 stp x27, x28, [x8], #16 stp x29, x9, [x8], #16 str x30, [x8] add x8, x1, x10 ldp x19, x20, [x8], #16 // restore callee-saved registers ldp x21, x22, [x8], #16 ldp x23, x24, [x8], #16 ldp x25, x26, [x8], #16 ldp x27, x28, [x8], #16 ldp x29, x9, [x8], #16 ldr x30, [x8] mov sp, x9 ret ================================================ FILE: exercises/lesson04/4/bl4ckout31/src/sched.c ================================================ #include "sched.h" #include "irq.h" #include "printf.h" static struct task_struct init_task = INIT_TASK; struct task_struct *current = &(init_task); struct task_struct *kernel_task = &(init_task); int nr_tasks = 1; void preempt_disable(void) { current->preempt_count++; } void preempt_enable(void) { current->preempt_count--; } void _schedule(void) { preempt_disable(); int c; struct task_struct *p, *next; while (1) { c = -1; for (p = kernel_task; p; p = p->next){ if (p->state == TASK_RUNNING && p->counter > c) { c = p->counter; next = p; } } if (c) { break; } for (p = kernel_task; p; p = p->next) { p->counter = (p->counter >> 1) + p->priority; } } switch_to(next); preempt_enable(); } void schedule(void) { current->counter = 0; _schedule(); } void switch_to(struct task_struct * next) { if (current == next) return; struct task_struct * prev = current; current = next; cpu_switch_to(prev, next); } void schedule_tail(void) { preempt_enable(); } void timer_tick() { --current->counter; if (current->counter>0 || current->preempt_count >0) { return; } current->counter=0; enable_irq(); _schedule(); disable_irq(); } ================================================ FILE: exercises/lesson04/4/bl4ckout31/src/timer.c ================================================ #include "utils.h" #include "printf.h" #include "sched.h" #include "peripherals/timer.h" const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); timer_tick(); } ================================================ FILE: exercises/lesson04/4/bl4ckout31/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson04/4/bl4ckout31/start.sh ================================================ #!/bin/bash mount -L rpiboot /mnt cp "kernel8.img" /mnt umount /mnt ================================================ FILE: exercises/lesson04/4/rs/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel7.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel7.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel7.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel7.elf -O binary kernel7.img ================================================ FILE: exercises/lesson04/4/rs/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson04/4/rs/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson04/4/rs/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of // AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED \ (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | \ SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of // AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of // AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of // AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #endif ================================================ FILE: exercises/lesson04/4/rs/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 272 // size of all saved registers #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #ifndef __ASSEMBLER__ void ret_from_fork(void); #endif #endif ================================================ FILE: exercises/lesson04/4/rs/include/fork.h ================================================ #ifndef _FORK_H #define _FORK_H int copy_process(unsigned long fn, unsigned long arg, long pri); #endif ================================================ FILE: exercises/lesson04/4/rs/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller(void); void irq_vector_init(void); void enable_irq(void); void disable_irq(void); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson04/4/rs/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init(void); char uart_recv(void); void uart_send(char c); void putc(void *p, char c); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson04/4/rs/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #include "peripherals/base.h" #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #define HIGH_MEMORY PBASE #define PAGING_MEMORY (HIGH_MEMORY - LOW_MEMORY) #define PAGING_PAGES (PAGING_MEMORY / PAGE_SIZE) #ifndef __ASSEMBLER__ unsigned long get_free_page(); void free_page(unsigned long p); void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson04/4/rs/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson04/4/rs/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE + 0x00200004) #define GPSET0 (PBASE + 0x0020001C) #define GPCLR0 (PBASE + 0x00200028) #define GPPUD (PBASE + 0x00200094) #define GPPUDCLK0 (PBASE + 0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson04/4/rs/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE + 0x0000B200) #define IRQ_PENDING_1 (PBASE + 0x0000B204) #define IRQ_PENDING_2 (PBASE + 0x0000B208) #define FIQ_CONTROL (PBASE + 0x0000B20C) #define ENABLE_IRQS_1 (PBASE + 0x0000B210) #define ENABLE_IRQS_2 (PBASE + 0x0000B214) #define ENABLE_BASIC_IRQS (PBASE + 0x0000B218) #define DISABLE_IRQS_1 (PBASE + 0x0000B21C) #define DISABLE_IRQS_2 (PBASE + 0x0000B220) #define DISABLE_BASIC_IRQS (PBASE + 0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson04/4/rs/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE + 0x00215004) #define AUX_MU_IO_REG (PBASE + 0x00215040) #define AUX_MU_IER_REG (PBASE + 0x00215044) #define AUX_MU_IIR_REG (PBASE + 0x00215048) #define AUX_MU_LCR_REG (PBASE + 0x0021504C) #define AUX_MU_MCR_REG (PBASE + 0x00215050) #define AUX_MU_LSR_REG (PBASE + 0x00215054) #define AUX_MU_MSR_REG (PBASE + 0x00215058) #define AUX_MU_SCRATCH (PBASE + 0x0021505C) #define AUX_MU_CNTL_REG (PBASE + 0x00215060) #define AUX_MU_STAT_REG (PBASE + 0x00215064) #define AUX_MU_BAUD_REG (PBASE + 0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson04/4/rs/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE + 0x00003000) #define TIMER_CLO (PBASE + 0x00003004) #define TIMER_CHI (PBASE + 0x00003008) #define TIMER_C0 (PBASE + 0x0000300C) #define TIMER_C1 (PBASE + 0x00003010) #define TIMER_C2 (PBASE + 0x00003014) #define TIMER_C3 (PBASE + 0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson04/4/rs/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void *putp, void (*putf)(void *, char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char *s, char *fmt, ...); void tfp_format(void *putp, void (*putf)(void *, char), char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson04/4/rs/include/sched.h ================================================ #ifndef _SCHED_H #define _SCHED_H #define THREAD_CPU_CONTEXT 0 // offset of cpu_context in task_struct #ifndef __ASSEMBLER__ #define THREAD_SIZE 4096 #define TASK_RUNNING 0 extern struct task_struct *current; extern struct task_struct *init; struct cpu_context { unsigned long x19; unsigned long x20; unsigned long x21; unsigned long x22; unsigned long x23; unsigned long x24; unsigned long x25; unsigned long x26; unsigned long x27; unsigned long x28; unsigned long fp; unsigned long sp; unsigned long pc; }; struct task_struct { struct cpu_context cpu_context; long state; long counter; long priority; long preempt_count; struct task_struct *next; }; extern void sched_init(void); extern void schedule(void); extern void timer_tick(void); extern void preempt_disable(void); extern void preempt_enable(void); extern void switch_to(struct task_struct *next); extern void cpu_switch_to(struct task_struct *prev, struct task_struct *next); #define INIT_TASK \ /*cpu_context*/ { \ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, /* state etc */ 0, 0, 1, 0, 0 \ } #endif #endif ================================================ FILE: exercises/lesson04/4/rs/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init(void); void handle_timer_irq(void); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson04/4/rs/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay(unsigned long); extern void put32(unsigned long, unsigned int); extern unsigned int get32(unsigned long); extern int get_el(void); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson04/4/rs/output.txt ================================================ copy_process: pid=1, sp=401000 copy_process: pid=2, sp=402000 schedule switch_to 1 tasks: [0] sp=0 [1] sp=401000 [2] sp=402000 schedule switch_to 2 tasks: [0] sp=3fff70 [1] sp=401000 [2] sp=402000 schedule switch_to 0 tasks: [0] sp=3fff70 [1] sp=400e20 [2] sp=402000 schedule switch_to 1 tasks: [0] sp=3fff70 [1] sp=400e20 [2] sp=401e20 schedule switch_to 2 tasks: [0] sp=3fff70 [1] sp=400e20 [2] sp=401e20 schedule switch_to 0 tasks: [0] sp=3fff70 [1] sp=400e20 [2] sp=401e20 schedule switch_to 1 tasks: [0] sp=3fff70 [1] sp=400e20 [2] sp=401e20 schedule switch_to 2 tasks: [0] sp=3fff70 [1] sp=400e20 [2] sp=401e20 ================================================ FILE: exercises/lesson04/4/rs/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #(2 * SECTION_SIZE) bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson04/4/rs/src/config.txt ================================================ arm_control=0x200 kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson04/4/rs/src/entry.S ================================================ #include "entry.h" .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] mrs x22, elr_el1 mrs x23, spsr_el1 stp x30, x22, [sp, #16 * 15] str x23, [sp, #16 * 16] .endm .macro kernel_exit ldr x23, [sp, #16 * 16] ldp x30, x22, [sp, #16 * 15] msr elr_el1, x22 msr spsr_el1, x23 ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 ventry irq_invalid_el0_64 // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry ERROR_INVALID_EL1h sync_invalid_el0_64: handle_invalid_entry SYNC_INVALID_EL0_64 irq_invalid_el0_64: handle_invalid_entry IRQ_INVALID_EL0_64 fiq_invalid_el0_64: handle_invalid_entry FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry ERROR_INVALID_EL0_32 el1_irq: kernel_entry bl handle_irq kernel_exit .globl ret_from_fork ret_from_fork: bl schedule_tail mov x0, x20 blr x19 //should never return .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson04/4/rs/src/fork.c ================================================ #include "entry.h" #include "mm.h" #include "sched.h" int copy_process(unsigned long fn, unsigned long arg, long pri) { preempt_disable(); struct task_struct *p; p = (struct task_struct *)get_free_page(); if (!p) return 1; p->priority = pri; p->state = TASK_RUNNING; p->counter = p->priority; p->preempt_count = 1; // disable preemtion untill schedule_tail p->next = 0; p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)p + THREAD_SIZE; struct task_struct *last = init; while (last->next) last = last->next; last->next = p; preempt_enable(); return 0; } ================================================ FILE: exercises/lesson04/4/rs/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson04/4/rs/src/irq.c ================================================ #include "peripherals/irq.h" #include "entry.h" #include "printf.h" #include "timer.h" #include "utils.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32"}; void enable_interrupt_controller() { put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { unsigned int irq = get32(IRQ_PENDING_1); switch (irq) { case (SYSTEM_TIMER_IRQ_1): handle_timer_irq(); break; default: printf("Inknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson04/4/rs/src/kernel.c ================================================ #include "fork.h" #include "irq.h" #include "mini_uart.h" #include "printf.h" #include "sched.h" #include "timer.h" #include "utils.h" void process(char *array) { while (1) { for (int i = 0; i < 5; i++) { uart_send(array[i]); delay(100000); } } } void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); printf("cp 1\r\n"); int res = copy_process((unsigned long)&process, (unsigned long)"12345", 1); if (res != 0) { printf("error while starting process 1"); return; } printf("cp 2\r\n"); res = copy_process((unsigned long)&process, (unsigned long)"abcde", 2); if (res != 0) { printf("error while starting process 2"); return; } printf("sched\r\n"); while (1) { schedule(); } } ================================================ FILE: exercises/lesson04/4/rs/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson04/4/rs/src/mini_uart.c ================================================ #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" #include "utils.h" void uart_send(char c) { while (1) { if (get32(AUX_MU_LSR_REG) & 0x20) break; } put32(AUX_MU_IO_REG, c); } char uart_recv(void) { while (1) { if (get32(AUX_MU_LSR_REG) & 0x01) break; } return (get32(AUX_MU_IO_REG) & 0xFF); } void uart_send_string(char *str) { for (int i = 0; str[i] != '\0'; i++) { uart_send((char)str[i]); } } void uart_init(void) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7 << 12); // clean gpio14 selector |= 2 << 12; // set alt5 for gpio14 selector &= ~(7 << 15); // clean gpio15 selector |= 2 << 15; // set alt5 for gpio15 put32(GPFSEL1, selector); put32(GPPUD, 0); delay(150); put32(GPPUDCLK0, (1 << 14) | (1 << 15)); delay(150); put32(GPPUDCLK0, 0); put32(AUX_ENABLES, 1); // Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG, 0); // Disable auto flow control and disable receiver // and transmitter (for now) put32(AUX_MU_IER_REG, 0); // Disable receive and transmit interrupts put32(AUX_MU_LCR_REG, 3); // Enable 8 bit mode put32(AUX_MU_MCR_REG, 0); // Set RTS line to be always high put32(AUX_MU_BAUD_REG, 270); // Set baud rate to 115200 put32(AUX_MU_CNTL_REG, 3); // Finally, enable transmitter and receiver } // This function is required by printf function void putc(void *p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson04/4/rs/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson04/4/rs/src/mm.c ================================================ #include "mm.h" static unsigned short mem_map[PAGING_PAGES] = { 0, }; unsigned long get_free_page() { for (int i = 0; i < PAGING_PAGES; i++) { if (mem_map[i] == 0) { mem_map[i] = 1; return LOW_MEMORY + i * PAGE_SIZE; } } return 0; } void free_page(unsigned long p) { mem_map[(p - LOW_MEMORY) / PAGE_SIZE] = 0; } ================================================ FILE: exercises/lesson04/4/rs/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf)(void *, char); static putcf stdout_putf; static void *stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc, char *bf) { int n = 0; unsigned int d = 1; while (num / d >= base) d *= base; while (d != 0) { int dgt = num / d; num %= d; d /= base; if (n || dgt > 0 || d == 0) { *bf++ = dgt + (dgt < 10 ? '0' : (uc ? 'A' : 'a') - 10); ++n; } } *bf = 0; } static void li2a(long num, char *bf) { if (num < 0) { num = -num; *bf++ = '-'; } uli2a(num, 10, 0, bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc, char *bf) { int n = 0; unsigned int d = 1; while (num / d >= base) d *= base; while (d != 0) { int dgt = num / d; num %= d; d /= base; if (n || dgt > 0 || d == 0) { *bf++ = dgt + (dgt < 10 ? '0' : (uc ? 'A' : 'a') - 10); ++n; } } *bf = 0; } static void i2a(int num, char *bf) { if (num < 0) { num = -num; *bf++ = '-'; } ui2a(num, 10, 0, bf); } static int a2d(char ch) { if (ch >= '0' && ch <= '9') return ch - '0'; else if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10; else if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10; else return -1; } static char a2i(char ch, char **src, int base, int *nump) { char *p = *src; int num = 0; int digit; while ((digit = a2d(ch)) >= 0) { if (digit > base) break; num = num * base + digit; ch = *p++; } *src = p; *nump = num; return ch; } static void putchw(void *putp, putcf putf, int n, char z, char *bf) { char fc = z ? '0' : ' '; char ch; char *p = bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp, fc); while ((ch = *bf++)) putf(putp, ch); } void tfp_format(void *putp, putcf putf, char *fmt, va_list va) { char bf[12]; char ch; while ((ch = *(fmt++))) { if (ch != '%') putf(putp, ch); else { char lz = 0; #ifdef PRINTF_LONG_SUPPORT char lng = 0; #endif int w = 0; ch = *(fmt++); if (ch == '0') { ch = *(fmt++); lz = 1; } if (ch >= '0' && ch <= '9') { ch = a2i(ch, &fmt, 10, &w); } #ifdef PRINTF_LONG_SUPPORT if (ch == 'l') { ch = *(fmt++); lng = 1; } #endif switch (ch) { case 0: goto abort; case 'u': { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int), 10, 0, bf); else #endif ui2a(va_arg(va, unsigned int), 10, 0, bf); putchw(putp, putf, w, lz, bf); break; } case 'd': { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int), bf); else #endif i2a(va_arg(va, int), bf); putchw(putp, putf, w, lz, bf); break; } case 'x': case 'X': #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int), 16, (ch == 'X'), bf); else #endif ui2a(va_arg(va, unsigned int), 16, (ch == 'X'), bf); putchw(putp, putf, w, lz, bf); break; case 'c': putf(putp, (char)(va_arg(va, int))); break; case 's': putchw(putp, putf, w, 0, va_arg(va, char *)); break; case '%': putf(putp, ch); default: break; } } } abort:; } void init_printf(void *putp, void (*putf)(void *, char)) { stdout_putf = putf; stdout_putp = putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va, fmt); tfp_format(stdout_putp, stdout_putf, fmt, va); va_end(va); } static void putcp(void *p, char c) { *(*((char **)p))++ = c; } void tfp_sprintf(char *s, char *fmt, ...) { va_list va; va_start(va, fmt); tfp_format(&s, putcp, fmt, va); putcp(&s, 0); va_end(va); } ================================================ FILE: exercises/lesson04/4/rs/src/sched.S ================================================ #include "sched.h" .globl cpu_switch_to cpu_switch_to: mov x10, #THREAD_CPU_CONTEXT add x8, x0, x10 mov x9, sp stp x19, x20, [x8], #16 // store callee-saved registers stp x21, x22, [x8], #16 stp x23, x24, [x8], #16 stp x25, x26, [x8], #16 stp x27, x28, [x8], #16 stp x29, x9, [x8], #16 str x30, [x8] add x8, x1, x10 ldp x19, x20, [x8], #16 // restore callee-saved registers ldp x21, x22, [x8], #16 ldp x23, x24, [x8], #16 ldp x25, x26, [x8], #16 ldp x27, x28, [x8], #16 ldp x29, x9, [x8], #16 ldr x30, [x8] mov sp, x9 ret ================================================ FILE: exercises/lesson04/4/rs/src/sched.c ================================================ #include "sched.h" #include "irq.h" struct task_struct init_task = INIT_TASK; struct task_struct *init = &(init_task); struct task_struct *current = &(init_task); void preempt_disable(void) { current->preempt_count++; } void preempt_enable(void) { current->preempt_count--; } void _schedule(void) { preempt_disable(); int c; struct task_struct *p, *next; while (1) { c = -1; for (p = init; p; p = p->next) { if (p && p->state == TASK_RUNNING && p->counter > c) { c = p->counter; next = p; } } if (c) { break; } for (p = init; p; p = p->next) { if (p) { p->counter = (p->counter >> 1) + p->priority; } } } switch_to(next); preempt_enable(); } void schedule(void) { current->counter = 0; _schedule(); } void switch_to(struct task_struct *next) { if (current == next) return; struct task_struct *prev = current; current = next; cpu_switch_to(prev, next); } void schedule_tail(void) { preempt_enable(); } void timer_tick() { --current->counter; if (current->counter > 0 || current->preempt_count > 0) { return; } current->counter = 0; enable_irq(); _schedule(); disable_irq(); } ================================================ FILE: exercises/lesson04/4/rs/src/timer.c ================================================ #include "peripherals/timer.h" #include "printf.h" #include "sched.h" #include "utils.h" const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init(void) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq(void) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); timer_tick(); } ================================================ FILE: exercises/lesson04/4/rs/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1, [x0] ret .globl get32 get32: ldr w0, [x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson04/5/avenito/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson04/5/avenito/README.md ================================================ # To use qemu 1. Change the 'boot.S'. Remember qemu starts at exception level 2 (EL2). 1. Change the 'mini uart' to 'uart'. 1. Change the 'timer' to 'generic timer' ================================================ FILE: exercises/lesson04/5/avenito/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson04/5/avenito/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson04/5/avenito/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #endif ================================================ FILE: exercises/lesson04/5/avenito/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 272 // size of all saved registers #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #ifndef __ASSEMBLER__ void ret_from_fork(void); #endif #endif ================================================ FILE: exercises/lesson04/5/avenito/include/fork.h ================================================ #ifndef _FORK_H #define _FORK_H int copy_process(unsigned long fn, unsigned long arg); #endif ================================================ FILE: exercises/lesson04/5/avenito/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson04/5/avenito/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #include "peripherals/base.h" #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #define HIGH_MEMORY PBASE #define PAGING_MEMORY (HIGH_MEMORY - LOW_MEMORY) #define PAGING_PAGES (PAGING_MEMORY/PAGE_SIZE) #ifndef __ASSEMBLER__ unsigned long get_free_page(); void free_page(unsigned long p); void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson04/5/avenito/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #define PERIPHERAL_BASE 0x40000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson04/5/avenito/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson04/5/avenito/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #define CORE0_INT_CTR (PERIPHERAL_BASE+0x40) #define CORE0_INT_SOURCE (PERIPHERAL_BASE+0x60) #define LOCAL_TIMER_INT (1 << 11) #define CNTPNSIRQ_Int (1 << 1) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson04/5/avenito/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) // Local timer #define TIMER_CTRL (PERIPHERAL_BASE+0x34) #define TIMER_FLAG (PERIPHERAL_BASE+0x38) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson04/5/avenito/include/peripherals/uart.h ================================================ #ifndef _P_UART_H #define _P_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00201000) #define UART_DR (PBASE+0x00201000) #define UART_FR (PBASE+0x00201018) #define UART_CR (PBASE+0x00201030) #define UART_IBRD (PBASE+0x00201024) #define UART_FBRD (PBASE+0x00201028) #define UARTLCR_LCRH (PBASE+0x0020102C) #endif /*_P_UART_H */ ================================================ FILE: exercises/lesson04/5/avenito/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson04/5/avenito/include/sched.h ================================================ #ifndef _SCHED_H #define _SCHED_H #define THREAD_CPU_CONTEXT 0 // offset of cpu_context in task_struct #ifndef __ASSEMBLER__ #define THREAD_SIZE 4096 #define NR_TASKS 3 // changed to 4 to be easier #define FIRST_TASK task[0] #define LAST_TASK task[NR_TASKS-1] #define TASK_RUNNING 0 extern struct task_struct *current; extern struct task_struct * task[NR_TASKS]; extern int nr_tasks; struct cpu_context { unsigned long x19; unsigned long x20; unsigned long x21; unsigned long x22; unsigned long x23; unsigned long x24; unsigned long x25; unsigned long x26; unsigned long x27; unsigned long x28; unsigned long fp; unsigned long sp; unsigned long pc; }; struct task_struct { struct cpu_context cpu_context; long state; long counter; long priority; long preempt_count; }; extern void sched_init(void); extern void schedule(void); extern void timer_tick(void); extern void preempt_disable(void); extern void preempt_enable(void); extern void switch_to(struct task_struct* next); extern void cpu_switch_to(struct task_struct* prev, struct task_struct* next); #define INIT_TASK \ /*cpu_context*/ { {0,0,0,0,0,0,0,0,0,0,0,0,0}, \ /* state etc */ 0,0,1, 0 \ } #endif #endif ================================================ FILE: exercises/lesson04/5/avenito/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson04/5/avenito/include/uart.h ================================================ #ifndef _UART_H #define _UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); void putc ( void* p, char c ); #endif /*_UART_H */ ================================================ FILE: exercises/lesson04/5/avenito/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); extern void generic_timer_init ( void ); extern void generic_timer_reset ( void ); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson04/5/avenito/saida.txt ================================================ _shedule chamado NR_TASKS: 6 switch_to(task[0]) ; ================================================ FILE: exercises/lesson04/5/avenito/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 //ldr x0, =SCR_VALUE //msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el2, x0 adr x0, el1_entry msr elr_el2, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson04/5/avenito/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson04/5/avenito/src/entry.S ================================================ #include "entry.h" .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] mrs x22, elr_el1 mrs x23, spsr_el1 stp x30, x22, [sp, #16 * 15] str x23, [sp, #16 * 16] .endm .macro kernel_exit ldr x23, [sp, #16 * 16] ldp x30, x22, [sp, #16 * 15] msr elr_el1, x22 msr spsr_el1, x23 ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 ventry irq_invalid_el0_64 // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry ERROR_INVALID_EL1h sync_invalid_el0_64: handle_invalid_entry SYNC_INVALID_EL0_64 irq_invalid_el0_64: handle_invalid_entry IRQ_INVALID_EL0_64 fiq_invalid_el0_64: handle_invalid_entry FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry ERROR_INVALID_EL0_32 el1_irq: kernel_entry bl handle_irq kernel_exit .globl ret_from_fork ret_from_fork: bl schedule_tail mov x0, x20 blr x19 //should never return .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson04/5/avenito/src/fork.c ================================================ #include "printf.h" #include "mm.h" #include "sched.h" #include "entry.h" int copy_process(unsigned long fn, unsigned long arg) { preempt_disable(); struct task_struct *p; p = (struct task_struct *) get_free_page(); if (!p) return 1; //p->priority = current->priority; p->priority = 1; p->state = TASK_RUNNING; //p->counter = current->priority; p->counter = 1; p->preempt_count = 1; //disable preemtion until schedule_tail p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)p + THREAD_SIZE; int pid = nr_tasks++; task[pid] = p; printf("\n\r----------- Task[%d] created -----------\r\n", pid); printf("\n\rStruct task allocated at 0x%08x.\r\n", p); printf("p->cpu_context.x19 = 0x%08x. (fn)\r\n", p->cpu_context.x19); printf("p->cpu_context.x20 = 0x%08x. (arg)\r\n", p->cpu_context.x20); printf("p->cpu_context.pc = 0x%08x. (ret_from_fork)\r\n", p->cpu_context.pc); printf("p->cpu_context.sp = 0x%08x. (sp)\r\n", p->cpu_context.sp); printf("p->counter = %d.\r\n", p->counter); preempt_enable(); return 0; } ================================================ FILE: exercises/lesson04/5/avenito/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson04/5/avenito/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "timer.h" #include "entry.h" #include "peripherals/irq.h" #include "peripherals/timer.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32" }; void enable_interrupt_controller() { // Enable IRQ Core 0 - Pag. 13 BCM2836_ARM-local_peripherals put32(CORE0_INT_CTR, (1 << 1)); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { unsigned int irq = get32(CORE0_INT_SOURCE); switch (irq) { case (CNTPNSIRQ_Int): handle_timer_irq(); break; default: printf("Unknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson04/5/avenito/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "timer.h" #include "irq.h" #include "fork.h" #include "sched.h" #include "uart.h" void process(char *array) { while (1){ for (int i = 0; i < 5; i++){ uart_send(array[i]); delay(1000000); } } } void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); generic_timer_init(); enable_interrupt_controller(); enable_irq(); int res = copy_process((unsigned long)&process, (unsigned long)"12345"); if (res != 0) { printf("error while starting process 1"); return; } res = copy_process((unsigned long)&process, (unsigned long)"abcde"); if (res != 0) { printf("error while starting process 2"); return; } while (1){ printf("\n\rSched"); schedule(); } } ================================================ FILE: exercises/lesson04/5/avenito/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson04/5/avenito/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson04/5/avenito/src/mm.c ================================================ #include "mm.h" static unsigned short mem_map [ PAGING_PAGES ] = {0,}; unsigned long get_free_page() { for (int i = 0; i < PAGING_PAGES; i++){ if (mem_map[i] == 0){ mem_map[i] = 1; return LOW_MEMORY + i*PAGE_SIZE; } } return 0; } void free_page(unsigned long p){ mem_map[(p - LOW_MEMORY) / PAGE_SIZE] = 0; } ================================================ FILE: exercises/lesson04/5/avenito/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson04/5/avenito/src/sched.S ================================================ #include "sched.h" .globl cpu_switch_to cpu_switch_to: mov x10, #THREAD_CPU_CONTEXT add x8, x0, x10 mov x9, sp stp x19, x20, [x8], #16 // store callee-saved registers stp x21, x22, [x8], #16 stp x23, x24, [x8], #16 stp x25, x26, [x8], #16 stp x27, x28, [x8], #16 stp x29, x9, [x8], #16 str x30, [x8] add x8, x1, x10 ldp x19, x20, [x8], #16 // restore callee-saved registers ldp x21, x22, [x8], #16 ldp x23, x24, [x8], #16 ldp x25, x26, [x8], #16 ldp x27, x28, [x8], #16 ldp x29, x9, [x8], #16 ldr x30, [x8] mov sp, x9 ret ================================================ FILE: exercises/lesson04/5/avenito/src/sched.c ================================================ #include "sched.h" #include "irq.h" #include "printf.h" static struct task_struct init_task = INIT_TASK; struct task_struct *current = &(init_task); struct task_struct * task[NR_TASKS] = {&(init_task), }; int nr_tasks = 1; void preempt_disable(void) { current->preempt_count++; } void preempt_enable(void) { current->preempt_count--; } void _schedule(void) { preempt_disable(); int next,c; struct task_struct * p; while (1) { c = -1; next = 0; for (int i = 0; i < NR_TASKS; i++){ p = task[i]; if (p && p->state == TASK_RUNNING && p->counter > c) { c = p->counter; next = i; } } if (c) { break; } for (int i = 0; i < NR_TASKS; i++) { p = task[i]; if (p) { p->counter = (p->counter >> 1) + p->priority; } } } switch_to(task[next]); preempt_enable(); } void schedule(void) { current->counter = 0; _schedule(); } void switch_to(struct task_struct * next) { struct task_struct * p; printf("\n\r\n\r----------- Task switch -----------\r\n"); for(int t=0; t < NR_TASKS; t++) { p = task[t]; printf("\n\rtask[%d] counter = %d\n\r", t, p->counter); printf("task[%d] priority = %d\n\r", t, p->priority); printf("task[%d] preempt_count = %d\n\r", t, p->preempt_count); printf("task[%d] sp = 0x%08x\n\r", t, p->cpu_context.sp); printf("\n\r------------------------------\r\n"); } printf("\n\rtask output: "); if (current == next) return; struct task_struct * prev = current; current = next; cpu_switch_to(prev, next); } void schedule_tail(void) { preempt_enable(); } void timer_tick() { --current->counter; if (current->counter>0 || current->preempt_count >0) { return; } current->counter=0; enable_irq(); _schedule(); disable_irq(); } ================================================ FILE: exercises/lesson04/5/avenito/src/timer.c ================================================ #include "utils.h" #include "printf.h" #include "sched.h" #include "peripherals/timer.h" const unsigned int interval = 20000000; unsigned int curVal = 0; void timer_init ( void ) { // Set value, enable Timer and Interrupt put32(TIMER_CTRL, ((1<<28) | interval)); } void handle_timer_irq( void ) { generic_timer_reset(); timer_tick(); } ================================================ FILE: exercises/lesson04/5/avenito/src/uart.c ================================================ #include "utils.h" #include "peripherals/uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(get32(UART_FR)&0x20) {} // wait if TX is full put32(UART_DR,c); // when TX is empty, send next char } char uart_recv ( void ) { while(get32(UART_FR)&0x10) {} // wait if RX is empty return(get32(UART_DR)&0xFF); // get recived char } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 4<<12; // set alt0 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 4<<15; // set alt0 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(UART_CR,0); // disable RX and TX to configure put32(UART_IBRD,26); //PrimeCell UART (PL011) rev.r1p5 pag.3-9 BAUDDIV = (FUARTCLK/(16 Baud rate)) = 48MHz/(16*115200) = 26.041666 put32(UART_FBRD,3); put32(UARTLCR_LCRH,0x60); //Enable 8 bit mode put32(UART_CR,0x301); // enable UART, RX and TX } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson04/5/avenito/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret /* Looking at See AArch64-referenc-manual p.5667 */ .globl generic_timer_init generic_timer_init: mov x0, #1 msr CNTP_CTL_EL0, x0 lsl x0, x0, #23 msr CNTP_TVAL_EL0, x0 ret .globl generic_timer_reset generic_timer_reset: mov x0, #1 lsl x0, x0, #23 msr CNTP_TVAL_EL0, x0 ret ================================================ FILE: exercises/lesson04/5/bl4ckout31/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson04/5/bl4ckout31/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson04/5/bl4ckout31/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson04/5/bl4ckout31/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #endif ================================================ FILE: exercises/lesson04/5/bl4ckout31/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 272 // size of all saved registers #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #ifndef __ASSEMBLER__ void ret_from_fork(void); #endif #endif ================================================ FILE: exercises/lesson04/5/bl4ckout31/include/fork.h ================================================ #ifndef _FORK_H #define _FORK_H int copy_process(unsigned long fn, unsigned long arg); #endif ================================================ FILE: exercises/lesson04/5/bl4ckout31/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson04/5/bl4ckout31/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson04/5/bl4ckout31/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #include "peripherals/base.h" #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #define HIGH_MEMORY PBASE #define PAGING_MEMORY (HIGH_MEMORY - LOW_MEMORY) #define PAGING_PAGES (PAGING_MEMORY/PAGE_SIZE) #ifndef __ASSEMBLER__ unsigned long get_free_page(); void free_page(unsigned long p); void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson04/5/bl4ckout31/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #define LPBASE 0x40000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson04/5/bl4ckout31/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson04/5/bl4ckout31/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) // See BCM2836 ARM-local peripherals at // https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2836/QA7_rev3.4.pdf #define TIMER_INT_CTRL_0 (0x40000040) #define INT_SOURCE_0 (LPBASE+0x60) #define TIMER_INT_CTRL_0_VALUE (1 << 1) #define GENERIC_TIMER_INTERRUPT (1 << 1) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson04/5/bl4ckout31/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson04/5/bl4ckout31/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson04/5/bl4ckout31/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson04/5/bl4ckout31/include/sched.h ================================================ #ifndef _SCHED_H #define _SCHED_H #define THREAD_CPU_CONTEXT 0 // offset of cpu_context in task_struct #ifndef __ASSEMBLER__ #define THREAD_SIZE 4096 #define NR_TASKS 64 #define FIRST_TASK task[0] #define LAST_TASK task[NR_TASKS-1] #define TASK_RUNNING 0 extern struct task_struct *current; extern struct task_struct * task[NR_TASKS]; extern int nr_tasks; struct cpu_context { unsigned long x19; unsigned long x20; unsigned long x21; unsigned long x22; unsigned long x23; unsigned long x24; unsigned long x25; unsigned long x26; unsigned long x27; unsigned long x28; unsigned long fp; unsigned long sp; unsigned long pc; }; struct task_struct { struct cpu_context cpu_context; long state; long counter; long priority; long preempt_count; }; extern void sched_init(void); extern void schedule(void); extern void timer_tick(void); extern void preempt_disable(void); extern void preempt_enable(void); extern void switch_to(struct task_struct* next); extern void cpu_switch_to(struct task_struct* prev, struct task_struct* next); #define INIT_TASK \ /*cpu_context*/ { {0,0,0,0,0,0,0,0,0,0,0,0,0}, \ /* state etc */ 0,0,1, 0 \ } #endif #endif ================================================ FILE: exercises/lesson04/5/bl4ckout31/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); void generic_timer_init ( void ); void handle_generic_timer_irq ( void ); extern void gen_timer_init(); extern void gen_timer_reset(); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson04/5/bl4ckout31/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson04/5/bl4ckout31/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SPSR_VALUE msr spsr_el2, x0 adr x0, el1_entry msr elr_el2, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson04/5/bl4ckout31/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson04/5/bl4ckout31/src/entry.S ================================================ #include "entry.h" .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] mrs x22, elr_el1 mrs x23, spsr_el1 stp x30, x22, [sp, #16 * 15] str x23, [sp, #16 * 16] .endm .macro kernel_exit ldr x23, [sp, #16 * 16] ldp x30, x22, [sp, #16 * 15] msr elr_el1, x22 msr spsr_el1, x23 ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 ventry irq_invalid_el0_64 // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry ERROR_INVALID_EL1h sync_invalid_el0_64: handle_invalid_entry SYNC_INVALID_EL0_64 irq_invalid_el0_64: handle_invalid_entry IRQ_INVALID_EL0_64 fiq_invalid_el0_64: handle_invalid_entry FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry ERROR_INVALID_EL0_32 el1_irq: kernel_entry bl handle_irq kernel_exit .globl ret_from_fork ret_from_fork: bl schedule_tail mov x0, x20 blr x19 //should never return .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson04/5/bl4ckout31/src/fork.c ================================================ #include "mm.h" #include "sched.h" #include "entry.h" int copy_process(unsigned long fn, unsigned long arg) { preempt_disable(); struct task_struct *p; p = (struct task_struct *) get_free_page(); if (!p) return 1; p->priority = current->priority; p->state = TASK_RUNNING; p->counter = p->priority; p->preempt_count = 1; //disable preemtion until schedule_tail p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)p + THREAD_SIZE; int pid = nr_tasks++; task[pid] = p; preempt_enable(); return 0; } ================================================ FILE: exercises/lesson04/5/bl4ckout31/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson04/5/bl4ckout31/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "timer.h" #include "entry.h" #include "peripherals/irq.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32" }; void enable_interrupt_controller() { // Enables Core 0 Timers interrupt control for the generic timer put32(TIMER_INT_CTRL_0, TIMER_INT_CTRL_0_VALUE); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { // Each Core has its own pending local intrrupts register unsigned int irq = get32(INT_SOURCE_0); switch (irq) { case (GENERIC_TIMER_INTERRUPT): handle_generic_timer_irq(); break; default: printf("Unknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson04/5/bl4ckout31/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "timer.h" #include "irq.h" #include "fork.h" #include "sched.h" #include "mini_uart.h" void process(char *array) { while (1){ for (int i = 0; i < 5; i++){ uart_send(array[i]); delay(5000000); } } } void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); generic_timer_init(); enable_interrupt_controller(); enable_irq(); int res = copy_process((unsigned long)&process, (unsigned long)"12345"); if (res != 0) { printf("error while starting process 1"); return; } res = copy_process((unsigned long)&process, (unsigned long)"abcde"); if (res != 0) { printf("error while starting process 2"); return; } while (1){ schedule(); } } ================================================ FILE: exercises/lesson04/5/bl4ckout31/src/linker.ld ================================================ SECTIONS { . = 0x80000; .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson04/5/bl4ckout31/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson04/5/bl4ckout31/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson04/5/bl4ckout31/src/mm.c ================================================ #include "mm.h" static unsigned short mem_map [ PAGING_PAGES ] = {0,}; unsigned long get_free_page() { for (int i = 0; i < PAGING_PAGES; i++){ if (mem_map[i] == 0){ mem_map[i] = 1; return LOW_MEMORY + i*PAGE_SIZE; } } return 0; } void free_page(unsigned long p){ mem_map[(p - LOW_MEMORY) / PAGE_SIZE] = 0; } ================================================ FILE: exercises/lesson04/5/bl4ckout31/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson04/5/bl4ckout31/src/sched.S ================================================ #include "sched.h" .globl cpu_switch_to cpu_switch_to: mov x10, #THREAD_CPU_CONTEXT add x8, x0, x10 mov x9, sp stp x19, x20, [x8], #16 // store callee-saved registers stp x21, x22, [x8], #16 stp x23, x24, [x8], #16 stp x25, x26, [x8], #16 stp x27, x28, [x8], #16 stp x29, x9, [x8], #16 str x30, [x8] add x8, x1, x10 ldp x19, x20, [x8], #16 // restore callee-saved registers ldp x21, x22, [x8], #16 ldp x23, x24, [x8], #16 ldp x25, x26, [x8], #16 ldp x27, x28, [x8], #16 ldp x29, x9, [x8], #16 ldr x30, [x8] mov sp, x9 ret ================================================ FILE: exercises/lesson04/5/bl4ckout31/src/sched.c ================================================ #include "sched.h" #include "irq.h" #include "printf.h" static struct task_struct init_task = INIT_TASK; struct task_struct *current = &(init_task); struct task_struct * task[NR_TASKS] = {&(init_task), }; int nr_tasks = 1; void preempt_disable(void) { current->preempt_count++; } void preempt_enable(void) { current->preempt_count--; } void _schedule(void) { preempt_disable(); int next,c; struct task_struct * p; while (1) { c = -1; next = 0; for (int i = 0; i < NR_TASKS; i++){ p = task[i]; if (p && p->state == TASK_RUNNING && p->counter > c) { c = p->counter; next = i; } } if (c) { break; } for (int i = 0; i < NR_TASKS; i++) { p = task[i]; if (p) { p->counter = (p->counter >> 1) + p->priority; } } } switch_to(task[next]); preempt_enable(); } void schedule(void) { current->counter = 0; _schedule(); } void switch_to(struct task_struct * next) { if (current == next) return; struct task_struct * prev = current; current = next; cpu_switch_to(prev, next); } void schedule_tail(void) { preempt_enable(); } void timer_tick() { --current->counter; if (current->counter>0 || current->preempt_count >0) { return; } current->counter=0; enable_irq(); _schedule(); disable_irq(); } ================================================ FILE: exercises/lesson04/5/bl4ckout31/src/timer.S ================================================ /** Here, the physical timer at EL1 is used with the TimerValue views. * Once the count-down has reach 0, the interrupt line is HIGH until * a new timer value > 0 is write into the CNTP_TVAL_EL0 system register. * * See AArch64-referenc-manual p.2326 at * https://developer.arm.com/docs/ddi0487/ca/arm-architecture-reference-manual-armv8-for-armv8-a-architecture-profile */ .globl gen_timer_init gen_timer_init: mov x0, #1 msr CNTP_CTL_EL0, x0 ret .globl gen_timer_reset gen_timer_reset: mov x0, #1 lsl x0, x0, #24 msr CNTP_TVAL_EL0, x0 ret ================================================ FILE: exercises/lesson04/5/bl4ckout31/src/timer.c ================================================ #include "utils.h" #include "printf.h" #include "sched.h" #include "peripherals/timer.h" #include "timer.h" const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); timer_tick(); } void generic_timer_init ( void ) { gen_timer_init(); gen_timer_reset(); } void handle_generic_timer_irq( void ) { gen_timer_reset(); timer_tick(); } ================================================ FILE: exercises/lesson04/5/bl4ckout31/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson04/5/bl4ckout31/start.sh ================================================ #!/bin/bash echo "###" echo "### Use C-a h for help" echo "###" echo "" qemu-system-aarch64 -machine raspi3 -serial null -serial mon:stdio -nographic -kernel kernel8.img ================================================ FILE: exercises/lesson05/1/avenito/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson05/1/avenito/README.md ================================================ ================================================ FILE: exercises/lesson05/1/avenito/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson05/1/avenito/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson05/1/avenito/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) // *************************************** // ESR_EL1, Exception Syndrome Register (EL1). Page 2431 of AArch64-Reference-Manual. // *************************************** #define ESR_ELx_EC_SHIFT 26 #define ESR_ELx_EC_SVC64 0x15 #endif ================================================ FILE: exercises/lesson05/1/avenito/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 272 // size of all saved registers #define S_X0 0 // offset of x0 register in saved stack frame #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #define SYNC_ERROR 16 #define SYSCALL_ERROR 17 #ifndef __ASSEMBLER__ void ret_from_fork(void); #endif #endif ================================================ FILE: exercises/lesson05/1/avenito/include/fork.h ================================================ #ifndef _FORK_H #define _FORK_H #include "sched.h" /* * PSR bits */ #define PSR_MODE_EL0t 0x00000000 #define PSR_MODE_EL1t 0x00000004 #define PSR_MODE_EL1h 0x00000005 #define PSR_MODE_EL2t 0x00000008 #define PSR_MODE_EL2h 0x00000009 #define PSR_MODE_EL3t 0x0000000c #define PSR_MODE_EL3h 0x0000000d int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg, unsigned long stack); int move_to_user_mode(unsigned long pc); struct pt_regs * task_pt_regs(struct task_struct *tsk); struct pt_regs { unsigned long regs[31]; unsigned long sp; unsigned long pc; unsigned long pstate; }; #endif ================================================ FILE: exercises/lesson05/1/avenito/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson05/1/avenito/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson05/1/avenito/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #include "peripherals/base.h" #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #define HIGH_MEMORY PBASE #define PAGING_MEMORY (HIGH_MEMORY - LOW_MEMORY) #define PAGING_PAGES (PAGING_MEMORY/PAGE_SIZE) #ifndef __ASSEMBLER__ unsigned long get_free_page(); void free_page(unsigned long p); void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson05/1/avenito/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson05/1/avenito/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson05/1/avenito/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson05/1/avenito/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson05/1/avenito/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson05/1/avenito/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson05/1/avenito/include/sched.h ================================================ #ifndef _SCHED_H #define _SCHED_H #define THREAD_CPU_CONTEXT 0 // offset of cpu_context in task_struct #ifndef __ASSEMBLER__ #define THREAD_SIZE 4096 #define NR_TASKS 64 #define FIRST_TASK task[0] #define LAST_TASK task[NR_TASKS-1] #define TASK_RUNNING 0 #define TASK_ZOMBIE 1 #define PF_KTHREAD 0x00000002 extern struct task_struct *current; extern struct task_struct * task[NR_TASKS]; extern int nr_tasks; struct cpu_context { unsigned long x19; unsigned long x20; unsigned long x21; unsigned long x22; unsigned long x23; unsigned long x24; unsigned long x25; unsigned long x26; unsigned long x27; unsigned long x28; unsigned long fp; unsigned long sp; unsigned long pc; }; struct task_struct { struct cpu_context cpu_context; long state; long counter; long priority; long preempt_count; unsigned long stack; unsigned long flags; }; extern void sched_init(void); extern void schedule(void); extern void timer_tick(void); extern void preempt_disable(void); extern void preempt_enable(void); extern void switch_to(struct task_struct * next, int index); extern void cpu_switch_to(struct task_struct* prev, struct task_struct* next); extern void exit_process(void); #define INIT_TASK \ /*cpu_context*/ { {0,0,0,0,0,0,0,0,0,0,0,0,0}, \ /* state etc */ 0,0,1, 0, 0, PF_KTHREAD \ } #endif #endif ================================================ FILE: exercises/lesson05/1/avenito/include/sys.h ================================================ #ifndef _SYS_H #define _SYS_H #define __NR_syscalls 4 #define SYS_WRITE_NUMBER 0 // syscal numbers #define SYS_MALLOC_NUMBER 1 #define SYS_CLONE_NUMBER 2 #define SYS_EXIT_NUMBER 3 #ifndef __ASSEMBLER__ void sys_write(char * buf); int sys_fork(); void call_sys_write(char * buf); int call_sys_clone(unsigned long fn, unsigned long arg, unsigned long stack); unsigned long call_sys_malloc(); void call_sys_exit(); #endif #endif /*_SYS_H */ ================================================ FILE: exercises/lesson05/1/avenito/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson05/1/avenito/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson05/1/avenito/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson05/1/avenito/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson05/1/avenito/src/entry.S ================================================ #include "arm/sysregs.h" #include "entry.h" #include "sys.h" .macro handle_invalid_entry el, type kernel_entry \el mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry, el sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] .if \el == 0 mrs x21, sp_el0 .else add x21, sp, #S_FRAME_SIZE .endif /* \el == 0 */ mrs x22, elr_el1 mrs x23, spsr_el1 stp x30, x21, [sp, #16 * 15] stp x22, x23, [sp, #16 * 16] .endm .macro kernel_exit, el ldp x22, x23, [sp, #16 * 16] ldp x30, x21, [sp, #16 * 15] .if \el == 0 msr sp_el0, x21 .endif /* \el == 0 */ msr elr_el1, x22 msr spsr_el1, x23 ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry el0_sync // Synchronous 64-bit EL0 ventry el0_irq // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry 1, SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry 1, IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry 1, FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry 1, ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry 1, SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry 1, FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry 1, ERROR_INVALID_EL1h fiq_invalid_el0_64: handle_invalid_entry 0, FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry 0, ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry 0, SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry 0, IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry 0, FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry 0, ERROR_INVALID_EL0_32 el1_irq: kernel_entry 1 bl handle_irq kernel_exit 1 el0_irq: kernel_entry 0 bl handle_irq kernel_exit 0 el0_sync: kernel_entry 0 mrs x25, esr_el1 // read the syndrome register lsr x24, x25, #ESR_ELx_EC_SHIFT // exception class cmp x24, #ESR_ELx_EC_SVC64 // SVC in 64-bit state b.eq el0_svc handle_invalid_entry 0, SYNC_ERROR sc_nr .req x25 // number of system calls scno .req x26 // syscall number stbl .req x27 // syscall table pointer el0_svc: adr stbl, sys_call_table // load syscall table pointer uxtw scno, w8 // syscall number in w8 mov sc_nr, #__NR_syscalls bl enable_irq cmp scno, sc_nr // check upper syscall limit b.hs ni_sys ldr x16, [stbl, scno, lsl #3] // address in the syscall table blr x16 // call sys_* routine b ret_from_syscall ni_sys: handle_invalid_entry 0, SYSCALL_ERROR ret_from_syscall: bl disable_irq str x0, [sp, #S_X0] // returned x0 kernel_exit 0 .globl ret_from_fork ret_from_fork: bl schedule_tail cbz x19, ret_to_user // not a kernel thread mov x0, x20 blr x19 ret_to_user: bl disable_irq kernel_exit 0 .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson05/1/avenito/src/fork.c ================================================ #include "mm.h" #include "sched.h" #include "printf.h" #include "fork.h" #include "entry.h" #include "utils.h" int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg, unsigned long stack) { preempt_disable(); struct task_struct *p; p = (struct task_struct *) get_free_page(); if (!p) { return -1; } struct pt_regs *childregs = task_pt_regs(p); memzero((unsigned long)childregs, sizeof(struct pt_regs)); memzero((unsigned long)&p->cpu_context, sizeof(struct cpu_context)); if (clone_flags & PF_KTHREAD) { p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; } else { struct pt_regs * cur_regs = task_pt_regs(current); *cur_regs = *childregs; childregs->regs[0] = 0; childregs->sp = stack + PAGE_SIZE; p->stack = stack; } p->flags = clone_flags; p->priority = current->priority; p->state = TASK_RUNNING; p->counter = p->priority; p->preempt_count = 1; //disable preemtion until schedule_tail p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)childregs; int pid = nr_tasks++; task[pid] = p; preempt_enable(); return pid; } int move_to_user_mode(unsigned long pc) { struct pt_regs *regs = task_pt_regs(current); memzero((unsigned long)regs, sizeof(*regs)); regs->pc = pc; regs->pstate = PSR_MODE_EL0t; unsigned long stack = get_free_page(); //alocate new user stack if (!stack) { return -1; } regs->sp = stack + PAGE_SIZE; current->stack = stack; return 0; } struct pt_regs * task_pt_regs(struct task_struct *tsk){ unsigned long p = (unsigned long)tsk + THREAD_SIZE - sizeof(struct pt_regs); return (struct pt_regs *)p; } ================================================ FILE: exercises/lesson05/1/avenito/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson05/1/avenito/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "timer.h" #include "entry.h" #include "peripherals/irq.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32", "SYNC_ERROR", "SYSCALL_ERROR" }; void enable_interrupt_controller() { put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { unsigned int irq = get32(IRQ_PENDING_1); switch (irq) { case (SYSTEM_TIMER_IRQ_1): handle_timer_irq(); break; default: printf("Inknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson05/1/avenito/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "timer.h" #include "irq.h" #include "sched.h" #include "fork.h" #include "mini_uart.h" #include "sys.h" void user_process1(char *array) { char buf[2] = {0}; while (1){ for (int i = 0; i < 5; i++){ buf[0] = array[i]; call_sys_write(buf); delay(100000); } } } void user_process(){ char buf[30] = {0}; tfp_sprintf(buf, "User process started\n\r"); call_sys_write(buf); unsigned long stack = call_sys_malloc(); if (stack < 0) { printf("Error while allocating stack for process 1\n\r"); return; } int err = call_sys_clone((unsigned long)&user_process1, (unsigned long)"12345", stack); if (err < 0){ printf("Error while clonning process 1\n\r"); return; } stack = call_sys_malloc(); if (stack < 0) { printf("Error while allocating stack for process 1\n\r"); return; } err = call_sys_clone((unsigned long)&user_process1, (unsigned long)"abcd", stack); if (err < 0){ printf("Error while clonning process 2\n\r"); return; } call_sys_exit(); } void kernel_process(){ printf("Kernel process started. EL %d\r\n", get_el()); int err = move_to_user_mode((unsigned long)&user_process); if (err < 0){ printf("Error while moving process to user mode\n\r"); } } void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); int res = copy_process(PF_KTHREAD, (unsigned long)&kernel_process, 0, 0); if (res < 0) { printf("error while starting kernel process"); return; } while (1){ schedule(); } } ================================================ FILE: exercises/lesson05/1/avenito/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson05/1/avenito/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson05/1/avenito/src/mm.S ================================================ .globl memcpy memcpy: ldr x3, [x0], #8 str x3, [x1], #8 subs x2, x2, #8 b.gt memcpy ret .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson05/1/avenito/src/mm.c ================================================ #include "mm.h" static unsigned short mem_map [ PAGING_PAGES ] = {0,}; unsigned long get_free_page() { for (int i = 0; i < PAGING_PAGES; i++){ if (mem_map[i] == 0){ mem_map[i] = 1; return LOW_MEMORY + i*PAGE_SIZE; } } return 0; } void free_page(unsigned long p){ mem_map[(p - LOW_MEMORY) / PAGE_SIZE] = 0; } ================================================ FILE: exercises/lesson05/1/avenito/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson05/1/avenito/src/sched.S ================================================ #include "sched.h" .globl cpu_switch_to cpu_switch_to: mov x10, #THREAD_CPU_CONTEXT add x8, x0, x10 mov x9, sp stp x19, x20, [x8], #16 // store callee-saved registers stp x21, x22, [x8], #16 stp x23, x24, [x8], #16 stp x25, x26, [x8], #16 stp x27, x28, [x8], #16 stp x29, x9, [x8], #16 str x30, [x8] add x8, x1, x10 ldp x19, x20, [x8], #16 // restore callee-saved registers ldp x21, x22, [x8], #16 ldp x23, x24, [x8], #16 ldp x25, x26, [x8], #16 ldp x27, x28, [x8], #16 ldp x29, x9, [x8], #16 ldr x30, [x8] mov sp, x9 ret ================================================ FILE: exercises/lesson05/1/avenito/src/sched.c ================================================ #include "sched.h" #include "irq.h" #include "printf.h" #include "fork.h" #include "utils.h" #include "mm.h" static struct task_struct init_task = INIT_TASK; struct task_struct *current = &(init_task); struct task_struct * task[NR_TASKS] = {&(init_task), }; int nr_tasks = 1; void preempt_disable(void) { current->preempt_count++; } void preempt_enable(void) { current->preempt_count--; } void _schedule(void) { preempt_disable(); int next,c; struct task_struct * p; while (1) { c = -1; next = 0; for (int i = 0; i < NR_TASKS; i++){ p = task[i]; if (p && p->state == TASK_RUNNING && p->counter > c) { c = p->counter; next = i; } } if (c) { break; } for (int i = 0; i < NR_TASKS; i++) { p = task[i]; if (p) { p->counter = (p->counter >> 1) + p->priority; } } } switch_to(task[next], next); preempt_enable(); } void schedule(void) { current->counter = 0; _schedule(); } void switch_to(struct task_struct * next, int index) { if (current == next) return; struct task_struct * prev = current; current = next; cpu_switch_to(prev, next); } void schedule_tail(void) { preempt_enable(); } void timer_tick() { --current->counter; if (current->counter>0 || current->preempt_count >0) { return; } current->counter=0; enable_irq(); _schedule(); disable_irq(); } void exit_process(){ preempt_disable(); for (int i = 0; i < NR_TASKS; i++){ if (task[i] == current) { task[i]->state = TASK_ZOMBIE; break; } } if (current->stack) { free_page(current->stack); } preempt_enable(); schedule(); } ================================================ FILE: exercises/lesson05/1/avenito/src/sys.S ================================================ #include "sys.h" .globl call_sys_write call_sys_write: mov w8, #SYS_WRITE_NUMBER svc #0 ret .globl call_sys_malloc call_sys_malloc: mov w8, #SYS_MALLOC_NUMBER svc #0 ret .globl call_sys_exit call_sys_exit: mov w8, #SYS_EXIT_NUMBER svc #0 ret .globl call_sys_clone call_sys_clone: /* Save args for the child. */ mov x10, x0 /*fn*/ mov x11, x1 /*arg*/ mov x12, x2 /*stack*/ /* Do the system call. */ mov x0, x2 /* stack */ mov x8, #SYS_CLONE_NUMBER svc 0x0 cmp x0, #0 beq thread_start ret thread_start: mov x29, 0 /* Pick the function arg and execute. */ mov x0, x11 blr x10 /* We are done, pass the return value through x0. */ mov x8, #SYS_EXIT_NUMBER svc 0x0 ================================================ FILE: exercises/lesson05/1/avenito/src/sys.c ================================================ #include "fork.h" #include "printf.h" #include "utils.h" #include "sched.h" #include "mm.h" void sys_write(char * buf){ printf(buf); } int sys_clone(unsigned long stack){ return copy_process(0, 0, 0, stack); } unsigned long sys_malloc(){ unsigned long addr = get_free_page(); if (!addr) { return -1; } return addr; } void sys_exit(){ exit_process(); } void * const sys_call_table[] = {sys_write, sys_malloc, sys_clone, sys_exit}; ================================================ FILE: exercises/lesson05/1/avenito/src/timer.c ================================================ #include "utils.h" #include "printf.h" #include "sched.h" #include "peripherals/timer.h" const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); timer_tick(); } ================================================ FILE: exercises/lesson05/1/avenito/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson05/1/bl4ckout31/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson05/1/bl4ckout31/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson05/1/bl4ckout31/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson05/1/bl4ckout31/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) // *************************************** // ESR_EL1, Exception Syndrome Register (EL1). Page 2431 of AArch64-Reference-Manual. // *************************************** #define ESR_ELx_EC_SHIFT 26 #define ESR_ELx_EC_SVC64 0x15 #define ESR_ELx_EC_SYS_INSTR 0x18 #endif ================================================ FILE: exercises/lesson05/1/bl4ckout31/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 272 // size of all saved registers #define S_X0 0 // offset of x0 register in saved stack frame #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #define SYNC_ERROR 16 #define SYSCALL_ERROR 17 #ifndef __ASSEMBLER__ void ret_from_fork(void); #endif #endif ================================================ FILE: exercises/lesson05/1/bl4ckout31/include/fork.h ================================================ #ifndef _FORK_H #define _FORK_H #include "sched.h" /* * PSR bits */ #define PSR_MODE_EL0t 0x00000000 #define PSR_MODE_EL1t 0x00000004 #define PSR_MODE_EL1h 0x00000005 #define PSR_MODE_EL2t 0x00000008 #define PSR_MODE_EL2h 0x00000009 #define PSR_MODE_EL3t 0x0000000c #define PSR_MODE_EL3h 0x0000000d int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg, unsigned long stack); int move_to_user_mode(unsigned long pc); struct pt_regs * task_pt_regs(struct task_struct *tsk); struct pt_regs { unsigned long regs[31]; unsigned long sp; unsigned long pc; unsigned long pstate; }; #endif ================================================ FILE: exercises/lesson05/1/bl4ckout31/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson05/1/bl4ckout31/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson05/1/bl4ckout31/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #include "peripherals/base.h" #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #define HIGH_MEMORY PBASE #define PAGING_MEMORY (HIGH_MEMORY - LOW_MEMORY) #define PAGING_PAGES (PAGING_MEMORY/PAGE_SIZE) #ifndef __ASSEMBLER__ unsigned long get_free_page(); void free_page(unsigned long p); void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson05/1/bl4ckout31/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson05/1/bl4ckout31/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson05/1/bl4ckout31/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson05/1/bl4ckout31/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson05/1/bl4ckout31/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson05/1/bl4ckout31/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson05/1/bl4ckout31/include/sched.h ================================================ #ifndef _SCHED_H #define _SCHED_H #define THREAD_CPU_CONTEXT 0 // offset of cpu_context in task_struct #ifndef __ASSEMBLER__ #define THREAD_SIZE 4096 #define NR_TASKS 64 #define FIRST_TASK task[0] #define LAST_TASK task[NR_TASKS-1] #define TASK_RUNNING 0 #define TASK_ZOMBIE 1 #define PF_KTHREAD 0x00000002 extern struct task_struct *current; extern struct task_struct * task[NR_TASKS]; extern int nr_tasks; struct cpu_context { unsigned long x19; unsigned long x20; unsigned long x21; unsigned long x22; unsigned long x23; unsigned long x24; unsigned long x25; unsigned long x26; unsigned long x27; unsigned long x28; unsigned long fp; unsigned long sp; unsigned long pc; }; struct task_struct { struct cpu_context cpu_context; long state; long counter; long priority; long preempt_count; unsigned long stack; unsigned long flags; }; extern void sched_init(void); extern void schedule(void); extern void timer_tick(void); extern void preempt_disable(void); extern void preempt_enable(void); extern void switch_to(struct task_struct * next, int index); extern void cpu_switch_to(struct task_struct* prev, struct task_struct* next); extern void exit_process(void); #define INIT_TASK \ /*cpu_context*/ { {0,0,0,0,0,0,0,0,0,0,0,0,0}, \ /* state etc */ 0,0,1, 0, 0, PF_KTHREAD \ } #endif #endif ================================================ FILE: exercises/lesson05/1/bl4ckout31/include/sys.h ================================================ #ifndef _SYS_H #define _SYS_H #define __NR_syscalls 4 #define SYS_WRITE_NUMBER 0 // syscal numbers #define SYS_MALLOC_NUMBER 1 #define SYS_CLONE_NUMBER 2 #define SYS_EXIT_NUMBER 3 #ifndef __ASSEMBLER__ void sys_write(char * buf); int sys_fork(); void call_sys_write(char * buf); int call_sys_clone(unsigned long fn, unsigned long arg, unsigned long stack); unsigned long call_sys_malloc(); void call_sys_exit(); #endif #endif /*_SYS_H */ ================================================ FILE: exercises/lesson05/1/bl4ckout31/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson05/1/bl4ckout31/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); extern unsigned int get_daif(); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson05/1/bl4ckout31/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson05/1/bl4ckout31/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson05/1/bl4ckout31/src/entry.S ================================================ #include "arm/sysregs.h" #include "entry.h" #include "sys.h" .macro handle_invalid_entry el, type kernel_entry \el mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry, el sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] .if \el == 0 mrs x21, sp_el0 .else add x21, sp, #S_FRAME_SIZE .endif /* \el == 0 */ mrs x22, elr_el1 mrs x23, spsr_el1 stp x30, x21, [sp, #16 * 15] stp x22, x23, [sp, #16 * 16] .endm .macro kernel_exit, el ldp x22, x23, [sp, #16 * 16] ldp x30, x21, [sp, #16 * 15] .if \el == 0 msr sp_el0, x21 .endif /* \el == 0 */ msr elr_el1, x22 msr spsr_el1, x23 ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry el0_sync // Synchronous 64-bit EL0 ventry el0_irq // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry 1, SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry 1, IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry 1, FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry 1, ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry 1, SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry 1, FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry 1, ERROR_INVALID_EL1h fiq_invalid_el0_64: handle_invalid_entry 0, FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry 0, ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry 0, SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry 0, IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry 0, FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry 0, ERROR_INVALID_EL0_32 el1_irq: kernel_entry 1 bl handle_irq kernel_exit 1 el0_irq: kernel_entry 0 bl handle_irq kernel_exit 0 el0_sync: kernel_entry 0 mrs x25, esr_el1 // read the syndrome register lsr x24, x25, #ESR_ELx_EC_SHIFT // exception class cmp x24, #ESR_ELx_EC_SVC64 // SVC in 64-bit state b.eq el0_svc cmp x24, #ESR_ELx_EC_SYS_INSTR // trapped mrs DAIF b.eq el0_sys_instr handle_invalid_entry 0, SYNC_ERROR sc_nr .req x25 // number of system calls scno .req x26 // syscall number stbl .req x27 // syscall table pointer el0_svc: adr stbl, sys_call_table // load syscall table pointer uxtw scno, w8 // syscall number in w8 mov sc_nr, #__NR_syscalls bl enable_irq cmp scno, sc_nr // check upper syscall limit b.hs ni_sys ldr x16, [stbl, scno, lsl #3] // address in the syscall table blr x16 // call sys_* routine b ret_from_syscall ni_sys: handle_invalid_entry 0, SYSCALL_ERROR ret_from_syscall: bl disable_irq str x0, [sp, #S_X0] // returned x0 kernel_exit 0 el0_sys_instr: bl show_trapped_sys_instr ldr x22, [sp, #16 * 16] // skip the trapped mrs instruction add x22, x22, #4 str x22, [sp, #16 * 16] b ret_to_user .globl ret_from_fork ret_from_fork: bl schedule_tail cbz x19, ret_to_user // not a kernel thread mov x0, x20 blr x19 ret_to_user: bl disable_irq kernel_exit 0 .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson05/1/bl4ckout31/src/fork.c ================================================ #include "mm.h" #include "sched.h" #include "printf.h" #include "fork.h" #include "entry.h" #include "utils.h" int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg, unsigned long stack) { preempt_disable(); struct task_struct *p; p = (struct task_struct *) get_free_page(); if (!p) { return -1; } struct pt_regs *childregs = task_pt_regs(p); memzero((unsigned long)childregs, sizeof(struct pt_regs)); memzero((unsigned long)&p->cpu_context, sizeof(struct cpu_context)); if (clone_flags & PF_KTHREAD) { p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; } else { struct pt_regs * cur_regs = task_pt_regs(current); *cur_regs = *childregs; childregs->regs[0] = 0; childregs->sp = stack + PAGE_SIZE; p->stack = stack; } p->flags = clone_flags; p->priority = current->priority; p->state = TASK_RUNNING; p->counter = p->priority; p->preempt_count = 1; //disable preemtion until schedule_tail p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)childregs; int pid = nr_tasks++; task[pid] = p; preempt_enable(); return pid; } int move_to_user_mode(unsigned long pc) { struct pt_regs *regs = task_pt_regs(current); memzero((unsigned long)regs, sizeof(*regs)); regs->pc = pc; regs->pstate = PSR_MODE_EL0t; unsigned long stack = get_free_page(); //alocate new user stack if (!stack) { return -1; } regs->sp = stack + PAGE_SIZE; current->stack = stack; return 0; } struct pt_regs * task_pt_regs(struct task_struct *tsk){ unsigned long p = (unsigned long)tsk + THREAD_SIZE - sizeof(struct pt_regs); return (struct pt_regs *)p; } ================================================ FILE: exercises/lesson05/1/bl4ckout31/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson05/1/bl4ckout31/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "timer.h" #include "entry.h" #include "peripherals/irq.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32", "SYNC_ERROR", "SYSCALL_ERROR" }; void enable_interrupt_controller() { put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void show_trapped_sys_instr() { printf("Trapped mrs DAIF\r\n"); } void handle_irq(void) { unsigned int irq = get32(IRQ_PENDING_1); switch (irq) { case (SYSTEM_TIMER_IRQ_1): handle_timer_irq(); break; default: printf("Inknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson05/1/bl4ckout31/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "timer.h" #include "irq.h" #include "sched.h" #include "fork.h" #include "mini_uart.h" #include "sys.h" void user_process1(char *array) { char buf[2] = {0}; if(array[0] == '1') { get_daif(); } while (1){ for (int i = 0; i < 5; i++){ buf[0] = array[i]; call_sys_write(buf); delay(100000); } } } void user_process(){ char buf[30] = {0}; tfp_sprintf(buf, "User process started\n\r"); call_sys_write(buf); unsigned long stack = call_sys_malloc(); if (stack < 0) { printf("Error while allocating stack for process 1\n\r"); return; } int err = call_sys_clone((unsigned long)&user_process1, (unsigned long)"12345", stack); if (err < 0){ printf("Error while clonning process 1\n\r"); return; } stack = call_sys_malloc(); if (stack < 0) { printf("Error while allocating stack for process 1\n\r"); return; } err = call_sys_clone((unsigned long)&user_process1, (unsigned long)"abcd", stack); if (err < 0){ printf("Error while clonning process 2\n\r"); return; } call_sys_exit(); } void kernel_process(){ printf("Kernel process started. EL %d\r\n", get_el()); int err = move_to_user_mode((unsigned long)&user_process); if (err < 0){ printf("Error while moving process to user mode\n\r"); } } void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); int res = copy_process(PF_KTHREAD, (unsigned long)&kernel_process, 0, 0); if (res < 0) { printf("error while starting kernel process"); return; } while (1){ schedule(); } } ================================================ FILE: exercises/lesson05/1/bl4ckout31/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson05/1/bl4ckout31/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson05/1/bl4ckout31/src/mm.S ================================================ .globl memcpy memcpy: ldr x3, [x0], #8 str x3, [x1], #8 subs x2, x2, #8 b.gt memcpy ret .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson05/1/bl4ckout31/src/mm.c ================================================ #include "mm.h" static unsigned short mem_map [ PAGING_PAGES ] = {0,}; unsigned long get_free_page() { for (int i = 0; i < PAGING_PAGES; i++){ if (mem_map[i] == 0){ mem_map[i] = 1; return LOW_MEMORY + i*PAGE_SIZE; } } return 0; } void free_page(unsigned long p){ mem_map[(p - LOW_MEMORY) / PAGE_SIZE] = 0; } ================================================ FILE: exercises/lesson05/1/bl4ckout31/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson05/1/bl4ckout31/src/sched.S ================================================ #include "sched.h" .globl cpu_switch_to cpu_switch_to: mov x10, #THREAD_CPU_CONTEXT add x8, x0, x10 mov x9, sp stp x19, x20, [x8], #16 // store callee-saved registers stp x21, x22, [x8], #16 stp x23, x24, [x8], #16 stp x25, x26, [x8], #16 stp x27, x28, [x8], #16 stp x29, x9, [x8], #16 str x30, [x8] add x8, x1, x10 ldp x19, x20, [x8], #16 // restore callee-saved registers ldp x21, x22, [x8], #16 ldp x23, x24, [x8], #16 ldp x25, x26, [x8], #16 ldp x27, x28, [x8], #16 ldp x29, x9, [x8], #16 ldr x30, [x8] mov sp, x9 ret ================================================ FILE: exercises/lesson05/1/bl4ckout31/src/sched.c ================================================ #include "sched.h" #include "irq.h" #include "printf.h" #include "fork.h" #include "utils.h" #include "mm.h" static struct task_struct init_task = INIT_TASK; struct task_struct *current = &(init_task); struct task_struct * task[NR_TASKS] = {&(init_task), }; int nr_tasks = 1; void preempt_disable(void) { current->preempt_count++; } void preempt_enable(void) { current->preempt_count--; } void _schedule(void) { preempt_disable(); int next,c; struct task_struct * p; while (1) { c = -1; next = 0; for (int i = 0; i < NR_TASKS; i++){ p = task[i]; if (p && p->state == TASK_RUNNING && p->counter > c) { c = p->counter; next = i; } } if (c) { break; } for (int i = 0; i < NR_TASKS; i++) { p = task[i]; if (p) { p->counter = (p->counter >> 1) + p->priority; } } } switch_to(task[next], next); preempt_enable(); } void schedule(void) { current->counter = 0; _schedule(); } void switch_to(struct task_struct * next, int index) { if (current == next) return; struct task_struct * prev = current; current = next; cpu_switch_to(prev, next); } void schedule_tail(void) { preempt_enable(); } void timer_tick() { --current->counter; if (current->counter>0 || current->preempt_count >0) { return; } current->counter=0; enable_irq(); _schedule(); disable_irq(); } void exit_process(){ preempt_disable(); for (int i = 0; i < NR_TASKS; i++){ if (task[i] == current) { task[i]->state = TASK_ZOMBIE; break; } } if (current->stack) { free_page(current->stack); } preempt_enable(); schedule(); } ================================================ FILE: exercises/lesson05/1/bl4ckout31/src/sys.S ================================================ #include "sys.h" .globl call_sys_write call_sys_write: mov w8, #SYS_WRITE_NUMBER svc #0 ret .globl call_sys_malloc call_sys_malloc: mov w8, #SYS_MALLOC_NUMBER svc #0 ret .globl call_sys_exit call_sys_exit: mov w8, #SYS_EXIT_NUMBER svc #0 ret .globl call_sys_clone call_sys_clone: /* Save args for the child. */ mov x10, x0 /*fn*/ mov x11, x1 /*arg*/ mov x12, x2 /*stack*/ /* Do the system call. */ mov x0, x2 /* stack */ mov x8, #SYS_CLONE_NUMBER svc 0x0 cmp x0, #0 beq thread_start ret thread_start: mov x29, 0 /* Pick the function arg and execute. */ mov x0, x11 blr x10 /* We are done, pass the return value through x0. */ mov x8, #SYS_EXIT_NUMBER svc 0x0 ================================================ FILE: exercises/lesson05/1/bl4ckout31/src/sys.c ================================================ #include "fork.h" #include "printf.h" #include "utils.h" #include "sched.h" #include "mm.h" void sys_write(char * buf){ printf(buf); } int sys_clone(unsigned long stack){ return copy_process(0, 0, 0, stack); } unsigned long sys_malloc(){ unsigned long addr = get_free_page(); if (!addr) { return -1; } return addr; } void sys_exit(){ exit_process(); } void * const sys_call_table[] = {sys_write, sys_malloc, sys_clone, sys_exit}; ================================================ FILE: exercises/lesson05/1/bl4ckout31/src/timer.c ================================================ #include "utils.h" #include "printf.h" #include "sched.h" #include "peripherals/timer.h" const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); timer_tick(); } ================================================ FILE: exercises/lesson05/1/bl4ckout31/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret .globl get_daif get_daif: mrs x0, DAIF ret ================================================ FILE: exercises/lesson05/1/bl4ckout31/start.sh ================================================ #!/bin/bash mount -L rpiboot /mnt cp "kernel8.img" /mnt umount /mnt ================================================ FILE: exercises/lesson05/1/xdfm1/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only -std=gnu99 ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson05/1/xdfm1/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson05/1/xdfm1/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson05/1/xdfm1/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) // *************************************** // ESR_EL1, Exception Syndrome Register (EL1). Page 2431 of AArch64-Reference-Manual. // *************************************** #define ESR_ELx_EC_SHIFT 26 #define ESR_ELx_EC_SVC64 0x15 #define ESR_ELx_EC_IABT_LOW 0x20 #define ESR_ELx_EC_IABT_CUR 0x21 #define ESR_ELx_EC_DABT_LOW 0x24 #define ESR_ELx_EC_DABT_CUR 0x25 #endif ================================================ FILE: exercises/lesson05/1/xdfm1/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 272 // size of all saved registers #define S_X0 0 // offset of x0 register in saved stack frame #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #define SYNC_ERROR 16 #define SYSCALL_ERROR 17 #ifndef __ASSEMBLER__ void ret_from_fork(void); #endif #endif ================================================ FILE: exercises/lesson05/1/xdfm1/include/fork.h ================================================ #ifndef _FORK_H #define _FORK_H #include "sched.h" /* * PSR bits */ #define PSR_MODE_EL0t 0x00000000 #define PSR_MODE_EL1t 0x00000004 #define PSR_MODE_EL1h 0x00000005 #define PSR_MODE_EL2t 0x00000008 #define PSR_MODE_EL2h 0x00000009 #define PSR_MODE_EL3t 0x0000000c #define PSR_MODE_EL3h 0x0000000d int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg, unsigned long stack); int move_to_user_mode(unsigned long pc); struct pt_regs * task_pt_regs(struct task_struct *tsk); struct pt_regs { unsigned long regs[31]; unsigned long sp; unsigned long pc; unsigned long pstate; }; #endif ================================================ FILE: exercises/lesson05/1/xdfm1/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson05/1/xdfm1/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson05/1/xdfm1/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #include "peripherals/base.h" #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #define HIGH_MEMORY PBASE #define PAGING_MEMORY (HIGH_MEMORY - LOW_MEMORY) #define PAGING_PAGES (PAGING_MEMORY/PAGE_SIZE) #ifndef __ASSEMBLER__ unsigned long get_free_page(); void free_page(unsigned long p); void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson05/1/xdfm1/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson05/1/xdfm1/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson05/1/xdfm1/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson05/1/xdfm1/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson05/1/xdfm1/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson05/1/xdfm1/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson05/1/xdfm1/include/sched.h ================================================ #ifndef _SCHED_H #define _SCHED_H #define THREAD_CPU_CONTEXT 0 // offset of cpu_context in task_struct #ifndef __ASSEMBLER__ #define THREAD_SIZE 4096 #define NR_TASKS 64 #define FIRST_TASK task[0] #define LAST_TASK task[NR_TASKS-1] #define TASK_RUNNING 0 #define TASK_ZOMBIE 1 #define PF_KTHREAD 0x00000002 extern struct task_struct *current; extern struct task_struct * task[NR_TASKS]; extern int nr_tasks; struct cpu_context { unsigned long x19; unsigned long x20; unsigned long x21; unsigned long x22; unsigned long x23; unsigned long x24; unsigned long x25; unsigned long x26; unsigned long x27; unsigned long x28; unsigned long fp; unsigned long sp; unsigned long pc; }; struct task_struct { struct cpu_context cpu_context; long state; long counter; long priority; long preempt_count; unsigned long stack; unsigned long flags; }; extern void sched_init(void); extern void schedule(void); extern void timer_tick(void); extern void preempt_disable(void); extern void preempt_enable(void); extern void switch_to(struct task_struct * next, int index); extern void cpu_switch_to(struct task_struct* prev, struct task_struct* next); extern void exit_process(void); #define INIT_TASK \ /*cpu_context*/ { {0,0,0,0,0,0,0,0,0,0,0,0,0}, \ /* state etc */ 0,0,1, 0, 0, PF_KTHREAD \ } #endif #endif ================================================ FILE: exercises/lesson05/1/xdfm1/include/sys.h ================================================ #ifndef _SYS_H #define _SYS_H #define __NR_syscalls 4 #define SYS_WRITE_NUMBER 0 // syscal numbers #define SYS_MALLOC_NUMBER 1 #define SYS_CLONE_NUMBER 2 #define SYS_EXIT_NUMBER 3 #ifndef __ASSEMBLER__ void sys_write(char * buf); int sys_fork(); void call_sys_write(char * buf); int call_sys_clone(unsigned long fn, unsigned long arg, unsigned long stack); unsigned long call_sys_malloc(); void call_sys_exit(); #endif #endif /*_SYS_H */ ================================================ FILE: exercises/lesson05/1/xdfm1/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson05/1/xdfm1/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson05/1/xdfm1/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson05/1/xdfm1/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson05/1/xdfm1/src/entry.S ================================================ #include "arm/sysregs.h" #include "entry.h" #include "sys.h" .macro handle_invalid_entry el, type kernel_entry \el mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro handle_data_abort el kernel_entry \el mrs x0, esr_el1 mrs x1, far_el1 bl show_data_abort_message .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry, el sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] .if \el == 0 mrs x21, sp_el0 .else add x21, sp, #S_FRAME_SIZE .endif /* \el == 0 */ mrs x22, elr_el1 mrs x23, spsr_el1 stp x30, x21, [sp, #16 * 15] stp x22, x23, [sp, #16 * 16] .endm .macro kernel_exit, el ldp x22, x23, [sp, #16 * 16] ldp x30, x21, [sp, #16 * 15] .if \el == 0 msr sp_el0, x21 .endif /* \el == 0 */ msr elr_el1, x22 msr spsr_el1, x23 ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry el1_sync // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry el0_sync // Synchronous 64-bit EL0 ventry el0_irq // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry 1, SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry 1, IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry 1, FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry 1, ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry 1, SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry 1, FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry 1, ERROR_INVALID_EL1h fiq_invalid_el0_64: handle_invalid_entry 0, FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry 0, ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry 0, SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry 0, IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry 0, FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry 0, ERROR_INVALID_EL0_32 el1_irq: kernel_entry 1 bl handle_irq kernel_exit 1 el1_sync: kernel_entry 1 mrs x25, esr_el1 lsr x24, x25, #ESR_ELx_EC_SHIFT cmp x24, #ESR_ELx_EC_DABT_CUR b.eq el1_da handle_invalid_entry 0, SYNC_ERROR el1_da: handle_data_abort 1 kernel_exit 1 el0_irq: kernel_entry 0 bl handle_irq kernel_exit 0 el0_sync: kernel_entry 0 mrs x25, esr_el1 // read the syndrome register lsr x24, x25, #ESR_ELx_EC_SHIFT // exception class cmp x24, #ESR_ELx_EC_SVC64 // SVC in 64-bit state b.eq el0_svc handle_invalid_entry 0, SYNC_ERROR sc_nr .req x25 // number of system calls scno .req x26 // syscall number stbl .req x27 // syscall table pointer el0_svc: adr stbl, sys_call_table // load syscall table pointer uxtw scno, w8 // syscall number in w8 mov sc_nr, #__NR_syscalls bl enable_irq cmp scno, sc_nr // check upper syscall limit b.hs ni_sys ldr x16, [stbl, scno, lsl #3] // address in the syscall table blr x16 // call sys_* routine b ret_from_syscall ni_sys: handle_invalid_entry 0, SYSCALL_ERROR ret_from_syscall: bl disable_irq str x0, [sp, #S_X0] // returned x0 kernel_exit 0 .globl ret_from_fork ret_from_fork: bl schedule_tail cbz x19, ret_to_user // not a kernel thread mov x0, x20 blr x19 ret_to_user: bl disable_irq kernel_exit 0 .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson05/1/xdfm1/src/fork.c ================================================ #include "mm.h" #include "sched.h" #include "printf.h" #include "fork.h" #include "entry.h" #include "utils.h" int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg, unsigned long stack) { preempt_disable(); struct task_struct *p; p = (struct task_struct *) get_free_page(); if (!p) { return -1; } struct pt_regs *childregs = task_pt_regs(p); memzero((unsigned long)childregs, sizeof(struct pt_regs)); memzero((unsigned long)&p->cpu_context, sizeof(struct cpu_context)); if (clone_flags & PF_KTHREAD) { p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; } else { struct pt_regs * cur_regs = task_pt_regs(current); *cur_regs = *childregs; childregs->regs[0] = 0; childregs->sp = stack + PAGE_SIZE; p->stack = stack; } p->flags = clone_flags; p->priority = current->priority; p->state = TASK_RUNNING; p->counter = p->priority; p->preempt_count = 1; //disable preemtion until schedule_tail p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)childregs; int pid = nr_tasks++; task[pid] = p; preempt_enable(); return pid; } int move_to_user_mode(unsigned long pc) { struct pt_regs *regs = task_pt_regs(current); memzero((unsigned long)regs, sizeof(*regs)); regs->pc = pc; regs->pstate = PSR_MODE_EL0t; unsigned long stack = get_free_page(); //alocate new user stack if (!stack) { return -1; } regs->sp = stack + PAGE_SIZE; current->stack = stack; return 0; } struct pt_regs * task_pt_regs(struct task_struct *tsk){ unsigned long p = (unsigned long)tsk + THREAD_SIZE - sizeof(struct pt_regs); return (struct pt_regs *)p; } ================================================ FILE: exercises/lesson05/1/xdfm1/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson05/1/xdfm1/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "timer.h" #include "entry.h" #include "peripherals/irq.h" #include "sched.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32" }; void enable_interrupt_controller() { put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void show_data_abort_message(unsigned long esr, unsigned long address) { printf("Data abort exception, ESR:%x, fault address: %x\r\n", esr, address); printf("terminating faulting process...\r\n"); exit_process(); } void handle_irq(void) { unsigned int irq = get32(IRQ_PENDING_1); switch (irq) { case (SYSTEM_TIMER_IRQ_1): handle_timer_irq(); break; default: printf("Inknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson05/1/xdfm1/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "timer.h" #include "irq.h" #include "sched.h" #include "fork.h" #include "mini_uart.h" #include "sys.h" void user_process1(char *array) { char buf[2] = {0}; while (1){ for (int i = 0; i < 5; i++){ buf[0] = array[i]; call_sys_write(buf); delay(100000); } if(array[0] == 'a') { //attempt accessing a system register printf("exception level is %d\n\r", get_el()); } } } void user_process(){ char buf[30] = {0}; tfp_sprintf(buf, "User process started\n\r"); call_sys_write(buf); unsigned long stack = call_sys_malloc(); if (stack < 0) { printf("Error while allocating stack for process 1\n\r"); return; } int err = call_sys_clone((unsigned long)&user_process1, (unsigned long)"12345", stack); if (err < 0){ printf("Error while clonning process 1\n\r"); return; } stack = call_sys_malloc(); if (stack < 0) { printf("Error while allocating stack for process 1\n\r"); return; } err = call_sys_clone((unsigned long)&user_process1, (unsigned long)"abcd", stack); if (err < 0){ printf("Error while clonning process 2\n\r"); return; } //attempt accessing a system register printf("exception level is %d\n\r", get_el()); call_sys_exit(); } void kernel_process(){ printf("Kernel process started. EL %d\r\n", get_el()); int err = move_to_user_mode((unsigned long)&user_process); if (err < 0){ printf("Error while moving process to user mode\n\r"); } } void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); int res = copy_process(PF_KTHREAD, (unsigned long)&kernel_process, 0, 0); if (res < 0) { printf("error while starting kernel process"); return; } while (1){ schedule(); } } ================================================ FILE: exercises/lesson05/1/xdfm1/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson05/1/xdfm1/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson05/1/xdfm1/src/mm.S ================================================ .globl memcpy memcpy: ldr x3, [x0], #8 str x3, [x1], #8 subs x2, x2, #8 b.gt memcpy ret .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson05/1/xdfm1/src/mm.c ================================================ #include "mm.h" static unsigned short mem_map [ PAGING_PAGES ] = {0,}; unsigned long get_free_page() { for (int i = 0; i < PAGING_PAGES; i++){ if (mem_map[i] == 0){ mem_map[i] = 1; return LOW_MEMORY + i*PAGE_SIZE; } } return 0; } void free_page(unsigned long p){ mem_map[(p - LOW_MEMORY) / PAGE_SIZE] = 0; } ================================================ FILE: exercises/lesson05/1/xdfm1/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson05/1/xdfm1/src/sched.S ================================================ #include "sched.h" .globl cpu_switch_to cpu_switch_to: mov x10, #THREAD_CPU_CONTEXT add x8, x0, x10 mov x9, sp stp x19, x20, [x8], #16 // store callee-saved registers stp x21, x22, [x8], #16 stp x23, x24, [x8], #16 stp x25, x26, [x8], #16 stp x27, x28, [x8], #16 stp x29, x9, [x8], #16 str x30, [x8] add x8, x1, x10 ldp x19, x20, [x8], #16 // restore callee-saved registers ldp x21, x22, [x8], #16 ldp x23, x24, [x8], #16 ldp x25, x26, [x8], #16 ldp x27, x28, [x8], #16 ldp x29, x9, [x8], #16 ldr x30, [x8] mov sp, x9 ret ================================================ FILE: exercises/lesson05/1/xdfm1/src/sched.c ================================================ #include "sched.h" #include "irq.h" #include "printf.h" #include "fork.h" #include "utils.h" #include "mm.h" static struct task_struct init_task = INIT_TASK; struct task_struct *current = &(init_task); struct task_struct * task[NR_TASKS] = {&(init_task), }; int nr_tasks = 1; void preempt_disable(void) { current->preempt_count++; } void preempt_enable(void) { current->preempt_count--; } void _schedule(void) { preempt_disable(); int next,c; struct task_struct * p; while (1) { c = -1; next = 0; for (int i = 0; i < NR_TASKS; i++){ p = task[i]; if (p && p->state == TASK_RUNNING && p->counter > c) { c = p->counter; next = i; } } if (c) { break; } for (int i = 0; i < NR_TASKS; i++) { p = task[i]; if (p) { p->counter = (p->counter >> 1) + p->priority; } } } switch_to(task[next], next); preempt_enable(); } void schedule(void) { current->counter = 0; _schedule(); } void switch_to(struct task_struct * next, int index) { if (current == next) return; struct task_struct * prev = current; current = next; cpu_switch_to(prev, next); } void schedule_tail(void) { preempt_enable(); } void timer_tick() { --current->counter; if (current->counter>0 || current->preempt_count >0) { return; } current->counter=0; enable_irq(); _schedule(); disable_irq(); } void exit_process(){ preempt_disable(); for (int i = 0; i < NR_TASKS; i++){ if (task[i] == current) { task[i]->state = TASK_ZOMBIE; break; } } if (current->stack) { free_page(current->stack); } preempt_enable(); schedule(); } ================================================ FILE: exercises/lesson05/1/xdfm1/src/sys.S ================================================ #include "sys.h" .globl call_sys_write call_sys_write: mov w8, #SYS_WRITE_NUMBER svc #0 ret .globl call_sys_malloc call_sys_malloc: mov w8, #SYS_MALLOC_NUMBER svc #0 ret .globl call_sys_exit call_sys_exit: mov w8, #SYS_EXIT_NUMBER svc #0 ret .globl call_sys_clone call_sys_clone: /* Save args for the child. */ mov x10, x0 /*fn*/ mov x11, x1 /*arg*/ mov x12, x2 /*stack*/ /* Do the system call. */ mov x0, x2 /* stack */ mov x8, #SYS_CLONE_NUMBER svc 0x0 cmp x0, #0 beq thread_start ret thread_start: mov x29, 0 /* Pick the function arg and execute. */ mov x0, x11 blr x10 /* We are done, pass the return value through x0. */ mov x8, #SYS_EXIT_NUMBER svc 0x0 ================================================ FILE: exercises/lesson05/1/xdfm1/src/sys.c ================================================ #include "fork.h" #include "printf.h" #include "utils.h" #include "sched.h" #include "mm.h" void sys_write(char * buf){ printf(buf); } int sys_clone(unsigned long stack){ return copy_process(0, 0, 0, stack); } unsigned long sys_malloc(){ unsigned long addr = get_free_page(); if (!addr) { return -1; } return addr; } void sys_exit(){ exit_process(); } void * const sys_call_table[] = {sys_write, sys_malloc, sys_clone, sys_exit}; ================================================ FILE: exercises/lesson05/1/xdfm1/src/timer.c ================================================ #include "utils.h" #include "printf.h" #include "sched.h" #include "peripherals/timer.h" const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); timer_tick(); } ================================================ FILE: exercises/lesson05/1/xdfm1/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson05/2/avenito/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson05/2/avenito/README.md ================================================ ================================================ FILE: exercises/lesson05/2/avenito/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson05/2/avenito/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson05/2/avenito/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) // *************************************** // ESR_EL1, Exception Syndrome Register (EL1). Page 2431 of AArch64-Reference-Manual. // *************************************** #define ESR_ELx_EC_SHIFT 26 #define ESR_ELx_EC_SVC64 0x15 #endif ================================================ FILE: exercises/lesson05/2/avenito/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 272 // size of all saved registers #define S_X0 0 // offset of x0 register in saved stack frame #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #define SYNC_ERROR 16 #define SYSCALL_ERROR 17 #ifndef __ASSEMBLER__ void ret_from_fork(void); #endif #endif ================================================ FILE: exercises/lesson05/2/avenito/include/fork.h ================================================ #ifndef _FORK_H #define _FORK_H #include "sched.h" /* * PSR bits */ #define PSR_MODE_EL0t 0x00000000 #define PSR_MODE_EL1t 0x00000004 #define PSR_MODE_EL1h 0x00000005 #define PSR_MODE_EL2t 0x00000008 #define PSR_MODE_EL2h 0x00000009 #define PSR_MODE_EL3t 0x0000000c #define PSR_MODE_EL3h 0x0000000d int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg, unsigned long stack); int move_to_user_mode(unsigned long pc); struct pt_regs * task_pt_regs(struct task_struct *tsk); struct pt_regs { unsigned long regs[31]; unsigned long sp; unsigned long pc; unsigned long pstate; }; #endif ================================================ FILE: exercises/lesson05/2/avenito/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson05/2/avenito/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson05/2/avenito/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #include "peripherals/base.h" #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #define HIGH_MEMORY PBASE #define PAGING_MEMORY (HIGH_MEMORY - LOW_MEMORY) #define PAGING_PAGES (PAGING_MEMORY/PAGE_SIZE) #ifndef __ASSEMBLER__ unsigned long get_free_page(); void free_page(unsigned long p); void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson05/2/avenito/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson05/2/avenito/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson05/2/avenito/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson05/2/avenito/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson05/2/avenito/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson05/2/avenito/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson05/2/avenito/include/sched.h ================================================ #ifndef _SCHED_H #define _SCHED_H #define THREAD_CPU_CONTEXT 0 // offset of cpu_context in task_struct #ifndef __ASSEMBLER__ #define THREAD_SIZE 4096 #define NR_TASKS 64 #define FIRST_TASK task[0] #define LAST_TASK task[NR_TASKS-1] #define TASK_RUNNING 0 #define TASK_ZOMBIE 1 #define PF_KTHREAD 0x00000002 extern struct task_struct *current; extern struct task_struct * task[NR_TASKS]; extern int nr_tasks; struct cpu_context { unsigned long x19; unsigned long x20; unsigned long x21; unsigned long x22; unsigned long x23; unsigned long x24; unsigned long x25; unsigned long x26; unsigned long x27; unsigned long x28; unsigned long fp; unsigned long sp; unsigned long pc; }; struct task_struct { struct cpu_context cpu_context; long state; long counter; long priority; long preempt_count; unsigned long stack; unsigned long flags; }; extern void sched_init(void); extern void schedule(void); extern void timer_tick(void); extern void preempt_disable(void); extern void preempt_enable(void); extern void switch_to(struct task_struct * next, int index); extern void cpu_switch_to(struct task_struct* prev, struct task_struct* next); extern void exit_process(void); #define INIT_TASK \ /*cpu_context*/ { {0,0,0,0,0,0,0,0,0,0,0,0,0}, \ /* state etc */ 0,0,1, 0, 0, PF_KTHREAD \ } #endif #endif ================================================ FILE: exercises/lesson05/2/avenito/include/sys.h ================================================ #ifndef _SYS_H #define _SYS_H #define __NR_syscalls 4 #define SYS_WRITE_NUMBER 0 // syscal numbers #define SYS_MALLOC_NUMBER 1 #define SYS_CLONE_NUMBER 2 #define SYS_EXIT_NUMBER 3 #ifndef __ASSEMBLER__ void sys_write(char * buf); int sys_fork(); void call_sys_write(char * buf); int call_sys_clone(unsigned long fn, unsigned long arg, unsigned long stack); unsigned long call_sys_malloc(); void call_sys_exit(); #endif #endif /*_SYS_H */ ================================================ FILE: exercises/lesson05/2/avenito/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson05/2/avenito/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson05/2/avenito/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson05/2/avenito/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson05/2/avenito/src/entry.S ================================================ #include "arm/sysregs.h" #include "entry.h" #include "sys.h" .macro handle_invalid_entry el, type kernel_entry \el mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry, el sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] .if \el == 0 mrs x21, sp_el0 .else add x21, sp, #S_FRAME_SIZE .endif /* \el == 0 */ mrs x22, elr_el1 mrs x23, spsr_el1 stp x30, x21, [sp, #16 * 15] stp x22, x23, [sp, #16 * 16] .endm .macro kernel_exit, el ldp x22, x23, [sp, #16 * 16] ldp x30, x21, [sp, #16 * 15] .if \el == 0 msr sp_el0, x21 .endif /* \el == 0 */ msr elr_el1, x22 msr spsr_el1, x23 ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry el0_sync // Synchronous 64-bit EL0 ventry el0_irq // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry 1, SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry 1, IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry 1, FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry 1, ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry 1, SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry 1, FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry 1, ERROR_INVALID_EL1h fiq_invalid_el0_64: handle_invalid_entry 0, FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry 0, ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry 0, SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry 0, IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry 0, FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry 0, ERROR_INVALID_EL0_32 el1_irq: kernel_entry 1 bl handle_irq kernel_exit 1 el0_irq: kernel_entry 0 bl handle_irq kernel_exit 0 el0_sync: kernel_entry 0 mrs x25, esr_el1 // read the syndrome register lsr x24, x25, #ESR_ELx_EC_SHIFT // exception class cmp x24, #ESR_ELx_EC_SVC64 // SVC in 64-bit state b.eq el0_svc handle_invalid_entry 0, SYNC_ERROR sc_nr .req x25 // number of system calls scno .req x26 // syscall number stbl .req x27 // syscall table pointer el0_svc: adr stbl, sys_call_table // load syscall table pointer uxtw scno, w8 // syscall number in w8 mov sc_nr, #__NR_syscalls bl enable_irq cmp scno, sc_nr // check upper syscall limit b.hs ni_sys ldr x16, [stbl, scno, lsl #3] // address in the syscall table blr x16 // call sys_* routine b ret_from_syscall ni_sys: handle_invalid_entry 0, SYSCALL_ERROR ret_from_syscall: bl disable_irq str x0, [sp, #S_X0] // returned x0 kernel_exit 0 .globl ret_from_fork ret_from_fork: bl schedule_tail cbz x19, ret_to_user // not a kernel thread mov x0, x20 blr x19 ret_to_user: bl disable_irq kernel_exit 0 .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson05/2/avenito/src/fork.c ================================================ #include "mm.h" #include "sched.h" #include "printf.h" #include "fork.h" #include "entry.h" #include "utils.h" int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg, unsigned long stack) { preempt_disable(); struct task_struct *p; p = (struct task_struct *) get_free_page(); if (!p) { return -1; } struct pt_regs *childregs = task_pt_regs(p); memzero((unsigned long)childregs, sizeof(struct pt_regs)); memzero((unsigned long)&p->cpu_context, sizeof(struct cpu_context)); if (clone_flags & PF_KTHREAD) { p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; } else { struct pt_regs * cur_regs = task_pt_regs(current); *cur_regs = *childregs; childregs->regs[0] = 0; childregs->sp = stack + PAGE_SIZE; p->stack = stack; } p->flags = clone_flags; p->priority = current->priority; p->state = TASK_RUNNING; p->counter = p->priority; p->preempt_count = 1; //disable preemtion until schedule_tail p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)childregs; int pid = nr_tasks++; task[pid] = p; preempt_enable(); return pid; } int move_to_user_mode(unsigned long pc) { struct pt_regs *regs = task_pt_regs(current); memzero((unsigned long)regs, sizeof(*regs)); regs->pc = pc; regs->pstate = PSR_MODE_EL0t; unsigned long stack = get_free_page(); //alocate new user stack if (!stack) { return -1; } regs->sp = stack + PAGE_SIZE; current->stack = stack; return 0; } struct pt_regs * task_pt_regs(struct task_struct *tsk){ unsigned long p = (unsigned long)tsk + THREAD_SIZE - sizeof(struct pt_regs); return (struct pt_regs *)p; } ================================================ FILE: exercises/lesson05/2/avenito/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson05/2/avenito/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "timer.h" #include "entry.h" #include "peripherals/irq.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32", "SYNC_ERROR", "SYSCALL_ERROR" }; void enable_interrupt_controller() { put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { unsigned int irq = get32(IRQ_PENDING_1); switch (irq) { case (SYSTEM_TIMER_IRQ_1): handle_timer_irq(); break; default: printf("Inknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson05/2/avenito/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "timer.h" #include "irq.h" #include "sched.h" #include "fork.h" #include "mini_uart.h" #include "sys.h" void user_process1(char *array) { char buf[2] = {0}; while (1){ for (int i = 0; i < 5; i++){ buf[0] = array[i]; call_sys_write(buf); delay(100000); } } } void user_process(){ char buf[30] = {0}; tfp_sprintf(buf, "User process started\n\r"); call_sys_write(buf); unsigned long stack = call_sys_malloc(); if (stack < 0) { printf("Error while allocating stack for process 1\n\r"); return; } int err = call_sys_clone((unsigned long)&user_process1, (unsigned long)"12345", stack); if (err < 0){ printf("Error while clonning process 1\n\r"); return; } stack = call_sys_malloc(); if (stack < 0) { printf("Error while allocating stack for process 1\n\r"); return; } err = call_sys_clone((unsigned long)&user_process1, (unsigned long)"abcd", stack); if (err < 0){ printf("Error while clonning process 2\n\r"); return; } call_sys_exit(); } void kernel_process(){ printf("Kernel process started. EL %d\r\n", get_el()); int err = move_to_user_mode((unsigned long)&user_process); if (err < 0){ printf("Error while moving process to user mode\n\r"); } } void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); int res = copy_process(PF_KTHREAD, (unsigned long)&kernel_process, 0, 0); if (res < 0) { printf("error while starting kernel process"); return; } while (1){ schedule(); } } ================================================ FILE: exercises/lesson05/2/avenito/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson05/2/avenito/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson05/2/avenito/src/mm.S ================================================ .globl memcpy memcpy: ldr x3, [x0], #8 str x3, [x1], #8 subs x2, x2, #8 b.gt memcpy ret .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson05/2/avenito/src/mm.c ================================================ #include "mm.h" static unsigned short mem_map [ PAGING_PAGES ] = {0,}; unsigned long get_free_page() { for (int i = 0; i < PAGING_PAGES; i++){ if (mem_map[i] == 0){ mem_map[i] = 1; return LOW_MEMORY + i*PAGE_SIZE; } } return 0; } void free_page(unsigned long p){ mem_map[(p - LOW_MEMORY) / PAGE_SIZE] = 0; } ================================================ FILE: exercises/lesson05/2/avenito/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson05/2/avenito/src/sched.S ================================================ #include "sched.h" .globl cpu_switch_to cpu_switch_to: mov x10, #THREAD_CPU_CONTEXT add x8, x0, x10 mov x9, sp stp x19, x20, [x8], #16 // store callee-saved registers stp x21, x22, [x8], #16 stp x23, x24, [x8], #16 stp x25, x26, [x8], #16 stp x27, x28, [x8], #16 stp x29, x9, [x8], #16 str x30, [x8] add x8, x1, x10 ldp x19, x20, [x8], #16 // restore callee-saved registers ldp x21, x22, [x8], #16 ldp x23, x24, [x8], #16 ldp x25, x26, [x8], #16 ldp x27, x28, [x8], #16 ldp x29, x9, [x8], #16 ldr x30, [x8] mov sp, x9 ret ================================================ FILE: exercises/lesson05/2/avenito/src/sched.c ================================================ #include "sched.h" #include "irq.h" #include "printf.h" #include "fork.h" #include "utils.h" #include "mm.h" static struct task_struct init_task = INIT_TASK; struct task_struct *current = &(init_task); struct task_struct * task[NR_TASKS] = {&(init_task), }; int nr_tasks = 1; void preempt_disable(void) { current->preempt_count++; } void preempt_enable(void) { current->preempt_count--; } void _schedule(void) { preempt_disable(); int next,c; struct task_struct * p; while (1) { c = -1; next = 0; for (int i = 0; i < NR_TASKS; i++){ p = task[i]; if (p && p->state == TASK_RUNNING && p->counter > c) { c = p->counter; next = i; } } if (c) { break; } for (int i = 0; i < NR_TASKS; i++) { p = task[i]; if (p) { p->counter = (p->counter >> 1) + p->priority; } } } switch_to(task[next], next); preempt_enable(); } void schedule(void) { current->counter = 0; _schedule(); } void switch_to(struct task_struct * next, int index) { if (current == next) return; struct task_struct * prev = current; current = next; cpu_switch_to(prev, next); } void schedule_tail(void) { preempt_enable(); } void timer_tick() { --current->counter; if (current->counter>0 || current->preempt_count >0) { return; } current->counter=0; enable_irq(); _schedule(); disable_irq(); } void exit_process(){ preempt_disable(); for (int i = 0; i < NR_TASKS; i++){ if (task[i] == current) { task[i]->state = TASK_ZOMBIE; break; } } if (current->stack) { free_page(current->stack); } preempt_enable(); schedule(); } ================================================ FILE: exercises/lesson05/2/avenito/src/sys.S ================================================ #include "sys.h" .globl call_sys_write call_sys_write: mov w8, #SYS_WRITE_NUMBER svc #0 ret .globl call_sys_malloc call_sys_malloc: mov w8, #SYS_MALLOC_NUMBER svc #0 ret .globl call_sys_exit call_sys_exit: mov w8, #SYS_EXIT_NUMBER svc #0 ret .globl call_sys_clone call_sys_clone: /* Save args for the child. */ mov x10, x0 /*fn*/ mov x11, x1 /*arg*/ mov x12, x2 /*stack*/ /* Do the system call. */ mov x0, x2 /* stack */ mov x8, #SYS_CLONE_NUMBER svc 0x0 cmp x0, #0 beq thread_start ret thread_start: mov x29, 0 /* Pick the function arg and execute. */ mov x0, x11 blr x10 /* We are done, pass the return value through x0. */ mov x8, #SYS_EXIT_NUMBER svc 0x0 ================================================ FILE: exercises/lesson05/2/avenito/src/sys.c ================================================ #include "fork.h" #include "printf.h" #include "utils.h" #include "sched.h" #include "mm.h" void sys_write(char * buf){ printf(buf); } int sys_clone(unsigned long stack){ return copy_process(0, 0, 0, stack); } unsigned long sys_malloc(){ unsigned long addr = get_free_page(); if (!addr) { return -1; } return addr; } void sys_exit(){ exit_process(); } void * const sys_call_table[] = {sys_write, sys_malloc, sys_clone, sys_exit}; ================================================ FILE: exercises/lesson05/2/avenito/src/timer.c ================================================ #include "utils.h" #include "printf.h" #include "sched.h" #include "peripherals/timer.h" const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); timer_tick(); } ================================================ FILE: exercises/lesson05/2/avenito/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson05/2/bl4ckout31/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson05/2/bl4ckout31/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson05/2/bl4ckout31/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson05/2/bl4ckout31/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) // *************************************** // ESR_EL1, Exception Syndrome Register (EL1). Page 2431 of AArch64-Reference-Manual. // *************************************** #define ESR_ELx_EC_SHIFT 26 #define ESR_ELx_EC_SVC64 0x15 #endif ================================================ FILE: exercises/lesson05/2/bl4ckout31/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 272 // size of all saved registers #define S_X0 0 // offset of x0 register in saved stack frame #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #define SYNC_ERROR 16 #define SYSCALL_ERROR 17 #ifndef __ASSEMBLER__ void ret_from_fork(void); #endif #endif ================================================ FILE: exercises/lesson05/2/bl4ckout31/include/fork.h ================================================ #ifndef _FORK_H #define _FORK_H #include "sched.h" /* * PSR bits */ #define PSR_MODE_EL0t 0x00000000 #define PSR_MODE_EL1t 0x00000004 #define PSR_MODE_EL1h 0x00000005 #define PSR_MODE_EL2t 0x00000008 #define PSR_MODE_EL2h 0x00000009 #define PSR_MODE_EL3t 0x0000000c #define PSR_MODE_EL3h 0x0000000d int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg, unsigned long stack); int move_to_user_mode(unsigned long pc); struct pt_regs * task_pt_regs(struct task_struct *tsk); struct pt_regs { unsigned long regs[31]; unsigned long sp; unsigned long pc; unsigned long pstate; }; #endif ================================================ FILE: exercises/lesson05/2/bl4ckout31/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson05/2/bl4ckout31/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson05/2/bl4ckout31/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #include "peripherals/base.h" #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #define HIGH_MEMORY PBASE #define PAGING_MEMORY (HIGH_MEMORY - LOW_MEMORY) #define PAGING_PAGES (PAGING_MEMORY/PAGE_SIZE) #ifndef __ASSEMBLER__ unsigned long get_free_page(); void free_page(unsigned long p); void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson05/2/bl4ckout31/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson05/2/bl4ckout31/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson05/2/bl4ckout31/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson05/2/bl4ckout31/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson05/2/bl4ckout31/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson05/2/bl4ckout31/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson05/2/bl4ckout31/include/sched.h ================================================ #ifndef _SCHED_H #define _SCHED_H #define THREAD_CPU_CONTEXT 0 // offset of cpu_context in task_struct #ifndef __ASSEMBLER__ #define THREAD_SIZE 4096 #define NR_TASKS 64 #define FIRST_TASK task[0] #define LAST_TASK task[NR_TASKS-1] #define TASK_RUNNING 0 #define TASK_ZOMBIE 1 #define PF_KTHREAD 0x00000002 extern struct task_struct *current; extern struct task_struct * task[NR_TASKS]; extern int nr_tasks; struct cpu_context { unsigned long x19; unsigned long x20; unsigned long x21; unsigned long x22; unsigned long x23; unsigned long x24; unsigned long x25; unsigned long x26; unsigned long x27; unsigned long x28; unsigned long fp; unsigned long sp; unsigned long pc; }; struct task_struct { struct cpu_context cpu_context; long state; long counter; long priority; long preempt_count; unsigned long stack; unsigned long flags; }; extern void sched_init(void); extern void schedule(void); extern void timer_tick(void); extern void preempt_disable(void); extern void preempt_enable(void); extern void switch_to(struct task_struct * next, int index); extern void cpu_switch_to(struct task_struct* prev, struct task_struct* next); extern void exit_process(void); #define INIT_TASK \ /*cpu_context*/ { {0,0,0,0,0,0,0,0,0,0,0,0,0}, \ /* state etc */ 0,0,1, 0, 0, PF_KTHREAD \ } #endif #endif ================================================ FILE: exercises/lesson05/2/bl4ckout31/include/sys.h ================================================ #ifndef _SYS_H #define _SYS_H #define __NR_syscalls 5 #define SYS_WRITE_NUMBER 0 // syscal numbers #define SYS_MALLOC_NUMBER 1 #define SYS_CLONE_NUMBER 2 #define SYS_EXIT_NUMBER 3 #define SYS_PRIORITY_NUMBER 4 #ifndef __ASSEMBLER__ void sys_write(char * buf); int sys_fork(); void call_sys_write(char * buf); int call_sys_clone(unsigned long fn, unsigned long arg, unsigned long stack); unsigned long call_sys_malloc(); void call_sys_exit(); void call_sys_priority(long priority); #endif #endif /*_SYS_H */ ================================================ FILE: exercises/lesson05/2/bl4ckout31/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson05/2/bl4ckout31/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson05/2/bl4ckout31/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson05/2/bl4ckout31/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson05/2/bl4ckout31/src/entry.S ================================================ #include "arm/sysregs.h" #include "entry.h" #include "sys.h" .macro handle_invalid_entry el, type kernel_entry \el mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry, el sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] .if \el == 0 mrs x21, sp_el0 .else add x21, sp, #S_FRAME_SIZE .endif /* \el == 0 */ mrs x22, elr_el1 mrs x23, spsr_el1 stp x30, x21, [sp, #16 * 15] stp x22, x23, [sp, #16 * 16] .endm .macro kernel_exit, el ldp x22, x23, [sp, #16 * 16] ldp x30, x21, [sp, #16 * 15] .if \el == 0 msr sp_el0, x21 .endif /* \el == 0 */ msr elr_el1, x22 msr spsr_el1, x23 ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry el0_sync // Synchronous 64-bit EL0 ventry el0_irq // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry 1, SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry 1, IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry 1, FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry 1, ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry 1, SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry 1, FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry 1, ERROR_INVALID_EL1h fiq_invalid_el0_64: handle_invalid_entry 0, FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry 0, ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry 0, SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry 0, IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry 0, FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry 0, ERROR_INVALID_EL0_32 el1_irq: kernel_entry 1 bl handle_irq kernel_exit 1 el0_irq: kernel_entry 0 bl handle_irq kernel_exit 0 el0_sync: kernel_entry 0 mrs x25, esr_el1 // read the syndrome register lsr x24, x25, #ESR_ELx_EC_SHIFT // exception class cmp x24, #ESR_ELx_EC_SVC64 // SVC in 64-bit state b.eq el0_svc handle_invalid_entry 0, SYNC_ERROR sc_nr .req x25 // number of system calls scno .req x26 // syscall number stbl .req x27 // syscall table pointer el0_svc: adr stbl, sys_call_table // load syscall table pointer uxtw scno, w8 // syscall number in w8 mov sc_nr, #__NR_syscalls bl enable_irq cmp scno, sc_nr // check upper syscall limit b.hs ni_sys ldr x16, [stbl, scno, lsl #3] // address in the syscall table blr x16 // call sys_* routine b ret_from_syscall ni_sys: handle_invalid_entry 0, SYSCALL_ERROR ret_from_syscall: bl disable_irq str x0, [sp, #S_X0] // returned x0 kernel_exit 0 .globl ret_from_fork ret_from_fork: bl schedule_tail cbz x19, ret_to_user // not a kernel thread mov x0, x20 blr x19 ret_to_user: bl disable_irq kernel_exit 0 .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson05/2/bl4ckout31/src/fork.c ================================================ #include "mm.h" #include "sched.h" #include "printf.h" #include "fork.h" #include "entry.h" #include "utils.h" int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg, unsigned long stack) { preempt_disable(); struct task_struct *p; p = (struct task_struct *) get_free_page(); if (!p) { return -1; } struct pt_regs *childregs = task_pt_regs(p); memzero((unsigned long)childregs, sizeof(struct pt_regs)); memzero((unsigned long)&p->cpu_context, sizeof(struct cpu_context)); if (clone_flags & PF_KTHREAD) { p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; } else { struct pt_regs * cur_regs = task_pt_regs(current); *cur_regs = *childregs; childregs->regs[0] = 0; childregs->sp = stack + PAGE_SIZE; p->stack = stack; } p->flags = clone_flags; p->priority = current->priority; p->state = TASK_RUNNING; p->counter = p->priority; p->preempt_count = 1; //disable preemtion until schedule_tail p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)childregs; int pid = nr_tasks++; task[pid] = p; preempt_enable(); return pid; } int move_to_user_mode(unsigned long pc) { struct pt_regs *regs = task_pt_regs(current); memzero((unsigned long)regs, sizeof(*regs)); regs->pc = pc; regs->pstate = PSR_MODE_EL0t; unsigned long stack = get_free_page(); //alocate new user stack if (!stack) { return -1; } regs->sp = stack + PAGE_SIZE; current->stack = stack; return 0; } struct pt_regs * task_pt_regs(struct task_struct *tsk){ unsigned long p = (unsigned long)tsk + THREAD_SIZE - sizeof(struct pt_regs); return (struct pt_regs *)p; } ================================================ FILE: exercises/lesson05/2/bl4ckout31/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson05/2/bl4ckout31/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "timer.h" #include "entry.h" #include "peripherals/irq.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32" }; void enable_interrupt_controller() { put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { unsigned int irq = get32(IRQ_PENDING_1); switch (irq) { case (SYSTEM_TIMER_IRQ_1): handle_timer_irq(); break; default: printf("Inknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson05/2/bl4ckout31/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "timer.h" #include "irq.h" #include "sched.h" #include "fork.h" #include "mini_uart.h" #include "sys.h" void user_process1(char *array) { char buf[2] = {0}; long priority = 1; while (1){ if(array[0] == '1') { call_sys_priority(++priority); } for (int j = 0; j < 4; j++) { for (int i = 0; i < 5; i++){ buf[0] = array[i]; call_sys_write(buf); delay(100000); } } } } void user_process(){ char buf[30] = {0}; tfp_sprintf(buf, "User process started\n\r"); call_sys_write(buf); unsigned long stack = call_sys_malloc(); if (stack < 0) { printf("Error while allocating stack for process 1\n\r"); return; } int err = call_sys_clone((unsigned long)&user_process1, (unsigned long)"12345", stack); if (err < 0){ printf("Error while clonning process 1\n\r"); return; } stack = call_sys_malloc(); if (stack < 0) { printf("Error while allocating stack for process 1\n\r"); return; } err = call_sys_clone((unsigned long)&user_process1, (unsigned long)"abcd", stack); if (err < 0){ printf("Error while clonning process 2\n\r"); return; } call_sys_exit(); } void kernel_process(){ printf("Kernel process started. EL %d\r\n", get_el()); int err = move_to_user_mode((unsigned long)&user_process); if (err < 0){ printf("Error while moving process to user mode\n\r"); } } void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); int res = copy_process(PF_KTHREAD, (unsigned long)&kernel_process, 0, 0); if (res < 0) { printf("error while starting kernel process"); return; } while (1){ schedule(); } } ================================================ FILE: exercises/lesson05/2/bl4ckout31/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson05/2/bl4ckout31/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson05/2/bl4ckout31/src/mm.S ================================================ .globl memcpy memcpy: ldr x3, [x0], #8 str x3, [x1], #8 subs x2, x2, #8 b.gt memcpy ret .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson05/2/bl4ckout31/src/mm.c ================================================ #include "mm.h" static unsigned short mem_map [ PAGING_PAGES ] = {0,}; unsigned long get_free_page() { for (int i = 0; i < PAGING_PAGES; i++){ if (mem_map[i] == 0){ mem_map[i] = 1; return LOW_MEMORY + i*PAGE_SIZE; } } return 0; } void free_page(unsigned long p){ mem_map[(p - LOW_MEMORY) / PAGE_SIZE] = 0; } ================================================ FILE: exercises/lesson05/2/bl4ckout31/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson05/2/bl4ckout31/src/sched.S ================================================ #include "sched.h" .globl cpu_switch_to cpu_switch_to: mov x10, #THREAD_CPU_CONTEXT add x8, x0, x10 mov x9, sp stp x19, x20, [x8], #16 // store callee-saved registers stp x21, x22, [x8], #16 stp x23, x24, [x8], #16 stp x25, x26, [x8], #16 stp x27, x28, [x8], #16 stp x29, x9, [x8], #16 str x30, [x8] add x8, x1, x10 ldp x19, x20, [x8], #16 // restore callee-saved registers ldp x21, x22, [x8], #16 ldp x23, x24, [x8], #16 ldp x25, x26, [x8], #16 ldp x27, x28, [x8], #16 ldp x29, x9, [x8], #16 ldr x30, [x8] mov sp, x9 ret ================================================ FILE: exercises/lesson05/2/bl4ckout31/src/sched.c ================================================ #include "sched.h" #include "irq.h" #include "printf.h" #include "fork.h" #include "utils.h" #include "mm.h" static struct task_struct init_task = INIT_TASK; struct task_struct *current = &(init_task); struct task_struct * task[NR_TASKS] = {&(init_task), }; int nr_tasks = 1; void preempt_disable(void) { current->preempt_count++; } void preempt_enable(void) { current->preempt_count--; } void _schedule(void) { preempt_disable(); int next,c; struct task_struct * p; while (1) { c = -1; next = 0; for (int i = 0; i < NR_TASKS; i++){ p = task[i]; if (p && p->state == TASK_RUNNING && p->counter > c) { c = p->counter; next = i; } } if (c) { break; } for (int i = 0; i < NR_TASKS; i++) { p = task[i]; if (p) { p->counter = (p->counter >> 1) + p->priority; } } } switch_to(task[next], next); preempt_enable(); } void schedule(void) { current->counter = 0; _schedule(); } void switch_to(struct task_struct * next, int index) { if (current == next) return; struct task_struct * prev = current; current = next; cpu_switch_to(prev, next); } void schedule_tail(void) { preempt_enable(); } void timer_tick() { --current->counter; if (current->counter>0 || current->preempt_count >0) { return; } current->counter=0; enable_irq(); _schedule(); disable_irq(); } void exit_process(){ preempt_disable(); for (int i = 0; i < NR_TASKS; i++){ if (task[i] == current) { task[i]->state = TASK_ZOMBIE; break; } } if (current->stack) { free_page(current->stack); } preempt_enable(); schedule(); } ================================================ FILE: exercises/lesson05/2/bl4ckout31/src/sys.S ================================================ #include "sys.h" .globl call_sys_write call_sys_write: mov w8, #SYS_WRITE_NUMBER svc #0 ret .globl call_sys_malloc call_sys_malloc: mov w8, #SYS_MALLOC_NUMBER svc #0 ret .globl call_sys_exit call_sys_exit: mov w8, #SYS_EXIT_NUMBER svc #0 ret .globl call_sys_clone call_sys_clone: /* Save args for the child. */ mov x10, x0 /*fn*/ mov x11, x1 /*arg*/ mov x12, x2 /*stack*/ /* Do the system call. */ mov x0, x2 /* stack */ mov x8, #SYS_CLONE_NUMBER svc 0x0 cmp x0, #0 beq thread_start ret .globl call_sys_priority call_sys_priority: mov w8, #SYS_PRIORITY_NUMBER svc #0 ret thread_start: mov x29, 0 /* Pick the function arg and execute. */ mov x0, x11 blr x10 /* We are done, pass the return value through x0. */ mov x8, #SYS_EXIT_NUMBER svc 0x0 ================================================ FILE: exercises/lesson05/2/bl4ckout31/src/sys.c ================================================ #include "fork.h" #include "printf.h" #include "utils.h" #include "sched.h" #include "mm.h" void sys_write(char * buf){ printf(buf); } int sys_clone(unsigned long stack){ return copy_process(0, 0, 0, stack); } unsigned long sys_malloc(){ unsigned long addr = get_free_page(); if (!addr) { return -1; } return addr; } void sys_exit(){ exit_process(); } void sys_priority(long priority) { current->priority = priority; } void * const sys_call_table[] = {sys_write, sys_malloc, sys_clone, sys_exit, sys_priority}; ================================================ FILE: exercises/lesson05/2/bl4ckout31/src/timer.c ================================================ #include "utils.h" #include "printf.h" #include "sched.h" #include "peripherals/timer.h" const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); timer_tick(); } ================================================ FILE: exercises/lesson05/2/bl4ckout31/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson05/2/bl4ckout31/start.sh ================================================ #!/bin/bash mount -L rpiboot /mnt cp "kernel8.img" /mnt umount /mnt ================================================ FILE: exercises/lesson05/2/xdfm1/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only -std=gnu99 ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson05/2/xdfm1/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson05/2/xdfm1/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson05/2/xdfm1/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2025 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 1923 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2022 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 288 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) // *************************************** // ESR_EL1, Exception Syndrome Register (EL1). Page 1899 of AArch64-Reference-Manual. // *************************************** #define ESR_ELx_EC_SHIFT 26 #define ESR_ELx_EC_SVC64 0x15 #endif ================================================ FILE: exercises/lesson05/2/xdfm1/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 272 // size of all saved registers #define S_X0 0 // offset of x0 register in saved stack frame #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #define SYNC_ERROR 16 #define SYSCALL_ERROR 17 #ifndef __ASSEMBLER__ void ret_from_fork(void); #endif #endif ================================================ FILE: exercises/lesson05/2/xdfm1/include/fork.h ================================================ #ifndef _FORK_H #define _FORK_H #include "sched.h" /* * PSR bits */ #define PSR_MODE_EL0t 0x00000000 #define PSR_MODE_EL1t 0x00000004 #define PSR_MODE_EL1h 0x00000005 #define PSR_MODE_EL2t 0x00000008 #define PSR_MODE_EL2h 0x00000009 #define PSR_MODE_EL3t 0x0000000c #define PSR_MODE_EL3h 0x0000000d int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg, unsigned long stack); int move_to_user_mode(unsigned long pc); struct pt_regs * task_pt_regs(struct task_struct *tsk); struct pt_regs { unsigned long regs[31]; unsigned long sp; unsigned long pc; unsigned long pstate; }; #endif ================================================ FILE: exercises/lesson05/2/xdfm1/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson05/2/xdfm1/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson05/2/xdfm1/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #include "peripherals/base.h" #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #define HIGH_MEMORY PBASE #define PAGING_MEMORY (HIGH_MEMORY - LOW_MEMORY) #define PAGING_PAGES (PAGING_MEMORY/PAGE_SIZE) #ifndef __ASSEMBLER__ unsigned long get_free_page(); void free_page(unsigned long p); void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson05/2/xdfm1/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson05/2/xdfm1/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson05/2/xdfm1/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson05/2/xdfm1/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson05/2/xdfm1/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson05/2/xdfm1/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson05/2/xdfm1/include/sched.h ================================================ #ifndef _SCHED_H #define _SCHED_H #define THREAD_CPU_CONTEXT 0 // offset of cpu_context in task_struct #ifndef __ASSEMBLER__ #define THREAD_SIZE 4096 #define NR_TASKS 64 #define FIRST_TASK task[0] #define LAST_TASK task[NR_TASKS-1] #define TASK_RUNNING 0 #define TASK_ZOMBIE 1 #define PF_KTHREAD 0x00000002 extern struct task_struct *current; extern struct task_struct * task[NR_TASKS]; extern int nr_tasks; struct cpu_context { unsigned long x19; unsigned long x20; unsigned long x21; unsigned long x22; unsigned long x23; unsigned long x24; unsigned long x25; unsigned long x26; unsigned long x27; unsigned long x28; unsigned long fp; unsigned long sp; unsigned long pc; }; struct task_struct { struct cpu_context cpu_context; long state; long counter; long priority; long preempt_count; unsigned long stack; unsigned long flags; }; extern void sched_init(void); extern void schedule(void); extern void timer_tick(void); extern void preempt_disable(void); extern void preempt_enable(void); extern void switch_to(struct task_struct * next, int index); extern void cpu_switch_to(struct task_struct* prev, struct task_struct* next); extern void exit_process(void); #define INIT_TASK \ /*cpu_context*/ { {0,0,0,0,0,0,0,0,0,0,0,0,0}, \ /* state etc */ 0,0,1, 0, 0, PF_KTHREAD \ } #endif #endif ================================================ FILE: exercises/lesson05/2/xdfm1/include/sys.h ================================================ #ifndef _SYS_H #define _SYS_H #define __NR_syscalls 5 #define SYS_WRITE_NUMBER 0 // syscal numbers #define SYS_MALLOC_NUMBER 1 #define SYS_CLONE_NUMBER 2 #define SYS_EXIT_NUMBER 3 #define SYS_PRIORITY_NUMBER 4 #ifndef __ASSEMBLER__ void sys_write(char * buf); int sys_fork(); void call_sys_write(char * buf); int call_sys_clone(unsigned long fn, unsigned long arg, unsigned long stack); unsigned long call_sys_malloc(); void call_sys_exit(); void call_sys_priority(int pid, unsigned long priority); #endif #endif /*_SYS_H */ ================================================ FILE: exercises/lesson05/2/xdfm1/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson05/2/xdfm1/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson05/2/xdfm1/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson05/2/xdfm1/src/config.txt ================================================ arm_control=0x200 kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson05/2/xdfm1/src/entry.S ================================================ #include "arm/sysregs.h" #include "entry.h" #include "sys.h" .macro handle_invalid_entry el, type kernel_entry \el mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry, el sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] .if \el == 0 mrs x21, sp_el0 .else add x21, sp, #S_FRAME_SIZE .endif /* \el == 0 */ mrs x22, elr_el1 mrs x23, spsr_el1 stp x30, x21, [sp, #16 * 15] stp x22, x23, [sp, #16 * 16] .endm .macro kernel_exit, el ldp x22, x23, [sp, #16 * 16] ldp x30, x21, [sp, #16 * 15] .if \el == 0 msr sp_el0, x21 .endif /* \el == 0 */ msr elr_el1, x22 msr spsr_el1, x23 ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry el0_sync // Synchronous 64-bit EL0 ventry el0_irq // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry 1, SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry 1, IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry 1, FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry 1, ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry 1, SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry 1, FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry 1, ERROR_INVALID_EL1h fiq_invalid_el0_64: handle_invalid_entry 0, FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry 0, ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry 0, SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry 0, IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry 0, FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry 0, ERROR_INVALID_EL0_32 el1_irq: kernel_entry 1 bl handle_irq kernel_exit 1 el0_irq: kernel_entry 0 bl handle_irq kernel_exit 0 el0_sync: kernel_entry 0 mrs x25, esr_el1 // read the syndrome register lsr x24, x25, #ESR_ELx_EC_SHIFT // exception class cmp x24, #ESR_ELx_EC_SVC64 // SVC in 64-bit state b.eq el0_svc handle_invalid_entry 0, SYNC_ERROR sc_nr .req x25 // number of system calls scno .req x26 // syscall number stbl .req x27 // syscall table pointer el0_svc: adr stbl, sys_call_table // load syscall table pointer uxtw scno, w8 // syscall number in w8 mov sc_nr, #__NR_syscalls bl enable_irq cmp scno, sc_nr // check upper syscall limit b.hs ni_sys ldr x16, [stbl, scno, lsl #3] // address in the syscall table blr x16 // call sys_* routine b ret_from_syscall ni_sys: handle_invalid_entry 0, SYSCALL_ERROR ret_from_syscall: bl disable_irq str x0, [sp, #S_X0] // returned x0 kernel_exit 0 .globl ret_from_fork ret_from_fork: bl schedule_tail cbz x19, ret_to_user // not a kernel thread mov x0, x20 blr x19 ret_to_user: bl disable_irq kernel_exit 0 .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson05/2/xdfm1/src/fork.c ================================================ #include "mm.h" #include "sched.h" #include "printf.h" #include "fork.h" #include "entry.h" #include "utils.h" int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg, unsigned long stack) { preempt_disable(); struct task_struct *p; p = (struct task_struct *) get_free_page(); if (!p) { return -1; } struct pt_regs *childregs = task_pt_regs(p); memzero((unsigned long)childregs, sizeof(struct pt_regs)); memzero((unsigned long)&p->cpu_context, sizeof(struct cpu_context)); if (clone_flags & PF_KTHREAD) { p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; } else { struct pt_regs * cur_regs = task_pt_regs(current); *cur_regs = *childregs; childregs->regs[0] = 0; childregs->sp = stack + PAGE_SIZE; p->stack = stack; } p->flags = clone_flags; p->priority = current->priority; p->state = TASK_RUNNING; p->counter = p->priority; p->preempt_count = 1; //disable preemtion untill schedule_tail p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)childregs; int pid = nr_tasks++; task[pid] = p; preempt_enable(); return pid; } int move_to_user_mode(unsigned long pc) { struct pt_regs *regs = task_pt_regs(current); memzero((unsigned long)regs, sizeof(*regs)); regs->pc = pc; regs->pstate = PSR_MODE_EL0t; unsigned long stack = get_free_page(); //alocate new user stack if (!stack) { return -1; } regs->sp = stack + PAGE_SIZE; current->stack = stack; return 0; } struct pt_regs * task_pt_regs(struct task_struct *tsk){ unsigned long p = (unsigned long)tsk + THREAD_SIZE - sizeof(struct pt_regs); return (struct pt_regs *)p; } ================================================ FILE: exercises/lesson05/2/xdfm1/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson05/2/xdfm1/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "timer.h" #include "entry.h" #include "peripherals/irq.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32" }; void enable_interrupt_controller() { put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { unsigned int irq = get32(IRQ_PENDING_1); switch (irq) { case (SYSTEM_TIMER_IRQ_1): handle_timer_irq(); break; default: printf("Inknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson05/2/xdfm1/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "timer.h" #include "irq.h" #include "sched.h" #include "fork.h" #include "mini_uart.h" #include "sys.h" void user_process1(char *array) { char buf[2] = {0}; while (1){ for (int i = 0; i < 5; i++){ buf[0] = array[i]; call_sys_write(buf); delay(100000); } } } void user_process(){ char buf[30] = {0}; tfp_sprintf(buf, "User process started\n\r"); call_sys_write(buf); unsigned long stack = call_sys_malloc(); if (stack < 0) { printf("Error while allocating stack for process 1\n\r"); return; } int err = call_sys_clone((unsigned long)&user_process1, (unsigned long)"12345", stack); if (err < 0){ printf("Error while clonning process 1\n\r"); return; } stack = call_sys_malloc(); if (stack < 0) { printf("Error while allocating stack for process 1\n\r"); return; } err = call_sys_clone((unsigned long)&user_process1, (unsigned long)"abcd", stack); if (err < 0){ printf("Error while clonning process 2\n\r"); return; } // err is pid of process2 call_sys_priority(err, 0xa); call_sys_exit(); } void kernel_process(){ printf("Kernel process started. EL %d\r\n", get_el()); int err = move_to_user_mode((unsigned long)&user_process); if (err < 0){ printf("Error while moving process to user mode\n\r"); } } void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); int res = copy_process(PF_KTHREAD, (unsigned long)&kernel_process, 0, 0); if (res < 0) { printf("error while starting kernel process"); return; } while (1){ schedule(); } } ================================================ FILE: exercises/lesson05/2/xdfm1/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson05/2/xdfm1/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio 15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baund rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson05/2/xdfm1/src/mm.S ================================================ .globl memcpy memcpy: ldr x3, [x0], #8 str x3, [x1], #8 subs x2, x2, #8 b.gt memcpy ret .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson05/2/xdfm1/src/mm.c ================================================ #include "mm.h" static unsigned short mem_map [ PAGING_PAGES ] = {0,}; unsigned long get_free_page() { for (int i = 0; i < PAGING_PAGES; i++){ if (mem_map[i] == 0){ mem_map[i] = 1; return LOW_MEMORY + i*PAGE_SIZE; } } return 0; } void free_page(unsigned long p){ mem_map[p / PAGE_SIZE] = 0; } ================================================ FILE: exercises/lesson05/2/xdfm1/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson05/2/xdfm1/src/sched.S ================================================ #include "sched.h" .globl cpu_switch_to cpu_switch_to: mov x10, #THREAD_CPU_CONTEXT add x8, x0, x10 mov x9, sp stp x19, x20, [x8], #16 // store callee-saved registers stp x21, x22, [x8], #16 stp x23, x24, [x8], #16 stp x25, x26, [x8], #16 stp x27, x28, [x8], #16 stp x29, x9, [x8], #16 str x30, [x8] add x8, x1, x10 ldp x19, x20, [x8], #16 // restore callee-saved registers ldp x21, x22, [x8], #16 ldp x23, x24, [x8], #16 ldp x25, x26, [x8], #16 ldp x27, x28, [x8], #16 ldp x29, x9, [x8], #16 ldr x30, [x8] mov sp, x9 ret ================================================ FILE: exercises/lesson05/2/xdfm1/src/sched.c ================================================ #include "sched.h" #include "irq.h" #include "printf.h" #include "fork.h" #include "utils.h" #include "mm.h" static struct task_struct init_task = INIT_TASK; struct task_struct *current = &(init_task); struct task_struct * task[NR_TASKS] = {&(init_task), }; int nr_tasks = 1; void preempt_disable(void) { current->preempt_count++; } void preempt_enable(void) { current->preempt_count--; } void _schedule(void) { preempt_disable(); int next,c; struct task_struct * p; while (1) { c = -1; next = 0; for (int i = 0; i < NR_TASKS; i++){ p = task[i]; if (p && p->state == TASK_RUNNING && p->counter > c) { c = p->counter; next = i; } } if (c) { break; } for (int i = 0; i < NR_TASKS; i++) { p = task[i]; if (p) { p->counter = (p->counter >> 1) + p->priority; } } } switch_to(task[next], next); preempt_enable(); } void schedule(void) { current->counter = 0; _schedule(); } void switch_to(struct task_struct * next, int index) { if (current == next) return; struct task_struct * prev = current; current = next; cpu_switch_to(prev, next); } void schedule_tail(void) { preempt_enable(); } void timer_tick() { --current->counter; if (current->counter>0 || current->preempt_count >0) { return; } current->counter=0; enable_irq(); _schedule(); disable_irq(); } void exit_process(){ preempt_disable(); for (int i = 0; i < NR_TASKS; i++){ if (task[i] == current) { task[i]->state = TASK_ZOMBIE; break; } } if (current->stack) { free_page(current->stack); } preempt_enable(); schedule(); } ================================================ FILE: exercises/lesson05/2/xdfm1/src/sys.S ================================================ #include "sys.h" .globl call_sys_write call_sys_write: mov w8, #SYS_WRITE_NUMBER svc #0 ret .globl call_sys_malloc call_sys_malloc: mov w8, #SYS_MALLOC_NUMBER svc #0 ret .globl call_sys_exit call_sys_exit: mov w8, #SYS_EXIT_NUMBER svc #0 ret .globl call_sys_clone call_sys_clone: /* Save args for the child. */ mov x10, x0 /*fn*/ mov x11, x1 /*arg*/ mov x12, x2 /*stack*/ /* Do the system call. */ mov x0, x2 /* stack */ mov x8, #SYS_CLONE_NUMBER svc 0x0 cmp x0, #0 beq thread_start ret .global call_sys_priority call_sys_priority: mov w8, #SYS_PRIORITY_NUMBER svc #0 ret thread_start: mov x29, 0 /* Pick the function arg and execute. */ mov x0, x11 blr x10 /* We are done, pass the return value through x0. */ mov x8, #SYS_EXIT_NUMBER svc 0x0 ================================================ FILE: exercises/lesson05/2/xdfm1/src/sys.c ================================================ #include "fork.h" #include "printf.h" #include "utils.h" #include "sched.h" #include "mm.h" void sys_write(char * buf){ printf(buf); } int sys_clone(unsigned long stack){ return copy_process(0, 0, 0, stack); } unsigned long sys_malloc(){ unsigned long addr = get_free_page(); if (!addr) { return -1; } return addr; } void sys_exit(){ exit_process(); } void sys_priority(int pid, unsigned long priority) { if(pid >= NR_TASKS) { return; } struct task_struct *p = task[pid]; p->priority = priority; schedule(); } void * const sys_call_table[] = {sys_write, sys_malloc, sys_clone, sys_exit, sys_priority}; ================================================ FILE: exercises/lesson05/2/xdfm1/src/timer.c ================================================ #include "utils.h" #include "printf.h" #include "sched.h" #include "peripherals/timer.h" const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); timer_tick(); } ================================================ FILE: exercises/lesson05/2/xdfm1/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson05/3/avenito/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson05/3/avenito/README.md ================================================ ================================================ FILE: exercises/lesson05/3/avenito/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson05/3/avenito/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson05/3/avenito/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) // *************************************** // ESR_EL1, Exception Syndrome Register (EL1). Page 2431 of AArch64-Reference-Manual. // *************************************** #define ESR_ELx_EC_SHIFT 26 #define ESR_ELx_EC_SVC64 0x15 #endif ================================================ FILE: exercises/lesson05/3/avenito/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 272 // size of all saved registers #define S_X0 0 // offset of x0 register in saved stack frame #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #define SYNC_ERROR 16 #define SYSCALL_ERROR 17 #ifndef __ASSEMBLER__ void ret_from_fork(void); #endif #endif ================================================ FILE: exercises/lesson05/3/avenito/include/fork.h ================================================ #ifndef _FORK_H #define _FORK_H #include "sched.h" /* * PSR bits */ #define PSR_MODE_EL0t 0x00000000 #define PSR_MODE_EL1t 0x00000004 #define PSR_MODE_EL1h 0x00000005 #define PSR_MODE_EL2t 0x00000008 #define PSR_MODE_EL2h 0x00000009 #define PSR_MODE_EL3t 0x0000000c #define PSR_MODE_EL3h 0x0000000d int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg, unsigned long stack); int move_to_user_mode(unsigned long pc); struct pt_regs * task_pt_regs(struct task_struct *tsk); struct pt_regs { unsigned long regs[31]; unsigned long sp; unsigned long pc; unsigned long pstate; }; #endif ================================================ FILE: exercises/lesson05/3/avenito/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson05/3/avenito/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson05/3/avenito/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #include "peripherals/base.h" #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #define HIGH_MEMORY PBASE #define PAGING_MEMORY (HIGH_MEMORY - LOW_MEMORY) #define PAGING_PAGES (PAGING_MEMORY/PAGE_SIZE) #ifndef __ASSEMBLER__ unsigned long get_free_page(); void free_page(unsigned long p); void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson05/3/avenito/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson05/3/avenito/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson05/3/avenito/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson05/3/avenito/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson05/3/avenito/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson05/3/avenito/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson05/3/avenito/include/sched.h ================================================ #ifndef _SCHED_H #define _SCHED_H #define THREAD_CPU_CONTEXT 0 // offset of cpu_context in task_struct #ifndef __ASSEMBLER__ #define THREAD_SIZE 4096 #define NR_TASKS 64 #define FIRST_TASK task[0] #define LAST_TASK task[NR_TASKS-1] #define TASK_RUNNING 0 #define TASK_ZOMBIE 1 #define PF_KTHREAD 0x00000002 extern struct task_struct *current; extern struct task_struct * task[NR_TASKS]; extern int nr_tasks; struct cpu_context { unsigned long x19; unsigned long x20; unsigned long x21; unsigned long x22; unsigned long x23; unsigned long x24; unsigned long x25; unsigned long x26; unsigned long x27; unsigned long x28; unsigned long fp; unsigned long sp; unsigned long pc; }; struct task_struct { struct cpu_context cpu_context; long state; long counter; long priority; long preempt_count; unsigned long stack; unsigned long flags; }; extern void sched_init(void); extern void schedule(void); extern void timer_tick(void); extern void preempt_disable(void); extern void preempt_enable(void); extern void switch_to(struct task_struct * next, int index); extern void cpu_switch_to(struct task_struct* prev, struct task_struct* next); extern void exit_process(void); #define INIT_TASK \ /*cpu_context*/ { {0,0,0,0,0,0,0,0,0,0,0,0,0}, \ /* state etc */ 0,0,1, 0, 0, PF_KTHREAD \ } #endif #endif ================================================ FILE: exercises/lesson05/3/avenito/include/sys.h ================================================ #ifndef _SYS_H #define _SYS_H #define __NR_syscalls 4 #define SYS_WRITE_NUMBER 0 // syscal numbers #define SYS_MALLOC_NUMBER 1 #define SYS_CLONE_NUMBER 2 #define SYS_EXIT_NUMBER 3 #ifndef __ASSEMBLER__ void sys_write(char * buf); int sys_fork(); void call_sys_write(char * buf); int call_sys_clone(unsigned long fn, unsigned long arg, unsigned long stack); unsigned long call_sys_malloc(); void call_sys_exit(); #endif #endif /*_SYS_H */ ================================================ FILE: exercises/lesson05/3/avenito/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson05/3/avenito/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson05/3/avenito/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson05/3/avenito/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson05/3/avenito/src/entry.S ================================================ #include "arm/sysregs.h" #include "entry.h" #include "sys.h" .macro handle_invalid_entry el, type kernel_entry \el mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry, el sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] .if \el == 0 mrs x21, sp_el0 .else add x21, sp, #S_FRAME_SIZE .endif /* \el == 0 */ mrs x22, elr_el1 mrs x23, spsr_el1 stp x30, x21, [sp, #16 * 15] stp x22, x23, [sp, #16 * 16] .endm .macro kernel_exit, el ldp x22, x23, [sp, #16 * 16] ldp x30, x21, [sp, #16 * 15] .if \el == 0 msr sp_el0, x21 .endif /* \el == 0 */ msr elr_el1, x22 msr spsr_el1, x23 ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry el0_sync // Synchronous 64-bit EL0 ventry el0_irq // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry 1, SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry 1, IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry 1, FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry 1, ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry 1, SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry 1, FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry 1, ERROR_INVALID_EL1h fiq_invalid_el0_64: handle_invalid_entry 0, FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry 0, ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry 0, SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry 0, IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry 0, FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry 0, ERROR_INVALID_EL0_32 el1_irq: kernel_entry 1 bl handle_irq kernel_exit 1 el0_irq: kernel_entry 0 bl handle_irq kernel_exit 0 el0_sync: kernel_entry 0 mrs x25, esr_el1 // read the syndrome register lsr x24, x25, #ESR_ELx_EC_SHIFT // exception class cmp x24, #ESR_ELx_EC_SVC64 // SVC in 64-bit state b.eq el0_svc handle_invalid_entry 0, SYNC_ERROR sc_nr .req x25 // number of system calls scno .req x26 // syscall number stbl .req x27 // syscall table pointer el0_svc: adr stbl, sys_call_table // load syscall table pointer uxtw scno, w8 // syscall number in w8 mov sc_nr, #__NR_syscalls bl enable_irq cmp scno, sc_nr // check upper syscall limit b.hs ni_sys ldr x16, [stbl, scno, lsl #3] // address in the syscall table blr x16 // call sys_* routine b ret_from_syscall ni_sys: handle_invalid_entry 0, SYSCALL_ERROR ret_from_syscall: bl disable_irq str x0, [sp, #S_X0] // returned x0 kernel_exit 0 .globl ret_from_fork ret_from_fork: bl schedule_tail cbz x19, ret_to_user // not a kernel thread mov x0, x20 blr x19 ret_to_user: bl disable_irq kernel_exit 0 .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson05/3/avenito/src/fork.c ================================================ #include "mm.h" #include "sched.h" #include "printf.h" #include "fork.h" #include "entry.h" #include "utils.h" int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg, unsigned long stack) { preempt_disable(); struct task_struct *p; p = (struct task_struct *) get_free_page(); if (!p) { return -1; } struct pt_regs *childregs = task_pt_regs(p); memzero((unsigned long)childregs, sizeof(struct pt_regs)); memzero((unsigned long)&p->cpu_context, sizeof(struct cpu_context)); if (clone_flags & PF_KTHREAD) { p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; } else { struct pt_regs * cur_regs = task_pt_regs(current); *cur_regs = *childregs; childregs->regs[0] = 0; childregs->sp = stack + PAGE_SIZE; p->stack = stack; } p->flags = clone_flags; p->priority = current->priority; p->state = TASK_RUNNING; p->counter = p->priority; p->preempt_count = 1; //disable preemtion until schedule_tail p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)childregs; int pid = nr_tasks++; task[pid] = p; preempt_enable(); return pid; } int move_to_user_mode(unsigned long pc) { struct pt_regs *regs = task_pt_regs(current); memzero((unsigned long)regs, sizeof(*regs)); regs->pc = pc; regs->pstate = PSR_MODE_EL0t; unsigned long stack = get_free_page(); //alocate new user stack if (!stack) { return -1; } regs->sp = stack + PAGE_SIZE; current->stack = stack; return 0; } struct pt_regs * task_pt_regs(struct task_struct *tsk){ unsigned long p = (unsigned long)tsk + THREAD_SIZE - sizeof(struct pt_regs); return (struct pt_regs *)p; } ================================================ FILE: exercises/lesson05/3/avenito/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson05/3/avenito/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "timer.h" #include "entry.h" #include "peripherals/irq.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32", "SYNC_ERROR", "SYSCALL_ERROR" }; void enable_interrupt_controller() { put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { unsigned int irq = get32(IRQ_PENDING_1); switch (irq) { case (SYSTEM_TIMER_IRQ_1): handle_timer_irq(); break; default: printf("Inknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson05/3/avenito/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "timer.h" #include "irq.h" #include "sched.h" #include "fork.h" #include "mini_uart.h" #include "sys.h" void user_process1(char *array) { char buf[2] = {0}; while (1){ for (int i = 0; i < 5; i++){ buf[0] = array[i]; call_sys_write(buf); delay(100000); } } } void user_process(){ char buf[30] = {0}; tfp_sprintf(buf, "User process started\n\r"); call_sys_write(buf); unsigned long stack = call_sys_malloc(); if (stack < 0) { printf("Error while allocating stack for process 1\n\r"); return; } int err = call_sys_clone((unsigned long)&user_process1, (unsigned long)"12345", stack); if (err < 0){ printf("Error while clonning process 1\n\r"); return; } stack = call_sys_malloc(); if (stack < 0) { printf("Error while allocating stack for process 1\n\r"); return; } err = call_sys_clone((unsigned long)&user_process1, (unsigned long)"abcd", stack); if (err < 0){ printf("Error while clonning process 2\n\r"); return; } call_sys_exit(); } void kernel_process(){ printf("Kernel process started. EL %d\r\n", get_el()); int err = move_to_user_mode((unsigned long)&user_process); if (err < 0){ printf("Error while moving process to user mode\n\r"); } } void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); int res = copy_process(PF_KTHREAD, (unsigned long)&kernel_process, 0, 0); if (res < 0) { printf("error while starting kernel process"); return; } while (1){ schedule(); } } ================================================ FILE: exercises/lesson05/3/avenito/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson05/3/avenito/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson05/3/avenito/src/mm.S ================================================ .globl memcpy memcpy: ldr x3, [x0], #8 str x3, [x1], #8 subs x2, x2, #8 b.gt memcpy ret .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson05/3/avenito/src/mm.c ================================================ #include "mm.h" static unsigned short mem_map [ PAGING_PAGES ] = {0,}; unsigned long get_free_page() { for (int i = 0; i < PAGING_PAGES; i++){ if (mem_map[i] == 0){ mem_map[i] = 1; return LOW_MEMORY + i*PAGE_SIZE; } } return 0; } void free_page(unsigned long p){ mem_map[(p - LOW_MEMORY) / PAGE_SIZE] = 0; } ================================================ FILE: exercises/lesson05/3/avenito/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson05/3/avenito/src/sched.S ================================================ #include "sched.h" .globl cpu_switch_to cpu_switch_to: mov x10, #THREAD_CPU_CONTEXT add x8, x0, x10 mov x9, sp stp x19, x20, [x8], #16 // store callee-saved registers stp x21, x22, [x8], #16 stp x23, x24, [x8], #16 stp x25, x26, [x8], #16 stp x27, x28, [x8], #16 stp x29, x9, [x8], #16 str x30, [x8] add x8, x1, x10 ldp x19, x20, [x8], #16 // restore callee-saved registers ldp x21, x22, [x8], #16 ldp x23, x24, [x8], #16 ldp x25, x26, [x8], #16 ldp x27, x28, [x8], #16 ldp x29, x9, [x8], #16 ldr x30, [x8] mov sp, x9 ret ================================================ FILE: exercises/lesson05/3/avenito/src/sched.c ================================================ #include "sched.h" #include "irq.h" #include "printf.h" #include "fork.h" #include "utils.h" #include "mm.h" static struct task_struct init_task = INIT_TASK; struct task_struct *current = &(init_task); struct task_struct * task[NR_TASKS] = {&(init_task), }; int nr_tasks = 1; void preempt_disable(void) { current->preempt_count++; } void preempt_enable(void) { current->preempt_count--; } void _schedule(void) { preempt_disable(); int next,c; struct task_struct * p; while (1) { c = -1; next = 0; for (int i = 0; i < NR_TASKS; i++){ p = task[i]; if (p && p->state == TASK_RUNNING && p->counter > c) { c = p->counter; next = i; } } if (c) { break; } for (int i = 0; i < NR_TASKS; i++) { p = task[i]; if (p) { p->counter = (p->counter >> 1) + p->priority; } } } switch_to(task[next], next); preempt_enable(); } void schedule(void) { current->counter = 0; _schedule(); } void switch_to(struct task_struct * next, int index) { if (current == next) return; struct task_struct * prev = current; current = next; cpu_switch_to(prev, next); } void schedule_tail(void) { preempt_enable(); } void timer_tick() { --current->counter; if (current->counter>0 || current->preempt_count >0) { return; } current->counter=0; enable_irq(); _schedule(); disable_irq(); } void exit_process(){ preempt_disable(); for (int i = 0; i < NR_TASKS; i++){ if (task[i] == current) { task[i]->state = TASK_ZOMBIE; break; } } if (current->stack) { free_page(current->stack); } preempt_enable(); schedule(); } ================================================ FILE: exercises/lesson05/3/avenito/src/sys.S ================================================ #include "sys.h" .globl call_sys_write call_sys_write: mov w8, #SYS_WRITE_NUMBER svc #0 ret .globl call_sys_malloc call_sys_malloc: mov w8, #SYS_MALLOC_NUMBER svc #0 ret .globl call_sys_exit call_sys_exit: mov w8, #SYS_EXIT_NUMBER svc #0 ret .globl call_sys_clone call_sys_clone: /* Save args for the child. */ mov x10, x0 /*fn*/ mov x11, x1 /*arg*/ mov x12, x2 /*stack*/ /* Do the system call. */ mov x0, x2 /* stack */ mov x8, #SYS_CLONE_NUMBER svc 0x0 cmp x0, #0 beq thread_start ret thread_start: mov x29, 0 /* Pick the function arg and execute. */ mov x0, x11 blr x10 /* We are done, pass the return value through x0. */ mov x8, #SYS_EXIT_NUMBER svc 0x0 ================================================ FILE: exercises/lesson05/3/avenito/src/sys.c ================================================ #include "fork.h" #include "printf.h" #include "utils.h" #include "sched.h" #include "mm.h" void sys_write(char * buf){ printf(buf); } int sys_clone(unsigned long stack){ return copy_process(0, 0, 0, stack); } unsigned long sys_malloc(){ unsigned long addr = get_free_page(); if (!addr) { return -1; } return addr; } void sys_exit(){ exit_process(); } void * const sys_call_table[] = {sys_write, sys_malloc, sys_clone, sys_exit}; ================================================ FILE: exercises/lesson05/3/avenito/src/timer.c ================================================ #include "utils.h" #include "printf.h" #include "sched.h" #include "peripherals/timer.h" const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); timer_tick(); } ================================================ FILE: exercises/lesson05/3/avenito/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson05/3/bl4ckout31/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson05/3/bl4ckout31/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson05/3/bl4ckout31/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson05/3/bl4ckout31/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) // *************************************** // ESR_EL1, Exception Syndrome Register (EL1). Page 2431 of AArch64-Reference-Manual. // *************************************** #define ESR_ELx_EC_SHIFT 26 #define ESR_ELx_EC_SVC64 0x15 #endif ================================================ FILE: exercises/lesson05/3/bl4ckout31/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 272 // size of all saved registers #define S_X0 0 // offset of x0 register in saved stack frame #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #define SYNC_ERROR 16 #define SYSCALL_ERROR 17 #ifndef __ASSEMBLER__ void ret_from_fork(void); #endif #endif ================================================ FILE: exercises/lesson05/3/bl4ckout31/include/fork.h ================================================ #ifndef _FORK_H #define _FORK_H #include "sched.h" /* * PSR bits */ #define PSR_MODE_EL0t 0x00000000 #define PSR_MODE_EL1t 0x00000004 #define PSR_MODE_EL1h 0x00000005 #define PSR_MODE_EL2t 0x00000008 #define PSR_MODE_EL2h 0x00000009 #define PSR_MODE_EL3t 0x0000000c #define PSR_MODE_EL3h 0x0000000d int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg, unsigned long stack); int move_to_user_mode(unsigned long pc); struct pt_regs * task_pt_regs(struct task_struct *tsk); struct pt_regs { unsigned long regs[31]; unsigned long sp; unsigned long pc; unsigned long pstate; }; #endif ================================================ FILE: exercises/lesson05/3/bl4ckout31/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson05/3/bl4ckout31/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson05/3/bl4ckout31/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #include "peripherals/base.h" #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #define HIGH_MEMORY PBASE #define PAGING_MEMORY (HIGH_MEMORY - LOW_MEMORY) #define PAGING_PAGES (PAGING_MEMORY/PAGE_SIZE) #ifndef __ASSEMBLER__ unsigned long get_free_page(); void free_page(unsigned long p); void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson05/3/bl4ckout31/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #define LPBASE 0x40000000 #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson05/3/bl4ckout31/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson05/3/bl4ckout31/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) // See BCM2836 ARM-local peripherals at // https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2836/QA7_rev3.4.pdf #define TIMER_INT_CTRL_0 (0x40000040) #define INT_SOURCE_0 (LPBASE+0x60) #define TIMER_INT_CTRL_0_VALUE (1 << 1) #define GENERIC_TIMER_INTERRUPT (1 << 1) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson05/3/bl4ckout31/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson05/3/bl4ckout31/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson05/3/bl4ckout31/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson05/3/bl4ckout31/include/sched.h ================================================ #ifndef _SCHED_H #define _SCHED_H #define THREAD_CPU_CONTEXT 0 // offset of cpu_context in task_struct #ifndef __ASSEMBLER__ #define THREAD_SIZE 4096 #define NR_TASKS 64 #define FIRST_TASK task[0] #define LAST_TASK task[NR_TASKS-1] #define TASK_RUNNING 0 #define TASK_ZOMBIE 1 #define PF_KTHREAD 0x00000002 extern struct task_struct *current; extern struct task_struct * task[NR_TASKS]; extern int nr_tasks; struct cpu_context { unsigned long x19; unsigned long x20; unsigned long x21; unsigned long x22; unsigned long x23; unsigned long x24; unsigned long x25; unsigned long x26; unsigned long x27; unsigned long x28; unsigned long fp; unsigned long sp; unsigned long pc; }; struct task_struct { struct cpu_context cpu_context; long state; long counter; long priority; long preempt_count; unsigned long stack; unsigned long flags; }; extern void sched_init(void); extern void schedule(void); extern void timer_tick(void); extern void preempt_disable(void); extern void preempt_enable(void); extern void switch_to(struct task_struct * next, int index); extern void cpu_switch_to(struct task_struct* prev, struct task_struct* next); extern void exit_process(void); #define INIT_TASK \ /*cpu_context*/ { {0,0,0,0,0,0,0,0,0,0,0,0,0}, \ /* state etc */ 0,0,1, 0, 0, PF_KTHREAD \ } #endif #endif ================================================ FILE: exercises/lesson05/3/bl4ckout31/include/sys.h ================================================ #ifndef _SYS_H #define _SYS_H #define __NR_syscalls 4 #define SYS_WRITE_NUMBER 0 // syscal numbers #define SYS_MALLOC_NUMBER 1 #define SYS_CLONE_NUMBER 2 #define SYS_EXIT_NUMBER 3 #ifndef __ASSEMBLER__ void sys_write(char * buf); int sys_fork(); void call_sys_write(char * buf); int call_sys_clone(unsigned long fn, unsigned long arg, unsigned long stack); unsigned long call_sys_malloc(); void call_sys_exit(); #endif #endif /*_SYS_H */ ================================================ FILE: exercises/lesson05/3/bl4ckout31/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); void generic_timer_init ( void ); void handle_generic_timer_irq ( void ); extern void gen_timer_init(); extern void gen_timer_reset(); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson05/3/bl4ckout31/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson05/3/bl4ckout31/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SPSR_VALUE msr spsr_el2, x0 adr x0, el1_entry msr elr_el2, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: exercises/lesson05/3/bl4ckout31/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson05/3/bl4ckout31/src/entry.S ================================================ #include "arm/sysregs.h" #include "entry.h" #include "sys.h" .macro handle_invalid_entry el, type kernel_entry \el mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry, el sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] .if \el == 0 mrs x21, sp_el0 .else add x21, sp, #S_FRAME_SIZE .endif /* \el == 0 */ mrs x22, elr_el1 mrs x23, spsr_el1 stp x30, x21, [sp, #16 * 15] stp x22, x23, [sp, #16 * 16] .endm .macro kernel_exit, el ldp x22, x23, [sp, #16 * 16] ldp x30, x21, [sp, #16 * 15] .if \el == 0 msr sp_el0, x21 .endif /* \el == 0 */ msr elr_el1, x22 msr spsr_el1, x23 ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry el0_sync // Synchronous 64-bit EL0 ventry el0_irq // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry 1, SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry 1, IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry 1, FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry 1, ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry 1, SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry 1, FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry 1, ERROR_INVALID_EL1h fiq_invalid_el0_64: handle_invalid_entry 0, FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry 0, ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry 0, SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry 0, IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry 0, FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry 0, ERROR_INVALID_EL0_32 el1_irq: kernel_entry 1 bl handle_irq kernel_exit 1 el0_irq: kernel_entry 0 bl handle_irq kernel_exit 0 el0_sync: kernel_entry 0 mrs x25, esr_el1 // read the syndrome register lsr x24, x25, #ESR_ELx_EC_SHIFT // exception class cmp x24, #ESR_ELx_EC_SVC64 // SVC in 64-bit state b.eq el0_svc handle_invalid_entry 0, SYNC_ERROR sc_nr .req x25 // number of system calls scno .req x26 // syscall number stbl .req x27 // syscall table pointer el0_svc: adr stbl, sys_call_table // load syscall table pointer uxtw scno, w8 // syscall number in w8 mov sc_nr, #__NR_syscalls bl enable_irq cmp scno, sc_nr // check upper syscall limit b.hs ni_sys ldr x16, [stbl, scno, lsl #3] // address in the syscall table blr x16 // call sys_* routine b ret_from_syscall ni_sys: handle_invalid_entry 0, SYSCALL_ERROR ret_from_syscall: bl disable_irq str x0, [sp, #S_X0] // returned x0 kernel_exit 0 .globl ret_from_fork ret_from_fork: bl schedule_tail cbz x19, ret_to_user // not a kernel thread mov x0, x20 blr x19 ret_to_user: bl disable_irq kernel_exit 0 .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson05/3/bl4ckout31/src/fork.c ================================================ #include "mm.h" #include "sched.h" #include "printf.h" #include "fork.h" #include "entry.h" #include "utils.h" int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg, unsigned long stack) { preempt_disable(); struct task_struct *p; p = (struct task_struct *) get_free_page(); if (!p) { return -1; } struct pt_regs *childregs = task_pt_regs(p); memzero((unsigned long)childregs, sizeof(struct pt_regs)); memzero((unsigned long)&p->cpu_context, sizeof(struct cpu_context)); if (clone_flags & PF_KTHREAD) { p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; } else { struct pt_regs * cur_regs = task_pt_regs(current); *cur_regs = *childregs; childregs->regs[0] = 0; childregs->sp = stack + PAGE_SIZE; p->stack = stack; } p->flags = clone_flags; p->priority = current->priority; p->state = TASK_RUNNING; p->counter = p->priority; p->preempt_count = 1; //disable preemtion until schedule_tail p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)childregs; int pid = nr_tasks++; task[pid] = p; preempt_enable(); return pid; } int move_to_user_mode(unsigned long pc) { struct pt_regs *regs = task_pt_regs(current); memzero((unsigned long)regs, sizeof(*regs)); regs->pc = pc; regs->pstate = PSR_MODE_EL0t; unsigned long stack = get_free_page(); //alocate new user stack if (!stack) { return -1; } regs->sp = stack + PAGE_SIZE; current->stack = stack; return 0; } struct pt_regs * task_pt_regs(struct task_struct *tsk){ unsigned long p = (unsigned long)tsk + THREAD_SIZE - sizeof(struct pt_regs); return (struct pt_regs *)p; } ================================================ FILE: exercises/lesson05/3/bl4ckout31/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson05/3/bl4ckout31/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "timer.h" #include "entry.h" #include "peripherals/irq.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32" }; void enable_interrupt_controller() { // Enables Core 0 Timers interrupt control for the generic timer put32(TIMER_INT_CTRL_0, TIMER_INT_CTRL_0_VALUE); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { // Each Core has its own pending local intrrupts register unsigned int irq = get32(INT_SOURCE_0); switch (irq) { case (GENERIC_TIMER_INTERRUPT): handle_generic_timer_irq(); break; default: printf("Unknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson05/3/bl4ckout31/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "timer.h" #include "irq.h" #include "sched.h" #include "fork.h" #include "mini_uart.h" #include "sys.h" void user_process1(char *array) { char buf[2] = {0}; while (1){ for (int i = 0; i < 5; i++){ buf[0] = array[i]; call_sys_write(buf); delay(5000000); } } } void user_process(){ char buf[30] = {0}; tfp_sprintf(buf, "User process started\n\r"); call_sys_write(buf); unsigned long stack = call_sys_malloc(); if (stack < 0) { printf("Error while allocating stack for process 1\n\r"); return; } int err = call_sys_clone((unsigned long)&user_process1, (unsigned long)"12345", stack); if (err < 0){ printf("Error while clonning process 1\n\r"); return; } stack = call_sys_malloc(); if (stack < 0) { printf("Error while allocating stack for process 1\n\r"); return; } err = call_sys_clone((unsigned long)&user_process1, (unsigned long)"abcd", stack); if (err < 0){ printf("Error while clonning process 2\n\r"); return; } call_sys_exit(); } void kernel_process(){ printf("Kernel process started. EL %d\r\n", get_el()); int err = move_to_user_mode((unsigned long)&user_process); if (err < 0){ printf("Error while moving process to user mode\n\r"); } } void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); generic_timer_init(); enable_interrupt_controller(); enable_irq(); int res = copy_process(PF_KTHREAD, (unsigned long)&kernel_process, 0, 0); if (res < 0) { printf("error while starting kernel process"); return; } while (1){ schedule(); } } ================================================ FILE: exercises/lesson05/3/bl4ckout31/src/linker.ld ================================================ SECTIONS { . = 0x80000; .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: exercises/lesson05/3/bl4ckout31/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson05/3/bl4ckout31/src/mm.S ================================================ .globl memcpy memcpy: ldr x3, [x0], #8 str x3, [x1], #8 subs x2, x2, #8 b.gt memcpy ret .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson05/3/bl4ckout31/src/mm.c ================================================ #include "mm.h" static unsigned short mem_map [ PAGING_PAGES ] = {0,}; unsigned long get_free_page() { for (int i = 0; i < PAGING_PAGES; i++){ if (mem_map[i] == 0){ mem_map[i] = 1; return LOW_MEMORY + i*PAGE_SIZE; } } return 0; } void free_page(unsigned long p){ mem_map[(p - LOW_MEMORY) / PAGE_SIZE] = 0; } ================================================ FILE: exercises/lesson05/3/bl4ckout31/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson05/3/bl4ckout31/src/sched.S ================================================ #include "sched.h" .globl cpu_switch_to cpu_switch_to: mov x10, #THREAD_CPU_CONTEXT add x8, x0, x10 mov x9, sp stp x19, x20, [x8], #16 // store callee-saved registers stp x21, x22, [x8], #16 stp x23, x24, [x8], #16 stp x25, x26, [x8], #16 stp x27, x28, [x8], #16 stp x29, x9, [x8], #16 str x30, [x8] add x8, x1, x10 ldp x19, x20, [x8], #16 // restore callee-saved registers ldp x21, x22, [x8], #16 ldp x23, x24, [x8], #16 ldp x25, x26, [x8], #16 ldp x27, x28, [x8], #16 ldp x29, x9, [x8], #16 ldr x30, [x8] mov sp, x9 ret ================================================ FILE: exercises/lesson05/3/bl4ckout31/src/sched.c ================================================ #include "sched.h" #include "irq.h" #include "printf.h" #include "fork.h" #include "utils.h" #include "mm.h" static struct task_struct init_task = INIT_TASK; struct task_struct *current = &(init_task); struct task_struct * task[NR_TASKS] = {&(init_task), }; int nr_tasks = 1; void preempt_disable(void) { current->preempt_count++; } void preempt_enable(void) { current->preempt_count--; } void _schedule(void) { preempt_disable(); int next,c; struct task_struct * p; while (1) { c = -1; next = 0; for (int i = 0; i < NR_TASKS; i++){ p = task[i]; if (p && p->state == TASK_RUNNING && p->counter > c) { c = p->counter; next = i; } } if (c) { break; } for (int i = 0; i < NR_TASKS; i++) { p = task[i]; if (p) { p->counter = (p->counter >> 1) + p->priority; } } } switch_to(task[next], next); preempt_enable(); } void schedule(void) { current->counter = 0; _schedule(); } void switch_to(struct task_struct * next, int index) { if (current == next) return; struct task_struct * prev = current; current = next; cpu_switch_to(prev, next); } void schedule_tail(void) { preempt_enable(); } void timer_tick() { --current->counter; if (current->counter>0 || current->preempt_count >0) { return; } current->counter=0; enable_irq(); _schedule(); disable_irq(); } void exit_process(){ preempt_disable(); for (int i = 0; i < NR_TASKS; i++){ if (task[i] == current) { task[i]->state = TASK_ZOMBIE; break; } } if (current->stack) { free_page(current->stack); } preempt_enable(); schedule(); } ================================================ FILE: exercises/lesson05/3/bl4ckout31/src/sys.S ================================================ #include "sys.h" .globl call_sys_write call_sys_write: mov w8, #SYS_WRITE_NUMBER svc #0 ret .globl call_sys_malloc call_sys_malloc: mov w8, #SYS_MALLOC_NUMBER svc #0 ret .globl call_sys_exit call_sys_exit: mov w8, #SYS_EXIT_NUMBER svc #0 ret .globl call_sys_clone call_sys_clone: /* Save args for the child. */ mov x10, x0 /*fn*/ mov x11, x1 /*arg*/ mov x12, x2 /*stack*/ /* Do the system call. */ mov x0, x2 /* stack */ mov x8, #SYS_CLONE_NUMBER svc 0x0 cmp x0, #0 beq thread_start ret thread_start: mov x29, 0 /* Pick the function arg and execute. */ mov x0, x11 blr x10 /* We are done, pass the return value through x0. */ mov x8, #SYS_EXIT_NUMBER svc 0x0 ================================================ FILE: exercises/lesson05/3/bl4ckout31/src/sys.c ================================================ #include "fork.h" #include "printf.h" #include "utils.h" #include "sched.h" #include "mm.h" void sys_write(char * buf){ printf(buf); } int sys_clone(unsigned long stack){ return copy_process(0, 0, 0, stack); } unsigned long sys_malloc(){ unsigned long addr = get_free_page(); if (!addr) { return -1; } return addr; } void sys_exit(){ exit_process(); } void * const sys_call_table[] = {sys_write, sys_malloc, sys_clone, sys_exit}; ================================================ FILE: exercises/lesson05/3/bl4ckout31/src/timer.S ================================================ /** Here, the physical timer at EL1 is used with the TimerValue views. * Once the count-down has reach 0, the interrupt line is HIGH until * a new timer value > 0 is write into the CNTP_TVAL_EL0 system register. * * See AArch64-referenc-manual p.2326 at * https://developer.arm.com/docs/ddi0487/ca/arm-architecture-reference-manual-armv8-for-armv8-a-architecture-profile */ .globl gen_timer_init gen_timer_init: mov x0, #1 msr CNTP_CTL_EL0, x0 ret .globl gen_timer_reset gen_timer_reset: mov x0, #1 lsl x0, x0, #24 msr CNTP_TVAL_EL0, x0 ret ================================================ FILE: exercises/lesson05/3/bl4ckout31/src/timer.c ================================================ #include "utils.h" #include "printf.h" #include "sched.h" #include "peripherals/timer.h" #include "timer.h" const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); timer_tick(); } void generic_timer_init ( void ) { gen_timer_init(); gen_timer_reset(); } void handle_generic_timer_irq( void ) { gen_timer_reset(); timer_tick(); } ================================================ FILE: exercises/lesson05/3/bl4ckout31/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: exercises/lesson05/3/bl4ckout31/start.sh ================================================ #!/bin/bash echo "###" echo "### Use C-a h for help" echo "###" echo "" qemu-system-aarch64 -machine raspi3 -serial null -serial mon:stdio -nographic -kernel kernel8.img ================================================ FILE: exercises/lesson06/1/xdfm1/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only -std=gnu99 -ggdb ASMOPS = -Iinclude -ggdb BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: exercises/lesson06/1/xdfm1/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: exercises/lesson06/1/xdfm1/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: exercises/lesson06/1/xdfm1/gdb.cmd ================================================ target remote localhost:1234 add-symbol-file build/boot_s.o 0x0 x/10i $pc ================================================ FILE: exercises/lesson06/1/xdfm1/include/arm/mmu.h ================================================ #ifndef _MMU_H #define _MMU_H #define MM_TYPE_PAGE_TABLE 0x3 #define MM_TYPE_PAGE 0x3 #define MM_TYPE_BLOCK 0x1 #define MM_ACCESS (0x1 << 10) #define MM_ACCESS_PERMISSION (0x01 << 6) /* * Memory region attributes: * * n = AttrIndx[2:0] * n MAIR * DEVICE_nGnRnE 000 00000000 * NORMAL_NC 001 01000100 */ #define MT_DEVICE_nGnRnE 0x0 #define MT_NORMAL_NC 0x1 #define MT_DEVICE_nGnRnE_FLAGS 0x00 #define MT_NORMAL_NC_FLAGS 0x44 #define MAIR_VALUE (MT_DEVICE_nGnRnE_FLAGS << (8 * MT_DEVICE_nGnRnE)) | (MT_NORMAL_NC_FLAGS << (8 * MT_NORMAL_NC)) #define MMU_FLAGS (MM_TYPE_BLOCK | (MT_NORMAL_NC << 2) | MM_ACCESS) #define MMU_DEVICE_FLAGS (MM_TYPE_BLOCK | (MT_DEVICE_nGnRnE << 2) | MM_ACCESS) #define MMU_PTE_FLAGS (MM_TYPE_PAGE | (MT_NORMAL_NC << 2) | MM_ACCESS | MM_ACCESS_PERMISSION) #define TCR_T0SZ (64 - 48) #define TCR_T1SZ ((64 - 48) << 16) #define TCR_TG0_4K (0 << 14) #define TCR_TG1_4K (2 << 30) #define TCR_VALUE (TCR_T0SZ | TCR_T1SZ | TCR_TG0_4K | TCR_TG1_4K) #endif ================================================ FILE: exercises/lesson06/1/xdfm1/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2025 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 1923 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2022 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 288 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) // *************************************** // ESR_EL1, Exception Syndrome Register (EL1). Page 1899 of AArch64-Reference-Manual. // *************************************** #define ESR_ELx_EC_SHIFT 26 #define ESR_ELx_EC_SVC64 0x15 #define ESR_ELx_EC_DABT_LOW 0x24 #endif ================================================ FILE: exercises/lesson06/1/xdfm1/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 272 // size of all saved registers #define S_X0 0 // offset of x0 register in saved stack frame #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define FIQ_INVALID_EL1h 5 #define ERROR_INVALID_EL1h 6 #define FIQ_INVALID_EL0_64 7 #define ERROR_INVALID_EL0_64 8 #define SYNC_INVALID_EL0_32 9 #define IRQ_INVALID_EL0_32 10 #define FIQ_INVALID_EL0_32 11 #define ERROR_INVALID_EL0_32 12 #define SYNC_ERROR 13 #define SYSCALL_ERROR 14 #define DATA_ABORT_ERROR 15 #ifndef __ASSEMBLER__ void ret_from_fork(void); #endif #endif ================================================ FILE: exercises/lesson06/1/xdfm1/include/fork.h ================================================ #ifndef _FORK_H #define _FORK_H #include "sched.h" /* * PSR bits */ #define PSR_MODE_EL0t 0x00000000 #define PSR_MODE_EL1t 0x00000004 #define PSR_MODE_EL1h 0x00000005 #define PSR_MODE_EL2t 0x00000008 #define PSR_MODE_EL2h 0x00000009 #define PSR_MODE_EL3t 0x0000000c #define PSR_MODE_EL3h 0x0000000d int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg); int move_to_user_mode(unsigned long start, unsigned long size, unsigned long pc); struct pt_regs * task_pt_regs(struct task_struct *tsk); struct pt_regs { unsigned long regs[31]; unsigned long sp; unsigned long pc; unsigned long pstate; }; #endif ================================================ FILE: exercises/lesson06/1/xdfm1/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: exercises/lesson06/1/xdfm1/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: exercises/lesson06/1/xdfm1/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #include "peripherals/base.h" #define VA_START 0xffff000000000000 #define PHYS_MEMORY_SIZE 0x40000000 #define PAGE_MASK 0xfffffffffffff000 #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #define HIGH_MEMORY DEVICE_BASE #define PAGING_MEMORY (HIGH_MEMORY - LOW_MEMORY) #define PAGING_PAGES (PAGING_MEMORY/PAGE_SIZE) #define PTRS_PER_TABLE (1 << TABLE_SHIFT) #define PGD_SHIFT PAGE_SHIFT + 3*TABLE_SHIFT #define PUD_SHIFT PAGE_SHIFT + 2*TABLE_SHIFT #define PMD_SHIFT PAGE_SHIFT + TABLE_SHIFT #define PG_DIR_SIZE (3 * PAGE_SIZE) #ifndef __ASSEMBLER__ #include "sched.h" unsigned long get_free_page(); void free_page(unsigned long p); void map_page(struct task_struct *task, unsigned long va, unsigned long page); void memzero(unsigned long src, unsigned long n); void memcpy(unsigned long src, unsigned long dst, unsigned long n); int copy_virt_memory(struct task_struct *dst); unsigned long allocate_kernel_page(); unsigned long allocate_user_page(struct task_struct *task, unsigned long va); extern unsigned long pg_dir; #endif #endif /*_MM_H */ ================================================ FILE: exercises/lesson06/1/xdfm1/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #include "mm.h" #define DEVICE_BASE 0x3F000000 #define PBASE (VA_START + DEVICE_BASE) #endif /*_P_BASE_H */ ================================================ FILE: exercises/lesson06/1/xdfm1/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPFSEL2 (PBASE+0x00200008) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: exercises/lesson06/1/xdfm1/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #endif /*_P_IRQ_H */ ================================================ FILE: exercises/lesson06/1/xdfm1/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: exercises/lesson06/1/xdfm1/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) #endif /*_P_TIMER_H */ ================================================ FILE: exercises/lesson06/1/xdfm1/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: exercises/lesson06/1/xdfm1/include/sched.h ================================================ #ifndef _SCHED_H #define _SCHED_H #define THREAD_CPU_CONTEXT 0 // offset of cpu_context in task_struct #ifndef __ASSEMBLER__ #define THREAD_SIZE 4096 #define NR_TASKS 64 #define FIRST_TASK task[0] #define LAST_TASK task[NR_TASKS-1] #define TASK_RUNNING 0 #define TASK_ZOMBIE 1 #define PF_KTHREAD 0x00000002 extern struct task_struct *current; extern struct task_struct * task[NR_TASKS]; extern int nr_tasks; struct cpu_context { unsigned long x19; unsigned long x20; unsigned long x21; unsigned long x22; unsigned long x23; unsigned long x24; unsigned long x25; unsigned long x26; unsigned long x27; unsigned long x28; unsigned long fp; unsigned long sp; unsigned long pc; }; #define MAX_PROCESS_PAGES 16 struct user_page { unsigned long phys_addr; unsigned long virt_addr; }; struct mm_struct { unsigned long pgd; int user_pages_count; struct user_page user_pages[MAX_PROCESS_PAGES]; int kernel_pages_count; unsigned long kernel_pages[MAX_PROCESS_PAGES]; }; struct task_struct { struct cpu_context cpu_context; long state; long counter; long priority; long preempt_count; unsigned long flags; struct mm_struct mm; }; extern void sched_init(void); extern void schedule(void); extern void timer_tick(void); extern void preempt_disable(void); extern void preempt_enable(void); extern void switch_to(struct task_struct* next); extern void cpu_switch_to(struct task_struct* prev, struct task_struct* next); extern void exit_process(void); #define INIT_TASK \ /*cpu_context*/ { { 0,0,0,0,0,0,0,0,0,0,0,0,0}, \ /* state etc */ 0,0,15, 0, PF_KTHREAD, \ /* mm */ { 0, 0, {{0}}, 0, {0}} \ } #endif #endif ================================================ FILE: exercises/lesson06/1/xdfm1/include/sys.h ================================================ #ifndef _SYS_H #define _SYS_H #define __NR_syscalls 3 #ifndef __ASSEMBLER__ void sys_write(char * buf); int sys_fork(); #endif #endif /*_SYS_H */ ================================================ FILE: exercises/lesson06/1/xdfm1/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); #endif /*_TIMER_H */ ================================================ FILE: exercises/lesson06/1/xdfm1/include/user.h ================================================ #ifndef _USER_H #define _USER_H void user_process1(char *array); void user_process(); extern unsigned long user_begin; extern unsigned long user_end; #endif /*_USER_H */ ================================================ FILE: exercises/lesson06/1/xdfm1/include/user_sys.h ================================================ #ifndef _USER_SYS_H #define _USER_SYS_H void call_sys_write(char * buf); int call_sys_fork(); void call_sys_exit(); extern void user_delay ( unsigned long); extern unsigned long get_sp ( void ); extern unsigned long get_pc ( void ); #endif /*_USER_SYS_H */ ================================================ FILE: exercises/lesson06/1/xdfm1/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern unsigned long get_el ( void ); extern void set_pgd(unsigned long pgd); extern unsigned long get_pgd(); #endif /*_UTILS_H */ ================================================ FILE: exercises/lesson06/1/xdfm1/src/boot.S ================================================ #include "arm/mmu.h" #include "arm/sysregs.h" #include "mm.h" #include "peripherals/base.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 mrs x0, CurrentEL lsr x0, x0, #2 cmp x0, #3 beq el3 // ldr x0, =HCR_VALUE // msr hcr_el2, x0 mrs x0, hcr_el2 orr x0, x0, #(1<<31) msr hcr_el2, x0 mov x0, #SPSR_VALUE msr spsr_el2, x0 adr x0, el1_entry msr elr_el2, x0 eret el3: ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero bl __create_idmap adrp x0, idmap_dir msr ttbr0_el1, x0 bl __create_page_tables mov x0, #VA_START add sp, x0, #LOW_MEMORY adrp x0, pg_dir msr ttbr1_el1, x0 ldr x0, =(TCR_VALUE) msr tcr_el1, x0 ldr x0, =(MAIR_VALUE) msr mair_el1, x0 ldr x2, =kernel_main mov x0, #SCTLR_MMU_ENABLED msr sctlr_el1, x0 br x2 .macro create_pgd_entry, tbl, virt, tmp1, tmp2 create_table_entry \tbl, \virt, PGD_SHIFT, \tmp1, \tmp2 create_table_entry \tbl, \virt, PUD_SHIFT, \tmp1, \tmp2 .endm .macro create_table_entry, tbl, virt, shift, tmp1, tmp2 lsr \tmp1, \virt, #\shift and \tmp1, \tmp1, #PTRS_PER_TABLE - 1 // table index add \tmp2, \tbl, #PAGE_SIZE orr \tmp2, \tmp2, #MM_TYPE_PAGE_TABLE str \tmp2, [\tbl, \tmp1, lsl #3] add \tbl, \tbl, #PAGE_SIZE // next level table page .endm .macro create_block_map, tbl, phys, start, end, flags, tmp1 lsr \start, \start, #SECTION_SHIFT and \start, \start, #PTRS_PER_TABLE - 1 // table index lsr \end, \end, #SECTION_SHIFT and \end, \end, #PTRS_PER_TABLE - 1 // table end index lsr \phys, \phys, #SECTION_SHIFT mov \tmp1, #\flags orr \phys, \tmp1, \phys, lsl #SECTION_SHIFT // table entry 9999: str \phys, [\tbl, \start, lsl #3] // store the entry add \start, \start, #1 // next entry add \phys, \phys, #SECTION_SIZE // next block cmp \start, \end b.ls 9999b .endm __create_idmap: mov x29, x30 adrp x0, idmap_dir mov x1, #PG_DIR_SIZE bl memzero adrp x0, idmap_dir mov x1, xzr create_pgd_entry x0, x1, x2, x3 mov x1, xzr mov x2, xzr ldr x3, =(PHYS_MEMORY_SIZE) create_block_map x0, x1, x2, x3, MMU_FLAGS, x4 mov x30, x29 ret __create_page_tables: mov x29, x30 // save return address adrp x0, pg_dir mov x1, #PG_DIR_SIZE bl memzero adrp x0, pg_dir mov x1, #VA_START create_pgd_entry x0, x1, x2, x3 /* Mapping kernel and init stack*/ mov x1, xzr // start mapping from physical offset 0 mov x2, #VA_START // first virtual address ldr x3, =(VA_START + DEVICE_BASE - SECTION_SIZE) // last virtual address create_block_map x0, x1, x2, x3, MMU_FLAGS, x4 /* Mapping device memory*/ mov x1, #DEVICE_BASE // start mapping from device base address ldr x2, =(VA_START + DEVICE_BASE) // first virtual address ldr x3, =(VA_START + PHYS_MEMORY_SIZE - SECTION_SIZE) // last virtual address create_block_map x0, x1, x2, x3, MMU_DEVICE_FLAGS, x4 mov x30, x29 // restore return address ret ================================================ FILE: exercises/lesson06/1/xdfm1/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: exercises/lesson06/1/xdfm1/src/entry.S ================================================ #include "arm/sysregs.h" #include "entry.h" #include "sys.h" .macro handle_invalid_entry el, type kernel_entry \el mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry, el sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] .if \el == 0 mrs x21, sp_el0 .else add x21, sp, #S_FRAME_SIZE .endif /* \el == 0 */ mrs x22, elr_el1 mrs x23, spsr_el1 stp x30, x21, [sp, #16 * 15] stp x22, x23, [sp, #16 * 16] .endm .macro kernel_exit, el ldp x22, x23, [sp, #16 * 16] ldp x30, x21, [sp, #16 * 15] .if \el == 0 msr sp_el0, x21 .endif /* \el == 0 */ msr elr_el1, x22 msr spsr_el1, x23 ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry el0_sync // Synchronous 64-bit EL0 ventry el0_irq // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry 1, SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry 1, IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry 1, FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry 1, ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry 1, SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry 1, FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry 1, ERROR_INVALID_EL1h fiq_invalid_el0_64: handle_invalid_entry 0, FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry 0, ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry 0, SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry 0, IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry 0, FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry 0, ERROR_INVALID_EL0_32 el1_irq: kernel_entry 1 bl handle_irq kernel_exit 1 el0_irq: kernel_entry 0 bl handle_irq kernel_exit 0 el0_sync: kernel_entry 0 mrs x25, esr_el1 // read the syndrome register lsr x24, x25, #ESR_ELx_EC_SHIFT // exception class cmp x24, #ESR_ELx_EC_SVC64 // SVC in 64-bit state b.eq el0_svc cmp x24, #ESR_ELx_EC_DABT_LOW // data abort in EL0 b.eq el0_da handle_invalid_entry 0, SYNC_ERROR sc_nr .req x25 // number of system calls scno .req x26 // syscall number stbl .req x27 // syscall table pointer el0_svc: adr stbl, sys_call_table // load syscall table pointer uxtw scno, w8 // syscall number in w8 mov sc_nr, #__NR_syscalls bl enable_irq cmp scno, sc_nr // check upper syscall limit b.hs ni_sys ldr x16, [stbl, scno, lsl #3] // address in the syscall table blr x16 // call sys_* routine b ret_from_syscall ni_sys: handle_invalid_entry 0, SYSCALL_ERROR ret_from_syscall: bl disable_irq str x0, [sp, #S_X0] // returned x0 kernel_exit 0 el0_da: bl enable_irq mrs x0, far_el1 mrs x1, esr_el1 bl do_mem_abort cmp x0, 0 b.eq 1f handle_invalid_entry 0, DATA_ABORT_ERROR 1: bl disable_irq kernel_exit 0 .globl ret_from_fork ret_from_fork: bl schedule_tail cbz x19, ret_to_user // not a kernel thread mov x0, x20 blr x19 ret_to_user: bl disable_irq kernel_exit 0 .globl err_hang err_hang: b err_hang ================================================ FILE: exercises/lesson06/1/xdfm1/src/fork.c ================================================ #include "mm.h" #include "sched.h" #include "fork.h" #include "utils.h" #include "entry.h" int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg) { preempt_disable(); struct task_struct *p; unsigned long page = allocate_kernel_page(); p = (struct task_struct *) page; struct pt_regs *childregs = task_pt_regs(p); if (!p) return -1; if (clone_flags & PF_KTHREAD) { p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; } else { struct pt_regs * cur_regs = task_pt_regs(current); *cur_regs = *childregs; childregs->regs[0] = 0; copy_virt_memory(p); } p->flags = clone_flags; p->priority = current->priority; p->state = TASK_RUNNING; p->counter = p->priority; p->preempt_count = 1; //disable preemtion until schedule_tail p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)childregs; int pid = nr_tasks++; task[pid] = p; preempt_enable(); return pid; } int move_to_user_mode(unsigned long start, unsigned long size, unsigned long pc) { struct pt_regs *regs = task_pt_regs(current); regs->pstate = PSR_MODE_EL0t; regs->pc = pc; regs->sp = 2 * PAGE_SIZE; unsigned long code_page = allocate_user_page(current, 0); if (code_page == 0) { return -1; } memcpy(start, code_page, size); set_pgd(current->mm.pgd); return 0; } struct pt_regs * task_pt_regs(struct task_struct *tsk) { unsigned long p = (unsigned long)tsk + THREAD_SIZE - sizeof(struct pt_regs); return (struct pt_regs *)p; } ================================================ FILE: exercises/lesson06/1/xdfm1/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: exercises/lesson06/1/xdfm1/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "timer.h" #include "entry.h" #include "peripherals/irq.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32" }; void enable_interrupt_controller() { put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { unsigned int irq = get32(IRQ_PENDING_1); switch (irq) { case (SYSTEM_TIMER_IRQ_1): handle_timer_irq(); break; default: printf("Inknown pending irq: %x\r\n", irq); } } ================================================ FILE: exercises/lesson06/1/xdfm1/src/kernel.c ================================================ #include #include #include "printf.h" #include "utils.h" #include "timer.h" #include "irq.h" #include "fork.h" #include "sched.h" #include "mini_uart.h" #include "sys.h" #include "user.h" void kernel_process(){ printf("Kernel process started. EL %d\r\n", get_el()); unsigned long begin = (unsigned long)&user_begin; unsigned long end = (unsigned long)&user_end; unsigned long process = (unsigned long)&user_process; int err = move_to_user_mode(begin, end - begin, process - begin); if (err < 0){ printf("Error while moving process to user mode\n\r"); } } void kernel_main() { uart_init(); init_printf(NULL, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); int res = copy_process(PF_KTHREAD, (unsigned long)&kernel_process, 0); if (res < 0) { printf("error while starting kernel process"); return; } while (1){ schedule(); } } ================================================ FILE: exercises/lesson06/1/xdfm1/src/linker.ld ================================================ SECTIONS { . = 0xffff000000080000; .text.boot : { *(.text.boot) } . = ALIGN(0x00001000); user_begin = .; .text.user : { build/user* (.text) } .rodata.user : { build/user* (.rodata) } .data.user : { build/user* (.data) } .bss.user : { build/user* (.bss) } user_end = .; .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; . = ALIGN(0x00001000); idmap_dir = .; .data.idmapd : {. += (3* (1 << 12));} . = ALIGN(0x00001000); pg_dir = .; .data.pgd : { . += (3 * (1 << 12)); } } ================================================ FILE: exercises/lesson06/1/xdfm1/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: exercises/lesson06/1/xdfm1/src/mm.S ================================================ .globl memcpy memcpy: ldr x3, [x0], #8 str x3, [x1], #8 subs x2, x2, #8 b.gt memcpy ret .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: exercises/lesson06/1/xdfm1/src/mm.c ================================================ #include "mm.h" #include "arm/mmu.h" static unsigned short mem_map [ PAGING_PAGES ] = {0,}; unsigned long allocate_kernel_page() { unsigned long page = get_free_page(); if (page == 0) { return 0; } return page + VA_START; } unsigned long allocate_user_page(struct task_struct *task, unsigned long va) { unsigned long page = get_free_page(); if (page == 0) { return 0; } map_page(task, va, page); return page + VA_START; } unsigned long get_free_page() { for (int i = 0; i < PAGING_PAGES; i++){ if (mem_map[i] == 0){ mem_map[i] = 1; unsigned long page = LOW_MEMORY + i*PAGE_SIZE; memzero(page + VA_START, PAGE_SIZE); return page; } } return 0; } void free_page(unsigned long p){ mem_map[(p - LOW_MEMORY) / PAGE_SIZE] = 0; } void map_table_entry(unsigned long *pte, unsigned long va, unsigned long pa) { unsigned long index = va >> PAGE_SHIFT; index = index & (PTRS_PER_TABLE - 1); unsigned long entry = pa | MMU_PTE_FLAGS; pte[index] = entry; } unsigned long map_table(unsigned long *table, unsigned long shift, unsigned long va, int* new_table) { unsigned long index = va >> shift; index = index & (PTRS_PER_TABLE - 1); if (!table[index]){ *new_table = 1; unsigned long next_level_table = get_free_page(); unsigned long entry = next_level_table | MM_TYPE_PAGE_TABLE; table[index] = entry; return next_level_table; } else { *new_table = 0; } return table[index] & PAGE_MASK; } void map_page(struct task_struct *task, unsigned long va, unsigned long page){ unsigned long pgd; if (!task->mm.pgd) { task->mm.pgd = get_free_page(); task->mm.kernel_pages[++task->mm.kernel_pages_count] = task->mm.pgd; } pgd = task->mm.pgd; int new_table; unsigned long pud = map_table((unsigned long *)(pgd + VA_START), PGD_SHIFT, va, &new_table); if (new_table) { task->mm.kernel_pages[++task->mm.kernel_pages_count] = pud; } unsigned long pmd = map_table((unsigned long *)(pud + VA_START) , PUD_SHIFT, va, &new_table); if (new_table) { task->mm.kernel_pages[++task->mm.kernel_pages_count] = pmd; } unsigned long pte = map_table((unsigned long *)(pmd + VA_START), PMD_SHIFT, va, &new_table); if (new_table) { task->mm.kernel_pages[++task->mm.kernel_pages_count] = pte; } map_table_entry((unsigned long *)(pte + VA_START), va, page); struct user_page p = {page, va}; task->mm.user_pages[task->mm.user_pages_count++] = p; } int copy_virt_memory(struct task_struct *dst) { struct task_struct* src = current; for (int i = 0; i < src->mm.user_pages_count; i++) { unsigned long kernel_va = allocate_user_page(dst, src->mm.user_pages[i].virt_addr); if( kernel_va == 0) { return -1; } memcpy(src->mm.user_pages[i].virt_addr, kernel_va, PAGE_SIZE); } return 0; } static int ind = 1; int do_mem_abort(unsigned long addr, unsigned long esr) { unsigned long dfs = (esr & 0b111111); if ((dfs & 0b111100) == 0b100) { unsigned long page = get_free_page(); if (page == 0) { return -1; } map_page(current, addr & PAGE_MASK, page); ind++; if (ind > 2){ return -1; } return 0; } return -1; } ================================================ FILE: exercises/lesson06/1/xdfm1/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: exercises/lesson06/1/xdfm1/src/sched.S ================================================ #include "sched.h" .globl cpu_switch_to cpu_switch_to: mov x10, #THREAD_CPU_CONTEXT add x8, x0, x10 mov x9, sp stp x19, x20, [x8], #16 // store callee-saved registers stp x21, x22, [x8], #16 stp x23, x24, [x8], #16 stp x25, x26, [x8], #16 stp x27, x28, [x8], #16 stp x29, x9, [x8], #16 str x30, [x8] add x8, x1, x10 ldp x19, x20, [x8], #16 // restore callee-saved registers ldp x21, x22, [x8], #16 ldp x23, x24, [x8], #16 ldp x25, x26, [x8], #16 ldp x27, x28, [x8], #16 ldp x29, x9, [x8], #16 ldr x30, [x8] mov sp, x9 ret ================================================ FILE: exercises/lesson06/1/xdfm1/src/sched.c ================================================ #include "sched.h" #include "irq.h" #include "printf.h" #include "utils.h" #include "mm.h" static struct task_struct init_task = INIT_TASK; struct task_struct *current = &(init_task); struct task_struct * task[NR_TASKS] = {&(init_task), }; int nr_tasks = 1; void preempt_disable(void) { current->preempt_count++; } void preempt_enable(void) { current->preempt_count--; } void _schedule(void) { preempt_disable(); int next,c; struct task_struct * p; while (1) { c = -1; next = 0; for (int i = 0; i < NR_TASKS; i++){ p = task[i]; if (p && p->state == TASK_RUNNING && p->counter > c) { c = p->counter; next = i; } } if (c) { break; } for (int i = 0; i < NR_TASKS; i++) { p = task[i]; if (p) { p->counter = (p->counter >> 1) + p->priority; } } } switch_to(task[next]); preempt_enable(); } void schedule(void) { current->counter = 0; _schedule(); } void switch_to(struct task_struct * next) { if (current == next) return; struct task_struct * prev = current; current = next; set_pgd(next->mm.pgd); cpu_switch_to(prev, next); } void schedule_tail(void) { preempt_enable(); } void timer_tick() { --current->counter; if (current->counter>0 || current->preempt_count >0) { return; } current->counter=0; enable_irq(); _schedule(); disable_irq(); } void exit_process(){ preempt_disable(); for (int i = 0; i < NR_TASKS; i++){ if (task[i] == current) { task[i]->state = TASK_ZOMBIE; break; } } preempt_enable(); schedule(); } ================================================ FILE: exercises/lesson06/1/xdfm1/src/sys.c ================================================ #include "fork.h" #include "printf.h" #include "utils.h" #include "sched.h" #include "mm.h" void sys_write(char * buf){ printf(buf); } int sys_fork(){ return copy_process(0, 0, 0); } void sys_exit(){ exit_process(); } void * const sys_call_table[] = {sys_write, sys_fork, sys_exit}; ================================================ FILE: exercises/lesson06/1/xdfm1/src/timer.c ================================================ #include "utils.h" #include "printf.h" #include "sched.h" #include "peripherals/timer.h" const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); timer_tick(); } ================================================ FILE: exercises/lesson06/1/xdfm1/src/user.c ================================================ #include "user_sys.h" #include "user.h" #include "printf.h" void loop(char* str) { char buf[2] = {""}; while (1){ for (int i = 0; i < 5; i++){ buf[0] = str[i]; call_sys_write(buf); user_delay(1000000); } } } void user_process() { call_sys_write("User process\n\r"); int pid = call_sys_fork(); if (pid < 0) { call_sys_write("Error during fork\n\r"); call_sys_exit(); return; } if (pid == 0){ loop("abcde"); } else { loop("12345"); } } ================================================ FILE: exercises/lesson06/1/xdfm1/src/user_sys.S ================================================ .section ".text.user" .set SYS_WRITE_NUMBER, 0 // syscal numbers .set SYS_FORK_NUMBER, 1 .set SYS_EXIT_NUMBER, 2 .globl user_delay user_delay: subs x0, x0, #1 bne user_delay ret .globl call_sys_write call_sys_write: mov w8, #SYS_WRITE_NUMBER svc #0 ret .globl call_sys_exit call_sys_exit: mov w8, #SYS_EXIT_NUMBER svc #0 ret .globl call_sys_fork call_sys_fork: mov w8, #SYS_FORK_NUMBER svc #0 ret ================================================ FILE: exercises/lesson06/1/xdfm1/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret .globl set_pgd set_pgd: msr ttbr0_el1, x0 tlbi vmalle1is DSB ISH // ensure completion of TLB invalidation isb ret .globl get_pgd get_pgd: mov x1, 0 ldr x0, [x1] mov x0, 0x1000 msr ttbr0_el1, x0 ldr x0, [x1] ret ================================================ FILE: exercises/lesson06/1/xdfm1/start.sh ================================================ #!/bin/bash echo "###" echo "### Use C-a h for help" echo "###" echo "" qemu-system-aarch64 -machine raspi3 -nographic -kernel kernel8.img -serial null -serial mon:stdio ================================================ FILE: src/lesson01/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: src/lesson01/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: src/lesson01/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: src/lesson01/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void uart_send_string(char* str); #endif /*_MINI_UART_H */ ================================================ FILE: src/lesson01/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: src/lesson01/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: src/lesson01/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: src/lesson01/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: src/lesson01/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); #endif /*_BOOT_H */ ================================================ FILE: src/lesson01/src/boot.S ================================================ #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: src/lesson01/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: src/lesson01/src/kernel.c ================================================ #include "mini_uart.h" void kernel_main(void) { uart_init(); uart_send_string("Hello, world!\r\n"); while (1) { uart_send(uart_recv()); } } ================================================ FILE: src/lesson01/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: src/lesson01/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } ================================================ FILE: src/lesson01/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: src/lesson01/src/utils.S ================================================ .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: src/lesson02/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: src/lesson02/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: src/lesson02/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: src/lesson02/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #endif ================================================ FILE: src/lesson02/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: src/lesson02/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: src/lesson02/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: src/lesson02/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: src/lesson02/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: src/lesson02/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: src/lesson02/include/utils.h ================================================ #ifndef _BOOT_H #define _BOOT_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_BOOT_H */ ================================================ FILE: src/lesson02/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: src/lesson02/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: src/lesson02/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "mini_uart.h" void kernel_main(void) { uart_init(); init_printf(0, putc); int el = get_el(); printf("Exception level: %d \r\n", el); while (1) { uart_send(uart_recv()); } } ================================================ FILE: src/lesson02/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: src/lesson02/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: src/lesson02/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: src/lesson02/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: src/lesson02/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: src/lesson03/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: src/lesson03/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: src/lesson03/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: src/lesson03/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #endif ================================================ FILE: src/lesson03/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 256 // size of all saved registers #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #endif ================================================ FILE: src/lesson03/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: src/lesson03/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: src/lesson03/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #ifndef __ASSEMBLER__ void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: src/lesson03/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: src/lesson03/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: src/lesson03/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #endif /*_P_IRQ_H */ ================================================ FILE: src/lesson03/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: src/lesson03/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) #endif /*_P_TIMER_H */ ================================================ FILE: src/lesson03/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: src/lesson03/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); #endif /*_TIMER_H */ ================================================ FILE: src/lesson03/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_UTILS_H */ ================================================ FILE: src/lesson03/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: src/lesson03/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: src/lesson03/src/entry.S ================================================ #include "entry.h" .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] str x30, [sp, #16 * 15] .endm .macro kernel_exit ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] ldr x30, [sp, #16 * 15] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 ventry irq_invalid_el0_64 // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry ERROR_INVALID_EL1h sync_invalid_el0_64: handle_invalid_entry SYNC_INVALID_EL0_64 irq_invalid_el0_64: handle_invalid_entry IRQ_INVALID_EL0_64 fiq_invalid_el0_64: handle_invalid_entry FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry ERROR_INVALID_EL0_32 el1_irq: kernel_entry bl handle_irq kernel_exit .globl err_hang err_hang: b err_hang ================================================ FILE: src/lesson03/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: src/lesson03/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "timer.h" #include "entry.h" #include "peripherals/irq.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32" }; void enable_interrupt_controller() { put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { unsigned int irq = get32(IRQ_PENDING_1); switch (irq) { case (SYSTEM_TIMER_IRQ_1): handle_timer_irq(); break; default: printf("Unknown pending irq: %x\r\n", irq); } } ================================================ FILE: src/lesson03/src/kernel.c ================================================ #include "printf.h" #include "timer.h" #include "irq.h" #include "mini_uart.h" void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); while (1){ uart_send(uart_recv()); } } ================================================ FILE: src/lesson03/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: src/lesson03/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: src/lesson03/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: src/lesson03/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: src/lesson03/src/timer.c ================================================ #include "utils.h" #include "printf.h" #include "peripherals/timer.h" const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); printf("Timer interrupt received\n\r"); } ================================================ FILE: src/lesson03/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: src/lesson04/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: src/lesson04/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: src/lesson04/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: src/lesson04/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) #endif ================================================ FILE: src/lesson04/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 272 // size of all saved registers #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #ifndef __ASSEMBLER__ void ret_from_fork(void); #endif #endif ================================================ FILE: src/lesson04/include/fork.h ================================================ #ifndef _FORK_H #define _FORK_H int copy_process(unsigned long fn, unsigned long arg); #endif ================================================ FILE: src/lesson04/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: src/lesson04/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: src/lesson04/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #include "peripherals/base.h" #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #define HIGH_MEMORY PBASE #define PAGING_MEMORY (HIGH_MEMORY - LOW_MEMORY) #define PAGING_PAGES (PAGING_MEMORY/PAGE_SIZE) #ifndef __ASSEMBLER__ unsigned long get_free_page(); void free_page(unsigned long p); void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: src/lesson04/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: src/lesson04/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: src/lesson04/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #endif /*_P_IRQ_H */ ================================================ FILE: src/lesson04/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: src/lesson04/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) #endif /*_P_TIMER_H */ ================================================ FILE: src/lesson04/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: src/lesson04/include/sched.h ================================================ #ifndef _SCHED_H #define _SCHED_H #define THREAD_CPU_CONTEXT 0 // offset of cpu_context in task_struct #ifndef __ASSEMBLER__ #define THREAD_SIZE 4096 #define NR_TASKS 64 #define FIRST_TASK task[0] #define LAST_TASK task[NR_TASKS-1] #define TASK_RUNNING 0 extern struct task_struct *current; extern struct task_struct * task[NR_TASKS]; extern int nr_tasks; struct cpu_context { unsigned long x19; unsigned long x20; unsigned long x21; unsigned long x22; unsigned long x23; unsigned long x24; unsigned long x25; unsigned long x26; unsigned long x27; unsigned long x28; unsigned long fp; unsigned long sp; unsigned long pc; }; struct task_struct { struct cpu_context cpu_context; long state; long counter; long priority; long preempt_count; }; extern void sched_init(void); extern void schedule(void); extern void timer_tick(void); extern void preempt_disable(void); extern void preempt_enable(void); extern void switch_to(struct task_struct* next); extern void cpu_switch_to(struct task_struct* prev, struct task_struct* next); #define INIT_TASK \ /*cpu_context*/ { {0,0,0,0,0,0,0,0,0,0,0,0,0}, \ /* state etc */ 0,0,1, 0 \ } #endif #endif ================================================ FILE: src/lesson04/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); #endif /*_TIMER_H */ ================================================ FILE: src/lesson04/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_UTILS_H */ ================================================ FILE: src/lesson04/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #(2 * SECTION_SIZE) bl kernel_main b proc_hang // should never come here ================================================ FILE: src/lesson04/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: src/lesson04/src/entry.S ================================================ #include "entry.h" .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] mrs x22, elr_el1 mrs x23, spsr_el1 stp x30, x22, [sp, #16 * 15] str x23, [sp, #16 * 16] .endm .macro kernel_exit ldr x23, [sp, #16 * 16] ldp x30, x22, [sp, #16 * 15] msr elr_el1, x22 msr spsr_el1, x23 ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 ventry irq_invalid_el0_64 // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry ERROR_INVALID_EL1h sync_invalid_el0_64: handle_invalid_entry SYNC_INVALID_EL0_64 irq_invalid_el0_64: handle_invalid_entry IRQ_INVALID_EL0_64 fiq_invalid_el0_64: handle_invalid_entry FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry ERROR_INVALID_EL0_32 el1_irq: kernel_entry bl handle_irq kernel_exit .globl ret_from_fork ret_from_fork: bl schedule_tail mov x0, x20 blr x19 //should never return .globl err_hang err_hang: b err_hang ================================================ FILE: src/lesson04/src/fork.c ================================================ #include "mm.h" #include "sched.h" #include "entry.h" int copy_process(unsigned long fn, unsigned long arg) { preempt_disable(); struct task_struct *p; p = (struct task_struct *) get_free_page(); if (!p) return 1; p->priority = current->priority; p->state = TASK_RUNNING; p->counter = p->priority; p->preempt_count = 1; //disable preemtion until schedule_tail p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)p + THREAD_SIZE; int pid = nr_tasks++; task[pid] = p; preempt_enable(); return 0; } ================================================ FILE: src/lesson04/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: src/lesson04/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "timer.h" #include "entry.h" #include "peripherals/irq.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32" }; void enable_interrupt_controller() { put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { unsigned int irq = get32(IRQ_PENDING_1); switch (irq) { case (SYSTEM_TIMER_IRQ_1): handle_timer_irq(); break; default: printf("Inknown pending irq: %x\r\n", irq); } } ================================================ FILE: src/lesson04/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "timer.h" #include "irq.h" #include "fork.h" #include "sched.h" #include "mini_uart.h" void process(char *array) { while (1){ for (int i = 0; i < 5; i++){ uart_send(array[i]); delay(100000); } } } void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); int res = copy_process((unsigned long)&process, (unsigned long)"12345"); if (res != 0) { printf("error while starting process 1"); return; } res = copy_process((unsigned long)&process, (unsigned long)"abcde"); if (res != 0) { printf("error while starting process 2"); return; } while (1){ schedule(); } } ================================================ FILE: src/lesson04/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: src/lesson04/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: src/lesson04/src/mm.S ================================================ .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: src/lesson04/src/mm.c ================================================ #include "mm.h" static unsigned short mem_map [ PAGING_PAGES ] = {0,}; unsigned long get_free_page() { for (int i = 0; i < PAGING_PAGES; i++){ if (mem_map[i] == 0){ mem_map[i] = 1; return LOW_MEMORY + i*PAGE_SIZE; } } return 0; } void free_page(unsigned long p){ mem_map[(p - LOW_MEMORY) / PAGE_SIZE] = 0; } ================================================ FILE: src/lesson04/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: src/lesson04/src/sched.S ================================================ #include "sched.h" .globl cpu_switch_to cpu_switch_to: mov x10, #THREAD_CPU_CONTEXT add x8, x0, x10 mov x9, sp stp x19, x20, [x8], #16 // store callee-saved registers stp x21, x22, [x8], #16 stp x23, x24, [x8], #16 stp x25, x26, [x8], #16 stp x27, x28, [x8], #16 stp x29, x9, [x8], #16 str x30, [x8] add x8, x1, x10 ldp x19, x20, [x8], #16 // restore callee-saved registers ldp x21, x22, [x8], #16 ldp x23, x24, [x8], #16 ldp x25, x26, [x8], #16 ldp x27, x28, [x8], #16 ldp x29, x9, [x8], #16 ldr x30, [x8] mov sp, x9 ret ================================================ FILE: src/lesson04/src/sched.c ================================================ #include "sched.h" #include "irq.h" #include "printf.h" static struct task_struct init_task = INIT_TASK; struct task_struct *current = &(init_task); struct task_struct * task[NR_TASKS] = {&(init_task), }; int nr_tasks = 1; void preempt_disable(void) { current->preempt_count++; } void preempt_enable(void) { current->preempt_count--; } void _schedule(void) { preempt_disable(); int next,c; struct task_struct * p; while (1) { c = -1; next = 0; for (int i = 0; i < NR_TASKS; i++){ p = task[i]; if (p && p->state == TASK_RUNNING && p->counter > c) { c = p->counter; next = i; } } if (c) { break; } for (int i = 0; i < NR_TASKS; i++) { p = task[i]; if (p) { p->counter = (p->counter >> 1) + p->priority; } } } switch_to(task[next]); preempt_enable(); } void schedule(void) { current->counter = 0; _schedule(); } void switch_to(struct task_struct * next) { if (current == next) return; struct task_struct * prev = current; current = next; cpu_switch_to(prev, next); } void schedule_tail(void) { preempt_enable(); } void timer_tick() { --current->counter; if (current->counter>0 || current->preempt_count >0) { return; } current->counter=0; enable_irq(); _schedule(); disable_irq(); } ================================================ FILE: src/lesson04/src/timer.c ================================================ #include "utils.h" #include "printf.h" #include "sched.h" #include "peripherals/timer.h" const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); timer_tick(); } ================================================ FILE: src/lesson04/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: src/lesson05/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: src/lesson05/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: src/lesson05/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: src/lesson05/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) // *************************************** // ESR_EL1, Exception Syndrome Register (EL1). Page 2431 of AArch64-Reference-Manual. // *************************************** #define ESR_ELx_EC_SHIFT 26 #define ESR_ELx_EC_SVC64 0x15 #endif ================================================ FILE: src/lesson05/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 272 // size of all saved registers #define S_X0 0 // offset of x0 register in saved stack frame #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define IRQ_INVALID_EL1h 5 #define FIQ_INVALID_EL1h 6 #define ERROR_INVALID_EL1h 7 #define SYNC_INVALID_EL0_64 8 #define IRQ_INVALID_EL0_64 9 #define FIQ_INVALID_EL0_64 10 #define ERROR_INVALID_EL0_64 11 #define SYNC_INVALID_EL0_32 12 #define IRQ_INVALID_EL0_32 13 #define FIQ_INVALID_EL0_32 14 #define ERROR_INVALID_EL0_32 15 #define SYNC_ERROR 16 #define SYSCALL_ERROR 17 #ifndef __ASSEMBLER__ void ret_from_fork(void); #endif #endif ================================================ FILE: src/lesson05/include/fork.h ================================================ #ifndef _FORK_H #define _FORK_H #include "sched.h" /* * PSR bits */ #define PSR_MODE_EL0t 0x00000000 #define PSR_MODE_EL1t 0x00000004 #define PSR_MODE_EL1h 0x00000005 #define PSR_MODE_EL2t 0x00000008 #define PSR_MODE_EL2h 0x00000009 #define PSR_MODE_EL3t 0x0000000c #define PSR_MODE_EL3h 0x0000000d int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg, unsigned long stack); int move_to_user_mode(unsigned long pc); struct pt_regs * task_pt_regs(struct task_struct *tsk); struct pt_regs { unsigned long regs[31]; unsigned long sp; unsigned long pc; unsigned long pstate; }; #endif ================================================ FILE: src/lesson05/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: src/lesson05/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: src/lesson05/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #include "peripherals/base.h" #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #define HIGH_MEMORY PBASE #define PAGING_MEMORY (HIGH_MEMORY - LOW_MEMORY) #define PAGING_PAGES (PAGING_MEMORY/PAGE_SIZE) #ifndef __ASSEMBLER__ unsigned long get_free_page(); void free_page(unsigned long p); void memzero(unsigned long src, unsigned long n); #endif #endif /*_MM_H */ ================================================ FILE: src/lesson05/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #define PBASE 0x3F000000 #endif /*_P_BASE_H */ ================================================ FILE: src/lesson05/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: src/lesson05/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #endif /*_P_IRQ_H */ ================================================ FILE: src/lesson05/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: src/lesson05/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) #endif /*_P_TIMER_H */ ================================================ FILE: src/lesson05/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: src/lesson05/include/sched.h ================================================ #ifndef _SCHED_H #define _SCHED_H #define THREAD_CPU_CONTEXT 0 // offset of cpu_context in task_struct #ifndef __ASSEMBLER__ #define THREAD_SIZE 4096 #define NR_TASKS 64 #define FIRST_TASK task[0] #define LAST_TASK task[NR_TASKS-1] #define TASK_RUNNING 0 #define TASK_ZOMBIE 1 #define PF_KTHREAD 0x00000002 extern struct task_struct *current; extern struct task_struct * task[NR_TASKS]; extern int nr_tasks; struct cpu_context { unsigned long x19; unsigned long x20; unsigned long x21; unsigned long x22; unsigned long x23; unsigned long x24; unsigned long x25; unsigned long x26; unsigned long x27; unsigned long x28; unsigned long fp; unsigned long sp; unsigned long pc; }; struct task_struct { struct cpu_context cpu_context; long state; long counter; long priority; long preempt_count; unsigned long stack; unsigned long flags; }; extern void sched_init(void); extern void schedule(void); extern void timer_tick(void); extern void preempt_disable(void); extern void preempt_enable(void); extern void switch_to(struct task_struct * next, int index); extern void cpu_switch_to(struct task_struct* prev, struct task_struct* next); extern void exit_process(void); #define INIT_TASK \ /*cpu_context*/ { {0,0,0,0,0,0,0,0,0,0,0,0,0}, \ /* state etc */ 0,0,1, 0, 0, PF_KTHREAD \ } #endif #endif ================================================ FILE: src/lesson05/include/sys.h ================================================ #ifndef _SYS_H #define _SYS_H #define __NR_syscalls 4 #define SYS_WRITE_NUMBER 0 // syscal numbers #define SYS_MALLOC_NUMBER 1 #define SYS_CLONE_NUMBER 2 #define SYS_EXIT_NUMBER 3 #ifndef __ASSEMBLER__ void sys_write(char * buf); int sys_fork(); void call_sys_write(char * buf); int call_sys_clone(unsigned long fn, unsigned long arg, unsigned long stack); unsigned long call_sys_malloc(); void call_sys_exit(); #endif #endif /*_SYS_H */ ================================================ FILE: src/lesson05/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); #endif /*_TIMER_H */ ================================================ FILE: src/lesson05/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern int get_el ( void ); #endif /*_UTILS_H */ ================================================ FILE: src/lesson05/src/boot.S ================================================ #include "arm/sysregs.h" #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main b proc_hang // should never come here ================================================ FILE: src/lesson05/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: src/lesson05/src/entry.S ================================================ #include "arm/sysregs.h" #include "entry.h" #include "sys.h" .macro handle_invalid_entry el, type kernel_entry \el mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry, el sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] .if \el == 0 mrs x21, sp_el0 .else add x21, sp, #S_FRAME_SIZE .endif /* \el == 0 */ mrs x22, elr_el1 mrs x23, spsr_el1 stp x30, x21, [sp, #16 * 15] stp x22, x23, [sp, #16 * 16] .endm .macro kernel_exit, el ldp x22, x23, [sp, #16 * 16] ldp x30, x21, [sp, #16 * 15] .if \el == 0 msr sp_el0, x21 .endif /* \el == 0 */ msr elr_el1, x22 msr spsr_el1, x23 ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry el0_sync // Synchronous 64-bit EL0 ventry el0_irq // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry 1, SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry 1, IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry 1, FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry 1, ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry 1, SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry 1, FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry 1, ERROR_INVALID_EL1h fiq_invalid_el0_64: handle_invalid_entry 0, FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry 0, ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry 0, SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry 0, IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry 0, FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry 0, ERROR_INVALID_EL0_32 el1_irq: kernel_entry 1 bl handle_irq kernel_exit 1 el0_irq: kernel_entry 0 bl handle_irq kernel_exit 0 el0_sync: kernel_entry 0 mrs x25, esr_el1 // read the syndrome register lsr x24, x25, #ESR_ELx_EC_SHIFT // exception class cmp x24, #ESR_ELx_EC_SVC64 // SVC in 64-bit state b.eq el0_svc handle_invalid_entry 0, SYNC_ERROR sc_nr .req x25 // number of system calls scno .req x26 // syscall number stbl .req x27 // syscall table pointer el0_svc: adr stbl, sys_call_table // load syscall table pointer uxtw scno, w8 // syscall number in w8 mov sc_nr, #__NR_syscalls bl enable_irq cmp scno, sc_nr // check upper syscall limit b.hs ni_sys ldr x16, [stbl, scno, lsl #3] // address in the syscall table blr x16 // call sys_* routine b ret_from_syscall ni_sys: handle_invalid_entry 0, SYSCALL_ERROR ret_from_syscall: bl disable_irq str x0, [sp, #S_X0] // returned x0 kernel_exit 0 .globl ret_from_fork ret_from_fork: bl schedule_tail cbz x19, ret_to_user // not a kernel thread mov x0, x20 blr x19 ret_to_user: bl disable_irq kernel_exit 0 .globl err_hang err_hang: b err_hang ================================================ FILE: src/lesson05/src/fork.c ================================================ #include "mm.h" #include "sched.h" #include "printf.h" #include "fork.h" #include "entry.h" #include "utils.h" int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg, unsigned long stack) { preempt_disable(); struct task_struct *p; p = (struct task_struct *) get_free_page(); if (!p) { return -1; } struct pt_regs *childregs = task_pt_regs(p); memzero((unsigned long)childregs, sizeof(struct pt_regs)); memzero((unsigned long)&p->cpu_context, sizeof(struct cpu_context)); if (clone_flags & PF_KTHREAD) { p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; } else { struct pt_regs * cur_regs = task_pt_regs(current); *childregs = *cur_regs; childregs->regs[0] = 0; childregs->sp = stack + PAGE_SIZE; p->stack = stack; } p->flags = clone_flags; p->priority = current->priority; p->state = TASK_RUNNING; p->counter = p->priority; p->preempt_count = 1; //disable preemtion until schedule_tail p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)childregs; int pid = nr_tasks++; task[pid] = p; preempt_enable(); return pid; } int move_to_user_mode(unsigned long pc) { struct pt_regs *regs = task_pt_regs(current); memzero((unsigned long)regs, sizeof(*regs)); regs->pc = pc; regs->pstate = PSR_MODE_EL0t; unsigned long stack = get_free_page(); //allocate new user stack if (!stack) { return -1; } regs->sp = stack + PAGE_SIZE; current->stack = stack; return 0; } struct pt_regs * task_pt_regs(struct task_struct *tsk){ unsigned long p = (unsigned long)tsk + THREAD_SIZE - sizeof(struct pt_regs); return (struct pt_regs *)p; } ================================================ FILE: src/lesson05/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: src/lesson05/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "timer.h" #include "entry.h" #include "peripherals/irq.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32", "SYNC_ERROR", "SYSCALL_ERROR" }; void enable_interrupt_controller() { put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { unsigned int irq = get32(IRQ_PENDING_1); switch (irq) { case (SYSTEM_TIMER_IRQ_1): handle_timer_irq(); break; default: printf("Inknown pending irq: %x\r\n", irq); } } ================================================ FILE: src/lesson05/src/kernel.c ================================================ #include "printf.h" #include "utils.h" #include "timer.h" #include "irq.h" #include "sched.h" #include "fork.h" #include "mini_uart.h" #include "sys.h" void user_process1(char *array) { char buf[2] = {0}; while (1){ for (int i = 0; i < 5; i++){ buf[0] = array[i]; call_sys_write(buf); delay(100000); } } } void user_process(){ char buf[30] = {0}; tfp_sprintf(buf, "User process started\n\r"); call_sys_write(buf); unsigned long stack = call_sys_malloc(); if (stack < 0) { printf("Error while allocating stack for process 1\n\r"); return; } int err = call_sys_clone((unsigned long)&user_process1, (unsigned long)"12345", stack); if (err < 0){ printf("Error while clonning process 1\n\r"); return; } stack = call_sys_malloc(); if (stack < 0) { printf("Error while allocating stack for process 1\n\r"); return; } err = call_sys_clone((unsigned long)&user_process1, (unsigned long)"abcd", stack); if (err < 0){ printf("Error while clonning process 2\n\r"); return; } call_sys_exit(); } void kernel_process(){ printf("Kernel process started. EL %d\r\n", get_el()); int err = move_to_user_mode((unsigned long)&user_process); if (err < 0){ printf("Error while moving process to user mode\n\r"); } } void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); int res = copy_process(PF_KTHREAD, (unsigned long)&kernel_process, 0, 0); if (res < 0) { printf("error while starting kernel process"); return; } while (1){ schedule(); } } ================================================ FILE: src/lesson05/src/linker.ld ================================================ SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ================================================ FILE: src/lesson05/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: src/lesson05/src/mm.S ================================================ .globl memcpy memcpy: ldr x3, [x1], #8 str x3, [x0], #8 subs x2, x2, #8 b.gt memcpy ret .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: src/lesson05/src/mm.c ================================================ #include "mm.h" static unsigned short mem_map [ PAGING_PAGES ] = {0,}; unsigned long get_free_page() { for (int i = 0; i < PAGING_PAGES; i++){ if (mem_map[i] == 0){ mem_map[i] = 1; return LOW_MEMORY + i*PAGE_SIZE; } } return 0; } void free_page(unsigned long p){ mem_map[(p - LOW_MEMORY) / PAGE_SIZE] = 0; } ================================================ FILE: src/lesson05/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: src/lesson05/src/sched.S ================================================ #include "sched.h" .globl cpu_switch_to cpu_switch_to: mov x10, #THREAD_CPU_CONTEXT add x8, x0, x10 mov x9, sp stp x19, x20, [x8], #16 // store callee-saved registers stp x21, x22, [x8], #16 stp x23, x24, [x8], #16 stp x25, x26, [x8], #16 stp x27, x28, [x8], #16 stp x29, x9, [x8], #16 str x30, [x8] add x8, x1, x10 ldp x19, x20, [x8], #16 // restore callee-saved registers ldp x21, x22, [x8], #16 ldp x23, x24, [x8], #16 ldp x25, x26, [x8], #16 ldp x27, x28, [x8], #16 ldp x29, x9, [x8], #16 ldr x30, [x8] mov sp, x9 ret ================================================ FILE: src/lesson05/src/sched.c ================================================ #include "sched.h" #include "irq.h" #include "printf.h" #include "fork.h" #include "utils.h" #include "mm.h" static struct task_struct init_task = INIT_TASK; struct task_struct *current = &(init_task); struct task_struct * task[NR_TASKS] = {&(init_task), }; int nr_tasks = 1; void preempt_disable(void) { current->preempt_count++; } void preempt_enable(void) { current->preempt_count--; } void _schedule(void) { preempt_disable(); int next,c; struct task_struct * p; while (1) { c = -1; next = 0; for (int i = 0; i < NR_TASKS; i++){ p = task[i]; if (p && p->state == TASK_RUNNING && p->counter > c) { c = p->counter; next = i; } } if (c) { break; } for (int i = 0; i < NR_TASKS; i++) { p = task[i]; if (p) { p->counter = (p->counter >> 1) + p->priority; } } } switch_to(task[next], next); preempt_enable(); } void schedule(void) { current->counter = 0; _schedule(); } void switch_to(struct task_struct * next, int index) { if (current == next) return; struct task_struct * prev = current; current = next; cpu_switch_to(prev, next); } void schedule_tail(void) { preempt_enable(); } void timer_tick() { --current->counter; if (current->counter>0 || current->preempt_count >0) { return; } current->counter=0; enable_irq(); _schedule(); disable_irq(); } void exit_process(){ preempt_disable(); for (int i = 0; i < NR_TASKS; i++){ if (task[i] == current) { task[i]->state = TASK_ZOMBIE; break; } } if (current->stack) { free_page(current->stack); } preempt_enable(); schedule(); } ================================================ FILE: src/lesson05/src/sys.S ================================================ #include "sys.h" .globl call_sys_write call_sys_write: mov w8, #SYS_WRITE_NUMBER svc #0 ret .globl call_sys_malloc call_sys_malloc: mov w8, #SYS_MALLOC_NUMBER svc #0 ret .globl call_sys_exit call_sys_exit: mov w8, #SYS_EXIT_NUMBER svc #0 ret .globl call_sys_clone call_sys_clone: /* Save args for the child. */ mov x10, x0 /*fn*/ mov x11, x1 /*arg*/ mov x12, x2 /*stack*/ /* Do the system call. */ mov x0, x2 /* stack */ mov x8, #SYS_CLONE_NUMBER svc 0x0 cmp x0, #0 beq thread_start ret thread_start: mov x29, 0 /* Pick the function arg and execute. */ mov x0, x11 blr x10 /* We are done, pass the return value through x0. */ mov x8, #SYS_EXIT_NUMBER svc 0x0 ================================================ FILE: src/lesson05/src/sys.c ================================================ #include "fork.h" #include "printf.h" #include "utils.h" #include "sched.h" #include "mm.h" void sys_write(char * buf){ printf(buf); } int sys_clone(unsigned long stack){ return copy_process(0, 0, 0, stack); } unsigned long sys_malloc(){ unsigned long addr = get_free_page(); if (!addr) { return -1; } return addr; } void sys_exit(){ exit_process(); } void * const sys_call_table[] = {sys_write, sys_malloc, sys_clone, sys_exit}; ================================================ FILE: src/lesson05/src/timer.c ================================================ #include "utils.h" #include "printf.h" #include "sched.h" #include "peripherals/timer.h" const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); timer_tick(); } ================================================ FILE: src/lesson05/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret ================================================ FILE: src/lesson06/Makefile ================================================ ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ================================================ FILE: src/lesson06/build.bat ================================================ docker run --rm -v %cd%:/app -w /app smatyukevich/raspberry-pi-os-builder make %1 ================================================ FILE: src/lesson06/build.sh ================================================ #!/bin/bash docker run --rm -v $(pwd):/app -w /app smatyukevich/raspberry-pi-os-builder make $1 ================================================ FILE: src/lesson06/include/arm/mmu.h ================================================ #ifndef _MMU_H #define _MMU_H #define MM_TYPE_PAGE_TABLE 0x3 #define MM_TYPE_PAGE 0x3 #define MM_TYPE_BLOCK 0x1 #define MM_ACCESS (0x1 << 10) #define MM_ACCESS_PERMISSION (0x01 << 6) /* * Memory region attributes: * * n = AttrIndx[2:0] * n MAIR * DEVICE_nGnRnE 000 00000000 * NORMAL_NC 001 01000100 */ #define MT_DEVICE_nGnRnE 0x0 #define MT_NORMAL_NC 0x1 #define MT_DEVICE_nGnRnE_FLAGS 0x00 #define MT_NORMAL_NC_FLAGS 0x44 #define MAIR_VALUE (MT_DEVICE_nGnRnE_FLAGS << (8 * MT_DEVICE_nGnRnE)) | (MT_NORMAL_NC_FLAGS << (8 * MT_NORMAL_NC)) #define MMU_FLAGS (MM_TYPE_BLOCK | (MT_NORMAL_NC << 2) | MM_ACCESS) #define MMU_DEVICE_FLAGS (MM_TYPE_BLOCK | (MT_DEVICE_nGnRnE << 2) | MM_ACCESS) #define MMU_PTE_FLAGS (MM_TYPE_PAGE | (MT_NORMAL_NC << 2) | MM_ACCESS | MM_ACCESS_PERMISSION) #define TCR_T0SZ (64 - 48) #define TCR_T1SZ ((64 - 48) << 16) #define TCR_TG0_4K (0 << 14) #define TCR_TG1_4K (2 << 30) #define TCR_VALUE (TCR_T0SZ | TCR_T1SZ | TCR_TG0_4K | TCR_TG1_4K) #endif ================================================ FILE: src/lesson06/include/arm/sysregs.h ================================================ #ifndef _SYSREGS_H #define _SYSREGS_H // *************************************** // SCTLR_EL1, System Control Register (EL1), Page 2025 of AArch64-Reference-Manual. // *************************************** #define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) #define SCTLR_EE_LITTLE_ENDIAN (0 << 25) #define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) #define SCTLR_I_CACHE_DISABLED (0 << 12) #define SCTLR_D_CACHE_DISABLED (0 << 2) #define SCTLR_MMU_DISABLED (0 << 0) #define SCTLR_MMU_ENABLED (1 << 0) #define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_DISABLED | SCTLR_D_CACHE_DISABLED | SCTLR_MMU_DISABLED) // *************************************** // HCR_EL2, Hypervisor Configuration Register (EL2), Page 1923 of AArch64-Reference-Manual. // *************************************** #define HCR_RW (1 << 31) #define HCR_VALUE HCR_RW // *************************************** // SCR_EL3, Secure Configuration Register (EL3), Page 2022 of AArch64-Reference-Manual. // *************************************** #define SCR_RESERVED (3 << 4) #define SCR_RW (1 << 10) #define SCR_NS (1 << 0) #define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) // *************************************** // SPSR_EL3, Saved Program Status Register (EL3) Page 288 of AArch64-Reference-Manual. // *************************************** #define SPSR_MASK_ALL (7 << 6) #define SPSR_EL1h (5 << 0) #define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) // *************************************** // ESR_EL1, Exception Syndrome Register (EL1). Page 1899 of AArch64-Reference-Manual. // *************************************** #define ESR_ELx_EC_SHIFT 26 #define ESR_ELx_EC_SVC64 0x15 #define ESR_ELx_EC_DABT_LOW 0x24 #endif ================================================ FILE: src/lesson06/include/entry.h ================================================ #ifndef _ENTRY_H #define _ENTRY_H #define S_FRAME_SIZE 272 // size of all saved registers #define S_X0 0 // offset of x0 register in saved stack frame #define SYNC_INVALID_EL1t 0 #define IRQ_INVALID_EL1t 1 #define FIQ_INVALID_EL1t 2 #define ERROR_INVALID_EL1t 3 #define SYNC_INVALID_EL1h 4 #define FIQ_INVALID_EL1h 5 #define ERROR_INVALID_EL1h 6 #define FIQ_INVALID_EL0_64 7 #define ERROR_INVALID_EL0_64 8 #define SYNC_INVALID_EL0_32 9 #define IRQ_INVALID_EL0_32 10 #define FIQ_INVALID_EL0_32 11 #define ERROR_INVALID_EL0_32 12 #define SYNC_ERROR 13 #define SYSCALL_ERROR 14 #define DATA_ABORT_ERROR 15 #ifndef __ASSEMBLER__ void ret_from_fork(void); #endif #endif ================================================ FILE: src/lesson06/include/fork.h ================================================ #ifndef _FORK_H #define _FORK_H #include "sched.h" /* * PSR bits */ #define PSR_MODE_EL0t 0x00000000 #define PSR_MODE_EL1t 0x00000004 #define PSR_MODE_EL1h 0x00000005 #define PSR_MODE_EL2t 0x00000008 #define PSR_MODE_EL2h 0x00000009 #define PSR_MODE_EL3t 0x0000000c #define PSR_MODE_EL3h 0x0000000d int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg); int move_to_user_mode(unsigned long start, unsigned long size, unsigned long pc); struct pt_regs * task_pt_regs(struct task_struct *tsk); struct pt_regs { unsigned long regs[31]; unsigned long sp; unsigned long pc; unsigned long pstate; }; #endif ================================================ FILE: src/lesson06/include/irq.h ================================================ #ifndef _IRQ_H #define _IRQ_H void enable_interrupt_controller( void ); void irq_vector_init( void ); void enable_irq( void ); void disable_irq( void ); #endif /*_IRQ_H */ ================================================ FILE: src/lesson06/include/mini_uart.h ================================================ #ifndef _MINI_UART_H #define _MINI_UART_H void uart_init ( void ); char uart_recv ( void ); void uart_send ( char c ); void putc ( void* p, char c ); #endif /*_MINI_UART_H */ ================================================ FILE: src/lesson06/include/mm.h ================================================ #ifndef _MM_H #define _MM_H #include "peripherals/base.h" #define VA_START 0xffff000000000000 #define PHYS_MEMORY_SIZE 0x40000000 #define PAGE_MASK 0xfffffffffffff000 #define PAGE_SHIFT 12 #define TABLE_SHIFT 9 #define SECTION_SHIFT (PAGE_SHIFT + TABLE_SHIFT) #define PAGE_SIZE (1 << PAGE_SHIFT) #define SECTION_SIZE (1 << SECTION_SHIFT) #define LOW_MEMORY (2 * SECTION_SIZE) #define HIGH_MEMORY DEVICE_BASE #define PAGING_MEMORY (HIGH_MEMORY - LOW_MEMORY) #define PAGING_PAGES (PAGING_MEMORY/PAGE_SIZE) #define PTRS_PER_TABLE (1 << TABLE_SHIFT) #define PGD_SHIFT PAGE_SHIFT + 3*TABLE_SHIFT #define PUD_SHIFT PAGE_SHIFT + 2*TABLE_SHIFT #define PMD_SHIFT PAGE_SHIFT + TABLE_SHIFT #define PG_DIR_SIZE (3 * PAGE_SIZE) #ifndef __ASSEMBLER__ #include "sched.h" unsigned long get_free_page(); void free_page(unsigned long p); void map_page(struct task_struct *task, unsigned long va, unsigned long page); void memzero(unsigned long src, unsigned long n); void memcpy(unsigned long dst, unsigned long src, unsigned long n); int copy_virt_memory(struct task_struct *dst); unsigned long allocate_kernel_page(); unsigned long allocate_user_page(struct task_struct *task, unsigned long va); extern unsigned long pg_dir; #endif #endif /*_MM_H */ ================================================ FILE: src/lesson06/include/peripherals/base.h ================================================ #ifndef _P_BASE_H #define _P_BASE_H #include "mm.h" #define DEVICE_BASE 0x3F000000 #define PBASE (VA_START + DEVICE_BASE) #endif /*_P_BASE_H */ ================================================ FILE: src/lesson06/include/peripherals/gpio.h ================================================ #ifndef _P_GPIO_H #define _P_GPIO_H #include "peripherals/base.h" #define GPFSEL1 (PBASE+0x00200004) #define GPSET0 (PBASE+0x0020001C) #define GPCLR0 (PBASE+0x00200028) #define GPPUD (PBASE+0x00200094) #define GPPUDCLK0 (PBASE+0x00200098) #endif /*_P_GPIO_H */ ================================================ FILE: src/lesson06/include/peripherals/irq.h ================================================ #ifndef _P_IRQ_H #define _P_IRQ_H #include "peripherals/base.h" #define IRQ_BASIC_PENDING (PBASE+0x0000B200) #define IRQ_PENDING_1 (PBASE+0x0000B204) #define IRQ_PENDING_2 (PBASE+0x0000B208) #define FIQ_CONTROL (PBASE+0x0000B20C) #define ENABLE_IRQS_1 (PBASE+0x0000B210) #define ENABLE_IRQS_2 (PBASE+0x0000B214) #define ENABLE_BASIC_IRQS (PBASE+0x0000B218) #define DISABLE_IRQS_1 (PBASE+0x0000B21C) #define DISABLE_IRQS_2 (PBASE+0x0000B220) #define DISABLE_BASIC_IRQS (PBASE+0x0000B224) #define SYSTEM_TIMER_IRQ_0 (1 << 0) #define SYSTEM_TIMER_IRQ_1 (1 << 1) #define SYSTEM_TIMER_IRQ_2 (1 << 2) #define SYSTEM_TIMER_IRQ_3 (1 << 3) #endif /*_P_IRQ_H */ ================================================ FILE: src/lesson06/include/peripherals/mini_uart.h ================================================ #ifndef _P_MINI_UART_H #define _P_MINI_UART_H #include "peripherals/base.h" #define AUX_ENABLES (PBASE+0x00215004) #define AUX_MU_IO_REG (PBASE+0x00215040) #define AUX_MU_IER_REG (PBASE+0x00215044) #define AUX_MU_IIR_REG (PBASE+0x00215048) #define AUX_MU_LCR_REG (PBASE+0x0021504C) #define AUX_MU_MCR_REG (PBASE+0x00215050) #define AUX_MU_LSR_REG (PBASE+0x00215054) #define AUX_MU_MSR_REG (PBASE+0x00215058) #define AUX_MU_SCRATCH (PBASE+0x0021505C) #define AUX_MU_CNTL_REG (PBASE+0x00215060) #define AUX_MU_STAT_REG (PBASE+0x00215064) #define AUX_MU_BAUD_REG (PBASE+0x00215068) #endif /*_P_MINI_UART_H */ ================================================ FILE: src/lesson06/include/peripherals/timer.h ================================================ #ifndef _P_TIMER_H #define _P_TIMER_H #include "peripherals/base.h" #define TIMER_CS (PBASE+0x00003000) #define TIMER_CLO (PBASE+0x00003004) #define TIMER_CHI (PBASE+0x00003008) #define TIMER_C0 (PBASE+0x0000300C) #define TIMER_C1 (PBASE+0x00003010) #define TIMER_C2 (PBASE+0x00003014) #define TIMER_C3 (PBASE+0x00003018) #define TIMER_CS_M0 (1 << 0) #define TIMER_CS_M1 (1 << 1) #define TIMER_CS_M2 (1 << 2) #define TIMER_CS_M3 (1 << 3) #endif /*_P_TIMER_H */ ================================================ FILE: src/lesson06/include/printf.h ================================================ /* File: printf.h Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA This library is really just two files: 'printf.h' and 'printf.c'. They provide a simple and small (+200 loc) printf functionality to be used in embedded systems. I've found them so usefull in debugging that I do not bother with a debugger at all. They are distributed in source form, so to use them, just compile them into your project. Two printf variants are provided: printf and sprintf. The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'. Zero padding and field width are also supported. If the library is compiled with 'PRINTF_SUPPORT_LONG' defined then the long specifier is also supported. Note that this will pull in some long math routines (pun intended!) and thus make your executable noticably longer. The memory foot print of course depends on the target cpu, compiler and compiler options, but a rough guestimate (based on a H8S target) is about 1.4 kB for code and some twenty 'int's and 'char's, say 60 bytes of stack space. Not too bad. Your milage may vary. By hacking the source code you can get rid of some hunred bytes, I'm sure, but personally I feel the balance of functionality and flexibility versus code size is close to optimal for many embedded systems. To use the printf you need to supply your own character output function, something like : void putc ( void* p, char c) { while (!SERIAL_PORT_EMPTY) ; SERIAL_PORT_TX_REGISTER = c; } Before you can call printf you need to initialize it to use your character output function with something like: init_printf(NULL,putc); Notice the 'NULL' in 'init_printf' and the parameter 'void* p' in 'putc', the NULL (or any pointer) you pass into the 'init_printf' will eventually be passed to your 'putc' routine. This allows you to pass some storage space (or anything really) to the character output function, if necessary. This is not often needed but it was implemented like that because it made implementing the sprintf function so neat (look at the source code). The code is re-entrant, except for the 'init_printf' function, so it is safe to call it from interupts too, although this may result in mixed output. If you rely on re-entrancy, take care that your 'putc' function is re-entrant! The printf and sprintf functions are actually macros that translate to 'tfp_printf' and 'tfp_sprintf'. This makes it possible to use them along with 'stdio.h' printf's in a single source file. You just need to undef the names before you include the 'stdio.h'. Note that these are not function like macros, so if you have variables or struct members with these names, things will explode in your face. Without variadic macros this is the best we can do to wrap these fucnction. If it is a problem just give up the macros and use the functions directly or rename them. For further details see source code. regs Kusti, 23.10.2004 */ #ifndef __TFP_PRINTF__ #define __TFP_PRINTF__ #include void init_printf(void* putp,void (*putf) (void*,char)); void tfp_printf(char *fmt, ...); void tfp_sprintf(char* s,char *fmt, ...); void tfp_format(void* putp,void (*putf) (void*,char),char *fmt, va_list va); #define printf tfp_printf #define sprintf tfp_sprintf #endif ================================================ FILE: src/lesson06/include/sched.h ================================================ #ifndef _SCHED_H #define _SCHED_H #define THREAD_CPU_CONTEXT 0 // offset of cpu_context in task_struct #ifndef __ASSEMBLER__ #define THREAD_SIZE 4096 #define NR_TASKS 64 #define FIRST_TASK task[0] #define LAST_TASK task[NR_TASKS-1] #define TASK_RUNNING 0 #define TASK_ZOMBIE 1 #define PF_KTHREAD 0x00000002 extern struct task_struct *current; extern struct task_struct * task[NR_TASKS]; extern int nr_tasks; struct cpu_context { unsigned long x19; unsigned long x20; unsigned long x21; unsigned long x22; unsigned long x23; unsigned long x24; unsigned long x25; unsigned long x26; unsigned long x27; unsigned long x28; unsigned long fp; unsigned long sp; unsigned long pc; }; #define MAX_PROCESS_PAGES 16 struct user_page { unsigned long phys_addr; unsigned long virt_addr; }; struct mm_struct { unsigned long pgd; int user_pages_count; struct user_page user_pages[MAX_PROCESS_PAGES]; int kernel_pages_count; unsigned long kernel_pages[MAX_PROCESS_PAGES]; }; struct task_struct { struct cpu_context cpu_context; long state; long counter; long priority; long preempt_count; unsigned long flags; struct mm_struct mm; }; extern void sched_init(void); extern void schedule(void); extern void timer_tick(void); extern void preempt_disable(void); extern void preempt_enable(void); extern void switch_to(struct task_struct* next); extern void cpu_switch_to(struct task_struct* prev, struct task_struct* next); extern void exit_process(void); #define INIT_TASK \ /*cpu_context*/ { { 0,0,0,0,0,0,0,0,0,0,0,0,0}, \ /* state etc */ 0,0,15, 0, PF_KTHREAD, \ /* mm */ { 0, 0, {{0}}, 0, {0}} \ } #endif #endif ================================================ FILE: src/lesson06/include/sys.h ================================================ #ifndef _SYS_H #define _SYS_H #define __NR_syscalls 3 #ifndef __ASSEMBLER__ void sys_write(char * buf); int sys_fork(); #endif #endif /*_SYS_H */ ================================================ FILE: src/lesson06/include/timer.h ================================================ #ifndef _TIMER_H #define _TIMER_H void timer_init ( void ); void handle_timer_irq ( void ); #endif /*_TIMER_H */ ================================================ FILE: src/lesson06/include/user.h ================================================ #ifndef _USER_H #define _USER_H void user_process1(char *array); void user_process(); extern unsigned long user_begin; extern unsigned long user_end; #endif /*_USER_H */ ================================================ FILE: src/lesson06/include/user_sys.h ================================================ #ifndef _USER_SYS_H #define _USER_SYS_H void call_sys_write(char * buf); int call_sys_fork(); void call_sys_exit(); extern void user_delay ( unsigned long); extern unsigned long get_sp ( void ); extern unsigned long get_pc ( void ); #endif /*_USER_SYS_H */ ================================================ FILE: src/lesson06/include/utils.h ================================================ #ifndef _UTILS_H #define _UTILS_H extern void delay ( unsigned long); extern void put32 ( unsigned long, unsigned int ); extern unsigned int get32 ( unsigned long ); extern unsigned long get_el ( void ); extern void set_pgd(unsigned long pgd); extern unsigned long get_pgd(); #endif /*_UTILS_H */ ================================================ FILE: src/lesson06/src/boot.S ================================================ #include "arm/mmu.h" #include "arm/sysregs.h" #include "mm.h" #include "peripherals/base.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret el1_entry: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero bl __create_page_tables mov x0, #VA_START add sp, x0, #LOW_MEMORY adrp x0, pg_dir msr ttbr1_el1, x0 ldr x0, =(TCR_VALUE) msr tcr_el1, x0 ldr x0, =(MAIR_VALUE) msr mair_el1, x0 ldr x2, =kernel_main mov x0, #SCTLR_MMU_ENABLED msr sctlr_el1, x0 br x2 .macro create_pgd_entry, tbl, virt, tmp1, tmp2 create_table_entry \tbl, \virt, PGD_SHIFT, \tmp1, \tmp2 create_table_entry \tbl, \virt, PUD_SHIFT, \tmp1, \tmp2 .endm .macro create_table_entry, tbl, virt, shift, tmp1, tmp2 lsr \tmp1, \virt, #\shift and \tmp1, \tmp1, #PTRS_PER_TABLE - 1 // table index add \tmp2, \tbl, #PAGE_SIZE orr \tmp2, \tmp2, #MM_TYPE_PAGE_TABLE str \tmp2, [\tbl, \tmp1, lsl #3] add \tbl, \tbl, #PAGE_SIZE // next level table page .endm .macro create_block_map, tbl, phys, start, end, flags, tmp1 lsr \start, \start, #SECTION_SHIFT and \start, \start, #PTRS_PER_TABLE - 1 // table index lsr \end, \end, #SECTION_SHIFT and \end, \end, #PTRS_PER_TABLE - 1 // table end index lsr \phys, \phys, #SECTION_SHIFT mov \tmp1, #\flags orr \phys, \tmp1, \phys, lsl #SECTION_SHIFT // table entry 9999: str \phys, [\tbl, \start, lsl #3] // store the entry add \start, \start, #1 // next entry add \phys, \phys, #SECTION_SIZE // next block cmp \start, \end b.ls 9999b .endm __create_page_tables: mov x29, x30 // save return address adrp x0, pg_dir mov x1, #PG_DIR_SIZE bl memzero adrp x0, pg_dir mov x1, #VA_START create_pgd_entry x0, x1, x2, x3 /* Mapping kernel and init stack*/ mov x1, xzr // start mapping from physical offset 0 mov x2, #VA_START // first virtual address ldr x3, =(VA_START + DEVICE_BASE - SECTION_SIZE) // last virtual address create_block_map x0, x1, x2, x3, MMU_FLAGS, x4 /* Mapping device memory*/ mov x1, #DEVICE_BASE // start mapping from device base address ldr x2, =(VA_START + DEVICE_BASE) // first virtual address ldr x3, =(VA_START + PHYS_MEMORY_SIZE - SECTION_SIZE) // last virtual address create_block_map x0, x1, x2, x3, MMU_DEVICE_FLAGS, x4 mov x30, x29 // restore return address ret ================================================ FILE: src/lesson06/src/config.txt ================================================ kernel_old=1 disable_commandline_tags=1 ================================================ FILE: src/lesson06/src/entry.S ================================================ #include "arm/sysregs.h" #include "entry.h" #include "sys.h" .macro handle_invalid_entry el, type kernel_entry \el mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm .macro ventry label .align 7 b \label .endm .macro kernel_entry, el sub sp, sp, #S_FRAME_SIZE stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] .if \el == 0 mrs x21, sp_el0 .else add x21, sp, #S_FRAME_SIZE .endif /* \el == 0 */ mrs x22, elr_el1 mrs x23, spsr_el1 stp x30, x21, [sp, #16 * 15] stp x22, x23, [sp, #16 * 16] .endm .macro kernel_exit, el ldp x22, x23, [sp, #16 * 16] ldp x30, x21, [sp, #16 * 15] .if \el == 0 msr sp_el0, x21 .endif /* \el == 0 */ msr elr_el1, x22 msr spsr_el1, x23 ldp x0, x1, [sp, #16 * 0] ldp x2, x3, [sp, #16 * 1] ldp x4, x5, [sp, #16 * 2] ldp x6, x7, [sp, #16 * 3] ldp x8, x9, [sp, #16 * 4] ldp x10, x11, [sp, #16 * 5] ldp x12, x13, [sp, #16 * 6] ldp x14, x15, [sp, #16 * 7] ldp x16, x17, [sp, #16 * 8] ldp x18, x19, [sp, #16 * 9] ldp x20, x21, [sp, #16 * 10] ldp x22, x23, [sp, #16 * 11] ldp x24, x25, [sp, #16 * 12] ldp x26, x27, [sp, #16 * 13] ldp x28, x29, [sp, #16 * 14] add sp, sp, #S_FRAME_SIZE eret .endm /* * Exception vectors. */ .align 11 .globl vectors vectors: ventry sync_invalid_el1t // Synchronous EL1t ventry irq_invalid_el1t // IRQ EL1t ventry fiq_invalid_el1t // FIQ EL1t ventry error_invalid_el1t // Error EL1t ventry sync_invalid_el1h // Synchronous EL1h ventry el1_irq // IRQ EL1h ventry fiq_invalid_el1h // FIQ EL1h ventry error_invalid_el1h // Error EL1h ventry el0_sync // Synchronous 64-bit EL0 ventry el0_irq // IRQ 64-bit EL0 ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 ventry error_invalid_el0_64 // Error 64-bit EL0 ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 ventry irq_invalid_el0_32 // IRQ 32-bit EL0 ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 ventry error_invalid_el0_32 // Error 32-bit EL0 sync_invalid_el1t: handle_invalid_entry 1, SYNC_INVALID_EL1t irq_invalid_el1t: handle_invalid_entry 1, IRQ_INVALID_EL1t fiq_invalid_el1t: handle_invalid_entry 1, FIQ_INVALID_EL1t error_invalid_el1t: handle_invalid_entry 1, ERROR_INVALID_EL1t sync_invalid_el1h: handle_invalid_entry 1, SYNC_INVALID_EL1h fiq_invalid_el1h: handle_invalid_entry 1, FIQ_INVALID_EL1h error_invalid_el1h: handle_invalid_entry 1, ERROR_INVALID_EL1h fiq_invalid_el0_64: handle_invalid_entry 0, FIQ_INVALID_EL0_64 error_invalid_el0_64: handle_invalid_entry 0, ERROR_INVALID_EL0_64 sync_invalid_el0_32: handle_invalid_entry 0, SYNC_INVALID_EL0_32 irq_invalid_el0_32: handle_invalid_entry 0, IRQ_INVALID_EL0_32 fiq_invalid_el0_32: handle_invalid_entry 0, FIQ_INVALID_EL0_32 error_invalid_el0_32: handle_invalid_entry 0, ERROR_INVALID_EL0_32 el1_irq: kernel_entry 1 bl handle_irq kernel_exit 1 el0_irq: kernel_entry 0 bl handle_irq kernel_exit 0 el0_sync: kernel_entry 0 mrs x25, esr_el1 // read the syndrome register lsr x24, x25, #ESR_ELx_EC_SHIFT // exception class cmp x24, #ESR_ELx_EC_SVC64 // SVC in 64-bit state b.eq el0_svc cmp x24, #ESR_ELx_EC_DABT_LOW // data abort in EL0 b.eq el0_da handle_invalid_entry 0, SYNC_ERROR sc_nr .req x25 // number of system calls scno .req x26 // syscall number stbl .req x27 // syscall table pointer el0_svc: adr stbl, sys_call_table // load syscall table pointer uxtw scno, w8 // syscall number in w8 mov sc_nr, #__NR_syscalls bl enable_irq cmp scno, sc_nr // check upper syscall limit b.hs ni_sys ldr x16, [stbl, scno, lsl #3] // address in the syscall table blr x16 // call sys_* routine b ret_from_syscall ni_sys: handle_invalid_entry 0, SYSCALL_ERROR ret_from_syscall: bl disable_irq str x0, [sp, #S_X0] // returned x0 kernel_exit 0 el0_da: bl enable_irq mrs x0, far_el1 mrs x1, esr_el1 bl do_mem_abort cmp x0, 0 b.eq 1f handle_invalid_entry 0, DATA_ABORT_ERROR 1: bl disable_irq kernel_exit 0 .globl ret_from_fork ret_from_fork: bl schedule_tail cbz x19, ret_to_user // not a kernel thread mov x0, x20 blr x19 ret_to_user: bl disable_irq kernel_exit 0 .globl err_hang err_hang: b err_hang ================================================ FILE: src/lesson06/src/fork.c ================================================ #include "mm.h" #include "sched.h" #include "fork.h" #include "utils.h" #include "entry.h" int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg) { preempt_disable(); struct task_struct *p; unsigned long page = allocate_kernel_page(); p = (struct task_struct *) page; struct pt_regs *childregs = task_pt_regs(p); if (!p) return -1; if (clone_flags & PF_KTHREAD) { p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; } else { struct pt_regs * cur_regs = task_pt_regs(current); *childregs = *cur_regs; childregs->regs[0] = 0; copy_virt_memory(p); } p->flags = clone_flags; p->priority = current->priority; p->state = TASK_RUNNING; p->counter = p->priority; p->preempt_count = 1; //disable preemtion until schedule_tail p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)childregs; int pid = nr_tasks++; task[pid] = p; preempt_enable(); return pid; } int move_to_user_mode(unsigned long start, unsigned long size, unsigned long pc) { struct pt_regs *regs = task_pt_regs(current); regs->pstate = PSR_MODE_EL0t; regs->pc = pc; regs->sp = 2 * PAGE_SIZE; unsigned long code_page = allocate_user_page(current, 0); if (code_page == 0) { return -1; } memcpy(code_page, start, size); set_pgd(current->mm.pgd); return 0; } struct pt_regs * task_pt_regs(struct task_struct *tsk) { unsigned long p = (unsigned long)tsk + THREAD_SIZE - sizeof(struct pt_regs); return (struct pt_regs *)p; } ================================================ FILE: src/lesson06/src/irq.S ================================================ .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ================================================ FILE: src/lesson06/src/irq.c ================================================ #include "utils.h" #include "printf.h" #include "timer.h" #include "entry.h" #include "peripherals/irq.h" const char *entry_error_messages[] = { "SYNC_INVALID_EL1t", "IRQ_INVALID_EL1t", "FIQ_INVALID_EL1t", "ERROR_INVALID_EL1T", "SYNC_INVALID_EL1h", "IRQ_INVALID_EL1h", "FIQ_INVALID_EL1h", "ERROR_INVALID_EL1h", "SYNC_INVALID_EL0_64", "IRQ_INVALID_EL0_64", "FIQ_INVALID_EL0_64", "ERROR_INVALID_EL0_64", "SYNC_INVALID_EL0_32", "IRQ_INVALID_EL0_32", "FIQ_INVALID_EL0_32", "ERROR_INVALID_EL0_32", "SYNC_ERROR", "SYSCALL_ERROR" }; void enable_interrupt_controller() { put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1); } void show_invalid_entry_message(int type, unsigned long esr, unsigned long address) { printf("%s, ESR: %x, address: %x\r\n", entry_error_messages[type], esr, address); } void handle_irq(void) { unsigned int irq = get32(IRQ_PENDING_1); switch (irq) { case (SYSTEM_TIMER_IRQ_1): handle_timer_irq(); break; default: printf("Inknown pending irq: %x\r\n", irq); } } ================================================ FILE: src/lesson06/src/kernel.c ================================================ #include #include #include "printf.h" #include "utils.h" #include "timer.h" #include "irq.h" #include "fork.h" #include "sched.h" #include "mini_uart.h" #include "sys.h" #include "user.h" void kernel_process(){ printf("Kernel process started. EL %d\r\n", get_el()); unsigned long begin = (unsigned long)&user_begin; unsigned long end = (unsigned long)&user_end; unsigned long process = (unsigned long)&user_process; int err = move_to_user_mode(begin, end - begin, process - begin); if (err < 0){ printf("Error while moving process to user mode\n\r"); } } void kernel_main() { uart_init(); init_printf(NULL, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); int res = copy_process(PF_KTHREAD, (unsigned long)&kernel_process, 0); if (res < 0) { printf("error while starting kernel process"); return; } while (1){ schedule(); } } ================================================ FILE: src/lesson06/src/linker.ld ================================================ SECTIONS { . = 0xffff000000000000; .text.boot : { *(.text.boot) } . = ALIGN(0x00001000); user_begin = .; .text.user : { build/user* (.text) } .rodata.user : { build/user* (.rodata) } .data.user : { build/user* (.data) } .bss.user : { build/user* (.bss) } user_end = .; .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; . = ALIGN(0x00001000); pg_dir = .; .data.pgd : { . += (3 * (1 << 12)); } } ================================================ FILE: src/lesson06/src/mini_uart.c ================================================ #include "utils.h" #include "peripherals/mini_uart.h" #include "peripherals/gpio.h" void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } // This function is required by printf function void putc ( void* p, char c) { uart_send(c); } ================================================ FILE: src/lesson06/src/mm.S ================================================ .globl memcpy memcpy: ldr x3, [x1], #8 str x3, [x0], #8 subs x2, x2, #8 b.gt memcpy ret .globl memzero memzero: str xzr, [x0], #8 subs x1, x1, #8 b.gt memzero ret ================================================ FILE: src/lesson06/src/mm.c ================================================ #include "mm.h" #include "arm/mmu.h" static unsigned short mem_map [ PAGING_PAGES ] = {0,}; unsigned long allocate_kernel_page() { unsigned long page = get_free_page(); if (page == 0) { return 0; } return page + VA_START; } unsigned long allocate_user_page(struct task_struct *task, unsigned long va) { unsigned long page = get_free_page(); if (page == 0) { return 0; } map_page(task, va, page); return page + VA_START; } unsigned long get_free_page() { for (int i = 0; i < PAGING_PAGES; i++){ if (mem_map[i] == 0){ mem_map[i] = 1; unsigned long page = LOW_MEMORY + i*PAGE_SIZE; memzero(page + VA_START, PAGE_SIZE); return page; } } return 0; } void free_page(unsigned long p){ mem_map[(p - LOW_MEMORY) / PAGE_SIZE] = 0; } void map_table_entry(unsigned long *pte, unsigned long va, unsigned long pa) { unsigned long index = va >> PAGE_SHIFT; index = index & (PTRS_PER_TABLE - 1); unsigned long entry = pa | MMU_PTE_FLAGS; pte[index] = entry; } unsigned long map_table(unsigned long *table, unsigned long shift, unsigned long va, int* new_table) { unsigned long index = va >> shift; index = index & (PTRS_PER_TABLE - 1); if (!table[index]){ *new_table = 1; unsigned long next_level_table = get_free_page(); unsigned long entry = next_level_table | MM_TYPE_PAGE_TABLE; table[index] = entry; return next_level_table; } else { *new_table = 0; } return table[index] & PAGE_MASK; } void map_page(struct task_struct *task, unsigned long va, unsigned long page){ unsigned long pgd; if (!task->mm.pgd) { task->mm.pgd = get_free_page(); task->mm.kernel_pages[++task->mm.kernel_pages_count] = task->mm.pgd; } pgd = task->mm.pgd; int new_table; unsigned long pud = map_table((unsigned long *)(pgd + VA_START), PGD_SHIFT, va, &new_table); if (new_table) { task->mm.kernel_pages[++task->mm.kernel_pages_count] = pud; } unsigned long pmd = map_table((unsigned long *)(pud + VA_START) , PUD_SHIFT, va, &new_table); if (new_table) { task->mm.kernel_pages[++task->mm.kernel_pages_count] = pmd; } unsigned long pte = map_table((unsigned long *)(pmd + VA_START), PMD_SHIFT, va, &new_table); if (new_table) { task->mm.kernel_pages[++task->mm.kernel_pages_count] = pte; } map_table_entry((unsigned long *)(pte + VA_START), va, page); struct user_page p = {page, va}; task->mm.user_pages[task->mm.user_pages_count++] = p; } int copy_virt_memory(struct task_struct *dst) { struct task_struct* src = current; for (int i = 0; i < src->mm.user_pages_count; i++) { unsigned long kernel_va = allocate_user_page(dst, src->mm.user_pages[i].virt_addr); if( kernel_va == 0) { return -1; } memcpy(kernel_va, src->mm.user_pages[i].virt_addr, PAGE_SIZE); } return 0; } static int ind = 1; int do_mem_abort(unsigned long addr, unsigned long esr) { unsigned long dfs = (esr & 0b111111); if ((dfs & 0b111100) == 0b100) { unsigned long page = get_free_page(); if (page == 0) { return -1; } map_page(current, addr & PAGE_MASK, page); ind++; if (ind > 2){ return -1; } return 0; } return -1; } ================================================ FILE: src/lesson06/src/printf.c ================================================ /* File: printf.c Copyright (C) 2004 Kustaa Nyholm This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "printf.h" typedef void (*putcf) (void*,char); static putcf stdout_putf; static void* stdout_putp; #ifdef PRINTF_LONG_SUPPORT static void uli2a(unsigned long int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%=d; d/=base; if (n || dgt>0|| d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void li2a (long num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } uli2a(num,10,0,bf); } #endif static void ui2a(unsigned int num, unsigned int base, int uc,char * bf) { int n=0; unsigned int d=1; while (num/d >= base) d*=base; while (d!=0) { int dgt = num / d; num%= d; d/=base; if (n || dgt>0 || d==0) { *bf++ = dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10); ++n; } } *bf=0; } static void i2a (int num, char * bf) { if (num<0) { num=-num; *bf++ = '-'; } ui2a(num,10,0,bf); } static int a2d(char ch) { if (ch>='0' && ch<='9') return ch-'0'; else if (ch>='a' && ch<='f') return ch-'a'+10; else if (ch>='A' && ch<='F') return ch-'A'+10; else return -1; } static char a2i(char ch, char** src,int base,int* nump) { char* p= *src; int num=0; int digit; while ((digit=a2d(ch))>=0) { if (digit>base) break; num=num*base+digit; ch=*p++; } *src=p; *nump=num; return ch; } static void putchw(void* putp,putcf putf,int n, char z, char* bf) { char fc=z? '0' : ' '; char ch; char* p=bf; while (*p++ && n > 0) n--; while (n-- > 0) putf(putp,fc); while ((ch= *bf++)) putf(putp,ch); } void tfp_format(void* putp,putcf putf,char *fmt, va_list va) { char bf[12]; char ch; while ((ch=*(fmt++))) { if (ch!='%') putf(putp,ch); else { char lz=0; #ifdef PRINTF_LONG_SUPPORT char lng=0; #endif int w=0; ch=*(fmt++); if (ch=='0') { ch=*(fmt++); lz=1; } if (ch>='0' && ch<='9') { ch=a2i(ch,&fmt,10,&w); } #ifdef PRINTF_LONG_SUPPORT if (ch=='l') { ch=*(fmt++); lng=1; } #endif switch (ch) { case 0: goto abort; case 'u' : { #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),10,0,bf); else #endif ui2a(va_arg(va, unsigned int),10,0,bf); putchw(putp,putf,w,lz,bf); break; } case 'd' : { #ifdef PRINTF_LONG_SUPPORT if (lng) li2a(va_arg(va, unsigned long int),bf); else #endif i2a(va_arg(va, int),bf); putchw(putp,putf,w,lz,bf); break; } case 'x': case 'X' : #ifdef PRINTF_LONG_SUPPORT if (lng) uli2a(va_arg(va, unsigned long int),16,(ch=='X'),bf); else #endif ui2a(va_arg(va, unsigned int),16,(ch=='X'),bf); putchw(putp,putf,w,lz,bf); break; case 'c' : putf(putp,(char)(va_arg(va, int))); break; case 's' : putchw(putp,putf,w,0,va_arg(va, char*)); break; case '%' : putf(putp,ch); default: break; } } } abort:; } void init_printf(void* putp,void (*putf) (void*,char)) { stdout_putf=putf; stdout_putp=putp; } void tfp_printf(char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(stdout_putp,stdout_putf,fmt,va); va_end(va); } static void putcp(void* p,char c) { *(*((char**)p))++ = c; } void tfp_sprintf(char* s,char *fmt, ...) { va_list va; va_start(va,fmt); tfp_format(&s,putcp,fmt,va); putcp(&s,0); va_end(va); } ================================================ FILE: src/lesson06/src/sched.S ================================================ #include "sched.h" .globl cpu_switch_to cpu_switch_to: mov x10, #THREAD_CPU_CONTEXT add x8, x0, x10 mov x9, sp stp x19, x20, [x8], #16 // store callee-saved registers stp x21, x22, [x8], #16 stp x23, x24, [x8], #16 stp x25, x26, [x8], #16 stp x27, x28, [x8], #16 stp x29, x9, [x8], #16 str x30, [x8] add x8, x1, x10 ldp x19, x20, [x8], #16 // restore callee-saved registers ldp x21, x22, [x8], #16 ldp x23, x24, [x8], #16 ldp x25, x26, [x8], #16 ldp x27, x28, [x8], #16 ldp x29, x9, [x8], #16 ldr x30, [x8] mov sp, x9 ret ================================================ FILE: src/lesson06/src/sched.c ================================================ #include "sched.h" #include "irq.h" #include "printf.h" #include "utils.h" #include "mm.h" static struct task_struct init_task = INIT_TASK; struct task_struct *current = &(init_task); struct task_struct * task[NR_TASKS] = {&(init_task), }; int nr_tasks = 1; void preempt_disable(void) { current->preempt_count++; } void preempt_enable(void) { current->preempt_count--; } void _schedule(void) { preempt_disable(); int next,c; struct task_struct * p; while (1) { c = -1; next = 0; for (int i = 0; i < NR_TASKS; i++){ p = task[i]; if (p && p->state == TASK_RUNNING && p->counter > c) { c = p->counter; next = i; } } if (c) { break; } for (int i = 0; i < NR_TASKS; i++) { p = task[i]; if (p) { p->counter = (p->counter >> 1) + p->priority; } } } switch_to(task[next]); preempt_enable(); } void schedule(void) { current->counter = 0; _schedule(); } void switch_to(struct task_struct * next) { if (current == next) return; struct task_struct * prev = current; current = next; set_pgd(next->mm.pgd); cpu_switch_to(prev, next); } void schedule_tail(void) { preempt_enable(); } void timer_tick() { --current->counter; if (current->counter>0 || current->preempt_count >0) { return; } current->counter=0; enable_irq(); _schedule(); disable_irq(); } void exit_process(){ preempt_disable(); for (int i = 0; i < NR_TASKS; i++){ if (task[i] == current) { task[i]->state = TASK_ZOMBIE; break; } } preempt_enable(); schedule(); } ================================================ FILE: src/lesson06/src/sys.c ================================================ #include "fork.h" #include "printf.h" #include "utils.h" #include "sched.h" #include "mm.h" void sys_write(char * buf){ printf(buf); } int sys_fork(){ return copy_process(0, 0, 0); } void sys_exit(){ exit_process(); } void * const sys_call_table[] = {sys_write, sys_fork, sys_exit}; ================================================ FILE: src/lesson06/src/timer.c ================================================ #include "utils.h" #include "printf.h" #include "sched.h" #include "peripherals/timer.h" const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); timer_tick(); } ================================================ FILE: src/lesson06/src/user.c ================================================ #include "user_sys.h" #include "user.h" #include "printf.h" void loop(char* str) { char buf[2] = {""}; while (1){ for (int i = 0; i < 5; i++){ buf[0] = str[i]; call_sys_write(buf); user_delay(1000000); } } } void user_process() { call_sys_write("User process\n\r"); int pid = call_sys_fork(); if (pid < 0) { call_sys_write("Error during fork\n\r"); call_sys_exit(); return; } if (pid == 0){ loop("abcde"); } else { loop("12345"); } } ================================================ FILE: src/lesson06/src/user_sys.S ================================================ .section ".text.user" .set SYS_WRITE_NUMBER, 0 // syscal numbers .set SYS_FORK_NUMBER, 1 .set SYS_EXIT_NUMBER, 2 .globl user_delay user_delay: subs x0, x0, #1 bne user_delay ret .globl call_sys_write call_sys_write: mov w8, #SYS_WRITE_NUMBER svc #0 ret .globl call_sys_exit call_sys_exit: mov w8, #SYS_EXIT_NUMBER svc #0 ret .globl call_sys_fork call_sys_fork: mov w8, #SYS_FORK_NUMBER svc #0 ret ================================================ FILE: src/lesson06/src/utils.S ================================================ .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret .globl put32 put32: str w1,[x0] ret .globl get32 get32: ldr w0,[x0] ret .globl delay delay: subs x0, x0, #1 bne delay ret .globl set_pgd set_pgd: msr ttbr0_el1, x0 tlbi vmalle1is DSB ISH // ensure completion of TLB invalidation isb ret .globl get_pgd get_pgd: mov x1, 0 ldr x0, [x1] mov x0, 0x1000 msr ttbr0_el1, x0 ldr x0, [x1] ret ================================================ FILE: translations/ko/Introduction.md ================================================ ## 라즈베리파이 OS 프로젝트 소개 또는 효율적으로 운영체제 개발을 학습하는 방법 몇 년 전, 처음으로 리눅스 커널의 소스 코드를 열었습니다. 그 당시 나는 다소 능숙한 소프트웨어 개발자라고 생각했습니다. 어셈블리어와 C 프로그래밍을 조금 알고 있었고, 공정 스케줄링, 가상 메모리 관리 등 주요 운영 체제 개념에 대한 높은 수준의 이해를 하고 있었습니다. 하지만, 첫 번째 시도는 완전히 실패했습니다. 나는 거의 아무것도 이해하지 못했습니다. 내가 다뤄야 하는 다른 소프트웨어 프로젝트의 경우, 나는 보통 매우 좋은 접근방식을 가지고 있습니다. 프로그램의 시작점을 찾은 다음, 내가 관심 있는 모든 세부사항을 이해하기 위해 필요한 만큼 깊이 들어가면서 소스 코드를 읽기 시작합니다. 이 접근법은 매우 좋지만, 운영체제만큼은 그렇지 않았습니다. 단지 진입점을 찾는 데만 일주일이 넘게 걸린 것이 아니라, 중요한 문제는 내가 몇 줄의 코드를 보고 있는 상황에서 그 코드들이 무엇을 하고 있는지 어떤 단서라도 찾아낼 수 있는 방법이 없다는 것을 내 자신을 발견했다는 점이었습니다. 이것은 특히 낮은 레벨의 어셈블리어 소스 코드에 대해서는 맞았지만, 내가 조사하려고 했던 시스템의 다른 어떤 부분에서는 효과가 없었습니다. 나는 처음부터 문제가 복잡해 보인다고 해서 그것을 무시하는 생각을 좋아하지 않습니다. 게다가 나는 복잡한 문제는 없다고 믿습니다. 대신에, 우리가 효율적으로 어떻게 해결해야 할지 모르는 많은 문제가 있기 때문에, 나는 OS 개발을 일반적이고 특히 Linux에서 배울 수 있는 효과적인 방법을 찾기 시작했습니다. ### OS 개발 학습에서의 어려움 리눅스 커널 개발에 관한 수많은 책과 문서들이 있지만, 내가 원하는 학습 경험을 제공하지 않았습니다. 그 자료들의 절반은 너무 피상적이었고 나는 그것을 이미 알고 있었습니다. 나머지 절반은 커널 소스 코드를 탐구하는 것과 매우 유사한 문제를 가지고 있었습니다. 즉, 책이 충분히 깊이 들어가자마자, 세부사항의 90%는 핵심 개념과 무관해보이고 일부 보안, 성능 또는 레거시 고려사항과 Linux 커널이 지원하는 수백만가지의 특징들과 관련있는 것으로 보였습니다. 결과적으로, 핵심 운영체제 개념을 배우는 대신에 항상 세부적인 사항을 파고들게 됩니다. 애초에 왜 운영체제 개발을 배워야 하는지 궁금할 겁니다. 나에게 있어서 주요한 이유는 내가 항상 어떻게 일이 돌아가는지에 관심이 있었기 때문입니다. 단순히 호기심만이 아닙니다. 여러분이 하고 있는 일이 더 어려울수록, 종종 운영 체제 수준까지 추적하기 시작합니다. 모든 것이 어떻게 낮은 수준에서 작동하는지 이해하지 못한다면, 완전히 정보에 입각한 결정을 내릴 수 없습니다. 또 다른 것은, 만약 여러분이 정말로 기술적 도전을 좋아한다면, OS 개발로 일하는 것은 여러분에게 신나는 일이 될 수 있다는 것입니다. 다음 질문은 "왜 리눅스를 사용하는가?" 입니다. 다른 운영체제는 아마도 접근하기가 더 쉬울 것입니다. 정답은 적어도 어떤 면에서는 현재 내가 하고 있는 일이 미래에 할 것으로 예상되는 일에 관련되기를 바란다는 것입니다. 이 점에서 리눅스는 완벽합니다. 왜냐하면 요즘은 작은 IoT 장치에서부터 큰 서버에 이르기까지 모든 것이 리눅스를 실행하는 경향이 있기 때문입니다. 내가 리눅스 커널 개발에 관한 대부분의 책들이 나에게 좋지 않았다고 말했을 때 정직하지 못했습니다. OS 개발의 초보자임에도 불구하고 충분히 이해할 수 있는 실제 소스 코드를 이용해 몇 가지 본질적인 개념을 설명한 책이 있었습니다. 이 책은 "Linux Device Drivers"이며 리눅스 커널에 관한 가장 유명한 기술 서적 중 하나입니다. 그것은 당신이 컴파일하고 가지고 놀 수 있는 간단한 드라이버의 소스 코드를 도입하는 것으로 시작됩니다. 그런 다음 새로운 드라이버 관련 개념을 하나씩 소개하기 시작하고 이러한 새로운 개념을 사용하기 위해 드라이버의 소스 코드를 수정하는 방법을 설명합니다. 그것이 바로 "좋은 학습 경험"이라고 부르는 것입니다. 이 책의 유일한 문제는 드라이버 개발에 초점을 맞추고 있으며 핵심 커널 구현 세부사항에 대해서는 거의 언급하지 않는다는 점입니다. 하지만 왜 아무도 커널 개발자들에 대해서도 비슷한 책을 만들지 않았을까요? 나는 경험을 위한 기본으로서 리눅스 커널 소스 코드를 사용하기 때문이라고 생각합니다. 그때에 이것은 가능하지 않습니다. 리눅스 소스에 대해서는 간단한 것이 없기 때문에 단순한 시작점으로 사용할 수 있는 함수나 구조나 모듈이 없습니다. 또한 소스 코드에서는 모든 것이 서로 매우 밀집하게 연관되어 있기 때문에 새로운 개념을 한 번에 하나씩 소개할 수도 없습니다. 이것을 깨달은 이후, 리눅스 커널이 너무 방대하고 복잡해서 OS 개발을 위한 출발점으로 이용될 수 없다면, 학습 목적을 위해 명시적으로 설계될 나만의 OS를 구현하는 것이 어떨까하는 생각이 떠올랐습니다. 이렇게 하면 OS를 충분히 간단하게 만들어 좋은 학습 경험을 제공할 수 있다고 생각합니다. 또한, 만약 이 OS가 리눅스 커널 소스의 다른 부분을 복사하고 단순화함으로써 대부분 구현된다면, 이를 리눅스 커널을 배우는 출발점으로 사용하는 것은 매우 쉬울 것입니다. OS 외에도 주요 OS 개발 개념을 가르치고 OS 소스 코드를 충분히 설명하는 강의 시리즈를 쓰기로 했습니다. ### OS 요건 나는 나중에 [RPi OS](https://github.com/s-matyukevich/raspberry-pi-os)가 될 프로젝트를 시작했습니다. 가장 먼저 해야 한 일은 내가 "기본적"으로 간주하는 커널 개발의 어떤 부분, 그리고 내가 그렇게 본질적이지 않고 생략할 수 있다고 간주하는 요소들을 결정하는 것이었습니다(적어도 초기에는). 내가 알기로는, 각 운영체제는 다음의 두 가지 기본 목표를 가지고 있습니다. 1. 사용자 프로세스를 격리된 상태로 실행하라. 2. 각 사용자 프로세스에서 시스템 하드웨어에 대한 통합적인 시각을 제공하라. 첫 번째 요건을 충족시키기 위해서는 RPi OS에는 자체 스케줄러가 있어야 합니다. 스케줄러를 구현하려면 타이머 인터럽트도 처리해야 합니다. 두 번째 요건는 OS가 일부 드라이버를 지원하고 이를 사용자 애플리케이션에 노출하기 위해 시스템 호출을 제공하는 것을 의미합니다. 이것은 초보자를 위한 것이기 때문에 복잡한 하드웨어로 작업하고 싶지 않습니다. 그래서 내가 신경 쓰는 드라이버는 화면에 뭔가를 쓰고 키보드에서 사용자 입력을 읽을 수 있는 드라이버뿐입니다. 또한, OS는 사용자 프로그램을 로드하고 실행할 수 있어야 하므로, 당연히 어떤 종류의 파일 시스템을 지원하고 어떤 종류의 실행 파일 형식을 이해할 수 있어야 합니다. OS가 기본적인 네트워킹을 지원할 수 있다면 좋겠지만, 초보자를 위한 내용으로 그것에 집중하고 싶지는 않습니다. 그래서 그것들은 기본적으로 내가 "모든 운영체제의 핵심 개념"으로 식별할 수 있는 것들입니다. 이제 내가 무시하고 싶은 것들을 살펴봅시다. 1. **성능** 나는 OS에서 어떤 정교한 알고리즘도 사용하고 싶지 않습니다. 또한 단순함을 위해 모든 캐시와 다른 성능 최적화 기법들을 비활성화할 것입니다. 1. **보안** RPi OS에는 최소한 하나의 보안 기능인 가상 메모리가 있는 것은 맞습니다. 다른 것은 무시될 수 있습니다. 1. **멀티프로세싱과 동기화** 나는 OS가 단일 프로세서 코어에 실행되어 매우 기쁩니다. 특히 이것은 엄청난 복잡성의 원인인 동기화의 문제를 없앨 수 있게 해줍니다. 1. **다양한 아키텍쳐들과 다른 유형의 디바이스들에 대한 지원** 다음 섹션에서 자세한 내용을 참조하세요. 1. **프로젝트 지원 운영체제가지원하는 수백만 가지의 특징** ### 라즈베리 파이가 어떻게 작동하게 되었는가 나는 이미 RPi OS가 여러 개의 컴퓨터 아키텍쳐나 많은 다른 디바이스들을 지원하지 않기를 바란다고 말했습니다. 리눅스 커널 드라이버 모델을 파고들자 이것에 대해 더욱 강하게 느꼈습니다. 유사한 목적을 가진 장치도 구현 세부사항에 따라 크게 달라질 수 있습니다. 이것은 다양한 드라이버 유형을 중심으로 간단한 추상화를 고안하는 것과 드라이버 소스 코드를 재사용하는 것을 매우 어렵게 만듭니다. 이것이 리눅스 커널의 복잡성의 주요 원인 중 하나처럼 보이며, 나는 반드시 RPi OS에서 그것을 피하고 싶습니다. 그런데 그땐 어떤 컴퓨터를 써야하는가? 나는 내 작업용 노트북을 사용하여 베어메탈 프로그램을 테스트하고 싶지는 않습니다. 왜냐하면 솔직히 말해서 그것이 살아남을 수 있을지 확실할 수 없기 때문입니다. 더 중요한 것은, 나는 사람들이 내 OS 개발 연습에 따르려고 비싼 노트북을 구입하는 것을 원하지 않는다고 생각합니다(어쨋든 아무도 이것을 하지 않을 것이라고 생각합니다). 에뮬레이터는 좋은 선택으로 보이지만, 베어메탈 프로그래밍을 하기보다는 내가 뭔가 실제적인 일을 하고 있는다는 느낌을 주기 때문에 나는 실제적인 디바이스로 작업하고 싶습니다. 나는 끝내 라즈베리 파이, 특히 [라즈베리 파이3 모델 B](https://www.raspberrypi.org/products/raspberry-pi-3-model-b/).를 사용하게 되었습니다. 이 장치를 사용하는 것은 여러가지로 이상적인 선택입니다. 1. 이것은 약 35달러가 듭니다. 나는 이것이 적당한 가격이라고 생각합니다. 1. 이 디바이스는 학습용으로 설계되었습니다. 그것의 내부 구조는 가능한 한 간단하고, 그것은 나의 요구에 완벽하게 부합합니다. 1. 이 디바이스는 ARM v8 아키텍쳐를 사용합니다. 이것은 단순한 RISC 아키텍쳐로서 OS 작성자들의 요구에 매우 잘 적응하고 있으며, 예를 들어 인기 있는 x86 아키텍처처럼 많은 레거시 요구사항을 가지고 있지 않습니다. 내 말을 믿기지 않으면 리눅스 커널의 /arch/arm64 및 /arch/x86 폴더에 있는 소스 코드의 양을 보고 확인할 수 있습니다. 이 OS는 라즈베리 파이의 이전 버전들과 호환되지 않는데, 그 중 어느 것도 64비트 ARM V8 아키텍처를 지원하지 않기 때문입니다. 그러나 미래의 모든 디바이스에 대한 지원은 사소해야한다고 생각합니다. ### 커뮤니티와 함께 작업하기 어떤 기술 서적의 가장 큰 단점은 발매 직후 각 책이 쓸모없게 된다는 것입니다. 오늘날 기술은 너무 빨리 진화하고 있어서 책 작가들이 그것을 따라가는 것은 거의 불가능합니다. 그래서 나는 인터넷에서 자유롭게 구할 수 있고 독자들이 콘텐츠 생성과 검증에 참여하도록 장려하는 책인 "오픈 소스 북"이라는 아이디어를 좋아합니다. 만약 책 내용이 깃헙에서 이용 가능하다면, 어떤 독자도 새로운 코드 샘플을 수정하고 개발하며, 책 내용을 업데이트하고, 새로운 장을 쓰는 데 참여하기가 매우 쉽습니다. 지금 이 프로젝트가 완벽하지 않다는 것을 알고 있습니다. 그리고 프로젝트를 쓸 당시에는 심지어 완성되지도 않았습니다. 그러나 나는 여전히 그것을 지금 진행하고 싶습니다. 왜냐하면 나는 공동체의 도움으로 그 프로젝트를 더 빨리 완성할 수 있을 뿐만 아니라 처음부터 그랬던 것보다 훨씬 더 좋고 더 유용하게 만들 수 있기를 희망하기 때문입니다. ##### 이전 페이지 [Main Page](https://github.com/s-matyukevich/raspberry-pi-os#learning-operating-system-development-using-linux-kernel-and-raspberry-pi) ##### 다음 페이지 [Prerequisites](../ko/Prerequisites.md) ##### 추가 사항 이 문서는 훌륭한 오픈소스 운영체제 학습 프로젝트인 Sergey Matyukevich의 문서를 영어에 능숙하지 않은 한국인들이 학습할 수 있도록 번역한 것입니다. 오타 및 오역이 있을 수 있습니다. Sergey Matyukevich는 한국어를 능숙하게 다루지 못합니다. 대신 저에게 elxm6123@gmail.com으로 연락해주세요. ================================================ FILE: translations/ko/Prerequisites.md ================================================ ## 필수 구성 요소 ### 1. [라즈베리 파이 3 모델 B](https://www.raspberrypi.org/products/raspberry-pi-3-model-b/) ARMv8 아키텍처를 지원하는 64비트 프로세서를 사용하도록 설계되었으며, 그러한 프로세서는 라즈베리 파이 3에서만 사용할 수 있기 때문에 이전 버전의 라즈베리 파이에서는 이 튜토리얼과 함께 작동하지 않을 것입니다. [라즈베리 파이 3 모델 B+](https://www.raspberrypi.org/products/raspberry-pi-3-model-b-plus/)를 포함한 최신 버전은 아직 테스트하지 않았지만 잘 작동할 것입니다. ### 2. [USB와 TTL 시리얼 케이블 연결](https://www.amazon.com/s/ref=nb_sb_noss_2?url=search-alias%3Daps&field-keywords=usb+to+ttl+serial+cable&rh=i%3Aaps%2Ck%3Ausb+to+ttl+serial+cable) 시리얼 케이블을 연결한 후에는 연결을 테스트해야 합니다. 이 가이드에 따르라고 권고하기 전에 이 작업을 수행한 적이 없는 경우, 시리얼 케이블을 통해 라즈베리 파이를 연결하는 과정을 자세히 설명합니다. 이 가이드에서는 시리얼 케이블을 사용하여 라즈베리 파이에 전력을 공급하는 방법도 설명합니다. RPi OS는 그러한 종류의 설정과 잘 작동하지만, 이 경우에는 케이블을 꽂은 후 바로 단말 에뮬레이터를 실행해야합니다. 자세한 내용은 [이 문제](https://github.com/s-matyukevich/raspberry-pi-os/issues/2)를 확인하십시오. ### 3. [라즈비언 OS](https://www.raspberrypi.org/downloads/raspbian/)가 설치된 [SD 카드](https://www.raspberrypi.org/documentation/installation/sd-cards.md) 우리는 처음에 USB와 TTL 케이블의 연결을 테스트하기 위해 라즈비언이 필요합니다. 또 다른 이유는 설치 후 SD 카드를 올바른 방식으로 포맷한 상태로 만들어야하기 때문입니다. ### 4. 도커 엄밀히 말하면 도커는 필수는 아닙니다. Docker를 사용하여 소스 코드를 만드는 것이 편리할 뿐이며, 특히 Mac과 Windows 사용자에게는 더욱 그러합니다. 수업마다 틀이 잡혀 있습니다. sh 스크립트(또는 윈도우즈 사용자를 위한 build.bat) 이 스크립트는 도커를 사용하여 수업의 소스 코드를 작성합니다. 플랫폼용 도커를 설치하는 방법은 [공식 도커 웹 사이트](https://docs.docker.com/engine/installation/)를 참조하십시오. 몇 가지 이유로 인해 도커 사용을 피하고 싶다면 [make 유틸리티](http://www.math.tau.ac.il/~danha/courses/software1/make-intro.html)와 `aarch64-linux-gnu` 툴 체인을 설치할 수 있습니다. Ubuntu를 사용하는 경우 `gcc-aarch64-linux-gnu` 및 `build-essential` 패키지를 설치하기만 하면 됩니다. ##### 이전 페이지 [Introduction](../ko/Introduction.md) ##### 다음 페이지 1.1 [Kernel Initialization: Introducing RPi OS, or bare metal "Hello, world!"](../ko/lesson01/rpi-os.md) ##### 추가 사항 이 문서는 훌륭한 오픈소스 운영체제 학습 프로젝트인 Sergey Matyukevich의 문서를 영어에 능숙하지 않은 한국인들이 학습할 수 있도록 번역한 것입니다. 오타 및 오역이 있을 수 있습니다. Sergey Matyukevich는 한국어를 능숙하게 다루지 못합니다. 대신 저에게 elxm6123@gmail.com으로 연락해주세요. ================================================ FILE: translations/ko/lesson01/exercises.md ================================================ ## 1.5: 연습 연습하기는 선택사항이지만 소스코드를 조금 실험해 볼 것을 강력히 권합니다. 연습 중 하나를 완료할 수 있는 경우 소스 코드를 다른 사람과 공유하십시오. 1. 상수 `baud_rate`를 도입하여 이 상수를 사용하여 필요한 Mini UART 레지스터 값을 계산하십시오. 프로그램이 115200 이외의 보레이트를 사용하여 작동할 수 있는지 확인하십시오. 2. Mini UART 대신 UART 장치를 사용하도록 OS 코드를 변경하십시오. UART 레지스터에 액세스하는 방법과 GPIO 핀을 구성하는 방법을 알아보려면 `BCM2837 ARM Peripherals` 메뉴얼을 사용하십시오. 3. 프로세서 코어 4개를 모두 사용해 보십시오. OS는 모든 코어에 대해 프로세서 <프로세서 인덱스>에서 Hello를 출력해야 합니다. 코어별로 별도의 스택을 설정하고 Mini UART가 한 번만 초기화되었는지 확인하십시오. 동기화에 글로벌 변수와 `delay` 함수의 조합을 사용할 수 있습니다. 4. qemu에서 실행되도록 레슨 01을 적용하십시오. [이 이슈](https://github.com/s-matyukevich/raspberry-pi-os/issues/8)를 참고해보세요. ##### 이전 페이지 1.4 [Kernel Initialization: Linux startup sequence](./kernel-startup.md) ##### 다음 페이지 2.1 [Processor initialization: RPi OS](../lesson02/rpi-os.md) ##### 추가 사항 이 문서는 훌륭한 오픈소스 운영체제 학습 프로젝트인 Sergey Matyukevich의 문서를 영어에 능숙하지 않은 한국인들이 학습할 수 있도록 번역한 것입니다. 오타 및 오역이 있을 수 있습니다. Sergey Matyukevich는 한국어를 능숙하게 다루지 못합니다. 대신 저에게 elxm6123@gmail.com으로 연락해주세요. ================================================ FILE: translations/ko/lesson01/linux/build-system.md ================================================ ## 1.3: 커널 빌드 시스템 리눅스 커널 구조를 조사한 후, 어떻게 그것을 만들고 실행할 수 있는지 조사하는 데 시간을 들일 가치가 있습니다. 리눅스 makefile은 복잡하지만 리눅스 makefile은 커널을 만들기 위해 `make` 유틸리티를 사용합니다. 우리가 makefile을 살펴보기 전에, "kbuild"라고 불리는 Linux 빌드 시스템에 대한 몇 가지 중요한 개념을 배워봅시다. ### 몇 가지 필수적인 kbuild 개념 * kbuild 변수를 사용하여 빌드 프로세스를 사용자 정의할 수 있습니다. 이 변수들은 `Kconfig` 파일에 정의돼 있습니다. 여기서 변수 자체와 변수 기본값을 정의할 수 있습니다. 변수는 문자열, 부울 및 정수를 포함하여 다른 유형을 가질 수 있습니다. Kconfig 파일에서 변수 간의 종속성을 정의할 수도 있습니다(예를 들어 변수 X를 선택하면 변수 Y가 암묵적으로 선택된다고 말할 수 있습니다). [arm64 Kconfig file](https://github.com/torvalds/linux/tree/v4.14/arch/arm64/Kconfig)를 예로 볼 수 있습니다. 이 파일은 'arm64' 아키텍처에 특정한 모든 변수를 정의합니다. `Kconfig` 기능은 표준 make의 일부가 아니며 리눅스 makefile에서 구현됩니다. `Kconfig`에서 정의한 변수는 중첩된 파일뿐만 아니라 커널 소스 코드에도 노출됩니다. 커널 구성 단계에서 변수 값을 설정할 수 있습니다. * 리눅스는 재귀적인 빌딩을 합니다. 리눅스 커널의 각 하위 폴더는 자체 `Makefile`과 `Kconfig`를 하위 폴더가 정의할 수 있다는 뜻입니다. 중첩된 대부분의 Makefile들은 매우 단순하며 어떤 객체 파일을 컴파일해야 하는지 정의하기만 하면 됩니다. 일반적으로 그러한 정의는 다음과 같은 형식을 가지고 있습니다. ``` obj-$(SOME_CONFIG_VARIABLE) += some_file.o ``` 이 정의는 `SOME_CONFIG_VARIABLE`을 설정한 경우에만 `some_file.c`를 컴파일하고 커널에 링크한다는 것을 의미합니다. 파일을 무조건 컴파일하고 연결하려면 이전 정의를 이렇게 변경해야 합니다. ``` obj-y += some_file.o ``` 중첩 Makefile의 예는 [여기](https://github.com/torvalds/linux/tree/v4.14/kernel/Makefile)에서 찾을 수 있습니다.An example of the nested Makefile can be found [here](https://github.com/torvalds/linux/tree/v4.14/kernel/Makefile). * 더 나아가기 전에, 기본적인 빌딩 규칙의 구조를 이해하고 make 용어에 익숙해져야 합니다. 공통 규칙 구조는 다음 도표에 설명되어 있습니다. ``` targets : prerequisites recipe … ``` * `targets` 스페이스들에 의해서 분리된 파일 이름들입니다. 타겟은 규칙이 수행된 이후 생성됩니다. 보통 타겟 당 하나의 규칙이 존재합니다. * `prerequisites` 은 타겟들이 업데이트가 필요한지 트래킹 할 수 있게 해주는 파일들입니다. * `recipe` 는 bash 스크립트입니다. prerequisites 중 일부가 업데이트되었을 때 호출됩니다. recipe은 목표물을 생성하는데 책임이 있습니다. * 타겟과 prerequisites는 모두 와일드 카드("%")를 포함할 수 있습니다. 와일드 카드를 사용할 경우, 각각의 캐시된 prerequisites에 대해 별도의 recipe를 실행합니다. 이 경우, `$<`와 `$@` 변수를 사용하여 recipe 내부의 prerequisites와 타겟을 참조할 수 있습니다. 우리는 이미 [RPi OS makefile](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson01/Makefile#L14)을 완성했습니다. 좀 더 추가적인 make 규칙을 알고 싶다면 [공식 문서](https://www.gnu.org/software/make/manual/html_node/Rule-Syntax.html#Rule-Syntax)를 참조하세요. * `make` 는 prerequisites가 변경되었는지 탐지하고 재구축해야 하는 대상만을 업데이트할 때 유용합니다. 그러나 recipe이 동적으로 업데이트되면 `make` 는 이 변화를 감지할 수 없습니다. 어떻게 이런 일이 일어날 수 있을까요? 메우 쉽게 한 가지 좋은 예는 일부 구성 변수를 변경할 때 레시피에 추가 옵션을 추가하는 것입니다. 기본적으로, 이 경우, `make`는 이전에 생성된 객체 파일을 재구성하지 않습니다. 그 prerequisites은 변경되지 않았고, recipe만 업데이트되었기 때문입니다. 이 동작을 해결하기 위해서 리눅스는 [if_changed](https://github.com/torvalds/linux/blob/v4.14/scripts/Kbuild.include#L264) 함수를 도입했습니다. 어떻게 작동하는지 보기 위해서 다음의 예를 생각해봅시다. ``` cmd_compile = gcc $(flags) -o $@ $< %.o: %.c FORCE $(call if_changed,compile) ``` 여기서 각 `.c` 파일에 대해 compile이라는 인수로 `if_changed"`함수를 호출하여 해당 `.o` 파일을 작성합니다. 만약 'cmd_compile' 명령이 실행되고 객체 파일이 재생성되는 경우에 `if_changed`는 `cmd_compile` 변수(첫 번째 인수에 `cmd_` 접두사를 추가함)를 찾아 이 변수가 마지막 실행 이후 업데이트 되었는지 또는 prerequisites이 변경되었는지 확인합니다. 샘플 규칙은 소스 .c 파일과 FORCE라는 두 가지 필수조건이 있습니다. `FORCE`는 `make` 명령을 부를 때마다 recipe를 불러야 하는 특별한 prerequiste입니다. 만약 없다면 레시피는 .c 파일을 바꿔야 합니다. `FORCE` 타겟에 대해서 [여기](https://www.gnu.org/software/make/manual/html_node/Force-Targets.html)를 통해 좀 더 읽을 수 있습니다. ### 커널 빌딩하기 이제 Linux 빌드 시스템에 대한 몇 가지 중요한 개념을 배웠을 때 make 명령을 입력한 후 정확히 어떤 일이 벌어지고 있는지 알아봅시다. 이 과정은 매우 복잡하고 많은 세부사항들을 포함하고 있는데, 우리는 대부분 생략할 것입니다. 우리의 목표는 2개의 질문에 답하는 것입니다. 1. 소스 파일이 객체 파일로 정확히 어떻게 컴파일되는가? 1. 오브젝트 파일이 OS 이미지에 어떻게 연결되어 있는가? 우리는 두 번째 문제를 먼저 다룰 것입니다. #### 링크 스테이지 * `make help`명령의 결과로 부터 너가 보려고 할때, 그 커널 빌딩의 책임이 있는 기본 결과는 `vmlinx`라고 부릅니다. * `vmlinux` 타겟 정의는 [여기](https://github.com/torvalds/linux/blob/v4.14/Makefile#L1004)에서 찾을 수 있고 이것은 아래와 같습니다. ``` cmd_link-vmlinux = \ $(CONFIG_SHELL) $< $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux) ; \ $(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true) vmlinux: scripts/link-vmlinux.sh vmlinux_prereq $(vmlinux-deps) FORCE +$(call if_changed,link-vmlinux) ``` 이 타겟은 이미 친숙한 `if_changed` 함수를 사용합니다. 일부 prerequsities를 업데이트될 때마다 `cmd_link-vmlinux` 커맨드는 실행됩니다. 이 커맨드는 [scripts/link-vmlinux.sh](https://github.com/torvalds/linux/blob/v4.14/scripts/link-vmlinux.sh) 스크르립트를 실행합니다. ( [$<](https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html)의 사용은 `cmd_link-vmlinux` 명령어 안에 자동적인 변수임을 강조합니다.). 이것은 또한 [postlink script](https://github.com/torvalds/linux/blob/v4.14/Documentation/kbuild/makefiles.txt#L1229)라는 구체적인 아키텍처를 실행합니다, 그러나 이것까지 신경쓸 필요는 없습니다. * [scripts/link-vmlinux.sh](https://github.com/torvalds/linux/blob/v4.14/scripts/link-vmlinux.sh)을 실행할 때 3가지 오브젝트 파일이 이미 작되어 있고 해당 위치가 세 가지 변수(`KBUILD_VMLINUX_INIT`, `KBUILD_VMLINUX_MAIN`, `KBUILD_VMLINUX_LIBS`)로 작성되어 있음을 가정합니다. * `link-vmlinux.sh` 스크립트는 먼저 `thin archive`을 모든 개체 파일로부터 만듭니다. `thin archive`는 개체 파일 집합과 결합된 기호 표를 참조하는 특별한 오브젝트 파일입니다. 이것은 [archive_builtin](https://github.com/torvalds/linux/blob/v4.14/scripts/link-vmlinux.sh#L56)함수 내에서 이루어집니다. `thin archive`함수를 만들기 위해서 이 함수는 [ar](https://sourceware.org/binutils/docs/binutils/ar.html) 유틸리를 사용합니다. 생성된 `thin archive`은 `built-in.o` 파일 안에 저장되고 링커에 의해서 이해될만한 포맷을 가지고 있고 그래서 이것은 다른 일반적인 오브젝트 파일로 사용할 수 있도록 합니다. * 다음은 [modpost_link](https://github.com/torvalds/linux/blob/v4.14/scripts/link-vmlinux.sh#L69) 라고 부릅니다 . 이 함수는 링커를 호출하고 `vmlinux.o` 오브젝트 파일을 생성합니다. 우리는 [Section mismatch analysis](https://github.com/torvalds/linux/blob/v4.14/lib/Kconfig.debug#L308) 이 오브젝트 파일이 필요합니다. 이 분석은 [modpost](https://github.com/torvalds/linux/tree/v4.14/scripts/mod)에 의해서 수행되는 프로그램이고 [이 라인](https://github.com/torvalds/linux/blob/v4.14/scripts/link-vmlinux.sh#L260)에서 실행됩니다. * 다음 커널 기호 테이블이 생성됩니다. 이것은 `vmlinux` 바이너리 안에서의 위치 뿐만 아니라 모든 함수와 전역 변수에 대한 정보를 포함하고 있습니다. 주요한 작업은 [kallsyms](https://github.com/torvalds/linux/blob/v4.14/scripts/link-vmlinux.sh#L146) 함수 내에서 이뤄집니다. 이 함수는 첫번째로 `vmlinux`바이너리에서 모든 기호를 추출하기 위해서 [nm](https://sourceware.org/binutils/docs/binutils/nm.html)을 사용합니다. 그때에 이것은 리눅스 커널에서 이해할 수 있는 모든 기호를 포함하는 특수 어셈블러 파일을 발생시키기 위해 [scripts/kallsyms](https://github.com/torvalds/linux/blob/v4.14/scripts/kallsyms.c) 유틸리티를 사용합니다. 다음으로, 이 어셈블러 파일을 컴파일하여 원래의 바이너리로 연결합니다. 이 프로세스는 일부 기호의 최종 링크 주소를 변경할 수 있기 때문에 여러 번 반복됩니다. 커널 기호 테이블의 정보는 런타임에 '/proc/kallsyms' 파일을 생성하는 데 사용됩니다. * 마지막으로 `vmlinux` 바이너리가 준비되었고 `System.map`이 제작되었습니다. `System.map`는 [kernel oops](https://en.wikipedia.org/wiki/Linux_kernel_oops)와 같은 정보가 들어 있지만, 정적 파일이며 `/proc/kallsyms`와 달리 런타임에 생성되지 않습니다. 같은 `nm` 유틸리티는 `System.map`를 빌드하기 위해서 사용됩니다. 이것은 [here](https://github.com/torvalds/linux/blob/v4.14/scripts/mksysmap#L44)에서 수행됩니다. #### 빌드 스테이지 * 이제 한 걸음 뒤로 물러나 소스 코드 파일이 오브젝트 파일로 어떻게 컴파일되는지 살펴보자. 'vmlinux' 타겟의 prerequisites 중 하나는 '$(vmlinux-deps)' 변수라는 것을 기억할 것입니다. 이제 메인 Linux makefile에서 몇 개의 관련 라인을 복사하여 이 변수가 작성되는 방법을 시연해 보이도록 하겠습니다. ``` init-y := init/ drivers-y := drivers/ sound/ firmware/ net-y := net/ libs-y := lib/ core-y := usr/ core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/ init-y := $(patsubst %/, %/built-in.o, $(init-y)) core-y := $(patsubst %/, %/built-in.o, $(core-y)) drivers-y := $(patsubst %/, %/built-in.o, $(drivers-y)) net-y := $(patsubst %/, %/built-in.o, $(net-y)) export KBUILD_VMLINUX_INIT := $(head-y) $(init-y) export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y2) $(drivers-y) $(net-y) $(virt-y) export KBUILD_VMLINUX_LIBS := $(libs-y1) export KBUILD_LDS := arch/$(SRCARCH)/kernel/vmlinux.lds vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN) $(KBUILD_VMLINUX_LIBS) ``` 이 모든 것은 빌드 가능한 소스 코드를 포함하는 리눅스 커널의 모든 하위 폴더를 포함하는 `init-y`, `core-y` 등과 같은 변수들로 시작합니다. 모든 하위 폴더 이름에 `Built-in.o`가 추가되므로, 예를 들어 `drivers/`는 `drivers/built-in.o`가 됩니다. `vmlinux-deps`는 모든 결과 값을 집계합니다. 결국 모든 `build-in.o` 파일에 의존하게 된 것도 이 때문입니다. * 다음 질문은 모든 `built-in.o` 객체들이 어떻게 만들어 지는가?입니다. 다시 한번 모든 관련된 라인들을 복사하여 그것이 어떻게 작동하는지 설명하겠습니다. ``` $(sort $(vmlinux-deps)): $(vmlinux-dirs) ; vmlinux-dirs := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \ $(core-y) $(core-m) $(drivers-y) $(drivers-m) \ $(net-y) $(net-m) $(libs-y) $(libs-m) $(virt-y))) build := -f $(srctree)/scripts/Makefile.build obj #Copied from `scripts/Kbuild.include` $(vmlinux-dirs): prepare scripts $(Q)$(MAKE) $(build)=$@ ``` 첫번째 줄은 `vmlinux-deps`가 `vmlinux-dirs`에 달려 있다고 말합니다. 다음 `/` 문자에 상관 없이 직접적인 루트 하위 폴더를 포함한다는 `vmlinux-dirs` 변수를 볼 수 있습니다. 그리고 여기서 가장 중요한 라인은 `$(vmlinux-dirs)` 타겟을 빌드하는 recipe입니다. 모든 변수들의 대체 후 이 recipe은 다음과 같습니다. (`drivers` 폴더를 예로 들지만, 이 규칙은 모든 루트 하위 폴더에 대해 실행될 것입니다.) ``` make -f scripts/Makefile.build obj=drivers ``` 이 라인은 다른 makefile ([scripts/Makefile.build](https://github.com/torvalds/linux/blob/v4.14/scripts/Makefile.build))릏 호출하고 컴파일할 폴더가 들어 있는 `obj` 변수를 통과시킵니다. * 다음 논리적인 단계는 [scripts/Makefile.build](https://github.com/torvalds/linux/blob/v4.14/scripts/Makefile.build)을 한번 살펴 보는 것입니다. 실행 후 가장 중요한 것은 현재 디렉토리에 정의된 `Makefile`이나 `Kbuild` 파일의 모든 변수들이 수행된 이후에 포함되는 것입니다. 현재 디렉토리로 나는 `obj` 변수에 의해 참조되는 디렉토리를 의미합니다. 이는 [뒤의 3라인](https://github.com/torvalds/linux/blob/v4.14/scripts/Makefile.build#L43-L45) 안에 있습니다. ``` kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src)) kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile) include $(kbuild-file) ``` 중첩된 makefiles 대부분 `obj-y` 같은 변수를 초기화하기 위한 책임이 있습니다. 빠르게 상기시키는 것으로서: `obj-y` 변수 모든 소스 코드 파일, 현재 디렉토리에 위치한 목록이 포함되어야 합니다. 중첩 makefile들에 의해 초기화되는 또 다른 중요한 변수는 `subdir-y`입니다. 이 변수에는 cheptnt 디렉토리의 소스 코드를 작성하기 전에 방문해야 하는 모든 하위 폴더 목록이 포함되어 있습니다. `subdir-y` 는 하위폴더에 재귀적으로 방문하는 것을 구현하기 위해 사용됩니다. * `make`는 타겟을 구체화하는 것 없이 호출될 때 첫번째 타겟을 사용합니다. `scripts/Makefile.build`을 위한 첫번째 타겟은 `__build`에 의해 호출되고 이것은 [여기](https://github.com/torvalds/linux/blob/v4.14/scripts/Makefile.build#L96)에서 확인 할 수 있습니다. 한번 살펴 봅시다. ``` __build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \ $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \ $(subdir-ym) $(always) @: ``` 보다시피 `__build` 타겟은은 recipe은 없지만 다른 타겟에 따라 다릅니다. `built-in.o`파일 작성을 담당하는 `$(builtin-target)`와 중첩된 디렉토리에 내림하는 책임을 맡고 있는 `$(subdir-ym)`에만 관심이 있습니다. * `subdir-ym`을 한법 봅시다. 이 변수는 [여기](https://github.com/torvalds/linux/blob/v4.14/scripts/Makefile.lib#L48)에서 초기화되고 이것은 `subdir-y`와 `subdir-m` 변수의 합성입니다. * `subdir-ym` 타겟은 [여기](https://github.com/torvalds/linux/blob/v4.14/scripts/Makefile.build#L572)에서 정의되고 우리에게 매우 익숙합니다. ``` $(subdir-ym): $(Q)$(MAKE) $(build)=$@ ``` 이 타겟은 중첩된 서브 폴더 중에 하나에 있는 `scripts/Makefile.build`의 실행을 트리거할 뿐입니다. * 이제 [builtin-target](https://github.com/torvalds/linux/blob/v4.14/scripts/Makefile.build#L467) 타겟에 대해서 설명하겠습니다. 일단 여기에 관련된 라인만 복사하겠습니다. ``` cmd_make_builtin = rm -f $@; $(AR) rcSTP$(KBUILD_ARFLAGS) cmd_make_empty_builtin = rm -f $@; $(AR) rcSTP$(KBUILD_ARFLAGS) cmd_link_o_target = $(if $(strip $(obj-y)),\ $(cmd_make_builtin) $@ $(filter $(obj-y), $^) \ $(cmd_secanalysis),\ $(cmd_make_empty_builtin) $@) $(builtin-target): $(obj-y) FORCE $(call if_changed,link_o_target) ``` 이 타겟은 `$(obj-y)` 타겟에 따라 달라지고 `obj-y`는 현재 폴더에 빌드해야하는 모든 객체 파일의 목록입니다. 해당 파일이 준비되면 `cmd_link_o_target` 명령이 실행됩니다. 이 `obj-y` 변수가 비어 있는경우 `cmd_make_empty_builtin`이 호출되면 빈 `built-in.o`를 생성합니다. 반면에 `cmd_make_builtin` 명령이 실행해서 익숙한 `ar` 툴을 사용해서 `built-in.o` 아카이브를 만듭니다. * 마침내 우리는 무언가를 편집해야할 단계입니다. 마지막 미개척 종속성은 `$(obj-y)`이고 `obj-y`는 객체 리스트에 불과한다는 것을 기억할 겁니다. 사응하는 .c 파일들로부터 컴파일하는 그 타켓은 [여기에서](https://github.com/torvalds/linux/blob/v4.14/scripts/Makefile.build#L313) 정의됩니다. 이 타겟을 이해하는데 필요한 모든 라인을 살펴봅시다. ``` cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $< define rule_cc_o_c $(call echo-cmd,checksrc) $(cmd_checksrc) \ $(call cmd_and_fixdep,cc_o_c) \ $(cmd_modversions_c) \ $(call echo-cmd,objtool) $(cmd_objtool) \ $(call echo-cmd,record_mcount) $(cmd_record_mcount) endef $(obj)/%.o: $(src)/%.c $(recordmcount_source) $(objtool_dep) FORCE $(call cmd,force_checksrc) $(call if_changed_rule,cc_o_c) ``` 내부에서는 이 타겟을 `rule_cc_o_c`라고 부릅니다. 이 규칙은 보통 에러를 위한 소스 코드 체크(`cmd_checksrc`) 또는 외부 모듈 심볼(`cmd_modversions_c`)을 위한 버전을 가능하게하는 것 또는 [objtool](https://github.com/torvalds/linux/tree/v4.14/tools/objtool)을 사용하여 생성된 개체 파일의 일부 측면을 검증하고 [ftrace](https://github.com/torvalds/linux/blob/v4.14/Documentation/trace/ftrace.txt)가 신속하게 찾을 수 잇도록 `mcount`함수에 대한 통화 목록을 구성하는 등같은 많은 것들을 담당합니다. 그러나 가장 중요한 것은 모든 `.c` 파일을 객체 파일에 컴파일하는 `cmd_cc_o_c` 명령어입니다. ### 결론 와우, 커널 빌드 시스템 내부로 가는 긴 여정이었어요! 그럼에도 불구하고, 우리는 많은 세부사항을 생략했고, 이 주제에 대해 더 알고 싶어하는 사람들을 위해 나는 다음의 [문서](https://github.com/torvalds/linux/blob/v4.14/Documentation/kbuild/makefiles.txt)들을 읽는 것을 추천하고 Makefiles 코드 읽는 것을 계속하겠습니다. 지금 중요한 점을 강조하는 당신이 이 장에서 가져갈 중요한 메시지입니다. 1. 어떻게 `.c` 파일들이 오브젝트 파일들로 컴파일되는지. 1. 어떻게 오브젝트파일들이 `build-in.o` 파일들로 합쳐지는지. 1. 어떻게 재귀적인 빌드가 모든 하위 `build-in.o` 파일들을 선택하고 하나로 결합 시키는지. 1. 어떻게 `vmlinux`가 톱레벨의 `build-in.o` 파일들로부터 링크되는지. 나의 주된 목표는 이 장을 읽고 나면 위의 모든 사항에 대한 일반적인 이해를 얻는 것이었습니다. ##### Previous Page 1.2 [Kernel Initialization: Linux project structure](./project-structure.md) ##### Next Page 1.4 [Kernel Initialization: Linux startup sequence](./kernel-startup.md) ##### 추가 사항 이 문서는 훌륭한 오픈소스 운영체제 학습 프로젝트인 Sergey Matyukevich의 문서를 영어에 능숙하지 않은 한국인들이 학습할 수 있도록 번역한 것입니다. 오타 및 오역이 있을 수 있습니다. Sergey Matyukevich는 한국어를 능숙하게 다루지 못합니다. 대신 저에게 elxm6123@gmail.com으로 연락해주세요. ================================================ FILE: translations/ko/lesson01/linux/kernel-startup.md ================================================ ## 1.4: 리눅스 스타트업 순서 ### 진입점 검색 리눅스 프로젝트 구조를 살펴보고 어떻게 구축할 수 있는지 검토한 후, 다음 논리적 단계는 프로그램 진입점을 찾는 것입니다. 이 단계는 많은 프로그램에서는 사소한 것일 수 있지만 Linux 커널에서는 그렇지 않습니다. 우리가 할 첫 번째 일은 [arm64 linker 스크립트](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/vmlinux.lds.S)를 살펴보는 것입니다. 우리는 이미 링커 스크립트가 [기본 makefile 안에서](https://github.com/torvalds/linux/blob/v4.14/Makefile#L970) 어떻게 사용되는지 이미 살펴보았습니다. 이 라인으로부터, 우리는 쉽게 유추할 수 있는데, 어디에서 특정한 아키텍처에 대한 링커 스크립트를 찾을 수 있습니다. 우리가 검토할 파일은 실제 링크 스크립트가 아니라 템플릿이며, 실제 링크 스크립트는 일부 매크로를 실제 값으로 대체하여 작성됩니다. 그러나 정확히 말해서 이 파일은 대부분 매크로로 구성되어 있기 때문에 다른 아키텍처들 사이에서 읽기와 포팅이 훨씬 쉬워집니다. 우리가 링커 스크립트안에서 발견할 수 있는 첫번째 섹션은 [.head.text](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/vmlinux.lds.S#L96)라고 불립니다. 진입점은 이 섹션에서 정의되어야 하기 때문에 이것은 우리에게 매우 중요합니다. 조금만 생각해 보면, 그것은 완벽하게 이해가 됩니다: 커널이 로드된 후, 이진 이미지의 내용이 어떤 메모리 영역으로 복사되고 그 영역의 시작부터 실행이 시작됩니다. 누가 .head.text를 사용하고 있는지 검색하는 것만으로도 진입점을 찾을 수 있어야 한다는 뜻입니다. arm64 아키텍처는 .section ".head.text"ax"로 확장된 [__HEAD](https://github.com/torvalds/linux/blob/v4.14/include/linux/init.h#L90) 매크로를 사용하는 단일 파일을 가지고 있는데, 이 파일은 [head.S]입니다. `head.S` 파일 안에서 우리가 찾을 수 있는 첫 실행 파일 라인은 [이것](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/head.S#L85)입니다. 여기서 ARM 어셈블러의 `b`와 `branch` 명령어를 [stext](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/head.S#L116) 함수로 점프하기 위해서 사용합니다. 그리고 이것이 커널을 부팅한 후에 실행되는 첫 번째 함수입니다. 다음 논리적 단계에서 어떻게 그 `stext` 함수에서 일어나는 일을 탐험하기 위해 아직 준비가 없다는 것입니다. 첫번째로, RPi는 OS에서 비슷한 기능을 구현하는 데, 그리고 2,3수업에 적용될 것입니다. 우리가 지금 할 것은 커널과 연관된 몇가지 중요한 개념을 설명하는 것입니다. ### 리눅스 부트로더 및 부트 프로토콜 리눅스 커널이 부팅될 때 기계 하드웨어가 어떤 "알려진 상태"로 준비되었다고 가정한다. 이 상태를 정의하는 규칙 집합을 "부팅 프로토콜"이라고 하며, `arm64` 아키텍처에 대해서는 [여기](https://github.com/torvalds/linux/blob/v4.14/Documentation/arm64/booting.txt)에 문서화합니다. 그 중에서도, 예를 들면, 주 CPU에서만 실행을 시작해야 하고, 메모리 매핑 유닛을 꺼야하며, 모든 인터럽트를 비활성화해야 한다고 규정합니다. 좋아요, 하지만 누가 기계를 그 알려진 상태로 들여올 책임이 있나요? 보통, 커널보다 먼저 실행되어 모든 초기화를 수행하는 특별한 프로그램이 있습니다. 그 프로그램은 부르토더라고 불립니다. 부트로더 코드는 기계마다 매우 다를 수 있으며, 라즈베리 파이가 이에 해당 됩니다. 라스베리 파이는 보드에 내장되어 있는 부트로더를 가지고 있습니다. 우리는 오직 이 행동을 커스텀마이즈 하기 위해서 [config.txt](https://www.raspberrypi.org/documentation/configuration/config-txt/) 파일을 사용할 수 있습니다. ### UEFI boot 그러나 커널 이미지 자체에 내장할 수 있는 부트 로더가 하나 있습니다. 이 bootloader는 [Unified Extensible Firmware Interface (UEFI)](https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface)가 지원하는 플랫폼에만 사용될 수 있습니다. UEFI를 지원하는 기기는 실행 중인 소프트웨어에 표준화된 서비스 세트를 제공하며 그러한 서비스는 기계 자체와 그 기능에 대한 모든 필요한 정보를 파악하는 데 사용될 수 있습니다. UEFI는 또한 컴퓨터 펌웨어가 [Portable Executable(PE)](https://en.wikipedia.org/wiki/Portable_Executable) 포맷 안에 실행 파일을 실행할 수 있어야 한다고 요구합니다. 리눅스 커널 UEFI 부트로더는 이 기능을 이용합니다. 리눅스 커널 이미지 시작 부분에 `PE` 헤더를 주입하여 컴퓨터 펌웨어가 이미지가 일반적인 `PE` 파일이라고 생각하도록 합니다. 이것은 [efi-header.S](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/efi-header.S) 파일 안에서 수행됩니다. 이 파일은 [head.S](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/head.S#L98) 안에 사용되는 [__EFI_PE_HEADER](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/efi-header.S#L13) 매크로를 정의합니다. which is used inside [head.S] `__EFI_PE_HEADER` 안에 정의된 한가지 중요한 속성은 [location of the UEFI entry point](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/efi-header.S#L33)에 대해 말합니다. 그리고 이 진입점은 스스로 [efi-entry.S](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/efi-entry.S#L32) 안에서 발견될 수 있습니다. ]이 위치부터 소스 코드를 따라 UEFI 부트로더가 정확히 무엇을 하고 있는지 검사할 수 있습니다. 그러나 본 섹션의 목적은 UEFI 부트로더를 자세히 조사하는 것이 아니라 UEFI가 무엇인지, 리눅스 커널이 어떻게 사용하는지를 대략적으로 알려주기 때문에 여기서 멈추려고 합니다. ### Device Tree 리눅스 커널의 스타트업 코드를 조사하기 시작했을 때 `Device Tree`에 대한 언급이 많았습니다. 그것은 본질적인 개념으로 보이며, 나는 그것을 논의할 필요가 있다고 생각합니다. `Raspberry PI OS` 커널을 작업했을 때, 특정 메모리 매핑 레지스터가 위치한 정확한 오프셋을 파악하려면 [BCM2837 ARM Peripherals manual](https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf)을 사용했었습니다. 이 정보는 분명히 각 게시판마다 다르며, 그 중 한 곳만 지원하면 되는 것이 다행입니다. 하지만 우리가 수백 개의 다른 보드를 지원해야 한다면? 우리가 커널 코드에 있는 각각의 보드에 대한 정보를 하드코딩하려고 하면 완전히 엉망진창이 될 것입니다. 그리고 설사 우리가 가까스로 그렇게 한다고 해도, 지금 어떤 보드를 사용하고 있는지 어떻게 알 수 있을까? 예를 들어 `BCM2837`은 실행 중인 커널에 그러한 정보를 전달하는 수단을 제공하지 않습니다. 장치 트리는 위에 언급된 문제에 대한 해결책을 우리에게 제공합니다. 컴퓨터 하드웨어를 설명하는 데 사용할 수 있는 특수한 형식입니다. 장치 트리는 [여기](https://www.devicetree.org/)에서 볼 수 있습니다. 커널이 실행되기 전에 부트로더는 적절한 장치 트리 파일을 선택하여 커널에 인수로 전달합니다. 라즈베리 PI SD카드의 부트 파티션에 있는 파일을 보면 여기서 `.dtb` 파일을 많이 찾을 수 있습니다. `.dtb`는 장치 트리 파일을 컴파일한 것입니다. `config.txt` 에서 그 중 일부를 선택하여 일부 라즈베리 파이 하드웨어를 활성화하거나 비활성화 할수 있습니다. 커널이 실행되기 전에 부트로더는 적절한 장치 트리 파일을 선택하여 커널에 인수로 전달합니다. Raspberry PI SD 카드의 부팅 파티션에 있는 파일을 보면, 여기서 `.dtb` 파일을 많이 찾을 수 있습니다. `.dtb`는 장치 트리 파일을 컴파일한 것입니다. "config.txt"에서 일부를 선택하여 일부 Raspberry PI 하드웨어를 활성화하거나 비활성화할 수 있습니다. 이 프로세스는 [Raspberry PI 공식 설명서](https://www.raspberrypi.org/documentation/configuration/device-tree.md)에 자세히 설명되어 있습니. 자, 이제 실제 장치 트리가 어떻게 생겼는지 살펴봅시다. 간단한 연습으로 [Rasperry PI 3 Model B](https://www.raspberrypi.org/products/raspberry-pi-3-model-b/)의 장치 트리를 찾아보자. [문서](https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2837/README.md)로부터 우리는 `BCM2837`를 사용하는 `Raspberry PI 3 Model B`를 이해할 수 있습니다. 이 파일 [/arch/arm64/boot/dts/broadcom/bcm2837-rpi-3-b.dts](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/boot/dts/broadcom/bcm2837-rpi-3-b.dts)을 검색하면 찾을 수 있습니다. 여러분이 볼 수 있듯이, 그것은 단지 `arm` 구조의 동일한 파일을 포함하고 있을 뿐입니다. `ARM.v8` 프로세서도 32비트 모드를 지원한다는 점에서 일리가 있다. 다음에, [arm](https://github.com/torvalds/linux/tree/v4.14/arch/arm) 아키텍처를 포함하고 있는 [bcm2837-rpi-3-b.dts](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837-rpi-3-b.dts)를 찾을 수 있습니다. 우리는 이미 장치 트리 파일이 다른 파일에 포함될 수 있다는 것을 보았습니다. 이는 `bcm2837-rpi-3-b.dts`의 경우로, `BCM2837`에 특정한 정의만을 포함하고 있으며, 그 밖의 모든 것을 재사용하고 있습니다. 예를 들어, `bcm2837-rpi-3-b.dts`는 [기기는 현재 1GB 메모리를 가지고 있다](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837-rpi-3-b.dts#L18)라고 명시하고 있습니다. 앞서 언급했듯이 `BCM2837`과 `BCM2835`는 주변 하드웨어가 동일하며, 이 하드웨어의 대부분을 실제로 정의하는 `bcm283x.dtsi`를 찾을 수 있습니다. 장치 트리 정의는 각각 중첩된 블록으로 구성됩니다. 탑레벨에서 우리는 보통 [cpus](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837.dtsi#L30)나 [memory](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837-rpi-3-b.dts#L17)와 같은 블록들을 찾을 수 있습니다. 그 블록들의 의미는 꽤 자기 스스로 설명 가능해야합니다. `bcm283x.dtsi` 안에서 발견할 수 있는 다른 흥미로운 탑 레벨 요소는 [SoC](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm283x.dtsi#L52)입니다. 이것은 그것은 모든 주변 장치가 메모리 맵 레지스터를 통해 어떤 메모리 영역에 직접 매핑된다는 것을 알려주는 [System on a chip](https://en.wikipedia.org/wiki/System_on_a_chip)을 의미합니다 . `soc` 요소는 모든 주변 장치의 상위 요소 역할을 합니다. 하위 요소들의 하나는 [gpio](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm283x.dtsi#L147)입니다. 이 요소는 [reg = <0x7e200000 0xb4>](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm283x.dtsi#L149) 속성을 GPIO 메모리 매핑 레지스터가 `[0x7e200000 : 0x7e2000b4]` 에 위치하고 있다는 것을 정의합니다. 어느`gpio` 요소의들의 [따른 정의](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm283x.dtsi#L474을 가지고 있습니다. ``` uart1_gpio14: uart1_gpio14 { brcm,pins = <14 15>; brcm,function = ; }; ``` 이 정의는 핀 14와 핀 15에 대해 대체 함수 5를 선택하면 해당 핀이 `uart1` 장치에 연결된다는 것을 알려줍니다. 이미 사용했던 `uart1` 장치가 미니 UART라는 것을 쉽게 짐작할 수 있습니다. 장치 트리에 대해 알아야 할 중요한 한 가지는 형식이 확장 가능하다는 것입니다. 각 장치는 자체 특성과 내포된 블록을 정의할 수 있습니다. 이러한 속성은 기기 드라이버로 투명하게 전달되며, 이를 해석하는 것은 드라이버의 책임입니다. 그러나 어떻게 커널이 장치 트리의 블록과 올바른 드라이버 사이의 통신을 알아낼 수 있을까요? 이를 위해 `compatible` 속성을 사용합니다. 예를 들어 `uart1` 장치의 경우 `compatible` 속성이 다음과 같이 지정됩니다. ``` compatible = "brcm,bcm2835-aux-uart"; ``` 그리고 실제로 리눅스 소스 코드에서 `bcm2835-aux-uart`를 검색하면 일치하는 드라이버를 찾을 수 있으며, 이는 [8250_bcm2835aux.c](https://github.com/torvalds/linux/blob/v4.14/drivers/tty/serial/8250/8250_bcm2835aux.c)에 정의되어 있습니다. ### Conclusion 여러분은 이 장에 대해 우리가 방금 탐구한 개념들을 이해하지 못한다면 `arm64` 부트 코드를 읽기 위한 준비로 생각할 수 있습니다. 다음 수업에서는 `stex`' 함수로 돌아가서 어떻게 작동하는지 자세히 살펴볼 것입니다. ##### Previous Page 1.3 [Kernel Initialization: Kernel build system](../../../docs/lesson01/linux/build-system.md) ##### Next Page 1.5 [Kernel Initialization: Exercises](../exercises.md) ##### 추가 사항 이 문서는 훌륭한 오픈소스 운영체제 학습 프로젝트인 Sergey Matyukevich의 문서를 영어에 능숙하지 않은 한국인들이 학습할 수 있도록 번역한 것입니다. 오타 및 오역이 있을 수 있습니다. Sergey Matyukevich는 한국어를 능숙하게 다루지 못합니다. 대신 저에게 elxm6123@gmail.com으로 연락해주세요. ================================================ FILE: translations/ko/lesson01/linux/project-structure.md ================================================ ## 1.2: 리눅스 프로젝트 구조 리눅스에 대해 말하겠습니다. 우선 우리의 커널이 쓰는 작은 단계를 완료하고 나서 리눅스에서도 같은 단계들이 어떻게 작동하는지 살펴볼 것입니다. 지금까지 우리는 조금 밖에 진행하지 않았습니다. 단지 우리의 첫 번째 베어 메탈 헬로우 월드 프로그램을 구현했습니다. 하지만 RPi OS와 Linux 사이의 유사점을 찾을 수 있을 것입니다. 그리고 이제부터 그들 중 일부를 탐험할 것입니다. ### 프로젝트 구조 대형 소프트웨어 프로젝트를 조사할 때마다 프로젝트 구조를 잠깐 살펴볼 가치가 있습니다. 이것은 프로젝트를 구성하는 모듈들과 높은 수준의 아키텍처를 이해할 수 있게 해주기 때문에 매우 중요합니다. 이제 Linux 커널의 프로젝트 구조를 살펴봅시다. 우선 리눅스 레포지토리를 복제해야 합니다. ``` git clone https://github.com/torvalds/linux.git cd linux git checkout v4.14 ``` "v4.14 버전은 작성 당시 최신 버전이었기 때문에 이를 사용하겠습니다. Linux 소스 코드에 대한 모든 참조는 이 특정 버전을 사용하여 이루어질 것입니다. 다음으로 리눅스 리포지토리 안에서 찾을 수 있는 폴더를 살펴보도록 합시다. 우리는 그 모든 것들을 볼 것이 아니라, 가장 중요하게 생각하는 것들로 시작할 것입니다. * [arch](https://github.com/torvalds/linux/tree/v4.14/arch) 이 폴더 각각 특정 프로세서 아키텍처에 대해 하위 폴더를 포함합니다. 대게 우리는 [arm64](https://github.com/torvalds/linux/tree/v4.14/arch/arm64)를 사용하여 작업할 것입니다. - 이건 ARM.v8 프로세서들과 호환됩니다. * [init](https://github.com/torvalds/linux/tree/v4.14/init) 커널은 항상 구체적인 코드 구조에 의해 부팅됩니다. 그러나 실행은 커널 초기화와 독립적인 커널 출발점 아키텍처를 책임하고 있는 [start_kernel](https://github.com/torvalds/linux/blob/v4.14/init/main.c#L509) 함수로 전달됩니다. `start_kernel` 함수는 다른 초기화 기능과 함께 `init`폴더에 정의되어 있습니다. * [kernel](https://github.com/torvalds/linux/tree/v4.14/kernel) 이것이 리눅스 커널의 핵심입니다. 거의 모든 주요 커널 서브시스템이 이곳에서 구현됩니다. * [mm](https://github.com/torvalds/linux/tree/v4.14/mm) 메모리 관리와 관련된 모든 데이터 구조와 방법이 여기서 정의됩니다. * [drivers](https://github.com/torvalds/linux/tree/v4.14/drivers) 이것은 리눅스 커널에서 가장 큰 폴더입니다. 여기에는 모든 장치 드라이버의 구현이 포함되어 있습니다. * [fs](https://github.com/torvalds/linux/tree/v4.14/fs) 다른 파일 시스템 구현을 찾으려면 여기를 클릭하십시오. 이 설명은 매우 어려운 설명이지만, 현재 이것으로 충분합니다. 다음 장에서는 리눅스 빌드 시스템을 자세히 살펴보겠습니다. ##### 이전 페이지 1.1 [Kernel Initialization: Introducing RPi OS, or bare metal "Hello, world!"](./lesson01/rpi-os.md) ##### 다음 페이지 1.3 [Kernel Initialization: Kernel build system](./build-system.md) ##### 추가 사항 이 문서는 훌륭한 오픈소스 운영체제 학습 프로젝트인 Sergey Matyukevich의 문서를 영어에 능숙하지 않은 한국인들이 학습할 수 있도록 번역한 것입니다. 오타 및 오역이 있을 수 있습니다. Sergey Matyukevich는 한국어를 능숙하게 다루지 못합니다. 대신 저에게 elxm6123@gmail.com으로 연락해주세요. ================================================ FILE: translations/ko/lesson01/rpi-os.md ================================================ ## 1.1: RPi OS 소개, 베어-메탈 “Hello, World” “Hello, World”라는 작은 베어메탈 애플리케이션으로 OS 개발하는 여정을 시작할 예정입니다. OS 개발에 필요한 [필수 요건](../Prerequisites.md)을 모두 준비했다고 생각합니다. 그렇지 않다면 이것을 먼저하세요. 진행하기 앞서 나는 간단한 명명 규칙을 세우려 합니다. README 파일에서 전체 자습서가 레슨으로 나누어져 있음을 알 수 있습니다. 각 레슨은 챕터라고 부르는 개별 파일로 구성되어 있습니다(지금 당신은 1장 1.1절을 읽고 있습니다). 한 챕터는 제목이 있는 섹션으로 나뉩니다. 이 규칙은 자료의 다른 부분에 대해 언급하게 해줍니다. 또 하나 주목했으면 하는 것은 자습서에 소스 코드 샘플이 많이 들어있다는 것입니다. 보통 완전한 코드 블록을 제공함으로써 설명을 시작하고 그다음 라인별로 설명합니다. ### 프로젝트 구성 각 수업의 소스 코드는 구조가 같습니다. 이 수업의 소스 코드는 [여기서](https://github.com/s-matyukevich/raspberry-pi-os/tree/master/src/lesson01) 찾을 수 있습니다. 이 폴더의 구성 요소에 대해서 간략하게 설명하겠습니다. 1. **Makefile** 우리는 [make](http://www.math.tau.ac.il/~danha/courses/software1/make-intro.html) 유틸리티를 사용하여 커널을 만들 것입니다. make의 동작은 소스 코드를 컴파일 하고 연결하는 방법에 대한 지침을 표시하는 Makefile에 의해 구성됩니다. 1. **build.sh 또는 build.bat** 도커를 사용하여 커널을 작성하려면 이 파일이 필요할 것입니다. 당신의 노트북에 메이킹 유틸리티나 컴파일러 툴 체인을 설치할 필요가 없을 것입니다. 1. **src** 이 폴더에는 모든 소스코드가 포함되어 있습니다. 1. **include** 모든 헤더 파일이 이 폴더에 있습니다. ### Makefile 이제 프로젝트의 Makefile을 자세히 살펴봅시다. 메이킹 유틸리티의 주된 목적은 프로그램의 어떤 조각을 다시 컴파일 해야 하는지를 자동적으로 결정하고 그들을 다시 컴파일 하는 명령을 내리는 것입니다. 만약 당신이 make와 makefile들에 익숙하지 않다면, [이것을](http://opensourceforu.com/2012/06/gnu-make-in-detail-for-beginners/) 읽을 것을 추천합니다. 첫 번째 레슨에 사용된 Makefile은 [여기에서](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson01/Makefile) 찾을 수 있습니다. 전체 Makefile은 아래에 나열되어 있습니다. ``` ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ``` 이제 파일을 자세히 살펴봅시다. ``` ARMGNU ?= aarch64-linux-gnu ``` Makefile은 변수 정의로 시작합니다. `ARMGNU`는 크로스 컴파일러 접두사입니다. `x86` 머신에서 `arm64` 아키텍처의 소스 코드를 컴파일하고 있기 때문에 [크로스 컴파일러](https://en.wikipedia.org/wiki/Cross_compiler)가 필요합니다. 그래서 `gcc` 대신 `arch64-linux-gnu-gcc`를 사용할 것입니다. ``` COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude ``` `COPS`와 `ASMOPS`는 각각 C와 어셈블리 코드를 컴파일 할 때 컴파일러에게 전달하는 옵션입니다. 아래 옵션들은 짧은 설명이 필요합니다. * **-Wall** 모든 경고들을 보여줍니다. * **-nostdlib** C 표준 라이브러리를 사용하지 않습니다. C표준 라이브러리의 대부분의 호출은 결국 운영체제와 상호작용한다. 우리는 베어-메탈 프로그램을 사용하고 있기 때문에 운영체제가 없으므로 C 표준 라이브러리는 동작하지 않습니다. * **- ffreestanding** freestanding 환경은 표준 라이브러리가 존재하지 않을 수 있고 프로그램 시작이 반드시 main이 아닐 수 있는 환경입니다. 옵션 `-ffreestanding`은 컴파일러에게 표준 함수가 통상적인 정의를 가지고 있다고 가정하지 않도록 지시합니다. * **-linclude** `include` 폴더에서 헤더 파일을 검색합니다. * **-mgeneral-regs-only** 범용레지스터만 사용합니다. ARM 프로세서에도 [NEON](https://developer.arm.com/technologies/neon) 레지스터가 있습니다. 컴파일러는 복잡성을 더하기 때문에 컴파일러에서 이러한 레지스터를 사용하지 않도록 해야합니다. ``` BUILD_DIR = build SRC_DIR = src ``` `SRC_DIR`와 `BUILD_DIR`는 소스코드와 컴파일된 오브젝트 파일들을 반복적으로 포함하고 있는 디렉토리들입니다. ``` all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img ``` 다음으로 Makefile에서 타겟을 만듭니다. 첫 번째 두 개의 타겟은 매우 간단합니다. 'all' 타겟은 기본 타겟이며, 아무 인자 없이 'make'를 입력할 때마다 실행됩니다("make"는 항상 첫 번째 타겟을 기본 타겟으로 사용합니다). 이 대상은 모든 작업을 다른 대상인kernel8.img로 리디렉션할 뿐입니다. 'clean' 대상은 모든 컴파일된 아티팩트와 컴파일된 커널 이미지를 삭제하는 역할을 합니다. ``` $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ ``` 다음 두 타겟은 C와 어셈블리 파일을 컴파일하는 일을 담당합니다. 예를 들어, `src` 디렉토리에 `foo.c`와 `foo.S`가 있습니다. 그 파일들은 각각 `build/foo_c.o`와 `build/foo_s.o`로 컴파일 됩니다. `$<`와 `$@`는 런타임에 입력과 출력 파일 이름(`foo.c`와 `foo_c.o`)로 대체됩니다. C 파일을 컴파일하기 전에 아직 존재하지 않을 경우를 대비해 `build` 디렉토리를 만듭니다. ``` C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) ``` 여기서는 C와 어셈블리 소스 파일의 결합으로 만들어진 모든 `OBJ_FILES`의 배열을 만들고 있습니다. ``` DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) ``` 다음 두 줄은 좀 까다롭습니다. C와 어셈블리 소스 파일의 컴파일 목표를 어떻게 정의했는지 살펴보면, 우리가 `-MMD` 매개변수를 사용했다는 것을 알 수 있습니다. 이 매개변수는 생성된 각 개체 파일에 대한 모든 종속성을 정의합니다. 이러한 종속성에는 일반적으로 포함된 모든 헤더의 목록이 포함됩니다. 헤더가 변경될 경우 정확히 무엇을 다시 컴파일해야 하는지 알 수 있도록 생성된 모든 종속 파일을 포함해야합니다. ``` $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o kernel8.elf $(OBJ_FILES) ``` 우리는 `kernel8.elf` 파일을 빌드하기 위해서 `OBJ_FILES` 배열을 사용합니다. 우리는 결과적으로 실행가능한 이미지의 기본적인 레이아웃을 정의하기 위해서 링커 스크립트 `src/linker.ld`를 사용합니다(다음 섹션에서 링커 스크립트에 대해 논의합니다). ``` $(ARMGNU)-objcopy kernel8.elf -O binary kernel8.img ``` `kernel8.elf`는 [ELF](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format) 형식입니다. 문제는 ELF 파일이 운영 체제에 의해 실행되도록 설계되어 있다는 것입니다. 베어메탈 프로그램을 작성하려면 ELF 파일에서 실행 파일과 데이터 섹션을 모두 추출해 'kernel8.img' 이미지에 넣어야 합니다. `8`은 64비트 아키텍처인 ARMv8을 의미합니다. 이 파일 이름은 프로세서를 64비트 모드로 부팅하도록 펌웨어에 알려줍니다. 또한 `config.txt` 파일에서 `arm_control=0x200` 플래그를 사용하여 CPU를 64비트 모드로 부팅할 수도 있습니다. RPi OS 이전에 이 방법을 사용했으며, 일부 exercise의 답변에서도 여전히 이 방법을 찾을 수 있습니다. 그러나 `arm_control` 프래그 문서화 되지 않았으며 대신 `kernel8.img` 명명 규칙을 사용하는 것이 바람직합니다. ### 링커 스크립트 링커 스크립트의 주요 목적은 입력 객체 파일(`_c.o와 `_s.o`)의 섹션을 출력 파일(`elf`)에 매핑하는 방법을 설명하는 것입니다. 링커 스크립트에 대한 자세한 내용을 [여기](https://sourceware.org/binutils/docs/ld/Scripts.html#Scripts)에서 알 수 있습니다. 이제 RPi OS 링커 스크립트를 살펴봅시다. ``` SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ``` 시작 후 라즈베리 파이는 `kernel8.img`를 메모리에 로드하고 이미지 파일 시작 부분부터 실행을 시작합니다. 그래서 `.text.boot` 섹센이 먼저 있어야합니다. 이 섹션 안에 OS의 시작 코드를 넣겠습니다. `.text`, `.rodata`, `.data` 섹션에는 커널을 컴파일한 명령어, 읽기 전용 데이터, 일반 데이터 등이 수록되어 있어 특별한 추가 사항은 없습니다. `.bss` 섹션에는 0으로 초기화해야할 데이터들이 들어 있습니다. 이러한 데이터들을 별도의 섹션에 배치함으로써 컴파일러는 ELF 이진 수에 약간의 공간을 절약할 수 있습니다. 단, 섹션 크기는 ELF 헤더에 저장되지만 섹션 자체는 생략됩니다. 이미지를 메모리에 로드한 이후, `.bss` 섹션을 0으로 초기화해야합니다. 그렇기 때문에 섹션의 시작과 끝(`bss_begin`과 `bss_end`)을 기록하고 섹션이 8의 배수로 시작되도록 정렬해야합니다. 이 구간이 정렬되지 않으면 8바이트 정렬 주소로만 `str`을 사용할 수 있기 때문에 `bss` 섹션의 시작 부분에 0을 저장하는 `str`명령어를 사용하는 것이 더 어려울 것입니다. ### Booting the kernel 이제 [boot.S](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson01/src/boot.S) 파일을 볼 시간입니다. 이 파일은 다음과 같은 커널 시작 코드가 포함되어 있습니다. ``` #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main ``` 이 파일을 자세히 검토해봅시다. ``` .section ".text.boot" ``` 먼저, `boot.S` 안에 모든 것은 `.text.boot` 섹션 안에 정의되어 있다고 명시합니다. 이전에 이 섹션이 링커 스크립트에 의해 커널 이미지의 시작 부분에 배치되는 것을 보았습니다. 따라서 커널이 시작되면 `start` 함수에서 실행이 시작됩니다. ``` .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang ``` 이 함수가 하는 첫번째 일은 프로세서 ID를 확인하는 것입니다. 라즈베리 파이 3는 4개의 코어 프로세서를 가지고 있으며, 장치 전원을 켠 후에 각 코어가 동일한 코드를 실행하기 시작합니다. 그러나 네 개의 코어를 가지고 동작하고 싶지 않습니다. 첫번째 코어만 작동시키고 싶기 때문에 다른 모든 코어들에게 무한 루프를 실행 시킵니다. 바로 이것이 `_start` 함수가 담당하는 것입니다. 이것은 [mpider_el1](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0500g/BABHBJCI.html) 시스템 레지스터로부터 프로세서 ID를 얻습니다. 현재 프로세스 ID가 0이라면, 실행은 `master` 함수로 이행됩니다. ``` master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero ``` 여기서 memzero를 불러 `.bss`구간을 클린합니다. 나중에 이 함수를 정의할 것입니다. ARMv8 아키텍처에서는 관례에 따라 처음 7개의 인수가 레지스터 x0-x6을 통해 호출된 함수에 전달됩니다. `memzero` 함수는 시작주소 (`bss_begin`)와 클린해야할 구간 크기(`bss_end-bss-begin`)의 두 가지 인수(x0,x1)만 허용합니다. ``` mov sp, #LOW_MEMORY bl kernel_main ``` `.bss` 섹션을 클리닝한 이후에 우리는 스택 포인터를 초기화하여 실행 상태를 `kernel_main` 함수로 전달합니다. 라즈베리 파이는 주소0에서 커널을 로드합니다. 그렇기 때문에 초기 스택 포인터를 충분히 높게 설정하여 스택이 커널 이미지를 오버라이드하지 않도록 해야합니다. `LOW_MEMORY`는 [mm.h](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson01/include/mm.h)에 정의되어 있으며, 4MB와 같습니다. 커널의 스택은 늘어나지 않을 것이고 이미지 자체가 작기 때문에 4MB는 우리에게 충분하고도 남습니다. ARM 어셈블리 구문에 익숙하지 않은 사람들을 위해 우리가 사용하는 명령어들을 요약해 봅시다. * [**mrs**](http://www.keil.com/support/man/docs/armasm/armasm_dom1361289881374.htm) 시스템 레지스터에서 범용 레지스터 중 하나로 값을 로드한다(x0–x30). * [**and**](http://www.keil.com/support/man/docs/armasm/armasm_dom1361289863017.htm) 논리적인 AND 연산을 수행한다. 이 명령을 사용하여 `mpidr_el1` 레지스터에서 얻은 값에서 마지막 바이트를 떼어낸다. * [**cbz**](http://www.keil.com/support/man/docs/armasm/armasm_dom1361289867296.htm) 이전에 실행한 작업 결과를 0과 비교하고 결과가 참이면 제공된 레이블로 점프(ARM에선 branch)한다. * [**b**](http://www.keil.com/support/man/docs/armasm/armasm_dom1361289863797.htm) 일부 레이블에 대해 무조건 분기를 수행한다. * [**adr**](http://www.keil.com/support/man/docs/armasm/armasm_dom1361289862147.htm) 레이블의 상대 주소를 타겟 레지스터에 로드한다. 이 경우 `bss` 영역의 시작과 끝의 포인터를 원한다. * [**sub**](http://www.keil.com/support/man/docs/armasm/armasm_dom1361289908389.htm) 두 레지스터에서 값을 빼라. * [**bl**](http://www.keil.com/support/man/docs/armasm/armasm_dom1361289865686.htm) “링크가 있는 브랜치”: 무조건 분기를 수행하고 반환 주소를 x30(링크 레지스터)에 저장하라. 서브루틴이 완료되면 `ret`명령을 사용하여 다시 리턴 주소로 점프하라. * [**mov**](http://www.keil.com/support/man/docs/armasm/armasm_dom1361289878994.htm) 레지스터 간 또는 상수에서 레지스토러 값을 이동하라. [여기](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0024a/index.html) ARMv8-A 개발자 가이드가 있다. ARM ISA가 익숙하지 않다면 좋은 자원이다. [이 페이지](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0024a/ch09s01s01.html)는 특히 ABI의 레지스터 사용 규칙을 개략적으로 설명한다. ### `kernel_main` 함수 우리는 부팅 코드가 결국 `kernel_main` 함수로 제어를 전달한다는 것을 알았다. 한번 보자. ``` #include "mini_uart.h" void kernel_main(void) { uart_init(); uart_send_string("Hello, world!\r\n"); while (1) { uart_send(uart_recv()); } } ``` 이 함수는 커널 안에 있는 가장 간단한 함수 중의 하나입니다. `Mini UART` 기기와 연동하여 스크린에 출력하고 사용자 입력 내용을 읽습니다. 이 커널은 `Hello, World!`를 출력하고 사용자로부터 문자를 읽고 다시 화면으로 보내는 무한 루프에 들어갑니다. ### 라즈베리 파이 디바이스 이제 라즈베리 파이 특징을 파헤칠 것입니다. 시작하기 전에 [BCM2837 ARM Peripherals 설명서](https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf)를 다운로드하십시오. BCM2837은 라즈베리 파이 3 모델 B와 B+에서 사용하는 보드입니다. 논의 중에 BCM2835와 BCM2836도 언급할 것입니다. 이들은 라즈베리 파이 이전 버전에서 사용되는 보드의 이름입니다. 구현 세부사항으로 진행하기 전에 메모리 매핑된 장치와 함께 작동하는 방법에 대한 몇 가지 기본 개념을 공유하고자 합니다. BCM2837은 단순한 [SOC(System on a chip)](https://en.wikipedia.org/wiki/System_on_a_chip) 보드입니다. 이러한 보드에서는 메모리 매핑된 레지스터를 통해 모든 장치에 대한 액세스가 수행됩니다. 라즈베리 파이 3는 장치에 0x3F000000 주소를 초과하는 메모리를 예약합니다. 특정 장치를 활성화하거나 구성하려면 장치의 레지스터 중 하나에 일부 데이터를 기록해야 합니다. 장치 레지스터는 메모리의 32비트 영역일 뿐입니다. 각 기기 레지스터의 각 비트의 의미는 `BCM2837 ARM Peripherals` 매뉴얼에 설명되어 있습니다. 메뉴얼에서 `0x7E000000`이 사용됨에도 불구하고 기본 주소로 `0x3F000000`을 사용하는 이유에 대한 자세한 내용은 매뉴얼의 섹션 1.2.3 ARM 물리적 주소와 주변 설명서를 참조하십시오. `kernal_main` 함수로부터, 작은 UART 디바이스와 함께 진행해야함을 추측할 수 있을 것입니다. UART는 [Universal asynchronous receiver-transmitter](https://en.wikipedia.org/wiki/Universal_asynchronous_receiver-transmitter)입니다. 이 장치는 메모리 매핑된 레지스터 중 하나에 저장된 값을 일련의 고전압 및 저전압으로 변환할 수 있습니다. 이 시퀀스는 "TTL 대 시리얼 케이블"을 통해 컴퓨터로 전달되며 단말 에뮬레이터에 의해 해석됩니다. 라즈베리 파이와의 통신을 용이하게 하기 위해 미니 UART를 사용할 것입니다. 미니 UART 레지스터의 사양을 보려면 `BCM2837 ARM Peripherals` 매뉴얼의 8페이지로 이동하세요. 라즈베리 파이는 두 개의 UART를 가지고 있습니다: 미니 UART와 PL011 UART. 이 튜토리얼에서는, 미니 UART로만 작업할 것입니다. 그것이 더 간단하기 때문입니다. 그러나 PL011 UART와 함께 작업하는 방법을 보여주는 선택적 [연습](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/docs/lesson01/exercises.md)도 있습니다. 라즈베리 파이의 UART들에 대해 자세히 알아보고 그 차이를 알고 싶다면 [공식 문서](https://www.raspberrypi.org/documentation/configuration/uart.md)를 참조하십시오. 더 친숙해져야할 또 다른 디바이스는 GPIO[General-purpose input/output](https://en.wikipedia.org/wiki/General-purpose_input/output)입니다. GPIO는 `GPIO 핀`들을 제어합니다. 이를 아래 이미지에서 쉽게 알아볼 수 있어야합니다. ![Raspberry Pi GPIO pins](../../../images/gpio-pins.jpg) GPIO는 다른 GPIO 핀의 동작을 구성하는 데 사용될 수 있습니다. 예를 들어 미니 UART를 사용할 수 있으려면 핀 14와 15를 활성화하고 이 장치를 사용하도록 설정해야 합니다. 아래 이미지는 GPIO 핀에 번호가 할당되는 방법을 보여줍니다. ![Raspberry Pi GPIO pin numbers](../../images/gpio-numbers.png) ### 미니 UART 초기화 이제 미니 UART가 어떻게 초기화 되었는지 살펴봅시다. 이 코드는 [mini_uart.c](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson01/src/mini_uart.c)에 정의되어 있습. ``` void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio 15 put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver } ``` 여기서는 `put32`와 `get32`의 두 가지 함수를 사용합니다. 이 함수들은 매우 간단합니다. 32비트 레지스터에서 데이터를 읽고 쓸 수 있게 합니다. 이 함수들은 [utils.S](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson01/src/utils.S)에서 어떻게 구현되어 있는지 볼 수 있습니다. `uart_init`은 이 레슨에서 가장 복잡하고 중요한 함수 중 하나이며, 다음 세 개의 섹션에서 계속 검토할 것입니다. #### GPIO 대체 함수 선택 먼저, 반드시 GPIO 핀을 활성화해야합니다. 핀들의 대부분은 다른 디바이스를 위해 사용될 수 있습니다. 그래서 특정 핀을 사용하기 전에 우리는 핀의 `alternative function`을 선택해야 합니다. `alternative function`는 0~5에서 각각의 핀을 위해 설정될 수 있고 핀에 연결된 디바이스를 구성할 수 있는 숫자에 불과합니다. 아래 메뉴얼 이미지에서 GPIO 대체 함수의 기능 목록을 볼 수 있습니다(`BCM2837 ARM Peripherals` 메뉴얼의 102페이지부터 얻은 이미지입니다.). ![Raspberry Pi GPIO alternative functions](../../../images/alt.png?raw=true) 핀 14번과 15번은 TXD1과 RXD1의 대체 이용 가능한 함수로 볼 수 있습니다. 이는 만약 우리가 핀 14번과 15번을 위한 대체 함수 5번을 선택한다면 이들을 미니 UART 전송 핀이나 미니 UART 수신 핀으로 반복적으로 사용될 수 있습니다. `GPFSEL1` 레지스터 핀은 10~19번에 대한 대체 함수를 제어하기 위해 사용됩니다. 레지스터 안의 모든 비트의 의미는 다음 테이블안에 나타나 있습니다(`BCM2837 ARM Peripherals` 메뉴얼의 102페이지부터 얻은 이미지입니다.). ![Raspberry Pi GPIO function selector](../../../images/gpfsel1.png?raw=true) 그래서 미니 UART 디바이스와 함께 동작하는 GPIO 핀 14번과 15번을 구성하기 위해 사용되는 다음 코드를 이해하기 위해 필요한 모든 것을 알았습니다. ``` unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // clean gpio14 selector |= 2<<12; // set alt5 for gpio14 selector &= ~(7<<15); // clean gpio15 selector |= 2<<15; // set alt5 for gpio 15 put32(GPFSEL1,selector); ``` #### GPIO pull-up/down GPIO 핀을 사용할 때 종종 pull-up/pull-down이라는 용어를 만날 것입니다. 이 개념들은 [여기](https://grantwinney.com/using-pullup-and-pulldown-resistors-on-the-raspberry-pi/)에 자세하게 설명되어 있습니다. 이 기사를 읽기에 너무 게으른 사람들을 위해 pull-up/pull-down 개념에 대해 짧게 설명하겠습니다. 특정 핀을 입력으로 사용하고 이 핀에 아무것도 연결하지 않으면 핀의 값이 1인지 0인지를 식별할 수 없습니다. 사실, 이 장치는 무작위 값을 보고할 것입니다. pull-up/pull-down 메커니즘은 당신이 이 문제를 극복할 수 있게 해줍니다. 핀을 풀업 상태로 설정했는데 아무 것도 연결되어 있지 않으면 항상 "1"이 보고됩니다(풀다운 상태의 경우 값은 항상 0이 됩니다). 우리의 경우 14핀과 15핀 모두 항상 연결되기 때문에 풀업 상태도 풀다운 상태도 필요하지 않습니다. 핀 상태는 재부팅 후에도 보존되기 때문에 핀을 사용하기 전에 항상 초기화를 해야 합니다. 3가지 상태(풀업, 풀다운, 둘다 아닌)가 있습니다. 핀 상태들 간의 스위칭은 전기 회로 위의 물리적인 토글링 전환이 필요하기 때문에 간단한 절차가 아닙니다. 이 과정은 `GPPUD`과 `GPPUDCLK` 레지스터를 포함하고 이것은 `BCM2837 ARM Peripherals` 메뉴얼 101 페이지에 설명되어 있습니다. 여기에 그것을 복사했습니다. ``` The GPIO Pull-up/down Clock Registers control the actuation of internal pull-downs on the respective GPIO pins. These registers must be used in conjunction with the GPPUD register to effect GPIO Pull-up/down changes. The following sequence of events is required: 1. Write to GPPUD to set the required control signal (i.e. Pull-up or Pull-Down or neither to remove the current Pull-up/down) 2. Wait 150 cycles – this provides the required set-up time for the control signal 3. Write to GPPUDCLK0/1 to clock the control signal into the GPIO pads you wish to modify – NOTE only the pads which receive a clock will be modified, all others will retain their previous state. 4. Wait 150 cycles – this provides the required hold time for the control signal 5. Write to GPPUD to remove the control signal 6. Write to GPPUDCLK0/1 to remove the clock ``` 이 절차는 우리가 어떻게 핀으로부터 풀업과 풀다운 상태를 제거할 수 있는지 설명합니다. 그런데 그 핀들은 아래 코드에서 핀 14번과 15번을 위해 행해지고 있는 것입니다. ``` put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); ``` #### 미니 UART 초기화하기 이제 미니 UART가 GPIO 핀에 연결되어 있으며 핀이 구성되어 있다. 나머지 `uart_init` 함수는 미니 UART를 초기화하는 전용입니다. ``` put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 put32(AUX_MU_IIR_REG,6); //Clear FIFO put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver ``` 코드를 한줄 한줄 봐보자. ``` put32(AUX_ENABLES,1); //Enable mini uart (this also enables access to its registers) ``` 이 코드는 미니 UART를 가능하게 합니다. 이것은 또한 다른 미니 UART 레지스터에 대한 접근을 가능하게 하기 때문에 처음에 이 코드를 실행해야합니다. ``` put32(AUX_MU_CNTL_REG,0); //Disable auto flow control and disable receiver and transmitter (for now) ``` 여기서는 구성이 완료 되기 전에 수신기와 송신기를 비활성화 합니다. 또한 GPIO 핀을 추가로 사용해야하고, TTL-to-Serial 케이블이 이를 지원하지 않기 때문에자동 흐름 제어 기능을 영구적으로 비활성화합다. 흐름 제어에 관한 추가 정보를 알려면 [여기](http://www.deater.net/weave/vmwprod/hardware/pi-rts/)를 참조할 수 있습니다. ``` put32(AUX_MU_IER_REG,0); //Disable receive and transmit interrupts ``` 새로운 데이터를 사용할 수 있을 때마다 프로세서 인터럽트를 생성하도록 미니 UART를 구성할 수 있습니다. 우리는 레슨 3에서 인터럽트를 사용하기 시작할 것입니다. 그래서 현재는 이 기능을 사용하지 않습니다. ``` put32(AUX_MU_LCR_REG,3); //Enable 8 bit mode ``` 미니 UART는 7비트 또는 8비트 작업을 지원할 수 있습니다. 이는 ASCII 문자가 표준 집합의 경우 7비트, 확장된 문자의 경우 8비트이기 때문입니다. 우리는 8비트 모드를 사용할 것입니다. ``` put32(AUX_MU_MCR_REG,0); //Set RTS line to be always high ``` RTS 라인은 흐름 제어에 활용되며 우리는 사용하지 않습니다. 그래서 항상 HIGH로 설정합니다. ``` put32(AUX_MU_BAUD_REG,270); //Set baud rate to 115200 ``` 보드 비율은 통신 채널에서 정보가 전송되는 속도입니다. “115200 보드는 직렬 포트가 초당 최대 115200 비트를 전송할 수 있다는 것을 의미합니다. 라즈베리 파이 미니 UART 장치의 보드 속도는 터미널 에뮬레이터의 보드 속도와 동일해야합니다. ``` baudrate = system_clock_freq / (8 * ( baudrate_reg + 1 )) ``` `system_clock_frq`는 250MHz이므로 `baudrate_reg` 값을 270으로 쉽게 계산할 수 있습니다. ``` put32(AUX_MU_CNTL_REG,3); //Finally, enable transmitter and receiver ``` 마지막으로 수신기와 송신기를 활성화합니다. 미니 UART 작업 준비가 완료됩니다. ### 미니 UART를 통해서 데이터 전송하기 미니 UART가 준비되면, 데이터를 주고 받을 수 있습니다. 이를 위해서 다음 두 함수를 사용할 수 있습니다. ``` void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } ``` 두 함수는 모두 기기가 데이터를 전송 또는 수신할 준비가 되었는지 여부를 확인하기 위해서 무한 루프에서 시작합니다. 이를 위해 `AUX_MU_LSR_REG` 레지스터를 사용하고 있습니다. 비트 0은 1로 설정하면 데이터가 준비되었음을 나타내며, 이는 UART에서 읽을 수 있음을 의미합니다. 비트 5는 1로 설정하면 송신기가 비어 있다는 것을 말해, UART를 쓸 수 있다는 것을 의미합니다. 다음으로 `AUX_MU_IO_REG`를 사용하여 전송된 문자의 값을 저장하거나 수신된 문자의 값을 읽습니다. 또한 문자 대신 문자열을 보낼 수 있는 매우 간단한 함수가 있습니다. ``` void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } ``` 이 함수는 문자열에서 하나하나 모든 문자에 대해 반복합니다. ### 라즈베리 파이 설정 라즈베리 파이 시작 순서는 다음과 같습니다. 1. 라즈베리 파이 전원 ON 1. GPU가 시작되어 부팅 파티션에서 `config.txt`를 읽습니다. 이 파일에는 GPU가 시동 시퀀스를 추가로 조정하는데 사용하는 몇 가지 구성 파라미터가 포함 되어 있습니다. 1. `kernel8.img` 는 로드되고 실행됩니다. OS를 실행하기 위해서 `config.txt` 파일은 다음과 같아야합니다. ``` kernel_old=1 disable_commandline_tags=1 ``` * `kernel_old=1` 는 커널 이미지 주소 0에서 로드되도록 지정합니다. * `disable_commandline_tags`는 부팅된 이미지에 명령줄 인수를 전달하지 않도록 GPU에 지시합니다. ### 커널 테스트하기 이제 모든 소스 코드를 다 살펴봤으니 실행해볼 때가 되었습니다. 커널을 만들고 테스트 하려면 다음을 수행해야합니다. 1. [src/lesson01](https://github.com/s-matyukevich/raspberry-pi-os/tree/master/src/lesson01)에서 온 `./build.sh`이나 `./build.bat`을 커널을 빌드하기 위해서 실행하세요. 1. 라즈베리 파이의 플래시 메모리의 `boot` 파티션으로 `kernel8.img`를 복사하세요. 그리고 `kernel7.img`을 삭제하세요. 부트 파티션의 다른 파일들은 모두 그대로 둡니다.([여기](https://github.com/s-matyukevich/raspberry-pi-os/issues/43)를 참고할 수 있습니다.) 1. 이전 섹션의 설명대로 `config.txt`를 수정하세요. 1. USB-to-TTL 시리얼 케이블을 연결하세요. [Prerequisites](../Prerequisites.md). 1. 라즈베리 파이에 전원을 키세요. 1. 터미널 에뮬레이터를 킵니다. 이제 `Hello, World!` 메시지를 확인할 수 있습니다. 위에 설명된 단계 순서는 당신이 당신의 SD 카드에 라즈비언을 설치한 것으로 간주합니다. 또한 빈 SD 카드를 사용하여 RPi OS를 실행할 수도 있습니다. 1. SD 카드 준비: * MBR 파티션 테이블 사용 * 부팅 파티션을 FAT32로 포맷 > 라즈비언 설치에 필요한 것과 같은 방식으로 SD 카드를 포맷해야합니다. 자세한 내용은 [공식 문서](https://www.raspberrypi.org/documentation/installation/noobs.md) 안에 `HOW TO FORMAT AN SD CARD AS FAT` 섹션을 참고하세요. 1. 다음 파일들을 SD 카드로 복사하세요. * [bootcode.bin](https://github.com/raspberrypi/firmware/blob/master/boot/bootcode.bin) GPU 부트로더인데, 이것은 GPU를시작하는 코드와 GPU 펌웨어를 로드하는 코드를 포함하고 있습니다. * [start.elf](https://github.com/raspberrypi/firmware/blob/master/boot/start.elf) GPU 펌웨어입니다. `config.txt`를 읽고 GPU가 로드 되는 것을 가능하게 하고 `kernel8.img`로부터 ARM의 특정 사용자 코드를 로드하게 합니다. 1. `kernel8.img`와 `config.txt` 파일을 복사합니다. 1. USB-to-TTL 시리얼 케이블을 연결합니다. 1. 라즈베리파이에 전원을 ON합니다. 1. 터미널 에뮬레이터를 활용하여 RPi OS에 연결합니다. 불행하게도, 라즈베리파이 펌웨어 파일들을 모두 비공개입니다. 라즈베리 파이 시작을 순서에 대한 많은 정보를 위해 [이것](https://raspberrypi.stackexchange.com/questions/10442/what-is-the-boot-sequence)와 [이것](https://github.com/DieterReuter/workshop-raspberrypi-64bit-os/blob/master/part1-bootloader.md)과 같은 비공식적 소스를 참고할 수도 있습니다. #### 이전 페이지 [Prerequisites](../Prerequisites.md) #### 다음 페이지 1.2 [Kernel Initialization: Linux project structure](./linux/project-structure.md) ##### 추가 사항 이 문서는 훌륭한 오픈소스 운영체제 학습 프로젝트인 Sergey Matyukevich의 문서를 영어에 능숙하지 않은 한국인들이 학습할 수 있도록 번역한 것입니다. 오타 및 오역이 있을 수 있습니다. Sergey Matyukevich는 한국어를 능숙하게 다루지 못합니다. 대신 저에게 elxm6123@gmail.com으로 연락해주세요. ================================================ FILE: translations/ko/lesson02/exercises.md ================================================ ## 2.3: 연습 1. EL3에서 EL1로 바로 점프하는 대신 먼저 EL2로 이동 한 다음 EL1로 전환하십시오. 1. 이 레슨에서 작업할 때 우연히 발견한 한 가지 문제는 FP/SIMD 레지스터를 사용할 경우 모든 것이 EL3에서는 잘 작동하지만 EL1의 인쇄 기능에 도달하는 즉시 작동이 중지된다는 것이었습니다. 이것이 내가 컴파일러 옵션에 [-mgeneral-regs-only](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/Makefile#L3) 매개 변수를 추가한 이유였습니다. 이제 이 파라미터를 제거하고 이 행동을 재현해 주길 바랍니다. 다음으로 `objdump` 도구를 사용하여 gcc가 `-meneral-regs only` 플래그가 없을 때 FP/SIMD 레지스터를 정확히 사용하는 방법을 확인할 수 있습니다. 마지막으로 FP/SIMD 레지스터를 사용할 수 있도록 'cpak_el1'을 사용하십시오. 1. qemu에서 실행되도록 레슨 2에 적용하십시오. 이 issue를 [확인](https://github.com/s-matyukevich/raspberry-pi-os/issues/8)하십시오. ##### Previous Page 2.2 [Processor initialization: Linux](../lesson02/linux.md) ##### Next Page 3.1 [Interrupt handling: RPi OS](../lesson03/rpi-os.md) ##### 추가 사항 이 문서는 훌륭한 오픈소스 운영체제 학습 프로젝트인 Sergey Matyukevich의 문서를 영어에 능숙하지 않은 한국인들이 학습할 수 있도록 번역한 것입니다. 오타 및 오역이 있을 수 있습니다. Sergey Matyukevich는 한국어를 능숙하게 다루지 못합니다. 대신 저에게 elxm6123@gmail.com으로 연락해주세요. ================================================ FILE: translations/ko/lesson02/linux.md ================================================  ## 2.2: 프로세서 초기화 (Linux) `arm64` 아키텍처의 진입 점 인 [stext](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/head.S#L116) 함수에서 Linux 커널 탐색을 중단했습니다. 이번에는 조금 더 깊이 들어가 이 수업과 이전 수업에서 이미 구현한 코드와 몇 가지 유사점을 찾아 보겠습니다. 이 장은 다른 ARM 시스템 레지스터와 Linux 커널에서 사용되는 방법에 대해 주로 설명하기 때문에 약간 지루할 수 있습니다. 그러나 다음과 같은 이유로 저는 여전히 이 내용이 매우 중요하다고 생각합니다. 1. 하드웨어가 소프트웨어에 제공하는 인터페이스를 이해할 필요가 있습니다. 이 인터페이스를 아는 것만으로도 많은 경우의 특정 커널 기능이 구현되는 방법과 이 기능을 구현하기 위해 소프트웨어와 하드웨어가 어떻게 협력하는지를 분석할 수 있을 것입니다. 1. 시스템 레지스터의 다른 옵션은 일반적으로 다양한 하드웨어 기능을 활성화 / 비활성화하는 것과 관련이 있습니다. ARM 프로세서에 어떤 시스템 레지스터가 있는지 알고 있다면 어떤 종류의 기능을 지원하는지 이미 알고있을 것입니다. 자, 이제 `stext` 함수의 기능을 다시 살펴 봅시다. ``` ENTRY(stext) bl preserve_boot_args bl el2_setup // Drop to EL1, w0=cpu_boot_mode adrp x23, __PHYS_OFFSET and x23, x23, MIN_KIMG_ALIGN - 1 // KASLR offset, defaults to 0 bl set_cpu_boot_mode_flag bl __create_page_tables /* * The following calls CPU setup code, see arch/arm64/mm/proc.S for * details. * On return, the CPU will be ready for the MMU to be turned on and * the TCR will have been set. */ bl __cpu_setup // initialise processor b __primary_switch ENDPROC(stext) ``` ### preserve_boot_args (부팅 인수 유지) [preserve_boot_args](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/head.S#L136) 함수는 부트 로더가 커널에 전달한 매개 변수 저장을 담당합니다. ``` preserve_boot_args: mov x21, x0 // x21=FDT adr_l x0, boot_args // record the contents of stp x21, x1, [x0] // x0 .. x3 at kernel entry stp x2, x3, [x0, #16] dmb sy // needed before dc ivac with // MMU off mov x1, #0x20 // 4 x 8 bytes b __inval_dcache_area // tail call ENDPROC(preserve_boot_args) ``` [커널 부트 프로토콜](https://github.com/torvalds/linux/blob/v4.14/Documentation/arm64/booting.txt#L150)에 따라 매개 변수는 레지스터 `x0-x3`의 커널로 전달됩니다. `x0`에는 시스템 RAM에있는 Device Tree BLOB (`.dtb`)의 물리적 주소가 포함됩니다. `x1-x3`은 향후 사용을 위해 예약되어 있습니다. 이 기능은 x0-x3 레지스터의 내용을 [boot_args](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/setup.c#L93) 배열에 복사 한 다음 데이터 캐시에서 해당 캐시 라인을 [무효화](https://developer.arm.com/docs/den0024/latest/caches/cache-maintenance)합니다. 멀티프로세서 시스템의 캐시 유지보수는 그 자체로 큰 주제인데, 우리는 일단 이 주제를 생략할 것입니다. 이 주제에 관심이 있는 사람들을 위해, `ARM 프로그래머 가이드`의 [캐쉬](https://developer.arm.com/docs/den0024/latest/caches)와 [멀티 코어 프로세서](https://developer.arm.com/docs/den0024/latest/multi-core-processors) 챕터를 읽는 것을 추천하겠습니다. ### el2_setup (el2 설정) [arm64boot protocol](https://github.com/torvalds/linux/blob/v4.14/Documentation/arm64/booting.txt#L159) 프로토콜에 따라 커널은 EL1 또는 EL2로 부팅 될 수 있습니다. 두 번째 경우(EL2), 커널은 가상화 확장에 액세스 할 수 있으며 호스트 운영 체제로 작동 할 수 있습니다. EL2에서 부팅될 정도로 운이 좋다면 [el2_setup](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/head.S#L386) 함수가 호출됩니다. EL2에서만 액세스 할 수있는 다른 매개 변수 구성과 EL1 의 삭제를 담당합니다. 이제 이 기능을 작은 부분으로 나누고 각 부분을 하나씩 설명하겠습니다. ``` msr SPsel, #1 // We want to use SP_EL{1,2} ``` 특수 목적 스택 포인터는 EL1 및 EL2 모두에 사용됩니다. 다른 옵션은 EL0의 스택 포인터를 재사용하는 것입니다. ``` mrs x0, CurrentEL cmp x0, #CurrentEL_EL2 b.eq 1f ``` 현재 EL이 레이블 `1`에 EL2 분기 인 경우에만 EL2 설정 할 수 있고 그렇지 않으면 EL2 설정을 수행 할 수 없으며 이 기능에서 수행 할 작업이 많지 않습니다. ``` mrs x0, sctlr_el1 CPU_BE( orr x0, x0, #(3 << 24) ) // Set the EE and E0E bits for EL1 CPU_LE( bic x0, x0, #(3 << 24) ) // Clear the EE and E0E bits for EL1 msr sctlr_el1, x0 mov w0, #BOOT_CPU_MODE_EL1 // This cpu booted in EL1 isb ret ``` EL1에서 실행되면 `sctlr_el1` 레지스터가 업데이트되어 [CPU_BIG_ENDIAN](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/Kconfig#L612) 구성 설정 값에 따라 CPU가 `little-endian` 모드의 `big-endian`에서 작동하도록 합니다. 그런 다음 `el2_setup` 함수를 종료하고 [BOOT_CPU_MODE_EL1](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/virt.h#L55) 상수를 반환합니다. 따라서 [ARM64 함수 호출 규칙](http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf) 에 따라 반환 값은 `x0` 레지스터 (또는이 경우` w0`)에 있어야합니다. `w0` 레지스터는 `x0`의 첫 번째 32 비트로 생각할 수 있습니다. ``` 1: mrs x0, sctlr_el2 CPU_BE( orr x0, x0, #(1 << 25) ) // Set the EE bit for EL2 CPU_LE( bic x0, x0, #(1 << 25) ) // Clear the EE bit for EL2 msr sctlr_el2, x0 ``` EL2에서 부팅 된 것으로 보이면 EL2에 대해 동일한 종류의 설정을 수행하는 것입니다 (이번에는`sctlr_el1` 대신`sctlr_el2` 레지스터가 사용됩니다). ``` #ifdef CONFIG_ARM64_VHE /* * Check for VHE being present. For the rest of the EL2 setup, * x2 being non-zero indicates that we do have VHE, and that the * kernel is intended to run at EL2. */ mrs x2, id_aa64mmfr1_el1 ubfx x2, x2, #8, #4 #else mov x2, xzr #endif ``` [ARM64_VHE](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/Kconfig#L926)구성 변수를 통해 [VHE (Virtualization Host Extensions)](https://developer.arm.com/products/architecture/a-profile/docs/100942/latest/aarch64-virtualization)를 활성화하고 호스트 시스템이 이를 지원하면 `x2`가 0이 아닌 값으로 업데이트됩니다. `x2`는 나중에 같은 기능에서 `VHE`가 활성화되어 있는지 확인하는 데 사용됩니다. ``` mov x0, #HCR_RW // 64-bit EL1 cbz x2, set_hcr orr x0, x0, #HCR_TGE // Enable Host Extensions orr x0, x0, #HCR_E2H set_hcr: msr hcr_el2, x0 isb ``` 여기서 `hcr_el2` 레지스터를 설정합니다. 동일한 레지스터를 사용하여 RPi OS에서 EL1에 대한 64 비트 실행 모드를 설정했습니다. 이것은 제공된 코드 샘플의 첫 번째 줄에서 수행되는 작업입니다. 또한`x2! = 0` 인 경우 VHE를 사용할 수 있고 커널이 이를 사용하도록 구성되어 있으면`hcr_el2`도 VHE를 활성화하는 데 사용됩니다. ``` /* * Allow Non-secure EL1 and EL0 to access physical timer and counter. * This is not necessary for VHE, since the host kernel runs in EL2, * and EL0 accesses are configured in the later stage of boot process. * Note that when HCR_EL2.E2H == 1, CNTHCTL_EL2 has the same bit layout * as CNTKCTL_EL1, and CNTKCTL_EL1 accessing instructions are redefined * to access CNTHCTL_EL2. This allows the kernel designed to run at EL1 * to transparently mess with the EL0 bits via CNTKCTL_EL1 access in * EL2. */ cbnz x2, 1f mrs x0, cnthctl_el2 orr x0, x0, #3 // Enable EL1 physical timers msr cnthctl_el2, x0 1: msr cntvoff_el2, xzr // Clear virtual offset ``` 다음 코드는 위의 주석에 잘 설명되어 있습니다. 추가 할 것이 없습니다. ``` #ifdef CONFIG_ARM_GIC_V3 /* GICv3 system register access */ mrs x0, id_aa64pfr0_el1 ubfx x0, x0, #24, #4 cmp x0, #1 b.ne 3f mrs_s x0, SYS_ICC_SRE_EL2 orr x0, x0, #ICC_SRE_EL2_SRE // Set ICC_SRE_EL2.SRE==1 orr x0, x0, #ICC_SRE_EL2_ENABLE // Set ICC_SRE_EL2.Enable==1 msr_s SYS_ICC_SRE_EL2, x0 isb // Make sure SRE is now set mrs_s x0, SYS_ICC_SRE_EL2 // Read SRE back, tbz x0, #0, 3f // and check that it sticks msr_s SYS_ICH_HCR_EL2, xzr // Reset ICC_HCR_EL2 to defaults 3: #endif ``` 다음 코드 일부는 GICv3을 사용할 수 있고 활성화 된 경우에만 실행됩니다. GIC는 Generic Interrupt Controller의 약자입니다. GIC 사양의 v3 버전은 가상화 컨텍스트에서 특히 유용한 몇 가지 기능을 추가합니다. 예를 들어, GICv3을 사용하면 LPI (Locality-specific Peripheral Interrupt)가 가능합니다. 이러한 인터럽트는 메시지 버스를 통해 라우팅되며 구성은 메모리의 특수 테이블에 유지됩니다. 제공된 코드는 SRE (시스템 레지스터 인터페이스)를 활성화합니다.이 단계는 `ICC_*_ELn` 레지스터를 사용하고 GICv3 기능을 활용하기 전에 수행해야합니다. ``` /* Populate ID registers. */ mrs x0, midr_el1 mrs x1, mpidr_el1 msr vpidr_el2, x0 msr vmpidr_el2, x1 ``` `midr_el1` 및`mpidr_el1`은 식별 레지스터 그룹의 읽기 전용 레지스터입니다. 프로세서 제조업체, 프로세서 아키텍처 이름, 코어 수 및 기타 정보에 대한 다양한 정보를 제공합니다. EL1에서 액세스하려는 모든 독자(reader)를 위해 이 정보를 변경할 수 있습니다. 여기서 우리는`vpidr_el2`와`vmpidr_el2`를`midr_el1`과`mpidr_el1`에서 가져온 값으로 채웁니다. 따라서 이 정보는 EL1 또는 높은 예외 레벨에서 액세스하려고 하더라도 동일합니다. ``` #ifdef CONFIG_COMPAT msr hstr_el2, xzr // Disable CP15 traps to EL2 #endif ``` 프로세서가 32 비트 실행 모드에서 실행될 때는 "coprocessor"라는 개념이 있습니다. 64비트 실행 모드에서 일반적으로 시스템 레지스터를 통해 액세스되는 정보에 Coprocessor를 사용할 수 있습니다. [공식 문서](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0311d/I1014521.html)에서 coprocessor를 통해 정확히 액세스 할 수있는 내용을 읽을 수 있습니다. `msr hstr_el2, xzr` 명령어를 사용하면 낮은 예외 수준의 보조 프로세서를 사용할 수 있습니다. 호환성 모드가 활성화 된 경우에만 수행하는 것이 좋습니다 (이 모드에서 커널은 64 비트 커널에서 32 비트 사용자 응용 프로그램을 실행할 수 있음). ``` /* EL2 debug */ mrs x1, id_aa64dfr0_el1 // Check ID_AA64DFR0_EL1 PMUVer sbfx x0, x1, #8, #4 cmp x0, #1 b.lt 4f // Skip if no PMU present mrs x0, pmcr_el0 // Disable debug access traps ubfx x0, x0, #11, #5 // to EL2 and allow access to 4: csel x3, xzr, x0, lt // all PMU counters from EL1 /* Statistical profiling */ ubfx x0, x1, #32, #4 // Check ID_AA64DFR0_EL1 PMSVer cbz x0, 6f // Skip if SPE not present cbnz x2, 5f // VHE? mov x1, #(MDCR_EL2_E2PB_MASK << MDCR_EL2_E2PB_SHIFT) orr x3, x3, x1 // If we don't have VHE, then b 6f // use EL1&0 translation. 5: // For VHE, use EL2 translation orr x3, x3, #MDCR_EL2_TPMS // and disable access from EL1 6: msr mdcr_el2, x3 // Configure debug traps ``` 이 코드는 `mdcr_el2` (EL2 (Monitor Debug Configuration Register))를 구성합니다. 이 레지스터는 가상화 확장과 관련된 다른 디버그 트랩을 설정합니다. 디버그 및 추적이 논의 범위를 벗어나기 때문에 이 코드 블록의 세부 사항을 설명 하지 않겠습니다.. 자세한 내용은 [AArch64-Reference-Manual](https://developer.arm.com/docs/ddi0487/ca/arm-architecture-reference-manual-armv8-for-armv8-a-architecture-profile) 의`2810` page의 `mdcr_el2` 레지스터에 대한 설명을 읽는 것이 좋습니다. ``` /* Stage-2 translation */ msr vttbr_el2, xzr ``` OS를 하이퍼 바이저로 사용하는 경우 게스트 OS에 대한 완벽한 메모리 격리를 제공해야합니다. 2 단계 가상 메모리 변환은이 용도로 정확하게 사용됩니다. 실제로 각 게스트 OS는 모든 시스템 메모리를 소유한다고 생각하지만 실제로는 각 메모리 액세스가 2 단계 변환에 의해 실제 메모리에 매핑됩니다. `vttbr_el2`는 2 단계 변환을위한 변환테이블의 기본 주소를 보유합니다. 이 시점에서 2 단계 변환이 비활성화되고`vttbr_el2`는 0으로 설정되어야합니다. ``` cbz x2, install_el2_stub mov w0, #BOOT_CPU_MODE_EL2 // This CPU booted in EL2 isb ret ``` 첫 번째`x2`는`0`과 비교되어 VHE가 활성화되어 있는지 확인합니다. 그렇다면`install_el2_stub` 레이블로 이동하십시오. 그렇지 않으면 CPU가 EL2 모드로 부팅 된 것을 기록하고`el2_setup` 기능을 종료하십시오. 후자의 경우 프로세서는 계속 EL2 모드에서 작동하며 EL1은 전혀 사용되지 않습니다. ``` install_el2_stub: /* sctlr_el1 */ mov x0, #0x0800 // Set/clear RES{1,0} bits CPU_BE( movk x0, #0x33d0, lsl #16 ) // Set EE and E0E on BE systems CPU_LE( movk x0, #0x30d0, lsl #16 ) // Clear EE and E0E on LE systems msr sctlr_el1, x0 ``` 이 시점에 도달하면 VHE가 필요하지 않고 곧 EL1로 전환 할 것이므로 미리 EL1 초기화를 수행해야합니다. 복사 된 코드 단편은`sctlr_el1` (시스템 제어 레지스터) 초기화를 담당합니다. 우리는 이미 RPi OS에서 [동일한 작업](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/src/boot.S#L18)을 수행했습니다. ``` /* Coprocessor traps. */ mov x0, #0x33ff msr cptr_el2, x0 // Disable copro. traps to EL2 ``` 이 코드를 통해 EL1은 `cpacr_el1` 레지스터에 액세스 할 수 있으며 결과적으로 Trace, Floating-point 및 Advanced SIMD 기능에 대한 접근을 제어 할 수 있습니다. ``` /* Hypervisor stub */ adr_l x0, __hyp_stub_vectors msr vbar_el2, x0 ``` 일부 기능에는 필요하지만 지금은 EL2를 사용할 계획이 없습니다. 하지만 우리는 현재 실행중인 커널에서 다른 커널로 로드하고 부팅 할 수있는 [kexec](https://linux.die.net/man/8/kexec) 시스템 호출을 구현하려면 EL2가 필요합니다. [_hyp_stub_vectors](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/hyp-stub.S#L33)는 EL2에 대한 모든 예외 처리기(Exception handler)의 주소를 가지고 있습니다. 다음 단원에서는 인터럽트 및 예외 처리에 대해 자세히 설명한 후 EL1에 대한 예외 처리 기능을 구현할 것입니다. ``` /* spsr */ mov x0, #(PSR_F_BIT | PSR_I_BIT | PSR_A_BIT | PSR_D_BIT |\ PSR_MODE_EL1h) msr spsr_el2, x0 msr elr_el2, lr mov w0, #BOOT_CPU_MODE_EL2 // This CPU booted in EL2 eret ``` 마지막으로 EL1에서 프로세서 상태를 초기화하고 예외 수준을 전환해야합니다. 우리는 이미 [RPi OS](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/src/boot.S#L27-L33) 에서 이 작업을 했으므로이 코드의 세부 사항을 설명하지 않을 것입니다. 여기서 유일하게 새로운 것은`elr_el2`가 초기화되는 방법입니다. `lr` 또는 링크 레지스터는`x30`의 별칭입니다. `bl` (Branch Link) 명령어를 실행할 때마다`x30`은 현재 명령어의 주소로 자동으로 채워집니다. 사실 일반적으로 `ret` 명령에 의해 사용되므로 정확히 어디로 돌아갈 지 알고 있습니다. 우리의 경우 lr은 [여기](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/head.S#L119)를 가리키며 `elr_el2`를 초기화하는 방식 때문에 EL1로 전환 한 후 실행이 재개되는 곳이기도합니다. ### Processor initialization at EL1 (EL1에서 프로세서 초기화) 이제 `stext` 함수로 돌아갑니다. 다음 몇 줄은 우리에게 별로 중요하지 않지만 온전한 설명을 위해 짚고 넘어 가겠습니다. ``` adrp x23, __PHYS_OFFSET and x23, x23, MIN_KIMG_ALIGN - 1 // KASLR offset, defaults to 0 ``` [KASLR](https://lwn.net/Articles/569635/)(Kernel Address Space Layout Randomization)은 커널을 메모리의 임의 주소에 배치 할 수있는 기술입니다. 이것은 오직 보안상의 이유로 필요합니다. 자세한 내용은 위의 링크를 읽으십시오. ``` bl set_cpu_boot_mode_flag ``` 여기서 CPU 부팅 모드는 [__boot_cpu_mode](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/virt.h#L74) 변수에 저장됩니다. 이 작업을 수행하는 코드는 이전에 살펴본 `preserve_boot_args` 함수와 매우 유사합니다. ``` bl __create_page_tables bl __cpu_setup // initialise processor b __primary_switch ``` 마지막 3 가지 기능은 매우 중요하지만 모두 가상 메모리 관리와 관련되어 있으므로 6 장까지 세부적인 설명을 미루겠습니다. 지금은 단지 의미를 간략하게 설명하고자합니다. * `__create_page_tables` 이름에서 알 수 있듯이 페이지 테이블을 생성합니다. * `__cpu_setup` 가상 프로세서 관리에 특화된 다양한 프로세서 설정을 초기화합니다. * `__primary_switch` MMU를 활성화하고 아키텍처 독립적 시작지점 인 start_kernel 함수로 이동합니다. ### 결론 이 장에서는 Linux 커널을 부팅 할 때 프로세서가 초기화되는 방법에 대해 간단히 설명했습니다. 다음 레슨에서도 계속 ARM 프로세서와 긴밀하게 작업하며 모든 OS의 핵심 주제 인 인터럽트 처리를 알아보겠습니다. ##### 이전 페이지 2.1 [Processor initialization: RPi OS](../lesson02/rpi-os.md) ##### 다음 페이지 2.3 [Processor initialization: Exercises](../lesson02/exercises.md) ##### 추가 사항 이 문서는 훌륭한 오픈소스 운영체제 학습 프로젝트인 Sergey Matyukevich의 문서를 영어에 능숙하지 않은 한국인들이 학습할 수 있도록 번역한 것입니다. 오타 및 오역이 있을 수 있습니다. Sergey Matyukevich는 한국어를 능숙하게 다루지 못합니다. 대신 저에게 elxm6123@gmail.com으로 연락해주세요. ================================================ FILE: translations/ko/lesson02/rpi-os.md ================================================ ## 2.1: 프로세서 초기화 이 레슨에서는 ARM 프로세서와 더욱 밀접하게 작업합니다. OS에서 활용 할 수 있는 몇 가지 필수 기능이 있습니다. 첫 번째 그러한 기능은 "Exception levels" 입니다. ### Exception levels (예외 레벨) ARM.v8 아키텍처를 지원하는 각 ARM 프로세서에는 4 개의 Exception level(예외 레벨)이 있습니다. Exception level (또는 간단히 `EL`)은 모든 작업 및 레지스터의 하위 집합 만 사용할 수있는 프로세서 실행 모드로 생각할 수 있습니다. 최소 권한 Exception level은 0 (EL0) 입니다. 프로세서가 이 level에서 작동 할 때는 대부분 범용 레지스터 (X0-X30)와 스택 포인터 레지스터 (SP) 만 사용합니다. 또한 EL0는 `STR` 및`LDR` 명령을 사용하여 사용자 프로그램에서 일반적으로 사용하는 몇 가지 명령어으로 메모리에 데이터를 로드하고 저장할 수 있습니다. 운영 체제는 프로세스 격리를 구현해야하므로 Exception levels(예외 레벨)을 처리해야합니다. 사용자 프로세스는 다른 프로세스의 데이터에 액세스할 수 없어야 합니다. 이러한 동작을 하기 위해 운영 체제는 항상 각 사용자 프로세스를 EL0에서 실행합니다. 이 예외수준에서 실행되는 프로세스는 자신의 가상 메모리만 사용 할 수 있으며 가상 메모리 설정을 변경하는 명령에 접근 할 수 없습니다. 따라서 프로세스 격리를 보장하기 위해 OS는 사용자 프로세스로 실행을 전송하기 전에 각 프로세스에 대해 별도의 가상 메모리 매핑을 준비하고 프로세서를 EL0에 배치해야합니다. 운영 체제 자체는 일반적으로 EL1에서 작동합니다. 이 예외 수준에서 실행되는 동안 프로세서는 일부 시스템 레지스터뿐만 아니라 가상 메모리 설정을 구성 할 수있는 레지스터에 액세스 할 수 있습니다. Raspberry Pi OS도 EL1을 사용합니다. 우리는 예외 레벨(EL) 2와 3을 많이 사용하지 않을 것입니다. 하지만 간단히 설명해서 이것들이 왜 필요한지 여러분이 알 수 있도록 하고 싶습니다. 하이퍼 바이저를 사용하는 시나리오에서 EL2가 사용됩니다. 이 경우 호스트 운영 체제는 EL2에서 실행되며 게스트 운영 체제는 EL 1 만 사용할 수 있습니다. 이렇게하면 OS가 사용자 프로세스를 분리하는 것과 비슷한 방식으로 호스트 OS가 게스트 OS를 격리 할 수 있습니다. EL3은 ARM "Secure World"에서 "Insecure world"로 전환하는 데 사용됩니다. 이 추상화는 서로 다른 두 "World"에서 실행되는 소프트웨어간에 완전한 하드웨어 격리를 제공하기 위해 존재합니다. "Insecure world"의 응용 프로그램은 "Secure World"에 속하는 정보 (명령 및 데이터 모두)에 액세스하거나 수정할 수 없으며 이러한 제한은 하드웨어 level에서 적용됩니다. ### Debugging the kernel (커널 디버깅) 다음으로 해야 할 일은 현재 사용중인 예외 레벨(`EL`)을 파악하는 것입니다. 그러나 이 작업을 시도했을 때 커널이 화면에 일정한 문자열 만 보여 줄 수 있다는 것을 알게되었습니다. 그래서 이 작업을 위해 필요한 것이 간단한 형식의 [printf](https://en.wikipedia.org/wiki/Printf_format_string) 함수입니다. `printf` 를 사용하면 다른 레지스터와 변수의 값을 쉽게 표시 할 수 있습니다. 커널 개발에 있어서 다른 디버거의 지원이 없고 , `printf`가 프로그램 내에서 무슨 일이 일어나고 있는지 알아낼 수 있는 유일한 수단이 되기 때문에 이러한 기능은 필수적입니다. RPi OS의 경우 이미 있는 것을 다시 만드느라 쓸데없이 시간을 낭비하지 않고 [기존 printf 구현](http://www.sparetimelabs.com/tinyprintf/tinyprintf.php) 중 하나를 사용하기로 결정했습니다. 이 기능은 대부분 문자열 조작으로 구성되며 커널 개발자의 관점에서 보면 그리 중요하지 않습니다. 내가 사용한 구현은 매우 작으며 외부 종속성이 없으므로 커널에 쉽게 통합 할 수 있습니다. 내가 해야 할 유일한 것은 화면에 단일 문자를 보낼 수있는`putc` 기능을 정의하는 것입니다. 이 함수는 [여기](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/src/mini_uart.c#L59) 에 정의되어 있으며 기존의 uart_send 함수 만 사용합니다. 또한 printf 라이브러리를 초기화하고 putc 함수의 위치를 지정해야합니다. 이것은 [한 줄의 코드](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/src/kernel.c#L8)로 이루어집니다. ### Finding current Exception level (현재 예외 레벨 찾기) 이제 `printf` 기능이 갖춰지면, 원래 작업을 완료 할 수 있습니다. OS가 부팅되는 예외 레벨을 파악하십시오. 이 질문에 대답할 수 있는 작은 기능이 [여기](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/src/utils.S#L1) 에 정의되어 있다. ``` .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret ``` 위에서 `mrs` 명령어를 사용하여`CurrentEL` 시스템 레지스터의 값을`x0` 레지스터로 읽습니다. 그런 다음 이 값을 2 비트 오른쪽으로 옮깁니다 ( `CurrentEL`레지스터의 처음 2 비트는 예약되어 있고 항상 값 0을 갖기 때문에이 작업이 필요합니다) 마지막으로 레지스터 `x0`에는 현재 예외를 나타내는 정수가 있습니다. 이제 남은 것은 이 값을 표시하는 것뿐입니다. [여기](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/src/kernel.c#L10)에서 그 작업을 볼 수 있습니다. ``` int el = get_el(); printf("Exception level: %d \r\n", el); ``` 이 작업을 재현해보면 화면에 'Exception level : 3'가 표시됩니다. ### Changing current exception level (현재 예외 레벨 변경) ARM 아키텍처에서는 이미 실행되고 있는 더 높은 레벨의 소프트웨어의 참여 없이는 자체적으로 예외 수준을 증가시키는 방법이 없습니다. 이건 완전 당연한 행동입니다. 그렇지 않으면 어떤 프로그램도 할당된 EL에서 벗어나 다른 프로그램 데이터에 접근할 수 있을 것입니다. 예외가 생성 된 경우에만 현재 EL을 변경할 수 있습니다. 예를 들어, 프로그램이 잘못된 명령을 실행하는 경우 (예 : 존재하지 않는 주소의 메모리 위치에 액세스하거나 0으로 나누려고 시도하는 경우) 발생할 수 있습니다. 또한 응용 프로그램은 `svc` 명령어를 실행하여 의도적으로 예외를 생성 할 수 있습니다. 하드웨어 생성 인터럽트(Hardware generated interrupt) 도 특수한 유형의 예외로 처리됩니다. 예외가 생성 될 때마다 다음과 같은 일련의 단계가 발생합니다 (예외는 EL`n`에서 처리되고`n`은 1, 2 또는 3 일 수 있다고 가정합니다). 1. 현재 명령어의 주소는`ELR_ELn` 레지스터에 저장됩니다. ( `Exception link register` 라고 불림) 2. 현재 프로세서 상태는 `SPSR_ELn` 레지스터 (`Saved Program Status Register`)에 저장됩니다. 3. Exception handler 가 실행되고 필요한 모든 작업을 수행합니다. 4. Exception handler 는`eret` 명령을 호출합니다. 이 명령어는 `SPSR_ELn`에서 프로세서 상태를 복원하고 `ELR_ELn`레지스터에 저장된 주소부터 시작하여 실행을 재개합니다. 실제 Exception handler는 모든 범용 레지스터의 상태를 저장하고 나중에 다시 복원해야 하기 때문에 절차가 조금 더 복잡하지만, 우리는 다음 수업에서 이 과정을 자세히 논의할 것이다. 현재로서는 그 과정을 전반적으로 이해하고 `ER_ELm`과 `SPSR_ELn` 레지스터의 의미를 기억하기만 하면 됩니다. 알아야 할 중요한 것은 Exception handler가 예외가 발생한 동일한 위치로 돌아갈 의무가 없다는 것입니다. `ER_ELm`과 `SPSR_ELn`은 모두 쓰기 가능하며, Exception handler는 원할 경우 수정할 수 있습니다. 코드에서 EL3에서 EL1로 바꾸려고 할 때 이 방법이 유용하게 사용 될 것입니다. ### Switching to EL1 (EL1으로 전환) 엄밀히 말하면, 우리의 운영체제는 EL1로 전환할 의무는 없지만, EL1은 우리에게 있어서 자연스러운 선택입니다. 왜냐면 이 레벨은 모든 일반적인 OS 과제를 실행할 수 있는 적절한 권한을 가지고 있기 때문입니다. 또한 예외 레벨의 전환이 어떻게 작동하는지 알아보는 재미있는 연습이 될 것입니다. 이러한 행동을 하는 [소스 코드](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/src/boot.S#L17)를 보시죠. ``` master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret ``` 보시다시피 코드는 대부분 몇 개의 시스템 레지스터 구성으로 구성됩니다. 이제 레지스터들을 하나씩 살펴볼 것입니다. 이렇게하려면 먼저 [AArch64-Reference-Manual](https://developer.arm.com/docs/ddi0487/ca/arm-architecture-reference-manual-armv8-for-armv8-a-architecture-profile)을 다운로드해야합니다. 이 문서는 ARM.v8 아키텍처의 세부 사양이 포함되어 있습니다. #### SCTLR_EL1, 시스템 제어 레지스터 (EL1), AArch64-Reference-Manual의 2654 페이지. ``` ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ``` 위에서 sctlr_el1 시스템 레지스터의 값을 설정합니다. `sctlr_el1`은 EL1에서 작동 할 때 프로세서의 다른 매개 변수를 구성합니다. 예를 들어, 캐시 사용 여부와 MMU (Memory Mapping Unit)가 켜져 있는지 여부를 가장 중요하게 제어합니다. `sctlr_el1`은 EL1보다 높거나 같은 모든 예외 수준(EL)에서 액세스 할 수 있습니다 (`_el1` 접미사 에서 이를 유추 할 수 있음) SCTLR_VALUE_MMU_DISABLED 상수는 [여기](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/include/arm/sysregs.h#L16)서 정의되며 이 값의 개별 비트는 다음과 같이 정의됨: * `#define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11)` `sctlr_el1` 레지스터 설명에서 일부 비트는 `RES1`으로 표시됩니다. 이 비트는 향후 사용을 위해 예약되어 있으며 `1`로 초기화해야합니다. * `#define SCTLR_EE_LITTLE_ENDIAN (0 << 25)` [Endianness](https://en.wikipedia.org/wiki/Endianness) 예외. 이 필드는 EL1에서 명시 적 데이터 액세스의 엔디안을 제어합니다. 리틀 엔디안 형식으로 만 작동하도록 프로세서를 구성하려고합니다. * `#define SCTLR_EOE_LITTLE_ENDIAN (0 << 24)` 이전 필드와 비슷하지만 EL1 대신 EL 0에서 명시 적 데이터 액세스의 엔디안을 제어합니다 * `#define SCTLR_I_CACHE_DISABLED (0 << 12)` 명령어 캐시를 비활성화합니다. 우리는 간단하게하기 위해 모든 캐시를 사용하지 않도록 할 것입니다. 데이터와 명령어 캐시에 대한 자세한 정보는 [여기](https://stackoverflow.com/questions/22394750/what-is-meant-by-data-cache-and-instruction-cache)서 찾을 수 있습니다. * `#define SCTLR_D_CACHE_DISABLED (0 << 2)` 데이터 캐시 비활성화. * `#define SCTLR_MMU_DISABLED (0 << 0)` MMU를 비활성화하십시오. 페이지 테이블을 준비하고 가상 메모리 작업을 시작하는 레슨 6까지 MMU를 비활성화해야합니다. #### HCR_EL2, 하이퍼 바이저 구성 레지스터 (EL2), AArch64-Reference-Manual의 2487 페이지. ``` ldr x0, =HCR_VALUE msr hcr_el2, x0 ``` 우리는 자체 하이퍼 바이저를 구현하지 않을 것입니다. 다른 설정 중에서도 EL1의 실행 상태를 제어하기 때문에 이 레지스터를 사용해야합니다. 실행 상태는 AArch32가 아니라 AArch64 여야합니다. [여기](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/include/arm/sysregs.h#L22)에 구성되어 있습니다. #### SCR_EL3, AArch64-Reference-Manual의 284 페이지, Secure Configuration Register(SCR) (EL3). ``` ldr x0, =SCR_VALUE msr scr_el3, x0 ``` 이 레지스터는 configuring security settings을 담당합니다. 예를 들어, 모든 하위 레벨이 "secure"또는 "nonsecure"상태에서 실행되는지 여부를 제어합니다. 또한 EL2에서 실행 상태를 제어합니다. [여기서](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/include/arm/sysregs.h#L26) 우리는 EL2가 AArch64 상태에서 실행되도록 설정하고 모든 하위 예외 수준은 "nonsecure"이 됩니다. #### SPSR_EL3, #### Saved Program Status Register (EL3), AArch64-Reference-Manual의 389 페이지 ``` ldr x0, =SPSR_VALUE msr spsr_el3, x0 ``` 이 레지스터는 이제 익숙해야 합니다. 예외 수준 변경 프로세스에 대해 논의할 때 언급하였습니다. `spsr_el3`에는 프로세서 상태가 포함되어 있으며, 이 상태는 `erer` 명령을 실행한 후 복원될 것입니다. 프로세서 상태란 무엇인지 간략하게 설명 할 필요가 있습니다. 프로세서 상태에는 다음 정보가 포함됩니다. * **Condition Flags** 이러한 플래그에는 이전에 실행 된 작업에 대한 정보가 포함됩니다. 결과가 음수 (N 플래그), 0 (A 플래그), 부호없는 오버플로 (C 플래그) 또는 부호있는 오버플로 (V 플래그) 여부. 이러한 플래그의 값은 조건부 분기 명령어에서 사용될 수 있습니다. 예를 들어 b.eq 명령어는 마지막 비교 작업의 결과가 0 인 경우에만 제공된 레이블로 이동합니다. 프로세서는 Z 플래그가 1로 설정되어 있는지 테스트 하여 이를 확인합니다. * **Interrupt disable bits** 이 비트들은 다른 유형의 인터럽트를 활성화 / 비활성화 할 수 있습니다. * 예외를 처리한 후 프로세서 실행 상태를 완전히 복원하는 데 필요한 기타 정보. 일반적으로 `spsr_el3`은 EL3에 예외가 발생하면 자동으로 저장됩니다. 그러나이 레지스터는 쓰기 가능하므로이 사실을 활용하고 수동으로 프로세서 상태를 준비합니다. `SPSR_VALUE`는 [여기](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/include/arm/sysregs.h#L35) 에서 준비되었으며 다음 필드를 초기화합니다. * `#define SPSR_MASK_ALL (7 << 6)` EL을 EL1로 변경하면 모든 유형의 인터럽트가 마스킹됩니다 (또는 비활성화 됨). * `#define SPSR_EL1h (5 << 0)` EL1에서는 자체 전용 스택 포인터를 사용하거나 EL0 스택 포인터를 사용할 수 있습니다. `EL1h` 모드는 EL1 전용 스택 포인터를 사용하고 있음을 의미합니다. #### ELR_EL3, Exception Link Register (EL3), AArch64-Reference-Manual의 351 페이지. ``` adr x0, el1_entry msr elr_el3, x0 eret ``` `elr_el3`은 `eret` 명령이 실행 된 후 반환 할 주소를 보유합니다. 여기에서이 주소를 `el1_entry` 레이블의 위치로 설정합니다. ### 결론 우리가`el1_entry` 함수에 들어갈 때 실행은 이미 EL1 모드에 있어야합니다. 시도해보십시오! ##### 이전페이지 1.5 [Kernel Initialization: Exercises](../lesson01/exercises.md) ##### 다음페이지 2.2 [Processor initialization: Linux](../lesson02/linux.md) ##### 추가 사항 이 문서는 훌륭한 오픈소스 운영체제 학습 프로젝트인 Sergey Matyukevich의 문서를 영어에 능숙하지 않은 한국인들이 학습할 수 있도록 번역한 것입니다. 오타 및 오역이 있을 수 있습니다. Sergey Matyukevich는 한국어를 능숙하게 다루지 못합니다. 대신 저에게 elxm6123@gmail.com으로 연락해주세요. ================================================ FILE: translations/ko/lesson03/exercises.md ================================================ ## 3.5: 연습 1. 시스템 타이머 대신 로컬 타이머를 사용하여 프로세서 인터럽트를 생성하십시오. 자세한 내용은 [이 문제](https://github.com/s-matyukevich/raspberry-pi-os/issues/70) 를 참조하십시오. 1. MiniUART 인터럽트를 처리하십시오. `kernel_main` 함수의 마지막 루프를 아무것도하지 않는 루프로 교체하십시오. 사용자가 새 문자를 입력하자마자 인터럽트를 생성하도록 MiniUART 장치를 설정하십시오. 새로 도착한 각 문자를 화면에 인쇄하는 인터럽트 처리기를 구현하십시오. 1. qemu에서 실행되도록 Lesson 03을 적용하십시오. [이 문제](https://github.com/s-matyukevich/raspberry-pi-os/issues/8) 를 참조하십시오. ##### 다음 페이지 3.4 [Interrupt handling: Timers](../../docs/lesson03/linux/timer.md) ##### 이전 페이지 4.1 [Process scheduler: RPi OS Scheduler](../lesson03/rpi-os.md) ##### 추가 사항 이 문서는 훌륭한 오픈소스 운영체제 학습 프로젝트인 Sergey Matyukevich의 문서를 영어에 능숙하지 않은 한국인들이 학습할 수 있도록 번역한 것입니다. 오타 및 오역이 있을 수 있습니다. Sergey Matyukevich는 한국어를 능숙하게 다루지 못합니다. 대신 저에게 elxm6123@gmail.com으로 연락해주세요. ================================================ FILE: translations/ko/lesson03/linux/interrupt_controllers.md ================================================ ## 3.3: 인터럽트 컨트롤러 이 장에서는 Linux 드라이버와 인터럽트를 처리하는 방법에 대해 많이 이야기 할 것입니다. 드라이버 초기화 코드부터 시작하여 [handle_arch_irq](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/irq.c#L44) 함수 이후에 인터럽트가 어떻게 처리되는지 살펴 보겠습니다. ### 디바이스 트리를 사용하여 필요한 장치 및 드라이버 찾기 RPi OS에서 인터럽트를 구현할 때 시스템 타이머와 인터럽트 컨트롤러의 두 가지 장치를 사용했습니다. 이제 우리의 목표는 Linux에서 동일한 장치가 어떻게 작동하는지 이해하는 것입니다. 가장 먼저해야 할 일은 언급 된 장치를 다루는 드라이버를 찾는 것입니다. 필요한 드라이버를 찾기 위해 [bcm2837-rpi-3-b.dts](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837-rpi-3-b.dts) 장치 트리 파일을 사용할 수 있습니다. 이 파일은 Raspberry Pi 3 Model B에 고유한 최상위 장치 트리 파일이며, 다른 버전의 Raspberry Pi간에 공유되는 다른 일반적인 장치 트리 파일을 포함합니다. 포함 체인을 따라 `timer`및 `interrupt-controller`를 검색하면 4개의 장치를 찾을 수 있습니다. 1. [Local interrupt controller](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837.dtsi#L11) 1. [Local timer](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837.dtsi#L20) 1. Global interrupt controller. It is defined [here](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm283x.dtsi#L109) and modified [here](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837.dtsi#L72). 1. [System timer](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm283x.dtsi#L57) 멈춰야하는데 왜 개가 아닌 4개의 장치가 있을까요? 여기에는 약간의 설명이 필요하며 다음 섹션에서이 질문을 다룰 것입니다. ### 로컬 vs 글로벌 인터럽트 컨트롤러 다중 프로세서 시스템의 인터럽트 처리에 대해 생각할 때 특정 인터럽트를 처리해야 할 코어는 무엇입니까? 인터럽트가 발생하면 4개의 코어가 모두 인터럽트됩니까, 아니면 하나의 코어만 중단됩니까? 특정 인터럽트를 특정 코어로 라우팅 할 수 있습니까? 궁금한 또 다른 질문은 정보를 전달해야 할 경우 한 프로세서가 다른 프로세서에게 어떻게 알릴 수 있습니까? 로컬 인터럽트 컨트롤러는 이러한 질문들을 대답하는 데 도움이되는 장치입니다. 다음 작업을 담당합니다. * 특정 인터럽트를 수신 할 코어 구성. * 코어 간 인터럽트 전송 이러한 인터럽트를 "mailbox"이라고하며 코어가 서로 통신 할 수 있도록합니다. * 로컬 타이머 및 성능 모니터 인터럽트(PMU)의 인터럽트 처리. 로컬 타이머뿐만 아니라 로컬 인터럽트 컨트롤러의 동작은 [BCM2836 ARM 로컬 주변 장치](https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2836/QA7_rev3.4.pdf) 메뉴얼에 설명되어 있습니다. 이미 로컬 타이머를 여러 번 언급했습니다. 이제 왜 시스템에 두 개의 독립 타이머가 필요한지 궁금 할 것입니다. 로컬 타이머를 사용하는 주요 사용 사례는 타이머 인터럽트를 동시에 받도록 4개의 코어를 모두 구성하려는 경우입니다. 시스템 타이머를 사용하면 인터럽트를 단일 코어로만 라우팅 할 수 있습니다. RPi OS로 작업 할 때 로컬 인터럽트 컨트롤러 또는 로컬 타이머로 작동하지 않았습니다. 기본적으로 로컬 인터럽트 컨트롤러는 모든 외부 인터럽트가 첫 번째 코어로 전송되도록 구성되어 있기 때문에 정확히 필요한 것입니다. 시스템 타이머를 대신 사용하기 때문에 로컬 타이머를 사용하지 않았습니다. ### 로컬 인터럽트 컨트롤러 [bcm2837.dtsi](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837.dtsi#L75)에 따르면 전역 인터럽트 컨트롤러는 로컬의 자식이다. 따라서 로컬 컨트롤러로 탐색을 시작하는 것이 좋습니다. 특정 장치에서 작동하는 드라이버를 찾아야하는 경우 [호환 가능한](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837.dtsi#L12) 속성을 사용해야합니다. 이 속성의 값을 검색하면 RPi 로컬 인터럽트 컨트롤러와 호환되는 단일 드라이버가 있음을 쉽게 찾을 수 있습니다. 여기에 해당 [정의](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2836.c#L315)가 있습니다. ``` IRQCHIP_DECLARE(bcm2836_arm_irqchip_l1_intc, "brcm,bcm2836-l1-intc", bcm2836_arm_irqchip_l1_intc_of_init); ``` 이제 드라이버 초기화 절차가 무엇인지 짐작할 수 있습니다. 커널은 장치 트리의 모든 장치 정의와 "compatible"속성을 사용하여 일치하는 드라이버를 찾는 각 정의를 안내합니다. 드라이버가 발견되면 해당 초기화 함수가 호출됩니다. 초기화 함수는 장치 등록 중에 제공되며이 경우 이 함수는 [bcm2836_arm_irqchip_l1_intc_of_init](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2836.c#L280)입니다. ``` static int __init bcm2836_arm_irqchip_l1_intc_of_init(struct device_node *node, struct device_node *parent) { intc.base = of_iomap(node, 0); if (!intc.base) { panic("%pOF: unable to map local interrupt registers\n", node); } bcm2835_init_local_timer_frequency(); intc.domain = irq_domain_add_linear(node, LAST_IRQ + 1, &bcm2836_arm_irqchip_intc_ops, NULL); if (!intc.domain) panic("%pOF: unable to create IRQ domain\n", node); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_CNTPSIRQ, &bcm2836_arm_irqchip_timer); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_CNTPNSIRQ, &bcm2836_arm_irqchip_timer); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_CNTHPIRQ, &bcm2836_arm_irqchip_timer); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_CNTVIRQ, &bcm2836_arm_irqchip_timer); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_GPU_FAST, &bcm2836_arm_irqchip_gpu); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_PMU_FAST, &bcm2836_arm_irqchip_pmu); bcm2836_arm_irqchip_smp_init(); set_handle_irq(bcm2836_arm_irqchip_handle_irq); return 0; } ``` 초기화 함수는 `node`및 `parent`의 두 매개 변수를 사용하며 둘 다 [struct device_node](https://github.com/torvalds/linux/blob/v4.14/include/linux/of.h#L49) 유형입니다. `node`는 장치 트리의 현재 노드를 나타내며 여기서는 [여기] (https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837.dtsi#L11)를 가리 킵니다. `parent`는 장치 트리 계층 구조의 부모 노드이며 로컬 인터럽트 컨트롤러의 경우`soc` 요소를 가리킵니다 (`soc`는 "system on chip"을 나타내며 매핑 가능한 가장 간단한 버스입니다) 모든 장치는 주 메모리에 직접 등록됩니다.). `node`는 현재 장치 트리 노드에서 다양한 속성을 읽는 데 사용될 수 있습니다. 예를 들어,`bcm2836_arm_irqchip_l1_intc_of_init` 함수의 첫 번째 줄은 [reg](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837dtsi # L13) 속성에서 장치 기본 주소를 읽습니다. 그러나 이 기능이 실행될 때 MMU가 이미 활성화되어 있고 일부 물리적 메모리 영역에 액세스하려면 먼저 이 영역을 가상 주소에 매핑해야하므로 프로세스가 그보다 더 복잡합니다. 이것은 정확히 [of_iomap](https://github.com/torvalds/linux/blob/v4.14/drivers/of/address.c#L759)의 기능입니다 : 제공된 노드의 `reg` 속성을 읽습니다. `reg` 속성으로 설명된 전체 메모리 영역을 일부 가상 메모리 영역에 매핑합니다. 다음 로컬 타이머 주파수는 [bcm2835_init_local_timer_frequency](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2836.c#L264) 함수에서 초기화됩니다. 이 기능에 대한 구체적인 내용은 없습니다. [BCM2836 ARM-local 주변 장치](https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2836/QA7_rev3.4.pdf) 매뉴얼에 설명 된 일부 레지스터 만 사용하여 로컬 타이머를 초기화합니다. 다음 줄에는 몇 가지 설명이 필요합니다. ``` intc.domain = irq_domain_add_linear(node, LAST_IRQ + 1, &bcm2836_arm_irqchip_intc_ops, NULL); ``` Linux는 각 인터럽트에 고유한 정수를 할당하므로이 숫자를 고유한 인터럽트 ID로 생각할 수 있습니다. 이 ID는 인터럽트로 무언가를 수행하려고 할 때마다 사용됩니다(예 : 처리기 할당 또는 처리 할 CPU 할당). 각 인터럽트에는 하드웨어 인터럽트 번호도 있습니다. 일반적으로 어떤 인터럽트 라인이 트리거되었는지를 나타내는 숫자입니다. `BCM2837 ARM 주변 장치 매뉴얼`에는 113 페이지의 주변 장치 인터럽트 테이블이 있습니다. 이 테이블의 인덱스를 하드웨어 인터럽트 번호로 생각할 수 있습니다. 따라서 Linux irq 번호를 하드웨어 irq 번호에 매핑하거나 그 반대로 매핑하는 메커니즘이 필요합니다. 인터럽트 컨트롤러가 하나만 있으면 일대일 매핑을 사용할 수 있지만 일반적으로 더 정교한 메커니즘을 사용해야합니다. Linux에서 [struct irq_domain](https://github.com/torvalds/linux/blob/v4.14/include/linux/irqdomain.h#L152)은 이러한 매핑을 구현합니다. 각 인터럽트 컨트롤러 드라이버는 자체 irq 도메인을 작성하고이 도메인에서 처리 할 수있는 모든 인터럽트를 등록해야합니다. 등록 함수는 나중에 인터럽트 작업에 사용되는 Linux irq 번호를 반환합니다. 다음 6 줄은 irq 도메인에 지원되는 각 인터럽트를 등록합니다. ``` bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_CNTPSIRQ, &bcm2836_arm_irqchip_timer); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_CNTPNSIRQ, &bcm2836_arm_irqchip_timer); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_CNTHPIRQ, &bcm2836_arm_irqchip_timer); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_CNTVIRQ, &bcm2836_arm_irqchip_timer); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_GPU_FAST, &bcm2836_arm_irqchip_gpu); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_PMU_FAST, &bcm2836_arm_irqchip_pmu); ``` [BCM2836 ARM-local 주변 장치](https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2836/QA7_rev3.4.pdf) 메뉴얼에 따르면 로컬 인터럽트 컨트롤러는 10개의 서로 다른 인터럽트를 처리합니다. 0-3은 로컬 타이머의 인터럽트, 4-7은 프로세스 간 통신에 사용되는 mailbox 인터럽트, 8은 전역에 의해 생성 된 모든 인터럽트에 해당 인터럽트 컨트롤러 및 인터럽트9는 성능 모니터 인터럽트입니다. [여기](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2836.c#L67) 드라이버가 각 인터럽트 당 하드웨어 irq 번호를 보유하는 상수 세트를 정의하고 있음을 알 수 있습니다. 위의 등록 코드는 mailbox 인터럽트를 제외하고 별도로 등록된 모든 인터럽트를 등록합니다. 등록 코드를 더 잘 이해하기 위해 [bcm2836_arm_irqchip_register_irq](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2836.c#L154) 함수을 검사 할 수 있습니다. ``` static void bcm2836_arm_irqchip_register_irq(int hwirq, struct irq_chip *chip) { int irq = irq_create_mapping(intc.domain, hwirq); irq_set_percpu_devid(irq); irq_set_chip_and_handler(irq, chip, handle_percpu_devid_irq); irq_set_status_flags(irq, IRQ_NOAUTOEN); } ``` 첫 번째 줄은 실제 인터럽트 등록을 수행합니다. [irq_create_mapping](https://github.com/torvalds/linux/blob/v4.14/kernel/irq/irqdomain.c#L632)은 하드웨어 인터럽트 번호를 입력으로 받아 Linux irq 번호를 반환합니다. [irq_set_percpu_devid](https://github.com/torvalds/linux/blob/v4.14/kernel/irq/irqdesc.c#L849)는 인터럽트를 "CPU 당"으로 구성하여 현재 CPU에서만 처리되도록합니다. 이것은 지금 논의하고 있는 모든 인터럽트가 로컬이고 모두 현재 CPU에서만 처리할 수 있기 때문에 쉽습니다. [irq_set_chip_and_handler](https://github.com/torvalds/linux/blob/v4.14/include/linux/irq.h#L608)는 이름에서 알 수 있듯이 irq 칩과 irq 핸들러를 설정합니다. Irq 칩은 드라이버에 의해 생성되어야하는 특정 구조체로, 특정 인터럽트를 마스킹 및 마스킹 해제하는 방법이 있습니다. 현재 검토중인 드라이버는 외부 타이머 장치에서 생성 된 모든 인터럽트를 제어하는 [timer](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2836.c#L118) 칩, [PMU](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2836.c#L134) 칩 및 [GPU](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2836.c#L148) 칩의 세 가지 irq 칩을 정의합니다. 핸들러는 인터럽트 처리를 담당하는 함수입니다. 이 경우 핸들러는 일반 [handle_percpu_devid_irq](https://github.com/torvalds/linux/blob/v4.14/kernel/irq/chip.c#L859) 함수로 설정됩니다. 이 핸들러는 나중에 전역 인터럽트 컨트롤러 드라이버에 의해 다시 작성됩니다. 이 경우 [irq_set_status_flags](https://github.com/torvalds/linux/blob/v4.14/include/linux/irq.h#L652)는 현재 인터럽트를 수동으로 활성화해야하며 기본적으로 활성화해서는 안됨을 나타내는 플래그를 설정합니다. `bcm2836_arm_irqchip_l1_intc_of_init` 함수로 돌아가면 2 개의 호출만 남습니다. 첫 번째는 [bcm2836_arm_irqchip_smp_init](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2836.c#L243)입니다. 여기에서 mailbox 인터럽트가 활성화되어 프로세서 코어가 서로 통신 할 수 있습니다. 마지막 함수 호출은 매우 중요합니다. 이것은 저수준 예외 처리 코드가 드라이버에 연결되는 곳입니다. ``` set_handle_irq(bcm2836_arm_irqchip_handle_irq); ``` [set_handle_irq](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/irq.c#L46)는 아키텍처 별 코드로 정의되어 있으며 이미이 기능을 만났습니다. 위의 줄에서 [bcm2836_arm_irqchip_handle_irq](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2836.c#L164)가 하위 수준 예외 코드에 의해 호출됨을 이해할 수 있습니다. 함수 자체는 아래와 같습니다. ``` static void __exception_irq_entry bcm2836_arm_irqchip_handle_irq(struct pt_regs *regs) { int cpu = smp_processor_id(); u32 stat; stat = readl_relaxed(intc.base + LOCAL_IRQ_PENDING0 + 4 * cpu); if (stat & BIT(LOCAL_IRQ_MAILBOX0)) { #ifdef CONFIG_SMP void __iomem *mailbox0 = (intc.base + LOCAL_MAILBOX0_CLR0 + 16 * cpu); u32 mbox_val = readl(mailbox0); u32 ipi = ffs(mbox_val) - 1; writel(1 << ipi, mailbox0); handle_IPI(ipi, regs); #endif } else if (stat) { u32 hwirq = ffs(stat) - 1; handle_domain_irq(intc.domain, hwirq, regs); } } ``` 이 함수는 `LOCAL_IRQ_PENDING`레지스터를 읽어 현재 보류중인 인터럽트를 파악합니다. 각각 자체 프로세서 코어에 해당하는 4 개의 `LOCAL_IRQ_PENDING`레지스터가 있으므로 현재 프로세서 인덱스를 사용하여 올바른 것을 선택합니다. 메일 함 인터럽트 및 기타 모든 인터럽트는 if 문의 두 가지 다른 절로 처리됩니다. 멀티 프로세서 시스템의 서로 다른 코어 간의 상호 작용은 현재 논의 범위를 벗어나므로 mailbox 인터럽트 처리 부분을 건너 뛸 것입니다. 이제 다음 두 줄만 설명 할 수 없습니다. ``` u32 hwirq = ffs(stat) - 1; handle_domain_irq(intc.domain, hwirq, regs); ``` 인터럽트가 다음 핸들러로 전달되었습니다. 우선 모든 하드웨어 irq 번호가 계산됩니다. 이를 위해 [ffs](https://github.com/torvalds/linux/blob/v4.14/include/asm-generic/bitops/ffs.h#L13) (첫 번째 비트 찾기) 함수가 사용됩니다. 하드웨어 irq 번호가 계산 된 후 [handle_domain_irq](https://github.com/torvalds/linux/blob/v4.14/kernel/irq/irqdesc.c#L622) 기능이 호출됩니다. 이 함수는 irq 도메인을 사용하여 하드웨어 irq 번호를 Linux irq 번호로 변환 한 다음 irq 구성 ([irq_desc](https://github.com/torvalds/linux/blob/v4.14/include/linux/irqdesc.h#L55) 구조체에 저장 됨)을 확인하고 인터럽트 핸들러를 호출합니다. 핸들러가 [handle_percpu_devid_irq](https://github.com/torvalds/linux/blob/v4.14/kernel/irq/chip.c#L859)로 설정되어 있음을 확인했습니다. 그러나 이 핸들러는 나중에 하위 인터럽트 컨트롤러에서 덮어 씁니다. 이제 어떻게 이런 일이 일어나는지 살펴봅시다. ### 제네릭 인터럽트 컨트롤러 디바이스 트리와 `compatible`속성을 사용하여 일부 디바이스에 해당하는 드라이버를 찾는 방법을 이미 살펴 봤으므로 이 부분을 건너 뛰고 일반적인 인터럽트 컨트롤러 드라이버 소스 코드로 바로 넘어가겠습니다. [irq-bcm2835.c](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2835.c) 파일에서 찾을 수 있습니다. 평소와 같이 초기화 기능으로 탐색을 시작합니다. 이를 [armctrl_of_init](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2835.c#L141)라고 합니다. ``` static int __init armctrl_of_init(struct device_node *node, struct device_node *parent, bool is_2836) { void __iomem *base; int irq, b, i; base = of_iomap(node, 0); if (!base) panic("%pOF: unable to map IC registers\n", node); intc.domain = irq_domain_add_linear(node, MAKE_HWIRQ(NR_BANKS, 0), &armctrl_ops, NULL); if (!intc.domain) panic("%pOF: unable to create IRQ domain\n", node); for (b = 0; b < NR_BANKS; b++) { intc.pending[b] = base + reg_pending[b]; intc.enable[b] = base + reg_enable[b]; intc.disable[b] = base + reg_disable[b]; for (i = 0; i < bank_irqs[b]; i++) { irq = irq_create_mapping(intc.domain, MAKE_HWIRQ(b, i)); BUG_ON(irq <= 0); irq_set_chip_and_handler(irq, &armctrl_chip, handle_level_irq); irq_set_probe(irq); } } if (is_2836) { int parent_irq = irq_of_parse_and_map(node, 0); if (!parent_irq) { panic("%pOF: unable to get parent interrupt.\n", node); } irq_set_chained_handler(parent_irq, bcm2836_chained_handle_irq); } else { set_handle_irq(bcm2835_handle_irq); } return 0; } ``` 이제, 이 함수를 살펴보겠습니다. ``` void __iomem *base; int irq, b, i; base = of_iomap(node, 0); if (!base) panic("%pOF: unable to map IC registers\n", node); intc.domain = irq_domain_add_linear(node, MAKE_HWIRQ(NR_BANKS, 0), &armctrl_ops, NULL); if (!intc.domain) panic("%pOF: unable to create IRQ domain\n", node); ``` 이 함수는 장치3에서 장치 기본 주소를 읽고 irq 도메인을 초기화하는 코드로 시작합니다. 로컬 irq 컨트롤러 드라이버에서 유사한 코드를 보았으므로이 부분은 이미 익숙 할 것입니다. ``` for (b = 0; b < NR_BANKS; b++) { intc.pending[b] = base + reg_pending[b]; intc.enable[b] = base + reg_enable[b]; intc.disable[b] = base + reg_disable[b]; ``` 다음으로, 모든 irq 뱅크를 반복하는 루프가 있습니다. 우리는 이 강의의 첫 번째 장에서 이미 irq 뱅크에 대해 간단히 언급했습니다. 인터럽트 컨트롤러에는 3 개의 irq 뱅크가 있으며, 이는 `ENABLE_IRQS_1`, `ENABLE_IRQS_2` 및 `ENABLE_BASIC_IRQS` 레지스터에 의해 제어됩니다. 각 뱅크에는 자체 활성화, 비활성화 및 보류 레지스터가 있습니다. 활성화 및 비활성화 레지스터를 사용하여 특정 뱅크에 속하는 개별 인터럽트를 활성화 또는 비활성화 할 수 있습니다. 보류 레지스터는 처리 대기중인 인터럽트를 판별하는 데 사용됩니다. ``` for (i = 0; i < bank_irqs[b]; i++) { irq = irq_create_mapping(intc.domain, MAKE_HWIRQ(b, i)); BUG_ON(irq <= 0); irq_set_chip_and_handler(irq, &armctrl_chip, handle_level_irq); irq_set_probe(irq); } ``` 다음으로, 지원되는 각 인터럽트를 등록하고 irq 칩과 핸들러를 설정하는 중첩 루프가 있습니다. 이미 로컬 인터럽트 컨트롤러 드라이버에서 동일한 함수가 어떻게 사용되는지 보았습니다. 그러나 몇 가지 중요한 사항을 강조하고 싶습니다. * [MAKE_HWIRQ](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2835.c#L57) 매크로는 하드웨어 irq 번호를 계산하는 데 사용됩니다. 뱅크 내 뱅크 지수 및 irq 지수를 기준으로 계산됩니다. * [handle_level_irq](https://github.com/torvalds/linux/blob/v4.14/kernel/irq/chip.c#L603)는 레벨 유형의 인터럽트에 사용되는 공통 핸들러입니다. 이러한 유형의 인터럽트는 인터럽트가 승인 될 때까지 인터럽트 라인을 "HIGH"로 설정합니다. 다른 방식으로 작동하는 에지 유형 인터럽트도 있습니다. * [irq_set_probe](https://github.com/torvalds/linux/blob/v4.14/include/linux/irq.h#L667) 함수는 [IRQ_NOPROBE](https://github.com/torvalds/linux/blob/v4.14/include/linux/irq.h#L64) 인터럽트 플래그를 설정 해제하여 인터럽트 자동 프로빙을 효과적으로 비활성화합니다. 인터럽트 자동 프로빙은 다른 드라이버가 장치에 연결된 인터럽트 라인을 발견 할 수 있도록하는 프로세스입니다. 이 정보는 장치 트리에서 인코딩되기 때문에 Raspberry Pi에는 필요하지 않지만 일부 장치의 경우 유용 할 수 있습니다. Linux 커널에서 자동 탐색이 작동하는 방식을 이해하려면 [여기](https://github.com/torvalds/linux/blob/v4.14/include/linux/interrupt.h#L662) 주석을 참조하십시오. 다음 코드는 BCM2836 및 BCM2835 인터럽트 컨트롤러와 다릅니다 (첫 번째 코드는 RPi 모델 2 및 3에 해당하고 두 번째 코드는 RPi 모델 1에 해당). BCM2836을 다루는 경우 다음 코드가 실행됩니다. ``` int parent_irq = irq_of_parse_and_map(node, 0); if (!parent_irq) { panic("%pOF: unable to get parent interrupt.\n", node); } irq_set_chained_handler(parent_irq, bcm2836_chained_handle_irq); ``` 장치 트리는 로컬 인터럽트 컨트롤러가 글로벌 인터럽트 컨트롤러의 부모임을 [나타냅니다](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837.dtsi#L75). 또 다른 장치 트리 [속성](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837.dtsi#L76)은 전역 인터럽트 컨트롤러가 로컬 컨트롤러의 interupt line number 8에 연결되어 있음을 나타냅니다. 이는 부모 irq가 하드웨어 irq 번호 8을 가진 것을 의미합니다.이 두 속성은 Linux 커널이 부모를 찾을 수 있도록합니다. 인터럽트 번호 (하드웨어 번호가 아닌 Linux 인터럽트 번호) 마지막으로 [irq_set_chained_handler](https://github.com/torvalds/linux/blob/v4.14/include/linux/irq.h#L636) 함수는 부모 irq의 핸들러를 [bcm2836_chained_handle_irq](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2835.c#L246) 함수로 바꿉니다. [bcm2836_chained_handle_irq](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2835.c#L246) 매우 짧고, 아래에 코드가 있습니다. ``` static void bcm2836_chained_handle_irq(struct irq_desc *desc) { u32 hwirq; while ((hwirq = get_next_armctrl_hwirq()) != ~0) generic_handle_irq(irq_linear_revmap(intc.domain, hwirq)); } ``` 이 코드는 우리가 RPi OS에서 [여기에](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/irq.c#L39) 수행 한 작업의 고급 버전으로 생각할 수 있습니다. [get_next_armctrl_hwirq](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2835.c#L217)는 대기중인 3 개의 레지스터를 모두 사용하여 어떤 인터럽트가 발생했는지 파악합니다. [irq_linear_revmap](https://github.com/torvalds/linux/blob/v4.14/include/linux/irqdomain.h#L377)은 irq 도메인을 사용하여 하드웨어 irq 번호를 Linux irq 번호로 변환하고 [generic_handle_irq](https://github.com/torvalds/linux/blob/v4.14/include/linux/irqdesc.h#L156)는 irq 핸들러를 실행합니다. Irq 핸들러는 초기화 함수에서 설정되었으며 [handle_level_irq](https://github.com/torvalds/linux/blob/v4.14/kernel/irq/chip.c#L603) 를 가리키며 결국 인터럽트와 관련된 모든 irq 조치를 실행합니다 (실제로 [여기서](https://github.com/torvalds/linux/blob/v4.14/kernel/irq/handle.c#L135) 수행됨). 현재, irq 조치 목록은 지원되는 모든 인터럽트에 대해 비어 있습니다. 일부 인터럽트를 처리하려는 드라이버는 해당 목록에 조치를 추가해야합니다. 다음 장에서는 시스템 타이머를 예로 사용하여이 작업을 수행하는 방법을 살펴 보겠습니다. ##### 이전 페이지 3.2 [Interrupt handling: Low-level exception handling in Linux](./low_level-exception_handling.md) ##### 다음 페이지 3.4 [Interrupt handling: Timers](./timer.md) ##### 추가 사항 이 문서는 훌륭한 오픈소스 운영체제 학습 프로젝트인 Sergey Matyukevich의 문서를 영어에 능숙하지 않은 한국인들이 학습할 수 있도록 번역한 것입니다. 오타 및 오역이 있을 수 있습니다. Sergey Matyukevich는 한국어를 능숙하게 다루지 못합니다. 대신 저에게 elxm6123@gmail.com으로 연락해주세요. ================================================ FILE: translations/ko/lesson03/linux/low_level-exception_handling.md ================================================ ## 3.2: 리눅스에서 저수준 익셉션 핸들링 거대한 Linux 커널 소스 코드가 주어지면 인터럽트 처리를 담당하는 코드를 찾는 좋은 방법은 무엇일까요? 하나의 아이디어를 제안 할 수 있습니다. 벡터 테이블 기본 주소는 `vbar_el1`레지스터에 저장해야하므로 `vbar_el1`을 검색하면 정확히 벡터 테이블이 초기화되는 위치를 파악할 수 있습니다. 실제로, 검색은 몇 가지 사용법을 제공하며 그중 하나인 [head.S](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/head.S)는 이미 우리에게 친숙한 것입니다. 이 코드는 [__primary_switched](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/head.S#L323) 함수에 있습니다. 이 기능은 MMU가 켜진 후에 실행됩니다. 코드는 다음과 같습니다. ``` adr_l x8, vectors // load VBAR_EL1 with virtual msr vbar_el1, x8 // vector table address ``` 이 코드를 통해 벡터 테이블을 `벡터`라고 할 수 있으며 [그 정의](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L367)를 쉽게 찾을 수 있어야합니다. ``` /* * Exception vectors. */ .pushsection ".entry.text", "ax" .align 11 ENTRY(vectors) kernel_ventry el1_sync_invalid // Synchronous EL1t kernel_ventry el1_irq_invalid // IRQ EL1t kernel_ventry el1_fiq_invalid // FIQ EL1t kernel_ventry el1_error_invalid // Error EL1t kernel_ventry el1_sync // Synchronous EL1h kernel_ventry el1_irq // IRQ EL1h kernel_ventry el1_fiq_invalid // FIQ EL1h kernel_ventry el1_error_invalid // Error EL1h kernel_ventry el0_sync // Synchronous 64-bit EL0 kernel_ventry el0_irq // IRQ 64-bit EL0 kernel_ventry el0_fiq_invalid // FIQ 64-bit EL0 kernel_ventry el0_error_invalid // Error 64-bit EL0 #ifdef CONFIG_COMPAT kernel_ventry el0_sync_compat // Synchronous 32-bit EL0 kernel_ventry el0_irq_compat // IRQ 32-bit EL0 kernel_ventry el0_fiq_invalid_compat // FIQ 32-bit EL0 kernel_ventry el0_error_invalid_compat // Error 32-bit EL0 #else kernel_ventry el0_sync_invalid // Synchronous 32-bit EL0 kernel_ventry el0_irq_invalid // IRQ 32-bit EL0 kernel_ventry el0_fiq_invalid // FIQ 32-bit EL0 kernel_ventry el0_error_invalid // Error 32-bit EL0 #endif END(vectors) ``` 익숙하지 않습니까? 실제로이 코드의 대부분을 복사하여 조금 단순화했습니다. [kernel_ventry] (https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L72) 매크로는 거의 RPi OS 안에서 정의된 [ventry](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/entry.S#L12)와 같습니다. 그러나 한 가지 차이점은 `kernel_ventry`도 커널 스택 오버플로가 발생했는지 확인하는 역할을 한다는 것입니다. 이 기능은`CONFIG_VMAP_STACK`이 설정되어 있고 `가상적으로 매핑 된 커널 스택`이라는 커널 기능의 일부인 경우에 활성화됩니다. ### 커널 앤트리 [kernel_entry](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L120) 매크로는 매우 익숙할 것입니다. [corresonding macro] (https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/entry.S#L17)와 RPI OS안에서 동일한 방식으로 사용됩니다. 그러나 원본 (Linux) 버전은 훨씬 더 복잡합니다. 코드는 아래와 같습니다. ``` .macro kernel_entry, el, regsize = 64 .if \regsize == 32 mov w0, w0 // zero upper 32 bits of x0 .endif stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] .if \el == 0 mrs x21, sp_el0 ldr_this_cpu tsk, __entry_task, x20 // Ensure MDSCR_EL1.SS is clear, ldr x19, [tsk, #TSK_TI_FLAGS] // since we can unmask debug disable_step_tsk x19, x20 // exceptions when scheduling. mov x29, xzr // fp pointed to user-space .else add x21, sp, #S_FRAME_SIZE get_thread_info tsk /* Save the task's original addr_limit and set USER_DS (TASK_SIZE_64) */ ldr x20, [tsk, #TSK_TI_ADDR_LIMIT] str x20, [sp, #S_ORIG_ADDR_LIMIT] mov x20, #TASK_SIZE_64 str x20, [tsk, #TSK_TI_ADDR_LIMIT] /* No need to reset PSTATE.UAO, hardware's already set it to 0 for us */ .endif /* \el == 0 */ mrs x22, elr_el1 mrs x23, spsr_el1 stp lr, x21, [sp, #S_LR] /* * In order to be able to dump the contents of struct pt_regs at the * time the exception was taken (in case we attempt to walk the call * stack later), chain it together with the stack frames. */ .if \el == 0 stp xzr, xzr, [sp, #S_STACKFRAME] .else stp x29, x22, [sp, #S_STACKFRAME] .endif add x29, sp, #S_STACKFRAME #ifdef CONFIG_ARM64_SW_TTBR0_PAN /* * Set the TTBR0 PAN bit in SPSR. When the exception is taken from * EL0, there is no need to check the state of TTBR0_EL1 since * accesses are always enabled. * Note that the meaning of this bit differs from the ARMv8.1 PAN * feature as all TTBR0_EL1 accesses are disabled, not just those to * user mappings. */ alternative_if ARM64_HAS_PAN b 1f // skip TTBR0 PAN alternative_else_nop_endif .if \el != 0 mrs x21, ttbr0_el1 tst x21, #0xffff << 48 // Check for the reserved ASID orr x23, x23, #PSR_PAN_BIT // Set the emulated PAN in the saved SPSR b.eq 1f // TTBR0 access already disabled and x23, x23, #~PSR_PAN_BIT // Clear the emulated PAN in the saved SPSR .endif __uaccess_ttbr0_disable x21 1: #endif stp x22, x23, [sp, #S_PC] /* Not in a syscall by default (el0_svc overwrites for real syscall) */ .if \el == 0 mov w21, #NO_SYSCALL str w21, [sp, #S_SYSCALLNO] .endif /* * Set sp_el0 to current thread_info. */ .if \el == 0 msr sp_el0, tsk .endif /* * Registers that may be useful after this macro is invoked: * * x21 - aborted SP * x22 - aborted PC * x23 - aborted PSTATE */ .endm ``` 이제 우리는`kernel_entry` 매크로를 자세히 살펴볼 것입니다. ``` .macro kernel_entry, el, regsize = 64 ``` 이 매크로는`el`과`regsize`의 두 매개 변수를 허용합니다. `el`은 EL0 또는 EL1에서 예외가 발생했는지에 따라`0` 또는 `1`일 수 있습니다. `regsize`는 32 비트 EL0에서 온 경우 32이고, 그렇지 않으면 64입니다. ``` .if \regsize == 32 mov w0, w0 // zero upper 32 bits of x0 .endif ``` 32 비트 모드에서는 32 비트 범용 레지스터 (`x0` 대신`w0`)를 사용합니다. `w0`는`x0`의 아랫 부분에 구조적으로 매핑되어 있습니다. 제공된 코드 스니펫은 `w0`을 자체적으로 작성하여 `x0`레지스터의 상위 32 비트를 0으로 만듭니다. ``` stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] ``` 이 부분은 모든 범용 레지스터를 스택에 저장합니다. 스택 포인터는 [kernel_ventry](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L74)에서 저장되는 것이 필요로한 모든 것에 맞게 이미 조정되어 있어야합니다. 레지스터를 저장하는 순서는 나중에 예외 처리기 내부에 저장된 레지스터에 액세스하는 데 사용되는 특수 구조 [pt_reg](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/ptrace.h#L119)가 있기 때문에 Linux에서 중요합니다. 보시다시피 이 구조에는 범용 레지스터뿐만 아니라 나중에 `kernel_entry`매크로에서 대부분 채워지는 다른 정보도 포함됩니다. 다음 레슨에서 비슷한 것을 구현하고 사용할 것이기 때문에`pt_regs` 구조체를 기억하는 것이 좋습니다. ``` .if \el == 0 mrs x21, sp_el0 ``` `x21`은 이제 중단 된 스택 포인터를 포함합니다. Linux의 작업은 사용자 및 커널 모드에 대해 서로 다른 2 개의 스택을 사용합니다. 사용자 모드의 경우 `sp_el0`레지스터를 사용하여 예외가 생성 된 순간 스택 포인터 값을 알아낼 수 있습니다. 컨텍스트 전환 중에 스택 포인터를 교체해야하기 때문에이 라인은 매우 중요합니다. 다음 강의에서 자세히 설명하겠습니다. ``` ldr_this_cpu tsk, __entry_task, x20 // Ensure MDSCR_EL1.SS is clear, ldr x19, [tsk, #TSK_TI_FLAGS] // since we can unmask debug disable_step_tsk x19, x20 // exceptions when scheduling. ``` `MDSCR_EL1.SS` 비트는 "소프트웨어 단계 예외"를 가능하게합니다. 이 비트가 설정되고 디버그 예외가 마스크 해제되면 명령이 실행 된 후 예외가 생성됩니다. 이것은 일반적으로 디버거에서 사용됩니다. 사용자 모드에서 예외를 처리 할 때는 먼저 [TIF_SINGLESTEP] (https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/thread_info.h#L93) 플래그를 확인해야합니다. 현재 작업에 설정되어 있습니다. 그렇다면, 이는 작업이 디버거에서 실행 중임을 나타내며`MDSCR_EL1.SS` 비트를 설정 해제해야합니다. 이 코드에서 이해해야 할 중요한 것은 현재 작업에 대한 정보를 얻는 방법입니다. Linux에서 각 프로세스 또는 스레드 (나중에 "작업"으로 참조)에는 [task_struct](https://github.com/torvalds/linux/blob/v4.14/include/linux/sched.h#L519)가 있습니다. )와 관련이 있습니다. 이 구조체에는 작업에 대한 모든 메타 데이터 정보가 포함됩니다. `arm64` 아키텍처에서`task_struct`는 [thread_info](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/thread_info.h#L39)라는 다른 구조를 포함합니다. 따라서 `task_struct`에 대한 포인터는 항상 `thread_info`에 대한 포인터로 사용될 수 있습니다. `thread_info`는`entry.S`에 직접 액세스해야하는 다른 저수준 값과 함께 플래그가 저장된 장소입니다. ``` mov x29, xzr // fp pointed to user-space ``` `x29`는 범용 레지스터이지만 일반적으로 특별한 의미가 있습니다. "프레임 포인터"로 사용됩니다. 이제 그 사용 목적을 설명하겠습니다.. 함수가 컴파일 될 때, 처음 몇 개의 명령어는 일반적으로 오래된 프레임 포인터와 링크 레지스터 값을 스택에 저장합니다. (단순히 알림 :`x30`은 링크 레지스터라고하며`ret` 명령어가 사용하는 "반환 주소"를 보유합니다.) 그런 다음 새 스택 프레임이 할당되어 함수의 모든 로컬 변수를 포함 할 수 있습니다. 프레임 포인터 레지스터가 프레임의 맨 아래를 가리키도록 설정되어 있습니다. 함수가 로컬 변수에 액세스해야 할 때마다 단순히 하드 코드 된 오프셋을 프레임 포인터에 추가합니다. 오류가 발생하여 스택 추적을 생성해야한다고 상상해보십시오. 현재 프레임 포인터를 사용하여 스택의 모든 로컬 변수를 찾을 수 있으며 링크 레지스터를 사용하여 호출자의 정확한 위치를 파악할 수 있습니다. 다음으로, 우리는 오래된 프레임 포인터와 링크 레지스터 값이 항상 스택 프레임의 시작 부분에 저장된다는 사실을 이용합니다. 호출자의 프레임 포인터를 얻은 후에는 모든 로컬 변수에도 액세스 할 수 있습니다. 이 프로세스는 스택 맨 위에 도달 할 때까지 재귀 적으로 반복되며 "스택 풀기"라고합니다. [ptrace](http://man7.org/linux/man-pages/man2/ptrace.2.html) 시스템 호출에서도 비슷한 알고리즘이 사용됩니다. 이제`kernel_entry` 매크로로 돌아가서, EL0에서 예외를 취한 후 왜 `x29` 레지스터를 지워야하는지 분명해야합니다. Linux에서 각 작업은 사용자 및 커널 모드에 대해 서로 다른 스택을 사용하기 때문에 일반적인 스택 추적을 갖는 것은 의미가 없습니다. ``` .else add x21, sp, #S_FRAME_SIZE ``` 이제 else 절 안에 있습니다. 이는이 코드가 EL1에서 가져온 예외를 처리하는 경우에만 관련이 있음을 의미합니다. 이 경우 우리는 오래된 스택을 재사용하고 제공된 코드 스 니펫은 나중에 사용하기 위해 원래의 `sp`값을 `x21`레지스터에 저장합니다. ``` /* Save the task's original addr_limit and set USER_DS (TASK_SIZE_64) */ ldr x20, [tsk, #TSK_TI_ADDR_LIMIT] str x20, [sp, #S_ORIG_ADDR_LIMIT] mov x20, #TASK_SIZE_64 str x20, [tsk, #TSK_TI_ADDR_LIMIT] ``` 작업 주소 제한은 사용할 수있는 가장 큰 가상 주소를 지정합니다. 사용자 프로세스가 32 비트 모드에서 작동 할 때이 제한은`2 ^ 32`입니다. 64 비트 커널의 경우 더 클 수 있으며 일반적으로`2 ^ 48`입니다. 32 비트 EL1에서 예외가 발생하면 작업 주소 제한을 [TASK_SIZE_64](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/memory.h#L80)로 변경해야합니다. 또한 실행이 사용자 모드로 돌아 가기 전에 복원해야하므로 원래 주소 제한을 저장해야합니다. ``` mrs x22, elr_el1 mrs x23, spsr_el1 ``` 예외 처리를 시작하기 전에`elr_el1`과`spsr_el1`을 스택에 저장해야합니다. RPI OS에서는 아직 수행하지 않았습니다. 현재로서는 예외가 발생한 동일한 위치로 항상 돌아 오기 때문입니다. 그러나 예외를 처리하는 동안 컨텍스트 전환을 수행해야하는 경우 어떻게해야합니까? 다음 강의에서이 시나리오에 대해 자세히 설명하겠습니다. ``` stp lr, x21, [sp, #S_LR] ``` 링크 레지스터 및 프레임 포인터 레지스터는 스택에 저장됩니다. 우리는 이미 프레임 포인터가 EL0 또는 EL1에서 예외를 가져 왔는지에 따라 다르게 계산되었으며이 계산 결과는 이미`x21` 레지스터에 저장되어 있음을 보았습니다. ``` /* * In order to be able to dump the contents of struct pt_regs at the * time the exception was taken (in case we attempt to walk the call * stack later), chain it together with the stack frames. */ .if \el == 0 stp xzr, xzr, [sp, #S_STACKFRAME] .else stp x29, x22, [sp, #S_STACKFRAME] .endif add x29, sp, #S_STACKFRAME ``` 여기 pt_regs 구조체의 [stackframe](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/ptrace.h#L140) 속성이 채워집니다. 이 속성에는 링크 레지스터와 프레임 포인터도 포함되어 있지만 이번에는`lr` 대신`elr_el1` (현재 x22) 값이 사용됩니다. `stackframe`은 스택 해제에만 사용됩니다. ``` #ifdef CONFIG_ARM64_SW_TTBR0_PAN alternative_if ARM64_HAS_PAN b 1f // skip TTBR0 PAN alternative_else_nop_endif .if \el != 0 mrs x21, ttbr0_el1 tst x21, #0xffff << 48 // Check for the reserved ASID orr x23, x23, #PSR_PAN_BIT // Set the emulated PAN in the saved SPSR b.eq 1f // TTBR0 access already disabled and x23, x23, #~PSR_PAN_BIT // Clear the emulated PAN in the saved SPSR .endif __uaccess_ttbr0_disable x21 1: #endif ``` `CONFIG_ARM64_SW_TTBR0_PAN` 매개 변수는 커널이 사용자 공간 메모리에 직접 액세스하지 못하게합니다. 이것이 언제 유용한 지 궁금하다면 [this](https://kernsec.org/wiki/index.php/Exploit_Methods/Userspace_data_usage)를 읽어보십시오. 지금은 이러한 보안 기능이 논의 범위를 벗어 났기 때문에이 기능의 작동 방식에 대한 자세한 설명도 생략하겠습니다. ``` stp x22, x23, [sp, #S_PC] ``` `elr_el1`과`spsr_el1`은 스택에 저장됩니다. ``` /* Not in a syscall by default (el0_svc overwrites for real syscall) */ .if \el == 0 mov w21, #NO_SYSCALL str w21, [sp, #S_SYSCALLNO] .endif ``` `pt_regs` 구조체에는 현재 예외가 시스템 콜인지 아닌지 여부를 나타내는 [field] (https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/ptrace.h#L132)가 있습니다. 기본적으로 시스템 콜이 아니라고 가정합니다. syscall이 작동하는 방법에 대한 자세한 설명은 5 번 강의까지 기다리십시오. ``` /* * Set sp_el0 to current thread_info. */ .if \el == 0 msr sp_el0, tsk .endif ``` 커널 모드에서 작업이 실행되면`sp_el0`이 필요하지 않습니다. 그 값은 이전에 스택에 저장되었으므로 `kernel_exit` 매크로로 쉽게 복원 할 수 있습니다. 이 시점부터 시작하여`sp_el0`은 현재 빠른 접속을 위한 [task_struct](https://github.com/torvalds/linux/blob/v4.14/include/linux/sched.h#L519)에 대한 포인터를 유지하는 데 사용됩니다. ### el1_irq 다음으로 살펴볼 것은 EL1에서 가져온 IRQ를 처리하는 처리기입니다. [vector table](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L374)에서 핸들러가`el1_irq`라는 것을 쉽게 알 수 있습니다. [여기](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L562)에 정의되어 있습니다. 이제 코드를 살펴보고 한 줄씩 살펴 보겠습니다. ``` el1_irq: kernel_entry 1 enable_dbg #ifdef CONFIG_TRACE_IRQFLAGS bl trace_hardirqs_off #endif irq_handler #ifdef CONFIG_PREEMPT ldr w24, [tsk, #TSK_TI_PREEMPT] // get preempt count cbnz w24, 1f // preempt count != 0 ldr x0, [tsk, #TSK_TI_FLAGS] // get flags tbz x0, #TIF_NEED_RESCHED, 1f // needs rescheduling? bl el1_preempt 1: #endif #ifdef CONFIG_TRACE_IRQFLAGS bl trace_hardirqs_on #endif kernel_exit 1 ENDPROC(el1_irq) ``` 이 함수 내에서 다음이 수행됩니다. * `kernel_entry` 및`kernel_exit` 매크로는 프로세서 상태를 저장하고 복원하기 위해 호출됩니다. 첫 번째 매개 변수는 EL1에서 예외가 발생했음을 나타냅니다. * `enable_dbg` 매크로를 호출하면 디버그 인터럽트가 마스크 해제됩니다. 이 시점에서 프로세서 상태가 이미 저장되어 있고 인터럽트 처리기 중간에 디버그 예외가 발생하더라도 올바르게 처리되므로 안전합니다. 처음에 인터럽트 처리 중에 디버그 예외를 마스크 해제해야하는 이유가 궁금하다면 [this](https://github.com/torvalds/linux/commit/2a2830703a2371b47f7b50b1d35cb15dc0e2b717) 커밋 메시지를 읽으십시오. * `#ifdef CONFIG_TRACE_IRQFLAGS` 블록 내부의 코드는 인터럽트 추적을 담당합니다. 인터럽트 시작 및 종료의 두 가지 이벤트를 기록합니다. * `#ifdef CONFIG_PREEMPT` 내부의 코드는 현재 작업 플래그에 액세스하여 스케줄러를 호출해야하는지 여부를 확인합니다. 이 코드는 다음 단원에서 자세히 살펴볼 것입니다. * `irq_handler`-실제 인터럽트 처리가 수행 된 곳입니다. [irq_handler](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L351)는 매크로이며 다음과 같이 정의됩니다. ``` .macro irq_handler ldr_l x1, handle_arch_irq mov x0, sp irq_stack_entry blr x1 irq_stack_exit .endm ``` 코드에서 볼 수 있듯이`irq_handler`는 [handle_arch_irq](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/irq.c#L44) 기능을 실행합니다. 이 기능은 "irq stack"이라고하는 특수 스택으로 실행됩니다. 다른 스택으로 전환해야하는 이유는 무엇입니까? 예를 들어 RPI OS에서는이 작업을 수행하지 않았습니다. 글쎄, 필요하지는 않지만 태스크 스택을 사용하여 인터럽트가 처리되며 인터럽트 핸들러에 얼마나 많은 인터럽트가 남아 있는지 확신 할 수 없습니다. 다음으로, 우리는 [handle_arch_irq](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/irq.c#L44)를 보아야합니다. 이것은 함수가 아니고 그러나 변수입니다. [set_handle_irq](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/irq.c#L46) 함수 내에서 설정됩니다. 그러나 누가 그것을 설정 하고 이 시점에 도달 한 후 인터럽트의 페이드는 무엇입니까? 우리는이 단원의 다음 장에서 답을 알아낼 것입니다. ### Conclusion 결론적으로, 우리는 이미 저수준 인터럽트 처리 코드를 살펴보고 벡터 테이블에서`handle_arch_irq`까지의 인터럽트 경로를 추적했다고 말할 수 있습니다. 이것은 인터럽트가 아키텍처 특정 코드를 떠나고 드라이버 코드에 의해 처리되기 시작한 시점입니다. 다음 장의 목표는 드라이버 소스 코드를 통해 타이머 인터럽트의 경로를 추적하는 것입니다. ##### Previous Page 3.1 [Interrupt handling: RPi OS](../rpi-os.md) ##### Next Page 3.3 [Interrupt handling: Interrupt controllers](./interrupt_controllers.md) ##### 추가 사항 이 문서는 훌륭한 오픈소스 운영체제 학습 프로젝트인 Sergey Matyukevich의 문서를 영어에 능숙하지 않은 한국인들이 학습할 수 있도록 번역한 것입니다. 오타 및 오역이 있을 수 있습니다. Sergey Matyukevich는 한국어를 능숙하게 다루지 못합니다. 대신 저에게 elxm6123@gmail.com으로 연락해주세요. ================================================ FILE: translations/ko/lesson03/linux/timer.md ================================================ ## 3.4: 타이머 우리는 글로벌 인터럽트 컨트롤러를 검사하여 마지막 장을 마쳤습니다. [bcm2836_chained_handle_irq](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2835.c#L246) 함수까지 타이머 인터럽트의 경로를 추적 할 수있었습니다. 다음 논리적 단계는 타이머 드라이버가 이 인터럽트를 처리하는 방법을 보는 것입니다. 그러나 이를 수행하기 전에 타이머 함수와 관련된 몇 가지 중요한 개념을 숙지해야합니다. 이들 모두는 [공식 커널 문서](https://github.com/torvalds/linux/blob/v4.14/Documentation/timers/timekeeping.txt)에 설명되어 있으며 이 문서를 읽는 것이 좋습니다. 그러나 너무 바빠서 읽을 수 없다면 언급 된 개념에 대한 간단한 설명을 제공 할 수 있습니다. 1. **Clock sources** 시간을 정확히 알아야 할 때마다 클럭 소스 프레임워크를 사용합니다. 일반적으로 클럭 소스는 0에서 2 ^ (n-1)까지 카운트 한 다음 0으로 감싼 후 다시 시작되는 단조 원자 n 비트 카운터로 구현됩니다. 클럭 소스는 카운터를 나노초 값으로 변환하는 수단도 제공합니다. 1. **Clock events** 이 추상화는 누구나 타이머 인터럽트를 구독 할 수 있도록하기 위해 도입되었습니다. 클록 이벤트 프레임 워크는 다음 이벤트의 설계 시간을 입력으로 사용하고이를 기반으로 타이머 하드웨어 레지스터의 적절한 값을 계산합니다. 1. **sched_clock()** 이 함수는 시스템이 시작된 후 나노초 수를 반환합니다. 일반적으로 타이머 레지스터를 직접 읽습니다. 이 기능은 매우 자주 호출되며 성능에 최적화되어야합니다. 다음 섹션에서는 시스템 타이머를 사용하여 클럭 소스, 클럭 이벤트 및 sched_clock 기능을 구현하는 방법을 살펴 보겠습니다. ### BCM2835 System Timer. 평소와 같이 장치 트리에서 위치를 찾아 특정 장치 탐색을 시작합니다. 시스템 타이머 노드는 [여기](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm283x.dtsi#L57)에 정의되어 있습니다. 이 정의를 여러 번 참조하기 때문에 이 정의를 잠시 동안 열어 둘 수 있습니다. 다음으로, 호환되는 속성을 사용하여 해당 드라이버의 위치를 알아 내야합니다. 드라이버는 [여기](https://github.com/torvalds/linux/blob/v4.14/drivers/clocksource/bcm2835_timer.c)에서 찾을 수 있습니다. 가장 먼저 살펴볼 것은 [bcm2835_timer](https://github.com/torvalds/linux/blob/v4.14/drivers/clocksource/bcm2835_timer.c#L42) 구조입니다. ``` struct bcm2835_timer { void __iomem *control; void __iomem *compare; int match_mask; struct clock_event_device evt; struct irqaction act; }; ``` 이 구조에는 드라이버가 작동하는 데 필요한 모든 상태가 포함됩니다. `control`과`compare` 필드는 대응하는 메모리 매핑 레지스터의 주소를 보유하고,`match_mask`는 우리가 사용할 4 개의 타이머 인터럽트 중 어떤 것을 결정하는데 사용되며`evt` 필드는 클럭 이벤트 프레임 워크으로 전달되는 구조를 포함하고 `act`는 현재 드라이버를 인터럽트 컨트롤러와 연결하는 데 사용되는 irq 액션입니다. 다음으로 드라이버 초기화 기능인 [bcm2835_timer_init](https://github.com/torvalds/linux/blob/v4.14/drivers/clocksource/bcm2835_timer.c#L83)를 살펴 보겠습니다. 크기는 크지 만 처음부터 생각하는 것만 큼 어렵지는 않습니다. ``` static int __init bcm2835_timer_init(struct device_node *node) { void __iomem *base; u32 freq; int irq, ret; struct bcm2835_timer *timer; base = of_iomap(node, 0); if (!base) { pr_err("Can't remap registers\n"); return -ENXIO; } ret = of_property_read_u32(node, "clock-frequency", &freq); if (ret) { pr_err("Can't read clock-frequency\n"); goto err_iounmap; } system_clock = base + REG_COUNTER_LO; sched_clock_register(bcm2835_sched_read, 32, freq); clocksource_mmio_init(base + REG_COUNTER_LO, node->name, freq, 300, 32, clocksource_mmio_readl_up); irq = irq_of_parse_and_map(node, DEFAULT_TIMER); if (irq <= 0) { pr_err("Can't parse IRQ\n"); ret = -EINVAL; goto err_iounmap; } timer = kzalloc(sizeof(*timer), GFP_KERNEL); if (!timer) { ret = -ENOMEM; goto err_iounmap; } timer->control = base + REG_CONTROL; timer->compare = base + REG_COMPARE(DEFAULT_TIMER); timer->match_mask = BIT(DEFAULT_TIMER); timer->evt.name = node->name; timer->evt.rating = 300; timer->evt.features = CLOCK_EVT_FEAT_ONESHOT; timer->evt.set_next_event = bcm2835_time_set_next_event; timer->evt.cpumask = cpumask_of(0); timer->act.name = node->name; timer->act.flags = IRQF_TIMER | IRQF_SHARED; timer->act.dev_id = timer; timer->act.handler = bcm2835_time_interrupt; ret = setup_irq(irq, &timer->act); if (ret) { pr_err("Can't set up timer IRQ\n"); goto err_iounmap; } clockevents_config_and_register(&timer->evt, freq, 0xf, 0xffffffff); pr_info("bcm2835: system timer (irq = %d)\n", irq); return 0; err_iounmap: iounmap(base); return ret; } ``` 이제이 함수를 자세히 살펴 보겠습니다. ``` base = of_iomap(node, 0); if (!base) { pr_err("Can't remap registers\n"); return -ENXIO; } ``` 메모리 레지스터를 매핑하고 레지스터 기본 주소를 얻는 것으로 시작합니다. 이 부분에 이미 익숙해야합니다. ``` ret = of_property_read_u32(node, "clock-frequency", &freq); if (ret) { pr_err("Can't read clock-frequency\n"); goto err_iounmap; } system_clock = base + REG_COUNTER_LO; sched_clock_register(bcm2835_sched_read, 32, freq); ``` 다음으로,`sched_clock` 서브 시스템이 초기화됩니다. `sched_clock`은 실행될 때마다 타이머 카운터 레지스터에 액세스해야하며이 작업을 지원하기 위해 [bcm2835_sched_read](https://github.com/torvalds/linux/blob/v4.14/drivers/clocksource/bcm2835_timer.c#L52)가 첫 번째 인수로 전달됩니다. 두 번째 인수는 타이머 카운터의 비트 수 (이 경우 32)에 해당합니다. 비트 수는 카운터가 0으로 줄 바꿈되는 시간을 계산하는 데 사용됩니다. 마지막 인수는 타이머 주파수를 지정합니다. 타이머 카운터의 값을 나노초로 변환하는 데 사용됩니다. 타이머 주파수는 [여기](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm283x.dtsi#L65) 라인의 장치 트리에서 정의됩니다. ``` clocksource_mmio_init(base + REG_COUNTER_LO, node->name, freq, 300, 32, clocksource_mmio_readl_up); ``` 다음 줄은 클럭 소스 프레임 워크를 초기화합니다. [clocksource_mmio_init](https://github.com/torvalds/linux/blob/v4.14/drivers/clocksource/mmio.c#L52)는 메모리 매핑 된 레지스터를 기반으로 간단한 클럭 소스를 초기화합니다. 클록 소스 프레임 워크는 일부 측면에서`sched_clock`의 기능을 복제하며 동일한 3 가지 기본 파라미터에 액세스해야합니다. * 타이머 카운터 레지스터의 위치입니다. * 카운터에서 유효한 비트 수입니다. * 타이머 주파수. 또 다른 3 개의 매개 변수에는 클럭 소스 이름, 클럭 소스 장치를 평가하는 데 사용되는 정격 및 타이머 카운터 레지스터를 읽을 수있는 함수가 포함됩니다. ``` irq = irq_of_parse_and_map(node, DEFAULT_TIMER); if (irq <= 0) { pr_err("Can't parse IRQ\n"); ret = -EINVAL; goto err_iounmap; } ``` 이 코드는 세 번째 타이머 인터럽트에 해당하는 Linux irq 번호를 찾는 데 사용됩니다 (번호 3은 [DEFAULT_TIMER](https://github.com/torvalds/linux/blob/v4.14/drivers/clocksource/bcm2835_timer.c#L108) 상수). 간단한 알림 : Raspberry Pi 시스템 타이머에는 4개의 독립적인 타이머 레지스터 세트가 있으며 여기에서 세 번째 타이머 레지스터가 사용됩니다. 장치 트리로 돌아 가면 [interrupts](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm283x.dtsi#L60) 속성을 찾을 수 있습니다. 이 속성은 장치에서 지원하는 모든 인터럽트와 이러한 인터럽트가 인터럽트 컨트롤러 라인에 매핑되는 방법을 설명합니다. 각 항목이 하나의 인터럽트를 나타내는 배열입니다. 항목의 형식은 인터럽트 컨트롤러에 따라 다릅니다. 이 경우 각 항목은 2개의 숫자로 구성됩니다. 첫 번째 항목은 인터럽트 뱅크를 지정하고 두 번째 항목은 뱅크 내의 인터럽트 번호를 지정합니다. [irq_of_parse_and_map](https://github.com/torvalds/linux/blob/v4.14/drivers/of/irq.c#L41)는 `interrupts` 속성의 값을 읽은 다음, 두 번째 인수를 사용하여 관심있는 지원되는 인터럽트를 찾고 요청 된 인터럽트에 대한 Linux irq 번호를 반환합니다. ``` timer = kzalloc(sizeof(*timer), GFP_KERNEL); if (!timer) { ret = -ENOMEM; goto err_iounmap; } ``` 여기에`bcm2835_timer` 구조를위한 메모리가 할당됩니다. ``` timer->control = base + REG_CONTROL; timer->compare = base + REG_COMPARE(DEFAULT_TIMER); timer->match_mask = BIT(DEFAULT_TIMER); ``` 다음으로, 제어 및 비교 레지스터의 주소가 계산되고 `match_mask`는 `DEFAULT_TIMER` 상수로 설정됩니다. ``` timer->evt.name = node->name; timer->evt.rating = 300; timer->evt.features = CLOCK_EVT_FEAT_ONESHOT; timer->evt.set_next_event = bcm2835_time_set_next_event; timer->evt.cpumask = cpumask_of(0); ``` 이 코드 스 니펫 [clock_event_device](https://github.com/torvalds/linux/blob/v4.14/include/linux/clockchips.h#L100) 구조체가 초기화됩니다. 여기서 가장 중요한 속성은 'bcm2835_time_set_next_event] (https://github.com/torvalds/linux/blob/v4.14/drivers/clocksource/bcm2835_timer.c#L57) 함수를 가리키는`set_next_event`입니다. 여기서 가장 중요한 속성은 `set_next_event`인데 이것은 [bcm2835_time_set_next_event](https://github.com/torvalds/linux/blob/v4.14/drivers/clocksource/bcm2835_timer.c#L57) 함수를 가르킨다. 이 함수는 클럭 이벤트 프레임 워크에 의해 호출되어 다음 인터럽트를 예약합니다. `bcm2835_time_set_next_event`는 매우 간단합니다. 비교 레지스터를 업데이트하여 원하는 간격 후에 인터럽트를 예약합니다. 이것은 우리가 RPi OS를 위해 여기에서 한 것과 유사합니다. ``` timer->act.flags = IRQF_TIMER | IRQF_SHARED; timer->act.dev_id = timer; timer->act.handler = bcm2835_time_interrupt; ``` 다음으로, irq 조치가 초기화됩니다. 여기서 가장 중요한 속성은 'handler'인데, 이는 [bcm2835_time_interrupt](https://github.com/torvalds/linux/blob/v4.14/drivers/clocksource/bcm2835_timer.c#L67)를 가리킵니다. 이것은 인터럽트가 발생한 후 호출되는 함수입니다. 살펴보면 모든 이벤트가 시계 이벤트 프레임 워크에 의해 등록된 이벤트 핸들러로 경로 재 지정됨을 알 수 있습니다. 이 이벤트 핸들러를 잠시 후에 살펴 보겠습니다. ``` ret = setup_irq(irq, &timer->act); if (ret) { pr_err("Can't set up timer IRQ\n"); goto err_iounmap; } ``` irq 조치가 구성된 후 타이머 인터럽트의 irq 조치 목록에 추가됩니다. ``` clockevents_config_and_register(&timer->evt, freq, 0xf, 0xffffffff); ``` 마지막으로 시계 이벤트 프레임 워크는 [clockevents_config_and_register](https://github.com/torvalds/linux/blob/v4.14/kernel/time/clockevents.c#L504)를 호출하여 초기화됩니다. `evt` 구조와 타이머 주파수는 처음 2 개의 인수로 전달됩니다. 마지막 2 개의 인수는 "원샷" 타이머 모드에서만 사용되며 현재 논의와 관련이 없습니다. 이제 우리는`bcm2835_time_interrupt` 함수까지 타이머 인터럽트의 경로를 추적했지만 실제 작업이 완료된 곳을 찾지 못했습니다. 다음 섹션에서는 클럭 이벤트 프레임 워크에 들어갈 때 인터럽트가 어떻게 처리되는지 자세히 살펴볼 것입니다. ### 클록 이벤트 프레임 워크에서 인터럽트가 처리되는 방법 이전 섹션에서 우리는 타이머 인터럽트를 처리하는 실제 작업이 시계 이벤트 프레임 워크로 아웃소싱되는 것을 보았습니다. 이것은 [따름의](https://github.com/torvalds/linux/blob/v4.14/drivers/clocksource/bcm2835_timer.c#L74) 몇 줄로 이루어집니다. ``` event_handler = ACCESS_ONCE(timer->evt.event_handler); if (event_handler) event_handler(&timer->evt); ``` 이제 우리의 목표는 정확히 'event_handler'가 설정되었고 그것이 호출 된 후 무슨 일이 발생했는지 알아내는 것입니다. [clockevents_config_and_register](https://github.com/torvalds/linux/blob/v4.14/kernel/time/clockevents.c#L504) 함수는 시계 이벤트 프레임 워크가 구성되는 위치이기 때문에 탐색을 시작하기에 좋은 장소이며, 이 함수의 논리를 따르면 결국 `event_handler`가 어떻게 설정되는지 찾아야합니다. 이제 필요한 장소로 연결되는 함수 호출 체인을 보여 드리겠습니다. 1. [clockevents_config_and_register](https://github.com/torvalds/linux/blob/v4.14/kernel/time/clockevents.c#L504) 이것이 최상위 초기화 함수입니다. 1. [clockevents_register_device](https://github.com/torvalds/linux/blob/v4.14/kernel/time/clockevents.c#L449) 이 함수에서 타이머는 전체 시계 이벤트 장치 목록에 추가됩니다. 1. [tick_check_new_device](https://github.com/torvalds/linux/blob/v4.14/kernel/time/tick-common.c#L300) 이 함수는 현재 장치가 "틱 장치"로 사용하기에 적합한지 여부를 확인합니다. 그렇다면, 그러한 장치는 주기적으로 틱을 생성하여 나머지 커널이 정기적으로 수행해야하는 모든 작업을 수행하는 데 사용됩니다. 1. [tick_setup_device](https://github.com/torvalds/linux/blob/v4.14/kernel/time/tick-common.c#L177) 이 함수는 장치 구성을 시작합니다. 1. [tick_setup_periodic](https://github.com/torvalds/linux/blob/v4.14/kernel/time/tick-common.c#L144) 이것은 장치가 주기적 틱을 위해 구성된 장소입니다. 1. [tick_set_periodic_handler](https://github.com/torvalds/linux/blob/v4.14/kernel/time/tick-broadcast.c#L432) 마지막으로 핸들러가 할당 된 위치에 도달했습니다! 콜 체인의 마지막 기능을 살펴보면 브로드 캐스트가 활성화되어 있는지 여부에 따라 Linux가 다른 처리기를 사용한다는 것을 알 수 있습니다. 틱 브로드캐스트는 아이들 CPU를 깨우는 데 사용됩니다. 여기서 더 자세히 읽을 수는 있지만 이를 무시하고보다 일반적인 틱 핸들러에 집중할 것입니다. 일반적으로 [tick_handle_periodic](https://github.com/torvalds/linux/blob/v4.14/kernel/time/tick-common.c#L99) 및 [tick_periodic](https://github.com/torvalds/linux/blob/v4.14/kernel/time/tick-common.c#L79) 함수가 호출됩니다. 후자는 정확히 우리가 관심을 갖는 함수입니다. 여기에 내용을 복사하겠습니다. ``` /* * Periodic tick */ static void tick_periodic(int cpu) { if (tick_do_timer_cpu == cpu) { write_seqlock(&jiffies_lock); /* Keep track of the next tick event */ tick_next_period = ktime_add(tick_next_period, tick_period); do_timer(1); write_sequnlock(&jiffies_lock); update_wall_time(); } update_process_times(user_mode(get_irq_regs())); profile_tick(CPU_PROFILING); } ``` 이 기능에서 몇 가지 중요한 작업이 수행됩니다: 1. `tick_next_period`는 다음 틱 이벤트를 예약 할 수 있도록 계산됩니다. 1. [do_timer](https://github.com/torvalds/linux/blob/v4.14/kernel/time/timekeeping.c#L2200)이 호출되면 `jiffies` 설정을 담당합니다. `jiffies`는 마지막 시스템 재부팅 이후 많은 틱입니다. `jiffies`는 나노초 정밀도가 필요하지 않은 경우`sched_clock` 함수와 같은 방식으로 사용될 수 있습니다. 1. [update_process_times](https://github.com/torvalds/linux/blob/v4.14/kernel/time/timer.c#L1583)이 호출됩니다. 현재 실행중인 프로세스에 주기적으로 수행해야하는 모든 작업을 수행 할 수있는 기회가 있습니다. 이 작업에는 예를 들어 로컬 프로세스 타이머 실행 또는 가장 중요한 것은 스케줄러에 틱 이벤트에 대해 알리는 작업이 포함됩니다. ### Conclusion 이제 일반적인 타이머 인터럽트의 길이는 얼마나되는지 알지만 처음부터 끝까지 따라갔습니다. 가장 중요한 것 중 하나는 마침내 스케줄러가 호출되는 위치에 도달했다는 것입니다. 스케줄러는 모든 운영 체제에서 가장 중요한 부분 중 하나이며 타이머 인터럽트에 크게 의존합니다. 이제 스케줄러 기능이 트리거되는 위치를 보았을 때 구현에 대해 논의 할 시간입니다. 이는 다음 학습에서 수행 할 작업입니다. ##### 이전 페이지 3.3 [Interrupt handling: Interrupt controllers](./linux/interrupt_controllers.md) ##### 다음 페이지 3.5 [Interrupt handling: Exercises](../exercises.md) ##### 추가 사항 이 문서는 훌륭한 오픈소스 운영체제 학습 프로젝트인 Sergey Matyukevich의 문서를 영어에 능숙하지 않은 한국인들이 학습할 수 있도록 번역한 것입니다. 오타 및 오역이 있을 수 있습니다. Sergey Matyukevich는 한국어를 능숙하게 다루지 못합니다. 대신 저에게 elxm6123@gmail.com으로 연락해주세요. ================================================ FILE: translations/ko/lesson03/rpi-os.md ================================================ ## 3.1: 인터럽트 1번 레슨에서 우리는 이미 하드웨어와 통신하는 방법을 알고 있습니다. 그러나 대부분의 경우 의사소통의 패턴은 그렇게 간단하지 않습니다. 일반적으로 이러한 패턴은 비동기식으로, 어떤 명령을 장치에 보내지만, 즉시 응답하지 않습니다. 대신 작업이 완료되면 알려줍니다. 이러한 비동기 알림은 정상적인 실행 흐름을 방해하고 프로세서가 "인터럽트 핸들러"를 실행하도록 하기 때문에 "인터럽트"라고 불립니다. 운영체제 개발에 특히 유용한 장치가 하나 있는데, 바로 시스템 타이머입니다. 일부 사전 정의된 주파수로 프로세서를 주기적으로 인터럽트하도록 구성할 수 있는 장치입니다. 프로세스 스케줄링에 사용되는 타이머의 특정 응용 프로그램입니다. 스케줄러는 각 프로세스가 실행된 기간을 측정하고 이 정보를 사용하여 실행할 다음 프로세스를 선택해야 합니다. 이 측정은 타이머 인터럽트를 기반으로 합니다. 다음 레슨에서는 공정 스케줄에 대해 자세히 이야기하겠지만, 현재로서는 시스템 타이머를 초기화하고 타이머 인터럽트 핸들러를 구현하는 것이 과제입니다. ### 인터럽트 vs 예외(익셉션) ARM.v8 아키텍처에서, 인터럽트는 익셉션의 더 일반적인 용어입니다. 4가지 유형의 익셉션이 있습니다. * **Synchronous exception** 이 유형의 예외는 항상 현재 실행된 명령어에 의해 발생합니다. 예를 들어 일부 데이터를 존재하지 않는 메모리 위치에 저장하려면 `str` 명령을 사용할 수 있습니다. 이 경우 동기 예외를 생성합니다. 동기식 예외를 사용하여 `소프트웨어 인터럽트`를 생성할 수도 있습니다. 소프트웨어 인터럽트는 `svc`의 명령어에 의해 의도적으로 발생하는 동기식 예외입니다. 우리는 5과에서 이 기술을 시스템 호출을 구현하기 위해 사용할 것입니다. * **IRQ (Interrupt Request)** 정상적인 인터럽트들입니다. 항상 비동기적이며, 이는 현재 실행되고 있는 명령어와 아무 관련이 없다는 것을 의미합니다. 동기 예외와는 대조적으로, 프로세서가 아니라 외부 하드웨어에 의해 항상 생성됩니다. * **FIQ (Fast Interrupt Request)** 이러한 유형의 예외는 "빠른 인터럽트"라고 불리며 예외의 우선순위를 정하는 목적으로만 존재합니다. 일부 인터럽트는 "normal"으로, 다른 인터럽트는 "fast"으로 구성할 수 있습니다. 빠른 인터럽트는 먼저 신호를 받고 별도의 예외 처리자에 의해 처리될 것입니다. 리눅스는 빠른 인터럽트를 사용하지 않으며 우리도 사용하지 않을 것 입니다. * **SError (System Error)** `IRQ`와 `FIQ` 같이, `SError` 예외는 비동기적이고 외부 하드웨어에 의해 발생합니다. `IRQ`와 `FIQ` 다르게, `SError` 항상 같은 에러 조건을 가르킵니다. [여기](https://community.arm.com/processors/f/discussions/3205/re-what-is-serror-detailed-explanation-is-required)에서 언제 `SError` 발생하는지 확인할 수 있습니다. ### 예외 벡터들 각 예외 유형은 자체 처리기를 필요로 한다. 또한 예외가 발생하는 각 실행 상태에 대해 별도의 핸들러를 정의해야 합니다. 예외 처리 관점에서 흥미있는 4개의 실행 상태가 있습니다. 만약 우리가 EL1에서 일하고 있다면, 그러한 상태는 다음과 같이 정의될 수 있습니다. 1. **EL1t** 스택 포인터가 EL0와 공유되는 동안 EL1으로부터 예외가 발생합니다. `SPSel` 레지스터가 `0`의 값을 갖을 때 발생합니다. 1. **EL1h** EL1에 전용 스택 포인터가 할당되었을 때 EL1에서 예외가 발생합니다. 이것은 `SPSel`이 값 `1`을 유지하고 있으며 이것이 현재 사용하고 있는 모드라는 것을 의미합니다. 1. **EL0_64** 64비트 모드에서 실행 중인 EL0에서 예외가 발생. 1. **EL0_32** 32비트 모드에서 실행 중인 EL0에서 예외가 발생. 총 16개(4개의 예외 레벨에 4개의 실행 상태를 곱한 것)의 예외 핸들러를 정의해야 합니다. 모든 예외 핸들러의 주소를 보유하는 특수 구조를 예외 `벡터 테이블` 또는 `벡터 테이블`이라고 합니다. 벡터 테이블의 구조는 [AArch64-Reference-Manual](https://developer.arm.com/docs/ddi0487/ca/arm-architecture-reference-manual-armv8-for-armv8-a-architecture-profile) 1876페이지의 `Table D1-7 Vector offsets from vector table base address`에 정의되어 있습니다. 벡터 표를 예외 벡터의 배열로 생각할 수 있습니다. 여기서 각 예외 벡터(또는 핸들러)는 특정 예외를 취급하는 연속적인 일련의 명령입니다. 따라서, `Table D1-7`에서 `AArch64-Reference-Manual`까지, 각 예외 벡터는 최대 `0x80` 바이트를 사용할 수 있다. 이것은 많지 않지만, 예외적인 벡터에서 다른 메모리 위치로 뛰어드는 것을 막는 사람은 아무도 없습니다. 이 모든 것이 훨씬 더 명확해질 것이라고 생각하므로, 이제 RPI-OS에서 예외 벡터가 어떻게 구현되는지 볼 때가 되었습니다. 예외 처리와 관련된 모든 것은 [entry.S](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/entry.S)에 정의되어 있고 지금 설명할 것입니다. 제일 유용한 매크고는 [ventry](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/entry.S#L12)라고 불리고 이것은 벡터 테이블 안에서 새로운 앤트리를 정의하기 위해서 사용됩니다. ``` .macro ventry label .align 7 b \label .endm ``` 이 정의에서 유추할 수 있듯이, 우리는 예외 벡터 안에서 바로 예외를 다루지 않고, 대신 우리는 `라벨` 인수로 매크로에 제공된 라벨로 점프합니다. 모든 예외 벡터는 0x80 바이트의 오프셋으로 배치해야 하므로 `.align 7`의 명령어가 필요합니다. 벡터 테이블은 [여기](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/entry.S#L64)에 정의되어 있고 이것은 16개의 `venrty` 정의로 구성되어 있습니다. 현재로서는 `EL1h`로부터 `IRQ`를 취급하는 데만 관심이 습니다. 그러나 여전히 16개의 핸들러 정의가 필요합니다. 이는 일부 하드웨어 요구 사항 때문이 아니라, 문제가 발생할 경우 의미 있는 오류 메시지를 보고 싶기 때문입니다. 일반적인 흐름 안에서 수행되어서는 안되는 모든 핸들러는 `invalid` 접두사를 갖고 있고 이것은 [handle_invalid_entry](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/entry.S#L3) 매크로를 사용합니다. 이 매크로가 어떻게 정의되는지 살펴봅시다. ``` .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm ``` 첫번째 줄에서는, `kernel_entry`라는 다른 매크로가 사용되는 것을 볼 수 있습니다. 나중에 간단히 논의할 예정입니다. 그때에 [show_invalid_entry_message](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/irq.c#L34)을 호출할 수 있고 이는 3개의 전달인자를 준비합니다. 첫번째 전달인자는 [이들](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/include/entry.h#L6) 값의 하나가 취할 수 있는 예외 유형입니다. 그것은 우리에게 어떤 예외 처리기가 실행되었는지 정확히 말해줍니다. 두 번째 매개 변수는 예외 신드롬 레지스터의 약자인 `ESR`이다. 이 전달인자는 `AArch64-Reference-Manual` 2431페이지에 설명되어 있는 `esr_el1` 레지스터로부터 취득됩니다. 이 레지스터에는 예외를 유발하는 요소에 대한 세부 정보가 포함되어 있습니다. 세 번째 전달인자는 대부분 동기 예외의 경우에 중요합니다. 예외 발생 시 실행된 지침의 주소가 포함된 `er_el1` 레지스터에서 이미 익숙한 값입니다. 동기 예외의 경우, 이것은 예외를 유발하는 명령어이기도 합니다. `show_invalid_entry_message` 함수가 모든 정보를 화면에 출력한 이후에 할 수 있는 것이 얼마 없으므로 프로세서를 무한 루프 상태로 둡니다. ### 레지스터 상태 저장 예외 처리기가 실행을 마친 후, 우리는 모든 일반 목적 레지스터가 예외를 생성하기 전에 가졌던 동일한 값을 갖기를 원합니다. 만약 우리가 그러한 기능을 구현하지 않는다면, 현재 코드를 실행하는 것과 무관한 중단은 이 코드의 동작에 예상치 못하게 영향을 미칠 수 있습니다. 그렇기 때문에 예외를 만든 후에 가장 먼저 해야 할 일은 프로세서 상태를 저장하는 것입니다. 이는 [kernel entry](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/entry.S) 매크로 안에서 수행됩니다. 이 매크로는 매우 간단합니다. `x0 - x30` 레지스터 값들을 스택에 저장합니다. 또한 예외 핸들러가 수행된 이후 호출되는 상응하는 [kernel exit](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/entry.S#L37) 매크로가 있습니다. `kernel_exit`는 `x0 - x30` 레지스터를 복사하고 복원함으로서 프로세서의 상태를 복원합니다. 또 정상적인 실행 흐름으로 되돌아가는 `eret` 명령도 실행합니다. 그런데, 범용 레지스터만 예외 핸들러를 실행하기 전에 저장해야 하므로, 현재로서는 우리의 간단한 커널로 충분하다. 마지막 레슨에서, `kernel_entry`와 `kernel_exit` 매크로를 추가할 것이다. ### 벡터 테이블 설정하기 이제 벡터 테이블을 준비했는데 프로세서는 이게 어디에 있는지 몰라서 사용할 수 없습니다. 예외 처리가 작동하려면 벡터 테이블 주소에 `vbar_el1`(Vector Base Address Register)을 설정해야 한다. 이는 [여기](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/irq.S#L2)에서 행해진다. ``` .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret ``` ### 인터럽트 마스킹/언마스킹 우리가 해야 할 또 다른 일은 모든 종류의 인터럽트를 푸는 것입니다. 인터럽트를 "unmasking" 것이 무슨 뜻인지 설명하겠습니다. 때때로 특정한 코드 조각이 비동기 방해에 의해 절대로 가로채지 않아야 한다는 것을 말할 필요가 있습니다. 예를 들어, `kernel_entry` 매크로의 바로 한가운데에서 인터럽트가 발생하면 어떻게 될까요? 이 경우 프로세서 상태는 덮어쓰고 손실될 수 있습니다. 그렇기 때문에 예외 처리기가 실행될 때마다 프로세서는 모든 유형의 인터럽트를 자동으로 비활성화해야합니다. 이것을 "masking"이라고 하는데, 이것 또한 우리가 필요하다면 수동으로 할 수 있다. 많은 사람들은 인터럽트가 예외 처리자의 전체 기간 동안 마스크를 착용해야 한다고 잘못 생각합니다. 이는 사실이 아닙니다. 프로세서 상태를 저장한 후 인터럽트를 마스킹 해제하는 것은 완전히 합법적이며, 따라서 인터럽트를 내포한 것도 합법적입니다. 지금 이것을 하지 않을 것이지만, 이것은 명심해야 할 중요한 정보입니다. [뒤의 2함수는](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/irq.S#L7-L15) 인터럽트들을 마스킹과 언마스킹합니다. ``` .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ``` ARM 프로세서 상태에는 서로 다른 유형의 인터럽트에 대한 마스크 상태를 유지하는 역할을 하는 4비트가 있습니다. 그 비트는 다음과 같이 정의됩니다. * **D** 마스크 디버그 예외. 이것들은 동기식 예외의 특별한 유형입니다. 분명한 이유로 모든 동기 예외를 마스킹할 수는 없지만 디버그 예외를 마스킹할 수 있는 별도의 플래그가 있으면 편리합니다. * **A** `SErrors`를 마스크한다. `SErrors`를 비동기적 중단이라고 부르기도 해서 `A`라고 부른다. * **I** `IRQs`를 마스크한다. * **F** `FIQs`를 마스크한다. 이제 인터럽트 마스크 상태 변경을 담당하는 레지스터를 `daifclr`와 `daifset`이라고 부르는 이유를 짐작할 수 있을 것입니다. 이러한 레지스터는 프로세서 상태에서 인터럽트 마스크 상태 비트를 설정 및 삭제합니다. 우리가 왜 두 가지 함수 모두에서 상수치인 `2`를 사용하는지 궁금하지 않는가? 두번째 (`I`) 비트만 세팅하고 싶기 때문입니다. ### 인터럽트 컨트롤러 구성하기 기기들은 대개 프로세서를 직접 중단하지 않습니다. 대신, 그들은 일을 하기 위해 인터럽트 컨트롤러에 의존합니다. 인터럽트 컨트롤러는 하드웨어가 전송하는 인터럽트를 활성화/비활성화하는 데 사용할 수 있습니다. 인터럽트 컨트롤러를 사용하여 어떤 장치가 인터럽트를 생성하는지 알아낼 수도 있습니다. Raspberry PI에는 [BCM2837 ARM 주변기기 매뉴얼](https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf)의 109페이지에 설명된 자체 인터럽트 컨트롤러가 있습니다. 라스베리 파이 인터럽트 컨트롤러에는 모든 유형의 인터럽트에 대해 활성화/비활성화 상태를 유지하는 레지스터 3개가 있습니다. 현재는 타이머 인터럽트에만 관심이 있습니다. 타이머 인터럽트는 `BCM2837 ARM Peripherals manual`의 116페이지에 설명되어 있는 [ENABLE_IRQS_1](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/include/peripherals/irq.h#L10) 레지스터를 사용하여 활성화 할 수 있습니다. 문서에 따르면, 인터럽트들은 두 개의 뱅크로 나뉩니다. 첫번째 뱅크는 `0 - 31`의 인터럽트 들로 구성되고 `ENBLE)IRQS_1` 레지스터의 다른 비트들을 셋팅함으로써 각각은 활성화되기도 비활성화되기도 합니다. 32개 인터럽트-`ENABLE_IRQS_2`를 위한 상응하는 레지스터와 ARM 로컬 인터럽트와 함께 일반적인 인터럽트를 제어하는 레지스터 - `ENABLE_BASIC IRQS`들이 존재합니다. (이 레슨의 다음 장에서는 ARM 로컬 인터럽트에 대해 이야기하겠습니다) 그러나 주변기기 매뉴얼은 많은 실수를 가지고 있고 그 중 하나는 우리의 논의와 직접적으로 관련이 있습니다. 주변 인터럽트 테이블(매뉴얼 113페이지에 설명됨)은 `0 - 3` 라인에서 시스템 타이머의 인터럽트 4개를 포함해야 한다. 리눅스 소스 코드 리버스 엔지니어링과 [몇몇 다른 소스](http://embedded-xinu.readthedocs.io/en/latest/arm/rpi/BCM2835-System-Timer.html)를 읽은 것으로부터 나는 타이머 인터럽트 0과 2가 GPU에 의해 예약되고 사용되며 인터럽트 1과 3은 어떤 다른 용도로도 사용될 수 있다는 것을 알아낼 수 있었습니다. 타이머 인터럽트 0과 2가 GPU에 의해 예약되고 사용되며 인터럽트 1과 3은 어떤 다른 용도로도 사용될 수 있다는 것을 알아낼 수 있었습니다. 그래서 여기에 시스템 타이머를 IRQ 숫자 1을 가능하게 하는 [함수](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/irq.c#L29)가 있습니다. ``` void enable_interrupt_controller() { put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1); } ``` ### 포괄적인 IRQ 핸들러 이전의 토론에서, 당신은 우리가 모든 `IRQ들`을 처리하는 단일 예외 처리 핸들러를 가지고 있다는 것을 기억해야 합니다. 이 핸들러는 [여기](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/irq.c#L39)에 정의되어 있습니다. ``` void handle_irq(void) { unsigned int irq = get32(IRQ_PENDING_1); switch (irq) { case (SYSTEM_TIMER_IRQ_1): handle_timer_irq(); break; default: printf("Unknown pending irq: %x\r\n", irq); } } ``` 핸들러에서, 우리는 어떤 장치가 인터럽트를 발생시키는지 알아낼 방법이 필요합니다. 인터럽트 컨트롤러는 우리에게 이 작업을 도와줄 수 있습니다. 인터럽트 컨트롤러는 `0 - 31` 인터럽트를 위한 인터럽트 상태 저장하는 `IRQ_PENDING_1` 레지스터를 갖고 있습니다. 이 레지스터를 사용하여 현재 인터럽트가 타이머에 의해 생성되었는지 또는 다른 장치 및 호출 장치별 인터럽트 핸들러에 의해 생성되었는지 확인할 수 있습니다. 여러 인터럽트가 동시에 보류될 수 있다는 점에 유의하십시오. 그렇기 때문에 각 기기별 인터럽트 핸들러는 인터럽트 처리를 완료하고 그 후에 `IRQ_PENDING_1`의 인터럽트 비트가 삭제된다는 것을 알아야합니다. 동일한 이유로, 생산 준비 OS의 경우 인터럽트 핸들러의 스위치 구조를 루프에 넣고 싶을 것입니다. 이렇게 하면 단일 핸들러 실행 중에 여러 개의 인터럽트를 처리할 수 있게 됩니다. ### 타이머 초기화 라스베리 파이 시스템 타이머는 매우 간단한 장치입니다. 매 시계 체크 후 1씩 값을 올리는 카운터를 가지고 있습니다. 또한 인터럽트 컨트롤러에 연결되는 4개의 인터럽트 라인과 4개의 해당 비교 레지스터가 있습니다. 카운터의 값이 비교 레지스터 중 하나에 저장된 값과 같아지면 해당 인터럽트가 실행됩니다. 그렇기 때문에 시스템 타이머 인터럽트를 사용하기 전에 0이 아닌 값으로 비교 레지스터 중 하나를 초기화해야 하는데, 값이 클수록 인터럽트는 나중에 생성됩니다. 이는 [timer_init](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/timer.c#L8) 함수 안에서 수행됩니다. ``` const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } ``` 첫 번째 라인은 전류 카운터 값을 읽고, 두 번째 라인은 이를 증가시키며, 세 번째 라인은 인터럽트 번호 1에 대한 비교 레지스터 값을 설정합니다. `interval` 값을 조작하면 첫 번째 타이머 인터럽트가 생성되는 시간을 조정할 수 있습니다. ### 타이머 인터럽트 핸들링 마침내, 우리는 타이머 인터럽트 핸들러에 도달했다. 그것은 사실 매우 간단합니다. ``` void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); printf("Timer iterrupt received\n\r"); } ``` 여기서는 먼저 비교 레지스터를 업데이트하여 동일한 시간 간격 후에 다음 인터럽트가 생성되도록 하십시오. 다음으로 `TIMER_CS` 레지스터에 1을 써넣어 인터럽트를 인정합니다. 문서에서 `TIMER_CS`는 "타이머 제어/상태" 레지스터라고 합니다. 이 레지스터의 비트 [0:3]는 사용 가능한 4개의 인터럽트 라인 중 하나에서 발생하는 인터럽트를 확인하는 데 사용할 수 있습니다. ### 결론 마지막으로 살펴봐야할 것은 [kernel_main](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/kernel.c#L7) 함수입니다. 그곳에 모든 이전에 논의됐던 함수들이 조화됩니다. 샘플을 컴파일하고 실행한 후 인터럽트가 실행된 후 "Timer interrupt received"(타이머 인터럽트 수신됨) 메시지를 출력해야 합니다. 혼자서 그것을 하도록 노력하고 코드를 주의 깊게 검사하고 그것을 실험하는 것을 잊지마세요. ##### 이전 페이지 2.3 [Processor initialization: Exercises](../lesson03/exercises.md) ##### 다음 페이지 3.2 [Interrupt handling: Low-level exception handling in Linux](./linux/low_level-exception_handling.md) ##### 추가 사항 이 문서는 훌륭한 오픈소스 운영체제 학습 프로젝트인 Sergey Matyukevich의 문서를 영어에 능숙하지 않은 한국인들이 학습할 수 있도록 번역한 것입니다. 오타 및 오역이 있을 수 있습니다. Sergey Matyukevich는 한국어를 능숙하게 다루지 못합니다. 대신 저에게 elxm6123@gmail.com으로 연락해주세요. ================================================ FILE: translations/ko/lesson05/exercises.md ================================================ ## 5.3: Exercises 1. task가 사용자 모드에서 실행될 때, 일부 시스템 레지스터에 접근해보아라. 이 경우 동기적 예외가 발생하는지 확인해라. 이 예외를 처리하려면 `esr_el1` 레지스터를 사용해 시스템 콜과 구분지어라. 1. 현재 task의 우선순위를 설정하기 위한 새로운 시스템 콜을 구현해보아라. task가 실행되는 동안 동적으로 우선 순위를 바꾸는 방법을 보여라. 1. lesson05의 소스코드가 qemu에서 실행되도록 수정하라. 다음 링크를 참고하라. [https://github.com/s-matyukevich/raspberry-pi-os/issues/8](https://github.com/s-matyukevich/raspberry-pi-os/issues/8) ##### Previous Page 5.2 [User processes and system calls: Linux](../../docs/lesson05/linux.md) ##### Next Page 6.1 [Virtual memory management: RPi OS](../../docs/lesson06/rpi-os.md) ================================================ FILE: translations/ko/lesson05/linux.md ================================================ ## 5.2: User processes and system calls RPi OS의 시스템 콜 구현은 리눅스에서 거의 복사해왔기 때문에 이번 챕터에서 설명할 것은 많지 않다. 하지만 리눅스에서 특정 시스템 콜이 어디에서 어떻게 구현되었는지 알 수 있도록 소스코드를 사용할 것이다. ### Creating first user process 먼저 첫 번째 사용자 프로세스가 어떻게 생성되는지에 대해 알아보자. [start_kernel](https://github.com/torvalds/linux/blob/v4.14/init/main.c#L509) 함수부터 살펴보는 것이 좋겠다. 이 함수는 linux/init/main.c에 있다. 앞에서 살펴본 바와 같이 이 함수는 커널 부팅 과정의 초기에 호출되는 최초의 아키텍처 독립적인 함수이다. 이 함수에서 커널 초기화를 시작하며, 커널 초기화 도중에 첫 번째 사용자 프로세스를 실행하는 것이 이해될 것이다. 실제로 `start_kernel`의 흐름을 따라가다 보면 곧 [kernel_init](https://github.com/torvalds/linux/blob/v4.14/init/main.c#L989) 함수를 발견할 것이다. kernel_init 함수는 다음 코드를 포함한다. ``` if (!try_to_run_init_process("/sbin/init") || !try_to_run_init_process("/etc/init") || !try_to_run_init_process("/bin/init") || !try_to_run_init_process("/bin/sh")) return 0; ``` 이것이 우리가 찾던 것인 듯하다. 이 코드로부터 리눅스 커널이 `init` 사용자 프로그램을 어디에서 시작해서 어느 순서로 찾는지를 추론할 수 있다. 그러면 `try_to_run_init_process` 함수는 [execve](http://man7.org/linux/man-pages/man2/execve.2.html)시스템 콜을 처리하기 위해 [do_execve](https://github.com/torvalds/linux/blob/v4.14/fs/exec.c#L1837)함수를 실행한다. 이 시스템 콜은 binary executable file을 읽어서 현재 프로세스에서 실행한다. `execve` 시스템 콜은 lesson9에서 자세히 살펴볼 것이다. 지금은 이 시스템 콜의 가장 중요한 작업은 executable file을 구문 분석(parsing)하고 해당 내용을 메모리에 로드하는 것이고 이는 [load_elf_binary](https://github.com/torvalds/linux/blob/v4.14/fs/binfmt_elf.c#L679) 함수에서 수행된다. 여기서 우리는 executable file이 [ELF](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format) 형식이라고 가정한다. `load_elf_binary` 함수의 끝부분에서 아키텍처 별 [start_thread](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/processor.h#L119) 함수가 호출된다. RPi OS에서는 이를 `move_to_user_mode` 루틴의 프로토타입으로 사용했으며, 다음 코드가 우리가 주로 관심 있는 코드이다. ``` static inline void start_thread_common(struct pt_regs *regs, unsigned long pc) { memset(regs, 0, sizeof(*regs)); forget_syscall(regs); regs->pc = pc; } static inline void start_thread(struct pt_regs *regs, unsigned long pc, unsigned long sp) { start_thread_common(regs, pc); regs->pstate = PSR_MODE_EL0t; regs->sp = sp; } ``` `start_thread`가 실행될 때, 현재 프로세스는 커널 모드에서 동작한다. `start_thread`는 저장된 `pstate`, `sp`, `pc` 필드를 설정하는 데 사용되는 현재 `pt_regs` 구조체에 접근할 수 있다. 이 논리는 RPi OS의 `move_to_user_mode` 함수와 정확히 동일하므로 생략하겠다. 기억해야 할 것은 `start_thread` 함수가 `kernel_exit` 매크로가 사용자 모드로 프로세스를 이동시키는 방식으로 저장된 프로세서 상태를 준비한다는 것이다. ### Linux syscalls 기본 시스템 콜 메커니즘은 리눅스 및 RPi OS에서 정확히 동일하다. 이제 이미 익숙한 [clone](http://man7.org/linux/man-pages/man2/clone.2.html) 시스템 콜을 사용하여 이 메커니즘의 자세한 부분을 이해해보자. [glibc clone wrapper](https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/aarch64/clone.S;h=e0653048259dd9a3d3eb3103ec2ae86acb43ef48;hb=HEAD#l35) 함수부터 시작하자. 이 함수는 RPi OS의 [call_sys_clone](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/sys.S#L22) 함수와 동일하게 동작하지만, 이전 함수에서 전달인자의 온전성을 검사하고 적절하게 예외를 처리한다는 점이 다르다. 꼭 이해하고 기억해야 할 것은 두 가지 경우 모두 `svc` 명령어를 사용해 동기적 예외를 생성하고, 시스템 콜 번호는 `x8` 레지스터에 저장하고 모든 전달인자는 `x0`-`x7` 레지스터에 저장하여 전달한다. 다음으로 `clone` 시스템 콜 정의를 살펴보자. 이 코드는 [여기](https://github.com/torvalds/linux/blob/v4.14/kernel/fork.c#L2153) 에서 찾을 수 있으며 다음과 같다. ``` SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp, int __user *, parent_tidptr, int __user *, child_tidptr, unsigned long, tls) { return _do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr, tls); } ``` [SYSCALL_DEFINE5](https://github.com/torvalds/linux/blob/v4.14/include/trace/syscall.h#L25) 매크로는 5개의 파라미터로 시스템 콜을 정의한다는 의미의 5를 이름에 포함한다. 이 매크로는 새로운 [syscall_metadata](https://github.com/torvalds/linux/blob/v4.14/include/trace/syscall.h#L25) 구조체를 할당한 뒤 값을 채우고, `sys_` 함수를 생성한다. 예를 들어 `clone` 시스템 콜의 경우 `sys_clone` 함수가 정의된다. 이 함수는 실제로 하위 계층 아키텍처 코드로부터 호출되는 시스템 콜 handler이다. 시스템 콜이 실행될 때, 커널은 시스템 콜 번호로 시스템 콜 handler를 찾을 방법이 필요하다. 이를 위한 가장 쉬운 방법은 시스템 콜 handler에 대한 포인터 배열을 생성하여 각 시스템 콜 번호를 배열의 인덱스로 사용하는 것이다. 이러한 접근 방식을 RPi OS에서 사용했으며 리눅스에서도 동일한 접근 방식을 사용한다. 이 배열을 [sys_call_table](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/sys.c#L62) 이라고 하며 다음과 같이 정의된다. ``` void * const sys_call_table[__NR_syscalls] __aligned(4096) = { [0 ... __NR_syscalls - 1] = sys_ni_syscall, #include }; ``` 모든 시스템 콜은 초기에 `sys_ni_syscall` 함수를 가리키도록 할당된다. 여기서 ni는 존재하지 않음을 의미한다. `0`번 시스템 콜과 현재 아키텍처에 대해 정의되지 않은 모든 시스템 콜은 이 handler를 유지한다. `sys_call_table` 배열에 있는 다른 모든 handler는 [asm/unistd.h](https://github.com/torvalds/linux/blob/v4.14/include/uapi/asm-generic/unistd.h) 헤더파일에 다시 쓰여진다. 이 파일은 간단히 시스템 콜 번호와 핸들러 함수 간의 맵핑을 제공한다. ### Low-level syscall handling code `sys_call_table`이 어떻게 생성되고 채워지는지를 보았다. 이제 하위 계층 시스템 콜 처리 코드에서 어떻게 사용되는지 알아보자. 다시 말하지만 기본 메커니즘은 RPi OS와 거의 동일하다. 일부 시스템 콜은 동기적 예외이며 모든 exception handler는 exception vector table에 정의되어 있다는 것을 알고 있다. 우리가 관심 있는 handler는 EL0에서 생성된 동기적 예외를 처리하는 것이다. 각 예외가 올바른 handler를 찾도록 하는 것을 [el0_sync](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L598)라고 한다. ``` el0_sync: kernel_entry 0 mrs x25, esr_el1 // read the syndrome register lsr x24, x25, #ESR_ELx_EC_SHIFT // exception class cmp x24, #ESR_ELx_EC_SVC64 // SVC in 64-bit state b.eq el0_svc cmp x24, #ESR_ELx_EC_DABT_LOW // data abort in EL0 b.eq el0_da cmp x24, #ESR_ELx_EC_IABT_LOW // instruction abort in EL0 b.eq el0_ia cmp x24, #ESR_ELx_EC_FP_ASIMD // FP/ASIMD access b.eq el0_fpsimd_acc cmp x24, #ESR_ELx_EC_FP_EXC64 // FP/ASIMD exception b.eq el0_fpsimd_exc cmp x24, #ESR_ELx_EC_SYS64 // configurable trap b.eq el0_sys cmp x24, #ESR_ELx_EC_SP_ALIGN // stack alignment exception b.eq el0_sp_pc cmp x24, #ESR_ELx_EC_PC_ALIGN // pc alignment exception b.eq el0_sp_pc cmp x24, #ESR_ELx_EC_UNKNOWN // unknown exception in EL0 b.eq el0_undef cmp x24, #ESR_ELx_EC_BREAKPT_LOW // debug exception in EL0 b.ge el0_dbg b el0_inv ``` 여기서 현재 예외가 시스템 콜인지 아닌지 알기 위해 `esr_el1`레지스터가 사용된다. 시스템 콜인 경우 [el0_svc](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L837) 함수가 호출된다. 이 함수에 대해 살펴보자. ``` el0_svc: adrp stbl, sys_call_table // load syscall table pointer mov wscno, w8 // syscall number in w8 mov wsc_nr, #__NR_syscalls el0_svc_naked: // compat entry point stp x0, xscno, [sp, #S_ORIG_X0] // save the original x0 and syscall number enable_dbg_and_irq ct_user_exit 1 ldr x16, [tsk, #TSK_TI_FLAGS] // check for syscall hooks tst x16, #_TIF_SYSCALL_WORK b.ne __sys_trace cmp wscno, wsc_nr // check upper syscall limit b.hs ni_sys ldr x16, [stbl, xscno, lsl #3] // address in the syscall table blr x16 // call sys_* routine b ret_fast_syscall ni_sys: mov x0, sp bl do_ni_syscall b ret_fast_syscall ENDPROC(el0_svc) ``` Now, let's examine it line by line. ``` el0_svc: adrp stbl, sys_call_table // load syscall table pointer mov wscno, w8 // syscall number in w8 mov wsc_nr, #__NR_syscalls ``` 처음 세 줄에서는 어떤 레지스터의 별칭인 `stbl`, `wscno`, `wsc_nr` 변수가 초기화된다. `stbl`은 시스템 콜 테이블의 주소를 가지고, `wsc_nr`은 총 시스템 콜 개수를 가지며, `wscno`는 `x8` 레지스터에 저장된 현재 시스템 콜 번호를 뜻한다. ``` stp x0, xscno, [sp, #S_ORIG_X0] // save the original x0 and syscall number ``` RPi OS에서 다루었듯이 시스템 콜이 종료된 후 `x0`은 `pt_regs` 영역에 덮여씌워진다. `x0` 레지스터의 기존 값이 필요한 경우, `pt_regs`구조체의 별도 필드에 저장된다. 마찬가지로 시스템 콜 번호도 `pt_regs`에 저장된다. ``` enable_dbg_and_irq ``` 인터럽트와 debug exception을 활성화한다. ``` ct_user_exit 1 ``` 사용자 모드에서 커널 모드로의 전환하는 것을 기록한다. ``` ldr x16, [tsk, #TSK_TI_FLAGS] // check for syscall hooks tst x16, #_TIF_SYSCALL_WORK b.ne __sys_trace ``` 현재 task가 시스템 콜 추적 프로그램에서 실행되는 경우에 `_TIF_SYSCALL_WORK` 플래그가 set되어야 한다. 이 경우 `__sys_trace` 함수가 호출된다. 일반적인 경우만 살펴보기 위해 이 함수는 넘어가겠다. ``` cmp wscno, wsc_nr // check upper syscall limit b.hs ni_sys ``` 현재 시스템 콜 번호가 총 시스템 콜 개수보다 큰 경우, 사용자에게 오류를 리턴한다. ``` ldr x16, [stbl, xscno, lsl #3] // address in the syscall table blr x16 // call sys_* routine b ret_fast_syscall ``` 시스템 콜 번호는 시스템 콜 테이블 배열에서 handler를 찾기위한 인덱스로 사용된다. Handler 주소는 `x16` 레지스터에 로드되어 실행된다. 마침내 `ret_fast_syscall` 함수에 이르렀다. ``` ret_fast_syscall: disable_irq // disable interrupts str x0, [sp, #S_X0] // returned x0 ldr x1, [tsk, #TSK_TI_FLAGS] // re-check for syscall tracing and x2, x1, #_TIF_SYSCALL_WORK cbnz x2, ret_fast_syscall_trace and x2, x1, #_TIF_WORK_MASK cbnz x2, work_pending enable_step_tsk x1, x2 kernel_exit 0 ``` 중요한 것은 첫 번째 줄에서 인터럽트가 비활성화되고 마지막 줄에서 `kernel_exit` 매크로가 호출된다는 것이다. 그 밖의 모든 것은 특수한 경우의 처리와 관련이 있다. 따라서 이 함수는 시스템 콜이 실제로 종료되고 사용자 프로세스로 실행이 옮겨지는 곳이다. ### Conclusion 이제 시스템 콜을 생성하고 처리하는 것을 다 살펴보았다. 이 과정은 상대적으로 간단하지만 운영체제에서 필수적이다. 왜냐하면 커널이 API를 설정하고, API가 사용자 프로그램과 커널 간의 유일한 통신 수단임을 확인하기 때문이다. ##### Previous Page 5.1 [User processes and system calls: RPi OS](../../docs/lesson05/rpi-os.md) ##### Next Page 5.3 [User processes and system calls: Exercises](../../docs/lesson05/exercises.md) ================================================ FILE: translations/ko/lesson05/rpi-os.md ================================================ ## 5.1: User processes and system calls RPi OS를 더 이상 bare-metal 프로그램이 아닌 실제 운영 체제처럼 만들어주는 많은 기능들을 추가하였다. 이제 RPi OS는 프로세스를 관리할 수 있지만 여전히 프로세스를 격리할 수는 없다는 큰 단점이 존재한다. 이번 lesson에서 이 문제를 해결해 볼 것이다. 먼저, 모든 사용자 프로세스를 EL0으로 옮겨서 특권 프로세스 동작에 대한 접근을 제한할 것이다. 이 작업 없이는 어떤 사용자 프로그램이 우리의 보안 설정을 덮어쓸 수 있고 그러면 격리가 무너지게 된다. 사용자 프로그램이 커널 함수에 직접 접근하는 것을 제한한다면 다른 문제가 발생한다. 예를 들어 사용자 프로그램이 사용자에게 무언가를 출력해야 한다면? 우리는 UART 장치를 직접적으로 동작시키길 절대 원하지 않는다. 대신에 OS가 API method 집합으로 각 프로그램을 제공하는 것이 좋을 듯하다. 이런 API는 사용자 프로그램이 호출할 때마다 현재 exception level을 EL1으로 올려야 하기 때문에 단일 method의 집합으로 구현될 수는 없다. 개별적인 API method를 system call이라고 하며 이번 lesson에서 RPi OS에 시스템 콜 집합을 추가할 것이다. 프로세스 격리에 대한 세 번째 개념이 있다. 각 프로세스는 독립적인 메모리를 가진다고 생각해야 한다. - 이 문제에 대해서는 lesson6에서 다룰 것이다. ### System calls implementation 시스템 콜에 대한 핵심 아이디어는 매우 간단하다. 각 시스템 콜은 사실 동기적 예외이다. 사용자 프로그램이 시스템 콜을 실행해야 할 때, 먼저 필요한 모든 전달인자를 준비하고 `svc`명령어를 실행한다. 이 명령어는 동기적 예외를 발생시킨다. 이런 예외는 운영체제에 의해 EL1에서 처리된다. 운영체제는 모든 전달인자의 유효성을 검사하고, 요청된 작업을 수행한 뒤, 정상적인 예외 리턴을 실행하여 EL0에서 `svc`명령어 바로 다음부터 실행이 재개되도록 한다. RPi OS는 4개의 간단한 시스템 콜을 정의한다: 1. `write` 이 시스템 콜은 UART 장치를 사용해 화면에 무언가를 출력한다. 첫 번째 전달인자로 출력할 문자열이 담긴 버퍼를 받는다. 1. `clone` 이 시스템 콜은 새로운 사용자 스레드를 생성한다. 첫 번째 전달인자로 새로 생성된 스레드의 스택 위치를 넘겨준다. 1. `malloc` 이 시스템 콜은 사용자 프로세스를 위해 메모리 페이지를 할당한다. 리눅스에는 이런 역할의 시스템 콜이 없다. RPi OS에서 필요한 이유는 아직 가상 메모리가 구현되지 않아서 모든 사용자 프로세스가 물리 메모리에서 동작하기 때문이다. 때문에 각 프로세스는 어떤 메모리 페이지를 사용할 수 있는지 찾을 방법이 필요하다. `malloc` 시스템 콜은 새로 할당된 페이지에 대한 포인터를 리턴하며, 에러가 발생한 경우에는 1을 리턴한다. 1. `exit` 각 프로세스는 실행을 마친 뒤 이 시스템 콜을 반드시 호출해야 한다. 이 시스템 콜은 필요한 모든 것을 정리할 것이다. 모든 시스템 콜은 [sys.c](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/sys.c) 파일에 정의되어 있다. 여기에는 모든 시스템 콜의 handler에 대한 포인터를 포함하는 [sys_call_table](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/sys.c) 배열이 있다. 각 시스템 콜은 syscall number를 가진다. — 이는 단순히 `sys_call_table` 배열의 인덱스이다. 모든 syscall numbers 는 [여기](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/include/sys.h#L6)에 정의되어 있다. — syscall number는 어셈블러 코드에서 원하는 시스템 콜을 지정할 때 사용된다. `write` 시스템 콜을 예로 들어 시스템 콜 wrapper 함수를 살펴보자. 이는 [여기](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/sys.S#L4)에서 찾을 수 있다. ``` .globl call_sys_write call_sys_write: mov w8, #SYS_WRITE_NUMBER svc #0 ret ``` 이 함수는 매우 간단하다. `w8` 레지스터에 시스템 콜 번호를 저장하고 `svc` 명령어를 실행해 동기적 예외를 생성한다. `w8` 규약에 따라 시스템 콜 번호로 사용된다. `x0`-`x7` 레지스터는 시스템 콜 전달인자에 사용되며 `x8`은 시스템 콜 번호로 사용되어 시스템 콜은 최대 8개의 전달인자를 가질 수 있다. 이러한 wrapper 함수는 보통 커널 자체에 포함되지 않는다. [glibc](https://www.gnu.org/software/libc/)와 같은 다른 언어의 표준 라이브러리에서 찾을 가능성이 높다. ### Handling synchronous exceptions 동기적 예외가 발생한 후에는 exception table에 등록된 [handler](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/entry.S#L98)가 호출된다. Handler 자체는 [여기](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/entry.S#L157)에서 찾을 수 있으며 이는 다음 코드로 시작한다. ``` el0_sync: kernel_entry 0 mrs x25, esr_el1 // read the syndrome register lsr x24, x25, #ESR_ELx_EC_SHIFT // exception class cmp x24, #ESR_ELx_EC_SVC64 // SVC in 64-bit state b.eq el0_svc handle_invalid_entry 0, SYNC_ERROR ``` 먼저, 모든 exception handler에서는 `kernel_entry` 매크로를 호출한다. 그런 후에 `esr_el1`(Exception Syndrome Register)를 확인한다. 이 레지스터는 오프셋 [ESR_ELx_EC_SHIFT](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/include/arm/sysregs.h#L46)에 exception class 필드를 포함한다. 만약 exception class가 [ESR_ELx_EC_SVC64](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/include/arm/sysregs.h#L47) 와 동일하다면 이는 현재 예외가 `svc` 명령어로부터 초래된 시스템 콜임을 의미한다. 이 경우, `el0_svc` 레이블로 분기하거나 아니면 오류 메시지를 표시한다. ``` sc_nr .req x25 // number of system calls scno .req x26 // syscall number stbl .req x27 // syscall table pointer el0_svc: adr stbl, sys_call_table // load syscall table pointer uxtw scno, w8 // syscall number in w8 mov sc_nr, #__NR_syscalls bl enable_irq cmp scno, sc_nr // check upper syscall limit b.hs ni_sys ldr x16, [stbl, scno, lsl #3] // address in the syscall table blr x16 // call sys_* routine b ret_from_syscall ni_sys: handle_invalid_entry 0, SYSCALL_ERROR ``` `el0_svc`는 먼저 `stbl`(x27의 별칭)에 있는 syscall table의 주소와 `scno` 변수의 시스템 콜 번호를 로드한다. 그런 뒤에 인터럽트가 활성화되고 시스템 콜 번호를 시스템의 총 시스템 콜 개수와 비교한다. 시스템 콜 번호가 전체 개수보다 크거나 같다면 오류 메시지를 출력한다. 시스템 콜 번호가 요구되는 범위 내로 들어오면 syscall handler의 포인터를 얻기 위해 syscall table 배열의 인덱스로 사용된다. 그런 다음 handler가 실행되고, 종료한 후에는 `ret_from_syscall`이 호출된다. 여기서 `x0`-`x7` 레지스터를 건들지 않는다는 것에 주의하자. 이 레지스터들은 그대로 handler에 전달된다. ``` ret_from_syscall: bl disable_irq str x0, [sp, #S_X0] // returned x0 kernel_exit 0 ``` `ret_from_syscall`에서는 먼저 인터럽트를 비활성화한다. 그리고 `x0` 레지스터의 값을 스택에 저장한다. `kernel_exit`에서 모든 저장된 범용 레지스터를 복구하지만 `x0`은 이제 syscall handler의 리턴값을 가지므로 사용자 코드에 이 값을 전달하기 위해서는 스택에 저장해야 한다. 드디어 `kernel_exit`가 호출되어 사용자 코드로 돌아간다. ### Switching between EL0 and EL1 이전 lesson을 주의깊게 읽었다면 `kernel_entry`와 `kernel_exit` 매크로의 변화를 눈치챘을 것이다. 이제 이 둘은 추가적인 전달인자를 받는다. 이 전달인자는 예외가 발생한 exception level을 나타낸다. 기존 exception level에 대한 정보는 스택 포인터를 적절히 저장 및 복구하기 위해 필요하다. 다음은 `kernel_entry`매크로와 `kernel_exit`매크로의 대응되는 부분이다. ``` .if \el == 0 mrs x21, sp_el0 .else add x21, sp, #S_FRAME_SIZE .endif /* \el == 0 */ ``` ``` .if \el == 0 msr sp_el0, x21 .endif /* \el == 0 */ ``` 우리는 EL0과 EL1에 대해 개별적인 2개의 스택 포인터를 사용한다. 따라서 EL0에서 예외가 전달된 직후 스택 포인터를 덮어쓴다. 기존의 스택 포인터는 `sp_el0` 레지스터에서 찾을 수 있다. 이 레지스터의 값은 handler에서 사용되지 않더라도 예외가 전달되기 전과 후에 저장 및 복구되어야 한다. 이 과정을 진행하지 않는다면 문맥 교환 후에 `sp` 레지스터는 결국 틀린 값을 가지게 될 것이다. EL1에서 예외가 발생한 경우에 `sp` 레지스터 값을 저장하지 않는 이유도 궁금할 것이다. 그 이유는 exception handler에서 동일한 커널 스택을 재사용할 것이기 때문이다. 예외 처리 도중에 `kernel_exit`에 의한 문맥 교환이 발생하더라도 `sp`는 이미 `cpu_switch_to` 함수에 의해 전환되었을 것이다. 리눅스에서는 exception handler에 다른 스택을 사용하기 때문에 동작이 이와 다르다. 또한 `eret` 명령 전에 어떤 exception level이 리턴되어야 하는지 명시적으로 지정할 필요가 없다. 이 정보는 `spsr_el1` 레지스터에 인코딩되어 있기 때문에 항상 예외가 발생했던 level로 리턴한다. ### Moving a task to user mode 어떤 시스템 콜이 발생하기 전에는 사용자 모드에서 task가 실행되어야 한다. 새로운 사용자 task를 생성하는 방법에는 두 가지가 있다. 커널 스레드가 사용자 모드로 이동하거나 사용자 task가 fork되어 새로운 사용자 task를 생성하는 것이다. 이 섹션에서는 첫 번째 경우를 살펴볼 것이다. 이 작업을 수행하는 함수는 [move_to_user_mode](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/fork.c#Li47)이다. 이 함수를 살펴보기 전에 어떻게 사용되는지 살펴보자. 그러기 위해 [kernel.c](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/kernel.c) 파일을 먼저 살펴보자. 다음이 관련 코드이다. ``` int res = copy_process(PF_KTHREAD, (unsigned long)&kernel_process, 0, 0); if (res < 0) { printf("error while starting kernel process"); return; } ``` 먼저 `kernel_main` 함수에서 새로운 커널 스레드를 생성한다. 이전 lesson에서 했던 것과 동일한 방법을 사용한다. 스케줄러가 새로 생성된 task를 실행한 뒤, `kernel_process` 함수가 커널 모드에서 실행된다. ``` void kernel_process(){ printf("Kernel process started. EL %d\r\n", get_el()); int err = move_to_user_mode((unsigned long)&user_process); if (err < 0){ printf("Error while moving process to user mode\n\r"); } } ``` `kernel_process` 함수는 상태 메시지를 출력하고, `user_process`에 대한 포인터를 첫 번째 전달인자로 하여 `move_to_user_mode` 함수를 호출한다. 이제 이 함수가 하는 일을 살펴보자. ``` int move_to_user_mode(unsigned long pc) { struct pt_regs *regs = task_pt_regs(current); memzero((unsigned long)regs, sizeof(*regs)); regs->pc = pc; regs->pstate = PSR_MODE_EL0t; unsigned long stack = get_free_page(); //allocate new user stack if (!stack) { return -1; } regs->sp = stack + PAGE_SIZE; current->stack = stack; return 0; } ``` 지금은 init task로부터 fork된 새로운 커널 스레드를 실행하는 중이다. 이전 lesson에서 프로세스를 fork하는 것에 대해 다루었고, 새로 생성된 task의 스택의 top에 `pt_regs` 영역이 예약되는 것을 보았다. 이번에 처음으로 이 영역을 사용할 것이다. 우리는 수동으로 준비된 프로세서 상태를 저장할 것이다. 이 상태는 `kernel_exit` 매크로가 기대하는 것과 정확히 같은 형식을 가지며 [pt_regs](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/include/fork.h#L21) 구조체에 의해 묘사된다. 다음은 `move_to_user_mode` 함수에서 초기화되는 `pt_regs` 구조체의 필드들이다. * `pc`: 이제 `pc`는 사용자 모드에서 실행되어야 할 함수를 가리킨다. `kernel_exit`는 `pc` 값을 `elr_el1` 레지스터에 복사하여 예외 리턴 후 `pc`주소로 돌아가게 한다. * `pstate` 이 필드는 `kernel_exit` 매크로에 의해 `spsr_el1`에 복사되어 예외가 리턴된 후의 프로세서 상태를 가진다. `pstate` 필드에 복사된 [PSR_MODE_EL0t](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/include/fork.h#L9) 상수를 준비하는 방식으로 예외는 EL0로 리턴될 수 있다. lesson2에서 EL3에서 EL1으로 전환할 때 이미 동일한 방법을 사용한 적이 있다. * `stack` `move_to_user_mode` 함수는 사용자 스택을 위한 새로운 페이지를 할당하고 sp 필드를 해당 페이지의 top에 대한 포인터로 설정한다. `task_pt_regs` 함수는 `pt_regs` 영역의 위치를 계산하는 데 사용된다. 현재의 커널 스레드를 초기화한 방식 때문에 `sp`가 `pt_regs` 영역 바로 앞을 가리킬 것이다. 이 동작은 [ret_from_fork](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/entry.S#L188) 함수에서 일어난다. ``` .globl ret_from_fork ret_from_fork: bl schedule_tail cbz x19, ret_to_user // not a kernel thread mov x0, x20 blr x19 ret_to_user: bl disable_irq kernel_exit 0 ``` `ret_from_fork` 함수가 업데이트되었다. 이제 커널 스레드가 종료한 후에 `ret_to_user` 레이블을 실행하여 인터럽트를 비활성화하고, 이전에 준비해 둔 프로세서 상태를 사용하여 정상적인 예외 리턴을 수행한다. ### Forking user processes 이제 `kernel.c` 파일로 돌아가자. 이전 섹션에서 보았듯이 `kernel_process` 함수가 끝난 뒤에 [user_process](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/kernel.c#L22) 함수가 사용자 모드에서 실행된다. 이 함수는 `clone` 시스템 콜을 두 번 호출하여 [user_process1](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/kernel.c#L10) 함수를 2개의 스레드에서 병렬적으로 실행한다. `clone` 시스템 콜은 전달인자로 새로운 사용자 스택의 위치가 필요하며, 두 개의 새로운 메모리 페이지를 할당하기 위해 `malloc` 시스템 콜도 호출해야 한다. 이제 `clone` 시스템 콜 wrapping 함수가 어떻게 생겼는지 [여기](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/sys.S#L22)에서 살펴보자. ``` .globl call_sys_clone call_sys_clone: /* Save args for the child. */ mov x10, x0 /*fn*/ mov x11, x1 /*arg*/ mov x12, x2 /*stack*/ /* Do the system call. */ mov x0, x2 /* stack */ mov x8, #SYS_CLONE_NUMBER svc 0x0 cmp x0, #0 beq thread_start ret thread_start: mov x29, 0 /* Pick the function arg and execute. */ mov x0, x11 blr x10 /* We are done, pass the return value through x0. */ mov x8, #SYS_EXIT_NUMBER svc 0x0 ``` `clone` 시스템 콜 wrapping 함수의 설계는 `glibc` 라이브러리의 [대응되는 함수](https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/aarch64/clone.S;h=e0653048259dd9a3d3eb3103ec2ae86acb43ef48;hb=HEAD#l35)의 동작을 열심히 흉내내보았다. 1. `x0`-`x2` 레지스터를 저장한다. 이 레지스터들은 시스템 콜의 파라미터를 포함하며 나중에 시스템 콜 handler에 의해 덮어씌워진다. 1. 시스템 콜 handler를 호출한다. 1. 시스템 콜의 리턴값을 확인한다. 그 값이 `0`이라면 새로 생성된 스레드에서 실행할 것이며, 이 경우 `thread_start` 레이블로 실행이 옮겨진다. 1. 0이 아닌 값이라면 이는 새로운 task의 PID이다. 이것은 시스템 콜 처리가 끝난 후 호출했던 위치로 돌아올 것이며 기존의 스레드에서 계속 실행한다는 의미이다. 1. 첫 번째 전달인자로 넘겨진 함수가 새로운 스레드에서 호출된다. 1. 이 함수의 수행이 끝난 후, 리턴하는 것이 아니라 `exit` 시스템 콜이 수행된다. 보다시피 clone wrapper 함수와 clone 시스템 콜의 의미는 다르다. Clone wrapper 함수는 전달인자로 실행될 함수에 대한 포인터를 받아서 호출자에게 두 번 리턴하게 된다. 첫 번째 리턴은 기존의 task에서 두 번째 리턴은 cloned task에서 발생한다. Clone 시스템 콜 핸들러는 [여기](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/sys.c#L11)에 있으며 매우 간단하다. 이미 우리에게 익숙한 `copy_process` 함수를 호출할 뿐이다. `copy_process` 함수는 이전 lesson 이후에 수정되었다. 이제 커널 스레드뿐만 아니라 사용자 스레드를 복제하는 것도 지원한다. 그 코드는 다음과 같다. ``` int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg, unsigned long stack) { preempt_disable(); struct task_struct *p; p = (struct task_struct *) get_free_page(); if (!p) { return -1; } struct pt_regs *childregs = task_pt_regs(p); memzero((unsigned long)childregs, sizeof(struct pt_regs)); memzero((unsigned long)&p->cpu_context, sizeof(struct cpu_context)); if (clone_flags & PF_KTHREAD) { p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; } else { struct pt_regs * cur_regs = task_pt_regs(current); *childregs = *cur_regs; childregs->regs[0] = 0; childregs->sp = stack + PAGE_SIZE; p->stack = stack; } p->flags = clone_flags; p->priority = current->priority; p->state = TASK_RUNNING; p->counter = p->priority; p->preempt_count = 1; //disable preemtion until schedule_tail p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)childregs; int pid = nr_tasks++; task[pid] = p; preempt_enable(); return pid; } ``` 새로운 커널 스레드를 생성하는 경우, 이전 lesson에서 설명했던 것과 동일하게 동작한다. 사용자 스레드를 복제하는 경우에는 이 코드가 실행된다. ``` struct pt_regs * cur_regs = task_pt_regs(current); *childregs = *cur_regs; childregs->regs[0] = 0; childregs->sp = stack + PAGE_SIZE; p->stack = stack; ``` 먼저 `kernel_entry` 매크로에 의해 저장된 프로세서 상태에 접근한다. 여기서 커널 스택의 top에 위치한 `pt_regs` 영역을 반환할 때와 동일한 `task_pt_regs` 함수를 사용할 수 있는 이유는 명확하지 않다. `pt_regs`가 스택의 다른 위치에 저장될 수 없는 이유는 무엇일까? 그 답은 이 코드가 `clone` 시스템 콜이 호출된 후에만 실행될 수 있다는 것이다. 시스템 콜이 발생할 당시의 커널 스택은 비어있었다. 사용자 모드로 이동한 후 비워둔 것이다. 이것이 `pt_regs`가 항상 커널 스택의 top에 저장되는 이유이다. 각 시스템 콜은 사용자 모드로 돌아가기 전에 커널 스택을 비울 것이기 때문에 이 규칙은 모든 시스템 콜에서 유지된다. 두 번째 줄에서 현재의 프로세서 상태가 새로운 task 상태로 복사된다. `x0`은 호출자가 시스템 콜의 리턴값으로 해석하기 때문에 새로운 상태에서 `0`으로 설정된다. Clone wrapper 함수는 이 값을 이용하여 기존 스레드에서 계속 실행할지 아니면 새로운 스레드에서 실행할지를 결정한다. 다음으로 새로운 task의 `sp`가 새로운 사용자 스택 페이지의 top에 대한 포인터로 설정된다. 또한 task가 종료된 후 cleanup하기 위해 스택 페이지에 대한 포인터를 저장한다. ### Exiting a task 각 사용자 task가 종료된 후에는 `exit` 시스템 콜을 호출해야 한다. 현재 구현된 것에서는 `clone` wrapper 함수에 의해 `exit`가 호출된다. `exit`시스템 콜은 task 비활성화를 담당하는 [exit_process](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/sched.c) 함수를 호출한다. 이 함수는 다음과 같다. ``` void exit_process(){ preempt_disable(); for (int i = 0; i < NR_TASKS; i++){ if (task[i] == current) { task[i]->state = TASK_ZOMBIE; break; } } if (current->stack) { free_page(current->stack); } preempt_enable(); schedule(); } ``` 리눅스의 관례에 따라 한 번에 task를 삭제하지 않는 대신에 상태를 `TASK_ZOMBIE`로 설정한다. 이 상태는 스케줄러에 의해 task가 선택되어 실행되지 않도록 한다. 리눅스에서는 자식 프로세스가 종료한 후 부모 프로세스가 그 정보를 알 수 있도록 하기 위해 이러한 접근방식이 사용된다. `exit_process`는 불필요한 사용자 스택을 즉시 삭제하고 `schedule` 함수를 호출한다. `schedule` 함수가 호출된 후에 새로운 task가 선택되며, 따라서 시스템 콜은 리턴하지 않게 된다. ### Conclusion 이제 RPi OS는 사용자 스택을 관리할 수 있고, 완전한 프로세스 격리에 훨씬 더 가까워졌다. 하지만 한 가지 중요한 단계가 남아있다. 모든 사용자 task는 동일한 물리 메모리를 공유하여 서로의 데이터를 쉽게 읽을 수 있어야 한다. 다음 lesson에서 가상 메모리를 구현하여 이 문제를 해결해보자. ##### Previous Page 4.5 [Process scheduler: Exercises](../../docs/lesson04/exercises.md) ##### Next Page 5.2 [User processes and system calls: Linux](../../docs/lesson05/linux.md) ================================================ FILE: translations/zh-cn/Prerequisites.md ================================================ ## 先决条件 ### 1. [Raspberry Pi 3 Model B](https://www.raspberrypi.org/products/raspberry-pi-3-model-b/) 较旧版本的Raspberry Pi无法在本教程中使用, 因为所有课程均设计为使用支持ARMv8架构的64位处理器, 并且该处理器仅在`Raspberry Pi 3`中可用. > 较新的版本, 包括[Raspberry Pi 3型号B +](https://www.raspberrypi.org/products/raspberry-pi-3-model-b-plus/)应该可以正常工作, 尽管尚未对其进行测试. ### 2. [USB to TTL serial cable](https://www.amazon.com/s/ref=nb_sb_noss_2?url=search-alias%3Daps&field-keywords=usb+to+ttl+serial+cable&rh=i%3Aaps%2Ck%3Ausb+to+ttl+serial+cable) 获得串行电缆后, 需要测试连接. 如果您从未做过此事, 我建议您遵循[本指南](https://cdn-learn.adafruit.com/downloads/pdf/adafruits-raspberry-pi-lesson-5-using-a-console-cable.pdf)详细描述了通过串行电缆连接Raspberry PI的过程. 该指南还介绍了如何使用串行电缆为Raspberry Pi供电. RPi OS在这种设置下运行良好, 但是, 在这种情况下, 您需要在插入电缆后立即运行终端仿真器. 检查[这个](https://github.com/s-matyukevich/raspberry-pi-os/issues/2)  问题的细节.. ### 3. [SD card](https://www.raspberrypi.org/documentation/installation/sd-cards.md) with installed [Raspbian OS](https://www.raspberrypi.org/downloads/raspbian/) 我们首先需要Raspbian来测试USB至TTL电缆的连接. 另一个原因是, 安装后它将以正确的方式格式化SD卡. ### 4. Docker 严格来说, Docker不是必需的依赖项, 只是使用Docker构建课程的源代码非常方便, 特别是对于Mac和Windows用户而言. 每节课都有`build.sh`脚本(或Windows用户的`build.bat`)此脚本使用Docker来构建课程的源代码. 可以在[官方docker网站](https://docs.docker.com/engine/installation/)上找到有关如何为您的平台安装docker的说明. 如果出于某些原因要避免使用Docker, 则可以安装[make utility](http://www.math.tau.ac.il/~danha/courses/software1/make-intro.html)以及`aarch64-linux-gnu`工具链. 如果您使用的是Ubuntu, 则只需安装`gcc-aarch64-linux-gnu`和`build-essential`软件包. ================================================ FILE: translations/zh-cn/lesson01/exercises.md ================================================ ## 1.5: 练习 练习是可选的, 尽管我强烈建议您稍微尝试一下源代码. 如果您能够完成任何练习 - 请与他人分享您的源代码. 有关详细信息, 请参见[contribution guide](../Contributions.md). 1. 引入一个常数`baud_rate`, 使用该常数计算必要的Mini UART寄存器值. 确保程序可以使用115200以外的波特率工作. 2. 更改操作系统代码以使用UART设备代替Mini UART. 采用 `BCM2837 ARM Peripherals` 手册弄清楚如何访问UART寄存器以及如何配置GPIO引脚. 3. 尝试使用所有4个处理器内核. 操作系统应打印 `Hello, from processor ` 对于不同核心. 不要忘记为每个内核设置单独的堆栈, 并确保Mini UART仅初始化一次. 您可以结合使用全局变量和 `delay` 同步功能. 4. 改编第01课以在qemu上运行. 查看 [这个](https://github.com/s-matyukevich/raspberry-pi-os/issues/8) issue以供参考. ##### 上一页 1.4 [Kernel 初始化: Linux启动顺序](../../../translations/zh-cn/lesson01/linux/kernel-startup.md) ##### 下一页 2.1 [处理器初始化: RPi OS](../../../translations/zh-cn/lesson02/rpi-os.md) ================================================ FILE: translations/zh-cn/lesson01/linux/build-system.md ================================================ ## 1.3: 内核构建系统 Linux也使用`make`实用程序来构建内核, 尽管`Linux makefile`要复杂得多. 在看`makefile`之前, 让我们学习一些有关Linux构建系统的重要概念, 称为 `kbuild` . ### 一些基本的kbuild概念 * 可以使用kbuild中的变量自定义构建过程. 这些变量在 `Kconfig` 文件中定义. 在这里, 您可以定义变量本身及其默认值. 变量可以具有不同的类型, 包括字符串, 布尔值和整数. 在`Kconfig`文件中, 您还可以定义变量之间的依赖关系(例如, 如果选择了变量X, 则可以隐式选择变量Y). 例如, 您可以看一下 [arm64 Kconfig文件](https://github.com/torvalds/linux/tree/v4.14/arch/arm64/Kconfig). 该文件定义了所有变量, 特定于`arm64`体系结构. Kconfig功能不是标准`make`的一部分, 而是在`Linux makefile`中实现的. 在 `Kconfig` 中定义的变量公开给内核源代码以及嵌套的`makefile`. 可以在内核配置步骤中设置变量值(例如, 如果键入`make menuconfig`, 则会显示一个控制台`GUI`. 它允许自定义所有内核变量的值并将这些值存储在`.config`中 ,`help` 命令以查看所有可能的选项来配置内核) * Linux使用递归构建. 这意味着Linux内核的每个子文件夹都可以定义它自己的`Makefile`和`Kconfig`. 大多数嵌套`Makefile`文件都非常简单, 只是定义文件需要编译什么对象. 通常情况下, 这样的定义具有以下格式. ``` obj-$(SOME_CONFIG_VARIABLE) += some_file.o ``` 这个定义意味着`some_file.c`只会在设置`SOME_CONFIG_VARIABLE`的情况下编译并链接到内核. 如果要无条件编译和链接文件, 则需要更改以前的定义, 如下所示. ``` obj-y += some_file.o ``` 可以找到嵌套的Makefile的示例在[这](https://github.com/torvalds/linux/tree/v4.14/kernel/Makefile). * 在继续前进之前, 您需要了解基本make规则的结构并熟悉make术语. 下图说明了通用规则结构. ``` targets : prerequisites recipe … ``` * `targets` 是文件名, 用空格分隔. 执行规则后将生成目标文件. 通常, 每个规则只有一个目 * `prerequisites` 是`make`跟踪的文件, 以查看是否需要更新目标文件. * `recipe` 是一个`bash`脚本. 在某些`prerequisites`更新后进行调用. `recipe`负责生成目标文件. * `targets` 和 `Prerequsites` 都可以包含通配符(%). 使用通配符时, 将分别针对每个已实现的`prerequisites`执行`recipe`. 在这种情况下, 可以使用`$ <`和`$ @`变量来引用`recipe`中的`prerequisites`和`targets`. 我们已经在 [RPi OS makefile](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson01/Makefile#L14) 中完成了此操作.   有关制作规则的其他信息, 请参考 [官方文档](https://www.gnu.org/software/make/manual/html_node/Rule-Syntax.html#Rule-Syntax). * `make` 可以很好地检测是否已更改任何`prerequisites`, 并且仅更新需要重建的目标文件. 但是, 如果`recipe`是动态更新的, 则 `make` 将无法检测到此更改. 怎么会这样? 非常简单. 一个很好的例子是, 当您更改一些配置变量时, 这会导致在`recipe`中附加一个附加选项. 默认情况下, `make`将不会重新编译以前生成的目标文件, 因为它们的`prerequisites`没有改变, 只有`recipe`已更新. 为了应对此行为, Linux引入了[if_changed](https://github.com/torvalds/linux/blob/v4.14/scripts/Kbuild.include#L264) 函数. 要查看其工作原理, 请考虑以下示例. ``` cmd_compile = gcc $(flags) -o $@ $< %.o: %.c FORCE $(call if_changed,compile) ``` 在这里对于每个`.c`文件, 我们通过使用带有`compile`参数的`if_changed`函数来构建相应的`.o`文件. 然后, `if_changed`查找`cmd_compile`变量(在第一个参数后添加`cmd_`前缀), 并检查此变量自上次执行以来是否已更新, 或者任何`prerequisites`已更改. 如果是, 则执行 `cmd_compile` 命令并重新生成目标文件. 我们的示例规则有两个`prerequisites`源 `.c`文件和`.0` 文件. `FORCE` 是一个特殊的`prerequisites`, 它强制每次调用 `make` 命令时都调用`recipe`. 没有它, 只有在`.c`文件被更改的情况下才会调用配方. 您可以阅读有关 `FORCE`目标的更多信息在[这](https://www.gnu.org/software/make/manual/html_node/Force-Targets.html). ### 构建内核 现在, 当我们了解了有关Linux构建系统的一些重要概念时, 让我们尝试弄清楚在键入`make`命令后到底发生了什么. 这个过程非常复杂, 并且包含很多细节, 我们将跳过其中的大多数细节. 我们的目标是回答2个问题. 1. 源文件如何精确地编译为目标文件? 2. 目标文件如何链接到OS映像中? 我们将首先解决第二个问题. #### 链接阶段 * 您可能会从`make help`命令的输出中看到, 负责构建内核的默认目标称为`vmlinux`. * `vmlinux` 目标定义可以在 [这](https://github.com/torvalds/linux/blob/v4.14/Makefile#L1004) 找到, 并且看起来像这样 ``` cmd_link-vmlinux = \ $(CONFIG_SHELL) $< $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux) ; \ $(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true) vmlinux: scripts/link-vmlinux.sh vmlinux_prereq $(vmlinux-deps) FORCE +$(call if_changed,link-vmlinux) ``` 这个目标使用我们已经熟悉的`if_changed`函数. 每当某些先决条件被更新时, 将执行 `cmd_link-vmlinux` 命令. 此命令执行 [scripts /link-vmlinux.sh](https://github.com/torvalds/linux/blob/v4.14/scripts/link-vmlinux.sh) 脚本(注意[$<](https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html) 为 `cmd_link-vmlinux` 命令中的自动变量). 它还执行特定于体系结构的 [postlink脚本](https://github.com/torvalds/linux/blob/v4.14/Documentation/kbuild/makefiles.txt#L1229), 但我们对此并不十分感兴趣. * 当执行 [scripts /link-vmlinux.sh](https://github.com/torvalds/linux/blob/v4.14/scripts/link-vmlinux.sh) 时, 它假定所有必需的目标文件均已构建并且它们的位置存储在3个变量中:`KBUILD_VMLINUX_INIT`, `KBUILD_VMLINUX_MAIN`和`KBUILD_VMLINUX_LIBS`. * `link-vmlinux.sh` 脚本首先创建 `thin archive` 从所有可用的目标文件. `thin archive` 是一个特殊的对象, 其中包含对一组目标文件及其组合符号表的引用. 这是在[archive_builtin](https://github.com/torvalds/linux/blob/v4.14/scripts/link-vmlinux.sh#L56) 函数内部完成的. 为了创建`thin archive` 使用了 [ar](https://sourceware.org/binutils/docs/binutils/ar.html) 实现. 产生的 `thin archive` 被储存在 `built-in.o` 文件中并具有链接器可以理解的格式, 因此它可以用作任何其他普通目标文件. * 下一个 [modpost_link](https://github.com/torvalds/linux/blob/v4.14/scripts/link-vmlinux.sh#L69) 被调用. 该函数调用链接器并生成`vmlinux.o`目标文件. 我们需要这个目标文件来执行[断面失配分析](https://github.com/torvalds/linux/blob/v4.14/lib/Kconfig.debug#L308). 该分析由 [modpost](https://github.com/torvalds/linux/tree/v4.14/scripts/mod) 程序组成并被触发在 [这](https://github.com/torvalds/linux/blob/v4.14/scripts/link-vmlinux.sh#L260) 行. * 下一个生成内核符号表. 它包含有关所有函数和全局变量的信息, 以及它们在`vmlinux`二进制文件中的位置. 主要工作在 [kallsyms](https://github.com/torvalds/linux/blob/v4.14/scripts/link-vmlinux.sh#L146) 函数内部完成. 该函数首先使用 [nm](https://sourceware.org/binutils/docs/binutils/nm.html) 从 `vmlinux` 二进制文件中提取所有符号. 然后, 它使用 [scripts /kallsyms](https://github.com/torvalds/linux/blob/v4.14/scripts/kallsyms.c) 实用程序生成一个特殊的汇编器文件, 其中包含所有格式特殊的符号, 可以被理解 `Linux`内核. 接下来, 将编译此汇编器文件并将其与原始二进制文件链接在一起. 因为可以更改某些符号的最终链接地址, 所以此过程重复了几次. 来自内核符号表的信息用于在运行时生成 `/proc /kallsyms` 文件. * 最后, `vmlinux`二进制文件已经准备好, 并且`System.map`已经构建. `System.map` 包含与 `/proc/kallsyms` 相同的信息, 但这是静态文件与 `/proc/kallsyms` 的不同, 它不是在运行时生成的. `System.map`主要用于在[kernel oops](https://en.wikipedia.org/wiki/Linux_kernel_oops) 期间将地址解析为符号名称. 相同的`nm`实用程序用于构建`System.map`. 这个完成在 [这](https://github.com/torvalds/linux/blob/v4.14/scripts/mksysmap#L44). #### 建立阶段 * 现在让我们向后退一步, 检查源代码文件如何编译为目标文件. 您可能还记得, `vmlinux`目标的先决条件之一是 `$(vmlinux-deps)` 变量. 现在, 让我从Linux `主makefile` 中复制一些相关的行, 以演示如何构建此变量. ```makefile init-y := init/ drivers-y := drivers/ sound/ firmware/ net-y := net/ libs-y := lib/ core-y := usr/ core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/ init-y := $(patsubst %/, %/built-in.o, $(init-y)) core-y := $(patsubst %/, %/built-in.o, $(core-y)) drivers-y := $(patsubst %/, %/built-in.o, $(drivers-y)) net-y := $(patsubst %/, %/built-in.o, $(net-y)) export KBUILD_VMLINUX_INIT := $(head-y) $(init-y) export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y2) $(drivers-y) $(net-y) $(virt-y) export KBUILD_VMLINUX_LIBS := $(libs-y1) export KBUILD_LDS := arch/$(SRCARCH)/kernel/vmlinux.lds vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN) $(KBUILD_VMLINUX_LIBS) ``` 所有这些都以变量 `init-y`, `core-y`等开头. 它们组合在一起, 包含Linux内核的所有子文件夹, 这些子文件夹包含可构建的源代码. 然后, 在所有子文件夹名称后附加 `built-in.o`, 因此, 例如, `drivers/` 成为 `drivers/built-in.o`. 然后, `vmlinux-deps`会汇总所有结果值. 这解释了`vmlinux`最终如何依赖于所有`build-in.o`文件. * 下一个问题是如何创建所有`built-in.o`对象?再一次, 让我复制所有相关的行, 并说明其工作原理. ```makefile $(sort $(vmlinux-deps)): $(vmlinux-dirs) ; vmlinux-dirs := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \ $(core-y) $(core-m) $(drivers-y) $(drivers-m) \ $(net-y) $(net-m) $(libs-y) $(libs-m) $(virt-y))) build := -f $(srctree)/scripts/Makefile.build obj #Copied from `scripts/Kbuild.include` $(vmlinux-dirs): prepare scripts $(Q)$(MAKE) $(build)=$@ ``` 第一行告诉我们`vmlinux-deps`依赖于`vmlinux-dirs`. 接下来, 我们可以看到`vmlinux-dirs`是一个变量, 它包含所有直接的根子文件夹, 末尾没有`/`字符. 最重要的一行是构建 `$(vmlinux-dirs)` 目标的方法. 替换所有变量后, 此配方如下所示(我们以`drivers`文件夹为例, 但是将对所有根子文件夹执行此规则) ``` make -f scripts/Makefile.build obj=drivers ``` 这行仅调用另一个makefile ([scripts/Makefile.build](https://github.com/torvalds/linux/blob/v4.14/scripts/Makefile.build)) 并传递 `obj`变量, 该变量包含要编译的文件夹. * 接下来的逻辑步骤是查看 [scripts /Makefile.build](https://github.com/torvalds/linux/blob/v4.14/scripts/Makefile.build). 执行之后发生的第一件事是, 包括当前目录中定义的`Makefile`或`Kbuild`文件中的所有变量. 当前目录是指`obj`变量引用的目录. 包含在[以下3行](https://github.com/torvalds/linux/blob/v4.14/scripts/Makefile.build#L43-L45)中. ```makefile kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src)) kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile) include $(kbuild-file) ``` 嵌套的makefile主要负责初始化诸如`obj-y`之类的变量. 快速提醒:`obj-y`变量应包含位于当前目录中的所有源代码文件的列表. 嵌套的`makefile`初始化的另一个重要变量是`subdir-y`. 此变量包含在构建`curent`目录中的源代码之前需要访问的所有子文件夹的列表. `subdir-y`用于实现递归递减到子文件夹. * 在不指定目标的情况下调用 `make` 时(例如在执行 `scripts /Makefile.build` 的情况下), 它将使用第一个目标. `scripts /Makefile.build` 的第一个目标称为 `__build`, 可以在[此处](https://github.com/torvalds/linux/blob/v4.14/scripts/Makefile.build#L96) 找到让我们来看一下. ```makefile __build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \ $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \ $(subdir-ym) $(always) @: ``` 如您所见, `__build` 目标没有收据, 但取决于其他目标. 我们只对`$(builtin-target)`感兴趣-它负责创建`built-in.o`文件, 而`$ {subdir-ym)`- 负责下降到嵌套目录. * 让我们看一下`subdir-ym`. 此变量在[此处](https://github.com/torvalds/linux/blob/v4.14/scripts/Makefile.lib#L48) 进行了初始化, 并且只是`subdir-y`和`subdir-m`个变量. (`subdir-m`变量类似于`subdir-y`, 但它定义了子文件夹需要包含在单独的[kernel模块](https://en.wikipedia.org/wiki/Loadable_kernel_module)中. 我们跳过了讨论模块, 以保持专注. ) * `subdir-ym` 目标是在[此处](https://github.com/torvalds/linux/blob/v4.14/scripts/Makefile.build#L572) 定义的, 应该看起来很熟悉. ``` $(subdir-ym): $(Q)$(MAKE) $(build)=$@ ``` 这个目标只是触发嵌套子文件夹之一中的 `scripts/Makefile.build` 的执行. * 现在是时候检查[内置目标](https://github.com/torvalds/linux/blob/v4.14/scripts/Makefile.build#L467) 目标了. 我再次在这里只复制相关行. ```makefile cmd_make_builtin = rm -f $@; $(AR) rcSTP$(KBUILD_ARFLAGS) cmd_make_empty_builtin = rm -f $@; $(AR) rcSTP$(KBUILD_ARFLAGS) cmd_link_o_target = $(if $(strip $(obj-y)),\ $(cmd_make_builtin) $@ $(filter $(obj-y), $^) \ $(cmd_secanalysis),\ $(cmd_make_empty_builtin) $@) $(builtin-target): $(obj-y) FORCE $(call if_changed,link_o_target) ``` 该目标取决于 `$(obj-y)`目标, 而`obj-y`是需要在当前文件夹中构建的所有目标文件的列表. 这些文件准备就绪后, 将执行 `cmd_link_o_target` 命令. 如果 `obj-y` 变量为空, 则调用 `cmd_make_empty_builtin` , 直到创建一个空的 `Built-in.o`. 否则, 执行 `cmd_make_builtin` 命令;它使用我们熟悉的 `ar` 工具创建 `built-in.o` 精简档案. * 最终我们到达需要编译一些东西的地步. 您还记得我们最后一个未开发的依赖项是 `$(obj-y)` 和 `obj-y` 只是目标文件列表. [此处](https://github.com/torvalds/linux/blob/v4.14/scripts/Makefile.build#L313) 定义了从相应的`.c`文件编译所有目标文件的目标. 让我们检查理解该目标所需的所有行. ```makefile cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $< define rule_cc_o_c $(call echo-cmd,checksrc) $(cmd_checksrc) \ $(call cmd_and_fixdep,cc_o_c) \ $(cmd_modversions_c) \ $(call echo-cmd,objtool) $(cmd_objtool) \ $(call echo-cmd,record_mcount) $(cmd_record_mcount) endef $(obj)/%.o: $(src)/%.c $(recordmcount_source) $(objtool_dep) FORCE $(call cmd,force_checksrc) $(call if_changed_rule,cc_o_c) ``` 在`recipe`内部, 此`target`称为 `rule_cc_o_c`. 这个规则负责很多事情, 像检查源代码中的一些常见错误(`cmd_checksrc`), 为导出的模块符号启用版本控制 (`cmd_modversions_c`), 使用 [objtool](https://github.com/torvalds/linux/tree/v4.14/tools/objtool) 验证生成的目标文件的某些方面, 并构建对`mcount`函数的调用列表以便 [ftrace](https://github.com/torvalds/linux/blob/v4.14/Documentation/trace/ftrace.txt) 可以很快找到他们. 但是最重​​要的是, 它调用`cmd_cc_o_c`命令, 该命令实际上将所有`.c`文件编译为目标文件. ### 结论 哇, 在内核构建系统内部进行漫长的旅程!尽管如此, 我们还是跳过了许多细节, 对于那些想了解更多有关该主题的人, 我建议阅读以下[document](https://github.com/torvalds/linux/blob/v4.14/Documentation/kbuild/makefiles.txt), 然后继续阅读`Makefiles`源代码. 现在让我强调重要的几点, 您应该将这些要点理解为本章的内容. 1. `.c` 文件如何被编译为目标文件. 2. 如何将目标文件合并到 `build-in.o` 文件中. 3. 递归构建如何拾取所有子`build-in.o`文件并将它们组合成一个文件. 4. `vmlinux` 是如何与所有顶级`build-in.o`文件链接的. 我的主要目标是, 阅读本章后, 您将对上述所有点有一个大致的了解. ##### 上一页 1.2 [Kernel 初始化: Linux 项目结构](./project-structure.md) ##### 下一页 1.4 [Kernel 初始化: Linux 启动顺序](./kernel-startup.md) ================================================ FILE: translations/zh-cn/lesson01/linux/kernel-startup.md ================================================ ## 1.4:Linux启动顺序 ### 搜索入口点 看完`Linux`项目结构并研究了如何构建之后, 下一个逻辑步骤就是找到程序入口点. 对于许多程序而言, 此步骤可能是微不足道的, 但对于Linux内核而言却并非如此. 我们要做的第一件事是看一下[arm64链接描述文件](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/vmlinux.lds.S). 我们已经看到了链接脚本 [如何在主makefile中使用](https://github.com/torvalds/linux/blob/v4.14/Makefile#L970). 从这一行, 我们可以轻松推断出可以找到特定体系结构的链接脚本的位置. 应该提到的是, 我们将要检查的文件不是实际的链接描述文件, 而是一个模板, 通过使用一些模板的实际值替换一些宏, 可以从中构建实际的链接描述文件. 但是正是因为该文件主要由宏组成, 所以读取和在不同体系结构之间移植变得更加容易. 我们在链接描述文件中可以找到的第一部分称为 [.head.text](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/vmlinux.lds.S#L96). 这对我们非常重要, 因为应该在本节中定义入口点. 如果您仔细考虑一下, 那么它是很完美的意思:在加载内核之后, 将二进制映像的内容复制到某个内存区域, 然后从该区域的开头开始执行. 这意味着仅仅通过搜索谁使用`.head.text`节, 我们就可以找到入口点. 实际上, `arm64` 架构只有一个文件 [head.S](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/head.S), 该文件会使用 [__HEAD](https://github.com/torvalds/linux/blob/v4.14/include/linux/init.h#L90) 宏, `_HEAD` 宏在编译前的预处理阶段会被替换为 `.section ".head.text","ax"`. 我们可以在`head.S`文件中找到的第一行可执行文件是 [这里](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/head.S#L85). 这里使用 arm 的 `b(branch)` 指令指示处理器跳转到 [stext](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/head.S#L116) 函数. 这是启动内核后执行的第一个函数. 逻辑上, 下一步我们应该继续探索 `stext` 函数内部正在发生的事情-但是我们还没有准备好. 首先, 我们必须在`RPi OS`中实现类似的功能, 这将在接下来的几课中介绍. 我们现在要做的是研究一些与内核引导有关的关键概念. ### Linux引导程序和引导协议 当linux内核启动时, 它假定机器硬件已准备成某种 `已知状态`. 定义此状态的规则集称为`启动协议`, 对于 `arm64` 体系结构, 此文档记录在[这里](https://github.com/torvalds/linux/blob/v4.14/Documentation/arm64/booting.txt). 例如, 它定义了执行只能在主CPU上开始, 必须关闭内存映射单元, 并且必须禁用所有中断. 但是由谁负责使计算机进入已知状态?通常, 有一个特殊的程序在内核之前运行并执行所有初始化. 这个程序叫做引导程序(`bootloader`). `Bootloader` 的代码是与计算机硬件相匹配的特定代码, `Raspberry PI`也有自己特定的 `bootloader` 代码. Raspberry PI 的引导程序内置在主板中. 我们只能使用 [config.txt](https://www.raspberrypi.org/documentation/configuration/config-txt/) 文件来自定义其行为. ### UEFI引导 除了 `Bootloader` 外, 还可以在内核映像中内置一个引导加载程序. 此引导加载程序只能在支持 [Unified Extensible Firmware Interface(UEFI)](https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface) 的平台上使用. 支持UEFI的设备为正在运行的软件提供了一组标准化服务, 这些服务可用于找出有关机器本身及其功能的所有必要信息. UEFI 还要求计算机固件应能够以 [Portable Executable(PE)](https://en.wikipedia.org/wiki/Portable_Executable) 格式运行可执行文件. Linux 内核的 UEFI 引导加载程序就利用了此功能:它在 Linux 内核映像的开头注入 `PE` 标准头部格式信息, 以便计算机固件认为该映像是普通的 `PE` 文件. [efi-header.S](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/efi-header.S) 文件中就完成了这个操作. 该文件定义了 [__EFI_PE_HEADER](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/efi-header.S#L13) 宏, 该宏在 [head.S](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/head.S#L98) 内部使用. 在 `__EFI_PE_HEADER` 内部定义的一项重要属性是用于说明 [UEFI入口点](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/efi-header.S#L33) 的属性和入口点本身(可以在 [efi-entry.S](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/efi-entry.S#L32) 中找到). 从此位置开始, 您可以遵循源代码并检查 UEFI 引导加载程序到底在做什么(源代码本身或多或少简单明了). 但是我们将在此处停止, 因为本节的目的不是详细检查 UEFI 引导程序, 而是让您大致了解 UEFI 是什么以及 Linux 内核如何使用它. ### 设备树 当我开始研究 Linux 内核的启动代码时, 发现了很多关于设备树(`device tree`) 的内容. 它似乎是一个必不可少的概念, 我认为有必要对其进行讨论. 当我们在开发 `Raspberry PI OS` 内核时, 我们从 [BCM2837 ARM Peripherals手册](https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf) 中找出了一个特定的内存映射寄存器在物理内存上的确切偏移量. 显然每个主板的偏移量信息都是不同的, 我们很幸运, 我们只需要支持 BCM2873 这一个. 但是, 如果我们需要支持数百个不同的主板怎么办?如果我们尝试在内核代码中硬编码每个主板的信息, 那将是一团糟. 即使我们设法做到这一点, 我们又如何确定当前使用的是哪种主板?例如, `BCM2837` 没有提供任何将此类信息传递给正在运行的内核的方法. 设备树为我们提供了解决上述问题的方法. 设备树一种特殊格式, 可用于描述计算机硬件. 可以在[device tree org](https://www.devicetree.org/)中找到设备树规范. 在执行内核之前, 引导程序会选择适当的设备树文件, 并将其作为参数传递给内核. 如果您查看 `Raspberry PI` SD 卡的引导分区(boot分区)中的文件, 会找到很多 `.dtb` 文件. `.dtb`是编译之后的设备树文件. 可以在 `config.txt` 中配置来启用或禁用 `Raspberry PI` 的硬件. [Raspberry PI官方文档](https://www.raspberrypi.org/documentation/configuration/device-tree.md) 中更详细地描述了此过程. 好的, 现在该看一下实际的设备树的文件了. 作为快速练习, 让我们尝试为[Raspberry PI 3 Model B](https://www.raspberrypi.org/products/raspberry-pi-3-model-b/)查找设备树. 从 [文档](https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2837/README.md) 中, 我们可以得出结论, `Raspberry PI 3 Model B` 使用的芯片名为 `BCM2837`. 如果你搜索此名称, 则可以找到 [/arch/arm64/boot/dts/broadcom/bcm2837-rpi-3-b.dts](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/boot/dts/broadcom/bcm2837-rpi-3-b.dts) 文件. 如您所见, 它只是包含了`arm` 体系下的相同文件. 这很合理, 因为 ARM.v8 处理器也支持32位模式. 接下来, 我们可以找到 [bcm2837-rpi-3-b.dts](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837-rpi-3-b.dts) 属于 [arm](https://github.com/torvalds/linux/tree/v4.14/arch/arm) 架构. 我们已经看到设备树文件可以包含在另一个文件中. `bcm2837-rpi-3-b.dts` 就是这种情况 - 它仅包含特定于 `BCM2837`的那些定义, 并重用其他所有内容. 例如, `bcm2837-rpi-3-b.dts` 指定[设备应该具有 1GB 的内存](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837-rpi-3-b.dts#L18). 正如我之前提到的, `BCM2837` 和 `BCM2835` 具有相同的外围硬件, 并且, 如果追寻包含链, 您可以找到 [bcm283x.dtsi](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm283x.dtsi) 实际上定义了大多数此类硬件. 设备树由彼此嵌套的块组成. 在顶层, 我们通常可以找到诸如 [cpus](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837.dtsi#L30) 或 [memory](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837-rpi-3-b.dts#L17), 块的含义通过块的节点名称也能简明的看出. 我们可以在 `bcm283x.dtsi` 中找到的另一个有趣的顶级节点是 [SoC](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/boot/dts/bcm283x.dtsi#L52) 的意思是 [System on chip](https://en.wikipedia.org/wiki/System_on_a_chip) . 它告诉我们所有外围设备都通过*内存映射寄存器*直接映射到某个内存区域. `soc` 节点用作所有外围设备的父节点. 它的子节点之一是 [gpio](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm283x.dtsi#L147) 节点. 此节点定义 [reg = <0x7e200000 0xb4>](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm283x.dtsi#L149) 属性, 该属性告诉我们 GPIO 的内存映射寄存器位于 `0x7e200000 ~ 0x7e2000b4` 区域. gpio 节点的子节点之一具有[以下定义](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm283x.dtsi#L351) ``` uart1_gpio14:uart1_gpio14 { brcm, pins = <14 15>; brcm, function = ; }; ``` 这个定义告诉我们, 如果为引脚14和15选择了替代功能5, 则这些引脚将连接到 `uart1` 设备. 你可以很容易地猜出 `uart1` 设备就是我们已经使用过的`Mini UART`. 你需要了解的有关设备树的另外重要的一件事是:设备树格式是可扩展的. 每个设备可以定义自己的属性和嵌套块. 这些属性透明地传递给设备驱动程序, 解释它们是驱动程序的职责. 但是内核如何找出设备树中的块与正确的驱动程序之间的对应关系?内核使用 `compatible` 属性来做到这一点. 例如, 对于 `uart1` 设备, `compatible` 属性是这样指定的 ``` compatible = "brcm,bcm2835-aux-uart"; ``` 实际上, 如果您在Linux源代码中搜索 `bcm2835-aux-uart`, 则可以找到匹配的驱动程序, 该驱动程序定义在 [8250_bcm2835aux.c](https://github.com/torvalds/linux/blob/v4.14/drivers/tty/serial/8250/8250_bcm2835aux.c) ### 结论 你可以考虑将本章作为阅读 `arm64` 引导代码的准备 - 在没有了解我们刚刚探讨的概念的情况下, 你将很难学习它. 在下一课中, 我们将返回到 `stext` 函数, 并详细研究其工作方式. #### 上一页 1.3 [内核初始化:内核构建系统](./build-system.md) #### 下一页 1.5 [内核初始化:练习](../exercises.md) ================================================ FILE: translations/zh-cn/lesson01/linux/project-structure.md ================================================ ## 1.2:Linux项目结构 这是我们第一次谈论Linux. 这个想法是首先完成编写我们自己的内核的一小步, 然后看一下相同的东西在Linux中的工作方式. 到目前为止, 我们仍然做得很少:仅实施了我们的第一个裸机`hello world` 程序, 我们仍然能够发现RPi OS和Linux之间的某些相似之处. 现在我们将探索其中的一些. ### 项目结构 每当您开始研究任何大型软件项目时, 都需要快速浏览一下项目结构. 这非常重要, 因为它使您能够了解由哪些模块组成项目以及什么是高级体系结构. 让我们尝试探索Linux内核的项目结构. 首先, 您需要克隆Linux系统信息库. ``` git clone https://github.com/torvalds/linux.git cd Linux git checkout v4.14 ``` 我们使用的是`v4.14`版本, 因为这是撰写本文时的最新版本. 使用此特定版本将对Linux源代码进行所有引用. 接下来, 让我们看一下我们可以在Linux系统信息库中找到的文件夹. 我们不会研究所有, 只会研究我认为最重要的那些. * [arch](https://github.com/torvalds/linux/tree/v4.14/arch) 此文件夹包含子文件夹, 每个子文件夹用于特定的处理器体系结构. 通常, 我们将使用 [arm64](https://github.com/torvalds/linux/tree/v4.14/arch/arm64) - 这是与 ARM.v8 处理器兼容的版本. * [init](https://github.com/torvalds/linux/tree/v4.14/init) 内核始终由体系结构特定的代码引导. 但是随后就会将执行传递给 [start_kernel](https://github.com/torvalds/linux/blob/v4.14/init/main.c#L509) 函数, 该函数负责常见的内核初始化同时该函数也是与体系结构无关的内核起点. 在`start`文件夹中定义了`start_kernel`函数以及一些其他初始化函数. * [kernel](https://github.com/torvalds/linux/tree/v4.14/kernel) 这是Linux内核的核心. 几乎所有主要的内核子系统都在此实现. * [mm](https://github.com/torvalds/linux/tree/v4.14/mm) 在此处定义了与内存管理(`Memory mangement`)相关的所有数据结构和方法. * [drivers](https://github.com/torvalds/linux/tree/v4.14/drivers) 这是Linux内核中最大的文件夹. 它包含所有设备驱动程序的实现. * [fs](https://github.com/torvalds/linux/tree/v4.14/fs) 您可以在此处查找不同的文件系统实现. 这种解释是非常高层次的, 但是到目前为止已经足够了. 在下一章中, 我们将尝试更详细地研究Linux构建系统. #### 上一页 1.1 [内核初始化:引入RPi OS或裸机 “Hello, world!”](../../../docs/lesson01/rpi-os.md) #### 下一页 1.3 [内核初始化:内核构建系统](../linux/build-system.md) ================================================ FILE: translations/zh-cn/lesson01/rpi-os.md ================================================ ## 1.1: 引入RPi OS或裸机 “Hello, World!” 我们将通过编写一个小的裸机“ Hello, World”应用程序开始我们的OS开发之旅. 我假设您已通过[Prerequisites](../Prerequisites.md)并已准备就绪. 如果没有, 现在是时候这样做了. 在我们前进之前, 我想建立一个简单的命名约定. 从README文件中, 您可以看到整个教程分为几节课. 每节课都由我称为“章节”的单独文件组成(现在, 您正在阅读第1课, 第1.1章). 一章进一步分为带有标题的“部分”. 这种命名约定使我可以引用材料的不同部分. 我想让您注意的另一件事是, 该教程包含许多源代码示例. 我通常会通过提供完整的代码块来开始说明, 然后逐行描述它. ### 项目结构 每节课的源代码具有相同的结构. 您可以在[此处](https://github.com/s-matyukevich/raspberry-pi-os/tree/master/src/lesson01)找到本课程的源代码. 让我们简要描述此文件夹的主要组件: 1. **Makefile** 我们将使用[make](http://www.math.tau.ac.il/~danha/courses/software1/make-intro.html)实用程序来构建内核. make的行为由Makefile配置, 该文件包含有关如何编译和链接源代码的说明. 2. **build.sh or build.bat** 如果要使用Docker构建内核, 则需要这些文件. 您无需在笔记本电脑上安装make实用程序或编译器工具链. 3. **src** 此文件夹包含所有源代码. 4. **include** 所有的头文件都放在这里. ### Makefile 现在, 让我们仔细看看项目Makefile. make程序的主要目的是自动确定需要重新编译什么程序的片段, 并发出命令来重新编译. 如果你不熟悉的Make和Makefile文件, 我建议你阅读[这](http://opensourceforu.com/2012/06/gnu-make-in-detail-for-beginners/)的文章. 第一课中使用的Makefile可以在[here](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson01/Makefile)中找到. 下面列出了整个Makefile: ``` ARMGNU ?= aarch64-linux-gnu COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude BUILD_DIR = build SRC_DIR = src all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) kernel8.img: $(SRC_DIR)/linker.ld $(OBJ_FILES) $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o $(BUILD_DIR)/kernel8.elf $(OBJ_FILES) $(ARMGNU)-objcopy $(BUILD_DIR)/kernel8.elf -O binary kernel8.img ``` 现在, 让我们详细阐述该文件: ``` ARMGNU ?= aarch64-linux-gnu ``` Makefile以变量定义开头. `ARMGNU` 是交叉编译器前缀. 因为我们正在`x86`计算机上编译`arm64`体系结构的源代码, 我们需要 [交叉编译](https://en.wikipedia.org/wiki/Cross_compiler). 因此, 我们将使用 `aarch64-linux-gnu-gcc` 代替 `gcc`. ``` COPS = -Wall -nostdlib -nostartfiles -ffreestanding -Iinclude -mgeneral-regs-only ASMOPS = -Iinclude ``` `COPS`和`ASMOPS`是在编译C和汇编代码时分别传递给编译器的选项. 这些选项的简短说明: * **-Wall** 显示所有警告. * **-nostdlib** 不使用C的标准库. C标准库中的大多数函数调用最终都会与操作系统交互. 我们正在编写一个裸机程序, 并且我们没有任何底层操作系统, 因此C标准库无论如何都无法为我们工作. * **-nostartfiles** 不要使用标准的启动文件. 启动文件负责设置初始堆栈指针, 初始化静态数据以及跳转到主入口点. 我们将自己完成所有这一切. * **-ffreestanding** 独立环境(`ffreestanding`)是标准库不存在的环境, 并且程序启动入口不是主要的. 选项`-ffreestanding`指示编译器不需要定义标准函数具有其通常的意义. * **-Iinclude** 在 `include` 文件夹中搜索头文件. * **-mgeneral-regs-only** 仅使用通用寄存器. ARM处理器还具有[NEON](https://developer.arm.com/technologies/neon)寄存器. 我们不希望编译器使用它们, 因为它们会增加额外的复杂性(例如, 因为我们需要在上下文切换期间存储寄存器). ``` BUILD_DIR = build SRC_DIR = src ``` `SRC_DIR` 和 `BUILD_DIR` 是分别包含源代码和编译后Object文件的目录. ``` all : kernel8.img clean : rm -rf $(BUILD_DIR) *.img ``` 接下来, 我们定义make目标. 前两个参数非常简单: `all` 参数是默认目标, 每当输入不带任何参数的`make`时, 它就会执行 (`make` 始终使用第一个参数作为默认参数). 这个参数只是将所有工作重定向到另一个参数, `kernel8.img`. `clean` 参数负责删除所有编译附加文件和已编译内核映像. ``` $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c mkdir -p $(@D) $(ARMGNU)-gcc $(COPS) -MMD -c $< -o $@ $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S $(ARMGNU)-gcc $(ASMOPS) -MMD -c $< -o $@ ``` 接下来的两个参数负责编译C和汇编文件. 例如, 如果在 `src` 文件夹中我们有 `foo.c` 和 `foo.S` 文件, 他们将会分别被编译为 `build/foo_c.o` 和 `build/foo_s.o`. `$<` 和 `$@` 在运行时将被替换成输入文件名和输出文件名 (`foo.c` and `foo_c.o`). 在编译C文件之前, 我们还创建了一个 `build` 目录, 以防该目录不存在. ``` C_FILES = $(wildcard $(SRC_DIR)/*.c) ASM_FILES = $(wildcard $(SRC_DIR)/*.S) OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o) OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o) ``` 在这里, 我们正在构建一个由C和汇编源文件的串联创建的目标文件(`OBJ_FILES`)的数组. ``` DEP_FILES = $(OBJ_FILES:%.o=%.d) -include $(DEP_FILES) ``` 接下来的两行有些棘手. 如果看一下我们如何为C和汇编源文件定义编译目标, 您会注意到我们使用了`-MMD`参数. 这个参数指示`gcc`编译器为每个生成的目标文件创建一个依赖文件. 依赖性文件定义了特定源文件的所有依赖性. 这些依赖项通常包含所有包含的头文件的列表. 我们需要包括所有生成的依赖文件, 以便在头文件更改时知道要重新编译的内容. ``` $(ARMGNU)-ld -T $(SRC_DIR)/linker.ld -o kernel8.elf $(OBJ_FILES) ``` 我们使用 `OBJ_FILES` 数组构建 `kernel8.elf` 文件. 我们使用链接器脚本`src /linker.ld`定义生成的可执行映像的基本布局. (我们将在下一部分中讨论链接器脚本). ``` $(ARMGNU)-objcopy kernel8.elf -O binary kernel8.img ``` `kernel8.elf` 遵循 [ELF](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format) 格式. 问题是ELF文件设计为由操作系统执行. 要编写裸机程序, 我们需要从ELF文件中提取所有可执行文件和数据段, 然后将它们放入 `kernel8.img` 中. 尾部的 `8` 表示ARMv8, 它是64位体系结构. 该文件名告诉固件将处理器引导到64位模式. 您也可以使用`config.txt`文件中的`arm_control = 0x200`标志以64位模式引导CPU. RPi OS以前使用此方法, 您仍然可以在一些练习答案中找到它. 然而, `arm_control`标志没有文档, 最好使用`kernel8.img`命名约定. ### The linker script 链接描述文件的主要目的是描述如何将输入目标文件(`_c.o`和`_s.o`)中的段映射到输出文件(`.elf`)中. 可以找到有关链接描述文件的更多信息在[这里](https://sourceware.org/binutils/docs/ld/Scripts.html#Scripts). 现在让我们看一下RPi OS链接器脚本: ``` SECTIONS { .text.boot : { *(.text.boot) } .text : { *(.text) } .rodata : { *(.rodata) } .data : { *(.data) } . = ALIGN(0x8); bss_begin = .; .bss : { *(.bss*) } bss_end = .; } ``` 启动后, Raspberry Pi将`kernel8.img`加载到内存中, 并从文件开头开始执行. 这就是为什么`.text.boot`部分必须放在第一的原因; 我们将把操作系统启动代码放入本节中. `.text`, `.rodata`, 和 `.data` 部分包含内核编译的指令, 只读数据, 和 一般数据 –– 这里没有什么需要特别补充的. `.bss`部分包含应初始化为 `0` 的数据. 通过将此类数据放在单独的部分中, 编译器可以在ELF二进制文件中节省一些空间––只有部分大小存储在ELF标头中, 但本节本身被省略. 将`img`加载到内存后, 我们必须将`.bss`部分初始化为`0`; 这就是为什么我们需要记录本节的开始和结束 (也就是`bss_begin`和`bss_end`符号) 和 对齐该节, 使其以8的倍数开头的地址开始. 如果该部分未对齐, 使用`str`指令在`bss`节的开头存储`0`会更加困难 因为`str`指令只能与8字节对齐的地址一起使用. ### 引导内核 现在是时候看看[boot.S](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson01/src/boot.S)文件了. 此文件包含内核启动代码: ``` #include "mm.h" .section ".text.boot" .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // Check processor id cbz x0, master // Hang for all non-primary CPU b proc_hang proc_hang: b proc_hang master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero mov sp, #LOW_MEMORY bl kernel_main ``` 让我们详细查看该文件: ``` .section ".text.boot" ``` 首先, 我们指定在`boot.S`中的所有内容都应在`.text.boot`部分中. 先前, 我们已经看到, 该节通过链接描述文件放置在内核映像的开头. 因此, 当内核启动时, 执行从`start`函数开始: ``` .globl _start _start: mrs x0, mpidr_el1 and x0, x0,#0xFF // 获取核心ID cbz x0, master // 暂停所有非主核心 b proc_hang ``` 这个函数做的第一件事就是检查 processor id. Raspberry Pi 3 有四个核心, 设备开启之后, 每个核心处理相同的代码. 然而, 我不们不需要四个核心都工作; 我们希望只是用第一个核心并且将其他核心放到尾部的循环中. 这就是 `_start` 函数的做的事情. 从[mpidr_el1](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0500g/BABHBJCI.html) 系统寄存器获取 processor ID. 如果当前 process ID 是 0, 之后执行跳转到 `master` 函数: ``` master: adr x0, bss_begin adr x1, bss_end sub x1, x1, x0 bl memzero ``` 在这里, 我们通过调用`memzero`来清理`.bss`部分. 我们稍后将定义此函数. 在ARMv8架构中, 按照惯例, 前七个参数通过寄存器x0–x6传递给调用的函数. `memzero` 函数仅接受两个参数: 起始地址 (`bss_begin`) 以及需要清理的部分的大小 (`bss_end - bss_begin`). ``` mov sp, #LOW_MEMORY bl kernel_main ``` 清理`.bss`部分后, 我们初始化堆栈指针并将执行传递给`kernel_main`函数. Raspberry Pi在地址0加载内核; 这就是为什么可以将初始堆栈指针设置到足够高的任何位置的原因, 以便当堆栈映像变得足够大时, 堆栈不会覆盖内核映像. `LOW_MEMORY` 在 [mm.h](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson01/include/mm.h) 中定义等于4MB. 我们的内核堆栈不会变得很大, 并且img本身很小, 因此`4MB`对我们来说绰绰有余. 对于那些不熟悉`ARM`汇编器语法的人, 让我快速总结一下我们已经使用的指令: * [**mrs**](http://www.keil.com/support/man/docs/armasm/armasm_dom1361289881374.htm) 将值从系统寄存器加载到通用寄存器之一(x0–x30) * [**and**](http://www.keil.com/support/man/docs/armasm/armasm_dom1361289863017.htm) 执行逻辑与运算. 我们使用此命令从 `mpidr_el1` 寄存器中将获得的值剥离最后一个字节. * [**cbz**](http://www.keil.com/support/man/docs/armasm/armasm_dom1361289867296.htm) 比较之前执行的操作的结果, 并且如果比较结果为真, 就跳转(在ARM术语中也叫做 `branch` ) 到提供的标签. * [**b**](http://www.keil.com/support/man/docs/armasm/armasm_dom1361289863797.htm) 无条件跳转到某个标签(`branch`). * [**adr**](http://www.keil.com/support/man/docs/armasm/armasm_dom1361289862147.htm) 将标签的相对地址加载到目标寄存器中. 在这种情况下, 我们需要指向`.bss`区域开始和结束的指针. * [**sub**](http://www.keil.com/support/man/docs/armasm/armasm_dom1361289908389.htm) 从两个寄存器取值互减. * [**bl**](http://www.keil.com/support/man/docs/armasm/armasm_dom1361289865686.htm) "Branch with a link": 执行无条件分支并将返回地址存储在x30中(链接寄存器). 子程序完成后, 使用`ret`指令跳回到调用人地址. * [**mov**](http://www.keil.com/support/man/docs/armasm/armasm_dom1361289878994.htm) 在寄存器之间移动一个值 或者 从常量移动到寄存器. [这里](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0024a/index.html) 是ARMv8-A开发人员指南. 如果你不熟悉ARM ISA, 这是一个很好的资源. [这个页面](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0024a/ch09s01s01.html) 特别概述了ABI中的寄存器使用约定. ### `kernel_main`函数 我们已经看到引导代码最终将控制权传递给了`kernel_main`函数. 让我们看一下: ``` #include "mini_uart.h" void kernel_main(void) { uart_init(); uart_send_string("Hello, world!\r\n"); while (1) { uart_send(uart_recv()); } } ``` 此功能是内核中最简单的功能之一. 它与 `Mini UART` 设备 打印到屏幕并阅读用户输入. 内核只是打印 `Hello, world!` 然后进入无限循环, 此循环从用户读取字符并将其发送回屏幕. ### Raspberry Pi 设备 现在, 我们将深入研究Raspberry Pi的特定功能. 开始之前, 我建议您下载[BCM2837 ARM Peripherals manual](https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf). `BCM2837` 是 Raspberry Pi 3 Model B 和 B+ 使用的芯片. 在我们讨论中, 我还将提到 `BCM2835` 和 `BCM2836` - 这些是旧版Raspberry Pi中使用的芯片的名称. 在我们进行到细节之前, 我想分享一些有关如何使用内存映射设备的基本概念. BCM2837 是一个简单的 [SOC (System on a chip 片上系统)](https://en.wikipedia.org/wiki/System_on_a_chip) 芯片. 在这样的芯片上, 通过映射到内存的寄存器访问设备. Raspberry Pi 3 保留比 `0x3F000000` 高位的地址用于设备. 去启用或者配置一个特定设备, 你需要写入到设备的一个寄存器中一些数据. 一个设备的寄存器在内存中占据32位. `BCM2837 ARM Peripherals` 手册中描述了寄存器每一位的作用. 在手册和周围的文件中看一下 1.2.3节 ARM 的物理地址和有关更多为什么我们用 `0x3F000000` 作为基地址的背景信息(即使 `0x7E000000` 在整个手册中被使用). 从`kernel_main`函数, 您可以猜测我们将使用Mini UART器件. UART代表 [Universal asynchronous receiver-transmitter 通用异步收发器](https://en.wikipedia.org/wiki/Universal_asynchronous_receiver-transmitter). 该设备能够将存储在其存储器映射寄存器之一中的值转换为高电压和低电压序列. 该序列通过 `TTL转串口电缆` 传递到您的计算机, 并由终端仿真器解释. 我们将使用Mini UART来促进与Raspberry Pi的通信. 如果要查看Mini UART寄存器的规范, 请转到`BCM2837 ARM Peripherals`手册的第8页. Raspberry Pi具有两个UART:迷你UART和PL011 UART. 在本教程中, 我们将仅使用第一个教程, 因为它更简单. 但是, 有一个可选的[exercise](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/docs/lesson01/exercises.md)显示了如何使用PL011 UART. 如果您想了解有关Raspberry Pi UART的更多信息并了解它们之间的区别, 可以参考[官方文档](https://www.raspberrypi.org/documentation/configuration/uart.md). 您需要熟悉的另一个设备是GPIO[General-purpose input/output 通用输入/输出](https://en.wikipedia.org/wiki/General-purpose_input/output). GPIO负责控制GPIO引脚. 您应该能够在下图中轻松识别它们: ![Raspberry Pi GPIO pins](../../images/gpio-pins.jpg) GPIO可用于配置不同GPIO引脚的行为. 例如, 为了能够使用Mini UART, 我们需要激活引脚14和15并将其设置为使用此设备. 下图说明了如何将数字分配给GPIO引脚: ![Raspberry Pi GPIO pin numbers](../../images/gpio-numbers.png) ### 迷你UART初始化 现在让我们看一下如何初始化迷你UART. 该代码在[mini_uart.c](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson01/src/mini_uart.c): ```c void uart_init ( void ) { unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // 把 gpio14 清除 selector |= 2<<12; // 把 gpio14 设置为 模式5 (alt5) selector &= ~(7<<15); // 把 gpio15 清除 selector |= 2<<15; // 把 gpio15 设置为 模式5 (alt5) put32(GPFSEL1,selector); put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); put32(AUX_ENABLES,1); // 启动 mini uart (同时允许写入它的寄存器) put32(AUX_MU_CNTL_REG,0); // 禁用自动流控, 接收器以及发射器 (这是暂时的) put32(AUX_MU_IER_REG,0); // 禁用接受和发送中断 put32(AUX_MU_LCR_REG,3); // 启用 8bit 模式 put32(AUX_MU_MCR_REG,0); // 把 RTS line 设置为永远高电平 put32(AUX_MU_BAUD_REG,270); // 设置比特率为 115200 put32(AUX_MU_CNTL_REG,3); // 最后, 启用发射器和接收器 } ``` 在这里, 我们使用两个函数`put32`和`get32`. 这些功能非常简单; 它们允许我们在32位寄存器中读写数据. 您可以看看它们是如何实现 [utils.S](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson01/src/utils.S). `uart_init` 是本课中最复杂, 最重要的功能之一, 我们将在接下来的三个部分中继续进行研究. #### GPIO 另类功能选择 首先, 我们需要激活GPIO引脚. 大多数引脚可以与不同的设备一起使用, 所以在使用特定的引脚之前, 我们需要选择引脚的`替代功能(alternative function)`. `替代功能`只是可以为每个引脚设置的0到5之间的数字, 并配置将哪个设备连接到该引脚. 您可以在下图中看到所有可用的GPIO替代功能的列表 (该图像取自《 BCM2837 ARM Peripherals》手册的第102页): ![Raspberry Pi GPIO alternative functions](../../images/alt.png?raw=true) 在这里您可以看到引脚14和15具有TXD1和RXD1替代功能. 这意味着如果我们为引脚14和15选择替代功能编号5, 它们将分别用作Mini UART发送数据引脚和Mini UART接收数据引脚. `GPFSEL1` 寄存器用于控制引脚10-19的替代功能. 这些寄存器中所有位的含义如下表所示 (page 92 of `BCM2837 ARM Peripherals` manual): ![Raspberry Pi GPIO function selector](../../images/gpfsel1.png?raw=true) 因此, 现在您知道了您需要了解的以下几行代码, 这些代码用于配置GPIO引脚14和15以与Mini UART器件配合使用: ``` unsigned int selector; selector = get32(GPFSEL1); selector &= ~(7<<12); // 把 gpio14 清除 selector |= 2<<12; // 把 gpio14 设置为 模式5 (alt5) selector &= ~(7<<15); // 把 gpio15 清除 selector |= 2<<15; // 把 gpio15 设置为 模式5 (alt5) put32(GPFSEL1,selector); ``` #### GPIO pull-up/down 当您使用Raspberry Pi GPIO引脚时, 经常会遇到诸如上拉/下拉等术语. 这些概念的详细解释在 [这篇](https://grantwinney.com/using-pullup-and-pulldown-resistors-on-the-raspberry-pi/) 文章中. 对于那些懒于阅读整篇文章的人, 我将简要解释上拉/下拉概念. 如果您使用特定的引脚作为输入, 并且不将该引脚连接任何东西, 则将无法识别该引脚的值是1还是0. 实际上, 设备将报告为随机值. 上拉/下拉机制可解决此问题. 如果将引脚设置为上拉状态, 但没有任何连接, 则引脚将始终报告 `1`(对于下拉状态, 该值始终为`0`). 就我们而言, 我们既不需要上拉状态也不需要下拉状态, 因为14和15引脚将一直保持连接状态. 即使重新启动后, 引脚状态也会保留, 因此在使用任何引脚之前, 我们总是必须初始化其状态. 有三种可用状态: 上拉, 下拉和两者都不显示(以删除当前的上拉或下拉状态), 我们需要第三个. 引脚状态之间的切换不是一个非常简单的过程, 因为它需要物理上触发电路上的一个开关. 该过程涉及`GPPUD`和`GPPUDCLK`寄存器, 并在 `BCM2837 ARM Peripherals` 手册的第101页中进行了描述. 我在这里复制了一段说明: > GPIO上/下时钟寄存器控制相应GPIO引脚上内部下拉的启动. > 这些寄存器必须与GPPUD结合使用 > 寄存器以影响GPIO上拉/下拉更改. > > 需要以下事件顺序: > > 1. 写入GPPUD以设置所需的控制信号(即上拉或下拉, 或都不设置以消除当前的上拉/下拉) > 2. 等待150个周期 – 这为控制信号提供了所需的建立时间 > 3. 写入 GPPUDCLK0/1 以将控制信号输入您要修改的GPIO焊盘 – 注意只有接收时钟的打击垫会被修改, 所有其他人将保持以前的状态. > 4. 等待150个循环 – 这为控制信号提供了所需的保持时间 > 5. 写入GPPUD以删除控制信号 > 6. 写入 GPPUDCLK0/1 以删除时钟 此过程描述了我们如何从引脚上移除上拉和下拉状态 在下面的代码中, 我们对引脚14和15进行操作: ``` put32(GPPUD,0); delay(150); put32(GPPUDCLK0,(1<<14)|(1<<15)); delay(150); put32(GPPUDCLK0,0); ``` #### 初始化Mini UART 现在我们的Mini UART已连接到GPIO引脚, 并且已配置了这些引脚. `uart_init`函数的其余部分专用于Mini UART初始化. ``` put32(AUX_ENABLES,1); // 启动 mini uart (同时允许写入它的寄存器) put32(AUX_MU_CNTL_REG,0); // 禁用自动流控, 接收器以及发射器 (这是暂时的) put32(AUX_MU_IER_REG,0); // 禁用接受和发送中断 put32(AUX_MU_LCR_REG,3); // 启用 8bit 模式 put32(AUX_MU_MCR_REG,0); // 把 RTS line 设置为永远高电平 put32(AUX_MU_BAUD_REG,270); // 设置比特率为 115200 put32(AUX_MU_IIR_REG,6); // Clear FIFO put32(AUX_MU_CNTL_REG,3); // 最后, 启用发射器和接收器 ``` 让我们逐行检查此代码段. ``` put32(AUX_ENABLES,1); // 启动 mini uart (同时允许写入它的寄存器) ``` 这行启动了 Mini UART. 我们必须在一开始就这样做, 因为这样还可以访问所有其他 Mini UART 寄存器. ``` put32(AUX_MU_CNTL_REG,0); // 禁用自动流控, 接收器以及发射器 (这是暂时的) ``` 在这里, 我们在配置完成之前禁用接收器和发送器. 我们还永久禁用自动流控制, 因为它需要我们使用其他GPIO引脚, TTL转串口电缆不支持. 有关自动流控制的更多信息, 你可以参考 [这](http://www.deater.net/weave/vmwprod/hardware/pi-rts/) 篇文章. ``` put32(AUX_MU_IER_REG,0); // 禁用接受和发送中断 ``` 可以配置Mini UART, 以在每次有新数据可用时产生处理器中断. 我们将在第3课中开始处理中断, 所以现在, 我们将禁用此功能. ``` put32(AUX_MU_LCR_REG,3); // 启用 8bit 模式 ``` 迷你UART可以支持7位或8位操作. 这是因为ASCII字符对于标准集是7位, 对于扩展是8位. 我们将使用8位模式. ``` put32(AUX_MU_MCR_REG,0); // 把 RTS line 设置为永远高电平 ``` RTS line 用于流量控制, 我们不需要它. 始终将其设置为高. ``` put32(AUX_MU_BAUD_REG,270); // 设置比特率为 115200 ``` 波特率是在通信信道中传输信息的速率. `115200` 表示该串行端口每秒最多可传输`115200`位. Raspberry Pi微型UART设备的波特率应与终端仿真器中的波特率相同. Mini UART根据以下公式计算波特率: ``` baudrate = system_clock_freq / (8 * ( baudrate_reg + 1 )) ``` `system_clock_freq`是 250 MHz, 这样我们就可以轻松计算出 `baudrate_reg` 应为 270. ``` put32(AUX_MU_CNTL_REG,3); // 最后, 启用发射器和接收器 ``` 执行完此行后, Mini UART准备就绪! ### 使用Mini UART发送数据 Mini UART准备好后, 我们可以尝试使用它来发送和接收一些数据. 为此, 我们可以使用以下两个函数: ``` void uart_send ( char c ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x20) break; } put32(AUX_MU_IO_REG,c); } char uart_recv ( void ) { while(1) { if(get32(AUX_MU_LSR_REG)&0x01) break; } return(get32(AUX_MU_IO_REG)&0xFF); } ``` 这两个函数均以无限循环开始, 其目的是验证设备是否已准备好发送或接收数据. 我们正在使用 `AUX_MU_LSR_REG` 寄存器来执行此操作. 第零位, 如果设置为1, 表示数据已准备就绪; 这意味着我们可以从UART中读取. 第五位, 如果设置为1, 告诉我们发射器是空的, 这意味着我们可以写入UART. 下一个, 我们使用`AUX_MU_IO_REG`来存储已发送字符的值或读取已接收字符的值. 我们还有一个非常简单的功能, 能够发送字符串而不是字符: ``` void uart_send_string(char* str) { for (int i = 0; str[i] != '\0'; i ++) { uart_send((char)str[i]); } } ``` 此函数仅遍历字符串中的所有字符, 然后将它们一一发送. ### Raspberry Pi 配置 Raspberry Pi的启动顺序如下(简化): 1. 设备上电. 2. GPU启动并从启动分区读取`config.txt`文件. 该文件包含一些配置参数, GPU使用这些参数进一步调整启动顺序. 3. `kernel8.img` 被加载到内存中并执行. 为了能够运行我们的简单操作系统, `config.txt`文件应为以下文件: ``` kernel_old=1 disable_commandline_tags=1 ``` * `kernel_old=1` specifies that the kernel image should be loaded at address 0. * `disable_commandline_tags` instructs the GPU to not pass any command line arguments to the booted image. ### 测试内核 现在我们已经遍历了所有源代码, 是时候来看一下它的工作了. 要构建和测试内核, 您需要执行以下操作: 1. 执行 `./build.sh` 或者 `./build.bat` 从 [src/lesson01](https://github.com/s-matyukevich/raspberry-pi-os/tree/master/src/lesson01) 去构建内核. 2. 将生成的 `kernel8.img` 文件复制到 Raspberry Pi 闪存卡的 `boot` 分区 和删除 `kernel7.img`. 确保您保留了启动分区中的所有其他文件 (查看 [这](https://github.com/s-matyukevich/raspberry-pi-os/issues/43) 问题了解详细情况) 3. 如上一节所述修改`config.txt`文件. 4. 按照以下说明连接USB至TTL串行电缆 - [Prerequisites](../Prerequisites.md). 5. 供电给你的 Raspberry Pi. 6. 打开终端模拟器. 您应该可以在那里看到 `Hello, world!` 消息. 请注意, 上述步骤顺序假定您的SD卡上已安装Raspbian. 也可以使用空的SD卡运行RPi OS. 1. 准备您的SD卡: * 使用MBR分区表 * 将启动分区格式化为FAT32 > 该卡的格式应与安装Raspbian所需的格式完全相同. 可以查看 `HOW TO FORMAT AN SD CARD AS FAT` 部分在 [official documenation 官方文档](https://www.raspberrypi.org/documentation/installation/noobs.md) 来获取更多信息. 2. 将以下文件复制到卡中: * [bootcode.bin](https://github.com/raspberrypi/firmware/blob/master/boot/bootcode.bin) 这是GPU引导程序, 它包含用于启动GPU和加载GPU固件的GPU代码. * [start.elf](https://github.com/raspberrypi/firmware/blob/master/boot/start.elf) 这是GPU固件. 它读取`config.txt`, 并使GPU从`kernel8.img`加载并运行ARM特定的用户代码. 3. 复制 `kernel8.img` 和 `config.txt` 文件. 4. 连接USB至TTL串行电缆. 5. 供电给Raspberry Pi. 6. 使用终端仿真器连接到RPi OS. 不幸的是, 所有Raspberry Pi固件文件都是闭源的没有文档. 有关Raspberry Pi启动顺序的更多信息, 你可以参考一些非官方的资料, 比如[这个]](https://raspberrypi.stackexchange.com/questions/10442/what-is-the-boot-sequence) StackExchange 问​​题 或者是 [这个](https://github.com/DieterReuter/workshop-raspberrypi-64bit-os/blob/master/part1-bootloader.md) Github 仓库. ##### 上一页 [Prerequisites 先决条件](../../docs/Prerequisites.md) ##### 下一页 1.2 [Kernel Initialization 内核初始化: Linux project structure Linux对象结构](../../docs/lesson01/linux/project-structure.md) ================================================ FILE: translations/zh-cn/lesson02/exercises.md ================================================ ## 2.3: Exercises 1. 与其直接从EL3跳到EL1, 不如尝试先到达EL2, 然后再切换到EL1. 2. 在学习本课程时, 我遇到的一个问题是, 如果使用`FP/SIMD`寄存器, 那么EL3的一切运行正常, 但是一旦进入EL1, 打印功能就会停止工作. 这就是为什么我添加 [-mgeneral-regs-only](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/Makefile#L3) 编译器选项的参数. 现在, 我希望您删除此参数并重现此行为. 接下来, 您可以使用 `objdump` 工具来查看gcc在没有 `-mgeneral-regs-only` 标志的情况下如何充分利用 `FP/SIMD` 寄存器. 最后, 我希望您使用 `cpacr_el1` 来允许使用 `FP/SIMD` 寄存器. 3. 改编第02课以在`qemu`上运行. 校验 [这个](https://github.com/s-matyukevich/raspberry-pi-os/issues/8) issue 供参考. ##### 上一页 2.2 [处理器初始化:Linux](../../docs/lesson02/linux.md) ##### Next Page 3.1 [中断处理:RPi OS](../../docs/lesson03/rpi-os.md) ================================================ FILE: translations/zh-cn/lesson02/linux.md ================================================ ## 2.2: 处理器初始化 (Linux) 我们通过[stext](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/head.S#L116) 函数停止了对 `Linux` 内核的探索. arm64体系结构的概念. 这次, 我们将更深入一点, 并发现与在本课程和上一课程中已经实现的代码有一些相似之处. 您可能会发现本章有些无聊, 因为它主要讨论了不同的`ARM`系统寄存器及其在`Linux`内核中的用法. 但是我仍然认为它非常重要, 原因如下: 1. 有必要了解硬件提供给软件的接口. 只需了解此接口, 您就可以在许多情况下解构如何实现特定的内核功能以及软件和硬件如何协作以实现此功能. 2. 系统寄存器中的不同选项通常与启用/禁用各种硬件功能有关. 如果您了解不同的系统注册的`ARM`处理器, 那么您将已经知道它支持哪种功能. 好的, 现在让我们继续对`stext`函数的研究. ``` ENTRY(stext) bl preserve_boot_args bl el2_setup // Drop to EL1, w0=cpu_boot_mode adrp x23, __PHYS_OFFSET and x23, x23, MIN_KIMG_ALIGN - 1 // KASLR offset, defaults to 0 bl set_cpu_boot_mode_flag bl __create_page_tables /* * The following calls CPU setup code, see arch/arm64/mm/proc.S for * details. * On return, the CPU will be ready for the MMU to be turned on and * the TCR will have been set. */ bl __cpu_setup // initialise processor b __primary_switch ENDPROC(stext) ``` ### preserve_boot_args [preserve_boot_args](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/head.S#L136) 函数负责保存由引导加载程序传递给内核的参数. ``` preserve_boot_args: mov x21, x0 // x21=FDT adr_l x0, boot_args // record the contents of stp x21, x1, [x0] // x0 .. x3 at kernel entry stp x2, x3, [x0, #16] dmb sy // needed before dc ivac with // MMU off mov x1, #0x20 // 4 x 8 bytes b __inval_dcache_area // tail call ENDPROC(preserve_boot_args) ``` 据此[kernel boot protocol](https://github.com/torvalds/linux/blob/v4.14/Documentation/arm64/booting.txt#L150), 参数在寄存器`x0-x3`中传递给内核. `x0`包含系统RAM中设备树Blob(`.dtb`)的物理地址. `x1 - x3` 保留供将来使用. 该函数正在做的是将寄存器 `x0-x3` 的内容复制到 [boot_args](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/setup.c#L93) 数组接着使[无效](https://developer.arm.com/docs/den0024/latest/caches/cache-maintenance) 数据缓存到相应缓存行. 多处理器系统中的高速缓存维护本身就是一个大话题, 我们现在将略过它. 对于那些对此主题感兴趣的人, 我可以推荐阅读 [Caches](https://developer.arm.com/docs/den0024/latest/caches) 和 [Multi-core processors](https://developer.arm.com/docs/den0024/latest/multi-core-processors) 的`ARM Programmer’s Guide`章节 . ### el2_setup 据此 [arm64boot protocol](https://github.com/torvalds/linux/blob/v4.14/Documentation/arm64/booting.txt#L159), 内核可以在`EL1`或`EL2`中引导. 在第二种情况下, 内核可以访问虚拟化扩展, 并且可以充当主机操作系统. 如果我们有幸可以在EL2中启动, [el2_setup](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/head.S#L386) 函数被调用. 它负责配置不同的参数(只能在EL2上访问), 并放到EL1上. 现在, 我将把这个功能分成几个小部分, 并逐一解释. ``` msr SPsel, #1 // We want to use SP_EL{1,2} ``` 专用堆栈指针将同时用于`EL1`和`EL2`. 另一个选择是重用`EL0`的堆栈指针. ``` mrs x0, CurrentEL cmp x0, #CurrentEL_EL2 b.eq 1f ``` 仅当当前EL为标签 `1` 的 `EL2` 分支时, 否则我们无法进行 `EL2` 设置, 并且此功能尚需完成. ``` mrs x0, sctlr_el1 CPU_BE( orr x0, x0, #(3 << 24) ) // Set the EE and E0E bits for EL1 CPU_LE( bic x0, x0, #(3 << 24) ) // Clear the EE and E0E bits for EL1 msr sctlr_el1, x0 mov w0, #BOOT_CPU_MODE_EL1 // This cpu booted in EL1 isb ret ``` 如果发生这种情况, 我们将在`EL1`执行, `sctlr_el1` 寄存器已更新, 以便CPU可以根据[CPU_BIG_ENDIAN](https://github.com/torvalds/linux/blob/v4.14/arch/arm64 /Kconfig#L612)的值在`little-endian`模式的`big-endian`模式下工作配置设置. 然后我们就退出`el2_setup`函数并返回[BOOT_CPU_MODE_EL1](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/virt.h#L55) 不变. 据此 [ARM64函数调用约定](http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf) 返回值应放在`x0`寄存器中(在本例中为`w0`. 您可以将`w0`寄存器视为`x0`的前32位. ). ``` 1: mrs x0, sctlr_el2 CPU_BE( orr x0, x0, #(1 << 25) ) // Set the EE bit for EL2 CPU_LE( bic x0, x0, #(1 << 25) ) // Clear the EE bit for EL2 msr sctlr_el2, x0 ``` 如果看来我们是在`EL2`中启动的, 则说明我们正在为`EL2`做相同的设置(请注意, 这次使用的是`sctlr_el2`寄存器, 而不是`sctlr_el1`. ). ``` #ifdef CONFIG_ARM64_VHE /* * Check for VHE being present. For the rest of the EL2 setup, * x2 being non-zero indicates that we do have VHE, and that the * kernel is intended to run at EL2. */ mrs x2, id_aa64mmfr1_el1 ubfx x2, x2, #8, #4 #else mov x2, xzr #endif ``` 如果 [虚拟主机扩展 (VHE)](https://developer.arm.com/products/architecture/a-profile/docs/100942/latest/aarch64-virtualization) 已启用通过 [ARM64_VHE](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/Kconfig#L926) config变量, 并且主机支持它们, 然后将`x2`更新为非零值. `x2`将用于检查以后是否在同一功能中启用了`VHE`. ``` mov x0, #HCR_RW // 64-bit EL1 cbz x2, set_hcr orr x0, x0, #HCR_TGE // Enable Host Extensions orr x0, x0, #HCR_E2H set_hcr: msr hcr_el2, x0 isb ``` 这里我们设置 `hcr_el2` 寄存器. 我们使用相同的寄存器为RPi OS中的EL1设置64位执行模式. 这正是在提供的代码示例的第一行中所做的. 同样, 如果 x2!= 0, 这意味着VHE可用并且内核被配置为使用它, 那么`hcr_el2`也被用来启用`VHE`. ``` /* * Allow Non-secure EL1 and EL0 to access physical timer and counter. * This is not necessary for VHE, since the host kernel runs in EL2, * and EL0 accesses are configured in the later stage of boot process. * Note that when HCR_EL2.E2H == 1, CNTHCTL_EL2 has the same bit layout * as CNTKCTL_EL1, and CNTKCTL_EL1 accessing instructions are redefined * to access CNTHCTL_EL2. This allows the kernel designed to run at EL1 * to transparently mess with the EL0 bits via CNTKCTL_EL1 access in * EL2. */ cbnz x2, 1f mrs x0, cnthctl_el2 orr x0, x0, #3 // Enable EL1 physical timers msr cnthctl_el2, x0 1: msr cntvoff_el2, xzr // Clear virtual offset ``` 下一段代码在上面的注释中得到了很好的解释. 我没有更多的补充. ``` #ifdef CONFIG_ARM_GIC_V3 /* GICv3 system register access */ mrs x0, id_aa64pfr0_el1 ubfx x0, x0, #24, #4 cmp x0, #1 b.ne 3f mrs_s x0, SYS_ICC_SRE_EL2 orr x0, x0, #ICC_SRE_EL2_SRE // Set ICC_SRE_EL2.SRE==1 orr x0, x0, #ICC_SRE_EL2_ENABLE // Set ICC_SRE_EL2.Enable==1 msr_s SYS_ICC_SRE_EL2, x0 isb // Make sure SRE is now set mrs_s x0, SYS_ICC_SRE_EL2 // Read SRE back, tbz x0, #0, 3f // and check that it sticks msr_s SYS_ICH_HCR_EL2, xzr // Reset ICC_HCR_EL2 to defaults 3: #endif ``` 仅当`GICv3`可用并启用时, 才执行下一个代码段. `GIC`代表通用中断控制器. `GIC`规范的`v3`版本增加了一些功能, 这些功能在虚拟化环境中特别有用. 例如, 使用`GICv3`, 就有可能具有`LPI`(本地特定的外围设备中断). 此类中断通过消息总线进行路由, 其配置保存在内存中的特殊表中. 提供的代码负责启用SRE(系统寄存器接口). 必须执行此步骤, 然后我们才能使用`ICC _ *_ ELn`寄存器并利用GICv3功能. ``` /* Populate ID registers. */ mrs x0, midr_el1 mrs x1, mpidr_el1 msr vpidr_el2, x0 msr vmpidr_el2, x1 ``` `midr_el1`和 `mpidr_el1` 是标识寄存器组中的只读寄存器. 它们提供了有关处理器制造商, 处理器体系结构名称, 内核数量以及其他一些信息的各种信息. 可以为所有尝试从`EL1`访问它的读者更改此信息. 在这里, 我们使用从`midr_el1`和`mpidr_el1`获取的值填充`vpidr_el2`和 `vmpidr_el2`, 因此无论您尝试从`EL1`还是更高级别的异常级别访问它, 此信息都是相同的. ``` #ifdef CONFIG_COMPAT msr hstr_el2, xzr // Disable CP15 traps to EL2 #endif ``` 当处理器以32位执行模式执行时, 存在 `协处理器` 的概念. 协处理器可用于访问信息, 该信息通常在64位执行模式下通过系统寄存器访问. 您可以在协处理器中[在官方文档中](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0311d/I1014521.html)了解确切可访问的内容. `msrhstr_el2, xzr` 指令允许从较低的异常级别使用协处理器. 仅当启用兼容模式时才有意义(在这种模式下, 内核可以在64位内核之上运行32位用户应用程序. ). ``` /* EL2 debug */ mrs x1, id_aa64dfr0_el1 // Check ID_AA64DFR0_EL1 PMUVer sbfx x0, x1, #8, #4 cmp x0, #1 b.lt 4f // Skip if no PMU present mrs x0, pmcr_el0 // Disable debug access traps ubfx x0, x0, #11, #5 // to EL2 and allow access to 4: csel x3, xzr, x0, lt // all PMU counters from EL1 /* Statistical profiling */ ubfx x0, x1, #32, #4 // Check ID_AA64DFR0_EL1 PMSVer cbz x0, 6f // Skip if SPE not present cbnz x2, 5f // VHE? mov x1, #(MDCR_EL2_E2PB_MASK << MDCR_EL2_E2PB_SHIFT) orr x3, x3, x1 // If we don't have VHE, then b 6f // use EL1&0 translation. 5: // For VHE, use EL2 translation orr x3, x3, #MDCR_EL2_TPMS // and disable access from EL1 6: msr mdcr_el2, x3 // Configure debug traps ``` 这段代码负责配置 `mdcr_el2`(监视器调试配置寄存器(EL2)). 该寄存器负责设置与虚拟化扩展相关的不同调试陷阱. 我将不解释此代码块的详细信息, 因为调试和跟踪在我们的讨论范围之外. 如果您对细节感兴趣, 我建议您阅读第2810页的 `mdcr_el2` 寄存器的描述. [AArch64-Reference-Manual](https://developer.arm.com/docs/ddi0487/ca/arm-architecture-reference-manual-armv8-for-armv8-a-architecture-profile). ``` /* Stage-2 translation */ msr vttbr_el2, xzr ``` 当您的操作系统用作虚拟机管理程序时, 应为其来宾操作系统提供完全的内存隔离. 阶段2虚拟内存转换正是用于此目的:每个来宾OS都认为它拥有所有系统内存, 尽管实际上每个内存访问都是通过阶段2转换映射到物理内存的. `vttbr_el2`存放第2阶段翻译的翻译表的基地址. 此时, 第2阶段转换被禁用, 并且`vttbr_el2`应该设置为0. ``` cbz x2, install_el2_stub mov w0, #BOOT_CPU_MODE_EL2 // This CPU booted in EL2 isb ret ``` 首先将`x2`与`0`进行比较, 以检查是否启用了`VHE`. 如果是, 则跳转至 `install_el2_stub` 标签, 否则记录 `CPU` 以 `EL2` 模式启动并退出 `el2_setup` 功能. 在后一种情况下, 处理器将继续以EL2模式运行, 并且将完全不使用EL1. ``` install_el2_stub: /* sctlr_el1 */ mov x0, #0x0800 // Set/clear RES{1,0} bits CPU_BE( movk x0, #0x33d0, lsl #16 ) // Set EE and E0E on BE systems CPU_LE( movk x0, #0x30d0, lsl #16 ) // Clear EE and E0E on LE systems msr sctlr_el1, x0 ``` 如果达到这一点, 则意味着我们不需要VHE, 并且将很快切换到EL1, 因此需要在此处进行早期的EL1初始化. 复制的代码段负责“ sctlr_el1”(系统控制寄存器)的初始化. 我们已经做了同样适用于RPi OS的工作在[这](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/src/boot.S#L18) . ``` /* Coprocessor traps. */ mov x0, #0x33ff msr cptr_el2, x0 // Disable copro. traps to EL2 ``` 该代码允许EL1访问`cpacr_el1`寄存器, 从而控制对跟踪, 浮点和高级SIMD功能的访问. ``` /* Hypervisor stub */ adr_l x0, __hyp_stub_vectors msr vbar_el2, x0 ``` 尽管某些功能需要它, 但我们现在不打算使用EL2. 例如, 我们需要它来实现[kexec](https://linux.die.net/man/8/kexec)系统调用, 该调用使您能够从当前运行的内核加载并引导到另一个内核. [_hyp_stub_vectors](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/hyp-stub.S#L33)包含EL2所有异常处理程序的地址. 在我们详细讨论中断和异常处理之后, 我们将在下一课中为EL1实现异常处理功能. ``` /* spsr */ mov x0, #(PSR_F_BIT | PSR_I_BIT | PSR_A_BIT | PSR_D_BIT |\ PSR_MODE_EL1h) msr spsr_el2, x0 msr elr_el2, lr mov w0, #BOOT_CPU_MODE_EL2 // This CPU booted in EL2 eret ``` 最后, 我们需要在EL1处初始化处理器状态并切换异常级别. 我们已经为 [RPi OS](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/src/boot.S#L27-L33) 因此, 我将不解释此代码的详细信息. 唯一的新东西是如何初始化`elr_el2`. `lr` 或链接寄存器是 `x30` 的别名. 每当执行 `bl`(分支链接)指令时, `x30` 都会自动填充当前指令的地址. 该事实通常由`ret`指令使用, 因此它知道确切返回的位置. 就我们而言, `lr` 在 [这](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/head.S#L119) 和 由于我们初始化`elr_el2`的方式, 这也是切换到EL1后将要恢复执行的地方. ### EL1处的处理器初始化 现在我们回到`stext`函数. 接下来的几行对我们来说不是很重要, 但是为了完整起见, 我想解释一下. ``` adrp x23, __PHYS_OFFSET and x23, x23, MIN_KIMG_ALIGN - 1 // KASLR offset, defaults to 0 ``` [KASLR](https://lwn.net/Articles/569635/), 或者也称 内核地址空间布局随机化, 是一种允许将内核放置在内存中随机地址处的技术. 仅出于安全原因才需要这样做. 有关更多信息, 您可以阅读上面的链接. ``` bl set_cpu_boot_mode_flag ``` 此处将CPU引导模式保存到 [__boot_cpu_mode](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/virt.h#L74) 变量. 执行此操作的代码与我们之前探讨的`preserve_boot_args`函数非常相似. ``` bl __create_page_tables bl __cpu_setup // initialise processor b __primary_switch ``` 最后3个功能非常重要, 但是它们都与虚拟内存管理有关, 因此我们将把它们的详细探索推迟到第6课. 现在, 我只想简短地描述一下其中的含义. * `__create_page_tables` 顾名思义, 它负责创建页表. * `__cpu_setup` 初始化各种处理器设置, 主要针对虚拟内存管理. * `__primary_switch` 启用MMU并跳至 [start_kernel](https://github.com/torvalds/linux/blob/v4.14/init/main.c#L509) 函数, 这是与体系结构无关的起点. ### 结论 在本章中, 我们简要讨论了引导Linux内核时如何初始化处理器. 在下一课中, 我们将继续与ARM处理器紧密合作, 并研究任何OS的重要主题:中断处理. ##### 上一页 2.1 [处理器初始化:RPi OS](../../docs/lesson02/rpi-os.md) ##### 下一页 2.3 [处理器初始化:练习](../../docs/lesson02/exercises.md) ================================================ FILE: translations/zh-cn/lesson02/rpi-os.md ================================================ ## 2.1: 处理器初始化 在本课程中, 我们将与`ARM`处理器更加紧密地合作. 它具有一些可由操作系统使用的基本功能. 第一个这样的功能称为 "Eception Levels"(异常级别). ### 特权级别 每个支持`ARM.v8`体系结构的`ARM`处理器都有`4`个 异常级别. 您可以将异常级别(简称 `EL`)视为处理器的执行模式, 在不同的执行模式下只有一部分操作和寄存器中可用. 最低的*EL*是`0`. 当处理器在该级别上运行时, 它通常仅使用通用寄存器(`X0-X30`)和栈指针寄存器(`SP`). `EL0` 还允许使用 `STR` 和 `LDR` 命令从内存中加载和存储数据, 以及用户程序通常使用的其他一些指令. 操作系统为了实现进程隔离, 会去负责异常级别的处理. 用户进程不应能够访问其他进程的数据. 为了实现这种行为, 操作系统始终在`EL0`上运行每个用户进程. 在此异常级别上运行时, 进程只能使用它自己的虚拟内存, 并且不能访问任何会更改虚拟内存设置的指令. 因此, 为了做到进程隔离, 操作系统需要为每个进程准备独立的虚拟内存映射, 而且在将处理器执行到用户进程之前, 需要将处理器转入`EL0` 级别. 操作系统本身通常在 `EL1` 上运行. 在此异常级别运行时, 处理器可以访问允许配置虚拟内存设置的寄存器以及某些系统寄存器. Raspberry Pi OS 也将使用 `EL1`. 我们不会大量使用异常级别2和3, 但是我只想简要地描述它们, 以便您了解为什么需要它们. `EL2` 用于我们使用虚拟机监控程序的场景. 在这种情况下, 主机操作系统在`EL2`上运行, 而访客操作系统只能使用 `EL1`. 这允许主机`OS`以隔离用户进程类似的方式来隔离访客`OS`. `EL3`用于从 ARM `安全世界` 到 `不安全世界` 的过渡. 存在这种抽象是为了给运行在两个不同的 `世界` 中的软件提供完全的硬件隔离. 来自 `不安全世界` 的应用程序绝不能访问或修改属于 `安全世界` 的信息(指令和数据), 并且这种限制是在硬件级别上强制执行的. ### 调试内核 我要做的下一件事是弄清楚我们当前正在使用的异常级别. 但是当我尝试执行此操作时, 我意识到内核只能在屏幕上打印一些常量字符串, 但是我需要的是类似 [printf](https://en.wikipedia.org/wiki/Printf_format_string) 的函数. 使用 `printf`, 我可以轻松显示不同寄存器和变量的值. 这样的功能对于内核开发是必不可少的, 因为您没有任何其他调试器可用, `printf` 就成为您确定程序内部正在发生什么的唯一手段. 对于 RPi OS, 我决定不重新发明轮子, 而是使用现有的一种 [printf](http://www.sparetimelabs.com/tinyprintf/tinyprintf.php) 的实现. 该函数主要由字符串操作组成, 从内核开发人员的角度来看不是很有趣. 我使用的这个实现很小, 并且没有外部依赖关系, 因此可以轻松地将其集成到内核中. 我唯一要做的就是定义可以将单个字符发送到屏幕的`putc`函数. 此函数在[mini_uart.c](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/src/mini_uart.c#L59) 定义, 它只是使用了已经存在的 `uart_send` 函数. 同样, 我们需要初始化 `printf` 库并指定 `putc` 函数的位置. 这是在 [kernel.c](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/src/kernel.c#L8) 中完成的. ### 查找当前的异常级别 现在, 当我们具备 `printf` 函数时, 我们可以完成我们的原始任务:确定操作系统在哪个异常级别启动. 一个可以回答这个问题的小函数在[utils.S](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/src/utils.S#L1) 中定义. ``` .globl get_el get_el: mrs x0, CurrentEL lsr x0, x0, #2 ret ``` 在这里, 我们使用 `mrs` 指令将 `CurrentEL` 系统寄存器中的值读入 `x0` 寄存器中. 然后, 我们将这个值向右移2位(我们需要这样做, 因为`CurrentEL`寄存器中的前2位是保留位, 并且始终为`0`), 最后在寄存器`x0`中, 我们有一个整数, 表示当前的异常水平. 现在剩下的唯一事情就是显示此值, 例如[kerner.c](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/src/kernel.c#L10). ``` int el = get_el(); printf("Exception level: %d \r\n", el); ``` 如果您重现此实验, 在屏幕上应该能看到 `Exception level:3`. ### 更改当前的异常级别 在 `ARM` 体系结构中, 如果没有已经在更高级别上运行的软件的参与, 程序就无法增加自己的异常级别. 这很有道理, 如果没有这个限制, 则任何程序都可以更改当前的 `EL` , 然后去访问其他程序的数据. 所以只有当发生了异常时, 才能更改当前的 EL. 如果程序执行某些非法指令(例如, 尝试访问不存在的内存地址、试图除以`0`), 则可能会发生这种情况. 应用程序也可以执行 `svc` 指令来故意产生异常. 硬件生成的中断也被视为特殊类型的异常. 每当生成异常时, 都会触发以下操作(在描述中, 我假设异常是在 EL`n` 处处理的, 而`n`可能是`1`、`2`或`3`). 1. 当前指令的地址保存在 `ELR_ELn` 寄存器中(`Exception link register`) 2. 当前处理器状态存储在 `SPSR_ELn` 寄存器中(`Saved Program Status Register`) 3. 异常处理程序将运行并执行所需的任何工作 4. 异常处理程序调用`eret`指令. 该指令从 `SPSR_ELn` 恢复处理器状态, 并从存储在 `ELR_ELn` 寄存器中的地址开始恢复执行. 在实践中, 该过程要复杂一些, 因为异常处理程序还需要存储所有通用寄存器的状态, 然后将其还原回去, 但是我们将在下一课中详细讨论该过程. 现在, 我们只需要大致了解该过程, 并记住`ELR_ELm`和`SPSR_ELn`寄存器的含义即可. 要知道的重要一点是, 异常处理程序没有义务返回到异常所源自的相同位置. **`ELR_ELm` 和 `SPSR_ELn` 都是可写的**, 如果需要, 异常处理程序可以对其进行修改. 当我们尝试在代码中从 `EL3` 切换到 `EL1` 时, 我们将利用这种技术来发挥优势. ### 切换到EL1 严格来说, 我们的操作系统不是必须切换到`EL1`, 但是`EL1`对我们来说是很自然的选择, 因为该级别具有执行所有常见 `OS` 任务的正确权限集. 看看切换异常级别是如何工作的, 这也是一个有趣的练习. 让我们看一下[boot.S](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/src/boot.S#L17). ``` master: ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ldr x0, =HCR_VALUE msr hcr_el2, x0 ldr x0, =SCR_VALUE msr scr_el3, x0 ldr x0, =SPSR_VALUE msr spsr_el3, x0 adr x0, el1_entry msr elr_el3, x0 eret ``` 如您所见, 该代码主要由配置一些系统寄存器组成. 现在我们将逐一检查这些寄存器. 为此, 我们首先需要下载 [AArch64-Reference-Manual](https://developer.arm.com/docs/ddi0487/ca/arm-architecture-reference-manual-armv8-for-armv8-a-architecture-profile). 本文档包含 `ARM.v8` 体系结构的详细规范. #### SCTLR_EL1, 系统控制寄存器 (EL1), Page 2654 of AArch64-Reference-Manual. ``` ldr x0, =SCTLR_VALUE_MMU_DISABLED msr sctlr_el1, x0 ``` 在这里, 我们设置 `sctlr_el1` 系统寄存器的值. `sctlr_el1` 负责在 EL1 上运行时配置处理器的不同参数. 例如, 它控制是否启用缓存以及对我们最重要的是是否打开 `MMU`(Memory Mapping Unit: 内存映射单元). 可以从所有高于或等于 `EL1` 的异常级别访问 `sctlr_el1` 寄存器(您也可以从 `_el1` 后缀中推断出这一点). `SCTLR_VALUE_MMU_DISABLED` 是一个常量, 定义在 [sysregs.h](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/include/arm/sysregs.h#L16) 中. 该值的各个位的定义如下: * `#define SCTLR_RESERVED(3 << 28)|(3 << 22)|(1 << 20)|(1 << 11)` sctlr_el1 寄存器描述中的某些位被标记为 `RES1`(Reserve). 这些保留位是供将来使用的, 应将其初始化为`1`. * `#define SCTLR_EE_LITTLE_ENDIAN (0 << 25)` 异常的[字节序](https://en.wikipedia.org/wiki/Endianness). 该字段控制在 EL1 处进行显式数据访问的顺序. 我们将配置处理器仅在 `little-endian` 下工作. * `#define SCTLR_EOE_LITTLE_ENDIAN (0 << 24)` 与上一字段类似, 但此字段控制 `EL0` 而不是`EL1`处的显式数据访问的字节序. * `#define SCTLR_I_CACHE_DISABLED (0 << 12)` 禁用指令缓存. 为了简单起见, 我们将禁用所有缓存. 您可以在[此处](https://stackoverflow.com/questions/22394750/what-is-meant-by-data-cache-and-instruction-cache)找到有关数据和指令高速缓存的更多信息. * `#define SCTLR_D_CACHE_DISABLED (0 << 2)` 禁用数据缓存. * `#define SCTLR_MMU_DISABLED (0 << 0)` 禁用MMU. 在第6课之前, 必须禁用 MMU, 在第6课中, 我们将准备页表并开始使用虚拟内存. #### HCR_EL2, 系统管理程序配置寄存器 (EL2), Page 2487 of AArch64-Reference-Manual. ``` ldr x0, =HCR_VALUE msr hcr_el2, x0 ``` 我们不会实施我们自己的[hypervisor](https://en.wikipedia.org/wiki/Hypervisor). 直到我们需要使用该寄存器, 因为在其他设置中, 它控制着EL1的执行状态. 执行状态必须是`AArch64`而不是`AArch32`. 此配置在[sysregs.h](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/include/arm/sysregs.h#L22). #### SCR_EL3, 安全配置寄存器 (EL3), Page 2648 of AArch64-Reference-Manual. ``` ldr x0, =SCR_VALUE msr scr_el3, x0 ``` 该寄存器负责配置安全设置. 例如, 它控制所有较低级别是在 `安全` 状态还是 `非安全` 状态下执行. 它还控制 `EL2` 的执行状态. 我们设置EL2将在`AArch64`处执行可参考在[这](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/include/arm/sysregs.h#L26). 所有较低的异常级别将是 `不安全的`. #### SPSR_EL3, 储存程序状态寄存器 (EL3), Page 389 of AArch64-Reference-Manual. ``` ldr x0, =SPSR_VALUE msr spsr_el3, x0 ``` 该寄存器应该已经为您所熟悉-在讨论更改异常级别的过程时我们提到了它. `spsr_el3` 包含处理器状态, 在我们执行 `eret` 指令后将恢复该状态. 值得说几句话来说明什么是处理器状态. 处理器状态包括以下信息: * **Condition Flags** 这些标志位包含了之前执行的操作的信息:结果是负数(N标志), 零(A标志), 无符号溢出(C标志)还是有符号溢出(V标志). 这些标志的值可以在条件分支指令中使用. 例如, 仅当上一次比较操作的结果等于0时, `b.eq`指令才会跳转到所提供的标签. 处理器通过测试Z标志是否设置为1来进行检查. * **Interrupt disable bits** 这些位允许启用/禁用不同类型的中断. * **其他信息** 处理异常后, 完全恢复处理器执行状态所需的一些其他信息. 通常, 当 EL3 发生异常时, 会自动保存`spsr_el3`. 但是该寄存器是可写的, 因此我们利用这一事实并手动准备处理器的状态. 在[sysregs.h](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson02/include/arm/sysregs.h#L35) 准备了`SPSR_VALUE`, 并初始化了以下域: * `#define SPSR_MASK_ALL (7 << 6)` 将`EL`更改为`EL1`后, 所有类型的中断都将被屏蔽(或禁用) * `#define SPSR_EL1h (5 << 0)` 在`EL1`, 我们可以使用自己专用的栈指针, 也可以使用`EL0`栈指针. `EL1h`模式意味着我们正在使用 `EL1` 的专用栈指针. #### ELR_EL3, 异常链接寄存器 (EL3), Page 351 of AArch64-Reference-Manual. ``` adr x0, el1_entry msr elr_el3, x0 eret ``` `elr_el3` 保存地址, 在执行 `eret` 指令后, 我们将返回该地址. 在这里, 我们将此地址设置为 `el1_entry` 标签的位置. ### 结论 差不多了:当我们输入 `el1_entry` 函数时, 执行应该已经处于`EL1`模式. 尝试下吧! ##### 上一页 1.5 [内核初始化:练习](./exercises.md) ##### 下一页 2.2 [Linux 处理器初始化](./linux.md) ================================================ FILE: translations/zh-cn/lesson03/exercises.md ================================================ ## 3.5: Exercises 1. 使用本地计时器而不是系统计时器来生成处理器中断. 有关详细信息, 请参见[这篇](https://github.com/s-matyukevich/raspberry-pi-os/issues/70) 问题. 2. 处理`MiniUART`中断. 用什么都不做的循环代替`kernel_main`函数中的最终循环. 设置`MiniUART`器件以在用户键入新字符后立即生成中断. 实现一个中断处理程序, 该处理程序负责在屏幕上打印每个新到达的字符. 3. 改编第03课以在`qemu`上运行. 检查[这个](https://github.com/s-matyukevich/raspberry-pi-os/issues/8)问题以供参考. ##### 上一页 3.4 [中断处理:计时器](../../docs/lesson03/linux/timer.md) ##### 下一页 4.1 [进程调度程序:RPi OS调度程序](../../docs/lesson04/rpi-os.md) ================================================ FILE: translations/zh-cn/lesson03/linux/interrupt_controllers.md ================================================ ## 3.3: 中断控制器 在本章中, 我们将大量讨论Linux驱动程序以及它们如何处理中断. 我们将从驱动程序初始化代码开始, 然后看看[handle_arch_irq](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/irq.c#L44)功能. ### 使用设备树查找所需的设备和驱动程序 在RPi OS中实现中断时, 我们一直在使用2种设备:系统定时器和中断控制器. 现在, 我们的目标是了解相同设备在Linux中的工作方式. 我们需要做的第一件事是找到负责使用提到的设备的驱动程序. 为了找到所需的驱动程序, 我们可以使用[bcm2837-rpi-3-b.dts](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dots/bcm2837-rpi-3-b.dts) 设备树文件. 这是特定于Raspberry Pi 3 Model B的顶级设备树文件, 它包含其他更常见的设备树文件, 这些文件在不同版本的Raspberry Pi之间共享. 如果遵循包含的链并搜索 `timer` 和 `interrupt-controller` , 则可以找到4个设备. 1. [本地中断控制器](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837.dtsi#L11) 1. [本地计时器](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837.dtsi#L20) 1. 全局中断控制器. 它被定义在 [这里](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm283x.dtsi#L109) 并修改 [这里](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837.dtsi#L72). 1. [系统计时器](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm283x.dtsi#L57) 停下来, 但是为什么我们有4个设备而不是2个?这需要一些解释, 我们将在下一部分中解决这个问题. ### 本地与全局中断控制器 考虑多处理器系统中的中断处理时, 您应该问自己一个问题:哪个内核应负责处理特定的中断?发生中断时, 是全部4个内核都中断了, 还是只有一个?是否可以将特定的中断路由到特定的内核?您可能想知道的另一个问题是, 如果一个处理器需要向其传递一些信息, 该处理器如何通知另一个处理器? 本地中断控制器是可以帮助您回答所有这些问题的设备. 它负责以下任务. * 配置哪个内核应该接收特定的中断. * 在内核之间发送中断. 这样的中断称为`mailboxs`, 并允许内核相互通信. * 处理来自本地计时器和性能监视器中断(PMU)的中断. [BCM2836 ARM本地外围设备](https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2836/QA7_rev3.4.pdf) 手册中记录了本地中断控制器以及本地计时器的行为. 我已经多次提到本地计时器. 现在您可能想知道为什么我们在系统中需要两个独立的计时器?我猜想使用本地计时器的主要用例是当您要配置所有4个内核以同时接收计时器中断时. 如果使用系统定时器, 则只能将中断路由到单个内核. 使用RPi OS时, 我们既不使用本地中断控制器也不使用本地计时器. 这是因为默认情况下, 本地中断控制器的配置方式是将所有外部中断都发送到第一个内核, 这正是我们所需要的. 我们没有使用本地计时器, 因为我们使用了系统计时器. ### 本地中断控制器 根据[bcm2837.dtsi](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837.dtsi#L75), 全局中断控制器是当地的. 因此, 从本地控制器开始我们的探索是有意义的. 如果我们需要找到适用于特定设备的驱动程序, 则应使用[compatible](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837.dtsi#L12) 属性. 搜索该属性的值, 您可以轻松地找到一个与RPi本地中断控制器兼容的驱动程序 - 这是对应的 [定义](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2836.c#L315). ``` IRQCHIP_DECLARE(bcm2836_arm_irqchip_l1_intc, "brcm,bcm2836-l1-intc", bcm2836_arm_irqchip_l1_intc_of_init); ``` 现在您可能已经猜出了驱动程序初始化的过程是什么:内核遍历设备树中的所有设备定义, 并且针对每个定义, 它使用 `compatible` 属性寻找匹配的驱动程序. 如果找到驱动程序, 则调用其初始化函数. 在设备注册过程中提供了初始化功能, 在本例中, 此功能是 [bcm2836_arm_irqchip_l1_intc_of_init](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2836.c#L280). ```cpp static int __init bcm2836_arm_irqchip_l1_intc_of_init(struct device_node *node, struct device_node *parent) { intc.base = of_iomap(node, 0); if (!intc.base) { panic("%pOF: unable to map local interrupt registers\n", node); } bcm2835_init_local_timer_frequency(); intc.domain = irq_domain_add_linear(node, LAST_IRQ + 1, &bcm2836_arm_irqchip_intc_ops, NULL); if (!intc.domain) panic("%pOF: unable to create IRQ domain\n", node); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_CNTPSIRQ, &bcm2836_arm_irqchip_timer); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_CNTPNSIRQ, &bcm2836_arm_irqchip_timer); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_CNTHPIRQ, &bcm2836_arm_irqchip_timer); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_CNTVIRQ, &bcm2836_arm_irqchip_timer); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_GPU_FAST, &bcm2836_arm_irqchip_gpu); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_PMU_FAST, &bcm2836_arm_irqchip_pmu); bcm2836_arm_irqchip_smp_init(); set_handle_irq(bcm2836_arm_irqchip_handle_irq); return 0; } ``` 初始化函数采用2个参数:`node`和`parent`, 它们都是类型[struct device_node](https://github.com/torvalds/linux/blob/v4.14/include/linux/of.h#L49). `node`代表设备树中的当前节点, 在本例中, 它指向 [这里](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837.dtsi#L11) `parent` 是设备树层次结构中的父节点, 对于本地中断控制器, 它指向 `soc` 元素( `soc` 代表 `片上系统`, 它是最简单的总线, 可以直接映射所有设备寄存器到主内存. ). 节点可用于从当前设备树节点读取各种属性. 例如, 函数`bcm2836_arm_irqchip_l1_intc_of_init`的第一行从[reg](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837dtsi#L13)读取设备基址属性. 但是, 此过程要复杂得多, 因为执行此功能时, 已启用MMU, 并且在我们能够访问物理内存的某个区域之前, 必须将该区域映射到某个虚拟地址. 这正是[of_iomap](https://github.com/torvalds/linux/blob/v4.14/drivers/of/address.c#L759) 函数的作用: 它读取提供的节点的`reg`属性, 并将由`reg`属性描述的整个内存区域映射到某个虚拟内存区域. 下一个本地计时器频率在[bcm2835_init_local_timer_frequency](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2836.c#L264) 函数中初始化. 此功能没有特别说明:它仅使用某些寄存器, 如[BCM2836 ARM本地外围设备](https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2836/QA7_rev3.4.pdf)手册中所述, 以初始化本地计时器. 下一行需要一些解释. ``` intc.domain = irq_domain_add_linear(node, LAST_IRQ + 1, &bcm2836_arm_irqchip_intc_ops, NULL); ``` Linux为每个中断分配一个唯一的整数, 您可以将此数字视为唯一的中断ID. 每次您想对中断执行操作时都会使用此ID(例如, 分配处理程序或分配哪个CPU应该处理它). 每个中断还具有一个硬件中断号. 这通常是一个数字, 告诉您触发了哪个中断线. `BCM2837 ARM外设手册` 的外设中断表位于第113页 - 您可以将此表中的索引视为硬件中断号. 因此, 显然, 我们需要某种机制将Linux irq号映射到硬件irq号, 反之亦然. 如果只有一个中断控制器, 则可以使用一对一的映射, 但是通常情况下, 需要使用更复杂的机制. 在Linux中[struct irq_domain](https://github.com/torvalds/linux/blob/v4.14/include/linux/irqdomain.h#L152)实现了这种映射. 每个中断控制器驱动程序应创建自己的`irq`域, 并注册该域可以处理的所有中断. 注册函数返回Linux irq号, 该编号以后将用于处理中断. 接下来的6行负责向`irq`域注册每个受支持的中断. ```cpp bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_CNTPSIRQ, &bcm2836_arm_irqchip_timer); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_CNTPNSIRQ, &bcm2836_arm_irqchip_timer); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_CNTHPIRQ, &bcm2836_arm_irqchip_timer); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_CNTVIRQ, &bcm2836_arm_irqchip_timer); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_GPU_FAST, &bcm2836_arm_irqchip_gpu); bcm2836_arm_irqchip_register_irq(LOCAL_IRQ_PMU_FAST, &bcm2836_arm_irqchip_pmu); ``` 根据[BCM2836 ARM本地外设](https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2836/QA7_rev3.4.pdf) 手动本地中断控制器处理10种不同的中断: 0-3是本地计时器的中断, 4-7是邮箱中断, 用于进程间通信, 8对应于全局中断控制器生成的所有中断, 中断9是性能监视器中断. [这里](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2836.c#L67) 您会看到驱动程序定义了一组常量, 每个常量都包含硬件irq号. 上面的注册代码注册所有中断, 但邮箱中断除外, 邮箱中断是单独注册的. 为了更好地了解注册码让我们来看看[bcm2836_arm_irqchip_register_irq](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2836.c#L154) function. ``` static void bcm2836_arm_irqchip_register_irq(int hwirq, struct irq_chip *chip) { int irq = irq_create_mapping(intc.domain, hwirq); irq_set_percpu_devid(irq); irq_set_chip_and_handler(irq, chip, handle_percpu_devid_irq); irq_set_status_flags(irq, IRQ_NOAUTOEN); } ``` 第一行执行实际的中断注册. [irq_create_mapping](https://github.com/torvalds/linux/blob/v4.14/kernel/irq/irqdomain.c#L632) 将硬件中断号作为输入并返回Linux irq号. [irq_set_percpu_devid](https://github.com/torvalds/linux/blob/v4.14/kernel/irq/irqdesc.c#L849)将中断配置为`每个CPU`, 因此只能在当前处理器上处理中央处理器. 这非常有道理, 因为我们现在讨论的所有中断都是本地中断, 并且所有中断只能在当前CPU上处理. [irq_set_chip_and_handler](https://github.com/torvalds/linux/blob/v4.14/include/linux/irq.h#L608), 顾名思义, 设置irq芯片和irq处理程序. Irq芯片是一种特殊的结构, 需要由驱动程序创建, 该结构具有用于屏蔽和取消屏蔽特定中断的方法. 我们正在检查的驱动程序现在定义了3种不同的irq芯片: [timer](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2836.c#L118) 芯片, [PMU](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2836.c#L134) 芯片 和 [GPU](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2836.c#L148) 芯片, 它控制由外部外围设备生成的所有中断. 处理程序是负责处理中断的功能. 在这种情况下, 处理程序设置为通用 [handle_percpu_devid_irq](https://github.com/torvalds/linux/blob/v4.14/kernel/irq/chip.c#L859) 函数. 稍后, 该处理程序将由全局中断控制器驱动程序重写. [irq_set_status_flags](https://github.com/torvalds/linux/blob/v4.14/include/linux/irq.h#L652) 在这种特殊情况下, 设置一个标志, 指示应手动启用当前中断, 并且默认情况下不应启用. 回到`bcm2836_arm_irqchip_l1_intc_of_init`函数, 只剩下两个调用. 第一个是 [bcm2836_arm_irqchip_smp_init](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2836.c#L243). 在此启用了邮箱中断, 从而允许处理器内核相互通信. 最后一个函数调用非常重要-这是将低级异常处理代码连接到驱动程序的地方. ``` set_handle_irq(bcm2836_arm_irqchip_handle_irq); ``` [set_handle_irq](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/irq.c#L46) 是在特定于体系结构的代码中定义的, 我们已经遇到了此功能. 从上面的行中我们可以了解到[bcm2836_arm_irqchip_handle_irq](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2836.c#L164) 将由低级异常代码调用. 函数本身在下面列出. ```cpp static void __exception_irq_entry bcm2836_arm_irqchip_handle_irq(struct pt_regs *regs) { int cpu = smp_processor_id(); u32 stat; stat = readl_relaxed(intc.base + LOCAL_IRQ_PENDING0 + 4 * cpu); if (stat & BIT(LOCAL_IRQ_MAILBOX0)) { #ifdef CONFIG_SMP void __iomem *mailbox0 = (intc.base + LOCAL_MAILBOX0_CLR0 + 16 * cpu); u32 mbox_val = readl(mailbox0); u32 ipi = ffs(mbox_val) - 1; writel(1 << ipi, mailbox0); handle_IPI(ipi, regs); #endif } else if (stat) { u32 hwirq = ffs(stat) - 1; handle_domain_irq(intc.domain, hwirq, regs); } } ``` 该函数读取 `LOCAL_IRQ_PENDING` 寄存器, 以找出当前正在处理的中断. 有4个 `LOCAL_IRQ_PENDING` 寄存器, 每个寄存器对应于其自己的处理器内核, 这就是为什么使用当前处理器索引来选择正确的寄存器的原因. 邮箱中断和所有其他中断在if语句的2个不同子句中处理. 多处理器系统的不同内核之间的交互超出了我们当前的讨论范围, 因此我们将跳过邮箱中断处理部分. 现在, 仅剩下以下两行无法解释. ``` u32 hwirq = ffs(stat) - 1; handle_domain_irq(intc.domain, hwirq, regs); ``` 这是将中断传递给下一个处理程序的地方. 首先计算硬件irq数. [ffs](https://github.com/torvalds/linux/blob/v4.14/include/asm-generic/bitops/ffs.h#L13) (查找第一位) 函数用于执行此操作. 计算出硬件irq数后 [handle_domain_irq](https://github.com/torvalds/linux/blob/v4.14/kernel/irq/irqdesc.c#L622) 函数被调用. 此功能使用irq域将硬件irq号码转换为Linux irq号码, 然后检查irq配置 (它存储在 [irq_desc](https://github.com/torvalds/linux/blob/v4.14/include/linux/irqdesc.h#L55) 结构) 并调用一个中断处理程序. 我们已经看到处理程序设置为 [handle_percpu_devid_irq](https://github.com/torvalds/linux/blob/v4.14/kernel/irq/chip.c#L859). 但是, 此处理程序稍后将被子中断控制器覆盖. 现在, 让我们检查一下这是如何发生的. ### 通用中断控制器 我们已经看到了如何使用设备树和`compatible`属性来查找与某个设备相对应的驱动程序, 因此我将跳过这一部分, 直接跳转到通用中断控制器驱动程序源代码. 你可以在找到它在 [irq-bcm2835.c](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2835.c) 文件. 和往常一样, 我们将从初始化功能开始探索. 叫做 [armctrl_of_init](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2835.c#L141). ```cpp static int __init armctrl_of_init(struct device_node *node, struct device_node *parent, bool is_2836) { void __iomem *base; int irq, b, i; base = of_iomap(node, 0); if (!base) panic("%pOF: unable to map IC registers\n", node); intc.domain = irq_domain_add_linear(node, MAKE_HWIRQ(NR_BANKS, 0), &armctrl_ops, NULL); if (!intc.domain) panic("%pOF: unable to create IRQ domain\n", node); for (b = 0; b < NR_BANKS; b++) { intc.pending[b] = base + reg_pending[b]; intc.enable[b] = base + reg_enable[b]; intc.disable[b] = base + reg_disable[b]; for (i = 0; i < bank_irqs[b]; i++) { irq = irq_create_mapping(intc.domain, MAKE_HWIRQ(b, i)); BUG_ON(irq <= 0); irq_set_chip_and_handler(irq, &armctrl_chip, handle_level_irq); irq_set_probe(irq); } } if (is_2836) { int parent_irq = irq_of_parse_and_map(node, 0); if (!parent_irq) { panic("%pOF: unable to get parent interrupt.\n", node); } irq_set_chained_handler(parent_irq, bcm2836_chained_handle_irq); } else { set_handle_irq(bcm2835_handle_irq); } return 0; } ``` 现在, 让我们更详细地研究此功能. ```cpp void __iomem *base; int irq, b, i; base = of_iomap(node, 0); if (!base) panic("%pOF: unable to map IC registers\n", node); intc.domain = irq_domain_add_linear(node, MAKE_HWIRQ(NR_BANKS, 0), &armctrl_ops, NULL); if (!intc.domain) panic("%pOF: unable to create IRQ domain\n", node); ``` 该函数以从设备3读取设备基地址并初始化irq域的代码开始. 您应该已经熟悉此部分, 因为我们已经在本地irq控制器驱动程序中看到了类似的代码. ```cpp for (b = 0; b < NR_BANKS; b++) { intc.pending[b] = base + reg_pending[b]; intc.enable[b] = base + reg_enable[b]; intc.disable[b] = base + reg_disable[b]; ``` 接下来, 有一个循环遍历所有irq库. 在本课程的第一章中, 我们已经简短地谈到了irq银行. 中断控制器具有3个irq bank, 由 `ENABLE_IRQS_1` , `ENABLE_IRQS_2` 和 `ENABLE_BASIC_IRQS` 寄存器控制. 每个存储区都有其自己的启用, 禁用和挂起寄存器. 启用和禁用寄存器可用于启用或禁用属于特定存储区的单个中断. 待处理寄存器用于确定正在等待处理的中断. ```cpp for (i = 0; i < bank_irqs[b]; i++) { irq = irq_create_mapping(intc.domain, MAKE_HWIRQ(b, i)); BUG_ON(irq <= 0); irq_set_chip_and_handler(irq, &armctrl_chip, handle_level_irq); irq_set_probe(irq); } ``` 接下来, 有一个嵌套循环, 负责注册每个受支持的中断并设置irq芯片和处理程序. 我们已经看到了本地中断控制器驱动程序中如何使用相同的功能. 但是, 我想强调一些重要的事情. * [MAKE_HWIRQ](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2835.c#L57) 宏用于计算硬件irq号. 它是根据银行内部的银行指数和irq指数计算的. * [handle_level_irq](https://github.com/torvalds/linux/blob/v4.14/kernel/irq/chip.c#L603) 是用于级别类型的中断的通用处理程序. 此类中断将中断线设置为“高”, 直到确认该中断为止. 还有边缘类型中断以不同的方式工作. * [irq_set_probe](https://github.com/torvalds/linux/blob/v4.14/include/linux/irq.h#L667) 函数只是未设置 [IRQ_NOPROBE](https://github.com/torvalds/linux/blob/v4.14/include/linux/irq.h#L64) 中断标志, 有效地禁用中断自动探测. 中断自动探测是允许不同的驱动程序发现其设备连接到哪条中断线的过程. Raspberry Pi不需要此功能, 因为此信息被编码在设备树中, 但是, 对于某些设备, 这可能很有用. 请参阅 [这个](https://github.com/torvalds/linux/blob/v4.14/include/linux/interrupt.h#L662) 评论以了解自动探测如何在Linux内核中工作. `BCM2836`和`BCM2835`中断控制器的下一段代码是不同的(第一个对应于RPi模型2和3, 第二个对应于RPi模型1). 如果我们正在处理BCM2836, 则执行以下代码. ```cpp int parent_irq = irq_of_parse_and_map(node, 0); if (!parent_irq) { panic("%pOF: unable to get parent interrupt.\n", node); } irq_set_chained_handler(parent_irq, bcm2836_chained_handle_irq); ``` 设备树[表明](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837.dtsi#L75)本地中断控制器是全局中断控制器的父级. 另一个设备树 [属性](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm2837.dtsi#L76) 告诉我们全局中断控制器已连接到本地控制器的中断线8, 这意味着您的父irq是硬件irq 8. 这2个属性允许Linux内核找出父中断号(这是Linux中断号, 而不是硬件号). 最后[irq_set_chained_handler](https://github.com/torvalds/linux/blob/v4.14/include/linux/irq.h#L636) 函数将父irq的处理程序替换为 [bcm2836_chained_handle_irq](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2835.c#L246) 功能. [bcm2836_chained_handle_irq](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2835.c#L246) 很简单,其代码在下面列出. ```cpp static void bcm2836_chained_handle_irq(struct irq_desc *desc) { u32 hwirq; while ((hwirq = get_next_armctrl_hwirq()) != ~0) generic_handle_irq(irq_linear_revmap(intc.domain, hwirq)); } ``` 您可以将这段代码视为我们所做工作的高级版本 [这里](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/irq.c#L39) 用于RPi OS. [get_next_armctrl_hwirq](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2835.c#L217) 使用所有3个暂挂寄存器来确定触发了哪个中断. [irq_linear_revmap](https://github.com/torvalds/linux/blob/v4.14/include/linux/irqdomain.h#L377) 使用irq域将硬件irq号码转换为Linux irq号码和 [generic_handle_irq](https://github.com/torvalds/linux/blob/v4.14/include/linux/irqdesc.h#L156) 只是执行irq处理程序. 在初始化函数中设置了Irq处理程序 它指向 [handle_level_irq](https://github.com/torvalds/linux/blob/v4.14/kernel/irq/chip.c#L603) 最终执行与中断相关的所有irq动作 (这实际上是完成的 [这里](https://github.com/torvalds/linux/blob/v4.14/kernel/irq/handle.c#L135).). 目前, 所有支持的中断的irq操作列表为空 - 对处理某些中断感兴趣的驱动程序应在相应的列表中添加一个操作. 在下一章中, 我们将以系统计时器为例来了解如何完成此操作. ##### 上一页 3.2 [中断处理:Linux中的低级异常处理](../../../docs/lesson03/linux/low_level-exception_handling.md) ##### 下一页 3.4 [中断处理:计时器](../../../docs/lesson03/linux/timer.md) ================================================ FILE: translations/zh-cn/lesson03/linux/low_level-exception_handling.md ================================================ ## 3.2: Linux中的低级异常处理 给定庞大的Linux内核源代码, 找到负责中断处理的代码的好方法是什么?我可以提出一个想法. 向量表的基地址应存储在`vbar_el1` 寄存器中, 因此, 如果搜索 `vbar_el1`, 则应该能够弄清楚向量表的初始化位置. 确实, 搜索仅给我们提供了一些用法, 其中之一属于我们已经熟悉的[head.S](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/头) 这段代码位于[__primary_switched](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/head.S#L323) 函数内部. `MMU` 打开后执行此功能. 该代码如下所示. ``` adr_l x8, vectors // load VBAR_EL1 with virtual msr vbar_el1, x8 // vector table address ``` 从这段代码中, 我们可以推断出向量表被称为 `向量`, 您应该能够轻松找到[其定义](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L367). ```cpp /* * Exception vectors. */ .pushsection ".entry.text", "ax" .align 11 ENTRY(vectors) kernel_ventry el1_sync_invalid // Synchronous EL1t kernel_ventry el1_irq_invalid // IRQ EL1t kernel_ventry el1_fiq_invalid // FIQ EL1t kernel_ventry el1_error_invalid // Error EL1t kernel_ventry el1_sync // Synchronous EL1h kernel_ventry el1_irq // IRQ EL1h kernel_ventry el1_fiq_invalid // FIQ EL1h kernel_ventry el1_error_invalid // Error EL1h kernel_ventry el0_sync // Synchronous 64-bit EL0 kernel_ventry el0_irq // IRQ 64-bit EL0 kernel_ventry el0_fiq_invalid // FIQ 64-bit EL0 kernel_ventry el0_error_invalid // Error 64-bit EL0 #ifdef CONFIG_COMPAT kernel_ventry el0_sync_compat // Synchronous 32-bit EL0 kernel_ventry el0_irq_compat // IRQ 32-bit EL0 kernel_ventry el0_fiq_invalid_compat // FIQ 32-bit EL0 kernel_ventry el0_error_invalid_compat // Error 32-bit EL0 #else kernel_ventry el0_sync_invalid // Synchronous 32-bit EL0 kernel_ventry el0_irq_invalid // IRQ 32-bit EL0 kernel_ventry el0_fiq_invalid // FIQ 32-bit EL0 kernel_ventry el0_error_invalid // Error 32-bit EL0 #endif END(vectors) ``` 看起来很熟悉, 不是吗?实际上, 我已经复制了大部分代码, 并将其简化了一些. [kernel_ventry](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L72)宏与[ventry](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/entry.S#L12)在RPi OS中定义. 不过, 一个区别是`kernel_ventry`还负责检查是否发生了内核堆栈溢出. 如果设置了`CONFIG_VMAP_STACK`, 则启用此功能, 它是内核功能的一部分, 称为虚拟映射内核堆栈. 我不会在这里详细解释它, 但是, 如果您有兴趣, 我建议您阅读[这](https://lwn.net/Articles/692208/)文章. ### kernel_entry [kernel_entry](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L120)宏对您也应该很熟悉. 与[相应宏](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/entry.S#L17) 的使用方式完全相同. RPI操作系统. 但是, 原始(Linux)版本要复杂得多. 该代码在下面列出. ```cpp .macro kernel_entry, el, regsize = 64 .if \regsize == 32 mov w0, w0 // zero upper 32 bits of x0 .endif stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] .if \el == 0 mrs x21, sp_el0 ldr_this_cpu tsk, __entry_task, x20 // Ensure MDSCR_EL1.SS is clear, ldr x19, [tsk, #TSK_TI_FLAGS] // since we can unmask debug disable_step_tsk x19, x20 // exceptions when scheduling. mov x29, xzr // fp pointed to user-space .else add x21, sp, #S_FRAME_SIZE get_thread_info tsk /* Save the task's original addr_limit and set USER_DS (TASK_SIZE_64) */ ldr x20, [tsk, #TSK_TI_ADDR_LIMIT] str x20, [sp, #S_ORIG_ADDR_LIMIT] mov x20, #TASK_SIZE_64 str x20, [tsk, #TSK_TI_ADDR_LIMIT] /* No need to reset PSTATE.UAO, hardware's already set it to 0 for us */ .endif /* \el == 0 */ mrs x22, elr_el1 mrs x23, spsr_el1 stp lr, x21, [sp, #S_LR] /* * In order to be able to dump the contents of struct pt_regs at the * time the exception was taken (in case we attempt to walk the call * stack later), chain it together with the stack frames. */ .if \el == 0 stp xzr, xzr, [sp, #S_STACKFRAME] .else stp x29, x22, [sp, #S_STACKFRAME] .endif add x29, sp, #S_STACKFRAME #ifdef CONFIG_ARM64_SW_TTBR0_PAN /* * Set the TTBR0 PAN bit in SPSR. When the exception is taken from * EL0, there is no need to check the state of TTBR0_EL1 since * accesses are always enabled. * Note that the meaning of this bit differs from the ARMv8.1 PAN * feature as all TTBR0_EL1 accesses are disabled, not just those to * user mappings. */ alternative_if ARM64_HAS_PAN b 1f // skip TTBR0 PAN alternative_else_nop_endif .if \el != 0 mrs x21, ttbr0_el1 tst x21, #0xffff << 48 // Check for the reserved ASID orr x23, x23, #PSR_PAN_BIT // Set the emulated PAN in the saved SPSR b.eq 1f // TTBR0 access already disabled and x23, x23, #~PSR_PAN_BIT // Clear the emulated PAN in the saved SPSR .endif __uaccess_ttbr0_disable x21 1: #endif stp x22, x23, [sp, #S_PC] /* Not in a syscall by default (el0_svc overwrites for real syscall) */ .if \el == 0 mov w21, #NO_SYSCALL str w21, [sp, #S_SYSCALLNO] .endif /* * Set sp_el0 to current thread_info. */ .if \el == 0 msr sp_el0, tsk .endif /* * Registers that may be useful after this macro is invoked: * * x21 - aborted SP * x22 - aborted PC * x23 - aborted PSTATE */ .endm ``` 现在, 我们将详细研究`kernel_entry`宏. ``` .macro kernel_entry, el, regsize = 64 ``` 该宏接受2个参数:`el` 和 `regsize` . `el` 可以是 `0` 或 `1`, 具体取决于是否在EL0或EL1上生成了异常. 如果我们来自32位EL0, 则 `regsize` 为32, 否则为64. ``` .if \regsize == 32 mov w0, w0 // zero upper 32 bits of x0 .endif ``` 在32位模式下, 我们使用32位通用寄存器(`w0`而不是`x0`). `w0`在结构上映射到`x0`的下部. 所提供的代码片段通过向自身写入w0来将x0寄存器的高32位清零. ``` stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] ``` 这部分将所有通用寄存器保存在堆栈中. 请注意, 在[kernel_ventry](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L74) 中已经对堆栈指针进行了调整, 以适应所有需要被存储. 保存寄存器的顺序很重要, 因为在Linux中有一种特殊的结构[pt_regs](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/ptrace.h#L1190), 用于稍后在异常处理程序中访问保存的寄存器. 如您所见, 该结构不仅包含通用寄存器, 还包含其他一些信息, 这些信息大多数稍后会在`kernel_entry`宏中填充. 我建议您记住`pt_regs`结构, 因为在接下来的几课中我们将实现并使用类似的结构. ``` .if \el == 0 mrs x21, sp_el0 ``` x21现在包含异常终止的堆栈指针. 请注意, Linux中的任务将两个不同的堆栈用于用户和内核模式. 在用户模式下, 我们可以使用 `sp_el0` 寄存器来计算产生异常时的堆栈指针值. 这行非常重要, 因为我们需要在上下文切换期间交换堆栈指针. 在下一课中, 我们将详细讨论. ``` ldr_this_cpu tsk, __entry_task, x20 // Ensure MDSCR_EL1.SS is clear, ldr x19, [tsk, #TSK_TI_FLAGS] // since we can unmask debug disable_step_tsk x19, x20 // exceptions when scheduling. ``` `MDSCR_EL1.SS`位负责启用 “软件步骤异常”. 如果该位置1并且调试异常未屏蔽, 则在执行任何指令后都会生成异常. 这是调试器通常使用的. 从用户模式获取异常时, 我们需要首先检查[TIF_SINGLESTEP](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/thread_info.h#L93) 标志为当前任务设置. 如果是, 则表明任务正在调试器下执行, 我们必须将`MDSCR_EL1.SS`位置1. 在此代码中要了解的重要一点是如何获取有关当前任务的信息. 在Linux中, 每个进程或线程(稍后我都会将它们称为“任务”)都有一个[task_struct](https://github.com/torvalds/linux/blob/v4.14/include/linux/sched .h#L519) 与其关联. 该结构包含有关任务的所有元数据信息. 在`arm64`体系结构上`task_struct`嵌入另一个称为[thread_info](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/thread_info.h#L39), 以便始终可以将指向`task_struct`的指针用作指向`thread_info`的指针. `thread_info` 是标志与 `entry.S` 需要直接访问的其他一些低层值一起存储的地方. ``` mov x29, xzr // fp pointed to user-space ``` 尽管`x29`是通用寄存器, 但通常具有特殊含义. 它用作`框架指针`. 现在, 我想花一些时间来解释其目的. 编译函数时, 第一对指令通常负责在堆栈中存储旧的帧指针和链接寄存器值. (快速提醒一下:`x30`被称为链接寄存器, 它包含一个由`ret`指令使用的“返回地址”)然后分配一个新的堆栈帧, 以便它可以包含函数的所有局部变量, 并且帧指针寄存器设置为指向帧的底部. 每当函数需要访问某些局部变量时, 它仅向帧指针添加硬编码的偏移量. 现在想象一下发生了一个错误, 我们需要生成一个堆栈跟踪. 我们可以使用当前帧指针在堆栈中查找所有局部变量, 并且可以使用链接寄存器来确定调用者的确切位置. 接下来, 我们利用以下事实:旧的帧指针和链接寄存器的值始终保存在堆栈帧的开头, 而我们只是从那里读取它们. 获取调用者的帧指针之后, 我们现在也可以访问其所有局部变量. 递归地重复此过程, 直到到达堆栈顶部为止, 这称为“堆栈展开”. [ptrace](http://man7.org/linux/man-pages/man2/ptrace.2.html) 系统调用使用了类似的算法. 现在, 回到 `kernel_entry` 宏, 应该很清楚为什么在从EL0中获取异常后为什么需要清除`x29`寄存器. 这是因为在Linux中, 每个任务在用户和内核模式下都使用不同的堆栈, 因此拥有通用堆栈跟踪没有任何意义. ``` .else add x21, sp, #S_FRAME_SIZE ``` 现在我们在else子句中, 这意味着仅当我们处理从EL1提取的异常时, 此代码才有意义. 在这种情况下, 我们将重用旧堆栈, 所提供的代码段仅将原始`sp`值保存在`x21`寄存器中, 以备后用. ``` /* Save the task's original addr_limit and set USER_DS (TASK_SIZE_64) */ ldr x20, [tsk, #TSK_TI_ADDR_LIMIT] str x20, [sp, #S_ORIG_ADDR_LIMIT] mov x20, #TASK_SIZE_64 str x20, [tsk, #TSK_TI_ADDR_LIMIT] ``` 任务地址限制指定了可以使用的最大虚拟地址. 当用户进程以32位模式运行时, 此限制为 `2^32`. 对于64位内核, 它可以更大, 通常为`2^48`. 如果碰巧从32位EL1中获取了异常, 则需要将任务地址限制更改为[TASK_SIZE_64](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/memory.h#L80). 此外, 还需要保存原始地址限制, 因为需要先还原该地址限制, 然后才能将执行返回到用户模式. ``` mrs x22, elr_el1 mrs x23, spsr_el1 ``` 在开始处理异常之前, 必须先将`elr_el1`和`spsr_el1`保存在堆栈中. 我们尚未在RPI OS中完成此操作, 因为到目前为止, 我们始终返回到发生异常的位置. 但是, 如果我们需要在处理异常时进行上下文切换, 该怎么办?在下一课中, 我们将详细讨论这种情况. ``` stp lr, x21, [sp, #S_LR] ``` 链接寄存器和帧指针寄存器保存在堆栈中. 我们已经看到, 根据是从`EL0`还是从`EL1`提取异常, 帧指针的计算方式有所不同, 并且该计算的结果已存储在x21寄存器中. ``` /* * In order to be able to dump the contents of struct pt_regs at the * time the exception was taken (in case we attempt to walk the call * stack later), chain it together with the stack frames. */ .if \el == 0 stp xzr, xzr, [sp, #S_STACKFRAME] .else stp x29, x22, [sp, #S_STACKFRAME] .endif add x29, sp, #S_STACKFRAME ``` 此处填充了`pt_regs`结构的[stackframe](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/ptrace.h#L140) 属性. 该属性还包含链接寄存器和帧指针, 尽管这次使用的是 `elr_el1` 的值(现在位于 `x22` 中)而不是 `lr`. `stackframe`仅用于堆栈退卷. ```cpp #ifdef CONFIG_ARM64_SW_TTBR0_PAN alternative_if ARM64_HAS_PAN b 1f // skip TTBR0 PAN alternative_else_nop_endif .if \el != 0 mrs x21, ttbr0_el1 tst x21, #0xffff << 48 // Check for the reserved ASID orr x23, x23, #PSR_PAN_BIT // Set the emulated PAN in the saved SPSR b.eq 1f // TTBR0 access already disabled and x23, x23, #~PSR_PAN_BIT // Clear the emulated PAN in the saved SPSR .endif __uaccess_ttbr0_disable x21 1: #endif ``` `CONFIG_ARM64_SW_TTBR0_PAN` 参数防止内核直接访问用户空间内存. 如果您想知道什么时候有用, 可以阅读[this](https://kernsec.org/wiki/index.php/Exploit_Methods/Userspace_data_usage)文章. 现在, 我还将跳过对此工作原理的详细说明, 因为此类安全功能对于我们的讨论而言已超出范围. ``` stp x22, x23, [sp, #S_PC] ``` 这里, `elr_el1`和`spsr_el1`被保存在堆栈中. ``` /* Not in a syscall by default (el0_svc overwrites for real syscall) */ .if \el == 0 mov w21, #NO_SYSCALL str w21, [sp, #S_SYSCALLNO] .endif ``` `pt_regs`结构体具有[field](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/ptrace.h#L132), 指示当前异常是否为系统打电话或不打电话. 默认情况下, 我们假设不是. 等到第5课, 再详细了解`syscalls`的工作原理. ``` /* * Set sp_el0 to current thread_info. */ .if \el == 0 msr sp_el0, tsk .endif ``` 在内核模式下执行任务时, 不需要`sp_el0`. 其值先前已保存在堆栈中, 因此可以在`kernel_exit`宏中轻松恢复. 从这一点开始, `sp_el0`将用于持有指向当前[task_struct](https://github.com/torvalds/linux/blob/v4.14/include/linux/sched.h#L519)的指针访问. ### el1_irq 接下来要探讨的是负责处理从EL1提取的IRQ的处理程序. 从[向量表](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L374)中, 我们可以轻松地发现该处理程序称为 `el1_irq` 并在[此处](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L562) 中定义. 现在让我们看一下代码, 逐行检查它. ```cpp el1_irq: kernel_entry 1 enable_dbg #ifdef CONFIG_TRACE_IRQFLAGS bl trace_hardirqs_off #endif irq_handler #ifdef CONFIG_PREEMPT ldr w24, [tsk, #TSK_TI_PREEMPT] // get preempt count cbnz w24, 1f // preempt count != 0 ldr x0, [tsk, #TSK_TI_FLAGS] // get flags tbz x0, #TIF_NEED_RESCHED, 1f // needs rescheduling? bl el1_preempt 1: #endif #ifdef CONFIG_TRACE_IRQFLAGS bl trace_hardirqs_on #endif kernel_exit 1 ENDPROC(el1_irq) ``` 在此函数内部执行以下操作. * 调用`kernel_entry`和`kernel_exit`宏来保存和恢复处理器状态. 第一个参数指示异常来自`EL1`. * 调用`enable_dbg`宏可以屏蔽调试中断. 此时, 这样做是安全的, 因为已经保存了处理器状态, 即使在中断处理程序的中间发生了调试异常, 也可以正确处理它. 如果您想知道为什么首先需要在中断处理期间取消屏蔽调试异常, 请阅读[这里](https://github.com/torvalds/linux/commit/2a2830703a2371b47f7b50b1d35cb15dc0e2b717)提交消息. * `#ifdef CONFIG_TRACE_IRQFLAGS`块中的代码负责跟踪中断. 它记录2个事件:中断开始和结束. * `#ifdef CONFIG_PREEMPT`中的代码阻止访问当前任务标志, 以检查我们是否需要调用调度程序. 下一课将详细检查此代码. * `irq_handler` - 这是执行实际中断处理的地方. [irq_handler](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L351) 是一个宏, 定义如下. ``` .macro irq_handler ldr_l x1, handle_arch_irq mov x0, sp irq_stack_entry blr x1 irq_stack_exit .endm ``` 从代码中您可能会看到, `irq_handler`执行[handle_arch_irq](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/irq.c#L44)函数. 该函数通过特殊的堆栈(称为 `irq堆栈`)执行. 为什么有必要切换到其他堆栈?例如, 在RPI OS中, 我们没有这样做. 好吧, 我想这是没有必要的, 但是如果没有它, 将使用任务堆栈来处理中断, 而且我们永远无法确定还有多少要留给中断处理程序. 接下来, 我们需要查看 [handle_arch_irq](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/irq.c#L44). 看来这不是一个函数, 而是一个变量. 它在[set_handle_irq](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/irq.c#L46) 函数中设置. 但是谁来设置它, 那么中断到达这一点后会逐渐消失吗?我们将在本课程的下一章中找到答案. ### 结论 总而言之, 我可以说我们已经研究了低级中断处理代码, 并从向量表一直跟踪到 `handle_arch_irq` 的中断路径. 这就是中断离开体系结构特定代码并开始由驱动程序代码处理的关键所在. 下一章的目标是通过驱动程序源代码跟踪计时器中断的路径. ##### 上一页 3.1 [中断处理:RPi OS](../../../docs/lesson03/rpi-os.md) ##### 下一页 3.3 [中断处理:中断控制器](../../../docs/lesson03/linux/interrupt_controllers.md) ================================================ FILE: translations/zh-cn/lesson03/linux/timer.md ================================================ ## 3.4: Timers 通过检查全局中断控制器, 我们完成了上一章. 我们能够一直跟踪计时器中断的路径, 直到[bcm2836_chained_handle_irq](https://github.com/torvalds/linux/blob/v4.14/drivers/irqchip/irq-bcm2835.c#L246) 功能. 下一步是查看计时器驱动程序如何处理此中断. 但是, 在我们执行此操作之前, 您需要熟悉一些与计时器功能相关的重要概念. 所有这些都在[official kernel documentation](https://github.com/torvalds/linux/blob/v4.14/Documentation/timers/timekeeping.txt), 我强烈建议您阅读本文档. 但是对于那些忙于阅读它的人, 我可以对所提到的概念提供自己的简短解释. 1. **Clock sources** 每次您需要确切地确定现在是几点, 您都在使用时钟源框架. 通常, 时钟源被实现为单调的原子n位计数器, 该计数器从0计数到`2^(n-1)`, 然后回绕到0并重新开始. 时钟源还提供了将计数器转换为纳秒级值的方法. 1. **Clock events** 引入此抽象是为了允许任何人订阅计时器中断. 时钟事件框架将下一个事件的设计时间作为输入, 并以此为基础计算计时器硬件寄存器的适当值. 1. **sched_clock()** 此函数返回自系统启动以来的纳秒数. 通常通过直接读取定时器寄存器来实现. 经常调用此函数, 应针对性能进行优化. 在下一节中, 我们将了解如何使用系统定时器来实现时钟源, 时钟事件和`sched_clock`功能. ### BCM2835 系统计时器. 像往常一样, 我们通过在设备树中找到其位置来开始探索特定设备. 系统计时器节点已定义 [这里](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm283x.dtsi#L57). 您可以暂时打开此定义, 因为我们将多次引用它. 接下来, 我们需要使用 `compatible` 属性来找出相应驱动程序的位置. 可以找到驱动程序在 [这里](https://github.com/torvalds/linux/blob/v4.14/drivers/clocksource/bcm2835_timer.c). 我们要看的第一件事是 [bcm2835_timer](https://github.com/torvalds/linux/blob/v4.14/drivers/clocksource/bcm2835_timer.c#L42) 结构体. ``` struct bcm2835_timer { void __iomem *control; void __iomem *compare; int match_mask; struct clock_event_device evt; struct irqaction act; }; ``` 该结构包含驱动程序运行所需的所有状态. `控制` 和`比较` 字段保存相应的内存映射寄存器的地址, `match_mask` 用于确定我们将使用的4个可用定时器中断中的哪一个, `evt` 字段包含传递给时钟的结构事件框架和`act` 是一个irq动作, 用于将当前驱动程序与中断控制器连接. 接下来, 我们将看一下[bcm2835_timer_init](https://github.com/torvalds/linux/blob/v4.14/drivers/clocksource/bcm2835_timer.c#L83), 它是驱动程序初始化功能. 它很大, 但没有一开始就想的那么难. ```cpp static int __init bcm2835_timer_init(struct device_node *node) { void __iomem *base; u32 freq; int irq, ret; struct bcm2835_timer *timer; base = of_iomap(node, 0); if (!base) { pr_err("Can't remap registers\n"); return -ENXIO; } ret = of_property_read_u32(node, "clock-frequency", &freq); if (ret) { pr_err("Can't read clock-frequency\n"); goto err_iounmap; } system_clock = base + REG_COUNTER_LO; sched_clock_register(bcm2835_sched_read, 32, freq); clocksource_mmio_init(base + REG_COUNTER_LO, node->name, freq, 300, 32, clocksource_mmio_readl_up); irq = irq_of_parse_and_map(node, DEFAULT_TIMER); if (irq <= 0) { pr_err("Can't parse IRQ\n"); ret = -EINVAL; goto err_iounmap; } timer = kzalloc(sizeof(*timer), GFP_KERNEL); if (!timer) { ret = -ENOMEM; goto err_iounmap; } timer->control = base + REG_CONTROL; timer->compare = base + REG_COMPARE(DEFAULT_TIMER); timer->match_mask = BIT(DEFAULT_TIMER); timer->evt.name = node->name; timer->evt.rating = 300; timer->evt.features = CLOCK_EVT_FEAT_ONESHOT; timer->evt.set_next_event = bcm2835_time_set_next_event; timer->evt.cpumask = cpumask_of(0); timer->act.name = node->name; timer->act.flags = IRQF_TIMER | IRQF_SHARED; timer->act.dev_id = timer; timer->act.handler = bcm2835_time_interrupt; ret = setup_irq(irq, &timer->act); if (ret) { pr_err("Can't set up timer IRQ\n"); goto err_iounmap; } clockevents_config_and_register(&timer->evt, freq, 0xf, 0xffffffff); pr_info("bcm2835: system timer (irq = %d)\n", irq); return 0; err_iounmap: iounmap(base); return ret; } ``` Now let's take a closer look at this function. ``` base = of_iomap(node, 0); if (!base) { pr_err("Can't remap registers\n"); return -ENXIO; } ``` 它从映射内存寄存器开始并获得寄存器基地址. 您应该已经熟悉此部分. ``` ret = of_property_read_u32(node, "clock-frequency", &freq); if (ret) { pr_err("Can't read clock-frequency\n"); goto err_iounmap; } system_clock = base + REG_COUNTER_LO; sched_clock_register(bcm2835_sched_read, 32, freq); ``` 接下来, 初始化“ sched_clock”子系统. sched_clock每次执行时都需要访问计时器计数器寄存器, 并且 [bcm2835_sched_read](https://github.com/torvalds/linux/blob/v4.14/drivers/clocksource/bcm2835_timer.c#L52) 作为第一个参数传递来协助完成此任务. 第二个参数对应于计时器计数器的位数(在我们的示例中为32). 位的数量用于计算计数器将换为0的时间. 最后一个参数指定计时器频率 - 它用于将计时器计数器的值转换为纳秒. 计时器频率在设备树中的以下位置定义 [这](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm283x.dtsi#L65) 行. ``` clocksource_mmio_init(base + REG_COUNTER_LO, node->name, freq, 300, 32, clocksource_mmio_readl_up); ``` 下一行初始化时钟源框架. [clocksource_mmio_init](https://github.com/torvalds/linux/blob/v4.14/drivers/clocksource/mmio.c#L52) 根据内存映射寄存器初始化一个简单的时钟源. 在某些方面, 时钟源框架复制了 `sched_clock` 的功能, 并且需要访问相同的3个基本参数. * 计时器计数器寄存器的位置. * 计数器中的有效位数. * 计时器频率. 另外3个参数包括时钟源的名称, 其评级(用于对时钟源设备进行评级)以及可以读取定时器计数器寄存器的功能. ``` irq = irq_of_parse_and_map(node, DEFAULT_TIMER); if (irq <= 0) { pr_err("Can't parse IRQ\n"); ret = -EINVAL; goto err_iounmap; } ``` 此代码段用于查找Linux irq编号, 该编号对应于第三个计时器中断(编号3硬编码为 [DEFAULT_TIMER](https://github.com/torvalds/linux/blob/v4.14/drivers/clocksource/bcm2835_timer.c#L108) 不变). 快速提醒一下:Raspberry Pi系统计时器具有4组独立的计时器寄存器, 此处使用第三个. 如果回到设备树, 您可以找到 [打断](https://github.com/torvalds/linux/blob/v4.14/arch/arm/boot/dts/bcm283x.dtsi#L60) 属性. 此属性描述设备支持的所有中断, 以及这些中断如何映射到中断控制器线路. 它是一个数组, 其中每个项代表一个中断. 项目的格式特定于中断控制器. 在我们的例子中, 每个项目由2个数字组成:第一个指定一个中断存储区, 第二个指定一个中断存储区 - 银行内部的中断号. [irq_of_parse_and_map](https://github.com/torvalds/linux/blob/v4.14/drivers/of/irq.c#L41) 读取的值 `interrupts` 属性, 然后它使用第二个参数来查找我们感兴趣的支持的中断, 并为请求的中断返回Linux irq号. ``` timer = kzalloc(sizeof(*timer), GFP_KERNEL); if (!timer) { ret = -ENOMEM; goto err_iounmap; } ``` 在这里分配了 `bcm2835_timer` 结构的内存. ``` timer->control = base + REG_CONTROL; timer->compare = base + REG_COMPARE(DEFAULT_TIMER); timer->match_mask = BIT(DEFAULT_TIMER); ``` 接下来, 计算控制和比较寄存器的地址, 并将 `match_mask` 设置为 `DEFAULT_TIMER` 常量. ``` timer->evt.name = node->name; timer->evt.rating = 300; timer->evt.features = CLOCK_EVT_FEAT_ONESHOT; timer->evt.set_next_event = bcm2835_time_set_next_event; timer->evt.cpumask = cpumask_of(0); ``` 在此代码段中, [clock_event_device](https://github.com/torvalds/linux/blob/v4.14/include/linux/clockchips.h#L100) 被初始化. 这里最重要的属性是`set_next_event`, 它指向[bcm2835_time_set_next_event](https://github.com/torvalds/linux/blob/v4.14/drivers/clocksource/bcm2835_timer.c#L57) 函数. 时钟事件框架调用此函数以调度下一个中断. `bcm2835_time_set_next_event` 很简单 - 它更新比较寄存器, 以便在需要的时间间隔后安排中断. 这类似于我们所做的 [这里](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/timer.c#L17) 适用于RPi OS. ``` timer->act.flags = IRQF_TIMER | IRQF_SHARED; timer->act.dev_id = timer; timer->act.handler = bcm2835_time_interrupt; ``` 接下来, irq动作被初始化. 这里最重要的属性是`handler`, 它指向[bcm2835_time_interrupt](https://github.com/torvalds/linux/blob/v4.14/drivers/clocksource/bcm2835_timer.c#L67) - 这是在触发中断后调用的函数. 如果看一看, 您会发现它会将所有工作重定向到由时钟事件框架注册的事件处理程序. 我们将在一段时间后检查此事件处理程序. ``` ret = setup_irq(irq, &timer->act); if (ret) { pr_err("Can't set up timer IRQ\n"); goto err_iounmap; } ``` 配置irq操作后, 它将被添加到计时器中断的irq操作列表中. ``` clockevents_config_and_register(&timer->evt, freq, 0xf, 0xffffffff); ``` 最后, 通过调用初始化时钟事件框架 [clockevents_config_and_register](https://github.com/torvalds/linux/blob/v4.14/kernel/time/clockevents.c#L504). `evt` 结构和计时器频率作为前两个参数传递. 后2个参数仅在 `单次` 计时器模式下使用, 与我们当前的讨论无关. 现在, 我们一直跟踪到计时器中断的路径, 一直到`bcm2835_time_interrupt`函数为止, 但是我们仍然没有找到完成实际工作的地方. 在下一部分中, 我们将更深入地研究并发现中断进入时钟事件框架时如何处理. ### 在时钟事件框架中如何处理中断 在上一节中, 我们看到了处理计时器中断的实际工作已外包给时钟事件框架. 这是在 [以下](https://github.com/torvalds/linux/blob/v4.14/drivers/clocksource/bcm2835_timer.c#L74) 几行完成. ``` event_handler = ACCESS_ONCE(timer->evt.event_handler); if (event_handler) event_handler(&timer->evt); ``` 现在我们的目标是弄清楚到底是设置了`event_handler`以及调用后会发生什么. [clockevents_config_and_register](https://github.com/torvalds/linux/blob/v4.14/kernel/time/clockevents.c#L504) 函数是开始探索的好地方, 因为这是配置时钟事件框架的地方, 并且, 如果我们遵循此函数的逻辑, 最终我们应该找到如何设置 `event_handler`. 现在, 让我向您展示函数调用链, 这些函数调用将我们引导至所需的位置. 1. [clockevents_config_and_register](https://github.com/torvalds/linux/blob/v4.14/kernel/time/clockevents.c#L504) 这是顶级初始化功能. 1. [clockevents_register_device](https://github.com/torvalds/linux/blob/v4.14/kernel/time/clockevents.c#L449) 在此功能中, 计时器被添加到时钟事件设备的全局列表中. 1. [tick_check_new_device](https://github.com/torvalds/linux/blob/v4.14/kernel/time/tick-common.c#L300) 此功能检查当前设备是否适合用作 `刻度设备`. 如果是, 则将使用此类设备生成定期的滴答声, 内核的其余部分将使用这些滴答声来完成需要定期执行的所有工作. 1. [tick_setup_device](https://github.com/torvalds/linux/blob/v4.14/kernel/time/tick-common.c#L177) 此功能启动设备配置. 1. [tick_setup_periodic](https://github.com/torvalds/linux/blob/v4.14/kernel/time/tick-common.c#L144) 这是设备配置为周期性抽动的地方. 1. [tick_set_periodic_handler](https://github.com/torvalds/linux/blob/v4.14/kernel/time/tick-broadcast.c#L432) 终于我们到达了分配处理程序的地方! 如果看一下调用链中的最后一个函数, 您会发现Linux使用不同的处理程序, 具体取决于是否启用了广播. Tick广播用于唤醒空闲的CPU, 您可以阅读有关它的更多信息在 [这里](https://lwn.net/Articles/574962/) 但是我们将忽略它, 而专注于更通用的滴答处理程序. 一般情况下 [tick_handle_periodic](https://github.com/torvalds/linux/blob/v4.14/kernel/time/tick-common.c#L99) 接着 [tick_periodic](https://github.com/torvalds/linux/blob/v4.14/kernel/time/tick-common.c#L79) 函数被调用. 后面的正是我们感兴趣的功能. 让我在这里复制其内容. ```cpp /* * Periodic tick */ static void tick_periodic(int cpu) { if (tick_do_timer_cpu == cpu) { write_seqlock(&jiffies_lock); /* Keep track of the next tick event */ tick_next_period = ktime_add(tick_next_period, tick_period); do_timer(1); write_sequnlock(&jiffies_lock); update_wall_time(); } update_process_times(user_mode(get_irq_regs())); profile_tick(CPU_PROFILING); } ``` 此功能完成了一些重要的事情: 1. `tick_next_period` 被用于计算以便可以安排下一个滴答事件. 1. [do_timer](https://github.com/torvalds/linux/blob/v4.14/kernel/time/timekeeping.c#L2200) 被调用, 负责设置“jiffies”. `jiffies`是自上次系统重新启动以来的滴答声. `jiffies` 在不需要纳秒精度的情况下, 可以与 `sched_clock`函数相同的方式使用. 1. [update_process_times](https://github.com/torvalds/linux/blob/v4.14/kernel/time/timer.c#L1583) 被调用. 在这里, 当前执行的进程有机会进行需要定期执行的所有工作. 这项工作包括, 例如, 运行本地进程计时器, 或者最重要的是, 将滴答事件通知调度程序. ### 结论 现在您可以看到普通定时器中断的时间有多长, 但是我们从头到尾一直遵循它. 最重要的事情之一是, 我们终于到达了调度程序的调用位置. 调度程序是任何操作系统中最关键的部分之一, 它严重依赖计时器中断. 因此, 现在, 当我们看到调度程序功能在何处触发时, 就该讨论其实现了, 这是我们在下一课中要做的事情. ##### 上一页 3.3 [中断处理:中断控制器](../../../docs/lesson03/linux/interrupt_controllers.md) ##### 下一页 3.5 [中断处理:练习](../../../docs/lesson03/exercises.md) ================================================ FILE: translations/zh-cn/lesson03/rpi-os.md ================================================ ## 3.1: 中断 从第1课开始, 我们已经知道如何与硬件通信. 但是, 大多数情况下, 沟通方式并不是那么简单. 通常, 这种模式是异步的:我们向设备发送一些命令, 但它不会立即响应. 而是在工作完成时通知我们. 此类异步通知称为“中断”, 因为它们会中断正常的执行流并迫使处理器执行“中断处理程序”. 有一种设备在操作系统开发中特别有用:系统计时器. 它是一种可以配置为以某个预定频率定期中断处理器的设备. 在过程调度中使用计时器的一种特殊应用. 调度程序需要测量每个进程执行了多长时间, 并使用此信息选择要运行的下一个进程. 此测量基于计时器中断. 在下一课中, 我们将详细讨论进程调度, 但是现在, 我们的任务是初始化系统计时器并实现计时器中断处理程序. ### 中断与异常 在ARM.v8体系结构中, 中断是更笼统的术语:异常. 异常有4种 * **同步异常** 这种类型的异常总是由当前执行的指令引起. 例如, 您可以使用 `str` 指令将一些数据存储在不存在的内存位置. 在这种情况下, 将生成同步异常. 同步异常也可以用于生成 `软件中断`. 软件中断是由 `svc` 指令有意产生的同步异常. 我们将在第5课中使用该技术来实现系统调用. * **IRQ(中断请求)** 这些是正常的中断. 它们始终是异步的, 这意味着它们与当前执行的指令无关. 与同步异常相反, 它们始终不是由处理器本身生成的, 而是由外部硬件生成的. * **FIQ(快速中断请求)** 这种类型的异常称为“快速中断”, 仅出于优先处理异常的目的而存在. 可以将某些中断配置为“正常”, 将其他中断配置为“快速”. 快速中断将首先发出信号, 并将由单独的异常处理程序处理. `Linux`不使用快速中断, 我们也不会这样做. * **SError(系统错误)** 像`IRQ`和`FIQ`一样, `SError`异常是异步的, 由外部硬件生成. 与 `IRQ` 和 `FIQ` 不同, `SError` 始终表示某种错误情况. 您可以在[这里](https://community.arm.com/processors/f/discussions/3205/re-what-is-serror-detailed-explanation-is-required)找到一个示例来说明何时可以使用`SError`产生中断. ### 异常向量 每个异常类型都需要有自己的处理程序. 另外, 应该为每个不同的执行状态(在其中生成异常)定义单独的处理程序. 从异常处理的角度来看, 有4种执行状态很有趣. 如果我们在EL1工作, 则这些状态可以定义如下: 1. **EL1t** 与EL0共享堆栈指针时, `EL1`发生异常. 当 `SPSel` 寄存器的值为 `0` 时, 就会发生这种情况. 1. **EL1h** 为EL1分配了专用堆栈指针时, EL1发生了异常. 这意味着 `SPSel` 拥有值 `1`, 这是我们当前正在使用的模式. 1. **EL0_64** 以64位模式执行的EL0产生异常. 1. **EL0_32** 以32位模式执行的EL0产生异常. 总共, 我们需要定义16个异常处理程序(4个异常级别乘以4个执行状态). 一个保存所有异常处理程序地址的特殊结构称为 *exception vector table* 或 *vector table*. 向量表的结构在[AArch64-Reference-Manual](https://developer.arm.com/docs/ddi0487/ca/arm-architecture-reference-manual-armv8-for-armv8-a-architecture-profile) 第1876页的 `表D1-7向量与向量表基址的向量偏移量` 中定义. 您可以将向量表视为异常向量的数组, 其中每个异常向量(或处理程序)是负责处理特定异常的连续指令序列. 因此, 对于来自 `AArch64-参考手册` 的 `表D1-7`, 每个异常向量最多可以占用`0x80` 字节. 这虽然不多, 但是没有人阻止我们从异常向量跳转到其他内存位置. 我认为通过一个示例, 所有这些都将更加清晰, 因此现在该是时候看看RPI-OS中如何实现异常向量了. 与异常处理相关的所有内容都在 [entry.S](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/entry.S) 中进行了定义立即开始进行检查. 第一个有用的宏称为[ventry](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/entry.S#L12), 它用于创建向量表中的条目. ``` .macro ventry label .align 7 b \label .endm ``` 您可能会从该定义中推断出, 我们不会在异常向量内部处理异常, 而是跳转到为宏提供的标签, 它是 `label` 参数. 我们需要 `.align 7` 指令, 因为所有异常向量都应位于彼此偏移的 `0x80` 字节上. 向量表在[此处](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/entry.S#L64) 定义, 它由16个清单组成定义. 现在, 我们只对处理来自`EL1h`的`IRQ`感兴趣, 但是我们仍然需要定义所有`16`个处理程序. 这不是因为某些硬件要求, 而是因为我们希望看到有意义的错误消息, 以防出现问题. 所有不应该在正常流程中执行的处理程序都具有无效的后缀, 并使用[handle_invalid_entry](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/entry.S#L3)宏. 让我们看看如何定义此宏. ``` .macro handle_invalid_entry type kernel_entry mov x0, #\type mrs x1, esr_el1 mrs x2, elr_el1 bl show_invalid_entry_message b err_hang .endm ``` 在第一行中, 您可以看到使用了另一个宏:`kernel_entry`. 我们将在短期内讨论. 然后我们调用[show_invalid_entry_message](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/irq.c#L34)并为其准备3个参数. 第一个参数是可以采用[these](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/include/entry.h#L6)值之一的异常类型. 它准确地告诉我们执行了哪个异常处理程序. 第二个参数是最重要的参数, 称为 `ESR`, 代表异常综合征寄存器. 该参数取自 `esr_el1` 寄存器, 该寄存器在 `AArch64-Reference-Manual` 的第2431页中进行了描述. 该寄存器包含有关导致异常的原因的详细信息. 第三个参数主要在同步异常的情况下很重要. 它的值取自我们熟悉的 `elr_el1` 寄存器, 其中包含生成异常时已执行的指令的地址. 对于同步异常, 这也是导致异常的指令. 在`show_invalid_entry_message`函数将所有这些信息打印到屏幕之后, 我们将处理器置于无限循环中, 因为我们无能为力了. ### 保存寄存器状态 异常处理程序完成执行后, 我们希望所有通用寄存器具有与生成异常之前相同的值. 如果我们不实现这种功能, 则与当前正在执行的代码无关的中断可能会不可预测地影响该代码的行为. 这就是为什么在生成异常后我们要做的第一件事就是保存处理器状态. 这是在[kernel_entry](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/entry.S)宏中完成的. 这个宏非常简单:它只将寄存器`x0-x30`存储到堆栈中. 还有一个相应的宏[kernel_exit](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/entry.S#L37), 在异常处理程序完成执行. `kernel_exit`通过复制回`x0-x30`寄存器的值来恢复处理器状态. 它还执行 `eret` 指令, 这使我们返回到正常的执行流程. 顺便说一句, 通用寄存器并不是执行异常处理程序之前唯一需要保存的内容, 但是对于我们现在的简单内核而言, 这已经足够了. 在后面的课程中, 我们将为`kernel_entry`和`kernel_exit`宏添加更多功能. ### 设置向量表 好的, 现在我们准备了向量表, 但是处理器不知道它的位置, 因此无法使用它. 为了使异常处理有效, 我们必须将 `vbar_el1`(向量基址寄存器)设置为向量表地址. 这是在[此处](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/irq.S#L2)完成的. ``` .globl irq_vector_init irq_vector_init: adr x0, vectors // load VBAR_EL1 with virtual msr vbar_el1, x0 // vector table address ret ``` ### 屏蔽/取消屏蔽中断 我们需要做的另一件事是取消屏蔽所有类型的中断. 让我解释一下“取消屏蔽”中断的含义. 有时有必要告诉您, 特定的代码段绝不能被异步中断拦截. 想象一下, 例如, 如果在`kernel_entry`宏的中间发生中断, 会发生什么?在这种情况下, 处理器状态将被覆盖并丢失. 这就是为什么每当执行异常处理程序时, 处理器都会自动禁用所有类型的中断. 这称为“遮罩”, 如果需要, 也可以手动完成. 许多人错误地认为必须在异常处理程序的整个过程中屏蔽中断. 这是不正确的-在保存处理器状态后取消屏蔽中断是完全合法的, 因此嵌套嵌套的中断也是合法的. 我们现在不打算这样做, 但是这是要记住的重要信息. [以下两个功能](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/irq.S#L7-L15)负责屏蔽和取消屏蔽中断. ``` .globl enable_irq enable_irq: msr daifclr, #2 ret .globl disable_irq disable_irq: msr daifset, #2 ret ``` ARM处理器状态有4位, 负责保持不同类型中断的屏蔽状态. 这些位定义如下. * **D** 屏蔽调试异常. 这些是同步异常的一种特殊类型. 出于明显的原因, 不可能屏蔽所有同步异常, 但是使用单独的标志可以屏蔽调试异常很方便. * **A** 掩盖`SErrors`. 之所以称为 `A`, 是因为有时将 `SErrors` 称为异步中止. * **I** 口罩 `IRQs` * **F** Masks `FIQs` 现在您可能会猜出为什么负责更改中断屏蔽状态的寄存器称为 `daifclr` 和 `daifset` . 这些寄存器在处理器状态下设置和清除中断屏蔽状态位. 您可能想知道的最后一件事是为什么我们在两个函数中都使用常量值 `2`?这是因为我们只想设置并清除第二个(I)位. ### 配置中断控制器 设备通常不直接中断处理器:相反, 它们依靠中断控制器来完成工作. 中断控制器可用于启用/禁用硬件发送的中断. 我们还可以使用中断控制器来确定哪个设备产生了中断. Raspberry PI具有自己的中断控制器, 该控制器在 [BCM2837 ARM 外设手册](https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2) 的第109页上进行了描述. Raspberry Pi中断控制器具有3个寄存器, 用于保存所有类型的中断的启用/禁用状态. 目前, 我们仅对计时器中断感兴趣, 可以使用[ENABLE_IRQS_1](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/include/外设/irq.h#L10)寄存器, 在 `BCM2837 ARM外设手册` 的第116页上进行了介绍. 根据文档, 中断分为2个存储区. 第一个存储区由中断 `0-31` 组成, 可以通过将寄存器 `ENABLE_IRQS_1` 的不同位置1来启用或禁用这些中断. 最后32个中断还有一个对应的寄存器 - `ENABLE_IRQS_2`和一个控制一些常见中断以及ARM本地中断的寄存器 - `ENABLE_BASIC_IRQS`(在本课程的下一章中将讨论ARM本地中断). 但是, 《外围设备手册》有很多错误, 其中之一与我们的讨论直接相关. 外围设备中断表(在手册第113页上进行了说明)应在 `0-3` 行包含4个来自系统定时器的中断. 从逆向工程Linux源代码并阅读[其他一些资源](http://embedded-xinu.readthedocs.io/en/latest/arm/rpi/BCM2835-System-Timer.html), 我能够弄清楚该计时器中断0和2被保留并由GPU使用, 中断1和3可以用于任何其他目的. 因此, 这是启用系统计时器IRQ编号1的[功能](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/irq.c#L29). ``` void enable_interrupt_controller() { put32(ENABLE_IRQS_1, SYSTEM_TIMER_IRQ_1); } ``` ### 通用IRQ处理程序 从前面的讨论中, 您应该记住, 我们只有一个异常处理程序, 负责处理所有的 `IRQ` . 此处理程序在[此处](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/irq.c#L39) 中定义. ``` void handle_irq(void) { unsigned int irq = get32(IRQ_PENDING_1); switch (irq) { case (SYSTEM_TIMER_IRQ_1): handle_timer_irq(); break; default: printf("Unknown pending irq: %x\r\n", irq); } } ``` 在处理程序中, 我们需要一种方法来确定哪个设备负责产生中断. 中断控制器可以帮助我们完成此工作:它具有`IRQ_PENDING_1`寄存器, 该寄存器保存中断`0-31`的中断状态. 使用该寄存器, 我们可以检查当前中断是由计时器还是由其他设备产生的, 并调用设备特定的中断处理程序. 注意, 多个中断可以同时挂起. 这就是每个设备特定的中断处理程序必须确认已完成对中断的处理的原因, 只有在`IRQ_PENDING_1`中的该中断挂起位被清除后, 该原因才会被清除. 由于相同的原因, 对于准备投入生产的`OS`, 您可能希望在循环中将开关构造包装在中断处理程序中:这样, 您将能够在单个处理程序执行期间处理多个中断. ### Timer initialization Raspberry Pi系统计时器是一个非常简单的设备. 它具有一个计数器, 该计数器在每个时钟滴答之后将其值增加1. 它还具有连接到中断控制器的4条中断线(因此它可以生成4个不同的中断)和4个相应的比较寄存器. 当计数器的值等于存储在比较寄存器之一中的值时, 将触发相应的中断. 这就是为什么在我们能够使用系统定时器中断之前, 我们需要使用一个非零值初始化比较寄存器之一, 该值越大-越晚生成中断. 这是在[timer_init](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/timer.c#L8) 函数中完成的. ``` const unsigned int interval = 200000; unsigned int curVal = 0; void timer_init ( void ) { curVal = get32(TIMER_CLO); curVal += interval; put32(TIMER_C1, curVal); } ``` 第一行读取当前计数器值, 第二行增加当前计数器值, 第三行为中断编号1设置比较寄存器的值. 通过操作“`interval` 值, 您可以调整第一次定时器中断的产生时间. ### 处理计时器中断 最后, 我们到达了计时器中断处理程序. 实际上很简单. ``` void handle_timer_irq( void ) { curVal += interval; put32(TIMER_C1, curVal); put32(TIMER_CS, TIMER_CS_M1); printf("Timer iterrupt received\n\r"); } ``` 在这里, 我们首先更新比较寄存器, 以便在相同的时间间隔后产生下一个中断. 接下来, 我们通过将1写入“`TIMER_CS` 寄存器来确认中断. 在文档 `TIMER_CS` 中称为 `计时器控制/状态` 寄存器. 该寄存器的位[0:3]可用于确认来自4条可用中断线之一的中断. ### 结论 您可能要看的最后一件事是[kernel_main](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson03/src/kernel.c#L7) 功能, 其中协调了所有先前讨论的功能. 编译并运行示例后, 应在中断发生后输出 `收到计时器中断`消息. 请尝试自己动手做, 不要忘记仔细检查代码并进行试验. ##### 上一页 2.3 [Processor initialization: Exercises](../../docs/lesson02/exercises.md) ##### 下一页 3.2 [Interrupt handling: Low-level exception handling in Linux](../../docs/lesson03/linux/low_level-exception_handling.md) ================================================ FILE: translations/zh-cn/lesson04/exercises.md ================================================ ## 4.5: 练习题 1. 将`printf`添加到所有主要的内核函数中, 以输出有关当前内存和处理器状态的信息. 确保在本课的RPi OS部分的末尾添加的状态图是正确的. (您不必每次都输出所有状态, 但是, 一旦发生一些重大事件, 您就可以输出当前的堆栈指针, 刚分配的对象的地址或您认为必要的任何东西. 防止信息溢出) 1. 介绍一种为任务分配优先级的方法. 确保具有较高优先级的任务比具有较低优先级的任务获得更多的处理器时间. 1. 允许用户进程使用FP /SIMD寄存器. 这些寄存器应保存在任务上下文中, 并在上下文切换期间交换. 1. 允许内核具有无限数量的任务(现在限制为64). 1. 通过第04课在qemu上运行. 校验 [这个](https://github.com/s-matyukevich/raspberry-pi-os/issues/8) issue 以供参考. ##### 上一页 4.4 [流程调度程序:调度程序](../../docs/lesson04/linux/scheduler.md) ##### 下一页 5.1 [用户进程和系统调用:RPi OS](../../docs/lesson05/rpi-os.md) ================================================ FILE: translations/zh-cn/lesson04/linux/basic_structures.md ================================================ ## 4.2: 调度程序的基本结构 在先前的所有课程中, 我们主要使用体系结构特定代码或驱动程序代码, 现在这是我们第一次深入研究Linux内核的核心. 该任务并不简单, 需要做一些准备工作:在能够理解Linux调度程序源代码之前, 您需要熟悉调度程序所基于的一些主要概念. ### [task_struct](https://github.com/torvalds/linux/blob/v4.14/include/linux/sched.h#L519) 这是整个内核中最关键的结构之一-它包含有关正在运行的任务的所有信息. 我们已经在第2课中简要介绍了 `task_struct` , 甚至为RPi OS实现了自己的 `task_struct` , 因此我认为到那时您应该已经对如何使用它有了一个基本的了解. 现在, 我想强调该结构中与我们的讨论相关的一些重要领域. * [thread_info](https://github.com/torvalds/linux/blob/v4.14/include/linux/sched.h#L525) 这是 `task_struct` 的第一个字段, 它包含所有必须由低级架构代码访问的字段. 我们已经在第2课中看到了这是如何发生的, 以后还会遇到其他一些示例. [thread_info](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/thread_info.h#L39) 是特定于体系结构的. 在 `arm64` 情况下, 它是一个具有几个字段的简单结构. ```cpp struct thread_info { unsigned long flags; /* low level flags */ mm_segment_t addr_limit; /* address limit */ #ifdef CONFIG_ARM64_SW_TTBR0_PAN u64 ttbr0; /* saved TTBR0_EL1 */ #endif int preempt_count; /* 0 => preemptable, <0 => bug */ }; ``` `flags` 字段使用非常频繁-它包含有关当前任务状态的信息(是否处于跟踪状态, 信号是否挂起等). 可以找到所有可能的标志值 [这里](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/thread_info.h#L79). * [state](https://github.com/torvalds/linux/blob/v4.14/include/linux/sched.h#L528) 任务当前状态 (它是否正在运行, 正在等待中断, 退出等. ). 描述了所有可能的任务状态在 [这里](https://github.com/torvalds/linux/blob/v4.14/include/linux/sched.h#L69). * [stack](https://github.com/torvalds/linux/blob/v4.14/include/linux/sched.h#L536) 在RPi OS上工作时, 我们已经看到`task_struct`始终位于任务堆栈的底部, 因此我们可以使用指向`task_struct`的指针作为指向堆栈的指针. 内核堆栈的大小恒定, 因此找到堆栈末端也是一项容易的任务. 我认为在Linux内核的早期版本中使用了相同的方法, 但是现在, 在引入Linux内核之后的[虚拟映射堆栈 vitually mapped stacks](https://lwn.net/Articles/692208/), `stack` 字段用于存储指向内核堆栈的指针. * [thread](https://github.com/torvalds/linux/blob/v4.14/include/linux/sched.h#L1108) 另一个重要的体系结构特定结构是 [thread_struct](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/processor.h#L81). 它包含所有信息 (如 [cpu_context](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/processor.h#L65)) 在上下文切换期间使用. 实际上, RPi OS实现了自己的`cpu_context`, 其使用方式与原始方式完全相同. * [sched_class and sched_entity](https://github.com/torvalds/linux/blob/v4.14/include/linux/sched.h#L562-L563) 这些字段用于调度算法 - 关于它们的更多内容如下. ### 调度程序类 在Linux中, 有一个可扩展的机制, 该机制允许每个任务使用其自己的调度算法. 该机制使用一种结构 [sched_class](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/sched.h#L1400). 您可以将此结构视为定义了`schedules类`必须实现的所有方法的接口. 让我们看看在`sched_class`接口中定义了哪种方法. (未显示所有方法, 仅显示了我认为对我们最重要的方法) * [enqueue_task](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/sched.h#L1403) 每次将新任务添加到调度程序类时都会执行. * [dequeue_task](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/sched.h#L1404) 可以从调度程序中删除任务时调用. * [pick_next_task](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/sched.h#L1418) 当核心调度程序代码需要确定接下来要运行的任务时, 将调用此方法. * [task_tick](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/sched.h#L1437) 在每个计时器滴答时调用, 使调度程序类有机会测量当前任务的执行时间, 并在需要抢占时通知核心调度程序代码. 有一些`sched_class`实现. 通常用于所有用户任务的最常用的一种称为“完全公平调度程序(CFS)” ### 完全公平的调度程序 Completely Fair Scheduler (CFS) CFS算法背后的原理非常简单: 1. 对于系统中的每个任务, CFS衡量已为其分配了多少CPU时间(该值存储在[sum_exec_runtime](https://github.com/torvalds/linux/blob/v4.14/include/linux/sched.h#L385) `sched_entity`结构的字段) 1. `sum_exec_runtime` 根据任务优先级进行调整并保存在 [vruntime](https://github.com/torvalds/linux/blob/v4.14/include/linux/sched.h#L386) 区域. 1. 当`CFS`需要选择一个新任务来执行时, 将选择 `vruntime` 最小的任务. Linux调度程序使用另一个重要的数据结构, 称为 `运行队列`, 由 [rq](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/sched.h#L667) 结构. 每个CPU只有一个运行队列实例. 当需要选择一个新任务来执行时, 仅从本地运行队列中进行选择. 但是, 如果有需要, 可以在不同的`rq`结构之间平衡任务. 所有调度程序类不仅使用CFS, 还使用运行队列. 所有CFS特定信息都保存在 [cfs_rq](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/sched.h#L420) 结构, 嵌入在`rq`结构中. `cfs_rq`结构的一个重要字段称为 [min_vruntime](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/sched.h#L425) — 这是分配给运行队列的所有任务中最低的 `vruntime` . 将 `min_vruntime` 分配给新分叉的任务-这确保了下一步将选择该任务, 因为CFS始终确保选择具有最小 `vruntime`的任务. 这种方法还可以确保新任务在被抢占之前不会长时间运行. 分配给特定运行队列并由CFS跟踪的所有任务都保留在 [tasks_timeline](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/sched.h#L430) `cfs_rq` 结构的字段. `tasks_timeline` 代表一个 [Red–black tree](https://en.wikipedia.org/wiki/Red%E2%80%93black_tree), 可以用来选择按其 `vruntime` 值排序的任务. 红黑树有一个重要的属性:对它的所有操作(搜索, 插入, 删除)都可以在 [O(log n)](https://en.wikipedia.org/wiki/Big_O_notation) 时间. 这意味着即使系统中有成千上万的并发任务, 所有调度程序方法仍将非常快速地执行. 红黑树的另一个重要属性是, 对于树中的任何节点, 其右子节点的 `vruntime`值将始终大于父节点, 而左子节点的 `vruntime` 将始终小于或等于父节点的 `vruntime`. 这有一个重要的含义:最左边的节点始终是 `vruntime` 最小的节点. ### [struct pt_regs](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/ptrace.h#L119) 我已经向您展示了在发生中断时如何将所有通用寄存器保存在堆栈上-我们探索了该过程如何在Linux内核中工作, 并为RPi OS实现了类似的过程. 我还没有提到的是, 在处理中断时操纵这些寄存器是完全合法的. 手动准备一组寄存器并将它们放到堆栈上也是合法的-当内核线程移至用户模式时便完成了, 下一课我们将实现相同的功能. 现在, 您只需要记住, `pt_regs`结构用于描述已保存的寄存器, 并且必须将其字段的顺序与寄存器的顺序相同, 并保存在`kernel_entry`宏中. 在本课程中, 我们将看到有关如何使用此结构的一些示例. ### 结论 与调度有关的结构, 算法和概念要重要得多, 但是我们刚刚探讨的是理解下两章所需的最小集合. 现在是时候看看所有这些实际如何工作了. ##### 上一页 4.1 [进程调度程序:RPi OS调度程序](../../../docs/lesson04/rpi-os.md) ##### 下一页 4.3 [流程计划程序:分派任务](../../../docs/lesson04/linux/fork.md) ================================================ FILE: translations/zh-cn/lesson04/linux/fork.md ================================================ ## 4.3: Forking a task 调度就是从可用任务列表中选择合适的任务来运行. 但是在调度程序能够完成其工作之前, 我们需要以某种方式填写此列表. 创建新任务的方式是本章的主题. 现在, 我们只希望专注于内核线程, 并将对用户模式功能的讨论推迟到下一堂课. 但是, 并非在所有地方都有可能, 因此也准备学习一些有关在用户模式下执行任务的知识. ### 初始化任务 启动内核后, 将运行一个任务:init任务. 相应的`task_struct`被定义在 [这里](https://github.com/torvalds/linux/blob/v4.14/init/init_task.c#L20) and is initialized by [INIT_TASK](https://github.com/torvalds/linux/blob/v4.14/include/linux/init_task.h#L226) 宏. 此任务对系统至关重要, 因为系统中的所有其他任务均源于该任务. ### 创建新任务 在Linux中, 不可能从头开始创建新任务-而是从当前正在运行的任务派生所有任务. 现在, 正如我们从初始任务的来源所看到的那样, 我们可以尝试探索如何从中创建新任务. 有四种创建新任务的方式. 1. [fork](http://man7.org/linux/man-pages/man2/fork.2.html) 系统调用将创建当前进程的完整副本, 包括其虚拟内存, 并用于创建新进程(而非线程). 该系统调用已定义在 [这里](https://github.com/torvalds/linux/blob/v4.14/kernel/fork.c#L2116). 1. [vfork](http://man7.org/linux/man-pages/man2/vfork.2.html) 系统调用与 `fork` 类似, 但是区别在于子代重用了父虚拟内存以及堆栈, 并且父代被阻塞, 直到子代执行完毕. 可以找到此系统调用的定义在 [这里](https://github.com/torvalds/linux/blob/v4.14/kernel/fork.c#L2128). 1. [clone](http://man7.org/linux/man-pages/man2/clone.2.html) 系统调用是最灵活的系统调用-它也复制当前任务, 但它允许使用`flags` 参数自定义过程, 并允许配置子任务的入口点. 在下一课中, 我们将了解如何实现 `glibc` 克隆包装器功能-该包装器允许使用 `clone` 系统调用来创建新线程. 1. 最后, [kernel_thread](https://github.com/torvalds/linux/blob/v4.14/kernel/fork.c#L2109) 函数可用于创建新的内核线程. 以上所有函数调用 [_do_fork](https://github.com/torvalds/linux/blob/v4.14/kernel/fork.c#L2020), 接受以下参数. * `clone_flags` 标志用于配置派生行为. 可以找到标志的完整列表在 [这里](https://github.com/torvalds/linux/blob/v4.14/include/uapi/linux/sched.h#L8). * `stack_start` 在`clone` `syscall`的情况下, 此参数指示新任务的用户堆栈的位置. 如果 `kernel_thread` 调用 `_do_fork` , 则此参数指向需要在内核线程中执行的函数. * `stack_size` 在 `arm64` 体系结构中, 仅当 `_do_fork` 由 `kernel_thread` 调用时, 才使用此参数. 它是指向需要传递给内核线程函数的参数的指针. (是的, 我也发现最后两个参数的命名具有误导性) * `parent_tidptr` `child_tidptr` 那两个参数仅在`clone` `syscall`中使用. Fork会将子线程ID存储在父代内存中的 `parent_tidptr` 位置, 也可以将父代ID存储在 `child_tidptr` 位置. * `tls` [Thread Local Storage 线程本地存储](https://en.wikipedia.org/wiki/Thread-local_storage) ### Fork procedure 接下来, 我要突出显示在 `_do_fork` 执行期间发生的最重要事件, 并保持其顺序. 1. [_do_fork](https://github.com/torvalds/linux/blob/v4.14/kernel/fork.c#L2020) 调用 [copy_process](https://github.com/torvalds/linux/blob/v4.14/kernel/fork.c#L1539) `copy_process`负责配置新的`task_struct`. 1. `copy_process` 调用 [dup_task_struct](https://github.com/torvalds/linux/blob/v4.14/kernel/fork.c#L512), 分配新的`task_struct`并复制原始字段的所有字段. 实际复制发生在特定于体系结构的地方 [arch_dup_task_struct](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/process.c#L244) 1. 分配了新的内核堆栈. 如果启用了 `CONFIG_VMAP_STACK` , 内核将使用 [virtually mapped stacks](https://lwn.net/Articles/692208/) 防止内核堆栈溢出. [link](https://github.com/torvalds/linux/blob/v4.14/kernel/fork.c#L525) 1. 任务的凭据已复制. [link](https://github.com/torvalds/linux/blob/v4.14/kernel/fork.c#L1628) 1. 通知调度程序有新任务派生. [link](https://github.com/torvalds/linux/blob/v4.14/kernel/fork.c#L1727) 1. [task_fork_fair](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/fair.c#L9063) 调用CFS调度程序类的方法. 此方法为当前正在运行的任务更新`vruntime`值(在[update_curr](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/fair.c#L827) 内部完成) )并更新当前运行队列的 `min_vruntime` 值(内部 [update_min_vruntime](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/fair.c#L514)). 然后将 `min_vruntime` 值分配给分叉的任务-这样可以确保下一步提取该任务. 请注意, 目前还没有将新任务添加到`task_timeline`中. 1. 许多不同的属性(例如, 有关文件系统, 打开的文件, 虚拟内存, 信号, 名称空间的信息)可以从当前任务中重用或复制. 通常根据`clone_flags`参数来决定是复制内容还是重用当前属性. [link](https://github.com/torvalds/linux/blob/v4.14/kernel/fork.c#L1731-L1765) 1. [copy_thread_tls](https://github.com/torvalds/linux/blob/v4.14/kernel/fork.c#L1766) 被称为反过来又调用特定于体系结构 [copy_thread](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/process.c#L254) 函数. 这个功能值得特别注意, 因为它可以作为 [copy_process](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/fork.c#L5) RPi OS中的函数, 我想更深入地研究它. ### copy_thread 整个功能在下面列出. ```cpp int copy_thread(unsigned long clone_flags, unsigned long stack_start, unsigned long stk_sz, struct task_struct *p) { struct pt_regs *childregs = task_pt_regs(p); memset(&p->thread.cpu_context, 0, sizeof(struct cpu_context)); if (likely(!(p->flags & PF_KTHREAD))) { *childregs = *current_pt_regs(); childregs->regs[0] = 0; /* * Read the current TLS pointer from tpidr_el0 as it may be * out-of-sync with the saved value. */ *task_user_tls(p) = read_sysreg(tpidr_el0); if (stack_start) { if (is_compat_thread(task_thread_info(p))) childregs->compat_sp = stack_start; else childregs->sp = stack_start; } /* * If a TLS pointer was passed to clone (4th argument), use it * for the new thread. */ if (clone_flags & CLONE_SETTLS) p->thread.tp_value = childregs->regs[3]; } else { memset(childregs, 0, sizeof(struct pt_regs)); childregs->pstate = PSR_MODE_EL1h; if (IS_ENABLED(CONFIG_ARM64_UAO) && cpus_have_const_cap(ARM64_HAS_UAO)) childregs->pstate |= PSR_UAO_BIT; p->thread.cpu_context.x19 = stack_start; p->thread.cpu_context.x20 = stk_sz; } p->thread.cpu_context.pc = (unsigned long)ret_from_fork; p->thread.cpu_context.sp = (unsigned long)childregs; ptrace_hw_copy_thread(p); return 0; } ``` 您可能已经对其中的某些代码有些熟悉. 让我们更深入地研究它. ``` struct pt_regs *childregs = task_pt_regs(p); ``` 该函数从分配新的开始 [pt_regs](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/ptrace.h#L119) 结构. 该结构用于提供对在`kernel_entry`中保存的寄存器的访问. 然后, 可以使用childregs变量准备新创建任务所需的任何状态. 如果任务然后决定转移到用户模式, 则状态将由`kernel_exit`宏恢复. 这里要了解的重要一点是 [task_pt_regs](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/processor.h#L161) 宏不分配任何东西 - 它只是计算内核堆栈上的位置, 即 `kernel_entry` 存储寄存器, 对于新创建的任务, 该位置将始终位于内核堆栈的顶部. ``` memset(&p->thread.cpu_context, 0, sizeof(struct cpu_context)); ``` 接下来, 清除分叉的任务 `cpu_context`. ``` if (likely(!(p->flags & PF_KTHREAD))) { ``` 然后进行检查以确定我们正在创建内核还是用户线程. 现在, 我们仅对内核线程情况感兴趣, 我们将在下一课中讨论第二种选择. ``` memset(childregs, 0, sizeof(struct pt_regs)); childregs->pstate = PSR_MODE_EL1h; if (IS_ENABLED(CONFIG_ARM64_UAO) && cpus_have_const_cap(ARM64_HAS_UAO)) childregs->pstate |= PSR_UAO_BIT; p->thread.cpu_context.x19 = stack_start; p->thread.cpu_context.x20 = stk_sz; ``` 如果我们正在创建内核线程, 则将`cpu_context`的`x19`和`x20`寄存器设置为指向需要执行的函数(`stack_start`)及其参数(`stk_sz`). 将CPU切换到分叉任务后, [ret_from_fork](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L942) 将使用这些寄存器跳转到所需的功能. (我不太明白为什么我们还要在这里设置`childregs-> pstate`. `ret_from_fork`在跳转到存储在`x19`中的函数之前不会调用`kernel_exit`, 即使内核线程决定转到用户模式`childregs`将会被覆盖. 有什么想法吗?) ``` p->thread.cpu_context.pc = (unsigned long)ret_from_fork; p->thread.cpu_context.sp = (unsigned long)childregs; ``` 接下来的`cpu_context.pc`设置为`ret_from_fork`指针-这确保我们在第一次上下文切换后返回到`ret_from_fork`. `cpu_context.sp`被设置为`childregs`的正下方. 我们仍然需要在堆栈顶部使用 `childregs`, 因为在内核线程完成执行之后, 任务将被移至用户模式, 并且将使用 `childregs` 结构. 在下一课中, 我们将详细讨论这种情况. 就是关于`copy_thread`函数. 现在, 让我们回到派生过程中的位置. ### Fork procedure (continued) 1. 在`copy_process`成功为分叉任务`_do_fork`准备好`task_struct`之后, 现在可以通过调用来运行它 [wake_up_new_task](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/core.c#L2438). 这个被完成在 [这](https://github.com/torvalds/linux/blob/v4.14/kernel/fork.c#L2074). 然后任务状态更改为 `TASK_RUNNING` 和 [enqueue_task_fair](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/fair.c#L4879) 调用CFS方法, 该方法触发执行 [__enqueue_entity](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/fair.c#L549) 实际上将任务添加到`task_timeline`红黑树中. 1. 在 [这](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/core.c#L2463) 行, [check_preempt_curr](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/core.c#L871) 被调用, 依次调用 [check_preempt_wakeup](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/fair.c#L6167) CFS 方法. 此方法负责检查当前任务是否应被其他任务抢占. 这正是即将发生的事情, 因为我们刚刚在时间轴上放置了一个新任务, 而该任务的`vruntime`可能性极小. 所以 [resched_curr](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/core.c#L479) 函数被触发, 为当前任务设置 `TIF_NEED_RESCHED` 标志. 1. `TIF_NEED_RESCHED` 在当前任务从异常处理程序退出之前进行检查( `fork`, `vfork`和`clone`都是系统调用, 每个系统调用都是一种特殊的异常类型. ) 可以查看[这里](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L801). 注意 [_TIF_WORK_MASK](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/thread_info.h#L109) 包括 ` _TIF_NEED_RESCHED` . 同样重要的是要了解, 在创建内核线程的情况下, 直到下一个定时器滴答或父任务挥发性地调用`schedule()`时, 才会启动新线程. 1. 如果当前任务需要重新安排, [do_notify_resume](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/signal.c#L744) 被触发, 依次调用 [schedule](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/core.c#L3418). 最终, 我们到达了触发任务计划的地步, 我们将在此点停止. ### 结论 既然您了解了如何创建新任务并将其添加到调度程序, 现在该看看调度程序本身如何工作以及如何实现上下文切换了. 这是我们将在下一章中探讨的内容. ##### 上一页 4.2 [流程调度程序:调度程序的基本结构](../../../docs/lesson04/linux/basic_structures.md) ##### 下一页 4.4 [流程调度程序:调度程序](../../../docs/lesson04/linux/scheduler.md) ================================================ FILE: translations/zh-cn/lesson04/linux/scheduler.md ================================================ ## 4.4: Scheduler 我们已经了解了许多有关Linux调度程序内部工作原理的详细信息, 因此我们所剩无几. 为了使本章更加完整, 我们将介绍两个重要的调度程序入口点: 1. [scheduler_tick()](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/core.c#L3003) 函数, 在每个定时器中断时调用. 1. [schedule()](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/core.c#L3418) 函数, 每次需要重新安排当前任务时调用. 本章要研究的第三项主要内容是上下文切换的概念. 上下文切换是挂起当前任务并运行另一个任务的过程-该过程是高度特定于体系结构的, 并且与我们使用RPi OS时所做的工作紧密相关. ### scheduler_tick 此功能很重要, 原因有两个: 1. 它为调度程序提供了一种更新当前任务的时间统计信息和运行时信息的方法. 1. 然后, 运行时信息用于确定是否需要抢占当前任务, 如果是, 则调用`schedule()`函数. 与大多数以前探索的功能一样, `scheduler_tick` 太复杂而无法完全解释-相反, 像往常一样, 我将仅强调最重要的部分. 1. 主要工作在CFS方法内部完成 [task_tick_fair](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/fair.c#L9044). 该方法调用 [entity_tick](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/fair.c#L3990) 对应于当前任务的 `sched_entity` . 在查看源代码时, 您可能想知道为什么不使用当前的`sched_entry`来调用`entry_tick`而是使用`for_each_sched_entity`宏呢? `for_each_sched_entity`不会遍历系统中的所有`sched_entry`. 相反, 它仅遍历`sched_entry`继承树直到根. 当对任务进行分组时, 这很有用 - 在更新特定任务的运行时信息后, 也会更新与整个组相对应的`sched_entry`. 1. [entity_tick](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/fair.c#L3990) 做两件事: * 调用 [update_curr](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/fair.c#L827), 它负责更新任务的 `vruntime` 和运行队列的 `min_vruntime`. 这里要记住的重要一点是, `vruntime`始终基于两件事:实际执行任务多长时间和任务优先级. * 调用 [check_preempt_tick](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/fair.c#L3834), 检查当前任务是否需要抢占. 抢占发生在2种情况下: 1. 如果当前任务已经运行了太长时间(比较使用的是正常时间, 而不是`vruntime`). [link](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/fair.c#L3842) 1. 如果任务的`vruntime`较小, 并且`vruntime`值之间的差异大于某个阈值. [link](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/fair.c#L3866) 在这两种情况下, 当前任务都通过调用 [resched_curr](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/core.c#L479) 函数来标记为抢占. 我们已经在上一章中看到了如何调用`resched_curr`导致为当前任务设置`TIF_NEED_RESCHED`标志, 并最终调用`schedule`. 就是关于 `schedule_tick` 的事情了, 现在我们终于可以看一下 `schedule` 功能了. ### schedule 我们已经看到了很多使用`schedule`的示例, 因此现在您可能急于了解此函数的实际工作方式. 您会惊讶地知道此函数的内部非常简单. 1. 主要工作在内部 [__schedule](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/core.c#L3278) 函数完成. 1. `__schedule` 调用 [pick_next_task](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/core.c#L3199) 将大部分工作重定向到 [pick_next_task_fair](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/fair.c#L6251) CFS调度程序的方法. 1. 如您所料 `pick_next_task_fair` 通常情况下, 只需从红黑树中选择最左边的元素并返回它即可. 它在 [这里](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/fair.c#L3915). 1. `__schedule` calls [context_switch](https://github.com/torvalds/linux/blob/v4.14/kernel/sched/core.c#L2750), 做一些准备工作并调用特定于体系结构 [__switch_to](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/process.c#L348) 函数, 在其中为交换机准备了低级别的特定于`Arch`的任务参数. 1. `__switch_to` 首先切换一些其他任务组件, 例如TLS(线程本地存储)和已保存的浮点和NEON寄存器. 1. 实际切换发生在汇编 [cpu_switch_to](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L914) 函数. 您应该已经熟悉此功能, 因为我几乎没有复制RPi OS就复制了它. 您可能还记得, 此函数切换被调用者保存的寄存器和任务堆栈. 返回后, 新任务将使用其自己的内核堆栈运行. ### 结论 现在我们完成了Linux调度程序. 好处是, 如果您只关注最基本的工作流程, 似乎并不那么困难. 了解基本的工作流程后, 您可能需要在计划代码中另辟蹊径, 并更加注意细节, 因为其中有很多. 但就目前而言, 我们对当前的理解感到满意, 并准备继续进行下一课, 该课描述了用户流程和系统调用. ##### 上一页 4.3 [流程计划程序:分派任务](../../../docs/lesson04/linux/fork.md) ##### 下一页 4.5 [流程计划程序:练习](../../../docs/lesson04/exercises.md) ================================================ FILE: translations/zh-cn/lesson04/rpi-os.md ================================================ ## 4.1: Scheduler 目前为止, PRi OS已经是一个相当复杂的裸机程序, 但是说实话, 我们仍然不能将其称为操作系统. 原因是它无法完成任何OS应该执行的任何核心任务. 这种核心任务之一称为流程调度. 通过调度, 我的意思是操作系统应该能够在不同进程之间共享CPU时间. 其中最困难的部分是, 一个进程应该不知道调度的发生:它应该将自己视为唯一占用CPU的进程. 在本课程中, 我们将将此功能添加到RPi OS. ### task_struct 如果要管理流程, 我们应该做的第一件事就是创建一个描述流程的结构. Linux具有这样的结构, 它称为`task_struct`(在Linux中, 线程和进程只是不同类型的任务). 由于我们主要模仿Linux的实现, 因此我们将做同样的事情. RPi OS [task_struct](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/include/sched.h#L36) 如下所示. ```cpp struct cpu_context { unsigned long x19; unsigned long x20; unsigned long x21; unsigned long x22; unsigned long x23; unsigned long x24; unsigned long x25; unsigned long x26; unsigned long x27; unsigned long x28; unsigned long fp; unsigned long sp; unsigned long pc; }; struct task_struct { struct cpu_context cpu_context; long state; long counter; long priority; long preempt_count; }; ``` 该结构具有以下成员: * `cpu_context` 这是一个嵌入式结构, 其中包含正在切换的任务之间可能不同的所有寄存器的值. 有一个合理的问题是为什么我们不保存所有寄存器, 而只保存寄存器 `x19-x30` 和 `sp` ?(`fp` 是 `x29` 并且 `pc` 是 `x30`) 答案是实际的上下文切换仅在任务中调用[cpu_switch_to](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/sched.S#L4)函数. 因此, 从正在切换的任务的角度来看, 它仅调用`cpu_switch_to`函数, 并在一段时间(可能很长)后返回. 该任务不会注意到在此期间发生了另一个任务. 根据ARM的调用约定, 寄存器 `x0-x18` 可以被调用的函数覆盖, 因此调用者不得假定这些寄存器的值在函数调用后仍然存在. 这就是为什么保存`x0-x18`寄存器没有意义的原因. * `state` 这是当前正在运行的任务的状态. 对于仅在CPU上做一些工作的任务, 状态始终为 [TASK_RUNNING](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/include/sched.h#L15). 实际上, 这是RPi OS目前要支持的唯一状态. 但是, 稍后我们将不得不添加一些其他状态. 例如, 等待中断的任务应移至其他状态, 因为在尚未发生所需的中断时唤醒任务是没有意义的. * `counter` 该字段用于确定当前任务已运行多长时间. `计数器`会在每个计时器滴答时减少1, 到0时便会安排另一个任务. * `priority` 安排新任务时, 将其 `优先级` 复制到 `计数器` 中. 通过设置任务优先级, 我们可以调节任务相对于其他任务获得的处理器时间. * `preempt_count` 如果该字段的值为非零值, 则表明当前任务正在执行一些不可中断的关键功能(例如, 它运行调度功能). 如果在此时间发生计时器滴答, 则将忽略它, 并且不会触发重新计划. 内核启动后, 只有一个任务正在运行:一个正在运行 [kernel_main](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/kernel.c#L19) 函数. 它称为“初始化任务”. 在启用调度程序功能之前, 我们必须填充与初始化任务相对应的 `task_struct`. 这个被完成在 [这](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/include/sched.h#L53). 所有任务都存储在 [task](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/sched.c#L7) 数组. 该阵列只有64个插槽-这是我们在RPi OS中可以同时执行的最大任务数. 对于生产就绪的OS来说, 它绝对不是最佳解决方案, 但对于我们的目标而言, 这是可以的. 还有一个非常重要的变量称为 [current](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/sched.c#L6) 总是指向当前正在执行的任务. `current` 和 `task` 数组都初始设置为持有指向init任务的指针. 还有一个全局变量称为 [nr_tasks](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/sched.c#L8) - 它包含系统中当前正在运行的任务数. 这些都是我们将用于实现调度程序功能的结构和全局变量. 在对`task_struct`的描述中, 我已经简要提到了调度工作的一些方面, 因为如果不了解如何使用特定的`task_struct`字段, 就无法理解其含义. 现在我们将更详细地研究调度算法, 我们将从 `kernel_main` 功能开始. ### `kernel_main` 函数 在深入探讨调度程序实现之前, 我想快速向您展示如何证明调度程序确实有效. 要了解它, 您需要看一下 [kernel.c](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/kernel.c) 文件. 让我在此处复制相关内容. ```cpp void kernel_main(void) { uart_init(); init_printf(0, putc); irq_vector_init(); timer_init(); enable_interrupt_controller(); enable_irq(); int res = copy_process((unsigned long)&process, (unsigned long)"12345"); if (res != 0) { printf("error while starting process 1"); return; } res = copy_process((unsigned long)&process, (unsigned long)"abcde"); if (res != 0) { printf("error while starting process 2"); return; } while (1){ schedule(); } } ``` 关于此代码, 有一些重要的事情. 1. 新函数 [copy_process](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/fork.c#L5) 介绍. `copy_process` 需要2个参数: 在新线程中执行的函数以及需要传递给该函数的参数. `copy_process` 分配一个新的 `task_struct` 并使其可用于调度程序. 2. 我们的另一个新函数称为 [schedule](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/sched.c#L21). 这是核心调度程序功能:它检查是否有新任务需要抢占当前任务. 如果任务目前没有任何工作, 可以自动调用`计划`. 计时器中断处理程序也会调用 `schedule`. 我们两次调用`copy_process`, 每次传递指向 [process](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/kernel.c#L9) 作为第一个参数. 处理功能非常简单. ```cpp void process(char *array) { while (1){ for (int i = 0; i < 5; i++){ uart_send(array[i]); delay(100000); } } } ``` 它只是一直在屏幕上打印数组中的字符, 将其作为参数传递. 第一次使用参数 `12345` 调用它, 第二次使用 `abcde` 参数. 如果我们的调度程序实现正确, 我们应该在屏幕上看到两个线程的混合输出. ### 内存分配 系统中的每个任务都应具有其专用的堆栈. 这就是为什么在创建新任务时我们必须有一种分配内存的方法. 目前, 我们的内存分配器非常原始. (可以在以下位置找到实现 [mm.c](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/mm.c) 文件) ```cpp static unsigned short mem_map [ PAGING_PAGES ] = {0,}; unsigned long get_free_page() { for (int i = 0; i < PAGING_PAGES; i++){ if (mem_map[i] == 0){ mem_map[i] = 1; return LOW_MEMORY + i*PAGE_SIZE; } } return 0; } void free_page(unsigned long p){ mem_map[p / PAGE_SIZE] = 0; } ``` 分配器只能与内存页面一起使用(每个页面的大小为4 KB). 有一个名为 `mem_map` 的数组, 该数组对于系统中的每个页面都保持其状态:分配还是空闲. 每当我们需要分配一个新页面时, 我们就循环遍历此数组并返回第一个空闲页面. 此实现基于两个假设: 1. 我们知道系统中的内存总量. 它是 `1 GB - 1 MB` (存储器的最后兆字节为设备寄存器保留. ). 此值存储在 [HIGH_MEMORY](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/include/mm.h#L14) 常量中. 1. 前4 MB的内存保留给内核映像和init任务堆栈. 此值存储在 [LOW_MEMORY](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/include/mm.h#L13) 常量. 所有内存分配都在此之后开始. ### 创建一个新任务 新任务分配在 [copy_process](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/fork.c#L5) 函数. ```cpp int copy_process(unsigned long fn, unsigned long arg) { preempt_disable(); struct task_struct *p; p = (struct task_struct *) get_free_page(); if (!p) return 1; p->priority = current->priority; p->state = TASK_RUNNING; p->counter = p->priority; p->preempt_count = 1; //disable preemtion until schedule_tail p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)p + THREAD_SIZE; int pid = nr_tasks++; task[pid] = p; preempt_enable(); return 0; } ``` 现在, 我们将详细研究它. ``` preempt_disable(); struct task_struct *p; ``` 该函数从禁用抢占和为新任务分配指针开始. 抢占已禁用, 因为我们不想在 `copy_process` 函数中间将其重新安排到其他任务. ``` p = (struct task_struct *) get_free_page(); if (!p) return 1; ``` 接下来, 分配一个新页面. 在此页面的底部, 我们为新创建的任务放置 `task_struct`. 该页面的其余部分将用作任务堆栈. ```cpp p->priority = current->priority; p->state = TASK_RUNNING; p->counter = p->priority; p->preempt_count = 1; //disable preemtion until schedule_tail ``` 分配好`task_struct`之后, 我们可以初始化其属性. 优先级和初始计数器是根据当前任务优先级设置的. 状态设置为 `TASK_RUNNING` , 表示新任务已准备好开始. `preempt_count`设置为1, 这意味着在执行任务之后, 在完成一些初始化工作之前, 不应重新计划其时间. ```cpp p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)p + THREAD_SIZE; ``` 这是功能最重要的部分. 这里 `cpu_context` 被初始化. 堆栈指针设置在新分配的内存页面的顶部. `pc` 被设置为 [ret_from_fork](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/entry.S#L146) 函数, 并且我们现在需要看一下这个函数, 以便理解为什么其余`cpu_context`寄存器以它们的方式初始化. ```cpp .globl ret_from_fork ret_from_fork: bl schedule_tail mov x0, x20 blr x19 //should never return ``` 如您所见, `ret_from_fork`第一次调用 [schedule_tail](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/sched.c#L65), 只是启用了抢占, 然后使用存储在`x20`中的参数调用存储在`x19`寄存器中的函数. 在调用`ret_from_fork`函数之前, 从`cpu_context`中恢复出`x19`和`x20`. 现在, 让我们回到`copy_process`. ``` int pid = nr_tasks++; task[pid] = p; preempt_enable(); return 0; ``` 最后, `copy_process` 将新创建的任务添加到 `task` 数组中, 并为当前任务启用抢占. 关于`copy_process`函数要了解的重要一点是, 它在完成执行后不会发生上下文切换. 该函数仅准备新的`task_struct`并将其添加到`task`数组中-仅在调用`schedule`函数后才执行此任务. ### 谁调用 `schedule`? 在深入了解`schedule`功能之前, 首先要弄清楚`schedule`的调用方式. 有2种情况. 1. 当一个任务现在没有任何事情要做, 但是仍然无法终止时, 它可以自动调用`schedule`. 那就是`kernel_main`函数所做的. 1. `schedule` 也定期从 [timer interrupt handler 时钟终端句柄](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/timer.c#L21). 现在让我们来看看 [timer_tick](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/sched.c#L70) 函数, 从计时器中断中调用. ```cpp void timer_tick() { --current->counter; if (current->counter>0 || current->preempt_count >0) { return; } current->counter=0; enable_irq(); _schedule(); disable_irq(); ``` 首先, 它减少了当前任务的计数器. 如果计数器大于0, 或者当前禁用了抢占功能, 则返回该函数, 否则调用`schedule`并启用中断. (我们在中断处理程序内部, 默认情况下禁用中断). 在下一部分中, 我们将了解为什么在调度程序执行期间必须启用中断. ### Scheduling algorithm 最后, 我们准备看一下调度程序算法. 我几乎是从Linux内核的第一个发行版中精确复制了此算法. 您可以找到原始版本在 [这里](https://github.com/zavg/linux-0.01/blob/master/kernel/sched.c#L68). ```cpp void _schedule(void) { preempt_disable(); int next,c; struct task_struct * p; while (1) { c = -1; next = 0; for (int i = 0; i < NR_TASKS; i++){ p = task[i]; if (p && p->state == TASK_RUNNING && p->counter > c) { c = p->counter; next = i; } } if (c) { break; } for (int i = 0; i < NR_TASKS; i++) { p = task[i]; if (p) { p->counter = (p->counter >> 1) + p->priority; } } } switch_to(task[next]); preempt_enable(); } ``` 该算法的工作原理如下: * 第一个内部的`for`循环遍历所有任务, 并尝试以最大计数器找到处于`TASK_RUNNING`状态的任务. 如果找到了这样的任务, 并且其计数器大于0, 我们立即从外部的 `while` 循环中中断, 并切换到该任务. 如果找不到这样的任务, 则意味着当前不存在处于 `TASK_RUNNING` 状态的任务, 或者所有此类任务的计数器均为0. 在实际的OS中, 例如, 当所有任务都在等待中断时, 可能会发生第一种情况. 在这种情况下, 将执行第二个嵌套的 `for` 循环. 对于每个任务(无论处于什么状态), 此循环都会增加其计数器. 计数器增加以非常聪明的方式完成: 1. 任务通过的第二个`for` 循环的迭代次数越多, 其计数器的计数就越高. 2. 任务计数器永远不能超过 `2 *优先级`. * 然后重复该过程. 如果至少有一个任务处于`TASK_RUNNIG`状态, 则外部`while`循环的第二次迭代将是最后一个, 因为在第一次迭代之后, 所有计数器都已经非零. 但是, 如果没有 `TASK_RUNNING` 任务, 则该过程会反复进行, 直到某些任务变为 `TASK_RUNNING` 状态. 但是, 如果我们在单个CPU上运行, 那么在此循环运行时如何更改任务状态?答案是, 如果某些任务正在等待中断, 则该中断可能在执行`计划`功能时发生, 并且中断处理程序可以更改任务的状态. 这实际上解释了为什么在“计划”执行期间必须启用中断. 这也说明了禁用中断和禁用抢占之间的重要区别. 时间表会在整个功能期间禁用抢占. 这样可以确保在我们执行原始函数的过程中不会调用嵌套的 `schedule`. 但是, 在 `计划` 函数执行期间, 中断可能合法发生. 我非常注意某些任务正在等待中断的情况, 尽管RPi OS尚未实现此功能. 但是我仍然认为有必要了解这种情况, 因为它是核心调度程序算法的一部分, 以后将添加类似的功能. ### 切换任务 找到具有非零计数器的 `TASK_RUNNING` 状态的任务后, [switch_to](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/sched.c#L56) 函数被调用. 看起来像这样. ```cpp void switch_to(struct task_struct * next) { if (current == next) return; struct task_struct * prev = current; current = next; cpu_switch_to(prev, next); } ``` 在这里, 我们检查下一个过程是否与当前过程不同, 如果不一致, 则更新`current`变量. 实际工作被重定向到 [cpu_switch_to](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/sched.S) 函数. ```cpp .globl cpu_switch_to cpu_switch_to: mov x10, #THREAD_CPU_CONTEXT add x8, x0, x10 mov x9, sp stp x19, x20, [x8], #16 // store callee-saved registers stp x21, x22, [x8], #16 stp x23, x24, [x8], #16 stp x25, x26, [x8], #16 stp x27, x28, [x8], #16 stp x29, x9, [x8], #16 str x30, [x8] add x8, x1, x10 ldp x19, x20, [x8], #16 // restore callee-saved registers ldp x21, x22, [x8], #16 ldp x23, x24, [x8], #16 ldp x25, x26, [x8], #16 ldp x27, x28, [x8], #16 ldp x29, x9, [x8], #16 ldr x30, [x8] mov sp, x9 ret ``` 这是实际上下文切换发生的地方. 让我们逐行检查它. ``` mov x10, #THREAD_CPU_CONTEXT add x8, x0, x10 ``` `THREAD_CPU_CONTEXT`常量包含`task_struct`中的`cpu_context`结构的偏移量. `x0`包含一个指向第一个参数的指针, 该指针是当前的`task_struct`(在这里, 当前是指我们要从中切换的那个). 复制的两行执行后, `x8`将包含指向当前`cpu_context`的指针. ``` mov x9, sp stp x19, x20, [x8], #16 // store callee-saved registers stp x21, x22, [x8], #16 stp x23, x24, [x8], #16 stp x25, x26, [x8], #16 stp x27, x28, [x8], #16 stp x29, x9, [x8], #16 str x30, [x8] ``` 接下来, 所有保存有`calle`的寄存器都按照在`cpu_context`结构中定义的顺序存储. `x30`是链接寄存器, 包含函数返回地址, 存储为`pc`, 当前堆栈指针存储为`sp`, `x29`存储为`fp`(帧指针). ``` add x8, x1, x10 ``` 现在, `x10`包含在`task_struct`内部的`cpu_context`结构的偏移量, `x1`是指向下一个`task_struct`的指针, 因此`x8`将包含指向下一个`cpu_context`的指针. ``` ldp x19, x20, [x8], #16 // restore callee-saved registers ldp x21, x22, [x8], #16 ldp x23, x24, [x8], #16 ldp x25, x26, [x8], #16 ldp x27, x28, [x8], #16 ldp x29, x9, [x8], #16 ldr x30, [x8] mov sp, x9 ``` 被调用者保存的寄存器从下一个`cpu_context`恢复. ``` ret ``` 函数返回到链接寄存器(`x30`)所指向的位置. 如果我们是第一次切换到某个任务, 则将 [ret_from_fork](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/entry.S#L148) 函数. 在所有其他情况下, 该位置将是先前由`cpu_switch_to`函数保存在`cpu_context`中的位置. ### 调度如何与异常进入/退出一起工作? 在上一课中, 我们看到了[kernel_entry](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/entry.S#L17) 和 [kernel_exit](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/entry.S#L4) 宏用于保存和恢复处理器状态. 引入调度程序后, 出现了一个新问题:现在完全可以合法地将中断作为一项任务输入, 而将其保留为另一项任务. 这是一个问题, 因为我们用来从中断返回的 `eret` 指令依赖于以下事实:返回地址应存储在 `elr_el1` 中, 处理器状态应存储在 `spsr_el1` 寄存器中. 因此, 如果要在处理中断时切换任务, 则必须将这两个寄存器与所有其他通用寄存器一起保存和恢复. 这样做的代码非常简单, 您可以找到保存部分在 [这里](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/entry.S#L35) 和 恢复到 [这里](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/entry.S#L46). ### 在上下文切换期间跟踪系统状态 我们已经检查了与上下文切换有关的所有源代码. 但是, 该代码包含许多异步交互, 这使得很难完全了解整个系统的状态如何随时间变化. 在本节中, 我想让您更轻松地完成此任务:我想描述从系统启动到第二次上下文切换之时发生的事件的顺序. 对于每个此类事件, 我还将包括一个表示事件发生时存储器状态的图表. 我希望这种表示形式将帮助您深入了解调度程序的工作方式. 所以, 让我们开始吧! 1. 内核已初始化并 [kernel_main](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/kernel.c#L19) 函数已被执行. 初始堆栈配置为开始于 [LOW_MEMORY](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/include/mm.h#L13), 这是4 MB. ``` 0 +------------------+ | kernel image | |------------------| | | |------------------| | init task stack | 0x00400000 +------------------+ | | | | 0x3F000000 +------------------+ | device registers | 0x40000000 +------------------+ ``` 1. `kernel_main` 首次调用 [copy_process](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/fork.c#L5) . 分配了新的4 KB内存页面, 并在该页面的底部放置了 `task_struct`. (稍后, 我将在此时创建的任务称为`任务1`) ``` 0 +------------------+ | kernel image | |------------------| | | |------------------| | init task stack | 0x00400000 +------------------+ | task_struct 1 | |------------------| | | 0x00401000 +------------------+ | | | | 0x3F000000 +------------------+ | device registers | 0x40000000 +------------------+ ``` 1. `kernel_main`第二次调用`copy_process`并且重复相同的过程. 任务2已创建并添加到任务列表. ``` 0 +------------------+ | kernel image | |------------------| | | |------------------| | init task stack | 0x00400000 +------------------+ | task_struct 1 | |------------------| | | 0x00401000 +------------------+ | task_struct 2 | |------------------| | | 0x00402000 +------------------+ | | | | 0x3F000000 +------------------+ | device registers | 0x40000000 +------------------+ ``` 1. `kernel_main` 自动调用 [schedule](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/sched.c#L21) 功能并决定运行任务1. 1. [cpu_switch_to](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/sched.S#L4) 将保存了`calee`的寄存器保存在位于内核映像内部的init任务 `cpu_context` 中. 1. `cpu_switch_to` 从任务1恢复已保存日历的寄存器 `cpu_context`. `sp` 现在指向 `0x00401000`, 链接注册到 [ret_from_fork](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/entry.S#L146) 函数, `x19` 包含一个指向 [process](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/kernel.c#L9) 函数 和 `x20` 一个指向字符串 `12345`的指针, 该字符串位于内核映像中的某个位置. 1. `cpu_switch_to` 调用`ret`指令, 该指令跳转到`ret_from_fork`函数. 1. `ret_from_fork` 读取 `x19` 和 `x20` 寄存器, 并使用参数 `12345` 调用 `process` 函数. 在`process`函数开始执行后, 其堆栈开始增长. ``` 0 +------------------+ | kernel image | |------------------| | | |------------------| | init task stack | 0x00400000 +------------------+ | task_struct 1 | |------------------| | | |------------------| | task 1 stack | 0x00401000 +------------------+ | task_struct 2 | |------------------| | | 0x00402000 +------------------+ | | | | 0x3F000000 +------------------+ | device registers | 0x40000000 +------------------+ ``` 1. 发生计时器中断. [kernel_entry](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/entry.S#L17) 宏保存所有通用寄存器 + `elr_el1` 和 `spsr_el1` 到任务1堆栈的底部. ``` 0 +------------------------+ | kernel image | |------------------------| | | |------------------------| | init task stack | 0x00400000 +------------------------+ | task_struct 1 | |------------------------| | | |------------------------| | task 1 saved registers | |------------------------| | task 1 stack | 0x00401000 +------------------------+ | task_struct 2 | |------------------------| | | 0x00402000 +------------------------+ | | | | 0x3F000000 +------------------------+ | device registers | 0x40000000 +------------------------+ ``` 1. `schedule` 被调用 并且它决定运行任务2. 但是我们仍然运行任务1, 并且其堆栈继续增长到任务1保存的寄存器区域以下. 在图中, 堆栈的这一部分标记为(int), 表示“中断堆栈” ``` 0 +------------------------+ | kernel image | |------------------------| | | |------------------------| | init task stack | 0x00400000 +------------------------+ | task_struct 1 | |------------------------| | | |------------------------| | task 1 stack (int) | |------------------------| | task 1 saved registers | |------------------------| | task 1 stack | 0x00401000 +------------------------+ | task_struct 2 | |------------------------| | | 0x00402000 +------------------------+ | | | | 0x3F000000 +------------------------+ | device registers | 0x40000000 +------------------------+ ``` 1. `cpu_switch_to` 运行任务2. 为此, 它执行与任务1完全相同的步骤序列. 任务2开始执行, 并且堆栈不断增长. 请注意, 我们此时并未从中断返回, 但这没关系, 因为现在已启用中断 (先前已在 [timer_tick](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/sched.c#L70) 之前 `schedule` 被调用) ``` 0 +------------------------+ | kernel image | |------------------------| | | |------------------------| | init task stack | 0x00400000 +------------------------+ | task_struct 1 | |------------------------| | | |------------------------| | task 1 stack (int) | |------------------------| | task 1 saved registers | |------------------------| | task 1 stack | 0x00401000 +------------------------+ | task_struct 2 | |------------------------| | | |------------------------| | task 2 stack | 0x00402000 +------------------------+ | | | | 0x3F000000 +------------------------+ | device registers | 0x40000000 +------------------------+ ``` 1. 另一个定时器中断发生, `kernel_entry`将所有通用寄存器+`elr_el1`和`spsr_el1`保存在任务2堆栈的底部. 任务2中断堆栈开始增长. ``` 0 +------------------------+ | kernel image | |------------------------| | | |------------------------| | init task stack | 0x00400000 +------------------------+ | task_struct 1 | |------------------------| | | |------------------------| | task 1 stack (int) | |------------------------| | task 1 saved registers | |------------------------| | task 1 stack | 0x00401000 +------------------------+ | task_struct 2 | |------------------------| | | |------------------------| | task 2 stack (int) | |------------------------| | task 2 saved registers | |------------------------| | task 2 stack | 0x00402000 +------------------------+ | | | | 0x3F000000 +------------------------+ | device registers | 0x40000000 +------------------------+ ``` 1. `schedule` 被调用. 它观察到所有任务的计数器都设置为0, 并将计数器设置为任务优先级. 1. `schedule` 选择要运行的初始化任务. (这是因为现在所有任务的计数器都设置为1, 而`init`任务是列表中的第一个). 但是实际上, 此时 `计划` 选择任务1或任务2是完全合法的, 因为它们的计数器值相等. 我们对选择任务1的情况更感兴趣, 所以现在让我们假设这是发生了什么. 1. `cpu_switch_to` 被调用 并从任务1恢复以前保存的被调用方保存的寄存器 `cpu_context`. 链接现在注册要点到 [这](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/sched.c#L63) 因为这是上次执行任务1时调用`cpu_switch_to`的位置. `sp` 指向任务1中断堆栈的底部. 1. `timer_tick` 函数恢复执行, 从 [这](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/sched.c#L79) 行开始. 最终禁用中断 [kernel_exit](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson04/src/sched.c#L79) 被执行. 到调用 `kernel_exit` 时, 任务1的中断堆栈已折叠为0. ``` 0 +------------------------+ | kernel image | |------------------------| | | |------------------------| | init task stack | 0x00400000 +------------------------+ | task_struct 1 | |------------------------| | | |------------------------| | task 1 saved registers | |------------------------| | task 1 stack | 0x00401000 +------------------------+ | task_struct 2 | |------------------------| | | |------------------------| | task 2 stack (int) | |------------------------| | task 2 saved registers | |------------------------| | task 2 stack | 0x00402000 +------------------------+ | | | | 0x3F000000 +------------------------+ | device registers | 0x40000000 +------------------------+ ``` 1. `kernel_exit` 恢复所有通用寄存器以及`elr_el1` 和 `spsr_el1`. `elr_el1` 现在指向 `process` 函数中间的某个位置. `sp`指向任务1堆栈的底部. ``` 0 +------------------------+ | kernel image | |------------------------| | | |------------------------| | init task stack | 0x00400000 +------------------------+ | task_struct 1 | |------------------------| | | |------------------------| | task 1 stack | 0x00401000 +------------------------+ | task_struct 2 | |------------------------| | | |------------------------| | task 2 stack (int) | |------------------------| | task 2 saved registers | |------------------------| | task 2 stack | 0x00402000 +------------------------+ | | | | 0x3F000000 +------------------------+ | device registers | 0x40000000 +------------------------+ ``` 1. `kernel_exit` 执行 `eret` 使用的指令 `elr_el1` 注册以跳转回 `process` 函数. 任务1恢复其正常执行. 上述步骤顺序非常重要 - 我个人认为这是整个教程中最重要的事情之一. 如果您在理解上有困难, 我可以建议您从本课开始进行练习1. ### 结论 我们已经完成了调度, 但是现在我们的内核只能管理内核线程:它们在`EL1`上执行, 并且可以直接访问任何内核函数或数据. 在接下来的2节课中, 我们将解决此问题, 并介绍系统调用和虚拟内存. ##### 上一页 3.5 [中断处理:练习](../../docs/lesson03/exercises.md) ##### 下一页 4.2 [流程调度程序:调度程序的基本结构](../../docs/lesson04/linux/basic_structures.md) ================================================ FILE: translations/zh-cn/lesson05/exercises.md ================================================ ## 5.3: 练习题 1. 在用户模式下执行任务时, 请尝试访问某些系统寄存器. 在这种情况下, 请确保生成同步异常. 处理此异常时, 请使用`esr_el1`寄存器将其与系统调用区分开. 1. 实施可用于设置当前任务优先级的新系统调用. 演示在任务运行时如何动态应用优先级更改. 1. 改编第05课以在qemu上运行. 校验 [this](https://github.com/s-matyukevich/raspberry-pi-os/issues/8) 供参考. ##### 上一页 5.2 [用户进程和系统调用:Linux](../../docs/lesson05/linux.md) ##### 下一页 6.1 [虚拟内存管理:RPi OS](../../docs/lesson06/rpi-os.md) ================================================ FILE: translations/zh-cn/lesson05/linux.md ================================================ ## 5.2: 用户流程和系统调用 本章将是简短的一章. 原因是我将`syscall`实现从Linux几乎精确地复制到了RPi OS, 因此不需要太多解释. 但是我仍然想引导您了解Linux源代码, 以便您可以了解在何处以及如何实现特定的`syscall`功能. ### 创建第一个用户进程 我们要解决的第一个问题是如何创建第一个用户进程. 一个开始寻找答案的好地方是 [start_kernel](https://github.com/torvalds/linux/blob/v4.14/init/main.c#L509) 函数 - 正如我们先前所见, 这是第一个与体系结构无关的功能, 在内核引导过程的早期就被调用. 这是内核初始化开始的地方, 在内核初始化期间运行第一个用户进程是有意义的. 确实, 如果遵循`start_kernel`的逻辑, 您很快就会发现 [kernel_init](https://github.com/torvalds/linux/blob/v4.14/init/main.c#L989) 具有以下代码的函数. ``` if (!try_to_run_init_process("/sbin/init") || !try_to_run_init_process("/etc/init") || !try_to_run_init_process("/bin/init") || !try_to_run_init_process("/bin/sh")) return 0; ``` 看起来这正是我们想要的. 从该代码中, 我们还可以推断Linux内核在何处以及以什么顺序查找 `init` 用户程序. `try_to_run_init_process` 然后执行 [do_execve](https://github.com/torvalds/linux/blob/v4.14/fs/exec.c#L1837) 函数, 这也负责处理 [execve](http://man7.org/linux/man-pages/man2/execve.2.html) 系统调用. 该系统调用读取二进制可执行文件, 并在当前进程中运行它. 在第9课中将详细探讨 `execve` 系统调用背后的逻辑, 到目前为止, 足以提及该 `syscall` 所做的最重要的工作是解析可执行文件并将其内容加载到内存中, 并且在里面完成 [load_elf_binary](https://github.com/torvalds/linux/blob/v4.14/fs/binfmt_elf.c#L679) 功能. (在这里, 我们假设可执行文件位于 [ELF](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format) 格式 - 它是最受欢迎的, 但不是唯一的选择) 在 `load_elf_binary` 方法的最后(其实 [这里](https://github.com/torvalds/linux/blob/v4.14/fs/binfmt_elf.c#L1149)) 有特定于体系结构的 [start_thread](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/include/asm/processor.h#L119) 函数. 我将其用作RPi OS`move_to_user_mode`例程的原型, 这是我们最关心的代码. 这里是. ```cpp static inline void start_thread_common(struct pt_regs *regs, unsigned long pc) { memset(regs, 0, sizeof(*regs)); forget_syscall(regs); regs->pc = pc; } static inline void start_thread(struct pt_regs *regs, unsigned long pc, unsigned long sp) { start_thread_common(regs, pc); regs->pstate = PSR_MODE_EL0t; regs->sp = sp; } ``` 在执行 `start_thread` 时, 当前进程将以内核模式运行. 允许 `start_thread` 访问当前的 `pt_regs` 结构, 该结构用于设置已保存的 `pstate`, `sp` 和 `pc` 字段. 这里的逻辑与RPi OS的`move_to_user_mode`函数完全相同, 所以我不想再重复一遍. 要记住的重要一件事是, `start_thread`会以这样的方式准备已保存的处理器状态:`kernel_exit`宏最终会将进程移至用户模式. ### Linux 系统调用 令您惊讶的是, 主要的系统调用机制在 `Linux` 和 RPi OS中完全相同. 现在我们将使用已经熟悉的 [clone](http://man7.org/linux/man-pages/man2/clone.2.html) syscall 了解此机制的详细信息. 从我们的探索开始是有意义的 [glibc clone wrapper function](https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/aarch64/clone.S;h=e0653048259dd9a3d3eb3103ec2ae86acb43ef48;hb=HEAD#l35). 它的工作原理与 [call_sys_clone](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/sys.S#L22) 函数在RPi OS中, 除了前一个函数执行者对完整性检查进行论证并正确处理异常外. 要记住和理解的重要一点是, 在两种情况下, 我们都使用`svc`指令生成同步异常, 使用`x8`寄存器传递`syscall` 编号, 并且将所有参数传递到寄存器 `x0` - `x7` 中. 接下来, 让我们看看如何定义`clone` syscall. 可以找到定义在 [这里](https://github.com/torvalds/linux/blob/v4.14/kernel/fork.c#L2153) 看起来像这样 ```cpp SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp, int __user *, parent_tidptr, int __user *, child_tidptr, unsigned long, tls) { return _do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr, tls); } ``` [SYSCALL_DEFINE5](https://github.com/torvalds/linux/blob/v4.14/include/trace/syscall.h#L25) 宏名称中的数字为`5`, 表示我们正在定义具有`5`个参数的`syscall`. 宏分配并填充新的 [syscall_metadata](https://github.com/torvalds/linux/blob/v4.14/include/trace/syscall.h#L25) 结构和创造 `sys_` 函数. 例如, 在定义`clone` syscall的情况下, 将定义`sys_clone`函数-这是将从底层架构代码中调用的实际 `syscall` 处理程序. 执行 `syscall` 时, 内核需要一种通过 `syscall` 编号查找 `syscall` 处理程序的方法. 实现此目的的最简单方法是创建一个指向 `syscall` 处理程序的指针数组, 并使用 `syscall` 号作为该数组中的索引. 这是我们在RPi OS中使用的方法, 与Linux内核中使用的方法完全相同. 指向 `syscall` 处理程序的指针数组 [sys_call_table](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/sys.c#L62) 并定义如下. ``` void * const sys_call_table[__NR_syscalls] __aligned(4096) = { [0 ... __NR_syscalls - 1] = sys_ni_syscall, #include }; ``` 最初, 所有系统调用都被分配为指向 `sys_ni_syscall` 函数(在此, `ni` 表示“不存在”). 编号为`0` 的 `Syscall` 以及尚未为当前体系结构定义的所有 `Syscall` 都将保留此处理程序. `sys_call_table` 数组中的所有其他`syscall`处理程序都将在 [asm/unistd.h](https://github.com/torvalds/linux/blob/v4.14/include/uapi/asm-generic/unistd.h) 头文件. 如您所见, 此文件仅提供 `syscall` 编号和 `syscall` 处理函数之间的映射. ### 低级系统调用处理代码 好的, 我们已经了解了如何创建和填充`sys_call_table`, 现在是时候研究底层 `syscall` 处理代码如何使用它了. 而且这里的基本机制将与RPi OS中的几乎完全相同. 我们知道任何系统调用都是同步异常, 并且所有异常处理程序都在异常向量表中定义 (这是我们的最爱 [vectors](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L367) 数组). 我们感兴趣的处理程序应该是处理在 `EL0` 处生成的同步异常的处理程序. 所有这些使找到合适的处理程序变得轻而易举, 它被称为 [el0_sync](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L598) 如下所示. ``` el0_sync: kernel_entry 0 mrs x25, esr_el1 // read the syndrome register lsr x24, x25, #ESR_ELx_EC_SHIFT // exception class cmp x24, #ESR_ELx_EC_SVC64 // SVC in 64-bit state b.eq el0_svc cmp x24, #ESR_ELx_EC_DABT_LOW // data abort in EL0 b.eq el0_da cmp x24, #ESR_ELx_EC_IABT_LOW // instruction abort in EL0 b.eq el0_ia cmp x24, #ESR_ELx_EC_FP_ASIMD // FP/ASIMD access b.eq el0_fpsimd_acc cmp x24, #ESR_ELx_EC_FP_EXC64 // FP/ASIMD exception b.eq el0_fpsimd_exc cmp x24, #ESR_ELx_EC_SYS64 // configurable trap b.eq el0_sys cmp x24, #ESR_ELx_EC_SP_ALIGN // stack alignment exception b.eq el0_sp_pc cmp x24, #ESR_ELx_EC_PC_ALIGN // pc alignment exception b.eq el0_sp_pc cmp x24, #ESR_ELx_EC_UNKNOWN // unknown exception in EL0 b.eq el0_undef cmp x24, #ESR_ELx_EC_BREAKPT_LOW // debug exception in EL0 b.ge el0_dbg b el0_inv ``` 这里的 `esr_el1`(异常征兆寄存器)用于确定当前异常是否是系统调用. 如果是这样的话 [el0_svc](https://github.com/torvalds/linux/blob/v4.14/arch/arm64/kernel/entry.S#L837) 函数被调用. 此功能在下面列出. ``` el0_svc: adrp stbl, sys_call_table // load syscall table pointer mov wscno, w8 // syscall number in w8 mov wsc_nr, #__NR_syscalls el0_svc_naked: // compat entry point stp x0, xscno, [sp, #S_ORIG_X0] // save the original x0 and syscall number enable_dbg_and_irq ct_user_exit 1 ldr x16, [tsk, #TSK_TI_FLAGS] // check for syscall hooks tst x16, #_TIF_SYSCALL_WORK b.ne __sys_trace cmp wscno, wsc_nr // check upper syscall limit b.hs ni_sys ldr x16, [stbl, xscno, lsl #3] // address in the syscall table blr x16 // call sys_* routine b ret_fast_syscall ni_sys: mov x0, sp bl do_ni_syscall b ret_fast_syscall ENDPROC(el0_svc) ``` Now, let's examine it line by line. ``` el0_svc: adrp stbl, sys_call_table // load syscall table pointer mov wscno, w8 // syscall number in w8 mov wsc_nr, #__NR_syscalls ``` 前3行会初始化变量`stbl`, `wscno`和`wsc_nr`, 这些变量只是某些寄存器的别名. `stbl` 保存系统调用表的地址, `wsc_nr` 包含系统调用的总数, `wscno` 是来自 `w8` 寄存器的当前系统调用号. ``` stp x0, xscno, [sp, #S_ORIG_X0] // save the original x0 and syscall number ``` 正如你可能从我们的RPI OS系统调用的讨论, `x0`是一个系统调用完成后覆盖在`pt_regs`区域记得. 在时可能需要的`x0`寄存器的初始值的情况下, 它被保存在`pt_regs`结构的独立领域. 同样, 系统调用号也保存在pt_regs中. ``` enable_dbg_and_irq ``` 启用了中断和调试异常. ``` ct_user_exit 1 ``` 记录了从用户模式切换到内核模式的事件. ``` ldr x16, [tsk, #TSK_TI_FLAGS] // check for syscall hooks tst x16, #_TIF_SYSCALL_WORK b.ne __sys_trace ``` 如果当前任务在系统调用跟踪器下执行, 则应设置 `_TIF_SYSCALL_WORK` 标志. 在这种情况下, 将调用 `__sys_trace` 函数. 由于我们的讨论仅针对一般情况, 因此我们将跳过此功能. ``` cmp wscno, wsc_nr // check upper syscall limit b.hs ni_sys ``` 如果当前系统调用数大于系统调用总数, 则会向用户返回错误. ``` ldr x16, [stbl, xscno, lsl #3] // address in the syscall table blr x16 // call sys_* routine b ret_fast_syscall ``` `Syscall` 编号用作 `syscall` 表数组中的索引以查找处理程序. 然后将处理程序地址加载到`x16` 寄存器中并执行. 最后调用 `ret_fast_syscall`函数. ``` ret_fast_syscall: disable_irq // disable interrupts str x0, [sp, #S_X0] // returned x0 ldr x1, [tsk, #TSK_TI_FLAGS] // re-check for syscall tracing and x2, x1, #_TIF_SYSCALL_WORK cbnz x2, ret_fast_syscall_trace and x2, x1, #_TIF_WORK_MASK cbnz x2, work_pending enable_step_tsk x1, x2 kernel_exit 0 ``` 这里重要的是第一行, 中断被禁用, 最后一行, `kernel_exit`-其他所有与特殊情况处理有关. 因此, 您可能会猜到这是系统调用实际完成并将执行转移到用户进程的地方. ### 结论 现在, 我们已经完成了生成和处理系统调用的过程. 这个过程相对简单, 但是对操作系统至关重要, 因为它允许内核设置API, 并确保该API是用户程序与内核之间通信的唯一手段. ##### Previous Page 5.1 [用户进程和系统调用:RPi OS](../../docs/lesson05/rpi-os.md) ##### Next Page 5.3 [用户流程和系统调用:练习](../../docs/lesson05/exercises.md) ================================================ FILE: translations/zh-cn/lesson05/rpi-os.md ================================================ ## 5.1: 用户进程和系统调用 我们已经在RPi OS中添加了许多功能, 使其看起来像一个实际的操作系统, 而不仅仅是裸机程序. RPi OS现在可以管理进程, 但是此功能仍然存在一个主要缺点:根本没有进程隔离. 在本课程中, 我们将解决此问题. 首先, 我们将所有用户进程移至EL0, 这将限制他们对特权处理器操作的访问. 没有此步骤, 任何其他隔离技术都没有意义, 因为任何用户程序都将能够重写我们的安全设置, 从而脱离隔离. 如果我们限制用户程序直接访问内核功能, 这将给我们带来另一个问题. 例如, 如果用户程序需要向用户打印某些内容怎么办?我们绝对不希望它直接与UART一起使用. 相反, 如果操作系统为每个程序提供一组API方法, 那就更好了. 此类API不能实现为一组简单的方法, 因为每次用户程序要调用一种API方法时, 当前异常级别都应提高到EL1. 这种API中的各个方法称为“系统调用”, 在本课程中, 我们将向RPi OS添加一组系统调用. 进程隔离还有第三个方面:每个进程都应该有自己独立的内存视图-我们将在第6课中解决这个问题. ### 系统调用实现 系统调用(简称`syscalls`)背后的主要思想非常简单:每个系统调用实际上都是一个同步异常. 如果用户程序需要执行系统调用, 它首先必须准备所有必要的参数, 然后运行`svc`指令. 该指令生成同步异常. 此类异常由操作系统在EL1处理. 然后, OS验证所有参数, 执行请求的操作并执行正常的异常返回, 以确保执行将在`svc`指令后立即在EL0恢复执行. RPi操作系统定义了4个简单的系统调用: 1. `write` 该系统调用使用UART设备在屏幕上输出某些内容. 它接受带有要打印的文本作为第一个参数的缓冲区. 1. `clone` 该系统调用将创建一个新的用户线程. 新创建的线程的堆栈位置作为第一个参数传递. 1. `malloc` 该系统调用为用户进程分配一个内存页. 在Linux中没有类似的系统调用(我认为在其他任何操作系统中也是如此). 我们需要它的唯一原因是RPi OS尚未实现虚拟内存, 并且所有用户进程都可以使用物理内存. 这就是每个进程都需要一种方法来确定哪个内存页面未被占用并可以使用的原因. `malloc` 系统调用返回指向新分配页面的指针, 如果发生错误则返回`-1`. 1. `exit` 每个进程完成执行后必须调用此`syscall`. 它将进行所有必要的清理. 所有系统调用均在 [sys.c](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/sys.c) 文件中. 还有一个数组 [sys_call_table](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/sys.c) 包含指向所有`syscall`处理程序的指针. 每个系统调用都有一个 “系统调用号 `syscall number`” — 这只是 `sys_call_table` 数组. 所有系统调用号均已定义在 [这里](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/include/sys.h#L6) — 汇编代码使用它们来指定我们感兴趣的`syscall`. 让我们以`write` `syscall`为例, 看看`syscall`包装函数. 你可以找到它在 [这里](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/sys.S#L4). ``` .globl call_sys_write call_sys_write: mov w8, #SYS_WRITE_NUMBER svc #0 ret ``` 该函数非常简单:它仅将系统调用号存储在`w8`寄存器中, 并通过执行`svc`指令生成同步异常. 按照惯例, `w8`用于系统调用编号:寄存器`x0`-`x7`用于系统调用参数, 而`x8`用于存储系统调用编号, 这允许系统调用最多包含8个参数. 此类包装函数通常不包含在内核本身中-您更有可能在其他语言的标准库中找到它们, 如 [glibc](https://www.gnu.org/software/libc/). ### 处理同步异常 生成同步异常后, [handler](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/entry.S#L98), 调用在异常表中注册的文件. 可以找到处理程序本身在 [这里](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/entry.S#L157) 它从以下代码开始. ``` el0_sync: kernel_entry 0 mrs x25, esr_el1 // read the syndrome register lsr x24, x25, #ESR_ELx_EC_SHIFT // exception class cmp x24, #ESR_ELx_EC_SVC64 // SVC in 64-bit state b.eq el0_svc handle_invalid_entry 0, SYNC_ERROR ``` 首先, 对于所有异常处理程序, 将调用`kernel_entry`宏. 然后检查 `esr_el1`(异常综合症寄存器). 该寄存器在偏移处包含“异常类”字段 [ESR_ELx_EC_SHIFT](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/include/arm/sysregs.h#L46). 如果异常类等于 [ESR_ELx_EC_SVC64](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/include/arm/sysregs.h#L47) 这意味着当前异常是由`svc`指令引起的, 它是系统调用. 在这种情况下, 我们跳转到 `el0_svc` 标签, 否则显示错误消息. ``` sc_nr .req x25 // number of system calls scno .req x26 // syscall number stbl .req x27 // syscall table pointer el0_svc: adr stbl, sys_call_table // load syscall table pointer uxtw scno, w8 // syscall number in w8 mov sc_nr, #__NR_syscalls bl enable_irq cmp scno, sc_nr // check upper syscall limit b.hs ni_sys ldr x16, [stbl, scno, lsl #3] // address in the syscall table blr x16 // call sys_* routine b ret_from_syscall ni_sys: handle_invalid_entry 0, SYSCALL_ERROR ``` 首先, `el0_svc`将系统调用表的地址加载到`stbl`中(它只是`x27`寄存器的别名. ), 将系统调用号加载到`scno`变量中. 然后启用中断, 并将系统调用数与系统中系统调用的总数进行比较-如果大于或等于, 则会显示一条错误消息. 如果`syscall`编号在要求的范围内, 它将用作`syscall`表数组中的索引, 以获得指向`syscall`处理程序的指针. 接下来, 执行处理程序, 并在完成后调用`ret_from_syscall`. 注意, 这里我们不要触摸寄存器 `x0` – `x7` – 它们被透明地传递给处理程序. ``` ret_from_syscall: bl disable_irq str x0, [sp, #S_X0] // returned x0 kernel_exit 0 ``` `ret_from_syscall`首先禁用中断. 然后将x0寄存器的值保存在堆栈中. 这是必需的, 因为`kernel_exit`将从其保存的值中恢复所有通用寄存器, 但是 `x0` 现在包含`syscall` 处理程序的返回值, 我们希望将此值传递给用户代码. 最后, `kernel_exit`被调用, 返回到用户代码. ### 在EL0和EL1之间切换 如果您仔细阅读了以前的课程, 您可能会注意到`kernel_entry`和`kernel_exit`宏中的变化:现在它们都接受一个附加参数. 此参数指示从哪个异常级别获取异常. 正确保存/恢复堆栈指针需要有关原始异常级别的信息. 这是来自`kernel_entry`和`kernel_exit`宏的两个相关部分. ``` .if \el == 0 mrs x21, sp_el0 .else add x21, sp, #S_FRAME_SIZE .endif /* \el == 0 */ ``` ``` .if \el == 0 msr sp_el0, x21 .endif /* \el == 0 */ ``` 我们为`EL0`和`EL1`使用了2个不同的堆栈指针, 这就是为什么从`EL0`提取异常后堆栈指针会被覆盖的原因. 原始堆栈指针可在 `sp_el0` 寄存器中找到. 即使在异常处理程序中不触摸`sp_el0`, 也必须在发生异常之前和之后存储并恢复该寄存器的值. 如果不这样做, 在上下文切换之后, 最终将在`sp`寄存器中得到错误的值. 您还可能会问, 如果从EL1中获取异常, 为什么我们不恢复`sp`寄存器的值呢?那是因为我们正在为异常处理程序重用相同的内核堆栈. 即使在异常处理过程中发生上下文切换, 在`kernel_exit`时, `sp`也已被`cpu_switch_to`函数切换. (顺便说一句, 在Linux中, 行为是不同的, 因为`Linux`使用不同的堆栈作为中断处理程序. ) 还值得注意的是, 我们不需要在`eret`指令之前明确指定需要返回的异常级别. 这是因为此信息是在`spsr_el1`寄存器中编码的, 因此我们总是返回到发生异常的级别. ### 将任务移至用户模式 在进行任何系统调用之前, 我们显然需要有一个在用户模式下运行的任务. 有两种创建新用户任务的可能性:将内核线程移至用户模式, 或者用户任务可以派生自身来创建新的用户任务. 在本节中, 我们将探讨第一种可能性. 实际完成工作的功能称为 [move_to_user_mode](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/fork.c#Li47), 但是在研究它之前, 让我们先检查一下如何使用此函数. 为此, 您需要先打开 [kernel.c](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/kernel.c) 文件. 让我在这里复制相关行. ```cpp int res = copy_process(PF_KTHREAD, (unsigned long)&kernel_process, 0, 0); if (res < 0) { printf("error while starting kernel process"); return; } ``` 首先, 在`kernel_main`函数中, 我们创建一个新的内核线程. 我们这样做的方式与上一课相同. 调度程序运行新创建的任务后, 将在内核模式下执行`kernel_process`函数. ```cpp void kernel_process(){ printf("Kernel process started. EL %d\r\n", get_el()); int err = move_to_user_mode((unsigned long)&user_process); if (err < 0){ printf("Error while moving process to user mode\n\r"); } } ``` 然后, `kernel_process`打印状态消息并调用`move_to_user_mode`, 将指向`user_process`的指针作为第一个参数. 现在, 让我们看看`move_to_user_mode`函数正在做什么. ```cpp int move_to_user_mode(unsigned long pc) { struct pt_regs *regs = task_pt_regs(current); memzero((unsigned long)regs, sizeof(*regs)); regs->pc = pc; regs->pstate = PSR_MODE_EL0t; unsigned long stack = get_free_page(); //allocate new user stack if (!stack) { return -1; } regs->sp = stack + PAGE_SIZE; current->stack = stack; return 0; } ``` 现在, 我们处于执行从init任务派生的内核线程的过程中. 在上一课中, 我们讨论了分叉过程, 并且看到在新创建的任务堆栈的顶部保留了一个小区域( `pt_regs`区域). 这是我们第一次使用此区域:我们将在此处保存手动准备的处理器状态. 该状态将具有与`kernel_exit`宏期望的格式完全相同的格式, 并且其结构由 [pt_regs](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/include/fork.h#L21) 结构组成. `pt_regs`结构的以下字段在`move_to_user_mode`函数中初始化. * `pc` 现在, 它指向需要在用户模式下执行的功能. `kernel_exit`将把`pc`复制到`elr_el1`寄存器中, 从而确保在执行异常返回后我们将返回到`pc`地址. * `pstate` 该字段将由`kernel_exit`复制到`spsr_el1`, 并在异常返回完成后成为处理器状态. [PSR_MODE_EL0t](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/include/fork.h#L9) 常量, 复制到 `pstate` 字段中的代码的方式是将异常返回到`EL0`级别. 从`EL3`切换到`EL1`时, 我们已经在第2课中做了相同的技巧. * `stack` `move_to_user_mode` 为用户堆栈分配一个新页面, 并将 `sp` 字段设置为指向该页面的顶部. `task_pt_regs` 函数用于计算 `pt_regs` 区域的位置. 由于我们初始化当前内核线程的方式, 我们确保在完成后, `sp`会指向`pt_regs`区域之前. 这发生在 [ret_from_fork](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/entry.S#L188) 函数中. ``` .globl ret_from_fork ret_from_fork: bl schedule_tail cbz x19, ret_to_user // not a kernel thread mov x0, x20 blr x19 ret_to_user: bl disable_irq kernel_exit 0 ``` 您可能会注意到`ret_from_fork`已更新. 现在, 在内核线程完成之后, 执行转到 `ret_to_user` 标签, 此处我们使用先前准备的处理器状态来禁用中断并执行正常的异常返回. ### Forking 用户进程 现在让我们回到`kernel.c`文件. 如上一节所述, `kernel_process`完成之后, [user_process](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/kernel.c#L22) 函数将在用户模式下执行. 该函数调用`clone`系统调用两次以执行 [user_process1](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/kernel.c#L10) 在2个并行线程中起作用. `clone`系统调用要求将新的用户堆栈的位置传递给它, 我们还需要调用`malloc` syscall以便分配2个新的内存页. 现在让我们看一下`clone` syscall包装函数的外观. 你可以在 [这里](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/sys.S#L22) 找到它. ``` .globl call_sys_clone call_sys_clone: /* Save args for the child. */ mov x10, x0 /*fn*/ mov x11, x1 /*arg*/ mov x12, x2 /*stack*/ /* Do the system call. */ mov x0, x2 /* stack */ mov x8, #SYS_CLONE_NUMBER svc 0x0 cmp x0, #0 beq thread_start ret thread_start: mov x29, 0 /* Pick the function arg and execute. */ mov x0, x11 blr x10 /* We are done, pass the return value through x0. */ mov x8, #SYS_EXIT_NUMBER svc 0x0 ``` 在 `clone` 系统调用包装函数的设计中, 我试图模拟 [coresponding function](https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/aarch64/clone.S;h=e0653048259dd9a3d3eb3103ec2ae86acb43ef48;hb=HEAD#l35) 从`glibc`库. 此功能执行以下操作. 1. 保存寄存器 `x0`-`x3`, 这些寄存器包含`syscall`的参数, 稍后将被`syscall`处理程序覆盖. 1. 调用`syscall`处理程序. 1. 检查`syscall`处理程序的返回值:如果它是`0`, 我们正在新创建的线程内执行. 在这种情况下, 执行将转到 `thread_start` 标签. 1. 如果返回值不为零, 则它是新任务的`PID`. 这意味着, 我们的系统调用完成后回到这里, 在我们原来的线程内部执行 - 只返回在这种情况下的调用者. 1. 最初作为第一个参数传递的函数在新线程中调用. 1. 函数完成后, 将执行`exit`系统调用 - 它永远不会返回. 如您所见, 克隆包装函数和克隆syscall的语义不同:前者接受指向要执行的函数的指针作为参数, 而后者则两次返回调用者:第一次是在原始任务中, 第二次是在克隆的任务. 可以找到克隆的系统调用处理程序在 [这里](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/sys.c#L11). 这非常简单, 它只调用已经熟悉的`copy_process`函数. 但是, 此功能自上一课以来已被修改-现在它支持克隆用户线程和内核线程. 下面列出了该函数的来源. ```cpp int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg, unsigned long stack) { preempt_disable(); struct task_struct *p; p = (struct task_struct *) get_free_page(); if (!p) { return -1; } struct pt_regs *childregs = task_pt_regs(p); memzero((unsigned long)childregs, sizeof(struct pt_regs)); memzero((unsigned long)&p->cpu_context, sizeof(struct cpu_context)); if (clone_flags & PF_KTHREAD) { p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; } else { struct pt_regs * cur_regs = task_pt_regs(current); *childregs = *cur_regs; childregs->regs[0] = 0; childregs->sp = stack + PAGE_SIZE; p->stack = stack; } p->flags = clone_flags; p->priority = current->priority; p->state = TASK_RUNNING; p->counter = p->priority; p->preempt_count = 1; //disable preemtion until schedule_tail p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)childregs; int pid = nr_tasks++; task[pid] = p; preempt_enable(); return pid; } ``` 以防万一, 当我们创建一个新的内核线程时, 该函数的行为完全相同, 如上一课中所述. 在另一种情况下, 当我们克隆用户线程时, 将执行这部分代码. ```cpp struct pt_regs * cur_regs = task_pt_regs(current); *childregs = *cur_regs; childregs->regs[0] = 0; childregs->sp = stack + PAGE_SIZE; p->stack = stack; ``` 我们在这里要做的第一件事是访问处理器状态, 该状态由 `kernel_entry` 宏保存. 但是, 为什么我们可以使用相同的 `task_pt_regs` 函数, 该函数仅返回内核堆栈顶部的 `pt_regs` 区域, 这并不明显. 为什么 `pt_regs` 不可能存储在堆栈中的其他位置?答案是只有在调用`clone` syscall后才能执行此代码. 在触发 `syscall` 时, 当前的内核堆栈为空(进入用户模式后我们将其保留为空). 这就是为什么 `pt_regs` 将始终存储在内核堆栈的顶部. 对于所有后续的系统调用, 将保留该规则, 因为它们中的每一个在返回用户模式之前都会使内核堆栈为空. 在第二行中, 当前处理器状态被复制到新任务的状态. 新状态下的`x0`设置为`0`, 因为调用者会将`x0`解释为`syscall`的返回值. 我们已经看到克隆包装器函数如何使用此值来确定我们是否仍在执行原始线程的一部分还是新线程. 新任务的下一个 `sp` 设置为指向新用户堆栈页面的顶部. 我们还将指针保存到堆栈页面, 以便在任务完成后进行清理. ### 退出任务 每个用户任务完成后, 应调用 `exit` 系统调用(在当前实现中, `clone` 包装器函数会隐式调用 `exit`). `exit` `syscall`然后调用 [exit_process](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson05/src/sched.c) 函数, 负责停用任务. 该功能在下面列出. ```cpp void exit_process(){ preempt_disable(); for (int i = 0; i < NR_TASKS; i++){ if (task[i] == current) { task[i]->state = TASK_ZOMBIE; break; } } if (current->stack) { free_page(current->stack); } preempt_enable(); schedule(); } ``` 按照Linux约定, 我们不会立即删除任务, 而是将其状态设置为`TASK_ZOMBIE`. 这样可以防止调度程序选择和执行任务. 在Linux中, 这种方法用于允许父进程即使在子进程完成后也可以查询有关该子进程的信息. `exit_process`现在也删除了不必要的用户堆栈并调用`schedule`. 在调用`计划`后, 将选择新任务, 这就是该系统调用永不返回的原因. ### 结论 既然RPi OS可以管理用户任务, 那么我们将更接近于完全的流程隔离. 但是仍然缺少一个重要步骤:所有用户任务共享相同的物理内存, 并且可以轻松读取彼此的数据. 在下一课中, 我们将介绍虚拟内存并解决此问题. ##### 上一页 4.5 [进程计划程序:练习](../../docs/lesson04/exercises.md) ##### 下一页 5.2 [用户进程和系统调用:Linux](../../docs/lesson05/linux.md) ================================================ FILE: translations/zh-cn/lesson06/exercises.md ================================================ ## 6.3 练习题 1. 改编第06课以在qemu上运行. 你必须实现 [identity mapping](https://wiki.osdev.org/Identity_Paging) 为此. 查看 [this](https://github.com/s-matyukevich/raspberry-pi-os/issues/8) issue 供参考. ##### 上一页 6.2 Virtual memory management: Linux (in progress) 6.1 jump backward to [Virtual memory management: RPi OS](../../docs/lesson06/rpi-os.md) ##### 下一页 7.1 信号和中断等待:RPi OS(待完成) ================================================ FILE: translations/zh-cn/lesson06/rpi-os.md ================================================ ## 6.1: 虚拟内存管理 RPi OS现在可以运行和调度用户进程, 但是它们之间的隔离还不完全-所有进程和内核本身共享相同的内存. 这允许任何进程轻松访问他人的数据, 甚至内核数据. 即使我们假设所有进程都不是恶意程序, 也存在另一个缺点:在分配内存之前, 每个进程都需要知道哪些内存区域已被占用-这会使进程的内存分配更加复杂. ### Translation process 在本课程中, 我们将通过引入虚拟内存来解决上述所有问题. 虚拟内存为每个进程提供了一个抽象, 使它认为它占用了所有可用内存. 每次进程需要访问某个内存位置时, 它都会使用虚拟地址, 该地址被转换为物理地址. 翻译过程对于该过程是完全透明的, 并且由特殊设备`MMU`(内存映射单元)执行. `MMU`使用转换表以便将虚拟地址转换为物理地址. 下图描述了翻译过程. ``` Virtual address Physical Memory +-----------------------------------------------------------------------+ +-----------------_+ | | PGD Index | PUD Index | PMD Index | PTE Index | Page offset | | | +-----------------------------------------------------------------------+ | | 63 47 | 38 | 29 | 20 | 11 | 0 | Page N | | | | | +--------------------+ +---->+------------------+ | | | +---------------------+ | | | | +------+ | | | | | | | | | +----------+ | | | |------------------| +------+ | PGD | | | +---------------->| Physical address | | ttbr |---->+-------------+ | PUD | | | |------------------| +------+ | | | | +->+-------------+ | PMD | | | | | +-------------+ | | | | | +->+-------------+ | PTE | +------------------+ +->| PUD address |----+ +-------------+ | | | | | +->+--------------+ | | | +-------------+ +--->| PMD address |----+ +-------------+ | | | | | | | | | +-------------+ +--->| PTE address |----+ +-------------_+ | | | +-------------+ | | +-------------+ +--->| Page address |----+ | | +-------------+ | | +--------------+ | | +-------------+ | | | | +--------------+ +------------------+ ``` 以下事实对于全面理解此图和内存转换过程至关重要. * 进程的内存始终以页分配. 一个页面是一个大小为4KB的内存区域(ARM处理器支持更大的页面, 但是4KB是最常见的情况, 我们将讨论仅限于此页面大小). * 页表具有层次结构. 在任何表中的项目包含了下表中的层次结构中的地址. * 表层次结构中有4个级别:PGD(页面全局目录), PUD(页面上层目录), PMD(页面中间目录), PTE(页面表条目). PTE是层次结构中的最后一个表, 它指向物理内存中的实际页面. * 内存转换过程从查找PGD(页面全局目录)表的地址开始. 该表的地址存储在 `ttbr0_el1` 寄存器中. 每个进程都有其所有页表(包括PGD)的副本, 因此每个进程必须保留其`PGD`地址. 在上下文切换期间, 下一个进程的PGD地址被加载到 `ttbr0_el1` 寄存器中. * 接下来, MMU使用PGD指针和虚拟地址来计算相应的物理地址. 在64个可用位中, 所有虚拟地址仅使用48个. 进行翻译时, MMU将地址分为4部分: * [39-47] 位包含PGD表中的索引. MMU使用此索引来查找PUD的位置. * [30-38] 位包含PUD表中的索引. MMU使用此索引来查找PMD的位置. * [21-29] 位包含PMD表中的索引. MMU使用此索引查找PTE的位置. * [12-20] 位包含PTE表中的索引. MMU使用此索引在物理内存中查找页面. * [0-11] 位包含物理页中的偏移量. MMU使用此偏移量来确定先前找到的页面中与原始虚拟地址相对应的确切位置. 现在, 让我们做一个小练习并计算页面表的大小. 从上图中, 我们知道页表中的索引占用9位(对于所有页表级别都是如此). 这意味着每个页表都包含 `2^9=512` 个项目. 页表中的每个项目都是层次结构中下一个页表的地址, 如果是PTE, 则是物理页的地址. 当我们使用64位处理器时, 每个地址的大小必须为64位或8个字节. 综合所有这些, 我们可以计算出页表的大小必须为 `512 *8 = 4096` 字节或4 KB. 但这恰好是一页的大小!这可能会给您一个直觉, 为什么 `MMU` 设计人员选择了这样的数字. ### 剖面图 在开始查看源代码之前, 我还要讨论另一件事:节映射. 有时是需要映射的连续物理内存大的部分. 在这种情况下, 我们可以直接映射 `2 MB` 的块, 而不是 `4 KB` 的页面, 这称为节. 这样可以消除1级翻译. 在这种情况下, 翻译图如下所示. ``` Virtual address Physical Memory +-----------------------------------------------------------------------+ +-----------------_+ | | PGD Index | PUD Index | PMD Index | Section offset | | | +-----------------------------------------------------------------------+ | | 63 47 | 38 | 29 | 20 | 0 | Section N | | | | | +---->+------------------+ | | | | | | | +------+ | | | | | | | | +----------+ | | |------------------| +------+ | PGD | | +------------------------->| Physical address | | ttbr |---->+-------------+ | PUD | | |------------------| +------+ | | | | +->+-------------+ | PMD | | | | +-------------+ | | | | | +->+-----------------+ | +------------------+ +->| PUD address |----+ +-------------+ | | | | | | | +-------------+ +--->| PMD address |----+ +-----------------+ | | | | | +-------------+ +--->| Section address |-----+ | | +-------------+ | | +-----------------+ | | +-------------+ | | | | +-----------------+ | | +------------------+ ``` 如您所见, 这里的区别是现在`PMD`包含一个指向物理部分的指针. 此外, 偏移量占用的是`21`位而不是`12`位(这是因为我们需要`21`位才能对`2MB`范围进行编码) ### 页面描述符格式 您可能会问, MMU如何知道PMD项是指向PTE还是物理的 `2 MB` 部分?为了回答这个问题, 我们需要仔细查看页表项的结构. 现在, 我可以承认, 声称页面表中的项始终包含下一个页面表或物理页面的地址时, 我并不是很准确:每个此类项还包含一些其他信息. 页表中的一项称为 `描述符` . 描述具有特殊格式, 如下所述. ``` Descriptor format `+------------------------------------------------------------------------------------------+ | Upper attributes | Address (bits 47:12) | Lower attributes | Block/table bit | Valid bit | +------------------------------------------------------------------------------------------+ 63 47 11 2 1 0 ``` 这里要理解的关键是, 每个描述符始终指向页面对齐的内容(层次结构中的物理页面, 节或下一页表). 这意味着存储在描述符中的地址的最后`12`位将始终为`0`. 这也意味着MUU可以使用这些位来存储更有用的内容 - 正是它的作用. 现在让我解释一下描述符中所有位的含义. * **Bit 0** 对于所有有效的描述符, 必须将该位设置为1. 如果MMU在转换过程中遇到无效描述符, 则会生成同步异常. 然后, 内核应处理该异常, 分配新页面并准备正确的描述符(稍后将详细介绍其工作原理) * **Bit 1** 该位指示当前描述符是指向层次结构中的下一个页面表(我们称该描述符为 `表描述符` )还是指向物理页面或部分(此类描述符称为`块描述符`). * **Bits [11:2]** 这些位被表描述符忽略. 对于块描述符, 它们包含一些控制属性, 例如, 映射的页面是否可缓存, 可执行等. * **Bits [47:12]** 这是描述符指向的地址的存储位置. 正如我之前提到的, 仅需要存储地址的位[47:12], 因为所有其他位始终为0. * **Bits [63:48]** 另一组属性. ### 配置页面属性 正如我在上一节中提到的那样, 每个块描述符都包含一组控制各种虚拟页面参数的属性. 但是, 对于我们的讨论而言最重要的属性未在描述符中直接配置. 相反, ARM处理器实现了一个技巧, 使他们可以在描述符属性部分中节省一些空间. ARM.v8体系结构引入了 `mair_el1` 寄存器. 该寄存器由8个部分组成, 每个部分为8位长. 每个此类部分都配置了一组公共属性. 然后, 描述符仅指定 `mair` 部分的索引, 而不是直接指定所有属性. 这允许在描述符中仅使用3位来引用 `mair` 部分. `air` 部分中每个位的含义在 `AArch64-Reference-Manual` 的第2609页中进行了描述. 在RPi OS中, 我们仅使用一些可用的属性选项. [这里](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/include/arm/mmu.h#L11) 是为 `air` 寄存器准备值的代码. ```cpp /* * Memory region attributes: * * n = AttrIndx[2:0] * n MAIR * DEVICE_nGnRnE 000 00000000 * NORMAL_NC 001 01000100 */ #define MT_DEVICE_nGnRnE 0x0 #define MT_NORMAL_NC 0x1 #define MT_DEVICE_nGnRnE_FLAGS 0x00 #define MT_NORMAL_NC_FLAGS 0x44 #define MAIR_VALUE (MT_DEVICE_nGnRnE_FLAGS << (8 * MT_DEVICE_nGnRnE)) | (MT_NORMAL_NC_FLAGS << (8 * MT_NORMAL_NC)) ``` 在这里, 我们仅使用`mair`寄存器中8个可用插槽中的2个. 第一个对应于设备内存, 第二个对应于普通不可缓存内存. `MT_DEVICE_nGnRnE`和`MT_NORMAL_NC`是我们将要在块描述符使用索引, `MT_DEVICE_nGnRnE_FLAGS`和`MT_NORMAL_NC_FLAGS`是我们正在存储在第一2个时隙的`mair_el1`寄存器值. ### 内核与用户虚拟内存 MMU打开后, 每个内存访问必须使用虚拟内存而不是物理内存. 这一事实的结果是, 内核本身必须准备使用虚拟内存并维护自己的页表集. 一种可能的解决方案是, 每当我们从用户模式切换到内核模式时, 都要重新加载pgd寄存器. 问题是切换pgd是非常昂贵的操作, 因为它要求所有缓存无效. 考虑到我们需要从用户模式切换到内核模式的频率, 此解决方案将使缓存完全无用, 因此该解决方案从未在OS开发中使用. 操作系统正在做的是将地址空间分为两部分:用户空间和内核空间. 32位体系结构通常为用户程序分配前3 GB的地址空间, 为内核保留后1 GB的地址空间. 在这方面, 64位体系结构由于其巨大的地址空间而更加受青睐. 甚至更多:ARM.v8体系结构具有本机功能, 可用于轻松实现用户/内核地址拆分. 有2个寄存器可以保存PGD的地址:`ttbr0_el1` 和 `ttbr1_el1`. 您可能还记得, 我们在64个可用地址中只使用了48位, 因此高16位可用于区分 `ttbr0`和 `ttbr1` 转换过程. 如果高16位都等于0, 则使用存储在 `ttbr0_el1` 中的 `PGD` 地址, 如果地址以 `0xffff` 开头(前16位均等于1), 则选择存储在 `ttbr1_el1` 中的PGD地址. . 该体系结构还确保运行在EL0上的进程永远不会访问以 `0xffff` 开头的虚拟地址, 而不会产生同步异常. 从该描述中, 您可以轻松推断出指向内核PGD的指针存储在`ttbr1_el1`中, 并在内核的整个生命周期中都保存在其中, 而`ttbr0_el1`用于存储当前用户进程PGD. 这种方法的一个含义是, 所有绝对内核地址都必须以 `0xffff` 开头. 在我们处理此问题的过程中, RPi OS源代码中有2个地方. 在 [linker script](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/linker.ld#L3) 里面 我们将图片的基址指定为 `0xffff000000000000`. 这将使编译器认为我们的映像将被加载到 `0xffff000000000000` 地址, 因此, 每当需要生成绝对地址时, 它将正确处理. (链接器脚本还有一些其他更改, 但是稍后我们将讨论它们. ) 我们还要对绝对内核基地址进行硬编码, 这还有一个地方: 在 [header](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/include/peripherals/base.h#L7) 里面我们定义设备基址的位置. 现在, 我们将访问从 `0xffff00003F000000` 开始的所有设备内存, 当然, 为了使其正常工作, 我们需要首先映射内核需要访问的所有内存. 在下一节中, 我们将详细探讨创建此映射的代码. ### 初始化内核页表 创建内核页表的过程是我们在引导过程的早期就需要处理的事情. 它开始于 [boot.S](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/boot.S#L42) 文件. 在我们切换到 `EL1` 并清除 `BSS` 之后 [__create_page_tables](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/boot.S#L92) 函数被调用. 让我们逐行检查它. ``` __create_page_tables: mov x29, x30 // save return address ``` 首先, 该函数保存 `x30`(链接寄存器). 当我们从 `__create_page_tables` 调用其他函数时, `x30` 将被覆盖. 通常将 `x30` 保存在堆栈中, 但是, 由于我们知道我们将不使用递归, 并且在执行 `__create_page_tables` 期间没有其他人会使用 `x29`, 因此这种保存链接寄存器的简单方法也可以正常工作. ``` adrp x0, pg_dir mov x1, #PG_DIR_SIZE bl memzero ``` 接下来, 我们清除初始页表区域. 这里要了解的重要一件事是该区域的位置以及如何知道其大小? 初始页表区域在 [linker script](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/linker.ld#L20) - 这意味着我们正在内核映像本身中为该区域分配斑点. 计算该区域的大小有些麻烦. 首先, 我们需要了解初始内核页表的结构. 我们知道所有映射都在1 GB区域内(这是RPi内存的大小). 一个PGD描述符可以覆盖 `2^39=512 GB` , 而一个PUD描述符可以覆盖 `2^30=1 GB` 的连续虚拟映射区域. (这些值是根据虚拟地址中的PGD和PUD索引位置计算的. )这意味着我们只需要一个PGD和一个PUD即可映射整个RPi存储器, 甚至更多-PGD和PUD都将包含一个描述符. 如果我们只有一个PUD条目, 那么还必须有一个PMD表, 该条目将指向该表. (单个PMD条目占2 MB, 一个PMD中有512个项目, 因此, 整个PMD表总共覆盖由单个PUD描述符覆盖的相同1 GB内存. ) 接下来, 我们知道我们需要映射1 GB的内存区域, 这是2 MB的倍数 - 因此我们可以使用节映射. 这意味着我们根本不需要PTE. 因此, 总共需要3个页面:一个用于PGD, PUD和PMD, 这恰好是初始页面表区域的大小. 现在, 我们要走出 `__create_page_tables` 函数之外, 并看一下2个基本宏: [create_table_entry](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/boot.S#L68) 和 [create_block_map](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/boot.S#L77). `create_table_entry`负责分配一个新的页表(在我们的例子中是PGD或PUD). 源代码在下面列出. ``` .macro create_table_entry, tbl, virt, shift, tmp1, tmp2 lsr \tmp1, \virt, #\shift and \tmp1, \tmp1, #PTRS_PER_TABLE - 1 // table index add \tmp2, \tbl, #PAGE_SIZE orr \tmp2, \tmp2, #MM_TYPE_PAGE_TABLE str \tmp2, [\tbl, \tmp1, lsl #3] add \tbl, \tbl, #PAGE_SIZE // next level table page .endm ``` 该宏接受以下参数. * `tbl` - 指向必须分配新表的内存区域的指针. * `virt` - 当前正在映射的虚拟地址. * `shift` - 我们需要将其应用于虚拟地址以提取当前表索引. (如果是PGD, 则为39;如果是PUD, 则为30) * `tmp1`, `tmp2` - 临时寄存器. 这个宏非常重要, 因此我们将花一些时间来理解它. ``` lsr \tmp1, \virt, #\shift and \tmp1, \tmp1, #PTRS_PER_TABLE - 1 // table index ``` 宏的前两行负责从虚拟地址中提取表索引. 我们首先应用右移将所有内容剥离到索引的右侧, 然后使用`和`操作将所有内容剥离到左侧. ``` add \tmp2, \tbl, #PAGE_SIZE ``` 然后计算下一页表的地址. 在这里, 我们使用的约定是, 所有初始页表都位于一个连续的内存区域中. 我们仅假设层次结构中的下一个页面表将与当前页面表相邻. ``` orr \tmp2, \tmp2, #MM_TYPE_PAGE_TABLE ``` 接下来, 将指向层次结构中下一页表的指针转换为表描述符. (描述符必须将2个低位设置为 `1`) ``` str \tmp2, [\tbl, \tmp1, lsl #3] ``` 然后将描述符存储在当前页表中. 我们使用先前计算的索引在表中找到正确的位置. ``` add \tbl, \tbl, #PAGE_SIZE // next level table page ``` 最后, 我们更改 `tbl` 参数以指向层次结构中的下一页表. 这很方便, 因为现在我们可以为层次结构中的下一个表再调用一次 `create_table_entry`, 而无需对`tbl`参数进行任何调整. 这正是我们在 [create_pgd_entry](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/boot.S#L63) 宏, 这只是分配PGD和PUD的包装器. 下一个重要的宏是`create_block_map`, 您可能会猜到该宏负责填充PMD表的条目. 看起来如下. ``` .macro create_block_map, tbl, phys, start, end, flags, tmp1 lsr \start, \start, #SECTION_SHIFT and \start, \start, #PTRS_PER_TABLE - 1 // table index lsr \end, \end, #SECTION_SHIFT and \end, \end, #PTRS_PER_TABLE - 1 // table end index lsr \phys, \phys, #SECTION_SHIFT mov \tmp1, #\flags orr \phys, \tmp1, \phys, lsl #SECTION_SHIFT // table entry 9999: str \phys, [\tbl, \start, lsl #3] // store the entry add \start, \start, #1 // next entry add \phys, \phys, #SECTION_SIZE // next block cmp \start, \end b.ls 9999b .endm ``` 这里的参数有些不同. * `tbl` - 指向PMD表的指针. * `phys` - 要映射的物理区域的开始. * `start` - 要映射的第一部分的虚拟地址. * `end` - 要映射的最后一部分的虚拟地址. * `flags` - 这些标志需要复制到块描述符的较低属性中. * `tmp1` - 临时注册. 现在, 让我们检查源代码. ``` lsr \start, \start, #SECTION_SHIFT and \start, \start, #PTRS_PER_TABLE - 1 // table index ``` 这两行从 `start` 虚拟地址中提取表索引. 完全以与之前在 `create_table_entry` 宏中相同的方式完成. ``` lsr \end, \end, #SECTION_SHIFT and \end, \end, #PTRS_PER_TABLE - 1 // table end index ``` 对 `结束` 地址重复相同的操作. 现在, `开始`和`结束`都不再包含虚拟地址, 而是PMD表中的索引, 对应于原始地址. ``` lsr \phys, \phys, #SECTION_SHIFT mov \tmp1, #\flags orr \phys, \tmp1, \phys, lsl #SECTION_SHIFT // table entry ``` 接下来, 准备块描述符并将其存储在 `tmp1` 变量中. 为了准备描述符, 首先将 `phys`参数右移, 然后使用`orr`指令将其与`flags`参数合并. 如果您想知道为什么我们必须来回移动地址-答案是, 这将清除`phys`地址中的前`21`位并使我们的宏通用, 从而使其可用于任何地址, 而不仅是第一个地址的部分. ``` 9999: str \phys, [\tbl, \start, lsl #3] // store the entry add \start, \start, #1 // next entry add \phys, \phys, #SECTION_SIZE // next block cmp \start, \end b.ls 9999b ``` 函数的最后一部分在循环内执行. 在这里, 我们首先将当前描述符存储在PMD表中的正确索引处. 接下来, 我们将当前索引增加1并更新描述符以指向下一部分. 我们重复相同的过程, 直到当前索引等于最后一个索引. 现在, 当您了解`create_table_entry`和`create_block_map`宏如何工作时, 将很容易理解`__create_page_tables`函数的其余部分. ``` adrp x0, pg_dir mov x1, #VA_START create_pgd_entry x0, x1, x2, x3 ``` 在这里, 我们创建PGD和PUD. 我们将它们配置为从以下位置开始映射 [VA_START](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/include/mm.h#L6) 虚拟地址. 即PMD -因为`create_table_entry`宏的语义, 后`create_pgd_entry`完成`x0`将包含层次结构中的下一个表的地址. ``` /* Mapping kernel and init stack*/ mov x1, xzr // start mapping from physical offset 0 mov x2, #VA_START // first virtual address ldr x3, =(VA_START + DEVICE_BASE - SECTION_SIZE) // last virtual address create_block_map x0, x1, x2, x3, MMU_FLAGS, x4 ``` 接下来, 我们创建整个内存的虚拟映射, 不包括设备寄存器区域. 我们用 [MMU_FLAGS](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/include/arm/mmu.h#L24) 常量作为 `flags` 参数-将所有要映射的部分标记为普通的不可缓存内存. (注意, `MM_ACCESS`标志也被指定为 `MMU_FLAGS`常量的一部分. 没有该标志, 每个内存访问将生成一个同步异常. ) ``` /* Mapping device memory*/ mov x1, #DEVICE_BASE // start mapping from device base address ldr x2, =(VA_START + DEVICE_BASE) // first virtual address ldr x3, =(VA_START + PHYS_MEMORY_SIZE - SECTION_SIZE) // last virtual address create_block_map x0, x1, x2, x3, MMU_DEVICE_FLAGS, x4 ``` 然后映射设备寄存器区域. 除了使用不同的开始和结束地址以及不同的标志外, 这与上一个代码示例完全相同. ``` mov x30, x29 // restore return address ret ``` 最后, 该函数恢复了链接寄存器并返回到调用方. ### 配置页面翻译 现在创建页面表, 回到 `el1_entry` 函数. 但是, 在打开MMU之前, 仍有一些工作要做. 这是发生了什么. ``` mov x0, #VA_START add sp, x0, #LOW_MEMORY ``` 我们正在更新初始化任务堆栈指针. 现在, 它使用虚拟地址, 而不是物理地址. (因此, 它可以仅是MMU上后使用. ) ``` adrp x0, pg_dir msr ttbr1_el1, x0 ``` `ttbr1_el1` 已更新为指向先前填充的PGD表. ``` ldr x0, =(TCR_VALUE) msr tcr_el1, x0 ``` 转换控制寄存器的`tcr_el1`负责配置MMU的一些常规参数. (例如, 在这里, 我们配置内核和用户页面表都应使用4 KB页面. ) ``` ldr x0, =(MAIR_VALUE) msr mair_el1, x0 ``` 我们已经在`配置页面属性`部分讨论了 `mair` 寄存器. 在这里, 我们只是设置它的值. ``` ldr x2, =kernel_main mov x0, #SCTLR_MMU_ENABLED msr sctlr_el1, x0 br x2 ``` `msr sctlr_el1, x0`是实际启用MMU的行. 现在我们可以跳转到`kernel_main`函数. 一个有趣的问题是为什么我们不能只执行`br kernel_main`指令?确实, 我们做不到. 在启用MMU之前, 我们一直在处理物理内存, 内核以物理偏移量0加载-这意味着当前程序计数器非常接近0. 打开MMU不会更新程序计数器. 如果现在执行 `br kernel_main` 指令, 则该指令将使用相对于当前程序计数器的偏移量, 并跳转到如果不打开MMU将会是 `kernel_main` 的位置. 另一方面, `ldr x2, = kernel_main` 将使用 `kernel_main` 函数的绝对地址加载 `x2` . 由于我们在链接描述文件中将图片基址设置为 `0xffff000000000000` , 因此 `kernel_main` 函数的绝对地址将被计算为距图片开头的偏移量加上 `0xffff000000000000` , 这正是我们需要. 您需要了解的另一件事是为什么在打开MMU之前必须执行`ldr x2, = kernel_main`指令. 原因是 `ldr` 也使用 `pc` 相对偏移, 因此, 如果我们尝试在MMU开启之后但在跳转到图像基地址之前执行该指令, 则该指令将产生页面错误. ### 分配用户进程 如果您使用的是真正的操作系统, 则可能希望它能够从文件系统读取程序并执行它. 对于RPi OS, 这是不同的-它还没有文件系统支持. 在上一课中, 我们并不为这个事实所困扰, 因为用户进程与内核共享相同的地址空间. 现在情况已经改变, 每个进程都应该有自己的地址空间, 因此我们需要弄清楚如何存储用户程序, 以便以后将其加载到新创建的进程中. 我最终实现的技巧是将用户程序存储在内核映像的单独部分中. 这是链接器脚本的相关部分, 负责执行此操作. ``` . = ALIGN(0x00001000); user_begin = .; .text.user : { build/user* (.text) } .rodata.user : { build/user* (.rodata) } .data.user : { build/user* (.data) } .bss.user : { build/user* (.bss) } user_end = .; ``` 我约定, 应在带有`user`前缀的文件中定义用户级源代码. 然后, 链接程序脚本可以在连续区域中隔离所有与用户相关的代码, 并定义 `user_begin` 和 `user_end`变量, 以标记该区域的开始和结束. 这样, 我们可以简单地将 `user_begin` 和 `user_end` 之间的所有内容复制到新分配的进程地址空间, 从而模拟加载用户程序. 这足够简单, 并且可以很好地用于我们当前的目的, 但是在实现文件系统支持并能够加载ELF文件之后, 我们将摆脱这种黑客. 现在, 在用户区域中编译了2个文件. * [user_sys.S](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/user_sys.S) 该文件包含`syscall`包装函数的定义. RPi OS仍然支持与上一课相同的系统调用, 除了现在我们将使用 `fork` 系统调用代替 `clone` 系统调用. 区别在于`fork`复制进程虚拟内存, 而这正是我们要尝试做的事情. * [user.c](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/user.c) 用户程序源代码. 几乎与上一课中使用的相同. ### 创建第一个用户进程 就像上一课一样, [move_to_user_mode](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/fork.c#L44) 函数负责创建第一个用户进程. 我们从内核线程调用此函数. 这是我们的操作方式. ``` void kernel_process(){ printf("Kernel process started. EL %d\r\n", get_el()); unsigned long begin = (unsigned long)&user_begin; unsigned long end = (unsigned long)&user_end; unsigned long process = (unsigned long)&user_process; int err = move_to_user_mode(begin, end - begin, process - begin); if (err < 0){ printf("Error while moving process to user mode\n\r"); } } ``` 现在我们需要3个参数来调用`move_to_user_mode`:指向用户代码区域开始处的指针, 该区域的大小以及其中的启动函数的偏移量. 该信息是根据先前讨论的`user_begin`和`user_end`变量计算的. 下面列出了`move_to_user_mode`函数. ```cpp int move_to_user_mode(unsigned long start, unsigned long size, unsigned long pc) { struct pt_regs *regs = task_pt_regs(current); regs->pstate = PSR_MODE_EL0t; regs->pc = pc; regs->sp = 2 * PAGE_SIZE; unsigned long code_page = allocate_user_page(current, 0); if (code_page == 0) { return -1; } memcpy(code_page, start, size); set_pgd(current->mm.pgd); return 0; } ``` 现在, 让我们尝试详细检查此处发生的情况. ``` struct pt_regs *regs = task_pt_regs(current); ``` 与上一课一样, 我们获得了指向`pt_regs`区域的指针并设置了`pstate`, 因此在`kernel_exit`之后, 我们将以EL0结尾. ``` regs->pc = pc; ``` 现在, `pc` 指向用户区域中启动功能的偏移量. ``` regs->sp = 2 * PAGE_SIZE; ``` 我们做了一个简单的约定, 即用户程序的大小不得超过1页. 我们将第二页分配到堆栈. ``` unsigned long code_page = allocate_user_page(current, 0); if (code_page == 0) { return -1; } ``` `allocate_user_page`保留1个内存页并将其映射到虚拟地址(作为第二个参数提供). 在映射过程中, 它填充与当前过程关联的页表. 我们将在本章后面详细研究此功能的工作方式. ``` memcpy(code_page, start, size); ``` 接下来, 我们将从偏移量0开始将整个用户区域复制到新的地址空间(在我们刚映射的页面中), 因此用户区域中的偏移量将成为起点的实际虚拟地址. ``` set_pgd(current->mm.pgd); ``` 最后, 我们称 [set_pgd](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/utils.S#L24), 它会更新 `ttbr0_el1` 寄存器, 从而激活当前的进程转换表. ### TLB (Translation lookaside buffer 翻译后备缓冲区) 如果看一下`set_pgd`函数, 您会发现它在设置了`ttbr0_el1`之后也会清除[TLB](https://en.wikipedia.org/wiki/Translation_lookaside_buffer) (Translation lookaside buffer). TLB是专门设计用于存储物理页面和虚拟页面之间的映射的缓存. 第一次将某个虚拟地址映射到物理地址时, 此映射存储在TLB中. 下次我们需要访问同一页面时, 我们不再需要执行整页表遍历. 因此, 在更新页表之后使TLB无效是完全合理的 - 否则, 我们的更改将不会应用于已经存储在TLB中的页面. 通常, 为了简化起见, 我们尽量避免使用所有缓存, 但是如果没有TLB, 则任何内存访问都将变得效率极低, 而且我认为甚至不可能完全禁用TLB. 另外, 尽管我们必须在切换`ttbr0_el1` 之后清理它, 但TLB并没有给操作系统增加任何其他复杂性. ### 映射虚拟页面 我们之前已经看过 [allocate_user_page](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/mm.c#L14) 函数被使用 - 现在该看里面的东西了. ```cpp unsigned long allocate_user_page(struct task_struct *task, unsigned long va) { unsigned long page = get_free_page(); if (page == 0) { return 0; } map_page(task, va, page); return page + VA_START; } ``` 此函数分配一个新页面, 将其映射到提供的虚拟地址, 然后返回指向该页面的指针. 现在, 当我们说`指针`时, 我们需要区分三件事:指向物理页面的指针, 内核地址空间内的指针和用户地址空间内的指针-所有这三种不同的指针都可以导致内存中的相同位置. 在我们的例子中, `page`变量是物理指针, 返回值是内核地址空间内的指针. 这个指针很容易计算, 因为我们线性映射了整个物理内存, 从 `VA_START` 虚拟地址开始. 我们也不必担心分配新的内核页表, 因为所有的内存都已经映射到`boot.S`中. 仍然需要创建用户映射, 这种情况发生在 [map_page](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/mm.c#L62) 函数中, 接下来我们将探讨. ```cpp void map_page(struct task_struct *task, unsigned long va, unsigned long page){ unsigned long pgd; if (!task->mm.pgd) { task->mm.pgd = get_free_page(); task->mm.kernel_pages[++task->mm.kernel_pages_count] = task->mm.pgd; } pgd = task->mm.pgd; int new_table; unsigned long pud = map_table((unsigned long *)(pgd + VA_START), PGD_SHIFT, va, &new_table); if (new_table) { task->mm.kernel_pages[++task->mm.kernel_pages_count] = pud; } unsigned long pmd = map_table((unsigned long *)(pud + VA_START) , PUD_SHIFT, va, &new_table); if (new_table) { task->mm.kernel_pages[++task->mm.kernel_pages_count] = pmd; } unsigned long pte = map_table((unsigned long *)(pmd + VA_START), PMD_SHIFT, va, &new_table); if (new_table) { task->mm.kernel_pages[++task->mm.kernel_pages_count] = pte; } map_table_entry((unsigned long *)(pte + VA_START), va, page); struct user_page p = {page, va}; task->mm.user_pages[task->mm.user_pages_count++] = p; } ``` `map_page` 以某种方式复制了我们在 `__create_page_tables` 函数中所做的工作:它分配并填充了页面表层次结构. 但是, 有3个重要的区别:现在, 我们使用C而不是汇编器进行此操作. `map_page` 映射单个页面而不是整个内存, 并使用普通页面映射而不是部分映射. 该过程涉及2个重要功能: [map_table](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/mm.c#L47) 和 [map_table_entry](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/mm.c#L40). 下面列出了 `map_table` . ```cpp unsigned long map_table(unsigned long *table, unsigned long shift, unsigned long va, int* new_table) { unsigned long index = va >> shift; index = index & (PTRS_PER_TABLE - 1); if (!table[index]){ *new_table = 1; unsigned long next_level_table = get_free_page(); unsigned long entry = next_level_table | MM_TYPE_PAGE_TABLE; table[index] = entry; return next_level_table; } else { *new_table = 0; } return table[index] & PAGE_MASK; } ``` 此函数具有以下参数. * `table` 这是指向父页面表的指针. 假定此页表已经分配, ​​但是可能为空. * `shift` 此参数用于从提供的虚拟地址中提取表索引. * `va` 虚拟地址本身. * `new_table` 这是一个输出参数. 如果已分配了新的子表, 则设置为1, 否则设置为0. 您可以将该函数视为`create_table_entry`宏的类似物. 它从虚拟地址中提取表索引, 并在父表中准备一个指向子表的描述符. 与`create_table_entry`宏不同, 我们不假定子表应该与父表在内存中相邻-相反, 我们依赖于`get_free_table`函数来返回可用的任何页面. 也可能是已经分配了子表(如果子页面表覆盖了先前已分配另一个页面的区域, 则可能会发生这种情况). 在这种情况下, 我们将 `new_table` 设置为0并从父表中读取子页表地址. `map_page`调用`map_table` 3次:一次用于`PGD`, `PUD`和`PMD`. 最后一个调用分配PTE并在PMD中设置描述符. 接下来, 调用 `map_table_entry`. 您可以在下面看到此功能. ```cpp void map_table_entry(unsigned long *pte, unsigned long va, unsigned long pa) { unsigned long index = va >> PAGE_SHIFT; index = index & (PTRS_PER_TABLE - 1); unsigned long entry = pa | MMU_PTE_FLAGS; pte[index] = entry; } ``` `map_table_entry`从虚拟地址中提取`PTE`索引, 然后准备并设置`PTE`描述符. 这类似于我们在`create_block_map`宏中所做的事情. 就是关于用户页表分配的, 但是`map_page`负责另一个重要的角色:它跟踪在虚拟地址映射过程中分配的页面. 所有此类页面均存储在 [kernel_pages](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/include/sched.h#L53) 数组. 我们需要此数组才能在任务退出后清理分配的页面. 也有 [user_pages](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/include/sched.h#L51) 数组, 也由` map_page` 函数填充. 该数组存储有关进程虚拟页面与任何物理页面之间的对应关系的信息. 我们需要此信息, 以便能够在`fork`期间复制进程虚拟内存(稍后会有更多信息). ### Forking a process 在继续前进之前, 让我总结一下到目前为止的情况:我们已经了解了如何创建第一个用户进程, 如何填充其页表, 将源代码复制到正确的位置并进行了堆栈初始化. 完成所有准备工作后, 该过程即可运行. 下面列出了在用户进程内部执行的代码. ```cpp void loop(char* str) { char buf[2] = {""}; while (1){ for (int i = 0; i < 5; i++){ buf[0] = str[i]; call_sys_write(buf); user_delay(1000000); } } } void user_process() { call_sys_write("User process\n\r"); int pid = call_sys_fork(); if (pid < 0) { call_sys_write("Error during fork\n\r"); call_sys_exit(); return; } if (pid == 0){ loop("abcde"); } else { loop("12345"); } } ``` 代码本身非常简单. 唯一棘手的部分是 `fork` 系统调用的语义. 与`clone`不同, 在执行`fork`时, 我们不需要提供需要在新进程中执行的功能. 而且, [fork wrapper function Fork包装功能](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/user_sys.S#L26) 比 `clone` 容易得多. 所有这些都是可能的, 因为`fork`会完整复制进程虚拟地址空间, 因此fork包装函数会返回两次:在原始进程中一次, 在新进程中一次. 此时, 我们有两个相同的过程, 具有相同的堆栈和 `pc` 位置. 唯一的区别是`fork` `syscall`的返回值:它在父进程中返回子PID, 在子进程中返回0. 从这一点开始, 两个进程都开始完全独立的生活, 并且可以使用内存中的相同地址修改其堆栈并写入不同的内容-所有这些都不会相互影响. 现在让我们看看如何实现fork系统调用. [copy_process](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/fork.c#L7) 函数做大部分的工作. ```cpp int copy_process(unsigned long clone_flags, unsigned long fn, unsigned long arg) { preempt_disable(); struct task_struct *p; unsigned long page = allocate_kernel_page(); p = (struct task_struct *) page; struct pt_regs *childregs = task_pt_regs(p); if (!p) return -1; if (clone_flags & PF_KTHREAD) { p->cpu_context.x19 = fn; p->cpu_context.x20 = arg; } else { struct pt_regs * cur_regs = task_pt_regs(current); *childregs = *cur_regs; childregs->regs[0] = 0; copy_virt_memory(p); } p->flags = clone_flags; p->priority = current->priority; p->state = TASK_RUNNING; p->counter = p->priority; p->preempt_count = 1; //disable preemtion until schedule_tail p->cpu_context.pc = (unsigned long)ret_from_fork; p->cpu_context.sp = (unsigned long)childregs; int pid = nr_tasks++; task[pid] = p; preempt_enable(); return pid; } ``` 此功能看起来与上一课几乎完全相同, 但有一个例外:现在, 在复制用户进程时, 无需修改新的进程堆栈指针和程序计数器, 我们调用 [copy_virt_memory](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/mm.c#L87). `copy_virt_memory` 看起来像这样. ```cpp int copy_virt_memory(struct task_struct *dst) { struct task_struct* src = current; for (int i = 0; i < src->mm.user_pages_count; i++) { unsigned long kernel_va = allocate_user_page(dst, src->mm.user_pages[i].virt_addr); if( kernel_va == 0) { return -1; } memcpy(kernel_va, src->mm.user_pages[i].virt_addr, PAGE_SIZE); } return 0; } ``` 遍历`user_pages`数组, 该数组包含当前进程分配的所有页面. 注意, 在 `user_pages` 数组中, 我们仅存储该过程实际可用的页面, 并包含其源代码或数据;我们这里不包括存储在`kernel_pages`数组中的页表页. 接下来, 对于每个页面, 我们分配另一个空页面, 然后在其中复制原始页面内容. 我们还将使用与原始页面相同的虚拟地址来映射新页面. 这就是我们获得原始进程地址空间的精确副本的方式. 分支过程的所有其他细节的工作方式与上一课中所述的完全相同. ### 按需分配新页面 如果回头看一下`move_to_user_mode`函数, 您可能会注意到我们只映射了一个从偏移量0开始的页面. 但是我们也假设第二页面将用作堆栈. 我们为什么还不映射第二页?如果您认为这是一个错误, 那不是-这是一个功能!第一次请求时, 将立即映射堆栈页面以及流程需要访问的任何其他页面. 现在, 我们将探索此机制的内部工作原理. 当进程尝试访问属于尚未映射的页面的某个地址时, 将生成一个同步异常. 这是我们将要支持的第二种同步异常(第一类是由svc指令生成的异常, 它是系统调用). 现在, 同步异常处理程序如下所示. ``` el0_sync: kernel_entry 0 mrs x25, esr_el1 // read the syndrome register lsr x24, x25, #ESR_ELx_EC_SHIFT // exception class cmp x24, #ESR_ELx_EC_SVC64 // SVC in 64-bit state b.eq el0_svc cmp x24, #ESR_ELx_EC_DABT_LOW // data abort in EL0 b.eq el0_da handle_invalid_entry 0, SYNC_ERROR ``` 在这里, 我们使用`esr_el1`寄存器来确定异常类型. 如果是页面错误异常(或相同的数据访问异常), 则调用 `el0_da` 函数. ``` el0_da: bl enable_irq mrs x0, far_el1 mrs x1, esr_el1 bl do_mem_abort cmp x0, 0 b.eq 1f handle_invalid_entry 0, DATA_ABORT_ERROR 1: bl disable_irq kernel_exit 0 ``` `el0_da`将主要工作重定向到 [do_mem_abort](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/src/lesson06/src/mm.c#L101) 函数. 该函数有两个参数 1. 我们试图访问的内存地址. 该地址取自 `far_el1` 寄存器(故障地址寄存器) 1. `esr_el1`(异常综合征寄存器)的内容 下面列出了`do_mem_abort`. ```cpp int do_mem_abort(unsigned long addr, unsigned long esr) { unsigned long dfs = (esr & 0b111111); if ((dfs & 0b111100) == 0b100) { unsigned long page = get_free_page(); if (page == 0) { return -1; } map_page(current, addr & PAGE_MASK, page); ind++; if (ind > 2){ return -1; } return 0; } return -1; } ``` 为了理解这个功能, 您需要对`esr_el1`寄存器的细节有所了解. 该寄存器的位[32:26]被称为​​ `异常类`. 我们检查 `el0_sync` 处理程序中的那些位, 以确定它是系统调用, 数据异常中止还是其他可能的原因. 异常类确定位的含义[24:0] - 的那些位通常用来提供有关异常的附加信息. 在 `AArch64-Reference-Manual` 的第2460页中描述了发生数据异常中止时[24:0]位的含义. 通常, 数据中止异常可能会在许多不同的情况下发生(可能是权限错误, 地址大小错误或许多其他情况). 我们仅对转换错误感兴趣, 该错误在当前虚拟地址的某些页表未初始化时发生. 因此, 在 `do_mem_abort` 函数的前两行中, 我们检查当前异常是否实际上是翻译错误. 如果是的, 我们分配了新的一页, 并将其映射到所请求的虚拟地址. 所有这些对于用户程序来说都是完全透明的-它没有注意到某些内存访问被中断, 同时还分配了新的页表. ### 结论 这是一个漫长而艰难的章节, 但我希望它也有用. 虚拟内存确实是所有操作系统中最基本的部分之一, 我很高兴我们阅读了本章, 并希望开始了解它在最低级别上的工作方式. 随着虚拟内存的引入, 我们现在已经实现了完全的进程隔离, 但是RPi OS仍远未完成. 它仍然不支持文件系统, 驱动程序, 信号和中断等待列表, 网络以及许多其他有用的概念, 我们将在即将开始的课程中继续发现它们. ##### 上一页 5.3 [用户流程和系统调用:练习](../../docs/lesson05/exercises.md) ##### 下一页 6.2 虚拟内存管理: Linux (进行中) 6.3 跳到 [虚拟内存管理:练习](../../docs/lesson06/exercises.md)