惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

人人都是产品经理
人人都是产品经理
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
P
Privacy International News Feed
Simon Willison's Weblog
Simon Willison's Weblog
I
Intezer
Spread Privacy
Spread Privacy
The Hacker News
The Hacker News
P
Palo Alto Networks Blog
TaoSecurity Blog
TaoSecurity Blog
S
Secure Thoughts
Google Online Security Blog
Google Online Security Blog
H
Heimdal Security Blog
N
News | PayPal Newsroom
Attack and Defense Labs
Attack and Defense Labs
Recent Commits to openclaw:main
Recent Commits to openclaw:main
博客园 - 【当耐特】
Webroot Blog
Webroot Blog
小众软件
小众软件
Help Net Security
Help Net Security
D
Darknet – Hacking Tools, Hacker News & Cyber Security
N
News and Events Feed by Topic
Hacker News - Newest:
Hacker News - Newest: "LLM"
PCI Perspectives
PCI Perspectives
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The Cloudflare Blog
Cloudbric
Cloudbric
AI
AI
WordPress大学
WordPress大学
博客园 - 聂微东
Jina AI
Jina AI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 三生石上(FineUI控件)
Hacker News: Ask HN
Hacker News: Ask HN
H
Hacker News: Front Page
博客园 - Franky
V
V2EX
Schneier on Security
Schneier on Security
G
GRAHAM CLULEY
S
SegmentFault 最新的问题
有赞技术团队
有赞技术团队
H
Help Net Security
量子位
S
Security @ Cisco Blogs
大猫的无限游戏
大猫的无限游戏
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Recorded Future
Recorded Future
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
J
Java Code Geeks
C
Cisco Blogs
S
Security Affairs

贰猹の小窝

个人哔哔: 谈薪时间-该问hr哪些问题 个人哔哔: 秋招与未来 从零开始的异世界主题开发生活-hexo事件管理与扩展使用 个人哔哔: 2022年度总结与全新的大饼 猹的魔改日记-文章页抛弃卡片、丢弃小菜单以及布局重构 猹的魔改日记-小小的重写个页脚 回忆录: 知心友与白月光 rust写个操作系统——课程实验blogos移至armV8深度解析:实验八下 内存管理 rust写个操作系统——课程实验blogos移至armV8深度解析:实验八上 内存管理 HNU工训综合项目二:基于485总线的评分系统速通指北 rust写个操作系统——课程实验blogos移至armV8深度解析:实验七 死锁与简单处理 rust写个操作系统——课程实验blogos移至armV8深度解析:实验五 输入 rust写个操作系统——课程实验blogos移至armV8深度解析:实验四 中断 rust写个操作系统——课程实验blogos移至armV8深度解析:实验二 Hello World 关于linux: 设备树浅析(更新中) rust写个操作系统——课程实验blogos移至armV8深度解析:实验一 环境配置 rust写个操作系统——课程实验blogos移至armV8深度解析 猹的魔改日记-小菜单和复制/F12提示栏 个人哔哔: 一年期至,又一年始(不定时写完)
rust写个操作系统——课程实验blogos移至armV8深度解析:实验六 GPIO关机
noionion · 2022-04-26 · via 贰猹の小窝

你将在每个实验对应分支上都看到这句话,确保作者实验代码在被下载后,能在正确的环境中运行。

运行环境请参考: lab1 环境搭建

cargo build
qemu-system-aarch64 -machine virt -m 1024M -cpu cortex-a53 -nographic -kernel target/aarch64-unknown-none-softfloat/debug/blogos_armv8 -semihosting

实验六 GPIO关机

我们不能一直到qemu的暴力退出来关机(就是不用系统的关机而暴力断电)。所幸,virt机器为我们提供了GPIO来实现关机功能。这节我们将编写GPIO相关的驱动来实现关机功能。


实验目的

实验指导书中这节也没有写实验目的。我大致把目的划分如下:

  1. 编写pl061(GPIO)通用输入输出模块的驱动

  2. 实现关机功能


pl061(GPIO)模块驱动编写

上一实验我们已经对tock-registers有了基础的了解,恰好实验六终于是有点意思,开始让我们自己写驱动了。

所以在这节我们将做一个示例,讲述我们该如何去描述一个硬件的驱动。

pl061(GPIO)基本知识

GPIO(General-purpose input/output),通用型之输入输出的简称,功能类似8051的P0—P3,其接脚可以供使用者由程控自由使用,PIN脚依现实考量可作为通用输入(GPI)或通用输出(GPO)或通用输入与输出(GPIO),如当clk generator, chip select等。

既然一个引脚可以用于输入、输出或其他特殊功能,那么一定有寄存器用来选择这些功能。对于输入,一定可以通过读取某个寄存器来确定引脚电位的高低;对于输出,一定可以通过写入某个寄存器来让这个引脚输出高电位或者低电位;对于其他特殊功能,则有另外的寄存器来控制它们。

