赞
踩
我们都知道可以从手机的耳机口,通过USB-串口转换器链接到电脑USB接口,然后在电脑上使用putty或者cutecom,设置好波特率之类的参数,就可以读取到手机中kernel的log,甚至还能读到xbl,abl阶段的log. 那么,这些log到底是怎么来的呢? 我们所说的uart到底是啥?
百度百科上是这么说的:通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),通常称作UART,是一种异步收发传输器,是电脑硬件的一部分。它将要传输的资料在串行通信和并行通信之间加以转换。作为把并行输入信号转成串行输出信号的芯片,UART通常被集成于其他通讯接口的连结上。
当然,在手机里面也可以有uart,并且我们的kernel中的printk log就是通过这个uart最终发送出来的.
先认识几个比较重要的结构.
console结构,这个里面的write函数其实就是后面printk会调用到的write.后面会具体分析.
- static struct console cons_ops = {
- .name = "ttyMSM",
- .write = msm_geni_serial_console_write,
- .device = uart_console_device,
- .setup = msm_geni_console_setup,
- .flags = CON_PRINTBUFFER,
- .index = -1,
- .data = &msm_geni_console_driver, //是下面的结构体
- };
uart_driver结构,显然,这个结构中有个cons的元素,就是上面的console结构:
- static struct uart_driver msm_geni_console_driver = {
- .owner = THIS_MODULE,
- .driver_name = "msm_geni_console",
- .dev_name = "ttyMSM",
- .nr = GENI_UART_NR_PORTS,
- .cons = &cons_ops, // ==>是上面的那个结构体.
- };
还有两个uart_ops的结构:
- static const struct uart_ops msm_geni_console_pops = {
- .tx_empty = msm_geni_serial_tx_empty,
- .stop_tx = msm_geni_serial_stop_tx,
- .start_tx = msm_geni_serial_start_tx,
- .stop_rx = msm_geni_serial_stop_rx,
- .set_termios = msm_geni_serial_set_termios,
- .startup = msm_geni_serial_startup,
- .config_port = msm_geni_serial_config_port,
- .shutdown = msm_geni_serial_shutdown,
- .type = msm_geni_serial_get_type,
- .set_mctrl = msm_geni_cons_set_mctrl,
- .get_mctrl = msm_geni_cons_get_mctrl,
- #ifdef CONFIG_CONSOLE_POLL
- .poll_get_char = msm_geni_serial_get_char,
- .poll_put_char = msm_geni_serial_poll_put_char,
- #endif
- .pm = msm_geni_serial_cons_pm,
- };
-
-
- serial_core.c中:
- static const struct tty_operations uart_ops = {
- .open = uart_open,
- .close = uart_close,
- .write = uart_write,
- .put_char = uart_put_char,
- .flush_chars = uart_flush_chars,
- .write_room = uart_write_room,
- .chars_in_buffer= uart_chars_in_buffer,
- .flush_buffer = uart_flush_buffer,
- .ioctl = uart_ioctl,
- .throttle = uart_throttle,
- .unthrottle = uart_unthrottle,
- .send_xchar = uart_send_xchar,
- .set_termios = uart_set_termios,
- .set_ldisc = uart_set_ldisc,
- .stop = uart_stop,
- .start = uart_start,
- .hangup = uart_hangup,
- .break_ctl = uart_break_ctl,
- .wait_until_sent= uart_wait_until_sent,
- #ifdef CONFIG_PROC_FS
- .proc_fops = &uart_proc_fops,
- #endif
- .tiocmget = uart_tiocmget,
- .tiocmset = uart_tiocmset,
- .get_icount = uart_get_icount,
- #ifdef CONFIG_CONSOLE_POLL
- .poll_init = uart_poll_init,
- .poll_get_char = uart_poll_get_char,
- .poll_put_char = uart_poll_put_char,
- #endif
- };

