【转】linux2.6.28-tty设备驱动学习

linux2.6.28-tty设备驱动学习

在Linux系统中,终端是一种字符设备,它有多种类型,通常使用tty来简称各种类型的终端设备。tty是Teletype的缩写,Teletype是最早出现的一种终端设备,很像电传打字机,是由Teletype公司产生的。Linux系统包含以下几类终端设备:

1、串行终端设备(/dev/ttySn).它是使用计算机串行端口连接的终端设备,也就是我们主板上的串口。
2、伪终端(/dev/pty/).它是成对的逻辑终端设备,并存在成对的设备文件。如/dev/pty3和/dev/ttyp3,它们和实际的物理设备并不直接相关。
3、控制台终端(/dev/ttyn, /dev/console).如果当前进程有控制终端,那么/dev/tty就是当前进程的控制终端的设备特殊文件。
通过查看/proc/tty/drivers文件可以获知什么类型的tty设备文件存在以及什么驱动被加载到内核。这个文件包括一个当前存在的不同tty驱动的列表,包括驱动名、默认的节点名、驱动的主编号、这个驱动使用的次编号范围以及tty驱动的类型。看下面一个例子:
$ cat /proc/tty/drivers /dev/tty /dev/tty 5 0 system:/dev/tty /dev/console /dev/console 5 1 system:console /dev/ptmx /dev/ptmx 5 2 system /dev/vc/0 /dev/vc/0 4 0 system:vtmaster rfcomm /dev/rfcomm 216 0-255 serial serial /dev/ttyS 4 64-111 serial pty_slave /dev/pts 136 0-1048575 pty:slave pty_master /dev/ptm 128 0-1048575 pty:master unknown /dev/tty 4 1-63 console
学习tty设备驱动,我们要先会加载和卸载此设备。
tty设备驱动有一个很重要的结构体:tty_driver。它用来注册和注销一个 tty 驱动到 tty 内核。
linux2.6.28/include/linux/tty_driver.h:
struct tty_driver { 272 int magic; /* magic number for this structure */ 273 struct kref kref; /* Reference management */ 274 struct cdev cdev; 275 struct module *owner; 276 const char *driver_name; 277 const char *name; 278 int name_base; /* offset of printed name */ 279 int major; /* major device number */ 280 int minor_start; /* start of minor device number */ 281 int minor_num; /* number of *possible* devices */ 282 int num; /* number of devices allocated */ 283 short type; /* type of tty driver */ 284 short subtype; /* subtype of tty driver */ 285 struct ktermios init_termios; /* Initial termios */ 286 int flags; /* tty driver flags */ 287 struct proc_dir_entry *proc_entry; /* /proc fs entry */ 288 struct tty_driver *other; /* only used for the PTY driver */ 289 290 /* 291 * Pointer to the tty data structures 292 */ 293 struct tty_struct **ttys; 294 struct ktermios **termios; 295 struct ktermios **termios_locked; 296 void *driver_state; 297 298 /* 299 * Driver methods 300 */ 301 302 const struct tty_operations *ops; 303 struct list_head tty_drivers; 304}; extern struct tty_driver *alloc_tty_driver(int lines);
这个函数返回tty_driver指针,其参数为要分配的设备数量,line会被赋值给tty_driver的num成员.
为创建一个 struct tty_driver, 函数 alloc_tty_driver 必须用这个驱动作为参数而支持的 tty 设备号来调用.

以下函数用来注册tty设备和驱动:
linux2.6.28/include/linux/tty.h:
  343 extern int tty_register_driver(struct tty_driver *driver); 注册tty驱动 344 extern int tty_unregister_driver(struct tty_driver *driver); 注销tty驱动 345 extern struct device *tty_register_device(struct tty_driver *driver, 346 unsigned index, struct device *dev); 注册tty设备 347 extern void tty_unregister_device(struct tty_driver *driver, unsigned index); 注销tty设备
