一.系统理论
PC机南桥的LPC总线(LowPin Count并行总线,代替以前的ISA总线)上挂接了一个超级I/O模块,而UART是这个超级模块芯片组的一部分,这个UART通过RS232线程转换与串行端口相连。与RS232不同,RS485并不是标准的PC接口,但在嵌入式领域,会为了可靠通信而使用RS485,RS485使用差分信号,因此其传输距离可以达到数百米,而RS232传输距离仅数几米,在处理器一端,RS485接口是半双工的UART操作。
Linux包含如下几种终端设备:串行端口终端(/dev/ttySn)、伪终端(/dev/pty)、控制终端(/dev/tty)、控制台终端(/dev/ttyn,/dev/conslole)。串行端口终端使用的设备名为/dev/ttyS0,/dev/ttyS1等,对应的设备号为(4,0),(4,1)。通过查看/proc/tty/drivers文件可以知道什么类型的tty设备存在以及什么驱动被加载到内核,这个文件包括一个当前存在的不同tty驱动的列表,包括驱动名,缺省的节点名,驱动的主编号,驱动的次编号范围,以及tty驱动的类型。
I/O系统调用是从带有线路规程的TTYI/O核心开始,然后通过TTY层,最后到达UART驱动层。主要涉及串口内核配置、UART层内核代码、TTY层内核代码、线路规程内核代码、串口测试代码五个部分。
二.串口内核配置
对于Mini2440串口驱动,我想从配置开始讲起。在内核中Kconfig必须完成一层层调用,如果没有在上一个Kconfig中调用该层Kconfig,那么该层Kconfig中的内容不会在此出现。这种情况下,只有当该层的Kconfig被其他层调用,该层Kconfig中的内容才会被显示。所以我们找找drivers/serial/Kconfig在哪里被调用的呢?
在/drivers/char/kconfig中可以看到一行代码source"drivers/serial/Kconfig",那我们就到drivers/serial/Kconfig下看看
Samsung SoC serialsupport对应于samsung.oserial_core.o
configSERIAL_SAMSUNG
tristate "Samsung SoC serial support"
depends on ARM && PLAT_S3C
select SERIAL_CORE
Support for consoleon Samsung SoC serial port对应于控制台驱动
SamsungS3C2440/S3C2442 Serial port support对应于s3c2440.o
在/drivers/char/Makefile中可以看到
obj-y+= mem.o random.o tty_io.o n_tty.o tty_ioctl.o tty_ldisc.otty_buffer.o tty_port.o
我们知道tty_io.on_tty.otty_ioctl.otty_ldisc.otty_buffer.otty_port.o已编入内核
自此,我们知道关于串口驱动,我们内核中被编译了s3c2440.osamsung.o serial_core.otty_io.o n_tty.otty_ioctl.otty_ldisc.otty_buffer.otty_port.o
我们对此进行分类,属于UART层的是s3c2440.osamsung.o;属于TTY层的是serial_core.o;属于线路规程的是tty_io.on_tty.otty_ioctl.otty_ldisc.otty_buffer.otty_port.o 。
好了,对于串口的地图我们已经分析好了,那我们就按照UART层,TTY层,线路规程一个个的逛逛吧。
三.UART层内核代码
我们先看看samsung.o的init代码吧,这里面完成了uart_driver的注册
static int __inits3c24xx_serial_modinit(void)
{
int ret;
ret =uart_register_driver(&s3c24xx_uart_drv);//注册uart_driver
if (ret < 0) {
printk(KERN_ERR "failed to register UART drivern");
return -1;
}
return 0;
}
static structuart_driver s3c24xx_uart_drv = {
.owner=THIS_MODULE,
.dev_name= "s3c2410_serial",//设备名
.nr= CONFIG_SERIAL_SAMSUNG_UARTS,//UART端口个数
.cons= S3C24XX_SERIAL_CONSOLE, //指向控制台结构
.driver_name =S3C24XX_SERIAL_NAME,//驱动的名字
.major= S3C24XX_SERIAL_MAJOR,//串口主设备号
.minor= S3C24XX_SERIAL_MINOR,//串口次设备号
};
我们关注下上面这个结构体中一个成员S3C24XX_SERIAL_CONSOLE
#defineS3C24XX_SERIAL_CONSOLE &s3c24xx_serial_console
static structconsole s3c24xx_serial_console = {
.name= S3C24XX_SERIAL_NAME,
.device= uart_console_device,
.flags= CON_PRINTBUFFER,
.index= -1,
.write= s3c24xx_serial_console_write,
.setup= s3c24xx_serial_console_setup
};
上面是控制台的结构体成员。
对于UART驱动,我们除了需要注册uart_driver外,还需要注册端口,我们看看s3c2440.o。
这个文件里面注册了一个平台设备,其中平台设备的探测函数最终调用了samsung.o中的s3c24xx_serial_probe函数。
ints3c24xx_serial_probe(struct platform_device *dev,
struct s3c24xx_uart_info *info)
{
struct s3c24xx_uart_port *ourport;
int ret;
dbg("s3c24xx_serial_probe(%p, %p) %dn", dev, info,probe_index);
ourport = &s3c24xx_serial_ports[probe_index];//选择s3c24xx_uart_port
probe_index++; //索引号自增
dbg("%s: initialising port %p...n", __func__, ourport);
ret = s3c24xx_serial_init_port(ourport, info,dev); //初始化串口
if (ret < 0)
goto probe_err;
dbg("%s: adding portn", __func__);
uart_add_one_port(&s3c24xx_uart_drv, &ourport->port);//向内核注册端口
platform_set_drvdata(dev, &ourport->port);//设置私有数据
ret = device_create_file(&dev->dev,&dev_attr_clock_source); //添加设备属性
if (ret < 0)
printk(KERN_ERR "%s: failed to add clksrc attr.n",__func__);
ret = s3c24xx_serial_cpufreq_register(ourport);//注册CPU频率
if (ret < 0)
dev_err(&dev->dev, "failed to add cpufreqnotifiern");
return 0;
probe_err:
return ret;
}
通过上面的函数,我们发现在UART层,我们调用了uart_add_one_port函数完成端口的添加,我们来看看添加了什么端口呢?
static structs3c24xx_uart_port s3c24xx_serial_ports[CONFIG_SERIAL_SAMSUNG_UARTS]= {
[0] = {
.port = {
.lock=__SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[0].port.lock),
.iotype=UPIO_MEM,
.irq= IRQ_S3CUART_RX0,
.uartclk = 0,
.fifosize = 16,
.ops=&s3c24xx_serial_ops,//对UART操作的函数
.flags= UPF_BOOT_AUTOCONF,
.line= 0,
}
},
[1] = {
.port = {
.lock=__SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[1].port.lock),
.iotype= UPIO_MEM,
.irq= IRQ_S3CUART_RX1,
.uartclk = 0,
.fifosize = 16,
.ops= &s3c24xx_serial_ops,//对UART操作的函数
.flags= UPF_BOOT_AUTOCONF,
.line= 1,
}
},
#ifCONFIG_SERIAL_SAMSUNG_UARTS > 2
[2] = {
.port = {
.lock=__SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[2].port.lock),
.iotype= UPIO_MEM,
.irq= IRQ_S3CUART_RX2,
.uartclk = 0,
.fifosize = 16,
.ops=&s3c24xx_serial_ops,//对UART操作的函数
.flags= UPF_BOOT_AUTOCONF,
.line= 2,
}
},
#endif
#ifCONFIG_SERIAL_SAMSUNG_UARTS > 3
[3] = {
.port = {
.lock=__SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[3].port.lock),
.iotype= UPIO_MEM,
.irq= IRQ_S3CUART_RX3,
.uartclk = 0,
.fifosize = 16,
.ops=&s3c24xx_serial_ops,//对UART操作的函数
.flags= UPF_BOOT_AUTOCONF,
.line= 3,
}
}
#endif
};
在端口的定义中,我们知道s3c24xx_uart_port中定义了一个uart_port结构体,继续跟踪对UART的操作函数
static structuart_ops s3c24xx_serial_ops = {
.pm= s3c24xx_serial_pm,
.tx_empty= s3c24xx_serial_tx_empty, //发送是否忙
.get_mctrl= s3c24xx_serial_get_mctrl,
.set_mctrl= s3c24xx_serial_set_mctrl,
.stop_tx =s3c24xx_serial_stop_tx,
.start_tx =s3c24xx_serial_start_tx,//类似于write
.stop_rx =s3c24xx_serial_stop_rx,
.enable_ms= s3c24xx_serial_enable_ms,
.break_ctl= s3c24xx_serial_break_ctl,
.startup =s3c24xx_serial_startup, //类似于open
.shutdown= s3c24xx_serial_shutdown,//类似于close
.set_termios= s3c24xx_serial_set_termios, //设置线路规程
.type= s3c24xx_serial_type,
.release_port =s3c24xx_serial_release_port, //释放端口资源
.request_port =s3c24xx_serial_request_port, //申请端口资源
.config_port= s3c24xx_serial_config_port, //配置端口
.verify_port= s3c24xx_serial_verify_port,
};
对于上述uart_ops函数,我们需要自己去实现uart层的具体操作。
我们在UART层主要涉及uart_driver,uart_port,uart_ops三个结构体,并调用tty层的uart_register_driver和uart_add_one_port完成驱动和端口的注册,UART层具体操作函数需要用户自己设计。
好了,总结下UART驱动层需要完成的任务:
其一,定义uart_driver、uart_ops、uart_port等结构体的实例并在适当的地方根据具体硬件和驱动的情况初始化它们,当然具体设备XXX的驱动可以将这些结构套在新定义的XXX_uart_driver、XXX_uart_ops、XXX_uart_port之内。
其二,在模块初始化时调用uart_register_driver()和uart_add_one_port()以注册UART驱动并添加端口,在模块卸载时调用uart_unregister_driver()和uart_remove_one_port()以注销UART驱动并移除端口。
其三,根据具体硬件的datasheet实现uart_ops中的成员函数,这些函数的实现成为UART驱动的主体工作。