riscv架构下linux4.15实现early打印

在高版本linux6.12.7源码中,early console介绍,可参考《riscv架构下linux6.12.7实现early打印》文章。

1 什么是early打印

适配内核到新的平台,基本环境搭建好之后,首要的就是要调通串口,方便后面的信息打印。
正常流程 init/main.c 中 start_kernel 入口,要到 console_init 之后才能真正打印,前面的打印,都是缓存在 printk 的 ringbuffer 中的。
如果在 console_init 前就异常了,此时就看不到打印信息,为了调试 console_init 前的状态,需要能更早的打印,内核提供了一种 early 打印的方式,尤其是 riscv 平台我们可以直接 ecall 调用 opensbi 的打印,这样 opensbi 适配好之后,这里就可以直接使用。

earlyprintk 的实现依赖于特定的硬件平台,并且通常与特定固件配合使用。
earlyprintk 是一个高级功能,主要用于内核开发和调试。在生产环境中,通常不需要启用此功能,因为它可能会干扰系统的正常启动过程,或暴露潜在的敏感信息。

从 earlyprintk 到串行控制台的转换,通常发生在内核初始化过程中,特别是在 register_console 函数被调用之后。这个函数负责注册串行控制台,并使其成为内核默认的打印信息输出设备。一旦串行控制台被注册,内核就会开始使用它来输出打印信息,而 earlyprintk 则不再被需要。

2 printk函数实现

printk函数代码实现,如下所示:

// 1.riscv-linux-4.15/include/linux/printk.h:
#define pr_info(fmt, ...) \
	printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)  调用==>

// 2.riscv-linux-4.15/kernel/printk/printk.c:
asmlinkage __visible int printk(const char *fmt, ...)
{
	va_list args;
	int r;

	va_start(args, fmt);
	r = vprintk_func(fmt, args);   调用==>
	va_end(args);

	return r;
}

// 3.riscv-linux-4.15/kernel/printk/printk_safe.c:
__printf(1, 0) int vprintk_func(const char *fmt, va_list args)
{
	...
	return vprintk_default(fmt, args);    调用==>
}

// 4.riscv-linux-4.15/kernel/printk/printk.c:
int vprintk_default(const char *fmt, va_list args)
{
	...
	r = vprintk_emit(0, LOGLEVEL_DEFAULT, NULL, 0, fmt, args);    调用==>

	return r;
}

// 5.riscv-linux-4.15/kernel/printk/printk.c:
asmlinkage int vprintk_emit(int facility, int level,
			    const char *dict, size_t dictlen,
			    const char *fmt, va_list args)
{
	...
	printed_len = log_output(facility, level, lflags, dict, dictlen, text, text_len);
	...
	console_unlock();    调用==>
	...
}

// 6.riscv-linux-4.15/kernel/printk/printk.c:
void console_unlock(void)
{
	...
	call_console_drivers(ext_text, ext_len, text, len);    调用==>
	...
}

// 7.riscv-linux-4.15/kernel/printk/printk.c:
static void call_console_drivers(const char *ext_text, size_t ext_len,
				 const char *text, size_t len)
{
	struct console *con;

	trace_console_rcuidle(text, len); 

	if (!console_drivers)
		return;

	for_each_console(con) {  // 遍历console_drivers中每个console
		if (exclusive_console && con != exclusive_console)
			continue;
		if (!(con->flags & CON_ENABLED))
			continue;
		if (!con->write)
			continue;
		if (!cpu_online(smp_processor_id()) &&
		    !(con->flags & CON_ANYTIME))
			continue;
		if (con->flags & CON_EXTENDED)
			con->write(con, ext_text, ext_len);
		else
			con->write(con, text, len); // 通过console的write函数打印内容
	}
}

在代码中,从printk开始,层层分析,最后在call_console_drivers函数中,会遍历console_drivers中每个console,并调用console的write函数来完成内容打印。

pr_info ==>
printk ==>
vprintk_func ==>
vprintk_default ==>
vprintk_emit ==>
console_unlock ==>
call_console_drivers ==>
con->write

3 console注册

console结构体定义:

// riscv-linux-4.15/include/linux/console.h:
struct console {
	char	name[16];
	void	(*write)(struct console *, const char *, unsigned);
	int	(*read)(struct console *, char *, unsigned);
	struct tty_driver *(*device)(struct console *, int *);
	void	(*unblank)(void);
	int	(*setup)(struct console *, char *);
	int	(*match)(struct console *, char *name, int idx, char *options);
	short	flags;
	short	index;
	int	cflag;
	void	*data;
	struct	 console *next;
};

通过register_console函数,可以将一个console进行注册,放入console_drivers链表中,如下:

// riscv-linux-4.15/arch/riscv/kernel/setup.c:
void __init setup_arch(char **cmdline_p)
{
#if defined(CONFIG_EARLY_PRINTK)
       if (likely(early_console == NULL)) {
               early_console = &riscv_sbi_early_console_dev;
               register_console(early_console); // 注册early console
       }
#endif
	*cmdline_p = boot_command_line;
	...
}

// early console定义
struct console riscv_sbi_early_console_dev __initdata = {
	.name	= "early",
	.write	= sbi_console_write,
	.flags	= CON_PRINTBUFFER | CON_BOOT | CON_ANYTIME,
	.index	= -1
};

// early console的write函数
static void sbi_console_write(struct console *co, const char *buf,
			      unsigned int n)
{
	int i;

	for (i = 0; i < n; ++i) {
		if (buf[i] == '\n')
			sbi_console_putchar('\r');
		sbi_console_putchar(buf[i]);
	}
}

// riscv-linux-4.15/arch/riscv/include/asm/sbi.h:
#define SBI_CALL(which, arg0, arg1, arg2) ({			\
	register uintptr_t a0 asm ("a0") = (uintptr_t)(arg0);	\
	register uintptr_t a1 asm ("a1") = (uintptr_t)(arg1);	\
	register uintptr_t a2 asm ("a2") = (uintptr_t)(arg2);	\
	register uintptr_t a7 asm ("a7") = (uintptr_t)(which);	\
	asm volatile ("ecall"					\
		      : "+r" (a0)				\
		      : "r" (a1), "r" (a2), "r" (a7)		\
		      : "memory");				\
	a0;							\
})

/* Lazy implementations until SBI is finalized */
#define SBI_CALL_0(which) SBI_CALL(which, 0, 0, 0)
#define SBI_CALL_1(which, arg0) SBI_CALL(which, arg0, 0, 0)
#define SBI_CALL_2(which, arg0, arg1) SBI_CALL(which, arg0, arg1, 0)

static inline void sbi_console_putchar(int ch)
{
	SBI_CALL_1(SBI_CONSOLE_PUTCHAR, ch);
}

static inline int sbi_console_getchar(void)
{
	return SBI_CALL_0(SBI_CONSOLE_GETCHAR);
}

4 SBI_CALL

console的write函数:先调用sbi_console_putchar,再调SBI_CALL。
SBI_CALL实现的功能,可大致理解为:

SBI_CALL(which, arg0, arg1, arg2) 
{			
	a0寄存器 = (uintptr_t)(arg0);	
	a1寄存器 = (uintptr_t)(arg1);	
	a2寄存器 = (uintptr_t)(arg2);	
	a7寄存器 = (uintptr_t)(which);	
	执行ecall指令;					
}

SBI_CALL宏,通过在RISC-V处理器上,执行ecall指令来调用一个服务:

  • 它通过将参数,放入特定的寄存器(a0、a1、a2);
  • 并将服务标识符(调用号),放入a7寄存器;
  • 然后,它执行ecall指令,并返回a0寄存器的值作为结果。

ecall系统调用,会触发异常(mcause寄存器定义的异常8或9)。只不过这种异常,是由U或S模式下,程序通过ecall指令,软件触发的异常,主要用于系统调用,实现一些底层调用,例如输出打印信息到串口等。

可以看到,这里定义了很多调用号Timer、Console、IPI、Shutdown等,如下:

// riscv-linux-4.15/arch/riscv/include/asm/sbi.h:
#define SBI_SET_TIMER 0
#define SBI_CONSOLE_PUTCHAR 1
#define SBI_CONSOLE_GETCHAR 2
#define SBI_CLEAR_IPI 3
#define SBI_SEND_IPI 4
#define SBI_REMOTE_FENCE_I 5
#define SBI_REMOTE_SFENCE_VMA 6
#define SBI_REMOTE_SFENCE_VMA_ASID 7
#define SBI_SHUTDOWN 8