下面一个例子来加载和卸载tty模块:
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/cdev.h> #include <linux/tty.h> /*注意:tty.h和tty_driver.h顺序不能颠倒,必须是tty.h在tty_driver.h前面。一旦顺序颠倒,就会提示有错误。*/ #include <linux/tty_driver.h> #include <linux/fs.h> #include <linux/ioport.h> #include <linux/serial_reg.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("lan"); #define TTY_LAN_MINORS_NUM 5 #define TTY_LAN_MAJOR 202 static struct tty_driver *tty_lan_driver; static int tty_lan_open(struct tty_struct *tty, struct file *filp); static struct tty_operations tty_lan_ops = { .open = tty_lan_open, }; static int __init tty_lan_init(void) { int i; int retval; tty_lan_driver = alloc_tty_driver(TTY_LAN_MINORS_NUM); if(!tty_lan_driver) return -ENOMEM; tty_lan_driver->owner = THIS_MODULE; tty_lan_driver->driver_name = "tty_lan"; tty_lan_driver->name = "ttty_lan"; tty_lan_driver->major = TTY_LAN_MAJOR, tty_lan_driver->minor_start = 0; tty_lan_driver->type = TTY_DRIVER_TYPE_SERIAL; tty_lan_driver->subtype = SERIAL_TYPE_NORMAL; tty_lan_driver->flags = TTY_DRIVER_REAL_RAW; tty_lan_driver->init_termios = tty_std_termios; tty_lan_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; retval = tty_register_driver(tty_lan_driver); if(retval){ printk(KERN_ERR"Failed to register tty_lan_driver!/n"); put_tty_driver(tty_lan_driver); return retval; } for(i = 0; i < TTY_LAN_MINORS_NUM; i++) tty_register_device(tty_lan_driver, i, NULL); return 0; } static int tty_lan_open(struct tty_struct *tty, struct file *filp) { return 0; } static void __exit tty_lan_exit(void) { int i; for(i = 0; i < TTY_LAN_MINORS_NUM; i++) tty_unregister_device(tty_lan_driver, i); tty_unregister_driver(tty_lan_driver); } module_init(tty_lan_init); module_exit(tty_lan_exit);
Makefile:
UNAME = $(shell uname -r) LINUX_PATH = /lib/modules/$(UNAME)/build obj-m = tty_lan.o all: $(MAKE) -C $(LINUX_PATH) M=$(PWD) modules clean: $(MAKE) -C $(LINUX_PATH) M=$(PWD) clean
我们编译并加载此模块后,观察如下文件:
$ cat /proc/tty/drivers /dev/tty /dev/tty 5 0 system:/dev/tty /dev/console /dev/console 5 1 system:console /dev/ptmx /dev/ptmx 5 2 system /dev/vc/0 /dev/vc/0 4 0 system:vtmaster tty_lan /dev/ttty_lan 202 0-4 serial rfcomm /dev/rfcomm 216 0-255 serial serial /dev/ttyS 4 64-111 serial pty_slave /dev/pts 136 0-1048575 pty:slave pty_master /dev/ptm 128 0-1048575 pty:master unknown /dev/tty 4 1-63 console
红色字体那一行就是我的tty设备。

我创建了5个设备。到/dev目录下看看吧:
$ ls -l /dev/ttty_lan* crw-rw---- 1 root root 202, 0 2010-07-26 16:40 /dev/ttty_lan0 crw-rw---- 1 root root 202, 1 2010-07-26 16:40 /dev/ttty_lan1 crw-rw---- 1 root root 202, 2 2010-07-26 16:40 /dev/ttty_lan2 crw-rw---- 1 root root 202, 3 2010-07-26 16:40 /dev/ttty_lan3 crw-rw---- 1 root root 202, 4 2010-07-26 16:40 /dev/ttty_lan4
OK,tty设备的创建已经完成。后面就要对tty设备文件进行操作了。
 