接下来,看看init函数是怎么实现的.
- static int __init msm_geni_serial_init(void)
- {
- int ret = 0;
- int i;
-
- for (i = 0; i < GENI_UART_NR_PORTS; i++) {
- msm_geni_serial_ports[i].uport.iotype = UPIO_MEM;
- msm_geni_serial_ports[i].uport.ops = &msm_geni_serial_pops;
- msm_geni_serial_ports[i].uport.flags = UPF_BOOT_AUTOCONF;
- msm_geni_serial_ports[i].uport.line = i;
- }
-
- for (i = 0; i < GENI_UART_CONS_PORTS; i++) {
- msm_geni_console_port.uport.iotype = UPIO_MEM;
- msm_geni_console_port.uport.ops = &msm_geni_console_pops; //这个操作函数结构体在上面
- msm_geni_console_port.uport.flags = UPF_BOOT_AUTOCONF;
- msm_geni_console_port.uport.line = i;
- }
-
- ret = console_register(&msm_geni_console_driver); //看下面,这个函数命名有点坑,实际上就是调用了 uart_register_driver().
- if (ret)
- return ret;
-
- ret = uart_register_driver(&msm_geni_serial_hs_driver);//看上去是有register了两个 uart driver.
- if (ret) {
- uart_unregister_driver(&msm_geni_console_driver);
- return ret;
- }
-
- ret = platform_driver_register(&msm_geni_serial_platform_driver); //再注册一个platform driver
- if (ret) {
- console_unregister(&msm_geni_console_driver);
- uart_unregister_driver(&msm_geni_serial_hs_driver);
- return ret;
- }
-
- pr_info("%s: Driver initialized", __func__);
- return ret;
- }

嗯,其实init函数也没什么很特别的,关键就是调用了uart_register_driver. 后面要好好看一下这个注册函数的实现.
- /**
- * uart_register_driver - register a driver with the uart core layer
- * @drv: low level driver structure
- *
- * Register a uart driver with the core driver. We in turn register
- * with the tty layer, and initialise the core driver per-port state.
- *
- * We have a proc file in /proc/tty/driver which is named after the
- * normal driver.
- *
- * drv->port should be NULL, and the per-port structures should be
- * registered using uart_add_one_port after this call has succeeded.
- */
- int uart_register_driver(struct uart_driver *drv)
- {
- struct tty_driver *normal;
- int i, retval;
-
- BUG_ON(drv->state);
-
- /*
- * Maybe we should be using a slab cache for this, especially if
- * we have a large number of ports to handle.
- */
- drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL); //nr是15.分配多个state
- if (!drv->state)
- goto out;
-
- normal = alloc_tty_driver(drv->nr); //normal是一个 struct tty_driver *
- if (!normal)
- goto out_kfree;
-
- drv->tty_driver = normal;
-
- normal->driver_name = drv->driver_name;
- normal->name = drv->dev_name;
- normal->major = drv->major;
- normal->minor_start = drv->minor;
- normal->type = TTY_DRIVER_TYPE_SERIAL;
- normal->subtype = SERIAL_TYPE_NORMAL;
- normal->init_termios = tty_std_termios;
- normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
- normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;
- normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
- normal->driver_state = drv;
- tty_set_operations(normal, &uart_ops); //就是normal->ops=&uart_ops.
-
- /*
- * Initialise the UART state(s).
- */
- for (i = 0; i < drv->nr; i++) {
- struct uart_state *state = drv->state + i;
- struct tty_port *port = &state->port; //每一个state有一个tty_port
-
- tty_port_init(port); //这个init主要是设置这个port的一些参数
- port->ops = &uart_port_ops; //port的操作函数结构体是uart_port_ops
- }
-
- retval = tty_register_driver(normal); //这个比较重要
- if (retval >= 0)
- return retval;
-
- for (i = 0; i < drv->nr; i++)
- tty_port_destroy(&drv->state[i].port);
- put_tty_driver(normal);
- out_kfree:
- kfree(drv->state);
- out:
- return -ENOMEM;
- }