在这里,就是:

  • 将调用号1放入a7寄存器,将欲打印字符ch放入a0寄存器,然后CPU执行ecall指令,就会触发一个异常;
  • 然后CPU会处理该异常,由于当前kernel运行在S模式,因此CPU会进入M模式,并跳转到M模式异常处理入口(Open SBI),在固件OpenSBI的处理代码中,会判断当调用号为1时,将字符ch打印出来(打印的方式,可以通过Uart或HTIF)。
    在这里插入图片描述
    在riscv-pk开源项目中,也支持通过ecall指令,来使用Uart或HTIF输出打印信息。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/953907.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HarmonyOS 鸿蒙 ArkTs(5.0.1 13)实现Scroll下拉到顶刷新/上拉触底加载,Scroll滚动到顶部

HarmonyOS 鸿蒙 ArkTs(5.0.1 13)实现Scroll下拉到顶刷新/上拉触底加载 效果展示 使用方法 import LoadingText from "../components/LoadingText" import PageToRefresh from "../components/PageToRefresh" import FooterBar from "../components/…

《自动驾驶与机器人中的SLAM技术》ch9:自动驾驶车辆的离线地图构建

目录 1 点云建图的流程 2 前端实现 2.1 前端流程 2.2 前端结果 3 后端位姿图优化与异常值剔除 3.1 两阶段优化流程 3.2 优化结果 ① 第一阶段优化结果 ② 第二阶段优化结果 4 回环检测 4.1 回环检测流程 ① 遍历第一阶段优化轨迹中的关键帧。 ② 并发计算候选回环对…

鸿蒙面试 2025-01-10

写了鉴权工具&#xff0c;你在项目中申请了那些权限&#xff1f;&#xff08;常用权限&#xff09; 位置权限 &#xff1a; ohos.permission.LOCATION_IN_BACKGROUND&#xff1a;允许应用在后台访问位置信息。 ohos.permission.LOCATION&#xff1a;允许应用访问精确的位置信息…

Windows图形界面(GUI)-QT-C/C++ - QT控件创建管理初始化

公开视频 -> 链接点击跳转公开课程博客首页 -> ​​​链接点击跳转博客主页 目录 控件创建 包含对应控件类型头文件 实例化控件类对象 控件设置 设置父控件 设置窗口标题 设置控件大小 设置控件坐标 设置文本颜色和背景颜色 控件排版 垂直布局 QVBoxLayout …

Unreal Engine 5 C++ Advanced Action RPG 七章笔记

第七章 Ranged Enemy 2-Ranged Enemy Starting Weapon 制作新敌人的流程准备 新敌人的武器起始的状态数据自己的战斗能力投射能力自己的行为树 创建角色,添加武器,添加数据,就是继承之前的基类敌人的 运行结果 3-Glacer Starting Stats 看看就行,就是复制曲线表格更改数…

funcaptcha手势指向验证码识别

注意&#xff0c;本文只提供学习的思路&#xff0c;严禁违反法律以及破坏信息系统等行为&#xff0c;本文只提供思路 如有侵犯&#xff0c;请联系作者下架 本文滑块识别已同步上线至OCR识别网站&#xff1a; http://yxlocr.nat300.top/ocr/other/21 该验证码会给出某物品所有的…

GAMES101学习笔记(三):Rasterization 光栅化(三角形的离散化、抗锯齿、深度测试)

文章目录 视口变换 Viewport三角形网格 Triangle Mesh采样 Sampling走样/反走样 Aliasing/Antialiasing采样频率、空间域与频率域深入理解采样、走样、反走样反走样总结深度测试 Depth testing 课程资源&#xff1a;GAMES101-现代计算机图形学入门-闫令琪 Lec5 ~ Lec6 学习笔记…

vscode 扩展Cline、Continue的差别?

Cline和Continue都是VSCode的AI编程插件&#xff0c;它们在功能、用户体验、性能、适用场景以及配置和使用步骤等方面存在一些差别&#xff1a; 一、功能差异 编辑功能 Cline&#xff1a;能够分析项目的文件结构和源代码抽象语法树&#xff08;AST&#xff09;&#xff0c;通…

鸿蒙打包发布

HarmonyOS应用/元服务发布&#xff08;打包发布&#xff09; https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V13/ide-publish-app-V13?catalogVersionV13 密钥&#xff1a;包含非对称加密中使用的公钥和私钥&#xff0c;存储在密钥库文件中&#xff0c;格式…

晨辉面试抽签和评分管理系统之九:随机编排考生的分组(以教师资格考试面试为例)

晨辉面试抽签和评分管理系统&#xff08;下载地址:www.chenhuisoft.cn&#xff09;是公务员招录面试、教师资格考试面试、企业招录面试等各类面试通用的考生编排、考生入场抽签、候考室倒计时管理、面试考官抽签、面试评分记录和成绩核算的面试全流程信息化管理软件。提供了考生…

sparkRDD教程之必会的题目

1.前期准备 &#xff08;1&#xff09;看看上一期的博客&#xff0c;最好跟着上一期的博客把sparkRDD的基本命令给熟练掌握后&#xff0c;再来做这篇文章的任务。 上一期的博客&#xff1a;sparkRDD教程之基本命令-CSDN博客 &#xff08;2&#xff09;新建文件task6.scala …

stack和queue专题

文章目录 stack最小栈题目解析代码 栈的压入弹出序列题目解析代码 queue二叉树的层序遍历题目解析代码 stack stack和queue都是空间适配器 最小栈 最小栈的题目链接 题目解析 minst是空就进栈&#xff0c;或者是val < minst.top()就进栈 代码 class MinStack { public:M…

欧拉路径算法

欧拉图&#xff1a; 对于应该连通图G&#xff0c;有&#xff1a; 1欧拉路径&#xff1a;一条路径&#xff0c;它能够不重复地遍历完所有的边&#xff0c;这个性质很像不重复地一笔画完所有边&#xff0c;所以有些涉及到欧拉路径的问题叫做一笔画问题。 2欧拉回路&#xff1a…

【C#设计模式(23)——模板方法模式(Template Method Pattern)】

前言 在抽象类中封装算法的结构&#xff0c;具体的实现步骤由子类定义&#xff0c;从而达到不改变算法结构的&#xff0c;允许子类重定义方法内容。代码 public abstract class Teamplate {public void TeamplateMethod(){Step1();Step2();Step3();}protected abstract void …

MyBatis——XML映射文件

在MyBatis中&#xff0c;既可以通过注解的方式配置SQL语句&#xff0c;也可以通过XML映射文件的方式配置SQL语句。对于简单的SQL语句建议直接通过注解的方式配置SQL语句&#xff1a; Delete("delete from user where id#{id}") Integer deleteById(Integer id);但是…

Mysql--运维篇--安全性(数据库访问控制,最小权限原则,表空间加密,TLS加密,证书签发,SQL注入及防范等)

一、数据库访问控制 MySQL的访问控制是确保数据库安全的关键机制之一。通过合理的用户权限管理和访问控制策略&#xff0c;可以防止未经授权的用户访问、修改或删除敏感数据。 1、MySQL访问控制的工作原理 MySQL使用基于用户的访问控制模型&#xff0c;每个用户都有特定的权…

抽奖滚动功能

代码 <template><div class"box"><video class"video" src"../../assets/video/底层.mp4" loop autoplay muted></video><img class"choujiang" src"../../assets/image/抽奖1.png" alt"&…

【Python】Python之locust压测教程+从0到1demo:基础轻量级压测实战(1)

文章目录 一、什么是Locust二、Locust 架构组成三、实战 Demo准备一个可调用的接口编写一个接口测试用例编写一个性能测试用例执行性能测试用例代码1、通过 Web UI 执行&#xff08;GUI模式&#xff09;2、通过命令行执行&#xff08;非GUI模式&#xff09; 小知识&#xff1a;…

Microsoft

Microsoft Word目录1.目录编号与文字的间距设置2. 目录编号缩进设置 Excel函数MID&#xff08;提取字符&#xff09;CONCAT&#xff08;组合字符串&#xff09;EXACT&#xff08;比较字符串&#xff09; PowerPointwindows 11 恢复右键传统菜单 Word 目录 1.目录编号与文字的…

用 Python 处理 CSV 和 Excel 文件

&#x1f496; 欢迎来到我的博客&#xff01; 非常高兴能在这里与您相遇。在这里&#xff0c;您不仅能获得有趣的技术分享&#xff0c;还能感受到轻松愉快的氛围。无论您是编程新手&#xff0c;还是资深开发者&#xff0c;都能在这里找到属于您的知识宝藏&#xff0c;学习和成长…