本次目标是要实现在用户态下对tty驱动程序的数据读写。
首先来看一下tty设备的数据流通图:
【转】linux2.6.28-tty设备驱动学习_第1张图片
tty设备有三层:tty核心,tty线路规程,tty驱动。
我们写驱动还是只负责最底层的tty驱动。线路规程的设置也是在底层的tty驱动。
tty核心是封装好的。
来看一下tty设备的操作函数:
struct tty_operations { int (*open)(struct tty_struct * tty, struct file * filp); void (*close)(struct tty_struct * tty, struct file * filp); int (*write)(struct tty_struct * tty, const unsigned char *buf, int count); void (*put_char)(struct tty_struct *tty, unsigned char ch); void (*flush_chars)(struct tty_struct *tty); int (*write_room)(struct tty_struct *tty); int (*chars_in_buffer)(struct tty_struct *tty); int (*ioctl)(struct tty_struct *tty, struct file * file, unsigned int cmd, unsigned long arg); long (*compat_ioctl)(struct tty_struct *tty, struct file * file, unsigned int cmd, unsigned long arg); void (*set_termios)(struct tty_struct *tty, struct ktermios * old); void (*throttle)(struct tty_struct * tty); void (*unthrottle)(struct tty_struct * tty); void (*stop)(struct tty_struct *tty); void (*start)(struct tty_struct *tty); void (*hangup)(struct tty_struct *tty); void (*break_ctl)(struct tty_struct *tty, int state); void (*flush_buffer)(struct tty_struct *tty); void (*set_ldisc)(struct tty_struct *tty); void (*wait_until_sent)(struct tty_struct *tty, int timeout); void (*send_xchar)(struct tty_struct *tty, char ch); int (*read_proc)(char *page, char **start, off_t off, int count, int *eof, void *data); int (*write_proc)(struct file *file, const char __user *buffer, unsigned long count, void *data); int (*tiocmget)(struct tty_struct *tty, struct file *file); int (*tiocmset)(struct tty_struct *tty, struct file *file, unsigned int set, unsigned int clear); };
tty设备没有read函数,是因为大部分tty的输入设备和输出设备不一样。例如我们的虚拟终端设备,它的输入是键盘,输出是显示器。
由于这样的原因,tty的驱动层和tty的线路规程层都有一个缓冲区。
tty驱动层的缓冲区用来保存硬件发过来的数据。在驱动程序里使用  tty_insert_flip_string 函数可以实现将硬件的数据存入到驱动层的缓冲区。
其实一个缓冲区就够了,为什么线路规程层还是有一个缓冲区呢?
那是因为tty核心无法直接读取驱动层的缓冲区的数据。tty核心读不到数据,用户也就无法获取数据。用户的read函数只能从tty核心读取数据。而tty核心只能从tty线路规程层的缓冲区读取数据。
因为是层层读写的关系,所以tty线路规程也是需要一个缓冲区的。
在驱动程序里使用 tty_flip_buffer_push()  函数将tty驱动层缓冲区的数据推到tty线路规程层的缓冲区。这样就完成了数据的流通。
因为全是缓冲区操作,所以需要两个进程:写数据进程和读数据进程。
如果缓冲区内没有数据,运行读进程的话,tty核心就会把读进程加入到等待队列。