其实这个注册函数看上去也还好,就是让我们的uart_driver结构变得更加庞大了.
drv->tty_driver===>也就是上面函数中的normal,并且初始化了这个tty_driver结构体.
drv->state->port ====>这个state还会指向一个 struct tty_port结构
看probe函数.省略掉不感兴趣的部分:
- static int msm_geni_serial_probe(struct platform_device *pdev)
- {
- int ret = 0;
- int line;
- struct msm_geni_serial_port *dev_port;
- struct uart_port *uport;
- struct resource *res;
- struct uart_driver *drv;
- const struct of_device_id *id;
- bool is_console = false;
- struct platform_device *wrapper_pdev;
- struct device_node *wrapper_ph_node;
- u32 wake_char = 0;
-
- id = of_match_device(msm_geni_device_tbl, &pdev->dev); //根据这个id,选出的是我们的msm_geni_console_driver
- if (id) {
- dev_dbg(&pdev->dev, "%s: %s\n", __func__, id->compatible);
- drv = (struct uart_driver *)id->data;
- } else {
- dev_err(&pdev->dev, "%s: No matching device found", __func__);
- return -ENODEV;
- }
-
- if (pdev->dev.of_node) {
- if (drv->cons)
- line = of_alias_get_id(pdev->dev.of_node, "serial"); //这个.
- else
- line = of_alias_get_id(pdev->dev.of_node, "hsuart");
- } else {
- line = pdev->id;
- }
-
- if (line < 0)
- line = atomic_inc_return(&uart_line_id) - 1;
-
- if ((line < 0) || (line >= GENI_UART_NR_PORTS))
- return -ENXIO;
- is_console = (drv->cons ? true : false); //我们的这个drv是有cons的呀.
- dev_port = get_port_from_line(line, is_console); //又出来一个新的结构,struct msm_geni_serial_port *dev_port;实际上这个结构在init函数里面有初始化的.
- if (IS_ERR_OR_NULL(dev_port)) {
- ret = PTR_ERR(dev_port);
- dev_err(&pdev->dev, "Invalid line %d(%d)\n",
- line, ret);
- goto exit_geni_serial_probe;
- }
-
- uport = &dev_port->uport; //重点,上面刚出现的这个结构指向的uport就是很重要的uport,也就是struct uart_port *uport;
-
- /* Don't allow 2 drivers to access the same port */
- if (uport->private_data) {
- ret = -ENODEV;
- goto exit_geni_serial_probe;
- }
-
- uport->dev = &pdev->dev;
-
- ......//中间做了不少对uport的设置.
-
-
- return uart_add_one_port(drv, uport); //将drv和uport绑定在一起.
-
- exit_geni_serial_probe:
- return ret;
- }

probe函数中,好像也没有做什么特别的事情,比较重要的是定义了一个struct msm_geni_serial_port *dev_port;而这个dev_port又指向一个uart_port的结构,并且对这个uart_port做了一些初始化.
当然,对系统结构分析最重要的是最后调用的函数uart_add_one_port. 接下来继续看看这个函数是怎么实现的.
- /**
- * uart_add_one_port - attach a driver-defined port structure
- * @drv: pointer to the uart low level driver structure for this port
- * @uport: uart port structure to use for this port.
- *
- * This allows the driver to register its own uart_port structure
- * with the core driver. The main purpose is to allow the low
- * level uart drivers to expand uart_port, rather than having yet
- * more levels of structures.
- */
- int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
- {
- struct uart_state *state;
- struct tty_port *port;
- int ret = 0;
- struct device *tty_dev;
- int num_groups;
-
- BUG_ON(in_interrupt());
-
- if (uport->line >= drv->nr)
- return -EINVAL;
-
- state = drv->state + uport->line; //uport->line=0.
- port = &state->port; //struct tty_port *port;
-
- mutex_lock(&port_mutex);
- mutex_lock(&port->mutex);
- if (state->uart_port) {
- ret = -EINVAL;
- goto out;
- }
-
- /* Link the port to the driver state table and vice versa */
- atomic_set(&state->refcount, 1);
- init_waitqueue_head(&state->remove_wait);
- state->uart_port = uport; //当当当!state指向的uart_port结构就是传参传进来的uport
- uport->state = state; //相应地,uport的state也就是传进来的drv->state.这两步就是将drv和uport绑定在一起的!
-
- state->pm_state = UART_PM_STATE_UNDEFINED;
- uport->cons = drv->cons; //uport的cons就是drv的cons
- uport->minor = drv->tty_driver->minor_start + uport->line;
-
- /*
- * If this port is a console, then the spinlock is already
- * initialised.
- */
- if (!(uart_console(uport) && (uport->cons->flags & CON_ENABLED))) {
- spin_lock_init(&uport->lock);
- lockdep_set_class(&uport->lock, &port_lock_key);
- }
- if (uport->cons && uport->dev)
- of_console_check(uport->dev->of_node, uport->cons->name, uport->line);
-
- //这个uart_configure_port里面会根据条件判断是否需要调用一个register_console.
- //并且会加入到console_drivers的list中.这个如果注册成功,在printk最后会调到uport->cons->write函数.
- //也就是开篇的static struct console cons_ops中的write函数.
- uart_configure_port(drv, state, uport);
-
- port->console = uart_console(uport);
-
- num_groups = 2;
- if (uport->attr_group)
- num_groups++;
-
- uport->tty_groups = kcalloc(num_groups, sizeof(*uport->tty_groups),
- GFP_KERNEL);
- if (!uport->tty_groups) {
- ret = -ENOMEM;
- goto out;
- }
- uport->tty_groups[0] = &tty_dev_attr_group;
- if (uport->attr_group)
- uport->tty_groups[1] = uport->attr_group;
-
- /*
- * Register the port whether it's detected or not. This allows
- * setserial to be used to alter this port's parameters.
- */
- tty_dev = tty_port_register_device_attr(port, drv->tty_driver,
- uport->line, uport->dev, port, uport->tty_groups);
- if (likely(!IS_ERR(tty_dev))) {
- device_set_wakeup_capable(tty_dev, 1);
- } else {
- dev_err(uport->dev, "Cannot register tty device on line %d\n",
- uport->line);
- }
-
- /*
- * Ensure UPF_DEAD is not set.
- */
- uport->flags &= ~UPF_DEAD;
-
- out:
- mutex_unlock(&port->mutex);
- mutex_unlock(&port_mutex);
-
- return ret;
- }