而在此实验中,我们用的arm架构的GPIO文档在此:ARM PrimeCell General Purpose Input/Output (PL061) Technical Reference Manual

virt机器关机原理

查看设备树:

gpio-keys {
#address-cells = <0x01>;
#size-cells = <0x00>;
compatible = "gpio-keys";

poweroff {
gpios = <0x8003 0x03 0x00>;
linux,code = <0x74>;
label = "GPIO Key Poweroff";
};
};

virt机器关机原理

可以看到,关机键接入到了GPIO处理芯片的三号输入口(设备树上的反映在gpio-keyspoweroff["gpios"]第二个参数反映。当外部输入关机指令时,三号线将产生一次信号并发生一次中断。让我们记住这一点,这是实现关机功能的关键。

驱动编写实例

由于我们只需要实现关机功能,所以这里我们也并不定义额外的寄存器。之所以我在这里称之为一个示例,是因为我们并没有完整的实现它。

当我们向GPIO中输入关机指令时,GPIORIS(中断状态寄存器PrimeCell GPIO raw interrupt status)中的第三位将从0跳变到1。而当GPIOIE(中断掩码寄存器PrimeCell GPIO interrupt mask)中的第三位为1时,GPIO处理芯片将向GIC中断控制器发送一次中断,中断号为39。而我们受到中断后,需要丢此次GPIO中断进行清除,将GPIOIC(中断清除寄存器PrimeCell GPIO interrupt clear)的对应位置为1,然后进行关机操作。

另外在设备树文件中,关于GPIO的设备描述如下:

pl061@9030000 {
phandle = <0x8003>;
clock-names = "apb_pclk";
clocks = <0x8000>;
interrupts = <0x00 0x07 0x04>;
gpio-controller;
#gpio-cells = <0x02>;
compatible = "arm,pl061\0arm,primecell";
reg = <0x00 0x9030000 0x00 0x1000>;
};

可以看到GPIO设备的内存映射起始地址是0x09030000

由于GPIORIS是一个只读寄存器,而我们知道一旦关机该寄存器的值将变为0b00001000(三号线产生的中断),在此我们并不需要将其在代码中体现。因此,我们在本节实验中,只需要定义GPIOIEGPIOIC两个寄存器。

现在我们开始动手写驱动了,新建src/pl061.rs,先写入一个基本的模板。

use tock_registers::{registers::{ReadWrite, WriteOnly, ReadOnly}, register_bitfields, register_structs};


pub const PL061REGS: *mut PL061Regs = (0x0903_0000) as *mut PL061Regs;


register_bitfields! [
u32,
];


register_structs! {
pub PL061Regs {
}
}

我们自顶向下,从寄存器结构定义和映射开始,在到寄存器位级细节进行对应的定义:

寄存器基本结构描述

首先是两个寄存器的定义,我们查看GPIO的寄存器表:Summary of PrimeCell GPIO registers,找到我们需要的两个寄存器信息:

GPIOIE

GPIOIC

需要记下的是两个寄存器的基址和读写类型,我们可以作如下基本的定义:


register_structs! {
pub PL061Regs {
(0x410 => pub ie: ReadWrite<u32>),
(0x41c => pub ic: WriteOnly<u32>),
}
}

tock-registers对寄存器结构定义有如下的要求,我用加粗标识出我们需要注意的部分:

寄存器的定义是通过register_structs宏完成的,该宏要求每个寄存器有一个偏移量、一个字段名和一个类型。寄存器必须按偏移量的递增顺序连续顺序声明。定义寄存器时,必须使用偏移量和间隙标识符(按照惯例,使用名为_reservedN的字段)显式注释间隙,但不使用类型。然后,宏将自动计算间隙大小并插入合适的填充结构。结构的末尾用大小和@end关键字标记,有效地指向寄存器列表后面的偏移量。

寄存器基址从0x000开始,故我们填入空缺,并在最后一个寄存器的下一个地址填入@end标记:


register_structs! {
pub PL061Regs {
(0x000 => __reserved_0),
(0x410 => pub ie: ReadWrite<u32>),
(0x414 => __reserved_1),
(0x41c => pub ic: WriteOnly<u32>),
(0x420 => @END),
}
}
寄存器位级细节

首先是GPIOIE寄存器的细节定义,我们查看该寄存器的细节:Interrupt mask register, GPIOIE

可以知道每位的值即为对应输入输出线的中断掩码。例如第3号位(0开始)的中断启用,则应设置第三位的值为1。我们在register_bitfields!宏中写入我们需要的第三号位具体描述:


register_bitfields![
u32,


pub GPIOIE [
IO3 OFFSET(3) NUMBITS(1) [
Disabled = 0,
Enabled = 1
]
],
];

这里的IO3只是对三号位的一个命名,OFFSET偏移参数指明该位为第三号位,NUMBITS指明该位功能共有1位。你在其它的定义中可能会见到以两位甚至更多来存储对应信息。

IO3下的键值更像是一种标识,定义后变可以以更方便的方式对寄存器进行读写。左边的命名是对右边赋值的解释。我们之后在解释时,不需要去记忆某一个位中赋值多少是什么功能,而可以通过命名去做精准的调用。例如官方文档示例中:

Control [
RANGE OFFSET(4) NUMBITS(2) [



VeryHigh = 0,
High = 1,
Low = 2
]]

我们在对Control寄存器的[4:6]号位赋值低电平时,只需要使用xx::Control.write(xx::Control::Low),而无需记忆低电平是0还是2

然后我们需要将寄存器结构描述中的寄存器与其细节联系起来,修改register_structs!宏中的0X410一行:

-         (0x410 => pub ie: ReadWrite<u32>),
+ (0x410 => pub ie: ReadWrite<u32, GPIOIE::Register>),

发生中断时,回调处理中GPIOIC寄存器的值我们可以直接写入GPIOIE来描述,这里对不对其进行细节描述并不重要。当然对其具体定义也不会有太大的问题。

而在模板开头我们引入了类型READONLY,而我们定义完寄存器后并没有使用它,因此删除这个引用。

- use tock_registers::{registers::{ReadWrite, WriteOnly, ReadOnly}, register_bitfields, register_structs};
+ use tock_registers::{registers::{ReadWrite, WriteOnly}, register_bitfields, register_structs};

最后记得向src/main.rs中引入驱动:mod pl061;,最终的pl061模块驱动如下:

use tock_registers::{registers::{ReadWrite, WriteOnly}, register_bitfields, register_structs};


pub const PL061REGS: *mut PL061Regs = (0x0903_0000) as *mut PL061Regs;


register_bitfields![
u32,


pub GPIOIE [
IO3 OFFSET(3) NUMBITS(1) [
Disabled = 0,
Enabled = 1
]
],
];


register_structs! {
pub PL061Regs {
(0x000 => __reserved_0),
(0x410 => pub ie: ReadWrite<u32, GPIOIE::Register>),
(0x414 => __reserved_1),
(0x41c => pub ic: WriteOnly<u32>),
(0x420 => @END),
}
}

个人也写了个较为完整的驱动(gpiodata那个描述可能有些问题),可以查看https://github.com/2X-ercha/blogOS-armV8/blob/lab6/src/pl061_all.rs

实现关机中断及其处理回调函数

关机中断仍然是el1_irq级别的中断,经过了上两个实验的回调函数编写,这部分可以说是熟门熟路了。

关机中断初始化

同前两个中断一样,我们还是需要对输入中断进行启用和配置。同时不一样的是,我们还要为GPIOGPIOIE中断掩码寄存器作初始化。修改src/interrupts.rs,新增如下内容:


const GPIO_IRQ: u32 = 39;

use tock_registers::interfaces::{Readable, Writeable};

pub fn init_gicv2() {



set_config(GPIO_IRQ, ICFGR_LEVEL);
set_priority(GPIO_IRQ, 0);
clear(GPIO_IRQ);
enable(GPIO_IRQ);


use crate::pl061::*;
unsafe{
let pl061r: &PL061Regs = &*PL061REGS;


pl061r.ie.write(GPIOIE::IO3::Enabled);
}
}

关机中断处理回调

然后对关机中断进行处理:修改我们的中断实际处理函数handle_irq_lines为如下,并新增输入中断处理函数handle_gpio_irq

fn handle_irq_lines(ctx: &mut ExceptionCtx, _core_num: u32, irq_num: u32) {
if irq_num == TIMER_IRQ {
handle_timer_irq(ctx);
} else if irq_num == UART0_IRQ {
handle_uart0_rx_irq(ctx);
} else if irq_num == GPIO_IRQ {
handle_gpio_irq(ctx);
} else{
catch(ctx, EL1_IRQ);
}
}

fn handle_gpio_irq(_ctx: &mut ExceptionCtx){
use crate::pl061::*;
crate::println!("power off!\n");
unsafe {
let pl061r: &PL061Regs = &*PL061REGS;


pl061r.ic.set(pl061r.ie.get());

asm!("mov w0, #0x18");
asm!("hlt #0xF000");
}
}

我们尝试关机,这里用到了ArmSemihosting功能。

Semihosting 的作用

Semihosting 能够让 bare-metal 的 ARM 设备通过拦截指定的 SVC 指令,在连操作系统都没有的环境中实现 POSIX 中的许多标准函数,比如 printf、scanf、open、read、write 等等。这些 IO 操作将被 Semihosting 协议转发到 Host 主机上,然后由主机代为执行。

构建并运行内核。为了启用semihosting功能,在QEMU执行时需要加入-semihosting参数

cargo build
qemu-system-aarch64 -machine virt -m 1024M -cpu cortex-a53 -nographic -kernel target/aarch64-unknown-none-softfloat/debug/blogos_armv8 -semihosting

在系统执行过程中,在窗口按键ctrl + a, c,后输入system_powerdown关机。(这里为了实验更加直观,我注释掉了时间中断的打点输出)

关机!