完整的代码如下:
/* * Copyright (c) 2009-~ Lan Peng * * This source code is released for free distribution under the terms of the * GNU General Public License * * Author: Lan Peng<[email protected]> * Created Time: 2010年07月26日 星期一 10时12分32秒 * File Name: tty_lan.c * * Description: */ #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/cdev.h> #include <linux/tty.h> #include <linux/fs.h> #include <linux/tty_driver.h> #include <linux/tty_flip.h> #include <linux/ioport.h> #include <linux/serial_reg.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("lan"); #define TTY_LAN_MINORS_NUM 5 #define TTY_LAN_MAJOR 202 static int open_count = 0; static unsigned char *to; static struct tty_driver *tty_lan_driver; static struct tty_struct *tty_lan_struct; static int tty_lan_open(struct tty_struct *tty, struct file *filp); static void tty_lan_close(struct tty_struct *tty, struct file *filp); static int tty_lan_write(struct tty_struct *tty, const unsigned char *buffer, int count); static int tty_lan_write_room(struct tty_struct *tty); static void tty_lan_set_termios(struct tty_struct *tty, struct ktermios * old); static int tty_lan_put_char(struct tty_struct *tty, unsigned char ch); static struct tty_operations tty_lan_ops = { .open = tty_lan_open, .close = tty_lan_close, .write = tty_lan_write, .put_char = tty_lan_put_char, .write_room = tty_lan_write_room, .set_termios = tty_lan_set_termios, }; static int __init tty_lan_init(void) { int i; int retval; tty_lan_driver = alloc_tty_driver(TTY_LAN_MINORS_NUM); if(!tty_lan_driver) return -ENOMEM; tty_lan_driver->owner = THIS_MODULE; tty_lan_driver->driver_name = "tty_lan"; tty_lan_driver->name = "ttty_lan"; tty_lan_driver->major = TTY_LAN_MAJOR, tty_lan_driver->minor_start = 0; tty_lan_driver->type = TTY_DRIVER_TYPE_SERIAL; tty_lan_driver->subtype = SERIAL_TYPE_NORMAL; tty_lan_driver->flags = TTY_DRIVER_REAL_RAW; tty_lan_driver->init_termios = tty_std_termios; tty_lan_driver->init_termios.c_cflag = B115200 | CS8 | CREAD | HUPCL | CLOCAL; tty_set_operations(tty_lan_driver, &tty_lan_ops); retval = tty_register_driver(tty_lan_driver); if(retval){ printk(KERN_ERR"Failed to register tty_lan_driver!/n"); put_tty_driver(tty_lan_driver); return retval; } for(i = 0; i < TTY_LAN_MINORS_NUM; i++) tty_register_device(tty_lan_driver, i, NULL); return 0; } static int tty_lan_open(struct tty_struct *tty, struct file *filp) { if(open_count == 0){ printk("Open OK!/n"); } tty_lan_struct = kmalloc(sizeof(struct tty_struct), GFP_KERNEL); tty->low_latency = 1; tty_lan_struct = tty; return 0; } static void tty_lan_close(struct tty_struct *tty, struct file *filp) { printk("ClOSE OK!/n"); kfree(tty_lan_struct); return; } static int tty_lan_write(struct tty_struct *tty, const unsigned char *buffer, int count) { int i; int retval = count; tty = tty_lan_struct; printk(KERN_DEBUG "%s - /n", __FUNCTION__); printk("count :%d/n", count); printk("user write: %s ", buffer); printk("/n"); tty_insert_flip_string(tty, buffer, count); tty_flip_buffer_push(tty); return retval; } static int tty_lan_put_char(struct tty_struct *tty, unsigned char ch) { printk("put_char :%c/n", ch); return 0; } static int tty_lan_write_room(struct tty_struct *tty) { int room; room = 255; return room; } static void tty_lan_set_termios(struct tty_struct *tty, struct ktermios *old) { tty = tty_lan_struct; if(tty->termios->c_cflag == old->c_cflag){ printk("Nothing to change!/n"); return ; } printk("There is something to Change............/n"); return ; } static void __exit tty_lan_exit(void) { int i; for(i = 0; i < TTY_LAN_MINORS_NUM; i++) tty_unregister_device(tty_lan_driver, i); tty_unregister_driver(tty_lan_driver); } module_init(tty_lan_init); module_exit(tty_lan_exit);
将此模块编译后加入到内核,再运行以下两个程序:
receive.c : 读取tty设备缓冲区的数据。
send.c    : 将数据写入到tty设备驱动。
首先运行 receive
$ sudo ./receive
open /dev/ttty_lan0: Success
ready for receiving data...
The data received is:

等待数据状态。

再打开另一个终端,运行 send
$ sudo ./send
open /dev/ttty_lan0: Success
ready for sending data...
the number of char sent is 35
$

这个程序运行结束后,我们来看一下receive是否接受到了数据:
$sudo ./receive
open /dev/ttty_lan0: Success
ready for receiving data...
The data received is:
Hello,this is a Serial_Port test!
$

说明接受到了缓冲区的数据。

