printk打印到LCD

2024-04-16 07:32
文章标签 打印 lcd printk

本文主要是介绍printk打印到LCD,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

command_line="initrd=0x30800000,0x440000 root=/dev/ram init=/linuxrc console=tty0";
这样就可以改变系统的控制台了. 还是在setup_arch函数中找到了以下代码,以后会用到的.
#ifdef CONFIG_VT
#if defined(CONFIG_VGA_CONSOLE)
 conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
 conswitchp = &dummy_con;
#endif
#endif
CONFIG_VT,在menuconfig的Character devices中选上Virtual terminal
CONFIG_VGA_CONSOLE,在menuconfig的Console drivers中选上VGA text console
CONFIG_DUMMY_CONSOLE,在menuconfig的Console drivers的Frame-buffer support中
选上Support for frame buffer devices,参看driver/video中的Config.in文件
意思是如果选择VGA文本控制台,那么缺省的显示设备接口是vga_con,如果是LCD之类的
Frame-buffer设备,则缺省的显示设备接口是dummy_con.
我这边是用的LCD,所以重点关注dummy_con.
const struct consw *conswitchp;
const struct consw dummy_con = {
    con_startup: dummycon_startup,
    con_init:  dummycon_init,
    con_deinit:  DUMMY,
    con_clear:  DUMMY,
    con_putc:  DUMMY,
    con_putcs:  DUMMY,
    con_cursor:  DUMMY,
    con_scroll:  DUMMY,
    con_bmove:  DUMMY,
    con_switch:  DUMMY,
    con_blank:  DUMMY,
    con_font_op: DUMMY,
    con_set_palette: DUMMY,
    con_scrolldelta: DUMMY,
};
还是回到start_kernel中,接下来调用了parse_options(command_line)来处理命令行
它会以空格对命令行进行分解,比如分解后使得line="console=ttyS0",然后调用
checksetup(line),下面分析一下这个函数
static int __init checksetup(char *line)
{
 struct kernel_param *p;
 p = &__setup_start; //setup段开始地址
 do {
  int n = strlen(p->str);
  if (!strncmp(line,p->str,n)) //比较line和p->str的n个字符
  {
   if (p->setup_func(line+n))//相等则调用函数
    return 1;
  }
  p++;
 } while (p < &__setup_end);
 return 0;
}
需要理解的是这个setup段,这和驱动的初始化init_call段类似,在include/linux/init.h
中可以找到
#define __setup(str, fn)        /
 static char __setup_str_##fn[] __initdata = str;    /
 static struct kernel_param __setup_##fn __attribute__((unused)) __initsetup = { __setup_str_##fn, fn }
#define __initsetup __attribute__ ((unused,__section__ (".setup.init")))
另外我在kernel/printk.c中找到了__setup("console=", console_setup);
通过这个函数,在链接时就会在段.setup.init中建立一个kernel_param结构,其中一个成员
是字符串"console="的指针,另一个则是console_setup这个函数指针,于是这样在checksetup
中就必然会调用到console_setup这个函数了.
该函数的作用是把传入的字符串进行处理并保存在全局结构中.我们以console=ttyS0 console=tty0
为例,这样就会调用console_setup两次,第一次是console_setup("ttyS0"),第二次是console_setup("tty0").
全局结构struct console_cmdline console_cmdline[MAX_CMDLINECONSOLES];
struct console_cmdline
{
 char name[8];   /* Name of the driver     */
 int index;    /* Minor dev. to use     */
 char *options;   /* Options for the driver   */
};
第一次就会使console_cmdline[0]的name="ttyS",index=0,options=NULL
第二次就会使console_cmdline[1]的name="tty",index=0,options=NULL
并且最后preferred_console = 1 ,这些结构会在注册控制台时用到.
接着下来还是回到start_kernel函数中,该函数调用了console_init()来初始化console
在该函数中调用了con_init()函数来初始化tty控制台,uart_console_init()初始化ttyS控制台
在con_init()函数中有
#ifdef CONFIG_VT_CONSOLE  //menuconfig的Character devices中选上Support for console...
 register_console(&vt_console_driver);
