主页 > 知识库 > 服务器 > Linux/BSD >

linux UART串口驱动开发文档

来源:中国IT实验室 作者:佚名 发表于:2012-11-15 13:36  点击:
w83697/w83977 super I/O串口驱动开发 内容简介: 介绍了Linux下的串口驱动的设计层次及接口, 并指出串口与TTY终端之间的关联层次(串口可作TTY终端使用), 以及Linux下的中断处理机制/中断共享机制, 还有串口缓冲机制当中涉及的软中断机制; 其中有关w836
w83697/w83977 super I/O串口驱动开发    内容简介: 介绍了Linux下的串口驱动的设计层次及接口, 并指出串口与TTY终端之间的关联层次(串口可作TTY终端使用), 以及Linux下的中断处理机制/中断共享机制, 还有串口缓冲机制当中涉及的软中断机制; 其中有关w83697/w83977 IC方面的知识, 具体参考相关手册, 对串口的配置寄存器有详细介绍, 本文不再进行说明。
    目录索引:
    一。 Linux的串口接口及层次。
    二。 Linux的中断机制及中断共享机制。
    三。 Linux的软中断机制。
    四。 TTY与串口的具体关联。
    一。 Linux的串口接口及层次。
    串口是使用已经非常广的设备了, 因此在linux下 面的支持已经很完善了, 具有统一的编程接口, 驱动开发者所要完整的工作就是针对不同的串口IC来做完成相应的配置宏, 这此配置宏包括读与写, 中断打开与关闭(如传送与接收中断), 接收状态处理, 有FIFO时还要处理FIFO的状态。 如下我们就首先切入这一部分, 具体了解一下与硬件串口IC相关的部分在驱动中的处理, 这一部分可以说是串口驱动中的最基础部分, 直接与硬件打交道, 完成最底层具体的串口数据传输。
    1. 串口硬件资源的处理。
    W83697及W83977在ep93xx板子上的映射的硬件物理空间如下:
    W83697: 0x20000000起1K空间。
    W83977: 0x30000000起1K空间。
    因为串口设备的特殊性, 可以当作终端使用, 但是终端的使用在内核还未完全初始化之前(关于串口与终端的关联及层次在第四节中详细), 此时还没有通过mem_init()建立内核的虚存管理机制, 所以不能通过ioreamp来进行物理内存到虚存的映射(物理内存必须由内核映射成系统管理的虚拟内存后才能进行读写访问), 这与先前所讲的framebuffer的物理内存映射是不同的, 具体原因如下:
    √终端在注册并使用的调用路径如下:
    start_kernel→console_init→uart_console_init→ep93xxuart_console_init→register_console→csambuart_console_write.
    √FrameBuffer显卡驱动中的物理内存映射调用路径如下:
    start_kernel→ rest_init→init(内核初始线程)→ do_basic_setup→ do_initcalls→fbmem_init→lanrryfb_init
    (Linux下用__setup启动初期初始机制与__initcall系统初始化完成后的调用机制, 这两个机制本质没有什么差别,主要是执行时所处的系统时段)
    √串口物理内存映射到虚存的时机:
    依据上面所介绍的两条执行路径,再看内核的内存初始化的调用时期,只有完成这个初始化后才能进行物理内存到虚存的映射,内存的初始化主要是在 start_kernel中调用的mem_init,这个调用明显在uart_console_init之后,在fbmem_init之后,到此就全部说 明了为何不能在对串口使用ioremap进行物理内存的映射了。那么究竟要在什么时机用什么方法进行串口物理内存的映射呢?
    √串口物理内存的映射方式:
    参考ep93xx的板载I/O的映射处理,它的处理方式是一次性将所有的物理I/O所在的内存空间映射到虚存空间,映射的基址是IO_BASE_VIRT,大小是IO_SIZE.
    /* Where in virtual memory the IO devices (timers, system controllers
    * and so on)。 This gets used in arch/arm/mach-ep93xx/mm.c.*/
    #define IO_BASE_VIRT 0xFF000000 // Virtual address of IO
    #define IO_BASE_PHYS 0x80000000 // Physical address of IO
    #define IO_SIZE 0x00A00000 // How much?
    完成映射的函数是ep93xx_map_io, 所有要进行映射内存都在ep93xx_io_desc结构当中描述,我们的串口映射也加在这个地方,基址分别如下:
    文件: linux-2.4.21/include/asm-arm/arch-ep93xx/regmap.h
    #define IO_W83697_UART_BASE 0x20000000
    #define IO_W83697_UART_SIZE 0x1000
    #define IO_W83977_UART_BASE 0x30000000
    #define IO_W83977_UART_SIZE 0x1000
    #define IO_SIZE_2 (IO_SIZE+0x100000)
    #define IO_BASE83697_VIRT IO_BASE_VIRT+IO_SIZE
    #define IO_BASE83977_VIRT IO_BASE_VIRT+IO_SIZE_2
    ep93xx_map_io完成是在arch初始化中赋值给struct machine_desc mdesc这个机器描述结构体,主要由位于mach-ep93xxarch.c文件中如下宏完成此结构的初始化:
    MACHINE_START(EDB9302, “edb9302”)
    ……
    MAPIO(ep93xx_map_io) //初始化。 map_io= ep93xx_map_io…
    MACHINE_END
    最终这个函数在调用路径如下:
    start_kernel→setup_arch→paging_init→(mdesc->map_io())
    至此完成串口物理内存的映射,这个过程在console_init调用之前,因此不会有问题, 此种方法建立映射是直接创建物理内存页与虚存页的对应,大小为4k一页,最终调用的是create_mapping(), 建立页表映射是与具体的平台相关的,位于mach_ep93xx/mm/ proc-arm920.S文件中提供了与具体平台相关的页表建立函数,其中包括TLB表操作/Cache操作/页表操作等:
    在上层的start_kernel→setup_arch→ setup_processor调用下,会在proc-arm920.S文件中查找“.proc.info”节的__arm920_proc_info, 并从中找到配置的process相关的操作函数,具体的arm页表建立的详情须要参看arm内存管理的相关手册。
    .section “.proc.info”, #alloc, #execinstr
    .type __arm920_proc_info,#object
    __arm920_proc_info:
    .long 0x41009200
    ……
    .long arm920_processor_functions
    .size __arm920_proc_info, . - __arm920_proc_info
    在arm920_processor_functions中包含的页表操作如下:
    /* pgtable */
    .word cpu_arm920_set_pgd
    .word cpu_arm920_set_pmd
    .word cpu_arm920_set_pte
    2. 与串口硬件相关的宏主。
    如下, 下面将详术如下, 并指出其具体被使用的环境上下文:
    <1>. 读写数据。
    #define UART_GET_CHAR(p) ((readb((p)->membase + W83697_UARTDR)) & 0xff)
    #define UART_PUT_CHAR(p, c) writeb((c), (p)->membase + W83697_UARTDR)
    <2>. 接收发送状态。
    #define UART_GET_RSR(p) ((readb((p)->membase + W83697_UARTRSR)) & 0xff)
    #define UART_PUT_RSR(p, c) writeb((c), (p)->membase + W83697_UARTRSR)
    <3>. 发送及接收中断状态。
    #define UART_GET_CR(p) ((readb((p)->membase + W83697_UARTCR)) & 0xff)
    #define UART_PUT_CR(p,c) writeb((c), (p)->membase + W83697_UARTCR)
    #define UART_GET_INT_STATUS(p) ((readb((p)->membase + W83697_UARTIIR)) & 0xff)
    <4>. 以及其它的中断使能设置等, 在传送时打开传送中断即会产生传送中断。
    #define UART_PUT_ICR(p, c) writeb((c), (p)->membase + W83697_UARTICR)
    <5>. FIFO的状态, 是否读空/是否写满; 每次读时必须读至FIFO空, 写时必须等到FIFO不满时才能写(要等硬件传送完) .
    接收中断读空FIFO的判断:
    status = UART_GET_FR(port);
    while (UART_RX_DATA(status) && max_count--) {
    ……
    }
    发送中断写FIFO: 当发送缓冲区中有数据要传送时, 置发送中断使能, 随后即产生传送中断, 此时FIFO为空, 传送半个FIFO大小的字节, 如果发送缓冲区数据传完,则关闭发送中断。
    <6>. 传送时可直接写串口数据口, 而不使用中断, 但必须等待检测FIFO的状态
    do {
    status = UART_GET_FR(port);
    } while (!UART_TX_READY(status)); //wait for tx buffer not full…
    3. 串口驱动的参数配置
    串口的参数主要包括如下几个参数,全部都记录在uart_port结构上,为静态的赋值,本串口驱动支持6个设备,所以驱动中就包括了6个port,一个串口对应一个port口,他们之间除了对应的中断号/寄存器起始基址/次设备号不同之外,其它的参数基本相同。
    √串口对应中断, 这里六个串口,其中有3个串口使用的系统外部中断0/1/2, 其中另外几个中断用提GPIO中断,具体有关GPIO中断的内容可参见EP93XX芯片手册, GPIO中断共享一个系统中断向量,涉及中断共享的问题,后面将详述LINUX中的中断共享支持。
    √串口时钟, 串口时钟用来转换计算须要设置到配置寄存器当中的波特率比值,其计算方法为:quot = (port->uartclk / (16 * baud)); baud为当前设置的波特率,可为115200等值, 取决于所选的串口时钟源, quot即为要设置到寄存器当中的比值。
    √串口基址, 即串口所有配置寄存器基础址。
    √串口次设备号(由驱动中的最低次设备号依次累加)
    前面已经讲过了六个串口中断,这里详细列出对应情况如下,方便查找:
    w83697的三个串口对应中断如下:
    uart 1: 读写数据寄存器偏移为00x3F8, 对应系统外部中断INT_EXT[0].
    uart 2: 读写数据寄存器偏移为00x2F8, 对应系统外部中断INT_EXT[1].
    uart 3: 读写数据寄存器偏移为00x3e8, 对应系统外部中断INT_EXT[2].
    uart 4: 读写数据寄存器偏移为00x3e8, 对应EGPIO[8].
    w83977的两个串口对应中断如下:
    uart 1: 读写数据寄存器偏移为00x3F8, 对应EGPIO[1].
    uart 2: 读写数据寄存器偏移为00x2F8, 对应EGPIO[2].
    下面列出其中一个具体的串口port的定义如下:
    {
    .port = {
    .membase = (void *)W83697_UART4_BASE,
    .mapbase = W83697_UART4_BASE,
    .iotype = SERIAL_IO_MEM,
    .irq = W83697_IRQ_UART4, //串口中断号
    .uartclk = 1846100, //uart时钟,默认。
    .fifosize = 8, //硬件fifo大小。
    .ops = &amba_pops, //底层驱动的硬件操作集,如开关中断等。
    .flags = ASYNC_BOOT_AUTOCONF,
    .line = 3, //串口在次设备数组中的索引号,须注意从0计起…
    },
    .dtr_mask = 0,
    .rts_mask = 0,
    }
    4. 串口驱动的底层接口函数
    驱动文件:linux-2.4.21/drivers/serial/Ep93xx_w83697.c
    相关文件: linux-2.4.21/drivers/serial/core.c 下面详述。
    函数: w83697uart_rx_chars(struct uart_port *port, struct pt_regs *regs)
    描述: 串口接收数据中断, 此函数中应当注意的要点如下:
    接收数据时,要注意判断FIFO是否读空(参见上述2点中说明)。
    接收数据放入flip缓冲区,此缓冲区专供缓存中断中接收到的数据,是最原始的串口数据,为更上一层中各种终端处理模式的原始数据,可以进行各种加工处理。
    接收数据到flip缓冲区中时,须根据硬件接收状态,置每一个接收到的字符的接收标志,放在flag_buf_ptr当中, 标志类型有TTY_NORMAL/ TTY_PARITY/ TTY_FRAME等,分别表示正常/校验出错/帧出错(无停止位)等。
    每接收数据之后,会通过在退出中断前调用tty_flip_buffer_push()来往tq_timer任务列表中加一个队列任务,串口的队列任务主 要是负责将中断接收到flip缓冲区中的数据往上传输至终端终冲区, 队列任务的机制将在后面介绍,它是一种异步执行机制,在软中断中触发执行。
    函数: static void w83697uart_tx_chars(struct uart_port *port)
    描述: 串口发送数据中断, 发送中断中要做的事比较少,比起接收中断简单了好多,注意事项如下:
    当上层要发送数据时,就会打开串口发送中断,此时FIFO为空,传送半个FIFO大小数据即退出, 通常打开中断是通过更上一层的uart_flush_chars()调用,最终调用的是w83697uart_start_tx()。
    检测当没有数据要传输的时候,关闭传送中断,在传送之前与传送完之后都有检测。
    最重要的一点是如果传送缓冲区当中的字符数已经小于WAKEUP_CHARS, 则可以唤醒当前正在使用串口进行传送的进程,这里是通过tasklet机制来完成,这也是一异步执行机制。
    顺带介绍开关中断接口:
    static void w83697uart_start_tx(struct uart_port *port, unsigned int tty_start)
    static void w83697uart_stop_tx(struct uart_port *port, unsigned int tty_stop)
    函数: static void w83697uart_int(int irq, void *dev_id, struct pt_regs *regs)

有帮助
(0)
0%
没帮助
(0)
0%