在Linux系统中,终端设备非常重要,没有终端设备,系统将无法向用户反馈信息,Linux中包含控制台、串口和伪终端3类终端设备。
14.1节阐述了终端设备的概念及分类,14.2节给出了Linux终端设备驱动的框架结构,重点描述tty_driver结构体及其成员。14.3~14.5节在14.2节的基础上,分别给出了Linux终端设备驱动模块加载/卸载函数和open()、close()函数,数据读写流程及tty设备线路设置的编程方法。在Linux中,串口驱动完全遵循tty驱动的框架结构,但是进行了底层操作的再次封装,14.6节描述了Linux针对串口tty驱动的这一封装,14.7节则具体给出了串口tty驱动的实现方法。14.8节基于14.6和14.7节的讲解给出了串口tty驱动的设计实例,即S3C2410集成UART的驱动。
14.1终端设备
在Linux系统中,终端是一种字符型设备,它有多种类型,通常使用tty来简称各种类型的终端设备。tty是Teletype的缩写,Teletype是最早出现的一种终端设备,很像电传打字机,是由Teletype公司生产的。Linux中包含如下几类终端设备:
1、串行端口终端(/dev/ttySn)
串行端口终端(Serial Port Terminal)是使用计算机串行端口连接的终端设备。计算机把每个串行端口都看作是一个字符设备。这些串行端口所对应的设备名称是 /dev/ttyS0(或/dev/tts/0)、/dev/ttyS1或/dev/tts/1)等,设备号分别是(4,0)、(4,1)等。
在命令行上把标准输出重定向到端口对应的设备文件名上就可以通过该端口发送数据,例如,在命令行提示符下键入: echo test > /dev/ttyS1会把单词“test”发送到连接在ttyS1端口的设备上。
2.伪终端(/dev/pty/)
伪终端(Pseudo Terminal)是成对的逻辑终端设备,并存在成对的设备文件,如/dev/ptyp3和/dev/ttyp3,它们与实际物理设备并不直接相关。如果一个程序把ttyp3看作是一个串行端口设备,则它对该端口的读/写操作会反映在该逻辑终端设备对应的ptyp3上,而ptyp3则是另一个程序用于读写操作的逻辑设备。这样,两个程序就可以通过这种逻辑设备进行互相交流,使用ttyp3的程序会认为自己正在与一个串行端口进行通信。
以telnet 为例,如果某人在使用telnet程序连接到Linux系统,则telnet程序就可能会开始连接到设备ptyp2上,而此时一个getty程序会运行在对应的ttyp2端口上。当telnet从远端获取了一个字符时,该字符就会通过ptyp2、ttyp2传递给 getty程序,而getty程序则会通过ttyp2、ptyp2和telnet程序返回“login:”字符串信息。这样,登录程序与telnet程序 就通过伪终端进行通信。通过使用适当的软件,可以把2个或多个伪终端设备连接到同一个物理串行端口上。
3.控制台终端(/dev/ttyn, /dev/console)
如果当前进程有控制终端(Controlling Terminal)的话,那么/dev/tty就是当前进程的控制终端的设备特殊文件。可以使用命令“ps –ax”来查看进程与哪个控制终端相连使用命令“tty”可以查看它具体对应哪个实际终端设备。/dev/tty有些类似于到实际所使用终端设备的一个联接。
在UNIX系统中,计算机显示器通常被称为控制台终端(Console)。它仿真了类型为Linux的一种终端(TERM=Linux),并且有一些设备特殊文件与之相关联:tty0、tty1、tty2等。当用户在控制台上登录时,使用的是tty1。使用Alt+[F1—F6]组合键时,我们就可以切换到tty2、tty3等上面去。tty1–tty6等称为虚拟终端,而tty0则是当前所使用虚拟终端的一个别名,系统所产生的信息会发送到该终端上。因此不管当前正在使用哪个虚拟终端,系统信息都会发送到控制台终端上。用户可以登录到不同的虚拟终端上去,因而可以让系统同时有几个不同的会话期存在。只有系统或超级用户root可以向/dev/tty0进行写操作。
在Linux 中,可以在系统启动命令行里指定当前的输出终端,格式如下:
console=device, options
device指代的是终端设备,可以是tty0(前台的虚拟终端)、ttyX(第X个虚拟终端)、ttySX(第X个串口)、lp0(第一个并口)等。options指代对device进行的设置,它取决于具体的设备驱动。对于串口设备,参数用来定义为:波特率、校验位、位数,格式为BBBBPN,其中BBBB表示波特率,P表示校验(n/o/e),N表示位数,默认options是9600n8。
用户可以在内核命令行中同时设定多个终端,这样输出将会在所有的终端上显示,而当用户调用open()打开/dev/console时,最后一个终端将会返回作为当前值。例如:
console=ttyS1, 9600 console=tty0
定义了2个终端,而调用open()打开/dev/console时,将使用虚拟终端tty0。但是内核消息会在tty0 VGA虚拟终端和串口ttyS1上同时显示。
通过查看/proc/tty/drivers文件可以获知什么类型的tty设备存在以及什么驱动被加载到内核,这个文件包括一个当前存在的不同 tty 驱动的列表,包括驱动名、缺省的节点名、驱动的主编号、这个驱动使用的次编号范围,以及 tty 驱动的类型。例如,下面给出了一个/proc/tty/drivers文件的例子:
14.2终端设备驱动结构
Linux内核中 tty的层次结构如图14.1所示,包含tty核心、tty线路规程和tty驱动,tty 线路规程的工作是以特殊的方式格式化从一个用户或者硬件收到的数据,这种格式化常常采用一个协议转换的形式,例如 PPP 和 Bluetooth。tty设备发送数据的流程为:tty核心从一个用户获取将要发送给一个 tty设备的数据,tty核心将数据传递给tty线路规程驱动,接着数据被传递到tty驱动,tty驱动将数据转换为可以发送给硬件的格式。接收数据的流程为: 从tty硬件接收到的数据向上交给tty驱动,进入tty线路规程驱动,再进入 tty 核心,在这里它被一个用户获取。尽管大多数时候tty核心和tty之间的数据传输会经历tty线路规程的转换,但是tty驱动与tty核心之间也可以直接传输数据。
图14.1 tty分层结构
图14.2显示了与tty相关的主要源文件及数据的流向。tty_io.c定义了tty 设备通用的file_operations结构体并实现了接口函数tty_register_driver()用于注册tty设备,它会利用 fs/char_dev.c提供的接口函数注册字符设备,与具体设备对应的tty驱动将实现tty_driver结构体中的成员函数。同时 tty_io.c也提供了tty_register_ldisc()接口函数用于注册线路规程,n_tty.c文件则实现了tty_disc结构体中的成员。
图14.2 tty主要源文件关系及数据流向
从图14.2可以看出,特定tty设备驱动的主体工作是填充tty_driver结构体中的成员,实现其中的成员函数,tty_driver结构体的定义如代码清单14.1。
代码清单14.1 tty_driver结构体
1 struct tty_driver
2 {
3 int magic;
4 struct cdev cdev;
5 struct module *owner;
6 const char *driver_name;
7 const char *devfs_name;
8 const char *name;
9 int name_base;
10 int major;
11 int minor_start;
12 int minor_num;
13 int num;
14 short type;
15 short subtype;
16 struct termios init_termios;
17 int flags;
18 int refcount;
19 struct proc_dir_entry *proc_entry;
20 struct tty_driver *other;
21 ...
22
23 int(*open)(struct tty_struct *tty, struct file *filp);
24 void(*close)(struct tty_struct *tty, struct file *filp);
25 int(*write)(struct tty_struct *tty, const unsigned char *buf, int count);
26 void(*put_char)(struct tty_struct *tty, unsigned char ch);
27 void(*flush_chars)(struct tty_struct *tty);
28 int(*write_room)(struct tty_struct *tty);
29 int(*chars_in_buffer)(struct tty_struct *tty);
30 int(*ioctl)(struct tty_struct *tty, struct file *file, unsigned int cmd,
31 unsigned long arg);
32 void(*set_termios)(struct tty_struct *tty, struct termios *old);
33 void(*throttle)(struct tty_struct *tty);
34 void(*unthrottle)(struct tty_struct *tty);
35 void(*stop)(struct tty_struct *tty);
36 void(*start)(struct tty_struct *tty);
37 void(*hangup)(struct tty_struct *tty);
38 void(*break_ctl)(struct tty_struct *tty, int state);
39 void(*flush_buffer)(struct tty_struct *tty);
40 void(*set_ldisc)(struct tty_struct *tty);
41 void(*wait_until_sent)(struct tty_struct *tty, int timeout);
42 void(*send_xchar)(struct tty_struct *tty, char ch);
43 int(*read_proc)(char *page, char **start, off_t off, int count, int *eof,
44 void *data);
45 int(*write_proc)(struct file *file, const char __user *buffer, unsigned long
46 count, void *data);
47 int(*tiocmget)(struct tty_struct *tty, struct file *file);
48 int(*tiocmset)(struct tty_struct *tty, struct file *file, unsigned int set,
49 unsigned int clear);
50
51 struct list_head tty_drivers;
52 };
tty_driver结构体中的magic表示给这个结构体的“幻数”,设为 TTY_DRIVER_MAGIC,在 alloc_tty_driver()函数中被初始化。name与driver_name的不同在于后者表示驱动的名字,用在 /proc/tty 和 sysfs中,而前者表示驱动的设备节点名。type 与subtype描述tty驱动的类型和子类型,subtype的值依赖于type,type成员的可能值为 TTY_DRIVER_TYPE_SYSTEM(由tty子系统内部使用,subtype 应当设为 SYSTEM_TYPE_TTY、SYSTEM_TYEP_CONSOLE、SYSTEM_TYPE_SYSCONS或 SYSTEM_TYPE_SYSPTMX,这个类型不应当被任何常规tty驱动使用)、TTY_DRIVER_TYPE_CONSOLE(仅被控制台驱动使用)、TTY_DRIVER_TYPE_SERIAL(被任何串行类型驱动使用,subtype 应当设为 SERIAL_TYPE_NORMAL 或SERIAL_TYPE_CALLOUT)、TTY_DRIVER_TYPE_PTY(被伪控制台接口pty使用,此时subtype需要被设置为 PTY_TYPE_MASTER 或 PTY_TYPE_SLAVE)。init_termios 为初始线路设置,为一个termios结构体,这个成员被用来提供一个线路设置集合。termios 用于保存当前的线路设置,这些线路设置控制当前波特率、数据大小、数据流控设置等,这个结构体包含tcflag_t c_iflag(输入模式标志)、tcflag_t c_oflag(输出模式标志)、tcflag_t c_cflag(控制模式标志)、tcflag_t c_lflag(本地模式标志)、cc_t c_line(线路规程类型)、cc_t c_cc[NCCS](一个控制字符数组)等成员。驱动会使用一个标准的数值集初始化这个成员,它拷贝自tty_std_termios变量,tty_std_termos在tty核心中的定义如代码清单14.2。
代码清单14.2 tty_std_termios变量
1 struct termios tty_std_termios =
2 {
3 .c_iflag = ICRNL | IXON,
4 .c_oflag = OPOST | ONLCR,
5 .c_cflag = B38400 | CS8 | CREAD | HUPCL,
6 .c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK |
7 ECHOCTL | ECHOKE | IEXTEN,
8 .c_cc = INIT_C_CC
9 };
tty_driver 结构体中的major、minor_start、minor_num表示主设备号、次设备号及可能的次设备数,name表示设备名(如ttyS),第 23~49行的函数指针实际和tty_operations结构体等同,它们通常需在特定设备tty驱动模块初始化函数中被赋值。put_char()为单字节写函数,当单个字节被写入设备时这个函数被 tty 核心调用,如果一个 tty 驱动没有定义这个函数,将使用count参数为1的write()函数。flush_chars()与wait_until_sent()函数都用于刷新数据到硬件。write_room()指示有多少缓冲区空闲,chars_in_buffer()指示缓冲区中包含的数据数。当 ioctl(2)在设备节点上被调用时,ioctl()函数将被 tty核心调用。当设备的 termios 设置被改变时,set_termios()函数将被tty核心调用。throttle ()、unthrottle()、stop()和start()为数据抑制函数,这些函数用来帮助控制 tty 核心的输入缓存。当 tty 核心的输入缓冲满时,throttle()函数将被调用,tty驱动试图通知设备不应当发送字符给它。当 tty 核心的输入缓冲已被清空时,unthrottle()函数将被调用暗示设备可以接收数据。stop()和start()函数非常像throttle()和 unthrottle()函数,但它们表示 tty 驱动应当停止发送数据给设备以及恢复发送数据。
当 tty驱动挂起 tty设备时,hangup()函数被调用,在此函数中进行相关的硬件操作。当tty 驱动要在 RS-232 端口上打开或关闭线路的 BREAK 状态时,break_ctl()线路中断控制函数被调用。如果state状态设为-1,BREAK 状态打开,如果状态设为 0,BREAK 状态关闭。如果这个函数由 tty 驱动实现,而tty核心将处理TCSBRK、TCSBRKP、TIOCSBRK和 TIOCCBRK这些ioctl命令。flush_buffer()函数用于刷新缓冲区并丢弃任何剩下的数据。set_ldisc()函数用于设置线路规程,当 tty 核心改变tty驱动的线路规程时这个函数被调用,这个函数通常不需要被驱动定义。send_xchar()为X-类型字符发送函数,这个函数用来发送一个高优先级 XON 或者 XOFF 字符给 tty设备,要被发送的字符在第2个参数ch中指定。read_proc()和write_proc()为/proc 读和写函数。tiocmget()函数用于获得tty 设备的线路设置,对应的tiocmset()用于设置tty设备的线路设置,参数set和clear包含了要设置或者清除的线路设置。
Linux内核提供了一组函数用于操作tty_driver结构体及tty设备,包括:
分配tty驱动
struct tty_driver *alloc_tty_driver(int lines);
这个函数返回tty_driver指针,其参数为要分配的设备数量,line会被赋值给tty_driver的num成员,例如:
xxx_tty_driver = alloc_tty_driver(XXX_TTY_MINORS);
if (!xxx_tty_driver) //分配失败
return -ENOMEM;
注册tty驱动
int tty_register_driver(struct tty_driver *driver);
注册tty驱动成功时返回0,参数为由alloc_tty_driver ()分配的tty_driver结构体指针,例如:
retval = tty_register_driver(xxx_tty_driver);
if (retval) //注册失败
{
printk(KERN_ERR "failed to register tiny tty driver");
put_tty_driver(xxx_tty_driver);
return retval;
}
注销tty驱动
int tty_unregister_driver(struct tty_driver *driver);
这个函数与tty_register_driver ()对应,tty驱动最终会调用上述函数注销tty_driver。
注册tty设备
void tty_register_device(struct tty_driver *driver, unsigned index,
struct device *device);
仅有tty_driver是不够的,驱动必须依附于设备,tty_register_device()函数用于注册关联于tty_driver的设备,index为设备的索引(范围是0~driver->num)
14.3终端设备驱动初始化与释放
14.3.1模块加载与卸载函数
tty驱动的模块加载函数中通常需要分配、初始化tty_driver结构体并申请必要的硬件资源,代码清单14.4。tty驱动的模块卸载函数完成与模块加载函数完成相反的工作。
代码清单14.4 终端设备驱动模块加载函数范例
1
2 static int __init xxx_init(void)
3 {
4 ...
5
6 xxx_tty_driver = alloc_tty_driver(XXX_PORTS);
7
8 xxx_tty_driver->owner = THIS_MODULE;
9 xxx_tty_driver->devfs_name = "tts/";
10 xxx_tty_driver->name = "ttyS";
11 xxx_tty_driver->major = TTY_MAJOR;
12 xxx_tty_driver->minor_start = 64;
13 xxx_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
14 xxx_tty_driver->subtype = SERIAL_TYPE_NORMAL;
15 xxx_tty_driver->init_termios = tty_std_termios;
16 xxx_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
17 xxx_tty_driver->flags = TTY_DRIVER_REAL_RAW;
18 tty_set_operations(xxx_tty_driver, &xxx_ops);
19
20 ret = tty_register_driver(xxx_tty_driver);
21 if (ret)
22 {
23 printk(KERN_ERR "Couldn't register xxx serial driver\n");
24 put_tty_driver(xxx_tty_driver);
25 return ret;
26 }
27
28 ...
29 ret = request_irq(...);
30 ...
31 }
14.3.2打开与关闭函数
当用户对tty驱动所分配的设备节点进行open()系统调用时,tty_driver中的open()成员函数将被tty核心调用。tty 驱动必须设置open()成员,否则,-ENODEV将被返回给调用open()的用户。open()成员函数的第1个参数为一个指向分配给这个设备的 tty_struct 结构体的指针,第2个参数为文件指针。
tty_struct结构体被 tty核心用来保存当前tty端口的状态,它的大多数成员只被 tty核心使用。tty_struct中的几个重要成员如下:
flags标示tty 设备的当前状态,包括TTY_THROTTLED、TTY_IO_ERROR、TTY_OTHER_CLOSED、TTY_EXCLUSIVE、 TTY_DEBUG、TTY_DO_WRITE_WAKEUP、TTY_PUSH、TTY_CLOSING、TTY_DONT_FLIP、 TTY_HW_COOK_OUT、TTY_HW_COOK_IN、TTY_PTY_LOCK、TTY_NO_WRITE_SPLIT等。
ldisc为给 tty 设备的线路规程。
write_wait、read_wait为给tty写/读函数的等待队列,tty驱动应当在合适的时机唤醒对应的等待队列。
termios为指向 tty 设备的当前 termios 设置的指针。
stopped:1指示是否停止tty设备,tty 驱动可以设置这个值;hw_stopped:1指示是否tty设备已经被停止,tty 驱动可以设置这个值;flow_stopped:1指示是否 tty 设备数据流停止。
driver_data、disc_data为数据指针,用于存储tty驱动和线路规程的“私有”数据。
驱动中可以定义1个设备相关的结构体,并在open()函数中将其赋值给tty_struct的driver_data成员,如代码清单14.5。
代码清单14.5 在tty驱动打开函数中赋值tty_struct的driver_data成员
1
2 struct xxx_tty
3 {
4 struct tty_struct *tty;
5 int open_count;
6 struct semaphore sem;
7 int xmit_buf;
8 ...
9 }
10
11
12 static int xxx_open(struct tty_struct *tty, struct file *file)
13 {
14 struct xxx_tty *xxx;
15
16
17 xxx = kmalloc(sizeof(*xxx), GFP_KERNEL);
18 if (!xxx)
19 return - ENOMEM;
20
21 init_MUTEX(&xxx->sem);
22 xxx->open_count = 0;
23 ...
24
25 tty->driver_data = xxx;
26 xxx->tty = tty;
27 ...
28 return 0;
29 }
在用户对前面使用 open()系统调用而创建的文件句柄进行close()系统调用时,tty_driver中的close()成员函数将被tty核心调用。
14.4 数据发送和接收
图14.3给出了终端设备数据发送和接收过程中的数据流以及函数调用关系。用户在有数据发送给终端设备时,通过“write()系统调用――tty核心――线路规程”的层层调用,最终调用tty_driver结构体中的write()函数完成发送。
因为速度和tty硬件缓冲区容量的原因,不是所有的写程序要求的字符都可以在调用写函数时被发送,因此写函数应当返回能够发送给硬件的字节数以便用户程序检查是否所有的数据被真正写入。如果在 wirte()调用期间发生任何错误,一个负的错误码应当被返回。
图14.3 终端设备数据发送和接收过程中的数据流和函数调用关系
tty_driver 的write()函数接受3个参数tty_struct、发送数据指针及要发送的字节数,一般首先会通过tty_struct的driver_data成员得到设备私有信息结构体,然后依次进行必要的硬件操作开始发送,代码清单14.6给出了tty_driver的write()函数范例。
代码清单14.6 tty_driver结构体的write()成员函数范例
1 static int xxx_write(struct tty_struct *tty, const unsigned char *buf, int count)
2 {
3
4 struct xxx_tty *xxx = (struct xxx_tty*)tty->driver_data;
5 ...
6
7 while (1)
8 {
9 local_irq_save(flags);
10 c = min_t(int, count, min(SERIAL_XMIT_SIZE - xxx->xmit_cnt - 1,
11 SERIAL_XMIT_SIZE - xxx->xmit_head));
12 if (c xmit_buf + xxx->xmit_head, buf, c);
19 xxx->xmit_head = (xxx->xmit_head + c) &(SERIAL_XMIT_SIZE - 1);
20 xxx->xmit_cnt += c;
21 local_irq_restore(flags);
22
23 buf += c;
24 count -= c;
25 total += c;
26 }
27
28 if (xxx->xmit_cnt && !tty->stopped && !tty->hw_stopped)
29 {
30 start_xmit(xxx);//开始发送
31 }
32 return total; //返回发送的字节数
33 }
当tty子系统自己需要发送数据到 tty 设备时,如果没有实现 put_char()函数,write()函数将被调用,此时传入的count参数为1,通过对代码清单14.7的分析即可获知。
代码清单14.7 put_char()函数的write()替代
1 int tty_register_driver(struct tty_driver *driver)
2 {
3 ...
4 if (!driver->put_char)//没有定义put_char()函数
5 driver->put_char = tty_default_put_char;
6 ...
7 }
8 static void tty_default_put_char(struct tty_struct *tty, unsigned char ch)
9 {
10 tty->driver->write(tty, &ch, 1);//调用tty_driver.write()函数
11 }
读者朋友们可能注意到了,tty_driver结构体中没有提供 read()函数。因为发送是用户主动的,而接收即用户调read()则是读一片缓冲区中已放好的数据。tty 核心在一个称为 struct tty_flip_buffer 的结构体中缓冲数据直到它被用户请求。因为tty核心提供了缓冲逻辑,因此每个 tty 驱动并非一定要实现它自身的缓冲逻辑。
tty驱动不必过于关心tty_flip_buffer 结构体的细节,如果其count字段大于或等于TTY_FLIPBUF_SIZE,这个flip缓冲区就需要被刷新到用户,刷新通过对 tty_flip_buffer_push()函数的调用来完成,代码清单代码清单14.8给出了范例。
代码清单14.8 tty_flip_buffer_push()范例
1 for (i = 0; i flip.count >= TTY_FLIPBUF_SIZE)
4 tty_flip_buffer_push(tty);//数据填满向上层“推”
5 tty_insert_flip_char(tty, data, TTY_NORMAL);//把数据插入缓冲区
6 }
7 tty_flip_buffer_push(tty);
从tty 驱动接收到字符通过tty_insert_flip_char()函数被插入到flip缓冲区。该函数的第1个参数是数据应当保存入的 tty_struct结构体,第 2 个参数是要保存的字符,第3个参数是应当为这个字符设置的标志,如果字符是一个接收到的常规字符,则设为TTY_NORMAL,如果是一个特殊类型的指示错误的字符,依据具体的错误类型,应当设为TTY_BREAK、 TTY_PARITY或TTY_OVERRUN。
14.5 TTY线路设置
14.5.1线路设置用户空间接口
用户可用如下2种方式改变tty设备的线路设置或者获取当前线路设置:
1、调用用户空间的termios库函数
用户空间的应用程序需引用termios.h头文件,该头文件包含了终端设备的I/O接口,实际是由POSIX定义的标准方法。对终端设备操作模式的描述由termios结构体完成,从代码清单14.2可以看出,这个结构体包含c_iflag、c_oflag、c_cflag、c_lflag和c_cc []几个成员。
termios的c_cflag主要包含如下位域信息:CSIZE(字长)、CSTOPB(2个停止位)、 PARENB(奇偶校验位使能)、PARODD (奇校验位,当PARENB被使能时)、 CREAD(字符接收使能,如果没有置位,仍然从端口接收字符,但这些字符都要被丢弃)、 CRTSCTS (如果被置位,使能CTS状态改变报告)、CLOCAL (如果没有置位,使能调制解调器状态改变报告)。
termios的c_iflag主要包含如下位域信息:INPCK (使能帧和奇偶校验错误检查)、BRKINT(break将清除终端输入/输出队列,向该终端上前台的程序发出SIGINT信号)、 PARMRK (奇偶校验和帧错误被标记,在INPCK被设置且IGNPAR未被设置的情况下才有意义)、IGNPAR (忽略奇偶校验和帧错误)、IGNBRK (忽略break)。
通过tcgetattr()、tcsetattr()函数即可完成对终端设备的操作模式的设置和获取,这2个函数的原型如下:
int tcgetattr (int fd, struct termios *termios_p);
int tcsetattr (int fd, int optional_actions, struct termios *termios_p);
例如,Raw模式的线路设置为:
非正规模式
关闭回显
禁止 CR 到 NL 的映射(ICRNL)、输入奇偶校验、输入第 8 位的截取(ISTRIP)以及输出流控制
8位字符(CS8),奇偶校验被禁止
禁止所有的输出处理
每次一个字节 (c_cc [VMIN] = 1、c_cc [VTIME] = 0)
则对应的对termios结构体的设置就为:
termios_p->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
| INLCR | IGNCR | ICRNL | IXON);
termios_p->c_oflag &= ~OPOST;
termios_p->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
termios_p->c_cflag &= ~(CSIZE | PARENB);
termios_p->c_cflag |= CS8;
通过如下一组函数可完成输入/输出波特率的获取和设置:
speed_t cfgetospeed (struct termios *termios_p); //获得输出波特率
speed_t cfgetispeed (struct termios *termios_p); //获得输入波特率
int cfsetospeed (struct termios *termios_p, speed_t speed); //设置输出波特率
int cfsetispeed (struct termios *termios_p, speed_t speed); //设置输入波特率
如下一组函数则完成线路控制:
int tcdrain (int fd); //等待所有输出都被发送
int tcflush (int fd, int queue_selector); //flush输入/输出缓存
int tcflow (int fd, int action); // 对输入和输出流进行控制
int tcsendbreak (int fd, int duration);//发送break
tcflush函数刷清(抛弃)输入缓存(终端驱动程序已接收到,但用户程序尚未读取)或输出缓存(用户程序已经写,但驱动尚未发送),queue参数可取TCIFLUSH(刷清输入队列)、TCOFLUSH(刷清输出队列)或TCIOFLUSH(刷清输入、输出队列)。
tcflow()对输入输出进行流控制,action参数可取TCOOFF(输出被挂起)、TCOON(重新启动以前被挂起的输出)、TCIOFF(发送1个STOP字符,使终端设备暂停发送数据)、TCION(发送1个START字符,使终端恢复发送数据)。
tcsendbreak()函数在一个指定的时间区间内发送连续的0二进位流。若duration参数为0,则此种发送延续0.25-0.5秒之间。POSIX.1说明若duration非0,则发送时间依赖于实现。
2、对tty设备节点进行ioctl()调用
大部分termios库函数会被转化为对tty设备节点的ioctl()调用,例如tcgetattr()、tcsetattr()函数对应着TCGETS、TCSETS IO控制命令。TIOCMGET (获得MODEM状态位)、TIOCMSET(设置MODEM状态位)、TIOCMBIC (清除指示MODEM位)、TIOCMBIS(设置指示MODEM位)这4个IO控制命令用于获取和设置MODEM握手,如RTS、CTS、DTR、 DSR、RI、CD等。
14.5.2 tty驱动set_termios函数
大部分 termios 用户空间函数被库转换为对驱动节点的 ioctl()调用,而tty ioctl中的大部分命令会被tty核心转换为对tty驱动的set_termios()函数的调用。set_termios()函数需要根据用户对 termios的设置(termios设置包括字长、奇偶校验位、停止位、波特率等)完成实际的硬件设置。
tty_operations中的set_termios()函数原型为:
void(*set_termios)(struct tty_struct *tty, struct termios *old);
新的设置被保存在tty_struct中,旧的设置被保存在old参数中,若新旧参数相同,则什么都不需要做,对于被改变的设置,需完成硬件上的设置,代码清单14.9给出了set_termios()函数的例子。
代码清单14.9 tty驱动程序set_termios()函数范例
1 static void xxx_set_termios(struct tty_struct *tty, struct termios *old_termios)
2 {
3 struct xxx_tty *info = (struct cyclades_port*)tty->driver_data;
4
5 if (tty->termios->c_cflag == old_termios->c_cflag)
6 return ;
7 ...
8
9
10 if ((old_termios->c_cflag &CRTSCTS) && !(cflag &CRTSCTS))
11 {
12 ...
13 }
14
15
16 if (!(old_termios->c_cflag &CRTSCTS) && (cflag &CRTSCTS))
17 {
18 ...
19 }
20
21
22 switch (tty->termios->c_cflag &CSIZE)
23 {
24
25 case CS5:
26 ...
27 case CS6:
28 ...
29 case CS7:
30 ...
31 case CS8:
32 ...
33 }
34
35
36 if (tty->termios->c_cflag &PARENB)
37 if (tty->termios->c_cflag &PARODD) //奇校验
38 ...
39 else //偶校验
40 ...
41 else //无校验
42 ...
43 }
14.5.3 tty驱动 tiocmget和tiocmset函数
对TIOCMGET、 TIOCMSET、TIOCMBIC和TIOCMBIS IO控制命令的调用将被tty核心转换为对tty驱动tiocmget()函数和tiocmset()函数的调用,TIOCMGET对应tiocmget ()函数,TIOCMSET、TIOCMBIC和TIOCMBIS 对应tiocmset()函数,分别用于读取Modem控制的设置和进行Modem的设置。代码清单14.10给出了tiocmget()函数的范例,代 码清单14.11则给出了tiocmset()函数的范例。
代码清单14.10 tty驱动程序tiocmget()函数范例
1 static int xxx_tiocmget(struct tty_struct *tty, struct file *file)
2 {
3 struct xxx_tty *info = tty->driver_ data;
4 unsigned int result = 0;
5 unsigned int msr = info->msr;
6 unsigned int mcr = info->mcr;
7 result = ((mcr &MCR_DTR) ? TIOCM_DTR : 0) |
8 ((mcr &MCR_RTS) ? TIOCM_RTS : 0) |
9 ((mcr &MCR_LOOP) ? TIOCM_LOOP : 0) |
10 ((msr &MSR_CTS) ? TIOCM_CTS : 0) |
11 ((msr &MSR_CD) ? TIOCM_CAR : 0) |
12 ((msr &MSR_RI) ? TIOCM_RI : 0) |
13 ((msr &MSR_DSR) ? TIOCM_DSR : 0);
14 return result;
15 }
代码清单14.11 tty驱动程序tiocmset()函数范例
1 static int xxx_tiocmset(struct tty_struct *tty, struct file *file, unsigned
2 int set, unsigned int clear)
3 {
4 struct xxx_tty *info = tty->driver_data;
5 unsigned int mcr = info->mcr;
6
7 if (set &TIOCM_RTS)
8 mcr |= MCR_RTS;
9 if (set &TIOCM_DTR)
10 mcr |= MCR_RTS;
11
12 if (clear &TIOCM_RTS)
13 mcr &= ~MCR_RTS;
14 if (clear &TIOCM_DTR)
15 mcr &= ~MCR_RTS;
16
17
18 tiny->mcr = mcr;
19 return 0;
20 }
tiocmget()函数会访问MODEM状态寄存器(MSR),而tiocmset()函数会访问MODEM控制寄存器(MCR)。
14.5.3 tty驱动ioctl函数
当用户在tty设备节点上进行ioctl(2) 调用时,tty_operations中的 ioctl()函数会被tty核心调用。如果 tty 驱动不知道如何处理传递给它的 ioctl 值,它返回 –ENOIOCTLCMD,之后tty 核心会执行一个通用的操作。
驱动中常见的需处理 的IO控制命令包括TIOCSERGETLSR(获得这个 tty 设备的线路状态寄存器LSR 的值)、TIOCGSERIAL(获得串口线信息)、TIOCMIWAIT(等待 MSR 改变)、TIOCGICOUNT(获得中断计数)等。代码清单14.12给出了tty驱动程序ioctl()函数的范例。
代码清单14.12 tty驱动程序ioctl()函数范例
1 static int xxx_ioctl(struct tty_struct *tty, struct file *filp, unsigned int
2 cmd, unsigned long arg)
3 {
4 struct xxx_tty *info = tty->driver_data;
5 ...
6
7 switch (cmd)
8 {
9 case TIOCGSERIAL:
10 ...
11 case TIOCSSERIAL:
12 ...
13 case TIOCSERCONFIG:
14 ...
15 case TIOCMIWAIT:
16 ...
17 case TIOCGICOUNT:
18 ...
19 case TIOCSERGETLSR:
20 ...
21 }
22 ...
23 }
14.6 UART设备驱动
尽管一个特定的UART设备驱动完全可以遵循14.2~14.5的方法来设计,即定义tty_driver并实现其中的成员函数,但是Linux已经在文件 serial_core.c中实现了UART设备的通用tty驱动层(姑且称其为串口核心层),这样,UART驱动的主要任务演变成实现serial- core.c中定义的一组uart_xxx接口而非tty_xxx接口,如图14.5所示。
serial_core.c串口核心层完全可以被当作14.2~14.5节tty设备驱动的实例,它实现了UART设备的tty驱动。
提示:Linux驱动的这种分层思想在许多类型的设备驱动中都得到了体现,例如上一章IDE设备驱动中,内核实现了通用的IDE层用于处理块设备I/O请求,而具体的IDE则只需使用ide_xxx这样的接口,甚至不必理会复杂的块设备驱动结构。
图14.5 串口核心层
串口核心层为串口设备驱动提供了如下3个结构体:
1、uart_driver
uart_driver包含串口设备的驱动名、设备名、设备号等信息,它封装了tty_driver,使得底层的UART驱动无需关心tty_driver,其定义如代码清单14.13。
代码清单14.13 uart_driver结构体
1 struct uart_driver
2 {
3 struct module *owner;
4 const char *driver_name; //驱动名
5 const char *dev_name; //设备名
6 const char *devfs_name; //设备文件系统名
7 int major; //主设备号
8 int minor; //次设备号
9 int nr;
10 struct console *cons;
11
12
13 struct uart_state *state;
14 struct tty_driver *tty_driver;
15 };
一个tty驱动必须注册/注销tty_driver,而一个UART驱动则演变为注册/注销uart_driver,使用如下接口:
int uart_register_driver(struct uart_driver *drv);
void uart_unregister_driver(struct uart_driver *drv);
实际上,uart_register_driver()和uart_unregister_driver()中分别包含了tty_register_driver()和tty_unregister_driver()的操作,如代码清单14.14所示。
代码清单14.14 uart_register_driver()和uart_unregister_driver()函数
1 int uart_register_driver(struct uart_driver *drv)
2 {
3 struct tty_driver *normal = NULL;
4 int i, retval;
5 ...
6
7 normal = alloc_tty_driver(drv->nr);
8 if (!normal)
9 goto out;
10 drv->tty_driver = normal;
11
12 normal->owner = drv->owner;
13 normal->driver_name = drv->driver_name;
14 normal->devfs_name = drv->devfs_name;
15 normal->name = drv->dev_name;
16 normal->major = drv->major;
17 normal->minor_start = drv->minor;
18 normal->type = TTY_DRIVER_TYPE_SERIAL;
19 normal->subtype = SERIAL_TYPE_NORMAL;
20 normal->init_termios = tty_std_termios;
21 normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
22 normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS;
23 normal->driver_state = drv;
24 tty_set_operations(normal, &uart_ops);
25
26 ...
27
28 retval = tty_register_driver(normal);
29 out:
30 if (retval state);
33 }
34 return retval;
35 }
36
37 void uart_unregister_driver(struct uart_driver *drv)
38 {
39 struct tty_driver *p = drv->tty_driver;
40 tty_unregister_driver(p);
41 put_tty_driver(p);
42 kfree(drv->state);
43 drv->tty_driver = NULL;
44 }
2、uart_port
uart_port用于描述一个UART端口(直接对应于一个串口)的I/O端口或I/O内存地址、FIFO大小、端口类型等信息,其定义如代码清单14.15。
代码清单14.15 uart_port结构体
1 struct uart_port
2 {
3 spinlock_t lock;
4 unsigned int iobase;
5 unsigned char __iomem *membase;
6 unsigned int irq;
7 unsigned int uartclk;
8 unsigned char fifosize;
9 unsigned char x_char;
10 unsigned char regshift;
11 unsigned char iotype;
12
13 #define UPIO_PORT (0)
14 #define UPIO_HUB6 (1)
15 #define UPIO_MEM (2)
16 #define UPIO_MEM32 (3)
17 #define UPIO_AU (4)
18
19 unsigned int read_status_mask;
20 unsigned int ignore_status_mask;
21 struct uart_info *info;
22 struct uart_icount icount;
23
24 struct console *cons;
25 #ifdef CONFIG_SERIAL_CORE_CONSOLE
26 unsigned long sysrq;
27 #endif
28
29 upf_t flags;
30
31 #define UPF_FOURPORT ((__force upf_t) (1 driver_data;
4 struct uart_port *port = state->port;
5 unsigned long flags;
6 //如果uart_ops中实现了send_xchar成员函数
7 if (port->ops->send_xchar)
8 port->ops->send_xchar(port, ch);
9 else //uart_ops中未实现send_xchar成员函数
10 {
11 port->x_char = ch; //xchar赋值
12 if (ch)
13 {
14 spin_lock_irqsave(&port->lock, flags);
15 port->ops->start_tx(port); //发送xchar
16 spin_unlock_irqrestore(&port->lock, flags);
17 }
18 }
19 }
注意: 整个调用流程为: 系统调用write()->uart_write()(tty_driver)->port->ops->start_tx();
在使用串口核心层这个通用串口tty驱动层的接口后,一个串口驱动要完成的主要工作将包括:
• 定义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驱动的主体工作。
14.7实例:S3C2410 UART的驱动
14.7.1 S3C2410串口硬件描述
S3C2410 内部具有3个独立的UART控制器,每个控制器都可以工作在Interrupt(中断)模式或DMA(直接内存访问)模式,也就是说UART控制器可以在 CPU与UART控制器传送资料的时候产生中断或DMA请求。S3C2410集成的每个UART均具有16字节的FIFO,支持的最高波特率可达到 230.4Kbps。
ULCONn(UART Line Control Register)寄存器用于S3C2410 UART的线路控制,用于设置模式、每帧的数据位数、停止位数及奇偶校验,如表14.1。
表14.1 S3C2410 UART的ULCONn寄存器
ULCONn 位 描述
保留 [7]
红外模式 [6] 0:正常模式 1:红外模式
奇偶校验 [5:3] 0xx:无校验 100:奇校验 101:偶校验 ...
停止位 [2] 0:1个停止位 1:2个停止位
字长 [1:0] 00:5位 01:6位 10:7位 11:8位
UCONn(UART Control Register)寄存器用于从整体上控制S3C2410 UART的中断模式及工作模式(DMA、中断、轮询)等,如表14.2。
表14.2 S3C2410 UART的UCONn寄存器
UCONn 位 描述
时钟选择 [10] 为UART的波特率产生选择PCLK或UCLK时钟
Tx中断 [9] 0:脉冲 1:电平
Rx中断 [8] 0:脉冲 1:电平
Rx超时使能 [7] 当UART被使能,使能/禁止Rx超时中断 0:禁止 1:使能
Rx错误状态中断使能 [6] 使能接收异常中断(如break、帧错误、校验错、溢出等)
loopback [5] 0:正常模式 1:回环
发送break [4] 设置该位将造成UART在1帧的时间内发送break,当发送完break后,该位将自动被清除
发送模式 [3:2] 发送数据到UART的模式,00:禁止 01:中断或轮询 10:DMA0(仅针对UART0)、DMA3(仅针对UART3) 11:DMA1(仅针对UART1)
接收模式 [1:0] 从UART接收数据的模式,00:禁止 01:中断或轮询 10:DMA0(仅针对UART0)
UFCONn(UART FIFO Conrtol Register)寄存器用于S3C2410 UART的FIFO控制,用于控制FIFO中断的触发级别以及复位时是否清空FIFO中的内容,如表14.3。
表14.3 S3C2410 UART的UFCONn寄存器
UFCONn 位 描述
Tx FIFO触发级别 [7:6] 决定发送FIFO的触发级别: 00:空 01:4字节 10:8字节 11:12字节
Rx FIFO触发级别 [5:4] 决定接收FIFO的触发级别: 00:4字节 01:8字节 10:12字节 11:16字节
Tx FIFO复位 [2] 复位FIFO后自动清除FIFO 0:正常 1:Tx FIFO复位
Rx FIFO复位 [1] 复位FIFO后自动清除FIFO 0:正常 1:Tx FIFO复位
FIFO使能 [0] 0:禁止 1:使能
代码清单14.19给出了UFCONn寄存器的位掩码和默认设置(使能FIFO、Tx FIFO为空时触发中断、Rx FIFO中包含8个字节时触发中断)。
代码清单14.19 S3C2410 UART UFCONn寄存器的位掩码和默认设置
1 #define S3C2410_UFCON_FIFOMODE (1
Rx FIFO数据数 [3:0]
由于UFSTATn寄存器中的Tx FIFO数据数和Rx FIFO数据数分别占据[7:4]和[3:0]位,因此定义S3C2410_UFSTAT_TXSHIFT和 S3C2410_UFSTAT_RXSHIFT分别为4和0,代码清单14.20给出了UFSTATn寄存器的位掩码等信息。
代码清单14.20 S3C2410 UART UFSTATn寄存器的位掩码
1 #define S3C2410_UFSTAT_TXFULL (1 2
39
40 [2] = {
41 .port = {
42 .lock = SPIN_LOCK_UNLOCKED,
43 .iotype = UPIO_MEM,
44 .irq = IRQ_S3CUART_RX2,
45 .uartclk = 0,
46 .fifosize = 16,
47 .ops = &s3c24xx_serial_ops,
48 .flags = UPF_BOOT_AUTOCONF,
49 .line = 2, //端口索引:2
50 }
51 }
52 #endif
53 };
S3C2410串口驱动中uart_ops结构体实例的定义如代码清单14.23,将一系列s3c24xx_serial_函数赋值给了uart_ops结构体的成员。
代码清单14.23 S3C2410串口驱动uart_ops结构体
1 static struct uart_ops s3c24xx_serial_ops =
2 {
3 .pm = s3c24xx_serial_pm,
4 .tx_empty = s3c24xx_serial_tx_empty,//发送缓冲区空
5 .get_mctrl = s3c24xx_serial_get_mctrl,//得到modem控制设置
6 .set_mctrl = s3c24xx_serial_set_mctrl, //设置modem控制(MCR)
7 .stop_tx = s3c24xx_serial_stop_tx, //停止接收字符
8 .start_tx = s3c24xx_serial_start_tx,//开始传输字符
9 .stop_rx = s3c24xx_serial_stop_rx, //停止接收字符
10 .enable_ms = s3c24xx_serial_enable_ms,// modem状态中断使能
11 .break_ctl = s3c24xx_serial_break_ctl,// 控制break信号的传输
12 .startup = s3c24xx_serial_startup,//启动端口
13 .shutdown = s3c24xx_serial_shutdown,// 禁用端口
14 .set_termios = s3c24xx_serial_set_termios,//改变端口参数
15 .type = s3c24xx_serial_type,//返回描述特定端口的常量字符串指针
16 .release_port = s3c24xx_serial_release_port,//释放端口占用的内存及IO资源
17 .request_port = s3c24xx_serial_request_port,//申请端口所需的内存和IO资源
18 .config_port = s3c24xx_serial_config_port,//执行端口所需的自动配置步骤
19 .verify_port = s3c24xx_serial_verify_port,//验证新的串行端口信息
20 };
set_mctrl()函数的原型为:
void (*set_mctrl)(struct uart_port *port, u_int mctrl);
它将参数port所对应的调制解调器控制线的值设为参数mctrl的值。
get_mctrl()函数的原型为:
unsigned int (*get_mctrl)(struct uart_port *port);
该函数返回调制解调器控制输入的现有状态,这些状态信息包括:TIOCM_CD(CD 信号状态)、TIOCM_CTS(CTS信号状态)、TIOCM_DSR(DSR信号状态)、TIOCM_RI(RI信号状态)等。如果信号被置为有效,则对应位将被置位。
端口启动函数startup()的原型为:
int (*startup)(struct uart_port *port, struct uart_info *info);
该函数申请所有中断资源,初始化底层驱动状态,并开启端口为可接收数据的状态。
shutdown()函数完成与startup()函数的作用相反,其原型:
void (*shutdown)(struct uart_port *port, struct uart_info *info);
这个函数禁用端口,释放所有的中断资源。
回过头来看s3c24xx_uart_port结构体,其中的s3c24xx_uart_info成员(代码清单14.22第6行)是一些针对S3C2410 UART的信息,其定义如代码清单14.24。
代码清单14.24 S3C2410串口驱动s3c24xx_uart_info结构体
1 static struct s3c24xx_uart_info s3c2410_uart_inf =
2 {
3 .name = "Samsung S3C2410 UART",
4 .type = PORT_S3C2410,
5 .fifosize = 16,
6 .rx_fifomask = S3C2410_UFSTAT_RXMASK,
7 .rx_fifoshift = S3C2410_UFSTAT_RXSHIFT,
8 .rx_fifofull = S3C2410_UFSTAT_RXFULL,
9 .tx_fifofull = S3C2410_UFSTAT_TXFULL,
10 .tx_fifomask = S3C2410_UFSTAT_TXMASK,
11 .tx_fifoshift = S3C2410_UFSTAT_TXSHIFT,
12 .get_clksrc = s3c2410_serial_getsource,
13 .set_clksrc = s3c2410_serial_setsource,
14 .reset_port = s3c2410_serial_resetport,
15 };
在S3C2410串口驱动中,针对UART的设置(UCONn、ULCONn、UFCONn寄存器等)被封装到s3c2410_uartcfg结构体中,其定义如代码清单14.25。
代码清单14.25 S3C2410串口驱动s3c2410_uartcfg结构体
1 struct s3c2410_uartcfg
2 {
3 unsigned char hwport;
4 unsigned char unused;
5 unsigned short flags;
6 unsigned long uart_flags;
7
8 unsigned long ucon;
9 unsigned long ulcon;
10 unsigned long ufcon;
11
12 struct s3c24xx_uart_clksrc *clocks;
13 unsigned int clocks_size;
14 };
14.7.3 S3C2410串口驱动初始化与释放
在S3C2410 串口驱动的模块加载函数中会调用uart_register_driver()注册s3c24xx_uart_drv这个uart_driver,同时经过s3c2410_serial_init()→s3c24xx_serial_init()→platform_driver_register()的调用导致s3c24xx_serial_probe()被执行,而s3c24xx_serial_probe()函数中会调用 s3c24xx_serial_init_port()初始化UART端口并调用uart_add_one_port()添加端口,整个过程的对应代码如 清单14.26。
代码清单14.26 S3C2410串口驱动初始化过程
1 static int __init s3c24xx_serial_modinit(void)
2 {
3 int ret;
4 //注册uart_driver
5 ret = uart_register_driver(&s3c24xx_uart_drv);
6 if (ret port);
49 platform_set_drvdata(dev, &ourport->port);
50
51 return 0;
52
53 probe_err:
54 return ret;
55 }
56 //42行调用的s3c24xx_serial_init_port()函数
57 static int s3c24xx_serial_init_port(struct s3c24xx_uart_port *ourport,
58 struct s3c24xx_uart_info *info,
59 struct platform_device *platdev)
60 {
61 struct uart_port *port = &ourport->port;
62 struct s3c2410_uartcfg *cfg;
63 struct resource *res;
64
65 dbg("s3c24xx_serial_init_port: port=%p, platdev=%p\n", port, platdev);
66
67 if (platdev == NULL)
68 return -ENODEV;
69
70 cfg = s3c24xx_dev_to_cfg(&platdev->dev);
71
72 if (port->mapbase != 0)
73 return 0;
74
75 if (cfg->hwport > 3)
76 return -EINVAL;
77
78
79 port->dev = &platdev->dev;
80 ourport->info = info;
81
82
83 ourport->port.fifosize = info->fifosize;
84
85 dbg("s3c24xx_serial_init_port: %p (hw %d)...\n", port, cfg->hwport);
86
87 port->uartclk = 1;
88
89 if (cfg->uart_flags & UPF_CONS_FLOW) {
90 dbg("s3c24xx_serial_init_port: enabling flow control\n");
91 port->flags |= UPF_CONS_FLOW;
92 }
93
94
95 res = platform_get_resource(platdev, IORESOURCE_MEM, 0);
96 if (res == NULL) {
97 printk(KERN_ERR "failed to find memory resource for uart\n");
98 return -EINVAL;
99 }
100
101 dbg("resource %p (%lx..%lx)\n", res, res->start, res->end);
102
103 port->mapbase = res->start;
104 port->membase = S3C24XX_VA_UART+(res->start - S3C24XX_PA_UART);
105 port->irq = platform_get_irq(platdev, 0);
106
107 ourport->clk = clk_get(&platdev->dev, "uart");
108
109 dbg("port: map=x, mem=x, irq=%d, clock=%ld\n",
110 port->mapbase, port->membase, port->irq, port->uartclk);
111
112
113 s3c24xx_serial_resetport(port, cfg);
114 return 0;
115 }
116 //113行调用的s3c24xx_serial_resetport()函数
117 static inline int s3c24xx_serial_resetport(struct uart_port * port,
118 struct s3c2410_uartcfg *cfg)
119 {
120 struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);
121
122 return (info->reset_port)(port, cfg);
123 }
124 //122行调用的info->reset_port()函数
125 static int s3c2410_serial_resetport(struct uart_port *port,
126 struct s3c2410_uartcfg *cfg)
127 {
128 dbg("s3c2410_serial_resetport: port=%p (lx), cfg=%p\n",
129 port, port->mapbase, cfg);
130
131 wr_regl(port, S3C2410_UCON, cfg->ucon);
132 wr_regl(port, S3C2410_ULCON, cfg->ulcon);
133
134
135 wr_regl(port, S3C2410_UFCON, cfg->ufcon | S3C2410_UFCON_RESETBOTH);
136 wr_regl(port, S3C2410_UFCON, cfg->ufcon);
137
138 return 0;
139 }
在S3C2410串口驱动模块被卸载时,它会最终调用uart_remove_one_port()释放uart_port并调用uart_unregister_driver()注销uart_driver,如代码清单14.27所示。
代码清单14.27 S3C2410串口驱动释放过程
1 static void __exit s3c24xx_serial_modexit(void)
2 {
3 s3c2410_serial_exit();
4 //注销uart_driver
5 uart_unregister_driver(&s3c24xx_uart_drv);
6 }
7
8 static inline void s3c2410_serial_exit(void)
9 {
10 //注销平台驱动
11 platform_driver_unregister(&s3c2410_serial_drv);
12 }
13
14 static int s3c24xx_serial_remove(struct platform_device *dev)
15 {
16 struct uart_port *port = s3c24xx_dev_to_port(&dev->dev);
17
18 //移除uart端口
19 if (port)
20 uart_remove_one_port(&s3c24xx_uart_drv, port);
21
22 return 0;
23 }
上述代码中对S3C24xx_serial_remove()的调用发生在platform_driver_unregister()之后,由于S3C2410的UART是集成于SoC芯片内部的一个独立的硬件单元,因此也被作为1个平台设备而定义。
14.7.4 S3C2410串口数据收发
S3C2410串口驱动uart_ops结构体的startup ()成员函数s3c24xx_serial_startup()用于启动端口,申请端口的发送、接收中断,使能端口的发送和接收,其实现如代码清单14.28。
代码清单14.28 S3C2410串口驱动startup ()函数
1 static int s3c24xx_serial_startup(struct uart_port *port)
2 {
3 struct s3c24xx_uart_port *ourport = to_ourport(port);
4 int ret;
5
6 dbg("s3c24xx_serial_startup: port=%p (lx,%p)\n",
7 port->mapbase, port->membase);
8
9 rx_enabled(port) = 1;//置接收使能状态为1
10 //申请接收中断
11 ret = request_irq(RX_IRQ(port),
12 s3c24xx_serial_rx_chars, 0,
13 s3c24xx_serial_portname(port), ourport);
14
15 if (ret != 0) {
16 printk(KERN_ERR "cannot get irq %d\n", RX_IRQ(port));
17 return ret;
18 }
19
20 ourport->rx_claimed = 1;
21
22 dbg("requesting tx irq...\n");
23
24 tx_enabled(port) = 1;//置发送使能状态为1
25 //申请发送中断
26 ret = request_irq(TX_IRQ(port),
27 s3c24xx_serial_tx_chars, 0,
28 s3c24xx_serial_portname(port), ourport);
29
30 if (ret) {
31 printk(KERN_ERR "cannot get irq %d\n", TX_IRQ(port));
32 goto err;
33 }
34
35 ourport->tx_claimed = 1;
36
37 dbg("s3c24xx_serial_startup ok\n");
38
39
40
41 return ret;
42
43 err:
44 s3c24xx_serial_shutdown(port);
45 return ret;
46 }
s3c24xx_serial_startup()的“反函数”为s3c24xx_serial_shutdown(),其释放中断,禁止发送和接收,实现如代码清单14.29。
代码清单14.29 S3C2410串口驱动shutdown ()函数
1 static void s3c24xx_serial_shutdown(struct uart_port *port)
2 {
3 struct s3c24xx_uart_port *ourport = to_ourport(port);
4
5 if (ourport->tx_claimed) {
6 free_irq(TX_IRQ(port), ourport);
7 tx_enabled(port) = 0; //置发送使能状态为0
8 ourport->tx_claimed = 0;
9 }
10
11 if (ourport->rx_claimed) {
12 free_irq(RX_IRQ(port), ourport);
13 ourport->rx_claimed = 0;
14 rx_enabled(port) = 0; //置接收使能状态为1
15 }
16 }
S3C2410 串口驱动uart_ops结构体的tx_empty()成员函数s3c24xx_serial_tx_empty()用于判断发送缓冲区是否为空,其实现 如代码清单14.30,当使能FIFO模式的时候,判断UFSTATn寄存器,否则判断UTRSTATn寄存器的相应位。
代码清单14.30 S3C2410串口驱动tx_empty()函数
1
2 static unsigned int s3c24xx_serial_tx_empty(struct uart_port *port)
3 {
4 //fifo模式,检查UFSTATn寄存器
5 struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port);
6 unsigned long ufstat = rd_regl(port, S3C2410_UFSTAT);
7 unsigned long ufcon = rd_regl(port, S3C2410_UFCON);
8
9 if (ufcon &S3C2410_UFCON_FIFOMODE)
10 {
11 if ((ufstat &info->tx_fifomask) != 0 || //Tx fifo数据数非0
12 (ufstat &info->tx_fifofull)) // Tx fifo满
13 return 0; //0:非空
14
15 return 1; //1:空
16 }
17
18 return s3c24xx_serial_txempty_nofifo(port);
19 }
20
21 static int s3c24xx_serial_txempty_nofifo(struct uart_port *port)
22 {
23 //非fifo模式,检查UTRSTATn寄存器
24 return (rd_regl(port, S3C2410_UTRSTAT) &S3C2410_UTRSTAT_TXE);
25 }
S3C2410 串口驱动uart_ops结构体的start_tx ()成员函数s3c24xx_serial_start_tx()用于启动发送,而stop_rx()成员函数 s3c24xx_serial_stop_rx()用于停止发送,代码清单14.31给出了这2个函数的实现。
代码清单14.31 S3C2410串口驱动start_tx ()、stop_rx()函数
1 static void s3c24xx_serial_start_tx(struct uart_port *port)
2 {
3 if (!tx_enabled(port)) {//如果端口发送未使能
4 if (port->flags & UPF_CONS_FLOW)
5 s3c24xx_serial_rx_disable(port);
6
7 enable_irq(TX_IRQ(port));//使能发送中断
8 tx_enabled(port) = 1;//置端口发送使能状态为1
9 }
10 }
11
12 static void s3c24xx_serial_stop_tx(struct uart_port *port)
13 {
14 if (tx_enabled(port)) {//如果端口发送已使能
15 disable_irq(TX_IRQ(port));//禁止发送中断
16 tx_enabled(port) = 0;//置端口发送使能状态为0
17 if (port->flags & UPF_CONS_FLOW)
18 s3c24xx_serial_rx_enable(port);
19 }
20 }
S3C2410 串口驱动uart_ops结构体的stop_rx ()成员函数s3c24xx_serial_stop_rx ()用于停止接收,代码清单14.32给出了这个函数的实现。注意uart_ops中没有start_rx()成员,因为接收并不是由“我方”启动,而是 由“它方”的发送触发“我方”的接收中断,“我方”再被动响应。
代码清单14.32 S3C2410串口驱动stop_rx ()函数
1 static void s3c24xx_serial_stop_rx(struct uart_port *port)
2 {
3 if (rx_enabled(port)) {//如果接收为使能
4 dbg("s3c24xx_serial_stop_rx: port=%p\n", port);
5 disable_irq(RX_IRQ(port));//禁止接收中断
6 rx_enabled(port) = 0;//置接收使能状态为0
7 }
8 }
在S3C2410 串口驱动中,与数据收发关系最密切的函数不是上述uart_ops成员函数,而是s3c24xx_serial_startup()为发送和接收中断注册 的中断处理函数s3c24xx_serial_rx_chars()和s3c24xx_serial_tx_chars()。s3c24xx_serial_rx_chars ()读取URXHn寄存器以获得接收到的字符,并调用uart_insert_char()将该字符添加了tty设备的flip缓冲区中,当接收到64个 字符或者不再能接受到字符后,调用tty_flip_buffer_push()函数向上层“推”tty设备的flip缓冲,其实现如代码清单 14.33。
代码清单14.33 S3C2410串口驱动接收中断处理函数
1 static irqreturn_t s3c24xx_serial_rx_chars(int irq, void *dev_id, struct
2 pt_regs *regs)
3 {
4 struct s3c24xx_uart_port *ourport = dev_id;
5 struct uart_port *port = &ourport->port; //获得uart_port
6 struct tty_struct *tty = port->info->tty; //获得tty_struct
7 unsigned int ufcon, ch, flag, ufstat, uerstat;
8 int max_count = 64;
9
10 while (max_count-- > 0)
11 {
12 ufcon = rd_regl(port, S3C2410_UFCON);
13 ufstat = rd_regl(port, S3C2410_UFSTAT);
14 //如果接收到0个字符
15 if (s3c24xx_serial_rx_fifocnt(ourport, ufstat) == 0)
16 break;
17
18 uerstat = rd_regl(port, S3C2410_UERSTAT);
19 ch = rd_regb(port, S3C2410_URXH); //读出字符
20
21 if (port->flags &UPF_CONS_FLOW)
22 {
23 int txe = s3c24xx_serial_txempty_nofifo(port);
24
25 if (rx_enabled(port)) //如果端口为使能接收状态
26 {
27 if (!txe) //如果发送缓冲区为空
28 {
29 rx_enabled(port) = 0; //置端口为使能接收状态为0
30 continue;
31 }
32 }
33 else //端口为禁止接收状态
34 {
35 if (txe) //如果发送缓冲区非空
36 {
37 ufcon |= S3C2410_UFCON_RESETRX;
38 wr_regl(port, S3C2410_UFCON, ufcon);
39 rx_enabled(port) = 1;//置端口为使能接收状态为1
40 goto out;
41 }
42 continue;
43 }
44 }
45
46
47 flag = TTY_NORMAL;
48 port->icount.rx++;
49
50 if (unlikely(uerstat &S3C2410_UERSTAT_ANY))
51 {
52 dbg("rxerr: port ch=0xx, rxs=0xx\n", ch, uerstat);
53
54 if (uerstat &S3C2410_UERSTAT_BREAK)
55 {
56 dbg("break!\n");
57 port->icount.brk++;
58 if (uart_handle_break(port))
59 goto ignore_char;
60 }
61
62 if (uerstat &S3C2410_UERSTAT_FRAME)
63 port->icount.frame++;
64 if (uerstat &S3C2410_UERSTAT_OVERRUN)
65 port->icount.overrun++;
66
67 uerstat &= port->read_status_mask;
68
69 if (uerstat &S3C2410_UERSTAT_BREAK)
70 flag = TTY_BREAK;
71 else if (uerstat &S3C2410_UERSTAT_PARITY)
72 flag = TTY_PARITY;
73 else if (uerstat &(S3C2410_UERSTAT_FRAME | S3C2410_UERSTAT_OVERRUN))
74 flag = TTY_FRAME;
75 }
76
77 if (uart_handle_sysrq_char(port, ch, regs)) //处理sysrq字符
78 goto ignore_char;
79 //插入字符到tty设备的flip 缓冲
80 uart_insert_char(port, uerstat, S3C2410_UERSTAT_OVERRUN, ch, flag);
81
82 ignore_char: continue;
83 }
84 tty_flip_buffer_push(tty); //刷新tty设备的flip 缓冲
85
86 out: return IRQ_HANDLED;
87 }
上述代码第80行的uart_insert_char()函数是串口核心层对tty_insert_flip_char()的封装,它作为内联函数被定义于serial_core.h文件中。
如代码清单14.34,s3c24xx_serial_tx_chars()读取uart_info中环形缓冲区中的字符,写入调用UTXHn寄存器。
代码清单14.34 S3C2410串口驱动发送中断处理函数
1 static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id, struct pt_regs
2 *regs)
3 {
4 struct s3c24xx_uart_port *ourport = id;
5 struct uart_port *port = &ourport->port;
6 struct circ_buf *xmit = &port->info->xmit; //得到环形缓冲区
7 int count = 256; //最多1次发256个字符
8
9 if (port->x_char) //如果定义了xchar,发送
10 {
11 wr_regb(port, S3C2410_UTXH, port->x_char);
12 port->icount.tx++;
13 port->x_char = 0;
14 goto out;
15 }
16
17
18 if (uart_circ_empty(xmit) || uart_tx_stopped(port))
19 {
20 s3c24xx_serial_stop_tx(port);
21 goto out;
22 }
23
24
25 while (!uart_circ_empty(xmit) && count-- > 0)
26 {
27 if (rd_regl(port, S3C2410_UFSTAT) &ourport->info->tx_fifofull)
28 break;
29
30 wr_regb(port, S3C2410_UTXH, xmit->buf[xmit->tail]);
31 xmit->tail = (xmit->tail + 1) &(UART_XMIT_SIZE - 1);
32 port->icount.tx++;
33 }
34
35 if (uart_circ_chars_pending(xmit) c_cflag &= ~(HUPCL | CMSPAR);
16 termios->c_cflag |= CLOCAL;
17
18
19 baud = uart_get_baud_rate(port, termios, old, 0, 115200*8);
20
21 if (baud == 38400 && (port->flags & UPF_SPD_MASK) == UPF_SPD_CUST)
22 quot = port->custom_divisor;
23 else
24 quot = s3c24xx_serial_getclk(port, &clksrc, &clk, baud);
25
26
27 if (ourport->clksrc != clksrc || ourport->baudclk != clk) {
28 s3c24xx_serial_setsource(port, clksrc);
29
30 if (ourport->baudclk != NULL && !IS_ERR(ourport->baudclk)) {
31 clk_disable(ourport->baudclk);
32 ourport->baudclk = NULL;
33 }
34
35 clk_enable(clk);
36
37 ourport->clksrc = clksrc;
38 ourport->baudclk = clk;
39 }
40
41
42 switch (termios->c_cflag & CSIZE) {
43 case CS5:
44 dbg("config: 5bits/char\n");
45 ulcon = S3C2410_LCON_CS5;
46 break;
47 case CS6:
48 dbg("config: 6bits/char\n");
49 ulcon = S3C2410_LCON_CS6;
50 break;
51 case CS7:
52 dbg("config: 7bits/char\n");
53 ulcon = S3C2410_LCON_CS7;
54 break;
55 case CS8:
56 default:
57 dbg("config: 8bits/char\n");
58 ulcon = S3C2410_LCON_CS8;
59 break;
60 }
61
62
63 ulcon |= (cfg->ulcon & S3C2410_LCON_IRM);
64
65 if (termios->c_cflag & CSTOPB)
66 ulcon |= S3C2410_LCON_STOPB;
67
68 umcon = (termios->c_cflag & CRTSCTS) ? S3C2410_UMCOM_AFC : 0;
69
70 if (termios->c_cflag & PARENB) {
71 if (termios->c_cflag & PARODD)
72 ulcon |= S3C2410_LCON_PODD;//计校验
73 else
74 ulcon |= S3C2410_LCON_PEVEN;//偶校验
75 } else {
76 ulcon |= S3C2410_LCON_PNONE;//无校验
77 }
78
79 spin_lock_irqsave(&port->lock, flags);
80
81 dbg("setting ulcon to x, brddiv to %d\n", ulcon, quot);
82
83 wr_regl(port, S3C2410_ULCON, ulcon);
84 wr_regl(port, S3C2410_UBRDIV, quot);
85 wr_regl(port, S3C2410_UMCON, umcon);
86
87 dbg("uart: ulcon = 0xx, ucon = 0xx, ufcon = 0xx\n",
88 rd_regl(port, S3C2410_ULCON),
89 rd_regl(port, S3C2410_UCON),
90 rd_regl(port, S3C2410_UFCON));
91
92
93 uart_update_timeout(port, termios->c_cflag, baud);
94
95
96 port->read_status_mask = S3C2410_UERSTAT_OVERRUN;
97 if (termios->c_iflag & INPCK)
98 port->read_status_mask |= S3C2410_UERSTAT_FRAME | S3C2410_UERSTAT_PARITY;
99
100
101 port->ignore_status_mask = 0;
102 if (termios->c_iflag & IGNPAR)
103 port->ignore_status_mask |= S3C2410_UERSTAT_OVERRUN;
104 if (termios->c_iflag & IGNBRK && termios->c_iflag & IGNPAR)
105 port->ignore_status_mask |= S3C2410_UERSTAT_FRAME;
106
107
108 if ((termios->c_cflag & CREAD) == 0)
109 port->ignore_status_mask |= RXSTAT_DUMMY_READ;
110
111 spin_unlock_irqrestore(&port->lock, flags);
112 }
由于S3C2410集成UART并不包含完整的Modem控制信号线,因此其uart_ops结构体的get_mctrl()、set_mctrl()成员 函数的实现非常简单,如代码清单14.36,get_mctrl()返回DSR一直有效,而CTS则根据UMSTATn寄存器的内容获得, set_mctrl()目前为空。
代码清单14.36 S3C2410串口驱动get_mctrl()和set_mctrl()函数
1 static unsigned int s3c24xx_serial_get_mctrl(struct uart_port *port)
2 {
3 unsigned int umstat = rd_regb(port,S3C2410_UMSTAT);
4
5 if (umstat & S3C2410_UMSTAT_CTS)//CTS信号有效(低电平)
6 return TIOCM_CAR | TIOCM_DSR | TIOCM_CTS;
7 else
8 return TIOCM_CAR | TIOCM_DSR;
9 }
10
11 static void s3c24xx_serial_set_mctrl(struct uart_port *port, unsigned int mctrl)
12 {
13
14 }
14.8总结
TTY设备驱动的主体工作围绕tty_driver这个结构体的成员函数展开,主要应实现其中的数据发送和接收流程以及tty设备线路设置接口函数。
针对串口,内核实现了串口核心层,这个层实现了串口设备通用的tty_driver。因此,串口设备驱动的主体工作从tty_driver转移到了uart_driver