#endif
struct console vt_console_driver = {
 name:  "tty",
 write:  vt_console_print,
 device:  vt_console_device,
 wait_key: keyboard_wait_for_keypress,
 flags:  CON_PRINTBUFFER,
 index:  -1,
};
在register_console函数中会把name和前面赋值的console_cmdline[1]中的name比较,
 console->flags |= CON_ENABLED;
 console->index = console_cmdline[i].index;
 if (i == preferred_console)  console->flags |= CON_CONSDEV;
这样其实在命令行最后的console的i才会等于preferred_console,所以它会是主控制台
 if ((console->flags & CON_CONSDEV) || console_drivers == NULL) {
  console->next = console_drivers;
  console_drivers = console;
 } else {
  console->next = console_drivers->next;
  console_drivers->next = console;
 }
会把控制台驱动组织成单链表格式,头结点是全局指针struct console *console_drivers
它指向命令行参数最后的console,以后注册的回插在其后
比如在我这里就是console_drivers == vt_console_driver --> s3c2410_cons --> NULL
前面已经说过printk和register_console这两个函数最后都会调用release_console_sem函数,
该函数最终调用了printk.c中的__call_console_drivers函数来打印字符。
static void __call_console_drivers(unsigned long start, unsigned long end)
{
 struct console *con;

 for (con = console_drivers; con; con = con->next) {
  if ((con->flags & CON_ENABLED) && con->write)
   con->write(con, &LOG_BUF(start), end - start);
 }
} 这个函数又调用了console_drivers中各个成员的write方法
对于vt_console_driver,其write方法就是vt_console_print,到这里我们还需要回去看几个
特别重要的结构,以便更好的理解这个函数。
在console.c的con_init函数中有如下代码 
for (currcons = 0; currcons < MIN_NR_CONSOLES; currcons++) {
  vc_cons[currcons].d = (struct vc_data *)
    alloc_bootmem(sizeof(struct vc_data));
  vt_cons[currcons] = (struct vt_struct *)
    alloc_bootmem(sizeof(struct vt_struct));
  visual_init(currcons, 1);
  screenbuf = (unsigned short *) alloc_bootmem(screenbuf_size);
  kmalloced = 0;
  vc_init(currcons, video_num_lines, video_num_columns, 
   currcons || !sw->con_save_screen); //sw=dummy_con
 }
 currcons = fg_console = 0;
 master_display_fg = vc_cons[currcons].d;

看看定义
#define MIN_NR_CONSOLES 1   
struct vc vc_cons [MAX_NR_CONSOLES];
struct vt_struct *vt_cons[MAX_NR_CONSOLES];
int fg_console;   fg_console is the current virtual console
static struct vc_data *master_display_fg;
这样我们可以知道这段代码首先分配了vc_cons[0].d的内存,vt_cons[0]的内存,
然后调用了visual_init来填充vc_cons[0].d结构
static void visual_init(int currcons, int init)
{
    /* ++Geert: sw->con_init determines console size */
    sw = conswitchp; //#define sw (vc_cons[currcons].d->vc_sw)
#ifndef VT_SINGLE_DRIVER //这里sw就被赋值为上面介绍过的dummy_con了
    if (con_driver_map[currcons]) //第一次调用时是NULL
 sw = con_driver_map[currcons];
#endif
    cons_num = currcons;
    display_fg = &master_display_fg;
    vc_cons[currcons].d->vc_uni_pagedir_loc = &vc_cons[currcons].d->vc_uni_pagedir;
    vc_cons[currcons].d->vc_uni_pagedir = 0;
    hi_font_mask = 0;
    complement_mask = 0;
    can_do_color = 0;
    sw->con_init(vc_cons[currcons].d, init); //调用了dummycon_init
    if (!complement_mask) //can_do_color=1
        complement_mask = can_do_color ? 0x7700 : 0x0800; 
    s_complement_mask = complement_mask;
    video_size_row = video_num_columns<<1; 
    screenbuf_size = video_num_lines*video_size_row;
}这里的video_num_lines=80  video_num_columns=30是在dummycon_init调用中赋值的。
接下来分配一个80*30*2的内存screenbuf,是用来存放需要显示到屏幕上的数据的,
当然由于刚开始我们的sw指向的是dummy_con,是丢弃控制台,其结构方法基本都是空操作,
所以并不会向屏幕上打印什么。只有在后来初始化了lcd的驱动,sw会指向fb_con结构,那
才能真正在lcd屏上显示。具体的在下面接着介绍。

