zoukankan      html  css  js  c++  java
  • 配置linux内核,解决lcd logo和十分钟息屏!

    第一个问题:启动Linux的时候LCD会全屏花屏大约0.5秒,然后左上角出现一块不明花斑。
     
    这个问题相对简单。因为我在Bootloader里面打开了液晶显示,缓冲区映射在某个地址上,当内核初始化MMU的时候,LCD控制寄存器里 缓冲区的位置信息就不对了,或者是Bootloader使用的缓冲区被内核的数据或代码覆盖,导致在内核初始化LCD之前,LCD花屏。
    那个不明花斑其实是Linux的可爱小企鹅图片,但是可能因为缓冲区像素位宽和格式、LCD调色板设置等问题显示不出来。
     
    花屏解决方法:在Bootloader加载系统之前关掉LCD控制器或者关掉LCD的背光,这样做比较简单。复杂的,就得改MMU映射部分代 码,在修改了MMU映射之后,立即修改LCD缓冲的位置。反正我就关掉LCD控制器了,因为内核很快就会初始化LCD,Windows启动都黑屏呢,我们 黑那么2秒钟也没有什么大不了的。
    花斑解决方法:相信如果做产品的话,不需要显示什么企鹅给用户看,所以可以在内核选项里将这个企鹅logo关掉。具体位置在Device Drivers>Graphics support>Logo configuration,对应的宏相信在.config里面很容易找到。如果非要显示这个企鹅,那么可以在/drivers/video /console/fbcon.c里面找找,我也不知道怎么弄。
     
    第二个问题:Linux启动之后,只要一段时间不动键盘(开发板上用IO扩展出来的键盘),LCD就会自动关闭(黑屏、显示慢慢消失之类),只要按下键盘就能恢复。
     
    这个问题让我花了一天多的时间。其实如果是手持设备,这样也没有什么。但是我们公司的产品是要一直显示东西的,必须解决这个问题。我看了很多论 坛,有不少人也遇到了这个问题,但是我刚才是搜索的时候,关键词不对,总找不到正确的答案。如果你遇到了同样的问题,而且不想看我的三脚猫分析,那么就在 百度上搜索“blankinterval”、“setterm -blank 0”之类的,马上你就能找到简单的解决方案。
     
    这个问题很容易让人想到屏幕保护和电源管理。的确,这是一种电源管理。但是,你却无法从Linux内核选项的电源管理中解决这个问题。我们一步步来。
     
    首先,我测量到LCD的PCLK时钟消失了,这意味着内核把LCD控制器关掉了。于是,从LCD驱动程序着手。我用的是S3C2440,这是 2410的升级版,但是LCD控制器是一样的,在我拿到的开发板厂商给我做好驱动的内核里,驱动的位置在/drivers/video /s3c2410fb.c。为什么后面有个fb呢?这是Framebuffer的缩写,百度下你能找到很多关于它的解释。Framebuffer是所有 Linux下GUI程序对硬件操作的设备接口,位于/dev中,一般为fb0。在s3c2410fb.c中可以找到一个类似 s3c2410_disable_controller()这样名称的函数,我的驱动里叫pxafb_disable_controller(),可以看 出这个驱动是从pxa处理器改的,当然厂家不一样名字也叫得不一样。里面有一句类似这样写的 __raw_writel(fbi->reg.lcdcon1 & ~S3C2410_LCDCON1_ENVID, S3C2410_LCDCON1);,把这句话删掉LCD就不会关掉了。这是第一个层次,我也看到有人是这样做的。但是,这有问题,按键盘恢复后,原本显 示在屏幕上的东西如果你不重画会消失,就算你重画了,也会看到屏幕的某些部分先黑了下,然后恢复了。当然如果你可以接受,那么就这样吧。
     
    然后,可以很自然的想到是谁调用了这个函数,从源头把这个问题消除掉。但是情况却不是这样的。我搜索这个函数名,找到了一个 set_ctrlr_state()的函数调用了pxafb_disable_controller(),搜索set_ctrlr_state(),发现 有个pxafb_task()调用了set_ctrlr_state(),但是到了pxafb_task()就没有办法再往上找了,因为这是提供给内核的 一个任务,以指针传递函数入口。我对内核了解太不够了,花了很多时间看了很多论坛上的文章,机缘巧合之下,我找到了/drivers/char/vt.c 这个文件。vt.c我感觉应该是2.4内核的console.c和vt.c的结合体,应为它集成了console基本上所有功能函数,就ioctl在 vt_ioctl.c这个文件里。这个文件的主要作用是负责管理控制台,如控制台的模式(图形、字符)、向控制台输出等等。其中能找到一些如 do_blank_screen(),blank_screen_t()这样的函数,就是这些函数关闭了LCD控制器,修改任意一个都可以起作用。网上的 一个解决方案是把blank_screen_t()变成空函数,但是我没有这样试过,我觉得已经来到了问题的根源附近,应该能从根本上解决。
     
    我们先看下屏幕关闭问题的真正起因,看这个控制台初始化函数
    static int __init con_init(void)
    {
     const char *display_desc = NULL;
     struct vc_data *vc;
     unsigned int currcons = 0;
     acquire_console_sem();
     if (conswitchp)
      display_desc = conswitchp->con_startup();
     if (!display_desc) {
      fg_console = 0;
      release_console_sem();
      return 0;
     }
     init_timer(&console_timer);
     console_timer.function = blank_screen_t;
     if (blankinterval) {
      blank_state = blank_normal_wait;
      mod_timer(&console_timer, jiffies + blankinterval);
     }
    // 这是对控制台定时器的初始化,定时器事件函数被连接到了blank_screen_t()
     /*
      * kmalloc is not running yet - we use the bootmem allocator.
      */
     for (currcons = 0; currcons < MIN_NR_CONSOLES; currcons++) {
      vc_cons[currcons].d = vc = alloc_bootmem(sizeof(struct vc_data));
      visual_init(vc, currcons, 1);
      vc->vc_screenbuf = (unsigned short *)alloc_bootmem(vc->vc_screenbuf_size);
      vc->vc_kmalloced = 0;
      vc_init(vc, vc->vc_rows, vc->vc_cols,
       currcons || !vc->vc_sw->con_save_screen);
     }
     currcons = fg_console = 0;
     master_display_fg = vc = vc_cons[currcons].d;
     set_origin(vc);
     save_screen(vc);
     gotoxy(vc, vc->vc_x, vc->vc_y);
     csi_J(vc, 0);
     update_screen(vc);
     printk("Console: %s %s %dx%d",
      vc->vc_can_do_color ? "colour" : "mono",
      display_desc, vc->vc_cols, vc->vc_rows);
     printable = 1;
     printk("/n");
     release_console_sem();
    #ifdef CONFIG_VT_CONSOLE
     register_console(&vt_console_driver);
    #endif
     return 0;
    }
    其中引用了一个叫blankinterval的全局变量和一个console_time,我不知道内核的定时器是具体是怎么工作,但是 这样的代码已经很明显了。这个定时器和电源管理宏PM_CONFIG没有任何关系,它是控制台的一部分。再看下blank_screen_t():
     
    static void blank_screen_t(unsigned long dummy)
    {
     blank_timer_expired = 1;
     schedule_work(&console_work);
    }
     
    可以发现vt.c开头的宏,static DECLARE_WORK(console_work, console_callback, NULL);,找到了console_callback()这个函数:
     
    static void console_callback(void *ignored)
    {
     acquire_console_sem();
     if (want_console >= 0) {
      if (want_console != fg_console &&
          vc_cons_allocated(want_console)) {
       hide_cursor(vc_cons[fg_console].d);
       change_console(vc_cons[want_console].d);
       /* we only changed when the console had already
          been allocated - a new console is not created
          in an interrupt routine */
      }
      want_console = -1;
     }
     if (do_poke_blanked_console) { /* do not unblank for a LED change */
      do_poke_blanked_console = 0;
      poke_blanked_console();
     }
     if (scrollback_delta) {
      struct vc_data *vc = vc_cons[fg_console].d;
      clear_selection();
      if (vc->vc_mode == KD_TEXT)
       vc->vc_sw->con_scrolldelta(vc, scrollback_delta);
      scrollback_delta = 0;
     }
     if (blank_timer_expired) {
      do_blank_screen(0);
      blank_timer_expired = 0;
     }
     release_console_sem();
    }
    再看do_blank_screen(),随着struct vc_data中的与fops类似指针跟踪下去,就可以找到驱动里面的相应代码了。写出来太麻烦,让我偷懒把。
     
    小总结下,其实在控制台内部就有一个定时器,它负责在一定时间之后将显示关闭,而无视是否打开了电源管理功能。那这和Framebuffer有什么关系呢?我从一个很弱智的角度解释,内核刚启动的时候有这样一句输出:
    Console: colour dummy device 80x30
    在初始化LCD控制器(Framebuffer)之后,有这样一句输出:
    Console: switching to colour frame buffer device 80x30
    我就理解:这时候,内核把控制台(也不知道是console还是tty)切换到了Framebuffer上,大虾们赶快跳出来批判我吧,呵呵。
     
    回到正题,从代码可以发现,根本的解决之道是让blankinterval = 0,blank_state就不会是blank_off之外的值,也就不会关闭屏幕了。
    但是问题到这里还是没有完全解决,如果用户程序希望改变blankinterval来实现屏保(当然在我的系统上用不着);另外,一些程序改变 了blankinterval,程序退出之后,屏幕在一段时间之后还是会关闭的。怎么才能在用户程序那头解决这个问题呢,这又耗费了我很多时间。
     
    我在追查代码的过程中走了个弯路,认为修改控制台的模式可以不让黑屏现象出现,但是后来发现,这样可能会使控制台没有办法画图,不知道对不对。
    忽略弯路,直接正解。vt.c中不是有很多操作控制台的函数么?看看是谁修改了blankinterval。于是搜索 blankinterval,发现setterm_command()修改了它,然后搜索setterm_command,找到了 do_con_trol()函数,搜索do_con_trol,找到了do_con_write()函数,搜索do_con_write,终于最终 BOSS现身了:con_write()函数。为什么说它是最终BOSS呢?看看这段:
    static struct tty_operations con_ops = {
     .open = con_open,
     .close = con_close,
     .write = con_write,
     .write_room = con_write_room,
     .put_char = con_put_char,
     .flush_chars = con_flush_chars,
     .chars_in_buffer = con_chars_in_buffer,
     .ioctl = vt_ioctl,
     .stop = con_stop,
     .start = con_start,
     .throttle = con_throttle,
     .unthrottle = con_unthrottle,
    };
    熟悉fops的话你就能看出来了,这是对tty设备的文件操作函数的表。也就是说,在用户程序里,通过open函数打开/dev/tty,然后 再用write函数就可以修改blankinterval了。原理是找到了,实践上有很大困难,那么多重函数调用,再看看do_con_trol()里面 的switch语句,正常人都要发晕。好在伟大的百度为我们提供了很多信息:在命令行下,可以使用setterm -blank 0指令来设置blankinterval。哈哈,救星来了,赶快看看setterm的源代码。setterm属于util-linux包,搜索一下很容易 找到。其中的perform_sequence()函数里有这样一段:
     
     /* -blank [0-60]. */
     if (opt_blank && vcterm)
      printf("/033[9;%d]", opt_bl_min);
     
    真得很神奇啊,用个printf就可以在用户程序里解决这个问题,本来我是打算只说用printf解决的,看到原理我想会更舒服一些;况且,在我的系统上用printf是不行的。
    但是!问题还没有完,往往在我们的系统中,LCD的虚拟控制台和控制台TTY不是同一个设备,也就是说,如果在程序里单纯的printf是不行的!这样只能修改你正在使用的TTY的blankinterval,而你用的却是文本方式的设备,不存在黑屏问题。
    于是,就需要仔细比较/dev/console、/dev/tty、/dev/ttyn的设备号,在我的系统里,用户程序里/dev /console和/dev/tty都是5,说明他们是一个东西,/dev/ttyn是4,这才是FB上的虚拟控制台。但是/dev/ttyn不是正在使 用的TTY,那么怎么printf呢?只好用write函数来解决了。
     
    写这样一段代码:
    #include <fcntl.h>
    #include <stdio.h>
    #include <sys/ioctl.h>
     
    void some_function()
    {
     int f;
     f = open("/dev/tty0", O_RDWR);
     write(f, "/033[9;0]", 8);
     close(f);
    }

    还有一种差不多的方法,就是在应用程序中写入

    system("echo -e "33[9;0]" > /dev/tty0");
    另外在控制台上
    echo 0 > /sys/class/graphics/fb0/blank//打开LCD
    echo 1 > /sys/class/graphics/fb0/blank//关闭LCD
    问题终于解决了。
    总结下,第二个问题有很多种解决方法:
    1.修改LCD驱动,把关闭LCD控制器的函数变为空(不推荐)
    2.修改vt.c中的blank_screen_t()函数,让其为空(在系统不需要使用关闭显示功能时推荐)
    3.修改vt.c中的blankinterval,让其为0(系统可能需要使用关闭显示功能,而且希望系统上电后正常状态下不会关闭显示时推荐)
    4.修改用户程序,加入设置blankinterval的代码(推荐)
     
    懒惰不会让你一下子跌到 但会在不知不觉中减少你的收获; 勤奋也不会让你一夜成功 但会在不知不觉中积累你的成果 越努力,越幸运。
  • 相关阅读:
    717 汉诺塔的非递归实现 (25分)
    JSP入门[续]
    JSP运行原理和九大隐式对象
    JSP入门
    Mysql基本命令
    DataGridView数据导出为Excel
    sql server 2005中如何添加外键
    自定义配置文件的读取
    WinForm(C#)CheckedlistBox绑定数据,并获得选中的值(ValueMember)和显示文本(DisplayMember
    No CurrentSessionContext configured Hibernate
  • 原文地址:https://www.cnblogs.com/Rainingday/p/12627300.html
Copyright © 2011-2022 走看看