到现在位置,驱动的初始化部分就结束了. 后面再看看是如何使用这个uart进行通信的.
直接上code:
- /**
- * printk - print a kernel message
- * @fmt: format string
- *
- * This is printk(). It can be called from any context. We want it to work.
- *
- * We try to grab the console_lock. If we succeed, it's easy - we log the
- * output and call the console drivers. If we fail to get the semaphore, we
- * place the output into the log buffer and return. The current holder of
- * the console_sem will notice the new output in console_unlock(); and will
- * send it to the consoles before releasing the lock.
- *
- * One effect of this deferred printing is that code which calls printk() and
- * then changes console_loglevel may break. This is because console_loglevel
- * is inspected when the actual printing occurs.
- *
- * See also:
- * printf(3)
- *
- * See the vsnprintf() documentation for format string extensions over C99.
- */
- asmlinkage __visible int printk(const char *fmt, ...)
- {
- va_list args;
- int r;
-
- va_start(args, fmt);
- r = vprintk_func(fmt, args);
- va_end(args);
-
- return r;
- }
- EXPORT_SYMBOL(printk);

接着看:
- static inline __printf(1, 0) int vprintk_func(const char *fmt, va_list args)
- {
- return vprintk_default(fmt, args);
- }
再看:
- int vprintk_default(const char *fmt, va_list args)
- {
- int r;
-
- #ifdef CONFIG_KGDB_KDB //这个是没有定义的...
- if (unlikely(kdb_trap_printk)) {
- r = vkdb_printf(KDB_MSGSRC_PRINTK, fmt, args);
- return r;
- }
- #endif
- r = vprintk_emit(0, LOGLEVEL_DEFAULT, NULL, 0, fmt, args);
-
- return r;
- }
- EXPORT_SYMBOL_GPL(vprintk_default);
继续往后看:
- asmlinkage int vprintk_emit(int facility, int level,
- const char *dict, size_t dictlen,
- const char *fmt, va_list args)
- {
- static bool recursion_bug;
- static char textbuf[LOG_LINE_MAX];
- static char textbuf1[LOG_LINE_MAX];
- char *text = textbuf;
- char *text1 = textbuf1;
- size_t text_len = 0;
- enum log_flags lflags = 0;
- unsigned long flags;
- int this_cpu;
- int printed_len = 0;
- int nmi_message_lost;
- bool in_sched = false;
- /* cpu currently holding logbuf_lock in this function */
- static unsigned int logbuf_cpu = UINT_MAX;
-
-
- if (level == LOGLEVEL_SCHED) {
- level = LOGLEVEL_DEFAULT;
- in_sched = true; //如果传进来的level==LOGLEVEL_SCHED,那就设为true
- }
-
- boot_delay_msec(level);
- printk_delay();
-
- ......
- /*
- * The printf needs to come first; we need the syslog
- * prefix which might be passed-in as a parameter.
- */
- text_len = vscnprintf(text1, sizeof(textbuf1), fmt, args);
-
- ......
-
- #ifdef CONFIG_EARLY_PRINTK_DIRECT
- printascii(text1);
- #endif
-
- if(needPrintTime)
- {
- char buf[64];
- size_t buf_size = sprintf(buf, "(CPU:%d-pid:%d:%s) ", smp_processor_id(), current->pid, current->comm);
- strncpy(text, buf, buf_size);
- strncpy(text + buf_size, text1, text_len);
- text_len += buf_size;
- }
- else
- {
- strncpy(text, text1, text_len);
- }
-
- if (level == LOGLEVEL_DEFAULT)
- level = default_message_loglevel;
-
- if (dict)
- lflags |= LOG_PREFIX|LOG_NEWLINE;
-
- printed_len += log_output(facility, level, lflags, dict, dictlen, text, text_len);
-
- if(lflags & LOG_NEWLINE)
- {
- needPrintTime = true;
- }
- else
- {
- needPrintTime = false;
- }
-
- logbuf_cpu = UINT_MAX;
- raw_spin_unlock(&logbuf_lock);
- lockdep_on();
- local_irq_restore(flags);
-
- /* If called from the scheduler, we can not call up(). */
- if (!in_sched) {
- lockdep_off();
- /*
- * Try to acquire and then immediately release the console
- * semaphore. The release will print out buffers and wake up
- * /dev/kmsg and syslog() users.
- */
- if (console_trylock())
- console_unlock(); //这里...
- lockdep_on();
- }
-
- return printed_len;
- }
- EXPORT_SYMBOL(vprintk_emit);

再看console_unlock, 这个函数中调了 call_console_drivers(level, ext_text, ext_len, text, len);
那么再看call_console_drivers:
- /*
- * Call the console drivers, asking them to write out
- * log_buf[start] to log_buf[end - 1].
- * The console_lock must be held.
- */
- static void call_console_drivers(int level,
- const char *ext_text, size_t ext_len,
- const char *text, size_t len)
- {
- struct console *con;
-
- trace_console_rcuidle(text, len);
-
- if (!console_drivers)
- return;
-
- for_each_console(con) { //遍历console_drivers,实际上只有前面提到的一个地方有成功加到这个list中
- if(strncmp(con->name,"logk",4) != 0){
- if (level >= console_loglevel && !ignore_loglevel)
- continue;
- if (exclusive_console && con != exclusive_console)
- continue;
- }
- if (!(con->flags & CON_ENABLED))
- continue;
- if (!con->write)
- continue;
- if (!cpu_online(smp_processor_id()) &&
- !(con->flags & CON_ANYTIME))
- continue;
- if (con->flags & CON_EXTENDED)
- con->write(con, ext_text, ext_len);
- else
- con->write(con, text, len); //这个write函数就是最开始的console结构体中的write函数.
- }
- }

至于上面这个函数中遍历的console_drivers,这个list成员的注册,有一个要求是根据cmdline传过来的参数需要一致. 在机台中读到cmdline中含有console=ttyMSM0. 也就是为什么只有最开篇的这个console结构体中的write函数最终被调到,并且有log 通过uart吐出来.
可以看到,printk丢log还是单向的.也就是说是只是printk函数最终调用了uart_driver这个结构体指向的一个结构console的write函数.
很显然,这并不是uart的全部功能,uart应该是双向通信,这个是可以通过对节点进行open,write,read等操作来实现的.
参考博文:
http://www.cnblogs.com/lidabo/p/5414007.html 驱动程序调试方法之printk——printk的原理与直接使用
非常感谢!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。