回到vt_console_print函数中看,其中一段代码
 while (count--) {
  c = *b++;
  if (c == 10 || c == 13 || c == 8 || need_wrap) {
   if (cnt > 0) {
    if (IS_VISIBLE)
     sw->con_putcs(vc_cons[currcons].d, start, cnt, y, x);
    x += cnt;
    if (need_wrap)
     x--;
    cnt = 0;
   }
   if (c == 8) {  /* backspace */
    bs(currcons);
    start = (ushort *)pos;
    myx = x;
    continue;
   }
   if (c != 13)
    lf(currcons);
   cr(currcons);
   start = (ushort *)pos;
   myx = x;
   if (c == 10 || c == 13)
    continue;
  }
  scr_writew((attr << 8) + c, (unsigned short *) pos);
  cnt++;
  if (myx == video_num_columns - 1) {
   need_wrap = 1;
   continue;
  }
  pos+=2;
  myx++;
 }
仔细分析一下可以知道,要显示的字符会通过scr_writew保存到vc_cons[0].d->vc_screenbuf
中pos指向的位置,当然保存的是(attr << 8) + c的值,而如果字符是10 13 8或者一行满的时候
则会调用sw->con_putcs方法把该行显示至屏幕,对于dummy_con当然是什么也不做了,而对于
fb_con则会在lcd上显示。看到有个条件是 IS_VISIBLE
#define IS_VISIBLE CON_IS_VISIBLE(vc_cons[currcons].d)
#define CON_IS_VISIBLE(conp) (*conp->vc_display_fg == conp)
是要求*(vc_cons[currcons].d->vc_display_fg) == vc_cons[currcons].d
而上面在visual_init中有display_fg = &master_display_fg;
出来在con_init中又有master_display_fg = vc_cons[currcons].d;
于是其实master_display_fg = vc_cons[0].d
这样对于currcons=0时 IS_VISIBLE 为真。

在没有注册vt_console_driver前调用printk函数,要打印的字符只会保存到全局数组log_buf中,
而在注册vt_console_driver时同样会把以前保存在log_buf中的打印出来.看代码就知道了