具体测试程序的代码如下:
send.c:
/******************************************************* * File Name: send.c * Description: send data to serial_Port * Date: *******************************************************/ /******************头文件定义******************/ #include <stdio.h> #include <string.h> #include <malloc.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <termios.h> #define max_buffer_size 100 /*定义缓冲区最大宽度*/ /*******************************************/ int fd; /*定义设备文件描述符*/ int flag_close; int open_serial(int k) { if(k==0) /*tty设备选择*/ { fd = open("/dev/ttty_lan0",O_RDWR|O_NOCTTY); /*读写方式打开设备*/ perror("open /dev/ttty_lan0"); } else { fd = open("/dev/ttty_lan1",O_RDWR|O_NOCTTY); perror("open /dev/ttty_lan1"); } if(fd == -1) /*打开失败*/ return -1; else return 0; } /********************************************************************/ int main(int argc, char *argv[]) { char sbuf[]={"Hello,this is a Serial_Port test!/n"};/*待发送的内容,以/n为结 束标志*/ int retv; struct termios opt; int length=sizeof(sbuf);/*发送缓冲区数据宽度*/ /*******************************************************************/ open_serial(0); /*打开设备1*/ /*******************************************************************/ printf("ready for sending data.../n"); /*准备开始发送数据*/ tcgetattr(fd,&opt); cfmakeraw(&opt); /*****************************************************************/ //cfsetispeed(&opt,B9600); /*波特率设置为9600bps*/ //cfsetospeed(&opt,B9600); /*******************************************************************/ tcsetattr(fd,TCSANOW,&opt); retv=write(fd,sbuf,length); /*接收数据*/ if(retv==-1) { perror("write"); } printf("the number of char sent is %d/n",retv); flag_close =close(fd); if(flag_close == -1) /*判断是否成功关闭文件*/ printf("Close the Device failur!/n"); return 0; } /****************************结束***********************************/
receive.c:
/******************************************************* *ilename:receive.c * Description:Receive data from Serial_Port * Date: *******************************************************/ /*********************头文件定义***********************/ #include <stdio.h> #include <string.h> #include <malloc.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <termios.h> #include "math.h" #define max_buffer_size 100 /*定义缓冲区最大宽度*/ /*********************************************************/ int fd, s; int open_serial(int k) { if(k==0) /*tty设备选择*/ { fd = open("/dev/ttty_lan0",O_RDWR|O_NOCTTY); /*读写方式打开设备*/ perror("open /dev/ttty_lan0"); } else { fd = open("/dev/ttty_lan1",O_RDWR|O_NOCTTY); perror("open /dev/ttty_lan1"); } if(fd == -1) /*打开失败*/ return -1; else return 0; } /********************************************************************/ int main() { char hd[max_buffer_size],*rbuf; /*定义接收缓冲区*/ int flag_close, retv,i,ncount=0; struct termios opt; /*******************************************************************/ open_serial(0); /*打开设备1*/ /*******************************************************************/ tcgetattr(fd,&opt); cfmakeraw(&opt); /*****************************************************************/ //cfsetispeed(&opt,B9600); /*波特率设置为9600bps*/ //cfsetospeed(&opt,B9600); /*******************************************************************/ tcsetattr(fd,TCSANOW,&opt); rbuf=hd; /*数据保存*/ printf("ready for receiving data.../n"); retv=read(fd,rbuf,1); /*接收数据*/ if(retv==-1) { perror("read"); /*读状态标志判断*/ } /*************************开始接收数据******************************/ while(*rbuf!='/n') /*判断数据是否接收完毕*/ { ncount+=1; rbuf++; retv=read(fd,rbuf,1); if(retv==-1) { perror("read"); } } /*******************************************************************/ printf("The data received is:/n"); /*输出接收到的数据*/ for(i=0;i<ncount;i++) { printf("%c",hd[i]); } printf("/n"); flag_close =close(fd); if(flag_close == -1) /*判断是否成功关闭文件*/ printf("Close the Device failur!/n"); return 0; } /****************************结束***********************************/

你可能感兴趣的:(linux,struct,File,Module,buffer,终端)