riscv Linux 目錄分布
通過文章 將 Linux 移植到新的處理器架構,第 1 部分:基礎 可知,我們進行一個新的處理器架構的移植,需要做到以下 3 點:
1、確定這是不是一個新的架構移植。
2、了解我們要移植的硬件。
3、了解內核的基本概念。
在riscv已經被移植支持的情況下,我們現在要做的是分析,Linux內核是如何支持riscv架構的。
- configs/:支持系統(tǒng)的默認配置 (i.e. *_defconfig files)
- include/asm/ :Linux源碼內部使用的頭文件
- include/uapi/asm: 對于要導出到用戶空間(例如 libc )的頭文件
- kernel/:通用內核管理
- lib/:優(yōu)化過的那套函數 (e.g. memcpy(), memset(), etc.)
- mm/:內存管理
-
configs文件中主要是一些配置文件,編譯時可以選擇默認配置進行編譯,配置項較多,我們暫時不進行分析。
-
include/asm/ 目錄下定義了大量頭文件,用于內核編譯時使用。
-
include/uapi/asm目錄下定義了很多結構體以及宏定義,可以供應用層使用,可以更方便的與內核統(tǒng)一使用一些定義好的數據。
-
kernel/目錄下有許多C文件,包含CPU獲取id,信號,中斷,ops,smp,time等功能。
-
lib/目錄下供9個文件,其中5個為匯編實現的代碼。用于底層基礎函數的實現。mm/目錄下進行內存的管理,包括虛擬內存分配,頁錯誤處理,cache刷新等。
架構相關的includ目錄存在于架構相關文件夾,非架構相關的存在與include/asm-gereric目錄下。
內核第一個運行的地方——head.S
kernel_entry*
start_kernel
setup_arch*
trap_init*
mm_init
mem_init*
init_IRQ*
time_init*
rest_init
kernel_thread
kernel_thread
cpu_startup_entry
內核的整體啟動流程如上所示,我們從代碼中進行分析,具體內核在啟動過程中做了什么。
首先我們找到head.S文件。
ENTRY(_start_kernel)
/* Mask all interrupts */
csrw CSR_IE, zero
csrw CSR_IP, zero
在內核啟動時,一開始就關閉了所有中斷。Technical Report UCB/EECS-2016-129 一文中講了,CSR 的寄存器分布。
關閉中斷后,關閉了 FPU 功能,以檢測內核空間內非法使用的定位點。后面是通過一系列的宏定義進行一些環(huán)境的配置,使得一些功能能夠跑起來。
這些宏定義有
ENTRY(_start_kernel)
關閉所有中斷
#ifdef CONFIG_RISCV_M_MODE
/* 刷新icache */
/* 復位所有寄存器,除了 ra, a0, a1 */
/*
設置一個PMP以允許訪問所有內存。有些機器可能不會實現pmp,因此我們設置了一個快速陷阱處理程序來跳過接觸任何陷阱上的pmp。
*/
/*
a0中的hardtid稍后才會出現,我們沒有固件可以處理它。
*/
#endif /* CONFIG_RISCV_M_MODE */
/* 加載全局指針 */
/*
*關閉FPU,檢測內核空間中非法使用浮點數的情況
*/
#ifdef CONFIG_RISCV_BOOT_SPINWAIT
/* 彩票系統(tǒng)只需要自旋等待啟動方法 */
#ifndef CONFIG_XIP_KERNEL
/* 選擇一個hart來運行主啟動序列 */
#else
/* Hart_lottery在flash中包含一個神奇的數字 */
/* 如果在RAM中沒有設置hart_lottery,這是第一次 */
#endif /* CONFIG_XIP */
#endif /* CONFIG_RISCV_BOOT_SPINWAIT */
#ifdef CONFIG_XIP_KERNEL
/*恢復a-的復制*/
#endif
#ifndef CONFIG_XIP_KERNEL
/*為展開的無ELF的鏡像清除BSS段 */
#endif
/* 保存hart ID和DTB物理地址*/
/* 初始化頁表并重新定位到虛擬地址 */
#ifdef CONFIG_BUILTIN_DTB
#else
#endif /* CONFIG_BUILTIN_DTB */
#ifdef CONFIG_MMU
#endif /* CONFIG_MMU *
/* Restore C environment */
#ifdef CONFIG_KASAN
#endif
/* 啟動內核 */
#if CONFIG_RISCV_BOOT_SPINWAIT
/* 設置陷阱向量永遠旋轉以幫助調試 */
/*
這個人沒有中彩票,所以我們等待中獎的人在啟動過程中走得足夠遠,它應該繼續(xù)。
*/
/* FIXME:我們應該WFI,以節(jié)省一些能源在這里。*/
#endif /* CONFIG_RISCV_BOOT_SPINWAIT */
END(_start_kernel)
內核運行的第一個C文件——init/main.c
第一個運行的C語言函數為start_kernel,在該函數中進行內核的第一個線程的創(chuàng)建。在創(chuàng)建之前,會執(zhí)行架構相關的函數,從而適配硬件。
kernel_entry*
start_kernel
setup_arch*
trap_init*
mm_init
mem_init*
init_IRQ*
time_init*
rest_init
kernel_thread
kernel_thread
cpu_startup_entry
setup_arch()
首先分析 setup_arch 這個函數,該函數屬于架構相關函數,對應的文件在 arch/riscv/kernel 文件下。
parse_dtb()
這個函數首先要執(zhí)行的是解析設備樹,這說明 riscv 像 arm 一樣,使用設備樹進行設備驅動的管理,我們查看 x86 架構下的 setup_arch 則無設備樹相關的配置。設備樹解析函數通過 drivers/of 目錄下的設備樹驅動進行解析,并取出設備樹中 model 名稱。
設備樹解析調用的函數時 parse_dtb,函數中調用了一個全局變量 dtb_early_va,這個變量是在 head.S 中進行的賦值,head.S 中調用該函數時,提前將變量放置于寄存器 a0 中,用于 C 函數的傳參。
設備樹地址傳參代碼:
#ifdef CONFIG_BUILTIN_DTB
la a0, __dtb_start
XIP_FIXUP_OFFSET a0
#else
mv a0, s1
#endif /* CONFIG_BUILTIN_DTB */
call setup_vm
setup_initial_init_mm()
設備樹解析完成后,進行了早期內存的初始化,給出了代碼段的起始與結束位置,數據段的結束位置,堆地址結束位置。
[0.000000] OF: fdt: Ignoring memory range 0x80000000 - 0x80200000
[0.000000] Machine model: riscv-virtio,qemu
[0.000000]start_code=0x80002000,end_code=0x806ae52c,end_data=0x812d2a00,brk=0x81322000
通過以上打印信息可知各個段的分配地址。CPU 內部的 RAM 尋址需要預留一些空間,所有 ram 起始地址就從 0x80000000 開始,地址空間分配完成之后將 boot_command_line 地址傳出,供后續(xù)使用。
early_ioremap_setup()
早期 ioremap 初始化,將 I/O 的物理地址映射到虛擬地址。當 CPU 讀取一段物理地址時,它可以讀取到映射了 I/O 設備的物理 RAM 區(qū)域。ioremap 就是用來把設備內存映射到內核地址空間的。
該函數是一個架構不相關的函數,位于 mm/early_ioremap.c,
jump_label_init()
架構無關函數,位于 kernel 目錄下,初始化 jump-label 子系統(tǒng),jump-label 用于取消 if 判斷分支,通過運行時修改代碼,來提高執(zhí)行的效率。
parse_early_param()
架構無關函數,解析早期傳入的參數。
efi_init()
paging_init()
完成系統(tǒng)分頁機制的初始化工作, 建立頁表, 從而內核可以完成虛擬內存的映射和轉換工作,這一個函數執(zhí)行完成之后,就可以通過虛擬地址來訪問實際的物理地址了。
misc_mem_init()
- 測試 ram 是否正常
- numa 架構初始化
- 內存模型 sparse 初始化
- 初始化 zone,用于管理物理內存地址區(qū)域
- 保留內核崩潰時內核信息導出時所用的內存區(qū)域。
- 打印內存分配情況 __memblock_dump_all(),實際未輸出
init_resources()
初始化內存資源,把系統(tǒng)的 ram 以及其他需要保留的 ram 進行保留
sbi_init()
函數相關打印如下,具體作用暫未分析
[ 0.000000] SBI specification v0.2 detected
[ 0.000000] SBI implementation ID=0x1 Version=0x9
[ 0.000000] SBI TIME extension detected
[ 0.000000] SBI IPI extension detected
[ 0.000000] SBI RFENCE extension detected
[ 0.000000] SBI HSM extension detected
kasan_init()
初始化 kasan 動態(tài)監(jiān)測內存錯誤的工具,初始化完成之后,可以在內存使用越界或者釋放后訪問時,產生出錯報告,幫助分析內核異常。
setup_smp()
配置 SMP 系統(tǒng),使芯片可以多核運行。
riscv_fill_hwcap()
打印信息如下:
[ 0.000000] riscv: ISA extensions acdfimsu
[ 0.000000] riscv: ELF capabilities acdfim
具體作用暫未分析。
trap_init()
未定義該函數
mem_init()
mem_init() 是架構相關函數,我們分析一下該函數具體做了哪些工作。
void __init mem_init(void)
{
#ifdef CONFIG_FLATMEM
BUG_ON(!mem_map);
#endif /* CONFIG_FLATMEM */
#ifdef CONFIG_SWIOTLB
if (swiotlb_force == SWIOTLB_FORCE ||
max_pfn > PFN_DOWN(dma32_phys_limit))
swiotlb_init(1);//軟件DMA映射,解決部分DMA外設無法訪問高地址內存的問題。
else
swiotlb_force = SWIOTLB_NO_FORCE;
#endif
memblock_free_all();//釋放空閑頁面給伙伴分配器
print_vm_layout();//打印內存分布情況
}
init_IRQ()
中斷初始化是一個架構相關的函數,首先從設備樹中取出中斷控制器(()interrupt-controller)這一節(jié)點。
通過命令將 qemu 的 DTB 文件導出.
sudo env PATH=/labs/linux-lab/boards/riscv64/virt/bsp/qemu/v6.0.0/bin/:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin qemu-system-riscv64 -bios /labs/linux-lab/boards/riscv64/virt/bsp/bios/opensbi/generic/fw_jump.elf -M virt,dumpdtb=my.dtb -m 128M -net nic,model=virtio -net tap -device virtio-net-device,netdev=net0,mac=$(tools/qemu/macaddr.sh) -netdev tap,id=net0 -smp 4 -kernel /labs/linux-lab/build/riscv64/virt/linux/v5.17/arch/riscv/boot/Image -no-reboot -drive if=none,file=/labs/linux-lab/build/riscv64/virt/bsp/root/2019.05/rootfs.ext2,format=raw,id=virtio-vda -device virtio-blk-device,drive=virtio-vda -nographic -append "route=$(ifconfig br0 | grep 'inet ' | tr -d -c '^[0-9. ]' | awk '{print $1}') iface=eth0 rw fsck.repair=yes rootwait root=/dev/vda console=ttyS0"
并將 dtb 文件反編譯成 dts 文件。
dtc -I dtb -O dts -o qemu-virt.dts my.dtb
init_IRQ()->irqchip_init()->of_irq_init()->
在 of_irq_init() 中遍歷設備樹,通過 __irq_of_table 進行匹配,匹配成功后進行初始化 irq。
查看設備樹,找到 interrupt-controller 的 compatible 為 riscv,cpu-intc
,
cpu@0 {
phandle = <0x07>;
device_type = "cpu";
reg = <0x00>;
status = "okay";
compatible = "riscv";
riscv,isa = "rv64imafdcsu";
mmu-type = "riscv,sv48";
interrupt-controller {
#interrupt-cells = <0x01>;
interrupt-controller;
compatible = "riscv,cpu-intc";
phandle = <0x08>;
};
};
通過匹配,最終調用的驅動是driver/irqchip/irq-riscv-intc.c
static int __init riscv_intc_init(struct device_node *node,
struct device_node *parent)
{
int rc, hartid;
pr_info("[nfk test] %s-%s-%d\r\n",__FILE__,__FUNCTION__,__LINE__);
hartid = riscv_of_parent_hartid(node);//獲取CPU id
if (hartid < 0) {
pr_warn("unable to find hart id for %pOF\n", node);
return 0;
}
else
{
pr_info("[nfk test] get hartid=%d\r\n",hartid);
}
/*
* The DT will have one INTC DT node under each CPU (or HART)
* DT node so riscv_intc_init() function will be called once
* for each INTC DT node. We only need to do INTC initialization
* for the INTC DT node belonging to boot CPU (or boot HART).
*/
if (riscv_hartid_to_cpuid(hartid) != smp_processor_id())
return 0;
//每一個CPU都會有其DT NODE,當前我們只需要初始化
//boot CPU 的DT NODE
intc_domain = irq_domain_add_linear(node, BITS_PER_LONG,
&riscv_intc_domain_ops, NULL);//向系統(tǒng)注冊一個irq domain,
//最終調用__irq_domain_add(),進行內存申請,domain回調函數配置,此處僅完成了irq_domain的注冊,后面的中斷映射關系還需要在具體驅動中實現。
if (!intc_domain) {//intc_domain就是interrupt-controller的軟件抽象
pr_err("unable to add IRQ domain\n");
return -ENXIO;
}
rc = set_handle_irq(&riscv_intc_irq);//配置中斷處理函數
if (rc) {
pr_err("failed to set irq handler\n");
return rc;
}
cpuhp_setup_state(CPUHP_AP_IRQ_RISCV_STARTING,
"irqchip/riscv/intc:starting",
riscv_intc_cpu_starting,
riscv_intc_cpu_dying);//對熱插拔函數進行配置
pr_info("%d local interrupts mapped\n", BITS_PER_LONG);
return 0;
}
[ 0.000000] riscv-intc: [nfk test] drivers/irqchip/irq-riscv-intc.c-riscv_intc_init-99
[ 0.000000] riscv-intc: get hartid=0
[ 0.000000] riscv-intc: hartid 0,cpuid 1 not smp processor_id
[ 0.000000] riscv-intc: [nfk test] drivers/irqchip/irq-riscv-intc.c-riscv_intc_init-99
[ 0.000000] riscv-intc: get hartid=1
[ 0.000000] riscv-intc: hartid 1,cpuid 2 not smp processor_id
[ 0.000000] riscv-intc: [nfk test] drivers/irqchip/irq-riscv-intc.c-riscv_intc_init-99
[ 0.000000] riscv-intc: get hartid=2
[ 0.000000] riscv-intc: hartid 2,cpuid 3 not smp processor_id
[ 0.000000] riscv-intc: [nfk test] drivers/irqchip/irq-riscv-intc.c-riscv_intc_init-99
[ 0.000000] riscv-intc: get hartid=3
[ 0.000000] riscv-intc: 64 local interrupts mapped
中斷初始化的打印如上所示。
time_init()
架構相關函數time_init(),
void __init time_init(void)
{
struct device_node *cpu;
u32 prop;
/*設備樹中解析CPU,并且讀取他的timebase-frequency*/
cpu = of_find_node_by_path("/cpus");
if (!cpu || of_property_read_u32(cpu, "timebase-frequency", &prop))
panic(KERN_WARNING "RISC-V system with no 'timebase-frequency' in DTS\n");
of_node_put(cpu);//減少引用計數
riscv_timebase = prop;
lpj_fine = riscv_timebase / HZ;
//遍歷設備樹,進行時鐘初始化,類似于of_irq_init(),linux-lab-disk中的虛擬開發(fā)板當前匹配為空
of_clk_init(NULL);
timer_probe();
}
timer_probe()中遍歷設備樹,通過__timer_of_table進行匹配,匹配成功后進行初始化timer。
void __init timer_probe(void)
{
struct device_node *np;
const struct of_device_id *match;
of_init_fn_1_ret init_func_ret;
unsigned timers = 0;
int ret;
pr_info("[nfk test] %s-%s-%d\n",__FILE__,__FUNCTION__,__LINE__);
for_each_matching_node_and_match(np, __timer_of_table, &match) {//遍歷設備樹,匹配timer
if (!of_device_is_available(np))
continue;
pr_info("[nfk test] %s-%s-%d\n",__FILE__,__FUNCTION__,__LINE__);
init_func_ret = match->data;
ret = init_func_ret(np);//timer初始化
if (ret) {
if (ret != -EPROBE_DEFER)
pr_err("Failed to initialize '%pOF': %d\n", np,
ret);
continue;
}
timers++;
}
timers += acpi_probe_device_table(timer);//注冊timer
if (!timers)
pr_crit("%s: no matching timers found\n", __func__);
pr_info("[nfk test] %s-%s-%d\n",__FILE__,__FUNCTION__,__LINE__);
}
添加調試信息,打印如下:
[ 0.000000] [nfk test] drivers/clocksource/timer-probe.c-timer_probe-23
[ 0.000000] [nfk test] drivers/clocksource/timer-probe.c-timer_probe-28
[ 0.000000] [nfk test] drivers/clocksource/timer-probe.c-timer_probe-28
[ 0.000000] [nfk test] drivers/clocksource/timer-probe.c-timer_probe-28
[ 0.000000] [nfk test] drivers/clocksource/timer-probe.c-timer_probe-28
[ 0.000000] riscv_timer_init_dt: Registering clocksource cpuid [0] hartid [3]
[ 0.000000] clocksource: riscv_clocksource: mask: 0xffffffffffffffff max_cycles: 0x24e6a1710, max_idle_ns: 440795202120 ns
[ 0.000126] sched_clock: 64 bits at 10MHz, resolution 100ns, wraps every 4398046511100ns
[ 0.002668] [nfk test] drivers/clocksource/timer-probe.c-timer_probe-46
通過以上信息,可知,匹配到了4次timer,通過中間的相關打印信息,找到驅動drivers/clocksource/timer-riscv.c。
static int __init riscv_timer_init_dt(struct device_node *n)
{
int cpuid, hartid, error;
struct device_node *child;
struct irq_domain *domain;
hartid = riscv_of_processor_hartid(n);//獲取node所在的hartid
if (hartid < 0) {
pr_warn("Not valid hartid for node [%pOF] error = [%d]\n",
n, hartid);
return hartid;
}
cpuid = riscv_hartid_to_cpuid(hartid);//獲取cpu id
if (cpuid < 0) {
pr_warn("Invalid cpuid for hartid [%d]\n", hartid);
return cpuid;
}
if (cpuid != smp_processor_id())
return 0;//判斷是否未boot cpu
domain = NULL;
child = of_get_compatible_child(n, "riscv,cpu-intc");
if (!child) {//獲取中斷的domain
pr_err("Failed to find INTC node [%pOF]\n", n);
return -ENODEV;
}
domain = irq_find_host(child);
of_node_put(child);
if (!domain) {
pr_err("Failed to find IRQ domain for node [%pOF]\n", n);
return -ENODEV;
}
riscv_clock_event_irq = irq_create_mapping(domain, RV_IRQ_TIMER);//建立中斷映射
if (!riscv_clock_event_irq) {
pr_err("Failed to map timer interrupt for node [%pOF]\n", n);
return -ENODEV;
}
pr_info("%s: Registering clocksource cpuid [%d] hartid [%d]\n",
__func__, cpuid, hartid);
error = clocksource_register_hz(&riscv_clocksource, riscv_timebase);//注冊timer
if (error) {
pr_err("RISCV timer register failed [%d] for cpu = [%d]\n",
error, cpuid);
return error;
}
sched_clock_register(riscv_sched_clock, 64, riscv_timebase);
error = request_percpu_irq(riscv_clock_event_irq,
riscv_timer_interrupt,
"riscv-timer", &riscv_clock_event);
//注冊中斷處理函數
if (error) {
pr_err("registering percpu irq failed [%d]\n", error);
return error;
}
error = cpuhp_setup_state(CPUHP_AP_RISCV_TIMER_STARTING,
"clockevents/riscv/timer:starting",
riscv_timer_starting_cpu, riscv_timer_dying_cpu);//熱插拔配置
if (error)
pr_err("cpu hp setup state failed for RISCV timer [%d]\n",
error);
return error;
}
關于設備樹匹配函數分析
以下函數是進行循環(huán)匹配的函數。
for_each_matching_node_and_match(np, __timer_of_table, &match)
for_each_matching_node_and_match(np, __irqchip_of_table, &match)
我們找到他的根本調用,參數描述如下,分別是設備樹節(jié)點,要掃描的結構體,匹配到的結構體。
/**
* of_find_matching_node_and_match - Find a node based on an of_device_id
* match table.
* @from: The node to start searching from or NULL, the node
* you pass will not be searched, only the next one
* will; typically, you pass what the previous call
* returned. of_node_put() will be called on it
* @matches: array of of device match structures to search in
* @match: Updated to point at the matches entry which matched
*
* Return: A node pointer with refcount incremented, use
* of_node_put() on it when done.
*/
搞清楚入參之后,我們找一下__timer_of_table從何處定義。
#define TIMER_OF_DECLARE(name, compat, fn) \
OF_DECLARE_1_RET(timer, name, compat, fn)
下一層宏定義
#define OF_DECLARE_1_RET(table, name, compat, fn) \
_OF_DECLARE(table, name, compat, fn, of_init_fn_1_ret)
下一層宏定義
#define _OF_DECLARE(table, name, compat, fn, fn_type) \
static const struct of_device_id __of_table_##name \
__used __section("__" #table "_of_table") \
__aligned(__alignof__(struct of_device_id)) \
= { .compatible = compat, \
.data = (fn == (fn_type)NULL) ? fn : fn }
所以我們根據宏定義TIMER_OF_DECLARE尋找與設備樹節(jié)點可以匹配的驅動,我們首先看一下傳入的node的信息。
truct device_node {
const char *name;
phandle phandle;
const char *full_name;
struct fwnode_handle fwnode;
struct property *properties;
struct property *deadprops; /* removed properties */
struct device_node *parent;
struct device_node *child;
struct device_node *sibling;
#if defined(CONFIG_OF_KOBJ)
struct kobject kobj;
#endif
unsigned long _flags;
void *data;
#if defined(CONFIG_SPARC)
unsigned int unique_id;
struct of_irq_controller *irq_trans;
#endif
};
最終調用匹配的函數如下所示:文章來源:http://www.zghlxwxcb.cn/news/detail-423519.html
static int __of_device_is_compatible(const struct device_node *device,
const char *compat, const char *type, const char *name)
{
struct property *prop;
const char *cp;
int index = 0, score = 0;
/* Compatible match has highest priority */
if (compat && compat[0]) {
prop = __of_find_property(device, "compatible", NULL);
for (cp = of_prop_next_string(prop, NULL); cp;
cp = of_prop_next_string(prop, cp), index++) {
if (of_compat_cmp(cp, compat, strlen(compat)) == 0) {
score = INT_MAX/2 - (index << 2);
break;
}
}
if (!score)
return 0;
}
/* Matching type is better than matching name */
if (type && type[0]) {
if (!__of_node_is_type(device, type))
return 0;
score += 2;
}
/* Matching name is a bit better than not */
if (name && name[0]) {
if (!of_node_name_eq(device, name))
return 0;
score++;
}
return score;
}
可以看到,能夠搜到的
drivers/clocksource/timer-riscv.c
TIMER_OF_DECLARE(riscv_timer, “riscv”, riscv_timer_init_dt);文章來源地址http://www.zghlxwxcb.cn/news/detail-423519.html
到了這里,關于riscv 啟動流程分析的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!