UART 串口驱动开发文档
-------w83697/w83977 super I/O 串口驱动开发
文档作者: 侯辉华
部 门:
时 间: 2007/08/12
内容简介: 介绍了 Linux 下的串口驱动的设计层次及接口, 并指出串口与 TTY 终端之间的关
联层次(串口可作 TTY 终端使用), 以及 Linux 下的中断处理机制/中断共享机制, 还有串口缓
冲机制当中涉及的软中断机制; 其中有关 w83697/w83977 IC 方面的知识, 具体参考相关手
册, 对串口的配置寄存器有详细介绍, 本文不再进行说明.
目录索引:
一. Linux的串口接口及层次.
二. Linux 的中断机制及中断共享机制.
三.
Linux的软中断机制.
四.
TTY与串口的具体关联.
五.
串口使用示例说明.
1
一. Linux 的串口接口及层次.
串口是使用已经非常广的设备了, 因此在 linux 下面的支持已经很完善了, 具有统一
的编程接口, 驱动开发者所要完整的工作就是针对不同的串口 IC 来做完成相应的配置
宏, 这此配置宏包括读与写, 中断打开与关闭(如传送与接收中断), 接收状态处理, 有
FIFO 时还要处理 FIFO 的状态. 如下我们就首先切入这一部分, 具体了解一下与硬件串
口 IC相关的部分在驱动中的处理, 这一部分可以说是串口驱动中的最基础部分, 直接与
硬件打交道, 完成最底层具体的串口数据传输.
1. 串口硬件资源的处理.
W83697 及 W83977 在 ep93xx 板子上的映射的硬件物理空间如下:
W83697: 0x20000000 起 1K 空间.
W83977: 0x30000000 起 1K 空间.
因为串口设备的特殊性, 可以当作终端使用, 但是终端的使用在内核还未完全初始
化之前(关于串口与终端的关联及层次在第四节中详细), 此时还没有通过 mem_init()
建立内核的虚存管理机制, 所以不能通过 ioreamp 来进行物理内存到虚存的映射(物
理内存必须由内核映射成系统管理的虚拟内存后才能进行读写访问), 这与先前所
讲的 framebuffer 的物理内存映射是不同的, 具体原因如下:
终端在注册并使用的调用路径如下:
start_kernelconsole_inituart_console_initep93xxuart_console_initregister_cons
olecsambuart_console_write.
FrameBuffer 显卡驱动中的物理内存映射调用路径如下:
start_kernel rest_initinit(内核初始线程) do_basic_setup
do_initcallsfbmem_initlanrryfb_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
2
#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-ep93xx\arch.c 文件中如下宏完成此结构的初始化:
MACHINE_START(EDB9302, "edb9302")
…..
MAPIO(ep93xx_map_io) //初始化. map_io= ep93xx_map_io….
MACHINE_END
最终这个函数在调用路径如下:
start_kernelsetup_archpaging_init(mdesc->map_io())
至此完成串口物理内存的映射,这个过程在 console_init 调用之前,因此不会有问
题, 此种方法建立映射是直接创建物理内存页与虚存页的对应,大小为 4k 一页,最
终调用的是 create_mapping(), 建立页表映射是与具体的平台相关的,位于
mach_ep93xx/mm/ proc-arm920.S 文件中提供了与具体平台相关的页表建立函数,其
中包括 TLB 表操作/Cache 操作/页表操作等:
在上层的 start_kernelsetup_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
3
.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
口,他们之间除了对应的中断号/寄存器起始基址/次设备号不同之外,其它的参数
基本相同.
4
串口对应中断, 这里六个串口,其中有 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 下面详述.
5