// 首先系统启动时, 函数指针会调用con_init(), 代码在drivers/tty/vt/vt.c,函数指针代码如下: /* * Initialize the console device. This is called *early*, so * we can't necessarily depend on lots of kernel help here. * Just do some early initializations, and do the complex setup * later. */ void __init console_init(void) { initcall_t *call; /* Setup the default TTY line discipline. */ tty_ldisc_begin(); /* * set up the console device so that later boot sequences can * inform about problems etc.. */ call = __con_initcall_start; while (call < __con_initcall_end) { (*call)(); call++; } } // 对于我使用的pxa平台pxa.c中的uart驱动并未使用console_initcall(serial_pxa_init);而是用了下面的方式, 如下: int __init serial_pxa_init(void) { int ret; ret = uart_register_driver(&serial_pxa_reg); if (ret != 0) return ret; ret = platform_driver_register(&serial_pxa_driver); if (ret != 0) uart_unregister_driver(&serial_pxa_reg); return ret; } void __exit serial_pxa_exit(void) { platform_driver_unregister(&serial_pxa_driver); uart_unregister_driver(&serial_pxa_reg); } module_init(serial_pxa_init); /* 此方式为常规的设备驱动模块注册,所以按照这个段的链接顺序遍历函数指针并后续探测设备. 下面我们具体看下log信息: [ 0.000000] ----------console_init---------- [ 0.000000] -------func pointer run-------start= c0019848, end= (null) 再看下地址0xc0019848的函数是什么,利用readelf(arm的)来看下是什么? 如下: 33064: c0019848 612 FUNC LOCAL DEFAULT 1 con_init 这样其实这里只有一个函数,就是con_init了. 然后到uart正真注册的时候将会把他替换掉,如下: serial_pxa_probe ---> uart_add_one_port ---> uart_configure_port ---> register_console 这样uart就注册上了,其他tty相关的这里就不讲述了. 下面我们看看上层printf打印驱动中所需要的函数部分: */ struct uart_ops serial_pxa_pops = { .tx_empty = serial_pxa_tx_empty, .set_mctrl = serial_pxa_set_mctrl, .get_mctrl = serial_pxa_get_mctrl, .stop_tx = serial_pxa_stop_tx, .start_tx = serial_pxa_start_tx, .stop_rx = serial_pxa_stop_rx, .enable_ms = serial_pxa_enable_ms, .break_ctl = serial_pxa_break_ctl, .startup = serial_pxa_startup, .shutdown = serial_pxa_shutdown, .set_termios = serial_pxa_set_termios, .pm = serial_pxa_pm, .type = serial_pxa_type, .release_port = serial_pxa_release_port, .request_port = serial_pxa_request_port, .config_port = serial_pxa_config_port, .verify_port = serial_pxa_verify_port, }; /* 这个便是驱动代码给上面注册的函数了, 先看下第一个函数吧:serial_pxa_startup*/ static int serial_pxa_startup(struct uart_port *port) { struct uart_pxa_port *up = (struct uart_pxa_port *)port; unsigned long flags; int retval; if (port->line == 3) /* HWUART */ up->mcr |= UART_MCR_AFE; else up->mcr = 0; up->port.uartclk = clk_get_rate(up->clk); /* * Allocate the IRQ */ retval = request_irq(up->port.irq, serial_pxa_irq, 0, up->name, up); if (retval) return retval; /* * Clear the FIFO buffers and disable them. * (they will be reenabled in set_termios()) */ serial_out(up, UART_FCR, UART_FCR_ENABLE_FIFO); serial_out(up, UART_FCR, UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT); serial_out(up, UART_FCR, 0); /* * Clear the interrupt registers. */ (void) serial_in(up, UART_LSR); (void) serial_in(up, UART_RX); (void) serial_in(up, UART_IIR); (void) serial_in(up, UART_MSR); /* * Now, initialize the UART */ serial_out(up, UART_LCR, UART_LCR_WLEN8); spin_lock_irqsave(&up->port.lock, flags); up->port.mctrl |= TIOCM_OUT2; serial_pxa_set_mctrl(&up->port, up->port.mctrl); spin_unlock_irqrestore(&up->port.lock, flags); /* * Finally, enable interrupts. Note: Modem status interrupts * are set via set_termios(), which will be occurring imminently * anyway, so we don't enable them here. */ if (up->dma_enable) { uart_pxa_dma_init(up); up->rx_stop = 0; pxa_uart_receive_dma_start(up); up->ier = UART_IER_DMAE | UART_IER_UUE | UART_IER_RTOIE; tasklet_init(&up->tklet, uart_task_action, (unsigned long)up); } else { up->ier = UART_IER_RLSI | UART_IER_RDI | UART_IER_RTOIE | UART_IER_UUE; } serial_out(up, UART_IER, up->ier); /* * And clear the interrupt registers again for luck. */ (void) serial_in(up, UART_LSR); (void) serial_in(up, UART_RX); (void) serial_in(up, UART_IIR); (void) serial_in(up, UART_MSR); return 0; } /* 要读明白这块代码首先需要把uart的寄存器部分手册再仔细阅读下,然后再来看会比较好,或者边看代码边看手册,首先得到了uart的时钟,然后请求中断,中断处理函数内容如下:*/ static inline irqreturn_t serial_pxa_irq(int irq, void *dev_id) { struct uart_pxa_port *up = dev_id; unsigned int iir, lsr; iir = serial_in(up, UART_IIR); if (iir & UART_IIR_NO_INT) return IRQ_NONE; /* timer is not active */ if (!mod_timer(&up->pxa_timer, jiffies + PXA_TIMER_TIMEOUT)) { #ifdef CONFIG_PXA95x dvfm_disable_lowpower(up->dvfm_dev_idx[PXA_UART_RX]); #elif defined(CONFIG_WAKELOCK) && defined(CONFIG_CPU_PXA910) wake_lock(&up->idle_lock[PXA_UART_RX]); #else pm_qos_update_request(&up->qos_idle[PXA_UART_RX], PM_QOS_CONSTRAINT); #endif } lsr = serial_in(up, UART_LSR); if (up->dma_enable) { if (UART_LSR_FIFOE & lsr) pxa_uart_receive_dma_err(up, &lsr); if (iir & UART_IIR_TOD) dma_receive_chars(up, &lsr); } else { if (lsr & UART_LSR_DR) receive_chars(up, &lsr); check_modem_status(up); if (lsr & UART_LSR_THRE) { transmit_chars(up); /* wait Tx empty */ while (!serial_pxa_tx_empty(\ (struct uart_port *)dev_id)) ; } } return IRQ_HANDLED; } /* 中断处理函数中先获得中断验证寄存器的内容,然后验证是否有中断等待处理(pending),没有的话直接返回; 假如有的话那么继续下去。 定时器部分就不看了,因为那部分代码我也没太看懂,有高手的话补充下... ... 下面读LSR寄存器(这个寄存器被读数据状态发送状态),所以非常重要,有一些发送过程中的错误在这里一般会有记录。所以下面就是根据一些触发中断后的状态信息去做相应处理了,如下: 假设dma启用的话,先判断是否FIFI错误,手册上说是至少一个校验错误,帧错误,或者是break indication for any of the chars in the FIFO,这个寄存器在所有错误字节被从FIFO中读后才被reset。所以下面就调用相应的错误处理了。这里就不展开了,下面的代码都是一系列的错误处理。 下面再判断是否是FIFO模式下Time out interrupt,是的话那么调用dma_receive_chars,这是启用DMA的情况,那么不启用DMA的情况这个函数变为receive_chars,然后就是调用check_modem_status这个函数了,这个函数其实很简单就是读MSR寄存器,然后检查寄存器的字段是否改变了, 对于CTS字段改变的结果是或者交换数据或者停止,不启用DMA就到这里了。 这个处理函数还有些具体函数被展开,只能以后再慢慢补充。 下面回到serial_pxa_startup这个函数,注册中断完事后,下面注释里面也讲的很清楚了,然后再清除中断寄存器,再然后就是这个函数serial_out(up, UART_LCR, UART_LCR_WLEN8);了,他很简单就是指定word length select,有7位和8位,这里指定了8位。下面明天再续了,今天没时间写了。 */