device端的逻辑
---------------
1.device什么地方一直往uart端口上吐数据?
LK中
uart_putc(0, c);
最终会调用到
static int _uart_putc(int port, char c)
{
if (!uart_ready)
return -1;
while (!(urd(UART_SR) & UART_SR_TX_READY)) ;
uwr(c, UART_TF);
return 0;
}
The UART_SR register is the UART status register.
bit[2] TXRDY This bit indicates that the transmit FIFO has space in it.
The UART_TF register is the UART transmit FIFO register
#define uwr(v,a) writel(v, uart_base + (a))
#define urd(a) readl(uart_base + (a))
这是在LK中,并没有tty及dev的概念,最纯洁最简单的串口输出数据.执行过程应该很简单,调用uart_putc把数据
写到UART的FIFO完事.
剩下的在能在电脑屏幕上看到log输出应该是PC机的事了.PC机通过一根串口线连接到设备的串口,然后从这个FIFO
读数据,显示到屏幕上.
Kernel中
mkbootimg命令或CONFIG_CMDLINE会传一些参数,其中就包括cmdline,
__setup("console=", console_setup)(kernel/printk.c)会用到这个cmdline.
kernel中的log不是直接显示的,而是先把数据扔到LOG_BUF中,然后再显示出来.这个buf的大小是128K.
也就是你调用的printk把log放到了LOG_BUF这个循环buf中.
我们可以看到终端在大约0.5秒时才打印register_console(drivers/serial/serial_core.c):
[ 0.527662] ==register_console==
这个register_console,顾名思义,注册一个console,这是你平台相关的serial文件的probe函数一路调用来的.
我的目前是drivers/serial/msm_serial_hs_lite.c,console对应的设备文件是ttyHSL0.
从0秒到0.5秒之间打印了很多log,大约有8.6K,它们放在了LOG_BUF中,直到调用register_console,屏幕才显示出
log.不信你可以把register_console函数注释掉,你会发现除了bootloader的log,kernel的串口log是看不到的.
所以我们习惯定义CONFIG_DEBUG_LL来看最开始打印了什么东西,调试机器起不来的问题.
kernel和lk大同小异,最终会调用到:
static void msm_hsl_console_putchar(struct uart_port *port, int ch)
{
wait_for_xmitr(port, UARTDM_ISR_TX_READY_BMSK);
msm_hsl_write(port, 1, UARTDM_NCF_TX_ADDR);
while (!(msm_hsl_read(port, UARTDM_SR_ADDR) & UARTDM_SR_TXRDY_BMSK)) {
udelay(1);
touch_nmi_watchdog();
}
msm_hsl_write(port, ch, UARTDM_TF_ADDR);
}
static inline void msm_hsl_write(struct uart_port *port,
unsigned int val, unsigned int off)
{
iowrite32(val, port->membase + off);
}
代码很明了,无需解释了.
PC端的逻辑
---------------
uart_port用于描述一个UART端口(直接对应于一个串口)的I/O端口或I/O内存地址、FIFO大小、端口类型等信息.
每一个/dev/ttyS*对应一个uart_port. 添加1个端口用封装了tty_register_device的uart_add_one_port函数.
可见uart驱动继承自tty驱动,因为uart也是tty的一种.
tty 硬件收到的数据向上回流通过 tty 驱动, 进入 tty 线路规程驱动, 再进入 tty 核心, 在这里它被一个用户获取.
tty 驱动从未看见 tty 线路规程. 这个驱动不能直接和线路规程通讯, 它甚至也不知道它存在. 驱动的工作是以硬件能够
理解的方式格式化发送给它的数据, 并且从硬件接收数据. tty 线路规程的工作是以特殊的方式格式化从一个用户或者硬件
收到的数据. 这种格式化常常采用一个协议转换的形式, 例如 PPP 和 Bluetooth.
tty driver竟然没有提供read函数. tty device的数据被保存在struct tty_flip_buffer中. tty核心负责缓冲数据
直到它被用户请求.
用户空间的程序直接对tty核心层进行读写等相关操作在tty_io.c中. tty core的read/write操作会调用到ldisc的
read/write. 即n_tty_read-->copy_from_read_buf-->read_buf[]. struct tty_ldisc_N_TTY就是线路规程
结构体,其中的read成员变量指向n_tty_read函数.
tty_buffer_init-->INIT_DELAYED_WORK(&tty->buf.work,flush_to_ldisc)
tty_flip_buffer_push-->schedule_delayed_work(&tty->buf.work, 1)
flush_to_ldisc-->receive_buf-->n_tty_receive_buf-->memcpy(tty->read_buf + tty->read_head, cp, i)
看到了吗,最终数据保存到了read_buf,正好接应了上面的分析.
那么tty_flip_buffer_push是在哪调用的呢? 下面是8250(常用的中断控制器)的调用流程:
serial8250_startup-->serial_link_irq_chain-->request_irq(up->port.irq, serial8250_interrupt,
irq_flags, "serial", i)-->serial8250_interrupt-->serial8250_handle_port-->receive_chars-->tty_flip_buffer_push
但是对于usb2uart来说是怎么处理的呢?
usb_serial_init-->usb_serial_generic_register-->usb_serial_register-->fixup_generic
fixup_generic函数初始化了struct usb_serial_driver *device. 这个结构体的read_bulk_callback会被用到.
read_bulk_callback = usb_serial_generic_read_bulk_callback,
process_read_urb = usb_serial_generic_process_read_urb,
read_bulk_callback-->process_read_urb-->tty_flip_buffer_push,接下来的流程上面已经分析了.
在usb_serial_probe中有一句:
usb_fill_bulk_urb(port->read_urb, dev,
usb_rcvbulkpipe(dev,
endpoint->bEndpointAddress),
port->bulk_in_buffer, buffer_size,
serial->type->read_bulk_callback, port);
read_bulk_callback什么时候调用还不太清楚,不过可以猜测一下,应该是usb core负责数据的探测,如果探测到有数据那么就调用此函数?.
到此分析了uart设备如何吐数据到串口,以及PC机如何通过usb转串口线把数据取过来. 剩下显示到屏幕上就好办了,就拿minicom来说吧,
minicom就是上面提到的用户空间程序,不外乎open /dev/ttyUSB0, 然后read,这个read操作最终会取上面read_buf[]中的数据,然后write到/dev/console.
在ARM中打印log可以这样,注意不能随便添加,不要破坏上下文所用的寄存器:
---------
b __printfascii_test
__printfascii_test:
mov r10, r0
adr r0, str_1
bl printascii
ldr r0, =0x01
bl printhex2
adr r0, str_2
bl printascii
mov r0, r10
str_1: .asciz "\n ==I have "
str_2: .asciz " iphone5==\n"
.align
==I have 01 iphone5==
---------
ioremap/request_region到底什么时候用?
request_region 申请io端口用
ioport_map /* 给x86用的 */
request_mem_region 申请io内存用
ioremap /* 在ARM中只用这个就可以了,因为IO端口和IO内存都当作IO内存来看待 */
board 里的 resourcemem/resourceio什么情况下用?
网上不好搜到啊,目前的理解是在arm架构下,用resource mem就OK,没io什么事.
地址总线,物理地址,外设,主存的关系
最近看串口,发现往一个地址上写东西就OK了,想看看是怎么回事.
先摘一段话,来自
从CPU连出来一把线:数据总线、地址总线、控制总线,这把线上挂着N个接口,有相同的,有不同的,名字叫做存储器接口、中断控制接口、DMA接口、并行接口、串行接口、AD接口……一个设备要想接入,就用自己的接口和总线上的某个匹配接口对接……于是总线上出现了各种设备:内存、硬盘,鼠标、键盘,显示器……
对于CPU而言,如果它要发数据到某个设备,其实是发到对应的接口,接口电路里有多个寄存器(也称为端口),访问设备实际上是访问相关的端口,所有的信息会由接口转给它的设备。那么CPU会准备数据到数据总线,但是诸多接口,该发给谁呢?这时就须要为各接口分配一个地址,然后把地址放在地址总线上,需要的控制信息放到控制总线上,就可以和设备通信了。
对一个系统而言,通常会有多个外设,每个外设的接口电路中,又会有多个端口,每个端口都需要一个地址,为他们标识一个具体的地址值,是系统必须解决的事,与此同时,你还有个内存条,可能是512M或1G或更大的金士顿、现代DDR2之类,他们的每一个地址也都需要分配一个标识值,另外,很多外设有自己的内存、缓冲区,就像你的内存条一样,你同样需要为它们分配内存……你的CPU可能需要和它们的每一个字节都打交道,所以:别指望偷懒,它们的每一寸土地都要规划好!这听起来就很烦,做起来可能就直接导致脑细胞全部阵亡。但事情总是得有人去做,ARM可能会这样做:他这次设计的CPU是32位的,最多也就能寻址2^32=4G空间,于是把这4GB空间丢给内存和端口,让他们瓜分。但英特尔或许有更好的分配方式……
这篇文章写的很好,但是这段话之后就看不太懂了.我把自己的理解写下来.
我感觉没那么多叫法,总线地址,内存地址,存储地址等等. 需要用到的叫法只有:物理地址,虚拟地址.
以前理解的物理地址就是对应内存上的地址,有了物理地址,不用说,那一定是访问内存了.可能这样理解是不对的.正如文章所说,物理地址即CPU地址总线传来的地址.这个地址不一定是内存上的一个地址,有可能是外设的.
因为主存和外设对cpu来说都是外设,在arm中,文章中已经说过了,外设和主存瓜分物理地址,这也就是arm的统一编址.所以在LK中直接写一个地址就访问到了串口.
x86是独立编址.外设和主存不瓜分物理地址.物理地址全部给主存用.x86另外用IO地址供外设用.这两种地址都发到地址线上,我怎么知道你要访问主存还是外设呢? x86就设立了专门的指令来访问IO.这样就实现了对不同设备(主存和外设)的寻址.