我们都知道可以从手机的耳机口,通过USB-串口转换器链接到电脑USB接口,然后在电脑上使用putty或者cutecom,设置好波特率之类的参数,就可以读取到手机中kernel的log,甚至还能读到xbl,abl阶段的log.  那么,这些log到底是怎么来的呢? 我们所说的uart到底是啥?

1.UART是什么?

百度百科上是这么说的:通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),通常称作UART,是一种异步收发传输器,是电脑硬件的一部分。它将要传输的资料在串行通信和并行通信之间加以转换。作为把并行输入信号转成串行输出信号的芯片,UART通常被集成于其他通讯接口的连结上。

当然,在手机里面也可以有uart,并且我们的kernel中的printk log就是通过这个uart最终发送出来的.

2.UART驱动结构分析

2.1 驱动的init

先认识几个比较重要的结构.

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 driverif (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.分配多个stateif (!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_porttty_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结构

2.2 probe函数

看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_driverif (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"); //这个.elseline = 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结构就是传参传进来的uportuport->state = state; //相应地,uport的state也就是传进来的drv->state.这两步就是将drv和uport绑定在一起的!state->pm_state = UART_PM_STATE_UNDEFINED;uport->cons = drv->cons;  //uport的cons就是drv的consuport->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进行通信的.

3 printk分析

直接上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;}
#endifr = 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_DIRECTprintascii(text1);
#endifif(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);elsecon->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的原理与直接使用

非常感谢!

Android uart driver相关推荐

  1. Android Binder Driver缺陷导致定屏问题分析

    本文讲解异步Android binder call是如何阻塞整个系统的,通过ramdump信息以及binder通信协议来演绎并还原定屏现场. 一.背景知识点 解决此问题所涉及到的基础知识点有:Trac ...

  2. Android Binder Driver流程分析

    Binder是Android中使用最为广泛的IPC框架,从实现的角度可以分为内核层和用户空间层,本文主要分析下Binder Driver在内核层的实现. Binder Driver初始化 1 2 3 ...

  3. android uart 助手,android串口通讯助手源码(ComAssistant)

    [实例简介]Android串口通信 android串口通讯助手源码,可以获取设备中可用串口.破特率,发送hex数据.文本txt数据等功能 [实例截图] [核心代码] ComAssistant └── ...

  4. Android Audio Driver基础

    Android Audio底层使用的是liunx alsa驱动,录制或播放或声音所需的基本硬件是音频芯片或声卡(Sound cards), alsa声卡设备如下图所示: 1 Control      ...

  5. Android Sensor Driver(四)——IIC总线和驱动

    IIC是手机中主要的传感器总线,当时IIC的设备驱动想对于IIC总线来说则简单的多.IIC设备驱动本来这里我打算说一下IIC设备的数据传输函数,但是工作需要我去研究java,为了吃饭,这个sensor ...

  6. 解决了android usb driver无法找到硬件信息的问题

    关于驱动的问题,应该是手机的VID.PID与驱动中的VID.PID不一致造成的.所以在驱动的inf文件里把VID.PID改成与手机的一样,就可以安装成功了.具体做法如下: 1. 连接手机,选择ADB( ...

  7. Android bluetooth介绍(二): android 蓝牙代码架构及其uart 到rfcomm流程

    关键词:蓝牙blueZ  UART  HCI_UART H4  HCI  L2CAP RFCOMM  版本:基于android4.2之前版本 bluez内核:linux/linux3.08 系统:an ...

  8. Android bluetooth介绍(两): android 蓝牙源架构和uart 至rfcomm过程

    关键词:蓝牙blueZ  UART  HCI_UART H4  HCI  L2CAP RFCOMM  版本号:基于android4.2先前版本 bluez内核:linux/linux3.08 系统:a ...

  9. Android上成功实现了蓝牙的一些Profile

    前段时间做蓝牙方面的开发,Google的Android只实现了Handset/Handfree和A2DP/AVRCP等Profile,而其 它常用的Profile如HID/DUN/SPP/OPP/FT ...

最新文章

  1. 理解LSTM/RNN中的Attention机制
  2. Windows系统调用学习笔记(四)—— 系统服务表SSDT
  3. python之字符串的切片操作
  4. Create your own blockchain amp; cryptocurrency! - understand blockchains by creating one in python
  5. ELK学习7_ELK文档资料:《ELK stack 权威指南/饶琛琳》勘误
  6. RHEL5.3下MRTG+SNMP的搭建
  7. python制作点亮图片_MicroPython拼插编程实例:点亮心形8x8点阵
  8. LeetCode: Convert BST to Greater Tree
  9. 如何选择bfs和dfs
  10. 左移寄存器vhdl_VHDL实验代码:8位移位寄存器
  11. m1芯片 服务器,今天来说说苹果M1 和华为ARM PC芯片
  12. 呼和浩特php平均工资,2019年内蒙古平均工资出炉
  13. 查找并删除EXCEL文件中的重复行(整行重复)
  14. 什么是独享锁/共享锁_java培训
  15. icheck结合datatable使用方法及实现全选、反选功能
  16. 年和日转化为天干地支
  17. openvino系列 15. OpenVINO OCR
  18. 校园网页设计成品 学校班级网页制作模板 dreamweaver网页作业 简单网页课程成品 大学生静态HTML网页源码
  19. linux挂载2T以上硬盘
  20. IE选项注册表控制大全

热门文章

  1. Mac电脑使用:下载安装SourceTree的步骤以及使用方法
  2. nasa注册_“NASA”商标注册案例分析
  3. 英语科技论文撰写技巧
  4. 计算机导论5000字论文,计算机导论论文
  5. 三光(可见光、红外光、激光)云台产品调研
  6. 四十六、基础框架(四)字符串物件
  7. hihocoder #1163 : 博弈游戏·Nim游戏
  8. 2021年安全员-A证(广西省-2021版)考试及安全员-A证(广西省-2021版)考试试卷
  9. 2022安全员-C证考试题模拟考试题库及模拟考试
  10. es6 新增 常用 语法 知识点 汇总