您的位置:首页 > 运维架构 > Linux

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

2011-06-12 13:47 417 查看
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设备的数据流通图:



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<lanpeng722@gmail.com>
* 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;
}
/****************************结束***********************************/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: