全志H3平台CLOCK简析
Posted dlijun
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了全志H3平台CLOCK简析相关的知识,希望对你有一定的参考价值。
1 概要
时钟管理模块是linux系统为统一管理各硬件的时钟而实现管理框架,负责所有模块的时钟调节和电源管理。
1.1 模块功能介绍
时钟管理模块主要负责处理各硬件模块的工作频率调节及电源切换管理。一个硬件模块要正常工作,必须先配置好硬件的工作频率、打开电源开关、总线访问开关等操作,时钟管理模块为设备驱动提供统一的操作接口,使驱动不用关心时钟硬件实现的具体细节。
1.2 相关术语介绍
晶振:晶体振荡器的简称,晶振有固定的振荡频率,如32K/24Mhz等,是芯片所有时钟的源头。
PLL: 锁相环,利用输入信号和反馈信号的差异提升频率输出。
时钟:驱动数字电路运转时的时钟信号。芯片内部的各硬件模块都需要时序控制,因此理解时钟信号对于底层编程非常重要。
1.3 系统时钟频率设置
lichee/tools/pack/chips/sun8iw7p1/configs/dolphin-p1/sys_config.fex 频率配置:
pll_video = 297
pll_ve = 402
pll_periph0 = 600
pll_gpu = 576
pll_periph1 = 600
pll_de = 864
对于没有配置的,系统设置频率为默认值。
系统时钟定义:
系统时钟主要是指一些源时钟,为其他硬件模块提供时钟源输入。系统时钟一般为多个硬件模块共享,不允许随意调节。
模块时钟定义
模块时钟主要是针对一些具体模块(如:gpu、de),在时钟频率配置、电源控制、访问控制等方面进行管理。一个典型的模块如下图所示,包含module gating、ahb gating、dram gating,以及reset控制。要想一个模块能够正常工作,必须在这几个方面作好相关的配置。
AHB,是Advanced High performance Bus的缩写,译作高级高性能总线,这是一种“系统总线”。AHB主要用于高性能模块(如CPU、DMA和DSP等)之间的连接。AHB 系统由主模块、从模块和基础结构(Infrastructure)3部分组成,整个AHB总线上的传输都由主模块发出,由从模块负责回应。
APB,是Advanced Peripheral Bus的缩写,这是一种外围总线。APB主要用于低带宽的周边外设之间的连接,例如UART、1284等,它的总线架构不像 AHB支持多个主模块,在APB里面唯一的主模块就是APB 桥。
这两者都是总线,符合AMBA规范。
2 时钟配置及注册相关代码
注:主要以dma时钟为例
lichee/linux-3.4/drivers/clk
linux-3.4/arch/arm/mach-sunxi/ sun8i.c
MACHINE_START(SUNXI, "sun8i")
.atag_offset = 0x100,
.init_machine = sunxi_dev_init,
.init_early = sunxi_init_early,
.map_io = sunxi_map_io,
#ifndef CONFIG_OF
.init_irq = sun8i_gic_init,
#endif
.handle_irq = gic_handle_irq,
.restart = sun8i_restart,
.timer = &sunxi_timer,
.dt_compat = NULL,
.reserve = sun8i_reserve,
.fixup = sun8i_fixup,
.nr_irqs = NR_IRQS,
#ifdef CONFIG_SMP
.smp = smp_ops(sunxi_smp_ops),
#if defined(CONFIG_ARCH_SUN8IW6) || defined(CONFIG_ARCH_SUN8IW9)
.smp_init = smp_init_ops(sun8i_smp_init_ops),
#endif
#endif
MACHINE_END
MACHINE_START主要是定义了"struct machine_desc"的类型,放在 section(".arch.info.init"),是初始化数据,Kernel 起来之后将被丢弃。
其余各个成员函数在setup_arch()中被赋值到内核结构体,在不同时期被调用:
1. .init_machine 在 arch/arm/kernel/setup.c 中被 customize_machine 调用,放在 arch_initcall() 段里面,会自动按顺序被调用。
2. .init_irq在start_kernel() --> init_IRQ() --> init_arch_irq()中被调用
3. .map_io 在 setup_arch() --> paging_init() --> devicemaps_init()中被调用
4. .timer是定义系统时钟。在start_kernel() --> time_init()中被调用。
5. .boot_params是bootloader向内核传递的参数的位置,这要和bootloader中参数的定义要一致。其他主要都在 setup_arch() 中用到。
static void __init sun8i_timer_init(void)
{
sunxi_init_clocks();
#if (defined(CONFIG_ARCH_SUN8IW6P1) || defined(CONFIG_ARCH_SUN8IW9P1))
if(!(readl(IO_ADDRESS(SUNXI_TIMESTAMP_CTRL_PBASE)) & 0x01))
writel(readl(IO_ADDRESS(SUNXI_TIMESTAMP_CTRL_PBASE)) |
0x01,IO_ADDRESS(SUNXI_TIMESTAMP_CTRL_PBASE));
#endif
#ifdef CONFIG_SUNXI_TIMER
sunxi_timer_init();
#endif
#ifdef CONFIG_ARM_ARCH_TIMER
arch_timer_register(&sun8i_arch_timer);
arch_timer_sched_clock_init();
#endif
}
struct sys_timer sunxi_timer __initdata = {
.init = sun8i_timer_init,
};
1.sunxi_init_clocks,本平台具体实现定义在sunxi/clk-sun8iw7.c中,其中要实现有:
sunxi_clk_factor_initlimits();//设置倍频系数最大值
注册晶振losc与hosc,e.g clk = clk_register_fixed_rate(NULL, "losc", NULL, CLK_IS_ROOT, 32768); //初始化clk_fixed_rate结构体,调用clk_register(dev, &fixed->hw)注册clock,其实质是初始clk结构体,相应成员变量的初始化及检测,最后加入相应的clk链表。
clk_register_clkdev(clk, "losc", NULL);
binder r-pio CPUS_APB0_GATE to pio-clk's gate-reset-register
2. sunxi_timer_init
drivers/clocksource/ sunxi_timer.c
配置sunxi timer相应寄存器后,调用clockevents_register_device(&sunxi_clockevent);
list_add(&dev->list, &clockevent_devices);
clockevents_do_notify(CLOCK_EVT_NOTIFY_ADD, dev);//通知链clockevents_chain
3. arch_timer_register(&sun8i_arch_timer);
struct arch_timer sun8i_arch_timer __initdata = {
.res[0] = {
.start = 29,
.end = 29,
.flags = IORESOURCE_IRQ,
},
.res[1] = {
.start = 30,
.end = 30,
.flags = IORESOURCE_IRQ,
},
};
arch_timer_common_register()
4. arch_timer_sched_clock_init();
3 函数接口
3.1 clk_get
drivers/clk/clkdev.c
struct clk *clk_get(struct device *dev, const char *con_id)
{
const char *dev_id = dev ? dev_name(dev) : NULL;
return clk_get_sys(dev_id, con_id);//根据dev_id及con_id在clocks链表中找到对应的clk
}
dma clk注册流程如下:
drivers/clk/sunxi/ clk-sun8iw7.c
SUNXI_CLK_PERIPH (dma,0,0,0,0,0,0,0,0,0,0,BUS_RST0,BUS_GATE0,0,0,6, 6,0,&clk_lock,NULL, 0)
struct periph_init_data sunxi_periphs_init[] = {
{"dma",0,ahb1mod_parents,ARRAY_SIZE(ahb1mod_parents),&sunxi_clk_periph_dma}
}
drivers/clk/sunxi/clk-periph.h 定义SUNXI_CLK_PERIPH宏
sunxi_init_clocks -> sunxi_clk_register_periph 及 clk_register_clkdev(clk, periph->name, NULL);
3.2 clk_prepare_enable
static inline int clk_prepare_enable(struct clk *clk)// include/linux/ clk.h
{
int ret;
ret = clk_prepare(clk);//通过调用__clk_prepare来实现,从代码来看,仅仅是将本节点及所有的父节点的prepare_count累加1
if (ret)
return ret;
ret = clk_enable(clk);//通过__clk_enable(clk)来实现
if (ret)
clk_unprepare(clk);
return ret;
}
int __clk_prepare(struct clk *clk)// drivers/clk/ clk.c
{
int ret = 0;
if (!clk)
return 0;
if (clk->prepare_count == 0) {
ret = __clk_prepare(clk->parent);
if (ret)
return ret;
if (clk->ops->prepare) {//为空,clk->ops对应于sunxi_clk_periph_ops
ret = clk->ops->prepare(clk->hw);
if (ret) {
__clk_unprepare(clk->parent);
return ret;
}
}
}
clk->prepare_count++;
return 0;
}
static int __clk_enable(struct clk *clk)
{
int ret = 0;
if (!clk)
return 0;
if (WARN_ON(clk->prepare_count == 0))
return -ESHUTDOWN;
if (clk->enable_count == 0) {
ret = __clk_enable(clk->parent);
if (ret)
return ret;
if (clk->ops->enable) {
ret = clk->ops->enable(clk->hw);//等价执行sunxi_clk_periph_ops.enable,即函数sunxi_clk_periph_enable,该函数通过__sunxi_clk_periph_enable(clk->hw)实现,其实质是通过写入寄存器相应的值来使能。
if (ret) {
__clk_disable(clk->parent);
return ret;
}
}
}
clk->enable_count++;
return 0;
}
drivers/clk/sunxi/ clk-periph.c
static int __sunxi_clk_periph_enable(struct clk_hw *hw)
{
unsigned long reg;
struct sunxi_clk_periph *periph = to_clk_periph(hw);
struct sunxi_clk_periph_gate *gate = &periph->gate;
/* de-assert module */
if(gate->reset && !(periph->flags & CLK_IGNORE_AUTORESET) && !IS_SHARE_RST_GATE(periph)) {
reg = periph_readl(periph,gate->reset);
reg = SET_BITS(gate->rst_shift, 1, reg, 1);
periph_writel(periph,reg, gate->reset);
}
/* enable bus gating */
if(gate->bus && !IS_SHARE_BUS_GATE(periph)) {
reg = periph_readl(periph,gate->bus);
reg = SET_BITS(gate->bus_shift, 1, reg, 1);
periph_writel(periph,reg, gate->bus);
}
/* enable module gating */
if(gate->enable&& !IS_SHARE_MOD_GATE(periph)) {
reg = periph_readl(periph,gate->enable);
if(periph->flags & CLK_REVERT_ENABLE)
reg = SET_BITS(gate->enb_shift, 1, reg, 0);
else
reg = SET_BITS(gate->enb_shift, 1, reg, 1);
periph_writel(periph,reg, gate->enable);
}
/* enable dram gating */
if(gate->dram&& !IS_SHARE_MBUS_GATE(periph)) {
reg = periph_readl(periph,gate->dram);
reg = SET_BITS(gate->ddr_shift, 1, reg, 1);
periph_writel(periph,reg, gate->dram);
}
return 0;
}
.gate = { \\
.flags = 0, \\
.enable = 0, \\
.reset = BUS_RST0, \\
.bus = BUS_GATE0, \\
.dram = 0, \\
.enb_shift = 0, \\
.rst_shift = 6, \\
.bus_shift = 6, \\
.ddr_shift = 0, \\
},
3.3 clk_unprepare
通过__clk_unprepare(clk)实现,即减少clk->prepare_count计数
3.4 clk_put
调用__clk_put(clk)释放clk
3.5 clk_set_rate
drivers/clk/clk.c
int clk_set_rate(struct clk *clk, unsigned long rate)
{
struct clk *top, *fail_clk;
int ret = 0;
/* prevent racing with updates to the clock topology */
mutex_lock(&prepare_lock);
/* bail early if nothing to do */
if (rate == clk->rate && !(clk->flags & CLK_GET_RATE_NOCACHE))
goto out;
if ((clk->flags & CLK_SET_RATE_GATE) && clk->prepare_count) {//判断是否设置有CLK_SET_RATE_GATE标志,并且clk->prepare_count > 0
ret = -EBUSY;
goto out;
}
/* calculate new rates and get the topmost changed clock */
top = clk_calc_new_rates(clk, rate);//遍历父节点,根据父节点计算出最佳的rate,并返回根节点
if (!top) {
ret = -EINVAL;
goto out;
}
/* notify that we are about to change rates */
fail_clk = clk_propagate_rate_change(top, PRE_RATE_CHANGE);//因为系统没有调用clk_notifier_register函数,即notifier_count为0,所以该函数并无实质作用
if (fail_clk) {
pr_warn("%s: failed to set %s rate\\n", __func__,
fail_clk->name);
clk_propagate_rate_change(top, ABORT_RATE_CHANGE);
ret = -EBUSY;
goto out;
}
/* change the rates */
clk_change_rate(top);
mutex_unlock(&prepare_lock);
return 0;
out:
mutex_unlock(&prepare_lock);
return ret;
}
static void clk_change_rate(struct clk *clk)
{
struct clk *child;
unsigned long old_rate;
unsigned long best_parent_rate = 0;
struct hlist_node *tmp;
old_rate = clk->rate;
if (clk->parent)
best_parent_rate = clk->parent->rate;
if (clk->ops->set_rate)
clk->ops->set_rate(clk->hw, clk->new_rate, best_parent_rate);//即执行sunxi_clk_periph_ops.set_rate,即函数
sunxi_clk_periph_set_rate,向对应寄存器中写入rate值。
if (clk->ops->recalc_rate)
clk->rate = clk->ops->recalc_rate(clk->hw, best_parent_rate);// 即执行sunxi_clk_periph_ops. recalc _rate,即函数sunxi_clk_periph_recalc_rate,调整后的值
else
clk->rate = best_parent_rate;
if (clk->notifier_count && old_rate != clk->rate)
__clk_notify(clk, POST_RATE_CHANGE, old_rate, clk->rate);
hlist_for_each_entry(child, tmp, &clk->children, child_node)
clk_change_rate(child);
}
4 CPU切频
4.1 管理策略
Linux内部共有五种对频率的管理策略:
1.performance :CPU会固定工作在其支持的最高运行频率上;
2.powersave :CPU会固定工作在其支持的最低运行频率上。因此这两种 governors都属于静态 governor ,即在使用它们时 CPU的运行频率不会根据系统运行时负载的变化动态作出调整。这两种 governors对应的是两种极端的应用场景,使用 performance governor体现的是对系统高性能的最大追求,而使用 powersave governor则是对系统低功耗的最大追求。
3.Userspace:最早的 cpufreq子系统通过 userspace governor 为用户提供了这种灵活性。系统将变频策略的决策权交给了用户态应用程序,并提供了相应的接口供用户态应用程序调节 CPU运行频率使用。(可以使用Dominik 等人开发了 cpufrequtils工具包)
4.ondemand :userspace是内核态的检测,效率低。而ondemand正是人们长期以来希望看到的一个完全在内核态下工作并且能够以更加细粒度的时间间隔对系统负载情况进行采样分析的governor。
5.conservative : ondemand governor的最初实现是在可选的频率范围内调低至下一个可用频率。这种降频策略的主导思想是尽量减小对系统性能的负面影响,从而不会使得系统性能在短时间内迅速降低以影响用户体验。但是在 ondemand governor的这种最初实现版本在社区发布后,大量用户的使用结果表明这种担心实际上是多余的, ondemand governor在降频时对于目标频率的选择完全可以更加激进。因此最新的 ondemand governor在降频时会在所有可选频率中一次性选择出可以保证 CPU 工作在 80% 以上负荷的频率,当然如果没有任何一个可选频率满足要求的话则会选择 CPU支持的最低运行频率。大量用户的测试结果表明这种新的算法可以在不影响系统性能的前提下做到更高效的节能。在算法改进后, ondemand governor的名字并没有改变,而 ondemand governor 最初的实现也保存了下来,并且由于其算法的保守性而得名 conservative。
Ondemand降频更加激进,conservative降频比较缓慢保守,事实使用ondemand的效果也是比较好的。
4.2 设置方法
调整管理策略,往scaling_governor文件中写入策略,
e.g echo "userspace" > /sys/devices/system/cpu/cpu2/cpufreq/scaling_governor
设置成想要的工作频率(khz):echo 640000 > scaling_setspeed
设置默认的模式:
linux-3.4/arch/arm/configs/sun8iw7p1smp_android_defconfig
CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE=y
/sys/devices/system/cpu 看到代表各 CPU 的文件夹按照 cpuX 的命名方式,如 cpu0、cpu1、cpu2 等。这些文件夹里面有一个 online 文件,如果其值为0则禁用该 CPU,如果为1则启用该 CPU。
4.3 具体代码
drivers\\cpufreq\\
cpufreq.c
cpufreq_conservative.c // 按次序切频
cpufreq_ondemand.c // 按命令切频
cpufreq_performance.c // 最高频率
cpufreq_powersave.c // 最低频率
cpufreq_stats.c
cpufreq_userspace.c
freq_table.c
drivers/cpufreq/ sunxi-cpufreq.c
static int __init sunxi_cpufreq_initcall(void)
{
……
/* init cpu frequency from sysconfig */
if(__init_freq_syscfg(vftbl_name)) {//从sys_config.fex获取cpu频率范围(480MHz~1.2GHz之间)
CPUFREQ_ERR("%s, use default cpu max/min frequency, max freq: %uMHz, min freq: %uMHz\\n",
__func__, cpu_freq_max/1000, cpu_freq_min/1000);
}else{
pr_debug("%s, get cpu frequency from sysconfig, max freq: %uMHz, min freq: %uMHz\\n",
__func__, cpu_freq_max/1000, cpu_freq_min/1000);
}
ret = __init_vftable_syscfg(vftbl_name, flag);// 从sys_config.fex获取LV1_freq、LV1_volt
if (ret) {
CPUFREQ_ERR("%s get V-F table failed\\n", __func__);
return ret;
} else {
__vftable_show();
}
/* register cpu frequency driver */
ret = cpufreq_register_driver(&sunxi_cpufreq_driver);// register a CPU Frequency driver
return ret;
}
static struct cpufreq_driver sunxi_cpufreq_driver = {
.name = "cpufreq-sunxi",
.flags = CPUFREQ_STICKY,
.init = sunxi_cpufreq_init,
.verify = sunxi_cpufreq_verify,
.target = sunxi_cpufreq_target,
.get = sunxi_cpufreq_get,
.getavg = sunxi_cpufreq_getavg,
.suspend = sunxi_cpufreq_suspend,
.resume = sunxi_cpufreq_resume,
};
cpufreq_register_driver函数,主要执行
1.subsys_interface_register(&cpufreq_interface);//给每一个cpu建立一个cpufreq_policy
2.register_hotcpu_notifier(&cpufreq_cpu_notifier);注册到CPU通知链cpu_chain,回调函数为cpufreq_cpu_callback。
static struct subsys_interface cpufreq_interface = {
.name = "cpufreq",
.subsys = &cpu_subsys,
.add_dev = cpufreq_add_dev,
.remove_dev = cpufreq_remove_dev,
};
drivers/cpufreq/cpufreq_powersave.c
module_init(cpufreq_gov_powersave_init);
struct cpufreq_governor cpufreq_gov_powersave = {
.name = "powersave",
.governor = cpufreq_governor_powersave,//通过调用__cpufreq_driver_target(policy, policy->min,
CPUFREQ_RELATION_L)实现,该函数又是调用cpufreq_driver->target,即sunxi_cpufreq_target来实现。
.owner = THIS_MODULE,
};
static int __init cpufreq_gov_powersave_init(void)
{
return cpufreq_register_governor(&cpufreq_gov_powersave);//将cpufreq_gov_powersave加入cpufreq_governor_list链表
}
static int sunxi_cpufreq_target(struct cpufreq_policy *policy, __u32 freq, __u32 relation)
{
……
for_each_cpu(i, policy->cpus) {
freqs.cpu = i;
cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE);//通知链上CPUFREQ_PRECHANGE事件通知
}
……
arisc_dvfs_set_cpufreq(freq, ARISC_DVFS_PLL1, ARISC_DVFS_SYN, NULL, NULL)//设置cpu频率。分配arisc_message结构体,调用arisc_hwmsgbox_send_message(pmessage, ARISC_SEND_MSG_TIMEOUT)函数发送,该函数定义于
drivers/arisc/hwmsgbox/ hwmsgbox.c
……
}
4.4 Ondemand的具体实现
drivers/cpufreq/cpufreq.c
static ssize_t store_scaling_governor(struct cpufreq_policy *policy,const char *buf, size_t count)
{
……
ret = __cpufreq_set_policy(policy, &new_policy);// 设置CPU频率策略,policy为当前策略
……
}
static int __cpufreq_set_policy(struct cpufreq_policy *data,
struct cpufreq_policy *policy)
{
……
/* adjust if necessary - all reasons */
blocking_notifier_call_chain(&cpufreq_policy_notifier_list,CPUFREQ_ADJUST, policy);//通知链上发送通知
/* adjust if necessary - hardware incompatibility*/
blocking_notifier_call_chain(&cpufreq_policy_notifier_list,CPUFREQ_INCOMPATIBLE, policy);
/* notification of the new policy */
blocking_notifier_call_chain(&cpufreq_policy_notifier_list,CPUFREQ_NOTIFY, policy);
……
/* end old governor */
if (data->governor) {
__cpufreq_governor(data, CPUFREQ_GOV_STOP);
unlock_policy_rwsem_write(policy->cpu);
__cpufreq_governor(data,CPUFREQ_GOV_POLICY_EXIT);
lock_policy_rwsem_write(policy->cpu);
}
/* start new governor */
data->governor = policy->governor;
if (!__cpufreq_governor(data, CPUFREQ_GOV_POLICY_INIT)) {
if (!__cpufreq_governor(data, CPUFREQ_GOV_START)) {
failed = 0;
} else {
unlock_policy_rwsem_write(policy->cpu);
__cpufreq_governor(data, CPUFREQ_GOV_POLICY_EXIT);
lock_policy_rwsem_write(policy->cpu);
}
}
……
}
停掉old governor,start new governor核心是调用__cpufreq_governor函数,而该函数调用policy->governor->governor(policy, event);进行处理,即对应governor的governor函数,例如ondemand策略,对应的函数为od_cpufreq_governor_dbs。
static struct common_dbs_data od_dbs_cdata = {
.governor = GOV_ONDEMAND,
.attr_group_gov_sys = &od_attr_group_gov_sys,
.attr_group_gov_pol = &od_attr_group_gov_pol,
.get_cpu_cdbs = get_cpu_cdbs,
.get_cpu_dbs_info_s = get_cpu_dbs_info_s,
.gov_dbs_timer = od_dbs_timer,
.gov_check_cpu = od_check_cpu,
.gov_ops = &od_ops,
.init = od_init,
.exit = od_exit,
};
static int od_cpufreq_governor_dbs(struct cpufreq_policy *policy,
unsigned int event)
{
return cpufreq_governor_dbs(policy, &od_dbs_cdata, event);
}
当event为CPUFREQ_GOV_POLICY_INIT时,
dbs_data = kzalloc(sizeof(*dbs_data), GFP_KERNEL);//分配dbs_data结构
if (!dbs_data) {
pr_err("%s: POLICY_INIT: kzalloc failed\\n", __func__);
return -ENOMEM;
}
dbs_data->cdata = cdata;
dbs_data->usage_count = 1;
rc = cdata->init(dbs_data);//调用od_dbs_cdata. Init,即od_init
if (rc) {
pr_err("%s: POLICY_INIT: init() failed\\n", __func__);
kfree(dbs_data);
return rc;
}
rc = sysfs_create_group(get_governor_parent_kobj(policy),get_sysfs_attr(dbs_data));//创建属性文件组
if (rc) {
cdata->exit(dbs_data);
kfree(dbs_data);
return rc;
}
policy->governor_data = dbs_data;
当event为CPUFREQ_GOV_START时,主要是初始化延时工作队列以及延时调度
INIT_DELAYED_WORK_DEFERRABLE(&j_cdbs->work,dbs_data->cdata->gov_dbs_timer);
gov_queue_work(dbs_data, policy,delay_for_sampling_rate(sampling_rate), true);
工作队列处理函数为od_dbs_timer,符合调整要求时,调用__cpufreq_driver_target(core_dbs_info->cdbs.cur_policy,
core_dbs_info->freq_lo, CPUFREQ_RELATION_H),通过cpufreq_driver->target(policy, target_freq, relation);实现频率切换;
gov_queue_work(dbs_data, dbs_info->cdbs.cur_policy, delay, modify_all)延时触发。
5 参考文献
《H3 Clock接口使用说明书V1.0.pdf》
http://www.xuebuyuan.com/2185926.html
以上是关于全志H3平台CLOCK简析的主要内容,如果未能解决你的问题,请参考以下文章
香蕉派 Banana Pi BPI-M2+四核开源开发板 全志H3芯片方案
全志H3 uboot传参到内核分析,boot.scr文件分析