接下来看看真正的lcd驱动是如何与控制台接轨的.
在drivers/video/s3c2410fb.c中找到lcd初始化的代码
int __init s3c2410fb_init(void)
{
    struct s3c2410fb_info *fbi;
    int ret;
    fbi = s3c2410fb_init_fbinfo();
    ret = -ENOMEM;
    if (!fbi)
 goto failed;
    ret = s3c2410fb_map_video_memory(fbi);
    if (ret)
 goto failed;
    s3c2410_lcd_init();
    s3c2410fb_set_var(&fbi->fb.var, -1, &fbi->fb);
    ret = register_framebuffer(&fbi->fb);
    if (ret < 0)
        goto failed;
    printk("Installed S3C2410 frame buffer/n");
    MOD_INC_USE_COUNT ;
    return 0;
failed:
    if (fbi)
 kfree(fbi);
    return ret;
}
主要是分配和填充struct s3c2410fb_info结构体,其中细节的地方下一次再分析,
这里主要看一下lcd驱动和控制台的关系.
在register_framebuffer(&fbi->fb)中调用了
take_over_console(&fb_con, first_fb_vc, last_fb_vc, fbcon_is_default);
这个函数会用fb_con替换dummy_con,具体看其中的代码
void take_over_console(const struct consw *csw, int first, int last, int deflt)
{
 int i, j = -1;
 const char *desc;
 desc = csw->con_startup();
 if (!desc) return;
 if (deflt)
  conswitchp = csw; //这里把fb_con赋给全局变量conswitchp
 for (i = first; i <= last; i++) {
  int old_was_color;
  int currcons = i;
  con_driver_map[i] = csw;
  if (!vc_cons[i].d || !vc_cons[i].d->vc_sw)
   continue;
  j = i;
  if (IS_VISIBLE)
   save_screen(i);
  old_was_color = vc_cons[i].d->vc_can_do_color;
  vc_cons[i].d->vc_sw->con_deinit(vc_cons[i].d);
  visual_init(i, 0); //这里又调用visual_init
  update_attr(i);
  if (old_was_color != vc_cons[i].d->vc_can_do_color)
   clear_buffer_attributes(i);
  if (IS_VISIBLE)
   update_screen(i);
 }
 printk("Console: switching ");
 if (!deflt)
  printk("consoles %d-%d ", first+1, last+1);
 if (j >= 0)
  printk("to %s %s %dx%d/n",
         vc_cons[j].d->vc_can_do_color ? "colour" : "mono",
         desc, vc_cons[j].d->vc_cols, vc_cons[j].d->vc_rows);
 else
  printk("to %s/n", desc);
}

再次调用visual_init的时候把vc_cons[0].d->vc_sw赋值为fb_con,同时通过
sw->con_init(vc_cons[currcons].d, init)调用了fbcon_init函数
该函数又调用了fbcon_setup函数fbcon_setup(unit, init, !init)
在fbcon_setup中如果logo=1的话就会把screenbuf空间中的上面一块
留给logo就是那个蜻蜓,又调用了vc_resize_con(nr_rows, nr_cols, con),
vc_resize_con会重新调整vc_cons[0].d->vc_screenbuf并把原来screenbuf中
对应的数据拷过来,原来是80*30,现在成了40*30,而上面的10*30留给了logo,
这样只把原来最下面的30*30复制过来,通过调用update_screen(currcons)来
更新屏幕,update_screen调用fbcon_switch显示logo,并显示screen_buf中的数据.
take_over_console以后再调用printk函数就会调用fbcon_putcs真正实现把
屏幕显示.

接下来看看几个显示设备的区别吧,分别是tty,tty0,console,vc/0
在drivers/char/mem.c函数中找到chr_dev_init函数,其调用的tty_init在
tty_io.c中,该函数注册了/dev/tty,/dev/console,/dev/vc/0这几个设备,
而/dev/tty0是通过mknod建立的节点,并不是devfs设备.
他们都是通过tty_register_driver注册的,而且其设备方法集都是tty_fops
tty      5  0
console    5  1
tty0    4  0
vc/0    4  0
major和minor如上所示,这样看来关键还是看tty_fops中的方法,先看tty_open
 device = inode->i_rdev;
 if (device == TTY_DEV) {
  if (!current->tty)
   return -ENXIO;
  device = current->tty->device; //打开tty时用current->tty->device
  filp->f_flags |= O_NONBLOCK; /* Don't let /dev/tty block */
  /* noctty = 1; */
 }
#ifdef CONFIG_VT
 if (device == CONSOLE_DEV) { //打开tty0和vc/0时用device=0x401
  extern int fg_console;
  device = MKDEV(TTY_MAJOR, fg_console + 1);
  noctty = 1;
 }
