一、实验目的
1.理解操作系统与硬件的接口方法
2.实现一个可打印字符的宏(非系统调用),用于后续的调试和开发
二、实验过程
1.在Linux中安装设备树格式转换工具
apt-get install device-tree-compiler
2.通过QEMU导出设备树并转成可读格式
qemu-system-aarch64 -machine virt,dumpdtb=virt.dtb -cpu cortex-a53 -nographic
dtc -I dtb -O dts -o virt.dts virt.dtb
virt.dtb转换后生成的virt.dts中可找到如下内容:
由上可以看出,virt机器包含有pl011的设备,该设备的寄存器在0x9000000开始处。pl011实际上是一个UART设备,即串口。可以看到virt选择使用pl011作为标准输出,这是因为与PC不同,大部分嵌入式系统默认情况下并不包含VGA设备。
3.在src/bsp目录下创建print.c文件,包含所需头文件以及定义后续将用到的宏
此处定义了pl011设备的基址寄存器地址,以及Data、Flag、Line Control寄存器对基址的偏移量,同时设置两个掩码,用于取出发送缓冲区满置位以及启用发送和接收IO,此外还实现了两个基本函数的宏定义,用于对设备寄存器进行读写。
4.进行串口的初始化
查阅用户手册可以发现,FIFO的使能位FEN位于Line Control Register的4号位,正好对应我们所设置的掩码0x10,所以此处串口初始化进行的操作为通过设置寄存器的使能位FEN来启用FIFO。
5.向串口发送字符
这里首先在我们定义的宏基础上,定义了将基址reg_base+偏移offset处寄存器的值读出以及向该寄存器中写入值的函数,以及定义了一个检查函数,通过Flag register中的TXFF位来判断发送缓冲是否已满,最后就是在最初定义的最大时间范围内轮询,若发送缓冲区没满则向数据寄存器写入字符,实现发送字符到串口,达到输出字符的目的。
6.支持格式化输出
此处利用可变参数列表va_list,通过调用函数vsnprintf_s来实现C语言中printf的格式化输出。
6.实现vsnprintf_s函数
vsnprintf_s函数的主要功能是依据格式控制符将可变参数列表转换成字符列表写入缓冲区,本实验从国产实时操作系统 RT-Thread 的 kservice.c 中引入了一个实现并进行了简单修改,在作业二中则使用libboundscheck 库中的vsnprintf_s 函数。
7.调用PRT_printf函数
8.将新增文件纳入构建系统
9.在start.S中启用FPU
至此,我们完成了基本的输出和调试手段,重新构建项目并执行,发现可以正常输出。
三、测试及分析
构建项目并正常执行,Hello,miniEuler!:
建议大家使用ASCII码编辑器进行个性化的输出,毕竟是自己写出的第一个操作系统内核~🥰
附上老师提供的设计网站:manytools.org
四、Lab2作业
作业一:不启用 fifo,通过检测 UARTFR 寄存器的 TXFE 位来发送数据。
根据上面报告中的分析,将fifo禁用:
在初始化串口时,不设置使能位FEN,禁用fifo:
查阅用户手册,可以发现,当fifo禁用时,TXFE会在holding register为空的时候被设置为1,为了取出Flag register中的TXFE位,我们设置一个新的掩码DW_XHR_NOT_FULL为0x80(从右至左Bits=7 ,故为第八位,将0x80转成二进制,对应就是第八位为1,其他位为0,取出第八位)。
修改检查函数为查看TXFE位:
因为当holding register为空时TXFE被设置为1,而我们的函数则是需要检测缓冲区是否为满,所以将检查函数返回值取反,可以满足缓冲区为满时函数返回1。
最后将轮询函数中的检查函数更改为查看TXFE的函数:
重新构建项目并执行,发现顺利输出:
作业二:采用 UniProton 提供的 libboundscheck 库实现 vsnprintf_s 函数。
1.首先在华为的OpenEuler的Gitee官网下载UniProton提供的vsnprintf_s函数
2.接着将下载的libboundscheck目录下src目录中的函数以及头文件放入我们操作系统内核的bsp目录
3.将libboundscheck目录下的文件include目录中的头文件加入我们操作系统内核中的include目录中
4.将新加入bsp目录的文件纳入cmake构建系统
5.至此准备工作完成,利用Cmake构建工程
6.最后运行程序:
五、心得体会
1.了解并实现了串口输出
2.加深了对操作系统与硬件之间的接口的理解
3.理解了底层不同寄存器的功能与函数实现