2019操作系统课程设计:Audio support for rCore

在本页面维护操作系统课程设计大实验"Audio support for rCore"的相关信息。老师、助教和选做课程设计的同学可修改该页面的内容。

实验目标描述

1. 为rCore提供audio output支持,并可在RPI上运行

  1. 对上层,以linux Open Sound System格式,提供audio output接口
  2. 对底层,通过bcm2837,使用DMA向PWM输出音频流,最后输出音频到3.5mm接口

2. 在rCore上实现一个简单的音频播放器

  1. 可支持播放音频文件,并提供简单的控制功能

3. 与其他组合作,实现SD卡上的音频播放

实验参与者信息

姓名

学号

电子邮箱

GitHub 账户

王晓智

2016011257

xz-wang16@mails.tsinghua.edu.cn

Bakser

高天宇

2016011348

gaotianyu1350@126.com

gaotianyu1350

实验代码仓库:

期中展示:

13周展示:

16周展示:

已有工作参考 / 相关文献

往届项目

[Rucore with Drivers](http://os.cs.tsinghua.edu.cn/oscourse/OS2018spring/projects/g15)

[Rust OS 上树莓派 USB 与 VideoCore IV 显卡驱动的移植](http://os.cs.tsinghua.edu.cn/oscourse/OsTrain2018/g2)

[uCore with GUI](http://os.cs.tsinghua.edu.cn/oscourse/OS2018spring/projects/g14)

[Rustable ucore在arm平台的rust移植](http://os.cs.tsinghua.edu.cn/oscourse/OS2018spring/projects/g13)

OSS相关

[OSS Programming Document](http://www.opensound.com/pguide/oss.pdf)

GPIO/DMA/PWM相关

[BCM2837 ARM Peripherals Document](https://cs140e.sergio.bz/docs/BCM2837-ARM-Peripherals.pdf)

[GPIO](https://www.raspberrypi.org/documentation/usage/gpio/)

树莓派裸机代码

[Circle C++](https://github.com/rsta2/circle)

[Rust bare-metal for Raspberry](https://github.com/rust-embedded/rust-raspi3-OS-tutorials)

实现方案

rCore File System对Device File支持

Rust语言特性带来的一些问题

- trait / struct的概念:

- 生命周期:

Rust的这些特性主要是从安全性的角度考虑,然而会给底层代码的编写造成很多麻烦,我们也花了很多时间来研究rust的代码特性,以及如何解决这些问题

File System只读问题

由于目前rCore还是FileSystem in memory,直接通过user image构建文件系统,因为没有多余的空间所以是只读的。通过在user image后面添加blank image,为FileSystem增加额外空间,可实现对FileSystem的修改(新建文件/文件夹等)

添加Device File

- 原本在rCore中stdio.rs已经有STDIN STDOUT的INode

- 在SFS中维护一个device list(INode list),在SFS的INodeImpl中记录其对应的device file的ID,这样当对该INodeImpl对应的device file进行读写操作时,如果该Impl类型为chardevice,则根据ID,在SFS的device list中寻找对应Device INode,并调用其读写接口

- 为SFS添加动态添加Device INode的接口

- 为INodeImpl添加了link一个device file的借口

- 添加一个新的device file的流程

- 添加IOCTL

PWM

PWM是树苺派3中的硬件,向其输入字节流形式的声音文件它会自动转换为另一种形式的字节流并通过GPIO输入到声卡,从而发出声音。

代码见bcm2837/pwm.rs中的PWMOutput

关于PWM的硬件规范文档见 [BCM2837 ARM Peripherals Document] (https://cs140e.sergio.bz/docs/BCM2837-ARM-Peripherals.pdf) 的138页。我们只用到了PWM mode的MSEN=0的方式。

PWM操作的流程:

- start

传入两个channel的range。

初始化GPIO clock。

- write_fifo

一次写一个数,向硬件fifo写入,轮询FIFO状态等待写入。

注意由于硬件限制,音乐最高12bit。(逻辑详见final PPT 14页)

- stop

把CTL寄存器设为0即可

DMA

DMA可以自行与IO设备交互,进行内存拷贝,比特流传输等操作,无需占用CPU时间,可实现各类异步的、快速的传输操作。

BCM2837的DMA具有以下特征:

- 16个channel可供选择,互相独立互不影响

- DREQ:peripheral-generated signal,可向BCM2837上的其他peripheral,例如GPIO、PWM等进行符合一定频率的输出,适合需要固定频率输入的任务,如音频

- Control Block控制:Control Block是一段内存中的地址,用于设定DMA任务的相关信息

- IRQ中断:DMA拷贝完成或拷贝错误触发

Control Block

Control Block主要包括以下内容,均在bcm2837/dma.rs中以register struct形式实现,可安全方便的调用:

- Transfer Information:一些拷贝相关的信息

- Source Address:拷贝源地址

- Destination Address:拷贝目标地址

- Transfer Length:拷贝长度

- 2D Mode Stride:2D Mode 步长,暂时没有用到

- Next Control Block Address:用于Control Block链,当前拷贝完成时跳到下一个Control Block

- Reserved

DMA Control Register

DMA Control Block是内存中的一段结构体,提供必要的信息。若要真正对DMA实现控制,还需要对DMA Control Register进行读写,其映射在某段固定的内存地址中:

DMA向PWM输出

通过DMA从内存中拷贝比特流到PWM主要需要以下步骤:

- 初始化PWM

- 初始化Control Block信息(包括地址,长度等)

- 设定对应channel的CS register(模式,control block address,以及开始传输)

- 传输开始,音频流输出

一些遇到的坑

- 所有peripherals里面指定的地址为bus address

- Memory ordering问题:因为对peripheral register操作的时候,访存可能会乱序,为了避免问题应该使用rust中的Volatile,或者使用汇编构建一个memory barrier。

- 为DMA设定的control block以及buffer应该避免使用cache(不然可能会有RAW等问题),要clear and invalidagte cache

音频播放

- 用户程序读入音频文件,打开/dev/dsp,可用ioctl控制播放模式/暂停,可直接将音频流写入/dev/dsp

- /dev/dsp对应的device代码打开PWM和DMA,为DMA申请control block和buffer的内存,拷贝信息和音频流,设定PWM和DMA相关参数,然后触发DMA

- DMA按照设定参数向PWM以PWM指定频率进行音频流拷贝

- PWM播放声音

代码接口

修改在bcm2837 repo dma.rs中

- DMA::start: 开始传输

- DMA::stop:停止传输

- DMA::is_active:是否仍然在传输中,是的话返回True

主要代码修改

1. BCM2837:添加PWM和DMA支持,GPIO时钟支持(const.rs, pwm.rs, dma.rs)

2. rcore-fs:使文件系统支持link device file

3. rcore-user:添加audio user program

4. rCore:添加audio驱动(fs部分,aarch64初始化部分)

5. QEMU对gpio调试支持

测试结果

rCore启动时将audio device file挂载在/dev/dsp下,可通过用户程序播放音频,支持同步播放和异步播放,异步下可暂停播放音乐。

在与其他组合作的项目中,可以从SD卡load audio driver kernel module,挂在/dev/dsp下,并调用SD卡上的用户程序进行音乐的播放。

日志

5.24 UPDATE DMA+PWM work

前面两周一直在调试DMA+PWM这两个东西,遇到了不少坑,不过现在可以跑通了,可以DMA->PWM->3.5mm,一些坑记录如下:

- PWM需要配置一个GPIO clock才能按照正确的频率工作

- 所有Peripheral用的都是bus address而非physical address,且rasp1和2/3的bus address不同

- 考虑memory ordering的问题,可以用rust Volatile / memory barrier / 延时解决

- DMA中需要clear and invalidate cache of control block and buffer

5.6 UPDATE:DMA->PWM->3.5mm音频硬件相关代码完成,仍未跑通

因为之前wiki不太稳定所以一直没更新,今天统一更新一下。目前的状态

- 写GPIO对应的端口可以在树莓派(硬件)上发出刺耳的声音

- 找到一个C++的裸机代码,可以在树莓派(硬件)上跑通并发出一段连续的声音(音乐)

- 实现了基于DMA的声音驱动(在BCM2837仓库中),跑通声音 -> device file -> DMA(QEMU+树莓派硬件),但目前仍无法发出声音。怀疑问题可能是buffer地址无效,继续debug,本周内让rCore+树莓派可以发出声音

4.13 qemu模拟树苺派GPIO;rcore支持ioctl和gpio

发现好像没什么人做过qemu里面的GPIO怎么输出来的问题,有一些做外部模拟GPIO的。

直接读了qemu的源码找到了模拟GPIO时callback的函数,成功把raspi3 model下的GPIO状态变化输出来了。


给device file添加了ioctl的功能。思考良久把这个东西加到了INode里面。本来我是想加到INodeImpl里面,但后来发现STDIN等是直接实现的INode,而且还有针对他的ioctl调用,所以作罢。

然后实现了一个gpio的device file,可以用ioctl指定pin,然后往里面输入东西就会set这个pin。再加上改进的qemu,运行的时候可以看到对应引脚的gpio被set了

4.12 qemu模拟树苺派; device file work!

找到了一个test版的[debian](https://wiki.debian.org/RaspberryPi3 ),手动mount起来改了一些东西终于能在QEMU的aarch64架构和raspi3 model里跑起来了,发现根本没有声卡。

改了boot的config打开声卡还是没用,发现QEMU的raspi3 model似乎就没模拟声卡。

决定放弃QEMU声卡穿透的思路,先看看能不能把GPIO穿透出来,后期直接上板子测试。


device file可以用了!在初始化的时候直接link了/dev/stdout,测试user程序可以正常打开stdout,向其输出会反馈到屏幕上

遇到的最主要的问题是,目前系统里面的stdout(有fd=1)是一个INode,而link到/dev下的因为要用simple file system,而sfs必须是INodeImpl,rust又不知道up/downcast,我就把这个stdout的INode丢到了INodeImpl里面。然而事实上因为rust的生命周期等等原因,sfs并没有这个INodeImpl的所有权,所以在打开这个文件的时候它对应的INodeImpl根本不存在!

我的解决方案:所有device inode都保存在sfs下,INodeImpl仅记录一个编号(貌似和ucore里面的实现是一样的)。因为INodeImpl还对应这一个DistINode,而这个东西是写在硬盘(现在是内存U0001f602)里面的,所以即使INodeImpl没有了,因为DiskINode还是有的,只要把信息恢复出来重新建一个INodeImpl就完了

4.11 研究qemu声卡穿透;成功link设备的/dev下

为了配置qemu声卡穿透让PC上模拟发出声音,需要用qemu模拟一个正常rpi OS来测试qemu对rpi声卡的支持。

但是现在qemu模拟rpi比较麻烦,大部分都是用的-M versatilepb,而这是不支持64位的。

正在研究怎么用aarch64和-M raspi3跑起来。


成功创建了设备文件并且link到/dev目录下,理论上读写这个文件就会调用对应设备的读写接口,但还没有测试

4.10 为INodeImpl添加设备id

SimpleFileSystem添加了一个设备INode列别,INodeImpl里面添加设备id(如果有的话),这样如果INodeImpl类型是device的话,就去filesystem里面找对应的设备,并且用该设备的读写方式,而非INodeImpl默认的读写

4.9 添加dev目录,sfs的device file

1. 添加了dev目录

2. 研究如何将device file加入sfs:目前sfs内维护的是struct INodeImpl,根据rust语言特性是不能继承的,但我们又需要为device file重写read和write方法。要调研rust中面向对象的正确姿势。

4.8 为sfs在内存中添加空闲区域

现在sfs是直接把user image加载到内存中,但如果要新建inode为其分配对应空间的时候,并没有检查是否有足够空间(事实上根本没有空间)

为sfs在内存中添加空闲区域。这里犯了个错误:直接在读user image的时候把长度改大了,本来以为这样没问题,但实际上user image从硬盘加载到内存的时候用的是unsafe的c代码,因此直接把长度改大虽然没有报错,其实已经访问到不该访问的内存了,导致后面出现了一系列错误,debug了一天...

最终方案是生成一个空白的img,然后把它load到user img的后面

4.7

尝试为rCore添加device file,发现在aarch64下无法mkdir,调试后发现是MemBuf的大小限制在load进来的user image

4.4

user程序运行不了,后来在周聿浩和贾越凯提醒下发现执行/biscuit/xxx才行

研究了下ucore和rcore对device file的实现,发现在rcore下process有一个叫files的属性,每sys_open一个device file/普通file就会加入到files里面,stdin/stdout默认在里面

还有一个坑:rcore大部分初始化在lazy_static里面执行,刚开始gdb跟踪的时候没发现这一点,找不到好多东西的初始化

4.3

从mac换到linux终于成功运行了rCore,不知道是toolchain有问题还是mac的qemu有问题……

4.2

aarch64的rCore跑不起来,似乎是在启动shell的时候挂了,咨询了下杰哥和王润基,可能是user的镜像的问题

4.1

尝试编译rCore,遇到奇怪的错误,发现是rust更新了特性。杰哥帮助修复了bug,之后fix rust版本,避免出现类似错误

lab

王晓智8个lab:https://github.com/oscourse-tsinghua/os2019-Bakser

高天宇8个lab:https://github.com/oscourse-tsinghua/os2019-gaotianyu1350

OS2019spring/projects/g08 (last edited 2019-06-16 15:53:15 by 2016011257)

MoinMoin Appliance - Powered by TurnKey Linux