我们都知道可以从手机的耳机口,通过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的原理与直接使用
非常感谢!