#endif
 if (device == SYSCONS_DEV) {
  struct console *c = console_drivers;
  while(c && !c->device)
   c = c->next;
  if (!c)
                        return -ENODEV;
                device = c->device(c);
  filp->f_flags |= O_NONBLOCK; /* Don't let /dev/console block */
  noctty = 1;
 }
而打开console时,调用了vt_console_device,使用串口控制台的话当然返回的是ttyS0的设备号,这里只介绍LCD控制台。

static kdev_t vt_console_device(struct console *c)
{
 return MKDEV(TTY_MAJOR, c->index ? c->index : fg_console + 1);
}
在我这里返回的也是0x401
这样看来tty0和vc/0和console甚至vc/1其device都是0x401
接下来调用了init_dev(device, &tty)分配填充tty结构,在init_dev函数中
driver = get_tty_driver(device)获取对应设备的驱动,0x401获取的并不是
在tty_init中注册的dev_tty_driver或dev_syscons_driver,而是在console.c
con_init中注册的console_driver,可以看到这里还同时要注册vc/1-63设备,不过
由于开始没有支持devfs,其实是在tty_init中调用con_init_devfs来注册的.
于是driver=&console_driver,这样tty->driver=&console_driver,在tty_open
中有filp->private_data = tty,并通过tty->driver.open(tty, filp)调用了con_open函数.
重点就是要知道filp->private_data = tty,tty这个结构,在tty_read和tty_write中都用到
再来看tty_write函数
tty = (struct tty_struct *)file->private_data;
do_tty_write(tty->ldisc.write, tty, file,(const unsigned char *)buf, count);
而tty结构中的ldisc其实是在console_init函数中注册的
tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY),就是tty_ldisc_N_TTY
其write方法就是write_chan,在drivers/char/N_tty.c中,事实上
在write_chan中还是通过tty->driver.write(tty, 1, b, nr)调用了console_driver的
write方法con_write,其又调用了do_con_write(tty, from_user, buf, count)
其中很关键的两句代码
struct vt_struct *vt = (struct vt_struct *)tty->driver_data;
currcons = vt->vc_num;
这样就必须回去看看tty->driver_data是怎么赋值的,这是在con_open中给出的
 currcons = MINOR(tty->device) - tty->driver.minor_start;
 i = vc_allocate(currcons);
 if (i)
  return i;
 vt_cons[currcons]->vc_num = currcons;
 tty->driver_data = vt_cons[currcons];
而console_driver.minor_start = 1
于是乎对于0x401设备来说currcons=0,vt_cons[0]->vc_num = 0
tty->driver_data = vt_cons[0]
回到do_con_write中定义了
#define FLUSH if (draw_x >= 0) { /
 sw->con_putcs(vc_cons[currcons].d, (u16 *)draw_from, (u16 *)draw_to-(u16 *)draw_from, y, draw_x); /
 draw_x = -1; /
 }
通过FLUSH调用了fb_con的con_putcs方法把数据写到lcd上去.
当然注意了这里
 if (DO_UPDATE && draw_x < 0) {
  draw_x = x;
  draw_from = pos;
 }
#define DO_UPDATE IS_VISIBLE
如果IS_VISIBLE为0的话就没法调用con_putcs更新屏幕了,
这也是为什么写vc/2等不能显示的原因了. 

这篇关于printk打印到LCD的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/908154

相关文章

多数据源的事务处理总是打印很多无用的log日志

之前做了一个项目,需要用到多数据源以及事务处理,在使用事务处理,服务器总是打印很多关于事务处理的log日志(com.atomikos.logging.Slf4jLogger),但是我们根本不会用到这些log日志,反而使得查询一些有用的log日志变得困难。那要如何屏蔽这些log日志呢? 之前的项目是提高项目打印log日志的级别,后来觉得这样治标不治本。 现在有一个更好的方法: 我使用的是log

fastreport打印trichedit分页问题的解决

用fastreport来打印richedit里面的内容。刚开始放一个frxrichview组件到报表上,然后在 var str: TMemoryStream; begin    begin      str:= TMemoryStream.Create;      CurrRichRecord.richedit.Lines.SaveToStream(str);      str.Posit

