本文主要是基于高通msm8953对lcd在lk阶段通过cmdline向kernel传递参数的过程进行分析。
一、LK阶段分析
相关文件:
app/aboot/aboot.c
Target/msm8953/Target_display.c
Dev/gcdb/display/gcdb_display_param.c
Target/msm8953/oem_panel.c
在gcdb_display_cmdline_arg函数中将屏信息保存到display_panel_buf 中,并通过boot_linux->update_cmdline函数中整合其他模块信息后,在update_device_tree函数中附加到bootargs中,从而想kernel传递信息。相关代码如下:
1、整个过程主要发生在update_cmdline中,如下:
App/aboot/aboot.c
Boot_linux
->Update_cmdline
->target_display_panel_node
->gcdb_display_cmdline_arg
//获取主屏信息(存储在panelstruct结构中),具体如1.1分析:
->mdss_dsi_get_panel_data
//oem_data存储的是副屏的信息,
//而由于oem_data.skip为false,
//故在这个函数中是返回false,且slave_panel_node此时还未赋值,具体如1.2分析:
->mdss_dsi_set_panel_node
//将主屏名称拷到panel_node中存储
-> ->panel_node = panelstruct.paneldata->panel_node_id
//根据副屏名称查找lookup_skip_panels数组,
//返回panel_dt_string成员存到slave_panel_node中,具体如1.3分析:
->panel_name_to_dt_string
->最终将panel_node + lookup_skip_panels + 其他信息 存到pbuf,即display_panel_buf中
//在update_cmdline中将display_panel_buf+其他数据整合
->update_cmdline
//将上述update_cmdline整合后的各信息附加到设备树的bootargs信息中,具体如1.4分析:
->update_device_tree
1.1)panelstruct是在oem_panel.c中_panel_data函数赋值,如下:
struct panel_struct mdss_dsi_get_panel_data(void)
{
return panelstruct;
}
case LT8911B_1080P_VIDEO_PANEL:
panelstruct->paneldata = <8911b_1080p_video_panel_data;
/*
static struct panel_config lt8911b_1080p_video_panel_data = {
"qcom,mdss_dsi_lt8911b_1080p_video", "dsi:0:", "qcom,mdss-dsi-panel",
10, 0, "DISPLAY_1", 0, 0, 60, 0, 0, 0, 1, 10000, 0, 0, 0, 0, 0, 0, NULL
};
*/
panelstruct->panelres = <8911b_1080p_video_panel_res;
panelstruct->color = <8911b_1080p_video_color;
panelstruct->videopanel = <8911b_1080p_video_video_panel;
panelstruct->commandpanel = <8911b_1080p_video_command_panel;
panelstruct->state = <8911b_1080p_video_state;
panelstruct->laneconfig = <8911b_1080p_video_lane_config;
panelstruct->paneltiminginfo
= <8911b_1080p_video_timing_info;
panelstruct->panelresetseq
= <8911b_1080p_video_reset_seq;
panelstruct->backlightinfo = <8911b_1080p_video_backlight;
pinfo->mipi.panel_on_cmds = lt8911b_1080p_video_on_command;
pinfo->mipi.num_of_panel_on_cmds
= LT8911B_1080P_VIDEO_ON_COMMAND;
pinfo->mipi.panel_off_cmds = NULL;
pinfo->mipi.num_of_panel_off_cmds
= 0;
memcpy(phy_db->timing,
lt8911b_1080p_video_timings,
MAX_TIMING_CONFIG * sizeof(uint32_t));
pinfo->mipi.signature = LT8911B_1080P_VIDEO_SIGNATURE;
panelstruct->paneldata->panel_operating_mode &= ~USE_DSI1_PLL_FLAG;
break;
相关结构体定义如下:
struct panel_struct {
struct panel_config *paneldata;
struct panel_resolution *panelres;
struct color_info *color;
struct videopanel_info *videopanel;
struct commandpanel_info *commandpanel;
struct command_state *state;
struct lane_configuration *laneconfig;
struct panel_timing *paneltiminginfo;
struct panel_reset_sequence *panelresetseq;
struct backlight *backlightinfo;
struct fb_compression fbcinfo;
struct topology_config *config;
};
struct panel_config{
char *panel_node_id;
char *panel_controller;
char *panel_compatible;
uint16_t panel_interface;
uint16_t panel_type;
char *panel_destination;
uint32_t panel_orientation;
/* panel_clockrate is deprecated in favor of panel_bitclock_freq */
uint32_t panel_clockrate;
uint16_t panel_framerate;
uint16_t panel_channelid;
uint16_t dsi_virtualchannel_id;
uint16_t panel_broadcast_mode;
uint16_t panel_lp11_init;
uint16_t panel_init_delay;
uint16_t dsi_stream;
uint8_t interleave_mode;
uint32_t panel_bitclock_freq;
uint32_t panel_operating_mode;
uint32_t panel_with_enable_gpio;
uint8_t mode_gpio_state;
char *slave_panel_node_id;
};
因此执行 panel_node = panelstruct.paneldata->panel_node_id时,实际为将主屏名称信息赋值给panel_node,即panel_node = qcom,mdss_dsi_lt8911b_1080p_video。
1.2)mdss_dsi_set_panel_node主要涉及oem_data数据,而oem_data数据是在set_panel_cmd_string中赋值,如下:
struct oem_panel_data oem_data = {{'\0'}, {'\0'}, false, false, false, SIM_NONE,
"dual_dsi", DSI_PLL_DEFAULT, {-1, -1}};
struct oem_panel_data {
char panel[MAX_PANEL_ID_LEN];
char sec_panel[MAX_PANEL_ID_LEN];
bool cont_splash;
bool skip;
bool swap_dsi_ctrl;
uint32_t sim_mode;
char dsi_config[DSI_CFG_SIZE];
uint32_t dsi_pll_src;
/* If dual-DSI, slave cfg will use 2nd index */
int cfg_num[2]; /* -ve number means no overide */
};
void set_panel_cmd_string(const char *panel_name)
{
//省略无关代码
ch = strstr((char *) panel_name, "sec:");
if (ch) {
ch += 4;
ch_tmp = get_panel_token_end((const char*) ch);
if (!ch_tmp)
ch_tmp = ch + strlen(ch);
for (i = 0; (ch + i) < ch_tmp; i++)
oem_data.sec_panel[i] = *(ch + i);
oem_data.sec_panel[i] = '\0';
/* Topology configuration for secondary panel */
ch_tmp = strstr((char *) ch, ":cfg");
if (ch_tmp)
oem_data.cfg_num[1] = atoi((const char*)(ch_tmp + 4));
} else {
oem_data.sec_panel[0] = '\0';
}
/* Skip LK configuration */
ch = strstr((char *) panel_name, ":skip");
oem_data.skip = ch ? true : false;
}
传入的参数格式为:set_panel_cmd_string("sec:dsi_lt8911b_1080p_dsi1_video");
因此,oem_data.skip实际为false,oem_data.sec_panel 实际为"dsi_lt8911b_1080p_dsi1_video"字符串。
1.3)panel_name_to_dt_string是根据oem_data.sec_panel匹配lookup_skip_panels数组中的panel_dt_string成员,从而找到节点名称,如下:
//oem_data.sec_panel在init_panel_data中调用set_panel_cmd_string进行赋值
ret_val = panel_name_to_dt_string(lookup_skip_panels,
ARRAY_SIZE(lookup_skip_panels), oem_data.sec_panel,
&slave_panel_node);
static int panel_name_to_dt_string(struct panel_lookup_list supp_panels[],
uint32_t supp_panels_size,
const char *panel_name, char **panel_node)
{
uint32_t i;
//省略无关代码
for (i = 0; i < supp_panels_size; i++) {
if (!strncmp(panel_name, supp_panels[i].name,
MAX_PANEL_ID_LEN)) {
*panel_node = supp_panels[i].panel_dt_string;
return supp_panels[i].is_split_dsi;
}
}
return ERR_NOT_FOUND;
}
形如:
struct panel_lookup_list lookup_skip_panels[] = {
{"dsi_lt8911b_1080p_dsi1_video", "qcom,mdss_dsi_lt8911b_1080p_dsi1_video", false},
}
因此,slave_panel_node = qcom,mdss_dsi_lt8911b_1080p_dsi1_video。
故在update_cmdline 函数时 display_panel_buf的大致数据为:
mdss_mdp.panel=1:dsi:0:qcom,mdss_dsi_lt8911exb_1080p_video:1:qcom,mdss_dsi_lt8911exb_1080p_dsi1_video:cfg:dual_dsi
1.4)先在update_cmdline中display_panel_buf赋值,再update_device_tree中将cmdline的信息追加到bootargs中,如下:
unsigned char *update_cmdline(const char * cmdline)
{
//省略无关代码
if (cmdline) {
if ((strstr(cmdline, DISPLAY_DEFAULT_PREFIX) == NULL) &&
target_display_panel_node(display_panel_buf,
MAX_PANEL_BUF_SIZE) &&
strlen(display_panel_buf)) {
cmdline_len += strlen(display_panel_buf);
}
}
}
int update_device_tree(void *fdt, const char *cmdline,
void *ramdisk, uint32_t ramdisk_size)
{
//省略无关代码
if (cmdline)
{
/* Adding the cmdline to the chosen node */
ret = fdt_appendprop_string(fdt, offset, (const char*)"bootargs", (const void*)cmdline);
if (ret)
{
dprintf(CRITICAL, "ERROR: Cannot update chosen node [bootargs]\n");
return ret;
}
}
}
二、kernel阶段分析
1、在Start_kernel函数中获取传到内核的buf,并将buf写入_param段中,如下:
init/main.c
Start_kernel
->setup_arch(&command_line);
->mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
->parse_args
->parse_one
将lk中传递的数据写到__param段中
->params[i].ops->set(val, ¶ms[i])=param_set_copystring
而param的定义如下:
struct kernel_param {
const char *name;
struct module *mod;
const struct kernel_param_ops *ops;
const u16 perm;
s8 level;
u8 flags;
union {
void *arg;
const struct kparam_string *str;
const struct kparam_array *arr;
};
};
const struct kernel_param_ops param_ops_string = {
.set = param_set_copystring,
.get = param_get_string,
};
int param_set_copystring(const char *val, const struct kernel_param *kp)
{
const struct kparam_string *kps = kp->str;
if (strlen(val)+1 > kps->maxlen) {
pr_err("%s: string doesn't fit in %u chars.\n",
kp->name, kps->maxlen-1);
return -ENOSPC;
}
strcpy(kps->string, val);
return 0;
}
int param_get_string(char *buffer, const struct kernel_param *kp)
{
const struct kparam_string *kps = kp->str;
return strlcpy(buffer, kps->string, kps->maxlen);
}
因此,boot_command_line中存储的是从lk传到kernel的参数,并将传参的参数写入_param段中,等待驱动加载时通过module_param_string使用。
2、分析如何使用_param段中的数据,主要从LCD驱动分析。
2.1)在mdss_mdp.c中存在驱动的传参定义,并且展开相应的宏定义后就是根据模块名在_param段中匹配屏的信息,如下;
module_param_string(panel, mdss_mdp_panel, MDSS_MAX_PANEL_LEN, 0600);
参数说明:
panel:模块名 mdss-mdp
mdss_mdp_panel:存储屏相关的信息
MDSS_MAX_PANEL_LEN:256
module_param_string定义:
#define module_param_string(name, string, len, perm) \
static const struct kparam_string __param_string_##name \
= { len, string }; \
__module_param_call(MODULE_PARAM_PREFIX, name, \
¶m_ops_string, \
.str = &__param_string_##name, perm, -1, 0);\
__MODULE_PARM_TYPE(name, "string")
//若是模块编译的为空,不是模块编译的话为 "模块名."
//由于makefile中定义为:obj-$(CONFIG_FB_MSM_MDSS) += mdss-mdp.o
//因此 模块名为mdss-mdp.o,即kernel传参数的时候,参数名为 mdss-mdp.panel
#ifdef MODULE
#define MODULE_PARAM_PREFIX /* empty */
#else
#define MODULE_PARAM_PREFIX KBUILD_MODNAME "."
#endif
#define __module_param_call(prefix, name, ops, arg, perm, level, flags) \
/* Default value instead of permissions? */ \
static const char __param_str_##name[] = prefix #name; \
static struct kernel_param __moduleparam_const __param_##name \
__used \
__attribute__ ((unused,__section__ ("__param"),aligned(sizeof(void *)))) \
= { __param_str_##name, THIS_MODULE, ops, \
VERIFY_OCTAL_PERMISSIONS(perm), level, flags, { arg } }
故:
module_param_string(panel, mdss_mdp_panel, MDSS_MAX_PANEL_LEN, 0600);
等价于:
#define module_param_string(name, string, len, perm) \
static const struct kparam_string __param_string_panel = {MDSS_MAX_PANEL_LEN, mdss_mdp_panel}; \
static const char __param_str_panel[] = mdss_mdp.panel;
static struct kernel_param __moduleparam_const __param_panel \
__used __attribute__ ((unused,__section__ ("__param"),aligned(sizeof(void *)))) \
= {
__param_str_panel,
THIS_MODULE,
param_ops_string,
VERIFY_OCTAL_PERMISSIONS(0600),
-1,
0,
.str = &__param_string_panel
}
2.2)mdss_mdp_panel数组的使用过程如下:
Video/fbdev/msm/Mdss_dsi.c
mdss_dsi_ctrl_probe->//由于主副屏原因,该函数会执行两轮
->mdss_dsi_config_panel
->mdss_dsi_get_panel_cfg
->ctrl->mdss_util->panel_intf_type(MDSS_PANEL_INTF_DSI)=mdss_panel_intf_type
->mdss_res->pan_cfg //在mdss_mdp_get_cmdline_config函数中赋值
->mdss_mdp_get_cmdline_config
//具体如2.3分析:
->mdss_mdp_get_pan_cfg
->mdss_mdp_panel//数组
//由于执行两轮,因此第一轮解析的cmdline是主屏信息,
//第二轮时解析的是副屏信息
//具体如2.4分析:
->mdss_dsi_find_panel_of_node
2.3 mdss_mdp_get_pan_cfg分析:
将参数mdss_mdp_panel进行解析到pan_cfg中,如下:
static int mdss_mdp_get_pan_cfg(struct mdss_panel_cfg *pan_cfg)
{
char *t = NULL;
char pan_intf_str[MDSS_MAX_PANEL_LEN];
int rc, i, panel_len;
char pan_name[MDSS_MAX_PANEL_LEN] = {'\0'};
if (!pan_cfg)
return -EINVAL;
if (mdss_mdp_panel[0] == '0') {
pr_debug("panel name is not set\n");
pan_cfg->lk_cfg = false;
pan_cfg->pan_intf = MDSS_PANEL_INTF_INVALID;
return -EINVAL;
} else if (mdss_mdp_panel[0] == '1') {
pan_cfg->lk_cfg = true;
} else {
/* read from dt */
pan_cfg->lk_cfg = true;
pan_cfg->pan_intf = MDSS_PANEL_INTF_INVALID;
return -EINVAL;
}
/* skip lk cfg and delimiter; ex: "1:" */
strlcpy(pan_name, &mdss_mdp_panel[2], MDSS_MAX_PANEL_LEN);
t = strnstr(pan_name, ":", MDSS_MAX_PANEL_LEN);
if (!t) {
pr_err("pan_name=[%s] invalid\n", pan_name);
pan_cfg->pan_intf = MDSS_PANEL_INTF_INVALID;
return -EINVAL;
}
for (i = 0; ((pan_name + i) < t) && (i < 4); i++)
pan_intf_str[i] = *(pan_name + i);
pan_intf_str[i] = 0;
pr_debug("%d panel intf %s\n", __LINE__, pan_intf_str);
/* point to the start of panel name */
t = t + 1;
strlcpy(&pan_cfg->arg_cfg[0], t, sizeof(pan_cfg->arg_cfg));
pr_debug("%d: t=[%s] panel name=[%s]\n", __LINE__,
t, pan_cfg->arg_cfg);
panel_len = strlen(pan_cfg->arg_cfg);
if (!panel_len) {
pr_err("Panel name is invalid\n");
pan_cfg->pan_intf = MDSS_PANEL_INTF_INVALID;
return -EINVAL;
}
rc = mdss_mdp_get_pan_intf(pan_intf_str);
pan_cfg->pan_intf = (rc < 0) ? MDSS_PANEL_INTF_INVALID : rc;
return 0;
}
2.4)mdss_dsi_find_panel_of_node过程分析
static struct device_node *mdss_dsi_find_panel_of_node(
struct platform_device *pdev, char *panel_cfg)
{
int len, i = 0;
int ctrl_id = pdev->id - 1;
char panel_name[MDSS_MAX_PANEL_LEN] = "";
char ctrl_id_stream[3] = "0:";
char *str1 = NULL, *str2 = NULL, *override_cfg = NULL;
char cfg_np_name[MDSS_MAX_PANEL_LEN] = "";
struct device_node *dsi_pan_node = NULL, *mdss_node = NULL;
struct mdss_dsi_ctrl_pdata *ctrl_pdata = platform_get_drvdata(pdev);
struct mdss_panel_info *pinfo = &ctrl_pdata->panel_data.panel_info;
len = strlen(panel_cfg);
ctrl_pdata->panel_data.dsc_cfg_np_name[0] = '\0';
if (!len) {
/* no panel cfg chg, parse dt */
pr_debug("%s:%d: no cmd line cfg present\n",
__func__, __LINE__);
goto end;
} else {
/* check if any override parameters are set */
pinfo->sim_panel_mode = 0;
override_cfg = strnstr(panel_cfg, "#" OVERRIDE_CFG, len);
if (override_cfg) {
*override_cfg = '\0';
if (mdss_dsi_set_override_cfg(override_cfg + 1,
ctrl_pdata, panel_cfg))
return NULL;
len = strlen(panel_cfg);
}
if (ctrl_id == 1)
strlcpy(ctrl_id_stream, "1:", 3);
/* get controller number */
str1 = strnstr(panel_cfg, ctrl_id_stream, len);
if (!str1) {
pr_err("%s: controller %s is not present in %s\n",
__func__, ctrl_id_stream, panel_cfg);
goto end;
}
if ((str1 != panel_cfg) && (*(str1-1) != ':')) {
str1 += CMDLINE_DSI_CTL_NUM_STRING_LEN;
pr_debug("false match with config node name in \"%s\". search again in \"%s\"\n",
panel_cfg, str1);
str1 = strnstr(str1, ctrl_id_stream, len);
if (!str1) {
pr_err("%s: 2. controller %s is not present in %s\n",
__func__, ctrl_id_stream, str1);
goto end;
}
}
str1 += CMDLINE_DSI_CTL_NUM_STRING_LEN;
/* get panel name */
str2 = strnchr(str1, strlen(str1), ':');
if (!str2) {
strlcpy(panel_name, str1, MDSS_MAX_PANEL_LEN);
} else {
for (i = 0; (str1 + i) < str2; i++)
panel_name[i] = *(str1 + i);
panel_name[i] = 0;
}
pr_info("%s: cmdline:%s panel_name:%s\n",
__func__, panel_cfg, panel_name);
if (!strcmp(panel_name, NONE_PANEL))
goto exit;
mdss_node = of_parse_phandle(pdev->dev.of_node,
"qcom,mdss-mdp", 0);
if (!mdss_node) {
pr_err("%s: %d: mdss_node null\n",
__func__, __LINE__);
return NULL;
}
dsi_pan_node = of_find_node_by_name(mdss_node, panel_name);
if (!dsi_pan_node) {
pr_err("%s: invalid pan node \"%s\"\n",
__func__, panel_name);
goto end;
} else {
/* extract config node name if present */
str1 += i;
str2 = strnstr(str1, "config", strlen(str1));
if (str2) {
str1 = strnchr(str2, strlen(str2), ':');
if (str1) {
for (i = 0; ((str2 + i) < str1) &&
i < (MDSS_MAX_PANEL_LEN - 1); i++)
cfg_np_name[i] = *(str2 + i);
if ((i >= 0)
&& (i < MDSS_MAX_PANEL_LEN))
cfg_np_name[i] = 0;
} else {
strlcpy(cfg_np_name, str2,
MDSS_MAX_PANEL_LEN);
}
strlcpy(ctrl_pdata->panel_data.dsc_cfg_np_name,
cfg_np_name, MDSS_MAX_PANEL_LEN);
}
}
return dsi_pan_node;
}
end:
if (strcmp(panel_name, NONE_PANEL))
dsi_pan_node = mdss_dsi_pref_prim_panel(pdev);
exit:
return dsi_pan_node;
}
三、总结
首先是通过module_param_string在start_kernel 中去解析从lk传来的cmdline(boot_command_line)并保存在Mdss_mdp_panel上,接着通过在mdss_mdp_get_pan_cfg函数中复制到pan_cfg中,最后在mdss_dsi_find_panel_of_node中根据pan_cfg对指定的dtsi文件进行解析获取panel参数。