模具要不要建设3D打印中心

随着3D打印技术的日益成熟与广泛应用,模具企业迎来了自建3D打印中心的热潮。这一举措不仅为企业带来了前所未有的发展机遇,同时也伴随着一系列需要克服的挑战,如何看待企业引进增材制造,小编为您全面分析。 机遇篇: 加速产品创新:3D打印技术如同一把钥匙,为模具企业解锁了快速迭代产品设计的可能。企业能够迅速将创意转化为实体模型,缩短产品从设计到市场的周期,抢占市场先机。 强化定制化服务:面

Java项目中,配置打印 JDBC 日志的几种方法

在 IDEA 项目中,如果你想打印 JDBC 日志,可以通过配置日志框架(如 Logback 或 Log4j)来实现。Spring Boot 使用的默认日志框架是 Logback,你可以通过在 application.yml 文件中配置日志级别来打印 JDBC 日志。 方法 1: 使用 application.yml 配置 JDBC 日志 logging:level:# 显示 SQL 语句co

一个C++程序运行,从点击运行到控制台打印文本,电脑硬件的资源是如何调动的

当点击运行一个 C++ 程序并看到控制台输出文本时,计算机硬件和操作系统之间协同工作,完成了多个步骤。这些步骤涉及 CPU、内存、存储设备、操作系统和输入输出设备的共同作用。下面是一个详细的过程描述: 1. 程序加载 启动:当你点击运行一个可执行文件时,操作系统(通常是 Windows、Linux 或 macOS)的文件系统管理器识别请求,并启动加载程序。读取可执行文件:加载程序将可执行文件从

LCD彩条显示——FPGA学习笔记10

部分素材来自原子哥 一、LCD简介         基本原理:在两块平行玻璃板中填充液晶材料,通过电场控制液晶分子旋转从而达到透光和遮光的目的。

【蓝桥杯嵌入式(二)Led、Key、Lcd】

蓝桥杯嵌入式(二)Led、Key、Lcd 五、Led模块1.原理图配置2. 知识点3.底层代码 六、Key模块1.原理图配置2.知识点3.底层代码底层代码(四⾏代码版本)底层代码(状态机版本) 七、LCD模块1.原理图配置2.知识点底层代码 五、Led模块 1.原理图配置 2. 知识点 链接: 上拉电阻的通俗解释 链接: 单⽚机怎么输出⾼电平!推挽输出和开

跨平台打印模板转化pdf源码--SAAS本地化及未来之窗行业应用跨平台架构

一、跨平台打印转pdf渲染 pdf渲染模式可以支持国产化系统,和手机系统,安卓,苹果系统,qq浏览器,火狐,谷歌刘安祺 二、代码 /*///cyberwin_offline_database_printtemp.js未来之窗打印模板解析技术 2024-09LeftMargin="0" TopMargin="0" RightMargin="0" BottomMargin="0"ReportP

日志框架log4j打印异常堆栈信息携带traceId,方便接口异常排查

一、异常堆栈无traceId 排查定位问题异常痛苦        在日常项目开发中,我们会自定义一个traceId方便,链路追踪。在log4j2.xml 我们可能是这样去配置日志打印格式。 <Console name="CONSOLE" target="SYSTEM_OUT"><PatternLayoutpattern="${APP_NAME} %-d{yyyy-MM-dd HH:mm:ss}

hiprint打印/jsPDF使用/html2canvas

最初我知道hiprint.print是可以打印双模板的,于是查看hiprint.print的源码发现底层实现是this.getHtml(t).hiwprint,于是断点查看getHtm的实现,得知它是遍历我们对print传参的list,利用list中模板对象的getHtml()方法得到模板的dom对象,同时利用append将两个模板dom拼接到一个模板对象里然后返回。至此我们可以拿到一个合成的模板